; ; 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 #> 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+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 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