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,124 @@
`timescale 1ns / 1ps
`include "cam_params.svh"
//==============================================================================
// candidate_fifo — Synchronous ready/valid FIFO for (row, score) candidates
//
// Implements a simple circular-buffer FIFO that accepts writes only when
// wr_valid_i && wr_ready_o and reads only when rd_valid_o && rd_ready_i.
// Provides empty/full flags. Default depth is `FIFO_DEPTH (16).
//
// Quality:
// - Pointers always wrap at DEPTH-1 (safe for non-power-of-two depths).
// - wr_ready_o goes high when full if a read is accepted in the same cycle,
// allowing a simultaneous write (throughput-friendly backpressure).
// - PTR_BITS is at least 1 for DEPTH=1.
//==============================================================================
module candidate_fifo #(
parameter int DEPTH = `FIFO_DEPTH,
parameter int ROW_BITS = `ROW_BITS,
parameter int SCORE_BITS = `SCORE_BITS,
// Ensure at least 1 bit even when DEPTH=1.
parameter int PTR_BITS = (DEPTH <= 1) ? 1 : $clog2(DEPTH)
) (
input logic clk,
input logic rst_n,
// Write interface (producer side)
input logic wr_valid_i,
output logic wr_ready_o,
input logic [ROW_BITS -1:0] wr_row_i,
input logic [SCORE_BITS -1:0] wr_score_i,
// Read interface (consumer side)
output logic rd_valid_o,
input logic rd_ready_i,
output logic [ROW_BITS -1:0] rd_row_o,
output logic [SCORE_BITS -1:0] rd_score_o,
// Status flags
output logic empty_o,
output logic full_o
);
//--------------------------------------------------------------------------
// Storage — dualport array.
//--------------------------------------------------------------------------
logic [ROW_BITS -1:0] mem_row [0:DEPTH-1];
logic [SCORE_BITS -1:0] mem_score [0:DEPTH-1];
//--------------------------------------------------------------------------
// Pointers and fill counter
//--------------------------------------------------------------------------
logic [PTR_BITS -1:0] wr_ptr, rd_ptr;
logic [PTR_BITS :0] count; // extra bit for full detect
// Wrap-at value (DEPTH-1), sized to match pointer width.
// Part-select DEPTH and use a 1'b1 literal so the RHS width equals PTR_BITS.
localparam logic [PTR_BITS-1:0] WRAP_AT = DEPTH[PTR_BITS-1:0] - 1'b1;
//--------------------------------------------------------------------------
// Status flag outputs (combinational)
//--------------------------------------------------------------------------
assign empty_o = (count == 0);
assign full_o = (count == DEPTH[PTR_BITS:0]);
//--------------------------------------------------------------------------
// Handshake outputs
//
// wr_ready_o is asserted when not full, OR when a read is accepted in
// the same cycle (full+pop → slot freed → write can proceed).
//--------------------------------------------------------------------------
assign wr_ready_o = ~full_o | (rd_valid_o & rd_ready_i);
assign rd_valid_o = ~empty_o;
//--------------------------------------------------------------------------
// Read data (combinational — value at current rd_ptr)
//--------------------------------------------------------------------------
assign rd_row_o = mem_row [rd_ptr];
assign rd_score_o = mem_score[rd_ptr];
//--------------------------------------------------------------------------
// Push / Pop decode
//--------------------------------------------------------------------------
logic push, pop;
assign push = wr_valid_i & wr_ready_o;
assign pop = rd_valid_o & rd_ready_i;
//--------------------------------------------------------------------------
// Sequential — pointer and count updates
//
// Pointers wrap at DEPTH-1, safe for any DEPTH (not just powers of two).
// Simultaneous push+pop leaves count unchanged.
//--------------------------------------------------------------------------
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= 0;
rd_ptr <= 0;
count <= 0;
end else begin
if (push) begin
mem_row [wr_ptr] <= wr_row_i;
mem_score[wr_ptr] <= wr_score_i;
if (wr_ptr == WRAP_AT)
wr_ptr <= 0;
else
wr_ptr <= wr_ptr + 1;
end
if (pop) begin
if (rd_ptr == WRAP_AT)
rd_ptr <= 0;
else
rd_ptr <= rd_ptr + 1;
end
// Update fill count
if (push & ~pop) count <= count + 1;
else if (~push & pop) count <= count - 1;
// else: no change (neither, or both)
end
end
endmodule