Created on
20/02/21 13:00

Modified on
20/02/21 13:00

Filed under
EDA

Tags
eda vivado tcl ip fpga

Preface

Vivado's IP Integrator offers convenient access to its vast collection of production-grade IPs for the designer to incorporate into their designs. Furthermore, they allow the designer to extend the experience to custom IP cores via the IP Packager, fostering better integration and promoting reuse within the Xilinx FPGA ecosystem. Both the IP Integrator and IP Packager offer a GUI interface for straightforward access, but that can become quite tedious when working with larger designs with multiple IPs and/or multiple interfaces. Fortunately, with the help of some short Tcl, both the IP packaging (in the IP Packager) and instantiation (in the IP Integrator, or more commonly known as a Block Design) can be automated.

Quick note

Most of the affairs discussed in this article are, unlike other flows in Vivado, such as the Project/Non-Project Batch Mode, not very well documented. The following materials provided most of the information:

  • UG912: Vivado Design Suite Properties Reference Guide
  • UG835: Vivado Design Suite Tcl Command Reference Guide
  • output in the Tcl Console when performing GUI
  • help command
  • report_property command
  • the Xilinx forum

IP Packager: mapping ports in interfaces

The single most tedious process in packaging an IP is mapping the interfaces to top-level ports. While IP Packager offers interface inference, it bails out on the slightest discrepancies between top-level port names and the interface definitions (such as Vivado v. Chisel for AXI4). The interface definitions, as well as nearly all other functions in the IP Packager, can actually be manipulated with ipx::* commands; you can list them with help ipx::*.

Use ips::add_bus_interface to create an interface on the IP, set the abstraction type, bus type, and interface mode, and then create port maps with ipx::add_port_map. For example, if we're adding a BRAM interface called $portName:

set core [ipx::current_core]
set intf [ipx::add_bus_interface $portName $core]

set_property -dict [list \
    abstraction_type_vlnv xilinx.com:interface:bram_rtl:1.0 \
    bus_type_vlnv xilinx.com:interface:bram:1.0 \
    interface_mode master] $intf

# can be stored as a dict
set_property physical_name ${portName}_address [ipx::add_port_map ADDR $intf]
set_property physical_name ${portName}_din [ipx::add_port_map DOUT $intf]
set_property physical_name ${portName}_dout [ipx::add_port_map DIN $intf]
set_property physical_name ${portName}_ce [ipx::add_port_map EN $intf]
set_property physical_name ${portName}_we [ipx::add_port_map WE $intf]

The above snippet is basically copied from the Tcl window after manually mapping one interface by hand. The defining keys for a BRAM interface are the abstract and bus VLNVs, which can be found in the command outputs. By storing the port map and bus interface objects, we can save time from the excessive ipx::get_port_maps and ipx::get_bus_interfaces commands. After testing the snippet out, it can then be incorporated into a proc to be used in further automation.

IP Integrator: building a block design

The block design can be built with Tcl calls rather than adding blocks and dragging connections between them, which quickly gets pretty tedious when more blocks get involved. You can choose to build from scratch, or base on the results from Export Block Design.

Instantiating blocks and connecting nets

create_bd_cell is used to create about every type of BD cells, but in most cases we care about IPs the most. set_property is then used to customize the IP. For example, to create a Block Memory Generator that is a true dual port memory, as well as the corresponding controller:

set bmgName bmg_0
set bmg [create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 $bmgName]
set_property -dict [list \
    CONFIG.Memory_Type {True_Dual_Port_RAM} \
    CONFIG.Assume_Synchronous_Clk {true}] $bmg

set axiCtrlName axi_bram_ctrl_0
set axiCtrl [create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl:4.1 $axiCtrlName]

Remember that the possible properties of a cell can be consulted via report_property -all. Make sure to check the reports and follow the RW/RO limitations, or the IP may misbehave.

connect_bd_net is used to create simple nets (i.e. non-interfaces) while connect_bd_intf_net is for interfaces (e.g. AXI4, BRAM). get_bd_{,intf_}{pins,ports} can be used to find a port/pin by path. As we're using paths, saving the path name in a variable for later access will most likely be convenient. The following snippet assumes the pins $clk $rstn and $axiM hold the clock, active-low synchronous reset, and master AXI pins.

foreach a {A B} {
    connect_bd_intf_net [get_bd_intf_pins $axiCtrlName/BRAM_PORT$a] [get_bd_intf_pins $bmgName/BRAM_PORT$a]
}
connect_bd_intf_net $axiM [get_bd_intf_pins $axiCtrlName/S_AXI]
connect_bd_net $clk [get_bd_pins $axiCtrlName/s_axi_aclk]
connect_bd_net $rstn [get_bd_pins $axiCtrlName/s_axi_aresetn]

Mapping address segments

There are two types of address segments in a block design for a specific IP such as the BRAM:

  • A slave segment on an IP, which defines a mappable memory area
  • A segment in the master address space corresponding to a slave segment

get_bd_addr_segs uses paths to locate both kinds, so it's important to distinguish these two kinds correctly. assign_bd_address can be used to automatically assign (map) a slave segment into the master space. set_property can then be used on the newly-created segment to modify its range or offset. In the following snippet, we map the AXI BRAM controller into 0xdead0000 (stored in $newOffset). We assume that the master is called $masterName and the address space is $addrSpaceName.

# Slave Interface: S_AXI | Base Name: Mem0
assign_bd_address [get_bd_addr_segs $axiCtrlName/S_AXI/Mem0]
set_property range $newOffset [get_bd_addr_segs $masterName/$addrSpaceName/SEG_${axiCtrlName}_Mem0]

Making hierarchies

When the connections and address assignments are done, grouping automatically generated BD components into hierarchies is usually desirable. group_bd_cells is used for this purpose. move_bd_cells can be used to add a cell to an existing hierarchy.

set hierName hier_0
group_bd_cells $hierName $bmg $axiCtrl

Note that after creating a hierarchy, the paths for member cells will change, which may invalidate stored paths.

Validate & save design

Validate design is an important step which allows IPs to propagate their parameters to connecting IPs. This mechanism supports various features such as width negotiation (bus or interface), link type negotiation (AXI4 or AXI4-Lite), and more complicated parameter propagation (such as BRAM operation mode). The following snippet validates, reorganizes (GUI-wise), and saves the current block design:

validate_bd_design
regenerate_bd_layout
save_bd_design

Note on built-in BD automation

While it is possible to use apply_bd_automation to simulate the automation process in block designs (e.g. instantiating BMGs or connecting AXIs), I recommend refraining from that and still connect things with explicit commands. This eliminates most surprises.

Comment

Personal details (portrait, CV) © Pengcheng Xu All Rights Reserved; articles licensed under CC BY-SA 4.0.
Powered by Pelican, Bootstrap, and NixOS. Icons by Font Awesome. Generated from d900894.