Files
retroSoC/tests/test_publication_registers.py
T
maksyuki 3ac9e73ab9 feat: refactor tests for mailbox and environment validation
- Updated mailbox purpose tests to reflect new write order and sequence handling.
- Added tests to ensure Linux-ready values match LP contract and constants match Linux helper.
- Introduced tests to verify that offset changes do not hide stale Linux helpers.
- Enhanced environment tests to report current locked launcher and runtime versions.
- Added checks for SBT launcher presence in environment labels.
- Improved full diagram tests to validate storage geometry and RSA vector configurations.
- Expanded register tests to ensure crypto interrupt registers publish all events and lifecycle annotations preserve security boundaries.
- Modified script tools tests to include Vexii generation validation and updated workflow assertions.
2026-09-30 10:51:04 +08:00

194 lines
8.1 KiB
Python

"""Publication register data must retain real ABI values and complete bit layouts."""
from __future__ import annotations
import copy
import re
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from publications.register_reference import ( # noqa: E402
collect_registers,
number,
refine_fields,
source_model,
validate_reference,
)
@pytest.fixture(scope="module")
def reference():
if not (ROOT / "rtl/managed/clusterip/rtc/rtl/rtc_reg.sv").exists():
pytest.skip("publication setup is required for managed IP documentation")
return collect_registers()
def test_current_apu_interface_declares_p5_default_and_p9_release(reference):
registers = {r["name"]: r for r in reference["apu"]["registers"]}
constants = (ROOT / "crt/include/retrosoc/hal/apu_regs.h").read_text(
encoding="utf-8"
)
assert re.search(r"RS_APU_IP_VERSION_VALUE\s+UINT32_C\(0x00010002\)", constants)
assert re.search(r"RS_APU_CAPABILITY0_P5_IMPLEMENTED\s+UINT32_C\(0x000001BD\)", constants)
assert re.search(r"RS_APU_CAPABILITY0_P7_IMPLEMENTED\s+UINT32_C\(0x000003FD\)", constants)
assert re.search(r"RS_APU_CAPABILITY1_IMPLEMENTED\s+UINT32_C\(0x01827020\)", constants)
assert "V1.2" in registers["IP_VERSION"]["description"]
assert "32 KiB" in registers["CAPABILITY1"]["description"]
def test_repeated_banks_keep_their_real_instance_ranges(reference):
usb = {g["id"]: g for g in reference["usb2"]["groups"]}
assert (usb["endpoint"]["base"], usb["endpoint"]["stride"], usb["endpoint"]["count"]) == (
0x200,
0x40,
8,
)
assert (usb["channel"]["base"], usb["channel"]["stride"], usb["channel"]["count"]) == (
0x500,
0x40,
16,
)
clint = {g["id"]: g for g in reference["aclint"]["groups"]}
assert clint["compare"]["count"] == 2
assert clint["compare"]["stride"] == 8
def test_gpio_windows_are_not_collapsed_into_one_register_table(reference):
values = {r["key"]: r for r in reference["gpio"]["registers"]}
assert values["user.INTR_STATE"]["offset"] == 0x14
assert values["admin.INTR_STATE"]["offset"] == 0x60
def test_field_overlap_and_missing_bits_are_rejected(reference):
specimen = {"uart": copy.deepcopy(reference["uart"])}
specimen["uart"]["registers"][0]["fields"][0]["msb"] = 32
with pytest.raises(ValueError, match="bit layout"):
validate_reference(specimen)
specimen = {"uart": copy.deepcopy(reference["uart"])}
specimen["uart"]["registers"][0]["fields"].pop()
with pytest.raises(ValueError, match="bit layout"):
validate_reference(specimen)
def test_duplicate_offsets_are_rejected_inside_a_bank(reference):
specimen = {"uart": copy.deepcopy(reference["uart"])}
specimen["uart"]["registers"][1]["offset"] = specimen["uart"]["registers"][0]["offset"]
with pytest.raises(ValueError, match="duplicate or unaligned"):
validate_reference(specimen)
def test_reset_disagreement_and_field_overflow_are_rejected(reference):
specimen = {"uart": copy.deepcopy(reference["uart"])}
register = specimen["uart"]["registers"][0]
register["reset"] = "0x100000000"
with pytest.raises(ValueError, match="reset outside"):
validate_reference(specimen)
register["reset"] = register["rtl_reset"]
register["fields"][0]["reset"] = "0x100000000"
with pytest.raises(ValueError, match="reset outside"):
validate_reference(specimen)
def test_constant_reader_does_not_execute_source_text():
assert number("8'(DEPTH / 2)", {"DEPTH": 16}) == 8
assert number("4'(MaxBurstBeats)", {"MaxBurstBeats": 16}) == 0
assert number("(EnableStreams ? 3'd3 : 3'd0)", {"EnableStreams": 1}) == 3
assert number("(EnableStreams ? 3'd3 : 3'd0)", {"EnableStreams": 0}) == 0
assert number("(DEPTH > 4) ? 32'd4 : DEPTH - 1", {"DEPTH": 16}) == 4
assert number("(32'd1 << 0) |\n (32'd1 << 8)") == 0x101
with pytest.raises(ValueError):
number("__import__('os').system('echo forbidden')")
def test_composite_fields_and_multiline_capability_keep_the_real_layout(reference):
usb = {r["key"]: r for r in reference["usb2"]["registers"]}
assert usb["main.CAPABILITY0"]["rtl_reset"] == "0x0F071FFF"
fields = {f["name"]: (f["lsb"], f["msb"]) for f in usb["endpoint.STATUS"]["fields"]}
assert fields["RESULT"] == (4, 7)
assert fields["IN_TOGGLE"] == (8, 8)
apu = next(r for r in reference["apu"]["registers"] if r["key"] == "main.ERROR_STATUS")
assert next(f for f in apu["fields"] if f["name"] == "CODE")["msb"] == 6
for item in reference.values():
for register in item["registers"]:
for field in register["fields"]:
assert ";" not in field["name"]
if "BEGIN" in field["name"]:
assert (item["id"], register["key"], field["name"]) == (
"crypto", "main.MEM_CONTROL", "BEGIN"
)
def test_read_predicate_is_not_mistaken_for_a_field_assignment():
model = source_model("logic [31:0] state; if (state[3:0] == 4'd2) begin error = 1'b1; end", {})
field = dict(lsb=0, msb=31, name="STATE", expression="state", reset="0", description="State.")
record = dict(symbol="CTRL", name="CTRL", fields=[field])
assert refine_fields(record, {}, {}, model) == [field]
def test_secret_apertures_do_not_claim_readback(reference):
for entry in reference["crypto"]["registers"]:
if entry["group"] in {"aes_key", "rsa_exponent"}:
assert entry["access"].startswith("WO")
assert "PSLVERR" in entry["description"]
def test_crypto_interrupt_registers_publish_all_six_events(reference):
registers = {entry["key"]: entry for entry in reference["crypto"]["registers"]}
expected = ["AES_DONE", "SHA_DONE", "RSA_DONE", "ERROR", "ZEROIZED", "MEMORY_READY"]
for key in ("main.IRQ_STATE", "main.IRQ_ENABLE", "main.IRQ_TEST"):
fields = registers[key]["fields"]
assert [field["name"] for field in fields[:6]] == expected
assert [(field["lsb"], field["msb"]) for field in fields[:6]] == [
(bit, bit) for bit in range(6)
]
assert (fields[6]["name"], fields[6]["lsb"], fields[6]["msb"]) == (
"Reserved", 6, 31
)
def test_crypto_lifecycle_annotations_preserve_security_boundaries(reference):
registers = {entry["key"]: entry for entry in reference["crypto"]["registers"]}
mem_status = {field["name"]: field for field in registers["main.MEM_STATUS"]["fields"]}
lock_description = mem_status["TABLE_LOCKED"]["description"]
assert "hard/PCLK reset" in lock_description
assert "zeroize does not unlock" in lock_description
irq_test = {field["name"]: field for field in registers["main.IRQ_TEST"]["fields"]}
test_description = irq_test["MEMORY_READY"]["description"]
assert "interrupt-path testing" in test_description
assert "not initialization or physical-erasure completion evidence" in test_description
def test_exact_register_description_wins_over_grouped_name_fragments(reference):
status = next(r for r in reference["xpi"]["registers"] if r["key"] == "main.STATUS")
assert "enable/busy/FIFO" in status["description"]
assert "W1C state" not in status["description"]
def test_api_listing_contains_declarations_not_inline_function_bodies():
import json
content = json.loads((ROOT / "publications/datasheets/ip-content.json").read_text())
for chapter in content.values():
for api in chapter.get("api", []):
assert "{" not in api["signature"]
assert "#if" not in api["signature"]
assert api["signature"].endswith(");")
def test_ip_page_validation_rejects_a_shared_start_page():
from publications.build_datasheet import validate_page_map
items = [
{"id": "a", "kind": "ip-start", "page": 1},
{"id": "a", "kind": "ip-end", "page": 1},
{"id": "b", "kind": "ip-start", "page": 1},
{"id": "b", "kind": "ip-end", "page": 2},
]
with pytest.raises(ValueError, match="new page"):
validate_page_map(items, [{"id": "a"}, {"id": "b"}])