27、可靠性设计:看门狗定时器、三模冗余(TMR)、CRC校验、EDAC纠错码
做FPGA开发,尤其是工业级或航天级项目,最怕什么?
怕死机,怕数据出错,怕单粒子翻转。
我早年做一款电力保护装置,设备在强电磁干扰环境下跑着跑着就“死”了。复位按钮按下去,又活了。但现场工人不可能天天盯着它按复位。从那以后,我对可靠性设计就特别上心。
今天聊的这四个技术——看门狗、三模冗余、CRC校验、EDAC纠错码,说白了就是给FPGA上“保险”。
27.1 看门狗定时器(Watchdog Timer)
看门狗是啥?就是一个“监工”。
你的主程序每隔一段时间得跟它打个招呼:“我还活着”。如果监工发现你超过时间没打招呼,它就认为你死机了,直接给你来一个硬复位。
27.1.1 工作原理
看门狗本质上是一个递减计数器。主程序在正常工作循环中,定期“喂狗”——把计数器重新置为最大值。如果计数器减到0,说明没人喂它,就触发复位信号。
关键点:喂狗的位置要选对。不能放在中断里,否则主程序卡死了,中断还能喂狗,看门狗就形同虚设了。
27.1.2 紫光同创FPGA实现
紫光的PDS软件里,我们可以用软核实现看门狗,也可以用逻辑自己搭一个。我个人习惯用纯逻辑实现,不依赖软核,这样更可靠。
// 简单的看门狗模块
module watchdog (
input wire clk,
input wire rst_n,
input wire feed_dog, // 喂狗信号
output reg wdt_rst_n // 看门狗复位输出
);
parameter TIMEOUT_CNT = 32'd100_000_000; // 假设1秒超时(100MHz时钟)
reg [31:0] cnt;
reg timeout_flag;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 32'd0;
timeout_flag <= 1'b0;
end else if (feed_dog) begin
cnt <= 32'd0; // 喂狗,计数器清零
timeout_flag <= 1'b0;
end else if (cnt >= TIMEOUT_CNT) begin
timeout_flag <= 1'b1; // 超时
end else begin
cnt <= cnt + 1'b1;
end
end
// 产生复位脉冲
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
wdt_rst_n <= 1'b0;
else if (timeout_flag)
wdt_rst_n <= 1'b0; // 超时后拉低复位
else
wdt_rst_n <= 1'b1;
end
endmodule
我的经验:喂狗周期建议设为超时时间的1/3到1/2。比如超时1秒,每300ms喂一次。这样即使程序偶尔抖动一下,也不会误触发复位。
27.2 三模冗余(TMR)
三模冗余,英文叫Triple Modular Redundancy。名字听着高大上,原理其实很简单:
把同一个逻辑复制三份,三个模块同时跑。输出时用“三取二”表决器,取两个相同的结果作为最终输出。
27.2.1 为什么需要TMR?
在太空或高辐射环境里,高能粒子可能击中FPGA的某个寄存器,导致它的值翻转——这就是单粒子翻转(SEU)。
你想想看,一个关键的状态机,如果某个bit被翻转了,可能直接从“空闲”状态跳到“故障”状态。后果不堪设想。
TMR就是对付这个的。三个模块同时被击中的概率极低,所以只要两个模块结果一致,我们就相信它是正确的。
27.2.2 表决器实现
// 三取二表决器
module voter (
input wire a, b, c,
output wire y
);
assign y = (a & b) | (b & c) | (a & c);
endmodule
这个电路很简单,但很实用。三个输入里,只要任意两个是1,输出就是1。
注意:TMR不是万能的。它只能对付单点故障。如果三个模块的时钟树或电源网络是共享的,那一个电源故障就能干掉所有三份逻辑。所以,真正的TMR设计,时钟、电源、复位都要做冗余。
27.2.3 什么时候用TMR?
- 状态机:关键状态机建议做TMR,比如通信协议的状态机
- 配置寄存器:控制关键参数的寄存器
- 计数器:长时间运行的计数器,比如时间戳
我做过一个卫星通信项目,里面的所有关键状态机都做了TMR。说实话,资源消耗翻了三倍不止,但为了可靠性,值。
27.3 CRC校验
CRC(Cyclic Redundancy Check)是数据通信里最常用的校验方式。它不像奇偶校验那么简单,也不像MD5那么复杂,属于“刚刚好”的那种。
27.3.1 CRC原理简述
发送端把数据看作一个多项式,除以一个约定的生成多项式,余数就是CRC校验码。接收端用同样的方式计算,如果余数为0,说明数据没出错。
说白了,就是做多项式除法。只不过在硬件里,我们用移位寄存器和异或门来实现。
27.3.2 紫光FPGA上的CRC实现
紫光的PDS里提供了CRC硬核,可以直接调用。但如果你需要自定义多项式,也可以用逻辑实现。
// CRC-16-CCITT 实现 (多项式 x^16 + x^12 + x^5 + 1)
module crc16_ccitt (
input wire clk,
input wire rst_n,
input wire data_in,
input wire data_valid,
output reg [15:0] crc_out
);
reg [15:0] crc_reg;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
crc_reg <= 16'hFFFF; // 初始值
else if (data_valid) begin
crc_reg[0] <= crc_reg[15] ^ data_in;
crc_reg[1] <= crc_reg[0];
crc_reg[2] <= crc_reg[1];
crc_reg[3] <= crc_reg[2];
crc_reg[4] <= crc_reg[3];
crc_reg[5] <= crc_reg[4] ^ crc_reg[15] ^ data_in;
crc_reg[6] <= crc_reg[5];
crc_reg[7] <= crc_reg[6];
crc_reg[8] <= crc_reg[7];
crc_reg[9] <= crc_reg[8];
crc_reg[10] <= crc_reg[9];
crc_reg[11] <= crc_reg[10];
crc_reg[12] <= crc_reg[11] ^ crc_reg[15] ^ data_in;
crc_reg[13] <= crc_reg[12];
crc_reg[14] <= crc_reg[13];
crc_reg[15] <= crc_reg[14];
end
end
assign crc_out = crc_reg;
endmodule
避坑指南:我曾经在一个项目里,CRC计算一直不对。查了半天,发现是字节序搞反了。发送端是MSB first,接收端用了LSB first。记住,通信双方的多项式、初始值、输出异或值、字节序,这四个参数必须完全一致。
27.4 EDAC纠错码
EDAC(Error Detection And Correction)比CRC更进一步。CRC只能发现错误,EDAC还能纠正错误。
最常用的是汉明码(Hamming Code)。它能纠正1位错误,检测2位错误。
27.4.1 汉明码原理
汉明码在数据位之间插入校验位。校验位的位置是2的幂次方(第1、2、4、8...位)。每个校验位负责校验一组数据位。
当数据出错时,通过校验位的组合,可以定位到具体是哪一位错了,然后取反纠正。
| 数据位宽 | 校验位数量 | 总位宽 | 纠错能力 |
|---|---|---|---|
| 4 | 3 | 7 | 纠正1位 |
| 8 | 4 | 12 | 纠正1位 |
| 16 | 5 | 21 | 纠正1位 |
| 32 | 6 | 38 | 纠正1位 |
27.4.2 紫光FPGA中的EDAC应用
紫光的某些系列FPGA,内部BRAM自带EDAC功能。你只需要在PDS里勾选一个选项,硬件自动帮你做纠错。
但如果你用的是不带EDAC的BRAM,或者你想在自定义逻辑里用,那就得自己实现了。
// 简化的汉明码编码(8位数据 -> 12位码字)
// 校验位位置: p1(bit1), p2(bit2), p4(bit4), p8(bit8)
module hamming_encoder (
input wire [7:0] data_in,
output wire [11:0] code_out
);
wire p1, p2, p4, p8;
// 计算校验位
assign p1 = data_in[0] ^ data_in[1] ^ data_in[3] ^ data_in[4] ^ data_in[6];
assign p2 = data_in[0] ^ data_in[2] ^ data_in[3] ^ data_in[5] ^ data_in[6];
assign p4 = data_in[1] ^ data_in[2] ^ data_in[3] ^ data_in[7];
assign p8 = data_in[4] ^ data_in[5] ^ data_in[6] ^ data_in[7];
// 组合码字
assign code_out = {data_in[7:4], p8, data_in[3:1], p4, data_in[0], p2, p1};
endmodule
重要提醒:EDAC不是免费的午餐。它需要额外的存储空间和延迟。每32位数据要额外6位校验位,存储效率约84%。而且编码和解码都会引入几个时钟周期的延迟。
27.5 如何选择?
这四个技术不是互斥的,实际项目中往往是组合使用:
- 看门狗:防止程序跑飞,所有系统必备
- TMR:对付单粒子翻转,航天、核工业必备
- CRC:通信链路校验,几乎所有接口都要用
- EDAC:保护存储数据,BRAM或DDR内存必备
我做过一个综合项目:主控状态机用TMR,内部BRAM开EDAC,通信链路加CRC,最外层再套一个看门狗。四层防护,设备在高温高湿的工业现场跑了三年没出过一次故障。
嗯,可靠性设计就是这样——平时看着多余,出问题的时候才知道它的价值。