code wiki / _hdl_build / rv64im_min_kernel_boot_smoke.nx

rv64im_min_kernel_boot_smoke.nx source

↩ module page · 120 lines · 5191 B

1// rv64im_min_kernel_boot_smoke.nx -- boot the nishi-os kernel ELF in sim. 2// 3// End-to-end test that closes the loop from 4// "shipped NishiHDL modules" -> "sim runs hand-coded RV64IM" 5// -> "ELF loader works" -> "ACTUAL KERNEL BOOTS in our pure-NishiLang 6// substrate, no qemu, no external interpreter" 7// 8// What this proves: 9// The 11 NishiHDL files under nishi-silicon/hdl/ collectively 10// implement enough of RV64IM-min that the shipped nishi-os 11// kernel ELF executes correctly: at minimum, the kernel's 12// selftest path prints the first [m0-mcs-live] UART marker 13// to our captured tx_buf within the step budget. 14// 15// What this DOES NOT prove (still needed for Tier A FPGA bring-up): 16// - nishi-synth gate-netlist emit (HDL graph -> Verilog/gates) 17// - Yosys + nextpnr place-and-route (gates -> ECP5 bitstream) 18// - FPGA board execution (bitstream -> ULX3S -> physical UART) 19// 20// Status: SEED. 2026-05-26. Initial harness. Step budget is 21// generous (10M) since the kernel runs many cycles before reaching 22// the first observable marker. 23 24import "nx_syscalls.nx" 25import "nishi_hdl_primitives.nx" 26import "rv64im_min_decoder.nx" 27import "rv64im_min_alu.nx" 28import "rv64im_min_regfile.nx" 29import "rv64im_min_csr.nx" 30import "rv64im_min_clint.nx" 31import "rv64im_min_uart.nx" 32import "rv64im_min_sim.nx" 33import "rv64im_min_elf_loader.nx" 34 35const BOOT_MEM_BASE: i64 = 0x80000000 36const BOOT_MEM_SIZE: i64 = 0x40000 // 256 KiB (kernel + stack + frames) 37const BOOT_TX_CAP: i64 = 4096 // capture up to 4 KiB of UART output 38const BOOT_MAX_STEPS: i64 = 10000000 // 10M cycle budget 39 40const BOOT_KERNEL_PATH: *u8 = "/mnt/c/Users/elder/nishi-os/kernel/nishi-kernel.elf" 41 42// Substring search: returns 1 if needle appears in haystack[0..hay_len]. 43// Naive O(n*m) -- fine for our 4 KiB haystack + 15-char needle. 44func boot_contains(hay: *u8, hay_len: i64, needle: *u8, needle_len: i64) -> i64 { 45 if needle_len == 0 { return 1 } 46 if needle_len > hay_len { return 0 } 47 var i: i64 = 0 48 while i <= hay_len - needle_len { 49 var j: i64 = 0 50 var matched: i64 = 1 51 while j < needle_len { 52 if (hay[i + j] & 0xff) != (needle[j] & 0xff) { matched = 0; j = needle_len } 53 j = j + 1 54 } 55 if matched == 1 { return 1 } 56 i = i + 1 57 } 58 return 0 59} 60 61func main() -> i64 { 62 // ----- read the kernel ELF ----- 63 let elf_len_out: *i64 = (sys_mmap(8)) as *i64 64 elf_len_out[0] = 0 65 let elf_bytes: *u8 = sys_read_file(BOOT_KERNEL_PATH, elf_len_out) 66 let elf_size: i64 = elf_len_out[0] 67 if (elf_bytes as i64) == 0 { return 1 } // file read failed 68 if elf_size < 64 { return 2 } // smaller than ELF64 header 69 70 // ----- allocate the simulated machine ----- 71 let rf_storage: *i64 = (sys_mmap(8 * NX_RV64IM_RF_N_REGS)) as *i64 72 let csr_storage: *i64 = (sys_mmap(8 * NX_CSR_SLOT_N)) as *i64 73 let clint_storage: *i64 = (sys_mmap(8 * NX_CLINT_SLOT_N)) as *i64 74 let uart_storage: *i64 = (sys_mmap(8 * NX_UART_SLOT_N)) as *i64 75 let mem: *u8 = sys_mmap(BOOT_MEM_SIZE) 76 let tx_buf: *u8 = sys_mmap(BOOT_TX_CAP) 77 78 let rf: *NxRv64imRegfile = (sys_mmap(64)) as *NxRv64imRegfile 79 let csr: *NxRv64imCsrFile = (sys_mmap(64)) as *NxRv64imCsrFile 80 let clint: *NxClint = (sys_mmap(64)) as *NxClint 81 let uart: *NxUart = (sys_mmap(64)) as *NxUart 82 let sim: *NxRv64imSim = (sys_mmap(128)) as *NxRv64imSim 83 84 nx_rv64im_rf_init(rf, rf_storage) 85 nx_rv64im_csr_init(csr, csr_storage, 0) 86 nx_clint_init(clint, clint_storage) 87 nx_uart_init(uart, uart_storage, tx_buf, BOOT_TX_CAP) 88 nx_rv64im_sim_init(sim, rf, csr, clint, uart, 89 BOOT_MEM_BASE, mem, BOOT_MEM_SIZE, 0) 90 91 // ----- load the kernel ELF into sim memory ----- 92 let elf_out: *NxElfLoad = (sys_mmap(64)) as *NxElfLoad 93 let rc: i64 = nx_elf_load(elf_bytes, elf_size, sim, elf_out) 94 if rc != NX_ELF_OK { 95 // Surface the negated verdict code via exit -- helps diagnostics. 96 return 10 + (0 - rc) 97 } 98 if elf_out.entry != BOOT_MEM_BASE { return 3 } // expect kernel entry = 0x80000000 99 if elf_out.n_pt_load < 1 { return 4 } // must have >= 1 PT_LOAD 100 101 // ----- run ----- 102 nx_rv64im_sim_run(sim, BOOT_MAX_STEPS) 103 104 // ----- assertions ----- 105 // 106 // We don't require sim.halted == 1 because the kernel may still 107 // be in its bench loop after the first marker prints; the 108 // selftest output is what we care about. 109 let tx_n: i64 = nx_uart_tx_count(uart) 110 if tx_n == 0 { return 5 } // no UART output at all 111 112 // The kernel's first observable marker is [m0-mcs-live] from 113 // sched.nx, which fires after MCS scheduler init. If it 114 // appears, we know decoder + ALU + regfile + CSR + UART all 115 // executed correctly through a substantial chunk of kernel boot. 116 let marker_mcs: *u8 = "[m0-mcs-live]" 117 if boot_contains(tx_buf, tx_n, marker_mcs, 13) != 1 { return 6 } 118 119 return 0 // GREEN -- kernel booted far enough to print [m0-mcs-live] 120}