Files
Mini-Nav/hw/sim/tests/top/no_noise/test_no_noise.py
SikongJueluo 8b4d4c1b57 refactor(cam): remove read noise from noise architecture (Phase 2)
- Make cam_read_noise a pass-through module, removing all noise injection logic
- Switch write noise to use noise_mask_bernoulli instead of noise_mask_grouped
- Add state machine to cam_write_noise for mask generation timing
- Remove noise_mask_grouped.sv (no longer needed)
- Remove read noise parameters from cam_noisy and cam_top
- Update simulation and benchmark code to reflect read noise removal
- Sync documentation to reflect Phase 2 architecture
2026-05-26 23:45:52 +08:00

351 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
所有噪声模块禁用,验证 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 编译,
因此所有测试无需运行时参数门控——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_lanes,
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
# ── 验证写+查在噪声关闭时与旧 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=max(2000, (dut_num_rows(dut) // dut_lanes(dut)) * 24 + 100)
)
assert len(beats) > 0
assert beats[-1][3] == 1 # last asserted