Vivado × Vitis 2019.2 固化 ZYNQ7000 系列 PL 端程序详细教程

认清形势,放弃幻想。

对于一般的 FPGA 来说,编写完 PL 端代码,直接烧录进 FLASH 或固化至 SD 卡相对较容易;但对于 ZYNQ7000 系列来说,却并没有那么简单。

ZYNQ7000 系列的启动流程由 PS 端主导,故当我们固化 PL 时,也必须引入 ARM 核控制的部分。如果你想问有没有不需要 PS 的办法,请看看文章开头的那句话。(为此,我还重装了一次 Vivado

概括地说,固化程序的流程如下:

flowchart LR
A["建立PL工程"]
B["配置ARM核IP"]
C["生成比特流\nand\n导出硬件"]
D["建立Vitis工程"]
E["生成启动文件"]
F["烧录至\nFLASH\n(或SD)"]
A-->B-->C-->D-->E-->F

环境

  • 板卡:ALINX ZYNQ 7010 开发板
  • 软件:Vivado & Vitis 2019.2
  • 系统:Windows 10 22H2

注意事项

  1. 本文教程仅适用于 2019.2 以上版本(“SDK” 已被 "Vitis" 取代,需要注意二者间的区别)。
  2. 本文教程适用于“仅有 PL 程序”时的固化,若含有 PS 端,本文可能不适合(可以适当参照本文的“参考”部分)。
  3. 正文部分的一些 IO 配置可能需要依板卡型号的不同而作出相应调整。
  4. 在烧录过程中,请特别留意教程中有关选择“启动模式”的说明。

正文

以固化一个最简单的“呼吸灯”为例。

建立 PL 端项目

(这部分和平时建工程没什么区别。)

建立工程

打开 Vivado,点击 File - Project - New

选择 RTL 类型。

创建 RTL 源码文件。

约束先跳过,不建立文件,进行器件选型。

暂时不定义端口。

编辑源码

编辑 led.v 写入代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module led(input sys_clk,
input rst_n,
output reg [3:0] led);
reg [31:0] timer_cnt;
always @(posedge sys_clk or negedge rst_n)
begin

if (!rst_n) begin
led <= 4'd0;
timer_cnt <= 32'd0;
end
else if (timer_cnt >= 32'd49_999_999)
begin
led <= ~led;
timer_cnt <= 32'd0;
end
else
begin
led <= led;
timer_cnt <= timer_cnt + 1;
end
end
endmodule

请留意这个模块所定义的输入输出端口,之后会用到。

操作如图所示,保存后,进行 RTL 分析。

修改管脚约束

如图,打开 I/O Ports

修改管脚约束如下。

保存文件并命名。

添加时序约束

如图,点击 Run Synthesis 进行综合,需要等待一段时间后综合才会完成。

综合完成后,对话框点 Cancel

随后打开时序约束助手:

修改时钟为 50Mhz(板载晶振),Skip to Finish - Finish

生成 Bit 文件并进行验证

按图示生成比特流。

随后,把 Bit 文件下发到 FPGA 上进行测试,以确保功能正常。(不详述了)。

请确保此步骤的 BitStream 验证通过后再进行下一步操作。

配置 ARM 核 IP

上文的比特流验证成功后,说明我们想要的 PL 端程序已经大体没什么问题了。接下来就是引入 PS 端的驱动,好消息是,Xilinx 已经提供好 IP 了。

添加 IP 并配置

新建一个 Block Design 并命名。

在右侧添加图示的 IP 核。

双击添加后的图块进行配置,首先是 IO 口。

对于高速的 Flash(QSPI 时钟大于40MHz),需要展开 Quad SPI Flash勾选 Feedback Clock

接下来配置 DDR,注意根据板子的手册选择兼容的内存型号。

(例如,hellofpga 的 mini ZYNQ 7000 v1.1 选型为MT41K128M16 JT125,数据位宽选择16bit)

调整 SPI 速率为 fast(可跳过)。

设置 Bank1 电压为 1.8V(视板卡情况而定——黑金的为1.8V,minifpga 的是 3.3V)。SD 卡部分如下。

配置完点 OK 即可。

随后按照图示连线把两个端口接起来,然后 Run Block Automation ,如果弹出对话框,直接点 OK

点击图示按钮初步检查 IP ,无误后继续。

注:经过实测,由于我的板子的 PL 端有独立的晶振,且我暂时用不到 PS 的功能。故 ip 核上只需要保留 DDRFIXED_IO 这两大端口即可,其余的都可以取消。操作流程如下:

  1. PS-PL Configuration - General - Enable Clock Resets 取消勾选 FCLK_RESET0_N
  2. PS-PL Configuration - AXI Non Secure Enablement - GP Master AXI Interface 取消勾选 M AXI GP0 interface
  3. Clock Configuration - PL Fabric Clock 取消勾选 FCLK_CLK0

生成 wrapper

回到项目管理器,如图,在添加的 IP 上右键,选择 Generate Output Products

点击 Generate

观察项目结构,由下图可见,操作后生成了 ps_download 的 RTL 代码。

随后,继续按照下图操作,生成 wrapper。

此时的项目结构如下图,wrapper 和 led 模块都位于顶层。

修改 wrapper

接下来需要修改 wrapper,将其变为顶层,而 led 则作为其子模块被例化。

我们的工作有:

  • 为 wrapper 这个 module 添加原先 led 模块所具有的端口

  • 在 wrapper 中例化 led

修改后的 wrapper 代码如下,需要重点关注的是我们自行添加的部分,这些在注释中做了说明。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//Copyright 1986-2019 Xilinx, Inc. All Rights Reserved.
//--------------------------------------------------------------------------------
//Tool Version: Vivado v.2019.2 (win64) Build 2708876 Wed Nov 6 21:40:23 MST 2019
//Date : xxx xxxx 4 17:14:17 2023
//Host : xxxxxx running 64-bit major release (build xxxx)
//Command : generate_target ps_download_wrapper.bd
//Design : ps_download_wrapper
//Purpose : IP block netlist
//--------------------------------------------------------------------------------
`timescale 1 ps / 1 ps

module ps_download_wrapper
(DDR_addr,
DDR_ba,
DDR_cas_n,
DDR_ck_n,
DDR_ck_p,
DDR_cke,
DDR_cs_n,
DDR_dm,
DDR_dq,
DDR_dqs_n,
DDR_dqs_p,
DDR_odt,
DDR_ras_n,
DDR_reset_n,
DDR_we_n,
FIXED_IO_ddr_vrn,
FIXED_IO_ddr_vrp,
FIXED_IO_mio,
FIXED_IO_ps_clk,
FIXED_IO_ps_porb,
FIXED_IO_ps_srstb,

// 自己添加的端口如下 这里就沿用 led 模块的端口名,以免混乱
sys_clk,
rst_n,
led
);

inout [14:0]DDR_addr;
inout [2:0]DDR_ba;
inout DDR_cas_n;
inout DDR_ck_n;
inout DDR_ck_p;
inout DDR_cke;
inout DDR_cs_n;
inout [3:0]DDR_dm;
inout [31:0]DDR_dq;
inout [3:0]DDR_dqs_n;
inout [3:0]DDR_dqs_p;
inout DDR_odt;
inout DDR_ras_n;
inout DDR_reset_n;
inout DDR_we_n;
inout FIXED_IO_ddr_vrn;
inout FIXED_IO_ddr_vrp;
inout [53:0]FIXED_IO_mio;
inout FIXED_IO_ps_clk;
inout FIXED_IO_ps_porb;
inout FIXED_IO_ps_srstb;

wire [14:0]DDR_addr;
wire [2:0]DDR_ba;
wire DDR_cas_n;
wire DDR_ck_n;
wire DDR_ck_p;
wire DDR_cke;
wire DDR_cs_n;
wire [3:0]DDR_dm;
wire [31:0]DDR_dq;
wire [3:0]DDR_dqs_n;
wire [3:0]DDR_dqs_p;
wire DDR_odt;
wire DDR_ras_n;
wire DDR_reset_n;
wire DDR_we_n;
wire FIXED_IO_ddr_vrn;
wire FIXED_IO_ddr_vrp;
wire [53:0]FIXED_IO_mio;
wire FIXED_IO_ps_clk;
wire FIXED_IO_ps_porb;
wire FIXED_IO_ps_srstb;

// 自行添加的端口定义
input sys_clk;
input rst_n;
output [3:0] led;

ps_download ps_download_i
(.DDR_addr(DDR_addr),
.DDR_ba(DDR_ba),
.DDR_cas_n(DDR_cas_n),
.DDR_ck_n(DDR_ck_n),
.DDR_ck_p(DDR_ck_p),
.DDR_cke(DDR_cke),
.DDR_cs_n(DDR_cs_n),
.DDR_dm(DDR_dm),
.DDR_dq(DDR_dq),
.DDR_dqs_n(DDR_dqs_n),
.DDR_dqs_p(DDR_dqs_p),
.DDR_odt(DDR_odt),
.DDR_ras_n(DDR_ras_n),
.DDR_reset_n(DDR_reset_n),
.DDR_we_n(DDR_we_n),
.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
.FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
.FIXED_IO_mio(FIXED_IO_mio),
.FIXED_IO_ps_clk(FIXED_IO_ps_clk),
.FIXED_IO_ps_porb(FIXED_IO_ps_porb),
.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb));

// 自行添加的 led 模块的例化
led led_inst(
.sys_clk(sys_clk),
.rst_n(rst_n),
.led(led)
);

endmodule

可以发现,代码中的 sys_clkrst_nled 就是上文中 PL 工程中 led module 的输入和输出端口。

修改后,项目最终结构如下图,只有一个 wrapper 作为顶层文件,其下包含了 PS 端的驱动以及我们的 led 模块:

注意,此时一定要确保 wrapper 文件为顶层,可以在其上 右键 - Set as Top 以确保万无一失。

笔者在使用 Vivado 2022.2 时,出现了 wrapper 不为顶层的情况,最终 VITIS 生成的固件只能在线运行,而断电后无法运行。

生成比特流 & 导出硬件

生成比特流

留意一下当前板子上资源的占用情况,然后重新生成比特流。

注意,重新生成后,资源占用情况应该和原来的差不多,否则可能有问题。

生成完毕后将比特流下载到 FPGA,验证是否正常工作。

还是那句话,请确保此步骤的 BitStream 验证通过后再进行下一步操作。

导出硬件包

验证成功后,按图示操作导出硬件包,注意勾选 Include Bitstream

如果报错,如图:

可以试着 Run Implementation 然后 Open Implemented Design,随后再执行导出操作。

导出可能较慢,需要耐心等待。

建立 PS 端项目

启动 Vitis,选取一个空目录作为 WorkSpace。然后点击 File - New -Application Project

为项目命名并选择路径,注意,新建的项目的路径应当和上述 Vivado 工程的路径区分开。

添加刚才在 Vivado 导出的 xsa 硬件文件。

导入后如图所示:

继续进行生成 boot 组件的配置,注意勾选 Generate boot components

选取 Zynq FSBL 作为模板(专用于固化下载),点击 Finish 后等待生成完成。

生成完毕后,左侧文件栏中可见有解压后的硬件包(绿色标)和 fsbl 工程(蓝色标)。

随后,在 fsbl 工程上右键 - Build Project

Tips:如果对下方绿色的硬件包右键,可以看到 Update Hardware Specification 选项,它可用于在硬件包发生变化后,进行硬件包的更新。(更新后可能还需要执行一下项目的 CleanBuild 操作)

确认编译完成没有报错后,可将程序临时下载至 FPGA 进行测试,此时的程序还是掉电即消失。

将 FPGA 的启动模式设置为 JTAG 后,上电,按图示步骤操作:

下载完成后,验证程序是否正常运行。

请确保此步骤验证通过后再进行下一步操作。

生成启动文件

确认无误后,可以按图示操作生成启动文件。

生成路径一般无需修改,我们只需要记住就行。

固化程序

“万事俱备,只欠东风。”

有了启动文件,就可以进行程序固化操作了。

有以下两种方式可供选择。

  1. 调整启动模式为 JTAG 模式后,重新上电并连接电脑。

  2. 在 fsbl 工程上右键,选择 Program Flash

    在弹出的窗口中进行如下设置,随后点击 Program 进行下载:

    暂时没搞明白选“System”和“Application”有什么区别

  3. 调整启动模式为QSPI 模式,重新上电,验证运行情况。

注意:

  • 如果显示 spi speed fallback to 100khz,可能是启动模式未设置为 JTAG,或者是 ZYNQ 核中忘记或者错误勾选 QSPI 模块。

  • 如果显示 flash 下载成功,而且通过 run as 下载也正常,但是最后运行异常,很可能是 vivado 生成 wrapper 文件出现问题。

以下内容待测试

  1. 复制生成的 BOOT.bin 至 SD 卡根目录。

  2. 调整启动模式为 SD 。

  3. 插入 SD 卡并上电。

参考

  1. 详细教程:vivado2019.2 & vitis2019.2下,zynq7000系列FPGA固化PL程序到外挂flash和SD卡

  2. Zynq> ERROR: [Xicom 50-186] Error while detecting SPI flash device - unrecognized JEDEC id bytes: 00, 00, 00