November 28, 2024

Antminer S9 board - Vivado -- blink the four board LED

Happy Thanksgiving. And thanks to Xilinx for making Vivado freely available for common Zynq parts.

The idea here is to generate a bitstream that will configure the FPGA so I can blink the four onboard super bright green LED from C code running on the PS side of the chip.

I could add a gpio to the block diagram and use it, but instead I would like to try something new and use the EMIO to control the LED. So I will have 4 EMIO signals getting routed to 4 ports. I will stick a small bit of verilog in that path, simply to split up the 4 signals. There doesn't seem to be an easy way to do that in Vivado. The EMIO comes out as a single group with 4 signals in it. There may be a way, but using verilog will get it done and I know how to do it.

Start the Vivado project

I am running Vivado 2022.2. I type "vivado" at the command line to get it started. I go through the usual "new project wizard". I select a board rather than a part and I find "Antminer S9" thanks to the board files I just installed.

Customize the Zynq block to get EMIO

Now select "Create Block Design" under "integrator" on the left. It wants a name for the design and I go with the suggested "design_1". I use the "+" to add "ZYNQ7 Processing System". I double click on this to customize the block, A big GUI thing with lots of green appears.

On the right side under Navigator is MIO configuration.
Inside of this look for I/O Peripherals, expand this and look for GPIO, expand this.
Check the EMIO checkbox, select 4 as the width, click OK.
Now on the Zynq block, there appears a thing "GPIO_0" that was not there before.

Add a Constraint file

This is as good a time as any to do this. My master constraint file is now at:
/u1/Projects/Zynq/Git_antminer/board_files/S9_Master.xdc
We need to copy this into our project. Up and left from your block diagram is a window which typically has the "design" tab selected by default. Select "sources" instead and use the big "+". Select "add or create constraints", then go find the file. Once you have found and selected it, ensure that the checkbox to "copy constraint files into project" is selected, then "finish".

Add a block to hold our Verilog

First, add the verilog file. Use the same "+" button you just used to add the constraint file, but this time say "add or create design sources". I use "create file" and name it "led_mapper.v". When I finish, it launches a dialog allowing me to create ports. I do pretty much this, creating 5 ports:
inputs - input bus 3:0
a - output
b - output
c - output
d - output
The input I select "bus" and set MSB = 3 and LSB = 0. Note that this exercise is just a helper, I could have done all this in verilog, gave it the file, and skipped this crazy dialog. Doing this will generate a verilog template we will have to flesh out. When this is all done the file will appear in the hierarchy being displayed.

Now to get a block on the diagram, find the file in the hierarchy under "sources", right click on the verilog file name and select "Add module to block design". The block should appear, mine does showing "inputs[3:0]"

Connect the block

This is always frustrating and difficult. I try to drag from either the GPIO_0 on the Zynq or the inputs on the RTL block, but it never does what I want. I can cancel whatever drag I have started by hitting ESC, which helps with this trial and error process.

Let's try creating the LED ports first. I can click on the background of the block diagram, get a menu, and select "create port". I create ports "led_A" and so forth, take care to make them outputs and then it is easy to connect them one by one to my RTL block. When I am done, I use the same menu to "regenerate layout" and it cleans up the mess.

As I dig deeper, I find that the problem is that the 4 bit wide EMIO I created is all inputs, and Vivado is sensibly refusing to connect inputs to inputs. How do I configure my EMIO "bus" as outputs?

I click on the text GPIO_0 on the Zynq block to select it, then on the menu I use "create interface port". This creates a port named "GPIO_0". I do the same thing on my RTL block, creating an interface port, but I change the name from inputs to GPIO_0 to match the other. This looks promising, but I'll be surprised if it works.

Somewhere along the line as I fuss with the mouse around the GPIO label on the Zynq block, a bunch of other things appear labeled GPIO_I, GPIO_O, GPIO_T -- perhaps input, output, and tri-state? Something is lurking here that deserves investigation.

I try to validate the design and it complains that M_AXI_GP0_ACLK is not connected to a valid clock source. Run block automation is not available. I have a theory of an indirect way to fix this. I add an AXI_GPIO block and then run block automation. The design now validates. Curiously the added AXI_GPIO block has its output routed to "leds_2bits" which are declared in the board files (and a useless legacy from the Ebaz board files)

The missing clock was obtained from a "rst_ps7_0_50M" block that got hauled in (along with ps7_0_axi_periph). The clock itself comes from "slowest_sync_clk" on the "rst" block.

Take this the rest of the way

I mostly want to do this to refresh my memory and to see if there are lurking problems.

Go to sources, find "design_1" under "Design Sources", right click to get a menu and select "create HDL wrapper". My notes say that if you come back to a design, add blocks, and want to redo this, you will need to use "refresh hierarchy" instead. Now follow the sequence: Generate block design Run synthesis Run implementation (skip "open implemented design" Generate bitstream Run the design


Each of these takes considerable time, and I can often hear my CPU fans crank up.
Keep an eye on the upper right corner of Vivado where messages appear telling you what it is doing.
Often you think a step is done, but it is not.
In particular, watch for a spinning circle arrow to indicate that it is busy and things are in progress.
You will see "Ready" when a step is all done.
If you are watching the screen, a popup appears (for a few seconds) at the lower right of the screen
telling you that steps have finished.

This failed during "run implementation" and for a perfectly good reason that I understand. The block "led_mapper_0" just has stub verilog -- I never fleshed it out! I had expected some kind of complaints about my ports GPIO_0, but that seems OK.

Where is that verilog file?

I prefer to find it and edit it externally using vim. The "stub" looks like this. I add the four "assign" statments:
module led_mapper(
    input [3:0] inputs,
    output a,
    output b,
    output c,
    output d
    );

	assign a = inputs[0];
    assign b = inputs[1];
    assign c = inputs[2];
    assign d = inputs[3];
endmodule
Vivado doesn't seem to disagree with this, but now I get an error I have never seen before saying that there are 4 unplaced ports and no place can be found to place them. The ports are GPIO_0[0] through GPIO_0[3]. I am not entirely surprised, and don't intend to push this any further. I'll probably delete this whole project entirely and start over trying something else.

Here is my idea why this didn't work -- these "ports" are not intended for connections within the diagram (like I tried to use them), but for connection to the outside world, however that may be defined. In my case it is connection to something specified in the constraint file. So I had 4 signals in, 4 signals out, all with the same name. Rather than connect them to each other, it saw that as a conflict as it was only willing to connect one or the other to the outside world. Maybe.

Where is the bitstream?

We don't have one for this project given that things failed, but if we had had success it would have been in a location like this:
cp /home/tom/vivado/ebaz_blink_4/ebaz_blink_4.runs/impl_1/ledio_wrapper.bit ledio.bit

Another approach

This fellow configures EMIO, then gets it to set up an interface port. He doesn't try to connect that to any other blocks (like I did), but he fiddles with it to get the port to connect directly to an LED declared in his constraint file. His example has just one LED, but perhaps I can do the same and get my 4 bit wide port connected to four LED?

I read somewhere that it is possible to get a GPIO (an AXI gpio in the case mentioned) connected to multiple ports by hand editing a "wrapper file", This is something to explore and investigate.

Conclusions and discussion

There must be a different approach to using EMIO. For something as important to the design of the Zynq, this seems surprisingly neglected in Vivado. We can only select some number of pins and they are inputs. What if we want to use different groups of EMIO pins for different purposes, some as inputs, and others as outputs. Surely there is a way to do that? Maybe not.

The other approach is to ignore EMIO and instantiate AXI gpio as needed. This surely will work, but if this is what we must do, then why have EMIO at all? I suspect there are other approached, probably outside of Vivado, that can be used to exploit EMIO.


Feedback? Questions? Drop me a line!

Tom's Computer Info / [email protected]