June 7, 2022

Xilinx Vivado - Ebaz - blink LED with fancy GPIO control

Here is the original idea that all this is pursuing.

For those who are new to all of this, the Ebaz board has a Xilinx Zynq chip. This chip has a dual core ARM core along with a big FPGA. The two are connected by a configurable bus called the AXI bus.

My board (the Ebaz 4205) has two LED's (one red, one green) connected to the FPGA. The ARM code in the Zynq chip has no way to access these without using the FPGA as a "middle man". I can set up an AXI gpio peripheral in the FPGA and I know how to talk to that from the ARM. I can then configure that in the FPGA to blink the lights for me.

The simple first version would be just 2 bits in the GPIO, 2 wires (in the FPGA setup) to connect those to the LED's and I am done. Software running on the ARM can do whatever it wants.

I could just do that, but why not do something more and learn more about FPGA and Verilog in the process. Here is plan B that I am pursuing in this writeup.

What about those unused 6 bits in that 8 bit register. Let's do something with them. (Actually the register ends up having 32 bits, sort of "free of charge", but we will only use 8 of them.

The LED control register I propose to implement will have 4 bits per LED as follows:

IPMM IPMM

4 bits for the green LED and 4 bits for the red LED

"I" is the bit to control the LED as per version 1. MM are two mode bits as follows:

00 let "I" control the LED
01 drive LED with 2 Hz signal
10 drive LED with 1 Hz signal
11 drive LED with 1/2 Hz signal

P is "phase" control, allowing us to invert the signal in all modes but 00

So, If I want both LED blinking at 1 Hz, I write 0x22 and it blinks forever at 1 Hz
If I want the LED to blink in alternation at 1 Hz, I write: 0x25 (setting the P bit for the second LED.
If I just want both LED off, I write 0x00

Now, to do all this, I'll need a clock source (I have that verilog code already to divide down a 50 Mhz clock). But I will need these two things (or maybe just one of them).

Also I get to try some Verilog code reuse. I make a LED module, and instantiate it twice (once for red, once for green).

First attempt (ends in train wreck)

I am doing this with Vivado 2019.1 and trying to avoid just step by step notes. At least I was until everything went to hell in a handbasket. I'll try to only indicate the general scheme of things, as well as points of confusion and trouble while using Vivado.

Incremental design with Vivado

I set up a command line script to start vivado. At some point I am going to get rid of the array of desktop icons to start all this Xilinx software. Also I made a "vivado" directory as well as a "vivado" bash script in /home/tom/bin. The script looks like:
#!/bin/bash
cd /home/tom/vivado
/tools/Xilinx/Vivado/2019.1/bin/vivado
Nothing complicated. The hope is that starting up vivado in its own directory will avoid having it leave all kinds of rubbish (.jou and .log files) in my home directory. And happily it causes it to default to placing new projects in this directory as well, rather than scattering them around in my home directory.

I start Vivado and create a new project for the Ebaz. I create an empty constraint file as well as a vivado source file. Then I go behind Vivado's back and copy the files I want over the top of those files. And I edit the files using vim also. Sometimes I edit using the Vivado editor, and when I save the file, vim immediately realizes it. Vivado is not so obvious. (The SDK would realize when I had changed a file, and make it easy for me to act on it).

Actually, you just have to watch very closely. Vivado opens a pale yellow bar at the very top of the screen (not in the area where you may be editing the files) that tells you that things have changed and offers to "refresh" the files.

I just stumbled over a tip that Vivado will allow you to change your editor. And it is easy. Go to tools -- settings, find text editor, then you have a menu of choices, including vim.

I set up a block diagram. This needs to be open before you can "Add Module to Block Design", which sort of makes sense.

Also I got weird errors about not having constraints when I tried to run Synthesis. This was because I needed to "Generate Block design" before doing that, or so I thought. Finally I write the bitstream, or I try to. I am still getting this darned error:

No constraints selected for write.
One suggestion is that the XDC file (constraints) may be malformed and giving Vivado indigestion, and it is being silently ignored. I ruthlessly pare it back to 2 lines as follows:
set_property -dict { PACKAGE_PIN W14 IOSTANDARD LVCMOS33 } [get_ports { red_led }];
set_property -dict { PACKAGE_PIN W13 IOSTANDARD LVCMOS33 } [get_ports { green_led }];
This time I look at every line spit out in the "Log" panel at the bottom of the screen. And during synthesis I see the following messages:
Parsing XDC File [/u1/home/tom/vivado/ebaz_blink_4/ebaz_blink_4.srcs/constrs_1/new/ebaz.xdc]
WARNING: [Vivado 12-584] No ports matched 'red_led'. [/u1/home/tom/vivado/ebaz_blink_4/ebaz_blink_4.srcs/constrs_1/new/ebaz.xdc:1]
CRITICAL WARNING: [Common 17-55] 'set_property' expects at least one object. [/u1/home/tom/vivado/ebaz_blink_4/ebaz_blink_4.srcs/constrs_1/new/ebaz.xdc:1]
Resolution: If [get_] was used to populate the object, check to make sure this command returns at least one valid object.
WARNING: [Vivado 12-584] No ports matched 'green_led'. [/u1/home/tom/vivado/ebaz_blink_4/ebaz_blink_4.srcs/constrs_1/new/ebaz.xdc:2]
CRITICAL WARNING: [Common 17-55] 'set_property' expects at least one object. [/u1/home/tom/vivado/ebaz_blink_4/ebaz_blink_4.srcs/constrs_1/new/ebaz.xdc:2]
Resolution: If [get_] was used to populate the object, check to make sure this command returns at least one valid object.
Finished Parsing XDC File [/u1/home/tom/vivado/ebaz_blink_4/ebaz_blink_4.srcs/constrs_1/new/ebaz.xdc]
Completed Processing XDC Constraints
My block design clearly has 2 external "connection thingies" labeled "red_led" and "green_led", so this does not make sense.

I try a variety of things and finally give up.

This is what I call a vivado train wreck and the only thing to do is to give up and start over entirely. I will rename the project ebaz_blink_4_wreck and try again.

Round 2

Type "vivado" at the command line to start. Create ebaz_blink_4 (at /home/tom/vivado/ebaz_blink_4). I let vivado create my source -- fancy.v with two ouput ports led1 and led2.
module fancy(
    output led1,
    output led2
    );

    assign led1 = 1;
    assign led2 = 1;

endmodule
Now I edit the constraints file ebaz.xdc (which is entirely empty) and give it only 2 lines:
set_property -dict { PACKAGE_PIN W14 IOSTANDARD LVCMOS33 } [get_ports { red_led }];
set_property -dict { PACKAGE_PIN W13 IOSTANDARD LVCMOS33 } [get_ports { green_led }];
I create a block design named "ledio", I pull Zynq into it, for no good reason right now, though I know I will need it later. I run block automation, which adds DDR and IO. I add "fancy" to the block design. I use "make external" to get the off-page connector thingies. I change their names to red_led and green_led.

I try "generate block design" and it complains about unconnected clocks. So I just delete Zynq, DDR, and IO and try again. This seems to go OK. You gotta keep your eye on the "status" in the upper right to know if it is still busy or if it is finished. Vivado is so slow that I have plenty of time to type stuff like this. I sure wish it didn't default to "open the implemented design", which just wastes my time and I never want to see.

I am still getting the exact same stupid "no constraints" rubbish.

I carefully review some of my notes on previous sucessful projects and discover an important step that I forgot:

Find "ledio" under Sources -- Design Sources.
Pull down a menu and select "Create HDL wrapper".

Now I do the usual sequence:

Run Synthesis
Run Implementation
Create Bitstream
Remember that the status info at the upper right is the only way to know what if anything is going on.

Where the heck is the bitstream?

It is at
ebaz_blink_4/ebaz_blink_4.runs/impl_1/ledio_wrapper.bit
Where else would it be? I have discovered a handy tool for downloading bitstreams without all the fuss of using the Vivado hardware manager. Namely "openFPGALoader" and I could use it like this:
openFPGALoader -c xvc-client ebaz_blink_4/ebaz_blink_4.runs/impl_1/ledio_wrapper.bit
But I made a little script "loadbit" that allows me to just do this:
loadbit ebaz_blink_4/ebaz_blink_4.runs/impl_1/ledio_wrapper.bit
(Note that xvcd-pico needs to be running for this to work).

The download works just fine, but when it finishes, the DONE led on the board is on, but both of my LED are off. I go back and edit "fancy.v". When I am done though I can go straight to "Run Synthesis". There is not need to regenerate the HDL wrapper or anything else apparently. Perhaps when I add blocks to the design I will need to do that.

And it works -- both of my LED are on now. My mistake was that I need to write a "0" to turn the LED on, not a "1", as follows:

module fancy(
    output led1,
    output led2
    );

    assign led1 = 1;
    assign led2 = 1;

endmodule

Make changes

Now that I have sanity and a working starting point, I want to add some more blocks, essentially repeating what I did in the "train wreck" above. It will be interesting to learn if I need to regenerate the HDL wrapper when I add some blocks. I add Zynq and AXI gpio blocks, run connection automation (but not automating the GPIO side of the AXI gpio).

Now I double click on AXI gpio block, edit IP properties, make it "all outputs" 32 bits wide. I also add a 32 bit input vector to fancy. And I connect the two so that fancy gets a 32 bit thing from axi gpio. While I am at it, I add a clock input to fancy also. I connect this to FCLK_CK0 on the diagram. The "fancy" verilog code declares these inputs, but doesn't do anything with them, but we should still be able to generate a bitstream.

I don't see any way to regenerate an HDL wrapper or anything else to make Vivado aware that I have overhauled the block diagram, so I just click "Run synthesis" and rattle through the usual sequence. I get a bitstream that acts the same as the last one (turns on both LED), so I really wouldn't know if my design changes really got picked up.

Now I want to start making signficant changes to fancy.v. First I want to divide down the clock and use it to blink those LED. I want to use vim to do my editing. I find the file I want to edit at:

ebaz_blink_4/ebaz_blink_4.srcs/sources_1/new/fancy.v
I make my edits, refresh the changed modules in Vivado and this time I click the little 2x2 matrix with some green at the top (which does the whole bitstream generation sequence). Here is the verilog I am now using. It now uses the clock input, but still ignores the 32 bit command vector.
module fancy(
    input [0:31] command,
    input clock,
    output led1,
    output led2
    );

    parameter p_CNT_1HZ = 50_000_000;

    reg [31:0] r_CNT = 0;

    reg  r_TOGGLE = 1'b0;

    always @ (posedge clock)
        begin
          if (r_CNT == p_CNT_1HZ-1)
            begin
              r_TOGGLE <= !r_TOGGLE;
              r_CNT    <= 0;
            end
          else
            r_CNT <= r_CNT + 1;
        end

    assign led1 = r_TOGGLE;
    assign led2 = ~ r_TOGGLE;

endmodule
And it works when downloaded. I now have both LED flashing in alternation.

Verilog games

What about multiple modules in a file?
Do modules need to be declared in the file before they are referenced?
Do we invert a signal with ~ or with ! ??

It seems to work perfectly well to put multiple modules in a file. I keep the "main" module at the top of the file, this being the one that has inputs and outputs that should have connections in the block diagram. Modules follow it that it uses and that seems to cause no problems. The following is accepted, and works just the same as the above verilog. Notice that it does not mind accepting an expression as an input to an instantiated module. The following is one step along the way.

module fancy(
    input [0:31] command,
    input clock,
    output led1,
    output led2
    );

    myled led01 ( r_TOGGLE, led1 );
    myled led02 ( ~r_TOGGLE, led2 );

    parameter p_CNT_1HZ = 50_000_000;

    reg [31:0] r_CNT = 0;

    reg  r_TOGGLE = 1'b0;

    always @ (posedge clock)
        begin
          if (r_CNT == p_CNT_1HZ-1)
            begin
              r_TOGGLE <= !r_TOGGLE;
              r_CNT    <= 0;
            end
          else
            r_CNT <= r_CNT + 1;
        end

    // assign led1 = r_TOGGLE;
    // assign led2 = ~ r_TOGGLE;

endmodule

module myled (
    input value,
    output led
    );

    assign led = value;

endmodule
After a number of iterations, I arrive at this. Which seems to work! Note that I just let the 3 bit "slow" counter overflow (who cares?)

Also a word here about Verilog array specifications. This is one of those things that authors are so familiar with that they neglect to explain it clearly and properly.

Verilog seems to let you use [0:31] or [31:0] interchangeably.
BUT they do mean something different.
The left number is always the MSB.
So, you have the choice of calling the MSB 0 or 31

I am taking the road of calling the MSB 31

module fancy(
    input [31:0] xcmd,
    input clock,
    output led1,
    output led2
    );

    // for testing, I set values here
    // for launch, be sure this line is commented out
    // (or Vivado complains about multiple agents driving
    // the same input -- which makes sense.)
    // and change the name ycmd to xcmd
    // wire [31:0] ycmd = 32'h00000062;
    wire [3:0] choice1 = xcmd[3:0];
    wire [3:0] choice2 = xcmd[7:4];

    myled led01 ( r_slow, choice1, led1 );      // red
    myled led02 ( r_slow, choice2, led2 );      // green

    parameter p_CNT_2HZ = 25_000_000;

    reg [31:0] r_fast = 0;

    reg [2:0] r_slow = 0;

    always @ (posedge clock)
        begin
          if (r_fast == p_CNT_2HZ-1)
            begin
              r_slow <= r_slow + 1;
              r_fast    <= 0;
            end
          else
            r_fast <= r_fast + 1;
        end

endmodule

// Writing a 0 turns the LED on.

module myled (
    input [2:0] clock,
    input [3:0] choice,
    output led
    );

    wire user = choice[3];
    wire phase = choice[2];
    wire [1:0] sel = choice[1:0];
    reg value;

    // I am reading curious things.
    // one is that case must always be inside always
    // next is that = (blocking), not <= should be
    // used inside an always @(*)
    always @(*)
        begin
            case (sel)
                2'b00 : value = user;
                2'b01 : value = clock[0];       /* fast */
                2'b10 : value = clock[1];
                2'b11 : value = clock[2];       /* slow */
            endcase
        end

    assign led = value ^ phase;

endmodule

What about an AXI gpio driver?

The above is not much good without this. So, it will be incorporated into this project:

Once I pull the AXI gpio block into my diagram, a tab appears called "Address Editor" and this handily displays the IO address (viewed from the ARM side) of this new device. In this case it is 0x4120_0000 (which is important to take note of).


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org