关键词:FPGA程序烧录;ARM;Linux操作系统;PCIe总线;SPIFlash中图分类号:TP316.8;TN401 文献标识码:A 文章编号:2096-4706(2025)07-0019-06
Abstract: Aiming at the problem that the FPGA program needs to use the program burmer,andthe burning speed is slow whenthe machine chassis isopen,amethod ofbuming theFPGA programunderthe Linux system oftheARMplatform is proposed.This methodisbasedonPCIedevice driverand SPIcontrollerdriver.TheFPGAisusedasaPCIedevice,egistered into thekemel,andaccessesthePCIeconfigurationspace,soastooperatetheregistersofSPIFlashpluggedontheFAand realize thereadand writeoperationofthe program execution file inthe specifedareaof SPIFlash intheLinux system.The experimentalresults show that the proposed FPGA program burming method does notrequire aditional burners and machine chassis opening lids.Itissimpleand fasttooperate,fast toburn,andreduces thecostofafter-sales programburning.
Keywords: FPGA program burning; ARM; Linux operating system; PCIe interface; SPI Flash
0 引言
电子信息技术的深入应用提高了嵌入式系统软件烧录的需求,包括实时烧录产品的程序。FPGA作为嵌入式开发领域的重要分支,凭借其高度定制化、灵活性、加速等优势[1],被广泛应用在高端制造、通信传输、军民两用等领域。为了适应不同的应用场景,FPGA 程序烧录方法也不唯—[2]。
目前,FPGA程序烧录方法主要有3种:
1)采用FPGA芯片原厂提供的程序烧录器,通过JTAG接口(串行口)下载可执行代码。产品交付客户使用后,每次烧录程序都需要额外的烧录器件和现场打开机箱,近距离操作,便利性差[3]。
2)将烧录功能集成到电路板上,并采用专用烧录器更新程序。该方法无须拆卸机箱盖,但增加了烧录程序相关的电路器件,扩大了硬件空间,提高了硬
件成本[4-5]。
3)采用通信接口实现FPGA程序的烧录。例如通过串口数据的传输与处理[,从而读写FPGA的Flash。该方法较好地解决了方法1)和2)的缺陷,但烧录速度较慢。
在需要高带宽和低延迟的应用场景中,FPGA作为PCIe设备与Linux主机连接工作,使得数据传输又快又稳[7-8]。为了实现便捷且更为快速的FPGA程序烧录,本文提出一种基于ARM平台Linux系统的FPGA程序烧录方法。与上述3种方法不同,该方法采用C语言开发驱动,并基于PCIe总线,实现了在应用层使用指令将程序二进制文件烧录到FPGA所外挂的SPIFlash中。通过在RK3568平台下的Linux操作系统中进行程序更新的实例,阐述了所提出的FPGA程序烧录方法。
1烧录方法总体架构设计
总体架构如图1所示。在RK3568平台上安装
Linux操作系统,并通过PCIe总线连接FPGA设备。此外,Flash设备通过SPI总线挂载在FPGA上[],并存储着FPGA的固件程序。在系统开机上电后,FPGA设备会从Flash设备中读取固件程序到内存中运行。
为了使Linux操作系统中的烧录程序能够与Flash设备交互,访问PCIe的配置空间,进而获取BAR空间地址,通过“BAR空间地址 + 偏移”的方式操作SPI总线寄存器,从而实现对Flash设备的读写操作。操作寄存器的方法分为两种:IO映射和内存映射。IO映射是CPU通过专门的IO指令来访问某个空间的地址;内存映射是将外设IO作为内存的一部分,CPU能够使用访问内存的方法进行读写操作。本文采用内存映射方法来操作外围设备的寄存器,BAR空间地址的内存分布情况如图2所示。由图可知,PCIe设备的配置空间偏移 0 x1 0 h-0 x1 4 h 是BaseAddressRegister0(BARO),通过采用“BAR0基地址 + 偏移”的方式访问SPI总线寄存器。而SPI总线寄存器主要包括片选、状态、控制、发送和接收寄存器。

结合图1和图2,FPGA程序烧录方法的实现主要步骤包括:
1)根据待烧录文件生成哈希值。为了预防烧录文件在传输过程中丢包,使用md5sum算法[生成待烧录文件的哈希校验码,便于后续与已烧录文件的哈希值进行比对。
2)将烧录程序执行文件写入SPIFlash。在Linux端,将FPGA视为PCIe设备,并采用内存映射方法访问PCIe设备的BAR0,通过“BAR0基地址 + 偏移”的方式控制SPI总线寄存器,进而操作FPGA外挂的SPI设备进行写数据和地址操作,达到将可执行文件bin中的内容通过SPI总线传输到Flash指定区域的目的。
3)从SPIFlash读取已烧录的程序内容,并生成文件哈希值。在步骤2)的基础上,控制接收寄存器,将写入操作变为读取操作,指定Flash中存储程序的位置来读取数据,命名为一个新的bin文件,并再次利用md5sum算法生成文件哈希值。
4)比较步骤1)和步骤3)的文件哈希值,输出比对结果。当校验结果一致时,此次烧录操作成功;反之,烧录失败。
2 软件总体设计
软件总体框图如图3所示。在Linux应用层,使用现有的“dd”命令直接读写FPGA外挂的SPIFlash设备。“md5sum”命令采用md5sum算法生成烧录程序的文件哈希值,从而检验烧录文件。文件/dev/mtdblock是SPIFlash驱动的设备节点,因此“dd”命令可以操作该设备节点,进而读写SPIFlash设备,当然前提是SPI设备和驱动匹配成功。通过SPIflashboardinfo内的信息与SPI设备驱动匹配成功后,SPI控制器驱动实现SPIFlash数据的写入和读取。

SPI设备和控制器驱动都是基于SPI子系统开发的,向上提供/dev/spi和/dev/mtdblock节点供应用层访问。并且,SPI控制器驱动是在PCIe设备驱动中实现的,从而通过PCIe设备的内存映射,访问PCIe的配置空间中的BAR0,进而控制SPIFlash内存的读写。
3 软件功能设计
从功能上主要由三部分组成,分别是PCIe设备驱动、SPIFlash控制器驱动和校验。运行流程图如图4所示。
3.1PCle设备驱动
通过PCIe配置空间的偏移 0 x1 0 h~ 0 x1 4 h ,获得BAR0空间地址。FPGA的SPI总线寄存器被映射在PCIeBARO上,也就是说,采用“BAR0基地址+ 偏移”的方式,能够控制SPI寄存器,实现读写操作。该模块创建了PCIe设备驱动,将FPGA视为一类PCIe设备,能够通过PCIe总线访问FPGA的寄存器,从而读写FPGA外置的Flash。因此,需要注册PCIe设备驱动。当通过PCIe驱动与设备匹配成功时,在evocprobe函数中初始化PCIe的基地址寄存器,并请求PCIe设备的IO资源空间,对其进行内存映射,获得虚拟地址空间,便于后续通过“BAR0基地址+ 偏移量”的方式访问PCIe设备外挂的SPIFlash寄存器。
staticint evoc_probe(struct pci_dev *pdev, conststruct pci_device_id_always_unused*ent) { int rc = - 1 rc pci_enable_device(pdev); rc
pci_request_regions(pdev,\"evoc_fpga\"); rc Σ= Σ pci_set_dma_mask(pdev,DMA_BIT
MASK(32)); ioaddr Σ= ioremap(pci_resource_start(pdev, O),pci_
resource_len(pdev, 0)); /*通过pci_resource_start获取到设备的BAR0
地址,再使用ioremap 映射到ioaddr*/ /*后续即可通过ioaddr+偏移的方式,访问SPI
寄存器*/ evoc_spi_probe(pdev,(unsigned char *)ioaddr); return O; }
3.2SPIFlash控制器驱动
本模块基于SPI子系统,创建SPI控制器驱动,以便访问Flash寄存器,完成SPI设备的操作,包括SPIFlash的读和写功能。由于SPI控制器通过SPI总线控制SPI设备,这里的SPIFlash设备(W25Q128)每页为256字节,16页构成一个扇区,大小为 4 K B 。每16个扇区构成一个块,块大小约为 6 4 K B 。并且,Flash仅能将1写成0。因此,需要使用spi_new_device函数申请SPI设备并将其注册进内核。
static struct flash_platform_data w25ql28_flash_data (20.name
\"w25q128\",};staticstructspi_board_infospi_flash_info[]
{.modalias = \" w2 5 q1 2 8 \" A.chip_select = 0 5.b
这个编号对应SPIcontroller注册
时候的 bus num.mode .platform_data amp;w25ql28_flash_data,}3 :spi_new_device(master,amp;spi_flash_info[i]);SPI控制器驱动中,定义了一个顶层结构体
evoc_spi,具有全局作用,如下:struct evoc_spi {struct spi_bitbang bitbang;//控制 SPI数据传输的结构体structcompletiondone://读写完成状态void_iomem*regs;//BAR 0基地址u8 *rx_ptr;//SPI接收字节数据const u8 *tx_ptr;//SPI发送字节数据u8 bytes_per_word://字节数intbuffer_size://缓冲区字节u32 cs_inactive;//片选信号unsigned int (*read_func)(void__iomem *);//读SPI寄存器void (*write_func)(u32,void__iomem *);//写SPI寄存器};在PCIe驱动和设备匹配后进入evoc_spi_probe函数,声明了SPI控制器结构体变量master,并为其分配指定大小的内存。通过master设置了SPI的极性、字节低位优先、默认的片选信号、总线编号、支持的片选信号数量等。SPI控制器由spi_bitbang 结构体来传输数据,它属于evoc_spi结构体的变量 fpga_spi的子变量。将 master 赋给 spi_bitbang变量的master,从而通过spi_bitbang.txrx_bufs函数指针指向的函数,并采用spi_transfer 结构体的rx_buf 指针作为Flash读数据的缓冲区,tx_buf指针作为Flash写数据的缓冲区,从而完成 SPI数据的读写处理。最终,通过 spi_bitbang_start函数将 SPI控制器注册到内核。master Σ= Σ spi_alloc_master(amp;pdev- . gt; dev,
sizeof(struct evoc_spi));if (!master)return -ENODEV;master-gt;mode_bits SPI_CPOLSPI_CPHASPI_
LSB_FIRSTSPI_LOOPSPI_CS_HIGH;//支持的模式fpga_spi Σ= spi_master_get_devdata(master);fpga_spi-gt;cs_inactive Oxfffff;
fpga_spi-gt;bitbang.master master;fpga_spi- . gt; bitbang.chipselect τ= τ evoc_spi_chipselect://实现控制片选的函数fpga_spi-gt;bitbang.setup_transfer Σ= evoc_spi_setup_transfer;//配置片选高有效还是低有效fpga_spi- . gt; bitbang.txrx_bufs Σ= evoc_spi_txrx_bufs;//实现发送和接收函数(spi发送和接收是同一个函数)fpga_spi-gt;regs
baseAddr;if (IS_ERR(fpga_spi-gt;regs)) {ret
PTR_ERR(fpga_spi-gt;regs);goto put_master;5master-gt;bus_n
controller的编号,跟I2C不同,不会自动递增,而是在驱动中固定它的值master-gt;num_chipselect
片选编号,默认为1,有时会有多个片选切换不同设备master-gt;dev.of_node
NULL;fpga_spi-gt;read_func Σ= xspi_read32://读 SPI寄存器fpga_spi-gt;write_func Σ= Σ Xspi_write32;//写 SPI寄存器fpga_spi-gt;bytes_per_word
bits_per_word / 8;fpga_spi- . gt; buffer_size Σ= Σ evoc_spi_find_buffer_size(fpga_spi);ret Σ= spi_bitbang_start(amp;fpga_spi-gt;bitbang)://通过这个接口注册一个spi controller具体地,SPI的数据读写操作逻辑都是在 spibitbang.txrx_bufs 函数指针指向的函数中实现的。该函数中,主要通过 fpga_spi-gt;read_func 和 fpga_spi-gt;write_func函数实现SPI寄存器的读写,输入参数为“BAR0基地址 + 偏移”的方式,实例如下:u32 data Σ= Σ fpga_spi-gt;read_func(fpga_spi-gt;regs + SPI_RXD_OFFSET);fpga_spi- ⋅ gt; write_func(O, fpga_spi ⋅ gt; regs + SPI_TXD_OFFSET);SPI数据读写逻辑如下:3.2.1写操作
1)写使能:分别设置控制寄存器和发送寄存器,重置输入和输出的先进先出寄存器,并发送写使能命令。2)设置片选信号:将SPI片选寄存器写为0,即选择SPI设备。
3)写命令和地址:设置发送寄存器,分别发送写数据命令和写数据的地址。4)读状态寄存器:读取状态寄存器,判断写命令和地址是否完成。若完成,则进行下一步;反之,等待完成。5)写数据:向发送寄存器中写入数据。6)读状态寄存器:读取状态寄存器,判断写数据是否完成。若完成,则进行下一步;反之,等待完成。7)取消片选信号:将SPI片选寄存器写为1,即取消选择SPI设备。
3.2.2 读操作
1)设置控制寄存器和发送寄存器,重置输入和输出的先进先出寄存器。2)设置片选信号:将SPI片选寄存器写为0,即选择 SPI设备。3)发送读命令:依次将读数据的命令和地址发送到发送寄存器。4)读状态寄存器:判断读命令是否完成,若完成则进行下一步;反之,等待完成。5)读数据:从接收寄存器中读取数据。6)读状态寄存器:判断读数据是否完成,若完成则进行下一步;反之,等待完成。7)取消片选信号:将SPI片选寄存器写为1,即取消选择SPI设备。
3.3 完整性检验
由于可能出现烧录数据前后丢包的风险,对烧录到SPIFlash前后的程序执行文件内容进行完整性校验[1],从而判断烧录程序的结果。在烧录程序和读取程序后,利用md5sum算法生成文件哈希值,从而判断程序在烧录过程中的完整性。
md5sum算法是一种报文摘要算法,它对任意长度的数据逐位运算,最终产生128位哈希值。该算法是一种单向哈希函数,不能逆向推演出原始数据,但计算速度快,同一数据所产生的哈希值固定,从而使得数据具有唯一性。它易于在多语言和平台上实现,被广泛应用。因此,本文利用该算法完成数据完整性校验。
4实验验证
测试过程包括以下6步:
1)切换到root权限,使用“insmod fpga_pcieko”命令加载FPGA作为PCIe设备的驱动。加载驱动完成后,使用“lsblk”命令查看是否多出FPGA外挂的Flash设备,这里是“mtdblock1”,如图5所示。
2)升级固件前,需要使用命令“md5sumxxx.bin”生成烧录文件的哈希值。如果烧录文件在传输过程中丢包,会导致烧录前后的文件哈希值不一致,这里的xxx.bin是fpgal04_a07.bin。
3)使用“ddif=xxx.bin of=/dev/Flash设备名 count=2192 012 status=progress”命令,将待烧录文件烧录到FPGA外挂的Flash中。每次烧录一个字节,总共烧录2192012次,并显示烧录进度,如图5所示。这里的xxx.bin是read.bin。
4)烧录程序结束后,读取烧录文件,采用命令“ddof=xxx2.binif=/dev/Flash设备名 count
192012status=progress”生成新的 x x x2 . 6 i n 文件。并且,再次使用“md5sumxxx2.bin”命令,获得文件的哈希值,如图6所示。
5)比较两次生成的文件哈希值,如果结果相同,表示此次烧录成功。
6)重启系统,使用“insmod fpga_pcie.ko”命令加载驱动,使用“dmesg”命令查看烧录成功的烧录文件版本。
对于程序文件的烧录,主要是在加载FPGA驱动后,采用dd命令将数据写入SPIFlash或从SPIFlash读取数据,并且在程序文件烧录前后采用md5sum命令生成文件校验码,最终通过比对差异,保证烧录的准确性。
5结论
本文所提出的程序烧录方法是在RK3568平台的Linux系统上,基于FPGA作为PCIe设备驱动,并结合SPI控制器驱动,将程序更新数据烧录到FPGA外挂的SPIFlash中。该方法只需注册(下转28页)