diff options
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/tests/test_valprobe.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/tests/test_valprobe.py | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/tests/test_valprobe.py b/debug/risc-v-gsp-rm-vm/tests/test_valprobe.py new file mode 100644 index 0000000..cbf530a --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/tests/test_valprobe.py @@ -0,0 +1,200 @@ +"""Tests for the validation-predicate probe (harness/valprobe.py, docs/47). + +The bundled FECS-promote matrix runs offline on the shipped 570.144 image +(skipped when the image is absent). No hardware, no module, no guest, no +network. + +Run:: + + python3 -m unittest tests.test_valprobe -v +""" +from __future__ import annotations + +import contextlib +import io +import json +import os +import struct +import sys +import tempfile +import unittest + +HERE = os.path.dirname(os.path.abspath(__file__)) +TOOL_ROOT = os.path.abspath(os.path.join(HERE, "..")) +if TOOL_ROOT not in sys.path: + sys.path.insert(0, TOOL_ROOT) + +from sim.decode import K_JALR, K_LUI, K_OPI, decode32 # noqa: E402 + +from harness.rehost import DEFAULT_IMAGE, _parse_u64 # noqa: E402 +from harness.valprobe import ( # noqa: E402 + MATRIX_FORMAT, + PROBE_FORMAT, + PROMOTE_VALIDATION, + SPEC_FORMAT, + ValprobeError, + decode_memory_spec, + encode_return_stub, + fecs_spec, + main, + reg_index, + run_spec, +) + +PROFILE = os.path.join(TOOL_ROOT, "sim", "profiles", "tu104-570.144.json") + + +def _run_main(argv: list[str]) -> tuple[int, str, str]: + out, err = io.StringIO(), io.StringIO() + with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): + rc = main(argv) + return rc, out.getvalue(), err.getvalue() + + +class EncodingTests(unittest.TestCase): + def test_stub_decodes_as_lui_addi_ret(self): + code = encode_return_stub(0x2FFFF9800) + self.assertEqual(len(code), 12) + words = struct.unpack("<III", code) + lui = decode32(0, words[0]) + addi = decode32(4, words[1]) + ret = decode32(8, words[2]) + self.assertEqual(lui[0], K_LUI) + self.assertEqual(lui[2], 10) + self.assertEqual(addi[0], K_OPI) + self.assertEqual(addi[2], 10) + self.assertEqual(ret[0], K_JALR) + self.assertEqual(ret[1], 0) # jalr zero, 0(ra) + + def test_stub_value_roundtrip(self): + # The lui+addi pair must reconstruct every value the probe uses + # (scratch addresses below 2^31, offset by the 12-bit signed addi). + for value in (0, 0x800, 0xFFF, 0x1234, 0x10000000, 0x1FFFFFFF, 0x7FFF_F000): + code = encode_return_stub(value) + hi = struct.unpack("<I", code[0:4])[0] >> 12 + lo = struct.unpack("<I", code[4:8])[0] >> 20 + if lo >= 0x800: + lo -= 0x1000 + self.assertEqual((hi << 12) + lo, value) + + def test_memory_spec_widths(self): + self.assertEqual(decode_memory_spec({"addr": "0x10", "hex": "aabb"}), + (0x10, b"\xaa\xbb")) + self.assertEqual(decode_memory_spec({"addr": "0x10", "u64": 1}), + (0x10, b"\x01" + b"\x00" * 7)) + self.assertEqual(decode_memory_spec({"addr": "0x10", "u32": 2}), + (0x10, b"\x02\x00\x00\x00")) + self.assertEqual(decode_memory_spec({"addr": "0x10", "u16": 3}), + (0x10, b"\x03\x00")) + self.assertEqual(decode_memory_spec({"addr": "0x10", "u8": 4}), + (0x10, b"\x04")) + with self.assertRaises(ValprobeError): + decode_memory_spec({"addr": "0x10"}) + + def test_register_aliases(self): + for name, index in (("s2", 18), ("x18", 18), ("a3", 13), ("t1", 6), ("fp", 8)): + self.assertEqual(reg_index(name), index) + with self.assertRaises(ValprobeError): + reg_index("r99") + + +class SpecTests(unittest.TestCase): + def test_fecs_spec_shape(self): + spec = fecs_spec() + self.assertEqual(spec["format"], SPEC_FORMAT) + self.assertEqual(_parse_u64(spec["target"]), PROMOTE_VALIDATION) + self.assertGreaterEqual(len(spec["variants"]), 15) + for variant in spec["variants"]: + self.assertTrue(variant["label"]) + self.assertIn(variant["expect"], set(spec["outcomes"].values())) + self.assertTrue(variant["stubs"]) + + def test_fecs_spec_layout_stays_inside_ram(self): + spec = fecs_spec() + profile_ram_mb = 12288 + ram_hi = profile_ram_mb * 1024 * 1024 + for variant in spec["variants"]: + for item in variant["memory"]: + self.assertLess(_parse_u64(item["addr"]) + 8, ram_hi) + + def test_missing_spec_is_clean_error(self): + rc, _, err = _run_main(["run", "--spec", "/nonexistent/spec.json"]) + self.assertEqual(rc, 2) + self.assertIn("error:", err) + + +@unittest.skipUnless(os.path.isfile(DEFAULT_IMAGE), "570.144 firmware absent") +class RealImageMatrixTests(unittest.TestCase): + def _reduced_spec(self) -> dict: + spec = fecs_spec() + keep = { + "FECS init vidmem, slot non-NULL", + "FECS init vidmem, slot NULL", + "FECS init sysmem attr 0x6, slot non-NULL", + "FECS no-init mapped, slot non-NULL", + "PM init", + } + spec["variants"] = [v for v in spec["variants"] if v["label"] in keep] + return spec + + def test_matrix_reproduces_live_verdicts_deterministically(self): + spec = self._reduced_spec() + first = run_spec(spec=spec, profile_path=PROFILE, record=False, name="valprobe-test") + second = run_spec(spec=spec, profile_path=PROFILE, record=False, name="valprobe-test") + self.assertEqual(first["format"], MATRIX_FORMAT) + self.assertEqual(first["evaluated"], len(spec["variants"])) + self.assertEqual(first["failed"], 0) + verdicts_a = [(r["label"], r["outcome"]) for r in first["variants"]] + verdicts_b = [(r["label"], r["outcome"]) for r in second["variants"]] + self.assertEqual(verdicts_a, verdicts_b) + by_label = dict(verdicts_a) + self.assertEqual(by_label["FECS init vidmem, slot non-NULL"], + "refuse-fecs-slot-exists-0x1f") + self.assertEqual(by_label["FECS init vidmem, slot NULL"], "accept-commit-path") + self.assertEqual(by_label["FECS no-init mapped, slot non-NULL"], "accept-skip-path") + + def test_probe_reports_firmware_slot_index_and_stub(self): + spec = self._reduced_spec() + spec["variants"] = [v for v in spec["variants"] + if v["label"] == "FECS init vidmem, slot non-NULL"] + matrix = run_spec(spec=spec, profile_path=PROFILE, record=False, name="valprobe-test") + row = matrix["variants"][0] + self.assertEqual(row["format"], PROBE_FORMAT) + self.assertEqual(row["status"], "outcome") + self.assertEqual(row["instructions"], row["instructions"]) + self.assertTrue(row["stubs"]) + slot_index = [r for r in row["reads"] if r.get("label") == "firmware-mapped-slot-index"] + self.assertEqual(slot_index[0]["u32"], 6) # firmware maps FECS -> slot 6 + + def test_boot_to_b3_then_probe(self): + spec = self._reduced_spec() + spec["variants"] = [v for v in spec["variants"] + if v["label"] == "FECS init vidmem, slot non-NULL"] + spec["boot_to"] = "B3" + matrix = run_spec(spec=spec, profile_path=PROFILE, record=False, name="valprobe-test") + row = matrix["variants"][0] + self.assertIsNotNone(row["boot"]) + self.assertEqual(row["boot"]["rung"], "B3") + self.assertTrue(row["boot"]["reached"]) + self.assertEqual(row["outcome"], "refuse-fecs-slot-exists-0x1f") + + def test_matrix_writes_report_json(self): + spec = self._reduced_spec() + spec["variants"] = spec["variants"][:2] + with tempfile.TemporaryDirectory() as tmp: + matrix = run_spec(spec=spec, profile_path=PROFILE, out=tmp, + record=False, name="valprobe-test") + self.assertTrue(os.path.isfile(os.path.join(tmp, "valprobe-matrix.json"))) + with open(matrix["report_json"], "r", encoding="utf-8") as fh: + doc = json.load(fh) + self.assertEqual(doc["format"], MATRIX_FORMAT) + self.assertEqual(len(doc["variants"]), 2) + + def test_cli_sweep_returns_zero(self): + rc, out, _ = _run_main(["sweep", "--no-record"]) + self.assertEqual(rc, 0) + self.assertIn("PASS", out) + + +if __name__ == "__main__": + unittest.main() |
