Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

3.2 锁步核与冗余——两个肾脏的工程实现

EMC实验室里,你的转向ECU正在接受ISO 11452-2规定的BCI(大电流注入)测试。测试工程师把电流探头夹在CAN总线线束上,注入200mA的150MHz射频干扰。频谱分析仪上的谐波像心跳一样跳动。你盯着Vector CANoe的trace窗口,一切正常。

然后工程师喊了一声:“来了。”

他按下了测试脚本上的一个按钮。那是一束从范德格拉夫起电机引出的1MeV α粒子流,穿过3mm的铝制屏蔽罩,精确瞄准了PCB上裸露的Cortex-R5封装上方。你知道接下来会发生什么:α粒子击中芯片内部的某个存储节点或组合逻辑路径,沉积的能量足以翻转一个逻辑电平。

在示波器触发的一瞬间,你看到了两件事几乎同时发生:

  • SMU(Safety Management Unit)的故障输出引脚从高电平变为低电平,上升沿到下降沿的间隔是7.3纳秒
  • CAN总线上多了一条3E故障帧,从故障发生到故障帧出现在CAN总线上,全程不到50微秒

你深吸一口气,调出SMU内部记录。告警源ID:0x0B(“Lockstep Comparator Mismatch”)。核心0说结果寄存器是0x0000A43F,核心1说结果寄存器是0x0000A42F。BIT[4]翻转了。α粒子击中了核心1的ALU第四位加法器。但整车没有任何功能降级,因为SMU在检测到锁步失配的5个时钟周期内就触发了故障响应。

你喝了一口咖啡。一次可能致命的随机硬件故障,在你的系统中存活了不到50微秒。

从Linus Pauling到双核锁步:冗余的数学根基

1949年,Linus Pauling在《Science》上发表了一篇改变医学史的文章:“Sickle Cell Anemia, a Molecular Disease”。他的核心发现是:镰刀型红细胞贫血症患者在血红蛋白分子上有一个氨基酸替换:谷氨酸变成了缬氨酸。这个替换导致血红蛋白在低氧条件下聚合,使红细胞变形。但最有趣的是,这个病只在纯合子(两个等位基因都突变)身上表现,杂合子(一个正常基因一个突变基因)几乎完全正常,而且还获得了对疟疾的部分抵抗力。

为什么?因为一个健康的β-珠蛋白基因产生的正常血红蛋白,足以在功能上补偿那个突变基因的缺陷。一个坏了,另一个顶上。这就是生物冗余的数学本质:通过复制关键组件,将单点故障转化为可容忍的降级状态。

在电子系统中,冗余是达到ASIL D(单点故障度量SPFM≥99%、潜在故障度量LFM≥90%)的必要手段。没有任何单个芯片能在没有冗余的情况下达到ASIL D的随机硬件故障度量目标。这不仅是工程实践的要求,更是ISO 26262-5:2018第8章和11章的数学必然。以一个典型的MCU故障率λ=100 FIT(每十亿小时故障100次)为例,如果不加冗余,单点故障度量会远低于ASIL B要求的90%,更不用说ASIL D的99%。

但冗余有两种截然不同的实现路径:计算冗余和功能冗余。锁步核实现了前者:用两颗相同的CPU做相同的计算,硬件级比对每一个结果。双传感器实现了后者,用一个独立传感器组件验证另一个的测量值。它们互补但无法互相替代。

Cortex-R5锁步:逐拍比对的艺术

ARM Cortex-R5是汽车领域最广泛部署的锁步架构之一,尤其常见于TI TMS570、NXP S32R、以及Xilinx Zynq UltraScale+ MPSoC的RPU(Realtime Processing Unit)。Cortex-R5的锁步模式不只是一颗CPU加一个“检查器“,它涉及整个处理器微架构的复制。

在锁步模式下,Cortex-R5内部有两套完全相同的处理器核心:从指令预取、指令译码、寄存器文件、ALU、乘法器、除法器、Load/Store单元,到AXI总线接口,全部是双份。这两份被称为“主核心“(Main Core)和“检查器核心“(Checker Core)。主核心正常执行所有操作,驱动总线和外部接口。检查器核心接收与主核心完全相同的输入(时钟、复位、中断、AXI读数据),但它不驱动任何外部总线,它的输出只被送到一个叫CCU(Compare and Checker Unit)的硬件单元。

CCU做的事情极其简单但极其苛刻:它在每一个时钟周期内,将检查器核心的所有输出信号与主核心的对应信号做异或比较。任何不一致的电平,立即锁存为错误标志。

  Cortex-R5 锁步架构简化框图:

  时钟 ──────┬────────────────────────────┐
             │                            │
             ▼                            ▼
     ┌───────────────┐          ┌───────────────┐
     │  Main Core    │          │ Checker Core  │
     │               │          │               │
     │ Fetch ▸ Decode │          │ Fetch ▸ Decode │
     │ RegFile ▸ ALU │          │ RegFile ▸ ALU │
     │ LSU ▸ AXI IF  │          │ LSU ▸ AXI IF  │
     └───────┬───────┘          └───────┬───────┘
             │      输出信号             │      内部信号
             │   ┌──────────────────┐   │
             │   │   CCU 比较器     │   │
             │   │                  │   │
             │   │  Main[i] XOR     │   │
             │   │  Checker[i]      │   │
             │   │       │          │   │
             │   │  任何≠0 → 错误锁存│   │
             │   └────────┬─────────┘   │
             │            │             │
             │            ▼             │
             │     SMU 告警输入         │
             │            │             │
             ▼            ▼             ▼
           AXI 总线    中断控制器     TCM
          (仅主核心驱动)

CCU比较的信号范围在Cortex-R5中是可以通过系统寄存器配置的。默认情况下,以下信号参与比较:

  • 所有AXI写地址通道信号
  • 所有AXI写数据通道信号
  • 所有AXI读地址通道信号
  • 内部寄存器文件写使能和写数据
  • TCM接口的所有写数据信号

注意一个关键的设计选择:CCU不比较AXI读数据、中断输入和复位信号,因为检查器核心和主核心共享这些输入,它们本来就是相同的。CCU只比较那些由核心自身生成且应该相同的信号。如果两个核心收到了相同的中断、相同的数据,但生成了不同的计算结果。那就是核心内部的问题。

在Cortex-R5的CP15系统控制协处理器中,锁步模式通过SCTLR(System Control Register)的一个bit来使能。配置代码通常运行在Boot ROM中,在跳转到用户应用之前完成:

/* Cortex-R5 锁步错误响应配置(位于 Boot ROM 中) */
void Enable_Lockstep(void)
{
    uint32 actlr_val;

    /* 锁步模式由芯片设计阶段的硬件配置决定(综合时tie-off)
     * 软件无法在运行时禁用锁步。但软件可以通过ACTLR配置错误响应行为: */
    asm volatile("MRC p15, 0, %0, c1, c0, 1" : "=r"(actlr_val));
    actlr_val |= (1UL << 3U);  /* ACTLR[3]: 启用锁步错误时的故障响应 */
    asm volatile("MCR p15, 0, %0, c1, c0, 1" :: "r"(actlr_val));

    /* 执行同步指令以确保配置生效 */
    asm volatile("ISB");
    asm volatile("DSB");
}

这段代码必须在Boot ROM阶段执行,因为一旦使能锁步,汇编指令本身就在两个核心上并行执行:你在用户代码中写这段配置代码,它通过检查器的对比也是相同的两个写入序列。锁步的使能必须在执行任何非对称代码(比如读取芯片序列号、校准参数——这些东西两个核心不会拿到相同的结果)之前完成。

锁步能做什么?不能做什么?

锁步核最漂亮的特性是零延迟检测。从硬件故障发生到被检测到,延迟可以低至两个时钟周期。以400MHz的Cortex-R5为例,一个周期是2.5ns,两个周期是5ns。与其他任何软件机制(看门狗需要毫秒级,E2E保护需要通信周期级)相比,这个检测延迟短了六个数量级。

但锁步只能检测错误,不能纠正错误。当CCU检测到失配时,它不知道哪个核心是对的:它只知道两个核心的结果不同。此时系统的正确响应只能是:进入安全状态,触发复位或降级。

这个“只检测不纠正“的特性,将锁步与另一种更强大的冗余方案区分开来:三模冗余(Triple Modular Redundancy, TMR)。TMR使用三个核心做相同的计算,输出通过多数表决器决定最终结果。如果一个核心出错,其他两个核心可以“投票否决“它,TMR可以检测并纠正单核故障。但TMR在汽车领域几乎不被采用,原因是成本和功耗:以单核为基准,锁步需要100%的额外硅面积(两个核),TMR需要200%(三个核)。汽车ECU对芯片面积和功耗的约束远比航天器严格。

  冗余方案对比:

  锁步(双核):              TMR(三核):
  ┌───┐  ┌───┐            ┌───┐ ┌───┐ ┌───┐
  │ A │  │ B │            │ A │ │ B │ │ C │
  └─┬─┘  └─┬─┘            └─┬─┘ └─┬─┘ └─┬─┘
    │       │               │     │     │
    └──┬────┘               └──┬──┴──┬──┘
       │                       │     │
       ▼                       ▼     ▼
   比较器 =?                ┌─────────────┐
       │                    │  多数表决器  │
    ┌──┴──┐                 │              │
    │     │                 │ A=B ✓ → 用A  │
  A=B   A≠B                 │ A=C ✓ → 用A  │
    │     │                 │ B=C ✓ → 用B  │
    ▼     ▼                 └──────┬───────┘
  继续  告警                      │
                                  ▼
                               输出(已纠错)

  检测: ✓          检测: ✓       纠正: ✓
  纠正: ✗          纠正: ✗
  面积开销: 100%    面积开销: 200%

锁步还有一个更深层的盲区:共因故障(Common Cause Failure, CCF)。两个核心共享相同的时钟源、相同的电源轨、相同的物理布局。如果VDD核心电压同时跌落导致两个核心都计算错误,CCU不会发现任何失配,因为两个核心在相同的错误条件下产生了相同的错误结果。类似地,如果RTL设计本身有缺陷(比如乘法器在特定操作数组合下产生错误结果),那么两个核心会同时产生相同的错误输出。

ISO 26262-9:2018的第7条款专门讨论了共因失效分析。对抗共因失效的手段必须在锁步之外的层面实施,包括但不限于:

  • 独立时钟源的看门狗(见本书3.1节)
  • 端到端通信保护(E2E)
  • 多样性冗余,使用不同架构的处理器做相同的逻辑判定

传感器冗余:肾脏的另一半

锁步保护的是计算逻辑。但汽车安全还需要另一类冗余:传感器冗余。如果两个锁步核心都在计算同一个错误的传感器信号,它们的计算结果会完全一致。并且都是错的。

以EPS(电动助力转向)为例,ISO 26262要求其达到ASIL D。这意味着转向角传感器也需要达到ASIL D,或者通过冗余达到等效的安全完整性。实际工程中,双芯片转向角传感器是最常见的方案:

  EPS 双传感器冗余架构:

  ┌─────────────────┐     ┌─────────────────┐
  │ TAS Sensor IC 1 │     │ TAS Sensor IC 2 │
  │ (主传感器)      │     │ (冗余传感器)    │
  │                 │     │                 │
  │ 磁编码器通道A   │     │ 磁编码器通道B   │
  │ AMR/GMR/Hall   │     │ AMR/GMR/Hall   │
  │ SPI输出        │     │ SENT输出       │
  └────────┬────────┘     └────────┬────────┘
           │                       │
           ▼                       ▼
     ┌─────────────────────────────────┐
     │   MCU A (ASIL D 域)            │
     │   处理传感器1数据                │
     │   与传感器2数据比较              │
     │   |angle1 - angle2| < 3° ?     │  ← 合理性检查
     │   如果超出 → 安全状态            │
     └─────────────────────────────────┘

加速踏板位置传感器(APP)也采用类似的冗余方案,通常在同一封装内放置两个独立的霍尔传感器,输出两路模拟电压或SENT协议信号。不同的物理原理(有些使用电感式、有些使用霍尔式)可以减少共因失效。

但这里有一个容易被忽视的细节:传感器的“合理性检查“需要在软件中实现。如果合理性检查代码本身出了故障,两个不同的传感器值被错误地判定为“一致“,冗余就形同虚设。谁来保护保护者?在ASIL D系统中,传感器合理性检查代码通常被放在锁步核心上运行,并且自身受到WdgM的Logical Supervision监控。

同构冗余 vs 异构冗余:多样性的价值

到目前为止讨论的都是同构冗余,使用相同设计(甚至相同物理批次)的组件进行备份。同构冗余对随机硬件故障(如α粒子引起的位翻转)有极好的覆盖率。但对系统设计缺陷(如代码bug、RTL bug)无能为力。

异构冗余用一个完全不同的设计来实现相同的功能。最经典的汽车例子是制动系统的液压备份:主制动是电子控制的EHB(电子液压制动),但法规要求必须保留一个完全机械/液压的备份制动回路,即使整车断电,驾驶员用力踩制动踏板,通过物理连杆也能推动制动主缸。

在电子层面,ASIL D系统有时会采用一个较小的独立监控MCU(比如8位或16位芯片),运行一个精简版的监控算法,与主MCU的输出做一致性校核。这个监控MCU使用不同的芯片架构、不同的编译器、甚至由不同的团队开发,尽可能保证没有共同的bug。

  MCU级异构冗余(监控MCU架构):

  ┌──────────────────────────┐      ┌──────────────────────┐
  │ 主MCU (Cortex-R5 锁步)   │      │ 监控MCU (S08 8-bit)   │
  │                          │      │                      │
  │ FOC 电机控制算法          │      │ 精简监控算法           │
  │ 转矩指令 = 123.4 Nm      │      │ 预期转矩 ∈ [100,150]  │
  │                          │      │                      │
  │ 输出 → 驱动桥            │      │ 通过SPI接收主MCU输出   │
  └──────────────────────────┘      └──────────┬───────────┘
                                               │
                                               ▼
                                    ┌─────────────────────┐
                                    │ 监控MCU独立判断:     │
                                    │ 是否在安全范围内?    │
                                    │ 否 → 拉低使能引脚   │
                                    │      → 关断驱动桥   │
                                    └─────────────────────┘

AURIX TC3xx系列中的锁步架构正是这一原理:主Tricore核心和Checker Core运行完全相同的指令序列,硬件比较器在每个周期比对两个核心的输出。Checker Core不缩减逻辑,它执行的是同一段编译好的二进制代码。这种设计的优势在于:无需软件修改,单核代码直接编译在锁步双核上运行,硬件自动解决所有比对。一旦比较器发现不匹配,通过SMU触发安全响应。

异构冗余的代价是更高的开发和维护成本(需要维护两套代码),但它是对抗系统设计缺陷的最有力武器。

锁步核极其擅长检测随机硬件故障:α粒子撞击、时钟抖动、晶体管老化导致的时序违例,这些故障以物理上独立的方式影响两个核心中的某一个。但锁步对共因故障完全盲视:如果两个核心共享相同的设计缺陷、相同的电源、相同的时钟,它们会在相同的条件下以相同的方式失效。这个盲区在ISO 26262中称为共因失效(Common Cause Failure)。对抗共因失效不能靠“更多锁步“,只能靠多样性,不同架构的处理器、不同原理的传感器、不同团队开发的代码。

同构冗余给你随机故障覆盖率,异构冗余给你系统故障覆盖率。两者缺一不可。这就是为什么ASIL D系统不能只依赖锁步,还必须搭配独立看门狗、E2E保护、以及监控MCU中的多样性算法。

从工程角度说,锁步的可贵之处在于它对软件完全透明,应用代码不需要写任何特殊逻辑。但也是它的陷阱:正因为透明,设计师容易忘记它的局限性。

本篇小结

  1. 锁步核(Lockstep)将两套完全相同的CPU核心逐拍比对输出,CCU比较器在5ns内检测任何不一致,但只能检测不能纠正。
  2. 共因失效(CCF)是锁步的根本盲区:共享时钟、电源和设计的两个核心可能以相同方式同时出错,需靠多样性冗余(异构架构、独立看门狗)弥补。
  3. 传感器冗余通过双芯片或双物理原理(霍尔+电感)实现测量值比对,但合理性检查代码本身必须受锁步和WdgM保护。
  4. 同构冗余覆盖随机硬件故障(α粒子、晶体管老化),异构冗余覆盖系统设计缺陷(代码bug),ASIL D系统两者缺一不可。

【下集预告】: 锁步核保护了CPU的计算逻辑,但数据在存储器中睡觉时依然是脆弱的:一颗α粒子击中SRAM的6T单元,就能把本该是3.2°的转向角变成327.6°。ECC用Hamming码在每一个32位字上附加7位校验位,读取时自动纠正单bit翻转。但ECC有一个致命的沉默前提:SRAM必须在第一行C代码运行之前被完整初始化,否则你在用一堆未经纠错的数据欺骗自己。下一节看这个“硅片上的DNA校对机制“如何在硬件层面做到透明且实时的纠错。