Vivado & the PYNQ-Z2

Installing Vivado, the PYNQ-Z2 board and its parts, creating a project, the XDC constraint file, and the flow from synthesis to bitstream.

1. From text to wires

Up to now every design in this course has lived in a simulator. A simulator only needs to know what your circuit does. To put it on a chip, a tool must decide which of the chip's tens of thousands of look-up tables (LUTs) and flip-flops implement each piece of logic, where they sit on the silicon, and which programmable wires connect them. For AMD (formerly Xilinx) FPGAs that tool is Vivado. Its final output is a bitstream (a .bit file): a few megabytes of configuration bits that set every LUT's truth table, every flip-flop's options and every routing switch on the chip.

This chapter is the practical bridge. We tour the PYNQ-Z2 board, install Vivado, create a project, write the constraint file that ties port names to physical pins, and follow one small design through every step of the flow — reading the reports that tell you whether the result can be trusted. Chapter 14 then builds the first real design on the board, and Chapter 15 explains the timing numbers you meet here.

.v / .vhd.xdcpins, I/O standard,clock periodelaborationgeneric RTL netlistsynthesisLUTs, FFs, carryimplementationoptplaceroutebitstream.bitconstraints guide synthesis and implementationschematic view,syntax/width errorsutilisation estimate,inferred latches/RAMsfinal utilisation, timing summary(WNS, TNS, WHS), DRC, powerprogram overJTAG or PYNQread:
The Vivado flow and its files. Each step reads the previous step's result; you can stop after any step, save a checkpoint (.dcp) and inspect reports.

2. A tour of the PYNQ-Z2

The PYNQ-Z2 (made by TUL) is built around a Zynq-7000 XC7Z020, part number XC7Z020-1CLG400C. A Zynq is two things in one package: a processing system (PS) — a dual-core ARM Cortex-A9 running at 650 MHz on this board, with 512 MB of DDR3 memory — and programmable logic (PL), an Artix-7-class FPGA fabric. Parts I–VI of this course use only the PL; Part VII connects the two halves.

Zynq XC7Z020PS2× Cortex-A9PL53 200 LUTsDDR3 512 MB125 MHz clockto PL pin H16powerswitch + jackmicro-USBJTAG + UARTboot jumperJTAG / SD / QSPImicro-SDEthernetgigabitUSB hostHDMI inHDMI outaudio codecline in, headphone/micRaspberry Pi header (40 pins)PMOD AJA 8 I/OPMOD BJB 8 I/OArduino shield headersdigital I/O + analog inputsLD0–LD3RGB LD4, LD5SW0–1BTN0–BTN3
Schematic sketch of the PYNQ-Z2 (not to scale, positions approximate). Orange: wired to the PL, which our HDL can drive directly. Sky blue: wired to the PS (ARM side). Grey: power and configuration.
FeatureConnected toUsed in this course
125 MHz clockPL pin H16every design: clk
4 green LEDs LD0–3PL (R14, P14, N16, M14)Ch. 13, 14 onwards
2 RGB LEDs LD4, LD5PL (3 pins each)this chapter
4 buttons, 2 switchesPLCh. 14 onwards
2 PMOD connectorsPL, 8 signals eachUART adapter, sensors
Arduino / Raspberry Pi headersPL (mostly)optional add-ons
HDMI in / outPLvideo projects
Audio codecPL (I2S + I2C)optional
USB-UART + JTAG (micro-USB)PS UART / JTAG chainprogramming, Linux console
Ethernet, USB host, micro-SDPSPYNQ Linux (Part VII)
512 MB DDR3PS memory controllerPart VII (DMA)

Two board details save hours of confusion. First, the boot jumper chooses where the Zynq loads its configuration at power-on. Set it to JTAG for the PL-only labs in Parts V–VI: the chip waits, and Vivado programs it over the micro-USB cable. Set it to SD to boot the PYNQ Linux image from the micro-SD card (Part VII). Second, the micro-USB serial port is wired to the PS, not the PL. A UART built in HDL cannot reach it directly; it needs a 3.3 V USB–serial adapter on a PMOD pin, or a path through the ARM processor.

Common mistake: 5 V on a PL pin
Every PL pin used in this course is in a 3.3 V bank (I/O standard LVCMOS33). The FPGA pins are not 5 V tolerant. Arduino shields designed for 5 V logic, or a 5 V USB–serial adapter on a PMOD, can damage the Zynq. Check the voltage of anything you plug in.

3. Installing Vivado

Vivado is free for small and mid-sized devices. The no-cost edition (called Vivado ML Standard in recent releases, formerly WebPACK) includes the XC7Z020, so you need no licence for anything in this course. A few practical facts:

  • Operating system: Linux or Windows only. On a Mac, run Vivado inside a Linux or Windows virtual machine (practical mainly on Intel Macs), or use a separate PC. Check the release notes for the exact supported distributions.
  • Disk space: tens of gigabytes even when you install only the 7-series devices. Deselect the device families you do not need (UltraScale, Versal) to keep it down.
  • Version: for PL-only designs any recent version works. Once you build overlays for the PYNQ Linux image (Part VII), use the version that matches your image: Vivado 2024.1 for PYNQ v3.1, 2022.1 for PYNQ v3.0.
  • Download: from the AMD website, with a free AMD account. The web installer downloads only what you select.
  • Cable drivers: on Linux, the JTAG cable drivers are installed by a separate script that ships with Vivado and needs root rights. Without them the Hardware Manager will not see the board.
Key idea: the simulator stays your main tool
Vivado has its own simulator, but builds take minutes and simulations with Icarus Verilog or GHDL take seconds. Keep the habit of the earlier chapters: test every module in simulation, and open Vivado only to turn a design you already trust into a bitstream.

4. Creating a project

Vivado can be driven two ways. In project mode the graphical interface keeps track of your sources, runs and reports in a project directory. In non-project mode you write a short Tcl script that runs each step explicitly (Section 9). Beginners usually start with project mode; the essential choices are the same.

On the board: a new RTL project for the PYNQ-Z2
  1. Start Vivado and create a new project. Give it a name and a path without spaces (spaces in paths are a classic cause of obscure tool errors).
  2. Choose RTL Project: you supply HDL sources. You may tick the option not to add sources yet.
  3. Choose the target: either the part xc7z020clg400-1 directly (filter by family Zynq-7000, package clg400, speed grade −1), or — if you have installed the TUL PYNQ-Z2 board files — the PYNQ-Z2 board, which selects the same part and also knows the board's interfaces for block designs in Part VII.
  4. Set the project's target language (Verilog or VHDL). It only affects generated files; you can mix both kinds of sources.
  5. Add your design sources (.v or .vhd), your simulation sources (testbenches, optional), and a constraints file (.xdc). Mark the top-level module as top.

The part number encodes everything the tools need: xc7z020 is the device, clg400 the 400-ball package (that is why pins have names like H16: row H, column 16 of the ball grid), and -1 the speed grade, the slowest of the family. Timing analysis uses delays for that speed grade, so a bitstream that meets timing for -1 works on every chip sold as -1.

5. A first design: switches, buttons and a colour cycle

Our example is deliberately simple but touches both kinds of logic. The four green LEDs show the two switches and two buttons directly — pure wires, no clock. The RGB LED LD4 cycles through its colours, driven by the top three bits of a free-running counter. Because RGB LEDs are dazzling at full current, the colour is only switched on one clock cycle in sixteen: a 7.8 MHz pulse that the eye averages to a gentle 6 % brightness. (This is the simplest form of pulse-width modulation.)

\[ t_\text{colour} = \frac{2^{\,CW-3}}{f_\text{clk}} = \frac{2^{24}}{125\ \text{MHz}} \approx 0.134\ \text{s}, \qquad \text{duty} = \frac{1}{16} = 6.25\,\% \]
sw_rgb — direct wires and a colour cycle
module sw_rgb #(
    parameter CW = 27                  // counter width: 2^27 cycles = 1.07 s at 125 MHz
) (
    input  wire       clk,             // 125 MHz, pin H16
    input  wire [1:0] sw,              // SW0, SW1
    input  wire [1:0] btn,             // BTN0, BTN1
    output wire [3:0] led,             // LD0-LD3
    output wire [2:0] rgb              // LD4: rgb[2]=R, rgb[1]=G, rgb[0]=B
);
    // Purely combinational: switches and buttons straight to the green LEDs
    assign led = {btn, sw};

    // Free-running counter. The top three bits choose a colour (0..7),
    // so the colour changes every 2^(CW-3) cycles.
    reg [CW-1:0] count = 0;
    always @(posedge clk)
        count <= count + 1'b1;

    // The RGB LED is very bright: light it only 1 cycle in 16 (6.25 % duty)
    wire bright = (count[3:0] == 4'd0);
    assign rgb = count[CW-1:CW-3] & {3{bright}};
endmodule

The counter width CW is a parameter so that the testbench can shrink it to 8 bits: one full colour cycle then takes 256 clock cycles. The testbench checks all sixteen switch/button combinations, counts how many cycles the RGB LED is lit (2 per colour × 7 colours = 14) and checks that all seven non-black colours appear. Both languages print the same PASS at 2064 ns.

Self-checking testbench for sw_rgb
`timescale 1ns/1ps
module sw_rgb_tb;
    reg        clk = 0;
    reg  [1:0] sw = 2'b00, btn = 2'b00;
    wire [3:0] led;
    wire [2:0] rgb;
    integer i, lit = 0, errors = 0;
    reg [7:0] seen = 8'b0;              // which colours appeared

    // CW = 8: the colour changes every 32 cycles instead of every 0.13 s
    sw_rgb #(.CW(8)) dut (.clk(clk), .sw(sw), .btn(btn), .led(led), .rgb(rgb));

    always #4 clk = ~clk;               // 125 MHz

    initial begin
        // 1. Green LEDs follow {btn, sw} for all 16 input combinations
        for (i = 0; i < 16; i = i + 1) begin
            {btn, sw} = i;
            #1;
            if (led !== i) errors = errors + 1;
        end
        // 2. One full counter period: 256 cycles
        for (i = 0; i < 256; i = i + 1) begin
            @(negedge clk);
            if (rgb != 3'b000) begin
                lit = lit + 1;
                seen[rgb] = 1'b1;
            end
        end
        if (errors == 0 && lit == 14 && seen == 8'b1111_1110)
            $display("PASS: LEDs follow inputs, RGB lit %0d of 256 cycles, 7 colours", lit);
        else
            $display("FAIL: errors=%0d lit=%0d seen=%b", errors, lit, seen);
        $finish;
    end
endmodule
Run it
# Verilog with Icarus
iverilog -g2012 -o tb sw_rgb.v sw_rgb_tb.v && vvp tb

# VHDL with GHDL
ghdl -a --std=08 sw_rgb.vhd sw_rgb_tb.vhd
ghdl -e --std=08 sw_rgb_tb && ghdl -r --std=08 sw_rgb_tb

6. The XDC constraint file

The HDL says there is an input called sw[0]. Nothing in it says that this input is ball M20 of the package. That is the job of the Xilinx Design Constraints (XDC) file. XDC is a set of Tcl commands, based on the industry-standard SDC format. Here is the complete file for sw_rgb:

sw_rgb.xdc
## sw_rgb.xdc -- PYNQ-Z2 pin constraints (every user I/O here is 3.3 V)

## Clock: pin H16, 125 MHz = 8 ns period, 50 % duty (rises at 0, falls at 4)
set_property -dict { PACKAGE_PIN H16 IOSTANDARD LVCMOS33 } [get_ports clk]
create_clock -name sys_clk -period 8.000 -waveform {0.000 4.000} [get_ports clk]

## Slide switches SW0, SW1 -- bus bits are written name[index], in braces
set_property -dict { PACKAGE_PIN M20 IOSTANDARD LVCMOS33 } [get_ports {sw[0]}]
set_property -dict { PACKAGE_PIN M19 IOSTANDARD LVCMOS33 } [get_ports {sw[1]}]

## Push-buttons BTN0, BTN1
set_property -dict { PACKAGE_PIN D19 IOSTANDARD LVCMOS33 } [get_ports {btn[0]}]
set_property -dict { PACKAGE_PIN D20 IOSTANDARD LVCMOS33 } [get_ports {btn[1]}]

## Green LEDs LD0-LD3
set_property -dict { PACKAGE_PIN R14 IOSTANDARD LVCMOS33 } [get_ports {led[0]}]
set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports {led[1]}]
set_property -dict { PACKAGE_PIN N16 IOSTANDARD LVCMOS33 } [get_ports {led[2]}]
set_property -dict { PACKAGE_PIN M14 IOSTANDARD LVCMOS33 } [get_ports {led[3]}]

## RGB LED LD4: rgb[0] = blue, rgb[1] = green, rgb[2] = red
set_property -dict { PACKAGE_PIN L15 IOSTANDARD LVCMOS33 } [get_ports {rgb[0]}]
set_property -dict { PACKAGE_PIN G17 IOSTANDARD LVCMOS33 } [get_ports {rgb[1]}]
set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS33 } [get_ports {rgb[2]}]

Read one line from the inside out:

  • get_ports {sw[0]} finds the top-level port object called sw[0]. Bus bits are named name[index] in both languages (VHDL's sw(0) also becomes sw[0]). The braces stop Tcl from treating [0] as a command to execute. get_ports led[*] with a wildcard would match all four bits.
  • PACKAGE_PIN M20 places that port on ball M20, which the board designers wired to switch SW0.
  • IOSTANDARD LVCMOS33 configures the I/O buffer for 3.3 V CMOS levels, matching the bank voltage on the board.
  • set_property -dict { ... } sets several properties at once; two separate set_property lines would be equivalent.

The clock line is different in kind. create_clock -period 8.000 does not configure any hardware; it tells the timing analyser that a clock with an 8 ns period (125 MHz) enters on port clk, rising at 0 ns and falling at 4 ns. Every path between flip-flops on that clock will then be checked against 8 ns. Without it Vivado still builds the design, but it cannot tell you whether it is fast enough — the timing summary reports the clock as unconstrained.

Common mistake: names that do not match
XDC lines refer to ports by the exact names in your top-level HDL. If the HDL says led and the XDC says LED, or you copied a master XDC for the whole board and left in pins your design does not have, Vivado warns that get_ports matched nothing and ignores the line. Then the real port is left unplaced, and bitstream generation stops with an error about unconstrained I/O. Read the critical warnings; they name the offending port.

7. What each step of the flow does

Elaboration reads the HDL, resolves parameters/generics and the module hierarchy, and builds a generic netlist of adders, multiplexers, registers and comparators. Width mismatches, missing signals and accidental latches show up here. Vivado's RTL schematic view draws this netlist — a good way to check that the hardware you imagined is the hardware you described.

Synthesis maps that generic netlist onto the real resources of the 7-series fabric: 6-input LUTs, flip-flops, carry chains for adders and counters, block RAM, DSP48E1 slices for multipliers. It also optimises: constant logic disappears, duplicate registers merge, and logic that drives no output is removed. (If a whole block of yours vanishes from the utilisation report, it is usually because nothing used its output.)

Implementation has three main steps. opt_design cleans up the synthesised netlist. place_design assigns every LUT and flip-flop to a specific site on the die, trying to keep connected cells close. route_design chooses the programmable wires between them. Only after routing are the wire delays known exactly, so only the post-route timing report is final.

Bitstream generation runs design-rule checks (DRC) — every port placed, every I/O standard set, no combinational loops — and writes the .bit file. For a 7020 it is about 4 MB regardless of how much of the chip you use, because every configuration bit is written.

sw_rgb resourceExpectedWhy
Flip-flops27the 27-bit counter
LUTsa fewthe increment lives mostly in the carry chain; plus the rgb gating
CARRY4about 727 bits / 4 bits per carry block, rounded up
Bonded IOB121 clk + 2 sw + 2 btn + 4 led + 3 rgb
BUFG1the clock goes onto a global clock buffer

Predicting the utilisation before you look is a powerful habit. If the report disagrees wildly with your prediction — 0 flip-flops, or thousands — you have misunderstood something about your own design.

8. Reading the reports

A bitstream is only as trustworthy as the reports behind it. After implementation, look at three things, in this order.

1. Messages. Errors stop the flow; critical warnings do not, but they usually mean the result is not what you intended: a constraint that matched no port, a port with no pin, a combinational loop. Ordinary warnings are worth a skim (for example, “signal is unused” or “latch inferred”).

2. Timing summary. For every clock path the tool computes the slack: how much earlier than required the data arrives. Positive slack means the path is fast enough. The summary reports:

NumberMeaningMust be
WNSworst negative slack: setup slack of the worst path≥ 0
TNStotal negative slack: sum of the negative setup slacks= 0
WHSworst hold slack≥ 0
THStotal hold slack (sum of negative hold slacks)= 0
Unconstrainedpaths not covered by any clocknone you care about
timing summary (abridged; the exact layout varies between versions)
Design Timing Summary
| WNS(ns) | TNS(ns) | TNS Failing Endpoints | WHS(ns) | THS(ns) | THS Failing Endpoints |
|   4.871 |   0.000 |                     0 |   0.172 |   0.000 |                     0 |

All user specified timing constraints are met.
\[ f_\text{max} \approx \frac{1}{T_\text{clk} - \text{WNS}} = \frac{1}{8\ \text{ns} - 4.871\ \text{ns}} \approx 320\ \text{MHz} \]

That estimate (for the illustrative numbers above) is only a rough guide: the tools optimised for 8 ns, and a tighter constraint would change placement. Chapter 15 derives setup and hold slack properly and shows what to do when WNS is negative.

3. Utilisation. How much of each resource the design uses. On the XC7Z020 the totals are 53 200 LUTs, 106 400 flip-flops, 140 block RAMs of 36 Kb and 220 DSP48E1 slices. A design that fits on paper can still be hard to place and route when one resource is nearly full, because the placer has no freedom left. The program below parses an excerpt of a report_utilization text report — the same parsing you would use to track utilisation across builds — and plots it against the device totals.

Python
script.py85 lines

Click Run to execute the Python code

Code will be executed with Python 3 on the server

Key idea: “it works on my board” is not a timing report
A design with negative slack may run correctly on your desk at room temperature and fail on a warm day, on another board, or after an unrelated change moves the placement. Never ship a bitstream with WNS < 0 or WHS < 0.

9. The batch flow: one Tcl script

Everything the graphical interface does is a Tcl command underneath. In non-project mode you call those commands yourself, which makes the build reproducible, scriptable and easy to keep in version control. The whole flow for sw_rgb fits in a page:

build.tcl
# build.tcl -- non-project batch flow.  Run with:
#   vivado -mode batch -source build.tcl
set top  sw_rgb
set part xc7z020clg400-1
set out  ./build
file mkdir $out

# 1. Read sources (use read_vhdl for .vhd files) and constraints
read_verilog sw_rgb.v
# read_vhdl  sw_rgb.vhd
read_xdc     sw_rgb.xdc

# 2. Synthesis: elaborate the HDL and map it to LUTs, flip-flops, carry chains
synth_design -top $top -part $part
write_checkpoint -force $out/post_synth.dcp
report_utilization -file $out/util_synth.rpt

# 3. Implementation: optimise, place, route
opt_design
place_design
route_design
write_checkpoint -force $out/post_route.dcp

# 4. Reports to read before trusting the bitstream
report_timing_summary -file $out/timing.rpt
report_utilization    -file $out/util_route.rpt
report_drc            -file $out/drc.rpt

# 5. Bitstream
write_bitstream -force $out/$top.bit

The checkpoints (.dcp) save the design after each stage; open_checkpoint build/post_route.dcp in the Vivado GUI lets you look at the placed and routed result, including the device view and the timing paths, without rebuilding. A design of this size builds in a minute or two on a laptop; most of that time is fixed start-up and device-loading overhead.

On the board: building and programming sw_rgb
  1. Put sw_rgb.v (or .vhd, then swap the read_* line), sw_rgb.xdc and build.tcl in one directory.
  2. Run vivado -mode batch -source build.tcl from a terminal where Vivado's settings script has been sourced (on Linux, the settings64.sh in the installation directory).
  3. Open build/timing.rpt: check that all constraints are met. Open build/util_route.rpt and compare with the table in Section 7.
  4. Boot jumper on JTAG, micro-USB connected, board on. In Vivado open the Hardware Manager, connect to the target, and program the device with build/sw_rgb.bit.
  5. Flip SW0/SW1 and press BTN0/BTN1: LD0–LD3 follow instantly. LD4 steps through its seven colours, about 0.13 s each, with a dark gap where the code is 000.

The bitstream is lost when power is removed: in JTAG mode the configuration lives only in the FPGA's SRAM cells. That is normal for SRAM-based FPGAs, and exactly what you want while developing.

Practice Problems

Progress: 0/5 (0%)
1
⭐ Easy
Write the XDC constraint that puts an input port called go on button BTN2 (pin L20) as a 3.3 V CMOS input, and explain why a port without these lines stops bitstream generation.
2
⭐ Easy
A timing summary reads WNS = −0.412 ns, TNS = −3.9 ns, WHS = 0.051 ns for a 125 MHz clock. Is the design safe to use? Roughly how fast could the slowest path run?
3
⭐⭐ Medium
In sw_rgb the RGB LED is lit only when count[3:0] == 0. (a) What is the duty cycle and the frequency of that brightness pulse? (b) With CW = 27, how long does each of the eight colours last?
4
⭐⭐ Medium
A student adds an output port uart_tx to the PL design and constrains it to the pin of the board's micro-USB UART, expecting to see characters in a terminal. Why can't this work, and what are two correct alternatives?
5
⭐⭐⭐ Hard
Design-size intuition. The report for a design shows: LUTs 41 000, registers 30 000, BRAM tiles 20, DSPs 210. (a) Compute each utilisation on the XC7Z020. (b) Which number worries you most, and why does a resource near 100 % matter more than its percentage suggests?

Problem Set Statistics

Total Problems
5
Attempted
0
Completion
0%
Share:XRedditLinkedIn
Rate this chapter: