☰
FPGA实现ARM双核锁步DCLS Lockstep:从原理到工程落地
2026/10/8 15:16:10 网站建设 项目流程

做功能安全相关开发的朋友,对“ARM双核锁步DCLS Lockstep”这个词应该不陌生。锁步(Lockstep)听起来像是在说CPU走正步,其实本质就是让两个处理器核心以完全相同的节奏执行同一条指令流,然后在关键节点对拍检查,一旦发现两边不一致,立刻拉高错误标志。这套机制在汽车电子、轨道交通、工业控制这些对可靠性要求极高的场景里,几乎是标配中的标配。

问题在于,真实芯片里的锁步方案(比如Cortex-R系列硬核)是厂家做好的黑盒,你只能通过寄存器配置来打开或关闭,看不到内部实现细节,更没法针对自己的项目做定制修改。这时候FPGA的价值就出来了:你完全可以在FPGA里自己搭一套双核锁步验证平台,把比较器、时钟管理、故障注入这些关键环节全部摊开来看,想怎么debug就怎么debug。

这篇文章我就以“FPGA实现ARM双核锁步DCLS Lockstep”为主线,把从原理到落地、从架构设计到实际调试的内容掰开揉碎讲一遍。适合正在做功能安全原型验证的同学,也适合对CPU冗余设计感兴趣、想搞清楚锁步到底怎么工作的FPGA工程师。内容偏工程实践,看完可以直接拿去搭自己的验证环境。

1. 先搞清楚DCLS Lockstep到底在解决什么问题

1.1 锁步不是“双备份”,而是“双份执行、周期级对拍”

很多人一听双核锁步,第一反应是“哦,是不是就像服务器里的双机热备,一个挂了另一个顶上”。这个理解方向对了一半,但机制完全不同。双机热备是检测到故障后切换,中间有切换时间,而且主备之间不是同步运行的;而锁步是两边的执行进度严格对齐,每个时钟周期都在做一致性的校验。

DCLS的全称是Dual Core Lock-Step,双核锁步。它的工作方式可以简化成这样一个模型:两个处理器核(Core0和Core1)从同一个复位信号释放,吃同一个时钟源,执行同一份代码,访问同一片地址空间。在任意一个时钟周期里,两个核的指令执行轨迹都应该完全一致。既然轨迹一致,那么它们对外发出的总线请求、写数据的值、地址信号、控制信号,自然也应该逐位一致。

比较器就挂在两个核的“输出边界”上,实时对拍这些信号。如果某个周期发现Core0和Core1的地址线、数据线或者控制信号有任意一位不相等,就直接判定系统出现故障,拉高错误输出。这就是DCLS的核心思想:不是“错了之后再恢复”,而是“错的那一拍就抓出来”。

这里要特别强调一个误解:Lockstep严格来说是个“故障检测”机制,不是“故障容错”机制。它能在错误发生的当拍或几拍之内发现异常,但它本身不能保证系统继续正确运行。检测到错误之后如何处理——是重启整个系统、切换到一个安全状态,还是触发看门狗复位——那是安全机制里外层干的事。所以设计DCLS的时候,不能只盯着比较器本身,还得想清楚错误上报之后的响应路径。

1.2 锁步的粒度选在哪个层级,直接决定成本和效果

既然要对拍,就涉及到“对拍什么”的问题。锁步的粒度有几档选择,从细到粗大致是:

指令级比较是最严格的方案,CPU内部每执行完一条指令,就把指令编码、通用寄存器结果、标志位全部送到比较器做比对。这种方案检测能力最强,任何一个逻辑错误都逃不掉,但实现代价也最高。因为比较点深入到处理器内核内部,占用的比较位宽巨大,而且比较逻辑会成为关键路径上的瓶颈,严重影响主频。更重要的是,这需要你能拿到处理器核内部的执行状态,如果是外购IP核或者ARM硬核,根本不给你这个接口。

总线级比较是工程上最常见的折中方案。比较点放在处理器核的对外总线接口上,比较地址总线、写数据总线、读数据总线的返回数据、读写控制信号等。这种方案不需要修改处理器核内部结构,对IP核的侵入性几乎为零。只要这两个核在做同样的操作,它们发出来的总线事务就必须完全一致,所以通过对比总线信号就能覆盖绝大多数单点故障。代价是如果故障发生在CPU内部但没体现在这一拍的总线上,可能要过几个周期才会暴露,实时性比指令级略差。

存储级比较是粒度最粗的一档,只对最终写入存储器的数据做校验。这种方案实现最简单,但检测延迟很大,而且对CPU内部寄存器、流水线的故障没有感知能力。真正常见的错误场景,比如ALU计算结果错了但写存储的时候恰好没用到这个错值,存储级比较就完全抓不到。

我做这个FPGA项目时选的是总线级比较,具体落在AXI总线的写地址通道、写数据通道和读数据通道上。原因很简单:总线信号数目可控,FPGA资源够用;覆盖范围足够覆盖大部分真实故障场景;而且方案可以迁移到任何带标准总线接口的软核处理器上,不管你是用MicroBlaze、Nios II还是RISC-V软核。

1.3 DCLS跟TMR、双核互检这些方案比,优势劣势在哪

做冗余设计,方案不止DCLS一种。不少做可靠性设计的同学会纠结,到底是做三模冗余(TMR)还是双核锁步,还是简单的双核软件自检。这里我列个对比,方便大家选型的时候心里有数。

方案冗余度故障检测能力纠错能力资源开销典型应用
DCLS双核锁步2核强(周期级检测)无,只能检测约2倍逻辑+比较器汽车MCU、工业控制器
TMR三模冗余3核强有,可以多数表决纠错约3倍逻辑+表决器航天电子、高可靠存储
双核互检(软件)2核中(软件周期检查)无低,复用已有核消费级、低成本的可靠性增强

从开销角度看,DCLS只比单核多复制一份CPU逻辑,外加一个比较器,面积大约是单核的2.1到2.3倍。TMR虽然是1.3倍往上的纠错能力看着很香,但面积是3倍起步,功耗也跟着涨,在很多对成本和功耗敏感的工业场景里根本吃不消。

TMR的优势是表决机制天然能“容忍”单次故障,系统可以继续运行;DCLS的优势是检测延迟低、面积小。两者不是替代关系,实际工程里还有组合用法,比如CPU子系统用DCLS保证错误快速被发现,外部的安全核再用软件手段做恢复策略。

软件层的双核互检就便宜很多了。两个核跑不同的代码,各自定期通过共享内存交换心跳和校验值,发现对方异常就上报。但这种方案检测延迟是毫秒级的,而且依赖软件正确执行——如果故障直接把核的PC指针跑飞了,软件检查代码本身可能压根就没有执行机会。所以它只能作为低成本辅助手段,不能替代硬件锁步。

2. 在FPGA里实现DCLS Lockstep的整体架构设计

2.1 双核复制、比较器、时钟管理,三个部分缺一不可

搞清楚了原理,再看怎么落地到FPGA。我采用的顶层架构分成三大块:双核处理器复制区、锁步比较器、时钟和复位管理单元。

双核处理器复制区很好理解,就是把一个处理器软核例化两份。这里的关键在于“复制”的程度。如果你用的是MicroBlaze这样的软核,直接例化两个相同的IP实例就行,两个核的配置参数必须完全一致,包括缓存大小、总线接口宽度、中断控制器配置等等。任何一处的配置差异都可能导致两个核行为不一致,让比较器瞬间误报。如果你用的是第三方RISC-V软核,道理一样,两个实例的参数必须逐字相同。

时钟和复位管理单元是整个锁步机制的“地基”。两个核必须共享同一个时钟源、同一套复位释放逻辑。这句话说起来容易,实际做的时候坑很深。如果两个核的时钟不是同一个BUFG扇出,而是各自过了不同的MMCM/PLL,那么它们之间的时钟偏斜可能会达到几百皮秒到几纳秒,而你的总线信号在这样的偏斜下必然会出现周期对齐误差,比较器就会产生大量误报。所以我的设计里是两个核共享同一个时钟网络,通过一个全局BUFG驱动到两边,比较器则用同一个时钟沿采样两边的信号。

锁步比较器是整个系统的核心判决部件。它位于两个核的总线输出端,实时采样的信号经过打拍对齐后做逐位比较,任何一位不一致就触发错误标志。比较器本身需要有错误锁存功能,也就是一旦检测到不匹配,错误寄存器保持拉高,直到外部安全逻辑主动清除。这是为了保证错误事件不会因为时序巧合被漏掉。

2.2 数据流视角下,锁步系统是怎么工作的

从数据流角度看,锁步系统的工作时序大致是这样:

复位释放之后,两个核同时从复位向量取指,开始执行同样的程序。因为两个核的存储器是共享的(或者至少映射到同一份存储器镜像),它们在同一拍发起的取指请求访问相同的地址,从存储器读回相同的数据,然后计算得到相同的结果。当其中任何一个核需要发起总线写操作时,写地址、写数据、写控制信号同时出现在两套总线上,比较器在下一拍对这些信号采样比对。如果一致,这次写事务就正常放行;如果不一致,比较器立即拉高错误标志,同时可以可选地抑制这次写操作,防止错误数据污染系统状态。

读事务的处理要稍微复杂一些。因为两个核共享存储器,读数据总线是双向的,比较器需要在读数据返回路径上也对齐一次。也就是说,比较器不仅要比对两个核“发出去”的地址和控制信号,还要比对存储器“返回给两个核”的数据是否一致。如果存储器端返回给Core0和Core1的数据不一致——这可能是存储器接口本身出了问题——那也得立刻报警。

我在这块实现的时候,是把比较器的采样点划分成了两组:一组叫上游比较点,监视两个核对外的请求信号;另一组叫下游比较点,监视存储器返回给两个核的响应信号。两组比较点分别打拍对齐后汇入同一个判决逻辑。

2.3 为什么比较点选在总线级就够了,而不追求核内全比较

很多第一次做锁步的同学都会问:既然要保证绝对可靠,为什么不把比较点深入到CPU的每个流水线级,把所有内部信号都拉出来比对?

想法很好,但工程上行不通。第一,拿不到核内信号。商用软核IP给你提供的调试接口通常只是AXI总线级的,核内部的流水线级信号一般不会开放。除非你自己写一个处理器RTL,否则连“全比较”的物理前提都不具备。第二,即便拿得到,比较位宽会爆炸。一个32位RISC-V核内部流水线相关的信号轻松超过上千根,把这上千根信号同时接入比较器,光布线就会把时序拖垮,主频会掉得非常难看。第三,有些内部信号是动态的、只在一个周期有效的,拿来做跨周期比对需要额外的同步机制,复杂度成倍上升。

所以工程上普遍接受的方案就是总线级比较。ARM自家的Cortex-R系列处理器做锁步时,比较点也是选在核心边界的总线上,而不是每个流水级。总线信号已经足够丰富——地址、数据、控制、响应——只要两个核执行轨迹一致,这些信号就必须一致,反之如果任何一个功能模块出问题导致了结果偏差,它最终都要反映到总线事务上。这就够了。

3. 核心环节实操:比较器与时钟管理怎么落地

3.1 时钟偏斜是锁步的头号杀手,先解决同源和相位问题

我现在明确告诉你,在FPGA里做双核锁步,十个误报里至少八个跟时钟偏斜有关,而不是真的发生了故障。所以第一步不是写比较器,而是把时钟方案定死。

我的推荐做法是:所有锁步相关逻辑(两个核、比较器、错误处理)都用同一个时钟域,这个时钟由一个MMCM/PLL产生后,经过一个全局时钟缓冲器BUFG扇出到所有模块。两个处理器核的时钟端口直接连这个BUFG的输出,不要在中间再插任何时钟分频器或者门控逻辑。

实际操作中有一个很容易踩的坑:为了调试方便,有人会在两个核的时钟路径上各自加一个时钟门控单元,想实现“单独停掉某个核的时钟”来模拟故障。结果时钟门控在关断和打开瞬间会产生毛刺,毛刺一进时钟树,两个核的触发沿瞬间错位,比较器就开始疯狂报错。正确的做法是,故障注入不要通过动时钟来实现,后面我会讲更可控的故障注入方法。

复位也有讲究。两个核必须使用同步释放的复位信号,而且这个复位信号也要走全局资源(比如全局复位网络),保证两个核在同一个周期退出复位。异步复位同步释放电路是必须的,不能把外部按钮的异步复位直接接进两个核的复位端。如果复位释放差了一个周期,一个核已经跑起来取指了,另一个还停在复位态,比较器会在启动那一拍立刻拉高错误。

我在仿真阶段就吃过这个亏。一开始图省事,复位信号直接用testbench里的异步复位,结果仿真波形里两个核的启动就差了一个周期,比较器在第一笔总线事务就报警。后来改成统一的复位同步释放模块,用同一个BUFG驱动的复位信号,问题才消失。

3.2 比较器设计要点:打拍对齐、错误锁存、安全状态输出

比较器本身的逻辑不复杂,难的是对齐和时机控制。两个核的总线信号在各自逻辑路径上经过的寄存器数量可能略有不同,所以同一拍总线事务到达比较器输入的时间会有细微差异。设计比较器时必须先把两路信号各自打几拍,确保比较的是“同一拍”的状态,而不是一个核的当前拍对另一个核的上一拍。

我用的比较器接口大概是这样的:

module lockstep_comparator #( parameter ADDR_W = 32, parameter DATA_W = 32, parameter CTRL_W = 8 )( input wire clk, input wire rst_n, // Core0 bus observation bus input wire [ADDR_W-1:0] c0_addr, input wire [DATA_W-1:0] c0_wdata, input wire [CTRL_W-1:0] c0_ctrl, // Core1 bus observation bus input wire [ADDR_W-1:0] c1_addr, input wire [DATA_W-1:0] c1_wdata, input wire [CTRL_W-1:0] c1_ctrl, // valid enable from bus transaction input wire cmp_valid, // output status output reg err_flag, output reg [7:0] err_cnt, output reg [31:0] err_info_addr ); wire [ADDR_W+DATA_W+CTRL_W-1:0] c0_all = {c0_addr, c0_wdata, c0_ctrl}; wire [ADDR_W+DATA_W+CTRL_W-1:0] c1_all = {c1_addr, c1_wdata, c1_ctrl}; reg [ADDR_W+DATA_W+CTRL_W-1:0] c0_q, c1_q; // stage alignment: collect same-cycle signals // wrong to compare c0_all and c1_all directly at this module port always @(posedge clk or negedge rst_n) begin if (!rst_n) begin c0_q <= 0; c1_q <= 0; end else if (cmp_valid) begin c0_q <= c0_all; c1_q <= c1_all; end end always @(posedge clk or negedge rst_n) begin if (!rst_n) begin err_flag <= 1'b0; err_cnt <= 8'h00; err_info_addr <= 32'h0; end else if (cmp_valid) begin if (c0_q != c1_q) begin err_flag <= 1'b1; err_cnt <= err_cnt + 1'b1; err_info_addr <= c0_q[ADDR_W+DATA_W+CTRL_W-1 -: ADDR_W]; end end end endmodule

这里有个容易被忽略的细节。比较器的输入不能直接拿两个核的原始总线信号做组合逻辑比较,因为两个信号的到达时间几乎不可能完全一样。组合比较一旦出现数据在窗口内变化的毛刺,输出就会抖动,可能产生一拍虚假的“不相等”。所以我在模块入口处先寄存再比较,所有输入信号在同一拍锁存,下一拍再对锁存后的值做比较。这样虽然多了两级延迟,但换来了比较结果的确定性,这个代价完全值得。

3.3 双核初始化与进入锁步的启动流程

两个核同时从复位释放,就一定能自动进入锁步吗?答案是能进入,但需要一些谨慎的处理逻辑。

启动阶段的一个重要问题是存储器初始化。如果两个核共享一份BRAM,上电后BRAM内容默认是0,两边取指都是0x00000000,那没问题。但如果你的系统里有一块ROM或者Flash用来放启动代码,而两个核访问这份启动代码的路径不一样(比如一个走AXI全互连,另一个走了简化的直连通道),那启动代码返回的数据和时序就可能不一致。解决办法是尽量让两个核对存储器的访问路径完全对称,走同一个互连管线。

还有一类启动问题是中断。两个核必须使用完全相同的中断配置,并且在锁步建立之前不要使能任何中断。如果Core0比Core1多响应了一次中断,两边执行流立刻分叉,比较器马上报警,而且这种错误具有传染性,后边永远追不回来。我的做法是启动阶段先屏蔽所有中断源,等两个核运行到“锁步握手点”(也就是两边都执行到一个约定好的同步标记)之后再统一使能中断。

握手流程可以这样设计:两个核各自执行一段自检代码,完成后往各自专用的状态寄存器写一个固定值“0xA5A5”,然后进入一个等待循环。外部安全状态机轮询这两个寄存器,发现都是0xA5A5之后,置位“锁步有效”信号,通知比较器开始工作。在锁步有效信号拉高之前,比较器处于旁路状态,不做判决。这样就把“启动过程中的正常差异”和“运行过程中的真实故障”干净地切开了。

4. 工程化落地:资源占用、时序收敛与验证方案

4.1 不同实现路线的资源开销对比

选FPGA型号之前,先大概估算一下双核锁步需要多少资源。以我用的32位RISC-V软核为例,单个核大约消耗3000到5000个LUT(具体取决于配置),加上本地中断控制器、总线接口逻辑,保守按单个核5000个LUT算,双核就是10000个LUT。比较器的位宽如果按总线信号全量算(地址32位、写数据32位、控制8位,读写各一套),大约需要几百个LUT,几乎可以忽略。再加上时钟管理和错误处理模块,整个系统的LUT开销大约在11000到13000之间。

模块预估LUT备注
Core0(32位RISC-V软核)4500~5500含总线接口、中断控制器
Core1(与Core0同配置)4500~5500两核完全对称
锁步比较器300~500打拍寄存器+比较逻辑
时钟/复位管理100~200BUFG、复位同步释放
故障注入调试接口500~800可选,建议保留

这个量级的资源,在Artix-7级别的FPGA(比如XC7A35T就有约20000个LUT)上已经可以跑得很舒服。如果你用的是带ARM硬核的Zynq系列,可以在PL端用软核搭锁步验证系统,PS端ARM跑监控和上层应用,这样还能顺便验证异构环境下的锁步集成。

选型建议就一句话:别为了省资源把两个核的缓存或者总线配置缩水,锁步系统里两个核的配置必须完整且完全对称,否则后面调试误报的时候你会怀疑人生的。

4.2 比较器的时序收敛经验

时序收敛是FPGA实现里最让人头疼的环节。双核锁步系统的关键路径往往不在CPU本身,而在比较器。

比较器的输入信号来自两个核的总线输出,这两路信号在布局布线之后到达比较器的路径延迟不可能完全一致。如果比较器还在同一拍做组合比较,建立时间裕量会被两路信号的偏斜吃掉,时序很容易跑不过。我的处理办法是把比较逻辑多打一拍:第一拍锁存两路信号,第二拍做比较,第三拍锁存错误状态。每增加一级流水,关键路径上就能松出大约1到2纳秒的余量,换来的是两三个周期的检测延迟,对于锁步场景完全可接受。

还有一类约束要主动设置。因为比较器比较的是同源数据,两个核的双份总线从逻辑上是冗余的,工具默认会认为它们之间没有逻辑依赖,可能会为了布线方便把这两路信号走到相隔很远的位置,导致偏斜加大。这需要手动加约束,把两个核的观察总线尽量约束在同一个时钟区域内。Xilinx系列的Pblock约束或者Altera系列的逻辑锁定都可以用来做这件事,实操下来对时序收敛帮助很明显。

4.3 故障注入验证:让系统“假装出故障”,再验证它真的能抓住

锁步系统做出来不是拿来跑hello world的,必须验证它真的能在故障发生时抓住错误。这就需要一个可控的故障注入机制。

我最推荐的故障注入方式是在总线观察信号上做多路选择。每个比较器的输入信号前面加一个选择器,正常模式下直通原始信号,故障模式下选择器输出人为翻转后的信号。这个选择器的控制端暴露成调试寄存器,通过串口或者JTAG访问。注入故障时,往调试寄存器写一个值,选择器就在指定拍把某个数据位强制取反。这样控制精确、可重复,而且不影响两个核本身的真实运行。

仿真阶段的故障注入更简单。直接用force命令在总线信号上强制赋值,观察比较器是否在预期周期拉高了err_flag。我在做功能验证时,会对以下场景分别做回归测试:

  • 写地址总线单bit翻转:Core0地址线第7位取反,期待比较器报警;
  • 写数据总线单bit翻转:Core1数据线第15位取反,期待比较器报警;
  • 控制信号翻转:将两个核的写使能置为不一致,期待比较器报警;
  • 存储返回数据不一致:模拟存储接口给两个核返回不同数据,期待比较器报警;
  • 两核同时位翻转且翻转位相同:这种“共模故障”比较器理论上发现不了,需要靠外层多样化设计兜底。

这里引出一个很关键但很多人会忽略的问题:如果两个核发生了完全相同的故障,比较器会认为“两边一致”,故障就溜过去了。真实ASIC里的锁步为了对抗这种共模故障,会引入多样化的执行方式,比如一个核比另一个核延迟两个周期,资料里叫延迟锁步。延迟锁步的好处是,同一时刻两个核处于不同的指令上下文,同一个物理扰动很难让两个核在各自的上下文里犯下完全相同的错误。FPGA原型验证里我也建议加上这个延迟特性,实现就是让其中一个核的总线信号多打两拍再进比较器,成本很低,但有效性提升明显。

5. 常见问题与调试记录

5.1 双核“时间上不同步”怎么排查

现象是上板之后,上电或者复位之后很快就报了err_flag,而且每次报错的位置都不固定。这是典型的双核没有真正同步启动的表现。

排查步骤我一般按这个顺序来:

  1. 确认两个核的复位信号是否来自同一个复位同步释放模块,用逻辑分析仪抓两个核的复位端口,确认释放沿在同一拍。
  2. 确认两个核的时钟是否同源,看时钟树报告,两个核的时钟路径必须从同一个BUFG扇出。
  3. 确认两个核的启动代码入口地址是否一致,不一致的话一个核跑A程序一个核跑B程序,不报警才怪。
  4. 确认两个核的中断是否完全关闭,尤其注意外部中断引脚在上电瞬间的随机电平,是否被误判为有效请求。

另一个我遇到过的隐蔽情况是BRAM初始化文件的差异。如果Core0和Core1各挂了独立的指令存储器,而两份存储器初始化文件因为编译过程的某种原因出现了微小差异(比如多了一行数据定义),两个核执行的指令流就会在某个点开始分叉。这种分叉后的锁步系统会在错误位置报警,但错误跟随机故障没关系,纯粹是配置不一致。

5.2 比较器误报率过高,先怀疑同步设计而非实际故障

如果系统运行一段时间后频繁误报,但用仿真跑同样的程序又完全正常,问题极大概率出在FPGA物理实现层面。

首先检查时钟约束。确认create_clock是否只定义了一个主时钟,所有相关时钟域关系是否被正确识别。比较器跨时钟域处理不当、异步信号没同步就打拍,会导致采样窗口内的亚稳态,产生随机误报。

其次,检查布线报告里比较器相关路径的偏斜值。正常情况下同一时钟区域内的偏斜在几十皮秒量级,如果查出来偏斜达到了几百皮秒甚至纳秒级,说明布线把两个核的输出路径拉得太远,需要用区域约束把它们绑近。

最后,对照有没有编译器自动做了“不期望的优化”。比如综合工具认为某个信号恒定为0,就把逻辑优化掉;但你让它做比较的两个信号恰好被工具判定为“同源恒等”,比较器就被优化成了永久不报警的空壳。这个问题在综合报告里会体现为“逻辑已被简化”,需要检查综合日志,必要时给比较器模块加(* keep = "true" *)属性防止被合并。

5.3 故障注入之后系统“卡死”而不是进入安全状态

Fault注入后如果系统只是死在那里,说明错误检测链路本身没问题,但安全响应机制没有闭环。锁步比较器拉高err_flag之后,外部安全状态机应该立即响应,比如生成复位请求、封锁总线写使能、切换LED状态。

我在第一次验证时就犯了这个错:比较器报了错,但总线写操作已经完成了,错误数据已经写进了存储器。后来加了“错误抑制”逻辑:一旦err_flag拉高,立即把两套总线输出的写使能信号强制拉低,让后续的错误数据无法进入存储器。同时安全状态机接管系统,进入预定义的安全停机状态,不再执行正常程序。功能安全设计里这个状态往往叫“安全状态”,具体表现因项目而异,但前提是必须有一个明确的响应路径。

5.4 问题速查表

现象可能原因排查动作
上电即报错复位不同步/启动代码入口不一致抓复位沿,比对启动PC
偶发误报时钟偏斜过大/比较器时序紧张检查时钟树报告,优化布线
运行一段时间后死机存储器内容被意外改写确认错误抑制逻辑是否生效
故障注入无反应比较器被综合工具优化添加保持属性,重新综合
两个核寄存器状态不一致但比较器不报共模故障引入延迟锁步机制
仿真正常但上板异常时序约束缺失补充完整时序约束

调试的过程其实也是加深对锁步理解的过程。每一次误报都在告诉你,你设计里的哪个假设没有被满足——可能是“两个核的时钟完全同源”这个假设,可能是“总线信号到达时间完全一致”这个假设。把假设一个个验证清楚,锁步系统也就稳定了。

最后再多说两句

这次做完这个FPGA双核锁步原型,我最大的一收获不是把比较器跑通了,而是彻底理解了为什么真实芯片里的锁步方案会设计成那样。那些看起来“多余”的寄存器打拍、延迟锁步、错误封锁逻辑,每一个都是对应着一个真实世界的故障模型。如果你也想搭一套类似的验证平台,我的建议是从最简单的总线级比较开始,先把时钟、复位这些地基打稳,再逐步加进故障注入、延迟锁步、安全状态机这些高级特性。别一上来就追求全面覆盖,能从稳定的双核执行跳到稳定报错,就已经成功了一大半。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询