FPGA Zero to Hero Vol 6 EBAZ4205 Chronicles #
In my previous blog posts Link and Link I created a basic Verilog module and used the JTAG to test it and booted Linux on the board. As promised I will continue the series and this time create a custom AXI lite peripheral and access it from within the Linux kernel running on the board. I will access the peripheral from within the Petalinux running on the board. But I found out that the older version of Petalinux and Vivado specifically did not work as expected with Ubuntu 22.04. I rolled back to Petalinux 2021.2 and Vivado 2021.2 to get it working. Sometime the latest version of the software does not work as expected with older version of Ubuntu linux. The result might be different for you if you are using a different version of the linux distribution.
NOTE: My current system configuration is:
- OS: Ubuntu 22.04
- VIVADO Version: Vivado v2021.2 (64-bit)
- PETALINUX Version: Petalinux 2021.2
What is AXI Lite #
AXI Lite is a simplified version of the AXI (Advanced eXtensible Interface) protocol which is part of family of bus protocols ARM Advanced Microcontroller Bus Architecture (AMBA) standard. It is widely used in FPGA designs for communication between FPGA PL and PS (Processing System). The lite version is designed for low-bandwidth, low-latency communication and is often used for control and status registers in peripherals. For our simple cases we can use AXI Lite to communicate with the peripherals like GPIO, SPI, I2C, etc.
AXI Lite provides a simpler interface compared to the full AXI protocol, making it easier to implement and use in designs where high throughput is not required. It supports single-word transactions and has a reduced set of signals, which makes it suitable for simple control applications.
AXI4-Lite Interface Signals #
AXI4-Lite uses five channels:
- Read Address
- Read Data
- Write Address
- Write Data
- Write Response
To know about the AXI4-Lite interface signals in detail you can refer to the Xilinx AXI4-Lite documentation also ARM AMBA AXI4-Lite documentation might be useful.
There is a good tutorial that explains the AXI4-Lite interface in detail for custom AXI-lite interface.
My aim is to create a AXI lite peripheral and access it from within the Linux kernel running on the board. I will not go into details of the AXI4-Lite interface as it is beyond the scope of this blog post. If in future I get time I will create a separate blog post on the AXI4-Lite, AXI and AXI streams interface in detail and how to use it in FPGA designs.
Vivado HW AXI Lite IP Creation #
I will be creating a custom AXI lite peripheral that can be used to switch on/off the LEDs on the EBAZ4205 board. The peripheral will use only 1 register that can be accessed from the Linux kernel running on the board. The last 2 bits of the register will be used to control the LEDs on the board. The first bit will be used to switch on/off the first LED, the second bit will be used to switch on/off the second LED. The LEDs are the one next to the ethernet port on the board.
To create a custom AXI lite peripheral we need to create a new Vivado project. I will not go into details of creating a Vivado project as I have already covered it in my previous blog Link. I will just provide the steps to create a custom AXI lite peripheral and use it in the Vivado project.
To create a custom AXI lite peripheral assuming you have already created a Vivado project you need to follow these steps:
Go to Menu bar and go Tools
-> Create and Package IP
-> Create New AXI4 Peripheral
. This will open the Create and Package IP wizard as shown below. Select Create a new AXI4 peripheral

Press Next and this will open the Periphereral Details wizard. The most important fields are Name, and IP Location. You can fill in the details as per your requirement. For this example, I will use the following details:

Press Next and this will open the Add Interface wizard. Here you can select the type of peripheral you want to create. For this example, I will select AXI4-Lite as the peripheral type and Slave mode as we want to create a slave peripheral that can be accessed by the master (Zynq PS). And for now we don’t need many Registers so we can leave the Number of Registers field as 4. which is the default value minimum possible value that you can put there. You can change it later if you want to add more registers to the peripheral.

Press Next and this will open the Create Peripheral wizard. You can see the summary of the peripheral you are going to create. Select Edit IP as we want to add custom code to the IP and press Finish to create the peripheral. This should open a new Vivado project with the peripheral created for editing the IP.

Now we need to add the custom code to the peripheral. To do this, go to the IP Sources tab in the Sources window and click on the “+” button or right click on Design Sources and click on Add Sources… to add a new file. The process is similar to adding a new file in any other Vivado project and I have couvered it in detail in my pervious blog Link. I will create a new Verilog file named led_ip.v
and add the following code to it:
`timescale 1ns / 1ps
module led_ip(
input wire [31:0] slv_reg,
input wire clk,
output reg led_1,
output reg led_2
);
always @(posedge clk) begin
if(slv_reg[0] == 1) begin
led_1 <= 1;
end else begin
led_1 <= 0;
end
if(slv_reg[1] == 1) begin
led_2 <= 1;
end else begin
led_2 <= 0;
end
end
endmodule
Now what it is doing is it is taking the slv_reg
input which is the register that we will be accessing from the Linux kernel and checking the first two bits of the register. If the first bit is set to 1 then it will switch on the first LED and if the second bit is set to 1 then it will switch on the second LED. The slv_reg
input is a 32-bit register that we will be accessing from the Linux kernel.
Now we need to connect this module to the AXI lite interface. To do this, we need to add an instance of the led_ip
module in the LED_IP_v1_0_S00_AXI.v
file and connect it to the AXI lite interface signals. Also we need to create two output wires to connect the LEDs. Go to the LED_IP_v1_0_S00_AXI.v
file and add the following code:
To add the custom output just add the following lines to the LED_IP_v1_0_S00_AXI.v
file where you see the comment // User to add ports here
as shown below:
module LED_IP_v1_0_S00_AXI #
(
// Users to add parameters here
// User parameters ends
// Do not modify the parameters beyond this line
// Width of S_AXI data bus
parameter integer C_S_AXI_DATA_WIDTH = 32,
// Width of S_AXI address bus
parameter integer C_S_AXI_ADDR_WIDTH = 4
)
(
// Users to add ports here
output wire led_1,
output wire led_2,
// User ports ends
...
...
and to add the instance of the led_ip
module just add the lines shown below to the LED_IP_v1_0_S00_AXI.v
file where you see the comment // Add user logic here
it should be in the end of the file:
// Add user logic here ...
led_ip l1 (
.slv_reg(slv_reg),
.clk(s_axi_aclk),
.led_1(led_1),
.led_2(led_2)
);
// User logic ends
endmodule
```verilog
We need to do same to the top level module of the AXI lite peripheral so that we can connect the led_ip
module to the AXI lite interface signals.
Open file LED_IP_v1_0.v
and we need to add the output wires for the LEDs and also instantiate the led_ip
module. Add the following lines to the LED_IP_v1_0.v
file where you see the comment // User to add ports here
it should look like this:
`timescale 1 ns / 1 ps
module LED_IP_v1_0 #
(
// Users to add parameters here
// User parameters ends
// Do not modify the parameters beyond this line
// Parameters of Axi Slave Bus Interface S00_AXI
parameter integer C_S00_AXI_DATA_WIDTH = 32,
parameter integer C_S00_AXI_ADDR_WIDTH = 4
)
(
// Users to add ports here
output wire led_1,
output wire led_2,
// User ports ends
// Do not modify the ports beyond this line
// Ports of Axi Slave Bus Interface S00_AXI
input wire s00_axi_aclk,
input wire s00_axi_aresetn,
input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_awaddr,
input wire [2 : 0] s00_axi_awprot,
input wire s00_axi_awvalid,
output wire s00_axi_awready,
input wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_wdata,
input wire [(C_S00_AXI_DATA_WIDTH/8)-1 : 0] s00_axi_wstrb,
input wire s00_axi_wvalid,
output wire s00_axi_wready,
output wire [1 : 0] s00_axi_bresp,
output wire s00_axi_bvalid,
input wire s00_axi_bready,
input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_araddr,
input wire [2 : 0] s00_axi_arprot,
input wire s00_axi_arvalid,
output wire s00_axi_arready,
output wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_rdata,
output wire [1 : 0] s00_axi_rresp,
output wire s00_axi_rvalid,
input wire s00_axi_rready
);
//wire led_1;
//wire led_2;
// Instantiation of Axi Bus Interface S00_AXI
LED_IP_v1_0_S00_AXI # (
.C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
.C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH)
) LED_IP_v1_0_S00_AXI_inst (
.S_AXI_ACLK(s00_axi_aclk),
.S_AXI_ARESETN(s00_axi_aresetn),
.S_AXI_AWADDR(s00_axi_awaddr),
.S_AXI_AWPROT(s00_axi_awprot),
.S_AXI_AWVALID(s00_axi_awvalid),
.S_AXI_AWREADY(s00_axi_awready),
.S_AXI_WDATA(s00_axi_wdata),
.S_AXI_WSTRB(s00_axi_wstrb),
.S_AXI_WVALID(s00_axi_wvalid),
.S_AXI_WREADY(s00_axi_wready),
.S_AXI_BRESP(s00_axi_bresp),
.S_AXI_BVALID(s00_axi_bvalid),
.S_AXI_BREADY(s00_axi_bready),
.S_AXI_ARADDR(s00_axi_araddr),
.S_AXI_ARPROT(s00_axi_arprot),
.S_AXI_ARVALID(s00_axi_arvalid),
.S_AXI_ARREADY(s00_axi_arready),
.S_AXI_RDATA(s00_axi_rdata),
.S_AXI_RRESP(s00_axi_rresp),
.S_AXI_RVALID(s00_axi_rvalid),
.S_AXI_RREADY(s00_axi_rready),
.led_1(led_1),
.led_2(led_2)
);
// Add user logic here
// User logic ends
endmodule
See that I have added two output wires led_1
and led_2
to the top level module added it to LED_IP_v1_0_S00_AXI_inst
instance. This will connect the led_ip
module to the AXI lite interface signals and also to the output wires that we will be using to control the LEDs on the board.
Now we need to package the IP so that we can use it in the Vivado project. To do this double click on component.xml
file in the IP Sources tab and this will open the IP Packager window. Notice all the pakaging steps should be green checked to work properly. If you see any edit symbol as shown then you need to do the specified steps to fix the errors or merge changes.

Finally package the IP by clicking on the Package IP button in the IP Packager window. This will create a new IP in the Vivado project that you can use in your design.

Add IP to Vivado Project #
First we need to enable a AXI Master interface on the Zynq PS to communicate with the AXI lite peripheral. To do this, open the Block Design in your Vivado project and double click on the Zynq7 Processing System IP block. This will open the Re-customize IP window. In the M_AXI_GP0 section, enable the AXI4-Lite interface by checking the box next to it. This will allow the Zynq PS to communicate with the AXI lite peripheral.

Open your Vivado project. In the IP Integrator canvas, right-click and select Add IP. Search for your newly created IP (e.g., LED_IP_v1_0
) and add it to the design.

Once you have done the above steps the Run Connection Automation
should show up press it and let Vivado connect the IP to the Zynq PS. If it does not show up then you can manually connect the IP to the Zynq PS by connecting the M_AXI_GP0 interface of the Zynq PS to the S00_AXI interface of the AXI lite peripheral with AXI interconnect.

it should add AXI interconnect and Process System Reset blocks to the design. The AXI interconnect block is used to connect the AXI lite peripheral to the Zynq PS and the Process System Reset block is used to generate reset signal.

No we neet to connect the output wires of the AXI lite peripheral to the LEDs on the board. The process is described in my previous blog Link. You can refer to that blog post for the details.

We need to add the led_1 and led_2 output wires to the constraints file so that we can use them to control the LEDs on the board. To do this, open the Constraints tab in the Sources window and double click on the constraints.xdc file. This will open the constraints file in the editor. Add the following lines to the constraints file:
# Green LED
set_property IOSTANDARD LVCMOS33 [get_ports {led_1_0}]
set_property PACKAGE_PIN W13 [get_ports {led_1_0}]
# Red LED
set_property IOSTANDARD LVCMOS33 [get_ports {led_2_0}]
set_property PACKAGE_PIN W14 [get_ports {led_2_0}]
Finally the block design should look like this:

Now we can synthesize, implement and generate the bitstream for the design. To do this, go to the Flow Navigator and click on Run Synthesis. Once the synthesis is complete, click on Run Implementation and then click on Generate Bitstream. Export Hardware and follow the steps from my previous blog Link to create a kernel image and boot the board with the new bitstream.
Petalinux Configuration and Rebuild #
Rebuild the Petalinux project to include the new AXI lite peripheral. If you have not created a Petalinux project then you can refer to my previous blog Link for the details.
Accessing the AXI Lite Peripheral from Linux Kernel #
We can access the AXI lite peripheral from the Linux kernel using the /dev/mem interface. The /dev/mem interface allows us to access the physical memory of the system and read/write to the registers of the AXI lite peripheral.
To access the AXI lite peripheral, we need to know its physical address. You can get it from the Device Tree file of the Petalinux project. The physical address of the AXI lite peripheral is defined in the Device Tree file.
You can find it by looking at components/plnx_workspace/device-tree/device-tree/pl.dtsi
file in the Petalinux project. The entry for the AXI lite peripheral should look like this:
dts/ {
amba_pl: amba_pl {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges ;
LED_IP_0: LED_IP@43c00000 {
clock-names = "s00_axi_aclk";
clocks = <&clkc 15>;
compatible = "xlnx,LED-IP-1.0";
reg = <0x43c00000 0x10000>;
xlnx,s00-axi-addr-width = <0x4>;
xlnx,s00-axi-data-width = <0x20>;
};
};
};
The physical address of the AXI lite peripheral is 0x43c00000
and the size is 0x10000
(64 KiB). We will use this address to access the AXI lite peripheral from the Linux kernel.
To access the AXI lite peripheral from the Linux kernel, we can use the following C code snippet. This code will toggle the first two bits of the register to switch on/off the LEDs on the board.
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#define LED_IP_BASE_PHYS 0x43C00000UL /* address from pl.dtsi*/
#define LED_IP_MAP_SIZE 0x10000 /* 64 KiB window */
#define REG_LED_CTRL 0x00 /* first register*/
static inline void busy_wait_ms(unsigned ms)
{
struct timespec ts = { .tv_sec = ms / 1000,
.tv_nsec = (ms % 1000) * 1000 * 1000 };
nanosleep(&ts, NULL);
}
int main(void)
{
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0) {
perror("open /dev/mem");
return EXIT_FAILURE;
}
void *virt = mmap(NULL, LED_IP_MAP_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, LED_IP_BASE_PHYS);
if (virt == MAP_FAILED) {
perror("mmap");
close(fd);
return EXIT_FAILURE;
}
volatile uint32_t *reg = (volatile uint32_t *)virt;
/* Simple blink loop: toggle bit 0 of REG_LED_CTRL ten times */
puts("Blinking LEDs …");
for (int i = 0; i < 10; ++i) {
reg[REG_LED_CTRL / 4] = 0x03; /* LEDs on */
busy_wait_ms(250);
reg[REG_LED_CTRL / 4] = 0x0; /* LEDs off */
busy_wait_ms(250);
}
close(fd);
return EXIT_SUCCESS;
}
Now to build the above code there are different ways to do it. You can create a new Petalinux application and add the above code to it or you can just compile it using the cross compiler provided by the Petalinux project.
You can also create a petalinux sdk using the following command:
petalinux-build --sdk
This will create required toolchain and you can use it to cross-compile the above code. and move it to the EBAZ4205 board with scp
command.
You can also include the gcc compiler on the board itself. To use the gcc compiler on the board you need to enable it in the Petalinux project. To do that you need to configure rootfs in the Petalinux project. You can do this by running the following command:
petalinux-config -c rootfs
This will open the Petalinux Rootfs Configuration menu. Then navigate to packagegroup-core-buildessential and enable it by checking the box next to it. This will include the gcc compiler in the root filesystem of the Petalinux project.
petalinux-config -c rootfs
Filesystem Packages --->
misc --->
[*] packagegroup-core-buildessential
compile the code using the following command:
gcc -o led_control led_control.c
And then run the code on the board using the following command:
sudo ./led_control
This will toggle the first two bits of the register and switch on/off the LEDs on the board. You should see the first two LEDs blinking on the board.
I did the same steps on the EBAZ4205 board and it worked as expected. The first two LEDs on the board started blinking. You can modify the code to control the LEDs as per your requirement.

Future Blog Posts #
I would give a homework to the readers to create a custom AXI-lite peripheral that can be used to control uses the SPI module we created in previous blog post Link and access it from within the Linux kernel running on the board. You can use the same steps as described in this blog post to create the AXI-lite peripheral and access it from within the Linux kernel.
I would like to continue the series and create more complex AXI based IP and access them from within the Linux kernel running on the board. I will also cover how to create custom device drivers for the AXI peripherals (Non-Lite version) and AXI streams and access them from user space applications.
In future blog posts I want to create a Neural Network Accelerator using the AXI interface and access it from within the Linux kernel running on the board. I will also cover how to create custom device drivers for the neural network accelerator and access it from user space applications. Also posible is to create a device driver for the LED_IP and access it from within the Linux kernel running on the board. Unfortunately It might take some time due to my busy schedule. But I will try to cover it as soon as possible.
Tags: #FPGA
#Zynq
#Xilinx
#Altera
#Verilog
#VHDL
#Hardware
#Programming
#DigitalDesign