Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
Martin Löffler | 074c1dd5ff | ||
Martin Löffler | 6955ca49ee | ||
Martin Löffler | a97824b26e | ||
Martin Löffler | 465aadd54e | ||
Martin Löffler | 01b87ade6d | ||
Martin Löffler | 8b86db359e | ||
Martin Löffler | 6d26e7de54 | ||
Martin Löffler | 8a1c99ee39 | ||
Martin Löffler | eb993eb1b4 | ||
Martin Löffler | 2fff8d2448 | ||
Martin Löffler | 6abdd5dff3 | ||
Martin Löffler | ed05dd5d30 | ||
Martin Löffler | 08a15a2e51 | ||
Martin Löffler | d8cf03afa1 | ||
Martin Löffler | cb26b0ff77 | ||
Martin Löffler | c61c814e55 | ||
Martin Löffler | c29389d3eb | ||
Martin Löffler | 51d2e29775 | ||
Martin Löffler | b9c5f33d93 | ||
Martin Löffler | d186950e99 | ||
Martin Löffler | c35340ecce | ||
Martin Löffler | 4017733dce | ||
Martin Löffler | 1b7b942dff | ||
Martin Löffler | 7737ab8de5 | ||
Martin Löffler | 17a8509898 |
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"rust-analyzer.checkOnSave.command": "clippy"
|
||||
}
|
832
cpudiag.asm
Normal file
832
cpudiag.asm
Normal 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
BIN
cpudiag.bin
Normal file
Binary file not shown.
|
@ -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| {
|
||||
|
|
|
@ -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(®ister, 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(®ister, 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(®ister, state).overflowing_add(1);
|
||||
set_cc(state, result as u16, 0b1101);
|
||||
set_register(®ister, 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(®ister, state).overflowing_sub(1);
|
||||
set_cc(state, result as u16, 0b1101);
|
||||
set_register(®ister, 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;
|
||||
}
|
||||
|
|
53
src/emulator/instructions/branch.rs
Normal file
53
src/emulator/instructions/branch.rs
Normal 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);
|
||||
}
|
|
@ -1 +1,3 @@
|
|||
pub mod arithmetic;
|
||||
pub mod branch;
|
||||
pub mod transfer;
|
||||
|
|
145
src/emulator/instructions/transfer.rs
Normal file
145
src/emulator/instructions/transfer.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
48
src/emulator/mapper/mod.rs
Normal file
48
src/emulator/mapper/mod.rs
Normal 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])
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue