fix(xdc): add hold false_path for ADC IDDR + reorganize build scripts by target
- Add set_false_path -hold for source-synchronous ADC IDDR paths in adc_clk_mmcm.xdc (eliminates 8 hold violations from build 12) - Add DDR falling-edge input delay constraints to xc7a50t_ftg256.xdc (parity with 200T XDC) - Reorganize scripts/ into target subdirectories: 50t/, 200t/, te0712/, te0713/, utils/ so users can run the correct build for their hardware - Delete obsolete build scripts (build17-20) superseded by build_50t/200t - Update project_root paths in all moved scripts (.. -> ../..)
This commit is contained in:
@@ -0,0 +1,800 @@
|
||||
# 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 script_dir [file dirname [file normalize [info script]]]
|
||||
set project_root [file normalize [file join $script_dir "../.."]]
|
||||
set default_ltx [file join $project_root "build" "aeris10_radar.runs" "impl_ila" "radar_system_top.ltx"]
|
||||
set default_output_base [file join $project_root "build" "captures"]
|
||||
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_output_base 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 [file join $default_output_base "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,755 @@
|
||||
################################################################################
|
||||
# insert_ila_probes.tcl
|
||||
#
|
||||
# AERIS-10 Radar FPGA — Post-Synthesis ILA Debug Core Insertion
|
||||
# Target: XC7A200T-2FBG484I
|
||||
# Design: radar_system_top (Build 16 frozen netlist)
|
||||
#
|
||||
# Usage:
|
||||
# vivado -mode batch -source insert_ila_probes.tcl
|
||||
#
|
||||
# This script:
|
||||
# 1. Opens the post-synth DCP from Build 16
|
||||
# 2. Inserts 4 ILA debug cores across 2 clock domains
|
||||
# 3. Runs full implementation with Build 16 directives
|
||||
# 4. Generates bitstream, reports, and .ltx probe file
|
||||
#
|
||||
# ILA 0: ADC Capture — 400 MHz (rx_inst/adc/clk_400m) — up to 9 bits
|
||||
# ILA 1: DDC Output — 100 MHz — up to 37 bits
|
||||
# ILA 2: Matched Filter Ctrl — 100 MHz — 4 signals
|
||||
# ILA 3: Doppler Output — 100 MHz — up to 45 bits
|
||||
#
|
||||
# APPROACH: Uses get_nets with -hierarchical wildcards and get_nets -of
|
||||
# [get_pins ...] to resolve post-synthesis net names. All probe connections
|
||||
# are fault-tolerant — if a net cannot be found it is logged and skipped,
|
||||
# rather than aborting the build.
|
||||
#
|
||||
# Author: auto-generated for Jason Stone
|
||||
# Date: 2026-03-18
|
||||
################################################################################
|
||||
|
||||
# ==============================================================================
|
||||
# 0. Configuration — all paths and parameters in one place
|
||||
# ==============================================================================
|
||||
|
||||
set script_dir [file dirname [file normalize [info script]]]
|
||||
set project_root [file normalize [file join $script_dir "../.."]]
|
||||
set project_base [file join $project_root "build"]
|
||||
set synth_dcp "${project_base}/aeris10_radar.runs/synth_1/radar_system_top.dcp"
|
||||
set synth_xdc [file join $project_root "constraints" "xc7a200t_fbg484.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 "build16_ila_${timestamp}"
|
||||
|
||||
# ILA parameters
|
||||
set ila_depth 4096
|
||||
set trigger_pos 512 ;# 512 pre-trigger samples
|
||||
|
||||
# Global counter: total probes actually connected (for final summary)
|
||||
set total_probes_connected 0
|
||||
|
||||
# ==============================================================================
|
||||
# 1. Helper procedures — fault-tolerant net resolution
|
||||
# ==============================================================================
|
||||
|
||||
# Try a sequence of strategies to find a single net. Returns the net object
|
||||
# or empty string "" if nothing was found. Never errors out.
|
||||
#
|
||||
# Each element in $strategies is itself a list:
|
||||
# { method arg }
|
||||
# where method is one of:
|
||||
# "net" — try exact path first (get_nets -quiet $arg), then hierarchical
|
||||
# "pin" — call get_nets -quiet -of [get_pins -quiet $arg]
|
||||
#
|
||||
# Example:
|
||||
# find_net { {net rx_inst/adc/adc_valid} {net *adc_valid*} }
|
||||
#
|
||||
proc find_net {strategies} {
|
||||
foreach strategy $strategies {
|
||||
set method [lindex $strategy 0]
|
||||
set arg [lindex $strategy 1]
|
||||
switch $method {
|
||||
"net" {
|
||||
# Try exact path first (works for fully-qualified hierarchical names)
|
||||
set result [get_nets -quiet $arg]
|
||||
# Fall back to hierarchical search (works for leaf names and wildcards)
|
||||
if {[llength $result] == 0} {
|
||||
set result [get_nets -quiet -hierarchical $arg]
|
||||
}
|
||||
}
|
||||
"pin" {
|
||||
set pins [get_pins -quiet $arg]
|
||||
if {[llength $pins] == 0} {
|
||||
# Also try hierarchical pin search
|
||||
set pins [get_pins -quiet -hierarchical $arg]
|
||||
}
|
||||
if {[llength $pins] > 0} {
|
||||
set result [get_nets -quiet -of $pins]
|
||||
} else {
|
||||
set result {}
|
||||
}
|
||||
}
|
||||
default {
|
||||
set result {}
|
||||
}
|
||||
}
|
||||
if {[llength $result] > 0} {
|
||||
# Return the first matching net
|
||||
set chosen [lindex $result 0]
|
||||
puts " INFO: Resolved '$arg' ($method) -> $chosen"
|
||||
return $chosen
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
# Try a sequence of strategies to find a bus (vector) of nets.
|
||||
# Returns a Tcl list of net objects. The list may be shorter than requested
|
||||
# if some bits were optimised away.
|
||||
#
|
||||
# Each element in $strategies is { method pattern } where pattern may contain
|
||||
# a literal '*' or a specific glob. The procedure evaluates ALL strategies
|
||||
# as a batch and picks the first one that returns >= 1 net.
|
||||
#
|
||||
proc find_bus {strategies} {
|
||||
foreach strategy $strategies {
|
||||
set method [lindex $strategy 0]
|
||||
set arg [lindex $strategy 1]
|
||||
switch $method {
|
||||
"net" {
|
||||
# Try exact path first (works for fully-qualified hierarchical paths)
|
||||
set result [get_nets -quiet $arg]
|
||||
# Fall back to hierarchical search (leaf names, wildcards)
|
||||
if {[llength $result] == 0} {
|
||||
set result [get_nets -quiet -hierarchical $arg]
|
||||
}
|
||||
}
|
||||
"pin" {
|
||||
set pins [get_pins -quiet $arg]
|
||||
if {[llength $pins] == 0} {
|
||||
# Also try hierarchical pin search
|
||||
set pins [get_pins -quiet -hierarchical $arg]
|
||||
}
|
||||
if {[llength $pins] > 0} {
|
||||
set result [get_nets -quiet -of $pins]
|
||||
} else {
|
||||
set result {}
|
||||
}
|
||||
}
|
||||
default {
|
||||
set result {}
|
||||
}
|
||||
}
|
||||
if {[llength $result] > 0} {
|
||||
puts " INFO: Bus resolved via '$arg' ($method) -> [llength $result] nets"
|
||||
return $result
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
# Connect a list of nets to the next available probe port on an ILA core.
|
||||
# If the net list is empty, logs a warning and returns the same probe index
|
||||
# (no probe port is consumed).
|
||||
#
|
||||
# ila_name: e.g. u_ila_0
|
||||
# probe_index: current probe port index (0 for PROBE0, etc.)
|
||||
# net_list: Tcl list of net objects to connect
|
||||
# label: human-readable description for log messages
|
||||
#
|
||||
# Returns the next available probe index.
|
||||
#
|
||||
proc connect_probe {ila_name probe_index net_list label} {
|
||||
global total_probes_connected
|
||||
|
||||
set width [llength $net_list]
|
||||
if {$width == 0} {
|
||||
puts " WARNING: No nets found for '$label' — skipping probe${probe_index} on $ila_name"
|
||||
return $probe_index
|
||||
}
|
||||
|
||||
puts " INFO: Connecting $width nets to ${ila_name}/probe${probe_index} ($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
|
||||
|
||||
incr total_probes_connected $width
|
||||
return [expr {$probe_index + 1}]
|
||||
}
|
||||
|
||||
# Deferred ILA creation — create the debug core, set properties, connect clock,
|
||||
# and wire up all resolved probes in one shot. If no probes resolved, the ILA
|
||||
# is NOT created at all (avoids dangling probe0 error).
|
||||
#
|
||||
# ila_name: e.g. u_ila_0
|
||||
# clk_net: clock net object
|
||||
# probe_list: list of {label net_list} pairs (pre-resolved)
|
||||
# depth: ILA sample depth
|
||||
#
|
||||
# Returns the number of probe ports actually connected.
|
||||
#
|
||||
proc create_ila_deferred {ila_name clk_net probe_list depth} {
|
||||
global total_probes_connected
|
||||
|
||||
# Filter to only probes that have at least 1 net
|
||||
set valid_probes {}
|
||||
foreach probe_entry $probe_list {
|
||||
set label [lindex $probe_entry 0]
|
||||
set net_list [lindex $probe_entry 1]
|
||||
if {[llength $net_list] > 0} {
|
||||
lappend valid_probes [list $label $net_list]
|
||||
} else {
|
||||
puts " WARNING: No nets found for '$label' on $ila_name — skipping"
|
||||
}
|
||||
}
|
||||
|
||||
if {[llength $valid_probes] == 0} {
|
||||
puts " WARNING: ALL probes failed for $ila_name — ILA core NOT created (avoiding dangling probe0)"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Now create the debug core — we know we have at least 1 probe
|
||||
puts " INFO: Creating $ila_name with [llength $valid_probes] probe(s)"
|
||||
create_debug_core $ila_name ila
|
||||
set_property ALL_PROBE_SAME_MU true [get_debug_cores $ila_name]
|
||||
set_property ALL_PROBE_SAME_MU_CNT 2 [get_debug_cores $ila_name]
|
||||
set_property C_ADV_TRIGGER false [get_debug_cores $ila_name]
|
||||
set_property C_DATA_DEPTH $depth [get_debug_cores $ila_name]
|
||||
set_property C_EN_STRG_QUAL true [get_debug_cores $ila_name]
|
||||
set_property C_INPUT_PIPE_STAGES 0 [get_debug_cores $ila_name]
|
||||
set_property C_TRIGIN_EN false [get_debug_cores $ila_name]
|
||||
set_property C_TRIGOUT_EN false [get_debug_cores $ila_name]
|
||||
|
||||
# Connect the clock
|
||||
set_property port_width 1 [get_debug_ports ${ila_name}/clk]
|
||||
connect_debug_port ${ila_name}/clk [get_nets $clk_net]
|
||||
|
||||
# Connect each resolved probe
|
||||
set probe_idx 0
|
||||
foreach probe_entry $valid_probes {
|
||||
set label [lindex $probe_entry 0]
|
||||
set net_list [lindex $probe_entry 1]
|
||||
set probe_idx [connect_probe $ila_name $probe_idx $net_list $label]
|
||||
}
|
||||
|
||||
return $probe_idx
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# 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. Resolve clock nets
|
||||
# ==============================================================================
|
||||
|
||||
puts "\n--- Resolving clock nets ---"
|
||||
|
||||
# 400 MHz clock — inside ADC interface (confirmed resolved to rx_inst/clk_400m)
|
||||
set clk_400m_net [find_net {
|
||||
{net rx_inst/clk_400m}
|
||||
{net rx_inst/adc/clk_400m}
|
||||
{net *adc*/clk_400m}
|
||||
{net *clk_400m*}
|
||||
}]
|
||||
if {$clk_400m_net eq ""} {
|
||||
error "FATAL: Cannot find 400 MHz clock net. Cannot insert ILA 0."
|
||||
}
|
||||
puts "INFO: 400 MHz clock net = $clk_400m_net"
|
||||
|
||||
# 100 MHz system clock
|
||||
set clk_100m_net [find_net {
|
||||
{net clk_100m_IBUF_BUFG}
|
||||
{net clk_100m_buf}
|
||||
{net clk_100m_BUFG}
|
||||
{net *clk_100m*}
|
||||
}]
|
||||
if {$clk_100m_net eq ""} {
|
||||
error "FATAL: Cannot find 100 MHz clock net. Cannot insert ILA 1/2/3."
|
||||
}
|
||||
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.
|
||||
# Probes: ADC data [7:0] + ADC valid = up to 9 bits.
|
||||
# 4096 samples at 400 MHz => ~10.24 us capture window.
|
||||
#
|
||||
# Uses DEFERRED creation: probes are resolved first, ILA is only created
|
||||
# if at least one probe has nets. This avoids dangling probe0 errors.
|
||||
# ==============================================================================
|
||||
|
||||
puts "\n====== ILA 0: ADC Capture (400 MHz) ======"
|
||||
|
||||
# Probe 0: ADC data [7:0]
|
||||
# Post-synth register name is adc_data_400m_reg_reg (double "reg" from synthesis).
|
||||
# Bit 7 is inverted: adc_data_400m_reg_reg[7]_inv.
|
||||
# Use pin-based discovery which catches both normal and _inv variants.
|
||||
set adc_data_nets [find_bus {
|
||||
{pin rx_inst/adc/adc_data_400m_reg_reg[*]/Q}
|
||||
{net rx_inst/adc/adc_data_400m_reg_reg[*]}
|
||||
{pin rx_inst/adc/adc_data_400m_reg[*]/Q}
|
||||
{net rx_inst/adc/A[*]}
|
||||
{pin rx_inst/adc/adc_data_cmos_reg[*]/Q}
|
||||
{net rx_inst/adc/adc_data_400m[*]}
|
||||
{net rx_inst/adc/adc_data_cmos[*]}
|
||||
}]
|
||||
|
||||
# Probe 1: ADC valid
|
||||
# Net confirmed as rx_inst/adc/adc_valid
|
||||
# Pin confirmed as rx_inst/adc/adc_data_valid_400m_reg_reg/Q (double "reg")
|
||||
set adc_valid_net [find_net {
|
||||
{net rx_inst/adc/adc_valid}
|
||||
{pin rx_inst/adc/adc_data_valid_400m_reg_reg/Q}
|
||||
{pin rx_inst/adc/adc_valid_reg/Q}
|
||||
{net *adc/adc_valid*}
|
||||
}]
|
||||
if {$adc_valid_net ne ""} {
|
||||
set adc_valid_list [list $adc_valid_net]
|
||||
} else {
|
||||
set adc_valid_list {}
|
||||
}
|
||||
|
||||
# Deferred creation: only create ILA if at least 1 probe resolves
|
||||
set ila0_probes [list \
|
||||
[list "ADC data" $adc_data_nets] \
|
||||
[list "ADC valid" $adc_valid_list] \
|
||||
]
|
||||
set ila0_count [create_ila_deferred u_ila_0 $clk_400m_net $ila0_probes $ila_depth]
|
||||
puts "INFO: ILA 0 — $ila0_count probe ports on 400 MHz clock"
|
||||
|
||||
# ==============================================================================
|
||||
# 5. ILA 1 — DDC Output (100 MHz domain)
|
||||
#
|
||||
# Monitors the digital down-converter output after CIC+FIR decimation.
|
||||
# Probes: DDC I [17:1] + DDC Q [17:1] + DDC valid = up to 35 bits.
|
||||
# Bit 0 is optimized away in synthesis.
|
||||
#
|
||||
# Uses DEFERRED creation to avoid dangling probe0 errors.
|
||||
# ==============================================================================
|
||||
|
||||
puts "\n====== ILA 1: DDC Output (100 MHz) ======"
|
||||
|
||||
# Probe 0: ddc_out_i — DDC I-channel baseband output
|
||||
# Nets confirmed as rx_inst/ddc/ddc_out_i[1] through [17] (bit 0 optimized away)
|
||||
# Use exact path WITHOUT -hierarchical, then fall back to pin-based and hierarchical
|
||||
set ddc_i_nets [find_bus {
|
||||
{net rx_inst/ddc/ddc_out_i[*]}
|
||||
{pin rx_inst/ddc/ddc_out_i_reg[*]/Q}
|
||||
{net *ddc/ddc_out_i[*]}
|
||||
}]
|
||||
|
||||
# Probe 1: ddc_out_q — DDC Q-channel baseband output
|
||||
# Nets confirmed as rx_inst/ddc/ddc_out_q[1] through [17] (bit 0 optimized away)
|
||||
set ddc_q_nets [find_bus {
|
||||
{net rx_inst/ddc/ddc_out_q[*]}
|
||||
{pin rx_inst/ddc/ddc_out_q_reg[*]/Q}
|
||||
{net *ddc/ddc_out_q[*]}
|
||||
}]
|
||||
|
||||
# Probe 2: DDC output valid
|
||||
# Confirmed nets: rx_inst/ddc_valid_q, rx_inst/ddc/baseband_valid_q
|
||||
set ddc_valid_net [find_net {
|
||||
{net rx_inst/ddc_valid_q}
|
||||
{net rx_inst/ddc/baseband_valid_q}
|
||||
{net rx_inst/ddc/fir_valid}
|
||||
{pin rx_inst/ddc/baseband_valid_q_reg/Q}
|
||||
{net *ddc*valid*}
|
||||
}]
|
||||
if {$ddc_valid_net ne ""} {
|
||||
set ddc_valid_list [list $ddc_valid_net]
|
||||
} else {
|
||||
set ddc_valid_list {}
|
||||
}
|
||||
|
||||
# Deferred creation: only create ILA if at least 1 probe resolves
|
||||
set ila1_probes [list \
|
||||
[list "DDC I" $ddc_i_nets] \
|
||||
[list "DDC Q" $ddc_q_nets] \
|
||||
[list "DDC valid" $ddc_valid_list] \
|
||||
]
|
||||
set ila1_count [create_ila_deferred u_ila_1 $clk_100m_net $ila1_probes $ila_depth]
|
||||
puts "INFO: ILA 1 — $ila1_count probe ports on 100 MHz clock"
|
||||
|
||||
# ==============================================================================
|
||||
# 6. ILA 2 — Matched Filter Control (100 MHz domain)
|
||||
#
|
||||
# Reduced probe set: only control/status signals that are confirmed to exist
|
||||
# in the post-synthesis netlist. Data nets (pc_i_w, pc_q_w) do NOT exist
|
||||
# post-synth due to hierarchy flattening.
|
||||
#
|
||||
# Probes: range_profile_valid + mf_valid_out + segment_request[1:0] = 4 bits.
|
||||
#
|
||||
# Uses DEFERRED creation to avoid dangling probe0 errors.
|
||||
# ==============================================================================
|
||||
|
||||
puts "\n====== ILA 2: Matched Filter Control (100 MHz) ======"
|
||||
|
||||
# Probe 0: range_profile_valid
|
||||
# Confirmed nets: rx_inst/mf_dual/range_profile_valid,
|
||||
# rx_inst/mf_dual/m_f_p_c/range_profile_valid,
|
||||
# rx_inst/range_decim/range_profile_valid
|
||||
set rpv_net [find_net {
|
||||
{net rx_inst/mf_dual/range_profile_valid}
|
||||
{net rx_inst/mf_dual/m_f_p_c/range_profile_valid}
|
||||
{net rx_inst/range_decim/range_profile_valid}
|
||||
{pin rx_inst/mf_dual/range_profile_valid_reg/Q}
|
||||
{net *mf_dual/range_profile_valid*}
|
||||
}]
|
||||
if {$rpv_net ne ""} {
|
||||
set rpv_list [list $rpv_net]
|
||||
} else {
|
||||
set rpv_list {}
|
||||
}
|
||||
|
||||
# Probe 1: mf_valid_out (internal MF output valid)
|
||||
# Confirmed nets: rx_inst/mf_dual/m_f_p_c/mf_inst/mf_valid_out,
|
||||
# rx_inst/mf_dual/m_f_p_c/mf_valid_in
|
||||
set mfv_net [find_net {
|
||||
{net rx_inst/mf_dual/m_f_p_c/mf_inst/mf_valid_out}
|
||||
{net rx_inst/mf_dual/m_f_p_c/mf_valid_in}
|
||||
{pin rx_inst/mf_dual/m_f_p_c/mf_inst/mf_valid_out_reg/Q}
|
||||
{net *mf_inst/mf_valid_out*}
|
||||
}]
|
||||
if {$mfv_net ne ""} {
|
||||
set mfv_list [list $mfv_net]
|
||||
} else {
|
||||
set mfv_list {}
|
||||
}
|
||||
|
||||
# Probe 2: segment_request[1:0] (confirmed in net dump)
|
||||
set seg_nets [find_bus {
|
||||
{pin rx_inst/mf_dual/segment_request_reg[*]/Q}
|
||||
{net rx_inst/mf_dual/segment_request[*]}
|
||||
{net *mf_dual/segment_request[*]}
|
||||
}]
|
||||
|
||||
# Deferred creation: only create ILA if at least 1 probe resolves
|
||||
set ila2_probes [list \
|
||||
[list "MF range_profile_valid" $rpv_list] \
|
||||
[list "MF mf_valid_out" $mfv_list] \
|
||||
[list "MF segment_request" $seg_nets] \
|
||||
]
|
||||
set ila2_count [create_ila_deferred u_ila_2 $clk_100m_net $ila2_probes $ila_depth]
|
||||
puts "INFO: ILA 2 — $ila2_count probe ports on 100 MHz clock (control signals only)"
|
||||
|
||||
# ==============================================================================
|
||||
# 7. ILA 3 — Doppler Output (100 MHz domain)
|
||||
#
|
||||
# Monitors the Doppler processor output (post-FFT).
|
||||
# Probes: doppler_data OBUF [31:0] + doppler_valid + doppler_bin [4:0]
|
||||
# + range_bin [5:0] + new_frame_pulse = up to 45 bits.
|
||||
# Uses _OBUF net variants which are guaranteed to exist at top-level I/O.
|
||||
#
|
||||
# Uses DEFERRED creation to avoid dangling probe0 errors.
|
||||
# ==============================================================================
|
||||
|
||||
puts "\n====== ILA 3: Doppler Output (100 MHz) ======"
|
||||
|
||||
# Probe 0: Doppler output data [31:0]
|
||||
# Use _OBUF variants (top-level output buffer nets) which are guaranteed
|
||||
# to exist. Fall back to register Q pins if OBUFs are not present.
|
||||
set dop_data_nets [find_bus {
|
||||
{net dbg_doppler_data_OBUF[*]}
|
||||
{pin rx_inst/doppler_proc/doppler_output_reg[*]/Q}
|
||||
{net *doppler_data_OBUF[*]}
|
||||
{net *doppler_output[*]}
|
||||
}]
|
||||
|
||||
# Probe 1: Doppler valid
|
||||
set dop_valid_net [find_net {
|
||||
{net dbg_doppler_valid_OBUF}
|
||||
{net rx_inst/doppler_proc/dbg_doppler_valid_OBUF}
|
||||
{pin rx_inst/doppler_proc/doppler_valid_reg/Q}
|
||||
{net *doppler_valid*}
|
||||
}]
|
||||
if {$dop_valid_net ne ""} {
|
||||
set dop_valid_list [list $dop_valid_net]
|
||||
} else {
|
||||
set dop_valid_list {}
|
||||
}
|
||||
|
||||
# Probe 2: Doppler bin [4:0]
|
||||
set dop_bin_nets [find_bus {
|
||||
{pin rx_inst/doppler_proc/doppler_bin_reg[*]/Q}
|
||||
{net rx_inst/doppler_bin_reg[*]}
|
||||
{net *doppler_bin_OBUF[*]}
|
||||
{net *doppler_bin[*]}
|
||||
}]
|
||||
|
||||
# Probe 3: Range bin [5:0]
|
||||
set rng_bin_nets [find_bus {
|
||||
{pin rx_inst/doppler_proc/range_bin_reg[*]/Q}
|
||||
{net rx_inst/range_bin_reg[*]}
|
||||
{net *range_bin_OBUF[*]}
|
||||
{net *range_bin[*]}
|
||||
}]
|
||||
|
||||
# Probe 4: new_frame_pulse — frame synchronization
|
||||
set frame_net [find_net {
|
||||
{net rx_inst/new_frame_pulse}
|
||||
{net *new_frame_pulse*}
|
||||
{pin rx_inst/new_frame_pulse_reg/Q}
|
||||
{net *frame_pulse*}
|
||||
}]
|
||||
if {$frame_net ne ""} {
|
||||
set frame_list [list $frame_net]
|
||||
} else {
|
||||
set frame_list {}
|
||||
}
|
||||
|
||||
# Deferred creation: only create ILA if at least 1 probe resolves
|
||||
set ila3_probes [list \
|
||||
[list "Doppler data" $dop_data_nets] \
|
||||
[list "Doppler valid" $dop_valid_list] \
|
||||
[list "Doppler bin" $dop_bin_nets] \
|
||||
[list "Range bin" $rng_bin_nets] \
|
||||
[list "Frame sync pulse" $frame_list] \
|
||||
]
|
||||
set ila3_count [create_ila_deferred u_ila_3 $clk_100m_net $ila3_probes $ila_depth]
|
||||
puts "INFO: ILA 3 — $ila3_count probe ports on 100 MHz clock"
|
||||
|
||||
# ==============================================================================
|
||||
# 8. Pre-implementation validation
|
||||
# ==============================================================================
|
||||
|
||||
puts "\n--- Pre-implementation ILA summary ---"
|
||||
puts "INFO: Total probe bits connected across all ILAs: $total_probes_connected"
|
||||
|
||||
# Sanity check: make sure we connected SOMETHING
|
||||
if {$total_probes_connected == 0} {
|
||||
error "FATAL: No probe nets were connected to any ILA. Check net names against the post-synth netlist."
|
||||
}
|
||||
|
||||
# List all debug cores for the log
|
||||
set created_cores [get_debug_cores -quiet]
|
||||
if {[llength $created_cores] > 0} {
|
||||
foreach core $created_cores {
|
||||
puts " DEBUG CORE: $core"
|
||||
}
|
||||
} else {
|
||||
puts " WARNING: No debug cores found (this should not happen if total_probes_connected > 0)"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# 9. Implement the modified design (Build 13 directives)
|
||||
# ==============================================================================
|
||||
|
||||
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"
|
||||
|
||||
# ==============================================================================
|
||||
# 10. 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"
|
||||
|
||||
# ==============================================================================
|
||||
# 11. 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"
|
||||
|
||||
# ==============================================================================
|
||||
# 12. 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"
|
||||
|
||||
# ==============================================================================
|
||||
# 13. 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 (only cores with resolved probes):"
|
||||
if {$ila0_count > 0} {
|
||||
puts " u_ila_0 : ADC Capture (400 MHz, depth=$ila_depth, ${ila0_count} probes)"
|
||||
} else {
|
||||
puts " u_ila_0 : ADC Capture — SKIPPED (no probes resolved)"
|
||||
}
|
||||
if {$ila1_count > 0} {
|
||||
puts " u_ila_1 : DDC Output (100 MHz, depth=$ila_depth, ${ila1_count} probes)"
|
||||
} else {
|
||||
puts " u_ila_1 : DDC Output — SKIPPED (no probes resolved)"
|
||||
}
|
||||
if {$ila2_count > 0} {
|
||||
puts " u_ila_2 : MF Control (100 MHz, depth=$ila_depth, ${ila2_count} probes)"
|
||||
} else {
|
||||
puts " u_ila_2 : MF Control — SKIPPED (no probes resolved)"
|
||||
}
|
||||
if {$ila3_count > 0} {
|
||||
puts " u_ila_3 : Doppler Output (100 MHz, depth=$ila_depth, ${ila3_count} probes)"
|
||||
} else {
|
||||
puts " u_ila_3 : Doppler Output — SKIPPED (no probes resolved)"
|
||||
}
|
||||
puts " Total probe bits connected: $total_probes_connected"
|
||||
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,366 @@
|
||||
# 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 script_dir [file dirname [file normalize [info script]]]
|
||||
set project_root [file normalize [file join $script_dir "../.."]]
|
||||
set default_bit [file join $project_root "build" "bitstream" "radar_system_top_build21.bit"]
|
||||
set default_ltx [file join $project_root "build" "aeris10_radar.runs" "impl_ila" "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."
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# run_cdc_and_netlist.tcl
|
||||
# Opens the routed design and runs:
|
||||
# 1. report_cdc — detailed CDC analysis to investigate TIMING-9
|
||||
# 2. write_verilog — post-synthesis functional simulation netlist
|
||||
#
|
||||
# Usage: vivado -mode batch -source run_cdc_and_netlist.tcl
|
||||
|
||||
set script_dir [file dirname [file normalize [info script]]]
|
||||
set project_root [file normalize [file join $script_dir "../.."]]
|
||||
set project_dir [file join $project_root "build"]
|
||||
set report_dir "${project_dir}/reports_impl"
|
||||
file mkdir $report_dir
|
||||
|
||||
# Open the routed checkpoint
|
||||
open_checkpoint ${project_dir}/aeris10_radar.runs/impl_1/radar_system_top_routed.dcp
|
||||
|
||||
# ============================================================================
|
||||
# 1. report_cdc — identify all CDC crossings and the TIMING-9 source
|
||||
# ============================================================================
|
||||
puts "INFO: Running report_cdc..."
|
||||
report_cdc -details -file ${report_dir}/cdc_report.txt
|
||||
|
||||
# ============================================================================
|
||||
# 2. Write post-synthesis functional simulation netlist
|
||||
# ============================================================================
|
||||
puts "INFO: Writing post-synthesis functional sim netlist..."
|
||||
|
||||
# Post-synthesis (from synth checkpoint) — simpler, no routing delays
|
||||
open_checkpoint ${project_dir}/aeris10_radar.runs/synth_1/radar_system_top.dcp
|
||||
write_verilog -force -mode funcsim \
|
||||
${project_dir}/sim/post_synth_funcsim.v
|
||||
|
||||
# Also write SDF for timing sim (from routed checkpoint)
|
||||
open_checkpoint ${project_dir}/aeris10_radar.runs/impl_1/radar_system_top_routed.dcp
|
||||
write_verilog -force -mode timesim \
|
||||
${project_dir}/sim/post_impl_timesim.v
|
||||
write_sdf -force \
|
||||
${project_dir}/sim/post_impl_timesim.sdf
|
||||
|
||||
puts "INFO: All reports and netlists generated."
|
||||
puts "INFO: CDC report: ${report_dir}/cdc_report.txt"
|
||||
puts "INFO: Post-synth sim: ${project_dir}/sim/post_synth_funcsim.v"
|
||||
puts "INFO: Post-impl sim: ${project_dir}/sim/post_impl_timesim.v"
|
||||
puts "INFO: SDF: ${project_dir}/sim/post_impl_timesim.sdf"
|
||||
Reference in New Issue
Block a user