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:
2026-05-19 18:19:05 +08:00
parent 8bcad1f23f
commit e4cbb5e30d
21 changed files with 2152 additions and 124 deletions

View File

@@ -0,0 +1,126 @@
`timescale 1ns / 1ps
`include "cam_params.svh"
//==============================================================================
// Result Serializer
//==============================================================================
// Serializes the packed Top-K array (rows and scores) into a rank-ordered
// handshake interface, one result per fire.
//
// Start is edge-triggered (sampled on the cycle start_i is high while not
// already active). Once started, the module presents outputs for the
// current rank (starting at 0) and advances on each valid-ready fire.
// After the last rank (K-1) fires, done_o pulses for one cycle and the
// module returns to idle with rank reset to 0.
//
// Back-pressure: holds current output while result_ready_i is low.
//
// Supports K=1 cleanly (last rank = rank 0, done on first fire).
//==============================================================================
module result_serializer #(
parameter int K = `TOPK_K,
parameter int ROW_BITS = `ROW_BITS,
parameter int SCORE_BITS = `SCORE_BITS,
parameter int RANK_BITS = (K <= 1) ? 1 : $clog2(K)
) (
input logic clk,
input logic rst_n,
input logic start_i,
output logic busy_o,
output logic done_o,
input logic [K*ROW_BITS-1:0] topk_rows_i,
input logic [K*SCORE_BITS-1:0] topk_scores_i,
output logic result_valid_o,
input logic result_ready_i,
output logic [RANK_BITS-1:0] result_rank_o,
output logic [ROW_BITS-1:0] result_row_o,
output logic [SCORE_BITS-1:0] result_score_o,
output logic result_last_o
);
// ── State encoding ─────────────────────────────────────────────────────
typedef enum logic {
IDLE,
ACTIVE
} state_t;
// ── Localparam for last-rank comparison (width-matched to avoid warnings) ─
localparam [RANK_BITS-1:0] LAST_RANK = RANK_BITS'(K - 1);
state_t state_q, state_next;
logic [RANK_BITS-1:0] rank_q, rank_next;
logic done_q, done_next;
// Snapshot of packed inputs captured at start (holds during back-pressure)
logic [K*ROW_BITS-1:0] rows_snapshot_q, rows_snapshot_next;
logic [K*SCORE_BITS-1:0] scores_snapshot_q, scores_snapshot_next;
// ── Sequential: state, rank, done, and snapshot registers ─────────────
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_q <= IDLE;
rank_q <= '0;
done_q <= 1'b0;
rows_snapshot_q <= '0;
scores_snapshot_q <= '0;
end else begin
state_q <= state_next;
rank_q <= rank_next;
done_q <= done_next;
rows_snapshot_q <= rows_snapshot_next;
scores_snapshot_q <= scores_snapshot_next;
end
end
// ── Combinational: next-state, snapshot, and output logic ──────────────
always_comb begin
// Defaults: stay in current state, retain rank, no done pulse,
// hold snapshot (outputs stable under back-pressure)
state_next = state_q;
rank_next = rank_q;
done_next = 1'b0;
rows_snapshot_next = rows_snapshot_q;
scores_snapshot_next = scores_snapshot_q;
if (state_q == IDLE) begin
// Edge-triggered start — capture packed inputs into snapshot
if (start_i) begin
state_next = ACTIVE;
rank_next = '0; // start from rank 0
rows_snapshot_next = topk_rows_i;
scores_snapshot_next = topk_scores_i;
end
end else begin
// ACTIVE: advance on fire
if (result_valid_o && result_ready_i) begin
// Is this the last rank?
if (rank_q == LAST_RANK) begin
state_next = IDLE;
rank_next = '0;
done_next = 1'b1;
end else begin
rank_next = rank_q + 1'b1;
end
end
end
end
// ── Output assignments ─────────────────────────────────────────────────
assign busy_o = (state_q == ACTIVE);
assign done_o = done_q; // registered — pulses after deactivation
assign result_valid_o = (state_q == ACTIVE);
// Slice the snapshot registers at the current rank
assign result_row_o = rows_snapshot_q[rank_q * ROW_BITS +: ROW_BITS];
assign result_score_o = scores_snapshot_q[rank_q * SCORE_BITS +: SCORE_BITS];
assign result_rank_o = rank_q;
// result_last_o: high only when active and on the last rank
assign result_last_o = (state_q == ACTIVE) && (rank_q == LAST_RANK);
endmodule