mirror of
https://github.com/SikongJueluo/Mini-Nav.git
synced 2026-07-12 20:15:31 +08:00
feat(hw/rtl): implement full Top-K CAM search pipeline with serial result output
- add TOPK_K, FIFO_DEPTH, RESULT_SERIAL parameters to cam_params - add candidate_fifo: synchronous ready/valid FIFO for (row, score) candidates - add topk_tracker: tracks top-K candidates with clear/ready handshake - add result_serializer: serializes packed Top-K array into rank-ordered stream - refactor match_engine_pipeline from 4-state to 8-state Top-K pipeline - extend cam_top with serial Top-K interface (result_rank/row/score/last) - add backward-compatible top1_index/top1_score aliases from rank-0 beat - add comprehensive tests for all new modules
This commit is contained in:
@@ -64,6 +64,63 @@ def dut_score_bits(dut):
|
||||
# ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def collect_topk(dut, timeout_cycles: int = 2000):
|
||||
"""Collect all serial Top-K result beats with result_ready held high.
|
||||
|
||||
Returns list of (rank, row, score, last) tuples.
|
||||
Raises AssertionError if the stream does not complete within timeout.
|
||||
"""
|
||||
dut.result_ready.value = 1
|
||||
beats = []
|
||||
for _ in range(timeout_cycles):
|
||||
if int(dut.result_valid.value):
|
||||
rank = int(dut.result_rank.value)
|
||||
row = int(dut.result_row.value)
|
||||
score = int(dut.result_score.value)
|
||||
last = int(dut.result_last.value)
|
||||
beats.append((rank, row, score, last))
|
||||
if last:
|
||||
return beats
|
||||
await RisingEdge(dut.clk)
|
||||
raise AssertionError("Top-K result stream did not finish")
|
||||
|
||||
|
||||
async def query_topk_once(dut, query, timeout_cycles=2000):
|
||||
"""Issue a query, collect full serial Top-K stream, and return beats + Top-1 metadata.
|
||||
|
||||
Returns (beats, top1_index, top1_score, score_debug).
|
||||
After this call the full result stream has been consumed and the DUT is idle.
|
||||
"""
|
||||
await wait_idle(dut)
|
||||
|
||||
dut.query_hash.value = int(query)
|
||||
dut.query_valid.value = 1
|
||||
|
||||
# Wait for handshake
|
||||
while True:
|
||||
await RisingEdge(dut.clk)
|
||||
if int(dut.query_ready.value):
|
||||
break
|
||||
|
||||
dut.query_valid.value = 0
|
||||
|
||||
# Consume full serial result stream
|
||||
beats = await collect_topk(dut, timeout_cycles=timeout_cycles)
|
||||
|
||||
# score_debug is available after query completes
|
||||
num_rows = dut_num_rows(dut)
|
||||
score_bits = dut_score_bits(dut)
|
||||
score_debug = None
|
||||
if hasattr(dut, "score_debug_flat"):
|
||||
score_debug = unpack_score_debug_flat(
|
||||
int(dut.score_debug_flat.value),
|
||||
num_rows,
|
||||
score_bits,
|
||||
)
|
||||
|
||||
return beats, beats[0][1], beats[0][2], score_debug
|
||||
|
||||
|
||||
async def reset_dut(dut):
|
||||
"""Reset the DUT with new handshake interface."""
|
||||
dut.rst_n.value = 0
|
||||
@@ -116,41 +173,11 @@ async def write_rows(dut, rows):
|
||||
|
||||
|
||||
async def query_once(dut, query):
|
||||
"""Issue a query and return (top1_index, top1_score, score_debug)."""
|
||||
await wait_idle(dut)
|
||||
|
||||
dut.query_hash.value = int(query)
|
||||
dut.query_valid.value = 1
|
||||
|
||||
# Wait for handshake
|
||||
while True:
|
||||
await RisingEdge(dut.clk)
|
||||
if int(dut.query_ready.value):
|
||||
break
|
||||
|
||||
dut.query_valid.value = 0
|
||||
|
||||
# Wait for result
|
||||
while int(dut.result_valid.value) == 0:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
top1_index = int(dut.top1_index.value)
|
||||
top1_score = int(dut.top1_score.value)
|
||||
|
||||
num_rows = dut_num_rows(dut)
|
||||
score_bits = dut_score_bits(dut)
|
||||
score_debug = None
|
||||
if hasattr(dut, "score_debug_flat"):
|
||||
score_debug = unpack_score_debug_flat(
|
||||
int(dut.score_debug_flat.value),
|
||||
num_rows,
|
||||
score_bits,
|
||||
)
|
||||
|
||||
dut.result_ready.value = 1
|
||||
await RisingEdge(dut.clk)
|
||||
dut.result_ready.value = 0
|
||||
"""Issue a query and return (top1_index, top1_score, score_debug).
|
||||
|
||||
Consumes the full serial Top-K stream and returns rank-0 data.
|
||||
"""
|
||||
_, top1_index, top1_score, score_debug = await query_topk_once(dut, query)
|
||||
return top1_index, top1_score, score_debug
|
||||
|
||||
|
||||
@@ -187,7 +214,7 @@ async def baseline_no_noise(dut):
|
||||
query = rows[query_index]
|
||||
|
||||
await write_rows(dut, rows)
|
||||
top1_index, top1_score, score_debug = await query_once(dut, query)
|
||||
beats, top1_index, top1_score, score_debug = await query_topk_once(dut, query)
|
||||
|
||||
expected = match_top1(query, rows, width=hash_bits)
|
||||
|
||||
@@ -196,6 +223,16 @@ async def baseline_no_noise(dut):
|
||||
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)
|
||||
|
||||
@@ -564,10 +601,10 @@ async def query_scan_blocks_writes_until_result_consumed(dut):
|
||||
assert int(dut.wr_ready.value) == 0
|
||||
dut.wr_valid.value = 0
|
||||
|
||||
while int(dut.result_valid.value) == 0:
|
||||
await RisingEdge(dut.clk)
|
||||
dut.result_ready.value = 1
|
||||
await RisingEdge(dut.clk)
|
||||
# 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
|
||||
|
||||
|
||||
# ── Test I: Read noise model match ──────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user