refactor(cam): remove read noise from noise architecture (Phase 2)

- Make cam_read_noise a pass-through module, removing all noise injection logic
- Switch write noise to use noise_mask_bernoulli instead of noise_mask_grouped
- Add state machine to cam_write_noise for mask generation timing
- Remove noise_mask_grouped.sv (no longer needed)
- Remove read noise parameters from cam_noisy and cam_top
- Update simulation and benchmark code to reflect read noise removal
- Sync documentation to reflect Phase 2 architecture
This commit is contained in:
2026-05-26 23:02:22 +08:00
parent e5d13917b2
commit 8b4d4c1b57
29 changed files with 277 additions and 863 deletions

View File

@@ -10,6 +10,4 @@ HASH_BITS ?= 512
WRITE_NOISE_EN ?= 1
WRITE_NOISE_RATE_NUM ?= 1
WRITE_NOISE_RATE_DEN ?= 100
WRITE_NOISE_BITS ?= $(shell echo $$(( $(HASH_BITS) / 64 )))
include $(SIM_ROOT)/mk/cocotb-common.mk

View File

@@ -2,8 +2,32 @@ from __future__ import annotations
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge
from model.ref_model import generate_write_flip_mask
from cocotb.triggers import RisingEdge, Timer
# Bernoulli: 1 PRIME + 16 RUN = 17 cycles internal
# + 1 cycle for mask_start propagation + 1 cycle for core_wr_valid output = 19
DEFAULT_WRITE_NOISE_LATENCY = 19
async def pulse_write(dut, row: int, value: int):
dut.wr_row.value = row
dut.wr_hash.value = value
dut.wr_valid.value = 1
await Timer(1, unit="step")
assert int(dut.wr_ready.value) == 1
await RisingEdge(dut.clk)
await Timer(1, unit="step")
dut.wr_valid.value = 0
async def wait_core_write(dut, max_cycles: int = 128) -> int:
cycles = 0
while int(dut.core_wr_valid.value) == 0:
assert cycles < max_cycles, "timed out waiting for core_wr_valid"
await RisingEdge(dut.clk)
await Timer(1, unit="step")
cycles += 1
return cycles
async def reset_write_noise(dut):
@@ -19,23 +43,52 @@ async def reset_write_noise(dut):
@cocotb.test()
async def write_noise_outputs_grouped_noisy_hash(dut):
async def write_noise_enabled_applies_bernoulli_mask_after_generation(dut):
"""Noise active: FSM enters WAIT_MASK, core_wr_hash deterministic across reset."""
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_write_noise(dut)
value = 0x123456789ABCDEF
dut.wr_row.value = 3
dut.wr_hash.value = value
dut.wr_valid.value = 1
value = (1 << 512) - 1 # all-ones: even low-rate Bernoulli may flip some bits
await pulse_write(dut, row=3, value=value)
await Timer(1, unit="step")
assert int(dut.wr_ready.value) == 0
cycles = await wait_core_write(dut)
assert cycles == DEFAULT_WRITE_NOISE_LATENCY
assert int(dut.core_wr_row.value) == 3
hash_after_first = int(dut.core_wr_hash.value)
await RisingEdge(dut.clk)
await Timer(1, unit="step")
assert int(dut.core_wr_valid.value) == 0
assert int(dut.wr_ready.value) == 1
# Deterministic across reset: same seed → same mask → same noisy hash
await reset_write_noise(dut)
await pulse_write(dut, row=3, value=value)
await wait_core_write(dut)
assert int(dut.core_wr_hash.value) == hash_after_first
@cocotb.test()
async def write_noise_backpressures_second_write_until_done(dut):
cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
await reset_write_noise(dut)
await pulse_write(dut, row=1, value=0xAA55)
dut.wr_row.value = 2
dut.wr_hash.value = 0x55AA
dut.wr_valid.value = 1
await Timer(1, unit="step")
for _ in range(4):
assert int(dut.wr_ready.value) == 0
assert int(dut.core_wr_valid.value) == 0
await RisingEdge(dut.clk)
await Timer(1, unit="step")
dut.wr_valid.value = 0
while int(dut.core_wr_valid.value) == 0:
await RisingEdge(dut.clk)
seed = 0xB504_F32D_B504_F32D
hash_bits = len(dut.wr_hash)
noise_bits = hash_bits // 64
flip, _ = generate_write_flip_mask((seed << 64) | seed, hash_bits, noise_bits, 1, 100)
assert int(dut.core_wr_row.value) == 3
assert int(dut.core_wr_hash.value) == (value ^ flip)
cycles = await wait_core_write(dut)
assert cycles == DEFAULT_WRITE_NOISE_LATENCY - 4 # 19 - 4 = 15
assert int(dut.core_wr_row.value) == 1