feat(rtl): migrate CAM interface to handshake protocol with integrated noise generation

BREAKING CHANGE: CAM write and query interface replaced with standard valid/ready
handshake. wr_en/wr_row/wr_hash → wr_valid/wr_ready/wr_addr/write_hash.
External noise_mask_lanes_flat removed; noise generation now handled internally
by cam_noisy module with configurable rate via parameters.

- cam_top: add parameters (NOISE_EN, NOISE_RATE_NUM/DEN, NOISE_GEN/SAMPLE_BITS, NOISE_SEED)
- cam_top: replace cam_core with cam_noisy (integrated noise generation)
- match_engine: remove external noise_mask_lanes_flat input
- hw/sim: update Makefile with noise parameters and compile args
- hw/sim/model: add generate_write_flip_mask() and xorshift64() matching RTL behavior
- hw/sim/tests: adapt testbench to new handshake protocol
This commit is contained in:
2026-05-04 18:02:34 +08:00
parent 0ae6d757dc
commit 2da17e101b
10 changed files with 723 additions and 201 deletions

View File

@@ -1,9 +1,41 @@
"""Sweep write-noise rates and measure top-1 stability.
Applies write-noise flip masks to stored rows (simulating noisy writes),
then queries the noisy rows and compares top-1 results against clean rows.
"""
from __future__ import annotations
import argparse
import numpy as np
from model.ref_model import match_top1, random_hashes, random_noise_masks
from model.ref_model import (
generate_write_flip_mask,
match_top1,
random_hashes,
)
def apply_write_noise(
rows: list[int],
*,
width: int,
rate_num: int,
rate_den: int,
noise_gen_bits: int = 8,
noise_sample_bits: int = 8,
seed: int = 0,
) -> list[int]:
"""Apply write-noise flip masks to every row, returning noisy copies."""
noisy: list[int] = []
state = 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
)
noisy.append((row ^ flip) & mask_w)
return noisy
def main() -> None:
@@ -12,6 +44,8 @@ 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(
"--rates",
type=float,
@@ -29,19 +63,30 @@ def main() -> None:
clean_results = [match_top1(q, rows, width=args.width) for q in queries]
print("rate,top1_stability,avg_clean_margin")
# Use a fixed denominator matching the 8-bit sample space (2^8 = 256).
# Note: floor() is used, matching RTL threshold = (rate_num * 256) // rate_den.
# Rates below 1/256 (≈0.39%) collapse to zero under this scheme.
rate_den = 256
print("rate,rate_num,effective_prob,top1_stability,avg_clean_margin")
for rate in args.rates:
rate_num = int(rate * rate_den)
effective = rate_num / rate_den if rate_den > 0 else 0.0
stable = 0
margins = []
noisy_rows = apply_write_noise(
rows,
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,
seed=args.seed,
)
for q, clean in zip(queries, clean_results):
noise_masks = random_noise_masks(
rng,
args.rows,
width=args.width,
bit_flip_rate=rate,
)
noisy = match_top1(q, rows, width=args.width, noise_masks=noise_masks)
noisy = match_top1(q, noisy_rows, width=args.width)
if noisy.top1_index == clean.top1_index:
stable += 1
@@ -50,7 +95,7 @@ def main() -> None:
margin = int(sorted_scores[-1] - sorted_scores[-2])
margins.append(margin)
print(f"{rate},{stable / args.queries:.6f},{np.mean(margins):.3f}")
print(f"{rate},{rate_num},{effective:.6f},{stable / args.queries:.6f},{np.mean(margins):.3f}")
if __name__ == "__main__":