diff --git a/9_Firmware/9_2_FPGA/constraints/debug_ila.xdc b/9_Firmware/9_2_FPGA/constraints/debug_ila.xdc new file mode 100644 index 0000000..d35e85e --- /dev/null +++ b/9_Firmware/9_2_FPGA/constraints/debug_ila.xdc @@ -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}] diff --git a/9_Firmware/9_2_FPGA/constraints/xc7a200t_fbg484.xdc b/9_Firmware/9_2_FPGA/constraints/xc7a200t_fbg484.xdc index 36d6370..0f995c3 100644 --- a/9_Firmware/9_2_FPGA/constraints/xc7a200t_fbg484.xdc +++ b/9_Firmware/9_2_FPGA/constraints/xc7a200t_fbg484.xdc @@ -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}] \ # -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 # ============================================================================ diff --git a/9_Firmware/9_2_FPGA/latency_buffer_2159.v b/9_Firmware/9_2_FPGA/latency_buffer.v similarity index 92% rename from 9_Firmware/9_2_FPGA/latency_buffer_2159.v rename to 9_Firmware/9_2_FPGA/latency_buffer.v index cd09662..1fe9fed 100644 --- a/9_Firmware/9_2_FPGA/latency_buffer_2159.v +++ b/9_Firmware/9_2_FPGA/latency_buffer.v @@ -1,7 +1,9 @@ `timescale 1ns / 1ps -// latency_buffer_2159_fixed.v -module latency_buffer_2159 #( +// latency_buffer.v — Parameterized BRAM-based latency/delay buffer +// 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 LATENCY = 3187 ) ( diff --git a/9_Firmware/9_2_FPGA/radar_receiver_final.v b/9_Firmware/9_2_FPGA/radar_receiver_final.v index 0c1bce6..fa6f8ad 100644 --- a/9_Firmware/9_2_FPGA/radar_receiver_final.v +++ b/9_Firmware/9_2_FPGA/radar_receiver_final.v @@ -192,7 +192,7 @@ end wire [15:0] delayed_ref_i, delayed_ref_q; wire mem_ready_delayed; -latency_buffer_2159 #( +latency_buffer #( .DATA_WIDTH(32), // 16-bit I + 16-bit Q .LATENCY(3187) ) ref_latency_buffer ( diff --git a/9_Firmware/9_2_FPGA/scripts/ila_capture.tcl b/9_Firmware/9_2_FPGA/scripts/ila_capture.tcl new file mode 100644 index 0000000..d196f1a --- /dev/null +++ b/9_Firmware/9_2_FPGA/scripts/ila_capture.tcl @@ -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 [options] +# +# Scenarios: adc | ddc | mf | doppler | all | health +# +# Options: +# -server Hardware server (default: localhost) +# -port Hardware server port (default: 3121) +# -outdir Output directory for CSV files (default: auto-timestamped) +# -depth Capture depth override (default: 4096) +# -timeout Trigger timeout in seconds (default: 30) +# -immediate Use immediate trigger (free-running, no condition) +# -ltx 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 \[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 Hardware server hostname (default: localhost)" + puts " -port Hardware server port (default: 3121)" + puts " -outdir Output directory for CSV exports" + puts " -depth Capture depth in samples (default: 4096)" + puts " -timeout Trigger timeout in seconds (default: 30)" + puts " -immediate Free-running capture (no trigger condition)" + puts " -ltx 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." +} diff --git a/9_Firmware/9_2_FPGA/scripts/insert_ila_probes.tcl b/9_Firmware/9_2_FPGA/scripts/insert_ila_probes.tcl new file mode 100644 index 0000000..11dd171 --- /dev/null +++ b/9_Firmware/9_2_FPGA/scripts/insert_ila_probes.tcl @@ -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 diff --git a/9_Firmware/9_2_FPGA/scripts/program_fpga.tcl b/9_Firmware/9_2_FPGA/scripts/program_fpga.tcl new file mode 100644 index 0000000..a6f9e03 --- /dev/null +++ b/9_Firmware/9_2_FPGA/scripts/program_fpga.tcl @@ -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 Hardware server hostname (default: localhost) +# -port Hardware server port (default: 3121) +# -bit Bitstream file path (overrides default) +# -ltx 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 " + 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." +} diff --git a/9_Firmware/9_2_FPGA/tb/cosim/validate_mem_files.py b/9_Firmware/9_2_FPGA/tb/cosim/validate_mem_files.py index 6fa98e7..910b2d7 100644 --- a/9_Firmware/9_2_FPGA/tb/cosim/validate_mem_files.py +++ b/9_Firmware/9_2_FPGA/tb/cosim/validate_mem_files.py @@ -7,7 +7,7 @@ Checks: 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 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: python3 validate_mem_files.py @@ -479,8 +479,9 @@ def test_latency_buffer(): # Check that the module name vs parameter is consistent print(f" LATENCY parameter: {LATENCY}") - print(f" Module name: latency_buffer_2159 (historical, actual LATENCY={LATENCY})") - warn("Module name 'latency_buffer_2159' is inconsistent with LATENCY=3187 parameter") + print(f" Module name: latency_buffer (parameterized, LATENCY={LATENCY})") + # 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 # read_ptr = (write_ptr - LATENCY) mod 4096 diff --git a/9_Firmware/9_2_FPGA/tb/tb_latency_buffer.v b/9_Firmware/9_2_FPGA/tb/tb_latency_buffer.v index 9f0caf3..009bfdf 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_latency_buffer.v +++ b/9_Firmware/9_2_FPGA/tb/tb_latency_buffer.v @@ -31,7 +31,7 @@ module tb_latency_buffer; always #(CLK_PERIOD/2) clk = ~clk; // ── DUT ──────────────────────────────────────────────────── - latency_buffer_2159 #( + latency_buffer #( .DATA_WIDTH(DATA_WIDTH), .LATENCY(LATENCY) ) uut (