Files
PLFM_RADAR/9_Firmware/9_2_FPGA/cdc_modules.v
T
Jason 675b1c0015 fix(pre-bringup): second-batch P1/P2/P3 audit findings
Addresses the remaining actionable items from
docs/DEVELOP_AUDIT_2026-04-19.md after commit 3f47d1e.

XDC (dead waivers — F-0.4, F-0.5, F-0.6, F-0.7):
- ft_clkout_IBUF CLOCK_DEDICATED_ROUTE now uses hierarchical filter;
  flat net name did not exist post-synth.
- reset_sync_reg[*] false-path rewritten to walk hierarchy and filter
  on CLR/PRE pins.
- adc_clk_mmcm.xdc ft601_clk_in references replaced with foreach-loop
  over real USB clock names, gated on -quiet existence.
- MMCM LOCKED waiver uses REF_PIN_NAME filter instead of the
  previously-missing u_core/ literal path.

CDC (F-1.1, F-1.2, F-1.3):
- Documented the quasi-static-bus stability invariant above the
  FT601 cmd_valid toggle block.
- cdc_adc_to_processing gains an `overrun` output; the two CIC->FIR
  instances feed a sticky cdc_cic_fir_overrun flag surfaced on
  gpio_dig5 so silent sample drops become visible to the MCU.
- Removed the dead mixers_enable synchronizer in ddc_400m.v; the _sync
  output was unused and every caller ties the port to 1'b1.

Diagnostics (F-6.4):
- range_bin_decimator watchdog_timeout plumbed through receiver
  and top-level, OR'd into gpio_dig5.

ADAR (F-4.7):
- delayUs() replaced with DWT cycle counter; self-initialising
  TRCENA/CYCCNTENA, overflow-safe unsigned subtraction.

Regression: tb_cdc_modules.v 57/57 passes under iverilog after
the cdc_modules.v change. Remote Vivado verification in progress.
2026-04-20 14:28:22 +05:45

307 lines
10 KiB
Verilog

`timescale 1ns / 1ps
// ============================================================================
// CDC FOR MULTI-BIT DATA (ADVANCED)
// Uses Gray-code encoding with synchronous reset on sync chain to avoid
// latch inference. ASYNC_REG attributes ensure Vivado places synchronizer
// FFs in the same slice for optimal MTBF.
// ============================================================================
module cdc_adc_to_processing #(
parameter WIDTH = 8,
parameter STAGES = 3
)(
input wire src_clk,
input wire dst_clk,
input wire src_reset_n,
input wire dst_reset_n,
input wire [WIDTH-1:0] src_data,
input wire src_valid,
output wire [WIDTH-1:0] dst_data,
output wire dst_valid,
// Audit F-1.2: overrun pulse in src_clk domain. Asserts for 1 src cycle
// whenever src_valid fires while the previous sample has not yet been
// acknowledged by the destination edge-detector (i.e., the transaction
// the CDC is silently dropping). Hold/count externally.
output wire overrun
`ifdef FORMAL
,output wire [WIDTH-1:0] fv_src_data_reg,
output wire [1:0] fv_src_toggle
`endif
);
// Gray encoding for safe CDC
function [WIDTH-1:0] binary_to_gray;
input [WIDTH-1:0] binary;
binary_to_gray = binary ^ (binary >> 1);
endfunction
function [WIDTH-1:0] gray_to_binary;
input [WIDTH-1:0] gray;
reg [WIDTH-1:0] binary;
integer i;
begin
binary[WIDTH-1] = gray[WIDTH-1];
for (i = WIDTH-2; i >= 0; i = i - 1) begin
binary[i] = binary[i+1] ^ gray[i];
end
gray_to_binary = binary;
end
endfunction
// Source domain registers
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;
// Destination domain synchronizer registers
// ASYNC_REG on memory arrays applies to all elements
(* ASYNC_REG = "TRUE" *) reg [WIDTH-1:0] dst_data_gray [0:STAGES-1];
(* ASYNC_REG = "TRUE" *) reg [1:0] dst_toggle_sync [0:STAGES-1];
reg [WIDTH-1:0] dst_data_reg;
reg dst_valid_reg = 0;
reg [1:0] prev_dst_toggle = 2'b00;
// 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
if (!src_reset_n) begin
src_data_reg <= 0;
src_data_gray <= 0;
src_toggle <= 2'b00;
end else if (src_valid) begin
src_data_reg <= src_data;
src_data_gray <= binary_to_gray(src_data);
src_toggle <= src_toggle + 1;
end
end
// CDC synchronization chain for data — SYNCHRONOUS RESET
// Using synchronous reset avoids latch inference in Vivado.
// For CDC synchronizers, synchronous reset is preferred because
// the reset value is sampled safely within the clock domain.
genvar i;
generate
for (i = 0; i < STAGES; i = i + 1) begin : data_sync_chain
always @(posedge dst_clk) begin
if (!dst_reset_n) begin
dst_data_gray[i] <= 0;
end else begin
if (i == 0) begin
// Sample registered Gray-code from source domain
dst_data_gray[i] <= src_data_gray;
end else begin
dst_data_gray[i] <= dst_data_gray[i-1];
end
end
end
end
for (i = 0; i < STAGES; i = i + 1) begin : toggle_sync_chain
always @(posedge dst_clk) begin
if (!dst_reset_n) begin
dst_toggle_sync[i] <= 2'b00;
end else begin
if (i == 0) begin
dst_toggle_sync[i] <= src_toggle;
end else begin
dst_toggle_sync[i] <= dst_toggle_sync[i-1];
end
end
end
end
endgenerate
// Detect new data — synchronous reset
always @(posedge dst_clk) begin
if (!dst_reset_n) begin
dst_data_reg <= 0;
dst_valid_reg <= 0;
prev_dst_toggle <= 2'b00;
end else begin
// Convert from gray code
dst_data_reg <= gray_to_binary(dst_data_gray[STAGES-1]);
// Check if toggle changed (new data)
if (dst_toggle_sync[STAGES-1] != prev_dst_toggle) begin
dst_valid_reg <= 1'b1;
prev_dst_toggle <= dst_toggle_sync[STAGES-1];
end else begin
dst_valid_reg <= 1'b0;
end
end
end
assign dst_data = dst_data_reg;
assign dst_valid = dst_valid_reg;
// ------------------------------------------------------------------
// Audit F-1.2: overrun detection
//
// The src-side `src_toggle` counter flips on each latched src_valid.
// We feed back a 1-bit "ack" toggle from the dst domain (flipped each
// time dst_valid fires) through a STAGES-deep synchronizer into the
// src domain. If a new src_valid arrives while src_toggle[0] already
// differs from the acked value, the previous sample is still in flight
// and this new latch drops it. Emit a 1-cycle overrun pulse.
// ------------------------------------------------------------------
reg dst_ack_toggle;
always @(posedge dst_clk) begin
if (!dst_reset_n) dst_ack_toggle <= 1'b0;
else if (dst_valid_reg) dst_ack_toggle <= ~dst_ack_toggle;
end
(* ASYNC_REG = "TRUE" *) reg [STAGES-1:0] ack_sync_chain;
always @(posedge src_clk) begin
if (!src_reset_n) ack_sync_chain <= {STAGES{1'b0}};
else ack_sync_chain <= {ack_sync_chain[STAGES-2:0], dst_ack_toggle};
end
wire ack_in_src = ack_sync_chain[STAGES-1];
reg overrun_r;
always @(posedge src_clk) begin
if (!src_reset_n) overrun_r <= 1'b0;
else overrun_r <= src_valid && (src_toggle[0] != ack_in_src);
end
assign overrun = overrun_r;
`ifdef FORMAL
assign fv_src_data_reg = src_data_reg;
assign fv_src_toggle = src_toggle;
`endif
endmodule
// ============================================================================
// CDC FOR SINGLE BIT SIGNALS
// Uses synchronous reset on sync chain to avoid metastability on reset
// deassertion. Matches cdc_adc_to_processing best practice.
// ============================================================================
module cdc_single_bit #(
parameter STAGES = 3
)(
input wire src_clk,
input wire dst_clk,
input wire reset_n,
input wire src_signal,
output wire dst_signal
);
(* ASYNC_REG = "TRUE" *) reg [STAGES-1:0] sync_chain;
always @(posedge dst_clk) begin
if (!reset_n) begin
sync_chain <= 0;
end else begin
sync_chain <= {sync_chain[STAGES-2:0], src_signal};
end
end
assign dst_signal = sync_chain[STAGES-1];
endmodule
// ============================================================================
// CDC FOR MULTI-BIT WITH HANDSHAKE
// Uses synchronous reset to avoid metastability on reset deassertion.
// ============================================================================
module cdc_handshake #(
parameter WIDTH = 32
)(
input wire src_clk,
input wire dst_clk,
input wire reset_n,
input wire [WIDTH-1:0] src_data,
input wire src_valid,
output wire src_ready,
output wire [WIDTH-1:0] dst_data,
output wire dst_valid,
input wire dst_ready
`ifdef FORMAL
,output wire fv_src_busy,
output wire fv_dst_ack,
output wire fv_dst_req_sync,
output wire [1:0] fv_src_ack_sync_chain,
output wire [1:0] fv_dst_req_sync_chain,
output wire [WIDTH-1:0] fv_src_data_reg_hs
`endif
);
// Source domain
reg [WIDTH-1:0] src_data_reg;
reg src_busy = 0;
reg src_ack_sync = 0;
(* ASYNC_REG = "TRUE" *) reg [1:0] src_ack_sync_chain = 2'b00;
// Destination domain
reg [WIDTH-1:0] dst_data_reg;
reg dst_valid_reg = 0;
reg dst_req_sync = 0;
(* ASYNC_REG = "TRUE" *) reg [1:0] dst_req_sync_chain = 2'b00;
reg dst_ack = 0;
`ifdef FORMAL
assign fv_src_busy = src_busy;
assign fv_dst_ack = dst_ack;
assign fv_dst_req_sync = dst_req_sync;
assign fv_src_ack_sync_chain = src_ack_sync_chain;
assign fv_dst_req_sync_chain = dst_req_sync_chain;
assign fv_src_data_reg_hs = src_data_reg;
`endif
// Source clock domain — synchronous reset
always @(posedge src_clk) begin
if (!reset_n) begin
src_data_reg <= 0;
src_busy <= 0;
src_ack_sync <= 0;
src_ack_sync_chain <= 2'b00;
end else begin
// Sync acknowledge from destination
src_ack_sync_chain <= {src_ack_sync_chain[0], dst_ack};
src_ack_sync <= src_ack_sync_chain[1];
if (!src_busy && src_valid) begin
src_data_reg <= src_data;
src_busy <= 1'b1;
end else if (src_busy && src_ack_sync) begin
src_busy <= 1'b0;
end
end
end
// Destination clock domain — synchronous reset
always @(posedge dst_clk) begin
if (!reset_n) begin
dst_data_reg <= 0;
dst_valid_reg <= 0;
dst_req_sync <= 0;
dst_req_sync_chain <= 2'b00;
dst_ack <= 0;
end else begin
// Sync request from source
dst_req_sync_chain <= {dst_req_sync_chain[0], src_busy};
dst_req_sync <= dst_req_sync_chain[1];
// Capture data when request arrives
if (dst_req_sync && !dst_valid_reg) begin
dst_data_reg <= src_data_reg;
dst_valid_reg <= 1'b1;
dst_ack <= 1'b1;
end else if (dst_valid_reg && dst_ready) begin
dst_valid_reg <= 1'b0;
end
// Clear acknowledge after source sees it
if (dst_ack && !dst_req_sync) begin
dst_ack <= 1'b0;
end
end
end
assign src_ready = !src_busy;
assign dst_data = dst_data_reg;
assign dst_valid = dst_valid_reg;
endmodule