Compare commits

..
4 Commits
+216 -166
View File
@@ -1,5 +1,4 @@
import rl "vendor:raylib" import rl "vendor:raylib"
import agent "vendor:agent"
struct Instruction struct Instruction
full: u16 full: u16
@@ -24,11 +23,13 @@ struct Machine
delay-timer: u8 delay-timer: u8
sound-timer: u8 sound-timer: u8
struct State class State
screen-pixels: [2048 rl/Color] screen-pixels: [2048 rl/Color]
screen-texture: rl/Texture2D screen-texture: rl/Texture2D
audio-stream: rl/AudioStream
font: rl/Font font: rl/Font
page: int page: int
fullscreen: bool
step-mode: bool step-mode: bool
clear-screen: bool clear-screen: bool
pending-key: u8? pending-key: u8?
@@ -37,7 +38,7 @@ struct State
skip-overflow-FX1E: bool skip-overflow-FX1E: bool
inc-index-0xFX-55-65: bool inc-index-0xFX-55-65: bool
const font: [80 u8] = const font: [_ u8] =
[0xF0, 0x90, 0x90, 0x90, 0xF0, ;; 0 [0xF0, 0x90, 0x90, 0x90, 0xF0, ;; 0
0x20, 0x60, 0x20, 0x20, 0x70, ;; 1 0x20, 0x60, 0x20, 0x20, 0x70, ;; 1
0xF0, 0x10, 0xF0, 0x80, 0xF0, ;; 2 0xF0, 0x10, 0xF0, 0x80, 0xF0, ;; 2
@@ -55,7 +56,7 @@ const font: [80 u8] =
0xF0, 0x80, 0xF0, 0x80, 0xF0, ;; E 0xF0, 0x80, 0xF0, 0x80, 0xF0, ;; E
0xF0, 0x80, 0xF0, 0x80, 0x80] ;; F 0xF0, 0x80, 0xF0, 0x80, 0x80] ;; F
const numbers: [16 [5 u8]] = const numbers: [_ [_ u8]] =
[[0xF0, 0x90, 0x90, 0x90, 0xF0] ;; 0 [[0xF0, 0x90, 0x90, 0x90, 0xF0] ;; 0
[0x20, 0x60, 0x20, 0x20, 0x70] ;; 1 [0x20, 0x60, 0x20, 0x20, 0x70] ;; 1
[0xF0, 0x10, 0xF0, 0x80, 0xF0] ;; 2 [0xF0, 0x10, 0xF0, 0x80, 0xF0] ;; 2
@@ -73,19 +74,14 @@ const numbers: [16 [5 u8]] =
[0xF0, 0x80, 0xF0, 0x80, 0xF0] ;; E [0xF0, 0x80, 0xF0, 0x80, 0xF0] ;; E
[0xF0, 0x80, 0xF0, 0x80, 0x80]] ;; F [0xF0, 0x80, 0xF0, 0x80, 0x80]] ;; F
const keypad-mapping: [16 u8] = const keypad-mapping: [_ u8] =
[1, 2, 3, 0xC, 4, 5, 6, 0xD, 7, 8, 9, 0xE, 0xA, 0, 0xB, 0xF] [1, 2, 3, 0xC, 4, 5, 6, 0xD, 7, 8, 9, 0xE, 0xA, 0, 0xB, 0xF]
const keypad-keymap: [16 rl/Key] = const keypad-keymap: [_ rl/Key] =
[:key-one, :key-two, :key-three, :key-four, [:key-one, :key-two, :key-three, :key-four,
:key-q, :key-w, :key-e, :key-r, :key-q, :key-w, :key-e, :key-r,
:key-a, :key-s, :key-d, :key-f, :key-a, :key-s, :key-d, :key-f,
:key-z, :key-x, :key-c, :key-v] :key-z, :key-x, :key-c, :key-v]
const hex-numbers: str = "0123456789ABCDEF"
const reg-names: [16 str] = ["V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7",
"V8", "V9", "VA", "VB", "VC", "VD", "VE", "VF"]
;; let screen-width: int = 1300 ;; let screen-width: int = 1300
;; let screen-height: int = 650 ;; let screen-height: int = 650
@@ -96,18 +92,44 @@ let pixels-height: u8 = 32
let ips: int = 700 let ips: int = 700
let cycles: f64 = 0.0 let cycles: f64 = 0.0
let cpu: Cpu = Cpu{} once cpu: Cpu = Cpu{}
let machine: Machine = Machine{} once machine: Machine = Machine{}
let state: State = State{} once state: State = State{}
let max-samples = 512
max-samples-per-update = 4096
frequency: f64 = 440.0
audio-frequency: f64 = 440.0
sine-idx: f64 = 0.0
volume: f64
target-volume: f64
fn load-rom(path: str) -> () fn load-rom(path: str) -> ()
let rom = slurp(path) let rom = slurp(path)
for i, b in rom copy(machine.memory[0x200..], rom )
machine.memory[0x200 + i] = b
fn parse-instruction(pc: u16) -> Instruction
let byte1: u8 = machine.memory[pc]
let byte2: u8 = machine.memory[pc + 1]
let full = u16(byte1) << 8 | u16(byte2)
Instruction{.full full
.op (byte1 >> 4)
.x (byte1 & 0x0F)
.y (byte2 >> 4)
.n (byte2 & 0x0F)
.nn byte2
.nnn (full & 0x0FFF)}
fn reset() -> () fn reset() -> ()
cpu = Cpu{} cpu = Cpu{}
machine = Machine{} machine = Machine{}
load-rom("./IBM Logo.ch8")
;; load-rom("./vendor/chip8-test-suite/bin/1-chip8-logo.ch8")
;; load-rom("./vendor/chip8-test-suite/bin/2-ibm-logo.ch8")
;; load-rom("./vendor/chip8-test-suite/bin/3-corax+.ch8")
;; load-rom("./vendor/chip8-test-suite/bin/4-flags.ch8")
;; load-rom("./roms/Breakout [Carmelo Cortez, 1979].ch8")
load-rom("./roms/Brix [Andreas Gustafsson, 1990].ch8")
cpu.pc = 0x200 cpu.pc = 0x200
fn decode-instruction(inst: Instruction) -> str fn decode-instruction(inst: Instruction) -> str
@@ -118,83 +140,65 @@ fn decode-instruction(inst: Instruction) -> str
0xE0 -> "CLS" 0xE0 -> "CLS"
0xEE -> "RET" 0xEE -> "RET"
_ -> "UNKNOWN" _ -> "UNKNOWN"
0x1 -> tformat("JP 0x{:03X}", inst.nnn) 0x1 -> format("JP 0x{:03X}", inst.nnn)
0x2 -> tformat("CALL 0x{:03X}", inst.nnn) 0x2 -> format("CALL 0x{:03X}", inst.nnn)
0x3 -> tformat("SE V{:X}, 0x{:02X}", inst.x, inst.nn) 0x3 -> format("SE V{:X}, 0x{:02X}", inst.x, inst.nn)
0x4 -> tformat("SNE V{:X}, 0x{:02X}", inst.x, inst.nn) 0x4 -> format("SNE V{:X}, 0x{:02X}", inst.x, inst.nn)
0x5 -> tformat("SE V{:X}, V{:X}", inst.x, inst.y) 0x5 -> format("SE V{:X}, V{:X}", inst.x, inst.y)
0x6 -> tformat("LD V{:X}, 0x{:02x}", inst.x, inst.nn) 0x6 -> format("LD V{:X}, 0x{:02x}", inst.x, inst.nn)
0x7 -> tformat("ADD V{:X}, 0x{:02x}", inst.x, inst.nn) 0x7 -> format("ADD V{:X}, 0x{:02x}", inst.x, inst.nn)
0x8 -> 0x8 ->
match inst.n match inst.n
0x0 -> tformat("LD V{:X}, V{:X}", inst.x, inst.y) 0x0 -> format("LD V{:X}, V{:X}", inst.x, inst.y)
0x1 -> tformat("OR V{:X}, V{:X}", inst.x, inst.y) 0x1 -> format("OR V{:X}, V{:X}", inst.x, inst.y)
0x2 -> tformat("AND V{:X}, V{:X}", inst.x, inst.y) 0x2 -> format("AND V{:X}, V{:X}", inst.x, inst.y)
0x3 -> tformat("XOR V{:X}, V{:X}", inst.x, inst.y) 0x3 -> format("XOR V{:X}, V{:X}", inst.x, inst.y)
0x4 -> tformat("ADD V{:X}, V{:X}", inst.x, inst.y) 0x4 -> format("ADD V{:X}, V{:X}", inst.x, inst.y)
0x5 -> tformat("SUB V{:X}, V{:X}", inst.x, inst.y) 0x5 -> format("SUB V{:X}, V{:X}", inst.x, inst.y)
0x6 -> tformat("SHR V{:X}, V{:X}", inst.x, inst.y) 0x6 -> format("SHR V{:X}, V{:X}", inst.x, inst.y)
0x7 -> tformat("SUBN V{:X}, V{:X}", inst.x, inst.y) 0x7 -> format("SUBN V{:X}, V{:X}", inst.x, inst.y)
0xE -> tformat("SHL V{:X}, V{:X}", inst.x, inst.y) 0xE -> format("SHL V{:X}, V{:X}", inst.x, inst.y)
_ -> "UNKNOWN" _ -> "UNKNOWN"
0x9 -> tformat("SNE V{:X}, V{:X}", inst.x, inst.y) 0x9 -> format("SNE V{:X}, V{:X}", inst.x, inst.y)
0xA -> tformat("LD I, 0x{:03X}", inst.nnn) 0xA -> format("LD I, 0x{:03X}", inst.nnn)
0xB -> tformat("JP V0, 0x{:03X}", inst.nnn) 0xB -> format("JP V0, 0x{:03X}", inst.nnn)
0xC -> tformat("RND V{:X}, 0x{:02x}", inst.x, inst.nn) 0xC -> format("RND V{:X}, 0x{:02x}", inst.x, inst.nn)
0xD -> tformat("DRW V{:X}, V{:X}, 0x{:X}", inst.x, inst.y, inst.n) 0xD -> format("DRW V{:X}, V{:X}, 0x{:X}", inst.x, inst.y, inst.n)
0xE -> 0xE ->
match inst.nn match inst.nn
0x9E -> tformat("SKP V{:X}", inst.x) 0x9E -> format("SKP V{:X}", inst.x)
0xA1 -> tformat("SKNP V{:X}", inst.x) 0xA1 -> format("SKNP V{:X}", inst.x)
_ -> "UNKNOWN" _ -> "UNKNOWN"
0xF -> 0xF ->
match inst.nn match inst.nn
0x07 -> tformat("LD V{:X}, DT", inst.x) 0x07 -> format("LD V{:X}, DT", inst.x)
0x0A -> tformat("LD V{:X}, K", inst.x) 0x0A -> format("LD V{:X}, K", inst.x)
0x15 -> tformat("LD DT, V{:X}", inst.x) 0x15 -> format("LD DT, V{:X}", inst.x)
0x18 -> tformat("LD ST, V{:X}", inst.x) 0x18 -> format("LD ST, V{:X}", inst.x)
0x1E -> tformat("ADD I, V{:X}", inst.x) 0x1E -> format("ADD I, V{:X}", inst.x)
0x29 -> tformat("LD F, V{:X}", inst.x) 0x29 -> format("LD F, V{:X}", inst.x)
0x33 -> tformat("LD B, V{:X}", inst.x) 0x33 -> format("LD B, V{:X}", inst.x)
0x55 -> tformat("LD [I], V{:X}", inst.x) 0x55 -> format("LD [I], V{:X}", inst.x)
0x65 -> tformat("LD V{:X}, [I]", inst.x) 0x65 -> format("LD V{:X}, [I]", inst.x)
_ -> "UNKNOWN" _ -> "UNKNOWN"
_ -> "UNKNOWN" _ -> "UNKNOWN"
fn parse-instruction(pc: u16) -> Instruction
let byte1: u8 = machine.memory[cpu.pc]
let byte2: u8 = machine.memory[cpu.pc + 1]
let full = u16(byte1) << 8 | u16(byte2)
Instruction{.full full
.op (byte1 >> 4)
.x (byte1 & 0x0F)
.y (byte2 >> 4)
.n (byte2 & 0x0F)
.nn byte2
.nnn (full & 0x0FFF)}
;; You should extract the execution from the other stuff here so you can manually ;; You should extract the execution from the other stuff here so you can manually
;; run an instruction when needed, all it needs is a parsed instruction ;; run an instruction when needed, all it needs is a parsed instruction
fn step() -> () fn step(inst: Instruction) -> ()
if cpu.pc >= length(machine.memory) - 2 if cpu.pc >= length(machine.memory) - 2
cpu.pc = 0 cpu.pc = 0
return return
let inst = parse-instruction(cpu.pc)
cpu.pc += 2 cpu.pc += 2
println("Executed: ", decode-instruction(inst), " || ",
"Next: ", decode-instruction(parse-instruction(cpu.pc + 1)))
match inst.op match inst.op
0x0 -> 0x0 ->
match inst.nn match inst.nn
;; TODO: Maybe this can be a draw command that clears the screen rather than special cased
0xE0 -> state.clear-screen = true 0xE0 -> state.clear-screen = true
0xEE -> 0xEE ->
cpu.pc = cpu.stack[cpu.sp]
cpu.sp -= 1 cpu.sp -= 1
_ -> println("Unrecognized 0x0 instruction") cpu.pc = cpu.stack[cpu.sp]
_ -> print("Unrecognized 0x0 instruction")
0x1 -> cpu.pc = inst.nnn 0x1 -> cpu.pc = inst.nnn
0x2 -> ;; Call a subroutine 0x2 -> ;; Call a subroutine
cpu.stack[cpu.sp] = cpu.pc cpu.stack[cpu.sp] = cpu.pc
@@ -204,7 +208,7 @@ fn step() -> ()
0x4 -> if cpu.v[inst.x] != inst.nn then cpu.pc += 2 0x4 -> if cpu.v[inst.x] != inst.nn then cpu.pc += 2
0x5 -> if cpu.v[inst.x] == cpu.v[inst.y] then cpu.pc += 2 0x5 -> if cpu.v[inst.x] == cpu.v[inst.y] then cpu.pc += 2
0x6 -> cpu.v[inst.x] = inst.nn 0x6 -> cpu.v[inst.x] = inst.nn
0x7 -> cpu.v[inst.x] += inst.nn 0x7 -> cpu.v[inst.x] +%= inst.nn
0x8 -> 0x8 ->
match inst.n match inst.n
0x0 -> cpu.v[inst.x] = cpu.v[inst.y] 0x0 -> cpu.v[inst.x] = cpu.v[inst.y]
@@ -216,24 +220,24 @@ fn step() -> ()
cpu.v[inst.x] = u8(sum) cpu.v[inst.x] = u8(sum)
cpu.v[0xF] = u8(sum >> 8) cpu.v[0xF] = u8(sum >> 8)
0x5 -> 0x5 ->
let diff = u16(cpu.v[inst.x]) - u16(cpu.v[inst.y]) let diff = u16(cpu.v[inst.x]) -% u16(cpu.v[inst.y])
cpu.v[inst.x] = u8(diff) cpu.v[inst.x] = u8(diff)
cpu.v[0xF] = u8(diff >> 8 & 0x1 ^ 1) cpu.v[0xF] = u8(diff >> 8 & 0x1 ^ 1)
0x6 -> 0x6 ->
if state.shift-vx-swap if state.shift-vx-swap
cpu.v[inst.x] = cpu.v[inst.y] cpu.v[inst.x] = cpu.v[inst.y]
cpu.v[0xF] = cpu.v[inst.x] & 1 cpu.v[0xF] = cpu.v[inst.x] & 1
cpu.v[inst.x] = cpu.v[inst.x] >> 1 cpu.v[inst.x] >>= 1
0x7 -> 0x7 ->
let diff = u16(cpu.v[inst.y]) - u16(cpu.v[inst.x]) let diff = u16(cpu.v[inst.y]) -% u16(cpu.v[inst.x])
cpu.v[inst.x] = u8(diff) cpu.v[inst.x] = u8(diff)
cpu.v[0xF] = u8(diff >> 8 & 0x1 ^ 1) cpu.v[0xF] = u8(diff >> 8 & 0x1 ^ 1)
0xE -> 0xE ->
if state.shift-vx-swap if state.shift-vx-swap
cpu.v[inst.x] = cpu.v[inst.y] cpu.v[inst.x] = cpu.v[inst.y]
cpu.v[0xF] = (cpu.v[inst.x] & 0x80) >> 7 cpu.v[0xF] = (cpu.v[inst.x] & 0x80) >> 7
cpu.v[inst.x] = cpu.v[inst.x] << 1 cpu.v[inst.x] <<= 1
_ -> println("Unrecognized 0x8 instruction") _ -> print("Unrecognized 0x8 instruction")
0x9 -> if cpu.v[inst.x] != cpu.v[inst.y] then cpu.pc += 2 0x9 -> if cpu.v[inst.x] != cpu.v[inst.y] then cpu.pc += 2
0xA -> cpu.index = inst.nnn 0xA -> cpu.index = inst.nnn
0xB -> cpu.pc = inst.nnn + if state.jump-xnn then cpu.v[inst.x] else cpu.v[0] 0xB -> cpu.pc = inst.nnn + if state.jump-xnn then cpu.v[inst.x] else cpu.v[0]
@@ -243,23 +247,22 @@ fn step() -> ()
y = cpu.v[inst.y] % u8(pixels-height) y = cpu.v[inst.y] % u8(pixels-height)
collision = false collision = false
for row in 0..inst.n loop row in 0..inst.n
let byte = machine.memory[cpu.index + row] let byte = machine.memory[cpu.index + row]
let idx = i32(x) + (row + i32(y)) * i32(pixels-width) let idx = i32(x) + (row + i32(y)) * i32(pixels-width)
for bit in reverse(u8(0)..8) loop bit in reverse(0..8)
if byte >> bit & 0x1 == 1 and idx < length(machine.vbuffer) if byte >> bit & 0x1 == 1 and idx < length(machine.vbuffer)
if machine.vbuffer[idx] then collision = true if machine.vbuffer[idx] then collision = true
machine.vbuffer[idx] = true machine.vbuffer[idx] = not machine.vbuffer[idx]
idx += 1 idx += 1
cpu.v[0xF] = if collision then 1 else 0 cpu.v[0xF] = if collision then 1 else 0
0xE -> 0xE ->
let pressed = machine.keypad[inst.x] let pressed = machine.keypad[cpu.v[inst.x]]
if (inst.nn == 0x9E and pressed) or (inst.nn == 0xA1 and not pressed) match inst.nn
cpu.pc += 2 0x9E -> if pressed then cpu.pc += 2
;; TODO: When we have better pattern matching, do it with that so we can this catch error 0xA1 -> if not pressed then cpu.pc += 2
if inst.nn != 0x9E or inst.nn != 0xA1 _ -> print("Unrecognized 0xE instruction")
println("Unrecognized 0x8 instruction")
0xF -> 0xF ->
match inst.nn match inst.nn
0x07 -> cpu.v[inst.x] = machine.delay-timer 0x07 -> cpu.v[inst.x] = machine.delay-timer
@@ -278,68 +281,80 @@ fn step() -> ()
machine.memory[cpu.index + 1] = vx % 100 / 10 machine.memory[cpu.index + 1] = vx % 100 / 10
machine.memory[cpu.index + 2] = vx % 10 machine.memory[cpu.index + 2] = vx % 10
0x55 -> 0x55 ->
for i in 0..=inst.x loop i in 0..=inst.x
machine.memory[cpu.index + i] = cpu.v[i] machine.memory[cpu.index + i] = cpu.v[i]
if state.inc-index-0xFX-55-65 if state.inc-index-0xFX-55-65
cpu.index += inst.x cpu.index += inst.x
0x65 -> 0x65 ->
for i in 0..=inst.x loop i in 0..=inst.x
cpu.v[i] = machine.memory[cpu.index + i] cpu.v[i] = machine.memory[cpu.index + i]
if state.inc-index-0xFX-55-65 if state.inc-index-0xFX-55-65
cpu.index += inst.x cpu.index += inst.x
_ -> println("Unrecognized 0xF instruction") _ -> print("Unrecognized 0xF instruction")
_ -> println("Instruction not handled: ", inst.op) _ -> print("Instruction not handled: {:X}", inst.op)
fn run-update() -> () fn run-update() -> ()
for i, key in keypad-keymap loop i, key in keypad-keymap
if rl/is-key-down(key) and not state.step-mode machine.keypad[keypad-mapping[i]] = rl/is-key-down(key)
machine.keypad[keypad-mapping[i]] = true if state.pending-key as reg
if state.pending-key as reg cpu.v[reg] = u8(i)
cpu.v[reg] = u8(i) state.pending-key = nil
state.pending-key = None
if rl/is-key-pressed(:key-minus) if rl/is-key-pressed(:key-minus)
state.page = max(0, state.page - 1) state.page = max(0, state.page - 1)
if rl/is-key-pressed(:key-equal) if rl/is-key-pressed(:key-equal)
state.page = min(5, state.page + 1) state.page = min(5, state.page + 1)
if rl/is-key-pressed(:key-r) if rl/is-key-pressed(:key-period)
reset() reset()
load-rom("./IBM Logo.ch8")
return return
if rl/is-key-pressed(:key-enter)
state.fullscreen = not state.fullscreen
if rl/is-key-pressed(:key-space)
state.step-mode = not state.step-mode
;; Generate sound
if machine.sound-timer > 0
target-volume = 1.0
machine.sound-timer -= 1
else
target-volume = 0.0
if state.pending-key? if state.pending-key?
if machine.delay-timer > 0 then machine.delay-timer -= 1 if machine.delay-timer > 0 then machine.delay-timer -= 1
if machine.sound-timer > 0 then machine.sound-timer -= 1
return return
if state.step-mode if state.step-mode
if rl/is-key-pressed(:key-n) if rl/is-key-pressed(:key-n) or rl/is-key-pressed-repeat(:key-n)
step() let inst = parse-instruction(cpu.pc)
step(inst)
else else
cycles += f64(rl/get-frame-time()) * ips cycles += f64(rl/get-frame-time()) * ips
loop cycles >= 1.0
while cycles >= 1.0 let inst = parse-instruction(cpu.pc)
step() step(inst)
;; Apparently on the COSMAC VIP and other devices, drawing would wait for the next scree
;; refresh which ran at 60hz
if inst.op == 0xD
cycles = 0.0
break
cycles -= 1.0 cycles -= 1.0
fill(state.screen-pixels, rl/black)
if state.clear-screen if state.clear-screen
println("cleared screen")
state.clear-screen = false state.clear-screen = false
for i,pixel in machine.vbuffer
state.screen-pixels[i] = rl/black
else else
for i,pixel in machine.vbuffer loop i,pixel in machine.vbuffer
state.screen-pixels[i] = if pixel then rl/white else rl/black if pixel
state.screen-pixels[i] = rl/white
;; for i,pixel in machine.vbuffer ;; loop i,pixel in machine.vbuffer
;; if i % 2 == 0 and i / pixels-width % 2 == 0 or i % 2 == 1 and i / pixels-width % 2 == 1 ;; if i % 2 == 0 and i / pixels-width % 2 == 0 or i % 2 == 1 and i / pixels-width % 2 == 1
;; state.screen-pixels[i] = rl/white ;; state.screen-pixels[i] = rl/white
rl/update-texture(state.screen-texture, Ptr(const u8)(addr(state.screen-pixels))) rl/update-texture(state.screen-texture, state.screen-pixels)
if machine.delay-timer > 0 then machine.delay-timer -= 1 if machine.delay-timer > 0 then machine.delay-timer -= 1
if machine.sound-timer > 0 then machine.sound-timer -= 1
fn draw-text-normal(text: str, x: $t, y: $t, color: rl/Color) -> () where is-numeric($t) fn draw-text-normal(text: str, x: $t, y: $t, color: rl/Color) -> () where is-numeric($t)
rl/draw-text-ex(state.font, text, rl/Vector2(f32(x), f32(y)), 21, 0.0, color) rl/draw-text-ex(state.font, text, rl/Vector2(f32(x), f32(y)), 21, 0.0, color)
@@ -347,58 +362,75 @@ fn draw-text-normal(text: str, x: $t, y: $t, color: rl/Color) -> () where is-num
fn run-drawing() -> () fn run-drawing() -> ()
rl/clear-background(rl/black) rl/clear-background(rl/black)
draw-text-normal("Keypad", 10, 200, rl/white)
let key-size = 25
for i, key in keypad-keymap
let x = 10 + (key-size * (i % 4))
y = 230 + (key-size * (i / 4))
color = if machine.keypad[keypad-mapping[i]] then rl/green else rl/white
rl/draw-rectangle-lines(x, y, key-size, key-size, color)
;; TODO: Maybe we don't need hex-numbers anymore?
draw-text-normal(hex-numbers[i..i + 1], x + 4, y + 4, color)
draw-text-normal("Actual", 150, 200, rl/white)
for i,key in machine.keypad
let x = 150 + (key-size * (i % 4))
y = 230 + (key-size * (i / 4))
color = if machine.keypad[i] then rl/green else rl/white
rl/draw-rectangle-lines(x, y, key-size, key-size, color)
draw-text-normal("0123456789ABCDEF"[i..i + 1], x + 4, y + 4, color)
;; Registers
let reg-x = 290
for i in 0..16
;; draw-text-normal(str(i64->bytes(i64(cpu.v[i]))), 520, 60 + 26 * i, rl/white)
let reg-val = tformat("V{:X}=0x{:02X}", i, cpu.v[i])
draw-text-normal(reg-val, reg-x, 200 + 21 * i, rl/white)
;; Texture ;; Texture
let scale: f32 = 5.5 let scale: f32 = if state.fullscreen then 14 else 5
src = rl/Rectangle(0, 0, f32(pixels-width), f32(pixels-height)) src = rl/Rectangle(0, 0, f32(pixels-width), f32(pixels-height))
dst = rl/Rectangle(10, 10, f32(pixels-width) * scale, f32(pixels-height) * scale) dst = rl/Rectangle(10, 10, f32(pixels-width) * scale, f32(pixels-height) * scale)
rl/draw-texture-pro(state.screen-texture, src, dst, rl/Vector2(0.0, 0.0), 0.0, rl/white) rl/draw-texture-pro(state.screen-texture, src, dst, rl/Vector2(0.0, 0.0), 0.0, rl/white)
rl/draw-rectangle-lines(i32(dst.x), i32(dst.y), i32(dst.width), i32(dst.height), rl/white) rl/draw-rectangle-lines(i32(dst.x), i32(dst.y), i32(dst.width), i32(dst.height), rl/white)
for i in 0..16 if state.fullscreen
return
draw-text-normal("Keypad", 10, 200, rl/white)
let key-size = 25
loop i, key in keypad-keymap
let x = 10 + (key-size * (i % 4))
y = 230 + (key-size * (i / 4))
color = if machine.keypad[keypad-mapping[i]] then rl/green else rl/white
rl/draw-rectangle-lines(x, y, key-size, key-size, color)
draw-text-normal(format("{:X}", i), x + 4, y + 4, color)
;; draw-text-normal("Actual", 150, 200, rl/white)
;; loop i,key in machine.keypad
;; let x = 150 + (key-size * (i % 4))
;; y = 230 + (key-size * (i / 4))
;; color = if machine.keypad[i] then rl/green else rl/white
;; rl/draw-rectangle-lines(x, y, key-size, key-size, color)
;; draw-text-normal("0123456789ABCDEF"[i..i + 1], x + 4, y + 4, color)
;; Instructions
draw-text-normal("Instructions", 125, 200, rl/white)
rl/draw-rectangle-lines(125, 230, 150, 100, rl/white)
rl/draw-rectangle(128, 258, 140, 20, rl/darkgreen)
loop i in 0..4
if cpu.pc <= 0 and i == 0
continue
let inst = parse-instruction(cpu.pc + u8(i) * 2 - 2)
text = decode-instruction(inst)
if text != "UNKNOWN"
draw-text-normal(text, 130, 235 + i * 23, rl/white)
;; Registers
let reg-x = 290
loop i in 0..16
;; draw-text-normal(str(i64->bytes(i64(cpu.v[i]))), 520, 60 + 26 * i, rl/white)
let reg-val = format("V{:X}=0x{:02X}", i, cpu.v[i])
draw-text-normal(reg-val, reg-x, 200 + 21 * i, rl/white)
;; Stack
loop i in 0..16
let row = i % 8 let row = i % 8
col = i / 8 col = i / 8
x = 10 x = 10
y = 20 * row y = 20 * row
w = 80 w = 70
h = 20 h = 20
start-y = 360 start-y = 360
idx-txt = tformat(" {:X}|", i) idx-txt = format("{:X}|", i)
rl/draw-rectangle-lines(x + col * w, start-y + y, w, h, rl/white) rl/draw-rectangle-lines(x + col * w, start-y + y, w, h, rl/white)
draw-text-normal(idx-txt, x + col * w, start-y + y, rl/white) draw-text-normal(idx-txt, x + 4 + col * w, start-y + y, rl/white)
if i < cpu.sp if i < cpu.sp
let addr-txt = tformat("0x{:02X}", cpu.stack[i]) let addr-txt = format("0x{:02X}", cpu.stack[i])
draw-text-normal(addr-txt, x + 33 + col * w, start-y + y, rl/white) draw-text-normal(addr-txt, x + 24 + col * w, start-y + y, rl/white)
let x = 195 ;; Additional data
draw-text-normal(tformat(" i=0x{:02X}", cpu.index), x, 360, rl/white) let x = 185
draw-text-normal(tformat("pc=0x{:02X}", cpu.pc), x, 380, rl/white) draw-text-normal(format(" i=0x{:02X}", cpu.index), x, 360, rl/white)
draw-text-normal(tformat("dt={}", machine.delay-timer), x, 400, rl/white) draw-text-normal(format("pc=0x{:02X}", cpu.pc), x, 380, rl/white)
draw-text-normal(tformat("st={}", machine.sound-timer), x, 420, rl/white) draw-text-normal(format("dt={}", machine.delay-timer), x, 400, rl/white)
draw-text-normal(format("st={}", machine.sound-timer), x, 420, rl/white)
;; Hex dump ;; Hex dump
let hex-rows = 32 let hex-rows = 32
@@ -412,21 +444,21 @@ fn run-drawing() -> ()
grid-h = hex-rows * cell-h + padding * 2 grid-h = hex-rows * cell-h + padding * 2
slots-per-page = hex-rows * hex-cols slots-per-page = hex-rows * hex-cols
for i in 0..4096 / slots-per-page + 1 loop i in 0..4096 / slots-per-page + 1
let color = if state.page == i then rl/green else rl/white let color = if state.page == i then rl/green else rl/white
tab-w = 80 tab-w = 80
start-num = i * slots-per-page start-num = i * slots-per-page
end-num = min(4096, (i + 1) * slots-per-page) end-num = min(4096, (i + 1) * slots-per-page)
tab-name = tformat("{:03X}-{:03X}", start-num, end-num - 1) tab-name = format("{:03X}-{:03X}", start-num, end-num - 1)
rl/draw-rectangle(grid-x - 1 + i * tab-w + (i * 2), grid-y - 20, tab-w, 20, color) rl/draw-rectangle(grid-x - 1 + i * tab-w + (i * 2), grid-y - 20, tab-w, 20, color)
draw-text-normal(tab-name, grid-x + 1 + i * tab-w + (i * 2), grid-y - 20, rl/black) draw-text-normal(tab-name, grid-x + 1 + i * tab-w + (i * 2), grid-y - 20, rl/black)
rl/draw-rectangle-lines(grid-x, grid-y, grid-w, grid-h, rl/white) rl/draw-rectangle-lines(grid-x, grid-y, grid-w, grid-h, rl/white)
for r in 0..hex-rows loop r in 0..hex-rows
for c in 0..hex-cols loop c in 0..hex-cols
let idx = c + r * hex-cols + hex-cols * hex-rows * state.page let idx = c + r * hex-cols + hex-cols * hex-rows * state.page
if idx < length(machine.memory) if idx < length(machine.memory)
let reg-val = tformat("{:02X}", machine.memory[idx]) let reg-val = format("{:02X}", machine.memory[idx])
x-padding = if c % 2 == 0 then 2 else -2 x-padding = if c % 2 == 0 then 2 else -2
x = grid-x + x-padding + c * cell-w x = grid-x + x-padding + c * cell-w
y = grid-y + padding + r * cell-h y = grid-y + padding + r * cell-h
@@ -436,12 +468,15 @@ fn run-drawing() -> ()
rl/draw-rectangle(x, y + 2, 19, 15, rl/red) rl/draw-rectangle(x, y + 2, 19, 15, rl/red)
draw-text-normal(reg-val, x, y, rl/white) draw-text-normal(reg-val, x, y, rl/white)
comment: fn audio-callback-fn(buffer: Ptr(u8), frames: u32) -> ()
cpu.stack[0] = 0xAA audio-frequency = frequency + (audio-frequency - frequency) * 0.95
cpu.stack[1] = 0xAB let incr = audio-frequency / 44100.0
cpu.stack[2] = 0xAC d = slice-from(Ptr(i16)(buffer), i32(frames))
cpu.stack[3] = 0xAD loop i in 0..frames
cpu.sp = 15 volume += (target-volume - volume) * 0.002
d[i] = i16(32000.0 * volume * sin(2 * pi * sine-idx))
sine-idx += incr
if sine-idx > 1.0 then sine-idx -= 1.0
fn main() -> i32 fn main() -> i32
rl/set-trace-log-level(:log-warning) rl/set-trace-log-level(:log-warning)
@@ -449,6 +484,15 @@ fn main() -> i32
defer rl/close-window() defer rl/close-window()
rl/set-target-fps(60) rl/set-target-fps(60)
rl/init-audio-device()
defer rl/close-audio-device()
rl/set-audio-stream-buffer-size-default(max-samples-per-update)
;; Init raw audio stream (sample rate: 44100, sample size: 16bit-short, channels: 1-mono)
state.audio-stream = rl/load-audio-stream(44100, 16, 1)
defer rl/unload-audio-stream(state.audio-stream)
rl/set-audio-stream-callback(state.audio-stream, audio-callback-fn)
rl/play-audio-stream(state.audio-stream)
let font = rl/load-font-ex("JetBrainsMono-Regular.otf", 21, []) let font = rl/load-font-ex("JetBrainsMono-Regular.otf", 21, [])
defer rl/unload-font(state.font) defer rl/unload-font(state.font)
state.font = font state.font = font
@@ -460,7 +504,7 @@ fn main() -> i32
rl/unload-image(img) rl/unload-image(img)
rl/set-texture-filter(state.screen-texture, :filter-point) rl/set-texture-filter(state.screen-texture, :filter-point)
until rl/window-should-close() loop not rl/window-should-close()
restart-case restart-case
agent/poll() agent/poll()
run-update() run-update()
@@ -473,9 +517,15 @@ fn main() -> i32
comment: comment:
reset() reset()
load-rom("./IBM Logo.ch8") loop i,pixel in machine.vbuffer
for i,pixel in machine.vbuffer
if pixel if pixel
println("Row:", i / pixels-width, "Col:", i % pixels-width) print("Row:", i / pixels-width, "Col:", i % pixels-width)
state.step-mode = false
state.step-mode = true
machine.sound-timer = 120
cpu.stack[0] = 0xAA
cpu.stack[1] = 0xAB
cpu.stack[2] = 0xAC
cpu.stack[3] = 0xAD
cpu.sp = 15
machine.vbuffer[8 + 12 * pixels-width + 1] = false machine.vbuffer[8 + 12 * pixels-width + 1] = false