ZYNQ | 可重构技术PCAP(2)——Flash启动

Intro

上一篇博客ZYNQ|可重构技术PCAP--例程学习中实现了从DDR到PL端的过程。然而在实际生产中,不能通过JTAG下载elf文件到PS再进行可重构,这就要求产品需要用非易失存储器来保存代码。ZYNQ官方文档指定的非易失存储器有4种:QSPI、SD卡、NAND、NOR,具体参考UG585第六章。笔者使用的是Alinx7020开发板,其PS端的MIO[2:6]连接着一片Flash,而对该Flash的读写则可通过ZYNQ内置QSPI控制器来读写。

生产环境下的效果应该是:

  1. 选择QSPI模式进行启动
  2. 上电,开发板加载启动镜像
  3. PS端通过QSPI控制器读取Flash中的比特流文件并烧录到PL端
  4. PL端开始工作

为了简化开发流程,这里省略掉前2步,使用JTAG模式将PS端代码下载到开发板,然后执行步骤3和4。

Preparation

首先要了解FLash存储器中包含的即是我们要写到PL端的完整数据,而FPGA需要的是.bit文件而非.bin文件,这一点在上篇博客中也提到过,具体两者的区别可以参考Xilinx官方的一篇回答How to use PCAP to config the PL in zynq,该帖子中ckohn部分可重构技术文档的作者,他提到

A .bin file is the binary representation of the configuration bitstream. The .bit contains the configuration data plus additional data in the bit file header.

具体转换方法参考上一篇博客

烧写Flash

打开SDK,将准备好的.bin文件烧写进Flash中。

烧写Flash

这里的Offset即为文件在Flash中的起始地址,默认用0即可。

通过QSPI读取Flash

导入QSPI轮询例程,如果没有,需要返回到Vivado,在Block Design中将MIO部分的QSPI的打开。

将QSPI例程中的ReadBuffer修改至合适大小,注意,这里可以选择直接在全局声明数组,或者在main中调用malloc函数。如果选择后者,那么需要在lscript.ld中将堆的大小修改至大于文件大小。

简化后的函数如下

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
int QspiFlashPolled(XQspiPs *QspiInstPtr, u16 QspiDeviceId,u32 Addr,u32 DataSize)
{
int Status;
// 经典套路,查询并初始化
XQspiPs_Config * QspiConfig = XQspiPs_LookupConfig(QSPI_DEVICE_ID);
XQspiPs_CfgInitialize(QspiInstPtr, QspiConfig,QspiConfig->BaseAddress);
// 自检
Status = XQspiPs_SelfTest(QspiInstPtr);
if(Status!=XST_SUCCESS)
{
xil_printf("Self Test Failed\r\n");
return;
}
// 例程自带选项不用改
XQspiPs_SetOptions(QspiInstPtr,
XQSPIPS_MANUAL_START_OPTION |
XQSPIPS_FORCE_SSELECT_OPTION |
XQSPIPS_HOLD_B_DRIVE_OPTION);
XQspiPs_SetClkPrescaler(QspiInstPtr, XQSPIPS_CLK_PRESCALE_8);
XQspiPs_SetSlaveSelect(QspiInstPtr);
// 使能QSPI
FlashQuadEnable(QspiInstPtr);
// Flash读函数,将数据读取到ReadBuffer中,ReadBuffer前4字节为Overhead
FlashRead(QspiInstPtr, Addr, DataSize, READ_CMD);
xil_printf("End reading flash\r\n");
return XST_SUCCESS;
}

这里就能将Flash中文件读取进来。即u8 * binFile = ReadBuffer + 4;

为了检验是否读取完整,可以通过XSCT窗口命令来将数据保存到电脑中进行验证。

这里提前用串口将buf的地址打印出来,我这里是0x1104CC,文件长度是4045564,除以4得到1011391。

1
2
3
xsct% cd C:/Dev/temp
mrd -bin -file flashRead.bin 0x1104CC 1011391
# mrd -bin -file name.bin startAddr SizeInWords

这样就能把文件保存到电脑上了,至于如何验证该文件是否与源文件一致,可以使用SHA-1校验,校验码一致则表示两个文件相同,这里不再过多赘述。

这里是个坑,当你发现两个文件校验通过时,完整的文件到了内存中,也就是完成了从Flash到DDR的过程。

实际上,实验做到这里,应该说接近完成了,总共分两步:

  1. 从Flash读取文件
  2. 将文件烧写到PL端

第一步已经完成,第二步参考上一篇博客,只是需要将参数进行修改:

1
2
3
4
5
// 例程调用方式
XDcfgPolledExample(&DcfgInstance, DCFG_DEVICE_ID,BIT_STREAM_LOCATION,BIT_STREAM_SIZE_WORDS);
// 修改后,注意这里的bitLoc = ReadBuffer+4
// 而 bitLoc+1是根据API的注释来的,表明这是最后一次DMA传输
XDcfgPolledExample(&DcfgInstance, DCFG_DEVICE_ID,(u8*)(bitLoc+1),(4045564)>>2);

至此,从理论上讲,第二步也应该完成了。但实际上程序运行后板子并没有相应的结果显示,Done指示灯也不会亮。这个Bug困扰了我好几天。

坑 & 填坑

与其说是坑,不如说自己对体系理解不到位。一开始,我不断Debug,总结下来就是:仅仅改变bit文件的地址,就会有不同的效果,官方的例程就是能成功,而我的就是不行。后来尝试了将flash读出来的文件填写到高地址(即在高地址声明指针),可是这种操作仍然不起作用。

后来在师兄的帮助下,填了坑。

XDcfgPolledExample()函数中实际起到关键作用的是XDcfg_Transfer()函数,参考API文档:

This function starts the DMA transfer.

This function only starts the operation and returns before the operation may be completed. If the interrupt is enabled, an interrupt will be generated when the operation is completed, otherwise it is necessary to poll the Status register to determine when it is completed. It is the responsibility of the caller to determine when the operation is completed by handling the generated interrupt or polling the Status Register.

Note

It is the responsibility of the caller to ensure that the cache is flushed and invalidated both before the DMA operation is started and after the DMA operation completes if the memory pointed to is cached. The caller must also ensure that the pointers contain physical address rather than a virtual address if address translation is being used.

The 2 LSBs of the SourcePtr (Source)/ DestPtr (Destination) address when equal to 2'b01 indicates the last DMA command of an overall transfer.

1
2
3
4
5
6
7
8
u32 XDcfg_Transfer	(
XDcfg * InstancePtr,
void * SourcePtr,
u32 SrcWordLength,
void * DestPtr,
u32 DestWordLength,
u32 TransferType
)

文档提到这里是该函数本质上是DMA传输,DMA是对DDR3这个器件直接进行数据操作,因此,如果数据不在DDR中,自然也不会达到想要的效果。

文档也提到了,DMA传输前后均要检查cache,而cache,正是这个坑,具体而言就是从Flash中读出来的文件还在Cache中而没有被更新到DDR中,因此导致了传输的文件和理论上的不一致。

填坑

解决办法很简单,既然问题出现在cache,应对方法有两种:

  1. 关掉cache
  2. 在DMA前进行Cache的Flush操作。

为什么会出现这个坑

这个问题实际上叫“Cache一致性问题”[1]

实验过程中,笔者通过XSCT窗口mrd命令下载了文件,进行了一次文件校验,校验结果显示从Flash中读取的文件没有错,的确如此,但是要注意,这里的XSCT窗口是直接与CPU打交道,因此当CPU再读取某一段数据时,如果数据存在于Cache中则会优先读取Cache中的数据,而非DDR中的数据。因此mrd命令是站在CPU的角度来观察整个存储体系

而DMA控制器作为DDR的另一个主控,直接与DDR进行数据交换,其对应的不是CPU视角下的整个存储体系。明白了这一点问题就容易多了。

至此可重构实验基本完成。

Reference


ZYNQ | 可重构技术PCAP(2)——Flash启动
https://blog.zorogh.top/2023/02/05/ZYNQ-可重构技术PCAP-Flash启动/
作者
ZoroGH
发布于
2023年2月5日
更新于
2024年4月21日
许可协议