Zynq 7000: Using a Command Line Workflow

So far we have used the graphical workflow to create block designs inside Xilinx Vivado. In this post I will present a different workflow based on TCL scripts, which can be run from the command line. Using TCL scripts a number of advantages when it comes it sharing designs across computers, with collaborators and when storing them in VCS (such as git). It also makes it easier to re-use (parts of) block designs in different projects.

This is part 3 of the Zynq 7000 seminar. If you have not used Vivado or the Zynq 7000 SoC before, I would recommend looking at the other posts in the series first:

  1. Getting started with the Xilinx Zynq 7000
  2. Using the ARM cores on the Xilinx Zynq 7000
  3. Zynq 7000: Using a Command Line Workflow [This Post]
  4. Zynq 7000: Adding Custom Blocks

The graphical interface is not the only method for programming the Zynq (or other Xilinx devices); instead a command-line based workflow can be used. In this post I will show how TCL scripts can be used to replicate the entire process we went through in the previous two posts, from creating the project through creating the block design to generating the bitstream. This example will still use block designs and projects; in future posts I will show examples that do not use block designs, or even projects.

There are a few benefits to using TCL scripts to generate the project and block designs. Xilinx project and block design files can be difficult to share between team members or devices; using a TCL script allows every user to create a new project locally, which is initialized to a known state. The TCL scripts are also much easier to place into version control systems such as Git, as they are just plain text files. Finally, when blocks designs are created by TCL scripts it becomes easier to re-use part of the design in a new project.

By using project mode and block designs in our TCL script we can still use all the good features of block designs, with the added benefits of better version control and easier re-use. If for any reason you want to inspect the generated project visually, you can simply open it in Vivado, and it will contain the same design as the project we created in the previous section.

LED Blinker

The first project we will consider is the LED blinking project, which was discussed in the first post in this series. The scripts to create this project can be found in this GitHub repository. The first script is the mk_proj.tcl script, which creates the project on disk, the second script is the src/bd/blinky.tcl file and the final script is the build.tcl script. The contents of the mk_proj.tcl script are shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
set projectname "blink-led"
set part xc7z010-1clg400
set outputdir "./$projectname"

file mkdir $outputdir
set files [glob -nocomplain "$outputdir/*"]
if {[llength $files] != 0} {
    puts "Deleting contents of $outputdir"
    file delete -force {*}[glob -directory $outputdir *];
} else {
    puts "$outputdir is empty"
}

create_project -part $part $projectname $outputdir

source ./../src/bd/blinky.tcl

make_wrapper -files [get_files $bdpath/$bdname.bd] -top
add_files -norecurse $bdpath/hdl/${bdname}_wrapper.v

read_xdc ../src/xdc/constraints.xdc

Lines 1-3 configure the parameters of the script: the name of the project, the part which we are targeting and the path of the directory where the project will be stored. Lines 5-12 deal with creating, or emptying, the project folder. Line 14 creates the project, using the parameters we configured at the top of the script. Line 16 calls another script to generate the block design; the contents of that script will be discussed next. Lines 18 and 19 are used to generate the HDL wrapper and add it to the project; and finally, line 21 reads the constraints file for this project.

On line 16 above we call the script src/bd/blinky.tcl which creates the block design. The contents of that script are shown in the code block below, and it follows closely the steps from before.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
set bdname "blinky"
create_bd_design $bdname

set processing_system [
    create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 processing_system7
]

apply_bd_automation -rule xilinx.com:bd_rule:processing_system7 -config {
    make_external "FIXED_IO, DDR"
    Master "Disable"
    Slave "Disable"
} $processing_system

set_property -dict [list \
    CONFIG.PCW_FPGA0_PERIPHERAL_FREQMHZ {125} \
    CONFIG.PCW_USE_M_AXI_GP0 {0}
] $processing_system


set binary_counter [
    create_bd_cell -type ip -vlnv xilinx.com:ip:c_counter_binary:12.0 c_counter_binary
]

set slice_counter [
    create_bd_cell -type ip -vlnv xilinx.com:ip:xlslice:1.0 xlslice_counter
]

set_property -dict [list CONFIG.Output_Width {27}] $binary_counter

set_property -dict [list \
    CONFIG.DIN_TO {26} \
    CONFIG.DIN_FROM {26} \
    CONFIG.DIN_WIDTH {27} \
    CONFIG.DOUT_WIDTH {1}
] $slice_counter

create_bd_port -dir O led

connect_bd_net [get_bd_pins processing_system7/FCLK_CLK0] [get_bd_pins c_counter_binary/CLK]
connect_bd_net [get_bd_pins c_counter_binary/Q] [get_bd_pins xlslice_counter/Din]
connect_bd_net [get_bd_ports led] [get_bd_pins xlslice_counter/Dout]

regenerate_bd_layout
save_bd_design

set bdpath [file dirname [get_files [get_property FILE_NAME [current_bd_design]]]]

Lines 1 and 2 create a block design with the name that has been configured, and in lines 4-7 a new IP module is added, specifically the “Zynq7 Processing System” IP block, and a reference to this module is stored in the variable $processing_system. Lines 8-12 apply a block design automation rule to add the external interfaces FIXED_IO and DDR, which is the same automation step we used in the graphical workflow. Lines 14-17 configure the properties of the processing system, in this case it sets the frequency of FCLK0 (again, this only informs Vivado of the frequency, it does not modify or control it) and disables the AXI GP0 bus. Lines 19-21 create a second IP block, this time a “Binary Counter” block, and store a reference to it; lines 23-25 add the slice block to the design. Line 27 configures the width of the binary counter, and lines 29-34 configure the slice block, selecting the most significant bit from the counter output. Line 36 creates the output port connecting to the LED and lines 38-40 connect the clock signal to the counter; the counter output to the slice block and the output of the slice block to the output port for the LED. Finally, lines 4 optimizes the block diagram layout (this is not necessary, but helps when you inspect the design in the GUI), and line 43 saves the new block design to the disk; line 45 sets a variable with the path of the block design; this variable can subsequently be used in the mk_proj.tcl script.

The final script in this project is the build.tcl script. This script can be used after the project has been created to start the process of synthesis, implementation and bitstream generation. This script can also be run after any of the project’s RTL sources or constraints have been modified, to regenerate the bitstream. The contents of this script are listed below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
set projectname "blink-led"
set outputdir "./$projectname"

open_project ./$outputdir/$projectname

reset_run synth_1

#launch synthesis
launch_runs synth_1
wait_on_run synth_1

launch_runs impl_1 -to_step write_bitstream
wait_on_run impl_1
puts "Implementation done!"

In the script above, lines 1 and 2 configure the project details, which have to match the mk_proj.tcl script. Line 4 opens the previously created project, and line 6 resets any existing synthesis results. Line 9 starts the synthesis of the project, and line 10 tells the script to block until synthesis is complete. Line 12 launches the implementation, including the bitstream generation, and line 13 blocks until the bitstream has been generated. Finally line 14 writes a message to the console to inform the user that the process has finished.

Based on my own experience I would believe that it is worth spending some time learning how to use a scripted workflow for Vivado. Being able to easily store the project files in Git for version control and team-wide collaboration has proven to be a great time-saver. Another reason why I like the scripts is that Vivado sometimes crashes and corrupts the block designs; in those situations, re-creating the block design is often the quickest solution and this is really easy to do with TCL scripts. The final reason I will mention is that the TCL scripts are much easier to re-use: with the TCL scripts you can easily copy-paste the sections of the design which you want to re-use in a different project.

Vivado has a nice feature to aid in learning the TCL commands: every action you take in the GUI runs a TCL command in the background, and these are shown in the console at the bottom of the screen. If you are not sure what command to use, you can open the GUI and perform the action there, then copy the command into your own TCL script. This really helped me get up to speed with the available commands when I first got started with the scripted workflow. You can also export a block design as a TCL script from the Vivado GUI and use that to learn the relevant commands. This option can be found under File > Export > Export Block Design. The exported scripts can be a bit difficult to follow, but nevertheles they can form a good starting point for your own TCL scripts, if necessary.

To use the scripted workflow, first clone the GitHub repository. Open a terminal in the git repository and source the Vivado settings script; you may have to chance the installation path (/opt/Xilinx) and version (2021.1) to the correct path for your setup.

1
2
3
git clone https://github.com/vvvverre/red-pitaya-tutorials.git
cd red-pitaya-tutorials
source /opt/Xilinx/Vivado/2021.1/settings64.sh

I am using a Linux-based PC for these tutorials and so the commands are based on this setup, but it might be possible to recreate these steps on Windows as well. Next, change directory to the axi-gpio project and create a new directory to hold the Vivado output and temporary files:

1
2
cd blink-led
mkdir vivado

Inside the new directory run the mk_proj.tcl script:

1
2
cd vivado
vivado -mode batch -source ../mk_proj.tcl

This script should take a few seconds to create and configure the blink-led project. After the script has finished, you could choose to open the project file, which is in the blink-led subdirectory, in the Vivado GUI. Or you could immediately continue to build the project and generate the bitstream:

1
 vivado -mode batch -source ../build.tcl

This script will run the synthesis and implementation, so it will take longer to finish. As a reference, on my laptop it takes around four minutes to complete. When complete Vivado should exit without any errors and you should see “Implementation done!” in its output.

Now the bitstream is available to be loaded onto the Red Pitaya in exactly the same way as before. When the bitstream is programmed into the PL the LED should blink in the same way as in the first post.

LED Control

The second project we will consider is that developed in the second post in this series, which allows control over the LEDs from the ARM cores. The scripts to create this project can be found in this GitHub repository. The first script is the mk_proj.tcl script, which creates the project on disk, the second script is the src/bd/axi_gpio.tcl file and the final script is the build.tcl script. The contents of the mk_proj.tcl script are shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
set projectname "axi-gpio"
set part xc7z010-1clg400
set outputdir "./$projectname"

file mkdir $outputdir
set files [glob -nocomplain "$outputdir/*"]
if {[llength $files] != 0} {
    puts "Deleting contents of $outputdir"
    file delete -force {*}[glob -directory $outputdir *];
} else {
    puts "$outputdir is empty"
}

create_project -part $part $projectname $outputdir

source ./../src/bd/axi_gpio.tcl

make_wrapper -files [get_files $bdpath/$bdname.bd] -top
add_files -norecurse $bdpath/hdl/${bdname}_wrapper.v

read_xdc ../src/xdc/constraints.xdc

The mk_proj.tcl file in this project is almost identical to the that in the blink-led project above. The only differences are in the name of the project and the name of the TCL script which generates the block design. The contents of that script, axi_gpio.tcl, are shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
set bdname "axi_gpio"
create_bd_design $bdname

set processing_system [
        create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 processing_system7
    ]

apply_bd_automation -rule xilinx.com:bd_rule:processing_system7 -config {
        make_external "FIXED_IO, DDR" 
        Master "Disable" 
        Slave "Disable"
    } $processing_system

set_property -dict [list \
        CONFIG.PCW_FPGA0_PERIPHERAL_FREQMHZ {125}
    ] $processing_system

set axi_gpio [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio:2.0 axi_gpio ]
set_property -dict [list CONFIG.C_GPIO_WIDTH {8} CONFIG.C_ALL_OUTPUTS {1}] $axi_gpio

apply_bd_automation -rule xilinx.com:bd_rule:axi4 -config {
        Clk_master {/processing_system7/FCLK_CLK0 (125 MHz)}
        Clk_slave {/processing_system7/FCLK_CLK0 (125 MHz)}
        Clk_xbar {/processing_system7/FCLK_CLK0 (125 MHz)}
        Master {/processing_system7/M_AXI_GP0}
        Slave {/axi_gpio/S_AXI}
        intc_ip {New AXI Interconnect}
        master_apm {0}
    }  [get_bd_intf_pins axi_gpio/S_AXI]

apply_bd_automation -rule xilinx.com:bd_rule:board \
    -config {
        Manual_Source {Auto}
    }  [get_bd_intf_pins axi_gpio/GPIO]

set_property NAME leds [get_bd_intf_ports /gpio_rtl_0]

set_property offset 0x41000000 [get_bd_addr_segs {processing_system7/Data/SEG_axi_gpio_Reg}]
set_property range 512 [get_bd_addr_segs {processing_system7/Data/SEG_axi_gpio_Reg}]

save_bd_design

set bdpath [file dirname [get_files [get_property FILE_NAME [current_bd_design]]]]

This script is different from the previous project, because the block design contains different IP blocks. Lines 1-13 are the same as in the previous script, and lines 14-16 are very similar. However, the first difference with the previous block design is that the AXI_GP0 is not disabled in this project, as we will use it to communicate with the AXI GPIO block. Line 18 creates a second IP block, the AXI GPIO block, and stores a reference to it; line 19 configures the properties of the block, using the same values as in the graphical workflow. Lines 21-29 apply the second automation rule, which is the AXI4 rule which sets up the AXI4 network; lines 32-35 apply another automation rule to add the external GPIO port; line 36 configures the name of the newly created interface port. Lines 38 and 39 configure the address range for the AXI GPIO core, using the same parameters used previously in the graphical address editor Finally, line 41 saves the new block design to the disk and line 43 sets a variable with the path of the block design. This variable can subsequently be used in the mk_proj.tcl script.

The commands to configure and then build the project are almost identical to before:

1
2
3
4
5
6
source /opt/Xilinx/Vivado/2021.1/settings64.sh
cd red-pitaya-tutorials/axi-gpio
mkdir vivado
cd vivado
vivado -mode batch -source ../mk_proj.tcl
vivado -mode batch -source ../build.tcl

At this point the bitstream will be available in the same location as in the previous post and it can be loaded on the Red Pitaya; the same C code from the previous post can be used to control the LEDs.

Summary

In this post we have seen how TCL scripts can be used to drive Vivado from the command line, as an alternative to using the GUI. There are a few advantages to using the command line workflow:

  1. The TCL scripts contain everything needed to regenerate the project. This makes it easy to do a clean build if Vivado crashes and corrupts the project, or incorrectly attempts to re-use cached results.
  2. The scripts are well suited to use with version control systems such as git, which allows you to easily track changes, compare revisions, archive your project pr share your project with other developers.
  3. Using a scripted workflow makes it easier to share and re-use parts of your design across projects and teams.

In this post the workflow still used Xilinx' project mode and block designs. This way developers can access the GUI at any point to inspect project settings or to visualize the design. There are, however, reasons why a developer may prefer to not use block designs, or even projects, and we will explore these options in future posts.