nim-chip8/src/cpu.nim

359 lines
8.3 KiB
Nim

import random
import def
import strutils
const F = 0xF # for easier access and alignment
type
CPU* = object
reg*: array[16, uint8]
ar*: uint16
stack*: array[64, uint16]
sp*: uint8
dt*: uint8
st*: uint8
fb*: array[FB_SIZE, uint32]
pc*: uint16
mem*: array[MEM_SIZE, uint8]
# not HW defined
needs_draw*: bool
keys*: array[16, bool]
reading_key*: uint8 = NOT_READING
template repr(r: string) =
when defined debug:
echo(
toHex(c.pc, 3) &
": " &
toHex(c.mem[c.pc].uint16 shl 8 or c.mem[c.pc + 1]) &
" " &
r
)
template n3_3: untyped =
(inst and 0x0FFF)
template n3_1: untyped =
((inst and 0x0F00) shr 8).uint8
template n2_1: untyped =
((inst and 0x00F0) shr 4).uint8
template n2_2: untyped =
(inst and 0x00FF).uint8
template n1_1: untyped =
(inst and 0x000F).uint8
proc next(c: var CPU) {.inline.} =
c.pc += 2
proc cls(c: var CPU) {.inline.} =
repr "CLS"
for i in 0 .. FB_SIZE - 1:
c.fb[i] = PIXEL_OFF
c.needs_draw = true
c.next
proc ret(c: var CPU) {.inline.} =
repr "RET"
c.pc = c.stack[c.sp]
c.sp -= 1
c.next
proc sys(c: var CPU, adr: uint16) {.inline.} =
repr "SYS 0x" & toHex(adr, 3)
c.pc = adr
proc jp(c: var CPU, adr: uint16) {.inline.} =
repr "JP 0x" & toHex(adr, 3)
c.pc = adr
proc call(c: var CPU, adr: uint16) {.inline.} =
repr "CALL 0x" & toHex(adr, 3)
c.sp += 1
c.stack[c.sp] = c.pc
c.pc = adr
proc se(c: var CPU, v: uint8, byte: uint8) {.inline.} =
repr "SE V" & toHex(v, 1) & ", 0x" & toHex(byte, 2)
if c.reg[v] == byte: c.next
c.next
proc sne(c: var CPU, v: uint8, byte: uint8) {.inline.} =
repr "SNE V" & toHex(v, 1) & ", 0x" & toHex(byte, 2)
if c.reg[v] != byte: c.next
c.next
proc se_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "SE V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
if c.reg[vx] == c.reg[vy]: c.next
c.next
proc ld(c: var CPU, v: uint8, byte: uint8) {.inline.} =
repr "LD V" & toHex(v, 1) & ", 0x" & toHex(byte, 2)
c.reg[v] = byte
c.next
proc add(c: var CPU, v: uint8, byte: uint8) {.inline.} =
repr "ADD V" & toHex(v, 1) & ", 0x" & toHex(byte, 2)
c.reg[v] += byte
c.next
proc ld_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "LD V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
c.reg[vx] = c.reg[vy]
c.next
proc or_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "OR V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
c.reg[vx] = c.reg[vx] or c.reg[vy]
c.next
proc and_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "AND V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
c.reg[vx] = c.reg[vx] and c.reg[vy]
c.next
proc xor_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "XOR V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
c.reg[vx] = c.reg[vx] xor c.reg[vy]
c.next
proc add_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "ADD V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
let orig_vx = c.reg[vx]
c.reg[vx] += c.reg[vy]
c.reg[F] =
if (c.reg[vx] < orig_vx) or (c.reg[vx] < c.reg[vy]): 1
else: 0
c.next
proc sub_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "SUB V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
let orig_vx = c.reg[vx]
c.reg[vx] -= c.reg[vy]
c.reg[F] =
if c.reg[vx] > orig_vx: 0
else: 1
c.next
proc shr_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "SHR V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
c.reg[vx] = c.reg[vy] # todo: configurable
let orig_vx = c.reg[vx]
c.reg[vx] = c.reg[vx] shr 1
c.reg[F] = orig_vx and 1
c.next
proc subn_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "SUBN V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
c.reg[vx] = c.reg[vy] - c.reg[vx]
c.reg[F] =
if c.reg[vx] > c.reg[vy]: 0
else: 1
c.next
proc shl_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "SHL V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
c.reg[vx] = c.reg[vy] # todo: configurable
let orig_vx = c.reg[vx]
c.reg[vx] = c.reg[vx] shl 1
c.reg[F] =
if (orig_vx and 0x80) == 0x80: 1
else: 0
c.next
proc sne_v(c: var CPU, vx: uint8, vy: uint8) {.inline.} =
repr "SNE V" & toHex(vx, 1) & ", V" & toHex(vy, 1)
if c.reg[vx] != c.reg[vy]: c.next
c.next
proc ld_i(c: var CPU, adr: uint16) {.inline.} =
repr "LD I, 0x" & toHex(adr, 3)
c.ar = adr
c.next
proc jp_v0(c: var CPU, adr: uint16) {.inline.} =
repr "JP v0, 0x" & toHex(adr, 3)
c.pc = c.reg[0] + adr
proc rnd(c: var CPU, v: uint8, byte: uint8) {.inline.} =
repr "RND V" & toHex(v, 1) & ", 0x" & toHex(byte, 2)
c.reg[v] = rand(0..0xFF).uint8 and byte
c.next
proc drw(c: var CPU, vx: uint8, vy: uint8, n: uint8) {.inline.} =
repr "DRW V" & toHex(vx, 1) & ", V" & toHex(vy, 1) & ", 0x0" & toHex(n, 1)
c.reg[F] = 0
for y in 0'u8 .. n - 1:
for x in 0'u8 .. 7:
let pixel = c.mem[c.ar + y]
if (pixel and (0x80'u8 shr x)) != 0:
let i = (c.reg[vx] + x) mod SCR_WIDTH + ((c.reg[vy] + y.uint) mod SCR_HEIGHT) * SCR_WIDTH
if c.fb[i] == PIXEL_ON:
c.reg[F] = 1
c.fb[i] = PIXEL_OFF
else:
c.fb[i] = PIXEL_ON
c.needs_draw = true
c.next
proc skp(c: var CPU, v: uint8) {.inline.} =
repr "SKP V" & toHex(v, 1)
if c.keys[c.reg[v]]: c.next
c.next
proc sknp(c: var CPU, v: uint8) {.inline.} =
repr "SKNP V" & toHex(v, 1)
if not c.keys[c.reg[v]]: c.next
c.next
proc ld_v_dt(c: var CPU, v: uint8) {.inline.} =
repr "LD V" & toHex(v, 1) & ", DT"
c.reg[v] = c.dt
c.next
proc ld_v_k(c: var CPU, v: uint8) {.inline.} =
repr "LD V" & toHex(v, 1) & ", K"
c.reading_key = v
c.next
proc ld_dt_v(c: var CPU, v: uint8) {.inline.} =
repr "LD DT, V" & toHex(v, 1)
c.dt = c.reg[v]
c.next
proc ld_st_v(c: var CPU, v: uint8) {.inline.} =
repr "LD ST, V" & toHex(v, 1)
c.st = c.reg[v]
c.next
proc add_i_v(c: var CPU, v: uint8) {.inline.} =
repr "ADD I, V" & toHex(v, 1)
c.ar += c.reg[v]
c.next
proc ld_f_v(c: var CPU, v: uint8) {.inline.} =
repr "LD F, V" & toHex(v, 1)
c.ar = c.reg[v] * 5 # todo: check if this is correct
c.next
proc ld_b_v(c: var CPU, v: uint8) {.inline.} =
repr "LD B, V" & toHex(v, 1)
c.mem[c.ar] = c.reg[v] div 100
c.mem[c.ar + 1] = (c.reg[v] mod 100) div 10
c.mem[c.ar + 2] = (c.reg[v] mod 100) mod 10
c.next
proc ld_i_v(c: var CPU, v: uint8) {.inline.} =
repr "LD [I], V" & toHex(v, 1)
for i in 0'u8..v:
c.mem[c.ar] = c.reg[i]
c.ar.inc
c.ar.inc
c.next
proc ld_v_i(c: var CPU, v: uint8) {.inline.} =
repr "LD V" & toHex(v, 1) & ", [I]"
for i in 0'u8..v:
c.reg[i] = c.mem[c.ar]
c.ar.inc
c.ar.inc
c.next
proc dispatch*(c: var CPU, inst: uint16) {.inline.} =
case (inst and 0xF000):
of 0x0000:
case (inst and 0x00FF):
of 0xE0: c.cls
of 0xEE: c.ret
else: c.sys n3_3
of 0x1000: c.jp n3_3
of 0x2000: c.call n3_3
of 0x3000: c.se n3_1, n2_2
of 0x4000: c.sne n3_1, n2_2
of 0x5000: c.se_v n3_1, n2_1
of 0x6000: c.ld n3_1, n2_2
of 0x7000: c.add n3_1, n2_2
of 0x8000:
case (inst and 0x000F):
of 0x0: c.ld_v n3_1, n2_1
of 0x1: c.or_v n3_1, n2_1
of 0x2: c.and_v n3_1, n2_1
of 0x3: c.xor_v n3_1, n2_1
of 0x4: c.add_v n3_1, n2_1
of 0x5: c.sub_v n3_1, n2_1
of 0x6: c.shr_v n3_1, n2_1
of 0x7: c.subn_v n3_1, n2_1
of 0xE: c.shl_v n3_1, n2_1
else: return
of 0x9000: c.sne_v n3_1, n2_1
of 0xA000: c.ld_i n3_3
of 0xB000: c.jp_v0 n3_3
of 0xC000: c.rnd n3_1, n2_2
of 0xD000: c.drw n3_1, n2_1, n1_1
of 0xE000:
case (inst and 0x00FF):
of 0x9E: c.skp n3_1
of 0xA1: c.sknp n3_1
else: return
of 0xF000:
case (inst and 0x00FF):
of 0x07: c.ld_v_dt n3_1
of 0x0A: c.ld_v_k n3_1
of 0x15: c.ld_dt_v n3_1
of 0x18: c.ld_st_v n3_1
of 0x1E: c.add_i_v n3_1
of 0x29: c.ld_f_v n3_1
of 0x33: c.ld_b_v n3_1
of 0x55: c.ld_i_v n3_1
of 0x65: c.ld_v_i n3_1
else: return
else: return
proc step*(c: var CPU) {.inline.} =
let inst = c.mem[c.pc].uint16 shl 8 or c.mem[c.pc + 1]
c.dispatch inst
proc init*(c: var CPU) =
for i in 0 .. FB_SIZE - 1:
c.fb[i] = PIXEL_OFF
c.pc = 0x200