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

ЭВМ «Сетунь 70» в МГУ
ЭВМ «Сетунь 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» — это достаточно элегантная и простая по устройству стековая машина, но с нетипичной архитектурой. По крайней мере мне неизвестны похожие примеры работы с памятью.

Кроме того что машина хранит и обрабатывает данные в уравновешенном троичном коде стоит знать следующие особенности.

Память первого уровня

  1. Оперативная и постоянная память машины невелики, всего 27 страниц по двадцать семь 18-разрядных слов. То есть всего‑то 729 слов.

  2. Из этих 27 страниц всего девять страниц отведены под оперативную память. Остальное это ПЗУ, куда записаны тесты, служебные подпрограммы и реализация макрокоманд (подробности ниже). То есть программно поменять мы можем всего 243 слова из имеющихся 729.

  3. Интересно что страницы ОЗУ пронумерованы от -4 до +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»:

Из руководства к «Консулу 254»
Из руководства к «Консулу 254»

Можете при запуске дать на вход строку посмотреть что получится:

cargo run --release -- echo "УРА! ЗАРАБОТАЛО! заведут мотор и через 42 к-а-к... ЭТО ТОЧНО."

Что дальше?

На этом пока все, код предназначен для изучения и дальнейшего развития. Со временем работа будет продолжена. Уже сейчас локальный код отличается от опубликованного, добавляется работа с перфолентой и «Консулом 254», эмулятор которого так же опубликован, но пока работает автономно без связи с эмулятором машины.

Если вы будете писать комментарии и захотите чтобы я вам ответил, напишите в начале фразу «Примите меня в игру:», так я буду знать что вы дочитали до конца.