chipty5/chip-8.asm

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