From cbafc4524e02e3ad5adab4e5596ebb703ec1fb79 Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Tue, 5 May 2026 19:30:51 +0800 Subject: [PATCH] feat(cam): migrate noise generation from xorshift64 to xorshift128 - Replace NOISE_GEN_BITS/NOISE_SAMPLE_BITS parameters with unified NOISE_BITS - Use xorshift128 (random128) instead of xorshift64 for PRNG - Add flip_mask_next combinational helper for single-cycle mask computation - Add random_enable signal to advance PRNG only on accepted noisy writes - Simplify FSM by removing mask_group_idx counter - Update parameter validation: GROUP_BITS (= HASH_BITS/NOISE_BITS) must equal 64 - Update ref_model.py and tests to match new seed convention: {seed, seed} - Update Makefile and sweep_noise.py with renamed parameters --- .opencode/opencode.json | 4 +- .safety-net.json | 2 +- hw/rtl/cam_noisy.sv | 156 ++++++++++++++++----------------- hw/rtl/cam_top.sv | 6 +- hw/sim/Makefile | 10 +-- hw/sim/model/ref_model.py | 66 +++++++------- hw/sim/sweep_noise.py | 19 ++-- hw/sim/tests/test_cam_basic.py | 67 +++++++++----- 8 files changed, 178 insertions(+), 152 deletions(-) diff --git a/.opencode/opencode.json b/.opencode/opencode.json index 9511065..d28cdc1 100644 --- a/.opencode/opencode.json +++ b/.opencode/opencode.json @@ -6,7 +6,9 @@ "python *": "deny", "make": "deny", "make *": "deny", - "just remote *": "ask" + "just remote *": "ask", + "verilator": "deny", + "verilator *": "deny" }, "read": { ".env": "deny" diff --git a/.safety-net.json b/.safety-net.json index 9b14779..0f8dbf2 100644 --- a/.safety-net.json +++ b/.safety-net.json @@ -18,7 +18,7 @@ "-h", "-v" ], - "reason": "Local Environment is not avaiable. Please use \"just remote \" to run commands in the remote docker. And remember to run \"just upload\" before run commands in the remote docker." + "reason": "Local Environment is not avaiable. Please use \"just remote \" to run commands in the remote docker. " }, { "name": "block-uv-sync", diff --git a/hw/rtl/cam_noisy.sv b/hw/rtl/cam_noisy.sv index d85cde4..81d02cf 100644 --- a/hw/rtl/cam_noisy.sv +++ b/hw/rtl/cam_noisy.sv @@ -2,46 +2,59 @@ `include "cam_params.svh" // Design constraints: -// HASH_BITS % NOISE_GEN_BITS == 0 -// NOISE_GEN_BITS * NOISE_SAMPLE_BITS == 64 +// HASH_BITS % NOISE_BITS == 0 +// GROUP_BITS (= HASH_BITS / NOISE_BITS) == 64 (needed for 6-bit index) +// NOISE_BITS * GROUP_RAND_BITS <= 128 // 0 <= NOISE_RATE_NUM <= NOISE_RATE_DEN // NOISE_RATE_DEN > 0 // NOISE_SEED != 64'd0 +// NOISE_BITS > 0 module cam_noisy #( - parameter bit NOISE_EN = 1'b1, - parameter int NOISE_RATE_NUM = 1, - parameter int NOISE_RATE_DEN = 100, - parameter int NOISE_GEN_BITS = 8, - parameter int NOISE_SAMPLE_BITS = 8, - parameter logic [63:0] NOISE_SEED = 64'hB504_F32D_B504_F32D + parameter bit NOISE_EN = 1'b1, + parameter int NOISE_RATE_NUM = 70, + parameter int NOISE_RATE_DEN = 100, + parameter logic [63:0] NOISE_SEED = 64'hB504_F32D_B504_F32D, + parameter int NOISE_BITS = 8 ) ( - input logic clk, - input logic rst_n, + input logic clk, + input logic rst_n, // Write interface (handshake) - input logic wr_valid, - output logic wr_ready, - input logic [(`ROW_BITS)-1:0] wr_addr, - input logic [(`HASH_BITS)-1:0] write_hash, + input logic wr_valid, + output logic wr_ready, + input logic [ (`ROW_BITS)-1:0] wr_addr, + input logic [(`HASH_BITS)-1:0] write_hash, // Read interface (passthrough to cam_core, combinational read) - input logic [(`LANES)*(`ROW_BITS)-1:0] rd_addr_lanes_flat, - output logic [(`LANES)*(`HASH_BITS)-1:0] rd_hash_lanes_flat + input logic [ (`LANES)*(`ROW_BITS)-1:0] rd_addr_lanes_flat, + output logic [(`LANES)*(`HASH_BITS)-1:0] rd_hash_lanes_flat ); + // ── Local parameters ── + localparam int GROUP_BITS = `HASH_BITS / NOISE_BITS; + localparam int BIT_INDEX_BITS = 6; + localparam int SAMPLE_BITS = 8; + localparam int GROUP_RAND_BITS = BIT_INDEX_BITS + SAMPLE_BITS; // 14 + localparam int SAMPLE_RANGE = 2 ** SAMPLE_BITS; // 256 + localparam int THRESHOLD = (NOISE_RATE_NUM * SAMPLE_RANGE) / NOISE_RATE_DEN; + // ── Elaboration-time parameter checks ── initial begin - if (`HASH_BITS % NOISE_GEN_BITS != 0) - $fatal(1, "HASH_BITS (%0d) must be divisible by NOISE_GEN_BITS (%0d)", `HASH_BITS, NOISE_GEN_BITS); - if (NOISE_GEN_BITS * NOISE_SAMPLE_BITS != 64) - $fatal(1, "NOISE_GEN_BITS*NOISE_SAMPLE_BITS must equal 64, got %0d", NOISE_GEN_BITS * NOISE_SAMPLE_BITS); + if (NOISE_BITS <= 0) + $fatal(1, "NOISE_BITS must be > 0, got %0d", NOISE_BITS); + if (`HASH_BITS % NOISE_BITS != 0) + $fatal(1, "HASH_BITS (%0d) must be divisible by NOISE_BITS (%0d)", `HASH_BITS, NOISE_BITS); + if (GROUP_BITS != 64) + $fatal(1, "GROUP_BITS (= HASH_BITS/NOISE_BITS) must equal 64 for 6-bit index, got %0d", GROUP_BITS); + if (NOISE_BITS * GROUP_RAND_BITS > 128) + $fatal(1, "NOISE_BITS*GROUP_RAND_BITS must be <= 128, got %0d", NOISE_BITS * GROUP_RAND_BITS); if (NOISE_RATE_DEN <= 0) $fatal(1, "NOISE_RATE_DEN must be > 0, got %0d", NOISE_RATE_DEN); if (NOISE_RATE_NUM < 0 || NOISE_RATE_NUM > NOISE_RATE_DEN) $fatal(1, "NOISE_RATE_NUM (%0d) must be in [0, NOISE_RATE_DEN=%0d]", NOISE_RATE_NUM, NOISE_RATE_DEN); if (NOISE_SEED == 64'd0) - $fatal(1, "NOISE_SEED must be nonzero — xorshift64 with zero seed never advances"); + $fatal(1, "NOISE_SEED must be nonzero — xorshift128/random128 with zero seed never advances"); end // ── FSM states ── @@ -58,24 +71,20 @@ module cam_noisy #( logic [(`HASH_BITS)-1:0] write_hash_q; // ── Noise generation ── - logic [63:0] prng_state; logic [(`HASH_BITS)-1:0] flip_mask; - localparam int MASK_GROUPS = `HASH_BITS / NOISE_GEN_BITS; - logic [($clog2(`HASH_BITS/NOISE_GEN_BITS+1))-1:0] mask_group_idx; // 0..HASH_BITS/NOISE_GEN_BITS-1 + logic [127:0] random_num; - // ── Precomputed threshold ── - localparam int SAMPLE_RANGE = 1 << NOISE_SAMPLE_BITS; - localparam int THRESHOLD = (NOISE_RATE_NUM * SAMPLE_RANGE) / NOISE_RATE_DEN; - - // ── xorshift64 PRNG function ── - function automatic logic [63:0] xorshift64(input logic [63:0] x); - logic [63:0] s; - s = x; - s ^= s << 13; - s ^= s >> 7; - s ^= s << 17; - return s; - endfunction + // ── Combinational next-mask helper ── + // Computes flip_mask_next from fresh random_num in one cycle. + logic [(`HASH_BITS)-1:0] flip_mask_next; + always_comb begin + flip_mask_next = '0; + for (int i = 0; i < NOISE_BITS; i++) begin + if (random_num[i * GROUP_RAND_BITS + BIT_INDEX_BITS +: SAMPLE_BITS] < THRESHOLD) begin + flip_mask_next[i * GROUP_BITS + random_num[i * GROUP_RAND_BITS +: BIT_INDEX_BITS]] = 1'b1; + end + end + end // ── Noisy hash for cam_core write ── logic [(`HASH_BITS)-1:0] noisy_hash; @@ -84,6 +93,10 @@ module cam_noisy #( // ── wr_ready: only in IDLE ── assign wr_ready = (curr_state == S_IDLE); + // ── random128 enable: advance random on accepted noisy write ── + logic random_enable; + assign random_enable = wr_ready && wr_valid && NOISE_EN && (NOISE_RATE_NUM > 0); + // ── FSM block 1: state register ── always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin @@ -99,16 +112,13 @@ module cam_noisy #( case (curr_state) S_IDLE: begin if (wr_valid && wr_ready) begin - if (NOISE_EN && (NOISE_RATE_NUM > 0)) - next_state = S_GEN_MASK; - else - next_state = S_COMMIT; + if (NOISE_EN && (NOISE_RATE_NUM > 0)) next_state = S_GEN_MASK; + else next_state = S_COMMIT; end end S_GEN_MASK: begin - if (int'(mask_group_idx) == (MASK_GROUPS - 1)) - next_state = S_COMMIT; + next_state = S_COMMIT; end S_COMMIT: begin @@ -122,17 +132,13 @@ module cam_noisy #( // ── FSM block 3: state actions / datapath registers ── always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin - addr_q <= '0; - write_hash_q <= '0; - flip_mask <= '0; - prng_state <= NOISE_SEED; - mask_group_idx <= '0; + addr_q <= '0; + write_hash_q <= '0; + flip_mask <= '0; end else begin - case (curr_state) S_IDLE: begin - flip_mask <= '0; - mask_group_idx <= '0; + flip_mask <= '0; if (wr_valid && wr_ready) begin addr_q <= wr_addr; write_hash_q <= write_hash; @@ -140,36 +146,21 @@ module cam_noisy #( end S_GEN_MASK: begin - // Advance PRNG — sample from NEW state to match ref_model - logic [63:0] prng_next; - prng_next = xorshift64(prng_state); - prng_state <= prng_next; - - // Generate NOISE_GEN_BITS flip decisions from 64-bit PRNG output - for (int b = 0; b < NOISE_GEN_BITS; b++) begin - logic [NOISE_SAMPLE_BITS-1:0] sample; - sample = prng_next[b * NOISE_SAMPLE_BITS +: NOISE_SAMPLE_BITS]; - if (int'(sample) < THRESHOLD) begin - flip_mask[mask_group_idx * NOISE_GEN_BITS + b] <= 1'b1; - end - end - - mask_group_idx <= mask_group_idx + 1; + flip_mask <= flip_mask_next; end S_COMMIT: begin - // Write noisy hash to cam_core (one cycle) - // cam_core.wr_en is asserted here via comb assign below + // Write happens via combinational core_wr_en end - default: ; // No-op + default: ; // No-op endcase end end // ── cam_core instance ── - logic core_wr_en; - logic [(`ROW_BITS)-1:0] core_wr_row; + logic core_wr_en; + logic [ (`ROW_BITS)-1:0] core_wr_row; logic [(`HASH_BITS)-1:0] core_wr_hash; assign core_wr_en = (curr_state == S_COMMIT); @@ -177,13 +168,22 @@ module cam_noisy #( assign core_wr_hash = noisy_hash; cam_core u_core ( - .clk (clk), - .rst_n (rst_n), - .wr_en (core_wr_en), - .wr_row (core_wr_row), - .wr_hash (core_wr_hash), - .rd_addr_lanes_flat (rd_addr_lanes_flat), - .rd_hash_lanes_flat (rd_hash_lanes_flat) + .clk (clk), + .rst_n (rst_n), + .wr_en (core_wr_en), + .wr_row (core_wr_row), + .wr_hash (core_wr_hash), + .rd_addr_lanes_flat(rd_addr_lanes_flat), + .rd_hash_lanes_flat(rd_hash_lanes_flat) + ); + + // ── Random number generator ── + random128 u_random_128 ( + .clk (clk), + .rst_n (rst_n), + .enable(random_enable), + .seed ({NOISE_SEED, NOISE_SEED}), + .out (random_num) ); endmodule diff --git a/hw/rtl/cam_top.sv b/hw/rtl/cam_top.sv index 5fcc6cc..64b5362 100644 --- a/hw/rtl/cam_top.sv +++ b/hw/rtl/cam_top.sv @@ -5,8 +5,7 @@ module cam_top #( parameter bit NOISE_EN = 1'b1, parameter int NOISE_RATE_NUM = 1, parameter int NOISE_RATE_DEN = 100, - parameter int NOISE_GEN_BITS = 8, - parameter int NOISE_SAMPLE_BITS = 8, + parameter int NOISE_BITS = 8, parameter logic [63:0] NOISE_SEED = 64'hB504_F32D_B504_F32D ) ( input logic clk, @@ -63,8 +62,7 @@ module cam_top #( .NOISE_EN (NOISE_EN), .NOISE_RATE_NUM (NOISE_RATE_NUM), .NOISE_RATE_DEN (NOISE_RATE_DEN), - .NOISE_GEN_BITS (NOISE_GEN_BITS), - .NOISE_SAMPLE_BITS (NOISE_SAMPLE_BITS), + .NOISE_BITS (NOISE_BITS), .NOISE_SEED (NOISE_SEED) ) u_noisy ( .clk (clk), diff --git a/hw/sim/Makefile b/hw/sim/Makefile index 4a78d4b..ccffbd2 100644 --- a/hw/sim/Makefile +++ b/hw/sim/Makefile @@ -13,10 +13,10 @@ LANES ?= 16 NOISE_EN ?= 1 NOISE_RATE_NUM ?= 1 NOISE_RATE_DEN ?= 100 -NOISE_GEN_BITS ?= 8 -NOISE_SAMPLE_BITS ?= 8 +NOISE_BITS ?= 8 # NOISE_SEED cannot be overridden via Makefile due to Verilator -G quoting issues -# with 64'h hex literals. To change the seed, edit the default in cam_noisy.sv. +# with 64'h hex literals. To change the seed, edit the default in cam_top.sv +# (sim top is cam_top, which passes it to cam_noisy). EXTRA_ARGS += +define+NUM_ROWS=$(NUM_ROWS) +define+HASH_BITS=$(HASH_BITS) +define+LANES=$(LANES) @@ -29,8 +29,7 @@ COMPILE_ARGS += $(EXTRA_DEFINES) COMPILE_ARGS += -GNOISE_EN=$(NOISE_EN) COMPILE_ARGS += -GNOISE_RATE_NUM=$(NOISE_RATE_NUM) COMPILE_ARGS += -GNOISE_RATE_DEN=$(NOISE_RATE_DEN) -COMPILE_ARGS += -GNOISE_GEN_BITS=$(NOISE_GEN_BITS) -COMPILE_ARGS += -GNOISE_SAMPLE_BITS=$(NOISE_SAMPLE_BITS) +COMPILE_ARGS += -GNOISE_BITS=$(NOISE_BITS) # Cleaner terminal output export QUIET ?= 1 @@ -48,6 +47,7 @@ endif VERILOG_SOURCES += $(PWD)/../rtl/popcount.sv VERILOG_SOURCES += $(PWD)/../rtl/argmax_update.sv VERILOG_SOURCES += $(PWD)/../rtl/cam_core.sv +VERILOG_SOURCES += $(PWD)/../rtl/random/random128.sv VERILOG_SOURCES += $(PWD)/../rtl/cam_noisy.sv VERILOG_SOURCES += $(PWD)/../rtl/match_engine.sv VERILOG_SOURCES += $(PWD)/../rtl/cam_top.sv diff --git a/hw/sim/model/ref_model.py b/hw/sim/model/ref_model.py index b9a571e..52c935a 100644 --- a/hw/sim/model/ref_model.py +++ b/hw/sim/model/ref_model.py @@ -53,50 +53,50 @@ def match_top1( ) -def xorshift64(state: int) -> int: - """64-bit XOR-shift PRNG, single step. Matches RTL behavior.""" - mask64 = (1 << 64) - 1 - s = state & mask64 - s ^= (s << 13) & mask64 - s ^= (s >> 7) & mask64 - s ^= (s << 17) & mask64 - return s +def xorshift128(state: int) -> int: + """128-bit xorshift PRNG, single step. Matches random128.sv.""" + mask32 = (1 << 32) - 1 + mask128 = (1 << 128) - 1 + s = state & mask128 + x = (s >> 96) & mask32 + y = (s >> 64) & mask32 + z = (s >> 32) & mask32 + w = s & mask32 + t = (x ^ ((x << 11) & mask32)) & mask32 + next_x = y + next_y = z + next_z = w + next_w = (w ^ (w >> 19) ^ t ^ (t >> 8)) & mask32 + return ((next_x << 96) | (next_y << 64) | (next_z << 32) | next_w) & mask128 def generate_write_flip_mask( prng_state: int, hash_bits: int, - noise_gen_bits: int, - noise_sample_bits: int, + noise_bits: int, rate_num: int, rate_den: int, ) -> tuple[int, int]: - """ - Generate write-noise flip mask. - Returns (flip_mask, next_prng_state). - Matches RTL multi-cycle GEN_MASK behavior. + """Generate one write-noise flip mask using one xorshift128 step.""" + assert hash_bits % noise_bits == 0 + group_bits = hash_bits // noise_bits + bit_index_bits = 6 + sample_bits = 8 + group_random_bits = bit_index_bits + sample_bits + assert group_bits == 64 + assert noise_bits * group_random_bits <= 128 - Each cycle processes noise_gen_bits bit decisions: - - Advance xorshift64 → 64-bit output - - Split into noise_gen_bits x noise_sample_bits-bit samples - - Each sample < THRESHOLD → that bit flips - """ - assert hash_bits % noise_gen_bits == 0 - assert noise_gen_bits * noise_sample_bits == 64 - mask = 0 - state = prng_state - sample_range = 1 << noise_sample_bits + sample_range = 1 << sample_bits threshold = (rate_num * sample_range) // rate_den - for bit_offset in range(0, hash_bits, noise_gen_bits): - # Advance PRNG - state = xorshift64(state) - - # Split into noise_gen_bits independent samples - for b in range(noise_gen_bits): - sample_b = (state >> (b * noise_sample_bits)) & (sample_range - 1) - if sample_b < threshold: - mask |= (1 << (bit_offset + b)) + state = xorshift128(prng_state) + mask = 0 + for group_idx in range(noise_bits): + group_rand = (state >> (group_idx * group_random_bits)) & ((1 << group_random_bits) - 1) + bit_idx = group_rand & ((1 << bit_index_bits) - 1) + sample = (group_rand >> bit_index_bits) & (sample_range - 1) + if sample < threshold: + mask |= 1 << (group_idx * group_bits + bit_idx) return mask, state diff --git a/hw/sim/sweep_noise.py b/hw/sim/sweep_noise.py index 7c7ae8f..d852246 100644 --- a/hw/sim/sweep_noise.py +++ b/hw/sim/sweep_noise.py @@ -22,17 +22,20 @@ def apply_write_noise( width: int, rate_num: int, rate_den: int, - noise_gen_bits: int = 8, - noise_sample_bits: int = 8, + noise_bits: int = 8, seed: int = 0, ) -> list[int]: - """Apply write-noise flip masks to every row, returning noisy copies.""" + """Apply write-noise flip masks to every row, returning noisy copies. + + *seed* is a 64-bit value (RTL NOISE_SEED). It is duplicated to form + the 128-bit xorshift initial state: {seed, seed}. + """ noisy: list[int] = [] - state = seed + state = (seed << 64) | seed mask_w = (1 << width) - 1 for row in rows: flip, state = generate_write_flip_mask( - state, width, noise_gen_bits, noise_sample_bits, rate_num, rate_den + state, width, noise_bits, rate_num, rate_den ) noisy.append((row ^ flip) & mask_w) return noisy @@ -44,8 +47,7 @@ def main() -> None: parser.add_argument("--queries", type=int, default=128) parser.add_argument("--width", type=int, default=512) parser.add_argument("--seed", type=int, default=1234) - parser.add_argument("--noise-gen-bits", type=int, default=8) - parser.add_argument("--noise-sample-bits", type=int, default=8) + parser.add_argument("--noise-bits", type=int, default=8) parser.add_argument( "--rates", type=float, @@ -80,8 +82,7 @@ def main() -> None: width=args.width, rate_num=rate_num, rate_den=rate_den, - noise_gen_bits=args.noise_gen_bits, - noise_sample_bits=args.noise_sample_bits, + noise_bits=args.noise_bits, seed=args.seed, ) diff --git a/hw/sim/tests/test_cam_basic.py b/hw/sim/tests/test_cam_basic.py index b9428d8..395b899 100644 --- a/hw/sim/tests/test_cam_basic.py +++ b/hw/sim/tests/test_cam_basic.py @@ -9,7 +9,6 @@ from model.ref_model import ( # noqa: E402 match_top1, random_hashes, unpack_score_debug_flat, - xorshift64, ) NUM_ROWS = 512 @@ -205,19 +204,45 @@ async def zero_rate_noise(dut): @cocotb.test() async def full_rate_noise(dut): - """THRESHOLD=256 → all bits flip. stored == ~written.""" + """NOISE_RATE_NUM=1, NOISE_RATE_DEN=1 → every group flips its selected bit per write. + + At full rate with default NOISE_BITS=8, exactly 8 deterministic bits flip per write + (one selected bit per group), not all 512 bits. We use ref_model.py PRNG to predict + the exact stored rows. + """ noise_en = _get_param(dut, "NOISE_EN", 1) rate_num = _get_param(dut, "NOISE_RATE_NUM", 1) rate_den = _get_param(dut, "NOISE_RATE_DEN", 100) if not noise_en or rate_num != 1 or rate_den != 1: dut._log.info("Skipping full_rate_noise: requires NOISE_EN=1, RATE_NUM=1, RATE_DEN=1.") return + if not hasattr(dut, "score_debug_flat"): + dut._log.info("Skipping full_rate_noise: requires SIM_DEBUG (score_debug_flat).") + return + cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) await reset_dut(dut) + noise_bits = _get_param(dut, "NOISE_BITS", 8) all_zero = 0 all_one = (1 << HASH_BITS) - 1 + # Predict stored rows using the same RTL seed convention as exact_noise_model_match. + RTL_SEED = 0xB504_F32D_B504_F32D + prng_state = (RTL_SEED << 64) | RTL_SEED + + # Flip mask for row 0 (all-zero written) + flip0, prng_state = generate_write_flip_mask( + prng_state, HASH_BITS, noise_bits, rate_num, rate_den, + ) + expected_row0 = all_zero ^ flip0 # stored value after noise + + # Flip mask for row 1 (all-one written) + flip1, prng_state = generate_write_flip_mask( + prng_state, HASH_BITS, noise_bits, rate_num, rate_den, + ) + expected_row1 = all_one ^ flip1 # stored value after noise + # Write all-zero to row 0, all-one to row 1, rest zero rows = [0] * NUM_ROWS rows[0] = all_zero @@ -225,20 +250,21 @@ async def full_rate_noise(dut): await write_rows(dut, rows) - # After 100% flip: row 0 stored = ~0 = all_one, row 1 stored = ~all_one = all_zero - # Query all-zero → matches row 1 (stored all_zero) with score = HASH_BITS - top1_index, top1_score, _ = await query_once(dut, all_zero) - assert top1_index == 1 - assert top1_score == HASH_BITS + # Query expected_row0 → should exactly match row 0's stored value + top1_index, top1_score, score_debug = await query_once(dut, expected_row0) + assert score_debug is not None, "score_debug required for full_rate_noise" + assert int(score_debug[0]) == HASH_BITS, ( + f"Row 0: expected exact match for predicted stored value, " + f"score={score_debug[0]} != {HASH_BITS}" + ) - # Query all-one → matches row 0 (stored all_one) with score = HASH_BITS - top1_index, top1_score, _ = await query_once(dut, all_one) - assert top1_index == 0 - assert top1_score == HASH_BITS - - # Query all-zero → score against row 0 (stored all_one) = 0 - # Re-query to verify: row 0 stored is all_one, query all_zero → score 0 - # But top1 picks row 1 with score HASH_BITS, so top1_index=1 + # Query expected_row1 → should exactly match row 1's stored value + top1_index, top1_score, score_debug = await query_once(dut, expected_row1) + assert score_debug is not None, "score_debug required for full_rate_noise" + assert int(score_debug[1]) == HASH_BITS, ( + f"Row 1: expected exact match for predicted stored value, " + f"score={score_debug[1]} != {HASH_BITS}" + ) # ── Test D: Default ~1% noise, reproducible ──────────────────────────────── @@ -378,22 +404,21 @@ async def exact_noise_model_match(dut): cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) await reset_dut(dut) - noise_gen_bits = _get_param(dut, "NOISE_GEN_BITS", 8) - noise_sample_bits = _get_param(dut, "NOISE_SAMPLE_BITS", 8) + noise_bits = _get_param(dut, "NOISE_BITS", 8) # Use a small subset to keep test fast n_test_rows = 4 rng = np.random.default_rng(99) rows = random_hashes(rng, n_test_rows, width=HASH_BITS) - # Predict stored hashes with Python model using the same seed - # RTL default seed: 64'hB504_F32D_B504_F32D + # Predict stored hashes with Python model using the same seed. + # RTL random128 seed: {NOISE_SEED, NOISE_SEED}, default 64'hB504_F32D_B504_F32D. RTL_SEED = 0xB504_F32D_B504_F32D - prng_state = RTL_SEED + prng_state = (RTL_SEED << 64) | RTL_SEED expected_stored = [] for row in rows: flip, prng_state = generate_write_flip_mask( - prng_state, HASH_BITS, noise_gen_bits, noise_sample_bits, rate_num, rate_den, + prng_state, HASH_BITS, noise_bits, rate_num, rate_den, ) expected_stored.append(row ^ flip)