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
Assembly instructions Linux from scratch on Xilinx Wiki
Video instructions for assembling Embedded Linux from Alexey Rostov for Advanced Engineering Radar Systems: part 1, part 2
Playlist FPGA Systems dedicated to Zynq
Prerequisites
To work you will need
Xilinx Vivado
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)
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)
Xilinx Device Tree Generator (https://github.com/Xilinx/device-tree-xlnx)
Xilinx ATF (https://github.com/Xilinx/arm-trusted-firmware)
Xilinx U-boot (https://github.com/Xilinx/u-boot-xlnx)
Xilinx Linux Kernel (https://github.com/Xilinx/linux-xlnx/)
Buildroot (git://git.buildroot.net/buildroot)
aarch64-linux-gnu-gcc
u-boot tools
Execution steps
The following components are required to run Linux on zynqmp
FSBL
PMU frimware
ARM trusted firmware
Bitstream
U-Boot
Devicetree
Linux Kernel
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 SourcesCreate HDL WrapperGenerate Output ProductsRun Synthesis Run Implementation Generate Bitstream) and export it to an XSA file along with the platform (File Export Export Hardware 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 Zynq MP DRAM tests 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
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( -j3
if 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 Create Boot Image Zynq Ultrascale+ Import Existing BIF file 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 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
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