本文转载自:巨大八爪鱼的博客
FPGA的型号为XC7A35TFGG484-2,开发板用的是米联客的。主程序hello_world运行于外部DDR3内存,SREC SPI Bootloader运行于FPGA片内BRAM。
用此文所述的方法固化程序:https://blog.csdn.net/zhengshuo5444/article/details/107357806
download.bit生成了,但是下载下去总是运行不了。就连bootloader本身都运行不了,串口也没有任何输出。
首先怀疑的是Vivado Block Design里面AXI Quad SPI的配置问题。但是和开发板的例程对比了一下,配置完全一样。实际上后来发现这里根本没有问题。勾选了STARTUP Primitive后,IP核虽然缺少QSPI_CLK引脚,但是并不影响通信。在原理图里面,FLASH_CLK接的是L12,是配置FPGA用的专用引脚,不能作为普通I/O口使用,在Package Pin的下拉列表里面也无法选择。所以,时钟引脚是不需要接的,只需要连接4个数据引脚和1个NSS片选引脚。
于是,在Vitis里面Debug一下bootloader工程,串口只输出了一个SREC SPI Bootloader。单步调试发现,是XIsf_Initialize出错了,返回了1,程序直接退出了main函数。再继续分析,发现程序走的是Atmel的分支,没有走Micron的分支,打开xparameters.h一看,XPAR_XISF_FLASH_FAMILY的值为1,说明设置的serial_flash_family根本就没有生效!
修改完serial_flash_family后,必须要重新Build一下platform工程,xparameters.h里面的XPAR_XISF_FLASH_FAMILY才会更新。
第二个地方就是,要点开Generate linker script看一下,bootloader是不是已配置为在BRAM里面运行,主程序是否是在DDR内存里面运行。如果不是,就要手动改过来。
第三个地方找了很久才发现,Program FPGA对话框里面默认的BRAM内容选的是bootloop,和那篇文章的差别就在这里!这里应该选择bootloader.elf才对!选择好了之后,点Program按钮合并Vivado的bit文件,以及Vitis bootloader工程的elf文件,生成download.bit。
下载download.bit文件到SPI Flash的0地址处:
下载主程序的elf文件到SPI Flash的非零地址处,注意勾选Convert ELF to SREC的复选框:
开发板断电,再上电启动后,串口终于有输出了,Bootloader运行起来了,但是,却只输出了一个SREC SPI Bootloader。
在bootloader程序的XIsf_Initialize函数里面加xil_printf打印,编译bootloader程序并运行:
(注意打开了XIsf_Initialize函数所在的文件后,窗口左边会自动选中platform工程,直接点build按钮编译的是platform工程。所以要先在左边选中bootloader工程,再点build编译)
/*
* Check for Intel/STM/Winbond/Spansion Serial Flash.
*/
Status = IntelStmFlashInitialize(InstancePtr, ReadBuf);
xil_printf("ReadBuf: ");
dump_data(ReadBuf, sizeof(ReadBuf));
xil_printf("IntelStmFlashInitialize=%d\r\n", Status);
发现XIsf_Initialize里面虽然正确进入了Micron的分支,但是IntelStmFlashInitialize函数执行失败了,返回了Status=1,然后main函数就返回XST_FAILURE退出了。将ReadBuf的内容用自己编写的dump_data函数打印出来,会发现内容不对,根本就没有读到Flash的数据。
而此时,按下开发板上的复位按键(普通I/O口做的复位引脚,连接到verilog里面所有模块的复位信号),就能加载成功,并成功启动程序。
再次断电,上电启动后执行IntelStmFlashInitialize函数失败,返回值还是Status=1
按下开发板上的自定义的复位按键,又能加载成功,并成功启动程序。
……
如果在程序启动成功正常运行的情况下,按下复位键,程序就会停止运行,bootloader没有运行,串口没有任何输出。
断电,再通电,bootload又能运行了。
推测:通电时,BRAM的初始值就是bootloader的程序内容,bootloader程序能运行,只是QSPI初始化失败。这个时候按下复位键,重新初始化QSPI成功了,就能启动程序。
程序启动后,BRAM的内容被覆盖掉了,bootloader程序没了。由于复位键是普通I/O口,按下后模块复位了,但BRAM内容无法恢复,bootloader当然就无法运行了。
上电后一开始IntelStmFlashInitialize函数执行失败,可能是因为通电时间不够,Flash器件还没有准备好。
我们可以改一下bootloader里面main函数的代码,把退出main函数的代码去掉,加上do-while循环,初始化失败后延时一段时间再重新初始化:
(延时用的usleep函数所在的头文件为
/*
* Initialize the Serial Flash Library.
*/
do
{
Status = XIsf_Initialize(&Isf, &Spi, ISF_SPI_SELECT, IsfWriteBuffer);
if (Status != XST_SUCCESS)
{
xil_printf("XIsf_Initialize failed! Status=%d\r\n", Status);
usleep(100000); // 延时100ms
}
} while (Status != XST_SUCCESS);
xil_printf("XIsf_Initialize OK!\r\n");
改好之后,重新编译程序,然后固化到开发板上。开发板断电,再开,就可以启动成功了,不用再按复位键了。
问题终于解决了。
2021年4月19日补充:
1. 程序本来是可以正常运行的,修改到DDR3内存里面后,就运行不了了,不断重启,才执行两句话就重启。
解决方案:Generate linker script时,把Stack Size改大(默认是1KB)。
2. 串口打印了Executing program starting at address: 00000000,但是却无法执行程序!串口没有任何输出!
解决方案一:Block Design里面,Microblaze启用Peripheral AXI Instruction Interface
解决方案二:Block Design里面,Microblaze启用Instruction Cache
实际上,Microblaze不需要外部DDR3内存也能独立运行,用BRAM作运行内存,此时Microblaze不能开启Instruction和Data Cache。也不需要外部复位引脚,用Clock Wizard输出的locked信号做Microblaze的复位信号,当locked=0时复位信号有效,属于低电平复位。Clock Wizard本身则可以在IP核配置窗口里面把reset信号去掉。
这样一来,Vitis编译出来的elf文件可以在Program FPGA对话框里面直接和Vivado的bit文件整合,生成download.bit,然后在Program Flash Memory中把download.bit文件烧到SPI Flash的0地址处,就可以上电开机运行了。不需要SREC SPI Bootloader。
在这种情况下,程序代码和程序变量都是保存在BRAM里面的。