mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-09 06:57:15 +00:00
chore: delete dead latency_buffer; doc cleanup for two stale comments
latency_buffer.v has had zero non-tb instantiations since RX-B (2026-04-23)
replaced its hookup in radar_receiver_final with a 1-FF alignment register.
The module was being kept "for potential future use" — exactly the kind of
dead weight the codebase does not need. Deleted, along with all build /
test infrastructure that dragged it along:
- 9_Firmware/9_2_FPGA/latency_buffer.v
- 9_Firmware/9_2_FPGA/tb/tb_latency_buffer.v
- run_regression.sh: removed from RTL_FILES and RECEIVER_RTL
- scripts/200t/build_200t.tcl: removed from synthesis source list
- tb/tb_system_e2e.v: removed from header compile-string example
- tb/cosim/validate_mem_files.py: deleted test_latency_buffer() (~75 lines),
its call site, and the corresponding entry in the module docstring
Historical RX-B comments referencing latency_buffer in radar_receiver_final.v,
tb_rxb_fullchain_latency.v, and tb_rxb_latency_measure.v are kept — they
explain WHY the module was removed, which is still useful design archaeology.
Two doc-only housekeeping touches bundled in:
- plfm_chirp_controller.v: replaced two empty "CRITICAL FIX: Generate
valid signal" labels at LONG_CHIRP and SHORT_CHIRP with one shared
chirp_valid policy comment block above LONG_CHIRP that explains the
actual rationale (downstream FIFO underrun on trailing samples).
- v7/models.py: replaced the "range_resolution and velocity_resolution
should be calibrated" docstring (sounded like an open TODO but was a
documented placeholder) with a clear pointer to the GUI-C3 fix in
workers.py:RadarDataWorker so future readers know the live path
derives correct values from WaveformConfig.
FPGA quick regression unchanged: 28/29 (1 fail is the unrelated iverilog/
Xilinx-IP RX-NEW-3 gap). GUI suite 180/180. Ruff clean.
This commit is contained in:
@@ -1,131 +0,0 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
// latency_buffer.v — Parameterized BRAM-based latency/delay buffer
|
||||
// Renamed from latency_buffer_2159 to latency_buffer (module name was
|
||||
// inconsistent with the actual LATENCY=3187 parameter).
|
||||
module latency_buffer #(
|
||||
parameter DATA_WIDTH = 32,
|
||||
parameter LATENCY = 3187
|
||||
) (
|
||||
input wire clk,
|
||||
input wire reset_n,
|
||||
input wire [DATA_WIDTH-1:0] data_in,
|
||||
input wire valid_in,
|
||||
output wire [DATA_WIDTH-1:0] data_out,
|
||||
output wire valid_out
|
||||
);
|
||||
|
||||
// ========== FIXED PARAMETERS ==========
|
||||
localparam ADDR_WIDTH = 12; // Enough for 4096 entries (>2159)
|
||||
|
||||
// ========== FIXED LOGIC ==========
|
||||
(* ram_style = "block" *) reg [DATA_WIDTH-1:0] bram [0:4095];
|
||||
reg [ADDR_WIDTH-1:0] write_ptr;
|
||||
reg [ADDR_WIDTH-1:0] read_ptr;
|
||||
reg valid_out_reg;
|
||||
|
||||
// Delay counter to track when LATENCY cycles have passed
|
||||
reg [ADDR_WIDTH-1:0] delay_counter;
|
||||
reg buffer_has_data; // Flag when buffer has accumulated LATENCY samples
|
||||
|
||||
// ========== FIXED INITIALIZATION ==========
|
||||
integer k;
|
||||
initial begin
|
||||
for (k = 0; k < 4096; k = k + 1) begin
|
||||
bram[k] = {DATA_WIDTH{1'b0}};
|
||||
end
|
||||
write_ptr = 0;
|
||||
read_ptr = 0;
|
||||
valid_out_reg = 0;
|
||||
delay_counter = 0;
|
||||
buffer_has_data = 0;
|
||||
end
|
||||
|
||||
// ========== BRAM WRITE (synchronous only, no async reset) ==========
|
||||
// Xilinx Block RAMs do not support asynchronous resets.
|
||||
// Separating the BRAM write into its own always block avoids Synth 8-3391.
|
||||
// The initial block above handles power-on initialization for FPGA.
|
||||
always @(posedge clk) begin
|
||||
if (valid_in) begin
|
||||
bram[write_ptr] <= data_in;
|
||||
end
|
||||
end
|
||||
|
||||
// ========== CONTROL LOGIC (with async reset) ==========
|
||||
always @(posedge clk or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
write_ptr <= 0;
|
||||
read_ptr <= 0;
|
||||
valid_out_reg <= 0;
|
||||
delay_counter <= 0;
|
||||
buffer_has_data <= 0;
|
||||
end else begin
|
||||
// Default: no valid output
|
||||
valid_out_reg <= 0;
|
||||
|
||||
// ===== WRITE SIDE =====
|
||||
if (valid_in) begin
|
||||
// Increment write pointer (wrap at 4095)
|
||||
if (write_ptr == 4095) begin
|
||||
write_ptr <= 0;
|
||||
end else begin
|
||||
write_ptr <= write_ptr + 1;
|
||||
end
|
||||
|
||||
// Count how many samples we've written
|
||||
if (delay_counter < LATENCY) begin
|
||||
delay_counter <= delay_counter + 1;
|
||||
|
||||
// When we've written LATENCY samples, buffer is "primed"
|
||||
if (delay_counter == LATENCY - 1) begin
|
||||
buffer_has_data <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ===== READ SIDE =====
|
||||
// Only start reading after we have LATENCY samples in buffer
|
||||
if (buffer_has_data && valid_in) begin
|
||||
// Read pointer follows write pointer with LATENCY delay
|
||||
// Calculate: read_ptr = (write_ptr - LATENCY) mod 4096
|
||||
|
||||
// Handle wrap-around correctly
|
||||
if (write_ptr >= LATENCY) begin
|
||||
read_ptr <= write_ptr - LATENCY;
|
||||
end else begin
|
||||
// Wrap around: 4096 + write_ptr - LATENCY
|
||||
read_ptr <= 4096 + write_ptr - LATENCY;
|
||||
end
|
||||
|
||||
// Output is valid
|
||||
valid_out_reg <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ========== BRAM READ (synchronous — required for Block RAM inference) ==========
|
||||
// Xilinx Block RAMs physically register the read output. An async read
|
||||
// (assign data_out = bram[addr]) forces Vivado to use distributed LUTRAM
|
||||
// instead, wasting ~704 LUTs. Registering the read adds 1 cycle of latency,
|
||||
// compensated by the valid pipeline stage below.
|
||||
reg [DATA_WIDTH-1:0] data_out_reg;
|
||||
|
||||
always @(posedge clk) begin
|
||||
data_out_reg <= bram[read_ptr];
|
||||
end
|
||||
|
||||
// Pipeline valid_out_reg by 1 cycle to align with registered BRAM read
|
||||
reg valid_out_pipe;
|
||||
always @(posedge clk or negedge reset_n) begin
|
||||
if (!reset_n)
|
||||
valid_out_pipe <= 1'b0;
|
||||
else
|
||||
valid_out_pipe <= valid_out_reg;
|
||||
end
|
||||
|
||||
assign data_out = data_out_reg;
|
||||
assign valid_out = valid_out_pipe;
|
||||
|
||||
|
||||
|
||||
endmodule
|
||||
@@ -280,11 +280,16 @@ always @(posedge clk_120m or negedge reset_n) begin
|
||||
chirp_data <= 8'd128;
|
||||
end
|
||||
|
||||
// chirp_valid policy (LONG_CHIRP + SHORT_CHIRP states): assert
|
||||
// chirp_valid HIGH for the entire active-sample window of each
|
||||
// chirp (sample_counter < T?_SAMPLES) so the downstream DAC sees a
|
||||
// continuous data-valid pulse, then ride out the remaining state
|
||||
// duration on idle code 8'd128. Without the per-cycle assert,
|
||||
// downstream FIFOs underrun on the trailing samples of each chirp.
|
||||
LONG_CHIRP: begin
|
||||
rf_switch_ctrl <= 1'b1;
|
||||
{adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b1111;
|
||||
|
||||
// CRITICAL FIX: Generate valid signal
|
||||
|
||||
if (sample_counter < T1_SAMPLES) begin
|
||||
chirp_data <= long_chirp_rd_data;
|
||||
chirp_valid <= 1'b1; // Valid during entire chirp
|
||||
@@ -306,8 +311,8 @@ always @(posedge clk_120m or negedge reset_n) begin
|
||||
SHORT_CHIRP: begin
|
||||
rf_switch_ctrl <= 1'b1;
|
||||
{adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b1111;
|
||||
|
||||
// CRITICAL FIX: Generate valid signal for short chirp
|
||||
|
||||
/* see chirp_valid policy block above LONG_CHIRP */
|
||||
if (sample_counter < T2_SAMPLES) begin
|
||||
chirp_data <= short_chirp_lut[sample_counter];
|
||||
chirp_valid <= 1'b1; // Valid during entire chirp
|
||||
|
||||
@@ -62,7 +62,6 @@ PROD_RTL=(
|
||||
fir_lowpass.v
|
||||
ddc_input_interface.v
|
||||
chirp_memory_loader_param.v
|
||||
latency_buffer.v
|
||||
matched_filter_multi_segment.v
|
||||
matched_filter_processing_chain.v
|
||||
range_bin_decimator.v
|
||||
@@ -101,7 +100,7 @@ RECEIVER_RTL=(
|
||||
tb/ad9484_interface_400m_stub.v
|
||||
ddc_400m.v nco_400m_enhanced.v cic_decimator_4x_enhanced.v
|
||||
cdc_modules.v fir_lowpass.v ddc_input_interface.v
|
||||
chirp_memory_loader_param.v latency_buffer.v
|
||||
chirp_memory_loader_param.v
|
||||
matched_filter_multi_segment.v matched_filter_processing_chain.v
|
||||
range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v
|
||||
xfft_2048.v fft_engine_axi_bridge.v
|
||||
|
||||
@@ -64,7 +64,6 @@ set rtl_files [list \
|
||||
"${rtl_dir}/edge_detector.v" \
|
||||
"${rtl_dir}/fir_lowpass.v" \
|
||||
"${rtl_dir}/frequency_matched_filter.v" \
|
||||
"${rtl_dir}/latency_buffer.v" \
|
||||
"${rtl_dir}/matched_filter_multi_segment.v" \
|
||||
"${rtl_dir}/matched_filter_processing_chain.v" \
|
||||
"${rtl_dir}/nco_400m_enhanced.v" \
|
||||
|
||||
@@ -7,7 +7,6 @@ Checks:
|
||||
2. FFT twiddle files: bit-exact match against cos(2*pi*k/N) in Q15
|
||||
3. Long chirp .mem files: reverse-engineer parameters, check for chirp structure
|
||||
4. Short chirp .mem files: check length, value range, spectral content
|
||||
5. latency_buffer LATENCY=3187 parameter validation
|
||||
|
||||
Usage:
|
||||
python3 validate_mem_files.py
|
||||
@@ -378,81 +377,6 @@ def test_chirp_vs_model():
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# TEST 6: Latency Buffer LATENCY=3187 Validation
|
||||
# ============================================================================
|
||||
def test_latency_buffer():
|
||||
|
||||
# The latency buffer delays the reference chirp data to align with
|
||||
# the matched filter processing chain output.
|
||||
#
|
||||
# The total latency through the processing chain depends on the branch:
|
||||
#
|
||||
# SYNTHESIS branch (fft_engine.v):
|
||||
# - Load: 1024 cycles (input)
|
||||
# - Forward FFT: LOG2N=10 stages x N/2=512 butterflies x 5-cycle pipeline = variable
|
||||
# - Reference FFT: same
|
||||
# - Conjugate multiply: 1024 cycles (4-stage pipeline in frequency_matched_filter)
|
||||
# - Inverse FFT: same as forward
|
||||
# - Output: 1024 cycles
|
||||
# Total: roughly 3000-4000 cycles depending on pipeline fill
|
||||
#
|
||||
# The LATENCY=3187 value was likely determined empirically to align
|
||||
# the reference chirp arriving at the processing chain with the
|
||||
# correct time-domain position.
|
||||
#
|
||||
# Key constraint: LATENCY must be < 4096 (BRAM buffer size)
|
||||
LATENCY = 3187
|
||||
BRAM_SIZE = 4096
|
||||
|
||||
check(LATENCY < BRAM_SIZE,
|
||||
f"LATENCY ({LATENCY}) < BRAM size ({BRAM_SIZE})")
|
||||
|
||||
# The fft_engine processes in stages:
|
||||
# - LOAD: 1024 clocks (accepts input)
|
||||
# - Per butterfly stage: 512 butterflies x 5 pipeline stages = ~2560 clocks + overhead
|
||||
# Actually: 512 butterflies, each takes 5 cycles = 2560 per stage, 10 stages
|
||||
# Total compute: 10 * 2560 = 25600 clocks
|
||||
# But this is just for ONE FFT. The chain does 3 FFTs + multiply.
|
||||
#
|
||||
# For the SIMULATION branch, it's 1 clock per operation (behavioral).
|
||||
# LATENCY=3187 doesn't apply to simulation branch behavior —
|
||||
# it's the physical hardware pipeline latency.
|
||||
#
|
||||
# For synthesis: the latency_buffer feeds ref data to the chain via
|
||||
# chirp_memory_loader_param → latency_buffer → chain.
|
||||
# But wait — looking at radar_receiver_final.v:
|
||||
# - mem_request drives valid_in on the latency buffer
|
||||
# - The buffer delays {ref_i, ref_q} by LATENCY valid_in cycles
|
||||
# - The delayed output feeds long_chirp_real/imag → chain
|
||||
#
|
||||
# The purpose: the chain in the SYNTHESIS branch reads reference data
|
||||
# via the long_chirp_real/imag ports DURING ST_FWD_FFT (while collecting
|
||||
# input samples). The reference data needs to arrive LATENCY cycles
|
||||
# after the first mem_request, where LATENCY accounts for:
|
||||
# - The fft_engine pipeline latency from input to output
|
||||
# - Specifically, the chain processes: load 1024 → FFT → FFT → multiply → IFFT → output
|
||||
# The reference is consumed during the second FFT (ST_REF_BITREV/BUTTERFLY)
|
||||
# which starts after the first FFT completes.
|
||||
|
||||
# For now, validate that LATENCY is reasonable (between 1000 and 4095)
|
||||
check(1000 < LATENCY < 4095,
|
||||
f"LATENCY={LATENCY} in reasonable range [1000, 4095]")
|
||||
|
||||
# Check that the module name vs parameter is consistent
|
||||
# Module name was renamed from latency_buffer_2159 to latency_buffer
|
||||
# to match the actual parameterized LATENCY value. No warning needed.
|
||||
|
||||
# Validate address arithmetic won't overflow
|
||||
min_read_ptr = 4096 + 0 - LATENCY
|
||||
check(min_read_ptr >= 0 and min_read_ptr < 4096,
|
||||
f"Min read_ptr after wrap = {min_read_ptr} (valid: 0..4095)")
|
||||
|
||||
# The latency buffer uses valid_in gated reads, so it only counts
|
||||
# valid samples. The number of valid_in pulses between first write
|
||||
# and first read is LATENCY.
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# TEST 7: Cross-check chirp memory loader addressing
|
||||
# ============================================================================
|
||||
@@ -553,7 +477,6 @@ def main():
|
||||
test_long_chirp()
|
||||
test_short_chirp()
|
||||
test_chirp_vs_model()
|
||||
test_latency_buffer()
|
||||
test_memory_addressing()
|
||||
test_seg3_padding()
|
||||
|
||||
|
||||
@@ -1,398 +0,0 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module tb_latency_buffer;
|
||||
|
||||
// ── Parameters ─────────────────────────────────────────────
|
||||
localparam CLK_PERIOD = 10.0; // 100 MHz
|
||||
localparam DATA_WIDTH = 32;
|
||||
// Use small LATENCY for fast simulation; full 3187 is too slow for iverilog
|
||||
localparam LATENCY = 17;
|
||||
|
||||
// ── Signals ────────────────────────────────────────────────
|
||||
reg clk;
|
||||
reg reset_n;
|
||||
reg [DATA_WIDTH-1:0] data_in;
|
||||
reg valid_in;
|
||||
wire [DATA_WIDTH-1:0] data_out;
|
||||
wire valid_out;
|
||||
|
||||
// ── Test bookkeeping ───────────────────────────────────────
|
||||
integer pass_count;
|
||||
integer fail_count;
|
||||
integer test_num;
|
||||
integer i;
|
||||
|
||||
reg [DATA_WIDTH-1:0] expected;
|
||||
integer valid_output_count;
|
||||
integer first_valid_cycle;
|
||||
reg saw_valid;
|
||||
|
||||
// ── Clock ──────────────────────────────────────────────────
|
||||
always #(CLK_PERIOD/2) clk = ~clk;
|
||||
|
||||
// ── DUT ────────────────────────────────────────────────────
|
||||
latency_buffer #(
|
||||
.DATA_WIDTH(DATA_WIDTH),
|
||||
.LATENCY(LATENCY)
|
||||
) uut (
|
||||
.clk (clk),
|
||||
.reset_n (reset_n),
|
||||
.data_in (data_in),
|
||||
.valid_in (valid_in),
|
||||
.data_out (data_out),
|
||||
.valid_out(valid_out)
|
||||
);
|
||||
|
||||
// ── Check task ─────────────────────────────────────────────
|
||||
task check;
|
||||
input cond;
|
||||
input [511:0] label;
|
||||
begin
|
||||
test_num = test_num + 1;
|
||||
if (cond) begin
|
||||
$display("[PASS] Test %0d: %0s", test_num, label);
|
||||
pass_count = pass_count + 1;
|
||||
end else begin
|
||||
$display("[FAIL] Test %0d: %0s", test_num, label);
|
||||
fail_count = fail_count + 1;
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
// ── Helper: apply reset ────────────────────────────────────
|
||||
task do_reset;
|
||||
begin
|
||||
reset_n = 0;
|
||||
valid_in = 0;
|
||||
data_in = 0;
|
||||
repeat (4) @(posedge clk);
|
||||
reset_n = 1;
|
||||
@(posedge clk); #1;
|
||||
end
|
||||
endtask
|
||||
|
||||
// ── Stimulus ───────────────────────────────────────────────
|
||||
initial begin
|
||||
$dumpfile("tb_latency_buffer.vcd");
|
||||
$dumpvars(0, tb_latency_buffer);
|
||||
|
||||
clk = 0;
|
||||
reset_n = 0;
|
||||
data_in = 0;
|
||||
valid_in = 0;
|
||||
pass_count = 0;
|
||||
fail_count = 0;
|
||||
test_num = 0;
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 1: Reset behaviour
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 1: Reset Behaviour ---");
|
||||
repeat (4) @(posedge clk); #1;
|
||||
check(valid_out === 1'b0, "valid_out = 0 during reset");
|
||||
check(data_out === {DATA_WIDTH{1'b0}}, "data_out = 0 during reset");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 2: Priming phase — no output for LATENCY cycles
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 2: Priming Phase ---");
|
||||
do_reset;
|
||||
|
||||
// Feed samples 1, 2, 3, ... continuously
|
||||
saw_valid = 0;
|
||||
for (i = 0; i < LATENCY; i = i + 1) begin
|
||||
data_in = i + 1;
|
||||
valid_in = 1;
|
||||
@(posedge clk); #1;
|
||||
if (valid_out) saw_valid = 1;
|
||||
end
|
||||
check(!saw_valid, "No valid output during first LATENCY input samples");
|
||||
|
||||
// The LATENCY-th sample is being written THIS cycle.
|
||||
// The buffer_has_data flag is set when delay_counter == LATENCY-1,
|
||||
// which happens on the LATENCY-th valid_in pulse (i == LATENCY-1 above).
|
||||
// But valid_out only appears on the NEXT valid_in cycle because
|
||||
// buffer_has_data is registered.
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 3: Exact latency measurement
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 3: Exact Latency Measurement ---");
|
||||
do_reset;
|
||||
|
||||
// Feed a known sequence: sample N has value (N+1)
|
||||
// After priming, output[N] should equal input[N - LATENCY]
|
||||
valid_output_count = 0;
|
||||
first_valid_cycle = -1;
|
||||
|
||||
for (i = 0; i < LATENCY + 20; i = i + 1) begin
|
||||
data_in = i + 1;
|
||||
valid_in = 1;
|
||||
@(posedge clk); #1;
|
||||
if (valid_out) begin
|
||||
if (first_valid_cycle < 0) first_valid_cycle = i;
|
||||
valid_output_count = valid_output_count + 1;
|
||||
end
|
||||
end
|
||||
|
||||
$display(" First valid output at input sample #%0d (expected ~%0d)",
|
||||
first_valid_cycle, LATENCY + 1);
|
||||
// After LATENCY samples written, buffer_has_data goes high.
|
||||
// On the NEXT valid_in, valid_out_reg fires. Then valid_out_pipe
|
||||
// (the actual output) fires one cycle later due to BRAM read register.
|
||||
// So first valid is at sample LATENCY + 1.
|
||||
check(first_valid_cycle == LATENCY + 1,
|
||||
"First valid output appears at sample LATENCY+1 (BRAM read pipeline)");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 4: Data integrity (exact delay)
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 4: Data Integrity ---");
|
||||
do_reset;
|
||||
|
||||
// Feed samples: value = (i + 100)
|
||||
// After priming, each valid output should match data_in from LATENCY samples ago.
|
||||
//
|
||||
// The DUT calculates read_ptr from write_ptr, with BRAM read output
|
||||
// registered for Block RAM inference. This adds 1 cycle of read latency
|
||||
// beyond the LATENCY parameter. The valid_out pipeline stage tracks this.
|
||||
// The auto-calibration below handles any offset empirically.
|
||||
|
||||
begin : data_check_block
|
||||
reg all_match;
|
||||
reg [DATA_WIDTH-1:0] input_history [0:4095];
|
||||
integer out_idx;
|
||||
integer match_count;
|
||||
integer expected_idx;
|
||||
|
||||
all_match = 1;
|
||||
match_count = 0;
|
||||
out_idx = 0;
|
||||
|
||||
for (i = 0; i < LATENCY + 100; i = i + 1) begin
|
||||
data_in = i + 100;
|
||||
input_history[i] = i + 100;
|
||||
valid_in = 1;
|
||||
@(posedge clk); #1;
|
||||
if (valid_out) begin
|
||||
// Determine which input this output corresponds to.
|
||||
// The first valid output appears at input cycle LATENCY.
|
||||
// At that point, read_ptr was set from write_ptr = LATENCY
|
||||
// => read_ptr = LATENCY - LATENCY = 0 => bram[0] = input_history[0].
|
||||
// But read_ptr is registered, so it takes effect next cycle.
|
||||
// Actually, let's just check: output should be input_history[out_idx]
|
||||
// where out_idx starts from 0.
|
||||
expected = input_history[out_idx];
|
||||
if (data_out !== expected) begin
|
||||
// Try out_idx+1 (off-by-one from registered read_ptr)
|
||||
if (out_idx > 0 && data_out === input_history[out_idx - 1]) begin
|
||||
// off by one — adjust
|
||||
end else begin
|
||||
if (all_match && match_count == 0) begin
|
||||
// First output — calibrate
|
||||
// Find which index data_out matches
|
||||
begin : find_idx
|
||||
integer j;
|
||||
for (j = 0; j <= i; j = j + 1) begin
|
||||
if (input_history[j] === data_out) begin
|
||||
out_idx = j;
|
||||
disable find_idx;
|
||||
end
|
||||
end
|
||||
// No match found
|
||||
all_match = 0;
|
||||
$display(" [WARN] First output %0d does not match any input",
|
||||
data_out);
|
||||
end
|
||||
end else begin
|
||||
all_match = 0;
|
||||
$display(" [WARN] Mismatch at out#%0d: got %0d, exp %0d",
|
||||
match_count, data_out, expected);
|
||||
end
|
||||
end
|
||||
end
|
||||
match_count = match_count + 1;
|
||||
out_idx = out_idx + 1;
|
||||
end
|
||||
end
|
||||
|
||||
$display(" Verified %0d output samples", match_count);
|
||||
check(match_count > 0, "Produced output samples after priming");
|
||||
check(all_match, "All outputs match input delayed by LATENCY");
|
||||
end
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 5: Valid gating — no output when valid_in=0
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 5: Valid Gating ---");
|
||||
do_reset;
|
||||
|
||||
// Prime the buffer
|
||||
for (i = 0; i < LATENCY + 5; i = i + 1) begin
|
||||
data_in = i + 1;
|
||||
valid_in = 1;
|
||||
@(posedge clk); #1;
|
||||
end
|
||||
|
||||
// Now de-assert valid_in — after pipeline drains (1 cycle), no more outputs
|
||||
valid_in = 0;
|
||||
data_in = 32'hDEADBEEF;
|
||||
// Allow 1 cycle for the valid pipeline to drain
|
||||
@(posedge clk); #1;
|
||||
valid_output_count = 0;
|
||||
for (i = 0; i < 20; i = i + 1) begin
|
||||
@(posedge clk); #1;
|
||||
if (valid_out) valid_output_count = valid_output_count + 1;
|
||||
end
|
||||
check(valid_output_count == 0, "No output when valid_in deasserted (after pipeline drain)");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 6: Intermittent valid_in
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 6: Intermittent Valid ---");
|
||||
do_reset;
|
||||
|
||||
// Feed with valid_in toggling every other cycle
|
||||
valid_output_count = 0;
|
||||
begin : intermittent_block
|
||||
integer valid_in_count;
|
||||
valid_in_count = 0;
|
||||
|
||||
for (i = 0; i < (LATENCY + 30) * 2; i = i + 1) begin
|
||||
if (i[0] == 1'b0) begin
|
||||
data_in = valid_in_count + 200;
|
||||
valid_in = 1;
|
||||
valid_in_count = valid_in_count + 1;
|
||||
end else begin
|
||||
valid_in = 0;
|
||||
end
|
||||
@(posedge clk); #1;
|
||||
if (valid_out) valid_output_count = valid_output_count + 1;
|
||||
end
|
||||
end
|
||||
|
||||
$display(" Intermittent: %0d valid outputs", valid_output_count);
|
||||
check(valid_output_count > 0, "Outputs produced with intermittent valid_in");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 7: Pointer wrap-around
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 7: Pointer Wrap-Around ---");
|
||||
do_reset;
|
||||
|
||||
// Feed 4096 + LATENCY + 50 samples to force write_ptr wrap-around
|
||||
// (4096 is the BRAM depth)
|
||||
begin : wrap_block
|
||||
reg wrap_all_match;
|
||||
reg [DATA_WIDTH-1:0] wrap_history [0:8191];
|
||||
integer wrap_out_idx;
|
||||
integer wrap_match_count;
|
||||
integer total_samples;
|
||||
|
||||
total_samples = 4096 + LATENCY + 50;
|
||||
wrap_all_match = 1;
|
||||
wrap_match_count = 0;
|
||||
wrap_out_idx = 0;
|
||||
|
||||
for (i = 0; i < total_samples; i = i + 1) begin
|
||||
data_in = i + 500;
|
||||
wrap_history[i] = i + 500;
|
||||
valid_in = 1;
|
||||
@(posedge clk); #1;
|
||||
if (valid_out) begin
|
||||
if (wrap_match_count == 0) begin
|
||||
// Calibrate: find which index
|
||||
begin : find_wrap_idx
|
||||
integer j;
|
||||
for (j = 0; j <= i; j = j + 1) begin
|
||||
if (wrap_history[j] === data_out) begin
|
||||
wrap_out_idx = j;
|
||||
disable find_wrap_idx;
|
||||
end
|
||||
end
|
||||
end
|
||||
end else begin
|
||||
expected = wrap_history[wrap_out_idx];
|
||||
if (data_out !== expected) begin
|
||||
wrap_all_match = 0;
|
||||
if (wrap_match_count < 5) begin
|
||||
$display(" [WARN] Wrap mismatch out#%0d: got %0d, exp %0d",
|
||||
wrap_match_count, data_out, expected);
|
||||
end
|
||||
end
|
||||
end
|
||||
wrap_match_count = wrap_match_count + 1;
|
||||
wrap_out_idx = wrap_out_idx + 1;
|
||||
end
|
||||
end
|
||||
|
||||
$display(" Wrap-around: %0d outputs verified", wrap_match_count);
|
||||
check(wrap_match_count > 4096, "More than 4096 outputs (proves wrap-around)");
|
||||
check(wrap_all_match, "Data integrity across pointer wrap-around");
|
||||
end
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 8: Reset mid-operation
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 8: Reset Mid-Operation ---");
|
||||
|
||||
// Prime and get some outputs flowing
|
||||
do_reset;
|
||||
for (i = 0; i < LATENCY + 10; i = i + 1) begin
|
||||
data_in = i + 1;
|
||||
valid_in = 1;
|
||||
@(posedge clk); #1;
|
||||
end
|
||||
// Feed one more cycle to ensure pipeline has flushed
|
||||
data_in = 32'hFFFF;
|
||||
valid_in = 1;
|
||||
@(posedge clk); #1;
|
||||
// Should be producing outputs now
|
||||
check(valid_out === 1'b1, "Outputs flowing before mid-op reset");
|
||||
|
||||
// Apply reset mid-stream
|
||||
reset_n = 0;
|
||||
valid_in = 0;
|
||||
repeat (4) @(posedge clk); #1;
|
||||
check(valid_out === 1'b0, "valid_out = 0 after mid-operation reset");
|
||||
|
||||
// Release reset and verify buffer needs full re-priming
|
||||
reset_n = 1;
|
||||
@(posedge clk); #1;
|
||||
|
||||
saw_valid = 0;
|
||||
for (i = 0; i < LATENCY; i = i + 1) begin
|
||||
data_in = i + 1000;
|
||||
valid_in = 1;
|
||||
@(posedge clk); #1;
|
||||
if (valid_out) saw_valid = 1;
|
||||
end
|
||||
check(!saw_valid, "No output during re-priming after reset");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 9: Large LATENCY parameter test
|
||||
// ════════════════════════════════════════════════════════
|
||||
// (We use a second instance with LATENCY=100 to verify parameterization)
|
||||
// Skipped in this TB to keep simulation short — the wrap-around test
|
||||
// already validates 4000+ samples.
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Summary
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("");
|
||||
$display("========================================");
|
||||
$display(" LATENCY BUFFER TESTBENCH RESULTS");
|
||||
$display(" PASSED: %0d / %0d", pass_count, test_num);
|
||||
$display(" FAILED: %0d / %0d", fail_count, test_num);
|
||||
if (fail_count == 0)
|
||||
$display(" ** ALL TESTS PASSED **");
|
||||
else
|
||||
$display(" ** SOME TESTS FAILED **");
|
||||
$display("========================================");
|
||||
$display("");
|
||||
|
||||
#100;
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -32,7 +32,7 @@
|
||||
* radar_receiver_final.v tb/ad9484_interface_400m_stub.v \
|
||||
* ddc_400m.v nco_400m_enhanced.v cic_decimator_4x_enhanced.v \
|
||||
* cdc_modules.v fir_lowpass.v ddc_input_interface.v \
|
||||
* chirp_memory_loader_param.v latency_buffer.v \
|
||||
* chirp_memory_loader_param.v \
|
||||
* matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||
* range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \
|
||||
* usb_data_interface.v edge_detector.v radar_mode_controller.v
|
||||
|
||||
@@ -105,8 +105,11 @@ class RadarSettings:
|
||||
tab and Opcode enum in radar_protocol.py. This dataclass holds only
|
||||
host-side display/map settings and physical-unit conversion factors.
|
||||
|
||||
range_resolution and velocity_resolution should be calibrated to
|
||||
the actual waveform parameters.
|
||||
range_resolution and velocity_resolution below are placeholders. Live
|
||||
operation derives the actual values from WaveformConfig in
|
||||
workers.py:RadarDataWorker (see GUI-C3 fix); these literals are only
|
||||
consulted by code paths that have not yet been migrated, and should
|
||||
not be relied on for physics-accurate display.
|
||||
"""
|
||||
system_frequency: float = 10.5e9 # Hz (carrier, used for velocity calc)
|
||||
range_resolution: float = 6.0 # Meters per range bin (c/(2*Fs)*decim = 1.5*4)
|
||||
|
||||
Reference in New Issue
Block a user