在使用Xilinx 7 系列FPGA开发 时,往往使用jtag工具,将bit程序下载到FPGA 芯片中, 反复不断的测试,直到项目最终完成。 当项目完成后,会将程序烧录到片外的存储器中,常见的是使用SPI Flash。有关Xilinx FPGA 如何制作mcs文件, 如何烧录到SPI Flash,请参阅相关文章:FII-PRX100-D开发板FPGA的烧录和RISC-V 软件代码下载
在很多项目中, 不但在出厂时需要烧录SPI Flash, 有时也需要产品被卖到客户手中后更新FPGA 的内容, 通常使用网络对产品进行远程的更新。但在产品远程更新的时候,1)如果更新的文件有问题(下载或者传输错误); 2)正在更新的产品突然断电 等原因, 这样会导致远程更新失败。 在这种情况下,失败的后果也是非常严重的。 即:产品不能重新启动(老的程序被破坏, 新的程序又没有烧录成功)。这时,就不是客户能够处理的问题了, 需要将产品邮寄回原厂, 原厂需要打开产品外壳, 使用jtag 工具对产品实施更新。 这种方法的运输成本高,时间成本也是很高的, 往往不能让客户满意。
1 MultiBoot 启动原理
为此, Xilinx 公司开发的一种新的方法, 可以保证在更新的数据失败后(数据本身被破坏, 或者正在更新的产品突然断电等),产品依然可以正常启动,这样就方便客户对产品做二次更新。具体方法是:在SPI Flash中保存两份FPGA 烧录文件,其中:
第一份文件被称作金码(golden code), 原厂在出厂的时候烧录进去,有时也可以将烧录后的SPI Flash扇区(保存金码的部分)直接保护起来(这个部分的扇区只读, 不可以在写,编程等操作)
第二分文件通常称作app 文件,这个部分在出厂是也是被烧录到Flash中的, 同时这个部分不需要写保护, 用户可以根据需要重新更新这个部分的内容。
产品的启动的过程如下:
图1
step 1:上电后,FPGA 从0地址开始启动,
step 2:根据金码中的内容直接跳转到app 地址。
step 3:这时候正常加载app 程序
step 4:如果加载的app 程序有错误,直接跳转会金码
step 5:执行金码中的内容。
由于每次只是更新app 地址中的内容, 所以不论是否成功, 都不会影响金码中的内容。这样就可以对app 程序反复更新, 知道成功为止。Xilinx 关于SPI Flash的multiboot有很多的方法,这里我们介绍一种比较简单的方法, 即修改XDC文件,就可以实现金码和app 程序两份文件的加载。 Xilinx 还有其他的方法实现更多的app 程序加载,3份,4份,更多份的程序按照顺序启动。但这种方式需要编写hdl 程序(verilog, vhdl 等)才能实现,过程相对复杂,但可以加载很多文件。
2 修改XDC文件
这里我们介绍一下只需要修改XDC文件,就可以实现金码和app 的两份文件交替加载。在图1 中我们会发现,所有的过程,都是在FPGA 上电或者reset 的时候, 自动完成的,并不需要用户的过多参与。用户或者开发者只需告诉金码和app 程序是否支持MultiBoot 和 启动时跳转的地址即可。这两方面的内容只需要修改XDC文件即可。
在金码XDC文件中添加两条命令:
set_property BITSTREAM.CONFIG.CONFIGFALLBACK ENABLE [current_design]
set_property BITSTREAM.CONFIG.NEXT_CONFIG_ADDR 0x00400000 [current_design]
允许失效返回(倒退)
正常的启动时,直接跳转到app的地址(0X00400000),
这样就可以直接启动app 程序了, 当app程序有问题, 可以返回金码。
在app程序中XDC文件中添加命令
set_property BITSTREAM.CONFIG.CONFIGFALLBACK ENABLE [current_design]
失效返回
这样app 失效后可以返回金码了。
修改XDC文件的方法比较简单,容易实现,但是缺点是不能增加app程序了, 也就是说只能实现金码和app 程序的转换, 不能增加更多的文件。
3 参考工程代码:
这里,我们需要创建两个工程, 其中一个工程为金码(golden code) , 另外一个工程为正常更新,使用的app工程。
首先,制作金码工程:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: Fraser Innovation Inc // Engineer: WilliamG // // Create Date: 2020/06/22 09:12:51 // Design Name: // Module Name: top.v // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module TOP# ( SIM_MODE = "FALSE" ) ( input IN_CLK_50M, //外部时钟 output [7:0] LED //fpga心跳 ); //======================================================================================= //时钟模块 复位电路 wire clk_50m; wire clk_25m; wire locked; pll SYS_PLL ( .clk_in1 (IN_CLK_50M), .locked (locked), .clk_out1 (clk_50m), .clk_out2 (clk_25m), .reset (1'b0) ); wire pb_rst; //按键复位脉冲 高电平有效 wire rst_r_n; //复位寄存 assign rst_r_n = locked & (~pb_rst); //低有效 locked 或按键触发 //======================================================================================= reg [31:0] rst_cnt = 0; //复位延时计数 reg sys_rst_n = 0; //系统时钟 locked 系统复位 always @ (posedge clk_50m) if(!rst_r_n) sys_rst_n <= 0; else begin if(rst_cnt >= 500) //delay 500ms sys_rst_n <= 1; else begin sys_rst_n <= 0; //计数不到 保持复位状态 rst_cnt <= rst_cnt +1; end end //======================================================================================= reg [31:0] led_cnt = 0; always @ (posedge clk_50m) if(!sys_rst_n) led_cnt <= 0; else led_cnt <= led_cnt + 1; assign LED = ~{7'b000_0000,led_cnt[23]}; //======================================================================================= endmodule
这个工程比较简单, 只有top.v 一个文件, 其中我们发现,当金码启动是,8个led 中的led[0] 会闪烁。
实现金码的XDC文件
####################################################### #================================================== #clk set_property -dict {PACKAGE_PIN D18 IOSTANDARD LVCMOS33} [get_ports IN_CLK_50M] create_clock -period 20.000 -name IN_CLK_50M -waveform {0.000 10.000} [get_ports IN_CLK_50M] #================================================== #led set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33} [get_ports {LED[0]}] set_property -dict {PACKAGE_PIN M19 IOSTANDARD LVCMOS33} [get_ports {LED[1]}] set_property -dict {PACKAGE_PIN P16 IOSTANDARD LVCMOS33} [get_ports {LED[2]}] set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports {LED[3]}] set_property -dict {PACKAGE_PIN N19 IOSTANDARD LVCMOS33} [get_ports {LED[4]}] set_property -dict {PACKAGE_PIN P19 IOSTANDARD LVCMOS33} [get_ports {LED[5]}] set_property -dict {PACKAGE_PIN N24 IOSTANDARD LVCMOS33} [get_ports {LED[6]}] set_property -dict {PACKAGE_PIN N23 IOSTANDARD LVCMOS33} [get_ports {LED[7]}] #################################################################################### ############ golden code xdc, jump to address 0x0040_0000 ####################### set_property BITSTREAM.CONFIG.CONFIGFALLBACK ENABLE [current_design] set_property BITSTREAM.CONFIG.NEXT_CONFIG_ADDR 0x0400000 [current_design] ########################################################################################
只需要在正常的XDC文件中增加
set_property BITSTREAM.CONFIG.CONFIGFALLBACK ENABLE [current_design]
set_property BITSTREAM.CONFIG.NEXT_CONFIG_ADDR 0x0400000 [current_design]
这两句即可实现正常启动是,从金码直接跳转到0x0040_0000 这个地址了。
制作app工程
工程文件:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: Fraser Innovation Inc // Engineer: WilliamG // // Create Date: 2020/06/22 09:12:51 // Design Name: // Module Name: top.v // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module TOP# ( SIM_MODE = "FALSE" ) ( input IN_CLK_50M, //外部时钟 output [7:0] LED //fpga心跳 ); //======================================================================================= //时钟模块 复位电路 wire clk_50m; wire clk_25m; wire locked; pll SYS_PLL ( .clk_in1 (IN_CLK_50M), .locked (locked), .clk_out1 (clk_50m), .clk_out2 (clk_25m), .reset (1'b0) ); wire pb_rst; //按键复位脉冲 高电平有效 wire rst_r_n; //复位寄存 assign rst_r_n = locked & (~pb_rst); //低有效 locked 或按键触发 //======================================================================================= reg [31:0] rst_cnt = 0; //复位延时计数 reg sys_rst_n = 0; //系统时钟 locked 系统复位 always @ (posedge clk_50m) if(!rst_r_n) sys_rst_n <= 0; else begin if(rst_cnt >= 500) //delay 500ms sys_rst_n <= 1; else begin sys_rst_n <= 0; //计数不到 保持复位状态 rst_cnt <= rst_cnt +1; end end //======================================================================================= reg [31:0] led_cnt = 0; always @ (posedge clk_50m) if(!sys_rst_n) led_cnt <= 0; else led_cnt <= led_cnt + 1; assign LED = ~{led_cnt[23], 7'b000_0000}; //======================================================================================= endmodule
在app 工程中, 如果被正常启动, 我们会发现8个led中的led[7] 会闪烁。
app工程中的XDC文件:
####################################################### #================================================== #clk set_property -dict {PACKAGE_PIN D18 IOSTANDARD LVCMOS33} [get_ports IN_CLK_50M] create_clock -period 20.000 -name IN_CLK_50M -waveform {0.000 10.000} [get_ports IN_CLK_50M] #================================================== #led set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33} [get_ports {LED[0]}] set_property -dict {PACKAGE_PIN M19 IOSTANDARD LVCMOS33} [get_ports {LED[1]}] set_property -dict {PACKAGE_PIN P16 IOSTANDARD LVCMOS33} [get_ports {LED[2]}] set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports {LED[3]}] set_property -dict {PACKAGE_PIN N19 IOSTANDARD LVCMOS33} [get_ports {LED[4]}] set_property -dict {PACKAGE_PIN P19 IOSTANDARD LVCMOS33} [get_ports {LED[5]}] set_property -dict {PACKAGE_PIN N24 IOSTANDARD LVCMOS33} [get_ports {LED[6]}] set_property -dict {PACKAGE_PIN N23 IOSTANDARD LVCMOS33} [get_ports {LED[7]}] #################################################################################### ################ app multiboot ####################################################### set_property BITSTREAM.CONFIG.CONFIGFALLBACK ENABLE [current_design] ########################################################################################
在app 工程中XDC文件只需提供 失效返回即可:
set_property BITSTREAM.CONFIG.CONFIGFALLBACK ENABLE [current_design]
4 制作mcs文件:
当我们完成上述两个工程的综合, 布局,布线,生成最终的bit 文件后, 就需要制作mcs 文件用来烧录SPI Flash。
在地址0的位置上烧录金码程序, 在地址0x0040_0000 的位置上烧录app 程序。
图2
生成mcs:
图3
图4
生成mcs文件之后, 我们可以查看一下报告:
图5
来验证制作的mcs文件是否符合我们的设计要求。
5 烧录mcs 文件到SPI Flash
将制作好的multiboot_test.mcs文件烧录的mt25ql128 SPI Flash中。
图6
6 验证实验现象:
图7
如图7所示: 当烧录后, 重新启动FPGA 开发板, 我们会发现led[7] 一直在闪烁。说明app 这个工程正在工作。这一部分工作模拟了产品的出厂设置情况。
图8
从jtag中读出FPGA 内部的寄存器, 可以看出FALLBACK 没有执行,也就是说app程序是没有问题的。
修改app工程的bit文件:
将top.bit 文件复制一份,重新命名为top_err.bit。打开编辑工具, 编辑top_err.bit 文件:
图9
在bit文件中,修改原来的数00 为 11 ,保存这个bit 文件。重新生成mcs文件, 这样,就可以模拟app 工程文件错误,然后烧录到FPGA 开发板中。 观察开发板在重新启动的情况:
图10
烧录到FPGA 开发板上进行测试:
图11
正如图11 所示, 因为app工程的bit 文件被修改了, 所以在启动时发现app 工程有错误, 那么系统将直接跳转的金码工程。 这时我们会发现 led【0】 一直在闪烁。这就模拟了真实用户升级的场景,当升级时发生错误, 系统依然可以正常启动(启动的是金码程序)。
图12
从jtag上我们也可以查看FPGA 内部的寄存器, 可以看到FALLBACK 已经生效了。
在真实系统中, 往往是金码和 app 程序中都包含一个SPI controller, 这样在线升级的时候,就不再依赖jtag 工具了, 可以直接使用FPGA 编写的SPI controller 对SPI Flash进行编程。
参考文章: xilinx 公司 7系列MultiBoot 启动SPI Flash
测试工程代码:
模块里wire pb_rst没有来源啊