Load Pipeline
本章介绍香山处理器南湖架构 load 流水线的设计以及 load 指令的处理流程. 香山处理器(雁栖湖架构)包含两条 load 流水线, 每条 load 流水线分成3个流水级.
流水线的划分
load 指令执行流水线各级划分如下:
Stage 0
- 指令和操作数被从保留站读出
- 加法器将操作数与立即数相加, 计算虚拟地址
- 虚拟地址送入 TLB 进行 TLB 查询
- 虚拟地址送入数据缓存进行 Tag 索引
Stage 1
- TLB 产生物理地址
- 完成快速异常检查
- 虚拟地址进行 Data 索引
- 物理地址送进数据缓存进行 Tag 比较
- 虚拟/物理地址送进 store queue / committed store buffer 开始进行 store 到 load 的前递操作
- 根据一级数据缓存返回的命中向量以及初步异常判断的结果, 产生提前唤醒信号送给保留站
- 如果在这一级就出现了会导致指令从保留站重发的事件, 通知保留站这条指令需要重发 (
feedbackFast
)
Stage 2
- 完成异常检查
- 根据一级数据缓存及前递返回的结果选择数据
- 根据 load 指令的要求, 对返回的结果做裁剪操作
- 更新 load queue 中对应项的状态
- 结果 (整数) 写回到公共数据总线
- 结果 (浮点) 送到浮点模块
Stage 3
- 根据 dcache 的反馈结果, 更新 load queue 中对应项的状态
- 根据 dcache 的反馈结果, 反馈到保留站, 通知保留站这条指令是否需要重发 (
feedbackSlow
)
Info
Stage 3 负责将一些由于时序原因被延迟的 stage 2 检查结果送出.
Load Miss
一条 miss 的 load 指令会执行以下操作来取得其所需的数据, 这一节将逐个介绍这些机制:
- 禁用当前指令的提前唤醒
- 更新 load queue 的状态
- 分配 dcache MSHR (MissQueue entry)
- 侦听 dcache refill
- 写回 miss 的 load 指令
在 load stage 1, 根据 dcache tag 比较结果, load 流水线可以得知当前指令是否 miss. 在发生 miss 时, 这条指令的提前唤醒有效位会被设置成false
, 以禁用当前指令的提前唤醒.
在 load stage 2, 如果发现 miss, load 流水线不会写回结果到寄存器堆, 不占用 load 指令写回端口. 同时更新 load queue 的状态, 这条发生 miss 的 load 指令此后会在在 load queue 中等待 dcache refill. 与此同时, dcache 会尝试为这条 miss 的 load 指令分配 dcache MSHR (MissQueue entry). 由于分配逻辑复杂, 分配的结果要在下一拍才能反馈到 load queue.
在 load stage 3, 根据 dcache MSHR 分配的结果再次更新 load queue 的状态. 如果 dcache MSHR 分配失败, 则请求保留站重发这条指令.
若这条指令成功被分配 dcache MSHR, 后续其将在 load queue 中侦听 dcache refill 的结果. 参见 load queue: Load Refill.
Replay From RS
load 流水线是非阻塞的, 亦即无论出现任何异常情况, 都不会影响流水线中有效指令的流动. 而在除 load miss 之外的异常情况发生导致 load 无法正常执行完时, load 流水线会利用从保留站重发(Replay From RS)机制来重新执行这条指令. 下面逐个介绍会触发从保留站重发机制的事件:
TLB miss
TLB miss 事件会通过使用 feedbackSlow 端口请求从保留站重发. TLB miss 的指令在重发时存在重发延迟, 在指令在保留站中等待到延迟结束后才被重发. 重发延迟的存在是因为 TLB 重填需要时间, 在 TLB 重填完成之前重发指令还会产生 TLB miss, 是没有意义的.
Bank Conflict
bank conflict 事件. bank conflict 事件包括两条 load 流水线之间的 bank 冲突, 以及 load 流水线和 store 操作写 cacheline 之间的冲突(这里的 store 操作指已经提交的 store 从 committed store buffer 写入到 dcache 当中). 目前, 我们仅允许两条 load 指令在不触发 bank 冲突的情况下同时执行. 而对于 load / store 的冲突, 由于时序关系我们没有复杂的检查, 只要 load / store 操作作用在同一个 cacheline 上, 我们就认为发生了冲突. 来源于 bank conflict 的重发使用 feedbackFast 端口. 从保留站重发不设延迟, 保留站在收到 bank 冲突重发请求时可以立即重发这条指令.
Dcache MSHR Allocate Failure
dcache MSHR 分配失败. dcache MSHR 分配失败的原因参见 dcache/MissQueue. dcache MSHR 分配失败导致的重发使用 feedbackSlow 端口发出重发请求. 重发无延迟, 保留站在收到 dcache MSHR 分配失败重发请求时可以立即重发这条指令.
Note
这里的设计有待优化, dcache MSHR 分配失败可能来源于几个不同的原因. 分开处理这些情况会有益于性能的提升.
Store Data Invalid
Store 地址就绪但数据未就绪. 这些指令不会更新 load queue 也不会写回, 而是在 load stage 3 通过 feedbackSlow 发出重发请求, 通知保留站, 这条指令正在等待此前的某条 store 指令的数据就绪. 在进行 store - load 前递检查的过程中, load 所依赖的 store 的 sqIdx 会被一并查出, 并通过 feedbackSlow 端口反馈到保留站. 这样产生的重发有非固定的延迟. 保留站可以根据查出的 sqIdx 等待到对应的 store data 产生之后再重新发射这条 load 指令.
异常的处理
load 流水线可以处理的异常分为两大类: 来自地址检查的异常和来自错误处理的异常. 异常使用单独的异常通路, 时序比 data 通路宽松. 地址检查结果会分级生成来优化时序表现, 参见 MMU 部分的介绍.
预取指令的处理
目前, 软件预取指令使用与 load 指令类似的处理流程, 软件预取指令会与正常的 load 指令一样进入 load 流水线, 在发现 miss 时向 dcache 的 MissQueue 发出请求, 触发对下层 cache 的访问. 特殊地, 软件预取指令执行期间会屏蔽所有例外, 且不会重发.
Load 的提前唤醒
南湖的保留站支持提前唤醒机制来尽快调度后续的指令. 但是, 南湖架构暂时不支持推测唤醒机制. 被提前唤醒的指令必须要能正常地执行, 否则就需要冲刷整个流水线. 如果一条 load 指令正常执行但没有发出提前唤醒信号, 则会导致依赖这条 load 的后续指令晚一个周期才能被发射, 造成少许的性能损失.
load 流水线会在 load stage 1 向保留站给出快速唤醒信号. 由于 MemBlock 和 IntBlock 之间的线延迟, 这一信号处于关键路径上. 在提前唤醒信号的产生, load 流水线会进行指令能否正常执行的粗略判断. 一旦指令有不能正常执行的迹象, 就不进行提前唤醒.
在极端情况下, load 指令会错误的发出提前唤醒, 此时需要冲刷整个流水线. load 指令错误的发出提前唤醒的条件为:load 指令在 load stage 1 之后发生异常 (来源于数据错误/总线错误).
Forward Failure
这一小节介绍南湖架构在虚地址前递失败时的处理. 当 store queue 或者 store buffer 反馈虚地址前递失败时, load 流水线会在 stage 2 将这条指令附加上 replayInst
(需要从取指重发) 的标签. 在这条指令到达 ROB 队尾时触发重定向. 由于虚地址前递失败是很罕见的现象, 这样的重定向不会对性能产生过大影响.
调试相关
load 流水线中设置了 trigger 触发机制. 出于时序考虑, 南湖架构只支持使用地址作为触发条件.