mirror of
https://github.com/SikongJueluo/Mini-Nav.git
synced 2026-07-12 20:15:31 +08:00
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
This commit is contained in:
@@ -6,7 +6,9 @@
|
||||
"python *": "deny",
|
||||
"make": "deny",
|
||||
"make *": "deny",
|
||||
"just remote *": "ask"
|
||||
"just remote *": "ask",
|
||||
"verilator": "deny",
|
||||
"verilator *": "deny"
|
||||
},
|
||||
"read": {
|
||||
".env": "deny"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"-h",
|
||||
"-v"
|
||||
],
|
||||
"reason": "Local Environment is not avaiable. Please use \"just remote <cmd>\" 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 <cmd>\" to run commands in the remote docker. "
|
||||
},
|
||||
{
|
||||
"name": "block-uv-sync",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user