diff --git a/9_Firmware/9_2_FPGA/radar_system_top.v b/9_Firmware/9_2_FPGA/radar_system_top.v index 69ce659..17b7dfd 100644 --- a/9_Firmware/9_2_FPGA/radar_system_top.v +++ b/9_Firmware/9_2_FPGA/radar_system_top.v @@ -839,7 +839,12 @@ if (USB_MODE == 0) begin : gen_ft601 .status_agc_current_gain(rx_agc_current_gain), .status_agc_peak_magnitude(rx_agc_peak_magnitude), .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 @@ -912,7 +917,12 @@ end else begin : gen_ft2232h .status_agc_current_gain(rx_agc_current_gain), .status_agc_peak_magnitude(rx_agc_peak_magnitude), .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 @@ -1139,24 +1149,34 @@ end 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. -// STM32 reads PD13 to detect clipping and adjust ADAR1000 VGA gain. -// DIG_6: AGC enable flag — mirrors host_agc_enable so STM32 outer-loop AGC -// tracks the FPGA register as single source of truth. -// DIG_7: Reserved (tied low for future use). -// gpio_dig5: "signal-chain clipped" — asserts on AGC saturation, DDC mixer/FIR -// overflow, or MTI 2-pulse saturation. Audit F-6.1/F-6.3: these were all -// previously invisible to the MCU. +// AUDIT-S10: gpio_dig5 previously OR'd six unrelated flags (signal-saturation +// AND control-faults), so the MCU outer-loop AGC could not distinguish +// "I'm clipping, reduce RF gain" from "FFT chain stalled, reset me". Gain +// reduction is the wrong response for a watchdog/CDC stall; it just hides the +// stall behind a quiet receive chain. The split routes the two control-fault +// classes (audit F-6.4 range-decimator watchdog, audit F-1.2 CIC→FIR CDC +// overrun) to gpio_dig7 (PD15) so the MCU can react differently — log + reset +// 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) | rx_ddc_overflow_any | (rx_ddc_saturation_count != 3'd0) - | (rx_mti_saturation_count != 8'd0) - | rx_range_decim_watchdog // audit F-6.4 - | rx_ddc_cic_fir_overrun; // audit F-1.2 + | (rx_mti_saturation_count != 8'd0); 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 diff --git a/9_Firmware/9_2_FPGA/radar_system_top_te0713_umft601x_dev.v b/9_Firmware/9_2_FPGA/radar_system_top_te0713_umft601x_dev.v index 68e0314..9e6bfa8 100644 --- a/9_Firmware/9_2_FPGA/radar_system_top_te0713_umft601x_dev.v +++ b/9_Firmware/9_2_FPGA/radar_system_top_te0713_umft601x_dev.v @@ -143,7 +143,10 @@ usb_data_interface usb_inst ( .status_agc_current_gain(4'd0), .status_agc_peak_magnitude(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 diff --git a/9_Firmware/9_2_FPGA/run_regression.sh b/9_Firmware/9_2_FPGA/run_regression.sh index 9155974..74e5eec 100755 --- a/9_Firmware/9_2_FPGA/run_regression.sh +++ b/9_Firmware/9_2_FPGA/run_regression.sh @@ -562,6 +562,10 @@ run_test "ADC PWDN opcode 0x32 (AUDIT-S25)" \ tb/tb_adc_pwdn_opcode.vvp \ 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 "" # =========================================================================== diff --git a/9_Firmware/9_2_FPGA/tb/tb_audit_s10_gpio_split.v b/9_Firmware/9_2_FPGA/tb/tb_audit_s10_gpio_split.v new file mode 100644 index 0000000..d701447 --- /dev/null +++ b/9_Firmware/9_2_FPGA/tb/tb_audit_s10_gpio_split.v @@ -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 diff --git a/9_Firmware/9_2_FPGA/tb/tb_ft2232h_frame_drop.v b/9_Firmware/9_2_FPGA/tb/tb_ft2232h_frame_drop.v index 3eaf0a4..a3a0e70 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_ft2232h_frame_drop.v +++ b/9_Firmware/9_2_FPGA/tb/tb_ft2232h_frame_drop.v @@ -141,7 +141,10 @@ module tb_ft2232h_frame_drop; .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) + .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; diff --git a/9_Firmware/9_2_FPGA/tb/tb_usb_data_interface.v b/9_Firmware/9_2_FPGA/tb/tb_usb_data_interface.v index eef162f..92bb901 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_usb_data_interface.v +++ b/9_Firmware/9_2_FPGA/tb/tb_usb_data_interface.v @@ -144,7 +144,10 @@ module tb_usb_data_interface; .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) + .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 ─────────────────────────────────────── diff --git a/9_Firmware/9_2_FPGA/usb_data_interface.v b/9_Firmware/9_2_FPGA/usb_data_interface.v index 1eca084..f7ede21 100644 --- a/9_Firmware/9_2_FPGA/usb_data_interface.v +++ b/9_Firmware/9_2_FPGA/usb_data_interface.v @@ -103,7 +103,15 @@ module usb_data_interface ( input wire [3:0] status_agc_current_gain, input wire [7:0] status_agc_peak_magnitude, 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) @@ -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) (* ASYNC_REG = "TRUE" *) reg [1:0] status_req_sync; 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; // 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_prev <= 1'b0; 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 // Synchronize valid strobes (2-stage sync chain) 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_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 if (status_req_ft601) begin // 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 8'd0, // [9:2] reserved 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, 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 // Delayed version of sync[1] for edge detection diff --git a/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v b/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v index f006f45..83bbf7f 100644 --- a/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v +++ b/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v @@ -163,7 +163,14 @@ module usb_data_interface_ft2232h ( input wire [3:0] status_agc_current_gain, input wire [7:0] status_agc_peak_magnitude, 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; 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_doppler_en = stream_ctrl_sync_1[1]; 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; frame_drop_sync_0 <= 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) status_words[si] <= 32'd0; 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_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 if (status_req_ft) begin // 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 8'd0, // [9:2] reserved status_range_mode}; // [1:0] - // 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). + // Word 5: {frame_drop_count[31:25], 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-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, 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 // ================================================================