code wiki / _hdl_build / rv64im_min_sim_smoke.nx
rv64im_min_sim_smoke.nx source
↩ module page · 221 lines · 9640 B
1// rv64im_min_sim_smoke.nx -- end-to-end smoke for the RV64IM-min sim.
2//
3// Hand-codes a tiny RV64IM program that writes "OK\n" via the UART
4// MMIO + poweroffs via the SiFive finisher. Runs it on the
5// simulator (which ties decoder + ALU + regfile + CSR + CLINT + UART
6// together) and asserts the captured UART byte stream matches
7// "OK\n".
8//
9// This is the first integration test that exercises every module
10// shipped in nishi-silicon. If this passes, the path from
11// "NishiHDL DSL declared" to "RV64IM program executes" is proven --
12// the remaining steps to FPGA bring-up are: ELF loader, kernel
13// boot, then nishi-synth + Yosys.
14//
15// Hand-coded program (RV64IM, position-independent, starts at PC):
16// lui a0, 0x10000 ; a0 = 0x10000000 (UART base)
17// addi a1, zero, 'O' ; a1 = 0x4F
18// sb a1, 0(a0) ; UART THR <- 'O'
19// addi a1, zero, 'K' ; a1 = 0x4B
20// sb a1, 0(a0) ; UART THR <- 'K'
21// addi a1, zero, '\n' ; a1 = 0x0A
22// sb a1, 0(a0) ; UART THR <- '\n'
23// lui a0, 0x100 ; a0 = 0x100000 (finisher base)
24// lui a1, 0x5 ; a1 = 0x5000
25// addi a1, a1, 0x555 ; a1 = 0x5555
26// sw a1, 0(a0) ; finisher <- 0x5555 -> sim halts
27//
28// Encoded as 32-bit little-endian instruction words below.
29
30import "nx_syscalls.nx"
31import "nishi_hdl_primitives.nx"
32import "rv64im_min_decoder.nx"
33import "rv64im_min_alu.nx"
34import "rv64im_min_regfile.nx"
35import "rv64im_min_csr.nx"
36import "rv64im_min_clint.nx"
37import "rv64im_min_uart.nx"
38import "rv64im_min_sim.nx"
39import "nx_gate_verdict.nx"
40
41// Decimal writer for the evidence row. Emits the SIGN: a negative here would mean a corrupted or
42// unread counter, and a printer that silently drops the minus makes that indistinguishable from a
43// small positive value (measured elsewhere today).
44func smoke_wnum(fd: i64, v: i64) -> i64 {
45 let bb: *u8 = sys_mmap(32)
46 var m: i64 = v
47 var neg: i64 = 0
48 if m < 0 { m = 0 - m; neg = 1 }
49 let t: *u8 = sys_mmap(32)
50 var k: i64 = 0
51 if m == 0 { t[0] = 48 as u8; k = 1 }
52 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
53 var o: i64 = 0
54 if neg == 1 { bb[0] = 45 as u8; o = 1 }
55 var i: i64 = 0
56 while i < k { bb[o + i] = t[k - 1 - i]; i = i + 1 }
57 sys_write(fd, bb, k + o)
58 return 0
59}
60
61const SMOKE_MEM_BASE: i64 = 0x80000000
62const SMOKE_MEM_SIZE: i64 = 4096
63const SMOKE_TX_CAP: i64 = 256
64
65// Helper: write a 32-bit value little-endian to a byte buffer.
66func smoke_write32(buf: *u8, off: i64, value: i64) -> i64 {
67 buf[off] = (value & 0xff) as u8
68 buf[off + 1] = ((value >> 8) & 0xff) as u8
69 buf[off + 2] = ((value >> 16) & 0xff) as u8
70 buf[off + 3] = ((value >> 24) & 0xff) as u8
71 return 0
72}
73
74// Hand-encoded RV64IM "write OK\n then poweroff" program.
75// 11 instructions x 4 bytes = 44 bytes of .text.
76func smoke_load_program(mem: *u8) -> i64 {
77 // 00: lui a0, 0x10000 (a0 = 0x10000000)
78 // opcode=0x37 rd=10 imm=0x10000
79 // imm[31:12]=0x10000 -> shifted into [31:12]
80 // encoding: imm[31:12] << 12 | rd << 7 | 0x37
81 // = 0x10000000 | 0x00000500 | 0x37 = 0x10000537
82 smoke_write32(mem, 0, 0x10000537)
83
84 // 04: addi a1, zero, 0x4F ('O')
85 // opcode=0x13 rd=11 funct3=0 rs1=0 imm=0x04F
86 // encoding: imm[11:0] << 20 | rs1 << 15 | funct3 << 12 | rd << 7 | 0x13
87 // = (0x04F << 20) | (0 << 15) | (0 << 12) | (11 << 7) | 0x13
88 // = 0x04F00000 | 0x00000580 | 0x13 = 0x04F00593
89 smoke_write32(mem, 4, 0x04f00593)
90
91 // 08: sb a1, 0(a0)
92 // opcode=0x23 funct3=0 rs1=10 rs2=11 imm=0
93 // imm[11:5]=0 imm[4:0]=0
94 // encoding: imm[11:5] << 25 | rs2 << 20 | rs1 << 15 | funct3 << 12 | imm[4:0] << 7 | 0x23
95 // = 0 | (11 << 20) | (10 << 15) | 0 | 0 | 0x23
96 // = 0x00B00000 | 0x00050000 | 0x23 = 0x00B50023
97 smoke_write32(mem, 8, 0x00b50023)
98
99 // 0C: addi a1, zero, 0x4B ('K')
100 // = (0x04B << 20) | (11 << 7) | 0x13 = 0x04B00593
101 smoke_write32(mem, 12, 0x04b00593)
102
103 // 10: sb a1, 0(a0)
104 smoke_write32(mem, 16, 0x00b50023)
105
106 // 14: addi a1, zero, 0x0A ('\n')
107 // = (0x00A << 20) | (11 << 7) | 0x13 = 0x00A00593
108 smoke_write32(mem, 20, 0x00a00593)
109
110 // 18: sb a1, 0(a0)
111 smoke_write32(mem, 24, 0x00b50023)
112
113 // 1C: lui a0, 0x100 (a0 = 0x100000, finisher base)
114 // = (0x100 << 12) | (10 << 7) | 0x37 = 0x00100537
115 smoke_write32(mem, 28, 0x00100537)
116
117 // 20: lui a1, 0x5 (a1 = 0x5000)
118 // = (0x5 << 12) | (11 << 7) | 0x37 = 0x000055B7
119 smoke_write32(mem, 32, 0x000055b7)
120
121 // 24: addi a1, a1, 0x555 (a1 = 0x5555)
122 // = (0x555 << 20) | (11 << 15) | 0 | (11 << 7) | 0x13
123 // = 0x55500000 | 0x00058000 | 0x00000580 | 0x13 = 0x55558593
124 smoke_write32(mem, 36, 0x55558593)
125
126 // 28: sw a1, 0(a0) (finisher write -> sim halts)
127 // opcode=0x23 funct3=2 rs1=10 rs2=11 imm=0
128 // = 0 | (11 << 20) | (10 << 15) | (2 << 12) | 0 | 0x23
129 // = 0x00B00000 | 0x00050000 | 0x00002000 | 0x23 = 0x00B52023
130 smoke_write32(mem, 40, 0x00b52023)
131
132 return 0
133}
134
135func main() -> i64 {
136 // ----- allocate device backings -----
137 let rf_storage: *i64 = (sys_mmap(8 * NX_RV64IM_RF_N_REGS)) as *i64
138 let csr_storage: *i64 = (sys_mmap(8 * NX_CSR_SLOT_N)) as *i64
139 let clint_storage: *i64 = (sys_mmap(8 * NX_CLINT_SLOT_N)) as *i64
140 let uart_storage: *i64 = (sys_mmap(8 * NX_UART_SLOT_N)) as *i64
141 let mem: *u8 = sys_mmap(SMOKE_MEM_SIZE)
142 let tx_buf: *u8 = sys_mmap(SMOKE_TX_CAP)
143
144 let rf: *NxRv64imRegfile = (sys_mmap(64)) as *NxRv64imRegfile
145 let csr: *NxRv64imCsrFile = (sys_mmap(64)) as *NxRv64imCsrFile
146 let clint: *NxClint = (sys_mmap(64)) as *NxClint
147 let uart: *NxUart = (sys_mmap(64)) as *NxUart
148 let sim: *NxRv64imSim = (sys_mmap(NX_RV64IM_SIM_BYTES)) as *NxRv64imSim
149
150 nx_rv64im_rf_init(rf, rf_storage)
151 nx_rv64im_csr_init(csr, csr_storage, 0)
152 nx_clint_init(clint, clint_storage)
153 nx_uart_init(uart, uart_storage, tx_buf, SMOKE_TX_CAP)
154 nx_rv64im_sim_init(sim, rf, csr, clint, uart,
155 SMOKE_MEM_BASE, mem, SMOKE_MEM_SIZE, 0)
156
157 smoke_load_program(mem)
158
159 // ----- run the program -----
160 let max_steps: i64 = 100
161 nx_rv64im_sim_run(sim, max_steps)
162
163 // ----- assert -----
164 //
165 // ★MADE OBSERVABLE 2026-08-07. These assertions were already correct and already PASSING, but
166 // this organ printed NOTHING and reported only a bare exit code -- and it had NEVER BEEN BUILT,
167 // so nothing had ever run them. That matters more than it looks: `sim.steps != 11` below is a
168 // direct check on NxRv64imSim.steps, which is EXACTLY the field that the hardcoded sys_mmap(128)
169 // allocation was writing out of bounds (the struct is 26 fields = 208B). A test that would have
170 // caught the defect existed, compiled, and passed -- and no ruler could read it.
171 // A SILENT PASS IS NOT EVIDENCE. A TEST NOBODY BUILDS IS A COMMENT.
172 //
173 // Teeth are now named and counted so nx_gate_green can judge this organ and a rollup can read it.
174 // The exit code is UNCHANGED for any existing caller: gv_verdict returns 0 iff pass==total.
175 let halted_ok: i64 = sim.halted
176 var txn_ok: i64 = 0
177 if nx_uart_tx_count(uart) == 3 { txn_ok = 1 }
178 var bytes_ok: i64 = 0
179 if (tx_buf[0] & 0xff) == 0x4f { if (tx_buf[1] & 0xff) == 0x4b { if (tx_buf[2] & 0xff) == 0x0a { bytes_ok = 1 } } }
180 var steps_ok: i64 = 0
181 if sim.steps == 11 { steps_ok = 1 }
182
183 let ctr: *i64 = gv_ctr()
184 gv_check("T1 the program HALTED cleanly on the sovereign rv64 sim" as *u8, halted_ok, ctr)
185 gv_check("T2 the UART captured EXACTLY 3 bytes (not 0, not a partial drain)" as *u8, txn_ok, ctr)
186 gv_check("T3 those bytes are 'O','K','\\n' -- the program's real output, byte-exact" as *u8, bytes_ok, ctr)
187 gv_check("T4 sim.steps == 11 -- the instruction count is EXACT (this is the field the 128B under-allocation corrupted)" as *u8, steps_ok, ctr)
188 let rc: i64 = gv_verdict("SIMSMOKEGATE" as *u8, ctr, "sovereign RV64IM sim smoke: an 11-instruction hand-encoded program runs on the Nishi CPU model, drives the 16550 UART with 'OK\\n', and halts cleanly through the SiFive finisher. T4 pins the exact step count, so any corruption of NxRv64imSim.steps -- the class of defect a hardcoded struct allocation causes -- fails this gate. probe=rv64im-sim-smoke" as *u8)
189
190 let lfd: i64 = sys_openat_append("knowledge/status/sim_smoke.log" as *u8, 0x1a4)
191 if lfd >= 0 {
192 var m: *u8 = "SIMSMOKEGATE verdict=RED" as *u8
193 if rc == 0 { m = "SIMSMOKEGATE verdict=GREEN" as *u8 }
194 var n: i64 = 0
195 while m[n] != (0 as u8) { n = n + 1 }
196 sys_write(lfd, m, n)
197 let t: *u8 = " probe=rv64im-sim-smoke halted=" as *u8
198 var tn: i64 = 0
199 while t[tn] != (0 as u8) { tn = tn + 1 }
200 sys_write(lfd, t, tn)
201 smoke_wnum(lfd, halted_ok)
202 let t2: *u8 = " uart_bytes=" as *u8
203 var t2n: i64 = 0
204 while t2[t2n] != (0 as u8) { t2n = t2n + 1 }
205 sys_write(lfd, t2, t2n)
206 smoke_wnum(lfd, nx_uart_tx_count(uart))
207 let t3: *u8 = " steps=" as *u8
208 var t3n: i64 = 0
209 while t3[t3n] != (0 as u8) { t3n = t3n + 1 }
210 sys_write(lfd, t3, t3n)
211 smoke_wnum(lfd, sim.steps)
212 let t4: *u8 = " expect_steps=11 epoch=" as *u8
213 var t4n: i64 = 0
214 while t4[t4n] != (0 as u8) { t4n = t4n + 1 }
215 sys_write(lfd, t4, t4n)
216 smoke_wnum(lfd, sys_now_realtime_sec())
217 sys_write(lfd, "\n" as *u8, 1)
218 sys_close(lfd)
219 }
220 return rc
221}