mirror of
https://github.com/SikongJueluo/Mini-Nav.git
synced 2026-07-13 04:25:32 +08:00
- 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
134 lines
4.4 KiB
Python
134 lines
4.4 KiB
Python
from __future__ import annotations
|
|
|
|
import cocotb
|
|
from cocotb.clock import Clock
|
|
from cocotb.triggers import RisingEdge
|
|
|
|
# ── Parameter defaults (matching cam_params.svh with NUM_ROWS=4096, HASH_BITS=512) ──
|
|
K = 4
|
|
ROW_BITS = 12
|
|
SCORE_BITS = 10 # $clog2(512 + 1)
|
|
ROW_SENTINEL = (1 << ROW_BITS) - 1 # all-ones
|
|
|
|
|
|
def golden(candidates, k=K):
|
|
"""Return top-k candidates sorted by score desc, then row asc (higher score wins; lower row breaks ties)."""
|
|
return sorted(candidates, key=lambda item: (-item[1], item[0]))[:k]
|
|
|
|
|
|
def unpack(dut, k=K):
|
|
"""Read packed topk_rows_o and topk_scores_o; return list of (row, score) tuples ordered by rank."""
|
|
rows_packed = int(dut.topk_rows_o.value)
|
|
scores_packed = int(dut.topk_scores_o.value)
|
|
result = []
|
|
for i in range(k):
|
|
row = (rows_packed >> (i * ROW_BITS)) & ((1 << ROW_BITS) - 1)
|
|
score = (scores_packed >> (i * SCORE_BITS)) & ((1 << SCORE_BITS) - 1)
|
|
result.append((row, score))
|
|
return result
|
|
|
|
|
|
# ── Helpers ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
async def reset_tracker(dut):
|
|
"""Assert reset for 5 cycles, release, then wait 2 cycles."""
|
|
dut.rst_n.value = 0
|
|
dut.clear_i.value = 0
|
|
dut.candidate_valid_i.value = 0
|
|
for _ in range(5):
|
|
await RisingEdge(dut.clk)
|
|
dut.rst_n.value = 1
|
|
for _ in range(2):
|
|
await RisingEdge(dut.clk)
|
|
|
|
|
|
async def push_candidate(dut, row: int, score: int):
|
|
"""Drive one candidate handshake (candidate_ready_o is always 1)."""
|
|
dut.candidate_valid_i.value = 1
|
|
dut.candidate_row_i.value = row
|
|
dut.candidate_score_i.value = score
|
|
await RisingEdge(dut.clk)
|
|
dut.candidate_valid_i.value = 0
|
|
|
|
|
|
# ── Test 1: Basic ordering ───────────────────────────────────────────────────────
|
|
|
|
|
|
@cocotb.test()
|
|
async def tracker_orders_by_score_then_lower_row(dut):
|
|
"""
|
|
Push candidates [(5,10), (3,11), (2,11), (9,7), (1,11), (0,3)] and verify
|
|
the tracker's Top-4 matches golden: [(1,11), (2,11), (3,11), (5,10)].
|
|
"""
|
|
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
|
|
await reset_tracker(dut)
|
|
|
|
candidates = [(5, 10), (3, 11), (2, 11), (9, 7), (1, 11), (0, 3)]
|
|
|
|
for row, score in candidates:
|
|
await push_candidate(dut, row, score)
|
|
|
|
result = unpack(dut)
|
|
expected = golden(candidates)
|
|
assert result == expected, f"Expected {expected}, got {result}"
|
|
assert dut.update_pending_o.value == 0, "update_pending_o should be 0"
|
|
|
|
|
|
# ── Test 2: Clear restarts query ─────────────────────────────────────────────────
|
|
|
|
|
|
@cocotb.test()
|
|
async def tracker_clear_starts_new_query(dut):
|
|
"""
|
|
After candidate (7,60), pulse clear_i, send candidate (4,12).
|
|
Expect rank 0 = (4,12), remaining slots = sentinel rows, score 0.
|
|
"""
|
|
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
|
|
await reset_tracker(dut)
|
|
|
|
# Push (7, 60)
|
|
await push_candidate(dut, 7, 60)
|
|
|
|
# Pulse clear
|
|
dut.clear_i.value = 1
|
|
await RisingEdge(dut.clk)
|
|
dut.clear_i.value = 0
|
|
|
|
# Push (4, 12)
|
|
await push_candidate(dut, 4, 12)
|
|
|
|
result = unpack(dut)
|
|
expected = [(4, 12)] + [(ROW_SENTINEL, 0)] * (K - 1)
|
|
assert result == expected, f"Expected {expected}, got {result}"
|
|
|
|
|
|
# ── Test 3: Random validation against golden ─────────────────────────────────────
|
|
|
|
|
|
@cocotb.test()
|
|
async def tracker_matches_random_python_golden(dut):
|
|
"""
|
|
Push 100 candidates (row in [0,63], score in [0,127]) seeded at 42.
|
|
Verify final Top-4 matches Python golden model.
|
|
"""
|
|
import random
|
|
random.seed(42)
|
|
|
|
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
|
|
await reset_tracker(dut)
|
|
|
|
max_row = 64
|
|
max_score = 128
|
|
candidates = []
|
|
|
|
for _ in range(100):
|
|
row = random.randint(0, max_row - 1)
|
|
score = random.randint(0, max_score - 1)
|
|
candidates.append((row, score))
|
|
await push_candidate(dut, row, score)
|
|
|
|
result = unpack(dut)
|
|
expected = golden(candidates)
|
|
assert result == expected, f"Expected {expected}, got {result}"
|