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

@@ -0,0 +1,13 @@
SIM_ROOT := $(abspath ../../..)
RTL_ROOT := $(abspath $(SIM_ROOT)/../rtl)
include $(SIM_ROOT)/mk/rtl-sources.mk
TOPLEVEL := cam_top
COCOTB_TEST_MODULES := tests.top.no_noise.test_no_noise
VERILOG_SOURCES := $(RTL_CAM_TOP)
# 禁用所有噪声模块
WRITE_NOISE_EN := 0
READ_NOISE_EN := 0
include $(SIM_ROOT)/mk/cocotb-common.mk

View File

@@ -0,0 +1 @@
# CAM top-level no-noise integration tests

View File

@@ -0,0 +1,348 @@
# -*- coding: utf-8 -*-
"""
CAM 顶层cam_topno_noise 配置集成测试WRITE_NOISE_EN=0, READ_NOISE_EN=0
所有噪声模块禁用,验证 CAM 在无噪声下的标准行为。
=== 测试清单 ===
- compile_includes_grouped_noise_helper — 编译冒烟
- baseline_no_noise — 基线检索正确性
- known_hamming_distance — 汉明距离验证
- tie_break_policy — 平局决胜
- all_zero_all_one_boundary — 全 0 / 全 1 边界
- half_duplex_write_priority — 半双工写入优先
- banked_pipeline_no_noise_top1 — 分块流水线 Top-1
- query_scan_blocks_writes_until_result_consumed — 查询阻塞写入
=== 配置背景 ===
本目录固定使用 WRITE_NOISE_EN=0 和 READ_NOISE_EN=0 编译,
因此所有测试无需运行时参数门控——Makefile 保证配置正确。
"""
from __future__ import annotations
import cocotb
import numpy as np
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge
from model.ref_model import (
match_top1,
random_hashes,
)
from tests.top.utils import (
collect_topk,
dut_hash_bits,
dut_num_rows,
query_once,
query_topk_once,
reset_dut,
wait_idle,
write_row,
write_rows,
)
# ═══════════════════════════════════════════════════════════════════════════════
# 编译冒烟测试 — 验证 cam_top 能正确 elaborate
# ═══════════════════════════════════════════════════════════════════════════════
@cocotb.test()
async def compile_includes_grouped_noise_helper(dut):
"""编译测试:验证群组噪声辅助模块被正确包含在 cam_top 中。
不验证功能,只确保 Verilator elaboration 不会报错。
"""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_dut(dut)
assert int(dut.wr_ready.value) in (0, 1)
# ═══════════════════════════════════════════════════════════════════════════════
# 测试 A基线WRITE_NOISE_EN=0, READ_NOISE_EN=0
# ── 验证写+查在噪声关闭时与旧 CAM 行为完全一致
# ═══════════════════════════════════════════════════════════════════════════════
@cocotb.test()
async def baseline_no_noise(dut):
"""基线测试:噪声全部关闭时,检索结果必须与 Python 参考模型完全一致。
验证内容:
1. Top-1 索引和分数与 match_top1 模型一致
2. 查询自身所在行 → 分数必须等于 HASH_BITS完全匹配
3. 串行 Top-K 流的第一个 beat rank=0
4. 最后一个 beat 的 result_last=1流终止
5. top1_index/top1_score 别名与第一个 beat 的值一致
6. score_debug如果存在与模型分数数组逐元素一致
"""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_dut(dut)
num_rows = dut_num_rows(dut)
hash_bits = dut_hash_bits(dut)
rng = np.random.default_rng(1)
rows = random_hashes(rng, num_rows, width=hash_bits)
query_index = min(123, num_rows - 1)
query = rows[query_index]
await write_rows(dut, rows)
beats, top1_index, top1_score, score_debug = await query_topk_once(dut, query)
expected = match_top1(query, rows, width=hash_bits)
assert top1_index == expected.top1_index
assert top1_score == expected.top1_score
assert top1_index == query_index
assert top1_score == hash_bits
# Serial Top-K stream verification: beats from the single query above
assert beats[0][0] == 0, "First beat must have rank 0"
assert beats[-1][3] == 1, "Last beat must assert result_last"
# Verify top1 aliases match first beat after stream fully consumed
assert int(dut.top1_index.value) == beats[0][1]
assert int(dut.top1_score.value) == beats[0][2]
# Verify returned top1 matches first beat rank0
assert top1_index == beats[0][1]
assert top1_score == beats[0][2]
if score_debug is not None:
assert np.array_equal(score_debug, expected.scores)
# ═══════════════════════════════════════════════════════════════════════════════
# 遗留测试 — 仅在噪声关闭时有意义(精确分数才有效)
# ═══════════════════════════════════════════════════════════════════════════════
@cocotb.test()
async def known_hamming_distance(dut):
"""汉明距离验证:写入已知模式的哈希,验证分数计算正确。
测试数据:
- Row 0: 全 0
- Row 10: 低 7 位为 1 → score = hash_bits - 7
- Row 11: 低 31 位为 1 → score = hash_bits - 31
- Row 12: 低 128 位为 1→ score = hash_bits - 128
- query = 0
验证Top-1 为 row 0完全匹配各行的 score_debug
精确等于理论汉明距离对应的匹配位数。
"""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_dut(dut)
num_rows = dut_num_rows(dut)
hash_bits = dut_hash_bits(dut)
query = 0
rows = [0] * num_rows
rows[min(10, num_rows - 1)] = (1 << 7) - 1
rows[min(11, num_rows - 1)] = (1 << 31) - 1
rows[min(12, num_rows - 1)] = (1 << 128) - 1
await write_rows(dut, rows)
top1_index, top1_score, score_debug = await query_once(dut, query)
assert top1_index == 0
assert top1_score == hash_bits
if score_debug is not None:
assert int(score_debug[min(10, num_rows - 1)]) == hash_bits - 7
assert int(score_debug[min(11, num_rows - 1)]) == hash_bits - 31
assert int(score_debug[min(12, num_rows - 1)]) == hash_bits - 128
@cocotb.test()
async def tie_break_policy(dut):
"""平局决胜策略:分数相同时,行号最小的获胜。
设置:
- row 10, 20, 200 都存储了 query 的值(满分匹配)
- 其余行随机填充
预期top1_index = 10不是 20 或 200
"""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_dut(dut)
num_rows = dut_num_rows(dut)
hash_bits = dut_hash_bits(dut)
rng = np.random.default_rng(2)
rows = random_hashes(rng, num_rows, width=hash_bits)
query = rows[min(200, num_rows - 1)]
rows[10] = query
rows[20] = query
rows[min(200, num_rows - 1)] = query
await write_rows(dut, rows)
top1_index, top1_score, _ = await query_once(dut, query)
assert top1_index == 10
assert top1_score == hash_bits
@cocotb.test()
async def all_zero_all_one_boundary(dut):
"""全 0 / 全 1 边界测试:验证极端哈希值的检索正确性。
存储:
- row 0: 全 0 (0x000...000)
- row 1: 全 1 (0xFFF...FFF)
- 查询 = 全 0
预期Top-1 = row 0, score = hash_bits
row 1 的 score = 0全 0 与全 1 无任何匹配位)
"""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_dut(dut)
num_rows = dut_num_rows(dut)
hash_bits = dut_hash_bits(dut)
rows = [0] * num_rows
rows[0] = 0
rows[1] = (1 << hash_bits) - 1
query = 0
await write_rows(dut, rows)
top1_index, top1_score, score_debug = await query_once(dut, query)
assert top1_score == hash_bits
assert top1_index == 0
if score_debug is not None:
assert int(score_debug[0]) == hash_bits
assert int(score_debug[1]) == 0
# ═══════════════════════════════════════════════════════════════════════════════
# 测试 F半双工写入优先级仲裁
# ── wr_valid 和 query_valid 同时有效 → 写入优先,查询被暂缓
# ═══════════════════════════════════════════════════════════════════════════════
@cocotb.test()
async def half_duplex_write_priority(dut):
"""半双工仲裁:同时发起写入和查询 → 写入必须胜出。
流程:
1. 预先写入 row 0值为 test_val
2. 同时驱动 wr_valid 和 query_valid写入 row 1查询 test_val
3. 断言写入被接受row 1 被正确写入
4. 随后查询 test_val → 应返回 row 0 和 row 1 都是满分
如果仲裁逻辑错误(查询胜出或同时处理),两个行中至少有一个会丢失。
"""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_dut(dut)
hash_bits = dut_hash_bits(dut)
test_val = (1 << hash_bits) - 1
await write_row(dut, 0, test_val)
await wait_idle(dut)
assert int(dut.wr_ready.value) == 1
assert int(dut.query_ready.value) == 1
dut.wr_valid.value = 1
dut.wr_addr.value = 1
dut.write_hash.value = 0
dut.query_valid.value = 1
dut.query_hash.value = test_val
await RisingEdge(dut.clk)
dut.wr_valid.value = 0
dut.query_valid.value = 0
await wait_idle(dut)
top1_index, top1_score, _ = await query_once(dut, test_val)
assert top1_index == 0
assert top1_score == hash_bits
top1_index, top1_score, _ = await query_once(dut, 0)
assert top1_index == 1
assert top1_score == hash_bits
# ═══════════════════════════════════════════════════════════════════════════════
# 测试 G分块流水线无噪声 Top-1
# ── 验证分块存储架构在无噪声时返回正确的 Top-1
# ═══════════════════════════════════════════════════════════════════════════════
@cocotb.test()
async def banked_pipeline_no_noise_top1(dut):
"""分块流水线 Top-1无噪声时分块架构的结果必须与纯模型一致。
这是 banked_pipeline 的冒烟测试——验证分块存储核心、
通道合并逻辑、以及匹配引擎流水线在端到端场景中协同工作。
"""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_dut(dut)
num_rows = dut_num_rows(dut)
hash_bits = dut_hash_bits(dut)
rng = np.random.default_rng(7)
rows = random_hashes(rng, num_rows, width=hash_bits)
query_index = min(17, num_rows - 1)
query = rows[query_index]
await write_rows(dut, rows)
top1_index, top1_score, score_debug = await query_once(dut, query)
expected = match_top1(query, rows, width=hash_bits)
assert top1_index == expected.top1_index
assert top1_score == expected.top1_score
assert top1_index == query_index
# ═══════════════════════════════════════════════════════════════════════════════
# 测试 H查询扫描期间阻塞写入
# ── 活跃的查询扫描会撤销 wr_ready直到结果被消费完毕
# ═══════════════════════════════════════════════════════════════════════════════
@cocotb.test()
async def query_scan_blocks_writes_until_result_consumed(dut):
"""半双工阻塞查询扫描活跃期间wr_ready 必须保持低电平。
流程:
1. 写入全部行
2. 发起查询query_valid=1
3. 在结果被消费之前,尝试写入 → 断言 wr_ready=0
4. 消费完整结果流 → DUT 回到空闲
这验证了半双工协议中「读期间禁止写」的约束。
"""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_dut(dut)
num_rows = dut_num_rows(dut)
hash_bits = dut_hash_bits(dut)
rows = [0] * num_rows
rows[0] = (1 << hash_bits) - 1
await write_rows(dut, rows)
await wait_idle(dut)
dut.query_hash.value = rows[0]
dut.query_valid.value = 1
await RisingEdge(dut.clk)
dut.query_valid.value = 0
dut.wr_valid.value = 1
dut.wr_addr.value = 1
dut.write_hash.value = 0
await RisingEdge(dut.clk)
assert int(dut.wr_ready.value) == 0
dut.wr_valid.value = 0
# Consume full serial stream so the DUT returns idle
beats = await collect_topk(dut, timeout_cycles=2000)
assert len(beats) > 0
assert beats[-1][3] == 1 # last asserted