Integrate MTI canceller and DC notch filter for ground clutter removal
MTI canceller (2-pulse, H(z)=1-z^{-1}) between range decimator and
Doppler processor. Subtracts previous chirp from current, nulling DC
Doppler (stationary clutter). Pass-through when host_mti_enable=0.
DC notch filter (post-Doppler, pre-CFAR) zeros bins within
+/-host_dc_notch_width of DC. Complements MTI for residual clutter.
New host registers: 0x26 (mti_enable), 0x27 (dc_notch_width).
Both default to 0 (disabled) - fully backward-compatible.
Verification: 23/23 regression, 29/29 MTI standalone, 3/3 real-data
co-sim (5137/5137 exact match) all PASS.
This commit is contained in:
@@ -0,0 +1,174 @@
|
|||||||
|
`timescale 1ns / 1ps
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mti_canceller.v
|
||||||
|
*
|
||||||
|
* Moving Target Indication (MTI) — 2-pulse canceller for ground clutter removal.
|
||||||
|
*
|
||||||
|
* Sits between the range bin decimator and the Doppler processor in the
|
||||||
|
* AERIS-10 receiver chain. Subtracts the previous chirp's range profile
|
||||||
|
* from the current chirp's profile, implementing H(z) = 1 - z^{-1} in
|
||||||
|
* slow-time. This places a null at zero Doppler (DC), removing stationary
|
||||||
|
* ground clutter while passing moving targets through.
|
||||||
|
*
|
||||||
|
* Signal chain position:
|
||||||
|
* Range Bin Decimator → [MTI Canceller] → Doppler Processor
|
||||||
|
*
|
||||||
|
* Algorithm:
|
||||||
|
* For each range bin r (0..NUM_RANGE_BINS-1):
|
||||||
|
* mti_out_i[r] = current_i[r] - previous_i[r]
|
||||||
|
* mti_out_q[r] = current_q[r] - previous_q[r]
|
||||||
|
*
|
||||||
|
* The previous chirp's 64 range bins are stored in a small BRAM.
|
||||||
|
* On the very first chirp after reset (or enable), there is no previous
|
||||||
|
* data — output is zero (muted) for that first chirp.
|
||||||
|
*
|
||||||
|
* When mti_enable=0, the module is a transparent pass-through with zero
|
||||||
|
* latency penalty (data goes straight through combinationally registered).
|
||||||
|
*
|
||||||
|
* Resources:
|
||||||
|
* - 2 BRAM18 (64 x 16-bit I + 64 x 16-bit Q) or distributed RAM
|
||||||
|
* - ~30 LUTs (subtract + mux)
|
||||||
|
* - ~40 FFs (pipeline + control)
|
||||||
|
* - 0 DSP48
|
||||||
|
*
|
||||||
|
* Clock domain: clk (100 MHz)
|
||||||
|
*/
|
||||||
|
|
||||||
|
module mti_canceller #(
|
||||||
|
parameter NUM_RANGE_BINS = 64,
|
||||||
|
parameter DATA_WIDTH = 16
|
||||||
|
) (
|
||||||
|
input wire clk,
|
||||||
|
input wire reset_n,
|
||||||
|
|
||||||
|
// ========== INPUT (from range bin decimator) ==========
|
||||||
|
input wire signed [DATA_WIDTH-1:0] range_i_in,
|
||||||
|
input wire signed [DATA_WIDTH-1:0] range_q_in,
|
||||||
|
input wire range_valid_in,
|
||||||
|
input wire [5:0] range_bin_in,
|
||||||
|
|
||||||
|
// ========== OUTPUT (to Doppler processor) ==========
|
||||||
|
output reg signed [DATA_WIDTH-1:0] range_i_out,
|
||||||
|
output reg signed [DATA_WIDTH-1:0] range_q_out,
|
||||||
|
output reg range_valid_out,
|
||||||
|
output reg [5:0] range_bin_out,
|
||||||
|
|
||||||
|
// ========== CONFIGURATION ==========
|
||||||
|
input wire mti_enable, // 1=MTI active, 0=pass-through
|
||||||
|
|
||||||
|
// ========== STATUS ==========
|
||||||
|
output reg mti_first_chirp // 1 during first chirp (output muted)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PREVIOUS CHIRP BUFFER (64 x 16-bit I, 64 x 16-bit Q)
|
||||||
|
// ============================================================================
|
||||||
|
// Small enough for distributed RAM on XC7A200T (64 entries).
|
||||||
|
// Using separate I/Q arrays for clean read/write.
|
||||||
|
|
||||||
|
reg signed [DATA_WIDTH-1:0] prev_i [0:NUM_RANGE_BINS-1];
|
||||||
|
reg signed [DATA_WIDTH-1:0] prev_q [0:NUM_RANGE_BINS-1];
|
||||||
|
|
||||||
|
// Track whether we have valid previous data
|
||||||
|
reg has_previous;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MTI PROCESSING
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Read previous chirp data (combinational)
|
||||||
|
wire signed [DATA_WIDTH-1:0] prev_i_rd = prev_i[range_bin_in];
|
||||||
|
wire signed [DATA_WIDTH-1:0] prev_q_rd = prev_q[range_bin_in];
|
||||||
|
|
||||||
|
// Compute difference with saturation
|
||||||
|
// Subtraction can produce DATA_WIDTH+1 bits; saturate back to DATA_WIDTH.
|
||||||
|
wire signed [DATA_WIDTH:0] diff_i_full = {range_i_in[DATA_WIDTH-1], range_i_in}
|
||||||
|
- {prev_i_rd[DATA_WIDTH-1], prev_i_rd};
|
||||||
|
wire signed [DATA_WIDTH:0] diff_q_full = {range_q_in[DATA_WIDTH-1], range_q_in}
|
||||||
|
- {prev_q_rd[DATA_WIDTH-1], prev_q_rd};
|
||||||
|
|
||||||
|
// Saturate to DATA_WIDTH bits
|
||||||
|
wire signed [DATA_WIDTH-1:0] diff_i_sat;
|
||||||
|
wire signed [DATA_WIDTH-1:0] diff_q_sat;
|
||||||
|
|
||||||
|
assign diff_i_sat = (diff_i_full > $signed({{2{1'b0}}, {(DATA_WIDTH-1){1'b1}}}))
|
||||||
|
? $signed({1'b0, {(DATA_WIDTH-1){1'b1}}}) // +max
|
||||||
|
: (diff_i_full < $signed({{2{1'b1}}, {(DATA_WIDTH-1){1'b0}}}))
|
||||||
|
? $signed({1'b1, {(DATA_WIDTH-1){1'b0}}}) // -max
|
||||||
|
: diff_i_full[DATA_WIDTH-1:0];
|
||||||
|
|
||||||
|
assign diff_q_sat = (diff_q_full > $signed({{2{1'b0}}, {(DATA_WIDTH-1){1'b1}}}))
|
||||||
|
? $signed({1'b0, {(DATA_WIDTH-1){1'b1}}})
|
||||||
|
: (diff_q_full < $signed({{2{1'b1}}, {(DATA_WIDTH-1){1'b0}}}))
|
||||||
|
? $signed({1'b1, {(DATA_WIDTH-1){1'b0}}})
|
||||||
|
: diff_q_full[DATA_WIDTH-1:0];
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MAIN LOGIC
|
||||||
|
// ============================================================================
|
||||||
|
always @(posedge clk or negedge reset_n) begin
|
||||||
|
if (!reset_n) begin
|
||||||
|
range_i_out <= {DATA_WIDTH{1'b0}};
|
||||||
|
range_q_out <= {DATA_WIDTH{1'b0}};
|
||||||
|
range_valid_out <= 1'b0;
|
||||||
|
range_bin_out <= 6'd0;
|
||||||
|
has_previous <= 1'b0;
|
||||||
|
mti_first_chirp <= 1'b1;
|
||||||
|
end else begin
|
||||||
|
// Default: no valid output
|
||||||
|
range_valid_out <= 1'b0;
|
||||||
|
|
||||||
|
if (range_valid_in) begin
|
||||||
|
// Always store current sample as "previous" for next chirp
|
||||||
|
prev_i[range_bin_in] <= range_i_in;
|
||||||
|
prev_q[range_bin_in] <= range_q_in;
|
||||||
|
|
||||||
|
// Output path
|
||||||
|
range_bin_out <= range_bin_in;
|
||||||
|
|
||||||
|
if (!mti_enable) begin
|
||||||
|
// Pass-through mode: no MTI processing
|
||||||
|
range_i_out <= range_i_in;
|
||||||
|
range_q_out <= range_q_in;
|
||||||
|
range_valid_out <= 1'b1;
|
||||||
|
// Reset first-chirp state when MTI is disabled
|
||||||
|
has_previous <= 1'b0;
|
||||||
|
mti_first_chirp <= 1'b1;
|
||||||
|
end else if (!has_previous) begin
|
||||||
|
// First chirp after enable: mute output (no subtraction possible).
|
||||||
|
// Still emit valid=1 with zero data so Doppler processor gets
|
||||||
|
// the expected number of samples per frame.
|
||||||
|
range_i_out <= {DATA_WIDTH{1'b0}};
|
||||||
|
range_q_out <= {DATA_WIDTH{1'b0}};
|
||||||
|
range_valid_out <= 1'b1;
|
||||||
|
|
||||||
|
// After last range bin of first chirp, mark previous as valid
|
||||||
|
if (range_bin_in == NUM_RANGE_BINS - 1) begin
|
||||||
|
has_previous <= 1'b1;
|
||||||
|
mti_first_chirp <= 1'b0;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
// Normal MTI: subtract previous from current
|
||||||
|
range_i_out <= diff_i_sat;
|
||||||
|
range_q_out <= diff_q_sat;
|
||||||
|
range_valid_out <= 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MEMORY INITIALIZATION (simulation only)
|
||||||
|
// ============================================================================
|
||||||
|
`ifdef SIMULATION
|
||||||
|
integer init_k;
|
||||||
|
initial begin
|
||||||
|
for (init_k = 0; init_k < NUM_RANGE_BINS; init_k = init_k + 1) begin
|
||||||
|
prev_i[init_k] = 0;
|
||||||
|
prev_q[init_k] = 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
`endif
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -51,7 +51,11 @@ module radar_receiver_final (
|
|||||||
input wire stm32_new_azimuth_rx,
|
input wire stm32_new_azimuth_rx,
|
||||||
|
|
||||||
// CFAR integration: expose Doppler frame_complete to top level
|
// CFAR integration: expose Doppler frame_complete to top level
|
||||||
output wire doppler_frame_done_out
|
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
|
||||||
);
|
);
|
||||||
|
|
||||||
// ========== INTERNAL SIGNALS ==========
|
// ========== INTERNAL SIGNALS ==========
|
||||||
@@ -102,6 +106,13 @@ wire signed [15:0] decimated_range_q;
|
|||||||
wire decimated_range_valid;
|
wire decimated_range_valid;
|
||||||
wire [5:0] decimated_range_bin;
|
wire [5:0] decimated_range_bin;
|
||||||
|
|
||||||
|
// ========== MTI CANCELLER SIGNALS ==========
|
||||||
|
wire signed [15:0] mti_range_i;
|
||||||
|
wire signed [15:0] mti_range_q;
|
||||||
|
wire mti_range_valid;
|
||||||
|
wire [5:0] mti_range_bin;
|
||||||
|
wire mti_first_chirp;
|
||||||
|
|
||||||
// ========== RADAR MODE CONTROLLER SIGNALS ==========
|
// ========== RADAR MODE CONTROLLER SIGNALS ==========
|
||||||
wire rmc_scanning;
|
wire rmc_scanning;
|
||||||
wire rmc_scan_complete;
|
wire rmc_scan_complete;
|
||||||
@@ -191,7 +202,7 @@ ddc_400m_enhanced ddc(
|
|||||||
.baseband_valid_i(ddc_valid_i), // Valid at 100MHz
|
.baseband_valid_i(ddc_valid_i), // Valid at 100MHz
|
||||||
.baseband_valid_q(ddc_valid_q),
|
.baseband_valid_q(ddc_valid_q),
|
||||||
.mixers_enable(1'b1)
|
.mixers_enable(1'b1)
|
||||||
);
|
);
|
||||||
|
|
||||||
ddc_input_interface ddc_if (
|
ddc_input_interface ddc_if (
|
||||||
.clk(clk),
|
.clk(clk),
|
||||||
@@ -328,6 +339,28 @@ range_bin_decimator #(
|
|||||||
.watchdog_timeout() // Diagnostic — unconnected (monitored via ILA if needed)
|
.watchdog_timeout() // Diagnostic — unconnected (monitored via ILA if needed)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ========== 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(64),
|
||||||
|
.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),
|
||||||
|
.mti_first_chirp(mti_first_chirp)
|
||||||
|
);
|
||||||
|
|
||||||
// ========== FRAME SYNC USING chirp_counter ==========
|
// ========== FRAME SYNC USING chirp_counter ==========
|
||||||
reg [5:0] chirp_counter_prev;
|
reg [5:0] chirp_counter_prev;
|
||||||
reg new_frame_pulse;
|
reg new_frame_pulse;
|
||||||
@@ -360,8 +393,9 @@ end
|
|||||||
assign new_chirp_frame = new_frame_pulse;
|
assign new_chirp_frame = new_frame_pulse;
|
||||||
|
|
||||||
// ========== DATA PACKING FOR DOPPLER ==========
|
// ========== DATA PACKING FOR DOPPLER ==========
|
||||||
assign range_data_32bit = {decimated_range_q, decimated_range_i};
|
// Use MTI-filtered data (or pass-through if MTI disabled)
|
||||||
assign range_data_valid = decimated_range_valid;
|
assign range_data_32bit = {mti_range_q, mti_range_i};
|
||||||
|
assign range_data_valid = mti_range_valid;
|
||||||
|
|
||||||
// ========== DOPPLER PROCESSOR ==========
|
// ========== DOPPLER PROCESSOR ==========
|
||||||
doppler_processor_optimized #(
|
doppler_processor_optimized #(
|
||||||
|
|||||||
@@ -234,6 +234,10 @@ reg [7:0] host_cfar_alpha; // Opcode 0x23: threshold multiplier (Q4.4)
|
|||||||
reg [1:0] host_cfar_mode; // Opcode 0x24: 00=CA, 01=GO, 10=SO
|
reg [1:0] host_cfar_mode; // Opcode 0x24: 00=CA, 01=GO, 10=SO
|
||||||
reg host_cfar_enable; // Opcode 0x25: 1=CFAR, 0=simple threshold
|
reg host_cfar_enable; // Opcode 0x25: 1=CFAR, 0=simple threshold
|
||||||
|
|
||||||
|
// Ground clutter removal registers (host-configurable via USB)
|
||||||
|
reg host_mti_enable; // Opcode 0x26: 1=MTI active, 0=pass-through
|
||||||
|
reg [2:0] host_dc_notch_width; // Opcode 0x27: DC notch ±width bins (0=off, 1..7)
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// CLOCK BUFFERING
|
// CLOCK BUFFERING
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -486,7 +490,10 @@ radar_receiver_final rx_inst (
|
|||||||
.stm32_new_elevation_rx(stm32_new_elevation),
|
.stm32_new_elevation_rx(stm32_new_elevation),
|
||||||
.stm32_new_azimuth_rx(stm32_new_azimuth),
|
.stm32_new_azimuth_rx(stm32_new_azimuth),
|
||||||
// CFAR: Doppler frame-complete pulse
|
// CFAR: Doppler frame-complete pulse
|
||||||
.doppler_frame_done_out(rx_frame_complete)
|
.doppler_frame_done_out(rx_frame_complete),
|
||||||
|
// Ground clutter removal
|
||||||
|
.host_mti_enable(host_mti_enable),
|
||||||
|
.host_dc_notch_width(host_dc_notch_width)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -499,6 +506,26 @@ assign rx_doppler_real = rx_doppler_output[15:0];
|
|||||||
assign rx_doppler_imag = rx_doppler_output[31:16];
|
assign rx_doppler_imag = rx_doppler_output[31:16];
|
||||||
assign rx_doppler_data_valid = rx_doppler_valid;
|
assign rx_doppler_data_valid = rx_doppler_valid;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DC NOTCH FILTER (post-Doppler-FFT, pre-CFAR)
|
||||||
|
// ============================================================================
|
||||||
|
// Zeros out Doppler bins within ±host_dc_notch_width of DC (bin 0).
|
||||||
|
// In a 32-point FFT, DC is bin 0; negative Doppler wraps to bins 31,30,...
|
||||||
|
// notch_width=1 → zero bins {0}. notch_width=2 → zero bins {0,1,31}. etc.
|
||||||
|
// When host_dc_notch_width=0: pass-through (no zeroing).
|
||||||
|
|
||||||
|
wire dc_notch_active;
|
||||||
|
wire [4:0] dop_bin_unsigned = rx_doppler_bin;
|
||||||
|
assign dc_notch_active = (host_dc_notch_width != 3'd0) &&
|
||||||
|
(dop_bin_unsigned < {2'b0, host_dc_notch_width} ||
|
||||||
|
dop_bin_unsigned > (5'd31 - {2'b0, host_dc_notch_width} + 5'd1));
|
||||||
|
|
||||||
|
// Notched Doppler data: zero I/Q when in notch zone, pass through otherwise
|
||||||
|
wire [31:0] notched_doppler_data = dc_notch_active ? 32'd0 : rx_doppler_output;
|
||||||
|
wire notched_doppler_valid = rx_doppler_valid;
|
||||||
|
wire [4:0] notched_doppler_bin = rx_doppler_bin;
|
||||||
|
wire [5:0] notched_range_bin = rx_range_bin;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// CFAR DETECTOR (replaces simple threshold detector)
|
// CFAR DETECTOR (replaces simple threshold detector)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -520,11 +547,11 @@ cfar_ca cfar_inst (
|
|||||||
.clk(clk_100m_buf),
|
.clk(clk_100m_buf),
|
||||||
.reset_n(sys_reset_n),
|
.reset_n(sys_reset_n),
|
||||||
|
|
||||||
// Doppler processor outputs
|
// Doppler processor outputs (DC-notch filtered)
|
||||||
.doppler_data(rx_doppler_output),
|
.doppler_data(notched_doppler_data),
|
||||||
.doppler_valid(rx_doppler_valid),
|
.doppler_valid(notched_doppler_valid),
|
||||||
.doppler_bin_in(rx_doppler_bin),
|
.doppler_bin_in(notched_doppler_bin),
|
||||||
.range_bin_in(rx_range_bin),
|
.range_bin_in(notched_range_bin),
|
||||||
.frame_complete(rx_frame_complete),
|
.frame_complete(rx_frame_complete),
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
@@ -703,6 +730,9 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
|||||||
host_cfar_alpha <= 8'h30; // alpha=3.0 (Q4.4)
|
host_cfar_alpha <= 8'h30; // alpha=3.0 (Q4.4)
|
||||||
host_cfar_mode <= 2'b00; // CA-CFAR
|
host_cfar_mode <= 2'b00; // CA-CFAR
|
||||||
host_cfar_enable <= 1'b0; // Disabled (simple threshold)
|
host_cfar_enable <= 1'b0; // Disabled (simple threshold)
|
||||||
|
// Ground clutter removal defaults (disabled — backward-compatible)
|
||||||
|
host_mti_enable <= 1'b0; // MTI off
|
||||||
|
host_dc_notch_width <= 3'd0; // DC notch off
|
||||||
end else begin
|
end else begin
|
||||||
host_trigger_pulse <= 1'b0; // Self-clearing pulse
|
host_trigger_pulse <= 1'b0; // Self-clearing pulse
|
||||||
host_status_request <= 1'b0; // Self-clearing pulse
|
host_status_request <= 1'b0; // Self-clearing pulse
|
||||||
@@ -741,6 +771,9 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
|||||||
8'h23: host_cfar_alpha <= usb_cmd_value[7:0];
|
8'h23: host_cfar_alpha <= usb_cmd_value[7:0];
|
||||||
8'h24: host_cfar_mode <= usb_cmd_value[1:0];
|
8'h24: host_cfar_mode <= usb_cmd_value[1:0];
|
||||||
8'h25: host_cfar_enable <= usb_cmd_value[0];
|
8'h25: host_cfar_enable <= usb_cmd_value[0];
|
||||||
|
// Ground clutter removal opcodes
|
||||||
|
8'h26: host_mti_enable <= usb_cmd_value[0];
|
||||||
|
8'h27: host_dc_notch_width <= usb_cmd_value[2:0];
|
||||||
8'hFF: host_status_request <= 1'b1; // Gap 2: status readback
|
8'hFF: host_status_request <= 1'b1; // Gap 2: status readback
|
||||||
default: ;
|
default: ;
|
||||||
endcase
|
endcase
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ PROD_RTL=(
|
|||||||
radar_mode_controller.v
|
radar_mode_controller.v
|
||||||
rx_gain_control.v
|
rx_gain_control.v
|
||||||
cfar_ca.v
|
cfar_ca.v
|
||||||
|
mti_canceller.v
|
||||||
)
|
)
|
||||||
|
|
||||||
# Source-only RTL (not instantiated at top level, but should still be lint-clean)
|
# Source-only RTL (not instantiated at top level, but should still be lint-clean)
|
||||||
@@ -377,6 +378,10 @@ run_test "RX Gain Control (digital gain)" \
|
|||||||
tb/tb_rx_gain_control.vvp \
|
tb/tb_rx_gain_control.vvp \
|
||||||
tb/tb_rx_gain_control.v rx_gain_control.v
|
tb/tb_rx_gain_control.v rx_gain_control.v
|
||||||
|
|
||||||
|
run_test "MTI Canceller (ground clutter)" \
|
||||||
|
tb/tb_mti_canceller.vvp \
|
||||||
|
tb/tb_mti_canceller.v mti_canceller.v
|
||||||
|
|
||||||
run_test "CFAR CA Detector" \
|
run_test "CFAR CA Detector" \
|
||||||
tb/tb_cfar_ca.vvp \
|
tb/tb_cfar_ca.vvp \
|
||||||
tb/tb_cfar_ca.v cfar_ca.v
|
tb/tb_cfar_ca.v cfar_ca.v
|
||||||
@@ -405,7 +410,7 @@ if [[ "$QUICK" -eq 0 ]]; then
|
|||||||
chirp_memory_loader_param.v latency_buffer.v \
|
chirp_memory_loader_param.v latency_buffer.v \
|
||||||
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
||||||
rx_gain_control.v
|
rx_gain_control.v mti_canceller.v
|
||||||
|
|
||||||
# Golden compare
|
# Golden compare
|
||||||
run_test "Receiver (golden compare)" \
|
run_test "Receiver (golden compare)" \
|
||||||
@@ -417,7 +422,7 @@ if [[ "$QUICK" -eq 0 ]]; then
|
|||||||
chirp_memory_loader_param.v latency_buffer.v \
|
chirp_memory_loader_param.v latency_buffer.v \
|
||||||
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
||||||
rx_gain_control.v
|
rx_gain_control.v mti_canceller.v
|
||||||
|
|
||||||
# Full system top (monitoring-only, legacy)
|
# Full system top (monitoring-only, legacy)
|
||||||
run_test "System Top (radar_system_tb)" \
|
run_test "System Top (radar_system_tb)" \
|
||||||
@@ -431,7 +436,7 @@ if [[ "$QUICK" -eq 0 ]]; then
|
|||||||
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
||||||
usb_data_interface.v edge_detector.v radar_mode_controller.v \
|
usb_data_interface.v edge_detector.v radar_mode_controller.v \
|
||||||
rx_gain_control.v cfar_ca.v
|
rx_gain_control.v cfar_ca.v mti_canceller.v
|
||||||
|
|
||||||
# E2E integration (46 strict checks: TX, RX, USB R/W, CDC, safety, reset)
|
# E2E integration (46 strict checks: TX, RX, USB R/W, CDC, safety, reset)
|
||||||
run_test "System E2E (tb_system_e2e)" \
|
run_test "System E2E (tb_system_e2e)" \
|
||||||
@@ -445,7 +450,7 @@ if [[ "$QUICK" -eq 0 ]]; then
|
|||||||
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
||||||
usb_data_interface.v edge_detector.v radar_mode_controller.v \
|
usb_data_interface.v edge_detector.v radar_mode_controller.v \
|
||||||
rx_gain_control.v cfar_ca.v
|
rx_gain_control.v cfar_ca.v mti_canceller.v
|
||||||
else
|
else
|
||||||
echo " (skipped receiver golden + system top + E2E — use without --quick)"
|
echo " (skipped receiver golden + system top + E2E — use without --quick)"
|
||||||
SKIP=$((SKIP + 4))
|
SKIP=$((SKIP + 4))
|
||||||
|
|||||||
@@ -0,0 +1,491 @@
|
|||||||
|
`timescale 1ns / 1ps
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tb_mti_canceller.v
|
||||||
|
*
|
||||||
|
* Testbench for mti_canceller.v (Moving Target Indication).
|
||||||
|
* Uses [PASS]/[FAIL] markers for run_regression.sh compatibility.
|
||||||
|
*
|
||||||
|
* Tests:
|
||||||
|
* T1: Pass-through mode (mti_enable=0) — data unchanged
|
||||||
|
* T2: First chirp muted (zeros) when MTI enabled
|
||||||
|
* T3: Second chirp = current - previous (correct subtraction)
|
||||||
|
* T4: Stationary target cancels to zero
|
||||||
|
* T5: Moving target (phase shift) passes through
|
||||||
|
* T6: Saturation on large difference
|
||||||
|
* T7: Enable toggle mid-stream — clean transition
|
||||||
|
* T8: Reset during operation — clean recovery
|
||||||
|
* T9: range_bin_out tracks range_bin_in
|
||||||
|
* T10: Back-to-back chirps (3+ chirps, verify continuous operation)
|
||||||
|
* T11: Negative input values handled correctly
|
||||||
|
*/
|
||||||
|
|
||||||
|
module tb_mti_canceller;
|
||||||
|
|
||||||
|
parameter DATA_W = 16;
|
||||||
|
parameter NUM_BINS = 64;
|
||||||
|
parameter CLK_PERIOD = 10;
|
||||||
|
|
||||||
|
reg clk;
|
||||||
|
reg reset_n;
|
||||||
|
|
||||||
|
reg signed [DATA_W-1:0] range_i_in;
|
||||||
|
reg signed [DATA_W-1:0] range_q_in;
|
||||||
|
reg range_valid_in;
|
||||||
|
reg [5:0] range_bin_in;
|
||||||
|
reg mti_enable;
|
||||||
|
|
||||||
|
wire signed [DATA_W-1:0] range_i_out;
|
||||||
|
wire signed [DATA_W-1:0] range_q_out;
|
||||||
|
wire range_valid_out;
|
||||||
|
wire [5:0] range_bin_out;
|
||||||
|
wire mti_first_chirp;
|
||||||
|
|
||||||
|
integer pass_count, fail_count;
|
||||||
|
|
||||||
|
// Output capture
|
||||||
|
reg signed [DATA_W-1:0] cap_i [0:NUM_BINS-1];
|
||||||
|
reg signed [DATA_W-1:0] cap_q [0:NUM_BINS-1];
|
||||||
|
reg [5:0] cap_bin [0:NUM_BINS-1];
|
||||||
|
integer cap_count;
|
||||||
|
|
||||||
|
mti_canceller #(
|
||||||
|
.NUM_RANGE_BINS(NUM_BINS),
|
||||||
|
.DATA_WIDTH(DATA_W)
|
||||||
|
) dut (
|
||||||
|
.clk(clk),
|
||||||
|
.reset_n(reset_n),
|
||||||
|
.range_i_in(range_i_in),
|
||||||
|
.range_q_in(range_q_in),
|
||||||
|
.range_valid_in(range_valid_in),
|
||||||
|
.range_bin_in(range_bin_in),
|
||||||
|
.range_i_out(range_i_out),
|
||||||
|
.range_q_out(range_q_out),
|
||||||
|
.range_valid_out(range_valid_out),
|
||||||
|
.range_bin_out(range_bin_out),
|
||||||
|
.mti_enable(mti_enable),
|
||||||
|
.mti_first_chirp(mti_first_chirp)
|
||||||
|
);
|
||||||
|
|
||||||
|
initial clk = 0;
|
||||||
|
always #(CLK_PERIOD/2) clk = ~clk;
|
||||||
|
|
||||||
|
task check;
|
||||||
|
input integer tnum;
|
||||||
|
input [255:0] desc;
|
||||||
|
input condition;
|
||||||
|
begin
|
||||||
|
if (condition) begin
|
||||||
|
$display("[PASS(T%0d)] %0s", tnum, desc);
|
||||||
|
pass_count = pass_count + 1;
|
||||||
|
end else begin
|
||||||
|
$display("[FAIL(T%0d)] %0s", tnum, desc);
|
||||||
|
fail_count = fail_count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task do_reset;
|
||||||
|
begin
|
||||||
|
reset_n = 0;
|
||||||
|
range_i_in = 0;
|
||||||
|
range_q_in = 0;
|
||||||
|
range_valid_in = 0;
|
||||||
|
range_bin_in = 0;
|
||||||
|
repeat (5) @(posedge clk);
|
||||||
|
reset_n = 1;
|
||||||
|
repeat (2) @(posedge clk);
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// Feed one range bin sample
|
||||||
|
task feed_sample;
|
||||||
|
input [5:0] bin;
|
||||||
|
input signed [DATA_W-1:0] i_val;
|
||||||
|
input signed [DATA_W-1:0] q_val;
|
||||||
|
begin
|
||||||
|
@(posedge clk);
|
||||||
|
range_i_in <= i_val;
|
||||||
|
range_q_in <= q_val;
|
||||||
|
range_valid_in <= 1'b1;
|
||||||
|
range_bin_in <= bin;
|
||||||
|
@(posedge clk);
|
||||||
|
range_valid_in <= 1'b0;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// Feed a full chirp (64 range bins) with constant I/Q
|
||||||
|
task feed_chirp_const;
|
||||||
|
input signed [DATA_W-1:0] i_val;
|
||||||
|
input signed [DATA_W-1:0] q_val;
|
||||||
|
integer r;
|
||||||
|
begin
|
||||||
|
for (r = 0; r < NUM_BINS; r = r + 1) begin
|
||||||
|
feed_sample(r[5:0], i_val, q_val);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// Feed a chirp where bin r has value i_base + r*i_step
|
||||||
|
task feed_chirp_ramp;
|
||||||
|
input signed [DATA_W-1:0] i_base;
|
||||||
|
input signed [DATA_W-1:0] i_step;
|
||||||
|
input signed [DATA_W-1:0] q_val;
|
||||||
|
integer r;
|
||||||
|
begin
|
||||||
|
for (r = 0; r < NUM_BINS; r = r + 1) begin
|
||||||
|
feed_sample(r[5:0], i_base + i_step * r[DATA_W-1:0], q_val);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// Capture outputs during a chirp
|
||||||
|
task capture_chirp;
|
||||||
|
integer timeout;
|
||||||
|
begin
|
||||||
|
cap_count = 0;
|
||||||
|
timeout = NUM_BINS * 4 + 100;
|
||||||
|
while (cap_count < NUM_BINS && timeout > 0) begin
|
||||||
|
@(posedge clk);
|
||||||
|
timeout = timeout - 1;
|
||||||
|
if (range_valid_out) begin
|
||||||
|
cap_i[cap_count] = range_i_out;
|
||||||
|
cap_q[cap_count] = range_q_out;
|
||||||
|
cap_bin[cap_count] = range_bin_out;
|
||||||
|
cap_count = cap_count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
integer i;
|
||||||
|
reg all_zero;
|
||||||
|
reg all_match;
|
||||||
|
reg signed [DATA_W-1:0] expected;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
$dumpfile("tb_mti_canceller.vcd");
|
||||||
|
$dumpvars(0, tb_mti_canceller);
|
||||||
|
|
||||||
|
pass_count = 0;
|
||||||
|
fail_count = 0;
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T1: Pass-through mode
|
||||||
|
// ================================================================
|
||||||
|
do_reset;
|
||||||
|
mti_enable = 1'b0;
|
||||||
|
|
||||||
|
// Feed one chirp with known data, capture output
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd1000, 16'sd500);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
check(1, "T1.1: Pass-through: 64 outputs", cap_count == 64);
|
||||||
|
check(1, "T1.2: Pass-through: I[0]=1000", cap_i[0] == 16'sd1000);
|
||||||
|
check(1, "T1.3: Pass-through: Q[0]=500", cap_q[0] == 16'sd500);
|
||||||
|
check(1, "T1.4: Pass-through: I[63]=1000", cap_i[63] == 16'sd1000);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T2: First chirp muted when MTI enabled
|
||||||
|
// ================================================================
|
||||||
|
do_reset;
|
||||||
|
mti_enable = 1'b1;
|
||||||
|
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd5000, 16'sd3000);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
all_zero = 1;
|
||||||
|
for (i = 0; i < cap_count; i = i + 1) begin
|
||||||
|
if (cap_i[i] != 0 || cap_q[i] != 0) all_zero = 0;
|
||||||
|
end
|
||||||
|
check(2, "T2.1: First chirp: 64 outputs", cap_count == 64);
|
||||||
|
check(2, "T2.2: First chirp: all zeros (muted)", all_zero == 1);
|
||||||
|
check(2, "T2.3: First chirp: mti_first_chirp was high", dut.has_previous == 1);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T3: Second chirp = current - previous
|
||||||
|
// ================================================================
|
||||||
|
// Previous chirp had I=5000, Q=3000. New chirp: I=7000, Q=4000.
|
||||||
|
// Expected: I=2000, Q=1000.
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd7000, 16'sd4000);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
check(3, "T3.1: Second chirp: 64 outputs", cap_count == 64);
|
||||||
|
check(3, "T3.2: MTI I[0] = 7000-5000 = 2000", cap_i[0] == 16'sd2000);
|
||||||
|
check(3, "T3.3: MTI Q[0] = 4000-3000 = 1000", cap_q[0] == 16'sd1000);
|
||||||
|
check(3, "T3.4: MTI I[32] = 2000", cap_i[32] == 16'sd2000);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T4: Stationary target cancels to zero
|
||||||
|
// ================================================================
|
||||||
|
// Feed identical chirp as previous (7000, 4000). Diff = 0.
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd7000, 16'sd4000);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
all_zero = 1;
|
||||||
|
for (i = 0; i < cap_count; i = i + 1) begin
|
||||||
|
if (cap_i[i] != 0 || cap_q[i] != 0) all_zero = 0;
|
||||||
|
end
|
||||||
|
check(4, "T4: Stationary target cancels to zero", all_zero == 1);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T5: Moving target passes through
|
||||||
|
// ================================================================
|
||||||
|
// Previous was (7000, 4000). New chirp: some bins different, some same.
|
||||||
|
// Bin 10: I=10000 → diff=3000. Bin 30: I=7000 → diff=0. Rest same.
|
||||||
|
begin : t5_block
|
||||||
|
integer r;
|
||||||
|
cap_count = 0;
|
||||||
|
for (r = 0; r < NUM_BINS; r = r + 1) begin
|
||||||
|
if (r == 10)
|
||||||
|
feed_sample(r[5:0], 16'sd10000, 16'sd4000);
|
||||||
|
else if (r == 30)
|
||||||
|
feed_sample(r[5:0], 16'sd7000, 16'sd4000);
|
||||||
|
else
|
||||||
|
feed_sample(r[5:0], 16'sd7000, 16'sd4000);
|
||||||
|
end
|
||||||
|
// Wait for outputs
|
||||||
|
repeat (10) @(posedge clk);
|
||||||
|
end
|
||||||
|
|
||||||
|
// Re-capture: since we didn't fork/join, manually count
|
||||||
|
// Actually let me re-do this properly
|
||||||
|
do_reset;
|
||||||
|
mti_enable = 1'b1;
|
||||||
|
|
||||||
|
// Chirp 1 (stored, output muted)
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd7000, 16'sd4000);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
// Chirp 2: bin 10 has moving target
|
||||||
|
begin : t5_feed
|
||||||
|
integer r;
|
||||||
|
for (r = 0; r < NUM_BINS; r = r + 1) begin
|
||||||
|
if (r == 10)
|
||||||
|
feed_sample(r[5:0], 16'sd10000, 16'sd6000);
|
||||||
|
else
|
||||||
|
feed_sample(r[5:0], 16'sd7000, 16'sd4000);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Capture in parallel didn't work cleanly with named blocks, so just wait
|
||||||
|
repeat (5) @(posedge clk);
|
||||||
|
|
||||||
|
// Check: we need to capture during feed. Let me use a different approach.
|
||||||
|
// Since feed_sample takes 2 cycles and output comes 1 cycle after valid_in,
|
||||||
|
// outputs interleave with feeds. Let me just check DUT state.
|
||||||
|
// Actually the capture task expects outputs; the issue is fork/join with
|
||||||
|
// named blocks in iverilog. Let me restructure.
|
||||||
|
|
||||||
|
// Reset and redo T5 cleanly
|
||||||
|
do_reset;
|
||||||
|
mti_enable = 1'b1;
|
||||||
|
|
||||||
|
// Chirp 1: all constant
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd1000, 16'sd500);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
// Chirp 2: bin 20 has a moving target (I=5000 vs previous 1000)
|
||||||
|
cap_count = 0;
|
||||||
|
fork
|
||||||
|
begin : t5_feed2
|
||||||
|
integer r;
|
||||||
|
for (r = 0; r < NUM_BINS; r = r + 1) begin
|
||||||
|
if (r == 20)
|
||||||
|
feed_sample(r[5:0], 16'sd5000, 16'sd500);
|
||||||
|
else
|
||||||
|
feed_sample(r[5:0], 16'sd1000, 16'sd500);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
check(5, "T5.1: Moving target: 64 outputs", cap_count == 64);
|
||||||
|
check(5, "T5.2: Stationary bin 0: I=0", cap_i[0] == 16'sd0);
|
||||||
|
check(5, "T5.3: Moving bin 20: I=4000", cap_i[20] == 16'sd4000);
|
||||||
|
check(5, "T5.4: Moving bin 20: Q=0", cap_q[20] == 16'sd0);
|
||||||
|
check(5, "T5.5: Stationary bin 63: I=0", cap_i[63] == 16'sd0);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T6: Saturation
|
||||||
|
// ================================================================
|
||||||
|
do_reset;
|
||||||
|
mti_enable = 1'b1;
|
||||||
|
|
||||||
|
// Chirp 1: I = -32000
|
||||||
|
fork
|
||||||
|
feed_chirp_const(-16'sd32000, 16'sd0);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
// Chirp 2: I = +32000. Diff = 64000, saturates to +32767.
|
||||||
|
cap_count = 0;
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd32000, 16'sd0);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
check(6, "T6.1: Saturation: 64 outputs", cap_count == 64);
|
||||||
|
check(6, "T6.2: Saturated I = 32767", cap_i[0] == 16'sd32767);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T7: Enable toggle mid-stream
|
||||||
|
// ================================================================
|
||||||
|
do_reset;
|
||||||
|
mti_enable = 1'b0;
|
||||||
|
|
||||||
|
// Feed one chirp in pass-through
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd2000, 16'sd1000);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
check(7, "T7.1: Pass-through I=2000", cap_i[0] == 16'sd2000);
|
||||||
|
|
||||||
|
// Enable MTI
|
||||||
|
mti_enable = 1'b1;
|
||||||
|
|
||||||
|
// First MTI chirp should be muted
|
||||||
|
cap_count = 0;
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd3000, 16'sd1500);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
all_zero = 1;
|
||||||
|
for (i = 0; i < cap_count; i = i + 1) begin
|
||||||
|
if (cap_i[i] != 0 || cap_q[i] != 0) all_zero = 0;
|
||||||
|
end
|
||||||
|
check(7, "T7.2: After enable: first chirp muted", all_zero == 1);
|
||||||
|
|
||||||
|
// Second MTI chirp should subtract
|
||||||
|
cap_count = 0;
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd5000, 16'sd2500);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
check(7, "T7.3: After enable: second chirp I=2000", cap_i[0] == 16'sd2000);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T8: Reset during operation
|
||||||
|
// ================================================================
|
||||||
|
do_reset;
|
||||||
|
mti_enable = 1'b1;
|
||||||
|
|
||||||
|
feed_chirp_const(16'sd1000, 16'sd500);
|
||||||
|
repeat (5) @(posedge clk);
|
||||||
|
|
||||||
|
// Reset mid-operation
|
||||||
|
reset_n = 0;
|
||||||
|
repeat (5) @(posedge clk);
|
||||||
|
reset_n = 1;
|
||||||
|
repeat (2) @(posedge clk);
|
||||||
|
|
||||||
|
check(8, "T8.1: After reset: first_chirp=1", mti_first_chirp == 1);
|
||||||
|
check(8, "T8.2: After reset: has_previous=0", dut.has_previous == 0);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T9: range_bin_out tracks range_bin_in
|
||||||
|
// ================================================================
|
||||||
|
do_reset;
|
||||||
|
mti_enable = 1'b0;
|
||||||
|
|
||||||
|
cap_count = 0;
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd100, 16'sd50);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
all_match = 1;
|
||||||
|
for (i = 0; i < cap_count; i = i + 1) begin
|
||||||
|
if (cap_bin[i] != i[5:0]) all_match = 0;
|
||||||
|
end
|
||||||
|
check(9, "T9: range_bin_out matches range_bin_in for all 64 bins", all_match == 1);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T10: Three consecutive chirps
|
||||||
|
// ================================================================
|
||||||
|
do_reset;
|
||||||
|
mti_enable = 1'b1;
|
||||||
|
|
||||||
|
// Chirp 1 (muted)
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd1000, 16'sd0);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
// Chirp 2: I=2000, diff=1000
|
||||||
|
cap_count = 0;
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd2000, 16'sd0);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
check(10, "T10.1: Chirp 2: diff I=1000", cap_i[0] == 16'sd1000);
|
||||||
|
|
||||||
|
// Chirp 3: I=5000, diff=3000
|
||||||
|
cap_count = 0;
|
||||||
|
fork
|
||||||
|
feed_chirp_const(16'sd5000, 16'sd0);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
check(10, "T10.2: Chirp 3: diff I=3000", cap_i[0] == 16'sd3000);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// T11: Negative input values
|
||||||
|
// ================================================================
|
||||||
|
do_reset;
|
||||||
|
mti_enable = 1'b1;
|
||||||
|
|
||||||
|
// Chirp 1: I=-3000
|
||||||
|
fork
|
||||||
|
feed_chirp_const(-16'sd3000, -16'sd1000);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
// Chirp 2: I=-1000. Diff = -1000 - (-3000) = 2000.
|
||||||
|
cap_count = 0;
|
||||||
|
fork
|
||||||
|
feed_chirp_const(-16'sd1000, -16'sd500);
|
||||||
|
capture_chirp;
|
||||||
|
join
|
||||||
|
|
||||||
|
check(11, "T11.1: Negative inputs: diff I = 2000", cap_i[0] == 16'sd2000);
|
||||||
|
check(11, "T11.2: Negative inputs: diff Q = 500", cap_q[0] == 16'sd500);
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// SUMMARY
|
||||||
|
// ================================================================
|
||||||
|
$display("");
|
||||||
|
$display("============================================");
|
||||||
|
$display(" MTI Canceller Testbench Results");
|
||||||
|
$display("============================================");
|
||||||
|
$display(" PASS: %0d", pass_count);
|
||||||
|
$display(" FAIL: %0d", fail_count);
|
||||||
|
$display("============================================");
|
||||||
|
|
||||||
|
if (fail_count > 0)
|
||||||
|
$display("[FAIL] %0d test(s) failed", fail_count);
|
||||||
|
else
|
||||||
|
$display("[PASS] All %0d tests passed", pass_count);
|
||||||
|
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
#10_000_000;
|
||||||
|
$display("[FAIL] Global watchdog timeout");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
Reference in New Issue
Block a user