Intel 4004 simulator

 


Увлекся 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

Короче, сейчас симулятор готов. Надеюсь, что он работает как должно быть.

Комментарии