code wiki / (root) / nx_chip8.nx

nx_chip8.nx source

↩ module page · 261 lines · 11031 B

1// nx_chip8.nx -- Implements a CHIP-8 interpreter with framebuffer, input, timers, and memory management in a single binary. 2const O_MAGIC_4096: i64 = 4096 3const O_MAGIC_2048: i64 = 2048 4const O_MAGIC_439041101: i64 = 439041101 5const O_MAGIC_88172645463325252: i64 = 88172645463325252 6// nx_chip8.nx -- R0.3 SOVEREIGN CHIP-8 interpreter. The on-ramp emulator: a public-domain VM spec (RCA 7// COSMAC VIP, 1977) implemented from scratch -> 100% Nishi, and PD CHIP-8 games are legally spotless. 8// Base-relative fixed-offset state (the proven nx_wasmfps idiom) so the SAME code runs NATIVE (base=mmap, 9// gateable) and WASM (base=0, browser). Exercises the whole sovereign substrate end to end: CPU interp + 10// framebuffer + input + timers. The same substrate carries x86/DOS (Phase 1). license_tier: ORIGINAL 11// 12// genealogy_id: sovereign_emulator_chip8 lineage_id: nx_chip8_v1 (parents: nx_emu_probe fetch-decode-execute) 13// 14// fixed linear-memory layout (byte offsets): 15const O_MEM: i64 = 0 // 4096 guest bytes (programs load at 0x200) 16const O_V: i64 = 4096 // 16 V registers (i64 each, hold an 8-bit value) -> 4224 17const O_REG: i64 = 4224 // 6 i64: I, PC, SP, DT, ST, RNG -> 4272 18const O_STACK: i64 = 4272 // 16-level call stack (i64) -> 4400 19const O_KEYS: i64 = 4400 // 16 key states 0/1 (i64) -> 4528 20const O_DISP: i64 = 4528 // 64*32 display pixels 0/1 (i64) -> 20912 21 22const RI_I: i64 = 0 23const RI_PC: i64 = 1 24const RI_SP: i64 = 2 25const RI_DT: i64 = 3 26const RI_ST: i64 = 4 27const RI_RNG: i64 = 5 28 29// write 5 font bytes at mem offset. 30func ch8_fw(mem: *u8, off: i64, a: i64, b: i64, c: i64, d: i64, e: i64) -> i64 { 31 mem[off] = a as u8; mem[off + 1] = b as u8; mem[off + 2] = c as u8; mem[off + 3] = d as u8; mem[off + 4] = e as u8 32 return 0 33} 34// the standard 16-char hex fontset at mem[0..80]; FX29 sets I = digit*5 to point here. 35func ch8_load_font(base: i64) -> i64 { 36 let mem: *u8 = (base + O_MEM) as *u8 37 ch8_fw(mem, 0, 240, 144, 144, 144, 240) // 0 38 ch8_fw(mem, 5, 32, 96, 32, 32, 112) // 1 39 ch8_fw(mem, 10, 240, 16, 240, 128, 240) // 2 40 ch8_fw(mem, 15, 240, 16, 240, 16, 240) // 3 41 ch8_fw(mem, 20, 144, 144, 240, 16, 16) // 4 42 ch8_fw(mem, 25, 240, 128, 240, 16, 240) // 5 43 ch8_fw(mem, 30, 240, 128, 240, 144, 240) // 6 44 ch8_fw(mem, 35, 240, 16, 32, 64, 64) // 7 45 ch8_fw(mem, 40, 240, 144, 240, 144, 240) // 8 46 ch8_fw(mem, 45, 240, 144, 240, 16, 240) // 9 47 ch8_fw(mem, 50, 240, 144, 240, 144, 144) // A 48 ch8_fw(mem, 55, 224, 144, 224, 144, 224) // B 49 ch8_fw(mem, 60, 240, 128, 128, 128, 240) // C 50 ch8_fw(mem, 65, 224, 144, 144, 144, 224) // D 51 ch8_fw(mem, 70, 240, 128, 240, 128, 240) // E 52 ch8_fw(mem, 75, 240, 128, 240, 128, 128) // F 53 return 0 54} 55 56func ch8_reset(base: i64) -> i64 { 57 let mem: *u8 = (base + O_MEM) as *u8 58 var i: i64 = 0 59 while i < O_MAGIC_4096 { mem[i] = 0 as u8; i = i + 1 } 60 let V: *i64 = (base + O_V) as *i64 61 i = 0; while i < 16 { V[i] = 0; i = i + 1 } 62 let stack: *i64 = (base + O_STACK) as *i64 63 i = 0; while i < 16 { stack[i] = 0; i = i + 1 } 64 let keys: *i64 = (base + O_KEYS) as *i64 65 i = 0; while i < 16 { keys[i] = 0; i = i + 1 } 66 let disp: *i64 = (base + O_DISP) as *i64 67 i = 0; while i < O_MAGIC_2048 { disp[i] = 0; i = i + 1 } 68 let reg: *i64 = (base + O_REG) as *i64 69 reg[RI_I] = 0 70 reg[RI_PC] = 512 // 0x200 -- programs start here 71 reg[RI_SP] = 0 72 reg[RI_DT] = 0 73 reg[RI_ST] = 0 74 reg[RI_RNG] = O_MAGIC_439041101 // 0x1A2B3C4D -- fixed seed -> deterministic (testable) CXNN 75 ch8_load_font(base) // 16-char hex fontset at mem[0..80] (FX29 points here) 76 return 0 77} 78 79// load one program byte into guest memory (the gate / browser uses this to place a ROM). 80func ch8_load(base: i64, addr: i64, byte: i64) -> i64 { 81 let mem: *u8 = (base + O_MEM) as *u8 82 mem[addr] = (byte & 0xff) as u8 83 return 0 84} 85 86// xorshift64 PRNG (its low bits are fine, unlike an LCG). advances + returns new state. 87func ch8_rand(s: i64) -> i64 { 88 var x: i64 = s 89 if x == 0 { x = O_MAGIC_88172645463325252 } 90 x = x ^ (x << 13) 91 x = x ^ (x >> 7) 92 x = x ^ (x << 17) 93 return x 94} 95 96// execute ONE instruction at PC. returns 0. 97func ch8_step(base: i64) -> i64 { 98 let mem: *u8 = (base + O_MEM) as *u8 99 let V: *i64 = (base + O_V) as *i64 100 let reg: *i64 = (base + O_REG) as *i64 101 let stack: *i64 = (base + O_STACK) as *i64 102 let keys: *i64 = (base + O_KEYS) as *i64 103 let disp: *i64 = (base + O_DISP) as *i64 104 105 var pc: i64 = reg[RI_PC] 106 let hi: i64 = mem[pc] as i64 107 let lo: i64 = mem[pc + 1] as i64 108 let op: i64 = (hi << 8) | lo 109 pc = pc + 2 110 let nnn: i64 = op & 0xfff 111 let nn: i64 = lo 112 let n: i64 = op & 0xf 113 let x: i64 = hi & 0xf 114 let y: i64 = (lo >> 4) & 0xf 115 let top: i64 = (op >> 12) & 0xf 116 117 if top == 0 { 118 if op == 224 { // 00E0 clear display 119 var i: i64 = 0 120 while i < O_MAGIC_2048 { disp[i] = 0; i = i + 1 } 121 } 122 if op == 238 { // 00EE return 123 reg[RI_SP] = reg[RI_SP] - 1 124 pc = stack[reg[RI_SP]] 125 } 126 } 127 if top == 1 { pc = nnn } // 1NNN jump 128 if top == 2 { // 2NNN call 129 stack[reg[RI_SP]] = pc 130 reg[RI_SP] = reg[RI_SP] + 1 131 pc = nnn 132 } 133 if top == 3 { if V[x] == nn { pc = pc + 2 } } // 3XNN skip eq 134 if top == 4 { if V[x] != nn { pc = pc + 2 } } // 4XNN skip ne 135 if top == 5 { if V[x] == V[y] { pc = pc + 2 } } // 5XY0 skip eq reg 136 if top == 6 { V[x] = nn } // 6XNN set 137 if top == 7 { V[x] = (V[x] + nn) & 0xff } // 7XNN add 138 if top == 8 { 139 if n == 0 { V[x] = V[y] } 140 if n == 1 { V[x] = (V[x] | V[y]) & 0xff } 141 if n == 2 { V[x] = (V[x] & V[y]) & 0xff } 142 if n == 3 { V[x] = (V[x] ^ V[y]) & 0xff } 143 if n == 4 { // add with carry 144 let s: i64 = V[x] + V[y] 145 var f: i64 = 0 146 if s > 255 { f = 1 } 147 V[x] = s & 0xff 148 V[15] = f 149 } 150 if n == 5 { // sub, VF = NOT borrow 151 var f: i64 = 0 152 if V[x] >= V[y] { f = 1 } 153 V[x] = (V[x] - V[y]) & 0xff 154 V[15] = f 155 } 156 if n == 6 { // shr, VF = lsb 157 let f: i64 = V[x] & 1 158 V[x] = (V[x] >> 1) & 0xff 159 V[15] = f 160 } 161 if n == 7 { // subn, VF = NOT borrow 162 var f: i64 = 0 163 if V[y] >= V[x] { f = 1 } 164 V[x] = (V[y] - V[x]) & 0xff 165 V[15] = f 166 } 167 if n == 14 { // 8XYE shl, VF = msb 168 let f: i64 = (V[x] >> 7) & 1 169 V[x] = (V[x] << 1) & 0xff 170 V[15] = f 171 } 172 } 173 if top == 9 { if V[x] != V[y] { pc = pc + 2 } } // 9XY0 skip ne reg 174 if top == 10 { reg[RI_I] = nnn } // ANNN set I 175 if top == 11 { pc = (nnn + V[0]) & 0xfff } // BNNN jump V0+NNN 176 if top == 12 { // CXNN rand & NN 177 reg[RI_RNG] = ch8_rand(reg[RI_RNG]) 178 V[x] = ((reg[RI_RNG] >> 20) & nn) & 0xff 179 } 180 if top == 13 { // DXYN draw sprite 181 let vx: i64 = V[x] 182 let vy: i64 = V[y] 183 V[15] = 0 184 var row: i64 = 0 185 while row < n { 186 let sprite: i64 = mem[reg[RI_I] + row] as i64 187 var col: i64 = 0 188 while col < 8 { 189 let bit: i64 = (sprite >> (7 - col)) & 1 190 if bit == 1 { 191 let px: i64 = (vx + col) & 63 192 let py: i64 = (vy + row) & 31 193 let idx: i64 = py * 64 + px 194 if disp[idx] == 1 { V[15] = 1 } 195 disp[idx] = disp[idx] ^ 1 196 } 197 col = col + 1 198 } 199 row = row + 1 200 } 201 } 202 if top == 14 { // EX9E / EXA1 key skips 203 if nn == 158 { if keys[V[x]] == 1 { pc = pc + 2 } } 204 if nn == 161 { if keys[V[x]] == 0 { pc = pc + 2 } } 205 } 206 if top == 15 { 207 if nn == 7 { V[x] = reg[RI_DT] } // FX07 get delay 208 if nn == 10 { // FX0A wait for key (block by re-running) 209 var got: i64 = 0 - 1 210 var k: i64 = 0 211 while k < 16 { if keys[k] == 1 { got = k } k = k + 1 } 212 if got < 0 { pc = pc - 2 } else { V[x] = got } 213 } 214 if nn == 21 { reg[RI_DT] = V[x] } // FX15 set delay 215 if nn == 24 { reg[RI_ST] = V[x] } // FX18 set sound 216 if nn == 30 { reg[RI_I] = (reg[RI_I] + V[x]) & 0xffff } // FX1E add to I 217 if nn == 41 { reg[RI_I] = V[x] * 5 } // FX29 font sprite addr (font at mem[0], 5 B/char) 218 if nn == 51 { // FX33 BCD 219 let vv: i64 = V[x] 220 mem[reg[RI_I]] = (vv / 100) as u8 221 mem[reg[RI_I] + 1] = ((vv / 10) % 10) as u8 222 mem[reg[RI_I] + 2] = (vv % 10) as u8 223 } 224 if nn == 85 { // FX55 store V0..Vx 225 var i: i64 = 0 226 while i <= x { mem[reg[RI_I] + i] = (V[i] & 0xff) as u8; i = i + 1 } 227 } 228 if nn == 101 { // FX65 load V0..Vx 229 var i: i64 = 0 230 while i <= x { V[i] = mem[reg[RI_I] + i] as i64; i = i + 1 } 231 } 232 } 233 234 reg[RI_PC] = pc 235 return 0 236} 237 238// run `cycles` instructions, then tick the 60 Hz timers once (one frame). 239func ch8_run_frame(base: i64, cycles: i64) -> i64 { 240 var c: i64 = 0 241 while c < cycles { ch8_step(base); c = c + 1 } 242 let reg: *i64 = (base + O_REG) as *i64 243 if reg[RI_DT] > 0 { reg[RI_DT] = reg[RI_DT] - 1 } 244 if reg[RI_ST] > 0 { reg[RI_ST] = reg[RI_ST] - 1 } 245 return 0 246} 247 248// ---- inspection / IO (the gate + the browser shell use these) ---- 249func ch8_key(base: i64, k: i64, down: i64) -> i64 { 250 let keys: *i64 = (base + O_KEYS) as *i64 251 keys[k & 15] = down 252 return 0 253} 254func ch8_reg(base: i64, xi: i64) -> i64 { let V: *i64 = (base + O_V) as *i64; return V[xi & 15] } 255func ch8_vf(base: i64) -> i64 { let V: *i64 = (base + O_V) as *i64; return V[15] } 256func ch8_geti(base: i64) -> i64 { let reg: *i64 = (base + O_REG) as *i64; return reg[RI_I] } 257func ch8_getpc(base: i64) -> i64 { let reg: *i64 = (base + O_REG) as *i64; return reg[RI_PC] } 258func ch8_pixel(base: i64, px: i64, py: i64) -> i64 { let disp: *i64 = (base + O_DISP) as *i64; return disp[py * 64 + px] } 259func ch8_disp_off() -> i64 { return O_DISP } 260func ch8_disp_w() -> i64 { return 64 } 261func ch8_disp_h() -> i64 { return 32 }