SimpleScalar模拟器是一个超标量、5级流水的RISC(Reduced Instruction Set Computing)体系结构模拟器,提供了从最简单的功能模拟到超标量乱序发射的不同的模拟程序。
SimpleScalar模拟器在功能级上实现了执行驱动、解释执行,在行为级上实现了流水线模拟。该工具集提供了一个以GCC为主的编译器以及相关组件,能够产生基于SimpleScalar体系结构的目标代码,然后在SimpleScalar模拟器上运行。
运行模拟器时,主程序main( )做所有的初始化工作,并将二进制目标码载入内存,然后调用sim_main(),sim_main()在每个模拟器中单独说明,预先译码整个正文段,加快模拟。然后开始目标程序的模拟:
(1)Sim-fast
Sim-fast是执行速度最快,最不关心模拟过程细节信息的子模拟器程序。它采用顺序执行指令的方式,没有指令并行;不支持cache的使用,也不进行指令正确性检查,由程序员保证每条指令的正确性;不支持模拟器本身内嵌的Dlite!调试器(类似于gdb调试器)。为了模拟器的速度优化,在缺省情况下,sim-fast模拟器不进行时间统计,不对指令的有关信息(如指令总数及访存指令数目)进行统计。当然,可以修改模拟器源程序,通过改变其设置,使模拟器更加符合设计人员的需求。
(2)Sim-safe
在工具集中,是最简单的最友好的模拟器,在sim-fast的基础上添加了Dlite!调试支持,检查所有的指令错误,不讲究速度。
(3)Sim-bpred
实现一个分支预测(branch prediction,也称作跳转预测)分析器,可采用五种分支预测方式:nottaken, taken, bimod,2lev, comb。
特有参数:
-bpred
-bpred:bimod
-bpred:2lev
(4)Sim-cache &sim-cheetah
Sim-cache实现cache模拟功能,为用户选择的cache和快表(TLB, translationlookaside buffer)设置生成cache统计,其中可能包含两级指令和数据cache ,还有一级指令和数据快表,不会生成时间信息。另外,实现cache模拟功能的还有sim-cheetah,能够有效地模拟全相联cache,并能同时生成各种cache set数配置下的cache统计量。同样地,sim-cheetah不会生成时间信息。
特有参数:
Sim-cache:
-cache:dl1
-cache:dl2
Sim-cheetah:
-refs
-R
-C
(5)Sim-eio
这个模拟器支持生成外部输入/输出跟踪(EIO traces)和断点文件。外部事件跟踪俘获程序的执行,并且允许被打包到一个单独的文件,以备以后的再次执行。这个模拟器也提供在外部事件跟踪执行中在任意一点做断点。断点文件可被用于在程序运行中启动simplescalar 模拟器。
特有参数:
-fastfwd
-trace
-perdump
-dump
(6)Sim-outorder
最完整的工具,前文中已提到。支持依序和乱序执行,branch predictor,memory hierarchy,function unit个数等参数设定。这个模拟器追踪潜在的所有流水(pipeline)操作。
特有参数:
-fetch:ifqsize
-fetch:mplat
-fetch:speed
-decode:width
(7)Sim-profile
也叫functional simulation,但提供较完整的模拟参数,可依照使用者之设定,决定所要模拟的统计量,如instruction classes andaddresses、text symbols、memory accesses、branches and data segmentsymbols等,以方便使用者整理收集数据材料。
特有参数:
-all
-iclass
-iprof
-brprof
-amprof
超标量流水线详解:
sim-outorder 是一个具有完整功能的模拟程序,在sim-outorder中使用了几乎所有的模拟资源。
具体乱序执行策略:
有五种很重要的功能单元支持sim-outorder对指令序列的乱序执行:保留站与重定序缓冲(RUU)、Load/Store队列(LSQ)、取指队列、输入输出相关链和寄存器忙闲表。它们在simplescalar中是通过五种数据结构来实现的。
RUU单元实现寄存器的同步和通讯功能,它将再定序缓冲和保留站统一起来,作为一个循环队列来管理。RUU队列记录了指令的操作类型、源操作数、数据有效性标识。其中的数据项在指令发射时分配,在提交时回收;当寄存器数据和存储器数据相关性满足时,实现乱序流出;
Load/Store队列处理存储器的相关性问题。如果store操作是猜测执行的,其值就被放入队列中。当所有之前的写入地址都已知之后,Load操作就可以访存。如果地址匹配,load操作可以在存储系统或者Load/Store队列中以前的store值的允许下进行。
取指队列是由取指段建立,在调度段译码并调度的指令队列;没有被调度的指令仍留在其中。它是用一个结构数组来实现的。
所谓输入输出相关链,即是用来记录前一条指令的输出数据(结果操作数)与后几条指令的输入数据(源操作数)的相关性的链表。
所谓寄存器忙闲表,即是用来记录当前各个寄存器被哪一条指令占用的结构数组。
Sim-outorder的具体的乱序过程如下:
A.取指段:根据配置的各种参数的要求,从一级指令Cache里预取指令,加入到取指队列里。如果在一级cache里找不到指令,同时配置了二级cache,就试图从二级cache里再找,否则就从存储器里寻找。
1. 根据分支预测的要求、cache 容量的支持、事先配置的取指队列的大小,确定预取多少条指令。
2. 在地址有效的条件下,取出指令,并根据一级指令CACHE的延时和一级指令TLB的延时计算出其取指延时的大小。
3. 若是分支指令,则要根据事先配置的分支预测策略预测下一条指令地址;若不是,指令地址自加一。
4. 把这一条指令加入取指队列里,更新取指队列。
B.调度段:从取指队列调度指令。指令首先被译码,然后为其分配RUU资源,判断是否存在数据相关性。如果不存在就可以发射出去,存在的话仍旧留在RUU队列里等待发射。若是访存指令则分配LSQ资源,最后更新输入输出的相关链。
C.发射执行段:检查从调度段发射出来的指令所需的功能部件是否可用(结构相关性),如果可用则将其发射执行。
1. 查看指令所需数据、功能部件是否准备好;
2. 如果是 store 指令,执行之。由于数据可先存在LSQ队列中,执行时间为零,实际的访存操作在 ruu_commit() 中执行。其他指令则需先查看无功能部件。
3. 如果是 load 指令,要确定 cache 访问的延时,先扫描LSQ队列看其前是否无访存地址相同的 store 指令。如果确实没有,那么store 指令存的数据就是 load 指令要取的数据,因而访存延时为一周期;如果没就并行访问数据 cache 和数据TLB,访存延时为二者中较大者。
4. 如果是非访存指令,操作时间为其功能部件的执行时间;不需功能部件的指令,操作时间为一个周期。如果是空指令操作时间为零。
D.LSQ队列更新:此过程是找出下一条数据相关性被满足了的指令,并将其发射。而这是通过检查LSQ队列,查找存储器阻塞的情况来实现的。
E.写回段:完成把功能部件的输出数据(结果操作数)写入RUU(registerupdate unit) 的任务。就这点来说,模拟器根据正在完成的指令的输出数据,确定取指队列中的后续指令的输入数据是否与其相关,如果是这样,将把这条指令从取指队列中调度出来进行发射。
F.提交段:这个阶段把已经完成的结果从RUU和LSQ提交到寄存器文件中,并且LSQ中的store 指令将把其存储数据提交到数据 cache 中。
1. RUU和LSQ中结果可提交,就执行提交。
2. 让LSQ中的 store 指令把其存储数据提交到数据 cache 中并计算其操作时间,其中要考虑TLB的延时。
3. 按序把已经完成的结果从RUU和LSQ提交到寄存器文件中,并更新RUU和LSQ。
SimpleScalar主要数据结构简介:
RUU(Register Update Unit):此结构将传统的保留站和排序缓冲站合并,实现了指令的乱序执行按序提交,用于支持处理器精确中断和误预测的状态恢复。而RUU将这两个部件合并可以减少硬件实现的花费。RUU被组织为循环队列,Head指向当前RUU中最先进入RUU的指令RUU单元,tail指针指向待分配RUU单元。在指令分配阶段,按取指先后顺序为每条指令分配RUU单元,指令在RUU中连续存放并且排列顺序与取指顺序一致,这使执行完成的指令能够按序提交。在执行阶段,RUU中的指令是乱序执行的,只要指令的操作数准备好,就可以将其送入RQ,为其分配执行单元执行指令。在回写段将执行结果广播到RUU,并解除相关。
LSQ (Load/Store Queue):用于处理Store/Load指令,其结构与RUU一致,一条Store/L
oad指令将被分成两个操作:地址计算和存取操作,地址计算操作作为加法运算放入RUU中,而存取操作放入LSQ中,提交的时候RUU和LSQ需要同步。
IFQ (Instruction Fetch Queue):用于存放取指(Fetch)段取出的指令队列,它在一个周期内尽可能多的取出指令,并保存取出指令的一些相关信息,如当前指令地址、预测下一条指令地址等。RQ (Ready Queue):如果指令执行所需所有操作数已准备好,则将指令放入此队列准备执行,它只是作为一种映射机制存在,将RUU中已经准备好的指令串联成一组,它本身并不保存任何信息,并且每当发射段运行一次后,RQ都被清空,准备在下一次运行分配段(Dispatch)的时候继续进行赋值。
EQ (Event Queue):该队列记录已发射(Issue)指令何时执行完毕,回写段以此确定何时将指令执行结果写回RUU。
以下是模拟器主程序主要部分源代码:
main(int argc, char **argv, char **envp)
{
sim_check_options(sim_odb, argc, argv);//检验命令行参数,并对个模块初始化,sim_*.c定义
mem_init();//程序加载前Memory初始化,mem.c定义
ld_load_prog();//程序加载,loader.c定义
regs_init();//寄存器初始化,regs.c定义
mem_init1();//程序加载后Memory初始化,mem.c定义
sim_init();//模拟器初始化,sim_*.c定义
sim_main()://模拟器执行入口函数,sim_*.c定义
}
SimpleScalar中Out-of-Order模拟器内核
Out -of-Order模拟器是提供了足够微体系结构细节的性能模拟器,是支持指令动态调度乱序执行的超标量模拟器,在2.0版的SimpleScalar中只为其提供了PISA一种指令集(4.0版还支持ARM、X86、Alpha指令集),PISA指令长度为64位,是一种类似MIPS的指令集,具有3种地址格式,包括32个整数寄存器,32个浮点寄存器。
Out-of-Order模拟器主函数sim_main()的程序结构表示如下:
void sim_main(void)
{
for ( ; ; )
{
ruu_commit();//提交
ruu_writeback();//写回
lsq_refresh();//LSQ队列刷新
ruu_issue();//发射执行
ruu_dispatch();//分配
ruu_fetch();//取指
sim_cycle++;//模拟器周期,用于统计程序执行周期
}
}
for 循环执行一次代表流水线执行一个机器周期,每个ruu_*()函数代表流水线的一段,采取反向执行顺序是为确保流水线各段之间的互锁同步。整个流水线共分为取指(Fetch)、分配(Dispatch)、发射(Issue&Execute)、回写(Writeback)、提交(Commit)五段。