FPGA切换启动镜像(可重构技术)


20250227235502

前言

这是一种无需重新烧录比特就能切换FPGA正在运行的比特的方法。
分为两种,一种是全局可重构,也可以叫做启动镜像切换,本质上是将两份或多份比特流同时固化到FLASH中,在运行过程实时中切换使用哪一份比特,这是基于NOR FLASH的片上执行特性(暂且这么称呼,本质上是因为NOR只需要给地址就能出数据,没有译码转址这一过程) 。所以对于配置了NOR FLASH的FPGA板卡,我们可以通过ICAP原语来给出比特流加载的同步字,然后送入加载基地址,并给出加载命令,来进行当前执行比特流的切换。

另一种是部分可重构,这种要更复杂一点,但是本质上只不过是把重加载全局比特变成了重加载部分模块的比特,所以会有个画pblock的过程,相比于全局可重构,流程会复杂许多,而且如果是中间模块的替换,由于输入输出端口的变化,会造成一些时序约束的问题,且无法直接使用OOC IP核,只能把IP核做成网表再调用,在我自己的工程上对于资源利用率的降低作用不显著。本文就只着眼全局可重构。

全局可重构我目前探索出来的路径有两条,一条已经实现,就是后文的合并bit到一个mcs文件中,然后使用icape原语启动。另外的一条路是上位机通过pcie的dma通道,流式传输要切换的比特到板卡上,完成比特的切换,这条路还在摸索,如果能够完成的话,那么对于机箱上的FPGA板卡来说,就可以让上位机保存多份比特,在运行之中随便切换,完成FPGA板卡功能的更新或者切换。

下面仅从怎么做的描述如何将两个工程的比特流合并到同一个mcs文件下,并使用这个mcs来对fpga进行烧录固化,并编写简单的代码来调用icape原语来对启动地址进行切换,以达到不重新烧写比特就能进行工程的切换的方法。

有关FLASH的几个工程xdc约束

需要添加到工程的xdc中

title
1
2
3
4
5
6
7
############## FLASH ##################
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 1 [current_design]
set_property CONFIG_MODE SPIx1 [current_design]
# 如果 FLASH 大小大于等于 256Mb,要增加如下约束,否则高于24位的地址会被忽略,导致无法启动对应的 update 镜像
set_property BITSTREAM.CONFIG.SPI_32BIT_ADDR YES [current_design]

set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]压缩比特,这是一个蛮有用的选项,如果不使用压缩比特的话,那么任何一个工程,不论代码有多简单或者多复杂,都会生成一个同样大小的,未经过压缩的比特,对于7k325t会占用大概11MB的大小,更复杂的片子会更大。如果启用了,就会根据工程代码的复杂程度对比特进行压缩,一般来说都挺有效果的,能把最简单的LED流水灯工程压缩到几百KB的水平。能把我的工程压缩到5.5MB左右大小

set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]设置烧录速度,50M是SPI时钟线上的时钟频率,一般的SPI存储芯片都能支持这个速率。

set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 1SPI宽度,这是用于控制烧录程序的接口的,因为我们要使用Fallback,所以这里必须是x1

set_property CONFIG_MODE SPIx1配置模式宽度,和上面一样

set_property BITSTREAM.CONFIG.SPI_32BIT_ADDR YES [current_design] 启用32bit地址线模式,如果FLASH大小大于等于256Mb,也就是32MB,需要启用这个约束,并且写入镜像的地址线的方法需要变动,见后文。我的板子上的s25fl256大小正好是256Mb,所以得开。

multiboot模块的调用方法示例

title
1
2
3
4
5
6
7
8
// ^ 全局可重构
multiboot_ctrl multiboot_ctrl_inst (
.clk (clk_out_100),
.rst_n(sys_rst_n),
.multiboot_start(~reconfig_key_n), // 使用外部按键触发
.multiboot_addr (32'h00500000), // 加载0x00500000处的Golden Image
.busy_o()
);

这里要注意的是给地址的要求,如果前面使能了32bit地址线,则要根据下文把multiboot_addr[31:8]写到WBSTAR[23:0]

title
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// ^ ICAP 原语实现程控 multiboot(多重启动),K7需要使用 ICAPE2 原语

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_o
);

//-------------------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'h3651093), // Specifies the pre-programmed Device ID value to be used for simulation purposes. K7-325T的为32'h3651093
.ICAP_WIDTH("X32"), // Specifies the input and output data width.
.SIM_CFG_FILE_NAME("NONE") // Specifies the Raw Bitstream (RBT) file to be parsed by the simulation model.
) 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: Active-Low ICAP Enable
.I (ICAPE2_I), // 32-bit input: Configuration data input bus
.RDWRB(ICAPE2_RDWRB) // 1-bit input: Read/Write Select input 1对应rd,0对应wr
);

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;

/*When using ICAPE2 to set the WBSTAR address, the 24 most significant address bits should be written
to WBSTAR[23:0]. For SPI 32-bit addressing mode, WBSTAR[23:0] are sent as address bits [31:8]. The
lower 8 bits of the address are undefined and the value could be as high as 0xFF. Any bitstream
at the WBSTAR address should contain 256 dummy bytes before the start of the bitstream.*/
wire [31:0] WBSTAR = {3'b000, 5'h0, multiboot_addr[31:8]};

wire [31:0] WR_CMD = 32'h30008001;
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;

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

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_o <= 1'b0;
default: busy_o <= 1'b1;
endcase
end

endmodule

生成两个.bit文件后合并到MCS文件

分别运行两个经过了FLASH约束和添加了multiboot模块的工程,获得它们的比特

然后点击VIVADO左上角工具栏的”Tools-Generate Memory Configuration File”

然后进行FLASH的选择,这里要根据FPGA板卡的原理图进行选择,我这块板卡的型号是这样的。

20250227162935

20250227162943

这里的Interface选择X1,因为之前的约束里表明了是x1的接口。

比特大小和地址的确定

第一个工程在应该被放置在地址零处,假设.bit文件占用4MB,也就是0x400000

那么我们可以吧把第二个地址设置在0x500000处,只要不占用第一个工程的地址最后且不超过FLASH的物理地址就可以。

成功生成mcs文件后就可以使用这个文件去进行烧录了。

注意事项:烧写固化msc后,需要板子下电后,拔掉JTAG再上电才会从FLASH中的程序从启动,否则在JTAG插着且VIVADO的硬件管理器页面在激活的情况下,是不会使用FLASH中的比特流启动的。

实例演示

我这里使用了两个工程,一个工程实现的物理效果是板子上4个LED的闪烁,另一个工程实现的是板子上8个LED的闪烁。使用板载的一个按键,来进行工程之间的互相切换。

将合并的MultiBoot_LEDs.mcs烧录进板卡,并下电后拔掉jtag再上电,然后按动板子上的按键,就能看到原本是4个LED闪烁,变为了8个LED闪烁,再按一下,变回了4个LED闪烁。这只是一个简单的例子,实现的效果虽然简单,但是本质确实是整个工程运行代码的切换。

图我就不放了,就是个LED闪烁工程。

工程打包

链接: https://pan.baidu.com/s/1QTcCIaY0xiVLThJ79uWMFA 提取码: mcqz

参考链接

https://blog.csdn.net/dare_xds/article/details/139151733

https://blog.csdn.net/qq_43557686/article/details/134750377


文章作者: Allen Hong
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Allen Hong !
  目录