针对每一单元,ISO26262手册定义了一些方法,来检测这些单元是否失效,并给出每一种方法的可靠度。比如传输线,可以有校验码,超时,计数器,发送测试向量等。再比如处理单元,可以使用软硬件自检,冗余加比较,额外硬件模块监测等方法。这些方法并不能简单的应用于芯片功能安全设计。那芯片上怎么办?我们采用自底向上的方法,先从晶体管开始分析,再到IP模块级,然后到芯片系统级,再讨论几个典型场景,最后自顶向下分析。
在芯片的随机错误中,有一类是永久错误,比如逻辑或者片上内存的某一位一直粘在0或者1,或者干脆短路及断路。对于这一类错误,在芯片封测的时候,我们可以使用边界扫描和MBIST来发现坏掉的晶体管。这样,问题就转换为怎样提高DFT的覆盖率。这一块,业界已经有成熟的方法了。
仅仅有出厂测试是不够的,晶体管会在使用过程中慢慢老化损坏。因此,我们需要在每次开机的时候都进行自检,提前发现问题,减少在系统运行状态下出错的可能。此时,我们需要使用LBIST和MBIST。其原理和出厂测试很像,也是利用扫描链,不同的是芯片里需要LBIST/MBIST控制器,用来运行测试向量和模板。自然,这会引入额外的成本。覆盖率越高,成本相应越大。
有了LBIST/MBIST也还不够,我们需要在晶体管失效发生后几个时钟周期就探测到错误,而不是开机时候发现。对于逻辑来说,为了做到这点,最直接的方法莫过于采用冗余设计,也就是把逻辑复制一份,然后用硬件比较器比较输出。通常这被称为锁步设计(Lock-Step)。理论上,对于有限状态机,只要输入一致,时钟周期一致,输出一定一致。通常数字部分不存在真随机单元,哪怕是缓存替换算法,也是伪随机的,所以上述条件可以满足。冗余的结果是逻辑面积增加一倍,比较器也会引入一些额外的面积开销和时序影响。
这么简单就实现了功能安全?并没有,有几个问题需要解决:第一个问题是,比较器到底比哪些信号?以处理器为例,如果我们只是在对总线的接口上增加比较器,芯片内部的很多模块,比如写缓冲,并不能在较短且确定的时间内把影响传递到对外接口,被比较器发现。此时,处理器可能是处于失效状态而并没有被探测出来。那我们就不能说当前冗余机制能覆盖此类失效。为此,我们需要把比较器连到内部子模块接口处,并且分析是不是能在较短时间内看到影响。这需要在设计阶段就考虑,具体做法如下图:
对于任何一个寄存器,一定可以找到影响它的组合逻辑和上一级寄存器。在这条通路上任何一位出了问题,那么在一个时钟周期后,我们就可以看到寄存器输出与其冗余的模块产生不一致。把这个节点记为1,然后再以1的输入寄存器为新起点,找到节点2。依次类推,我们可以往前找出一条没有循环的通路,这条通路上的任何一点发生问题,在确定的较短时间内,一定会在最终输出上反应出来。我们把这个通路记为模块X。通过一定的EDA工具,我们可以在芯片内找出若干个模块X,如下图的例子:
这里,IP模块被划为存取单元(A门),标志单元(B门),计算单元(C门)和寄存器组(D门)。从输出端看,于上一级寄存器间连线所覆盖的组合逻辑为门数,一个寄存器算10个门。如上图,存取单元的地址寄存器输出受24个组合逻辑门外加2个寄存器的影响,那共存在44种单点错误会引起失效。依此类推,寄存器组的1号输出,受28个门影响,而2号受49个门影响。加起来总共121种可能。简单计算可知,存取单元失效率44/121=36.4%,寄存器组合计77/121=63.6%。是其中有些门被统计了多次,比如图中的G1,这一点会反映在总的概率里面。
基于上述的思想,我们来看处理器是怎么做的。在EDA工具的帮助下,我们将它划分为几个大模块:内存管理单元,写缓冲,取指单元,数据处理单元,程序追踪缓冲, 数据/指令缓存,总线接口单元, 时钟和重置控制单元, ECC/奇偶校验控制单元, 中断接口, 监听控制单元。此处,我们没有把片上内存包含进去,即使是讨论缓存,也指的是控制逻辑部分。
每一个单元内,又可以细分成很多子模块。以数据处理单元为例, 又分为通用寄存器组,存取单元,浮点单元,浮点寄存器组,解码单元,调试单元,控制信号单元,系统寄存器组,分支执行单元等。每一个子单元又可以再一次细分。细分的目的是判断在晶体管失效时,受其影响的寄存器是不是会失效,并且这个失效能被外部比较器探测到。这就需要把内部信号拉到外面。那到底怎么决定哪些信号拉出去哪些不拉?覆盖率是不是足够?工具给的节点和模块信息只能作为参考,设计人员还是要一个个检查来做最后决定。通常会有很多信号被拉出来,比如Cortex-R5,20多万门的逻辑,最终送到比较器的信号数达2000多个,平均每100门就有一个信号。
在芯片过认证的时候,如果IP本身没有过经过认证,或者以前没有被广泛采用,认证机构可能会需要一条条的和芯片公司讨论,看看连出来的的管脚是不是能提供足够的失效检测覆盖率。通常这些设计相关的信息,IP公司并不会提供给芯片公司,所以认证公司可能会要和IP设计公司拿这些信息,导致更长的认证时间。相应的,如果是广泛使用的IP模块,这个时间可以缩短。
解决了冗余设计覆盖率的问题,还有第二个问题。如果遭受电磁冲击或者射线影响,即使用了冗余设计,也可能两个模块同一时间产生一样的错误。这个比较容易处理,只要把两个同样的逻辑,输入错开几拍就可以。在输出的时候,错开相同的拍数,使得比较器还是看到相同的结果。
第三个问题,复制了一份逻辑,并且比较器发现了错误,能把他纠正过来吗?很可惜,不能。除非复制两分逻辑,三个同时比较。这样的代价就是再增加原先100%的逻辑部分面积,对于大的处理器设计,基本没人这么做。如果是小的处理逻辑,比如看门狗电路,倒是可以。
第四,逻辑比较器本身,也是可能出错的。这类错误已经被ISO26262定义,也就是所谓的潜藏错误Latent Fault。如果发现比较器本身的失效覆盖率不够,那同样可以对比较器采用冗余设计,做比较器的比较器,提高它的覆盖率。对于Asil-D来说,潜藏错误覆盖率需要达到90%,而Asil-B是60%。
以上都是对于逻辑错误的分析。还有一类是内存错误。这里内存指的是片上内存,也包含嵌入式闪存。内存的错误比较容易发现,通常ECC就可以做到99%覆盖率,1位纠正多位报错。有些内存,比如一级指令缓存,只支持奇偶校验,不支持纠正。
对于逻辑的冗余和内存的ECC,为了验证探测机制本身是不是能达到设计的要求,芯片里面需要加入错误注入。请注意,错误注入机制本身并不是为了验证芯片里单点错误失效和多点错误失效率,只是为了验证错误探测机制。
综上所述,逻辑冗余和内存ECC是帮助我们达到Asil-B/D等级的必要手段。没有冗余设计的时候,把一个程序在一个核上运行两遍,然后比较结果,也是一种通向高等级安全的办法,但仅仅适用于简单的,实时性要求不高的运算。如果存在永久错误,这个方法就会失效。同样,用两个非冗余处理器同时做相同运算,也是一种方法。但如果计算很复杂,这样做不但会增加系统延迟和带宽,成本也并不低。
上述两种方法并不能从本质上改善安全等级,如果最终安全等级需要Asil-D,这两种方法会要求拆解后也得达到Asil-B。而Asil-B的单点90%覆盖率,不用冗余机制同样很难达到。还有一种方法,单路计算,另一路判断其结果是不是合理。作为监测的这一路提高到Asil-D。这只有在特定场景才有可能应用,我们后面会讨论到。
综上所述,要做通用的Asil-B/D,最好从设计开始就使用逻辑冗余和内存ECC。
实际设计中,特别是对于处理器,在冗余设计之外,还有一套错误发现和纠正机制。Arm把它称作RAS (Reliability,Availability,Serviceability)。RAS并不能代替冗余设计来实现Asilb-B/D,毕竟它的覆盖率太低。但有些场景,比如ECC报错,指令报错,这套机制可以在不重启核心的情况下纠正错误,或者阻止错误在纠正前被扩散(Data Poisoning),又或者记录下错误时的上下文。这是它的优点,在没有冗余设计的芯片里也是有一些用处的。
让我们结合ISO26262文档,来看看Arm的面向汽车应用的IP是怎么实现高等级功能安全的。
上面是A76AE配置图,也就是面向汽车的A76,它引入了Split-Lock的设计。正常情况下,可以当4核SMP用,在冗余模式下,核心内所有的逻辑和内存都复制两份,互为备份。这两种模式需要重启来进行切换,不能动态切换,对于汽车应用来说足够。核心内部添加的比较器,约占5%的面积,频率也会有5%左右的损失。