这也是《FPGA实现串口升级及MultiBoot》系列中的一篇文章,作为一个专题单独出来说明。
本篇文章分为三个主题:固化、启动和MultiBoot实现。
固化分为SPI和BPI FLASH两种情况;启动分为SREC解析及加快启动模式的ELF直读;最后就是MultiBoot实现的时候应该注意什么。
固化
软核的固化和外部FLASH及应用程序大小有很大关系。小应用程序,使用BRAM即可运行,固化的时候和逻辑一起固化即可运行,这时候不管外部是什么类型FLASH,和逻辑固化一样。大应用程序,需要用到外部DDR,就需要两个启动程序,一个小的Bootloader,和逻辑一起启动,起来后从外部FLASH读取相应的应用程序放到DDR里运行,这时候就要根据不同的FLASH,设计相应的读取逻辑及应用。下面我们根据不同的FLASH类型进行相应的说明。
SPI FLASH
小应用程序
小应用程序,比如串口通信,IIC、SPI等配置通信,不占用多少STACK_SIZE和HEAP_SIZE,用内部BRAM就可运行,这时候将应用程序的LD设置为运行到BRAM即可,生成elf后就可以将其和逻辑的BIT融合在一起生成新的BIT,正常生成MCS即可烧写。
elf和bit融合成新的BIT,有两种方式,下面详细介绍:
调试阶段
调试阶段通过Vitis或者SDK一步即可生成新的BIT:
即在vitis工作目录-->应用程序目录-->_ide-->bitstream-->download.bit
该bit就是融合后的BIT,接下来可以使用Vitis/SDK或者Vivado进行下载。
稳定阶段
应用程序一般比较简单,所以调试稳定后,就可以将elf交给逻辑端,让他们继续折腾。
Vivado中在生成bit前,通过下面步骤将elf和软核关联起来,后面Vivado生成bit后,就是包含了软核程序的BIT,继续后续的固化工作即可。
大应用程序
大应用程序就会复杂很多,主要是建立软核读取外部FLASH的通道。
大的应用程序需要运行到SDRAM中,节省内部BRAM,这里的“大”并不是我们说的elf文件大,而是需要的运行空间大。
其实可以完全不需要内部BRAM也可以运行大应用程序程序,不过制作过程比较复杂,我们这次还是需要一点内部BRAM启动bootloader。
整个流程框图如下:
首先这种方式需要三个文件:1、FPGA的bit文件(需要包含FLASH的读写控制器);2、bootloader 生成的elf文件;3、大应用应用程序的ELF文件。
这里要用到比较重要的IP-axi_quad_spi(可以自己按照需求写IP),将IP按照下面设置进行设置:
主要是使能STARTUP原语。
PS:7系列的在IP内部使能STARTUP即可,对于U+系列由于FLASH的IO位于BANK0,需要在顶层再使用STARTUP3原语进行IO引出(IP内部虽然也使用了原语,但是我调试的时候只有在顶层添加STARTUP3原语才能进行通信,不清楚是BUG还是没设置好,按照官方说明使用方式也是要在顶层再添加原语)。
逻辑端添加上诉IP即可,接下来是应用程序设计。
在SDK或者Vitis中添加下面BootLoader程序:
接下来要修改第一个地方:
就是这个起始地址,其中0x44A00000就是Vivado中IP的基地址,0x80000是从FLASH读取的地址,即应用存放在FLASH中的地址,该地址要和生成MCS时候地址相匹配。
第二个需要注意的地方就是BootLoader要放到BRAM里运行,查看以下LD:
这样就可以和上面小应用程序一样,随着逻辑端一起启动。
接下来就是固化,固化也是分为两个阶段。
调试阶段
调试阶段首先将大应用程序放到FLASH相应的偏移地址上,通过以下工具可以直接烧写:
这里先选择大应用程序生成的elf,然后选择烧写的偏移地址(按照BootLoader里设置的偏移地址进行设置),然后选择对应的FLASH,最后一定要勾选Convert ELF to bootloadable SREC format and program,这是目前官方驱动程序能读取的二进制结构-SREC。
关于怎么减少启动时间我们后面会单独讨论。
注意点1:修改驱动
如果不能读取FLASH,那么需要修改驱动文件。Board support Package setting,修改xilisf,设置serial_flash_family(根据FLASH厂家修改)serial_flash_interface根据需求修改。
注意点2:读取超时
增加等待flash初始化时间:
原位置:
修改
/* * Initialize the Serial Flash Library. */ volatile int wait; do { Status = XIsf_Initialize(&Isf, &Spi, ISF_SPI_SELECT, IsfWriteBuffer); if(Status != XST_SUCCESS) { xil_printf("XIsf_initialize failed! Status=%d ", Status); for (wait=0;wait < 100000; wait++); for (wait=0;wait < 100000; wait++); } }while(Status != XST_SUCCESS );
注意点3:读取速度
某些国产FLASH的速度远低于进口FALSH,所以注意看下数据手册,控制好ext_spi_clk,同时下图还有一个分频系数:
稳定阶段
如果BootLoader能够引到启动后,可以将BootLoader的ELF添加到Vivado工程中,这样在大应用程序需要重建后无需每次都建立BootLoader工程进行调试。同时axi_quad_spi不更改(基地址不变)情况下,BootLoader都不需要修改。
BPI FLASH
小应用程序
小应用程序和SPI的一样,不随着外围FLASH不同而改变,就不赘述了。
大应用程序
大应用程序就更复杂一些,也是需要建立软核读取外部FLASH的通道。
接下来我们按照另一个思路讲解BPI FLASH大应用程序的固化。上面我们说过大应用程序的固化需要三个文件:1、FPGA的bit文件(需要包含FLASH的读写控制器);2、bootloader 生成的elf文件;3、大应用程序的ELF文件。我们按照顺序讲解每个文件生成需要的必要条件:
FPGA的bit文件
FPGA设计中需要增加对FLASH控制,对于BPI FLASH,官方推荐AXI-EMC IP,通过AXI-MEM映射外部FLASH,这个IP可以控制常见的BPI FLASH(可配置参数很低),对于不能控制的FLASH,需要自己写控制器+控制驱动。
AXI_EMC IP 概述
AXI_EMC是FPGA的一个ip core,axi 外部存储控制器,支持sram,nor flash ,psram,cellularRAM,IP核使用AXI4接口,支持32bit和64bit的数据位宽,支持以下memory type,即:
异步SRAM
同步SRAM
串行flash or并行nor flash
伪静态随机存储器
每个emc控制器支持挂接4个存储设备
来源:pg100-figure1-1
信号连接
我们这次使用的BPI FLASH为S29GL01GSXXXX,可以通过AXI-EMC 控制,具体在FPGA中的连线如下:
主要将这个IP连到MB上,外接引脚比较重要,因为IP要兼容的东西比较多,所以引出来的引脚比较多,对于不同的外设需要连接不同的引脚,对于本次设计的引脚连接如下表所示:
图中红线部分需根据表进行连接
IP界面时序参数配置
(1)第一页
默认配置即可,如果使用比较大的内存则可选总线位宽为64位,我们控制FLASH,默认32位即可:
这里注意以下,如果使用U+系列,这个IP会在下面位置有个使能STARTUP3原语的选项:
使能后和SPI FLASH一样,需要在顶层添加STARTUP原语引出BPI的DQ0~DQ3。这里说下原因,因为7系列FPGA的DQ0~DQ3是在BANK14,而U+是在BANK0上,同时官方IP可能有BUG,导致需要2次使用原语。
(2)第二页
这一页的设置是核心,需要根据FLASH数据手册进行配置:
Memory Type
支持Sync SRAM, Async SRAM, Linear Flash, Page Mode Flash, PSRAM, or Micron Flash
Data Width
Memory 数据位宽,也就是接的存储设备的数据位宽,支持8,16,32,64位位宽
Parity
可以设置为No parity,Odd Parity,or Even Parity,只有Memory Type为Sync SRAM时才能设置
Delay Mode
可以设置为Flow-Through model or Pipeline Model,只有Memory Type为Sync SRAM时才能设置
Read CE Low to Data Valid Period
根据描述,该参数在不同的memory type下含义不同,如果是flash,则和tELQV的值相等,这里以Page Mode Flash为例说明,基本上也就是片选拉低的时间,其他类型的存储设备没有验证,暂不清楚,以flash型号为S29GL01GSXXX(容量大小:128Mbyte)为例,根据芯片手册中的描述:
该芯片读时序如下:
对该芯片来说,该参数值就是tCE,即100ns = 100000ps
Read Address Valid to Data Valid Period
这里的意思为读地址在数据有效前的保持时间,不是片选保持的时间,对于S29GL01GSXX来说,就是tAVQV的值,和描述的一样,即为100ns = 100000ps,如上图所示。
Page Access Period
根据描述的含义,对于Page Mode Flash来说就是访问一页需要的时间,以S29GL01GSXX为例,也就是tPACC,为25ns,如下:
Read CE High to Data Bus HZ Period
该参数的含义是指片选(CE)从有效变为无效后,至少要保持多长时间,即从低变高后,要保持多长时间的高电平,以S29GL01GSXX为例,就是图中的tDF也就是tEHQZ,为20ns
Read OE High to Data Bus HZ Period
和上边参数一样,指OE高电平保持的时间,也就是上图中的tDF,为20ns
AXI Read Timing
根据AXI EMC IP核手册的page47页得知以上读时序图,结合“IP核参数默认值”查看
AXI Write Timing
根据AXI EMC IP核手册的page47页得知以上写时序图,结合“IP核参数默认值”查看
IP核参数默认值:
Write Cycle Period
根据描述,AXI EMC core根据该参数去保持CE为低的时间,但不是指CE保持的时间,以S29GL01GSXX为例,值等于tWC,即60ns,如下
Write Enable Minimum Pulse Width
该参数指片选(WE)保持的最小时间,以S29GL01GSXX为例,值等于tWP,即最小25ns
Write Phase Period
该参数表示两个WE之间的最小间隔,以S29GL01GSXX为例,值等于tWPH,即最小20ns
Write WE High to Data Bus LZ Period
该参数表示在一个写周期里,写使能(WE)到数据总线低阻的时间,或者写到读的恢复时间(可能理解的不正确,关于此参数配置时请谨慎),以S29GL01GSXX为例,使用的默认值,即0ps。
Write Recovery Period for Flash Memory
这个参数应该指的是数据写完后,WE还需要保持多少个时钟周期的时间,这里使用默认值,根据下图,此参数小于等于twrr,具体多少不知道,个人理解大于等于tWPH应该就可以了。
IP其他界面默认即可,地址分配界面将IP寻址空间大小设置成实际FLASH大小,如下:
编译后导出bit到SDK或者Vitis,就可以制作BootLoader.
以上参数解释,参考下面的博文:
https://blog.csdn.net/qq_33166886/article/details/112490916
这里要用到比较重要的IP-axi_quad_spi(可以自己按照需求写IP),将IP按照下面设置进行设置:
BootLoader制作
打开sdk或者Vitis,新建一个SREC的工程。这个就是传说中启动文件。
其中1是用来制作BPI FALSH的启动文件,2是用来制作SPI FLASH启动文件的。
修改FLASH的镜像地址。地址不是乱写的。是根据你的mcs文件大小,文件大小在runs/imp/ 文件夹下的 prm 文件上有告知。后面接上的。然后生成文件。注意这个地址也是你后面真正运行的elf地址写入。
也可以按照prm文件后续接上elf文件:
bootloader工程,注意选择ld看链接表设定。不要把代码和数据放在DDR上面。
然后编译工程。生成elf文件。然后按照之前的方式将BootLoader elf和vivado生成的bit合成一个新的bit。
剩下的步骤和之前SPI FLASH一样了,就不赘述了。
启动
固化完成了,接下来就是启动了。小应用程序和FPGA启动一起,所以就不说明了,重点介绍大应用程序。
大应用程序如果使用官方的BootLoader,在读取大应用到DDR中时,是读取的SREC,那么为什么要读取SREC,SREC文件有什么特点?我们接下来开始揭开面纱。
SREC文件
SREC文件是带有程序的地址信息和数据校验功能,所以在读取SREC文件时是连读连校验并且要转译,所以在读取SREC时候会很慢,好处就是可以避免很多因为文件错误导致功能异常。结构如下:
Record type | Byte count | Address | Data | Checksum |
---|
SREC 格式文件由一系列ASCII文本记录组成。这些记录从左到右具有以下结构:
a.Record type——2个字节ASCII字符,第一个字符为‘S’(ASCII 0x53),第二个字符为ASCII数字的‘0’~‘9’(ASCII 0x30 到 0x39)
b.Byte count——两个十六进制数字(“00”至“FF”),表示记录其余部分(地址 + 数据 + 校验和)后面的字节数(十六进制数字对)。此字段的最小值为 3(16 位地址字段加 1 个校验和字节时为 2),最大值为 255(0xFF)。“00”/“01”/“02”为非法值。
c.Address——大端地址——4/6/8个16进制的ASCII数字,取决于Record type的类型
d.Data——数据 ——2*n个16进制的ASCII数字(n字节数据)
e.Checksum ——2个16进制的ASCII数字,即字节数、地址和数据字段的两个十六进制数字对所表示的值之和的补码的最低有效字节。在C 编程语言中,总和通过以下方式转换为校验和:0xFF - (sum & 0xFF)
图片来源:https://en.wikipedia.org/wiki/SREC_(file_format)
ELF和SREC对比:
ELF转换成SREC
如果使用官方的BootLoader,那么SREC格式是必不可少的,而SDK或者Vitis只能生成ELF文件,下面介绍几种方式将ELF转换成SREC。
方式一:通过XSCT Console
将elf转换成SREC format,打开Xilinx—> XSCT Console。
通过cd 命令进入elf所在的目录:
输入:mb-objcopy -O srec app.elf app.srec
就会在ELF所在目录生成SREC文件。
方式二:自动转换
在应用上右击,属性,打开属性窗口。在图中位置添加命令:
mb-objcopy -O srec {ProjName}.srec
这样每次在APP编译完成后就会将ELF自动转换成SREC(在ELF目录里)。
方式二:使用命令手动设置
使用下图中两个脚本,cd到elf文件位置后,使用mb-object命令完成转换:
最后这种方式和方式一类似,好处就是可以使用Win下的脚本调用,然后生成一键脚本。
加快启动
从上面的分析可知,在读取SREC时候会进行很多无关操作,导致启动时间大大增加,尤其是大应用程序生成的ELF比较大(SREC也会比较大)的时候,这个时间肯定不能忍受的。
方式一:减少打印
在进行调试的时候,BootLoader会有读取进程通过串口打印,这一操作方便调试的时候快速进行问题定位,完成调试的时候,可以将打印去掉,注释掉下图位置语句即可:
更改读取方式
我们可以减少读取过程中的转译和校验等过程,直接引导搬运FLASH的elf文件至DDR,减少“中间商赚差价”的时间,可大大提高启动速度,相关的工程可以参考下面的链接。
https://github.com/henrikbrixandersen/elf-bootloader?_ga=2.259888963.835186866.1691390458-2003547133.1691118205
在eb-config.h文件中更改实际用到的FLASH地址以及匹配FLASH信息,主要就是修改对应FLASH的opration指令以及dummy cycle,可以根据你使用的FLASH类型查阅数据手册。
MultiBoot实现注意点
在小应用程序时候其实和纯FPGA应用一样,没什么大的区别,没什么注意的。主要在大应用程序:
大应用程序主要包含上面几个文件组成(根据自己需求可能有所不同)。考虑到升级过程中传输完成擦除FLASH后及突然断电等特殊情况,所以需要考虑CRC error(突然断电)、IDCODE error(升级包制作错误)、Watchdog timer time-out error(擦除FLASH后未升级、突然断电)三种情况。
Golden区无需变动,主要在M区的程序,在升级APP时候面对上面的情况怎么操作?因为目前的MultiBoot机制只能在FPGA逻辑层进行操作,如果APP程序错误可能触发不了回退机制。
这里提供一个思路,就是对M区APP增加CRC校验,BootLoader在读取APP的ELF或者SREC时如果CRC校验错误,那么就破坏M区的FPGA程序(FLASH),让FPGA程序启动时候触发四种错误的任何一种,就可以回退到Golden区再进行升级。
总结
总结就下面的一张图了:
关于这部分内容,大家有什么建议或者经验,欢迎大家评论区留言讨论~
全部0条评论
快来发表一下你的评论吧 !