实验框架使用
可以修改的文件
backend/backend_commit.cpp
编写后端指令提交逻辑,以及指令流入后端的逻辑frontend/frontend_wp.cpp
编写前端分支预测逻辑include/reservation_station.hpp
编写保留站唤醒和发射逻辑include/branch_predict_model.h
设计分支预测相关的数据结构include/with_predict.h
向前端增加你新设计的数据结构backend/load_buffer.cpp
和backend/store_buffer.cpp
完成 Load Buffer 和 Store Buffer 的实现。
其余代码文件不可以修改。
如果进行了修改,你需要保证你的模拟器在不改动其他文件的情况下仍然可以执行。
.gitlab-ci.yml
文件原则上不可修改,否则实验成绩以零分处理(详见评分细则)。
部分接口说明
模拟器中的每个函数都进行了必要的注释,如果有框架理解的问题,请在课程群或者网络学堂答疑区进行提问。下面只会描述实验过程中需要使用或者需要实现的接口。
框架使用了 std::optional<> 等在新标准中出现的内容,详细使用方法可以自行检索。
通用接口
getFUType
在头文件 instruction.h
中。
用于获取指令对应的执行单元。
FUType 为 enum class,在 defines.h
中定义。
特别地,FUType::NONE 表示该指令不需要进入任何执行单元。在本实验中,只有 EXTRA::EXIT 为这种指令。
MaskedLiteral 和 Instruction
在 include/masked_literal.h
中,定义了本实验中需要使用的所有指令。
Instruction 为抽象的指令模型,用于指令解码和执行。
如果需要判断某个 Instruction 是否为某类指令,可以使用 ==
,例子如下:
#include <assert.h>
#include "instructions.h"
#include "masked_literal.h"
...
Instruction inst(0x00000013);
assert(inst == RV32I::ADDI);
也可以使用 != 等符号,instruction还可以重载了 << 符号,可以使用 std::ostream 进行输出。更详细的内容可以参见 include/masked_literal.h
和 include/instructions.h
。
Logger
实验框架提供了一个简易的 Logger 用于向 stderr 中按照同一格式进行输出。
同时可以设置每个层级的信息是否输出。信息一共分为 Debug
, Info
, Warning
, Error
四级。
使用方法与 printf 类似,例子如下:
#include "logger.h"
#include <sstream>
Instruction inst(0x00000013);
std::stringstream ss;
ss << inst;
Logger::setInfoOutput(true);
Logger::Info("Instruction is %s", ss.str().c_str());
输出如下:
[ INFO ] Instruction is ADDI zero, zero, zero
你可以使用其他的第三方 Logger 来美化输出,但请不要更改已有的输出的格式和内容。
Reorder Buffer 接口
框架提供了 Reorder Buffer 的基础实现。Reorder Buffer 的实现使用了循环队列 buffer 的方法。详细接口说明和实现可以参见 backend/rob.cpp
中的注释。
bool canPush() const; // 检查 ROB 是否有空间插入新项目
bool canPop() const; // 检查 ROB 是否可以弹出项目
unsigned push(const Instruction &x, bool ready); // 向 ROB 中插入新项目,可以决定该项是否已经 ready
void pop(); // 从 ROB 中 弹出项目
void flush(); // 清空 ROB
std::optional<ROBEntry> getFront() const; // 获取 ROB popPtr 指向的 ROB 项,若该项不 valid,则返回 std::nullopt
void writeState(const ROBStatusWritePort &x); // 向 ROB 写入指令执行结果,并标记 ready
unsigned getPopPtr() const; // 获取 popPtr
unsigned read(unsigned addr) const; // 读取对应项的 state.result
bool checkReady(unsigned addr) const; // 检查对应项是否 ready
Store Buffer 接口
框架提供了 Store Buffer 的基础实现。Store Buffer 的实现使用了循环队列 buffer 的方法。详细接口说明和实现可以参见 backend/store_buffer.cpp
中的注释。
为保证 store buffer 不溢出,我们将 store buffer 的大小设置成与 reorder buffer 相同。
void push(unsigned addr, unsigned value); // 向 store buffer 中插入新项目
StoreBufferSlot pop(); // 从 store buffer 中弹出项目
void flush(); // 清空 store buffer
std::optional<unsigned> query(unsigned addr, unsigned robIdx, unsigned robPopPtr); // TODO: 查询 store buffer, 若对应地址命中,返回最新的结果,否则返回 std::nullopt
Load Buffer 接口
框架提供了 Load Buffer 的基础实现。与 Store Buffer 不同,Load Buffer 的表项编号与 ROB 保持一致。详细接口说明和实现可以参见 backend/load_buffer.cpp
中的注释。
void LoadBuffer::push(unsigned addr, unsigned robIdx); // 向 Load Buffer 中推入新项目
LoadBufferSlot LoadBuffer::pop(unsigned robIdx); // 从 Load Buffer 中弹出项目
void LoadBuffer::flush(); // 清空 load buffer
void LoadBuffer::check(unsigned addr, unsigned robIdx, unsigned robPopPtr); // TODO: 根据 store 指令信息,查询并无效化 load buffer 中的对应项目
Processor 处理器抽象
Processor 的步进函数实现如下,可以进行模拟器执行顺序的参考。
bool Processor::step() {
bool finish = backend.step(frontend);
auto newInst = frontend.step();
if (newInst.has_value()) {
if (!backend.dispatchInstruction(newInst.value()))
frontend.haltDispatch();
else {
std::stringstream ss;
ss << newInst.value();
Logger::Info("Dispatching %s with pc = %08x\n", ss.str().c_str(), newInst.value().pc);
}
}
return finish;
}
Register File
框架提供了 Register File 的基础实现。详细接口说明和实现可以参见 common/register_file.cpp
中的注释。
unsigned read(unsigned addr) const; // 读寄存器
void write(unsigned addr, unsigned val, unsigned robIdx); // 写寄存器,当 robIdx 匹配时清除 busy
bool isBusy(unsigned addr) const; // 查询寄存器是否 busy
void markBusy(unsigned addr, unsigned robIdx); // 标记寄存器 busy,同时设置占用 ROB 下标
unsigned getBusyIndex(unsigned addr) const; // 获取对应的占用 ROB 下标
void flush(); // 清除 busy 标记
保留站
由于保留站的实现带有模板参数,因此其定义和实现均在文件 include/reservation_station.hpp
中。模板参数 size 为保留站大小。
bool hasEmptySlot() const; // 检查保留站是否已满
void insertInstruction(const Instruction &inst,
unsigned robIdx,
std::shared_ptr<RegisterFile> regFile,
const ReorderBuffer &reorderBuffer); // TODO: 向保留站插入新指令,构造 issue slot
void wakeup(const ROBStatusWritePort &x); // cdb 唤醒指令
bool canIssue() const; // TODO: 检查是否有 issue slot 可以发射
IssueSlot issue(); // TODO: 发射一个 issue slot
void flush(); // 清空保留站
后端接口
Backend 的接口定义如下:
bool dispatchInstruction(const Instruction &inst); // TODO: 前端调用,向后端发射新指令
bool step(Frontend &frontend); // 后端步进函数
unsigned read(unsigned addr) const; // 读取数据内存
bool commitInstruction(const ROBEntry &inst, Frontend &frontend); // TODO:提交一条指令
前端接口
Frontend 的接口定义如下:
protected:
virtual BranchPredictBundle bpuFrontendUpdate(unsigned int pc); // Optional TODO: 分支预测逻辑
public:
virtual std::optional<Instruction> step() final; // 前端步进函数
virtual void jump(unsigned int jumpAddress) final; // 后端调用接口,发生跳转
virtual void haltDispatch() final; // 后端调用接口,表示指令不能发射
virtual unsigned calculateNextPC(unsigned pc) const; // Optional TODO: 分支 预测计算 next pc 逻辑
virtual void bpuBackendUpdate(const BpuUpdateData &x); // Optional TODO: 分支预测后端更新逻辑
分支预测需要指令带有额外的预测信息进行处理,这些数据结构定义在 include/branch_predict_model
中,可以进行修改,但请不要改动已有的项目,部分计算逻辑基于这些信息,如 mispredict = predictJump != actualJump || jumpTarget != predictTarget;
评测器 checker
代码在 program/checker.cpp
中。
使用方式如下:
Tomasulo Simulator Tester
Usage:
tomasulo-tester [OPTION...]
-o, --output arg Output Log file (default: output.log)
-h, --help Print Usage
-f, --file arg Input elf file
-c, --chk-file arg Check file
-d, --debug Print debug infos
-p, --predict Use frontend with predictor
一个简单的使用例子:
./checker -f ./test/selection_sort -c ../checkfiles/selection_sort.chk
正确的输出如下:
[ INFO ] Reading ELF file ./test/selection_sort
[ OK ] Testcase 1, WORD matched
[ OK ] Testcase 2, WORD matched
[ OK ] Testcase 3, WORD matched
[ OK ] Testcase 4, WORD matched
[ OK ] Testcase 5, WORD matched
[ OK ] Testcase 6, WORD matched
[ OK ] Testcase 7, WORD matched
[ OK ] Testcase 8, WORD matched
[ OK ] Testcase 9, WORD matched
[ OK ] Testcase 10, WORD matched
[ OK ] Testcase 11, WORD matched
[ OK ] Testcase 12, WORD matched
[ OK ] Testcase 13, WORD matched
[ OK ] Testcase 14, WORD matched
[ OK ] Testcase 15, WORD matched
[ OK ] Testcase 16, WORD matched
[ OK ] 16 testcase(s) passed
上述指令执行了 selection_sort 测例,测例的实现代码在 ./test
文件夹下。
如果需要自定义测例,可以直接在该目录下添加新的 cpp 文件。注意测例中不能使用标准库,并以 EXTRA::EXIT
结尾,可以参考该选择排序测例。同时也可以使用内联汇编自定义指令序列,如下:
int main() {
asm (
"li t0, 0x12345\n"
".word 0x0000000b\n"
:
:
: "t0"
);
}
关于内联汇编的更多使用方法,请自行检索。
测例检查文件的编写在 ./checkfiles
文件夹中。不同的工具链由于编译结果不同,需要使用不同的检查文件。编写可以使用如下两种指令,分别检查内存和寄存器。
RAM <address in hex> <value>
REG <address> value
测试程序的编译结果放在 ./build/test/
文件夹下,有程序的 elf 和反汇编 asm
。调试时可以参考该反汇编文件。