code wiki / _hdl_build / rv64im_min_mmu.nx
rv64im_min_mmu.nx
buildroot/runtime/_hdl_build/rv64im_min_mmu.nx
about
rv64im_min_mmu.nx -- the Sv39 PAGE-TABLE-WALK device (virtual-memory-paging-mmu).
The genuine hard core of paging: a SOVEREIGN Sv39 multi-level address-translation datapath. On
a doorbell the device reads satp (root page-table PPN + MODE) and a virtual address, WALKS the
3-level Sv39 page table in guest RAM (level 2->1->0, checking each PTE's Valid bit and detecting
a leaf via the R/W/X bits, assembling the physical address with the right page size for the leaf
level -- gigapage/megapage/4K), and latches the translated PADDR + a FAULT flag (set on an
invalid PTE or a walk that reaches no leaf). This is the MMU's translation algorithm, exercised
end-to-end by a driver (emitted by nx_drv_proto_emit) -- the same MMIO+doorbell pattern as the
virtio/nndev devices. Wiring translation transparently into the CPU's load/store datapath +
privilege modes + page-fault traps is the documented follow-on rung (this proves the WALK).
Register window @ NX_MMU_BASE (256 bytes), independent instance (other devices untouched):
0x00 SATP_LO (WO) low 32 bits of satp (root PPN)
0x04 SATP_HI (WO) high 32 bits of satp (MODE in bits 31:28 -> satp bits 63:60; 8 = Sv39)
0x08 VADDR (WO) the (low 32 of the) virtual address to translate
0x0C DOORBELL (WO) write 1 -> walk the page table in guest RAM, latch PADDR + FAULT
0x10 PADDR (RO) translated physical address (low 32) from the last walk
0x14 FAULT (RO) 0 = translated OK; 1 = page fault (invalid PTE / no leaf)
Status: SEED 2026-06-13 (virtual-memory-paging-mmu, Sv39 walk). license_tier: ORIGINAL
dependencies 2 imports · 8 importers
imports: nx_syscalls.nxnishi_hdl_primitives.nx
imported by: nx_nxc_baremetal.nxnx_nxc_run.nxnx_rv64_kernel_gate.nxnx_rv64_mmu_oracle.nxnx_rv64_realc_gate.nxnx_rv64_run_bin.nxnx_rv64_timer_oracle.nxrv64im_min_sim.nx
structs
| 51 | struct NxMmu |
consts
| 25 | const NX_MMU_BASE: i64 = 0x10006000 |
| 26 | const NX_MMU_END: i64 = 0x10006100 |
| 28 | const NX_MMU_OFF_SATPLO: i64 = 0x00 |
| 29 | const NX_MMU_OFF_SATPHI: i64 = 0x04 |
| 30 | const NX_MMU_OFF_VADDR: i64 = 0x08 |
| 31 | const NX_MMU_OFF_DOORBELL: i64 = 0x0C |
| 32 | const NX_MMU_OFF_PADDR: i64 = 0x10 |
| 33 | const NX_MMU_OFF_FAULT: i64 = 0x14 |
| 35 | const NX_MMU_SLOT_SATPLO: i64 = 0 |
| 36 | const NX_MMU_SLOT_SATPHI: i64 = 1 |
| 37 | const NX_MMU_SLOT_VADDR: i64 = 2 |
| 38 | const NX_MMU_SLOT_PADDR: i64 = 3 |
| 39 | const NX_MMU_SLOT_FAULT: i64 = 4 |
| 40 | const NX_MMU_SLOT_N: i64 = 5 |
| 42 | const NX_MMU_OK: i64 = 0 |
| 43 | const NX_MMU_ADDR_OUT_OF_RANGE: i64 = 1 |
| 46 | const NX_SV39_MODE: i64 = 8 |
| 47 | const NX_PTE_V: i64 = 1 // Valid |
| 48 | const NX_PTE_RWX: i64 = 0xE // R|W|X (bits 3:1); any set => leaf |
| 49 | const NX_PPN_MASK: i64 = 0xFFFFFFFFFFF // 44-bit PPN |
functions
| 57 | func nx_mmu_reset(storage: *i64) -> i64 called by 1: nx_mmu_init |
| 66 | func nx_mmu_init(d: *NxMmu, storage: *i64) -> i64 |
| 76 | func nx_mmu_in_range(d: *NxMmu, addr: i64) -> i64 |
| 82 | func nx_mmu_read32(d: *NxMmu, addr: i64, value_out: *i64) -> i64 |
| 93 | func nx_mmu_write32(d: *NxMmu, addr: i64, value: i64) -> i64 |
| 105 | func nx_mmu_rd64(mem_buf: *u8, off: i64) -> i64 |
| 112 | func nx_mmu_wr64(mem_buf: *u8, off: i64, v: i64) -> i64 called by 1: nx_rv64im_xlate |
| 117 | func nx_mmu_inrange(off: i64, w: i64, mem_size: i64) -> i64 { if off < 0 { return 0 } if off > mem_size - w { return 0 } return 1 } |
| 128 | func nx_sv39_walk(satp: i64, mem_buf: *u8, mem_base: i64, mem_size: i64, vaddr: i64, fault_out: *i64, perm_out: *i64, pte_addr_out: *i64) -> i64 |
| 164 | func nx_mmu_doorbell_walk(d: *NxMmu, mem_buf: *u8, mem_base: i64, mem_size: i64) -> i64 |