Архитектура и программирование микрокалькулятора HP-41
/ HP-41 Advanced Programming Tips /
Как многие знают, в конце 1980-х в СССР были весьма популярны программируемые микрокалькуляторы, совместимые с Б3-34: МК-54, МК-61, МК-52. Для них создавали программы, игры, исследовали недокументированные возможности, писали статьи. Я и сам через это прошёл в своё время. И вот недавно задумался: а ведь в США тоже должно было быть что-то подобное, близкое по духу именно ко всему тому, что происходило вокруг наших программируемых калькуляторов. И да — я оказался прав. Встречайте: HP-41.
Как и Б3-34, HP-41 — это программируемый RPN калькулятор (RPN — обратная польская запись, вычисления в форме «2 2 +», а не «2 + 2 =» ) с похожей идеологией, но значительно более функциональный. Появился он практически в то же время, что и наш Б3-34 — 1979 год и вскоре стал культовым: для него написано множество программ, книги — в том числе, о недокументированных возможностях, и даже до сих пор выпускаются модули расширения. Всего было выпущено полтора миллиона этих калькуляторов.
К схожей судьбе можно добавить что, как наш МК-52 летал на «Союзах» в качестве резервного вычислительного устройства, так же и HP-41 летал на «Шаттлах».
Хотя существует три модификации HP-41 (C, CV, CX), их можно считать полностью совместимыми, так как отличаются они очень незначительно — по сути, только объёмом памяти. Калькуляторы HP с другими номерами несовместимы с HP-41, хотя и имеют некоторые общие черты.
Одной из особенностей HP-41 является достаточно редкий для калькуляторов индикатор — 14-сегментный. Это позволяет отображать на HP-41 буквы и различные символы что, наряду со звуком и модулями расширения, является большим преимуществом перед Б3-34.
Память HP-41C с точки зрения пользователя — 63 регистра, по 7 байт каждый. При этом можно выбирать, сколько используется под программу и сколько под данные. Модули расширения увеличивают доступный объём памяти — скажем, 82106A — это ещё 64 регистра. Максимум при помощи таких модулей можно получить порядка 2кб, если занять все четыре слота.
Процессор — свой, специфический. Чаще всего его называют NUT CPU, хотя это обобщённое название нескольких разных процессоров. Тактовая частота 0.35 мГц. Что касается разрядности то, как это нередко бывает с калькуляторами, в силу специфики архитектуры сложно назвать точную цифру.
Помимо модулей памяти (для HP-41C) существует много других модулей расширения — библиотеки программ на ПЗУ, устройство записи/чтения на магнитных картах, считыватель штрихкодов и пр.
Наряду с традиционным, для всех калькуляторов, командным режимом, когда вычисления выполняются непосредственно, существуют три способа программировать HP-41.
Первый и основной из них — штатный язык программируемого калькулятора. Идеологически он похож на язык Б3-34 и, хотя и называется FOCAL, к одноимённому языку программирования отношения не имеет — расшифровывается слово как «Forty One Calculator Language». Команды FOCAL это, по сути, вызовы подпрограмм в машинных кодах — что-то вроде инструкций виртуальной машины, заточенной под вычисления, десятичную систему и плавающую точку.
Второй, весьма популярный, способ называется Synthetic programming и является набором недокументированных расширений FOCAL, основанных на использовании уязвимости в прошивке калькулятора, позволяющей создавать новые команды.
Третий, редкий способ — программирование непосредственно в машинных кодах микропроцессора калькулятора, называемых MCODE. Этот способ довольно сложен по причинам, о которых будет сказано ниже.
В данной статье я буду рассказывать преимущественно о первом способе, остальных двух коснусь лишь вкратце.
Несмотря на продвинутый алфавитно-цифровой индикатор, клавиатура у калькулятора самая обычная. Что, при огромном числе различных режимов и функций, делает ввод программы и операции с ней весьма утомительным занятием (вполне сопоставимым с таковым для Б3-34 совместимых калькуляторов).
Каждая клавиша имеет три функции (в отдельных случаях — больше). Скажем, кнопка «0», помимо цифры 0, предназначена для ввода пробела и числа Пи.
Не все функции доступны через комбинации кнопок — некоторые требуется набирать по буквам, в режиме «ALPHA». Буквы подписаны над кнопками в порядке «ABCDEF…».
Надо сказать, что клавиатура сделана очень качественно. Использован типовой для HP железок приём — кнопка имеет ось в нижней части и при нажатии поворачивается вокруг неё. Индикатор, несмотря на отсутствие подсветки, также вполне читаемый. Напрягает только медленное обновление изображения (что связано, скорее всего, с медленным процессором).
Интересно, что операторы доступные через комбинацию кнопок можно вводить и побуквенно. К примеру, звуковой сигнал («BEEP») получается нажатием SHIFT 4, но можно нажать кнопку XEQ, затем ALPHA, набрать слово «BEEP» по буквам, снова нажать ALPHA.
Собственно, XEQ (от слова «execute») позволяет сразу выполнить любую встроенную функцию или вызвать имеющуюся в ОЗУ или ПЗУ — в том числе, в модуле расширения.
Список всех фактически доступных в калькуляторе функций можно получить через SHIFT CATALOG 3 (управление просмотром через R/S, SST, BST)
Ниже речь идёт о регистрах, непосредственно доступных пользователю калькулятора. Это не регистры микропроцессора!
Регистр ALPHA (A) — может хранить до 24 символов и его содержимое отображается на экране.
0,1,2,3,… — регистры данных, могут хранить или одно число или до 6 символов (или до 7 шагов программы)
X,Y,Z,T — стековые регистры (на самом деле тоже регистры данных, но организованы в виде стека). X — верхний.
L — сюда сохраняется последнее, перед изменением, содержимое регистра X
PC — текущий шаг программы
На дисплее обычно отображается содержимое X или ALPHA регистра, но можно вывести и другие.
Если просто набрать число на клавиатуре, оно попадает в X регистр (соответственно, он и отображается на экране).
Если набрать на клавиатуре строку символов (после нажатия кнопки ALPHA), то строка помещается в ALPHA регистр (аналогично, он и отображается на экране).
Однако, само нахождение на экране какой-либо информации не означает, что она находится в регистре. Это касается, к примеру, сообщений об ошибках и результатов VIEW. Они как бы закрывают сверху отображаемый регистр. В таких случаях, чтобы убрать сообщение не меняя содержимого регистров, используется клавиша “<-“.
В случае же, когда на экране отображено содержимое регистра, та же клавиша “<-” удаляет его содержимое.
Нажатие ENTER проталкивает копию числа в стек. Т.е., если набрать 1 ENTER то 1 окажется и в регистре X и в регистре Y. Если затем набрать 2, то в регистре X будет 2, в регистре Y будет 1.
CLX очищает X, CLA очищает ALPHA
X<>Y меняет местами содержимое X и Y
+, -, *, / совершают операцию над содержимым X и Y и помещают результат в X, при этом то, что было в Y регистре — теряется, а то, что было в X — помещается в регистр L (при необходимости может быть скопировано обратно в X командной LASTX).
RCL номер_регистра — копирует содержимое регистра данных с указанным номером в X (т.е., отображает его)
ARCL номер_регистра — присоединяет содержимое регистра данных с указанным номером к регистру ALPHA
ASHF сдвигает содержимое регистра ALPHA влево на 6 символов (первые 6 символов теряются).
Просмотреть содержимое регистра можно и не помещая его в отображаемый X. Для этого используется команда VIEW (для просмотра регистров стека) и AVIEW (для просмотра ALPHA регистра)
Тут надо заметить, что других операций со строками в системе команд нет. И это не случайно — дело в том, что памяти настолько мало, что работа со строками, даже с модулем расширения памяти, не имеет особого смысла. Впрочем, существуют модули расширения, где операции со строками реализованы.
STO номер_регистра — копирует содержимое регистра X в указанный регистр данных
ASTO номер_регистра — копирует содержимое регистра ALPHA (только первые 6 символов!) в указанный регистр данных
Чтобы RCL и STO работали с именованными регистрами стека, нужно добавлять “.”:STO .Z
Команда SIZE устанавливает количество регистров данных, которые можно использовать (соответственно, при этом увеличивается или уменьшается количество доступных шагов программы):
Чем меньше SIZE, тем больше места под код.
Чтобы очистить всю память, нужно включить калькулятор, удерживая кнопку “<-” и после включения сразу отпустить её. Должно появится сообщение «MEMORYLOST» (срабатывает не очень стабильно).
Переход в режим программирования (и обратно) — по клавише PRGM. В отсутствие программы отображается «00 REG nn». Число nn показывает количество регистров, доступных для шагов программы (см. выше про SIZE). По мере набора программы калькулятор иногда пишет PACKING, пытаясь уплотнить код. Если памяти для очередной команды не хватает, пишет TRY AGAIN.
При вводе программы слева показывается текущий шаг. Один шаг — одна команда (неважно, введённая одной клавишей или побуквенно). Но надо учитывать, что один шаг может занимать разный объём памяти — мало, если это простая команда типа CLA, и много, если это, скажем, длинная текстовая строка.
Перемещение по шагам — SST (вперёд) и BST (назад). Удаление текущего шага — “<-“.
Программа запускается из командного режима (т.е. надо снова нажать PRGM) клавишей R/S. Ей же и останавливается.
В программном режиме доступны практически все функции имеющиеся в командном. Ввод команд, которые отмечены на клавишах, производится простым нажатием. Остальные команды вводятся через XEQ. Например, чтобы ввести TONE 3 надо нажать XEQ потом нажать ALPHA потом набрать побуквенно TONE снова нажать ALPHA и затем нажать 3.
Стирание программы: CLP метка (стирается от метки до END)
Переход на конкретный шаг: GTO.002 (предварительно надо выйти из программного режима).
Переход на начало: SHIFT RTN
Узнать текущее положение из командного режима можно, нажав и удерживая клавишу R/S либо SST
Метки, на которые потом можно осуществить переход, задаются через «LBL метка» и бывают двух типов — глобальные (имена текстовые, вводятся в режиме ALPHA) и локальные (имена цифровые, либо однобуквенные текстовые). Цифровые занимают меньше памяти.
Переход на метку «GTP метка»
Полезно всегда ставить метку в первый шаг программы. Это позволяет запускать её, не переходя каждый раз в начало — через XEQ метка или GTO метка.
Есть также косвенный переход GTO IND (фанаты HP-41 приводят это как свидетельство того, что машина Turing complete ;).
В конце программы вводится GTO… (при этом появляется сообщение PACKING). В этом месте на экране появляется END
Например, программа по умножению любого числа на 2 выглядит так:
LBL "PRGNAME"
2
*
END
Работа с подпрограммами (допускается вложенность до шести):
XEQ 04
...
LBL 04
...подпрограмма...
RTN
Условные переходы:
X=Y?
2
1
В этом примере если X равно Y, то в стек (регистр X) помещается 2. В противном случае 1
Другими словами, если условие выполняется, то команда следующая после проверки — пропускается.
Циклы
ISG — Increment and Skip if Greater
DSE — Decrement and Skip if Equal to or less than
Пример
1.00301
STO 01
LBL 01
BEEP
ISG 01
GTO 01
Этот фрагмент можно использовать на собеседовании вместо крышек люков. С вопросом «Сколько раз выполнится BEEP и почему?». Правильный ответ — 3 раза.
Объяснение: параметры цикла задаются единственным дробным числом, которое помещается в стек. Число имеет формат iiiii.fffcc, где:
iiii — начальное, оно же текущее, значение счётчика (индекс),
fff — конечное значение
cc — шаг
Таким образом, 1.00301 означает счёт от 1 до 3 с шагом 1
Очевидно, такое своеобразное решение позволяет экономить память, хотя читаемость кода, скажем так, слегка страдает.
Немного о выводе на экран строк:
AVIEW выводит на экран регистр ALPHA, VIEW — регистр X
Команда APPEND присоединяет указанные символы к строке в ALPHA регистре. Вводится с клавиатуры как SHIFT K, в исходниках это выглядит как >«TEXT»
Пример:
"HELLO WORLD!" ;помещаем строку в ALPHA регистр
AVIEW ; выводим на экран
PSE ; делаем паузу
CLD ; очищаем экран
При очистке экрана на него возвращается стандартный символ «летящего гуся», показывающий, что программа выполняется. Если на экране текст, гусь не появляется.
Хотя на экране 12 знакомест, максимальная длина строки в одном программном шаге — 15. С применением APPEND можно получить 24 (т.е. полная длина регистра ALPHA). При выводе на экран длинной строки, она автоматически скроллится:
"1234567890"
>"ABCDEFGHIJKLMN"
AVIEW
Операции со строками ограничены тремя командами:
ASTO X — помещает 6 первых символов из ALPHA в указанный регистр
ARCL X — присоединяет в конец ALPHA строку из указанного регистра
ASHF — сдвиг ALPHA на 6 символов влево (они теряются)
Ввод данных:
PROMPT — выводит на экран содержимое ALPHA регистра и останавливает программу (соответственно, можно что-то ввести и нажать R/S, таким образом продолжив выполнение)
PSE — приостанавливает выполнение программы примерно на секунду. При этом, если были нажаты цифры или буквы, пауза продлевается ещё на секунду, а итоговое значение помещается в регистр для дальнейшей обработки.
О звуке:
BEEP — проигрывает стандартную последовательность одних и тех же четырёх нот
TONE цифра — короткий писк одной из 10 частот (0 — самая низкая…9 — самая высокая). Частоты выбраны довольно странным образом. По-видимому, это было обусловлено экономией памяти.
Среди периферийных устройств есть модули расширения памяти, ПЗУ с готовыми программами, устройство чтения/записи магнитной ленты (HP 82161A), магнитных карт (HP82104A), считыватель штрих-кодов, инфракрасный порт, принтер, плоттер, часы, интерфейс HP-IL (через который можно подключить калькулятор к различному оборудованию) и другое.
Модуль чтения/записи на магнитные карты мне достался в комплекте с HP-41. Карты — это полоски магнитной плёнки на бумажном основании (в московском метро раньше были похожего типа проездные).
Каждая полоска имеет две дорожки — т.е. её можно вставлять левой, либо правой стороной. На каждую сторону влезает 112 байт. Типичная программа занимает несколько карт.
Можно защитить сторону карты от записи, отрезав уголок.
Когда модуль вставлен в калькулятор, задействовано его ПЗУ. Соответственно, в калькуляторе появляется много новых команд для работы с картами. Можно читать и писать программы, регистры, и т.п. Можно даже защитить записываемую программу от просмотра (т.е. можно будет её загрузить и запустить, но нельзя будет увидеть саму программу).
Здесь можно посмотреть, как работает накопитель на магнитных картах.
К сожалению, устройство это ненадёжно и очень прожорливо до батареек (питается от самого калькулятора). Моё не работало — жужжало мотором, но не протягивало ленту. Оказалось, что прижимной ролик внутри не просто развалился, а полностью исчез, оставив после себя лишь каплю вязкой грязи. Ролик я поменял на самодельный, но ленту он протягивает явно с трудом — нужно точно подгонять диаметр. Проблема настолько типична, что на ebay даже продаются комплекты этих роликов.
Другим интересным устройством. также довольно распространённым среди владельцев HP-41, является считыватель штрих-кодов, который используется, в том числе, для загрузки программ в калькулятор непосредственно из книг. Представьте — никаких долгих вбиваний программ с клавиатуры!.. Выглядит это так:
Конечно, писать на FOCAL можно и прямо на калькуляторе. Но это довольно утомительно — куда удобнее писать программу в текстовом файле. Но с компиляторами и эмуляторами ситуация сложная. Все они довольно странные и не очень стабильно работающие. Из тех, что запускаются под Win10, есть sim41 и v41 (v.7b). Первый запускается только из Visual Runfox, но зато в нём есть отдельный редактор программы (т.е. необязательно вводить и редактировать её с клавиатуры калькулятора).
Второй запускается без прелюдий, заметно лучше эмулирует калькулятор (хотя и не на уровне железа, о чём говорит, например, рассинхронизация звука с кодом), но программу вводить надо либо полностью вручную, либо загружать в виде бинарного .raw, который является не машинным кодом, а бинарным представлением FOCAL). Проблема в том, что для компиляции текстового исходника в raw придётся использовать утилиту HP41UC.EXE, которая запускается только из под DOS. Я использовал vDos с батником, замапив нужный директорий на диск через use f: c:tmp
Компиляция исходника в бинарник:
hp41uc /t=test.txt /r=test.raw
Декомпиляция бинарника в исходник:
hp41uc /r=text.raw /t=text.txt
Чтобы лучше прочувствовать платформу, я написал небольшое 256 байт интро для DiHALT demo party.
Именно 256 байт просто потому, что больше в калькулятор, даже с установленным модулем расширения ОЗУ, не влезло бы. Понятно, что особых визуальных эффектов ожидать от калькулятора не приходится. Используется вывод различных строк, в том числе автоматический скроллинг длинных строк. Анимация с лицом — вывод двух строк в цикле. DTMF имитируется очень условно, музыка тоже совершенно не похожа не оригинал — в силу того, что не получится выбрать ни нужной тональности, ни длительности. Тем не менее, на музыку это всё же похоже. В конце используется стандартная особенность калькулятора — отображать «летящего гуся» при занятости процессора и пустом регистре ALPHA.
Первоначально интро было раза в два длиннее, даже использовало подпрограммы. Но когда в эмуляторе всё было отлажено и я стал вбивать это дело в калькулятор, оказалось, что оно туда не влезает (понадеялся, что эмулятор сообщит о нехватке памяти, но нет). Пришлось сокращать и переписывать.
Здесь можно посмотреть оба исходника.
Synthetic programming — методика, основанная на использовании уязвимости, обнаруженной в редакторе программы калькулятора. Обычные штатные инструкции закодированы в памяти калькулятора несколькими байтами. Уязвимость позволяет (после довольно сложной подготовительной процедуры) изменять эти байты, получая новые инструкции с разнообразной функциональностью. К примеру, можно получить от команды TONE больше звуков, чем позволено штатно. Можно вывести на экран больше символов (из прошитого в ПЗУ набора), получать доступ к системным флагам и ряду других полезных вещей. Повторюсь, на практике использовать эту методику сложно и утомительно. Правда, существуют модули с подпрограммами, которые это облегчают.
Могут возникать ситуации, когда в результате некорректных или неаккуратных синтетических действий калькулятор зависнет (например, не будет реагировать на клавиши) вплоть до необходимости вытащить и вставить батарейки. Такое у меня случалось. Интересно, что в литературе встречаются предупреждения что, в отдельных случаях, чтобы калькулятор снова включился, батарейки необходимо вытаскивать на двое суток (!).
Ещё одна сложность с применением синтетический инструкций заключается в том, что иногда они состоят из не-ASCII символов. Там возникают специфические сложности как с их вводом, так и с печатью такой программы на штатном принтере для HP-41. Да даже просто опубликовать такую программу в книге или журнале — целая проблема (обычно рядом с командами пишут пояснения, как их следует понимать). Одно из решений проблемы — считывание каждого символа при помощи считывателя штрихкодов из специальной таблицы:
Собственно, synthetic programming очень близко по духу к «еггогологии» в Б3-34 совместимых калькуляторах. В качестве иллюстрации можно посмотреть вот это письмо.
Люди даже писали на эту тему стихи! (взято из книги Synthetic Programming for the HP41C (W.C.Wickes)
‘Twas octal, and the synthetic codes
were scanned without a loss.
In and out of PRGM mode,
Byte-jumpers nybbled the CMOS.
«Beware 0 STO c, my son,
The MEMORY LOST, the keyboard lock.
Beware the NNN, and shun
The curious phase 1 clock.»
He took his black box codes in hand,
Long time the backwards goose he sought;
The secret beast from Aitchpee land–
All searches came to nought.
In demented thought he stood, and then:
The goose, with LCD’s alight,
A leap for every LBL 10,
Came honking left-to-right!
STO b! STO d!, and RCL P!
His keyboard went clickety-clack.
With the proper code in number mode
The goose came flapping back.
«And hast thou found the phantom fowl?
Come to my arms, my binary boy.
Let Corvallis hear us howl
As we chortle in our joy!»
‘Twas octal, and the synthetic codes
Were scanned without a loss.
In and out of PRGM mode,
Byte-jumpers nybbled the CMOS.
–Apologies to Lewis Carroll
Машинный код, выполняемый непосредственно микропроцессором калькуляторам HP-41 называемый MCODE — он в 5-120 раз быстрее, чем стандартный FOCAL.
Для запуска на калькуляторе программа в MCODE должна быть записана в ПЗУ (либо в эмулятор ПЗУ). Существуют специальные модули, позволяющие загружать в себя код по USB или RS232 и даже писать в M-CODE непосредственно на калькуляторе. Обобщённо они называются MLDL и бывают как древними, от самой HP, так и современными.
Из кросс-ассемблеров я нашёл только древний — под DOS.
Пара слов об архитектуре процессора. Поскольку он заточен главным образом под математику, есть своя специфика. Основные регистры (а регистры процессора это вовсе не регистры используемые в FOCAL!) A,B,C,N,M — 56-разрядные.
Есть также более короткие регистры флагов, клавиатуры, динамика, указателей, 16-разрядный счётчик команд, а также четырёхуровневый стек возвратов (четыре 16-разрядных регистра).
В ПЗУ, связанном с процессором последовательной шиной и где, собственно, находится управляющая программа калькулятора, написанная на MCODE, байты имеют ширину 10 бит. Процессор адресует 64K ПЗУ, из которых 12K занимает операционная система. Что касается ОЗУ, то оно не отображается в адресное пространство и является для процессора периферийным устройством. Байты ОЗУ имеют ширину 8 бит, но логически процессор работает с ОЗУ как с 56-разрядными регистрами.
Поскольку я не писал на MCODE (задушила жаба на $250 за эмулятор ПЗУ), личным опытом программирования в MCODE поделиться не смогу.
Инструкции там вполне традиционны, хотя многие мнемоники довольно специфические. К примеру:
B=A ; скопировать содержимое регистра A в B
A<>C ; поменять местами A и C
A=A+B ; сложить A и B и поместить результат в A
A=B=C=0 ; поместить 0 в регистры A,B,C
C=0 M ; поместить 0 в мантиссу (нибблы 3-12) регистра C
?A<C ; установить флаг переноса, если A меньше C
JC -02 ; перейти по смещению, если установлен флаг переноса
READ n ; поместить содержимое регистра ОЗУ (от 1 до 15) в регистр C
PUSH addr ; поместить адрес в стек подпрограмм
GOSUB 815B ; переход к подпрограмме
Примерный MCODE аналог FOCAL команды TONE n:
178 C=REG 5/M ; recalls status register M
358 ST=C ; rightmost byte (nybbles 1 and 0 ) are loaded in status bits (flags 0 to 7)
379 *
05A NCGO 16DE ; переход на адрес подпрограммы XTONE в ПЗУ
Что касается управления индикатором, то его контроллер не позволяет включать и выключать произвольные сегменты — можно выводить лишь существующие в знакогенераторе символы. Это тоже стало причиной, по которой я не стал заморачиваться с эмулятором ПЗУ и программированием в MCODE.
Для вывода символов нужно выбрать индикатор инструкцией процессора PRPH SLCT FD и далее работать с регистрами индикатора через WRIT/READ
Честно говоря, логика работы и система команд калькулятора довольно запутанные. На мой взгляд, для человека, который может освоить подобное — нет никакой проблемы просто писать в машинных кодах какого-нибудь простого процессора. В наших Б3-34 совместимых калькуляторов всё, конечно, тоже непросто, но там и возможностей намного меньше, из-за чего не было ощущения такой запутанности.
В принципе, аргумент за нагромождение в HP-41 псевдокода поверх микропроцессора — необходимость математических вычислений, поскольку, всё же, именно это должно быть простым для типичного пользователя калькулятора.
Тоже самое касается и клавиатуры — можно было бы сделать сразу обычную алфавитно-цифровую клавиатуру, не навешивая команды на кнопки. Ведь всё равно функционал побуквенного ввода команд уже был реализован (впрочем, калькуляторы с полноценной клавиатурой вскоре начали выпускаться).
Я собрал различную документацию по HP41 в один архив, если кому интересно — можете скачать (выложил на время, потом уберу).