code wiki / _hdl_build / rv64im_min_sim.nx

rv64im_min_sim.nx source

↩ module page · 1276 lines · 61942 B

1// rv64im_min_sim.nx -- single-cycle RV64IM-min behavioural simulator. 2// 3// First runnable thing in nishi-silicon. Ties the previously-shipped 4// modules together so RV64IM programs execute end-to-end in pure 5// NishiLang: 6// 7// decoder (rv64im_min_decoder) -> opcode kind + operand fields 8// ALU (rv64im_min_alu) -> arithmetic compute 9// regfile (rv64im_min_regfile) -> 32 GPRs storage 10// CSR (rv64im_min_csr) -> M-mode CSRs 11// CLINT (rv64im_min_clint) -> MTIP interrupt source 12// UART (rv64im_min_uart) -> debug console 13// 14// Why "single-cycle" not "pipelined": a single-cycle model is the 15// canonical "first simulator" -- one instruction per simulator step, 16// no pipeline hazards, no fetch/decode/execute overlap. Verifies 17// the combinational + sequential modules without the complexity of 18// IF/ID/EX latches or hazard detection. Pipelined model lands in a 19// follow-on commit; Tier A FPGA bitstream gets the pipelined version 20// (no point fabbing a single-cycle core when the pipelined one is 21// also single-issue in-order and only marginally larger). 22// 23// What this enables: 24// - Run RV64IM .text segments emitted by the SOVEREIGN toolchain (nx_cc 25// -> nxasm rv64 backend); gcc/llvm are a diff-lane reference ONLY, never on 26// the chain (this sim IS the genesis ISA spec + the sovereign-cycle yardstick) 27// - Validate the decoder + ALU against any RV64IM disassembler 28// - Boot the nishi-os kernel ELF (after the load/store unit + memory 29// bus land in a follow-on commit) before fabbing FPGA bitstream 30// 31// Status: SEED. 2026-05-26. Single-cycle non-interrupt loop. 32// Interrupt handling, load/store, mret/ecall flow land per the TODO 33// markers below. 34 35import "nx_syscalls.nx" 36import "nishi_hdl_primitives.nx" 37import "rv64im_min_decoder.nx" 38import "rv64im_min_alu.nx" 39import "rv64im_min_regfile.nx" 40import "rv64im_min_csr.nx" 41import "rv64im_min_clint.nx" 42import "rv64im_min_uart.nx" 43import "rv64im_min_virtio.nx" 44import "rv64im_min_nvme.nx" 45import "rv64im_min_nndev.nx" 46import "rv64im_min_mmu.nx" 47import "rv64im_min_gpu.nx" 48import "nx_rvc_expand.nx" // RV64 C (compressed) extension: expand 16-bit -> 32-bit at fetch 49 50// ===== Sim state ================================================= 51// 52// The simulator owns instances of every device and a PC register. 53// Memory is a flat byte buffer with the CLINT + UART carve-outs 54// dispatched by the load/store path. 55 56struct NxRv64imSim { 57 pc: i64 58 rf: *NxRv64imRegfile 59 csr: *NxRv64imCsrFile 60 clint: *NxClint 61 uart: *NxUart 62 virtio: *NxVirtioMmio // legacy virtio-MMIO transport, blk @0x10001000 (K-R2-001a) 63 virtio_net:*NxVirtioMmio // second legacy virtio-MMIO transport, net @0x10002000 (K-R2-001c1) 64 nvme: *NxNvmeCtrl // NVMe-class controller, admin SQ/CQ @0x10003000 (X-DRV-W1 stage B) 65 nndev: *NxNnDev // Nishi-native device protocol (NNDP) @0x10005000 (sovereign-device-protocol exceed) 66 mmu: *NxMmu // Sv39 page-table-walk device @0x10006000 (virtual-memory-paging-mmu) 67 xlate_fault: i64 // set by nx_rv64im_xlate on a data-access page fault; step loop traps on it 68 gpu: *NxGpu // GPU-class command-submission controller @0x10007000 (driver-from-spec D) 69 mem_base: i64 // physical address of mem_buf[0] (e.g., 0x80000000) 70 mem_buf: *u8 // byte-addressable RAM 71 mem_size: i64 // bytes 72 valid: i64 73 halted: i64 74 halt_code: i64 // exit code on halt 75 steps: i64 // instructions executed 76 priv: i64 // current privilege: 3=M, 1=S, 0=U (reset M). Sv39 translation 77 // applies only in S/U; M-mode is always Bare (riscv-priv-spec). 78 xlate_store: i64 // 1 if the in-flight data access is a store (page-fault cause select) 79 // ----- Per-PC cycle attribution (M2 silicon-feedback foundation) ----- 80 // pc_cycles[i] = total cycles attributable to PC = mem_base + (i << 2). 81 // Indexed by word (4-byte stride) since RV64IM instructions are 32-bit. 82 // Caller allocates pc_cycles_buf with at least (mem_size >> 2) i64s. 83 // Without these the substrate has no per-instruction perf data; with 84 // them the silicon-feedback loop (M3) can pick out top-N hot PCs. 85 pc_cycles_buf: *i64 86 pc_cycles_cap: i64 // capacity in i64 slots 87 pc_cycles_on: i64 // 1 = attribution active; 0 = skip (cheaper hot loop) 88 // ----- A-extension (atomics) LR/SC reservation (F107h, 2026-07-18) ----- 89 // Single-address reservation set: lr.w/d records (resv_addr, resv_valid); sc.w/d succeeds 90 // iff still valid AND matches; any sc or amo* clears it. Single-hart model -> an lr/sc pair 91 // with no intervening reservation-breaker succeeds (matches QEMU on the canonical CAS loop); 92 // multi-hart contention is the F107g follow-on. 93 resv_addr: i64 94 resv_valid: i64 95} 96 97// Allocation size for ONE NxRv64imSim, in bytes. EVERY site that mmaps a sim MUST use this -- 98// never a literal. 99// 100// ★ROOT FIX 2026-08-07. Five sites hardcoded `sys_mmap(128)` for this struct. 128 bytes is 16 101// i64 slots; the struct has been grown to 26 fields (208 bytes) by the virtio-net, NVMe, NNDP, 102// Sv39-MMU, GPU, privilege, per-PC-cycle and A-extension work. So `halted` (offset 128), 103// `halt_code` (136) and `steps` (144) -- THE THREE FIELDS EVERY VERDICT IS COMPUTED FROM -- were 104// written and read OUT OF BOUNDS. 105// 106// It stayed invisible for months because sys_mmap served each small request from its own 4 KiB 107// page, so the 80-byte overrun landed in page slack and read back correctly BY LUCK. When 108// NXA_SMALL_MAX went 64 -> 256 (2026-08-07) a 128-byte request began coming from the shared small 109// arena, the overrun started landing on a live neighbour, and nx_boot_run_sov reported 110// steps=1953655343 -- which is 0x74726976, the ASCII "virt" of the virtio magic the driver under 111// test had just verified. The arena's own ring detector named it: ARENA-OVERRUN prev_alloc_size=128. 112// 113// AN OUT-OF-BOUNDS WRITE ABSORBED BY PAGE SLACK IS A BUG YOUR ALLOCATOR PAYS 4KB APIECE TO HIDE. 114// 115// 256 is chosen deliberately, not rounded up thoughtlessly: it covers 208 with room for 6 more 116// fields, AND it is <= NXA_SMALL_MAX, so this allocation KEEPS coming from the instrumented small 117// arena. Sizing it above NXA_SMALL_MAX would hand it a private VMA and restore exactly the page 118// slack that hid this bug for months. Growing the struct past 32 fields must raise this constant; 119// _simsize_gate.nx enforces that mechanically so it cannot drift again. 120const NX_RV64IM_SIM_BYTES: i64 = 256 121 122// ===== Sim init ================================================= 123// 124// Caller provides allocated regfile/csr/clint/uart + memory buffer. 125// Sim init initialises PC to mem_base + entry_offset (typically 0 126// for kernel ELFs which start at the base of mem). 127 128func nx_rv64im_sim_init(s: *NxRv64imSim, 129 rf: *NxRv64imRegfile, 130 csr: *NxRv64imCsrFile, 131 clint: *NxClint, 132 uart: *NxUart, 133 mem_base: i64, 134 mem_buf: *u8, 135 mem_size: i64, 136 entry_offset: i64) -> i64 { 137 if (s as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 138 if (mem_buf as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 139 if mem_size <= 0 { return 0 - NX_HDL_BAD_KIND } 140 s.pc = mem_base + entry_offset 141 s.rf = rf 142 s.csr = csr 143 s.clint = clint 144 s.uart = uart 145 // virtio attaches separately via nx_rv64im_sim_attach_virtio (keeps the init 146 // signature stable -- API contract stability). Null until the runner wires it. 147 s.virtio = (0 as i64) as *NxVirtioMmio 148 s.virtio_net = (0 as i64) as *NxVirtioMmio 149 s.nvme = (0 as i64) as *NxNvmeCtrl 150 s.nndev = (0 as i64) as *NxNnDev 151 s.mmu = (0 as i64) as *NxMmu 152 s.gpu = (0 as i64) as *NxGpu 153 s.xlate_fault = 0 154 s.priv = 3 // reset to M-mode 155 s.xlate_store = 0 156 s.mem_base = mem_base 157 s.mem_buf = mem_buf 158 s.mem_size = mem_size 159 s.valid = 1 160 s.halted = 0 161 s.halt_code = 0 162 s.steps = 0 163 // Cycle attribution opt-in: caller wires nx_rv64im_sim_enable_pc_cycles 164 // after init. Default off so the hot step loop stays branch-free for 165 // bench runs that don't need profiling. 166 s.pc_cycles_buf = (0 as i64) as *i64 167 s.pc_cycles_cap = 0 168 s.pc_cycles_on = 0 169 // A-extension reservation starts clear (no lr outstanding). 170 s.resv_addr = 0 171 s.resv_valid = 0 172 return NX_HDL_OK 173} 174 175// ===== virtio-MMIO attach (K-R2-001a) ================================================= 176// 177// Wires a (already-init'd) virtio-MMIO transport into the sim so load32/store32 route 178// the 0x10001000..NX_VIRTIO_END window to it. Kept separate from sim_init so the init 179// signature stays stable. Null virtio = no device (load32/store32 fall through to 0). 180func nx_rv64im_sim_attach_virtio(s: *NxRv64imSim, virtio: *NxVirtioMmio) -> i64 { 181 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND } 182 s.virtio = virtio 183 return NX_HDL_OK 184} 185 186// ===== second virtio-MMIO attach: virtio-net (K-R2-001c1) ================================= 187// Wires the net device (DeviceID=1) so load32/store32 route the 0x10002000..NX_VIRTIO_NET_END 188// window to it. Independent of the blk device -- the blk window (0x10001000) is untouched. 189func nx_rv64im_sim_attach_virtio_net(s: *NxRv64imSim, virtio_net: *NxVirtioMmio) -> i64 { 190 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND } 191 s.virtio_net = virtio_net 192 return NX_HDL_OK 193} 194 195// ===== NVMe-class controller attach (X-DRV-W1 stage B) ================================= 196// Wires the NVMe controller (admin SQ/CQ @0x10003000) so load32/store32 route the 197// 0x10003000..NX_NVME_END window to it. Independent of the virtio devices -- the blk + net 198// windows are untouched (additive). Null nvme = no device (the window falls through to 0). 199func nx_rv64im_sim_attach_nvme(s: *NxRv64imSim, nvme: *NxNvmeCtrl) -> i64 { 200 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND } 201 s.nvme = nvme 202 return NX_HDL_OK 203} 204 205// ===== Nishi-native device protocol attach (sovereign-device-protocol exceed) ================= 206// Wires the NNDP device (NND1 @0x10004000) so load32/store32 route the 0x10004000..NX_NNDEV_END 207// window to it. Independent of virtio + nvme (their windows are untouched -- additive). Null 208// nndev = no device (the window falls through to 0). 209func nx_rv64im_sim_attach_nndev(s: *NxRv64imSim, nndev: *NxNnDev) -> i64 { 210 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND } 211 s.nndev = nndev 212 return NX_HDL_OK 213} 214 215// ===== GPU-class controller attach (driver-from-spec instance D, GPU trajectory) ================= 216// Wires the GPU controller (command ring @0x10007000) so load32/store32 route the 0x10007000.. 217// NX_GPU_END window to it. Independent of the other devices -- their windows are untouched (additive). 218func nx_rv64im_sim_attach_gpu(s: *NxRv64imSim, gpu: *NxGpu) -> i64 { 219 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND } 220 s.gpu = gpu 221 return NX_HDL_OK 222} 223 224// ===== Sv39 MMU walk device attach (virtual-memory-paging-mmu) ================================= 225// Wires the Sv39 page-table-walk device (@0x10006000) so load32/store32 route the 0x10006000.. 226// NX_MMU_END window to it. Independent of all other devices (additive). Null mmu = no device. 227func nx_rv64im_sim_attach_mmu(s: *NxRv64imSim, mmu: *NxMmu) -> i64 { 228 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND } 229 s.mmu = mmu 230 return NX_HDL_OK 231} 232 233// ===== Cycle attribution opt-in ================================================= 234// 235// Hand in a pre-allocated buffer of (mem_size >> 2) i64s (one slot per 236// 4-byte instruction word). Once enabled, every step() bumps the slot 237// for the executing PC. Disabled paths skip the bump in 1 branch. 238 239func nx_rv64im_sim_enable_pc_cycles(s: *NxRv64imSim, 240 buf: *i64, cap: i64) -> i64 { 241 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND } 242 if (buf as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 243 let need: i64 = s.mem_size >> 2 244 if cap < need { return 0 - NX_HDL_GRAPH_FULL } 245 s.pc_cycles_buf = buf 246 s.pc_cycles_cap = cap 247 s.pc_cycles_on = 1 248 var i: i64 = 0 249 while i < cap { buf[i] = 0; i = i + 1 } 250 return NX_HDL_OK 251} 252 253// Read cycles attributable to a specific PC (returns 0 if out of range 254// or attribution disabled). 255func nx_rv64im_sim_pc_cycles_at(s: *NxRv64imSim, pc: i64) -> i64 { 256 if s.pc_cycles_on != 1 { return 0 } 257 if pc < s.mem_base { return 0 } 258 let off: i64 = (pc - s.mem_base) >> 2 259 if off < 0 { return 0 } 260 if off >= s.pc_cycles_cap { return 0 } 261 return s.pc_cycles_buf[off] 262} 263 264// Pick the top-N hottest PCs. out_pcs[i] / out_cycles[i] filled with 265// the i-th hottest (descending). Returns count actually filled (<= n). 266// V1 is O(cap * n) -- fine for n <= ~50; better algorithm when N gets 267// large. 268func nx_rv64im_sim_pc_cycles_topn(s: *NxRv64imSim, n: i64, 269 out_pcs: *i64, out_cycles: *i64) -> i64 { 270 if s.pc_cycles_on != 1 { return 0 } 271 if (out_pcs as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 272 if (out_cycles as i64) == 0 { return 0 - NX_HDL_BAD_KIND } 273 var filled: i64 = 0 274 while filled < n { 275 var best_off: i64 = 0 - 1 276 var best_val: i64 = 0 277 var i: i64 = 0 278 while i < s.pc_cycles_cap { 279 let v: i64 = s.pc_cycles_buf[i] 280 // Skip slots already picked (encoded by negating after pick). 281 if v > best_val { 282 best_val = v 283 best_off = i 284 } 285 i = i + 1 286 } 287 if best_off < 0 { return filled } 288 if best_val == 0 { return filled } 289 out_pcs[filled] = s.mem_base + (best_off << 2) 290 out_cycles[filled] = best_val 291 // Mark picked by zeroing the slot. Destructive but acceptable 292 // for a one-shot top-N read; caller can re-enable + re-run if 293 // they need fresh data. 294 s.pc_cycles_buf[best_off] = 0 295 filled = filled + 1 296 } 297 return filled 298} 299 300// ===== Memory access (load/store) ================================================= 301// 302// Routes physical addresses to the right device: 303// - RAM (mem_base..mem_base+mem_size) -> mem_buf 304// - CLINT (0x02000000..0x0200C000) -> clint MMIO 305// - UART (0x10000000..0x10000008) -> uart MMIO 306// - Anything else -> halt (illegal access) 307 308func nx_rv64im_sim_load8(s: *NxRv64imSim, addr: i64) -> i64 { 309 if addr >= s.mem_base { 310 let off: i64 = addr - s.mem_base 311 if off < s.mem_size { 312 return s.mem_buf[off] as i64 313 } 314 } 315 // UART path: LSR polling reads via lbu. Returns the byte in 316 // the low 8 bits; sign-extension done by the caller per funct3. 317 if addr >= NX_UART_BASE { if addr < NX_UART_END { 318 let out: *i64 = (sys_mmap(8)) as *i64 319 out[0] = 0 320 nx_uart_read8(s.uart, addr, out) 321 return out[0] & 0xff 322 }} 323 // CLINT byte reads not used by kernel; return 0. 324 return 0 325} 326 327func nx_rv64im_sim_load16(s: *NxRv64imSim, addr: i64) -> i64 { 328 if addr >= s.mem_base { 329 let off: i64 = addr - s.mem_base 330 if off < s.mem_size - 1 { 331 let b0: i64 = s.mem_buf[off] as i64 332 let b1: i64 = s.mem_buf[off + 1] as i64 333 return b0 | (b1 << 8) 334 } 335 } 336 return 0 337} 338 339func nx_rv64im_sim_load32(s: *NxRv64imSim, addr: i64) -> i64 { 340 if addr >= s.mem_base { 341 let off: i64 = addr - s.mem_base 342 if off < s.mem_size - 3 { 343 let b0: i64 = s.mem_buf[off] as i64 344 let b1: i64 = s.mem_buf[off + 1] as i64 345 let b2: i64 = s.mem_buf[off + 2] as i64 346 let b3: i64 = s.mem_buf[off + 3] as i64 347 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 348 } 349 } 350 // virtio-MMIO transport path (K-R2-001a): lw from the device register window 351 // returns the device-modelled 32-bit register value (identity / features / status). 352 if addr >= NX_VIRTIO_BASE { if addr < NX_VIRTIO_END { 353 if (s.virtio as i64) != 0 { 354 let vout: *i64 = (sys_mmap(8)) as *i64 355 vout[0] = 0 356 nx_virtio_read32(s.virtio, addr, vout) 357 return vout[0] & 0xffffffff 358 } 359 }} 360 // virtio-net device register window (K-R2-001c1): lw from the second transport block 361 // at 0x10002000 returns DeviceID=1 + the same identity/feature/status surface as blk. 362 if addr >= NX_VIRTIO_NET_BASE { if addr < NX_VIRTIO_NET_END { 363 if (s.virtio_net as i64) != 0 { 364 let nout: *i64 = (sys_mmap(8)) as *i64 365 nout[0] = 0 366 nx_virtio_read32(s.virtio_net, addr, nout) 367 return nout[0] & 0xffffffff 368 } 369 }} 370 // NVMe controller register window (X-DRV-W1 stage B): lw from 0x10003000.. returns CAP/VS, 371 // the latched CC/AQA/ASQ/ACQ regs, CSTS (RDY flop the driver polls), and the CQPEEK binding- 372 // proof instrument. An INDEPENDENT device instance -- the virtio windows are untouched. 373 if addr >= NX_NVME_BASE { if addr < NX_NVME_END { 374 if (s.nvme as i64) != 0 { 375 let mout: *i64 = (sys_mmap(8)) as *i64 376 mout[0] = 0 377 nx_nvme_read32(s.nvme, addr, mout) 378 return mout[0] & 0xffffffff 379 } 380 }} 381 // Nishi-native device window (sovereign-device-protocol): lw from 0x10004000.. returns MAGIC/ 382 // DEVCLASS identity, the latched ENABLE/CMD_ADDR, and the STATUS/RESULT completion the driver 383 // reads back after a doorbell. An INDEPENDENT instance -- virtio + nvme windows untouched. 384 if addr >= NX_NNDEV_BASE { if addr < NX_NNDEV_END { 385 if (s.nndev as i64) != 0 { 386 let dout: *i64 = (sys_mmap(8)) as *i64 387 dout[0] = 0 388 nx_nndev_read32(s.nndev, addr, dout) 389 return dout[0] & 0xffffffff 390 } 391 }} 392 // Sv39 MMU walk device window (virtual-memory-paging-mmu): lw from 0x10006000.. returns the 393 // translated PADDR + FAULT the device latched on the last doorbell walk. Independent instance. 394 if addr >= NX_MMU_BASE { if addr < NX_MMU_END { 395 if (s.mmu as i64) != 0 { 396 let uout: *i64 = (sys_mmap(8)) as *i64 397 uout[0] = 0 398 nx_mmu_read32(s.mmu, addr, uout) 399 return uout[0] & 0xffffffff 400 } 401 }} 402 // GPU-class controller register window (driver-from-spec D): lw from 0x10007000.. returns ID, 403 // CTRL/STATUS, RING base, the FENCE seqno + the RESULTPEEK binding-proof word. Independent instance. 404 if addr >= NX_GPU_BASE { if addr < NX_GPU_END { 405 if (s.gpu as i64) != 0 { 406 let gout: *i64 = (sys_mmap(8)) as *i64 407 gout[0] = 0 408 nx_gpu_read32(s.gpu, addr, gout) 409 return gout[0] & 0xffffffff 410 } 411 }} 412 return 0 413} 414 415// Sign-extend an 8/16/32-bit value to 64 bits. 416func nx_rv64im_sext8(v: i64) -> i64 { 417 let low: i64 = v & 0xff 418 if (low & 0x80) != 0 { return low | (0 - 256) } 419 return low 420} 421func nx_rv64im_sext16(v: i64) -> i64 { 422 let low: i64 = v & 0xffff 423 if (low & 0x8000) != 0 { return low | (0 - 65536) } 424 return low 425} 426 427func nx_rv64im_sim_load64(s: *NxRv64imSim, addr: i64) -> i64 { 428 if addr >= s.mem_base { 429 let off: i64 = addr - s.mem_base 430 if off < s.mem_size - 7 { 431 var v: i64 = 0 432 var i: i64 = 0 433 while i < 8 { 434 let b: i64 = s.mem_buf[off + i] as i64 435 v = v | (b << (i * 8)) 436 i = i + 1 437 } 438 return v 439 } 440 } 441 // CLINT MMIO path 442 if addr >= NX_CLINT_BASE { if addr < NX_CLINT_END { 443 let out: *i64 = (sys_mmap(8)) as *i64 444 out[0] = 0 445 nx_clint_read64(s.clint, addr, out) 446 return out[0] 447 }} 448 return 0 449} 450 451func nx_rv64im_sim_store64(s: *NxRv64imSim, addr: i64, value: i64) -> i64 { 452 if addr >= s.mem_base { 453 let off: i64 = addr - s.mem_base 454 if off < s.mem_size - 7 { 455 var i: i64 = 0 456 while i < 8 { 457 s.mem_buf[off + i] = ((value >> (i * 8)) & 0xff) as u8 458 i = i + 1 459 } 460 return NX_HDL_OK 461 } 462 } 463 if addr >= NX_CLINT_BASE { if addr < NX_CLINT_END { 464 return nx_clint_write64(s.clint, addr, value) 465 }} 466 // UART path (8-bit writes only at THR; 64-bit writes get low byte) 467 if addr >= NX_UART_BASE { if addr < NX_UART_END { 468 return nx_uart_write8(s.uart, addr, value & 0xff) 469 }} 470 return 0 - NX_HDL_BAD_KIND 471} 472 473func nx_rv64im_sim_store8(s: *NxRv64imSim, addr: i64, value: i64) -> i64 { 474 if addr >= s.mem_base { 475 let off: i64 = addr - s.mem_base 476 if off < s.mem_size { 477 s.mem_buf[off] = (value & 0xff) as u8 478 return NX_HDL_OK 479 } 480 } 481 if addr >= NX_UART_BASE { if addr < NX_UART_END { 482 return nx_uart_write8(s.uart, addr, value & 0xff) 483 }} 484 return 0 - NX_HDL_BAD_KIND 485} 486 487func nx_rv64im_sim_store16(s: *NxRv64imSim, addr: i64, value: i64) -> i64 { 488 if addr >= s.mem_base { 489 let off: i64 = addr - s.mem_base 490 if off < s.mem_size - 1 { 491 s.mem_buf[off] = (value & 0xff) as u8 492 s.mem_buf[off + 1] = ((value >> 8) & 0xff) as u8 493 return NX_HDL_OK 494 } 495 } 496 return 0 - NX_HDL_BAD_KIND 497} 498 499func nx_rv64im_sim_store32(s: *NxRv64imSim, addr: i64, value: i64) -> i64 { 500 if addr >= s.mem_base { 501 let off: i64 = addr - s.mem_base 502 if off < s.mem_size - 3 { 503 s.mem_buf[off] = (value & 0xff) as u8 504 s.mem_buf[off + 1] = ((value >> 8) & 0xff) as u8 505 s.mem_buf[off + 2] = ((value >> 16) & 0xff) as u8 506 s.mem_buf[off + 3] = ((value >> 24) & 0xff) as u8 507 return NX_HDL_OK 508 } 509 } 510 // virtio-MMIO transport path (K-R2-001a): sw into the device register window 511 // latches GuestFeatures / drives the Status handshake (ACK/DRIVER/FEATURES_OK/ 512 // DRIVER_OK) or resets the device (Status write of 0). On a QueueNotify kick 513 // (K-R2-001b2a) the device DMA-reads descriptor 0's first field from the QueuePFN- 514 // bound ring page in guest RAM (the sim owns mem_buf, so we hand it in) and latches 515 // it into QueueDescPeek -- proving the device walked the descriptor the driver laid. 516 if addr >= NX_VIRTIO_BASE { if addr < NX_VIRTIO_END { 517 if (s.virtio as i64) != 0 { 518 let wr: i64 = nx_virtio_write32(s.virtio, addr, value) 519 if addr == (NX_VIRTIO_BASE + NX_VIRTIO_OFF_QUEUENOTIFY) { 520 nx_virtio_notify_dma(s.virtio, s.mem_buf, s.mem_base, s.mem_size) 521 } 522 return wr 523 } 524 }} 525 // virtio-net device register window (K-R2-001c1/c2/c3): sw into the second transport block 526 // at 0x10002000 latches GuestFeatures / drives the Status handshake (c1) + the legacy 527 // virtqueue-config registers (c2). On a QueueNotify kick (K-R2-001c3) the net device DMA- 528 // walks the driver's tx ring EXACTLY like the blk branch above: nx_virtio_notify_dma is 529 // device-instance-generic (it walks v.storage[QPFN] -> desc-read + avail/used + the data 530 // round-trip), so handing it s.virtio_net makes the net FRAME word the driver laid in the 531 // tx descriptor's data buffer round-trip into QueueSectPeek with NO new DMA code. The blk 532 // device at 0x10001000 is untouched -- this is the net block's own QueueNotify trigger. 533 if addr >= NX_VIRTIO_NET_BASE { if addr < NX_VIRTIO_NET_END { 534 if (s.virtio_net as i64) != 0 { 535 let wrn: i64 = nx_virtio_write32(s.virtio_net, addr, value) 536 if addr == (NX_VIRTIO_NET_BASE + NX_VIRTIO_OFF_QUEUENOTIFY) { 537 nx_virtio_notify_dma(s.virtio_net, s.mem_buf, s.mem_base, s.mem_size) 538 } 539 return wrn 540 } 541 }} 542 // NVMe controller register window (X-DRV-W1 stage B): sw into 0x10003000.. latches CC (and 543 // sets/clears the CSTS.RDY flop on CC.EN), AQA, the ASQ/ACQ base-address registers, and the 544 // doorbells. On the SQ-Tail doorbell (off_sq0tdbl) the device runs the doorbell DMA: it 545 // fetches the 64-byte SQE from ASQ_base (reads opcode+CID), posts a phase-tagged 16-byte CQE 546 // into ACQ_base, and latches NvmeCqPeek = (CID<<16)|status|phase. The sim owns mem_buf, so 547 // it hands guest RAM in -- mirroring the virtio QueueNotify branch. The virtio devices are 548 // untouched -- this is the NVMe instance's own doorbell trigger. 549 if addr >= NX_NVME_BASE { if addr < NX_NVME_END { 550 if (s.nvme as i64) != 0 { 551 let wrm: i64 = nx_nvme_write32(s.nvme, addr, value) 552 if addr == (NX_NVME_BASE + NX_NVME_OFF_SQ0TDBL) { 553 nx_nvme_doorbell_dma(s.nvme, s.mem_buf, s.mem_base, s.mem_size) 554 } 555 return wrm 556 } 557 }} 558 // Nishi-native device window (sovereign-device-protocol): sw into 0x10004000.. latches ENABLE 559 // (single-step bring-up) / CMD_ADDR. On the DOORBELL write the device runs the inline command 560 // at CMD_ADDR (DMA-reads the descriptor + the data buffer in guest RAM, the sim owns mem_buf so 561 // we hand it in) and latches STATUS + RESULT inline -- ONE descriptor, ONE round-trip, NO rings. 562 // virtio + nvme devices are untouched -- this is the NNDP instance's own doorbell trigger. 563 if addr >= NX_NNDEV_BASE { if addr < NX_NNDEV_END { 564 if (s.nndev as i64) != 0 { 565 let wrd: i64 = nx_nndev_write32(s.nndev, addr, value) 566 if addr == (NX_NNDEV_BASE + NX_NNDEV_OFF_DOORBELL) { 567 nx_nndev_doorbell_dma(s.nndev, s.mem_buf, s.mem_base, s.mem_size) 568 } 569 return wrd 570 } 571 }} 572 // Sv39 MMU walk device window (virtual-memory-paging-mmu): sw into 0x10006000.. latches satp / 573 // vaddr. On the DOORBELL write the device WALKS the 3-level Sv39 page table in guest RAM (the 574 // sim owns mem_buf, so we hand it in) and latches PADDR + FAULT. Other devices untouched. 575 if addr >= NX_MMU_BASE { if addr < NX_MMU_END { 576 if (s.mmu as i64) != 0 { 577 let wru: i64 = nx_mmu_write32(s.mmu, addr, value) 578 if addr == (NX_MMU_BASE + NX_MMU_OFF_DOORBELL) { 579 nx_mmu_doorbell_walk(s.mmu, s.mem_buf, s.mem_base, s.mem_size) 580 } 581 return wru 582 } 583 }} 584 // GPU-class controller window (driver-from-spec D): sw into 0x10007000.. latches CTRL (sets/clears 585 // STATUS.READY on CTRL.EN) + the RING base. On the DOORBELL write the device runs the dispatch DMA: 586 // it fetches the command packet from RING base, validates the SPIR-V magic, signals the FENCE + latches 587 // RESULTPEEK. The sim owns mem_buf, so it hands guest RAM in. Other devices untouched. 588 if addr >= NX_GPU_BASE { if addr < NX_GPU_END { 589 if (s.gpu as i64) != 0 { 590 let wrg: i64 = nx_gpu_write32(s.gpu, addr, value) 591 if addr == (NX_GPU_BASE + NX_GPU_OFF_DOORBELL) { 592 nx_gpu_doorbell_dma(s.gpu, s.mem_buf, s.mem_base, s.mem_size) 593 } 594 return wrg 595 } 596 }} 597 // SiFive test/finisher at 0x100000: 32-bit write of 0x5555 = clean 598 // poweroff per kernel tasks.nx:203-204. 599 if addr == 0x100000 { 600 if (value & 0xffff) == 0x5555 { 601 s.halted = 1 602 s.halt_code = 0 // clean exit 603 return NX_HDL_OK 604 } 605 } 606 return 0 - NX_HDL_BAD_KIND 607} 608 609// ===== CPU-datapath Sv39 translation (virtual-memory-paging-mmu, integration rung) ============ 610// 611// When satp.MODE == Sv39 (8), DATA loads/stores translate their address through the page table in 612// guest RAM (the SAME nx_sv39_walk the MMU walk device uses). satp.MODE == 0 (Bare, the reset + 613// every existing test) -> identity, so there is ZERO change to all prior gates. Device MMIO 614// windows stay physical (identity) even under Sv39 in this slice so a paging test can still drive 615// the UART/finisher. Instruction FETCH is NOT translated here (the image is physically addressed); 616// fetch-translation + the page-fault TRAP on a bad data walk are the documented follow-on rungs -- 617// on a walk fault this returns the vaddr unchanged (a tampered mapping then reads out-of-range -> 0, 618// which the gate's verify catches). 619func nx_rv64im_is_device(addr: i64) -> i64 { 620 if addr >= 0x100000 { if addr < 0x100008 { return 1 } } // SiFive finisher 621 if addr >= 0x02000000 { if addr < 0x0200C000 { return 1 } } // CLINT 622 if addr >= 0x10000000 { if addr < 0x10000008 { return 1 } } // UART 623 if addr >= 0x10001000 { if addr < 0x10007000 { return 1 } } // virtio/nvme/nndev/mmu windows 624 return 0 625} 626const NX_ACC_FETCH: i64 = 0 627const NX_ACC_LOAD: i64 = 1 628const NX_ACC_STORE: i64 = 2 629// Translate `vaddr` for an access of type `access` (FETCH/LOAD/STORE) and ENFORCE the page's permission 630// bits (riscv-priv-spec): a fetch needs X (the NX-bit / W^X foundation -- a data page is not executable), 631// a load needs R, a store needs W. A valid leaf lacking the required bit signals xlate_fault, which the 632// step loop routes to the SAME page-fault cause it already uses per access (instr=12/load=13/store=15). 633// Identity in M-mode and under Bare, and device MMIO stays physical -> every physically-addressed path 634// is unchanged (perm enforcement applies only to S/U-mode Sv39 RAM accesses). 635func nx_rv64im_xlate(s: *NxRv64imSim, vaddr: i64, access: i64) -> i64 { 636 if s.priv == 3 { return vaddr } // M-mode: always Bare (riscv-priv-spec) 637 let satp: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_SATP) 638 if ((satp >> 60) & 0xf) != 8 { return vaddr } // Bare / non-Sv39: identity 639 if nx_rv64im_is_device(vaddr) == 1 { return vaddr } // device MMIO stays physical 640 let fb: *i64 = sys_mmap(16) as *i64 641 let pb: *i64 = sys_mmap(16) as *i64 // leaf PTE perm bits (V|R|W|X) from the walk 642 let pab: *i64 = sys_mmap(16) as *i64 // leaf PTE physical address (A/D write-back target) 643 let pa: i64 = nx_sv39_walk(satp, s.mem_buf, s.mem_base, s.mem_size, vaddr, fb, pb, pab) 644 if fb[0] == 1 { s.xlate_fault = 1; return vaddr } // walk fault: signal the step loop to trap 645 var need: i64 = 8 // FETCH needs X (bit 3) 646 if access == NX_ACC_LOAD { need = 2 } // LOAD needs R (bit 1) 647 if access == NX_ACC_STORE { need = 4 } // STORE needs W (bit 2) 648 if (pb[0] & need) == 0 { s.xlate_fault = 1; return vaddr } // R/W/X permission violation -> page fault 649 // USER/SUPERVISOR isolation (riscv-priv-spec, the U-bit = bit 4): a U-mode access REQUIRES a 650 // user page; an S-mode access to a USER page is denied unless mstatus.SUM (Supervisor User Memory). 651 let is_user_page: i64 = pb[0] & 0x10 652 if s.priv == 0 { if is_user_page == 0 { s.xlate_fault = 1; return vaddr } } // U-mode: page must be U=1 653 if s.priv == 1 { if is_user_page != 0 { // S-mode touching a U=1 page 654 let mstatus: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_MSTATUS) 655 if (mstatus & 0x40000) == 0 { s.xlate_fault = 1; return vaddr } // SUM=0 -> deny (kernel can't deref user memory by default) 656 } } 657 // ACCESSED/DIRTY bits (hardware-managed, riscv-priv-spec Svadu): a SUCCESSFUL translation sets A on 658 // any access + D on a store, in the leaf PTE. Additive -- A/D (bits 6/7) don't affect the PA/perm 659 // decode, so every prior test is unchanged; they let the OS drive page replacement + dirty write-back. 660 let pte_off: i64 = pab[0] - s.mem_base 661 if nx_mmu_inrange(pte_off, 8, s.mem_size) == 1 { 662 let cur: i64 = nx_mmu_rd64(s.mem_buf, pte_off) 663 var npte: i64 = cur | 0x40 // A (bit 6) 664 if access == NX_ACC_STORE { npte = npte | 0x80 } // D (bit 7) on a store 665 if npte != cur { nx_mmu_wr64(s.mem_buf, pte_off, npte) } 666 } 667 return pa 668} 669 670// ===== Map decoded {opcode, funct3, funct7} -> ALU op ================================================= 671// 672// The decoder gave us NX_RV64IM_OP_OP / OP_IMM / OP_32 / etc.; we 673// need the finer-grained ALU op for the compute call. 674 675func nx_rv64im_sim_alu_select(op_kind: i64, funct3: i64, funct7: i64) -> i64 { 676 if op_kind == NX_RV64IM_OP_OP { 677 if funct7 == 0x01 { 678 // M extension 679 if funct3 == 0 { return NX_RV64IM_ALU_MUL } 680 if funct3 == 1 { return NX_RV64IM_ALU_MULH } 681 if funct3 == 2 { return NX_RV64IM_ALU_MULHSU } 682 if funct3 == 3 { return NX_RV64IM_ALU_MULHU } 683 if funct3 == 4 { return NX_RV64IM_ALU_DIV } 684 if funct3 == 5 { return NX_RV64IM_ALU_DIVU } 685 if funct3 == 6 { return NX_RV64IM_ALU_REM } 686 if funct3 == 7 { return NX_RV64IM_ALU_REMU } 687 } 688 if funct3 == 0 { 689 if funct7 == 0x20 { return NX_RV64IM_ALU_SUB } 690 return NX_RV64IM_ALU_ADD 691 } 692 if funct3 == 1 { return NX_RV64IM_ALU_SLL } 693 if funct3 == 2 { return NX_RV64IM_ALU_SLT } 694 if funct3 == 3 { return NX_RV64IM_ALU_SLTU } 695 if funct3 == 4 { return NX_RV64IM_ALU_XOR } 696 if funct3 == 5 { 697 if funct7 == 0x20 { return NX_RV64IM_ALU_SRA } 698 return NX_RV64IM_ALU_SRL 699 } 700 if funct3 == 6 { return NX_RV64IM_ALU_OR } 701 if funct3 == 7 { return NX_RV64IM_ALU_AND } 702 } 703 if op_kind == NX_RV64IM_OP_OP_IMM { 704 // Same as OP except immediates; ALU is the same compute layer 705 // (the executor swaps operand b for the immediate value). 706 if funct3 == 0 { return NX_RV64IM_ALU_ADD } 707 if funct3 == 1 { return NX_RV64IM_ALU_SLL } 708 if funct3 == 2 { return NX_RV64IM_ALU_SLT } 709 if funct3 == 3 { return NX_RV64IM_ALU_SLTU } 710 if funct3 == 4 { return NX_RV64IM_ALU_XOR } 711 if funct3 == 5 { 712 // SRLI vs SRAI distinguished by inst[30] (funct7 bit 5). FIXED 2026-08-23 (nishios NO3, 713 // riscv-arch-test 2.7.4 srai-01 + c.srai-01 FAILED while register sra-01 PASSED): on RV64 714 // the I-type shamt is SIX bits, so shamt[5] lives in inst[25] = funct7 bit 0. A shift by 715 // 32..63 therefore carries funct7 = 0x21, the old `funct7 == 0x20` test missed it and the 716 // instruction fell through to the LOGICAL shift -- sign bits silently dropped. Test the 717 // marker bit alone; the register form (funct7 exactly 0x20/0x00) is unchanged by this. 718 if (funct7 & 0x20) == 0x20 { return NX_RV64IM_ALU_SRA } 719 return NX_RV64IM_ALU_SRL 720 } 721 if funct3 == 6 { return NX_RV64IM_ALU_OR } 722 if funct3 == 7 { return NX_RV64IM_ALU_AND } 723 } 724 if op_kind == NX_RV64IM_OP_OP_32 { 725 if funct7 == 0x01 { 726 if funct3 == 0 { return NX_RV64IM_ALU_MULW } 727 if funct3 == 4 { return NX_RV64IM_ALU_DIVW } 728 if funct3 == 5 { return NX_RV64IM_ALU_DIVUW } 729 if funct3 == 6 { return NX_RV64IM_ALU_REMW } 730 if funct3 == 7 { return NX_RV64IM_ALU_REMUW } 731 } 732 if funct3 == 0 { 733 if funct7 == 0x20 { return NX_RV64IM_ALU_SUBW } 734 return NX_RV64IM_ALU_ADDW 735 } 736 if funct3 == 1 { return NX_RV64IM_ALU_SLLW } 737 if funct3 == 5 { 738 if funct7 == 0x20 { return NX_RV64IM_ALU_SRAW } 739 return NX_RV64IM_ALU_SRLW 740 } 741 } 742 if op_kind == NX_RV64IM_OP_OP_IMM_32 { 743 if funct3 == 0 { return NX_RV64IM_ALU_ADDW } 744 if funct3 == 1 { return NX_RV64IM_ALU_SLLW } 745 if funct3 == 5 { 746 if funct7 == 0x20 { return NX_RV64IM_ALU_SRAW } 747 return NX_RV64IM_ALU_SRLW 748 } 749 } 750 return NX_RV64IM_ALU_INVALID 751} 752 753// ===== Trap entry ================================================= 754// 755// Common path for all exceptions + interrupts. Saves mepc, sets 756// mcause, transitions mstatus per riscv-privileged-spec §3.1.6.2: 757// mstatus.MPIE <- mstatus.MIE (save the interrupt-enable bit) 758// mstatus.MIE <- 0 (disable interrupts in handler) 759// mstatus.MPP <- current privilege (M-mode in Tier A) 760// mepc <- pc-of-faulting-instruction 761// pc <- mtvec (direct mode; vectored mode unused) 762 763const NX_MCAUSE_INT_MACHINE_TIMER: i64 = 0 - 9223372036854775801 // (1<<63)|7 764const NX_MCAUSE_EXC_ECALL_U: i64 = 8 // environment call from U-mode 765const NX_MCAUSE_EXC_ECALL_S: i64 = 9 // environment call from S-mode 766const NX_MCAUSE_EXC_ECALL_M: i64 = 11 767const NX_MCAUSE_EXC_ILLEGAL_INSTR: i64 = 2 768const NX_MCAUSE_EXC_INSTR_PAGE_FAULT: i64 = 12 // Sv39 instruction-FETCH walk fault 769const NX_MCAUSE_EXC_LOAD_PAGE_FAULT: i64 = 13 // Sv39 data-load walk fault 770const NX_MCAUSE_EXC_STORE_PAGE_FAULT: i64 = 15 // Sv39 data-store walk fault 771 772const NX_MSTATUS_MIE_BIT: i64 = 0x08 // bit 3 773const NX_MSTATUS_MPIE_BIT: i64 = 0x80 // bit 7 774const NX_MSTATUS_MPP_MASK: i64 = 0x1800 // bits 11..12 775const NX_MIE_MTIE_BIT: i64 = 0x80 // bit 7 776 777func nx_rv64im_sim_take_trap(s: *NxRv64imSim, cause: i64, mepc_val: i64) -> i64 { 778 // S-MODE DELEGATION (the real OS trap path): an EXCEPTION (cause>=0, not an interrupt) taken from 779 // S/U-mode (priv<3) whose medeleg bit is set traps to S-mode -- stvec/sepc/scause/sstatus.SPP -- so 780 // the kernel handles its own page-faults/syscalls in S-mode and sret's back. medeleg resets to 0 781 // (mmap-zeroed) so with no delegation every trap still goes to M-mode exactly as before (no regression). 782 if cause >= 0 { if s.priv < 3 { 783 let medeleg: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_MEDELEG) 784 if ((medeleg >> cause) & 1) == 1 { 785 let sst: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_MSTATUS) 786 var nsst: i64 = sst & (0 - 1 ^ 0x100) // clear SPP (mstatus bit 8) 787 nsst = nsst | ((s.priv & 1) << 8) // SPP <- the privilege we trap FROM (S=1 / U=0) 788 nx_rv64im_csr_write(s.csr, NX_CSR_MSTATUS, nsst) 789 nx_rv64im_csr_write(s.csr, NX_CSR_SEPC, mepc_val) 790 nx_rv64im_csr_write(s.csr, NX_CSR_SCAUSE, cause) 791 s.priv = 1 // enter S-mode (the kernel) 792 let stvec: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_STVEC) 793 s.pc = stvec & (0 - 4) 794 return NX_HDL_OK 795 } 796 } } 797 let mstatus: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_MSTATUS) 798 var new_mstatus: i64 = mstatus 799 // Save MIE into MPIE. 800 if (mstatus & NX_MSTATUS_MIE_BIT) != 0 { 801 new_mstatus = new_mstatus | NX_MSTATUS_MPIE_BIT 802 } 803 if (mstatus & NX_MSTATUS_MIE_BIT) == 0 { 804 new_mstatus = new_mstatus & (0 - 1 ^ NX_MSTATUS_MPIE_BIT) 805 } 806 // Disable interrupts. 807 new_mstatus = new_mstatus & (0 - 1 ^ NX_MSTATUS_MIE_BIT) 808 // MPP <- the privilege we are trapping FROM (so mret can restore it); then enter M-mode. 809 new_mstatus = (new_mstatus & (0 - 1 ^ NX_MSTATUS_MPP_MASK)) | ((s.priv & 0x3) << 11) 810 nx_rv64im_csr_write(s.csr, NX_CSR_MSTATUS, new_mstatus) 811 nx_rv64im_csr_write(s.csr, NX_CSR_MEPC, mepc_val) 812 nx_rv64im_csr_write(s.csr, NX_CSR_MCAUSE, cause) 813 s.priv = 3 // the handler runs in M-mode 814 // Jump to handler. Direct mode: low 2 bits of mtvec are 00. 815 let mtvec: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_MTVEC) 816 s.pc = mtvec & (0 - 4) // clear low 2 bits 817 return NX_HDL_OK 818} 819 820func nx_rv64im_sim_mret(s: *NxRv64imSim) -> i64 { 821 let mstatus: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_MSTATUS) 822 var new_mstatus: i64 = mstatus 823 // MIE <- MPIE 824 if (mstatus & NX_MSTATUS_MPIE_BIT) != 0 { 825 new_mstatus = new_mstatus | NX_MSTATUS_MIE_BIT 826 } 827 if (mstatus & NX_MSTATUS_MPIE_BIT) == 0 { 828 new_mstatus = new_mstatus & (0 - 1 ^ NX_MSTATUS_MIE_BIT) 829 } 830 // MPIE <- 1 831 new_mstatus = new_mstatus | NX_MSTATUS_MPIE_BIT 832 // privilege <- MPP (the saved mode); then MPP <- U (0), per riscv-priv-spec mret. 833 let mpp: i64 = (mstatus >> 11) & 0x3 834 new_mstatus = new_mstatus & (0 - 1 ^ NX_MSTATUS_MPP_MASK) 835 nx_rv64im_csr_write(s.csr, NX_CSR_MSTATUS, new_mstatus) 836 s.priv = mpp 837 s.pc = nx_rv64im_csr_read(s.csr, NX_CSR_MEPC) 838 return NX_HDL_OK 839} 840 841// S-mode trap return -- the S-mode twin of mret (riscv-priv-spec): privilege <- sstatus.SPP (mstatus 842// bit 8: 1=S / 0=U); pc <- sepc; then SPP <- U. Lets an S-mode kernel return to the trapped context. 843func nx_rv64im_sim_sret(s: *NxRv64imSim) -> i64 { 844 let sst: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_MSTATUS) 845 let spp: i64 = (sst >> 8) & 1 846 let nsst: i64 = sst & (0 - 1 ^ 0x100) // SPP <- U(0) after sret 847 nx_rv64im_csr_write(s.csr, NX_CSR_MSTATUS, nsst) 848 s.priv = spp 849 s.pc = nx_rv64im_csr_read(s.csr, NX_CSR_SEPC) 850 return NX_HDL_OK 851} 852 853// ===== CSR address extraction ================================================= 854// 855// SYSTEM-op CSR address is inst[31:20] treated as UNSIGNED 12-bit. 856// Note this is the same bit slice as imm_i but NOT sign-extended. 857 858func nx_rv64im_csr_addr(inst: i64) -> i64 { 859 return (inst >> 20) & 0xfff 860} 861 862// ===== Single-cycle step ================================================= 863// 864// Executes ONE instruction at PC, advances PC + steps counter, ticks 865// CLINT once. Returns NX_HDL_OK on success, 0 - <verdict> on halt. 866 867func nx_rv64im_sim_step(s: *NxRv64imSim) -> i64 { 868 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND } 869 if s.halted == 1 { return 0 - NX_HDL_BAD_KIND } 870 871 // Tick CLINT first so MTIME advances per cycle (kernel polls it). 872 nx_clint_tick(s.clint) 873 nx_rv64im_csr_tick_mcycle(s.csr) 874 875 // Per-PC cycle attribution (M2 silicon-feedback foundation). Bumps 876 // the slot indexed by the executing PC; single branch when disabled. 877 if s.pc_cycles_on == 1 { 878 if s.pc >= s.mem_base { 879 let _attr_off: i64 = (s.pc - s.mem_base) >> 2 880 if _attr_off < s.pc_cycles_cap { 881 s.pc_cycles_buf[_attr_off] = s.pc_cycles_buf[_attr_off] + 1 882 } 883 } 884 } 885 886 // Interrupt check. CLINT asserts MTIP via nx_clint_tick; CPU 887 // takes the timer trap if mstatus.MIE && mie.MTIE. This is the 888 // path that fires kernel preemption every TICK_CYCLES. 889 if nx_clint_mtip_get(s.clint) == 1 { 890 let mstatus: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_MSTATUS) 891 let mie: i64 = nx_rv64im_csr_read(s.csr, NX_CSR_MIE) 892 if (mstatus & NX_MSTATUS_MIE_BIT) != 0 { 893 if (mie & NX_MIE_MTIE_BIT) != 0 { 894 nx_rv64im_sim_take_trap(s, NX_MCAUSE_INT_MACHINE_TIMER, s.pc) 895 s.steps = s.steps + 1 896 return NX_HDL_OK 897 } 898 } 899 } 900 901 // Fetch -- the PC is a VIRTUAL address in S/U-mode under Sv39, so the instruction fetch translates 902 // through the page table just like a data access (nx_rv64im_xlate returns identity in M-mode and 903 // under Bare, so every physically-addressed test -- M-mode boot, handlers, Bare programs -- is 904 // unchanged). A walk fault on the code page raises an instruction-page-fault (mcause=12) and 905 // aborts the fetch, vectoring to mtvec (where the M-mode handler is fetched physically). 906 s.xlate_fault = 0 907 let fetch_pa: i64 = nx_rv64im_xlate(s, s.pc, NX_ACC_FETCH) // fetch enforces the X bit 908 if s.xlate_fault == 1 { // code-page walk fault -> instruction-page-fault trap 909 nx_rv64im_csr_write(s.csr, NX_CSR_MTVAL, s.pc) 910 nx_rv64im_sim_take_trap(s, NX_MCAUSE_EXC_INSTR_PAGE_FAULT, s.pc) 911 s.steps = s.steps + 1 912 return NX_HDL_OK 913 } 914 // C extension: read a halfword; if compressed (low 2 bits != 11) expand it to the 32-bit equivalent + advance pc by 915 // 2, else read the full 32-bit word + advance by 4. TRANSPARENT to 32-bit code (low bits 11 -> not compressed). 916 // Fetch the code word ONCE and reuse it. Was two load32 calls per 32-bit instruction (the low 917 // 16 bits drive the compressed-check; the full 32 bits are the instruction) -- a redundant 918 // memory read every step. JIT-ladder rung 0: eliminate it. Behaviour-identical. 919 let fetch_word: i64 = nx_rv64im_sim_load32(s, fetch_pa) & 0xffffffff 920 let half: i64 = fetch_word & 0xffff 921 var inst: i64 = 0 922 var instr_len: i64 = 4 923 if nx_rvc_is_compressed(half) == 1 { inst = nx_rvc_expand(half); instr_len = 2 } 924 else { inst = fetch_word } 925 if inst == 0 { 926 // All-zero instruction: treat as halt sentinel. Kernel never 927 // emits these; real RV64 would raise illegal-instr. V1 sim 928 // uses this as a clean test-end signal. 929 s.halted = 1 930 s.halt_code = nx_rv64im_rf_read(s.rf, 10) // exit code in a0 931 return 0 932 } 933 934 // Decode 935 let op_kind: i64 = nx_rv64im_decode_kind(inst) 936 let rd: i64 = nx_rv64im_rd(inst) 937 let rs1: i64 = nx_rv64im_rs1(inst) 938 let rs2: i64 = nx_rv64im_rs2(inst) 939 let funct3: i64 = nx_rv64im_funct3(inst) 940 let funct7: i64 = nx_rv64im_funct7(inst) 941 942 let rs1_val: i64 = nx_rv64im_rf_read(s.rf, rs1) 943 let rs2_val: i64 = nx_rv64im_rf_read(s.rf, rs2) 944 945 var next_pc: i64 = s.pc + instr_len // C ext: +2 for compressed, +4 for 32-bit 946 947 if op_kind == NX_RV64IM_OP_LUI { 948 nx_rv64im_rf_write(s.rf, rd, nx_rv64im_imm_u(inst)) 949 } 950 if op_kind == NX_RV64IM_OP_AUIPC { 951 nx_rv64im_rf_write(s.rf, rd, s.pc + nx_rv64im_imm_u(inst)) 952 } 953 if op_kind == NX_RV64IM_OP_JAL { 954 nx_rv64im_rf_write(s.rf, rd, s.pc + instr_len) 955 next_pc = s.pc + nx_rv64im_imm_j(inst) 956 } 957 if op_kind == NX_RV64IM_OP_JALR { 958 let tgt: i64 = (rs1_val + nx_rv64im_imm_i(inst)) & (0 - 2) 959 nx_rv64im_rf_write(s.rf, rd, s.pc + instr_len) 960 next_pc = tgt 961 } 962 if op_kind == NX_RV64IM_OP_BRANCH { 963 var take: i64 = 0 964 if funct3 == 0 { if rs1_val == rs2_val { take = 1 } } // beq 965 if funct3 == 1 { if rs1_val != rs2_val { take = 1 } } // bne 966 if funct3 == 4 { if rs1_val < rs2_val { take = 1 } } // blt 967 if funct3 == 5 { if rs1_val >= rs2_val { take = 1 } } // bge 968 if funct3 == 6 { take = nx_rv64im_ltu(rs1_val, rs2_val) } // bltu 969 if funct3 == 7 { 970 let lt: i64 = nx_rv64im_ltu(rs1_val, rs2_val) 971 if lt == 0 { take = 1 } // bgeu 972 } 973 if take == 1 { next_pc = s.pc + nx_rv64im_imm_b(inst) } 974 } 975 if op_kind == NX_RV64IM_OP_LOAD { 976 let vaddr: i64 = rs1_val + nx_rv64im_imm_i(inst) 977 s.xlate_fault = 0 978 let addr: i64 = nx_rv64im_xlate(s, vaddr, NX_ACC_LOAD) // Sv39 data translation + R enforcement 979 if s.xlate_fault == 1 { // walk/perm fault -> load-page-fault trap (instr aborted) 980 nx_rv64im_csr_write(s.csr, NX_CSR_MTVAL, vaddr) 981 nx_rv64im_sim_take_trap(s, NX_MCAUSE_EXC_LOAD_PAGE_FAULT, s.pc) 982 s.steps = s.steps + 1 983 return NX_HDL_OK 984 } 985 if funct3 == 0 { nx_rv64im_rf_write(s.rf, rd, nx_rv64im_sext8(nx_rv64im_sim_load8(s, addr))) } // lb 986 if funct3 == 1 { nx_rv64im_rf_write(s.rf, rd, nx_rv64im_sext16(nx_rv64im_sim_load16(s, addr))) } // lh 987 if funct3 == 2 { nx_rv64im_rf_write(s.rf, rd, nx_rv64im_sext32(nx_rv64im_sim_load32(s, addr))) } // lw 988 if funct3 == 3 { nx_rv64im_rf_write(s.rf, rd, nx_rv64im_sim_load64(s, addr)) } // ld 989 if funct3 == 4 { nx_rv64im_rf_write(s.rf, rd, nx_rv64im_sim_load8(s, addr) & 0xff) } // lbu 990 if funct3 == 5 { nx_rv64im_rf_write(s.rf, rd, nx_rv64im_sim_load16(s, addr) & 0xffff) } // lhu 991 if funct3 == 6 { nx_rv64im_rf_write(s.rf, rd, nx_rv64im_sim_load32(s, addr) & 0xffffffff) } // lwu 992 } 993 if op_kind == NX_RV64IM_OP_STORE { 994 let vaddr: i64 = rs1_val + nx_rv64im_imm_s(inst) 995 s.xlate_fault = 0 996 let addr: i64 = nx_rv64im_xlate(s, vaddr, NX_ACC_STORE) // Sv39 data translation + W enforcement 997 if s.xlate_fault == 1 { // walk/perm fault -> store-page-fault trap (instr aborted) 998 nx_rv64im_csr_write(s.csr, NX_CSR_MTVAL, vaddr) 999 nx_rv64im_sim_take_trap(s, NX_MCAUSE_EXC_STORE_PAGE_FAULT, s.pc) 1000 s.steps = s.steps + 1 1001 return NX_HDL_OK 1002 } 1003 if funct3 == 0 { nx_rv64im_sim_store8(s, addr, rs2_val) } // sb (UART THR + general) 1004 if funct3 == 1 { nx_rv64im_sim_store16(s, addr, rs2_val) } // sh 1005 if funct3 == 2 { nx_rv64im_sim_store32(s, addr, rs2_val) } // sw (+ finisher 0x100000) 1006 if funct3 == 3 { nx_rv64im_sim_store64(s, addr, rs2_val) } // sd 1007 } 1008 if op_kind == NX_RV64IM_OP_OP { 1009 let alu_op: i64 = nx_rv64im_sim_alu_select(op_kind, funct3, funct7) 1010 nx_rv64im_rf_write(s.rf, rd, nx_rv64im_alu_compute(alu_op, rs1_val, rs2_val)) 1011 } 1012 if op_kind == NX_RV64IM_OP_OP_IMM { 1013 let alu_op: i64 = nx_rv64im_sim_alu_select(op_kind, funct3, funct7) 1014 let imm: i64 = nx_rv64im_imm_i(inst) 1015 // SLLI/SRLI/SRAI shamt is encoded in the immediate's low 6 bits. 1016 nx_rv64im_rf_write(s.rf, rd, nx_rv64im_alu_compute(alu_op, rs1_val, imm)) 1017 } 1018 if op_kind == NX_RV64IM_OP_OP_32 { 1019 let alu_op: i64 = nx_rv64im_sim_alu_select(op_kind, funct3, funct7) 1020 nx_rv64im_rf_write(s.rf, rd, nx_rv64im_alu_compute(alu_op, rs1_val, rs2_val)) 1021 } 1022 // M-extension R-type ops (mul/div/rem family). The decoder gives 1023 // them their own op_kind (OP_M_MUL / OP_M_DIV / *_32); alu_select 1024 // keys on the base OP / OP_32 kind + funct7=0x01, so route through 1025 // those. Without these four cases the sim silently skipped every 1026 // multiply/divide/remainder and left rd unwritten. 1027 if op_kind == NX_RV64IM_OP_M_MUL { 1028 let alu_op: i64 = nx_rv64im_sim_alu_select(NX_RV64IM_OP_OP, funct3, funct7) 1029 nx_rv64im_rf_write(s.rf, rd, nx_rv64im_alu_compute(alu_op, rs1_val, rs2_val)) 1030 } 1031 if op_kind == NX_RV64IM_OP_M_DIV { 1032 let alu_op: i64 = nx_rv64im_sim_alu_select(NX_RV64IM_OP_OP, funct3, funct7) 1033 nx_rv64im_rf_write(s.rf, rd, nx_rv64im_alu_compute(alu_op, rs1_val, rs2_val)) 1034 } 1035 if op_kind == NX_RV64IM_OP_M_MUL_32 { 1036 let alu_op: i64 = nx_rv64im_sim_alu_select(NX_RV64IM_OP_OP_32, funct3, funct7) 1037 nx_rv64im_rf_write(s.rf, rd, nx_rv64im_alu_compute(alu_op, rs1_val, rs2_val)) 1038 } 1039 if op_kind == NX_RV64IM_OP_M_DIV_32 { 1040 let alu_op: i64 = nx_rv64im_sim_alu_select(NX_RV64IM_OP_OP_32, funct3, funct7) 1041 nx_rv64im_rf_write(s.rf, rd, nx_rv64im_alu_compute(alu_op, rs1_val, rs2_val)) 1042 } 1043 if op_kind == NX_RV64IM_OP_OP_IMM_32 { 1044 let alu_op: i64 = nx_rv64im_sim_alu_select(op_kind, funct3, funct7) 1045 nx_rv64im_rf_write(s.rf, rd, nx_rv64im_alu_compute(alu_op, rs1_val, nx_rv64im_imm_i(inst))) 1046 } 1047 if op_kind == NX_RV64IM_OP_FENCE { 1048 // No-op in single-cycle in-order sim (no memory reordering). 1049 } 1050 if op_kind == NX_RV64IM_OP_SYSTEM { 1051 let csr_addr: i64 = nx_rv64im_csr_addr(inst) 1052 1053 if funct3 == 0 { 1054 // ecall / ebreak / mret -- distinguished by inst[31:20]. 1055 // Per spec table 3.4 / 9.1: 1056 // ecall -> imm[11:0] = 000000000000 1057 // ebreak -> imm[11:0] = 000000000001 1058 // mret -> imm[11:0] = 001100000010 (0x302) 1059 if csr_addr == 0x000 { 1060 // ecall -- the cause depends on the originating privilege (8=U, 9=S, 11=M); the 1061 // handler at mtvec is the kernel's syscall dispatcher. 1062 var ecause: i64 = NX_MCAUSE_EXC_ECALL_M 1063 if s.priv == 0 { ecause = NX_MCAUSE_EXC_ECALL_U } 1064 if s.priv == 1 { ecause = NX_MCAUSE_EXC_ECALL_S } 1065 nx_rv64im_sim_take_trap(s, ecause, s.pc) 1066 s.steps = s.steps + 1 1067 return NX_HDL_OK 1068 } 1069 if csr_addr == 0x001 { 1070 // ebreak -- treat as halt for the V1 sim (kernel 1071 // never executes ebreak in normal flow). 1072 s.halted = 1 1073 s.halt_code = nx_rv64im_rf_read(s.rf, 10) 1074 return 0 1075 } 1076 if csr_addr == 0x302 { 1077 // mret -- pc <- mepc, restore MIE from MPIE. 1078 nx_rv64im_sim_mret(s) 1079 s.steps = s.steps + 1 1080 return NX_HDL_OK 1081 } 1082 if csr_addr == 0x102 { 1083 // sret -- S-mode trap return: pc <- sepc, privilege <- sstatus.SPP. 1084 nx_rv64im_sim_sret(s) 1085 s.steps = s.steps + 1 1086 return NX_HDL_OK 1087 } 1088 // Unrecognised SYSTEM/funct3=0 op -- illegal. 1089 nx_rv64im_sim_take_trap(s, NX_MCAUSE_EXC_ILLEGAL_INSTR, s.pc) 1090 s.steps = s.steps + 1 1091 return NX_HDL_OK 1092 } 1093 1094 // csrr* family. Build the "operand" -- register value for 1095 // funct3 = 1/2/3; zero-extended 5-bit immediate for 5/6/7. 1096 var operand: i64 = 0 1097 if funct3 == 1 { operand = rs1_val } 1098 if funct3 == 2 { operand = rs1_val } 1099 if funct3 == 3 { operand = rs1_val } 1100 if funct3 == 5 { operand = rs1 & 0x1f } // csrrwi uimm = rs1 field 1101 if funct3 == 6 { operand = rs1 & 0x1f } 1102 if funct3 == 7 { operand = rs1 & 0x1f } 1103 1104 let old_out: *i64 = (sys_mmap(8)) as *i64 1105 old_out[0] = 0 1106 let cur: i64 = nx_rv64im_csr_read(s.csr, csr_addr) 1107 1108 if funct3 == 1 { // csrrw 1109 nx_rv64im_csr_swap(s.csr, csr_addr, operand, old_out) 1110 nx_rv64im_rf_write(s.rf, rd, old_out[0]) 1111 } 1112 if funct3 == 5 { // csrrwi 1113 nx_rv64im_csr_swap(s.csr, csr_addr, operand, old_out) 1114 nx_rv64im_rf_write(s.rf, rd, old_out[0]) 1115 } 1116 if funct3 == 2 { // csrrs -- set bits indicated by operand 1117 nx_rv64im_csr_write(s.csr, csr_addr, cur | operand) 1118 nx_rv64im_rf_write(s.rf, rd, cur) 1119 } 1120 if funct3 == 6 { // csrrsi 1121 nx_rv64im_csr_write(s.csr, csr_addr, cur | operand) 1122 nx_rv64im_rf_write(s.rf, rd, cur) 1123 } 1124 if funct3 == 3 { // csrrc -- clear bits indicated by operand 1125 nx_rv64im_csr_write(s.csr, csr_addr, cur & (0 - 1 ^ operand)) 1126 nx_rv64im_rf_write(s.rf, rd, cur) 1127 } 1128 if funct3 == 7 { // csrrci 1129 nx_rv64im_csr_write(s.csr, csr_addr, cur & (0 - 1 ^ operand)) 1130 nx_rv64im_rf_write(s.rf, rd, cur) 1131 } 1132 } 1133 if op_kind == NX_RV64IM_OP_AMO { 1134 // RV64A atomics: lr/sc + amo* (F107h). funct5 = inst[31:27] selects the op; funct3 = 2 (.w, 1135 // 32-bit) or 3 (.d, 64-bit). The address is rs1 EXACTLY (no immediate offset, per spec). 1136 let funct5: i64 = (inst >> 27) & 0x1f 1137 var is_d: i64 = 0 1138 if funct3 == 3 { is_d = 1 } 1139 let amo_va: i64 = rs1_val 1140 s.xlate_fault = 0 1141 let amo_pa: i64 = nx_rv64im_xlate(s, amo_va, NX_ACC_STORE) // amo needs R+W; enforce W (identity in M/Bare) 1142 if s.xlate_fault == 1 { 1143 nx_rv64im_csr_write(s.csr, NX_CSR_MTVAL, amo_va) 1144 nx_rv64im_sim_take_trap(s, NX_MCAUSE_EXC_STORE_PAGE_FAULT, s.pc) 1145 s.steps = s.steps + 1 1146 return NX_HDL_OK 1147 } 1148 // old value: sign-extended 32-bit for .w (so rd + signed compares are correct); full 64 for .d. 1149 var amo_old: i64 = 0 1150 if is_d == 1 { amo_old = nx_rv64im_sim_load64(s, amo_pa) } 1151 if is_d == 0 { amo_old = nx_rv64im_sext32(nx_rv64im_sim_load32(s, amo_pa)) } 1152 1153 if funct5 == 0x02 { 1154 // lr.w/d: record the reservation; rd <- old. 1155 s.resv_addr = amo_pa 1156 s.resv_valid = 1 1157 nx_rv64im_rf_write(s.rf, rd, amo_old) 1158 } 1159 if funct5 == 0x03 { 1160 // sc.w/d: success iff the reservation is still valid AND matches this address. 1161 var sc_ok: i64 = 0 1162 if s.resv_valid == 1 { if s.resv_addr == amo_pa { sc_ok = 1 } } 1163 if sc_ok == 1 { 1164 if is_d == 1 { nx_rv64im_sim_store64(s, amo_pa, rs2_val) } 1165 if is_d == 0 { nx_rv64im_sim_store32(s, amo_pa, rs2_val & 0xffffffff) } 1166 nx_rv64im_rf_write(s.rf, rd, 0) // 0 = success 1167 } 1168 if sc_ok == 0 { nx_rv64im_rf_write(s.rf, rd, 1) } // 1 = failure 1169 s.resv_valid = 0 // sc always clears the reservation 1170 } 1171 // amo* arithmetic/logical/min-max family (every funct5 except lr=0x02 / sc=0x03). 1172 if funct5 != 0x02 { if funct5 != 0x03 { 1173 // second operand: full 64 for .d; sign-extended low-32 for .w. 1174 var amo_src: i64 = rs2_val 1175 if is_d == 0 { amo_src = nx_rv64im_sext32(rs2_val & 0xffffffff) } 1176 var amo_new: i64 = amo_old 1177 if funct5 == 0x01 { amo_new = amo_src } // amoswap 1178 if funct5 == 0x00 { amo_new = amo_old + amo_src } // amoadd 1179 if funct5 == 0x04 { amo_new = amo_old ^ amo_src } // amoxor 1180 if funct5 == 0x0c { amo_new = amo_old & amo_src } // amoand 1181 if funct5 == 0x08 { amo_new = amo_old | amo_src } // amoor 1182 if funct5 == 0x10 { if amo_old < amo_src { amo_new = amo_old } if amo_old >= amo_src { amo_new = amo_src } } // amomin (signed) 1183 if funct5 == 0x14 { if amo_old < amo_src { amo_new = amo_src } if amo_old >= amo_src { amo_new = amo_old } } // amomax (signed) 1184 if funct5 == 0x18 { // amominu (unsigned) 1185 var uo: i64 = amo_old 1186 var us: i64 = amo_src 1187 if is_d == 0 { uo = amo_old & 0xffffffff; us = amo_src & 0xffffffff } 1188 if nx_rv64im_ltu(uo, us) == 1 { amo_new = amo_old } 1189 if nx_rv64im_ltu(uo, us) == 0 { amo_new = amo_src } 1190 } 1191 if funct5 == 0x1c { // amomaxu (unsigned) 1192 var uo2: i64 = amo_old 1193 var us2: i64 = amo_src 1194 if is_d == 0 { uo2 = amo_old & 0xffffffff; us2 = amo_src & 0xffffffff } 1195 if nx_rv64im_ltu(uo2, us2) == 1 { amo_new = amo_src } 1196 if nx_rv64im_ltu(uo2, us2) == 0 { amo_new = amo_old } 1197 } 1198 s.resv_valid = 0 // an amo writes the line -> breaks any reservation 1199 if is_d == 1 { nx_rv64im_sim_store64(s, amo_pa, amo_new) } 1200 if is_d == 0 { nx_rv64im_sim_store32(s, amo_pa, amo_new & 0xffffffff) } 1201 nx_rv64im_rf_write(s.rf, rd, amo_old) 1202 } } 1203 } 1204 if op_kind == NX_RV64IM_OP_INVALID { 1205 // Illegal instruction. Real kernel: trap with mcause=2. 1206 // V1 sim: halt loud. 1207 s.halted = 1 1208 s.halt_code = 0 - 2 1209 return 0 - NX_HDL_BAD_KIND 1210 } 1211 1212 s.pc = next_pc 1213 s.steps = s.steps + 1 1214 return NX_HDL_OK 1215} 1216 1217// ===== Run loop ================================================= 1218// 1219// Steps until halted or max_steps reached (guard against infinite 1220// loops in misbehaving test programs). Returns the final exit code 1221// from the halt path. 1222 1223func nx_rv64im_sim_run(s: *NxRv64imSim, max_steps: i64) -> i64 { 1224 var i: i64 = 0 1225 while i < max_steps { 1226 if s.halted == 1 { return s.halt_code } 1227 nx_rv64im_sim_step(s) 1228 i = i + 1 1229 } 1230 return 0 - 1 // exceeded max_steps; not halted cleanly 1231} 1232 1233// ===== Deterministic snapshot / restore (F107i, 2026-07-19) ================================================= 1234// 1235// Save + restore the ARCHITECTURAL machine state (32 GPRs + pc/priv/reservation/step-counters + all of RAM) 1236// to/from a caller buffer. Because the sovereign emulator is bit-DETERMINISTIC (no floating point, no host 1237// nondeterminism -- the determinism exceed bet), a restore is EXACT: continuing from a restored snapshot is 1238// byte-identical to never having stopped. This is what QEMU needs icount + savevm to approximate; here it 1239// is free. (CSR/CLINT/UART device state is the documented follow-on rung; this covers register+memory- 1240// defined behaviour -- the common case.) Buffer must be >= NX_RV64IM_SNAP_HDR + mem_size bytes. 1241const NX_RV64IM_SNAP_HDR: i64 = 312 // (7 scalars + 32 GPRs) * 8 bytes 1242 1243func nx_snap_wr64(b: *u8, off: i64, v: i64) -> i64 { var i: i64=0; while i<8 { b[off+i]=((v>>(i*8))&0xff) as u8; i=i+1 } return 0 } 1244func nx_snap_rd64(b: *u8, off: i64) -> i64 { var v: i64=0; var i: i64=0; while i<8 { v=v|((b[off+i] as i64)<<(i*8)); i=i+1 } return v } 1245 1246func nx_rv64im_sim_snapshot(s: *NxRv64imSim, buf: *u8) -> i64 { 1247 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND } 1248 nx_snap_wr64(buf, 0, s.pc) 1249 nx_snap_wr64(buf, 8, s.priv) 1250 nx_snap_wr64(buf, 16, s.halted) 1251 nx_snap_wr64(buf, 24, s.halt_code) 1252 nx_snap_wr64(buf, 32, s.steps) 1253 nx_snap_wr64(buf, 40, s.resv_addr) 1254 nx_snap_wr64(buf, 48, s.resv_valid) 1255 var i: i64 = 0 1256 while i < 32 { nx_snap_wr64(buf, 56 + i*8, nx_rv64im_rf_read(s.rf, i)); i = i + 1 } 1257 var m: i64 = 0 1258 while m < s.mem_size { buf[NX_RV64IM_SNAP_HDR + m] = s.mem_buf[m]; m = m + 1 } 1259 return NX_RV64IM_SNAP_HDR + s.mem_size 1260} 1261 1262func nx_rv64im_sim_restore(s: *NxRv64imSim, buf: *u8) -> i64 { 1263 if s.valid != 1 { return 0 - NX_HDL_BAD_KIND } 1264 s.pc = nx_snap_rd64(buf, 0) 1265 s.priv = nx_snap_rd64(buf, 8) 1266 s.halted = nx_snap_rd64(buf, 16) 1267 s.halt_code = nx_snap_rd64(buf, 24) 1268 s.steps = nx_snap_rd64(buf, 32) 1269 s.resv_addr = nx_snap_rd64(buf, 40) 1270 s.resv_valid = nx_snap_rd64(buf, 48) 1271 var i: i64 = 0 1272 while i < 32 { nx_rv64im_rf_write(s.rf, i, nx_snap_rd64(buf, 56 + i*8)); i = i + 1 } 1273 var m: i64 = 0 1274 while m < s.mem_size { s.mem_buf[m] = buf[NX_RV64IM_SNAP_HDR + m]; m = m + 1 } 1275 return NX_HDL_OK 1276}