nx_emu_probe.nx source
↩ module page · 110 lines · 4654 B
1const PO_MAGIC_1000000: i64 = 1000000
2// nx_emu_probe.nx -- R0.0 FOUNDATION PROOF for the sovereign emulator track.
3//
4// An EMULATOR-SHAPED, array-heavy kernel that (a) runs native for a correctness KAT and (b) compiles
5// .nx -> wat -> wasm through the sovereign pipeline (nx_compile_wat.sov.elf + nx_wat_compiler.sov.elf),
6// to LOCK the reproducible recipe + idiom the CHIP-8 core (R0.3) and later the x86/DOS core (Phase 1) reuse.
7//
8// Base-relative fixed linear-memory layout -- the proven nx_wasmfps / nx_render_core idiom: every buffer
9// lives at a fixed byte offset; base=0 in wasm (linear memory), an mmap region in native. This sidesteps
10// the wasm backend's only real gap (array/address-taken local allocas need a shadow stack) by construction.
11//
12// The three state shapes ANY machine emulator needs are all here: guest MEMORY (bytes, load8/store8),
13// REGISTERS (i64), and a FRAMEBUFFER (i64 RGBA, i64.store on a computed address). license_tier: ORIGINAL
14//
15// genealogy_id: sovereign_emulator_substrate
16// lineage_id: nx_emu_probe_v1 (parents: nx_wasmfps base-relative idiom, nx_render_core caller-buffers)
17
18// ---- fixed linear-memory layout (byte offsets) ----
19const PO_MEM: i64 = 0 // 4096 guest bytes (the CHIP-8 address space size)
20const PO_REG: i64 = 4096 // 16 V registers, i64 each (128 bytes)
21const PO_FB: i64 = 4224 // 64*32 display cells, i64 RGBA each (16384 bytes); ends at 20608
22
23// ---- guest memory (byte-addressed: exercises wasm i64.load8_u / i64.store8) ----
24func p_memset_byte(base: i64, addr: i64, v: i64) -> i64 {
25 let mem: *u8 = (base + PO_MEM) as *u8
26 mem[addr] = (v & 0xff) as u8
27 return 0
28}
29func p_memget_byte(base: i64, addr: i64) -> i64 {
30 let mem: *u8 = (base + PO_MEM) as *u8
31 return mem[addr] as i64
32}
33// seed guest memory: mem[i] = (i*7+3) & 0xff (store8 + gep loop)
34func p_seed(base: i64, n: i64) -> i64 {
35 let mem: *u8 = (base + PO_MEM) as *u8
36 var i: i64 = 0
37 while i < n { mem[i] = ((i * 7 + 3) & 0xff) as u8; i = i + 1 }
38 return 0
39}
40// sum guest bytes 0..n (load8 + gep + accumulate) -- a known-answer reduction
41func p_sum(base: i64, n: i64) -> i64 {
42 let mem: *u8 = (base + PO_MEM) as *u8
43 var i: i64 = 0
44 var s: i64 = 0
45 while i < n { s = s + (mem[i] as i64); i = i + 1 }
46 return s
47}
48
49// ---- registers (i64-addressed: exercises wasm i64.load / i64.store on computed addresses) ----
50func p_reg_get(base: i64, x: i64) -> i64 {
51 let reg: *i64 = (base + PO_REG) as *i64
52 return reg[x]
53}
54func p_reg_set(base: i64, x: i64, v: i64) -> i64 {
55 let reg: *i64 = (base + PO_REG) as *i64
56 reg[x] = v
57 return 0
58}
59
60// ---- ONE fetch-decode-execute step (the emulator inner loop, in miniature) ----
61// reads a 2-byte big-endian opcode at pc and dispatches a tiny CHIP-8-shaped op set:
62// 6XNN -> Vx = NN ; 7XNN -> Vx += NN ; 8XY4 -> Vx += Vy (all wrapped to a byte)
63// returns the next pc. This is exactly the shape nx_chip8 (R0.3) scales to all 35 opcodes.
64func p_step(base: i64, pc: i64) -> i64 {
65 let mem: *u8 = (base + PO_MEM) as *u8
66 let reg: *i64 = (base + PO_REG) as *i64
67 let hi: i64 = mem[pc] as i64
68 let lo: i64 = mem[pc + 1] as i64
69 let op: i64 = (hi << 8) | lo
70 let top: i64 = (op >> 12) & 0xf
71 let x: i64 = hi & 0xf
72 let y: i64 = (lo >> 4) & 0xf
73 let nn: i64 = lo
74 if top == 6 { reg[x] = nn }
75 if top == 7 { reg[x] = (reg[x] + nn) & 0xff }
76 if top == 8 { reg[x] = (reg[x] + reg[y]) & 0xff }
77 return pc + 2
78}
79
80// ---- framebuffer (i64 RGBA at a computed address: the browser blits this to <canvas>) ----
81func p_fb_set(base: i64, idx: i64, rgba: i64) -> i64 {
82 let fb: *i64 = (base + PO_FB) as *i64
83 fb[idx] = rgba
84 return 0
85}
86func p_fb_get(base: i64, idx: i64) -> i64 {
87 let fb: *i64 = (base + PO_FB) as *i64
88 return fb[idx]
89}
90func p_fb_off() -> i64 { return PO_FB }
91func p_disp_w() -> i64 { return 64 }
92func p_disp_h() -> i64 { return 32 }
93
94// ---- a single deterministic "do everything" entry (the wasm/VM side calls this) ----
95// seeds memory, runs a 2-instruction program (6105 7103 -> V1=8), writes a framebuffer cell,
96// and returns a composite number the harness can check: sum + V1*1000000 + (fb[7] low 16 bits).
97func p_probe(base: i64) -> i64 {
98 p_seed(base, 256)
99 let s: i64 = p_sum(base, 256)
100 p_memset_byte(base, 0, 0x61)
101 p_memset_byte(base, 1, 0x05)
102 p_memset_byte(base, 2, 0x71)
103 p_memset_byte(base, 3, 0x03)
104 var pc: i64 = 0
105 pc = p_step(base, pc)
106 pc = p_step(base, pc)
107 let v1: i64 = p_reg_get(base, 1)
108 p_fb_set(base, 7, 0xdeadbeef)
109 return s + v1 * PO_MAGIC_1000000 + (p_fb_get(base, 7) & 0xffff)
110}