`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