Compare commits

...

25 commits

Author SHA1 Message Date
Martin Löffler 074c1dd5ff
cpudiag, fix dumb bugs 2023-01-29 01:41:51 +01:00
Martin Löffler 6955ca49ee
input/output instructions 2023-01-29 00:33:18 +01:00
Martin Löffler a97824b26e
memory mapping 2023-01-28 23:43:16 +01:00
Martin Löffler 465aadd54e
branching instructions
that's all of the 8080 instruction set besides the I/O instructions

god bless
2023-01-28 07:31:19 +01:00
Martin Löffler 01b87ade6d
16-bit exchange instructions 2023-01-28 06:26:37 +01:00
Martin Löffler 8b86db359e
stack instructions 2023-01-28 05:52:44 +01:00
Martin Löffler 6d26e7de54
fix memory related bugs
god fucking damnit that's not how the program counter works lea
2023-01-28 05:24:15 +01:00
Martin Löffler 8a1c99ee39
reading and writing words at addresses 2023-01-28 05:13:10 +01:00
Martin Löffler eb993eb1b4
shld and lhld 2023-01-26 06:57:46 +01:00
Martin Löffler 2fff8d2448
accumulator transfer instructions 2023-01-26 06:02:11 +01:00
Martin Löffler 6abdd5dff3
move instructions 2023-01-26 05:30:14 +01:00
Martin Löffler ed05dd5d30
accumulator rotate instructions
that's the rest of the arithmetics
2023-01-26 04:51:55 +01:00
Martin Löffler 08a15a2e51
fix wrong registers being parsed from instructions 2023-01-26 04:29:58 +01:00
Martin Löffler d8cf03afa1
carry and complement instructions 2023-01-26 04:23:56 +01:00
Martin Löffler cb26b0ff77
compare instructions 2023-01-26 04:16:02 +01:00
Martin Löffler c61c814e55
bitwise instructions 2023-01-26 03:52:50 +01:00
Martin Löffler c29389d3eb
subtraction 2023-01-26 03:04:12 +01:00
Martin Löffler 51d2e29775
better addition and cc setting 2023-01-26 00:30:30 +01:00
Martin Löffler b9c5f33d93
compact down add instructions 2023-01-25 23:57:48 +01:00
Martin Löffler d186950e99
16-bit increment and decrement 2023-01-25 23:44:10 +01:00
Martin Löffler c35340ecce
register pair manipulation functions 2023-01-25 23:26:16 +01:00
Martin Löffler 4017733dce
add extra derives on structs and enums 2023-01-25 22:56:19 +01:00
Martin Löffler 1b7b942dff
special instructions should come first 2023-01-25 22:48:25 +01:00
Martin Löffler 7737ab8de5
appease clippy 2023-01-25 22:45:50 +01:00
Martin Löffler 17a8509898
enable formatting and clippy in vscode 2023-01-25 22:42:27 +01:00
11 changed files with 1607 additions and 117 deletions

4
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"editor.formatOnSave": true,
"rust-analyzer.checkOnSave.command": "clippy"
}

832
cpudiag.asm Normal file
View file

@ -0,0 +1,832 @@
.PROJECT cpudiag
ORG 0x0
JMP START ;JUMP TO 0x100
;***********************************************************************
; MICROCOSM ASSOCIATES 8080/8085 CPU DIAGNOSTIC VERSION 1.0 (C) 1980
;***********************************************************************
;
;DONATED TO THE "SIG/M" CP/M USER'S GROUP BY:
;KELLY SMITH, MICROCOSM ASSOCIATES
;3055 WACO AVENUE
;SIMI VALLEY, CALIFORNIA, 93065
;(805) 527-9321 (MODEM, CP/M-NET (TM))
;(805) 527-0518 (VERBAL)
;
;
;
;
;
;
ORG 00100H
;
;
;
START:
JMP CPU ;JUMP TO 8080 CPU DIAGNOSTIC
;
;
;
DB 'MICROCOSM ASSOCIATES 8080/8085 CPU DIAGNOSTIC'
DB ' VERSION 1.0 (C) 1980'
;
;
;
;
;
;
;MESSAGE OUTPUT ROUTINE
;
MSG: PUSH D ;EXILE D REG.
PUSH A ;EXILE A REG.
XCHG ;SWAP H&L REGS. TO D&E REGS.
MSGLOOP:
LDAX D
CPI '$' ;TERMINATE IF WE ENCOUNTER A '$'
JZ MSGEND
OUT 0x1
INX D
JMP MSGLOOP
MSGEND:
POP A ;BACK FROM EXILE
POP D
RET
;
;
;
;CHARACTER OUTPUT ROUTINE
;
PCHAR: PUSH A
MOV A, E
OUT 0x1
POP A
RET
TERMINATE:
MVI A, 0xa
OUT 0x1
OUT 0x1
DI
TERMLOOP:
HLT
JMP TERMLOOP
;
;
;
BYTEO: PUSH PSW
CALL BYTO1
MOV E,A
CALL PCHAR
POP PSW
CALL BYTO2
MOV E,A
JMP PCHAR
BYTO1: RRC
RRC
RRC
RRC
BYTO2: ANI 0FH
CPI 0AH
JM BYTO3
ADI 7
BYTO3: ADI 30H
RET
;
;
;
;************************************************************
; MESSAGE TABLE FOR OPERATIONAL CPU TEST
;************************************************************
;
OKCPU: DB 0CH,0DH,0AH,' CPU IS OPERATIONAL$'
;
NGCPU: DB 0CH,0DH,0AH,' CPU HAS FAILED! ERROR EXIT=$'
;
;
;
;************************************************************
; 8080/8085 CPU TEST/DIAGNOSTIC
;************************************************************
;
;NOTE: (1) PROGRAM ASSUMES "CALL",AND "LXI SP" INSTRUCTIONS WORK!
;
; (2) INSTRUCTIONS NOT TESTED ARE "HLT","DI","EI","RIM","SIM",
; AND "RST 0" THRU "RST 7"
;
;
;
;TEST JUMP INSTRUCTIONS AND FLAGS
;
CPU: LXI SP,STACK ;SET THE STACK POINTER
ANI 0 ;INITIALIZE A REG. AND CLEAR ALL FLAGS
JZ J010 ;TEST "JZ"
CALL CPUER
J010: JNC J020 ;TEST "JNC"
CALL CPUER
J020: JPE J030 ;TEST "JPE"
CALL CPUER
J030: JP J040 ;TEST "JP"
CALL CPUER
J040: JNZ J050 ;TEST "JNZ"
JC J050 ;TEST "JC"
JPO J050 ;TEST "JPO"
JM J050 ;TEST "JM"
JMP J060 ;TEST "JMP" (IT'S A LITTLE LATE,BUT WHAT THE HELL!
J050: CALL CPUER
J060: ADI 6 ;A=6,C=0,P=1,S=0,Z=0
JNZ J070 ;TEST "JNZ"
CALL CPUER
J070: JC J080 ;TEST "JC"
JPO J080 ;TEST "JPO"
JP J090 ;TEST "JP"
J080: CALL CPUER
J090: ADI 070H ;A=76H,C=0,P=0,S=0,Z=0
JPO J100 ;TEST "JPO"
CALL CPUER
J100: JM J110 ;TEST "JM"
JZ J110 ;TEST "JZ"
JNC J120 ;TEST "JNC"
J110: CALL CPUER
J120: ADI 081H ;A=F7H,C=0,P=0,S=1,Z=0
JM J130 ;TEST "JM"
CALL CPUER
J130: JZ J140 ;TEST "JZ"
JC J140 ;TEST "JC"
JPO J150 ;TEST "JPO"
J140: CALL CPUER
J150: ADI 0FEH ;A=F5H,C=1,P=1,S=1,Z=0
JC J160 ;TEST "JC"
CALL CPUER
J160: JZ J170 ;TEST "JZ"
JPO J170 ;TEST "JPO"
JM AIMM ;TEST "JM"
J170: CALL CPUER
;
;
;
;TEST ACCUMULATOR IMMEDIATE INSTRUCTIONS
;
AIMM: CPI 0 ;A=F5H,C=0,Z=0
JC CPIE ;TEST "CPI" FOR RE-SET CARRY
JZ CPIE ;TEST "CPI" FOR RE-SET ZERO
CPI 0F5H ;A=F5H,C=0,Z=1
JC CPIE ;TEST "CPI" FOR RE-SET CARRY ("ADI")
JNZ CPIE ;TEST "CPI" FOR RE-SET ZERO
CPI 0FFH ;A=F5H,C=1,Z=0
JZ CPIE ;TEST "CPI" FOR RE-SET ZERO
JC ACII ;TEST "CPI" FOR SET CARRY
CPIE: CALL CPUER
ACII: ACI 00AH ;A=F5H+0AH+CARRY(1)=0,C=1
ACI 00AH ;A=0+0AH+CARRY(0)=0BH,C=0
CPI 00BH
JZ SUII ;TEST "ACI"
CALL CPUER
SUII: SUI 00CH ;A=FFH,C=0
SUI 00FH ;A=F0H,C=1
CPI 0F0H
JZ SBII ;TEST "SUI"
CALL CPUER
SBII: SBI 0F1H ;A=F0H-0F1H-CARRY(0)=FFH,C=1
SBI 00EH ;A=FFH-OEH-CARRY(1)=F0H,C=0
CPI 0F0H
JZ ANII ;TEST "SBI"
CALL CPUER
ANII: ANI 055H ;A=F0H<AND>55H=50H,C=0,P=1,S=0,Z=0
CPI 050H
JZ ORII ;TEST "ANI"
CALL CPUER
ORII: ORI 03AH ;A=50H<OR>3AH=7AH,C=0,P=0,S=0,Z=0
CPI 07AH
JZ XRII ;TEST "ORI"
CALL CPUER
XRII: XRI 00FH ;A=7AH<XOR>0FH=75H,C=0,P=0,S=0,Z=0
CPI 075H
JZ C010 ;TEST "XRI"
CALL CPUER
;
;
;
;TEST CALLS AND RETURNS
;
C010: ANI 000H ;A=0,C=0,P=1,S=0,Z=1
CC CPUER ;TEST "CC"
CPO CPUER ;TEST "CPO"
CM CPUER ;TEST "CM"
CNZ CPUER ;TEST "CNZ"
CPI 000H
JZ C020 ;A=0,C=0,P=0,S=0,Z=1
CALL CPUER
C020: SUI 077H ;A=89H,C=1,P=0,S=1,Z=0
CNC CPUER ;TEST "CNC"
CPE CPUER ;TEST "CPE"
CP CPUER ;TEST "CP"
CZ CPUER ;TEST "CZ"
CPI 089H
JZ C030 ;TEST FOR "CALLS" TAKING BRANCH
CALL CPUER
C030: ANI 0FFH ;SET FLAGS BACK!
CPO CPOI ;TEST "CPO"
CPI 0D9H
JZ MOVI ;TEST "CALL" SEQUENCE SUCCESS
CALL CPUER
CPOI: RPE ;TEST "RPE"
ADI 010H ;A=99H,C=0,P=0,S=1,Z=0
CPE CPEI ;TEST "CPE"
ADI 002H ;A=D9H,C=0,P=0,S=1,Z=0
RPO ;TEST "RPO"
CALL CPUER
CPEI: RPO ;TEST "RPO"
ADI 020H ;A=B9H,C=0,P=0,S=1,Z=0
CM CMI ;TEST "CM"
ADI 004H ;A=D7H,C=0,P=1,S=1,Z=0
RPE ;TEST "RPE"
CALL CPUER
CMI: RP ;TEST "RP"
ADI 080H ;A=39H,C=1,P=1,S=0,Z=0
CP TCPI ;TEST "CP"
ADI 080H ;A=D3H,C=0,P=0,S=1,Z=0
RM ;TEST "RM"
CALL CPUER
TCPI: RM ;TEST "RM"
ADI 040H ;A=79H,C=0,P=0,S=0,Z=0
CNC CNCI ;TEST "CNC"
ADI 040H ;A=53H,C=0,P=1,S=0,Z=0
RP ;TEST "RP"
CALL CPUER
CNCI: RC ;TEST "RC"
ADI 08FH ;A=08H,C=1,P=0,S=0,Z=0
CC CCI ;TEST "CC"
SUI 002H ;A=13H,C=0,P=0,S=0,Z=0
RNC ;TEST "RNC"
CALL CPUER
CCI: RNC ;TEST "RNC"
ADI 0F7H ;A=FFH,C=0,P=1,S=1,Z=0
CNZ CNZI ;TEST "CNZ"
ADI 0FEH ;A=15H,C=1,P=0,S=0,Z=0
RC ;TEST "RC"
CALL CPUER
CNZI: RZ ;TEST "RZ"
ADI 001H ;A=00H,C=1,P=1,S=0,Z=1
CZ CZI ;TEST "CZ"
ADI 0D0H ;A=17H,C=1,P=1,S=0,Z=0
RNZ ;TEST "RNZ"
CALL CPUER
CZI: RNZ ;TEST "RNZ"
ADI 047H ;A=47H,C=0,P=1,S=0,Z=0
CPI 047H ;A=47H,C=0,P=1,S=0,Z=1
RZ ;TEST "RZ"
CALL CPUER
;
;
;
;TEST "MOV","INR",AND "DCR" INSTRUCTIONS
;
MOVI: MVI A,077H
INR A
MOV B,A
INR B
MOV C,B
DCR C
MOV D,C
MOV E,D
MOV H,E
MOV L,H
MOV A,L ;TEST "MOV" A,L,H,E,D,C,B,A
DCR A
MOV C,A
MOV E,C
MOV L,E
MOV B,L
MOV D,B
MOV H,D
MOV A,H ;TEST "MOV" A,H,D,B,L,E,C,A
MOV D,A
INR D
MOV L,D
MOV C,L
INR C
MOV H,C
MOV B,H
DCR B
MOV E,B
MOV A,E ;TEST "MOV" A,E,B,H,C,L,D,A
MOV E,A
INR E
MOV B,E
MOV H,B
INR H
MOV C,H
MOV L,C
MOV D,L
DCR D
MOV A,D ;TEST "MOV" A,D,L,C,H,B,E,A
MOV H,A
DCR H
MOV D,H
MOV B,D
MOV L,B
INR L
MOV E,L
DCR E
MOV C,E
MOV A,C ;TEST "MOV" A,C,E,L,B,D,H,A
MOV L,A
DCR L
MOV H,L
MOV E,H
MOV D,E
MOV C,D
MOV B,C
MOV A,B
CPI 077H
CNZ CPUER ;TEST "MOV" A,B,C,D,E,H,L,A
;
;
;
;TEST ARITHMETIC AND LOGIC INSTRUCTIONS
;
XRA A
MVI B,001H
MVI C,003H
MVI D,007H
MVI E,00FH
MVI H,01FH
MVI L,03FH
ADD B
ADD C
ADD D
ADD E
ADD H
ADD L
ADD A
CPI 0F0H
CNZ CPUER ;TEST "ADD" B,C,D,E,H,L,A
SUB B
SUB C
SUB D
SUB E
SUB H
SUB L
CPI 078H
CNZ CPUER ;TEST "SUB" B,C,D,E,H,L
SUB A
CNZ CPUER ;TEST "SUB" A
MVI A,080H
ADD A
MVI B,001H
MVI C,002H
MVI D,003H
MVI E,004H
MVI H,005H
MVI L,006H
ADC B
MVI B,080H
ADD B
ADD B
ADC C
ADD B
ADD B
ADC D
ADD B
ADD B
ADC E
ADD B
ADD B
ADC H
ADD B
ADD B
ADC L
ADD B
ADD B
ADC A
CPI 037H
CNZ CPUER ;TEST "ADC" B,C,D,E,H,L,A
MVI A,080H
ADD A
MVI B,001H
SBB B
MVI B,0FFH
ADD B
SBB C
ADD B
SBB D
ADD B
SBB E
ADD B
SBB H
ADD B
SBB L
CPI 0E0H
CNZ CPUER ;TEST "SBB" B,C,D,E,H,L
MVI A,080H
ADD A
SBB A
CPI 0FFH
CNZ CPUER ;TEST "SBB" A
MVI A,0FFH
MVI B,0FEH
MVI C,0FCH
MVI D,0EFH
MVI E,07FH
MVI H,0F4H
MVI L,0BFH
ANA A
ANA C
ANA D
ANA E
ANA H
ANA L
ANA A
CPI 024H
CNZ CPUER ;TEST "ANA" B,C,D,E,H,L,A
XRA A
MVI B,001H
MVI C,002H
MVI D,004H
MVI E,008H
MVI H,010H
MVI L,020H
ORA B
ORA C
ORA D
ORA E
ORA H
ORA L
ORA A
CPI 03FH
CNZ CPUER ;TEST "ORA" B,C,D,E,H,L,A
MVI A,000H
MVI H,08FH
MVI L,04FH
XRA B
XRA C
XRA D
XRA E
XRA H
XRA L
CPI 0CFH
CNZ CPUER ;TEST "XRA" B,C,D,E,H,L
XRA A
CNZ CPUER ;TEST "XRA" A
MVI B,044H
MVI C,045H
MVI D,046H
MVI E,047H
MVI H,(TEMP0 / 0FFH) ;HIGH BYTE OF TEST MEMORY LOCATION
MVI L,(TEMP0 & 0FFH) ;LOW BYTE OF TEST MEMORY LOCATION
MOV M,B
MVI B,000H
MOV B,M
MVI A,044H
CMP B
CNZ CPUER ;TEST "MOV" M,B AND B,M
MOV M,D
MVI D,000H
MOV D,M
MVI A,046H
CMP D
CNZ CPUER ;TEST "MOV" M,D AND D,M
MOV M,E
MVI E,000H
MOV E,M
MVI A,047H
CMP E
CNZ CPUER ;TEST "MOV" M,E AND E,M
MOV M,H
MVI H,(TEMP0 / 0FFH)
MVI L,(TEMP0 & 0FFH)
MOV H,M
MVI A,(TEMP0 / 0FFH)
CMP H
CNZ CPUER ;TEST "MOV" M,H AND H,M
MOV M,L
MVI H,(TEMP0 / 0FFH)
MVI L,(TEMP0 & 0FFH)
MOV L,M
MVI A,(TEMP0 & 0FFH)
CMP L
CNZ CPUER ;TEST "MOV" M,L AND L,M
MVI H,(TEMP0 / 0FFH)
MVI L,(TEMP0 & 0FFH)
MVI A,032H
MOV M,A
CMP M
CNZ CPUER ;TEST "MOV" M,A
ADD M
CPI 064H
CNZ CPUER ;TEST "ADD" M
XRA A
MOV A,M
CPI 032H
CNZ CPUER ;TEST "MOV" A,M
MVI H,(TEMP0 / 0FFH)
MVI L,(TEMP0 & 0FFH)
MOV A,M
SUB M
CNZ CPUER ;TEST "SUB" M
MVI A,080H
ADD A
ADC M
CPI 033H
CNZ CPUER ;TEST "ADC" M
MVI A,080H
ADD A
SBB M
CPI 0CDH
CNZ CPUER ;TEST "SBB" M
ANA M
CNZ CPUER ;TEST "ANA" M
MVI A,025H
ORA M
CPI 037H
CNZ CPUER ;TEST "ORA" M
XRA M
CPI 005H
CNZ CPUER ;TEST "XRA" M
MVI M,055H
INR M
DCR M
ADD M
CPI 05AH
CNZ CPUER ;TEST "INR","DCR",AND "MVI" M
LXI B,12FFH
LXI D,12FFH
LXI H,12FFH
INX B
INX D
INX H
MVI A,013H
CMP B
CNZ CPUER ;TEST "LXI" AND "INX" B
CMP D
CNZ CPUER ;TEST "LXI" AND "INX" D
CMP H
CNZ CPUER ;TEST "LXI" AND "INX" H
MVI A,000H
CMP C
CNZ CPUER ;TEST "LXI" AND "INX" B
CMP E
CNZ CPUER ;TEST "LXI" AND "INX" D
CMP L
CNZ CPUER ;TEST "LXI" AND "INX" H
DCX B
DCX D
DCX H
MVI A,012H
CMP B
CNZ CPUER ;TEST "DCX" B
CMP D
CNZ CPUER ;TEST "DCX" D
CMP H
CNZ CPUER ;TEST "DCX" H
MVI A,0FFH
CMP C
CNZ CPUER ;TEST "DCX" B
CMP E
CNZ CPUER ;TEST "DCX" D
CMP L
CNZ CPUER ;TEST "DCX" H
STA TEMP0
XRA A
LDA TEMP0
CPI 0FFH
CNZ CPUER ;TEST "LDA" AND "STA"
LHLD TEMPP
SHLD TEMP0
LDA TEMPP
MOV B,A
LDA TEMP0
CMP B
CNZ CPUER ;TEST "LHLD" AND "SHLD"
LDA TEMPP+1
MOV B,A
LDA TEMP0+1
CMP B
CNZ CPUER ;TEST "LHLD" AND "SHLD"
MVI A,0AAH
STA TEMP0
MOV B,H
MOV C,L
XRA A
LDAX B
CPI 0AAH
CNZ CPUER ;TEST "LDAX" B
INR A
STAX B
LDA TEMP0
CPI 0ABH
CNZ CPUER ;TEST "STAX" B
MVI A,077H
STA TEMP0
LHLD TEMPP
LXI D,00000H
XCHG
XRA A
LDAX D
CPI 077H
CNZ CPUER ;TEST "LDAX" D AND "XCHG"
XRA A
ADD H
ADD L
CNZ CPUER ;TEST "XCHG"
MVI A,0CCH
STAX D
LDA TEMP0
CPI 0CCH
STAX D
LDA TEMP0
CPI 0CCH
CNZ CPUER ;TEST "STAX" D
LXI H,07777H
DAD H
MVI A,0EEH
CMP H
CNZ CPUER ;TEST "DAD" H
CMP L
CNZ CPUER ;TEST "DAD" H
LXI H,05555H
LXI B,0FFFFH
DAD B
MVI A,055H
CNC CPUER ;TEST "DAD" B
CMP H
CNZ CPUER ;TEST "DAD" B
MVI A,054H
CMP L
CNZ CPUER ;TEST "DAD" B
LXI H,0AAAAH
LXI D,03333H
DAD D
MVI A,0DDH
CMP H
CNZ CPUER ;TEST "DAD" D
CMP L
CNZ CPUER ;TEST "DAD" B
STC
CNC CPUER ;TEST "STC"
CMC
CC CPUER ;TEST "CMC
MVI A,0AAH
CMA
CPI 055H
CNZ CPUER ;TEST "CMA"
;ORA A ;RE-SET AUXILIARY CARRY
;DAA
;CPI 055H
;CNZ CPUER ;TEST "DAA"
;MVI A,088H
;ADD A
;DAA
;CPI 076H
;CNZ CPUER ;TEST "DAA"
;XRA A
;MVI A,0AAH
;DAA
;CNC CPUER ;TEST "DAA"
;CPI 010H
;CNZ CPUER ;TEST "DAA"
;XRA A
;MVI A,09AH
;DAA
;CNC CPUER ;TEST "DAA"
;CNZ CPUER ;TEST "DAA"
STC
MVI A,042H
RLC
CC CPUER ;TEST "RLC" FOR RE-SET CARRY
RLC
CNC CPUER ;TEST "RLC" FOR SET CARRY
CPI 009H
CNZ CPUER ;TEST "RLC" FOR ROTATION
RRC
CNC CPUER ;TEST "RRC" FOR SET CARRY
RRC
CPI 042H
CNZ CPUER ;TEST "RRC" FOR ROTATION
RAL
RAL
CNC CPUER ;TEST "RAL" FOR SET CARRY
CPI 008H
CNZ CPUER ;TEST "RAL" FOR ROTATION
RAR
RAR
CC CPUER ;TEST "RAR" FOR RE-SET CARRY
CPI 002H
CNZ CPUER ;TEST "RAR" FOR ROTATION
LXI B,01234H
LXI D,0AAAAH
LXI H,05555H
XRA A
PUSH B
PUSH D
PUSH H
PUSH PSW
LXI B,00000H
LXI D,00000H
LXI H,00000H
MVI A,0C0H
ADI 0F0H
POP PSW
POP H
POP D
POP B
CC CPUER ;TEST "PUSH PSW" AND "POP PSW"
CNZ CPUER ;TEST "PUSH PSW" AND "POP PSW"
CPO CPUER ;TEST "PUSH PSW" AND "POP PSW"
CM CPUER ;TEST "PUSH PSW" AND "POP PSW"
MVI A,012H
CMP B
CNZ CPUER ;TEST "PUSH B" AND "POP B"
MVI A,034H
CMP C
CNZ CPUER ;TEST "PUSH B" AND "POP B"
MVI A,0AAH
CMP D
CNZ CPUER ;TEST "PUSH D" AND "POP D"
CMP E
CNZ CPUER ;TEST "PUSH D" AND "POP D"
MVI A,055H
CMP H
CNZ CPUER ;TEST "PUSH H" AND "POP H"
CMP L
CNZ CPUER ;TEST "PUSH H" AND "POP H"
LXI H,00000H
DAD SP
SHLD SAVSTK ;SAVE THE "OLD" STACK-POINTER!
LXI SP,TEMP4
DCX SP
DCX SP
INX SP
DCX SP
MVI A,055H
STA TEMP2
CMA
STA TEMP3
POP B
CMP B
CNZ CPUER ;TEST "LXI","DAD","INX",AND "DCX" SP
CMA
CMP C
CNZ CPUER ;TEST "LXI","DAD","INX", AND "DCX" SP
LXI H,TEMP4
SPHL
LXI H,07733H
DCX SP
DCX SP
XTHL
LDA TEMP3
CPI 077H
CNZ CPUER ;TEST "SPHL" AND "XTHL"
LDA TEMP2
CPI 033H
CNZ CPUER ;TEST "SPHL" AND "XTHL"
MVI A,055H
CMP L
CNZ CPUER ;TEST "SPHL" AND "XTHL"
CMA
CMP H
CNZ CPUER ;TEST "SPHL" AND "XTHL"
LHLD SAVSTK ;RESTORE THE "OLD" STACK-POINTER
SPHL
LXI H,CPUOK
PCHL ;TEST "PCHL"
;
;
;
CPUER: LXI H,NGCPU ;OUTPUT "CPU HAS FAILED ERROR EXIT=" TO CONSOLE
CALL MSG
XTHL
MOV A,H
CALL BYTEO ;SHOW ERROR EXIT ADDRESS HIGH BYTE
MOV A,L
CALL BYTEO ;SHOW ERROR EXIT ADDRESS LOW BYTE
JMP TERMINATE
;
;
;
CPUOK: LXI H,OKCPU ;OUTPUT "CPU IS OPERATIONAL" TO CONSOLE
CALL MSG
JMP TERMINATE
;
;
;
TEMPP: DW TEMP0 ;POINTER USED TO TEST "LHLD","SHLD",
; AND "LDAX" INSTRUCTIONS
;
TEMP0: DS 1 ;TEMPORARY STORAGE FOR CPU TEST MEMORY LOCATIONS
TEMP1: DS 1 ;TEMPORARY STORAGE FOR CPU TEST MEMORY LOCATIONS
TEMP2 DS 1 ;TEMPORARY STORAGE FOR CPU TEST MEMORY LOCATIONS
TEMP3: DS 1 ;TEMPORARY STORAGE FOR CPU TEST MEMORY LOCATIONS
TEMP4: DS 1 ;TEMPORARY STORAGE FOR CPU TEST MEMORY LOCATIONS
SAVSTK: DS 2 ;TEMPORARY STACK-POINTER STORAGE LOCATION
;
;
;
STACK EQU TEMPP+256 ;DE-BUG STACK POINTER STORAGE AREA
;
;
;
END

BIN
cpudiag.bin Normal file

Binary file not shown.

View file

@ -15,7 +15,7 @@ fn main() {
let mut index: u32 = 0;
while let Some(byte) = data.next() {
let current_index = index.clone();
let current_index = index;
index += 1;
let mut next = |len: u8| {

View file

@ -1,69 +1,133 @@
use crate::{EmulatorState, Register, get_register, structs::set_register};
use crate::mapper::MemoryMapper;
use crate::structs::{get_register_pair, set_register, set_register_pair};
use crate::{get_register, EmulatorState, Register};
/// Sets the condition code flags according to `result`. `flags` parameter
/// indicates which flags will be set, 0b1111 will set all (Z, S, C, P)
/// while 0b1000 will only set Z.
fn set_cc(state: &mut EmulatorState, result: u16, flags: u8) {
if flags & 0b1000 > 0 { state.cc.z = (result & 0xff) == 0; }
if flags & 0b0100 > 0 { state.cc.s = (result & 0x80) > 0; }
if flags & 0b0010 > 0 { state.cc.c = result > 0xff; }
if flags & 0b0001 > 0 { state.cc.p = (result & 0xff).count_ones() % 2 == 0; }
/// Sets the condition code flags according to `result`.
/// Does not set the carry flag and will always set the Z, S and P flags.
#[inline(always)]
fn set_cc<M: MemoryMapper>(state: &mut EmulatorState<M>, result: u8) {
state.cc.z = result == 0;
state.cc.s = result & 0x80 > 0;
state.cc.p = result.count_ones() % 2 == 0;
}
/// Add values of `register` and `A`
pub fn add(register: Register, state: &mut EmulatorState) {
let result = get_register(&register, state) as u16 + state.a as u16;
set_cc(state, result, 0b1111);
state.a = (result & 0xff) as u8;
/// Add values of `register` and `A`, add +1 if carry arg is set (either false or state.cc.c)
pub fn add_reg<M: MemoryMapper>(register: Register, carry: bool, state: &mut EmulatorState<M>) {
add(get_register(register, state), carry, state);
}
/// Add values of input byte and `A`
pub fn adi(byte: u8, state: &mut EmulatorState) {
let result = state.a as u16 + byte as u16;
set_cc(state, result, 0b1111);
state.a = result as u8;
/// Add values of input byte and `A`, add +1 if carry arg is set (either false or state.cc.c)
pub fn add<M: MemoryMapper>(byte: u8, carry: bool, state: &mut EmulatorState<M>) {
let (a, first) = state.a.overflowing_add(byte);
let (result, second) = a.overflowing_add(carry as u8);
state.cc.c = first != second;
set_cc(state, result);
state.a = result;
}
/// Add values of `register` and `A` and add +1 if carry bit is set
pub fn adc(register: Register, state: &mut EmulatorState) {
let result = get_register(&register, state) as u16 + state.a as u16 + if state.cc.c { 1 } else { 0 };
set_cc(state, result, 0b1111);
state.a = (result & 0xff) as u8;
pub fn sub_reg<M: MemoryMapper>(register: Register, borrow: bool, state: &mut EmulatorState<M>) {
sub(get_register(register, state), borrow, state);
}
/// Add values of input byte and `A` and add +1 if carry bit is set
pub fn aci(byte: u8, state: &mut EmulatorState) {
let result = state.a as u16 + byte as u16 + if state.cc.c { 1 } else { 0 };
set_cc(state, result, 0b1111);
state.a = result as u8;
pub fn sub<M: MemoryMapper>(byte: u8, borrow: bool, state: &mut EmulatorState<M>) {
let (a, first) = state.a.overflowing_sub(byte);
let (result, second) = a.overflowing_sub(borrow as u8);
state.cc.c = first != second;
set_cc(state, result);
state.a = result;
}
macro_rules! bitwise_op {
($name:ident, $reg_name:ident, $op:tt) => {
pub fn $reg_name<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
$name(get_register(register, state), state);
}
pub fn $name<M: MemoryMapper>(byte: u8, state: &mut EmulatorState<M>) {
let result = state.a $op byte;
state.cc.c = false;
set_cc(state, result);
state.a = result;
}
}
}
bitwise_op!(and, and_reg, &);
bitwise_op!(or, or_reg, |);
bitwise_op!(xor, xor_reg, ^);
pub fn cmp_reg<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
cmp(get_register(register, state), state);
}
pub fn cmp<M: MemoryMapper>(byte: u8, state: &mut EmulatorState<M>) {
let (result, carry) = state.a.overflowing_sub(byte);
state.cc.c = carry;
set_cc(state, result);
}
/// Double precision add - Add B&C, D&E or H&L to H&L
pub fn dad(register: Register, state: &mut EmulatorState) {
let num = match register {
Register::B => u16::from_le_bytes([state.c, state.b]),
Register::D => u16::from_le_bytes([state.e, state.d]),
Register::H => u16::from_le_bytes([state.l, state.h]),
Register::SP => state.sp,
_ => panic!("Cannot perform DAD on register {:?}", register),
};
pub fn dad<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
let num = get_register_pair(register, state);
let (result, overflow) = num.overflowing_add(get_register_pair(Register::H, state));
let (result, overflow) = num.overflowing_add(u16::from_le_bytes([state.l, state.h]));
// this is the only 16-bit arithmetic function that sets the other flags
state.cc.z = result == 0;
state.cc.s = result & 0x8000 > 0;
state.cc.c = overflow;
state.h = (result >> 8) as u8;
state.l = result as u8;
state.cc.p = result.count_ones() % 2 == 0;
set_register_pair(Register::H, result, state);
}
/// Increase register
pub fn inr(register: Register, state: &mut EmulatorState) {
let (result, _) = get_register(&register, state).overflowing_add(1);
set_cc(state, result as u16, 0b1101);
set_register(&register, result, state);
pub fn inr<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
let (result, _) = get_register(register, state).overflowing_add(1);
set_cc(state, result);
set_register(register, result, state);
}
/// Decrease register
pub fn dcr(register: Register, state: &mut EmulatorState) {
let (result, _) = get_register(&register, state).overflowing_sub(1);
set_cc(state, result as u16, 0b1101);
set_register(&register, result, state);
pub fn dcr<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
let (result, _) = get_register(register, state).overflowing_sub(1);
set_cc(state, result);
set_register(register, result, state);
}
/// Increase register pair
pub fn inx<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
let (result, _) = get_register_pair(register, state).overflowing_add(1);
set_register_pair(register, result, state);
}
/// Decrease register pair
pub fn dcx<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
let (result, _) = get_register_pair(register, state).overflowing_sub(1);
set_register_pair(register, result, state);
}
pub fn rlc<M: MemoryMapper>(state: &mut EmulatorState<M>) {
let result = state.a.rotate_left(1);
state.a = result;
state.cc.c = result & 0x01 > 0;
}
pub fn rrc<M: MemoryMapper>(state: &mut EmulatorState<M>) {
state.cc.c = state.a & 0x01 > 0;
state.a = state.a.rotate_right(1);
}
pub fn ral<M: MemoryMapper>(state: &mut EmulatorState<M>) {
let new_carry = state.a & 0x80 > 0;
let result = state.a << 1;
state.a = result | state.cc.c as u8;
state.cc.c = new_carry;
}
pub fn rar<M: MemoryMapper>(state: &mut EmulatorState<M>) {
let new_carry = state.a & 0x01 > 0;
let result = state.a >> 1;
state.a = result | (state.cc.c as u8) << 7;
state.cc.c = new_carry;
}

View file

@ -0,0 +1,53 @@
use crate::mapper::MemoryMapper;
use crate::transfer::{pop, push};
use crate::{get_register_pair, EmulatorState, Register};
/// Jump (set PC) to specified address
pub fn jump<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
state.pc = address;
}
/// Jump to a specified address only if `cond` is true
pub fn jump_cond<M: MemoryMapper>(address: u16, cond: bool, state: &mut EmulatorState<M>) {
if cond {
jump(address, state);
}
}
/// Push the current PC to the stack and jump to the specified address
pub fn call<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
push(state.pc, state);
jump(address, state);
}
// Push the current PC to the stack and jump to the specified address if `cond` is true
pub fn call_cond<M: MemoryMapper>(address: u16, cond: bool, state: &mut EmulatorState<M>) {
if cond {
call(address, state);
}
}
// Pop a value from the stack and jump to it
pub fn ret<M: MemoryMapper>(state: &mut EmulatorState<M>) {
jump(pop(state), state);
}
// Pop a value from the stack and jump to it if `cond` is true
pub fn ret_cond<M: MemoryMapper>(cond: bool, state: &mut EmulatorState<M>) {
if cond {
ret(state)
}
}
/// "Restart" at (call) a specific interrupt vector
///
/// Panics if `vector` is 8 or above
pub fn restart<M: MemoryMapper>(vector: u8, state: &mut EmulatorState<M>) {
assert!(vector < 8, "Illegal restart vector");
call((vector * 8) as u16, state);
}
/// Load a new program counter from the HL register pair
pub fn pchl<M: MemoryMapper>(state: &mut EmulatorState<M>) {
state.pc = get_register_pair(Register::H, state);
}

View file

@ -1 +1,3 @@
pub mod arithmetic;
pub mod branch;
pub mod transfer;

View file

@ -0,0 +1,145 @@
use crate::mapper::MemoryMapper;
use crate::{
get_register, get_register_pair, set_register, set_register_pair, EmulatorState, Register,
};
/// Move (copy) value from source to destination register
pub fn mov<M: MemoryMapper>(src: Register, dest: Register, state: &mut EmulatorState<M>) {
let data = get_register(src, state);
set_register(dest, data, state);
}
/// Move immediate into destination register
pub fn mvi<M: MemoryMapper>(byte: u8, dest: Register, state: &mut EmulatorState<M>) {
set_register(dest, byte, state);
}
// Store accumulator to the speficied address
pub fn sta<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
state.write_byte(address, state.a);
}
// Load accumulator from the specified address
pub fn lda<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
state.a = state.read_byte(address);
}
/// Store accumulator using the BC or DE register pair
pub fn stax<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
let address = get_register_pair(register, state);
state.write_byte(address, state.a);
}
/// Load accumulator using the BC or DE register pair
pub fn ldax<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
let address = get_register_pair(register, state);
state.a = state.read_byte(address);
}
/// Store a 16-bit word from H and L to the specified address
pub fn shld<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
state.write_byte(address, state.l);
state.write_byte(address.overflowing_add(1).0, state.h)
}
/// Load a 16-bit word into H and L from the specified address
pub fn lhld<M: MemoryMapper>(address: u16, state: &mut EmulatorState<M>) {
state.l = state.read_byte(address);
state.h = state.read_byte(address.overflowing_add(1).0);
}
/// Exchange the HL register pair with the value at the top of the stack
pub fn xthl<M: MemoryMapper>(state: &mut EmulatorState<M>) {
let at_hl = get_register_pair(Register::H, state);
let at_stack = state.read_word(state.sp);
// Set HL to the 16-bit value currently on top of the stack
set_register_pair(Register::H, at_stack, state);
// Set the 16-bit word at the current stack position to what HL was
state.write_word(state.sp, at_hl);
}
/// Load the stack pointer from the HL register pair
pub fn sphl<M: MemoryMapper>(state: &mut EmulatorState<M>) {
state.sp = get_register_pair(Register::H, state);
}
/// Exchange the HL and DE register pairs
pub fn xchg<M: MemoryMapper>(state: &mut EmulatorState<M>) {
let at_hl = get_register_pair(Register::H, state);
let at_de = get_register_pair(Register::D, state);
// Set HL to DE
set_register_pair(Register::H, at_de, state);
// Set DE to previous value of HL
set_register_pair(Register::D, at_hl, state);
}
/* STACK */
/// Push a 16-bit value from a register pair onto the stack
pub fn push_reg<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
push(get_register_pair(register, state), state);
}
/// Push a 16-bit value onto the stack
pub fn push<M: MemoryMapper>(value: u16, state: &mut EmulatorState<M>) {
(state.sp, ..) = state.sp.overflowing_sub(2);
state.write_word(state.sp, value);
}
/// Pop a 16-bit value from the stack into a register pair
pub fn pop_reg<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) {
set_register_pair(register, pop(state), state);
}
/// Pop a 16-bit value from the stack
pub fn pop<M: MemoryMapper>(state: &mut EmulatorState<M>) -> u16 {
let value = state.read_word(state.sp);
(state.sp, ..) = state.sp.overflowing_add(2);
value
}
#[cfg(test)]
mod tests {
use crate::mapper::TestMapper;
use crate::EmulatorState;
#[test]
fn push() {
// From the altairclone.com 8080 programmers manual:
// (1) The most significant 8 bits of data are stored at the
// memory address one less than the contents of the
// stack pointer.
// (2) The least significant 8 bits of data are stored at the
// memory address two less than the contents of the
// stack pointer.
// (3) The stack pointer is automatically decremented by two.
let mut state: EmulatorState<TestMapper> = EmulatorState {
sp: 0x12,
..Default::default()
};
super::push(0x1234, &mut state);
assert_eq!(state.sp, 0x10);
assert_eq!(state.mapper.0[0x10..=0x11], [0x34, 0x12]);
}
#[test]
fn pop() {
// From the altairclone.com 8080 programmers manual:
// (1) The second register of the pair, or the least significant
// 8 bits of the program counter, are loaded from the
// memory address held in the stack pointer.
// (2) The first register of the pair, or the most significant
// 8 bits of the program counter, are loaded from the
// memory address one greater than the address held in
// the stack pointer.
// (3) The stack pointer is automatically incremented by two.
let mut state: EmulatorState<TestMapper> = EmulatorState {
sp: 0x10,
..Default::default()
};
state.mapper.0[0x10] = 0x34;
state.mapper.0[0x11] = 0x12;
assert_eq!(super::pop(&mut state), 0x1234);
}
}

View file

@ -1,28 +1,18 @@
use std::{fs, env};
use instructions::arithmetic;
use instructions::{arithmetic, branch, transfer};
use mapper::MemoryMapper;
use std::{env, fs};
use crate::structs::*;
mod instructions;
mod mapper;
mod structs;
pub const MEMORY_SIZE: usize = 8192;
fn main() {
let mut state = EmulatorState {
a: 0,
b: 0,
c: 0,
d: 0,
e: 0,
h: 0,
l: 0,
sp: 0,
cc: ConditionCodes { z: true, s: true, p: true, c: true },
pc: 0,
ei: true,
memory: [0; MEMORY_SIZE],
};
use crate::mapper::TestMapper;
let mut state = EmulatorState::<TestMapper>::default();
// Load the ROM into memory
let mut args = env::args();
@ -31,11 +21,10 @@ fn main() {
.expect("Provide a path to a ROM file to emulate as an argument");
let file = fs::read(filename).expect("where file");
for i in 0..(MEMORY_SIZE.min(file.len())) {
state.memory[i] = file[i];
}
let to_copy = state.mapper.0.len().min(file.len());
state.mapper.0[..to_copy].copy_from_slice(&file[..to_copy]);
while state.pc < MEMORY_SIZE as u16 {
while state.pc < u16::MAX {
tick(&mut state);
}
@ -43,19 +32,82 @@ fn main() {
print_state(&state);
}
fn tick(state: &mut EmulatorState) {
let instruction = state.memory[state.pc as usize];
let mut next_byte = || {
state.pc += 1;
return state.memory[state.pc as usize];
};
fn tick<M: MemoryMapper>(state: &mut EmulatorState<M>) {
let instruction = state.next_byte();
match instruction {
0x00 => {} // NOP
/* Maths */
/* Special */
0xd3 => {
// OUT
let port = state.next_byte();
state.write_io(port, state.a);
}
0xdb => {
// IN
let port = state.next_byte();
state.a = state.read_io(port);
}
0xfb => state.ei = true, // EI
0xf3 => state.ei = false, // DI
0x76 => {
// HLT
if state.ei {
todo!()
} else {
println!("HLT called after DI; exiting.");
print_state(state);
std::process::exit(0);
}
}
/* Data Transfer */
// MVI
0x06 | 0x0e | 0x16 | 0x1e | 0x26 | 0x2e | 0x36 | 0x3e => transfer::mvi(
state.next_byte(),
register_from_num((instruction & 0x38) >> 3),
state,
),
// MOV
0x40..=0x7f => transfer::mov(
register_from_num(instruction & 0x7),
register_from_num((instruction & 0x38) >> 3),
state,
),
// Accumulator transfer instructions
0x02 => transfer::stax(Register::B, state), // STAX B
0x0a => transfer::ldax(Register::B, state), // LDAX B
0x12 => transfer::stax(Register::D, state), // STAX D
0x1a => transfer::ldax(Register::D, state), // LDAX D
0x32 => transfer::sta(state.next_word(), state), // STA
0x3a => transfer::lda(state.next_word(), state), // LDA
// 16-bit transfer instructions
0x01 => set_register_pair(Register::B, state.next_word(), state), // LXI B
0x11 => set_register_pair(Register::D, state.next_word(), state), // LXI D
0x21 => set_register_pair(Register::H, state.next_word(), state), // LXI H
0x31 => set_register_pair(Register::SP, state.next_word(), state), // LXI SP
0x22 => transfer::shld(state.next_word(), state), // SHLD
0x2a => transfer::lhld(state.next_word(), state), // LHLD
0xe3 => transfer::xthl(state),
0xeb => transfer::xchg(state),
0xf9 => transfer::sphl(state),
// Stack instructions
0xc1 => transfer::pop_reg(Register::B, state), // POP B
0xc5 => transfer::push_reg(Register::B, state), // PUSH B
0xd1 => transfer::pop_reg(Register::D, state), // POP D
0xd5 => transfer::push_reg(Register::D, state), // PUSH D
0xe1 => transfer::pop_reg(Register::H, state), // POP H
0xe5 => transfer::push_reg(Register::H, state), // PUSH H
0xf1 => transfer::pop_reg(Register::A, state), // POP PSW
0xf5 => transfer::push_reg(Register::A, state), // PUSH PSW
/* Maths */
// INR
0x04 => arithmetic::inr(Register::B, state),
0x0c => arithmetic::inr(Register::C, state),
@ -82,28 +134,104 @@ fn tick(state: &mut EmulatorState) {
0x29 => arithmetic::dad(Register::H, state),
0x39 => arithmetic::dad(Register::SP, state),
0x80..=0x87 => arithmetic::add(register_from_num(instruction & 0xf), state), // ADD
0x88..=0x8f => arithmetic::adc(register_from_num(instruction & 0xf), state), // ADC
0xc6 => arithmetic::adi(next_byte(), state), // ADI
0xce => arithmetic::aci(next_byte(), state), // ACI
// INX
0x03 => arithmetic::inx(Register::B, state),
0x13 => arithmetic::inx(Register::D, state),
0x23 => arithmetic::inx(Register::H, state),
0x33 => arithmetic::inx(Register::SP, state),
// DCX
0x0b => arithmetic::dcx(Register::B, state),
0x1b => arithmetic::dcx(Register::D, state),
0x2b => arithmetic::dcx(Register::H, state),
0x3b => arithmetic::dcx(Register::SP, state),
/* Special */
0xfb => state.ei = true, // EI
0xf3 => state.ei = false, // DI
0x76 => if state.ei { todo!() } else { // HLT
println!("HLT called after DI; exiting.");
print_state(state);
std::process::exit(0);
},
// Accumulator rotates
0x07 => arithmetic::rlc(state),
0x0f => arithmetic::rrc(state),
0x17 => arithmetic::ral(state),
0x1f => arithmetic::rar(state),
// Carry and complements
0x27 => panic!("Auxiliary Carry not implemented, unable to execute DAA instruction"),
0x2f => state.a = !state.a, // CMA
0x37 => state.cc.c = true, // STC
0x3f => state.cc.c = !state.cc.c, // CMC
0x80..=0x87 => arithmetic::add_reg(register_from_num(instruction & 0x7), false, state), // ADD
0x88..=0x8f => arithmetic::add_reg(register_from_num(instruction & 0x7), state.cc.c, state), // ADC
0xc6 => arithmetic::add(state.next_byte(), false, state), // ADI
0xce => arithmetic::add(state.next_byte(), state.cc.c, state), // ACI
0x90..=0x97 => arithmetic::sub_reg(register_from_num(instruction & 0x7), false, state), // SUB
0x98..=0x9f => arithmetic::sub_reg(register_from_num(instruction & 0x7), state.cc.c, state), // SBB
0xd6 => arithmetic::sub(state.next_byte(), false, state), // SUI
0xde => arithmetic::sub(state.next_byte(), state.cc.c, state), // SBI
0xa0..=0xa7 => arithmetic::and_reg(register_from_num(instruction & 0x7), state), // ANA
0xa8..=0xaf => arithmetic::xor_reg(register_from_num(instruction & 0x7), state), // XRA
0xb0..=0xb7 => arithmetic::or_reg(register_from_num(instruction & 0x7), state), // ORA
0xb8..=0xbf => arithmetic::cmp_reg(register_from_num(instruction & 0x7), state), // CMP
0xe6 => arithmetic::and(state.next_byte(), state), // ANI
0xee => arithmetic::xor(state.next_byte(), state), // XRI
0xf6 => arithmetic::or(state.next_byte(), state), // ORI
0xfe => arithmetic::cmp(state.next_byte(), state), // CPI
/* Branch instructions */
// Jumps
0xc2 => branch::jump_cond(state.next_word(), !state.cc.z, state), // JNZ
0xc3 => branch::jump(state.next_word(), state), // JMP
0xca => branch::jump_cond(state.next_word(), state.cc.z, state), // JZ
0xd2 => branch::jump_cond(state.next_word(), !state.cc.c, state), // JNC
0xda => branch::jump_cond(state.next_word(), state.cc.c, state), // JC
0xe2 => branch::jump_cond(state.next_word(), !state.cc.p, state), // JPO
0xea => branch::jump_cond(state.next_word(), state.cc.p, state), // JPE
0xf2 => branch::jump_cond(state.next_word(), !state.cc.s, state), // JP
0xfa => branch::jump_cond(state.next_word(), state.cc.s, state), // JM
// Calls
0xc4 => branch::call_cond(state.next_word(), !state.cc.z, state), // CNZ
0xcc => branch::call_cond(state.next_word(), state.cc.z, state), // CZ
0xcd => branch::call(state.next_word(), state), // CALL
0xd4 => branch::call_cond(state.next_word(), !state.cc.c, state), // CNC
0xdc => branch::call_cond(state.next_word(), state.cc.c, state), // CC
0xe4 => branch::call_cond(state.next_word(), !state.cc.p, state), // CPO
0xec => branch::call_cond(state.next_word(), state.cc.p, state), // CPE
0xf4 => branch::call_cond(state.next_word(), !state.cc.s, state), // CP
0xfc => branch::call_cond(state.next_word(), state.cc.s, state), // CM
// Returns
0xc0 => branch::ret_cond(!state.cc.z, state), // RNZ
0xc8 => branch::ret_cond(state.cc.z, state), // RZ
0xc9 => branch::ret(state), // RET
0xd0 => branch::ret_cond(!state.cc.c, state), // RNC
0xd8 => branch::ret_cond(state.cc.c, state), // RC
0xe0 => branch::ret_cond(!state.cc.p, state), // RPO
0xe8 => branch::ret_cond(state.cc.p, state), // RPE
0xf0 => branch::ret_cond(!state.cc.s, state), // RP
0xf8 => branch::ret_cond(state.cc.s, state), // RM
// Restarts
0xc7 => branch::restart(0, state),
0xcf => branch::restart(1, state),
0xd7 => branch::restart(2, state),
0xdf => branch::restart(3, state),
0xe7 => branch::restart(4, state),
0xef => branch::restart(5, state),
0xf7 => branch::restart(6, state),
0xff => branch::restart(7, state),
// PCHL
0xe9 => branch::pchl(state),
_ => not_implemented(state),
}
state.pc += 1;
}
fn not_implemented(state: &EmulatorState) {
let instruction = state.memory[state.pc as usize];
panic!("Unimplemented instruction {:#02X} at {:#04X}", instruction, state.pc);
fn not_implemented<M: MemoryMapper>(state: &mut EmulatorState<M>) {
let instruction = state.read_byte(state.pc);
panic!(
"Unimplemented instruction {:#02X} at {:#04X}",
instruction, state.pc
);
}

View file

@ -0,0 +1,48 @@
pub trait MemoryMapper {
/// Read a byte at the specified address through the memory mapper
fn read(&mut self, address: u16) -> u8;
/// Write a byte to the specified address through the memory mapper
fn write(&mut self, address: u16, value: u8);
/// Receive a byte from a device on the I/O bus through the memory mapper
#[allow(unused_variables)]
fn read_io(&mut self, port: u8) -> u8 {
0
}
/// Send a byte to a device on the I/O bus through the memory mapper
#[allow(unused_variables)]
fn write_io(&mut self, port: u8, value: u8) {}
}
//#[cfg(test)]
pub struct TestMapper(pub [u8; u16::MAX as usize + 1]);
//#[cfg(test)]
impl MemoryMapper for TestMapper {
fn read(&mut self, address: u16) -> u8 {
self.0[address as usize]
}
fn write(&mut self, address: u16, value: u8) {
self.0[address as usize] = value;
}
fn write_io(&mut self, port: u8, value: u8) {
if port == 0x1 {
if value == b'\n' {
println!();
} else {
print!("{}", value as char);
}
}
}
}
//#[cfg(test)]
impl Default for TestMapper {
fn default() -> Self {
Self([0; u16::MAX as usize + 1])
}
}

View file

@ -1,6 +1,7 @@
use crate::MEMORY_SIZE;
use crate::mapper::MemoryMapper;
pub struct EmulatorState {
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct EmulatorState<M: MemoryMapper> {
pub a: u8,
pub b: u8,
pub c: u8,
@ -17,10 +18,121 @@ pub struct EmulatorState {
pub pc: u16,
/// Enable interrupts
pub ei: bool,
/// Memory map
pub memory: [u8; MEMORY_SIZE],
/// Memory mapper
pub mapper: M,
}
impl<M: MemoryMapper> EmulatorState<M> {
pub fn new(mapper: M) -> Self {
Self {
a: 0,
b: 0,
c: 0,
d: 0,
e: 0,
h: 0,
l: 0,
sp: 0,
cc: ConditionCodes {
z: true,
s: true,
p: true,
c: true,
},
pc: 0,
ei: false,
mapper,
}
}
/// Read a byte at a specific address
///
/// May return an indeterminate value if an invalid region is accessed.
/// May cause internal mapper state (eg. interrupts, emulated shift
/// registers) to be updated. Subsequent reads from the same address may
/// yield different values.
#[inline]
pub fn read_byte(&mut self, address: u16) -> u8 {
self.mapper.read(address)
}
/// Write a byte at a specific address
///
/// May do nothing if an invalid region is accessed. Subsequent reads
/// from the same address may yield different values.
#[inline]
pub fn write_byte(&mut self, address: u16, value: u8) {
self.mapper.write(address, value);
}
/// Read the next byte from memory pointed at by PC
#[inline]
pub fn next_byte(&mut self) -> u8 {
let value = self.read_byte(self.pc);
(self.pc, ..) = self.pc.overflowing_add(1);
value
}
/// Read a 16-bit word at a specific address
///
/// May return an indeterminate value if an invalid region is accessed.
/// May cause internal mapper state (eg. interrupts, emulated shift
/// registers) to be updated. Subsequent reads from the same address may
/// yield different values.
#[inline]
pub fn read_word(&mut self, address: u16) -> u16 {
u16::from_le_bytes([
self.mapper.read(address),
self.mapper.read(address.overflowing_add(1).0),
])
}
/// Write a 16-bit word at a specific address
///
/// May do nothing if an invalid region is accessed. Subsequent reads
/// from the same address may yield different values.
#[inline]
pub fn write_word(&mut self, address: u16, value: u16) {
let [low, high] = u16::to_le_bytes(value);
self.mapper.write(address, low);
self.mapper.write(address.overflowing_add(1).0, high);
}
/// Read the next 16-bit word from memory pointed at by PC, in little endian order
#[inline]
pub fn next_word(&mut self) -> u16 {
let value = self.read_word(self.pc);
(self.pc, ..) = self.pc.overflowing_add(2);
value
}
/// Receive a byte from a device on the I/O bus
///
/// May return an indeterminate value if an invalid device is accessed.
/// May (and usually will) cause internal mapper state (eg. interrupts)
/// to be updated.
#[inline]
pub fn read_io(&mut self, port: u8) -> u8 {
self.mapper.read_io(port)
}
/// Send a byte to a device on the I/O bus
///
/// May do nothing if an invalid device is accessed.
#[inline]
pub fn write_io(&mut self, port: u8, value: u8) {
self.mapper.write_io(port, value);
}
}
impl<M: MemoryMapper + Default> Default for EmulatorState<M> {
fn default() -> Self {
Self::new(M::default())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct ConditionCodes {
/// Zero (Z), set if the result is zero.
pub z: bool,
@ -35,15 +147,21 @@ pub struct ConditionCodes {
// ac: bool,
}
#[derive(PartialEq)]
#[derive(Debug)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Register {
B, C, D, E,
H, L, M, A,
B,
C,
D,
E,
H,
L,
M,
A,
SP,
}
/// Returns a Register enum based on the input number 0..7 in the order B,C,D,E,H,L,M,A
#[track_caller]
pub fn register_from_num(b: u8) -> Register {
match b {
0 => Register::B,
@ -58,21 +176,21 @@ pub fn register_from_num(b: u8) -> Register {
}
}
pub fn get_register(register: &Register, state: &EmulatorState) -> u8 {
pub fn get_register<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) -> u8 {
match register {
Register::B => state.b as u8,
Register::C => state.c as u8,
Register::D => state.d as u8,
Register::E => state.e as u8,
Register::H => state.h as u8,
Register::L => state.l as u8,
Register::A => state.a as u8,
Register::M => state.memory[u16::from_le_bytes([state.l, state.h]) as usize],
Register::B => state.b,
Register::C => state.c,
Register::D => state.d,
Register::E => state.e,
Register::H => state.h,
Register::L => state.l,
Register::A => state.a,
Register::M => state.mapper.read(u16::from_le_bytes([state.l, state.h])),
Register::SP => unreachable!(),
}
}
pub fn set_register(register: &Register, value: u8, state: &mut EmulatorState) {
pub fn set_register<M: MemoryMapper>(register: Register, value: u8, state: &mut EmulatorState<M>) {
match register {
Register::B => state.b = value,
Register::C => state.c = value,
@ -81,23 +199,119 @@ pub fn set_register(register: &Register, value: u8, state: &mut EmulatorState) {
Register::H => state.h = value,
Register::L => state.l = value,
Register::A => state.a = value,
Register::M => state.memory[u16::from_le_bytes([state.l, state.h]) as usize] = value,
Register::M => state
.mapper
.write(u16::from_le_bytes([state.l, state.h]), value),
Register::SP => panic!("Cannot set 'SP' through set_register()"),
};
}
pub fn get_register_pair<M: MemoryMapper>(register: Register, state: &mut EmulatorState<M>) -> u16 {
match register {
Register::B => u16::from_le_bytes([state.c, state.b]),
Register::D => u16::from_le_bytes([state.e, state.d]),
Register::H => u16::from_le_bytes([state.l, state.h]),
Register::A => {
// the PSW looks like this: SZ0A0P1C
let flags: u8 = u8::from(state.cc.s) << 7 // bit 7
| u8::from(state.cc.z) << 6 // bit 6
//| u8::from(state.cc.a) << 4 // bit 4
| u8::from(state.cc.p) << 2 // bit 2
| 0x02 // bit 1
| u8::from(state.cc.c); // bit 0
u16::from_le_bytes([flags, state.a])
}
Register::SP => state.sp,
_ => unreachable!(),
}
}
pub fn set_register_pair<M: MemoryMapper>(
register: Register,
value: u16,
state: &mut EmulatorState<M>,
) {
let arr = value.to_le_bytes();
let high = arr[1];
let low = arr[0];
match register {
Register::B => {
state.b = high;
state.c = low;
}
Register::D => {
state.d = high;
state.e = low;
}
Register::H => {
state.h = high;
state.l = low;
}
Register::A => {
state.a = high;
// the PSW looks like this: SZ0A0P1C
state.cc.s = low & 0b1000_0000 > 0;
state.cc.z = low & 0b0100_0000 > 0;
debug_assert!(low & 0b0010_0000 == 0, "malformed PSW");
//state.cc.a = low & 0b0001_0000 > 0;
debug_assert!(low & 0b0000_1000 == 0, "malformed PSW");
state.cc.p = low & 0b0000_0100 > 0;
debug_assert!(low & 0b0000_0010 > 0, "malformed PSW");
state.cc.c = low & 0b0000_0001 > 0;
}
Register::SP => state.sp = value,
_ => unreachable!(),
}
}
/// Print values of registers and flags to stdout
pub fn print_state(state: &EmulatorState) {
pub fn print_state<M: MemoryMapper>(state: &EmulatorState<M>) {
// State
println!("\nsp\tpc\tei");
println!("{:#06x}\t{:#06x}\t{}", state.sp, state.pc, state.ei);
// Registers
println!("\nB\tC\tD\tE\tH\tL\tA");
println!("{:#04x}\t{:#04x}\t{:#04x}\t{:#04x}\t{:#04x}\t{:#04x}\t{:#04x}",
state.b, state.c, state.d, state.e, state.h, state.l, state.a);
println!(
"{:#04x}\t{:#04x}\t{:#04x}\t{:#04x}\t{:#04x}\t{:#04x}\t{:#04x}",
state.b, state.c, state.d, state.e, state.h, state.l, state.a
);
// Flags
println!("\nz\ts\tp\tc");
println!("{}\t{}\t{}\t{}", state.cc.z, state.cc.s, state.cc.p, state.cc.c);
println!(
"{}\t{}\t{}\t{}",
state.cc.z, state.cc.s, state.cc.p, state.cc.c
);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mapper::TestMapper;
#[test]
fn read_word() {
let mut state = EmulatorState::<TestMapper>::default();
state.mapper.0[0x10] = 0x34;
state.mapper.0[0x11] = 0x12;
assert_eq!(state.read_word(0x10), 0x1234);
}
#[test]
fn write_word() {
let mut state = EmulatorState::<TestMapper>::default();
state.write_word(0x10, 0x1234);
assert_eq!(state.mapper.0[0x10..=0x11], [0x34, 0x12]);
}
#[test]
fn next_word_at_pc() {
let mut state = EmulatorState::<TestMapper>::default();
state.mapper.0[0x10] = 0x34;
state.mapper.0[0x11] = 0x12;
state.pc = 0x10;
assert_eq!(state.next_word(), 0x1234);
}
}