mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-08 14:44:56 +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
|
||||
|
||||
[script]
|
||||
read_verilog -formal cdc_modules.v
|
||||
read_verilog -formal cdc_async_fifo.v
|
||||
read_verilog -formal fv_cdc_adc.v
|
||||
prep -top fv_cdc_adc
|
||||
clk2fflogic
|
||||
|
||||
[files]
|
||||
../cdc_modules.v
|
||||
../cdc_async_fifo.v
|
||||
fv_cdc_adc.v
|
||||
|
||||
@@ -1,14 +1,36 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
// ============================================================================
|
||||
// Formal Verification Wrapper: cdc_adc_to_processing
|
||||
// AERIS-10 Radar FPGA — Multi-bit CDC with Gray Code
|
||||
// Formal Verification Wrapper: cdc_async_fifo
|
||||
// AERIS-10 Radar FPGA — Multi-bit CDC via Cummings SNUG-2002 async FIFO
|
||||
// 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;
|
||||
|
||||
parameter WIDTH = 8;
|
||||
parameter STAGES = 3;
|
||||
parameter WIDTH = 8;
|
||||
parameter DEPTH = 16;
|
||||
|
||||
`ifdef FORMAL
|
||||
|
||||
@@ -18,7 +40,7 @@ module fv_cdc_adc;
|
||||
(* gclk *) reg formal_clk;
|
||||
|
||||
// ================================================================
|
||||
// Asynchronous clock generation via $anyseq
|
||||
// Asynchronous src/dst clock generation via $anyseq
|
||||
// ================================================================
|
||||
reg src_clk_r = 1'b0;
|
||||
reg dst_clk_r = 1'b0;
|
||||
@@ -37,8 +59,7 @@ module fv_cdc_adc;
|
||||
wire dst_clk = dst_clk_r;
|
||||
|
||||
// ================================================================
|
||||
// Clock liveness — each clock must toggle within 7 gclk cycles
|
||||
// 4-bit counters with saturation.
|
||||
// Clock liveness — each clock toggles within 7 gclk cycles.
|
||||
// ================================================================
|
||||
reg [3:0] src_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;
|
||||
|
||||
// ================================================================
|
||||
// Reset generation — hold reset long enough for both clocks to
|
||||
// see at least one posedge during reset (stall bound 7).
|
||||
// Reset generation — hold reset long enough for both clocks to see
|
||||
// at least one posedge during reset (stall bound 7).
|
||||
// ================================================================
|
||||
reg reset_n = 1'b0;
|
||||
reg [4:0] reset_cnt = 0;
|
||||
@@ -104,10 +125,13 @@ module fv_cdc_adc;
|
||||
reg src_valid = 1'b0;
|
||||
wire [WIDTH-1:0] dst_data;
|
||||
wire dst_valid;
|
||||
wire overrun;
|
||||
|
||||
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;
|
||||
assign src_valid_next = $anyseq;
|
||||
|
||||
@@ -121,46 +145,31 @@ module fv_cdc_adc;
|
||||
// ================================================================
|
||||
// DUT instantiation
|
||||
// ================================================================
|
||||
wire [WIDTH-1:0] fv_src_data_reg;
|
||||
wire [1:0] fv_src_toggle;
|
||||
|
||||
cdc_adc_to_processing #(
|
||||
cdc_async_fifo #(
|
||||
.WIDTH (WIDTH),
|
||||
.STAGES(STAGES)
|
||||
.DEPTH (DEPTH)
|
||||
) dut (
|
||||
.src_clk (src_clk),
|
||||
.dst_clk (dst_clk),
|
||||
.src_clk (src_clk),
|
||||
.dst_clk (dst_clk),
|
||||
.src_reset_n(reset_n),
|
||||
.dst_reset_n(reset_n),
|
||||
.src_data (src_data),
|
||||
.src_valid(src_valid),
|
||||
.dst_data (dst_data),
|
||||
.dst_valid(dst_valid),
|
||||
.fv_src_data_reg(fv_src_data_reg),
|
||||
.fv_src_toggle (fv_src_toggle)
|
||||
.src_data (src_data),
|
||||
.src_valid (src_valid),
|
||||
.dst_data (dst_data),
|
||||
.dst_valid (dst_valid),
|
||||
.overrun (overrun)
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// Past-valid tracker
|
||||
// Past-valid + per-domain reset-done tracking (mirrors fv_cdc_handshake)
|
||||
// ================================================================
|
||||
reg fv_past_valid = 1'b0;
|
||||
always @(posedge formal_clk) begin
|
||||
fv_past_valid <= 1'b1;
|
||||
end
|
||||
always @(posedge formal_clk) fv_past_valid <= 1'b1;
|
||||
|
||||
// ================================================================
|
||||
// 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 dst_saw_posedge = 1'b0;
|
||||
reg src_reset_done = 1'b0;
|
||||
reg dst_reset_done = 1'b0;
|
||||
reg src_reset_done = 1'b0;
|
||||
reg dst_reset_done = 1'b0;
|
||||
|
||||
always @(posedge formal_clk) begin
|
||||
if (!reset_n && src_posedge)
|
||||
@@ -180,86 +189,69 @@ module fv_cdc_adc;
|
||||
wire dut_initialized = reset_n && src_reset_done && dst_reset_done;
|
||||
|
||||
// ================================================================
|
||||
// PROPERTY 1: Gray code round-trip identity
|
||||
// binary_to_gray(gray_to_binary(x)) == x for all x
|
||||
// gray_to_binary(binary_to_gray(x)) == x for all x
|
||||
//
|
||||
// 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.
|
||||
// PROPERTY 1: Reset behaviour
|
||||
// After both domains have seen a clock edge under reset, the FIFO
|
||||
// reports empty (dst_valid=0, dst_data=0) and overrun=0.
|
||||
// ================================================================
|
||||
always @(posedge formal_clk) begin
|
||||
if (!reset_n && src_reset_done && dst_reset_done) begin
|
||||
assert(dst_valid == 1'b0);
|
||||
assert(dst_data == {WIDTH{1'b0}});
|
||||
assert(dst_data == {WIDTH{1'b0}});
|
||||
assert(overrun == 1'b0);
|
||||
end
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
// PROPERTY 3: No spurious dst_valid without preceding src_valid
|
||||
//
|
||||
// Track whether any src_valid has ever been asserted after reset.
|
||||
// Before the first src_valid, dst_valid must remain 0.
|
||||
// PROPERTY 2: No spurious dst_valid before any successful write
|
||||
// Until at least one accepted (non-overrun) src_valid pulse has
|
||||
// occurred, dst_valid must remain low.
|
||||
// ================================================================
|
||||
reg fv_any_src_valid = 1'b0;
|
||||
|
||||
reg fv_any_src_accept = 1'b0;
|
||||
always @(posedge formal_clk) begin
|
||||
if (!reset_n)
|
||||
fv_any_src_valid <= 1'b0;
|
||||
else if (src_posedge && src_valid)
|
||||
fv_any_src_valid <= 1'b1;
|
||||
fv_any_src_accept <= 1'b0;
|
||||
else if (src_posedge && src_valid && !overrun)
|
||||
fv_any_src_accept <= 1'b1;
|
||||
end
|
||||
|
||||
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);
|
||||
end
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
// PROPERTY 4: Data integrity
|
||||
// When dst_valid asserts, dst_data must match the DUT's latched
|
||||
// src_data_reg (exposed via formal port fv_src_data_reg).
|
||||
// PROPERTY 3: Overrun semantics
|
||||
// overrun should only assert in cycles where src_valid is high
|
||||
// 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
|
||||
// clk2fflogic timing issues with $anyseq inputs), we directly
|
||||
// compare dst_data against fv_src_data_reg. This verifies that
|
||||
// the gray-code CDC path correctly transfers the latched value.
|
||||
// We assume each src_valid pulse is followed by enough quiet time
|
||||
// (7'd80 gclk ticks) for the value to write into the FIFO,
|
||||
// propagate the wptr Gray pointer to the dst domain, be read out,
|
||||
// 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
|
||||
// apart for the previous transfer to propagate (STAGES+2 dst_clk
|
||||
// cycles). We enforce this with a cooldown counter.
|
||||
// This is intentionally weaker than a multi-in-flight FIFO-order
|
||||
// proof — adapting the original cdc_adc_to_processing single-latch
|
||||
// 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;
|
||||
|
||||
@@ -267,86 +259,86 @@ module fv_cdc_adc;
|
||||
if (!reset_n) begin
|
||||
fv_src_cooldown <= 0;
|
||||
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
|
||||
fv_src_cooldown <= fv_src_cooldown - 1;
|
||||
end
|
||||
end
|
||||
|
||||
// Assume sufficient spacing between src_valid pulses
|
||||
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);
|
||||
end
|
||||
end
|
||||
|
||||
// Track in-flight transfers for cover properties
|
||||
reg fv_inflight = 1'b0;
|
||||
reg [1:0] fv_transfer_count = 0;
|
||||
// Capture the src_data of each accepted write.
|
||||
reg [WIDTH-1:0] fv_pending_data;
|
||||
reg fv_pending_valid;
|
||||
|
||||
always @(posedge formal_clk) begin
|
||||
if (!reset_n) begin
|
||||
fv_inflight <= 1'b0;
|
||||
fv_transfer_count <= 0;
|
||||
end else if (src_posedge && src_valid) begin
|
||||
fv_inflight <= 1'b1;
|
||||
end else if (dst_posedge && dst_valid) begin
|
||||
fv_inflight <= 1'b0;
|
||||
if (fv_transfer_count < 2'd3)
|
||||
fv_transfer_count <= fv_transfer_count + 1;
|
||||
fv_pending_data <= {WIDTH{1'b0}};
|
||||
fv_pending_valid <= 1'b0;
|
||||
end else begin
|
||||
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
|
||||
fv_pending_valid <= 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// When dst_valid fires, dst_data must match fv_src_data_reg
|
||||
// (the value the DUT's source domain actually captured).
|
||||
// When dst_valid fires with a pending tracked write, dst_data must
|
||||
// match the captured src_data.
|
||||
always @(posedge formal_clk) begin
|
||||
if (dut_initialized && dst_posedge && dst_valid) begin
|
||||
assert(dst_data == fv_src_data_reg);
|
||||
end
|
||||
if (dut_initialized && dst_posedge && dst_valid && fv_pending_valid)
|
||||
assert(dst_data == fv_pending_data);
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
// PROPERTY 5: Toggle detection — src_valid eventually produces
|
||||
// dst_valid (bounded liveness).
|
||||
//
|
||||
// After src_valid fires, dst_valid must assert within a bounded
|
||||
// number of gclk cycles (STAGES sync + output register).
|
||||
// PROPERTY 5: 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
|
||||
// spacing of 80 gclk and 2-stage Gray-pointer sync chains, the
|
||||
// actual end-to-end latency is well under 80; we use 100 for
|
||||
// margin.
|
||||
// ================================================================
|
||||
reg [6:0] fv_propagation_timer = 0;
|
||||
|
||||
always @(posedge formal_clk) begin
|
||||
if (!reset_n) begin
|
||||
if (!reset_n)
|
||||
fv_propagation_timer <= 0;
|
||||
end else if (src_posedge && src_valid && !fv_inflight) begin
|
||||
fv_propagation_timer <= 1;
|
||||
end else if (fv_propagation_timer > 0 && !(dst_posedge && dst_valid)) begin
|
||||
else if (fv_pending_valid)
|
||||
fv_propagation_timer <= fv_propagation_timer + 1;
|
||||
end else if (dst_posedge && dst_valid) begin
|
||||
else
|
||||
fv_propagation_timer <= 0;
|
||||
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
|
||||
if (dut_initialized && fv_propagation_timer > 0) begin
|
||||
assert(fv_propagation_timer < 80);
|
||||
end
|
||||
if (dut_initialized)
|
||||
assert(fv_propagation_timer < 100);
|
||||
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
|
||||
if (dut_initialized) begin
|
||||
// Cover: src captures data
|
||||
cover(src_posedge && src_valid);
|
||||
cover(src_posedge && src_valid && !overrun);
|
||||
|
||||
// Cover: dst presents valid data
|
||||
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: 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,7 +334,15 @@ ad9484_interface_400m adc (
|
||||
// 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
|
||||
// the AD9484 overrange diagnostic consistent.
|
||||
wire clear_monitors_pulse = 1'b0; // future host-driven clear hook
|
||||
//
|
||||
// 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
|
||||
reg adc_overrange_sticky_400m;
|
||||
always @(posedge clk_400m or negedge reset_n) begin
|
||||
if (!reset_n)
|
||||
@@ -403,7 +411,9 @@ ddc_400m_enhanced ddc(
|
||||
// Test/debug inputs — explicit tie-low (were floating)
|
||||
.test_mode(2'b00),
|
||||
.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
|
||||
// DDC's internal stickies and the AD9484 OR sticky above clear from
|
||||
// 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_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 (
|
||||
.clk(clk),
|
||||
.reset_n(reset_n),
|
||||
@@ -429,7 +445,7 @@ ddc_input_interface ddc_if (
|
||||
.adc_i(adc_i_scaled),
|
||||
.adc_q(adc_q_scaled),
|
||||
.adc_valid(adc_valid_sync),
|
||||
.data_sync_error()
|
||||
.data_sync_error() // unconnected — see M-10 note above
|
||||
);
|
||||
|
||||
// 2b. Digital Gain Control with AGC
|
||||
|
||||
@@ -688,9 +688,16 @@ assign rx_doppler_data_valid = rx_doppler_valid;
|
||||
wire dc_notch_active;
|
||||
wire [`RP_DOPPLER_BIN_WIDTH-1:0] dop_bin_unsigned = rx_doppler_bin;
|
||||
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) &&
|
||||
(bin_within_sf < {1'b0, host_dc_notch_width} ||
|
||||
bin_within_sf > (4'd15 - {1'b0, host_dc_notch_width} + 4'd1));
|
||||
({1'b0, bin_within_sf} <= notch_lo ||
|
||||
{1'b0, bin_within_sf} >= notch_hi);
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -87,9 +87,14 @@ reg [14:0] frame_peak; // Peak |sample| this frame (15-bit unsigned)
|
||||
reg agc_enable_prev;
|
||||
|
||||
// Combinational helpers for inclusive frame-boundary snapshot
|
||||
// (used when valid_in and frame_boundary coincide)
|
||||
reg wire_frame_sat_incr;
|
||||
reg wire_frame_peak_update;
|
||||
// (used when valid_in and frame_boundary coincide). Audit S-7: previously
|
||||
// declared `reg` and blocking-assigned inside the clocked always — risked
|
||||
// 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
|
||||
@@ -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] 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
|
||||
// =========================================================================
|
||||
@@ -203,14 +213,11 @@ always @(posedge clk or negedge reset_n) begin
|
||||
// Track AGC enable transitions
|
||||
agc_enable_prev <= agc_enable;
|
||||
|
||||
// Compute inclusive metrics: if valid_in fires this cycle,
|
||||
// include current sample in the snapshot taken at frame_boundary.
|
||||
// This avoids losing the last sample when valid_in and
|
||||
// S-7: wire_frame_sat_incr / wire_frame_peak_update are now
|
||||
// module-scope continuous assigns — see top of file. The boundary
|
||||
// snapshot below still consumes them inclusively when valid_in and
|
||||
// frame_boundary coincide (NBA last-write-wins would otherwise
|
||||
// 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) ----
|
||||
valid_out <= valid_in;
|
||||
|
||||
+4085
-4085
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user