Full-chain MTI+CFAR real-data co-simulation: bit-exact match across all 10247 checkpoints (decim->MTI->Doppler->DC notch->CFAR) using ADI CN0566 data
This commit is contained in:
@@ -759,6 +759,243 @@ def run_doppler_fft(range_data_i, range_data_q, twiddle_file_32=None):
|
|||||||
return doppler_map_i, doppler_map_q
|
return doppler_map_i, doppler_map_q
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Stage 3c: MTI Canceller (2-pulse, bit-accurate)
|
||||||
|
# ===========================================================================
|
||||||
|
def run_mti_canceller(decim_i, decim_q, enable=True):
|
||||||
|
"""
|
||||||
|
Bit-accurate model of mti_canceller.v — 2-pulse canceller.
|
||||||
|
|
||||||
|
Input: decim_i/q — shape (N_chirps, NUM_RANGE_BINS), 16-bit signed
|
||||||
|
Output: mti_i/q — shape (N_chirps, NUM_RANGE_BINS), 16-bit signed
|
||||||
|
|
||||||
|
When enable=True:
|
||||||
|
- First chirp (chirp 0): output is all zeros (muted, no previous data)
|
||||||
|
- Subsequent chirps: out[c][r] = current[c][r] - previous[c-1][r],
|
||||||
|
with saturation to 16-bit.
|
||||||
|
When enable=False:
|
||||||
|
- Pass-through (output = input).
|
||||||
|
|
||||||
|
RTL detail (from mti_canceller.v):
|
||||||
|
diff_full = {I_in[15], I_in} - {prev[15], prev} (17-bit signed)
|
||||||
|
saturate to 16-bit: clamp to [-32768, +32767]
|
||||||
|
"""
|
||||||
|
n_chirps, n_bins = decim_i.shape
|
||||||
|
mti_i = np.zeros_like(decim_i)
|
||||||
|
mti_q = np.zeros_like(decim_q)
|
||||||
|
|
||||||
|
print(f"[MTI] 2-pulse canceller, enable={enable}, {n_chirps} chirps x {n_bins} bins")
|
||||||
|
|
||||||
|
if not enable:
|
||||||
|
mti_i[:] = decim_i
|
||||||
|
mti_q[:] = decim_q
|
||||||
|
print(f" Pass-through mode (MTI disabled)")
|
||||||
|
return mti_i, mti_q
|
||||||
|
|
||||||
|
for c in range(n_chirps):
|
||||||
|
if c == 0:
|
||||||
|
# First chirp: output muted (zeros) — no previous data
|
||||||
|
mti_i[c, :] = 0
|
||||||
|
mti_q[c, :] = 0
|
||||||
|
else:
|
||||||
|
for r in range(n_bins):
|
||||||
|
# Sign-extend to 17-bit, subtract, saturate back to 16-bit
|
||||||
|
diff_i = int(decim_i[c, r]) - int(decim_i[c - 1, r])
|
||||||
|
diff_q = int(decim_q[c, r]) - int(decim_q[c - 1, r])
|
||||||
|
mti_i[c, r] = saturate(diff_i, 16)
|
||||||
|
mti_q[c, r] = saturate(diff_q, 16)
|
||||||
|
|
||||||
|
print(f" Chirp 0: muted (zeros)")
|
||||||
|
print(f" Chirps 1-{n_chirps-1}: I range [{mti_i[1:].min()}, {mti_i[1:].max()}], "
|
||||||
|
f"Q range [{mti_q[1:].min()}, {mti_q[1:].max()}]")
|
||||||
|
return mti_i, mti_q
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Stage 3d: DC Notch Filter (post-Doppler, bit-accurate)
|
||||||
|
# ===========================================================================
|
||||||
|
def run_dc_notch(doppler_i, doppler_q, width=2):
|
||||||
|
"""
|
||||||
|
Bit-accurate model of the inline DC notch filter in radar_system_top.v.
|
||||||
|
|
||||||
|
Input: doppler_i/q — shape (NUM_RANGE_BINS, NUM_DOPPLER_BINS), 16-bit signed
|
||||||
|
Output: notched_i/q — shape (NUM_RANGE_BINS, NUM_DOPPLER_BINS), 16-bit signed
|
||||||
|
|
||||||
|
Zeros Doppler bins within ±width of DC (bin 0).
|
||||||
|
In a 32-point FFT, DC is bin 0; negative Doppler wraps to bins 31,30,...
|
||||||
|
width=0: pass-through
|
||||||
|
width=1: zero bins {0}
|
||||||
|
width=2: zero bins {0, 1, 31}
|
||||||
|
width=3: zero bins {0, 1, 2, 30, 31} etc.
|
||||||
|
|
||||||
|
RTL logic (from radar_system_top.v lines 517-524):
|
||||||
|
dc_notch_active = (width != 0) &&
|
||||||
|
(dop_bin < width || dop_bin > (31 - width + 1))
|
||||||
|
notched_data = dc_notch_active ? 0 : doppler_data
|
||||||
|
"""
|
||||||
|
n_range, n_doppler = doppler_i.shape
|
||||||
|
notched_i = doppler_i.copy()
|
||||||
|
notched_q = doppler_q.copy()
|
||||||
|
|
||||||
|
print(f"[DC NOTCH] width={width}, {n_range} range bins x {n_doppler} Doppler bins")
|
||||||
|
|
||||||
|
if width == 0:
|
||||||
|
print(f" Pass-through (width=0)")
|
||||||
|
return notched_i, notched_q
|
||||||
|
|
||||||
|
zeroed_count = 0
|
||||||
|
for dbin in range(n_doppler):
|
||||||
|
# Replicate RTL comparison (unsigned 5-bit):
|
||||||
|
# dop_bin < width OR dop_bin > (31 - width + 1)
|
||||||
|
active = (dbin < width) or (dbin > (31 - width + 1))
|
||||||
|
if active:
|
||||||
|
notched_i[:, dbin] = 0
|
||||||
|
notched_q[:, dbin] = 0
|
||||||
|
zeroed_count += 1
|
||||||
|
|
||||||
|
print(f" Zeroed {zeroed_count} Doppler bin columns")
|
||||||
|
return notched_i, notched_q
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Stage 3e: CA-CFAR Detector (bit-accurate)
|
||||||
|
# ===========================================================================
|
||||||
|
def run_cfar_ca(doppler_i, doppler_q, guard=2, train=8,
|
||||||
|
alpha_q44=0x30, mode='CA', simple_threshold=500):
|
||||||
|
"""
|
||||||
|
Bit-accurate model of cfar_ca.v — Cell-Averaging CFAR detector.
|
||||||
|
|
||||||
|
Input: doppler_i/q — shape (NUM_RANGE_BINS, NUM_DOPPLER_BINS), 16-bit signed
|
||||||
|
Output: detect_flags — shape (NUM_RANGE_BINS, NUM_DOPPLER_BINS), bool
|
||||||
|
magnitudes — shape (NUM_RANGE_BINS, NUM_DOPPLER_BINS), uint17
|
||||||
|
thresholds — shape (NUM_RANGE_BINS, NUM_DOPPLER_BINS), uint17
|
||||||
|
|
||||||
|
CFAR algorithm per Doppler column:
|
||||||
|
1. Compute magnitude |I| + |Q| for all range bins in that column
|
||||||
|
2. For each CUT (Cell Under Test) at range index k:
|
||||||
|
a. Leading training cells: indices [k-G-T .. k-G-1] (clamped to valid)
|
||||||
|
b. Lagging training cells: indices [k+G+1 .. k+G+T] (clamped to valid)
|
||||||
|
c. noise_sum = sum of training cells (CA mode: both sides)
|
||||||
|
d. threshold = (alpha * noise_sum) >> 4 (Q4.4 shift)
|
||||||
|
e. detect if magnitude[k] > threshold
|
||||||
|
|
||||||
|
RTL details (from cfar_ca.v):
|
||||||
|
- Magnitude: |I| + |Q| (L1 norm, 17-bit unsigned)
|
||||||
|
- Alpha in Q4.4 fixed-point (8-bit unsigned)
|
||||||
|
- ALPHA_FRAC_BITS = 4
|
||||||
|
- Threshold saturates to 17 bits
|
||||||
|
- Edge handling: uses only available training cells at boundaries
|
||||||
|
- Pipeline: ST_CFAR_THR → ST_CFAR_MUL → ST_CFAR_CMP
|
||||||
|
|
||||||
|
Modes:
|
||||||
|
CA: noise_sum = leading_sum + lagging_sum
|
||||||
|
GO: noise_sum = side with greater average (cross-multiply comparison)
|
||||||
|
SO: noise_sum = side with smaller average
|
||||||
|
"""
|
||||||
|
n_range, n_doppler = doppler_i.shape
|
||||||
|
ALPHA_FRAC_BITS = 4
|
||||||
|
|
||||||
|
# Ensure train >= 1 (RTL clamps 0 → 1)
|
||||||
|
if train == 0:
|
||||||
|
train = 1
|
||||||
|
|
||||||
|
print(f"[CFAR] mode={mode}, guard={guard}, train={train}, "
|
||||||
|
f"alpha=0x{alpha_q44:02X} (Q4.4={alpha_q44/16:.2f}), "
|
||||||
|
f"{n_range} range x {n_doppler} Doppler")
|
||||||
|
|
||||||
|
# Compute magnitudes: |I| + |Q| (17-bit unsigned, matching RTL L1 norm)
|
||||||
|
# RTL: abs_i = I[15] ? (~I + 1) : I; abs_q = Q[15] ? (~Q + 1) : Q
|
||||||
|
# For I = -32768: ~(-32768 as 16-bit) + 1 = 32768 (unsigned)
|
||||||
|
magnitudes = np.zeros((n_range, n_doppler), dtype=np.int64)
|
||||||
|
for rbin in range(n_range):
|
||||||
|
for dbin in range(n_doppler):
|
||||||
|
i_val = int(doppler_i[rbin, dbin])
|
||||||
|
q_val = int(doppler_q[rbin, dbin])
|
||||||
|
abs_i = (-i_val) & 0xFFFF if i_val < 0 else i_val & 0xFFFF
|
||||||
|
abs_q = (-q_val) & 0xFFFF if q_val < 0 else q_val & 0xFFFF
|
||||||
|
magnitudes[rbin, dbin] = abs_i + abs_q # 17-bit unsigned
|
||||||
|
|
||||||
|
detect_flags = np.zeros((n_range, n_doppler), dtype=np.bool_)
|
||||||
|
thresholds = np.zeros((n_range, n_doppler), dtype=np.int64)
|
||||||
|
|
||||||
|
total_detections = 0
|
||||||
|
|
||||||
|
# Process each Doppler column independently (matching RTL column-by-column)
|
||||||
|
for dbin in range(n_doppler):
|
||||||
|
col = magnitudes[:, dbin] # 64 magnitudes for this Doppler bin
|
||||||
|
|
||||||
|
for cut_idx in range(n_range):
|
||||||
|
# Compute leading sum (cells before CUT, outside guard zone)
|
||||||
|
leading_sum = 0
|
||||||
|
leading_count = 0
|
||||||
|
for t in range(1, train + 1):
|
||||||
|
idx = cut_idx - guard - t
|
||||||
|
if 0 <= idx < n_range:
|
||||||
|
leading_sum += int(col[idx])
|
||||||
|
leading_count += 1
|
||||||
|
|
||||||
|
# Compute lagging sum (cells after CUT, outside guard zone)
|
||||||
|
lagging_sum = 0
|
||||||
|
lagging_count = 0
|
||||||
|
for t in range(1, train + 1):
|
||||||
|
idx = cut_idx + guard + t
|
||||||
|
if 0 <= idx < n_range:
|
||||||
|
lagging_sum += int(col[idx])
|
||||||
|
lagging_count += 1
|
||||||
|
|
||||||
|
# Mode-dependent noise estimate
|
||||||
|
if mode == 'CA' or mode == 'CA-CFAR':
|
||||||
|
noise_sum = leading_sum + lagging_sum
|
||||||
|
elif mode == 'GO' or mode == 'GO-CFAR':
|
||||||
|
if leading_count > 0 and lagging_count > 0:
|
||||||
|
if leading_sum * lagging_count > lagging_sum * leading_count:
|
||||||
|
noise_sum = leading_sum
|
||||||
|
else:
|
||||||
|
noise_sum = lagging_sum
|
||||||
|
elif leading_count > 0:
|
||||||
|
noise_sum = leading_sum
|
||||||
|
else:
|
||||||
|
noise_sum = lagging_sum
|
||||||
|
elif mode == 'SO' or mode == 'SO-CFAR':
|
||||||
|
if leading_count > 0 and lagging_count > 0:
|
||||||
|
if leading_sum * lagging_count < lagging_sum * leading_count:
|
||||||
|
noise_sum = leading_sum
|
||||||
|
else:
|
||||||
|
noise_sum = lagging_sum
|
||||||
|
elif leading_count > 0:
|
||||||
|
noise_sum = leading_sum
|
||||||
|
else:
|
||||||
|
noise_sum = lagging_sum
|
||||||
|
else:
|
||||||
|
noise_sum = leading_sum + lagging_sum # Default to CA
|
||||||
|
|
||||||
|
# Threshold = (alpha * noise_sum) >> ALPHA_FRAC_BITS
|
||||||
|
# RTL: noise_product = r_alpha * noise_sum_reg (31-bit)
|
||||||
|
# threshold = noise_product[ALPHA_FRAC_BITS +: MAG_WIDTH]
|
||||||
|
# saturate if overflow
|
||||||
|
noise_product = alpha_q44 * noise_sum
|
||||||
|
threshold_raw = noise_product >> ALPHA_FRAC_BITS
|
||||||
|
|
||||||
|
# Saturate to MAG_WIDTH=17 bits
|
||||||
|
MAX_MAG = (1 << 17) - 1 # 131071
|
||||||
|
if threshold_raw > MAX_MAG:
|
||||||
|
threshold_val = MAX_MAG
|
||||||
|
else:
|
||||||
|
threshold_val = int(threshold_raw)
|
||||||
|
|
||||||
|
# Detection: magnitude > threshold
|
||||||
|
if int(col[cut_idx]) > threshold_val:
|
||||||
|
detect_flags[cut_idx, dbin] = True
|
||||||
|
total_detections += 1
|
||||||
|
|
||||||
|
thresholds[cut_idx, dbin] = threshold_val
|
||||||
|
|
||||||
|
print(f" Total detections: {total_detections}")
|
||||||
|
print(f" Magnitude range: [{magnitudes.min()}, {magnitudes.max()}]")
|
||||||
|
|
||||||
|
return detect_flags, magnitudes, thresholds
|
||||||
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# Stage 4: Detection (magnitude threshold)
|
# Stage 4: Detection (magnitude threshold)
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
@@ -1060,6 +1297,105 @@ def main():
|
|||||||
np.save(os.path.join(output_dir, "fullchain_doppler_i.npy"), fc_doppler_i)
|
np.save(os.path.join(output_dir, "fullchain_doppler_i.npy"), fc_doppler_i)
|
||||||
np.save(os.path.join(output_dir, "fullchain_doppler_q.npy"), fc_doppler_q)
|
np.save(os.path.join(output_dir, "fullchain_doppler_q.npy"), fc_doppler_q)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Full-chain with MTI + DC Notch + CFAR
|
||||||
|
# This models the complete RTL data flow:
|
||||||
|
# range FFT → decimator → MTI canceller → Doppler → DC notch → CFAR
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
print(f"\n{'=' * 72}")
|
||||||
|
print("Stage 3c: MTI Canceller (2-pulse, on decimated data)")
|
||||||
|
mti_i, mti_q = run_mti_canceller(decim_i, decim_q, enable=True)
|
||||||
|
write_hex_files(output_dir, mti_i, mti_q, "fullchain_mti_ref")
|
||||||
|
np.save(os.path.join(output_dir, "fullchain_mti_i.npy"), mti_i)
|
||||||
|
np.save(os.path.join(output_dir, "fullchain_mti_q.npy"), mti_q)
|
||||||
|
|
||||||
|
# Doppler on MTI-filtered data
|
||||||
|
print(f"\n{'=' * 72}")
|
||||||
|
print("Stage 3b+c: Doppler FFT on MTI-filtered decimated data")
|
||||||
|
mti_doppler_i, mti_doppler_q = run_doppler_fft(
|
||||||
|
mti_i, mti_q, twiddle_file_32=twiddle_32
|
||||||
|
)
|
||||||
|
write_hex_files(output_dir, mti_doppler_i, mti_doppler_q, "fullchain_mti_doppler_ref")
|
||||||
|
np.save(os.path.join(output_dir, "fullchain_mti_doppler_i.npy"), mti_doppler_i)
|
||||||
|
np.save(os.path.join(output_dir, "fullchain_mti_doppler_q.npy"), mti_doppler_q)
|
||||||
|
|
||||||
|
# DC notch on MTI-Doppler data
|
||||||
|
DC_NOTCH_WIDTH = 2 # Default test value: zero bins {0, 1, 31}
|
||||||
|
print(f"\n{'=' * 72}")
|
||||||
|
print(f"Stage 3d: DC Notch Filter (width={DC_NOTCH_WIDTH})")
|
||||||
|
notched_i, notched_q = run_dc_notch(mti_doppler_i, mti_doppler_q, width=DC_NOTCH_WIDTH)
|
||||||
|
write_hex_files(output_dir, notched_i, notched_q, "fullchain_notched_ref")
|
||||||
|
|
||||||
|
# Write notched Doppler as packed 32-bit for RTL comparison
|
||||||
|
fc_notched_packed_file = os.path.join(output_dir, "fullchain_notched_ref_packed.hex")
|
||||||
|
with open(fc_notched_packed_file, 'w') as f:
|
||||||
|
for rbin in range(DOPPLER_RANGE_BINS):
|
||||||
|
for dbin in range(DOPPLER_FFT_SIZE):
|
||||||
|
i_val = int(notched_i[rbin, dbin]) & 0xFFFF
|
||||||
|
q_val = int(notched_q[rbin, dbin]) & 0xFFFF
|
||||||
|
packed = (q_val << 16) | i_val
|
||||||
|
f.write(f"{packed:08X}\n")
|
||||||
|
print(f" Wrote {fc_notched_packed_file} ({DOPPLER_RANGE_BINS * DOPPLER_FFT_SIZE} packed IQ words)")
|
||||||
|
|
||||||
|
# CFAR on DC-notched data
|
||||||
|
CFAR_GUARD = 2
|
||||||
|
CFAR_TRAIN = 8
|
||||||
|
CFAR_ALPHA = 0x30 # Q4.4 = 3.0
|
||||||
|
CFAR_MODE = 'CA'
|
||||||
|
print(f"\n{'=' * 72}")
|
||||||
|
print(f"Stage 3e: CA-CFAR (guard={CFAR_GUARD}, train={CFAR_TRAIN}, alpha=0x{CFAR_ALPHA:02X})")
|
||||||
|
cfar_flags, cfar_mag, cfar_thr = run_cfar_ca(
|
||||||
|
notched_i, notched_q,
|
||||||
|
guard=CFAR_GUARD, train=CFAR_TRAIN,
|
||||||
|
alpha_q44=CFAR_ALPHA, mode=CFAR_MODE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write CFAR reference files
|
||||||
|
# 1. Magnitude map (17-bit unsigned, row-major: 64 range x 32 Doppler = 2048)
|
||||||
|
cfar_mag_file = os.path.join(output_dir, "fullchain_cfar_mag.hex")
|
||||||
|
with open(cfar_mag_file, 'w') as f:
|
||||||
|
for rbin in range(DOPPLER_RANGE_BINS):
|
||||||
|
for dbin in range(DOPPLER_FFT_SIZE):
|
||||||
|
m = int(cfar_mag[rbin, dbin]) & 0x1FFFF
|
||||||
|
f.write(f"{m:05X}\n")
|
||||||
|
print(f" Wrote {cfar_mag_file} ({DOPPLER_RANGE_BINS * DOPPLER_FFT_SIZE} mag values)")
|
||||||
|
|
||||||
|
# 2. Threshold map (17-bit unsigned)
|
||||||
|
cfar_thr_file = os.path.join(output_dir, "fullchain_cfar_thr.hex")
|
||||||
|
with open(cfar_thr_file, 'w') as f:
|
||||||
|
for rbin in range(DOPPLER_RANGE_BINS):
|
||||||
|
for dbin in range(DOPPLER_FFT_SIZE):
|
||||||
|
t = int(cfar_thr[rbin, dbin]) & 0x1FFFF
|
||||||
|
f.write(f"{t:05X}\n")
|
||||||
|
print(f" Wrote {cfar_thr_file} ({DOPPLER_RANGE_BINS * DOPPLER_FFT_SIZE} threshold values)")
|
||||||
|
|
||||||
|
# 3. Detection flags (1-bit per cell)
|
||||||
|
cfar_det_file = os.path.join(output_dir, "fullchain_cfar_det.hex")
|
||||||
|
with open(cfar_det_file, 'w') as f:
|
||||||
|
for rbin in range(DOPPLER_RANGE_BINS):
|
||||||
|
for dbin in range(DOPPLER_FFT_SIZE):
|
||||||
|
d = 1 if cfar_flags[rbin, dbin] else 0
|
||||||
|
f.write(f"{d:01X}\n")
|
||||||
|
print(f" Wrote {cfar_det_file} ({DOPPLER_RANGE_BINS * DOPPLER_FFT_SIZE} detection flags)")
|
||||||
|
|
||||||
|
# 4. Detection list (text)
|
||||||
|
cfar_detections = np.argwhere(cfar_flags)
|
||||||
|
cfar_det_list_file = os.path.join(output_dir, "fullchain_cfar_detections.txt")
|
||||||
|
with open(cfar_det_list_file, 'w') as f:
|
||||||
|
f.write(f"# AERIS-10 Full-Chain CFAR Detection List\n")
|
||||||
|
f.write(f"# Chain: decim -> MTI -> Doppler -> DC notch(w={DC_NOTCH_WIDTH}) -> CA-CFAR\n")
|
||||||
|
f.write(f"# CFAR: guard={CFAR_GUARD}, train={CFAR_TRAIN}, alpha=0x{CFAR_ALPHA:02X}, mode={CFAR_MODE}\n")
|
||||||
|
f.write(f"# Format: range_bin doppler_bin magnitude threshold\n")
|
||||||
|
for det in cfar_detections:
|
||||||
|
r, d = det
|
||||||
|
f.write(f"{r} {d} {cfar_mag[r, d]} {cfar_thr[r, d]}\n")
|
||||||
|
print(f" Wrote {cfar_det_list_file} ({len(cfar_detections)} detections)")
|
||||||
|
|
||||||
|
# Save numpy arrays
|
||||||
|
np.save(os.path.join(output_dir, "fullchain_cfar_mag.npy"), cfar_mag)
|
||||||
|
np.save(os.path.join(output_dir, "fullchain_cfar_thr.npy"), cfar_thr)
|
||||||
|
np.save(os.path.join(output_dir, "fullchain_cfar_flags.npy"), cfar_flags)
|
||||||
|
|
||||||
# Run detection on full-chain Doppler map
|
# Run detection on full-chain Doppler map
|
||||||
print(f"\n{'=' * 72}")
|
print(f"\n{'=' * 72}")
|
||||||
print("Stage 4: Detection on full-chain Doppler map")
|
print("Stage 4: Detection on full-chain Doppler map")
|
||||||
@@ -1148,6 +1484,8 @@ def main():
|
|||||||
print(f" Detections (direct): {len(detections)} (threshold={args.threshold})")
|
print(f" Detections (direct): {len(detections)} (threshold={args.threshold})")
|
||||||
print(f" Full-chain decimator: 1024→64 peak detection")
|
print(f" Full-chain decimator: 1024→64 peak detection")
|
||||||
print(f" Full-chain detections: {len(fc_detections)} (threshold={args.threshold})")
|
print(f" Full-chain detections: {len(fc_detections)} (threshold={args.threshold})")
|
||||||
|
print(f" MTI+CFAR chain: decim → MTI → Doppler → DC notch(w={DC_NOTCH_WIDTH}) → CA-CFAR")
|
||||||
|
print(f" CFAR detections: {len(cfar_detections)} (guard={CFAR_GUARD}, train={CFAR_TRAIN}, alpha=0x{CFAR_ALPHA:02X})")
|
||||||
print(f" Hex stimulus files: {output_dir}/")
|
print(f" Hex stimulus files: {output_dir}/")
|
||||||
print(f" Ready for RTL co-simulation with Icarus Verilog")
|
print(f" Ready for RTL co-simulation with Icarus Verilog")
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
|||||||
|
# AERIS-10 Full-Chain CFAR Detection List
|
||||||
|
# Chain: decim -> MTI -> Doppler -> DC notch(w=2) -> CA-CFAR
|
||||||
|
# CFAR: guard=2, train=8, alpha=0x30, mode=CA
|
||||||
|
# Format: range_bin doppler_bin magnitude threshold
|
||||||
|
2 27 40172 38280
|
||||||
|
2 28 65534 40749
|
||||||
|
2 29 58080 31302
|
||||||
|
2 30 16565 13386
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,651 @@
|
|||||||
|
`timescale 1ns / 1ps
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tb_fullchain_mti_cfar_realdata.v
|
||||||
|
*
|
||||||
|
* Full-chain co-simulation testbench: feeds real ADI CN0566 radar data
|
||||||
|
* (post-range-FFT, 32 chirps x 1024 bins) through the complete signal
|
||||||
|
* processing pipeline:
|
||||||
|
*
|
||||||
|
* range_bin_decimator (peak detection, 1024->64)
|
||||||
|
* -> mti_canceller (2-pulse, mti_enable=1)
|
||||||
|
* -> doppler_processor_optimized (Hamming + 32-pt FFT)
|
||||||
|
* -> DC notch filter (width=2, inline logic)
|
||||||
|
* -> cfar_ca (CA mode, guard=2, train=8, alpha=0x30)
|
||||||
|
*
|
||||||
|
* and compares outputs bit-for-bit against the Python golden reference
|
||||||
|
* (golden_reference.py) at multiple checkpoints:
|
||||||
|
*
|
||||||
|
* Checkpoint 1: Decimator output matches
|
||||||
|
* Checkpoint 2: MTI canceller output matches
|
||||||
|
* Checkpoint 3: Doppler output (post-DC-notch) matches
|
||||||
|
* Checkpoint 4: CFAR magnitudes match
|
||||||
|
* Checkpoint 5: CFAR thresholds match
|
||||||
|
* Checkpoint 6: CFAR detection flags match
|
||||||
|
*
|
||||||
|
* Stimulus:
|
||||||
|
* tb/cosim/real_data/hex/fullchain_range_input.hex
|
||||||
|
* 32768 x 32-bit packed {Q[31:16], I[15:0]} -- 32 chirps x 1024 bins
|
||||||
|
*
|
||||||
|
* Golden reference files:
|
||||||
|
* fullchain_mti_ref_i.hex, fullchain_mti_ref_q.hex -- MTI output (2048)
|
||||||
|
* fullchain_notched_ref_i.hex, fullchain_notched_ref_q.hex -- DC-notched Doppler (2048)
|
||||||
|
* fullchain_cfar_mag.hex -- CFAR magnitudes (2048)
|
||||||
|
* fullchain_cfar_thr.hex -- CFAR thresholds (2048)
|
||||||
|
* fullchain_cfar_det.hex -- CFAR detection flags (2048)
|
||||||
|
*
|
||||||
|
* Pass criteria: ALL outputs match exactly.
|
||||||
|
*
|
||||||
|
* Compile:
|
||||||
|
* iverilog -Wall -DSIMULATION -g2012 \
|
||||||
|
* -o tb/tb_fullchain_mti_cfar_realdata.vvp \
|
||||||
|
* tb/tb_fullchain_mti_cfar_realdata.v \
|
||||||
|
* range_bin_decimator.v mti_canceller.v doppler_processor.v \
|
||||||
|
* xfft_32.v fft_engine.v cfar_ca.v
|
||||||
|
*
|
||||||
|
* Run from: 9_Firmware/9_2_FPGA/
|
||||||
|
* vvp tb/tb_fullchain_mti_cfar_realdata.vvp
|
||||||
|
*/
|
||||||
|
|
||||||
|
module tb_fullchain_mti_cfar_realdata;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PARAMETERS
|
||||||
|
// ============================================================================
|
||||||
|
localparam CLK_PERIOD = 10.0; // 100 MHz
|
||||||
|
localparam DOPPLER_FFT = 32;
|
||||||
|
localparam RANGE_BINS = 64;
|
||||||
|
localparam CHIRPS = 32;
|
||||||
|
localparam INPUT_BINS = 1024;
|
||||||
|
localparam DECIM_FACTOR = 16;
|
||||||
|
|
||||||
|
localparam TOTAL_INPUT_SAMPLES = CHIRPS * INPUT_BINS; // 32768
|
||||||
|
localparam TOTAL_MTI_SAMPLES = CHIRPS * RANGE_BINS; // 2048
|
||||||
|
localparam TOTAL_DOPPLER_SAMPLES = RANGE_BINS * DOPPLER_FFT; // 2048
|
||||||
|
localparam TOTAL_CFAR_CELLS = RANGE_BINS * DOPPLER_FFT; // 2048
|
||||||
|
|
||||||
|
// Generous timeout: decimator + MTI + Doppler + CFAR processing
|
||||||
|
localparam MAX_CYCLES = 3_000_000;
|
||||||
|
|
||||||
|
// DC notch width for this test
|
||||||
|
localparam DC_NOTCH_WIDTH = 3'd2;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CLOCK AND RESET
|
||||||
|
// ============================================================================
|
||||||
|
reg clk;
|
||||||
|
reg reset_n;
|
||||||
|
|
||||||
|
initial clk = 0;
|
||||||
|
always #(CLK_PERIOD / 2) clk = ~clk;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DECIMATOR SIGNALS
|
||||||
|
// ============================================================================
|
||||||
|
reg signed [15:0] decim_i_in;
|
||||||
|
reg signed [15:0] decim_q_in;
|
||||||
|
reg decim_valid_in;
|
||||||
|
|
||||||
|
wire signed [15:0] decim_i_out;
|
||||||
|
wire signed [15:0] decim_q_out;
|
||||||
|
wire decim_valid_out;
|
||||||
|
wire [5:0] decim_bin_index;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MTI CANCELLER SIGNALS
|
||||||
|
// ============================================================================
|
||||||
|
wire signed [15:0] mti_i_out;
|
||||||
|
wire signed [15:0] mti_q_out;
|
||||||
|
wire mti_valid_out;
|
||||||
|
wire [5:0] mti_bin_out;
|
||||||
|
wire mti_first_chirp;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DOPPLER SIGNALS
|
||||||
|
// ============================================================================
|
||||||
|
// Wire MTI output into Doppler input (matching RTL system_top)
|
||||||
|
wire [31:0] range_data_32bit;
|
||||||
|
wire range_data_valid;
|
||||||
|
|
||||||
|
assign range_data_32bit = {mti_q_out, mti_i_out};
|
||||||
|
assign range_data_valid = mti_valid_out;
|
||||||
|
|
||||||
|
reg new_chirp_frame;
|
||||||
|
|
||||||
|
wire [31:0] doppler_output;
|
||||||
|
wire doppler_valid;
|
||||||
|
wire [4:0] doppler_bin;
|
||||||
|
wire [5:0] range_bin;
|
||||||
|
wire processing_active;
|
||||||
|
wire frame_complete;
|
||||||
|
wire [3:0] dut_status;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DC NOTCH FILTER SIGNALS (inline, replicating radar_system_top.v logic)
|
||||||
|
// ============================================================================
|
||||||
|
wire [4:0] dop_bin_unsigned;
|
||||||
|
wire dc_notch_active;
|
||||||
|
wire [31:0] notched_doppler_data;
|
||||||
|
wire notched_doppler_valid;
|
||||||
|
wire [4:0] notched_doppler_bin;
|
||||||
|
wire [5:0] notched_range_bin;
|
||||||
|
|
||||||
|
assign dop_bin_unsigned = doppler_bin;
|
||||||
|
assign dc_notch_active = (DC_NOTCH_WIDTH != 3'd0) &&
|
||||||
|
(dop_bin_unsigned < {2'b0, DC_NOTCH_WIDTH} ||
|
||||||
|
dop_bin_unsigned > (5'd31 - {2'b0, DC_NOTCH_WIDTH} + 5'd1));
|
||||||
|
|
||||||
|
assign notched_doppler_data = dc_notch_active ? 32'd0 : doppler_output;
|
||||||
|
assign notched_doppler_valid = doppler_valid;
|
||||||
|
assign notched_doppler_bin = doppler_bin;
|
||||||
|
assign notched_range_bin = range_bin;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CFAR SIGNALS
|
||||||
|
// ============================================================================
|
||||||
|
wire cfar_detect_flag;
|
||||||
|
wire cfar_detect_valid;
|
||||||
|
wire [5:0] cfar_detect_range;
|
||||||
|
wire [4:0] cfar_detect_doppler;
|
||||||
|
wire [16:0] cfar_detect_magnitude;
|
||||||
|
wire [16:0] cfar_detect_threshold;
|
||||||
|
wire [15:0] cfar_detect_count;
|
||||||
|
wire cfar_busy;
|
||||||
|
wire [7:0] cfar_status_w;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DUT INSTANTIATION: Range Bin Decimator
|
||||||
|
// ============================================================================
|
||||||
|
range_bin_decimator #(
|
||||||
|
.INPUT_BINS(INPUT_BINS),
|
||||||
|
.OUTPUT_BINS(RANGE_BINS),
|
||||||
|
.DECIMATION_FACTOR(DECIM_FACTOR)
|
||||||
|
) range_decim (
|
||||||
|
.clk(clk),
|
||||||
|
.reset_n(reset_n),
|
||||||
|
.range_i_in(decim_i_in),
|
||||||
|
.range_q_in(decim_q_in),
|
||||||
|
.range_valid_in(decim_valid_in),
|
||||||
|
.range_i_out(decim_i_out),
|
||||||
|
.range_q_out(decim_q_out),
|
||||||
|
.range_valid_out(decim_valid_out),
|
||||||
|
.range_bin_index(decim_bin_index),
|
||||||
|
.decimation_mode(2'b01), // Peak detection mode
|
||||||
|
.start_bin(10'd0),
|
||||||
|
.watchdog_timeout()
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DUT INSTANTIATION: MTI Canceller
|
||||||
|
// ============================================================================
|
||||||
|
mti_canceller #(
|
||||||
|
.NUM_RANGE_BINS(RANGE_BINS),
|
||||||
|
.DATA_WIDTH(16)
|
||||||
|
) mti_inst (
|
||||||
|
.clk(clk),
|
||||||
|
.reset_n(reset_n),
|
||||||
|
.range_i_in(decim_i_out),
|
||||||
|
.range_q_in(decim_q_out),
|
||||||
|
.range_valid_in(decim_valid_out),
|
||||||
|
.range_bin_in(decim_bin_index),
|
||||||
|
.range_i_out(mti_i_out),
|
||||||
|
.range_q_out(mti_q_out),
|
||||||
|
.range_valid_out(mti_valid_out),
|
||||||
|
.range_bin_out(mti_bin_out),
|
||||||
|
.mti_enable(1'b1), // MTI always enabled for this test
|
||||||
|
.mti_first_chirp(mti_first_chirp)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DUT INSTANTIATION: Doppler Processor
|
||||||
|
// ============================================================================
|
||||||
|
doppler_processor_optimized doppler_proc (
|
||||||
|
.clk(clk),
|
||||||
|
.reset_n(reset_n),
|
||||||
|
.range_data(range_data_32bit),
|
||||||
|
.data_valid(range_data_valid),
|
||||||
|
.new_chirp_frame(new_chirp_frame),
|
||||||
|
.doppler_output(doppler_output),
|
||||||
|
.doppler_valid(doppler_valid),
|
||||||
|
.doppler_bin(doppler_bin),
|
||||||
|
.range_bin(range_bin),
|
||||||
|
.processing_active(processing_active),
|
||||||
|
.frame_complete(frame_complete),
|
||||||
|
.status(dut_status)
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DUT INSTANTIATION: CFAR Detector
|
||||||
|
// ============================================================================
|
||||||
|
cfar_ca cfar_inst (
|
||||||
|
.clk(clk),
|
||||||
|
.reset_n(reset_n),
|
||||||
|
.doppler_data(notched_doppler_data),
|
||||||
|
.doppler_valid(notched_doppler_valid),
|
||||||
|
.doppler_bin_in(notched_doppler_bin),
|
||||||
|
.range_bin_in(notched_range_bin),
|
||||||
|
.frame_complete(frame_complete),
|
||||||
|
.cfg_guard_cells(4'd2),
|
||||||
|
.cfg_train_cells(5'd8),
|
||||||
|
.cfg_alpha(8'h30), // Q4.4 = 3.0
|
||||||
|
.cfg_cfar_mode(2'b00), // CA-CFAR
|
||||||
|
.cfg_cfar_enable(1'b1), // CFAR enabled
|
||||||
|
.cfg_simple_threshold(16'd500),
|
||||||
|
.detect_flag(cfar_detect_flag),
|
||||||
|
.detect_valid(cfar_detect_valid),
|
||||||
|
.detect_range(cfar_detect_range),
|
||||||
|
.detect_doppler(cfar_detect_doppler),
|
||||||
|
.detect_magnitude(cfar_detect_magnitude),
|
||||||
|
.detect_threshold(cfar_detect_threshold),
|
||||||
|
.detect_count(cfar_detect_count),
|
||||||
|
.cfar_busy(cfar_busy),
|
||||||
|
.cfar_status(cfar_status_w)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Internal DUT state (for debug)
|
||||||
|
wire [2:0] decim_state = range_decim.state;
|
||||||
|
wire [2:0] doppler_state = doppler_proc.state;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// INPUT DATA MEMORY (loaded from hex file)
|
||||||
|
// ============================================================================
|
||||||
|
reg [31:0] input_mem [0:TOTAL_INPUT_SAMPLES-1];
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
$readmemh("tb/cosim/real_data/hex/fullchain_range_input.hex", input_mem);
|
||||||
|
end
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// REFERENCE DATA (loaded from hex files)
|
||||||
|
// ============================================================================
|
||||||
|
// MTI reference: 2048 x 16-bit signed (32 chirps x 64 bins, row-major)
|
||||||
|
reg signed [15:0] ref_mti_i [0:TOTAL_MTI_SAMPLES-1];
|
||||||
|
reg signed [15:0] ref_mti_q [0:TOTAL_MTI_SAMPLES-1];
|
||||||
|
|
||||||
|
// DC-notched Doppler reference: 2048 x 16-bit signed (64 range x 32 Doppler)
|
||||||
|
reg signed [15:0] ref_notched_i [0:TOTAL_DOPPLER_SAMPLES-1];
|
||||||
|
reg signed [15:0] ref_notched_q [0:TOTAL_DOPPLER_SAMPLES-1];
|
||||||
|
|
||||||
|
// CFAR reference: magnitude, threshold, detection flags
|
||||||
|
reg [16:0] ref_cfar_mag [0:TOTAL_CFAR_CELLS-1];
|
||||||
|
reg [16:0] ref_cfar_thr [0:TOTAL_CFAR_CELLS-1];
|
||||||
|
reg [0:0] ref_cfar_det [0:TOTAL_CFAR_CELLS-1];
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
$readmemh("tb/cosim/real_data/hex/fullchain_mti_ref_i.hex", ref_mti_i);
|
||||||
|
$readmemh("tb/cosim/real_data/hex/fullchain_mti_ref_q.hex", ref_mti_q);
|
||||||
|
$readmemh("tb/cosim/real_data/hex/fullchain_notched_ref_i.hex", ref_notched_i);
|
||||||
|
$readmemh("tb/cosim/real_data/hex/fullchain_notched_ref_q.hex", ref_notched_q);
|
||||||
|
$readmemh("tb/cosim/real_data/hex/fullchain_cfar_mag.hex", ref_cfar_mag);
|
||||||
|
$readmemh("tb/cosim/real_data/hex/fullchain_cfar_thr.hex", ref_cfar_thr);
|
||||||
|
$readmemh("tb/cosim/real_data/hex/fullchain_cfar_det.hex", ref_cfar_det);
|
||||||
|
end
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MTI OUTPUT CAPTURE
|
||||||
|
// ============================================================================
|
||||||
|
integer mti_out_count;
|
||||||
|
reg signed [15:0] mti_cap_i [0:TOTAL_MTI_SAMPLES-1];
|
||||||
|
reg signed [15:0] mti_cap_q [0:TOTAL_MTI_SAMPLES-1];
|
||||||
|
|
||||||
|
always @(posedge clk or negedge reset_n) begin
|
||||||
|
if (!reset_n) begin
|
||||||
|
mti_out_count <= 0;
|
||||||
|
end else if (mti_valid_out) begin
|
||||||
|
if (mti_out_count < TOTAL_MTI_SAMPLES) begin
|
||||||
|
mti_cap_i[mti_out_count] <= mti_i_out;
|
||||||
|
mti_cap_q[mti_out_count] <= mti_q_out;
|
||||||
|
end
|
||||||
|
mti_out_count <= mti_out_count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DOPPLER OUTPUT CAPTURE (post DC-notch)
|
||||||
|
// ============================================================================
|
||||||
|
reg signed [15:0] dop_cap_i [0:TOTAL_DOPPLER_SAMPLES-1];
|
||||||
|
reg signed [15:0] dop_cap_q [0:TOTAL_DOPPLER_SAMPLES-1];
|
||||||
|
reg [5:0] dop_cap_rbin [0:TOTAL_DOPPLER_SAMPLES-1];
|
||||||
|
reg [4:0] dop_cap_dbin [0:TOTAL_DOPPLER_SAMPLES-1];
|
||||||
|
integer dop_out_count;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CFAR OUTPUT CAPTURE
|
||||||
|
// ============================================================================
|
||||||
|
reg [16:0] cfar_cap_mag [0:TOTAL_CFAR_CELLS-1];
|
||||||
|
reg [16:0] cfar_cap_thr [0:TOTAL_CFAR_CELLS-1];
|
||||||
|
reg [0:0] cfar_cap_det [0:TOTAL_CFAR_CELLS-1];
|
||||||
|
reg [5:0] cfar_cap_rbin [0:TOTAL_CFAR_CELLS-1];
|
||||||
|
reg [4:0] cfar_cap_dbin [0:TOTAL_CFAR_CELLS-1];
|
||||||
|
integer cfar_out_count;
|
||||||
|
integer cfar_det_count;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PASS / FAIL TRACKING
|
||||||
|
// ============================================================================
|
||||||
|
integer pass_count, fail_count, test_count;
|
||||||
|
|
||||||
|
task check;
|
||||||
|
input cond;
|
||||||
|
input [511:0] label;
|
||||||
|
begin
|
||||||
|
test_count = test_count + 1;
|
||||||
|
if (cond) begin
|
||||||
|
pass_count = pass_count + 1;
|
||||||
|
end else begin
|
||||||
|
$display(" [FAIL] %0s", label);
|
||||||
|
fail_count = fail_count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MAIN TEST SEQUENCE
|
||||||
|
// ============================================================================
|
||||||
|
integer i, chirp, sample_idx, cycle_count;
|
||||||
|
integer n_exact, n_within_tol;
|
||||||
|
integer max_err_i, max_err_q;
|
||||||
|
integer abs_diff_i, abs_diff_q;
|
||||||
|
reg signed [31:0] diff_i, diff_q;
|
||||||
|
integer mismatches_printed;
|
||||||
|
reg [31:0] packed_iq;
|
||||||
|
integer cfar_ref_idx;
|
||||||
|
integer cfar_mag_mismatches, cfar_thr_mismatches, cfar_det_mismatches;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
// ---- Init ----
|
||||||
|
pass_count = 0;
|
||||||
|
fail_count = 0;
|
||||||
|
test_count = 0;
|
||||||
|
dop_out_count = 0;
|
||||||
|
cfar_out_count = 0;
|
||||||
|
cfar_det_count = 0;
|
||||||
|
decim_i_in = 0;
|
||||||
|
decim_q_in = 0;
|
||||||
|
decim_valid_in = 0;
|
||||||
|
new_chirp_frame = 0;
|
||||||
|
reset_n = 0;
|
||||||
|
|
||||||
|
// ---- Reset ----
|
||||||
|
#(CLK_PERIOD * 10);
|
||||||
|
reset_n = 1;
|
||||||
|
#(CLK_PERIOD * 5);
|
||||||
|
|
||||||
|
$display("============================================================");
|
||||||
|
$display(" Full-Chain Real-Data Co-Simulation (MTI + CFAR)");
|
||||||
|
$display(" range_bin_decimator (peak, 1024->64)");
|
||||||
|
$display(" -> mti_canceller (2-pulse, enable=1)");
|
||||||
|
$display(" -> doppler_processor_optimized (Hamming + 32-pt FFT)");
|
||||||
|
$display(" -> DC notch filter (width=%0d)", DC_NOTCH_WIDTH);
|
||||||
|
$display(" -> cfar_ca (CA, guard=2, train=8, alpha=0x30)");
|
||||||
|
$display(" ADI CN0566 Phaser 10.525 GHz X-band FMCW");
|
||||||
|
$display(" Input: %0d chirps x %0d range FFT bins = %0d samples", CHIRPS, INPUT_BINS, TOTAL_INPUT_SAMPLES);
|
||||||
|
$display(" Expected: %0d MTI outputs, %0d Doppler outputs, %0d CFAR cells", TOTAL_MTI_SAMPLES, TOTAL_DOPPLER_SAMPLES, TOTAL_CFAR_CELLS);
|
||||||
|
$display("============================================================");
|
||||||
|
|
||||||
|
// ---- Debug: check hex files loaded ----
|
||||||
|
$display(" input_mem[0] = %08h", input_mem[0]);
|
||||||
|
$display(" input_mem[32767] = %08h", input_mem[32767]);
|
||||||
|
$display(" ref_mti_i[0]=%04h, ref_mti_q[0]=%04h", ref_mti_i[0], ref_mti_q[0]);
|
||||||
|
$display(" ref_notched_i[0]=%04h, ref_notched_q[0]=%04h", ref_notched_i[0], ref_notched_q[0]);
|
||||||
|
$display(" ref_cfar_mag[0]=%05h, ref_cfar_thr[0]=%05h, ref_cfar_det[0]=%01h", ref_cfar_mag[0], ref_cfar_thr[0], ref_cfar_det[0]);
|
||||||
|
|
||||||
|
// ---- Check 1: DUTs start in expected states ----
|
||||||
|
check(decim_state == 3'd0, "Decimator starts in ST_IDLE");
|
||||||
|
check(doppler_state == 3'b000, "Doppler starts in S_IDLE");
|
||||||
|
|
||||||
|
// ---- Pulse new_chirp_frame to start Doppler accumulation ----
|
||||||
|
@(posedge clk);
|
||||||
|
new_chirp_frame <= 1;
|
||||||
|
@(posedge clk);
|
||||||
|
@(posedge clk);
|
||||||
|
new_chirp_frame <= 0;
|
||||||
|
@(posedge clk);
|
||||||
|
|
||||||
|
// ---- Feed input data: 32 chirps x 1024 range bins ----
|
||||||
|
$display("\n--- Feeding %0d chirps x %0d bins = %0d samples ---", CHIRPS, INPUT_BINS, TOTAL_INPUT_SAMPLES);
|
||||||
|
|
||||||
|
for (chirp = 0; chirp < CHIRPS; chirp = chirp + 1) begin
|
||||||
|
for (i = 0; i < INPUT_BINS; i = i + 1) begin
|
||||||
|
@(posedge clk);
|
||||||
|
sample_idx = chirp * INPUT_BINS + i;
|
||||||
|
packed_iq = input_mem[sample_idx];
|
||||||
|
decim_i_in <= packed_iq[15:0];
|
||||||
|
decim_q_in <= packed_iq[31:16];
|
||||||
|
decim_valid_in <= 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
@(posedge clk);
|
||||||
|
decim_valid_in <= 0;
|
||||||
|
decim_i_in <= 0;
|
||||||
|
decim_q_in <= 0;
|
||||||
|
|
||||||
|
// Wait for decimator to return to IDLE
|
||||||
|
cycle_count = 0;
|
||||||
|
while (decim_state != 3'd0 && cycle_count < 200) begin
|
||||||
|
@(posedge clk);
|
||||||
|
cycle_count = cycle_count + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
if (chirp < 3 || chirp == CHIRPS - 1) begin
|
||||||
|
$display(" Chirp %0d: IDLE after %0d extra cycles, mti_out=%0d", chirp, cycle_count, mti_out_count);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Allow a few extra cycles for the last MTI output to propagate
|
||||||
|
repeat (10) @(posedge clk);
|
||||||
|
|
||||||
|
$display(" All input fed. MTI outputs: %0d (expected %0d)", mti_out_count, TOTAL_MTI_SAMPLES);
|
||||||
|
|
||||||
|
// ---- Check: MTI produced correct number of outputs ----
|
||||||
|
check(mti_out_count == TOTAL_MTI_SAMPLES, "MTI output count == 2048");
|
||||||
|
|
||||||
|
// ---- Wait for Doppler processing to complete ----
|
||||||
|
$display("\n--- Waiting for Doppler to process and emit %0d outputs ---", TOTAL_DOPPLER_SAMPLES);
|
||||||
|
|
||||||
|
cycle_count = 0;
|
||||||
|
while (dop_out_count < TOTAL_DOPPLER_SAMPLES && cycle_count < MAX_CYCLES) begin
|
||||||
|
@(posedge clk);
|
||||||
|
cycle_count = cycle_count + 1;
|
||||||
|
|
||||||
|
if (doppler_valid) begin
|
||||||
|
// Capture DC-notched Doppler output
|
||||||
|
dop_cap_i[dop_out_count] = notched_doppler_data[15:0];
|
||||||
|
dop_cap_q[dop_out_count] = notched_doppler_data[31:16];
|
||||||
|
dop_cap_rbin[dop_out_count] = notched_range_bin;
|
||||||
|
dop_cap_dbin[dop_out_count] = notched_doppler_bin;
|
||||||
|
dop_out_count = dop_out_count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
$display(" Collected %0d Doppler outputs in %0d cycles", dop_out_count, cycle_count);
|
||||||
|
check(dop_out_count == TOTAL_DOPPLER_SAMPLES, "Doppler output count == 2048");
|
||||||
|
check(cycle_count < MAX_CYCLES, "Doppler processing within timeout");
|
||||||
|
|
||||||
|
// ---- Wait for CFAR to complete ----
|
||||||
|
$display("\n--- Waiting for CFAR to process %0d cells ---", TOTAL_CFAR_CELLS);
|
||||||
|
|
||||||
|
cycle_count = 0;
|
||||||
|
while (cfar_out_count < TOTAL_CFAR_CELLS && cycle_count < MAX_CYCLES) begin
|
||||||
|
@(posedge clk);
|
||||||
|
cycle_count = cycle_count + 1;
|
||||||
|
|
||||||
|
if (cfar_detect_valid) begin
|
||||||
|
cfar_cap_mag[cfar_out_count] = cfar_detect_magnitude;
|
||||||
|
cfar_cap_thr[cfar_out_count] = cfar_detect_threshold;
|
||||||
|
cfar_cap_det[cfar_out_count] = cfar_detect_flag;
|
||||||
|
cfar_cap_rbin[cfar_out_count] = cfar_detect_range;
|
||||||
|
cfar_cap_dbin[cfar_out_count] = cfar_detect_doppler;
|
||||||
|
if (cfar_detect_flag) cfar_det_count = cfar_det_count + 1;
|
||||||
|
cfar_out_count = cfar_out_count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
$display(" Collected %0d CFAR outputs in %0d cycles (%0d detections)", cfar_out_count, cycle_count, cfar_det_count);
|
||||||
|
check(cfar_out_count == TOTAL_CFAR_CELLS, "CFAR output count == 2048");
|
||||||
|
check(cycle_count < MAX_CYCLES, "CFAR processing within timeout");
|
||||||
|
|
||||||
|
// ==================================================================
|
||||||
|
// CHECKPOINT 1: MTI OUTPUT COMPARISON
|
||||||
|
// ==================================================================
|
||||||
|
$display("");
|
||||||
|
$display("--- Checkpoint 1: MTI canceller output vs golden reference ---");
|
||||||
|
|
||||||
|
max_err_i = 0;
|
||||||
|
max_err_q = 0;
|
||||||
|
n_exact = 0;
|
||||||
|
mismatches_printed = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < TOTAL_MTI_SAMPLES; i = i + 1) begin
|
||||||
|
diff_i = mti_cap_i[i] - ref_mti_i[i];
|
||||||
|
diff_q = mti_cap_q[i] - ref_mti_q[i];
|
||||||
|
abs_diff_i = (diff_i < 0) ? -diff_i : diff_i;
|
||||||
|
abs_diff_q = (diff_q < 0) ? -diff_q : diff_q;
|
||||||
|
|
||||||
|
if (abs_diff_i > max_err_i) max_err_i = abs_diff_i;
|
||||||
|
if (abs_diff_q > max_err_q) max_err_q = abs_diff_q;
|
||||||
|
|
||||||
|
if (diff_i == 0 && diff_q == 0)
|
||||||
|
n_exact = n_exact + 1;
|
||||||
|
|
||||||
|
if ((abs_diff_i > 0 || abs_diff_q > 0) && mismatches_printed < 10) begin
|
||||||
|
$display(" [%4d] chirp=%0d bin=%0d RTL=(%6d,%6d) REF=(%6d,%6d) ERR=(%4d,%4d)", i, i / RANGE_BINS, i % RANGE_BINS, $signed(mti_cap_i[i]), $signed(mti_cap_q[i]), $signed(ref_mti_i[i]), $signed(ref_mti_q[i]), diff_i, diff_q);
|
||||||
|
mismatches_printed = mismatches_printed + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
check(abs_diff_i == 0 && abs_diff_q == 0, "MTI output bin match");
|
||||||
|
end
|
||||||
|
|
||||||
|
$display(" MTI: exact=%0d/%0d, max_err I=%0d Q=%0d", n_exact, TOTAL_MTI_SAMPLES, max_err_i, max_err_q);
|
||||||
|
|
||||||
|
// ==================================================================
|
||||||
|
// CHECKPOINT 2: DC-NOTCHED DOPPLER OUTPUT COMPARISON
|
||||||
|
// ==================================================================
|
||||||
|
$display("");
|
||||||
|
$display("--- Checkpoint 2: DC-notched Doppler output vs golden reference ---");
|
||||||
|
|
||||||
|
max_err_i = 0;
|
||||||
|
max_err_q = 0;
|
||||||
|
n_exact = 0;
|
||||||
|
mismatches_printed = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < TOTAL_DOPPLER_SAMPLES; i = i + 1) begin
|
||||||
|
diff_i = dop_cap_i[i] - ref_notched_i[i];
|
||||||
|
diff_q = dop_cap_q[i] - ref_notched_q[i];
|
||||||
|
abs_diff_i = (diff_i < 0) ? -diff_i : diff_i;
|
||||||
|
abs_diff_q = (diff_q < 0) ? -diff_q : diff_q;
|
||||||
|
|
||||||
|
if (abs_diff_i > max_err_i) max_err_i = abs_diff_i;
|
||||||
|
if (abs_diff_q > max_err_q) max_err_q = abs_diff_q;
|
||||||
|
|
||||||
|
if (diff_i == 0 && diff_q == 0)
|
||||||
|
n_exact = n_exact + 1;
|
||||||
|
|
||||||
|
if ((abs_diff_i > 0 || abs_diff_q > 0) && mismatches_printed < 10) begin
|
||||||
|
$display(" [%4d] rbin=%2d dbin=%2d RTL=(%6d,%6d) REF=(%6d,%6d) ERR=(%4d,%4d)", i, dop_cap_rbin[i], dop_cap_dbin[i], $signed(dop_cap_i[i]), $signed(dop_cap_q[i]), $signed(ref_notched_i[i]), $signed(ref_notched_q[i]), diff_i, diff_q);
|
||||||
|
mismatches_printed = mismatches_printed + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
check(abs_diff_i == 0 && abs_diff_q == 0, "Notched Doppler output bin match");
|
||||||
|
end
|
||||||
|
|
||||||
|
$display(" Notched Doppler: exact=%0d/%0d, max_err I=%0d Q=%0d", n_exact, TOTAL_DOPPLER_SAMPLES, max_err_i, max_err_q);
|
||||||
|
|
||||||
|
// ==================================================================
|
||||||
|
// CHECKPOINT 3: CFAR MAGNITUDE, THRESHOLD, AND DETECTION COMPARISON
|
||||||
|
// ==================================================================
|
||||||
|
$display("");
|
||||||
|
$display("--- Checkpoint 3: CFAR output vs golden reference ---");
|
||||||
|
|
||||||
|
cfar_mag_mismatches = 0;
|
||||||
|
cfar_thr_mismatches = 0;
|
||||||
|
cfar_det_mismatches = 0;
|
||||||
|
mismatches_printed = 0;
|
||||||
|
|
||||||
|
// The CFAR outputs cells in Doppler-column order:
|
||||||
|
// column 0 (dbin=0): range bins 0..63
|
||||||
|
// column 1 (dbin=1): range bins 0..63
|
||||||
|
// ...
|
||||||
|
// But golden reference is in row-major order (rbin, dbin).
|
||||||
|
// We need to map CFAR output index to golden reference index.
|
||||||
|
for (i = 0; i < cfar_out_count; i = i + 1) begin
|
||||||
|
// CFAR output: range=cfar_cap_rbin[i], doppler=cfar_cap_dbin[i]
|
||||||
|
// Golden ref index: rbin * 32 + dbin (row-major)
|
||||||
|
cfar_ref_idx = cfar_cap_rbin[i] * DOPPLER_FFT + cfar_cap_dbin[i];
|
||||||
|
|
||||||
|
if (cfar_cap_mag[i] != ref_cfar_mag[cfar_ref_idx]) begin
|
||||||
|
cfar_mag_mismatches = cfar_mag_mismatches + 1;
|
||||||
|
if (mismatches_printed < 10) begin
|
||||||
|
$display(" MAG[%4d] rbin=%2d dbin=%2d RTL=%0d REF=%0d", i, cfar_cap_rbin[i], cfar_cap_dbin[i], cfar_cap_mag[i], ref_cfar_mag[cfar_ref_idx]);
|
||||||
|
mismatches_printed = mismatches_printed + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (cfar_cap_thr[i] != ref_cfar_thr[cfar_ref_idx]) begin
|
||||||
|
cfar_thr_mismatches = cfar_thr_mismatches + 1;
|
||||||
|
if (mismatches_printed < 10) begin
|
||||||
|
$display(" THR[%4d] rbin=%2d dbin=%2d RTL=%0d REF=%0d", i, cfar_cap_rbin[i], cfar_cap_dbin[i], cfar_cap_thr[i], ref_cfar_thr[cfar_ref_idx]);
|
||||||
|
mismatches_printed = mismatches_printed + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (cfar_cap_det[i] != ref_cfar_det[cfar_ref_idx]) begin
|
||||||
|
cfar_det_mismatches = cfar_det_mismatches + 1;
|
||||||
|
if (mismatches_printed < 10) begin
|
||||||
|
$display(" DET[%4d] rbin=%2d dbin=%2d RTL=%0d REF=%0d (mag=%0d thr=%0d)", i, cfar_cap_rbin[i], cfar_cap_dbin[i], cfar_cap_det[i], ref_cfar_det[cfar_ref_idx], cfar_cap_mag[i], cfar_cap_thr[i]);
|
||||||
|
mismatches_printed = mismatches_printed + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Per-cell pass/fail for CFAR
|
||||||
|
for (i = 0; i < cfar_out_count; i = i + 1) begin
|
||||||
|
cfar_ref_idx = cfar_cap_rbin[i] * DOPPLER_FFT + cfar_cap_dbin[i];
|
||||||
|
check(cfar_cap_mag[i] == ref_cfar_mag[cfar_ref_idx], "CFAR magnitude match");
|
||||||
|
check(cfar_cap_thr[i] == ref_cfar_thr[cfar_ref_idx], "CFAR threshold match");
|
||||||
|
check(cfar_cap_det[i] == ref_cfar_det[cfar_ref_idx], "CFAR detection flag match");
|
||||||
|
end
|
||||||
|
|
||||||
|
$display(" CFAR mag mismatches: %0d / %0d", cfar_mag_mismatches, cfar_out_count);
|
||||||
|
$display(" CFAR thr mismatches: %0d / %0d", cfar_thr_mismatches, cfar_out_count);
|
||||||
|
$display(" CFAR det mismatches: %0d / %0d", cfar_det_mismatches, cfar_out_count);
|
||||||
|
$display(" CFAR total detections: RTL=%0d", cfar_det_count);
|
||||||
|
|
||||||
|
// ==================================================================
|
||||||
|
// SUMMARY
|
||||||
|
// ==================================================================
|
||||||
|
$display("");
|
||||||
|
$display("============================================================");
|
||||||
|
$display(" SUMMARY: Full-Chain Real-Data Co-Simulation (MTI + CFAR)");
|
||||||
|
$display("============================================================");
|
||||||
|
$display(" Chain: decim(peak) -> MTI -> Doppler -> DC notch(w=%0d) -> CFAR(CA)", DC_NOTCH_WIDTH);
|
||||||
|
$display(" Input samples: %0d (%0d chirps x %0d bins)", TOTAL_INPUT_SAMPLES, CHIRPS, INPUT_BINS);
|
||||||
|
$display(" MTI outputs: %0d (expected %0d)", mti_out_count, TOTAL_MTI_SAMPLES);
|
||||||
|
$display(" Doppler outputs: %0d (expected %0d)", dop_out_count, TOTAL_DOPPLER_SAMPLES);
|
||||||
|
$display(" CFAR outputs: %0d (expected %0d)", cfar_out_count, TOTAL_CFAR_CELLS);
|
||||||
|
$display(" CFAR detections: %0d", cfar_det_count);
|
||||||
|
$display(" Pass: %0d Fail: %0d Total: %0d", pass_count, fail_count, test_count);
|
||||||
|
$display("============================================================");
|
||||||
|
|
||||||
|
if (fail_count == 0)
|
||||||
|
$display("RESULT: ALL TESTS PASSED (%0d/%0d)", pass_count, test_count);
|
||||||
|
else
|
||||||
|
$display("RESULT: %0d TESTS FAILED", fail_count);
|
||||||
|
|
||||||
|
$display("============================================================");
|
||||||
|
|
||||||
|
#(CLK_PERIOD * 10);
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// WATCHDOG
|
||||||
|
// ============================================================================
|
||||||
|
initial begin
|
||||||
|
#(CLK_PERIOD * MAX_CYCLES * 2);
|
||||||
|
$display("[TIMEOUT] Simulation exceeded %0d cycles -- aborting", MAX_CYCLES * 2);
|
||||||
|
$display("SOME TESTS FAILED");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
Reference in New Issue
Block a user