这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

IFU

IFU简介

IFU(Instruction Fetch Unit),取指令单元,负责从内存或ICache取出指令,经过预译码、扩展RVC和预检之后,将指令交给后续译码器进行进一步的译码。

IFU的子模块包括PreDecode,F3PreDecoder,RVCExpander,PredChecker和FrontendTrigger。

以下是IFU的架构简图:

framework

IFU功能介绍

XS_IFU流水级划分

香山的IFU一共分为5个阶段。

F0阶段:接收FTQ请求,同时告诉FTQ自己已经ready了

F1阶段:从FTQ请求中先计算出每个指令的pc,half_pc、cut_ptr(这是后续将icache返回的指令码进行切分的依据)

F2阶段:从icache获取响应数据(缓存行)并校验,提取出异常信息(包括页错误、访问错误、mmio信息);生成预测到的指令范围(但这并不是一个数字,而是一个用多位表示的bool数组,该位为1表示这一指令在预测块范围内);从缓存行中,利用上一阶段求出的cut_ptr切分出17×2的初步指令码,最后进行预译码和指令扩展。

F3阶段:这一阶段主要是对译码阶段的结果进行预检查,以及MMIO状态下的处理逻辑。

WB(写回)阶段:将预检查的结果写回FTQ,并向IBuffer写指令码和前端信息。

接收FTQ取指令请求(F0流水级)

​ 在F0流水级,IFU接收来自FTQ以预测块为单位的取指令请求。请求内容包括预测块起始地址、起始地址所在cache line的下一个cache line开始地址、下一个预测块的起始地址、该预测块在FTQ里的队列指针、该预测块有无taken的CFI指令(控制流指令)和该taken的CFI指令在预测块里的位置以及请求控制信号(请求是否有效和IFU是否ready)。每个预测块最多包含32字节指令码,最多为16条指令。IFU需要置位ready驱动FTQ向ICache发送请求。

指令切分产生初始指令码(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复制一份拼接在一起,从中间截取就可以拿到数据。

预译码(F2流水级,主要由PreDecode模块完成)

在F2流水级,我们需要将上一步完成切分的指令码交给PreDecode子模块,他的作用主要有二:

其一是生成预译码信息,包括该指令是否是有效指令的开始、是否是RVC指令、是否是CFI指令、CFI指令类型(branch/jal/jalr/call/ret)、CFI指令的目标地址计算偏移等。输出的预译码信息中brType域的编码如下:

CFI指令类型 brType类型编码
非CFI 00
branch指令 01
jal指令 10
jalr指令 11

brType类型一览

其二是将初始指令码两两组合之后,得到16x4字节的指令码(从起始地址开始,2字节做地址递增,地址开始的4字节作为一条32位初始指令码)。

此外,预译码阶段还需要分类讨论,得出两种指令有效向量(起始指令是不是RVI指令的后半部分),并交给IFU进行判断,可以参阅后面的跨预测块32位指令处理部分

其他功能和详细内容参见PreDecode子模块的描述。

指令扩展(F3流水级)

这一部分将从PreDecode返回的16条指令码分别送交指令扩展器进行32位指令扩展(RVI保持不变, RVC指令根据手册的规定进行扩充)。

但是,如果指令非法,需要向IBuffer写入原始指令码。

预测错误预检查(F3流水级,主要由PreChecker子模块完成)

这一功能是为了将一些不依赖于执行结果的预测错误在早期就发现出来。这一阶段检查五类错误:

jal类型错误:预测块的范围内有jal指令,但是预测器没有对这条指令预测跳转;

ret类型错误:预测块的范围内有ret指令,但是预测器没有对这条指令预测跳转;

无效指令预测错误:预测器对一条无效的指令(不在预测块范围/是一条32位指令中间)进行了预测;

非CFI指令预测错误:预测器对一条有效但是不是CFI的指令进行了预测;

转移目标地址错误:预测器给出的转移目标地址不正确。

在预检查的最后将会修正之前预测的各个指令的跳转情况。同时,如果存在jal或者ret类型预测错误,还将修正fixedRange——这是指令有效范围向量,可以看作一个bool数组,其中某一位为1也就是对应的指令在这一范围内。

前端重定向(WB阶段)

如果在预测错误预检查的部分发现了上述的五类错误,那么需要在写回阶段产生一个前端重定向将F3以外的流水级进行冲刷, 从而让BPU能够从正确路径重新开始预测。

还有一种情况下需要冲刷流水线。在下一节中,如果误判了当前预测块的最后2B为RVI指令的上半部分,则也需要冲刷当前预测块F3之前的流水级。

跨预测块32位指令处理

因为预测块的长度有限制,因此存在一条RVI指令前后两字节分别在两个预测块的情况。IFU首先在第一个预测块里检查最后2字节是不是一条RVI指令的开始,如果是并且该预测块没有跳转,那么就设置一个标识寄存器f3_lastHalf_valid,告诉接下来的预测块含有后半条指令。在F2预译码时,会产生两种不同的指令有效向量:

预测块起始地址开始即为一条指令的开始,以这种方式根据后续指令是RVC还是RVI产生指令有效向量

预测块起始地址是一条RVI指令的中间,以起始地址 + 2位一条指令的开始产生有效向量

在F3,根据是否有跨预测块RVI标识来决定选用哪种作为最终的指令有效向量,如果f3_lastHalf_valid为高则选择后一种(即这个预测块第一个2字节不是指令的开始)。IFU所做的处理只是把这条指令算在第一个预测块里,而把第二个预测块的起始地址位置的2字节通过改变指令有效向量来无效掉。

将指令码和前端信息送入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正确。

分支预测overriding冲刷流水线

当FTQ内未缓存足够预测块时,IFU可能直接使用简单分支预测器提供的预测地址进行取指,这种情况下,当精确预测器发现简单预测器错误时,需要通知IFU取消正在进行的取指请求。具体而言,当BPU的S2流水级发现错误时,需要冲刷IFU的F0流水级;当BPU的S3流水级发现错误时,需要冲刷IFU的F0/F1流水级(BPU的简单预测器在S1给出结果,最晚在S3进行overriding,因此IFU的F2/F3流水级一定是最好的预测,不需要冲刷;类似地,不存在BPU S2到IFU F1的冲刷)。

IFU在收到BPU发送的冲刷请求时,会将F0/F1流水级上取指请求的指针与BPU发送的冲刷请求的指针进行比较,若冲刷的指针在取指的指针之前,说明当前取指请求在错误的执行路径上,需要进行流水线冲刷;反之,IFU可以忽略BPU发送的这一冲刷请求。此外,比较的时候还需要注意flag的情况,flag是一个指示队列循环的指针,flag不同即在不同的“圈”上,此时反而是idx的值更小,ftqIdx才会更大。

指令信息和误预测信息写回FTQ(WB阶段)

在F3的下一级WB级,IFU将指令PC、预译码信息、错误预测指令的位置、正确的跳转地址以及预测块的正确指令范围等信息写回FTQ,同时传递该预测块的FTQ指针用以区分不同请求。

同时,正如前面提到的,IFU检测到预测错误时会进行前端冲刷,同样地,FTQ也需要据此进行冲刷,因此,这也是IFU写回错误信息的意义——可以辅助FTQ判断是否冲刷流水线。

MMIO处理逻辑

在处理器上电复位时,内存还没有准备好,此时需要从Flash中取指令执行。 这种情况下需要IFU向MMIO总线发送宽度为64位的请求从flash地址空间取指令执行。同时IFU禁止对MMIO总线的推测执行,即IFU需要等到每一条指令执行完成得到准确的下一条指令地址之后才继续向总线发送请求。

这之后,根据FTQ中的指令地址,决定是否MMIO取指令。

mmio_states

以上是MMIO状态机的简图。在一开始,处于m_idle状态,如果处于mmio请求的场景,则转换到 m_waitLastCmt,之后只要之前所有的指令都已完成提交——或者这一指令就是第一条指令, 则进入m_sendReq状态将请求发送到InstrUncache模块,发送完成后进入m_waitResp状态。 接收请求后,由于MMIO总线的带宽限制为64位, 因此存在一条指令一次MMIO请求不能取得完整指令码的情况(这是由于MMIO并不支持非对齐访问,具体地说,如果RVI指令的起始地址的[2, 1]两位为b11,则64位总线无法一次传递所有指令),所以需要增加地址进行重发,进入m_sendTLB状态, 再次查询ITLB,如果tlb的pbmt状态和上一条的存在差别,则为访问异常,综合TLB自身的异常结果和根据pbmt判定的访问异常,如果存在异常,则直接把指令和异常信息发到Ibuffer,进入等待,否则进入m_sendPMP状态,向PMP发送请求, 这里需要查看pmp_recheck的结果,如果该请求的mmio状态和上一条的mmio状态不一致,那么说明可能存在访问错误,置为访问异常,否则根据PMP的回复结果决定是否存在PMP异常。 如果存在异常,则将报错信息发送给Ibuffer,直接进入等待。 一切正常的话,进入m_resendReq状态,重新发送请求到InstrUncache模块。 无论是否重发,最后获得完整数据之后,根据地址从64位数据中截取指令码。并以每个预测块一条指令的形式(相当于只有起始地址的指令)发送到IBuffer。 接下来进入m_waitCommit状态等待,直到ROB返回指令已提交的信号即进入m_commited状态,CFI指令由后端发送给FTQ进行冲刷,而顺序指令则由IFU复用前端重定向通路刷新流水线, 同时复用FTQ写回机制,把它当作一条错误预测的指令进行冲刷,重定向到该指令地址 + 2或者+4(根据这条指令是RVI还是RVC选择)

对于跨缓存行预测块,他们的mmio和pbmt状态应当等同。不匹配的错误应当只在后一个缓存行报告。

此外,如果当前pbmt为nc,则会跳过waitLastCmt和waitCommit状态。因为这些内存空间是幂等的,所以可以进行推测性取指。

Trigger实现对于PC的硬件断点功能

该工作主要由FrontEndTrigger子模块完成。

IFU接口说明

为方便测试开展,需要对IFU的接口进行进一步的说明,以明确各个接口的含义。

FTQ交互接口

编译后可用的接口包括:

req FTQ取指请求

req是FTQ向IFU的取指令请求,编译后包含以下成员:

接口名 解释
ftqIdx 指示当前预测块在FTQ中的位置。
ftqOffset 指示预测块的大小
startAddr 当前预测块的起始地址。
nextlineStart 起始地址所在cacheline的下一个cacheline的开始地址。
nextStartAddr 下一个预测块的起始地址

redirect FTQ重定向请求

FTQ会向IFU发送重定向请求,这通过fromFtq.redirect完成,从而指示IFU应该冲刷的内容。

编译后,redirect包含以下接口成员:

接口名 解释
ftqIdx 需要冲刷的ftq预测块序号,包含flag和value两个量。
level 重定向等级
ftq_offset ftq预测块中跳转指令的位置

此外,还有valid变量指示是否需要重定向。

fromBPUFlush

来自IFU的冲刷请求,这是预测错误引起的,包括s3和s2两个同构成员,指示是否在BPU的s3和s2流水级发现了问题,s3的详细结构如下

接口名 解释
valid 是否存在s3流水级冲刷要求
ftqIdx s3流水级请求冲刷的预测块的指针

toFtq_pdWb 写回

接口名 解释
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自己已经准备好了,可以发送缓存行了。
icache_stop IFU在F3流水级之前出现了问题,通知ICache停下。

ICacheInter.resp ICache传送给IFU的信息

接口名 解释
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重发请求时活跃。

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 是否传导

IFU功能点和测试点

功能点1 接收FTQ预测块请求

功能点1.1 F0流水级接收请求

向FTQ报告自己已ready。

所以,我们只需要在发送请求后检查和ftq相关的的ready情况即可。

序号 名称 描述
1 ready置位 IFU接收FTQ请求后,设置ready

功能点2 指令切分产生初始指令码

功能点2.1 F1流水级计算信息和切分指针

F1流水级也会计算PC。

同时还需要生成17位的切分指针(也就是从拼接后的缓存行切出初始指令码的idx数组,在昆明湖架构中,计算方式为拼接00和startAddr[5:1], 然后分别与0~16相加) 用于后续从缓存行提取初始指令码。

所以,首先我们需要检查F1流水级生成的PC的正确与否。如果可能,也需要检查一下切分指针的生成。

所以,可以总结出以下的测试点:

序号 名称 描述
2.1.1 PC生成 IFU接收FTQ请求后,在F1流水级生成PC
2.1.2 切取指针生成 IFU接收FTQ请求后,在F1流水级生成后续切取缓存行的指针

功能点2.2 F2流水级获取指令信息

包括获取异常信息、物理地址、客户物理地址、是否在MMIO空间等。

获取异常信息之后,还需要计算异常向量。ICache会为每个缓存行返回一个异常类型,只需要计算每个指令pc属于哪个缓存行, 然后将对应缓存行的异常类型赋给该位置即可。

所以,只需要分别检查几种指令信息即可。

序号 名称 描述
2.2.1 异常向量生成 IFU接收ICache内容后,会根据ICache的结果生成属于每个指令的异常向量
2.2.2 物理地址提取 IFU接收ICache内容后,会根据ICache的结果生成属于每个端口的物理地址。
2.2.3 客户物理地址提取 IFU接收ICache内容后,会根据ICache的结果生成0号端口的客户物理地址。
2.2.4 MMIO空间信息提取 IFU接收ICache内容后,会根据ICache的结果判断当前取指请求是否属于MMIO空间。

功能点2.3 F2流水级计算预测块有效指令范围

指令有效范围包括两种,无跳转和有跳转的

无跳转的指令有效范围为当前预测块从起始地址到下一个预测块的起始地址的所有指令。

有跳转的指令有效范围jump_range为当前预测块的起始地址到预测块中第一个跳转指令地址(包含第一个跳转指令地址)之间的所有指令。

最终的指令有效范围是两者相与的结果。

序号 名称 描述
2.3.1 无跳转指令有效范围生成 IFU根据FTQ请求,计算无跳转指令有效范围
2.3.2 有跳转指令有效范围生成 IFU根据FTQ请求,计算跳转指令有效范围

功能点2.4 提取初始指令码

IFU需要将ICache返回的缓存行复制一份并拼接。然后利用上一流水级计算的idx数组,从缓存行提取17x2字节的初始指令码。

序号 名称 描述
2.4 切取初始指令码 IFU根据上一流水级的切取指针,从缓存行提取初始指令码。

功能点3 预译码

多数的功能都由preDecoder子模块完成,因此这里只罗列由IFU本身需要完成的功能。

功能点3.1 F3流水级选取指令有效向量

由于存在跨缓存行的32位指令,IFU需要做的是,根据上一个预测块最后两字节是否为一条RVI指令的开始,从两种指令有效开始向量中,选择一种。

序号 名称 描述
3.1.1 上一预测块结尾为RVC或RVI下半部分 上一预测块的最后2字节恰为RVC指令或RVI指令的后半部分,选择第一位为True的有效开始向量
3.1.2 上一预测块结尾为RVI上半部分 上一预测块的最后2字节为RVI,选择第一位为False的有效开始向量

功能点4 指令扩展

将PreDecode返回的16条指令码分别送交指令扩展其进行32位指令扩展,RVI保持不变。RVC指令根据手册规定进行扩展。 如果指令非法,则需要将原始指令填写到CSR(控制状态寄存器)中。IFU自身仅仅控制最后填写的是哪种指令。

所以,我们对IFU模块,只关注最后写的指令是何种指令。注意,这里可以修改fsIsOff的入参,测试c.fp指令是否返回原始指令码

序号 名称 描述
4.1 合法RVC指令写扩展指令码 对合法RVC指令,写扩展后的指令码
4.2 非法RVC指令写原始指令码 对非法RVC指令,写原始指令码
4.3 RVI指令不扩展 RVI指令直接写入原始指令即可

功能点5 预测错误预检

主要由PredChecker子模块完成。测试点和PredChecker子模块的测试点类似,IFU没有额外的测试点。预检可以和重定向一起测试。

功能点6 前端重定向和流水线冲刷

功能点6.1 预测错误重定向

如果发现了预检阶段检出的错误,则需要产生前端重定向,将F3以外的流水级冲刷

只需要构造有预测错误的预测请求,检查冲刷情况即可。

序号 名称 描述
6.1.1 JAL预测错误冲刷 预测请求中存在JAL预测错误,需要冲刷流水线
6.1.2 RET预测错误冲刷 预测请求中存在RET预测错误,需要冲刷流水线
6.1.3 非CFI预测错误冲刷 预测请求中存在非CFI预测错误,需要冲刷流水线
6.1.4 无效指令预测错误冲刷 预测请求中存在无效指令预测错误,需要冲刷流水线
6.1.5 跳转目标错误冲刷 预测请求中存在跳转目标错误,需要冲刷流水线

功能点6.2 跨预测块32位指令处理

如果发现当前预测块的最后两个字节是一条RVI指令的开始,则设置一个标识f3_lastHalf_valid,告诉接下来的预测块含有后半条指令。

我们没有办法直接观察到这个标识,但是可以通过下一预测块来判定:

序号 名称 描述
6.2.1 跨预测块32位指令处理 连续传入两个预测块,其中有一条32位指令跨两个预测块,后一个预测块的指令开始向量的首位应该为False

但是,如果这一判断出现问题(比如当前预测块存在跳转),则需要进行流水线冲刷。

这一功能需要PredChecker子模块“配合”(仅仅通过外部IO的修改很难触发这个防御机制),实现起来比较麻烦,但是还是列举一个测试点:

序号 名称 描述
6.2.2 跨预测块指令误判 当IFU根据PredChecker修复的指令有效范围错判了跨预测块指令时,需要将F3以外的流水级全部冲刷

功能点7 将指令码和前端信息输出给IBuffer

功能点7.1 传送指令码和前端信息

传送给IBuffer的信息包括:经过扩展的32位指令码、16条指令中每条指令的例外信息、预译码信息、FTQ队列中的指针位置、其他后端需要的信息(经过折叠的PC)、 io_toIbuffer_bits_valid(表示指令是否是一条指令的开始)、io_toIbuffer_bits_enqEnable(前者与上被修正过的预测块指令范围, 从而还能表示指令是否在预测块表示的指令范围内)。

这里要做的只是确认这些信息是否正确传递

序号 名称 描述
7.1.1 指令码传送 IFU向IBuffer传送扩展后的指令码
7.1.2 异常信息传送 IFU向IBuffer传送每个指令的异常信息
7.1.3 预译码信息传送 IFU向IBuffer传递每个指令的预译码信息
7.1.4 FTQ指针传送 IFU向IBuffer传送FTQ预测块的指针
7.1.5 折叠PC传送 IFU向IBuffer传送折叠的PC
7.1.6 有效开始向量 IFU向IBuffer传送表示指令有效和指令是否为指令开始的向量

功能点7.2 客户页错误传送gpaddr信息

当且仅当发生guest page fault时,后端需要gpaddr信息,为了节省面积,gpaddr不走正常通路进入ibuffer, 而是随ftqPtr被发送到gpaMem,后端需要时从gpaMem读出。IFU需要保证gpf发生时通向gpaMem的valid拉高、gpaddr正确,同时还要传递预测块的ftqIdx(通过waddr传入)。

这里我们只需要确保在客户页错误发生时通向gpaMem的valid为高,且gpaddr正确填入。

序号 名称 描述
7.2 客户页错误 客户页错误发生时,IFU应将gpaMem的valid拉高且填入gpaddr

功能点8 分支预测冲刷流水线

当精确预测器发现简单预测器错误时,通知IFU取消正在进行的取指请求。

功能点8.1 核验指针

IFU收到BPU冲刷请求后,会将F0/F1流水级上取指令请求的指针比较,冲刷的指针在取指之前,即当前取指令请求在错误的执行路径上,才需要 冲刷IFU。

我们仍然需要从两个方向校验这个功能,即当冲刷指针在取指令的指针之前时,IFU能够对流水线进行冲刷。 然而,当冲刷指令在取指令的指针之后时,则不能对流水线进行冲刷。

序号 名称 描述
8.1.1 错误执行路径 当冲刷指针在取指令的指针之前时,IFU能够对流水线进行冲刷。
8.1.2 执行路径无误 当冲刷指令在取指令的指针之后时,IFU不能对流水线进行冲刷。

功能点8.2 BPU S2流水级发现错误

BPU的S2流水级发现错误时,需冲刷IFU的F0流水级

序号 名称 描述
测试点8.2 BPU S2流水级发现错误 当BPU的S2流水级出现错误,并且当前取指指针在错误执行路径上时,需要对IFU的F0流水级进行冲刷

功能点8.3 BPU S3流水级发现错误

BPU的S3流水级发现错误时,需要冲刷IFU的F0和F1流水级

序号 名称 描述
8.3 BPU S3流水级发现错误 当BPU的S3流水级出现错误,并且当前取指指针在错误执行路径上时,需要对IFU的F0和F1流水级进行冲刷

功能点9 指令信息和误预测信息写回FTQ

功能点9.1 写回指令信息和误预测信息

将指令PC、预译码信息、错误预测指令的位置、正确的跳转地址以及预测块的正确指令范围等信息写回FTQ,并传递该预测块的FTQ指针。

序号 名称 描述
9.1.1 写回指令PC IFU的WB流水级,需要向FTQ写回指令PC
9.1.2 写回预译码信息 IFU的WB流水级,需要向FTQ写回每个指令的预译码信息
9.1.3 写回误预测指令位置 IFU的WB流水级,需要向FTQ写回BPU错误预测的指令位置
9.1.4 写回正确跳转地址 IFU的WB流水级,需要向FTQ写回该预测块的正确跳转地址
9.1.5 写回正确指令范围 IFU的WB流水级,需要向FTQ写回预测块的正确指令范围
9.1.6 传递预测块FTQ指针 IFU的WB流水级,需要向FTQ传递预测块的FTQ指针

功能点10 MMIO处理

功能点10.1 上电复位处理

处理器上电复位时,IFU需向MMIO总线发送宽度为64位的请求从flash地址空间取指令,并禁止对MMIO总线的推测执行。

上电的情况和正常情况其实没有任何区别,但是,上电时的MMIO请求没有任何差别,只是,第一条请求一定是MMIO,并且不需要等待。

序号 名称 描述
10.1 第一条MMIO指令 IFU收到的第一条MMIO请求可以直接查询Instr Uncache

功能点10.2 向InstrUncache发送请求

在正常的处理逻辑下,如果请求地址处于MMIO地址空间,则IFU会向FTQ查询指令提交状态,IFU需要等待当前请求之前的所有请求(包括MMIO和非MMIO)提交完成, 才能向InstrUncache模块发送请求。

这里需要和FTQ交互,可以让FTQ模拟请求提交情况,从而测试等待情况。 如果MMIO请求之前的请求都已经提交,则也不需要等待。反之,则需要一直等待直到查询结果表明前面的指令均已提交。故设计测试点如下:

序号 名称 描述
10.2.1 阻塞等待提交 IFU收到MMIO请求后,查询FTQ,如果前面还有尚未提交的指令,持续等待
10.2.2 无阻塞发送请求 如果查到FTQ不再有未提交的指令,则IFU将指令发送给Instr Uncache

功能点10.3 跨总线请求处理

由于MMIO不支持非对齐访问,因此当检测到的RVI指令地址[2,1]两位为b11时,64位总线无法一次传递所有指令,所以需要增加地址进行重发,再次查询ITLB。

序号 名称 描述
10.3.1 重发查询ITLB 遇到一次无法查询完毕的RVI指令时,需要向ITLB查询获得新增指令的物理地址

如果存在异常,则直接将指令和异常信息发送到IBuffer并等待,否则向PMP发送请求。

序号 名称 描述
10.3.2.1 ITLB异常 IFU查询ITLB出现异常时,应当将异常信息发送到IBuffer,然后等待ROB提交完成
10.3.2.2 ITLB返回物理地址 IFU查询ITLB正常返回物理地址时,IFU继续向PMP请求检查

根据pmp_recheck的结果,如果和上一次请求状态不一致,则说明存在访问错误, 为访问异常,不然则根据PMP的回复结果决定是否存在异常。如存在异常(访问异常和其他异常),则将报错信息发送给IBuffer并等待。如无异常,重新向InstrUncache模块 发送请求。

序号 名称 描述
10.3.3.1 请求状态不一致 IFU检查PMP之后如果发现重发请求状态和上一条请求状态不一致,是访问异常,需要将异常直接发送到IBuffer
10.3.3.2 PMP检查异常 PMP检查出现异常的情况下,也需要将异常直接发送到IBuffer并等待ROB提交。
10.3.3.3 Instr Cache请求重发 PMP检查若无异常,则向Instr Uncache发送请求获取指令码的后半部分。

功能点10.4 向IBuffer发送指令

IFU获得完整数据之后,根据地址从64位数据中截取指令码,并以每个预测块一条指令的形式发送到Ibuffer。等待ROB返回指令已提交的信号。

序号 名称 描述
10.4 向IBuffer发送指令 IFU在获得完整数据后,截取获得指令码,以每个预测块一条指令的形式发送给IBuffer

功能点10.5 指令冲刷

CFI指令的冲刷由后端发送给FTQ完成。所以不需要在这里设置测试点。

顺序指令由IFU复用前端重定向通路刷新流水线,并复用FTQ写回机制,将该指令当作误预测指令冲刷,重定向到+2或+4的位置。

+2和+4是由RVC和RVI指令决定的,所以设置测试点如下:

序号 名称 描述
10.5.1 RVI指令重定向 如果是RVI指令,传递给FTQ的冲刷请求应该重定向到PC+4
10.5.2 RVC指令重定向 如果是RVC指令,传递给FTQ的冲刷请求应该重定向到PC+2

功能点11 硬件断点

该功能主要由FrontEndTrigger子模块完成。不需要为这一功能额外设置测试点(参照FrontendTrigger的测试点即可)

测试点汇总

序号 功能 名称 描述
1 接收FTQ预测块请求 ready置位 IFU接收FTQ请求后,设置ready
2.1.1 F1流水级计算信息和切分指针 PC生成 IFU接收FTQ请求后,在F1流水级生成PC
2.1.2 F1流水级计算信息和切分指针 切取指针生成 IFU接收FTQ请求后,在F1流水级生成后续切取缓存行的指针
2.2.1 F2流水级获取指令信息 异常向量生成 IFU接收ICache内容后,会根据ICache的结果生成属于每个指令的异常向量
2.2.2 F2流水级获取指令信息 物理地址提取 IFU接收ICache内容后,会根据ICache的结果生成属于每个端口的物理地址。
2.2.3 F2流水级获取指令信息 客户物理地址提取 IFU接收ICache内容后,会根据ICache的结果生成0号端口的客户物理地址。
2.2.4 F2流水级获取指令信息 MMIO空间信息提取 IFU接收ICache内容后,会根据ICache的结果判断当前取指请求是否属于MMIO空间。
2.3.1 F2流水级计算预测块有效指令范围 无跳转指令有效范围生成 IFU根据FTQ请求,计算无跳转指令有效范围
2.3.2 F2流水级计算预测块有效指令范围 有跳转指令有效范围生成 IFU根据FTQ请求,计算跳转指令有效范围
2.4 提取初始指令码 切取初始指令码 IFU根据上一流水级的切取指针,从缓存行提取初始指令码。
3.1.1 F3流水级选取指令有效向量 上一预测块结尾为RVC或RVI下半部分 上一预测块的最后2字节恰为RVC指令或RVI指令的后半部分,选择第一位为True的有效开始向量
3.1.2 F3流水级选取指令有效向量 上一预测块结尾为RVI上半部分 上一预测块的最后2字节为RVI,选择第一位为False的有效开始向量
4.1 指令扩展 合法RVC指令写扩展指令码 对合法RVC指令,写扩展后的指令码
4.2 指令扩展 非法RVC指令写原始指令码 对非法RVC指令,写原始指令码
4.3 指令扩展 RVI指令不扩展 RVI指令直接写入原始指令即可
6.1.1 预测错误重定向 JAL预测错误冲刷 预测请求中存在JAL预测错误,需要冲刷流水线
6.1.2 预测错误重定向 RET预测错误冲刷 预测请求中存在RET预测错误,需要冲刷流水线
6.1.3 预测错误重定向 非CFI预测错误冲刷 预测请求中存在非CFI预测错误,需要冲刷流水线
6.1.4 预测错误重定向 无效指令预测错误冲刷 预测请求中存在无效指令预测错误,需要冲刷流水线
6.1.5 预测错误重定向 跳转目标错误冲刷 预测请求中存在跳转目标错误,需要冲刷流水线
6.2.1 跨预测块32位指令处理 跨预测块32位指令处理 连续传入两个预测块,其中有一条32位指令跨两个预测块,后一个预测块的指令开始向量的首位应该为False
6.2.2 跨预测块32位指令处理 跨预测块指令误判 当IFU根据PredChecker修复的指令有效范围错判了跨预测块指令时,需要将F3以外的流水级全部冲刷
7.1.1 传送指令码和前端信息 指令码传送 IFU向IBuffer传送扩展后的指令码
7.1.2 传送指令码和前端信息 异常信息传送 IFU向IBuffer传送每个指令的异常信息
7.1.3 传送指令码和前端信息 预译码信息传送 IFU向IBuffer传递每个指令的预译码信息
7.1.4 传送指令码和前端信息 FTQ指针传送 IFU向IBuffer传送FTQ预测块的指针
7.1.5 传送指令码和前端信息 折叠PC传送 IFU向IBuffer传送折叠的PC
7.1.6 传送指令码和前端信息 有效开始向量 IFU向IBuffer传送表示指令有效和指令是否为指令开始的向量
7.2 客户页错误传送gpaddr信息 客户页错误 客户页错误发生时,IFU应将gpaMem的valid拉高且填入gpaddr
8.1.1 核验指针 错误执行路径 当冲刷指针在取指令的指针之前时,IFU能够对流水线进行冲刷。
8.1.2 核验指针 执行路径无误 当冲刷指令在取指令的指针之后时,IFU不能对流水线进行冲刷。
8.2 BPU S2流水级发现错误 BPU S2流水级发现错误 当BPU的S2流水级出现错误,并且当前取指指针在错误执行路径上时,需要对IFU的F0流水级进行冲刷
8.3 BPU S2流水级发现错误 BPU S3流水级发现错误 当BPU的S3流水级出现错误,并且当前取指指针在错误执行路径上时,需要对IFU的F0和F1流水级进行冲刷
9.1.1 写回指令信息和误预测信息 写回指令PC IFU的WB流水级,需要向FTQ写回指令PC
9.1.2 写回指令信息和误预测信息 写回预译码信息 IFU的WB流水级,需要向FTQ写回每个指令的预译码信息
9.1.3 写回指令信息和误预测信息 写回误预测指令位置 IFU的WB流水级,需要向FTQ写回BPU错误预测的指令位置
9.1.4 写回指令信息和误预测信息 写回正确跳转地址 IFU的WB流水级,需要向FTQ写回该预测块的正确跳转地址
9.1.5 写回指令信息和误预测信息 写回指令范围 IFU的WB流水级,需要向FTQ写回预测块的正确指令范围
9.1.6 写回指令信息和误预测信息 传递预测块FTQ指针 IFU的WB流水级,需要向FTQ传递预测块的FTQ指针
10.1 上电复位处理 第一条MMIO指令 IFU收到的第一条MMIO请求可以直接查询Instr Uncache
10.2.1 向InstrUncache发送请求 阻塞等待提交 IFU收到MMIO请求后,查询FTQ,如果前面还有尚未提交的指令,持续等待
10.2.2 向InstrUncache发送请求 无阻塞发送请求 如果查到FTQ不再有未提交的指令,则IFU将指令发送给Instr Uncache
10.3.1 跨总线请求处理 重发查询ITLB 遇到一次无法查询完毕的RVI指令时,需要向ITLB查询获得新增指令的物理地址
10.3.2.1 跨总线请求处理 ITLB异常 IFU查询ITLB出现异常时,应当将异常信息发送到IBuffer,然后等待ROB提交完成
10.3.2.2 跨总线请求处理 ITLB返回物理地址 IFU查询ITLB正常返回物理地址时,IFU继续向PMP请求检查
10.3.3.1 跨总线请求处理 请求状态不一致 IFU检查PMP之后如果发现重发请求状态和上一条请求状态不一致,是访问异常,需要将异常直接发送到IBuffer
10.3.3.2 跨总线请求处理 PMP检查异常 PMP检查出现异常的情况下,也需要将异常直接发送到IBuffer并等待ROB提交。
10.3.3.3 跨总线请求处理 Instr Cache请求重发 PMP检查若无异常,则向Instr Uncache发送请求获取指令码的后半部分。
10.4 向IBuffer发送指令 向IBuffer发送指令 IFU在获得完整数据后,截取获得指令码,以每个预测块一条指令的形式发送给IBuffer
10.5.1 指令冲刷 RVI指令重定向 如果是RVI指令,传递给FTQ的冲刷请求应该重定向到PC+4
10.5.2 指令冲刷 RVC指令重定向 如果是RVC指令,传递给FTQ的冲刷请求应该重定向到PC+2
11.1.1 断点设置和检查 select1判定 给定tdata1的select位为1,随机构造其它输入,检查断点是否没有触发
11.1.2.1 断点设置和检查 select0关系匹配判定 给定tdata1的select位为0,构造PC与tdata2数据的关系同tdata2的match位匹配的输入,检查断点是否触发
11.1.2.2 断点设置和检查 select0关系不匹配判定 给定tdata1的select位为0,构造PC与tdata2数据的关系同tdata2的match位不匹配的输入,检查断点是否触发
11.2.1 链式断点 chain位测试 对每个trigger,在满足PC断点触发条件的情况下,设置chain位,检查断点是否一定不触发
11.2.2 链式断点 timing测试 对两个trigger,仅设置前一个trigger的chain位,且两trigger的timing位不同,随机设置PC等,测试后一个trigger是否一定不触发
11.2.3.1 链式断点 未命中测试 对两个trigger,仅设置前一个trigger的chain位,且两trigger的timing位相同,设置后一个trigger命中而前一个未命中,检查后一个trigger是否一定不触发
11.2.3.2 链式断点 命中测试 对两个trigger,仅设置前一个trigger的chain位,且两trigger的timing位相同且均命中,检查后一个trigger是否触发

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寄存器),分别对应着压栈和弹栈。详细的对应情况如下:

links

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

2 - FrontendTrigger

FrontendTrigger子模块

该子模块的主要作用是在前端设置硬件断点和检查。

该模块的输入pc有一个隐含条件,那就是这个pc是通过ftq传递的startAddr计算出来的。

FrontendTrigger功能介绍

断点设置和断点检查

在IFU的FrontendTrigger模块里共4个Trigger,编号为0,1,6,8,每个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。

前端的0、6、8号Trigger支持Chain功能。 当它们对应的Chain位被置时,只有当该Trigger和编号在它后面一位的Trigger同时命中,且timing配置相同时(在最新的手册中,这一要求已被删除),处理器才会产生异常。其中可以和6,8号trigger实现chain功能的7,9号trigger在后端访存部件中。

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位且两trigger的timing位相同,设置后一个trigger命中而前一个未命中,检查后一个trigger是否一定不触发。
2.2.2 命中测试 对两个trigger,仅设置前一个trigger的chain位且两trigger的timing位相同且均命中,检查后一个trigger是否触发。

测试点汇总

序号 功能 名称 描述
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 - PredChecker

子模块:PredChecker简介

分支预测检查器PredChecker接收来自IFU的预测块信息(包括预测跳转指令在预测块的位置、预测的跳转目标、预译码得到的指令信息、指令PC以及预译码得到的跳转目标偏移等),在模块内部检查五种类型的分支预测错误。模块内部分为两个流水线stage,分别输出信息,第一个stage输出给f3阶段,用于修正预测块的指令范围和预测结果。第二个stage输出给wb阶段,用于在发现分支预测错误时产生前端重定向以及写回给FTQ正确的预测信息。

PredChecker功能介绍

JAL预测错误检查

jal指令预测错误的条件是,预测块中有一条jal指令(由预译码信息给出),但是要么这个预测块没有预测跳转,要么此预测块预测跳转的指令在这条jal指令之后(即这条jal指令没有被预测跳转)。

RET预测错误检查

ret指令预测错误的条件是,预测块中有一条ret指令(由预译码信息给出),但是要么这个预测块没有预测跳转,要么此预测块预测跳转的指令在这条ret指令之后(即这条ret指令没有被预测跳转)。

更新指令有效范围向量

PredChecker在检查出Jal/Ret指令预测错误时,需要重新生成指令有效范围向量,有效范围截取到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:表示该预测块的跳转指令是否存在(valid),以及跳转指令的序号(bits)。

instrRange:对每条指令,表示该指令是否在预测块的有效指令范围内。

instrValid:表示的是对于每条32位的拼接指令,其是否为一条有效的指令(即低16位为一条RVC指令,或者整个32位为一条RVI指令)。

jumpOffset:如果某一指令为跳转指令,jumpOffset表示这个指令的跳转目标。

pc:指令的pc。

pds:预译码信息,包含指令的brType、是否为Ret(isRet)、是否为RVC指令(isRVC)。

target:下个预测块的开始地址。

输出接口

第一阶段输出

fixedRange:修复的指令有效范围向量,对每条指令i,fixedRange_i为真表示这条指令是否在当前预测块的有效指令范围内

fixedTaken:修复过后的CFI指令选取情况,对每条指令,fixedTaken_i为真表示这条指令是否是这个预测块的第一条CFI指令

第二阶段输出

fixedMissPred:对每条指令,PredChecker检查出的存在预测错误的情况,fixedMissPred_i为真表示这条指令存在预测错误

fixedTarget:对每条指令,给出修复过的下一条指令的位置(可以是常规的pc+2或+4,或者如果是跳转指令,给出跳转目标)。

jalTarget:对每条指令,给出跳转目标。

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 更新指令有效范围向量和预测跳转的指令

PredChecker在检查出Jal/Ret指令预测错误时,需要重新生成指令有效范围向量, 有效范围截取到Jal/Ret指令的位置,之后的bit全部置为0。 同时,还需要根据每条指令的预译码信息和BPU的预测信息修复预测跳转的结果。

所以,根据功能要求,我们可以划分出三类情况,分别是预测的有效范围和取用的跳转指令正确的情况, 由于RET和JAL预测错误引起的有效范围偏大和错判非跳转指令和无效指令引起的有效范围偏小。

序号 名称 描述
3.1 有效范围无误 不存在任何错误的情况下,PredChecker应当保留之前的预测结果。
3.2 RET和JAL预测错误引起的范围偏大 如果检测到了JAL或RET类的预测错误,PredChecker应该将有效指令的范围修正为预测块开始至第一条跳转指令。同时,应该将预测跳转的指令位置修正为预测块中的第一条跳转指令。
3.3 非CFI和无效指令引起的预测范围偏小 如果出现了非控制流指令和无效指令的误预测,不应该将预测跳转的指令重新修正到预测块中第一条跳转指令,因为后续会直接冲刷并重新从重定向的位置取指令,如果这里修正的话,会导致下一预测块传入重复的指令

功能点4 非CFI预测错误检查

非CFI预测错误的条件是被预测跳转的指令根据预译码信息显示不是一条CFI指令。

要检验这一功能,我们仍然按误检和正确检验来设计测试点:

序号 名称 描述
4.1.1 误检测试1 构造不存在CFI指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误
4.1.2 误检测试2 构造存在CFI指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误
4.2 正确检测测试 构造不存在CFI指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出非CFI预测错误

功能点5 无效指令预测错误检查

无效指令预测错误的条件是被预测的指令的位置根据预译码信息中的指令有效向量显示不是一条有效指令的开始。

要检验这一功能,我们按照误检和正确检测来设计测试点:

序号 名称 描述
5.1.1 误检测试1 构造不存在跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误
5.1.2 误检测试2 构造存在无效跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误
5.1.3 误检测试3 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误
5.2 正确检测测试 构造无效指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出无效指令预测错误

功能点6 目标地址预测错误检查

目标地址预测错误的条件是,被预测的是一条有效的jal或者branch指令, 同时预测的跳转目标地址和由指令码计算得到的跳转目标不一致。

和先前的思路一样,我们仍然按误检和检出两类组织测试点:

序号 名称 描述
6.1.1 误检测试1 构造不存在跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检目标地址预测错误
6.1.2 误检测试2 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检目标地址预测错误
6.2 正确检测测试 构造存在有效跳转指令的预测块和预测跳转但跳转目标计算错误的预测信息作为输入,测试PredChecker能否检出目标地址预测错误

功能点7 生成跳转和顺序目标

PredChecker还需要负责生成跳转和顺序目标。

我们通过随机生成译码信息进行测试

序号 名称 描述
7.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 更新指令有效范围向量和预测跳转的指令 有效范围无误 不存在任何错误的情况下,PredChecker应当保留之前的预测结果。
3.2 更新指令有效范围向量和预测跳转的指令 RET和JAL预测错误引起的范围偏大 如果检测到了JAL或RET类的预测错误,PredChecker应该将有效指令的范围修正为预测块开始至第一条跳转指令。同时,应该将预测跳转的指令位置修正为预测块中的第一条跳转指令。
3.3 更新指令有效范围向量和预测跳转的指令 如果出现了非控制流指令和无效指令的误预测,不应该将预测跳转的指令重新修正到预测块中第一条跳转指令,因为后续会直接冲刷并重新从重定向的位置取指令,如果这里修正的话,会导致下一预测块传入重复的指令。
4.1.1 非CFI预测错误检查 误检测试1 构造不存在CFI指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误
4.1.2 非CFI预测错误检查 误检测试2 构造存在CFI指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检非CFI预测错误
4.2 非CFI预测错误检查 正确检测测试 构造不存在CFI指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出非CFI预测错误
5.1.1 无效指令预测错误检查 误检测试1 构造不存在跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误
5.1.2 无效指令预测错误检查 误检测试2 构造存在无效跳转指令并且未预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误
5.1.3 无效指令预测错误检查 误检测试3 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检无效指令预测错误
5.2 无效指令预测错误检查 正确检测测试 构造无效指令但是预测了跳转的预测信息作为输入,测试PredChecker是否能检查出无效指令预测错误
6.1.1 目标地址预测错误检查 误检测试1 构造不存在跳转指令并且未预测跳转的预测信息作输入,测试PredChecker是否会错检目标地址预测错误
6.1.2 目标地址预测错误检查 误检测试2 构造存在有效跳转指令并且正确预测跳转的预测信息作为输入,测试PredChecker是否会错检目标地址预测错误
6.2 目标地址预测错误检查 正确检测测试 构造存在有效跳转指令的预测块和预测跳转但跳转目标计算错误的预测信息作为输入,测试PredChecker能否检出目标地址预测错误
7.1 生成跳转和顺序目标 随机测试 随机提供译码信息,检测生成的跳转目标和顺序目标。

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

其中,决定指令是否为RVC的部分在于指令的[1, 0]两位,不为3的情况下都是RVC指令。

其余的指令性质判定功能(CFI类型、是否为call和ret)被时序优化到了F3PreDecoder中,不过也可以认为是PreDecoder的一部分。

最后比较麻烦的是CFI指令的目标地址计算偏移,主要是对J和BR分支指令进行的计算,这需要综合RVI和RVC中jal和br指令的结构。 首先,是手册中对于C.J的描述

JOP

这里对imm立即数的注解是,立即数的每一位最后对应到的是偏移的哪一位。

所以,可以认为立即数是这么重组的:

instr(12) + instr(8) + instr(10, 9) + instr(6) + instr(7) + instr(2) + instr(11) +instr(5,3) + “0”

而RVI中,对于JAL指令,是这么定义的:

RVIJ

我们可以类似地计算立即数。

同样的,我们可以查询手册,参考BR类指令的立即数计算RVC和RVI指令对应的偏移。

RVIBR

RVCBR

PreDecode接口说明

输入接口

in_bits_data 17 x 2B的初始指令码,其中,每2个字节既可以代表一条RVC指令,也可以代表一个RVI指令的一半。

输出接口

instr:拼接后的 16 x 4B的初始指令码

jumpOffset:如果这条指令是跳转指令,则jumpOffset表示其跳转偏移

pd:每条指令预译码信息,在时序优化之后,PreDecode模块的控制信息只剩下了valid和isRVC,后者表示这条指令是否为RVC指令

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中。

据此,我们可以设计下述测试点。

首先是判定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指令,检查计算的偏移

功能点3 生成指令开始向量

最后,预译码还需要生成两种指令开始向量:

序号 名称 描述
2.3.1 有效指令开始向量计算1 对预测块,假定第一条指令为一条有效指令的开始,对每条指令计算其是否为有效指令开始
2.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 有效指令开始向量计算1 对预测块,假定第一条指令为一条有效指令的开始,对每条指令计算其是否为有效指令开始
2.3.2 有效指令开始向量计算2 对预测块,假定第一条指令为一条有效指令的结束,对每条指令计算其是否为有效指令开始

5 - RVCExpander

子模块:RVCExpander简介

RVCExpander是IFU的子模块,负责对传入的指令进行指令扩展,并解码计算非法信息。

该模块接收的输入量是两个:一条RVC指令或者RVI指令;CSR对fs.status的使能情况。

输出量也是两个:输入指令对应的RVI指令;RVC指令是否非法。

指令扩展

如果是RVI指令,则无需扩展。

否则对RVC指令,按照手册的约定进行扩展。

非法指令判断

RVI指令永远判断为合法。

对于RVC指令的判定,详细内容参阅20240411的RISCV手册的26.8节表格列出的指令条件。

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 |

当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 |