gem5 使用记录, 基于理解来写个最简单的计数器程序
学习GEM5其实是因为工作需要,主要是用来做数字电路的模型仿真的,之前用过 systemC,现在公司用的 gem5,其实本质上都是 C++只是套个不同的壳然后拿去仿真而已,SC本身就提供了时钟可以仿真,gem5用的是事件触发,对我来说都差不多,反正能跑起来就行。只是GEM5的资源要多一些,SC实在是感觉不太行,应该不大起得来,号称的系统级设计其实GEM5一样的可搞定。之前一直在关注我们的工作设计本身,平台都是前人搭好了的,我们直接用,不用太关心,现在有些空,所以对平台本身还是有点兴趣,研究了一下,感觉其实还挺好玩的,这次我自己写了个最简单的程序用这个平台跑起来,一方面试一下可行性,另一方面其实是我很久没有碰这个东西了,都忘完了,正好回忆一下。
这个小代码主要目的就是想搞清楚他这个事件到底是怎么弄的,verilog是有时钟的,C++没有时钟,都是顺序执行,所以他搞了个事件(event)的概念,每个事件相当于一个周期的时钟,我目测只要设计写好了,我写个for语句把程序调个几百万次好像也可以实现这么多个时钟的效果一样。但他这个平台提供了很多现成的直接用的东西感觉可能是要方便点吧。
这次就写了一个最简单的计数器,函数里面只有一个加1的语句,每执行一次值才增1,就像数字电路的计数器给他几个时钟他才加几次,就是想来研究他是不是执行一次事件他才加1,最终值跟事件执行的次数相关。下面是第一个代码,是一个.h文件,只定义了类和函数名。
第10行就是类的名字,所有的类都要继承SimObject,这是gem5的规定,这个类本身还有参数,所以也会有params在构造函数里面。
第13行是构造函数,mcccParams是这个平台自己会识别和生成的参数类型,就是类的名字mccc和Params的组合。
第14行是一个C++的智能指针,可以不用释放,方便一些。这里我为了方便后面处理可以用[0]这种下标就用了数组的形式,其实普通指针对数组操作更友好点,智能指针稍烦一点,这个指针其实就是声明了一个int数组的智能指针。
第15行是事件的声明,EventFunctionWrapper是gem5定义好了的一个事件类型,我们声明一个这个类型的变量event,后面再加入具体的要处理的事情。
第16行是一个整型变量,主要是向计数器传入初值,计数器不一定都要从0开始计数,也可以我们想从哪开始就从哪开始,所以有一个初始值的变量,以供我们配置。
第19,20行是仿照数字电路仿真时需要的时钟频率和我们要仿真的时长。这两个参数也是可以我们后面传入的。
第22行就是相当于是我们设计的顶层入口,相当于verilog中的设计的计数器顶层,第24行相当于是tb了,用来启动仿真的。这些都只是类比 verilog而已,正常的解释应该是计数器的实现和开始仿真和控制仿真的一些功能。
第23行是平台里面的一个函数,在正式启动仿真前会自动调用一次这个函数,也是从这时开始算时间,此时为0 TICK。这个函数的作用其实就是启动第一次事件,后面的事件就要靠里面的其实函数根据需要情况来启动了。
1 #include <memory>
2
3 //#include "learning_gem5/part5/mccc.h"
4 #include "params/mccc.hh"
5 #include "sim/sim_object.hh"
6
7 namespace gem5
8 {
9
10 class mccc : public SimObject
11 {
12 public:
13 mccc(const mcccParams &p);
14 std::unique_ptr <int []> counter;
15 EventFunctionWrapper event;
16 int firstnumber;
17 // int * counter;
18
19 int clkfreq;//mhz
20 int runtime;//us
21
22 void process();
23 void startup();
24 void startsim();
25 };
26
27 }
下面是.cc文件,这个gem5里面的文件为了区别和C++自己搞了个后辍名,头文件叫.hh,主体文件叫.cc。我这里还是用以前的叫.h。
第8行就是构造函数,当然有个params参数。9到13行就是初始化一些变量,9行就是把params传给SimObject,10行是构造具体的事件,按照官方文档的说法,event的参数有两个,第一个应该是一个函数,主要是我们真正想执行的程序的入口,第二个就是一个名字而已,一般我们就用当前类的名字加上一个.event来表示,实际代码中第一个参数是一个隐匿函数,里面有一个我们真正想在事件中执行的函数startsim(),每次执行事件时就会执行这个函数,这个函数就相当于我们整个设计的顶层入口,我的这个代码简单就一两个函数。第二个名字部分实际应该是mccc.event,代码中用name()这个函数来表示,这个函数的返回值就是当前类的名字,再加上.event一起构成了完整的名字。11到13行就是通过传参里面的一些参数来初始化赋值给变量,包括计数器初始值,时钟频率,仿真时间。
第15行是构造函数的函数体,主要就是干了一件事就是初始化counter指针,智能指针初始化稍和普通指针有一点不同,这里我用的reset来初始化的,new一个整型变量并且把fistnumber作为初始值给这个变量,这个变量的指针就给了counter。16行是一个打印,主要是把构造函数完成后打印出来可以直接看,方便看点。
第19行就是startsim的函数体了,21行是计算要执行的事件的总数,其实就相当于是总的时钟周期数,用频率乘以时间就是总的周期数,前面说了每一次事件执行相当于是一个时钟周期。23行是执行process()函数,这个函数就是我扪的计数器函数,每执行一次计数器加1。
第24行和29行都是为了方便看效果加的打印,可以直接看出当前的时钟数和计数器的值。curTick()这个函数的返回值就是当前的tick数,也就是周期数可以理解为。
第25行是判断当前的时间是不是已经完成所有的周期数,如果还没有完成就是继续执行下一次事件,如果完成了就不执行,gem5有个机制是如果一直不执行事件的话到一定的时间后就会自动退出仿真。
第27行就是执行事件,schedule这个单词就有安排计划的意思,这里也比较形象,就是安排去执行事件了,里面两个参数,第一个就是我们的event,第二个参数就是时间,一般是用当前时间加上一个值去表示从当前开始数,到了加的这个值的时间就去执行这个事件,比如我下面写的curTick()+1,就是在下一个周期时执行第一个参数的事件。这里一般不要写固定值,因为这个时间不能是过去的时间,所以都是curTick()再加一个固定值,才表示从当前开始算,只一种情况可以写固定值,就是startup里面,因为这里就是Tick开始计数的时间我们知道就是0,所以写个固定值没问题,并且这个startup只执行一次,也不会影响后面的执行。
第31行就是startup函数,可以看到里面就是安排了一次事件,因为他是整个仿真的第一步,需要他来启动第一次安排,后面的按排都在startsim里面根据时间自己安排了。这里可以写100,不要curTick(),因为此时cutTick()也为0。
第36行是process函数,就是我们设计的目的所在,是一个计数器,可以看到里面就是一个计数器加1而已,没啥特别的,这里我用的数组的形式,指针可以用这种,很多人在实际过程中也喜欢用指针来代替数组,三级指针就可以代替三维数组。counter[0]就跟*counter是一样效果这里。
1 #include "learning_gem5/part5/mccc.h"
2 #include "base/logging.hh"
3 #include "base/trace.hh"
4 #include "sim/sim_exit.hh"
5
6 namespace gem5
7 {
8 mccc::mccc(const mcccParams ¶ms) :
9 SimObject (params),
10 event ([this]{startsim();}, name()+".event"),
11 firstnumber (params.firstnumber),
12 clkfreq (params.clkfreq),
13 runtime (params.runtime)
14 {
15 counter.reset(new int (firstnumber));
16 std::cout<<"construct finished"<<std::endl;
17 }
18
19 void mccc::startsim()
20 {
21 int alltick = runtime * clkfreq;
22
23 process();
24 std::cout<<"before: "<<curTick()<<std::endl;
25 if(curTick() < alltick)
26 {
27 schedule(event, curTick() + 1);
28 }
29 std::cout<<counter[0]<<std::endl;
30 }
31 void mccc::startup()
32 {
33 schedule(event, curTick() + 100);
34 }
35
36 void mccc::process()
37 {
38 counter[0] = counter[0] + 1;
39 }
40
41 }
下面是本次代码对应的py文件,每个C++需要一个对应的python文件,第4行是类的名字mccc,也是要继承SimObject。
第5到7行算是固定写法吧,是对这个类的说明相当于,按照模版写就行。
第9到11行就是我们之前代码里说要传入的参数,这些参数在这里声明,后面再传入到C++里面去,这里的参数都是声明的Param里面的类型,Param是平台自带的一个参数类,里面把常见的类型封装了一下,比如int在这里就是Int,封一下之后用的时候可以传东西进去,可以传一个参数或两个参数,如果只传一个参数那就是对这个参数的说明,比如第9行就是对这个参数的说明而已,方便后人理解。如果传两个参数的话,第一个参数就是默认值,第二个才是参数说明。如果在仿真文件里面不传入新的值进来,则就会把这个默认值传入C++里面。我这里没有设置默认值。
1 from m5.params import *
2 from m5.SimObject import SimObject
3
4 class mccc(SimObject):
5 type = 'mccc'
6 cxx_header = "learning_gem5/part5/mccc.h"
7 cxx_class = 'gem5::mccc'
8
9 firstnumber = Param.Int("fist number of counter")
10 clkfreq = Param.Int("clk freq MHz")
11 runtime = Param.Int("run time us")
下面的代码就是本次仿真的入口文件,一般是放在config里面的一个pyhton文件,相当于整个设计加上验证一起的顶层。第4行也算是固定写法吧,每个设计里面都要有一个最高的例化,就是ROOT,里面的是选择是不是全系统仿真,我们这里当然不是。
第6行就是例化我们的设计模块,就是在这里传入我之前设定的参数。第8行和11行就是初始化仿真器和执行仿真,10行12行是一些打印方便看。12行会有个退出的时间和原因打印,之前说过我扪的事件完成后,一段时间没有安排的话就会自动退出。这里的原因最后打来基本上都是事件等待超时之类的。
1 import m5
2 from m5.objects import *
3
4 root = Root(full_system = False)
5
6 root.mccc = mccc(firstnumber = 5, clkfreq = 1000, runtime = 2)
7
8 m5.instantiate()
9
10 print("Beginning simulation!")
11 exit_event = m5.simulate()
12 print('Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause()))
下面是最后一步,把我们的设计加到整个gem5中去,让他在编译的时候带上,也就是每个设计代码文件夹里面都会有的SConscript文件,我的设计代码是新建了个文件夹part5,我的这个文件代码如下:
1 Import('*')
2
3 SimObject('mccc.py')
4
5 Source('mccc.cpp')
这个就是参考其他的这个文件格式来的,还是挺简单。把我们写的放进去就可以。
这里我们的代码就写完了,可以编译和跑起来了。命令如下:
1 sudo python3 `which scons` build/NULL/gem5.opt -j8 //compile
2
3 sudo build/NULL/gem5.opt configs/learning_gem5/part5/mccc.py //run
最后结果如下:
1 before: 1990
2 1896
3 before: 1991
4 1897
5 before: 1992
6 1898
7 before: 1993
8 1899
9 before: 1994
10 1900
11 before: 1995
12 1901
13 before: 1996
14 1902
15 before: 1997
16 1903
17 before: 1998
18 1904
19 before: 1999
20 1905
21 before: 2000
22 1906
23 Exiting @ tick 18446744073709551615 because simulate() limit reached
这里我设的时间长2000个周期,所以计数器比较大,显示不完,我取了最后一点。可以看出时间是跑了2000次,符合设计,计数器值最后是1906,因为我的仿真起始位置是从100开始的所以2000对应的计数器就应该是1900,但又因为我的计数器的初始值是从5开始的,所以最终结果就是1906。也符合设计意图。
自家电脑上最好把 sudo加上,不然会有很多问题,或者自己把GEM5所有文件的权限全部改一下也可以。下面记录一下这次过程中主要碰到的一些问题。
第一个就是我最开始不想用他这个里面的后缀名.hh和.cc,但中间有些生成文件又是这样的,反正还是有点麻烦,算了,用他的平台还是按他的规矩来吧,都用 .hh和.cc做文件后缀吧。
第二个是各种include文件,这个我现在还没有研究清楚,因为用了平台很多现成的类,需要包含进来,我就直接参考他里面的例子了,不然又各种错。
第三个就是参数名,一定记住是你的设计的类的名字和Params的组合,平台能自动识别,比如我这个就是mcccParams,区分大小写注意。gem5的所有代码设计风格都是大小写组合的,我个人不太喜欢这种命名风格,切换大小写烦死,代码的搜索也不好搜。我还是喜欢小写加下划线这种的。恼火。。
第四个就是在构造函数中一定要把事件加进去,这是规定。
第五个还是参数的问题,之前GEM5平台是要求设计者把参数的create函数写出来,但现在可以不用写了,但条件是声明参数的格式一定要按他的来,就是这样的mccc(const mcccParams &p)。const要加上,如果不加平台识别不了会报错,只有这种格式才可以识别并给你自动加上。。不然就只能手动加了。
其实这个小设计我还是想来类比verilog的仿真,有了这个设计,其实如果自己写一个新的功能的话,相当于只需要把process换成新的设计主函数就可以,其他的部分相当于是验证平台了。并且可以配置时钟频率和仿真时长。作为数字电路的模型用途好像也够了,当然这是最基本的。实际中还会有很多复杂的,比如回归,配置文件等等。
gem5 使用记录, 基于理解来写个最简单的计数器程序的更多相关文章
- 基于Spring aop写的一个简单的耗时监控
前言:毕业后应该有一两年没有好好的更新博客了,回头看看自己这一年,似乎少了太多的沉淀了.让自己做一个爱分享的人,好的知识点拿出来和大家一起分享,一起学习. 背景: 在做项目的时候,大家肯定都遇到对一些 ...
- php 如何写一个自己项目的安装程序
版权声明:此篇文章只是用作笔记,如果版权冲突,请邮件通知一下(15201155501@163.com) https://blog.csdn.net/shenpengchao/article/detai ...
- 放弃antd table,基于React手写一个虚拟滚动的表格
缘起 标题有点夸张,并不是完全放弃antd-table,毕竟在react的生态圈里,对国人来说,比较好用的PC端组件库,也就antd了.即便经历了2018年圣诞彩蛋事件,antd的使用者也不仅不减,反 ...
- WebAPI 用ExceptionFilterAttribute实现错误(异常)日志的记录(log4net做写库操作)
WebAPI 用ExceptionFilterAttribute实现错误(异常)日志的记录(log4net做写库操作) 好吧,还是那个社区APP,非管理系统,用户行为日志感觉不是很必要的,但是,错误日 ...
- 依赖注入(DI)和控制反转(IOC)的理解,写的太好了。
学习过spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...
- 轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI) 依赖注入和控制反转的理解,写的太好了。
轻松学,浅析依赖倒置(DIP).控制反转(IOC)和依赖注入(DI) 2017年07月13日 22:04:39 frank909 阅读数:14269更多 所属专栏: Java 反射基础知识与实战 ...
- 基于 vite2 + Vue3 写一个在线帮助文档工具
提起帮助文档,想必大家都会想到 VuePress等,我也体验了一下,但是感觉和我的思路不太一样,我希望的是那种可以直接在线编辑文档,然后无需编译就可以直接发布的方式,另外可以在线写(修改)代码并且运行 ...
- 基于File NIO写的一个文件新增内容监控器
基于File NIO写的一个文件新增内容监控器 需求说明 监控一个文件,如果文件有新增内容,则在控制台打印出新增内容. 代码示例 FileMoniter文件监控器类 package com.black ...
- .Net写txt文件-简单的记录执行日志信息代码
在执行一些批量操作时,想记录一些执行日志信息,越简单方便越好啊.提供一个常用的简单方法,将信息记录在txt文件里: public static void log(string content, str ...
随机推荐
- 用console画条龙?
相识 console一定是各位前端er最熟悉的小伙伴了,无论是console控制台,还是console对象,做前端做久了,打开一个网页总是莫名自然的顺手打开控制台,有些调皮的网站还会故意在控制台输出一 ...
- docker删除镜像报错 Error response from daemon: conflict: unable to delete f73fe6298efc (cannot be forced) - image has dependent child images
方法1 docker rmi 镜像ID 方法2 docker rmi -f 镜像ID 方法3 docker rmi 镜像仓库名:tag
- LeetCode. 812. 最大三角形面积
812. 最大三角形面积 鞋带公式 鞋带公式,用于计算任意多边形的面积,可用于计算三角形的面积 已知 ΔABC 三个顶点的坐标 A:(x1,y1). B:(x2,y2). C:(x3,y3) 对应的矩 ...
- NC14585 大吉大利,今晚吃鸡
NC14585 大吉大利,今晚吃鸡 题目 题目描述 糖和抖m在玩个游戏,规定谁输了就要请谁吃顿大餐:抖m给糖a b c三个驻, 并在a柱上放置了数量为n的圆盘,圆盘的大小从上到下依次增大,现在要做的事 ...
- 让你的Nginx支持分布式追踪
Background NGINX 是一个通用且流行的应用程序.也是最流行的 Web 服务器,它可用于提供静态文件内容,但也通常与其他服务一起用作分布式系统中的组件,在其中它用作反向代理.负载均衡 或 ...
- 2022省选前联考 AVL树/平衡树
题目描述 pks 得到了一棵 \(N\) 个节点,权值为 \(1\sim N\) 的 \(AVL\) 树,他觉得这棵树太大了,于是他想要删掉一些节点使得最后剩下的树恰好有 \(K\) 个节点.如果 p ...
- 漏洞扫描工具nessus、rapid7 insightvm、openvas安装&简单使用
Rapid7-insightvm 申请试用 申请地址 邮件地址不能用常用邮件,要使用自己域名的邮件,可以使用这个临时邮箱 手机号随便输入,10位以上 提交后会跳转下载页面 安装 安装:./Rapid7 ...
- 聊聊 C++ 中的四种类型转换符
一:背景 在玩 C 的时候,经常会用 void* 来指向一段内存地址开端,然后再将其强转成尺度更小的 char* 或 int* 来丈量一段内存,参考如下代码: int main() { void* p ...
- [原创]树莓派CM4配置GPIO复用为i2c
1.简介 项目中需要控制各种外设的电源,正常应该是通过GPIO进行控制,但是树莓派CM4的GPIO管脚有限,因此需要使用i2c扩展IO 查阅CM4-datesheet发现GPIO22和GPIO23可以 ...
- 【每天学一点-03】 使用Html5+Less实现简单的静态登录界面(入门Less)
1.首先引用Less 有npm安装.cdn引用.或者下载Less.js本地引用,我采用的是第三种方法 less.js引用: 下载地址:https://github.com/less/less.js/t ...