Aller au contenu

SEIML SW: Introduction to Linux on the Zedboard

Hardware setup

  • Download the lab material
  • Move it to /users/local/seiml_sw_lab


Working in /users/local/ is important as it is a local disk, which will significantly speed up each step of the lab.

First of all, we will create a vivado project to generate the hardware.

  • Create a new Vivado project
    • Project name: vivado_project
    • Location: /users/local/seiml_sw_lab
    • Project type: RTL project
    • Skip Add sources and Add constraints
    • Click on Boards and select Zedboard

New project

  • Create block design
    • Design name: system
    • Directory: local to project
  • Add IP (+ button): ZYNQ7 Processing System
  • Add 2 IP: AXI GPIO
  • Add IP: Chenillard
    • Go to IP Catalog
    • Right click and add repository: /users/local/seiml_sw_lab/resources/ip_repo_chen
    • Add chenillard in the block diagram
  • Regenerate layout
  • Click the Run Block Automation button
  • Click the Run Connection Automation button
  • Only connect S_AXI interfaces

Block design

  • Rename axi_gpio_0 to btns_gpio (In the Block Properties tab)
  • Rename axi_gpio_1 to sws_gpio (In the Block Properties tab)
  • Rerun the Connection Automation and connect the GPIO to the sws_8bits and the btns_5bits ports
  • Enable and connect gpio interruption to the ZYNQ7 Processing System
    • Double click on the btns_gpio block
    • Enable interruption
    • Double click on the ZYNQ7 Processing System block
    • Go to interrupts
    • Enable Fabric Interrupts and IRQ_F2P in PL-PS Interrupts section
    • Go back block diagram
    • Draw the connection beween ip2intc_irpt gpio output port and IRQ_F2P input port of the Zynq
  • Right click on the block diagram and Create Interface Port for leds
    • Interface name: leds_8bits
    • VLNV:
  • Connect the leds to the chenillard GPIO interface
  • Regenerate layout

Your block design should look like this:

Final block design

  • Click the Generate Block Design button (under IP integrator section in the left-most tab)
  • Create HDL Wrapper (in the Sources tab)

Final block design

  • Let Vivado manage wrapper
  • Click Add source -> Add or create constraints
  • Add the file system.xdc that can be found in /users/local/seiml_sw_lab/resources
  • Generate Bitstream
  • Export the hardware locally (File -> Export -> Export Hardware...)
  • Include the bistream
  • Tools -> Launch Vitis IDE

Software setup

We are now going to produce all the necessary software components:

  • First Stage Boot Loader
  • Boot Loader
  • Linux Kernel
  • Device Tree Blob
  • Root File System (rootfs)

All the components that are necessary for boot will be stored on the QSPI flash, with the following memory layout:

QSPI Flash memory layout

The root file system will be stored on an NFS server on the host machine. Therefore we will need to configure U-Boot to load the rootfs on the nfs. But first things first, the First Stage Boot Loader:

First Stage Boot Loarder

We will generate the FSBL using Xilinx SDK

  • Create a new Application project (File -> New -> Application project)
  • Create a new platform from hardware (select your own .xsa)
  • Click Next
  • Insert Application project name: zynq_fsbl
  • Click Next
  • Domain (nothing to change here)
  • Click Next
  • Choose Zynq FSBL template
  • Click Finish
  • Project -> Build Project

The build will create the zynq_fsbl.elf executable in the Debug folder, that will be used later. Note that the board support package has also been generated.


U-Boot will be the bootloader that is proposed by Xilinx by default. It is available online here, but we will use the version that has been packaged in the lab material folder seiml_sw_lab/u-boot-xlnx.

cd /users/local/seiml_sw_lab/u-boot-xlnx # switch to u-boot source folder
SETUP MEE_VITIS20202 # vivado tools are needed (e.g. the cross compiler)
source /users/local/seiml_sw_lab/resources/ # setup environment
make xilinx_zynq_virt_defconfig # load u-boot default configuration for Zedboard
make menuconfig # open configuration menu


For the different tools, and vivado setup, source and SETUP MEE_VITIS20202 each time you open a terminal !

As aforementioned, we want u-boot environment to be saved at 0x004D0000 on the QSPI flash.

  • In Environment, select Environment is in SPI flash, and set Environment offset to 0x4D0000
  • Exit menuconfig and save (in .config as proposed)
  • Build U-Boot
make -j4

The output product is u-boot.elf, that will be loaded in the flash. It also produces some tools for the linux kernel build, namely mkimage that can be found in u-boot-xlnx/tools

Linux Kernel

In the same way as for U-Boot, Xilinx provide the linux kernel sources adapted to their hardware targets (here).

cd ../linux-xlnx
export PATH=../u-boot-xlnx/tools:$PATH
make xilinx_zynq_defconfig
make menuconfig

In the menus, verify that NFS File System is enabled and that RootFS can be located on NFS.

make -j4  zImage # compile the kernel

The compiled images are located in arch/arm/boot/.


Now that the kernel has been built, we need some system utilities in order to get a complete operating system.

cd ../busybox-1.36.1
make menuconfig # and exit, leaving the config unchanged
make -j4
make install

Here you can see all the utilities that will be compiled, and the memory footprint. One could remove some features to lower the size of the OS. These utilities will populate our rootfs.

Device Tree

A device tree is necessary for the kernel to be aware of the devices that need to be supported. The device tree includes information about device addresses, interruptions, compatible drivers. We need to generate the sources of this device tree for the Zedboard. Xilinx provide a device tree generator (here) and it is included in the lab material. We will use the Device Tree Compiler (dtc) provided by u-boot to compile the device tree blob that is needed at boot time.

cd ../build_image
make devicetree.dtb


Flash image

In order to build the image to be written in the QSPI flash, the bootgen tool provided by Vivado will be used. First of all, let's create a folder to import all the software components that were precedently generated.

cd ../build_image

In this directory, copy the following files:

  • The FSBL (file zynq_fsbl.elf),
  • the bitstream (file system_wrapper.bit),
  • the bootloader (file u-boot.elf),
  • the linux kernel (file zImage),
  • the device tree blob (file devicetree.dtb).

A file named boot.bif must be given to bootgen in order to place the components at the right places in memory. Create a boot.bif in /users/local/seiml_sw_lab/build_image with the following content:

test : {

The offset values correspond to the location of the corresponding files on the Flash.

During boot, U-Boot will automatically load the kernel (uImage.bin) and the device tree blob (devicetree.dtb) in the Zynq RAM at the addresses that are specified (0x3000000 and 0x2a00000).

Now create the boot.bin image to be loaded into the QSPI Flash :

make boot.bin

Root FS

A NFS server is set up on your machine at /srv/nfsroot. Create a folder that will store your rootfs.

mkdir /srv/nfsroot/zednfsroot

BusyBox provides the base of this rootfs:

cp -r ../busybox-1.36.1/_install/* /srv/nfsroot/zednfsroot

Some modifications are necessary to finalize the setup

  • Create following directory and files
cd /srv/nfsroot/zednfsroot
mkdir -p dev etc/init.d mnt opt proc root sys tmp var/log var/www lib usr/lib home/user
touch etc/fstab etc/passwd etc/init.d/inittab etc/init.d/rcS etc/group
  • Add GCC libs
cp $XILINX_VITIS/gnu/aarch32/lin/gcc-arm-linux-gnueabi/cortexa9t2hf-neon-xilinx-linux-gnueabi/lib/* /srv/nfsroot/zednfsroot/lib/
cp -r $XILINX_VITIS/gnu/aarch32/lin/gcc-arm-linux-gnueabi/cortexa9t2hf-neon-xilinx-linux-gnueabi/usr/lib/* /srv/nfsroot/zednfsroot/usr/lib/
  • Edit etc/fstab
LABEL=/ /           tmpfs   defaults        0 0
none    /dev/pts    devpts  gid=5,mode=620  0 0
none    /proc       proc    defaults        0 0
none    /sys        sysfs   defaults        0 0
none    /tmp        tmpfs   defaults        0 0
  • Edit etc/passwd
  • Create and edit etc/group
  • Edit etc/init.d/inittab
# /bin/ash
# Start an askfirst shell on the serial ports

# What to do when restarting the init process

# What to do before rebooting
::shutdown:/bin/umount -a -r
  • Edit etc/init.d/rcS
echo "Starting rcS..."
echo "++ Mounting filesystem"
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp

echo "++ Setting up mdev"
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
mkdir -p /dev/pts
mkdir -p /dev/i2c
mount -t devpts devpts /dev/pts
echo "++ Starting telnet daemon"
telnetd -l /bin/sh
echo "++ Starting http daemon"
httpd -h /var/www
echo "++ Starting ftp daemon"
tcpsvd 0:21 ftpd ftpd -w /&
echo "rcS Complete"
  • Set etc/init.d/rcS permission to 755
chmod 755 etc/init.d/rcS


It's (finally) time to boot !

  • Verify that the jumpers are correctly set to boot from the flash memory:
MIO[2..6] = [0 0 0 1 0]
  • Connect all the necessary cables
    • Ethernet cable to the host computer
    • Power supply
    • USB cable for PROG (J17)
    • USB cable for UART (J14)
  • Turn on power switch button
  • Flash the board
    • Move back to the build_image directory
    • make flash
  • After the flash, and each time you want to reset the CPU, use:
    • make reset
  • Open a serial terminal with putty to communicate with the board:
    • putty -serial /dev/ttyACM0 -sercfg 115200,8,n,1,N
  • Reset the board using the PS-RST button
  • Interrupt autoboot by hitting a key before the end of the countdown

  • Change the boot environment in order to boot from memory, and use our nfs server as rootfs.

# On the zedboard, via putty
setenv bootcmd "bootm 0x3000000 - 0x2A00000" # boot from memory, giving addresses of kernel and device tree blob
setenv bootargs "root=/dev/nfs rw nfsroot=,vers=3 ip= console=ttyPS0,115200 earlyprintk" # setup bootargs to boot from nfs
saveenv # save environment in flash

The Zedboard should now boot. The last thing to do is to change the ownership of all the folders that are not mounted. From now on, all the files in the nfs won't be accessible from the host. We only let the home/user folder accessible from host. This is where you will work from the host computer to create your own scripts and source code, and compile them.

chown -R root:root /bin /dev /etc /lib /mnt /opt /root /sbin /usr /var /linuxrc

You're all set !

Hands on

One LED (MIO7) and two push buttons (MIO50 and MIO51) are directly connected to the PS. Therefore these are accessible directly using the /sys/class/gpio/ interface that drives the MIOs.

  • Compile and test a simple "Hello World" program.
    • On the host machine, cd /srv/nfsroot/zednfsroot/home/user
    • Write a hello world program
    • Compile with arm-linux-gnueabihf-gcc hello.c -o hello
    • On the zedboard (via putty) cd /home/user
    • Run the program ./hello
  • Using only shell, light up the MIO7 LED
cd /sys/class/gpio
echo 913 > export # enable MIO7 driver
echo out > gpio913/direction # configure as output
echo 1 > gpio913/value # switch on the led
echo 0 > gpio913/value # switch off the led
  • Develop a C program that displays the states of BTN8 and BTN9, using the same gpio interface.

Driver development


First of all, on the host machine, move the seiml_sw_lab/resources/driver folder into /srv/nfsroot/zednfsroot.

In order to test the proper functioning of the PL part, we will perform a quick test by writing directly into the registers of our hardware design. To do this, we will use the pseudo file /dev/mem (See man 4 mem). In order to write in this pseudo file, we have to use the mmap function (See man 2 mmap).

  • Develop a test program that activates chenillard mode 1 and sets the speed to 7.


We will now develop a specific driver for the chenillard. This driver will allow access to the PL for the LED chenillard (and only the LEDs at first). Information about Linux driver development is covered in detail in the book Linux Device Drivers (LDD3) here. All questions below refer to this document.

  • A Makefile is provided to simplify compilation and installation. Compile, install, and test the module. Loading and unloading the module is done via the shell scripts and Testing will be done with the echo & cat commands first. For more information about the Linux driver structure, you can study chapter 1 and 2 of the LDD.
  • Complete the chenillard initialization function (chenillard_init): memory reservation, ioremap. The TBD (To Be Done) fields are to be replaced by code. Cf chapter 9 of the LDD notably the paragraph "I/O Memory Allocation and Mapping". Also complete the chenillard_remove function. Check that the memory area corresponding to the PL is reserved and freed in /proc/iomem.
  • Complete the chenillard_i_read and chenillard_i_write functions: Reading /dev/chenillard should return the current chenillard mode and speed. Use the app.c test program to check. Writing to the pseudo file should set the mode and speed. Modify app.c to test writing. See paragraph "read and write" of chapter 3 of the LDD.


The development of the driver for the chenillard part is now finished, we will now focus on the push buttons. The operation of the driver for the button management is slightly different because it implements a hardware interrupt.

  • Based on the structure of the chase maintenance code, develop the necessary functions for button management (axi_btns_of_probe, btns_i_open ...).
  • Write the interrupt function irqreturn_t axi_btns_irq_handler(). At first, this function will only perform a printk. Please refer to chapter 10 of the LDD. Modify the btns_init function to take into account this new interrupt handler. Test it. Verify that the interrupt is listed in /proc/interrupts.
  • We choose to take the interrupt into account using a blocking read in the driver. An interruption that occurs should unblock the reading. See ''Blocking I/O`` in chapter 6 in LDD.

Important notes: - As this interrupt is based on the AXI GPIOs, it must be activated at the BUS level. Refer to the GPIO interrupt registers in the documentation (see registers IER, GIE and ISR). - Only IRQ_TYPE_EDGE_RISING is supported by the hardware.


In the previous exercise, we noted that the integration of an external event (e.g. button push) into a program is quite constraining. It is possible to use threads to manage the blocking read.

  • Develop an application to change the chenillard mode and speed using both push buttons and keyboard. To do this, use two threads. The first one will be in charge of reading the push buttons, the second one will be in charge of the keyboard. The use of threads under Linux is described on the wikipedia page: POSIX Threads


Finally, you will add an AXI hardware timer in the PL part of your system, as you did in the HW labs. Then recompile the necessary software elements. Develop a driver like you did for the chenillard and the buttons, by setting the timer to interrupt every second. To check the added functionality, compare the measured time with two methods, on the one hand with the gettimeofday() system call, on the other hand with your timer. This measurement will be done on an interval of ten seconds for example.

Dernière mise à jour: March 19, 2024