Files
Mini-Nav/hw/sim/tests/top/no_noise/test_no_noise.py
SikongJueluo 424cf6e1d3 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
2026-05-21 21:22:12 +08:00

349 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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