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}