May 20, 2022

Xilinx Vivado - Connecting the PS and PL tutorial

This will be my third (and last perhaps) go with this tutorial. I want to use the Ebaz board, run a loop in the PS to blink the LED and use the FPGA simply to route the signal through the FPGA to the LED. The above links have the tutorial that got me started on this. I have been through it twice now, so this time I will work from my own notes and probably move along much faster.

I launch Vivado 2019.1. I am gathering quite a collection of icons for different Vivado and Vitis versions on my dekstop, so this takes some care. Vivado likes to steal focus, so I sometimes get interrupted as I type things like this, such as just now when it starts up. I create a new project "ebaz_blink_1. It is an RTL project and I select the ebaz board.

I add a source file connect.v, and skip the port setup dialog. I type in the following verilog.

module connect (
    input red_ps,
    output red_led
    );

    assign red_led = ! red_ps;

endmodule
My intention is that the "red_led" output will drive the LED and the "res_ps" input will be a signal coming from the PS (learning how that happens is the object of this game). I invert the signal to the red_led. Since it is active low, this lets writing a "1" from the PS turn the led on.

I add a constraints file, calling it master.xdc and simply copying my 2 line master constraints file into place.

set_property -dict { PACKAGE_PIN W14 IOSTANDARD LVCMOS33 } [get_ports { red_led }];
set_property -dict { PACKAGE_PIN W13 IOSTANDARD LVCMOS33 } [get_ports { green_led }];

Now, with both of those files in place, it is off to the IP integrator. I use "create block design" under "IP integrator". I call the design "clock_1". Next I use the "+" button in the Block design diagram and add the Zynq7 processing system. Once the block appears, I click on "Run Block Automation". I accept the defaults to interface to DDR and IO and click OK. Connections to DDR and FIXED_IO appear on the diagram.

Now I go to the sources tab, select "connect.v" and right click it. I click the entry "Add Module to block design" and it appears on my diagram.

Add an AXI gpio block

I use the "+" button again to add an "AXI GPIO" block. Now I click "run connection automation", add all, but unclick GPIO in axi_gpio_0, and click OK. This does magic, pulling in two more blocks and hooking the axi gpio block to the Zynq.

Now the axi_gpio block needs to be configured. I double click it and get a big dialog. I go to IP Configuration, select "all outputs" and make the GPIO width "1". Click OK.

Now to connect the axi gpio block to my logic. I hover next to "GPIO" on the axi_gpio block and click to make it give me a dingus to connect to, then use the mouse pencil to connect it to the input of my "connect" block.

Now to connect my "connect" block to the external LED. I hover over the output (red_led) and then a right mouse click gives a menu, and I select make external. This gives me a wire to a tab labeled "red_led_0". I need to change that name to match my constraints file. Clicking on the tab gives a dialog to fiddle with "External port properties". I change the name from red_led_0 to "red_led" to match the constraint file. Be sure to type return and confirm the change of name on the diagram.

At this point we are basically done. I click on the arrow in a circle (regenerate layout) to make the layout look more pretty and inspect it. Then I type Ctrl-S to save it.

Now in the sources tab, under design sources, I select the design (clocks_1) right click to get a menu, select "create HDL wrapper", then "let vivado manage and auto update" and OK.

At the very top of Vivado is an icon with some green and what looks like 4 boxes in a 2x2 arrangement. This is "generate bitstream". I click on this. It offers to save my project (yes, please) and it tells me that the synthesis and implementation are out of date and need to be rerun. I click OK and wait the usual long time. Note that the upper left of vivado tells you what it is doing and I see a spinning green arrow and the words "Running ...", so I know to just be patient. The long time is longer than usual with all the AXI blocks. Just keep checking what it is doing by glancing at the upper right corner.

Finally it finishes. There is a nice message "write bitstream Complete" with a green check in the upper right, along with a popup notification which I cancel to dismiss.

Now I use File -- Export -- Export Hardware. I take care to check "include bitstream". Exporting to local project is correct and I click OK.

Off to the SDK

I use File -- Launch SDK. This brings up a GUI of its own with lots of information, including a bunch of device addresses displayed in a "hardware platform specification". But never mind that for now.

I use File -- New -- Application project. I give it the name "ebaz_blink_1" the same as the Vivado project, hoping this won't yield a conflict. I note some settings (OS is set to "standalone"). I click Next to get a menu of templates and select "Hello World". Then Finish.

Now on the left is a file hierarchy. I expand "ebaz_blink_1" and then "src" and I see helloworld.c. I double click to open this and start hacking.

Now I do something a bit different. I hate the built in editor (this is probably Eclipse) and would much rather use Vim for anything more than changing a line or two, so I locate the source file as follows:

cd /home/tom/ebaz_blink_1/ebaz_blink_1.sdk/ebaz_blink_1/src
vi helloworld.c
As I edit the file, when I save changes, the SDK informs me and offers to reload the file which is exactly what I want.

I pull in sleep.h to get some delay functions (macros?). I can use sleep(seconds) or usleep(microseconds). I go with usleep ( 500000 ) to get a 1/2 second delay.

Typing Ctrl-S both saves the file and triggers the build process.

Now for the Ebaz board. I plug in a console connection and fire up picocom to listen to it. I power up the board. It boots (once I attach a network cable) and runs some old standalone test code I wrote a year ago.

How do we load the FPGA?

I have the pi pico JTAG gadget attached. I need to run "xvcd-pico" in some window. I run it as user "tom" which seems to work. My notes indicate it runs on TCP port 2542. I should hack it to announce this port number.

At the bottom left of the SDK is "Target Connections". I see "Hardware Server" and local is set up on port 3121 on localhost. Indeed there is something running on that port.

The SDK has a button to add a new target connection, but the types it offers me are:

Nothing about using an XVC (Xilinx Virtual Cable).

Doing this from the SDK is mysterious thus far, but I can go back to Vivado, open the hardware manager and thrash around.

I try open Target and plug along with the Wizard. I get to a place where it offers "Add Xilinx Virtual Cable". I enter localhost and it already has port 2542. Now back at the "open new hardware target" I have that target as an option and I need to click "Add Xilinx Virtual Cable" a second time and it detects my hardware (arm_dap_0 and xc7z010_1).

Now, how the heck do I program a bitstream? Back in the Hardware pane is now a pretty picture of my hardware in tree form. I select xc7z010_1 and right click gives me a menu that includes "Program device" and this seems to do the job. Now both the red and green LED go out, and what I need to do now is to load and run the ARM code.

I go to the left column (Project explorer), select "ebaz_blink_1", then right click to get a menu. I use "Run As", "Launch on Hardware" and ...

It works! The red LED is blinking away now at 1 Hz. I am also getting proper messages at 115200 baud on my console window. It was not the least bit annoyed by my previous code that was running, it just did whatever it had to do to stop it and start the new code.

My only complaint is that their "print" function does not properly handle a newline. I have to put "\n\r" in my strings to get correct results.

The final C code for this looks like:

#include 
#include "platform.h"
#include "xil_printf.h"
#include "xgpio.h"
#include "sleep.h"

int main()
{
    XGpio led_gpio;;
    int state = 0;

    init_platform();

    print("Here we go ....\n\r");

    XGpio_Initialize ( &led_gpio, XPAR_AXI_GPIO_0_DEVICE_ID);
    XGpio_SetDataDirection ( &led_gpio,1,0);

    for ( ;; ) {
        usleep ( 500000 );
        state = ! state;
        XGpio_DiscreteWrite ( &led_gpio, 1, state);
        print ( "beep\n\r" );
    }

    cleanup_platform();
    return 0;
}

Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org