MEEK

MEEK:Re-thinking Heterogeneous Parallel Error Detection Architecture for Real-World OoO Superscalar Processors

芯片检测错误关键就是增加冗余去检测错误,大致可以分为三种,本文采用的是物理冗余

1740120752592

摘要

我们构建了首个全RTL设计的MEEK,并将其集成到一个开源SoC中,从微架构和指令集架构(ISA)到操作系统和编程模型。我们识别并解决了之前工作中被忽视的瓶颈和错误,并证明MEEK能够在可承受的开销下实现微秒级的检测能力。通过在协同设计的硬件-软件层之间权衡架构功能,MEEK仅对成熟的乱序超标量核心进行了轻微的改动,简化了协调软件层,并且只需要几行操作系统代码

介绍

硬件故障,无论是永久性的还是瞬态的,都会导致系统异常和执行错误,随着现代处理器中晶体管数量的增加和工作电压的降低,这种情况变得更加普遍[1]、[2]、[3]、[4]、[5]。为了减轻硬件故障引起的错误,存在从错误代码到容错架构的保护机制。检测始终是关键:一旦检测到错误,系统可以转换到安全状态,从而采取纠正措施(例如系统恢复或故障隔离)。

根据行业的全球安全标准,例如汽车的ISO26262[6]和航空电子的DO-178C[7],硬件故障必须在升级为危险之前得到解决,即在容错时间间隔(FTTI)内,通常以毫秒为单位[8]

软件机制(例如多线程 [4] [9] 和software scanner [10] [11])通常会导致显著的性能下降或提供有限的故障覆盖范围 [12],这使得它们不足以满足需要严格可靠性标准的处理器(例如ISO26262中的ASIL-D [6])。硬件机制通常采用一个专用核心来执行程序副本(lock-step),从而在每个时钟周期比较核心的引脚(例如锁步 [13] [14] [15])。通过在单独的同步核心上重放所有内容,并在信号级别进行运行时验证,实现了全覆盖和实时保证。尽管双核心和三核心锁步已经在许多生命关键应用场景中的微控制器级处理器核心中得到成功应用 [16] [17],但由于能量、面积和功耗成本过高 [18] [19] [20],它们已被证明对于乱序超标量核心不切实际。

异构并行错误检测 作为一种有前途的替代方案,异构并行错误检测 [21] [22] [2] 利用强归纳法,将运行在乱序超标量高性能核心(大核心)上的软件程序划分为多个离散段,使用寄存器检查点(RCPs),并在一组较小的能效核心(小核心)上重新执行这些段以进行验证。为了重放内存和其他不可重复的操作,从程序流中提取相关指令(例如load和store)的addr和data,在大核心的commit阶段生成加载和存储操作的分区分布式日志。当段的日志填满,或指令超时,或陷入内核模式时,会触发一个新的RCP,相应的小核心开始验证从开始RCP(SRCP)到结束RCP(ERCP)之间的段。通过重叠验证任务,小核心集体提供了足够的计算能力来跟上大核心的步伐,确保全覆盖且开销低 [21]。(很像difftest)

这使大核心上的应用程序线程可以使用RCPs进行分段,并在任何小核心上使用任何数量的检查线程进行重放和验证,同时仍然允许其他线程在它们上面执行(图1)

1739418044205

II. MEEK:一种CPU/OS协同设计方法

为了在保持高性能的同时以最小的复杂度实现MEEK,我们必须在硬件和软件之间进行仔细的设计选择分区(图2)。一方面,我们别无选择,只能在硬件中实现加载和存储日志记录,用于在小核心上重放执行

如果需要重放大核上的程序,必须得记录load和store,因为load和store在重放时可能被大核改变,从而导致错误

另一方面,由于小核心与大核心异步执行PTW,操作系统必须在某种程度上感知它们,因此我们让操作系统完全控制它们的调度,避免在硬件中实现不在性能关键路径上的复杂决策,并允许小核心执行标准进程。

与其采用完全透明的接口并避免在硬件中实现昂贵的错误纠正 [22],程序通过在main之前插入的协调函数与MEEK-ISA交互,这些函数请求操作系统提供检查资源,验证检查输出,并在需要时调用故障处理代码。为此,我们对大核心的微架构进行了轻微的修改,在提交阶段插入了一个只读观察通道(图2 a),收集大核心的状态数据(即架构、控制和状态寄存器文件)以及RCP之间的运行时数据(内存和其他不可重复操作的地址和数据)。

我们构建了一个专用的数据结构(图2 b),选择性地广播/路由提取的数据到小核心,最小化数据通信的反压。在小核心中,接收到的数据被缓冲在加载存储日志(LSL)中,替换程序重放期间的L1缓存,允许小核心重置其架构状态到给定的SRCP,重放SRCP和ERCP之间的精确指令,并在ERCP处使用不同类型的数据验证执行的正确性(图2 c)。

我们的ISA接口(图2 d和表I)(重新)配置小核心的检查特性,即操作模式(应用程序或检查模式)以及它们与大核心的关联:小核心最初以应用程序模式运行,作为标准核心执行程序。当与大核心关联时,可以部署并运行检查线程在检查模式下。应用程序和检查线程仍然在小核心上共存,操作模式仅在上下文切换时切换。

检测方法。错误检查通过检查线程并行化(图2 b):应用程序线程使用RCPs进行分段,当目标LSL已满、指令超时或陷入内核模式时,这些段由检查线程重新执行,假设所有先前的段都是正确的。重新执行后,检查线程将其架构寄存器与应用程序线程在相同RCP处提供的寄存器进行比较。如果寄存器匹配,则认为该段是正确的。如果所有段都通过检查,则认为整个执行是正确的。然而,在ERCP处无法验证内存操作。在重新执行期间,直接在LSL中比较内存操作的地址和数据。类似的设计方法用于控制和状态寄存器(CSRs)以验证不可重复的指令。

这里讲述了检查线程的触发条件:LSL满,指令超时,陷入内核模式

1739418630897

III. 微架构

特别是在数据传输方面。我们识别并缓解了之前工作 [21] [22] [26] 中缺失的关键瓶颈,由于缺乏RTL实现,这些工作仅通过抽象模拟进行。我们还识别了冗余数据存储方面的低效,其中大部分转发路径所需的信息已经在提交时间之前缓冲在核心内部,这意味着不需要专用结构;相反,我们可以从现有结构转发数据。尽管存在瓶颈,我们证明可以从一个成熟的异构SoC构建MEEK的微架构,只需进行少量改动,避免大量的工程工作。我们将MEEK构建到一个开源异构SoC(Rocket Chip [24])中,该SoC既包含高性能核心(BOOM)也包含能效核心(Rocket)。我们利用Rocket Chip来证明我们的方法适用于其他异构SoCs,例如ARM的big.LITTLE [27] 和Intel的P-和E类核心 [28]。

A. 乱序超标量核心(大核心)

1739419219032

程序重放需要收集状态数据和运行时数据:PRFs,CSRs,memtrace,在提交阶段提取,并且必须在之后指令提交前提取完成

这里最多两个周期提取?否则可能会被覆盖()

鉴于此,我们开发了一个非侵入式数据提取单元(DEU)(图3),包含提交检测器(CD)、控制电路和旁路电路,注入到PRFs、CSRs和LDQ。CD监控来自ROB的指令提交,并选择旁路电路以在RCPs或RCP之间的运行时数据处提取数据。这使得可以在不增加额外缓冲区或更改现有寄存器路径的情况下及时提取数据。

PRF示例。图3展示了使用PRFs(用于收集RCPs)的微架构示例,其中DEU与ROB、PRFs和F2进行交互。在每条指令提交时,操作码和功能码从ROB(图3 a)路由到CD,允许CD确定是否提取数据(图3 b)。如果到达RCP,则控制电路生成一个信号,抢占PRF控制器以读取寄存器文件并将其转发到F2(图3 c)。PRF控制器在ROB和DEU之间多路复用,DEU在需要时具有优先访问权,从而实现数据的立即读取并防止数据被覆盖。CSRs和LSQ。为CSRs实现了类似的微架构,允许从任意CSR地址提取数据。相比之下,由于LSQ的顶部始终持有最近提交指令的数据,当CD决定转发运行时数据时,旁路电路直接从队列顶部传输,最小化了设计复杂性并减少了数据争用。

大核主要工作就是提取出有效的状态

B. 转发结构(F2)

由于大核心的并行提交产生的大量运行时数据,与RCPs的频繁到达(通常以突发形式出现)发生拥塞,特别是在核心在RCP边界处提交多个加载或存储指令时,需要在一个周期内进行多次数据传输并需要高吞吐量。我们设计了F2,带有双通道缓冲区(DC-Buffers)和半双工多播片上网络(HM-NoC),用于存储和路由提取的数据(图2 b)。每个提交路径连接一个DC-Buffer,为状态和运行时数据添加独立的FIFO。这确保了所有运行时数据可以在提交的同一周期内存储,即使同时生成状态数据,避免了数据需要在原始核心设计中更长时间地存储在核心内部结构中。

HM-NoC使用半双工(1到N)曼哈顿网格。为了实现足够高的吞吐量,这个NoC允许每周期传输两个数据包,同时保持顺序。

转发策略。与之前的工作不同,之前所有段的数据在RCP处缓冲并集体转发,F2允许在收集时立即传输和使用数据,使小核心能够更早地重新执行。此外,由于相同的状态数据可能被两个小核心需要(分别用作SRCP和ERCP),当小核心能够接收数据时,数据会被选择性地广播到小核心,消除了冗余事务。

C. 顺序标量核心(小核心)

1739420233845
为了实现线程级错误检测并允许检查线程和应用程序线程共存,小核心的微架构必须支持不同的操作模式并抽象出一个软件控制接口。

因此,我们升级了小核心的微架构(图4),增加了模式切换单元(MSU,图4 a)和加载存储日志(LSL,图4 b)。MSU作为控制引擎,LSL缓冲接收到的数据包。在复制大核心的状态时,MSU记录小核心的架构寄存器文件并用LSL中的状态数据替换它们。MSU还通过检查运行线程的线程ID(TID)与检查线程的TID来管理小核心的操作模式。当调度检查线程时,MSU切换操作模式,此时加载和不可重复指令的结果从LSL中获取。鉴于小核心以顺序方式访问LSL,我们使用双路FIFO实现了LSL,比传统的组相联架构更简单。

小核直接从这个fifo读取memtrace

流水线集成。图4展示了LSL和MSU如何集成到一个5级流水线的小核心中。LSL被添加到内存访问(MA)阶段(图4 b),通过部署一个复用器并将其连接到加载存储单元(LSU)的地址端口。复用器根据操作模式(由MSU返回)选择性地将内存访问路由到LSL,并将虚拟索引和物理标签(由TLB返回)组合到LSL的地址端口。此外,在MA阶段集成一个解复用器,将读取的数据定向到后续阶段。在指令解码(ID)阶段部署了一对复用器和解复用器,以允许记录和更新架构寄存器。最后,我们在MA阶段部署了一个迷你解码器(Mini-D,图4 c),以区分传统的RISC-V和MEEK-ISA。

IV. 指令集架构、操作系统和编程模型

为了减轻微架构的复杂性,我们在软件支持方面进行了有限的改进,这些改进更简单、更容易验证或对正确功能是必要的。我们详细说明了ISA支持,以添加软件控制块来增强具有容错支持的程序。通过在内核中进行几行代码的更改,它可以调度和保留检查线程的资源,同时允许其他线程的标准调度和上下文切换。

A. ISA支持

1739428633135

新的ISA分为两类(表I),用于大核心(b.x())和小核心(l.x())。我们使用b.hook()设置大核心和小核心之间的关联,然后使用b.check()通过打开/关闭DEU来启用/禁用检查功能。对于小核心,l.mode()设置其操作模式,一对l.record()和l.apply()从给定来源记录和应用架构寄存器。为了将PC重定向到应用程序线程,我们开发了一个l.jal(),从原始跳转指令修改而来,目标地址有所更改。通过将检查点结束视为类似分支的操作,流水线处理了PC更改的控制危害,无需进一步更改。最后,l.rslt()指示是否检测到RCP不匹配。由于b.hook()和b.check()可能导致小核心的使用争用,l.mode()可能导致意外的内存访问错误,因此它们是特权指令,通过OS syscall执行。

B. 检查线程及其编程模型

1739429031611

检查线程在初始化应用程序线程时,通过使用构造函数和析构函数 [35] 增强应用程序线程的main函数(算法1:第14行)。由于检查线程依赖于LSL中的数据来重放内存操作,而LSL使用FIFO设计,因此上下文切换的日志是不可取的。因此,在调度时间,LSL被保留用于单个检查线程(算法1:第12行),即使多个线程可以调度到核心上。一旦LSL被保留,只有与关联的检查线程相关的数据才会被转发,直到重执行完成。同样,固定到特定应用程序线程的检查线程在重执行完成之前不能迁移。由于每个检查点的大小是有限的(最多5000条指令),并且所有权在每个检查点结束时返回给OS以供重新分配,因此这不会导致资源饥饿。编程模型。我们基于新的ISA开发了检查线程,确保最小的编码工作量:最初,使用l.record()记录当前的架构寄存器状态(算法2:第15行),允许核心在验证后返回。然后,创建一个忙等待循环,等待LSL中的状态数据到达(算法2:第19行)。接收到后,调用l.apply()以根据应用程序段修改核心的架构状态(算法2:第20行),并调用l.jalr()将PC重定向到复制的目标(算法2:第21行)。最后,使用l.rslt()返回验证状态。如果检测到错误,则触发中断以通知OS采取纠正措施(算法2:第16-18行)。

大小核心都有可能陷入模式

C. 操作系统内核及其验证

有了新的ISA,内核修改可以限制在调度程序中的上下文切换函数内,允许配置检查功能并管理检查线程 [36] [37]。根据大核心和小核心的不同角色,应用程序线程和检查线程的上下文切换被不同地修改。在从应用程序线程进入上下文切换时,调用b.check(DISABLE)禁用检查功能(算法1:第3行),在离开上下文切换时,调用b.check(ENABLE)重新启用它(算法1:第20行)。此外,如果调度了一个新发布的线程执行,则使用b.hook(core_index, next→checker_index[x])将其与小核心关联(算法1:第10-13行)。对于应用程序线程的上下文切换,唯一需要的修改是使用l.mode()切换小核心的操作模式(算法1:第3和7行),其余部分保持不变。内核验证和死锁解决。由于MEEK在线程级别启用错误检查,OS内核可以被视为一个专用的应用程序线程,并像其他应用程序线程一样进行验证。然而,在开发过程中,我们观察到一个之前文献 [21] [22] [26] 中遗漏的死锁,这是由于缺乏对OS的评估。因为检查线程可能会阻塞主线程,直到小核心完成,由于SRAM日志是有限的,这种行为表现为小核心持有的锁,而大核心需要。如果出现相反的情况(大核心持有锁,例如软件互斥锁,小核心需要),则会导致死锁(图5 a)。

这个死锁可以通过使检查线程永远不需要获取锁来解决。确保检查线程至少比主线程落后一条指令,这意味着后者会首先到达故障。通过在I/O上进行同步,可以防止写入可能被未完成的检查线程使用的页面(图5 b)。

1739429918710

V. 评估

A. 性能开销

1739433129563

图6显示了在MEEK中运行SPECint和Parsec时大核心的减速情况,与在LLVM中实现的软件基对应物(Nzdc [39])和等效面积锁步(EA-LockStep)进行比较。选择Nzdc进行比较是因为它是唯一可用的开源软件机制,而锁步是最广泛使用的硬件机制。然而,简单地在锁步中复制核心会消耗大核心面积的两倍,同时实现与普通核心相同的性能,导致无趣的比较。因此,我们通过线性插值每个可配置的BOOM组件,创建了一个比较器,使两个核心的组合面积与MEEK的面积开销相匹配。在所有情况下,MEEK配置为使用四个小核心。使用四个小核心足以执行SPEC,开销为1.4%,Parsec为4.4%(几何平均值)。对于所有工作负载,除了swaptions,观察到的减速低于5%。然而,swaptions的减速最高,为22%,这是由于频繁的除法,Rocket核心的除法器性能显著低于BOOM核心。对于比较器,Nzdc在Parsec上引入了几何平均值60.2%的开销,在SPEC上引入了94.2%的开销,反映了其软件实现的局限性。硬件对应物EA-LockStep在Parsec上引入了几何平均值31.2%的开销,在SPEC上引入了48.7%的开销,大约是MEEK的6.1倍和33.7倍,证明了MEEK的性能-面积优势。

B. 检测延迟

为了检查检测延迟,我们在连接到大核心的F2中注入了转发数据的错误,例如内存操作的数据和地址以及架构寄存器数据,模拟硬件故障,同时不干扰大核心的正常执行。对于每个工作负载,随机生成了5000到10000个故障,并且检测延迟的密度如图7所示。每个分布都有一个长而非常薄的尾部延伸到右侧:平均检测延迟低于1微秒,而最坏情况下的延迟是平均值的5到10倍,最高可达2.7微秒(在ferret中)。尽管故障注入是随机的,但大量的样本点(总计超过100,000个)表明,3微秒足以覆盖超过99.9%的硬件故障,这比ASIL-D合规所需的毫秒级FTTI要求低几个数量级。