[Перевод] Реверс-инжиниринг калькулятора с логикой -17В и частотой работы 200КГц

Осторожно! Впереди кроличья нора

ixj7z_j2_bro7loqh7b_v8qlqre.jpeg

Разбираем внутреннее устройство старого промышленного калькулятора Rockwell 920 и на аппаратно-программном уровне пытаемся отследить неисправность, из-за которой он не работает. Процесс оказывается не столь простым, как можно было предположить, и на пути возникает ряд «странностей».

Rockwell 920/3


У меня есть калькулятор Rockwell 920/3.
Родом этот внушительный зверь, где-то года так из 1975. Он программируемый, а его полностью расширенная версия может иметь 996 программных шагов. Помимо этого, он оснащен устройством чтения/записи магнитных карт и шестнадцатизначным дисплеем. Этот калькулятор может сохранять на картах как данные, так и программы. Конкретно первый оказавшийся у меня экземпляр использовался для ведения платежных ведомостей в крупной английской каталожной компании.

Достался он мне с нерабочим кардридером, что было досадно, ведь у меня как раз имелось несколько подходящих карт с программами, которые я давно уже хотел извлечь. Решилась эта проблема просто. Увидев в продаже другой такой же калькулятор, я с радостью его купил и просто переставил кардридер. В результате все прекрасно заработало, и я даже снял небольшое видео:


Программы я напечатал и как-нибудь займусь их транскрибированием. Позже в продаже появился третий аналогичный калькулятор, который я также без сомнений приобрел. В результате это оказалась модель 920/2, а не 920/3, что означало меньший объем памяти. Внутренне эти машины оснащены одинаковой материнской платой, снаряженной скромным ОЗУ. Вот одна из этих плат с моими подключениями для отладки:
kyu0p0oird3dkmzlmpyvyxcmvrk.jpeg

Исходя из его архитектуры, этот калькулятор можно поистине назвать одноплатным компьютером. Здесь есть процессор, ОЗУ и ПЗУ вместе с интерфейсом ввода/вывода общего назначения (GPIO). В качестве процессора установлен Rockwell PPS-4, четырехбитный чип, использовавшийся для небольшого числа устройств в 70-х годах, в частности калькуляторов и машин для игры в pinball. Работает он от нестандартного, по крайней мере на сегодня, источника питания -17В. Логика здесь отрицательная, следовательно ноль — это -17В, а единица — это 0В. В результате подключить такое устройство к логическому анализатору, да даже к осциллографу, оказалось проблематичным. Обычно логический анализатор позволяет отлаживать напряжения от нуля до +5В, где земля — это логический нуль, а +5В — логическая единица (прим. переводчика). Частота процессора тоже далека от современных показателей и составляет всего 200 кГц. Второй и третий экземпляр были в нерабочем состоянии, так что я взялся за их починку. И поскольку было бы кстати понять, что именно делает код, я решил инструментировать сигналы шины и проследить его выполнение, а также, если повезет, создать дамп ПЗУ.

Я мог бы выпаять эти ПЗУ (все из которых находятся в нестандартном 42-контактном корпусе QIP с шахматным порядком выводов), но тогда мне бы понадобилось устройство для их дампа, работающее также от -17В. Позже я, может, и соберу такое, если мне не удастся сделать дамп всего ПЗУ с помощью перехвата шины на работающем калькуляторе. Думаю, что если прогнать его по всем функциям при подключенном сканере, то должно получиться постепенно перехватить содержимое ПЗУ. Как минимум выполняемые его части. Я собрал тестовую схему, чтобы оценить, заработает ли вообще хоть что-то при -17В. Результат на этом видео:


Заработала схема отлично, поэтому я продолжил и собрал плату с достаточным количеством входов для перехвата нужных сигналов и анализа выполнения потока кода. Оказалось, что простой (и дешевый, что будет на руку, если при -17В я вдруг допущу ошибку) микроконтроллер STM32F103C8 имеет достаточно GPIO для обработки адресной шины (на языке PPS-4 это A/B), шины данных (или I/D) и сигналов управления шинами.
yy4_0usxbljc4hvpdkxfcm3kfga.jpeg

Техническое описание PPS-4 доступно в интернете, и мне удалось найти образец кода в его патенте. Теперь я могу протестировать свой дизассемблер и любой объектный код на работоспособность. При рассмотрении шина процессора может вызвать пугающие ощущения, по крайней мере в сравнении с Z80. Это мультиплексированная, чередующаяся двухфазная штуковина. В ней присутствует четыре фазы тактов (моя терминология). Адреса ПЗУ поступают на адресную шину в фазе 1, в то время как данные ОЗУ или ввода/вывода находятся на шине данных. Затем в фазе 3 адрес ОЗУ или ввода/вывода передается на адресную шину, а ПЗУ на шину данных. Для лучшего понимания стоит взглянуть на схему из технической документации:

ydld_8mrod9tlhvgf5tpancmlw4.png

Если поразглядывать ее минут 10, то постепенно все становится понятно. Да, у процессора есть два тактовых входа, работающих на двух разных частотах и несколько отличающихся по фазам. Здесь есть специальная микросхема, генерирующая эти тактовые сигналы. На самом деле выпаивание микросхем ПЗУ для считывания является наиболее простой частью всего процесса. После выпаивания нужно сделать их дамп, используя описанную схему шин, и при этом не забывая, что логические уровни сигналов равны 0В и -17В.

Из полезного в данной схеме шин можно выделить пару фаз «освобождения шины». Это позволяет легко управлять фазами при трассировке кода. Можно было подумать, что все просто, но PPS-4 ставит еще одну подножку, инвертируя частоту B и некоторые (только некоторые) данные на шине до их использования. Иногда он инвертирует полубайт, иногда байт. В данном случае уцепиться можно за пустую фазу.

После некоторых простых тестов с микроконтроллером я получил скетч, который фиксирует сигналы шины настолько быстро, насколько это возможно. Я добавил задержку перед запуском в несколько тактов от выхода сигнала сброса в неактивное состояние, чтобы можно было перемещать окно захваченных сигналов шины с целью захвата начального выполнения кода. Надеюсь, что это поможет понять, почему плата не работает.

Трассировка шины выглядят так:

A:000 D:04 CLKA:0 CLKB:1 WOL:0 DO:0  - 0
A:000 D:00 CLKA:0 CLKB:1 WOL:0 DO:0  - 1
A:000 D:00 CLKA:0 CLKB:1 WOL:0 DO:0  - 2
A:000 D:00 CLKA:0 CLKB:1 WOL:0 DO:0  - 3
 
A:003 D:00 CLKA:0 CLKB:0 WOL:0 DO:0  - 0
A:003 D:00 CLKA:0 CLKB:0 WOL:0 DO:0  - 1
A:003 D:07 CLKA:1 CLKB:0 WOL:0 DO:0  - 2
 
A:000 D:0F CLKA:1 CLKB:1 WOL:0 DO:0  - 0
A:000 D:00 CLKA:1 CLKB:1 WOL:0 DO:0  - 1
A:000 D:00 CLKA:1 CLKB:1 WOL:0 DO:0  - 2
A:000 D:00 CLKA:1 CLKB:1 WOL:0 DO:0  - 3
 
A:003 D:1C CLKA:1 CLKB:0 WOL:0 DO:0  - 0
A:FFF D:1C CLKA:1 CLKB:0 WOL:0 DO:0  - 1 1C
A:FFF D:1C CLKA:1 CLKB:0 WOL:0 DO:0  - 2
A:6AB D:1C CLKA:0 CLKB:0 WOL:0 DO:0  - 3

Обратите внимание на адрес 3 во втором блоке — это адрес ПЗУ. Затем в четвертом блоке данные из ПЗУ помещаются на шину. Содержимое шины данных при представлении адреса ПЗУ — это данные ОЗУ или ввода-вывода из предыдущей инструкции ввода-вывода или чтения.

Перехваченные данные копируются в текстовые файлы по одному для каждого «окна» предварительной задержки. Серия скриптов извлекает информацию ПЗУ. Это скудные данные, поскольку захватываются, очевидно, только адреса ПЗУ, которые выполняются, или к которым обращаются инструкции. Одна из основных последовательностей — это код запуска.

000     0x81       t 0x1
001     0x82       t 0x2
002     0x74       ldi 0xB
003     0x1C       iol 0x1E  
005     0x7E       ldi 0x1
006     0x1C       iol 0x1D  

Процессор после сброса начинает с адреса 000, так что у нас есть точка для начала трассировки. Следующая инструкция — это переход (t: transfer) из адреса 000 к адресу 001. Это несколько странно, но похоже на правду. Затем идет переход из 001 к 002. Опять же, странно, но может процессору требуется произвести какие-то настройки в первых циклах инструкций, либо счетчик программы таким образом «завершается», или что-то в том духе? Я нашел в патенте какой-то код PPS-4, который начинается так:
0000 81        T       #1                                                   
 	*SET O/P TO ZERO                                                          
 	0001 7F        LDI     0                                                    
 	0002 10 0E   IOL     #E                                                   
 	0004 7F        LDI     0                                                    
 	0005 10 0D   IOL     #D                                                   
 	0007 7F        LDI                                                          
 	0008 10 07   IOL     1   

Здесь мы видим такую же инструкцию перехода, что еще раз подтверждает наличие конкретной точки для трассировки. Тем не менее перехода из 001 к 002здесь уже нет. Мне не удалось найти объяснения, откуда они вообще взялись.

Инструкция IOL отправляет команду микросхемам GPIO. Они не отображаются в память или I/O, вместо чего в них заложены номера устройств. IOL ox1E отправляет команду oxE устройству 1. Так происходит настройка сигналов сканирования клавиатуры/дисплея. Я отследил достаточно сигналов на печатной плате, чтобы определить номера устройств для микросхем GPIO. До этого момента я называл микросхему PPS-4 процессором, но это не совсем верно. У этой микросхемы действительно есть порты ввода/вывода. Это не порты GPIO, поскольку являются фиксированными вводами или выводами, поэтому в некотором смысле данная микросхема больше походит на микроконтроллер. Порт вывода в 920-м используется для управления демультиплексором, который контролирует каждую цифру дисплея. Вводы же используются для распознавания матрицы клавиатуры (сигналы сканирования для клавиатуры — это те же сигналы сканирования, которые используются для управления цифрами дисплея).

Набор инструкций


Я знаком с 8-битными процессорами Z80 и 6502, а также с набором инструкций ARM. Мне также доводилось программировать на ассемблере для PIC, Z8, 6301, 8086, 8051, 4-битных микроконтроллеров и т.д. Но при этом некоторые из инструкций PPS-4 меня удивили. Такое ощущение, что они были придуманы до того, как люди сформулировали устойчивые правила. Например, инструкция load immediate:
dyejpp5c3ftcxealfuctt8330wk.png

Четырехбитное содержимое, поле immediate field I (4:1) инструкции, помещается в накопитель (см. примечание ниже)

Хорошо, это 4-битный вариант, и промежуточным значением является полубайт в коде операции. Тогда обратим внимание на правую часть, где идет ссылка на примечание 3.
3cyyc029_afzin6xk0pwvsmmlv8.png

Инструкции ADI, LD, EX, EXD, LDI, LB и LBL содержат в своих immediate field (мгновенных полях) закодированное числовое значение. Это числовое значение должно присутствовать на шине в виде дополнения. Все «мгновенные» поля, которые инвертируются, показываются в квадратных скобках
Например: инструкцияADI 1, которую программист пишет, желая добавить единицу к значению в накопителе, преобразуется в 6E(16)=0110[1110]. В скобках указано двоичное значение в том виде, в каком оно представлено на шине данных.

Если программист использует Rockwell Assembler, ему не нужно вручную определять подходящее инвертированное значение, так как ассемблер делает это сам.


Хорошо, для этих инструкций (не всех инструкций) полубайт, являющийся «мгновенными» данными, инвертируется в ПЗУ и, следовательно, на шине. Мда, несколько странно, но дизассемблер или ассемблер это обработает. Далее мы замечаем, что в четвертом блоке слева есть ссылка на еще одно примечание. На этот раз ссылка ведет сюда:
4i1czhbxsrzm10lr7cnjfbxnuto.png

Будет выполнено только первое вхождение инструкции LB или LBL . Программа будет игнорировать оставшиеся LB или LBL и выполнять следующую действительную инструкцию. Внутри подпрограмм инструкция LB должна использоваться с осторожностью, потому что содержимое SB было изменено.

Так, теперь наблюдается некоторая странность. Если загрузить накопитель с 6, а затем с 4, то в итоге его значение будет 6. Когда я столкнулся с этим впервые, то удивился, но после изучения кода решил, что в этом есть смысл. Если вам нужно ввести программу с различными параметрами, то вы можете сделать следующее:
Enter6	LDI 6
Enter7	LDI 7
Enter 8   LDI 8
           * Выполнить действие с A
          RTN

Если перейти к Enter6, то накопитель загрузится с 6, последующие загрузки игнорируются, и вы выполняете действия с 6. Если перейти к Enter7, тогда накопитель загружается с 7, и следующая загрузка игнорируется, а вы выполняете нужные действия с 8. Я понимаю, в чем здесь польза.

Вот инструкция:

bsg6if_pwc_hdvnhaeyzzan3pxc.png
48 последовательных адресов на странице 3 ПЗУ содержат данные указателей, которые определяют адреса входа подпрограмм. Эти адреса входа ограничены страницами с 4 по 7. Данная инструкция TM будет сохранять адрес следующего слова ПЗУ в регистр SA после загрузки исходного содержимого SA в SB. После этого происходит переход к одному из адресов входа подпрограмм. Эта инструкция занимает в ПЗУ одно слово, но для выполнения требует два цикла.

Она показывает использование таблиц данных в ПЗУ на уровне инструкций.

Что дальше?


Теперь мне нужно отследить выполнение кода вплоть до цикла, в котором оно заканчивается. Может, тогда мне удастся понять, что здесь не так. Мне также нужно нарисовать схему. Плотность заполнения печатных плат невысока, так что это не должно оказаться затруднительным.

Расширение памяти


Небольшое отступление.

Модель 920/2 содержит одну подключаемую плату расширения памяти, а в 920/3 их две:

sv1l7j39x-xxec7ejdkelw_rmko.jpeg

Удивляет, что на плате ОЗУ в модели 920/2 присутствует четыре микросхемы, а на платах 920/3 их по 6. Еще один сюрприз в том, что эти две платы микросхем отличаются друг от друга. Мне кажется, что у них разные распиновки разъемов, несмотря на то, что объем памяти на них одинаковый. Поэтому вместо того, чтобы задействовать одну печатную плату для обеих конфигураций расширения памяти (часть, заполняющая плату 920/2) я использовал три разные.

От переводчика: Как видно, автор реверс-инжиниринга данного калькулятора делает много интересных открытий. Мы будем и дальше следить за его публикациями, и как только он напишет что-то новое по данной теме, обязательно опубликуем.

oug5kh6sjydt9llengsiebnp40w.png

© Habrahabr.ru