refactor(core/cam_core_banked): extract per-bank modules for improved timing isolation

- Extract cam_bank as a parameterized submodule with independent read/write ports
- Replace flat 2D memory array with generate loop of bank instances
- Derive bank selection from address bit slicing instead of modulo arithmetic
- Align rd_base_row_i check with new bank addressing scheme
- Add test verifying bank address isolation across multiple banks
This commit is contained in:
2026-05-19 13:59:25 +08:00
parent 5d09f13a08
commit 8bcad1f23f
2 changed files with 119 additions and 11 deletions

View File

@@ -1,6 +1,34 @@
`timescale 1ns / 1ps `timescale 1ns / 1ps
`include "cam_params.svh" `include "cam_params.svh"
module cam_bank #(
parameter int HASH_BITS = 512,
parameter int BANK_DEPTH = 512,
parameter int BANK_ADDR_BITS = 9
) (
input logic clk,
input logic wr_en,
input logic [BANK_ADDR_BITS-1:0] wr_addr,
input logic [HASH_BITS-1:0] wr_data,
input logic rd_en,
input logic [BANK_ADDR_BITS-1:0] rd_addr,
output logic [HASH_BITS-1:0] rd_data
);
(* ram_style = "block" *) logic [HASH_BITS-1:0] mem [0:BANK_DEPTH-1];
always_ff @(posedge clk) begin
if (wr_en) begin
mem[wr_addr] <= wr_data;
end
if (rd_en) begin
rd_data <= mem[rd_addr];
end
end
endmodule
module cam_core_banked ( module cam_core_banked (
input logic clk, input logic clk,
input logic rst_n, input logic rst_n,
@@ -18,21 +46,51 @@ module cam_core_banked (
output logic [(`LANES)-1:0] rd_lane_valid_o output logic [(`LANES)-1:0] rd_lane_valid_o
); );
localparam int BANKS = `LANES; localparam int BANKS = `LANES;
localparam int BANK_DEPTH = `NUM_ROWS / `LANES; localparam int BANK_DEPTH = `NUM_ROWS / BANKS;
localparam int BANK_SEL_BITS = $clog2(BANKS);
localparam int BANK_ADDR_BITS = $clog2(BANK_DEPTH);
(* ram_style = "block" *) logic [(`HASH_BITS)-1:0] bank_mem [0:BANKS-1][0:BANK_DEPTH-1]; logic [BANK_SEL_BITS-1:0] wr_bank_id;
logic [BANK_ADDR_BITS-1:0] wr_bank_addr;
logic [BANK_ADDR_BITS-1:0] rd_bank_addr;
logic [(`HASH_BITS)-1:0] bank_rd_data [0:BANKS-1];
assign wr_ready = 1'b1; assign wr_ready = 1'b1;
assign wr_bank_id = wr_row[BANK_SEL_BITS-1:0];
assign wr_bank_addr = wr_row[(`ROW_BITS)-1:BANK_SEL_BITS];
assign rd_bank_addr = rd_base_row_i[(`ROW_BITS)-1:BANK_SEL_BITS];
`ifndef SYNTHESIS `ifndef SYNTHESIS
initial begin initial begin
if (`NUM_ROWS % `LANES != 0) $fatal(1, "NUM_ROWS must be divisible by LANES"); if (`NUM_ROWS != (BANK_DEPTH * BANKS)) $fatal(1, "NUM_ROWS must be divisible by LANES");
if (`LANES != (1 << BANK_SEL_BITS)) $fatal(1, "LANES must be a power of two");
end end
`endif `endif
always_ff @(posedge clk) begin generate
if (wr_valid) begin for (genvar b = 0; b < BANKS; b++) begin : g_bank
bank_mem[wr_row % `LANES][wr_row / `LANES] <= wr_hash; cam_bank #(
.HASH_BITS(`HASH_BITS),
.BANK_DEPTH(BANK_DEPTH),
.BANK_ADDR_BITS(BANK_ADDR_BITS)
) u_bank (
.clk(clk),
.wr_en(wr_valid && (wr_bank_id == b[BANK_SEL_BITS-1:0])),
.wr_addr(wr_bank_addr),
.wr_data(wr_hash),
.rd_en(rd_valid_i),
.rd_addr(rd_bank_addr),
.rd_data(bank_rd_data[b])
);
end
endgenerate
always_comb begin
rd_hashes_o = '0;
if (rd_valid_o) begin
for (int lane = 0; lane < `LANES; lane++) begin
rd_hashes_o[lane*`HASH_BITS +: `HASH_BITS] = bank_rd_data[lane];
end
end end
end end
@@ -40,21 +98,19 @@ module cam_core_banked (
if (!rst_n) begin if (!rst_n) begin
rd_valid_o <= 1'b0; rd_valid_o <= 1'b0;
rd_row_ids_o <= '0; rd_row_ids_o <= '0;
rd_hashes_o <= '0;
rd_lane_valid_o <= '0; rd_lane_valid_o <= '0;
end else begin end else begin
rd_valid_o <= rd_valid_i; rd_valid_o <= rd_valid_i;
rd_lane_valid_o <= rd_valid_i ? {`LANES{1'b1}} : '0; rd_lane_valid_o <= rd_valid_i ? {`LANES{1'b1}} : '0;
`ifndef SYNTHESIS `ifndef SYNTHESIS
if (rd_valid_i && ((rd_base_row_i % `LANES) != 0)) begin if (rd_valid_i && ((rd_base_row_i[BANK_SEL_BITS-1:0]) != '0)) begin
$fatal(1, "rd_base_row_i must be LANES-aligned"); $fatal(1, "rd_base_row_i must be LANES-aligned");
end end
`endif `endif
for (int lane = 0; lane < `LANES; lane++) begin for (int lane = 0; lane < `LANES; lane++) begin
rd_row_ids_o[lane*`ROW_BITS +: `ROW_BITS] <= rd_base_row_i + lane[(`ROW_BITS)-1:0]; rd_row_ids_o[lane*`ROW_BITS +: `ROW_BITS] <= rd_base_row_i + lane[(`ROW_BITS)-1:0];
rd_hashes_o[lane*`HASH_BITS +: `HASH_BITS] <= bank_mem[lane][rd_base_row_i / `LANES];
end end
end end
end end

View File

@@ -19,6 +19,26 @@ async def reset_core(dut):
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
def lane_word(value: int, lane: int, width: int) -> int:
return (value >> (lane * width)) & ((1 << width) - 1)
async def write_row(dut, row: int, hash_value: int):
dut.wr_valid.value = 1
dut.wr_row.value = row
dut.wr_hash.value = hash_value
await RisingEdge(dut.clk)
dut.wr_valid.value = 0
async def request_batch(dut, base_row: int):
dut.rd_base_row_i.value = base_row
dut.rd_valid_i.value = 1
await RisingEdge(dut.clk)
dut.rd_valid_i.value = 0
await RisingEdge(dut.clk)
@cocotb.test() @cocotb.test()
async def banked_core_reads_aligned_eight_row_batch_after_one_cycle(dut): async def banked_core_reads_aligned_eight_row_batch_after_one_cycle(dut):
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
@@ -47,3 +67,35 @@ async def banked_core_reads_aligned_eight_row_batch_after_one_cycle(dut):
got_hash = (int(dut.rd_hashes_o.value) >> (lane * HASH_BITS)) & ((1 << HASH_BITS) - 1) got_hash = (int(dut.rd_hashes_o.value) >> (lane * HASH_BITS)) & ((1 << HASH_BITS) - 1)
assert got_row == lane assert got_row == lane
assert got_hash == lane + 0x100 assert got_hash == lane + 0x100
@cocotb.test()
async def banked_core_keeps_internal_bank_addresses_isolated(dut):
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_core(dut)
LANES = len(dut.rd_lane_valid_o)
ROW_BITS = len(dut.rd_row_ids_o) // LANES
HASH_BITS = len(dut.rd_hashes_o) // LANES
expected_by_row = {}
for bank_addr in range(3):
base_row = bank_addr * LANES
for lane in range(LANES):
row = base_row + lane
hash_value = 0xABC000 + (bank_addr << 8) + lane
expected_by_row[row] = hash_value
await write_row(dut, row, hash_value)
for bank_addr in range(3):
base_row = bank_addr * LANES
await request_batch(dut, base_row)
assert int(dut.rd_valid_o.value) == 1
assert int(dut.rd_lane_valid_o.value) == (1 << LANES) - 1
for lane in range(LANES):
row = base_row + lane
got_row = lane_word(int(dut.rd_row_ids_o.value), lane, ROW_BITS)
got_hash = lane_word(int(dut.rd_hashes_o.value), lane, HASH_BITS)
assert got_row == row
assert got_hash == expected_by_row[row]