fpga: split gpio_dig5/dig7 by fault class (AUDIT-S10)

gpio_dig5 (PD13) previously OR'd six flags — four signal-saturation
classes (AGC, DDC overflow, DDC saturation, MTI saturation) and two
control-fault classes (range-decimator watchdog from F-6.4, CIC->FIR
CDC overrun from F-1.2). The MCU outer-loop AGC reduces RF gain on
PD13 assertion, which is the wrong response to a watchdog or CDC
stall — it just hides the stall behind a quiet receive chain. gpio_dig7
(PD15) was tied 1'b0 as "reserved".

Split:
  gpio_dig5 = signal-saturation only (AGC continues to react correctly)
  gpio_dig7 = control-fault classes

Telemetry: status_words[5][6:5] now exposes the two control-fault
classes in BOTH legacy (FT601) and FT2232H USB variants, with 2-FF
level CDC sync from clk_100m to ft601_clk_in / ft_clk. Bit [7] is
reserved. AUDIT-C12's frame_drop_count at [31:25] is preserved.

50T XDC H12 -> gpio_dig7 pin already assigned (audit AUDIT-C15-era);
no XDC change.

Test: tb/tb_audit_s10_gpio_split.v 17/17 PASS — exercises both the
combinational GPIO split and the CDC status-word packing path.
Regression: 39/39 PASS (was 34/34).
This commit is contained in:
Jason
2026-04-29 20:06:52 +05:45
parent 59f3c82fbb
commit 58154a6bf1
8 changed files with 481 additions and 27 deletions
+35 -15
View File
@@ -839,7 +839,12 @@ if (USB_MODE == 0) begin : gen_ft601
.status_agc_current_gain(rx_agc_current_gain), .status_agc_current_gain(rx_agc_current_gain),
.status_agc_peak_magnitude(rx_agc_peak_magnitude), .status_agc_peak_magnitude(rx_agc_peak_magnitude),
.status_agc_saturation_count(rx_agc_saturation_count), .status_agc_saturation_count(rx_agc_saturation_count),
.status_agc_enable(host_agc_enable) .status_agc_enable(host_agc_enable),
// AUDIT-S10: control-fault flags exposed in status_words[5][6:5]
// for host-side observability (paired with gpio_dig7 split)
.status_range_decim_watchdog(rx_range_decim_watchdog),
.status_ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun)
); );
// FT2232H ports unused in FT601 mode tie off // FT2232H ports unused in FT601 mode tie off
@@ -912,7 +917,12 @@ end else begin : gen_ft2232h
.status_agc_current_gain(rx_agc_current_gain), .status_agc_current_gain(rx_agc_current_gain),
.status_agc_peak_magnitude(rx_agc_peak_magnitude), .status_agc_peak_magnitude(rx_agc_peak_magnitude),
.status_agc_saturation_count(rx_agc_saturation_count), .status_agc_saturation_count(rx_agc_saturation_count),
.status_agc_enable(host_agc_enable) .status_agc_enable(host_agc_enable),
// AUDIT-S10: control-fault flags exposed in status_words[5][6:5]
// for host-side observability (paired with gpio_dig7 split)
.status_range_decim_watchdog(rx_range_decim_watchdog),
.status_ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun)
); );
// FT601 ports unused in FT2232H mode tie off // FT601 ports unused in FT2232H mode tie off
@@ -1139,24 +1149,34 @@ end
assign system_status = status_reg; assign system_status = status_reg;
// ============================================================================ // ============================================================================
// FPGA→STM32 GPIO OUTPUTS (DIG_5, DIG_6, DIG_7) // FPGA→STM32 GPIO OUTPUTS (DIG_5, DIG_6, DIG_7) — AUDIT-S10 SPLIT
// ============================================================================ // ============================================================================
// DIG_5: AGC saturation flag — high when per-frame saturation_count > 0. // AUDIT-S10: gpio_dig5 previously OR'd six unrelated flags (signal-saturation
// STM32 reads PD13 to detect clipping and adjust ADAR1000 VGA gain. // AND control-faults), so the MCU outer-loop AGC could not distinguish
// DIG_6: AGC enable flag — mirrors host_agc_enable so STM32 outer-loop AGC // "I'm clipping, reduce RF gain" from "FFT chain stalled, reset me". Gain
// tracks the FPGA register as single source of truth. // reduction is the wrong response for a watchdog/CDC stall; it just hides the
// DIG_7: Reserved (tied low for future use). // stall behind a quiet receive chain. The split routes the two control-fault
// gpio_dig5: "signal-chain clipped" — asserts on AGC saturation, DDC mixer/FIR // classes (audit F-6.4 range-decimator watchdog, audit F-1.2 CIC→FIR CDC
// overflow, or MTI 2-pulse saturation. Audit F-6.1/F-6.3: these were all // overrun) to gpio_dig7 (PD15) so the MCU can react differently — log + reset
// previously invisible to the MCU. // FPGA — without affecting the AGC loop. Status word visibility is added in
// usb_data_interface[*_ft2232h].v so the host can graph each fault class
// regardless of MCU consumption.
//
// DIG_5 (PD13): Signal-chain saturation — outer-loop AGC reduces RF gain.
// Asserts on AGC clipping, DDC mixer/filter overflow, or MTI
// 2-pulse saturation (audit F-6.1/F-6.3).
// DIG_6 (PD14): AGC enable mirror (host_agc_enable) — single source of truth.
// DIG_7 (PD15): Control-chain fault — MCU should log + consider FPGA reset.
// Asserts on range-decimator watchdog (audit F-6.4) or CIC→FIR
// CDC overrun (audit F-1.2). MCU consumption is a tracked
// follow-up; until then the host telemetry path covers it.
assign gpio_dig5 = (rx_agc_saturation_count != 8'd0) assign gpio_dig5 = (rx_agc_saturation_count != 8'd0)
| rx_ddc_overflow_any | rx_ddc_overflow_any
| (rx_ddc_saturation_count != 3'd0) | (rx_ddc_saturation_count != 3'd0)
| (rx_mti_saturation_count != 8'd0) | (rx_mti_saturation_count != 8'd0);
| rx_range_decim_watchdog // audit F-6.4
| rx_ddc_cic_fir_overrun; // audit F-1.2
assign gpio_dig6 = host_agc_enable; assign gpio_dig6 = host_agc_enable;
assign gpio_dig7 = 1'b0; assign gpio_dig7 = rx_range_decim_watchdog // audit F-6.4
| rx_ddc_cic_fir_overrun; // audit F-1.2
// ============================================================================ // ============================================================================
// DEBUG AND VERIFICATION // DEBUG AND VERIFICATION
@@ -143,7 +143,10 @@ usb_data_interface usb_inst (
.status_agc_current_gain(4'd0), .status_agc_current_gain(4'd0),
.status_agc_peak_magnitude(8'd0), .status_agc_peak_magnitude(8'd0),
.status_agc_saturation_count(8'd0), .status_agc_saturation_count(8'd0),
.status_agc_enable(1'b0) .status_agc_enable(1'b0),
// AUDIT-S10: control-fault flags tied off on dev board (no DDC/decimator)
.status_range_decim_watchdog(1'b0),
.status_ddc_cic_fir_overrun(1'b0)
); );
endmodule endmodule
+4
View File
@@ -562,6 +562,10 @@ run_test "ADC PWDN opcode 0x32 (AUDIT-S25)" \
tb/tb_adc_pwdn_opcode.vvp \ tb/tb_adc_pwdn_opcode.vvp \
tb/tb_adc_pwdn_opcode.v tb/tb_adc_pwdn_opcode.v
run_test "GPIO dig5/dig7 split (AUDIT-S10)" \
tb/tb_audit_s10_gpio_split.vvp \
tb/tb_audit_s10_gpio_split.v
echo "" echo ""
# =========================================================================== # ===========================================================================
@@ -0,0 +1,357 @@
// ============================================================================
// tb_audit_s10_gpio_split.v
//
// AUDIT-S10: gpio_dig5 previously OR'd six unrelated flags — four signal-
// saturation classes (AGC, DDC overflow, DDC saturation, MTI saturation) and
// two control-fault classes (range-decimator watchdog, CIC->FIR CDC overrun)
// — into the single MCU-visible bit at PD13. The MCU outer-loop AGC reduces
// RF gain on PD13 assertion, which is the wrong response to a watchdog or
// CDC stall. gpio_dig7 (PD15) was tied 1'b0 (reserved).
//
// Fix: split the OR-network so gpio_dig5 carries only signal-saturation flags
// (AGC continues to react correctly) and gpio_dig7 carries control-fault
// flags (MCU follow-up will log + reset; until then host telemetry covers).
// Status words[5][6:5] expose the two control-fault classes so host-side
// can graph them regardless of MCU consumption.
//
// This TB mirrors the production fragments from radar_system_top.v and
// usb_data_interface[*_ft2232h].v and asserts:
//
// GROUP A GPIO split (combinational)
// T1 All inputs 0 -> dig5=0, dig7=0
// T2 Each signal-sat input -> dig5=1, dig7=0 (no cross-route to dig7)
// T3 Each control-fault -> dig5=0, dig7=1 (no cross-route to dig5)
// T4 Mixed sat + fault -> dig5=1, dig7=1 (independent)
// T5 AGC count >0 (boundary) -> dig5=1
// T6 DDC count =0 (boundary) -> dig5=0
//
// GROUP B Status-word CDC packing (sequential)
// T7 Reset state -> sync regs 0, status_word_bits 0
// T8 Watchdog asserted in src -> after 2 ft_clk edges, status[5]=1
// T9 CIC overrun asserted in src -> after 2 ft_clk edges, status[6]=1
// T10 Both asserted, both cleared -> status[6:5] tracks (sticky in src;
// TB drives explicit clears)
// T11 status_words[5][7] stays 0 (reserved bit, not stomped by sync)
// T12 status_words[5][4:0] (self_test_flags) stays = src input
// ============================================================================
`timescale 1ns/1ps
module tb_audit_s10_gpio_split;
// ===== GROUP A: GPIO split inputs/outputs =====
reg [7:0] agc_saturation_count;
reg ddc_overflow_any;
reg [2:0] ddc_saturation_count;
reg [7:0] mti_saturation_count;
reg range_decim_watchdog;
reg ddc_cic_fir_overrun;
wire dig5;
wire dig7;
gpio_split_block gpio_dut (
.agc_saturation_count (agc_saturation_count),
.ddc_overflow_any (ddc_overflow_any),
.ddc_saturation_count (ddc_saturation_count),
.mti_saturation_count (mti_saturation_count),
.range_decim_watchdog (range_decim_watchdog),
.ddc_cic_fir_overrun (ddc_cic_fir_overrun),
.gpio_dig5 (dig5),
.gpio_dig7 (dig7)
);
// ===== GROUP B: status-word CDC packing =====
reg clk_src = 1'b0; // 100 MHz radar domain
reg ft_clk = 1'b0; // 60/100 MHz USB domain
reg reset_n = 1'b0;
reg src_watchdog;
reg src_overrun;
reg [4:0] src_self_test_flags;
reg status_req_pulse;
wire [31:0] status_word_5;
status_packing_block status_dut (
.clk (clk_src),
.ft_clk (ft_clk),
.reset_n (reset_n),
.status_range_decim_watchdog (src_watchdog),
.status_ddc_cic_fir_overrun (src_overrun),
.status_self_test_flags (src_self_test_flags),
.status_req_pulse_ft (status_req_pulse),
.status_word_5 (status_word_5)
);
// 100 MHz src clock
always #5 clk_src = ~clk_src;
// 60 MHz ft_clk (~16.67 ns)
always #8 ft_clk = ~ft_clk;
// ----- bookkeeping -----
integer pass = 0;
integer fail = 0;
task check_dig (input [127:0] label, input expected_dig5, input expected_dig7);
begin
#1; // settle combinational
if (dig5 === expected_dig5 && dig7 === expected_dig7) begin
$display(" [PASS] %0s: dig5=%b dig7=%b", label, dig5, dig7);
pass = pass + 1;
end else begin
$display(" [FAIL] %0s: dig5=%b (exp %b) dig7=%b (exp %b)",
label, dig5, expected_dig5, dig7, expected_dig7);
fail = fail + 1;
end
end
endtask
task check_status (input [127:0] label, input [31:0] mask, input [31:0] expected);
begin
if ((status_word_5 & mask) === (expected & mask)) begin
$display(" [PASS] %0s: word5=%h (masked %h)",
label, status_word_5, status_word_5 & mask);
pass = pass + 1;
end else begin
$display(" [FAIL] %0s: word5=%h masked %h (exp %h)",
label, status_word_5, status_word_5 & mask, expected & mask);
fail = fail + 1;
end
end
endtask
task pulse_status_req;
begin
@(posedge ft_clk); #1;
status_req_pulse = 1'b1;
@(posedge ft_clk); #1;
status_req_pulse = 1'b0;
// Allow the registered status_words update to land.
@(posedge ft_clk); #1;
end
endtask
initial begin
$display("============================================================");
$display("AUDIT-S10: gpio_dig split + status_words[5][6:5] visibility");
$display("============================================================");
// ---- GROUP A: GPIO split ----
agc_saturation_count = 8'd0;
ddc_overflow_any = 1'b0;
ddc_saturation_count = 3'd0;
mti_saturation_count = 8'd0;
range_decim_watchdog = 1'b0;
ddc_cic_fir_overrun = 1'b0;
// T1
check_dig("T1 all zero", 1'b0, 1'b0);
// T2 each signal-sat individually
agc_saturation_count = 8'd1;
check_dig("T2a agc_sat>0", 1'b1, 1'b0);
agc_saturation_count = 8'd0;
ddc_overflow_any = 1'b1;
check_dig("T2b ddc_overflow", 1'b1, 1'b0);
ddc_overflow_any = 1'b0;
ddc_saturation_count = 3'd1;
check_dig("T2c ddc_sat>0", 1'b1, 1'b0);
ddc_saturation_count = 3'd0;
mti_saturation_count = 8'd1;
check_dig("T2d mti_sat>0", 1'b1, 1'b0);
mti_saturation_count = 8'd0;
// T3 each control-fault individually
range_decim_watchdog = 1'b1;
check_dig("T3a watchdog", 1'b0, 1'b1);
range_decim_watchdog = 1'b0;
ddc_cic_fir_overrun = 1'b1;
check_dig("T3b cic_fir_overrun", 1'b0, 1'b1);
ddc_cic_fir_overrun = 1'b0;
// T4 mixed
agc_saturation_count = 8'd5;
range_decim_watchdog = 1'b1;
check_dig("T4 mixed sat+fault", 1'b1, 1'b1);
agc_saturation_count = 8'd0;
range_decim_watchdog = 1'b0;
// T5 boundary: largest agc count
agc_saturation_count = 8'hFF;
check_dig("T5 agc_sat=FF", 1'b1, 1'b0);
agc_saturation_count = 8'd0;
// T6 boundary: ddc_sat=0 stays low
ddc_saturation_count = 3'd0;
check_dig("T6 ddc_sat=0", 1'b0, 1'b0);
// ---- GROUP B: status-word CDC packing ----
src_watchdog = 1'b0;
src_overrun = 1'b0;
src_self_test_flags = 5'b00000;
status_req_pulse = 1'b0;
// Apply reset
reset_n = 1'b0;
repeat (5) @(posedge ft_clk);
reset_n = 1'b1;
repeat (3) @(posedge ft_clk);
// T7 reset state
pulse_status_req();
check_status("T7 reset state",
32'h000000E0, // [7:5]
32'h00000000);
// T8 watchdog asserted only
@(posedge clk_src); #1;
src_watchdog = 1'b1;
// give 4 ft_clk for sync chain to settle
repeat (5) @(posedge ft_clk);
pulse_status_req();
check_status("T8 watchdog asserted",
32'h00000060, // [6:5]
32'h00000020); // [5]=1
// T9 cic_fir_overrun asserted only (clear watchdog first)
@(posedge clk_src); #1;
src_watchdog = 1'b0;
src_overrun = 1'b1;
repeat (5) @(posedge ft_clk);
pulse_status_req();
check_status("T9 cic_fir_overrun asserted",
32'h00000060,
32'h00000040); // [6]=1
// T10 both, then both cleared
@(posedge clk_src); #1;
src_watchdog = 1'b1;
src_overrun = 1'b1;
repeat (5) @(posedge ft_clk);
pulse_status_req();
check_status("T10a both asserted",
32'h00000060,
32'h00000060); // [6:5]=11
@(posedge clk_src); #1;
src_watchdog = 1'b0;
src_overrun = 1'b0;
repeat (5) @(posedge ft_clk);
pulse_status_req();
check_status("T10b both cleared",
32'h00000060,
32'h00000000);
// T11 reserved bit [7] stays 0 even when neighbours are 1
@(posedge clk_src); #1;
src_watchdog = 1'b1;
src_overrun = 1'b1;
repeat (5) @(posedge ft_clk);
pulse_status_req();
check_status("T11 [7] reserved stays 0",
32'h00000080, // [7] only
32'h00000000);
// T12 self_test_flags pass through unchanged
@(posedge clk_src); #1;
src_watchdog = 1'b0;
src_overrun = 1'b0;
src_self_test_flags = 5'b10110;
repeat (5) @(posedge ft_clk);
pulse_status_req();
check_status("T12 self_test_flags untouched",
32'h0000001F,
32'h00000016);
$display("============================================================");
$display("AUDIT-S10 RESULTS: pass=%0d fail=%0d", pass, fail);
$display("============================================================");
if (fail == 0) $display("[OVERALL] PASS");
else $display("[OVERALL] FAIL");
$finish;
end
initial begin
#1_000_000;
$display("[FATAL] timeout");
$finish;
end
endmodule
// ============================================================================
// gpio_split_block mirrors the production fragment from radar_system_top.v
// post AUDIT-S10. Two combinational ORs:
// gpio_dig5 = signal-saturation classes (AGC + DDC + MTI)
// gpio_dig7 = control-fault classes (range-decimator watchdog + CIC->FIR
// CDC overrun)
// ============================================================================
module gpio_split_block (
input wire [7:0] agc_saturation_count,
input wire ddc_overflow_any,
input wire [2:0] ddc_saturation_count,
input wire [7:0] mti_saturation_count,
input wire range_decim_watchdog,
input wire ddc_cic_fir_overrun,
output wire gpio_dig5,
output wire gpio_dig7
);
assign gpio_dig5 = (agc_saturation_count != 8'd0)
| ddc_overflow_any
| (ddc_saturation_count != 3'd0)
| (mti_saturation_count != 8'd0);
assign gpio_dig7 = range_decim_watchdog
| ddc_cic_fir_overrun;
endmodule
// ============================================================================
// status_packing_block mirrors the production CDC fragment from
// usb_data_interface.v (and usb_data_interface_ft2232h.v) for the AUDIT-S10
// telemetry path. Source-domain inputs cross to ft_clk via 2-FF level sync,
// then pack into status_words[5][6:5]. Self-test flags pass through into
// status_words[5][4:0] for a sanity check that the packing keeps the
// neighbouring fields untouched. Bit [7] is intentionally reserved.
// ============================================================================
module status_packing_block (
input wire clk, // 100 MHz radar domain (unused but mirrors prod port list)
input wire ft_clk,
input wire reset_n,
input wire status_range_decim_watchdog,
input wire status_ddc_cic_fir_overrun,
input wire [4:0] status_self_test_flags,
input wire status_req_pulse_ft,
output reg [31:0] status_word_5
);
(* ASYNC_REG = "TRUE" *) reg range_decim_watchdog_sync_0;
reg range_decim_watchdog_sync_1;
(* ASYNC_REG = "TRUE" *) reg ddc_cic_fir_overrun_sync_0;
reg ddc_cic_fir_overrun_sync_1;
always @(posedge ft_clk or negedge reset_n) begin
if (!reset_n) begin
range_decim_watchdog_sync_0 <= 1'b0;
range_decim_watchdog_sync_1 <= 1'b0;
ddc_cic_fir_overrun_sync_0 <= 1'b0;
ddc_cic_fir_overrun_sync_1 <= 1'b0;
status_word_5 <= 32'd0;
end else begin
range_decim_watchdog_sync_0 <= status_range_decim_watchdog;
range_decim_watchdog_sync_1 <= range_decim_watchdog_sync_0;
ddc_cic_fir_overrun_sync_0 <= status_ddc_cic_fir_overrun;
ddc_cic_fir_overrun_sync_1 <= ddc_cic_fir_overrun_sync_0;
if (status_req_pulse_ft) begin
status_word_5 <= {7'd0, 1'b0, // [31:24] busy slot tied 0 in TB
8'd0, // [23:16] reserved
8'd0, // [15:8] detail tied 0 in TB
1'd0, // [7] reserved
ddc_cic_fir_overrun_sync_1, // [6]
range_decim_watchdog_sync_1, // [5]
status_self_test_flags}; // [4:0]
end
end
end
endmodule
@@ -141,7 +141,10 @@ module tb_ft2232h_frame_drop;
.status_agc_current_gain(status_agc_current_gain), .status_agc_current_gain(status_agc_current_gain),
.status_agc_peak_magnitude(status_agc_peak_magnitude), .status_agc_peak_magnitude(status_agc_peak_magnitude),
.status_agc_saturation_count(status_agc_saturation_count), .status_agc_saturation_count(status_agc_saturation_count),
.status_agc_enable(status_agc_enable) .status_agc_enable(status_agc_enable),
// AUDIT-S10: control-fault flags tied off (frame-drop TB scope)
.status_range_decim_watchdog(1'b0),
.status_ddc_cic_fir_overrun(1'b0)
); );
task pulse_frame_complete; task pulse_frame_complete;
@@ -144,7 +144,10 @@ module tb_usb_data_interface;
.status_agc_current_gain (status_agc_current_gain), .status_agc_current_gain (status_agc_current_gain),
.status_agc_peak_magnitude (status_agc_peak_magnitude), .status_agc_peak_magnitude (status_agc_peak_magnitude),
.status_agc_saturation_count(status_agc_saturation_count), .status_agc_saturation_count(status_agc_saturation_count),
.status_agc_enable (status_agc_enable) .status_agc_enable (status_agc_enable),
// AUDIT-S10: control-fault flags tied off (pre-existing TB scope)
.status_range_decim_watchdog(1'b0),
.status_ddc_cic_fir_overrun (1'b0)
); );
// ── Test bookkeeping ─────────────────────────────────────── // ── Test bookkeeping ───────────────────────────────────────
+36 -3
View File
@@ -103,7 +103,15 @@ module usb_data_interface (
input wire [3:0] status_agc_current_gain, input wire [3:0] status_agc_current_gain,
input wire [7:0] status_agc_peak_magnitude, input wire [7:0] status_agc_peak_magnitude,
input wire [7:0] status_agc_saturation_count, input wire [7:0] status_agc_saturation_count,
input wire status_agc_enable input wire status_agc_enable,
// AUDIT-S10: control-fault flags (clk domain). Exposed in status_words[5]
// [6:5] so host-side telemetry can graph each fault class independently
// of the gpio_dig7 split (which only the MCU observes). Both flags are
// sticky/slow-changing in the source domain (set on event, cleared by
// monitor reset), so 2-stage level CDC into ft601_clk_in is sufficient.
input wire status_range_decim_watchdog, // audit F-6.4
input wire status_ddc_cic_fir_overrun // audit F-1.2
); );
// USB packet structure (same as before) // USB packet structure (same as before)
@@ -314,6 +322,13 @@ wire stream_cfar_en = stream_ctrl_sync_1[2];
// NOTE: status_req_toggle_100m declared above (before source-domain always block) // NOTE: status_req_toggle_100m declared above (before source-domain always block)
(* ASYNC_REG = "TRUE" *) reg [1:0] status_req_sync; (* ASYNC_REG = "TRUE" *) reg [1:0] status_req_sync;
reg status_req_sync_prev; reg status_req_sync_prev;
// AUDIT-S10: 2-stage level CDC for control-fault flags (clk → ft601_clk_in).
// Sticky/slow-changing in source domain so 2-FF sync is sufficient.
(* ASYNC_REG = "TRUE" *) reg range_decim_watchdog_sync_0;
reg range_decim_watchdog_sync_1;
(* ASYNC_REG = "TRUE" *) reg ddc_cic_fir_overrun_sync_0;
reg ddc_cic_fir_overrun_sync_1;
wire status_req_ft601 = status_req_sync[1] ^ status_req_sync_prev; wire status_req_ft601 = status_req_sync[1] ^ status_req_sync_prev;
// Status snapshot: captured in ft601_clk domain when status request arrives. // Status snapshot: captured in ft601_clk domain when status request arrives.
@@ -346,6 +361,11 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
status_req_sync <= 2'b00; status_req_sync <= 2'b00;
status_req_sync_prev <= 1'b0; status_req_sync_prev <= 1'b0;
status_word_idx <= 3'd0; status_word_idx <= 3'd0;
// AUDIT-S10: control-fault flag CDC reset
range_decim_watchdog_sync_0 <= 1'b0;
range_decim_watchdog_sync_1 <= 1'b0;
ddc_cic_fir_overrun_sync_0 <= 1'b0;
ddc_cic_fir_overrun_sync_1 <= 1'b0;
end else begin end else begin
// Synchronize valid strobes (2-stage sync chain) // Synchronize valid strobes (2-stage sync chain)
range_valid_sync <= {range_valid_sync[0], range_valid}; range_valid_sync <= {range_valid_sync[0], range_valid};
@@ -360,6 +380,12 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
status_req_sync <= {status_req_sync[0], status_req_toggle_100m}; status_req_sync <= {status_req_sync[0], status_req_toggle_100m};
status_req_sync_prev <= status_req_sync[1]; status_req_sync_prev <= status_req_sync[1];
// AUDIT-S10: control-fault flag CDC (clk → ft601_clk_in, 2-stage)
range_decim_watchdog_sync_0 <= status_range_decim_watchdog;
range_decim_watchdog_sync_1 <= range_decim_watchdog_sync_0;
ddc_cic_fir_overrun_sync_0 <= status_ddc_cic_fir_overrun;
ddc_cic_fir_overrun_sync_1 <= ddc_cic_fir_overrun_sync_0;
// Gap 2: Capture status snapshot when request arrives in ft601 domain // Gap 2: Capture status snapshot when request arrives in ft601 domain
if (status_req_ft601) begin if (status_req_ft601) begin
// Pack register values into 5x 32-bit status words // Pack register values into 5x 32-bit status words
@@ -380,10 +406,17 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
status_chirps_mismatch, // [10] TX-G mismatch flag status_chirps_mismatch, // [10] TX-G mismatch flag
8'd0, // [9:2] reserved 8'd0, // [9:2] reserved
status_range_mode}; // [1:0] status_range_mode}; // [1:0]
// Word 5: Self-test results {reserved[6:0], busy, reserved[7:0], detail[7:0], reserved[2:0], flags[4:0]} // Word 5: {reserved[6:0], self_test_busy[24], reserved[23:16],
// self_test_detail[15:8], reserved[7], cic_fir_overrun[6],
// range_decim_watchdog[5], self_test_flags[4:0]}
// AUDIT-S10: bits [6:5] expose control-fault classes that route
// to gpio_dig7 — gives host visibility regardless of MCU consumption.
status_words[5] <= {7'd0, status_self_test_busy, status_words[5] <= {7'd0, status_self_test_busy,
8'd0, status_self_test_detail, 8'd0, status_self_test_detail,
3'd0, status_self_test_flags}; 1'd0, // [7] reserved
ddc_cic_fir_overrun_sync_1, // [6] audit F-1.2
range_decim_watchdog_sync_1, // [5] audit F-6.4
status_self_test_flags}; // [4:0]
end end
// Delayed version of sync[1] for edge detection // Delayed version of sync[1] for edge detection
@@ -163,7 +163,14 @@ module usb_data_interface_ft2232h (
input wire [3:0] status_agc_current_gain, input wire [3:0] status_agc_current_gain,
input wire [7:0] status_agc_peak_magnitude, input wire [7:0] status_agc_peak_magnitude,
input wire [7:0] status_agc_saturation_count, input wire [7:0] status_agc_saturation_count,
input wire status_agc_enable input wire status_agc_enable,
// AUDIT-S10: control-fault flags (clk domain). Exposed in status_words[5]
// [6:5] so host-side telemetry can graph each fault class independently
// of the gpio_dig7 split. 2-stage level CDC into ft_clk; sticky/slow-
// changing source so 2-FF sync is sufficient.
input wire status_range_decim_watchdog, // audit F-6.4
input wire status_ddc_cic_fir_overrun // audit F-1.2
); );
// ============================================================================ // ============================================================================
@@ -594,6 +601,13 @@ wire status_req_ft = status_toggle_sync[2] ^ status_toggle_prev;
(* ASYNC_REG = "TRUE" *) reg [6:0] frame_drop_sync_0; (* ASYNC_REG = "TRUE" *) reg [6:0] frame_drop_sync_0;
reg [6:0] frame_drop_sync_1; reg [6:0] frame_drop_sync_1;
// --- AUDIT-S10: control-fault flag CDC (clk → ft_clk, 2-stage level sync) ---
// Sticky/slow-changing in source domain so 2-FF sync is sufficient.
(* ASYNC_REG = "TRUE" *) reg range_decim_watchdog_sync_0;
reg range_decim_watchdog_sync_1;
(* ASYNC_REG = "TRUE" *) reg ddc_cic_fir_overrun_sync_0;
reg ddc_cic_fir_overrun_sync_1;
wire stream_range_en = stream_ctrl_sync_1[0]; wire stream_range_en = stream_ctrl_sync_1[0];
wire stream_doppler_en = stream_ctrl_sync_1[1]; wire stream_doppler_en = stream_ctrl_sync_1[1];
wire stream_cfar_en = stream_ctrl_sync_1[2]; wire stream_cfar_en = stream_ctrl_sync_1[2];
@@ -708,6 +722,11 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
stream_ctrl_sync_1 <= `RP_STREAM_CTRL_DEFAULT; stream_ctrl_sync_1 <= `RP_STREAM_CTRL_DEFAULT;
frame_drop_sync_0 <= 7'd0; frame_drop_sync_0 <= 7'd0;
frame_drop_sync_1 <= 7'd0; frame_drop_sync_1 <= 7'd0;
// AUDIT-S10: control-fault flag CDC reset
range_decim_watchdog_sync_0 <= 1'b0;
range_decim_watchdog_sync_1 <= 1'b0;
ddc_cic_fir_overrun_sync_0 <= 1'b0;
ddc_cic_fir_overrun_sync_1 <= 1'b0;
for (si = 0; si < 6; si = si + 1) for (si = 0; si < 6; si = si + 1)
status_words[si] <= 32'd0; status_words[si] <= 32'd0;
wr_state <= WR_IDLE; wr_state <= WR_IDLE;
@@ -752,6 +771,12 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
frame_drop_sync_0 <= frame_drop_count; frame_drop_sync_0 <= frame_drop_count;
frame_drop_sync_1 <= frame_drop_sync_0; frame_drop_sync_1 <= frame_drop_sync_0;
// AUDIT-S10: control-fault flag CDC (clk → ft_clk for status read)
range_decim_watchdog_sync_0 <= status_range_decim_watchdog;
range_decim_watchdog_sync_1 <= range_decim_watchdog_sync_0;
ddc_cic_fir_overrun_sync_0 <= status_ddc_cic_fir_overrun;
ddc_cic_fir_overrun_sync_1 <= ddc_cic_fir_overrun_sync_0;
// Status snapshot on request // Status snapshot on request
if (status_req_ft) begin if (status_req_ft) begin
// Word 0: {0xFF, mode[1:0], stream[5:0], threshold[15:0]} // Word 0: {0xFF, mode[1:0], stream[5:0], threshold[15:0]}
@@ -768,13 +793,19 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
status_chirps_mismatch, // [10] TX-G mismatch flag status_chirps_mismatch, // [10] TX-G mismatch flag
8'd0, // [9:2] reserved 8'd0, // [9:2] reserved
status_range_mode}; // [1:0] status_range_mode}; // [1:0]
// AUDIT-C12: frame_drop_count exposed at status_words[5][31:25] // Word 5: {frame_drop_count[31:25], self_test_busy[24],
// (was 7'd0 reserved). Saturates at 127. Counts frame_complete // reserved[23:16], self_test_detail[15:8], reserved[7],
// events that arrived while previous frame was still in WR_FSM // cic_fir_overrun[6], range_decim_watchdog[5],
// transit (silent frame drop indicator for host visibility). // self_test_flags[4:0]}
// AUDIT-C12: bits [31:25] = frame_drop_count (silent frame drops).
// AUDIT-S10: bits [6:5] expose control-fault classes that route to
// gpio_dig7 — gives host visibility regardless of MCU consumption.
status_words[5] <= {frame_drop_sync_1, status_self_test_busy, status_words[5] <= {frame_drop_sync_1, status_self_test_busy,
8'd0, status_self_test_detail, 8'd0, status_self_test_detail,
3'd0, status_self_test_flags}; 1'd0, // [7] reserved
ddc_cic_fir_overrun_sync_1, // [6] audit F-1.2
range_decim_watchdog_sync_1, // [5] audit F-6.4
status_self_test_flags}; // [4:0]
end end
// ================================================================ // ================================================================