fix(pipeline): add S_WAIT_READ_RESP state to fix read-noise PRNG timing

- Emit rd_valid_o for exactly one combinational cycle before waiting for
  read response, ensuring the read-noise PRNG advances once per batch issue
- Fix query handshake: wait for query_ready before asserting query_valid
  to avoid valid&&ready handshake drops on clock edges
- Add dynamic timeout estimation in test utilities based on DUT parameters
- Update test-top Makefile to run all noise configurations by default
- Remove uv run prefix from cocotb-config Makefile invocation
This commit is contained in:
2026-05-22 15:06:26 +08:00
parent 424cf6e1d3
commit 29f4cc91f6
7 changed files with 60 additions and 26 deletions

View File

@@ -35,6 +35,7 @@ from model.ref_model import (
from tests.top.utils import (
collect_topk,
dut_hash_bits,
dut_lanes,
dut_num_rows,
query_once,
query_topk_once,
@@ -44,7 +45,6 @@ from tests.top.utils import (
write_rows,
)
# ═══════════════════════════════════════════════════════════════════════════════
# 编译冒烟测试 — 验证 cam_top 能正确 elaborate
# ═══════════════════════════════════════════════════════════════════════════════
@@ -343,6 +343,8 @@ async def query_scan_blocks_writes_until_result_consumed(dut):
dut.wr_valid.value = 0
# Consume full serial stream so the DUT returns idle
beats = await collect_topk(dut, timeout_cycles=2000)
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

View File

@@ -170,14 +170,33 @@ async def collect_topk(dut, timeout_cycles: int = 2000):
raise AssertionError("Top-K result stream did not finish")
async def query_topk_once(dut, query, timeout_cycles=2000):
# ── 默认超时估算 ──────────────────────────────────────────────────
def dut_query_timeout_cycles(dut):
"""基于 DUT 参数估算完整查询(扫描 + 串行结果输出)的超时周期数。
各通道串行输出一个 beat 需要约 24 个流水线周期;
总超时 = ceil(全部行数 / 通道数) * 24 + 固定裕量 2000 周期。
至少返回 2000 周期以防止极小配置下的不合理值。
Example: 4096 rows / 8 lanes → ceil(4096/8) * 24 + 2000 = 14288 cycles.
"""
num_rows = dut_num_rows(dut)
lanes = dut_lanes(dut)
batches = (num_rows + lanes - 1) // lanes # ceil division, no math import
return max(2000, batches * 24 + 2000)
async def query_topk_once(dut, query, timeout_cycles=None):
"""发起一次查询并收集完整的串行 Top-K 结果流。
完整流程:
1. 等待 DUT 空闲
2. 通过 query_valid/query_ready 握手发送查询
3. 消费完整的结果流
4. 读取 score_debug_flat如果存在
2. 等待 query_ready 为高
3. 通过 query_valid/query_ready 握手发送查询
4. 消费完整的结果流
5. 读取 score_debug_flat如果存在
返回:(beats, top1_index, top1_score, score_debug)
- beats: [(rank, row, score, last), ...]
@@ -186,16 +205,21 @@ async def query_topk_once(dut, query, timeout_cycles=2000):
await wait_idle(dut)
dut.query_hash.value = int(query)
dut.query_valid.value = 1
# 等待查询握手完成
while True:
# 等待 query_ready 为高DUT 已就绪),避免组合逻辑下降沿导致的
# valid&&ready 握手丢失问题
while not int(dut.query_ready.value):
await RisingEdge(dut.clk)
if int(dut.query_ready.value):
break
# assert query_valid 覆盖一个上升沿完成握手
dut.query_valid.value = 1
await RisingEdge(dut.clk)
dut.query_valid.value = 0
# 若调用者未指定超时,根据 DUT 参数动态估算
if timeout_cycles is None:
timeout_cycles = dut_query_timeout_cycles(dut)
# 消费完整串行结果流
beats = await collect_topk(dut, timeout_cycles=timeout_cycles)
@@ -213,12 +237,19 @@ async def query_topk_once(dut, query, timeout_cycles=2000):
return beats, beats[0][1], beats[0][2], score_debug
async def query_once(dut, query):
async def query_once(dut, query, timeout_cycles=None):
"""发起查询,返回 (top1_index, top1_score, score_debug)。
内部调用 query_topk_once 并消费完整结果流,仅保留 rank-0 数据。
Parameters
----------
timeout_cycles : int or None
传递给 query_topk_once 的超时周期数。None 表示根据 DUT 参数动态估算。
"""
_, top1_index, top1_score, score_debug = await query_topk_once(dut, query)
_, top1_index, top1_score, score_debug = await query_topk_once(
dut, query, timeout_cycles=timeout_cycles,
)
return top1_index, top1_score, score_debug