Linux From Scratch on Zynq UltraScale+ MPSoC

In this article I will try to describe the process of creating a custom Linux image on Zynq UltraScale+ MPSoCc. Each required component will be assembled separately using the appropriate utilities. The article is divided into sections that will introduce you step by step to the process of building and running the system on this platform.

In the following articles we will try to build the entire system at once using buildroot and Yocto/petalinux.

Sources for Further Study

  1. Assembly instructions Linux from scratch on Xilinx Wiki

  2. Video instructions for assembling Embedded Linux from Alexey Rostov for Advanced Engineering Radar Systems: part 1, part 2

  3. Playlist FPGA Systems dedicated to Zynq

Prerequisites

To work you will need

  1. Xilinx Vivado

  2. Xilinx Vitis (I will use Vitis Unified IDE from version 2023.2.2, however similar steps can be repeated in Vitis Classic from previous versions)

  3. A machine with Linux (you can either install Xilinx applications on Windows and run the rest in VM/dualboot/etc, or immediately work from Linux)

  4. Xilinx Device Tree Generator (https://github.com/Xilinx/device-tree-xlnx)

  5. Xilinx ATF (https://github.com/Xilinx/arm-trusted-firmware)

  6. Xilinx U-boot (https://github.com/Xilinx/u-boot-xlnx)

  7. Xilinx Linux Kernel (https://github.com/Xilinx/linux-xlnx/)

  8. Buildroot (git://git.buildroot.net/buildroot)

  9. aarch64-linux-gnu-gcc

  10. u-boot tools

Execution steps

The following components are required to run Linux on zynqmp

  1. FSBL

  2. PMU frimware

  3. ARM trusted firmware

  4. Bitstream

  5. U-Boot

  6. Devicetree

  7. Linux Kernel

  8. Linux RootFS

Creating a project in Vivado

I will be working with the Alinx AXU9EGB board.
AXU9EGB User Manual
SCH for SoM ACU9EG_SCH

When setting up the peripherals, I will be guided by the example project provided by the manufacturer on GitHub.

According to the diagram, XCZU9EG-FFVB1156-2-I is installed on the board. Accordingly, we will create a project for it. Let’s add the Zynq UltraScale block to the Block Design diagram and move on to its settings.

Memory settings

The board has 4 MT40A512M16LY-062E chips from Micron, with a total capacity of 4 GB. However, due to the capabilities of Zynq, we will use the profile from 083E, i.e. frequency 1200 MHz, Speed ​​Bin 2400P and delays 16-16-16. Let's install the MT40A256M16LY_083E preset, change the DRAM capacity to 8192 Mbit and the number of bits in the row counter to 16. The final version is shown in the figure

Required Peripherals

  • QSPI for FSBL, PMUFW, ATF and U-Boot

  • SD for Linux

  • UART is needed to interact with U-Boot

  • GEM (Ethernet) for managing Linux via ssh

  • If necessary, now turn off the AXI Master bus

  • Let's also add a Processing System Reset block to the diagram.

Final scheme

To build this circuit, you can pass the following commands to the TCL console
create_bd_cell -type ip -vlnv xilinx.com:ip:zynq_ultra_ps_e zynq_ultra_ps_e_0

apply_bd_automation -rule xilinx.com:bd_rule:zynq_ultra_ps_e -config {apply_board_preset "1" }  [get_bd_cells zynq_ultra_ps_e_0]

set_property -dict [list \
CONFIG.SUBPRESET1 {DDR4_MICRON_MT40A256M16GE_083E} \
CONFIG.PSU__DDRC__DEVICE_CAPACITY {8192 MBits} \
CONFIG.PSU__DDRC__ROW_ADDR_COUNT {16} \
CONFIG.PSU__DDRC__CWL {16} \
CONFIG.PSU__UART0__PERIPHERAL__ENABLE {1} \
CONFIG.PSU__UART0__PERIPHERAL__IO {MIO 42 .. 43} \
CONFIG.PSU__QSPI__PERIPHERAL__ENABLE {1} \
CONFIG.PSU__QSPI__PERIPHERAL__DATA_MODE {x4} \
CONFIG.PSU__QSPI__PERIPHERAL__IO {MIO 0 .. 12} \
CONFIG.PSU__QSPI__PERIPHERAL__MODE {Dual Parallel} \
CONFIG.PSU__QSPI__GRP_FBCLK__ENABLE {1} \
CONFIG.PSU__SD0__PERIPHERAL__ENABLE {1} \
CONFIG.PSU__SD0__DATA_TRANSFER_MODE {8Bit} \
CONFIG.PSU__SD0__PERIPHERAL__IO {MIO 13 .. 22} \
CONFIG.PSU__SD0__SLOT_TYPE {eMMC} \
CONFIG.PSU__SD0__RESET__ENABLE {1} \
CONFIG.PSU__SD1__PERIPHERAL__ENABLE {1} \
CONFIG.PSU__SD1__PERIPHERAL__IO {MIO 46 .. 51} \
CONFIG.PSU__SD1__GRP_CD__ENABLE {1} \
CONFIG.PSU__SD1__SLOT_TYPE {SD 2.0} \
CONFIG.PSU__TTC0__PERIPHERAL__ENABLE {1} \
CONFIG.PSU__TTC1__PERIPHERAL__ENABLE {1} \
CONFIG.PSU__TTC2__PERIPHERAL__ENABLE {1} \
CONFIG.PSU__TTC3__PERIPHERAL__ENABLE {1} \
CONFIG.PSU_BANK_0_IO_STANDARD {LVCMOS18} \
CONFIG.PSU_BANK_1_IO_STANDARD {LVCMOS18} \
CONFIG.PSU_BANK_2_IO_STANDARD {LVCMOS18} \
CONFIG.PSU__USE__M_AXI_GP0 {0} \
CONFIG.PSU__USE__M_AXI_GP2 {0} \
\
] [get_bd_cells zynq_ultra_ps_e_0]

# Create instance: proc_sys_reset_0, and set properties
set proc_sys_reset_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset:5.0 proc_sys_reset_0 ]

# Create port connections
connect_bd_net -net zynq_ultra_ps_e_0_pl_clk0 [get_bd_pins zynq_ultra_ps_e_0/pl_clk0] [get_bd_pins proc_sys_reset_0/slowest_sync_clk]
connect_bd_net -net zynq_ultra_ps_e_0_pl_resetn0 [get_bd_pins zynq_ultra_ps_e_0/pl_resetn0] [get_bd_pins proc_sys_reset_0/ext_reset_in]

validate_bd_design
save_bd_design

Let's generate bitstream (Design Sources\rightarrowCreate HDL Wrapper\rightarrowGenerate Output Products\rightarrowRun Synthesis\rightarrow Run Implementation \rightarrow Generate Bitstream) and export it to an XSA file along with the platform (File\rightarrow Export\rightarrow Export Hardware\rightarrow Include Bitstream). This completes the work in Vivado, let’s move on to Vitis.

FSBL and PMUFW

I will work in Vitis Unified IDE, instructions for Xilinx SDK can be found in this article, and for Vitis Classic – in this video.

In the Vivado folder of the project, create a directory vitis_projects and copy the exported .xsa file, this folder will be Workspace for Vitis. Launch Vitis and select the previously created folder in the Open Workspace command.

Let's create a new project using the command File->New Component->Platform. At the platform selection stage, select Hardware Design and transfer our xsa file.

At the environment selection stage, we set the following settings:

We assemble the project and Output->%project_name%->sw->boot will contain the ones we need fsbl.elf And pmufw.elf.

These files will be useful to us later.

Generating FSBL and PMU FW from XSCT

Note: if you cannot open XSCT from the console, then you need to add the folder in which you have Vitis installed to your PATH.

create a file gensoft.tcl

hsi::open_hw_design -name alynx-linux simple_linux_wrapper.xsa
set sw_pmufw [hsi::create_sw_design pmufw -app zynqmp_pmufw -proc psu_pmu_0]
common::set_property -name APP_COMPILER_FLAGS -value "-DENABLE_EM" -objects $sw_pmufw
hsi::generate_app -sw $sw_pmufw -compile -dir boot/pmufw -app zynqmp_pmufw
set sw_fsbl [hsi::create_sw_design fsbl -app zynqmp_fsbl -proc psu_cortexa53_0]
common::set_property -name APP_COMPILER_FLAGS -value "-DFSBL_NAND_EXCLUDE_VAL=1" -objects $sw_fsbl
common::set_property -name APP_COMPILER_FLAGS -value "-DFSBL_FSBL_SECURE_EXCLUDE_VAL=1" -objects $sw_fsbl
common::set_property -name APP_COMPILER_FLAGS -value "-DFSBL_FSBL_BS_EXCLUDE_VAL=1" -objects $sw_fsbl
# common::set_property -name APP_COMPILER_FLAGS -value "-DFSBL_FSBL_DEBUG=1" -objects $sw_fsbl
hsi::generate_app -sw $sw_fsbl -compile -dir boot/fsbl -app zynqmp_fsbl
hsi::close_hw_design -name alynx-linux

in the console

xsct -eval source gensoft.tcl

and copy the resulting “executable.elf” files in subfolders (the chosen path is explained below)

find . -type f -name "*.elf" | grep pmufw | xargs -i cp {} ~/alynx-linux/output/pmufw.elf 
find . -type f -name "*.elf" | grep fsbl | xargs -i cp {} ~/alynx-linux/output/fsbl.elf

Zynq MP DRAM tests

Let's check if the DDR settings are correct. Let's create a new project based on the RAM test template.

Examples \rightarrow Zynq MP DRAM tests \rightarrow Create Application Component from Template

Build, Run

To interact with the board, you will also need some kind of utility to view the COM port. I will use the extension Serial Monitor for VSCode, simply because I have it. For Windows I can recommend Terminal 1.9b

Interaction with the device via a UART terminal

Interaction with the device via a UART terminal

Device Tree

Let's clone the device-tree-xlnx repository onto our PC and move to the branch with the release corresponding to the Xilinx IDE version.

git clone https://github.com/Xilinx/device-tree-xlnx.git
cd device-tree-xlnx
git branch -r
git checkout xlnx_rel_v2023.2

Let's copy the xsa file to a separate folder. In it we will create a new tcl file with the following content

hsi::open_hw_design simple_linux_wrapper.xsa
hsi::set_repo_path /home/lazba/device-tree-xlnx/
hsi::create_sw_design device-tree -os device_tree -proc psu_cortexa53_0
hsi::generate_target -dir my_dts
hsi::close_hw_design [hsi current_hw_design]

Launch the terminal (cmd/powershell/bash/etc) in this folder. Note: if you cannot open XSCT from the console, then you need to add the folder in which you have Vitis installed to your PATH.

xsct -eval source gendt.tcl

PS devicetree can also be taken from the Vitis platform project, it is located on the path /export/platform/hw/sdt– the whole folder is needed.

Preparing for further work

Further work will take place in Linux. I will list the main packages that you will need (however, your distribution may not have enough others, be guided by errors in the console)

  • git

  • base-devel (on Debian-based this is build-essential)

  • gcc, g++

  • aarch64-linux-gnu-gcc (and others)

  • make

  • binutils

  • python, python-setuptools

  • bison

  • flex

  • swig

  • tar

  • cpio

  • zip, unzip

  • patch

  • dtc

If the repositories don’t have dtc and aarc64-gcc, don’t worry, I’ll tell you about it later.

In Home we will create a directory alynx-linux for our project. Let's create subdirectories in it

  • output – for final binaries and other files

  • devicetree

  • uboot – for building U-Boot

  • kernel – for the kernel

  • buildroot – for RootFS

mkdir -p ~/alynx-linux/output
mkdir -p ~/alynx-linux/uboot
mkdir -p ~/alynx-linux/kernel
mkdir -p ~/alynx-linux/buildroot
mkdir -p ~/alynx-linux/devicetree

Let's copy the .bit file to the hw folder, it can be in the folder with the xsa file (XSCT) or in the Output/platform/hw folder (Vitis).

In the future, when working with the Xilinx SDK tools, if you have them installed on Windows, I will assume that you copied the necessary files to the Windows machine that were previously generated in Linux.

Device Tree (continued)

Copy the contents of devictree (my_dts from xsct or dts from Vitis) to the ~/alynx-linux/devicetree folder

DTG generated several files, but for further work it is necessary to collect them all into one. Open the alynx-linux folder in the terminal

ставим опции: cd ~/alynx-linux/devicetree

gcc -I devicetree -E -nostdinc -undef -D__DTS__ -x assembler-with-cpp -o $HOME/alynx-linux/alynx-linux.dts my_dts/system-top.dts
dtc -I dts -O dtb -o $HOME/alynx-linux/output/alynx-linux.dtb $HOME/alynx-linux/output/alynx-linux.dts
If there are no DTCs in the repositories
git clone https://git.kernel.org/pub/scm/utils/dtc/dtc.git
cd dtc
make
export PATH=$PATH:$PWD

ARM Trusted Firmware

Let's display a list of tags in the remote repository, select and clone the latest stable release. Parameter --depth 1 indicates that it is necessary to download only files from the specified commit without the history of changes, other branches, etc., it will save us some amount of polymers in the future.

cd ~/
git ls-remote -t https://github.com/Xilinx/arm-trusted-firmware
git clone --depth 1 --branch xlnx_rebase_v2.8_2023.2  https://github.com/Xilinx/arm-trusted-firmware.git   

To build, you must have aarch64-linux-gnu-gcc installed. If it is not in your repositories, then you can download it from the ARM website.

Installation from tar
tar -xf %archive-name% -v

Rename the extracted directory to a simpler name, like aarch64-none-linux-gnu. Скопируйте в удобное место, например в home или /tools/.And add the directory to path.

make CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=aarch64 PLAT=zynqmp RESET_TO_BL31=1  

The file we need is in …arm-trusted-firmware/build/zynqmp/release/bl31/bl31.elf. Let's copy it to the working directory.

cp $HOME/arm-trusted-firmware/build/zynqmp/release/bl31/bl31.elf $HOME/alynx-linux/hw/bl31.elf 

PS If make issues `Error 1` after a warning about access rights, then simply ignore it, the file will still be built. If you want to remove this error, add --no-warn-rwx-segments to the linker flags in the Makefile.

U-Boot

Let's clone the u-boot-xlnx repository onto our PC and move to the commit corresponding to version 2023.2.

cd ~/
git ls-remote -t https://github.com/Xilinx/u-boot-xlnx
git clone --depth 1 --branch "xlnx_rebase_v2023.01_2023.2" https://github.com/Xilinx/u-boot-xlnx

Let's move alynx-linux.dts to the u-boot working directory

cd ~/alynx-linux
mkdir -p uboot/arch/arm/dts/
cp output/alynx-linux.dts uboot/arch/arm/dts/alynx-linux.dts

In the uboot folder we will create a build-uboot build script. The chmod+x command gives the script permission to execute.

cd uboot
touch build_uboot                                                                                                                                                                                      ✔   
chmod +x build_uboot
build-uboot
#!/bin/sh

make distclean

make -C $HOME/u-boot-xlnx distclean

make -C $HOME/u-boot-xlnx \
O=$PWD \
xilinx_zynqmp_virt_defconfig

sed -i 's/\(CONFIG_DEFAULT_DEVICE_TREE="\)[^"]*/\1'alynx-linux'/' .config
sed -i 's/\(CONFIG_OF_LIST="\)\([^"]*\)/\1'alynx-linux\ '\2/' .config
sed -i 's/\(CONFIG_SPL_OF_LIST="\)\([^"]*\)/\1'alynx-linux\ '\2/' .config

make -j3 -C $HOME/u-boot-xlnx \
O=$PWD \
CROSS_COMPILE=aarch64-none-linux-gnu- \
DEVICE_TREE="alynx-linux"

cp $HOME/alynx-linux/uboot/u-boot.elf $HOME/alynx-linux/output/u-boot.elf

This script applies the standard settings for ZynqMP, adds the (sed) dts settings for our device, after which the build starts using up to 3 parallel threads( -j3if you have more cores, you can replace, for example, with -j8 ). After assembly, the elf file we need is copied to the output folder.

Test run

Let's check the launch of U-Boot and load it into QSPI, without the system for now. Let's put ZynqMP into JTAG loading mode and run xsct from the folder with our elf files (if you have Vivado on Windows, then just copy the files there). In order to boot from JTAG, in accordance with UG1137, the PMU must be unlocked. Let's connect the UART bus of the device to the PC (to monitor the output from the device). Let's turn on the device in JTAG download mode. In folder alynx-linux/hw (where the bootloader images are contained) create a tcl script jtagload.tcl.

jtagload.tcl
#Disable Security gates to view PMU MB target
targets -set -filter {name =~ "PSU"}

#By default, JTAGsecurity gates are enabled
#This disables security gates for DAP, PLTAP and PMU.
mwr 0xffca0038 0x1ff
after 500

#Load and run PMU FW
targets -set -filter {name =~ "MicroBlaze PMU"}
dow pmufw.elf
con
after 500

#Reset A53, load and run FSBL
targets -set -filter {name =~ "Cortex-A53 #0"}
rst -processor
dow fsbl.elf
con

#Give FSBL time to run
after 5000
stop

#Other SW...
dow u-boot.elf
dow bl31.elf
con

#по желанию можно так же загрузить битстрим
#Targets -set -nocase -filter {name =~ "*PL*"}
#fpga simple-linux.bit

In the terminal we write

xsct -eval jtagload.tcl

After which we will see the following picture in the UART terminal

Now let's create a bootable boot.bin to run from QSPI/SD. In the folder with elf files, create a bif file that will store a description of the file loading sequence and options.

//arch = zynqmp; split = false; format = BIN
the_ROM_image:
{
        [bootloader, destination_cpu = a53-0]fsbl.elf
        [pmufw_image]pmufw.elf
        [destination_cpu = a53-0, exception_level = el-3, trustzone]bl31.elf
        [destination_cpu = a53-0, exception_level = el-2]u-boot.elf
}

And generate boot.bin

bootgen -image boot.bif -o boot.bin -arch zynqmp

Or in Vitis: Vitis \rightarrow Create Boot Image \rightarrow Zynq Ultrascale+ \rightarrow Import Existing BIF file \rightarrow Create Image

Then upload the program to QSPI FLASH (make sure the Zynq is in JTAG boot mode)

xsct
connect
exec program_flash -f boot.bin -flash_type qspi-x8-dual_parallel -fsbl fsbl.elf -blank_check -verify
exit

Or in Vitis: Vitis \rightarrow Program Flash

Now, if the BOOT switches are set to boot from QSPI, FSBL->PMUFW->ATF->U-Boot will automatically start. We will download Bitstream not from FSBL, but from Linux from SD.

Linux Kernel

Let's clone the repository on the branch we need

git ls-remote -h https://github.com/Xilinx/linux-xlnx
git clone --depth 1 --branch "xlnx_rebase_v6.6_LTS" https://github.com/Xilinx/linux-xlnx

In the working directory we will create a file kernel_build, which will compile the kernel in its own subfolder and copy the result to output.

kernel_build
#!/bin/sh

make -C $HOME/linux-xlnx \
O=$PWD \
ARCH=arm64 \
xilinx_zynqmp_defconfig

make -C $HOME/linux-xlnx \
O=$PWD \
ARCH=arm64 \
LOCALVERSION= \
CROSS_COMPILE=aarch64-none-linux-gnu- \
nconfig

make -C $HOME/linux-xlnx -j4 \
O=$PWD \
ARCH=arm64 \
LOCALVERSION= \
CROSS_COMPILE=aarch64-none-linux-gnu- 

cp $HOME/alynx-linux/kernel/arch/arm64/boot/Image $HOME/alynx-linux/output/Image 
cp $HOME/alynx-linux/kernel/arch/arm64/boot/Image.gz $HOME/alynx-linux/output/Image.gz

In the configurator we enable overlay filesystem support (as in the picture) – we need this to load bitstream from runtime.

Compiled files Image And Image.gz will be copied to the output folder.

RootFS

The root file system contains the files and programs necessary to run the environment.

In the buildroot folder we will create a script: br_config to launch the buildroot configurator.

br_config
#!/bin/sh

make -C /home/lazba/buildroot \
O=$PWD \
nconfig

Let's launch the configurator and go to the settings of the desired FS

Target Options

Let's change the architecture to aarch64 le, and leave the rest as default.

Toolchain

We already have aarch64 gcc installed, so in the settings we select

  • Toolchain type (External toolchain)

  • Toolchain (Arm AArch64**)

  • Toolchain origin (Pre-installed toolchain)

System configuration

  • System hostname root

  • Enable root login with password

  • Init system – systemd (you can leave busybox as default)

  • /bin/sh (default shell) – bash (you can leave busybox as default)

Target packages

Miscellaneous:

Networking applications:

  • dhcp

  • dhcpd – obtaining an IP address

  • dropbear (SSH client)

  • ifupdown – network interface management

  • iperf, iperf3 – run a network test at the end

  • iptables, iputils

  • lftp – throwing files

  • openssh (required by lftp, sort of)

Text editors and viewers

Filesystem Images

Let's create images in ext4 formats, which we will upload to a flash drive; and in cpio format, which will be needed if we decide to run the system from ramdisk, at the same time we will sign the image for U-Boot.

Host Utilities

  • host dosftools

  • host genimage

  • host mtools

Starting the build

We save the configuration by F6 and close the configurator F9.

Let's create and run the file br_build.

br_build
#!/bin/sh

make -C /home/lazba/buildroot \
O=$PWD \
BR2_JLEVEL="$(($(nproc)))"

cp $PWD/images/rootfs.cpio.uboot  $PWD/../output/rootfs.cpio.uboot
cp $PWD/images/rootfs.ext2  $PWD/../output/rootfs.ext2
cp $PWD/images/rootfs.ext4  $PWD/../output/rootfs.ext4

Upon completion of compilation of the FS image, the files we need will be copied to the output folder.

Preparing the SD card

Media markup

It is necessary to create two partitions on the card – fat32 for the kernel and dt, ext4 for rootfs. On Linux, this can be done using GUI utilities such as Gparted or KDE Partition Manager, or in the terminal, as I will show below.

We insert the card into the PC and see which file to mount it with

sudo dmesg

The output will be something like this:

usb-storage 1-6.2.1:1.0: USB Mass Storage device detected
scsi host6: usb-storage 1-6.2.1:1.0
scsi 6:0:0:0: Direct-Access     Mass     Storage Device   1.00 PQ: 0 ANSI: 0 CCS
sd 6:0:0:0: [sdb] 61945856 512-byte logical blocks: (31.7 GB/29.5 GiB)
sd 6:0:0:0: [sdb] Write Protect is off
sd 6:0:0:0: [sdb] Mode Sense: 03 00 00 00
sd 6:0:0:0: [sdb] No Caching mode page found
sd 6:0:0:0: [sdb] Assuming drive cache: write through
sdb: sdb1 sdb2
sd 6:0:0:0: [sdb] Attached SCSI removable disk

We understand that the card is in /dev/sdb. Let's clear the partition table using dd.

sudo dd if=/dev/zero of=/dev/sdb bs=1024 count=1

Let's check

sudo fdisk -l /dev/sdb

# ответ
Disk /dev/sdb: 29,54 GiB, 31716278272 bytes, 61945856 sectors
Disk model: Storage Device  
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

Information about the partitions was not displayed, so the table was erased. Let's create new partitions – 1 GB for the BOOT partition, and everything else – for the ROOTFS. You can read more about working with fdisk Here.

sudo fdisk /dev/sdb

Next, we sequentially send the following commands to fdisk

  • o – create MBR table

  • n – create a new partition

  • default partition type is primary (just Enter)

  • Leave the section number as default (just Enter)

  • We leave the first sector as default (hereinafter I will write a dash as the default value)

  • We select the last sector using the size indication, write: +1G

    The first section is created, let's move on

  • n

  • The second section is also created

  • t – change partition type

  • 1 – first section

  • c – W95 FAT32 (LBA)

  • p – see what happened

  • w – write changes to disk and close the program

PS The last part, changing the type of the first partition to W95 FAT32 (LBA) is optional – you can leave both partitions as Linux. I installed it like this to try to run completely from the SD card. For the same reason, the MBR partition table was chosen – zynqmpus+ does not support media with GPT markup for launching. At the same time, if we have u-boot on QSPI, then we can safely format everything in GPT and not worry about the type of partitions.

Initializing file systems on partitions and writing RootFS

Let's create a file system on our disk

sudo mkfs.vfat -F 32 -n BOOT /dev/sdb1 
sudo mkfs.ext4 -L ROOTFS /dev/sdb2  

Let's write the rootfs image to the second partition. After running the dd command, the label of the second partition may change to rootfs, I personally did not notice any problems, but it is believed that lowercase names may not work correctly on some systems, so it is better to fix this.

sudo dd if=/home/lazba/alynx-linux/output/rootfs.ext4 \
of=/dev/sdb2 status=progress

sudo e2label /dev/sdb2 ROOTFS 

Working with the BOOT partition.

Let's mount boot

sudo mkdir -p /mnt/boot
sudo mount /dev/sdb1 /mnt/boot

Let's transfer the kernel image there, as well as the DTB

sudo cp $HOME/alynx-linux/output/Image /mnt/boot/Image
sudo cp $HOME/alynx-linux/output/alynx-linux.dtb /mnt/boot/alynx-linux.dtb

You also need to create a file on the card extlinux/extlinux.conf the following content.

label linux
  kernel /Image
  devicetree /alynx-linux.dtb
  append console="ttyPS0,115200" root="/dev/mmcblk1p2" rw rootwait

This file stores the settings for U-Boot, which it uses to start the system.

First start

We insert the card into the board and start it up. We see the U-Boot menu, which scans all media for startup instructions, finds extlinux.conf and starts the kernel.

Once the download is complete, log in to the system using your username/password (root/root).

Let's connect the board to the router and try to connect via SSH. First, find out your IP address (still in the UART terminal)

We see that the router gave us the address 192.168.3.122. Connect via SSH from the host

Finally, let's test the connection speed between the PC and Zynqmp using iperf/iperf3. Those who are curious can additionally look at the article about that how to use iperf.

Possible problems

the kernel does not want to load rootfs from the sd card

If at startup the kernel gives you an error like this:

mmcblk1: mmc1:b369 SDABC 29.5 GiB (ro)
mmc1: Tuning failed, falling back to fixed sampling clock
mmcblk1: p1 p2
/dev/root: Can't open blockdev
VFS: Cannot open root device "/dev/mmcblk1p2" or unknown-block(179,26): error -30
Please append a correct "root=" boot option; here are the available partitions:

The problem is that the card is connected in write protect mode. This can be checked by starting from the ramdisk that we generated along with the ext image. Upload the file to the flash drive rootfs.cpio.uboot. Let's restart the board and interrupt U-Boot autoloading. In the U-Boot terminal we write:

load mmc 1:1 0x00200000 Image
load mmc 1:1 0x00100000 alynx-linux.dtb
load mmc 1:1 0x04000000 rootfs.cpio.uboot
booti 0x00200000 0x04000000 0x00100000

We boot into the system and try to mount the SD card into the system:

mkdir -p /mnt/part2
mount /dev/mmcblk1p2 /mnt/part2

We see a message from the system

The system reports RO access

The system reports RO access

To fix this, we find in the collected dts file (for us it was alynx-linux.dts) the section that is responsible for SD:

  sdhci1: mmc@ff170000 {
   u-boot,dm-pre-reloc;
   compatible = "xlnx,zynqmp-8.9a", "arasan,sdhci-8.9a";
   status = "okay";
   interrupt-parent = <&gic>;
   interrupts = <0 49 4>;
   reg = <0x0 0xff170000 0x0 0x1000>;
   clock-names = "clk_xin", "clk_ahb";
   iommus = <&smmu 0x871>;
   power-domains = <&zynqmp_firmware 40>;
   #clock-cells = <1>;
   clock-output-names = "clk_out_sd1", "clk_in_sd1";
   resets = <&zynqmp_reset 39>;
  };

And add a line there that disables WP:

  sdhci1: mmc@ff170000 {
   u-boot,dm-pre-reloc;
   compatible = "xlnx,zynqmp-8.9a", "arasan,sdhci-8.9a";
   status = "okay";
   interrupt-parent = <&gic>;
   interrupts = <0 49 4>;
   reg = <0x0 0xff170000 0x0 0x1000>;
   clock-names = "clk_xin", "clk_ahb";
   iommus = <&smmu 0x871>;
   power-domains = <&zynqmp_firmware 40>;
   #clock-cells = <1>;
   clock-output-names = "clk_out_sd1", "clk_in_sd1";
   resets = <&zynqmp_reset 39>;
   disable-wp;
  };

We rebuild dtb using dtc, upload it to a flash drive, turn on

Running bitstream from linux

Let's write a simple LED flasher for testing

module led_blink(
    input sys_clk_p,
    input sys_clk_n,
    output reg led,
    output reg led2
    );

IBUFDS #( .DIFF_TERM("FALSE") ) ibufds_inst (
    .I(sys_clk_p),
    .IB(sys_clk_n),
    .O(internal_clk)
);

reg [31:0]count;

always @(posedge internal_clk) begin
    if(count == 200000000) begin //Time is up
        count <= 0;             //Reset count register
        led <= ~led;            //Toggle led (in each second)
        led2 <= ~led2;            //Toggle led (in each second)
    end else begin
        count <= count + 1;     //Counts 200MHz clock
        end
end


endmodule

The constraints are as follows:

#pl led
set_property -dict { PACKAGE_PIN AM13   IOSTANDARD LVCMOS33 } [get_ports { led }];
set_property -dict { PACKAGE_PIN AP12   IOSTANDARD LVCMOS33 } [get_ports { led2 }];
# pl clock
set_property IOSTANDARD DIFF_SSTL12 [get_ports sys_clk_p]
set_property PACKAGE_PIN AL8 [get_ports sys_clk_p]
set_property PACKAGE_PIN AL7 [get_ports sys_clk_n]
set_property IOSTANDARD DIFF_SSTL12 [get_ports sys_clk_n]
create_clock -period 5.000 -name sys_clk_clk_p -waveform {0.000 2.500} [get_ports sys_clk_p]

We generate a bitstream, flash the FPGA, look – it blinks, it’s beautiful (but they only show it).

Let's copy the resulting bitstream to the alynx-linux/output folder with the name firmware.bit. We connect the flash drive to the PC and copy our file to /lib/firmware

sudo mkdir -p /run/media/lazba/ROOTFS/lib/firmware
sudo cp $HOME/alynx-linux/output/firmware.bit /run/media/lazba/ROOTFS/lib/firmware/firmware.bit

We start the board and in the console we register the download of the bit file from fpga-manager

echo 0 > /sys/class/fpga_manager/fpga0/flags
echo firmware.bit > /sys/class/fpga_manager/fpga0/firmware

We observe the blinking LED! 🙂

PS this is not the most correct way to run the PL part in runtime, more details about this are written in this article on Xilinx Wiki.

Let's sum it up

In this article we

  • Created a minimal design for Zynqmp US+ in Vivado

  • Run a memory test

  • Generated FSBL and PMU Firmware

  • Generated and compiled device-tree devices

  • ARM Trusted Firmware assembled

  • Assembled U-Boot

  • Compiled and launched from the board boot.bin with fsbl+pmufw+atf+uboot

  • Build Linux kernel

  • We collected the RootFS image

  • Prepared an SD card to run the OS

  • Launched several applications from the system

  • Uploaded bitstream to the PL part from the running OS

That's all, see you in the next articles!

Come to our chat: https://t.me/fpgasystems_embd

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *