IFU-V3

1. 前言
2. IFU的功能
3. IFU的压力
4. IFU的设计方案
	4.1. IFU主体结构确定
	4.2. IFU每一级功能确定与时序预估
	4.3. IFU结构图说明
	4.4. 设计细节
		4.4.1. IFU子模块Trigger 
		4.4.2. IFU子模块InstrBoundary 
		4.4.3. IFU子模块指令紧密排列计算
		4.4.4. IFU子模块uncache 
		4.4.5. IFU子模块预译码
		4.4.6. IFU子模块预译码检查
		4.4.7. IFU的two Fetch支持（草稿）
		4.4.8. IFU的权衡与配合
5. 杂记

前端设计组

1. 前言

IFU（Instruction Fetch Unit）是前端流水线中负责组织供指数据的关键模块，位于分支预测和指令缓存之后。在预测路径和 ICache 命中结果确定后，IFU 需要完成指令对齐、打包、简单的预译码等操作，并将处理好的指令送入后续模块（如 IBuffer）。

由于 IFU 处于供指链路的下游，其处理延迟直接影响整个前端的取指响应能力，尤其在发生重定向（如分支预测错误）时，IFU 的执行周期会成为恢复路径的关键延迟点。因此，在功能完整和时序允许的前提下，尽量减少 IFU 的处理周期，有助于提升供指效率和分支恢复速度。

1

2. IFU的功能

●IFU负责将ICache传输过来的指令数据进行切分。确定预测块中每一条指令的位置，获得对应的有效指令数据。

●IFU负责进行预译码，获取每条分支指令对应的分支类型，以及对应的jumpOffset。从性能角度考虑，IFU还会进行预译码检查，及早纠正能够发现的预测错误。

3. IFU的压力

IFU的主要压力在于时序，IFU需要平衡指令位置定界，IBuffer入队的时序紧张问题。另外从ICache读取32条指令的时序压力也是存在的。

4. IFU的设计方案

4.1. IFU主体结构确定

IFU与多个模块都有信息交互，分别是FTQ、ICache、IBuffer、以及MMIO总线。1. IFU会接收来自FTQ的取指请求和重定向信号。当IFU发现预译码与预测结果不符时，也会FTQ发送重定向信号。2. IFU在完成预译码信息整理后，会将指令数据传递给IBuffer。3. FTQ向IFU发送取指请求的同时，也会向ICache发送取指请求。理论ICache能够在下一拍返回指令数据，但时序不好，IFU内部需要再打一拍。

相当于IFU从接收取指请求后，需要打两拍才能正式处理ICache Data。指令数据需要进行划分和预译码，至少需要再打一拍才能送入IBuffer。

所以我们可以得出几个关键的点，如果目前V3的结构不变动的话。IFU最快需要在S2阶段进行预译码，S3阶段才能将整理的指令数据传输给IBuffer。所以IFU是3级流水线结构（不考虑特殊的MMIO状态机）。

4.2. IFU每一级功能确定与时序预估

目前S0级与S1级之间主要是计算预测块的指令范围，计算预测块的指令大小。计划还会计算一些PC序列低位的信息，因为第二个预测块拼接在第一个预测块后面，所以第二个预测块的PC信息是需要偏移

2

的。

目前S1级与S2级之间主要是进行指令定界，以及计算指令密集排布需要的指令索引。因为ICache的返回数据不与预测块的首地址进行对齐操作的，为了减轻这个一块的压力，可能需要承担部分移位操作。

目前s2级与s3级之间的提取指令数据，供有32个读口，推测从物理端的布局布线上存在压力。我们进行dup的备份操作，分散读口。

目前对于S3级存在的IBuffer入队时序压力，存在两点优化。一是分为Valid序列和enqEnable序列，Valid序列直接提供给IBuffer的entry选择需要的有效指令，enqEnable用于最终确定有效指令是否写入IBuffer，因为enqEnable是经过predChecker缩减了有效指令的范围。二是IFU与IBuffer的特殊约定，IFU的特殊准备，能够缓解时序上的压力。

需要注意的是，目前我的想法主要在解决时序问题和实现上，对于面积和时钟门控没有过多考虑。计划这是在代码稳定后基础上，后续优化点。

4.3. IFU结构图说明

紧密排序

预译码检查

icahce

信息

预测块请

预测块请

求信息B

req 	resp

uncache 通道

FTQ

4.4. 设计细节

4.4.1. IFU子模块Trigger

触发器不在调试模式下工作，调试模式可以建立一系列的触发器。当同时分别触发进入调试模式和断点异常时，优先保证进入调试模式的发生。推荐是进入调试模式和生成断点异常，都要发生。触发器可以用于本地调试，即不依赖外部调试器（JTAG），而是通过异常机制来进行调试。触发器通常由用户态或监督态设置，但也支持机器态进行设置（M模式下，开发人员必须清晰的知道自己在干什么）。

触发寄存器仅在机器模式和调试模式下可设置。IFU中的trigger逻辑是为了配合后端实现的Trigger模块，目前来看只涉及到了送往IBuffer的PC对比。有关预译码和指令数据使用目前在IFU中的Trigger还

3

没有支持。目前IFU的Trigger中有4个tdata寄存器，值由后端设置。遍历送入IBuffer的所有PC，根据matchType的匹配规则，进行匹配。在匹配的情况下，还需要判断是tdata.select是否为地址匹配目标，以及是否处于非debug模式，才能确定命中。即使是命中情况下，也还需要根据tdata.timing和tdata.chain以及tdata.action做出综合判断。（分别是触发的时机要求，触发的链式匹配要求，以及触发的动作）

4.4.2. IFU子模块InstrBoundary

InstrBoundary的主要功能是负责确定每条指令的位置，我们通过标记指令的开头或者末尾进行确定。如下图所示，以两字节划分（因为指令的最小单位是2字节），对于64字节的预测块来说，最多可以划分为长为32的序列。目前我们暂时分别有一个标记指令开头的instrValid序列，和一个标记指令末尾的instrEndVec序列。目前序列中的任何位置都可能标记为指令的开头和末尾。

进行指令定界需要原始的指令数据和已知的原始指令数据最开始的两字节情况。它的原理是这样	的：

余量。在实际使用中我们对指令定界进行了划分，采用多路推断再合并拼接的操作。如下图所示，我们可以先计算0~15号的指令位置，按照两路假设分别计算16号~31号的指令位置。当0~15号计算完毕，16号~31号也基本计算完毕，根据15号位置的实际结果选择一路假设拼接16~31号的结果即可。

4

拼接合并

●InstrBoundary输入输出信号说明：

○	InstrRange信号：用于初步缩减指令块的有效范围。需要强调的是InstrBoundary计算的指令数	据，可能是由两个预测块的指令数据拼接而成。目前的设计未确认当第一个预测块末尾为半条	指令，第二个预测块末尾为半条指令。

道预测块的结束位置是否为RVI的半条指令。

○	isRVC: isRVC序列可以在我们判定指令位置的过程中自然而然的产生。

4.4.3. IFU子模块指令紧密排列计算

根据InstrBoundary计算出来的InstrValid序列信息，我们可以将稀疏分布的有效指令信息进行紧密排列。在此之前，让我们首先解释一下需要紧密排列的信号分别是：instrIndex，selectBlock，isRVC，pcLowerResult，instrEndOffset。

instrIndex是指令索引，用于从ICache返回的数据中截取对应的指令。你可以这么理解，如果instrIndex的3号位有效，则instrIndex(3)作为地址从ICacheData读出的指令数据，就是预测块按照两字节划分时，序号位为3的有效指令数据。未进行紧密排列时rawInstrIndex(i) = (startAddr + 2*i)(5,1)，startAddr是预测块的首地址。

selectBlock是选择信号，用于指示当前指令数据来自于1号预测块还是2号预测块。如果
selectBlock(i)为1，我们就根据instrIndex(i)值从1号预测块对应的ICacheData中取出指令数据。相应的rawSelectBlock(i) = i > 0号预测块的Size。稍微解释一下，以两字节为单位。如果序号i< 0号预测块的Szie，则说明rawInstr(i)的指令数据来自0号预测块。因为原始指令的数据的拼接关系是1号预测块的指令数据拼接在0号预测块的后面。

instrEndOffset，用于指示当前指令末尾地址相对于预测块的偏移值。如果是RVC指令或者说当前指令开头位置相对于预测块的首地址偏移值，就很好理解，因为第i条指令的相对于预测块的偏移值为i。所以instrEndOffset(i) = i + !isRVC。

5

我们需要强调一点的是rawInstr与instr。前者我们认为预测块每两字节划分的位置是一条指令，后者我们指代的是真实指令。本节的作用就是从rawInstr中刷选出对应的有效instr，就是从rawInstrIndex，rawInstrEndOffset，刷选出对应的有效instrIndex以及instrEndOffset。请注意指令信息与指令数据是一一绑定。

原理：我们根据instrValid序列计算出对应的instrCount序列，其中instrCount(i)代表
instrValid(0)~instrValid(i-1)的sum。当instrValid(i)有效时，其对应的sum(i)，就是紧密排列后对应指令有效信息要写入的位置。不存在instrValid(i)与instrValid(j)有效时，sum(i) == sum(j)。所以有如下代码，我可以将每个位置序号，与sum序列对比，找到命中的项进行写入。更进一步的，我们考虑到有效指令的稀疏排布也是有规律的，比如有效指令之间不存在连续的不有效指令.....。根据这一点确定instrIndex(4)，我们只需要考虑rawInstrIndex(4)~rawInstrIndex(9)，因为最好情况rawInstrIndex每一条都是有效指令，则instrIndex(4) = rawInstrIndex(4)，最差情况rawInstrIndex全是I指令instrIndex(4) = rawInstrIndex(8)，嗯，好像混入了啥。（思路就是这样）

4.4.4. IFU子模块uncache

uncache模块主要负责从uncache总线提取指令，同时负责判断阻止MMIO指令的推测执行。受PBMT属性的影响，部分非缓存的指令数据是允许推测执行的，因此需要对MMIO指令进行特殊区分。需要强调一点的是，uncache的指令数据来自于s3级，因此任何在S1、S2级依赖指令数据计算出来的结果，都值得重新审视。

uncache本身不处理跨页的情况下，当遇到uncache指令跨页时将分为两个预测块进行处理。这与cache指令跨页的处理情况有些类似。uncache需要什么？uncache需要物理地址，用于向uncache总线发出取指请求。uncache需要ftqIdx，以便向FTQ请求查询预测块的提交状态用于处理MMIO的阻塞逻辑。uncache还需要pmp的结果和PBMT的结果，用于确认当前的uncache请求是否是需要进行推测阻塞的MMIO请求。需要注意的是对于处理器执行的第一条指令，其不存在更旧的指令，我们将越过阻塞机制直接向uncache总线发出取指请求。

需要注意的是对于uncache总线，它每次总是返回64字节对齐的数据，这意味着如果指令跨越了64字节的边界，将会发送两次请求，这个由前端的uncache通路完成，IFU对此无感。区别在于，当指令跨

6

页时，理论上可能存在两个页的物理地址不连续，前端的uncache通路，将无力进行发送两次请求，它

会返回crossPage的状态标志，这需要IFU做额外的拼接处理。

uncache的处理流程如下图所示：

○	第一步：uncache状态机接收来自IFU的uncache请求，判断是否为非MMIO。如果是非MMIO直

接向uncache通路发送请求并转第三步，否则等待MMIO指令成为最旧指令并转第二步。

○	第二步：如果MMIO指令是处理器执行的第一条指令或者MMIO指令是最旧指令，则转第三步，

否则持续在第二步进行等待。

○	第三步：如果uncache通道接收了请求，则转第四步，否则继续在第三步等待。

○	第四步：如果接收到uncache通道的返回数据，uncache子模块将会把数据回传到IFU的主体，

转IFU主体进行处理。否则继续在第四步中进行等待。

○	无条件冲刷：uncache子模块接收到Flush信号后，不管处于第几步都将复位所有的控制寄存

器。不考虑uncache通道是否处理完毕，因为uncache通道也会接收到Flush信号。

●IFU主体在接收到uncache子模块的信息后，将会根据是否跨页分为两种情况处理：

○	跨页情况下：1. 如果预测器给出的结果是顺序取指，IFU主体将暂存这半截的指令数据信息，等

待下一个预测块继续走完uncache子模块流程。拼接两个预测块的指令数据。2.如果预测器给出

的结果是跳转，IFU同样将暂存这半截的指令数据信息，但是会直接发送重定向，从而达到从正

确的下一个预测块获得的另外半截数据。

○	非跨页情况下：一条指令在一个预测块中取完。

○	目前IFU在完成一条uncache的取指后，不管预测器是否给出顺序执行都会发送重定向要求顺序

执行。如果对uncache取指有有速度的需求，这是可能的后续优化点。

7

Uncache Unit

Send uncache fetch req 
Step1: 接收请求并判断
MMIO / ⾮MMIO

uncache 通道

uncache instrcution

数据并回传到 IFU 
	Uncache Cross-Page Processing

: uncache crossPage flag

4.4.5. IFU子模块预译码

预译码器PreDecoder接受有效指令码并进行指令码生成，每个指令码查询预译码表产生预译码信息，预译码信息包括CFI指令类型、是否是RVC指令、是否是Call指令以及是否是Ret指令，以及分支指令的立即数Offset。

对于C指令我们还额外做了一些操作，处理模块是RvcExpander.scala。RvcExpander负责将C指令转换为I指令。我们还会对C指令是否有效进行检查，如果是非法指令，我们将输出C指令的非法标记ill。对于非法的C指令我们将保持原数据，对于有效的C指令我们输出的是扩展后。

原因：理论上所有C指令都可以转换为I指令，前期将C指令转换为I指令，后续就只用处理I指令，如果时序允许，这将降低后续模块的处理复杂度。对于非法C指令，我们需要记录报异常的原因，因此送入后续流程的是原始指令。

4.4.6. IFU子模块预译码检查

预译码模块负责检查一些容易被发现的预测错误，以便及时纠正。从性能的角度讲，越早发现预测错误，就能尽早将指令流纠正回正确执行路径上。IFU阶段实际上可以获取到指令的全部信息，无能为力的是获取正确的寄存器信息。

8

预测方向上：我们可以知道jal/jalr指令一定跳转，我们也知道普通指令一定不跳转（异常）。遇到这种情况，我们可以裁剪预测块的大小，让处理器不要在错误路劲上狂奔突进。

目的地址：我们知道普通指令一定是顺序执行的，我们还能计算出jal指令和br指令的目的地址。特殊的我们大概率知道jalr类型中函数返回指令的目的指令。

从这个角度上说，IFU还是有一定的辅助预测的作用。对于预测块范围上的修正，我们是能修就修正。对于目的地址，我们是不修正的，我们相信预测器。除非是我们发现了预测块范围上的错误，然后提供我们给的目的地址。为什么这么做，这是从实际效果和成本上考虑的折中。预测器将指令类型预测正确了，将指令位置预测正确，然后目的地址预测错误了 ---- 嗯~~，不能说没有，从讨论的结果和目前自己浅显的设计经验来看，概率有些低。从实现角度如果检查目的地址，就得进行目的地址比较。逻辑是PC+Offset计算出目的地址，再进行比较。逻辑有点长了，能不能做？能做也许需要打一拍。---- 怎么说呢性能角度有比没有要强一点点，可有可无吧。~~~ 哦，我发现了华点。如果时序允许，我将在后续的版本中，将predChecker的计算提前一拍。~~~ 哦，我又发现了华点，也许面积还能优化一点。

⽬的地址序列

指令序列顺序地址

PredChecker Unit

4.4.7. IFU的two Fetch支持（草稿）

IFU支持有限制的two Fetch。有如下要求：

●限制two Fetch的两个预测块的最大支持宽度为64字节。(原因，最大取指宽度为64字节，如果我们不想弄两条IFU通路，复用一条通路的话，最大为64字节。)

●限制不同时处理包含uncache数据的two Fetch操作。至少间隔一拍。

●目前限制为不在同拍处理two Fetch中的如果第一个预测块存在LastHalfRVi情况。

目前的融合操作是将第二个预测块的指令数据拼接到第一个预测块的末尾。按照2字节的划分操作，这意味着第二个预测块的指令数据和ICache返回的指令信息需要进行偏移操作如下。目前IFU的V3代码没有更正这一点，这是后续考虑点。IFU的two Fetch有待继续完善，更多问题有待进一步思考暴露出来。

9

4.4.8. IFU的权衡与配合

简单说明一下IBuffer的入队原理，IBuffer是先进先出的队列。enqPtr代表当前的IBuffer可入队第一项entry位置，enqPtr + 1代表当前的IBuffer可入队第二项的entry位置。如果IFU提供给IBuffer的是稀疏的有效指令，则IBuffer第一项可入队的entry需要从IFU提供的稀疏有效指令中选出第一项有效指令。则IBuffer第二项可入队的entry需要从IFU提供的稀疏指令中选出第二项有效指令。极端的由于IFU最多提供32条指令，IBuffer的每一项都会面临32选1的尴尬境地。

那么IFU和IBuffer能否相互配合做出一种特殊的约定呢？实际上是可以的，IBuffer实现写分bank策略，给IBuffer队列每一项进行编号，获得ibuffer_mod_id = IBuffer编号%4。IFU提供的入口指令也进行编号，获得对应的ifu_mod_id = IFU编号%4。IBuffer的entry只从对应的IBuffer_mod_id === ifu_mod_id项中进行选择。真是美妙的想法，从32选1变成了8选1。这对吗？理论上是否可行？代价是什么？

理论上还需要进一步的推敲。从连续性角度角度讲，如果IBuffer的当前entry为ibuffer_mod_id = 3的项入队了，则IBuffer的下一项entry的ibuffer_mod_id = 0。如果你不打乱顺序的话，则要求IFU送入的有效指令是连续的，稀疏的指令排布会打破对应的默契。图形是最为直观的，我将为你画出第一点要求。OK，现在我们知道了要求一：IFU的输出指令要求的是有效指令连续。图形是最为直观的，我将为你画出第二点要求。

10

代价：有效指令的排布，要求筛选出紧密排布的有效指令索引，IFU引入了相关的计算逻辑。权衡的艺术：相对于送入IBuffer时的每条指令携带的数据信息进行刷选，IFU计算指令索引需要的逻辑会少一点点。IFU将有效指令进行了紧密排列，意味着虽然有32个指令信息存储位置，但我们可以进行较为精细的时钟门控操作。偏移计算，这要求提前掌控IBuffer的入队指针，实际上偏移计算本身会带来一定的计算量。不过相对好些的是需要进行偏移计算的数据量较少。~~~ 哦我又发现了华点，也许偏移计算的大头可以直接砍掉了，但与功能没有关系。

前文已经讨论过了有效指令筛选机制，现在我们讨论一下IFU有效指令的对齐，我们需要将第一条指令偏移到，当前IBuffer入队指针enqPtr % 4对应的位置。所以最大偏移量是3，进行有效指令对齐的最大问题就是提前获取enqPtr的值。实际上我们能够做到这一点。enqPtr值实际上就是有效指令数量的不停累加。遇到后端重定向时，enqPtr的值复位为0。IFU能够确定送入IBuffer的有效指令数量，同时也会接收来自后端的重定向，因此我们可以提前计算prevIBufferEnqPtr，如下图所示：

11

有效指令信息

predChecker

instrCount 	instrCount

redirect: prevPtr += fixedCnt

5. 杂记

12

