Fix all 10 CDC bugs from report_cdc audit, add overflow guard in range_bin_decimator

CDC fixes across 6 RTL files based on post-implementation report_cdc analysis:
- P0: sync stm32_mixers_enable and new_chirp_pulse to clk_120m via toggle CDC
       in radar_transmitter, add ft601 reset synchronizer and USB holding
       registers with proper edge detection in usb_data_interface
- P1: add ASYNC_REG to edge_detector, convert new_chirp_frame to toggle CDC,
       fix USB valid edge detect to use fully-synced signal
- P2: register Gray encoding in cdc_adc_to_processing source domain, sync
       ft601_txe and stm32_mixers_enable for status_reg in radar_system_top
- Safety: add in_bin_count overflow guard in range_bin_decimator to prevent
          downstream BRAM corruption

All 13 regression test suites pass (159 individual tests).
This commit is contained in:
Jason
2026-03-17 13:48:47 +02:00
parent fb59e98737
commit 5fd632bc47
9 changed files with 2299 additions and 2098 deletions
+12 -7
View File
@@ -44,6 +44,7 @@ module cdc_adc_to_processing #(
// Source domain registers // Source domain registers
reg [WIDTH-1:0] src_data_reg; reg [WIDTH-1:0] src_data_reg;
reg [WIDTH-1:0] src_data_gray; // Gray-encoded in source domain
reg [1:0] src_toggle = 2'b00; reg [1:0] src_toggle = 2'b00;
// Destination domain synchronizer registers // Destination domain synchronizer registers
@@ -54,14 +55,18 @@ module cdc_adc_to_processing #(
reg dst_valid_reg = 0; reg dst_valid_reg = 0;
reg [1:0] prev_dst_toggle = 2'b00; reg [1:0] prev_dst_toggle = 2'b00;
// Source domain: capture data and toggle synchronous reset // Source domain: capture data, Gray-encode, and toggle synchronous reset
// Gray encoding is registered in src_clk to avoid combinational logic
// before the first synchronizer FF (fixes CDC-10 violations).
always @(posedge src_clk) begin always @(posedge src_clk) begin
if (!reset_n) begin if (!reset_n) begin
src_data_reg <= 0; src_data_reg <= 0;
src_toggle <= 2'b00; src_data_gray <= 0;
src_toggle <= 2'b00;
end else if (src_valid) begin end else if (src_valid) begin
src_data_reg <= src_data; src_data_reg <= src_data;
src_toggle <= src_toggle + 1; src_data_gray <= binary_to_gray(src_data);
src_toggle <= src_toggle + 1;
end end
end end
@@ -77,8 +82,8 @@ module cdc_adc_to_processing #(
dst_data_gray[i] <= 0; dst_data_gray[i] <= 0;
end else begin end else begin
if (i == 0) begin if (i == 0) begin
// Convert to gray code at domain crossing // Sample registered Gray-code from source domain
dst_data_gray[i] <= binary_to_gray(src_data_reg); dst_data_gray[i] <= src_data_gray;
end else begin end else begin
dst_data_gray[i] <= dst_data_gray[i-1]; dst_data_gray[i] <= dst_data_gray[i-1];
end end
+2 -2
View File
@@ -5,8 +5,8 @@ module edge_detector_enhanced (
output wire rising_falling_edge output wire rising_falling_edge
); );
reg signal_in_prev; (* ASYNC_REG = "TRUE" *) reg signal_in_prev;
reg signal_in_prev2; (* ASYNC_REG = "TRUE" *) reg signal_in_prev2;
always @(posedge clk or negedge reset_n) begin always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin if (!reset_n) begin
+63 -5
View File
@@ -133,6 +133,11 @@ wire clk_120m_dac_buf;
wire ft601_clk_buf; wire ft601_clk_buf;
wire sys_reset_n; wire sys_reset_n;
wire sys_reset_120m_n; // Reset synchronized to clk_120m_dac domain wire sys_reset_120m_n; // Reset synchronized to clk_120m_dac domain
wire sys_reset_ft601_n; // Reset synchronized to ft601_clk domain
// CDC: synchronized versions of async inputs for status_reg
wire stm32_mixers_enable_100m; // stm32_mixers_enable sync'd to clk_100m
wire ft601_txe_100m; // ft601_txe sync'd to clk_100m
// Transmitter internal signals // Transmitter internal signals
wire [7:0] tx_chirp_data; wire [7:0] tx_chirp_data;
@@ -214,6 +219,37 @@ always @(posedge clk_120m_dac_buf or negedge reset_n) begin
end end
assign sys_reset_120m_n = reset_sync_120m[1]; assign sys_reset_120m_n = reset_sync_120m[1];
// Reset synchronization (ft601_clk domain)
// FT601 has its own asynchronous clock from the USB controller.
// All FT601-domain registers need a properly synchronized reset.
(* ASYNC_REG = "TRUE" *) reg [2:0] reset_sync_ft601; // 3-stage for better MTBF
always @(posedge ft601_clk_buf or negedge reset_n) begin
if (!reset_n) begin
reset_sync_ft601 <= 3'b000;
end else begin
reset_sync_ft601 <= {reset_sync_ft601[1:0], 1'b1};
end
end
assign sys_reset_ft601_n = reset_sync_ft601[2];
// CDC synchronizers for status_reg inputs (async -> clk_100m)
// stm32_mixers_enable is an async GPIO; ft601_txe is on ft601_clk domain
cdc_single_bit #(.STAGES(2)) cdc_mixers_en_status (
.src_clk(clk_100m_buf), // Pseudo-source for async GPIO
.dst_clk(clk_100m_buf),
.reset_n(sys_reset_n),
.src_signal(stm32_mixers_enable),
.dst_signal(stm32_mixers_enable_100m)
);
cdc_single_bit #(.STAGES(2)) cdc_ft601_txe_status (
.src_clk(ft601_clk_buf),
.dst_clk(clk_100m_buf),
.reset_n(sys_reset_n),
.src_signal(ft601_txe),
.dst_signal(ft601_txe_100m)
);
// ============================================================================ // ============================================================================
// CLOCK DOMAIN CROSSING: TRANSMITTER (120 MHz) -> SYSTEM (100 MHz) // CLOCK DOMAIN CROSSING: TRANSMITTER (120 MHz) -> SYSTEM (100 MHz)
// ============================================================================ // ============================================================================
@@ -232,17 +268,38 @@ cdc_adc_to_processing #(
.dst_valid(tx_current_chirp_sync_valid) .dst_valid(tx_current_chirp_sync_valid)
); );
// CDC for new_chirp_frame: single-bit 3-stage synchronizer // CDC for new_chirp_frame: toggle CDC (pulse on clk_120m -> pulse on clk_100m)
// new_chirp_frame is a 1-cycle pulse on clk_120m_dac. A level synchronizer
// at 100 MHz can miss it. Toggle CDC converts pulse -> level toggle,
// synchronizes the toggle, then detects edges to recover the pulse.
reg chirp_frame_toggle_120m;
always @(posedge clk_120m_dac_buf or negedge sys_reset_120m_n) begin
if (!sys_reset_120m_n)
chirp_frame_toggle_120m <= 1'b0;
else if (tx_new_chirp_frame)
chirp_frame_toggle_120m <= ~chirp_frame_toggle_120m;
end
wire chirp_frame_toggle_100m;
cdc_single_bit #( cdc_single_bit #(
.STAGES(3) .STAGES(3)
) cdc_new_chirp_frame ( ) cdc_new_chirp_frame (
.src_clk(clk_120m_dac_buf), .src_clk(clk_120m_dac_buf),
.dst_clk(clk_100m_buf), .dst_clk(clk_100m_buf),
.reset_n(sys_reset_n), .reset_n(sys_reset_n),
.src_signal(tx_new_chirp_frame), .src_signal(chirp_frame_toggle_120m),
.dst_signal(tx_new_chirp_frame_sync) .dst_signal(chirp_frame_toggle_100m)
); );
reg chirp_frame_toggle_100m_prev;
always @(posedge clk_100m_buf or negedge sys_reset_n) begin
if (!sys_reset_n)
chirp_frame_toggle_100m_prev <= 1'b0;
else
chirp_frame_toggle_100m_prev <= chirp_frame_toggle_100m;
end
assign tx_new_chirp_frame_sync = chirp_frame_toggle_100m ^ chirp_frame_toggle_100m_prev;
// ============================================================================ // ============================================================================
// RADAR TRANSMITTER INSTANTIATION // RADAR TRANSMITTER INSTANTIATION
// ============================================================================ // ============================================================================
@@ -396,6 +453,7 @@ assign usb_cfar_valid = rx_cfar_valid;
usb_data_interface usb_inst ( usb_data_interface usb_inst (
.clk(clk_100m_buf), .clk(clk_100m_buf),
.reset_n(sys_reset_n), .reset_n(sys_reset_n),
.ft601_reset_n(sys_reset_ft601_n), // FT601-domain synchronized reset
// Radar data inputs // Radar data inputs
.range_profile(usb_range_profile), .range_profile(usb_range_profile),
@@ -445,8 +503,8 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
if (!sys_reset_n) begin if (!sys_reset_n) begin
status_reg <= 4'b0000; status_reg <= 4'b0000;
end else begin end else begin
status_reg[0] <= stm32_mixers_enable; // Mixers enabled status_reg[0] <= stm32_mixers_enable_100m; // Mixers enabled (CDC sync'd)
status_reg[1] <= ft601_txe; // USB TX ready status_reg[1] <= ft601_txe_100m; // USB TX ready (CDC sync'd)
status_reg[2] <= rx_doppler_valid; // Data valid status_reg[2] <= rx_doppler_valid; // Data valid
status_reg[3] <= tx_new_chirp_frame_sync; // New chirp frame (CDC-sync'd) status_reg[3] <= tx_new_chirp_frame_sync; // New chirp frame (CDC-sync'd)
end end
+47 -2
View File
@@ -84,11 +84,56 @@ wire new_chirp_pulse;
wire new_elevation_pulse; wire new_elevation_pulse;
wire new_azimuth_pulse; wire new_azimuth_pulse;
// CDC: Synchronized versions of signals crossing clk_100m -> clk_120m_dac
wire mixers_enable_120m; // stm32_mixers_enable sync'd to clk_120m_dac
wire new_chirp_pulse_120m; // new_chirp_pulse (toggle CDC) in clk_120m_dac domain
// Chirp Control Signals // Chirp Control Signals
wire [7:0] chirp_data; wire [7:0] chirp_data;
wire chirp_valid; wire chirp_valid;
wire chirp_sequence_done; wire chirp_sequence_done;
// Toggle CDC for new_chirp_pulse: clk_100m -> clk_120m_dac
// Edge detector produces a 1-cycle pulse on clk_100m. A level synchronizer
// would miss it (120/100 MHz ratio). Toggle CDC converts pulse to level toggle,
// syncs the toggle, then detects edges on the destination side.
reg chirp_toggle_100m;
always @(posedge clk_100m or negedge reset_n) begin
if (!reset_n)
chirp_toggle_100m <= 1'b0;
else if (new_chirp_pulse)
chirp_toggle_100m <= ~chirp_toggle_100m;
end
// Sync the toggle to clk_120m_dac domain
wire chirp_toggle_120m;
cdc_single_bit #(.STAGES(3)) cdc_chirp_toggle (
.src_clk(clk_100m),
.dst_clk(clk_120m_dac),
.reset_n(reset_n),
.src_signal(chirp_toggle_100m),
.dst_signal(chirp_toggle_120m)
);
// Detect edges on synchronized toggle to recover pulse in clk_120m domain
reg chirp_toggle_120m_prev;
always @(posedge clk_120m_dac or negedge reset_n) begin
if (!reset_n)
chirp_toggle_120m_prev <= 1'b0;
else
chirp_toggle_120m_prev <= chirp_toggle_120m;
end
assign new_chirp_pulse_120m = chirp_toggle_120m ^ chirp_toggle_120m_prev;
// Sync stm32_mixers_enable (async GPIO level) to clk_120m_dac domain
cdc_single_bit #(.STAGES(3)) cdc_mixers_en_120m (
.src_clk(clk_100m), // Treat as pseudo-source (GPIO is async)
.dst_clk(clk_120m_dac),
.reset_n(reset_n),
.src_signal(stm32_mixers_enable),
.dst_signal(mixers_enable_120m)
);
// Enhanced STM32 Input Edge Detection with Debouncing // Enhanced STM32 Input Edge Detection with Debouncing
edge_detector_enhanced chirp_edge ( edge_detector_enhanced chirp_edge (
.clk(clk_100m), .clk(clk_100m),
@@ -116,11 +161,11 @@ plfm_chirp_controller_enhanced plfm_chirp_inst (
.clk_120m(clk_120m_dac), .clk_120m(clk_120m_dac),
.clk_100m(clk_100m), .clk_100m(clk_100m),
.reset_n(reset_n), .reset_n(reset_n),
.new_chirp(new_chirp_pulse), .new_chirp(new_chirp_pulse_120m), // CDC-synchronized pulse in clk_120m domain
.new_elevation(new_elevation_pulse), .new_elevation(new_elevation_pulse),
.new_azimuth(new_azimuth_pulse), .new_azimuth(new_azimuth_pulse),
.new_chirp_frame(new_chirp_frame), .new_chirp_frame(new_chirp_frame),
.mixers_enable(stm32_mixers_enable), .mixers_enable(mixers_enable_120m), // CDC-synchronized level in clk_120m domain
.chirp_data(chirp_data), .chirp_data(chirp_data),
.chirp_valid(chirp_valid), .chirp_valid(chirp_valid),
.chirp_done(chirp_sequence_done), .chirp_done(chirp_sequence_done),
@@ -236,6 +236,12 @@ always @(posedge clk or negedge reset_n) begin
if (range_valid_in) begin if (range_valid_in) begin
in_bin_count <= in_bin_count + 1; in_bin_count <= in_bin_count + 1;
// Overflow guard: stop if we've consumed all input bins
// Prevents corruption of downstream Doppler BRAM if matched
// filter emits more than INPUT_BINS valid samples
if (in_bin_count >= INPUT_BINS - 1) begin
state <= ST_DONE;
end else begin
// Mode-specific sample processing // Mode-specific sample processing
case (decimation_mode) case (decimation_mode)
2'b00: begin // Simple decimation 2'b00: begin // Simple decimation
@@ -266,6 +272,7 @@ always @(posedge clk or negedge reset_n) begin
end else begin end else begin
group_sample_count <= group_sample_count + 1; group_sample_count <= group_sample_count + 1;
end end
end // else (overflow guard)
end end
end end
@@ -0,0 +1,41 @@
# run_cdc_and_netlist.tcl
# Opens the routed design and runs:
# 1. report_cdc — detailed CDC analysis to investigate TIMING-9
# 2. write_verilog — post-synthesis functional simulation netlist
#
# Usage: vivado -mode batch -source run_cdc_and_netlist.tcl
set project_dir "/home/jason-stone/PLFM_RADAR_work/vivado_project"
set report_dir "${project_dir}/reports_impl"
# Open the routed checkpoint
open_checkpoint ${project_dir}/aeris10_radar.runs/impl_1/radar_system_top_routed.dcp
# ============================================================================
# 1. report_cdc — identify all CDC crossings and the TIMING-9 source
# ============================================================================
puts "INFO: Running report_cdc..."
report_cdc -details -file ${report_dir}/cdc_report.txt
# ============================================================================
# 2. Write post-synthesis functional simulation netlist
# ============================================================================
puts "INFO: Writing post-synthesis functional sim netlist..."
# Post-synthesis (from synth checkpoint) — simpler, no routing delays
open_checkpoint ${project_dir}/aeris10_radar.runs/synth_1/radar_system_top.dcp
write_verilog -force -mode funcsim \
${project_dir}/sim/post_synth_funcsim.v
# Also write SDF for timing sim (from routed checkpoint)
open_checkpoint ${project_dir}/aeris10_radar.runs/impl_1/radar_system_top_routed.dcp
write_verilog -force -mode timesim \
${project_dir}/sim/post_impl_timesim.v
write_sdf -force \
${project_dir}/sim/post_impl_timesim.sdf
puts "INFO: All reports and netlists generated."
puts "INFO: CDC report: ${report_dir}/cdc_report.txt"
puts "INFO: Post-synth sim: ${project_dir}/sim/post_synth_funcsim.v"
puts "INFO: Post-impl sim: ${project_dir}/sim/post_impl_timesim.v"
puts "INFO: SDF: ${project_dir}/sim/post_impl_timesim.sdf"
File diff suppressed because it is too large Load Diff
@@ -55,6 +55,7 @@ module tb_usb_data_interface;
usb_data_interface uut ( usb_data_interface uut (
.clk (clk), .clk (clk),
.reset_n (reset_n), .reset_n (reset_n),
.ft601_reset_n (reset_n), // In TB, share same reset for both domains
.range_profile (range_profile), .range_profile (range_profile),
.range_valid (range_valid), .range_valid (range_valid),
.doppler_real (doppler_real), .doppler_real (doppler_real),
+64 -20
View File
@@ -1,6 +1,7 @@
module usb_data_interface ( module usb_data_interface (
input wire clk, // Main clock (100MHz recommended) input wire clk, // Main clock (100MHz recommended)
input wire reset_n, input wire reset_n,
input wire ft601_reset_n, // FT601-domain synchronized reset
// Radar data inputs // Radar data inputs
input wire [31:0] range_profile, input wire [31:0] range_profile,
@@ -67,7 +68,40 @@ reg ft601_data_oe; // Output enable for bidirectional data bus
(* ASYNC_REG = "TRUE" *) reg [1:0] doppler_valid_sync; (* ASYNC_REG = "TRUE" *) reg [1:0] doppler_valid_sync;
(* ASYNC_REG = "TRUE" *) reg [1:0] cfar_valid_sync; (* ASYNC_REG = "TRUE" *) reg [1:0] cfar_valid_sync;
// Synchronized data captures (registered in ft601_clk_in domain) // Delayed versions of sync[1] for proper edge detection
reg range_valid_sync_d;
reg doppler_valid_sync_d;
reg cfar_valid_sync_d;
// Holding registers: data captured in SOURCE domain (clk_100m) when valid
// asserts, then read by ft601 domain after synchronized valid edge.
// This is safe because the data is stable for the entire time the valid
// pulse is being synchronized (2+ ft601_clk cycles).
reg [31:0] range_profile_hold;
reg [15:0] doppler_real_hold;
reg [15:0] doppler_imag_hold;
reg cfar_detection_hold;
// Source-domain holding registers (clk domain)
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
range_profile_hold <= 32'd0;
doppler_real_hold <= 16'd0;
doppler_imag_hold <= 16'd0;
cfar_detection_hold <= 1'b0;
end else begin
if (range_valid)
range_profile_hold <= range_profile;
if (doppler_valid) begin
doppler_real_hold <= doppler_real;
doppler_imag_hold <= doppler_imag;
end
if (cfar_valid)
cfar_detection_hold <= cfar_detection;
end
end
// FT601-domain captured data (sampled from holding regs on sync'd edge)
reg [31:0] range_profile_cap; reg [31:0] range_profile_cap;
reg [15:0] doppler_real_cap; reg [15:0] doppler_real_cap;
reg [15:0] doppler_imag_cap; reg [15:0] doppler_imag_cap;
@@ -77,43 +111,53 @@ wire range_valid_ft;
wire doppler_valid_ft; wire doppler_valid_ft;
wire cfar_valid_ft; wire cfar_valid_ft;
always @(posedge ft601_clk_in or negedge reset_n) begin always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
if (!reset_n) begin if (!ft601_reset_n) begin
range_valid_sync <= 2'b00; range_valid_sync <= 2'b00;
doppler_valid_sync <= 2'b00; doppler_valid_sync <= 2'b00;
cfar_valid_sync <= 2'b00; cfar_valid_sync <= 2'b00;
range_valid_sync_d <= 1'b0;
doppler_valid_sync_d <= 1'b0;
cfar_valid_sync_d <= 1'b0;
range_profile_cap <= 32'd0; range_profile_cap <= 32'd0;
doppler_real_cap <= 16'd0; doppler_real_cap <= 16'd0;
doppler_imag_cap <= 16'd0; doppler_imag_cap <= 16'd0;
cfar_detection_cap <= 1'b0; cfar_detection_cap <= 1'b0;
end else begin end else begin
// Synchronize valid strobes // 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};
doppler_valid_sync <= {doppler_valid_sync[0], doppler_valid}; doppler_valid_sync <= {doppler_valid_sync[0], doppler_valid};
cfar_valid_sync <= {cfar_valid_sync[0], cfar_valid}; cfar_valid_sync <= {cfar_valid_sync[0], cfar_valid};
// Capture data on rising edge of synchronized valid // Delayed version of sync[1] for edge detection
if (range_valid_sync[0] && !range_valid_sync[1]) range_valid_sync_d <= range_valid_sync[1];
range_profile_cap <= range_profile; doppler_valid_sync_d <= doppler_valid_sync[1];
if (doppler_valid_sync[0] && !doppler_valid_sync[1]) begin cfar_valid_sync_d <= cfar_valid_sync[1];
doppler_real_cap <= doppler_real;
doppler_imag_cap <= doppler_imag; // Capture data on rising edge of FULLY SYNCHRONIZED valid (sync[1])
// Data in holding regs is stable by the time sync[1] rises (2+ cycles)
if (range_valid_sync[1] && !range_valid_sync_d)
range_profile_cap <= range_profile_hold;
if (doppler_valid_sync[1] && !doppler_valid_sync_d) begin
doppler_real_cap <= doppler_real_hold;
doppler_imag_cap <= doppler_imag_hold;
end end
if (cfar_valid_sync[0] && !cfar_valid_sync[1]) if (cfar_valid_sync[1] && !cfar_valid_sync_d)
cfar_detection_cap <= cfar_detection; cfar_detection_cap <= cfar_detection_hold;
end end
end end
// Rising-edge detect on synchronized valid (pulse in ft601_clk_in domain) // Rising-edge detect on FULLY SYNCHRONIZED valid (sync[1], not sync[0])
assign range_valid_ft = range_valid_sync[0] && !range_valid_sync[1]; // This provides full 2-stage metastability protection before use.
assign doppler_valid_ft = doppler_valid_sync[0] && !doppler_valid_sync[1]; assign range_valid_ft = range_valid_sync[1] && !range_valid_sync_d;
assign cfar_valid_ft = cfar_valid_sync[0] && !cfar_valid_sync[1]; assign doppler_valid_ft = doppler_valid_sync[1] && !doppler_valid_sync_d;
assign cfar_valid_ft = cfar_valid_sync[1] && !cfar_valid_sync_d;
// FT601 data bus direction control // FT601 data bus direction control
assign ft601_data = ft601_data_oe ? ft601_data_out : 32'hzzzz_zzzz; assign ft601_data = ft601_data_oe ? ft601_data_out : 32'hzzzz_zzzz;
always @(posedge ft601_clk_in or negedge reset_n) begin always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
if (!reset_n) begin if (!ft601_reset_n) begin
current_state <= IDLE; current_state <= IDLE;
byte_counter <= 0; byte_counter <= 0;
ft601_data_out <= 0; ft601_data_out <= 0;
@@ -248,8 +292,8 @@ ODDR #(
`else `else
// Simulation: behavioral clock forwarding // Simulation: behavioral clock forwarding
reg ft601_clk_out_sim; reg ft601_clk_out_sim;
always @(posedge ft601_clk_in or negedge reset_n) begin always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
if (!reset_n) if (!ft601_reset_n)
ft601_clk_out_sim <= 1'b0; ft601_clk_out_sim <= 1'b0;
else else
ft601_clk_out_sim <= 1'b1; ft601_clk_out_sim <= 1'b1;