Menu Close

Xilinx 7 系列FPGA 使用MultiBoot 功能 启动SPI Flash

在使用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中的, 同时这个部分不需要写保护, 用户可以根据需要重新更新这个部分的内容。

产品的启动的过程如下:

%title插图%num

图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 程序。

%title插图%num

图2

生成mcs:

%title插图%num

图3

%title插图%num

图4

生成mcs文件之后, 我们可以查看一下报告:

%title插图%num

图5

来验证制作的mcs文件是否符合我们的设计要求。

5 烧录mcs 文件到SPI Flash

将制作好的multiboot_test.mcs文件烧录的mt25ql128 SPI Flash中。

%title插图%num

图6

6 验证实验现象:

%title插图%num

图7

如图7所示: 当烧录后, 重新启动FPGA 开发板, 我们会发现led[7] 一直在闪烁。说明app 这个工程正在工作。这一部分工作模拟了产品的出厂设置情况。

%title插图%num

图8

从jtag中读出FPGA 内部的寄存器, 可以看出FALLBACK 没有执行,也就是说app程序是没有问题的。

 

修改app工程的bit文件:

将top.bit 文件复制一份,重新命名为top_err.bit。打开编辑工具, 编辑top_err.bit 文件:

%title插图%num

图9

在bit文件中,修改原来的数00 为 11 ,保存这个bit 文件。重新生成mcs文件, 这样,就可以模拟app 工程文件错误,然后烧录到FPGA 开发板中。 观察开发板在重新启动的情况:

%title插图%num

图10

烧录到FPGA 开发板上进行测试:

%title插图%num

图11

正如图11 所示, 因为app工程的bit 文件被修改了, 所以在启动时发现app 工程有错误, 那么系统将直接跳转的金码工程。 这时我们会发现 led【0】 一直在闪烁。这就模拟了真实用户升级的场景,当升级时发生错误, 系统依然可以正常启动(启动的是金码程序)。

%title插图%num

图12

从jtag上我们也可以查看FPGA 内部的寄存器, 可以看到FALLBACK 已经生效了。

在真实系统中, 往往是金码和 app 程序中都包含一个SPI controller, 这样在线升级的时候,就不再依赖jtag 工具了, 可以直接使用FPGA 编写的SPI controller 对SPI Flash进行编程。

 

参考文章: xilinx 公司 7系列MultiBoot 启动SPI Flash

测试工程代码:

 

Posted in FPGA, FPGA 教材教案, Verilog, Verilog, 教材与教案, 文章

1 Comment

发表回复

相关链接