Files
PLFM_RADAR/9_Firmware/9_2_FPGA/radar_receiver_final.v
T
Jason fd6094ee9e Fix P0/P1 RTL bugs found during pre-hardware audit
P0-1: nco_400m_enhanced.v — DSP48E1 OPMODE corrected from PCIN to P
      feedback (was routing stale cascade data into accumulator)
P0-2: radar_receiver_final.v — removed same-clock CDC that corrupted
      ADC data path between ad9484_interface and DDC
P1-5: fir_lowpass.v — fixed zero replication count in coefficient
      symmetric extension ({0{1'b0}} is empty, now uses explicit 0)

Also updates .gitignore to exclude debug/scratch artifacts.

All 30+ testbenches pass (unit, co-sim, integration).
2026-03-16 22:24:06 +02:00

375 lines
12 KiB
Verilog

`timescale 1ns / 1ps
module radar_receiver_final (
input wire clk, // 100MHz
input wire reset_n,
// ADC Physical Interface (LVDS Inputs)
input wire [7:0] adc_d_p, // ADC Data P (LVDS)
input wire [7:0] adc_d_n, // ADC Data N (LVDS)
input wire adc_dco_p, // Data Clock Output P (400MHz LVDS)
input wire adc_dco_n, // Data Clock Output N (400MHz LVDS)
output wire adc_pwdn,
// Chirp counter from transmitter (for frame sync and matched filter)
input wire [5:0] chirp_counter,
output wire [31:0] doppler_output,
output wire doppler_valid,
output wire [4:0] doppler_bin,
output wire [5:0] range_bin
);
// ========== INTERNAL SIGNALS ==========
wire use_long_chirp;
// NOTE: chirp_counter is now an input port (was undriven internal wire — bug NEW-1)
wire chirp_start;
wire azimuth_change;
wire elevation_change;
// Mode controller outputs → matched_filter_multi_segment
wire mc_new_chirp;
wire mc_new_elevation;
wire mc_new_azimuth;
wire [1:0] segment_request;
wire mem_request;
wire [15:0] ref_i, ref_q;
wire mem_ready;
wire [15:0] adc_i_scaled, adc_q_scaled;
wire adc_valid_sync;
// Reference signals for the processing chain
wire [15:0] long_chirp_real, long_chirp_imag;
wire [15:0] short_chirp_real, short_chirp_imag;
// ========== DOPPLER PROCESSING SIGNALS ==========
wire [31:0] range_data_32bit;
wire range_data_valid;
wire new_chirp_frame;
// Doppler processor outputs
wire [31:0] doppler_spectrum;
wire doppler_spectrum_valid;
wire [4:0] doppler_bin_out;
wire doppler_processing;
wire doppler_frame_done;
// ========== RANGE BIN DECIMATOR SIGNALS ==========
wire signed [15:0] decimated_range_i;
wire signed [15:0] decimated_range_q;
wire decimated_range_valid;
wire [5:0] decimated_range_bin;
// ========== RADAR MODE CONTROLLER SIGNALS ==========
wire rmc_scanning;
wire rmc_scan_complete;
wire [5:0] rmc_chirp_count;
wire [5:0] rmc_elevation_count;
wire [5:0] rmc_azimuth_count;
// ========== MODULE INSTANTIATIONS ==========
// 0. Radar Mode Controller — drives chirp/elevation/azimuth timing signals
// Default mode: auto-scan (2'b01). Change to 2'b00 for STM32 pass-through.
radar_mode_controller rmc (
.clk(clk),
.reset_n(reset_n),
.mode(2'b01), // Auto-scan mode
.stm32_new_chirp(1'b0), // Unused in auto mode
.stm32_new_elevation(1'b0), // Unused in auto mode
.stm32_new_azimuth(1'b0), // Unused in auto mode
.trigger(1'b0), // Unused in auto mode
.use_long_chirp(use_long_chirp),
.mc_new_chirp(mc_new_chirp),
.mc_new_elevation(mc_new_elevation),
.mc_new_azimuth(mc_new_azimuth),
.chirp_count(rmc_chirp_count),
.elevation_count(rmc_elevation_count),
.azimuth_count(rmc_azimuth_count),
.scanning(rmc_scanning),
.scan_complete(rmc_scan_complete)
);
wire clk_400m;
// NOTE: lvds_to_cmos_400m removed — ad9484_interface_400m now provides
// the buffered 400MHz DCO clock via adc_dco_bufg, avoiding duplicate
// IBUFDS instantiations on the same LVDS clock pair.
// 1. ADC + CDC + AGC
// CMOS Output Interface (400MHz Domain)
wire [7:0] adc_data_cmos; // 8-bit ADC data (CMOS, from ad9484_interface_400m)
wire adc_valid; // Data valid signal
// ADC power-down control (directly tie low = ADC always on)
assign adc_pwdn = 1'b0;
ad9484_interface_400m adc (
.adc_d_p(adc_d_p),
.adc_d_n(adc_d_n),
.adc_dco_p(adc_dco_p),
.adc_dco_n(adc_dco_n),
.sys_clk(clk),
.reset_n(reset_n),
.adc_data_400m(adc_data_cmos),
.adc_data_valid_400m(adc_valid),
.adc_dco_bufg(clk_400m)
);
// NOTE: The cdc_adc_to_processing instance that was here used src_clk=dst_clk=clk_400m
// (same clock domain — no crossing). Gray-code CDC on same-clock with fast-changing
// ADC data corrupts samples because Gray coding only guarantees safe transfer of
// values that change by 1 LSB at a time. The real 400MHz→100MHz CDC crossing is
// handled inside ddc_400m_enhanced via CIC decimation + CDC_FIR instances.
// Removed: cdc_adc_to_processing instance. ADC data now goes directly to DDC.
// 2. DDC Input Interface
wire signed [17:0] ddc_out_i;
wire signed [17:0] ddc_out_q;
wire ddc_valid_i;
wire ddc_valid_q;
ddc_400m_enhanced ddc(
.clk_400m(clk_400m), // 400MHz clock from ADC DCO
.clk_100m(clk), // 100MHz system clock //used by the 2 FIR
.reset_n(reset_n),
.adc_data(adc_data_cmos), // ADC data at 400MHz (direct from ADC interface)
.adc_data_valid_i(adc_valid), // Valid at 400MHz
.adc_data_valid_q(adc_valid), // Valid at 400MHz
.baseband_i(ddc_out_i), // I output at 100MHz
.baseband_q(ddc_out_q), // Q output at 100MHz
.baseband_valid_i(ddc_valid_i), // Valid at 100MHz
.baseband_valid_q(ddc_valid_q),
.mixers_enable(1'b1),
.bypass_mode(1'b1)
);
ddc_input_interface ddc_if (
.clk(clk),
.reset_n(reset_n),
.ddc_i(ddc_out_i),
.ddc_q(ddc_out_q),
.valid_i(ddc_valid_i),
.valid_q(ddc_valid_q),
.adc_i(adc_i_scaled),
.adc_q(adc_q_scaled),
.adc_valid(adc_valid_sync),
.data_sync_error()
);
// 3. Dual Chirp Memory Loader
wire [9:0] sample_addr_from_chain;
chirp_memory_loader_param chirp_mem (
.clk(clk),
.reset_n(reset_n),
.segment_select(segment_request),
.mem_request(mem_request),
.use_long_chirp(use_long_chirp),
.sample_addr(sample_addr_from_chain),
.ref_i(ref_i),
.ref_q(ref_q),
.mem_ready(mem_ready)
);
// Sample address generator
reg [9:0] sample_addr_reg;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
sample_addr_reg <= 0;
end else if (mem_request) begin
sample_addr_reg <= sample_addr_reg + 1;
if (sample_addr_reg == 1023) sample_addr_reg <= 0;
end
end
// sample_addr_wire removed — was unused implicit wire (synthesis warning)
// 4. CRITICAL: Reference Chirp Latency Buffer
// This aligns reference data with FFT output (2159 cycle delay)
wire [15:0] delayed_ref_i, delayed_ref_q;
wire mem_ready_delayed;
latency_buffer_2159 #(
.DATA_WIDTH(32), // 16-bit I + 16-bit Q
.LATENCY(3187)
) ref_latency_buffer (
.clk(clk),
.reset_n(reset_n),
.data_in({ref_i, ref_q}),
.valid_in(mem_request),
.data_out({delayed_ref_i, delayed_ref_q}),
.valid_out(mem_ready_delayed)
);
// Assign delayed reference signals
assign long_chirp_real = delayed_ref_i;
assign long_chirp_imag = delayed_ref_q;
assign short_chirp_real = delayed_ref_i;
assign short_chirp_imag = delayed_ref_q;
// 5. Dual Chirp Matched Filter
wire signed [15:0] range_profile_i;
wire signed [15:0] range_profile_q;
wire range_valid;
matched_filter_multi_segment mf_dual (
.clk(clk),
.reset_n(reset_n),
.ddc_i({{2{adc_i_scaled[15]}}, adc_i_scaled}),
.ddc_q({{2{adc_q_scaled[15]}}, adc_q_scaled}),
.ddc_valid(adc_valid_sync),
.use_long_chirp(use_long_chirp),
.chirp_counter(chirp_counter),
.mc_new_chirp(mc_new_chirp),
.mc_new_elevation(mc_new_elevation),
.mc_new_azimuth(mc_new_azimuth),
.long_chirp_real(delayed_ref_i), // From latency buffer
.long_chirp_imag(delayed_ref_q),
.short_chirp_real(delayed_ref_i), // Same for short chirp
.short_chirp_imag(delayed_ref_q),
.segment_request(segment_request),
.mem_request(mem_request),
.sample_addr_out(sample_addr_from_chain),
.mem_ready(mem_ready),
.pc_i_w(range_profile_i),
.pc_q_w(range_profile_q),
.pc_valid_w(range_valid)
);
// ========== CRITICAL: RANGE BIN DECIMATOR ==========
// Convert 1024 range bins to 64 bins for Doppler
range_bin_decimator #(
.INPUT_BINS(1024),
.OUTPUT_BINS(64),
.DECIMATION_FACTOR(16)
) range_decim (
.clk(clk),
.reset_n(reset_n),
.range_i_in(range_profile_i),
.range_q_in(range_profile_q),
.range_valid_in(range_valid),
.range_i_out(decimated_range_i),
.range_q_out(decimated_range_q),
.range_valid_out(decimated_range_valid),
.range_bin_index(decimated_range_bin),
.decimation_mode(2'b01), // Peak detection mode
.start_bin(10'd0)
);
// ========== FRAME SYNC USING chirp_counter ==========
reg [5:0] chirp_counter_prev;
reg new_frame_pulse;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
chirp_counter_prev <= 6'd0;
new_frame_pulse <= 1'b0;
end else begin
// Default: no pulse
new_frame_pulse <= 1'b0;
// ===== CHOOSE ONE FRAME DETECTION METHOD =====
// METHOD A: Detect frame start at chirp_counter = 0
// (Assumes frames are 64 chirps: 0-63)
//if (chirp_counter == 6'd0 && chirp_counter_prev != 6'd0) begin
// new_frame_pulse <= 1'b1;
//end
// METHOD B: Detect frame start at chirp_counter = 0 AND 32
// (For 32-chirp frames in a 64-chirp sequence)
if ((chirp_counter == 6'd0 || chirp_counter == 6'd32) &&
(chirp_counter_prev != chirp_counter)) begin
new_frame_pulse <= 1'b1;
end
// METHOD C: Programmable frame start
// localparam FRAME_START_CHIRP = 6'd0; // Set based on your sequence
// if (chirp_counter == FRAME_START_CHIRP &&
// chirp_counter_prev != FRAME_START_CHIRP) begin
// new_frame_pulse <= 1'b1;
// end
// Store previous value
chirp_counter_prev <= chirp_counter;
end
end
assign new_chirp_frame = new_frame_pulse;
// ========== DATA PACKING FOR DOPPLER ==========
assign range_data_32bit = {decimated_range_q, decimated_range_i};
assign range_data_valid = decimated_range_valid;
// ========== DOPPLER PROCESSOR ==========
doppler_processor_optimized #(
.DOPPLER_FFT_SIZE(32),
.RANGE_BINS(64),
.CHIRPS_PER_FRAME(32) // MUST MATCH YOUR ACTUAL FRAME SIZE!
) doppler_proc (
.clk(clk),
.reset_n(reset_n),
.range_data(range_data_32bit),
.data_valid(range_data_valid),
.new_chirp_frame(new_chirp_frame),
// Outputs
.doppler_output(doppler_output),
.doppler_valid(doppler_valid),
.doppler_bin(doppler_bin),
.range_bin(range_bin),
// Status
.processing_active(doppler_processing),
.frame_complete(doppler_frame_done),
.status()
);
// ========== OUTPUT CONNECTIONS ==========
// doppler_output, doppler_valid, doppler_bin, range_bin are directly
// connected to doppler_proc ports above
// ========== STATUS ==========
// ========== DEBUG AND VERIFICATION ==========
reg [31:0] frame_counter;
reg [5:0] chirps_in_current_frame;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
frame_counter <= 0;
chirps_in_current_frame <= 0;
end else begin
// Count chirps in current frame
if (range_data_valid && decimated_range_bin == 0) begin
// First range bin of a chirp
chirps_in_current_frame <= chirps_in_current_frame + 1;
end
// Detect frame completion
if (new_chirp_frame) begin
frame_counter <= frame_counter + 1;
`ifdef SIMULATION
$display("[TOP] Frame %0d started. Previous frame had %0d chirps",
frame_counter, chirps_in_current_frame);
`endif
chirps_in_current_frame <= 0;
end
// Monitor chirp counter pattern
if (chirp_counter != chirp_counter_prev) begin
`ifdef SIMULATION
$display("[TOP] chirp_counter: %0d ? %0d",
chirp_counter_prev, chirp_counter);
`endif
end
end
end
endmodule