`timescale 1ns / 1ps `include "cam_params.svh" //============================================================================== // Top-K Tracker //============================================================================== // Maintains a sorted Top-K array of (row, score) candidates. // Insertion sort on every candidate handshake: // - Higher score wins. // - If equal score, lower row wins (tie-break). // - Entries that are worse than all K current slots are dropped. // // candidate_ready_o is always 1 — the tracker accepts a candidate every cycle. // update_pending_o is always 0 — insertion is fully combinational (one cycle). // // Packed outputs: // topk_rows_o [i*ROW_BITS +: ROW_BITS] = rank i row // topk_scores_o [i*SCORE_BITS +: SCORE_BITS] = rank i score //============================================================================== module topk_tracker #( parameter int K = `TOPK_K, parameter int ROW_BITS = `ROW_BITS, parameter int SCORE_BITS = `SCORE_BITS ) ( input logic clk, input logic rst_n, input logic clear_i, input logic candidate_valid_i, output logic candidate_ready_o, input logic [ROW_BITS-1:0] candidate_row_i, input logic [SCORE_BITS-1:0] candidate_score_i, output logic [K*ROW_BITS-1:0] topk_rows_o, output logic [K*SCORE_BITS-1:0] topk_scores_o, output logic update_pending_o ); // ── Sequential state: K-entry sorted arrays ──────────────────────────── logic [ROW_BITS-1:0] rows_q [0:K-1]; logic [SCORE_BITS-1:0] scores_q [0:K-1]; // ── Next-state signals ───────────────────────────────────────────────── logic [ROW_BITS-1:0] rows_next [0:K-1]; logic [SCORE_BITS-1:0] scores_next [0:K-1]; // ── Static output assignments ────────────────────────────────────────── assign candidate_ready_o = 1'b1; assign update_pending_o = 1'b0; // ── Combinational: insertion logic ───────────────────────────────────── always_comb begin int insert_idx; insert_idx = K; // always assigned — default: no insertion // Default: retain current state for (int i = 0; i < K; i++) begin rows_next[i] = rows_q[i]; scores_next[i] = scores_q[i]; end // Only evaluate insertion when a valid candidate is presented if (candidate_valid_i) begin // Find the first slot where candidate is strictly better than the // current occupant. "Better" means higher score, or equal score // with lower row (tie-break). // Note: guard on insert_idx == K avoids loop-local break which // Yosys rejects; the effect is the same — only the first match // is captured because subsequent iterations see insert_idx != K. for (int i = 0; i < K; i++) begin if (insert_idx == K && (candidate_score_i > scores_q[i] || (candidate_score_i == scores_q[i] && candidate_row_i < rows_q[i]))) begin insert_idx = i; end end // Shift slots down from the insertion point and insert. // Static loop bounds with guard for Yosys compatibility. if (insert_idx < K) begin for (int i = 0; i < K; i++) begin if (i == insert_idx) begin rows_next[i] = candidate_row_i; scores_next[i] = candidate_score_i; end else if (i > insert_idx) begin rows_next[i] = rows_q[i - 1]; scores_next[i] = scores_q[i - 1]; end // i < insert_idx: keep default from copy loop above end end end end // ── Sequential: state update ─────────────────────────────────────────── always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin for (int i = 0; i < K; i++) begin rows_q[i] <= {ROW_BITS{1'b1}}; // all-ones sentinel scores_q[i] <= '0; end end else if (clear_i) begin for (int i = 0; i < K; i++) begin rows_q[i] <= {ROW_BITS{1'b1}}; scores_q[i] <= '0; end end else if (candidate_valid_i) begin for (int i = 0; i < K; i++) begin rows_q[i] <= rows_next[i]; scores_q[i] <= scores_next[i]; end end end // ── Combinational: pack arrays into flat outputs ─────────────────────── always_comb begin topk_rows_o = '0; topk_scores_o = '0; for (int i = 0; i < K; i++) begin topk_rows_o[i*ROW_BITS +: ROW_BITS] = rows_q[i]; topk_scores_o[i*SCORE_BITS +: SCORE_BITS] = scores_q[i]; end end endmodule