在工业控制和嵌入式设备领域,FPGA程序的远程升级一直是个令人头疼的问题。传统方式需要工程师带着下载器到现场操作,既费时又费力。今天我要分享的这套纯Verilog实现的远程升级方案,仅需串口调试助手就能完成FPGA固件的烧录、验证和回滚全套操作。
这个方案的核心价值在于:
我曾在一个光伏逆变器项目中使用这套方案,成功实现了分布在全国各地30多个电站的FPGA程序远程更新,将平均维护时间从原来的2周缩短到2小时。
系统由五个关键模块组成:
verilog复制module fpga_upgrade_top(
input clk_50m,
input rst_n,
input uart_rx,
output uart_tx,
output [3:0] status_led
);
// 各模块实例化代码...
endmodule
常规波特率发生器存在累计误差问题,这里采用相位累加器设计:
verilog复制module baud_gen(
input clk,
input rst,
output reg baud_tick
);
parameter CLK_FREQ = 50_000_000;
parameter BAUD_RATE = 115200;
localparam ACC_WIDTH = 32;
localparam INCREMENT = (BAUD_RATE << (ACC_WIDTH-16)) / (CLK_FREQ >> 16);
reg [ACC_WIDTH-1:0] phase_acc;
always @(posedge clk or posedge rst) begin
if(rst) begin
phase_acc <= 0;
baud_tick <= 0;
end else begin
{baud_tick, phase_acc} <= phase_acc + INCREMENT;
end
end
endmodule
这种设计在50MHz时钟下,115200波特率的误差仅为0.16%,远优于传统计数器方案。
为提升抗干扰能力,接收模块采用3倍过采样技术:
verilog复制module uart_rx(
input clk,
input rst,
input baud_tick,
input rx,
output reg [7:0] data,
output reg data_valid
);
// 状态机定义
localparam IDLE = 3'd0;
localparam START = 3'd1;
// ...其他状态
reg [2:0] state;
reg [2:0] sample_cnt;
reg [3:0] bit_cnt;
reg [2:0] samples;
always @(posedge clk) begin
case(state)
IDLE:
if(!rx) begin // 检测起始位
state <= START;
sample_cnt <= 0;
end
START:
if(baud_tick) begin
if(sample_cnt == 3'd4) begin
if(!samples[1]) begin // 验证起始位
state <= DATA;
bit_cnt <= 0;
end
sample_cnt <= 0;
end else begin
samples[sample_cnt] <= rx;
sample_cnt <= sample_cnt + 1;
end
end
// ...其他状态处理
endcase
end
endmodule
针对常见的SPI FLASH芯片(如W25Q128),控制器需要实现以下功能:
verilog复制module flash_ctrl(
input clk,
input rst,
input [23:0] addr,
input [7:0] wr_data,
output [7:0] rd_data,
input wr_en,
input rd_en,
output reg busy,
// SPI接口
output reg sck,
output reg cs_n,
output reg mosi,
input miso
);
// 指令定义
localparam CMD_READ = 8'h03;
localparam CMD_WRITE = 8'h02;
localparam CMD_ERASE = 8'h20;
// 状态机实现...
// 页编程时序生成
task page_program;
input [23:0] addr;
input [7:0] data;
begin
cs_n <= 0;
send_byte(CMD_WRITE);
send_byte(addr[23:16]);
send_byte(addr[15:8]);
send_byte(addr[7:0]);
send_byte(data);
cs_n <= 1;
wait_ready();
end
endtask
endmodule
为实现安全升级,采用双Bank存储架构:
verilog复制// 地址分配
localparam BANK0_BASE = 24'h000000;
localparam BANK1_BASE = 24'h080000;
localparam GOLDEN_BASE = 24'h0F0000;
采用自定义可靠传输协议:
code复制| 帧头(2B) | 长度(1B) | 命令(1B) | 数据(NB) | 校验(2B) |
| 命令码 | 功能描述 | 数据格式 |
|---|---|---|
| 0x01 | 擦除扇区 | 起始地址(3B)+扇区数(1B) |
| 0x02 | 写入数据 | 地址(3B)+数据(NB) |
| 0x03 | 读取数据 | 地址(3B)+长度(1B) |
| 0x04 | 切换Bank | Bank编号(1B) |
| 0x05 | 回滚操作 | 目标版本(1B) |
verilog复制// 状态标志定义
localparam ST_READY = 8'h00;
localparam ST_PREPARE = 8'h01;
localparam ST_WRITING = 8'h02;
localparam ST_VERIFY = 8'h03;
localparam ST_DONE = 8'hFF;
通过串口发送特定指令触发调试功能:
code复制// 读取FPGA内部温度
module temp_mon(
input clk,
input rst,
input cmd_en,
output [7:0] temp_value
);
// Xilinx芯片温度读取实现
always @(posedge clk) begin
if(cmd_en) begin
temp_value <= XADC_temp_read();
end
end
endmodule
设计状态上报机制,可实时获取:
在Xilinx Artix-7平台测试结果:
校验失败:
断电恢复:
| 错误码 | 含义 | 处理建议 |
|---|---|---|
| 0xE1 | 校验错误 | 检查串口线或降低波特率 |
| 0xE2 | FLASH超时 | 确认FLASH型号是否正确 |
| 0xE3 | 地址越界 | 检查固件大小是否匹配 |
在某工业网关项目中的实际应用:
部署情况:
实施效果:
这套方案我已经在多个项目中验证过其可靠性,特别是在恶劣工业环境下表现优异。对于想实现FPGA远程升级的开发者,建议先从简单的串口通信开始,逐步添加安全机制和异常处理功能。在实际部署前,务必进行充分的断电测试和长时间稳定性测试。