Compare commits
8
Commits
70bb250bcd
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ca694f77d | ||
|
|
de58014d0f | ||
|
|
49c471ece6 | ||
|
|
38f6b95da8 | ||
|
|
f23796c4a9 | ||
|
|
eddf7ba800 | ||
|
|
4db4b774b3 | ||
|
|
b5bc179a3c |
No files matched your search
Binary file not shown.
+326
-125
@@ -1,5 +1,4 @@
|
||||
import rl "vendor:raylib"
|
||||
import agent "vendor:agent"
|
||||
|
||||
struct Instruction
|
||||
full: u16
|
||||
@@ -10,12 +9,6 @@ struct Instruction
|
||||
nn: u8
|
||||
nnn: u16
|
||||
|
||||
struct DrawCommand
|
||||
x: u8
|
||||
y: u8
|
||||
pixels: u8
|
||||
addr: u16
|
||||
|
||||
struct Cpu
|
||||
pc: u16
|
||||
sp: u8
|
||||
@@ -30,17 +23,22 @@ struct Machine
|
||||
delay-timer: u8
|
||||
sound-timer: u8
|
||||
|
||||
struct State
|
||||
class State
|
||||
screen-pixels: [2048 rl/Color]
|
||||
screen-texture: rl/Texture2D
|
||||
audio-stream: rl/AudioStream
|
||||
font: rl/Font
|
||||
draw-commands: Vec(DrawCommand)
|
||||
page: int
|
||||
fullscreen: bool
|
||||
step-mode: bool
|
||||
clear-screen: bool
|
||||
pending-key: u8?
|
||||
shift-vx-swap: 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
|
||||
0x20, 0x60, 0x20, 0x20, 0x70, ;; 1
|
||||
0xF0, 0x10, 0xF0, 0x80, 0xF0, ;; 2
|
||||
@@ -58,61 +56,149 @@ const font: [80 u8] =
|
||||
0xF0, 0x80, 0xF0, 0x80, 0xF0, ;; E
|
||||
0xF0, 0x80, 0xF0, 0x80, 0x80] ;; F
|
||||
|
||||
const keypad-mapping: [16 u8] =
|
||||
const numbers: [_ [_ u8]] =
|
||||
[[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: [16 rl/Key] =
|
||||
const keypad-keymap: [_ rl/Key] =
|
||||
[:key-one, :key-two, :key-three, :key-four,
|
||||
:key-q, :key-w, :key-e, :key-r,
|
||||
:key-a, :key-s, :key-d, :key-f,
|
||||
: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-height: int = 650
|
||||
let screen-width: int = 965
|
||||
let screen-height: int = 550
|
||||
let pixels-width: int = 64
|
||||
let pixels-height: int = 32
|
||||
let pixels-width: u8 = 64
|
||||
let pixels-height: u8 = 32
|
||||
let ips: int = 700
|
||||
let cycles: f64 = 0.0
|
||||
|
||||
let cpu: Cpu = Cpu{}
|
||||
let machine: Machine = Machine{}
|
||||
let state: State = State{}
|
||||
once cpu: Cpu = Cpu{}
|
||||
once machine: Machine = Machine{}
|
||||
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) -> ()
|
||||
let rom = slurp(path)
|
||||
for i, b in rom
|
||||
machine.memory[0x200 + i] = b
|
||||
copy(machine.memory[0x200..], rom )
|
||||
|
||||
fn step() -> ()
|
||||
if cpu.pc >= length(machine.memory)
|
||||
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() -> ()
|
||||
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
|
||||
let byte1: u8 = machine.memory[cpu.pc]
|
||||
let byte2: u8 = machine.memory[cpu.pc + 1]
|
||||
let full = u16(byte1) << 8 | u16(byte2)
|
||||
let inst = Instruction{.full full
|
||||
.op (byte1 >> 4)
|
||||
.x (byte1 & 0x0F)
|
||||
.y (byte2 >> 4)
|
||||
.n (byte2 & 0x0F)
|
||||
.nn byte2
|
||||
.nnn (full & 0x0FFF)}
|
||||
|
||||
cpu.pc += 2
|
||||
match inst.op
|
||||
0x0 ->
|
||||
match inst.nn
|
||||
0xE0 -> state.clear-screen = true
|
||||
0xEE ->
|
||||
cpu.pc = cpu.stack[cpu.sp]
|
||||
cpu.sp -= 1
|
||||
_ -> println("Unrecognized 0x0 instruction")
|
||||
cpu.pc = cpu.stack[cpu.sp]
|
||||
_ -> print("Unrecognized 0x0 instruction")
|
||||
0x1 -> cpu.pc = inst.nnn
|
||||
0x2 -> ;; Call a subroutine
|
||||
cpu.stack[cpu.sp] = cpu.pc
|
||||
@@ -122,7 +208,7 @@ fn step() -> ()
|
||||
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
|
||||
0x6 -> cpu.v[inst.x] = inst.nn
|
||||
0x7 -> cpu.v[inst.x] += inst.nn
|
||||
0x7 -> cpu.v[inst.x] +%= inst.nn
|
||||
0x8 ->
|
||||
match inst.n
|
||||
0x0 -> cpu.v[inst.x] = cpu.v[inst.y]
|
||||
@@ -134,72 +220,141 @@ fn step() -> ()
|
||||
cpu.v[inst.x] = u8(sum)
|
||||
cpu.v[0xF] = u8(sum >> 8)
|
||||
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[0xF] = u8(diff >> 8 & 0x1 ^ 1)
|
||||
0x6 ->
|
||||
if state.shift-vx-swap
|
||||
cpu.v[inst.x] = cpu.v[inst.y]
|
||||
cpu.v[0xF] = cpu.v[inst.x] & 1
|
||||
cpu.v[inst.x] = cpu.v[inst.x] >> 1
|
||||
cpu.v[inst.x] >>= 1
|
||||
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[0xF] = u8(diff >> 8 & 0x1 ^ 1)
|
||||
0xE ->
|
||||
if state.shift-vx-swap
|
||||
cpu.v[inst.x] = cpu.v[inst.y]
|
||||
cpu.v[0xF] = (cpu.v[inst.x] & 0x80) >> 7
|
||||
cpu.v[inst.x] = cpu.v[inst.x] << 1
|
||||
_ -> println("Unrecognized 0x8 instruction")
|
||||
cpu.v[inst.x] <<= 1
|
||||
_ -> print("Unrecognized 0x8 instruction")
|
||||
0x9 -> if cpu.v[inst.x] != cpu.v[inst.y] then cpu.pc += 2
|
||||
0xA -> cpu.index = inst.nnn
|
||||
0xB -> cpu.pc = inst.nnn + if state.jump-xnn then cpu.v[inst.x] else cpu.v[0]
|
||||
0xC -> cpu.v[inst.x] = u8(rand-int()) & inst.nn
|
||||
;; DXYN
|
||||
0xD ->
|
||||
put(state.draw-commands, DrawCommand{.x (cpu.v[inst.x] % u8(pixels-width))
|
||||
.y (cpu.v[inst.y] % u8(pixels-height))
|
||||
.pixels inst.n
|
||||
.addr cpu.index})
|
||||
cpu.v[0xF] = 0
|
||||
let x = cpu.v[inst.x] % u8(pixels-width)
|
||||
y = cpu.v[inst.y] % u8(pixels-height)
|
||||
collision = false
|
||||
|
||||
loop row in 0..inst.n
|
||||
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[inst.x]
|
||||
if (inst.nn == 0x9E and pressed) or (inst.nn == 0xA1 and not pressed)
|
||||
cpu.pc += 2
|
||||
_ -> println("Instruction not handled: ", inst.op)
|
||||
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() -> ()
|
||||
for i, key in keypad-keymap
|
||||
machine.keypad[keypad-mapping[int(i)]] = rl/is-key-down(key)
|
||||
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)
|
||||
|
||||
cycles += f64(rl/get-frame-time()) * ips
|
||||
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
|
||||
|
||||
while cycles >= 1.0
|
||||
step()
|
||||
cycles -= 1.0
|
||||
;; 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
|
||||
loop cycles >= 1.0
|
||||
let inst = parse-instruction(cpu.pc)
|
||||
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
|
||||
|
||||
fill(state.screen-pixels, rl/black)
|
||||
if state.clear-screen
|
||||
state.clear-screen = false
|
||||
for i,pixel in machine.vbuffer
|
||||
state.screen-pixels[i] = rl/black
|
||||
else
|
||||
for i,pixel in machine.vbuffer
|
||||
state.screen-pixels[i] = if pixel then rl/white else rl/black
|
||||
loop i,pixel in machine.vbuffer
|
||||
if pixel
|
||||
state.screen-pixels[i] = rl/white
|
||||
|
||||
for 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
|
||||
state.screen-pixels[i] = rl/white
|
||||
;; 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
|
||||
;; 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.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)
|
||||
@@ -207,58 +362,77 @@ fn draw-text-normal(text: str, x: $t, y: $t, color: rl/Color) -> () where is-num
|
||||
fn run-drawing() -> ()
|
||||
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)
|
||||
|
||||
let scale: f32 = 5.5
|
||||
;; Texture
|
||||
let scale: f32 = if state.fullscreen then 14 else 5
|
||||
src = rl/Rectangle(0, 0, f32(pixels-width), f32(pixels-height))
|
||||
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-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
|
||||
col = i / 8
|
||||
x = 10
|
||||
y = 20 * row
|
||||
w = 80
|
||||
w = 70
|
||||
h = 20
|
||||
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)
|
||||
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
|
||||
let addr-txt = tformat("0x{:02X}", cpu.stack[i])
|
||||
draw-text-normal(addr-txt, x + 33 + col * w, start-y + y, rl/white)
|
||||
let addr-txt = format("0x{:02X}", cpu.stack[i])
|
||||
draw-text-normal(addr-txt, x + 24 + col * w, start-y + y, rl/white)
|
||||
|
||||
let x = 195
|
||||
draw-text-normal(tformat(" i=0x{:02X}", cpu.index), x, 360, rl/white)
|
||||
draw-text-normal(tformat("pc=0x{:02X}", cpu.sp), x, 380, rl/white)
|
||||
draw-text-normal(tformat("dt=0x{:02}", machine.delay-timer), x, 400, rl/white)
|
||||
draw-text-normal(tformat("st=0x{:02}", machine.sound-timer), x, 420, 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
|
||||
@@ -270,29 +444,39 @@ fn run-drawing() -> ()
|
||||
grid-h = hex-rows * cell-h + padding * 2
|
||||
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
|
||||
tab-w = 80
|
||||
start-num = i * 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)
|
||||
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)
|
||||
for r in 0..hex-rows
|
||||
for c in 0..hex-cols
|
||||
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 = tformat("{:02X}", machine.memory[idx])
|
||||
let x-padding = if c % 2 == 0 then 2 else -2
|
||||
draw-text-normal(reg-val, grid-x + x-padding + c * cell-w, grid-y + padding + r * cell-h, rl/white)
|
||||
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)
|
||||
|
||||
cpu.stack[0] = 0xAA
|
||||
cpu.stack[1] = 0xAB
|
||||
cpu.stack[2] = 0xAC
|
||||
cpu.stack[3] = 0xAD
|
||||
cpu.sp = 15
|
||||
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
|
||||
rl/set-trace-log-level(:log-warning)
|
||||
@@ -300,18 +484,27 @@ fn main() -> i32
|
||||
defer rl/close-window()
|
||||
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
|
||||
state.page = 2
|
||||
cpu.pc = 0x200
|
||||
state.step-mode = true
|
||||
let img = rl/gen-image-color(pixels-width, pixels-height, rl/black)
|
||||
state.screen-texture = rl/load-texture-from-image(img)
|
||||
defer rl/unload-texture(state.screen-texture)
|
||||
rl/unload-image(img)
|
||||
rl/set-texture-filter(state.screen-texture, :filter-point)
|
||||
|
||||
until rl/window-should-close()
|
||||
loop not rl/window-should-close()
|
||||
restart-case
|
||||
agent/poll()
|
||||
run-update()
|
||||
@@ -323,8 +516,16 @@ fn main() -> i32
|
||||
0
|
||||
|
||||
comment:
|
||||
load-rom("./IBM Logo.ch8")
|
||||
u8(state.memory[0x200])
|
||||
state.memory[0x200..0x220]
|
||||
; (at (.memory state) (.. 0x200 0x220))
|
||||
u16(state.memory[state.pc]) << 8 | u16(state.memory[state.pc + 1])
|
||||
reset()
|
||||
loop i,pixel in machine.vbuffer
|
||||
if pixel
|
||||
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
|
||||
Reference in new issue
Block a user