基础知识
- 流水线(pipeline)
1. 单(机器)周期(的)cpu
2. 多(机器)周期(且)单(条)流水线(的)cpu
// 将一条指令的执行过程 划分为 多个 小过程(stage), stage 与 stage 之间加latch
// 可以简单认为 : N 周期 对应了 N 个stage(N级流水线)
// 5 级流水线(5-stage pipeline)
// 其实 大多数的stage都是一个机器周期 , 部分 stage 的 机器周期不定
3. 多(机器)周期(且)多(条)流水线(的)cpu //
- 流水线中的latch
流水线 中 , 每个 stage 之间,都要
1. 有 latch , 一组寄存器(pipeline register)来暂存当前阶段的结果 . 这些寄存器在时钟上升沿更新 ⇒ 就叫做“流水寄存器”或“阶段间寄存器”
2. 打一拍 // 举例 : 将 Fetch(取指)阶段的输出 打一拍(打一拍 = 通过寄存器延迟一个周期),再送给 Decode(译码)模块使用。
// IF 阶段每个周期会取出一些指令、PC、预测信息等;
// 这些信号通过 流水线寄存器(你看到的这段代码),在 clk 上升沿锁存下来;
// 锁存后的信号称为 *_if,然后在下一个周期供 ID 阶段使用。
- 前端与后端
不管 pipeline 实现如何, 一般 都有前端 和 后端
前端 一般是 取指译码
后端 一般是 执行,...
前端与后端有接口
- 超标量
注意 : 这里说的几个 , 一般是 在 rtl(流水线顶层) 代码中 实例化 几次
取指单元 | 译码单元 | 执行 | 写回 | |
---|---|---|---|---|
简单超标量 | 1(个) | 1(个) | N(个) | 1(个) |
复杂超标量 | N(2/3/4/6/8/更多) | N | N | N |
SMT | N(典型值为2/4/8) | N | 1 | 1 |
前后端的参数
- 前后端的 stage
一条流水线 的 stage 可以分属2个
1. 分属前端的stage
2. 分属后端的stage
对于 ridecore 来讲
IF → ID → DP → SW → EX → COM
↑前端 ↑前端 ↑前端 ↑后端 ↑后端 ↑后端
Ridecore 的前端是 2-way、3-stage 前端管线
Ridecore 的后端是支持最多 5 条指令同时 issue 的 3-stage 后端管线
- 前后端的界限
在传统流水线中,发射(issue)常常被视为前后端的交界点,因为它既包含了指令的准备(前端的部分)也涉及了指令的执行调度(后端的部分)。
在复杂的动态调度流水线中,发射(issue) 主要属于后端,因为它涉及到执行单元的选择、调度以及执行顺序等任务。
- 前端的way
前端重点是 Fetch/Decode 的宽度(way)
如果流水线 前端与后端的接口侧
前端一次发送给后端1条指令 : 1-way
前端一次"最多"发送给后端2条指令 : 2-way // Dual-Issue
// 最多的意思是, 不是所有的指令对都能够被双发射的
- 后端中的issue 即 N-issue
后端重点是 issue/execution 的吞吐能力(issue width)
后端设计中
发射模块一次最多发送给 执行单元的 指令个数
1条 : 1 issue
2条 : 2 issue
4条 : 4 issue // 4-issue:后端每周期最多可以发射(issue)4条指令;
部分 | 用词建议 | 说明 |
---|---|---|
前端 | “2-way fetch/decode” | 表示同时处理两条指令 |
后端 | “5-issue width” | 表示支持每周期最多 issue 5 条指令 |
从多个层面看前后端
层面 | 前后端是一体 | 前后端分离 |
---|---|---|
单条指令生命周期 | ✅ 是连续的 | |
多条指令并行处理 | ✅ 逻辑功能和性能上分离 | |
性能分析角度 | ✅ 可能受限或瓶颈不同 | |
设计角度 | ✅ 逻辑功能上明确分离的 |
✅ 在设计架构时,不以“一条指令的完整生命周期”为主线,而是:
将整条流水线拆解成多个功能模块(fetch、decode、rename、issue、execute、writeback、commit)。
各模块各司其职,分别负责处理“指令流的一段阶段”。
前后端的设计团队各自优化自己负责的区域,例如前端专注于:
- 高带宽的 fetch
- 精确的分支预测
- 快速的 decode & rename
后端专注于:
- 高效的调度(issue logic)
- 多发射(multiple execution units)
- 精确提交(retire/commit)
- ROB、RS、LSQ 等资源管理
顺序与乱序
什么是 序
多条指令在汇编文件中, 一条指令A在前,一条指令B在后. 如果 A后B // A后面为B
// 这个我们可以很明确的在汇编文件 中看到 // 或者在反汇编文件中
一个指令(A/B) 在pipeline 中 被 分成多个 阶段, 每个 stage "开始执行" 的时机 为一个精确的时刻(我们称为 s1 s2 ... )
// 这个我们可以在 rtl 中 , 仿真时, 拉信号看到
我们考虑顺序乱序, 只是 在考虑 在某个 stage 两条指令 是否顺序乱序
1. 如果 100% 机率 As1 后 Bs1 , 那么 就叫做 s1 100% 顺序
2. 如果 50% 机率 Bs2 后 As2 , 那么 就叫做 s2 乱序 (只要不能保证100%顺序,那么就叫顺序)
如果 有 1个 stage 乱序, 那么我们就叫做 这个 pipeline 乱序 , 即 Out of Order pipeline.
只要 有 1个 pipeline 乱序, 那么我们就叫做这个 cpu 乱序
后端 执行阶段 是否乱序 是设计者决定的(使用动态调度算法,则乱序. 禁用,则顺序)
在前后端分离的CPU流水线设计中,后端执行阶段(Execution Stage)的指令到达顺序是否确定,取决于具体的设计选择。以下是关键点的分析和设计方法:
1. 指令到达顺序是否确定?
- 默认情况下可能乱序:现代高性能CPU通常采用乱序执行(Out-of-Order Execution, OOO),后端执行阶段的指令到达顺序由动态调度逻辑(如Tomasulo算法)决定,而非程序顺序。执行顺序受以下因素影响:
- 操作数就绪时间:数据依赖(RAW依赖)未解决的指令会等待。
- 功能单元空闲情况:如乘法单元忙时,指令会被延迟。
- 动态调度策略:如发射队列(Issue Queue)的优先级。
- 顺序执行设计:如果设计为顺序执行(In-Order),则指令严格按程序顺序进入执行阶段。
2. 如何强制顺序执行?
若需保证指令按程序顺序到达执行阶段,需以下设计:
- 禁用乱序逻辑:
- 顺序发射(In-Order Issue):仅当指令是程序顺序的下一条且操作数就绪时,才发射到执行单元。
- 阻塞流水线:若前一条指令未完成,后续指令不进入执行阶段(类似经典5级流水线)。
- 同步机制:
- 插入显式屏障指令(如
FENCE
),强制后续指令等待。 - 通过数据依赖人为制造顺序(如用虚假寄存器依赖链)。
- 插入显式屏障指令(如
3. 如何实现乱序执行?
若需允许乱序执行,需以下设计:
- 动态调度硬件:
- 保留站(Reservation Stations):指令在操作数就绪后立即执行,无需等待程序顺序。
- 重排序缓冲区(ROB):跟踪指令状态,确保乱序执行的结果按程序顺序提交(Retire)。
- 消除顺序约束:
- 寄存器重命名:消除虚假数据依赖(WAR/WAW依赖),释放指令并行性。
- 多发射(Multi-Issue):允许每个周期发射多条独立指令到不同功能单元。
4. 设计示例对比
顺序执行设计
前端(取指/解码) → 顺序发射队列 → 执行单元(按序)
- 行为:指令B必须等待指令A执行完毕,即使B的操作数已就绪。
乱序执行设计
前端(取指/解码) → 重命名/发射队列 → 保留站 → 执行单元(乱序) → ROB → 提交(按序)
- 行为:若指令B的操作数先就绪,可先于指令A执行。
5. 关键权衡
- 顺序执行:硬件简单(适合低功耗场景),但性能较低。
- 乱序执行:提高IPC(指令级并行),但增加功耗和面积(复杂的调度逻辑)。
总结
- 顺序到达:需禁用动态调度,按程序顺序发射指令。
- 乱序到达:启用保留站、寄存器重命名和ROB,允许指令就绪后立即执行。
设计时应根据目标场景(高性能/低功耗)选择策略,并通过硬件调度逻辑控制顺序性。