Compare commits

..
10 Commits
2 changed files with 382 additions and 84 deletions

No files matched your search

Binary file not shown.
+382 -84
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
@@ -10,12 +9,6 @@ struct Instruction
nn: u8 nn: u8
nnn: u16 nnn: u16
struct DrawCommand
x: u8
y: u8
pixels: u8
addr: u16
struct Cpu struct Cpu
pc: u16 pc: u16
sp: u8 sp: u8
@@ -30,15 +23,22 @@ struct Machine
delay-timer: u8 delay-timer: u8
sound-timer: u8 sound-timer: u8
struct State class State
clear-screen: bool
screen-pixels: [2048 rl/Color] screen-pixels: [2048 rl/Color]
screen-texture: rl/Texture2D screen-texture: rl/Texture2D
draw-commands: Vec(DrawCommand) audio-stream: rl/AudioStream
font: rl/Font
page: int
fullscreen: bool
step-mode: bool
clear-screen: bool
pending-key: u8?
shift-vx-swap: bool shift-vx-swap: bool
jump-xnn: bool jump-xnn: bool
skip-overflow-FX1E: 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
@@ -56,57 +56,149 @@ 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
;; keypad-keys is in layout order, so keypad[i] isn't hex key i. That doesn't matter until EX9E and EXA1. const numbers: [_ [_ u8]] =
const keypad-keys: [16 rl/Key] = [[0xF0, 0x90, 0x90, 0x90, 0xF0] ;; 0
[0x20, 0x60, 0x20, 0x20, 0x70] ;; 1
[0xF0, 0x10, 0xF0, 0x80, 0xF0] ;; 2
[0xF0, 0x10, 0xF0, 0x10, 0xF0] ;; 3
[0x90, 0x90, 0xF0, 0x10, 0x10] ;; 4
[0xF0, 0x80, 0xF0, 0x10, 0xF0] ;; 5
[0xF0, 0x80, 0xF0, 0x90, 0xF0] ;; 6
[0xF0, 0x10, 0x20, 0x40, 0x40] ;; 7
[0xF0, 0x90, 0xF0, 0x90, 0xF0] ;; 8
[0xF0, 0x90, 0xF0, 0x10, 0xF0] ;; 9
[0xF0, 0x90, 0xF0, 0x90, 0x90] ;; A
[0xE0, 0x90, 0xE0, 0x90, 0xE0] ;; B
[0xF0, 0x80, 0x80, 0x80, 0xF0] ;; C
[0xE0, 0x90, 0x90, 0x90, 0xE0] ;; D
[0xF0, 0x80, 0xF0, 0x80, 0xF0] ;; E
[0xF0, 0x80, 0xF0, 0x80, 0x80]] ;; F
const keypad-mapping: [_ u8] =
[1, 2, 3, 0xC, 4, 5, 6, 0xD, 7, 8, 9, 0xE, 0xA, 0, 0xB, 0xF]
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]
;; [:key-one, :key-two, :key-three, :key-c,
;; :key-four, :key-five, :key-six, :key-d,
;; :key-seven, :key-eight, :key-nine, :key-e,
;; :key-a, :key-zero, :key-b, :key-f]
const keypad-chars: str = "123c456d789ea0bf" ;; let screen-width: int = 1300
;; let screen-height: int = 650
let screen-width: int = 1024 let screen-width: int = 965
let screen-height: int = 768 let screen-height: int = 550
let pixels-width: int = 64 let pixels-width: u8 = 64
let pixels-height: int = 32 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 step() -> () fn parse-instruction(pc: u16) -> Instruction
if cpu.pc >= length(machine.memory) let byte1: u8 = machine.memory[pc]
cpu.pc = 0 let byte2: u8 = machine.memory[pc + 1]
return
let byte1: u8 = machine.memory[cpu.pc]
let byte2: u8 = machine.memory[cpu.pc + 1]
let full = u16(byte1) << 8 | u16(byte2) let full = u16(byte1) << 8 | u16(byte2)
let inst = Instruction{.full full Instruction{.full full
.op (byte1 >> 4) .op (byte1 >> 4)
.x (byte1 & 0x0F) .x (byte1 & 0x0F)
.y (byte2 >> 4) .y (byte2 >> 4)
.n (byte2 & 0x0F) .n (byte2 & 0x0F)
.nn byte2 .nn byte2
.nnn (full & 0x0FFF)} .nnn (full & 0x0FFF)}
fn reset() -> ()
cpu = Cpu{}
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
fn decode-instruction(inst: Instruction) -> str
match inst.op
0x0 ->
match inst.nn
;; TODO: Maybe this can be a draw command that clears the screen rather than special cased
0xE0 -> "CLS"
0xEE -> "RET"
_ -> "UNKNOWN"
0x1 -> format("JP 0x{:03X}", inst.nnn)
0x2 -> format("CALL 0x{:03X}", inst.nnn)
0x3 -> format("SE V{:X}, 0x{:02X}", inst.x, inst.nn)
0x4 -> format("SNE V{:X}, 0x{:02X}", inst.x, inst.nn)
0x5 -> format("SE V{:X}, V{:X}", inst.x, inst.y)
0x6 -> format("LD V{:X}, 0x{:02x}", inst.x, inst.nn)
0x7 -> format("ADD V{:X}, 0x{:02x}", inst.x, inst.nn)
0x8 ->
match inst.n
0x0 -> format("LD V{:X}, V{:X}", inst.x, inst.y)
0x1 -> format("OR V{:X}, V{:X}", inst.x, inst.y)
0x2 -> format("AND V{:X}, V{:X}", inst.x, inst.y)
0x3 -> format("XOR V{:X}, V{:X}", inst.x, inst.y)
0x4 -> format("ADD V{:X}, V{:X}", inst.x, inst.y)
0x5 -> format("SUB V{:X}, V{:X}", inst.x, inst.y)
0x6 -> format("SHR V{:X}, V{:X}", inst.x, inst.y)
0x7 -> format("SUBN V{:X}, V{:X}", inst.x, inst.y)
0xE -> format("SHL V{:X}, V{:X}", inst.x, inst.y)
_ -> "UNKNOWN"
0x9 -> format("SNE V{:X}, V{:X}", inst.x, inst.y)
0xA -> format("LD I, 0x{:03X}", inst.nnn)
0xB -> format("JP V0, 0x{:03X}", inst.nnn)
0xC -> format("RND V{:X}, 0x{:02x}", inst.x, inst.nn)
0xD -> format("DRW V{:X}, V{:X}, 0x{:X}", inst.x, inst.y, inst.n)
0xE ->
match inst.nn
0x9E -> format("SKP V{:X}", inst.x)
0xA1 -> format("SKNP V{:X}", inst.x)
_ -> "UNKNOWN"
0xF ->
match inst.nn
0x07 -> format("LD V{:X}, DT", inst.x)
0x0A -> format("LD V{:X}, K", inst.x)
0x15 -> format("LD DT, V{:X}", inst.x)
0x18 -> format("LD ST, V{:X}", inst.x)
0x1E -> format("ADD I, V{:X}", inst.x)
0x29 -> format("LD F, V{:X}", inst.x)
0x33 -> format("LD B, V{:X}", inst.x)
0x55 -> format("LD [I], V{:X}", inst.x)
0x65 -> format("LD V{:X}, [I]", inst.x)
_ -> "UNKNOWN"
_ -> "UNKNOWN"
;; 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
fn step(inst: Instruction) -> ()
if cpu.pc >= length(machine.memory) - 2
cpu.pc = 0
return
cpu.pc += 2 cpu.pc += 2
match inst.op match inst.op
0x0 -> 0x0 ->
match inst.nn match inst.nn
0xE0 -> state.clear-screen = true 0xE0 -> state.clear-screen = true
0xEE -> 0xEE ->
cpu.pc = cpu.stack[cpu.sp]
cpu.sp -= 1 cpu.sp -= 1
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
@@ -116,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]
@@ -124,83 +216,267 @@ fn step() -> ()
0x2 -> cpu.v[inst.x] &= cpu.v[inst.y] 0x2 -> cpu.v[inst.x] &= cpu.v[inst.y]
0x3 -> cpu.v[inst.x] ^= cpu.v[inst.y] 0x3 -> cpu.v[inst.x] ^= cpu.v[inst.y]
0x4 -> 0x4 ->
let sum = cpu.v[inst.x] + cpu.v[inst.y] let sum = u16(cpu.v[inst.x]) + u16(cpu.v[inst.y])
cpu.v[inst.x] = 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] = 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] = 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
_ -> 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 -> 0xB -> cpu.pc = inst.nnn + if state.jump-xnn then cpu.v[inst.x] else cpu.v[0]
;; DXYN 0xC -> cpu.v[inst.x] = u8(rand-int()) & inst.nn
0xD -> 0xD ->
put(state.draw-commands, DrawCommand{.x (cpu.v[inst.x] % u8(pixels-width)) let x = cpu.v[inst.x] % u8(pixels-width)
.y (cpu.v[inst.y] % u8(pixels-height)) y = cpu.v[inst.y] % u8(pixels-height)
.pixels inst.n collision = false
.addr cpu.index})
cpu.v[0xF] = 0 loop row in 0..inst.n
_ -> println("Instruction not handled: ", inst.op) let byte = machine.memory[cpu.index + row]
let idx = i32(x) + (row + i32(y)) * i32(pixels-width)
loop bit in reverse(0..8)
if byte >> bit & 0x1 == 1 and idx < length(machine.vbuffer)
if machine.vbuffer[idx] then collision = true
machine.vbuffer[idx] = not machine.vbuffer[idx]
idx += 1
cpu.v[0xF] = if collision then 1 else 0
0xE ->
let pressed = machine.keypad[cpu.v[inst.x]]
match inst.nn
0x9E -> if pressed then cpu.pc += 2
0xA1 -> if not pressed then cpu.pc += 2
_ -> print("Unrecognized 0xE instruction")
0xF ->
match inst.nn
0x07 -> cpu.v[inst.x] = machine.delay-timer
0x0A -> state.pending-key = inst.x
0x15 -> machine.delay-timer = cpu.v[inst.x]
0x18 -> machine.sound-timer = cpu.v[inst.x]
0x1E ->
cpu.index += cpu.v[inst.x]
if not state.skip-overflow-FX1E
cpu.v[0xF] = u8(cpu.index >> 12)
cpu.index += cpu.v[inst.x]
0x29 -> cpu.index = cpu.v[inst.x]
0x33 ->
let vx = cpu.v[inst.x]
machine.memory[cpu.index + 0] = vx / 100
machine.memory[cpu.index + 1] = vx % 100 / 10
machine.memory[cpu.index + 2] = vx % 10
0x55 ->
loop i in 0..=inst.x
machine.memory[cpu.index + i] = cpu.v[i]
if state.inc-index-0xFX-55-65
cpu.index += inst.x
0x65 ->
loop i in 0..=inst.x
cpu.v[i] = machine.memory[cpu.index + i]
if state.inc-index-0xFX-55-65
cpu.index += inst.x
_ -> print("Unrecognized 0xF instruction")
_ -> print("Instruction not handled: {:X}", inst.op)
fn run-update() -> () fn run-update() -> ()
loop i, key in keypad-keymap
machine.keypad[keypad-mapping[i]] = rl/is-key-down(key)
if state.pending-key as reg
cpu.v[reg] = u8(i)
state.pending-key = nil
if rl/is-key-pressed(:key-minus)
state.page = max(0, state.page - 1)
if rl/is-key-pressed(:key-equal)
state.page = min(5, state.page + 1)
if rl/is-key-pressed(:key-period)
reset()
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 machine.delay-timer > 0 then machine.delay-timer -= 1
return
if state.step-mode
if rl/is-key-pressed(:key-n) or rl/is-key-pressed-repeat(:key-n)
let inst = parse-instruction(cpu.pc)
step(inst)
else
cycles += f64(rl/get-frame-time()) * ips cycles += f64(rl/get-frame-time()) * ips
loop cycles >= 1.0
for i, key in keypad-keys let inst = parse-instruction(cpu.pc)
machine.keypad[int(i)] = rl/is-key-down(key) step(inst)
;; Apparently on the COSMAC VIP and other devices, drawing would wait for the next scree
while cycles >= 1.0 ;; refresh which ran at 60hz
step() 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
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)
rl/draw-text-ex(state.font, text, rl/Vector2(f32(x), f32(y)), 21, 0.0, color)
fn run-drawing() -> () fn run-drawing() -> ()
rl/clear-background(rl/black) rl/clear-background(rl/black)
rl/draw-fps(10, 10)
rl/draw-text("Keypad", 10, 40, 21, rl/white) ;; Texture
let key-size = 30 let scale: f32 = if state.fullscreen then 14 else 5
for i, key in keypad-keys
let x = 10 + (key-size * (i % 4))
y = 70 + (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)
rl/draw-text(keypad-chars[i..i + 1], x + key-size / 4, y + key-size / 4, 16, color)
let scale: f32 = 8.0
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, 250, 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)
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
col = i / 8
x = 10
y = 20 * row
w = 70
h = 20
start-y = 360
idx-txt = format("{:X}|", i)
rl/draw-rectangle-lines(x + col * w, start-y + y, w, h, rl/white)
draw-text-normal(idx-txt, x + 4 + col * w, start-y + y, rl/white)
if i < cpu.sp
let addr-txt = format("0x{:02X}", cpu.stack[i])
draw-text-normal(addr-txt, x + 24 + col * w, start-y + y, rl/white)
;; Additional data
let x = 185
draw-text-normal(format(" i=0x{:02X}", cpu.index), x, 360, rl/white)
draw-text-normal(format("pc=0x{:02X}", cpu.pc), x, 380, 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
let hex-rows = 32
hex-cols = 24
padding = 4
cell-w = 24
cell-h = 16
grid-x = 370
grid-y = 25
grid-w = hex-cols * cell-w + padding * 2
grid-h = hex-rows * cell-h + padding * 2
slots-per-page = hex-rows * hex-cols
loop i in 0..4096 / slots-per-page + 1
let color = if state.page == i then rl/green else rl/white
tab-w = 80
start-num = i * slots-per-page
end-num = min(4096, (i + 1) * slots-per-page)
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)
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)
loop r in 0..hex-rows
loop c in 0..hex-cols
let idx = c + r * hex-cols + hex-cols * hex-rows * state.page
if idx < length(machine.memory)
let reg-val = format("{:02X}", machine.memory[idx])
x-padding = if c % 2 == 0 then 2 else -2
x = grid-x + x-padding + c * cell-w
y = grid-y + padding + r * cell-h
if idx == cpu.pc
rl/draw-rectangle(x, y + 2, 38, 15, rl/darkgreen)
if idx == cpu.index
rl/draw-rectangle(x, y + 2, 19, 15, rl/red)
draw-text-normal(reg-val, x, y, rl/white)
fn audio-callback-fn(buffer: Ptr(u8), frames: u32) -> ()
audio-frequency = frequency + (audio-frequency - frequency) * 0.95
let incr = audio-frequency / 44100.0
d = slice-from(Ptr(i16)(buffer), i32(frames))
loop i in 0..frames
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)
@@ -208,26 +484,48 @@ 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, [])
defer rl/unload-font(state.font)
state.font = font
cpu.pc = 0x200 cpu.pc = 0x200
state.step-mode = true
let img = rl/gen-image-color(pixels-width, pixels-height, rl/black) let img = rl/gen-image-color(pixels-width, pixels-height, rl/black)
state.screen-texture = rl/load-texture-from-image(img) state.screen-texture = rl/load-texture-from-image(img)
defer rl/unload-texture(state.screen-texture) defer rl/unload-texture(state.screen-texture)
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()
rl/with-drawing: rl/with-drawing:
run-drawing() run-drawing()
free-temp()
restart continue() restart continue()
() ()
0 0
comment: comment:
load-rom("./IBM Logo.ch8") reset()
u8(state.memory[0x200]) loop i,pixel in machine.vbuffer
state.memory[0x200..0x220] if pixel
; (at (.memory state) (.. 0x200 0x220)) print("Row:", i / pixels-width, "Col:", i % pixels-width)
u16(state.memory[state.pc]) << 8 | u16(state.memory[state.pc + 1]) 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