Phase 1 hardware bring-up prep: ILA debug probes, CDC waivers, programming scripts

- Rename latency_buffer_2159 -> latency_buffer (module + file + all refs)
- Add CDC waivers for 5 verified false-positive criticals to XDC
- Add ILA debug probe insertion script (4 cores, 126 probe bits, 2 clock domains)
- Add FPGA programming script (7-step flow with DONE pin verification)
- Add ILA capture script (4 scenarios + health check, CSV export)
- Add debug_ila.xdc with MARK_DEBUG fallback attributes
- Full regression clean: 13/13 suites, 266/266 checks, 2048/2048 golden match
This commit is contained in:
Jason
2026-03-18 01:28:42 +02:00
parent 254c0e6f03
commit f6877aab64
9 changed files with 1988 additions and 7 deletions
@@ -0,0 +1,203 @@
################################################################################
# debug_ila.xdc
#
# AERIS-10 Radar FPGA — mark_debug Constraints for ILA Probe Signals
# Target: XC7A200T-2FBG484I
#
# ALTERNATIVE APPROACH: If the post-synthesis ILA insertion script
# (insert_ila_probes.tcl) encounters net-name resolution issues, add this
# XDC to the Vivado project *before* synthesis. The mark_debug attributes
# will preserve the nets through optimization and make them available for
# ILA insertion in the Setup Debug wizard or via TCL.
#
# Usage:
# 1. Add this file to the Vivado project as a constraint source
# 2. Re-run synthesis (nets will be preserved with MARK_DEBUG)
# 3. Use Vivado GUI: Flow > Set Up Debug, or run insert_ila_probes.tcl
#
# NOTE: mark_debug must be applied to RTL-level signal names. After
# synthesis, Vivado will propagate the attribute to the corresponding
# netlist nets regardless of renaming or flattening.
################################################################################
# ==============================================================================
# ILA 0 — ADC Capture (400 MHz domain)
#
# Raw ADC samples from the AD9484 CMOS interface inside the receiver.
# 8-bit data bus + valid strobe. Clocked at 400 MHz (adc_dco_p derived).
# ==============================================================================
# ADC raw data bus [7:0]
set_property MARK_DEBUG true [get_nets {rx_inst/adc/adc_data_cmos[0]}]
set_property MARK_DEBUG true [get_nets {rx_inst/adc/adc_data_cmos[1]}]
set_property MARK_DEBUG true [get_nets {rx_inst/adc/adc_data_cmos[2]}]
set_property MARK_DEBUG true [get_nets {rx_inst/adc/adc_data_cmos[3]}]
set_property MARK_DEBUG true [get_nets {rx_inst/adc/adc_data_cmos[4]}]
set_property MARK_DEBUG true [get_nets {rx_inst/adc/adc_data_cmos[5]}]
set_property MARK_DEBUG true [get_nets {rx_inst/adc/adc_data_cmos[6]}]
set_property MARK_DEBUG true [get_nets {rx_inst/adc/adc_data_cmos[7]}]
# ADC data valid
set_property MARK_DEBUG true [get_nets {rx_inst/adc/adc_valid}]
# ==============================================================================
# ILA 1 — DDC Output (100 MHz domain)
#
# Digital down-converter baseband I/Q outputs after CIC + FIR decimation.
# 18-bit I + 18-bit Q + valid strobe. Clocked at 100 MHz (clk_100m_buf).
# ==============================================================================
# DDC I-channel [17:0]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[0]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[1]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[2]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[3]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[4]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[5]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[6]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[7]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[8]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[9]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[10]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[11]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[12]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[13]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[14]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[15]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[16]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_i[17]}]
# DDC Q-channel [17:0]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[0]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[1]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[2]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[3]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[4]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[5]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[6]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[7]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[8]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[9]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[10]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[11]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[12]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[13]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[14]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[15]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[16]}]
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_out_q[17]}]
# DDC valid strobe
set_property MARK_DEBUG true [get_nets {rx_inst/ddc_valid_i}]
# ==============================================================================
# ILA 2 — Matched Filter Output (100 MHz domain)
#
# Pulse-compression output from the multi-segment matched filter.
# 16-bit I + 16-bit Q + valid + 2-bit segment index.
# ==============================================================================
# Matched filter I-channel [15:0]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[0]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[1]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[2]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[3]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[4]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[5]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[6]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[7]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[8]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[9]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[10]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[11]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[12]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[13]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[14]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_i_w[15]}]
# Matched filter Q-channel [15:0]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[0]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[1]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[2]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[3]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[4]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[5]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[6]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[7]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[8]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[9]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[10]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[11]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[12]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[13]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[14]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_q_w[15]}]
# Matched filter valid
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/pc_valid_w}]
# Matched filter segment request [1:0]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/segment_request[0]}]
set_property MARK_DEBUG true [get_nets {rx_inst/mf_dual/segment_request[1]}]
# ==============================================================================
# ILA 3 — Doppler Processor Output (100 MHz domain)
#
# Range-Doppler map output from FFT-based Doppler processor.
# 32-bit spectrum + valid + 5-bit Doppler bin + 6-bit range bin + frame sync.
# ==============================================================================
# Doppler output spectrum [31:0]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[0]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[1]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[2]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[3]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[4]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[5]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[6]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[7]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[8]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[9]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[10]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[11]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[12]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[13]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[14]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[15]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[16]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[17]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[18]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[19]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[20]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[21]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[22]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[23]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[24]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[25]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[26]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[27]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[28]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[29]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[30]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_output[31]}]
# Doppler valid
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_valid}]
# Doppler bin index [4:0]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_bin[0]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_bin[1]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_bin[2]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_bin[3]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/doppler_bin[4]}]
# Range bin index [5:0]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/range_bin[0]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/range_bin[1]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/range_bin[2]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/range_bin[3]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/range_bin[4]}]
set_property MARK_DEBUG true [get_nets {rx_inst/doppler_proc/range_bin[5]}]
# Frame synchronization pulse
set_property MARK_DEBUG true [get_nets {rx_inst/new_frame_pulse}]
@@ -696,6 +696,53 @@ set_property -quiet IOB TRUE [get_cells -hierarchical -filter {NAME =~ *usb_inst
# -from [get_cells -hierarchical -filter {NAME =~ *cic_*/integrator_*_dsp}] \ # -from [get_cells -hierarchical -filter {NAME =~ *cic_*/integrator_*_dsp}] \
# -to [get_cells -hierarchical -filter {NAME =~ *cic_*/integrator_*_dsp}] # -to [get_cells -hierarchical -filter {NAME =~ *cic_*/integrator_*_dsp}]
# ============================================================================
# CDC WAIVERS — Verified False Positives (Build 13 Freeze Candidate)
# ============================================================================
# These 5 CDC critical warnings were analyzed during pre-hardware audit.
# All are structurally safe and do not represent real metastability risks.
# See project documentation for detailed justification of each waiver.
#
# Waiver 1: CDC-11 — 100MHz reset_sync → 400MHz ADC reset synchronizer
# Standard async-assert/sync-deassert pattern. ASYNC_REG is applied on
# the destination synchronizer chain. Reset is held for many source cycles.
create_waiver -type CDC -id CDC-11 \
-from [get_pins -quiet -hierarchical -filter {NAME =~ *reset_sync_reg[1]/C}] \
-to [get_pins -quiet -hierarchical -filter {NAME =~ *rx_inst/adc/reset_sync_400m_reg[0]/CLR}] \
-description "Reset synchronizer 100M->400M: async-assert/sync-deassert, ASYNC_REG applied"
# Waiver 2: CDC-7 — 100MHz reset_sync → DDC active-high reset PRE
# Active-high derived reset uses PRE (preset). PRE is the safe async
# direction for this reset polarity. Parent chain has ASYNC_REG.
create_waiver -type CDC -id CDC-7 \
-from [get_pins -quiet -hierarchical -filter {NAME =~ *reset_sync_reg[1]/C}] \
-to [get_pins -quiet -hierarchical -filter {NAME =~ *rx_inst/ddc/reset_400m_reg/PRE}] \
-description "DDC active-high reset via PRE: safe async direction, ASYNC_REG on parent chain"
# Waiver 3: CDC-11 — 100MHz reset_sync → DDC 400MHz reset synchronizer
# Same pattern as Waiver 1, different destination module (DDC vs ADC).
create_waiver -type CDC -id CDC-11 \
-from [get_pins -quiet -hierarchical -filter {NAME =~ *reset_sync_reg[1]/C}] \
-to [get_pins -quiet -hierarchical -filter {NAME =~ *rx_inst/ddc/reset_sync_400m_reg[0]/CLR}] \
-description "Reset synchronizer 100M->400M in DDC: async-assert/sync-deassert, ASYNC_REG applied"
# Waiver 4: CDC-11 — doppler_valid fan-out to USB doppler_valid_sync
# Single rx_doppler_valid register fans out to two independent 2-stage
# synchronizers in usb_data_interface. Both sync chains have ASYNC_REG.
# The fan-out is covered by set_false_path (clk_100m ↔ ft601_clk_in).
create_waiver -type CDC -id CDC-11 \
-from [get_pins -quiet -hierarchical -filter {NAME =~ *doppler_valid_reg/C}] \
-to [get_pins -quiet -hierarchical -filter {NAME =~ *usb_inst/doppler_valid_sync_reg[0]/D}] \
-description "doppler_valid CDC fan-out to USB sync chain 1: ASYNC_REG + false_path applied"
# Waiver 5: CDC-11 — doppler_valid fan-out to USB range_valid_sync
# Second fan-out endpoint of the same doppler_valid signal. Same
# justification as Waiver 4.
create_waiver -type CDC -id CDC-11 \
-from [get_pins -quiet -hierarchical -filter {NAME =~ *doppler_valid_reg/C}] \
-to [get_pins -quiet -hierarchical -filter {NAME =~ *usb_inst/range_valid_sync_reg[0]/D}] \
-description "doppler_valid CDC fan-out to USB sync chain 2: ASYNC_REG + false_path applied"
# ============================================================================ # ============================================================================
# END OF CONSTRAINTS # END OF CONSTRAINTS
# ============================================================================ # ============================================================================
@@ -1,7 +1,9 @@
`timescale 1ns / 1ps `timescale 1ns / 1ps
// latency_buffer_2159_fixed.v // latency_buffer.v — Parameterized BRAM-based latency/delay buffer
module latency_buffer_2159 #( // Renamed from latency_buffer_2159 to latency_buffer (module name was
// inconsistent with the actual LATENCY=3187 parameter).
module latency_buffer #(
parameter DATA_WIDTH = 32, parameter DATA_WIDTH = 32,
parameter LATENCY = 3187 parameter LATENCY = 3187
) ( ) (
+1 -1
View File
@@ -192,7 +192,7 @@ end
wire [15:0] delayed_ref_i, delayed_ref_q; wire [15:0] delayed_ref_i, delayed_ref_q;
wire mem_ready_delayed; wire mem_ready_delayed;
latency_buffer_2159 #( latency_buffer #(
.DATA_WIDTH(32), // 16-bit I + 16-bit Q .DATA_WIDTH(32), // 16-bit I + 16-bit Q
.LATENCY(3187) .LATENCY(3187)
) ref_latency_buffer ( ) ref_latency_buffer (
+797
View File
@@ -0,0 +1,797 @@
# ila_capture.tcl
# AERIS-10 Radar ILA Trigger Setup and Data Capture
# Target FPGA: XC7A200T-2FBG484I (Artix-7)
#
# Captures data from 4 ILA checkpoints in the radar signal processing chain:
# Scenario 1 (adc) — Raw ADC samples at 400 MHz
# Scenario 2 (ddc) — DDC I/Q output at 100 MHz
# Scenario 3 (mf) — Matched filter range profile I/Q at 100 MHz
# Scenario 4 (doppler) — Doppler spectrum output at 100 MHz
#
# Usage:
# vivado -mode batch -source ila_capture.tcl -tclargs <scenario> [options]
#
# Scenarios: adc | ddc | mf | doppler | all | health
#
# Options:
# -server <hostname> Hardware server (default: localhost)
# -port <port> Hardware server port (default: 3121)
# -outdir <path> Output directory for CSV files (default: auto-timestamped)
# -depth <samples> Capture depth override (default: 4096)
# -timeout <seconds> Trigger timeout in seconds (default: 30)
# -immediate Use immediate trigger (free-running, no condition)
# -ltx <path> Debug probes file path (overrides default)
#
# Examples:
# vivado -mode batch -source ila_capture.tcl -tclargs adc
# vivado -mode batch -source ila_capture.tcl -tclargs all -immediate
# vivado -mode batch -source ila_capture.tcl -tclargs health
# vivado -mode batch -source ila_capture.tcl -tclargs ddc -timeout 60 -outdir /tmp/captures
# ============================================================================
# DEFAULTS
# ============================================================================
set default_server "localhost"
set default_port 3121
set default_ltx "/home/jason-stone/PLFM_RADAR_work/vivado_project/bitstream/radar_system_top.ltx"
set default_depth 4096
set default_timeout 30
# ============================================================================
# ILA CONFIGURATION TABLE
# ============================================================================
# Each entry: {ila_instance_name trigger_signal trigger_edge clock_mhz description csv_filename}
array set ila_config {
adc {
ila_name "hw_ila_1"
trigger_net "radar_system_top/rx_inst/adc_if/adc_valid"
trigger_val "R"
clock_mhz 400
description "Raw ADC Samples (400 MHz)"
csv_file "adc_capture.csv"
}
ddc {
ila_name "hw_ila_2"
trigger_net "radar_system_top/rx_inst/ddc_inst/ddc_valid"
trigger_val "R"
clock_mhz 100
description "DDC I/Q Output (100 MHz)"
csv_file "ddc_capture.csv"
}
mf {
ila_name "hw_ila_3"
trigger_net "radar_system_top/rx_inst/mf_chain/mf_valid"
trigger_val "R"
clock_mhz 100
description "Matched Filter Range Profile I/Q (100 MHz)"
csv_file "mf_capture.csv"
}
doppler {
ila_name "hw_ila_4"
trigger_net "radar_system_top/rx_inst/doppler_proc/doppler_valid"
trigger_val "R"
clock_mhz 100
description "Doppler Spectrum Output (100 MHz)"
csv_file "doppler_capture.csv"
}
}
# ============================================================================
# UTILITY PROCEDURES
# ============================================================================
proc log_info {msg} {
puts "INFO: \[ILA\] $msg"
}
proc log_warn {msg} {
puts "WARN: \[ILA\] $msg"
}
proc log_error {msg} {
puts "ERROR: \[ILA\] $msg"
}
proc log_sep {} {
puts [string repeat "=" 72]
}
proc log_kv {key value} {
puts [format " %-28s : %s" $key $value]
}
# ============================================================================
# ARGUMENT PARSING
# ============================================================================
proc parse_args {} {
global argc argv
global default_server default_port default_ltx default_depth default_timeout
global hw_server_host hw_server_port probes_path capture_depth trigger_timeout
global capture_scenario use_immediate output_dir
set hw_server_host $default_server
set hw_server_port $default_port
set probes_path $default_ltx
set capture_depth $default_depth
set trigger_timeout $default_timeout
set use_immediate 0
set output_dir ""
set capture_scenario ""
if {[info exists argv]} {
set args $argv
} else {
set args {}
}
if {[llength $args] == 0} {
print_usage
return -code error "NO_SCENARIO"
}
# First positional argument is the scenario
set capture_scenario [string tolower [lindex $args 0]]
set valid_scenarios {adc ddc mf doppler all health}
if {$capture_scenario ni $valid_scenarios} {
log_error "Unknown scenario: '$capture_scenario'"
log_error "Valid scenarios: [join $valid_scenarios {, }]"
print_usage
return -code error "INVALID_SCENARIO"
}
# Parse remaining keyword arguments
set i 1
while {$i < [llength $args]} {
set arg [lindex $args $i]
switch -exact -- $arg {
"-server" {
incr i
set hw_server_host [lindex $args $i]
}
"-port" {
incr i
set hw_server_port [lindex $args $i]
}
"-ltx" {
incr i
set probes_path [lindex $args $i]
}
"-outdir" {
incr i
set output_dir [lindex $args $i]
}
"-depth" {
incr i
set capture_depth [lindex $args $i]
}
"-timeout" {
incr i
set trigger_timeout [lindex $args $i]
}
"-immediate" {
set use_immediate 1
}
default {
log_warn "Unknown argument '$arg' ignoring."
}
}
incr i
}
# Auto-generate timestamped output directory if not specified
if {$output_dir eq ""} {
set timestamp [clock format [clock seconds] -format {%Y%m%d_%H%M%S}]
set output_dir "/home/jason-stone/PLFM_RADAR_work/vivado_project/captures/ila_${capture_scenario}_${timestamp}"
}
}
proc print_usage {} {
puts ""
puts "Usage: vivado -mode batch -source ila_capture.tcl -tclargs <scenario> \[options\]"
puts ""
puts "Scenarios:"
puts " adc Capture raw ADC samples (ILA 0, 400 MHz)"
puts " ddc Capture DDC I/Q output (ILA 1, 100 MHz)"
puts " mf Capture matched filter range profile (ILA 2, 100 MHz)"
puts " doppler Capture Doppler spectrum (ILA 3, 100 MHz)"
puts " all Run all 4 captures sequentially"
puts " health Quick health check all captures with pass/fail verdict"
puts ""
puts "Options:"
puts " -server <host> Hardware server hostname (default: localhost)"
puts " -port <port> Hardware server port (default: 3121)"
puts " -outdir <path> Output directory for CSV exports"
puts " -depth <N> Capture depth in samples (default: 4096)"
puts " -timeout <sec> Trigger timeout in seconds (default: 30)"
puts " -immediate Free-running capture (no trigger condition)"
puts " -ltx <path> Debug probes file path"
puts ""
}
# ============================================================================
# HARDWARE CONNECTION
# ============================================================================
proc connect_to_hw {} {
global hw_server_host hw_server_port probes_path
log_info "Connecting to hw_server at ${hw_server_host}:${hw_server_port}..."
if {[catch {open_hw_manager} err]} {
log_warn "open_hw_manager: $err (may already be open)"
}
if {[catch {
connect_hw_server -url ${hw_server_host}:${hw_server_port} -allow_non_jtag
} err]} {
log_error "Cannot connect to hw_server: $err"
return -code error "HW_SERVER_CONNECT_FAILED"
}
if {[catch {open_hw_target} err]} {
log_error "Cannot open hardware target: $err"
catch {disconnect_hw_server}
return -code error "NO_HW_TARGET"
}
# Select the first device (the XC7A200T)
set hw_devices [get_hw_devices]
if {[llength $hw_devices] == 0} {
log_error "No devices on JTAG chain."
return -code error "NO_DEVICES"
}
set target_device [lindex $hw_devices 0]
current_hw_device $target_device
log_info "Device selected: $target_device"
# Verify the device is configured (DONE pin high)
refresh_hw_device $target_device
set done [get_property REGISTER.CONFIG_STATUS.DONE $target_device]
if {$done != 1} {
log_error "FPGA is not configured (DONE=LOW). Program the bitstream first."
return -code error "DEVICE_NOT_CONFIGURED"
}
# Load debug probes
if {![file exists $probes_path]} {
log_error "Debug probes file not found: $probes_path"
return -code error "LTX_NOT_FOUND"
}
set_property PROBES.FILE $probes_path $target_device
refresh_hw_device $target_device
# Verify ILA cores are present
set ila_cores [get_hw_ilas -quiet]
if {[llength $ila_cores] == 0} {
log_error "No ILA cores detected. Ensure bitstream matches .ltx file."
return -code error "NO_ILA_CORES"
}
log_info "ILA cores found: [llength $ila_cores]"
foreach ila $ila_cores {
log_info " $ila (depth: [get_property CONTROL.DATA_DEPTH $ila])"
}
return $target_device
}
# ============================================================================
# SINGLE ILA CAPTURE
# ============================================================================
# Resolve the ILA core object from hw_ila name.
# The .ltx probe mapping names ILAs as hw_ila_1, hw_ila_2, etc.
# get_hw_ilas returns the actual objects; we match by cell name.
proc resolve_ila {ila_hw_name} {
set all_ilas [get_hw_ilas -quiet]
foreach ila $all_ilas {
set cell [get_property CELL_NAME $ila]
if {[string match "*${ila_hw_name}*" $cell] || [string match "*${ila_hw_name}*" $ila]} {
return $ila
}
}
# Fallback: try direct name match
set ila [get_hw_ilas -quiet $ila_hw_name]
if {$ila ne ""} {
return $ila
}
return ""
}
# Configure trigger for a single ILA core.
# trigger_net: hierarchical probe name
# trigger_val: "R" (rising), "F" (falling), "B" (both), "1", "0", or "X" (don't care)
proc configure_trigger {ila_obj trigger_net trigger_val use_immediate} {
if {$use_immediate} {
log_info " Trigger mode: IMMEDIATE (free-running)"
set_property CONTROL.TRIGGER_MODE BASIC $ila_obj
set_property CONTROL.TRIGGER_POSITION 0 $ila_obj
# Set all trigger compare values to don't-care for immediate
set all_probes [get_hw_probes -of_objects $ila_obj -quiet]
foreach probe $all_probes {
set_property TRIGGER_COMPARE_VALUE "eq0'bX" $probe
}
return
}
log_info " Trigger: $trigger_net = $trigger_val (rising edge)"
set_property CONTROL.TRIGGER_MODE BASIC $ila_obj
# Place trigger at 1/4 depth so we capture mostly post-trigger data
set_property CONTROL.TRIGGER_POSITION 1024 $ila_obj
# Reset all probes to don't-care first
set all_probes [get_hw_probes -of_objects $ila_obj -quiet]
foreach probe $all_probes {
catch {
set_property TRIGGER_COMPARE_VALUE "eq0'bX" $probe
}
}
# Set the specific trigger condition
set trig_probe [get_hw_probes -of_objects $ila_obj -filter "NAME =~ *$trigger_net*" -quiet]
if {$trig_probe eq ""} {
# Try partial match on the leaf name
set leaf_name [lindex [split $trigger_net "/"] end]
set trig_probe [get_hw_probes -of_objects $ila_obj -filter "NAME =~ *$leaf_name*" -quiet]
}
if {$trig_probe eq ""} {
log_warn " Trigger probe '$trigger_net' not found. Falling back to immediate trigger."
set_property CONTROL.TRIGGER_POSITION 0 $ila_obj
return
}
# Configure edge detection based on trigger_val
switch -exact -- $trigger_val {
"R" {
# Rising edge: transition from 0 to 1
set_property TRIGGER_COMPARE_VALUE "eq1'b1" $trig_probe
}
"F" {
# Falling edge: transition from 1 to 0
set_property TRIGGER_COMPARE_VALUE "eq1'b0" $trig_probe
}
"1" {
set_property TRIGGER_COMPARE_VALUE "eq1'b1" $trig_probe
}
"0" {
set_property TRIGGER_COMPARE_VALUE "eq1'b0" $trig_probe
}
default {
set_property TRIGGER_COMPARE_VALUE "eq1'b1" $trig_probe
}
}
log_info " Trigger probe resolved: $trig_probe"
}
# Run a single ILA capture and export to CSV.
# Returns a dict with {status triggered sample_count csv_path stats}
proc run_single_capture {scenario_name} {
global ila_config capture_depth trigger_timeout use_immediate output_dir
log_sep
log_info "CAPTURE: $scenario_name"
log_sep
# Parse the config for this scenario
array set cfg $ila_config($scenario_name)
set ila_hw_name $cfg(ila_name)
set trigger_net $cfg(trigger_net)
set trigger_val $cfg(trigger_val)
set clock_mhz $cfg(clock_mhz)
set description $cfg(description)
set csv_file $cfg(csv_file)
log_info "Description: $description"
log_info "ILA: $ila_hw_name @ ${clock_mhz} MHz"
# Resolve the ILA core object
set ila_obj [resolve_ila $ila_hw_name]
if {$ila_obj eq ""} {
log_error "ILA core '$ila_hw_name' not found in design."
log_error "Available ILAs: [get_hw_ilas -quiet]"
return [dict create status "FAIL" triggered 0 sample_count 0 \
csv_path "" stats "ILA not found"]
}
log_info "ILA object: $ila_obj"
# Set capture depth
set max_depth [get_property CONTROL.DATA_DEPTH $ila_obj]
set effective_depth [expr {min($capture_depth, $max_depth)}]
if {$effective_depth < $capture_depth} {
log_warn "Requested depth $capture_depth exceeds ILA max $max_depth. Using $effective_depth."
}
set_property CONTROL.DATA_DEPTH $effective_depth $ila_obj
log_info " Capture depth: $effective_depth samples"
# Configure trigger
configure_trigger $ila_obj $trigger_net $trigger_val $use_immediate
# Ensure output directory exists
file mkdir $output_dir
set csv_path "${output_dir}/${csv_file}"
# Arm the ILA
log_info " Arming ILA..."
if {[catch {
run_hw_ila $ila_obj
} err]} {
log_error "Failed to arm ILA: $err"
return [dict create status "FAIL" triggered 0 sample_count 0 \
csv_path "" stats "Arm failed: $err"]
}
# Wait for trigger with timeout
log_info " Waiting for trigger (timeout: ${trigger_timeout}s)..."
set triggered 0
if {[catch {
set wait_result [wait_on_hw_ila -timeout $trigger_timeout $ila_obj]
set triggered 1
} err]} {
# Check if it was a timeout
set ila_status [get_property STATUS.CORE_STATUS $ila_obj]
if {[string match "*WAITING*" $ila_status] || [string match "*ARMED*" $ila_status]} {
log_warn " Trigger TIMEOUT after ${trigger_timeout}s (ILA status: $ila_status)"
log_warn " Signal may not be active. Try -immediate for free-running capture."
return [dict create status "TIMEOUT" triggered 0 sample_count 0 \
csv_path "" stats "Trigger timeout"]
} else {
# ILA may have triggered but wait_on_hw_ila reported an unexpected status
log_warn " wait_on_hw_ila returned: $err (status: $ila_status)"
set triggered 1
}
}
if {!$triggered} {
return [dict create status "TIMEOUT" triggered 0 sample_count 0 \
csv_path "" stats "No trigger"]
}
# Upload captured data from ILA
log_info " Trigger hit uploading captured data..."
if {[catch {
upload_hw_ila_data $ila_obj
} err]} {
log_error "Failed to upload ILA data: $err"
return [dict create status "FAIL" triggered 1 sample_count 0 \
csv_path "" stats "Upload failed: $err"]
}
# Export to CSV
log_info " Exporting to CSV: $csv_path"
if {[catch {
write_hw_ila_data -csv_file $csv_path -force [current_hw_ila_data $ila_obj]
} err]} {
log_error "Failed to write CSV: $err"
return [dict create status "FAIL" triggered 1 sample_count 0 \
csv_path "" stats "CSV export failed: $err"]
}
# Compute summary statistics from the ILA data
set stats_result [compute_capture_stats $ila_obj $scenario_name]
log_info " Capture complete."
log_info " CSV: $csv_path"
log_info " Stats: $stats_result"
return [dict create status "PASS" triggered 1 sample_count $effective_depth \
csv_path $csv_path stats $stats_result]
}
# ============================================================================
# CAPTURE STATISTICS
# ============================================================================
# Compute min/max/mean of the primary data probes in the captured ILA data.
# Uses get_hw_ila_data to read sample values from the uploaded waveform.
proc compute_capture_stats {ila_obj scenario_name} {
global ila_config
array set cfg $ila_config($scenario_name)
set ila_data [current_hw_ila_data $ila_obj]
set sample_count [get_property DATA_DEPTH $ila_data]
# Get all data probes (non-trigger probes carry the captured signal data)
set data_probes [get_hw_probes -of_objects $ila_obj -filter {IS_DATA == true} -quiet]
if {[llength $data_probes] == 0} {
return "No data probes found"
}
# Analyze the first data probe (primary signal)
set primary_probe [lindex $data_probes 0]
set probe_name [get_property NAME $primary_probe]
set probe_width [get_property WIDTH $primary_probe]
set min_val 999999999
set max_val -999999999
set sum_val 0
set nonzero_count 0
for {set i 0} {$i < $sample_count} {incr i} {
if {[catch {
set sample_val [get_property "SAMPLE.$i" [get_hw_probes $primary_probe \
-of_objects $ila_obj]]
} err]} {
# Fallback: some Vivado versions use different property access
break
}
# Convert binary/hex string to integer
set int_val [scan_ila_value $sample_val $probe_width]
if {$int_val < $min_val} { set min_val $int_val }
if {$int_val > $max_val} { set max_val $int_val }
set sum_val [expr {$sum_val + $int_val}]
if {$int_val != 0} { incr nonzero_count }
}
if {$sample_count > 0 && $min_val != 999999999} {
set mean_val [expr {double($sum_val) / $sample_count}]
return [format "probe=%s width=%d min=%d max=%d mean=%.1f nonzero=%d/%d" \
$probe_name $probe_width $min_val $max_val $mean_val \
$nonzero_count $sample_count]
}
# Fallback: use Vivado's built-in ILA data summary if per-sample access failed
return [format "probe=%s width=%d samples=%d (per-sample stats unavailable)" \
$probe_name $probe_width $sample_count]
}
# Convert an ILA sample value (hex or binary string) to a signed integer.
proc scan_ila_value {val width} {
# ILA data may come as hex (0xABCD), binary (0b1010...), or decimal
set val [string trim $val]
if {[string match "0x*" $val] || [string match "0X*" $val]} {
set unsigned [scan [string range $val 2 end] %x]
} elseif {[string match "0b*" $val] || [string match "0B*" $val]} {
set bin_str [string range $val 2 end]
set unsigned 0
foreach bit [split $bin_str ""] {
set unsigned [expr {($unsigned << 1) | $bit}]
}
} elseif {[string is integer -strict $val]} {
set unsigned $val
} else {
# Try hex without prefix
if {[catch {set unsigned [scan $val %x]} err]} {
return 0
}
}
# Convert to signed if MSB is set (two's complement)
set sign_bit [expr {1 << ($width - 1)}]
if {$unsigned >= $sign_bit} {
set unsigned [expr {$unsigned - (1 << $width)}]
}
return $unsigned
}
# ============================================================================
# MULTI-CAPTURE SCENARIOS
# ============================================================================
proc run_all_captures {} {
set scenarios {adc ddc mf doppler}
set results [dict create]
foreach sc $scenarios {
if {[catch {
set result [run_single_capture $sc]
} err]} {
log_error "Capture '$sc' failed with exception: $err"
set result [dict create status "ERROR" triggered 0 sample_count 0 \
csv_path "" stats $err]
}
dict set results $sc $result
}
return $results
}
proc run_health_check {} {
global use_immediate
log_sep
log_info "AERIS-10 RADAR QUICK HEALTH CHECK"
log_info "Running all 4 ILA captures with immediate trigger..."
log_sep
# Force immediate trigger for health check so we don't wait for signals
set saved_immediate $use_immediate
set use_immediate 1
set scenarios {adc ddc mf doppler}
set results [dict create]
set pass_count 0
set fail_count 0
foreach sc $scenarios {
if {[catch {
set result [run_single_capture $sc]
} err]} {
log_error "Health check capture '$sc' failed: $err"
set result [dict create status "ERROR" triggered 0 sample_count 0 \
csv_path "" stats $err]
}
dict set results $sc $result
# Determine pass/fail: PASS if capture completed and data has non-zero values
set status [dict get $result status]
set stats [dict get $result stats]
if {$status eq "PASS" && [string match "*nonzero=*" $stats]} {
# Extract nonzero count from stats string
if {[regexp {nonzero=(\d+)/} $stats -> nz_count]} {
if {$nz_count > 0} {
incr pass_count
} else {
incr fail_count
}
} else {
# Could not parse, assume pass if status is PASS
incr pass_count
}
} elseif {$status eq "PASS"} {
# Stats unavailable but capture succeeded
incr pass_count
} else {
incr fail_count
}
}
set use_immediate $saved_immediate
# Print health check summary
log_sep
log_info "HEALTH CHECK SUMMARY"
log_sep
set overall [expr {$fail_count == 0 ? "PASS" : "FAIL"}]
foreach sc $scenarios {
set result [dict get $results $sc]
set status [dict get $result status]
set stats [dict get $result stats]
set verdict "???"
if {$status eq "PASS"} {
if {[regexp {nonzero=(\d+)/} $stats -> nz]} {
set verdict [expr {$nz > 0 ? "PASS (data active)" : "WARN (all zeros)"}]
} else {
set verdict "PASS (capture ok)"
}
} elseif {$status eq "TIMEOUT"} {
set verdict "FAIL (timeout)"
} else {
set verdict "FAIL ($status)"
}
log_kv [string toupper $sc] $verdict
}
puts ""
log_kv "Overall" "$overall ($pass_count/4 passed)"
log_kv "Timestamp" [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]
log_sep
return $results
}
# ============================================================================
# RESULT SUMMARY
# ============================================================================
proc print_capture_summary {results} {
log_sep
log_info "CAPTURE SUMMARY"
log_sep
dict for {scenario result} $results {
set status [dict get $result status]
set samples [dict get $result sample_count]
set csv [dict get $result csv_path]
set stats [dict get $result stats]
puts ""
log_kv "Scenario" [string toupper $scenario]
log_kv "Status" $status
log_kv "Samples" $samples
if {$csv ne ""} {
log_kv "CSV File" $csv
}
log_kv "Statistics" $stats
}
puts ""
log_kv "Timestamp" [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]
log_sep
}
# ============================================================================
# CLEANUP
# ============================================================================
proc cleanup_hw {} {
log_info "Closing hardware connection..."
catch {close_hw_target}
catch {disconnect_hw_server}
catch {close_hw_manager}
}
# ============================================================================
# MAIN ENTRY POINT
# ============================================================================
if {[catch {parse_args} err]} {
# parse_args already printed usage or error
return
}
log_sep
log_info "AERIS-10 Radar ILA Capture"
log_info "Timestamp: [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]"
log_sep
log_kv "Scenario" $capture_scenario
log_kv "HW Server" "${hw_server_host}:${hw_server_port}"
log_kv "Probes File" $probes_path
log_kv "Capture Depth" $capture_depth
log_kv "Timeout" "${trigger_timeout}s"
log_kv "Trigger Mode" [expr {$use_immediate ? "IMMEDIATE" : "CONDITIONAL"}]
log_kv "Output Dir" $output_dir
log_sep
# Connect to hardware
if {[catch {connect_to_hw} err]} {
log_error "Hardware connection failed: $err"
cleanup_hw
return
}
# Dispatch based on scenario
set exit_ok 1
if {[catch {
switch -exact -- $capture_scenario {
"adc" - "ddc" - "mf" - "doppler" {
set result [run_single_capture $capture_scenario]
set results [dict create $capture_scenario $result]
print_capture_summary $results
}
"all" {
set results [run_all_captures]
print_capture_summary $results
}
"health" {
set results [run_health_check]
# Health check prints its own summary
}
}
} err]} {
log_error "Capture failed: $err"
set exit_ok 0
}
# Cleanup
cleanup_hw
if {$exit_ok} {
log_info "ILA capture session complete."
} else {
log_error "ILA capture session finished with errors."
}
@@ -0,0 +1,567 @@
################################################################################
# insert_ila_probes.tcl
#
# AERIS-10 Radar FPGA — Post-Synthesis ILA Debug Core Insertion
# Target: XC7A200T-2FBG484I
# Design: radar_system_top (Build 13 frozen netlist)
#
# Usage:
# vivado -mode batch -source insert_ila_probes.tcl
#
# This script:
# 1. Opens the post-synth DCP from Build 13
# 2. Inserts 4 ILA debug cores across 2 clock domains
# 3. Runs full implementation with Build 13 directives
# 4. Generates bitstream, reports, and .ltx probe file
#
# ILA 0: ADC Capture — 400 MHz (rx_inst/clk_400m) — 9 bits
# ILA 1: DDC Output — 100 MHz (clk_100m_buf) — 37 bits
# ILA 2: Matched Filter Out — 100 MHz (clk_100m_buf) — 35 bits
# ILA 3: Doppler Output — 100 MHz (clk_100m_buf) — 45 bits
#
# Author: auto-generated for Jason Stone
# Date: 2026-03-18
################################################################################
# ==============================================================================
# 0. Configuration — all paths and parameters in one place
# ==============================================================================
set project_base "/home/jason-stone/PLFM_RADAR_work/vivado_project"
set synth_dcp "${project_base}/aeris10_radar.runs/impl_1/radar_system_top.dcp"
set synth_xdc "${project_base}/synth_only.xdc"
set output_dir "${project_base}/aeris10_radar.runs/impl_ila"
set top_module "radar_system_top"
set part "xc7a200tfbg484-2"
# Timestamp for output file naming
set timestamp [clock format [clock seconds] -format {%Y%m%d_%H%M%S}]
set run_tag "build13_ila_${timestamp}"
# ILA parameters
set ila_depth 4096
set trigger_pos 512 ;# 512 pre-trigger samples
# ==============================================================================
# 1. Helper procedures
# ==============================================================================
# Resolve a net with fallback wildcard patterns. Returns the net object or
# raises an error with diagnostic info if nothing is found.
proc resolve_net {primary_pattern args} {
# Try the primary pattern first
set nets [get_nets -quiet $primary_pattern]
if {[llength $nets] > 0} {
puts "INFO: Resolved net '$primary_pattern' -> [lindex $nets 0]"
return [lindex $nets 0]
}
# Try each fallback pattern
foreach fallback $args {
set nets [get_nets -quiet $fallback]
if {[llength $nets] > 0} {
puts "INFO: Primary '$primary_pattern' not found. Resolved via fallback '$fallback' -> [lindex $nets 0]"
return [lindex $nets 0]
}
}
# Nothing found — dump available nets in the hierarchy for diagnostics
set hier_prefix [lindex [split $primary_pattern "/"] 0]
puts "ERROR: Could not resolve net '$primary_pattern'"
puts " Available nets under '${hier_prefix}/*' (first 40):"
set nearby [get_nets -quiet -hierarchical "${hier_prefix}/*"]
set count 0
foreach n $nearby {
puts " $n"
incr count
if {$count >= 40} { puts " ... (truncated)"; break }
}
error "Net resolution failed for '$primary_pattern'. See log above for nearby nets."
}
# Resolve a bus (vector) of nets. Returns a list of net objects.
# pattern should contain %d which will be replaced with bit indices.
# Example: resolve_bus "rx_inst/adc/adc_data_cmos\[%d\]" 7 0
# tries bits 7 down to 0
proc resolve_bus {pattern msb lsb args} {
set net_list {}
for {set i $msb} {$i >= $lsb} {incr i -1} {
set bit_pattern [string map [list "%d" $i] $pattern]
# Build fallback list for this bit
set bit_fallbacks {}
foreach fb $args {
lappend bit_fallbacks [string map [list "%d" $i] $fb]
}
lappend net_list [resolve_net $bit_pattern {*}$bit_fallbacks]
}
return $net_list
}
# Connect a list of nets to an ILA probe port, creating additional probe ports
# as needed. The first probe port (DATA) is already created by create_debug_core.
# probe_index: starting probe port index (0 = use existing PROBE0)
# Returns the next available probe index.
proc connect_probe_nets {ila_name probe_index net_list probe_label} {
set width [llength $net_list]
puts "INFO: Connecting $width nets to ${ila_name}/probe${probe_index} ($probe_label)"
if {$probe_index > 0} {
create_debug_port $ila_name probe
}
set_property port_width $width [get_debug_ports ${ila_name}/probe${probe_index}]
connect_debug_port ${ila_name}/probe${probe_index} $net_list
return [expr {$probe_index + 1}]
}
# ==============================================================================
# 2. Open the synthesized checkpoint
# ==============================================================================
puts "======================================================================"
puts " AERIS-10 ILA Insertion Starting at [clock format [clock seconds]]"
puts "======================================================================"
# Create output directory
file mkdir $output_dir
# Open the frozen Build 13 post-synth DCP
puts "\nINFO: Opening post-synth DCP: $synth_dcp"
open_checkpoint $synth_dcp
# Verify the part
set loaded_part [get_property PART [current_design]]
puts "INFO: Design part = $loaded_part"
if {$loaded_part ne $part} {
puts "WARNING: Expected part '$part', got '$loaded_part'. Continuing anyway."
}
# Read the synthesis-only constraints (pin assignments, clocks, etc.)
puts "INFO: Reading XDC: $synth_xdc"
read_xdc $synth_xdc
# ==============================================================================
# 3. Verify clock nets exist before inserting ILA cores
# ==============================================================================
puts "\n--- Verifying clock nets ---"
# 400 MHz clock — BUFG output inside ADC interface
set clk_400m_net [resolve_net \
"rx_inst/clk_400m" \
"rx_inst/adc/clk_400m" \
"rx_inst/ad9484_interface_400m_inst/clk_400m" \
"rx_inst/*/O" \
]
# 100 MHz system clock — BUFG output
set clk_100m_net [resolve_net \
"clk_100m_buf" \
"bufg_100m/O" \
"clk_100m_BUFG" \
]
puts "INFO: 400 MHz clock net = $clk_400m_net"
puts "INFO: 100 MHz clock net = $clk_100m_net"
# ==============================================================================
# 4. ILA 0 — ADC Capture (400 MHz domain)
#
# Monitors raw ADC data at the CMOS interface output.
# 8-bit ADC data + 1-bit valid = 9 probed bits.
# 4096 samples at 400 MHz => ~10.24 us capture window —
# sufficient for one chirp segment observation.
# ==============================================================================
puts "\n====== ILA 0: ADC Capture (400 MHz) ======"
create_debug_core u_ila_0 ila
set_property ALL_PROBE_SAME_MU true [get_debug_cores u_ila_0]
set_property ALL_PROBE_SAME_MU_CNT 1 [get_debug_cores u_ila_0]
set_property C_ADV_TRIGGER false [get_debug_cores u_ila_0]
set_property C_DATA_DEPTH $ila_depth [get_debug_cores u_ila_0]
set_property C_EN_STRG_QUAL true [get_debug_cores u_ila_0]
set_property C_INPUT_PIPE_STAGES 0 [get_debug_cores u_ila_0]
set_property C_TRIGIN_EN false [get_debug_cores u_ila_0]
set_property C_TRIGOUT_EN false [get_debug_cores u_ila_0]
# Clock: 400 MHz BUFG output from ADC interface
set_property port_width 1 [get_debug_ports u_ila_0/clk]
connect_debug_port u_ila_0/clk [get_nets $clk_400m_net]
# Probe 0: adc_data_cmos[7:0] — raw 8-bit ADC sample from AD9484
set adc_data_nets [resolve_bus \
"rx_inst/adc/adc_data_cmos\[%d\]" 7 0 \
"rx_inst/adc/adc_data_400m\[%d\]" \
"rx_inst/ad9484_interface_400m_inst/adc_data_cmos\[%d\]" \
"rx_inst/*/adc_data_cmos\[%d\]" \
]
set probe_idx 0
set probe_idx [connect_probe_nets u_ila_0 $probe_idx $adc_data_nets "ADC raw data\[7:0\]"]
# Probe 1: adc_valid — data valid strobe
set adc_valid_net [resolve_net \
"rx_inst/adc/adc_valid" \
"rx_inst/ad9484_interface_400m_inst/adc_valid" \
"rx_inst/*/adc_valid" \
]
set probe_idx [connect_probe_nets u_ila_0 $probe_idx [list $adc_valid_net] "ADC valid"]
puts "INFO: ILA 0 configured 9 probe bits on 400 MHz clock"
# ==============================================================================
# 5. ILA 1 — DDC Output (100 MHz domain)
#
# Monitors the digital down-converter output after CIC+FIR decimation.
# 18-bit I + 18-bit Q + 1-bit valid = 37 probed bits.
# With 4x decimation the effective sample rate is 25 MSPS,
# so 4096 samples => ~163.8 us — covers multiple chirp periods.
# ==============================================================================
puts "\n====== ILA 1: DDC Output (100 MHz) ======"
create_debug_core u_ila_1 ila
set_property ALL_PROBE_SAME_MU true [get_debug_cores u_ila_1]
set_property ALL_PROBE_SAME_MU_CNT 1 [get_debug_cores u_ila_1]
set_property C_ADV_TRIGGER false [get_debug_cores u_ila_1]
set_property C_DATA_DEPTH $ila_depth [get_debug_cores u_ila_1]
set_property C_EN_STRG_QUAL true [get_debug_cores u_ila_1]
set_property C_INPUT_PIPE_STAGES 0 [get_debug_cores u_ila_1]
set_property C_TRIGIN_EN false [get_debug_cores u_ila_1]
set_property C_TRIGOUT_EN false [get_debug_cores u_ila_1]
# Clock: 100 MHz system clock
set_property port_width 1 [get_debug_ports u_ila_1/clk]
connect_debug_port u_ila_1/clk [get_nets $clk_100m_net]
# Probe 0: ddc_out_i[17:0] — DDC I-channel baseband output
set ddc_i_nets [resolve_bus \
"rx_inst/ddc_out_i\[%d\]" 17 0 \
"rx_inst/ddc_400m_inst/ddc_out_i\[%d\]" \
"rx_inst/*/ddc_out_i\[%d\]" \
]
set probe_idx 0
set probe_idx [connect_probe_nets u_ila_1 $probe_idx $ddc_i_nets "DDC I\[17:0\]"]
# Probe 1: ddc_out_q[17:0] — DDC Q-channel baseband output
set ddc_q_nets [resolve_bus \
"rx_inst/ddc_out_q\[%d\]" 17 0 \
"rx_inst/ddc_400m_inst/ddc_out_q\[%d\]" \
"rx_inst/*/ddc_out_q\[%d\]" \
]
set probe_idx [connect_probe_nets u_ila_1 $probe_idx $ddc_q_nets "DDC Q\[17:0\]"]
# Probe 2: ddc_valid_i — DDC output valid strobe (I path; Q valid assumed coincident)
set ddc_valid_net [resolve_net \
"rx_inst/ddc_valid_i" \
"rx_inst/ddc_400m_inst/ddc_valid_i" \
"rx_inst/*/ddc_valid_i" \
"rx_inst/ddc_valid" \
]
set probe_idx [connect_probe_nets u_ila_1 $probe_idx [list $ddc_valid_net] "DDC valid"]
puts "INFO: ILA 1 configured 37 probe bits on 100 MHz clock"
# ==============================================================================
# 6. ILA 2 — Matched Filter Output (100 MHz domain)
#
# Monitors the pulse-compression matched filter output.
# 16-bit I + 16-bit Q + 1-bit valid + 2-bit segment index = 35 probed bits.
# This allows verifying correct chirp segment correlation and range profile.
# ==============================================================================
puts "\n====== ILA 2: Matched Filter Output (100 MHz) ======"
create_debug_core u_ila_2 ila
set_property ALL_PROBE_SAME_MU true [get_debug_cores u_ila_2]
set_property ALL_PROBE_SAME_MU_CNT 1 [get_debug_cores u_ila_2]
set_property C_ADV_TRIGGER false [get_debug_cores u_ila_2]
set_property C_DATA_DEPTH $ila_depth [get_debug_cores u_ila_2]
set_property C_EN_STRG_QUAL true [get_debug_cores u_ila_2]
set_property C_INPUT_PIPE_STAGES 0 [get_debug_cores u_ila_2]
set_property C_TRIGIN_EN false [get_debug_cores u_ila_2]
set_property C_TRIGOUT_EN false [get_debug_cores u_ila_2]
# Clock: 100 MHz system clock (shared with ILA 1)
set_property port_width 1 [get_debug_ports u_ila_2/clk]
connect_debug_port u_ila_2/clk [get_nets $clk_100m_net]
# Probe 0: pc_i_w[15:0] — matched filter range-compressed I output
set mf_i_nets [resolve_bus \
"rx_inst/mf_dual/pc_i_w\[%d\]" 15 0 \
"rx_inst/matched_filter_multi_segment_inst/pc_i_w\[%d\]" \
"rx_inst/*/pc_i_w\[%d\]" \
]
set probe_idx 0
set probe_idx [connect_probe_nets u_ila_2 $probe_idx $mf_i_nets "MF I\[15:0\]"]
# Probe 1: pc_q_w[15:0] — matched filter range-compressed Q output
set mf_q_nets [resolve_bus \
"rx_inst/mf_dual/pc_q_w\[%d\]" 15 0 \
"rx_inst/matched_filter_multi_segment_inst/pc_q_w\[%d\]" \
"rx_inst/*/pc_q_w\[%d\]" \
]
set probe_idx [connect_probe_nets u_ila_2 $probe_idx $mf_q_nets "MF Q\[15:0\]"]
# Probe 2: pc_valid_w — matched filter output valid
set mf_valid_net [resolve_net \
"rx_inst/mf_dual/pc_valid_w" \
"rx_inst/matched_filter_multi_segment_inst/pc_valid_w" \
"rx_inst/*/pc_valid_w" \
]
set probe_idx [connect_probe_nets u_ila_2 $probe_idx [list $mf_valid_net] "MF valid"]
# Probe 3: segment_request[1:0] — chirp segment being correlated (0-3)
set seg_nets [resolve_bus \
"rx_inst/mf_dual/segment_request\[%d\]" 1 0 \
"rx_inst/matched_filter_multi_segment_inst/segment_request\[%d\]" \
"rx_inst/*/segment_request\[%d\]" \
]
set probe_idx [connect_probe_nets u_ila_2 $probe_idx $seg_nets "MF segment\[1:0\]"]
puts "INFO: ILA 2 configured 35 probe bits on 100 MHz clock"
# ==============================================================================
# 7. ILA 3 — Doppler Output (100 MHz domain)
#
# Monitors the Doppler processor output (post-FFT).
# 32-bit spectrum + 1-bit valid + 5-bit Doppler bin + 6-bit range bin
# + 1-bit frame sync = 45 probed bits.
# Allows verification of the range-Doppler map generation.
# ==============================================================================
puts "\n====== ILA 3: Doppler Output (100 MHz) ======"
create_debug_core u_ila_3 ila
set_property ALL_PROBE_SAME_MU true [get_debug_cores u_ila_3]
set_property ALL_PROBE_SAME_MU_CNT 1 [get_debug_cores u_ila_3]
set_property C_ADV_TRIGGER false [get_debug_cores u_ila_3]
set_property C_DATA_DEPTH $ila_depth [get_debug_cores u_ila_3]
set_property C_EN_STRG_QUAL true [get_debug_cores u_ila_3]
set_property C_INPUT_PIPE_STAGES 0 [get_debug_cores u_ila_3]
set_property C_TRIGIN_EN false [get_debug_cores u_ila_3]
set_property C_TRIGOUT_EN false [get_debug_cores u_ila_3]
# Clock: 100 MHz system clock (shared with ILA 1, ILA 2)
set_property port_width 1 [get_debug_ports u_ila_3/clk]
connect_debug_port u_ila_3/clk [get_nets $clk_100m_net]
# Probe 0: doppler_output[31:0] — Doppler FFT magnitude/spectrum output
set dop_out_nets [resolve_bus \
"rx_inst/doppler_proc/doppler_output\[%d\]" 31 0 \
"rx_inst/doppler_processor_inst/doppler_output\[%d\]" \
"rx_inst/*/doppler_output\[%d\]" \
]
set probe_idx 0
set probe_idx [connect_probe_nets u_ila_3 $probe_idx $dop_out_nets "Doppler spectrum\[31:0\]"]
# Probe 1: doppler_valid — Doppler output valid strobe
set dop_valid_net [resolve_net \
"rx_inst/doppler_proc/doppler_valid" \
"rx_inst/doppler_processor_inst/doppler_valid" \
"rx_inst/*/doppler_valid" \
]
set probe_idx [connect_probe_nets u_ila_3 $probe_idx [list $dop_valid_net] "Doppler valid"]
# Probe 2: doppler_bin[4:0] — Doppler frequency bin index (0-31)
set dop_bin_nets [resolve_bus \
"rx_inst/doppler_proc/doppler_bin\[%d\]" 4 0 \
"rx_inst/doppler_processor_inst/doppler_bin\[%d\]" \
"rx_inst/*/doppler_bin\[%d\]" \
]
set probe_idx [connect_probe_nets u_ila_3 $probe_idx $dop_bin_nets "Doppler bin\[4:0\]"]
# Probe 3: range_bin[5:0] — range bin index (0-63)
set rng_bin_nets [resolve_bus \
"rx_inst/doppler_proc/range_bin\[%d\]" 5 0 \
"rx_inst/doppler_processor_inst/range_bin\[%d\]" \
"rx_inst/*/range_bin\[%d\]" \
]
set probe_idx [connect_probe_nets u_ila_3 $probe_idx $rng_bin_nets "Range bin\[5:0\]"]
# Probe 4: new_frame_pulse — top-level frame synchronization pulse
set frame_net [resolve_net \
"rx_inst/new_frame_pulse" \
"rx_inst/radar_receiver_final_inst/new_frame_pulse" \
"rx_inst/*/new_frame_pulse" \
"new_frame_pulse" \
]
set probe_idx [connect_probe_nets u_ila_3 $probe_idx [list $frame_net] "Frame sync pulse"]
puts "INFO: ILA 3 configured 45 probe bits on 100 MHz clock"
# ==============================================================================
# 8. Implement the modified design
# ==============================================================================
puts "\n======================================================================"
puts " Implementation matching Build 13 directives"
puts "======================================================================"
# Save the post-ILA-insertion checkpoint for reference
set ila_dcp "${output_dir}/${top_module}_ila_inserted.dcp"
write_checkpoint -force $ila_dcp
puts "INFO: Saved ILA-inserted checkpoint: $ila_dcp"
# --- opt_design (Explore) ---
puts "\n--- opt_design -directive Explore ---"
opt_design -directive Explore
write_checkpoint -force "${output_dir}/${top_module}_opt.dcp"
# --- place_design (ExtraTimingOpt) ---
puts "\n--- place_design -directive ExtraTimingOpt ---"
place_design -directive ExtraTimingOpt
write_checkpoint -force "${output_dir}/${top_module}_placed.dcp"
# Post-place timing estimate
report_timing_summary -file "${output_dir}/timing_post_place.rpt" -max_paths 20
# --- phys_opt_design (AggressiveExplore) — post-place ---
puts "\n--- phys_opt_design -directive AggressiveExplore (post-place) ---"
phys_opt_design -directive AggressiveExplore
write_checkpoint -force "${output_dir}/${top_module}_physopt.dcp"
# --- route_design (AggressiveExplore) ---
puts "\n--- route_design -directive AggressiveExplore ---"
route_design -directive AggressiveExplore
write_checkpoint -force "${output_dir}/${top_module}_routed.dcp"
# Post-route timing check
report_timing_summary -file "${output_dir}/timing_post_route.rpt" -max_paths 50
# --- post-route phys_opt_design (AggressiveExplore) ---
puts "\n--- phys_opt_design -directive AggressiveExplore (post-route) ---"
phys_opt_design -directive AggressiveExplore
# Final routed + physopt checkpoint
set final_dcp "${output_dir}/${top_module}_postroute_physopt.dcp"
write_checkpoint -force $final_dcp
puts "INFO: Final checkpoint: $final_dcp"
# ==============================================================================
# 9. Generate reports for comparison with Build 13
# ==============================================================================
puts "\n======================================================================"
puts " Reports"
puts "======================================================================"
# Timing summary (compare WNS/TNS/WHS/THS against Build 13)
report_timing_summary \
-file "${output_dir}/timing_summary_final.rpt" \
-max_paths 100 \
-report_unconstrained
# Per-clock-domain timing (critical for multi-clock radar design)
report_timing \
-file "${output_dir}/timing_per_clock.rpt" \
-max_paths 20 \
-sort_by group
# Utilization (expect ~2-4% increase from ILA cores on XC7A200T)
report_utilization \
-file "${output_dir}/utilization.rpt"
report_utilization \
-file "${output_dir}/utilization_hierarchical.rpt" \
-hierarchical
# DRC
report_drc \
-file "${output_dir}/drc.rpt"
# Clock interaction / CDC (important with 400 MHz <-> 100 MHz crossing)
report_clock_interaction \
-file "${output_dir}/clock_interaction.rpt" \
-delay_type min_max
# Clock networks (verify BUFG usage)
report_clock_networks \
-file "${output_dir}/clock_networks.rpt"
# Power estimate
report_power \
-file "${output_dir}/power.rpt"
# ILA core summary
report_debug_core \
-file "${output_dir}/debug_core_summary.rpt"
puts "INFO: All reports written to $output_dir"
# ==============================================================================
# 10. Write debug probes file (.ltx) for Vivado Hardware Manager
# ==============================================================================
puts "\n--- Writing debug probes .ltx file ---"
set ltx_file "${output_dir}/${top_module}.ltx"
write_debug_probes -force $ltx_file
puts "INFO: Debug probes file: $ltx_file"
# Also copy the .ltx next to the bitstream for convenience
file copy -force $ltx_file "${output_dir}/debug_nets.ltx"
# ==============================================================================
# 11. Generate bitstream
# ==============================================================================
puts "\n======================================================================"
puts " Bitstream Generation"
puts "======================================================================"
set bitstream_file "${output_dir}/${top_module}.bit"
write_bitstream -force $bitstream_file
puts "INFO: Bitstream written: $bitstream_file"
# Also generate a .bin file for SPI flash programming if needed
write_cfgmem -force \
-format BIN \
-size 32 \
-interface SPIx4 \
-loadbit "up 0x0 $bitstream_file" \
"${output_dir}/${top_module}.bin"
puts "INFO: SPI flash image: ${output_dir}/${top_module}.bin"
# ==============================================================================
# 12. Final summary
# ==============================================================================
puts "\n======================================================================"
puts " AERIS-10 ILA Insertion Complete"
puts "======================================================================"
puts ""
puts " Output directory: $output_dir"
puts " Final DCP: $final_dcp"
puts " Bitstream: $bitstream_file"
puts " Debug probes: $ltx_file"
puts " Run tag: $run_tag"
puts ""
puts " ILA Cores Inserted:"
puts " u_ila_0 : ADC Capture (400 MHz, 9 bits, depth=$ila_depth)"
puts " u_ila_1 : DDC Output (100 MHz, 37 bits, depth=$ila_depth)"
puts " u_ila_2 : Matched Filter (100 MHz, 35 bits, depth=$ila_depth)"
puts " u_ila_3 : Doppler Output (100 MHz, 45 bits, depth=$ila_depth)"
puts ""
puts " Compare these reports against Build 13 baseline:"
puts " - timing_summary_final.rpt (WNS/TNS/WHS/THS)"
puts " - utilization.rpt (BRAM/LUT/FF overhead)"
puts " - clock_interaction.rpt (CDC paths)"
puts ""
puts " To load in Hardware Manager:"
puts " 1. Program bitstream: $bitstream_file"
puts " 2. Load probes file: $ltx_file"
puts " 3. Set trigger position to $trigger_pos for pre/post capture"
puts ""
puts " Finished at [clock format [clock seconds]]"
puts "======================================================================"
close_design
@@ -0,0 +1,364 @@
# program_fpga.tcl
# AERIS-10 Radar FPGA Bitstream Programming Flow
# Target FPGA: XC7A200T-2FBG484I (Artix-7)
#
# Programs the radar_system_top bitstream onto the target device via
# Vivado Hardware Manager and optionally loads ILA debug probes.
#
# Usage:
# Interactive: source program_fpga.tcl
# Batch: vivado -mode batch -source program_fpga.tcl
# With args: vivado -mode batch -source program_fpga.tcl -tclargs \
# -server 192.168.1.50 -port 3121 -no_probes
#
# Arguments:
# -server <hostname> Hardware server hostname (default: localhost)
# -port <port> Hardware server port (default: 3121)
# -bit <path> Bitstream file path (overrides default)
# -ltx <path> Debug probes file path (overrides default)
# -no_probes Skip loading debug probes even if .ltx exists
# -force Program even if device ID doesn't match expected
# ============================================================================
# DEFAULTS
# ============================================================================
set default_server "localhost"
set default_port 3121
set default_bit "/home/jason-stone/PLFM_RADAR_work/vivado_project/bitstream/radar_system_top.bit"
set default_ltx "/home/jason-stone/PLFM_RADAR_work/vivado_project/bitstream/radar_system_top.ltx"
set expected_part "xc7a200t"
set expected_pkg "fbg484"
# ============================================================================
# ARGUMENT PARSING
# ============================================================================
proc parse_args {} {
global argc argv
global default_server default_port default_bit default_ltx
global hw_server_host hw_server_port bitstream_path probes_path
global skip_probes force_program
set hw_server_host $default_server
set hw_server_port $default_port
set bitstream_path $default_bit
set probes_path $default_ltx
set skip_probes 0
set force_program 0
# In batch mode, argv comes from -tclargs; in interactive it may be empty
if {[info exists argv]} {
set args $argv
} else {
set args {}
}
set i 0
while {$i < [llength $args]} {
set arg [lindex $args $i]
switch -exact -- $arg {
"-server" {
incr i
set hw_server_host [lindex $args $i]
}
"-port" {
incr i
set hw_server_port [lindex $args $i]
}
"-bit" {
incr i
set bitstream_path [lindex $args $i]
}
"-ltx" {
incr i
set probes_path [lindex $args $i]
}
"-no_probes" {
set skip_probes 1
}
"-force" {
set force_program 1
}
default {
puts "WARNING: Unknown argument '$arg' ignoring."
}
}
incr i
}
}
# ============================================================================
# UTILITY PROCEDURES
# ============================================================================
proc log_info {msg} {
puts "INFO: \[AERIS-10\] $msg"
}
proc log_warn {msg} {
puts "WARN: \[AERIS-10\] $msg"
}
proc log_error {msg} {
puts "ERROR: \[AERIS-10\] $msg"
}
proc log_sep {} {
puts [string repeat "=" 72]
}
# Print a key-value pair aligned for the summary table
proc log_kv {key value} {
puts [format " %-28s : %s" $key $value]
}
# ============================================================================
# PROGRAMMING FLOW
# ============================================================================
proc program_fpga {} {
global hw_server_host hw_server_port bitstream_path probes_path
global skip_probes force_program expected_part expected_pkg
set result "FAIL"
set probes_loaded "N/A"
set device_name "unknown"
log_sep
log_info "AERIS-10 Radar FPGA Programming Flow"
log_info "Timestamp: [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]"
log_sep
# ------------------------------------------------------------------
# Step 1: Validate bitstream file exists
# ------------------------------------------------------------------
log_info "Step 1/7: Validating bitstream file..."
if {![file exists $bitstream_path]} {
log_error "Bitstream not found: $bitstream_path"
log_error "Ensure the build completed successfully and the file is accessible."
return -code error "BITSTREAM_NOT_FOUND"
}
set bit_size [file size $bitstream_path]
log_info "Bitstream: $bitstream_path ([expr {$bit_size / 1024}] KB)"
# ------------------------------------------------------------------
# Step 2: Open Hardware Manager
# ------------------------------------------------------------------
log_info "Step 2/7: Opening Vivado Hardware Manager..."
if {[catch {open_hw_manager} err]} {
# Hardware manager may already be open in interactive mode
log_warn "open_hw_manager returned: $err (may already be open)"
}
# ------------------------------------------------------------------
# Step 3: Connect to hardware server
# ------------------------------------------------------------------
log_info "Step 3/7: Connecting to hw_server at ${hw_server_host}:${hw_server_port}..."
if {[catch {
connect_hw_server -url ${hw_server_host}:${hw_server_port} -allow_non_jtag
} err]} {
log_error "Failed to connect to hardware server: $err"
log_error "Troubleshooting:"
log_error " 1. Ensure hw_server is running: hw_server -d"
log_error " 2. Check that the JTAG cable is connected and powered"
log_error " 3. Verify firewall allows port $hw_server_port"
log_error " 4. For remote: vivado -mode batch -source program_fpga.tcl -tclargs -server <ip>"
return -code error "HW_SERVER_CONNECT_FAILED"
}
log_info "Connected to hw_server."
# ------------------------------------------------------------------
# Step 4: Open JTAG target and auto-detect device
# ------------------------------------------------------------------
log_info "Step 4/7: Scanning JTAG chain for target device..."
if {[catch {
open_hw_target
} err]} {
log_error "Failed to open hardware target: $err"
log_error "No JTAG targets found. Check cable and board power."
catch {disconnect_hw_server}
return -code error "NO_HW_TARGET"
}
# Enumerate devices on the chain
set hw_devices [get_hw_devices]
if {[llength $hw_devices] == 0} {
log_error "No devices detected on JTAG chain."
catch {close_hw_target}
catch {disconnect_hw_server}
return -code error "NO_DEVICES"
}
log_info "Devices on JTAG chain: $hw_devices"
# ------------------------------------------------------------------
# Step 5: Identify and verify the target XC7A200T
# ------------------------------------------------------------------
log_info "Step 5/7: Verifying target device is $expected_part..."
set target_device ""
foreach dev $hw_devices {
set part_name [string tolower [get_property PART $dev]]
log_info " Found device: $dev (part: $part_name)"
if {[string match "${expected_part}*" $part_name]} {
set target_device $dev
set device_name $part_name
break
}
}
if {$target_device eq ""} {
if {$force_program} {
log_warn "Expected $expected_part not found. -force specified, using first device."
set target_device [lindex $hw_devices 0]
set device_name [get_property PART $target_device]
} else {
log_error "Target device $expected_part not found on JTAG chain."
log_error "Found devices: $hw_devices"
log_error "Use -force to program a different device."
catch {close_hw_target}
catch {disconnect_hw_server}
return -code error "DEVICE_MISMATCH"
}
}
# Make this the current device
current_hw_device $target_device
log_info "Target device selected: $target_device ($device_name)"
# ------------------------------------------------------------------
# Step 6: Program the bitstream
# ------------------------------------------------------------------
log_info "Step 6/7: Programming bitstream..."
# Set the programming file
set_property PROGRAM.FILE $bitstream_path $target_device
# If probes file exists and not skipped, associate it now so ILA cores
# are recognized immediately after programming
if {!$skip_probes && [file exists $probes_path]} {
log_info "Associating debug probes: $probes_path"
set_property PROBES.FILE $probes_path $target_device
}
# Execute programming
if {[catch {
program_hw_devices $target_device
} err]} {
log_error "Bitstream programming FAILED: $err"
log_error "Possible causes:"
log_error " - Bitstream built for a different part/package"
log_error " - JTAG communication error (check cable)"
log_error " - Board power supply issue"
log_error " - Bitstream file corruption"
catch {close_hw_target}
catch {disconnect_hw_server}
return -code error "PROGRAMMING_FAILED"
}
# ------------------------------------------------------------------
# Step 7: Verify DONE pin
# ------------------------------------------------------------------
log_info "Step 7/7: Verifying DONE pin status..."
# Refresh device status registers
refresh_hw_device $target_device
set done_status [get_property REGISTER.CONFIG_STATUS.DONE $target_device]
set init_status [get_property REGISTER.CONFIG_STATUS.INIT_COMPLETE $target_device]
if {$done_status == 1} {
log_info "DONE pin is HIGH device configured successfully."
set result "PASS"
} else {
log_error "DONE pin is LOW configuration may have failed."
log_error "CONFIG_STATUS.INIT_COMPLETE: $init_status"
set result "FAIL"
}
# ------------------------------------------------------------------
# Optional: Load debug probes (ILA)
# ------------------------------------------------------------------
if {!$skip_probes && [file exists $probes_path]} {
log_info "Loading ILA debug probes..."
if {[catch {
# Probes were already associated before programming.
# Refresh to enumerate ILA cores.
refresh_hw_device $target_device
set ila_cores [get_hw_ilas -quiet]
if {[llength $ila_cores] > 0} {
log_info "ILA cores detected: [llength $ila_cores]"
foreach ila $ila_cores {
set ila_name [get_property DESCRIPTION $ila]
set ila_depth [get_property CONTROL.DATA_DEPTH $ila]
log_info " $ila : depth=$ila_depth"
}
set probes_loaded "YES ([llength $ila_cores] ILAs)"
} else {
log_warn "No ILA cores found in the design. Probes file may not match bitstream."
set probes_loaded "NO (no ILA cores detected)"
}
} err]} {
log_warn "Debug probe loading encountered an issue: $err"
set probes_loaded "ERROR"
}
} elseif {$skip_probes} {
set probes_loaded "SKIPPED (-no_probes)"
} elseif {![file exists $probes_path]} {
log_info "No .ltx probes file found at: $probes_path"
set probes_loaded "NO (.ltx not found)"
}
# ------------------------------------------------------------------
# Summary
# ------------------------------------------------------------------
log_sep
log_info "PROGRAMMING SUMMARY"
log_sep
log_kv "Result" $result
log_kv "Target Device" $device_name
log_kv "Bitstream" [file tail $bitstream_path]
log_kv "Bitstream Size" "[expr {[file size $bitstream_path] / 1024}] KB"
log_kv "DONE Pin" [expr {$done_status == 1 ? "HIGH (OK)" : "LOW (FAIL)"}]
log_kv "INIT_COMPLETE" $init_status
log_kv "Debug Probes" $probes_loaded
log_kv "HW Server" "${hw_server_host}:${hw_server_port}"
log_kv "Timestamp" [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]
log_sep
if {$result eq "FAIL"} {
return -code error "PROGRAMMING_VERIFICATION_FAILED"
}
return $result
}
# ============================================================================
# MAIN ENTRY POINT
# ============================================================================
parse_args
log_info "Configuration:"
log_kv "HW Server" "${hw_server_host}:${hw_server_port}"
log_kv "Bitstream" $bitstream_path
log_kv "Probes" [expr {$skip_probes ? "DISABLED" : $probes_path}]
log_kv "Force Mode" [expr {$force_program ? "YES" : "NO"}]
if {[catch {program_fpga} err]} {
log_error "Programming flow terminated with error: $err"
# In batch mode, exit with non-zero status
if {[string match "batch" [get_property MODE [current_hw_server -quiet]]]} {
exit 1
}
} else {
log_info "Programming flow completed successfully."
}
@@ -7,7 +7,7 @@ Checks:
2. FFT twiddle files: bit-exact match against cos(2*pi*k/N) in Q15 2. FFT twiddle files: bit-exact match against cos(2*pi*k/N) in Q15
3. Long chirp .mem files: reverse-engineer parameters, check for chirp structure 3. Long chirp .mem files: reverse-engineer parameters, check for chirp structure
4. Short chirp .mem files: check length, value range, spectral content 4. Short chirp .mem files: check length, value range, spectral content
5. latency_buffer_2159 LATENCY=3187 parameter validation 5. latency_buffer LATENCY=3187 parameter validation
Usage: Usage:
python3 validate_mem_files.py python3 validate_mem_files.py
@@ -479,8 +479,9 @@ def test_latency_buffer():
# Check that the module name vs parameter is consistent # Check that the module name vs parameter is consistent
print(f" LATENCY parameter: {LATENCY}") print(f" LATENCY parameter: {LATENCY}")
print(f" Module name: latency_buffer_2159 (historical, actual LATENCY={LATENCY})") print(f" Module name: latency_buffer (parameterized, LATENCY={LATENCY})")
warn("Module name 'latency_buffer_2159' is inconsistent with LATENCY=3187 parameter") # Module name was renamed from latency_buffer_2159 to latency_buffer
# to match the actual parameterized LATENCY value. No warning needed.
# Validate address arithmetic won't overflow # Validate address arithmetic won't overflow
# read_ptr = (write_ptr - LATENCY) mod 4096 # read_ptr = (write_ptr - LATENCY) mod 4096
+1 -1
View File
@@ -31,7 +31,7 @@ module tb_latency_buffer;
always #(CLK_PERIOD/2) clk = ~clk; always #(CLK_PERIOD/2) clk = ~clk;
// DUT // DUT
latency_buffer_2159 #( latency_buffer #(
.DATA_WIDTH(DATA_WIDTH), .DATA_WIDTH(DATA_WIDTH),
.LATENCY(LATENCY) .LATENCY(LATENCY)
) uut ( ) uut (