mirror of
https://github.com/SikongJueluo/Mini-Nav.git
synced 2026-07-12 20:15:31 +08:00
feat(hw): add banked CAM pipeline with grouped read/write noise
- Add cam_core_banked.sv with 8-lane banked CAM core - Add cam_write_noise.sv and cam_read_noise.sv for grouped noise injection - Add noise_mask_grouped.sv generating grouped flip masks from 128-bit PRNG - Add match_engine_pipeline.sv with multi-stage pipelined top-1 selection - Add popcount_pipeline.sv for pipelined popcount operations - Refactor test_cam_basic.py with parametrized DUT introspection helpers - Add Python ref_model match_top1_with_read_noise() for read noise verification - Update Makefile with separate WRITE_NOISE_* and READ_NOISE_* parameter groups - Add new testbenches: test_cam_core_banked, test_cam_read_noise, test_cam_write_noise, test_match_engine_pipeline, test_ref_model_noise breaking change hint: NUM_ROWS default changed from 512→4096, LANES from 16→8
This commit is contained in:
@@ -101,6 +101,130 @@ def generate_write_flip_mask(
|
||||
return mask, state
|
||||
|
||||
|
||||
def generate_grouped_flip_mask(
|
||||
*,
|
||||
random_value: int,
|
||||
hash_bits: int,
|
||||
noise_bits: int,
|
||||
rate_num: int,
|
||||
rate_den: int,
|
||||
) -> int:
|
||||
"""Generate a grouped flip mask from one 128-bit value.
|
||||
|
||||
This is the shared write/read noise model: 8 default 64-bit groups, one
|
||||
candidate flip per group, 6-bit bit index and 8-bit threshold sample.
|
||||
It is not independent Bernoulli sampling over all 512 bits.
|
||||
"""
|
||||
assert noise_bits > 0
|
||||
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
|
||||
assert rate_den > 0
|
||||
assert 0 <= rate_num <= rate_den
|
||||
|
||||
sample_range = 1 << sample_bits
|
||||
threshold = (rate_num * sample_range) // rate_den
|
||||
mask = 0
|
||||
|
||||
for group_idx in range(noise_bits):
|
||||
group_rand = (random_value >> (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
|
||||
|
||||
|
||||
def lane_seed_128(seed: int, lane: int) -> int:
|
||||
"""Derive a nonzero 128-bit lane seed matching the RTL salt convention."""
|
||||
mask128 = (1 << 128) - 1
|
||||
salt = ((lane + 1) * 0x9E37_79B9_7F4A_7C15) & ((1 << 64) - 1)
|
||||
mixed64 = (int(seed) ^ salt) & ((1 << 64) - 1)
|
||||
state = ((mixed64 << 64) | mixed64) & mask128
|
||||
assert state != 0
|
||||
return state
|
||||
|
||||
|
||||
def generate_read_lane_masks(
|
||||
lane_states: list[int],
|
||||
*,
|
||||
hash_bits: int,
|
||||
noise_bits: int,
|
||||
rate_num: int,
|
||||
rate_den: int,
|
||||
lane_valid: list[bool],
|
||||
) -> tuple[list[int], list[int]]:
|
||||
"""Advance valid lane PRNG states once and return one mask per lane."""
|
||||
next_states: list[int] = []
|
||||
masks: list[int] = []
|
||||
|
||||
for lane, state in enumerate(lane_states):
|
||||
if lane_valid[lane]:
|
||||
next_state = xorshift128(state)
|
||||
mask = generate_grouped_flip_mask(
|
||||
random_value=next_state,
|
||||
hash_bits=hash_bits,
|
||||
noise_bits=noise_bits,
|
||||
rate_num=rate_num,
|
||||
rate_den=rate_den,
|
||||
)
|
||||
else:
|
||||
next_state = state
|
||||
mask = 0
|
||||
next_states.append(next_state)
|
||||
masks.append(mask)
|
||||
|
||||
return masks, next_states
|
||||
|
||||
|
||||
def match_top1_with_read_noise(
|
||||
query: int,
|
||||
rows: Sequence[int],
|
||||
*,
|
||||
width: int = 512,
|
||||
lanes: int = 8,
|
||||
noise_bits: int = 8,
|
||||
rate_num: int = 1,
|
||||
rate_den: int = 100,
|
||||
seed: int = 0x6A09_E667_F3BC_C909,
|
||||
) -> MatchResult:
|
||||
"""Top-1 matching with dynamic read noise, one query in flight."""
|
||||
assert lanes > 0
|
||||
assert len(rows) % lanes == 0
|
||||
|
||||
scores = np.zeros(len(rows), dtype=np.int32)
|
||||
best_index = 0
|
||||
best_score = -1
|
||||
lane_states = [lane_seed_128(seed, lane) for lane in range(lanes)]
|
||||
|
||||
for base in range(0, len(rows), lanes):
|
||||
lane_valid = [True] * lanes
|
||||
masks, lane_states = generate_read_lane_masks(
|
||||
lane_states,
|
||||
hash_bits=width,
|
||||
noise_bits=noise_bits,
|
||||
rate_num=rate_num,
|
||||
rate_den=rate_den,
|
||||
lane_valid=lane_valid,
|
||||
)
|
||||
|
||||
for lane in range(lanes):
|
||||
row_idx = base + lane
|
||||
noisy_row = int(rows[row_idx]) ^ masks[lane]
|
||||
score = xnor_popcount_score(int(query), noisy_row, width)
|
||||
scores[row_idx] = score
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_index = row_idx
|
||||
|
||||
return MatchResult(top1_index=int(best_index), top1_score=int(best_score), scores=scores)
|
||||
|
||||
|
||||
def random_hashes(
|
||||
rng: np.random.Generator,
|
||||
n: int,
|
||||
|
||||
Reference in New Issue
Block a user