"""MMU (NVMPU) model tests: CSR semantics, translation, machine integration. The model is exercised directly and through the machine's guest paths; the real-firmware relocation-copy success is covered by ``tests/test_sim_devices.py`` (boot ladder). All fixtures are synthetic; no hardware, module, guest or network is touched. """ from __future__ import annotations import unittest from sim.machine import Machine, R_EBREAK from sim.mpu import ( MSTATUS_VM_NVMPU, MMPUATTR, MMPUIDX, MMPUPA, MMPURNG, MMPUVA, RiscvMpu, ) KB = 1024 def enable(mpu: RiscvMpu) -> None: mpu.on_csr_write(0x300, MSTATUS_VM_NVMPU << 24) def entry(mpu: RiscvMpu, index: int, va: int, pa: int, rng: int, attr: int = 0x600) -> None: mpu.on_csr_write(MMPUIDX, index) mpu.on_csr_write(MMPUPA, pa) mpu.on_csr_write(MMPURNG, rng) mpu.on_csr_write(MMPUATTR, attr) mpu.on_csr_write(MMPUVA, va | 1) # VLD class RiscvMpuTests(unittest.TestCase): def test_disabled_does_not_translate(self): mpu = RiscvMpu() entry(mpu, 0, 0x80000000, 0x1000, 0x1000) self.assertFalse(mpu.enabled) self.assertIsNone(mpu.translate(0x80000000)) def test_enable_is_the_nvmpu_vm_mode(self): mpu = RiscvMpu() entry(mpu, 0, 0x80000000, 0x1000, 0x1000) mpu.on_csr_write(0x300, 0x1E000000) self.assertTrue(mpu.enabled) # Bare mode disables translation again. mpu.on_csr_write(0x300, 0) self.assertFalse(mpu.enabled) self.assertIsNone(mpu.translate(0x80000000)) def test_translate_range_and_offset(self): mpu = RiscvMpu() entry(mpu, 2, 0x400E000, 0x1000, 0x3000) enable(mpu) self.assertEqual(mpu.translate(0x400E000), 0x1000) self.assertEqual(mpu.translate(0x400E800), 0x1800) self.assertEqual(mpu.translate(0x4010FFF), 0x3FFF) self.assertIsNone(mpu.translate(0x400DFFF)) self.assertIsNone(mpu.translate(0x4011000)) def test_lowest_index_precedence(self): mpu = RiscvMpu() entry(mpu, 3, 0x1000, 0x9000, 0x1000) entry(mpu, 0, 0x1000, 0x5000, 0x1000) entry(mpu, 1, 0x1000, 0x7000, 0x1000) enable(mpu) self.assertEqual(mpu.translate(0x1000), 0x5000) def test_vld_clear_disables_entry(self): mpu = RiscvMpu() entry(mpu, 0, 0x80000000, 0x1000, 0x1000) enable(mpu) self.assertIsNotNone(mpu.translate(0x80000000)) mpu.on_csr_write(MMPUIDX, 0) mpu.on_csr_write(MMPUVA, 0) # VLD clear self.assertIsNone(mpu.translate(0x80000000)) def test_index_field_is_six_bits(self): mpu = RiscvMpu() mpu.on_csr_write(MMPUIDX, 0x140) # 0x40 | 0x00 -> index 0 entry(mpu, 0, 0x1000, 0x2000, 0x1000) enable(mpu) self.assertEqual(mpu.translate(0x1000), 0x2000) def test_state_and_reset(self): mpu = RiscvMpu() entry(mpu, 0, 0x80000000, 0x1000, 0x1000) enable(mpu) state = mpu.state() self.assertTrue(state["enabled"]) self.assertEqual(state["entries"][0]["index"], 0) self.assertEqual(state["entries"][0]["pa"], 0x1000) mpu.reset() self.assertFalse(mpu.enabled) self.assertIsNone(mpu.translate(0x80000000)) class MachineMpuTests(unittest.TestCase): def test_guest_load_store_through_mapping(self): m = Machine(memory_mb=1, ram_base=0, unknown_mmio="fault") m.write_mem(0x1000, b"\x11\x22\x33\x44\x55\x66\x77\x88") entry(m.mpu, 0, 0x80000000, 0x1000, 0x1000) enable(m.mpu) self.assertEqual(m._guest_load(0x80000000, 8), int.from_bytes(b"\x11\x22\x33\x44\x55\x66\x77\x88", "little")) m._guest_store(0x80000004, 8, 0xAABBCCDD) self.assertEqual(m.read_mem(0x1004, 4), b"\xdd\xcc\xbb\xaa") # The debugger surface stays raw/strict: the mapped VA has no backing. with self.assertRaises(Exception): m.read_mem(0x80000000, 4) def test_instruction_fetch_is_translated(self): m = Machine(memory_mb=1, ram_base=0, unknown_mmio="fault") program = bytes.fromhex("13057000" "73001000") # addi a0,zero,7; ebreak m.write_mem(0x2000, program) entry(m.mpu, 0, 0x80002000, 0x2000, 0x1000) enable(m.mpu) m.pc = 0x80002000 # virtual entry; the backing lives at PA 0x2000 reason, pc, _, _ = m.run(10) self.assertEqual(reason, R_EBREAK) self.assertEqual(m.x[10], 7) self.assertEqual(pc, 0x80002004) def test_unmapped_fetch_still_faults(self): m = Machine(memory_mb=1, ram_base=0, unknown_mmio="fault") entry(m.mpu, 0, 0x80002000, 0x2000, 0x1000) enable(m.mpu) m.pc = 0x90000000 # no entry covers it, no backing RAM reason, _, _, _ = m.run(1) self.assertNotEqual(reason, R_EBREAK) class Wpr2RelativeOffsetTests(unittest.TestCase): def test_window_relative_offsets_read_the_same_pair(self): from sim.devices.fwsec import Wpr2Block dev = Wpr2Block() dev.set_range(0x2000000, 0x6000000) self.assertEqual(dev.read(0x824, 4), dev.read(0x1FA824, 4)) self.assertEqual(dev.read(0x828, 4), dev.read(0x1FA828, 4)) self.assertIsNone(dev.read(0x820, 4)) class BootShimArgsTests(unittest.TestCase): def test_bootstrap_args_written_and_image_mirror_absent(self): from sim.gsp_boot import _apply_boot_shim, DEFAULT_IMAGE, DEFAULT_PROFILE from greeneye.elf import load_elf from sim.devices import load_profile profile = load_profile(DEFAULT_PROFILE) m = Machine(memory_mb=16, ram_base=0, unknown_mmio="lenient") shim = dict(profile.get("boot_shim", {})) shim["_image_path"] = DEFAULT_IMAGE meta = {"enabled": True, "assumptions": []} _apply_boot_shim(m, shim, load_elf(DEFAULT_IMAGE), meta) args = profile["boot_shim"]["bootstrap_args"] pa = int(args["pa"], 0) import struct fields = struct.unpack("<6Q", m.read_mem(pa, 48)) self.assertEqual(fields[0], int(args["imem_base_pa"], 0)) self.assertEqual(fields[1], int(args["imem_size"], 0)) self.assertEqual(fields[2], int(args["imem_bs_size"], 0)) self.assertEqual(fields[3], int(args["dmem_base_pa"], 0)) self.assertEqual(fields[4], int(args["dmem_size"], 0)) self.assertEqual(fields[5], int(args["dmem_bs_size"], 0)) # The DMEM image mirror hack is gone from this profile. self.assertNotIn("image_at", shim) self.assertNotIn("image_at", meta) if __name__ == "__main__": unittest.main()