Слышали о семействе советских троичных ЭВМ «Сетунь»? А в политехническом музее бывали, машины видели? Нет? Да? Тогда вот вам эмулятор «Сетуни 70», чтобы посмотреть что к чему, разобраться и написать для нее свою программу.

Введение
Существующие публикации по теме семейства троичных ЭВМ «Сетунь» не отличаются информативностью и точностью, в целом дают только общее представление и по сути просто сообщают о том что такие машины были, не предоставляя никаких технических деталей. Что достаточно странно, поскольку в советские времена и в наше время авторами, конструкторами и пользователями «Сетуни» было напечатано достаточно много публикаций, издано с десяток книг. Были даже учебные материалы по ее программированию и применению этой машины для самых разных целей — от вычислений и обработки научных материалов до интерпретирующих программ и даже среды обучения программированию.
Есть весьма любопытные статьи, например, о реализации системы команд «Сетуни 70» на предыдущей модели «Сетуни» — 1959 года. Вам не слабо на машине всего с 81 восемнадцатиразрядным словом оперативной памяти, без ПЗУ и ОС, написать эмулятор системы команд куда более сложной и развитой архитектуры, в которой даже просто памяти почти в 20 раз больше? А они — справились! Причем без всяких многомегабайтовых прошивок, хитроумных сред программирования и невесть что оптимизирующих компиляторов.
Краткая предыстория появления эмулятора
Работа над эмулятором «Сетуни 70» идет давно: началась еще в 2001-м году, и последовательно велась на разных языка программирования с разным успехом из‑за ограничений в знаниях архитектуры эмулируемой машины; недостатков, ограничений и синтаксиса языков. Некоторые из ограничений обходились легко, некоторые поддавались не просто. Скажем, в большинстве языков отсутствуют массивы с отрицательными индексами, из‑за чего приходилось использовать смещения и городить проверки, в результате плодя ошибки и теряя львиную часть времени работы эмулятора впустую, попутно постоянно переводя смещения в адреса и обратно.
Зачем там отрицательные индексы? Машина ведь использует уравновешенную (симметричную) троичную систему счисления с цифрами {-1, 0, +1). Один разряд здесь называется трит. И хочешь не хочешь, таким способом представлены и выражены все параметры машины. Да вот хоть бы номера страниц памяти и номера слов в них пронумерованы от -13 до +13, а номера коротких ячеек от -40 до +40. Значение в 6-тритном трайте (это так называемый слог 18-разрядного троичного слова) представлено в диапазоне от -384 до +384. Ну а диапазон значений 18-разрядного троичного слова посчитайте сами.
Последовательность реализации и набор языков были следующими:
Ява,
Паскаль,
Си,
Си++
Ассемблер,
Хаскель,
Эльм,
Идрис,
Снова Си++,
И, наконец, дело пришло к Раст вместе с ИИ.
Одним из критериев было стремление уложиться в 2 тысячи строк кода, однако эта цель так ни разу и не была достигнута. В разное время выбор конкретного языка был продиктован желанием опробовать возможности языка в этой области. Самыми удобными в реализации оказались как ни странно Хаскель и Эльм, а самым простым и наглядным оказался Ассемблер.
Не подумайте что все это время я с утра до вечера только этим и занимался. Нет, у меня и другие дела есть, а к этой задаче я возвращался время от времени с перерывами в несколько месяцев и даже лет по мере пополнения знаний об архитектуре.
Трудности эмуляции недвоичных машин
Кроме синтаксиса, индексов и производительности есть еще хитроумная и не всеми осознаваемая логическая ловушка под названием «Эффективность представления» — своеобразная когнитивная деформация проявляющаяся у всех, кто считает двоичную систему счисления главной и «эталонной», пытаясь через нее выразить все остальные, не дав себе труда подумать об иных вариантах.
Если вы интересовались вопросом представления чисел в уравновешенной троичной системе счисления, то скорее всего знаете про два наиболее часто встречающихся варианта: «пара бит на один трит» и «знаковые слои» (что в принципе сводится к той же паре бит на один трит, но уже при иной организации). Думаю не ошибусь, сказав что все, кто задумывался об этом вопросе, рано или поздно к ним приходили. Можно легко впасть в ошибку и думать, — «Это же на 50% менее эффективное представление чем прямой двоичное!». Кажется что как ни крути, все остальные варианты еще менее эффективны. Но это ведь только если не думать об отрицательных числах (+1 разряд или еще больше), устойчивости к ошибкам (ну тут может быть и 1:1 с троичным), об округлении и обо всем остальном.
А если подумать, то оказывается что... больше бит на троичный разряд это вовсе не недостаток, а правильное и неизбежное направление. Дам несколько подсказок, а решение которое устроило по всем критериям, будет описано дальше.
Подсказка 1. Думать надо не о том как как хранить каждый разряд, а о том, зачем вам надо его хранить. Если посмотрите на программы, обрабатывающие какие‑нибудь данные, то можно заметить что они как ни странно всего‑то и делают что читают, модифицируют и куда‑то записывают (заменяя или дополняя исходные) числа в заранее определенном диапазоне. Причем чтобы вы ни делали, вы никогда не сможете изменить заданного архитектурой машины диапазона или способа представления числа.
Подсказка 2. Сколько бы раз вы ни считали 2+2, то в любой целочисленной системе вы гарантированно получите 4. Каждый раз. Единственное полезное что вы можете получить в результате непрестанного счета 2+2 — поймать сбой питания, памяти, процессора или шины. Только зачем?
Подсказка 3. Считав откуда‑то байт и сложив его с другим байтом, вы через какое‑то время легко получите результат по значению превосходящий 8 двоичных разрядов, а записать его на место одного байта вы уже не сможете — либо потеряете часть данных, либо потребуется записать второй байт еще куда‑то. Половину или полтора байта на место одного не запишете, он не никуда подвинется, не расширится и не сузится.
Подсказка 4. К сожалению, современный нам окружающий цифровой мир почти полностью двоичный, и в нашем случае всегда потребуется переводить двоичные цифры в троичные и наоборот.
Подсказка 5. Если вы думаете что один объявленный байт в структуре вашего кода после компиляции так и остается одним байтом в памяти при выполнении программы, то вы живете в мире фантазий — на самом деле один байт превращается в памяти в десятки, а иногда и в сотни байт.
Подсказка 6. Результат надо показывать человеку в каком‑то понятном ему виде, часто это цифры или буквы. Результат работы любой программы всегда предназначен для человека. Возможно только в конце длинной цепочки, но происходит и об этом очень важно помнить.
Краткое описание архитектуры машины
ЭВМ «Сетунь 70» — это достаточно элегантная и простая по устройству стековая машина, но с нетипичной архитектурой. По крайней мере мне неизвестны похожие примеры работы с памятью.
Кроме того что машина хранит и обрабатывает данные в уравновешенном троичном коде стоит знать следующие особенности.
Память первого уровня
Оперативная и постоянная память машины невелики, всего 27 страниц по двадцать семь 18-разрядных слов. То есть всего‑то 729 слов.
Из этих 27 страниц всего девять страниц отведены под оперативную память. Остальное это ПЗУ, куда записаны тесты, служебные подпрограммы и реализация макрокоманд (подробности ниже). То есть программно поменять мы можем всего 243 слова из имеющихся 729.
Интересно что страницы ОЗУ пронумерованы от -4 до +4, то есть «сгруппированы» вокруг нуля, и сверху и снизу «окружены» страницами ПЗУ.
Одна из страниц ОЗУ отведена под стек процессора.
И это вся память. Не спешите падать духом и махать руками, лучше рассматривайте это как аналог кеш‑памяти более‑менее современного процессора, чем оно в принципе по современным представлениям и является.
В исходной архитектуре «Сетуни 70» уже заложена возможность увеличения числа страниц таким образом, что не придется переписывать старые программы. В наборе регистров есть три так называемых «регистра приписки» (H1, H2, H3), которые всегда указывают при выборке данных из страницы оперативной памяти в стек. Вместе с увеличением их разрядности увеличится и число адресуемых страниц.
Память второго уровня
Кроме оперативной памяти к машине может быть подключено два магнитных барабана, каждый хранит 243 страницы по 81 трайту, то есть емкостью около 19,6 мегатрайт (здесь «мега» надо читать по‑человечески, по основанию 10). Вот ее‑то и использует машина в качестве оперативной. В системе команд машины есть команды обмена целыми страницами памяти первого уровня с магнитным барабаном. Что, кстати, для машин того времени это более‑менее обычный подход.
Система прерываний
Среди прочего у машины есть схема определение ситуаций исчерпания и переполнения страниц памяти, по наступлению которых сохраняется контекст и вызывается подпрограмма обработки из ПЗУ. Причем в разных режимах вызываются разные обработчики. Например, если при последовательном выполнении кода пользовательской программы вы достигли конца страницы и произошла попытка считать данные за пределами страницы, то произойдет вызов обработчика из ПЗУ. Если в режиме макроопераций произойдет попытка выполнения еще одной макрооперации, то снова произойдет прерывание с переходом к обработчику.
Система команд
В составе системы команд есть так называемый «слог‑ссылка» и еще 81 другая команда. Двадцать семь из них это макрокоманды которые реализуются программно, хранятся и вызываются из ПЗУ.
Слог‑ссылка
Это операция копирования содержимого слова или части слова (6-тритных слогов) из страницы памяти в стек данных. Своего рода аналог команды PUSH какого‑нибудь современного процессора.
Базовые команды
Набор из 27 команд работы с данными и стеком — арифметика, вращения, пересылка и так далее.
Системные команды
Включает в себя 27 команд загрузки данных в некоторые управляющие регистры, обращение к внешним устройствам и другие.
Макрооперации или макрокоманды
Тоже 27 штук. Собственно, их реализация была записана в сменных страницах ПЗУ машины и выполнялась процессором при переходе в особый макро‑режим. Предполагалось что при необходимости их можно сменить заменой плат соответствующих страниц памяти в машине и получить новый проблемно‑ориентированный набор для выполнения специализированной задачи.
Прочая периферия
Кроме магнитного барабана к «Сетуни 70» были подключены:
считыватель перфолент;
ленточный перфоратор (для подготовки новых перфолент);
электрифицированная пишущая программно‑управляемая машинка «Консул 254», выполнявшая роли клавиатуры и принтера;
через адаптер‑приставку «Сетунь 72» подключались 27 выносных клавишных терминалов с индикаторами для приема зачетов и экзаменов;
в одной или двух статьях упоминалось подключение к «Сетуни 70» цветного телевизора для проведения психофизического эксперимента.
Ну и была конечно же всемогущая консоль управления с лампочками-индикаторами и часами, с помощью которой можно в любой момент остановить любую программу, посмотреть и изменить значения чисел, хранящихся в оперативной памяти или регистрах, и запустить программу дальше.
И как это работало?
Кажется что загружаемая программа, кроме самых простейших, выполняла роль диспетчера последовательности пересылки данных и кода программы с магнитного барабана в память и обратно, вызова системных обработчиков событий и сервисов операционной системы (ага, она там тоже была! — так и называлась «Операционная Система»).
А ведь на «Сетуни 70» были очень серьезные длинные программы (средства разработки, текстовый редактор, ассемблер, средства поддержки психофизического эксперимента, автоматизированная система обучения «Наставник» — обучала студентов МГУ иностранному языку и языкам программирования, принимала у них зачеты и экзамены), даже игры и все работало.
Собственно, написание программ в кодах процессора и работа напрямую с аппаратурой не предполагались. Вместо этого использовалась ранее написанная интерпретирующая программа, команды ОС или компилятор языка высокого уровня. Но если очень хочется, то можно было писать программы и на ассемблере, которых было даже несколько штук.
Особенности реализации архитектуры машины «Сетунь 70»
Вернемся к эмулятору. Мой эмулятор написан для запуска в Ubuntu и реализует архитектуру второй модели семейства «Сетунь» — «Сетунь 70». Как я уже отмечал выше, она значительно сложнее, более гибкая и производительнее чем предыдущая модель «Сетуни» 1959 года.
Хочу обратить ваше внимание на тот факт, что под одним названием «Сетунь 70» существовали две заметно различающихся архитектуры машины:
ранняя, исходная или базовая 1970-го года (оттуда и 70 в названии) с одним стеком (для данных),
переработанная модификация примерно 1975-го года (поддержка идеи структурированного программирования).
Вторая отличается измененными режимами работы; появлением второго стека для хранения адресов возврата; несколькими замененными командами для поддержки структурированного программирования. В результате машина стала удобнее в работе и программировании.
Об этом важно знать и помнить, так как программы предназначенные для работы на одной архитектуре скорее всего не будут работать на другой. Возможно за исключением самых простейших (умещающихся в одну страницу памяти и самодостаточных).
Мой эмулятор на данный момент реализует только раннюю модификацию, так как она чуть проще в понимании, освоении и реализации, чем поздняя модифицированная. Но в будущем конечно добавится и вторая.
Библиотека «Аналемма»
Собственно, библиотека «Аналемма» это то самое упомянутое мной выше решение, которое удовлетворяет всем критериям, что были приведены выше как подсказки. Она позволяет снять или снизить проблемы представления чисел и падения производительности.
Почему аналемма? Потому что аналемма.
В определенных условиях аналемма бывает дискретна и симметрична.
«Аналемма» дает возможность хранить целые уравновешенные троичные числа в удобной для работы форме, производить с ними арифметические операции и преобразования представления без почти потери времени. На данный момент поддерживаются 3-, 6-, 9- и 18-разрядные числа.
Смысл достаточно прост и очевиден. Вместо того чтобы хранить в массиве какое‑то представление значения самого троичного числа, тратить время на его поиск, выравнивание, преобразование, вычисление и обработку и так далее мы поступаем следующим образом:
Создаем структуру‑описатель числа для всех чисел заданной разрядности (по умолчанию это 729 для 6 разрядов). В каждом описателе есть поля для представлений, которые требовались при подготовке и тестировании эмулятора:
двоичное число (для обращения к внешнему миру из троичной программы),
двоично‑кодированное троичное представление целиком (сейчас это «пара бит на трит»),
массив троичных разрядов по‑отдельности,
номер первого ненулевого трита (по его знаку определяется знак всего числа, а если этот номер равен минус единице, значит это число — 0),
признак четности числа,
уравновешенное девятеричное представление числа с алфавитом {W, X,Y, Z, 0, 1,2, 3, 4},
строковое представление троичного числа с алфавитом {N,Z,P},
строковое девятеричное представление числа,
строковое десятичное представление числа,
строковое шестнадцатеричное представление числа.
Сама таблица создается один раз при сборке проекта. Вместо вычисления или хранения такого набора для каждого числа в массиве памяти или регистре мы просто храним там ссылку на экземпляр числа. Как это делать решит компилятор.
Да, каждое такое число в результате занимает немало места в памяти, но оно делает это всего один раз, а мы ссылаемся на него одним обычным двоичным адресом.
Кроме того мы не будем каждый раз вычислять значения арифметических операций, они тоже вычисляются при сборке и просто выбираются из подготовленной таблицы, если библиотека собрана для 3-, 6- и 9-разрядных трайтов. Если для 18-разрядов, то часть работы выполняется в процессе выполнения — иначе вместо скромных 4 мегабайт ссылок нам потребуется таблица в 17 гигабайт.
Библиотека сейчас хранит числа по принципу «достаточно просто для понимания» и при необходимости может быть оптимизирована для более компактного или более удобного представления. Может быть реализована аппаратно в качестве интерфейса или периферийного устройства.
В дальнейших версиях планируется поддержать составное представление 3, 6 и 9 разрядов таким образом чтобы их можно было сочетать — и вместо только 6-тритрых чисел в массиве какие‑то будут 3- или 9-тритные, или и те и те вместе. А при необходимости будут переменно‑разрядные пары в 12 и 15 разрядов, вместо одно числа.
Уровень конфигурации архитектуры конкретной модели
Чтобы была возможность изменить архитектуру предусмотрел слой конфигурации, в которой задаются признаки команд и их мнемоники, расширяющие и дополняющие базовый набор структур «Аналеммы». Кроме того задается набор символов и управляющих кодов для устройства символьного ввода‑вывода («Консул 254»). С помощью слоя конфигурации можно будет переключаться с исходной на модифицированную архитектуру машины, когда ее реализация будет добавлена.
Ограничения эмулятора
Это одна из первых версий кода эмулятора, который уже начал более‑менее работать, пусть и в тепличных условиях. Он заведомо не полный, однако в общих чертах соответствует имеющемуся описанию архитектуры.
В реализации есть определенные допущения и упрощения, связанные с неполнотой описания архитектуры в области работы с периферийными устройствами, отсутствием стартовой прошивки и программ для проверки.
Пока не эмулируется фронтальная панель, ее индикаторы и пульт управления.
Можно сказать что это только начало. Но это очень хорошее начало.
Слушай, где эмулятор, а?
Да, если вам уже надоело читать, то берите код, собирайте и запускайте. Можете даже на своем Raspberry Pi, Gamestick или подходящем по характеристикам микроконтроллере запустить.
Обратите внимание что эмулятор использует библиотеку, поэтому библиотека и эмулятор должны лежать на одном уровне рядом друг с другом в соседних папках.
1.Аналемма
2.Эмулятор
Соберите и протестируйте в Ubuntu сначала «Аналемму» командами:
cargo build --release && cargo test
Если все собралось и тесты прошли успешно, повторите их для эмулятора. Если при сборке эмулятора увидите ошибку что «Аналемма» не найдена, скорректируйте путь или просто скопируйте код эмулятора в папку по соседству с «Аналеммой» и повторите сборку.
Запуск эмулятора
Если сборка прошла нормально, выполните команду:
cargo run --release
В результате вы должны увидеть строку:
Я СЕТУНЬ 70
После чего эмулятор заканчивает работу. Чтобы сделать что‑нибудь «полезное» запустите командой:
cargo run --release -- echo
И теперь сможете «поработать» на машине — она принимает ввод с клавиатуры, фильтрует символы и игнорирует те, которых нет в кодировке машины, а те что есть выводит обратно в терминал. Например, в кодировке есть только заглавные буквы кириллического и латинского алфавитов. И даже более того - коды символов, которые по начертанию совпадают в обоих алфавитах (А, В, Е, К, Н, О, С, Т, и т.д.), есть только в кириллическом варианте, воспроизводя таким образом особенность литероносителя и клавиатуры «Консула 254». Чтобы стало понятнее, вот раскладка клавиатуры «Консула 254»:

Можете при запуске дать на вход строку посмотреть что получится:
cargo run --release -- echo "УРА! ЗАРАБОТАЛО! заведут мотор и через 42 к-а-к... ЭТО ТОЧНО."
Что дальше?
На этом пока все, код предназначен для изучения и дальнейшего развития. Со временем работа будет продолжена. Уже сейчас локальный код отличается от опубликованного, добавляется работа с перфолентой и «Консулом 254», эмулятор которого так же опубликован, но пока работает автономно без связи с эмулятором машины.
Если вы будете писать комментарии и захотите чтобы я вам ответил, напишите в начале фразу «Примите меня в игру:», так я буду знать что вы дочитали до конца.

