- trace : 程序运行时的指令序列、指令地址、数据地址等信息,与处理器细节无关
- trace-driven simulator : 将trace作为simulator输入,只需维持控制指令时序的数据
- 优点:不需要考虑指令解释,速度较快
- 缺点:大的trace需要更多内存,微架构不直接影响程序,建模时引入的错误难以发现,增加了验证工作量
-
信号级模型 : 完全模拟所有硬件单元 (reg , wire, dff, mem) 。 准确,但灵活性和速度拉,修改困难
-
trace-driven simulator : 见上文
-
execution-driven simulator : 运行时一边产生trace一边分析,使用function model生成trace
trace-driven exec-driven的缺点: 1. 都cycle by cycle 模拟处理器内部状态,速度和灵活性低 2. 只建立处理器模型,未建立workload模型
-
基于统计的模型 :概率模型比较麻烦,略
sampling (SMARTS算法)
利用统计原理和实际程序运行表现出来的统计特性实现了对误差的估计和控制。对给定的误差范围和置信度,如果采样结果不能满足要求,它能自动推荐很可能满足要求的采样频率。
warmup (减少sampling误差)
保证在开始测量一个样本时,模拟器的微体系结构状态处于正确或者基本正确的状态。一种预热方法是在开始测量前先详细模拟一段指令,如何决定这段指令的长短和预热误差是关键问题。
simpoint
先完整地运行程序(用 trace生成工具或快速模拟器),得到每个样本中所有基本块执行次数所构成的基本块矢量(BBV),并根据 BBV的相似性通过聚类算法( PCA )把所有样本分成若干类,然后为每类挑选一个样本来做为代表,最后得到一组作为代表的样本。整体的性能由所有代表样本的性能及其权重决定。
单遍多配置算法
举例:运行一次就可以获得多个CACHE 配置(不同大小,不同相联度)的失效率数据
预处理
对trace或者目标可执行程序进行预处理,常常可以减少模拟器运行时的开销 (感觉是编译器工作)
软件缓存
把模拟器重复做的事情缓存起来,加速其运行 (这就是龙芯模拟器1200行的struct出处
并行
关键是要解决预热和误差控制问题
- 不针对特定处理器的模拟器,倾向于 高估性能。这通常是因为它没有反映实际硬件中的一些限制条件
- 针对特定结构设计的模拟器在未经验证之前通常低估性能,因为不容易正确实现
- 忽略操作系统的影响可能导致高达 100%的误差
作用: 改进流片前的性能模型, 优化编译器和操作系统
方法:使用硬件性能计数器,但是乱序处理器不能只看单一指标,可以考虑采样一条指令生命周期的详细数据,或者采样临近指令
trace-driven ,但trace的生成有两种方案
- atom 插桩法, 送实际执行的指令,但是没有考虑错误预测的指令,精确度低
- aint 指令集解释器,考虑到错误预测路径,能输出 程序执行的拍数、每拍平均执行了多条指令以及功能部件的使用、每种指令类型花在每个主要流水级的拍数、频繁执行的指令序列、分支预测和其它预测的报告、很难预测的指令等等
结论(踩坑):
- 模拟器开发早期需要灵活性,相当重要
- bpu测试,需要专用模拟器单独追踪分支指令
- event-driven不大行,只有35%的时间在这块
- 基于文本的log调试很有效
trace-driven,trace generate 和 model 分离,两种工作模式
可取之处:
- 逆向流水线设计
- 合并中构建不停滞的几个连续流水级
- 采用单队列最小化流水级间信息交换开销
- 采样技术
- 程序设计上:预处理译码,编译时参数化,编译优化
exec-driven, 提供了一套参考模拟器,包括快速的功能模拟器、用于分支预测评估的模拟器、 用于 CACHE 层次评估的模拟器,以及一个详细的超标量多发射处理器微体系结构模拟器,但不针对特定微架构。
只提供单线程、静态链接、用户程序的模拟,用户程序对操作系统的调用是通过一个代理接口(proxy)调用主机操作系统来完成的
全系统模拟包括外设,支持 指令集模拟器 + 超标量模拟器 + 二进制翻译,调试强大,缺点:不是很开源
特殊的trace结构,{体系结构状态,内存,外部中断} ,trace由硬件从实际程序中运行得到,模拟时不实际执行遇到的系统调用(因为系统调用的效果不是确定的),而是用踪迹中保存的相关信息重现系统调用的效果,这样可以保证模拟是完全可重现,理解为checkpoint.
性能模型和RTL采用分布式环境交叉验证,所有异常提交到数据库,由专门小组负责,整体比较激进
著名的商业模拟环境,gem5前身
exec-driven,模拟了超标量处理器、存储层次以及多处理器互连和协议。最终版本ML-RSIM我已经开源
cycle accurate model
踩的坑:
- 太精确导致速度太慢,不好改
- 只关心处理器核,没有考虑实际的系统IO,访存性能,集成性能不理想(集成包括处理器、配套芯片组、操作系统、编译器)
- 需要科学的性能分析方法和流程来定位性能瓶颈
定义:多个处理器性能模拟器和相关辅助工具,龙芯2号实现了如下工具
- cycle accurate model ict-godson 模拟器
- exec-driven perf-model
- RTL仿真环境
- FPGA仿真环境
- 硅后优化软件
- 详细的硬件模拟。模拟了龙芯 2 号处理器内部的所有硬件包括存储器、 寄存器、触发器和连线,和 RTL 的逻辑行为几乎完全一致。
- 全系统模拟。模拟了必要的外设(如串口、磁盘、内存),能够运行操作系统
- 比较完善的调试功能。通过命令行调试接口可以逐拍运行模拟器,设置 断点,选择记录某些事件到事件日志,以及检查处理器内部状态等等。 它还支持检查点(checkpoint)保存和恢复,极大地方便了调试。
- 多角度的数据收集功能。能够生成基于事件的统计、基于流水级的统计、 基于指令的统计、基于数据分布的统计,从各种角度反映处理器的行为。
- RTL 测试向量生成。能够将模块级接口上的输入输出数据转化为 RTL 测试向量。
- 并行运行
- 可以作为一个可执行的硬件设计文档
- 性能 50-100k/s
==TODO: 如果我们来做cycle accurate model,要实现哪些功能==
1. systemc 模拟所有硬件模块功能
2. systemc tlm 模拟总线
3. checkpoint支持,硬件计数器
4. systemc不支持并行,并且由于上下文切换成本高,线程越多越慢,如何优化?
5. 优秀的文档
6. 可以不考虑性能,只要正确性达标
- 速度快,500k inst/s
- 灵活性高,功能和时序模拟分离,exec-driven
- 支持spec cpu2000,一小时内跑完
- 支持功耗模拟
- 支持全系统模拟和用户模拟
可以用verilator , vcs替代
IO需要改进
- 硬件计数器
- 分析硬件计数器
- 链接优化器
- trace generator
- verilog难以和软件配合
- 仿真需要和商业软件配合,不能针对特定代码优化
- 软件模拟并发
- 硬件中的赋值并不是立刻生效,而是有一定延迟,由于这个延迟的存在,硬件描述语言里 可以直接交换两个寄存器的值,而 C/C++中不引入中间变量无法交换两个变量的值。
- 数据类型表示。例如,硬件描述语言中可以表示任意长的线,可以表示不确定值 X,高阻 Z 等
SystemC 采用进程来对硬件并发行为建模。每个进程是一个独立的控制流,在某些事件或者信号值的改变时被唤醒进行一些处理,完成处理后挂起等待下一 次唤醒。对于赋值延迟问题,SystemC 引入 delta cycle 的概念来解决。一个 delta cycle 是用户不可见的很小的时间单位,每个赋值在一个 delta cycle 之后 才能被其它进程观察到。
缺点:
- 开销大,一个复杂系统的模型需要用到大量的进程(或者线程),它们之间的通信、同步等开销会相当可观
- 效率低,一个简单的相加都需要调用一个函数来完成。因此在性能上并不能比 RTL 模型高很多
-
赋值延迟,使用一对变量来表示所有的寄存器和存储器。例如寄存器 R 用 r 和 r_bak 表示。每一拍中,寄存器输出端的值用 r 表示, 输入端的值用 r_bak 表示。所有寄存器赋值对输入端操作,一拍结束后统一“采 样”到输出端(即把输入端的值拷贝到输出端)
-
数据类型问题,ICT-godson 用 C 的标量类型定义了各种 64 位以下的线和寄存器。大于 64 位的类型以及模块间的总线使用结构来表示,无法表示信号的 X 和 Z 值,但这些值主要用于测试复位逻辑,对性能分析没有实质影响
-
并发行为表现在信号级模拟器的 C 语言实现中是调用次序的问题,C 语言中函数和语句只能以一个固定的次序执行,这样其中一些逻辑上同时发生的事件会被指定某种次序,进而导致问题。可以看作一个信号传播的问题。解决并发的问题即如何保证本拍信号的稳定值能够传播到每个模块,使用一种重复调用的策略,要求每个模块能在一个周期内执行多次而不影响其有效输出。因为,每个模块的输出只依赖寄存器的(输出端)值和输入总线的值,寄存器值在一拍内不会被修改,因此只要输入相同,每次调用的输出一定是相同的。 迭代若干次之后,信号必定会稳定下来。
- 实现了检查点(checkpoint)支持,它能够把某个时刻的处理器 内部状态记录到文件,利用该文件以后可以迅速恢复到该时刻的状态。可以把操作系统完 成启动时的状态保存为一个检查点,并利用这个检查点节省模拟操作系统启动的时间。还有运行、单步执行、设置断点、 单步跟踪等功能
要考察某个配置上所有 SPEC CPU2000 程序的性能,只需要一个命令将该配置提交给作业管理器。作业管理器会选择空闲节点运行所有的模拟,最后 从各节点收集生成的数据,并自动生成汇总表格
ict-godson经过软件优化提速300%,优化如下:
寄存器还包括各类存储器,例如CACHE和分支预测器的RAM 等等,也有类似的读写次序问题
-
对于 clock_begin 中的拷贝,只要模块没有引用寄存器输入端的值,而且寄存器不是有条件赋值,忽略
-
对于clock_end,可以将输入和输出端用同一个变量表示,需保证引用都出现在赋值后面。编程风格需要将生成输出的代码放在输入处理代码前面
run_one_cycle: for (I=0;I<MAX_ITERATION;I++) { //为了达到稳定状态需要的循环次数 clock_begin(); //把寄存器输入端的值恢复为本拍开始时输出端的值 module_a(); module_b(); … module_…(); } clock_end () // 把输入端的值复制到输出端,即使得寄存器的新值对下一拍可见
每个模块的代码可以划分成三个部分:
第一部分只由寄存器值生成输出总线; 只依赖内部状态,只要在第一个循环运行
第二部分通过输入总线和寄存器生成下一拍的寄存器; 不影响其它模块,只要输入准备好后运行一次就可以
第三部分则通过输入总线和寄存器生成输出总线; 看情况决定,一般需要两个循环都运行,但这种代码很少
使用profiling加速,例如,ICT-godson 的 Load/Store 队列模块,它一度占 24%以上的总运行时间。 使用主循环削减和代码分区之后大约减少了一半的时间,再通过各种代码变换最后减少到 4%左右。
- 总线无效时避免空操作。早期 ICT-godson 按硬件的习惯,不管是否有有效输出,输出总线每个域的生成逻辑都会运行。可以利用有效位来避免没有意义的逻辑操作
- 及早退出循环。很多队列操作都是遍历整个队列从中找出满足某个条件的项。针对硬件的写法通常对所有的项生成是否满足的条件,然后从中挑出满足的项。C 模拟器中可以利用队列的有序性,往往只需要查看很少的几项就能完成任务
- hash表 代替TLB查找
- 拷贝优化: for循环按字节拷贝 替换为 memcpy
相比cycle accurate的ict-godson 更灵活,支持设计空间探索,硅前性能预测,运行更快,支持大程序评估,支持simpoint等功能。基于simplescalar改,做了如下工作:
-
mips指令集寄存器定义、指令译码、功能实现
-
elf parser
-
实现对目标操作系统调用的代理。Simplescalar 采用一种被称为“代理(proxy)”的方法来处理应用程序 中的系统调用。它首先从被模拟系统中得到目标系统调用的相关参数,然后调用主机操作系统的系统调用实现该功能,最后根据主机系统调用的结果设 置被模拟系统的寄存器和内存,使得它看起来象刚完成一个系统调用,主要工作是识别系统调用的类型和参数,以及转换目标操作系统与主机操作系统的数据结构
独立的指令执行引擎来解释和执行指令,性能模型部分只维护确定指令流动时序所需要的微体系结构,不关心实际的数据。
例如,访问 CACHE 时,性能模型只需要知道访问是否命中(以计算访存指令的延迟),而不关心实际的数据。大体上性能模型里只有控制通路,没有数据通路。==疑问:那怎么知道cache是否miss?你并没有存数据==
模块 A 要输出数据给模块 B,而是否被接受依赖于模块 B 送给 A 的允许信号,模块 A 要根据输出是否被接受来更新自己的寄存器。
ict-godson 通过多次调用解决
sim-godson把 B 中允许信号的生成逻辑放到 A 中就能以先 A 后 B 的次序解决问题
消除这种向后的控制之后,流水级只向前传递数据,那么以和流水进行方向相反的次序调用流水级,就能保证流水级之间生成、使用数据次序正确。避免了流水级管理的开销,能够显著地提高速度
一旦发现分支预测错误,指令执行引擎进入猜测模式,此时所有对体系结构状态(寄存器和内存)的改变都不直接生效,而是暂存在其它结构里(采用 Copy-on-write 方式)。误预测的分支被纠正时,猜测模式的结果被抛弃,指令执行引擎重新从正确路径开始执行。多重误预测引入了错误层次,增强了指令执行引擎的体系结构状态读写接口。
不需要进行实际的重命名,而只关心寄存器值什么时候可用,只维护指令间的依赖关系,实际硬件情况维护空闲物理寄存器数目,在空闲数小于4时停止重命名
能够模拟可变延迟的功能部件、端口竞争等情况
精确模拟,软件实现
详细模拟了访存部件的各流水级,未采用event-driven
- 软件来缓存常用的数据,例如最近访问的 TLB 项,CACHE 项等, 降低查找开销
- 用指针传递来完成指令在流水级间的流动,最小化数据复制
- 用一个数据结构表示每条运行中的指令所有信息。系统初始化时分配足 够的指令信息结构,用一个链表保存 (1200 lines)
高性能微处理器的性能不仅仅取决于处理器核心。处理器的接口、外围芯片组、编译器以及操作系统等都会影响最终系统性能。
Sim-godson 的处理器性能模型既可以独立进行用户级模拟,也可以作为 SIMOS 的一个 CPU 模块参与全系统模拟。其中SIMOS类比Qemu
相关改造:
- 把 Sim-godson 的处理器内部状态封装到一个结构,以便 SIMOS 实例化 多个处理器。
- 实现和提供 SIMOS 需要的接口,包括初始化、切换、单步运行、调试 等
- 补全缺少的指令,实现对系统态指令的处理,并完善例外处理。
有3种方法:
- 截断法,只模拟整个程序的一个片断。具体运行方式类似DeepLearning,分为从头开始运行一定指令数后停止;先快速模拟前 X 条指令 (希望能跳过不感兴趣的初始化部分)再切换到详细模拟方式模拟 Y 条,类似深度学习动态学习率;在快速模拟 X 条指令后,详细模拟 Y 条指令来预热微 体系结构状态,然后再统计 Z 条指令,warmup
- 缩小输入集,但保证程序的缩小集特性和原输入集特性全面相似非常困难
- 采样法,simpoint先完整地运行程序(用踪迹生成工具或快速模拟器),得到每个样本中所有基本块执行次数所构成的基本块矢量(BBV, basic block vector[SPC01]),再根据 BBV 的相似性通过聚类算法把所有样本分成 若干类,然后为每类挑选一个样本来做为代表,最后得到一组作为代表的样 本。BBV 与具体结构没有关系,因此选中的样本并不依赖于具体微体系结 构。
使用simpoint的前提是得到bbv
- 得到目标工作负载的 BBV方法1:能够解释执行指令的功能模拟器。这样的模拟器相对比较快:在 3.0G Pentium4 处理器上,Sim-godson 的快速版本每秒可以模拟 20M 条以上的指令,约几小时。
- 得到目标工作负载的 BBV方法2:基于ALTO生成。(ALTO 是 Muth 等人在 Alpha 平台开发的一 个链接级优化器,它能够对已经链接好的可执行程序进行再优化。通过充分利用 此时所拥有的整个程序的信息,它能够取得良好的效果)
- 对比两种方法,ALTO更快但是不准
接着使用simpoint选择样本,再用模拟器模拟选定的样本,把各样本的数据按其权重综合起来得到对整体性能的估计。
最终sim-godson加速比从 13 到 586 不等,多数程序的 IPC 误差甚至在 1%以内,但是simpoint会低估存储系统的影响。
模拟器必须同时支持快速功能模拟和详细模拟,以及 它们之间的切换。模拟器还需要支持有选择的数据统计,以便支持预热。大部分情况很准确,但是分支预测误差率有时比较大。
(1) 根据目标微体系结构特性设计微基准程序 (尽可能全面地覆盖目标微体系结构特性
(2) 用微基准程序验证模拟器
(3) 根据误差分析结果修正模拟器。回到步骤(2)
(4) 采用更实际的工作负载验证,目前采用 SPEC CPU2000
(5) 分析验证误差
a) 如果迹象显示误差可能由某些特性模拟偏差所致,回到步骤(1), 增加针对该特性的微基准程序
b) 根据误差分析结果修正模拟器。回到步骤(2)
一些经验:
- 分析spec cpu的误差比分析第一步的误差困难很多,3 和 5 的反馈相当重要:
==我们不止一次发现,修正了一个问 题后,某些程序误差小了,但另一些原先结果貌似正确的测试程序却出现较大的 偏差。这是因为几个实现问题有时会互相掩盖。进行了任何修改都应该重新验证 所有的程序==
- 仅仅比较运行时间或者 IPC,有时会得出错误的结论
==未经验证的 Sim-godson 运行 SPEC CPU2000,很多程序的 IPC 误差小于 10%,只有 bzip2、crafty、twolf 等几个程 序误差较大。但是,该版本模拟器的浮点部件延迟、发射策略、分支预测等都还 存在严重的问题。如果我们检查其它统计数据,如分支预测率等,就会发现异常之处==
3.利用模拟器提供的数据收集功能, 我们可以从事件计数统计、流水级吞吐统计以及按指令的统计数据等多种角度进 行分析
分为控制类、执行类、访存类,每个程序都比较简单,通过考察每个微体系结构特性是否被正确模拟来进行验证。
==除访存类以外,所有程序都驻留在 ICACHE、DCACHE 和 TLB 里,以排除存储访问部件的影响== (是因为足够小1.
-
控制类程序 考察bpu
C-cond : if-then-else 语句循环,其分支指令按一次跳转、一次不跳转的规律变化 2bit饱和计数器 C-recur : 递归调用一个函数 1000 次,该函数把两个参数相加 ras C-switch1、C-switch2、C-switch3 : 测试 switch-case 语句,只是选中各case语句的频率不同 btb
-
执行类程序 考察func unit和流水线设计
E-I 是一系列不相关的整数加指令集合 E-F 是一系列不相关的浮点加指令集合 E-D{1-6}是一系列的相关指令链,用于考察指令调度和发射策略 D1 表示每条指令和其上一条相关, D2 则是和上上条指令相关。 E-DM1 是一系列的相关整数乘法指令,用来考察整数乘法器的延迟。
-
访存类质量, 考察存储层次
M-I 包含一系列不相关的CACHE命中的 Load 操作,测试dcache带宽 M-F 包含一系列不相关的CACHE命中的 Load,但是 Load 的结果是一个浮点数,用于累加 M-D 包含一系列连续相关 Load,每个 Load 都在 CACHE 中命中,用于考察cache延迟 M-M 通过一系列 CACHE 不命中的相关 Load 指令来测试访存延迟
微程序验证发现的问题举例
例1 :==ghr(g-share 预测器的全局 历史寄存器)的更新有问题。在表达式 ghr=ghr<<1 | taken,taken 值应为 0 或 1, 实际却用到饱和计数器的值,可能取 0-3,导致 C-cond 的 IPC 只有 0.55==
那么是如何发现的? 指令统计信息显示其中一个转移指令全部猜测错误
例2 :==最初的 Sim-godson 中 M-F 的 IPC 为 1.23,而 ICT-godson 为 0.99。==
分析汇编代码和 ICT-godson 的指令统计信息可以发现,原本应该不相关的浮点 load 操作在龙芯 2 号中表现为相关。这是由于龙芯2 号在 MIPS I 兼容模式下用一个 64 位物理寄存器存储两个 32 位浮点寄存器,写一个 32 位浮点寄存器先要把目标寄存器读出,以保留另 32 位的值,造成浮点 load 操 作对目标寄存器的隐性依赖。
例3 :==一些微基准程序的 IPC 还存在微小的误差==
随机因素可能导致两个模拟器中核心循环的初始状态不同,而循环初始状态对循环的性能影响有时会比较明显
微基准测试只能考察各个单元部件(类似单元测试),没有考虑各部件的相关关系,测试不重复。spec cpu2000每个程序最多运行 10 亿条指令,能全面考察处理器模型。
在实际工作中发现的问题如下:
- 第一轮验证发现了模拟器的几个功能错误,包括一些死锁情况和内存越界访问 ==模块间bug==
- 第二轮中,发现大多数程序 IPC 误差已经小于 10%,但还有几个程序的 IPC 误差很大(20%以上),包括 crafty、 twolf、bzip2、gap 等。调查发现其中 crafty、twolf 等程序的分支预测失效率明显 高于 ICT-godson,有时甚至达到 2 倍,其原因是 ghr 寄存器更新时机模拟不正确 ==模块内bug==
- 第三轮中,针对少数误差偏大的程序,更多的细节模拟,包括把定点乘、除指令分解为两个内部操作;实现指令 CACHE 的 hit under miss 支持(即在一个取指访问失效访存时,另一个取指访问能够继续在指令 CACHE 命中) ==性能调优==
RTL 模型 :处理器的逻辑行为,使用vcs或者candence相关工具进行综合仿真
FPGA : 处理器和目标系统的配合情况,主要用于功能验证,但是也能用于性能摸的交叉验证。
RTL与cycle accurate model交叉验证 : difftest / ca model产生测试向量输入rtl仿真
RTL与perf model交叉有验证 :通过上面的微基准测试进行