硬件线程(Hart)的微架构:流水线基础、多发射与乱序执行、硬件线程上下文

好,我们接着聊硬件线程——也就是 Hart。很多刚接触 RISC-V 的朋友会问我:Hart 和 CPU 核心到底有什么区别?

我一般这么解释:一个 CPU 核心可以包含多个 Hart,但每个 Hart 都拥有自己完整的一套程序可见状态。说白了,Hart 就是硬件上独立执行指令流的那个“虚拟处理器”。

流水线基础:从取指到写回

流水线是 Hart 最基础的微架构技术。我刚开始做处理器设计时,觉得流水线不就是把指令切几段嘛,有什么难的?后来踩了坑才知道,流水线设计处处是细节。

经典的 RISC-V 流水线一般分为五级:

  • IF(取指):从指令存储器中取出指令
  • ID(译码):解析指令,读取寄存器
  • EX(执行):ALU 运算或地址计算
  • MEM(访存):读写数据存储器
  • WB(写回):将结果写回寄存器

嗯,这里要注意:RISC-V 的指令编码非常规整,译码阶段其实比 x86 简单得多。我在项目中遇到过,同样的流水线深度,RISC-V 的译码逻辑面积能省 30% 以上。

关键点:流水线每一级之间都有流水线寄存器,它们保存了该级的中间结果。这些寄存器就是流水线的“状态”。

多发射:让 Hart 跑得更快

单发射流水线每个周期只能处理一条指令。你想想看,这太浪费了。现代处理器普遍采用多发射技术——每个周期发射多条指令到不同的执行单元。

多发射有两种常见实现方式:

  • 静态多发射(VLIW):编译器负责打包指令,硬件简单。我早期做过一个 VLIW 的 DSP 处理器,编译器优化得好,性能确实不错。但兼容性是个大问题。
  • 动态多发射(超标量):硬件动态决定发射哪些指令。RISC-V 的很多高性能实现都走这条路。

我曾经在一个项目里尝试 4 发射的 RISC-V 核,结果发现发射逻辑的复杂度增长远超预期。说白了,发射宽度每增加一倍,指令冲突检测的逻辑面积可能增加 3-4 倍。

我的建议:如果做嵌入式场景,2 发射就够用了。追求极致性能才考虑 4 发射以上。

乱序执行:打破顺序执行的枷锁

乱序执行,说白了就是让后面的指令先执行,只要不影响最终结果。为什么要这么做?

我给你举个例子:

ld x1, 0(x2)    // 加载,需要等内存
add x3, x1, x4  // 依赖 x1,必须等
sub x5, x6, x7  // 不依赖前面,可以提前执行

在顺序执行中,sub 指令必须等 ld 和 add 都完成才能执行。但在乱序执行中,sub 可以提前执行。这就是性能提升的关键。

乱序执行的核心组件包括:

  • 重排序缓冲区(ROB):记录指令的原始顺序,保证提交时按序
  • 保留站(Reservation Station):暂存等待执行的指令
  • 寄存器重命名:消除伪数据相关

我记得有一次调试一个乱序执行的 RISC-V 核,发现性能始终上不去。查了三天,最后发现是 ROB 的深度设置不合理——太浅了,乱序窗口太小,根本发挥不出乱序的优势。

避坑指南:我曾经在乱序执行中忽略了精确异常的处理。结果导致异常发生时,指令的提交状态不一致,调试起来非常痛苦。记住:乱序执行必须保证异常是精确的,即异常发生时,所有之前的指令都已提交,之后的指令都未提交。

硬件线程上下文:Hart 的灵魂

每个 Hart 都有一组硬件线程上下文,这是它区别于其他 Hart 的根本。RISC-V 规范中明确规定了 Hart 必须包含的状态:

组件 说明 我的经验
通用寄存器(x0-x31) 32 个 64 位寄存器(RV64) x0 硬连为 0,写它不会报错但也没效果
PC(程序计数器) 指向当前指令地址 注意 PC 在异常处理时的保存与恢复
CSR(控制状态寄存器) 包括状态、中断、异常等控制 mstatus、mie、mtvec 这几个最常用
浮点寄存器(可选) f0-f31,用于浮点运算 如果实现 F/D 扩展,这些是必须的

多 Hart 系统中,每个 Hart 的上下文是独立的。这意味着:

  • Hart 0 的 x1 和 Hart 1 的 x1 是两回事
  • Hart 0 的 PC 不会影响 Hart 1 的执行
  • CSR 中的 mhartid 寄存器标识了当前 Hart 的编号

我在做一个 4 Hart 的 RISC-V 芯片时,遇到过一个坑:软件工程师在初始化时忘了读取 mhartid,结果所有 Hart 都执行了同一段代码,导致系统崩溃。嗯,从那以后我都在 Boot ROM 里强制要求先读 mhartid。

总结一下:Hart 的微架构设计,核心就是在流水线基础上,通过多发射和乱序执行来提升指令级并行度。而硬件线程上下文,则是每个 Hart 独立存在的基石。设计时一定要权衡性能、面积和功耗,没有银弹。

好了,这一章的内容就到这里。下一章我们会聊聊多核缓存一致性——那又是一个让人又爱又恨的话题。