ChipCamp探索系列 -- 7L. 开源CPU之BOOM的指令发射(Issue)模块注释 - 教程

本篇分析BOOM-v1.0的指令发射模块,包括如下4个记录。但其中的issue_unordered.scala和issue_ageordered.scala是并列的两种Issue模块实现因此只选了前者进行分析。

$ ls -la issue*.scala
-rw-r--r-- 1 chipcamp 197609 4401 Sep 10 19:12 issue.scala
-rw-r--r-- 1 chipcamp 197609 5300 Aug 30 19:15 issue_ageordered.scala
-rw-r--r-- 1 chipcamp 197609 7523 Aug 30 19:15 issue_slot.scala
-rw-r--r-- 1 chipcamp 197609 5710 Aug 30 19:15 issue_unordered.scala

源代码参考路径:

https://gitcode.com/ChipCamp/BOOM-v1.0

图0、先看一下ISSUE在整个CPU Core里的位置:

Regfile和RegisterRead。前者尚未展开看,后者已经看过了。就是----ISSUE模块的上游是Rename模块,下游

----想起来RegisterRead的左侧IN端口还没有看,正好看一下!

----RegisterReadIO的信号集里,iss_uops是宽度较大的MicroOp信号集,结合上面的图来看,这来自于ISSUE模块和RegisterRead模块之间的寄存器。后面关注ISSUE模块如何输出给RegisterReadIO的这两个信号。

图1、issue.scala文件一幅图。

----ISSUE模块的位置:上接Decode后的Dispatch、下接Reserved Station和执行单元。结合前面的图来理解,ISSUE下接的又是RegfileRead模块。因此某种意义上说,保留站RS大概的形式就是Regfile!!

----22行:定义电路模块IO端口Bundle类IssueUnitIO。

----28~29行:端口名字iss_valids和uops,都带有复数形式,却是和下游RegfileRead对接的信号。注意注意,和RegfileRead对接的信号,是Issue模块的iss_valids和uops,而不是Issue_Slot模块的valid和uop。后面将会看到Issue_Slot模块的IO信号名是valid和uop。

$ grep register_read *

core.scala: val register_read = Module(new RegisterRead( issue_width,

core.scala: register_read.io.iss_valids(w) :=iss_valids(w)

core.scala: register_read.io.iss_uops(w) :=iss_uops(w)

----30行:端口名字叫wakeup_pdsts,端口数量为num_wakeup_ports,这个稍微注意一下,在此先提醒。

----35行:brinfo是作为IN端口,看起来解码以后指令是否为分支代码以及分支代码里面的立即数Imm都已经清晰了。也难怪这个brinfo是调用BrResolutionInfo()模块的结果、作为本模块的输入端口。

抽象类存在的意义。就是----41行:定义了一个抽象的模块类IssueUnit。使用的是IssueUnitIO端口。后面继承该类的时候大概率不再需要定义IO端口、而是直接继承这个端口了,端口的定义具有“统一性”,而ISSUE的具体方法则允许有所变化,这

----50~59行:把输入端口上的信号io.dis_uops拷贝到模块内部变量中。这个变量是一个Array阵列的存储器。

----64行:没有看到往iss_uops端口输出,而是看到的生成了一批IssueSlot的模块。此处存疑,推测可能在本抽象类的继承类中会看到具体的实现!后面的分析证实了这一猜测,见下。

图2、issue_ageordered.scala两幅图

----23~24行:继承自抽象类IssueUnit,因此抽象类里的io成员也自动继承了。

----63行:抽象类中定义了issue_slots和dis_uops,这里也直接继承过来并使用了。

----64~80行:给issue_slot(i).xxx赋值。每个发射槽管理一条指令的生命周期(等待操作数、就绪、发射)。大胆猜一下76行wakeup_dsts就是dst操作数(目标寄存器)Ready后wakeup的信号。

----往下看:

----98~108行:对io.iss_uops赋值了!这正是前面分析抽象基类所没有看到的!在继承的类里完成也算是符合预期了。当然这里的赋值都是一些0值/无效值!后面129~130才是赋非0值/有效值。

----117~137行: 对io.iss_uops和io.iss_valid赋值(129~130)!对issue_slots(i)的grant赋值!这个grant看起来非常有控制意义。如上所说,每个发射槽管理一条指令的生命周期(等待操作数、就绪、发射),那么这里的grant就是授权发射的意思了。为什么是【授权发射】而不是【自己发射】呢?授权代表的是一种【状态记录】,说明issue_slot是一种【状态记录】的东东。下面看一下这个issue_slot。

图3、issue_slot.scala两图。

----24行:IssueSlotIO端口Bundle的grant信号!

和ISSUE模块的iss_uops/iss_valids信号对应的。就是----31~33行:in_uop是IN端口,update_uop和uop则是OUT端口。它们都是MicroOp的类型,其位宽比较大。uop/valid则

----但注意注意,和RegisterRead电路模块对接的是Issue模块、而不是Issue_Slot模块Issue_Slot模块只是在Issue模块内部实例化并被使用来保存数据的(所谓生命周期管理)只在Issue模块里存在!就是。体现在下面Issue的电路类里面:io.iss_uops(w) := issue_slots(i).uop。还有像issue_slots(i)的grant和wakeup_dsts这两个比较独特的信号,也都

----49行:IssueSlot电路模块使用IssueSlotIO端口Bundle。这个Bundle是在Issue模块内部实例化并使用的。对外的接口由Issue提供(包括对上游的Rename和对下游的RegisterRead)。

在Issue_Slot模块里达成的。就是----63~69行:定义了5个Reg变量。后续当能看到这些变量作为【:=】左值赋值。其中的69行声明的slotUop寄存器变量 是一个MicroOp的宽信号。对照前面的整体流程图可知,这5个Register就是位于Issue模块和 RegisterRead模块之间的寄存器,只不过这个寄存器

----要再三强调的是,Issue_Slot模块完全属于Issue模块内部,而5个寄存器则在Issue_Slot模块里定义,因此整个Stage Register就在Issue模块内部定义的。这在BOOM-v1.0的代码里是很常见的作法,像Core.scala这样的顶层模块主要负责的是各子模块的实例化以及连线,包括使用【<>】的双向连线和使用【:=】的单向连线。

----继续往下看issue_slot.scala。

----78~93:slot_state该Reg变量,在clk驱动下被赋值,一共看到了3种以上的状态,其中84行的状态,来自于输入端口上的值,可能有多个状态。

----101~129:update_state和update_uops两个Reg变量,在clk驱动下被赋值。

----132~154行:next_p1/p2/p3三个Reg变量在clk驱动下被赋值。

----189~192行:io.request和io.request_hp都是发射槽模块的OUT端口。这里的意思是【请求/request】ReservedStation或者近似的队列电路来取走指令?NO!如前面所说,Issue_Slot整个都是Issue模块内部使用的,request和request_hp也不超过Issue的范围!先不管它。

连线到了Issue模块的io.iss_uops(s)和io.iss_valids(i)上了!就是----209~239行:给io.update_uop和io.uop这两个位宽很大的信号赋值作为OUT。如前面所说,Issue_Slot整个都是Issue模块内部使用的,Issue_slot的io.uop和io.valid作为模块的OUT端口、最后

连线到了Issue模块的io.iss_uops(s)和io.iss_valids(i)上了!就是----210~211行:给io.uop和io.valid两个关键信号赋值输出!如前面所说,Issue_Slot整个都是Issue模块内部采用的,Issue_slot的io.uop和io.valid作为模块的OUT端口、最后

图4、看看Core.scala中的Issue Stage。

----432~435行:ISSUE电路模块的IN端口,接到Core模块(顶层模块)的变量上。

----450~474行:给issue_unit.io.wakeup_pdsts.valid信号。

----438~444行 和 485~486行:把issue_unit.io.iss_uops和iss_valids(ISSUE电路模块的OUT端口) 赋值给中间变量(Wire类型变量)、并最终赋给register_read.io.iss_uops信号!这个地方是导线直连直通的。

----仍然以ISSUE模块和RegisterRead模块之间的连线为抓手,看看它们之间的信号连线和赋值。

$ grep register_read *

core.scala: val register_read = Module(new RegisterRead( issue_width,

core.scala: register_read.io.rf_read_ports <> regfile.io.read_ports

core.scala: register_read.io.iss_valids(w) := iss_valids(w)

core.scala: register_read.io.iss_uops(w) := iss_uops(w)

----能够看出最后2行就是上面图里列出来的两个赋值操作(485~486行),代表了从ISSUE到RegisterRead模块的“单向数据流”或“单向连线”。

通过----至此,能够说Issue模块已经在粗颗粒的层次上比较清楚了,包括:Issue_Slot是Issue模块内部使用。Issue模块上游和Rename对接(尚未展开看)。Issue模块下游和RegisterRead模块对接(和总体架构图对上了)。在细颗粒度上,Issue模块什么时候发射,应该有检查指令uop的操作数所涉及的物理寄存器是否Ready,以及执行模块是否Ready。但后者可能性很小,前者则主要是前端的Rename相关联,因此把这些细节的部分留到后面分析Rename的时候再看。TBD。

【摘要】本文分析了BOOM-v1.0处理器的指令发射模块(ISSUE),涉及Issue和Issue_Slot的模块概念及两者之间的关系,以及Issue模块和下游RegisterRead模块的接口和流水线寄存器,本文对着两方面进行了分析。而对于Issue和上游Rename模块的对接关系则要留到Rename模块的分析了。

<<<<<<<< END >>>>>>>>

posted @ 2025-09-19 09:45  wzzkaifa  阅读(8)  评论(0)    收藏  举报