Хабрахабр

[Из песочницы] О том, как найти ошибку в микропроцессоре, выпущенном тридцать пять лет назад

К1801ВМ1А

Недавно мне довелось в этом убедиться на примере 16-ти разрядного микропроцессора 1801ВМ1А, на основе которого в свое время в СССР было создано семейство бытовых компьютеров БК-0010/11М. В это трудно поверить, но иногда ошибки в процессорах, по-сути, живут дольше, чем сами процессоры. Об этом семействе на Хабре неоднократно писали.

В эти годы, усилиями многочисленных энтузиастов-одиночек, а также групп кружковцев и кооператоров, был наработан основной массив прикладных программ для БК: игры, утилиты, разнообразные "ДОСы" (дисковые операционные системы). Период активной жизни "БэКашек" приходится на конец 80-х — начало 90-х годов прошлого века. В целом, экосистема этих 16-ти разрядных PDP-подобных ЭВМ развивалась по схожим принципам, как, например, развивались ранние 8-ми битные открытые архитектуры на основе Intel 8080 и шины S-100. Параллельно с развитием софта, создавалась периферия, под которую писался свой системный софт. Позже, по мере отхода от утилитарной роли БК, фокус в программировании сместился в сторону демосцены.

Конечно, по сравнению, например, с ZX-Spectrum, этот объем значительно скромнее. Объем программного обеспечения для БК можно оценить, посетив общедоступные сайты с коллекциями программ. Можно ли найти что-то необычное в поведении процессора, после более чем тридцатилетней практики его использования? Тем не менее, даже такого объема, казалось бы, должно было хватить, чтобы обойти все мыслимые закоулки машинного кода. Об этом и пойдет речь ниже. Как оказалось — да!

Прежде всего, должен сразу заметить, что я вовсе не "программист со стажем", ни по роду занятий, ни по принадлежности к когорте энтузиастов БК, о которых я написал выше. Пожалуй, имеет смысл рассказать эту историю в хронологическом порядке. БК "в железе" у меня нет и программы под БК я, как правило, запускаю и отлаживаю в эмуляторе bkemu на планшете под Андроид. К БК я пришел окольными путями, частично через ностальгию по увлечениям детства и юности (аналоговая и цифровая электроника, журнал Юный Техник, ЮТ-88 и прочие поделки и недоделки), а частично через интерес к архитектуре и системе команд PDP-11.

Программа была написана в 1976 г. Некоторое время назад, я заинтересовался программой "Kaleidoscope" за авторством Li-Chen Wang-a. Мне захотелось детально разобрать алгоритм Li-Chen Wang-a и, заодно, портировать его на БК. в машинных кодах, под микропроцессор Intel 8080 в составе компьютера Altair 8800 с графическим адаптером Cromemco Dazzler. Надо сказать, что желание портировать Калейдоскоп под БК высказывалось в среде демосценеров и ранее, и даже были попытки разобрать алгоритм, но они не увенчались успехом.

Для дальнейшего же, достаточно будет указать, что задача была решена, и Калейдоскоп был успешно портирован на БК. В своей следующей статье, я, возможно, разберу этот алгоритм в деталях (а для нетерпеливых тут выложу ссылку на исходники кросс-платформенного Калейдоскопа под libSDL на С). Более того, в алгоритм на БК была добавлена генерация звука, причем, поскольку и картинка, и звук генерируются одним и тем же кодом, можно сказать, что сама картинка и звучит (вся демка уместилась менее, чем в 256 байт машинного кода, и, надеюсь, будет представлена общественности на CAFe Demoparty 2019 в Казани в конце октября).

Особенно меня интересовало воспроизведение звука, так как тайминги в эмуляторе могли отличаться от таймингов на реальном железе. Закончив с написанием и отладкой моей программы в эмуляторе, я обратился к Дамиру ("Адамычу") Насырову (он один из организаторов CAFe Demoparty и весьма известный в среде демосценеров человек) с просьбой проверить выполнение программы на реальной БК. Каково же было мое разочарование, когда Дамир сообщил мне, что на реальной БК изображение есть, а звука нет!

Звук в БК организован довольно просто: 6-й бит в регистре ввода/вывода с восьмеричным адресом 177716 (регистр управления магнитофоном) выведен через буфер на пьезоэлектрический динамик (бипер). Последующие несколько вечеров прошли в попытках вычитать из системной документации на БК-0011М и принципиальной схемы, где же могла быть ошибка со звуком. С выхода этого преобразователя звук может идти на магнитофон. В дополнение к 6-му разряду, разряды 2 и 5 того же самого регистра заведены на простейший цифро-аналоговый преобразователь на 4-х резисторах. Параллельно были установлены и протестированы все известные мне эмуляторы БК — и звук работал во всех! Все исключительно ясно и логично, но звука на реальной БК упорно не было, независимо от комбинаций битовых масок, которые я пытался применить к данным, выводимым в этот регистр.

У меня закончились идеи, и обитатели телеграм-канала по БК-тематике тоже ничего не могли подсказать… Однако, помог, как водится, случай. В какой-то момент мне даже почти удалось убедить Дамира, что его БК неисправна, однако поведение повторилось на другой живой БК-0011М, а также и на БК-0010. И вот тут ему удалось заметить, что не только звук в эмуляторе есть, а на БК нет, но еще и картинки в эмуляторе и на живой БК отличаются! В процессе одного из экспериментов, Дамир запустил демку на эмуляторе, чтобы убедиться, что звук в эмуляторе есть. Соответственно, все это время я искал причину не в том месте: причина была в коде, который генерировал данные для содержимого экрана. Здесь я должен вам напомнить, что в моей программе и картинка, и звук генерируются одним кодом.

Однако причина, почему алгоритм так себя ведет оставалась туманной. Дамир прислал мне снимок экрана, и стало понятно, что алгоритм производит байты с нулевым содержимым старших 4-х разрядов, и, по стечению обстоятельств, именно эти биты выводились на звук (т.е., всегда нули). Вот это место в коде (ассемблер macro11 от PDP-11, регистры r0-r5 переименованы!):

; renamed registers
a = %0
b = %1
c = %2
d = %3
e = %4
h = %5 ... ... asr b ; sets CF bic #177760, b bis b, c bis (h)+, c ; screen address in c movb (c), a ; get a byte from screen RAM bcc 1$ ; check CF bic #177760, a ; keep bits 0-3, clear rest bisb d, a ; fill bits 4-7 br 2$
1$: bic #177417, a ; keep bits 4-7, clear rest bisb e, a ; fill bits 0-3
2$: ... ...

То есть, инструкция bcc всегда воспринимала флаг переноса, как сброшенный, хотя инструкция сдвига ASR могла этот флаг установить как в 0, так и в 1. По какой-то причине, на реальной БК, всегда выполнялся условный переход по метке 1$. Как такое могло быть, ведь по документации на процессор, ни BIC, ни BIS, ни MOVB не должны оказывать влияние на флаг переноса?!

Стало ясно, что реальный процессор 1801ВМ1А работает в этом случае не по документации. Причем, во всех эмуляторах (которые ведь писались по документации на процессор!) так и есть: эти инструкции не трогают флаг С. Осталось это подтвердить.

Для начала, очевидный quick fix:

... asr b ; sets CF mfps -(sp) ; store PSW on stack bic #177760, b bis b, c bis (h)+, c ; screen address in c movb (c), a ; get a byte from screen RAM mtps (sp)+ ; restore PSW from stack bcc 1$ ; check CF ...

Осталось сузить "круг подозреваемых". Сохранение флагов в стеке сразу после инструкции сдвига и их восстановление перед условным переходом тут же решило проблему, что показало, что я на верном пути. Для проверки гипотезы был сначала написан такой синтетический тест (тут регистры не переименованы; начальная инициализация опущена, чтобы не загромождать код; emt 64 — программное прерывание для печати строки):

... mov #1, r1 jsr pc, test clr r1 jsr pc, test halt test: mov #40000, r2 ; r2 points to screen RAM mov #dummy, r5 ; r5 points to dummy = 200 ; *** begin *** asr r1 ; affects CF bic #177760, r1 bis r1, r2 bis (r5)+, r2 movb (r2), r0 ; *** end *** jsr pc, prt rts pc prt: mov #msg1, r0 bcs l1 mov #msg2, r0
l1: emt 64 rts pc msg1: .asciz /Flag CF set/
msg2: .asciz /Flag CF clear/
dummy: .word 200 ...

Программа напечатала на экране И тест … не сработал!

Flag CF set
Flag CF clear

Оказалось, что исходное предположение, что фрагмент кода между begin и end просто портит флаг C — неверно, и требует уточнения. Что же оказалось? А тем, что между блоком "подозрительных" команд и условным переходом появились другие инструкции. Чем же так отличается этот тест от исходного кода? Поэтому, следующий тест был такой: Не влияющие на флаг С, но тем не менее, меняющие внутреннее состояние процессора.

... mov #1, r1 jsr pc, test clr r1 jsr pc, test halt test: mov #40000, r2 mov #dummy, r5 ; *** begin *** asr r1 ; affects CF bic #177760, r1 bis r1, r2 bis (r5)+, r2 movb (r2), r0 bcc l1 ; *** end *** mov #msg1, r0 emt 64 rts pc
l1: mov #msg2, r0 emt 64 rts pc msg1: .asciz /Flag CF set/
msg2: .asciz /Flag CF clear/
dummy: .word 200 ...

И вот этот тест уже напечатал на реальной БК-0011М:

Flag CF clear
Flag CF clear

На эмуляторе же, по-прежнему,

Flag CF set
Flag CF clear

Путем постепенных упрощений был получен вот такой минимальный тест, на котором воспроизводится баг (привожу исходник целиком): Дальнейшее — дело техники.

.title test .psect code .=.+1000 mov #15, r0 emt 63 sec jsr pc, test clc jsr pc, test halt test: movb r0, r0 bcc l1 mov #msg1, r0 emt 64 rts pc
l1: mov #msg2, r0 emt 64 rts pc msg1: .asciz /Flag CF set/
msg2: .asciz /Flag CF clear/ .end

На реальной БК-0011М этот тест выводит

Flag CF clear
Flag CF clear

Если же между MOVB и BCC вставить, например, NOP, то поведение вернется к документированному, и программа напечатает То есть, виновата оказалась инструкция MOVB, стоящая прямо перед командой условного перехода, причем вид первого операнда не суть важен.

Flag CF set
Flag CF clear

Что позволило сформулировать уточненную гипотезу (цитирую себя, из телеграм-канала):

Как я себе представляю, MOVB src, dst (кстати, похоже что операнды не суть важны) из-за каких-то особенностей архитектуры временно портит флаг С внутрях проца, но не фатально, ибо проц, похоже, сохраняет копию этого флага. … По поводу бага: поведение вроде прояснилось. В результате, если между MOVB и условным переходом есть другие команды (не влияющие на С), например, NOP, то поведение как по документации.

Дальше, коллеги из канала помогли привлечь к обсуждению Вячеслава (@K1801ВМ1, легендарного человека, который ранее отреверсил этот процессор на уровне транзисторов). Что же было дальше? Реакция Вячеслава (Yuot), когда он протестировал поведение на стенде с реальным 1801ВМ1A (орфография и пунктуация сохранены):

Stanislav Maslovski:
минимум для воспроизводства нужны две команды
movb и условный переход по С
ну и перед этим флаг С выставить в известное состояние

Yuot:
Флаг с всегда сброшен получается

Stanislav Maslovski:
да
теперь вставь ноп

Yuot:
А теперь ни всигда

Yuot:
Чередуется 0 1
Это какой-то позор

Регистр этот связан с автоматом микропрограмм и условные переходы берут значения флагов из него. С помощью Вячеслава выяснились детали, а именно, что причина бага в том, что в процессоре, помимо PSW есть еще один 4-х битный регистр, который, в норме, хранит копию флагов из PSW. Однако, при выполнении следующей инструкции, значение временного регистра восстанавливается из PSW. При выполнении же инструкций МОVB, SXT, SWAB, MFPS, из-за особенностей обработки знакового расширения, и из-за ошибки в микрокоде, копия флага С в этом регистре сбрасывается и условные переходы по этому флагу работают некорректно. Именно поэтому, вставка NOP восстанавливает правильное поведение.

Заглавное фото к статье любезно предоставлено Manwe SandS. В заключение, хотел бы также поблагодарить подписчиков телеграм-канала "БК0010/11М World" за участие в обсуждении этого бага, и за высказанные замечания по тексту статьи. Что еще более интересно, Manwe был близок к обнаружению того же самого бага, практически в то же время, когда мы с Дамиром бились над решением проблемы звука!

Ведь сам процессор, увы, уже не исправишь. Теперь же дело за малым (шучу) — привести все эмуляторы в соответствие с реальным поведением процессора.

Надеюсь, было интересно. На этом я и закончу.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»