# -*- coding: utf-8 -*- """ CAM 顶层(cam_top)no_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_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, 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=max(2000, (dut_num_rows(dut) // dut_lanes(dut)) * 24 + 100) ) assert len(beats) > 0 assert beats[-1][3] == 1 # last asserted