ZYNQ | 可重构技术PCAP(1)——例程学习

Intro & Concept

这学期开始着手做毕设了,毕设里一个重要的组成部分是使用Zynq7020实现可重构技术,大概的目标是板子上电后,能从存储器(Flash、ROM啥啥啥的)中读取bit stream并将其下载到PL端。网上参考都是利用PS把bit stream下载到PL端。注意,这里是全局重构(Full configuration,我瞎起的名字),指的是把完整的bit stream文件下载到PL端,而与之对应的还有另外一种技术叫Partial Reconfiguration,本人暂时还妹学会,跳过。

为什么是从PS下载到PL端,而不能直接把bit stream下载到PL端呢?当然是没有参考我不会写啦。实际上这是对ZYNQ整个架构的理解问题。

ZYNQ一般被看成PS+PL,但是在下载程序时的PS与PL实际上地位不等,不能简单的认为是ARM芯片+FPGA芯片,然后中间用许多线连着。(虽然也可以这么说)参考UG585 Chapter 6 Figure 6-1,这里很明显可以看到在ZYNQ启动后(假设是从Flash启动),那么APU会先执行BootROM,然后是FSBL、SSBL,在非安全模式下(non-secure mode),PL端是在FSBL和SSBL期间被Programed。

The FSBLcode can clear, program and enable the PL.

image-20230116175747052

这就意味着PL是后于PS启动。继续阅读UG585 Chapter 6,这里摘一下文档。

The PL can be configured and reconfigured by PS software in secure or non-secure mode. The PCAP path is the most commonly deployed method as it does not require that the PL be pre-programmed with a bitstream. The PL can also be configured by the TAP controller on the JTAG chain in non-secure mode. Multiplexing of the datapath is done in the PL configuration module using the devc.CTRL [PCAP_MODE] and [PCAP_PR] bits.

-- from 6.1.8 PL Configuration Paths.

这段话就表明了可重构技术是可能实现的。(废话

UG585给了总共3条路,具体参考下图:

image-20230117191409551

  • JTAG Debug Path
  • PCAP Path
  • ICAP Path

三条路都可以对PL进行配置(配置指configure,烧写比特流)。但ICAP Path需要占用PL的逻辑,对于部署而言不友好,处于一个很尴尬的地位。那剩下的两条路,JTAG模式是在调试环境下使用,实际生产工作环境中,PS端从Flash读取启动镜像后,就会通过PCAP Path来Program PL。因此,在启动阶段,可以把PL看成PS的一个外设,下载数据的通路即是PCAP Path。可以看到图中两个多路选择器实际上都是devc的寄存器值,所以可重构的关键就是正确配置devc的寄存器。具体编程指南参考UG585 Chapter 6,(虽然我也妹读完呐)。

理论成立,实践开始。

Start Code

Preparation

本次实验使用的是Alinx7020开发板,连接电源线、UART、JTAG。一通操作,创建Vivado工程、创建Block Design、添加ZYNQ IP核、打开MIO的QSPI(后续要用)、UART、GPIO(optional)。添加好后继续无脑操作,Auto连线、Validate验证、Wrapper、Generate,导出硬件,打开SDK,创建空工程。

另外,要提前准备一份比特流,并通过JTAG下载到PL端进行验证,记住该bit流的效果。笔者准备了一个LED闪烁的bit stream,这里记住,从PS端烧写到PL端的不是.bit文件,而是比特流对应的.bin文件,通过Vivado自带的工具可以进行转换。

选择Generate Memory Configuration File...

image-20230204210839579

参数如下,该操作即将.bit文件转换为.bin文件。

image-20230204211044519

DevC Example

打开SDK后并创建好工程后,在板级支持包中找到system.mss,可以找到devcfg的例程,导入轮询例程,下面截取一部分代码:

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
121
int XDcfgPolledExample(XDcfg *DcfgInstPtr, u16 DeviceId)
{
int Status;
u32 IntrStsReg = 0;
u32 StatusReg;
u32 PartialCfg = 0;

XDcfg_Config *ConfigPtr;
// 常规初始化流程,查询、初始化
ConfigPtr = XDcfg_LookupConfig(DeviceId);
Status = XDcfg_CfgInitialize(DcfgInstPtr, ConfigPtr, ConfigPtr->BaseAddr);
if (Status != XST_SUCCESS)
{
return XST_FAILURE;
}
// 自检
Status = XDcfg_SelfTest(DcfgInstPtr);
if (Status != XST_SUCCESS)
{
return XST_FAILURE;
}
// 这部分是关于部分可重构的,本实验不用管
/*
* Check first time configuration or partial reconfiguration
*/
IntrStsReg = XDcfg_IntrGetStatus(DcfgInstPtr);
if (IntrStsReg & XDCFG_IXR_DMA_DONE_MASK)
{
PartialCfg = 1;
}
// 使能时钟
/*
* Enable the pcap clock.
*/
StatusReg = Xil_In32(SLCR_PCAP_CLK_CTRL);
if (!(StatusReg & SLCR_PCAP_CLK_CTRL_EN_MASK))
{
Xil_Out32(SLCR_UNLOCK, SLCR_UNLOCK_VAL);
Xil_Out32(SLCR_PCAP_CLK_CTRL, (StatusReg | SLCR_PCAP_CLK_CTRL_EN_MASK));
Xil_Out32(SLCR_UNLOCK, SLCR_LOCK_VAL);
}

/*
* Disable the level-shifters from PS to PL.
*/
if (!PartialCfg)
{
Xil_Out32(SLCR_UNLOCK, SLCR_UNLOCK_VAL);
Xil_Out32(SLCR_LVL_SHFTR_EN, 0xA);
Xil_Out32(SLCR_LOCK, SLCR_LOCK_VAL);
}

/*
* Select PCAP interface for partial reconfiguration
*/
if (PartialCfg)
{
XDcfg_EnablePCAP(DcfgInstPtr);
XDcfg_SetControlRegister(DcfgInstPtr, XDCFG_CTRL_PCAP_PR_MASK);
}

/*
* Clear the interrupt status bits
*/
XDcfg_IntrClear(DcfgInstPtr, (XDCFG_IXR_PCFG_DONE_MASK |
XDCFG_IXR_D_P_DONE_MASK |
XDCFG_IXR_DMA_DONE_MASK));

/* Check if DMA command queue is full */
StatusReg = XDcfg_ReadReg(DcfgInstPtr->Config.BaseAddr,
XDCFG_STATUS_OFFSET);
if ((StatusReg & XDCFG_STATUS_DMA_CMD_Q_F_MASK) ==
XDCFG_STATUS_DMA_CMD_Q_F_MASK)
{
return XST_FAILURE;
}

/*
* Download bitstream in non secure mode
* 该函数就是实际将bit流下载到PL端的函数
*/
XDcfg_Transfer(DcfgInstPtr, (u8 *)BIT_STREAM_LOCATION,
BIT_STREAM_SIZE_WORDS,
(u8 *)XDCFG_DMA_INVALID_ADDRESS,
0, XDCFG_NON_SECURE_PCAP_WRITE);

/* Poll IXR_DMA_DONE */
IntrStsReg = XDcfg_IntrGetStatus(DcfgInstPtr);
while ((IntrStsReg & XDCFG_IXR_DMA_DONE_MASK) !=
XDCFG_IXR_DMA_DONE_MASK)
{
IntrStsReg = XDcfg_IntrGetStatus(DcfgInstPtr);
}

if (PartialCfg)
{
/* Poll IXR_D_P_DONE */
while ((IntrStsReg & XDCFG_IXR_D_P_DONE_MASK) !=
XDCFG_IXR_D_P_DONE_MASK)
{
IntrStsReg = XDcfg_IntrGetStatus(DcfgInstPtr);
}
}
else
{
/* Poll IXR_PCFG_DONE */
while ((IntrStsReg & XDCFG_IXR_PCFG_DONE_MASK) !=
XDCFG_IXR_PCFG_DONE_MASK)
{
IntrStsReg = XDcfg_IntrGetStatus(DcfgInstPtr);
}
/*
* Enable the level-shifters from PS to PL.
*/
Xil_Out32(SLCR_UNLOCK, SLCR_UNLOCK_VAL);
Xil_Out32(SLCR_LVL_SHFTR_EN, 0xF);
Xil_Out32(SLCR_LOCK, SLCR_LOCK_VAL);
}

return XST_SUCCESS;
}

实际上这个函数非常简单,即初始化+DMA,XDcfg_Transfer()函数是实际将bit流写入到PL端的函数。函数原型如下:

1
2
3
4
5
6
7
8
u32 XDcfg_Transfer	(	
XDcfg * InstancePtr, // XDcfg 实例指针
void * SourcePtr, // bit Stream 地址
u32 SrcWordLength, // bit stream 长度÷4(Size in Words,32bit)
void * DestPtr, // 目标指针
u32 DestWordLength, // 待传输到目标地址的数据长度(Size in Words)
u32 TransferType // 传输类型,参考xdevcfg.h中宏定义
)

很明显,这里DMA传输的目的地为PL端,参考程序中只需要修改源地址和数据长度即可将任意bit流下载到PL端。例程中的宏定义BIT_STREAM_LOCATION以及BIT_STREAM_SIZE_WORDS,可以认为是一种不安全的操作,直接指定地址会造成数据的冲突。

Experiment

从DDR到PL

例程即是从DDR的某一个地址将bit文件通过DMA的方式传输到PL端。使用Xilinx SDK自带的工具将预先准备好的二进制文件

image-20230204210807644

配置示例如下:

image-20230204211528008

注意这里的写到DDR内存中的地址一定要注意,不能超出实际DDR的内存空间,地址也不能过低,由于运行PS端裸机代码运行过程中一般都会在较低的地址运行程序,因此需要避开低地址空间。(这也是这段例程最不好的地方,直接访问一个地址,既不安全也不可靠。)

注意,在将文件Restore进Memory之前,首先需要运行一下CPU,直接run,不管结果如何,目的是先让CPU启动。

然后将例程中的宏定义进行修改:

1
2
3
4
5
6
/* The 2 LSBs of the Source/Destination address when equal to 2'b01 indicate the last DMA command of an overall transfer.
* The 2 LSBs of the BIT_STREAM_LOCATION in this example is set to 2b01 indicating that this is the last DMA transfer (and the only one).*/
// 实际这里如果是00也可以,但不懂为啥
#define BIT_STREAM_LOCATION 0x30000001 /* Bitstream location */
// 笔者生成的bin文件大小为 4045564 Bytes,对应0xf6ebf in Words(32bit) ,下面这个值只要比文件值大即可,这里保持默认
#define BIT_STREAM_SIZE_WORDS 0xF6EC0 /* Size in Words (32 bit)*/

修改完后,Restore Memory,运行程序,即可观察到相应的现象。

至此从DDR3到PL的可重构实验即完成。后续介绍从Flash读取文件并将其烧写到PL端。


ZYNQ | 可重构技术PCAP(1)——例程学习
https://blog.zorogh.top/2023/02/04/ZYNQ-可重构技术PCAP/
作者
ZoroGH
发布于
2023年2月4日
更新于
2024年4月21日
许可协议