Files
PLFM_RADAR/9_Firmware/9_2_FPGA/latency_buffer_2159.v
T
Jason fcf3999e39 Fix CDC reset domain bug (P0), strengthen testbenches with 31 structural assertions
Split cdc_adc_to_processing reset_n into src_reset_n/dst_reset_n so source
and destination clock domains use correctly-synchronized resets. Previously
cdc_chirp_counter's destination-side sync chain (100MHz) was reset by
sys_reset_120m_n (120MHz domain), causing 30 CDC critical warnings.

RTL changes:
- cdc_modules.v: split reset port, source logic uses src_reset_n,
  destination sync chains + output logic use dst_reset_n
- radar_system_top.v: cdc_chirp_counter gets proper per-domain resets
- ddc_400m.v: CDC_FIR_i/q use reset_n_400m (src) and reset_n (dst)
- formal/fv_cdc_adc.v: updated wrapper for new port interface

Build 7 fixes (previously untouched):
- radar_transmitter.v: SPI level-shifter assigns, STM32 GPIO CDC sync
- latency_buffer_2159.v: BRAM read registration
- constraints: ft601 IOB -quiet fix
- tb_latency_buffer.v: updated for BRAM changes

Testbench hardening (tb_cdc_modules.v, +31 new assertions):
- A5-A7: split-domain reset tests (staggered deassertion, independent
  dst reset while src active — catches the P0 bug class)
- A8: port connectivity (no X/Z on outputs)
- B7: cdc_single_bit port connectivity
- C6: cdc_handshake reset recovery + port connectivity

Full regression: 13/13 test suites pass (257 total assertions).
2026-03-17 19:38:09 +02:00

129 lines
4.0 KiB
Verilog

`timescale 1ns / 1ps
// latency_buffer_2159_fixed.v
module latency_buffer_2159 #(
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