Files
PLFM_RADAR/9_Firmware/9_2_FPGA/ddc_400m.v
T
Jason e93bc33c6c Production fixes 1-7: detection bugs, cfar→threshold rename, digital gain control, Doppler mismatch protection, decimator watchdog, bypass_mode dead code removal, range-mode register (21/21 regression PASS)
Fix 1: Combinational magnitude + non-sticky detection flag (tb: 23/23)
Fix 2: Rename all cfar_* signals to detect_*/threshold_* (honest naming)
Fix 3: New rx_gain_control.v between DDC and FFT, opcode 0x16 (tb: 33/33)
Fix 4: Clamp host_chirps_per_elev to DOPPLER_FFT_SIZE, error flag (E2E: 54/54)
Fix 5: Decimator watchdog timeout, 256-cycle limit (tb: 63/63)
Fix 6: Remove bypass_mode dead code from ddc_400m.v (DDC tb: 21/21)
Fix 7: Range-mode register 0x20 with status readback (USB tb: 77/77)
2026-03-20 04:38:35 +02:00

723 lines
23 KiB
Verilog

`timescale 1ns / 1ps
module ddc_400m_enhanced (
input wire clk_400m, // 400MHz clock from ADC DCO
input wire clk_100m, // 100MHz system clock
input wire reset_n,
input wire mixers_enable,
input wire [7:0] adc_data, // ADC data at 400MHz
input wire adc_data_valid_i, // Valid at 400MHz
input wire adc_data_valid_q,
output wire signed [17:0] baseband_i,
output wire signed [17:0] baseband_q,
output wire baseband_valid_i,
output wire baseband_valid_q,
output wire [1:0] ddc_status,
// Enhanced interfaces
output wire [7:0] ddc_diagnostics,
output wire mixer_saturation,
output wire filter_overflow,
input wire [1:0] test_mode,
input wire [15:0] test_phase_inc,
input wire force_saturation,
input wire reset_monitors,
output wire [31:0] debug_sample_count,
output wire [17:0] debug_internal_i,
output wire [17:0] debug_internal_q
);
// Parameters for numerical precision
parameter ADC_WIDTH = 8;
parameter NCO_WIDTH = 16;
parameter MIXER_WIDTH = 18;
parameter OUTPUT_WIDTH = 18;
// IF frequency parameters
parameter IF_FREQ = 120000000;
parameter FS = 400000000;
parameter PHASE_WIDTH = 32;
// Internal signals
wire signed [15:0] sin_out, cos_out;
wire nco_ready;
wire cic_valid;
wire fir_valid;
wire [17:0] cic_i_out, cic_q_out;
wire signed [17:0] fir_i_out, fir_q_out;
// Diagnostic registers
reg [2:0] saturation_count;
reg overflow_detected;
reg [7:0] error_counter;
// ============================================================================
// 400 MHz Reset Synchronizer
//
// reset_n arrives from the 100 MHz domain (sys_reset_n from radar_system_top).
// Using it directly as an async reset in the 400 MHz domain causes the reset
// deassertion edge to violate timing: the 100 MHz flip-flop driving reset_n
// has its output fanning out to 1156 registers across the FPGA in the 400 MHz
// domain, requiring 18.243ns of routing (WNS = -18.081ns).
//
// Solution: 2-stage async-assert, sync-deassert reset synchronizer in the
// 400 MHz domain. Reset assertion is immediate (asynchronous — combinatorial
// path from reset_n to all 400 MHz registers). Reset deassertion is
// synchronized to clk_400m rising edge, preventing metastability.
//
// All 400 MHz submodules (NCO, CIC, mixers, LFSR) use reset_n_400m.
// All 100 MHz submodules (FIR, output stage) continue using reset_n directly
// (already synchronized to 100 MHz at radar_system_top level).
// ============================================================================
(* ASYNC_REG = "TRUE" *) reg [1:0] reset_sync_400m;
(* max_fanout = 50 *) wire reset_n_400m = reset_sync_400m[1];
// Active-high reset for DSP48E1 RST ports (avoids LUT1 inverter fan-out)
(* max_fanout = 50 *) reg reset_400m;
always @(posedge clk_400m or negedge reset_n) begin
if (!reset_n) begin
reset_sync_400m <= 2'b00;
reset_400m <= 1'b1;
end else begin
reset_sync_400m <= {reset_sync_400m[0], 1'b1};
reset_400m <= ~reset_sync_400m[1];
end
end
// CDC synchronization for control signals (2-stage synchronizers)
(* ASYNC_REG = "TRUE" *) reg [1:0] mixers_enable_sync_chain;
(* ASYNC_REG = "TRUE" *) reg [1:0] force_saturation_sync_chain;
wire mixers_enable_sync;
wire force_saturation_sync;
// Debug monitoring signals
reg [31:0] sample_counter;
wire signed [17:0] debug_mixed_i_trunc;
wire signed [17:0] debug_mixed_q_trunc;
// Real-time status monitoring
reg [7:0] signal_power_i, signal_power_q;
// Internal mixing signals
// DSP48E1 with AREG=1, BREG=1, MREG=1, PREG=1 handles all internal pipelining
// Latency: 3 cycles (1 for AREG/BREG, 1 for MREG, 1 for PREG)
wire signed [MIXER_WIDTH-1:0] adc_signed_w;
reg signed [MIXER_WIDTH + NCO_WIDTH -1:0] mixed_i, mixed_q;
reg mixed_valid;
reg mixer_overflow_i, mixer_overflow_q;
// Pipeline valid tracking: 3-stage shift register to match DSP48E1 AREG+MREG+PREG latency
reg [2:0] dsp_valid_pipe;
// Output stage registers
reg signed [17:0] baseband_i_reg, baseband_q_reg;
reg baseband_valid_reg;
// ============================================================================
// Phase Dithering Signals
// ============================================================================
wire [7:0] phase_dither_bits;
reg [31:0] phase_inc_dithered;
// ============================================================================
// Debug Signal Assignments
// ============================================================================
assign debug_internal_i = mixed_i[25:8];
assign debug_internal_q = mixed_q[25:8];
assign debug_sample_count = sample_counter;
assign debug_mixed_i_trunc = mixed_i[25:8];
assign debug_mixed_q_trunc = mixed_q[25:8];
// ============================================================================
// Clock Domain Crossing for Control Signals (2-stage synchronizers)
// ============================================================================
assign mixers_enable_sync = mixers_enable_sync_chain[1];
assign force_saturation_sync = force_saturation_sync_chain[1];
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin
mixers_enable_sync_chain <= 2'b00;
force_saturation_sync_chain <= 2'b00;
end else begin
mixers_enable_sync_chain <= {mixers_enable_sync_chain[0], mixers_enable};
force_saturation_sync_chain <= {force_saturation_sync_chain[0], force_saturation};
end
end
// ============================================================================
// Sample Counter and Debug Monitoring
// ============================================================================
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m || reset_monitors) begin
sample_counter <= 0;
error_counter <= 0;
end else if (adc_data_valid_i && adc_data_valid_q ) begin
sample_counter <= sample_counter + 1;
end
end
// ============================================================================
// Enhanced Phase Dithering Instance
// ============================================================================
lfsr_dither_enhanced #(
.DITHER_WIDTH(8)
) phase_dither_gen (
.clk(clk_400m),
.reset_n(reset_n_400m),
.enable(nco_ready),
.dither_out(phase_dither_bits)
);
// ============================================================================
// Phase Increment Calculation with Dithering
// ============================================================================
// Calculate phase increment for 120MHz IF at 400MHz sampling
localparam PHASE_INC_120MHZ = 32'h4CCCCCCD;
// Apply dithering to reduce spurious tones (registered for 400 MHz timing)
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m)
phase_inc_dithered <= PHASE_INC_120MHZ;
else
phase_inc_dithered <= PHASE_INC_120MHZ + {24'b0, phase_dither_bits};
end
// ============================================================================
// Enhanced NCO with Diagnostics
// ============================================================================
nco_400m_enhanced nco_core (
.clk_400m(clk_400m),
.reset_n(reset_n_400m),
.frequency_tuning_word(phase_inc_dithered),
.phase_valid(mixers_enable),
.phase_offset(16'h0000),
.sin_out(sin_out),
.cos_out(cos_out),
.dds_ready(nco_ready)
);
// ============================================================================
// Enhanced Mixing Stage — DSP48E1 direct instantiation for 400 MHz timing
//
// Architecture:
// ADC data → sign-extend to 18b → DSP48E1 A-port (AREG=1 pipelines it)
// NCO cos/sin → sign-extend to 18b → DSP48E1 B-port (BREG=1 pipelines it)
// Multiply result captured by MREG=1, then output registered by PREG=1
// force_saturation override applied AFTER DSP48E1 output (not on input path)
//
// Latency: 3 clock cycles (AREG/BREG + MREG + PREG)
// PREG=1 absorbs DSP48E1 CLK→P delay internally, preventing fabric timing violations
// In simulation (Icarus), uses behavioral equivalent since DSP48E1 is Xilinx-only
// ============================================================================
// Combinational ADC sign conversion (no register — DSP48E1 AREG handles it)
assign adc_signed_w = {1'b0, adc_data, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} -
{1'b0, {ADC_WIDTH{1'b1}}, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} / 2;
// Valid pipeline: 3-stage shift register matching DSP48E1 AREG+MREG+PREG latency
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin
dsp_valid_pipe <= 3'b000;
end else begin
dsp_valid_pipe <= {dsp_valid_pipe[1:0], (nco_ready && adc_data_valid_i && adc_data_valid_q)};
end
end
`ifdef SIMULATION
// ---- Behavioral model for Icarus Verilog simulation ----
// Mimics DSP48E1 with AREG=1, BREG=1, MREG=1, PREG=1 (3-cycle latency)
reg signed [MIXER_WIDTH-1:0] adc_signed_reg; // Models AREG
reg signed [15:0] cos_pipe_reg, sin_pipe_reg; // Models BREG
reg signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_i_internal, mult_q_internal; // Models MREG
reg signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_i_reg, mult_q_reg; // Models PREG
// Stage 1: AREG/BREG equivalent
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin
adc_signed_reg <= 0;
cos_pipe_reg <= 0;
sin_pipe_reg <= 0;
end else begin
adc_signed_reg <= adc_signed_w;
cos_pipe_reg <= cos_out;
sin_pipe_reg <= sin_out;
end
end
// Stage 2: MREG equivalent
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin
mult_i_internal <= 0;
mult_q_internal <= 0;
end else begin
mult_i_internal <= $signed(adc_signed_reg) * $signed(cos_pipe_reg);
mult_q_internal <= $signed(adc_signed_reg) * $signed(sin_pipe_reg);
end
end
// Stage 3: PREG equivalent
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin
mult_i_reg <= 0;
mult_q_reg <= 0;
end else begin
mult_i_reg <= mult_i_internal;
mult_q_reg <= mult_q_internal;
end
end
`else
// ---- Direct DSP48E1 instantiation for Vivado synthesis ----
// This guarantees AREG/BREG/MREG are used, achieving timing closure at 400 MHz
wire [47:0] dsp_p_i, dsp_p_q;
// DSP48E1 for I-channel mixer (adc_signed * cos_out)
DSP48E1 #(
// Feature control attributes
.A_INPUT("DIRECT"),
.B_INPUT("DIRECT"),
.USE_DPORT("FALSE"),
.USE_MULT("MULTIPLY"),
.USE_SIMD("ONE48"),
// Pipeline register attributes — all enabled for max timing
.AREG(1),
.BREG(1),
.MREG(1),
.PREG(1), // P register enabled — absorbs CLK→P delay for timing closure
.ADREG(0),
.ACASCREG(1),
.BCASCREG(1),
.ALUMODEREG(0),
.CARRYINREG(0),
.CARRYINSELREG(0),
.CREG(0),
.DREG(0),
.INMODEREG(0),
.OPMODEREG(0),
// Pattern detector (unused)
.AUTORESET_PATDET("NO_RESET"),
.MASK(48'h3fffffffffff),
.PATTERN(48'h000000000000),
.SEL_MASK("MASK"),
.SEL_PATTERN("PATTERN"),
.USE_PATTERN_DETECT("NO_PATDET")
) dsp_mixer_i (
// Clock and reset
.CLK(clk_400m),
.RSTA(reset_400m),
.RSTB(reset_400m),
.RSTM(reset_400m),
.RSTP(reset_400m),
.RSTALLCARRYIN(1'b0),
.RSTALUMODE(1'b0),
.RSTCTRL(1'b0),
.RSTC(1'b0),
.RSTD(1'b0),
.RSTINMODE(1'b0),
// Clock enables
.CEA1(1'b0), // AREG=1 uses CEA2
.CEA2(1'b1),
.CEB1(1'b0), // BREG=1 uses CEB2
.CEB2(1'b1),
.CEM(1'b1),
.CEP(1'b1), // P register clock enable (PREG=1)
.CEAD(1'b0),
.CEALUMODE(1'b0),
.CECARRYIN(1'b0),
.CECTRL(1'b0),
.CEC(1'b0),
.CED(1'b0),
.CEINMODE(1'b0),
// Data ports
.A({{12{adc_signed_w[MIXER_WIDTH-1]}}, adc_signed_w}), // Sign-extend 18b to 30b
.B({{2{cos_out[15]}}, cos_out}), // Sign-extend 16b to 18b
.C(48'b0),
.D(25'b0),
.CARRYIN(1'b0),
// Control ports
.OPMODE(7'b0000101), // P = M (multiply only, no accumulate)
.ALUMODE(4'b0000), // Z + X + Y + CIN
.INMODE(5'b00000), // A2 * B2 (direct)
.CARRYINSEL(3'b000),
// Output ports
.P(dsp_p_i),
.PATTERNDETECT(),
.PATTERNBDETECT(),
.OVERFLOW(),
.UNDERFLOW(),
.CARRYOUT(),
// Cascade ports (unused)
.ACIN(30'b0),
.BCIN(18'b0),
.CARRYCASCIN(1'b0),
.MULTSIGNIN(1'b0),
.PCIN(48'b0),
.ACOUT(),
.BCOUT(),
.CARRYCASCOUT(),
.MULTSIGNOUT(),
.PCOUT()
);
// DSP48E1 for Q-channel mixer (adc_signed * sin_out)
DSP48E1 #(
.A_INPUT("DIRECT"),
.B_INPUT("DIRECT"),
.USE_DPORT("FALSE"),
.USE_MULT("MULTIPLY"),
.USE_SIMD("ONE48"),
.AREG(1),
.BREG(1),
.MREG(1),
.PREG(1),
.ADREG(0),
.ACASCREG(1),
.BCASCREG(1),
.ALUMODEREG(0),
.CARRYINREG(0),
.CARRYINSELREG(0),
.CREG(0),
.DREG(0),
.INMODEREG(0),
.OPMODEREG(0),
.AUTORESET_PATDET("NO_RESET"),
.MASK(48'h3fffffffffff),
.PATTERN(48'h000000000000),
.SEL_MASK("MASK"),
.SEL_PATTERN("PATTERN"),
.USE_PATTERN_DETECT("NO_PATDET")
) dsp_mixer_q (
.CLK(clk_400m),
.RSTA(reset_400m),
.RSTB(reset_400m),
.RSTM(reset_400m),
.RSTP(reset_400m),
.RSTALLCARRYIN(1'b0),
.RSTALUMODE(1'b0),
.RSTCTRL(1'b0),
.RSTC(1'b0),
.RSTD(1'b0),
.RSTINMODE(1'b0),
.CEA1(1'b0),
.CEA2(1'b1),
.CEB1(1'b0),
.CEB2(1'b1),
.CEM(1'b1),
.CEP(1'b1), // P register clock enable (PREG=1)
.CEAD(1'b0),
.CEALUMODE(1'b0),
.CECARRYIN(1'b0),
.CECTRL(1'b0),
.CEC(1'b0),
.CED(1'b0),
.CEINMODE(1'b0),
.A({{12{adc_signed_w[MIXER_WIDTH-1]}}, adc_signed_w}),
.B({{2{sin_out[15]}}, sin_out}),
.C(48'b0),
.D(25'b0),
.CARRYIN(1'b0),
.OPMODE(7'b0000101),
.ALUMODE(4'b0000),
.INMODE(5'b00000),
.CARRYINSEL(3'b000),
.P(dsp_p_q),
.PATTERNDETECT(),
.PATTERNBDETECT(),
.OVERFLOW(),
.UNDERFLOW(),
.CARRYOUT(),
.ACIN(30'b0),
.BCIN(18'b0),
.CARRYCASCIN(1'b0),
.MULTSIGNIN(1'b0),
.PCIN(48'b0),
.ACOUT(),
.BCOUT(),
.CARRYCASCOUT(),
.MULTSIGNOUT(),
.PCOUT()
);
// Extract the multiply result from DSP48E1 P output
// adc_signed is 18 bits, NCO is 16 bits → product is 34 bits (bits [33:0] of P)
wire signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_i_reg = dsp_p_i[MIXER_WIDTH+NCO_WIDTH-1:0];
wire signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_q_reg = dsp_p_q[MIXER_WIDTH+NCO_WIDTH-1:0];
`endif
// ============================================================================
// Post-DSP48E1 output stage: force_saturation override + overflow detection
// force_saturation mux is intentionally AFTER the DSP48E1 output to avoid
// polluting the critical input path with extra logic
// ============================================================================
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin
mixed_i <= 0;
mixed_q <= 0;
mixed_valid <= 0;
mixer_overflow_i <= 0;
mixer_overflow_q <= 0;
saturation_count <= 0;
overflow_detected <= 0;
end else if (dsp_valid_pipe[2]) begin
// Force saturation for testing (applied after DSP output, not on input path)
if (force_saturation_sync) begin
mixed_i <= 34'h1FFFFFFFF;
mixed_q <= 34'h200000000;
mixer_overflow_i <= 1'b1;
mixer_overflow_q <= 1'b1;
end else begin
// Normal path: take DSP48E1 multiply result
mixed_i <= mult_i_reg;
mixed_q <= mult_q_reg;
// Overflow detection on current cycle's multiply result
mixer_overflow_i <= (mult_i_reg > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
(mult_i_reg < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
mixer_overflow_q <= (mult_q_reg > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
(mult_q_reg < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
end
mixed_valid <= 1;
if (mixer_overflow_i || mixer_overflow_q) begin
saturation_count <= saturation_count + 1;
overflow_detected <= 1'b1;
end else begin
overflow_detected <= 1'b0;
end
end else begin
mixed_valid <= 0;
mixer_overflow_i <= 0;
mixer_overflow_q <= 0;
overflow_detected <= 1'b0;
end
end
// ============================================================================
// Enhanced CIC Decimators
// ============================================================================
wire cic_valid_i, cic_valid_q;
cic_decimator_4x_enhanced cic_i_inst (
.clk(clk_400m),
.reset_n(reset_n_400m),
.data_in(mixed_i[33:16]),
.data_valid(mixed_valid),
.data_out(cic_i_out),
.data_out_valid(cic_valid_i)
);
cic_decimator_4x_enhanced cic_q_inst (
.clk(clk_400m),
.reset_n(reset_n_400m),
.data_in(mixed_q[33:16]),
.data_valid(mixed_valid),
.data_out(cic_q_out),
.data_out_valid(cic_valid_q)
);
assign cic_valid = cic_valid_i & cic_valid_q;
// ============================================================================
// Enhanced FIR Filters with FIXED valid signal handling
// NOTE: Wire declarations moved BEFORE CDC instances to fix forward-reference
// error in Icarus Verilog (was originally after CDC instantiation)
// ============================================================================
wire fir_in_valid_i, fir_in_valid_q;
wire fir_valid_i, fir_valid_q;
wire fir_i_ready, fir_q_ready;
wire [17:0] fir_d_in_i, fir_d_in_q;
cdc_adc_to_processing #(
.WIDTH(18),
.STAGES(3)
)CDC_FIR_i(
.src_clk(clk_400m),
.dst_clk(clk_100m),
.src_reset_n(reset_n_400m),
.dst_reset_n(reset_n),
.src_data(cic_i_out),
.src_valid(cic_valid_i),
.dst_data(fir_d_in_i),
.dst_valid(fir_in_valid_i)
);
cdc_adc_to_processing #(
.WIDTH(18),
.STAGES(3)
)CDC_FIR_q(
.src_clk(clk_400m),
.dst_clk(clk_100m),
.src_reset_n(reset_n_400m),
.dst_reset_n(reset_n),
.src_data(cic_q_out),
.src_valid(cic_valid_q),
.dst_data(fir_d_in_q),
.dst_valid(fir_in_valid_q)
);
// ============================================================================
// FIR Filter Instances
// ============================================================================
// FIR I channel
fir_lowpass_parallel_enhanced fir_i_inst (
.clk(clk_100m),
.reset_n(reset_n),
.data_in(fir_d_in_i), // Use synchronized data
.data_valid(fir_in_valid_i), // Use synchronized valid
.data_out(fir_i_out),
.data_out_valid(fir_valid_i),
.fir_ready(fir_i_ready),
.filter_overflow()
);
// FIR Q channel
fir_lowpass_parallel_enhanced fir_q_inst (
.clk(clk_100m),
.reset_n(reset_n),
.data_in(fir_d_in_q), // Use synchronized data
.data_valid(fir_in_valid_q), // Use synchronized valid
.data_out(fir_q_out),
.data_out_valid(fir_valid_q),
.fir_ready(fir_q_ready),
.filter_overflow()
);
assign fir_valid = fir_valid_i & fir_valid_q;
// ============================================================================
// Enhanced Output Stage
// ============================================================================
always @(posedge clk_100m or negedge reset_n) begin
if (!reset_n) begin
baseband_i_reg <= 0;
baseband_q_reg <= 0;
baseband_valid_reg <= 0;
end else if (fir_valid) begin
baseband_i_reg <= fir_i_out;
baseband_q_reg <= fir_q_out;
baseband_valid_reg <= 1;
end else begin
baseband_valid_reg <= 0;
end
end
// ============================================================================
// Output Assignments
// ============================================================================
assign baseband_i = baseband_i_reg;
assign baseband_q = baseband_q_reg;
assign baseband_valid_i = baseband_valid_reg;
assign baseband_valid_q = baseband_valid_reg;
assign ddc_status = {mixer_overflow_i | mixer_overflow_q, nco_ready};
assign mixer_saturation = overflow_detected;
assign ddc_diagnostics = {saturation_count, error_counter[4:0]};
// ============================================================================
// Enhanced Debug and Monitoring
// ============================================================================
reg [31:0] debug_cic_count, debug_fir_count, debug_bb_count;
`ifdef SIMULATION
always @(posedge clk_100m) begin
if (fir_valid_i && debug_fir_count < 20) begin
debug_fir_count <= debug_fir_count + 1;
$display("FIR_OUTPUT: fir_i=%6d, fir_q=%6d", fir_i_out, fir_q_out);
end
if (adc_data_valid_i && adc_data_valid_q && debug_bb_count < 20) begin
debug_bb_count <= debug_bb_count + 1;
$display("BASEBAND_OUT: i=%6d, q=%6d, count=%0d",
baseband_i, baseband_q, debug_bb_count);
end
end
`endif
// In ddc_400m.v, add these debug signals:
// Debug monitoring (simulation only)
`ifdef SIMULATION
reg [31:0] debug_adc_count = 0;
reg [31:0] debug_baseband_count = 0;
always @(posedge clk_400m) begin
if (adc_data_valid_i && adc_data_valid_q && debug_adc_count < 10) begin
debug_adc_count <= debug_adc_count + 1;
$display("DDC_ADC: data=%0d, count=%0d, time=%t",
adc_data, debug_adc_count, $time);
end
end
always @(posedge clk_100m) begin
if (baseband_valid_i && baseband_valid_q && debug_baseband_count < 10) begin
debug_baseband_count <= debug_baseband_count + 1;
$display("DDC_BASEBAND: i=%0d, q=%0d, count=%0d, time=%t",
baseband_i, baseband_q, debug_baseband_count, $time);
end
end
`endif
endmodule
// ============================================================================
// Enhanced Phase Dithering Module
// ============================================================================
`timescale 1ns / 1ps
module lfsr_dither_enhanced #(
parameter DITHER_WIDTH = 8 // Increased for better dithering
)(
input wire clk,
input wire reset_n,
input wire enable,
output wire [DITHER_WIDTH-1:0] dither_out
);
reg [DITHER_WIDTH-1:0] lfsr_reg;
reg [15:0] cycle_counter;
reg lock_detected;
// Polynomial for better randomness: x^8 + x^6 + x^5 + x^4 + 1
wire feedback;
generate
if (DITHER_WIDTH == 4) begin
assign feedback = lfsr_reg[3] ^ lfsr_reg[2];
end else if (DITHER_WIDTH == 8) begin
assign feedback = lfsr_reg[7] ^ lfsr_reg[5] ^ lfsr_reg[4] ^ lfsr_reg[3];
end else begin
assign feedback = lfsr_reg[DITHER_WIDTH-1] ^ lfsr_reg[DITHER_WIDTH-2];
end
endgenerate
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
lfsr_reg <= {DITHER_WIDTH{1'b1}}; // Non-zero initial state
cycle_counter <= 0;
lock_detected <= 0;
end else if (enable) begin
lfsr_reg <= {lfsr_reg[DITHER_WIDTH-2:0], feedback};
cycle_counter <= cycle_counter + 1;
// Detect LFSR lock after sufficient cycles
if (cycle_counter > (2**DITHER_WIDTH * 8)) begin
lock_detected <= 1'b1;
end
end
end
assign dither_out = lfsr_reg;
endmodule