mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
- Updated fault reference documentation to clarify Linux termination behavior and NPU-P6 requirements. - Enhanced Linux runtime section with emphasis on platform setup and kernel handoff processes. - Revised overview section to include detailed descriptions of NPU capabilities and memory configurations. - Improved product use documentation to specify Linux rootfs behavior and acceptance checkpoints. - Clarified reset summary to differentiate between Linux and smoke/NPU-P6 acceptance processes. - Updated software section to reflect changes in Linux image loading and readiness protocols. - Modified build scripts to support RV64 architecture and ensure proper ELF requirements. - Enhanced NPU compiler to support cycle counting for RV64 and RV32 architectures. - Added tests to validate new configurations and ensure compliance with updated publication contracts.
197 lines
9.5 KiB
Python
197 lines
9.5 KiB
Python
"""Prevent publication drift in HP acceptance and ready-only Linux integration."""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from publications import boot_acceptance_reference as br
|
|
from publications.software_reference import function_body
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
@pytest.fixture
|
|
def source_tree(tmp_path):
|
|
for relative in br.SOURCES:
|
|
target = tmp_path / relative
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(ROOT / relative, target)
|
|
return tmp_path
|
|
|
|
|
|
def replace(root: Path, relative: str, old: str, new: str) -> None:
|
|
path = root / relative
|
|
text = path.read_text(encoding="utf-8")
|
|
assert old in text
|
|
path.write_text(text.replace(old, new), encoding="utf-8")
|
|
|
|
|
|
def test_actual_boot_requires_bounded_complete_acceptance():
|
|
result = br.collect_boot_acceptance(ROOT, function_body)
|
|
assert result["event_poll_iterations"] == 64000000
|
|
assert result["npu_defines"] == ["RS_NPU_P5_ACCEPTANCE", "RS_NPU_P6_ACCEPTANCE"]
|
|
assert [(row["event"], row["argument"], row["sequence"]) for row in result["messages"]] == [
|
|
(1, 0x4C4E5801, 1), (2, 0x47413244, 2), (4, 0x43424F4B, 3)]
|
|
assert result["rootfs_ready_only"]
|
|
assert result["linux_ready_is_terminal"]
|
|
assert result["smoke_terminal_requires_cache_handoff"]
|
|
assert result["npu_p6_reuses_linux_transport"]
|
|
assert result["failure_reset_requires_no_hp_ga2d_owner"]
|
|
|
|
|
|
def test_mailbox_purposes_follow_registers_in_the_actual_write_order():
|
|
rows = br.ready_mailbox(ROOT)
|
|
assert [(row["address"], row["register"], row["purpose"]) for row in rows] == [
|
|
("0x10019020", "HP_EVENT", "Linux-ready event"),
|
|
("0x10019024", "HP_ARG0", "Ready-state argument"),
|
|
("0x10019028", "HP_SEQUENCE", "Publication sequence"),
|
|
("0x1001902C", "HP_DOORBELL", "LP interrupt request"),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("relative,old,new,match", [
|
|
(br.HAL_SOURCE, "RS_HP_MAILBOX_HP_EVENT_OFFSET UINT32_C(0x020)",
|
|
"RS_HP_MAILBOX_HP_EVENT_OFFSET UINT32_C(0x024)", "HAL/RTL"),
|
|
(br.HAL_SOURCE, "message->code = *rs_hp_mailbox_register(RS_HP_MAILBOX_HP_EVENT_OFFSET);",
|
|
"message->code = *rs_hp_mailbox_register(RS_HP_MAILBOX_HP_ARG0_OFFSET);", "field semantics"),
|
|
(br.MAILBOX_SOURCE, "`APB4_HP_MAILBOX__HP_DOORBELL: s_lp_intr_state_d = 1'b1;",
|
|
"`APB4_HP_MAILBOX__HP_DOORBELL: s_hp_intr_state_d = 1'b1;", "LP interrupt"),
|
|
(br.READY_SOURCE, "for (;;) {", "return 0;\n for (;;) {", "ready-only"),
|
|
(br.READY_SOURCE, "mailbox[11] = 1U;", "mailbox[8] = 2U;\n mailbox[11] = 1U;", "sequence"),
|
|
])
|
|
def test_mailbox_or_service_drift_requires_new_review(source_tree, relative, old, new, match):
|
|
replace(source_tree, relative, old, new)
|
|
with pytest.raises(ValueError, match=match):
|
|
br.ready_mailbox(source_tree)
|
|
|
|
|
|
@pytest.mark.parametrize("old,new", [
|
|
("mailbox[8] = (error == 0U) ? 1U : 3U;", "mailbox[8] = (error == 0U) ? 2U : 3U;"),
|
|
("UINT32_C(0x4C4E5801) : error", "UINT32_C(0x4C4E5802) : error"),
|
|
("mailbox[10] = 1U;", "mailbox[10] = 2U;"),
|
|
("mailbox[11] = 1U;", "mailbox[11] = 2U;"),
|
|
])
|
|
def test_each_linux_ready_value_must_match_the_lp_contract(source_tree, old, new):
|
|
replace(source_tree, br.READY_SOURCE, old, new)
|
|
with pytest.raises(ValueError, match="values differ"):
|
|
br.ready_mailbox(source_tree)
|
|
|
|
|
|
@pytest.mark.parametrize("old,new", [
|
|
("RS_HP_BOOT_READY_EVENT UINT32_C(1)",
|
|
"RS_HP_BOOT_READY_EVENT UINT32_C(2)"),
|
|
("RS_HP_BOOT_READY_ARG UINT32_C(0x4C4E5801)",
|
|
"RS_HP_BOOT_READY_ARG UINT32_C(0x4C4E5802)"),
|
|
("RS_HP_BOOT_MAILBOX_READY_SEQUENCE UINT32_C(1)",
|
|
"RS_HP_BOOT_MAILBOX_READY_SEQUENCE UINT32_C(2)"),
|
|
])
|
|
def test_each_lp_ready_constant_must_match_the_linux_helper(source_tree, old, new):
|
|
replace(source_tree, br.BOOT_SOURCE, old, new)
|
|
with pytest.raises(ValueError, match="values differ"):
|
|
br.ready_mailbox(source_tree)
|
|
|
|
|
|
def test_coordinated_hal_rtl_offset_change_cannot_hide_a_stale_linux_helper(source_tree):
|
|
replace(source_tree, br.HAL_SOURCE,
|
|
"RS_HP_MAILBOX_HP_EVENT_OFFSET UINT32_C(0x020)",
|
|
"RS_HP_MAILBOX_HP_EVENT_OFFSET UINT32_C(0x024)")
|
|
replace(source_tree, br.REGISTER_SOURCE,
|
|
"`define APB4_HP_MAILBOX__HP_EVENT 12'h020",
|
|
"`define APB4_HP_MAILBOX__HP_EVENT 12'h024")
|
|
with pytest.raises(ValueError, match="addresses differ"):
|
|
br.ready_mailbox(source_tree)
|
|
|
|
|
|
def test_rtl_doorbell_offset_change_cannot_hide_a_stale_linux_helper(source_tree):
|
|
replace(source_tree, br.REGISTER_SOURCE,
|
|
"`define APB4_HP_MAILBOX__HP_DOORBELL 12'h02C",
|
|
"`define APB4_HP_MAILBOX__HP_DOORBELL 12'h050")
|
|
with pytest.raises(ValueError, match="addresses differ"):
|
|
br.ready_mailbox(source_tree)
|
|
|
|
|
|
@pytest.mark.parametrize("old,new,match", [
|
|
("timeout < RS_HP_BOOT_EVENT_TIMEOUT; ++timeout", "; ++timeout", "bounded poll"),
|
|
("if (status == 1U)", "if (status != 1U)", "token/error"),
|
|
("if (!s_hp_boot_ga2d_owned_by_hp)", "if (s_hp_boot_ga2d_owned_by_hp)", "reset ownership"),
|
|
("rs_hp_boot_fail(UINT8_C(13));", "rs_hp_boot_fail(UINT8_C(11));", "completion condition"),
|
|
("cache_clean_completed = true;", "cache_clean_completed = false;", "phase or ordering"),
|
|
("rs_resource_set_owner(RS_RESOURCE_GA2D, RS_RESOURCE_OWNER_LP, false)",
|
|
"rs_resource_set_owner(RS_RESOURCE_GA2D, RS_RESOURCE_OWNER_HP, false)", "phase or ordering"),
|
|
("#if defined(RS_NPU_P5_ACCEPTANCE) || defined(RS_NPU_P6_ACCEPTANCE)",
|
|
"#if 1", "optional NPU"),
|
|
])
|
|
def test_inaccurate_boot_protocol_claims_fail_collection(source_tree, old, new, match):
|
|
replace(source_tree, br.BOOT_SOURCE, old, new)
|
|
with pytest.raises(ValueError, match=match):
|
|
br.collect_boot_acceptance(source_tree, function_body)
|
|
|
|
|
|
def test_ready_marker_cannot_become_terminal_success_unnoticed(source_tree):
|
|
replace(source_tree, br.BOOT_SOURCE, 'printf("HP_LINUX_READY\\n");',
|
|
'printf("HP_LINUX_READY\\n");\n rs_test_finish(RS_TEST_PASSED, UINT8_C(0));')
|
|
with pytest.raises(ValueError, match="terminal pass"):
|
|
br.collect_boot_acceptance(source_tree, function_body)
|
|
|
|
|
|
@pytest.mark.parametrize("old,new", [
|
|
("if (rs_hp_boot_wait_hp_held())", "if (!rs_hp_boot_wait_hp_held())"),
|
|
("rs_resource_acknowledge_cache_clean() == RS_OK", "rs_resource_acknowledge_cache_clean() != RS_OK"),
|
|
("if (rs_hp_boot_wait_cache_request())", "if (!rs_hp_boot_wait_cache_request())"),
|
|
("if (rs_hp_boot_wait_message(RS_HP_BOOT_GA2D_CACHE_EVENT,", "if (!rs_hp_boot_wait_message(RS_HP_BOOT_GA2D_CACHE_EVENT,"),
|
|
])
|
|
def test_inverted_cache_success_guard_cannot_qualify_handoff(source_tree, old, new):
|
|
replace(source_tree, br.BOOT_SOURCE, old, new)
|
|
with pytest.raises(ValueError, match="positive cache-handoff"):
|
|
br.collect_boot_acceptance(source_tree, function_body)
|
|
|
|
|
|
@pytest.mark.parametrize("function,old,new,match", [
|
|
("rs_hp_boot_wait_hp_held", "&& hp_status.reset_asserted", "", "held-state"),
|
|
("rs_hp_boot_wait_hp_held", "!hp_status.released", "hp_status.released", "held-state"),
|
|
("rs_hp_boot_wait_hp_held", "!hp_status.draining", "hp_status.draining", "held-state"),
|
|
("rs_hp_boot_wait_hp_held", "!hp_status.forced_fault &&", "", "held-state"),
|
|
("rs_hp_boot_wait_hp_held", "rs_hp_boot_ga2d_idle(RS_RESOURCE_OWNER_HP)",
|
|
"rs_hp_boot_ga2d_idle(RS_RESOURCE_OWNER_LP)", "held-state"),
|
|
("rs_hp_boot_wait_hp_held", "|| defined(RS_NPU_P6_ACCEPTANCE)", "", "held-state NPU"),
|
|
("rs_hp_boot_wait_hp_held", "&& rs_hp_boot_npu_idle(RS_RESOURCE_OWNER_HP)",
|
|
"|| rs_hp_boot_npu_idle(RS_RESOURCE_OWNER_HP)", "held-state NPU"),
|
|
("rs_hp_boot_wait_cache_request", "cache_status.request && !cache_status.clean",
|
|
"cache_status.request && cache_status.clean", "cache request/clean"),
|
|
("rs_hp_boot_ga2d_idle", "!ga2d_status.draining &&", "", "ga2d safe-idle"),
|
|
("rs_hp_boot_npu_idle", "RS_NPU_STATUS_BUSY | RS_NPU_STATUS_DRAINING | RS_NPU_STATUS_RECOVERING",
|
|
"RS_NPU_STATUS_BUSY | RS_NPU_STATUS_DRAINING", "npu safe-idle"),
|
|
])
|
|
def test_weakened_held_observation_cannot_qualify_handoff(source_tree, function, old, new, match):
|
|
path = source_tree / br.BOOT_SOURCE
|
|
source = path.read_text(encoding="utf-8")
|
|
body = function_body(source, function)
|
|
assert old in body
|
|
path.write_text(source.replace(body, body.replace(old, new), 1), encoding="utf-8")
|
|
with pytest.raises(ValueError, match=match):
|
|
br.collect_boot_acceptance(source_tree, function_body)
|
|
|
|
|
|
def test_poll_budget_is_derived_without_inventing_time_units(source_tree):
|
|
replace(source_tree, br.STATUS_SOURCE, "((rs_timeout_t)1000000U)", "((rs_timeout_t)2000000U)")
|
|
result = br.collect_boot_acceptance(source_tree, function_body)
|
|
assert result["default_poll_iterations"] == 2000000
|
|
assert result["event_poll_iterations"] == 128000000
|
|
assert not any("milliseconds" in key or "seconds" in key for key in result)
|
|
|
|
|
|
def test_mailbox_address_is_derived_from_the_memory_map(source_tree):
|
|
path = source_tree / br.MAP_SOURCE
|
|
source = json.loads(path.read_text(encoding="utf-8"))
|
|
mailbox = next(row for row in source["regions"] if row["symbol"] == "APB4_HP_MAILBOX")
|
|
mailbox["base"] = "0x1001A000"
|
|
path.write_text(json.dumps(source), encoding="utf-8")
|
|
replace(source_tree, br.READY_SOURCE, "0x10019000", "0x1001A000")
|
|
rows = br.ready_mailbox(source_tree)
|
|
assert rows[0]["address"] == "0x1001A020"
|
|
assert rows[0]["purpose"] == "Linux-ready event"
|