什么是远程升级?
是一种更新程序的方法。在实际情况下,我们做好一个产品,进行更新的时候,不可能把产品拆开,对里面的固件进行升级。所以会涉及到一种远程升级的方法,就是上位机通过串口,或者PCIe等网络协议,将需要更新的程序放入FPGA的fifo,FPGA通过spi控制与flash进行交互,将从上位机收到的数据传入flash。
这里的flash就是一个存储产品配置的一个存储器。
什么是Multiboot?
将flash分区,放入不同的镜像工程,可以根据地址寻找不同的镜像工程进行加载。
flash存储的形式?
在flash中,我们会进行分区。
Golden Image区:从地址0开始,存储产品的一个稳定版本。当update区的程序启动失败时,让产品加载这个稳定版本,不至于让产品不能启动。
Update Image区:在golden区的地址之上开辟。用来存储我们的升级版本。
这是一个加载的顺序图,从图上我们可以看出:
step1:FPGA首先从地址0开始加载,这时候我们处于Golden镜像。
step2:由于我们在golden镜像的工程里面设置了跳转。所以这个时候我们会跳过Golden区。跳到我们设置的flash地址。
step3:这时候我们开始开始加载Update Image
step4:这里会进行一个判断,如果Update Image没有问题。我们就直接加载Update镜像,也就是我们需要升级的程序。如果有问题,那我们会进入step5;
step5:如果Update Image有问题,会进行一个fallback,也就是回滚。加载稳定程序 Golden Image.
想要做到多镜像启动,有两种方法。
1.是在bit流中加入指令,让FPGA加载的时候根据指令跳入到需要加载的flash镜像地址。
2.Xlinx提供了一个原语,可以在代码中进行操作,本质还是在bit流文件中加入指令。不过在代码中操作更方便。
小测试:
代码工程放在最下面,需要的自取
说一下测试的功能点以及怎么体现Multiboot:
1.两个工程:
a.golden工程:点亮led0
b.update工程:点亮led1
2.使用ICAPE2原语进行分区控制。
设备ID查找表:
怎么使用?我先贴出一个代码参考:
`default_nettype none
module multiboot_ctrl(
input wire clk,
input wire rst_n,
input wire multiboot_start, //触发Multiboot, 上升沿有效
input wire [31:0] multiboot_addr, //要启动的Muliboot Image的起始地址
output reg busy
);
wire multiboot_start_pe;
reg multiboot_start_d0;
reg multiboot_start_d1;
assign multiboot_start_pe = multiboot_start_d0 & (~multiboot_start_d1); // 采集控制使能的下降沿
always @(posedge clk) begin
multiboot_start_d0 <= multiboot_start;
multiboot_start_d1 <= multiboot_start_d0;
end
//-------------------ICAPE2原语-----------------------------
wire ICAPE2_CLK;
wire [31:0] ICAPE2_O;
reg ICAPE2_CSIB;
wire [31:0] ICAPE2_I;
reg ICAPE2_RDWRB;
assign ICAPE2_CLK = clk;
ICAPE2 #(
.DEVICE_ID (32'h362d093), // 需要看自己芯片对应的设备ID A7-35T的为32'h362d093
.ICAP_WIDTH ("X32"), // 输入,输出位宽配置 "X32", "X8", "X16" 默认 "X32"
.SIM_CFG_FILE_NAME ("NONE") // 配置仿真模型要解析的原始比特流文件,这里没有 所以填"NONE"
)
ICAPE2_inst(
.O (ICAPE2_O), // 32-bit output: Configuration data output bus
.CLK (ICAPE2_CLK), // 1-bit input: Clock Input
.CSIB (ICAPE2_CSIB), // 1-bit input: ICAP的使能信号,低有效。如果不发指令或地址的时候,拉高
.I (ICAPE2_I), // 32-bit input: 数据总线,输入对应的指令或flash跳转地址
.RDWRB (ICAPE2_RDWRB) // 1-bit input: 1对应rd,0对应wr。发指令或地址的时候就是0,否则1
);
wire [31:0] Dummy = 32'hFFFFFFFF; //伪指令,不进行操作,实际上为等待状态
wire [31:0] Sync_Word = 32'hAA995566; //跳转指令同步字
wire [31:0] NOOP = 32'h20000000; //中断指令
wire [31:0] WR_WBSTAR = 32'h30020001; //type1 写入跳转空间的字节
wire [31:0] WBSTAR = {3'b000, 5'h0, multiboot_addr[31:8]}; //跳转空间地址,可以根据bit大小修改
wire [31:0] WR_CMD = 32'h30008001; //type1 写入的控制指令字
wire [31:0] IPROG = 32'h0000000F; //跳转到下一个程序的控制指令
//ICAPE2位翻转
reg [31:0] wrdat;
assign ICAPE2_I = {wrdat[24], wrdat[25], wrdat[26], wrdat[27], wrdat[28], wrdat[29], wrdat[30], wrdat[31],
wrdat[16], wrdat[17], wrdat[18], wrdat[19], wrdat[20], wrdat[21], wrdat[22], wrdat[23],
wrdat[8], wrdat[9], wrdat[10], wrdat[11], wrdat[12], wrdat[13], wrdat[14], wrdat[15],
wrdat[0], wrdat[1], wrdat[2], wrdat[3], wrdat[4], wrdat[5], wrdat[6], wrdat[7]};
//------------------------FSM----------------------------------
localparam S_IDLE = 16'h0001;
localparam S_DUMMY = 16'h0002;
localparam S_SYN_WORD = 16'h0004;
localparam S_NOOP1 = 16'h0008;
localparam S_WR_WBSTAR = 16'h0010;
localparam S_WBSTAR = 16'h0020;
localparam S_WR_CMD = 16'h0040;
localparam S_IPROG = 16'h0080;
localparam S_NOOP2 = 16'h0100;
localparam S_STOP = 16'h0200;
reg [15:0] state = S_IDLE;
reg [15:0] next_state;
always @(posedge clk) begin
if(~rst_n) begin
state <= S_IDLE;
end
else begin
state <= next_state;
end
end
always @(*) begin
case(state)
S_IDLE: begin
if(multiboot_start_pe) begin
next_state <= S_DUMMY;
end
else begin
next_state <= S_IDLE;
end
end
S_DUMMY: next_state <= S_SYN_WORD;
S_SYN_WORD: next_state <= S_NOOP1;
S_NOOP1: next_state <= S_WR_WBSTAR;
S_WR_WBSTAR: next_state <= S_WBSTAR;
S_WBSTAR: next_state <= S_WR_CMD;
S_WR_CMD: next_state <= S_IPROG;
S_IPROG: next_state <= S_NOOP2;
S_NOOP2: next_state <= S_STOP;
S_STOP: next_state <= S_IDLE;
default: next_state <= S_IDLE;
endcase
end
always @(posedge clk) begin
case(state)
S_IDLE: begin
wrdat <= 32'd0;
ICAPE2_CSIB <= 1'b1;
ICAPE2_RDWRB <= 1'b1;
end
S_DUMMY: begin
wrdat <= Dummy;
ICAPE2_CSIB <= 1'b0;
ICAPE2_RDWRB <= 1'b0;
end
S_SYN_WORD: begin
wrdat <= Sync_Word;
ICAPE2_CSIB <= 1'b0;
ICAPE2_RDWRB <= 1'b0;
end
S_NOOP1: begin
wrdat <= NOOP;
ICAPE2_CSIB <= 1'b0;
ICAPE2_RDWRB <= 1'b0;
end
S_WR_WBSTAR: begin
wrdat <= WR_WBSTAR;
ICAPE2_CSIB <= 1'b0;
ICAPE2_RDWRB <= 1'b0;
end
S_WBSTAR: begin
wrdat <= WBSTAR;
ICAPE2_CSIB <= 1'b0;
ICAPE2_RDWRB <= 1'b0;
end
S_WR_CMD: begin
wrdat <= WR_CMD;
ICAPE2_CSIB <= 1'b0;
ICAPE2_RDWRB <= 1'b0;
end
S_IPROG: begin
wrdat <= IPROG;
ICAPE2_CSIB <= 1'b0;
ICAPE2_RDWRB <= 1'b0;
end
S_NOOP2: begin
wrdat <= NOOP;
ICAPE2_CSIB <= 1'b0;
ICAPE2_RDWRB <= 1'b0;
end
S_STOP: begin
wrdat <= 32'd0;
ICAPE2_CSIB <= 1'b1;
ICAPE2_RDWRB <= 1'b1;
end
default: begin
wrdat <= 32'd0;
ICAPE2_CSIB <= 1'b1;
ICAPE2_RDWRB <= 1'b1;
end
endcase
end
always @(*) begin
case(state)
S_IDLE: busy <= 1'b0;
default: busy <= 1'b1;
endcase
end
endmodule
就是根据一个使能信号,然后一步一步往ICAPE2里面发指令,具体看上面代码。
代码参考这位大佬的:K7系列FPGA多重启动(Multiboot)_multiboot多重配置-CSDN博客
3.XDC文件的配置
#Updata Image工程中添加
set_property BITSTREAM.CONFIG.CONFIGFALLBACK ENABLE [current_design]
set_property CONFIG_MODE SPIX1 [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 1 [current_design]
#Golden Image工程中添加
set_property BITSTREAM.CONFIG.CONFIGFALLBACK ENABLE [current_design]
set_property BITSTREAM.CONFIG.NEXT_CONFIG_ADDR 0x00400000 [current_design]
set_property CONFIG_MODE SPIX1 [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 1 [current_design]
这一条是让golden可以进行跳转,以及跳转到哪个地址。
这一条是配置回滚使能。让Update Image加载失败的时候能够回滚到Golden Image。
这两个是配置SPI的模式,后面生成mcs要用。
4.bit流生成
生成之后看下对应的功能是否正常。具体请看步骤1
5.生成mcs文件。
对上图进行一下讲解:
1.生成文件类型,毫无疑问选MCS(因为我们就是要生成这个)
2.根据你的flash的大小填写,我这边是16MB,这个是以字节为单位哈。
3.填写你的生成文件路径
4.程序下载的方式,这里和上面XDC对应上,XDC写的SPI 1,所以这里选的SPIx1
5.你的Golden Image的bit文件路径,记住这里的开始地址是0;
6.你的Update Image的bit文件路径,记住这里的开始地址是你自己设置的update Image的起始地址。如果你不知道应该设置多大,你可以先填写大一点。然后在prm文件里面可以查看。
prm文件:是mcs的伴生文件,里面是一些mcs文件的信息。
例如下图:
可以看到bit流文件的大小。
点击OK,然后在你填写的文件生成路径下面可以看到两个文件,一个mcs一个prm
6.将mcs文件固化到板子上。
这个就不具体说了,不清楚的搜下看看。
7.测试正常功能
固化之后,断电,拔掉Jtag,然后上电。正常的话,会看到led1亮,也就是Update工程被加载了。
8.测试fallback
正常我们是加载Update Image,现在我们测试一下能不能回滚:
1.修改update的bit流文件。打开之后随便在中间改几个字节。
我用的vscode,下载一个插件就能打开了
2.改好了之后,我们生成一个新的mcs文件,就是战损版。
3.使用战损版mcs进行固化
4.这个时候应该看到led0亮,因为update的bit流被我们人为破坏了,所以回滚到了Gloden 工程。
重点:
1.理解多镜像分区,以及为什么要分区。
2.ICAPE2原语的使用。
3.具体代码可以在我的gitee上自取,有现成的mcs文件。如果有帮助,麻烦gitee上点个心。
Xilinx-Multiboot-ICAPE2-test: 使用Xilinx的ICAPE2原语,实现一个Multiboot的小测试
如果有说错的地方,欢迎评论区或私聊指出,大家可以一起讨论哈~~~
更多嵌入式,FPGA资料可参考:天津大学四川院FPGA培训中心 -- www.sxfpga.cn