diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-04 18:42:36 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-04 18:42:36 -0400 |
| commit | 1f2073d4fbb1d412d9bbf373312e3c51b83db92a (patch) | |
| tree | a661537d7ec9510324e0a40a5a65e4b8c853f01b /debug/risc-v-gsp-rm-vm/tests/test_mpu.py | |
| parent | c1adf2727983f5cace8ca0f5d1119ac2256c348e (diff) | |
| download | open-iov-1f2073d4fbb1d412d9bbf373312e3c51b83db92a.tar.gz open-iov-1f2073d4fbb1d412d9bbf373312e3c51b83db92a.zip | |
greeneye: GMMU/FBIF observability, PTE decoder and oracle, MPU/libos simulator wave
Simulator development wave (docs 42-44): GMMU page-table entry decode with its oracle/workflow harness, FBIF observability, and MPU/LIBOS device surfaces in the device model, plus refreshed boot/machine/profile data (tu104-570.144), the qemu-line results, and the new unit tests. Docs 00/06/35-41 updated to match; harness helpers and .gitignore included.
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/tests/test_mpu.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/tests/test_mpu.py | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/tests/test_mpu.py b/debug/risc-v-gsp-rm-vm/tests/test_mpu.py new file mode 100644 index 0000000..73ac81f --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/tests/test_mpu.py @@ -0,0 +1,177 @@ +"""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() |
