Увлекся 4битными процессорами и занялся Intel 4004. Как раз его я не изучил из-за наличия каличных симуляторов. Нашел документацию, исходные тексты эмуляторов и написал код эмуляции, используя сырок автора Copyright (C) 2001 dondalah@ripco.com (Dondalah).
Нарисовал простой гуй - отображаются регистры и засел за дизассемблер. Взял таблицу данных эмулятора, написанного на JavaScript'e. Теперь стал искать ассемблеры для подготовки кода и отладки моего симулятора.
RetroAssembler Пригодился для того, чтобы скачать, набрать код, помучиться и удалить говноподелку.
Вот код, на котором ассемблер подавился:
ld 1 ; теперь уменьшим счетчик на 1
dac
; .byte $14 $12;jcn az,finish ; выйдем если достигли нуля
jcn 2,F_
xch 1 ; возвращаем обновленное значение в счетчик
jun 0,repeat ; и к новой итерации цикла
F_:
nop
Во-первых, для перехода по адресу(инструкция JUN) требуется два аргумента : в примере 0, repeat. Получается, что ассемблер не может определит правильный адрес и принимает 12-битное значение 0*256+repeat. Это с какого перепуга?. Ладно, для простого кода сойдет аргумент 0, .На второй инструкции jcn 2,F_ ассемблер вытошнило:
Та же истерика с параметром 2, а не 4. Читаю доки - JCN переход по условию требует два аргумента: комбинация битов и адрес. Комбинация битов такая
;0001CCCC AAAAAAAA JCN - Jump conditional
;3 if 1 invert condition
;2 if 1 jump if Acc=0
;1 if 1 jump if carry=1
;0 if 1 jump if testbit=0
А ассемблер принимает те комбинации, которые не рекомендуется использовать. УДОЛИЛ.
Попался еще один
ассемблер. Для его использования нужно немало нервов. Начнем с того, что код получается в формате Intel HEX. Нашел конвертер, но результат не получается - 5 errors, распишитесь в получении. Указал имя листинга. Теперь появилась новая забава - искать ошибки в сырке и угадывать, что не понравилось.
Первый пример
; сложим числа в R6:R7
fim 0,$67 ; загружает 6 в R0 и 7 в R1
ld 0 ; 6 пошло в аккумулятор
clc ; очистили перенос
add 1 ; выполнили сложение, аккум теперь 13
daa ; а теперь аккум 3 и единичка ушла в перенос
; а теперь выведем результат в R2:R3
xch 3 ; выписываем аккум в R3
ldm 0 ; и очищаем аккумулятор
ral ; сдвигаем в него флаг переноса
xch 2 ; и выписываем в R2
У меня уже почти готов интерфейс, я могу гонять код по шагам. Отладка споткнулся на add 1. Читаю доку: эта инструкция складывает аккумулятор с регистром R1 и с флагом переноса.
Снова заглядываю в исходник на Си:
void add(reg)
int *reg;
{
*reg = *reg + rega + fcarry;
if (*reg > 15) fcarry = 1;
else fcarry = 0;
*reg = *reg & 15;
} /* add reg */
Программист молодец: он складывает регистр с аккумулятором и переносом и помещает в регистр. Исправил, вместе с SUB. Оставляю заметку"После завершения работы выверить код". Выверил и нашел немало ошибок.
Но на этом ворох ошибок в коде не закончился. Я подобрал часть примеров и стал гонять в свое программе. На одном из примеров вижу неверный дизассемблер. Выяснилось, в чем причина: взял таблицу из JS-эмулятора, а мнемоники написаны коряво:
var opctab= [
'NOP', 'HLT', 'BBS', 'LCR', 'OR4', 'OR5', 'AN6', 'AN7', // 00
'DB0', 'DB1', 'SB0', 'SB1', 'EIN', 'DIN', 'RPM', '???', // 08
'???', 'JCN TZ','JCN CZ','???', 'JCN AZ','???', '???', '???', // 10
'???', 'JCN TN','JCN CN','???', 'JCN AN','???', '???', '???', // 18
'FIM P0','SRC P0','FIM P1','SRC P1','FIM P2','SRC P2','FIM P3','SRC P3', // 20
'FIM P4','SRC P4','FIM P5','SRC P5','FIM P6','SRC P6','FIM P7','SRC P7', // 28
'FIN P0','JIN P0','FIN P1','JIN P1','FIN P2','JIN P2','FIN P3','JIN P3', // 30
'FIN P4','JIN P4','FIN P5','JIN P5','FIN P6','JIN P6','FIN P7','JIN P7', // 38
'JUN', 'JUN', 'JUN', 'JUN', 'JUN', 'JUN', 'JUN', 'JUN', // 40
'JUN', 'JUN', 'JUN', 'JUN', 'JUN', 'JUN', 'JUN', 'JUN', // 48
'JMS', 'JMS', 'JMS', 'JMS', 'JMS', 'JMS', 'JMS', 'JMS', // 50
'JMS', 'JMS', 'JMS', 'JMS', 'JMS', 'JMS', 'JMS', 'JMS', // 58
'INC R0','INC R1','INC R2','INC R3','INC R4','INC R5','INC R6','INC R7', // 60
'INC R8','INC R9','INC R10','INC R11','INC R12','INC R13','INC R14','INC R15',
'ISZ R0','ISZ R1','ISZ R2','ISZ R3','ISZ R4','ISZ R5','ISZ R6','ISZ R7', // 70
'ISZ R8','ISZ R9','ISZ R10','ISZ R11','ISZ R12','ISZ R13','ISZ R14','ISZ R15',
'ADD R0','ADD R1','ADD R2','ADD R3','ADD R4','ADD R5','ADD R6','ADD R7', // 80
'ADD R8','ADD R9','ADD R10','ADD R11','ADD R12','ADD R13','ADD R14','ADD R15',
'SUB R0','SUB R1','SUB R2','SUB R3','SUB R4','SUB R5','SUB R6','SUB R7', // 90
'SUB R8','SUB R9','SUB R10','SUB R11','SUB R12','SUB R13','SUB R14','SUB R15',
'LD R0', 'LD R1', 'LD R2', 'LD R3', 'LD R4', 'LD R5', 'LD R6', 'LD R7', // A0
'LD R8', 'LD R9', 'LD R10','LD R11','LD R12','LD R13','LD R14','LD R15', // A8
'XCH R0','XCH R1','XCH R2','XCH R3','XCH R4','XCH R5','XCH R6','XCH R7', // B0
'XCH R8','XCH R9','XCH R10','XCH R11','XCH R12','XCH R13','XCH R14','XCH R15',
'BBL 0', 'BBL 1', 'BBL 2', 'BBL 3', 'BBL 4', 'BBL 5', 'BBL 6', 'BBL 7', // C0
'BBL 8', 'BBL 9', 'BBL 10','BBL 11','BBL 12','BBL 13','BBL 14','BBL 15', // C8
'LDM 0', 'LDM 1', 'LDM 2', 'LDM 3', 'LDM 4', 'LDM 5', 'LDM 6', 'LDM 7', // C0
'LDM 8', 'LDM 9', 'LDM 10','LDM 11','LDM 12','LDM 13','LDM 14','LDM 15', // C8
'WRM', 'WMP', 'WRR', 'WPM', 'WR0', 'WR1', 'WR2', 'WR3', // E0
'SBM', 'RDM', 'RDR', 'ADM', 'RD0', 'RD1', 'RD2', 'RD3', // E8
'CLB', 'CLC', 'IAC', 'CMC', 'CMA', 'RAL', 'RAR', 'TCC', // F0
'DAC', 'TCS', 'STC', 'DAA', 'KBP', 'DCL', '???', '???', // F8
];
Здесь немало неверных мнемоник, но в моем случае BBL и LDM указаны аргументом в виде десятичного числа. Исправил, стало лучше.
Еще одна часть - показ содержимого стека. в разных эмуляторах стек реализован неверно, или сделан с учетом механизма i4040. Камрады из канала Телеграмм подсказали, где копать. Поэтому я сделал немного иначе - оставил три регистра для стека, при использовании опкода JMS в стек записывается значение PC - адрес следующей инструкции. Потом значение стека увеличивается на 1, если индекс стека=2, то получится 0. При возврате из процедуры BBL N стек уменьшается и получается новое значение PC. Понятно, что количество вызовов ограничено - не более трех.
Стал смотреть, как сделан эмулятор JS:
Отображается по-другому. Вообще говоря, у симулятора на JS вылезло немало недостатков. Для отладки я тренировался в написании программ, например умножение
;register pairs
p0 equ 0
p1 equ 2
p2 equ 4
p3 equ 6
p4 equ 8
p5 equ 10
p6 equ 12
p7 equ 14
;http://www.massmind.org/techref/zilog/z80/part4.htm
;Mul8b: ; this routine performs the operation HL=H*E
; ld d,0 ; clearing D and L
; ld l,d
; ld b,8 ; we have 8 bits
;Mul8bLoop:
; add hl,hl ; advancing a bit
; jp nc,Mul8bSkip ; if zero, we skip the addition (jp is used for speed)
; add hl,de ; adding to the product if necessary
;Mul8bSkip:
; djnz Mul8bLoop
; ret
;R0P=R0*R3
fim 0,20h;R0=2,R1=0(HL)
fim 2,0Eh;R2=0,R3=3 (DE)
fim 4,0Ch;R5=-4; loop count
MUL:
clc
ld 1
add 1
xch 1
ld 0
add 0
xch 0
jcn cn,SKP
clc
ld 1
add 3
xch 1
ld 0
add 2
xch 0
SKP:
isz 5,MUL
END
На JS код не заработал. Стал сравнивать с исходником и обнаружил, что ассемблер слишком вольно определяет аргументы как номера регистровых пар.Вот как выглядит программа для эмулятора:
;http://e4004.szyc.org/emu/
;;R0P=R0*R3
fim 0,$20;R0=2,R1=0(HL)
fim 1,$0E;R2=0,R3=3 (DE)
fim 2,$0C;R5=-4; loop count
MUL
clc
ld 1
add 1
xch 1
ld 0
add 0
xch 0
jcn c0,SKP
clc
ld 1
add 3
xch 1
ld 0
add 2
xch 0
SKP
isz 5,MUL
Самое трудное было в проверке кода эмуляции опкодов процессора. В других исходниках обнаружен просто бардак. Например, в DAC - уменьшение аккумулятора на 1. Часто реализовано так:
void dac()
{
rega--;
if (rega & ~15) fcarry = 1;
else fcarry = 0;
rega = rega & 15;
} /* dac */
Описание говорит другое, да и для простоты прибавить 15 к аккумулятору. Тогда флаг переноса изменится иначе:
; 1001
; 1111 -1
; 11000 cy=1 no borrow
; 0000
; 1111
; 01111 cy=0 borrow
Короче, сейчас симулятор
готов. Надеюсь, что он работает как должно быть.
Комментарии
Отправить комментарий