fix(fpga): implement 5 P0 invariant fixes with adversarial testbenches

Fixes 5 critical cross-layer invariant violations found during
system-level analysis. Each fix has a dedicated adversarial testbench
that actively tries to break the fix under race conditions, reset
mid-operation, overflow, and pathological input patterns.

RTL fixes:
- Fix #1: Replace flawed cdc_adc_to_processing with Gray-coded async
  FIFO (cdc_async_fifo) for DDC 400->100 MHz CDC path. Pre-fetch
  show-ahead architecture with CDC-safe registered reads.
- Fix #2: XOR toggle detection for mc_new_chirp in matched filter
  (cross-clock-domain safe vs level-sensitive).
- Fix #3: ST_WAIT_LISTEN state with configurable listen_delay to
  prevent matched filter re-trigger during chirp dead time.
- Fix #4: Overlap-save output trim in matched filter to suppress
  circular convolution artifacts at segment boundaries.
- Fix #7: Falling-edge frame_complete pulse in doppler_processor
  (was stuck high, causing continuous AGC resets).

RTL cleanup:
- Refactor CDC synchronizer arrays from memory arrays to scalar regs
  for explicit ASYNC_REG flop naming and synthesis constraint clarity.

Testbenches (70 checks total, all passing):
- tb_p0_async_fifo.v: 20 checks (fill, overflow, reset, streaming,
  show-ahead capacity, pathological data patterns)
- tb_p0_mf_adversarial.v: 33 checks (toggle detection, listen state,
  overlap trim, rapid chirp sequences, reset recovery)
- tb_p0_frame_pulse.v: 17 checks (pulse width, idle behavior,
  processing duration sweep, regression vs old stuck-high bug)

Regression: 24/24 pass (--quick), 57/57 existing CDC tests pass.
Golden references updated for doppler output timing change.
This commit is contained in:
Jason
2026-04-17 01:53:06 +05:45
parent fa5e1dcdf4
commit 7742b517b6
10 changed files with 5954 additions and 4173 deletions
+18 -1
View File
@@ -531,6 +531,23 @@ xfft_16 fft_inst (
// Status Outputs
// ==============================================
assign processing_active = (state != S_IDLE);
assign frame_complete = (state == S_IDLE && frame_buffer_full == 0);
// frame_complete must be a single-cycle pulse, not a level.
// The AGC (rx_gain_control) uses this as frame_boundary to snapshot
// per-frame metrics and update gain. If held high continuously,
// the AGC would re-evaluate every clock with zeroed accumulators,
// collapsing saturation_count/peak_magnitude to zero.
//
// Detect the falling edge of processing_active: the exact clock
// when the Doppler processor finishes all sub-frame FFTs and
// returns to S_IDLE with the frame buffer drained.
reg processing_active_prev;
always @(posedge clk or negedge reset_n) begin
if (!reset_n)
processing_active_prev <= 1'b0;
else
processing_active_prev <= processing_active;
end
assign frame_complete = (~processing_active & processing_active_prev);
endmodule