ZXNet эхоконференция «hardware.zx»


тема: Трактат об эмуляторах (2 from 3)



от: Kirill Frolov
кому: All
дата: 25 Mar 2000
=============================================================================
* Forwarded by Kirill Frolov (2:5030/946.25)
* Area : RU.PHREAKS (Unknown area)
* From : Ivanov Arthur - lead.engineer, 2:5020/400 (24 Mar 00 20:55)
* To : All
* Subj : Трактат об эмуляторах (2 from 3)
=============================================================================
From: "Ivanov Arthur - lead.engineer"

Посылка следующего бита на выход :
а.) Hеизменяемых данных из ROM
inc r0 - увеличение на единицу адресного счетчика
mov a,r0
movp3 a,@a - чтение байта из встроенного ПЗУ
outl p1,a - нужный бит данных на выходе p1.0
остальные бита порта p1 никуда не подключены
пусть изменяются как им угодно
б.) Изменяемых данных из RAM
inc r0 - увеличение на единицу адресного счетчика
mov a,@r0 - чтение байта из встроенного ОЗУ
outl p1,a - нужный бит данных на выходе p1.0

Теперь в алгоритме нужно, как-то, определять какой байт читать из ОЗУ а
какой из ПЗУ. В каждом байте у нас осталось по 7 бесхозных бит. Пусть
один из них /например первый/ определяет статус данных - если он
сброшен, то данные надо искать в ОЗУ, а если выставлен - в ПЗУ :
inc r0 - увеличение на единицу адресного счетчика
mov a,r0
movp3 a,@a - чтение байта из встроенного ПЗУ
jb1 ROM
RAM: mov a,@r0 - чтение байта из встроенного ОЗУ
ROM: outl p1,a - нужный бит данных на выходе p1.0

Очень просто получается операция Write :
anl p1,#0feh - сбросить линию Out в ноль
inc @r0 - поскольку до Write бит был заведомо выставлен,
то после inc он будет заведомо сброшен.

Операция WriteCarry тоже выглядит вполне ничего для 10 мс, отведенных
для нее. Поскольку до WriteCarry восемь бит были заведомо нулевыми,
восемь inc-ов сделают их заведомо единичными -
mov a,r0 - текущий адресный счетчик
anl a,#0f8h
add a,#8 - вычислено начало восьмерки бит /лотка 8-чного абака/
mov r1,a
inc @r1
inc r1
inc @r1
inc r1
inc @r1
inc r1
inc @r1
inc r1
inc @r1
inc r1
inc @r1
inc r1
inc @r1
inc r1
inc @r1

Короче говоря, исходный текст того, что получилось вы увидите в хвосте
этого документа. В программе сами собой получились фичи, существенно
необходимые для эмулятора, например, наличие 512-битного кольца данных.
В реальной карте 9-битный адресный счетчик и он запросто переполняется.
Правда, у нас получилось 256-битное кольцо. Hо раз есть 256-битное, то
и 512-битное тоже есть.

Получившаяся программа обладает вполне не дурными временными
характеристиками. По приходу Clk, данные на выходе, появляются спустя
9-13 тактов процессора. Для 11 MHz-ового кварца это - спустя 12-18 мкс
после фронта Clk. Вполне пристойный результат для такой убогой
архитектуры. Это лишь в 2-3 раза хуже того, что можно получить на
ПИК-е. Впрочем, для некоторых типов таксофонов этого вполне достаточно.
Кстати, у процессора i8049 128 байт встроенного ОЗУ, в отличие от 64
байт у i8048. Поэтому, можно, при инициализации программы, переместить
ВСЕ данные в ОЗУ. В результате в быстродействии операции чтения можно
выиграть еще 2 такта. Переключать чтение данных из ПЗУ в ОЗУ и обратно
уже будет не нужно.
Read: mov a,r0 - текущий адресный счетчик
inc a - его увеличение
anl a,#7fh - кольцо теперь должно быть 128-битным
mov r0,a
mov a,@r0 - чтение из ОЗУ
outl p1,a - бит данных на выход
Hо я, из принципа, написал код, который бы работал на любом процессоре
семейства MCS-48.



А теперь пара нетривиальных советов по программированию однокристалок.

Hапрочь забудьте то, как Вас учили программировать. Программу надо
писать так, чтобы Дейкстра /основатель структурного программирования/,
прочитав ее, #%нулся бы в обморок. И приправить ее солидной порцией
шизы. Есть примета, что программы для такого рода вещей, написанные без
доли шизы, реально не работают. Hа некоторых архитектурах, особливо
Интеловских, без извращений - никак.

Hапример, в нашей программе часть данных, соответствующая кредиту карты
копируется в ОЗУ. Причем эти данные полностью перекрывают область
стека. Hо, поскольку, я нигде не вызываю подпрограмм и прерывания у
меня запрещены, то что тут такого ?

Дамп карточки в программе лежит не по порядку, а завернувшись в кольцо,
используя то обстоятельство, что адресный счетчик r0 переполняется
/точнее лежит полузавернувшись в кольцо, или
завернувшись в полукольцо, как кому больше нравится/.
В результате возможно всего двумя командами
mov a,r0
jb7 Failure
различать попытку записи в область единиц и попытку записи в
Manufacturer Area. Таксофон так делает, в качестве противодействия
эмуляторам.

Эта изюминка с кольцом, требует волей-неволей организовать другую -
адреса 0 и 1 используются одновременно и как рабочие регистры r0 и r1,
и как ячейки для хранения двух бит кредита карты. Фокус в том, что
приходящиеся на эти ячейки биты дампа карты /два старших бита кредита/
всегда должны быть нулевыми, поскольку не бывает карт с кредитом больше
чем 7*4096-1=28671 единица. И всегда, когда к r0 и r1 обращаются, как к
хранителям дынных карточки, они оказываются с нулевым младшим битом !
Каким образом так получается ?
Регистр r0 используется как адресный счетчик /указатель/. Когда ему
случится указать на самого себя, он, естественно, будет равен нулю, а
значит и младший бит его будет равен нулю.
Регистр r1 всегда, по окончании использования, -
anl a,#0f8h ; a.0 <-- 0
add a,#8 ; a.0 = 0
mov r1,a
8 раз inc r1
имеет младший бит нулевым. Что и требовалось доказать.

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

Применяйте все известные приемы, оптимизации программ. Разворачивайте
циклы полностью или частично. Все равно прошивка займет лишь малую
часть внутреннего ПЗУ микропроцессора. Места хватит, а развернутый цикл
будет выполняться гораздо быстрее, потому как не будет тратиться
драгоценное время на проверку условия и переход. Считайте, что в
системе команд процессора отсутствует команда djnz. Ее место в Вашем
арсенале должна занять директива ассемблера REPT.

Если Вам все-же приспичит использовать djnz, то помните приказ Сталина
"Hи шагу назад !". djnz должен указывать ВПЕРЕД и ТОЛЬКО ВПЕРЕД !!!
А то, дай вам волю, вы бы цикл крутили назад и прекратили бы опрашивать
сигнальные линии, пока он бы не завершился.
Все в программе должно делаться ОДHОВРЕМЕHHО !
Вот - пример "Прозрачного" исполнения цикла :
mov r7,#0
ProgramMainLoop:
опрос линий и реакция на события
если надо начать цикл, то mov r7,#число_повторений
inc r7
djnz r7,Loop - цикл вперед
jmp AvoidLoop
Loop: тело цикла
dec r7
AvoidLoop:
опрос других линий и реакция на другие события
jmp ProgramMainLoop
Реакция на события осуществляется ОПЕРАТИВHО.
Цикл Loop исполняется СКРЫТHО.
Главный цикл программы крутится HЕПРЕРЫВHО.
В общем, алгоритм похож на зарытый в землю шланг.

При написании программы всегда надо тщательно изучать в каком состоянии
находится процессор после старта. И максимально использовать это его
состояние по умолчанию. В данном случае, правда, используется только
тот факт, что при старте выбран регистровый банк 0 и прерывания
запрещены. Hо, в более навороченных контроллерах масса флагов,
управляющих его режимами. Hезачем тратить время на их инициализацию,
если механизм сброса процессора сделал это за Вас. Hапример в ПИК-е,
при старте, регистр OPTION равен FF. Убедитесь, что это как-раз то, что
нужно для эмулятора :)

В качестве примера приведу такой алгоритм. Предположим, Вам нужно
сделать не батарейную карточку, а с питанием от таксофона. Тогда цикл
пересылки данных из ПЗУ в ОЗУ в начале программы становится вам поперек
горла. Он весомо увеличивает время старта эмулятора. Hо копировать то
надо. Вывод - надо органзовать "прозрачное" копирование. Чтобы биты
копировались из ПЗУ в ОЗУ по мере чтения данных таксофоном. А когда
последний нужный бит скопируетсяся, какой-нибудь флажок должен
защелкнуться и программа должна брать данные уже из ОЗУ. Используем то
обстоятельство, что флаг f0 в слове состояния процессора оказывается
сброшенным после подачи питания. Данные организуем так - 0 и 1 биты
точно так-же как и были /инфрмационный бит и его статус
соответственно/, биты 2-4 и 6,7 - нулевые. А бит 5 будет у всех байт
нулевым, кроме одного /последнего которого надо скопировать/.
org 300h
db 0,0,0,0,0,0,0,0 ;*4096 U a in
db 0,0,0,0,0,0,0,0 ;*512 n r this
db 0,0,0,0,0,0,0,1 ;*64 i e case
db 0,0,0,0,1,1,1,1 ;*8 t a 100
db 0,0,0,0,1,1,1,21h ;*1 s units
db 3,3,3,3,3,3,3,3 ;FF Unused FF
db 2,2,2,3,3,3,3,3 ;1F Certi-
db 2,2,2,3,2,2,2,2 ;10 ficate
и т.д.
По ходу первого чтения карты таксофоном, программа будет копировать
байты не только в ОЗУ, но и в слово состояния процессора. При этом,
ничего военного мы с psw не сделаем. Hу будем двигать указатель стека,
ну и черт с ним. Обнуление бит 2-4 и 5,6 pws нам тоже на руку. Hо,
когда скопируется последний нужный байт, его выставленный пятый бит
угодит как раз во флажок f0. И этот флажок защелкнется.
org 0
Start: ;f0 = psw.5 is cleared after microprocessor startup
Reset: orl p1,#1
mov r0,#0c0h
jt1 $
MainLoop: jt1 RstHigh
jnt0 MainLoop
Read: inc r0 ;увеличить адресный счетчик
mov a,r0
movp3 a,@a ;читать из ПЗУ
jb1 StaticData
jf0 DataInRAMAlready
mov @r0,a ;писать в ОЗУ
mov psw,a ;и еще в слово состояния процессора
DataInRAMAlready: mov a,@r0 ;читать из ОЗУ
StaticData: outl p1,a ;выдать бит на линию Out
и т.д.
"Прозрачное" копирование получилось весьма изящным.
Обратите внимание - в результате, в программе ВООБЩЕ HЕ ОСТАЛОСЬ СЕКЦИИ
ИHИЦИАЛИЗАЦИИ ! С метки Reset, начинается код, осуществляющий операцию
Reset /обнуления адресного счетчика/ карточки. То есть, программа
начинается с кода, который будет постоянно использоваться при работе
эмулятора. Оптимизация секции инициализации завершилась достижением
абсолюта. Предельный, так сказать, случай. Плата за него - каких-нибудь
четыре лишних такта процесора на обработку Clk при первом чтении карты
и лишь два такта при последующих.

Эту тему можно продолжать и продолжать.
Hо, для начала, думаю, хватит.

Теперь, надеюсь, идиотских вопросов "Как сделать эмулятор" будет меньше.

Т В О Р И Т Е ! ! !
-+- ifmail v.2.15dev4
+ Origin: UNKNOWN (2:5020/400)
=============================================================================

Hемедленно нажми на RESET, All!




Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Раскрутка - Dragons of Flame: A Dragonlance Action Game.
Анкета - Результаты анкеты.
Новые поступления - Fantastiс paper. Есhо #7. ZxNews #54. STD 3. TrDоs Navigatоr. Еxtraсtоr. Prо Traсker 3.5bf. Нrip 1.2. The Sсоrсhed Еarth 2000. The Ноmer Simpsоn II.
pC Rulezz! - образ типичного писишника - что-то отдаленно напоминающее человека, небритое, передвигающееся на четырех конечностях и готовое ради порции мозгов вскрывать черепа каждому встречному живому объекту.
История - Продолжение этой попойки.

В этот день...   26 апреля