AMD FPGA Design Debugging

Supplementing a standard SW-configuration interface on an AMD-based FPGA design is incredibly useful for simple testing, debugging, and initial bring-up. Depending on the complexity of the task, you might choose from several methods. However, for quick implementation, a simple Virtual IO (VIO) core is often the best option. It is perfect for setting static values, triggering custom resets, checking status flags and counters, and even measuring clock frequencies. You can easily see this in action by utilizing the code snippet below and connecting it to the input of the VIO (the corresponding probe will then increment once per second).

Coarse clock frequency check
process(ACLK)
begin
  if rising_edge(ACLK) then
    if ARESETn = '0' then
      Free_Cnt <= (others => '0');
      Cnt_1s   <= (others => '0'); 
    else
      if Free_Cnt = slv(to_unsigned(EXPECTED_CLOCK_FREQUENCY_HZ - 1,32)) then
        Free_Cnt <= (others => '0');
        Cnt_1s   <= slv(unsigned(Cnt_1s) + 1);
      else
        Free_Cnt <= slv(unsigned(Free_Cnt) + 1);
      end if;
    end if;
  end if;
end process;

For more complex designs that utilize APB, AXI4-Lite, or similar interfaces, it is usually more convenient to use a JTAG-to-AXI Master combined with a custom TCL script. This allows you to initiate the exact same configuration sequences normally handled by a standard software interface. A major benefit of this approach is that Vivado Lab Edition is completely sufficient; you can execute the entire sequence simply by running "source -notrace MyScript.tcl". For this method, sticking to the AXI4-Lite interface is generally the best practice, as opting for full AXI4 often just adds unnecessary complexity regarding data width and protocol conversions.

However, if you need to configure multiple endpoints or require the higher performance of a full AXI4 interface, using the Vivado IP Integrator is highly recommended. It allows you to quickly add essential infrastructure IPs for clock, width, and protocol conversions. Furthermore, it simplifies integrating external DDRx memory, which is invaluable when you need to upload or download large datasets, such as images, databases, or test vectors. A typical block design example is shown below.

TCL Script for External Memory Access via AXI4
#############################################################
######## JTAG BURST SCRIPT - Linear Memory Read/Write #######
#############################################################
# NOTE: Usage "source -notrace JTAG_MemTest.tcl"
# NOTE: This script requires Full AXI4-Interface
set JTAG_MASTER [get_hw_axis hw_axi_1]
set start_time [clock milliseconds]

#####################################
###### Parameters Definitions #######
set BASE_ADDR     0x00000000
set NUM_WORDS     1024
set OUTPUT_FILE   "JTAG_Linear_Dump.txt"
set MAX_BURST_LEN 256

#############################################################
######## Linear Write Test to External DDRx Memory   ########
puts "============================================================"
puts "Starting Programming of $NUM_WORDS words at base address ..."

set words_written 0
while {$words_written < $NUM_WORDS} {
    set remaining [expr {$NUM_WORDS - $words_written}]
    set burst_len [expr {$remaining > $MAX_BURST_LEN ? $MAX_BURST_LEN : $remaining}]

    ##################################################
    ###### Setup Custom Data Word [MSB --> LSB] ######
    set burst_data_hex ""
    for {set w [expr {$burst_len - 1}]} {$w >= 0} {incr w -1} {
        set val [expr {$words_written + $w}]
        append burst_data_hex [format "%08X" $val]
    }

    ##########################################
    ####### Setup Transaction Address  #######
    set current_addr [expr {$BASE_ADDR + ($words_written * 4)}]
    set hex_addr [format "0x%08X" $current_addr]

    ###################################################
    ####### Create and Issue Write Transaction #######
    create_hw_axi_txn WR_BURST $JTAG_MASTER -address $hex_addr -type write -len $burst_len -force -data "0x$burst_data_hex"
    run_hw_axi -quiet [get_hw_axi_txn WR_BURST]
    delete_hw_axi_txn [get_hw_axi_txn WR_BURST]

    incr words_written $burst_len

    ###################################
    ###### Show Progress ..... ########
    if {($words_written % 128) == 0 || $words_written == $NUM_WORDS} {
        puts "Written: $words_written / $NUM_WORDS Words"
    }
}

############################################################
######## Linear Read Test to External DDRx Memory   ########
puts "===================================="
puts "Begin Memory Dump to $OUTPUT_FILE..."
set file_id [open $OUTPUT_FILE w]

set words_read 0

while {$words_read < $NUM_WORDS} {
    set remaining [expr {$NUM_WORDS - $words_read}]
    set burst_len [expr {$remaining > $MAX_BURST_LEN ? $MAX_BURST_LEN : $remaining}]

    ########################################
    ###### Setup Transaction Address #######
    set current_addr [expr {$BASE_ADDR + ($words_read * 4)}]
    set hex_addr [format "0x%08X" $current_addr]

    ##################################################
    ####### Create And Launch Read Transaction #######
    create_hw_axi_txn RD_BURST $JTAG_MASTER -address $hex_addr -type read -len $burst_len -force
    run_hw_axi -quiet [get_hw_axi_txn RD_BURST]

    ################################################
    ####### Extract Transaction's Hex String #######
    set raw_data [get_property DATA [get_hw_axi_txn RD_BURST]]
    delete_hw_axi_txn [get_hw_axi_txn RD_BURST]

    #######################################################
    ####### Ensure Correct Alignment of Hex Strings #######
    set expected_len [expr {$burst_len * 8}]
    set pad_len [expr {$expected_len - [string length $raw_data]}]
    if {$pad_len > 0} {
        set raw_data "[string repeat "0" $pad_len]$raw_data"
    }

    ######################################################
    ####### Separate, Fix and Write to Output File #######
    set data_len [string length $raw_data]
    for {set w 0} {$w < $burst_len} {incr w} {
        ###########################################
        ####### First Data are LSB - Based ########
        set right_idx [expr {$data_len - 1 - ($w * 8)}]
        set left_idx [expr {$right_idx - 7}]
        set word_hex [string range $raw_data $left_idx $right_idx]
        set word_addr [format "0x%08X" [expr {$current_addr + ($w * 4)}]]
        
        # Write to file in format: Address: Data
        puts $file_id "$word_addr: 0x$word_hex"
    }

    incr words_read $burst_len

    ######################################
    ####### Show Some Progress ... #######
    if {($words_read % 128) == 0 || $words_read == $NUM_WORDS} {
        puts "Read: $words_read / $NUM_WORDS Words"
    }
}

close $file_id

###########################
####### End of Story ######
set end_time [clock milliseconds]
set duration_ms [expr {$end_time - $start_time}]
set duration_sec [expr {$duration_ms / 1000.0}]
puts "Data dump Complete in $duration_sec seconds. Check Output File!"

For more advanced use cases requiring dynamic debugging and interaction with interfaces like UART, SPI, or I2C, embedding a MicroBlaze soft processor and writing the control logic in C is usually the best approach. The hardware design can remain quite compact. At a minimum, you need the MicroBlaze core, local program memory (BRAM), and an AXI4-Lite interface. Adding an AXI Uartlite IP is also highly recommended so you can monitor the microcontroller in action via a serial terminal.

A crucial tip during the hardware design phase is to check the Address Editor to ensure your local memory is sufficiently large (e.g., 64KB or 128KB) to hold your compiled C program. Once the block design is complete, the flow requires exporting the hardware handoff file (.xsa) and launching the Vitis IDE to write your software. Thanks to modern AI assistants, writing the boilerplate C code for these peripherals has never been easier. Finally, you have two execution paths: you can dynamically load and debug the application using the Vitis debugger via JTAG, or you can associate the compiled .elf file back in Vivado to embed it directly into the final bitstream for a standalone boot.

TCL Script for JTAG-Master Block Design

################################################################
# This is a generated script based on design: Example_Design
#
# Though there are limitations about the generated script,
# the main purpose of this utility is to make learning
# IP Integrator Tcl commands easier.
################################################################

namespace eval _tcl {
proc get_script_folder {} {
   set script_path [file normalize [info script]]
   set script_folder [file dirname $script_path]
   return $script_folder
}
}
variable script_folder
set script_folder [_tcl::get_script_folder]

################################################################
# Check if script is running in correct Vivado version.
################################################################
set scripts_vivado_version 2025.2
set current_vivado_version [version -short]

if { [string first $scripts_vivado_version $current_vivado_version] == -1 } {
   puts ""
   if { [string compare $scripts_vivado_version $current_vivado_version] > 0 } {
      catch {common::send_gid_msg -ssname BD::TCL -id 2042 -severity "ERROR" " This script was generated using Vivado <$scripts_vivado_version> and is being run in <$current_vivado_version> of Vivado. Sourcing the script failed since it was created with a future version of Vivado."}

   } else {
     catch {common::send_gid_msg -ssname BD::TCL -id 2041 -severity "ERROR" "This script was generated using Vivado <$scripts_vivado_version> and is being run in <$current_vivado_version> of Vivado. Please run the script in Vivado <$scripts_vivado_version> then open the design in Vivado <$current_vivado_version>. Upgrade the design by running \"Tools => Report => Report IP Status...\", then run write_bd_tcl to create an updated script."}

   }

   return 1
}

################################################################
# START
################################################################

# To test this script, run the following commands from Vivado Tcl console:
# source Example_Design_script.tcl

# If there is no project opened, this script will create a
# project, but make sure you do not have an existing project
# <./myproj/project_1.xpr> in the current working folder.

set list_projs [get_projects -quiet]
if { $list_projs eq "" } {
   create_project project_1 myproj -part xcku5p-ffvb676-2-e
   set_property BOARD_PART xilinx.com:kcu116:part0:1.5 [current_project]
}


# CHANGE DESIGN NAME HERE
variable design_name
set design_name Example_Design

# This script was generated for a remote BD. To create a non-remote design,
# change the variable <run_remote_bd_flow> to <0>.

set run_remote_bd_flow 1
if { $run_remote_bd_flow == 1 } {
  # Set the reference directory for source file relative paths (by default 
  # the value is script directory path)
  set origin_dir ./Sources

  # Use origin directory path location variable, if specified in the tcl shell
  if { [info exists ::origin_dir_loc] } {
     set origin_dir $::origin_dir_loc
  }

  set str_bd_folder [file normalize ${origin_dir}]
  set str_bd_filepath ${str_bd_folder}/${design_name}/${design_name}.bd

  # Check if remote design exists on disk
  if { [file exists $str_bd_filepath ] == 1 } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2030 -severity "ERROR" "The remote BD file path <$str_bd_filepath> already exists!"}
     common::send_gid_msg -ssname BD::TCL -id 2031 -severity "INFO" "To create a non-remote BD, change the variable <run_remote_bd_flow> to <0>."
     common::send_gid_msg -ssname BD::TCL -id 2032 -severity "INFO" "Also make sure there is no design <$design_name> existing in your current project."

     return 1
  }

  # Check if design exists in memory
  set list_existing_designs [get_bd_designs -quiet $design_name]
  if { $list_existing_designs ne "" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2033 -severity "ERROR" "The design <$design_name> already exists in this project! Will not create the remote BD <$design_name> at the folder <$str_bd_folder>."}

     common::send_gid_msg -ssname BD::TCL -id 2034 -severity "INFO" "To create a non-remote BD, change the variable <run_remote_bd_flow> to <0> or please set a different value to variable <design_name>."

     return 1
  }

  # Check if design exists on disk within project
  set list_existing_designs [get_files -quiet */${design_name}.bd]
  if { $list_existing_designs ne "" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2035 -severity "ERROR" "The design <$design_name> already exists in this project at location:
    $list_existing_designs"}
     catch {common::send_gid_msg -ssname BD::TCL -id 2036 -severity "ERROR" "Will not create the remote BD <$design_name> at the folder <$str_bd_folder>."}

     common::send_gid_msg -ssname BD::TCL -id 2037 -severity "INFO" "To create a non-remote BD, change the variable <run_remote_bd_flow> to <0> or please set a different value to variable <design_name>."

     return 1
  }

  # Now can create the remote BD
  # NOTE - usage of <-dir> will create <$str_bd_folder/$design_name/$design_name.bd>
  create_bd_design -dir $str_bd_folder $design_name
} else {

  # Create regular design
  if { [catch {create_bd_design $design_name} errmsg] } {
     common::send_gid_msg -ssname BD::TCL -id 2038 -severity "INFO" "Please set a different value to variable <design_name>."

     return 1
  }
}

current_bd_design $design_name

set bCheckIPsPassed 1
##################################################################
# CHECK IPs
##################################################################
set bCheckIPs 1
if { $bCheckIPs == 1 } {
   set list_check_ips "\ 
xilinx.com:ip:ddr4:2.2\
xilinx.com:ip:jtag_axi:1.2\
xilinx.com:ip:axi_clock_converter:2.1\
xilinx.com:ip:axi_protocol_converter:2.1\
xilinx.com:ip:proc_sys_reset:5.0\
xilinx.com:ip:util_vector_logic:2.0\
"

   set list_ips_missing ""
   common::send_gid_msg -ssname BD::TCL -id 2011 -severity "INFO" "Checking if the following IPs exist in the project's IP catalog: $list_check_ips ."

   foreach ip_vlnv $list_check_ips {
      set ip_obj [get_ipdefs -all $ip_vlnv]
      if { $ip_obj eq "" } {
         lappend list_ips_missing $ip_vlnv
      }
   }

   if { $list_ips_missing ne "" } {
      catch {common::send_gid_msg -ssname BD::TCL -id 2012 -severity "ERROR" "The following IPs are not found in the IP Catalog:\n  $list_ips_missing\n\nResolution: Please add the repository containing the IP(s) to the project." }
      set bCheckIPsPassed 0
   }

}

if { $bCheckIPsPassed != 1 } {
  common::send_gid_msg -ssname BD::TCL -id 2023 -severity "WARNING" "Will not continue with creation of design due to the error(s) above."
  return 3
}

##################################################################
# DESIGN PROCs
##################################################################



# Procedure to create entire design; Provide argument to make
# procedure reusable. If parentCell is "", will use root.
proc create_root_design { parentCell } {

  variable script_folder
  variable design_name

  if { $parentCell eq "" } {
     set parentCell [get_bd_cells /]
  }

  # Get object for parentCell
  set parentObj [get_bd_cells $parentCell]
  if { $parentObj == "" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2090 -severity "ERROR" "Unable to find parent cell <$parentCell>!"}
     return
  }

  # Make sure parentObj is hier blk
  set parentType [get_property TYPE $parentObj]
  if { $parentType ne "hier" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2091 -severity "ERROR" "Parent <$parentObj> has TYPE = <$parentType>. Expected to be <hier>."}
     return
  }

  # Save current instance; Restore later
  set oldCurInst [current_bd_instance .]

  # Set parent object as current
  current_bd_instance $parentObj


  # Create interface ports
  set M_AXIL_RTL [ create_bd_intf_port -mode Master -vlnv xilinx.com:interface:aximm_rtl:1.0 M_AXIL_RTL ]
  set_property -dict [ list \
   CONFIG.ADDR_WIDTH {32} \
   CONFIG.DATA_WIDTH {32} \
   CONFIG.HAS_BURST {0} \
   CONFIG.HAS_CACHE {0} \
   CONFIG.HAS_LOCK {0} \
   CONFIG.HAS_QOS {0} \
   CONFIG.HAS_REGION {0} \
   CONFIG.PROTOCOL {AXI4LITE} \
   ] $M_AXIL_RTL

  set C0_DDR4 [ create_bd_intf_port -mode Master -vlnv xilinx.com:interface:ddr4_rtl:1.0 C0_DDR4 ]


  # Create ports
  set SYS_ACLK [ create_bd_port -dir I -type clk -freq_hz 300000000 SYS_ACLK ]
  set SYS_ARESETn [ create_bd_port -dir I -type rst SYS_ARESETn ]
  set M_ACLK [ create_bd_port -dir O -type clk M_ACLK ]
  set_property -dict [ list \
   CONFIG.ASSOCIATED_BUSIF {M_AXIL_RTL} \
   CONFIG.ASSOCIATED_RESET {M_ARESETn} \
 ] $M_ACLK
  set M_ARESETn [ create_bd_port -dir O -from 0 -to 0 -type rst M_ARESETn ]

  # Create instance: KCU_DDR, and set properties
  set KCU_DDR [ create_bd_cell -type ip -vlnv xilinx.com:ip:ddr4:2.2 KCU_DDR ]
  set_property -dict [list \
    CONFIG.ADDN_UI_CLKOUT1_FREQ_HZ {100} \
    CONFIG.C0.DDR4_Specify_MandD {false} \
    CONFIG.C0_CLOCK_BOARD_INTERFACE {Custom} \
    CONFIG.C0_DDR4_BOARD_INTERFACE {ddr4_sdram_062} \
    CONFIG.System_Clock {No_Buffer} \
  ] $KCU_DDR


  # Create instance: JTAG_MASTER, and set properties
  set JTAG_MASTER [ create_bd_cell -type ip -vlnv xilinx.com:ip:jtag_axi:1.2 JTAG_MASTER ]

  # Create instance: AXI_IC, and set properties
  set AXI_IC [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 AXI_IC ]

  # Create instance: AXI_CLK_CONV, and set properties
  set AXI_CLK_CONV [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_clock_converter:2.1 AXI_CLK_CONV ]

  # Create instance: AXI4_TO_AXI4_Lite, and set properties
  set AXI4_TO_AXI4_Lite [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_protocol_converter:2.1 AXI4_TO_AXI4_Lite ]
  set_property CONFIG.MI_PROTOCOL {AXI4LITE} $AXI4_TO_AXI4_Lite


  # Create instance: SYS_RESET, and set properties
  set SYS_RESET [ create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset:5.0 SYS_RESET ]

  # Create instance: LOGIC_NOT, and set properties
  set LOGIC_NOT [ create_bd_cell -type ip -vlnv xilinx.com:ip:util_vector_logic:2.0 LOGIC_NOT ]
  set_property -dict [list \
    CONFIG.C_OPERATION {not} \
    CONFIG.C_SIZE {1} \
  ] $LOGIC_NOT


  # Create interface connections
  connect_bd_intf_net -intf_net AXI4_TO_AXI4_Lite_M_AXI [get_bd_intf_ports M_AXIL_RTL] [get_bd_intf_pins AXI4_TO_AXI4_Lite/M_AXI]
  connect_bd_intf_net -intf_net AXI_IC_M01_AXI [get_bd_intf_pins AXI4_TO_AXI4_Lite/S_AXI] [get_bd_intf_pins AXI_IC/M01_AXI]
  connect_bd_intf_net -intf_net KCU_DDR_C0_DDR4 [get_bd_intf_ports C0_DDR4] [get_bd_intf_pins KCU_DDR/C0_DDR4]
  connect_bd_intf_net -intf_net axi_clock_converter_0_M_AXI [get_bd_intf_pins AXI_CLK_CONV/M_AXI] [get_bd_intf_pins AXI_IC/S00_AXI]
  connect_bd_intf_net -intf_net axi_interconnect_0_M00_AXI [get_bd_intf_pins KCU_DDR/C0_DDR4_S_AXI] [get_bd_intf_pins AXI_IC/M00_AXI]
  connect_bd_intf_net -intf_net jtag_axi_0_M_AXI [get_bd_intf_pins AXI_CLK_CONV/S_AXI] [get_bd_intf_pins JTAG_MASTER/M_AXI]

  # Create port connections
  connect_bd_net -net ACLK_1  [get_bd_pins KCU_DDR/c0_ddr4_ui_clk] \
  [get_bd_pins AXI_IC/ACLK] \
  [get_bd_pins AXI_IC/S00_ACLK] \
  [get_bd_pins AXI_IC/M00_ACLK] \
  [get_bd_pins AXI_IC/M01_ACLK] \
  [get_bd_pins AXI_CLK_CONV/m_axi_aclk] \
  [get_bd_pins AXI4_TO_AXI4_Lite/aclk] \
  [get_bd_pins SYS_RESET/slowest_sync_clk] \
  [get_bd_ports M_ACLK]
  connect_bd_net -net KCU_DDR_c0_ddr4_ui_clk_sync_rst  [get_bd_pins KCU_DDR/c0_ddr4_ui_clk_sync_rst] \
  [get_bd_pins SYS_RESET/ext_reset_in]
  connect_bd_net -net SYS_ARESETn_1  [get_bd_ports SYS_ARESETn] \
  [get_bd_pins JTAG_MASTER/aresetn] \
  [get_bd_pins LOGIC_NOT/Op1] \
  [get_bd_pins AXI_CLK_CONV/s_axi_aresetn]
  connect_bd_net -net SYS_RESET_interconnect_aresetn  [get_bd_pins SYS_RESET/interconnect_aresetn] \
  [get_bd_pins AXI_IC/ARESETN] \
  [get_bd_pins AXI_IC/S00_ARESETN] \
  [get_bd_pins AXI_IC/M00_ARESETN] \
  [get_bd_pins AXI_IC/M01_ARESETN] \
  [get_bd_pins AXI_CLK_CONV/m_axi_aresetn] \
  [get_bd_pins AXI4_TO_AXI4_Lite/aresetn] \
  [get_bd_ports M_ARESETn]
  connect_bd_net -net SYS_RESET_peripheral_aresetn  [get_bd_pins SYS_RESET/peripheral_aresetn] \
  [get_bd_pins KCU_DDR/c0_ddr4_aresetn]
  connect_bd_net -net c0_sys_clk_i_0_1  [get_bd_ports SYS_ACLK] \
  [get_bd_pins KCU_DDR/c0_sys_clk_i] \
  [get_bd_pins JTAG_MASTER/aclk] \
  [get_bd_pins AXI_CLK_CONV/s_axi_aclk]
  connect_bd_net -net util_vector_logic_0_Res  [get_bd_pins LOGIC_NOT/Res] \
  [get_bd_pins KCU_DDR/sys_rst]

  # Create address segments
  assign_bd_address -offset 0x00000000 -range 0x40000000 -target_address_space [get_bd_addr_spaces JTAG_MASTER/Data] [get_bd_addr_segs KCU_DDR/C0_DDR4_MEMORY_MAP/C0_DDR4_ADDRESS_BLOCK] -force
  assign_bd_address -offset 0x80000000 -range 0x00010000 -target_address_space [get_bd_addr_spaces JTAG_MASTER/Data] [get_bd_addr_segs M_AXIL_RTL/Reg] -force


  # Restore current instance
  current_bd_instance $oldCurInst

  validate_bd_design
  save_bd_design
}
# End of create_root_design()


##################################################################
# MAIN FLOW
##################################################################

create_root_design ""


TCL Script for Microcontroller Block Design

################################################################
# This is a generated script based on design: Example_MC
#
# Though there are limitations about the generated script,
# the main purpose of this utility is to make learning
# IP Integrator Tcl commands easier.
################################################################

namespace eval _tcl {
proc get_script_folder {} {
   set script_path [file normalize [info script]]
   set script_folder [file dirname $script_path]
   return $script_folder
}
}
variable script_folder
set script_folder [_tcl::get_script_folder]

################################################################
# Check if script is running in correct Vivado version.
################################################################
set scripts_vivado_version 2025.2
set current_vivado_version [version -short]

if { [string first $scripts_vivado_version $current_vivado_version] == -1 } {
   puts ""
   if { [string compare $scripts_vivado_version $current_vivado_version] > 0 } {
      catch {common::send_gid_msg -ssname BD::TCL -id 2042 -severity "ERROR" " This script was generated using Vivado <$scripts_vivado_version> and is being run in <$current_vivado_version> of Vivado. Sourcing the script failed since it was created with a future version of Vivado."}

   } else {
     catch {common::send_gid_msg -ssname BD::TCL -id 2041 -severity "ERROR" "This script was generated using Vivado <$scripts_vivado_version> and is being run in <$current_vivado_version> of Vivado. Please run the script in Vivado <$scripts_vivado_version> then open the design in Vivado <$current_vivado_version>. Upgrade the design by running \"Tools => Report => Report IP Status...\", then run write_bd_tcl to create an updated script."}

   }

   return 1
}

################################################################
# START
################################################################

# To test this script, run the following commands from Vivado Tcl console:
# source Example_MC_script.tcl

# If there is no project opened, this script will create a
# project, but make sure you do not have an existing project
# <./myproj/project_1.xpr> in the current working folder.

set list_projs [get_projects -quiet]
if { $list_projs eq "" } {
   create_project project_1 myproj -part xcku5p-ffvb676-2-e
   set_property BOARD_PART xilinx.com:kcu116:part0:1.5 [current_project]
}


# CHANGE DESIGN NAME HERE
variable design_name
set design_name Example_MC

# This script was generated for a remote BD. To create a non-remote design,
# change the variable <run_remote_bd_flow> to <0>.

set run_remote_bd_flow 1
if { $run_remote_bd_flow == 1 } {
  # Set the reference directory for source file relative paths (by default 
  # the value is script directory path)
  set origin_dir ./Sources

  # Use origin directory path location variable, if specified in the tcl shell
  if { [info exists ::origin_dir_loc] } {
     set origin_dir $::origin_dir_loc
  }

  set str_bd_folder [file normalize ${origin_dir}]
  set str_bd_filepath ${str_bd_folder}/${design_name}/${design_name}.bd

  # Check if remote design exists on disk
  if { [file exists $str_bd_filepath ] == 1 } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2030 -severity "ERROR" "The remote BD file path <$str_bd_filepath> already exists!"}
     common::send_gid_msg -ssname BD::TCL -id 2031 -severity "INFO" "To create a non-remote BD, change the variable <run_remote_bd_flow> to <0>."
     common::send_gid_msg -ssname BD::TCL -id 2032 -severity "INFO" "Also make sure there is no design <$design_name> existing in your current project."

     return 1
  }

  # Check if design exists in memory
  set list_existing_designs [get_bd_designs -quiet $design_name]
  if { $list_existing_designs ne "" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2033 -severity "ERROR" "The design <$design_name> already exists in this project! Will not create the remote BD <$design_name> at the folder <$str_bd_folder>."}

     common::send_gid_msg -ssname BD::TCL -id 2034 -severity "INFO" "To create a non-remote BD, change the variable <run_remote_bd_flow> to <0> or please set a different value to variable <design_name>."

     return 1
  }

  # Check if design exists on disk within project
  set list_existing_designs [get_files -quiet */${design_name}.bd]
  if { $list_existing_designs ne "" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2035 -severity "ERROR" "The design <$design_name> already exists in this project at location:
    $list_existing_designs"}
     catch {common::send_gid_msg -ssname BD::TCL -id 2036 -severity "ERROR" "Will not create the remote BD <$design_name> at the folder <$str_bd_folder>."}

     common::send_gid_msg -ssname BD::TCL -id 2037 -severity "INFO" "To create a non-remote BD, change the variable <run_remote_bd_flow> to <0> or please set a different value to variable <design_name>."

     return 1
  }

  # Now can create the remote BD
  # NOTE - usage of <-dir> will create <$str_bd_folder/$design_name/$design_name.bd>
  create_bd_design -dir $str_bd_folder $design_name
} else {

  # Create regular design
  if { [catch {create_bd_design $design_name} errmsg] } {
     common::send_gid_msg -ssname BD::TCL -id 2038 -severity "INFO" "Please set a different value to variable <design_name>."

     return 1
  }
}

current_bd_design $design_name

set bCheckIPsPassed 1
##################################################################
# CHECK IPs
##################################################################
set bCheckIPs 1
if { $bCheckIPs == 1 } {
   set list_check_ips "\ 
xilinx.com:ip:microblaze:11.0\
xilinx.com:ip:proc_sys_reset:5.0\
xilinx.com:ip:mdm:3.2\
xilinx.com:ip:axi_uartlite:2.0\
xilinx.com:ip:lmb_v10:3.0\
xilinx.com:ip:blk_mem_gen:8.4\
xilinx.com:ip:lmb_bram_if_cntlr:4.0\
"

   set list_ips_missing ""
   common::send_gid_msg -ssname BD::TCL -id 2011 -severity "INFO" "Checking if the following IPs exist in the project's IP catalog: $list_check_ips ."

   foreach ip_vlnv $list_check_ips {
      set ip_obj [get_ipdefs -all $ip_vlnv]
      if { $ip_obj eq "" } {
         lappend list_ips_missing $ip_vlnv
      }
   }

   if { $list_ips_missing ne "" } {
      catch {common::send_gid_msg -ssname BD::TCL -id 2012 -severity "ERROR" "The following IPs are not found in the IP Catalog:\n  $list_ips_missing\n\nResolution: Please add the repository containing the IP(s) to the project." }
      set bCheckIPsPassed 0
   }

}

if { $bCheckIPsPassed != 1 } {
  common::send_gid_msg -ssname BD::TCL -id 2023 -severity "WARNING" "Will not continue with creation of design due to the error(s) above."
  return 3
}

##################################################################
# DESIGN PROCs
##################################################################


# Hierarchical cell: MC_Memory
proc create_hier_cell_MC_Memory { parentCell nameHier } {

  variable script_folder

  if { $parentCell eq "" || $nameHier eq "" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2092 -severity "ERROR" "create_hier_cell_MC_Memory() - Empty argument(s)!"}
     return
  }

  # Get object for parentCell
  set parentObj [get_bd_cells $parentCell]
  if { $parentObj == "" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2090 -severity "ERROR" "Unable to find parent cell <$parentCell>!"}
     return
  }

  # Make sure parentObj is hier blk
  set parentType [get_property TYPE $parentObj]
  if { $parentType ne "hier" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2091 -severity "ERROR" "Parent <$parentObj> has TYPE = <$parentType>. Expected to be <hier>."}
     return
  }

  # Save current instance; Restore later
  set oldCurInst [current_bd_instance .]

  # Set parent object as current
  current_bd_instance $parentObj

  # Create cell and set as current instance
  set hier_obj [create_bd_cell -type hier $nameHier]
  current_bd_instance $hier_obj

  # Create interface pins
  create_bd_intf_pin -mode MirroredMaster -vlnv xilinx.com:interface:lmb_rtl:1.0 LMB_M

  create_bd_intf_pin -mode MirroredMaster -vlnv xilinx.com:interface:lmb_rtl:1.0 LMB_M1


  # Create pins
  create_bd_pin -dir I -type clk ACLK_FREE
  create_bd_pin -dir I -type rst SYS_Rst

  # Create instance: ILMB_CNTRL, and set properties
  set ILMB_CNTRL [ create_bd_cell -type ip -vlnv xilinx.com:ip:lmb_v10:3.0 ILMB_CNTRL ]

  # Create instance: PROGRAM_DATA, and set properties
  set PROGRAM_DATA [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 PROGRAM_DATA ]
  set_property CONFIG.Memory_Type {True_Dual_Port_RAM} $PROGRAM_DATA


  # Create instance: ILMB_BRAM_CNTRL, and set properties
  set ILMB_BRAM_CNTRL [ create_bd_cell -type ip -vlnv xilinx.com:ip:lmb_bram_if_cntlr:4.0 ILMB_BRAM_CNTRL ]

  # Create instance: DLMB_CNTRL, and set properties
  set DLMB_CNTRL [ create_bd_cell -type ip -vlnv xilinx.com:ip:lmb_v10:3.0 DLMB_CNTRL ]

  # Create instance: DLMB_BRAM_CNTRL, and set properties
  set DLMB_BRAM_CNTRL [ create_bd_cell -type ip -vlnv xilinx.com:ip:lmb_bram_if_cntlr:4.0 DLMB_BRAM_CNTRL ]

  # Create interface connections
  connect_bd_intf_net -intf_net Conn [get_bd_intf_pins ILMB_BRAM_CNTRL/SLMB] [get_bd_intf_pins ILMB_CNTRL/LMB_Sl_0]
  connect_bd_intf_net -intf_net Conn1 [get_bd_intf_pins DLMB_BRAM_CNTRL/SLMB] [get_bd_intf_pins DLMB_CNTRL/LMB_Sl_0]
  connect_bd_intf_net -intf_net DLMB_BRAM_CNTRL_0_BRAM_PORT [get_bd_intf_pins PROGRAM_DATA/BRAM_PORTA] [get_bd_intf_pins DLMB_BRAM_CNTRL/BRAM_PORT]
  connect_bd_intf_net -intf_net ILMB_BRAM_CNTRL_BRAM_PORT [get_bd_intf_pins PROGRAM_DATA/BRAM_PORTB] [get_bd_intf_pins ILMB_BRAM_CNTRL/BRAM_PORT]
  connect_bd_intf_net -intf_net microblaze_0_DLMB [get_bd_intf_pins LMB_M1] [get_bd_intf_pins DLMB_CNTRL/LMB_M]
  connect_bd_intf_net -intf_net microblaze_0_ILMB [get_bd_intf_pins LMB_M] [get_bd_intf_pins ILMB_CNTRL/LMB_M]

  # Create port connections
  connect_bd_net -net Clk_0_1  [get_bd_pins ACLK_FREE] \
  [get_bd_pins DLMB_CNTRL/LMB_Clk] \
  [get_bd_pins ILMB_BRAM_CNTRL/LMB_Clk] \
  [get_bd_pins DLMB_BRAM_CNTRL/LMB_Clk] \
  [get_bd_pins ILMB_CNTRL/LMB_Clk]
  connect_bd_net -net proc_sys_reset_0_bus_struct_reset  [get_bd_pins SYS_Rst] \
  [get_bd_pins DLMB_CNTRL/SYS_Rst] \
  [get_bd_pins ILMB_BRAM_CNTRL/LMB_Rst] \
  [get_bd_pins DLMB_BRAM_CNTRL/LMB_Rst] \
  [get_bd_pins ILMB_CNTRL/SYS_Rst]

  # Restore current instance
  current_bd_instance $oldCurInst
}


# Procedure to create entire design; Provide argument to make
# procedure reusable. If parentCell is "", will use root.
proc create_root_design { parentCell } {

  variable script_folder
  variable design_name

  if { $parentCell eq "" } {
     set parentCell [get_bd_cells /]
  }

  # Get object for parentCell
  set parentObj [get_bd_cells $parentCell]
  if { $parentObj == "" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2090 -severity "ERROR" "Unable to find parent cell <$parentCell>!"}
     return
  }

  # Make sure parentObj is hier blk
  set parentType [get_property TYPE $parentObj]
  if { $parentType ne "hier" } {
     catch {common::send_gid_msg -ssname BD::TCL -id 2091 -severity "ERROR" "Parent <$parentObj> has TYPE = <$parentType>. Expected to be <hier>."}
     return
  }

  # Save current instance; Restore later
  set oldCurInst [current_bd_instance .]

  # Set parent object as current
  current_bd_instance $parentObj


  # Create interface ports
  set MC_UART [ create_bd_intf_port -mode Master -vlnv xilinx.com:interface:uart_rtl:1.0 MC_UART ]

  set MC_AXIL [ create_bd_intf_port -mode Master -vlnv xilinx.com:interface:aximm_rtl:1.0 MC_AXIL ]
  set_property -dict [ list \
   CONFIG.ADDR_WIDTH {32} \
   CONFIG.DATA_WIDTH {32} \
   CONFIG.FREQ_HZ {200000000} \
   CONFIG.HAS_BURST {0} \
   CONFIG.HAS_CACHE {0} \
   CONFIG.HAS_LOCK {0} \
   CONFIG.HAS_QOS {0} \
   CONFIG.HAS_REGION {0} \
   CONFIG.PROTOCOL {AXI4LITE} \
   ] $MC_AXIL


  # Create ports
  set ACLK_FREE [ create_bd_port -dir I -type clk -freq_hz 200000000 ACLK_FREE ]
  set_property -dict [ list \
   CONFIG.ASSOCIATED_BUSIF {MC_AXIL} \
   CONFIG.ASSOCIATED_RESET {SYS_RST:MC_ARESETn} \
 ] $ACLK_FREE
  set SYS_RST [ create_bd_port -dir I -type rst SYS_RST ]
  set_property -dict [ list \
   CONFIG.POLARITY {ACTIVE_HIGH} \
 ] $SYS_RST
  set MC_ARESETn [ create_bd_port -dir O -from 0 -to 0 -type rst MC_ARESETn ]

  # Create instance: Micro_Cntrl, and set properties
  set Micro_Cntrl [ create_bd_cell -type ip -vlnv xilinx.com:ip:microblaze:11.0 Micro_Cntrl ]
  set_property -dict [list \
    CONFIG.C_USE_DIV {1} \
    CONFIG.C_USE_FPU {1} \
    CONFIG.G_TEMPLATE_LIST {8} \
  ] $Micro_Cntrl


  # Create instance: Micro_Reset, and set properties
  set Micro_Reset [ create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset:5.0 Micro_Reset ]

  # Create instance: Micro_Debug, and set properties
  set Micro_Debug [ create_bd_cell -type ip -vlnv xilinx.com:ip:mdm:3.2 Micro_Debug ]

  # Create instance: Micro_UART, and set properties
  set Micro_UART [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_uartlite:2.0 Micro_UART ]

  # Create instance: AXI_IC, and set properties
  set AXI_IC [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 AXI_IC ]

  # Create instance: MC_Memory
  create_hier_cell_MC_Memory [current_bd_instance .] MC_Memory

  # Create interface connections
  connect_bd_intf_net -intf_net AXI_IC_M01_AXI [get_bd_intf_ports MC_AXIL] [get_bd_intf_pins AXI_IC/M01_AXI]
  connect_bd_intf_net -intf_net S00_AXI_1 [get_bd_intf_pins AXI_IC/S00_AXI] [get_bd_intf_pins Micro_Cntrl/M_AXI_DP]
  connect_bd_intf_net -intf_net axi_interconnect_0_M00_AXI [get_bd_intf_pins Micro_UART/S_AXI] [get_bd_intf_pins AXI_IC/M00_AXI]
  connect_bd_intf_net -intf_net axi_uartlite_0_UART [get_bd_intf_ports MC_UART] [get_bd_intf_pins Micro_UART/UART]
  connect_bd_intf_net -intf_net mdm_0_MBDEBUG_0 [get_bd_intf_pins Micro_Debug/MBDEBUG_0] [get_bd_intf_pins Micro_Cntrl/DEBUG]
  connect_bd_intf_net -intf_net microblaze_0_DLMB [get_bd_intf_pins Micro_Cntrl/DLMB] [get_bd_intf_pins MC_Memory/LMB_M1]
  connect_bd_intf_net -intf_net microblaze_0_ILMB [get_bd_intf_pins MC_Memory/LMB_M] [get_bd_intf_pins Micro_Cntrl/ILMB]

  # Create port connections
  connect_bd_net -net Clk_0_1  [get_bd_ports ACLK_FREE] \
  [get_bd_pins Micro_Cntrl/Clk] \
  [get_bd_pins Micro_Reset/slowest_sync_clk] \
  [get_bd_pins Micro_UART/s_axi_aclk] \
  [get_bd_pins AXI_IC/ACLK] \
  [get_bd_pins AXI_IC/S00_ACLK] \
  [get_bd_pins AXI_IC/M00_ACLK] \
  [get_bd_pins AXI_IC/M01_ACLK] \
  [get_bd_pins MC_Memory/ACLK_FREE]
  connect_bd_net -net ext_reset_in_0_1  [get_bd_ports SYS_RST] \
  [get_bd_pins Micro_Reset/ext_reset_in]
  connect_bd_net -net mdm_0_Debug_SYS_Rst  [get_bd_pins Micro_Debug/Debug_SYS_Rst] \
  [get_bd_pins Micro_Reset/mb_debug_sys_rst]
  connect_bd_net -net proc_sys_reset_0_bus_struct_reset  [get_bd_pins Micro_Reset/bus_struct_reset] \
  [get_bd_pins MC_Memory/SYS_Rst]
  connect_bd_net -net proc_sys_reset_0_interconnect_aresetn  [get_bd_pins Micro_Reset/interconnect_aresetn] \
  [get_bd_pins AXI_IC/S00_ARESETN] \
  [get_bd_pins AXI_IC/ARESETN] \
  [get_bd_pins AXI_IC/M00_ARESETN] \
  [get_bd_pins AXI_IC/M01_ARESETN]
  connect_bd_net -net proc_sys_reset_0_mb_reset  [get_bd_pins Micro_Reset/mb_reset] \
  [get_bd_pins Micro_Cntrl/Reset]
  connect_bd_net -net proc_sys_reset_0_peripheral_aresetn  [get_bd_pins Micro_Reset/peripheral_aresetn] \
  [get_bd_pins Micro_UART/s_axi_aresetn] \
  [get_bd_ports MC_ARESETn]

  # Create address segments
  assign_bd_address -offset 0x00000000 -range 0x00020000 -target_address_space [get_bd_addr_spaces Micro_Cntrl/Data] [get_bd_addr_segs MC_Memory/DLMB_BRAM_CNTRL/SLMB/Mem] -force
  assign_bd_address -offset 0x80000000 -range 0x00010000 -target_address_space [get_bd_addr_spaces Micro_Cntrl/Data] [get_bd_addr_segs MC_AXIL/Reg] -force
  assign_bd_address -offset 0x40600000 -range 0x00010000 -target_address_space [get_bd_addr_spaces Micro_Cntrl/Data] [get_bd_addr_segs Micro_UART/S_AXI/Reg] -force
  assign_bd_address -offset 0x00000000 -range 0x00020000 -target_address_space [get_bd_addr_spaces Micro_Cntrl/Instruction] [get_bd_addr_segs MC_Memory/ILMB_BRAM_CNTRL/SLMB/Mem] -force


  # Restore current instance
  current_bd_instance $oldCurInst

  validate_bd_design
  save_bd_design
}
# End of create_root_design()


##################################################################
# MAIN FLOW
##################################################################

create_root_design ""


Final Pro Tip: Struggling with complex AXI interfaces or unexpected IP lock-ups? Drop a System ILA (Integrated Logic Analyzer) directly into your IP Integrator canvas. It automatically sniffs AXI protocol channels and is an absolute lifesaver for capturing hidden bugs and protocol violations.