refactor(hw/sim): reorganize CAM top-level tests into per-noise-config suites

Split the monolithic test_cam_basic.py into separate test suites
organized by noise configuration (no_noise, write_noise, read_noise),
with shared utilities extracted to tests/top/utils.py.

- Remove test_cam_basic.py; add no_noise/, write_noise/, read_noise/
  test suites with Makefiles that set noise parameters statically
- Extract helpers (reset_dut, write_rows, query_once, collect_topk,
  etc.) into tests/top/utils.py
- Update hw/sim/Makefile with per-config test targets and a
  test-top-all meta-target
- Update scripts/run_cam_correctness.py to build per-directory
  instead of centrally, removing inline parameter overrides
- Add __init__.py for result_serializer and topk_tracker module tests
- Expand docstrings in test_ref_model_noise.py with architectural
  context and test rationale
This commit is contained in:
2026-05-21 21:19:33 +08:00
parent 5a1d3ea977
commit 424cf6e1d3
16 changed files with 1197 additions and 712 deletions

View File

@@ -64,7 +64,7 @@ default_scenarios: list[Scenario] = [
Scenario(
name="cam_top_basic",
kind="cocotb",
module="tests.test_cam_basic",
module="tests.top.no_noise.test_no_noise",
testcase=None,
params={},
noise_mode="none",
@@ -73,28 +73,18 @@ default_scenarios: list[Scenario] = [
Scenario(
name="cam_top_write_noise",
kind="cocotb",
module="tests.test_cam_basic",
module="tests.top.write_noise.test_write_noise",
testcase=None,
params={
"WRITE_NOISE_EN": "1",
"WRITE_NOISE_RATE_NUM": "1",
"WRITE_NOISE_RATE_DEN": "100",
"READ_NOISE_EN": "0",
},
params={},
noise_mode="write_noise",
expected="bit_flip_injected",
),
Scenario(
name="cam_top_read_noise",
kind="cocotb",
module="tests.test_cam_basic",
module="tests.top.read_noise.test_read_noise",
testcase=None,
params={
"WRITE_NOISE_EN": "0",
"READ_NOISE_EN": "1",
"READ_NOISE_RATE_NUM": "1",
"READ_NOISE_RATE_DEN": "100",
},
params={},
noise_mode="read_noise",
expected="read_perturbed",
),
@@ -150,19 +140,28 @@ default_scenarios: list[Scenario] = [
# Command builders
# ---------------------------------------------------------------------------
# Internal: map unit scenario names to their Verilog TOPLEVEL module.
# cam_top_* scenarios use the Makefile default (cam_top) and are omitted.
_UNIT_TOPLEVEL: dict[str, str] = {
"cam_core_banked": "cam_core_banked",
"match_engine_pipeline": "match_engine_pipeline",
"cam_write_noise": "cam_write_noise",
"cam_read_noise": "cam_read_noise",
# Map scenario names to their Makefile directory under hw/sim/tests/.
# Top-level cam_top tests are now split by noise configuration.
_MAKE_DIR: dict[str, str] = {
"cam_top_basic": "top/no_noise",
"cam_top_write_noise": "top/write_noise",
"cam_top_read_noise": "top/read_noise",
"cam_core_banked": "modules/cam_core_banked",
"match_engine_pipeline": "modules/match_engine_pipeline",
"cam_write_noise": "modules/cam_write_noise",
"cam_read_noise": "modules/cam_read_noise",
}
def _infer_toplevel(scenario: Scenario) -> str | None:
"""Return the Verilog TOPLEVEL for *scenario* or None to use Makefile default."""
return _UNIT_TOPLEVEL.get(scenario.name)
def _make_dir_for(scenario: Scenario) -> str:
"""Return the relative Makefile directory under hw/sim/tests/ for *scenario*."""
if scenario.name in _MAKE_DIR:
return _MAKE_DIR[scenario.name]
# Fallback: guess from module path (e.g. tests.top.foo → top/foo)
parts = scenario.module.split(".") if scenario.module else []
if len(parts) >= 4 and parts[0] == "tests":
return "/".join(parts[1:-1]) # tests.top.no_noise → top/no_noise
return "."
def build_scenario_command(
@@ -178,15 +177,16 @@ def build_scenario_command(
"uv",
"run",
"pytest",
"hw/sim/tests/test_ref_model_noise.py",
"hw/sim/tests/model/test_ref_model_noise.py",
"-q",
]
# cocotb
# cocotb — run make in the specific test subdirectory
make_dir = f"hw/sim/tests/{_make_dir_for(scenario)}"
cmd = [
"make",
"-C",
"hw/sim",
make_dir,
f"NUM_ROWS={num_rows}",
f"HASH_BITS={hash_bits}",
f"LANES={lanes}",
@@ -196,9 +196,6 @@ def build_scenario_command(
cmd.append(f"COCOTB_TESTCASE={scenario.testcase}")
for key, value in scenario.params.items():
cmd.append(f"{key}={value}")
toplevel = _infer_toplevel(scenario)
if toplevel is not None:
cmd.append(f"TOPLEVEL={toplevel}")
return cmd
@@ -206,7 +203,8 @@ def build_clean_command(scenario: Scenario) -> list[str] | None:
"""Build a clean command or None if cleanup is not needed."""
if scenario.kind == "pytest":
return None
return ["make", "-C", "hw/sim", "clean"]
make_dir = f"hw/sim/tests/{_make_dir_for(scenario)}"
return ["make", "-C", make_dir, "clean"]
# ---------------------------------------------------------------------------