817 lines
26 KiB
NASM
817 lines
26 KiB
NASM
;
|
|
; CHIP-8 emulation core
|
|
;
|
|
|
|
EMBEDSAMPLE = true
|
|
|
|
EF_SHIFTX = %0000_0001
|
|
EF_REGS = %0001_0000
|
|
EF_LOAD = %0010_0000
|
|
EF_PAUSE = %0100_0000
|
|
EF_RESET = %1000_0000
|
|
|
|
C8START = $0200
|
|
|
|
.section zp
|
|
zptr1 .word ?
|
|
zptr2 .word ?
|
|
pc .word ?
|
|
.endsection zp
|
|
|
|
.section bss
|
|
reg .block
|
|
v .fill 16
|
|
i .word ?
|
|
sp .byte ?
|
|
dt .byte ?
|
|
st .byte ?
|
|
pc .word ?
|
|
.endblock
|
|
ins .block
|
|
opcode .word ?
|
|
c .byte ?
|
|
x .byte ?
|
|
y .byte ?
|
|
w .word ?
|
|
ptr .word ?
|
|
.endblock
|
|
c8stack .fill 48
|
|
rngst .fill 4
|
|
rng_t1 .byte ?
|
|
c8screen .fill (64*32)/8
|
|
c8screenend = *-1
|
|
scrdirty .byte ?
|
|
.endsection bss
|
|
|
|
.section code
|
|
|
|
; This is called 60 times per second from the machine specific interrupt
|
|
; handler
|
|
Tick .proc
|
|
lda reg.dt
|
|
beq +
|
|
dec reg.dt
|
|
+ lda reg.st
|
|
beq +
|
|
dec reg.st
|
|
bne +
|
|
jsr SoundOff
|
|
+ rts
|
|
.endproc
|
|
|
|
C8Reset .proc
|
|
.CopyMem sprites, C8RAM, size(sprites)
|
|
;.FillMem C8RAM+size(sprites), 0, C8RAMSIZE-size(sprites)
|
|
.if EMBEDSAMPLE
|
|
.CopyMem samplecode, C8RAM+C8START, size(samplecode)
|
|
.endif
|
|
warm .FillMem reg, 0, size(reg)
|
|
jsr c8cls
|
|
jsr SoundOff
|
|
.if (C8START & $ff) != 0
|
|
lda #<C8START
|
|
sta reg.pc
|
|
.endif
|
|
.if (C8START >> 8) != 0
|
|
lda #>C8START
|
|
sta reg.pc+1
|
|
.endif
|
|
lda eflags
|
|
and #~EF_RESET
|
|
sta eflags
|
|
rts
|
|
.endproc
|
|
|
|
C8Run .proc
|
|
jsr C8Reset
|
|
|
|
fetch
|
|
jsr HeartBeat
|
|
lda eflags
|
|
tax
|
|
and #EF_REGS
|
|
beq +
|
|
jsr ShowRegisters
|
|
+ txa
|
|
and #EF_RESET
|
|
beq +
|
|
lda eflags
|
|
and #~EF_RESET
|
|
sta eflags
|
|
jsr C8Reset.warm
|
|
jmp fetch
|
|
+ txa
|
|
and #EF_PAUSE
|
|
beq +
|
|
jsr VideoDraw
|
|
- lda eflags
|
|
and #EF_PAUSE
|
|
bne -
|
|
+ txa
|
|
and #EF_LOAD
|
|
beq +
|
|
lda eflags
|
|
and #~EF_LOAD
|
|
sta eflags
|
|
jsr InputFileName
|
|
lda ch8filenamelen
|
|
beq +
|
|
jsr LoadCH8
|
|
jsr C8Reset.warm
|
|
jmp fetch
|
|
+ lda scrdirty
|
|
beq +
|
|
jsr GetVBlank
|
|
bcs +
|
|
jsr VideoDraw
|
|
|
|
+ clc
|
|
lda #<C8RAM
|
|
adc reg.pc
|
|
sta pc
|
|
lda #>C8RAM
|
|
adc reg.pc+1
|
|
sta pc+1
|
|
; advance reg.pc
|
|
clc
|
|
lda #2
|
|
adc reg.pc
|
|
sta reg.pc
|
|
lda #0
|
|
adc reg.pc+1
|
|
sta reg.pc+1
|
|
|
|
decode
|
|
ldy #0
|
|
lda (pc), y
|
|
sta ins.opcode+1
|
|
and #$0f
|
|
sta ins.w+1
|
|
sta ins.x
|
|
lda (pc), y
|
|
lsr a
|
|
lsr a
|
|
lsr a
|
|
lsr a
|
|
sta ins.c
|
|
iny
|
|
lda (pc), y
|
|
sta ins.opcode
|
|
sta ins.w
|
|
lsr a
|
|
lsr a
|
|
lsr a
|
|
lsr a
|
|
sta ins.y
|
|
lda ins.c
|
|
cmp #8
|
|
bne +
|
|
; decode 8xxx instructions
|
|
lda ins.w
|
|
and #$0f
|
|
asl a
|
|
tay
|
|
lda jumptable.op8, y
|
|
sta ins.ptr
|
|
lda jumptable.op8+1, y
|
|
sta ins.ptr+1
|
|
bne execute
|
|
beq oINV
|
|
+ asl a
|
|
tay
|
|
lda jumptable.main, y
|
|
sta ins.ptr
|
|
lda jumptable.main+1, y
|
|
sta ins.ptr+1
|
|
bne execute
|
|
ldy #0
|
|
- lda jumptable.sparse+3, y
|
|
beq oINV
|
|
lda ins.opcode
|
|
cmp jumptable.sparse, y
|
|
bne +
|
|
lda ins.opcode+1
|
|
and #$f0
|
|
cmp jumptable.sparse+1, y
|
|
bne +
|
|
lda jumptable.sparse+2, y
|
|
sta ins.ptr
|
|
lda jumptable.sparse+3, y
|
|
sta ins.ptr+1
|
|
bne execute
|
|
beq oINV
|
|
+ iny
|
|
iny
|
|
iny
|
|
iny
|
|
bne -
|
|
execute
|
|
ldx ins.x
|
|
ldy ins.y
|
|
jmp (ins.ptr)
|
|
.endproc
|
|
|
|
FetchNext .macro
|
|
jmp C8Run.fetch
|
|
.endmacro
|
|
|
|
oINV .proc
|
|
inc $d020 ; FIXME
|
|
jmp *
|
|
.endproc
|
|
|
|
oNOP .proc
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 00E0 - CLS
|
|
oCLS .proc
|
|
jsr c8cls
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 00EE - RET
|
|
oRET .proc
|
|
lda reg.sp
|
|
asl a
|
|
tax
|
|
lda c8stack, x
|
|
sta reg.pc
|
|
lda c8stack+1, x
|
|
sta reg.pc+1
|
|
dec reg.sp
|
|
; FIXME: check stack boundaries
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 2nnn - CALL addr
|
|
oCALL .proc
|
|
inc reg.sp
|
|
; FIXME: check stack boundaries
|
|
lda reg.sp
|
|
asl a
|
|
tax
|
|
lda reg.pc
|
|
sta c8stack, x
|
|
lda reg.pc+1
|
|
sta c8stack+1, x
|
|
; could fallthrough to oJP instead
|
|
lda ins.w
|
|
sta reg.pc
|
|
lda ins.w+1
|
|
and #$0f
|
|
sta reg.pc+1
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 1nnn - JP addr
|
|
oJP .proc
|
|
lda ins.w
|
|
sta reg.pc
|
|
stx reg.pc+1
|
|
.FetchNext
|
|
.endproc
|
|
|
|
skip .proc
|
|
clc
|
|
lda #2
|
|
adc reg.pc
|
|
sta reg.pc
|
|
lda #0
|
|
adc reg.pc+1
|
|
sta reg.pc+1
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 3xkk - SE Vx, byte
|
|
oSEK .proc
|
|
lda reg.v, x
|
|
cmp ins.w
|
|
beq skip
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 4xkk - SNE Vx, byte
|
|
oSNEK .proc
|
|
lda reg.v, x
|
|
cmp ins.w
|
|
bne skip
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 5xy0 - SE Vx, Vy
|
|
oSE .proc
|
|
lda reg.v, x
|
|
cmp reg.v, y
|
|
beq skip
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 6xkk - LD Vx, byte
|
|
oLDn .proc
|
|
lda ins.w
|
|
sta reg.v, x
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 7xkk - ADD Vx, byte
|
|
oADDn .proc
|
|
lda ins.w
|
|
clc
|
|
adc reg.v, x
|
|
sta reg.v, x
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 8xy0 - LD Vx, Vy
|
|
oLD .proc
|
|
lda reg.v, y
|
|
sta reg.v, x
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 8xy1 - OR Vx, Vy
|
|
oOR .proc
|
|
lda reg.v, y
|
|
ora reg.v, x
|
|
sta reg.v, x
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 8xy2 - AND Vx, Vy
|
|
oAND .proc
|
|
lda reg.v, y
|
|
and reg.v, x
|
|
sta reg.v, x
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 8xy3 - XOR Vx, Vy
|
|
oXOR .proc
|
|
lda reg.v, y
|
|
eor reg.v, x
|
|
sta reg.v, x
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 8xy4 - ADD Vx, Vy
|
|
oADD .proc
|
|
lda reg.v, y
|
|
clc
|
|
adc reg.v, x
|
|
sta reg.v, x
|
|
lda #0
|
|
rol a
|
|
sta reg.v+$f
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 8xy5 - SUB Vx, Vy
|
|
oSUB .proc
|
|
lda reg.v, x
|
|
sec
|
|
sbc reg.v, y
|
|
sta reg.v, x
|
|
lda #0
|
|
rol a
|
|
sta reg.v+$f
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 8xy6 - SHR Vx {, Vy}
|
|
oSHR .proc
|
|
lda #EF_SHIFTX
|
|
and eflags
|
|
beq _shifty
|
|
lda reg.v, x
|
|
lsr a
|
|
bpl +
|
|
_shifty
|
|
lda reg.v, y
|
|
lsr a
|
|
sta reg.v, y
|
|
+ sta reg.v, x
|
|
lda #0
|
|
rol a
|
|
sta reg.v+$f
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 8xy7 - SUBN Vx, Vy
|
|
oSUBN .proc
|
|
lda reg.v, y
|
|
sec
|
|
sbc reg.v, x
|
|
sta reg.v, x
|
|
lda #0
|
|
rol a
|
|
sta reg.v+$f
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 8xyE - SHL Vx {, Vy}
|
|
oSHL .proc
|
|
lda #EF_SHIFTX
|
|
and eflags
|
|
clc
|
|
beq _shifty
|
|
|
|
lda reg.v, x
|
|
rol a
|
|
jmp +
|
|
_shifty
|
|
lda reg.v, y
|
|
rol a
|
|
sta reg.v, y
|
|
+ sta reg.v, x
|
|
lda #0
|
|
rol a
|
|
sta reg.v+$f
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; 9xy0 - SNE Vx, Vy
|
|
oSNE .proc
|
|
lda reg.v, x
|
|
cmp reg.v, y
|
|
bne skip
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Annn - LD I, addr
|
|
oLDI .proc
|
|
lda ins.w
|
|
sta reg.i
|
|
stx reg.i+1
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Bnnn - JP V0, addr
|
|
oJPI .proc
|
|
lda reg.v
|
|
clc
|
|
adc ins.w
|
|
sta reg.pc
|
|
txa
|
|
adc #0
|
|
sta reg.pc+1
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Cxkk - RND Vx, byte
|
|
oRND .proc
|
|
jsr rand
|
|
and ins.w
|
|
sta reg.v, x
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Dxyn - DRW Vx, Vy, nibble
|
|
oDRW .proc
|
|
lda reg.v, y
|
|
and #%0001_1111
|
|
asl a
|
|
asl a
|
|
asl a
|
|
sta startpos
|
|
lda reg.v, x
|
|
and #%0011_1111
|
|
tax
|
|
and #%0000_0111
|
|
sta bitshift
|
|
txa
|
|
lsr a
|
|
lsr a
|
|
lsr a
|
|
ora startpos
|
|
sta startpos
|
|
lda #0
|
|
sta reg.v+$f
|
|
lda ins.w
|
|
and #%0000_1111
|
|
beq done
|
|
sta ins.w
|
|
lda #1
|
|
sta scrdirty
|
|
jsr regItozptr1
|
|
ldy #0
|
|
nextline
|
|
lda (zptr1), y
|
|
sta bitsl
|
|
lda #0
|
|
sta bitsr
|
|
ldx bitshift
|
|
beq +
|
|
- lsr bitsl
|
|
ror bitsr
|
|
dex
|
|
bne -
|
|
+ ldx startpos
|
|
lda bitsl
|
|
and c8screen, x
|
|
beq +
|
|
lda #1
|
|
sta reg.v+$f
|
|
+ lda bitsl
|
|
eor c8screen, x
|
|
sta c8screen, x
|
|
inx
|
|
beq done
|
|
txa
|
|
and #$7
|
|
beq skipr
|
|
lda bitsr
|
|
and c8screen, x
|
|
beq +
|
|
lda #1
|
|
sta reg.v+$f
|
|
+ lda bitsr
|
|
eor c8screen, x
|
|
sta c8screen, x
|
|
skipr lda #8
|
|
clc
|
|
adc startpos
|
|
bcs done
|
|
sta startpos
|
|
iny
|
|
cpy ins.w
|
|
bmi nextline
|
|
done .FetchNext
|
|
|
|
.section bss
|
|
startpos .byte ?
|
|
bitshift .byte ?
|
|
bitsl .byte ?
|
|
bitsr .byte ?
|
|
.endsection
|
|
|
|
.endproc
|
|
|
|
; Ex9E - SKP Vx
|
|
oSKP .proc
|
|
jsr ReadKeyboard
|
|
bmi +
|
|
cmp reg.v, x
|
|
beq skip
|
|
+ .FetchNext
|
|
.endproc
|
|
|
|
; ExA1 - SKNP Vx
|
|
oSKNP .proc
|
|
jsr ReadKeyboard
|
|
bmi skip
|
|
cmp reg.v, x
|
|
bne skip
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Fx07 - LD Vx, DT
|
|
oLDxDT .proc
|
|
lda reg.dt
|
|
sta reg.v, x
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Fx0A - LD Vx, K
|
|
oLDK .proc
|
|
jsr ReadKeyboard
|
|
bpl +
|
|
sec
|
|
lda reg.pc
|
|
sbc #2
|
|
sta reg.pc
|
|
lda reg.pc+1
|
|
sbc #0
|
|
sta reg.pc+1
|
|
.FetchNext
|
|
+ sta reg.v, x
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Fx15 - LD DT, Vx
|
|
oLDDTx .proc
|
|
lda reg.v, x
|
|
sta reg.dt
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Fx18 - LD ST, Vxsnd_
|
|
oLDSTx .proc
|
|
lda reg.v, x
|
|
sta reg.st
|
|
beq +
|
|
jsr SoundOn
|
|
.FetchNext
|
|
+ jsr SoundOff
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Fx1E - ADD I, Vx
|
|
oADDI .proc
|
|
lda reg.v, x
|
|
clc
|
|
adc reg.i
|
|
sta reg.i
|
|
lda #0
|
|
adc reg.i+1
|
|
sta reg.i+1
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Fx29 - LD F, Vx
|
|
oLDF .proc
|
|
lda reg.v, x
|
|
asl a
|
|
asl a
|
|
clc
|
|
adc reg.v, x
|
|
sta reg.i
|
|
lda #0
|
|
sta reg.i+1
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Fx33 - LD B, Vx
|
|
oLDB .proc
|
|
jsr regItozptr1
|
|
lda #0
|
|
tay
|
|
sta (zptr1), y
|
|
iny
|
|
sta (zptr1), y
|
|
lda reg.v, x
|
|
sta zptr2
|
|
ldx #8
|
|
sed
|
|
ldy #1
|
|
- asl zptr2
|
|
lda (zptr1), y
|
|
adc (zptr1), y
|
|
sta (zptr1), y
|
|
dey
|
|
lda (zptr1), y
|
|
adc (zptr1), y
|
|
sta (zptr1), y
|
|
iny
|
|
dex
|
|
bne -
|
|
cld
|
|
; y == 1
|
|
; now I -> 0A BC xx
|
|
; we need I -> 0A 0B 0C
|
|
lda (zptr1), y
|
|
tax
|
|
and #$0f
|
|
iny
|
|
sta (zptr1), y
|
|
dey
|
|
txa
|
|
lsr a
|
|
lsr a
|
|
lsr a
|
|
lsr a
|
|
sta (zptr1), y
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Fx55 - LD [I], Vx
|
|
oLDIx .proc
|
|
jsr regItozptr1
|
|
txa
|
|
tay
|
|
- lda reg.v, y
|
|
sta (zptr1), y
|
|
dey
|
|
bpl -
|
|
.FetchNext
|
|
.endproc
|
|
|
|
; Fx65 - LD Vx, [I]
|
|
oLDxI .proc
|
|
jsr regItozptr1
|
|
txa
|
|
tay
|
|
- lda (zptr1), y
|
|
sta reg.v, y
|
|
dey
|
|
bpl -
|
|
.FetchNext
|
|
.endproc
|
|
|
|
regItozptr1 .proc
|
|
ADDRMASK = C8RAMSIZE - 1
|
|
clc
|
|
lda reg.i
|
|
.if (ADDRMASK & $00ff) != $00ff
|
|
and #<ADDRMASK
|
|
.endif
|
|
.if (C8RAM & $00ff) != 0
|
|
adc #<C8RAM
|
|
.endif
|
|
sta zptr1
|
|
lda reg.i+1
|
|
and #>ADDRMASK
|
|
adc #>C8RAM
|
|
sta zptr1+1
|
|
rts
|
|
.endproc
|
|
|
|
c8cls .proc
|
|
.FillMem c8screen, 0, size(c8screen)
|
|
lda #1
|
|
sta scrdirty
|
|
rts
|
|
.endproc
|
|
|
|
rand .proc
|
|
; stir rng state and return next value in A
|
|
; https://github.com/edrosten/8bit_rng/blob/master/rng-4261412736.c
|
|
; unsigned char t = x ^ (x << 4);
|
|
; x=y;
|
|
; y=z;
|
|
; z=a;
|
|
; a = z ^ t ^ ( z >> 1) ^ (t << 1);
|
|
|
|
lda rngst
|
|
asl a
|
|
asl a
|
|
asl a
|
|
asl a
|
|
eor rngst
|
|
sta rng_t1
|
|
asl a
|
|
eor rng_t1
|
|
sta rng_t1 ; t1 = (t << 1) ^ t
|
|
lda rngst+1
|
|
sta rngst
|
|
lda rngst+2
|
|
sta rngst+1
|
|
lda rngst+3
|
|
sta rngst+2
|
|
lsr a
|
|
eor rngst+2 ; ra = ( z >> 1 ) ^ z
|
|
eor rng_t1
|
|
sta rngst+3
|
|
rts
|
|
.endproc
|
|
|
|
.endsection code
|
|
|
|
.section data
|
|
sprites .block
|
|
.byte $F0, $90, $90, $90, $F0 ; 0
|
|
.byte $20, $60, $20, $20, $70 ; 1
|
|
.byte $F0, $10, $F0, $80, $F0 ; 2
|
|
.byte $F0, $10, $F0, $10, $F0 ; 3
|
|
.byte $90, $90, $F0, $10, $10 ; 4
|
|
.byte $F0, $80, $F0, $10, $F0 ; 5
|
|
.byte $F0, $80, $F0, $90, $F0 ; 6
|
|
.byte $F0, $10, $20, $40, $40 ; 7
|
|
.byte $F0, $90, $F0, $90, $F0 ; 8
|
|
.byte $F0, $90, $F0, $10, $F0 ; 9
|
|
.byte $F0, $90, $F0, $90, $90 ; a
|
|
.byte $E0, $90, $E0, $90, $E0 ; b
|
|
.byte $F0, $80, $80, $80, $F0 ; c
|
|
.byte $E0, $90, $90, $90, $E0 ; d
|
|
.byte $F0, $80, $F0, $80, $F0 ; e
|
|
.byte $F0, $80, $F0, $80, $80 ; f
|
|
.bend
|
|
|
|
eflags .byte 0
|
|
|
|
jumptable .block
|
|
ops = [0, oJP, oCALL, oSEK, oSNEK, oSE, oLDn, oADDn, oINV, oSNE, oLDI, oJPI, oRND, oDRW, 0, 0]
|
|
ops8 = [oLD, oOR, oAND, oXOR, oADD, oSUB, oSHR, oSUBN, oINV, oINV, oINV, oINV, oINV, oINV, oSHL, oINV]
|
|
main .addr ops
|
|
op8 .addr ops8
|
|
sparse .word $00e0, oCLS
|
|
.word $00ee, oRET
|
|
.word $e09e, oSKP
|
|
.word $e0a1, oSKNP
|
|
.word $f007, oLDxDT
|
|
.word $f00a, oLDK
|
|
.word $f015, oLDDTx
|
|
.word $f018, oLDSTx
|
|
.word $f01e, oADDI
|
|
.word $f029, oLDF
|
|
.word $f033, oLDB
|
|
.word $f055, oLDIx
|
|
.word $f065, oLDxI
|
|
.word 0, 0
|
|
.endblock
|
|
|
|
.if EMBEDSAMPLE
|
|
samplecode .block
|
|
; .binary "samples/ibmlogo.ch8"
|
|
; .binary "samples/tetris.ch8"
|
|
; .binary "samples/lunarlander.ch8"
|
|
; .binary "diags/keypad.ch8"
|
|
.binary "samples/opcodes.ch8"
|
|
; .binary "diags/sound.ch8"
|
|
.endblock
|
|
.endif
|
|
|
|
.endsection data
|
|
|
|
; vim: syntax=64tass
|