前端模块验证文档
Frontend
- 1: FTQ概述
- 1.1: FTQ顶层
- 1.2: FTQ子队列
- 1.3: FTQ接收BPU分支预测结果
- 1.4: FTQ向IFU发送取指目标
- 1.5: IFU向FTQ写回预译码信息
- 1.6: FTQ接收后端重定向
- 1.7: FTQ接收IFU重定向
- 1.8: FTQ向后端发送取指目标
- 1.9: 执行单元修改FTQ状态队列
- 1.10: 冲刷指针和状态队列
- 1.11: FTQ向BPU发送更新与重定向信息
- 2: ICache
- 3: IFU
- 3.1: F3PreDecoder
- 3.2: FrontendTrigger
- 3.3: PredChecker
- 3.4: PreDecode
- 3.5: RVCExpander
- 4: ITLB
1 - FTQ概述
下文(包括所有的FTQ文档)中会提到一些关于BPU和IFU的相关知识,详情需要去查看对应的文档:
hint:建议先从BPU基础设计中着重理解以下概念:
- 什么是分支预测?
- 什么是分支预测块?一个有帮助的链接:预测块
- (可选)什么是重定向,什么是预测结果重定向?
- (可选)分支预测的流水级
简介
FTQ 是分支预测和取指单元之间的缓冲队列,它的主要职能是暂存 BPU 预测的取指目标,并根据这些取指目标给 IFU 发送取指请求。它的另一重要职能是暂存 BPU 各个预测器的预测信息,在指令提交后把这些信息送回 BPU 用作预测器的训练,因此它需要维护指令从预测到提交的完整的生命周期。另外,后端将存储来自FTQ的取指目标PC,便于自身读取。
![[Pasted image 20250222103931.png]]
模块之间的中转站
从上图,FTQ很大程度上相当于一个中转站,中间人的角色,一方面,它承担着BPU和IFU之间的交互,这通常是因为BPU预测的速度快于IFU取值执行,所以使用FTQ作为缓冲。另一方面,它承担着后端与前端的交互,比如把前端将要执行的pc交给后端去执行。
显然,FTQ的 中转远不止这么多,下面更具体地讨论一下FTQ怎么中转各个前端或后端模块的信息的。
BPU和FTQ
BPUtoFTQ:BPU会将分支预测结果和meta数据发给FTQ。
- 从分支预测结果中,我们可以提取出分支预测块对应的取值目标,比如,一个不跨缓存行且所有指令均为RVC指令的分支预测块对应的取值目标,是从分支预测块起始地址开始的以2B为间隔的连续16条指令。
- meta信息则存储了各个预测器相关的预测信息,由于BPU预测有三个流水级,每个流水级都有相应的预测器,所以只有到s3阶段才有可能收集到所有预测器的预测信息,直到此时FTQ才接受到完整的meta,这些信息会在该分支预测块的全部指令被后端提交时交给BPU进行训练
- FTBEntry:严格来说,它其实也是meta的一部分,但是因为更新的时候ftb_entry需要在原来的基础上继续修改,为了不重新读一遍ftb,另外给它存储一个副本。
FTQtoBPU:FTQ会将带元数据的训练信息和重定向信息发回给BPU
- 请参照BPU文档链接 BPU 模块整体对外接口 (PredirectIO)
FTQ和IFU
FTQtoIFU:FTQ会将存储的取值目标发往IFU进行取值译码和把后端的重定向信息也移交给IFU
- 取值目标同时也发给:
- toICache:同样的取值目标会被发给指令缓存单元,看对应的指令是否在缓存单元内存在,如果有会被直接发送给IFU加速取值效率
- toPrefetch: prefetch是ICache的一个组件,负责预取功能
- 转发后端重定向:
- 后端重定向不仅需要转发给BPU帮助其回到正确状态,也同时需要转发给IFU帮助其回到正确状态
IFUtoFTQ:IFU将预译码信息和重定向信息写回FTQ
- 预译码信息:包含分支预测块对应的预测宽度内所有指令的预译码信息 预测宽度:一个指令块预测块覆盖的指令范围,香山中是16条rvc指令
- 重定向信息其实也是根据预译码信息得到的:当预译码信息中指出预测块内某一条指令预测出错时,写回IFU重定向信息
后端和FTQ
FTQ到后端:FTQ会将存储的取值目标发往后端,后端存储 PC后,在本地即可进行读取取指目标。
- 除了IFU,预测块的取值目标也会发给后端,但这里有一点区别:IFU空闲时才能从FTQ中获取取值目标,但是后端会一直取得最新的预测块的取指目标
后端到FTQ:后端重定向和指令commit
- 后端重定向与更新:后端是实际执行指令的单元,通过后端的执行结果,才能确认一条指令是否执行错误,产生重定向,同时,在发生重定向时,根据后端实际执行结果生成更新信息。
- 指令commit:当一个分支预测块内的所有指令都被执行,在后端提交,这标志着FTQ队列中这个分支预测块对应的FTQ项已经结束了它的生命周期,可以从队列中移除了,这时候,我们就可以把它的更新信息发给FTQ了。
FTQ指针
FTQ的全名叫取值目标队列,队列中的一个项叫做FTQ项,BPU写入预测结果时是写入队列中哪个位置,IFU又是从哪个队列取FTQ项?这时候,我们需要一个FTQ指针去索引FTQ项,而由于和不同模块的交互需要索引不同的FTQ项,因此,有以下类型的FTQ指针,下面,由指令生命周期为例,大致介绍这些指针:
指令在 FTQ 中的生存周期
指令以预测块为单位,从 BPU 预测后便送进 FTQ,直到指令所在的预测块中的所有指令全部在后端提交完成,FTQ 才会在存储结构中完全释放该预测块所对应的项。这个过程中发生的事如下:
- 预测块从 BPU 发出,进入 FTQ,
bpuPtr
指针加一,初始化对应 FTQ 项的各种状态,把各种预测信息写入存储结构;如果预测块来自 BPU 覆盖预测逻辑,则恢复bpuPtr
和ifuPtr
- FTQ 向 IFU 发出取指请求,
ifuPtr
指针加一,等待预译码信息写回 - IFU 写回预译码信息,
ifuWbPtr
指针加一,如果预译码检测出了预测错误,则给 BPU 发送相应的重定向请求,恢复bpuPtr
和ifuPtr
- 指令进入后端执行,如果后端检测出了误预测,则通知 FTQ,给 IFU 和 BPU 发送重定向请求,恢复
bpuPtr
、ifuPtr
和ifuWbPtr
- 指令在后端提交,通知 FTQ,等 FTQ 项中所有的有效指令都已提交,
commPtr
指针加一,从存储结构中读出相应的信息,送给 BPU 进行训练
预测块 n
内指令的生存周期会涉及到 FTQ 中的 bpuPtr
、ifuPtr
、ifuWbPtr
和 commPtr
四个指针,当 bpuPtr
开始指向 n+1
时,预测块内的指令进入生存周期,当 commPtr
指向 n+1
后,预测块内的指令完成生存周期。
循环队列
FTQ队列实际上是一个循环队列,所有类型的FTQ指针都是同一类型,ftqPtr的value字段用来表示索引,flag字段则用来表示循环轮数,flag只有一位,进入新的循环时flag位翻转。 这样,我们就可以在一个有限的队列空间内不断更新新的项,以及正确进行比较,判断哪个项在队列中更靠前或者更靠后。
1.1 - FTQ顶层
简述
在FTQ概述中,我们已经知道了,FTQ的作用就是多个模块交互的中转站,大致了解了它接受其他模块的哪些信息,它如何接受并存储这些信息在FTQ中,并如何把这些存储信息传递给需要的模块。 下面我们来具体了解一下FTQ与其他模块的交互接口,我们会对这种交互有一个更具体的认识。
IO一览
模块间IO
- fromBpu:接受BPU预测结果的接口(BpuToFtqIO)
- fromIfu:接受IFU预译码写回的接口(IfuToFtqIO)
- fromBackend:接受后端执行结果和commit信号的接口(CtrlToFtqIO)
- toBpu:向BPU发送训练信息和重定向信息的接口(FtqToBpuIO)
- toIfu:向IFU发送取值目标和重定向信息的接口(FtqToIfuIO)
- toICache:向ICache发送取值目标的接口(FtqToICacheIO)
- toBackend:向后端发送取值目标的接口(FtqToCtrlIO)
- toPrefetch:向Prefetch发送取值目标的接口(FtqToPrefetchIO)
- mmio
其他
上述是主要的IO接口,此外,还有一些用于性能统计的IO接口,比如对BPU预测正确和错误结果次数进行统计,并进行转发的IO, 还有转发BPU各预测器预测信息的IO。
BpuToFtqIO
IfuToFtqIO
我们知道从IFU,我们会得到预译码信息和重定向信息,而后者其实也是从预译码信息中生成。所以从IFU到FTQ的接口主要就是用来传递预译码信息的
- pdWb:IFU向FTQ写回某个FTQ项的预译码信息
- 接口类型:PredecodeWritebackBundle
- 信号列表:
- pc:一个分支预测块覆盖的预测范围内的所有pc
- 接口类型:Vec(PredictWidth, UInt(VAddrBits.W))
- pd:预测范围内所有指令的预译码信息
- 接口类型:Vec(PredictWidth, new PreDecodeInfo)
- PreDecodeInfo:每条指令的预译码信息
- 接口类型:PreDecodeInfo
- 信号列表:
- valid:预译码有效信号
- 接口类型:Bool
- isRVC:是RVC指令
- 接口类型:Bool
- brType:跳转指令类型
- 接口类型:UInt(2.W)
- 说明:根据brType的值判断跳转指令类型
- b01:对应分支指令
- b10:对应jal
- b11:对应jalr
- b00:对应非控制流指令
- isCall:是Call指令
- 接口类型:Bool
- isRet:是Ret指令
- 接口类型:Bool
- valid:预译码有效信号
- ftqIdx:FTQ项的索引,标记写回到哪个FTQ项
- 接口类型:FtqPtr
- ftqOffset:由BPU预测结果得到的,在该指令块中指令控制流指令的位置(指令控制流指令就是实际发生跳转的指令)
- 接口类型:UInt(log2Ceil(PredictWidth).W)
- misOffset:预译码发现发生预测错误的指令在指令块中的位置
- 接口类型:ValidUndirectioned(UInt(log2Ceil(PredictWidth).W))
- 说明:它的valid信号拉高表示该信号有效,也就说明存在预测错误,会引发重定向
- cfiOffset:由预译码结果得到的,在该指令块中指令控制流指令的位置(指令控制流指令就是实际发生跳转的指令)
- 接口类型:ValidUndirectioned(UInt(log2Ceil(PredictWidth).W))
- target:该指令块的目标地址
- 接口类型:UInt(VAddrBits.W)
- 说明:所谓目标地址,即在指令块中有控制流指令时,控制流指令的地址,在没有控制流指令时,指令块顺序执行,该指令块最后一条指令的下一条指令
- jalTarget:jal指令的跳转地址
- 接口类型:UInt(VAddrBits.W)
- instrRange:有效指令范围
- 接口类型:Vec(PredictWidth, Bool())
- 说明:表示该条指令是不是在这个预测块的有效指令范围内(第一条有效跳转指令之前的指令)
- pc:一个分支预测块覆盖的预测范围内的所有pc
CtrlToFtqIO
后端控制块向FTQ发送指令提交信息,后端执行结果的接口。
- rob_commits:一个提交宽度内的RobCommitInfo信息。
- 接口类型:Vec(CommitWidth, Valid(new RobCommitInfo))
- 详情链接:RobCommitInfo
- redirect:后端提供重定向信息的接口。
- 接口类型:Valid(new Redirect)
- 详情链接:Redirect
- ftqIdxAhead:提前重定向的FTQ指针,将要重定向的FTQ项的指针提前发送
- 接口类型: Vec(BackendRedirectNum, Valid(new FtqPtr))
- 说明:虽然有三个接口,但实际上只用到了第一个接口,后面两个弃用了
- ftqIdxSelOH:独热码,本来是依靠该信号从提前重定向ftqIdxAhead中选择一个,但现在只有一个接口了,独热码也只有一位了。
- 接口类型:Valid(UInt((BackendRedirectNum).W))
- 说明:为了实现提前一拍读出在ftq中存储的重定向数据,减少redirect损失,后端会向ftq提前一拍(相对正式的后端redirect信号)传送ftqIdxAhead信号和ftqIdxSelOH信号。
FtqToBpuIO
FtqToICacheIO
FTQ向IFU发送取值目标,ICache是指令缓存,如果取值目标在ICache中命中,由ICache将指令发给IFU
- req:FTQ向ICache发送取值目标的请求
- 接口类型:Decoupled(new FtqToICacheRequestBundle)
- 信号列表:
- pcMemRead:FTQ针对ICache发送的取值目标,ICache通过5个端口同时读取取指目标
- 接口类型:Vec(5, new FtqICacheInfo)
- FtqICacheInfo: FTQ针对ICache发送的取值目标
- 信号列表:
- ftqIdx:指令块在FTQ中的位置索引
- 接口类型:FtqPtr
- startAddr:预测块起始地址
- 接口类型:UInt(VAddrBits.W)
- nextlineStart:起始地址所在cacheline的下一个cacheline的开始地址
- 接口类型:UInt(VAddrBits.W)
- 说明:通过startAddr(blockOffBits - 1)这一位(也就是块内偏移地址的最高位)可以判断该预读取pc地址是位于cacheline的前半块还是后半块,若是前半块,由于取值块大小为cacheline大小的一半,不会发生跨cacheline行
- ftqIdx:指令块在FTQ中的位置索引
- 信号列表:
- readValid: 对应5个pcMemRead是否有效
- backendException:是否有后端异常
- pcMemRead:FTQ针对ICache发送的取值目标,ICache通过5个端口同时读取取指目标
FtqToCtrlIO
FTQ向后端控制模块转发PC,后端将这些pc存储在本地,之后直接在本地读取这些pc 写入后端pc mem
- pc_mem_wen:FTQ向后端pc存储单元pc_mem写使能信号
- 接口类型:Output(Bool())
- pc_mem_waddr:写入地址
- 接口类型:Output(UInt(log2Ceil(FtqSize).W))
- pc_mem_wdata:写入数据,是一个指令块的取值目标
- 接口类型:Output(new Ftq_RF_Components),详见FTQ子队列相关介绍 写入最新目标
- newest_entry_en:是否启用
- 接口类型:Output(Bool())
- newest_entry_target:最新指令块的跳转目标
- 接口类型:Output(UInt(VAddrBits.W))
- newest_entry_ptr:最新指令块的索引值
- 接口类型: Output(new FtqPtr)
FtqToPrefetchIO
- req:FTQ向Prefetch发送取值目标的请求
- 接口类型:FtqICacheInfo
- flushFromBPU: 来自BPU的冲刷信息
- 接口类型:BpuFlushInfo
- 信号列表:
- s2 :BPU预测结果重定向(注意这种重定向是BPU自己产生的,与其他类型要做区分)发生在s2阶段时,此阶段的分支预测块的索引
- 接口类型:Valid(new FtqPtr)
- 说明:valid信号有效时,说明此时s2流水级分支预测结果与其s1阶段预测结果不一致,产生s2阶段重定向
- s3:BPU预测结果重定向(注意这种重定向是BPU自己产生的,与其他类型要做区分)发生在s3阶段时,此阶段的分支预测块的索引
- 接口类型:Valid(new FtqPtr)
- 说明:与s2类似
- 说明:发生预测结果重定向的时候,预取单元和IFU都可能会被冲刷,比如,如果发生s2阶段重定向,FTQ会比较发给IFU req接口中的ftqIdx和s2阶段预测结果的ftqIdx,如果s2阶段的ftqIdx不在req的ftqIdx之后,这意味着,s2阶段产生的预测结果重定向之前的错误预测结果s1阶段预测结果被发给IFU进行取指了,为了消除这种错误,需要向IFU发送s2阶段flush信号。
- s2 :BPU预测结果重定向(注意这种重定向是BPU自己产生的,与其他类型要做区分)发生在s2阶段时,此阶段的分支预测块的索引
- backendException:后端执行发生的异常
- 接口类型:UInt(ExceptionType.width.W)
- 说明:表示后端执行时发生异常的类型,有这样几种类型的异常:
def none: UInt = "b00".U(width.W)
def pf: UInt = "b01".U(width.W) // instruction page fault
def gpf: UInt = "b10".U(width.W) // instruction guest page fault
def af: UInt = "b11".U(width.W) // instruction access fault
1.2 - FTQ子队列
文档概述
请注意:从本篇开始,就涉及待验证的功能点和测试点了
在之前的介绍中,我们采用FTQ项这个术语描述描述FTQ队列中的每一个元素,实际上,这只是一种便于抽象的说法。
实际上的FTQ队列,是由好多个子队列共同构成的,一些子队列维护一类信息,另一些子队列维护另一类信息,相同ftqIdx索引的子队列信息共同构成一个完整的FTQ项。
为什么要把它们分开成多个子队列呢?因为某些模块只需要FTQ项中的某一些信息,比如IFU想要取值目标,它只需要专门存储取值目标的子队列提供的信息就行了。另外,在我们更改FTQ项的内容时,也只需要写入需要更新的子队列,比如IFU预译码写回时,只需要写回专门存储预译码信息的队列了。
下面来介绍一些FTQ的主要子队列,以及它们内部存储的数据结构。此外,FTQ还有一些存储中间状态的更小的队列
术语说明
名称 | 定义 |
---|---|
FTB项 | 分支预测结果的基本组成项,包含对预测块中分支指令和跳转指令的预测 |
取指目标 | 一个预测块内包含的所有指令PC,当然,它不是直接发送所有PC,而是发送部分信号,接收方可由该信号推出所有PC |
子模块列表
子模块 | 描述 |
---|---|
ftq_redirect_mem |
重定向存储子队列,存储来自分支预测结果的重定向信息 |
ftq_pd_mem | 预译码存储子队列,存储来自IFU的对指令块的预译码信息 |
ftb_entry_mem | FTB项存储子队列,存储自分支预测结果中的ftb项 |
ftq_pc_mem | 取指目标子队列,存储来自分支预测结果的取指目标 |
模块功能说明
1. ftq_redirect_mem存储重定向信息
ftq_redirect_mem是香山ftq的一个子队列。它记录了重定向需要的一些信息,帮助重定向回正确状态,这些信息来自于BPU分支预测中的RAS预测器,以及顶层的分支历史指针,如果想要了解,可以参考BPU的RAS子文档了解如何通过这些信息回溯到之前的状态。
它是一个寄存器堆,由64(FtqSize)个表项(Ftq_Redirect_SRAMEntry)构成。支持同步读写操作。有3个读端口和1个写端口,每个读端口负责与不同的模块交互。
1.1 ftq_redirect_mem读操作
- 读操作:
- 输入:
- 需要使能ren,这是一个向量,可指定任意读端口可读
- 对应接口:ren
- 从任意读端口中输入要读取的元素在ftq_redirect_mem中的地址,这是一个从0到ftqsize-1的索引
- 对应接口:raddr
- 需要使能ren,这是一个向量,可指定任意读端口可读
- 输出:
- 从发起输入的读端口对应的读出端口中读出Ftq_Redirect_SRAMEntry。
- 对应接口:rdata
- 从发起输入的读端口对应的读出端口中读出Ftq_Redirect_SRAMEntry。
- 输入:
1.2 ftq_redirect_mem写操作
- 写操作
- 输入:
- 需要使能wen,可指定写端口可写
- 对应接口:wen
- 向写端口中输入要写入的元素在ftq_redirect_mem中的地址,这是一个从0到ftqsize-1的索引
- 对应接口:waddr
- 向wdata中写入Ftq_Redirect_SRAMEntry
- 对应接口:wdata
- 需要使能wen,可指定写端口可写
- 输入:
- 多端口读:可以从多个读端口读取结果
每个子队列的读写基本都是类似的,后面不再赘述
Ftq_Redirect_SRAMEntry
ftq_redirect_mem存储的表项。继承自SpeculativeInfo,存储RAS预测器相关重定向信息,根据这些信息回溯到之前的状态
- sc_disagree:统计分支指令在sc预测器中预测是否发生错误
- 接口类型:Some(Vec(numBr, Bool()))
- 说明:Option 类型,表明这个值可能不存在,在非FPGA平台才有,否则为none
- 信号列表:
- SpeculativeInfo:推测信息,帮助BPU在发生重定向的时候回归正常的状态
- 接口列表:
- histPtr:重定向请求需要恢复的全局历史指针,可参见BPU顶层文档了解详情
- 接口类型:CGHPtr
- histPtr:重定向请求需要恢复的全局历史指针,可参见BPU顶层文档了解详情
- 说明:以下都属于RAS重定向信息,可参见BPU文档了解如何利用这些信息进行重定向
- ssp:重定向请求指令对应的 RAS 推测栈栈顶在提交栈位置的指针
- 接口类型:UInt(log2Up(RasSize).W)
- sctr:重定向请求指令对应的 RAS 推测栈栈顶递归计数 Counter
- 接口类型:RasCtrSize.W
- TOSW:重定向请求指令对应的 RAS 推测栈(队列)写指针
- 接口类型:RASPtr
- TOSR:重定向请求指令对应的 RAS 推测栈(队列)读指针
- 接口类型:RASPtr
- NOS:重定向请求指令对应的 RAS 推测栈(队列)读指针
- 接口类型:RASPtr
- topAddr:
- 接口类型:UInt(VAddrBits.W)
- ssp:重定向请求指令对应的 RAS 推测栈栈顶在提交栈位置的指针
- 接口列表:
- SpeculativeInfo:推测信息,帮助BPU在发生重定向的时候回归正常的状态
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1 | FTQ_REDIRECT_MEM | WRITE | 向单端口输入wen,waddr决定是否写入以及写入地址,写入wdata |
1.2 | FTQ_REDIRECT_MEM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
2. ftq_pd_mem存储预译码信息
由64(FtqSize)个表项(Ftq_pd_Entry)构成。支持同步读写操作。有2个读端口和1个写端口。具有读写使能信号。
存储来自IFU预译码的写回信息,它是一个寄存器堆,由64(FtqSize)个表项(Ftq_pd_Entry)构成。有2个读端口和1个写端口。
ftq_pd_mem直接接收来自IfuToFtqIO的信号,从中获取Ftq_pd_Entry,表示一个指令块对应的预译码信息表项。读取时获取预测块内某条指令的预测信息
Ftq_pd_Entry
- brMask:一个指令预测宽度内(16条rvc指令)的指令块中,哪些指令是分支指令
- 接口类型:Vec(PredictWidth, Bool())
- jmpInfo:jump信息,其值对应不同的jmp指令类型,表示指令块内jmp指令类型
- 接口类型:ValidUndirectioned(Vec(3, Bool()))
- 说明: jumpinfo有效的时候,第0位是0,表示jal指令,第0位是1,表示jalr指令,第1位是1,表示call指令,第二位是1,表示ret指令。
- jmpOffset:jmp指令在指令预测块中的偏移地址
- 接口类型: UInt(log2Ceil(PredictWidth).W)
- rvcMask:一个预测块内的指令(16条rvc指令)哪些是rvc指令
- 接口类型:Vec(PredictWidth, Bool())
2.1 ftq_pd_mem写操作
PredecodeWritebackBundle(IfuToFtqIO)如何写入ftq_pd_mem的一条Ftq_pd_Entry
Ftq_pd_Entry项的写入是通过PredecodeWritebackBundle这个接口进行写入的(其实也就是IfuToFtqIO) 从fromPdWb接口中接收信号生成表项:
- brmask:PredecodeWritebackBundle有一个预测块内的所有指令的预译码信息,当一条指令的预译码信息有效(valid)且是分支指令(is_br)时, bool序列对应位置的指令被判定为分支指令
- jumpInfo:
- valid:预测块内存在一条指令,其预译码信息有效(valid),且是jmp指令(isJal或者isJalr)时,jumpInfo有效
- bits:预测块内的第一条有效跳转指令的info,它是一个三位序列,从低到高(拉高)对应该指令被预译码为是isJalr,isCall,isRet
- jmpOffset:预测块内第一条有效jmp跳转指令的偏移
- rvcMask:原封不动接受同名信号
- jalTarget:原封不动接收同名信号
2.2 ftq_pd_mem写操作
ftq_pd_mem的一条Ftq_pd_Entry如何以PreDecodeInfo(to pd)的形式输出
PreDecodeInfo是一个Ftq_pd_Entry中的一条指令的预译码,需要输入offset,指定该预译码指令在预测块内的偏移
-
valid:直接set为1
-
isRVC:设置为rvcMask bool序列中对应偏移的值
-
isBr:设置为brMask bool序列中对应偏移的值
-
isJalr:输入的偏移量等于jumpOffset,且jumpInfo有效并指明该指令type是isJalr(jmpInfo.valid && jmpInfo.bits(0))
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
2.1 | FTQ_PD_MEM | WRITE | 向单端口输入wen,waddr决定是否写入以及写入地址,写入wdata |
2.2 | FTQ_PD_MEM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
3. ftb_entry_mem存储FTB项
有两个读端口,一个写端口,FtqSize个表项,存储的数据项为FTBEntry_FtqMem,FTBEntry_FtqMem与FTBEntry基本上是一致的。
FTBEntry_FtqMem
- brSlots:分支指令槽
- 接口类型:Vec(numBrSlot, new FtbSlot_FtqMem)
- FtbSlot_FtqMem:
- 信号列表:
- offset:给分支指令在相对于指令块起始地址的偏移
- 接口类型:UInt(log2Ceil(PredictWidth).W)
- sharing:对于tailSlot来说,启用sharing表示把这个slot让给分支指令来被预测
- 接口类型:Bool
- valid:预测槽有效
- 接口类型:Bool
- 说明:当slot有效时,我们才能说这条指令是br指令还是jmp指令
- offset:给分支指令在相对于指令块起始地址的偏移
- 信号列表:
- tailSlot:跳转指令槽
- 接口类型:FtbSlot_FtqMem
- FTBEntry_part:FTBEntry_FtqMem的父类,存储部分FTB信息,记录跳转指令的类型
- 信号列表:
- isCall:接口类型:Bool
- isRet:接口类型:Bool
- isJalr:接口类型:Bool
- 信号列表:
3.1 ftb_entry_mem读操作
除了读出FTB项之外,顶层还可以从FTBEntry_FtqMem获取以下有效信息,在这里我们不需要验证以下内容,但是在验证顶层的时候我们会用到以下内容,在此处提一下,此外,以下内容并不会生成具体的信号接口,而是产生相应的判断逻辑:
- jmpValid:预测块中jmp指令有效
- 说明:当tailslot有效且不分享给分支指令时,jmp有效
- getBrRecordedVec:三维向量,对于三个slot
- 说明:接收一个offset偏移,如果命中有效分支slot(或者sharing拉高的tailslot),对应slot的向量元素拉高。
- brIsSaved:给定offset的指令是否是分支指令
- 说明:采用slot预测结果来说明是不是分支指令,前提需要信号有效
- getBrMaskByOffset:
- 说明:在给定offset范围内的三个slot中的指令是否是有效分支指令,用一个三位maks表示
- newBrCanNotInsert:能否插入新的brSlot
- 说明:给定offset超过有效tailSlot对应的offset时,不能插入新的brSlot
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
3.1 | FTQ_ENTRY_MEM | WRITE | 向单端口输入wen,waddr决定是否写入以及写入地址,写入wdata |
3.2 | FTQ_ENTRY_MEM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
4. ftq_pc_mem存储取指目标
pc存储子队列。存储项为Ftq_RF_Components,用来读取取指信息,取值信息交给IFU进行取指。
Ftq_RF_Components
信号含义
- startAddr: 预测块的起始地址
- nexLineAddr: 预测块下一个缓存行的起始地址
- startAddr加上64个字节,一个缓存行的大小是64字节
- isNextMask: 一个预测宽度内的16条指令各自是否属于下一个预测块(在最新版本rtl中已被编译优化掉)
- 通过计算某条指令相对于预测块起始地址的偏移量(每条指令两个字节)得到偏移地址,该偏移地址的第4位(从0开始)为1,表示该指令属于下一个预测块。
- 进一步说,其实也就可以根据它判断该指令是否在预测块跨缓存行的时候判断该指令是否属于下一个cacheline了
- fallThruError :预测出的下一个顺序取指地址是否存在错误
4.1 ftq_pc_mem写操作
信息获取:上述信息都可以从一个单流水级分支预测结果 (BranchPredictionBundle)中获取。 获取方式:startAddr直接获取BranchPredictonBundle中的pc,fallThruError直接获取BranchPredictionBundle中的fallThruError。
4.2 ftq_pc_mem读操作
多端口读:ftq_pc_mem的每个读端口的读地址被直接连到各个FTQ指针的写入信号,这样做的目的,是可以及时的读取,从pc存储子队列读出的项一定是此时FTQ指针指向的项
读写时机
写入时机:BPU流水级的S1阶段,创建新的预测entry时写入 读出时机: 读数据每个时钟周期都会存进Reg。如果IFU不需要从bypass中读取数据,Reg数据直连给Icache和IFU,如果IFU不需要从bypass中读取数据,Reg数据直连给Icache和IFU
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
4.1 | FTQ_PC_MEM | WRITE | 向单端口输入wen,waddr决定是否写入以及写入地址,写入wdata |
4.2 | FTQ_PC_MEM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
5. ftq_meta_1r_sram存储meta信息
存储的数据为Ftq_1R_SRAMEntry,同样有FtqSize项 Ftq_1R_SRAMEntry接口列表
- meta:分支预测的meta数据
- ftb_entry:分支预测的FTB项 写入时机:在 BPU的s3阶段接收信息,因为对于一个指令预测块,只有在其s3阶段才能获取完整的mata信息,同样被接收的还有最后阶段ftqentry信息
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
5.1 | FTQ_META_1R_SRAM | WRITE | 向单端口输入wen,waddr决定是否写入以及写入地址,写入wdata |
5.2 | FTQ_META_1R_SRAM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
接口说明
Ftq_Redirect_SRAMEntry
ftq_redirect_mem存储的表项。继承自SpeculativeInfo,存储RAS预测器相关重定向信息,根据这些信息回溯到之前的状态
- sc_disagree:统计分支指令在sc预测器中预测是否发生错误
- 接口类型:Some(Vec(numBr, Bool()))
- 说明:Option 类型,表明这个值可能不存在,在非FPGA平台才有,否则为none
- 信号列表:
- SpeculativeInfo:推测信息,帮助BPU在发生重定向的时候回归正常的状态
- 接口列表:
- histPtr:重定向请求需要恢复的全局历史指针,可参见BPU顶层文档了解详情
- 接口类型:CGHPtr
- histPtr:重定向请求需要恢复的全局历史指针,可参见BPU顶层文档了解详情
- 说明:以下都属于RAS重定向信息,可参见BPU文档了解如何利用这些信息进行重定向
- ssp:重定向请求指令对应的 RAS 推测栈栈顶在提交栈位置的指针
- 接口类型:UInt(log2Up(RasSize).W)
- sctr:重定向请求指令对应的 RAS 推测栈栈顶递归计数 Counter
- 接口类型:RasCtrSize.W
- TOSW:重定向请求指令对应的 RAS 推测栈(队列)写指针
- 接口类型:RASPtr
- TOSR:重定向请求指令对应的 RAS 推测栈(队列)读指针
- 接口类型:RASPtr
- NOS:重定向请求指令对应的 RAS 推测栈(队列)读指针
- 接口类型:RASPtr
- topAddr:
- 接口类型:UInt(VAddrBits.W)
- ssp:重定向请求指令对应的 RAS 推测栈栈顶在提交栈位置的指针
- 接口列表:
- SpeculativeInfo:推测信息,帮助BPU在发生重定向的时候回归正常的状态
Ftq_pd_Entry
- brMask:一个指令预测宽度内(16条rvc指令)的指令块中,哪些指令是分支指令
- 接口类型:Vec(PredictWidth, Bool())
- jmpInfo:jump信息,其值对应不同的jmp指令类型,表示指令块内jmp指令类型
- 接口类型:ValidUndirectioned(Vec(3, Bool()))
- 说明: jumpinfo有效的时候,第0位是0,表示jal指令,第0位是1,表示jalr指令,第1位是1,表示call指令,第二位是1,表示ret指令。
- jmpOffset:jmp指令在指令预测块中的偏移地址
- 接口类型: UInt(log2Ceil(PredictWidth).W)
- rvcMask:一个预测块内的指令(16条rvc指令)哪些是rvc指令
- 接口类型:Vec(PredictWidth, Bool())
测试点总表
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1 | FTQ_REDIRECT_MEM | WRITE | 向单端口输入wen,waddr决定是否写入以及写入地址,写入wdata |
1.2 | FTQ_REDIRECT_MEM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
2.1 | FTQ_PD_MEM | WRITE | 向单端口输入wen,waddr决定是否写入以及写入地址,写入wdata |
2.2 | FTQ_PD_MEM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
3.1 | FTQ_ENTRY_MEM | WRITE | 向单端口输入wen,waddr决定是否写入以及写入地址,写入wdata |
3.2 | FTQ_ENTRY_MEM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
4.1 | FTQ_PC_MEM | WRITE | 向单端口输入wen,waddr决定是否写入以及写入地址,写入wdata |
4.2 | FTQ_PC_MEM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
5.1 | FTQ_META_1R_SRAM | WRITE | 向单端口输入wen,waddr决定是否写入以及写入地址,写入wdata |
5.2 | FTQ_META_1R_SRAM | READ | 向多端口中输入ren,raddr决定是否读以及读取地址,从rdata读取 |
附录
虽然列在附录,但实际上这段内容依然十分重要,当你需要的时候请一定要查看。
其余状态子队列
上述存储结构是FTQ中比较核心的存储结构,实际上,还有一些子队列用来存储一些状态信息,也同样都是存储ftqsize个(64)元素。主要有以下:
update_target:记录每个FTQ项的跳转目标,跳转目标有两种,一种是当该FTQ项对应的分支预测结果中指明的该分支预测块中执行跳转的分支指令将要跳转到的地址,另一种则是分支预测块中不发生跳转,跳转目标为分支预测块中指令顺序执行的下一条指令地址。
- 此外,与之配套的还有newest_entry_target,newest_entry_ptr用来指示新写入的跳转目标地址,和它对应的指令预测块或者说FTQ项的在FTQ中的位置,同时,有辅助信号newest_entry_target_modified和newest_entry_ptr_modified用来标识该地址的FTQ项跳转地址是否被修改。
写入时机:上一个周期的bpu_in_fire有效的时候,或者说相对于bpu_in_fire有效时延迟一个周期写入。
newest_entry_ptr,newest_entry_target:这几个内部信号,表明我们当前最新的有效FTQ项。BPU新的写入,重定向等等都会对最新FTQ项进行新的安排,在相应的文档中,对其生成方式做具体的描述。
cfiIndex_vec:记录每个FTQ项的发生跳转的指令cfi(control flow instruction)指令在其分支预测块中的位置 写入时机:相对于bpu_in_fire有效时延迟一个周期写入。
mispredict_vec:记录每个FTQ项的分支预测结果是否有误,初始化为false
pred_stage:记录每个FTQ项的分支预测结果是来自于哪个阶段 写入时机:相对于bpu_in_fire有效时延迟一个周期写入。
pred_s1_cycle:记录每个FTQ项的分支预测结果对应的s1阶段的分支预测结果生成的时间(cycle数) 写入时机:相对于bpu_in_fire有效时延迟两个周期写入。
commitStateQueueReg:记录每个FTQ项中对应的分支预测块中每条指令(一般是16条rvc指令,对应一个预测宽度)的提交状态,提交状态有c_empty ,c_toCommit ,c_committed ,c_flushed,依次用从0开始的从小到大的枚举量表示,初始化为c_empty状态 写入时机:相对于bpu_in_fire有效时延迟一个周期写入。
entry_fetch_status:记录每个FTQ项的分支预测结果是否被送到ifu中,该状态由两个枚举量f_to_send , f_sent来表示, 初始化为f_sent状态。 写入时机:上一个周期的bpu_in_fire有效的时候,相对于bpu_in_fire有效时延迟一个周期写入。 写入数据:写入f_to_send
entry_hit_status:记录每个FTQ项拿到的分支预测结果是否是ftb entry hit的,即生成该分支预测结果的时候是否是从FTB ( 预测结果生成:hit)(非必须了解)中,读取到了对应的记录表项。初始化为not_hit状态。 写入时机:当来自BPU的全局分支预测信息中s2阶段的分支预测结果有效时,写入s2阶段分支预测结果中指名的hit状态,因为FTB预测器是分支预测s2阶段开始生效的,在此时判断预测项是否在FTB缓存中命中
newest_entry_ptr,newest_entry_target这几个内部信号,它们不是队列,但是它们很重要,表明我们当前应该关注的最新的FTQ项及对应的跳转目标。BPU新的写入,重定向等等都会对最新FTQ项进行新的安排,在涉及到修改该信号的相应的文档中,对其生成方式做具体的描述。
1.3 - FTQ接收BPU分支预测结果
文档概述
BPU会将分支预测结果和meta数据发给FTQ。
- 从分支预测结果中,我们可以提取出分支预测块对应的取值目标,比如,一个不跨缓存行且所有指令均为RVC指令的分支预测块对应的取值目标,是从分支预测块起始地址开始的以2B为间隔的连续16条指令。
- meta信息则存储了各个预测器相关的预测信息,由于BPU预测有三个流水级,每个流水级都有相应的预测器,所以只有到s3阶段才有可能收集到所有预测器的预测信息,直到此时FTQ才接受到完整的meta,这些信息会在该分支预测块的全部指令被后端提交时交给BPU进行训练
- FTBEntry:严格来说,它其实也是meta的一部分,但是因为更新的时候ftb_entry需要在原来的基础上继续修改,为了不重新读一遍ftb,另外给它存储一个副本。
术语说明
名称 | 定义 |
---|---|
BPU (Branch Prediction Unit) | 分支预测单元 |
FTQ (Fetch Target Queue) | 采集目标队列 |
IFU (Instruction Fetch Unit) | 指令采集单元 |
RAS (Return Address Stack) | 返回地址堆 |
FTQ Entry | FTQ队列中的单个表项 |
模块功能说明
1. 新的预测块进队条件
1.1 成功接收数据
1.1.1 FTQ准备好接收信号
- FTQ准备好接收信号: 当FTQ队列中元素小于FtqSize或者可以提交指令块(canCommit拉高,说明可以提交指令块,在后面的文档: FTQ向BPU发送更新信息中介绍怎么判断是否可以提交指令块)的时候,来自BPU的新的指令预测块可以进入FTQ队列,队列准备好接收新的预测块,fromBpu的resp接口ready信号拉高。
1.1.2 BPU准备好要发送的信号
- BPU准备好要发送的信号: 当BPU发往FTQ的接口vaid信号拉高,表示发送信号准备好
满足以上两个条件时,fromBpu的resp接口fire,表示接口数据被成功发送到FTQ中。
1.2 允许BPU入队allowBpuIn
- 重定向发生时,会回滚到之前的状态,新发送的BPU预测信息自然就不需要了。允许BPU入队时不能发生重定向
1.2.1 后端重定向发生
- 后端重定向发生:
- 标志:接收后端写回信息的接口fromBackend的重定向接口redirect有效,则该周期不允许入队,如果没有发生真实提前重定向realAhdValid(参见FTQ接收后端重定向一文),则下一个周期也不允许入队。
1.2.2 IFU重定向发生
- IFU重定向发生:
- 标志:IFU重定向信息生成的两个周期,均不许入队(参见FTQ接收IFU重定向一文了解IFU重定向信息的生成)
只要避免上述两种重定向出现的情况,就可以允许BPU入队,即可以把发送到FTQ的数据,写入FTQ项
1.3 以BPU预测结果重定向的方式入队
上述的BPU入队方式是一个全新的预测块进队,即BPU分支预测的s1阶段结果入队,此时未发生预测结果重定向。
当BPU发生预测结果重定向时,只要允许BPU入队allowBpuIn,也可以看作预测结果入队,不过这种入队是覆写队列中已有的FTQ项,没有写入新的指令块。
- BPU预测结果发生重定向的具体标志:fromBpu的resp接口的s2(s2阶段的预测信息)有效,且s2的hasRedirect拉高,表示在s2阶段发生了重定向,s3阶段重定向是一样的。
综合两种形式的BPU入队,这里称之为广义BPU入队方便区分,记为bpu_in_fire,该信号拉高,表明发生广义BPU入队。
2. 写入FTQ项
之前已经说明过了,FTQ项只是一个抽象的概念,FTQ有很多个子队列组成,它们的项共同构成一个FTQ项,所以,向FTQ中写入FTQ项,实际上就是就是把BPU的预测信息写到对应的FTQ子队列中。
FTQ主要获取以下信息作为bpu_in_resp
- bpu_in_resp:BPU交给FTQ的resp详见BPU文档,resp中含有s1,s2,s3三个阶段的指令预测信息,bpu_in_resp将获取其中某一阶段预测信息selectedResp作为其值。未发生重定向时,使用s1作为预测结果,s2或者s3发生重定向信息时,优先s3的预测信息作为selectedResp。某阶段发生重定向的标志与上文讲述的一样一样。 从selectedResp(bpu_in_resp)中,我们还可以获取以下目标信息帮助我们写入子队列:ftq_idx,帮助我们索引写入子队列的地址
2.1 写入FTQ子队列:
2.1.1 写入ftq_pc_mem
- ftq_pc_mem: 来自BPU的selectedResp预测信息被写入ftq_pc_mem, 该存储结构有ftqsize个表项,对应队列中的所有ftq表项,每个存储元素可以推出对应的ftq表项中每条指令的pc地址
接收信号列表:
- wen:接收bpu_in_fire作为写使能信号
- waddr:接收selectedResp的ftq_idx
- wdata:selectedResp的相应信号
2.1.2 写入ftq_redirect_mem
- ftq_redirect_mem: 在BPU的s3(也就是最终阶段)接收信息,因为重定向信息只有在s3阶段才能得到。里面存储了RAS重定向相关的信息帮助BPU进行重定向。
接收信号列表:
- wen:从BPU(fromBpu)回应(resp)的lastStage有效信号
- waddr:从BPU回应的lastStage的ftq_idx.value
- wdata:从BPU回应的last_stage_spec_info
2.1.3 写入ftq_meta_1r_sram
- ftq_meta_1r_sram:在 BPU的s3阶段接收信息,同样是因为对于一个指令预测块,只有在其s3阶段才能获取完整的mata信息,同样被接收的还有最后阶段ftqentry信息
接收信号列表:
- wen:从BPU(fromBpu)回应(resp)的lastStage有效信号
- waddr:从BPU回应的lastStage的ftq_idx的value
- wdata:
- meta:从BPU回应的last_stage_meta
- ftb_entry:从BPU回应的last_stage_ftb_entry
2.1.4 写入ftb_entry_mem
- ftb_entry_mem:虽然ftq_meta_1r_sram中存储有最后阶段ftbentry,但此处出于更高效率读取专门把它存在ftb_entry_mem中。
接收信号列表:
- wen:从BPU(fromBpu)回应(resp)的lastStage有效信号
- waddr:从BPU回应的lastStage的ftq_idx的value字段
- wdata:从BPU回应的last_stage_ftb_entry 从中可以看到,FTQ虽然名字上听起来是一个队列,实际上内部却是由数个队列组成,他们共同构成了FTQ这个大队列
2.2 写入状态队列
上述存储结构是FTQ中比较核心的存储结构,实际上,还有一些子队列用来存储一些状态信息,也同样都是存储ftqsize个(64)元素,需要被写入,写入时机是在发生bpu_in_fire的下一个周期,或者再下一个周期 。主要有以下:
2.2.1 写入update_target
update_target:记录每个FTQ项的跳转目标,跳转目标有两种,一种是当该FTQ项对应的分支预测结果中指明的该分支预测块中执行跳转的分支指令将要跳转到的地址,另一种则是分支预测块中不发生跳转,跳转目标为分支预测块中指令顺序执行的下一条指令地址。
- 此外,与之配套的还有newest_entry_target,newest_entry_ptr用来指示bpu_in_resp推出的跳转目标地址,表示下一次预测时开始的目标地址,和它对应的bpu_in_resp指令预测块在FTQ中的位置。
- 同时,有辅助信号newest_entry_target_modified和newest_entry_ptr_modified用来标识该这两个字段是否被修改。
- 写入时机:相对于bpu_in_fire有效时延迟一个周期写入。
- 写入地址:bpu_in_resp记录的要写入FTQ的地址
- 写入数据:bpu_in_resp.getTarget
2.2.2 写入cfiIndex_vec
cfiIndex_vec:记录每个FTQ项的发生跳转的指令cfi(control flow instruction)指令在其分支预测块中的位置
- 写入时机:相对于bpu_in_fire有效时延迟一个周期写入。
- 写入地址:bpu_in_resp记录的要写入FTQ的地址
- 写入数据:bpu_in_resp推断出的跳转目标
2.2.3 写入mispredict_vec
mispredict_vec:记录每个FTQ项的所有指令的预测结果是否有误,初始化为false
- 写入时机:相对于bpu_in_fire有效时延迟两个周期写入。
- 写入地址:bpu_in_resp记录的要写入FTQ的地址
- 写入数据:将该指令块的所有预测结果对应的值设置为false
2.2.4 写入pred_stage
pred_stage:记录每个FTQ项的分支预测结果是来自于哪个阶段
- 写入时机:相对于bpu_in_fire有效时延迟一个周期写入。
- 写入地址:bpu_in_resp记录的要写入FTQ的地址
写入pred_s1_cycle(不需要测试)
pred_s1_cycle:记录每个FTQ项的分支预测结果对应的s1阶段的分支预测结果生成的时间(cycle数)
- 写入时机:相对于bpu_in_fire有效时延迟两个周期写入。
- 写入地址:bpu_in_resp记录的要写入FTQ的地址
2.2.5 写入commitStateQueueReg
commitStateQueueReg:记录每个FTQ项中对应的分支预测块中每条指令(一般是16条rvc指令,对应一个预测宽度)的提交状态,提交状态有c_empty ,c_toCommit ,c_committed ,c_flushed,依次用从小到大的枚举量表示,初始化为c_empty状态
- 写入时机:相对于bpu_in_fire有效时延迟一个周期写入。
- 写入数据:写入c_empty
- 写入地址:bpu_in_resp记录的要写入FTQ的地址
2.2.6 写入entry_fetch_status
entry_fetch_status:记录每个FTQ项的分支预测结果是否被送到ifu中,该状态由两个枚举量f_to_send , f_sent来表示, 初始化为f_sent状态。
- 写入时机:相对于bpu_in_fire有效时延迟一个周期写入。
- 写入数据:写入f_to_send
- 写入地址:bpu_in_resp记录的要写入FTQ的地址
2.2.7 写入entry_hit_status
entry_hit_status:记录每个FTQ项拿到的分支预测结果是否是ftb entry hit的,即生成该分支预测结果的时候是否是从ftb中,读取到了对应的记录表项。初始化为not_hit状态。
- 写入时机:当来自BPU的全局分支预测信息中s2阶段的分支预测结果有效时,写入s2阶段分支预测结果中指名的hit状态
- 写入地址:bpu_in_resp记录的要写入FTQ的地址
- 写入数据:f_to_send
注:之所以延迟时钟周期写入,是为了缩短关键路径,以及帮助减少扇出
3 转发分支预测重定向:
3.1 转发给IFU
- s2以及s3阶段的预测重定向信息通过FTQ与Ifu的接口toIfu的flushFromBpu发送给IFU,当完整分支预测结果中的s2阶段分支预测结果发生预测结果重定向时,flushFromBpu.s2.valid拉高,flushFromBpu.s2.bits接收s2阶段分支预测结果中指明的该分支预测结果在FTQ中的位置ftq_idx。
3.2 转发给预取
- 该重定向信号同样会通过toPrefetch.flushFromBpu接口以相同的方式传递给Prefetch s3阶段向IFU以及Prefetch的重定向传递与s2阶段的重定向信号传递一样。该阶段的重定向信号传递会覆盖可能的s2阶段重定向信号传递结果
4 修正FTQ指针
此外,分支预测结果重定向也会影响ifuPtr与pfPtr两个指针信号的写入信号。
4.1 正常修改
- 正常情况下,allowToIfu(条件和allowToBpu一样),同时BPU向Ifu发送FTQ项的io接口toIfu.req发生fire的时候,ifuPtr寄存器中写入ifuPtr+1。同样发生修改的还有pfPtr,当allowToIfu,同时BPU向Prefetch发送FTQ项的io接口totoPrefetch.req发生fire的时候。
4.2 发生重定向时修改
- 而如果是发生重定向的时候,比如s2阶段预测结果发生重定向,此时,若ifuPtr不在s2阶段预测结果中指明的ftq_idx之前,ifuPtr写入该ftq_idx,pfPtr_write同样如此
bpuptr: 由FTQ交给BPU用于指示新的指令预测块应该放到FTQ队列中的位置,上述存储结构,ftq_pc_mem,ftq_redirect_mem,ftq_meta_1r_sram,ftb_entry_mem基本上也是通过与该指针相关的信号得知信息应该存储的addr(bpuptr交给BPU,BPU基于此获知每个阶段预测结果的ftq_idx)。
bpuptr寄存器的输出值直接连到FTQ发往BPU的接口toBpu的enq_ptr字段中,当然,再次之前,bpuptr的值会根据实际情况修改。
在enq from bpu的过程中,正常情况下,发生enq的时候,也就是新的预测块进队时,bpuptr+1,BPU将要向FTQ中写入的位置前进一位
但是,如果发生重定向的时候,比如,如果s2阶段预测结果发生重定向,bpuptr被更新为s2阶段分支预测结果的ftq_idx+1,表示BPU将要向FTQ中写入的位置为s2阶段预测结果在FTQ中位置的后一位,因为此时新的全局预测结果会基于s2的预测结果展开下一轮预测(即以s2分支预测块的下一块展开预测,自然会被写入),该结果会覆盖enq_fire发生时的结果,此外s3阶段的分支预测重定向时,会覆盖可能的s2阶段重定向修改的bpuptr
其他的ftq指针也是类似的,用于指示写入FTQ的地址
接口说明
FTQ接收BPU分支预测结果工程中涉及到的IO接口如下,在FTQ顶层IO一文中有详细说明
接口 | 作用 |
---|---|
fromBackend | 根据是否有重定向确认是否允许BPU预测结果入队 |
fromBPU | 接收BPU预测结果 |
toIfu | 发送更新的IFU指针,转发BPU预测结果重定向 |
toPrefetch | 发送更新的Prefetch指针,转发BPU预测结果重定向 |
toBpu | 发送更新的BPU指针 |
测试点总表
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1.1 | BPU_IN_RECEIVE | FTQ_READY | 当FTQ队列中元素小于FtqSize或者可以提交指令块的时候,队列准备好接收新的预测块 |
1.1.2 | BPU_IN_RECEIVE | BPU_VALID | BPU准备好要发送的信号 |
1.2.1 | BPU_IN_ALLOW | BACKEND | 接收后端写回信息的接口fromBackend的重定向接口redirect有效,则该周期不允许入队,如果没有发生真实提前重定向,则下一个周期也不允许入队 |
1.2.2 | BPU_IN_ALLOW | IFU | IFU重定向信息生成的两个周期,均不许入队 |
1.3.1 | BPU_IN_BY_REDIRECT | REDIRECT | 当BPU发生预测结果重定向时,只要允许BPU入队allowBpuIn,也可以看作预测结果入队 |
2.1.1 | WRITE_FTQ_SUBQUEUE | FTQ_PC | 根据BPU预测结果写入ftq_pc_mem |
2.1.2 | WRITE_FTQ_SUBQUEUE | FTQ_REDIRECT | 根据BPU预测结果写入ftq_redirect_mem |
2.1.3 | WRITE_FTQ_SUBQUEUE | FTQ_MATA | 根据BPU预测结果写入ftq_meta_1r_sram |
2.1.4 | WRITE_FTQ_SUBQUEUE | FTQ_ENTRY | 根据BPU预测结果写入ftb_entry_mem |
2.2.1 | WRITE_FTQ_STATEQUEUE | UPDATED_TARGET | 根据BPU预测结果写入update_target |
2.2.2 | WRITE_FTQ_STATEQUEUE | CFIINDEX | 根据BPU预测结果写入cfiIndex_vec |
2.2.3 | WRITE_FTQ_STATEQUEUE | MISPREDICT | 根据BPU预测结果写入mispredict_vec |
2.2.4 | WRITE_FTQ_STATEQUEUE | PRED_STAGE | 根据BPU预测结果写入pred_stage |
2.2.5 | WRITE_FTQ_STATEQUEUE | COMMITSTATE | 根据BPU预测结果写入commitStateQueueReg |
2.2.6 | WRITE_FTQ_STATEQUEUE | ENTRY_FETCH_STATU | 根据BPU预测结果写入entry_fetch_status |
2.2.7 | WRITE_FTQ_STATEQUEUE | ENTRY_HIT_STATU | 根据BPU预测结果写入entry_hit_status |
3.1 | TRANSFER_BPU_REDIRECT | IFU | 转发分支预测重定向给IFU |
3.2 | TRANSFER_BPU_REDIRECT | PREFETCH | 转发分支预测重定向给PREFETCH |
4.1 | UPDATE_FTQ_PTR | NORMAL | 正常情况下修改FTQ指针 |
4.2 | UPDATE_FTQ_PTR | REDIRECT | 发生重定向时修改FTQ指针 |
1.4 - FTQ向IFU发送取指目标
文档概述
IFU需要取FTQ中的项进行取指令操作,同时也会简单地对指令进行解析,并写回错误的指令 FTQ发送给IFU的信号同时也需发送给ICache一份,ICache是指令缓存,帮助快速读取指令。
术语说明
- ifuPtr:该寄存器信号指示了当前FTQ中需要读取的项的指针。直接发送给io.toIfu.req接口的ftqIdx。
- entry_is_to_send:entry_fetch_status存储每个FTQ项的发送状态,初始化并默认为当前ifuptr指向的项对应的发送状态,后续可能因为旁路逻辑等改变
- entry_ftq_offset: 从cfiIndex_vec中初始化并默认为当前ifuptr指向项的跳转指令在预测块中的偏移,后续可能因为旁路逻辑等改变
- entry_next:本次取指结束后下一次取值的开始地址
- pc_mem_ifu_ptr_rdata:获取ifuptr指向FTQ项的取指信息(从ftq_pc_mem的读取接口ifuPtr_rdata中获取)
- pc_mem_ifu_plus1_rdata:获取ifuptr+1指向FTQ项的pc相关信息(从ftq_pc_mem的读取接口ifuPtrPlus1_rdata中)
- copied_ifu_plus1_to_send:多个相同的复制信号,entry_fetch_status中指向ifuPtrPlus1的项是f_to_send状态或者上一周期bpu_in_fire,同时旁路bpu指针bpu_in_bypass_ptr等于ifuptr+1时,信号copied_ifu_plus1_to_send在一周期后拉高
- copied_ifu_ptr_to_send:同理,只是把ifuptr+1改成了ifuptr
模块功能说明
1. 获取取指目标信息
获取取指目标有两个来源,一个是BPU写入信息时,直接将取指目标旁路出来,一种则是从存储取指目标的队列ftq_pc_mem中读取。使用前一种方式的前提,是刚好ifuPtr指向的读取项刚好就是旁路指针信号bpu_in_resp_ptr(BPU入队时写入项的ftqIdx)
- 旁路逻辑:pc信号在被写入存储子队列时就被旁路一份,写入信号ftq_pc_mem.io.wdata在bpu_in_fire信号拉高时被旁路到旁路信号寄存器bpu_in_bypass_buf中。同时被旁路的还有指针信号bpu_in_resp_ptr,在同样的条件下被旁路到寄存器bpu_in_bypass_ptr中
- 读取ftq_pc_mem: 存储pc相关的取指目标,该存储队列有多个读接口,对所有ftqptr的写入信号(比如ifuPtr_write, ifuPtrPlus1_write等)被直接连接到存储队列的读取接口,这样,在ftqPtr寄存器正式被更新时,就可以同时直接从对应的读取接口中返回对应指针的读取结果,比如ftq_pc_mem.io.ifuPtr_rdata
1.1 准备发往ICache的取指目标
有以下三种情况,分别对应测试点1.1.1,1.1.2,1.1.3
- 旁路生效,即旁路bpu指针等于ifuptr,且上一周期bpu输入有效结果(last_cycle_bpu_in表示上一周期bpu_in_fire)有效(也就相当于该旁路指针是有效的),此时,直接向toICache接口输入旁路pc信息bpu_in_bypass_buf
- 不满足情况1,但是上一周期发生ifu_fire(即FTQ发往IFU的接口发生fire),成功传输信号,此toICache中被写入pc存储子队列ftq_pc_mem中ifuptr+1对应项的结果,这是因为此时发生了ifu_fire,新的ifuptr还未来得及更新(即加1),所以直接从后一项中获取新的发送数据
- 前两种情况都不满足,此时toICache接口中被写入pc存储队列中ifuptr对应项的结果
1.2 提前一周期准备发往Prefetch的取指目标
有以下三种情况,分别对应测试点1.2.1,1.2.2,1.2.3 同样有三种情况:
- bpu有信号写入(bpu_in_fire),同时bpu_in_resp_ptr等于pfptr的写入信号pfptr_write, (此时pfptr_write还没有正式被写入pfptr中),读取bpu向pc存储队列的写入信号wdata,下一周期写入ToPrefetch xxxptr_write:是相应FTQptr寄存器的write信号,连接到寄存器的写端口,寄存器在时钟上升沿成功写入write信号
- 不满足情况1,且由bpu到prefetch的接口发生fire,即bpu向预取单元成功发送信号,pc存储单元的pfPtrPlus1_rdata下一周期写入ToPrefetch接口,选择指针加1对应项的原因与toICache类似。
- 不满足以上两种情况:pc存储单元的pfPtr_rdata在下一周期被写入ToPrefetch接口
1.3 设置下一个发送的指令块的起始地址
有以下三种情况,分别对应测试点1.3.1,1.3.2,1.3.3
target(entry_next_addr)旁路逻辑: 有三种情况:
- 上一周期bpu写入信号,且旁路指针等于ifuptr:
- toIfu:写入旁路pc信息bpu_in_bypass_buf
- entry_is_to_send :拉高
- entry_next_addr :bpu预测结果中跳转地址last_cycle_bpu_target
- entry_ftq_offset :bpu预测结果中跳转指令在预测块中的偏移last_cycle_cfiIndex
- 不满足情况1,bpu到ifu的接口发生fire,信号成功写入
- toIfu:写入pc存储队列的读出信号ifuPtrPlus1_rdata,这同样是因为ifuptr还没来得及更改,所以直接使用ifuptr+1对应项的rdata
- entry_is_to_send :发送状态队列中ifuPtrPlus1对应项为f_to_send或者在上一周期bpu有写入时旁路bpu指针等于ifuptr加1,entry_is_to_send拉高。
- entry_next_addr :
- 如果上一周期bpu有写入且bpu旁路指针等于ifuptr+1,写入bpu旁路pc信号的startAddr字段,而这个项的pc信息还没有写入,正在pc旁路信号中,这是因为ifuptr+1对应下一个指令预测块,它的起始地址实际上就是ifuptr对应指令的预测块的跳转目标。
- 如果不满足该条件,
- ifuptr等于newest_entry_ptr: 使用newest_entry_target作为entry_next_addr,newest_entry_ptr,newest_entry_target这几个内部信号,表明我们当前队列中最新的有效的FTQ项。如之前所说,BPU新的写入,重定向等等都会对最新FTQ项进行新的安排,在相应的文档中,对其生成方式做具体的描述。
- 不满足条件1:使用pc存储队列的ifuPtrPlus2_rdata.startAddr
- 不满足情况1,2:
- toIfu:写入pc存储队列的读出信号ifuPtr_rdata
- entry_is_to_send :发送状态队列中ifuPtr对应项为f_to_send或者在上一周期bpu有写入时旁路bpu指针等于ifuptr
- entry_next_addr :
- 如果上一周期bpu有写入且bpu旁路指针等于ifuptr+1,写入bpu旁路pc信号的startAddr字段。
- 如果不满足该条件, 1. ifuptr等于newest_entry_ptr: 使用newest_entry_target作为entry_next_addr。 2. 不满足上面的条件1:使用pc存储队列的ifuPtrPlus1_rdata.startAddr,为什么条件2和条件3,一个使用ifuPtrPlus2_rdata.startAddr作为entry_next_addr ,一个使用ifuPtrPlus1_rdata.startAddr作为,这也是出于时序的考虑: 因为要获得实际上的ifuptr+1对应项的start值作为结果,而因为第一处那里因为ifuptr还没来得及更新(加1)同步到当前实际的ifuptr,所以要加2来达到实际上的ifuptr+1对应的值,而第二处的ifuptr已经更新了,所以只用加1就行了。
2. 发送取指信息
2.1 发送取指目标
2.1.1 发送给IFU
toIfu接口的req接口: FTQ通过该接口向IFU发送取指信号:
- valid:要发送的FTQ项处于将发送状态entry_is_to_send且ifuptr不等于bpuptr
- nextStartAddr:递交最终的entry_next_addr
- ftqOffset:递交最终的entry_ftq_offset
- toIfu:递交pc信息
2.1.2 发送给ICache
toICache的req接口: FTQ通过该接口向ICache发送取指信号:
- valid:FTQ项处于将发送状态entry_is_to_send且ifuptr不等于bpuptr
- readValid:ICache的有多个read接口,readVlid是一个向量,表示这几个read接口是否有效,readVlid中的每个元素的写入值与valid一样
- pcMemRead:同样是一个向量,对应readVlid向量的ICache的多个pc信号read接口,从toIfu接口中将pc信息结果写入向量中各接口,接口的ftqIdx字段被写入ifuPtr
- backendException:后端出现异常,同时后端pc错误指针等于ifuPtr
2.1.3 发送给Prefetch
toPrefetch的req接口:
- valid:传给预取模块的项的状体toPrefetchEntryToSend为1,(toPrefetchEntryToSend会玩一个周期存储nextCycleToPrefetchEntryToSend的值),且pfptr不等于bpuptr,
- toPrefetch:递交pc
- ftqIdx字段被设置为pfptr寄存器的值
- backendException:在后端pc错误指针等于pfptr的时候,传入后端异常信号,否则传入无异常信号
2.2 错误命中
错误命中falsehit: 当发往Ifu的pc接口toIfu中发生fallThruError(预测块的fall through地址小于预测的起始地址时),且hit状态队列entry_hit_status中ifuPtr对应项显示命中的话,进行如下判断:
当发往ifu的接口toIfu的req接口发生fire,且bpu的预测结果不发生满足以下条件的重定向: s2或者s3的重定向的预测块对应的FTQ项索引号ftq_idx等于ifuptr, 此时,hit状态队列中ifuptr对应项被设置为false_hit。
2.3 BPU冲刷
bpu向ifu的req请求的flush: 发往ifu的flushfrombpu(来自bpu的冲刷)接口中,记录有s2,s3阶段的指针,如果其中一条指针不大于发往ifu的req接口的ftqIdx的时候,表示应该被冲刷掉req信号,即冲刷掉新的发送给FTQ的预测信息。
2.4 更新发送状态
成功发送: 发往ifu的req接口发生fire,且req不被来自bpu的flush给冲刷掉时: entry_fetch_status状态队列中ifuptr对应项的发送状态置为f_sent。表示该ftq项被成功发送 了
接口说明
顶层IO | 子接口 | 作用 |
---|---|---|
toIFU | req | 发送取指目标 |
toIFU | flushfrombpu | 冲刷掉发送给IFU的取指目标 |
toICache | req | 发送取指目标 |
toPrefetch | req | 发送取指目标 |
测试点总表
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1.1 | GET_PC_FOR_ICACHE | COND1 | 旁路生效,即旁路bpu指针等于ifuptr,且上一周期bpu输入有效结果有效,直接向toICache接口输入旁路pc信息bpu_in_bypass_buf |
1.1.2 | GET_PC_FOR_ICACHE | COND2 | 不满足情况1,但是上一周期发生ifu_fire,成功传输信号,此时toICache中被写入pc存储子队列ftq_pc_mem中ifuptr+1对应项的结果 |
1.1.3 | GET_PC_FOR_ICACHE | COND3 | 前两种情况都不满足,此时toICache中被写入pc存储队列中ifuptr对应项的结果 |
1.2.1 | GET_PC_FOR_PREFETCH | COND1 | bpu有信号写入,同时bpu_in_resp_ptr等于pfptr的写入信号pfptr_write, 读取bpu向pc存储队列的写入信号wdata,下一周期写入ToPrefetch |
1.2.2 | GET_PC_FOR_PREFETCH | COND2 | 不满足情况1,且由bpu到prefetch的接口发生fire,即bpu向预取单元成功发送信号,pc存储单元的pfPtrPlus1_rdata下一周期写入ToPrefetch接口 |
1.2.3 | GET_PC_FOR_PREFETCH | COND3 | 不满足以上两种情况:pc存储单元的pfPtr_rdata在下一周期被写入ToPrefetch接口 |
1.3.1 | SET_NEXT_ADDR | COND1 | 上一周期bpu写入信号,且旁路指针等于ifuptr时设置下一个发送的指令块的起始地址 |
1.3.2 | SET_NEXT_ADDR | COND2 | 不满足情况1,bpu到ifu的接口发生fire时设置下一个发送的指令块的起始地址 |
1.3.3 | SET_NEXT_ADDR | COND3 | 不满足情况1,2时设置下一个发送的指令块的起始地址 |
2.1.1 | SEND_PC | IFU | 向IFU发送取指目标 |
2.1.2 | SEND_PC | ICACHE | 向ICache发送取指目标 |
2.1.3 | SEND_PC | PREFETCH | 向Prefetch发送取指目标 |
2.2 | FALSE_HIT | FALSE_HIT | 当发往Ifu的pc接口toIfu中发生fallThruError,且FTB项命中时判断是否是错误命中 |
2.3 | FLUSH_FROM_BPU | FLUSH_FROM_BPU | 发往ifu的flushfrombpu(来自bpu的冲刷)接口中的s2,s3阶段的指针其中一条指针不大于发往ifu的req接口的ftqIdx的时候,应该冲刷掉新的发送给FTQ的预测信息 |
2.4 | UPDATE_SEND_STATU | UPDATE_SEND_STATU | 发往ifu的req接口发生fire,且req不被来自bpu的flush给冲刷掉时: entry_fetch_status状态队列中ifuptr对应项的发送状态置为f_sent |
1.5 - IFU向FTQ写回预译码信息
文档概述
IFU获取来自BPU的预测信息之后,会执行预译码,并将FTQ项写回FTQ中去。我们会比对FTQ中原BPU预测项和预译码的结果,判断是否有预测错误
基本流程
预译码写回ftq_pd_mem:
- FTQ从pdWb接口中获取IFU的写回信息,FTQ首先将预译码写回信息写回到ftq_pd_mem,
更新提交状态队列commitStateQueue:
- 然后根据写回信息中指令的有效情况更新提交状态队列commitStateQueue。
比对错误:
- 同时,从ftb_entry_mem读出ifu_Wb_idx所指的FTB项,将该FTB项的预测结果与预译码写回结果进行对比,看两者对分支的预测结果是否有所不同。
综合错误:
- 之后就综合根据预译码信息可能得到的错误:有前面说的比对BPU的预测结果和预译码结果得到的错误,也有直接根据预译码得到的错误预测信息。根据错误预测结果更新命中状态队列。
更新写回指针
- 最后,如果IFU成功写回,ifu_Wb_idx更新加1。
术语说明
名称 | 定义 |
---|---|
预译码 | IFU会对取指目标进预译码,之后写回FTQ |
ifuWbPtr | IFU写回指针,知识IFU预译码要写入FTQ的位置 |
模块功能说明
1. 预译码写回ftq_pd_mem
写回有效:预译码信息pdWb有效时,写有效 写回地址:pdWb的ftqIdx的value 写回值:解析整个pdWb的结果
2. 更新提交状态队列
当预译码信息pdWb有效时,相当于写回有效,此时,根据预译码信息中每条指令的有效情况和该指令是否在有效范围内,判断指令的提交状态是否可以修改,若可以修改,则将提交状态队列,写回项中的指令状态修改
详细信号表示
pdWb有效时,ifu_wb_valid拉高。 此时,对于预译码信息中每一条指令的预译码结果pd做判断: 如果预译码结果valid,且指令在有效范围内(根据insrtRange的bool数组指示),则提交状态队列commitStateQueue中,写回项中的指令状态修改为c_toCommit,表示可以提交,这是因为只有在FTQ项被预译码写回后,才能根据后端提交信息提交该FTQ项,之后会把预译码信息一并发往更新通道。
3. 比对预测结果与预译码结果
从ftb存储队列ftb_entry_mem中的读取ifu写回指针ifuwbptr的对应项:
- pdWb有效的时候,读有效,读取地址为预译码信息中指示的ftqIdx。 当命中状态队列指示待比对项ftb命中,且回写有效时,读取出FTB存储队列中对应的项,与预译码信息进行比对,当BPU预测的FTB项指示指令是有效分支指令,而预译码信息中则指示不是有效分支指令时,发生分支预测错误,当BPU预测的FTB项指示指令是有效jmp指令,而预译码信息中则指示不是有效jmp指令时,发生跳转预测错误
详细信号表示:
ifu_wb_valid回写有效时,ftb_entry_mem回写指针对应读使能端口ren有效,读取地址为ifu_wb_idx预测译码信息中指示的ftqIdx的value值。 回写项命中且回写有效,hit_pd_valid信号有效,此时,读取ftb存储队列中的FTB项,读出brSlots与tailSlot,并进行比对:
3.1 判断是否有分支预测错误br_false_hit
测试点3.1.1和3.1.2对应以下两种条件导致的br_false_hit
- 判断是否有分支预测错误br_false_hit:
- brSlots的任意一项有效,同时在预译码信息中不满足这一项对应的pd有效且isBr字段拉高表明是分支指令,
- taiSlot有效且sharing字段拉高表明该slot为分支slot,同时在预译码信息中不满足这一项对应的pd有效且isBr字段拉高表明是分支指令 满足任意条件可判断发生分支预测错误br_false_hit,该信号拉高
3.2 判断是否发生jmp预测错误jal_false_hit
- 判断是否发生jmp预测错误jal_false_hit:
- 预测结果中必须指明指令预测有效,且其中isJal拉高表面是jal指令或者指明是isjalr指令
4. 预译码错误
直接从预测结果中获取错误预测相关信息,如果回写项ftb命中且missoffset字段有效表明有错误预测的指令,hit_pd_mispred信号拉高,表示预译码结果中直接指明有预测错误的指令。
5. 综合错误
综合比对预测结果与预译码结果得到的错误信息,与预译码错误直接获得的预测错误,任意一种发生时has_false_hit拉高表示有预测错误,此时,命中状态队列entry_hit_status中写回项的状态置为h_false_hit
6. 更新写回指针
ifu_wb_valid拉高,表示写回有效,将ifuWbPtr更新为原值加1。
接口说明
顶层IO | 子接口 |
---|---|
fromIfu | pdWb |
测试点总表
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1 | WB_PD | WB_PD | 向ftq_pd_mem中写回预译码信息 |
2 | UPDATE_COMMITSTATE | UPDATE_COMMITSTATE | 当预译码信息pdWb有效时,根据预译码信息中每条指令的有效情况和该指令是否在有效范围内,判断指令的提交状态是否可以修改,若可以修改,则将提交状态队列,写回项中的指令状态修改 |
3.1.1 | BR_FALSE_HIT | COND1 | brSlots的任意一项有效,同时在预译码信息中不满足这一项对应的pd有效且isBr字段拉高 |
3.1.2 | BR_FALSE_HIT | COND2 | taiSlot有效且sharing字段拉高表明该slot为分支slot,同时在预译码信息中不满足这一项对应的pd有效且isBr字段拉高 |
3.2 | JAL_FALSE_HIT | JAL_FALSE_HIT | 指令预测有效,且其中isJal拉高或者指明是isjalr指令 |
4 | PD_MISS | PD_MISS | 如果回写项ftb命中且missoffset字段有效表明有错误预测的指令,hit_pd_mispred信号拉高 |
5 | FALSE_HIT | FALSE_HIT | 综合比对预测结果与预译码结果得到的错误信息,与预译码错误直接获得的预测错误,任意一种发生时has_false_hit拉高表示有预测错误,此时,命中状态队列entry_hit_status中写回项的状态置为h_false_hit |
6 | UPDATE_IFU_WB_PTR | UPDATE_IFU_WB_PTR | ifu_wb_valid拉高,将ifuWbPtr更新为原值加1 |
1.6 - FTQ接收后端重定向
文档概述
FTQ重定向信息有两个来源,分别是IFU 和 后端。两者的 重定向接口大致相似,但重定向的过程有一定区别。
对于重定向,后端有提前重定向机制,为了实现提前一拍读出在ftq中存储的重定向数据,减少redirect损失,后端会向ftq提前一拍(相对正式的后端redirect信号)传送ftqIdxAhead信号和ftqIdxSelOH信号。ftqIdxSelOH信号出现的原因,是早期版本要读多个ftqIdxAhead信号,以独热码的形式选其中一路作为最终确认的提前索引值,但现在只需要从一个端口获取ftqIdx信号了,ftqIdxAhead只能确认这一个端口了。
术语说明
名称 | 定义 |
---|---|
sc_disagree | 统计SC预测错误用的性能计数器中需要用到的值,SC预测器是BPU子预测器TAGE-SC预测器的一个部分 |
模块功能说明
1. 接收后端重定向信号
时序
1.1 提前重定向
第一个周期:
- 后端重定向写回时,首先会从后端到FTQ的IO接口(CtrltoFtqIO)中,看ftqIdx是不是有效信号,且此时后端正式重定向信号redirect无效(因为提前重定向会比正式重定向提前一拍,所以此时正式重定向无效),这时,提前重定向信号aheadValid有效, 将使用提前获取的重定向ftqIdx,
1.2 真实提前重定向
第二个周期:
- 如果此时后端正式重定向信号有效了,且ftqIdxSelOH拉高,说明在正式重定向阶段成功对ftqIdxAhead信号进行选中,同时上一周期重定向信号aheadValid是有效的,则真实提前重定向信号realAhdValid拉高,在此时读取
1.3 存储后端重定向信号
第三个周期:
- 该周期会把来自后端的重定向信息的存储一份在寄存器backendRedirectReg中,具体的来说,当上一个周期后端重定向有效时,将后端重定向bits字段(存储实际内容)被写入寄存器的bits字段。
- 而实际决定信号是否有效的valid字段(决定该信号是否有效)则在上一周期真实提前重定向信号有效(表示确实使用了提前重定向的ftqIdx进行重定向)的情况下,被写入false,因为提前重定向发生时,我们直接使用当前的后端重定向信号交给FTQ就可以了。而不需要多保存一个周期。
- 真实提前重定向信号无效时,则由上一周期后端正式重定向的有效值决定,只有信号有效时,我们才需要把它存下来,之后交给FTQ。
2. 选择重定向信号
信号抉择: 是提前获取后端重定向信息还是延迟一个周期从寄存器内读取? 真实重定向有效时,直接将后端重定向信息传递给FTQ,否则,取重定向寄存器内的信号作为重定向信息传递给FTQ,相当于晚一个周期发送重定向信息。最后被选择的重定向信息作为后端重定向结果fromBackendRedirect发送给FTQ
接下来讲讲后端重定向在这三个周期到底通过ftqIdx到底读了哪些FTQ子队列中的信息,以及怎么使用它们。
3. 整合子队列信号
3.1 读取子队列
接下来讲讲后端重定向在这三个周期到底通过ftqIdx到底读了哪些FTQ子队列中的信息,以及怎么使用它们。
后端重定向读取的子队列:
- ftq_redirect_mem:FTQ会根据后端重定向提供的ftqIdx读出ftq_Redirect_SRAMEntry,借助它提供的信息重定向到之前的状态。
- ftq_entry_mem:读出重定向指令块对应的FTB项
- ftq_pd_mem:读出重定向指令块的预译码信息
3.1.1 发生提前重定向时,读取子队列需要两个周期
3.1.2 未发生提前重定向时,读取子队列需要三个周期
读子队列时序: 第一个周期:
- 提前重定向信号有效时,将子队列的读端口,读有效信号拉高,输入ftqIdxAhead的value字段作为读地址,发起读取请求。
第二个周期:
- case1. 如果第一周期的提前重定向无效,而现在正式重定向有效,则在此时才拉高读有效信号,使用正式重定向接口的ftqIdx作为读取地址,发起读取请求。
- case2. 真实提前重定向有效了,此时因为前一个周期已经发起读取请求,此时可以直接从子队列的读端口读出了
第三个周期
- 真实提前重定向无效,但至少前一个周期正式重定向发起的读取请求能保证在当前周期从子队列中读出。
3.2 将子队列信息整合到后端重定向信号
处理读取信息 FTQ会将从子队列中读出的信息整合到fromBackendRedirect中。 具体来说:
- 重定向redirect接口的CfiUpdateInfo接口直接接收ftq_Redirect_SRAMEntry中的同名信号。
- 利用fromBackendRedirect中指示的ftqOffset读取指令块预译码信息中实际跳转指令的预译码信息,该ftqOffset为后端执行过后确定的控制流指令在指令块内的偏移。
- 得到的预译码信息被直接连接到CfiUpdateInfo接口的pd接口中
- 对于读出的指令块对应的FTB项,我们可以从中得知实际执行时得到的跳转指令,是否在FTB项被预测为跳转指令,或者是被预测为jmp指令,如果是,则cfiUpdateInfo的br_hit接口或者jr_hit接口被拉高,表示对应的分支预测结果正确了。
- 具体来说:通过发送ftqOffset,ftb项以brIsSaved的方式判断是否br_hit,判断是否jr_hit的方式也是类似的(r_ftb_entry.isJalr && r_ftb_entry.tailSlot.offset === r_ftqOffset)。
- 在CfiUpdateInfo接口设置为br_hit的时候,还会根据这条发生跳转的分支指令是哪个槽从ftq_Redirect_SRAMEntry重定向接口的sc_disagree统计SC预测错误用的性能计数器中,获取对应值,最后整合到后端重定向接口中(如果没有br_hit,对应计数器的两个值都为0)。
接口说明
顶层IO | 功能 |
---|---|
fromBackend | 接收后端重定向信息 |
测试点总表
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1 | RECERIVE_BACKEND_REDIRECT | REDIRECT_AHEAD | 后端重定向写回时,首先会从后端到FTQ的IO接口(CtrltoFtqIO)中,看ftqIdx是不是有效信号,且此时后端正式重定向信号redirect无效,这时,提前重定向信号aheadValid有效 |
1.2 | RECERIVE_BACKEND_REDIRECT | REAL_REDIRECT_AHEAD | 如果此时后端正式重定向信号有效了,且ftqIdxSelOH拉高,同时上一周期重定向信号aheadValid是有效的,则真实提前重定向信号realAhdValid拉高 |
1.3 | RECERIVE_BACKEND_REDIRECT | STORE_REDIRECT | 后端真实重定向无效时写入寄存器 |
2 | CHOOSE_AHEAD | CHOOSE_AHEAD | 真实重定向有效时,直接将后端重定向信息传递给FTQ,否则,取重定向寄存器内的信号作为重定向信息传递给FTQ |
3.1.1 | READ_FTQ_SUBQUEUE | READ_AHEAD | 发生提前重定向时,读取子队列需要两个周期 |
3.1.2 | READ_FTQ_SUBQUEUE | READ_NO_AHEAD | 未发生提前重定向时,读取子队列需要三个周期 |
3.2 | ADD_SUBQUEUE_INFO | ADD_SUBQUEUE_INFO | 将子队列信息整合到后端重定向信号 |
1.7 - FTQ接收IFU重定向
文档概述
除了后端,IFU也会发送重定向相关消息,和后端不同,IFU的重定向信息来自于预译码写回信息。相同的是,它们都是通过BranchPredictionRedirect的接口传递重定向信息。
术语说明
名称 | 定义 |
---|---|
RedirectLevel | 重定向等级,重定向请求是否包括本位置,低表示在本位置后重定向,高表示在本位置重定向。它在之后决定了由重定向导致的冲刷信号是否会影响到发生重定向的指令 |
模块功能说明
1. IFU重定向信号生成
流程
IFU重定向是通过这个BranchPredictionRedirect接口传递的,下面来讲述IFU重定向怎么生成IFU的BranchPredictionRedirect内相应信号的,这个过程需要两个周期 信号列表: 第一个周期
1.1 IFU 重定向触发条件
- valid:当预译码写回pdWb有效,且pdWb的missOffset字段有效表明存在预测错误的指令,同时后端冲刷信号backendFlush无效时,valid信号有效。
1.2 IFU生成重定向信号
- ftqIdx:接收pdWb指定的ftqIdx
- ftqOffset:接收pdWb的missOffset的bits字段
- level:RedirectLevel.flushAfter,将重定向等级设置为flushAfter
- BTBMissBubble:true
- debugIsMemVio:false
- debugIsCtrl:false
- cfiUpdate:
信号列表:
- pc:pdWb中记录的指令块中所有指令pc中,missOffset对应的pc
- pd:pdWb中记录的指令块中所有指令的pd中,missOffset对应的pd
- predTaken:从cfiIndex_vec子队列中读取pdWb中ftqIdx索引的项是否valid,有效说明指令块内被预测为有控制流指令。
- target:pdWb中的target
- taken:pdWb中cfiOffset的valid字段,有效时表明预译码认为指令块中存在指令控制流指令
- isMisPred:pdWb中missOffset的valid字段,有效时表明预译码认为指令块中存在预测错误的指令
第二个周期: 该周期进行的信号生成是在第一周期valid字段有效的情况下才继续的
- cifUpdate:
信号列表:
- 重定向RAS相关信号:通过ftqIdx索引从 ftq_redirect_mem读出ftq_Redirect_SRAMEntry,把其中的所有信号直接传递给cfiUpdate的同名信号中。
- target:已在第一周期写入cfiUpdate的pd有效,且isRet字段拉高,指明发生预测错误的指令本是一条Ret指令,此时,将target设置为cfiUpdate的topAddr,帮助回到发生错误之前的状态。
2. 重定向结果生效
两个周期生成完整的重定向信息后,IFU重定向信息才有效,有可能被FTQ采取,完整的IFU重定向结果记为ifuRedirectToBpu
3. IFU 冲刷信号 (ifuFlush
)
指令流控制信号: ifuFlush:来自IFU的冲刷信号,主要是由IFU重定向造成的,生成IFU重定向信息的两个周期内,该信号都拉高
- 标志:IFU重定向信息产生接口BranchPredictionRedirect中valid有效,表示开始生成重定向信息,该周期以及下一个周期,ifuFlush拉高
接口说明
顶层IO | 作用 |
---|---|
fromIFU | 接收来自IFU的预译码信息 |
接口时序
测试点总表
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1 | IFU_REDIRECT | IFU_REDIRECT_GRN_VALID | 当预译码写回pdWb有效,且pdWb的missOffset字段有效表明存在预测错误的指令,同时后端冲刷信号backendFlush无效时,valid信号有效 |
1.2 | IFU_REDIRECT | IFU_REDIRECT_GEN | 允许生成IFU重定向时,在两周期内生成具体信号 |
2 | IFU_REDIRECT_TO_BPU | IFU_REDIRECT_TO_BPU | IFU重定向生成后,IFU重定向结果生效 |
3 | IFU_FLUSH | IFU_FLUSH | 生成IFU重定向信息的两个周期内,ifuFlush信号都拉高 |
1.8 - FTQ向后端发送取指目标
文档概述
pc取值目标会发给后端pc mem让他自己进行存储,之后从自己的pc mem取指,此外,最新的FTQ项和对应的跳转目标也会发给后端。
怎样算是一个最新的FTQ项,BPU最新发送的预测块可以是最新的FTQ项,其次,重定向发生时,需要回滚到发生错误预测之前的状态,从指定的FTQ项开始重新开始预测,预译码等等,这也可以是被更新的最新的FTQ项。
术语说明
名称 | 定义 |
---|---|
暂无 | 暂无 |
模块功能说明
流程
1.发送取值目标到pc mem
- 发送时机:bpu_in_fire,即BPU向前端发送有效预测信息,或者重定向信息的时候。以此为基础之后的第二个周期,进行发送,通过将toBackend接口的pc_mem_wen设置为true的方式指明开始发送
- 接口信号列表:
- pc_mem_wen:设置为true
- pc_mem_waddr:接收bpu_in_fire那个周期BPU发送的ftqIdx
- pc_mem_wdata:接收bpu_in_fire那个周期,FTQ读取的ftq_pc_mem中的取指目标
2.更新最新的FTQ项
- 发送时机:
- 最新的FTQ项可能是由BPU写入最新预测信息造成的,发送取值目标到pc mem也是因为BPU写入最新预测信息才写入的,如果是这种情况造成的,更新FTQ项和写入pc mem的时机是一致的。
- 此外发生重定向时,也会进行状态回滚更新FTQ项,标志是后端接口fromBackend的重定向redirect信号有效,或者写入BPU的接口toBPU的redirctFromIFU拉高说明当前有来自IFU的重定向
- (注释(可忽略)IFU重定向信号生成有两个周期,可以认为第一个周期预译码信息中missoffset有效说明IFU重定向发生,也可以认为第二个周期redirctFromIFU拉高说明重定向发生,此处取后者)。
- 同样是向toBackend中写入
- 接口信号列表:
- newest_entry_en:前面说的发送时机到来时,再延迟一个周期达到真正的写入时机,这时才拉高信号
- newest_entry_ptr:发送时机到来时的newest_entry_ptr,在真正的写入时机写入
- newest_entry_target:发送时机到来时的newest_entry_target newest_entry_ptr,newest_entry_target这几个都是同名的内部信号,如之前所说,BPU新的写入,重定向等等都会对最新FTQ项进行新的安排,在相应的文档中,对其生成方式做具体的描述。
接口说明
顶层IO | 作用 |
---|---|
toBackend | 发送取指令目标,让后端进行储存 |
测试点总表
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1 | SEND_PC_TO_BACKEND | SEND_PC | 发送取值目标到pc mem |
2 | SEND_PC_TO_BACKEND | UPDATE_NEWEST | 更新最新的FTQ项 |
1.9 - 执行单元修改FTQ状态队列
文档概述
后端的写回信息,包括重定向信息和更新信息,实际上都是执行之后,由实际执行单元根据结果发回的
术语说明
名称 | 定义 |
---|---|
cfiIndex_vec | 控制流指令索引队列,记录每个指令块中控制流指令的索引 |
update_target | 更新目标队列,记录每个指令块的跳转目标 |
FTQ最新项 | BPU新的写入,重定向等等都会对最新FTQ项进行新的安排,表明我们当前关注的最新FTQ项。 |
模块功能说明
1. 由后端的写回信号修改FTQ状态
1.1 修改FTQ状态队列
从后端写回FTQ接口fromBackend中的redirect接口中,我们可以读出valid,ftqPtr,ftqOffset(后端实际执行时确认的控制流指令的偏移),taken,mispred字段,依靠它们来判断,如何修改FTQ的状态队列和相关的变量
后端执行单元写回时被修改的队列:
1.1.1 修改cfiIndex_vec
- cfiIndex_vec:
修改方式:执行写回修改队列中ftqPtr那一项
- valid:fromBackend中的redirect接口中,valid有效,taken有效,且ftqOffset小于或者等于cfiIndex_vec中ftqPtr那一项指定的偏移:这说明重定向发生,实际执行结果判断ftqPtr索引的指令块确实会发生跳转,且实际执行跳转的指令在被预测为发生跳转的指令之前或等于它。所以这时指令块是会发生跳转的,控制流索引队列的ftqPtr项valid
- bits:fromBackend中的redirect接口中,valid有效,taken有效,且ftqOffset小于cfiIndex_vec中ftqPtr那一项指定的偏移,偏移量被更新为更小值ftqOffset。
1.1.2 修改update_target
- update_target:
- ftqPtr索引项的跳转目标修改为fromBackend的redirect接口中的cifUpdate中指定的target
1.1.3 修改mispredict_vec
- mispredict_vec:
- 如果该重定向指令是来自后端的重定向指令, ftqPtr索引项的ftqOffset偏移指令被设置为fromBackend的redirect接口中的cifUpdate中指定的isMisPred
1.2 修改FTQ最新项
- newest_entry_target:
- 被修改为重定向接口中cfiUpdate指定的target
- 辅助信号newest_entry_target_modified被指定为true
- newest_entry_ptr:
- 修改为重定向接口指定的ftqIdx
- 辅助信号newest_entry_ptr_modified被指定为true
2. 由IFU的写回信号修改FTQ状态
IFU既然也能和后端一样生成重定向信息,那么他也能在产生重定向信息的时候修改这些状态队列和FTQ最新项,区别:
- 但是,由于IFU没有真的执行,所以它的预译码结果并不能作为决定指令块是不是真的被错误预测了,所以它不能修改mispredict_vec的状态
- 其次,后端重定向优先级永远高于IFU重定向,两者同时发生时只采用后端重定向。
所以这个部分也有以下测试点:
2.1.1 修改cfiIndex_vec
2.1.2 修改update_target
2.2 修改FTQ最新项
常量说明
常量名 | 常量值 | 解释 |
---|---|---|
常量1 | 64 | 常量1解释 |
常量2 | 8 | 常量2解释 |
常量3 | 16 | 常量3解释 |
接口说明
顶层IO | 子接口 | |
---|---|---|
fromBackend | redirect |
测试点总表
实际使用下面的表格时,请用有意义的英文大写的功能名称和测试点名称替换下面表格中的名称
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1.1 | BACKEDN_REDIRECT_UPDATE_STATE | UPDATE_CFIINDEXVEC | 后端重定向修改cfiinedex状态队列 |
1.1.2 | BACKEDN_REDIRECT_UPDATE_STATE | UPDATE_UPDATE_TARGET | 后端重定向修改update_target状态队列 |
1.1.3 | BACKEDN_REDIRECT_UPDATE_STATE | UPDATE_MISPREDICTVEC | 后端重定向修改mispredict状态队列 |
1.2 | BACKEDN_REDIRECT_UPDATE_NEWEST | BACKEDN_REDIRECT_UPDATE_NEWEST | 后端重定向修改FTQ最新项 |
2.1.1 | IFU_REDIRECT_UPDATE_STATE | UPDATE_CFIINDEXVEC | IFU重定向修改cfiinedex状态队列 |
2.1.2 | IFU_REDIRECT_UPDATE_STATE | UPDATE_UPDATE_TARGET | IFU重定向修改update_target状态队列 |
2.2 | IFU_REDIRECT_UPDATE_NEWEST | IFU_REDIRECT_UPDATE_NEWEST | IFU重定向修改FTQ最新项 |
1.10 - 冲刷指针和状态队列
文档概述
之前讲了,后端和IFU重定向写回会修改一些状态队列。此外,FtqPtr也是一种比较重要的维护信息。由后端或者IFU引起的重定向,需要恢复各种类型用来索引FTQ项的FtqPtr。而当重定向是由后端发起的时候,还要修改提交状态队列,说明指令已经被执行。
术语说明
名称 | 定义 |
---|---|
FTQ指针 | 用来索引FTQ项,有不同类型的FTQ指针,比如bpuPtr,ifuPtr |
flush | 冲刷,发生时需要重置FTQ指针,以及重置其他状态 |
融合指令 | 一条指令可以和其他指令融合,形成融合指令 |
模块功能说明
1. 冲刷FTQ指针及提交状态队列
流程
后端和IFU的重定向信号都会冲刷指针,更具体的来说:
1.1 冲刷条件
- 后端写回接口fromBackend有效,或者IFU重定向有效:(当预译码写回pdWb有效,且pdWb的missOffset字段有效表明存在预测错误的指令,同时后端冲刷信号backendFlush无效)。(参考:从IFU重定向的第一个周期,重定向valid值有效条件)
1.2 冲刷指针
第一个周期:
- 冲刷指针:确认后端和IFU的重定向信号可能冲刷指针时,从两个重定向来源的redirect接口读出重定向信息,包括ftqIdx,ftqOffset,重定向等级RedirectLevel。有两个来源时,优先后端的重定向信息。
冲刷指针列表:
- bpuPtr:ftqIdx+1
- ifuPtr:ftqIdx+1
- ifuWbPtr:ftqIdx+1
- pfPtr:ftqIdx+1 注:只是在当前周期向指针寄存器写入更新信息,实际生效是在下一个周期。 这样一来,所有类型指针当前指向的都是发生重定向的指令块的下一项了,我们从这一项开始重新进行分支预测,预译码,等等。
1.3 冲刷提交状态队列
第二个周期: 如果上一个周期的重定向来源是后端,FTQ会进一步更改提交状态队列
- 提交状态队列中,对于重定向的指令块(通过ftqIdx索引),位于ftqOffset后面的指令的状态被设置为c_empty
- 对于正好处于ftqOffset的指令,判断RedirectLevel,低表示在本位置后flush,高表示在本位置flush,所以level为高时,对于的指令提交状态被设置为flush。
2 转发到顶层IO
实际上,在发生重定向的时候,还涉及一些将重定向信息通过FTQ顶层IO接口转发给其他模块的操作,比如ICache需要flush信号取进行冲刷,IFU也需要后端的重定向信号对它进行重定向,具体来说: 在流程的第一个周期:
2.1 flush转发到icacheFlush
- flush信号顶层IO转发(icacheFlush):
- 确认后端和IFU的重定向信号可能冲刷指针时,拉高FTQ顶层IO接口中的icacheFlush信号,把重定向产生的flush信号转发给ICache
2.2 重定向信号转发到IFU
- 重定向信号顶层IO转发(toIFU):
- redirect:
- bits:接收来自后端的重定向信号
- valid:后端的重定向信号有效时有效,保持有效,直到下个周期依然有效
- redirect:
3 重排序缓冲区提交
其实,除了后端重定向会更新提交状态队列,最直接的更新提交状态队列的方式是通过FTQ顶层IO中frombackend里提供的提交信息,rob_commits告知我们哪些指令需要被提交。
rob_commits的valid字段有效,可以根据其中信息对指令进行提交,修改状态队列。对于被执行的指令,是如何提交的,如何对应地修改提交状态队列,有两种情况:
3.1 提交普通指令
- 对于普通指令,根据rob_commits的ftqIdx和ftqOffset索引提交状态队列中的某条指令,将对应的提交状态设置为c_commited
3.2 提交融合指令
- 对于融合指令,根据提交类型commitType对被索引的指令和另一与之融合的指令进行提交,将对应的提交状态设置为c_commited
- commitType = 4:同时把被索引指令的下一条指令设为c_commited
- commitType = 5:同时把被索引指令的之后的第二条指令设为c_commited
- commitType = 6:同时把被指令块的下一个指令块的第0条指令设为c_commited
- commitType = 7:同时把被指令块的下一个指令块的第1条指令设为c_commited
接口说明
顶层IO | 作用 |
---|---|
fromBackend | 接收后端重定向和指令提交 |
fromIfu | 接收IFU重定向 |
icacheFlush | 将flush信号转发到icache |
toIFU | 将后端重定向转发到IFU |
测试点总表
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1 | FLUSH_FTQPTR_AND_COMMITSTATE | FLUSH_COND | 后端写回接口fromBackend有效,或者IFU重定向有效时,进行冲刷 |
1.2 | FLUSH_FTQPTR_AND_COMMITSTATE | FLUSH_FTQ_PTR | 优先采用后端重定向信息冲刷FTQ指针 |
1.3 | FLUSH_FTQPTR_AND_COMMITSTATE | FLUSH_COMMIT_STATE | 发生后端重定向时,进一步修改提交状态队列 |
2.1 | TRANSFER_TO_TOP | FLUSH | 后端和IFU的重定向信号可能冲刷指针,拉高FTQ顶层IO接口中的icacheFlush信号 |
2.2 | TRANSFER_TO_TOP | IFU | 将重定向信号转发到IFU |
3.1 | COMMIT_BY_ROB | NORMAL | 对于普通指令,根据rob_commits的ftqIdx和ftqOffset索引提交状态队列中的某条指令,将对应的提交状态设置为c_commited |
3.2 | COMMIT_BY_ROB | FUSION | 对于融合指令,根据提交类型commitType对被索引的指令和另一与之融合的指令进行提交,将对应的提交状态设置为c_commited |
1.11 - FTQ向BPU发送更新与重定向信息
文档概述
FTQ将已提交指令的更新信息发往BPU进行训练,同时转发重定向信息。
术语说明
名称 | 定义 |
---|---|
暂无 | 暂无 |
模块功能说明
1. 转发重定向
向toBPU接口进行转发:
1.1 IFU重定向结果有效
- redirctFromIFU:IFU重定向结果有效时,拉高该信号(注意:IFU重定向有效的时机有两种说法,因为IFU重定向结果生成需要两个周期,此处取后者,即,IFU重定向生成过程的第二个周期有效,也是IFU生成完整重定向结果的周期)
1.2 选择后端重定向或者IFU重定向
- redirect:如果后端重定向结果fromBackendRedirect有效,选用fromBackendRedirect,否则选用IFU重定向结果ifuRedirectToBpu
2 BPU更新暂停
BPU的更新需要两个周期,故需要三种状态去表明我们当前的更新状态:更新的第一个周期,第二个周期,更新完成。 当发生更新的时候,会暂停FTQ对指令块的提交以及发送更新信息。
3 提交指令块
FTQ需要对当前comPtr指向的当前提交指令块,进行判断是否能够提交。 这个过程比较复杂。 由于 香山V2版本 的后端会在 ROB 中重新压缩 FTQ entry,因此并不能保证提交一个 entry 中的每条指令,甚至不能保证每一个 entry 都有指令提交。
判断一个 entry 是否被提交有如下几种可能:
- robCommPtr 在 commPtr 之后(ptr更大)。也就是说,后端已经开始提交之后 entry 的指令,在 robCommPtr 指向的 entry 之前的 entry 一定都已经提交完成
- commitStateQueue 中的某个指令块内最后一条有效范围内指令被提交。FTQ项中该指令被提交意味着这FTQ项内的指令已经全部被提交
在此以外,还必须要考虑到,后端存在 flush itself 的 redirect 请求,这意味着这条指令自身也需要重新执行,这包括异常、load replay 等情况。在这种情况下,这一FTQ项不应当被提交以更新 BPU,否则会导致 BPU 准确率显著下降。
3.1 canCommit
具体来看,判断commPtr指向的指令块能否提交,如果可以提交记为canCommit。
canCommit的设置条件如下:
3.1.1 COND1
- 当commPtr不等于ifuWbPtr,且没有因为BPU更新而暂停,同时robCommPtr在commPtr之后。之所以要求commPtr不等于ifuWbPtr是因为,前面说过了必须先预译码写回FTQ项才能提交
3.1.2 COND2
- commitStateQueue 中commPtr对应指令块有指令处于c_toCommit 或c_committed状态。且指令块中最后一条处于c_toCommit 或c_committed状态的指令是c_committed的。
这两种情况下,canCommit拉高,说明可以提交该指令块
3.2 canMoveCommPtr
3.2.1 提交指令块更新提交指针
在commPtr指向的指令块如果能提交,那么我们自然可以移动CommPtr指向下一个FTQ项了。
3.2.2 指令冲刷更新提交指针
但除此之外,commitStateQueue 中commPtr对应指令块的第一条指令被后端重定向冲刷掉了时,这表明该指令需要重新执行,这一FTQ项不应被提交,但是却可以更新CommPtr指针,因为该指令块内已经没有可以提交的指令了。
- CanMoveCommPtr时,commPtr指针更新加1(一周期后成功写入)。
3.3 robCommPtr更新
有几种情况
3.3.1 COND1
- 当来自后端接口fromBackend的rob_commits信息中,有信息有效时,取最后一条有效交割信息的ftqIdx作为robCommPtr
3.3.2 COND2
- 不满足情况1,选取commPtr, robCommPtr中较大的那个
3.4 mmio提交
发往mmioCommitRead接口
- mmioLastCommit:
3.4.1 COND1
- 当commPtr比来自mmioCommitRead接口的mmioFtqPtr大时,
3.4.2 COND2
- 或者两者正好相等,且commPtr指向的指令块中有c_toCommit 或c_committed状态的指令,最后一条处于c_toCommit 或c_committed状态的指令是c_committed的
在这两种情况下,mmioLastCommit信号在下一个周期被拉高
4 发送BPU更新信息
FTQ需要从FTQ子队列中,读取提交项的预测信息,重定向信息,meta信息,用这些信息来对BPU发送更新信息。
当canCommit时,可以提交commPtr指向的指令块时,从ftq_pd_mem,ftq_redirect_mem,ftq_meta_1r_sram_mem这些子队列,以及一些小的状态队列中读出对应指令块的相应信息,这些信息需要专门花一个周期才能读取到。具体来说:
- 从预译码信息子队列ftq_pd_mem中读取提交提交指令块(commptr所指)的预译码信息
- 从取指目标子队列ftq_pc_mem中读取取指信息
- 从分支预测重定向信息子队列ftq_redirect_mem中读取提交指令块的重定向信息。
- 从预测阶段状态队列中读取提交块来自BPU的哪个预测阶段
- 从meta信息子队列ftq_meta_1r_sram中读取提交指令块的meta,和相应的ftb_entry。
- 从提交状态队列commitStateQueueReg中读取提交状态,并确认指令块中哪些指令为c_committed,用bool数组表示
- 从控制流索引状态队列cfiIndex_vec中读取指令控制流指令在块中索引
- 结合错误预测状态队列mispredict_vec,和提交状态队列信息确认指令块中的提交错误指令。(即提交状态指示为c_commited 同时错误预测指示为预测错误)
- 从表项命中状态队列entry_hit_status中读取提交指令块是否命中
根据相关信息进行判断:
- 获取提交块的目标,如果commPtr等于newest_entry_ptr,则取newest_entry_target_modified拉高时记录下的newest_entry_target,否则取ftq_pc_mem.io.commPtrPlus1_rdata.startAddr,获取到的提交块目标将会被用来辅助新FTB项的生成
4.1 将子队列读取信息发向更新通道
整合完上述信息后,FTQ会向toBpu的update接口发送更新请求,具体如下:
- valid:canCommit 且 指令块满足命中或者存在cfi指令,valid接口有效,表明可以发送更新请求
- bits:
- false_hit:提交块命中状态指示为h_false_hit时,该信号拉高
- pc:提交块的取指信息中的startAddr
- meta:提交块的meta
- cfi_idx:提交块中cfi指令的index
- full_target:提交块的目标
- from_stage:提交块来自哪个预测阶段
- spec_info:提交块的meta
- pred_hit:提交块的命中状态为hit或者false_hit
另外,被更新的FTB表项也会同时被转发到更新接口,但是新的FTB表项生成方式相对复杂,下一节专门展开叙述
4.2 修正FTB项
更新结果会基于旧的FTB项进行更新,然后直接转发给更新接口。你可能需要先阅读FTB项相关文档了解FTB项的结构和相关信号生成方式
commit表项的相关信息会被发送给一个名为FTBEntryGen的接口,经过一系列组合电路处理,输出更新后的FTB表项信息。
为了更新FTB项,提交项如下信息会被读取:
- 取值目标中的起始地址 startAddr
- meta中旧FTB项 old_entry
- 包含FTQ项内32Byte内所有分支指令的预译码信息 pd
- 此FTQ项内有效指令的真实跳转结果 cfiIndex,包括是否跳转,以及跳转指令相对startAddr的偏移
- 此FTQ项内分支指令(如跳转)的跳转地址(执行结果)
- 预测时FTB是否真正命中(旧FTB项是否有效)
- 对应FTQ项内所有可能指令的误预测 mask
接下来介绍如何通过这些信息更新FTB。 FTB项生成逻辑:
4.2.1 情况1:FTB未命中,则创建一个新的FTB项
我们会根据预译码信息进行判断,预译码会告诉我们,指令块中cfi指令是否是br指令,jmp指令信息(以及是哪种类型的jmp指令)
- 无条件跳转指令处理:
- 不论是否被执行,都一定会被写入新FTB项的tailSlot
- 如果最终FTQ项内跳转的指令是条件分支指令,写入新FTB项的第一个brSlot(目前也只有这一个),对应的strongbias被设置为1作为初始化
- pftAddr设置:
- 存在无条件跳转指令时:以无条件跳转指令的结束地址设置
- 无无条件跳转指令时:以startAddr+取指宽度(32B)设置
- 特殊情况:当4Byte宽度的无条件跳转指令起始地址位于startAddr+30时,虽然结束地址超出取指宽度范围,仍按startAddr+32设置
- carry位根据pftAddr的条件同时设置
- 设置分支类型标志:
- isJalr、isCall、isRet按照无条件跳转指令的类型设置
- 特殊标志:当且仅当4Byte宽度的无条件跳转指令起始地址位于startAddr+30时,置last_may_be_rvi_call位
详细信号说明:
-
cfiIndex有效(说明指令块存在跳转指令),且pd的brmask指明该指令是br指令。则判断控制流指令是br指令
-
pd的jmpinfo有效,且cifIndx有效。则进一步根据jmpinfo判断是那种类型的jmp指令
- 第零位为0:jal
- 第零位为1:jalr
- 第一位为1:call
- 第二位为1:ret
-
判断最后一条指令是否是rvi(4byte)的jmp指令:jmpinfo有效,pd中jmpOffset等于15,且pd的rvcMask指明最后一条指令不是rvc指令
-
判断cfi指令是否是jal指令:cfiindx = jmpOffset,且根据之前的判断确认jmp指令是jal指令
-
判断cfi指令是jalr指令也是同理的。
-
FTB生成:valid被初始化为true
- brslot:在判断控制流指令是br指令时,进行填充
- valid:初始化为true
- offset:cfiindx
- lower和stat:根据startaddr和提交块指定的target计算
- 对应的strongbias:被初始化为true
- tailslot:pd的jmpinfo有效时,进行填充
- valid:根据之前的判断确认jmp指令是jal指令或者是jalr指令时,valid有效
- offset:pd的jmpoffset
- lower和stat:根据startaddr和target计算,如果cfi指令是jalr指令,使用提交块指定的target,否则用pd预测的jalTarget
- 对应的strongbias:根据之前的判断确认jmp指令是jalr指令时,拉高。strongbias是针对于BPU的ittage预测器的,该预测器基于一些统计信息工作,strongbias用来指向指令跳转偏好的强弱,其中jal指令不需要记录strongbias。
- pftAddr:上方介绍已经够详细了
- carry:上方介绍已经足够
- isJalr/isCall/isRet
- last_may_be_rvi_call
- brslot:在判断控制流指令是br指令时,进行填充
4.2.2 情况2:FTB命中,修改旧的FTB项
4.2.2.1 插入brslot的FTB项
在原来的基础上改动即可,比如插入新的slot,注意,只针对新的brslot
- 修改条件:首先根据oldftbentry判断在旧entry中,cfi指令是否被记录为br指令,如果不是,则说明这是一个新的br指令。
- 接着从旧FTB中判断哪些slot可以被插入slot:
- brslot:如果旧FTB的brslot无效,表示该slot空闲,此时可以在此位置插入新的brslot,此外,如果新slot在旧slot之前(新的br指令在旧slotbr指令之前执行,或者说在指令块之前的位置),即使不空也能插入
- tailslot:当不能在brslot插入时,才考虑tailslot,同样,在该slot空闲或者新slot在旧slot之前,可以插入此位置
- 插入slot:
- brslot:能插入时则在这里插入,不能的时候,把对应的strongbias拉低,因为这说明新slot一定在旧slot之后(如果不想要详细了解ittage的原理可以不用理解原因)。
- tailslot:能插入时则在这里插入,不能的时候,如果新slot在旧slot之后,把对应的strongbias拉低,如果不在之后,当原brslot有效(即不空闲),则用插入前的brslot代替该tailslot。对应的strongbias维持不变。
注:tailslot不能插入且新slot在其之前,其实就已经说明brslot一定是可以插入的,所以才有后面的替代
pftaddr 出现新的br指令,同时旧的FTB项内没有空闲的slot,这说明确实发生了在FTB项内确实发生了FTB项的替换,pftaddr也需要做相应的调整。
- 如果没有能插入的位置,使用新的br指令的偏移作为pftaddr对应的偏移,因为此时,新br指令一定在两个slot之后。否则,使用旧FTB项的最后一个slot的offset。将ptfoffset结合startAddr得到最后的pftAddr,carry也进行相应的设置。
- last_may_be_rvi_call,isCall,isRet ,isJalr全部置false。
4.2.2.2 修改jmp target的FTB项
修改条件:当cfi指令是一个jalr指令,且旧的tailslot对应的是一个jump的指令,但tailslot指示的target与提交项指示的target不同时,说明需要对跳转目标进行修改。
- 根据正确的跳转目标对lower和stat进行修改
- 两位strongbias设置成0
4.2.2.3 修改bias的FTB项
当cfi指令就是原FTB项的条件跳转指令,只需要根据跳转情况设置跳转的强弱
- brslot:旧的brslot有发生跳转时,bias在原bias拉高,发生跳转的cfiindex等于该slot的offset,brslot有效时,保持拉高,其余情况拉低。
- tailslot:旧的brslot没有跳转,而tailslot有分支指令且发生跳转,把brslot的bias置为false,tailslot保持bias的方式与上面的brslot一致。
修改条件:当旧的bias拉高且对应的旧的FTB项中的slot中有分支指令,同时修改后的bias拉低。任何一个slot出现这种情况都需要进行修改。
最后,需要抉择出一个修改的FTB项
- 如果cfi是一个新的分支指令,我们采用插入新的slot的FTB项。
- 如果是cfi是一个jalr指令,且跳转目标发生修改,我们采用修改jmp跳转目标的FTB项
- 如cfi指令就是原FTB项的条件跳转指令,采用修改bias的FTB项
4.3 发送新FTB项及相关信号
此时,根据是否hit,我们已经得到更新后的FTB项了,在这个基础上我们继续更新一些相关信号以发送到FTQ更新接口。
- new_br_insert_pos:使用之前我们判断的FTB项中可插入位置的bool数组
- taken_mask:根据cfi指令在更新后FTB项的位置判断,只有分支指令才做此计算,若是jmp指令置为0。
- jump_taken: cfi指令在更新后FTB项的taislot,且jmpValid。
- mispred_mask的最后一项:更新后的FTB项jumpValid,且预译码推断的jmp指令在提交项的错误预测信息中指示错误。
- mispred_mask 预测块内预测错误的掩码。第一、二位分别代表两个条件分支指令是否预测错误,第三位指示无条件跳转指令是否预测错误。
- 接口类型:
Vec(numBr+1, Bool())
- 接口类型:
- mispred_mask 预测块内预测错误的掩码。第一、二位分别代表两个条件分支指令是否预测错误,第三位指示无条件跳转指令是否预测错误。
- old_entry:如果hit,且FTB项不做任何修改,即不满足上述三种修改FTB项的条件,拉高该信号,说明更新后的FTB项是旧的FTB项。
发送处理后的更新信息
此时,我们就可以向BPU发送处理好的更新信息了,下面是update的接口接收的信号
- ftb_entry:更新后的FTB项
- new_br_insert_pos:上一小节已述
- mispred_mask:上一小节已述
- old_entry:上一小节已述
- br_taken_mask: 上一小节已述
- br_committed:根据提交项的提交状态信息判断新FTB项中的有效分支指令是否已经提交
- jmp_taken:上一小节已述
接口说明
顶层IO | 作用 | |
---|---|---|
toBpu | 向BPU发送重定向信息与更新信息 | |
fromBackend | 获取指令交割信息,判断指令块是否被提交 | |
mmioCommiRead | 发送mmio指令的提交信息 |
测试点总表 (【必填项】针对细分的测试点,列出表格)
实际使用下面的表格时,请用有意义的英文大写的功能名称和测试点名称替换下面表格中的名称
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1 | TRANSFER_REDIRECT | REDIRECT_FROM_FLUSH | IFU重定向结果有效时,拉高该信号 |
1.2 | TRANSFER_REDIRECT | CHOOSE_REDIRECT | 如果后端重定向结果fromBackendRedirect有效,选用fromBackendRedirect,否则选用IFU重定向结果ifuRedirectToBpu |
2 | UPDATE_STALL | UPDATE_STALL | 当发生BPU的更新时候,会暂停FTQ对指令块的提交以及发送更新信息 |
3.1.1 | CAN_COMMIT_ENTRY | COND1 | 当commPtr不等于ifuWbPtr,且没有因为BPU更新而暂停,同时robCommPtr在commPtr之后,canCommit拉高 |
3.1.2 | CAN_COMMIT_ENTRY | COND2 | commitStateQueue 中commPtr对应指令块有指令处于c_toCommit 或c_committed状态。且指令块中最后一条处于c_toCommit 或c_committed状态的指令是c_committed的,canCommit拉高 |
3.2.1 | MOVECOMMPTR | BY_ROB_COMMIT | 在commPtr指向的指令块如果能提交,可以移动CommPtr |
3.2.2 | MOVECOMMPTR | BY_FLUSH | commitStateQueue 中commPtr对应指令块的第一条指令被后端重定向冲刷掉,可以移动CommPtr |
3.3.1 | UPDATE_ROB_COMM_PTR | COND1 | 当来自后端接口fromBackend的rob_commits信息中,有信息有效时,取最后一条有效交割信息的ftqIdx作为robCommPtr |
3.3.2 | UPDATE_ROB_COMM_PTR | COND2 | 不满足情况1,选取commPtr, robCommPtr中较大的那个 |
3.4.1 | MMIO_LAST_COMMIT | COND1 | 当commPtr比来自mmioCommitRead接口的mmioFtqPtr大时,mmioLastCommit信号在下一个周期被拉高 |
3.4.2 | MMIO_LAST_COMMIT | COND2 | 两者正好相等,且commPtr指向的指令块中有c_toCommit 或c_committed状态的指令,最后一条处于c_toCommit 或c_committed状态的指令是c_committed的,mmioLastCommit信号在下一个周期被拉高 |
4.1 | SEND_UPDATE_TO_BPU | SEND_SUBQUEUE_INFO_TO_UPDATE | 将提交项的子队列读取信息发向更新通道 |
4.2.1 | UPDATE_FTB_ENTRY | CREATE_NEW | FTB未命中,创建一个新的FTB项 |
4.2.2.1 | CREATE_NEW_FTB_ENTRY | INSERT | FTB未命中,创建一个新的FTB项,在原来的基础上改动即可,插入新的slot |
4.2.2.2 | CREATE_NEW_FTB_ENTRY | jmp target | FTB未命中,创建一个新的FTB项,在原来的基础上改动即可,当cfi指令是一个jalr指令,且旧的tailslot对应的是一个jump的指令,但tailslot指示的target与提交项指示的target不同时,说明需要对跳转目标进行修改 |
4.2.2.3 | CREATE_NEW_FTB_ENTRY | bias | FTB未命中,创建一个新的FTB项,在原来的基础上改动即可,当cfi指令就是原FTB项的条件跳转指令,只需要根据跳转情况设置跳转的强弱 |
4.3 | SEND_UPDATE_TO_BPU | SEND_NEW_FTB_RELATED | 根据是否hit,我们已经得到更新后的FTB项了,在这个基础上我们继续更新一些相关信号以发送到FTQ更新接口。 |
2 - ICache
本文档参考香山 IFU 设计文档写成
本次验证对象是昆明湖前端指令缓存(ICache)的模块。验证的代码版本为XiangShan_20250307_4b2c87ba
请注意,本文档撰写的测试点仅供参考,部分测试点需要修改,如能补充或修改测试点,最终获得的奖励可能更高!
ICache 说明文档
文档概述
本文档描述 ICache 的模块列表、设计规格、参数列表、功能概述和详述。
功能概述部分提供 ICache 整体数据流向图和过程。
术语说明
缩写 | 全称 | 中文名称 | 说明 |
---|---|---|---|
ICache | Instruction Cache | L1 指令缓存 | 用于存储最近访问过的指令,以减少对主内存的访问次数,从而提高处理速度。 |
DCache | Data Cache | L1 数据缓存 | 用于存储最近访问过的数据,以减少对主内存的访问次数,从而提高处理速度。 |
L2 Cache | Level Two Cache | L2 缓存 | 从主内存中预取指令和数据,作为 iCache 和主内存之间的缓冲区。 |
ITLB | Instruction TLB | 指令地址转换缓冲 | 用于将虚拟地址转换为物理地址的缓冲。 |
BPU | Branch Prediction Unit | 分支预测单元 | 预测程序中的条件分支,以便提前获取和解码后续指令,这样可以减少等待分支结果的时间。 |
FTQ | Fetch Target Queue | 取指目标队列 | 暂存 BPU 预测的取指目标,并根据这些取指目标给 IFU 发送取指请求。 |
IFU | Instruction Fetch Unit | 取指单元 | 进行取指,预译码,分支预测检查,指令扩展和非法检查。 |
BEU | Bus Error Unit | 总线错误单元 | 总线错误可以使用总线错误单元 (BEU) 对 hart 产生中断。 |
PF | Page Fault | 缺页异常 | 当 CPU 访问的内存地址不在物理内存中时,会触发缺页异常。 |
GPF | Guest Page Fault | 虚拟机缺页异常 | 是在虚拟化环境中,客户机(guest)操作系统尝试访问的虚拟地址未能成功映射到物理地址时产生的异常。 |
AF | Access Fault | 访问错误 | 当 CPU 试图访问一个不允许的物理地址时,会触发访问错误。 |
PMP | Physical Memory Protection | 物理内存保护模块 | RISC-V 架构中提供的一种硬件机制,用于控制不同内存区域的读、写和执行权限,主要目的是提供对物理内存的保护和隔离。 |
PMA | Physical Memory Attribute | 物理内存属性模块(PMP 的一部分) | RISC-V 系统中,机器物理地址空间的每个区域的这些属性和能力称为物理内存属性。 |
PBMT | Page-Base Memory Type | 基于页面的内存类型 | 一种内存管理机制,它使用分页(paging)技术来管理虚拟内存。见特权手册 Svpbmt 扩展。 |
MMIO | Memory-Mapped Input/Output | 内存映射输入/输出 | 在 MMIO 中,外设的寄存器和内存被映射到同一个地址空间。 |
cacheline | cacheline | 缓存行 | 缓存中的最小存储单位。 |
MetaArray | MetaArray | 元数据数组 | 用于存储指令的元数据信息,包括指令的地址、访问权限、是否有效等。 |
DataArray | DataArray | 数据数组 | 用于存储指令数据的数组。 |
SRAM | Static Random Access Memory | 静态随机存取存储器 | 一种用于存储数据的存储器,具有随机访问特性。 |
MSHR | Miss Status Holding Register | 缺失状态保持寄存器 | 用来处理非阻塞缓存(non-blocking cache)中的缓存未命中(cache miss)情况 。 |
SATP | Supervisor Address Translation and Protection | 监管者地址转换和保护单元 | 管理地址转换和保护机制,它决定了虚拟地址到物理地址的转换方式以及访问权限的控制。 |
VS | Virtual Supervisor | 虚拟监管者 | 是 H 扩展引入的一种特权模式,用于运行虚拟机中的操作系统。包括两级地址翻译,虚拟 CSR 和异常和中断虚拟化等机制。 |
hartID | hardware thread ID | 硬件线程标识 | RISC-V 硬件线程 ID。在 RISC-V 架构中,每个处理器核心都有一个唯一的 hart ID,用于区分同一处理器中运行的多个硬件线程。 |
SFENCE.VMA | Supervisor Memory-Management Fence Instruction | 监管者内存管理屏障指令 | 同步对内存中内存管理数据结构的更新与当前执行的指令。 |
fence.i | fence.i | 屏障指令 | 用于同步指令流与数据流,确保在指令之前的存储操作对后续取指可见。 |
ICache 和 DCache 区别
功能用途
ICache 专门用于存储指令。当 CPU 从内存中读取指令时,这些指令会先被存储在 ICache 中。这样,当 CPU 需要再次执行相同的指令时,可以直接从 ICache 中读取,而无需再次访问速度较慢的内存,从而提高指令的读取速度,加快程序的执行效率。
DCache 用于存储数据。程序运行过程中,CPU 需要频繁地读取和写入数据。DCache 的作用就是缓存这些数据,使得 CPU 对数据的访问速度更快。当 CPU 访问内存中的数据时,如果数据已经在 DCache 中,就可以直接从 DCache 中读取或写入,减少了访问内存的次数,提高了数据访问的效率。
数据一致性问题
ICache 通常不需要考虑数据一致性问题。因为指令是只读的,一旦程序开始运行,指令的内容一般不会改变。所以,ICache 中的指令可以一直使用,直到程序结束或者新的指令被加载进来。即使内存中的指令被修改了,也不会影响 ICache 中已经缓存的指令的执行。
DCache 数据一致性是一个重要的问题。因为数据可能会被多个处理器或者设备访问和修改。如果一个处理器修改了 DCache 中的数据,而其他处理器或者设备仍然使用旧的数据,就会导致数据不一致的问题。为了保证数据一致性,需要采用一些机制,如缓存一致性协议(如 MESI 协议等),来确保所有处理器和设备看到的数据是一致的。
为什么我们需要区分数据和指令呢?原因之一是出于性能的考量。CPU 在执行程序时,可以同时获取指令和数据,做到硬件上的并行,提升性能。另外,指令和数据有很大的不同。例如,指令一般不会被修改,所以 iCache 在硬件设计上是可以是只读的,这在一定程度上降低硬件设计的成本。所以硬件设计上,系统中一般存在 L1 dCache 和 L1 iCache,L2 Cache 和 L3 Cache。
区分原因
指令访问模式是:通常按照程序的顺序依次执行,具有较高的局部性。并且在程序运行过程中通常是只读的,不会被修改。 数据访问模式是:数据的访问通常较为随机,可能涉及频繁的读写操作。数据需要支持读写操作,这意味着 DCache 需要处理数据的写入和一致性问题。
将数据和指令分开存储到 DCache 和 ICache,有利于提高命中率和减少冲突,提升性能(CPU 在执行程序时,可以同时获取指令和数据,做到硬件上的并行),简化设计(ICache 可以专注读指令,而 DCache 需要数据的读写操作,还需要考虑数据一致性问题)。
模块列表
子模块 | 描述 |
---|---|
MainPipe | 主流水线 |
IPrefetchPipe | 预取流水线 |
WayLookup | 元数据缓冲队列 |
MetaArray | 元数据 SRAM |
DataArray | 数据 SRAM |
MissUnit | 缺失处理单元 |
Replacer | 替换策略单元 |
CtrlUnit | 控制单元,目前仅用于控制错误校验/错误注入功能 |
ICache | ICache 顶层模块 |
FIFO | 先入先出循环队列,在 MissUnit 中有使用 |
设计规格
- 缓存指令数据
- 缺失时通过 tilelink 总线向 L2 请求数据
- 软件维护 L1 I/D Cache 一致性(
fence.i
) - 支持跨 cacheline (预)取指请求
- 支持冲刷(bpu redirect、backend redirect、
fence.i
) - 支持预取指请求
- 硬件预取为 FDIP 预取算法
- 软件预取为 Zicbop 扩展
prefetch.i
指令
- 支持可配置的替换算法
- 支持可配置的缺失状态寄存器数量
- 支持检查地址翻译错误、物理内存保护错误
- 支持错误检查 & 错误恢复 & 错误注入1
- 默认采用 parity code
- 通过从 L2 重取实现错误恢复
- 软件可通过 MMIO 空间访问的错误注入控制寄存器
- DataArray 支持分 bank 存储,细存储粒度实现低功耗
参数列表
若和 chisel 代码不一致,以 chisel 代码为准。
参数 | 默认值 | 描述 | 要求 |
---|---|---|---|
nSets | 256 | SRAM set 数量 | 2 的幂次 |
nWays | 4 | SRAM way 数量 | |
nFetchMshr | 4 | 取指 MSHR 的数量 | |
nPrefetchMshr | 10 | 预取 MSHR 的数量 | |
nWayLookupSize | 32 | WayLookup 深度,同时可以反压限制预取最大距离 | |
DataCodeUnit | 64 | 校验单元大小,单位为 bit,每 64bit 对应 1bit 的校验位 | |
ICacheDataBanks | 8 | cacheline 划分 bank 数量 | |
ICacheDataSRAMWidth | 66 | DataArray 基本 SRAM 的宽度 | 大于每 bank 的 data 和 code 宽度之和 |
功能概述

FTQ 指针示意
FTQ 中存储着 BPU 生成的预测块,fetchPtr 指向取指预测块,prefetchPtr 指向预取预测块,当复位时 prefetchPtr 与 fetchPtr 相同,每成功发送一次取指请求时 fetchPtr++,每成功发送一次预取请求时 prefetchPtr++。详细说明见FTQ 设计文档。

ICache整体数据流向图
ICache 结构如上图所示,有 MainPipe 和 IPrefetchPipe 两个流水线,MainPipe 接收来自 FTQ 的取指请求,IPrefetchPipe 接收来自 FTQ/MemBlock 的硬/软件预取请求。
-
对于预取请求,IPrefetch 对 MetaArray 进行查询,将元数据(在哪一路命中、ECC 校验码、是否发生异常等)存储到 WayLookup 中,如果该请求缺失,就发送至 MissUnit 进行预取。
-
对于取指请求,MainPipe 首先从 WayLookup 中读取命中信息,如果 WayLookup 中没有可用信息,MainPipe 就会阻塞,直至 IPrefetchPipe 将信息写入 WayLookup 中,该方案将 MetaArray 和 DataArray 的访问分离,一次只访问 DataArray 单路,代价是产生了一个周期的重定向延迟。
-
MissUnit 处理来自 MainPipe 的取指请求和来自 IPrefetchPipe 的预取请求,通过 MSHR 进行管理,所有 MSHR 公用一组数据寄存器以减少面积。
-
CtrlUnit 主要负责 ECC 校验使能/错误注入等功能。从 MetaArray 读取元数据,之后向 MetaArray 写入毒化的标签,向 DataArray 写入毒化的数据。Tilelink 用于外部配置和状态监控,通过 eccctrl 和 ecciaddr 寄存器实现读写交互。
-
Replacer 为替换器,采用 PLRU 替换策略,接收来自 MainPipe 的命中更新,向 MissUnit 提供写入的 waymask。
-
MetaArray 分为奇偶两个 bank,用于支持跨 cacheline 的双行访问。
-
DataArray 中的 cacheline 分为 8 个 bank 存储,每个 bank 中存储的有效数据为 64bit,另外对于每 64bit 还需要 1bit 的校验位,由于 65bit 宽度的 SRAM 表现不好,所以选用深度 256*宽度 66bit 的 SRAM 作为基本单元,一共有 32 个这样的基本单元。一次访问需要 34Byte 的指令数据,每次需要访问 5 个 bank($8\times 5 > 34$),根据起始地址进行选择。
功能详述
取指/预取指请求

ICache 两条流水线的关系
FTQ 分别把取指/预取指请求发送到对应的取指/预取指流水线进行处理。如前所述,由 IPrefetch 对 MetaArray 和 ITLB 进行查询,将元数据(在哪一路命中、ECC 校验码、是否发生异常等)在 IPrefetchPipe s1 流水级存储到 WayLookup 中,以供 MainPipe s0 流水级读取。
在上电解复位/重定向时,由于 WayLookup 为空,而 FTQ 的 prefetchPtr、fetchPtr 复位到同一位置,MainPipe s0 流水级不得不阻塞等待 IPrefetchPipe s1 流水级的写入,这引入了一拍的额外重定向延迟。但随着 BPU 向 FTQ 填充预测块的进行和 MainPipe/IFU 因各种原因阻塞(e.g. miss、IBuffer 满),IPrefetchPipe 将工作在 MainPipe 前(prefetchPtr > fetchPtr
),而 WayLookup 中也会有足够的元数据,此时 MainPipe s0 级和 IPrefetchPipe s0 级的工作将是并行的。
详细的取指过程见MainPipe、IPrefetchPipe和WayLookup。
硬件预取与软件预取
V2R2 后,ICache 可能接受两个来源的预取请求:
- 来自 Ftq 的硬件预取请求,基于 FDIP 算法。
- 来自 Memblock 中 LoadUint 的软件预取请求,其本质是 Zicbop 扩展中的 prefetch.i 指令,请参考 RISC-V CMO 手册。
然而,PrefetchPipe 每周期仅可以处理一个预取请求,故需要进行仲裁。ICache 顶层负责缓存软件预取请求,并与来自 Ftq 的硬件预取请求二选一送往 PrefetchPipe,软件预取请求的优先级高于硬件预取请求。
逻辑上来说,每个 LoadUnit 都有可能发出软件预取请求,因此每周期至多会有 LoadUnit 数量(目前默认参数为 LduCnt=3)个软件预取请求。但出于实现成本和性能收益考量,ICache 每周期至多仅接收并处理一个,多余的会被丢弃,端口下标最小的优先。此外,若 PrefetchPipe 阻塞,而 ICache 内已经缓存了一个软件预取请求,那么原先的软件预取请求将被覆盖。

ICache 预取请求接收与仲裁
发送到 PrefetchPipe 后,软件预取请求的处理和硬件预取请求的处理几乎是一致的,除了:
- 软件预取请求不会影响控制流,即不会发送到 MainPipe(和后续的 Ifu、IBuffer 等环节),仅做:1) 判断是否 miss 或异常;2) 若 miss 且无异常,发送到 MissUnit 做预取指并重填 SRAM。
关于 PrefetchPipe 的细节请查看子模块文档。
异常传递/特殊情况处理
ICache 负责对取指请求的地址进行权限检查(通过 ITLB 和 PMP),可能的异常和特殊情况有:
来源 | 异常 | 描述 | 处理 |
---|---|---|---|
ITLB | af | 虚拟地址翻译过程出现访问错误 | 禁止取指,标记取指块为 af,经 IFU、IBuffer 发送到后端处理 |
ITLB | gpf | 客户机页错误 | 禁止取指,标记取指块为 gpf,经 IFU、IBuffer 发送到后端处理,将有效的 gpaddr 和 isForNonLeafPTE 发送到后端的 GPAMem 以备使用 |
ITLB | pf | 页错误 | 禁止取指,标记取指块为 pf,经 IFU、IBuffer 发送到后端处理 |
backend | af/pf/gpf | 同 ITLB 的 af/pf/gpf | 同 ITLB 的 af/pf/gpf |
PMP | af | 物理地址无权限访问 | 同 ITLB af |
MissUnit | L2 corrupt | L2 cache 响应 corrupt | 标记取指块为 af,经 IFU、IBuffer 发送到后端处理 |
需要指出,对于一般的取指流程来说,并不存在 backend 异常这一项。但 XiangShan 出于节省硬件资源的考虑,在前端传递的 pc 只有 41 / 50 bit(Sv394 / Sv484),而对于 jr
、jalr
等指令,跳转目标来源于 64 bit 寄存器。根据 RISC-V 规范,高位非全 0 或全 1 时的地址不合法,需要引发异常,这一检查只能由后端完成,并随同后端重定向信号一起发送到 Ftq,进而随同取指请求一起发送到 ICache。其本质是一种 ITLB 异常,因此解释描述和处理方式与 ITLB 相同。
另外,L2 cache 通过 tilelink 总线响应 corrupt 可能是 L2 ECC 错误(d.corrupt
),亦可能是无权限访问总线地址空间导致拒绝访问(d.denied
)。tilelink 手册规定,当拉高 d.denied
时必须同时拉高 d.corrupt
。而这两种情况都需要将取指块标记为 access fault,因此目前在 ICache 中无需区分这两种情况(即无需关注 d.denied
,其可能被 chisel 自动优化掉而导致 verilog 中看不到)。
这些异常间存在优先级:backend 异常 > ITLB 异常 > PMP 异常 > MissUnit 异常。这是自然的:
- 当出现 backend 异常时,发送到前端的 vaddr 不完整且不合法,故 ITLB 地址翻译过程无意义,检查出的异常无效;
- 当出现 ITLB 异常时,翻译得到的 paddr 无效,故 PMP 检查过程无意义,检查出的异常无效;
- 当出现 PMP 异常时,paddr 无权限访问,不会发送(预)取指请求,故不会从 MissUnit 得到响应。
而对于 backend 的三种异常、ITLB 的三种异常,由 backend 和 ITLB 内部进行有优先级的选择,保证同时至多只有一种拉高。
此外,一些机制还会引发一些特殊情况,在旧版文档/代码中也称为异常,但其实际上并不引发 RISC-V 手册定义的 exception
,为了避免混淆,此后将称为特殊情况:
来源 | 特殊情况 | 描述 | 处理 |
---|---|---|---|
PMP | mmio | 物理地址为 mmio 空间 | 禁止取指,标记取指块为 mmio,由 IFU 进行非推测性取指 |
ITLB | pbmt.NC | 页属性为不可缓存、幂等 | 禁止取指,由 IFU 进行推测性取指 |
ITLB | pbmt.IO | 页属性为不可缓存、非幂等 | 同 pmp mmio |
MainPipe | ECC error | 主流水检查发现 MetaArray/DataArray ECC 错误 | 见ECC 一节,旧版同 ITLB af,新版做自动重取 |
DataArray 分 bank 的低功耗设计
目前,ICache 中每个 cacheline 分为 8 个 bank,bank0-7。一个取指块需要 34B 指令数据,故一次访问连续的 5 个 bank。存在两种情况:
- 这 5 个 bank 位于单个 cacheline 中(起始地址位于 bank0-3)。假设起始地址位于 bank2,则所需数据位于 bank2-6。如下图 a。
- 跨 cacheline(起始地址位于 bank4-7)。假设起始地址位于 bank6,则数据位于 cacheline0 的 bank6-7、cacheline1 的 bank0-2。有些类似于环形缓冲区。如下图 b。

DataArray 分 bank 示意图
当从 SRAM 或 MSHR 中获取 cacheline 时,根据地址将数据放入对应的 bank。
由于每次访问只需要 5 个 bank 的数据,因此 ICache 到 IFU 的端口实际上只需要一个 64B 的端口,将两个 cacheline 各自的 bank 选择出来并拼接在一起返回给 IFU(在 DataArray 模块内完成);IFU 将这一个 64B 的数据复制一份拼接在一起,即可直接根据取指块起始地址选择出取指块的数据。不跨行/跨行两种情况的示意图如下:

DataArray 单行数据返回示意图

DataArray 双行数据返回示意图
亦可参考 IFU.scala 中的注释。
NOTE: the following `Cat(_data, _data)` _is_ intentional.
Explanation:
In the old design, IFU is responsible for selecting requested data from two adjacent cachelines,
so IFU has to receive 2*64B (2cacheline * 64B) data from ICache, and do `Cat(_data(1), _data(0))` here.
However, a fetch block is 34B at max, sending 2*64B is quiet a waste of power.
In current design (2024.06~), ICacheDataArray is responsible for selecting data from two adjacent cachelines,
so IFU only need to receive 40B (5bank * 8B) valid data, and use only one port is enough.
For example, when pc falls on the 6th bank in cacheline0(so this is a doubleline request):
MSB LSB
cacheline 1 || 1-7 | 1-6 | 1-5 | 1-4 | 1-3 | 1-2 | 1-1 | 1-0 ||
cacheline 0 || 0-7 | 0-6 | 0-5 | 0-4 | 0-3 | 0-2 | 0-1 | 0-0 ||
and ICacheDataArray will respond:
fromICache.bits.data || 0-7 | 0-6 | xxx | xxx | xxx | 1-2 | 1-1 | 1-0 ||
therefore simply make a copy of the response and `Cat` together, and obtain the requested data from centre:
f2_data_2_cacheline || 0-7 | 0-6 | xxx | xxx | xxx | 1-2 | 1-1 | 1-0 | 0-7 | 0-6 | xxx | xxx | xxx | 1-2 | 1-1 | 1-0 ||
requested data: ^-----------------------------^
For another example, pc falls on the 1st bank in cacheline 0, we have:
fromICache.bits.data || xxx | xxx | 0-5 | 0-4 | 0-3 | 0-2 | 0-1 | xxx ||
f2_data_2_cacheline || xxx | xxx | 0-5 | 0-4 | 0-3 | 0-2 | 0-1 | xxx | xxx | xxx | 0-5 | 0-4 | 0-3 | 0-2 | 0-1 | xxx ||
requested data: ^-----------------------------^
Each "| x-y |" block is a 8B bank from cacheline(x).bank(y)
Please also refer to:
- DataArray selects data:
https://github.com/OpenXiangShan/XiangShan/blob/d4078d6edbfb4611ba58c8b0d1d8236c9115dbfc/src/main/scala/xiangshan/frontend/icache/ICache.scala#L355-L381
https://github.com/OpenXiangShan/XiangShan/blob/d4078d6edbfb4611ba58c8b0d1d8236c9115dbfc/src/main/scala/xiangshan/frontend/icache/ICache.scala#L149-L161
- ICache respond to IFU:
https://github.com/OpenXiangShan/XiangShan/blob/d4078d6edbfb4611ba58c8b0d1d8236c9115dbfc/src/main/scala/xiangshan/frontend/icache/ICacheMainPipe.scala#L473
冲刷
在后端/IFU 重定向、BPU 重定向、fence.i
指令执行时,需要视情况对 ICache 内的存储结构和流水级进行冲刷。可能的冲刷目标/动作有:
- MainPipe、IPrefetchPipe 所有流水级
- 冲刷时直接将
s0/1/2_valid
置为false.B
即可
- 冲刷时直接将
- MetaArray 中的 valid
- 冲刷时直接将
valid
置为false.B
即可 tag
、code
不需要冲刷,因为它们的有效性由valid
控制- DataArray 中的数据不需要冲刷,因为它们的有效性由 MetaArray 中的
valid
控制
- 冲刷时直接将
- WayLookup
- 读写指针复位
gpf_entry.valid
置为false.B
- MissUnit 中所有 MSHR
- 若 MSHR 尚未向总线发出请求,请求和预取请求直接置无效(
valid === false.B
) - 若 MSHR 已经向总线发出请求,记录待冲刷(
flush === true.B
或fencei === true.B
),等到 d 通道收到 grant 响应时再置无效,同时不把 grant 的数据回复给 MainPipe/PrefetchPipe,也不写入 SRAM - 需要留意,当 d 通道收到 grant 响应的同时收到冲刷(io.flush === true.B
或io.fencei === true.B
)时,MissUnit 同样不写入 SRAM,但会将数据回复给 MainPipe/PrefetchPipe,避免将端口的延时引入响应逻辑中,此时 MainPipe/PrefetchPipe 也同步收到了冲刷请求,因此会将数据丢弃
- 若 MSHR 尚未向总线发出请求,请求和预取请求直接置无效(
每种冲刷原因需要执行的冲刷目标:
冲刷原因 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
后端/IFU 重定向 | Y | Y | Y | |
BPU 重定向 | Y2 | |||
fence.i |
Y3 | Y | Y3 | Y |
ICache 进行冲刷时不接收取指/预取请求(io.req.ready === false.B
)
对 ITLB 的冲刷
ITLB 的冲刷比较特殊,其缓存的页表项仅需要在执行 sfence.vma
指令时冲刷,而这条冲刷通路由后端负责,因此前端或 ICache 一般不需要管理 ITLB 的冲刷。只有一个特例:目前 ITLB 为了节省资源,不会存储 gpaddr
,而是在 gpf
发生时去 L2TLB 重取,重取状态由一个 gpf
缓存控制,这要求 ICache 在收到 ITLB.resp.excp.gpf_instr
时保证下面两个条件之一:
- 重发相同的
ITLB.req.vaddr
,直到ITLB.resp.miss
拉低(此时gpf
、gpaddr
均有效,正常发往后端处理即可),ITLB 此时会冲刷gpf
缓存。 - 给
ITLB.flushPipe
,ITLB 在收到该信号时会冲刷gpf
缓存。
若 ITLB 的 gpf
缓存未被冲刷,就收到了不同 ITLB.req.vaddr
的请求,且再次发生 gpf
,将导致核卡死。
因此,每当冲刷 IPrefetchPipe 的 s1 流水级时,无论冲刷原因为何,都需要同步冲刷 ITLB 的 gpf
缓存(即拉高 ITLB.flushPipe
)。
ECC
首先需要指出,ICache 在默认参数下使用 parity code,其仅具备 1 bit 错误检测能力,不具备错误恢复能力,严格意义上不能算是 ECC(Error Correction Code)。但一方面,其可以配置为使用 secded code;另一方面,我们在代码中大量使用 ECC 来命名错误检测与错误恢复相关的功能(ecc_error
、ecc_inject
等)。因此本文档仍将使用 ECC 一词来指代错误检测、错误恢复、错误注入相关功能以保证与代码的一致性。
ICache 支持错误检测、错误恢复、错误注入功能,是 RAS4 能力的一部分,可以参考 RISC-V RERI5 手册,由 CtrlUnit 进行控制。
错误检测
在 MissUnit 向 MetaArray 和 DataArray 重填数据时,会计算 meta 和 data 的校验码,前者和 meta 一起存储在 Meta SRAM 中,后者存储在单独的 Data Code SRAM 中。
当取指请求读取 SRAM 时,会同步读取出校验码,在 MainPipe 的 s1/s2 流水级中分别对 meta/data 进行校验。软件可以通过向 mmio-mapped CSR 中相应位置写入特定的值来使能/关闭这一功能,详见 CtrlUnit 文档。
在校验码设计方面,ICache 使用的校验码可由参数控制,默认使用的是 parity code,即校验码为对数据做规约异或 $code = \oplus data$。检查时只需将校验码和数据一起做规约异或 $error = (\oplus data) \oplus code$,结果为 1 则发生错误,反之认为没有错误(可能出现偶数个错误,但此处检查不出来)。
ICache 支持错误注入,这要求 ICache 支持向 MetaArray/DataArray 写入错误的校验码。因此实现了一个poison
位,当其拉高时,翻转写入的 code,即 $code = (\oplus data) \oplus poison$。
为了减少检查不出的情况,目前将 data 划分成 DataCodeUnit(默认为 64bit)的单元分别进行奇偶校验,因此对每个 64B 的缓存行,总计会计算 $8(data) + 1(meta) = 9$ 个校验码。
当 MainPipe 的 s1/s2 流水级检查到错误时,会进行以下处理:
在 6 月至 11 月的版本中:
- 错误处理:引起 access fault 异常,由软件处理。
- 错误报告:向 BEU 报告错误,后者会引起中断向软件报告错误。
- 取消请求:当 MetaArray 被检查出错误时,其读出的 ptag 不可靠,进而对 hit 与否的判断不可靠,因此无论是否 hit 都不向 L2 Cache 发送请求,而是直接将异常传递到 IFU、进而传递到后端处理。
在后续版本(#3899 后)实现了错误自动恢复机制,故只需进行以下处理:
- 错误处理:从 L2 Cache 重新取指,见错误自动恢复。
- 错误报告:拉高 erros.valid 向顶层报告错误。
错误自动恢复
注意到,ICache 与 DCache 不同,是只读的,因此其数据必然不是 dirty 的,这意味着我们总是可以从下级存储结构(L2/3 Cache、memory)中重新获取正确的数据。因此,ICache 可以通过向 L2 Cache 重新发起 miss 请求来实现错误自动恢复。
实现重取功能本身只需要复用现有的 miss 取指路径,走 MainPipe -> MissUnit -> MSHR –tilelink-> L2 Cache 的请求路径。MissUnit 向 SRAM 重填数据时会自然地计算新的校验码并存储,因此在重取后会回到无错误的状态而不需要额外的处理。
6-11 月和后续代码行为差异的伪代码示意如下:
- exception = itlb_exception || pmp_exception || ecc_error
+ exception = itlb_exception || pmp_exception
- should_fetch = !hit && !exception
+ should_fetch = (!hit || ecc_error) && !exception
需要留意的是:为了避免重取后出现 multi-hit(即,同一个 set 内存在多个 way 的 ptag 相同),需要在重取前将 metaArray 对应位置的 valid 清空:
- 若 MetaArray 错误:meta 保存的 ptag 本身可能出错,命中结果(one-hot 的 waymask)不可靠,“对应位置”指该 set 的所有 way
- 若 DataArray 错误:命中结果可靠,“对应位置”指该 set 中 waymask 拉高的那一 way
错误注入
根据 RERI 手册5的说明,为了使软件能够测试 ECC 功能,进而更好地判断硬件功能是否正常,需要提供错误注入功能,即主动地触发 ECC 错误。
ICache 的错误注入功能由 CtrlUnit 控制,通过向 mmio-mapped CSR 中相应位置写入特定的值来触发。详见 CtrlUnit 文档。
目前 ICache 支持:
- 向特定 paddr 注入,当请求注入的 paddr 未命中时,注入失败
- 向 MetaArray 或 DataArray 注入
- 当 ECC 校验功能本身未使能时,注入失败
软件注入流程示意如下:
inject_target:
# maybe do something
ret
test:
la t0, $BASE_ADDR # 载入 mmio-mapped CSR 基地址
la t1, inject_target # 载入注入目标地址
jalr ra, 0(t1) # 跳转到注入目标以保证其加载到 ICache
sd t1, 8(t0) # 向 CSR 写入注入目标地址
la t2, ($TARGET << 2 | 1 << 1 | 1 << 0) # 设置注入目标、注入使能、校验使能
sd t1, 0(t0) # 向 CSR 写入注入请求
loop:
ld t1, 0(t0) # 读取 CSR
andi t1, t1, (0b11 << (4+1)) # 读取注入状态
beqz t1, loop # 如果注入未完成,继续等待
addi t1, t1, -1
bnez t1, error # 如果注入失败,跳转到错误处理
jalr ra, 0(t1) # 注入成功,跳转到注入目标地址以触发错误
j finish # 结束
error:
# handle error
finish:
# finish
我们编写了一个测试用例,见此仓库,其测试了如下情况:
- 正常注入 MetaArray
- 正常注入 DataArray
- 注入无效的目标
- 注入但 ECC 校验未使能
- 注入未命中的地址
- 尝试写入只读的 CSR 域
参考文献
- Glenn Reinman, Brad Calder, and Todd Austin. “Fetch directed instruction prefetching.” 32nd Annual ACM/IEEE International Symposium on Microarchitecture (MICRO). 1999.
ICache 模块功能说明
以下是IPrefetchPipe模块的功能
1. 接收预取请求
从 FTQ 接收预取请求,请求可能有效( io.req.valid 为高),可能无效; IPrefetchPipe 可能处于空闲( io.req.ready 为高),可能处于非空闲状态。 只有在请求有效且 IPrefetchPipe 处于空闲状态时,预取请求才会被接收(这里暂不考虑 s0 的刷新信号 s0_flush ,默认其为低)。 预取请求分为不同类型,包括硬件预取请求 (isSoftPrefetch = false)和软件预取请求 (isSoftPrefetch = true)。 cacheline 也分为单 cacheline 和双 cacheline。
1.1 硬件预取请求:
预取请求为硬件 (isSoftPrefetch = false)
序号 | 名称 | 描述 |
---|---|---|
1.1.1 | 预取请求可以继续 | 当预取请求有效且 IPrefetchPipe 处于空闲状态时,预取请求应该被接收。 s0_fire 信号在没有 s0 的刷新信号( s0_flush 为低)时,应该被置为高。 |
1.1.2 | 预取请求被拒绝–预取请求无效 | 当预取请求无效时,预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.1.3 | 预取请求被拒绝–IPrefetchPipe 非空闲 | 当 IPrefetchPipe 非空闲时,预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.1.4 | 预取请求被拒绝–预取请求无效且 IPrefetchPipe 非空闲 | 当预取请求无效且 IPrefetchPipe 非空闲时,预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.1.5 | 预取请求有效且为单 cacheline | 当预取请求有效且为单 cacheline 时,预取请求应该被接收。 s0_fire 为高,s0_doubleline 应该被置低(false)。 |
1.1.6 | 预取请求有效且为双 cacheline | 当预取请求有效且为双 cacheline 时,预取请求应该被接收。 s0_fire 为高,s0_doubleline 应该被置高(true)。 |
1.2 软件预取请求:
预取请求为软件 (isSoftPrefetch = true)
序号 | 名称 | 描述 |
---|---|---|
1.2.1 | 软件预取请求可以继续 | 当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。 当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。 |
1.2.2 | 软件预取请求被拒绝–预取请求无效 | 当预取请求无效时,软件预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.2.3 | 软件预取请求被拒绝–IPrefetchPipe 非空闲 | 当 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.2.4 | 软件预取请求被拒绝–预取请求无效且 IPrefetchPipe 非空闲 | 当预取请求无效且 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.2.5 | 软件预取请求有效且为单 cacheline | 当软件预取请求有效且为单 cacheline 时,软件预取请求应该被接收。 s0_fire 为高,s0_doubleline 应该被置低(false)。 |
1.2.6 | 软件预取请求有效且为双 cacheline | 当软件预取请求有效且为双 cacheline 时,软件预取请求应该被接收。 s0_fire 为高,s0_doubleline 应该被置高(true)。 |
2. 接收来自 ITLB 的响应并处理结果
接收 ITLB 的响应,完成虚拟地址到物理地址的转换。 当 ITLB 发生缺失(miss)时,保存请求信息,等待 ITLB 完成后再继续处理。
2.1 地址转换完成
- 根据 ITLB 的响应,接收物理地址(paddr),并完成地址转换。
- 处理 ITLB 响应可能在不同周期到达的情况,管理有效信号和数据保持机制,确保正确使用物理地址。
序号 | 名称 | 描述 |
---|---|---|
2.1.1 | ITLB 正常返回物理地址 | ITLB 在一个周期内成功返回物理地址 paddr,s1_valid 为高。 确认 s1 阶段正确接收到 paddr。 |
2.1.2 | ITLB 发生 TLB 缺失,需要重试 | fromITLB(PortNumber).bits.miss 为高,表示对应通道的 ITLB 发生了 TLB 缺失,需要重发。 重发完成后,后续步骤继续进行,fromITLB(PortNumber).bits.miss 为低。 |
2.2 处理 ITLB 异常
- 根据 ITLB 的异常信息,处理可能的异常。pf 缺页、pgf 虚拟机缺页、af 访问错误。
序号 | 名称 | 描述 |
---|---|---|
2.2.1 | ITLB 发生页错误异常 | s1_itlb_exception 返回的页错误。 iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示页错误 pf。 |
2.2.2 | ITLB 发生虚拟机页错误异常 | s1_itlb_exception 返回的虚拟机页错误。 iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示虚拟机页错误 pgf。 |
2.2.3 | ITLB 发生访问错误异常 | s1_itlb_exception 返回的访问错误。 iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示访问错误 af。 |
2.3 处理虚拟机物理地址(用于虚拟化)
- 在虚拟化环境下,处理虚拟机物理地址(gpaddr),确定访问是否针对二级虚拟机的非叶子页表项(isForVSnonLeafPTE)。
序号 | 名称 | 描述 |
---|---|---|
2.3.1 | 发生虚拟机页错误异常返回虚拟机物理地址(gpaddr) | 发生 pgf 后,需要返回对应的 gpaddr。 只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。 |
2.3.2 | ITLB 发生虚拟机页错误异常 | 发生 gpf 后,如果是访问二级虚拟机的非叶子页表项时,需要返回对应的 gpaddr。 只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。 |
2.4 返回基于页面的内存类型 pbmt 信息
- TLB 有效时,返回 pbmt 信息。
3. 接收来自 IMeta(缓存元数据)的响应并检查缓存命中
从 Meta SRAM 中读取缓存标签和有效位。 将物理地址的标签部分与缓存元数据中的标签比较,确定是否命中。
序号 | 名称 | 描述 |
---|---|---|
3.1 | 缓存标签比较和有效位检查: | 从物理地址中提取物理标签(ptag),将其与缓存元数据中的标签进行比较,检查所有缓存路(Way)。检查有效位,确保只考虑有效的缓存行。 |
3.1 | 缓存未命中(标签不匹配或有效位为假): | 当标签不匹配或者标签匹配,但是有效位为假时,表示缓存未命中。 s1_meta_ptags(PortNumber)(nWays) 不等于 ptags(PortNumber) 或者它们相等,但是对应的 s1_meta_valids 为低时,总之返回的 waymasks 为全 0。 |
3.2 | 单路缓存命中(标签匹配且有效位为真): | 当标签匹配,且有效位为真时,表示缓存命中。 waymasks 对应的位为 1。 |
4. PMP(物理内存保护)权限检查
对物理地址进行 PMP 权限检查,确保预取操作的合法性。 处理 PMP 返回的异常和 MMIO 信息
序号 | 名称 | 描述 |
---|---|---|
4.1 | 访问被允许的内存区域 | itlb 返回的物理地址在 PMP 允许的范围内。 s1_pmp_exception(i) 为 none。 |
4.2 | 访问被禁止的内存区域 | s1_req_paddr(i) 对应的地址在 PMP 禁止的范围内。 s1_pmp_exception(i) 为 af。 |
4.3 | 访问 MMIO 区域 | itlb 返回的物理地址在 MMIO 区域。 s1_pmp_mmio 为高。 |
5. 异常处理和合并
backend 优先级最高,merge 方法里的异常越靠前优先级越高 合并来自后端、ITLB、PMP 的异常信息,按照优先级确定最终的异常类型。
序号 | 名称 | 描述 |
---|---|---|
5.1 | 仅 ITLB 产生异常 | s1_itlb_exception(i) 为非零,s1_pmp_exception(i) 为零。 s1_exception_out(i) 正确包含 ITLB 异常。 |
5.2 | 仅 PMP 产生异常 | s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为非零。 s1_exception_out(i) 正确包含 PMP 异常。 |
5.3 | 仅 后端 产生异常 | s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为零。 s1_exception_out(i) 正确包含 后端 异常。 |
5.4 | ITLB 和 PMP 都产生异常 | s1_itlb_exception(i) 和 s1_pmp_exception(i) 都为非零。 s1_exception_out(i) 包含 ITLB 异常(优先级更高)。 |
5.5 | ITLB 和 后端 都产生异常 | s1_itlb_exception(i) 和 s1_backendException(i) 都为非零。 s1_exception_out(i) 包含 后端 异常(优先级更高)。 |
5.6 | PMP 和 后端 都产生异常 | s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。 s1_exception_out(i) 包含 后端 异常(优先级更高)。 |
5.7 | ITLB、PMP 和 后端 都产生异常 | s1_itlb_exception(i)、s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。 s1_exception_out(i) 包含 后端 异常(优先级更高)。 |
5.8 | 无任何异常 | s1_itlb_exception(i)、s1_pmp_exception(i)、s1_backendException(i) 都为零。 s1_exception_out(i) 指示无异常。 |
6. 发送请求到 WayLookup 模块
当条件满足时,将请求发送到 WayLookup 模块,以进行后续的缓存访问。
序号 | 名称 | 描述 |
---|---|---|
6.1 | 正常发送请求到 WayLookup | toWayLookup.valid 为高,toWayLookup.ready 为高,s1_isSoftPrefetch 为假。 请求成功发送,包含正确的地址、标签、waymask 和异常信息。 |
6.2 | WayLookup 无法接收请求 | toWayLookup.valid 为高,toWayLookup.ready 为假。 状态机等待 WayLookup 准备好,不会错误地推进。 |
6.3 | 软件预取请求不发送到 WayLookup | s1_isSoftPrefetch 为真。 toWayLookup.valid 为假,不会发送预取请求到 WayLookup。 |
7. 状态机控制和请求处理流程
使用状态机管理 s1 阶段的请求处理流程。 包括处理 ITLB 重发、Meta 重发、进入 WayLookup、等待 s2 准备等状态
7.1 初始为 m_idle 状态
序号 | 名称 | 描述 |
---|---|---|
7.1.1 | 正常流程推进,保持 m_idle 状态 | s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为真,s2_ready 为真。 状态机保持在 m_idle 状态,s1 阶段顺利推进。 |
7.1.2 | ITLB 未完成,需要重发 | s1_valid 为高,itlb_finish 为假。 状态机进入 m_itlbResend 状态,等待 ITLB 完成。 |
7.1.3 | ITLB 完成,WayLookup 未命中 | s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为假。 状态机进入 m_enqWay 状态,等待 WayLookup 入队。 |
7.2 初始为 m_itlbResend 状态
序号 | 名称 | 描述 |
---|---|---|
7.2.1 | ITLB 命中, MetaArray 空闲,需要 WayLookup 入队 | itlb_finish 为假,toMeta.ready 为真。 状态机进入 m_enqWay 状态,等待 WayLookup 入队。 |
7.2.2 | ITLB 命中, MetaArray 繁忙,等待 MetaArray 读请求 | itlb_finish 为假,toMeta.ready 为假。 状态机进入 m_metaResend 状态,MetaArray 读请求 |
7.3 初始为 m_metaResend 状态
序号 | 名称 | 描述 |
---|---|---|
7.3 | MetaArray 空闲 ,需要 WayLookup 入队 | toMeta.ready 为真。 状态机进入 m_enqWay 状态,等待 WayLookup 入队。 |
7.4 初始为 m_enqWay 状态
序号 | 名称 | 描述 |
---|---|---|
7.4.1 | WayLookup 入队完成或者为软件预取, S2 空闲, 重新进入空闲状态 | toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为假。 状态机进入空闲状态 m_idle。 |
7.4.2 | WayLookup 入队完成或者为软件预取, S2 繁忙,需要 enterS2 状态 | toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为真。 状态机进入 m_enterS2 状态,等待 s2 阶段准备好。 |
7.5 初始为 m_enterS2 状态
序号 | 名称 | 描述 |
---|---|---|
7.5 | s2 阶段准备好,请求进入下流水级,流入后进入 m_idle 状态 | s2_ready 为真。 状态机进入空闲状态 m_idle。 |
8. 监控 missUnit 的请求
检查 missUnit 的响应,更新缓存的命中状态和 MSHR 的匹配状态。
序号 | 名称 | 描述 |
---|---|---|
8.1 | 请求与 MSHR 匹配且有效: | s2_req_vSetIdx 和 s2_req_ptags 与 fromMSHR 中的数据匹配,且 fromMSHR.valid 为高,fromMSHR.bits.corrupt 为假。 s2_MSHR_match(PortNumber) 为真, s2_MSHR_hits(PortNumber) 应保持为真 |
8.2 | 请求在 SRAM 中命中: | s2_waymasks(PortNumber) 中有一位为高,表示在缓存中命中。 s2_SRAM_hits(PortNumber) 为真,s2_hits(PortNumber) 应为真。 |
8.3 | 请求未命中 MSHR 和 SRAM: | 请求未匹配 MSHR,且 s2_waymasks(PortNumber) 为空。 s2_MSHR_hits(PortNumber)、s2_SRAM_hits(PortNumber) 均为假, s2_hits(PortNumber) 为假。 |
9. 发送请求到 missUnit
对于未命中的预取请求,向 missUnit 发送请求,以获取缺失的数据。
9.1 确定需要发送给 missUnit 的请求
根据命中状态、异常信息、MMIO 信息等,确定哪些请求需要发送到 missUnit(即 s2_miss)。
序号 | 名称 | 描述 |
---|---|---|
9.1.1 | 请求未命中且无异常,需要发送到 missUnit: | s2_hits(PortNumber) 为假(未命中缓存),s2_exception 无异常,s2_mmio 为假(不是 MMIO 或不可缓存的内存)。 s2_miss(PortNumber) 为真,表示需要发送请求到 missUnit。 |
9.1.2 | 请求命中或有异常,不需要发送到 missUnit: | s2_hits(i) 为真(已命中)或者 s2_exception 有异常 或者 s2_mmio 为真(MMIO 访问)。 s2_miss(i) 为假,不会发送请求到 missUnit。 |
9.1.3 | 双行预取时,处理第二个请求的条件: | s2_doubleline 为真,处理第二个请求。 如果第一个请求有异常或 MMIO,s2_miss(1) 应为假,后续请求被取消或处理。 |
9.2 避免发送重复请求,发送请求到 missUnit
- 使用寄存器 has_send 记录每个端口是否已发送请求,避免重复发送。
- 将需要发送的请求通过仲裁器 toMSHRArbiter 发送到 missUnit。
序号 | 名称 | 描述 |
---|---|---|
9.2.1 | 在 s1_real_fire 时,复位 has_send: | s1_real_fire 为高。 has_send(PortNumber) 应被复位为假,表示新的请求周期开始。 |
9.2.2 | 当请求成功发送时,更新 has_send: | toMSHRArbiter.io.in(PortNumber).fire 为高(请求已发送)。 has_send(PortNumber) 被设置为真,表示该端口已发送请求。 |
9.2.3 | 避免重复发送请求: | 同一请求周期内,has_send(PortNumber) 为真,s2_miss(PortNumber) 为真。 toMSHRArbiter.io.in(PortNumber).valid 为假,不会再次发送请求。 |
9.2.4 | 正确发送需要的请求到 missUnit: | s2_valid 为高,s2_miss(i) 为真,has_send(i) 为假。 toMSHRArbiter.io.in(i).valid 为高,请求被成功发送。 |
9.2.5 | 仲裁器正确仲裁多个请求: | 多个端口同时需要发送请求。 仲裁器按照优先级或设计要求选择请求发送到 missUnit,未被选中的请求在下个周期继续尝试发送。 |
10. 刷新机制
- io.flush: 全局刷新信号,当该信号为高时,所有请求都需要刷新。
- from_bpu_s0_flush:当请求不是软件预取(!s0_isSoftPrefetch, 软件预取请求是由特定的指令触发的,与指令流中的分支预测无关。因此,在处理刷新信号时,对于软件预取请求,通常不受来自 BPU 的刷新信号影响。),且 BPU 指示需要在 Stage 2 或 Stage 3 刷新的请求,由于该请求尚未进入 s1 阶段,因此在 s0 阶段也需要刷新。
- s0_flush:综合考虑全局刷新信号、来自 BPU 的刷新信号,以及 s1 阶段的刷新信号
- from_bpu_s1_flush:当 s1 阶段的请求有效且不是软件预取,且 BPU 指示在 Stage 3 需要刷新,则在 s1 阶段需要刷新。
- io.itlbFlushPipe:当 s1 阶段需要刷新时,该信号用于通知 ITLB 刷新其流水线,以保持一致性。
- s1_flush:综合考虑全局刷新信号和来自 BPU 的刷新信号。
- s2_flush:用于控制 s2 阶段是否需要刷新。
序号 | 名称 | 描述 |
---|---|---|
10.1 | 发生全局刷新 | io.flush 为高。 s0_flush、s1_flush、s2_flush 分别为高,所有阶段的请求被正确清除。 |
10.2 | 来自 BPU 的刷新 | io.flushFromBpu.shouldFlushByStageX 为真(X 为 2 或 3),且请求不是软件预取。 对应阶段的 from_bpu_sX_flush 为高,sX_flush 为高,阶段请求被刷新。 |
10.3 | 刷新时状态机复位 | s1_flush 为高。 状态机 state 被重置为 m_idle 状态。 |
10.4 | ITLB 管道同步刷新 | s1_flush 为高。 io.itlbFlushPipe 为高,ITLB 被同步刷新。 |
以下是MainPipe模块的功能
11. 访问 DataArray 的单路
根据从 WayLookup 获取信息,包括路命中信息和 ITLB 查询结果还有 DataArray 当前的情况,决定是否需要从 DataArray 中读取数据。
序号 | 名称 | 描述 |
---|---|---|
11.1 | 访问 DataArray 的单路 | 当 WayLookup 中的信息表明路命中时,ITLB 查询成功,并且 DataArray 当前没有写时,MainPipe 会向 DataArray 发送读取请求,以获取数据。 s0_hits 为高(一路命中),s0_itlb_exception 信号为零(ITLB 查询成功),toData.last.ready 为高(DataArray 没有正在进行的写操作)。 toData.valid 信号为高,表示 MainPipe 向 DataArray 发出了读取请求。 |
11.2 | 不访问 DataArray(Way 未命中) ==会访问,但是返回数据无效== | 当 WayLookup 中的信息表明路未命中时,MainPipe 不会向 DataArray 发送读取请求。 s0_hits 为低表示缓存未命中 toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 |
11.3 | 不访问 DataArray(ITLB 查询失败)==会访问,但是返回数据无效== | 当 ITLB 查询失败时,MainPipe 不会向 DataArray 发送读取请求。 s0_itlb_exception 信号不为零(ITLB 查询失败)。 toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 |
11.4 | 不访问 DataArray(DataArray 正在进行写操作) | 当 DataArray 正在进行写操作时,MainPipe 不会向 DataArray 发送读取请求。 toData.last.ready 信号为低,表示 DataArray 正在进行写操作。 toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 |
12. Meta ECC 校验
将物理地址的标签部分与对应的 Meta 进行 ECC 校验,以确保 Meta 的完整性。
序号 | 名称 | 描述 |
---|---|---|
12.1 | 无 ECC 错误 | 当 waymask 全为 0(没有命中),则 hit_num 为 0 或 waymask 有一位为 1(一路命中),hit_num 为 1 且 ECC 对比通过(encodeMetaECC(meta) == code) s1_meta_corrupt 为假。 |
12.2 | 单路命中的 ECC 错误 | 当 waymask 有一位为 1(一路命中),ECC 对比失败(encodeMetaECC(meta) != code) s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 |
12.3 | 多路命中 | > hit multi-way, must be an ECC failure 当 waymask 有两位及以上为 1(多路命中),视为 ECC 错误。 s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 |
12.4 | ECC 功能关闭 | 当奇偶校验关闭时(ecc_enable 为低),强制清除 s1_meta_corrupt 信号置位。 不管是否发生 ECC 错误,s1_meta_corrupt 都为假。 |
13. PMP 检查
- 将 S1 的物理地址 s1_req_paddr(i) 和指令 TlbCmd.exec 发往 PMP,判断取指是否合法。
- 防止非法地址,区分普通内存和 MMIO 内存。
序号 | 名称 | 描述 |
---|---|---|
13.1 | 没有异常 | s1_pmp_exception 为全零,表示没有 PMP 异常。 |
13.2 | 通道 0 有 PMP 异常 | s1_pmp_exception(0) 为真,表示通道 0 有 PMP 异常。 |
13.3 | 通道 1 有 PMP 异常 | s1_pmp_exception(1) 为真,表示通道 1 有 PMP 异常。 |
13.4 | 通道 0 和通道 1 都有 PMP 异常 | s1_pmp_exception(0) 和 s1_pmp_exception(1) 都为真,表示通道 0 和通道 1 都有 PMP 异常。 |
13.5 | 没有映射到 MMIO 区域 | s1_pmp_mmio(0) 和 s1_pmp_mergemmio(1) 都为假,表示没有映射到 MMIO 区域。 |
13.6 | 通道 0 映射到了 MMIO 区域 | s1_pmp_mmio(0) 为真,表示映射到了 MMIO 区域。 |
13.7 | 通道 1 映射到了 MMIO 区域 | s1_pmp_mmio(1) 为真,表示映射到了 MMIO 区域。 |
13.8 | 通道 0 和通道 1 都映射到了 MMIO 区域 | s1_pmp_mmio(0) 和 s1_pmp_mmio(1) 都为真,表示通道 0 和通道 1 都映射到了 MMIO 区域。 |
14. 异常合并
- 将 s1_itlbmergeption 与 s1_pmp_exception 合并生成 s1_exception_out。
- ITLB 异常通常优先于 PMP 异常。merge
序号 | 名称 | 描述 |
---|---|---|
14.1 | 没有异常 | s1_exception_out 为全零,表示没有异常。 |
14.2 | 只有 ITLB 异常 | s1_exception_out 和 s1_itlb_exception 一致 |
14.3 | 只有 PMP 异常 | s1_exception_out 和 s1_pmp_exception 一致 |
14.4 | ITLB 与 PMP 异常同时出现 | > itlb has the highest priority, pmp next s1_exception_out 和 s1_itlb_exception 一致 |
15. MSHR 匹配和数据选择
- 检查当前的请求是否与 MSHR 中正在处理的缺失请求匹配。
- 判断 缓存组索引相同(s1_req_vSetIdx(i) == fromMSHR.bits.vSetIdx) ,物理标签相同 (s1_req_ptags(i) == fromMSHR.bits.blkPaddr);若匹配 MSHR 有效且没有错误(fromMSHR.valid && !fromMSHR.bits.corrupt),则优先使用 MSHR 中的数据
- 避免重复访问 Data SRAM,提升性能;当 MSHR 中已有重填结果时,可立即命中。
序号 | 名称 | 描述 |
---|---|---|
15.1 | 命中 MSHR | MSHR 中已有正确数据时,S1 阶段能直接拿到 s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 s1_bankMSHRHit(i),s1_data_is_from_MSHR(i) 为 true |
15.2 | 未命中 MSHR | MSHR 中存放的地址与当前请求不同,那么应该读取 SRAM 的数据 s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false |
15.3 | MSHR 数据 corrupt | fromMSHR.bits.corrupt = true,那么 MSHR 将不匹配,应该读取 SRAM 的数据 s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false |
16. Data ECC 校验
在 S2 阶段,对从 S1 或 MSHR 获得的数据(如 s2_datas)进行 ECC 校验:
- 若 ECC 校验失败,则标记 s2_data_corrupt(i) = true。
- 若数据来自 MSHR,则不重复进行 ECC 校验(或忽略 corrupt)
序号 | 名称 | 描述 |
---|---|---|
16.1 | 无 ECC 错误 | s2_bank 全部没有损坏,bank 也选对了对应的端口和 bank,数据不来自 MSHR s2_data_corrupt(i) 为 false,没有 ECC 错误。 |
16.2 | 单 Bank ECC 错误 | s2_bank_corrupt(bank) 有一个为 true ,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 |
16.3 | 多 Bank ECC 错误 | s2_bank_corrupt(bank) 有两个或以上为 true,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 |
16.4 | ECC 功能关闭 | 当奇偶校验关闭时(ecc_enable 为低),强制清除 s2_data_corrupt 信号置位。 不管是否发生 ECC 错误,s2_data_corrupt 都为假。 |
17. 冲刷 MetaArray
Meta 或者 Data ECC 校验错误时,会冲刷 MetaArray,为重取做准备。
序号 | 名称 | 描述 |
---|---|---|
17.1 | 只有 Meta ECC 校验错误 | > if is meta corrupt, clear all way (since waymask may be unreliable) 当 s1_meta_corrupt 为真时,MetaArray 的所有路都会被冲刷。 toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。 |
17.2 | 只有 Data ECC 校验错误 | > if is data corrupt, only clear the way that has error 当 s2_data_corrupt 为真时,只有对应路会被冲刷。 toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的对应路置位。 |
17.3 | 同时有 Meta ECC 校验错误和 Data ECC 校验错误 | 处理 Meta ECC 的优先级更高, 将 MetaArray 的所有路冲刷。 toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。 |
18. 监控 MSHR 匹配与数据更新
- 判断是否命中 MSHR
- 根据 MSHR 是否命中和 s1 阶段是否发射来更新 s2 的数据,s2 的命中状态和 l2 是否损坏
序号 | 名称 | 描述 |
---|---|---|
18.1 | MSHR 命中(匹配且本阶段有效) | MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效 s2_MSHR_match,s2_MSHR_hits 为高,s2_bankMSHRHit 对应 bank 为高 s1_fire 无效时,s2_datas 更新为 MSHR 的数据,将 s2_data_is_from_MSHR 对应位置位,s2_hits 置位,清除 s2_data_corrupt,l2 的 corrupt 更新为 fromMSHR.bits.corrupt s1_fire 有效时,s2_datas 为 s1_datas 的数据,将 s2_data_is_from_MSHR 对应位置为 s1 的 s1_data_is_from_MSHR,s2_hits 置为 s1_hits,清除 s2_data_corrupt,l2 的 corrupt 为 false |
18.2 | MSHR 未命中 | MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效,至少有一个未达成 s2_MSHR_hits(i) = false,S2 不会更新 s2_datas,继续保持原先 SRAM 数据或进入 Miss 流程。 |
19. Miss 请求发送逻辑和合并异常
- 通过计算 s2_should_fetch(i) 判断是否需要向 MSHR 发送 Miss 请求:
- 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) || s2_data_corrupt(i)) 时,需要请求重新获取。
- 若端口存在异常或处于 MMIO 区域,则不发送 Miss 请求。
- 使用 Arbiter 将多个端口的请求合并后发送至 MSHR。
- 通过 s2_has_send(i) 避免重复请求。
- 将 S2 阶段已有的 ITLB/PMP 异常(s2_exception)与 L2 Cache 报告的 s2_l2_corrupt(i)(封装后为 s2_l2_exception(i))进行合并。
序号 | 名称 | 描述 | ||
---|---|---|---|---|
19.1 | 未发生 Miss | 当 s2_hits(i) 为高(s2 已经命中),s2 的 meta 和 data 都没有错误,s2 异常,处于 mmio 区域 以上条件至少满足一个时,s2_should_fetch(i) 为低,表示不发送 Miss 请求。 |
||
19.2 | 单口 Miss | 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) s2_data_corrupt(i)),端口不存在异常且未处于 MMIO 区域时,会向 MSHR 发送 Miss 请求。 toMSHRArbiter.io.in(i).valid = true ,Arbiter 只发送一条 Miss 请求。 |
||
19.3 | 双口都需要 Miss | 同上,但是两个端口都满足 s2_should_fetch 为高的条件。 toMSHRArbiter.io.in(0).valid、toMSHRArbiter.io.in(1).valid 均为 true,Arbiter 根据仲裁顺序依次发出请求。 |
||
19.4 | 重复请求屏蔽 | 当 s1_fire 为高,表示可以进入 s2 阶段,那么 s2 还没有发送 s2_has_send(i) := false.B 如果已经有请求发送了,那么对应的 toMSHRArbiter.io.in(i).fire 为高,表示对应的请求可以发送,s2_has_send(i) := true。 此时再次发送,toMSHRArbiter.io.in(i).valid 为低,表示发送失败。 |
||
19.5 | 仅 ITLB/PMP 异常 | S1 阶段已记录了 ITLB 或 PMP 异常,L2 corrupt = false。 2_exception_out 仅保留 ITLB/PMP 异常标记,无新增 AF 异常。 |
||
19.6 | 仅 L2 异常 | S2 阶段 s2_l2_corrupt(i) = true,且无 ITLB/PMP 异常。 s2_exception_out(i) 表示 L2 访问错误(AF)。 |
||
19.7 | ITLB + L2 同时出现 | 同时触发 ITLB 异常和 L2 corrupt。 s2_exception_out 优先保留 ITLB 异常类型,不被 L2 覆盖。 |
||
19.8 | s2 阶段取指完成 | s2_should_fetch 的所有端口都为低,表示需要取指,那么取指完成 s2_fetch_finish 为高 |
20. 响应 IFU
- 若当前周期 S2 成功发射(s2_fire = true)且数据获取完毕(s2_fetch_finish),则把数据、异常信息、物理地址等打包到 toIFU.bits 输出。
- 若为双行请求(s2_doubleline = true),也会向 IFU 发送第二路的信息(地址、异常)。
序号 | 名称 | 描述 |
---|---|---|
20.1 | 正常命中并返回 | 不存在任何异常或 Miss,s2 命中,s2 阶段取指完成,外部的 respStall 停止信号也为低 。 toIFU.valid = true,toIFU.bits.data 为正确的 Cacheline 数据,toIFU.bits.exception、pmp_mmio、itlb_pbmt = none。 |
20.2 | 异常返回 | 设置 ITLB、PMP、或 L2 corrupt 异常。 toIFU.bits.exception(i) = 对应异常类型,pmp_mmio、itlb_pbmt 根据是否有对应的异常设置为 true。 |
20.3 | 跨行取指 | s2_doubleline = true,同时检查第一路、第二路返回情况。 toIFU.bits.doubleline = true。 若第二路正常,toIFU.bits.exception(1) = none;若第二路异常,则 exception(1) 标记相应类型。 pmp_mmio、itlb_pbmt 类似。 |
20.4 | RespStall | 外部 io.respStall = true,导致 S2 阶段无法发射到 IFU。 s2_fire = false,toIFU.valid 也不拉高,S2 保持原状态等待下一拍(或直到 respStall 解除)。 |
21. L2 Corrupt 报告
- 当检测到 L2 Cache 返回的 corrupt 标记时(s2_l2_corrupt(i) = true),在 S2 完成发射后额外向外部错误接口 io.errors(i) 报告。
- 与 Data ECC 或 Meta ECC 不同,L2 corrupt 由 L2 自己报告给 BEU,这里不需要再次报告给 beu。
序号 | 名称 | 描述 |
---|---|---|
21.1 | L2 Corrupt 单路 | s2 阶段准备完成可以发射(s2_fire 为高),s2_MSHR_hits(0)和 fromMSHR.bits.corrupt 为高 s2_l2_corrupt(0) = true,io.errors(0).valid = true,io.errors(0).bits.source.l2 = true。 |
21.2 | 双路同时 corrupt | 端口 0 和端口 1 都从 L2 corrupt 数据中获取。 s2_l2_corrupt 均为 true,发射后分别报告到 io.errors(0) 和 io.errors(1)。 |
22. 刷新机制
- io.flush:外部的全局刷新信号,它用于指示整个流水线需要被冲刷(清空)。
- s0_flush: S0 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S0 阶段的刷新操作。
- s1_flush: S1 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S1 阶段的刷新操作。
- s2_flush: S2 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S2 阶段的刷新操作。
序号 | 名称 | 描述 |
---|---|---|
22.1 | 全局刷新 | io.flush 被激活时,流水线的各个阶段(S0, S1 和 S2)都能正确响应并执行刷新操作。 io.flush = true。 s0_flush, s1_flush, s2_flush = true。 |
22.2 | S0 阶段刷新 | s0_flush = true。 s0_fire = false。 |
22.3 | S1 阶段刷新 | s1_flush = true。 s1_valid, s1_fire = false。 |
22.4 | S2 阶段刷新 | s2_flush = true。 s2_valid, toMSHRArbiter.io.in(i).valid , s2_fire = false |
以下是WayLookup模块的功能
23. 刷新操作
- 接收到全局刷新刷新信号 io.flush 后,读、写指针和 GPF 信息都被重置。
序号 | 名称 | 描述 |
---|---|---|
23.1 | 刷新读指针 | io.flush 为高时,重置读指针。 readPtr.value 为 0, readPtr.flag 为 false。 |
23.2 | 刷新写指针 | io.flush 为高时,重置写指针。 writePtr.value 为 0, writePtr.flag 为 false。 |
23.3 | 刷新 GPF 信息 | io.flush 为高时,重置 GPF 信息。 gpf_entry.valid 为 0, gpf_entry.bits 为 0。 |
24. 读写指针更新
- 读写信号握手完毕之后(io.read.fire/io.write.fire 为高),对应指针加一。
- 因为是在环形队列上,所以超过队列大小后,指针会回到队列头部。
序号 | 名称 | 描述 |
---|---|---|
24.1 | 读指针更新 | 当 io.read.fire 为高时,读指针加一。 readPtr.value 加一。 如果 readPtr.value 超过环形队列的大小,readPtr.flag 会翻转。 |
24.2 | 写指针更新 | 当 io.write.fire 为高时,写指针加一。 writePtr.value 加一。 如果 writePtr.value 超过环形队列的大小,writePtr.flag 会翻转。 |
25. 更新操作
- MissUnit 处理完 Cache miss 后,向 WayLookup 写入命中信息,也就是 update 操作。
- 情况分为两种:
- 命中:更新 waymask 和 meta_codes。
- 未命中:重置 waymask。
序号 | 名称 | 描述 |
---|---|---|
25.1 | 命中更新 | MissUnit 返回的更新信息和 WayLookup 的信息相同时,更新 waymask 和 meta_codes。 vset_same 和 ptag_same 为真。 waymask 和 meta_codes 更新。 hits 对应位为高。 |
25.2 | 未命中更新 | vset_same 和 way_same 为真。 waymask 清零。 hit 对应位为高。 |
25.3 | 不更新 | 其他情况下不更新。 vset_same 为假或者 ptag_same 和 way_same 都为假。 hits 对应位为低。 |
26. 读操作
- 读操作会根据读指针从环形队列中读取信息。
- 如果达成了绕过条件,优先绕过。
序号 | 名称 | 描述 |
---|---|---|
26.1 | Bypass 读 | 队列为空,并且 io.write.valid 写有效时,可以直接读取,而不经过队列。 empty 和 io.write.valid 都为真。 io.read.bits = io.write.bits |
26.2 | 读信号无效 | 队列为空(readPtr === writePtr)且写信号 io.write.valid 为低。 io.read.valid 为低,读信号无效。 |
26.3 | 正常读 | 未达成绕过条件(empty 和 io.write.valid 至少有一个为假)且 io.read.valid 为高。 从环形队列中读取信息。 io.read.bits.entry = entries(readPtr.value) |
26.4 | gpf 命中 | io.read.valid 为高,可以读。 当 gpf_hits 为高时,从 GPF 队列中读取信息。 io.read.bits.gpf = gpf_entry.bits |
26.5 | gpf 命中且被读取 | io.read.valid 为高,可以读。 > also clear gpf_entry.valid when it’s read 当 gpf 命中且被读取其时(io.read.fire 为高),gpf_entry.valid 会被置为 0。 |
26.6 | gpf 未命中 | io.read.valid 为高,可以读。 io.read.bits.gpf 清零。 |
27. 写操作
- 写操作会根据写指针从环形队列中读取信息。
- 如果有 gpf 停止,就会停止写。
序号 | 名称 | 描述 |
---|---|---|
27.1 | gpf 停止 | > if there is a valid gpf to be read, we should stall write gpf 队列数据有效,并且没有被读取或者没有命中,就会产生 gpf 停止,此时写操作会被停止。 gpf_entry.valid && !(io.read.fire && gpf_hit) 为高时,写操作会被停止(io.write.ready 为低)。 |
27.2 | 写就绪无效 | 当队列为满((readPtr.value === writePtr.value) && (readPtr.flag ^ writePtr.flag))或者 gpf 停止时,写操作会被停止。 (io.write.ready 为低) |
27.3 | 正常写 | 当 io.write.valid 为高时(没满且没有 gpf 停止),写操作会被执行。 正常握手完毕 io.write.fire 为高。 写信息会被写入环形队列。 entries(writePtr.value) = io.write.bits.entry。 |
27.4 有 ITLB 异常的写
- 前面与正常写相同,只不过当写信息中存在 ITLB 异常时,会更新 gpf 队列和 gpf 指针。
- 此时如果已经被绕过直接读取了,那么就不需要存储它了。
序号 | 名称 | 描述 |
---|---|---|
27.4.1 | 被绕过直接读取了 | can_bypass 和 io.read.fire 都为高。 gpf_entry.valid 为 false。 gpf_entry.bits = io.write.bits.gpf gpfPtr = writePtr |
27.4.2 | 没有被绕过直接读取 | can_bypass 为低。 gpf_entry.valid 为 true。 gpf_entry.bits = io.write.bits.gpf gpfPtr = writePtr |
以下是FIFO模块的功能
28. 入队操作
序号 | 名称 | 描述 |
---|---|---|
28.1 | 队未满,正常入队 | 当队列未满,且空位不小于一时,可以正常入队,如果从零号位开始入队到最大容量,入队指针的 flag 不会翻转。 io.enq.fire 为高有效,regFiles(enq_ptr.value) = io.enq.bits,enq_ptr.value+1 入队指针移动,入队指针标记位不翻转。 重复以上操作至队满。 |
28.2 | 队未满,入队后标记位翻转 | 当队未满,但是空位却是靠近队尾时,入队一位后就到达了队头,入队指针的 flag 会翻转。 队列的容量为 10,入队指针指向 9,队未满。此时如果 io.enq.fire 为高,则 regFiles(9) = io.enq.bits,enq_ptr.value+1(循环队列,加完后 enq_ptr.value=0)入队指针移动,入队指针标记位翻转。 |
28.3 | 队满,入队就绪信号为低,无法入队 | 当队满时,(enq_ptr.value === deq_ptr.value) && (enq_ptr.flag ^ deq_ptr.flag) 为高,io.enq.ready 为低,io.enq.fire 为低无效。 此时入队,入队指针的 value 和 flag 不变。 |
29. 出队操作
序号 | 名称 | 描述 |
---|---|---|
29.1 | 队非空,正常出队 | 当队列非空时,可以正常出队,如果出队指针不经过最大容量位置,出队指针的 flag 不会翻转。 io.deq.fire 为高有效,io.deq.bits = regFiles(deq_ptr.value),deq_ptr.value+1 出队指针移动,出队指针标记位不翻转。 |
29.2 | 队非空,出队后标记位翻转 | 当队非空,但是出队指针是靠近队尾时,出队一位后就到达了队头,出队指针的 flag 会翻转。 队列的容量为 10,出队指针指向 9,队非空。此时如果 io.deq.fire 为高,则 io.deq.bits = regFiles(9),deq_ptr.value+1(循环队列,加完后 deq_ptr.value=0)出队指针移动,出队指针标记位翻转。 |
29.3 | 队空,出队有效信号为低,无法出队 | 当队空时,enq_ptr === deq_ptr 为高,io.deq.valid 为低,io.deq.fire 为低无效。 此时出队,出队指针的 value 和 flag 不变。 |
30. 刷新清空操作
序号 | 名称 | 描述 |
---|---|---|
30.1 | flush 清空 | 当刷新信号有效时,重置出队和入队的指针和标记位,清空队列。 当 flush 为高时,deq_ptr.value=0,enq_ptr.value=0,deq_ptr.flag=false,enq_ptr.flag=false,empty=true,full=false。 |
以下是MissUnit模块的功能
31. 处理取指缺失请求
处理来自 MainPipe 的取指单元的缓存缺失请求,将缺失请求分发到多个 Fetch MSHR 中的一个,避免重复请求。 低索引的请求优先处理。
序号 | 名称 | 描述 |
---|---|---|
31.1 | 接受新的取指请求 | 当新的 fetch miss 与 MSHR 中的已有请求不重复时(通过 io.fetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Fetch MSHR 中。 当有新的取指缺失请求到达时(io.fetch_req.valid 为高),且没有命中已有的 MSHR(fetchHit 为低),io.fetch_req.ready 应为高,表示可以接受请求。 io.fetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。 |
31.2 | 处理已有的取指请求 | 当已有取指缺失请求到达时(io.fetch_req.valid 为高),且命中已有的 MSHR(fetchHit 为高),io.fetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。 fetchDemux.io.in.valid 应为低,fetchDemux.io.in.fire 为低,表示没有新的请求被分发到 MSHR。 |
31.3 | 低索引的请求优先进入 MSHR | Fetch 的请求会通过 fetchDemux 分配到多个 Fetch MSHR,fetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。 当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。 |
32. 处理预取缺失请求
与 Fetch Miss 类似,但走另一些 MSHR(Prefetch MSHR)。
序号 | 名称 | 描述 |
---|---|---|
32.1 | 接受新的预取请求 | 当新的 prefetch miss 与 MSHR 中的已有请求不重复时(通过 io.prefetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Prefetch MSHR 中。 当有新的预取缺失请求到达时(io.prefetch_req.valid 为高),且没有命中已有的 MSHR(prefetchHit 为低),io.prefetch_req.ready 应为高,表示可以接受请求。 io.prefetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。 |
32.2 | 处理已有的预取请求 | 当已有预取缺失请求到达时(io.prefetch_req.valid 为高),且命中已有的 MSHR(prefetchHit 为高),io.prefetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。 prefetchDemux.io.in.valid 应为低,prefetchDemux.io.in.fire 为低,表示请求被接受但未分发到新的 MSHR。 |
32.3 | 低索引的请求优先进入 MSHR | Prefetch 的请求会通过 prefetchDemux 分配到多个 Prefetch MSHR,prefetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。 当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。 |
32.4 | 先进入 MSHR 的优先进入 prefetchArb | 从 prefetchDemux 离开后,请求的编号会进入 priorityFIFO,priorityFIFO 会根据进入队列的顺序排序,先进入队列的请求会先进入 prefetchArb。 prefetchDemux.io.in.fire 为高,并且 prefetchDemux.io.chosen 有数据时,将其编号写入 priorityFIFO。 在 priorityFIFO 中有多个编号时,出队的顺序和入队顺序一致。 检查 priorityFIFO.io.deq.bit 中的数据即可。 |
33. MSHR 管理与查找
序号 | 名称 | 描述 |
---|---|---|
33.1 | MSHR 查找命中逻辑 | 当新的请求到来时,能够正确查找所有 MSHR,判断请求是否命中已有 MSHR。 当新的请求(取指或预取)到来时,系统遍历所有 MSHR,根据所有 MSHR 的查找信号 allMSHRs(i).io.lookUps(j).hit,检查请求是否已经存在于某个 MSHR 中。 如果命中,则对应的 fetchHit 或 prefetchHit 为高。 对于 prefetchHit 为高,还有一种情况:预取的物理块地址和组索引与取指的相等((io.prefetch_req.bits.blkPaddr === io.fetch_req.bits.blkPaddr) && (io.prefetch_req.bits.vSetIdx === io.fetch_req.bits.vSetIdx))并且有取指请求 io.fetch_req.valid 有效时,也算命中 |
33.2 | MSHR 状态的更新与释放 | 当请求完成后,也就是来自内存总线的响应完成(D 通道接收完所有节拍),MSHR 能够正确地释放(清除其有效位),以便接收新的请求。 TileLink D 通道返回的 source ID ,即 io.mem_grant.bits.source。 无效化信号 allMSHRs(i).io.invalid 为高,对应的 MSHR 的有效位 allMSHRs(i).valid 变为低 |
34. acquireArb 仲裁
预取和取指的 acquire 都会发送给 acquireArb,acquireArb 会选择一个 acquire 发送给 mem_acquire。 acquireArb 使用 chisel 自带的 Arbiter 实现,Arbiter 使用固定优先级仲裁,优先级从编号 0 开始,编号越小优先级越高。
序号 | 名称 | 描述 |
---|---|---|
34.1 | acquireArb 仲裁 | acquireArb 会选择一个 acquire 发送给 mem_acquire。 当有多个 MSHR 同时发出请求时,acquireArb 会根据优先级进行仲裁,选择优先级最高的 MSHR 发送请求。 取指请求总是在 0-3 号,预取请求直接在最后一号,所以取指请求优先级高于预取请求。 当取指 acquire 和预取 acquire 同时发出时,fetchMSHRs(i).io.acquire 和 prefetchMSHRs(i).io.acquire 都有效,仲裁结果 acquireArb.io.out 应该和 fetchMSHRs(i).io.acquire 一致。 |
35. Grant 数据接收与 Refill
在收到 TileLink D 通道数据时收集整行
- 累计 beat 数(readBeatCnt),直到完成一整行 (last_fire)
- 记录 corrupt 标志
- 将完成的请求映射回对应的 MSHR (id_r = mem_grant.bits.source)
序号 | 名称 | 描述 |
---|---|---|
35.1 | 正常完整 Grant 流程,readBeatCnt 为 0 时 | readBeatCnt 初始为 0,refillCycles - 1 也为 0。 io.mem_grant.valid 为高(因为 io.mem_grant.ready 默认为高,所以 io.mem_grant.fire 为高只需要 io.mem_grant.valid 为高)且 io.mem_grant.bits.opcpde(0)为高。 此时 respDataReg(0)= io.mem_grant.bits.data readBeatCnt 加一为 1。 |
35.2 | 正常完整 Grant 流程,readBeatCnt 为 1 时 | io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高。 此时 respDataReg(1)= io.mem_grant.bits.data readBeatCnt 重置回 0。 last_fire 为高。 下一拍 last_fire_r 为高,id_r=io.mem_grant.bits.source。 |
35.3 | 正常完整 Grant 流程,last_fire_r 为高 | last_fire_r 为高,并且 id_r 为 0-13 中的一个。 对应的 fetchMSHRs 或者 prefetchMSHRs 会被无效,也就是 fetchMSHRs_i 或 prefetchMSHRs_i-4 的 io_invalid 会被置高。 |
35.4 | Grant 带有 corrupt 标志 | io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高,则 corrupt_r 应为高。 如果 io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高中有一个不满足,且此时 last_fire_r 为高,则 corrupt_r 重置为低。 |
36. 替换策略更新 (Replacer)
MissUnit 在发出 Acquire 请求时,还会将本次选中的 victim way 对应的索引告诉 io.victim,让替换策略更新其记录(替换策略采用 PLRU) 只有当 Acquire 真正“fire”时,才说明成功替换,replacer 需要更新状态
序号 | 名称 | 描述 |
---|---|---|
36.1 | 正常替换更新 | 当 io.mem.acquire.ready & acquireArb.io.out.valid 同时为高,也就是 acquireArb.io.out.fir 为高时,io.victim.vSetIdx.valid 也为高。 io.victim.vSetIdx.bits = 当前 MSHR 请求的 acquireArb.io.out.bits.vSetIdx。 |
36.2 | 生成 waymask | 根据从 L2 返回的 mshr_resp 中 mshr_resp.bits.way 生成 waymask 信息。 返回的 mshr_resp.bits.way 有 16 位,通过独热码生成一位掩码信息,waymask 表示其中哪一路被替换。 生成的 waymask 应该和 mshr_resp.bits.way 一致。 |
37. 写回 SRAM (Meta / Data)
在一条 Miss Request refill 完成时,将新得到的 Cache line 写到 ICache。 生成 io.meta_write 和 io.data_write 的请求,带上 waymask, tag, idx, data 。 生成 io.meta_write.valid 和 io.data_write.valid 信号。
序号 | 名称 | 描述 |
---|---|---|
37.1 | 生成 io.meta_write.valid 和 io.data_write.valid 信号 | 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。 并且此时没有硬件刷新信号和软件刷新信号,也就是 io.flush 和 io.fencei 为低。 在等待 l2 响应的过程中,没有刷新信号 也没有数据 corrupt,即 corrupt_r 为低。 那么 io.meta_write.valid 和 io.data_write.valid 均为高。 |
37.2 | 正常写 SRAM | io.meta_write.bits 的 virIdx、phyTag、waymask、bankIdx、poison 应该正常更新 io.data_write.bits 的 virIdx、data、waymask、bankIdx、poison 应该正常更新 |
38. 向 mainPipe/prefetchPipe 发出 Miss 完成响应(fetch_resp)
在完成 refill 后,无论是否要真正写阵列,都会向取指端发送“Miss 请求完成” 更新 io.fetch_resp.valid 和 fetch_resp.bits。
序号 | 名称 | 描述 |
---|---|---|
38.1 | 正常 Miss 完成响应 | 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。 无论此时是否有硬件刷新信号和软件刷新信号, io.fetch_resp.valid 都为高,说明可向取指端发送响应。 io.fetch_resp.bits 中的数据更新: io.fetch_resp.bits.blkPaddr = mshr_resp.bits.blkPaddr io.fetch_resp.bits.vSetIdx = mshr_resp.bits.vSetIdx io.fetch_resp.bits.waymask = waymask io.fetch_resp.bits.data = respDataReg.asUInt io.fetch_resp.bits.corrupt = corrupt_r |
39. 处理 flush / fencei
一旦收到 io.flush 或 io.fencei 时,对未发射的请求可立即取消,对已经发射的请求在拿到数据后也不写 SRAM。
序号 | 名称 | 描述 |
---|---|---|
39.1 | MSHR 未发射前 fencei | 如果 MSHR 还没有通过 io.acquire.fire 发出请求,就应立即取消该 MSHR(mshr_resp.valid= false),既不发出请求,也不要写 SRAM。 当 io.fencei 为高时,fetchMSHRs 和 prefetchMSHRs 的 io.req.ready 和 io.acquire.valid 均为低,表示请求不发射。 |
39.2 | MSHR 未发射前 flush | 由于 fetchMSHRs 的 io.flush 被直接设置为 false,所以 io.flush 对 fetchMSHRs 无效,但是对 prefetchMSHRs 有效。 当 io.flush 为高时,只能发射 fetchMSHRs 的请求。 |
39.3 | MSHR 已发射后 flush/fencei | 已经发射了请求,之后再有刷新信号,那么等数据回来了但不写 SRAM。 在发射后,io.flush/io.fencei 为高时,等待数据回来,但是写 SRAM 的信号,write_sram_valid、io.meta_write.valid 和 io.data_write.valid 均为低,表示不写 SRAM。 对于 response fetch 无影响。 |
以下是CtrlUnit模块的功能
40. ECC 启用/禁用
控制 eccctrl.enable 字段来启用或禁用 ECC 功能。外部系统可以通过写寄存器 eccctrl 来控制 ECC 是否启用。
- 通过寄存器写入控制信号 enable,当 enable 为 true 时,ECC 功能启用;为 false 时,ECC 功能禁用。
序号 | 名称 | 描述 |
---|---|---|
40.1 | 启用 ECC | 向 eccctrl.enable 寄存器写入 true,验证模块内部 eccctrl.enable 设置为 true,并确保后续的错误注入操作能够成功进行。此测试确保 eccctrl.enable 写操作被执行。 确保 eccctrl.enable 被正确设置为 true,并触发 eccctrlRegWriteFn 中的写操作逻辑。 |
40.2 | 禁用 ECC | 向 eccctrl.enable 寄存器写入 false,验证模块内部 eccctrl.enable 设置为 false,并确保在后续的错误注入过程中,ECC 功能被禁用,不允许进行错误注入。此测试确保 eccctrl.enable 写操作被正确设置为 false。 验证禁用 ECC 时 eccctrl.enable 为 false,并触发 eccctrlRegWriteFn 中的错误处理分支。x.istatus = eccctrlInjStatus.error 和 x.ierror = eccctrlInjError.notEnabled |
41. 状态机转换
根据状态机的状态,验证错误注入的流程是否正确。
序号 | 名称 | 描述 |
---|---|---|
41.1 | is_idle 状态 | 初始为 is_idle 状态。 当 eccctrl.istatus 为 working 时,验证此时的状态为 is_readMetaReq。 |
41.2 | is_readMetaReq 状态 | 当握手成功后(io.metaRead.ready 和 io.metaRead.valid 都为高),验证此时的状态为 is_readMetaResp。 |
41.3 is_readMetaResp 状态
序号 | 名称 | 描述 |
---|---|---|
41.3.1 | 未命中 | 当 waymask 全零的时候,表示没有命中,会进入 is_idle 状态,并且设置错误错误注入状态和错误原因。 验证此时的状态为 is_idle, eccctrl.istatus = error 和 eccctrl.ierror = notFound。 |
41.3.2 | 命中 | 当 waymask 不全零的时候,表示命中,会根据错误注入目标来判断是向元数据还是数据阵列写入错误。 当 eccctrl.itarget=metaArray 时,验证此时的状态为 is_writeMeta ;当 eccctrl.itarget!=metaArray 时,验证此时的状态为 is_writeData。 |
41.4 is_writeMeta 状态
序号 | 名称 | 描述 |
---|---|---|
41.4.1 | RegWriteFn | 此状态进入后,io.dataWrite.valid 会为高 x.itarget = req.itarget 当 req.inject 为高并且 x.istatus = idle 时: 1. 如果 ecc 的 req.enable = false,则验证 x.istatus = error 且 x.ierror = notEnabled 2. 否则,如果 req.itarget != metaArray 和 dataArray,则验证 x.istatus = error 且 x.ierror = targetInvalid 3. 如果都不满足,则验证 x.istatus = working |
41.4.2 | 状态转换 | 当 io.metaWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。 |
41.5 is_writeData 状态
序号 | 名称 | 描述 |
---|---|---|
41.5.1 | RegWriteFn | 此状态进入后,io.dataWrite.valid 会为高 res.inject = false 当 ready 为高,且 x.istatus = injected 或 x.istatus = error 时,验证 x.istatus = idle 和 x.ierror = notEnabled |
41.5.2 | 状态转换 | 当 io.dataWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。 |
42. 寄存器映射和外部访问
通过 TileLink 总线将寄存器映射到特定地址,使外部模块可以读写 ECC 控制寄存器和注入地址寄存器。
- 使用 TLRegisterNode 实现寄存器的映射,使得外部系统可以通过地址访问寄存器。寄存器的读写操作通过 TileLink 协议进行。
序号 | 名称 | 描述 |
---|---|---|
42.1 | 外部读取和写入 ECC 控制寄存器 | 验证外部模块可以通过 TileLink 协议正确读取和写入 eccctrl 和 ecciaddr 寄存器,并对模块内部的状态产生影响,确保读写操作完全覆盖。 |
42.2 | 外部模块触发错误注入 | 通过外部模块经 TileLink 总线向 eccctrl.inject 寄存器写入 true,触发错误注入,验证内部状态是否按 RegWriteFn 内部过程执行。 |
以下是ICache 顶层模块的功能
43. FTQ 预取请求处理
接收来自 FTQ 的预取请求,经 IPrefetchPipe 请求过滤(查询 ITLB 地址,是否命中 MetaArry,PMP 检查),(有异常则由 MissUnit 处理)后进入 WayLookup。
序号 | 名称 | 描述 |
---|---|---|
43.1 | 预取地址命中,无异常 | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查通过。 如果没有监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该命中的取指数据。 如果监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该是未命中的取指数据。 |
43.2 | 预取地址未命中,无异常 | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 不匹配,pmp 检查通过。 如果监听到 MSHR 将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该命中的取指数据。 如果监听到 MSHR 没有将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该未命中的取指数据。 |
43.3 | 预取地址 TLB 异常,无其他异常 | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 异常。 验证 wayLookup.io.write 的 itlb_exception 内容中,其有对应的异常类型编号(pf:01;gpf:10;af:11)。 |
43.4 | 预取地址 PMP 异常,无其他异常 | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查未通过。 验证 wayLookup.io.write 的 tlb_pbmt 内容中,其有对应的异常类型编号(nc:01;io:10)。 |
44. FTQ 取指请求处理
io.fetch.resp <> mainPipe.io.fetch.resp 发送回 IFU 的数据是在 io.fetch.resp。 接收来自 FTQ 的取指请求,从 WayLookup 获取路命中信息和 ITLB 查询结果,再访问 DataArray,监控 MSHR 的响应。更新 replacer,做 pmp 检查。后做 DataArray 和 MetaArray 的 ECC 校验。最后将数据发送给 IFU。
序号 | 名称 | 描述 |
---|---|---|
44.1 | 取指请求命中,无异常 | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。 验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。 验证 io.fetch.resp 的数据应该是取指的数据。 |
44.2 | 取指请求未命中,MSHR 返回的响应命中,无异常 | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,未命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。 请求在 MSHR 返回的响应命中。 验证 missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。 验证 io.fetch.resp 的数据应该是取指的数据。 |
44.3 | 取指请求命中,ECC 校验错误,无其他异常 | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 或 MetaArray 的 ECC 校验错误。 验证 io.error.valid 为高,且 io.error.bits 内容为对应的错误源和错误类型。 先刷 MetaArray 的 ValidArray,给 MissUnit 发请求,由其在 L2 重填,阻塞至数据返回。 验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。 验证 io.fetch.resp 的数据应该是取指的数据。 |
44.4 | 取指请求未命中,但是 exception 非 0(af、gpf、pf),无其他异常 | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查未通过,DataArray 和 MetaArray 的 ECC 校验正常。 验证 io.fetch.resp 为对应的错误源和错误类型。 验证 io.fetch.resp 的数据无效,里面有异常类型。 |
44.5 | 取指请求未命中,通过 WayLookup 中读取到的预取过来的 itlb 中返回 pbmt。 | 有 itlb_pbmt 和 pmp_mmio 时,他们合成 s1_mmio,传递到 s2_mmio,生成 s2_miss,有特殊情况就不会取指。 io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查通过,DataArray 和 MetaArray 的 ECC 校验正常。 验证 io.fetch.resp 为对应的错误源和错误类型。 验证 io.fetch.resp 的数据无效,里面有特殊情况类型类型。 |
44.6 | 取指请求未命中,pmp 返回 mmio 。 | 处理同 5。 |
45. MetaArray 功能
在 IPrefetchPipe 的 S0,接收来自 IPrefetchPipe 的读请求 read,返回对应路和组的响应 readResp。 在 miss 的时候,MissUnit 会将会应的数据写入 write 到 MetaArray。 MetaArray 主要存储了每个 Cache 行的标签和 ECC 校验码。
序号 | 名称 | 描述 |
---|---|---|
45.1 | 元数据写入操作(对应的 Set 已满): ICacheMetaArray 应当能够正确地将元数据(标签和有效位)写入到指定的 Set 和 Way 。 | 从 MissUnit 返回的请求都是未命中的请求(已命中不会向 MissUnit 请求,那么 MissUnit 自然也不会向 MetaArray 写入)。 发送一个写请求 write 到 ICacheMetaArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最后指定 virIdx、phyTag、waymask、bankIdx、poison。 写入操作后,发起一个对相同虚拟索引的读请求。验证 readResp 的 metas 和 codes 分别包含写入的 ptag 和 ecc code,并且对于写入的路,readResp.entryValid 信号被置为有效。 |
45.2 | 元数据读取操作 (命中): 当一个读请求在 ICacheMetaArray 中命中时(存在有效的条目),它应该返回正确的元数据(标签和有效位)。 | 首先,向特定的虚拟索引(组和路)写入元数据(参照上面的写入操作)。然后,向相同的虚拟索引发送一个读请求。 验证 readResp.metas 包含之前写入的物理标签,并且对于相应的路,readResp.entryValid 信号被置为有效。 |
45.3 | 元数据读取操作 (未命中): 当读取一个尚未被写入的地址时,ICacheMetaArray 应当指示未命中(条目无效)。 | 向 ICacheMetaArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。 验证对于任何路,readResp.entryValid 信号都没有被置为有效。 对应的 readResp.metas 和 codes 的内容是 DontCare 也就是 0。 |
45.4 | 独立的缓存组刷新:在第 i 个端口是有效的刷新请求,并且该请求的 waymask 指定了当前正在处理的第 w 路时,应该使第 i 个端口的条目无效。 | 先向 ICacheMetaArray 写入指定一个或多个端口的元数据,然后再给对应的端口的路发送刷新请求 io.flush,其包含虚拟索引 virIdx 和路掩码 waymask。 验证 valid_array 对应的路中的 virIdx 被置为无效,io.readResp.entryValid 对应路的对应端口为无效。 |
45.5 | 全部刷新操作: ICacheMetaArray 应当能够在接收到全部刷新请求时,使所有条目无效。 | 先向多个不同的虚拟索引写入元数据。然后置位 io.flushAll 信号。 验证步骤: 在 io.flushAll 信号置位后,发起对所有之前写入过的虚拟索引的读请求。验证在所有的读取响应中,对于任何路,readResp.entryValid 信号都没有被置为有效。 |
46. DataArray 功能
与 MetaArray 类似,在 MainPipe 的 S0,接收来自 MainPipe 的读请求 read,返回对应路和组的响应 readResp。 在 miss 的时候,MissUnit 会将会应的数据写入 write 到 DataArray。 DataArray 主要存储了每个 Cache 行的标签和 ECC 校验码。
序号 | 名称 | 描述 |
---|---|---|
46.1 | 数据写入操作(对应的 Set 已满): ICacheDataArray 应当能够正确地将数据写入到指定的 Set (组)、Way (路) 和数据 Bank (存储体)。 | 发送一个写请求 write 到 ICacheDataArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最终指定虚拟索引、数据、路掩码、存储体索引 bankIdx 和毒化位。写入的数据模式应跨越多个数据存储体。 写入操作后,发起一个对相同虚拟索引和块偏移量的读请求。验证 readResp.datas 与写入的数据相匹配。 |
46.2 | 数据读取操作 (命中): 当一个读请求命中时(相应的元数据有效),它应该从相应的组、路和数据存储体返回正确的数据。 | 首先,向特定的虚拟索引和块偏移量写入数据。然后,向相同的虚拟索引和块偏移量发送一个读请求。使用不同的块偏移量进行测试,以覆盖存储体的选择逻辑。 验证 readResp.datas 包含之前写入的数据。 |
46.3 | 数据读取操作 (未命中): 当读取一个尚未被写入的地址时,ICacheDataArray 的输出应该是默认值或无关值。 | 向 ICacheDataArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。 验证 readResp.datas 为 0。 |
ICache 接口说明
为方便测试开展,需要对 ICache 的接口进行进一步的说明,以明确各个接口的含义。
*注意:源文件编译成 verilog/system verilog 后,部分接口会被优化,实际接口以编译后的为准。
IPrefetch 模块接口
控制接口
接口名 | 解释 |
---|---|
csr_pf_enable | 控制 s1_real_fire,软件控制预取开关 |
ecc_enable | 编译后被优化 ,控制 ecc 开启 |
flush | 刷新信号 |
req:FTQ 预取请求
由于 BPU 基本无阻塞,它经常能走到 IFU 的前面,于是 BPU 提供的这些还没发到 IFU 的取指请求就可以用作指令预取,FTQ 中实现了这部分逻辑,直接给 ICache 发送预取请求。 预取请求来自 FTQ,在 S0 流水级传入。
接口名 | 解释 |
---|---|
ready | |
valid | 指示软件预取或者硬件预取是否有效。 |
startAddr | 预测块起始地址。 |
nextlineStart | 预测块下一个缓存行的起始地址。 |
ftqIdx | 指示当前预测块在 FTQ 中的位置,包含 flag 和 value 两个量。 |
isSoftPrefetch | 是否为软件预取(来自 Memblock 中 LoadUint 的软件预取请求)。 |
backendException | ICache 向 IFU 报告后端存在的异常类型。 |
flushFromBpu:来自 BPU 的刷新信息
由 FTQ 传递而来的 BPU 刷新信息,在 S0 流水级传入。 这是预测错误引起的,包括 s2 和 s3 两个同构成员,指示是否在 BPU 的 s2 和 s3 流水级发现了问题。 s2 的详细结构如下:
接口名 | 解释 |
---|---|
valid | 指示 s2 是否有效。 |
ftqIdx | 指示 s2 流水级请求冲刷的预测块在 FTQ 中的位置,包含 flag 和 value 两个量。 |
itlb:请求和响应 itlb 的信息
在 s0 流水级,发送 itlb_req;在 s1 流水级,如果 itlb 命中则直接接收 itlb_resp,否则重发 itlb_req。
req 的结构如下:
接口名 | 解释 |
---|---|
valid | 指示 req 请求是否有效。 |
Tlbreq | 有多个子结构,这里我们只用上了 vaddr,即 req 请求的虚拟地址 |
resp 的结构如下:
接口名 | 解释 |
---|---|
paddr | 指令物理地址。 |
gpaddr | 客户页地址。 |
pbmt | 基于页面的内存类型。 |
miss | 指示 itlb 是否未命中。 |
isForVSnonLeafPTE | 指示是否为非叶 PTE。 |
excp | ITLB 可能产生的异常,包括:访问异常指令 af_instr、客户页错误指令 gpf_instr、页错误指令 pf_instr。见异常传递/特殊情况处理 |
itlbFlushPipe:itlb 刷新信号
在 itlb 中,如果出现 gpf 的取指请求处于推测路径上,且发现出现错误的推测,则会通过 flushPipe 信号进行刷新(包括后端 redirect、或前端多级分支预测器出现后级预测器的预测结果更新前级预测器的预测结果等)。 当 iprefetchpipe 的 s1 被刷新时,itlb 也应该被刷新,该信号会在 s1 流水被置位。
pmp: 物理内存保护相关的信息
在 s1 流水级做 pmp 检查。 pmp 包含 req 和 resp 两个子结构。
req 的结构如下(编译后):
接口名 | 解释 |
---|---|
addr | pmp 检查的地址。 |
resp 的结构如下(编译后):
接口名 | 解释 |
---|---|
instr | 指示物理地址是否有权限访问,没有则会引起 pmp 的 af 异常。 |
mmio | 地址在 mmio 空间。 |
metaRead: 和 MetaArray 交互的读请求和读响应
在 s1 流水级读 meta。
metaRead 包含 toIMeta 和 fromIMeta 两个子结构,即读请求和读响应。
toIMeta 的结构如下(编译后):
接口名 | 解释 |
---|---|
vSetIdx | 虚拟地址的缓存组索引(Virtual Set Index)。 |
isDoubleLine | 预测块是否跨缓存行。 |
fromIMeta 的结构如下(编译后):
接口名 | 解释 |
---|---|
metas | MetaArray 的本身,包含 tag 量。tag,即 cache 的标签。 |
codes | ptag 的 ecc 校验码。 |
entryValid | 指示 meta 是否有效。 |
MSHRReq: MSHR 请求
预取逻辑检测到未命中时,在 s2 流水级,向 MissUnit 发送请求。
接口名 | 解释 |
---|---|
blkPaddr | 要从 tilelink 获取的缓存行的物理地址。 |
vSetIdx | 虚拟地址的缓存组索引。 |
MSHRResp: MSHR 响应
用于在 s1 流水级更新 waymasks 和 meta_codes 以及 s2 流水级判断返回的响应是否命中。
接口名 | 解释 |
---|---|
blkPaddr | 已从 tilelink 获取的缓存行的物理地址。 |
vSetIdx | 虚拟地址的缓存组索引。 |
waymask | 标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。 |
corrupt | 返回的数据块是否损坏。 |
wayLookupWrite: 向 waylookup 写数据
在 s1 流水级,向 waylookup 写数据。 包含 entry(WayLookupEntry)和 gpf(WayLookupGPFEntry)两个子结构。
entry 的结构如下:
接口名 | 解释 |
---|---|
vSetIdx | 虚拟地址的缓存组索引。 |
waymask | 来自 MSHR 的 waymask。 |
ptag | 物理地址标签。 |
itlb_exception | 指示 itlb 是否产生了异常 pf/gpf/af |
itlb_pbmt | 指示 itlb 是否产生 pbmt。 |
meta_codes | meta 的 ecc 校验码。 |
gpf 的结构如下:
接口名 | 解释 |
---|---|
gpaddr | 客户页地址。 |
isForVSnonLeafPTE | 指示是否为非叶 PTE。 |
MainPipe 模块接口
不需要关注的接口
hartId
硬件线程 ID,difftest 使用,不需要关注。
perfInfo
性能信息,不需要关注。
dataArray:和 DataArray 交互的读请求和读响应
在 s0 流水级读请求。
dataArray 包含 toData 和 fromData 两个子结构,即读请求和读响应。
toData 的结构如下:
接口名 | 解释 |
---|---|
vSetIdx | 虚拟地址的缓存组索引。 |
waymask | 标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。通过 MissUnit 写给 prefetch,prefetch 写入 waylookup,mainpipe 从 waylookup 中读出。 |
blkOffset | 指令在块中的偏移。 |
isDoubleLine | 预测块是否跨缓存行。 |
fromData 的结构如下:
接口名 | 解释 |
---|---|
datas | DataArray 本身的数据。 |
codes | data 的 ecc 校验码。 |
metaArrayFlush: 刷新 metaArray
在 s2 流水级,向 metaArray 发送刷新请求, 为重新取指做准备。
接口名 | 解释 |
---|---|
virIdx | 需要刷新的虚拟地址索引。 |
waymask | 需要刷新的路。 |
touch: 更新 replacer
在 s1 流水级,更新 replacer,向 replacer 发送 touch 请求。 把一次访问告诉 replacer,让它更新 plru 状态。
接口名 | 解释 |
---|---|
vSetIdx | 被访问的缓存行的虚拟组索引。 |
way | 被访问的缓存行在集合中的路。 |
wayLookupRead: 读取预取流水写入 waylookup 的信息
在 s0 流水级,从 waylookup 获取元信息。 包含 entry(WayLookupEntry)和 gpf(WayLookupGPFEntry)两个子结构。
entry 的结构如下:
接口名 | 解释 |
---|---|
waymask | 来自 MSHR 的 waymask。 |
ptag | 物理地址标签。 |
itlb_exception | 指示 itlb 是否产生了异常 pf/gpf/af |
itlb_pbmt | 指示 itlb 是否产生 pbmt。 |
meta_codes | meta 的 ecc 校验码。 |
gpf 的结构如下:
接口名 | 解释 |
---|---|
gpaddr | 客户页地址。 |
isForVSnonLeafPTE | 指示是否为非叶 PTE。 |
mshr: 对 MissUnit 中的 mshr 的请求和响应
在 s1 流水级,监听 MSHR 的响应。 在 s2 流水级,缺失时将请求发送至 MissUnit,同时对 MSHR 的响应进行监听,命中时寄存 MSHR 响应的数据。
包含 req 和 resp 两个子结构。
req 的结构如下:
接口名 | 解释 |
---|---|
blkPaddr | 要从 tilelink 获取的缓存行的物理地址。 |
vSetIdx | 虚拟地址的缓存组索引。 |
resp 的结构如下: | |
接口名 | 解释 |
———- | —————————————————— |
blkPaddr | 已从 tilelink 获取的缓存行的物理地址。 |
vSetIdx | 虚拟地址的缓存组索引。 |
waymask | 标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。 |
data | 返回的数据块。 |
corrupt | 返回的数据块是否损坏。 |
fetch: 与 FTQ 交互和 IFU 交互接口
包含 req 和 resp 两个子结构。
req: FTQ 取指请求
在 s0 流水级,接收 FTQ 的取指请求。 包含 pcMemRead,readValid 和 backendException 三个子结构。
其中 pcMemRead 的结构如下:
接口名 | 解释 |
---|---|
startAddr | 预测块起始地址。 |
nextlineStart | 预测块下一个缓存行的起始地址。 |
readValid:读取请求的有效性。
backendException:是否有来自后端的 Exception。
resp: IFU 取指响应
在 s2 流水级,向 IFU 发送取指响应。
接口名 | 解释 |
---|
|doubleLine| 指示预测块是否跨缓存行。| |vaddr |指令块起始虚拟地址、下一个缓存行的虚拟地址。| |data |要传送的缓存行。| |paddr |指令块的起始物理地址| |exception| 向 IFU 报告每个缓存行上的异常情况,方便 ICache 生成每个指令的异常向量。| |pmp_mmio| 指示当前指令块是否在 MMIO 空间。| |itlb_pbmt |ITLB 基于客户页的内存类型,对 MMIO 状态有用。| |backendException| 后端是否存在异常。| |gpaddr| 客户页地址。| |isForVSnonLeafPTE| 是否为非叶的 PTE,来自 itlb。|
flush:全局刷新信号
来自 FTQ。
pmp: 物理内存保护相关的信息
在 s1 流水级做 pmp 检查。 pmp 包含 req 和 resp 两个子结构。
req 的结构如下(编译后):
接口名 | 解释 |
---|---|
addr | 需要检查的地址 |
resp 的结构如下(编译后):
接口名 | 解释 |
---|---|
instr | 指示物理地址是否有权限访问,没有则会引起 pmp 的 af 异常。 |
mmio | 地址在 mmio 空间。 |
respStall
IFU 的 f3_ready 为低时会被置位,表示 IFU 没有准备好接收数据,此时需要 stall。
errors: 向 BEU 报告指令缓存中检测到的错误
在 s2 流水级,综合 data 的 ECC 校验加上从 s1 传来的 meta 的 ECC 校验结果,决定是否向 BEU 报告错误。
编译后:
接口名 | 解释 |
---|---|
valid | 指示 errors 是否有效。 |
bits | 有两个量。paddr 表示错误的物理地址,report_to_beu 表示是否向 beu 报告错误 |
perfInfo: 性能相关信息,不关注
WayLookup 模块接口
flush:全局刷新信号
来自 FTQ。
read:Mainpipe 的读接口
包含 entry(WayLookupEntry)和 gpf(WayLookupGPFEntry)两个子结构。
entry 的结构如下:
接口名 | 解释 |
---|---|
waymask | 来自 MSHR 的 waymask。 |
ptag | 物理地址标签。 |
itlb_exception | 指示 itlb 是否产生了异常 pf/gpf/af |
itlb_pbmt | 指示 itlb 是否产生 pbmt。 |
meta_codes | meta 的 ecc 校验码。 |
gpf 的结构如下:
接口名 | 解释 |
---|---|
gpaddr | 客户页地址。 |
isForVSnonLeafPTE | 指示是否为非叶 PTE。 |
write:IprefetchPipe 的写接口
包含 entry(WayLookupEntry)和 gpf(WayLookupGPFEntry)两个子结构。
entry 的结构如下:
接口名 | 解释 |
---|---|
vSetIdx | 虚拟地址的缓存组索引。 |
waymask | 来自 MSHR 的 waymask。 |
ptag | 物理地址标签。 |
itlb_exception | 指示 itlb 是否产生了异常 pf/gpf/af |
itlb_pbmt | 指示 itlb 是否产生 pbmt。 |
meta_codes | meta 的 ecc 校验码。 |
gpf 的结构如下:
接口名 | 解释 |
---|---|
gpaddr | 客户页地址。 |
isForVSnonLeafPTE | 指示是否为非叶 PTE。 |
update:MissUnit 的更新接口
在 IPrefetchPipe 中等待入队 WayLookup 阶段和在 WayLookup 中等待出队阶段,可能会发生 MSHR 对 Meta/DataArray 的更新。也就是命中状态可能在出入队 WayLookup 时不同。
接口名 | 解释 |
---|---|
blkPaddr | 已从 tilelink 获取的缓存行的物理地址。 |
vSetIdx | 虚拟地址的缓存组索引。 |
waymask | 标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。 |
corrupt | 返回的数据块是否损坏。 |
MissUnit 模块接口
fencei: 软件刷新信号
flush:全局刷新信号
来自 FTQ。
fetch_req:MainPipe 的取指请求缺失时的请求
接口名 | 解释 |
---|---|
blkPaddr | 要从 tilelink 获取的缓存行的物理地址。 |
vSetIdx | 虚拟地址的缓存组索引。 |
fetch_respf: MainPipe 的取指响应缺失时的响应
接口名 | 解释 |
---|---|
blkPaddr | 已从 tilelink 获取的缓存行的物理地址。 |
vSetIdx | 虚拟地址的缓存组索引。 |
waymask | 标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。 |
data | 返回的数据块。 |
corrupt | 返回的数据块是否损坏。 |
prefetch_req: IPrefetchPipe 的预取请求缺失时的请求
接口名 | 解释 |
---|---|
blkPaddr | 要从 tilelink 获取的缓存行的物理地址。 |
vSetIdx | 虚拟地址的缓存组索引。 |
meta_write: MetaArray 的写请求接口
接口名 | 解释 |
---|---|
virIdx | 需要写入的虚拟地址索引。 |
phyTag | 需要写入的物理地址标签。 |
waymask | 指示写入哪一路。 |
bankIdx | 指示写入哪一个存储体索引。 |
data_write: DataArray 的写请求接口
接口名 | 解释 |
---|---|
virIdx | 需要写入的虚拟地址索引。 |
data | 需要写入的数据块。 |
waymask | 需要写入的路。 |
victim:与缓存的替换器(replacer)交互,获取需要被替换的缓存路(way)的信息
接口名 | 解释 |
---|---|
vSetIdx | 虚拟地址的缓存组索引。 |
way | 被替换的路。 |
mem_acquire:Tilelink a 通道发送请求
L2 的总线空闲时,发送请求。
接口名 | 解释 |
---|---|
source | 标识发起此请求的源。 |
address | 要访问的内存的起始物理地址。 |
mem_grant:Tilelink d 通道返回数据
接口名 | 解释 |
---|---|
opcode | 标识响应消息类型的关键字段,它指示了响应的性质和意图。针对 acquire 请求的响应是 GrantData (5,授予) |
source | 请求的源标识。 |
data | 返回的数据块。 |
corrupt | 返回的数据块是否损坏。 |
FIFO 模块接口
enq: 入队信号
接口名 | 解释 |
---|---|
valid | 指示 enq 是否有效。 |
bits | 要入队的数据。 |
deq: 出队信号
接口名 | 解释 |
---|---|
ready | 指示 deq 是否就绪。 |
bits | 要出队的数据。 |
Replacer 模块接口
touch: 更新 replacer
接口名 | 解释 |
---|---|
vSetIdx | 被访问的缓存行的虚拟组索引。 |
way | 被访问的缓存行在集合中的路。 |
victim: 与缓存的替换器(replacer)交互,获取需要被替换的缓存路(way)的信息
接口名 | 解释 |
---|---|
vSetIdx | 虚拟地址的缓存组索引。 |
way | 被替换的路。 |
MetaArray 模块接口
write: MetaArray 的写请求接口
写请求来自 MissUnit 或者 CtrlUnit。
接口名 | 解释 |
---|---|
virIdx | 需要写入的虚拟地址索引。 |
phyTag | 需要写入的物理地址标签。 |
waymask | 指示写入哪一路。 |
bankIdx | 指示写入哪一个存储体索引。 |
poison | 指示是否为毒化位。 |
read: MetaArray 的读请求接口
接口名 | 解释 |
---|---|
vSetIdx | 虚拟地址的缓存组索引(Virtual Set Index)。 |
isDoubleLine | 预测块是否跨缓存行。 |
readResp: MetaArray 的读响应接口
fromIMeta 的结构如下(编译后):
接口名 | 解释 |
---|---|
metas | MetaArray 的本身,包含 tag 量。tag,即 cache 的标签。 |
codes | ptag 的 ecc 校验码。 |
entryValid | 指示 meta 是否有效。 |
flush:双端刷新信号
来自 MainPipe 的刷新信号。可以只刷新指定的某一个端口,也可以都刷新。
接口名 | 解释 |
---|---|
virIdx | 需要刷新的虚拟地址索引。 |
waymask | 需要刷新的路。 |
flushAll:刷新所有 MetaArray
来自软件刷新信号 fencei。
DataArray 模块接口
write:DataArray 的写请求接口
来自 MissUnit 或者 CtrlUnit 的写请求。
接口名 | 解释 |
---|---|
virIdx | 需要写入的虚拟地址索引。 |
data | 需要写入的数据块。 |
waymask | 需要写入的路。 |
poison | 指示是否为毒化位。 |
read:DataArray 的读请求接口
来自 MainPipe 的读请求。
接口名 | 解释 |
---|---|
vSetIdx | 虚拟地址的缓存组索引。 |
waymask | 标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。通过 MissUnit 写给 prefetch,prefetch 写入 waylookup,mainpipe 从 waylookup 中读出。 |
blkOffset | 指令在块中的偏移。 |
readResp:DataArray 的读响应接口
接口名 | 解释 |
---|---|
datas | DataArray 本身的数据。 |
codes | data 的 ecc 校验码。 |
CtrlUnit 模块接口
auto_in: Tilelink 相关接口
CtrlUnit 和 Tilelink 交互,分为 a 通道和 d 通道。
a 通道:
接口名 | 解释 |
---|---|
opcode | 标识携带消息类型。 |
size | 传输的数据大小对数,表示操作的字节数为$2^n$。 |
source | 标识发起此请求的源,主设备源标识符。 |
address | 传输的目标字节地址。 |
mask | 要读的字节通道。 |
data | 忽略。 |
d 通道:
接口名 | 解释 |
---|---|
opcode | 标识携带消息类型。 |
size | 传输的数据大小对数,表示操作的字节数为$2^n$。 |
source | 标识发起此请求的源,主设备源标识符。 |
data | 数据载荷。 |
ecc_enable: ecc 控制信号
指示 ecc 是否开启。
injecting: ecc 注入信号
指示 eccctrl 的 istatus 域是否处于 working 状态,即收到注入请求,注入中
metaRead: 对 MetaArray 的读请求
在对应读状态机 is_readMetaReq 中,对 MetaArray 发起读请求。
接口名 | 解释 |
---|---|
vSetIdx | 要读取的拟地址的缓存组索引。 |
metaReadResp: 对 MetaArray 的读响应
在状态机 is_readMetaResp 中,接收 MetaArray 的读响应。
接口名 | 解释 |
---|---|
metas | MetaArray 的本身,包含 tag 量。tag,即 cache 的标签。 |
entryValid | 指示 meta 是否有效。 |
metaWrite: 对 MetaArray 的写
在状态机 is_writeMeta 中,对 MetaArray 发起写。
接口名 | 解释 |
---|---|
virIdx | 需要写入的虚拟地址索引。 |
phyTag | 需要写入的物理地址标签。 |
waymask | 指示写入哪一路。 |
bankIdx | 指示写入哪一个存储体索引。 |
dataWrite: 对 DataArray 的写请求
在状态机 is_writeData 中,对 DataArray 发起写请求。
接口名 | 解释 |
---|---|
virIdx | 需要写入的虚拟地址索引。 |
waymask | 需要写入的路。 |
ICache 顶层模块接口
在 scala 代码中,顶层模块除了包含对外的接口,实际上还包括了 MetaArray、DataArray 和 Replacer。在编译成 verilog 后,这三个模块会被编译成三个独立的模块,然后再通过顶层模块的接口连接起来。
不需要关注的接口
hartId
硬件线程 ID,difftest 使用,不需要关注。
perfInfo
性能信息,不需要关注。
auto_ctrlUnitOpt_in:CtrlUnit 和 Tilelink 交互的接口
CtrlUnit 和 Tilelink 交互,分为 a 通道和 d 通道。
a 通道:
接口名 | 解释 |
---|---|
opcode | 标识携带消息类型。 |
size | 传输的数据大小对数,表示操作的字节数为$2^n$。 |
source | 标识发起此请求的源,主设备源标识符。 |
address | 传输的目标字节地址。 |
mask | 要读的字节通道。 |
data | 忽略。 |
d 通道:
接口名 | 解释 |
---|---|
opcode | 标识携带消息类型。 |
size | 传输的数据大小对数,表示操作的字节数为$2^n$。 |
source | 标识发起此请求的源,主设备源标识符。 |
data | 数据载荷。 |
auto_client_out: MissUnit 和 Tilelink 交互的接口
MissUnit 和 Tilelink 交互,分为 a 通道和 d 通道。 a 通道:
接口名 | 解释 |
---|---|
source | 标识发起此请求的源。 |
address | 要访问的内存的起始物理地址。 |
d 通道:
接口名 | 解释 |
---|---|
opcode | 标识响应消息类型的关键字段,它指示了响应的性质和意图。针对 acquire 请求的响应是 GrantData (5,授予) |
source | 请求的源标识。 |
data | 返回的数据块。 |
corrupt | 返回的数据块是否损坏。 |
fetch: 与 FTQ 交互和 IFU 交互接口
包含 req 和 resp 两个子结构。
req: FTQ 取指请求
在 s0 流水级,接收 FTQ 的取指请求。 包含 pcMemRead,readValid 和 backendException 三个子结构。
其中 pcMemRead 的结构如下:
接口名 | 解释 |
---|---|
startAddr | 预测块起始地址。 |
nextlineStart | 预测块下一个缓存行的起始地址。 |
readValid:读取请求的有效性。
backendException:是否有来自后端的 Exception。
resp: IFU 取指响应
在 s2 流水级,向 IFU 发送取指响应。
接口名 | 解释 |
---|---|
doubleLine | 指示预测块是否跨缓存行。 |
vaddr | 指令块起始虚拟地址、下一个缓存行的虚拟地址。 |
data | 要传送的缓存行。 |
paddr | 指令块的起始物理地址 |
exception | 向 IFU 报告每个缓存行上的异常情况,方便 ICache 生成每个指令的异常向量。 |
pmp_mmio | 指示当前指令块是否在 MMIO 空间。 |
itlb_pbmt | ITLB 基于客户页的内存类型,对 MMIO 状态有用。 |
backendException | 后端是否存在异常。 |
gpaddr | 客户页地址。 |
isForVSnonLeafPTE | 是否为非叶的 PTE,来自 itlb。 |
ftqPrefetch:FTQ 预取相关信息
包含三个子结构: req: 来自 FTQ 的预取请求 flushFromBpu:来自 BPU 的刷新信息 bakckendException:来自后端的异常信息
req: 来自 FTQ 的预取请求
由于 BPU 基本无阻塞,它经常能走到 IFU 的前面,于是 BPU 提供的这些还没发到 IFU 的取指请求就可以用作指令预取,FTQ 中实现了这部分逻辑,直接给 ICache 发送预取请求。 预取请求来自 FTQ,在 MainPipe 的 S0 流水级传入。
接口名 | 解释 |
---|---|
ready | |
valid | 指示软件预取或者硬件预取是否有效。 |
startAddr | 预测块起始地址。 |
nextlineStart | 预测块下一个缓存行的起始地址。 |
ftqIdx | 指示当前预测块在 FTQ 中的位置,包含 flag 和 value 两个量。 |
flushFromBpu:来自 BPU 的刷新信息
由 FTQ 传递而来的 BPU 刷新信息,在 MainPipe 的 S0 流水级传入。 这是预测错误引起的,包括 s2 和 s3 两个同构成员,指示是否在 BPU 的 s2 和 s3 流水级发现了问题。 s2 的详细结构如下:
接口名 | 解释 |
---|---|
valid | 指示 s2 是否有效。 |
ftqIdx | 指示 s2 流水级请求冲刷的预测块在 FTQ 中的位置,包含 flag 和 value 两个量。 |
backendException: 后端异常信息
ICache 向 IFU 报告后端存在的异常类型
softPrefetch: 来自 Memblock 的软件预取信息
接口名 | 解释 |
---|---|
vaddr | 软件预取的虚拟地址。 |
stop: IFU 发送到 ICache 的停止信号
IFU 在 F3 流水级之前出现了问题,通知 ICache 停下。
ToIFU: 发送给 I FU 的就绪信号
由 MainPipe 的 s0 流水级 s0_can_go 生成。该信号用于提醒 IFU,Icache 的流水可用,可以发送换存行了。
pmp: MainPipe 和 PrefetchPipe 的 pmp 信息
0,1 通道为 MainPipe 的 pmp 信息,2,3 通道为 PrefetchPipe 的 pmp 信息。 pmp 包含 req 和 resp 两个子结构。
req 的结构如下(编译后):
接口名 | 解释 |
---|---|
addr | 需要检查的地址 |
resp 的结构如下(编译后):
接口名 | 解释 |
---|---|
instr | 指示物理地址是否有权限访问,没有则会引起 pmp 的 af 异常。 |
mmio | 地址在 mmio 空间。 |
itlb:PrefetchPipe 的 itlb 信息
在 PrefetchPipe 的 s0 流水级,发送 itlb_req;在 PrefetchPipe 的 s1 流水级,如果 itlb 命中则直接接收 itlb_resp,否则重发 itlb_req。
itlb 包含 req 和 resp 两个子结构。
req 的结构如下:
接口名 | 解释 |
---|---|
valid | 指示 req 请求是否有效。 |
Tlbreq | 有多个子结构,这里我们只用上了 vaddr,即 req 请求的虚拟地址 |
resp 的结构如下:
接口名 | 解释 |
---|---|
paddr | 指令物理地址。 |
gpaddr | 客户页地址。 |
pbmt | 基于页面的内存类型。 |
miss | 指示 itlb 是否未命中。 |
isForVSnonLeafPTE | 指示是否为非叶 PTE。 |
excp | ITLB 可能产生的异常,包括:访问异常指令 af_instr、客户页错误指令 gpf_instr、页错误指令 pf_instr。见异常传递/特殊情况处理 |
itlbFlushPipe: itlb 刷新信号
在 itlb 中,如果出现 gpf 的取指请求处于推测路径上,且发现出现错误的推测,则会通过 flushPipe 信号进行刷新(包括后端 redirect、或前端多级分支预测器出现后级预测器的预测结果更新前级预测器的预测结果等)。 当 iprefetchpipe 的 s1 被刷新时,itlb 也应该被刷新,该信号会在 iprefetchpipe 的 s1 流水被置位。
error:向 BEU 报告指令缓存中检测到的错误
将 MainPipe 中收集到的 errors 多个错误信息,使用优先级选择器选择索引最小且有效的错误信息,然后通过 error 信号发送给 BEU。接口结构和 MainPipe 中相同,区别在于 MainPipe 中有两个端口,所以有两个 errors,而这里要发送的只有一个。
编译后:
接口名 | 解释 |
---|---|
valid | 指示 errors 是否有效。 |
bits | 有两个量。paddr 表示错误的物理地址,report_to_beu 表示是否向 beu 报告错误 |
csr_pf_enable
控制 s1_real_fire,软件控制预取开关
fencei: 软件刷新信号
flush: 全局刷新信号
在 FTQ 中,有后端重新定向或者 IFU 重定向时,会将其 icacheFlush 信号拉高,触发 icache 的刷新。
测试点汇总
再次声明,本测试点仅供参考,如果有其他测试点需要补充可以告知我们。
建议覆盖点采用功能名称
_测试点名称
命名。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1.1 | ICACHE_PREFETCH_HARDPREFETCH | RECEIVE | 当预取请求有效且 IPrefetchPipe 处于空闲状态时,预取请求应该被接收。 s0_fire 信号在没有 s0 的刷新信号( s0_flush 为低)时,应该被置为高。 |
1.1.2 | ICACHE_PREFETCH_HARDPREFETCH | INVALID_PREFETCH | 当预取请求无效时,预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.1.3 | ICACHE_PREFETCH_HARDPREFETCH | PREFETCHPIPE_BUSY | 当 IPrefetchPipe 非空闲时,预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.1.4 | ICACHE_PREFETCH_HARDPREFETCH | INVALID_PREFETCH_AND_PREFETCHPIPE_BUSY | 当预取请求无效且 IPrefetchPipe 非空闲时,预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.1.5 | ICACHE_PREFETCH_HARDPREFETCH | SINGLE_CACHELINE | 当预取请求有效且为单 cacheline 时,预取请求应该被接收。 s0_fire 为高,s0_doubleline 应该被置低(false)。 |
1.1.6 | ICACHE_PREFETCH_HARDPREFETCH | DOUBLE_CACHELINE | 当预取请求有效且为双 cacheline 时,预取请求应该被接收。 s0_fire 为高,s0_doubleline 应该被置高(true)。 |
1.2.1 | ICACHE_PREFETCH_SOFTPREFETCH | RECEIVE | 当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。 当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。 |
1.2.2 | ICACHE_PREFETCH_SOFTPREFETCH | INVALID_PREFETCH | 当预取请求无效时,软件预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.2.3 | ICACHE_PREFETCH_SOFTPREFETCH | PREFETCHPIPE_BUSY | 当 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.2.4 | ICACHE_PREFETCH_SOFTPREFETCH | INVALID_PREFETCH_AND_PREFETCHPIPE_BUSY | 当预取请求无效且 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。 s0_fire 信号应该被置为低。 |
1.2.5 | ICACHE_PREFETCH_SOFTPREFETCH | SINGLE_CACHELINE | 当软件预取请求有效且为单 cacheline 时,软件预取请求应该被接收。 s0_fire 为高,s0_doubleline 应该被置低(false)。 |
1.2.6 | ICACHE_PREFETCH_SOFTPREFETCH | DOUBLE_CACHELINE | 当软件预取请求有效且为双 cacheline 时,软件预取请求应该被接收。 s0_fire 为高,s0_doubleline 应该被置高(true)。 |
2.1.1 | ICACHE_PREFETCH_ITLB_ADDR | RETURN_PADDR | ITLB 在一个周期内成功返回物理地址 paddr,s1_valid 为高。 确认 s1 阶段正确接收到 paddr。 |
2.1.2 | ICACHE_PREFETCH_ITLB_ADDR | TLB_MISS | fromITLB(PortNumber).bits.miss 为高,表示对应通道的 ITLB 发生了 TLB 缺失,需要重发。 重发完成后,后续步骤继续进行,fromITLB(PortNumber).bits.miss 为低。 |
2.2.1 | ICACHE_PREFETCH_ITLB_EXCEPTION | PF | s1_itlb_exception 返回的页错误。 iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示页错误 pf。 |
2.2.2 | ICACHE_PREFETCH_ITLB_EXCEPTION | PGF | s1_itlb_exception 返回的虚拟机页错误。 iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示虚拟机页错误 pgf。 |
2.2.3 | ICACHE_PREFETCH_ITLB_EXCEPTION | AF | s1_itlb_exception 返回的访问错误。 iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示访问错误 af。 |
2.3.1 | ICACHE_PREFETCH_ITLB_GPADDR | PGF | 发生 pgf 后,需要返回对应的 gpaddr。 只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。 |
2.3.2 | ICACHE_PREFETCH_ITLB_GPADDR | VS_NONLEAF_PTE | 发生 gpf 后,如果是访问二级虚拟机的非叶子页表项时,需要返回对应的 gpaddr。 只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。 |
2.4 | ICACHE_PREFETCH_ITLB_PBMT | PBMT | TLB 有效时,返回 pbmt 信息。 |
3.1 | ICACHE_PREFETCH_IMETA | PTAG_AND_VALID | 从物理地址中提取物理标签(ptag),将其与缓存元数据中的标签进行比较,检查所有缓存路(Way)。检查有效位,确保只考虑有效的缓存行。 |
3.1 | ICACHE_PREFETCH_IMETA | MISS | 当标签不匹配或者标签匹配,但是有效位为假时,表示缓存未命中。 s1_meta_ptags(PortNumber)(nWays) 不等于 ptags(PortNumber) 或者它们相等,但是对应的 s1_meta_valids 为低时,总之返回的 waymasks 为全 0。 |
3.2 | ICACHE_PREFETCH_IMETA | SINGLE_HIT | 当标签匹配,且有效位为真时,表示缓存命中。 waymasks 对应的位为 1。 |
4.1 | ICACHE_PREFETCH_PMP | NONE_EXCEPTION | itlb 返回的物理地址在 PMP 允许的范围内。 s1_pmp_exception(i) 为 none。 |
4.2 | ICACHE_PREFETCH_PMP | AF_EXCEPTION | s1_req_paddr(i) 对应的地址在 PMP 禁止的范围内。 s1_pmp_exception(i) 为 af。 |
4.3 | ICACHE_PREFETCH_PMP | MMIO | itlb 返回的物理地址在 MMIO 区域。 s1_pmp_mmio 为高。 |
5.1 | ICACHE_PREFETCH_EXCEPTION_MERGE | ONLY_ITLB_EXCEPTION | s1_itlb_exception(i) 为非零,s1_pmp_exception(i) 为零。 s1_exception_out(i) 正确包含 ITLB 异常。 |
5.2 | ICACHE_PREFETCH_EXCEPTION_MERGE | ONLY_PMP_EXCEPTION | s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为非零。 s1_exception_out(i) 正确包含 PMP 异常。 |
5.3 | ICACHE_PREFETCH_EXCEPTION_MERGE | ONLY_BACKEND_EXCEPTION | s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为零。 s1_exception_out(i) 正确包含 后端 异常。 |
5.4 | ICACHE_PREFETCH_EXCEPTION_MERGE | ITLB_AND_PMP_EXCEPTION | s1_itlb_exception(i) 和 s1_pmp_exception(i) 都为非零。 s1_exception_out(i) 包含 ITLB 异常(优先级更高)。 |
5.5 | ICACHE_PREFETCH_EXCEPTION_MERGE | ITLB_AND_BACKEND_EXCEPTION | s1_itlb_exception(i) 和 s1_backendException(i) 都为非零。 s1_exception_out(i) 包含 后端 异常(优先级更高)。 |
5.6 | ICACHE_PREFETCH_EXCEPTION_MERGE | PMP_AND_BACKEND_EXCEPTION | s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。 s1_exception_out(i) 包含 后端 异常(优先级更高)。 |
5.7 | ICACHE_PREFETCH_EXCEPTION_MERGE | ALL_EXCEPTION | s1_itlb_exception(i)、s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。 s1_exception_out(i) 包含 后端 异常(优先级更高)。 |
5.8 | ICACHE_PREFETCH_EXCEPTION_MERGE | NONE_EXCEPTION | s1_itlb_exception(i)、s1_pmp_exception(i)、s1_backendException(i) 都为零。 s1_exception_out(i) 指示无异常。 |
6.1 | ICACHE_PREFETCH_WAYLOOKUP | NORMAL | toWayLookup.valid 为高,toWayLookup.ready 为高,s1_isSoftPrefetch 为假。 请求成功发送,包含正确的地址、标签、waymask 和异常信息。 |
6.2 | ICACHE_PREFETCH_WAYLOOKUP | WAYLOOKUP_REJECT | toWayLookup.valid 为高,toWayLookup.ready 为假。 状态机等待 WayLookup 准备好,不会错误地推进。 |
6.3 | ICACHE_PREFETCH_WAYLOOKUP | SOFTPREFETCH_NOT_GO_WAYLOOKUP | s1_isSoftPrefetch 为真。 toWayLookup.valid 为假,不会发送预取请求到 WayLookup。 |
7.1.1 | ICACHE_PREFETCH_FSM_IDLE | NORMAL_IMPEL_KEEP_IDLE | s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为真,s2_ready 为真。 状态机保持在 m_idle 状态,s1 阶段顺利推进。 |
7.1.2 | ICACHE_PREFETCH_FSM_IDLE | ITLB_UNFINISH_RESEND | s1_valid 为高,itlb_finish 为假。 状态机进入 m_itlbResend 状态,等待 ITLB 完成。 |
7.1.3 | ICACHE_PREFETCH_FSM_IDLE | ITLB_FINISH_WAYLOOKUP_MISS | s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为假。 状态机进入 m_enqWay 状态,等待 WayLookup 入队。 |
7.2.1 | ICACHE_PREFETCH_FSM_ITLBRESEND | WAIT_WAYLOOKUP_ENQ | itlb_finish 为假,toMeta.ready 为真。 状态机进入 m_enqWay 状态,等待 WayLookup 入队。 |
7.2.2 | ICACHE_PREFETCH_FSM_ITLBRESEND | WAIT_META_READ_REQ | itlb_finish 为假,toMeta.ready 为假。 状态机进入 m_metaResend 状态,MetaArray 读请求 |
7.3 | ICACHE_PREFETCH_FSM_METARESEND | WAIT_WAYLOOKUP_ENQ | toMeta.ready 为真。 状态机进入 m_enqWay 状态,等待 WayLookup 入队。 |
7.4.1 | ICACHE_PREFETCH_FSM_ENQWAY | ENTER_IDLE | toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为假。 状态机进入空闲状态 m_idle。 |
7.4.2 | ICACHE_PREFETCH_FSM_ENQWAY | WAIT_ENTERS2 | toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为真。 状态机进入 m_enterS2 状态,等待 s2 阶段准备好。 |
7.5 | ICACHE_PREFETCH_FSM_ENTERS2 | ENTER_IDLE | s2_ready 为真。 状态机进入空闲状态 m_idle。 |
8.1 | ICACHE_PREFETCH_MISSUNIT_MONITER | MATCH_AND_VALID | s2_req_vSetIdx 和 s2_req_ptags 与 fromMSHR 中的数据匹配,且 fromMSHR.valid 为高,fromMSHR.bits.corrupt 为假。 s2_MSHR_match(PortNumber) 为真, s2_MSHR_hits(PortNumber) 应保持为真 |
8.2 | ICACHE_PREFETCH_MISSUNIT_MONITER | SRAM_HIT | s2_waymasks(PortNumber) 中有一位为高,表示在缓存中命中。 s2_SRAM_hits(PortNumber) 为真,s2_hits(PortNumber) 应为真。 |
8.3 | ICACHE_PREFETCH_MISSUNIT_MONITER | MISS | 请求未匹配 MSHR,且 s2_waymasks(PortNumber) 为空。 s2_MSHR_hits(PortNumber)、s2_SRAM_hits(PortNumber) 均为假, s2_hits(PortNumber) 为假。 |
9.1.1 | ICACHE_PREFETCH_MISSUNIT_REQ | SEND_TO_MISSUNIT | s2_hits(PortNumber) 为假(未命中缓存),s2_exception 无异常,s2_mmio 为假(不是 MMIO 或不可缓存的内存)。 s2_miss(PortNumber) 为真,表示需要发送请求到 missUnit。 |
9.1.2 | ICACHE_PREFETCH_MISSUNIT_REQ | NO_SEND_TO_MISSUNIT | s2_hits(i) 为真(已命中)或者 s2_exception 有异常 或者 s2_mmio 为真(MMIO 访问)。 s2_miss(i) 为假,不会发送请求到 missUnit。 |
9.1.3 | ICACHE_PREFETCH_MISSUNIT_REQ | DOUBLE_PREFETCH | s2_doubleline 为真,处理第二个请求。 如果第一个请求有异常或 MMIO,s2_miss(1) 应为假,后续请求被取消或处理。 |
9.2.1 | ICACHE_PREFETCH_MISSUNIT_UNIQUE | RESET_HAS_SEND | s1_real_fire 为高。 has_send(PortNumber) 应被复位为假,表示新的请求周期开始。 |
9.2.2 | ICACHE_PREFETCH_MISSUNIT_UNIQUE | UPDATE_HAS_SEND | toMSHRArbiter.io.in(PortNumber).fire 为高(请求已发送)。 has_send(PortNumber) 被设置为真,表示该端口已发送请求。 |
9.2.3 | ICACHE_PREFETCH_MISSUNIT_UNIQUE | UNIQUE_REQ | 同一请求周期内,has_send(PortNumber) 为真,s2_miss(PortNumber) 为真。 toMSHRArbiter.io.in(PortNumber).valid 为假,不会再次发送请求。 |
9.2.4 | ICACHE_PREFETCH_MISSUNIT_UNIQUE | RIGHTLY_SEND | s2_valid 为高,s2_miss(i) 为真,has_send(i) 为假。 toMSHRArbiter.io.in(i).valid 为高,请求被成功发送。 |
9.2.5 | ICACHE_PREFETCH_MISSUNIT_UNIQUE | ARBITRATE_REQ | 多个端口同时需要发送请求。 仲裁器按照优先级或设计要求选择请求发送到 missUnit,未被选中的请求在下个周期继续尝试发送。 |
10.1 | ICACHE_PREFETCH_FLUSH | GLOBAL_FLUSH | io.flush 为高。 s0_flush、s1_flush、s2_flush 分别为高,所有阶段的请求被正确清除。 |
10.2 | ICACHE_PREFETCH_FLUSH | FLUSH_FROM_BPU | io.flushFromBpu.shouldFlushByStageX 为真(X 为 2 或 3),且请求不是软件预取。 对应阶段的 from_bpu_sX_flush 为高,sX_flush 为高,阶段请求被刷新。 |
10.3 | ICACHE_PREFETCH_FLUSH | RESET_FSM | s1_flush 为高。 状态机 state 被重置为 m_idle 状态。 |
10.4 | ICACHE_PREFETCH_FLUSH | ITLB_FLUSH_PIPE | s1_flush 为高。 io.itlbFlushPipe 为高,ITLB 被同步刷新。 |
11.1 | ICACHE_MAINPIPE_DATA_ARRAY | WAY_HIT_AND_ITLB_SUCCESS | 当 WayLookup 中的信息表明路命中时,ITLB 查询成功,并且 DataArray 当前没有写时,MainPipe 会向 DataArray 发送读取请求,以获取数据。 s0_hits 为高(一路命中),s0_itlb_exception 信号为零(ITLB 查询成功),toData.last.ready 为高(DataArray 没有正在进行的写操作)。 toData.valid 信号为高,表示 MainPipe 向 DataArray 发出了读取请求。 |
11.2 | ICACHE_MAINPIPE_DATA_ARRAY | WAY_MISS | 当 WayLookup 中的信息表明路未命中时,MainPipe 不会向 DataArray 发送读取请求。 s0_hits 为低表示缓存未命中 toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 |
11.3 | ICACHE_MAINPIPE_DATA_ARRAY | ITLB_FAIL | 当 ITLB 查询失败时,MainPipe 不会向 DataArray 发送读取请求。 s0_itlb_exception 信号不为零(ITLB 查询失败)。 toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 |
11.4 | ICACHE_MAINPIPE_DATA_ARRAY | DATA_ARRAY_WRITING | 当 DataArray 正在进行写操作时,MainPipe 不会向 DataArray 发送读取请求。 toData.last.ready 信号为低,表示 DataArray 正在进行写操作。 toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 |
12.1 | ICACHE_MAINPIPE_META_ECC | NO_ECC_ERROR | 当 waymask 全为 0(没有命中),则 hit_num 为 0 或 waymask 有一位为 1(一路命中),hit_num 为 1 且 ECC 对比通过(encodeMetaECC(meta) == code) s1_meta_corrupt 为假。 |
12.2 | ICACHE_MAINPIPE_META_ECC | SINGLE_ECC_ERROR | 当 waymask 有一位为 1(一路命中),ECC 对比失败(encodeMetaECC(meta) != code) s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 |
12.3 | ICACHE_MAINPIPE_META_ECC | MULTI_WAY_HIT | > hit multi-way, must be an ECC failure 当 waymask 有两位及以上为 1(多路命中),视为 ECC 错误。 s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 |
12.4 | ICACHE_MAINPIPE_META_ECC | ECC_CLOSE | 当奇偶校验关闭时(ecc_enable 为低),强制清除 s1_meta_corrupt 信号置位。 不管是否发生 ECC 错误,s1_meta_corrupt 都为假。 |
13.1 | ICACHE_MAINPIPE_PMP | NONE_EXCEPTION | s1_pmp_exception 为全零,表示没有 PMP 异常。 |
13.2 | ICACHE_MAINPIPE_PMP | CHANNEL_0_EXCEPTION | s1_pmp_exception(0) 为真,表示通道 0 有 PMP 异常。 |
13.3 | ICACHE_MAINPIPE_PMP | CHANNEL_1_EXCEPTION | s1_pmp_exception(1) 为真,表示通道 1 有 PMP 异常。 |
13.4 | ICACHE_MAINPIPE_PMP | CHANNEL_0_AND_1_EXCEPTION | s1_pmp_exception(0) 和 s1_pmp_exception(1) 都为真,表示通道 0 和通道 1 都有 PMP 异常。 |
13.5 | ICACHE_MAINPIPE_PMP | NONE_MMIO | s1_pmp_mmio(0) 和 s1_pmp_mergemmio(1) 都为假,表示没有映射到 MMIO 区域。 |
13.6 | ICACHE_MAINPIPE_PMP | CHANNEL_0_MMIO | s1_pmp_mmio(0) 为真,表示映射到了 MMIO 区域。 |
13.7 | ICACHE_MAINPIPE_PMP | CHANNEL_1_MMIO | s1_pmp_mmio(1) 为真,表示映射到了 MMIO 区域。 |
13.8 | ICACHE_MAINPIPE_PMP | CHANNEL_0_AND_1_MMIO | s1_pmp_mmio(0) 和 s1_pmp_mmio(1) 都为真,表示通道 0 和通道 1 都映射到了 MMIO 区域。 |
14.1 | ICACHE_MAINPIPE_EXCEPTION_MERGE | NONE_EXCEPTION | s1_exception_out 为全零,表示没有异常。 |
14.2 | ICACHE_MAINPIPE_EXCEPTION_MERGE | ITLB_EXCEPTION | s1_exception_out 和 s1_itlb_exception 一致 |
14.3 | ICACHE_MAINPIPE_EXCEPTION_MERGE | PMP_EXCEPTION | s1_exception_out 和 s1_pmp_exception 一致 |
14.4 | ICACHE_MAINPIPE_EXCEPTION_MERGE | ITLB_AND_PMP_EXCEPTION | > itlb has the highest priority, pmp next s1_exception_out 和 s1_itlb_exception 一致 |
15.1 | ICACHE_MAINPIPE_MSHR_MATCH | MSHR_HIT | MSHR 中已有正确数据时,S1 阶段能直接拿到 s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 s1_bankMSHRHit(i),s1_data_is_from_MSHR(i) 为 true |
15.2 | ICACHE_MAINPIPE_MSHR_MATCH | MSHR_MISS | MSHR 中存放的地址与当前请求不同,那么应该读取 SRAM 的数据 s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false |
15.3 | ICACHE_MAINPIPE_MSHR_MATCH | MSHR_DATA_CORRUPT | fromMSHR.bits.corrupt = true,那么 MSHR 将不匹配,应该读取 SRAM 的数据 s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false |
16.1 | ICACHE_MAINPIPE_DATA_ECC | NO_ECC_ERROR | s2_bank 全部没有损坏,bank 也选对了对应的端口和 bank,数据不来自 MSHR s2_data_corrupt(i) 为 false,没有 ECC 错误。 |
16.2 | ICACHE_MAINPIPE_DATA_ECC | SINGLE_Bank_ECC_ERROR | s2_bank_corrupt(bank) 有一个为 true ,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 |
16.3 | ICACHE_MAINPIPE_DATA_ECC | MULTIPLE_Bank_ECC_ERROR | s2_bank_corrupt(bank) 有两个或以上为 true,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 |
16.4 | ICACHE_MAINPIPE_DATA_ECC | ECC_CLOSE | 当奇偶校验关闭时(ecc_enable 为低),强制清除 s2_data_corrupt 信号置位。 不管是否发生 ECC 错误,s2_data_corrupt 都为假。 |
17.1 | ICACHE_MAINPIPE_META_FLUSH | FLUSH_META_ARRAY | > if is meta corrupt, clear all way (since waymask may be unreliable) 当 s1_meta_corrupt 为真时,MetaArray 的所有路都会被冲刷。 toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。 |
17.2 | ICACHE_MAINPIPE_META_FLUSH | FLUSH_DATA_ARRAY | > if is data corrupt, only clear the way that has error 当 s2_data_corrupt 为真时,只有对应路会被冲刷。 toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的对应路置位。 |
17.3 | ICACHE_MAINPIPE_META_FLUSH | FLUSH_META_ARRAY_AND_DATA_ARRAY | 处理 Meta ECC 的优先级更高, 将 MetaArray 的所有路冲刷。 toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。 |
18.1 | ICACHE_MAINPIPE_MSHR_MONITER | MSHR_HIT | MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效 s2_MSHR_match,s2_MSHR_hits 为高,s2_bankMSHRHit 对应 bank 为高 s1_fire 无效时,s2_datas 更新为 MSHR 的数据,将 s2_data_is_from_MSHR 对应位置位,s2_hits 置位,清除 s2_data_corrupt,l2 的 corrupt 更新为 fromMSHR.bits.corrupt s1_fire 有效时,s2_datas 为 s1_datas 的数据,将 s2_data_is_from_MSHR 对应位置为 s1 的 s1_data_is_from_MSHR,s2_hits 置为 s1_hits,清除 s2_data_corrupt,l2 的 corrupt 为 false |
18.2 | ICACHE_MAINPIPE_MSHR_MONITER | MSHR_MISS | MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效,至少有一个未达成 s2_MSHR_hits(i) = false,S2 不会更新 s2_datas,继续保持原先 SRAM 数据或进入 Miss 流程。 |
19.1 | ICACHE_MAINPIPE_MISS_REQ | NONE_MISS | 当 s2_hits(i) 为高(s2 已经命中),s2 的 meta 和 data 都没有错误,s2 异常,处于 mmio 区域 以上条件至少满足一个时,s2_should_fetch(i) 为低,表示不发送 Miss 请求。 |
19.2 | ICACHE_MAINPIPE_MISS_REQ | SINGLE_MISS | 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) |
19.3 | ICACHE_MAINPIPE_MISS_REQ | DOUBLE_MISS | 同上,但是两个端口都满足 s2_should_fetch 为高的条件。 toMSHRArbiter.io.in(0).valid、toMSHRArbiter.io.in(1).valid 均为 true,Arbiter 根据仲裁顺序依次发出请求。 |
19.4 | ICACHE_MAINPIPE_MISS_REQ | BLOCK_REPETITION | 当 s1_fire 为高,表示可以进入 s2 阶段,那么 s2 还没有发送 s2_has_send(i) := false.B 如果已经有请求发送了,那么对应的 toMSHRArbiter.io.in(i).fire 为高,表示对应的请求可以发送,s2_has_send(i) := true。 此时再次发送,toMSHRArbiter.io.in(i).valid 为低,表示发送失败。 |
19.5 | ICACHE_MAINPIPE_MISS_REQ | ONLY_ITLB_OR_PMP_EXCEPTION | S1 阶段已记录了 ITLB 或 PMP 异常,L2 corrupt = false。 2_exception_out 仅保留 ITLB/PMP 异常标记,无新增 AF 异常。 |
19.6 | ICACHE_MAINPIPE_MISS_REQ | ONLY_L2_EXCEPTION | S2 阶段 s2_l2_corrupt(i) = true,且无 ITLB/PMP 异常。 s2_exception_out(i) 表示 L2 访问错误(AF)。 |
19.7 | ICACHE_MAINPIPE_MISS_REQ | ITLB_AND_L2_EXCEPTION | 同时触发 ITLB 异常和 L2 corrupt。 s2_exception_out 优先保留 ITLB 异常类型,不被 L2 覆盖。 |
19.8 | ICACHE_MAINPIPE_MISS_REQ | S2_FETCH_FINISH | s2_should_fetch 的所有端口都为低,表示需要取指,那么取指完成 s2_fetch_finish 为高 |
20.1 | ICACHE_MAINPIPE_IFU | HIT_AND_RETURN | 不存在任何异常或 Miss,s2 命中,s2 阶段取指完成,外部的 respStall 停止信号也为低 。 toIFU.valid = true,toIFU.bits.data 为正确的 Cacheline 数据,toIFU.bits.exception、pmp_mmio、itlb_pbmt = none。 |
20.2 | ICACHE_MAINPIPE_IFU | ABNORMAL_RETURN | 设置 ITLB、PMP、或 L2 corrupt 异常。 toIFU.bits.exception(i) = 对应异常类型,pmp_mmio、itlb_pbmt 根据是否有对应的异常设置为 true。 |
20.3 | ICACHE_MAINPIPE_IFU | CROSSLINE_FETCH | s2_doubleline = true,同时检查第一路、第二路返回情况。 toIFU.bits.doubleline = true。 若第二路正常,toIFU.bits.exception(1) = none;若第二路异常,则 exception(1) 标记相应类型。 pmp_mmio、itlb_pbmt 类似。 |
20.4 | ICACHE_MAINPIPE_IFU | RESPSTALL | 外部 io.respStall = true,导致 S2 阶段无法发射到 IFU。 s2_fire = false,toIFU.valid 也不拉高,S2 保持原状态等待下一拍(或直到 respStall 解除)。 |
21.1 | ICACHE_MAINPIPE_L2_CORRUPT | SINGLE_L2_CORRUPT | s2 阶段准备完成可以发射(s2_fire 为高),s2_MSHR_hits(0)和 fromMSHR.bits.corrupt 为高 s2_l2_corrupt(0) = true,io.errors(0).valid = true,io.errors(0).bits.source.l2 = true。 |
21.2 | ICACHE_MAINPIPE_L2_CORRUPT | DOUBLE_L2_CORRUPT | 端口 0 和端口 1 都从 L2 corrupt 数据中获取。 s2_l2_corrupt 均为 true,发射后分别报告到 io.errors(0) 和 io.errors(1)。 |
22.1 | ICACHE_MAINPIPE_FLUSH | GLOBAL_FLUSH | io.flush 被激活时,流水线的各个阶段(S0, S1 和 S2)都能正确响应并执行刷新操作。 io.flush = true。 s0_flush, s1_flush, s2_flush = true。 |
22.2 | ICACHE_MAINPIPE_FLUSH | S0_FLUSH | s0_flush = true。 s0_fire = false。 |
22.3 | ICACHE_MAINPIPE_FLUSH | S1_FLUSH | s1_flush = true。 s1_valid, s1_fire = false。 |
22.4 | ICACHE_MAINPIPE_FLUSH | S2_FLUSH | s2_flush = true。 s2_valid, toMSHRArbiter.io.in(i).valid , s2_fire = false |
23.1 | ICACHE_WAYLOOKUP_FLUSH | READ_POINTER | io.flush 为高时,重置读指针。 readPtr.value 为 0, readPtr.flag 为 false。 |
23.2 | ICACHE_WAYLOOKUP_FLUSH | WRITE_POINTER | io.flush 为高时,重置写指针。 writePtr.value 为 0, writePtr.flag 为 false。 |
23.3 | ICACHE_WAYLOOKUP_FLUSH | GPF | io.flush 为高时,重置 GPF 信息。 gpf_entry.valid 为 0, gpf_entry.bits 为 0。 |
24.1 | ICACHE_WAYLOOKUP_UPDATE_POINTER | READ_POINTER | 当 io.read.fire 为高时,读指针加一。 readPtr.value 加一。 如果 readPtr.value 超过环形队列的大小,readPtr.flag 会翻转。 |
24.2 | ICACHE_WAYLOOKUP_UPDATE_POINTER | WRITE_POINTER | 当 io.write.fire 为高时,写指针加一。 writePtr.value 加一。 如果 writePtr.value 超过环形队列的大小,writePtr.flag 会翻转。 |
25.1 | ICACHE_WAYLOOKUP_UPDATE | HIT | MissUnit 返回的更新信息和 WayLookup 的信息相同时,更新 waymask 和 meta_codes。 vset_same 和 ptag_same 为真。 waymask 和 meta_codes 更新。 hits 对应位为高。 |
25.2 | ICACHE_WAYLOOKUP_UPDATE | MISS | vset_same 和 way_same 为真。 waymask 清零。 hit 对应位为高。 |
25.3 | ICACHE_WAYLOOKUP_UPDATE | NONE_UPDATE | 其他情况下不更新。 vset_same 为假或者 ptag_same 和 way_same 都为假。 hits 对应位为低。 |
26.1 | ICACHE_WAYLOOKUP_READ | BYPASS_READ | 队列为空,并且 io.write.valid 写有效时,可以直接读取,而不经过队列。 empty 和 io.write.valid 都为真。 io.read.bits = io.write.bits |
26.2 | ICACHE_WAYLOOKUP_READ | READ_INVALID | 队列为空(readPtr === writePtr)且写信号 io.write.valid 为低。 io.read.valid 为低,读信号无效。 |
26.3 | ICACHE_WAYLOOKUP_READ | NORMAL_READ | 未达成绕过条件(empty 和 io.write.valid 至少有一个为假)且 io.read.valid 为高。 从环形队列中读取信息。 io.read.bits.entry = entries(readPtr.value) |
26.4 | ICACHE_WAYLOOKUP_READ | GPF_HIT | io.read.valid 为高,可以读。 当 gpf_hits 为高时,从 GPF 队列中读取信息。 io.read.bits.gpf = gpf_entry.bits |
26.5 | ICACHE_WAYLOOKUP_READ | GPF_HIT_AND_READ | io.read.valid 为高,可以读。 > also clear gpf_entry.valid when it’s read 当 gpf 命中且被读取其时(io.read.fire 为高),gpf_entry.valid 会被置为 0。 |
26.6 | ICACHE_WAYLOOKUP_READ | GPF_MISS | io.read.valid 为高,可以读。 io.read.bits.gpf 清零。 |
27.1 | ICACHE_WAYLOOKUP_WRITE | GPF_STOP | > if there is a valid gpf to be read, we should stall write gpf 队列数据有效,并且没有被读取或者没有命中,就会产生 gpf 停止,此时写操作会被停止。 gpf_entry.valid && !(io.read.fire && gpf_hit) 为高时,写操作会被停止(io.write.ready 为低)。 |
27.2 | ICACHE_WAYLOOKUP_WRITE | WRITE_READY_INVALID | 当队列为满((readPtr.value === writePtr.value) && (readPtr.flag ^ writePtr.flag))或者 gpf 停止时,写操作会被停止。 (io.write.ready 为低) |
27.3 | ICACHE_WAYLOOKUP_WRITE | NORMAL_WRITE | 当 io.write.valid 为高时(没满且没有 gpf 停止),写操作会被执行。 正常握手完毕 io.write.fire 为高。 写信息会被写入环形队列。 entries(writePtr.value) = io.write.bits.entry。 |
27.4.1 | ICACHE_WAYLOOKUP_WRITE_WITH_ITLB_EXCEPTION | BYPASS | can_bypass 和 io.read.fire 都为高。 gpf_entry.valid 为 false。 gpf_entry.bits = io.write.bits.gpf gpfPtr = writePtr |
27.4.2 | ICACHE_WAYLOOKUP_WRITE_WITH_ITLB_EXCEPTION | NO_BYPASS | can_bypass 为低。 gpf_entry.valid 为 true。 gpf_entry.bits = io.write.bits.gpf gpfPtr = writePtr |
28.1 | ICACHE_WAYLOOKUP_ENQ | NORMAL_ENQ | 当队列未满,且空位不小于一时,可以正常入队,如果从零号位开始入队到最大容量,入队指针的 flag 不会翻转。 io.enq.fire 为高有效,regFiles(enq_ptr.value) = io.enq.bits,enq_ptr.value+1 入队指针移动,入队指针标记位不翻转。 重复以上操作至队满。 |
28.2 | ICACHE_WAYLOOKUP_ENQ | REVERSE_FLAG | 当队未满,但是空位却是靠近队尾时,入队一位后就到达了队头,入队指针的 flag 会翻转。 队列的容量为 10,入队指针指向 9,队未满。此时如果 io.enq.fire 为高,则 regFiles(9) = io.enq.bits,enq_ptr.value+1(循环队列,加完后 enq_ptr.value=0)入队指针移动,入队指针标记位翻转。 |
28.3 | ICACHE_WAYLOOKUP_ENQ | FULL | 当队满时,(enq_ptr.value === deq_ptr.value) && (enq_ptr.flag ^ deq_ptr.flag) 为高,io.enq.ready 为低,io.enq.fire 为低无效。 此时入队,入队指针的 value 和 flag 不变。 |
29.1 | ICACHE_WAYLOOKUP_DEQ | NORMAL_DEQ | 当队列非空时,可以正常出队,如果出队指针不经过最大容量位置,出队指针的 flag 不会翻转。 io.deq.fire 为高有效,io.deq.bits = regFiles(deq_ptr.value),deq_ptr.value+1 出队指针移动,出队指针标记位不翻转。 |
29.2 | ICACHE_WAYLOOKUP_DEQ | REVERSE_FLAG | 当队非空,但是出队指针是靠近队尾时,出队一位后就到达了队头,出队指针的 flag 会翻转。 队列的容量为 10,出队指针指向 9,队非空。此时如果 io.deq.fire 为高,则 io.deq.bits = regFiles(9),deq_ptr.value+1(循环队列,加完后 deq_ptr.value=0)出队指针移动,出队指针标记位翻转。 |
29.3 | ICACHE_WAYLOOKUP_DEQ | EMPTY | 当队空时,enq_ptr === deq_ptr 为高,io.deq.valid 为低,io.deq.fire 为低无效。 此时出队,出队指针的 value 和 flag 不变。 |
30.1 | ICACHE_WAYLOOKUP_FLUSH | FLUSH | 当刷新信号有效时,重置出队和入队的指针和标记位,清空队列。 当 flush 为高时,deq_ptr.value=0,enq_ptr.value=0,deq_ptr.flag=false,enq_ptr.flag=false,empty=true,full=false。 |
31.1 | ICACHE_MISSUNIT_HANDLE_FETCH | RECEIVE_NEW_REQ | 当新的 fetch miss 与 MSHR 中的已有请求不重复时(通过 io.fetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Fetch MSHR 中。 当有新的取指缺失请求到达时(io.fetch_req.valid 为高),且没有命中已有的 MSHR(fetchHit 为低),io.fetch_req.ready 应为高,表示可以接受请求。 io.fetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。 |
31.2 | ICACHE_MISSUNIT_HANDLE_FETCH | ALREADY_REQ | 当已有取指缺失请求到达时(io.fetch_req.valid 为高),且命中已有的 MSHR(fetchHit 为高),io.fetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。 fetchDemux.io.in.valid 应为低,fetchDemux.io.in.fire 为低,表示没有新的请求被分发到 MSHR。 |
31.3 | ICACHE_MISSUNIT_HANDLE_FETCH | MSHR_PRIORITY | Fetch 的请求会通过 fetchDemux 分配到多个 Fetch MSHR,fetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。 当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。 |
32.1 | ICACHE_MISSUNIT_HANDLE_PREFETCH | RECEIVE_NEW_REQ | 当新的 prefetch miss 与 MSHR 中的已有请求不重复时(通过 io.prefetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Prefetch MSHR 中。 当有新的预取缺失请求到达时(io.prefetch_req.valid 为高),且没有命中已有的 MSHR(prefetchHit 为低),io.prefetch_req.ready 应为高,表示可以接受请求。 io.prefetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。 |
32.2 | ICACHE_MISSUNIT_HANDLE_PREFETCH | ALREADY_REQ | 当已有预取缺失请求到达时(io.prefetch_req.valid 为高),且命中已有的 MSHR(prefetchHit 为高),io.prefetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。 prefetchDemux.io.in.valid 应为低,prefetchDemux.io.in.fire 为低,表示请求被接受但未分发到新的 MSHR。 |
32.3 | ICACHE_MISSUNIT_HANDLE_PREFETCH | MSHR_PRIORITY | Prefetch 的请求会通过 prefetchDemux 分配到多个 Prefetch MSHR,prefetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。 当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。 |
32.4 | ICACHE_MISSUNIT_HANDLE_PREFETCH | FIFO_PRIORITY | 从 prefetchDemux 离开后,请求的编号会进入 priorityFIFO,priorityFIFO 会根据进入队列的顺序排序,先进入队列的请求会先进入 prefetchArb。 prefetchDemux.io.in.fire 为高,并且 prefetchDemux.io.chosen 有数据时,将其编号写入 priorityFIFO。 在 priorityFIFO 中有多个编号时,出队的顺序和入队顺序一致。 检查 priorityFIFO.io.deq.bit 中的数据即可。 |
33.1 | ICACHE_MISSUNIT_MSHR | HIT | 当新的请求到来时,能够正确查找所有 MSHR,判断请求是否命中已有 MSHR。 当新的请求(取指或预取)到来时,系统遍历所有 MSHR,根据所有 MSHR 的查找信号 allMSHRs(i).io.lookUps(j).hit,检查请求是否已经存在于某个 MSHR 中。 如果命中,则对应的 fetchHit 或 prefetchHit 为高。 对于 prefetchHit 为高,还有一种情况:预取的物理块地址和组索引与取指的相等((io.prefetch_req.bits.blkPaddr === io.fetch_req.bits.blkPaddr) && (io.prefetch_req.bits.vSetIdx === io.fetch_req.bits.vSetIdx))并且有取指请求 io.fetch_req.valid 有效时,也算命中 |
33.2 | ICACHE_MISSUNIT_MSHR | UPDATE_AND RELEASE | 当请求完成后,也就是来自内存总线的响应完成(D 通道接收完所有节拍),MSHR 能够正确地释放(清除其有效位),以便接收新的请求。 TileLink D 通道返回的 source ID ,即 io.mem_grant.bits.source。 无效化信号 allMSHRs(i).io.invalid 为高,对应的 MSHR 的有效位 allMSHRs(i).valid 变为低 |
34.1 | ICACHE_MISSUNIT_ACQUIREARB | ARBITRATE | acquireArb 会选择一个 acquire 发送给 mem_acquire。 当有多个 MSHR 同时发出请求时,acquireArb 会根据优先级进行仲裁,选择优先级最高的 MSHR 发送请求。 取指请求总是在 0-3 号,预取请求直接在最后一号,所以取指请求优先级高于预取请求。 当取指 acquire 和预取 acquire 同时发出时,fetchMSHRs(i).io.acquire 和 prefetchMSHRs(i).io.acquire 都有效,仲裁结果 acquireArb.io.out 应该和 fetchMSHRs(i).io.acquire 一致。 |
35.1 | ICACHE_MISSUNIT_GRANT | READBEATCNT_EQUAL_0 | readBeatCnt 初始为 0,refillCycles - 1 也为 0。 io.mem_grant.valid 为高(因为 io.mem_grant.ready 默认为高,所以 io.mem_grant.fire 为高只需要 io.mem_grant.valid 为高)且 io.mem_grant.bits.opcpde(0)为高。 此时 respDataReg(0)= io.mem_grant.bits.data readBeatCnt 加一为 1。 |
35.2 | ICACHE_MISSUNIT_GRANT | READBEATCNT_EQUAL_1 | io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高。 此时 respDataReg(1)= io.mem_grant.bits.data readBeatCnt 重置回 0。 last_fire 为高。 下一拍 last_fire_r 为高,id_r=io.mem_grant.bits.source。 |
35.3 | ICACHE_MISSUNIT_GRANT | LAST_FIRE_R_EQUAL_HIGH | last_fire_r 为高,并且 id_r 为 0-13 中的一个。 对应的 fetchMSHRs 或者 prefetchMSHRs 会被无效,也就是 fetchMSHRs_i 或 prefetchMSHRs_i-4 的 io_invalid 会被置高。 |
35.4 | ICACHE_MISSUNIT_GRANT | GRANT_WITH_CORRUPT | io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高,则 corrupt_r 应为高。 如果 io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高中有一个不满足,且此时 last_fire_r 为高,则 corrupt_r 重置为低。 |
36.1 | ICACHE_MISSUNIT_REPLACER | UPDATE | 当 io.mem.acquire.ready & acquireArb.io.out.valid 同时为高,也就是 acquireArb.io.out.fir 为高时,io.victim.vSetIdx.valid 也为高。 io.victim.vSetIdx.bits = 当前 MSHR 请求的 acquireArb.io.out.bits.vSetIdx。 |
36.2 | ICACHE_MISSUNIT_REPLACER | GENERATE_WAYMASK | 根据从 L2 返回的 mshr_resp 中 mshr_resp.bits.way 生成 waymask 信息。 返回的 mshr_resp.bits.way 有 16 位,通过独热码生成一位掩码信息,waymask 表示其中哪一路被替换。 生成的 waymask 应该和 mshr_resp.bits.way 一致。 |
37.1 | ICACHE_MISSUNIT_WRITE_SRAM | GENERATE_META_AND_DATA_WRITE_VALID | 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。 并且此时没有硬件刷新信号和软件刷新信号,也就是 io.flush 和 io.fencei 为低。 在等待 l2 响应的过程中,没有刷新信号 也没有数据 corrupt,即 corrupt_r 为低。 那么 io.meta_write.valid 和 io.data_write.valid 均为高。 |
37.2 | ICACHE_MISSUNIT_WRITE_SRAM | WRITE_SRAM | io.meta_write.bits 的 virIdx、phyTag、waymask、bankIdx、poison 应该正常更新 io.data_write.bits 的 virIdx、data、waymask、bankIdx、poison 应该正常更新 |
38.1 | ICACHE_MISSUNIT_FINISH | MISS_REQ_FINISH | 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。 无论此时是否有硬件刷新信号和软件刷新信号, io.fetch_resp.valid 都为高,说明可向取指端发送响应。 io.fetch_resp.bits 中的数据更新: io.fetch_resp.bits.blkPaddr = mshr_resp.bits.blkPaddr io.fetch_resp.bits.vSetIdx = mshr_resp.bits.vSetIdx io.fetch_resp.bits.waymask = waymask io.fetch_resp.bits.data = respDataReg.asUInt io.fetch_resp.bits.corrupt = corrupt_r |
39.1 | ICACHE_MISSUNIT_FLUSH_OR_FENCEI | FENCEI_BEFORE_MSHR_ISSUE | 如果 MSHR 还没有通过 io.acquire.fire 发出请求,就应立即取消该 MSHR(mshr_resp.valid= false),既不发出请求,也不要写 SRAM。 当 io.fencei 为高时,fetchMSHRs 和 prefetchMSHRs 的 io.req.ready 和 io.acquire.valid 均为低,表示请求不发射。 |
39.2 | ICACHE_MISSUNIT_FLUSH_OR_FENCEI | FLUSH_BEFORE_MSHR_ISSUE | 由于 fetchMSHRs 的 io.flush 被直接设置为 false,所以 io.flush 对 fetchMSHRs 无效,但是对 prefetchMSHRs 有效。 当 io.flush 为高时,只能发射 fetchMSHRs 的请求。 |
39.3 | ICACHE_MISSUNIT_FLUSH_OR_FENCEI | FLUSH_OR_FENCEI_AFTER_MSHR_ISSUE | 已经发射了请求,之后再有刷新信号,那么等数据回来了但不写 SRAM。 在发射后,io.flush/io.fencei 为高时,等待数据回来,但是写 SRAM 的信号,write_sram_valid、io.meta_write.valid 和 io.data_write.valid 均为低,表示不写 SRAM。 对于 response fetch 无影响。 |
40.1 | ICACHE_CTRLUNIT_ECCCTRL | ENABLE_ECC | 向 eccctrl.enable 寄存器写入 true,验证模块内部 eccctrl.enable 设置为 true,并确保后续的错误注入操作能够成功进行。此测试确保 eccctrl.enable 写操作被执行。 确保 eccctrl.enable 被正确设置为 true,并触发 eccctrlRegWriteFn 中的写操作逻辑。 |
40.2 | ICACHE_CTRLUNIT_ECCCTRL | DISANLE_ECC | 向 eccctrl.enable 寄存器写入 false,验证模块内部 eccctrl.enable 设置为 false,并确保在后续的错误注入过程中,ECC 功能被禁用,不允许进行错误注入。此测试确保 eccctrl.enable 写操作被正确设置为 false。 验证禁用 ECC 时 eccctrl.enable 为 false,并触发 eccctrlRegWriteFn 中的错误处理分支。x.istatus = eccctrlInjStatus.error 和 x.ierror = eccctrlInjError.notEnabled |
41.1 | ICACHE_CTRLUNIT_FSM | IDLE | 初始为 is_idle 状态。 当 eccctrl.istatus 为 working 时,验证此时的状态为 is_readMetaReq。 |
41.2 | ICACHE_CTRLUNIT_FSM | READMETAREQ | 当握手成功后(io.metaRead.ready 和 io.metaRead.valid 都为高),验证此时的状态为 is_readMetaResp。 |
41.3.1 | ICACHE_CTRLUNIT_FSM_READMETARESP | MISS | 当 waymask 全零的时候,表示没有命中,会进入 is_idle 状态,并且设置错误错误注入状态和错误原因。 验证此时的状态为 is_idle, eccctrl.istatus = error 和 eccctrl.ierror = notFound。 |
41.3.2 | ICACHE_CTRLUNIT_FSM_READMETARESP | HIT | 当 waymask 不全零的时候,表示命中,会根据错误注入目标来判断是向元数据还是数据阵列写入错误。 当 eccctrl.itarget=metaArray 时,验证此时的状态为 is_writeMeta ;当 eccctrl.itarget!=metaArray 时,验证此时的状态为 is_writeData。 |
41.4.1 | ICACHE_CTRLUNIT_FSM_WRITEMETA | REGWRITEFN | 此状态进入后,io.dataWrite.valid 会为高 x.itarget = req.itarget 当 req.inject 为高并且 x.istatus = idle 时: 1. 如果 ecc 的 req.enable = false,则验证 x.istatus = error 且 x.ierror = notEnabled 2. 否则,如果 req.itarget != metaArray 和 dataArray,则验证 x.istatus = error 且 x.ierror = targetInvalid 3. 如果都不满足,则验证 x.istatus = working |
41.4.2 | ICACHE_CTRLUNIT_FSM_WRITEMETA | STATE_TRANSFER | 当 io.metaWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。 |
41.5.1 | ICACHE_CTRLUNIT_FSM_WRITEDATA | REGWRITEFN | 此状态进入后,io.dataWrite.valid 会为高 res.inject = false 当 ready 为高,且 x.istatus = injected 或 x.istatus = error 时,验证 x.istatus = idle 和 x.ierror = notEnabled |
41.5.2 | ICACHE_CTRLUNIT_FSM_WRITEDATA | STATE_TRANSFER | 当 io.dataWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。 |
42.1 | ICACHE_CTRLUNIT_EXTERNAL | READ_OR_WRITE_ECC_REGISTER | 验证外部模块可以通过 TileLink 协议正确读取和写入 eccctrl 和 ecciaddr 寄存器,并对模块内部的状态产生影响,确保读写操作完全覆盖。 |
42.2 | ICACHE_CTRLUNIT_EXTERNAL | EXTERNAL_MODULE_TRIGGER_ERROR_INJECT | 通过外部模块经 TileLink 总线向 eccctrl.inject 寄存器写入 true,触发错误注入,验证内部状态是否按 RegWriteFn 内部过程执行。 |
43.1 | ICACHE_ICACHE_PREFETCH | PREFETCH_HIT_WITHOUT_EXCEPTION | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查通过。 如果没有监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该命中的取指数据。 如果监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该是未命中的取指数据。 |
43.2 | ICACHE_ICACHE_PREFETCH | PREFETCH_MISS_WITHOUT_EXCEPTION | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 不匹配,pmp 检查通过。 如果监听到 MSHR 将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该命中的取指数据。 如果监听到 MSHR 没有将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该未命中的取指数据。 |
43.3 | ICACHE_ICACHE_PREFETCH | ONLY_TLB_EXCEPTION | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 异常。 验证 wayLookup.io.write 的 itlb_exception 内容中,其有对应的异常类型编号(pf:01;gpf:10;af:11)。 |
43.4 | ICACHE_ICACHE_PREFETCH | ONLY_TPMP_EXCEPTION | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查未通过。 验证 wayLookup.io.write 的 tlb_pbmt 内容中,其有对应的异常类型编号(nc:01;io:10)。 |
44.1 | ICACHE_ICACHE_FETCH | FETCH_HIT_WITHOUT_EXCEPTION | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。 验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。 验证 io.fetch.resp 的数据应该是取指的数据。 |
44.2 | ICACHE_ICACHE_FETCH | FETCH_MISS_MSHR_HIT_WITHOUT_EXCEPTION | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,未命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。 请求在 MSHR 返回的响应命中。 验证 missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。 验证 io.fetch.resp 的数据应该是取指的数据。 |
44.3 | ICACHE_ICACHE_FETCH | FETCH_MISS_AND_ECC_ERROR_WITHOUT_ELSE_EXCEPTION | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 或 MetaArray 的 ECC 校验错误。 验证 io.error.valid 为高,且 io.error.bits 内容为对应的错误源和错误类型。 先刷 MetaArray 的 ValidArray,给 MissUnit 发请求,由其在 L2 重填,阻塞至数据返回。 验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。 验证 io.fetch.resp 的数据应该是取指的数据。 |
44.4 | ICACHE_ICACHE_FETCH | FETCH_MISS_AND_SOME_EXCEPTION_WITHOUT_ELSE_EXCEPTION | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查未通过,DataArray 和 MetaArray 的 ECC 校验正常。 验证 io.fetch.resp 为对应的错误源和错误类型。 验证 io.fetch.resp 的数据无效,里面有异常类型。 |
44.5 | ICACHE_ICACHE_FETCH | FETCH_MISS_AND_ITLB_PBMT | 有 itlb_pbmt 和 pmp_mmio 时,他们合成 s1_mmio,传递到 s2_mmio,生成 s2_miss,有特殊情况就不会取指。 io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查通过,DataArray 和 MetaArray 的 ECC 校验正常。 验证 io.fetch.resp 为对应的错误源和错误类型。 验证 io.fetch.resp 的数据无效,里面有特殊情况类型类型。 |
44.6 | ICACHE_ICACHE_FETCH | FETCH_MISS_AND_PMP_MMIO | 处理同 5。 |
45.1 | ICACHE_ICACHE_METAARRAY | WRITE_META | 从 MissUnit 返回的请求都是未命中的请求(已命中不会向 MissUnit 请求,那么 MissUnit 自然也不会向 MetaArray 写入)。 发送一个写请求 write 到 ICacheMetaArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最后指定 virIdx、phyTag、waymask、bankIdx、poison。 写入操作后,发起一个对相同虚拟索引的读请求。验证 readResp 的 metas 和 codes 分别包含写入的 ptag 和 ecc code,并且对于写入的路,readResp.entryValid 信号被置为有效。 |
45.2 | ICACHE_ICACHE_METAARRAY | READ_META_HIT | 首先,向特定的虚拟索引(组和路)写入元数据(参照上面的写入操作)。然后,向相同的虚拟索引发送一个读请求。 验证 readResp.metas 包含之前写入的物理标签,并且对于相应的路,readResp.entryValid 信号被置为有效。 |
45.3 | ICACHE_ICACHE_METAARRAY | READ_META_MISS | 向 ICacheMetaArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。 验证对于任何路,readResp.entryValid 信号都没有被置为有效。 对应的 readResp.metas 和 codes 的内容是 DontCare 也就是 0。 |
45.4 | ICACHE_ICACHE_METAARRAY | SINGLE_CACHELINE_FLUSH | 先向 ICacheMetaArray 写入指定一个或多个端口的元数据,然后再给对应的端口的路发送刷新请求 io.flush,其包含虚拟索引 virIdx 和路掩码 waymask。 验证 valid_array 对应的路中的 virIdx 被置为无效,io.readResp.entryValid 对应路的对应端口为无效。 |
45.5 | ICACHE_ICACHE_METAARRAY | FLUSH_ALL | 先向多个不同的虚拟索引写入元数据。然后置位 io.flushAll 信号。 验证步骤: 在 io.flushAll 信号置位后,发起对所有之前写入过的虚拟索引的读请求。验证在所有的读取响应中,对于任何路,readResp.entryValid 信号都没有被置为有效。 |
46.1 | ICACHE_ICACHE_DATAARRAY | WRITE_DATA | 发送一个写请求 write 到 ICacheDataArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最终指定虚拟索引、数据、路掩码、存储体索引 bankIdx 和毒化位。写入的数据模式应跨越多个数据存储体。 写入操作后,发起一个对相同虚拟索引和块偏移量的读请求。验证 readResp.datas 与写入的数据相匹配。 |
46.2 | ICACHE_ICACHE_DATAARRAY | READ_DATA_HIT | 首先,向特定的虚拟索引和块偏移量写入数据。然后,向相同的虚拟索引和块偏移量发送一个读请求。使用不同的块偏移量进行测试,以覆盖存储体的选择逻辑。 验证 readResp.datas 包含之前写入的数据。 |
46.3 | ICACHE_ICACHE_DATAARRAY | READ_DATA_MISS | 向 ICacheDataArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。 验证 readResp.datas 为 0。 |
-
BPU 精确预测器(BPU s2/s3 给出结果)可能覆盖简单预测器(BPU s0 给出结果)的预测,显然其重定向请求最晚在预取请求的 1- 2 拍之后就到达 ICache,因此仅需要:
BPU s2 redirect:冲刷 IPrefetchPipe s0
BPU s3 redirect:冲刷 IPrefetchPipe s0/1
当 IPrefetchPipe 的对应流水级中的请求来自于软件预取时
isSoftPrefetch === true.B
,不需要进行冲刷当 IprefetchPipe 的对应流水级中的请求来自于硬件预取,但
ftqIdx
与冲刷请求不匹配时,不需要进行冲刷 ↩︎ -
fence.i
在逻辑上需要冲刷 MainPipe 和 IPrefetchPipe(因为此时流水级中的数据可能无效),但实际上io.fencei
拉高必然伴随一个后端重定向,因此目前的实现中没有冲刷 MainPipe 和 IPrefetchPipe 的必要。 ↩︎ ↩︎ -
此 RAS(Reliability, Availability, and Serviceability)非彼 RAS(Return Address Stack)。 ↩︎
-
RERI(RAS Error-record Register Interface),参考 RISC-V RERI 手册。 ↩︎ ↩︎
2.1 - CtrlUnit
CtrlUnit
目前 CtrlUnit 主要负责 ECC 校验使能/错误注入等功能。 RegField 案例类和伴生对象的作用,RegReadFn 和 RegWriteFn 案例类和伴生对象的作用。
通过两个控制寄存器 CSR:eccctrl 和 ecciaddr,来实现错误注入。 在 eccctrlBundle 中,定义 eccctrl 的 ierror、istatus、itarget、inject、enable 域的初始值。 在 ecciaddrBundle 中,定义 ecciaddr 的 paddr 域的初始值。
mmio-mapped CSR
CtrlUnit 内实现了一组 mmio-mapped CSR,连接在 tilelink 总线上,地址可由参数 cacheCtrlAddressOpt
配置,默认地址为0x38022080
。总大小为 128B。
当参数 cacheCtrlAddressOpt
为 None
时,CtrlUnit 不会实例化。此时 ECC 校验使能默认开启,软件不可控制关闭;软件不可控制错误注入。
目前实现的 CSR 如下:
64 10 7 4 2 1 0
0x00 eccctrl | WARL | ierror | istatus | itarget | inject | enable |
64 PAddrBits-1 0
0x08 ecciaddr | WARL | paddr |
CSR | field | desp |
---|---|---|
eccctrl | enable | ECC 错误校验使能,原 sfetchctl(0) 。 注:即使不使能,在 icache 重填时仍会计算 parity,可能会有额外功耗;但如果不计算,则在未使能转换成使能时需要冲刷 icache(否则读出的 parity 有 50%概率是错的)。 |
eccctrl | inject | ECC 错误注入使能,写 1 即使能,读恒 0 |
eccctrl | itarget | ECC 错误注入目标 0: metaArray 1: rsvd 2: dataArray 3: rsvd |
eccctrl | istatus | ECC 错误注入状态(read-only) 0: idle:注入控制器闲置 1: working:收到注入请求,注入中 2: injected:注入完成,等待触发 3: rsvd 4: rsvd 5: rsvd 6: rsvd 7: error:注入出错 |
eccctrl | ierror | ECC 错误原因(read-only) 0: ECC 未使能 (i.e. !eccctrl.enable) 1: inject 目标 SRAM 无效 (i.e. eccctrl.itarget==rsvd) 2: inject 目标地址 (i.e. ecciaddr.paddr) 不在 ICache 中 3: rsvd 4: rsvd 5: rsvd 6: rsvd 7: rsvd |
ecciaddr | paddr | ECC 错误注入物理地址 |
RERI standard | RERI 手册还要求了错误计数等寄存器,用于软件获取 RAS controller 状态,参考手册,可能需要与 dcache、L2cache 统一在后端实现,icache 像现在给 BEU 送 error 一样送给后端。 即:暂时不需要在 icache 实现,但要把错误计数等机制所需的接口准备出来 |
错误校验使能
CtrlUnit 的 eccctrl.enable
位直接连接到 MainPipe,控制 ECC 校验使能。当该位为 0 时,ICache 不会进行 ECC 校验。但仍会在重填时计算校验码并存储,这可能会有少量的额外功耗;如果不计算,则在未使能转换成使能时需要冲刷 ICache(否则读出的 parity code 可能是错的)。
错误注入使能
CtrlUnit 内部使用一个状态机控制错误注入过程,其 status (注意:与 eccctrl.istatus
不同)有:
- idle:注入控制器闲置
- readMetaReq:发送读取 metaArray 请求
- readMetaResp:接收读取 metaArray 响应
- writeMeta:写入 metaArray
- writeData:写入 dataArray
当软件向 eccctrl.inject
写入 1 时,进行以下简单检查,检查通过时状态机进入 readMetaReq
状态:
- 若
eccctrl.enable
为 0,报错eccctrl.ierror=0
- 若
eccctrl.itarget
为 rsvd(1/3),报错eccctrl.ierror=1
在 readMetaReq
状态下,CtrlUnit 向 MetaArray 发送 ecciaddr.paddr
地址对应的 set 读取的请求,等待握手。握手后转移到 readMetaResp
状态。
在 readMetaResp
状态下,CtrlUnit 接收到 MetaArray 的响应,检查 ecciaddr.paddr
地址对应的 ptag 是否命中,若未命中则报错 eccctrl.ierror=2
。否则,根据 eccctrl.itarget
进入 writeMeta
或 writeData
状态。
在 writeMeta
或 writeData
状态下,CtrlUnit 向 MetaArray/DataArray 写入任意数据,同时拉高 poison
位,写入完成后状态机进入 idle
状态。
ICache 顶层中实现了一个 Mux,当 CtrlUnit 的状态机不为 idle
时,将 MetaArray/DataArray 的读写口连接到 CtrlUnit,而非 MainPipe/IPrefetchPipe/MissUnit。当状态机 idle
时反之。
状态机和错误注入流程
is_idle
:模块处于空闲状态,等待错误注入的触发。只有当eccctrl.istatus
为working
时,状态机才会转移到is_readMetaReq
状态,准备读取元数据。is_readMetaReq
:发送读取元数据请求。通过接口metaRead
向缓存发送读取请求。当握手成功后状态会转移到is_readMetaResp
。is_readMetaResp
:接收元数据响应并验证。如果未命中,则会设置错误状态。没命中会转移状态到is_idle
,并设置错误错误注入状态和错误原因;如果找到有效的缓存行并且标签匹配,根据错误注入目标来判断是向元数据还是数据阵列写入错误。is_writeMeta
:写入带 poison 标记的数据完成注入。当握手成功后,错误注入状态设置为 injected(注入完成,等待触发),状态转移到is_idle
。is_writeData
:写入带 poison 标记的数据完成注入。当握手成功后,向数据阵列写入错误数据,错误注入状态设置为 injected(注入完成,等待触发),状态转移到is_idle
。
寄存器和接口映射
- eccctrl:控制 ECC 启用、错误注入状态等。寄存器通过 eccctrlRegField 进行映射。
- ecciaddr:指定错误注入的物理地址。通过 ecciaddrRegField 映射。
- 通过寄存器描述符(RegFieldDesc)和寄存器字段(RegField),定义了寄存器的这些是寄存器的描述信息和读写逻辑。
- 通过 node.regmap,这两个寄存器被映射到指定的地址偏移。eccctrl 寄存器被映射到 params.eccctrlOffset 地址,ecciaddr 寄存器被映射到 params.ecciaddrOffset 地址。
node.regmap
使得这两个寄存器可以通过外部的 TileLink 接口进行访问,外部模块可以读写这些寄存器以控制 ECC 和错误注入功能。
CtrlUnit 的功能点和测试点
ECC 启用/禁用
控制 eccctrl.enable 字段来启用或禁用 ECC 功能。外部系统可以通过写寄存器 eccctrl 来控制 ECC 是否启用。
- 通过寄存器写入控制信号 enable,当 enable 为 true 时,ECC 功能启用;为 false 时,ECC 功能禁用。
- 启用 ECC
- 向 eccctrl.enable 寄存器写入 true,验证模块内部 eccctrl.enable 设置为 true,并确保后续的错误注入操作能够成功进行。此测试确保 eccctrl.enable 写操作被执行。
- 确保 eccctrl.enable 被正确设置为 true,并触发 eccctrlRegWriteFn 中的写操作逻辑。
- 禁用 ECC
- 向 eccctrl.enable 寄存器写入 false,验证模块内部 eccctrl.enable 设置为 false,并确保在后续的错误注入过程中,ECC 功能被禁用,不允许进行错误注入。此测试确保 eccctrl.enable 写操作被正确设置为 false。
- 验证禁用 ECC 时 eccctrl.enable 为 false,并触发 eccctrlRegWriteFn 中的错误处理分支。x.istatus = eccctrlInjStatus.error 和 x.ierror = eccctrlInjError.notEnabled
状态机转换
根据状态机的状态,验证错误注入的流程是否正确。
- is_idle 状态
- 初始为 is_idle 状态。
- 当 eccctrl.istatus 为 working 时,验证此时的状态为 is_readMetaReq。
- is_readMetaReq 状态
- 当握手成功后(io.metaRead.ready 和 io.metaRead.valid 都为高),验证此时的状态为 is_readMetaResp。
- is_readMetaResp 状态
- 未命中
- 当 waymask 全零的时候,表示没有命中,会进入 is_idle 状态,并且设置错误错误注入状态和错误原因。
- 验证此时的状态为 is_idle, eccctrl.istatus = error 和 eccctrl.ierror = notFound。
- 命中
- 当 waymask 不全零的时候,表示命中,会根据错误注入目标来判断是向元数据还是数据阵列写入错误。
- 当 eccctrl.itarget=metaArray 时,验证此时的状态为 is_writeMeta ;当 eccctrl.itarget!=metaArray 时,验证此时的状态为 is_writeData。
- is_writeMeta 状态
- RegWriteFn
- 此状态进入后,io.dataWrite.valid 会为高
- x.itarget = req.itarget
- 当 req.inject 为高并且 x.istatus = idle 时:
- 如果 ecc 的 req.enable = false,则验证 x.istatus = error 且 x.ierror = notEnabled
- 否则,如果 req.itarget != metaArray 和 dataArray,则验证 x.istatus = error 且 x.ierror = targetInvalid
- 如果都不满足,则验证 x.istatus = working
- 状态转换
- 当 io.metaWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。
- is_writeData 状态
- RegWriteFn
- 此状态进入后,io.dataWrite.valid 会为高
- res.inject = false
- 当 ready 为高,且 x.istatus = injected 或 x.istatus = error 时,验证 x.istatus = idle 和 x.ierror = notEnabled
- 状态转换
- 当 io.dataWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。
寄存器映射和外部访问
通过 TileLink 总线将寄存器映射到特定地址,使外部模块可以读写 ECC 控制寄存器和注入地址寄存器。
- 使用 TLRegisterNode 实现寄存器的映射,使得外部系统可以通过地址访问寄存器。寄存器的读写操作通过 TileLink 协议进行。
- 外部读取和写入 ECC 控制寄存器
- 验证外部模块可以通过 TileLink 协议正确读取和写入 eccctrl 和 ecciaddr 寄存器,并对模块内部的状态产生影响,确保读写操作完全覆盖。
- 外部模块触发错误注入
- 通过外部模块经 TileLink 总线向 eccctrl.inject 寄存器写入 true,触发错误注入,验证内部状态是否按 RegWriteFn 内部过程执行。
2.2 - ICache
ICache
各种组合数据的宽度以 system verilog/verilog 中的为准。
- IPrefetchPipe 接收来自 FTQ 的预取请求,然后向 MetaArray 和 ITLB 发送请求,再从 ITLB 的响应得到 paddr,之后与 MetaArray 返回的 tag 进行比较得到命中信息,把命中信息、MetaArray ECC 校验信息和 ITLB 信息一并写入 WayLookup,同时进行 PMP 检查。未命中就将信息发送给 MissUnit 处理,MissUnit 通过 TileLink 总线向 L2Cache 发起请求,获取数据后返回给 MetaArray 和 IPrefetchPipe。之后会判断是否 Miss,如果 Miss 则把预取请求发送到 MissUnit,它会通过 TileLink 向 L2 做预取指。
- MainPipe 接收来自 FTQ 的取指请求,然后从 WayLookup 获取路命中信息和 ITLB 查询结果,再访问 DataArray。命中后向 replacer 发送 touch 请求,replacer 采用 PLRU 替换策略,接收到 MainPipe 的命中更新,向 MissUnit 提供写入的 waymask。同时进行 PMP 检查,接收 DataArry 返回的数据。对 DataArray 做 ECC 校验,根据 DataArry 和 MetaArry 的校验结果(MetaArray 的校验结果来自 Waylookup)判断是否将错误报告给总线(beu)。之后,如果 DataArry 没有命中,将信息发往 MissUnit 处理。MissUnit 通过 TileLink 总线向 L2Cache 发起请求,获取数据后返回给 DataArray 和 MainPipe。之后就可以将数据返回给 IFU。
- MetaArray 存储缓存行的标签(Tag)和 ECC 校验码
- 使用双 Bank SRAM 结构,支持双线访问(Double-Line),每个 Bank 存储部分元数据。
- 标签包含物理地址的高位,用于地址匹配。
- 支持标签 ECC 校验,检测和纠正存储错误。
- valid_array 记录每个 Way 的有效状态,Flush 操作会清零
- DataArray 存储实际的指令数据块。
- 数据按 Bank 划分为八个,每个 Bank 宽度为 64 位,支持多 Bank 并行访问。
- 数据 ECC 校验,分段生成校验码,增强错误检测能力。
- 支持双线访问,根据地址偏移选择 Bank,单周期可读取 32 字节数据。
- 冲刷信号有三种:ftqPrefetch.flushFromBpu,itlbFlushPipe,模块外部的 fencei 和 flush 信号。
- ftqPrefetch.flushFromBpu:通过 FTQ 来自的 BPU 刷新信号,用于控制预取请求的冲刷。
- itlbFlushPipe:ITLB 的冲刷信号,itlb 在收到该信号时会冲刷 gpf 缓存。
- fencei:刷新 MetaArray,清除所有路的 valid_array 清零;missUnit 中所有 MSHR 置无效。
- flush:mainPipe 和 prefetchPipe 所有流水级直接置无效,wayLookup 读写指针复位,gpf_entry 直接置无效,missUnit 中所有 MSHR 置无效。
Replacer
采用 PLRU 更新算法,考虑到每次取指可能访问连续的 doubleline,对于奇地址和偶地址设置两个 replacer,在进行 touch 和 victim 时根据地址的奇偶分别更新 replacer。

PLRU 算法示意
touch
Replacer 具有两个 touch 端口,用以支持双行,根据 touch 的地址奇偶分配到对应的 replacer 进行更新。
victim
Replacer 只有一个 victim 端口,因为同时只有一个 MSHR 会写入 SRAM,同样根据地址的奇偶从对应的 replacer 获取 waymask。并且在下一拍再进行 touch 操作更新 replacer。
ICache 的功能点和测试点
FTQ 预取请求处理
接收来自 FTQ 的预取请求,经 IPrefetchPipe 请求过滤(查询 ITLB 地址,是否命中 MetaArry,PMP 检查),(有异常则由 MissUnit 处理)后进入 WayLookup。
- 预取地址命中,无异常
- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查通过。
- 如果没有监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该命中的取指数据。
- 如果监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该是未命中的取指数据。
- 预取地址未命中,无异常
- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 不匹配,pmp 检查通过。
- 如果监听到 MSHR 将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该命中的取指数据。
- 如果监听到 MSHR 没有将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该未命中的取指数据。
- 预取地址 TLB 异常,无其他异常
- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 异常。
- 验证 wayLookup.io.write 的 itlb_exception 内容中,其有对应的异常类型编号(pf:01;gpf:10;af:11)。
- 预取地址 PMP 异常,无其他异常
- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查未通过。
- 验证 wayLookup.io.write 的 tlb_pbmt 内容中,其有对应的异常类型编号(nc:01;io:10)。
FTQ 取指请求处理
io.fetch.resp <> mainPipe.io.fetch.resp 发送回 IFU 的数据是在 io.fetch.resp。
接收来自 FTQ 的取指请求,从 WayLookup 获取路命中信息和 ITLB 查询结果,再访问 DataArray,监控 MSHR 的响应。更新 replacer,做 pmp 检查。后做 DataArray 和 MetaArray 的 ECC 校验。最后将数据发送给 IFU。
- 取指请求命中,无异常
- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。
- 验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
- 验证 io.fetch.resp 的数据应该是取指的数据。
- 取指请求未命中,MSHR 返回的响应命中,无异常
- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,未命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。
- 请求在 MSHR 返回的响应命中。
- 验证 missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
- 验证 io.fetch.resp 的数据应该是取指的数据。
- 取指请求命中,ECC 校验错误,无其他异常
- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 或 MetaArray 的 ECC 校验错误。
- 验证 io.error.valid 为高,且 io.error.bits 内容为对应的错误源和错误类型。
- 先刷 MetaArray 的 ValidArray,给 MissUnit 发请求,由其在 L2 重填,阻塞至数据返回。
- 验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
- 验证 io.fetch.resp 的数据应该是取指的数据。
- 取指请求未命中,但是 exception 非 0(af、gpf、pf),无其他异常
- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查未通过,DataArray 和 MetaArray 的 ECC 校验正常。
- 验证 io.fetch.resp 为对应的错误源和错误类型。
- 验证 io.fetch.resp 的数据无效,里面有异常类型。
- 取指请求未命中,通过 WayLookup 中读取到的预取过来的 itlb 中返回 pbmt。
- 有 itlb_pbmt 和 pmp_mmio 时,他们合成 s1_mmio,传递到 s2_mmio,生成 s2_miss,有特殊情况就不会取指。
- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查通过,DataArray 和 MetaArray 的 ECC 校验正常。
- 验证 io.fetch.resp 为对应的错误源和错误类型。
- 验证 io.fetch.resp 的数据无效,里面有特殊情况类型类型。
- 取指请求未命中,pmp 返回 mmio ,处理同 5。
MetaArray 功能
在 IPrefetchPipe 的 S0,接收来自 IPrefetchPipe 的读请求 read,返回对应路和组的响应 readResp。 在 miss 的时候,MissUnit 会将会应的数据写入 write 到 MetaArray。 MetaArray 主要存储了每个 Cache 行的标签和 ECC 校验码。
- 元数据写入操作(对应的 Set 已满): ICacheMetaArray 应当能够正确地将元数据(标签和有效位)写入到指定的 Set 和 Way 。
- 从 MissUnit 返回的请求都是未命中的请求(已命中不会向 MissUnit 请求,那么 MissUnit 自然也不会向 MetaArray 写入)。
- 发送一个写请求 write 到 ICacheMetaArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最后指定 virIdx、phyTag、waymask、bankIdx、poison。
- 写入操作后,发起一个对相同虚拟索引的读请求。验证 readResp 的 metas 和 codes 分别包含写入的 ptag 和 ecc code,并且对于写入的路,readResp.entryValid 信号被置为有效。
- 元数据读取操作 (命中): 当一个读请求在 ICacheMetaArray 中命中时(存在有效的条目),它应该返回正确的元数据(标签和有效位)。
- 首先,向特定的虚拟索引(组和路)写入元数据(参照上面的写入操作)。然后,向相同的虚拟索引发送一个读请求。
- 验证 readResp.metas 包含之前写入的物理标签,并且对于相应的路,readResp.entryValid 信号被置为有效。
- 元数据读取操作 (未命中): 当读取一个尚未被写入的地址时,ICacheMetaArray 应当指示未命中(条目无效)。
- 向 ICacheMetaArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。
- 验证对于任何路,readResp.entryValid 信号都没有被置为有效。 对应的 readResp.metas 和 codes 的内容是 DontCare 也就是 0。
- 独立的缓存组刷新:在第 i 个端口是有效的刷新请求,并且该请求的 waymask 指定了当前正在处理的第 w 路时,应该使第 i 个端口的条目无效。
- 先向 ICacheMetaArray 写入指定一个或多个端口的元数据,然后再给对应的端口的路发送刷新请求 io.flush,其包含虚拟索引 virIdx 和路掩码 waymask。
- 验证 valid_array 对应的路中的 virIdx 被置为无效,io.readResp.entryValid 对应路的对应端口为无效。
- 全部刷新操作: ICacheMetaArray 应当能够在接收到全部刷新请求时,使所有条目无效。
- 先向多个不同的虚拟索引写入元数据。然后置位 io.flushAll 信号。
- 验证步骤: 在 io.flushAll 信号置位后,发起对所有之前写入过的虚拟索引的读请求。验证在所有的读取响应中,对于任何路,readResp.entryValid 信号都没有被置为有效。
DataArray 功能
与 MetaArray 类似,在 MainPipe 的 S0,接收来自 MainPipe 的读请求 read,返回对应路和组的响应 readResp。 在 miss 的时候,MissUnit 会将会应的数据写入 write 到 DataArray。 DataArray 主要存储了每个 Cache 行的标签和 ECC 校验码。
- 数据写入操作(对应的 Set 已满): ICacheDataArray 应当能够正确地将数据写入到指定的 Set (组)、Way (路) 和数据 Bank (存储体)。
- 发送一个写请求 write 到 ICacheDataArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最终指定虚拟索引、数据、路掩码、存储体索引 bankIdx 和毒化位。写入的数据模式应跨越多个数据存储体。
- 写入操作后,发起一个对相同虚拟索引和块偏移量的读请求。验证 readResp.datas 与写入的数据相匹配。
- 数据读取操作 (命中): 当一个读请求命中时(相应的元数据有效),它应该从相应的组、路和数据存储体返回正确的数据。
- 首先,向特定的虚拟索引和块偏移量写入数据。然后,向相同的虚拟索引和块偏移量发送一个读请求。使用不同的块偏移量进行测试,以覆盖存储体的选择逻辑。
- 验证 readResp.datas 包含之前写入的数据。
- 数据读取操作 (未命中): 当读取一个尚未被写入的地址时,ICacheDataArray 的输出应该是默认值或无关值。
- 向 ICacheDataArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。
- 验证 readResp.datas 为 0。
2.3 - IPrefetchPipe
IPrefetchPipe
IPrefetchPipe 为预取的流水线,三级流水设计,负责预取请求的过滤。

IPrefetchPipe结构示意图
- 接收预取请求(s0 阶段):
- 从 FTQ 或后端接收预取请求。
- 发送读请求到 ITLB 和 MetaArray 缓存元数据模块。
- 地址转换和缓存检查(s1 阶段):
- 接收 ITLB 的地址转换结果,处理可能的缺失和重发。
- 从缓存元数据中读取标签和有效位,检查是否命中。
- 进行 PMP 权限检查,合并异常信息。
- 根据情况决定是否发送请求到 WayLookup 模块。
- 未命中请求处理(s2 阶段):
- 检查与 missUnit 的交互,更新命中状态。
- 对于无异常的未命中请求,向 missUnit 发送请求以获取数据。
- 控制流水线的推进和刷新,处理可能的阻塞和异常。
S0 流水级
在 S0 流水级,接收来自 FTQ 的预取请求,向 MetaArray 和 ITLB 发送请求。
- 接收预取请求:从 FTQ 或后端接收预取请求,提取预取请求的虚拟地址、FTQ 索引、是否为软件预取、是否跨缓存行信、虚拟组索引(s0_req_vSetIdx)和后端的异常信息。
- 发送请求到 ITLB:将虚拟地址发送到 ITLB 进行地址转换。
- 发送请求到缓存元数据(Meta SRAM):将请求发送到缓存的元数据存储器,以便在后续阶段读取缓存标签和有效位。
S1 流水级
软件预取 enqway 持续一拍…
- 接收 ITLB 的响应:从 ITLB 接收地址转换的结果,包括物理地址 paddr、异常类型(
af
/pf
)和特殊情况(pbmt.nc
/pbmt.io
)。 - 接收缓存元数据的响应并检查缓存命中:从缓存元数据存储器 MetaArray 读取缓存标签
tag
和有效位,检查预取地址是否在缓存中已存在,命中结果存入waymask
中。 - 权限检查:使用 PMP 对物理地址进行权限检查,确保预取操作的合法性。
- 异常处理和合并:合并来自后端、ITLB、PMP 的异常信息,准备在后续阶段处理。
- 发送请求到 WayLookup 模块:当条件满足时,将元数据(命中信息
waymask
、ITLB 信息paddr
/af
/pf
)发送到 WayLookup 模块,以便进行后续的缓存访问。 - 状态机转换:根据当前状态和条件,更新下一个状态。
- 状态机初始状态为
idle
,当 S1 流水级进入新的请求时,首先判断 ITLB 是否缺失,如果缺失,就进入itlbResend
;如果 ITLB 命中但命中信息未入队 WayLookup,就进入enqWay
;如果 ITLB 命中且 WayLookup 入队但 S2 请求未处理完,就进入enterS2
。 - 在
itlbResend
状态,重发 ITLB 请求,此时占用 ITLB 端口,直至请求回填完成,在回填完成的当拍向 MetaArray 再次发送读请求,回填期间可能发生新的写入,如果 MetaArray 繁忙(正在被写入),就进入metaResend
,否则进入enqWay
。 - 在
metaResend
状态,重发 MetaArray 读请求,发送成功后进入enqWay
。 - 在
enqWay
状态,尝试将元数据入队 WayLookup,如果 WayLookup 队列已满,就阻塞至 WayLookup 入队成功,另外在 MSHR 发生新的写入时禁止入队,主要是为了防止写入的信息与命中信息所冲突,需要对命中信息进行更新。当成功入队 WayLookup 或者是软件预取时,如果 S2 空闲,就直接进入idle
,否则进入enterS2
。 - 在
enterS2
状态,尝试将请求流入下一流水级,流入后进入idle
。
- 状态机初始状态为

IPrefetchPipe S1状态机
S2 流水级
- 监控 missUnit 的请求:更新 MSHR 的匹配状态。综合该请求的命中结果、ITLB 异常、PMP 异常、meta 损坏,判断是否需要预取,只有不存在异常时才进行预取。
- 发送请求到 missUnit:因为同一个预测块可能对应两个 cacheline,所以通过 Arbiter 依次将请求发送至 MissUnit。
命中信息的更新
在 S1 流水级中得到命中信息后,距离命中信息真正在 MainPipe 中被使用要经过两个阶段,分别是在 IPrefetchPipe 中等待入队 WayLookup 阶段和在 WayLookup 中等待出队阶段,在等待期间可能会发生 MSHR 对 Meta/DataArray 的更新,因此需要对 MSHR 的响应进行监听,分为两种情况:
- 请求在 MetaArray 中未命中,监听到 MSHR 将该请求对应的 cacheline 写入了 SRAM,需要将命中信息更新为命中状态。
- 请求在 MetaArray 中已经命中,监听到同样的位置发生了其它 cacheline 的写入,原有数据被覆盖,需要将命中信息更新为缺失状态。
为了防止更新逻辑的延迟引入到 DataArray 的访问路径上,在 MSHR 发生新的写入时禁止入队 WayLookup,在下一拍入队。
刷新机制
在 IPrefetch 中如果收到后端重定向、IFU 预译码、fencei 带来的刷新,就冲刷整个流水线
IPrefetchPipe 模块中的刷新信号主要来自以下两个方面:
- 全局刷新信号:由系统的其他模块发出的全局刷新信号,如怀疑流水线中存在错误数据或需要清除流水线时触发。
- io.flush:模块输入的全局刷新信号。当系统需要清除所有流水线阶段的数据时,该信号被置为高。
- 来自分支预测单元(BPU)的刷新信号:当分支预测错误或需要更新预测信息时,BPU 会发出刷新信号。
- io.flushFromBpu:包含来自 BPU 的刷新信息,指示哪些指令需要被刷新。
IPrefetchPipe 的功能点和测试点
接收预取请求
从 FTQ 接收预取请求,请求可能有效( io.req.valid 为高),可能无效; IPrefetchPipe 可能处于空闲( io.req.ready 为高),可能处于非空闲状态。 只有在请求有效且 IPrefetchPipe 处于空闲状态时,预取请求才会被接收(这里暂不考虑 s0 的刷新信号 s0_flush ,默认其为低)。 预取请求分为不同类型,包括硬件预取请求 (isSoftPrefetch = false)和软件预取请求 (isSoftPrefetch = true)。 cacheline 也分为单 cacheline 和双 cacheline。
-
硬件预取请求: 预取请求为硬件 (isSoftPrefetch = false)
- 预取请求可以继续:
- 当预取请求有效且 IPrefetchPipe 处于空闲状态时,预取请求应该被接收。
- s0_fire 信号在没有 s0 的刷新信号( s0_flush 为低)时,应该被置为高。
- 预取请求被拒绝–预取请求无效时:
- 当预取请求无效时,预取请求应该被拒绝。
- s0_fire 信号应该被置为低。
- 预取请求被拒绝–IPrefetchPipe 非空闲时:
- 当 IPrefetchPipe 非空闲时,预取请求应该被拒绝。
- s0_fire 信号应该被置为低。
- 预取请求被拒绝–预取请求无效且 IPrefetchPipe 非空闲时:
- 当预取请求无效且 IPrefetchPipe 非空闲时,预取请求应该被拒绝。
- s0_fire 信号应该被置为低。
- 预取请求有效且为单 cacheline 时:
- 当预取请求有效且为单 cacheline 时,预取请求应该被接收。
- s0_fire 为高,s0_doubleline 应该被置低(false)。
- 预取请求有效且为双 cacheline 时:
- 当预取请求有效且为双 cacheline 时,预取请求应该被接收。
- s0_fire 为高,s0_doubleline 应该被置高(true)。
- 预取请求可以继续:
-
软件预取请求: 预取请求为软件 (isSoftPrefetch = true)
- 软件预取请求可以继续:
- 当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。
- s0_fire 信号在没有 s0 的刷新信号( s0_flush 为低)时,应该被置为高。
- 软件预取请求被拒绝–预取请求无效时:
- 当预取请求无效时,软件预取请求应该被拒绝。
- s0_fire 信号应该被置为低。
- 软件预取请求被拒绝–IPrefetchPipe 非空闲时:
- 当 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。
- s0_fire 信号应该被置为低。
- 软件预取请求被拒绝–预取请求无效且 IPrefetchPipe 非空闲时:
- 当预取请求无效且 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。
- s0_fire 信号应该被置为低。
- 软件预取请求有效且为单 cacheline 时:
- 当软件预取请求有效且为单 cacheline 时,软件预取请求应该被接收。
- s0_fire 为高,s0_doubleline 应该被置低(false)。
- 软件预取请求有效且为双 cacheline 时:
- 当软件预取请求有效且为双 cacheline 时,软件预取请求应该被接收。
- s0_fire 为高,s0_doubleline 应该被置高(true)。
- 软件预取请求可以继续:
接收来自 ITLB 的响应并处理结果
接收 ITLB 的响应,完成虚拟地址到物理地址的转换。 当 ITLB 发生缺失(miss)时,保存请求信息,等待 ITLB 完成后再继续处理。
-
地址转换完成:
- 根据 ITLB 的响应,接收物理地址(paddr),并完成地址转换。
- 处理 ITLB 响应可能在不同周期到达的情况,管理有效信号和数据保持机制,确保正确使用物理地址。
-
当 ITLB 正常返回物理地址时:
- ITLB 在一个周期内成功返回物理地址 paddr,s1_valid 为高。
- 确认 s1 阶段正确接收到 paddr。
-
当 ITLB 发生 TLB 缺失,需要重试时:
- fromITLB(PortNumber).bits.miss 为高,表示对应通道的 ITLB 发生了 TLB 缺失,需要重发。
- 重发完成后,后续步骤继续进行,fromITLB(PortNumber).bits.miss 为低。
-
处理 ITLB 异常:
- 根据 ITLB 的异常信息,处理可能的异常。pf 缺页、pgf 虚拟机缺页、af 访问错误。
- 当 ITLB 发生页错误异常时:
- s1_itlb_exception 返回的页错误。
- iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示页错误 pf。
- 当 ITLB 发生虚拟机页错误异常时:
- s1_itlb_exception 返回的虚拟机页错误。
- iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示虚拟机页错误 pgf。
- 当 ITLB 发生访问错误异常时:
- s1_itlb_exception 返回的访问错误。
- iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示访问错误 af。
-
处理虚拟机物理地址(用于虚拟化):
- 在虚拟化环境下,处理虚拟机物理地址(gpaddr),确定访问是否针对二级虚拟机的非叶子页表项(isForVSnonLeafPTE)。
- 发生虚拟机页错误异常返回虚拟机物理地址(gpaddr):
- 发生 pgf 后,需要返回对应的 gpaddr。
- 只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。
- 当访问二级虚拟机的非叶子页表项时:
- 发生 gpf 后,如果是访问二级虚拟机的非叶子页表项时,需要返回对应的 gpaddr。
- 只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。
-
返回基于页面的内存类型 pbmt 信息:
- TLB 有效时,返回 pbmt 信息。
接收来自 IMeta(缓存元数据)的响应并检查缓存命中
从 Meta SRAM 中读取缓存标签和有效位。 将物理地址的标签部分与缓存元数据中的标签比较,确定是否命中。
-
缓存标签比较和有效位检查:
- 从物理地址中提取物理标签(ptag),将其与缓存元数据中的标签进行比较,检查所有缓存路(Way)。检查有效位,确保只考虑有效的缓存行。
- 缓存未命中(标签不匹配或有效位为假):
- 当标签不匹配或者标签匹配,但是有效位为假时,表示缓存未命中。
- s1_meta_ptags(PortNumber)(nWays) 不等于 ptags(PortNumber) 或者它们相等,但是对应的 s1_meta_valids 为低时,总之返回的 waymasks 为全 0。
- 单路缓存命中(标签匹配且有效位为真):
- 当标签匹配,且有效位为真时,表示缓存命中。
- waymasks 对应的位为 1。
PMP(物理内存保护)权限检查
对物理地址进行 PMP 权限检查,确保预取操作的合法性。 处理 PMP 返回的异常和 MMIO 信息
- 访问被允许的内存区域
- itlb 返回的物理地址在 PMP 允许的范围内。
- s1_pmp_exception(i) 为 none。
- 访问被禁止的内存区域
- s1_req_paddr(i) 对应的地址在 PMP 禁止的范围内。
- s1_pmp_exception(i) 为 af。
- 访问 MMIO 区域
- itlb 返回的物理地址在 MMIO 区域。
- s1_pmp_mmio 为高。
异常处理和合并
backend 优先级最高,merge 方法里的异常越靠前优先级越高
合并来自后端、ITLB、PMP 的异常信息,按照优先级确定最终的异常类型。
- 仅 ITLB 产生异常
- s1_itlb_exception(i) 为非零,s1_pmp_exception(i) 为零。
- s1_exception_out(i) 正确包含 ITLB 异常。
- 仅 PMP 产生异常
- s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为非零。
- s1_exception_out(i) 正确包含 PMP 异常。
- 仅 后端 产生异常
- s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为零。
- s1_exception_out(i) 正确包含 后端 异常。
- ITLB 和 PMP 都产生异常
- s1_itlb_exception(i) 和 s1_pmp_exception(i) 都为非零。
- s1_exception_out(i) 包含 ITLB 异常(优先级更高)。
- ITLB 和 后端 都产生异常
- s1_itlb_exception(i) 和 s1_backendException(i) 都为非零。
- s1_exception_out(i) 包含 后端 异常(优先级更高)。
- PMP 和 后端 都产生异常
- s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。
- s1_exception_out(i) 包含 后端 异常(优先级更高)。
- ITLB、PMP 和 后端 都产生异常
- s1_itlb_exception(i)、s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。
- s1_exception_out(i) 包含 后端 异常(优先级更高)。
- 无任何异常
- s1_itlb_exception(i)、s1_pmp_exception(i)、s1_backendException(i) 都为零。
- s1_exception_out(i) 指示无异常。
发送请求到 WayLookup 模块
当条件满足时,将请求发送到 WayLookup 模块,以进行后续的缓存访问。
- 正常发送请求到 WayLookup
- toWayLookup.valid 为高,toWayLookup.ready 为高,s1_isSoftPrefetch 为假。
- 请求成功发送,包含正确的地址、标签、waymask 和异常信息。
- WayLookup 无法接收请求
- toWayLookup.valid 为高,toWayLookup.ready 为假。
- 状态机等待 WayLookup 准备好,不会错误地推进。
- 软件预取请求不发送到 WayLookup
- s1_isSoftPrefetch 为真。
- toWayLookup.valid 为假,不会发送预取请求到 WayLookup。
状态机控制和请求处理流程
使用状态机管理 s1 阶段的请求处理流程。 包括处理 ITLB 重发、Meta 重发、进入 WayLookup、等待 s2 准备等状态
-
初始为 m_idle 状态:
- 正常流程推进,保持 m_idle 状态
- s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为真,s2_ready 为真。
- 状态机保持在 m_idle 状态,s1 阶段顺利推进。
- ITLB 未完成,需要重发
- s1_valid 为高,itlb_finish 为假。
- 状态机进入 m_itlbResend 状态,等待 ITLB 完成。
- ITLB 完成,WayLookup 未命中
- s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为假。
- 状态机进入 m_enqWay 状态,等待 WayLookup 入队。
-
初始为 m_itlbResend 状态:
- ITLB 命中, MetaArray 空闲,需要 WayLookup 入队
- itlb_finish 为假,toMeta.ready 为真。
- 状态机进入 m_enqWay 状态,等待 WayLookup 入队。
- ITLB 命中, MetaArray 繁忙,等待 MetaArray 读请求
- itlb_finish 为假,toMeta.ready 为假。
- 状态机进入 m_metaResend 状态,MetaArray 读请求
- ITLB 命中, MetaArray 空闲,需要 WayLookup 入队
-
初始为 m_metaResend 状态:
- MetaArray 空闲 ,需要 WayLookup 入队
- toMeta.ready 为真。
- 状态机进入 m_enqWay 状态,等待 WayLookup 入队。
- MetaArray 空闲 ,需要 WayLookup 入队
-
初始为 m_enqWay 状态:
- WayLookup 入队完成或者为软件预取, S2 空闲, 重新进入空闲状态
- toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为假。
- 状态机进入空闲状态 m_idle。
- WayLookup 入队完成或者为软件预取, S2 繁忙,需要 enterS2 状态
- toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为真。
- 状态机进入 m_enterS2 状态,等待 s2 阶段准备好。
- WayLookup 入队完成或者为软件预取, S2 空闲, 重新进入空闲状态
-
初始为 m_enterS2 状态:
- s2 阶段准备好,请求进入下流水级,流入后进入 m_idle 状态
- s2_ready 为真。
- 状态机进入空闲状态 m_idle。
监控 missUnit 的请求
检查 missUnit 的响应,更新缓存的命中状态和 MSHR 的匹配状态。
-
请求与 MSHR 匹配且有效:
- s2_req_vSetIdx 和 s2_req_ptags 与 fromMSHR 中的数据匹配,且 fromMSHR.valid 为高,fromMSHR.bits.corrupt 为假。
- s2_MSHR_match(PortNumber) 为真, s2_MSHR_hits(PortNumber) 应保持为真
-
请求在 SRAM 中命中:
- s2_waymasks(PortNumber) 中有一位为高,表示在缓存中命中。
- s2_SRAM_hits(PortNumber) 为真,s2_hits(PortNumber) 应为真。
-
请求未命中 MSHR 和 SRAM:
- 请求未匹配 MSHR,且 s2_waymasks(PortNumber) 为空。
- s2_MSHR_hits(PortNumber)、s2_SRAM_hits(PortNumber) 均为假, s2_hits(PortNumber) 为假。
发送请求到 missUnit
对于未命中的预取请求,向 missUnit 发送请求,以获取缺失的数据。
- 确定需要发送给 missUnit 的请求
-
根据命中状态、异常信息、MMIO 信息等,确定哪些请求需要发送到 missUnit(即 s2_miss)。
-
请求未命中且无异常,需要发送到 missUnit:
- s2_hits(PortNumber) 为假(未命中缓存),s2_exception 无异常,s2_mmio 为假(不是 MMIO 或不可缓存的内存)。
- s2_miss(PortNumber) 为真,表示需要发送请求到 missUnit。
-
请求命中或有异常,不需要发送到 missUnit:
- s2_hits(i) 为真(已命中)或者 s2_exception 有异常 或者 s2_mmio 为真(MMIO 访问)。
- s2_miss(i) 为假,不会发送请求到 missUnit。
-
双行预取时,处理第二个请求的条件:
- s2_doubleline 为真,处理第二个请求。
- 如果第一个请求有异常或 MMIO,s2_miss(1) 应为假,后续请求被取消或处理。
-
- 避免发送重复请求,发送请求到 missUnit
-
使用寄存器 has_send 记录每个端口是否已发送请求,避免重复发送。
-
将需要发送的请求通过仲裁器 toMSHRArbiter 发送到 missUnit。
-
在 s1_real_fire 时,复位 has_send:
- s1_real_fire 为高。
- has_send(PortNumber) 应被复位为假,表示新的请求周期开始。
-
当请求成功发送时,更新 has_send:
- toMSHRArbiter.io.in(PortNumber).fire 为高(请求已发送)。
- has_send(PortNumber) 被设置为真,表示该端口已发送请求。
-
避免重复发送请求:
- 同一请求周期内,has_send(PortNumber) 为真,s2_miss(PortNumber) 为真。
- toMSHRArbiter.io.in(PortNumber).valid 为假,不会再次发送请求。
-
正确发送需要的请求到 missUnit:
- s2_valid 为高,s2_miss(i) 为真,has_send(i) 为假。
- toMSHRArbiter.io.in(i).valid 为高,请求被成功发送。
-
仲裁器正确仲裁多个请求:
- 多个端口同时需要发送请求。
- 仲裁器按照优先级或设计要求选择请求发送到 missUnit,未被选中的请求在下个周期继续尝试发送。
-
刷新机制
- io.flush: 全局刷新信号,当该信号为高时,所有请求都需要刷新。
- from_bpu_s0_flush:当请求不是软件预取(!s0_isSoftPrefetch, 软件预取请求是由特定的指令触发的,与指令流中的分支预测无关。因此,在处理刷新信号时,对于软件预取请求,通常不受来自 BPU 的刷新信号影响。),且 BPU 指示需要在 Stage 2 或 Stage 3 刷新的请求,由于该请求尚未进入 s1 阶段,因此在 s0 阶段也需要刷新。
- s0_flush:综合考虑全局刷新信号、来自 BPU 的刷新信号,以及 s1 阶段的刷新信号
- from_bpu_s1_flush:当 s1 阶段的请求有效且不是软件预取,且 BPU 指示在 Stage 3 需要刷新,则在 s1 阶段需要刷新。
- io.itlbFlushPipe:当 s1 阶段需要刷新时,该信号用于通知 ITLB 刷新其流水线,以保持一致性。
- s1_flush:综合考虑全局刷新信号和来自 BPU 的刷新信号。
- s2_flush:用于控制 s2 阶段是否需要刷新。
- 发生全局刷新
- io.flush 为高。
- s0_flush、s1_flush、s2_flush 分别为高,所有阶段的请求被正确清除。
- 来自 BPU 的刷新
- io.flushFromBpu.shouldFlushByStageX 为真(X 为 2 或 3),且请求不是软件预取。
- 对应阶段的 from_bpu_sX_flush 为高,sX_flush 为高,阶段请求被刷新。
- 刷新时状态机复位
- s1_flush 为高。
- 状态机 state 被重置为 m_idle 状态。
- ITLB 管道同步刷新
- s1_flush 为高。
- io.itlbFlushPipe 为高,ITLB 被同步刷新。
2.4 - MainPipe
MainPipe
MainPipe 为 ICache 的主流水,为三级流水设计,负责从 DataArray 中读取数据,pmp 检查,缺失处理,并且将结果返回给 IFU。

MainPipe结构示意图
- 从 WayLookup 获取信息,访问 DataArray 单路(S0 阶段) 在 S0 流水级,从 WayLookup 获取元数据,包括路命中信息和 ITLB 查询结果,访问 DataArray 的单路,如果 DataArray 正在被写或 WayLookup 中没有有效表项,流水线就会阻塞。每次重定向后,FTQ 中同一个请求被同时发送到 MainPipe 和 IPrefetchPipe 中,MainPipe 始终需要等待 IPrefetchPipe 将请求的查询信息写入 WayLookup 后才能向下走,导致了 1 拍重定向延迟,当预取超过取指时,该延迟就会被覆盖。
- 接收并解析来自 FTQ 的取指请求,提取必要的请求信息,如虚拟地址、缓存组索引、块内偏移、是否为双行读、后端的异常信息。
- 从 WayLookup 模块获取缓存命中信息和 TLB 信息,包括 waymask、物理标签、虚拟机物理地址、是否为叶节点、 ITLB 异常、ITLB 的 PBMT 信息、缓存元数据的校验码。
- 访问 DataArray 的单路,如果 DataArray 正在被写或 WayLookup 中没有有效表项,流水线就会阻塞。
- 每次重定向后,FTQ 中同一个请求被同时发送到 MainPipe 和 IPrefetchPipe 中,MainPipe 始终需要等待 IPrefetchPipe 将请求的查询信息写入 WayLookup 后才能向下走,导致了 1 拍重定向延迟,当预取超过取指时,该延迟就会被覆盖。
- 接收上一个阶段的信息并进行数据暂存、PMP 检查、从 DataArray 获取读响应异常合并、替换策略更新以及监控 MissUnit(S1 阶段)
- 寄存并延迟 S0 阶段信息
- 从 S0 获取的地址、tag、命中方式(waymask)、TLB 异常标志、下一拍要用的数据等,都会在 S1 寄存一拍,保证在流水线停顿时也能维持正确值。
- Meta ECC 检查
- 对 S0 读出的 meta 和其校验码(ECC/Parity)进行比对,判断是否发生错误。如果关闭 parity 功能,则跳过该检查。
- 更新 replacer
- 对确定命中的访问请求,进行“touch”更新,标记最近使用过的 way,以便后续替换算法正确运行。
- PMP 检查
- 根据 S0 得到的物理地址(paddr),在 S1 对其进行 PMP 检查,判断是否拥有执行权限、是否为 MMIO 等。在当拍收到响应,将结果寄存到下一流水级进行处理。
- 需要指出,IPrefetchPipe s1 流水级也会进行 PMP 检查,和此处的检查实际上是完全一样的,分别检查只是为了优化时序(避免
ITLB(reg) -> ITLB.resp -> PMP.req -> PMP.resp -> WayLookup.write -> bypass -> WayLookup.read -> MainPipe s1(reg)
的超长组合逻辑路径)。
- 异常合并
- 将 ITLB 与 PMP 异常进行优先级合并,产生最终的异常标记。
- 选择数据来源:MSHR 或 SRAM
- 接收 DataArray 返回的 data 和 code 并寄存,同时监听 MSHR 的响应,当 DataArray 和 MSHR 的响应同时有效时,后者的优先级更高。当 MSHR 已在填充一些数据,如果当前请求与 MSHR 命中,可以在 S1 阶段直接选用 MSHR 的数据,而不必依赖 SRAM 读出的结果。
- 监控 MissUnit,在 ECC 校验、异常处理和缺失处理之后,将最终的数据、异常信息传递给 IFU,完成取指流程(S2 阶段)
- ECC 校验
- DataArray ECC 校验,对 S1 流水级寄存的 code 进行校验,生成 data 是否损坏信号 s2_data_corrupt。如果校验出错,就将错误报告给 BEU。
- MetaArray ECC 校验,IPrefetchPipe 读出 MetaArray 的数据后会直接进行校验,并将校验结果随命中信息一起入队 WayLookup 并随 MainPipe 流水到达 S2 级(meta_corrupt 信号),在此处随 DataArray 的 ECC 校验结果一起报告给 BEU。
- 监控 MissUnit 响应端口
- 检查当前 S2 阶段的请求是否与 MSHR 中的条目匹配,命中时寄存 MSHR 响应的数据,为了时序在下一拍才将数据发送到 IFU。
- 更新 Data 和其是否来自 MSHR 的信息。
- 更新 s2_hits 和处理异常。
- 处理 L2 Cache 的 Corrupt 标志。
- 缺失处理,发送 Miss 请求到 MSHR
- 计算是否需要重新获取(Refetch)。
- 通过是否命中、ECC 错误、正确跨行、是否异常和是否属于 MMIO 区域来发送 Miss 请求。
- 设置 Arbiter 合并多个端口的 Miss 请求,确保一次只处理一个请求,同时有避免重复请求的设置。
- 判断 Fetch 是否完成。
- 生成 L2 Cache 的异常标记,再将当前 S2 阶段的异常(包括 ITLB、PMP)与 L2 Cache 的异常进行合并。
- 响应 IFU
- 将最终的数据、异常信息传递给 IFU,完成取指流程。
- 根据请求是否为跨行,决定如何处理双行数据。
- 报告 TileLink 的 Corrupt 错误
- 对于每个端口,如果在当前周期 s2_fire 时检测到来自 L2 Cache 的数据 corrupt 错误,就将错误报告给 BEU。
MainPipe 的功能点和测试点
访问 DataArray 的单路
根据从 WayLookup 获取信息,包括路命中信息和 ITLB 查询结果还有 DataArray 当前的情况,决定是否需要从 DataArray 中读取数据。
- 访问 DataArray 的单路
- 当 WayLookup 中的信息表明路命中时,ITLB 查询成功,并且 DataArray 当前没有写时,MainPipe 会向 DataArray 发送读取请求,以获取数据。
- s0_hits 为高(一路命中),s0_itlb_exception 信号为零(ITLB 查询成功),toData.last.ready 为高(DataArray 没有正在进行的写操作)。
- toData.valid 信号为高,表示 MainPipe 向 DataArray 发出了读取请求。
- 不访问 DataArray(Way 未命中) ==会访问,但是返回数据无效==
- 当 WayLookup 中的信息表明路未命中时,MainPipe 不会向 DataArray 发送读取请求。
- s0_hits 为低表示缓存未命中
- toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。
- 不访问 DataArray(ITLB 查询失败)==会访问,但是返回数据无效==
- 当 ITLB 查询失败时,MainPipe 不会向 DataArray 发送读取请求。
- s0_itlb_exception 信号不为零(ITLB 查询失败)。
- toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。
- 不访问 DataArray(DataArray 正在进行写操作)
- 当 DataArray 正在进行写操作时,MainPipe 不会向 DataArray 发送读取请求。
- toData.last.ready 信号为低,表示 DataArray 正在进行写操作。
- toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。
Meta ECC 校验
将物理地址的标签部分与对应的 Meta 进行 ECC 校验,以确保 Meta 的完整性。
- 无 ECC 错误
- 当 waymask 全为 0(没有命中),则 hit_num 为 0 或 waymask 有一位为 1(一路命中),hit_num 为 1 且 ECC 对比通过(encodeMetaECC(meta) == code)
- s1_meta_corrupt 为假。
- 单路命中的 ECC 错误
- 当 waymask 有一位为 1(一路命中),ECC 对比失败(encodeMetaECC(meta) != code)
- s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。
- 多路命中
hit multi-way, must be an ECC failure
- 当 waymask 有两位及以上为 1(多路命中),视为 ECC 错误。
- s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。
- ECC 功能关闭
- 当奇偶校验关闭时(ecc_enable 为低),强制清除 s1_meta_corrupt 信号置位。
- 不管是否发生 ECC 错误,s1_meta_corrupt 都为假。
PMP 检查
- 将 S1 的物理地址 s1_req_paddr(i) 和指令 TlbCmd.exec 发往 PMP,判断取指是否合法。
- 防止非法地址,区分普通内存和 MMIO 内存。
- 没有异常
- s1_pmp_exception 为全零,表示没有 PMP 异常。
- 通道 0 有 PMP 异常
- s1_pmp_exception(0) 为真,表示通道 0 有 PMP 异常。
- 通道 1 有 PMP 异常
- s1_pmp_exception(1) 为真,表示通道 1 有 PMP 异常。
- 通道 0 和通道 1 都有 PMP 异常
- s1_pmp_exception(0) 和 s1_pmp_exception(1) 都为真,表示通道 0 和通道 1 都有 PMP 异常。
- 没有映射到 MMIO 区域
- s1_pmp_mmio(0) 和 s1_pmp_mergemmio(1) 都为假,表示没有映射到 MMIO 区域。
- 通道 0 映射到了 MMIO 区域
- s1_pmp_mmio(0) 为真,表示映射到了 MMIO 区域。
- 通道 1 映射到了 MMIO 区域
- s1_pmp_mmio(1) 为真,表示映射到了 MMIO 区域。
- 通道 0 和通道 1 都映射到了 MMIO 区域
- s1_pmp_mmio(0) 和 s1_pmp_mmio(1) 都为真,表示通道 0 和通道 1 都映射到了 MMIO 区域。
异常合并
- 将 s1_itlbmergeption 与 s1_pmp_exception 合并生成 s1_exception_out。
- ITLB 异常通常优先于 PMP 异常。merge
- 没有异常
- s1_exception_out 为全零,表示没有异常。
- 只有 ITLB 异常
- s1_exception_out 和 s1_itlb_exception 一致
- 只有 PMP 异常
- s1_exception_out 和 s1_pmp_exception 一致
- ITLB 与 PMP 异常同时出现
itlb has the highest priority, pmp next
- s1_exception_out 和 s1_itlb_exception 一致
MSHR 匹配和数据选择
- 检查当前的请求是否与 MSHR 中正在处理的缺失请求匹配。
- 判断 缓存组索引相同(s1_req_vSetIdx(i) == fromMSHR.bits.vSetIdx) ,物理标签相同 (s1_req_ptags(i) == fromMSHR.bits.blkPaddr);若匹配 MSHR 有效且没有错误(fromMSHR.valid && !fromMSHR.bits.corrupt),则优先使用 MSHR 中的数据
- 避免重复访问 Data SRAM,提升性能;当 MSHR 中已有重填结果时,可立即命中。
- 命中 MSHR
- MSHR 中已有正确数据时,S1 阶段能直接拿到
- s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 s1_bankMSHRHit(i),s1_data_is_from_MSHR(i) 为 true
- 未命中 MSHR
- MSHR 中存放的地址与当前请求不同,那么应该读取 SRAM 的数据
- s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false
- MSHR 数据 corrupt
- fromMSHR.bits.corrupt = true,那么 MSHR 将不匹配,应该读取 SRAM 的数据
- s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false
Data ECC 校验
在 S2 阶段,对从 S1 或 MSHR 获得的数据(如 s2_datas)进行 ECC 校验:
- 若 ECC 校验失败,则标记 s2_data_corrupt(i) = true。
- 若数据来自 MSHR,则不重复进行 ECC 校验(或忽略 corrupt)
- 无 ECC 错误
- s2_bank 全部没有损坏,bank 也选对了对应的端口和 bank,数据不来自 MSHR
- s2_data_corrupt(i) 为 false,没有 ECC 错误。
- 单 Bank ECC 错误
- s2_bank_corrupt(bank) 有一个为 true ,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR
- s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。
- 多 Bank ECC 错误
- s2_bank_corrupt(bank) 有两个或以上为 true,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR
- s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。
- ECC 功能关闭
- 当奇偶校验关闭时(ecc_enable 为低),强制清除 s2_data_corrupt 信号置位。
- 不管是否发生 ECC 错误,s2_data_corrupt 都为假。
冲刷 MetaArray
Meta 或者 Data ECC 校验错误时,会冲刷 MetaArray,为重取做准备。
- 只有 Meta ECC 校验错误
if is meta corrupt, clear all way (since waymask may be unreliable)
- 当 s1_meta_corrupt 为真时,MetaArray 的所有路都会被冲刷。
- toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。
- 只有 Data ECC 校验错误
if is data corrupt, only clear the way that has error
- 当 s2_data_corrupt 为真时,只有对应路会被冲刷。
- toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的对应路置位。
- 同时有 Meta ECC 校验错误和 Data ECC 校验错误
- 处理 Meta ECC 的优先级更高, 将 MetaArray 的所有路冲刷。
- toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。
监控 MSHR 匹配与数据更新
- 判断是否命中 MSHR
- 根据 MSHR 是否命中和 s1 阶段是否发射来更新 s2 的数据,s2 的命中状态和 l2 是否损坏
- MSHR 命中(匹配且本阶段有效)
- MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效
- s2_MSHR_match,s2_MSHR_hits 为高,s2_bankMSHRHit 对应 bank 为高
- s1_fire 无效时,s2_datas 更新为 MSHR 的数据,将 s2_data_is_from_MSHR 对应位置位,s2_hits 置位,清除 s2_data_corrupt,l2 的 corrupt 更新为 fromMSHR.bits.corrupt
- s1_fire 有效时,s2_datas 为 s1_datas 的数据,将 s2_data_is_from_MSHR 对应位置为 s1 的 s1_data_is_from_MSHR,s2_hits 置为 s1_hits,清除 s2_data_corrupt,l2 的 corrupt 为 false
- MSHR 未命中
- MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效,至少有一个未达成
- s2_MSHR_hits(i) = false,S2 不会更新 s2_datas,继续保持原先 SRAM 数据或进入 Miss 流程。
Miss 请求发送逻辑和合并异常
- 通过计算 s2_should_fetch(i) 判断是否需要向 MSHR 发送 Miss 请求:
- 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) || s2_data_corrupt(i)) 时,需要请求重新获取。
- 若端口存在异常或处于 MMIO 区域,则不发送 Miss 请求。
- 使用 Arbiter 将多个端口的请求合并后发送至 MSHR。
- 通过 s2_has_send(i) 避免重复请求。
- 将 S2 阶段已有的 ITLB/PMP 异常(s2_exception)与 L2 Cache 报告的 s2_l2_corrupt(i)(封装后为 s2_l2_exception(i))进行合并。
- 未发生 Miss
- 当 s2_hits(i) 为高(s2 已经命中),s2 的 meta 和 data 都没有错误,s2 异常,处于 mmio 区域
- 以上条件至少满足一个时,s2_should_fetch(i) 为低,表示不发送 Miss 请求。
- 单口 Miss
- 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) || s2_data_corrupt(i)),端口不存在异常且未处于 MMIO 区域时,会向 MSHR 发送 Miss 请求。
- toMSHRArbiter.io.in(i).valid = true ,Arbiter 只发送一条 Miss 请求。
- 双口都需要 Miss
- 同上,但是两个端口都满足 s2_should_fetch 为高的条件。
- toMSHRArbiter.io.in(0).valid、toMSHRArbiter.io.in(1).valid 均为 true,Arbiter 根据仲裁顺序依次发出请求。
- 重复请求屏蔽
- 当 s1_fire 为高,表示可以进入 s2 阶段,那么 s2 还没有发送 s2_has_send(i) := false.B
- 如果已经有请求发送了,那么对应的 toMSHRArbiter.io.in(i).fire 为高,表示对应的请求可以发送,s2_has_send(i) := true。
- 此时再次发送,toMSHRArbiter.io.in(i).valid 为低,表示发送失败。
- 仅 ITLB/PMP 异常
- S1 阶段已记录了 ITLB 或 PMP 异常,L2 corrupt = false。
- 2_exception_out 仅保留 ITLB/PMP 异常标记,无新增 AF 异常。
- 仅 L2 异常
- S2 阶段 s2_l2_corrupt(i) = true,且无 ITLB/PMP 异常。
- s2_exception_out(i) 表示 L2 访问错误(AF)。
- ITLB + L2 同时出现
- 同时触发 ITLB 异常和 L2 corrupt。
- s2_exception_out 优先保留 ITLB 异常类型,不被 L2 覆盖。
- s2 阶段取指完成
- s2_should_fetch 的所有端口都为低,表示需要取指,那么取指完成
- s2_fetch_finish 为高
响应 IFU
- 若当前周期 S2 成功发射(s2_fire = true)且数据获取完毕(s2_fetch_finish),则把数据、异常信息、物理地址等打包到 toIFU.bits 输出。
- 若为双行请求(s2_doubleline = true),也会向 IFU 发送第二路的信息(地址、异常)。
- 正常命中并返回
- 不存在任何异常或 Miss,s2 命中,s2 阶段取指完成,外部的 respStall 停止信号也为低 。
- toIFU.valid = true,toIFU.bits.data 为正确的 Cacheline 数据,toIFU.bits.exception、pmp_mmio、itlb_pbmt = none。
- 异常返回
- 设置 ITLB、PMP、或 L2 corrupt 异常。
- toIFU.bits.exception(i) = 对应异常类型,pmp_mmio、itlb_pbmt 根据是否有对应的异常设置为 true。
- 跨行取指
- s2_doubleline = true,同时检查第一路、第二路返回情况。
- toIFU.bits.doubleline = true。
- 若第二路正常,toIFU.bits.exception(1) = none;若第二路异常,则 exception(1) 标记相应类型。
- pmp_mmio、itlb_pbmt 类似。
- RespStall
- 外部 io.respStall = true,导致 S2 阶段无法发射到 IFU。
- s2_fire = false,toIFU.valid 也不拉高,S2 保持原状态等待下一拍(或直到 respStall 解除)。
L2 Corrupt 报告
- 当检测到 L2 Cache 返回的 corrupt 标记时(s2_l2_corrupt(i) = true),在 S2 完成发射后额外向外部错误接口 io.errors(i) 报告。
- 与 Data ECC 或 Meta ECC 不同,L2 corrupt 由 L2 自己报告给 BEU,这里不需要再次报告给 beu。
- L2 Corrupt 单路
- s2 阶段准备完成可以发射(s2_fire 为高),s2_MSHR_hits(0)和 fromMSHR.bits.corrupt 为高
- s2_l2_corrupt(0) = true,io.errors(0).valid = true,io.errors(0).bits.source.l2 = true。
- 双路同时 corrupt
- 端口 0 和端口 1 都从 L2 corrupt 数据中获取。
- s2_l2_corrupt 均为 true,发射后分别报告到 io.errors(0) 和 io.errors(1)。
刷新机制
- io.flush:外部的全局刷新信号,它用于指示整个流水线需要被冲刷(清空)。
- s0_flush: S0 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S0 阶段的刷新操作。
- s1_flush: S1 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S1 阶段的刷新操作。
- s2_flush: S2 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S2 阶段的刷新操作。
- 全局刷新
- io.flush 被激活时,流水线的各个阶段(S0, S1 和 S2)都能正确响应并执行刷新操作。
- io.flush = true。
- s0_flush, s1_flush, s2_flush = true。
- S0 阶段刷新
- s0_flush = true。
- s0_fire = false。
- S1 阶段刷新
- s1_flush = true。
- s1_valid, s1_fire = false。
- S2 阶段刷新
- s2_flush = true。
- s2_valid, toMSHRArbiter.io.in(i).valid , s2_fire = false
2.5 - MissUnit
子模块:FIFO
- 一个先入先出的循环队列,目前仅在 MissUnit 中有使用,作为优先队列 priorityFIFO。
- 按照在 MissUnit 中的实例化,pipe 是默认值 false,hasflush 是 true。
- 队列的指针都是环形的,分为入队指针(写指针,ent_ptr)和出队指针(读指针,deq_ptr),记录读和写的位置。
- 两个指针都有对应的 flag 位,当指针超过队列大小时,flag 位会翻转,用以判断是否已经循环。
- 在入队、出队对应的 fire(valid && ready) 信号有效时,移动对应的指针。
FIFO 的功能点和测试点
入队操作
- 队未满,正常入队
- 当队列未满,且空位不小于一时,可以正常入队,如果从零号位开始入队到最大容量,入队指针的 flag 不会翻转。
- io.enq.fire 为高有效,regFiles(enq_ptr.value) = io.enq.bits,enq_ptr.value+1 入队指针移动,入队指针标记位不翻转。
- 重复以上操作至队满。
- 队未满,入队后标记位翻转
- 当队未满,但是空位却是靠近队尾时,入队一位后就到达了队头,入队指针的 flag 会翻转。
- 队列的容量为 10,入队指针指向 9,队未满。此时如果 io.enq.fire 为高,则 regFiles(9) = io.enq.bits,enq_ptr.value+1(循环队列,加完后 enq_ptr.value=0)入队指针移动,入队指针标记位翻转。
- 队满,入队就绪信号为低,无法入队
- 当队满时,(enq_ptr.value === deq_ptr.value) && (enq_ptr.flag ^ deq_ptr.flag) 为高,io.enq.ready 为低,io.enq.fire 为低无效。
- 此时入队,入队指针的 value 和 flag 不变。
出队操作
- 队非空,正常出队
- 当队列非空时,可以正常出队,如果出队指针不经过最大容量位置,出队指针的 flag 不会翻转。
- io.deq.fire 为高有效,io.deq.bits = regFiles(deq_ptr.value),deq_ptr.value+1 出队指针移动,出队指针标记位不翻转。
- 队非空,出队后标记位翻转
- 当队非空,但是出队指针是靠近队尾时,出队一位后就到达了队头,出队指针的 flag 会翻转。
- 队列的容量为 10,出队指针指向 9,队非空。此时如果 io.deq.fire 为高,则 io.deq.bits = regFiles(9),deq_ptr.value+1(循环队列,加完后 deq_ptr.value=0)出队指针移动,出队指针标记位翻转。
- 队空,出队有效信号为低,无法出队
- 当队空时,enq_ptr === deq_ptr 为高,io.deq.valid 为低,io.deq.fire 为低无效。
- 此时出队,出队指针的 value 和 flag 不变。
刷新清空操作
- flush 清空
- 当刷新信号有效时,重置出队和入队的指针和标记位,清空队列。
- 当 flush 为高时,deq_ptr.value=0,enq_ptr.value=0,deq_ptr.flag=false,enq_ptr.flag=false,empty=true,full=false。
MissUnit

MissUnit 结构
- 接收并管理多个 Miss 请求
- 处理来自 Fetch 和 Prefetch 的 Miss 请求。
- 将这些请求分派给适当数量的 MSHR 进行排队和状态管理。
- 管理 MSHR
- ICacheMissUnit 使用多个 MSHR 来跟踪和管理未完成的缓存未命中请求。为了防止 flush 时取指 MSHR 不能完全释放,设置取指 MSHR 的数量为 4,预取 MSHR 的数量为 10。采用数据和地址分离的设计方法,所有的 MSHR 共用一组数据寄存器,在 MSHR 只存储请求的地址信息、状态等信息。
- 接收来自 MainPipe 的取指请求和来自 IPrfetchPipe 的预取请求,取指请求只能被分配到 fetchMSHR,预取请求只能分配到 prefetchMSHR,入队时采用低 index 优先的分配方式。
- 在入队的同时对 MSHR 进行查询,如果请求已经在 MSHR 中存在,就丢弃该请求,对外接口仍表现 fire,只是不入队到 MSHR 中。==在入队时向 Replacer 请求写入 waymask==。当请求完成后,MSHR 会被释放,以便处理新的请求。
- 通过 TileLink 协议与 L2 缓存进行通信,发送获取缓存块的请求(mem_acquire),并接收 L2 缓存的响应(mem_grant)。
- 当到 L2 的总线空闲时,选择 MSHR 表现进行处理,整体 fetchMSHR 的优先级高于 prefetchMSHR,只有没有需要处理的 fetchMSHR,才会处理 prefetchMSHR。
- 对于 fetchMSHR,采用低 index 优先的优先级策略,因为同时最多只有两个请求需要处理,并且只有当两个请求都处理完成时才能向下走,所有 fetchMSHR 之间的优先级并不重要。
- 对于 prefetchMSHR,考虑到预取请求之间具有时间顺序,采用先到先得的优先级策略,在入队时通过一个 FIFO 记录入队顺序,处理时按照入队顺序进行处理。
- 通过状态机与 Tilelink 的 D 通道进行交互,到 L2 的带宽为 32byte,需要分 2 次传输,并且不同的请求不会发生交织,所以只需要一组寄存器来存储数据。
- 当一次传输完成时,根据传输的 id 选出对应的 MSHR,从 MSHR 中读取地址、掩码等信息,将相关信息写入 SRAM,同时将 MSHR 释放。
- 向 MetaArray 和 DataArray 发送写请求,向 MainPipe 发送响应
- 当数据传回后,MissUnit 根据相应的替换策略信息(victim way),将新数据写回 ICache 的 SRAM(Meta/Data) 。
- 同时向取指端(或预取端)返回“Miss 已完成”的响应,包括:写入了哪一路(way)、实际数据以及可能的校验信息(如 corrupt 标记等)。
- 处理特殊情况(如 flush、fencei、数据损坏等)
- 遇到 Flush 或 fence.i 等指令时,MissUnit 可以终止或跳过某些 Miss 请求的写回,从而保证不在无效或过期的情况下写入缓存。
- 数据若出现 corrupt(部分拍损坏),也会在写回或发给前端时进行特殊处理或标记。
过程:
- fetch_req 和 prefetch_req 分别先经过 DeMultiplexer (Demux),把请求分发给对应数量的 MSHR。fetch 的 MSHR 和 prefetch 的 MSHR 分成两组,分别处理取指和预取请求。
- 每个 MSHR 内部会记录当前 Miss 请求的地址、索引、是否已经发出 acquire 等状态。当有其它相同的 miss 请求进来时,可以直接 “ hit MSHR ” 而不用重复创建新的请求。
- 对于 fetchMSHR,采用低 index 优先的优先级策略;对于 prefetchMSHR,采用先到先得的优先级策略,在入队 prefetchMSHR 前通过一个 priorityFIFO.记录入队顺序,处理时按照入队顺序进行处理。
- fetchMSHR 发出的请求与 prefetchArb 选出的 prefetchMSHR 通过 acquireArb 合并后,通过 mem_acquire 发送给下一级或外部存储。
- mem_grant 表示对这一条 Miss 请求的返回数据。需要分多个 beat 收集,直到收满一个 Cacheline。
- 收集完 Cacheline 数据后,会根据对应 MSHR 的信息向 metaArray 和 dataArray 发起写操作 (meta_write, data_write),同时向取指端 (fetch_resp) 发送补全后的数据和标记 (waymask 等)。
- 如果发生 flush 或 fencei,在未发出请求前,请求会被无效化;请求被发出后,会持续阻止新请求进入,已经发出的访问最终会将返回过程走完,但收到的响应并不会回复给 MainPipe 和 IPrefetchPipe,也不会写给 MetaArray 和 DataArray。
Demultiplexer 类 grant:选择第一个 ready(能写)的 mshr,写进去(第 0 到 n 个端口,前面有 ready 的。比如 grant=seq(false,true),grant(1)为 true,表示 1 端口前面有一个 ready 的端口(0 端口)) io.out(i).valid:前 i-1 个 mshr 没有 ready 的,输入的写有效。 io.in.ready := grant.last || io.out.last.ready,给 MissUnit 的 ready 信号有一个有效,那么 MissUnit 给 MSHR 的 ready 信号就有效。
MissUnit 的功能点和测试点
处理取指缺失请求
处理来自 MainPipe 的取指单元的缓存缺失请求,将缺失请求分发到多个 Fetch MSHR 中的一个,避免重复请求。 低索引的请求优先处理。
-
接受新的取指请求
- 当新的 fetch miss 与 MSHR 中的已有请求不重复时(通过 io.fetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Fetch MSHR 中。
- 当有新的取指缺失请求到达时(io.fetch_req.valid 为高),且没有命中已有的 MSHR(fetchHit 为低),io.fetch_req.ready 应为高,表示可以接受请求。
- io.fetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。
-
处理已有的取指请求
- 当已有取指缺失请求到达时(io.fetch_req.valid 为高),且命中已有的 MSHR(fetchHit 为高),io.fetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。
- fetchDemux.io.in.valid 应为低,fetchDemux.io.in.fire 为低,表示没有新的请求被分发到 MSHR。
-
低索引的请求优先进入 MSHR
- Fetch 的请求会通过 fetchDemux 分配到多个 Fetch MSHR,fetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。
- 当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。
处理预取缺失请求
与 Fetch Miss 类似,但走另一些 MSHR(Prefetch MSHR)。
-
接受新的预取请求
- 当新的 prefetch miss 与 MSHR 中的已有请求不重复时(通过 io.prefetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Prefetch MSHR 中。
- 当有新的预取缺失请求到达时(io.prefetch_req.valid 为高),且没有命中已有的 MSHR(prefetchHit 为低),io.prefetch_req.ready 应为高,表示可以接受请求。
- io.prefetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。
-
处理已有的预取请求
- 当已有预取缺失请求到达时(io.prefetch_req.valid 为高),且命中已有的 MSHR(prefetchHit 为高),io.prefetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。
- prefetchDemux.io.in.valid 应为低,prefetchDemux.io.in.fire 为低,表示请求被接受但未分发到新的 MSHR。
-
低索引的请求优先进入 MSHR
- Prefetch 的请求会通过 prefetchDemux 分配到多个 Prefetch MSHR,prefetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。
- 当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。
-
先进入 MSHR 的优先进入 prefetchArb
- 从 prefetchDemux 离开后,请求的编号会进入 priorityFIFO,priorityFIFO 会根据进入队列的顺序排序,先进入队列的请求会先进入 prefetchArb。
- prefetchDemux.io.in.fire 为高,并且 prefetchDemux.io.chosen 有数据时,将其编号写入 priorityFIFO。
- 在 priorityFIFO 中有多个编号时,出队的顺序和入队顺序一致。
- 检查 priorityFIFO.io.deq.bit 中的数据即可。
MSHR 管理与查找
-
MSHR 查找命中逻辑
- 当新的请求到来时,能够正确查找所有 MSHR,判断请求是否命中已有 MSHR。
- 当新的请求(取指或预取)到来时,系统遍历所有 MSHR,根据所有 MSHR 的查找信号 allMSHRs(i).io.lookUps(j).hit,检查请求是否已经存在于某个 MSHR 中。
- 如果命中,则对应的 fetchHit 或 prefetchHit 为高。
- 对于 prefetchHit 为高,还有一种情况:预取的物理块地址和组索引与取指的相等((io.prefetch_req.bits.blkPaddr === io.fetch_req.bits.blkPaddr) && (io.prefetch_req.bits.vSetIdx === io.fetch_req.bits.vSetIdx))并且有取指请求 io.fetch_req.valid 有效时,也算命中
-
MSHR 状态的更新与释放
- 当请求完成后,也就是来自内存总线的响应完成(D 通道接收完所有节拍),MSHR 能够正确地释放(清除其有效位),以便接收新的请求。
- TileLink D 通道返回的 source ID ,即 io.mem_grant.bits.source。
- 无效化信号 allMSHRs(i).io.invalid 为高,对应的 MSHR 的有效位 allMSHRs(i).valid 变为低
acquireArb 仲裁
预取和取指的 acquire 都会发送给 acquireArb,acquireArb 会选择一个 acquire 发送给 mem_acquire。 acquireArb 使用 chisel 自带的 Arbiter 实现,Arbiter 使用固定优先级仲裁,优先级从编号 0 开始,编号越小优先级越高。
- acquireArb 仲裁
- acquireArb 会选择一个 acquire 发送给 mem_acquire。
- 当有多个 MSHR 同时发出请求时,acquireArb 会根据优先级进行仲裁,选择优先级最高的 MSHR 发送请求。
- 取指请求总是在 0-3 号,预取请求直接在最后一号,所以取指请求优先级高于预取请求。
- 当取指 acquire 和预取 acquire 同时发出时,fetchMSHRs(i).io.acquire 和 prefetchMSHRs(i).io.acquire 都有效,仲裁结果 acquireArb.io.out 应该和 fetchMSHRs(i).io.acquire 一致。
Grant 数据接收与 Refill
在收到 TileLink D 通道数据时收集整行
- 累计 beat 数(readBeatCnt),直到完成一整行 (last_fire)
- 记录 corrupt 标志
- 将完成的请求映射回对应的 MSHR (id_r = mem_grant.bits.source)
- 正常完整 Grant 流程,readBeatCnt 为 0 时
- readBeatCnt 初始为 0,refillCycles - 1 也为 0。
- io.mem_grant.valid 为高(因为 io.mem_grant.ready 默认为高,所以 io.mem_grant.fire 为高只需要 io.mem_grant.valid 为高)且 io.mem_grant.bits.opcpde(0)为高。
- 此时 respDataReg(0)= io.mem_grant.bits.data
- readBeatCnt 加一为 1。
- 正常完整 Grant 流程,readBeatCnt 为 1 时
- io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高。
- 此时 respDataReg(1)= io.mem_grant.bits.data
- readBeatCnt 重置回 0。
- last_fire 为高。
- 下一拍 last_fire_r 为高,id_r=io.mem_grant.bits.source。
- 正常完整 Grant 流程,last_fire_r 为高
- last_fire_r 为高,并且 id_r 为 0-13 中的一个。
- 对应的 fetchMSHRs 或者 prefetchMSHRs 会被无效,也就是 fetchMSHRs_i 或 prefetchMSHRs_i-4 的 io_invalid 会被置高。
- Grant 带有 corrupt 标志
- io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高,则 corrupt_r 应为高。
- 如果 io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高中有一个不满足,且此时 last_fire_r 为高,则 corrupt_r 重置为低。
替换策略更新 (Replacer)
MissUnit 在发出 Acquire 请求时,还会将本次选中的 victim way 对应的索引告诉 io.victim,让替换策略更新其记录(替换策略采用 PLRU) 只有当 Acquire 真正“fire”时,才说明成功替换,replacer 需要更新状态
- 正常替换更新
- 当 io.mem.acquire.ready & acquireArb.io.out.valid 同时为高,也就是 acquireArb.io.out.fir 为高时,io.victim.vSetIdx.valid 也为高。
- io.victim.vSetIdx.bits = 当前 MSHR 请求的 acquireArb.io.out.bits.vSetIdx。
- 生成 waymask
- 根据从 L2 返回的 mshr_resp 中 mshr_resp.bits.way 生成 waymask 信息。
- 返回的 mshr_resp.bits.way 有 16 位,通过独热码生成一位掩码信息,waymask 表示其中哪一路被替换。
- 生成的 waymask 应该和 mshr_resp.bits.way 一致。
写回 SRAM (Meta / Data)
在一条 Miss Request refill 完成时,将新得到的 Cache line 写到 ICache。 生成 io.meta_write 和 io.data_write 的请求,带上 waymask, tag, idx, data 。 生成 io.meta_write.valid 和 io.data_write.valid 信号。
- 生成 io.meta_write.valid 和 io.data_write.valid 信号
- 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。
- 并且此时没有硬件刷新信号和软件刷新信号,也就是 io.flush 和 io.fencei 为低。 在等待 l2 响应的过程中,没有刷新信号
- 也没有数据 corrupt,即 corrupt_r 为低。
- 那么 io.meta_write.valid 和 io.data_write.valid 均为高。
- 正常写 SRAM
- io.meta_write.bits 的 virIdx、phyTag、waymask、bankIdx、poison 应该正常更新
- io.data_write.bits 的 virIdx、data、waymask、bankIdx、poison 应该正常更新
向 mainPipe/prefetchPipe 发出 Miss 完成响应(fetch_resp)
在完成 refill 后,无论是否要真正写阵列,都会向取指端发送“Miss 请求完成” 更新 io.fetch_resp.valid 和 fetch_resp.bits。
- 正常 Miss 完成响应
- 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。
- 无论此时是否有硬件刷新信号和软件刷新信号, io.fetch_resp.valid 都为高,说明可向取指端发送响应。
- io.fetch_resp.bits 中的数据更新:
- io.fetch_resp.bits.blkPaddr = mshr_resp.bits.blkPaddr
- io.fetch_resp.bits.vSetIdx = mshr_resp.bits.vSetIdx
- io.fetch_resp.bits.waymask = waymask
- io.fetch_resp.bits.data = respDataReg.asUInt
- io.fetch_resp.bits.corrupt = corrupt_r
处理 flush / fencei
一旦收到 io.flush 或 io.fencei 时,对未发射的请求可立即取消,对已经发射的请求在拿到数据后也不写 SRAM。
- MSHR 未发射前 fencei
- 如果 MSHR 还没有通过 io.acquire.fire 发出请求,就应立即取消该 MSHR(mshr_resp.valid= false),既不发出请求,也不要写 SRAM。
- 当 io.fencei 为高时,fetchMSHRs 和 prefetchMSHRs 的 io.req.ready 和 io.acquire.valid 均为低,表示请求不发射。
- MSHR 未发射前 flush
- 由于 fetchMSHRs 的 io.flush 被直接设置为 false,所以 io.flush 对 fetchMSHRs 无效,但是对 prefetchMSHRs 有效。
- 当 io.flush 为高时,只能发射 fetchMSHRs 的请求。
- MSHR 已发射后 flush/fencei
- 已经发射了请求,之后再有刷新信号,那么等数据回来了但不写 SRAM。
- 在发射后,io.flush/io.fencei 为高时,等待数据回来,但是写 SRAM 的信号,write_sram_valid、io.meta_write.valid 和 io.data_write.valid 均为低,表示不写 SRAM。
- 对于 response fetch 无影响。
2.6 - WayLookup
WayLookup

WayLookup 读写结构

WayLookup 更新结构
- 内部是 FIFO 环形队列结构。暂存 IPrefetchPipe 查询 MetaArray 和 ITLB 得到的元数据,以备 MainPipe 使用。同时监听 MSHR 写入 SRAM 的 cacheline,对命中信息进行更新。
- 通过 readPtr 和 writePtr 来管理读写位置。当有 flush 信号时,读写指针都会被重置。当写入数据时,写指针递增;读取时,读指针递增。需要处理队列的空和满的情况,empty 是读指针等于写指针,而 full 则是两者的值相同且标志位不同。
- 处理 GPF 的部分,有一个 gpf_entry 寄存器,存储 GPF 的相关信息。当写入的数据包含 GPF 异常时,需要将信息存入 gpf_entry,并记录当前的写指针位置到 gpfPtr。当读取的时候,如果当前读指针的位置与 gpfPtr 匹配,并且 gpf_entry 有效,那么就将 GPF 信息一并输出。
- IPrefetchPipe 向其写入 WayLookupInfo 信息(包括 vSetIdx,waymask,ptag,itlb_exception,itlb_pbmt,meta_codes,gpaddr,isForVSnonLeafPTE)。
- 写入前,需要考虑队列是否已满,以及是否有 GPF 阻塞。如果有 GPF 信息待读取且未被处理,则写入需要等待,防止覆盖 GPF 信息。写入时,如果数据中包含 GPF 异常,就将信息存入 gpf_entry,并更新 gpfPtr。
- MainPipe 从其读出 WayLookupInfo 信息。
- 在读取上,有两种情况:当队列为空但有写请求时,可以直接将写的数据旁路(bypass)给读端口;否则就从 entries 数组中读取对应读指针的数据。同时,如果当前读的位置存在 GPF 信息,就将 GPF 信息一起输出,并在读取后清除有效位。
- 允许 bypass(当队列为空但有写请求时,可以直接将写的数据旁路给读端口),为了不将更新逻辑的延迟引入到 DataArray 的访问路径上,在 MSHR 有新的写入时禁止出队,MainPipe 的 S0 流水级也需要访问 DataArray,当 MSHR 有新的写入时无法向下走,所以该措施并不会带来额外影响。
- MissUnit 向其写入命中信息。
- 若是命中则将 waymask 更新 ICacheMissResp 信息(包括 blkPaddr,vSetIdx,waymask,data,corrupt)且 meta_codes 也更新,否则 waymask 清零。更新逻辑与 IPrefetchPipe 中相同,见 IPrefetchPipe 子模块文档中的“命中信息的更新”一节。
GPaddr 省面积机制
由于 gpaddr
仅在 guest page fault 发生时有用,并且每次发生 gpf 后前端实际上工作在错误路径上,后端保证会送一个 redirect(WayLookup flush)到前端(无论是发生 gpf 前就已经预测错误/发生异常中断导致的;还是 gpf 本身导致的),因此在 WayLookup 中只需存储 reset/flush 后第一个 gpf 有效时的 gpaddr。对双行请求,只需存储第一个有 gpf 的行的 gpaddr。
在实现上,把 gpf 相关信号(目前只有 gpaddr
)与其它信号(paddr
,etc.)拆成两个 bundle,其它信号实例化 nWayLookupSize 个,gpf 相关只实例化一个寄存器。同时另用一个 gpfPtr
指针。总计可以节省$(\text{nWayLookupSize}\times2-1)\times \text{GPAddrBits} - \log_2{(\text{nWayLookupSize})} - 1$bit 的寄存器。
当 prefetch 向 WayLookup 写入时,若有 gpf 发生,且 WayLookup 中没有已经存在的 gpf,则将 gpf/gpaddr 写入 gpf_entry
寄存器,同时将 gpfPtr
设置为此时的 writePtr。
当 MainPipe 从 WayLookup 读取时,若 bypass,则仍然直接将 prefetch 入队的数据出队;否则,若 readPtr === gpfPtr
,则读出 gpf_entry;否则读出全 0。
需要指出:
- 考虑双行请求,
gpaddr
只需要存一份(若第一行发生 gpf,则第二行肯定也在错误路径上,不必存储),但 gpf 信号本身仍然需要存两份,因为 ifu 需要判断是否是跨行异常。 readPtr===gpfPtr
这一条件可能导致 flush 来的比较慢时readPtr
转了一圈再次与gpfPtr
相等,从而错误地再次读出 gpf,但如前所述,此时工作在错误路径上,因此即使再次读出 gpf 也无所谓。- 需要注意一个特殊情况:一个跨页的取指块,其 32B 在前一页且无异常,后 2B 在后一页且发生 gpf,若前 32B 正好是 16 条 RVC 压缩指令,则 IFU 会将后 2B 及对应的异常信息丢弃,此时可能导致下一个取指块的
gpaddr
丢失。需要在 WayLookup 中已有一个未被 MainPipe 取走的 gpf 及相关信息时阻塞 WayLookup 的入队(即 IPrefetchPipe s1 流水级),见 PR#3719。
WayLookup 的功能点和测试点
刷新操作
- 接收到全局刷新刷新信号 io.flush 后,读、写指针和 GPF 信息都被重置。
- 刷新读指针
- io.flush 为高时,重置读指针。
- readPtr.value 为 0, readPtr.flag 为 false。
- 刷新写指针
- io.flush 为高时,重置写指针。
- writePtr.value 为 0, writePtr.flag 为 false。
- 刷新 GPF 信息
- io.flush 为高时,重置 GPF 信息。
- gpf_entry.valid 为 0, gpf_entry.bits 为 0。
读写指针更新
- 读写信号握手完毕之后(io.read.fire/io.write.fire 为高),对应指针加一。
- 因为是在环形队列上,所以超过队列大小后,指针会回到队列头部。
- 读指针更新
- 当 io.read.fire 为高时,读指针加一。
- readPtr.value 加一。
- 如果 readPtr.value 超过环形队列的大小,readPtr.flag 会翻转。
- 写指针更新
- 当 io.write.fire 为高时,写指针加一。
- writePtr.value 加一。
- 如果 writePtr.value 超过环形队列的大小,writePtr.flag 会翻转。
更新操作
- MissUnit 处理完 Cache miss 后,向 WayLookup 写入命中信息,也就是 update 操作。
- 情况分为两种:
- 命中:更新 waymask 和 meta_codes。
- 未命中:重置 waymask。
- 命中更新
- MissUnit 返回的更新信息和 WayLookup 的信息相同时,更新 waymask 和 meta_codes。
- vset_same 和 ptag_same 为真。
- waymask 和 meta_codes 更新。
- hits 对应位为高。
- 未命中更新
- vset_same 和 way_same 为真。
- waymask 清零。
- hit 对应位为高。
- 不更新
- 其他情况下不更新。
- vset_same 为假或者 ptag_same 和 way_same 都为假。
- hits 对应位为低。
读操作
- 读操作会根据读指针从环形队列中读取信息。
- 如果达成了绕过条件,优先绕过。
- Bypass 读
- 队列为空,并且 io.write.valid 写有效时,可以直接读取,而不经过队列。
- empty 和 io.write.valid 都为真。
- io.read.bits = io.write.bits
- 读信号无效
- 队列为空(readPtr === writePtr)且写信号 io.write.valid 为低。
- io.read.valid 为低,读信号无效。
- 正常读
- 未达成绕过条件(empty 和 io.write.valid 至少有一个为假)且 io.read.valid 为高。
- 从环形队列中读取信息。
- io.read.bits.entry = entries(readPtr.value)
- gpf 命中
- io.read.valid 为高,可以读。
- 当 gpf_hits 为高时,从 GPF 队列中读取信息。
- io.read.bits.gpf = gpf_entry.bits
- gpf 命中且被读取
-
io.read.valid 为高,可以读。
also clear gpf_entry.valid when it’s read
-
当 gpf 命中且被读取其时(io.read.fire 为高),gpf_entry.valid 会被置为 0。
- gpf 未命中
- io.read.valid 为高,可以读。
- io.read.bits.gpf 清零。
写操作
- 写操作会根据写指针从环形队列中读取信息。
- 如果有 gpf 停止,就会停止写。
- gpf 停止
if there is a valid gpf to be read, we should stall write
- gpf 队列数据有效,并且没有被读取或者没有命中,就会产生 gpf 停止,此时写操作会被停止。
- gpf_entry.valid && !(io.read.fire && gpf_hit) 为高时,写操作会被停止(io.write.ready 为低)。
- 写就绪无效
- 当队列为满((readPtr.value === writePtr.value) && (readPtr.flag ^ writePtr.flag))或者 gpf 停止时,写操作会被停止。
- (io.write.ready 为低)
- 正常写
- 当 io.write.valid 为高时(没满且没有 gpf 停止),写操作会被执行。
- 正常握手完毕 io.write.fire 为高。
- 写信息会被写入环形队列。
- entries(writePtr.value) = io.write.bits.entry。
有 ITLB 异常的写
- 前面与正常写相同,只不过当写信息中存在 ITLB 异常时,会更新 gpf 队列和 gpf 指针。
- 此时如果已经被绕过直接读取了,那么就不需要存储它了。
-
- 被绕过直接读取了
- can_bypass 和 io.read.fire 都为高。
- gpf_entry.valid 为 false。
- gpf_entry.bits = io.write.bits.gpf
- gpfPtr = writePtr
-
- 没有被绕过直接读取
- can_bypass 为低。
- gpf_entry.valid 为 true。
- gpf_entry.bits = io.write.bits.gpf
- gpfPtr = writePtr
-
3 - IFU
本文档参考香山IFU设计文档写成
本文档撰写的内容截至[c670557]
请注意,本文档撰写的测试点仅供参考,如能补充更多测试点,最终获得的奖励可能更高!
IFU说明文档
文档概述
本文档描述IFU的功能,并根据功能给出测试点参考,方便测试的参与者理解测试需求,编写相关测试用例。
为方便验证参与者,本文档中还额外给出了整体框图和流水级的示意图,以及各个rtl接口的详细说明。此外,本文档还给出了两个时序示例。
术语说明
名称 | 描述 |
---|---|
RVC(RISC-V Compressed Instructions) | RISC-V 手册"C"扩展规定的 16 位长度压缩指令 |
RVI(RISC-V Integer Instructions) | RISC-V 手册规定的 32 位基本整型指令 |
IFU(Instruction Fetch Unit) | 取指令单元 |
FTQ(Fetch Target Queue) | 取指目标队列 |
ICache(L1 Instruction Cache) | 一级指令缓存 |
IBuffer(Instruction Buffer) | 指令缓冲 |
CFI(Control Flow Instruction) | 控制流指令 |
ITLB(Instruction Translation Lookaside Buffer) | 指令地址转译后备缓冲器 |
InstrUncache(Instruction Ucache Module) | 指令 MMIO 取指处理单元 |
整体框图
以下是IFU的架构简图:
流水级示意图
香山的IFU一共分为5个stage。
F0 stage:接收FTQ请求,同时告诉FTQ自己已经ready了,同时,FTQ也会通知ICache准备数据。此外,这一阶段还会接受重定向信息。
F1 stage:从FTQ请求中先计算出每个指令的pc,half_pc,并计算cut_ptr(这是后续将icache返回的指令码进行切分的依据)
F2 stage:从icache获取响应数据(缓存行)并校验,提取出异常信息(包括页错误、访问错误、mmio信息);生成预测到的指令范围(但这并不是一个数字,而是一个用多位表示的bool数组,该位为1表示这一指令在预测块范围内);从缓存行中,利用上一阶段求出的cut_ptr切分出17×2的初步指令码,最后进行预译码和指令扩展。
F3 stage:这一阶段主要是对译码阶段的结果进行预检查,对RVC指令进行扩展,以及MMIO状态下的处理逻辑,并向IBuffer写指令码和前端信息
WB(写回)stage:将预检查的结果写回FTQ。根据预检查结果判断是否进行内部冲刷。
以下是一张示意图:
子模块列表
子模块 | 描述 |
---|---|
PreDecoder | 预译码模块 |
F3Predecoder | F3阶段预译码模块 |
RVCExpander | RVC指令扩展模块 |
PredChecker | 预检查模块 |
FrontendTrigger | 前端断点模块 |
IFU模块功能说明
FTQ 将预测块请求分别发送到 ICache 和 IFU 模块,IFU 等到来自 ICache 返回至多两个缓存行的指令码后,进行切分产生取指令请求范围限定的初始指令码,并送到预译码器进行预译码下一拍根据预译码信息修正有效指令范围,同时进行指令码扩展并将指令码及其他信息发送给 IBuffer 模块。当 ICache 查询地址属性发现是 MMIO 地址空间时,IFU 需要将地址发送给 MMIO 处理单元取指令,这个时候处理器进入多周期顺序执行模式,IFU 阻塞流水线直到收到来自 ROB 的提交信号时,IFU 才允许下一个取指令请求的进行,同时 IFU 需要对跨页的 MMIO 地址空间 32 位指令做特殊处理(重发机制)。
1. 接收FTQ取指令请求(F0流水级)
在F0流水级,IFU接收来自FTQ以预测块为单位的取指令请求。请求内容包括预测块起始地址、起始地址所在cache line的下一个cache line开始地址、下一个预测块的起始地址、该预测块在FTQ里的队列指针、该预测块有无taken的CFI指令(控制流指令)和该taken的CFI指令在预测块里的位置以及请求控制信号(请求是否有效和IFU是否ready)。每个预测块最多包含32字节指令码,最多为16条指令。IFU需要置位ready驱动FTQ向ICache发送请求。
1.1. F0流水级接收请求
IFU应当能向FTQ报告自己已ready。
所以,对于这一测试点我们只需要在发送请求后检查和ftq相关的的ready情况即可。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1.1 | IFU_RCV_REQ | READY | IFU接收FTQ请求后,设置ready |
2. 指令切分产生初始指令码(F1、F2流水级)
F0流水级时,FTQ同时会向ICache发送取缓存行的指令。这是ICache在其S2流水级需要返回的,所以IFU在F2流水级才会得到ICache返回的缓存行。在此之前,IFU会在F1流水线先进行PC的计算,以及计算切分缓存行的指针。
进入F2流水级,IFU将会针对每个指令,取出对应的异常信息、物理地址、客户物理地址等。同时,根据FTQ的taken信息,IFU将会计算该预测块在无跳转和跳转发生情况下的有效指令范围。无跳转情况下的指令有效范围ftr_range即当前预测块从起始地址到下一个预测块的起始地址的差值。有跳转情况下的指令有效范围jump_range即当前预测块的起始地址到预测块中第一个跳转指令地址的差值。
最后,IFU需要从缓存行和上一流水级计算的指针,完成对17x2字节初始指令码的拼接。这里的拼接代码可能存在一些迷惑性
val f2_cache_response_data = fromICache.map(_.bits.data)
val f2_data_2_cacheline = Cat(f2_cache_response_data(0), f2_cache_response_data(0))
在调用cut之前,我们先是从ICache获取了缓存行(ICache返回的缓存行种类已经在ICache中进行了分类讨论,IFU中直接使用即可),然后将第0个缓存数据进行了拼接, 这一操作的原因来自于ICache中对数据的细粒度拆分:
fetch block可能跨缓存行,但是由于fetch block最大只有34B,如果将两个缓存行(2x64B)都传送给IFU则显得浪费,因此,fetch block的选择由ICache完成。
ICache返回给IFU的并不是直接的预测块,而是带有跨缓存行信息的64字节。
我们将每个缓存行分为8份,如下所示:
cacheline 0: |0-7|0-6|0-5|0-4|0-3|0-2|0-1|0-0|
cacheline 1: |1-7|1-6|1-5|1-4|1-3|1-2|1-1|1-0|
如果fetch block的起始位置为0-1,则必定不跨缓存行。
如果fetch block的起始位置为0-6,那么fetch block的位置为0-6~1-2,此时传送的缓存行结构如下:
cacheline 0: |0-7|0-6|xx|xx|xx|1-2|1-1|1-0|
由此,只要将该缓存行复制一遍,即可获得拼接后的fetch block。
综上所述,对这两种情况,我们都只需要把返回的cacheline复制一份拼接在一起,从中间截取就可以拿到数据。
详细的信息可以参考ICache文档
2.1. F1流水级计算信息和切分指针
F1流水级也会计算PC。
同时还需要生成17位的切分指针(也就是从拼接后的缓存行切出初始指令码的idx数组,在昆明湖架构中,计算方式为拼接00和startAddr[5:1], 然后分别与0~16相加) 用于后续从缓存行提取初始指令码。
所以,首先我们需要检查F1流水级生成的PC的正确与否。如果可能,也需要检查一下切分指针的生成。
可以总结出以下的细分测试点:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
2.1.1 | IFU_F1_INFOS | PC | IFU接收FTQ请求后,在F1流水级生成PC |
2.1.2 | IFU_F1_INFOS | CUT_PTR | IFU接收FTQ请求后,在F1流水级生成后续切取缓存行的指针 |
2.2. F2流水级获取指令信息
包括获取异常信息、物理地址、客户物理地址、是否在MMIO空间等。
获取异常信息之后,还需要计算异常向量。ICache会为每个缓存行返回一个异常类型,只需要计算每个指令pc属于哪个缓存行, 然后将对应缓存行的异常类型赋给该位置即可。
所以,只需要分别检查几种指令信息即可。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
2.2.1 | IFU_F2_INFOS | EXCP_VEC | IFU接收ICache内容后,会根据ICache的结果生成属于每个指令的异常向量 |
2.2.2 | IFU_F2_INFOS | PADDR | IFU接收ICache内容后,会根据ICache的结果生成属于每个端口的物理地址。 |
2.2.3 | IFU_F2_INFOS | GPADDR | IFU接收ICache内容后,会根据ICache的结果生成0号端口的客户物理地址。 |
2.2.4 | IFU_F2_INFOS | MMIO | IFU接收ICache内容后,会根据ICache的结果判断当前取指请求是否属于MMIO空间。 |
2.3. F2流水级计算预测块有效指令范围
指令有效范围包括两种,无跳转和有跳转的
无跳转的指令有效范围为当前预测块从起始地址到下一个预测块的起始地址的所有指令。
有跳转的指令有效范围jump_range为当前预测块的起始地址到预测块中第一个跳转指令地址(包含第一个跳转指令地址)之间的所有指令。
最终的指令有效范围是两者相与的结果。
我们需要分别对两种有效范围进行检查,再检查最终结果。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
2.3.1 | IFU_INSTR_VALID_RANGE | NORMAL | IFU根据FTQ请求,计算无跳转指令有效范围 |
2.3.2 | IFU_INSTR_VALID_RANGE | JUMP | IFU根据FTQ请求,计算跳转指令有效范围 |
2.3.3 | IFU_INSTR_VALID_RANGE | FINAL | IFU综合两类指令有效范围,生成最终指令有效范围 |
2.4. 提取初始指令码
IFU需要将ICache返回的缓存行复制一份并拼接。然后利用上一流水级计算的idx数组,从缓存行提取17x2字节的初始指令码。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
2.4 | IFU_INSTR_CUT | CUT | IFU根据上一流水级的切取指针,从缓存行提取初始指令码。 |
3. 预译码(F2流水级,主要由PreDecode模块完成)
在F2流水级,我们需要将上一步完成切分的指令码交给PreDecode子模块,他的作用主要有二:
其一是生成预译码信息,包括该指令是否是有效指令的开始、是否是RVC指令、是否是CFI指令、CFI指令类型(branch/jal/jalr/call/ret)、CFI指令的目标地址计算偏移等。输出的预译码信息中brType域的编码如下:
CFI指令类型 | brType类型编码 |
---|---|
非CFI | 00 |
branch指令 | 01 |
jal指令 | 10 |
jalr指令 | 11 |
其二是将初始指令码两两组合之后,得到16x4字节的指令码(从起始地址开始,2字节做地址递增,地址开始的4字节作为一条32位初始指令码)。
此外,预译码阶段还需要分类讨论,得出两种指令有效向量(起始指令是不是RVI指令的后半部分),并交给IFU进行判断选择,可以参阅后面的跨预测块32位指令处理部分
其他功能和详细内容(比如怎么判断RET和CALL指令等)参见PreDecode和F3Predecoder子模块的描述。
3.1. 指令码拼接
对于上一功能中生成的指令序列,应当拼接成为16x4的指令码序列。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
3.1.1 | IFU_PREDECODE | CONCAT | 将生成的指令序列拼接成为16x4的指令码序列 |
3.2. 判定RVC指令
PreDecode功能需要判断一条指令是否为RVC指令
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
3.2.1 | IFU_PREDECODE_RVC | RVC | 传入RVC指令,应该判断为RVC |
3.2.2 | IFU_PREDECODE_RVC | RVI | 传入RVI指令,不应判断为RVC |
3.3. 计算跳转偏移
预译码阶段需要对BR和J类型的跳转指令偏移进行计算。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
3.3.1 | IFU_PREDECODE_JMP_TGT | RVC_J | 对传入RVC扩展的J指令,检查计算的偏移 |
3.3.2 | IFU_PREDECODE_JMP_TGT | RVI_J | 对传入RVI扩展的J指令,检查计算的偏移 |
3.3.3 | IFU_PREDECODE_JMP_TGT | RVC_BR | 对传入RVC扩展的BR指令,检查计算的偏移 |
3.3.4 | IFU_PREDECODE_JMP_TGT | RVI_BR | 对传入RVI扩展的BR指令,检查计算的偏移 |
3.4. 判定CFI指令类型
预译码阶段需要对CFI指令的类型进行判断,一共有四种判断结果:非CFI指令、BR指令、JAL指令、JALR指令
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
3.4.1 | IFU_PREDECODE_CFI_TYPE | NON_CFI | 对传入的非CFI指令(包括RVC.EBREAK),应该判定为类型0 |
3.4.2 | IFU_PREDECODE_CFI_TYPE | BR | 对传入的BR指令,应该判定为类型1 |
3.4.3 | IFU_PREDECODE_CFI_TYPE | JAL | 对传入的JAL指令,应该判定为类型2 |
3.4.4 | IFU_PREDECODE_CFI_TYPE | JALR | 对传入的JALR指令,应该判定为类型3 |
3.5. 判定RET和CALL
预译码阶段需要判断一条指令是否为ret或者call指令,具体请参阅F3Predecoder文档
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
3.5.1 | IFU_PREDECODE_RET_CALL | NON_CFI_BR | 对传入的非CFI和BR指令,都不应判定为call或者ret |
3.5.2.1.1 | IFU_PREDECODE_RET_CALL | RVI_JAL_CALL | 对传入的RVI.JAL指令,当rd设置为1或5,应当判定该指令为call |
3.5.2.1.2 | IFU_PREDECODE_RET_CALL | RVI_JAL_NOP | 对传入的RVI.JAL指令,当rd设置为1和5之外的值,不应当判定该指令为call或ret |
3.5.2.2 | IFU_PREDECODE_RET_CALL | RVC_JAL_NOP | 对传入的RVC.JAL指令,无论什么情况都不能判定为call或ret |
3.5.3.1.1 | IFU_PREDECODE_RET_CALL | RVI_JALR_CALL | 传入RVI.JALR指令,并且rd为1或5,无论其他取值,都应判定为call |
3.5.3.1.2 | IFU_PREDECODE_RET_CALL | RVI_JALR_RET | 传入RVI.JALR指令,rd不为1和5,rs为1或5,应判定为ret |
3.5.3.1.3 | IFU_PREDECODE_RET_CALL | RVI_JALR_NOP | 对传入的JALR指令,若rd和rs均不为link,则不应判定为ret和call |
3.5.3.2.1 | IFU_PREDECODE_RET_CALL | RVC_JALR_CALL | 传入RVC.JALR指令,必定为call |
3.5.3.2.2.1 | IFU_PREDECODE_RET_CALL | RVC_JR_RET | 传入RVC.JR指令,rs为1或5,应判定为ret |
3.5.3.2.2.2 | IFU_PREDECODE_RET_CALL | RVC_JR_NOP | 传入RVC.JR指令,rs不为1或5,不应判定为ret |
3.6. 计算指令有效开始向量
预译码阶段需要根据两种情况计算有效指令开始向量,IFU top需要对有效指令开始向量进行选择。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
3.6.1 | IFU_PREDECODE_VALID_STARTS | LAST_IS_END | 上一预测块的最后2字节恰为RVC指令或RVI指令的后半部分,按第一位为True推导有效开始向量 |
3.6.2 | IFU_PREDECODE_VALID_STARTS | LAST_NOT_END | 上一预测块的最后2字节上一预测块的最后2字节为RVI指令的前半部分,按第一位为False推导有效开始向量 |
4. 指令扩展(F3流水级)
这一部分将从PreDecode返回的16条指令码分别送交指令扩展器(RVCExpander)进行32位指令扩展(RVI保持不变, RVC指令根据手册的规定进行扩充)。
但是,如果RVC指令非法,需要向IBuffer写入原始指令码。
4.1. 指令扩展和检错
指令扩展阶段需要分RVC和RVI指令进行考虑,其中RVC指令需要判断合法与否。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
4.1.1 | IFU_RVC_EXPAND | VALID_RVC | 对合法RVC指令,写扩展后的指令码,判断结果为合法指令 |
4.1.2 | IFU_RVC_EXPAND | INVALID_RVC | 对非法RVC指令,写原始指令码,判断结果为非法指令 |
4.1.3 | IFU_RVC_EXPAND | RVI | RVI指令直接写入原始指令即可,判断结果为合法指令 |
5. 预测错误预检查(F3流水级,主要由PreChecker子模块完成)
这一功能是为了将一些不依赖于执行结果的预测错误在早期就发现出来。这一阶段检查五类错误:
jal类型错误:预测块的范围内有jal指令,但是预测器没有对这条指令预测跳转;
ret类型错误:预测块的范围内有ret指令,但是预测器没有对这条指令预测跳转;
jalr类型错误:预测块的范围内有jalr指令,但是预测器没有对这条指令预测跳转;
无效指令预测错误:预测器对一条无效的指令(不在预测块范围/是一条32位指令中间)进行了预测;
非CFI指令预测错误:预测器对一条有效但是不是CFI的指令进行了预测;
转移目标地址错误:预测器给出的转移目标地址不正确。
在预检查的最后将会修正之前预测的各个指令的跳转情况。同时,如果存在jal或者ret类型预测错误,还将修正fixedRange——这是指令有效范围向量,可以看作一个bool数组,其中某一位为1也就是对应的指令在这一范围内。
这一部分的功能点和PredChecker子模块的功能点相同。
5.1. BPU预测信息的JAL预测错误检查
PredChecker会对传入的预测块进行JAL预测错误预检查并修正指令有效范围向量和预测的跳转指令。
对这一模块的测试,我们分为两部分:正确的输入是否会误检和确有JAL检测错误的预测块输入能否检出。
对此,我们设计如下的测试点:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
5.1.1.1 | IFU_PRECHECK_JAL_MISS | NOP | 预测块中没有JAL指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报JAL预测错误。 |
5.1.2.1 | IFU_PRECHECK_JAL_MISS | CORRECT | 预测块中有JAL指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报JAL预测错误。 |
5.1.2.1 | IFU_PRECHECK_JAL_CHECK | NO_SEL | 预测块中存在JAL指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出JAL预测错误。 |
5.1.2.2 | IFU_PRECHECK_JAL_CHECK | SEL_LATE | 预测块中存在JAL指令,但是BPU预测信息取的跳转指令在第一条JAL指令之后,检查PredChecker是否能检测出JAL预测错误。 |
5.2. BPU预测信息的RET预测错误检查
PredChecker会对传入的预测块进行RET预测错误预检查并修正指令有效范围向量和新的预测结果。
和JAL预测错误类似,我们也按照误检和正检来构造。
为此,我们设计如下的测试点:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
5.2.1.1 | IFU_PRECHECK_RET_MISS | NOP | 预测块中没有RET指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报RET预测错误。 |
5.2.2.1 | IFU_PRECHECK_RET_MISS | CORRECT | 预测块中有RET指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报RET预测错误。 |
5.2.2.1 | IFU_PRECHECK_RET_CHECK | NO_SEL | 预测块中存在RET指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出RET预测错误。 |
5.2.2.2 | IFU_PRECHECK_RET_CHECK | SEL_LATE | 预测块中存在RET指令,但是BPU预测信息取的跳转指令在第一条RET指令之后,检查PredChecker是否能检测出RET预测错误。 |
5.3. BPU预测信息的JALR预测错误检查
PredChecker会对传入的预测块进行JALR预测错误预检查并修正指令有效范围向量和新的预测结果。
和JAL/RET预测错误类似,我们也按照误检和正检来构造。
为此,我们设计如下的测试点:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
5.3.2.2 | IFU_PRECHECK_JALR_MISS | NOP | 预测块中没有JALR指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报JALR预测错误。 |
5.3.2.2 | IFU_PRECHECK_JALR_MISS | CORRECT | 预测块中有JALR指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报JALR预测错误。 |
5.3.2.2 | IFU_PRECHECK_JALR_CHECK | NO_SEL | 预测块中存在JALR指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出RET预测错误。 |
5.3.2.2 | IFU_PRECHECK_JALR_CHECK | SEL_LATE | 预测块中存在JALR指令,但是BPU预测信息取的跳转指令在第一条JALR指令之后,检查PredChecker是否能检测出JALR预测错误。 |
5.4. 更新指令有效范围向量和预测跳转的指令
PredChecker在检查出Jal/Ret/Jalr指令预测错误时,需要重新生成指令有效范围向量, 有效范围截取到Jal/Ret/Jalr指令的位置,之后的bit全部置为0。 同时,还需要根据每条指令的预译码信息和BPU的预测信息修复预测跳转的结果。
所以,根据功能要求,我们可以划分出三类情况,分别是预测的有效范围和取用的跳转指令正确的情况, 由于RET和JAL预测错误引起的有效范围偏大和错判非跳转指令和无效指令引起的有效范围偏小。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
5.4.1 | IFU_PREDCHECK_FIX | NOP | 不存在任何错误的情况下,PredChecker应当保留之前的预测结果。 |
5.4.2 | IFU_PREDCHECK_FIX | BIGGER_FIX | 如果检测到了JAL、RET、JALR类的预测错误,PredChecker应该将有效指令的范围修正为预测块开始至第一条跳转指令。同时,应该将预测跳转的指令位置修正为预测块中的第一条跳转指令。 |
5.4.3 | IFU_PREDCHECK_FIX | SMALLER_NOP | 如果出现了非控制流指令和无效指令的误预测,不应该将预测跳转的指令重新修正到预测块中第一条跳转指令(也即不能扩大范围),因为后续会直接冲刷并重新从重定向的位置取指令,如果这里修正的话,会导致下一预测块传入重复的指令 |
5.5. 非CFI预测错误检查
非CFI预测错误的条件是被预测跳转的指令根据预译码信息显示不是一条CFI指令。
要检验这一功能,我们仍然按误检和正确检验来设计测试点:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
5.5.1.1 | IFU_PREDCHECK_NON_CFI_MISS | NOP | 构造不存在CFI指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误 |
5.5.1.2 | IFU_PREDCHECK_NON_CFI_MISS | CORRECT | 构造存在CFI指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误 |
5.5.2 | IFU_PREDCHECK_NON_CFI_CHECK | ERROR | 构造不存在CFI指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出非CFI预测错误 |
5.6. 无效指令预测错误检查
目标地址预测错误的条件是,被预测的是一条有效的jal或者branch指令, 同时预测的跳转目标地址和由指令码计算得到的跳转目标不一致。
和先前的思路一样,我们仍然按误检和检出两类组织测试点:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
5.6.1.1 | IFU_PREDCHECK_INVALID_MISS | NOP | 构造不存在跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
5.6.1.2 | IFU_PREDCHECK_INVALID_MISS | INVALID_JMP | 构造存在无效跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
5.6.1.3 | IFU_PREDCHECK_INVALID_MISS | CORRECT | 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
5.6.2 | IFU_PREDCHECK_INVALID_MISS | ERROR | 构造无效指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出无效指令预测错误 |
5.7. 目标地址预测错误检查
无效指令预测错误的条件是被预测的指令的位置根据预译码信息中的指令有效向量显示不是一条有效指令的开始。
要检验这一功能,我们按照误检和正确检测来设计测试点:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
5.7.1.1 | IFU_PREDCHECK_TARGET_MISS | NOP | 构造不存在跳转指令并且未预测跳转的预测信息作输入,测试PredChecker是否会错检目标地址预测错误 |
5.7.1.2 | IFU_PREDCHECK_TARGET_MISS | CORRECT | 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检目标地址预测错误 |
5.7.2 | IFU_PREDCHECK_TARGET_CHECK | ERROR | 构造存在有效跳转指令的预测块和预测跳转但跳转目标计算错误的预测信息作为输入,测试PredChecker能否检出目标地址预测错误 |
5.8. 生成跳转和顺序目标
PredChecker还需要负责生成跳转和顺序目标。
我们通过随机生成译码信息进行测试
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
5.8 | IFU_PREDCHECK_TARGETS | TARGETS | 随机提供译码信息,检测生成的跳转目标和顺序目标。 |
6. 前端重定向(WB阶段)
如果在预测错误预检查的部分发现了上述的6类错误,那么需要在写回阶段产生一个前端重定向将F3以外的流水级进行冲刷, 从而让BPU能够从正确路径重新开始预测。
还有一种情况下需要冲刷流水线。在下一节中,如果误判了当前预测块的最后2B为RVI指令的上半部分,则也需要冲刷当前预测块F3之前的流水级。
6.1. 预测错误重定向
如果发现了预检阶段检出的错误,则需要产生前端重定向,将F3以外的流水级冲刷
只需要构造有预测错误的预测请求,检查冲刷情况即可。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
6.1.1 | IFU_REDIRECT | JAL | 预测请求中存在JAL预测错误,需要冲刷流水线 |
6.1.2 | IFU_REDIRECT | RET | 预测请求中存在RET预测错误,需要冲刷流水线 |
6.1.3 | IFU_REDIRECT | JALR | 预测请求中存在JALR预测错误,需要冲刷流水线 |
6.1.4 | IFU_REDIRECT | NON_CFI | 预测请求中存在非CFI预测错误,需要冲刷流水线 |
6.1.5 | IFU_REDIRECT | INVALID | 预测请求中存在无效指令预测错误,需要冲刷流水线 |
6.1.6 | IFU_REDIRECT | TARGET_FAULT | 预测请求中存在跳转目标错误,需要冲刷流水线 |
7. 跨预测块32位指令处理
因为预测块的长度有限制,因此存在一条RVI指令前后两字节分别在两个预测块的情况。IFU首先在第一个预测块里检查最后2字节是不是一条RVI指令的开始,如果是并且该预测块没有跳转,那么就设置一个标识寄存器f3_lastHalf_valid,告诉接下来的预测块含有后半条指令。在F2预译码时,会产生两种不同的指令有效向量:
-
预测块起始地址开始即为一条指令的开始,以这种方式根据后续指令是RVC还是RVI产生指令有效向量
-
预测块起始地址是一条RVI指令的中间,以起始地址 + 2位一条指令的开始产生有效向量
在F3,根据是否有跨预测块RVI标识来决定选用哪种作为最终的指令有效向量,如果f3_lastHalf_valid为高则选择后一种(即这个预测块第一个2字节不是指令的开始)。IFU所做的处理只是把这条指令算在第一个预测块里,而把第二个预测块的起始地址位置的2字节通过改变指令有效向量来无效掉。
7.1. 跨预测块32位指令处理
如果发现当前预测块的最后两个字节是一条RVI指令的开始,则设置一个标识f3_lastHalf_valid,告诉接下来的预测块含有后半条指令。
我们没有办法直接观察到这个标识,但是可以通过下一预测块的开始向量的首位来判断。
7.2. 跨预测块指令误判
但是,如果这一判断出现问题(比如当前预测块存在跳转),则需要进行流水线冲刷。
这一功能需要PredChecker子模块“配合”(仅仅通过外部IO的修改很难触发这个防御机制),实现起来比较麻烦,但是还是列举一个测试点(见后文总表)
8. 将指令码和前端信息送入IBuffer(F3流水级)
F3流水级最终得到经过扩展的32位指令码(或者对于非法指令直接传递原始指令码),以及16条指令中每条指令的例外信息、 预译码信息、FTQ队列中的指针位置、其他后端需要的信息(比如经过折叠的PC)等。IFU除了常规的valid-ready控制信号外, 还会给IBuffer两个特殊的信号:一个是16位的io_toIbuffer_bits_valid(因为我们最后组合出来的指令也是16条, 所以这里每一位刚好也对应一个指令的状态,为1说明是一条指令的开始,为0则是说明是一条指令的中间),标识预测块里有效的指令。 另一个是16位的io_toIbuffer_bits_enqEnable,这个在io_toIbuffer_bits_valid的基础上与上了被修正过的预测块的指令范围fixedRange。 enqEnable为1表示这个2字节指令码是一条指令的开始且在预测块表示的指令范围内。
除此之外,异常信息也需要写给IBuffer。
注意一个特例:当且仅当发生guest page fault时,后端需要gpaddr信息,为了节省面积,gpaddr不走正常通路进入ibuffer, 而是随ftqPtr被发送到gpaMem,后端需要时从gpaMem读出。IFU需要保证gpf发生时通向gpaMem的valid拉高、gpaddr正确。
8.1. 传送指令码和前端信息
传送给IBuffer的信息包括:经过扩展的32位指令码、16条指令中每条指令的例外信息、预译码信息、FTQ队列中的指针位置、其他后端需要的信息(经过折叠的PC)、 io_toIbuffer_bits_valid(表示指令是否是一条指令的开始)、io_toIbuffer_bits_enqEnable(前者与上被修正过的预测块指令范围, 从而还能表示指令是否在预测块表示的指令范围内)。
这里要做的只是确认这些信息是否正确传递
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
8.1.1 | IFU_TO_IBUFFER | INSTRS | IFU向IBuffer传送扩展后的指令码 |
8.1.2 | IFU_TO_IBUFFER | EXCP | IFU向IBuffer传送每个指令的异常信息 |
8.1.3 | IFU_TO_IBUFFER | PD_INFO | IFU向IBuffer传递每个指令的预译码信息 |
8.1.4 | IFU_TO_IBUFFER | FTQ_PTR | IFU向IBuffer传送FTQ预测块的指针 |
8.1.5 | IFU_TO_IBUFFER | FOLD_PC | IFU向IBuffer传送折叠的PC |
8.1.6 | IFU_TO_IBUFFER | VALID_STARTS | IFU向IBuffer传送表示指令有效和指令是否为指令开始的向量 |
功能点8.2. 客户页错误传送gpaddr信息
当且仅当发生guest page fault时,后端需要gpaddr信息,为了节省面积,gpaddr不走正常通路进入ibuffer, 而是随ftqPtr被发送到gpaMem,后端需要时从gpaMem读出。IFU需要保证gpf发生时通向gpaMem的valid拉高、gpaddr正确,同时还要传递预测块的ftqIdx(通过waddr传入)。
这里我们只需要确保在客户页错误发生时通向gpaMem的valid为高,且gpaddr正确填入。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
8.2.1 | IFU_TO_GPAMEM | GPADDR | 客户页错误发生时,IFU应将gpaMem的valid拉高且填入gpaddr |
9. 分支预测overriding冲刷流水线
当FTQ内未缓存足够预测块时,IFU可能直接使用简单分支预测器提供的预测地址进行取指,这种情况下,当精确预测器发现简单预测器错误时,需要通知IFU取消正在进行的取指请求。具体而言,当BPU的S2流水级和S3流水级发现错误时,需要冲刷且仅冲刷IFU的F0流水级(参见香山的提交#6f9d483)。
IFU在收到BPU发送的冲刷请求时,会将F0流水级上取指请求的指针与BPU发送的冲刷请求的指针进行比较,若冲刷的指针在取指的指针之前,说明当前取指请求在错误的执行路径上,需要进行流水线冲刷;反之,IFU可以忽略BPU发送的这一冲刷请求。此外,比较的时候还需要注意flag的情况,flag是一个指示队列循环的指针,flag不同即在不同的“圈”上,此时反而是idx的值更小,ftqIdx才会更大。
9.1 核验指针
IFU收到BPU冲刷请求后,会将F0/F1流水级上取指令请求的指针比较,冲刷的指针在取指之前,即当前取指令请求在错误的执行路径上,才需要 冲刷IFU。
我们仍然需要从两个方向校验这个功能,即当冲刷指针在取指令的指针之前时,IFU能够对流水线进行冲刷。 然而,当冲刷指令在取指令的指针之后时,则不能对流水线进行冲刷。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
9.1.1 | IFU_OVERRIDE_FLUSH_CHECK | BEFORE | 当冲刷指针在取指令的指针之前时,IFU能够对流水线进行冲刷。 |
9.1.2 | IFU_OVERRIDE_FLUSH_CHECK | NOT_BEFORE | 当冲刷指令在取指令的指针相同或之后时,IFU不能对流水线进行冲刷。 |
9.2 BPU S2/S3流水级发现错误
BPU的S2和S3流水级发现错误时,需冲刷IFU的F0流水级。故设计下列检查点
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
9.2.1 | IFU_OVERRIDE_FLUSH | S2 | 当BPU的S2流水级出现错误,并且当前取指指针在错误执行路径上时,需要对IFU的F0流水级进行冲刷 |
9.2.2 | IFU_OVERRIDE_FLUSH | S3 | 当BPU的S3流水级出现错误,并且当前取指指针在错误执行路径上时,需要对IFU的F0流水级进行冲刷 |
10. 指令信息和误预测信息写回FTQ(WB阶段)
在F3的下一级WB级,IFU将指令PC、预译码信息、错误预测指令的位置、正确的跳转地址以及预测块的正确指令范围等信息写回FTQ,同时传递该预测块的FTQ指针用以区分不同请求。
同时,正如前面提到的,IFU检测到预测错误时会进行前端冲刷,同样地,FTQ也需要据此进行冲刷,因此,这也是IFU写回错误信息的意义——可以辅助FTQ判断是否冲刷流水线。
10.1 写回指令信息和误预测信息
将指令PC、预译码信息、错误预测指令的位置、正确的跳转地址以及预测块的正确指令范围等信息写回FTQ,并传递该预测块的FTQ指针。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
10.1.1 | IFU_WB_FTQ | PCS | IFU的WB流水级,需要向FTQ写回指令PC |
10.1.2 | IFU_WB_FTQ | PD_INFO | IFU的WB流水级,需要向FTQ写回每个指令的预译码信息 |
10.1.3 | IFU_WB_FTQ | ERR_POS | IFU的WB流水级,需要向FTQ写回BPU错误预测的指令位置 |
10.1.4 | IFU_WB_FTQ | TARGET | IFU的WB流水级,需要向FTQ写回该预测块的正确跳转地址 |
10.1.5 | IFU_WB_FTQ | RANGE | IFU的WB流水级,需要向FTQ写回预测块的正确指令范围 |
10.1.6 | IFU_WB_FTQ | FTQ_PTR | IFU的WB流水级,需要向FTQ传递预测块的FTQ指针 |
11. MMIO处理逻辑
在处理器上电复位时,内存还没有准备好,此时需要从Flash中取指令执行。 这种情况下需要IFU向MMIO总线发送宽度为64位的请求从flash地址空间取指令执行。同时IFU禁止对MMIO总线的推测执行,即IFU需要等到每一条指令执行完成得到准确的下一条指令地址之后才继续向总线发送请求。
这之后,根据FTQ中的指令地址,决定是否MMIO取指令。
- 状态机默认在
m_idle
状态,若 F3 流水级是 MMIO 取指令请求,且此前没有发生异常,状态机进入m_waitLastCmt
状态。 - (
m_waitLastCmt
)IFU 通过 mmioCommitRead 端口到 FTQ 查询,IF3 预测块之前的指令是否都已提交,如果没有提交则阻塞等待前面的指令都提交完1。 - (
m_sendReq
)将请求发送到 InstrUncache 模块,向 MMIO 总线发送请求。 - (
m_waitResp
)InstrUncache 模块返回后根据 pc 从 64 位数据中截取指令码。 - 若 pc 低位为
3'b110
,由于 MMIO 总线的带宽限制为 8B 且只能访问对齐的区域,本次请求的高 2B 将不是有效的数据。若返回的指令数据表明指令不是 RVC 指令,则这种情况需要对 pc+2 的位置(即对齐到下一个 8B 的位置)进行重发才能取回完整的 4B 指令码。- 重发前,需要重新对 pc+2 进行 ITLB 地址翻译和 PMP 检查(因为可能跨页)(
m_sendTLB
、m_TLBResp
、m_sendPMP
),若 ITLB 或 PMP 出现异常(access fault、page fault、guest page fault)、或检查发现 pc+2 的位置不在 MMIO 地址空间,则直接将异常信息发送到后端,不进行取指。 - 若无异常,(
m_resendReq
、m_waitResendResp
)类似 2/3 两步向 InstrUncache 发出请求并收到指令码。
- 重发前,需要重新对 pc+2 进行 ITLB 地址翻译和 PMP 检查(因为可能跨页)(
- 当 IFU 寄存了完整的指令码,或出错(重发时的ITLB/PMP出错,
或 Uncache 模块 tilelink 总线返回 corrupt2)时,(m_waitCommit
)即可将指令数据和异常信息发送到 IBuffer。需要注意,MMIO 取指令每次只能非推测性地向总线发起一条指令的取指请求,因此也只能向 IBuffer 发送一条指令数据。并等待指令提交。- 若这条指令是 CFI 指令,由后端发送向 FTQ 发起冲刷。
- 若是顺序指令,则由 IFU 复用前端重定向通路刷新流水线,同时复用 FTQ 写回机制,把它当作一条错误预测的指令进行冲刷,重定向到该指令地址 +2 或者 +4(根据这条指令是 RVI 还是 RVC 选择)。这一机制保证了 MMIO 每次只取入一条指令。
- 提交后,(
m_commited
)状态机复位到m_idle
并清空各类寄存器。
除了上电时,debug 扩展、Svpbmt 扩展可能也会使处理器在运行的任意时刻跳到一块 MMIO 地址空间取指令,请参考 RISC-V 手册。对这些情况中 MMIO 取指的处理是相同的。
11.1. 上电复位处理
处理器上电复位时,IFU需向MMIO总线发送宽度为64位的请求从flash地址空间取指令,并禁止对MMIO总线的推测执行。
上电的情况和正常情况其实没有任何区别,但是,上电时的MMIO请求没有任何差别,只是,第一条请求一定是MMIO,并且不需要等待。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
11.1.1 | IFU_MMIO_RESET | FIRST_MMIO | IFU收到的第一条MMIO请求可以直接查询Instr Uncache |
11.2. 向InstrUncache发送请求
在正常的处理逻辑下,如果请求地址处于MMIO地址空间,则IFU会向FTQ查询指令提交状态,IFU需要等待当前请求之前的所有请求(包括MMIO和非MMIO)提交完成, 才能向InstrUncache模块发送请求。
这里需要和FTQ交互,可以让FTQ模拟请求提交情况,从而测试等待情况。 如果MMIO请求之前的请求都已经提交,则也不需要等待。反之,则需要一直等待直到查询结果表明前面的指令均已提交。
此外,对于属性为NC
的内存区域,可以进行推测执行,无需等待前面的指令提交。
故设计测试点如下:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
11.2.1 | IFU_MMIO_SEND_UNCACHE | BLOCK | IFU收到MMIO请求后,查询FTQ,如果前面还有尚未提交的指令,持续等待 |
11.2.2 | IFU_MMIO_SEND_UNCACHE | FREE | 如果查到FTQ不再有未提交的指令,则IFU将指令发送给Instr Uncache |
11.2.3 | IFU_MMIO_SEND_UNCACHE | NC | 对于属性为NC的内存区域,无需等待前一条指令完成提交 |
11.3. 跨总线请求处理
由于MMIO不支持非对齐访问,因此当检测到的RVI指令地址[2,1]两位为b11时,64位总线无法一次传递所有指令,所以需要增加地址进行重发,再次查询ITLB。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
11.3.1 | IFU_MMIO_RESEND_ITLB | RESEND | 遇到一次无法查询完毕的RVI指令时,需要向ITLB查询获得新增指令的物理地址 |
如果存在异常,则直接将指令和异常信息发送到IBuffer并等待,否则向PMP发送请求。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
11.3.2.1 | IFU_MMIO_RESEND_ITLB | EXCP | IFU查询ITLB出现异常时,应当将异常信息发送到IBuffer,然后等待ROB提交完成 |
11.3.2.2 | IFU_MMIO_RESEND_ITLB | PADDR | IFU查询ITLB正常返回物理地址时,IFU继续向PMP请求检查 |
根据pmp_recheck的结果,如果和上一次请求状态不一致,则说明存在访问错误, 为访问异常,不然则根据PMP的回复结果决定是否存在异常。如存在异常(访问异常和其他异常),则将报错信息发送给IBuffer并等待。如无异常,重新向InstrUncache模块 发送请求。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
11.3.3.1 | IFU_MMIO_PMP_RECHECK | STATUS_DIFF | IFU检查PMP之后如果发现重发请求状态和上一条请求状态不一致,是访问异常,需要将异常直接发送到IBuffer |
11.3.3.2 | IFU_MMIO_PMP_RECHECK | EXCP | PMP检查出现异常的情况下,也需要将异常直接发送到IBuffer并等待ROB提交。 |
11.3.3.3 | IFU_MMIO_PMP_RECHECK | RESEND_UNCACHE | PMP检查若无异常,则向Instr Uncache发送请求获取指令码的后半部分。 |
11.4. 向IBuffer发送指令
IFU获得完整数据之后,根据地址从64位数据中截取指令码,并以每个预测块一条指令的形式发送到Ibuffer。等待ROB返回指令已提交的信号。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
11.4 | IFU_MMIO_TO_IBUFFER | INSTR | IFU在获得完整数据后,截取获得指令码,以每个预测块一条指令的形式发送给IBuffer |
11.5. 指令冲刷
CFI指令的冲刷由后端发送给FTQ完成。所以只需要指令类型正确传达即可。
顺序指令由IFU复用前端重定向通路刷新流水线,并复用FTQ写回机制,将该指令当作误预测指令冲刷,重定向到+2或+4的位置。
+2和+4是由RVC和RVI指令决定的,所以设置测试点如下:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
11.5.1 | IFU_MMIO_FLUSH_NON_CFI | RVI | 如果是RVI指令,传递给FTQ的冲刷请求应该重定向到PC+4 |
11.5.2 | IFU_MMIO_FLUSH_NON_CFI | RVC | 如果是RVC指令,传递给FTQ的冲刷请求应该重定向到PC+2 |
12. Trigger实现对于PC的硬件断点功能
该工作主要由FrontEndTrigger子模块完成。本处先进行简单说明。
在 IFU 的 FrontendTrigger 模块里共 4 个 Trigger,编号为 0-3,每个 Trigger 的配置信息(断点类型、匹配地址等)保存在 tdata
寄存器中。
当软件向 CSR 寄存器 tselect
、tdata1/2
写入特定的值时,CSR 会向 IFU 发送 tUpdate 请求,更新 FrontendTrigger 内的 tdata
寄存器中的配置信息。目前前端的 Trigger 仅可以配置成 PC 断点(mcontrol.select
寄存器为 0;当 mcontrol.select
=1 时,该 Trigger 将永远不会命中,且不会产生异常)。
在取指时,IFU 的 F3 流水级会向 FrontendTrigger 模块发起查询并在同一周期得到结果。后者会对取指块内每一条指令在每一个 Trigger 上做检查,当不处于 debug 模式时,指令的 PC 和 tdata2
寄存器内容的关系满足 mcontrol.match
位所指示的关系(香山支持 mcontrol.match
位为 0、2、3,对应等于、大于、小于)时,该指令会被标记为 Trigger 命中,随着执行在后端产生断点异常,进入 M-Mode 或调试模式。前端的 Trigger 支持 Chain 功能。当它们对应的 mcontrol.chain
位被置时,只有当该 Trigger 和编号在它后面一位的 Trigger 同时命中时,处理器才会产生异常3。
FrontendTrigger的测试点可参照子模块文档,这里转录如下:
12.1. 设置断点和断点检查
FrontEndTrigger目前仅支持设置PC断点,这通过设置断点的tdata1寄存器的select位为0实现。 同时,tdata2寄存器的mcontrol位负责设置指令PC和tdata2寄存器的地址需要满足的关系, 关系满足时,该指令会被标记为trigger命中。
所以,基于以上功能描述,我们需要测试:
select位为1时,断点是否永远不会触发。
select位为0时,当PC和tdata2的数据的关系满足tdata2的match位时,是否会设置断点。
select位为0时,当PC和tdata2的数据的关系不满足tdata2的match位时,断点是否一定不会触发。
综上所述,我们在这一功能点设计的测试点如下:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
12.1.1 | IFU_FRONTEND_TRIGGER | SELECT1 | 给定tdata1的select位为1,随机构造其它输入,检查断点是否没有触发 |
12.1.2.1 | IFU_FRONTEND_TRIGGER_SELECT0 | MATCH | 给定tdata1的select位为0,构造PC与tdata2数据的关系同tdata2的match位匹配的输入,检查断点是否触发 |
12.1.2.2 | IFU_FRONTEND_TRIGGER_SELECT0 | NOT_MATCH | 给定tdata1的select位为0,构造PC与tdata2数据的关系同tdata2的match位不匹配的输入,检查断点是否触发 |
12.2. 链式断点
当某一个trigger的chain位被置后,当其后的trigger的chain位未设置,且两个trigger均命中时,后一个trigger才会触发。
对0号trigger,不需要考虑链式的情况
由此,我们可以设置几种测试点:
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
12.2.1 | IFU_FRONTEND_TRIGGER_CHAIN | SELF | 对每个trigger,在满足PC断点触发条件的情况下,设置chain位,检查断点是否一定不触发。 |
12.2.2.1 | IFU_FRONTEND_TRIGGER_CHAIN | NOT_HIT | 对两个trigger,仅设置前一个trigger的chain位,设置后一个trigger命中而前一个未命中,检查后一个trigger是否一定不触发。 |
12.2.2.2 | IFU_FRONTEND_TRIGGER_CHAIN | HIT | 对两个trigger,仅设置前一个trigger的chain位且均命中,检查后一个trigger是否触发。 |
IFU接口说明
为方便测试开展,需要对IFU的接口进行进一步的说明,以明确各个接口的含义。
FTQ交互接口
编译后可用的接口包括:
req FTQ取指请求
在f0流水级传入
req是FTQ向IFU的取指令请求,编译后包含以下成员:
接口名 | 解释 |
---|---|
ftqIdx | 指示当前预测块在FTQ中的位置。 |
ftqOffset | 指示预测块的大小 |
startAddr | 当前预测块的起始地址。 |
nextlineStart | 起始地址所在cacheline的下一个cacheline的开始地址。 |
nextStartAddr | 下一个预测块的起始地址 |
redirect FTQ重定向请求
在f0流水级传入
FTQ会向IFU发送重定向请求,这通过fromFtq.redirect完成,从而指示IFU应该冲刷的内容。
编译后,redirect包含以下接口成员:
接口名 | 解释 |
---|---|
ftqIdx | 需要冲刷的ftq预测块序号,包含flag和value两个量。 |
level | 重定向等级 |
ftq_offset | ftq预测块中跳转指令的位置 |
此外,还有valid变量指示是否需要重定向。
fromBPUFlush
在f0流水级传入
来自BPU的冲刷请求,这是预测错误引起的,包括s3和s2两个同构成员,指示是否在BPU的s3和s2流水级发现了问题,s3的详细结构如下
接口名 | 解释 |
---|---|
valid | 是否存在s3流水级冲刷要求 |
ftqIdx | s3流水级请求冲刷的预测块的指针 |
toFtq_pdWb 写回
在WB阶段传出
接口名 | 解释 |
---|---|
cfioffset | 经由PredChecker修复的跳转指令的预测位置。但经过编译后,cfioffset的数值已经被优化了,只剩下了cfioffset_valid表示是否存在编译优化。 |
ftqIdx | 表明预测块在FTQ中的位置,这条信息主要是对FTQ有用,要和FTQ传入的请求保持一致。 |
instrRange | 可以看作是一个bool数组,表示该条指令是不是在这个预测块的有效指令范围内(即第一条有效跳转指令之前的指令)。 |
jalTarget | 表明该预测块跳转指令的跳转目标。 |
misOffset | 表明错误预测的指令在预测块中的位置。 |
pc | 预测块中所有指令的PC指针。 |
pd | 每条指令的预测信息,包括CFI指令的类型、isCall、isRet和isRVC。 |
target | 该预测块最后一条指令的下一条指令的pc。 |
ICache交互接口
控制信号
接口名 | 解释 |
---|---|
icache_ready | ICache通知IFU自己已经准备好了,可以发送缓存行了。f0流水级就要设置。 |
icache_stop | IFU在F3流水级之前出现了问题,通知ICache停下。 |
ICacheInter.resp ICache传送给IFU的信息
在f2流水级使用
接口名 | 解释 |
---|---|
data | ICache传送的缓存行。 |
doubleLine | 指示ICache传来的预测块是否跨缓存行。 |
exception | ICache向IFU报告每个缓存行上的异常情况,方便ICache生成每个指令的异常向量。 |
backendException | ICache向IFU报告后端是否存在异常 |
gpaddr | 客户页地址 |
isForVSnonLeafPTE | 是否为非叶的PTE,这个数据最终会流向写回gpaddrMem的信号 |
itlb_pbmt | ITLB基于客户页的内存类型,对MMIO状态有用 |
paddr | 指令块的起始物理地址 |
vaddr | 指令块起始虚拟地址、下一个缓存行的虚拟地址 |
pmp_mmio | 指示当前指令块是否在MMIO空间 |
性能相关接口
ICachePerf和perf,可以先不关注。
ITLBInter
该接口仅在MMIO状态下,IFU重发请求时活跃(f3流水级用到)。
req IFU向ITLB发送的请求
这是IFU向ITLB发送的查询请求,只有一个量:bits_vaddr,传递需要让ITLB查询的虚拟地址。
resp ITLB返回给IFU的查询结果
这是ITLB返回给IFU的查询结果,包含如下接口:
接口名 | 解释 |
---|---|
excp | 指令的异常信息,包含三个量:访问异常指令af_instr、客户页错误指令gpf_instr、页错误指令pf_instr |
gpaddr | 客户页地址 |
isForVSnonLeafPTE | 指示传入的是否是非叶PTE |
paddr | 指令物理地址 |
pbmt | 指令的基于页的内存类型 |
UncacheInter
该接口在MMIO状态下活跃,负责接收IFU并返回指令码。
toUncache
这是IFU向Uncache发送的请求,除了ready和valid以外,还传送了一个48位数据,即需要获取的指令的物理地址。
fromUncache
这是Uncache给IFU的回复,除了valid以外,还传送一个32位数据,即指令码(可为RVC或RVI指令)
toIbuffer
IFU通过这个接口向Ibuffer写入取指结果。包含以下成员:
接口名 | 解释 |
---|---|
backendException | 是否存在后端异常 |
crossPageIPFFix | 表示跨页异常向量 |
valid | 和一般意义上的valid相区别,表示每条指令是否是合法指令的开始(RVI指令的上半条或者RVC指令) |
enqable | 对每条指令,其为valid并且在预测块的范围内 |
exceptionType | 每个指令的异常类型 |
foldpc | 压缩过后的pc |
ftqOffset | 指令是否在预测块范围中 |
ftqPtr | ftq预测块在FTQ的位置 |
illegalInstr | 这条指令是否为非法指令 |
instrs | 拼接后的指令码 |
isLastInFtqEntry | 判断该指令是否为这个预测块中最后一条有效指令的开始 |
pd | 指令控制信息,包括CFI指令的类型和RVC指令的判定 |
triggered | 指令是否触发前端的trigger |
toBackend_gpaddrMem
这组接口在gpfault发生时使用,由IFU向gpaddrMem传递预测块指针和页错误地址。
接口名 | 解释 |
---|---|
waddr | 传递ftq指针 |
wdata.gpaddr | 传递出错的客户页地址 |
wdata.isForVSnonLeafPTE | 指示是否为非叶PTE |
wen | 类似valid,指示gpaddrMem存在gpfault需要处理 |
io_csr_fsIsOff
指示是否使能了fs.CSR,对非法指令的判断很关键。
rob_commits 来自ROB的提交信息
共分为8个相同结构的rob_commit,包含以下成员
接口名 | 解释 |
---|---|
ftqIdx | 预测块指针 |
ftqOffset | 预测块的大小 |
pmp
和物理内存保护相关,在mmio状态下重发请求时使用。
req
IFU向pmp发起的请求,传递前一步从ITLB查询得到的物理地址。
resp
PMP给IFU的回复结果,包含以下成员
接口名 | 解释 |
---|---|
mmio | 在MMIO空间 |
instr | 对指令的判断结果,当指令不可执行时,该值为true |
mmio_commits
mmioFtqPtr
IFU传递给FTQ的idx,用于查询上一个预测块的MMIO状态
mmioLastCommit
上一个请求是MMIO请求
frontendTrigger
用于设置前端断点
包含以下成员:
debugMode
debug的模式
triggerCanRaiseBpExp
trigger是否可以引起断点异常
tEnableVec
信号数组,表示是否使能对应的trigger
tupdate
表示更新的断点信息,其中包含tdata和addr,addr是请求设置的断点idx。
tdata包括下列成员:
接口名 | 解释 |
---|---|
matchType | 断点匹配类型,等于、大于、小于 |
action | 触发执行的动作 |
tdata2 | 触发端点的基准数值 |
select | 是否选择 |
chain | 是否传导 |
接口时序
FTQ 请求接口时序示例
上图示意了三个 FTQ 请求的示例,req1 只请求缓存行 line0,紧接着 req2 请求 line1 和 line2,当到 req3 时,由于指令缓存 SRAM 写优先,此时指令缓存的读请求 ready 被指低,req3 请求的 valid 和地址保持直到请求被接收。
ICache 返回接口以及到 Ibuffer 和写回 FTQ 接口时序示例
上图展示了指令缓存返回数据到 IFU 发现误预测直到 FTQ 发送正确地址的时序,group0 对应的请求在 f2 阶段了两个缓存行 line0 和 line1,下一拍 IFU 做误预测检查并同时把指令给 Ibuffer,但此时后端流水线阻塞导致 Ibuffer 满,Ibuffer 接收端的 ready 置低,goup0 相关信号保持直到请求被 Ibuffer 接收。但是 IFU 到 FTQ 的写回在 tio_toIbuffer_valid 有效的下一拍就拉高,因为此时请求已经无阻塞地进入 wb 阶段,这个阶段锁存的了 PredChecker 的检查结果,报告 group0 第 4(从 0 开始)个 2 字节位置对应的指令发生了错误预测,应该重定向到 vaddrA,之后经过 4 拍(冲刷和重新走预测器流水线),FTQ 重新发送给 IFU 以 vaddrA 为起始地址的预测块。
MMIO 请求接口时序示例
上图展示了一个 MMIO 请求 req1 的取指令时序,首先 ICache 返回的 tlbExcp 信息报告了这是一条 MMIO 空间的指令(其他例外信号必须为低),过两拍 IFU 向 InstrUncache 发送请求,一段时间后收到响应和 32 位指令码,同拍 IFU 将这条指令作为一个预测块发送到 Ibuffer,同时发送对 FTQ 的写回,复用误预测信号端口,重定向地址为紧接着下一条指令的地址。此时 IFU 进入等待指令执行完成。一段时间后 rob_commits 端口报告此条指令执行完成,并且没有后端重定向。则 IFU 重新发起下一条 MMIO 指令的取指令请求。
测试点汇总
再次声明,本测试点仅供参考,如果有其他测试点需要补充可以告知我们。
建议覆盖点采用功能名称
_测试点名称
命名。
序号 | 功能名称 | 测试点名称 | 描述 |
---|---|---|---|
1 | IFU_RCV_REQ | READY | IFU接收FTQ请求后,设置ready |
2.1.1 | IFU_F1_INFOS | PC | IFU接收FTQ请求后,在F1流水级生成PC |
2.1.2 | IFU_F1_INFOS | CUT_PTR | IFU接收FTQ请求后,在F1流水级生成后续切取缓存行的指针 |
2.2.1 | IFU_F2_INFOS | EXCP_VEC | IFU接收ICache内容后,会根据ICache的结果生成属于每个指令的异常向量 |
2.2.2 | IFU_F2_INFOS | PADDR | IFU接收ICache内容后,会根据ICache的结果生成属于每个端口的物理地址。 |
2.2.3 | IFU_F2_INFOS | GPADDR | IFU接收ICache内容后,会根据ICache的结果生成0号端口的客户物理地址。 |
2.2.4 | IFU_F2_INFOS | MMIO | IFU接收ICache内容后,会根据ICache的结果判断当前取指请求是否属于MMIO空间。 |
2.3.1 | IFU_INSTR_VALID_RANGE | NORMAL | IFU根据FTQ请求,计算无跳转指令有效范围 |
2.3.2 | IFU_INSTR_VALID_RANGE | JUMP | IFU根据FTQ请求,计算跳转指令有效范围 |
2.3.3 | IFU_INSTR_VALID_RANGE | FINAL | IFU综合两类指令有效范围,生成最终指令有效范围 |
2.4 | IFU_INSTR_CUT | CUT | IFU根据上一流水级的切取指针,从缓存行提取初始指令码。 |
3.1.1 | IFU_PREDECODE | CONCAT | 将生成的指令序列拼接成为16x4的指令码序列 |
3.2.1 | IFU_PREDECODE_RVC | RVC | 传入RVC指令,应该判断为RVC |
3.2.2 | IFU_PREDECODE_RVC | RVI | 传入RVI指令,不应判断为RVC |
3.3.1 | IFU_PREDECODE_JMP_TGT | RVC_J | 对传入RVC扩展的J指令,检查计算的偏移 |
3.3.2 | IFU_PREDECODE_JMP_TGT | RVI_J | 对传入RVI扩展的J指令,检查计算的偏移 |
3.3.3 | IFU_PREDECODE_JMP_TGT | RVC_BR | 对传入RVC扩展的BR指令,检查计算的偏移 |
3.3.4 | IFU_PREDECODE_JMP_TGT | RVI_BR | 对传入RVI扩展的BR指令,检查计算的偏移 |
3.4.1 | IFU_PREDECODE_CFI_TYPE | NON_CFI | 对传入的非CFI指令(包括RVC.EBREAK),应该判定为类型0 |
3.4.2 | IFU_PREDECODE_CFI_TYPE | BR | 对传入的BR指令,应该判定为类型1 |
3.4.3 | IFU_PREDECODE_CFI_TYPE | JAL | 对传入的JAL指令,应该判定为类型2 |
3.4.4 | IFU_PREDECODE_CFI_TYPE | JALR | 对传入的JALR指令,应该判定为类型3 |
3.5.1 | IFU_PREDECODE_RET_CALL | NON_CFI_BR | 对传入的非CFI和BR指令,都不应判定为call或者ret |
3.5.2.1.1 | IFU_PREDECODE_RET_CALL | RVI_JAL_CALL | 对传入的RVI.JAL指令,当rd设置为1或5,应当判定该指令为call |
3.5.2.1.2 | IFU_PREDECODE_RET_CALL | RVI_JAL_NOP | 对传入的RVI.JAL指令,当rd设置为1和5之外的值,不应当判定该指令为call或ret |
3.5.2.2 | IFU_PREDECODE_RET_CALL | RVC_JAL_NOP | 对传入的RVC.JAL指令,无论什么情况都不能判定为call或ret |
3.5.3.1.1 | IFU_PREDECODE_RET_CALL | RVI_JALR_CALL | 传入RVI.JALR指令,并且rd为1或5,无论其他取值,都应判定为call |
3.5.3.1.2 | IFU_PREDECODE_RET_CALL | RVI_JALR_RET | 传入RVI.JALR指令,rd不为1和5,rs为1或5,应判定为ret |
3.5.3.1.3 | IFU_PREDECODE_RET_CALL | RVI_JALR_NOP | 对传入的JALR指令,若rd和rs均不为link,则不应判定为ret和call |
3.5.3.2.1 | IFU_PREDECODE_RET_CALL | RVC_JALR_CALL | 传入RVC.JALR指令,必定为call |
3.5.3.2.2.1 | IFU_PREDECODE_RET_CALL | RVC_JR_RET | 传入RVC.JR指令,rs为1或5,应判定为ret |
3.5.3.2.2.2 | IFU_PREDECODE_RET_CALL | RVC_JR_NOP | 传入RVC.JR指令,rs不为1或5,不应判定为ret |
3.6.1 | IFU_PREDECODE_VALID_STARTS | LAST_IS_END | 上一预测块的最后2字节恰为RVC指令或RVI指令的后半部分,按第一位为True推导有效开始向量 |
3.6.2 | IFU_PREDECODE_VALID_STARTS | LAST_NOT_END | 上一预测块的最后2字节上一预测块的最后2字节为RVI指令的前半部分,按第一位为False推导有效开始向量 |
4.1.1 | IFU_RVC_EXPAND | VALID_RVC | 对合法RVC指令,写扩展后的指令码,判断结果为合法指令 |
4.1.2 | IFU_RVC_EXPAND | INVALID_RVC | 对非法RVC指令,写原始指令码,判断结果为非法指令 |
4.1.3 | IFU_RVC_EXPAND | RVI | RVI指令直接写入原始指令即可,判断结果为合法指令 |
5.1.1.1 | IFU_PRECHECK_JAL_MISS | NOP | 预测块中没有JAL指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报JAL预测错误。 |
5.1.2.1 | IFU_PRECHECK_JAL_MISS | CORRECT | 预测块中有JAL指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报JAL预测错误。 |
5.1.2.1 | IFU_PRECHECK_JAL_CHECK | NO_SEL | 预测块中存在JAL指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出JAL预测错误。 |
5.1.2.2 | IFU_PRECHECK_JAL_CHECK | SEL_LATE | 预测块中存在JAL指令,但是BPU预测信息取的跳转指令在第一条JAL指令之后,检查PredChecker是否能检测出JAL预测错误。 |
5.2.1.1 | IFU_PRECHECK_RET_MISS | NOP | 预测块中没有RET指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报RET预测错误。 |
5.2.2.1 | IFU_PRECHECK_RET_MISS | CORRECT | 预测块中有RET指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报RET预测错误。 |
5.2.2.1 | IFU_PRECHECK_RET_CHECK | NO_SEL | 预测块中存在RET指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出RET预测错误。 |
5.2.2.2 | IFU_PRECHECK_RET_CHECK | SEL_LATE | 预测块中存在RET指令,但是BPU预测信息取的跳转指令在第一条RET指令之后,检查PredChecker是否能检测出RET预测错误。 |
5.3.2.2 | IFU_PRECHECK_JALR_MISS | NOP | 预测块中没有JALR指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报JALR预测错误。 |
5.3.2.2 | IFU_PRECHECK_JALR_MISS | CORRECT | 预测块中有JALR指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报JALR预测错误。 |
5.3.2.2 | IFU_PRECHECK_JALR_CHECK | NO_SEL | 预测块中存在JALR指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出RET预测错误。 |
5.3.2.2 | IFU_PRECHECK_JALR_CHECK | SEL_LATE | 预测块中存在JALR指令,但是BPU预测信息取的跳转指令在第一条JALR指令之后,检查PredChecker是否能检测出JALR预测错误。 |
5.4.1 | IFU_PREDCHECK_FIX | NOP | 不存在任何错误的情况下,PredChecker应当保留之前的预测结果。 |
5.4.2 | IFU_PREDCHECK_FIX | BIGGER_FIX | 如果检测到了JAL、RET、JALR类的预测错误,PredChecker应该将有效指令的范围修正为预测块开始至第一条跳转指令。同时,应该将预测跳转的指令位置修正为预测块中的第一条跳转指令。 |
5.4.3 | IFU_PREDCHECK_FIX | SMALLER_NOP | 如果出现了非控制流指令和无效指令的误预测,不应该将预测跳转的指令重新修正到预测块中第一条跳转指令(也即不能扩大范围),因为后续会直接冲刷并重新从重定向的位置取指令,如果这里修正的话,会导致下一预测块传入重复的指令 |
5.5.1.1 | IFU_PREDCHECK_NON_CFI_MISS | NOP | 构造不存在CFI指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误 |
5.5.1.2 | IFU_PREDCHECK_NON_CFI_MISS | CORRECT | 构造存在CFI指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误 |
5.5.2 | IFU_PREDCHECK_NON_CFI_CHECK | ERROR | 构造不存在CFI指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出非CFI预测错误 |
5.6.1.1 | IFU_PREDCHECK_INVALID_MISS | NOP | 构造不存在跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
5.6.1.2 | IFU_PREDCHECK_INVALID_MISS | INVALID_JMP | 构造存在无效跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
5.6.1.3 | IFU_PREDCHECK_INVALID_MISS | CORRECT | 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
5.6.2 | IFU_PREDCHECK_INVALID_MISS | ERROR | 构造无效指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出无效指令预测错误 |
5.7.1.1 | IFU_PREDCHECK_TARGET_MISS | NOP | 构造不存在跳转指令并且未预测跳转的预测信息作输入,测试PredChecker是否会错检目标地址预测错误 |
5.7.1.2 | IFU_PREDCHECK_TARGET_MISS | CORRECT | 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检目标地址预测错误 |
5.7.2 | IFU_PREDCHECK_TARGET_CHECK | ERROR | 构造存在有效跳转指令的预测块和预测跳转但跳转目标计算错误的预测信息作为输入,测试PredChecker能否检出目标地址预测错误 |
5.8 | IFU_PREDCHECK_TARGETS | TARGETS | 随机提供译码信息,检测生成的跳转目标和顺序目标。 |
6.1.1 | IFU_REDIRECT | JAL | 预测请求中存在JAL预测错误,需要冲刷流水线 |
6.1.2 | IFU_REDIRECT | RET | 预测请求中存在RET预测错误,需要冲刷流水线 |
6.1.3 | IFU_REDIRECT | JALR | 预测请求中存在JALR预测错误,需要冲刷流水线 |
6.1.4 | IFU_REDIRECT | NON_CFI | 预测请求中存在非CFI预测错误,需要冲刷流水线 |
6.1.5 | IFU_REDIRECT | INVALID | 预测请求中存在无效指令预测错误,需要冲刷流水线 |
6.1.6 | IFU_REDIRECT | TARGET_FAULT | 预测请求中存在跳转目标错误,需要冲刷流水线 |
7.1 | IFU_CROSS_BLOCK | NORMAL | 连续传入两个预测块,其中有一条32位指令跨两个预测块,后一个预测块的指令开始向量的首位应该为False |
7.2 | IFU_CROSS_BLOCK | ERROR | 当IFU根据PredChecker修复的指令有效范围错判了跨预测块指令时,需要将F3以外的流水级全部冲刷 |
8.1.1 | IFU_TO_IBUFFER | INSTRS | IFU向IBuffer传送扩展后的指令码 |
8.1.2 | IFU_TO_IBUFFER | EXCP | IFU向IBuffer传送每个指令的异常信息 |
8.1.3 | IFU_TO_IBUFFER | PD_INFO | IFU向IBuffer传递每个指令的预译码信息 |
8.1.4 | IFU_TO_IBUFFER | FTQ_PTR | IFU向IBuffer传送FTQ预测块的指针 |
8.1.5 | IFU_TO_IBUFFER | FOLD_PC | IFU向IBuffer传送折叠的PC |
8.1.6 | IFU_TO_IBUFFER | VALID_STARTS | IFU向IBuffer传送表示指令有效和指令是否为指令开始的向量 |
8.2.1 | IFU_TO_GPAMEM | GPADDR | 客户页错误发生时,IFU应将gpaMem的valid拉高且填入gpaddr |
9.1.1 | IFU_OVERRIDE_FLUSH_CHECK | BEFORE | 当冲刷指针在取指令的指针之前时,IFU能够对流水线进行冲刷。 |
9.1.2 | IFU_OVERRIDE_FLUSH_CHECK | NOT_BEFORE | 当冲刷指令在取指令的指针相同或之后时,IFU不能对流水线进行冲刷。 |
9.2.1 | IFU_OVERRIDE_FLUSH | S2 | 当BPU的S2流水级出现错误,并且当前取指指针在错误执行路径上时,需要对IFU的F0流水级进行冲刷 |
9.2.2 | IFU_OVERRIDE_FLUSH | S3 | 当BPU的S3流水级出现错误,并且当前取指指针在错误执行路径上时,需要对IFU的F0流水级进行冲刷 |
10.1.1 | IFU_WB_FTQ | PCS | IFU的WB流水级,需要向FTQ写回指令PC |
10.1.2 | IFU_WB_FTQ | PD_INFO | IFU的WB流水级,需要向FTQ写回每个指令的预译码信息 |
10.1.3 | IFU_WB_FTQ | ERR_POS | IFU的WB流水级,需要向FTQ写回BPU错误预测的指令位置 |
10.1.4 | IFU_WB_FTQ | TARGET | IFU的WB流水级,需要向FTQ写回该预测块的正确跳转地址 |
10.1.5 | IFU_WB_FTQ | RANGE | IFU的WB流水级,需要向FTQ写回预测块的正确指令范围 |
10.1.6 | IFU_WB_FTQ | FTQ_PTR | IFU的WB流水级,需要向FTQ传递预测块的FTQ指针 |
11.1.1 | IFU_MMIO_RESET | FIRST_MMIO | IFU收到的第一条MMIO请求可以直接查询Instr Uncache |
11.2.1 | IFU_MMIO_SEND_UNCACHE | BLOCK | IFU收到MMIO请求后,查询FTQ,如果前面还有尚未提交的指令,持续等待 |
11.2.2 | IFU_MMIO_SEND_UNCACHE | FREE | 如果查到FTQ不再有未提交的指令,则IFU将指令发送给Instr Uncache |
11.2.3 | IFU_MMIO_SEND_UNCACHE | NC | 对于属性为NC的内存区域,无需等待前一条指令完成提交 |
11.3.1 | IFU_MMIO_RESEND_ITLB | RESEND | 遇到一次无法查询完毕的RVI指令时,需要向ITLB查询获得新增指令的物理地址 |
11.3.2.1 | IFU_MMIO_RESEND_ITLB | EXCP | IFU查询ITLB出现异常时,应当将异常信息发送到IBuffer,然后等待ROB提交完成 |
11.3.2.2 | IFU_MMIO_RESEND_ITLB | PADDR | IFU查询ITLB正常返回物理地址时,IFU继续向PMP请求检查 |
11.3.3.1 | IFU_MMIO_PMP_RECHECK | STATUS_DIFF | IFU检查PMP之后如果发现重发请求状态和上一条请求状态不一致,是访问异常,需要将异常直接发送到IBuffer |
11.3.3.2 | IFU_MMIO_PMP_RECHECK | EXCP | PMP检查出现异常的情况下,也需要将异常直接发送到IBuffer并等待ROB提交。 |
11.3.3.3 | IFU_MMIO_PMP_RECHECK | RESEND_UNCACHE | PMP检查若无异常,则向Instr Uncache发送请求获取指令码的后半部分。 |
11.4 | IFU_MMIO_TO_IBUFFER | INSTR | IFU在获得完整数据后,截取获得指令码,以每个预测块一条指令的形式发送给IBuffer |
11.5.1 | IFU_MMIO_FLUSH_NON_CFI | RVI | 如果是RVI指令,传递给FTQ的冲刷请求应该重定向到PC+4 |
11.5.2 | IFU_MMIO_FLUSH_NON_CFI | RVC | 如果是RVC指令,传递给FTQ的冲刷请求应该重定向到PC+2 |
12.1.1 | IFU_FRONTEND_TRIGGER | SELECT1 | 给定tdata1的select位为1,随机构造其它输入,检查断点是否没有触发 |
12.1.2.1 | IFU_FRONTEND_TRIGGER_SELECT0 | MATCH | 给定tdata1的select位为0,构造PC与tdata2数据的关系同tdata2的match位匹配的输入,检查断点是否触发 |
12.1.2.2 | IFU_FRONTEND_TRIGGER_SELECT0 | NOT_MATCH | 给定tdata1的select位为0,构造PC与tdata2数据的关系同tdata2的match位不匹配的输入,检查断点是否触发 |
12.2.1 | IFU_FRONTEND_TRIGGER_CHAIN | SELF | 对每个trigger,在满足PC断点触发条件的情况下,设置chain位,检查断点是否一定不触发。 |
12.2.2.1 | IFU_FRONTEND_TRIGGER_CHAIN | NOT_HIT | 对两个trigger,仅设置前一个trigger的chain位,设置后一个trigger命中而前一个未命中,检查后一个trigger是否一定不触发。 |
12.2.2.2 | IFU_FRONTEND_TRIGGER_CHAIN | HIT | 对两个trigger,仅设置前一个trigger的chain位且均命中,检查后一个trigger是否触发。 |
-
需要特别指出的是,Svpbmt 扩展增加了一个
NC
属性,其代表该内存区域是不可缓存的、但是幂等的,这意味着我们可以对NC
的区域进行推测执行,也就是不需要“等待前面的指令提交”就可以向总线发送取指请求,表现为状态机跳过等待状态。实现见 #3944。 ↩︎ -
截至本文档撰写的版本,这个功能尚未实现(不过在比较新的提交里已经实现了),后续新的rtl加入后会去掉该下划线,或者读者可以自行编译香山源码生成rtl以支持这一特性。 ↩︎
-
在过去(riscv-debug-spec-draft,对应 XiangShan 2024.10.05 合入的 PR#3693 前)的版本中,Chain 还需要满足两个 Trigger 的
mcontrol.timing
是相同的。而在新版(riscv-debug-spec-v1.0.0)中,mcontrol.timing
被移除。目前 XiangShan 的 scala 实现仍保留了这一位,但其值永远为 0 且不可写入,编译生成的 verilog 代码中没有这一位。参考:https://github.com/riscv/riscv-debug-spec/pull/807。 ↩︎
3.1 - F3PreDecoder
子模块:F3PreDecoder模块简介
这个模块是从PreDecoder中时序优化出来的,负责判定CFI指令的类型
F3PreDecoder功能介绍
CFI指令类型判定
要想确定CFI指令类型,只需要分别尝试匹配JAL、JALR、BR和他们的RVC版本即可,注意,RVC的EBREAK 不应该被视为CFI指令。在匹配的过程中,自然CFI指令的类型就被甄别出来了。在这一步中,我们将所有指令分到如下四类brType中:
CFI指令类型 | brType类型编码 |
---|---|
非CFI | 00 |
branch指令 | 01 |
jal指令 | 10 |
jalr指令 | 11 |
ret、call判定
然后,我们需要判断是否为call或者ret,这可以通过rd和rs的取值来考察,具体来说,RISCV的RVI指令中,提供了对rd和rs取值的约定, 当二者取到link寄存器的序号(x1为标准的返回地址寄存器,x5为备用的link寄存器),分别对应着压栈和弹栈。详细的对应情况如下:
F3Predecoder接口说明
in_instr: 传递 16 x 4B的拼接指令码
out_pd:每条指令的预译码信息,在F3Predecoder分析得到的是brType、isCall和isRet
F3PreDecoder子模块测试点和功能点
功能点1 CFI指令类型判定
要想确定CFI指令类型,只需要分别尝试匹配JAL、JALR、BR和他们的RVC版本即可,注意,RVC的EBREAK 不应该被视为CFI指令。
序号 | 名称 | 描述 |
---|---|---|
1.1 | 非CFI判定 | 对传入的非CFI指令(包括RVC.EBREAK),应该判定为类型0 |
1.2 | BR判定 | 对传入的BR指令,应该判定为类型1 |
1.3 | JAL判定 | 对传入的JAL指令,应该判定为类型2 |
1.4 | JALR判定 | 对传入的JALR指令,应该判定为类型3 |
功能点2 ret、call判定
然后,需要判断是否为call或者ret,这可以通过rd和rs的取值来考察。当然,首先必须得满足无条件跳转指令。
对于类型2,只有不为RVC指令且目的寄存器rd为link寄存器(x1或x5)时,才为Call。
对于类型3,在RVI指令下,当rd为link寄存器时,必为Call。当rs为link寄存器且rd不为时,必为Ret。 在RVC指令下,对C.JALR指令,为call,对C.JR指令,当rs1为link时,为Ret
序号 | 名称 | 描述 |
---|---|---|
2.1 | 非CFI和BR不判定 | 对传入的非CFI和BR指令,都不应判定为call或者ret |
2.2.1.1 | RVI.JAL判定call | 对传入的RVI.JAL指令,当rd设置为1或5,应当判定该指令为call |
2.2.1.2 | RVI.JAL例外 | 对传入的RVI.JAL指令,当rd设置为1和5之外的值,不应当判定该指令为call或ret |
2.2.2 | RVC.JAL不判定 | 对传入的RVC.JAL指令,无论什么情况都不能判定为call或ret |
2.3.1.1 | RVI.JALR和rd为link | 传入RVI.JALR指令,并且rd为1或5,无论其他取值,都应判定为call |
2.3.1.2 | RVI.JALR且仅rs为link | 传入RVI.JALR指令,rd不为1和5,rs为1或5,应判定为ret |
2.3.1.3 | RVI.JALR无link | 对传入的JALR指令,若rd和rs均不为link,则不应判定为ret和cal |
2.3.2.1 | RVC.JALR为Ret | 传入RVC.JALR指令,必定为call |
2.3.2.2.1 | RVC.JR且rs为link | 传入RVC.JR指令,rs为1或5,应判定为ret |
2.3.2.2.2 | RVC.JR且rs不为link | 传入RVC.JR指令,rs不为1或5,不应判定为ret |
测试点汇总
序号 | 功能 | 名称 | 描述 |
---|---|---|---|
1.1 | CFI指令类型判定 | 非CFI判定 | 对传入的非CFI指令(包括RVC.EBREAK),应该判定为类型0 |
1.2 | CFI指令类型判定 | BR判定 | 对传入的BR指令,应该判定为类型1 |
1.3 | CFI指令类型判定 | JAL判定 | 对传入的JAL指令,应该判定为类型2 |
1.4 | CFI指令类型判定 | JALR判定 | 对传入的JALR指令,应该判定为类型3 |
2.1 | ret、call判定 | 非CFI和BR不判定 | 对传入的非CFI和BR指令,都不应判定为call或者ret |
2.2.1.1 | ret、call判定 | RVC.JAL判定call | 对传入的RVC.JAL指令,当rd设置为1或5,应当判定该指令为call |
2.2.1.2 | ret、call判定 | RVC.JAL例外 | 对传入的RVC.JAL指令,当rd设置为1和5之外的值,不应当判定该指令为call或ret |
2.2.2 | ret、call判定 | RVI.JAL不判定 | 对传入的RVI.JAL指令,无论什么情况都不能判定为call或ret |
2.3.1.1 | ret、call判定 | RVI.JALR和rd为link | 传入RVI.JALR指令,并且rd为1或5,无论其他取值,都应判定为call |
2.3.1.2 | ret、call判定 | RVI.JALR且仅rs为link | 传入RVI.JALR指令,rd不为1和5,rs为1或5,应判定为ret |
2.3.1.3 | ret、call判定 | RVI.JALR无link | 对传入的JALR指令,若rd和rs均不为link,则不应判定为ret和cal |
2.3.2.1 | ret、call判定 | RVC.JALR为Ret | 传入RVC.JALR指令,必定为call |
2.3.2.2.1 | ret、call判定 | RVC.JR且rs为link | 传入RVC.JR指令,rs为1或5,应判定为ret |
2.3.2.2.2 | ret、call判定 | RVC.JR且rs不为link | 传入RVC.JR指令,rs不为1或5,不应判定为ret |
3.2 - FrontendTrigger
FrontendTrigger子模块
该子模块的主要作用是在前端设置硬件断点和检查。
该模块的输入pc有一个隐含条件,那就是这个pc是通过ftq传递的startAddr计算出来的。
FrontendTrigger功能介绍
断点设置和断点检查
在IFU的FrontendTrigger模块里共4个Trigger,编号为0,1,2,3,每个Trigger的配置信息(断点类型、匹配地址等)保存在tdata寄存器中。
当软件向CSR寄存器tselect
、tdata1/2
写入特定的值时,CSR会向IFU发送tUpdate请求,更新FrontendTrigger内的tdata
寄存器中的配置信息。
目前前端的Trigger仅可以配置成PC断点mcontrol.tdata1
寄存器的select位为0;当select=1时,该Trigger将永远不会命中,且不会产生异常)。
在取指时,IFU的F3流水级会向FrontendTrigger模块发起查询并在同一周期得到结果。后者会对取指块内每一条指令在每一个Trigger上做检查,
当指令的PC和tdata2
寄存器内容的关系满足mcontrol.match
位所指示的关系(香山支持match位为0、2、3,对应等于、大于等于、小于)时,
该指令会被标记为Trigger命中,随着执行在后端产生断点异常,进入M-Mode或调试模式。
链式断点
根据RISCV的debug spec,香山实现的是mcontrol6。
当它们对应的Chain位被置时,只有当该Trigger和编号在它后面一位的Trigger同时命中,且timing配置相同时(在最新的手册中,这一要求已被删除),处理器才会产生异常。
在过去(riscv-debug-spec-draft,对应 XiangShan 2024.10.05 合入的 PR#3693 前)的版本中,Chain 还需要满足两个 Trigger 的 mcontrol.timing
是相同的。而在新版(riscv-debug-spec-v1.0.0)中,mcontrol.timing
被移除。目前 XiangShan 的 scala 实现仍保留了这一位,但其值永远为 0 且不可写入,编译生成的 verilog 代码中没有这一位。
FrontendTrigger 接口说明
设计上并没有提供一个或一组对外的接口来查询某个断点的状态,因此,要在测试中检查断点状态,要么需要检查内部信号的情况(仓库中提供的构建脚本已经暴露了所有内部信号),要么通过具体执行过程中,断点的触发情况来判定。
输入接口
主要分为控制接口和执行信息(目前执行信息只有pc)
控制接口 io_frontendTrigger
本接口存储了frontendTrigger的控制信息,包含以下信号/信号组:
debugMode
当前是否处于debug模式下
tEnableVec
对FrontendTrigger的每个断点,指示其是否有效。
tUpdate
更新断点的控制信息,包含以下信号/信号组:
valid:此次更新是否有效/是否更新。
bits_addr:此次更新的是哪个断点(0~3)
bits_tdata_action:断点触发条件达成后的行为
bits_tdata_chain:断点是否链式传导
bits_tdata_matchType:断点匹配类型(等于、大于、小于三种)
bits_tdata_select:目前为止,select为0时为pc断点
bits_tdata_tdata2:用于和PC比较的基准值
triggerCanRaiseBpExp
trigger是否可以引起异常
pc
pc有一个隐含条件,就是16条指令的pc必定是连续的
输出接口
triggered:16条指令的断点触发情况。
FrontEndTrigger 测试点和功能点
功能点1 设置断点和断点检查
FrontEndTrigger目前仅支持设置PC断点,这通过设置断点的tdata1寄存器的select位为0实现。 同时,tdata2寄存器的mcontrol位负责设置指令PC和tdata2寄存器的地址需要满足的关系, 关系满足时,该指令会被标记为trigger命中。
所以,基于以上功能描述,我们需要测试:
select位为1时,断点是否永远不会触发。
select位为0时,当PC和tdata2的数据的关系满足tdata2的match位时,是否会设置断点。
select位为0时,当PC和tdata2的数据的关系不满足tdata2的match位时,断点是否一定不会触发。
综上所述,我们在这一功能点设计的测试点如下:
序号 | 名称 | 描述 |
---|---|---|
1.1 | select1判定 | 给定tdata1的select位为1,随机构造其它输入,检查断点是否没有触发 |
1.2.1 | select0关系匹配判定 | 给定tdata1的select位为0,构造PC与tdata2数据的关系同tdata2的match位匹配的输入,检查断点是否触发 |
1.2.2 | select0关系不匹配判定 | 给定tdata1的select位为0,构造PC与tdata2数据的关系同tdata2的match位不匹配的输入,检查断点是否触发 |
功能点2 链式断点
当某一个trigger的chain位被置后,当其后的trigger的chain位未设置,且两个trigger均命中并且两个trigger的timing相同时,后一个trigger才会触发。
对0号trigger,不需要考虑链式的情况
由此,我们可以设置几种测试点:
序号 | 名称 | 描述 |
---|---|---|
2.1 | chain位测试 | 对每个trigger,在满足PC断点触发条件的情况下,设置chain位,检查断点是否一定不触发。 |
2.2.1 | 未命中测试 | 对两个trigger,仅设置前一个trigger的chain位 |
2.2.2 | 命中测试 | 对两个trigger,仅设置前一个trigger的chain位 |
测试点汇总
序号 | 功能 | 名称 | 描述 |
---|---|---|---|
1.1 | 断点设置和检查 | select1判定 | 给定tdata1的select位为1,随机构造其它输入,检查断点是否没有触发 |
1.2.1 | 断点设置和检查 | select0关系匹配判定 | 给定tdata1的select位为0,构造PC与tdata2数据的关系同tdata2的match位匹配的输入,检查断点是否触发 |
1.2.2 | 断点设置和检查 | select0关系不匹配判定 | 给定tdata1的select位为0,构造PC与tdata2数据的关系同tdata2的match位不匹配的输入,检查断点是否触发 |
2.1 | 链式断点 | chain位测试 | 对每个trigger,在满足PC断点触发条件的情况下,设置chain位,检查断点是否一定不触发 |
2.2.1 | 链式断点 | 未命中测试 | 对两个trigger,仅设置前一个trigger的chain位,设置后一个trigger命中而前一个未命中,检查后一个trigger是否一定不触发 |
2.2.2 | 链式断点 | 命中测试 | 对两个trigger,仅设置前一个trigger的chain位,检查后一个trigger是否触发 |
3.3 - PredChecker
子模块:PredChecker简介
分支预测检查器PredChecker接收来自IFU的预测块信息(包括预测跳转指令在预测块的位置、预测的跳转目标、预译码得到的指令信息、指令PC以及预译码得到的跳转目标偏移等),在模块内部检查五种类型的分支预测错误。模块内部分为两个流水线stage,分别输出信息,第一个stage输出给IFU的f3阶段,用于修正预测块的指令范围和预测结果。第二个stage输出给wb阶段,用于在发现分支预测错误时产生前端重定向以及写回给FTQ(Fetch Target Queue)正确的预测信息。
PredChecker功能介绍
JAL预测错误检查
jal指令预测错误的条件是,预测块中有一条有效jal指令(由预译码信息给出),但是要么这个预测块没有预测跳转,要么此预测块预测跳转的指令在这条jal指令之后(即这条jal指令没有被预测跳转)。
JALR预测错误检查
jalr指令预测错误的条件是,预测块中有一条有效jalr指令(由预译码信息给出),而且这个指令不是ret指令,但是要么这个预测块没有预测跳转,要么此预测块预测跳转的指令在这条jalr指令之后(即这条jalr指令没有被预测跳转)。
RET预测错误检查
ret指令预测错误的条件是,预测块中有一条有效ret指令(由预译码信息给出),但是要么这个预测块没有预测跳转,要么此预测块预测跳转的指令在这条ret指令之后(即这条ret指令没有被预测跳转)。
更新指令有效范围向量
PredChecker在检查出Jal/Ret/JALR指令预测错误时,需要重新生成指令有效范围向量,有效范围截取到Jal/Ret指令的位置,之后的bit全部置为0。 需要注意的是,jal和ret指令的错误检查都会导致指令有效范围的缩短, 所以需要重新生成指令有效范伟fixedRange,同时修复预测结果。需要注意的是,这个修复只会针对RET预测错误和JAL预测错误导致的范围错误,对于后续要介绍的非CFI(控制流指令)预测错误和无效指令预测错误,尽管他们会造成预测块的范围偏小,但是不会进行修复,而是直接在这里进行重定向。这样,重定向后重新取的指令会从这个出错的指令开始。
非CFI预测错误检查
非CFI预测错误的条件是被预测跳转的指令根据预译码信息显示不是一条CFI指令。
无效指令预测错误检查
无效指令预测错误的条件是被预测的指令的位置根据预译码信息中的指令有效向量显示不是一条有效指令的开始。
目标地址预测错误检查
目标地址预测错误的条件是,被预测的是一条有效的jal或者branch指令,同时预测的跳转目标地址和由指令码计算得到的跳转目标不一致。
分级输出检查结果
以上PredChecker检查结果会分为两级分别输出,前面已经提到,Jal/Ret指令由于需要重新生成指令有效范围向量和重新指定预测位置, 所以需要在错误产生的当拍(F3)直接输出结果到Ibuffer用于及时更正进入后端的指令 。而由于时序的考虑,其他错误信息(比如五种错误的错误位置、正确的跳转地址等)则是等到下一拍(WB)阶段才返回给IFU做前端重定向。
PredChecker接口说明
输入接口
fire_in:这个信号可以简单认为是模块有效性的控制信号。
ftqOffset:来自BPU(分支预测单元)的预测信息,表示该预测块的跳转指令是否存在(valid),以及跳转指令的序号(bits)。
instrRange:来自PreDecode的预译码信息,对每条指令,表示该指令是否在预测块的有效指令范围内。
instrValid:来自PreDecode的预译码信息,表示的是对于每条32位的拼接指令,其是否为一条有效的指令(即低16位为一条RVC指令,或者整个32位为一条RVI指令)。
jumpOffset:来自PreDecode的预译码信息,如果某一指令为跳转指令,jumpOffset表示这个指令的跳转目标。
pc:指令的pc。
pds:来自PreDecode模块的预译码信息,包含指令的brType、是否为Ret(isRet)、是否为RVC指令(isRVC)。
target:来自BPU,下个预测块的开始地址。
输出接口
第一阶段输出
fixedRange:修复的指令有效范围向量,对每条指令i,fixedRange_i为真表示这条指令是否在当前预测块的有效指令范围内
fixedTaken:修复过后的CFI指令选取情况,对每条指令,fixedTaken_i为真表示这条指令是否是这个预测块的第一条CFI指令
第二阶段输出
fixedMissPred:对每条指令,PredChecker检查出的存在预测错误的情况,fixedMissPred_i为真表示这条指令存在预测错误
fixedTarget:对每条指令,给出修复过的下一条指令的位置(可以是常规的pc+2或+4,或者如果是跳转指令,给出跳转目标)。
jalTarget:对每条指令,给出跳转目标。
faultType:每条指令的错误类型,取指范围包含noFault,jalFault,retFault,targetFault,notCFIFault,invalidTaken,jalrFault,分别对应数字0~6
PredChecker测试点和功能点
功能点1 BPU预测信息的JAL预测错误检查
PredChecker会对传入的预测块进行JAL预测错误预检查并修正指令有效范围向量和预测的跳转指令。
对这一模块的测试,我们分为两部分:正确的输入是否会误检和确有JAL检测错误的预测块输入能否检出。
对于误检,我们设计如下的测试点:
序号 | 名称 | 描述 |
---|---|---|
1.1.1 | 误检测试1 | 预测块中没有JAL指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报JAL预测错误。 |
1.1.2 | 误检测试2 | 预测块中有JAL指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报JAL预测错误。 |
对于JAL预测错误的正确检验,我们设计如下的测试点:
序号 | 名称 | 描述 |
---|---|---|
1.2.1 | 存在JAL未预测 | 预测块中存在JAL指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出JAL预测错误。 |
1.2.2 | 预测的JAL并非第一条 | 预测块中存在JAL指令,但是BPU预测信息取的跳转指令在第一条JAL指令之后,检查PredChecker是否能检测出JAL预测错误。 |
功能点2 BPU预测信息的RET预测错误检查
PredChecker会对传入的预测块进行RET预测错误预检查并修正指令有效范围向量和新的预测结果。
和JAL预测错误类似,我们也按照误检和正检来构造。
对于误检,我们设计如下的测试点:
序号 | 名称 | 描述 |
---|---|---|
2.1.1 | 误检测试1 | 预测块中没有RET指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报RET预测错误。 |
2.1.2 | 误检测试2 | 预测块中有RET指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报RET预测错误。 |
对于RET预测错误的正确检出,我们设计如下的测试点:
序号 | 名称 | 描述 |
---|---|---|
2.2.1 | 存在RET未预测 | 预测块中存在RET指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出RET预测错误。 |
2.2.2 | 预测的跳转并非第一条 | 预测块中存在RET指令,但是BPU预测信息取的跳转指令在第一条RET指令之后,检查PredChecker是否能检测出RET预测错误。 |
功能点3 BPU预测信息的JALR预测错误检查
PredChecker会对传入的预测块进行JALR预测错误预检查并修正指令有效范围向量和新的预测结果。
和JAL/RET预测错误类似,我们也按照误检和正检来构造。
对于误检,我们设计如下的测试点:
序号 | 名称 | 描述 |
---|---|---|
3.1.1 | 误检测试1 | 预测块中没有JALR指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报JALR预测错误。 |
3.1.2 | 误检测试2 | 预测块中有JALR指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报JALR预测错误。 |
对于JALR预测错误的正确检出,我们设计如下的测试点:
序号 | 名称 | 描述 |
---|---|---|
3.2.1 | 存在JALR未预测 | 预测块中存在JALR指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出RET预测错误。 |
3.2.2 | 预测的跳转并非第一条 | 预测块中存在JALR指令,但是BPU预测信息取的跳转指令在第一条JALR指令之后,检查PredChecker是否能检测出JALR预测错误。 |
功能点4 更新指令有效范围向量和预测跳转的指令
PredChecker在检查出Jal/Ret/Jalr指令预测错误时,需要重新生成指令有效范围向量, 有效范围截取到Jal/Ret/Jalr指令的位置,之后的bit全部置为0。 同时,还需要根据每条指令的预译码信息和BPU的预测信息修复预测跳转的结果。
所以,根据功能要求,我们可以划分出三类情况,分别是预测的有效范围和取用的跳转指令正确的情况, 由于RET和JAL预测错误引起的有效范围偏大和错判非跳转指令和无效指令引起的有效范围偏小。
序号 | 名称 | 描述 |
---|---|---|
4.1 | 有效范围无误 | 不存在任何错误的情况下,PredChecker应当保留之前的预测结果。 |
4.2 | RET、JAL、JALR预测错误引起的范围偏大 | 如果检测到了JAL、RET、JALR类的预测错误,PredChecker应该将有效指令的范围修正为预测块开始至第一条跳转指令。同时,应该将预测跳转的指令位置修正为预测块中的第一条跳转指令。 |
4.3 | 非CFI和无效指令引起的预测范围偏小 | 如果出现了非控制流指令和无效指令的误预测,不应该将预测跳转的指令重新修正到预测块中第一条跳转指令,因为后续会直接冲刷并重新从重定向的位置取指令,如果这里修正的话,会导致下一预测块传入重复的指令 |
功能点5 非CFI预测错误检查
非CFI预测错误的条件是被预测跳转的指令根据预译码信息显示不是一条CFI指令。
要检验这一功能,我们仍然按误检和正确检验来设计测试点:
序号 | 名称 | 描述 |
---|---|---|
5.1.1 | 误检测试1 | 构造不存在CFI指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误 |
5.1.2 | 误检测试2 | 构造存在CFI指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误 |
5.2 | 正确检测测试 | 构造不存在CFI指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出非CFI预测错误 |
功能点6 无效指令预测错误检查
无效指令预测错误的条件是被预测的指令的位置根据预译码信息中的指令有效向量显示不是一条有效指令的开始。
要检验这一功能,我们按照误检和正确检测来设计测试点:
序号 | 名称 | 描述 |
---|---|---|
6.1.1 | 误检测试1 | 构造不存在跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
6.1.2 | 误检测试2 | 构造存在无效跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
6.1.3 | 误检测试3 | 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
6.2 | 正确检测测试 | 构造无效指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出无效指令预测错误 |
功能点7 目标地址预测错误检查
目标地址预测错误的条件是,被预测的是一条有效的jal或者branch指令, 同时预测的跳转目标地址和由指令码计算得到的跳转目标不一致。
和先前的思路一样,我们仍然按误检和检出两类组织测试点:
序号 | 名称 | 描述 |
---|---|---|
7.1.1 | 误检测试1 | 构造不存在跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检目标地址预测错误 |
7.1.2 | 误检测试2 | 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检目标地址预测错误 |
7.2 | 正确检测测试 | 构造存在有效跳转指令的预测块和预测跳转但跳转目标计算错误的预测信息作为输入,测试PredChecker能否检出目标地址预测错误 |
功能点8 生成跳转和顺序目标
PredChecker还需要负责生成跳转和顺序目标。
我们通过随机生成译码信息进行测试
序号 | 名称 | 描述 |
---|---|---|
8.1 | 随机测试 | 随机提供译码信息,检测生成的跳转目标和顺序目标。 |
测试点汇总
综上所述,所有的测试点如下:
序号 | 功能 | 名称 | 描述 |
---|---|---|---|
1.1.1 | BPU预测信息的JAL预测错误检查 | 误检测试1 | 预测块中没有JAL指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报JAL预测错误。 |
1.1.2 | BPU预测信息的JAL预测错误检查 | 误检测试2 | 预测块中有JAL指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报JAL预测错误。 |
1.2.1 | BPU预测信息的JAL预测错误检查 | 存在JAL未预测 | 预测块中存在JAL指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出JAL预测错误。 |
1.2.2 | BPU预测信息的JAL预测错误检查 | 预测的JAL并非第一条 | 预测块中存在JAL指令,但是BPU预测信息取的跳转指令在第一条JAL指令之后,检查PredChecker是否能检测出JAL预测错误。 |
2.1.1 | BPU预测信息的RET预测错误检查 | 误检测试1 | 预测块中没有RET指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报RET预测错误。 |
2.1.2 | BPU预测信息的RET预测错误检查 | 误检测试2 | 预测块中有RET指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报RET预测错误。 |
2.2.1 | BPU预测信息的RET预测错误检查 | 存在RET未预测 | 预测块中存在RET指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出RET预测错误。 |
2.2.2 | BPU预测信息的RET预测错误检查 | 预测的跳转并非第一条 | 预测块中存在RET指令,但是BPU预测信息取的跳转指令在第一条RET指令之后,检查PredChecker是否能检测出RET预测错误。 |
3.1.1 | BPU预测信息的JALR预测错误检查 | 误检测试1 | 预测块中没有JALR指令且BPU预测信息也没有取用任何跳转指令的输入,检查PredChecker是否会误报JALR预测错误。 |
3.1.2 | BPU预测信息的JALR预测错误检查 | 误检测试2 | 预测块中有JALR指令且BPU预测信息取用的正是本条跳转指令的输入,检查PredChecker是否会误报JALR预测错误。 |
3.2.1 | BPU预测信息的JALR预测错误检查 | 存在JALR未预测 | 预测块中存在JALR指令,但是BPU预测信息未预测跳转,检查PredChecker是否能检测出RET预测错误。 |
3.2.2 | BPU预测信息的JALR预测错误检查 | 预测的跳转并非第一条 | 预测块中存在JALR指令,但是BPU预测信息取的跳转指令在第一条JALR指令之后,检查PredChecker是否能检测出JALR预测错误。 |
4.1 | 更新指令有效范围向量和预测跳转的指令 | 有效范围无误 | 不存在任何错误的情况下,PredChecker应当保留之前的预测结果。 |
4.2 | 更新指令有效范围向量和预测跳转的指令 | RET和JAL预测错误引起的范围偏大 | 如果检测到了JAL或RET类的预测错误,PredChecker应该将有效指令的范围修正为预测块开始至第一条跳转指令。同时,应该将预测跳转的指令位置修正为预测块中的第一条跳转指令。 |
4.3 | 更新指令有效范围向量和预测跳转的指令 | 范围偏小不修正 | 如果出现了非控制流指令和无效指令的误预测,不应该将预测跳转的指令重新修正到预测块中第一条跳转指令,因为后续会直接冲刷并重新从重定向的位置取指令,如果这里修正的话,会导致下一预测块传入重复的指令。 |
5.1.1 | 非CFI预测错误检查 | 误检测试1 | 构造不存在CFI指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误 |
5.1.2 | 非CFI预测错误检查 | 误检测试2 | 构造存在CFI指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误 |
5.2 | 非CFI预测错误检查 | 正确检测测试 | 构造不存在CFI指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出非CFI预测错误 |
6.1.1 | 无效指令预测错误检查 | 误检测试1 | 构造不存在跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
6.1.2 | 无效指令预测错误检查 | 误检测试2 | 构造存在无效跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
6.1.3 | 无效指令预测错误检查 | 误检测试3 | 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误 |
6.2 | 无效指令预测错误检查 | 正确检测测试 | 构造无效指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出无效指令预测错误 |
7.1.1 | 目标地址预测错误检查 | 误检测试1 | 构造不存在跳转指令并且未预测跳转的预测信息作输入,测试PredChecker是否会错检目标地址预测错误 |
7.1.2 | 目标地址预测错误检查 | 误检测试2 | 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检目标地址预测错误 |
7.2 | 目标地址预测错误检查 | 正确检测测试 | 构造存在有效跳转指令的预测块和预测跳转但跳转目标计算错误的预测信息作为输入,测试PredChecker能否检出目标地址预测错误 |
8.1 | 生成跳转和顺序目标 | 随机测试 | 随机提供译码信息,检测生成的跳转目标和顺序目标。 |
3.4 - PreDecode
子模块:PreDecoder简介
预译码器PreDeocoder接受初始指令码并进行指令码拼接,拼接之后对每个指令码查询预译码表产生预译码信息,预译码信息包括该位置是否是有效指令开始、CFI指令类型、是否是RVC指令、是否是Call指令以及是否是Ret指令。预译码器会产生两种有效指令开始的向量,一种是默认第1个二字节必为有效指令开始,另一种是默认第2个二字节必为有效指令的开始,最终的选择在IFU端做。
所以,预译码器接收的输入是: 17 x 2B的初始指令码,这个2字节的初始指令码要么是一条RVC指令,要么是一条RVI指令的前半或后半部分。
预译码器的输出是:16x4B的拼接指令码;对每个4B指令码,该条指令是否为RVI或RVC指令(RVC指令只考虑该4B的低2B);对每个4B指令码,该条指令的跳转偏移;两个16位的有效指令开始向量,其中第一种向量假定当前预测块的起始2字节为一条有效指令的开始,而第二种向量假定当前预测块的起始2字节为一条有效RVI指令的结束(但是由于第二种向量的前两位必然为0和1,所以编译优化后,第二种向量实际只有14个信号,表示2-15位;同理,第1种向量的第0位因为恒为1,所以也被优化)
功能介绍
指令码生成
预译码器接受来自IFU完成指令切分的17 × 2字节的初始指令码,并以4字节为窗口,2字节为步进长度, 从第1个2字节开始,直到第16个2字节,选出总共16个4字节的指令码。
预译码信息生成
预译码器根据指令码产生预译码信息,主要包括:是否是RVC指令、是否是CFI指令、 CFI指令类型(branch/jal/jalr/call/ret)、CFI指令的目标地址计算偏移。
首先是判断是否是RVC指令,RVC指令的具体格式参阅RISCV手册的描述:
其中,决定指令是否为RVC的部分在于指令的[1, 0]两位,不为3的情况下都是RVC指令。
其余的指令性质判定功能(CFI类型、是否为call和ret)被时序优化到了F3PreDecoder中,不过也可以认为是PreDecoder的一部分,可以设置测试点进行测试
最后比较麻烦的是CFI指令的目标地址计算偏移,主要是对J和BR分支指令进行的计算,这需要综合RVI和RVC中jal和br指令的结构。 首先,是手册中对于C.J的描述
这里对imm立即数的注解是,立即数的每一位最后对应到的是偏移的哪一位。
所以,可以认为立即数是这么重组的:
instr(12) + instr(8) + instr(10, 9) + instr(6) + instr(7) + instr(2) + instr(11) +instr(5,3) + “0”
而RVI中,对于JAL指令,是这么定义的:
我们可以类似地计算立即数。
同样的,我们可以查询手册,参考BR类指令的立即数计算RVC和RVI指令对应的偏移。
PreDecode接口说明
输入接口
in_bits_data 17 x 2B的初始指令码,其中,每2个字节既可以代表一条RVC指令,也可以代表一个RVI指令的一半。
输出接口
instr:拼接后的 16 x 4B的初始指令码
jumpOffset:如果这条指令是跳转指令,则jumpOffset表示其跳转偏移
pd:每条指令预译码信息,包括valid、isRVC、brType、isRet、isCall。其中第0条指令的valid已经被优化了
hasHalfValid:这个信号需要和pd的valid结合起来看,PreDecode的一个功能是求出指令开始向量,也就是对每个4B的拼接指令,判断其低2B是否为一条有效指令的开始(即一条RVI指令的前半部分,或者一条RVC指令),但是需要分类讨论该预测块的第一个2B是否为一条有效指令的开始。hasHalfValid表示的是当前预测块的第一个2B指令为一条RVI指令的后半部分时,给出的指令开始向量。类似地,pd中的valid指的是当前预测块的第一个2B指令为一条指令的开始时,给出的指令开始向量。
PreDecoder测试点和功能点
功能点1 生成指令码
子模块:PreDecoder简介
预译码器PreDeocoder接受初始指令码并进行指令码拼接,拼接之后对每个指令码查询预译码表产生预译码信息,预译码信息包括该位置是否是有效指令开始、CFI指令类型、是否是RVC指令、是否是Call指令以及是否是Ret指令。预译码器会产生两种有效指令开始的向量,一种是默认第1个二字节必为有效指令开始,另一种是默认第2个二字节必为有效指令的开始,最终的选择在IFU端做。
所以,预译码器接收的输入是: 17 x 2B的初始指令码,这个2字节的初始指令码要么是一条RVC指令,要么是一条RVI指令的前半或后半部分。
预译码器的输出是:16x4B的拼接指令码;对每个4B指令码,该条指令是否为RVI或RVC指令(RVC指令只考虑该4B的低2B);对每个4B指令码,该条指令的跳转偏移;两个16位的有效指令开始向量,其中第一种向量假定当前预测块的起始2字节为一条有效指令的开始,而第二种向量假定当前预测块的起始2字节为一条有效RVI指令的结束(但是由于第二种向量的前两位必然为0和1,所以编译优化后,第二种向量实际只有14个信号,表示2-15位;同理,第1种向量的第0位因为恒为1,所以也被优化) 功能介绍 指令码生成
预译码器接受来自IFU完成指令切分的17 × 2字节的初始指令码,并以4字节为窗口,2字节为步进长度, 从第1个2字节开始,直到第16个2字节,选出总共16个4字节的指令码。 预译码信息生成
预译码器根据指令码产生预译码信息,主要包括:是否是RVC指令、是否是CFI指令、 CFI指令类型(branch/jal/jalr/call/ret)、CFI指令的目标地址计算偏移。
预译码器从IFU接收完成指令切分的17 x 2 字节的初始指令码,以4字节为窗口,2字节为步进长度,选出16 x 4字节的指令码
我们需要随机生成初始指令码,并测试拼接的结果。
序号 | 名称 | 描述 |
---|---|---|
1 | 拼接测试 | 随机生成17 x 2字节的初始指令码,检验PreDecoder拼接结果 |
功能点2 生成预译码信息
预译码器会根据指令码产生预译码信息,包括RVC指令的判定和CFI指令的目标地址计算偏移。
CFI类型的判定则时序优化到了F3PreDecoder中。 可以设计测试点测试PreDecode对F3PreDecoder的使用。
据此,我们可以设计下述测试点。
首先是判定RVC指令,我们随机生成输入初始指令码,对返回的16位RVC判定结果进行检验。 具体来说,对每32位指令,考虑RVC和RVI两种情况。
序号 | 名称 | 描述 |
---|---|---|
2.1.1 | RVC判定 | 传入RVC指令,应该判断为RVC |
2.1.2 | RVI判定 | 传入RVI指令,不应判断为RVC |
然后,需要分别根据手册构造RVC和RVI扩展下的J指令和BR指令们,所以有如下的测试点:
序号 | 名称 | 描述 |
---|---|---|
2.2.1 | RVC.J计算 | 对传入RVC扩展的J指令,检查计算的偏移 |
2.2.2 | RVI.J计算 | 对传入RVI扩展的J指令,检查计算的偏移 |
2.2.3 | RVC.BR计算 | 对传入RVC扩展的BR指令,检查计算的偏移 |
2.2.4 | RVI.BR计算 | 对传入RVI扩展的BR指令,检查计算的偏移 |
参照F3PreDecoder的测试点,设计如下测试点:
序号 | 名称 | 描述 |
---|---|---|
2.3.1 | 非CFI判定 | 对传入的非CFI指令(包括RVC.EBREAK),应该判定为类型0 |
2.3.2 | BR判定 | 对传入的BR指令,应该判定为类型1 |
2.3.3 | JAL判定 | 对传入的JAL指令,应该判定为类型2 |
2.3.4 | JALR判定 | 对传入的JALR指令,应该判定为类型3 |
2.4.1 | 非CFI和BR不判定 | 对传入的非CFI和BR指令,都不应判定为call或者ret |
2.4.2.1.1 | RVI.JAL判定call | 对传入的RVI.JAL指令,当rd设置为1或5,应当判定该指令为call |
2.4.2.1.2 | RVI.JAL例外 | 对传入的RVI.JAL指令,当rd设置为1和5之外的值,不应当判定该指令为call或ret |
2.4.2.2 | RVC.JAL不判定 | 对传入的RVC.JAL指令,无论什么情况都不能判定为call或ret |
2.4.3.1.1 | RVI.JALR和rd为link | 传入RVI.JALR指令,并且rd为1或5,无论其他取值,都应判定为call |
2.4.3.1.2 | RVI.JALR且仅rs为link | 传入RVI.JALR指令,rd不为1和5,rs为1或5,应判定为ret |
2.4.3.1.3 | RVI.JALR无link | 对传入的JALR指令,若rd和rs均不为link,则不应判定为ret和cal |
2.4.3.2.1 | RVC.JALR为Ret | 传入RVC.JALR指令,必定为call |
2.4.3.2.2.1 | RVC.JR且rs为link | 传入RVC.JR指令,rs为1或5,应判定为ret |
2.4.3.2.2.2 | RVC.JR且rs不为link | 传入RVC.JR指令,rs不为1或5,不应判定为ret |
功能点3 生成指令开始向量
最后,预译码还需要生成两种指令开始向量:
序号 | 名称 | 描述 |
---|---|---|
3.1 | 有效指令开始向量计算1 | 对预测块,假定第一条指令为一条有效指令的开始,对每条指令计算其是否为有效指令开始 |
3.2 | 有效指令开始向量计算2 | 对预测块,假定第一条指令为一条有效指令的结束,对每条指令计算其是否为有效指令开始 |
测试点汇总
综上所述,对PredDecoder,所有的测试点为:
序号 | 功能 | 名称 | 描述 |
---|---|---|---|
1 | 拼接指令码 | 拼接测试 | 随机生成17 x 2字节的初始指令码,检验PreDecoder拼接结果 |
2.1.1 | RVC判定 | RVC判定 | 传入RVC指令,应该判断为RVC |
2.1.2 | RVC判定 | RVI判定 | 传入RVI指令,不应判断为RVC |
2.2.1 | 跳转目标计算 | RVC.J计算 | 对传入RVC扩展的J指令,检查计算的偏移 |
2.2.2 | 跳转目标计算 | RVI.J计算 | 对传入RVI扩展的J指令,检查计算的偏移 |
2.2.3 | 跳转目标计算 | RVC.BR计算 | 对传入RVC扩展的BR指令,检查计算的偏移 |
2.2.4 | 跳转目标计算 | RVI.BR计算 | 对传入RVI扩展的BR指令,检查计算的偏移 |
2.3.1 | CFI指令类型判定 | 非CFI判定 | 对传入的非CFI指令(包括RVC.EBREAK),应该判定为类型0 |
2.3.2 | CFI指令类型判定 | BR判定 | 对传入的BR指令,应该判定为类型1 |
2.3.3 | CFI指令类型判定 | JAL判定 | 对传入的JAL指令,应该判定为类型2 |
2.3.4 | CFI指令类型判定 | JALR判定 | 对传入的JALR指令,应该判定为类型3 |
2.4.1 | ret、call判定 | 非CFI和BR不判定 | 对传入的非CFI和BR指令,都不应判定为call或者ret |
2.4.2.1.1 | ret、call判定 | RVI.JAL判定call | 对传入的RVI.JAL指令,当rd设置为1或5,应当判定该指令为call |
2.4.2.1.2 | ret、call判定 | RVI.JAL例外 | 对传入的RVI.JAL指令,当rd设置为1和5之外的值,不应当判定该指令为call或ret |
2.4.2.2 | ret、call判定 | RVC.JAL不判定 | 对传入的RVC.JAL指令,无论什么情况都不能判定为call或ret |
2.4.3.1.1 | ret、call判定 | RVI.JALR和rd为link | 传入RVI.JALR指令,并且rd为1或5,无论其他取值,都应判定为call |
2.4.3.1.2 | ret、call判定 | RVI.JALR且仅rs为link | 传入RVI.JALR指令,rd不为1和5,rs为1或5,应判定为ret |
2.4.3.1.3 | ret、call判定 | RVI.JALR无link | 对传入的JALR指令,若rd和rs均不为link,则不应判定为ret和cal |
2.4.3.2.1 | ret、call判定 | RVC.JALR为Ret | 传入RVC.JALR指令,必定为call |
2.4.3.2.2.1 | ret、call判定 | RVC.JR且rs为link | 传入RVC.JR指令,rs为1或5,应判定为ret |
2.4.3.2.2.2 | ret、call判定 | RVC.JR且rs不为link | 传入RVC.JR指令,rs不为1或5,不应判定为ret |
3.1 | 计算有效指令开始向量 | 有效指令开始向量计算1 | 对预测块,假定第一条指令为一条有效指令的开始,对每条指令计算其是否为有效指令开始 |
3.2 | 计算有效指令开始向量 | 有效指令开始向量计算2 | 对预测块,假定第一条指令为一条有效指令的结束,对每条指令计算其是否为有效指令开始 |
3.5 - RVCExpander
子模块:RVCExpander简介
RVCExpander是IFU的子模块,负责对传入的指令进行指令扩展,并解码计算非法信息。
该模块接收的输入量是两个:一条RVC指令或者RVI指令;CSR对fs.status的使能情况。
输出量也是两个:输入指令对应的RVI指令;RVC指令是否非法。
指令扩展
如果是RVI指令,则无需扩展。
否则对RVC指令,按照手册的约定进行扩展。
非法指令判断
RVI指令永远判断为合法。
对于RVC指令的判定,详细内容参阅20240411的RISCV手册的26.8节表格列出的指令条件。
常量说明
常量名 | 常量值 | 解释 |
---|---|---|
XLEN | 64 | 通用寄存器位宽,决定指令扩展时使用rv32还是rv64还是rv128 |
fLen | 64 | 香山支持d扩展,故为64 |
RVCExpander接口说明
输入接口
fsIsOff:表示CSR是否使能fs.status
in:传入一个32位数据,其可以是一个完整的RVI指令,也可以是低16位RVC指令+高16位为RVI指令的一半(当然低16位也有可能是RVI指令的后半部分,但是RVCExpander不会区分,可以认为RVCExpander假定传入的32位数据的低16位一定为一条指令的开始)
输出接口
ill:表示这条指令是否为非法指令
out_bits:对RVI指令,直接返回,对RVC指令,返回扩展后的32位指令。
功能点和测试点
功能点1 指令扩展
RVCExpander负责接收预译码器拼接的指令码,并进行指令扩展,如果是16位RVC指令,需要按照RISCV手册的约定完成扩展
对此,我们需要随机生成RVI指令和RVC指令,送入预译码器:
序号 | 名称 | 描述 |
---|---|---|
1.1 | RVI指令保留 | 构造RVI指令传入,检查保留情况 |
1.2 | RVC指令扩展 | 构造RVC指令传入,按手册检查扩展结果 |
功能点2 非法指令判断
RVCExpander在解析指令时,如发现指令违反了手册的约定,则需要判定该指令非法
对此,我们需要随机生成非法指令送入RVI中,并检测RVCExpander对合法位的校验;同时,我们还需要校验合法指令是否会被误判为非法指令:
此外,需要判定C.fp指令在CSR未使能fs.status的情况下,能否将这类指令判定为非法。
序号 | 名称 | 描述 |
---|---|---|
2.1 | 常规非法指令测试 | 随机构造非法RVC指令传入,检查判断结果 |
2.2 | 合法指令测试 | 随机构造合法RVC指令传入,检查判断结果 |
2.3 | C.fp指令测试 | CSR未使能fs.status的情况下,C.fp指令应该为非法 |
测试点汇总
序号 | 功能 | 名称 | 描述 |
---|---|---|---|
1.1 | 指令扩展 | RVI指令保留 | 构造RVI指令传入,检查保留情况 |
1.2 | 指令扩展 | RVC指令扩展 | 构造RVC指令传入,按手册检查扩展结果 |
2.1 | 非法指令判断 | 非法指令测试 | 随机构造非法RVC指令传入,检查判断结果 |
2.2 | 非法指令判断 | 合法指令测试 | 随机构造合法RVC指令传入,检查判断结果 |
2.3 | C.fp指令测试 | CSR未使能fs.status的情况下,C.fp指令应该为非法 |
RVC扩展辅助阅读材料
为方便参考模型的书写,在这里根据20240411版本的手册内容整理了部分指令扩展的思路。
对于RVC指令来说,op = instr(1, 0);funct = instr(15, 13)
op\funct | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
---|---|---|---|---|---|---|---|---|
00 | addi4spn | fld | lw | ld | lbu lhu;lh sb;sh |
fsd | sw | sd |
01 | addi | addiw | li | lui addi16sp zcmop |
ARITHs zcb |
j | beqz | bnez |
10 | slli | fldsp | lwsp | ldsp | jr;mv ebreak jalr;add |
fsdsp | fwsp | sdsp |
在开始阅读各指令的扩展规则时,需要了解一些RVC扩展的前置知识,比如:
rd’, rs1’和rs2’寄存器:受限于16位指令的位宽限制,这几个寄存器只有3位来表示,他们对应到x8~x15寄存器。
op = b'00'
funct = b'000’: ADDI4SPN
该指令将一个0扩展的非0立即数加到栈指针寄存器x2上,并将结果写入rd'
其中,nzuimm[5:4|9:6|2|3]的含义是:
这条指令的第12至11位是立即数的5至4位,第10至7位是立即数的9至6位,第6位是立即数的第2位,第7位是立即数的第3位。
这条指令最终扩展成为addi rd’, x2, nzuimm[9:2]
addi的格式形如:| imm[11:0] | rs1 | 000 | rd | 0010011 |
注意,该指令的立即数为0的时候,不合法。
funct = b'001’: fld
该指令从内存加载一个双精度浮点数到rd’寄存器。
offset的低三位是0,高位进行了0扩展。
这条指令最终扩展成为fld rd′,offset(rs1′)
fld的格式形如: | imm[11:0] | rs1 | 011 | rd | 0000111 |
注意:在昆明湖环境下,该指令要求CSR使能fs.status,也即入参fsIsOff为假。
funct = b'010’: lw
该指令从内存加载一个32位的值到rd’寄存器。
offset的低两位是0,高位进行了0扩展。
这条指令最终扩展成为lw rd′,offset(rs1′)
lw的格式形如: | imm[11:0] | rs1 | 010 | rd | 0000011 |
funct = b'011’: ldsp
该指令从内存加载一个64位的值到rd’寄存器。
offset的低两位是0,高位进行了0扩展。
这条指令最终扩展成为ld rd′,offset(rs1′)
ld的格式形如: | imm[11:0] | rs1 | 011 | rd | 0000011 |
funct = b'100’: zcb extensions 1
在RVC指令中,这部分对应的是zcb扩展中的5条指令:lbu,lhu,lh,sb,sh
在zcb扩展中,进一步地取instr[12:10]作为zcb扩展的指令码,我们记作funct_zcb
funct_zcb = b'000’: lbu
| 100 | 000 | rs1’ | uimm[0|1] | rd’ | 00 |
这个指令从rs1’+uimm的地址读取一字节,用0扩展并并加载到rd’中。
最终翻译为 lb rd’, uimm(rs1')
lb指令的格式形如:| imm[11:0] | rs1 | 000 | rd | 0000011 |
funct_zcb = b'001’, instr[6] =0 : lhu
| 100 | 001 | rs1’ | 0 | uimm[1] | rd’ | 00 |
这个指令从地址rs1’ + uimm读取半word,用0扩展加载到rd’中。
最终翻译为 lhu rd’, uimm(rs1')
lhu指令的格式形如:| imm[11:0] | rs1 | 101 | rd | 0000011 |
funct_zcb = b'001’, instr[6] =1 : lh
| 100 | 001 | rs1’ | 1 | uimm[1] | rd’ | 00 |
这个指令从地址rs1’ + uimm读取半word,符号扩展并加载到rd’中。
最终翻译为 lh rd’, uimm(rs1')
lh指令的格式形如:| imm[11:0] | rs1 | 001 | rd | 0000011 |
funct_zcb = b'010’: sb
| 100 | 010 | rs1’ | uimm[0 | 1] | rd’ | 00 |
这个指令把rs2’的低字节存储到地址rs1’ + uimm指示的内存地址中。
最终翻译为 sb rs2, uimm(rs1')
RVI中sb指令的格式形如:|imm[11:5] | rs2 | rs1 | 000 | imm[4:0] | 0100011 |
funct_zcb = b'011’: sh
| 100 | 011 | rs1’ | 0 | uimm[1] | rd’ | 00 |
这个指令把rs2’的低半字存储到地址rs1’ + uimmz指示的内存地址中。
最终翻译为 sh rd’, uimm(rs1')
sh指令的格式形如:|imm[11:5] | rs2 | rs1 | 001 | imm[4:0] | 0100011 |
funct = b'101’: fsd
fsd将rs2’中的双精度浮点数存储到rs1’ + imm指示的内存区域
该指令的立即数低3位为0,同时进行了0符号扩展。
最终这个指令将被扩展为fsd rs2′, offset(rs1′)
RVI的FSD格式形如:| imm[11:5]| rs2 | rs1 | 011 | imm[4:0] | 0100011 |
注意:在昆明湖环境下,该指令要求CSR使能fs.status,也即入参fsIsOff为假。
funct = b'110’: sw
sw将rs2’中的一个字存储到rs1’ + imm指示的内存区域
该指令的立即数低2位为0,同时进行了0符号扩展。
最终这个指令将被扩展为sw rs2′, offset(rs1′)
RVI的SW格式形如:| imm[11:5]| rs2 | rs1 | 010 | imm[4:0] | 0100011 |
funct = b'111’: sd
fsd将rs2’中的双字存储到rs1’ + imm指示的内存区域
该指令的立即数低3位为0,同时进行了0符号扩展。
最终这个指令将被扩展为sd rs2′, offset(rs1′)
RVI的SD格式形如:| imm[11:5]| rs2 | rs1 | 011 | imm[4:0] | 0100111 |
op = b'01'
funct = b'000’: addi
该指令将一个符号扩展的非0立即数加到rd存储的数字上,并将结果写入rd。
尽管手册规定立即数和rd不为0,但是立即数和rd为0的情况仍可视为合法。前者是HINT指令,而后者是NOP。
这条指令最终扩展成为addi rd, rd, imm
addi的格式形如:| imm[11:0] | rs1 | 000 | rd | 0010011 |
funct = b'001’: addiw
该指令的功能和addi类似,但是先计算得到32位数,然后再符号扩展至64位。
该指令的rd为0时非法。
当立即数不为0时,该指令最终扩展成为addiw, rd, rd, imm
addiw的指令格式为| imm[11:0] | rs1 | 000 | rd | 0011011 |
如果立即数为0,该指令将会扩展成为sext.w rd,不过和addiw的格式是一样的,因此可以将他们归为一类。
funct = b'010’: li
该指令将符号扩展的立即数加载到rd中。
当立即数为0时,该指令为hint,可以看作合法。
这条指令最终扩展成为addi rd, x0, imm
addi的格式形如:| imm[11:0] | rs1 | 000 | rd | 0010011 |
funct = b'011’: lui/addi16sp/zcm
当rd不为0且不为2时,为lui指令,可以扩展为lui rd, imm
lui指令的格式形如: | imm[31:12] | rd | 0110111 |
当立即数为0时,这一字段reserved
当rd为0时,为hint,也可当作cli进行译码。
当rd为2时,为addi16sp指令:
扩展为addi x2, x2, nzimm[9:4]
addi的格式形如:| imm[11:0] | rs1 | 000 | rd | 0010011 |
对addi16sp,立即数为0时非法。
此外,当第12至11位皆为0,第7位是1且第6至2位为0时,为zcmop,可以直接翻译为一个不起效的指令,比如与立即数0。
funct = b'100’: arith & zcb extension2
在RVC指令中,这部分对应的是数学运算指令和zcb扩展中的另一部分指令,数学计算指令的对应如下:
其中SRLI64和SRAI64在昆明湖环境下可以不考虑。
srli
当funct2为00时,为srli。
最终可翻译为srli rd′, rd′, 64
srli的格式形如:|0000000|shamt|rs1|101|rd|0010011|
srai
当funct2为01时,为srai。
最终可翻译为srai rd′, rd′, 64
SRAI的格式形如:|0100000|shamt|rs1|101|rd|0010011|
andi
该指令最终扩展为andi rd′, rd′, imm
andi的格式形如|imm[11:0]|rs1|111|rd|0010011|
sub
这条指令最终可以扩展为:sub rd′, rd′, rs2′
sub指令的格式形如:|0100000|rs2|rs1|000|rd|0110011|
xor
这条指令最终可以扩展为:xor rd′, rd′, rs2′
xor指令的格式形如:|0000000|rs2|rs1|100|rd|0110011|
or
这条指令最终可以扩展为:or rd′, rd′, rs2′
or指令的格式形如:|0000000|rs2|rs1|110|rd|0110011|
and
这条指令最终可以扩展为:and rd′, rd′, rs2′
and指令的格式形如:|0000000|rs2|rs1|111|rd|0110011|
subw
这条指令最终可以扩展为:subw rd′, rd′, rs2′
subw指令的格式形如:|0100000|rs2|rs1|000|rd|0111011|
addw
这条指令最终可以扩展为:addw rd′, rd′, rs2′
addw指令的格式形如:|0000000|rs2|rs1|000|rd|0111011|
mul
从mul开始的一部分指令属于zcb扩展。
zcb扩展中,当instr(12, 10) == “111”,且instr(6, 5)为"10"时,为mul指令。
zcb扩展中,当instr(12, 10) == “111”,且instr(6, 5)为"11"时,根据instr(4,2), 共有000的zext.b,001的sext.b,010的zext.h,011的sext.h,100的zext.w和101的not。
该指令可扩展为mul rd, rd, rs2
mul的格式为:|0000001|rs2|rs1|000|rd|0110011|
zext.b
这条指令可以翻译为:andi rd’/rs1’, rd’/rs1’, 0xff
andi的格式形如|imm[11:0]|rs1|111|rd|0010011|
sext.b
该指令翻译为sext.b rd, rd
sext.b指令在RVI下形如:
zext.h
该指令翻译为zext.h rd, rd
zext.h指令在RVI下形如:
sext.h
该指令翻译为sext.h rd, rd
sext.h指令在RVI下形如:
zext.w
该指令等价为add.uw rd’/rs1’, rd’/rs1’, zero
add.uw指令在RVI下形如:
not
该指令等价为xori rd’/rs1’, rd’/rs1’, -1
xori指令在RVI下形如: | imm[11:0] | rs1| 100 | rd | 0010011 |
funct = b'101’: j
最终这个指令将被扩展为jal x0, offset
jal的格式形如:| imm[20|10:1|11|19:12] | rd | 1101111 |
funct = b'110’: beqz
该指令可以扩展到beq rs1‘, x0, offset
beq指令形如: |imm[12|10:5]|rs2|rs1|000|imm[4:1|11]|1100011| imm[12|10:5]rs2rs1001imm[4:1|11]1100011BNE
funct = b'111’: bnez
最终这个指令将被扩展为bne rs1′, x0, offset
bne指令形如:|imm[12|10:5]| rs2 | rs1 | 001 | imm[4:1|11] | 1100011|
op = b'10'
funct = b'000’: slli
该指令将一个符号扩展的非0立即数加到rd存储的数字上,并将结果写入rd。
尽管手册规定立即数和rd不为0,但是立即数和rd为0的情况仍可视为合法。前者是HINT指令,而后者是NOP。
这条指令最终扩展成为slli rd, rd, shamt[5:0]
slli的格式形如:|000000|shamt|rs1|001|rd|0010011|
funct = b'001’: fldsp
该指令最终扩展成为fld rd, offset(x2)
fld的格式形如: | imm[11:0] | rs1 | 011 | rd | 0000111 |
该指令要求CSR使能fs.status
funct = b'010’: lwsp
rd为0时非法。
这条指令最终扩展成为lw rd, offset(x2)
lw的格式形如: | imm[11:0] | rs1 | 010 | rd | 0000011 |
funct = b'011’: ldsp
rd为0时非法。
这条指令最终扩展成为ld rd, offset(x2)
lw的格式形如: | imm[11:0] | rs1 | 011 | rd | 0000011 |
funct = b'100’: jr/mv/ebreak/jalr/add
jr
当rd为0时,非法。
该指令最终可以扩展为jalr x0, 0(rs1)
jalr指令的格式为:|imm[11:0]|rs1|000|rd|1100111|
mv
rd为0时,是hint指令。
该指令最终可以扩展为add rd, x0, rs2
add指令形如:|0000000|rs2|rs1|000|rd|0110011|
ebreak
可以扩展为ebreak指令。
形如:|00000000000100000000000001110011|
jalr
该指令最终可以扩展为jalr x1, 0(rs1)
jalr指令的格式为:|imm[11:0]|rs1|000|rd|1100111|
add
该指令最终可以扩展为add rd, rd, rs2
add指令形如:|0000000|rs2|rs1|000|rd|0110011|
funct = b'101’: fsdsp
这条指令最终扩展成为fsd rs2, offset(x2)
RVI的FSD格式形如:| imm[11:5]| rs2 | rs1 | 011 | imm[4:0] | 0100011 |
该指令要求CSR使能fs.status
funct = b'110’: swsp
这条指令最终扩展成为sw rs2, offset(x2)
RVI的SW格式形如:| imm[11:5]| rs2 | rs1 | 010 | imm[4:0] | 0100011 |
funct = b'111’: sdsp
该指令最终扩展成为sd rd, offset(x2)
RVI的SD格式形如:| imm[11:5]| rs2 | rs1 | 011 | imm[4:0] | 0100111 |
4 - ITLB
TLB 功能概述
现代操作系统通常采用虚拟内存管理机制(Virtual Memory Management),在处理器中对应需要内存管理单元(MMU,Memory Management Unit)来进行虚实地址的映射。MMU 负责处理 CPU 的内存访问请求,其功能包括虚实地址的映射、内存保护、CPU 高速缓存控制等。
虚实地址的映射是以页(Page)为单位的。在物理内存管理中,内核会将整个物理内存空间划分为一个一个的页帧(Page Frame),一般情况下页帧大小为 4KB,称为一个物理页帧,内核会将每一个物理页帧进行编号(PFN,Page Frame Number),每个页帧有唯一确定的 PFN。对于一个进程来说,如果它直接使用物理地址构建自己的地址空间,那么作为进程就需要关心每一个变量存放在哪一个物理地址,也就是说程序员需要清楚数据在内存中的具体布局,还需要每次都要考虑内存的分配问题;同时,对于多个进程同时进行的情况,哪些数据是共享的,如何避免地址冲突等等都会成为问题。
MMU 为每个进程创建自己的虚拟地址空间,存储虚实地址的映射,在进程的视角看来它独享一段确定的(通常是连续的)地址,避免了其它进程的干扰;同时提供了虚实地址转换功能,这使得进程不必关心实际的物理地址在哪里,只需要对自己的地址空间进行操作。同时,对于一个进程来说,每次访问内存时并不是访问整个虚拟内存空间,因此进程实际需要占用的物理内存大小可以小于其虚拟地址空间的大小,由操作系统来决定要把哪一部分留在内存中,将剩余部分保存在磁盘中,在需要时再加载进入内存,极大的扩展了可用内存空间。
程序局部性原理,是计算机科学术语,指程序在执行时呈现出局部性规律,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。局部性原理又表现为:时间局部性和空间局部性。
- 时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。
- 空间局部性是指一旦程序访问了某个存储单元,则不久之后,其附近的存储单元也将被访问。
这样的由 MMU 创建的并负责维护的由虚拟地址指向物理地址的映射也将成为一项存储在一个物理页帧中,MMU 为了访问这样的物理页帧也需要一个根页表,根页表中存储着指向这些物理页帧的页表项(PTE),称为叶子 PTE。一个 PTE 的长度一般为 64 Bit(8 Bytes),而每一个一般物理页帧的大小为 4KB,这也就意味着一个物理页帧最多可以存储 4KB/8B = 2^9 个 PTE,因此根页表可以索引的范围即为 2^9 × 4KB = 2MB。2MB 的页表并不能满足内存日益增大的需要,在香山中实现的 SV48 即采用了四级页表的形式,通过四级的查询最终得到物理地址,每一级页表都能够索引 2^9 个下一级页表,最终找到需要的映射。四级页表下能够索引的地址范围达到了 2^9 × 2^9 × 2^9 × 2MB = 256TB。而页表本身也会比较大,如果存满的话大小会达到 4KB + 2^9 × 4KB + 2^9 × 2^9 × 4KB + 2^9 × 2^9 × 2^9 × 4KB = 537921540KB ≈ 513GB。当然,不是说每一级页表都要填满,页表的四级结构可以理解为一个多叉树形结构,只有需要用到的才会实际使用,很多的分支都不需要使用,因此页表的大小是可变的。
页表一般很大,需要存放在内存中,而处理器每一次访问内存的请求都需要先访问页表查找对应的物理页号然后再去读取所需数据,因此在不发生缺页的情况下,每次访存操作都需要两次访问内存才能得到物理地址,然后再次访问才能得到需要的数据。为了减少多次访存造成的开销,引入了地址转换后援缓存器(TLB,Translation Lookaside Buffer)。MMU 通常借助 TLB 来进行虚实地址的转换。TLB 一般是相连高速缓存(associative cache),相当于页表的 Cache,负责将最可能会用到的页表项对应的映射(虚拟地址与对应的物理地址)存储下来;在查找页表时首先查找 TLB 内存储的映射,如果没有命中再去查找内存中存储的完整页表。
同 Cache 一样,TLB 中页表项的组织方式一般有直接映射、全相联映射、组相连映射三种方式。直接映射一般通过模运算匹配,例如对昆明湖 48 行的 TLB 来说,其第 1 块只能对应内存的第 1/49/97/…/(n×48+1) 块,硬件结构简单、成本低、转换速度快,但是 TLB 表项利用率低,TLB miss 频繁,只适用于 TLB 大小与页表大小较接近的情况。全相联映射则不同,内存中的所有表项可以存放在 TLB 中的任意一项中,可以充分利用 TLB 的空间,冲突概率更低,但因此查找开销较高,适用于小容量 TLB。组相联映射是一种折中,可以二路组相联、四路组相联等。在香山的 TLB 模块中提供了丰富的参数配置,其中即包括采取哪一种相连方式,可以通过传入参数自行配置。本次验证的 ITLB 即采用 48 项全相联的结构。
香山的 MMU 模块由 TLB、Repeator、L2TLB、PMP&PMA 组成,其中 L2TLB 又包含了 Page Cache、Page Table Walker、Last Level Page Table Walker、Miss Queue 和 Prefetcher。在核内每次进行内存的操作(读写)时都需要通过 MMU 模块进行虚实地址的翻译,而 TLB 将被实例化为 ITLB(前端取指)和 DTLB(后端访存)。以 ITLB 为例,每当 ICache 或 IFU 需要进行取指操作,会先向 ITLB 发送一个地址转换请求,把需要转换的虚拟地址发给 ITLB;ITLB 接收到请求后就要查找自己存储的表项里有没有这个虚拟地址对应的映射,如果有的话就输出对应物理地址(paddr),之后由 PMP&PMA 模块检查对该物理地址的访问是否有效(包括地址是否有效、访问者是否有访问权限、页表属性等,其中对 ITLB 来说由于取出来的物理地址是待执行的指令,需要检查是否可以执行),检查通过后就可以把物理地址返回给前端。如果 ITLB 发现自己没有存储这样的表项,那么立即回应 miss,并同时发起 PTW 请求。前端接收到 miss 信号后会通过一些调度策略重新发起访问,在香山中体现为 miss 后不断重新给 TLB 发请求直到 hit。PTW 请求将交由 Page Table Walker 来执行,通过一些策略访问 L2TLB、Page Cache、内存中的完整页表,之后把访问到的 PTE(页表项)发回给 TLB(如果 PTW 都找不到那么会发生 Page Fault,同样返回给 TLB,TLB 收到 Page Fault 后会上报并由操作系统等从磁盘中加载页面)。TLB 接收到 PTE 的同时将 PTE 填充进自己的缓存中并向前端返回物理地址,前端才能通过该物理地址找到对应的指令。
香山实现了二级 TLB,包括 TLB 与 L2TLB。同样类似于 Cache 与 L2Cache,TLB(一级 TLB)通常是小容量、高速缓存,直接与处理器核心连接,用于加速最近访问过的虚拟地址到物理地址的转换;L2TLB(二级 TLB)容量较大,速度稍慢,但比直接访问内存要快。L2TLB 用来缓存更多的页表项,减少一级 TLB 未命中(TLB Miss)时对内存的频繁访问,香山目前有 1 个 ITLB 和 3 个 DTLB,都与同一个 L2TLB 连接。在这种二级结构下,TLB 未命中时将会首先查找 L2TLB,之后如果再次未命中才去访问内存,可以有效提高地址转换的命中率和性能。由于在 TLB 与 L2TLB 之间有着一定的物理距离,因此在 TLB 向 L2TLB 发出读取请求的时候需要进行加拍,这项工作交给了 MMU 中的 repeater 进行,是 TLB 与 L2TLB 之间的一个请求缓冲。同时,repeator 还需要负责对 TLB 向 L2TLB 发送的请求进行过滤(即 Filter 的功能),把重复的请求过滤掉,以减少 L2TLB 性能损失。
昆明湖架构支持 RISC-V 手册中定义的 Hypervisor 扩展,即 H 扩展。H 扩展为处理器提供了虚拟化的支持,即允许虚拟机运行在主机上,此时虚拟机将与主机共享 TLB,那么在 MMU 中也需要进行相应的调整与支持。TLB 需要能够同时容纳多个虚拟机的条目并做到隔离,同时需要引入 Hypervisor Page Table Walker(HPTW)用于遍历虚拟机的页表。
在 MMU 模块中还需要实现 PMP(Physical Memory Protection)与 PMA(Physical Memory Access)检查,不过这与 TLB 无关,在实现中无论请求是否有效或有足够权限,都会通过 TLB 先进行地址转换,之后再把转换的结果(物理地址)送到 PMP&PMA 模块进行权限检查。
验证功能点列表及说明
此处将给出划分功能点及测试点的示例。如果您划分了新的功能点,请及时与我们沟通,我们会根据沟通结果修正功能点列表。测试点原则上可根据验证实际情况自行划分,此处仅给出示例。
功能点1:TLB接收请求
功能说明
TLB 应当正常接收来自 IFU 与 ICache 的取指令请求,查找自身页表并作出适当的反应:miss 情况下返回 miss 并同时向 PTW 发送遍历请求,hit 情况返回正确结果。验证时此处应关注 TLB 做出的反应,无需关注请求本身的多种情况。
测试点示例
No. | 名称 | 说明 |
---|---|---|
1.1 | 接收来自 ICache 请求(requestor0、1) | ITLB 根据请求查找自身缓存 TLBuffer,返回 hit/miss 结果 |
1.2 | 接收来自 IFU 请求(requestor2) | 注意此处为阻塞式访问,每次访问后若 miss 应当 reset 后再次访问 |
1.3 | 接收条件判断(requestor0、1) | valid 信号 |
1.4 | 接受条件判断(requestor2) | valid-ready 信号 |
功能点2:TLB miss 处理
功能说明
miss 情况下发送页表遍历请求,额外需要注意边界情况下的请求处理,保证 TLB 发送的 PTW 请求正确。
测试点示例
No. | 名称 | 说明 |
---|---|---|
2.1 | 返回 miss 结果 | 比对后发现 miss 并返回 |
2.2 | 发起 PTW req(同时检验 PTW req valid 0/1) | 从端口 0/1 发起页表遍历请求 |
2.3 | 发起 PTW req(同时检验 PTW req valid-ready 2) | 从端口 2 发起页表遍历请求 |
2.4 | 不同情况下发起 PTW req | 改变 CSR(vsatp、hgatp),依然能够正常发送请求 |
2.5 | PTW resp valid 信号有效 | 检验该信号是否正常 |
2.6 | 重填 nonStage 条目,之后能正确访问 | 无 |
2.7 | 重填 OnlyStage1 条目,之后能正确访问 | 无 |
2.8 | 重填 OnleStage2 条目,之后能正确访问 | 无 |
2.9 | 重填 allStage 条目,之后能正确访问 | 无 |
功能点3:TLB hit 处理
功能说明
hit 情况下返回查询到的物理地址。requestor2 应当结束阻塞。
测试点示例
No. | 名称 | 说明 |
---|---|---|
3.1 | 主机查询得到物理地址 | paddr |
3.2 | 虚拟机查询得到物理地址 | gpaddr |
3.3 | 虚拟机查询得到中间物理地址 | IPA |
功能点4:替换策略
功能说明
根据文档说明,香山的 ITLB 使用 PLRU 替换策略,具体实现时使用的是外部提供的库。验证时可自学 PLRU 算法,并设计合理策略。
测试点示例
No. | 名称 | 说明 |
---|---|---|
4.1 | 填满后持续重填随机次数 | 建议建立参考模型进行对比 |
4.2 | 随机 hit/miss 一段时间 | 建议建立参考模型进行对比 |
功能点5:TLB 缓存大小
功能说明
检验 TLB 是否能够支持理论最大(48*8)项页表条目的存储。注意 PLRU 替换策略将导致一定情况下不能填满,验证中无需考虑因为该策略导致的未填充满。
测试点示例
No. | 名称 | 说明 |
---|---|---|
5.1 | 顺序填充至满 | 检验最终能够存储的最大条目数,这将直接影响 TLB 加速取指的效率 |
5.2 | 乱序随机 | 模拟应用场景,记录并检验条目数 |
功能点6:TLB 压缩
功能说明
支持 TLB 压缩,具体可见文档。注意保证随机性。
测试点示例
No. | 名称 | 说明 |
---|---|---|
6.1 | 压缩 8 项条目 | 一个压缩条目内的 8 项页表项都可以正常 hit |
6.2 | 全满压力测试 | 全部填满时可连续命中 |
6.3 | idx 随机测试 | 检测对应 idx 信号是否有效 |
功能点7:刷新
功能说明
TLB 模块需要在进程切换等场景下频繁刷新,也需要接收定向刷新指令刷新指定条目。验证中要建立填入-刷新-检验miss情况的流程,建议自定义函数完成。注意页表属性 Global 的影响,自行制定合适的策略。
测试点示例
No. | 名称 | 说明 |
---|---|---|
7.1 | SFENCE rs1=0 rs2=0 | 刷新全部条目 |
7.2 | SFENCE rs1=0 rs2=1 | 刷新指定条目 |
7.3 | SFENCE rs1=1 rs2=0 | 刷新指定地址空间 |
7.4 | SFENCE rs1=1 rs2=1 | 刷新指定地址空间的指定条目 |
7.5 | 带 flushpipe 的 Sfence | 清空流水线 |
7.6 | SFENCE hv=1/hg=1 | 刷新虚拟机的条目 |
7.7 | flushPipe0 | 清空流水线0 |
7.8 | flushPipe1 | 清空流水线1 |
7.9 | flushPipe2 | 清空流水线2 |
7.10 | satp.changed | 按一定策略刷新 |
7.11 | vsatp.changed | 按一定策略刷新 |
7.12 | hgatp.changed | 按一定策略刷新 |
功能点8:Reset
功能说明
保证正常复位。TLB 工作流程涉及多个周期,需保证在各个阶段中执行 reset 均能正常复位。
测试点示例
No. | 名称 | 说明 |
---|---|---|
8.1 | Reset 复位 | 检查所有信号按预期复位 |
8.2 | 请求同时复位 | 检查所有信号按预期复位 |
8.3 | resp 同时复位 | 检查所有信号按预期复位 |
功能点9:权限检查
功能说明
TLB 并不涉及复杂的页属性检查,仅涉及用户态/内核态的权限。由于 ITLB 存储的全部为指令页,页属性必须全部可执行。
测试点示例
No. | 名称 | 说明 |
---|---|---|
9.1 | 主机状态下(U/S)访问权限检查 | U 只能访问 U=1,S 只能访问 U=0 |
9.2 | 虚拟机状态下(VU/VS)访问权限检查 | VU 只能访问 U=1,VS 只能访问 U=0 |
9.3 | 权限切换时的行为 | IT级别验证时报告权限切换时会出现一个信号异常,可重点关注 |
9.4 | X=0 | 页面不可执行时的行为 |
功能点10:异常处理
功能说明
ITLB 在异常方面承担的主要职责就是上报。当下层模块报告 GPF 时,由于 ITLB 不存储中间物理地址,此时重填需要首先发送一个带 GetGPA 标志的重填,标志当前重填请求是请求的虚拟机物理地址,PTW 会把这个请求标志发送回来,带该标志的 PTW resp 不会被存入 TLBuffer。
测试点示例
No. | 名称 | 说明 |
---|---|---|
10.1 | s1-pf | 主机缺页异常 |
10.2 | s1-af | 主机访问权限异常 |
10.3 | s2-gpf | 虚拟机缺页异常 |
10.4 | s2-gaf | 虚拟机访问权限异常 |
10.5 | getGPA 信号相关 | 验证中自行拆分 |
功能点11:隔离
功能说明
地址空间隔离,通过 asid、vmid 实现进程、虚拟机之间的隔离。
测试点示例
No. | 名称 | 说明 |
---|---|---|
11.1 | 进程间隔离 | 无 |
11.2 | 虚拟机间隔离 | 无 |
11.3 | 虚拟机的进程间隔离 | 无 |
功能点12:并行访问
功能说明
模块包含 3 个端口,其访问是可以同时接收的,但是TLB的查询必须按序。TLB会将接收的请求暂存,以队列形式处理,当然 requestor2 作为阻塞式访问不参与这个过程。在实际场景下,对与 TLB miss 的情况,ICache 会自行组织重新持续发送请求。
测试点示例
No. | 名称 | 说明 |
---|---|---|
12.1 | 同时 hit | 三个预期会 hit 的请求同一拍进入 TLB |
12.2 | 同时 miss | 三个预期会 miss 的请求同一拍进入 TLB |
12.3 | 随机顺序回填 | 模拟实际场景持续发送请求,并以随机顺序回填 |
12.4 | 发送请求同时回填请求的地址 | 在同一拍对同一个地址发送请求&回填 |
功能点13:大小页支持
功能说明
TLB 支持保存全部大小页,不同 level 的页面都应该可以存入 TLB 中。
测试点示例
No. | 名称 | 说明 |
---|---|---|
13.1 | level=0 | 无 |
13.2 | level=1 | 无 |
13.3 | level=2 | 无 |
13.4 | level=3 | 无 |
功能点14:时序
功能点说明
检验 TLB 时序,保证每拍的信号级别行为正确。
测试点示例
No. | 名称 | 说明 |
---|---|---|
14.1 | 请求命中时序(requestor0、1) | 无 |
14.2 | 请求命中时序(requestor2) | 无 |
14.3 | 请求miss时序(requestor0、1) | 无 |
14.4 | 请求miss时序(requestor2) | 无 |
4.1 - IO接口说明
香山实例化 TLB.sv 接口说明(ITLB)
基本控制信号
clock
: 时钟信号,驱动TLB
的时序逻辑。reset
: 复位信号,用于重置TLB
的状态。
刷新(SFENCE)接口信号
io_sfence_valid
:SFENCE
操作的有效性标志。io_sfence_bits_rs1
:SFENCE
操作是否使用寄存器rs1
的值。io_sfence_bits_rs2
:SFENCE
操作是否使用寄存器rs2
的值。io_sfence_bits_addr
:SFENCE
操作指定的地址,用于选择性刷新特定地址的TLB
条目。io_sfence_bits_id
: 刷新操作指定的asid/vmid
,用于选择性刷新特定地址空间的TLB
条目。io_sfence_bits_flushPipe
: 刷新整个管道。io_sfence_bits_hv
: 指示指令是否为HFENCE.VVMA
,即是否刷新虚拟化下由vsatp
寄存器控制的条目。io_sfence_bits_hg
: 指示指令是否为HFENCE.GVMA
,即是否刷新由hgatp
寄存器控制的条目。
控制与状态寄存器(CSR)接口信号
io_csr_satp_mode
:SATP
寄存器的模式字段(如裸模式、Sv32
、Sv39
等)。io_csr_satp_asid
: 当前SATP
寄存器的ASID
(地址空间标识符)。io_csr_satp_changed
: 指示SATP
寄存器的值是否已更改。io_csr_vsatp_mode
:VSATP
寄存器的模式字段。io_csr_vsatp_asid
:VSATP
寄存器的ASID
。io_csr_vsatp_changed
: 指示VSATP
寄存器的值是否已更改。io_csr_hgatp_mode
:HGATP
寄存器的模式字段。io_csr_hgatp_vmid
:HGATP
寄存器的VMID
(虚拟机标识符)。io_csr_hgatp_changed
: 指示HGATP
寄存器的值是否已更改。io_csr_priv_virt
: 指示是否在虚拟模式下运行。io_csr_priv_imode
: 指令模式的特权级(如用户态、内核态等)。
请求者(Requestor)接口信号
Requestor 0 信号
io_requestor_0_req_valid
:requestor0
的请求有效信号。io_requestor_0_req_bits_vaddr
:requestor0
的请求虚拟地址。io_requestor_0_resp_bits_paddr_0
:requestor0
的物理地址响应信号。io_requestor_0_resp_bits_gpaddr_0
:requestor0
的物理地址转换为GPA
(Guest Physical Address
)的响应信号。io_requestor_0_resp_bits_miss
:requestor0
请求的地址未命中的信号。io_requestor_0_resp_bits_excp_0_gpf_instr
:requestor0
出现General Protection Fault
(GPF
) 异常的信号。io_requestor_0_resp_bits_excp_0_pf_instr
:requestor0
出现Page Fault
(PF
) 异常的信号。io_requestor_0_resp_bits_excp_0_af_instr
:requestor0
出现Access Fault
(AF
) 异常的信号。
Requestor 1 信号
io_requestor_1_req_valid
:requestor1
的请求有效信号。io_requestor_1_req_bits_vaddr
:requestor1
的请求虚拟地址。io_requestor_1_resp_bits_paddr_0
:requestor1
的物理地址响应信号。io_requestor_1_resp_bits_gpaddr_0
:requestor1
的GPA
响应信号。io_requestor_1_resp_bits_miss
:requestor1
的未命中信号。io_requestor_1_resp_bits_excp_0_gpf_instr
:requestor1
出现GPF
异常的信号。io_requestor_1_resp_bits_excp_0_pf_instr
:requestor1
出现PF
异常的信号。io_requestor_1_resp_bits_excp_0_af_instr
:requestor1
出现AF
异常的信号。
Requestor 2 信号
io_requestor_2_req_ready
:requestor2
的请求就绪信号。io_requestor_2_req_valid
:requestor2
的请求有效信号。io_requestor_2_req_bits_vaddr
:requestor2
的请求虚拟地址。io_requestor_2_resp_ready
:requestor2
的响应就绪信号。io_requestor_2_resp_valid
:requestor2
的响应有效信号。io_requestor_2_resp_bits_paddr_0
:requestor2
的物理地址响应信号。io_requestor_2_resp_bits_gpaddr_0
:requestor2
的GPA
响应信号。io_requestor_2_resp_bits_excp_0_gpf_instr
:requestor2
出现GPF
异常的信号。io_requestor_2_resp_bits_excp_0_pf_instr
:requestor2
出现PF
异常的信号。io_requestor_2_resp_bits_excp_0_af_instr
:requestor2
出现AF
异常的信号。
刷新管道(Flush Pipe)信号
io_flushPipe_0
: 刷新管道0
的信号。io_flushPipe_1
: 刷新管道1
的信号。io_flushPipe_2
: 刷新管道2
的信号。
页表遍历(Page Table Walker, PTW)接口信号
PTW 请求信号
io_ptw_req_0_valid
:PTW req0
有效信号。io_ptw_req_0_bits_vpn
:PTW req0
的虚拟页号(VPN
)。io_ptw_req_0_bits_s2xlate
: 指示PTW req0
的转换模式。io_ptw_req_0_bits_getGpa
:PTW req0
的获取GPA
信号。io_ptw_req_1_valid
:PTW req1
有效信号。io_ptw_req_1_bits_vpn
:PTW req1
的虚拟页号。io_ptw_req_1_bits_s2xlate
: 指示PTW req1
的转换模式。io_ptw_req_1_bits_getGpa
:PTW req1
的获取GPA
信号。io_ptw_req_2_ready
:PTW req2
就绪信号。io_ptw_req_2_valid
:PTW req2
有效信号。io_ptw_req_2_bits_vpn
:PTW req2
的虚拟页号。io_ptw_req_2_bits_s2xlate
: 指示PTW req2
的转换模式。io_ptw_req_2_bits_getGpa
:PTW req2
的获取GPA
信号。
PTW 响应信号
io_ptw_resp_valid
:PTW resp
有效信号。io_ptw_resp_bits_s2xlate
: 指示PTW resp
的地址转换类型。io_ptw_resp_bits_s1_entry_tag
:PTW resp
的第一阶段页表条目标签。io_ptw_resp_bits_s1_entry_asid
:PTW resp
的第一阶段页表条目ASID
。io_ptw_resp_bits_s1_entry_vmid
:PTW resp
的第一阶段页表条目VMID
。io_ptw_resp_bits_s1_entry_perm_d
:PTW resp
的第一阶段页表条目可写位。io_ptw_resp_bits_s1_entry_perm_a
:PTW resp
的第一阶段页表条目已访问位。io_ptw_resp_bits_s1_entry_perm_g
:PTW resp
的第一阶段页表条目全局位。io_ptw_resp_bits_s1_entry_perm_u
:PTW resp
的第一阶段页表条目用户模式位。io_ptw_resp_bits_s1_entry_perm_x
:PTW resp
的第一阶段页表条目可执行位。io_ptw_resp_bits_s1_entry_perm_w
:PTW resp
的第一阶段页表条目可写位。io_ptw_resp_bits_s1_entry_perm_r
:PTW resp
的第一阶段页表条目可读位。io_ptw_resp_bits_s1_entry_level
:PTW resp
的第一阶段页表条目级别。io_ptw_resp_bits_s1_entry_ppn
:PTW resp
的第一阶段页表条目物理页号(PPN
)。io_ptw_resp_bits_s1_addr_low
:PTW resp
的第一阶段页表条目地址低位。io_ptw_resp_bits_s1_ppn_low_*
:PTW resp
的第一阶段页表条目PPN
低位。io_ptw_resp_bits_s1_valididx_*
:PTW resp
的第一阶段页表条目有效索引。io_ptw_resp_bits_s1_pteidx_*
:PTW resp
的第一阶段页表条目PTE
索引。io_ptw_resp_bits_s1_pf
:PTW resp
的第一阶段页表条目出现PF
。io_ptw_resp_bits_s1_af
:PTW resp
的第一阶段页表条目出现AF
。io_ptw_resp_bits_s2_entry_tag
:PTW resp
的第二阶段页表条目标签。io_ptw_resp_bits_s2_entry_vmid
:PTW resp
的第二阶段页表条目VMID
。io_ptw_resp_bits_s2_entry_ppn
:PTW resp
的第二阶段页表条目PPN
。io_ptw_resp_bits_s2_entry_perm_*
:PTW resp
的第二阶段页表条目的权限位。io_ptw_resp_bits_s2_entry_level
:PTW resp
的第二阶段页表条目级别。io_ptw_resp_bits_s2_gpf
:PTW resp
的第二阶段页表条目GPF
信号。io_ptw_resp_bits_s2_gaf
:PTW resp
的第二阶段页表条目GAF
信号。io_ptw_resp_bits_getGpa
:PTW resp
的获取GPA
信号。
4.2 - 功能详述
支持 SV48 分页机制
SV48
(Supervisor-mode Virtual Memory
)是一种基于 RISC-V
的页表虚拟内存寻址模式,指定了 48
位虚拟地址空间的结构,支持 256TB
的虚拟内存地址空间。使用四级页表结构:
在 SV48
的一个 PTE
中包含了如下字段:
-
N
:- 指示是否为
NAPOT PTE
。供Svnapot
扩展使用,如果未实现Svnapot
则该位必须由软件置0
,否则应当出现Page Fault
。目前香山昆明湖架构尚未支持此扩展。
- 指示是否为
-
PBMT
:Page-Based Memory Types
,即基于页面的内存类型,供Svpbmt
扩展使用,允许操作系统为每个页面指定不同的内存访问属性。0
:None
,没有特定的内存属性。1
:NC
,非缓存、幂等、弱序(RVWMO
),适用于主存。2
:IO
,非缓存、非幂等、强序(I/O
排序),适用于I/O
设备。3
:Reserved
,保留供将来标准使用。
同样的,如果未实现
Svpbmt
则这两位必须由软件置0
,否则应当出现Page Fault
。 -
Reserved
:- 保留位,供未来的标准使用。如果有任意一位不是
0
则会触发PF
异常。
- 保留位,供未来的标准使用。如果有任意一位不是
-
PPN
:- 表示物理页框号,指向实际的物理内存页。
PPN
与页面内偏移结合形成完整的物理地址,用于地址转换。
- 表示物理页框号,指向实际的物理内存页。
-
RSW
:- 保留供软件使用的位,通常用于特定的标志或操作,以便在软件实现中提供灵活性。
-
D
:- 脏位,指示该页面是否被写入。如果该位为
1
,表示该页的数据已被修改,需在换出时写回到存储设备。
- 脏位,指示该页面是否被写入。如果该位为
-
A
:- 访问位,指示该页是否被访问过。如果该位为
1
,表示该页已被读取或写入,用于页面替换算法。
- 访问位,指示该页是否被访问过。如果该位为
-
G
:- 全局页位,指示该页是否是全局页。如果该位为
1
,表示该页对所有进程可见,用于共享代码或数据。
- 全局页位,指示该页是否是全局页。如果该位为
-
U
:- 用户访问权限位,指示该页是否可被用户(
U
)模式访问。如果该位为1
,用户模式可以访问该页;若为0
,则仅限于特权模式。
- 用户访问权限位,指示该页是否可被用户(
-
X
:- 可执行位,指示该页是否可执行。如果该位为
1
,表示该页可以执行代码;若为0
,则不可执行。
- 可执行位,指示该页是否可执行。如果该位为
-
W
:- 可写位,指示该页是否可写。如果该位为
1
,表示该页可以写入数据;若为0
,则不可写。
- 可写位,指示该页是否可写。如果该位为
-
R
:- 可读位,指示该页是否可读。如果该位为
1
,表示该页可以读取数据;若为0
,则不可读。
- 可读位,指示该页是否可读。如果该位为
-
V
:- 有效位,指示该页表项是否有效。如果该位为
1
,表示该项有效,可以进行地址转换;若为0
,则表示该项无效。
- 有效位,指示该页表项是否有效。如果该位为
值得一提的是,如果该 PTE
并不是叶子 PTE
,即它所存储的 PPN
用来指向下一级页表,那么它的 X
、W
、R
位应全为零。在手册中的要求如下:
RISC-V H
扩展即 Hypervisor
扩展,增加了对虚拟化和 hypervisor
模式的支持,将会允许虚拟机监控程序和虚拟机的管理程序,允许操作系统运行在虚拟机上,并可以通过 hypervisor
调度虚拟机的运行。在 hypervisor
下使用 SV48x4
寻址模式,支持四倍页表扩展。
VPN[3]
进行了两位的扩展,也即大小从原来的 4KB
变为 16KB
,支持 $2^{11}$ 个 PTE
。值得注意的是,SV48x4
作用于虚拟机物理地址 VPA
,在虚拟机上创建进程地址空间时仍然采用的是 SV48
。也正是因此,虚拟机进行虚实地址转换的时候,首先将 48
位的虚拟机虚拟地址(GVA
)转换为 50
位的虚拟机物理地址(GPA
),之后再将 GPA
(相当于主机的 HVA
)转换为主机物理地址(HPA
)。在页表项中存储的是 44
位的 PPN
,这是由 56
位的物理地址去掉 12
位的页内偏移得到的,因此完全可以存的下扩展了两位(38
位)的 VPN
。
出于对面积等的优化考虑,在香山中采用 48
位的主机物理地址,而不是 Sv48
要求的 56
位物理地址,这是因为 48
位的物理地址已经可以索引 256TB
的物理地址空间,目前来说已经足够使用。但是由于 TLB
对虚拟机的支持,在虚拟机两阶段地址转换过程中(两阶段地址转换可见支持两阶段虚实地址翻译过程部分),虚拟机通过 VS
阶段转换的结果仍然是 56
位的虚拟机物理地址,只不过在进入 G
阶段地址转换时,G
阶段要求传入的 GPA
的高 6
位必须为 0
,这是因为在 Sv48x4
中客户机物理地址要求为 50
位,而 VS
阶段得到的物理地址是 56
位。为了保持 gpaddr
的完整性,PTW
传入 TLB
的 ppn
信号的位宽依然为 44
位,然而由于 TLB
不存储中间转换结果(中间物理地址 IPA
),也就不需要存储 44
位的 ppn
,在 TLB
表项中存储的只有主机的 ppn
,也即 36
位的 ppn
。
支持缓存映射条目
TLB
中存储的条目并不是页表项 PTE
,而是一个映射,一个从虚拟地址(来自于请求)到物理地址(来自于查找结果)的映射,当然还有一些访问所必须的信息。在目前的香山中 TLB
所存储的条目包含 tag[35]
、asid[16]
、vmid[14]
、level[2]
、ppn[33]
、8 × ppn_low[3]
、8 × valididx
、8 × pteidx
、s2xlate
、perm[6]
、g_perm[4]
。为供以后使用 svpbmt
扩展,还存储了 pbmt
与 g_pbmt
字段。
-
tag[34:0]
tag
,用于匹配条目。来源于VPN
的高35
位,在匹配的过程中,输入一个38
位的VPN
,通过将输入的VPN
的前35
位与tag
比较找到对应的条目,可以看到在一个条目中存储了PPN
的高位部分和8
个ppn_low
,之后将VPN
的后三位作为索引,可以索引这8
个ppn_low
,即可将ppn
与ppn_low[vpn_low]
拼接得到物理页框号。
-
asid[15:0]
- 地址空间标识符,用于区分不同的进程地址空间。
-
vmid[13:0]
- 虚拟机标识符,用于区分不同的虚拟机。
-
level[1:0]
- 指示页面的大小。
0
:4KB
,1
:2MB
,2
:1GB
,3
:512GB
。
- 指示页面的大小。
-
ppn[32:0]
- 物理页框号的高
33
位。在Sv48
要求下本该是41
位,出于面积考虑优化至33
位(见支持Sv48
分页机制部分)。
- 物理页框号的高
-
ppn_low[2:0]×8
- 物理页框号的低
3
位。用于TLB
压缩(见支持TLB
压缩部分)。
- 物理页框号的低
-
valididx×8
- 指示对应的
ppn_low
是否有效。用于TLB
压缩,为0
表示条目无效,即对应物理地址没有存储页表条目。
- 指示对应的
-
pteidx×8
- 指示原始请求对应压缩条目的哪一项。例如
vpn
低三位为010
,那么pteidx[3]
为1
,其它7
位为0
。
- 指示原始请求对应压缩条目的哪一项。例如
-
s2xlate[1:0]
- 指示是否启用两阶段地址转换。
0b00
:不启用,0b01
:仅使用第一阶段,0b10
:仅使用第二阶段,0b11
:启用两阶段地址转换。
- 指示是否启用两阶段地址转换。
-
perm[5:0]
- 指示主机的权限以及异常信息,包括
pf
、af
、a
、g
、u
、x
六位。其中pf
(page fault
)指示是否发生缺页异常;af
(access fault
)指示是否发生地址错误等访问错误异常;a
(access
)指示该表项是否最近被访问过,任何形式的访问(包括读、写、取指)均会将a
位置1
,用于页面替换算法;g
(global
)指示该条目指向的页面是否为全局页面;u
(user
)指示该条目指向的页面是否可以被用户模式访问,u
位为1
说明可以被UMode
访问,为0
说明可以被SMode
访问;x
(执行)指示该条目指向的页面是否可执行,itlb
用于取指的加速,所有取出的条目必须是可执行的。
- 指示主机的权限以及异常信息,包括
-
g_perm[3:0]
- 指示虚拟机的权限以及异常信息,包括
gpf
、gaf
、a
、x
四位,虚拟机的g
和u
两位不单独存储,与主机共用。一般情况下虚拟机对全局页、用户模式的处理与主机相同,而替换策略与访问权限控制可能不同,所以共用了g
、u
而不共用a
、x
。gpf
(guest page fault
)为虚拟机缺页异常,gaf
(guest access fault
)为虚拟机访问错误异常。
- 指示虚拟机的权限以及异常信息,包括
香山的 ITLB
采用 48
项全相联的结构,保存全部大小页,共能存储 48
条映射。
在支持 H
扩展的前提下,对于不同的 s2xlate
的状态 TLB
中存储的条目的值代表的意义也会有所区别:
类型 | s2xlate | tag | ppn | perm | g_perm | level |
---|---|---|---|---|---|---|
noS2xlate | b00 | 非虚拟化下的虚拟页号 | 非虚拟化下的物理页号 | 非虚拟化下的页表项 perm | 不使用 | 非虚拟化下的页表项 level |
allStage | b11 | 第一阶段页表的虚拟页号 | 第二阶段页表的物理页号 | 第一阶段页表的 perm | 第二阶段页表的 perm | 两阶段翻译中最大的 level |
onlyStage1 | b01 | 第一阶段页表的虚拟页号 | 第一阶段页表的物理页号 | 第一阶段页表的 perm | 不使用 | 第一阶段页表的 level |
onlyStage2 | b10 | 第二阶段页表的虚拟页号 | 第二阶段页表的物理页号 | 不使用 | 第二阶段页表的 perm | 第二阶段页表的 level |
支持 H
扩展后 TLB
中缓存的条目会有所变化(表中未提及的条目即没有变化):
支持 H 扩展 | vmid | s2xlate | g_perm |
---|---|---|---|
否 | 不保存 | 不保存 | 不保存 |
是 | 14位 | 2位 | 4位 |
支持保存全部大小页
在 RISC-V
架构中,大小页机制旨在优化虚拟内存的使用效率和性能。Sv48
支持多种页面大小,包括 4KB
、2MB
、1GB
页,在标准的设计中没有定义 512GB
的页,理论上可行,但目前并没有这样的需要,512GB
的页也无法加载进内存,因此标准不做要求。但是出于对完整性的考虑,香山中依然实现了对 512GB
大页的支持。
在一般的应用程序需求中,4KB
的页面足够满足日常的使用,可以存储较小的数据结构以及程序等,常用于大多数应用程序中。然而,有的程序可能会需要频繁访问大的数据结构或数据集,这时引入大页可以提升内存访问效率。每个大页覆盖的虚拟地址空间更大,可以显著减少页表条目的数量;在映射相同数量的内存时,所需的页表条目会大幅降低,这可以减少内存开销、减少页表查找频率,从而优化内存访问速度,尤其对频繁访问大块内存的应用,能够显著提升性能。大页通常包含连续的数据,可以提高命中率,更有效地利用缓存资源。
当然,由于大页覆盖的地址空间较大,可能导致内存碎片,而未被使用的大页空间无法被其他请求有效利用,也会浪费一定的内存资源。同时,管理不同大小的页面为内存管理带来了额外的复杂性。在混合使用小页和大页时,操作系统需要复杂的算法来优化内存分配和使用。现代操作系统通常采用混合使用大小页的模式以满足不同应用的不同需求。
在香山的 TLB
中,支持保存任意大小的页面,这是通过保存页面的 level
来实现的。根据不同的 level
,可以决定最终生成物理地址的方法(index
为页内偏移,来源于 vaddr
的低 12
位;ppn
、ppn_low
、tag
来源于 TLB
中存储的映射条目):
level | 页面大小 | paddr[47:0] |
---|---|---|
0 | 4KB | ppn[32:0] + ppn_low[2:0] + index[11:0] |
1 | 2MB | ppn[32:6] + tag[8:0] + index[11:0] |
2 | 1GB | ppn[32:15] + tag[17:0] + index[11:0] |
3 | 512GB | ppn[32:24] + tag[26:0] + index[11:0] |
支持 TLB 压缩
随着虚拟地址空间的不断扩展,传统 TLB
的大小和效率面临挑战,可能不足以覆盖应用程序的需求,导致频繁的缺失(TLB miss
),从而影响系统性能,导致性能瓶颈。为了应对这一问题,TLB
压缩技术应运而生,旨在提高 TLB
的有效性和性能。
在操作系统分配内存的时候,由于使用伙伴地址分配策略等原因,会倾向于将连续的物理页分配给连续的虚拟页。虽然随着程序的不断运行,页分配逐渐的从有序趋向于无序,但是这种页的相连性普遍存在,因此可以通过将多个连续的页表项在 TLB
硬件中合成为一个 TLB
项,以增大 TLB
容量。TLB
压缩通过优化页表结构,支持连续的映射,通过引入范围映射(range mapping
)机制,一个 TLB
条目可以映射一段连续的虚拟地址到一段连续的物理地址。
在实际中,以香山昆明湖架构为例,在 TLB
中存储 35
位的 vpn_high
(即 tag
),剩下的三位用于索引对应的 ppn_low
(一共有 8
个所以需要 3
位来索引)。每次匹配中,TLB
用传入的 vaddr[49:15]
(高 35
位)与 tag
进行匹配,找到对应的条目,这个条目中可以存储 8
个 PTE
,再根据 vaddr[14:12]
找到对应的 ppn_low
,之后检查对应的 valididx
是否有效,如果有效说明 hit
,将 ppn_low
与 ppn_high
拼接得到 PPN
,再与 vaddr[11:0]
拼接得到 paddr
。
在支持了 H
扩展后(见支持两阶段虚实地址翻译),TLB
压缩仅在 OnlyStage1
和 noS2xlate
下启用,在其他情况下不启用。
支持 TLB
压缩后 TLB
中缓存的条目会有所变化(表中未提及的条目即没有变化):
是否压缩 | tag | ppn | valididx | pteidx | ppn_low |
---|---|---|---|---|---|
否 | 38位 | 36位 | 不保存 | 不保存 | 不保存 |
是 | 35位 | 33位 | 8位 | 8位 | 8×3位 |
在支持了大小页的情况下,TLB
压缩在大页情况下(2MB/1GB/512GB
)不启用,仅在查询结果为小页(4KB
)情况下启用。大页在返回时会将 valididx
的 8
位全部设置为 1
,而由于大页的查询过程中只需要 PPN
的高位,大页下不使用 ppn_low
,ppn_low
的值在此时是未定义的。
支持 Hypervisor 扩展与两阶段虚实地址翻译
在 RISC-V
特权指令手册中定义了虚实地址的翻译过程:
-
设
a
为satp.ppn
×PAGESIZE
,并设i = LEVELS - 1
。(对于Sv48
,PAGESIZE = 2^{12}
,LEVELS = 4
)此时,satp
寄存器必须处于活动状态,即有效的特权模式必须是S
模式或U
模式。 -
设
pte
为地址a + va.vpn[i]
×PTESIZE
处的PTE
值。(对于Sv48
,PTESIZE = 8
)如果访问pte
违反了PMA
或PMP
检查,则引发与原始访问类型相应的访问错误异常。 -
如果
pte.v = 0
,或者pte.r = 0
且pte.w = 1
,或者pte
中设置了任何为未来标准使用保留的位或编码,则停止并引发与原始访问类型相应的页面错误异常。 -
否则,
PTE
是有效的。如果pte.r = 1
或pte.x = 1
,则转到步骤 5。否则,此PTE
是指向下一级页面表的指针。设i = i - 1
。如果i < 0
,则停止并引发与原始访问类型相应的页面错误异常。否则,设a = pte.ppn
×PAGESIZE
并转到步骤 2。 -
找到了叶子
PTE
。根据当前特权模式和mstatus
寄存器的SUM
和MXR
字段的值,确定请求的内存访问是否被pte.r
、pte.w
、pte.x
和pte.u
位允许。如果不允许,则停止并引发与原始访问类型相应的页面错误异常。 -
如果
i > 0
且pte.ppn[i-1 : 0] = 0
,则这是一个未对齐的大页;停止并引发与原始访问类型相应的页面错误异常。 -
如果
pte.a = 0
,或者如果原始内存访问是存储且pte.d = 0
,则引发与原始访问类型相应的页面错误异常,或者执行以下操作:- 如果对
pte
的存储将违反PMA
或PMP
检查,则引发与原始访问类型相应的访问错误异常。 - 以原子方式执行以下步骤:
- 比较
pte
与地址a + va.vpn[i]
×PTESIZE
处的PTE
值。 - 如果值匹配,将
pte.a
设为1
,并且如果原始内存访问是存储,还将pte.d
设为1
。 - 如果比较失败,返回步骤 2。
- 比较
- 如果对
-
翻译成功。翻译后的物理地址如下:
pa.pgoff = va.pgoff
。- 如果
i > 0
,则这是一个大页翻译,且pa.ppn[i - 1 : 0] = va.vpn[i - 1 : 0]
。 pa.ppn[LEVELS - 1 : i] = pte.ppn[LEVELS - 1 : i]
。
在一般的虚实地址翻译过程中,将按照如上所述的过程进行转换,由 satp 寄存器控制进行地址翻译。其中,前端取指通过 ITLB
进行地址翻译,后端访存通过 DTLB
进行地址翻译。ITLB
和 DTLB
如果 miss
,会通过 Repeater
向 L2TLB
发送请求。在目前设计中,前端取指和后端访存对 TLB
均采用非阻塞式访问,即一个请求 miss
后,会将请求 miss
的信息返回,请求来源调度重新发送 TLB
查询请求,直至命中。也就是说,TLB
本体是非阻塞的,可以向它连续发送请求,无论结果都可以在下一拍发送任意的请求,但是总体上由于前端的调度,体现为阻塞访问。
在支持了 H
扩展的前提下,香山的虚拟化地址翻译过程会经历两个阶段的地址转换,可以将它划分为 VS
阶段和 G
阶段。VS
阶段的地址转换由 vsatp
寄存器控制,其实与主机的 satp
寄存器非常相似。
页表项(PTE
)的长度为 64 bit
,也即每个 4KB
的页面可以存储 $2^9$ 个 PTE
。在 vsatp
寄存器中存储了第一级页表(即根页表)的物理地址 PPN
,通过这个 PPN
可以找到根页表,并根据 GVA
中的 VPN[3]
找到对应页表项 PTE
,在 PTE
中存储了指向下一级页表的 PPN
以及权限位等。以此方式通过逐级的查找最终达到叶子 PTE
并得到 PPN
,与 offset
合成后得到 GPA
。注意这里的 GPA
应当是 50
位的,最后一级的 PPN
应当是 38
位的,这是因为支持 SV48x4
的原因,虚拟机的物理地址被拓宽了两位。这样的拓宽并不难实现,只需要在主机分配虚拟机内存空间的时候分配一个 16KB
的大页作为根页表即可;通过多使用 12KB
(本来分配的根页表大小是 4KB
)的物理内存就可以实现虚拟机地址空间增大四倍。至于页表项能否放下多了两位的 PPN
,观察 PTE
中 PPN
的位数为 44
位,不需要担心这个问题。44
位的 PPN
放 38
位,前六位并没有清零要求,但是是被忽略的。
G
阶段的地址翻译则不同,由于支持了 SV48x4
,其根页表被扩展为 11
位 16KB
,因此需要特别注意 hgatp
寄存器中存储的 PPN
应当对齐 16KB
页,在标准情况下 PPN
的最后两 bit
应当被固定为零,意味着 hgatp
寄存器应当指向一个 16KB
页的起始地址,以避免根页表在不同的小页面内。
在实际的实现中,地址的翻译并不是这样理想化的先查虚拟机的页表得到 GPA
再使用这个 GPA
查主机的页表得到 HPA
。事实上的实现中,我们通过虚拟机的页表查到的下一级页表的物理地址是 GPA
,并不能通过它访问到下一级页表,每次访问虚拟机的下一级页表都需要进行一次 GPA
到 HPA
的转换。比如此时给定一个 GVA
,之后在虚拟机的一级页表(根页表)中根据 GVA[2]
(11 bit
)查找得到一个 PTE
,这个 PTE
存储的是二级页表的 GPA
,得到这个 GPA
并不能找到二级页表,因此需要将它转换为 HPA
,也就是 G
阶段的地址翻译。依次查找直到找到最终需要的那个 HPA
,共需要经历五次 G
阶段地址翻译,才能得到最终的 HPA
。
支持阻塞式与非阻塞式访问
阻塞式访问代表着 TLB
的端口同时仅支持一个请求,阻塞端口带 valid-ready
握手信号。在 TLB
准备好接收请求时,会将 ready
置 1
,由外部检测到 ready
后会发送请求。请求到达 TLB
时 valid
为 1
则 TLB
接收请求并将 ready
置 0
,不再接受新的请求。之后 TLB
会对请求进行匹配,查找结果,如果 miss
则发送 ptw
请求(同样为阻塞),等待直到 ptw
返回结果(物理地址或 pf
异常),然后 TLB
将结果保存并上报给请求方,再将 ready
置 1
。
对于非阻塞式请求,仅带 valid
信号,每当 valid
置 1
,TLB
即接受请求并在下一拍返回结果(hit/miss/异常
),无论是否命中都能在请求下一拍得到结果。如果 miss
的话,TLB
在返回 miss
结果同时会发起 PTW
请求(非阻塞),PTW
接收到请求则进行处理,在处理完成后回填进 TLB
中,然后如果请求方再次发起请求就可以命中。在香山 ITLB
的具体实现中,TLB
本体虽然是非阻塞的,不存储请求的信息,但当前端发起的取指请求 miss
后,将会由前端进行调度不断发起相同取指请求直到 hit
,才能将指令送到处理器进行处理,因此会体现出阻塞的效果。
请求来源 | iCache | IFU |
---|---|---|
请求数量 | 2 | 1 |
请求类型 | 非阻塞请求 | 阻塞请求 |
握手信号 | 仅带 valid 信号 | 带 valid 和 ready 信号 |
处理方式 | 可以继续处理其他指令 | 等待 iTLB 响应后继续处理指令 |
支持读取 PTW 返回条目
每次 TLB
发生 miss
之后,会向 L2TLB
发送 Page Table Walk
请求。由于 TLB
与 L2TLB
之间有比较长的物理距离,需要在中间加拍,这项工作由 repeator
完成。同时,repeator
还需要对 PTW
请求进行过滤,以避免 TLB
中出现重复项,因此也被称为 filter
。目前香山中 TLB
发出的 PTW
请求的内容包含 VPN
、s2xlate
、getGPA
三个信号以及必要的控制信号:
-
VPN:
- 虚拟页框号,
TLB
在miss
之后会将VPN
发送给PTW
用于索引对应的物理页,PTW
会将叶子页表的PPN
返回给TLB
,下次TLB
查询的时候就可以找到该页并可以通过页内偏移找到物理地址。
- 虚拟页框号,
-
s2xlate:
- 两阶段地址转换标志,指示当前的两阶段地址转换模式。
TLB
中该标志将通过vsatp
与hgatp
寄存器的mode
域进行判断:
s2xlate vsatp.mode hgatp.mode 0b00 0 0 0b01 1 0 0b10 0 1 0b11 1 1 - 两阶段地址转换标志,指示当前的两阶段地址转换模式。
-
getGPA:
- 指示当前
PTW
请求是否为请求客户机物理地址。用于客户机缺页等情况的处理(详见支持发生GPF
时重新发起请求部分)。
- 指示当前
在支持了 TLB
压缩后,PTW
返回的结果主要包括 resp_valid
、tag[33:0]
、asid[15:0]
、perm[6:0]
、level[1:0]
、ppn[35:0]
、addr_low[2:0]
、ppn_low[2:0]
× 8
、valididx
× 8
、pteidx
× 8
、pf
、af
(各个信号的含义可见支持缓存映射条目部分)。TLB
接收到有效的 PTW resp
后即将这些条目存进自己的缓存中。
在支持了 H
扩展后,TLB
压缩仅在 noS2xlate
和 onlyStage1
时启用,需要添加 s2xlate
信号指示两阶段地址转换的类型,并分开返回 s1
和 s2
。其中,s1
阶段可以与之前的主机地址转换合并,在主机地址转换中,s1
添加的部分信号以及位宽不适用,添加或扩充的信号如下所示:
支持 H 扩展 | s2xlate[1:0] | tag | vmid[13:0] | ppn | s2 | getGPA |
---|---|---|---|---|---|---|
否 | 无 | [32:0] | 无 | [32:0] | 无 | 无 |
是 | 有 | [34:0] | 有 | [40:0] | 有 | 有 |
其中,tag
扩充的两位是由于虚拟机采用 Sv48x4
,将 hypervisor
下的虚拟地址从 48
位扩充为 50
位,因此 tag
相应需要多两位。vmid
指示虚拟机号。ppn
多的 8
位是因为主机采用 48
位物理地址,而第一阶段转换出来的虚拟机物理地址为 56
位(在进入下一阶段时要求前 6
位是 0
,变为 50
位),getGPA
可见后面支持发生 GPF
时重新发起请求部分。
s2
部分用于第二阶段地址转换,即从虚拟机物理地址到主机物理地址的转换,此时 asid
无效,resp
的信号包括 tag[37:0]
、vmid[13:0]
、ppn[37:0]
、perm[6:0]
、level
、gpf
、gaf
。由于不考虑 TLB
压缩,tag
即为 38
位,来自 50
位虚拟地址的高 38
位。值得注意,在目前的香山昆明湖架构中,这里的 ppn
有效的位数仅有后 36
位,之所以 ppn
位宽为 38
位是出于优化的需要,香山 TLB
中通过 readResult
方法从 PTW
中读取信息,s1
、s2
阶段复用了 readResult
方法,由于在 s1
阶段的需要用到 50
位的物理地址,readResult.ppn
被定义为 38
位,以至于在 verilog
文件中传入 s2.ppn
时也需要额外多传 2
位,事实上这两位仅仅传进 TLB
中而不起作用,可以忽略。
支持 H 扩展 | s2_tag[37:0] | s2_vmid[14:0] | s2_ppn[37:0] | s2_perm[6:0] | s2_level[1:0] | gpf | gaf |
---|---|---|---|---|---|---|---|
否 | 无 | 无 | 无 | 无 | 无 | 无 | 无 |
是 | 有 | 有 | 有 | 有 | 有 | 有 | 有 |
添加了 H
拓展后的 MMU
,PTW
返回的结构分为三部分,第一部分 s1
是原先设计中的 PtwSec-torResp
,存储第一阶段翻译的页表,第二部分 s2
是 HptwResp
,存储第二阶段翻译的页表,第三部分是 s2xlate
,代表这次 resp
的类型,仍然分为 noS2xlate
、allStage
、onlyStage1
和 onlyStage2
。如下图。其中 PtwSectorEntry
是采用了 TLB
压缩技术的 PtwEntry
。
支持回填条目与两阶段条目融合
参照支持缓存映射条目与支持读取 PTW
返回条目,对于主机地址转换(nos2xlate
)的情况对应填入 entry
中的对应表项即可,此时访客有关信号无效。注意大页时,即 level
不为 0
时,ppn_low
无效。
TLB entry | 填入的来自 PTW 的信号 |
---|---|
s2xlate[1:0] | 0b00 (nos2xlate) |
tag[34:0] | s1.tag[34:0] |
asid[15:0] | s1.asid[15:0] |
vmid[13:0] | 无效 |
level[1:0] | s1.level[1:0] |
ppn[32:0] | s1.ppn[32:0] |
ppn_low[2:0]×8 | s1.ppn_low_* |
valididx×8 | s1.valididx_* |
pteidx×8 | s1.pteidx_* |
perm_pf | s1.pf |
perm_af | s1.af |
perm_a | s1.perm.a |
perm_g | s1.perm.g |
perm_u | s1.perm.u |
perm_x | s1.perm.x |
gperm_gpf | 无效 |
gperm_gaf | 无效 |
gperm_a | 无效 |
gperm_x | 无效 |
s2xlate=0b00 时填入 TLB entry 示意表 |
在 OnlyStage1
的情况下,主机的异常信号以及部分不可复用的权限位无效,其余均与主机地址转换一致。
TLB entry | 填入的来自 PTW 的信号 |
---|---|
s2xlate[1:0] | 0b01 (OnlyStage1) |
tag[34:0] | s1.tag[34:0] |
asid[15:0] | s1.asid[15:0] |
vmid[13:0] | s1.vmid[13:0] |
level[1:0] | s1.level[1:0] |
ppn[32:0] | s1.ppn[32:0] |
ppn_low[2:0]×8 | s1.ppn_low_* |
valididx×8 | s1.valididx_* |
pteidx×8 | s1.pteidx_* |
perm_pf | s1.pf |
perm_af | s1.af |
perm_a | s1.perm.a |
perm_g | s1.perm.g |
perm_u | s1.perm.u |
perm_x | s1.perm.x |
gperm_gpf | 无效 |
gperm_gaf | 无效 |
gperm_a | 无效 |
gperm_x | 无效 |
s2xlate=0b01 时填入 TLB entry 示意表 |
对于 OnlyStage2
的情况,asid
无效,vmid
使用 s1.vmid
(由于 PTW
模块无论什么情况都会填写这个字段,所以可以直接使用这个字段写入),pteidx
根据 s2
的 tag
的低 3
位来确定。如果 s2
是大页,那么 TLB
项的 valididx
均为有效,否则 TLB
项的 pteidx
对应 valididx
有效。ppn
的填写复用了 allStage
的逻辑,将在 allStage
的情况下介绍。
TLB entry | 填入的来自 PTW 的信号 |
---|---|
s2xlate[1:0] | 0b10 (OnlyStage2) |
tag[34:0] | s2.tag[37:3] |
asid[15:0] | 无效 |
vmid[13:0] | s1.vmid[13:0] |
level[1:0] | s2.level[1:0] |
ppn[32:0] | s2.ppn[35:3] |
ppn_low[2:0]×8 | { s2.ppn[2:0], 无效×7 } |
valididx×8 | { 1, 0×7 } |
pteidx×8 | s2.tag[2:0] |
perm_pf | 无效 |
perm_af | 无效 |
perm_a | 无效 |
perm_g | 无效 |
perm_u | 无效 |
perm_x | 无效 |
gperm_gpf | s2.gpf |
gperm_gaf | s2.gaf |
gperm_a | s2.perm.a |
gperm_x | s2.perm.x |
s2xlate=0b10 时填入 TLB entry 示意表 |
如果两阶段地址转换均启用,TLB
将两阶段的结果合并存储,并丢弃中间物理地址(s1
阶段的 ppn
),仅存储最终物理地址。level
需要取 s1.level
与 s2.level
中的较大值,此时需要注意,当 s1
阶段为大页,而 s2
阶段为小页的情况下,例如中间物理地址指向一个 2MB
页,而 s2
阶段转换的结果却是一个 4KB
页,在这种情况下,需要特殊处理,将 s1.tag
的高位(在此例子中为高 11+9+9=29
位)和 s2.tag
的低位(在此例子中为低 9
位)共 38
位合并存储到 tag
与 pteidx
中,如果不足 38
位则在后面补 0
(例如中间物理地址指向 1GB
页而 s2
阶段指向 2MB
页,此时 tag[34:0] = {s1.tag[34:15], s2.tag[17:9], 6'b0}
)。在这种情况(s1
大页 s2
小页)下 ppn
也需要处理后存储,根据 s2.level
将 s2.ppn
与 s2.tag
进行拼接后存储。
TLB entry | 填入的来自 PTW 的信号 |
---|---|
s2xlate[1:0] | 0b11 (allStage) |
tag[34:0] | 根据策略选择 s1.tag/s2.tag 的部分位 |
asid[15:0] | s1.asid |
vmid[13:0] | s1.vmid |
level[1:0] | s1.level 与 s2.level 的较大者 |
ppn[32:0] | s2.ppn 与 s2.tag 根据 s2.level 的拼接的高位 |
ppn_low[2:0]×8 | s2.ppn 与 s2.tag 根据 s2.level 的拼接的低位 |
valididx×8 | 根据 level 确定 |
pteidx×8 | tag 的低位 |
perm_pf | s1.pf |
perm_af | s1.af |
perm_a | s1.perm.a |
perm_g | s1.perm.g |
perm_u | s1.perm.u |
perm_x | s1.perm.x |
gperm_gpf | s2.gpf |
gperm_gaf | s2.gaf |
gperm_a | s2.perm.a |
gperm_x | s2.perm.x |
s2xlate=0b11 时填入 TLB entry 示意表 |
支持发生 GPF 时重新发起 PTW 请求
在香山的 TLB
中并不会保存中间物理地址。在两阶段地址转换过程中,如果第一阶段发生缺页异常,即 PTW
返回 gpf
,此时 TLB
将 PTW
返回的结果存入 TLB
项内,请求方再次请求的时候发现 gpf
,此时 TLB
会返回 miss
,即使已经存储了这个映射。同时,TLB
将发起带 getGPA
标志的 PTW
请求,请求这个虚拟地址,并维护一组寄存器暂存相关信号:
信号 | 作用 |
---|---|
need_gpa | 表示此时有一个请求正在获取 gpaddr |
need_gpa_robidx | 存储请求的 ROB(Reorder Buffer)索引,用于跟踪请求来源,目前未使用 |
need_gpa_vpn[37:0] | 存储请求的 vpn,即 50 位虚拟地址的高 38 位 |
need_gpa_gvpn[43:0] | 存储获取的 gpaddr 的 gvpn,虚拟机通过转换得到的 56 位虚拟机物理地址的高 44 位,前六位在第二阶段地址转换中被要求为全 0 |
need_gpa_refill | 表示该请求的 gpaddr 已经被填入 need_gpa_gvpn |
每当 TLB
发起带 getGPA
标志的请求时,就会将 need_gpa
置 1
,并将请求的 vpn
填入到 need_gpa_vpn
中,同时将 need_gpa_refill
置 0
。当 PTW
返回结果的时候,TLB
将 PTW resp
中的 vpn
提取出来与 need_gpa_vpn
进行比较,判断是否是对之前 getGPA
请求的回应。如果是,那么将 PTW resp
中的 s2 tag
填入到 need_gpa_gvpn
中并将 need_gpa_refill
置 1
,表示已经获取到需要的 gvpn
。下一次 TLB
接收到相同请求时就可以通过 need_gpa_gvpn
得到 gpaddr
,之后 TLB
会将 need_gpa
置 0
,但保留其它寄存器,因此下次其它的请求发生 gpf
时也可以再次使用相同的 need_gpa_vpn
找到 paddr
而无需再次发起 PTW
请求。
注意这里的 gvpn
是 44
位的,这是由于客户机采用 56
位物理地址,为了维护 gpaddr
的完整性,所以在这里需要存储 44
位的 gvpn
,但是事实上 gvpn
的前 6
位一定会是 0
,否则说明第一阶段产生了错误的物理地址,会触发 gpf
,在此时需要将错误信息保存在 mtval2/htval
寄存器中,因此需要完整的 gpaddr
,正常情况下并不需要。(当发生页面错误时,mtval2
将被填充为生成错误的物理地址,帮助异常处理程序;htval
将被填充为导致异常的虚拟地址,帮助 hypervisor
识别问题)
如果发生了 redirect
,即重定向(可能触发了跳转/分支指令等或发生异常),此时之前的指令可能不会再访问 TLB
,TLB
需要根据 robidx
跟踪请求来源,有选择性地刷新相关的寄存器(即上表中提到的)。目前香山昆明湖架构中尚未实现,而是通过在需要 redirect
的时候发送 flushPipe
指令来实现的,无论哪一个请求端口被刷新均会导致这些寄存器被刷新。
getGPA
标志并不用于判断指令是否是请求 gpaddr
,PTW
不需要关心请求是干什么的,只需要负责查找并返回结果;TLB
内会通过一系列寄存器的比较来判断。这个信号的作用在于防止 TLB
重填,每次 TLB
发送带 getGPA
标志的请求时,PTW
在返回时会将 getGPA
信号传递回 TLB
,从而使 TLB
不进行重填,不存储此项 gpaddr
。
支持 PLRU 替换算法
LRU
(Least Recently Used
)算法核心思想就是替换掉最近最少使用的页,也就是最长时间没有访问的页。LRU
算法将内存中的每个页组织成了一个链表的形式,如图所示:
链表有两端,一端是最近最少使用的页,可以称为 LRU
端,另一端是最近刚刚使用的页,即最近使用最频繁的页,称之为 MRU
(Most Recently Used
)端。每次访问的时候如果命中,那么就将命中的页移动到 MRU
端,如果 miss
则触发缺页,此时需要加载页面。如果这时候内存已满,那么就需要进行页面替换,选择 LRU
端的页进行替换,并把新访问的页放在 MRU
端。这就是 LRU
替换算法,是 cache
替换的经典算法。
但是由于 LRU
需要为 cache
行维护一个链表数据结构,在多路组相联的 cache
行中需要为每一路配置链表并跟踪每一行的使用时间,LRU
算法有着巨大的开销。因此虽然 LRU
在页面替换中表现出色,也依然不常使用。
在香山的昆明湖架构中,TLB
采用 PLRU
(pseudo-LRU
)替换算法,详细来说是 tree-based PLRU
算法。假设当前 Cache
是 n
路组相联(n
一般是 2
的整数幂)的结构,那么需要定义 n-1
位用来进行二叉树索引,假设为 0
表示左,为 1
表示右,如图所示:
对目前的香山昆明湖架构来说,采用每路 48 cache
行的二路组相联结构下,PLRU
需要维护一个 48
项的链表和一个一级的二叉树(1
位),而采用 LRU
将需要维护一个 48
项的链表和 48
个 2
项的链表,有一定的开销优势,随着路数的增加,优势会更加明显;同时,对二叉树的维护成本也比链表更低。
当然,PLRU
多级二叉树的选择策略下并不能做到与 LRU
一样精确控制,每次二分地排除掉一半不一定能找到绝对 LRU
的条目。
支持 SFENCE.VMA 指令
SFENCE.VMA
指令(Supervisor Memory-Management Fence Instruction
)是定义在 RISC-V
指令架构的指令:
在内存管理中,页表负责将虚拟地址映射到物理地址。当修改了页表后,这些修改不会自动在处理器的缓存中生效。为了确保后续的指令能使用更新后的页表,必须通过 SFENCE.VMA
指令来刷新这些缓存。此外,处理器在执行指令时,可能隐式地对内存管理数据结构进行读取和写入操作,但这些隐式操作和显式的内存操作通常是无序的。SFENCE.VMA
指令可以强制处理器将某些隐式操作在显式操作之前完成,从而确保操作的顺序性。
SFENCE.VMA
是 RISC-V
架构中的一条特权指令,用于刷新与地址翻译相关的本地硬件缓存,处理内存管理数据结构的同步,特别是当需要确保对这些数据结构的修改在不同的硬件组件之间保持一致时需要频繁使用该指令。SFENCE.VMA
只影响本地核心(hart
),如果需要在多个核心之间同步,则需要核间中断等额外机制。虽然 SFENCE.VMA
指令对于维护一致性至关重要,但频繁调用可能会影响系统性能,因此,应根据实际需要合理使用,以平衡一致性和性能之间的关系。
SFENCE.VMA
的行为依赖于 rs1
和 rs2
,在 RISC-V
特权指令集中如下所述:
条件 |
---|
- 如果 rs1=x0 且 rs2=x0 ,栅栏会对所有地址空间的页面表的所有读写进行排序,并将所有地址翻译缓存条目标记为 invalid。 |
- 如果 rs1=x0 且 rs2 不是 x0 ,栅栏会对指定的地址空间的页面表的所有读写进行排序,但不对全局映射进行排序。它还会失效与指定地址空间匹配的地址翻译缓存条目,但不包括全局映射的条目。 |
- 如果 rs1 不是 x0 且 rs2=x0 ,栅栏会对所有地址空间的与 rs1 对应的虚拟地址的叶子页面表条目的读写进行排序,并失效包含该虚拟地址的所有叶子页面表条目的地址翻译缓存条目。 |
- 如果 rs1 不是 x0 且 rs2 不是 x0 ,栅栏会对与 rs1 对应的虚拟地址在指定地址空间的叶子页面表条目的读写进行排序,并失效与 rs1 对应的虚拟地址并匹配指定地址空间的所有叶子页面表条目的地址翻译缓存条目,但不包括全局映射的条目。 |
- 如果 rs1 中的值不是有效的虚拟地址,则 SFENCE.VMA 指令没有效果,且不会引发异常。 |
- 当 rs2=x0 时,rs2 中的值的 SXLEN-1:ASIDMAX 位保留供将来标准使用。在标准扩展定义其用法之前,这些位应由软件置为零并被当前实现忽略。此外,如果 ASIDLEN < ASIDMAX ,则实现应忽略 rs2 中值的 ASIDMAX-1:ASIDLEN 位。 |
SFENCE.VMA
指令的作用是确保在执行该指令之前的所有写入操作已经被提交到内存。这意味着 Store Buffer
中的所有未完成写入都会被写入到 DCache
或最终的内存地址中;SFENCE.VMA
发出刷新信号,通知 MMU
(内存管理单元)更新 TLB
(转换后备缓冲区)等内部状态。这一刷新信号是瞬时的,并且没有返回确认信号。在验证时需要通过再次访问观察是否 miss
的形式来进行,也可以通过分析波形文件观察 TLB
内部寄存器行为。
Store Buffer(存储缓冲区) |
---|
Store Buffer 用于提高内存写入效率,允许 CPU 在发出写入操作后,立即继续执行后续指令,而不需要等待内存系统确认写入完成。这有助于减少 CPU 的闲置时间,提高指令执行的整体效率。写回时,写入数据首先被放入 Store Buffer,随后,数据会按某种策略写入主内存(如 DCache 或其他存储层级)。Store Buffer 维护写入操作的顺序,但不保证这些写入操作立即反映在内存中。在多核处理器中,Store Buffer 可以帮助降低缓存一致性协议的复杂性。 |
支持 HFENCE.VVMA 与 HFENCE.GVMA 指令
事实上,对 hv
(SFENCE Bundle
中的信号,用于刷新第一阶段地址转换的条目)和 hg
(SFENCE Bundle
中的信号,用于刷新第二阶段地址转换的条目)信号不为 0
的情况执行的指令并不是 SFENCE.VMA
,而是 HFENCE.VVMA
和 HFENCE.GVMA
:
这两个指令与 SFENCE.VMA
功能很相似,区别在于 HFENCE.VVMA
适用于由 vsatp
控制的 VS
级别内存管理数据结构;HFENCE.GVMA
适用于由 hgatp
控制的虚拟机监管程序 G
阶段内存管理数据结构。
HFENCE.VVMA
仅在 M
模式或 HS
模式生效,类似于暂时进入 VS
模式并执行 SFENCE.VMA
指令,可以保证当前 hart
之前的所有存储操作在后续的隐式读取 VS
级别内存管理数据结构之前都已经排序;注意这里所说的隐式读取指的仅有在 HFENCE.VVMA
之后执行的,并且 hgatp.VMID
与执行 HFENCE.VVMA
相同的时候,简单来说就是仅对当前这一个虚拟机生效。rs1
与 rs2
的功能与 SFENCE.VMA
相同。
对 HFENCE.GVMA
来说,rs1
指定的是客机的物理地址。由于主机采用 SV48
而虚拟机采用 SV48x4
,客机物理地址比主机物理地址多两位,因此此时需要将 rs1
对应的客机物理地址右移两位。如果某一个虚拟机的地址翻译模式更改了,也即 hgatp.MODE
对某个 VMID
更改了,则必须使用 HFENCE.GVMA
指令,将 rs1
设为 0
,rs2
设为 0
或 VMID
进行刷新。
在香山中,由于 TLB
本身不存储中间物理地址,也即 TLB
并不存储 VS
阶段转换出来的虚拟机物理地址,也无法单独提供 G
阶段地址转换请求。在 TLB
中存储的是两阶段地址翻译的最终结果,因此 HFENCE.VVMA
与 HFENCE.GVMA
在 TLB
中作用相同,均为刷新掉两阶段地址翻译的结果。无论 hv
与 hg
哪一个信号为 1
都将刷新两阶段的条目。
支持 SINVAL 扩展
在 RISC-V
特权指令集中定义了 Svinval
扩展(Supervisor Virtual Address Invalidation
),在香山昆明湖架构实现了该扩展。Svinval
扩展的意义在于将 SFENCE.VMA
指令更加细化为 SFENCE.W.INVAL
、SINVAL.VMA
、SFENCE.INVAL.IR
三条指令(HFENCE.VVMA
和 HFENCE.GVMA
同理)。
SINVAL.VMA
指令事实上与 SFENCE.VMA
指令的功能基本一致,只是添加了对 SFENCE.W.INVAL
与 SFENCE.INVAL.IR
两个指令的相互排序,可以理解为需要在两个指令中间进行。SFENCE.W.INVAL
指令用于确保当前 RISC-V hart
可见的任何先前存储在后续由同一个 hart
执行的 SINVAL.VMA
指令之前被重新排序。SFENCE.INVAL.IR
指令确保当前 hart
执行的任何先前 SINVAL.VMA
指令在后续隐式引用内存管理数据结构之前被排序。当由单个 hart
按顺序(不一定连续)执行 SFENCE.W.INVAL
、SINVAL.VMA
和 SFENCE.INVAL.IR
时,可以相当于执行了 SFENCE.VMA
指令。
支持软件更新 A/D 位
A
位(Access
)用于指示某一页面是否被访问过。如果处理器对该页面进行任何形式的访问(读/写/取指),则 A
位会被设置为 1
。每当 CPU
访问某个页面时,操作系统或硬件会自动将 A
位设置为 1
,这种更新通常是硬件支持的,由处理器在地址转换时自动进行。
D
位(Dirty
)指示页面是否被修改。如果页面在内存中被写入,则 D
位会被设置为 1
,表示该页面的内容已被更改。当处理器对页面进行写操作时,通常会自动将 D
位设置为 1
,这种更新通常也是由硬件支持的。在页面替换过程中,操作系统会检查 D
位,如果 D
位为 1
,操作系统会将页面写回到磁盘,并在写回后清除 D
位,以表示页面已经被保存且不再是“脏”的。
在香山昆明湖架构中,并不支持硬件更新 A/D
位,而是在需要更新的时候通过 Page Fault
通知软件进行页表更新。具体来说,每当处理器访问某一页时检查该页 A
位如果是 0
,那么会发生 PF
;同样的,每当处理器写入某一页时检查该页的 D
位如果是 0
,同样会发生 PF
。在软件处理异常后,操作系统会允许处理器再次访问页面,只有在页表得到更新且相关状态位(A
和 D
位)被正确设置后,处理器才能继续进行后续的内存访问。
4.3 - 关键信号说明
相关 CSR 寄存器
val csr = Input(new TlbCsrBundle)
csr
:包含 satp
、vsatp
、hgatp
三个寄存器的信息以及一些权限信息。
class TlbCsrBundle(implicit p: Parameters) extends XSBundle {
val satp = new TlbSatpBundle()
val vsatp = new TlbSatpBundle()
val hgatp = new TlbHgatpBundle()
val priv = new Bundle {
val mxr = Bool()
val sum = Bool()
val vmxr = Bool()
val vsum = Bool()
val virt = Bool()
val spvp = UInt(1.W)
val imode = UInt(2.W)
val dmode = UInt(2.W)
}
override def toPrintable: Printable = {
p"Satp mode:0x${Hexadecimal(satp.mode)} asid:0x${Hexadecimal(satp.asid)} ppn:0x${Hexadecimal(satp.ppn)} " +
p"Priv mxr:${priv.mxr} sum:${priv.sum} imode:${priv.imode} dmode:${priv.dmode}"
}
}
TlbCsrBundle
中包含了 satp
、vsatp
、hgatp
以及 priv
特权标志。其中 satp
与 vsatp
通过 TlbSatpBundle
实现,包括 mode
、asid
、ppn
、changed
以及一个 apply
方法:
class SatpStruct(implicit p: Parameters) extends XSBundle {
val mode = UInt(4.W)
val asid = UInt(16.W)
val ppn = UInt(44.W)
}
class TlbSatpBundle(implicit p: Parameters) extends SatpStruct {
val changed = Bool()
// Todo: remove it
def apply(satp_value: UInt): Unit = {
require(satp_value.getWidth == XLEN)
val sa = satp_value.asTypeOf(new SatpStruct)
mode := sa.mode
asid := sa.asid
ppn := sa.ppn
changed := DataChanged(sa.asid) // when ppn is changed, software need do the flush
}
}
hgatp
通过 TlbHgatpBundle
实现,区别在于将 asid
替换为 vmid
:
class HgatpStruct(implicit p: Parameters) extends XSBundle {
val mode = UInt(4.W)
val vmid = UInt(16.W)
val ppn = UInt(44.W)
}
class TlbHgatpBundle(implicit p: Parameters) extends HgatpStruct {
val changed = Bool()
// Todo: remove it
def apply(hgatp_value: UInt): Unit = {
require(hgatp_value.getWidth == XLEN)
val sa = hgatp_value.asTypeOf(new HgatpStruct)
mode := sa.mode
vmid := sa.vmid
ppn := sa.ppn
changed := DataChanged(sa.vmid) // when ppn is changed, software need do the flush
}
}
SATP
satp (Supervisor Address Translation and Protection)
用于内核态(Supervisor mode
)进行虚拟地址到物理地址的转换管理,通常在非虚拟化环境或作为虚拟机监控程序(VMM
)时使用。mode
:地址转换模式,控制虚拟地址的转换,位宽为4
。其允许的值包含0
、8
、9
,如果是其它值应当触发illegal instruction fault
。0
:Bare
模式,不进行地址转换。8
:SV39
模式,使用三级页表支持39
位虚拟地址空间。9
:SV48
模式,使用四级页表支持48
位虚拟地址空间。
asid
:地址空间标识符,用于区分不同进程,香山昆明湖架构使用的SV48
中最大长度为16
。ppn
:Page Table Pointer
,根页表的物理页框号,其位宽为44
位,由物理地址右移12
位得到。
VSATP
vsatp (Virtual Supervisor Address Translation and Protection)
是虚拟机中客体操作系统的地址转换寄存器,提供虚拟机的虚拟地址到中间物理地址(IPA
)的转换。mode
:页表模式,控制虚拟地址的转换,模式值与satp
中的类似。asid
:虚拟机内地址空间标识符。ppn
:虚拟机页表的物理基地址。
HGATP
hgatp (Hypervisor Guest Address Translation and Protection)
是虚拟机监控程序(Hypervisor
)的二级地址转换寄存器,用于将虚拟机的中间物理地址(IPA
)转换为主机物理地址(HPA
)。mode
:页表模式,如SV39x4
或SV48x4
,用于虚拟机的二级地址转换。0
:Bare
模式,不进行二级地址转换。8
:SV39x4
模式,即39
位虚拟地址空间,允许四倍页表扩展。9
:SV48x4
模式,即48
位虚拟地址空间,允许四倍页表扩展。
vmid
:虚拟机标识符,区分不同虚拟机。ppn
:二级页表的物理基地址。
satp
管理主机地址空间的虚拟地址到物理地址的转换,vsatp
用于虚拟机中的虚拟地址到中间物理地址(IPA
)的转换,而 hgatp
则负责虚拟机二级地址转换,将 IPA
转换为主机物理地址。
PRIV
-
mxr :
Bool()
机器可执行只读(MXR)位。控制在用户模式下是否允许执行某些在机器层面被标记为只读的页面。 -
sum :
Bool()
特权模式可访问用户(SUM)位。控制特权模式下对用户模式地址的访问权限。 -
vmxr :
Bool()
虚拟机器可执行只读(VMXR
)位。控制虚拟机内的用户是否可以执行只读页面。 -
vsum :
Bool()
虚拟特权模式可访问用户(VSUM
)位。控制虚拟化环境中特权模式对用户模式地址的访问权限。 -
virt :
Bool()
虚拟化状态位。指示当前系统是否处于虚拟化模式。 -
spvp :
UInt(1.W)
超级特权虚拟模式(SPVP
)。指示当前是否处于虚拟化环境中的超级特权模式。 -
imode :
UInt(2.W)
指示当前(ITLB
)指令的处理模式:0x3
: ModeM(机器模式)0x2
: ModeH(虚拟机监控程序模式,已删除)0x1
: ModeS(特权模式)0x0
: ModeU(用户模式)
-
dmode :
UInt(2.W)
指示当前(DTLB
)数据的处理模式。
changed
- 用于标志对应
CSR
中的信息是否更改,一旦Mode
或Asid
(Vmid
)更改则必须同步将changed
置1
,TLB
在检测到changed
为1
时将会执行刷新操作,刷新掉旧的Asid
(Vmid
)的映射。
base_connect()
def base_connect(sfence: SfenceBundle, csr: TlbCsrBundle): Unit = {
this.sfence <> sfence
this.csr <> csr
}
// overwrite satp. write satp will cause flushpipe but csr.priv won't
// satp will be delayed several cycles from writing, but csr.priv won't
// so inside mmu, these two signals should be divided
def base_connect(sfence: SfenceBundle, csr: TlbCsrBundle, satp: TlbSatpBundle) = {
this.sfence <> sfence
this.csr <> csr
this.csr.satp := satp
}
sfence
val sfence = Input(new SfenceBundle)
sfence
:用于传入 SfenceBundle
,执行 SFENCE
指令刷新 TLB
缓存。
class SfenceBundle(implicit p: Parameters) extends XSBundle {
val valid = Bool()
val bits = new Bundle {
val rs1 = Bool()
val rs2 = Bool()
val addr = UInt(VAddrBits.W)
val id = UInt((AsidLength).W) // asid or vmid
val flushPipe = Bool()
val hv = Bool()
val hg = Bool()
}
override def toPrintable: Printable = {
p"valid:0x${Hexadecimal(valid)} rs1:${bits.rs1} rs2:${bits.rs2} addr:${Hexadecimal(bits.addr)}, flushPipe:${bits.flushPipe}"
}
}
valid
- 有效标志信号,指示
SFENCE.VMA
操作的请求是否有效。如果该信号为高(1
),表示当前的SFENCE.VMA
操作需要执行;如果为低(0
),则没有操作需要执行。
rs1
- 表示需要使用
SFENCE.VMA
指令中的rs1
寄存器的值,这个值通过信号addr
传入,标记了需要刷新的虚拟地址。 - 当
rs1
为非零时,表示SFENCE.VMA
只针对该虚拟地址所对应的页表条目进行刷新操作;如果rs1
为零,则表示刷新所有虚拟地址的映射。
rs2
- 表示需要使用
SFENCE.VMA
指令中的rs2
寄存器的值,其中存储着需要刷新的ASID
,通过信号id
传入。 - 当
rs2
为非零时,表示SFENCE.VMA
只对指定的ASID
进行刷新操作;如果rs2
为零,则表示刷新所有地址空间的映射。这个信号主要用于区分不同进程的地址空间。
addr
- 表示
SFENCE.VMA
指令中rs1
对应的虚拟地址(可能是部分地址)。该信号提供了具体的虚拟地址信息,当rs1
为非零时,TLB
将使用该地址作为参考,刷新与该地址对应的页表条目。它用于精细控制哪些地址映射需要被刷新。 - 信号的位宽为
VAddrBits
,即虚拟地址的位宽,可见于 \ref{subsec:consts},大小被定义为50
,其中事实上使用的只有addr[47:12]
,也即四级页表的四级索引部分,用于找到对应虚拟地址的页表项。
id
- 表示
SFENCE.VMA
操作涉及的地址空间标识符(ASID
)。用于指定某个具体的ASID
。它允许在多地址空间的场景下(例如多个进程共享一个处理器),只刷新某个特定进程的地址映射。 - 信号位宽为
AsidLength
,可见于 \ref{subsec:consts},大小为16
,意味着同时支持 $2^{16}$ 个虚拟地址空间。
flushPipe
- 控制是否需要 清空流水线。
SFENCE.VMA
操作不仅可能涉及刷新TLB
,还可能需要清空流水线以确保所有未完成的指令(可能依赖旧的地址映射)不会继续使用过时的页表映射。这个信号为高时,表示需要清空流水线。
hv
- 表示当前指令是否为
HFENCE.VVMA
。
hg
- 表示当前指令是否为
HFENCE.GVMA
。
外部传入参数
参数说明
class TLB(Width: Int, nRespDups: Int = 1, Block: Seq[Boolean], q: TLBParameters)(implicit p: Parameters) extends TlbModule
with HasCSRConst
with HasPerfEvents
参数 | 说明 |
---|---|
Width: Int |
指示 requestor 的数量 |
nRespDups: Int = 1 |
需要复制 response 的数目,默认为 1 (不复制) |
Block: Seq[Boolean] |
指示每个 requestor 是否被阻塞 |
q: TLBParameters |
TLB 使用的参数 |
p: Parameter |
全局参数(香山架构参数) |
实例化 TLB
时以香山架构的 itlb
为例:
val itlb = Module(new TLB(coreParams.itlbPortNum, nRespDups = 1, Seq.fill(PortNumber)(false) ++ Seq(true), itlbParams))
-
Width
值为coreParams.itlbParams
(实际计算逻辑):itlbPortNum: Int = ICacheParameters().PortNumber + 1 // Parameters.scala: line 276 ICacheParameters.PortNumber: Int = 2 // ICache.scala: line 43
最终
Width = 3
-
Block
参数说明:Seq.fill(PortNumber)(false) ++ Seq(true) // 前 2 端口不阻塞,第 3 端口阻塞
对应
itlb
的三个requestor
:requestor0/1
不阻塞,requestor2
阻塞。
VAddrBits
def VAddrBits = {
if (HasHExtension) {
if (EnableSv48)
coreParams.GPAddrBitsSv48x4
else
coreParams.GPAddrBitsSv39x4
} else {
if (EnableSv48)
coreParams.VAddrBitsSv48
else
coreParams.VAddrBitsSv39
}
} // Parameters.scala: line 596~608
// 相关参数定义
def HasHExtension = coreParams.HasHExtension // Parameters.scala: line582
coreParams.HasHExtension: Boolean = true // Parameters.scala: line66
coreParams.EnableSv48: Boolean = true // Parameters.scala: line91
// 地址位宽定义
coreParams.VAddrBitsSv39: Int = 39
coreParams.GPAddrBitsSv39x4: Int = 41
coreParams.VAddrBitsSv48: Int = 48
coreParams.GPAddrBitsSv48x4: Int = 50 // Parameters.scala: line71~74
- 香山昆明湖架构下的值:
50
- 地址处理逻辑:
- 主机地址转换时仅使用后 48 位(前两位忽略)
- 支持虚拟机时,物理地址扩展为 50 位(符合
Sv48x4
规范)
AsidLength
def AsidLength = coreParams.AsidLength // Parameters.scala: line 619
AsidLength: Int = 16 // Parameters.scala: line 79
- ASID 位宽:16 位
- 作用:标识地址空间,防止进程/虚拟机虚拟地址冲突
- 支持规模:
- 最大
65536
个并发进程(16 位) - 虚拟机通过
vmid
标识(14 位,支持16384
个虚拟机,符合手册要求)
- 最大
4.4 - 环境配置
WSL2+Ubuntu22.04+GTKWave(Windows用户推荐使用)
我们推荐 Windows10/11 用户通过 WSL2 进行开发,在此给出通过此方法进行环境配置的教程集锦,仅供参考。如环境安装过程中出现任何问题,欢迎在QQ群(群号:976081653)中提出,我们将尽力帮助解决。此页面将收集大家提出的所有环境配置相关问题并提供解决方案,欢迎随时向我们提问!
1、在 Windows 下安装 WSL2(Ubuntu22.04)
参考资源:
— 微软官方教程:如何使用 WSL 在 Windows 上安装 Linux
— 其它资源:安装WSL2和Ubuntu22.04版本
2、打开 WSL,换源
推荐使用清华源:清华大学开源软件镜像站-Ubuntu软件仓库
3、配置验证环境
请参照开放验证平台学习资源-快速开始-搭建验证环境配置环境。
以下是示例方法:
# 基本工具包
cd ~ && sudo apt-get update
sudo apt-get install -y build-essential cmake git wget curl lcov autoconf flex bison libgoogle-perftools-dev gcc python3.11 python3.11-dev python3.11-distutils python3-pip python-is-python3
rm -rf /var/lib/apt/lists/*
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1
curl -sS https://bootstrap.pypa.io/get-pip.py | python3.11
# verilator
git clone https://github.com/verilator/verilator.git
cd verilator
git checkout v4.218 # 4.218为最低需求版本,可自行查看并选择新版本
autoconf && ./configure && make -j$(nproc) && make install
cd .. && rm -rf verilator
# verible
curl -sS https://github.com/chipsalliance/verible/releases/download/v0.0-3946-g851d3ff4/verible-v0.0-3946-g851d3ff4-linux-static-x86_64.tar.gz -o /tmp/
tar -zxvf /tmp/verible-v0.0-3946-g851d3ff4-linux-static-x86_64.tar.gz -C /tmp/
copy /tmp/verible-v0.0-3946-g851d3ff4/bin/verible-* /usr/local/bin/
sudo chmod +x /usr/local/bin/verible-*
rm /tmp/verible-*
# pcre2
curl -sS https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.45/pcre2-10.45.tar.gz -o /tmp/
tar -zxvf /tmp/pcre2-10.45.tar.gz -C /tmp/
cd /tmp/pcre2-10.45
./configure --prefix=/usr/local && make -j$(nproc) && make install
rm -rf /tmp/pcre2* && cd ~
# swig
# 注意不要使用 apt install swig,将会下载不符合最低要求的版本 4.0.2
curl -sS http://prdownloads.sourceforge.net/swig/swig-4.3.0.tar.gz -o /tmp/
tar -zxvf /tmp/swig-4.3.0.tar.gz -C /tmp/
cd /tmp/swig-4.3.0
./configure --prefix=/usr/local && make -j$(nproc) && make install
rm -rf /tmp/swig* && cd ~
# 更新本地包
apt-get update && apt-get -y upgrade
# picker
git clone https://github.com/XS-MLVP/picker.git --depth=1
cd picker
make init && make && make install
cd .. && rm -rf picker
# UnityChipForXiangShan
git clone https://github.com/XS-MLVP/UnityChipForXiangShan.git
cd UnityChipForXiangShan
pip3 install --no-cache-dir -r requirements.txt
4、使用 GTKWave 查看波形文件
使用重庆大学硬件综合设计实验文档-Windows原生GTKWave给出的方法,可以通过在WSL中输入 gtkwave.exe wave.fst
打开在 Windows 下安装的 GTKWave。请注意,gtkwave在使用中需要进入 fst 文件所在文件夹,否则会出现无法
initialize 的情况。
gtkwave.exe /out/{test_name}.fst
5、使用 VSCode 插件 Live Server 查看验证报告
成功安装插件Live Server后,打开文件列表,定位到 /out/report/2025*-itlb-doc-*/index.html
右键并选择 Open With Live Server
,之后在浏览器中打开提示的端口(默认为//localhost:5500
)即可。
docker一键部署方案(MAC用户可用)
我们提供了 MAC 可用的 docker 环境,已在 Docker Hub 发布,名称为 unitychip-env
。安装 Docker Desktop 后在命令行使用以下命令即可获取并打开开发环境。需下载约 500MB 的镜像,展开后约占用 1GB 空间。
docker search unitychip-env
docker pull dingjunbi/unitychip-env && docker run unitychip-env
cd UnityChipForXiangShan && git pull