课题需要可能要在 ZYNQ 上多次部署 Linux 并测试,普通的脚本安装方式太过繁琐,Xilinx 的 Petalinux 工具简化了很多流程。这里记录了一些主要步骤,由于实验室 Vivado 版本,所以选择的 Petalinux 版本也不是最新的
why petalinux?
比分步编译更便捷的配置和编译源码
-
优势:petalinux 读取输入硬件配置,并根据硬件来自动的配置编译 u-boot,kernel,devicetree.
-
缺点:软件对系统版本,依赖版本要求比较高,配置相对麻烦。如果不按照规定好的顺序执行命令会遇上较多未知 BUG
-
目的:简化编译的过程,缩短时间并生成与硬件对应的正确的设备树文件
petalinux 的安装
petalinux 对操作系统版本和依赖版本要求很高,只能在官方文档指定的发行版安装。这里以 petalinux 2017.04 为例
- OS: ubuntu16.04(docker x86-64)
- Dependencies:
1
2
3
4
5
6
7
|
sudo dpkg --add-architecture i386
sudo apt install libssl-dev flex bison chrpath socat autoconf libtool texinfo gcc-multilib libsdl1.2-dev libglib2.0-dev screen pax net-tools wget diffstat xterm gawk xvfb git make libncurse5-dev tftpd zlib1g libssl-dev gnupg tar unzip build-essential libtool-bin dialog cpio lsb-release zlib1g:i386 zlib1g-dev:i386 locales openjdk-8-jdk
sudo dpkg-reconfigure locales
export LANG=en_US.UTF-8
|
由于 Petalinux 依赖发行版版本,推荐采用 Docker 环境安装。请查看 Petalinux 2017.04 Docker 环境
petalinux 的使用
- 创建一个 ZYNQ 的工程模板:
1
|
petalinux-create --type project --template zynq --name petalinux
|
- 读取分析硬件所使用的开发版型号来配置:
1
|
petalinux-config --get-hw-description /mnt/linux_base.sdk
|
- 配置内核和根文件系统:
1
2
|
petalinux-config -c kernel
petalinux-config -c rootfs
|
- 开始编译:
- 将编译好的工程打包输出:
1
|
petalinux-package --boot --fsbl ./images/linux/zynq_fsbl.elf --fpga --u-boot --force
|
- 使用 qemu 虚拟化平台对产生的 BootLoader 和 linux 内核进行测试(可选)
1
|
petalinux-boot --qemu --prebuilt 3
|
- 将输出的文件移动到开发板启动,或者使用 tftp 方式远程启动:
image.ub: linux 的内核镜像,并且打包了设备树文件 plnx_arm-system.dtb, 在内存中运行的文件系统 ramdisk.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
# image.ub
images {
kernel@0 {
description = "Linux Kernel";
data = /incbin/("zImage");
type = "kernel";
arch = "arm";
os = "linux";
compression = "none";
load = <0x8000>;
entry = <0x8000>;
hash@1 {
algo = "sha1";
};
};
fdt@0 {
description = "Flattened Device Tree blob";
data = /incbin/("plnx_arm-system.dtb");
type = "flat_dt";
arch = "arm";
compression = "none";
hash@1 {
algo = "sha1";
};
};
ramdisk@0 {
description = "ramdisk";
data = /incbin/("petalinux-user-image-plnx_arm.cpio.gz");
type = "ramdisk";
arch = "arm";
os = "linux";
compression = "none";
hash@1 {
algo = "sha1";
};
};
};
|
petalinux 产生设备树的分析
使用 petalinux 的优势:自动分析硬件并产生设备树,也可以添加需要的部分并手动编译。
1
|
$(project)/components/plnx_workspace/device-tree/device-tree-generation/
|
- 设备树主要分两个部分:
- ARM CPU 相关的设备包括处理器内存,系统总线等等,在 zynq-7000.dtsi 中。(dtsi:设备树中描述 SOC 级的信息,一般不需要修改;dts: 设备树的源文件,修改设备树的主要对象;dtb: 由 dts 文件编译生成的二进制文件,由内核在启动时候读取并解析)
- petalinux 根据硬件的配置来生成 pl.dtsi 文件,文件内包括在根节点下的 FPGA 部分的设备树。
- pl.dtsi 和 zynq-7000.dtsi 包含在 system-top.dts 内,手动添加的设备树也包含在内。
- plnx_arm-system.dts 是处理了包含关系后的文件,编译生成 plnx_arm-system.dtb.
1
2
3
|
pl.dtsi ---------|
pcw.dtsi ---------|----> system-top.dts ----> plnx_arm-system.dts -----(dtc)----> plnx_arm-system.dtb
zynq-7000.dtsi ---|
|
实验:产生 GPIO 设备树文件,使用 Linux 内的 gpio 驱动程序,用文件 IO 方式驱动 led
系统描述:PS 和 PL 各有一个 led,通过 petalinux 产生一个完整 linux 系统,FPGA 端烧写一个 GPIO 控制器,输出 1 位信号到 R19 引脚,FPGA 的 R19 引脚连接了 pl 侧的 led 灯:
1
2
|
set_property PACKAGE_PIN R19 [get_ports {gpio_out}] # R19引脚连接板上的led灯
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_out}]
|
linux 内核包含了 gpio 的驱动,可以根据设备树信息来自动检测硬件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
$ cd /sys/class/gpio && ls
export gpiochip906 gpiochip504 unexport
# 访问/sys/class/gpio/目录,gpio906和gpio504分别是PS端和PL端的gpio控制器, export是内核提供的文件用于导出gpio的操作接口。
$ echo 906 > export
$ echo 504 > export
# 向export文件写入GPIO编号,就可以获得这个GPIO的操作接口。
$ ls
export gpio906 gpiochip906 gpio504 gpiochip504 unexport
# 新产生了gpio906和gpio504目录,目录中就是操作接口。
$ cd gpio906 && ls
active_low direction power uevent device edge subsystem value
# direction控制gpio的方向,value为gpio的输入输出值。
$ echo out > direction
$ echo 1 > value # led灯灭
$ echo 0 > value # led灯亮
# 通过一般的IO操作value这个文件就可以控制灯的亮灭。
|
产生的 GPIO 设备树部分(FPGA 侧):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
axi_gpio_0: gpio@41200000 {
#gpio-cells = <2>;
compatible = "xlnx,xps-gpio-1.00.a";
gpio-controller ;
reg = <0x41200000 0x10000>;
xlnx,all-inputs = <0x0>;
xlnx,all-inputs-2 = <0x0>;
xlnx,all-outputs = <0x1>;
xlnx,all-outputs-2 = <0x0>;
xlnx,dout-default = <0x00000000>;
xlnx,dout-default-2 = <0x00000000>;
xlnx,gpio-width = <0x1>;
xlnx,gpio2-width = <0x20>;
xlnx,interrupt-present = <0x0>;
xlnx,is-dual = <0x0>;
xlnx,tri-default = <0xFFFFFFFF>;
xlnx,tri-default-2 = <0xFFFFFFFF>;
};
|
实验:直接将 FPGA 寄存器信号输出到 R19 引脚,通过 AXI-Lite 总线读写寄存器来控制 led
定义一个 32-bits 寄存器来接受总线信号,写入寄存器的值取一位输出到 led 灯
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
# 定义一个32位寄存器
reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg0;
# 将寄存器的第0位连接到输出信号
assign test_out = slv_reg0[0];
# 将总线模块打包,在test中调用AXI总线,添加一个输出信号test_out
myip_v1_0_S00_AXI # (
.C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
.C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH)
) myip_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),
.test_out(test_out)
);
# 修改wrapper添加输出信号
output wire test_out
# 绑定输出信号到led灯相连的引脚
set_property PACKAGE_PIN R19 [get_ports {test_out}]
set_property IOSTANDARD LVCMOS33 [get_ports {test_out}]
|
petalinux 根据系统硬件设计添加了 AXI-Lite 总线对应的设备树部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# amba-axi总线的设备树部分
/ {
amba_pl: amba_pl {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges ;
myip_v1_0_0: myip_v1_0@43c00000 { # 寄存器的物理地址是0x43c00000
compatible = "xlnx,myip-v1-0-1.0";
reg = <0x43c00000 0x10000>;
xlnx,s00-axi-addr-width = <0x4>;
xlnx,s00-axi-data-width = <0x20>;
};
};
};
|
寄存器的物理地址是 0x43c00000,对这个地址的第一个字节的 0 位写值就可以控制 led 灯的亮灭
1
2
|
$ busybox devmem 0x43c00000 8 0x01 # 灯灭
$ busybox devmem 0x43c00000 8 0x00 # 灯亮
|
Q: 最小的寄存器组: 4 个(0x43c00000-0x43c0000f), 从 0x40000000-0x4fffffff 全部被映射到了最初的 16 个字节。
实际上被映射的物理内存区域有 1G,从地址 0x40000000-0x4fffffff
测试:从 Linux 端读写寄存器,寄存器连接一个宽度 16bit,深度 256 的 Block RAM,通过读写 3 个寄存器来实现对 Block RAM 的指定地址的读写。
由一个 TOP 模块,来例化了一个 AXI-Lite 总线接口,这个总线接口定义了 60 个寄存器并且引出。还有一个 BlockRAM 模块,将 AXI 总线定义的三个寄存器输入到 RAM 的控制接口里,然后通过对总线读写来控制 RAM。
AXI 总线模块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
AXI_Lite axi_lite
(.register00(AXI_Lite_register00),
.register01(AXI_Lite_register01),
.register02(AXI_Lite_register02),
.register03(AXI_Lite_register03),
.register04(AXI_Lite_register04),
.register05(AXI_Lite_register05),
.register06(AXI_Lite_register06),
.register07(AXI_Lite_register07),
.register08(AXI_Lite_register08),
.register09(AXI_Lite_register09),
.register10(AXI_Lite_register10),
......
|
对 BlockRAM 的接口定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
bram_wrapper mappingRAM
(
# 写地址:0x000-0x100(深度256)
.BRAM_PORTA_0_addr(AXI_Lite_register01[7:0]),
# 写时钟:FCLK_CLK0
.BRAM_PORTA_0_clk(s00_axi_aclk_0_1),
# 写数据:16bit数据
.BRAM_PORTA_0_din(AXI_Lite_register02[15:0]),
# PORTA使能
.BRAM_PORTA_0_en(1'b1),
# PORTA写使能
.BRAM_PORTA_0_we(1'b1),
# PORTB读地址
.BRAM_PORTB_0_addr(AXI_Lite_register03[7:0]),
# PORTB读时钟
.BRAM_PORTB_0_clk(s00_axi_aclk_0_1),
# PORTB读数据
.BRAM_PORTB_0_dout(w_ramout[15:0]),
# PORTB读使能
.BRAM_PORTB_0_en(1'b1)
);
|
ramout_0 -> led(R19)
1
2
3
4
5
6
7
8
9
10
11
12
|
# register
———————————————————— ————————————-
| register01[7:0] | ————> 写地址,输入到PORTA addr ——————> | |
————————————————————
| register02[15:0] | ————> 写数据,输入到PORTA din ——————> | 256 x 16 |
————————————————————
| register03[7:0] | ————> 读地址,输入到PORTB addr ——————> | |
———————————————————— ————————————-
|
ramout[0] —————————————— |
led <———————————————————| ramout[15:0] |—————————
——————————————
|
测试过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# 初始状态:ram内全部清零,ramout[0] 输出为0,"led灯亮"
# 给RAM的写地址端口写入0x43c00004
$ busybox devmem 0x43c00004 8 0x55
# 给RAM的写数据端口写入16位数据0x0001
$ busybox devmem 0x43c00008 16 0x0001
此时RAM的0x55地址写入了数据 0000 0000 0000 0001
# 给0x43c0000c地址写入0x55,表示给RAM的读地址端口写0x55
$ busybox devmem 0x43c0000c 8 0x55
此时ramout的第0位由0变成了1,"led灯灭"
# 把0x55的数据重新写为0
$ busybox devmem 0x43c00008 16 0x0000
"led灯亮"
# 或者改变读地址的值
$ busybox devmem 0x43c0000c 8 0x56
"led灯亮"
|
上述实验证明数据写入 RAM 成功