The AMD FPGA tools have been evolving quite a bit over the past few years with the transition from XSDK to Vitis back in 2019, the introduction of accelerated platform development, and the recent upgrade in Vitis to the Vitis Unified IDE last year in 2023. Now with version 2024.1 of the tools, I wanted to start an update getting started tutorial series that spent more time focusing on outlining the difference between fixed platform designs and extensible (aka reprogrammable) platform designs. In my opinion, this is the biggest differentiator between design flows now for AMD FPGAs.
Fixed platforms refer to designs where the design implemented in the programmable logic of an FPGA is completed fully in HDL in Vivado. This contrasts extensible platform designs where the HDL design in the FPGA's programmable logic contains hooks for additional programmable components to be added such as PL kernels and AI Engine. The fixed platform workflow is the traditional workflow most are familiar with, while the extensible platform workflow is covered in my past tutorials referred to as "accelerated designs" as they are PL kernels I'm using to implement hardware acceleration in the design.
This project write-up serves to be an (mostly) all-encompassing example of the fixed platform for the Zynq-7000 SoC FPGA for Vivado 2024.1, and will be followed up with the corresponding project tutorials in Vitis 2024.1 and PetaLinux 2024.1. While I am using Digilent's Zybo development board, the general workflow is applicable to any Zynq-7000 development board.
Add Board Definition Files If NeededWhen using an FPGA development board like the Zybo, Vivado has the option for users preload a development board's settings/configuration with board definition files. Vivado has backend hooks to a central board definition file repository for AMD's own development boards and common vendors such as Digilent, Avnet, Trenz Electronic, Opal Kelly, etc.
In the event that the target FPGA development board is not in that backend repository maintained in Vivado by AMD, a custom directory can be added containing the board definition files.
Launch Vivado and select Tools > Settings.
In the Settings window, navigate to Vivado Store > Board Repository.
Create a directory somewhere to house the board definition files. I recommend putting it somewhere easy to remember like the home directory since this is where any board definition files not in the backend repository will be housed.
Back in the Settings window, click the + button in the Board Repository tab and point to the directory that was just created:
Click OK to save the new settings and close the Settings window.
Note that the TCL console will echo the new settings:
To create a new Vivado project, select Create Project from the Quick Start menu. Once a project has been opened for the first time, it will appear in the list of Recent Projects as a shortcut.
The new project creation wizard will appear with a quick intro window first:
The next window will prompt for the desired project name and directory path. The option for Create project subdirectory simply creates a top level directory to place the project structure within. It is enabled by default and I personally would recommend always leaving it enabled.
The next window asks for the project type. A regular project (and default option) is an RTL Project. For an RTL project, there are two options:
- Do not specify sources at this time: Enabling this option allows for a new Vivado project without any source files in it (an empty project). Existing files can be imported any time, but disabling this option will allow for those files to be imported in this step if they are available.
- Project is an extensible Vitis platform: Leaving this option disabled (unselected) is what sets the project for the fixed platform workflow. This project setting is accessible to be changed at any time in the project settings later, but since this tutorial is for the fixed platform workflow, it's not selected.
The next window is where the target FPGA part is selected. This selection can either be just a specific FPGA chip (selected under the Parts tab) or it can be a full FPGA development board (selected under the Boards tab).
In my case, I am using the Zybo development board from Digilent so switching over to the Boards tab I just searched "Zybo" in the search bar.
If this is the first time Vivado has been used since it was installed, it needs to ping the backend Xilinx board file repository. Click the Refresh button to do this (a network connection is necessary). Otherwise, the only board definition files available will be for AMD development boards.
The boards can also be filtered by vendor:
As previously mentioned, the the only board definition files downloaded by default are for the AMD development boards. So the first time a third party vendor board is used like the Zybo, it needs to be downloaded (as indicated under the Status column).
Click the install button in the Status column for the target development board to download the board definition files from the backend repository:
Once installed, single left click on the line for the board to select it and click Next.
The final window in the project creation wizard will give a summary of the specified project settings. Click Finish to create the Vivado project:
Vivado will take a few moments to generate the new project, which at this point is just an empty project:
Vivado has two workflows/methods for working with HDL in a project: coding HDL in a text editor and a graphical drag+drop/connection of HDL modules in a block design. The block design workflow is optional, as all of the AMD IPs have instantiation templates in both VHDL and Verilog to copy+paste into an HDL source file. I personally find the block design workflow to be convenient due to the automation and pre-configuration functionalities it has, especially when working with a development board like a Zybo.
To create a new block design, click on Create Block Design under the IP Integrator tab in the Flow Navigator sidebar:
Give the block design the desire name in the pop up window and click OK:
Vivado will then take a moment to create the block design and then automatically open it to start editing:
The first IP to add to a block design for a Zynq-based FPGA is the Zynq Processing System (PS) IP that provides the hooks to configure the ARM-core plus its peripherals as desired, and for the rest of the design in the programmable logic to access it.
Click the + button to open the IP catalog and type "zynq" in the search bar. Double-click the ZYNQ Processing System IP to add it to the block design:
A few moments after adding the Zynq PS IP, the block design will detect it and offer the option to Run Block Automation. Under the hood, Vivado is reading from the board definition files what the required configuration of the Zynq PS needs to be for the Zybo and is offering to apply that configuration automatically.
Click Run Block Automation, the automation wizard will pop up showing everything the block automation will do, which in this case is mainly applying board presets.
After clicking OK in the automation wizard window, ports for the DDR and fixed IO will appear and the core configured to enable the peripherals connected to the Zynq on the Zybo board.
To clarify, this step is just enabling the corresponding MIO pins on the Zynq where peripherals are routed directly into the ARM-core via dedicated package pins on the FPGA chip. This configuration is fully dependent on the routing of the PCB on the development board because if a peripheral isn't routed to the dedicated package pin for it on the FPGA, then enabling it in the Zynq PS IP won't do anything.
While the MIO is set in stone by block automation for the development board, other options are still open to be configured as the design needs such as AXI ports to communicate with other IP blocks in the block design.
It is important to note the need to double-check that at least one master AXI interface is enabled on the Zynq PS so that it is able to communicate with AXI peripherals added to the block design.
Add Peripherals to Block DesignWith the Zynq PS IP in the block design to cover the ARM-core in the Zynq FPGA, the rest of the peripherals on the Zybo not connected to the dedicated MIO package pins need HDL/IPs.
This where another convenience functionality of the block design comes into play. In the window where the source files of the project are viewed in the Sources tab and the hierarchy of the IPs in the block design is shown in the Design tab, there is a fourth tab called Board. The Board tab is only populated when targeting an FPGA development board (vs just a FPGA chip) with the peripherals on the development board not routed through the Zynq MIO.
Furthermore, when connecting to peripherals from the Board tab, Vivado applies a filter to the IP library to only show the IPs compatible with that particular peripheral.
Right-click on the desired peripheral to connect, which to start on the Zybo is the RGB LEDs, and select Connect Board Component...
This will bring up the filtered IP catalog list of IPs compatible with driving the RGB LEDs (which is just various forms of GPIO). Select GPIO under AXI GPIO and click OK:
This adds an AXI GPIO IP with channel 1 (GPIO) configured with the appropriate direction and bus width for the RGB LEDs.
A moments after the AXI GPIO is added to the block design, the option to Run Connection Automation will appear:
When the block design detects an AXI-based peripheral, it is able to automatically connect the AXI interface and corresponding clock/reset lines based on a set of basic design rules such as keeping within the same clock domain, etc.
Click Run Connection Automation to bring up the automation wizard. Again, it will show all of the options for the automation to connect and allow for manual selections to be made. However, when it comes to clock/timing selections, the Auto option will be the best option 95% of the time.
After letting the connection automation run, the block design may look like a mess of spaghetti. Click on the circle arrow icon to regenerate the layout which will reposition all of the IPs and connections to be as readable as possible:
A new IP doesn't always have to be instantiated for each peripheral. The Connect Board Component option will also allow for compatible peripherals to be added to the remaining available channels of existing IPs in the block design.
Add a second peripheral that is also GPIO to the second channel of the AXI GPIO just added. In this case, I chose to add the regular LEDs to the second channel of the AXI GPIOs that the RGB LEDs are on.
I also chose to add the two input GPIO peripherals for the buttons and switches on the Zybo board to another AXI GPIO IP using the same method:
While peripherals such as buttons, switches, and LEDs are covered by IPs readily available in the AMD IP library in Vivado, some custom peripherals might need third-party IPs. A good example of this are the Pmod connector peripherals on the Zybo, at this point the only option for compatible IPs listed under the Connect Board Component option is AXI GPIO. However, Digilent sells many Pmod peripheral boards with more functionalities than just general purpose IO.
Add IP Libraries & ReposSome vendors provide IP libraries for the peripherals on their FPGA development boards. Digilent provides IPs for their Pmod and Zmod interfaces many of their FPGA development boards are equipped with. Third party IPs can be added to the IP catalog in a Vivado project so they are available to use just like the native AMD IPs.
First, download/clone the third party IP library. For Digilent IPs, they are maintained in their vivado-library repository on Github:
Since I have several Digilent FPGA boards I use, I chose to clone the Digilent IP library to a easy location to remember in my home directory:
To add this IP repository to the Vivado project, open Settings:
Then navigate to the Repository tab under IP. Click the + button and nagivate to the cloned IP directory (vivado-library).
Vivado will validate the IPs it detects in the specified directory as a sanity check. Click OK in that popup window to acknowledge, then click OK in the Settings window to apply the changes to the Vivado project.
Returning to the Board tab in the block design, the new IPs will then appear as options under their respective peripherals as options under Connect Board Component...
So for the Pmod connectors where only AXI GPIO showed as a compatible IP before, all of the IPs for Digilent's various Pmod peripheral boards are available as options to connect to the Pmod headers on the Zybo:
I have several of these Pmod peripheral boards on hand so I chose to add a couple to the design, the first of which being the SD card Pmod board:
And the second of which being the H bridge controller Pmod board that I used in my first version of the Lego Land Rover robot with my Kria KR260:
Overall, the block design for this fixed design included two AXI GPIO IPs to drive the LEDs/buttons/switches, a Pmod SD Card IP, and a HB1 Pmod IP:
Once the block design has been completed, the next step is to run the validation tool to check for any critical warnings or errors.
That are new to Vivado probably notice that a lot of regular level warnings and info messages are created. Then there are errors and critical warnings. Errors in a project stop the Vivado tool from continuing through the workflow. Critical warnings don't prevent you from continuing on through the workflow and ultimately generating a bitstream to run on hardware.
However, critical warnings are basically Vivado's way of telling you "just because you can, doesn't mean you should". Usually, critical warnings stem from design methodology issues (ie - unhandled clock domain crossings, unrelated clock sources, etc.), and under-specified timing constraints.
These types of issues can lead meta-stability issues in your design so its behavior is unpredictable/inconsistent in hardware. This is even despite the fact that it might work fine in simulation.
In the Diagram window of the block design, click the check-mark box icon to run validation on the block design:
Once the block design is free of errors and/or critical warnings, save it using the save icon in the toolbar right below the Edit tab.
With the block design validated and saved, the next step is to create an HDL wrapper file to instantiate the block design in the project and pull the external posts up to the top level to be tied to specific FPGA package pins in a constraints file.
Right-click on the block design file in the Sources window and select Create HDL Wrapper...
There are two options for the generated HDL: to either let Vivado manage it and overwrite it automatically when updates to the block design are made, or leave it up to the user to manage and update it when the block design is modified.
Since I'm not adding any HDL source files outside of the block design (so the entire design is contained within the block design) I chose to let Vivado manage the HDL wrapper.
Once the HDL wrapper file is generated, it will appear in the Sources window and show the block design file is instantiated within it.
While the file is managed by Vivado, the HDL wrapper can be opened and the instantiation can be copy+pasted into a custom HDL source file.
Personally I copy+paste the block design instantiation into a custom HDL wrapper source file and set it as the new top file, then disable the auto-generated file until I need to modify the block design again. This way, I can still take advantage of the auto-generation of the HDL wrapper in Vivado.
Generate Block DesignThe option to generate the block design an explicitly necessary step, but it can save some time in the synthesis process for certain designs because "generating the block design" is simply running synthesis for each of the IPs in the block design before running synthesis for the overall project (which would be done anyways if it weren't when clicking the Run Synthesis option in the Flow Navigator).
Select the Generate Block Design option in the Flow Navigator window. Leave the default option for running synthesis Out of context per IP selected and click Generate.
The progress of synthesis runs for each IP can be seen in the Design Run tabs tab:
While not utilized in this project, if custom HDL sources files are needed by selecting Add Sources from the Flow Navigator window:
Then select Add or create design sources:
Existing HDL files can be imported and/or new source files can be created. The language will default to what is set in the project settings, but projects can consist of a mix of HDL languages.
As mentioned previously, I am not using any source HDL outside of the block design in this particular project.
Add or Create ConstraintsAt this point, with the HDL design complete for the logic, the top level signals need to be routed to the specific package pins of the FPGA chip with the relative timing parameters specified for each signal. This is done in Vivado using constraints files in the.xdc format.
Constraints files can be created/added to the project by again selecting Add Sources from the Flow Navigator window, then choosing Add or create constraints:
For larger designs, I personally like to have two constraints files in a design: one for the pinout and one for the timing constraints. I've found that in larger designs especially, trying to have everything in one constraints file gets overwhelming and unmanageable.
However, this design on the Zybo is small enough I'm just putting everything in one constraints file titled zybo_pinout:
Constraints files appear in the Sources window under another folder titled Constraints:
Which is then found in the Vivado project directory in /<project_name>.srcs/constrs_1/new
:
At this point, you can open the constraints file to add constraints manually or you can run synthesis and utilized the constraints wizard to use a GUI so that you don't have to write all of the initial syntax out by hand.
Run SynthesisRun synthesis by selecting the Run Synthesis option from the Flow Navigator window:
Leave the default selection to Launch runs on local host selected and click OK:
Once the synthesis run has completed, a pop-up window will prompt you for the next action. Select Open Synthesized Design and click OK.
The first thing to do is add the pinouts for the top level signals from the design. For this particular design, since I'm using the board definition files that already contain the pinout in its constraints file under the hood then everything should already be hooked up.
In the upper righthand corner, change the drop-down menu from Default Layout to I/O Planning in order to access the pinout GUI:
It appears a few of the pins for the Pmod peripheral IPs were missing from the board definition files for the Zybo so I added them back manually.
All of the IO's are in a 3.3V bank in the Zynq chip so the IO Standard needs to be set to LVCMOS33:
The master pinouts for all Digilent FPGA development boards can be found in their digilent-xdc repository on Github:
Instead of changing the IO Standard for each individual signal, a whole group can be set at once using the IO Standard drop down for the group name:
Then specify the FPGA pin for each signal under the Package Pin column:
Then use the save icon in the toolbar below the Edit tab to save the edits made to the synthesized design via the pinout GUI:
Vivado will then ask if you want to save the constraints to the existing constraints file or create a new file to save them to.
Once the new constraints have been saved, close the synthesized design using the X in the blue bar below the drop down menu where I/O planning is selected:
Rerun synthesis then after reopening the synthesized design, select the Timing Constraints Wizard from the Flow Navigator window to add the initial timing constraints for the design such as setup & hold timing for each signal tied to an FPGA package pin.
The first window of the timing constraint window gives a brief summary of the timing constraints that it can be used to add.
Again, since I'm using an FPGA development board with board definition files, timing constraints for the primary clock (the clock provided by the main oscillator chip on the PCB) are already defined with the board definition files. So I'm leaving the Primary Clocks options blank here, because redefining them will cause the design to thrown critical warnings about the redundant definitions of the same clock in separate constraints files.
For this particular design there are no generated clocks, forwarded clocks, or external feedback delays that need to be defined. Just click the Next button to proceed.
The next page is to set the input delays for each external signal to define their clock to output delay times. On a smaller board like the Zybo with regular single-end signals like GPIO, LEDs, etc. using a 50MHz fabric clock, I like to start with a max Tco (clock to output time) of 2ns and min Tco of 1ns. These values can be adjusted later as needed if timing fails in the place and route (aka - implementation) phase of the design.
After setting the input delay values, the next page sets the output delays for each external signal to define their setup & hold times. Again, for this design I'm starting with a setup time and trace delay min of 1ns with a hold time and trace delay max of 2ns.
This particular design does not require any combinational delays, physically exclusive clock groups, logically exclusive clock groups with no interaction, logically exclusive clock groups with interaction, or asynchronous clock domain crossings.
Finally, the timing constraints wizard will provide the option to view a summary of all the constraints just defined upon generating them. Make the desired selections for reports to create and click Finish:
The timing constraints wizard writes these new timing constraints to whichever constraints file is set as the target constraints file which is the zybo_pinout.xdc
file:
At this point, the timing constraints can be edited either by reopening the timing constraints wizard from the synthesized or implemented design or manually updating the values directly in the.xdc file.
Run Implementation (Place & Route)Updating the timing constraints will again put the synthesized design out-of-date, so close the synthesized design again and rerun synthesis again.
However, after this synthesis run completes, select the option to run implementation:
Once completed, select the option to open the implemented design to view the design now that it has been placed & routed for the target FPGA to verify there are no timing or methodology errors or critical warnings:
Upon opening the implemented design, there is a pop-up alerting the the design does not meeting timing:
To see which specific timing paths are not meeting timing, switch to the Timing tab at the bottom of the window:
Select the first failing signal path then maximize the Path Properties window to see the details of the signal path and its respective time delays.
The first failing signal path indicates that the signal on pin 3 of the Pmod A connector is arriving too early to the logic block clocking it in. The signal's Arrival Time is less than its Required Time.
To fix this, the input delay needs to be increased in the constraints to compensate for the here difference between the signal's Arrival Time vs Required Time. Since the signal's arrival time is 0.4ns too soon, I updated its minimum input delay for ja_pin3_io from 2ns to 3ns to account for the 0.4ns and give some room for error but not too much.
The second failing path is on pin 2 of the Pmod A connector. This signal is also arriving 0.4ns too early to the logic block clocking it in.
So I also increased the minimum input delay for ja_pin2_io from 2ns to 3ns:
After saving these modifications to the timing constraints in zybo_pinout.xdc
, close the implemented design and rerun synthesis and implementation one more time.
This time upon opening the implemented design, the Timing tab shows that the design now meets timing:
After verifying there are no other errors/critical warnings, close the implemented design.
Generate BitstreamThe final step in the workflow for a fixed platform design in Vivado is to generate a bitstream. Select Generate Bitstream from the Flow Navigator window then clock OK to launch the run.
Once completed, open the implemented design one more time to verify there are no other errors/critical warnings that popped up during the generation of the bitstream
Once the bitstream has been generated with the design free of errors/critical warnings, the final step is to export the hardware platform from Vivado to use in the Vitis IDE and/or PetaLinux to develop the software to run on the Zynq's ARM-core processor.
From the File menu tab, select Export > Export Hardware...
The export wizard will pop up with a brief initial summary of the process of exporting a hardware platform. Click Next.
On the next page, be sure to select the option to include the bitstream within the exported hardware platform. This is necessary for functionality like debugging on hardware from the Vitis IDE later and creating a PetaLinux project.
The hardware platform is exported from Vivado as an.xsa file. Select the desired name for the.xsa file and export location. I personally leave the export path as the default, which is the top level of the Vivado project directory. This way it's easier to keep track of which Vivado project an.xsa file has come from.
The final page of the export shows a summary, click Finish to export the.xsa for the project.
Validate the.xsa is present in the top level of the Vivado project directory:
At this point, the decision needs to be made about what type of software will be developed for this hardware design. If a bare-metal/no-OS application is the desire, then jump straight to the Vitis IDE next.
If the desire is to develop an embedded Linux image with Linux-based applications, use PetaLinux to create the embedded Linux image with a sysroot before using the Vitis IDE to develop the Linux application itself.
Comments