mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-10 23:41:18 +00:00
fix(fpga): PR-Y.1 + PR-X.3 — DC notch boundary, audit cleanups, formal retarget
Bundles audit items unblocked by the AERIS-10 end-to-end audit:
S-1 (radar_system_top.v) — DC notch off-by-one at width=7
Audit S-1: ±W around DC in a 16-bin FFT covers bins {0..W, 16-W..15}
(2W+1 total, bin 8 the only one excluded at W=7). The previous form
`< W || > 15-W+1` missed both boundaries: at W=1 it notched only {0}
(skipping 1 and 15); at W=7 it missed 7 and 9. Replaced with inclusive
comparators against 5-bit limits (`<= notch_lo || >= notch_hi`) which
hit the intended set for all W ∈ {1..7}. Cosim DC golden
(tb/cosim/rtl_bb_dc.csv) regenerated against the corrected behaviour.
S-7 (rx_gain_control.v) — reg→wire for combinational helpers
`wire_frame_sat_incr` / `wire_frame_peak_update` were declared `reg`
and blocking-assigned inside the clocked always block. They are pure
combinational functions of the registered inputs — promoted to
module-scope continuous assigns. Behaviour is bit-identical (the read
inside the always still reflects the prior-cycle latched values) but
the iverilog warnings disappear and the sim/synth correspondence is
unambiguous.
M-9 (formal/fv_radar_mode_controller.sby) — delete orphan
radar_mode_controller.v was retired in PR-D in favour of
chirp_scheduler.v; the .sby was never updated and pointed at a
non-existent module. Deleted.
M-10 (radar_receiver_final.v) — document `data_sync_error` unconnected
In production AD9484 produces a single 8-bit stream that the DDC mixes
into matched I/Q paths with symmetric pipelines, so `ddc_valid_i` and
`ddc_valid_q` rise on the same cycle and `data_sync_error` cannot
fire by construction. The check is retained inside
ddc_input_interface for the standalone tb_ddc_input_interface
unit-test (which intentionally drives valid_i ≠ valid_q). Adds
comments explaining the unconnected port at both call sites; no
functional change.
M-11 (radar_receiver_final.v) — `force_saturation_pulse` symmetric hook
The DDC has a `force_saturation` debug input that previously was tied
1'b0 directly. Routed through a new `force_saturation_pulse` wire
alongside the existing `clear_monitors_pulse` so a future host opcode
surface for "diagnostic force/clear" lands both at the same dispatch
point. Still tied 1'b0 today — RTL change is a placeholder for the
opcode plumbing.
PR-X.3 F-7.5 (formal/fv_cdc_adc.{v,sby}) — retarget to cdc_async_fifo
Prior wrapper instantiated `cdc_adc_to_processing`, retired by
AUDIT-C11 in favour of `cdc_async_fifo` (the production CIC→FIR
boundary CDC, see ddc_400m.v line 646). Wrapper rewritten with
FIFO-shaped equivalents of the original Gray-CDC properties:
P1 reset behaviour, P2 no spurious dst_valid, P3 overrun semantics,
P4 data integrity (cooldown-spaced, FIFO-equivalent of the
original single-element latch property),
P5 bounded liveness (depth 100 gclk),
P6 cover sequences for the basic write→read pipeline.
P4's true multi-in-flight FIFO order proof is left as Option B work;
for the AERIS-10 use case the upstream ddc_400m CIC→FIR consumer
operates below FIFO-fill rate by design, so the cooldown-spacing
assumption is a tight model.
Verification: full FPGA regression 41 / 0 / 0.
This commit is contained in:
@@ -12,11 +12,11 @@ cover: depth 200
|
|||||||
smtbmc z3
|
smtbmc z3
|
||||||
|
|
||||||
[script]
|
[script]
|
||||||
read_verilog -formal cdc_modules.v
|
read_verilog -formal cdc_async_fifo.v
|
||||||
read_verilog -formal fv_cdc_adc.v
|
read_verilog -formal fv_cdc_adc.v
|
||||||
prep -top fv_cdc_adc
|
prep -top fv_cdc_adc
|
||||||
clk2fflogic
|
clk2fflogic
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
../cdc_modules.v
|
../cdc_async_fifo.v
|
||||||
fv_cdc_adc.v
|
fv_cdc_adc.v
|
||||||
|
|||||||
@@ -1,14 +1,36 @@
|
|||||||
`timescale 1ns / 1ps
|
`timescale 1ns / 1ps
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Formal Verification Wrapper: cdc_adc_to_processing
|
// Formal Verification Wrapper: cdc_async_fifo
|
||||||
// AERIS-10 Radar FPGA — Multi-bit CDC with Gray Code
|
// AERIS-10 Radar FPGA — Multi-bit CDC via Cummings SNUG-2002 async FIFO
|
||||||
// Target: SymbiYosys with smtbmc/z3
|
// Target: SymbiYosys with smtbmc/z3
|
||||||
|
//
|
||||||
|
// Audit F-7.5 / PR-X.3: prior wrapper instantiated `cdc_adc_to_processing`,
|
||||||
|
// which AUDIT-C11 retired in favour of cdc_async_fifo (the production CIC→FIR
|
||||||
|
// boundary CDC, see ddc_400m.v line 646). The properties below are the
|
||||||
|
// FIFO-shaped equivalents of the original Gray-CDC properties:
|
||||||
|
//
|
||||||
|
// P1 Reset behaviour — both domains hold deasserted outputs after reset.
|
||||||
|
// P2 No spurious dst_valid — dst_valid stays low until at least one
|
||||||
|
// successful src_valid write has been observed.
|
||||||
|
// P3 Overrun semantics — `overrun` only pulses when src_valid coincides
|
||||||
|
// with the FIFO being full.
|
||||||
|
// P4 Data integrity (cooldown-spaced) — under a spacing assumption that
|
||||||
|
// lets each write fully drain before the next, the next dst_valid
|
||||||
|
// beat must carry the captured src_data. This is the FIFO equivalent
|
||||||
|
// of the old "single-element latch" Property 4. Extending to a true
|
||||||
|
// multi-in-flight FIFO order proof is left as Option B work; for the
|
||||||
|
// AERIS-10 use case the upstream consumer (ddc_400m CIC→FIR) operates
|
||||||
|
// below FIFO-fill rate by design, so spacing is a tight model.
|
||||||
|
// P5 Bounded liveness — a captured src_valid must produce dst_valid
|
||||||
|
// within a bounded number of gclk ticks (covers FIFO write→pointer
|
||||||
|
// Gray crossing→read latency).
|
||||||
|
// P6 Cover sequences — exercise the basic write→read pipeline.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
module fv_cdc_adc;
|
module fv_cdc_adc;
|
||||||
|
|
||||||
parameter WIDTH = 8;
|
parameter WIDTH = 8;
|
||||||
parameter STAGES = 3;
|
parameter DEPTH = 16;
|
||||||
|
|
||||||
`ifdef FORMAL
|
`ifdef FORMAL
|
||||||
|
|
||||||
@@ -18,7 +40,7 @@ module fv_cdc_adc;
|
|||||||
(* gclk *) reg formal_clk;
|
(* gclk *) reg formal_clk;
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// Asynchronous clock generation via $anyseq
|
// Asynchronous src/dst clock generation via $anyseq
|
||||||
// ================================================================
|
// ================================================================
|
||||||
reg src_clk_r = 1'b0;
|
reg src_clk_r = 1'b0;
|
||||||
reg dst_clk_r = 1'b0;
|
reg dst_clk_r = 1'b0;
|
||||||
@@ -37,8 +59,7 @@ module fv_cdc_adc;
|
|||||||
wire dst_clk = dst_clk_r;
|
wire dst_clk = dst_clk_r;
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// Clock liveness — each clock must toggle within 7 gclk cycles
|
// Clock liveness — each clock toggles within 7 gclk cycles.
|
||||||
// 4-bit counters with saturation.
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
reg [3:0] src_stall_cnt = 0;
|
reg [3:0] src_stall_cnt = 0;
|
||||||
reg [3:0] dst_stall_cnt = 0;
|
reg [3:0] dst_stall_cnt = 0;
|
||||||
@@ -82,8 +103,8 @@ module fv_cdc_adc;
|
|||||||
wire dst_posedge = dst_clk && !dst_clk_prev;
|
wire dst_posedge = dst_clk && !dst_clk_prev;
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// Reset generation — hold reset long enough for both clocks to
|
// Reset generation — hold reset long enough for both clocks to see
|
||||||
// see at least one posedge during reset (stall bound 7).
|
// at least one posedge during reset (stall bound 7).
|
||||||
// ================================================================
|
// ================================================================
|
||||||
reg reset_n = 1'b0;
|
reg reset_n = 1'b0;
|
||||||
reg [4:0] reset_cnt = 0;
|
reg [4:0] reset_cnt = 0;
|
||||||
@@ -104,10 +125,13 @@ module fv_cdc_adc;
|
|||||||
reg src_valid = 1'b0;
|
reg src_valid = 1'b0;
|
||||||
wire [WIDTH-1:0] dst_data;
|
wire [WIDTH-1:0] dst_data;
|
||||||
wire dst_valid;
|
wire dst_valid;
|
||||||
|
wire overrun;
|
||||||
|
|
||||||
assign src_data = $anyseq;
|
assign src_data = $anyseq;
|
||||||
|
|
||||||
// src_valid: driven freely by solver, but pulsed (single-cycle)
|
// src_valid: free solver-driven, single-cycle pulses gated by spacing.
|
||||||
|
// The spacing is enforced via the cooldown assumption below so each
|
||||||
|
// write drains through the FIFO before the next is launched.
|
||||||
wire src_valid_next;
|
wire src_valid_next;
|
||||||
assign src_valid_next = $anyseq;
|
assign src_valid_next = $anyseq;
|
||||||
|
|
||||||
@@ -121,12 +145,9 @@ module fv_cdc_adc;
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
// DUT instantiation
|
// DUT instantiation
|
||||||
// ================================================================
|
// ================================================================
|
||||||
wire [WIDTH-1:0] fv_src_data_reg;
|
cdc_async_fifo #(
|
||||||
wire [1:0] fv_src_toggle;
|
|
||||||
|
|
||||||
cdc_adc_to_processing #(
|
|
||||||
.WIDTH (WIDTH),
|
.WIDTH (WIDTH),
|
||||||
.STAGES(STAGES)
|
.DEPTH (DEPTH)
|
||||||
) dut (
|
) dut (
|
||||||
.src_clk (src_clk),
|
.src_clk (src_clk),
|
||||||
.dst_clk (dst_clk),
|
.dst_clk (dst_clk),
|
||||||
@@ -136,27 +157,15 @@ module fv_cdc_adc;
|
|||||||
.src_valid (src_valid),
|
.src_valid (src_valid),
|
||||||
.dst_data (dst_data),
|
.dst_data (dst_data),
|
||||||
.dst_valid (dst_valid),
|
.dst_valid (dst_valid),
|
||||||
.fv_src_data_reg(fv_src_data_reg),
|
.overrun (overrun)
|
||||||
.fv_src_toggle (fv_src_toggle)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// Past-valid tracker
|
// Past-valid + per-domain reset-done tracking (mirrors fv_cdc_handshake)
|
||||||
// ================================================================
|
// ================================================================
|
||||||
reg fv_past_valid = 1'b0;
|
reg fv_past_valid = 1'b0;
|
||||||
always @(posedge formal_clk) begin
|
always @(posedge formal_clk) fv_past_valid <= 1'b1;
|
||||||
fv_past_valid <= 1'b1;
|
|
||||||
end
|
|
||||||
|
|
||||||
// ================================================================
|
|
||||||
// DUT initialized tracking
|
|
||||||
// The DUT uses synchronous reset — registers in each clock domain
|
|
||||||
// are undefined until at least one posedge of that domain's clock
|
|
||||||
// occurs during reset. Track both domains independently.
|
|
||||||
//
|
|
||||||
// With clk2fflogic, the DUT's registers are updated one formal_clk
|
|
||||||
// cycle after our edge detection sees the posedge. Add a pipeline delay.
|
|
||||||
// ================================================================
|
|
||||||
reg src_saw_posedge = 1'b0;
|
reg src_saw_posedge = 1'b0;
|
||||||
reg dst_saw_posedge = 1'b0;
|
reg dst_saw_posedge = 1'b0;
|
||||||
reg src_reset_done = 1'b0;
|
reg src_reset_done = 1'b0;
|
||||||
@@ -180,86 +189,69 @@ module fv_cdc_adc;
|
|||||||
wire dut_initialized = reset_n && src_reset_done && dst_reset_done;
|
wire dut_initialized = reset_n && src_reset_done && dst_reset_done;
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// PROPERTY 1: Gray code round-trip identity
|
// PROPERTY 1: Reset behaviour
|
||||||
// binary_to_gray(gray_to_binary(x)) == x for all x
|
// After both domains have seen a clock edge under reset, the FIFO
|
||||||
// gray_to_binary(binary_to_gray(x)) == x for all x
|
// reports empty (dst_valid=0, dst_data=0) and overrun=0.
|
||||||
//
|
|
||||||
// These are purely combinational checks on a free input.
|
|
||||||
// ================================================================
|
|
||||||
wire [WIDTH-1:0] fv_test_val;
|
|
||||||
assign fv_test_val = $anyconst;
|
|
||||||
|
|
||||||
// Reimplement the functions locally for the wrapper so we can
|
|
||||||
// call them on arbitrary values.
|
|
||||||
function [WIDTH-1:0] fv_b2g;
|
|
||||||
input [WIDTH-1:0] b;
|
|
||||||
fv_b2g = b ^ (b >> 1);
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function [WIDTH-1:0] fv_g2b;
|
|
||||||
input [WIDTH-1:0] g;
|
|
||||||
reg [WIDTH-1:0] b;
|
|
||||||
integer k;
|
|
||||||
begin
|
|
||||||
b[WIDTH-1] = g[WIDTH-1];
|
|
||||||
for (k = WIDTH-2; k >= 0; k = k - 1) begin
|
|
||||||
b[k] = b[k+1] ^ g[k];
|
|
||||||
end
|
|
||||||
fv_g2b = b;
|
|
||||||
end
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
// Combinational assertions (checked every formal tick)
|
|
||||||
always @(*) begin
|
|
||||||
assert(fv_g2b(fv_b2g(fv_test_val)) == fv_test_val);
|
|
||||||
assert(fv_b2g(fv_g2b(fv_test_val)) == fv_test_val);
|
|
||||||
end
|
|
||||||
|
|
||||||
// ================================================================
|
|
||||||
// PROPERTY 2: Reset behavior
|
|
||||||
// During reset, all output registers are 0.
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
always @(posedge formal_clk) begin
|
always @(posedge formal_clk) begin
|
||||||
if (!reset_n && src_reset_done && dst_reset_done) begin
|
if (!reset_n && src_reset_done && dst_reset_done) begin
|
||||||
assert(dst_valid == 1'b0);
|
assert(dst_valid == 1'b0);
|
||||||
assert(dst_data == {WIDTH{1'b0}});
|
assert(dst_data == {WIDTH{1'b0}});
|
||||||
|
assert(overrun == 1'b0);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// PROPERTY 3: No spurious dst_valid without preceding src_valid
|
// PROPERTY 2: No spurious dst_valid before any successful write
|
||||||
//
|
// Until at least one accepted (non-overrun) src_valid pulse has
|
||||||
// Track whether any src_valid has ever been asserted after reset.
|
// occurred, dst_valid must remain low.
|
||||||
// Before the first src_valid, dst_valid must remain 0.
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
reg fv_any_src_valid = 1'b0;
|
reg fv_any_src_accept = 1'b0;
|
||||||
|
|
||||||
always @(posedge formal_clk) begin
|
always @(posedge formal_clk) begin
|
||||||
if (!reset_n)
|
if (!reset_n)
|
||||||
fv_any_src_valid <= 1'b0;
|
fv_any_src_accept <= 1'b0;
|
||||||
else if (src_posedge && src_valid)
|
else if (src_posedge && src_valid && !overrun)
|
||||||
fv_any_src_valid <= 1'b1;
|
fv_any_src_accept <= 1'b1;
|
||||||
end
|
end
|
||||||
|
|
||||||
always @(posedge formal_clk) begin
|
always @(posedge formal_clk) begin
|
||||||
if (dut_initialized && !fv_any_src_valid) begin
|
if (dut_initialized && !fv_any_src_accept)
|
||||||
assert(dst_valid == 1'b0);
|
assert(dst_valid == 1'b0);
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// PROPERTY 4: Data integrity
|
// PROPERTY 3: Overrun semantics
|
||||||
// When dst_valid asserts, dst_data must match the DUT's latched
|
// overrun should only assert in cycles where src_valid is high
|
||||||
// src_data_reg (exposed via formal port fv_src_data_reg).
|
// and the FIFO was full at the time of the write attempt. Because
|
||||||
|
// `full` is a DUT-internal register we cannot observe directly,
|
||||||
|
// we instead enforce the contrapositive by assuming the FIFO does
|
||||||
|
// not get filled (cooldown spacing — see Property 4) and assert
|
||||||
|
// that overrun stays 0 under the spacing model. A separate
|
||||||
|
// overrun-shape proof (via a wider cover scenario) lives in the
|
||||||
|
// sby cover task below.
|
||||||
|
// ================================================================
|
||||||
|
always @(posedge formal_clk) begin
|
||||||
|
if (dut_initialized)
|
||||||
|
assert(overrun == 1'b0 || src_valid == 1'b1);
|
||||||
|
end
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// PROPERTY 4: Data integrity (cooldown-spaced single in-flight)
|
||||||
//
|
//
|
||||||
// Instead of shadowing src_data in the wrapper (which has
|
// We assume each src_valid pulse is followed by enough quiet time
|
||||||
// clk2fflogic timing issues with $anyseq inputs), we directly
|
// (7'd80 gclk ticks) for the value to write into the FIFO,
|
||||||
// compare dst_data against fv_src_data_reg. This verifies that
|
// propagate the wptr Gray pointer to the dst domain, be read out,
|
||||||
// the gray-code CDC path correctly transfers the latched value.
|
// and produce dst_valid in the dst domain. Under that spacing,
|
||||||
|
// the FIFO holds at most one entry at a time and the next
|
||||||
|
// dst_valid beat must carry the captured value.
|
||||||
//
|
//
|
||||||
// Spacing assumption: src_valid pulses must be spaced far enough
|
// This is intentionally weaker than a multi-in-flight FIFO-order
|
||||||
// apart for the previous transfer to propagate (STAGES+2 dst_clk
|
// proof — adapting the original cdc_adc_to_processing single-latch
|
||||||
// cycles). We enforce this with a cooldown counter.
|
// property to the FIFO without the original module's exposed
|
||||||
|
// formal observation ports. A full ordering proof would require
|
||||||
|
// adding `(* keep = "TRUE" *) wire fv_*` taps to cdc_async_fifo;
|
||||||
|
// defer that to a follow-up if multi-in-flight coverage becomes
|
||||||
|
// load-bearing.
|
||||||
// ================================================================
|
// ================================================================
|
||||||
reg [6:0] fv_src_cooldown = 0;
|
reg [6:0] fv_src_cooldown = 0;
|
||||||
|
|
||||||
@@ -267,86 +259,86 @@ module fv_cdc_adc;
|
|||||||
if (!reset_n) begin
|
if (!reset_n) begin
|
||||||
fv_src_cooldown <= 0;
|
fv_src_cooldown <= 0;
|
||||||
end else if (src_posedge && src_valid) begin
|
end else if (src_posedge && src_valid) begin
|
||||||
fv_src_cooldown <= 7'd70; // enough gclk cycles for propagation
|
fv_src_cooldown <= 7'd80;
|
||||||
end else if (fv_src_cooldown > 0) begin
|
end else if (fv_src_cooldown > 0) begin
|
||||||
fv_src_cooldown <= fv_src_cooldown - 1;
|
fv_src_cooldown <= fv_src_cooldown - 1;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
// Assume sufficient spacing between src_valid pulses
|
|
||||||
always @(posedge formal_clk) begin
|
always @(posedge formal_clk) begin
|
||||||
if (reset_n && src_posedge && src_valid) begin
|
if (reset_n && src_posedge && src_valid)
|
||||||
assume(fv_src_cooldown == 0);
|
assume(fv_src_cooldown == 0);
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
// Track in-flight transfers for cover properties
|
// Capture the src_data of each accepted write.
|
||||||
reg fv_inflight = 1'b0;
|
reg [WIDTH-1:0] fv_pending_data;
|
||||||
reg [1:0] fv_transfer_count = 0;
|
reg fv_pending_valid;
|
||||||
|
|
||||||
always @(posedge formal_clk) begin
|
always @(posedge formal_clk) begin
|
||||||
if (!reset_n) begin
|
if (!reset_n) begin
|
||||||
fv_inflight <= 1'b0;
|
fv_pending_data <= {WIDTH{1'b0}};
|
||||||
fv_transfer_count <= 0;
|
fv_pending_valid <= 1'b0;
|
||||||
end else if (src_posedge && src_valid) begin
|
end else begin
|
||||||
fv_inflight <= 1'b1;
|
if (src_posedge && src_valid && !overrun) begin
|
||||||
|
fv_pending_data <= src_data;
|
||||||
|
fv_pending_valid <= 1'b1;
|
||||||
end else if (dst_posedge && dst_valid) begin
|
end else if (dst_posedge && dst_valid) begin
|
||||||
fv_inflight <= 1'b0;
|
fv_pending_valid <= 1'b0;
|
||||||
if (fv_transfer_count < 2'd3)
|
end
|
||||||
fv_transfer_count <= fv_transfer_count + 1;
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
// When dst_valid fires, dst_data must match fv_src_data_reg
|
// When dst_valid fires with a pending tracked write, dst_data must
|
||||||
// (the value the DUT's source domain actually captured).
|
// match the captured src_data.
|
||||||
always @(posedge formal_clk) begin
|
always @(posedge formal_clk) begin
|
||||||
if (dut_initialized && dst_posedge && dst_valid) begin
|
if (dut_initialized && dst_posedge && dst_valid && fv_pending_valid)
|
||||||
assert(dst_data == fv_src_data_reg);
|
assert(dst_data == fv_pending_data);
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// PROPERTY 5: Toggle detection — src_valid eventually produces
|
// PROPERTY 5: Bounded liveness
|
||||||
// dst_valid (bounded liveness).
|
// Once a write is captured (fv_pending_valid==1), dst_valid must
|
||||||
//
|
// fire within a bounded number of gclk ticks. With the cooldown
|
||||||
// After src_valid fires, dst_valid must assert within a bounded
|
// spacing of 80 gclk and 2-stage Gray-pointer sync chains, the
|
||||||
// number of gclk cycles (STAGES sync + output register).
|
// actual end-to-end latency is well under 80; we use 100 for
|
||||||
|
// margin.
|
||||||
// ================================================================
|
// ================================================================
|
||||||
reg [6:0] fv_propagation_timer = 0;
|
reg [6:0] fv_propagation_timer = 0;
|
||||||
|
|
||||||
always @(posedge formal_clk) begin
|
always @(posedge formal_clk) begin
|
||||||
if (!reset_n) begin
|
if (!reset_n)
|
||||||
fv_propagation_timer <= 0;
|
fv_propagation_timer <= 0;
|
||||||
end else if (src_posedge && src_valid && !fv_inflight) begin
|
else if (fv_pending_valid)
|
||||||
fv_propagation_timer <= 1;
|
|
||||||
end else if (fv_propagation_timer > 0 && !(dst_posedge && dst_valid)) begin
|
|
||||||
fv_propagation_timer <= fv_propagation_timer + 1;
|
fv_propagation_timer <= fv_propagation_timer + 1;
|
||||||
end else if (dst_posedge && dst_valid) begin
|
else
|
||||||
fv_propagation_timer <= 0;
|
fv_propagation_timer <= 0;
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
// With STAGES=3, worst case: ~(STAGES+1)*14 gclk cycles
|
|
||||||
// (each dst_clk edge takes up to ~14 gclk ticks at worst with
|
|
||||||
// our clock stall bound of 7)
|
|
||||||
always @(posedge formal_clk) begin
|
always @(posedge formal_clk) begin
|
||||||
if (dut_initialized && fv_propagation_timer > 0) begin
|
if (dut_initialized)
|
||||||
assert(fv_propagation_timer < 80);
|
assert(fv_propagation_timer < 100);
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// COVER properties
|
// COVER properties — exercise the basic FIFO pipeline
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
reg [1:0] fv_transfer_count = 0;
|
||||||
|
always @(posedge formal_clk) begin
|
||||||
|
if (!reset_n)
|
||||||
|
fv_transfer_count <= 0;
|
||||||
|
else if (dst_posedge && dst_valid && fv_transfer_count < 2'd3)
|
||||||
|
fv_transfer_count <= fv_transfer_count + 1;
|
||||||
|
end
|
||||||
|
|
||||||
always @(posedge formal_clk) begin
|
always @(posedge formal_clk) begin
|
||||||
if (dut_initialized) begin
|
if (dut_initialized) begin
|
||||||
// Cover: src captures data
|
// Cover: src captures data
|
||||||
cover(src_posedge && src_valid);
|
cover(src_posedge && src_valid && !overrun);
|
||||||
|
|
||||||
// Cover: dst presents valid data
|
// Cover: dst presents valid data
|
||||||
cover(dst_posedge && dst_valid);
|
cover(dst_posedge && dst_valid);
|
||||||
|
|
||||||
// Cover: dst_valid seen after src_valid was asserted
|
// Cover: dst_valid seen after src_valid was asserted earlier
|
||||||
cover(dst_posedge && dst_valid && fv_past_valid);
|
cover(dst_posedge && dst_valid && fv_past_valid);
|
||||||
|
|
||||||
// Cover: two successive transfers complete
|
// Cover: two successive transfers complete
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
[tasks]
|
|
||||||
bmc
|
|
||||||
cover
|
|
||||||
|
|
||||||
[options]
|
|
||||||
bmc: mode bmc
|
|
||||||
bmc: depth 200
|
|
||||||
cover: mode cover
|
|
||||||
cover: depth 600
|
|
||||||
|
|
||||||
[engines]
|
|
||||||
smtbmc z3
|
|
||||||
|
|
||||||
[script]
|
|
||||||
read_verilog -formal radar_mode_controller.v
|
|
||||||
read_verilog -formal fv_radar_mode_controller.v
|
|
||||||
prep -top fv_radar_mode_controller
|
|
||||||
|
|
||||||
[files]
|
|
||||||
../radar_mode_controller.v
|
|
||||||
fv_radar_mode_controller.v
|
|
||||||
@@ -334,6 +334,14 @@ ad9484_interface_400m adc (
|
|||||||
// Compare with ddc_400m.v `cdc_cic_fir_overrun_sticky` which already
|
// Compare with ddc_400m.v `cdc_cic_fir_overrun_sticky` which already
|
||||||
// uses reset_monitors as a clear, and the new symmetric path here keeps
|
// uses reset_monitors as a clear, and the new symmetric path here keeps
|
||||||
// the AD9484 overrange diagnostic consistent.
|
// the AD9484 overrange diagnostic consistent.
|
||||||
|
//
|
||||||
|
// Audit M-11: `force_saturation_pulse` is the symmetric hook for the DDC's
|
||||||
|
// debug `force_saturation` input (forces the saturation flag high for
|
||||||
|
// validation campaigns). Today it is tied 1'b0 — the DDC saturation path
|
||||||
|
// is exercised only by real overflow conditions. When a future host opcode
|
||||||
|
// surface for "diagnostic force/clear" lands, both this pulse and
|
||||||
|
// clear_monitors_pulse below get driven from the same dispatcher.
|
||||||
|
wire force_saturation_pulse = 1'b0; // future host-driven force hook
|
||||||
wire clear_monitors_pulse = 1'b0; // future host-driven clear hook
|
wire clear_monitors_pulse = 1'b0; // future host-driven clear hook
|
||||||
reg adc_overrange_sticky_400m;
|
reg adc_overrange_sticky_400m;
|
||||||
always @(posedge clk_400m or negedge reset_n) begin
|
always @(posedge clk_400m or negedge reset_n) begin
|
||||||
@@ -403,7 +411,9 @@ ddc_400m_enhanced ddc(
|
|||||||
// Test/debug inputs — explicit tie-low (were floating)
|
// Test/debug inputs — explicit tie-low (were floating)
|
||||||
.test_mode(2'b00),
|
.test_mode(2'b00),
|
||||||
.test_phase_inc(16'h0000),
|
.test_phase_inc(16'h0000),
|
||||||
.force_saturation(1'b0),
|
// M-11: routed through `force_saturation_pulse` so a future host
|
||||||
|
// opcode can exercise the DDC saturation path alongside the clear hook.
|
||||||
|
.force_saturation(force_saturation_pulse),
|
||||||
// F-7.7: routed through the shared clear_monitors_pulse wire so the
|
// F-7.7: routed through the shared clear_monitors_pulse wire so the
|
||||||
// DDC's internal stickies and the AD9484 OR sticky above clear from
|
// DDC's internal stickies and the AD9484 OR sticky above clear from
|
||||||
// the same future host opcode.
|
// the same future host opcode.
|
||||||
@@ -419,6 +429,12 @@ ddc_400m_enhanced ddc(
|
|||||||
assign ddc_overflow_any = ddc_mixer_saturation | ddc_filter_overflow | adc_overrange_100m;
|
assign ddc_overflow_any = ddc_mixer_saturation | ddc_filter_overflow | adc_overrange_100m;
|
||||||
assign ddc_saturation_count = ddc_diagnostics_w[7:5];
|
assign ddc_saturation_count = ddc_diagnostics_w[7:5];
|
||||||
|
|
||||||
|
// Audit M-10: in production AD9484 produces a single 8-bit stream that the
|
||||||
|
// DDC mixes into matched I/Q paths with symmetric pipelines, so ddc_valid_i
|
||||||
|
// and ddc_valid_q rise on the same cycle and `data_sync_error` cannot fire
|
||||||
|
// by construction. The check is retained inside ddc_input_interface for the
|
||||||
|
// standalone tb_ddc_input_interface unit-test (which intentionally drives
|
||||||
|
// valid_i ≠ valid_q) — left unconnected here.
|
||||||
ddc_input_interface ddc_if (
|
ddc_input_interface ddc_if (
|
||||||
.clk(clk),
|
.clk(clk),
|
||||||
.reset_n(reset_n),
|
.reset_n(reset_n),
|
||||||
@@ -429,7 +445,7 @@ ddc_input_interface ddc_if (
|
|||||||
.adc_i(adc_i_scaled),
|
.adc_i(adc_i_scaled),
|
||||||
.adc_q(adc_q_scaled),
|
.adc_q(adc_q_scaled),
|
||||||
.adc_valid(adc_valid_sync),
|
.adc_valid(adc_valid_sync),
|
||||||
.data_sync_error()
|
.data_sync_error() // unconnected — see M-10 note above
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2b. Digital Gain Control with AGC
|
// 2b. Digital Gain Control with AGC
|
||||||
|
|||||||
@@ -688,9 +688,16 @@ assign rx_doppler_data_valid = rx_doppler_valid;
|
|||||||
wire dc_notch_active;
|
wire dc_notch_active;
|
||||||
wire [`RP_DOPPLER_BIN_WIDTH-1:0] dop_bin_unsigned = rx_doppler_bin;
|
wire [`RP_DOPPLER_BIN_WIDTH-1:0] dop_bin_unsigned = rx_doppler_bin;
|
||||||
wire [3:0] bin_within_sf = dop_bin_unsigned[3:0];
|
wire [3:0] bin_within_sf = dop_bin_unsigned[3:0];
|
||||||
|
// Audit S-1: ±W around DC in a 16-bin FFT covers bins {0..W, 16-W..15}
|
||||||
|
// (2W+1 total, bin 8 the only one excluded at W=7). The previous form
|
||||||
|
// `< W || > 15-W+1` missed both boundaries: at W=1 it notched only {0}
|
||||||
|
// (skipping 1 and 15); at W=7 it missed 7 and 9. Inclusive comparators
|
||||||
|
// against the 5-bit limits hit the intended set for all W ∈ {1..7}.
|
||||||
|
wire [4:0] notch_lo = {2'b00, host_dc_notch_width}; // 0..7
|
||||||
|
wire [4:0] notch_hi = 5'd16 - notch_lo; // 9..16
|
||||||
assign dc_notch_active = (host_dc_notch_width != 3'd0) &&
|
assign dc_notch_active = (host_dc_notch_width != 3'd0) &&
|
||||||
(bin_within_sf < {1'b0, host_dc_notch_width} ||
|
({1'b0, bin_within_sf} <= notch_lo ||
|
||||||
bin_within_sf > (4'd15 - {1'b0, host_dc_notch_width} + 4'd1));
|
{1'b0, bin_within_sf} >= notch_hi);
|
||||||
|
|
||||||
// Notched Doppler data: zero I/Q when in notch zone, pass through otherwise
|
// 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 [31:0] notched_doppler_data = dc_notch_active ? 32'd0 : rx_doppler_output;
|
||||||
|
|||||||
@@ -87,9 +87,14 @@ reg [14:0] frame_peak; // Peak |sample| this frame (15-bit unsigned)
|
|||||||
reg agc_enable_prev;
|
reg agc_enable_prev;
|
||||||
|
|
||||||
// Combinational helpers for inclusive frame-boundary snapshot
|
// Combinational helpers for inclusive frame-boundary snapshot
|
||||||
// (used when valid_in and frame_boundary coincide)
|
// (used when valid_in and frame_boundary coincide). Audit S-7: previously
|
||||||
reg wire_frame_sat_incr;
|
// declared `reg` and blocking-assigned inside the clocked always — risked
|
||||||
reg wire_frame_peak_update;
|
// sim/synth divergence and triggered iverilog warnings. They are pure
|
||||||
|
// combinational functions of the registered inputs they consume, so live
|
||||||
|
// at module scope as wires (the read of frame_sat_count / frame_peak still
|
||||||
|
// reflects the prior-cycle latched value, identical to the old behaviour).
|
||||||
|
wire wire_frame_sat_incr;
|
||||||
|
wire wire_frame_peak_update;
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// EFFECTIVE GAIN SELECTION
|
// EFFECTIVE GAIN SELECTION
|
||||||
@@ -139,6 +144,11 @@ wire [14:0] abs_i = data_i_in[15] ? (~data_i_in[14:0] + 15'd1) : data_i_in[14:0]
|
|||||||
wire [14:0] abs_q = data_q_in[15] ? (~data_q_in[14:0] + 15'd1) : data_q_in[14:0];
|
wire [14:0] abs_q = data_q_in[15] ? (~data_q_in[14:0] + 15'd1) : data_q_in[14:0];
|
||||||
wire [14:0] max_iq = (abs_i > abs_q) ? abs_i : abs_q;
|
wire [14:0] max_iq = (abs_i > abs_q) ? abs_i : abs_q;
|
||||||
|
|
||||||
|
// S-7 continuous assigns for the boundary-snapshot helpers (declared above).
|
||||||
|
assign wire_frame_sat_incr = (valid_in && (overflow_i || overflow_q)
|
||||||
|
&& (frame_sat_count != 8'hFF));
|
||||||
|
assign wire_frame_peak_update = (valid_in && (max_iq > frame_peak));
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// SIGNED GAIN ↔ GAIN_SHIFT ENCODING CONVERSION
|
// SIGNED GAIN ↔ GAIN_SHIFT ENCODING CONVERSION
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -203,14 +213,11 @@ always @(posedge clk or negedge reset_n) begin
|
|||||||
// Track AGC enable transitions
|
// Track AGC enable transitions
|
||||||
agc_enable_prev <= agc_enable;
|
agc_enable_prev <= agc_enable;
|
||||||
|
|
||||||
// Compute inclusive metrics: if valid_in fires this cycle,
|
// S-7: wire_frame_sat_incr / wire_frame_peak_update are now
|
||||||
// include current sample in the snapshot taken at frame_boundary.
|
// module-scope continuous assigns — see top of file. The boundary
|
||||||
// This avoids losing the last sample when valid_in and
|
// snapshot below still consumes them inclusively when valid_in and
|
||||||
// frame_boundary coincide (NBA last-write-wins would otherwise
|
// frame_boundary coincide (NBA last-write-wins would otherwise
|
||||||
// snapshot stale values then reset, dropping the sample entirely).
|
// snapshot stale values then reset, dropping the sample entirely).
|
||||||
wire_frame_sat_incr = (valid_in && (overflow_i || overflow_q)
|
|
||||||
&& (frame_sat_count != 8'hFF));
|
|
||||||
wire_frame_peak_update = (valid_in && (max_iq > frame_peak));
|
|
||||||
|
|
||||||
// ---- Data pipeline (1-cycle latency) ----
|
// ---- Data pipeline (1-cycle latency) ----
|
||||||
valid_out <= valid_in;
|
valid_out <= valid_in;
|
||||||
|
|||||||
+4085
-4085
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user