- 测试平台:WSL2,利用 XLaunch 得到可视化 GUI。
- 可行平台:普通 Linux 主机,Mac OS,Windows(需要修改
Makefile
部分内容)。 - 语言:C++ 与 Python3
- 软件需求:gcc 编译工具,Python 3.8.0 及以上,Make 工具。
- 库需求:仅需要安装
tkinter
包。
在 sample
文件夹下放置输入汇编文件,并以 inputXXX.asm
命名,如 input5.asm
。
之后在根目录使用如下命令即可对 input5.asm
进行可视化展示:
make simulate TEST=5
如果只需要后端输出,可以使用以下命令:
make test TEST=5
.cpp
与 .hpp
文件为封装后的 template.c
文件,也即后端部分。
simulator.py
为可视化前端,assembler.py
为汇编器。
首先将实验分为前后端与汇编器三个部分:
-
汇编器用 Python 编写,只进行了简单的汇编到机器码的翻译。
-
前端使用 Python 编写,负责在后端给出指令运行结果的前提下,展示 Tomasulo 算法的可视化流程;
-
后端利用
template.c
改写,使用 C++ 语言,负责 Tomasulo 算法的主体逻辑模拟与实现。
基本按照给定框架完成,其中利用面向对象,将原有的状态转移函数修改为了 machineState
的成员函数,省去了一些指针的传递麻烦。
为了方便检查,有以下约定:
-
其中未处理
LOAD
与STORE
指令地址冲突,默认不存在。 -
规定每周期只可发射一条指令、只可提交一条指令,其余阶段无限制。
-
STORE
指令由于在提交阶段执行写入,所以添加了storeQueue
用于记录当前正在内存中写入的STORE
指令信息,并在加入队列的两周期后,对其进行实际写入,用以模拟真实的写入延迟,并且在实际写入过程中,仍占用对应的保留站/缓冲区。 -
LOAD
指令的执行阶段被设置为$3$ 个时钟周期,这是$1$ 个地址计算的整数计算周期和$2$ 个读内存的时钟周期相加得到的。 -
特别地,为了防止读取非法内存,在
pc >= memorySize
时,停止发射指令(这说明上一条发射的指令为halt
,有概率需要停机)。
在每个时钟周期,按照倒序分别进行提交、写结果、执行、发射和尝试发射阶段:
- 提交:
- 首先判断 ROB 头指令是否处于提交状态,如果处于且非分支非跳转指令,则只需将寄存器结果写入,或加入
storeQueue
队列。 - 对于分支和跳转指令,先根据实际结果,更新 BTB 表项,再判断是否预测成功,如果失败则需清空当前 ROB,并设置 PC 为 ROB 中
result
值。
- 首先判断 ROB 头指令是否处于提交状态,如果处于且非分支非跳转指令,则只需将寄存器结果写入,或加入
- 写结果:
- 对于
STORE
指令,需要判断写入的寄存器是否就绪,如果未就绪,则不改变,如果就绪,则设置合适的result
和storeAddress
字段并进入提交阶段。 - 对于其他指令,只须调用
getResult()
获得结果并进入提交阶段。
- 对于
- 执行:
- 只须将
exTimeLeft
减一,并判断是否为$0$ ,如果为$0$ ,即可进入写结果阶段。
- 只须将
- 发射:
- 对于已经发射的指令,如果其
QJ
和QK
均为$0$ ,或者QJ
为$0$ 且为STORE
指令,则进入执行阶段。
- 对于已经发射的指令,如果其
- 尝试发射:
- 对于当前
pc
,如果地址合法(小于memorySize
)且存在空闲的保留站和 ROB,则发射指令。 - 并根据
getTarget
获取下一条指令 PC 值的预测。 - 发射时,特别注意需要将分支指令与跳转指令的
VK
设置为pc + 1
,并且对于部分指令需要修改寄存器文件结构。
- 对于当前
我同时完成了选做的分支预测缓冲区 BTB 的实现。
为了方便实现,我在 ROB 的字段中添加了当条指令对应的 PC 值和前瞻执行的下条 PC 值,并实现了 FIFO 的 BTB 替换策略,为此我在 machineState
中添加了 btPtr
字段表示下一个可用于插入的编号。
-
对于
getPrediction()
函数,我将其改写,设计为传入pc
查询 BTB 表中是否存在对应的表项并且合法,如果存在返回对应编号,否则返回$-1$ 。 -
对于
updateBTB()
函数,我认为branchPC
为分支指令的 PC 值,targetPC
为分支指令如果跳转其对应的跳转地址,outcome
代表分支指令本次是否跳转。- 首先尝试查找表项,如果存在,则根据
outcome
更新$2$ 位饱和计数器,即如果跳转,则branchPred
加一(不超过$3$ ),否则减一(不小于$0$ )。 - 如果不存在,则根据
btPtr
来进行表项插入。
- 首先尝试查找表项,如果存在,则根据
-
对于
getTarget()
函数,我利用getPrediction()
快速判断表项是否存在,并根据结果与branchPred
来确定返回branchTarget
或者pc + 1
。
为了修复分支预测错误,我在提交分支指令和跳转指令时,统一进行了分支预测验证,如果预测失败,则清空 ROB 及其对应的保留站和寄存器结果表。
利用 tkinter
包可视化。
首先读取后端输出的信息文件,并将其解析为 Python 格式的字典留待使用。
在可视化过程中,维护一个全局变量 CYCLES
,表示当前所在的时钟周期数,则对应的各表内容即可根据当前周期的输出得到。
为了方便的查看不同周期的 CPU 状态,我设计了四个按钮分别用于:
- 进入下一个时钟周期。
- 回到上一个时钟周期。
- 跳过连续
$10$ 个时钟周期。 - 重置回到初始状态。