本文档参考香山LSQ设计文档写成
本文档撰写的内容截至[ca892e73]
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
本文档参考香山LSQ设计文档写成
本文档撰写的内容截至[ca892e73]
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
LoadQueueRAR用于保存已经完成的load指令的用于load to load违例检测的信息。
多核环境下会出现load to load违例。单核环境下相同地址的load乱序执行本来是不关心的,但是如果两个load之间有另外一个核做了相同地址的store,并且本身这个核的两个load做了乱序调度,就有可能导致新的load没有看到store更新的结果,但是旧的load看到了,出现了顺序错误。
多核环境下的load-load违例有一个特征,当前DCache一定会收到L2 cache发来的Probe请求,使得DCache主动释放掉这个数据副本,这时DCache会通知load queue,将相同地址的load queue中已经完成访存的项做一个release标记。后续发往流水线的load指令会查询load queue中在它之后相同地址的load指令,如果存在release标记,就发生了load-load违例。
名称 | 描述 |
---|---|
L2Cache | 二级高速缓存 |
DCache | 数据缓存 |
ROB | 重排序缓冲区 |
CAM | 内容可寻址存储器 |
FTQ | 取指目标队列 |
多核环境下,可能会出现load to load违例:在单核环境中,相同地址的load乱序执行通常不被关注,因为它们在同一核内执行,不会影响其他核的状态,也不会被其他核的操作影响。但是,当两个load操作之间有另一个核对相同地址进行了store操作,情况就变得复杂。
考虑以下指令序列:
load1(core1)
store(core2)
load2(core1)
指令的实际执行顺序为:
load2(core1)
store(core2)
load1(core1)
由于指令的乱序执行,可能导致以下情况:旧的 load1 指令在执行时读取到了 store 修改后的新数据,而新的 load2 指令却读取到了未被修改的旧数据。这种执行顺序的变化会导致数据的不一致性,进而引发访存错误。
因此,在多核环境中,正确处理指令的执行顺序和内存一致性是至关重要的,以确保所有核都能看到一致的内存状态。
LoadQueueRAR最多能够存储72条指令(为了同VirtualLoadQueue的大小保持一致),每条指令占用一个条目。每个条目包含指令的物理地址(paddr)、与指令相关的信息(uop)、以及标记为已释放(released)和已分配(allocated)的状态寄存器。
该模块通过 FreeList 子模块管理 entry 资源,FreeList 中存储的是 entry 的编号。当一条指令满足入队条件时,FreeList 会为其分配一个 entry 编号,并将该指令存放在相应的 entry 中。指令出队时,需要释放所占用的 entry 资源,并将条目编号重新放回 FreeList 中,以供后续指令使用。
PaddrModule 的实现基于内容可寻址存储器(CAM),其深度为 72,数据宽度为 48。CAM 为每条流水线提供一个写端口,其中物理地址(paddr)作为写数据(wdata),条目编号作为写地址(waddr)。此外,CAM 还为每条流水线提供了一个地址查询端口(releaseViolationMdata),并为数据缓存(DCache)提供另一个地址查询端口(releaseMdata)。
当query到达load流水线的s2时,判断是否满足入队条件,如果在当前load指令之前有未完成的load指令,且当前指令没有被flush时,当前load可以入队。
具体入队条件如下:
指令的入队请求必须有效,具体通过检查 query.req.valid
是否等于 1。如果该条件满足,系统将继续处理指令的入队。
指令必须确认尚未写回到重排序缓冲区(ROB)。这一条件通过比较指令在 VirtualLoadQueue 中的写回指针与该指令分配的 lqIdx
来验证。指令只有在到达 VirtualLoadQueue 的队头,并且其地址和数据均已准备好后,才能被写回到 ROB。这一机制确保了指令执行的顺序性和数据的有效性。
指令不能处于冲刷状态。为此,系统需要比较重定向指针所指向的指令与该指令的 robIdx
、ftqidx
及 FTQ 内的偏移(ftqoffset
)。如果两者不相同,则说明该指令可以安全入队,从而避免潜在的冲突和数据不一致性。
在 LoadQueueRAR 指令成功入队后,系统会执行一系列响应操作,以确保指令被正确管理和处理。具体的入队响应操作如下:
拉高 allocated 寄存器。系统将指令的 allocated
寄存器设置为高电平。这一操作的目的是明确标识该指令已成功分配到 LoadQueueRAR 中。通过将 allocated
寄存器拉高,后续的处理逻辑能够迅速识别出该指令的状态,从而避免对未分配指令的误操作。
写入指令相关信息到 uop。指令的相关信息将被写入到微操作(uop
)中。这些信息包括指令的类型、目标寄存器、操作数等关键信息。将这些信息存储在 uop
中,确保后续的执行阶段能够准确获取和使用这些数据,从而执行相应的操作。这一过程对于指令的正确执行至关重要。
物理地址写入 PaddrModule。指令的物理地址将被写入到 PaddrModule 中。这一操作的主要目的是为后续的地址查询和管理提供支持。
检测 Release 的 Valid 信号。系统将检测 release
的有效信号是否被拉高。如果该信号有效,将进一步比较物理地址是否相同。如果物理地址一致,则对应条目的 released
信号将被设置为高电平,可以用于后续操作。
在 Load 指令的处理过程中,为了确保数据的一致性和正确性,系统需要检测潜在的 Load-Load 违例。当 load 到达流水线的 s2 时,会检查RAR队列中是否存在与当前load指令物理地址相同且比当前指令年轻的load指令,如果这些 load 已经拿到了数据,并且被标记了release,说明发生 load - load 违例,被标记release的指令需要从取指重发。 该检测过程主要涉及将查询指令的物理地址和相关信息与队列中存储的指令进行对比。具体流程如下:
releaseViolationMmask(w)(i)
来进行,以确定两条指令是否访问了相同的内存位置。released
寄存器被拉高,表明该指令已被标记为释放,说明它可以被重新使用。一旦检测到 Load-Load 违例,系统将在下一个时钟周期内将 resp.rep_rm_fetch
信号拉高,以通知其他组件发生了违例。触发 Load-Load 违例的 Load 指令将被标记为需要重新从取指阶段执行。重定向请求将在这些指令到达 ROB 队列的尾部时发出,确保指令能够在合适的时机得到正确的处理。
该过程分为两个时钟周期进行:
resp.rep_rm_fetch
)。由于 Load-Load 违例出现的频率相对较低,因此系统会选择在指令到达 ROB 的头部时才进行处理。这种处理方式类似于异常处理,确保系统能够在合适的时机对潜在的违例情况进行响应。
released寄存器需要更新的三种情况:
release信号的到达时机可以分为以下两种情况:
值得注意的是,dcache release 信号在更新 load queue 中 released
状态位时, 会与正常 load 流水线中的 load-load 违例检查争用 load paddr cam 端口. release 信号更新 load queue 有更高的优先级. 如果争用不到资源, 流水线中的 load 指令将立刻被从保留站重发.
Load指令的出队需要满足以下条件其中之一:
出队执行的操作:
allocated
寄存器设置为低电平。这一操作的目的是标识该指令不再占用 LoadQueueRAR 的资源,从而为后续指令的入队和处理腾出空间。free
掩码拉高,表示该条目已被释放并可供后续使用。在load流水线的s3阶段可以向队列发送revoke信号撤销上一拍的请求。如果指令当前周期的revoke信号拉高(revoke ==1),并且在上一个周期已经入队,需要执行撤销操作:
name | I/O | width | Description | |
---|---|---|---|---|
redirect | ||||
io.redirect.valid | input | 1 | 后端重定向的有效位 | |
io.redirect.bits.robIdx.flag | input | 1 | 后端重定向的flag,用于在循环列表中判断先后 | |
io.redirect.bits.robIdx.value | input | 8 | 后端重定向的位置value | |
io.redirect.bits.level | ||||
input | 1 | 后端重定向的level: | ||
1’b0:冲刷之后的指令; | ||||
1‘b1:冲刷这条指令本身 | ||||
vecFeedback | io.vecFeedback_0/1.valid | input | 1 | 来自两条流水线的向量反馈信息有效位 |
io.vecFeedback_0/1.bits | input | 17 | 来自两条流水线的向量反馈信息 | |
query | io.query_0/1/2.req.ready | output | 1 | 能否接收3条数据通路中load违例检查请求 |
io.query_0/1/2.req.valid | input | 1 | 3条数据通路中load违例检查有效位 | |
io.query_0/1/2.req.bits.uop.robIdx.flag | input | 1 | 3条数据通路中load违例检查uop在rob中的flag | |
io.query_0/1/2.req.bits.uop.robIdx.value | input | 8 | 3条数据通路中load违例检查uop在rob中的value | |
io.query_0/1/2.req.bits.uop.lqIdx.flag | input | 1 | 3条数据通路中load违例检查uop在LoadQueue中的flag | |
io.query_0/1/2.req.bits.uop.lqIdx.value | input | 7 | 3条数据通路中load违例检查uop在LoadQueue中的value | |
io.query_0/1/2.req.bits.paddr | input | 48 | 3条数据通路中load违例检查的物理地址 | |
io.query_0/1/2.req.bits.data.valid | input | 1 | 3条数据通路中load违例检查data的有效 | |
io.query_0/1/2.resp.valid | output | 1 | 3条数据通路中load违例检查响应的有效位 | |
io.query_0/1/2.resp.bits.rep.frm.fetch | output | 1 | 3条数据通路中load违例检查的响应 | |
io.query_0/1/2.revoke | input | 1 | 3条数据通路中load违例检查的撤销 | |
release | io.release.valid | input | 1 | Dcache释放块有效位 |
io.release.bits.paddr | input | 48 | Dcache释放块的物理地址 | |
ldwbptr | io.ldWbPtr.flag | input | 1 | VirtualLoadQueue中writeback的flag |
io.ldWbPtr.value | input | 7 | VirtualLoadQueue中writeback的位置value | |
Lqfull | io.lqFull | output | 1 | 表示loadqueue RAR满了 |
performance | io.perf_0/1_value | output | 6 | 性能计数器 |
本文档参考香山LSQ设计文档写成
本文档撰写的内容截至[ca892e73]
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
LoadQueueRAW是用于处理store-load违例的。由于load和store在流水线中都是乱序执行,会经常出现load越过了更老的相同地址的store,即这条load本应该前递store的数据,但是由于store地址或者数据没有准备好,导致这条load没有前递到store的数据就已经提交,后续使用这条load结果的指令也都发生了错误,于是产生store to load forwarding违例。
当store address通过STA保留站发射出来进入store流水线时,会去查询LQRAW中在这条store后面的所有已经完成访存的相同地址的load,以及load流水线中正在进行的在该条store之后的相同地址的load,一旦发现有,就发生了store to load forwarding违例,可能有多个load发生了违例,需要找到离store最近的load,也就是最老的违例的load,然后给RedirectGenerator部件发送重定向请求,冲刷最老的违例的load及之后的所有指令。
当store流水线执行cbo zero指令时,也需要进行store-load违例检查。
在现代处理器中,Load 和 Store 指令通常采用乱序执行的方式进行处理。这种执行策略旨在提高处理器的并行性和整体性能。然而,由于 Load 和 Store 指令在流水线中的乱序执行,常常会出现 Load 指令越过更早的相同地址的 Store 指令的情况。这意味着,Load 指令本应通过前递(forwarding)机制从 Store 指令获取数据,但由于 Store 指令的地址或数据尚未准备好,导致 Load 指令未能成功前递到 Store 的数据,而 Store 指令已被提交。由此,后续依赖于该 Load 指令结果的指令可能会出现错误,这就是 st-ld 违例。
考虑以下伪代码示例:
ST R1, 0(R2) ; 将 R1 的值存储到 R2 指向的内存地址
LD R3, 0(R2) ; 从 R2 指向的内存地址加载值到 R3
ADD R4, R3, R5 ; 使用 R3 的值进行计算
假设在这个过程中,Store 指令由于某种原因(如缓存未命中)未能及时完成,而 Load 指令已经执行并读取了旧的数据(例如,从内存中读取到的值为 0
)。此时,Load 指令并未获得 Store 指令更新后的值,导致后续计算的数据错误。
通过上述例子,可以清楚地看到 Store-to-Load 违例如何在乱序执行的环境中导致数据一致性问题。这种问题强调了在指令调度和执行过程中,确保正确的数据流动的重要性。现代处理器通过多种机制来检测和解决这种违例,以维护程序的正确性和稳定性。
LoadQueueRAW最多能够存储64条指令,通过FreeList子模块管理空闲资源。FreeList 中存储的是 entry 的编号。当一条指令满足入队条件时,FreeList 会为其分配一个 entry 编号,并将该指令存放在相应的 entry 中。指令出队时,需要释放所占用的 entry 资源,并将条目编号重新放回 FreeList 中,以供后续指令使用。Load指令在s2阶段在 LoadQueueRAR 中查询 store-to-load 违例,在s3阶段返回响应。
当query到达load流水线的s2时,判断是否满足入队条件,如果在当前load指令之前有地址未准备好的store指令,且当前指令没有被flush时,当前load可以入队。具体流程如下:
在 Store 指令到达 Store 流水线的 s1 阶段时,系统会进行 Store-to-Load 违例检查。此时,Store 指令需要与 Load Queue 中已经完成访存的 Load 指令,以及在 Load 流水线 s1 和 s2 阶段正在访存的 Load 指令进行比较。这些 Load 指令可能尚未通过前递(forwarding)机制获取 Store 指令执行的结果。
具体的违例检查流程如下:
datavalid
),或者由于缓存未命中正在等待数据回填(dcache miss
),则可以确定这些 Load 指令不会将数据前递给当前的 Store 指令。Load指令的出队需要满足以下条件其中之一:
出队执行的操作:
allocated
寄存器设置为低电平。这一操作的目的是标识该指令不再占用 LoadQueueRAR 的资源,从而为后续指令的入队和处理腾出空间。free
掩码拉高,表示该条目已被释放并可供后续使用。在load流水线的s3阶段可以向队列发送revoke信号撤销上一拍的请求。如果指令当前周期的revoke信号拉高(revoke ==1),并且在上一个周期已经入队,需要执行撤销操作:
name | I/O | width | Description | |
---|---|---|---|---|
redirect | io.redirect.valid | input | 1 | 后端重定向的有效位 |
io.redirect.bits.robIdx.flag | input | 1 | 后端重定向的flag,用于在循环列表中判断先后 | |
io.redirect.bits.robIdx.value | input | 8 | 后端重定向的位置value | |
io.redirect.bits.level | input | 1 | 后端重定向的level:1’b0:冲刷之后的指令;1‘b1:冲刷这条指令本身 | |
vecFeedback | io.vecFeedback_0/1.valid | input | 1 | 来自两条流水线的向量反馈信息有效位 |
io.vecFeedback_0/1.bits | input | 17 | 来自两条流水线的向量反馈信息 | |
query | io.query_0/1/2.req.ready | output | 1 | 能否接收3条数据通路中load违例检查请求 |
io.query_0/1/2.req.valid | input | 1 | 3条数据通路中load违例检查有效位 | |
io.query_0/1/2.req.bits.uop.robIdx.flag | input | 1 | 3条数据通路中load违例检查uop在rob中的flag | |
io.query_0/1/2.req.bits.uop.robIdx.value | input | 8 | 3条数据通路中load违例检查uop在rob中的value | |
io.query_0/1/2.req.bits.uop.lqIdx.flag | input | 1 | 3条数据通路中load违例检查uop在LoadQueue中的flag | |
io.query_0/1/2.req.bits.uop.lqIdx.value | input | 7 | 3条数据通路中load违例检查uop在LoadQueue中的value | |
io.query_0/1/2.req.bits.paddr | input | 48 | 3条数据通路中load违例检查的物理地址 | |
io.query_0/1/2.req.bits.data.valid | input | 1 | 3条数据通路中load违例检查data的有效 | |
io.query_0/1/2.revoke | input | 1 | 3条数据通路中load违例检查的撤销 | |
storeIn | storeIn_0/1.bits | input | 84 | 两条store流水线store指令相关信息 |
storeIn_0/1.valid | input | 1 | 两条store流水线store指令相关信息有效位 | |
rollback | rollback_0/1.valid | output | 1 | 两条store流水线回滚信息的有效性 |
rollback_0/1.bits | output | 31 | 两条store流水线回滚信息 | |
stAddrReadySqPtr | stAddrReadySqPtr | input | 7 | 指向 store 队列中已准备好的地址条目 |
stIssuePtr | stIssuePtr | input | 7 | 指向 store 队列中准备发射执行的指令条目 |
lqFull | lqFull | output | 1 | 判断队列是否满 |
本文档参考香山LSQ设计文档写成
本文档撰写的内容截至[ca892e73]
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
LoadQueueReplay 模块是现代处理器架构中用于处理 Load 指令重发的重要组成部分。它负责管理因各种原因而需要重发的 Load 指令,确保指令执行的正确性和高效性。
LoadQueueReplay 最多存放72条指令,涉及多个状态和存储的信息。其关键组成部分如下:
LoadReplayQueue 通过 FreeList 管理队列的空闲状态。FreeList 的大小等于 LoadReplayQueue 的项数,分配宽度为3(Load Unit 的数量),释放宽度为 4。同时,Free List 可以反馈 Load Replay Queue 的空余项数量以及是否已满的信息。除了FreeList,LoadQueueReplay还包含两个子模块:AgeDetector 和 LqVAddrModule,其中 AgeDetector 用于寻找一系列load replay queue项中最早入队的一项。
例如昆明湖V1的Load宽度为2,则会将load replay queue分为两半,从偶数项和奇数项中分别挑选一项最老的进行重发。LqVAddrModule 用于保存load replay queue项数个虚拟地址,读口和写口的数量均为Load的宽度(LoadUnit的数量)。
Field | Description |
---|---|
allocated | 是否已经被分配,也代表是否该项是否有效。 |
scheduled | 是否已经被调度,代表该项已经被选出,已经或即将被发送至LoadUnit进行重发。 |
uop | load指令执行包括的uop信息。 |
vecReplay | 向量load指令相关信息。 |
vaddrModule | Load指令的虚拟地址。 |
cause | 某load replay queue项对应load指令重发的原因,包括: - C_MA(位0): store-load预测违例 - C_TM(位1): tlb miss - C_FF(位2): store-to-load-forwarding store数据为准备好,导致失败 - C_DR(位3): 出现DCache miss,但是无法分配MSHR - C_DM(位4): 出现DCache miss - C_WF(位5): 路预测器预测错误 - C_BC(位6): Bank冲突 - C_RAR(位7): LoadQueueRAR没有空间接受指令 - C_RAR(位8): LoadQueueRAW没有空间接受指令 - C_NK(位9): LoadUnit监测到store-to-load-forwarding违例 - C_MF(位10): LoadMisalignBuffer没用空间接受指令 |
blocking | Load指令正在被阻塞。 |
strict | 访存依赖预测器判断指令是否需要等待它之前的所有store指令执行完毕进入调度阶段。 |
blockSqIdx | 与load指令有相关性的store指令的StoreQueue Index。 |
missMSHRId | load指令的dcache miss请求接受ID。 |
tlbHintId | load指令的tlb miss请求接受ID。 |
replacementUpdated | DCache的替换算法是否已经更新。 |
replayCarry | DCache的路预测器预测信息。 |
missDbUpdated | ChiselDB中Miss相关情况更新。 |
dataInLastBeatReg | Load指令需要的数据在两笔回填请求的最后一笔。 |
根据是否满足以下条件以及freelist是否可以分配的空闲槽位决定能否直接入队:
enq_X.valid
信号有效。enq.bits.rep_info.need_rep
)。LoadQueueReplay 中的指令出队分三拍:
在重发过程中,根据重发的原因和当前条件解锁相应项。在不满足解锁条件时,将会被阻塞,无法参与重发仲裁。其中C_BC(Dcache 块冲突)、C_NK(oad_unit 在 S1、S2 阶段发生 store-load 违例)、C_DR(Dcache miss 且 MSHR 满)、C_WF(路径预测失败)无需条件即可立即重发。
其他重发原因和对应的解锁条件如下:
sqIdx
与被阻塞的 Idx
相同,store 地址未发生 TLB miss。SeqIdx
在 store_queue 发送的 stAddrReadySqPtr
之前。SeqIdx
在 stAddrReadyVec
的向量组内。sqIdx
与被阻塞的 Idx
相同。SqIdx
在 store_queue 发送的 stDataReadySqPtr
之前。SeqIdx
在 stDataReadyVec
的向量组内。tl_d_channel.mshrid
与阻塞的 missMSHRId
相同。lqIdx
在 ldWbPtr
之前。lqIdx
在 stAddrReadySqPtr
之前。LoadQueueReplay有3种选择调度方式:
根据入队年龄
LoadQueueReplay使用3个年龄矩阵(每一个Bank对应一个年龄矩阵),来记录入队的时间。年龄矩阵会从已经准备好可以重发的指令中,选择一个入队时间最长的指令调度重发。
根据Load指令的年龄
LoadQueuReplay可以根据LqPtr判断靠近最老的load指令重发,判断宽度为OldestSelectStride=4。
DCache数据相关的load指令优先调度
LoadQueueReply首先调度因L2 Hint调度的重发(当dcache miss后,需要继续查询下级缓存L2 Cache。在L2 Cache回填前的2或3拍,L2 Cache会提前给LoadQueueReplay唤醒信号,称为L2 Hint)当收到L2 Hint后,LoadQueueReplay可以更早地唤醒这条因dcache miss而阻塞的Load指令进行重发。
如果不存在L2 Hint情况,会将其余Load Replay的原因分为高优先级和低优先级。高优先级包括因dcache缺失或st-ld forward导致的重发,而将其他原因归纳为低优先级。如果能够从LoadQueueReplay中找出一条满足重发条件的Load指令(有效、未被调度、且不被阻塞等待唤醒),则选择该Load指令重发,否则按照入队顺序,通过AgeDetector模块寻找一系列load replay queue项中最早入队的一项进行重发。
Load_unit s3过来的请求根据enq.bits.isLoadReplay判断是否是已经从replay_queue出队的序列,如果是已经出队的序列,根据是否needReplay和有异常做下一步的判断,如果有异常或者不需要重发则释放这个槽位,并从agedetector里面把该项出队,如果需要重发则将这个项对应的scheduled位置为false来参与后续的出队仲裁竞争。
从freelist中选出发给load unit的有效项,项数为load unit的宽度(即有几条load unit的流水线),根据优先级来进行出队。
第0拍将数据传递给第1拍由s0_can_go控制,当s0_can_go为1时才能将0拍得到的数据发给第一拍,s0_can_go有效的条件是s0被重定向或者s1_can_go为1。
第一拍从vaddr内部取出需要的虚拟地址,发给下一拍流水线。 ColdCouter的值在0到12之间,上一拍没有被阻塞并且整个过程没有发生重定向的时候,向load unit发送请求。
发送给下一拍流水线的数据受s1_can_go控制,s1_can_go为1的条件是:
ColdCouter的值在0到12之间 且 上一拍完成操作(未被阻塞)或者不需要发送数据两者之一。
发生数据的重定向。
name | I/O | description |
---|---|---|
redirect | input | 后端重定向相关信息 |
vecFeedback | input | 来自两条流水线的向量反馈信息 |
enq | input | 表示外部模块希望将 load 指令传递给当前模块,来自 load 指令流水线的 s3 级 |
storeAddrIn | input | 在一个时钟周期内接收多条 store 指令的地址信息,用于判断指令存储的地址是否已经准备好 |
storeDataIn | input | 在一个时钟周期内接收多条 store 指令的数据信息,用于判断指令存储的数据是否已准备好 |
replay | output | 用于处理 load 指令的重发请求,每个元素对应一个重发接口 |
tl_d_channel | input | 用于接收来自数据缓存(Dcache)的信息,在处理 load 指令时会使用该端口进行数据转发 |
stAddrReadySqPtr | input | 指向当前准备好地址的 store 指令 |
stAddrReadyVec | input | 向量中对应 store 指令的地址是否已经准备好 |
stDataReadySqPtr | input | 指向当前准备好数据的 store 指令 |
stDataReadyVec | input | 向量中对应 store 指令的数据是否已经准备好 |
sqEmpty | input | 当前 store 队列是否为空 |
lqFull | output | 当前 load 队列是否已满 |
ldWbPtr | input | 指向当前写回的load指令 |
rarFull | input | rar 队列是否已满 |
rawFull | input | raw 队列是否已满 |
l2_hint | input | 当 dcache miss 后,需要继续查询下级缓存 L2 Cache。在 L2 Cache 回填前的 2 或 3 拍,L2 Cache 会提前给 LoadQueueReplay 唤醒信号,称为 L2 Hint |
tlb_hint | input | 作用类似于 l2_hint,接收当前的 TLB 提示信息 |
tlbReplayDelayCycleCtrl | input | 控制 TLB 重发的延迟周期 |
本文档参考香山LSQ设计文档写成
本文档撰写的内容截至[66e9b546]
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
LoadQueueUncache 和 Uncache 模块,对于 uncache load 访问请求来说,起到一个从 LoadUnit 流水线到总线访问的中间站作用。其中 Uncache 模块,作为靠近总线的一方,主要用于处理 uncache 访问到总线的请求和响应。LoadQueueUncache 作为靠近流水线的一方,需要承担以下责任:
接收 LoadUnit 流水线传过来的 uncache load 请求。
选择已准备好 uncache 访问的 uncache load 请求 发送到 Uncache Buffer。
接收来自 Uncache Buffer 的处理完的 uncache load 请求。
将处理完的 uncache load 请求 返回给 LoadUnit。
LoadQueueUncache 结构上,目前有 4 项(项数可配)UncacheEntry,每一项独立负责一个请求并利用一组状态寄存器控制其具体处理流程;有一个 FreeList,管理各项分配和回收的情况。而 LoadQueueUncache 主要是协同 4 项的新项分配、请求选择、响应分派、出队等统筹逻辑。
UnCacheBuffer 最多存放4条指令,除了 FreeList 之外,另一个重要的子模块是 UncacheEntry,管理每个Uncahce请求,负责发起Uncache,写回Uncache数据。每个Entry内维护一个用于发起Uncache请求的状态机,状态机的状态转换图如下:
s_idl:该项还未发起一个MMIO请求。
s_req:向uncache模块发起MMIO请求,等待请求被接收。
s_resp:等待uncache模块的MMIO响应。
s_wait:等待将MMIO结果写回流水线。
LoadQueueUncache 负责接收来自 LoadUnit 0、1、2 三个模块的请求,这些请求可以是 MMIO 请求,也可以是 NC 请求。
首先,系统会根据请求的 robIdx 按照时间顺序(从最老到最新)对请求进行排序,以确保最早的请求能优先分配到空闲项,避免特殊情况下因老项回滚(rollback)而导致死锁。
进入入队处理的条件是:请求没有重发、没有异常,并且系统会根据 FreeList 中可分配的空闲项依次为请求分配项。
当 LoadQueueUncache 达到容量上限,且仍有请求未分配到项时,系统会从这些未分配的请求中选择最早的请求进行 rollback。
UncacheBuffer 的入队分为 s1 和 s2 两个阶段:
s1:
请求收集:通过 io.req.map(_.bits)
收集所有请求的内容,形成 s1_req
向量。
有效性标记:通过 io.req.map(_.valid)
收集所有请求的有效性,形成 s1_valid
向量。
s2:
执行入队操作,主要分为以下几步:
使用 RegEnable
将 s1 阶段的请求 s1_req
注册到 s2_req
,确保在请求有效时保持其状态。
通过以下条件生成s2_valid
向量,判断每个请求是否有效:
RegNext(s1_valid(i))
:确保请求在 s1 阶段有效。
!s2_req(i).uop.robIdx.needFlush(RegNext(io.redirect))
:确保请求的 ROB 索引不需要因重定向而被刷新。
!s2_req(i).uop.robIdx.needFlush(io.redirect)
:确保请求的 ROB 索引不需要因当前重定向而被刷新。
检查每个请求是否需要重发,结果存储在 s2_need_replay
向量中。
在 s2 阶段,使用 s2_enqueue
向量来决定哪些请求成功入队。入队条件包括:
s2_valid(w)
:请求在 s2 阶段有效。
!s2_has_exception(w)
:请求没有异常。
!s2_need_replay(w)
:请求不需要重发。
s2_req(w).mmio
:请求是一个内存映射 IO(MMIO)请求。
通过 enqValidVec
和 enqIndexVec
的有效管理,确保每个加载请求在满足有效性和可分配条件时能够正确地申请和分配FreeList槽位。
当一个项完成 Uncache 访问操作并返回给 LoadUnit ,或被 redirect 刷新时,则该项出队并释放 FreeList 中该项的标志。
具体流程如下:
计算freeMaskVec
掩码,用于标记每个槽位的释放状态,指示相应槽位是否可用。
如果当前条目被选择 (e.io.select
) 且其输出信号有效 (e.io.ldout.fire
),则对应槽位的释放状态被标记为 true
,表示该槽位可用。
如果接收到刷新信号 (e.io.flush
),同样将对应槽位的释放状态设置为 true
。
同一拍可能有多个项出队。返回给 LoadUnit 的请求,会在第一拍中选出,第二拍返回。
其中,可供处理 uncache 返回请求的 LoadUnit 端口是预先设定的。当前,MMIO 只返回到 LoadUnit 2;NC 可返回到 LoadUnit 1\2。在多个端口返回的情况下,利用 uncache entry id 与端口数的余数,来指定每个项可以返回到的 LoadUnit 端口,并从该端口的候选项中选择一个项进行返回。
第一拍先从当前已准备好 uncache 访问中选择一个,第二拍将其发送给 Uncache Buffer。发送的请求中,会标记选中项的 id,称为 mid 。其中是否被成功接收,可根据 req.ready 判断。
如果发送的请求被 Uncache Buffer 接收,那么会在接收的下一拍收到 Uncache 的 idResp。该响应中,包含 mid 和 Uncache Buffer 为该请求分配 entry id(称为 sid)。LoadQueueUncache 利用 mid 找到内部对应的项,并将 sid 存储在该项中。
待 Uncache Buffer 完成该请求的总线访问后,会将访问结果返回给 LoadQueueUncache。该响应中,包含 sid。考虑到 Uncache Buffer 的合并特性(详细入队合并逻辑见 Uncache),一个 sid 可能对应 LoadQueueUncache 的多个项。LoadQueueUncache 利用 sid 找到内部所有相关项,并将访问结果传递给这些项。
freelist 没有空闲表现导致 MMIO Load 进入 UncacheBuffer 失败时需要进行 rollback,此 时需要根据 robidx 选择不能入队的 MMIO 中最老的指令进行 rollback。整个流程分为以下几个周期:
Cycle 0:进行 uncache 请求入队。
Cycle 1:选择最旧的 uncache 加载请求。
Cycle 2:发出重定向请求。
从 load 流水线中选择最旧的 load 请求。
根据检测到的拒绝情况准备重定向请求。
如果重定向请求有效,则发出请求。
使用 selectOldestRedirect
函数来选择最旧的重定向请求,具体步骤如下:
比较向量生成:
compareVec
,用于判断请求的顺序,比较每个请求的 ROB 索引。生成独热编码结果:
resultOnehot
向量根据有效性和比较结果生成,标记出最旧的可重定向请求。name | I/O | description |
---|---|---|
redirect | input | 后端重定向相关信息 |
req | input | 接收写入请求 |
ldout | output | 写回 MMIO 数据接口,输出 MemExuOutput 类型的数据,处理与 MMIO 的写回操作 |
ld_raw_data | output | 读取原始数据输出接口 |
rob | input | 接收来自 ROB 的信号或数据 |
uncache | output | 发送数据或信号给 uncache 模块 |
rollback | output | 当 uncache 缓存满时,从前端进行回滚 |
本文档参考香山LSQ设计文档写成
本文档撰写的内容截至[ca892e73]
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
StoreQueue是一个队列,用来装所有的 store 指令,功能如下:
在跟踪 store 指令的执行状态
存储 store 的数据,跟踪数据的状态(是否到达)
为load提供查询接口,让load可以forward相同地址的store
负责 MMIO store和NonCacheable store的执行
将被 ROB 提交的 store 写到 sbuffer 中
维护地址和数据就绪指针,用于LoadQueueRAW的释放和LoadQueueReplay的唤醒
store进行了地址与数据分离发射的优化,即 StoreUnit 是 store 的地址发射出来走的流水线,StdExeUnit 是 store 的数据发射出来走的流水线,是两个不同的保留站,store 的数据就绪了就可以发射到 StdExeUnit,store 的地址就绪了就可以发射到 StoreUnit。
StoreQueue最多可以存放64条指令,store queue 中重要的状态位有:
StoreQueue支持处理非对齐的Store指令,每一个非对齐的Store指令占用一项,并在写入dataBuffer对地址和数据对齐后写入。
如图2所示,StoreQueue会给向量store指令预分配一些项。
StoreQueue通过vecMbCommit控制向量store的提交:
针对每个 store,从反馈向量 fbk 中获取相应的信息。
判断该 store 是否符合提交条件(valid 且标记为 commit 或 flush),并且检查该 store 是否与 uop(i) 对应的指令匹配(通过 robIdx 和 uopIdx)。只有当满足所有条件时,才会将该 store 标记为提交。判断VecStorePipelineWidth内是否有指令满足条件,满足则判断该向量store提交,否则不提交。
特殊情况处理(跨页 store 指令):
在特殊情况下(当 store 跨页且 storeMisalignBuffer 中有相同的 uop),如果该 store 符合条件io.maControl.toStoreQueue.withSameUop
,会强制将 vecMbCommit设置为 true,表示该 store 无论如何都已提交。
StoreQueue 每次最多会有 2 个 entry 入队,通过入队指针 enqPtrExt 控制。在 dispatch 阶段最多可以分配2个 entry,指针每次右移 1 位或 2 位。
通过比较入队指针 enqPtrExt 和出队指针 deqPtrExt 得出已经在队列中有效 entry。只有空闲的 entry 大于需要请求入队的指令时才会分配 entry 入队。
入队时设置 entry 的状态位 allocated 为 true,其他状态位都为 false。
StoreQueue 每次最多会有2个 entry 出队释放,通过输出指针 deqPtrExt 控制,每次指针右移一位或 2 位。
STQ 出队的触发信号是isbuffer(i).fire延后一拍的信号,因为 sbuffer 的写动作要用 2 拍完成,在 sbuffer 写完成之前 entry 不释放可以继续 forward 数据。
store 的地址从保留站发出来后会经过 StoreUnit 流水线,通过lsq/lsq_replenish总线接口在S1/S2把地址信息更新到store queue 中:
在store流水线s1阶段,获得 DTLB hit/miss 的信息, 以及指令的虚拟地址vaddr和物理地址paddr
在store流水线s2阶段,获得 mmio/pmp 信息,以及是否是mmio地址空间操作等信息
store 的数据是从与地址不同的保留站发出来的后经过StdExeUnit
流水线,通过storeDataIn
接口在S0/S1把数据写到对应的entry的datamodule
里:
S0:给datamodule
发写请求
S1:写入数据到datamodule
同时更新 entry 的datavalid
属性为True,接收 store 的mask到STQ 的Datamodule
store 的地址从保留站发出来之后会经过StoreUnit
流水线,s0_mask_out
在S0把地址中的mask信息更新到对应entry的datamodule
里。
io.forwrd.sqIdx
) 和 StoreQueue 的出栈指针比较,找出所有比 load 指令老的 storeQueue 中的 entry。以 flag 相同或不同分为2种情况:(1)same flag-> older Store范围是 (tail, sqIdx),如图3(a)所示
(2)different flags-> older Store范围是(tail, VirtualLoadQueueSize) +(0, sqIdx),如图3(b)所示
查询总线用va 和pa同时查询,如果发现物理地址匹配但是虚拟地址不匹配;或者虚拟地址匹配但是物理地址不匹配的情况就需要将那条 load 设置为 replayInst,等 load 到 ROB head 后replay。
如果只发现一笔 entry 匹配且数据准备好,则直接 forward
如果只发现一笔 entry 匹配且数据没有准备好,就需要让保留站负责重发
如果发现多笔匹配,则选择最老的一笔 store forward,StoreQueue以1字节为单位,采用树形数据选择逻辑,如图4
store 指令能被 load forward的条件:
SSID (Store-Set-ID) 标记了之前 load 预测执行失败历史信息,如果当前 load 命中之前历史中的SSID,会等之前所有 older 的 store 都执行完;如果没有命中就只会等pa相同的 older Store 执行完成。
MMIO 空间的 store 也只能等它到达 ROB 的 head 时才能执行,但是跟 load 稍微有些不同,store 到达 ROB 的 head 时,它不一定位于 store queue 的尾部,有可能有的 store 已经提交,但是还在 store queue 中没有写入到 sbuffer,需要等待这些 store 写到 sbuffer 之后,才能让这条 MMIO 的 store 去执行。
利用一个状态机去控制MMIO的store执行
s_idle:空闲状态,接收到MMIO的store请求后进入到s_req状态;
s_req:给MMIO通道发请求,请求被MMIO通道接受后进入s_resp状态;
s_resp:MMIO通道返回响应,接收后记录是否产生异常,并进入到 s_wb 状态
s_wb:将结果转化为内部信号,写回给 ROB,成功后,如果有异常,则进入s_idle, 否则进入到 s_wait 状态
s_wait:等待 ROB 将这条 store 指令提交,提交后重新回到 s_idle 状态
NonCacheable空间的store指令,需要等待上一个NonCacheable Store指令提交之后,才能从StoreQueue按序发送请求
利用一个状态机去控制NonCacheable的store执行
nc_idle:空闲状态,接收到NonCacheable的store请求后进入到nc_req状态;
nc_req:给NonCacheable通道发请求,请求被NonCachable通道接受后, 如果启用uncacheOutstanding功能,则进入nc_idle,否则进入nc_resp状态;
nc_resp:接受NonCacheable通道返回响应,并进入到nc_idle状态
StoreQueue采用提前提交的方式进行提交。
检查进入提交阶段的条件
(1)指令有效。
(2)指令的ROB对头指针不超过待提交指针。
(3)指令不需要取消。
(4)指令不等待Store操作完成,或者是向量指令
如果是CommitGroup的第一条指令, 则
(1)检查MMIO状态: 没有MMIO操作或者有MMIO操作并且MMIO store以及提交。
(2)如果是向量指令,需满足vecMbCommit条件。
如果不是CommitGroup的第一条指令,则:
(1)提交状态依赖于前一条指令的提交状态。
(2)如果是向量指令,需满足vecMbCommit条件。
提交之后可以按顺序写到 sbuffer, 先将这些 store 写到 dataBuffer 中,dataBuffer 是一个两项的缓冲区(0,1通道),用来处理从大项数 store queue 中的读出延迟。只有0通道可以编写未对齐的指令,同时为了简化设计,即使两个端口出现异常,但仍然只有一个未对齐出队。
写入有效信号生成
0通道指令存在非对齐且跨越16字节边界时:
(1) 0通道的指令已分配和提交
(2) dataBuffer的0,1通道能同时接受指令,
(3) 0通道的指令不是向量指令,并且地址和数据有效;或者是向量且vsMergeBuffer以及提交。
(4) 没有跨越4K页表;或者跨越4K页表但是可以被出队,并且1)如果是0通道:允许有异常的数据写入; 2)如果是1通道:不允许有异常的数据写入。
(5) 之前的指令没有NonCacheable指令,如果是第一条指令,自身不能是Noncacheable指令
否则,需要满足:
(1) 指令已分配和提交。
(2) 不是向量且地址和数据有效,或者是向量且vsMergeBuffer以及提交。
(3) 之前的指令没有NonCacheable和MMIO指令,如果是第一条指令,自身不能是Noncacheable和MMIO指令。
(4) 如果未对齐store,则不能跨越16字节边界,且地址和数据有效或有异常
地址拆分为高低两部分:
(1) 低位地址:8字节对齐地址
(2) 高位地址:低位地址加上8偏移量
数据拆分为高低两部分:
(1) 跨16字节边界数据:原始数据左移地址低4位偏移量包含的字节数
(2) 低位数据:跨16字节边界数据的低128位;
(3) 高位数据:跨16字节边界数据的高128位;
写入选择逻辑:
如果dataBuffer能接受非对齐指令写入,通道0的指令是非对齐并且跨越了16字节边界,则检查:
(1) 是否跨4K页表同时跨4K页表且可以出队: 通道0使用低位地址和低位数据写入dataBuffer; 通道1使用StoreMisaligBuffer的物理地址和高位数据写入dataBuffer
(2) 否则: 通道0使用低位地址和低位数据写入dataBuffer; 通道1使用高位地址和高位数据写入dataBuffer
(3) 如果通道指令没有跨越16字节并且非对齐,则使用16字节对齐地址和对齐数据写入dataBuffer
(4) 否则,将原始数据和地址写给dataBuffer
StoreQueue采用双阈值的方法控制强制刷新Sbuffer:上阈值和下阈值。
name | description |
---|---|
enq | 接收来自外部模块的信息,包含入队请求、控制信号等 |
brqRedirect | 分支重定向信号 |
vecFeedback | 向量反馈信息 |
storeAddrIn | store指令的地址 |
storeAddrInRe | store指令的地址,用于处理MMIO 和异常情况 |
storeDataIn | store指令的数据 |
storeMaskIn | 传递store掩码,从保留站(RS)发送到 Store Queue(SQ)。store掩码通常用于指示哪些字节在store操作中是有效的。 |
sbuffer | 存储已提交的 Store 请求到sbuffer |
uncacheOutstanding | 指示是否有未完成的uncached请求 |
cmoOpReg | 发送缓存管理操作请求 |
cmoOpResp | 接收缓存管理操作的响应 |
mmioStout | 写回uncache的存储操作的结果 |
forward | 查询forwarding信息 |
rob | 接收来自 ROB 的信号或数据 |
uncache | 发送数据或信号给 uncache 模块 |
flushSbuffer | 冲刷sbuffer缓冲区 |
sqEmpty | 标识store queue为空 |
stAddrReadySqPtr | 指向当前准备好地址的 store 指令 |
stAddrReadyVec | 向量中对应 store 指令的地址是否已经准备好 |
stDataReadySqPtr | 指向当前准备好数据的 store 指令 |
stDataReadyVec | 向量中对应 store 指令的数据是否已经准备好 |
stIssuePtr | 跟踪当前发出的store请求 |
sqCancelCnt | 指示在store queue中可以被取消的请求数量 |
sqDeq | 当前store queue中出队的请求位置 |
force_write | 是否强制写入存储操作 |
maControl | 与存储管理缓冲区(MA)进行控制信号的交互 |
本文档参考香山LSQ设计文档写成
本文档撰写的内容截至[ca892e73]
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
Virtualloadqueue是一个队列,用于存储所有load指令的微操作(MicroOp),并维护这些load指令之间的顺序,它的功能类似于重排序缓冲区(Reorder Buffer, ROB),但专注于load指令的管理。其主要功能是跟踪Load指令执行状态,以确保在并发执行的环境中,加载操作能够正确、有序地完成。
Virtualloadqueue最多可以存放72条指令,dispatch阶段最多支持6条指令同时入队,最多支持8条指令出队。Virtualloadqueue对于每一个 entry 中的 load 指令都有若干状态位来标识这个 load 处于什么状态:
allocated:该项是否分配了load,用于确定load指令的生命周期。
isvec:该指令是否是向量load指令。
committed: 该项是否提交。
在调度阶段,保留站通过入队(enq)总线向VirtualLoadQueue发起入队请求,最多支持六组并发请求。成功入队的条件包括以下几点:
成功入队之后,系统会执行以下操作:
在 load 流水线的s3阶段,load unit会将指令执行的信息通过总线 ldin 写回到 VirtualLoadQueue。具体写回信息包括:
写回需要满足的条件如下:
need_rep
信号为 0),否则将影响写回的正常进行。在满足写回条件后,系统将生成相应的写回响应,具体包括以下几个方面:
addrvalid
信号将被置为 1,表示地址信息有效。datavalid
信号将被置为 1,表示数据有效。ldin
总线的写使能信号 data_wen_dup
拉高时,将更新队列中的uop信息,以确保指令的状态及时反映。系统将addrvalid
和datavalid
分开进行处理是考虑到在一些情况下,地址可以被重用,而数据可能需要重新请求(如dcache miss/mmio/软件预取等)。分开标识可以减少流水线停顿,允许处理器在地址有效时继续执行其他指令,而不必等待数据有效性确认,从而优化整体性能。
name | I/O | width | description | |
---|---|---|---|---|
redirect | io.redirect.valid | input | 1 | 后端重定向有效位 |
io.redirect.bits.robIdx.flag | input | 1 | 后端重定向相关信息 | |
io.redirect.bits.robIdx.value | input | 8 | 后端重定向相关信息 | |
io.redirect.bits.level | input | 1 | 后端重定向相关信息 | |
enq | io.enq.canAccept | output | 1 | Lq能否接收派遣指令 |
io.enq.sqcanAccept | input | 1 | sq能否接收派遣至零 | |
io.enq.needAlloc_0~5 | input | 1 | ||
io.enq.req_0~5.valid | input | 1 | 入队请求的有效信号 | |
io.enq.req_0~5.bits.robIdx.flag | input | 1 | 入队请求ROB指针的flag | |
io.enq.req_0~5.bits.robIdx.value | input | 8 | 入队请求ROB指针的value | |
io.enq.req_0~5.bits.lqIdx.value | input | 7 | 入队请求lqidx的value | |
io.enq.req_0~5.bits.numLsElem | input | 5 | 1. 向量寄存器的总位宽为128位,每个向量元素的大小为8位,因此每个向量寄存器可以存储16个,numLsElem表示向量寄存器中元素的个数,因此位宽为5。 2. 如果是标量值零,numLsElem的值恒为5‘b1 3. 如果是向量指令,每个端口的numLsElem的最大值为[16 2 2 2 2 2] | |
ldin | io.ldin_0/1/2.valid | input | 1 | load写回到loadqueue的信息有效 |
io.ldin_0/1/2.bits.uop.cf.exceptionVec_3/4/5/13/21 | input | 1 | Load写回到流水线的指令发生异常 | |
io.ldin_0/1/2.bits.uop.robIdx_flag | input | 1 | load写回lq指令的rob指针的flag | |
io.ldin_0/1/2.bits.uop.robIdx_value | input | 8 | load写回lq指令的rob指针的value | |
io.ldin_0/1/2.bits.uop.lqIdx.value | input | 7 | load写回lq指令的lq指针的value | |
io.ldin_0/1/2.bits.miss | input | 1 | Load写回到Lq的指令发生cacheMiss | |
io.ldin_0/1.bits.tlbMiss | input | 1 | Load写回到Lq的指令发生tlbMiss | |
io.ldin_0/1/2.bits.mmio | input | 1 | Load写回到Lq的指令是MMIO指令 | |
io.ldin_0/1/2.bits.isPrefetch | input | 1 | 指令为预取操作,预取分为软件预取和硬件预取 | |
io.ldin_0/1/2.bits.isHWPrefetch | input | 1 | 指令为硬件预取 | |
io.ldin_0/1/2.bits.dcacheRequireReplay | input | 1 | Load写回到Lq的指令需要replay | |
io.ldin_0/1/2.bits.rep.info.cause_0~9 | input | 1 | Load写回到Lq的指令需要replay的原因: =0:st-ld violention predirect =1:tlb miss =2:st-ld forward =3:dcache replay =4:dcache miss =5:wpu predict fail =6:dcache bank conflict =7:RAR queue nack =8:RAW queue nack =9:st-ld violention | |
io.ldin_0/1/2.bits.data_wen_dup_1 | input | 1 | uop信息的写入使能信号 | |
ldWbPtr | io.ldWbPtr.flag | output | 1 | writeback指针的flag |
io.ldWbPtr.value | output | 7 | writeback指针的value | |
lqEmpty | io.lqEmpty | output | 1 | Lq是否空 |
lqDeq | io.lqDeq | output | 3 | 出队表项数量 |
lqCancelCnt | io.lqCancelCnt | output | 7 | 后端发生重定向时取消的load数量 |