`timescale 1ns / 1ps `include "radar_params.vh" 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) // Audit F-0.1: AD9484 OR (overrange) LVDS pair input wire adc_or_p, input wire adc_or_n, output wire adc_pwdn, // Chirp counter from transmitter (for matched filter indexing) input wire [5:0] chirp_counter, // Frame-start pulse from transmitter (CDC-synchronized, 1 clk_100m cycle) input wire tx_frame_start, output wire [31:0] doppler_output, output wire doppler_valid, output wire [4:0] doppler_bin, output wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin, // 9-bit // Raw matched-filter output (debug/bring-up) output wire signed [15:0] range_profile_i_out, output wire signed [15:0] range_profile_q_out, output wire range_profile_valid_out, // Decimated 512-bin range profile (for USB bulk frames / downstream consumers) output wire [15:0] decimated_range_mag_out, output wire decimated_range_valid_out, // Host command inputs (Gap 4: USB Read Path, CDC-synchronized) // CDC-synchronized in radar_system_top.v before reaching here input wire [1:0] host_mode, // Radar mode: 00=STM32, 01=auto-scan, 10=single-chirp input wire host_trigger, // Single-chirp trigger pulse (1 clk cycle) input wire [1:0] host_range_mode, // Range mode: 00=3km (short only), 01=long-range (dual chirp) // Gap 2: Host-configurable chirp timing (CDC-synchronized in radar_system_top.v) input wire [15:0] host_long_chirp_cycles, input wire [15:0] host_long_listen_cycles, input wire [15:0] host_guard_cycles, input wire [15:0] host_short_chirp_cycles, input wire [15:0] host_short_listen_cycles, input wire [5:0] host_chirps_per_elev, // Digital gain control (Fix 3: between DDC output and matched filter) // [3]=direction: 0=amplify(left shift), 1=attenuate(right shift) // [2:0]=shift amount: 0..7 bits. Default 0 = pass-through. input wire [3:0] host_gain_shift, // AGC configuration (opcodes 0x28-0x2C, active only when agc_enable=1) input wire host_agc_enable, // 0x28: 0=manual, 1=auto AGC input wire [7:0] host_agc_target, // 0x29: target peak magnitude input wire [3:0] host_agc_attack, // 0x2A: gain-down step on clipping input wire [3:0] host_agc_decay, // 0x2B: gain-up step when weak input wire [3:0] host_agc_holdoff, // 0x2C: frames before gain-up // STM32 toggle signals for mode 00 (STM32-driven) pass-through. // These are CDC-synchronized in radar_system_top.v / radar_transmitter.v // before reaching this module. In mode 00, the RX mode controller uses // these to synchronize receiver processing with STM32-timed chirps. input wire stm32_new_chirp_rx, input wire stm32_new_elevation_rx, input wire stm32_new_azimuth_rx, // CFAR integration: expose Doppler frame_complete to top level output wire doppler_frame_done_out, // Ground clutter removal controls input wire host_mti_enable, // 1=MTI active, 0=pass-through input wire [2:0] host_dc_notch_width, // DC notch: zero Doppler bins within ±width of DC // AUDIT-C3: AD9484 sign-conversion select (opcode 0x33). Selects DDC // sign-conversion to match the SCLK/DFS strap (SJ1) on the Main Board. // 2'b00 = offset-binary (default), 2'b01 = two's-complement. input wire [1:0] host_adc_format, // AUDIT-S25: AD9484 power-down control (opcode 0x32). Active-high per // AD9484 datasheet ("Power-Down (PWDN)" section). 1'b0 = ADC powered up // (default), 1'b1 = PWDN asserted. Lets the MCU recover the ADC from a // stuck state without dropping main power. Pin drives AD9484 PWDN net via // the R36/R37 divider on the Main Board (CMOS thresholds, no level // translation needed). Stable single-bit level — no CDC needed. input wire host_adc_pwdn, // ADC raw data tap (clk_100m domain, post-DDC, for self-test / debug) output wire [15:0] dbg_adc_i, // DDC output I (16-bit signed, 100 MHz) output wire [15:0] dbg_adc_q, // DDC output Q (16-bit signed, 100 MHz) output wire dbg_adc_valid, // DDC output valid (100 MHz) // AGC status outputs (for status readback / STM32 outer loop) output wire [7:0] agc_saturation_count, // Per-frame clipped sample count output wire [7:0] agc_peak_magnitude, // Per-frame peak (upper 8 bits) output wire [3:0] agc_current_gain, // Effective gain_shift encoding // DDC overflow diagnostics (audit F-6.1 — previously deleted at boundary). // Not yet plumbed into the USB status packet (protocol contract is frozen); // exposed here for gpio aggregation and ILA mark_debug visibility. output wire ddc_overflow_any, output wire [2:0] ddc_saturation_count, // MTI 2-pulse canceller saturation count (audit F-6.3). output wire [7:0] mti_saturation_count_out, // Range-bin decimator watchdog (audit F-6.4 — previously tied off // with an ILA-only note). A high pulse here means the decimator // FSM has not seen the expected number of input samples within // its timeout window, i.e. the upstream FIR/CDC has stalled. output wire range_decim_watchdog, // Audit F-1.2: sticky CIC→FIR CDC overrun flag. Asserts on the first // silent sample drop between the 400 MHz CIC output and the 100 MHz // FIR input; stays high until the next reset. OR'd into the GPIO // diagnostic bit at the top level. output wire ddc_cic_fir_overrun ); // ========== 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; // Gain-controlled signals (between DDC output and matched filter) wire signed [15:0] gc_i, gc_q; wire gc_valid; wire [7:0] gc_saturation_count; // Diagnostic: per-frame clipped sample counter wire [7:0] gc_peak_magnitude; // Diagnostic: per-frame peak magnitude wire [3:0] gc_current_gain; // Diagnostic: effective gain_shift // Reference signal for the processing chain (carries long OR short ref // depending on use_long_chirp — selected by chirp_memory_loader_param) wire [15:0] ref_chirp_real, ref_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; // frame_complete from doppler_processor is a LEVEL signal (high whenever // state == S_IDLE && !frame_buffer_full). Downstream consumers (USB FT2232H, // AGC, CFAR) expect a single-cycle PULSE. Convert here at the source so all // consumers are safe. wire doppler_frame_done_level; // raw level from doppler_processor reg doppler_frame_done_prev; wire doppler_frame_done; // rising-edge pulse (1 clk cycle) // [RX-E FIX] doppler_frame_done_level is HIGH at reset (state==S_IDLE, // frame_buffer_full==0). Initializing prev to 1'b0 produces a spurious // rising-edge pulse on cycle 1, before any real frame has been processed, // which causes a stale AGC gain update and a phantom CFAR tick. Initialize // prev to 1'b1 so the first edge fires only after the doppler processor // actually exits idle for a real frame and returns. always @(posedge clk or negedge reset_n) begin if (!reset_n) doppler_frame_done_prev <= 1'b1; else doppler_frame_done_prev <= doppler_frame_done_level; end assign doppler_frame_done = doppler_frame_done_level & ~doppler_frame_done_prev; assign doppler_frame_done_out = 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 [`RP_RANGE_BIN_WIDTH_MAX-1:0] decimated_range_bin; // 9-bit // ========== MTI CANCELLER SIGNALS ========== wire signed [15:0] mti_range_i; wire signed [15:0] mti_range_q; wire mti_range_valid; wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] mti_range_bin; // 9-bit wire mti_first_chirp; // ========== 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(host_mode), // Controlled by host via USB (default: 2'b01 auto-scan) .range_mode(host_range_mode), // Range mode: 00=3km, 01=long-range (drives chirp type) .stm32_new_chirp(stm32_new_chirp_rx), .stm32_new_elevation(stm32_new_elevation_rx), .stm32_new_azimuth(stm32_new_azimuth_rx), .trigger(host_trigger), // Single-chirp trigger from host via USB // Gap 2: Runtime-configurable timing from host USB commands .cfg_long_chirp_cycles(host_long_chirp_cycles), .cfg_long_listen_cycles(host_long_listen_cycles), .cfg_guard_cycles(host_guard_cycles), .cfg_short_chirp_cycles(host_short_chirp_cycles), .cfg_short_listen_cycles(host_short_listen_cycles), .cfg_chirps_per_elev(host_chirps_per_elev), .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 + Digital Gain // 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 // AUDIT-S25: ADC power-down driven by host_adc_pwdn (opcode 0x32). Default // 0 keeps the ADC powered up — same behavior as the previous hard-tied 1'b0. // Set to 1 to assert AD9484 PWDN; see port comment for full design notes. assign adc_pwdn = host_adc_pwdn; wire adc_overrange_400m; 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), .adc_or_p(adc_or_p), .adc_or_n(adc_or_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), .adc_overrange_400m(adc_overrange_400m) ); // Audit F-0.1: stickify the 400 MHz OR pulse, then CDC to clk_100m via 2FF. // Same reasoning as ddc_cic_fir_overrun: single-bit, low→high-only once // latched, so a 2FF sync is sufficient for a GPIO-class diagnostic. Cleared // only by global reset_n. reg adc_overrange_sticky_400m; always @(posedge clk_400m or negedge reset_n) begin if (!reset_n) adc_overrange_sticky_400m <= 1'b0; else if (adc_overrange_400m) adc_overrange_sticky_400m <= 1'b1; end (* ASYNC_REG = "TRUE" *) reg [1:0] adc_overrange_sync_100m; always @(posedge clk or negedge reset_n) begin if (!reset_n) adc_overrange_sync_100m <= 2'b00; else adc_overrange_sync_100m <= {adc_overrange_sync_100m[0], adc_overrange_sticky_400m}; end wire adc_overrange_100m = adc_overrange_sync_100m[1]; // 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 diagnostic signals (audit F-6.1 — all outputs previously unconnected) wire [1:0] ddc_status_w; wire [7:0] ddc_diagnostics_w; wire ddc_mixer_saturation; wire ddc_filter_overflow; (* mark_debug = "true" *) wire ddc_mixer_saturation_dbg = ddc_mixer_saturation; (* mark_debug = "true" *) wire ddc_filter_overflow_dbg = ddc_filter_overflow; (* mark_debug = "true" *) wire [7:0] ddc_diagnostics_dbg = ddc_diagnostics_w; 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 .adc_format(host_adc_format), // AUDIT-C3: opcode 0x33 selects offset-binary vs 2C .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), // Diagnostics (audit F-6.1) — previously all unconnected .ddc_status(ddc_status_w), .ddc_diagnostics(ddc_diagnostics_w), .mixer_saturation(ddc_mixer_saturation), .filter_overflow(ddc_filter_overflow), // Test/debug inputs — explicit tie-low (were floating) .test_mode(2'b00), .test_phase_inc(16'h0000), .force_saturation(1'b0), .reset_monitors(1'b0), .debug_sample_count(), .debug_internal_i(), .debug_internal_q(), .cdc_cic_fir_overrun(ddc_cic_fir_overrun) ); // Audit F-0.1: AD9484 overrange aggregated here so a single gpio_dig bit // covers DDC-internal saturation, FIR overflow, AND raw ADC clipping. assign ddc_overflow_any = ddc_mixer_saturation | ddc_filter_overflow | adc_overrange_100m; assign ddc_saturation_count = ddc_diagnostics_w[7:5]; 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() ); // 2b. Digital Gain Control with AGC // Host-configurable power-of-2 shift between DDC output and matched filter. // Default gain_shift=0, agc_enable=0 → pass-through (no behavioral change). // When agc_enable=1: auto-adjusts gain per frame based on peak/saturation. rx_gain_control gain_ctrl ( .clk(clk), .reset_n(reset_n), .data_i_in(adc_i_scaled), .data_q_in(adc_q_scaled), .valid_in(adc_valid_sync), .gain_shift(host_gain_shift), // AGC configuration .agc_enable(host_agc_enable), .agc_target(host_agc_target), .agc_attack(host_agc_attack), .agc_decay(host_agc_decay), .agc_holdoff(host_agc_holdoff), // Frame boundary from Doppler processor .frame_boundary(doppler_frame_done), // Outputs .data_i_out(gc_i), .data_q_out(gc_q), .valid_out(gc_valid), .saturation_count(gc_saturation_count), .peak_magnitude(gc_peak_magnitude), .current_gain(gc_current_gain) ); // 3. Dual Chirp Memory Loader wire [10: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) ); // 4. [RX-B FIX, Option A 2026-04-23] Reference chirp wired to MF chain with // a single-FF alignment delay. Previously ran through `latency_buffer` with // LATENCY=3187 — that module is a count-N-valid-pulses-then-prime FIFO, // not a true cycle delay. It needed ~2 frames of mem_request pulses before // any ref reached the chain (so frame 1 saw all-zero ref → noise output). // Removed in favour of a direct-wire path with one FF. // // Why the 1-FF stage: multi_segment ST_PROCESSING latches `adc_data` through // one register stage (`fft_input_i <= buf_rdata_i`) before it reaches the // chain. The ref path from chirp_memory_loader is combinational into the // chain. Without compensation, ref leads sig by 1 cycle → autocorrelation // peak at bin 1 instead of bin 0 (verified in tb/tb_rxb_fullchain_latency.v // against fft_engine.v synthesis path: peak/mean ratio ~80× confirms clean // correlation; peak position fixed to bin 0 by this register stage). reg [15:0] ref_chirp_real_d, ref_chirp_imag_d; always @(posedge clk or negedge reset_n) begin if (!reset_n) begin ref_chirp_real_d <= 16'd0; ref_chirp_imag_d <= 16'd0; end else begin ref_chirp_real_d <= ref_i; ref_chirp_imag_d <= ref_q; end end assign ref_chirp_real = ref_chirp_real_d; assign ref_chirp_imag = ref_chirp_imag_d; // 5. Dual Chirp Matched Filter wire signed [15:0] range_profile_i; wire signed [15:0] range_profile_q; wire range_valid; // Expose matched filter output to top level for USB range profile assign range_profile_i_out = range_profile_i; assign range_profile_q_out = range_profile_q; assign range_profile_valid_out = range_valid; // Manhattan magnitude: |I| + |Q|, saturated to 16 bits wire [15:0] abs_mti_i = mti_range_i[15] ? (~mti_range_i + 16'd1) : mti_range_i; wire [15:0] abs_mti_q = mti_range_q[15] ? (~mti_range_q + 16'd1) : mti_range_q; wire [16:0] manhattan_sum = {1'b0, abs_mti_i} + {1'b0, abs_mti_q}; assign decimated_range_mag_out = manhattan_sum[16] ? 16'hFFFF : manhattan_sum[15:0]; assign decimated_range_valid_out = mti_range_valid; matched_filter_multi_segment mf_dual ( .clk(clk), .reset_n(reset_n), .ddc_i({{2{gc_i[15]}}, gc_i}), .ddc_q({{2{gc_q[15]}}, gc_q}), .ddc_valid(gc_valid), .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), .ref_chirp_real(ref_chirp_real), // 1-FF aligned ref (RX-B fix) .ref_chirp_imag(ref_chirp_imag), .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 2048 range bins to 512 bins for Doppler range_bin_decimator #( .INPUT_BINS(`RP_FFT_SIZE), // 2048 .OUTPUT_BINS(`RP_MAX_OUTPUT_BINS), // 512 (50T) / 4096 (200T) [RX-D] .DECIMATION_FACTOR(`RP_DECIMATION_FACTOR) // 4 ) 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(11'd0), .watchdog_timeout(range_decim_watchdog) // Audit F-6.4 — plumbed out ); // ========== MTI CANCELLER (Ground Clutter Removal) ========== // 2-pulse canceller: subtracts previous chirp from current chirp. // H(z) = 1 - z^{-1} → null at DC Doppler, removes stationary clutter. // When host_mti_enable=0: transparent pass-through. mti_canceller #( .NUM_RANGE_BINS(`RP_MAX_OUTPUT_BINS), // 512 (50T) / 4096 (200T) [RX-D] .DATA_WIDTH(`RP_DATA_WIDTH) // 16 ) mti_inst ( .clk(clk), .reset_n(reset_n), .range_i_in(decimated_range_i), .range_q_in(decimated_range_q), .range_valid_in(decimated_range_valid), .range_bin_in(decimated_range_bin), .range_i_out(mti_range_i), .range_q_out(mti_range_q), .range_valid_out(mti_range_valid), .range_bin_out(mti_range_bin), .mti_enable(host_mti_enable), .use_long_chirp(use_long_chirp), .mti_first_chirp(mti_first_chirp), .mti_saturation_count(mti_saturation_count_out) ); // ========== FRAME SYNC FROM TRANSMITTER ========== // [FPGA-001 FIXED] Use the authoritative new_chirp_frame signal from the // transmitter (via plfm_chirp_controller_enhanced), CDC-synchronized to // clk_100m in radar_system_top. Previous code tried to derive frame // boundaries from chirp_counter == 0, but that counter comes from the // transmitter path (plfm_chirp_controller_enhanced) which does NOT wrap // at chirps_per_elev — it overflows to N and only wraps at 6-bit rollover // (64). This caused frame pulses at half the expected rate for N=32. reg tx_frame_start_prev; reg new_frame_pulse; always @(posedge clk or negedge reset_n) begin if (!reset_n) begin tx_frame_start_prev <= 1'b0; new_frame_pulse <= 1'b0; end else begin new_frame_pulse <= 1'b0; // Edge detect: tx_frame_start is a toggle-CDC derived pulse that // may be 1 clock wide. Capture rising edge for clean 1-cycle pulse. if (tx_frame_start && !tx_frame_start_prev) begin new_frame_pulse <= 1'b1; end tx_frame_start_prev <= tx_frame_start; end end assign new_chirp_frame = new_frame_pulse; // ========== DATA PACKING FOR DOPPLER ========== // Use MTI-filtered data (or pass-through if MTI disabled) assign range_data_32bit = {mti_range_q, mti_range_i}; assign range_data_valid = mti_range_valid; // ========== DOPPLER PROCESSOR ========== doppler_processor_optimized #( .DOPPLER_FFT_SIZE(`RP_DOPPLER_FFT_SIZE), // 16 .RANGE_BINS(`RP_MAX_OUTPUT_BINS), // 512 (50T) / 4096 (200T) [RX-D] .CHIRPS_PER_FRAME(`RP_CHIRPS_PER_FRAME), // 32 .CHIRPS_PER_SUBFRAME(`RP_CHIRPS_PER_SUBFRAME) // 16 ) 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_level), .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 end end // ========== ADC DEBUG TAP (for self-test / bring-up) ========== assign dbg_adc_i = adc_i_scaled; assign dbg_adc_q = adc_q_scaled; assign dbg_adc_valid = adc_valid_sync; // ========== AGC STATUS OUTPUTS ========== assign agc_saturation_count = gc_saturation_count; assign agc_peak_magnitude = gc_peak_magnitude; assign agc_current_gain = gc_current_gain; endmodule