Custom Axi lite slave

There are several ways to create a custom AXI-lite interface, we use the following options :

  • Create the AXI interface coding from scratch.
  • Using the Xilinx IP template.
  • Using the Vivado HLS directive.
  • Using the IPIF wrapper.
  • Using system generator interface.


In this tutorial we are going to create two AXI lite slaves, the first on using the Xilinx IP template and then we are going to use the system generator interface to make a connection with the CASPER tools.


First we are going to create our base project sourcing the tcl command of the first tutorial.

source make_project.tcl

For this tutorial we are not going to use the ADCs, DACs and GPIO so we delete them. At the end we should have only the PS, a reset block, the AXI interconnect and one Buffer for the daisy chain input clock.

Only to give a proper termination to the ADC clock we should put a buffer into that port, so we search for the the Utility Buffer IP, add it to the model. We have to be sure that it is set into IBUFDS (Input buffer for LVDS signal) and connect the p and n ports with the adc_clk_p_i and adc_clk_n_i.

At the end your model should look something near to the next image:


After deleting ADCs, DAC and the GPIO you should have something like this.

Before start, we are going to present our plans for this tutorial:

  • We are going to create a basic adder, the first variable is going to be given by the Xilinx template and the second one is going to be given by the system generator interface, lastly the output is going to be store in the Xilinx AXI lite.
  • The adder is going to be implemented in the simulink environment with an AXI bust interface in the output.
  • We also are going to flash some leds, because in the first tutorial we didnt play with them (what a shame!)

Xilinx Template:

(Note: We are going to use verilog code in this tutorial, I think depending on your settings preference Vivado could generate the sample code in VHDL too)

To use the Xilinx template, we have to press Tools>Create and Package new IP. Then it should deploys a windows wth some instructions, we press next. Now we mark the option Create a new AXI4 peripheral and press next

Now we have to give a name to our IP a name, in a delirium of creativity we put axi_lite_test, now you could select things like were to store the IP or give it a description, etc, we press next.


Now you should arrive to a nice window where you can select the type of AXI flavor you want to use, the default settings work for us. There is a little thing about the numbers of registers option that is worth to mention: If you recall the AXI lite was a single beat protocol which enables you to write/read a register, this option set the default number of registers in our IP.

Click on next and in the final window mark Edit IP and press OK. It should opens a new Vivado window with some Design sources created, for our case the name of those sources are axi_lite_test_v1_0 and axi_lite_test_v1_0_S00_AXI, press them to modify the code.

If you look at the code it has a lots of comments, so with a little introduction of the name conventions and the behavior of the protocol you should be prepared to read it and understand it.

We are going to start with the axi_lite_test_v1_0_S00_AXI which contains the AXI lite sample code. As a short explanation there are four registers named slv_reg0, slv_reg1, slv_reg2, slv_reg3 of length 32, when the write condition is present the system reads the content of the address channel and write the correspondent register. About the addressing is important to mention that the base address of each device is a modifiable parameter so the address values of the registers are referential. So, for example if we gave the address base_addr+0x08 we are accessing the slv_reg2 (remember that the AXI bus works with byte addressing).


First we add our output and input ports (start in line 17 or so) :

// Users to add ports here
        output [C_S_AXI_DATA_WIDTH-1:0] operand1,
        output [C_S_AXI_DATA_WIDTH-1:0] leds,
        input  [C_S_AXI_DATA_WIDTH-1:0] sum_out,
   // User ports ends
  // Do not modify the ports beyond this line

Now we go at the bottom of the file and add the following lines (line 403 or so):

// Add user logic here
    assign operand1 = slv_reg0;
    assign leds = slv_reg1;
  // User logic ends

So we are mapping the output ports of the leds and operand1 with the registers of the AXI-lite that we write from the master, just what we want.


Until now we only add some code, but for the input data we have to modify a little bit the code (line 370 or so):


 always @(*)
 begin
       // Address decoding for reading registers
       case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
         2'h0   : reg_data_out <= slv_reg0;
         2'h1   : reg_data_out <= slv_reg1;
         2'h2   : reg_data_out <= sum_out; //slv_reg2
         2'h3   : reg_data_out <= slv_reg3;
         default : reg_data_out <= 0;
       endcase
 end

In this portion of the code we map the read of the third register to the sum_out port. So when we issue the reading of the 0x08 address we are going to take as response the value in the sum_out port.


We save the file and now go to the axi_lite_test_v1_0 which its the wrapper of the AXI lite, so we have to add our ports to the wrapper and add the new connect them to the AXI lite instantiation.

In the lines ~17 or so we add:

// Users to add ports here
        output [C_S00_AXI_DATA_WIDTH-1:0] operand1,
        output [C_S00_AXI_DATA_WIDTH-1:0] leds,
        input  [C_S00_AXI_DATA_WIDTH-1:0] sum_out,
// User ports ends


We do the same in the instantiation (line 52 or so):

axi_lite_test_v1_0_S00_AXI # ( 
   .C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
  .C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH)
 ) axi_lite_test_v1_0_S00_AXI_inst (
  .operand1(operand1),
  .leds(leds),
  .sum_out(sum_out),
  .S_AXI_ACLK(s00_axi_aclk),
  .S_AXI_ARESETN(s00_axi_aresetn),
  .S_AXI_AWADDR(s00_axi_awaddr),


Save and now we are ready to package our IP. Go to the Package IP tab, press file tab and there should be a message in blue, press it to update the package, you have to update other fields like customization Parameters, just press the blue messages until you get a check in all the fields. Press the Review and Package field and press the Re-Package IP button.

Click yes in the pop up window and you are ready!

Now if you search in the IP catalog should be a new library (the typical name is User Repository) that has our IP.

System generator:

To use this part you need to have the right installation of the simulink toolchain, so keep that in mind.

Open our good old friend Maltab and create a new simulink design.

First we are going to put an adder and set the output as user defined, set the data as an unsigned 32 bits with 0 binary point and press Ok.

Now we search for the Gateway In component, place three of them and connect them to the input ports of our adder. Name those gateway as you wish but they are going to be our interface with the logic in Vivado. Again with a lot of imagination of our part we name two of them as axi_in and normal_in.

Open the normal_in gateway and change the data to unsigned and to 32 bits with 0 binary point.

Now open the axi_in gateway do the same, but before closing the box click into the implementation tab and change the Interface to AXI Lite and press OK:

Now search for the Gateway Out and place two of them into the model. We are going to named them as sumout_tdata and sumout_tvalid and as you should guess we name the remaining Gateway In as sumout_tready.

Open sumout_tvalid and sumout_tready and set them as Boolean. We search for a register, connect the sumout_tvalid to the input register and the sumout_tready to the output register (Those signals does nothing in this model).

Also open the sumout_tdata and set it to unsigned 32 bits and 0 binary point.

The simulink model should look like this

Finally we have to add the Xilinx System Generator token, click it.

Now press the Clocking field and set the FPGA clock period as 8. Return to the Compilation field and set the following values:

Press OK and save the project in a suitable location.

Reopen the token check if everything is set as before (I dont know why, but sometimes it doesn't save the FPGA part). If everything looks good press the Generate button. Its going to deploy the usual Xilinx logo and starts the compilation, after some seconds its ready.

Mix everything

Close Matlab and return to our Vivado project. And now we are ready to connect everything.

First add our axi_lite_test IP, run autoconnection to connect it to the AXI interconnect. Then search for the slice IP and configure it to only keep the 8 lower bits:

Now connect the leds output at the slice and connect the output to the leds port.

Now we have to add the simulink part, for that we have to Tool>Settings. It should pop up a new window, go to IP and in the Repository field press the + .

Search for the folder where you store the simulink project, now inside this folder should be a new directory named netlist (o whatever you put in the target directory in the simulink xilinx token) click select on it. Then a pop up window should appear saying that there is an IP inside this directory.

Click Ok and Ok again, and now we should have our simulink IP in the catalog. Search for it and add it. It had all our ports and also see that again Vivado recognize the AXI stream name convention to create the reduced package. Again run autoconnection to get the AXI lite port connected to the AXI interconnect.

System generator block

The final connections are to connect the sumout_tdata of the system generator block to the sum_out port of the HDL block and connect the operand1 with normal_in.

After that we have to look at the addresses of the devices, press the Address Editor tab. Sometimes Vivado gives a lot of memory range to blocks that doesn't need that amount, for example our blocks could be set into 4k of addresses and they still been over considered. We set both as 4k range addresses which is the minimum allowed.

Now we are ready to compile.

This are our addresses for the slaves, we are going to use them.

Software side

Now we are ready to test the system. We are going to use two different approaches, the both are very basic and simple.


Method 1:

Put the SD card in the Red Pitaya, connect it to your LAN and power it. Wait a little bit to the system boots and then open a terminal, go to the project folder, like I didn't change the location of the base project, mine is tmp/new_prj now issue:

find . -name *.bit
#this should returns the location of the bitfile, we denote as: <bitfile>

scp <bitfile> root@<redpitaya_ip_addr>:~/axi_lite_test.bit

The default password is root. It should transfer the bit file into the home of the red pitaya OS. Now issue;

ssh root@<redpitaya_ip_addr>

After that you are in the os, issue ls to check if the bitfile is in the home directory. To program the FPGA issue:

cat axi_lite_test.bit > /dev/xdevcfg

Now we have our model running in the FPGA, we could turn the leds on with:

monitor 0x43c00004 0x31

With this command 5 leds should turn on. Now to test our adder we have to write the base address of the HDL IP and of the Simulink IP and look in the 0x43c00008 address to see the output for that we could send:

monitor 0x43c00000 0x10
monitor 0x43c10000 0x10
monitor 0x43c00008 #this one returns 0x20

Easy as pie, the monitor command write the value that you gave to him and if there is no parameter it reads the value.


Method 2:

Now we want to program using the JTAG, so remove the SD card in order to boot everything from the JTAG. We also need one more step into the Vivado tool. You have to click into File>Export>Export Hardware and then press File>Launch SDK

This should launch a new program, where you could code the Software side of your project. We only look to generate a file to initialize the PS. When it finish to initialize you could close it (as mention we only want to create a file).


Look that the Red Pitaya has his JTAG connector next the ethernet port.

To use the Xilinx debugger you have to run:

/tools/Xilinx/SDK/2019.1/bin/xsdb

Where tools/Xilinx is where I have my installation Xilinx. Check again that you are in the project folder if you are not move it with cd. First thing we have to do is connect to the system:

connect
target 

Repeat the target command until you see that the xsdb answer something like:

  1  APU
  2  ARM Cortex-A9 MPCore #0 (Running)
  3  ARM Cortex-A9 MPCore #1 (Running)
  4  xc7z010

Those are the targets we could connect to send commands. There is some initialization procedure we have to make in order to start the processor, that's the configuration file that we create when we open the SDK program. It name is ps7_init.tcl which has several functions to configure the internal registers of the ARM processor. So first we have too look that file and sourcing it to xsdb.

 exec find . -name ps7*

There should be a lot of files who met that requirement, we are interested in the one with the tcl termination and in a directory under sdk. So for example mine look: ./tmp/new_prj/new_prj.sdk/system_wrapper_hw_platform_0/ps7_init.tcl

Also we need the location of the bit file:

exec find . -name *.bit

Now we are ready, we source the tcl file and program the fpga:

source ./tmp/new_prj/new_prj.sdk/system_wrapper_hw_platform_0/ps7_init.tcl
fpga -f ./tmp/new_prj/new_prj.runs/impl_1/system_wrapper.bit

Then the FPGA gets programmed, so we connect to the ARM0 core (in our target list is the 2):

target 2
stop
ps7_init
ps7_post_config
rst -processor

With those commands we configure the ARM core, and stop it in a vector catch mode, if we had generate a script and cross compiled for the arm core this is the moment to upload it. To start running you have to issue the command con.

con

Now we are able to play with the processor, for that we introduce you two commands:

  • mwr addr value
  • mrd addr number_of_reads

So to light up the leds we could send

mwr -force 0x43c00004 31

And to use the adder:

mwr -force 0x43c00000 0x10
mwr -force 0x43c10000 0x10
mrd -force 0x43c00008 

But we could do more with this commands, in this mode we have all the RAM address available to write what we want. Also this prompt enable us to use some tcl like scripting. For example we could write several address with the instruction:

for {set i 0x0} {$i < 0x1000} {incr i 4} { mwr -force $i $i; echo $i}

In the same way we could read all those memories with one instruction:

mrd 0x0 0x400

As a mode of recipe to work in the bare metal you should follow the next steps:

  • Export the hardware from Vivado and open a SDK to create the ps7_init file
  • Launch the xsdb, connect to the hardware, source the ps7_init from the SDK (As I remember the other ones didnt work)
  • Program the fpga, target the ARM core and initialize it with ps7_init and ps7_post_config
  • Start playing with the commands.