mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-10 23:41:18 +00:00
ft2232h: add frame drop counter (AUDIT-C12) + cfar RMW cadence guard (AUDIT-S22)
AUDIT-C12: usb_data_interface_ft2232h had a misleading single-buffer comment that overstated the timing slack and referenced a frame_ack_toggle CDC that was never implemented. Re-verified actual numbers: at 178 fps the slack is 1.14 ms (20%), not "much shorter than gap". No data corruption today (write order matches read order, addresses don't collide), but frame_complete firing while WR_FSM is still draining the previous frame causes silent frame drops via the missed frame_ready_toggle edge. Fix is instrumentation, not architectural rework: add wr_done_toggle (ft_clk -> clk CDC) on WR_DONE -> WR_IDLE, track frame_pending in clk domain, count drops in 7-bit saturating frame_drop_count, surface in unused upper 7 bits of status_words[5]. Host now has visibility into the failure mode if margin ever shrinks (faster frame rate or USB bandwidth shortfall). Replaced misleading comment with corrected timing breakdown. AUDIT-S22: cfar_ca emits one detection per 3 cycles (THR/MUL/CMP); the detection RMW takes 3 cycles. Match by construction today, fragile against any CFAR speedup. Added a header comment in cfar_ca.v documenting the dependency, and a SIMULATION-only assertion in usb_data_interface_ft2232h.v that fires [ASSERT FAIL] AUDIT-S22 if cfar_valid arrives while RMW busy. Catches silent-drop regressions in the test suite. Verification: new tb_ft2232h_frame_drop.v with 5 scenarios (no drops / stalled drops / multi-drop / recovery / saturation at 127) - 10/10 PASS. Quick regression 31/31 PASS (was 30/30; +1 new test, 0 regressions).
This commit is contained in:
@@ -51,6 +51,21 @@
|
||||
* = 0.55 ms. Frame period @ PRF=1932 Hz, 32 chirps = 16.6 ms. Fits easily.
|
||||
* (3 cycles per CUT due to pipeline: THR → MUL → CMP)
|
||||
*
|
||||
* AUDIT-S22 — DOWNSTREAM CADENCE DEPENDENCY (DO NOT BREAK):
|
||||
* detect_valid pulses every 3rd cycle (one per CUT triplet). The downstream
|
||||
* consumer usb_data_interface_ft2232h.v runs a 3-cycle read-modify-write
|
||||
* on the detection-flag BRAM (idle → read-wait → write-back) and silently
|
||||
* drops cfar_valid arriving while RMW is busy. The two cadences match
|
||||
* today by construction.
|
||||
*
|
||||
* If you optimize this pipeline below 3 cycles per CUT (e.g., merging
|
||||
* ST_CFAR_MUL+CMP into a single state, or feeding the comparator
|
||||
* combinationally), you MUST also pipeline the RMW in
|
||||
* usb_data_interface_ft2232h.v to keep up — otherwise every Nth
|
||||
* detection is silently lost. A SIMULATION-only assertion in that
|
||||
* module fires `[ASSERT FAIL] AUDIT-S22: cfar_valid arrived while RMW
|
||||
* busy` to catch this regression in the test suite.
|
||||
*
|
||||
* Resources:
|
||||
* - 1 BRAM36K for magnitude buffer (16384 x 17 bits)
|
||||
* - 1 DSP48 for alpha multiply
|
||||
|
||||
@@ -550,6 +550,10 @@ run_test "FFT AXI Bridge tready handshake (AUDIT-C10)" \
|
||||
tb/tb_fft_engine_axi_bridge.vvp \
|
||||
tb/tb_fft_engine_axi_bridge.v fft_engine_axi_bridge.v
|
||||
|
||||
run_test "FT2232H Frame Drop Counter (AUDIT-C12)" \
|
||||
tb/tb_ft2232h_frame_drop.vvp \
|
||||
tb/tb_ft2232h_frame_drop.v usb_data_interface_ft2232h.v
|
||||
|
||||
echo ""
|
||||
|
||||
# ===========================================================================
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
`timescale 1ns / 1ps
|
||||
`include "radar_params.vh"
|
||||
// ============================================================================
|
||||
// tb_ft2232h_frame_drop.v — verifies AUDIT-C12 frame_drop_count instrumentation
|
||||
// ============================================================================
|
||||
// The bridge logic added under AUDIT-C12 surfaces silent USB frame drops via
|
||||
// a 7-bit `frame_drop_count` register exposed in `status_words[5][31:25]`.
|
||||
// Tracking handshake:
|
||||
// - clk-domain frame_pending: SET on frame_complete, CLEARED by wr_done_pulse
|
||||
// - ft_clk-domain wr_done_toggle: flips on WR_DONE → WR_IDLE (frame fully sent)
|
||||
// - 3-stage CDC syncs wr_done_toggle into clk for edge-detect
|
||||
//
|
||||
// Test cases:
|
||||
// 1. Single frame, USB drains promptly → drop count stays 0
|
||||
// 2. Two back-to-back frame_complete with USB stalled → drop count = 1
|
||||
// 3. Multiple drops while stalled → drop count saturates at 127
|
||||
// 4. Stalled + recovery → drop count stable, frame_pending clears post-drain
|
||||
//
|
||||
// Stimulus uses `stream_control = 6'b001_000` (mag_only=1, no sections enabled)
|
||||
// so the WR FSM goes HDR (8B) → FOOTER (1B) → DONE in 9 ft_clk cycles. This
|
||||
// gives a fast, deterministic per-frame transfer time. AUDIT-C9 sim assertion
|
||||
// is satisfied (mag_only=1, sparse_det=0).
|
||||
//
|
||||
// PASS criteria:
|
||||
// - frame_drop_count matches expected value after each scenario
|
||||
// - frame_pending tracks correctly across handshake
|
||||
// - wr_done_toggle observed to flip on each successful drain
|
||||
// ============================================================================
|
||||
|
||||
module tb_ft2232h_frame_drop;
|
||||
localparam CLK_PER = 10.0; // 100 MHz
|
||||
localparam FT_CLK_PER = 16.667; // 60 MHz
|
||||
|
||||
reg clk = 1'b0;
|
||||
reg ft_clk = 1'b0;
|
||||
reg reset_n = 1'b0;
|
||||
reg ft_reset_n = 1'b0;
|
||||
|
||||
// Radar data inputs (clk domain) - all idle for this test
|
||||
reg [31:0] range_profile = 32'd0;
|
||||
reg range_valid = 1'b0;
|
||||
reg [15:0] doppler_real = 16'd0;
|
||||
reg [15:0] doppler_imag = 16'd0;
|
||||
reg doppler_valid = 1'b0;
|
||||
reg cfar_detection = 1'b0;
|
||||
reg cfar_valid = 1'b0;
|
||||
|
||||
reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin_in = 0;
|
||||
reg [4:0] doppler_bin_in = 5'd0;
|
||||
reg frame_complete = 1'b0;
|
||||
|
||||
// FT2232H interface (ft_clk domain)
|
||||
wire [7:0] ft_data;
|
||||
reg ft_rxf_n = 1'b1; // No host read commands
|
||||
reg ft_txe_n = 1'b0; // 0 = USB ready (default)
|
||||
wire ft_rd_n;
|
||||
wire ft_wr_n;
|
||||
wire ft_oe_n;
|
||||
wire ft_siwu;
|
||||
|
||||
wire [31:0] cmd_data;
|
||||
wire cmd_valid;
|
||||
wire [7:0] cmd_opcode;
|
||||
wire [7:0] cmd_addr;
|
||||
wire [15:0] cmd_value;
|
||||
|
||||
// mag_only=1, sparse_det=0, all sections disabled (skip range/doppler/cfar)
|
||||
// → WR FSM: HDR → FOOTER → DONE = fast deterministic drain
|
||||
reg [5:0] stream_control = 6'b001_000;
|
||||
|
||||
// Status inputs (irrelevant for this test)
|
||||
reg status_request = 1'b0;
|
||||
reg [15:0] status_cfar_threshold = 16'd0;
|
||||
reg [5:0] status_stream_ctrl = 6'b001_000;
|
||||
reg [1:0] status_radar_mode = 2'd0;
|
||||
reg [15:0] status_long_chirp = 16'd0;
|
||||
reg [15:0] status_long_listen = 16'd0;
|
||||
reg [15:0] status_guard = 16'd0;
|
||||
reg [15:0] status_short_chirp = 16'd0;
|
||||
reg [15:0] status_short_listen = 16'd0;
|
||||
reg [5:0] status_chirps_per_elev = 6'd0;
|
||||
reg [1:0] status_range_mode = 2'd0;
|
||||
reg status_chirps_mismatch = 1'b0;
|
||||
reg [4:0] status_self_test_flags = 5'd0;
|
||||
reg [7:0] status_self_test_detail = 8'd0;
|
||||
reg status_self_test_busy = 1'b0;
|
||||
reg [3:0] status_agc_current_gain = 4'd0;
|
||||
reg [7:0] status_agc_peak_magnitude = 8'd0;
|
||||
reg [7:0] status_agc_saturation_count = 8'd0;
|
||||
reg status_agc_enable = 1'b0;
|
||||
|
||||
integer pass = 0;
|
||||
integer fail = 0;
|
||||
|
||||
always #(CLK_PER/2) clk = ~clk;
|
||||
always #(FT_CLK_PER/2) ft_clk = ~ft_clk;
|
||||
|
||||
usb_data_interface_ft2232h u_dut (
|
||||
.clk(clk),
|
||||
.reset_n(reset_n),
|
||||
.ft_reset_n(ft_reset_n),
|
||||
.range_profile(range_profile),
|
||||
.range_valid(range_valid),
|
||||
.doppler_real(doppler_real),
|
||||
.doppler_imag(doppler_imag),
|
||||
.doppler_valid(doppler_valid),
|
||||
.cfar_detection(cfar_detection),
|
||||
.cfar_valid(cfar_valid),
|
||||
.range_bin_in(range_bin_in),
|
||||
.doppler_bin_in(doppler_bin_in),
|
||||
.frame_complete(frame_complete),
|
||||
.ft_data(ft_data),
|
||||
.ft_rxf_n(ft_rxf_n),
|
||||
.ft_txe_n(ft_txe_n),
|
||||
.ft_rd_n(ft_rd_n),
|
||||
.ft_wr_n(ft_wr_n),
|
||||
.ft_oe_n(ft_oe_n),
|
||||
.ft_siwu(ft_siwu),
|
||||
.ft_clk(ft_clk),
|
||||
.cmd_data(cmd_data),
|
||||
.cmd_valid(cmd_valid),
|
||||
.cmd_opcode(cmd_opcode),
|
||||
.cmd_addr(cmd_addr),
|
||||
.cmd_value(cmd_value),
|
||||
.stream_control(stream_control),
|
||||
.status_request(status_request),
|
||||
.status_cfar_threshold(status_cfar_threshold),
|
||||
.status_stream_ctrl(status_stream_ctrl),
|
||||
.status_radar_mode(status_radar_mode),
|
||||
.status_long_chirp(status_long_chirp),
|
||||
.status_long_listen(status_long_listen),
|
||||
.status_guard(status_guard),
|
||||
.status_short_chirp(status_short_chirp),
|
||||
.status_short_listen(status_short_listen),
|
||||
.status_chirps_per_elev(status_chirps_per_elev),
|
||||
.status_range_mode(status_range_mode),
|
||||
.status_chirps_mismatch(status_chirps_mismatch),
|
||||
.status_self_test_flags(status_self_test_flags),
|
||||
.status_self_test_detail(status_self_test_detail),
|
||||
.status_self_test_busy(status_self_test_busy),
|
||||
.status_agc_current_gain(status_agc_current_gain),
|
||||
.status_agc_peak_magnitude(status_agc_peak_magnitude),
|
||||
.status_agc_saturation_count(status_agc_saturation_count),
|
||||
.status_agc_enable(status_agc_enable)
|
||||
);
|
||||
|
||||
task pulse_frame_complete;
|
||||
begin
|
||||
@(posedge clk); #1;
|
||||
frame_complete = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
frame_complete = 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
task wait_cycles;
|
||||
input integer n;
|
||||
integer i;
|
||||
begin
|
||||
for (i = 0; i < n; i = i + 1) @(posedge clk);
|
||||
end
|
||||
endtask
|
||||
|
||||
task check;
|
||||
input integer test_id;
|
||||
input [127:0] label;
|
||||
input integer expected;
|
||||
input integer actual;
|
||||
begin
|
||||
if (expected == actual) begin
|
||||
$display("[PASS] Test %0d (%0s): %0d == %0d",
|
||||
test_id, label, actual, expected);
|
||||
pass = pass + 1;
|
||||
end else begin
|
||||
$display("[FAIL] Test %0d (%0s): got %0d, expected %0d",
|
||||
test_id, label, actual, expected);
|
||||
fail = fail + 1;
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
$display("===========================================================");
|
||||
$display("tb_ft2232h_frame_drop — AUDIT-C12 frame_drop_count regression");
|
||||
$display("===========================================================");
|
||||
|
||||
// Reset both domains
|
||||
repeat (4) @(posedge clk);
|
||||
reset_n = 1'b1;
|
||||
ft_reset_n = 1'b1;
|
||||
wait_cycles(20);
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Test 1: Normal flow. ft_txe_n=0 (USB ready).
|
||||
// Single frame_complete; expect drain to complete and drop count=0.
|
||||
// -----------------------------------------------------------
|
||||
$display("\n[TEST 1] Single frame, USB ready -> no drops");
|
||||
ft_txe_n = 1'b0;
|
||||
pulse_frame_complete();
|
||||
// Wait for frame to drain through WR_FSM. With mag_only mode and
|
||||
// stream_control[2:0]=000, FSM goes HDR (8B) -> FOOTER (1B) -> DONE.
|
||||
// Each byte = 1 ft_clk cycle. Plus CDC latency. Allow ~50 ft_clk
|
||||
// = ~833 ns = ~83 clk cycles. Be generous: wait 200 clk cycles.
|
||||
wait_cycles(200);
|
||||
check(1, "drop_count", 0, u_dut.frame_drop_count);
|
||||
check(1, "frame_pending_cleared", 0, u_dut.frame_pending);
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Test 2: USB stalled, two frame_completes back-to-back.
|
||||
// Expect drop_count = 1 (second pulse arrives while pending).
|
||||
// -----------------------------------------------------------
|
||||
$display("\n[TEST 2] USB stalled, 2 frame_completes -> drop count = 1");
|
||||
ft_txe_n = 1'b1; // Stall USB
|
||||
wait_cycles(10);
|
||||
pulse_frame_complete(); // frame N: pending=1, no drop
|
||||
wait_cycles(20); // give time for any spurious wr_done_pulse
|
||||
pulse_frame_complete(); // frame N+1: pending was 1, drop count++
|
||||
wait_cycles(20);
|
||||
check(2, "drop_count_after_stall", 1, u_dut.frame_drop_count);
|
||||
check(2, "frame_pending_still_set", 1, u_dut.frame_pending);
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Test 3: Multiple drops while stalled.
|
||||
// 3 more frame_completes -> drop count = 4 (1 from test 2 + 3 new).
|
||||
// -----------------------------------------------------------
|
||||
$display("\n[TEST 3] 3 more frames during stall -> drop count = 4");
|
||||
pulse_frame_complete(); wait_cycles(15);
|
||||
pulse_frame_complete(); wait_cycles(15);
|
||||
pulse_frame_complete(); wait_cycles(15);
|
||||
check(3, "drop_count_after_3_more", 4, u_dut.frame_drop_count);
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Test 4: Recovery. Release USB, FSM drains, pending clears.
|
||||
// Then a new frame_complete should NOT increment drop count.
|
||||
// -----------------------------------------------------------
|
||||
$display("\n[TEST 4] USB recovers, drain completes, no new drop");
|
||||
ft_txe_n = 1'b0; // USB ready
|
||||
wait_cycles(200); // Allow drain
|
||||
check(4, "frame_pending_cleared_after_drain", 0, u_dut.frame_pending);
|
||||
check(4, "drop_count_stable_after_drain", 4, u_dut.frame_drop_count);
|
||||
// Now a clean frame_complete should add no drop
|
||||
pulse_frame_complete();
|
||||
wait_cycles(200);
|
||||
check(4, "drop_count_unchanged_clean_frame", 4, u_dut.frame_drop_count);
|
||||
check(4, "frame_pending_cleared_post_clean", 0, u_dut.frame_pending);
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Test 5: Saturation at 127. Stall USB and pulse frame_complete
|
||||
// many times. drop_count should saturate, not wrap.
|
||||
// -----------------------------------------------------------
|
||||
$display("\n[TEST 5] Saturation: 200 drops requested -> count saturates at 127");
|
||||
ft_txe_n = 1'b1;
|
||||
// First make pending=1 (this isn't a drop)
|
||||
pulse_frame_complete(); wait_cycles(10);
|
||||
// Now 199 more frame_completes — each is a drop after the first counted earlier
|
||||
// We're at drop_count=4 from prior tests, plus this new sequence will drive it up.
|
||||
// After ~130 more pulses, should saturate at 127.
|
||||
begin: sat_loop
|
||||
integer k;
|
||||
for (k = 0; k < 200; k = k + 1) begin
|
||||
pulse_frame_complete();
|
||||
wait_cycles(5);
|
||||
end
|
||||
end
|
||||
check(5, "drop_count_saturated", 127, u_dut.frame_drop_count);
|
||||
|
||||
$display("\n-----------------------------------------------------------");
|
||||
$display("RESULTS: %0d PASS, %0d FAIL", pass, fail);
|
||||
$display("-----------------------------------------------------------");
|
||||
if (fail == 0)
|
||||
$display("[OVERALL PASS]");
|
||||
else
|
||||
$display("[OVERALL FAIL]");
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#(CLK_PER * 50000);
|
||||
$display("[FATAL] Global timeout");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -204,6 +204,12 @@ localparam [3:0] WR_IDLE = 4'd0,
|
||||
|
||||
reg [3:0] wr_state;
|
||||
|
||||
// AUDIT-C12 instrumentation: ft_clk → clk handshake. Toggles when WR_FSM
|
||||
// completes a successful frame transfer (WR_DONE → WR_IDLE). Lets the clk
|
||||
// domain detect frame drops (frame_complete arrives while previous transfer
|
||||
// is still in flight). See full analysis in the clk-domain block below.
|
||||
reg wr_done_toggle;
|
||||
|
||||
// ============================================================================
|
||||
// READ FSM STATES (Host → FPGA, ft_clk domain — unchanged from legacy)
|
||||
// ============================================================================
|
||||
@@ -484,25 +490,76 @@ always @(posedge clk or negedge reset_n) begin
|
||||
range_write_counter <= {RANGE_BIN_BITS{1'b0}};
|
||||
end
|
||||
|
||||
// === Resume filling after ft_clk domain acknowledges frame transfer ===
|
||||
// (handled below via frame_ack_toggle CDC)
|
||||
// For simplicity, we resume filling immediately — the USB transfer reads
|
||||
// from the same BRAM while new data writes. Since the Doppler burst
|
||||
// (0.164 ms) is much shorter than the inter-frame gap (5.6 ms), and USB
|
||||
// transfer (0.875 ms) also fits within the gap, there's no overlap:
|
||||
// t=0: frame_complete pulse (1 clk cycle, from edge detector)
|
||||
// t=0-0.87ms: USB reads BRAM (old frame data is stable)
|
||||
// t=5.6ms: next Doppler burst starts writing
|
||||
// So single-buffer is safe. Re-enable filling after a short delay.
|
||||
// === Resume filling after frame_complete ===
|
||||
// AUDIT-C12 timing analysis (corrected from earlier comment):
|
||||
// - Frame period (178 fps): 5.62 ms
|
||||
// - Doppler emit window: ~0.5 ms at end of frame (16384 cells × 1
|
||||
// emission/cycle + per-range-bin 16-pt FFT compute, ~50K cycles)
|
||||
// - USB transfer (Hi-Speed bulk @ 8 MB/s): 35849 bytes / 8 MB/s
|
||||
// = 4.48 ms (NOT 0.875 ms as the original comment claimed)
|
||||
// - Slack at 178 fps: 5.62 − 4.48 = 1.14 ms (~20%)
|
||||
//
|
||||
// Write/read order BOTH advance range_bin slowest, doppler_bin fastest;
|
||||
// FPGA write of frame N+1 starts at addr 0 while USB read of frame N is
|
||||
// near addr 16383, so the brief overlap (~0.16 ms at end of WR_DOPPLER)
|
||||
// never collides on the same address. No data corruption today.
|
||||
//
|
||||
// Real failure mode: at higher frame rates (or USB bandwidth shortfalls),
|
||||
// frame_complete N+1 may fire while WR_FSM is still draining frame N.
|
||||
// frame_ready_toggle's edge in the ft_clk domain is missed unless WR_FSM
|
||||
// is in WR_IDLE — frame N+1 silently dropped. The frame_drop_count
|
||||
// counter below makes this loud.
|
||||
//
|
||||
// Filling continues immediately so the next frame's data is captured
|
||||
// (it goes to the same BRAM, possibly stomping on stale frame N data
|
||||
// — but stale-stomp is fine: USB has either read those addresses
|
||||
// already, or it hasn't and will read frame N+1's data at those
|
||||
// addresses, which is what the host wants when frames drop).
|
||||
if (!frame_filling && !frame_complete) begin
|
||||
// Re-enable after 1 cycle (frame_complete already deasserted)
|
||||
frame_filling <= 1'b1;
|
||||
detect_clearing <= 1'b1; // Clear detection BRAM for next frame
|
||||
frame_filling <= 1'b1;
|
||||
detect_clearing <= 1'b1; // Clear detection BRAM for next frame
|
||||
detect_clear_addr <= 14'd0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ============================================================================
|
||||
// AUDIT-C12: frame_pending + frame_drop_count (clk domain)
|
||||
// ============================================================================
|
||||
// Tracks whether a frame is queued for USB transfer and counts dropped frames
|
||||
// (frame_complete fires while previous frame still in WR_FSM transit). The
|
||||
// counter is exposed in status_words[5] for host visibility.
|
||||
//
|
||||
// CDC: wr_done_toggle (ft_clk) flips on every WR_DONE → WR_IDLE; we 3-stage
|
||||
// sync into clk and edge-detect to clear frame_pending.
|
||||
// ============================================================================
|
||||
reg frame_pending;
|
||||
reg [6:0] frame_drop_count; // 7-bit, saturates at 127
|
||||
|
||||
(* ASYNC_REG = "TRUE" *) reg [2:0] wr_done_sync;
|
||||
reg wr_done_prev;
|
||||
wire wr_done_pulse = wr_done_sync[2] ^ wr_done_prev;
|
||||
|
||||
always @(posedge clk or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
frame_pending <= 1'b0;
|
||||
frame_drop_count <= 7'd0;
|
||||
wr_done_sync <= 3'b000;
|
||||
wr_done_prev <= 1'b0;
|
||||
end else begin
|
||||
wr_done_sync <= {wr_done_sync[1:0], wr_done_toggle};
|
||||
wr_done_prev <= wr_done_sync[2];
|
||||
|
||||
if (frame_complete) begin
|
||||
if (frame_pending && frame_drop_count != 7'd127)
|
||||
frame_drop_count <= frame_drop_count + 7'd1;
|
||||
frame_pending <= 1'b1;
|
||||
end else if (wr_done_pulse) begin
|
||||
frame_pending <= 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ============================================================================
|
||||
// TOGGLE CDC: clk (100 MHz) → ft_clk (60 MHz)
|
||||
// ============================================================================
|
||||
@@ -533,6 +590,10 @@ wire status_req_ft = status_toggle_sync[2] ^ status_toggle_prev;
|
||||
(* ASYNC_REG = "TRUE" *) reg [5:0] stream_ctrl_sync_0;
|
||||
(* ASYNC_REG = "TRUE" *) reg [5:0] stream_ctrl_sync_1;
|
||||
|
||||
// --- AUDIT-C12: frame_drop_count CDC (slow-changing 7-bit value, 2-stage sync) ---
|
||||
(* ASYNC_REG = "TRUE" *) reg [6:0] frame_drop_sync_0;
|
||||
reg [6:0] frame_drop_sync_1;
|
||||
|
||||
wire stream_range_en = stream_ctrl_sync_1[0];
|
||||
wire stream_doppler_en = stream_ctrl_sync_1[1];
|
||||
wire stream_cfar_en = stream_ctrl_sync_1[2];
|
||||
@@ -645,6 +706,8 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
status_toggle_prev <= 1'b0;
|
||||
stream_ctrl_sync_0 <= `RP_STREAM_CTRL_DEFAULT;
|
||||
stream_ctrl_sync_1 <= `RP_STREAM_CTRL_DEFAULT;
|
||||
frame_drop_sync_0 <= 7'd0;
|
||||
frame_drop_sync_1 <= 7'd0;
|
||||
for (si = 0; si < 6; si = si + 1)
|
||||
status_words[si] <= 32'd0;
|
||||
wr_state <= WR_IDLE;
|
||||
@@ -671,6 +734,7 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
cmd_opcode <= 8'd0;
|
||||
cmd_addr <= 8'd0;
|
||||
cmd_value <= 16'd0;
|
||||
wr_done_toggle <= 1'b0;
|
||||
end else begin
|
||||
cmd_valid <= 1'b0;
|
||||
|
||||
@@ -684,6 +748,10 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
stream_ctrl_sync_0 <= stream_control;
|
||||
stream_ctrl_sync_1 <= stream_ctrl_sync_0;
|
||||
|
||||
// AUDIT-C12: frame_drop_count CDC (clk → ft_clk for status read)
|
||||
frame_drop_sync_0 <= frame_drop_count;
|
||||
frame_drop_sync_1 <= frame_drop_sync_0;
|
||||
|
||||
// Status snapshot on request
|
||||
if (status_req_ft) begin
|
||||
// Word 0: {0xFF, mode[1:0], stream[5:0], threshold[15:0]}
|
||||
@@ -700,7 +768,11 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
status_chirps_mismatch, // [10] TX-G mismatch flag
|
||||
8'd0, // [9:2] reserved
|
||||
status_range_mode}; // [1:0]
|
||||
status_words[5] <= {7'd0, status_self_test_busy,
|
||||
// AUDIT-C12: frame_drop_count exposed at status_words[5][31:25]
|
||||
// (was 7'd0 reserved). Saturates at 127. Counts frame_complete
|
||||
// events that arrived while previous frame was still in WR_FSM
|
||||
// transit (silent frame drop indicator for host visibility).
|
||||
status_words[5] <= {frame_drop_sync_1, status_self_test_busy,
|
||||
8'd0, status_self_test_detail,
|
||||
3'd0, status_self_test_flags};
|
||||
end
|
||||
@@ -977,9 +1049,10 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
end
|
||||
|
||||
WR_DONE: begin
|
||||
ft_wr_n <= 1'b1;
|
||||
ft_data_oe <= 1'b0;
|
||||
wr_state <= WR_IDLE;
|
||||
ft_wr_n <= 1'b1;
|
||||
ft_data_oe <= 1'b0;
|
||||
wr_done_toggle <= ~wr_done_toggle; // AUDIT-C12: signal frame transfer complete to clk domain
|
||||
wr_state <= WR_IDLE;
|
||||
end
|
||||
|
||||
default: wr_state <= WR_IDLE;
|
||||
@@ -1070,4 +1143,28 @@ always @(posedge clk) begin
|
||||
end
|
||||
`endif
|
||||
|
||||
// ============================================================================
|
||||
// AUDIT-S22: cfar_valid-vs-RMW-busy checker (simulation only)
|
||||
//
|
||||
// Detection RMW (idle→read→write-back) takes 3 cycles. cfar_ca emits one
|
||||
// detect_valid pulse per 3 cycles (THR/MUL/CMP pipeline). They match by
|
||||
// construction today — line 469 silently rejects cfar_valid arriving when
|
||||
// detect_rmw_state != 0, which never fires at the current cadence.
|
||||
//
|
||||
// If cfar_ca is ever optimized to <3-cycle cadence (e.g., merging MUL+CMP,
|
||||
// flagged as a possible target in cfar_ca.v), the silent rejection becomes
|
||||
// a silent detection-drop. This assertion makes that violation loud, so the
|
||||
// regression suite catches the coupling on the day someone speeds CFAR up
|
||||
// without also pipelining the RMW. Synthesis-inert.
|
||||
// ============================================================================
|
||||
`ifdef SIMULATION
|
||||
always @(posedge clk) begin
|
||||
if (reset_n && cfar_valid && frame_filling && !detect_clearing &&
|
||||
detect_rmw_state != 2'd0) begin
|
||||
$display("[ASSERT FAIL] AUDIT-S22: cfar_valid arrived while RMW busy (state=%0d) — detection at range_bin=%0d doppler_bin=%0d dropped",
|
||||
detect_rmw_state, range_bin_in, doppler_bin_in);
|
||||
end
|
||||
end
|
||||
`endif
|
||||
|
||||
endmodule
|
||||
|
||||
Reference in New Issue
Block a user