diff --git a/hw/rtl/cam_core.sv b/hw/rtl/cam_core.sv index e949ba3..5d547c6 100644 --- a/hw/rtl/cam_core.sv +++ b/hw/rtl/cam_core.sv @@ -1,80 +1,17 @@ `timescale 1ns / 1ps +`include "cam_params.svh" -module cam_core #( - parameter int NUM_ROWS = 512, - parameter int HASH_BITS = 512, - parameter int LANES = 16, - - parameter int ROW_BITS = $clog2(NUM_ROWS), - parameter int SCORE_BITS = $clog2(HASH_BITS + 1) -) ( - input logic clk, - input logic rst_n, - - // Static load interface. In the first prototype, writes are expected - // before online queries begin. - input logic [ ROW_BITS-1:0] wr_row, - input logic [HASH_BITS-1:0] wr_hash, - input logic wr_en, - - // Single-request blocking query interface. - input logic query_valid, - output logic query_ready, - input logic [HASH_BITS-1:0] query_hash, - - output logic result_valid, - input logic result_ready, - output logic [ ROW_BITS-1:0] top1_index, - output logic [SCORE_BITS-1:0] top1_score, - -`ifdef SIM_NOISE - // Flattened for easier cocotb/Verilator handling: - // lane k mask = noise_mask_lanes_flat[k*HASH_BITS +: HASH_BITS] - input logic [LANES*HASH_BITS-1:0] noise_mask_lanes_flat, -`endif - -`ifdef SIM_DEBUG - // Flattened for easier cocotb/Verilator handling: - // score[row] = score_debug_flat[row*SCORE_BITS +: SCORE_BITS] - output logic [NUM_ROWS*SCORE_BITS-1:0] score_debug_flat, -`endif - - output logic busy +module cam_core ( + input logic clk, + input logic rst_n, + input logic wr_en, + input logic [(`ROW_BITS)-1:0] wr_row, + input logic [(`HASH_BITS)-1:0] wr_hash, + input logic [(`LANES)*(`ROW_BITS)-1:0] rd_addr_lanes_flat, + output logic [(`LANES)*(`HASH_BITS)-1:0] rd_hash_lanes_flat ); - localparam int NUM_BATCHES = (NUM_ROWS + LANES - 1) / LANES; - typedef enum logic [1:0] { - S_IDLE, - S_SCAN, - S_DONE - } state_t; - - state_t state_q, state_d; - - logic [HASH_BITS-1:0] cam_mem[NUM_ROWS]; - - logic [HASH_BITS-1: 0] query_q; - logic [ROW_BITS-1 : 0] base_row_q; - logic [ROW_BITS-1 : 0] base_row_d; - - logic [ROW_BITS-1:0] best_index_q, best_index_d; - logic [SCORE_BITS-1:0] best_score_q, best_score_d; - - logic [ ROW_BITS-1:0] lane_best_index; - logic [SCORE_BITS-1:0] lane_best_score; - - logic [ ROW_BITS-1:0] lane_best_index_next[LANES+1]; - logic [SCORE_BITS-1:0] lane_best_score_next[LANES+1]; - - logic [SCORE_BITS-1:0] lane_score [ LANES]; - logic [ ROW_BITS-1:0] lane_row [ LANES]; - logic lane_valid [ LANES]; - - assign query_ready = (state_q == S_IDLE); - assign result_valid = (state_q == S_DONE); - assign top1_index = best_index_q; - assign top1_score = best_score_q; - assign busy = (state_q == S_SCAN); + logic [(`HASH_BITS)-1:0] cam_mem[0:(`NUM_ROWS)-1]; // Memory write path. always_ff @(posedge clk) begin @@ -83,132 +20,13 @@ module cam_core #( end end -`ifdef SIM_DEBUG - // Clear by default. Individual rows are overwritten during scan. - initial begin - score_debug_flat = '0; - end -`endif - - genvar lane; + // Per-lane combinational read. + genvar l; generate - for (lane = 0; lane < LANES; lane++) begin : gen_lanes - logic [HASH_BITS-1:0] row_hash; - logic [HASH_BITS-1:0] effective_hash; - logic [HASH_BITS-1:0] match_bits; - - assign lane_row[lane] = base_row_q + lane[ROW_BITS-1:0]; - assign lane_valid[lane] = (lane_row[lane] < NUM_ROWS[ROW_BITS-1:0]); - - // This read is modeled behaviorally. Later this can be replaced - // by banked BRAM/URAM without changing the compare path. - assign row_hash = lane_valid[lane] ? cam_mem[lane_row[lane]] : '0; - -`ifdef SIM_NOISE - logic [HASH_BITS-1:0] lane_noise_mask; - assign lane_noise_mask = noise_mask_lanes_flat[lane*HASH_BITS+:HASH_BITS]; - assign effective_hash = row_hash ^ lane_noise_mask; -`else - assign effective_hash = row_hash; -`endif - - assign match_bits = ~(query_q ^ effective_hash); - - popcount #( - .WIDTH(HASH_BITS), - .GROUP(8), - .OUT_WIDTH(SCORE_BITS) - ) u_popcount ( - .bits_i (match_bits), - .count_o(lane_score[lane]) - ); - - argmax_update #( - .ROW_BITS (ROW_BITS), - .SCORE_BITS(SCORE_BITS) - ) u_argmax_lane ( - .best_index_i(lane_best_index_next[lane]), - .best_score_i(lane_best_score_next[lane]), - .cand_index_i(lane_row[lane]), - .cand_score_i(lane_valid[lane] ? lane_score[lane] : '0), - .best_index_o(lane_best_index_next[lane+1]), - .best_score_o(lane_best_score_next[lane+1]) - ); + for (l = 0; l < `LANES; l++) begin : rd_lane + assign rd_hash_lanes_flat[l*`HASH_BITS +: `HASH_BITS] = + cam_mem[rd_addr_lanes_flat[l*`ROW_BITS +: `ROW_BITS]]; end endgenerate - assign lane_best_index_next[0] = best_index_q; - assign lane_best_score_next[0] = best_score_q; - assign lane_best_index = lane_best_index_next[LANES]; - assign lane_best_score = lane_best_score_next[LANES]; - - always_comb begin - state_d = state_q; - base_row_d = base_row_q; - best_index_d = best_index_q; - best_score_d = best_score_q; - - unique case (state_q) - S_IDLE: begin - if (query_valid) begin - state_d = S_SCAN; - base_row_d = '0; - best_index_d = {ROW_BITS{1'b1}}; - best_score_d = '0; - end - end - - S_SCAN: begin - best_index_d = lane_best_index; - best_score_d = lane_best_score; - - if (base_row_q + LANES >= NUM_ROWS) begin - state_d = S_DONE; - end else begin - base_row_d = base_row_q + LANES[ROW_BITS-1:0]; - end - end - - S_DONE: begin - if (result_ready) begin - state_d = S_IDLE; - end - end - - default: begin - state_d = S_IDLE; - end - endcase - end - - always_ff @(posedge clk or negedge rst_n) begin - if (!rst_n) begin - state_q <= S_IDLE; - query_q <= '0; - base_row_q <= '0; - best_index_q <= '0; - best_score_q <= '0; - end else begin - state_q <= state_d; - base_row_q <= base_row_d; - best_index_q <= best_index_d; - best_score_q <= best_score_d; - - if ((state_q == S_IDLE) && query_valid) begin - query_q <= query_hash; - end - -`ifdef SIM_DEBUG - if (state_q == S_IDLE && query_valid) begin - score_debug_flat <= '0; - end else if (state_q == S_SCAN) begin - for (int l = 0; l < LANES; l++) begin - if (lane_valid[l]) begin - score_debug_flat[lane_row[l]*SCORE_BITS+:SCORE_BITS] <= lane_score[l]; - end - end - end -`endif - end - end endmodule diff --git a/hw/rtl/cam_params.svh b/hw/rtl/cam_params.svh new file mode 100644 index 0000000..865a2dc --- /dev/null +++ b/hw/rtl/cam_params.svh @@ -0,0 +1,54 @@ +//============================================================================== +// CAM Parameter Definitions +//============================================================================== +// This file defines shared compile-time parameters for the Content-Addressable +// Memory (CAM) subsystem. All `define macros use `ifndef guards to allow +// command-line override via +define+. +// +// Macros: +// NUM_ROWS — Number of rows in the CAM array +// HASH_BITS — Width of the hash value in bits +// LANES — Number of parallel comparison lanes +// ROW_BITS — Bits needed to index NUM_ROWS rows ($clog2(NUM_ROWS)) +// SCORE_BITS — Bits needed to represent HASH_BITS-bit scores ($clog2(HASH_BITS+1)) +// +// The TIE_BREAK_SENTINEL localparam is a bitmask with all bits set to 1'b1, +// used as the initial / tie-breaking value in the row-priority arbiter. +//============================================================================== + +`ifndef CAM_PARAMS_SVH +`define CAM_PARAMS_SVH + +// Number of CAM rows. +`ifndef NUM_ROWS +`define NUM_ROWS 512 +`endif + +// Width of the hash value stored per row. +`ifndef HASH_BITS +`define HASH_BITS 512 +`endif + +// Number of parallel comparison lanes. +`ifndef LANES +`define LANES 16 +`endif + +// Bits required to represent a row index. +`ifndef ROW_BITS +`define ROW_BITS $clog2(`NUM_ROWS) +`endif + +// Bits required to represent a full-score count. +`ifndef SCORE_BITS +`define SCORE_BITS $clog2(`HASH_BITS + 1) +`endif + +// Tie-break sentinel: all ones in the row-index width. +// Used as the initial best-index value before any match is found. +localparam logic [(`ROW_BITS)-1:0] TIE_BREAK_SENTINEL = {(`ROW_BITS){1'b1}}; + +// Current fixed parameters require NUM_ROWS divisible by LANES. +// Non-divisible tail-group valid-mask is not implemented in this round. + +`endif // CAM_PARAMS_SVH diff --git a/hw/rtl/cam_top.sv b/hw/rtl/cam_top.sv index f6afd82..75540ac 100644 --- a/hw/rtl/cam_top.sv +++ b/hw/rtl/cam_top.sv @@ -1,64 +1,64 @@ `timescale 1ns/1ps +`include "cam_params.svh" -module cam_top #( - parameter int NUM_ROWS = 512, - parameter int HASH_BITS = 512, - parameter int LANES = 16, - - parameter int ROW_BITS = $clog2(NUM_ROWS), - parameter int SCORE_BITS = $clog2(HASH_BITS + 1) -) ( +module cam_top ( input logic clk, input logic rst_n, input logic wr_en, - input logic [ROW_BITS-1:0] wr_row, - input logic [HASH_BITS-1:0] wr_hash, + input logic [(`ROW_BITS)-1:0] wr_row, + input logic [(`HASH_BITS)-1:0] wr_hash, input logic query_valid, output logic query_ready, - input logic [HASH_BITS-1:0] query_hash, + input logic [(`HASH_BITS)-1:0] query_hash, output logic result_valid, input logic result_ready, - output logic [ROW_BITS-1:0] top1_index, - output logic [SCORE_BITS-1:0] top1_score, + output logic [(`ROW_BITS)-1:0] top1_index, + output logic [(`SCORE_BITS)-1:0] top1_score, `ifdef SIM_NOISE - input logic [LANES*HASH_BITS-1:0] noise_mask_lanes_flat, + input logic [(`LANES)*(`HASH_BITS)-1:0] noise_mask_lanes_flat, `endif `ifdef SIM_DEBUG - output logic [NUM_ROWS*SCORE_BITS-1:0] score_debug_flat, + output logic [(`NUM_ROWS)*(`SCORE_BITS)-1:0] score_debug_flat, `endif output logic busy ); - cam_core #( - .NUM_ROWS(NUM_ROWS), - .HASH_BITS(HASH_BITS), - .LANES(LANES), - .ROW_BITS(ROW_BITS), - .SCORE_BITS(SCORE_BITS) - ) u_core ( - .clk(clk), - .rst_n(rst_n), - .wr_row(wr_row), - .wr_hash(wr_hash), - .wr_en(wr_en), - .query_valid(query_valid), - .query_ready(query_ready), - .query_hash(query_hash), - .result_valid(result_valid), - .result_ready(result_ready), - .top1_index(top1_index), - .top1_score(top1_score), + wire [(`LANES)*(`ROW_BITS)-1:0] rd_addr_lanes_flat; + wire [(`LANES)*(`HASH_BITS)-1:0] rd_hash_lanes_flat; + + cam_core u_core ( + .clk (clk), + .rst_n (rst_n), + .wr_en (wr_en), + .wr_row (wr_row), + .wr_hash (wr_hash), + .rd_addr_lanes_flat (rd_addr_lanes_flat), + .rd_hash_lanes_flat (rd_hash_lanes_flat) + ); + + match_engine u_match ( + .clk (clk), + .rst_n (rst_n), + .query_valid (query_valid), + .query_ready (query_ready), + .query_hash (query_hash), + .result_valid (result_valid), + .result_ready (result_ready), + .result_row (top1_index), + .result_score (top1_score), + .busy (busy), + .rd_addr_lanes_flat (rd_addr_lanes_flat), + .rd_hash_lanes_flat (rd_hash_lanes_flat) `ifdef SIM_NOISE - .noise_mask_lanes_flat(noise_mask_lanes_flat), + ,.noise_mask_lanes_flat (noise_mask_lanes_flat) `endif `ifdef SIM_DEBUG - .score_debug_flat(score_debug_flat), + ,.score_debug_flat (score_debug_flat) `endif - .busy(busy) ); endmodule diff --git a/hw/rtl/match_engine.sv b/hw/rtl/match_engine.sv new file mode 100644 index 0000000..5a5306b --- /dev/null +++ b/hw/rtl/match_engine.sv @@ -0,0 +1,191 @@ +`timescale 1ns / 1ps +`include "cam_params.svh" + +module match_engine ( + input logic clk, + input logic rst_n, + // Query interface + input logic query_valid, + output logic query_ready, + input logic [(`HASH_BITS)-1:0] query_hash, + output logic result_valid, + input logic result_ready, + output logic [(`ROW_BITS)-1:0] result_row, + output logic [(`SCORE_BITS)-1:0] result_score, + output logic busy, + // To/from cam_core + output logic [(`LANES)*(`ROW_BITS)-1:0] rd_addr_lanes_flat, + input logic [(`LANES)*(`HASH_BITS)-1:0] rd_hash_lanes_flat +`ifdef SIM_NOISE + ,input logic [(`LANES)*(`HASH_BITS)-1:0] noise_mask_lanes_flat +`endif +`ifdef SIM_DEBUG + ,output logic [(`NUM_ROWS)*(`SCORE_BITS)-1:0] score_debug_flat +`endif +); + + typedef enum logic [1:0] { // state_t + S_IDLE, // Waiting for query_valid + S_SCAN, // Scanning rows in LANES-wide batches + S_DONE // Result ready, waiting for result_ready + } state_t; + + state_t state_q, state_d; + logic [(`HASH_BITS)-1:0] query_q; + logic [(`ROW_BITS)-1:0] base_row_q, base_row_d; + + logic [(`ROW_BITS)-1:0] prev_best_idx [0:(`LANES)]; + logic [(`ROW_BITS)-1:0] next_best_idx [0:(`LANES)-1]; + logic [(`SCORE_BITS)-1:0] prev_best_score [0:(`LANES)]; + logic [(`SCORE_BITS)-1:0] next_best_score [0:(`LANES)-1]; + + logic [(`SCORE_BITS)-1:0] lane_score [0:(`LANES)-1]; + logic [(`ROW_BITS)-1:0] lane_row [0:(`LANES)-1]; + logic lane_valid [0:(`LANES)-1]; + + logic [(`ROW_BITS)-1:0] batch_best_idx; + logic [(`SCORE_BITS)-1:0] batch_best_score; + + logic [(`ROW_BITS)-1:0] best_idx_q, best_idx_d; + logic [(`SCORE_BITS)-1:0] best_score_q, best_score_d; + + assign query_ready = (state_q == S_IDLE); + assign result_valid = (state_q == S_DONE); + assign result_row = best_idx_q; + assign result_score = best_score_q; + assign busy = (state_q == S_SCAN); + + genvar lane; + generate + for (lane = 0; lane < `LANES; lane++) begin : gen_lanes + logic [(`HASH_BITS)-1:0] row_hash; + logic [(`HASH_BITS)-1:0] effective_hash; + logic [(`HASH_BITS)-1:0] match_bits; + + assign lane_row[lane] = base_row_q + lane[(`ROW_BITS)-1:0]; + assign lane_valid[lane] = (lane_row[lane] < `NUM_ROWS); + + assign rd_addr_lanes_flat[lane*`ROW_BITS +: `ROW_BITS] = lane_row[lane]; + assign row_hash = rd_hash_lanes_flat[lane*`HASH_BITS +: `HASH_BITS]; + +`ifdef SIM_NOISE + assign effective_hash = row_hash ^ noise_mask_lanes_flat[lane*`HASH_BITS +: `HASH_BITS]; +`else + assign effective_hash = row_hash; +`endif + + assign match_bits = ~(query_q ^ effective_hash); + + popcount #( + .WIDTH(`HASH_BITS), + .GROUP(8), + .OUT_WIDTH(`SCORE_BITS) + ) u_popcount ( + .bits_i(match_bits), + .count_o(lane_score[lane]) + ); + + argmax_update #( + .ROW_BITS(`ROW_BITS), + .SCORE_BITS(`SCORE_BITS) + ) u_argmax_update ( + .best_index_i(prev_best_idx[lane]), + .best_score_i(prev_best_score[lane]), + .cand_index_i(lane_row[lane]), + .cand_score_i(lane_valid[lane] ? lane_score[lane] : '0), + .best_index_o(next_best_idx[lane]), + .best_score_o(next_best_score[lane]) + ); + end + endgenerate + + // Initialize chain with current batch seed + assign prev_best_idx[0] = best_idx_q; + assign prev_best_score[0] = best_score_q; + + // Propagate per-lane results + for (genvar l = 0; l < `LANES; l++) begin : chain_link + assign prev_best_idx[l+1] = next_best_idx[l]; + assign prev_best_score[l+1] = next_best_score[l]; + end + + assign batch_best_idx = prev_best_idx[`LANES]; + assign batch_best_score = prev_best_score[`LANES]; + + always_comb begin + state_d = state_q; + base_row_d = base_row_q; + best_idx_d = best_idx_q; + best_score_d = best_score_q; + + unique case (state_q) + S_IDLE: begin + if (query_valid) begin + state_d = S_SCAN; + base_row_d = '0; + best_idx_d = TIE_BREAK_SENTINEL; // Lower index wins tie-break + best_score_d = '0; + end + end + + S_SCAN: begin + best_idx_d = batch_best_idx; + best_score_d = batch_best_score; + + if (base_row_q + `LANES >= `NUM_ROWS) begin + state_d = S_DONE; + end else begin + base_row_d = base_row_q + `LANES; + end + end + + S_DONE: begin + if (result_ready) begin + state_d = S_IDLE; + end + end + + default: begin + state_d = S_IDLE; + end + endcase + end + + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + state_q <= S_IDLE; + query_q <= '0; + base_row_q <= '0; + best_idx_q <= '0; + best_score_q <= '0; + end else begin + state_q <= state_d; + base_row_q <= base_row_d; + best_idx_q <= best_idx_d; + best_score_q <= best_score_d; + + if ((state_q == S_IDLE) && query_valid) begin + query_q <= query_hash; + end + +`ifdef SIM_DEBUG + if (state_q == S_IDLE && query_valid) begin + score_debug_flat <= '0; + end else if (state_q == S_SCAN) begin + for (int l = 0; l < `LANES; l++) begin + if (lane_valid[l]) begin + score_debug_flat[lane_row[l]*`SCORE_BITS +: `SCORE_BITS] <= lane_score[l]; + end + end + end +`endif + end + end + +`ifdef SIM_DEBUG + initial begin + score_debug_flat = '0; + end +`endif + +endmodule diff --git a/hw/sim/Makefile b/hw/sim/Makefile index 4b7633f..49fcbe2 100644 --- a/hw/sim/Makefile +++ b/hw/sim/Makefile @@ -14,17 +14,20 @@ NUM_ROWS ?= 512 HASH_BITS ?= 512 LANES ?= 16 -EXTRA_ARGS += -DNUM_ROWS=$(NUM_ROWS) -DHASH_BITS=$(HASH_BITS) -DLANES=$(LANES) +EXTRA_ARGS += +define+NUM_ROWS=$(NUM_ROWS) +define+HASH_BITS=$(HASH_BITS) +define+LANES=$(LANES) # cocotb passes PLUSARGS/EXTRA_ARGS differently across simulators. Keep # SystemVerilog parameters explicit through COMPILE_ARGS for Verilator. COMPILE_ARGS += -Wall -Wno-fatal +COMPILE_ARGS += -I$(PWD)/../rtl COMPILE_ARGS += +define+SIM_DEBUG COMPILE_ARGS += $(EXTRA_DEFINES) VERILOG_SOURCES += $(PWD)/../rtl/popcount.sv VERILOG_SOURCES += $(PWD)/../rtl/argmax_update.sv + VERILOG_SOURCES += $(PWD)/../rtl/cam_core.sv +VERILOG_SOURCES += $(PWD)/../rtl/match_engine.sv VERILOG_SOURCES += $(PWD)/../rtl/cam_top.sv -include $(shell cocotb-config --makefiles)/Makefile.sim +include $(shell uv run cocotb-config --makefiles)/Makefile.sim