359 lines
8.3 KiB
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
|