概述

- SystemC语言学习笔记
- 背景
- SystemC语法
- 声明子模块
- 几种处理过程的对比
- 模块间调用
- 仿真测试
- 信号发生器
- 仿真开始
- 声明子模块
- SystemC库的编译
- 在windows下编译systemC库
- 在linux下编译systemc库
- Visual Studio 2019的SystemC环境配置
- Visual Studio Code的SystemC环境配置
- VCD波形文件的使用
- 总结
- 参考内容
背景
最近在看一些仿真器的源码,有些开源项目使用了SystemC来开发,为了能够顺利地看下去这些代码,及时补充自己在这一块编程语言上的不足。由于SystemC是一个C++的类库,所以基础语法的学习还是很容易上手的,这篇文章就把我学习过程记录一下,方便日后回顾。
SystemC语法
由于没有迫切地需要用SystemC来写项目,所以我只是粗略地了解了一下SystemC的语法,保证我能够看得懂别人的项目,至于更详细一点的语法可以看这个PPT,概括地应该蛮详细(给自己留坑,有需求再学习)
声明子模块
和Verilog类似,使用SystemC也用模块的方式定义一些功能模块,每一个子模块使用C++中的一个类来声明,为了从视觉上看起来和硬件描述语言更像,SystemC库使用一个宏定义SC_MODULE(<module name>)
替换对应的类声明,每一个子模块的类主要分成三个部分,分别是端口声明,功能描述以及功能注册,这一部分一般写在头文件.h
中,对应的源文件用来描述具体的功能实现。 - 端口声明
首先要对模块的输入输出端口进行声明,一般使用sc_in<type>
、sc_out<type>
和sc_inout<type>
3种端口类型,其中type为端口的数据类型,可以是C++中的一些数据类型,也可以是SystemC中自带的一些数据类型,如sc_int<WIDTH>
等,其中WIDTH为数据位宽,一般时钟和复位型号的数据类型定义为布尔类型,即sc_in<bool> clk,rst_n
。 - 功能描述
接下来是对模块的功能进行描述,以无参数函数的形式表示,这个功能描述就像是Verilog中的Always块,如:
void process();
void rxProcess(); // The receiving process
void txProcess(); // The transmitting process
void perCycleUpdate();
一般我们只是在头文件中对需要用到的功能函数进行声明,然后在对应的源文件中实现功能的描述:
void Router::process()
{
txProcess();
rxProcess();
}
在读写端口时,一般使用.read()
读取输入端口的数据并使用.write()
向输出端口写入数据,如:
...
reset.read()
...
ack_rx[i].write(0)
...
在具体赋值时,功能描述中的赋值都是阻塞赋值,这一点在具体写代码的时候需要注意。 - 功能注册
最后对我们描述的功能函数进行注册,实际是编写模块类对应的构造函数,在构造函数中我们需要注册功能,描述连接关系以及说明敏感列表,同样为了视觉上更直观,构造函数也用宏定义SC_CTOR(<module name>)
进行了一个包装,具体代码如下:
SC_CTOR(Router) {
SC_METHOD(process);
sensitive << reset;
sensitive << clock.pos();
...
在这段代码中,我们写了Router的构造函数,并将其process功能使用SC_METHOD()
进行注册,我们描述的功能需要经过METHOD(或THREAD等)的方法注册才作为模块的一个功能。在构造函数中还需要添加该功能的敏感变量,这和Always块同理,当敏感变量发生变化时,对应的功能模块就会执行,敏感变量分为电平敏感和边沿敏感,对应的代码为:
sensitive <<
sensitive_pos <<
sensitive_neg <<
至此,一个功能模块的定义与描述结束。
几种处理过程的对比
在上面的案例中,我给出了一个SC_METHOD()
的一段内容,在SystemC中有三种类型的处理过程: - Methods: SC_METHOD()
,这是用来描述组合逻辑的,由输入信号的变化触发,不能包含无限循环,以及wait()
等; - Threads: SC_TEREAD()
,这种描述可以用在基本上所有地方,由输入信号的变化触发,但可以在两次调用时保存控制状态,如可以使用wait()
,在调用时,进程被挂起等待下一次触发继续执行; - Clock Threads: 时SC_THREAD()
的一种特殊情况,可以产生更好的行为综合效果,只能由时钟信号沿触发。
模块间调用
对于一个层次较高的模块或者是顶层模块,其需要调用其他子模块,并进行相关的连接,模块之间连接的定义是在构造函数中描述的,即SC_CTOR()
,我看的工程比较复杂,这里贴一个网上简单的例子:
SC_MODULE(matrix_vector_mul) {
...
vector_mul *pe[VEC_NUM];
SC_CTOR(matrix_vector_mul) {
std::ostringstream pe_name;
for (int i = 0; i < VEC_NUM; ++i) {
pe_name << "pe" << i;
pe[i] = new vector_mul(pe_name.str().c_str());
pe[i]->clk(clk);
pe[i]->rst_n(rst_n);
for (int j = 0; j < VEC_WIDTH; ++j) {
pe[i]->vec1[j](matrix[i][j]);
pe[i]->vec2[j](vector_in[j]);
}
pe[i]->vec_o(vector_out[i]);
pe_name.str("");
}
};
};
从这个例子中我们可以看到,连接主要可以分为三个部分: 1. 声明模块指针,这个指针用来存储后面实例化的模块的地址 2. 实例化模块,在实例化模块时要保证每一个实例模块名字的唯一性 3. 连接各模块之间的接口<point>-><port name>(<signal name>)
以上,我们结束了模块间的连接。
仿真测试
在Verilog中我们会编写Testbench来对代码进行测试,同理,在SystemC中我们会使用主函数来调用对应的测试模块,测试之前首先要根据我们的需求,写一个信号产生的模块。
信号发生器
我们可以把我们在现实中测试电路时用到的信号发生器当作一个类似于电路一样的模块,在仿真时我们可以写一个简易的发生器来满足我们仿真测试的需求。
...
void generate_reset(void) {
rst_n.write(1);
wait(1,SC_NS);
rst_n.write(0);
wait(1,SC_NS);
rst_n.write(1);
};
SC_CTOR(driver) {
SC_THREAD(generate_input);
sensitive_neg << clk;
SC_THREAD(generate_reset);
};
部分代码如上,在实现信号发生器的功能函数时,我们可以使用wait()
来进行阻塞,从而实现一个信号维持对应时间的电平状态。同时在写构造函数时,使用THREAD宏注册为功能,这一宏与METHOD的区别是这一种功能进程在仿真开始时运行,碰到wait()跳出,直到敏感列表中的信号再次触发这一进程,从上次跳出的wait()处继续运行。以复位信号的生成为例,由于SC_METHOD
和SC_THREAD
都是阻塞执行的,即在执行对应的函数时,其他函数无法进行,所以在执行generate_reset()
函数时,其他函数暂停,那么在写高电平信号后,遇到wait()
后,程序会跳出generate_rest()
,转而执行其他函数,当其敏感信号,即时钟信号下降沿到来时,会继续执行上次跳出的位置后的语句,即rst_n.write(0);
。
仿真开始
接下来就是写仿真主函数,主函数主要分为几个部分:信号声明、波形文件相关声明、模块实例化以及端口连接。 - 信号声明
在主函数中,我们不需要声明端口,但是需要提前声明连接各模块所需要的信号线,时钟信号有专门的声明语句,具体如下:
sc_clock clk("clk",10,SC_NS);
sc_signal<bool> rst_n;
- 波形文件相关声明
VCD波形文件是我们用来查看仿真结果的重要文件,SystemC直接生成VCD文件,首先我们需要创建一个波形文件并进行一些初始化操作:
sc_trace_file *fp; // Create VCD file
fp=sc_create_vcd_trace_file("wave");// open(fp), create wave.vcd file
fp->set_time_unit(1, SC_NS); // set tracing resolution to ns
然后,我们需要在仿真开始前指定波形文件需要跟踪的信号:
sc_trace(fp,clk,"clk");
sc_trace(fp,rst_n,"rst_n");
一切准备就绪后,我们执行sc_start()
开始仿真,执行时间为1000ns。
sc_start(1000,SC_NS);
sc_close_vcd_trace_file(fp); // close(fp)
仿真结束后,关闭VCD波形记录文件。 - 模块实例化&端口连接
有两种方法可以实例化模块,一种是和子模块一样,先声明指针,再创建对象:
// NoC instance
n = new NoC("NoC");
也可以直接声明对象:
matrix_vector_mul dut("dut");
这两种方法均可。
实例化完成需要的模块后,把测试模块和信号发生模块通过声明的信号线连接起来。
SystemC库的编译
- 下载库源码 从这里下载systemc库的源码,linux选择
tar.gz
格式,windows选择.zip
格式
在windows下编译systemC库
在解压完成的目录中,打开systemc-2.3.3msvc10SystemC
目录下的SystemC.sln
,用高版本的vs打开可能需要重定向,按照提示走就可以,然后进行build,就可以在Debug
目录下看到生成的SystemC.lib
文件
在linux下编译systemc库
- 首先完成解压
- 创建临时文件夹
cd systemc-2.3.3 >mkdir objdir >cd objdir
- 指定安装目录
../configure -prefix=/home/user/systemc
要保证prefix指向的位置文件夹存在。这个指令执行完毕,目标文件夹下并未产生文件。 4. make&install
make
make install
这两步执行完毕后,目标文件夹下出现安装文件
至此完成
Visual Studio 2019的SystemC环境配置
在正常创建完工程后,对工程的属性做如下修改:
C/C++: 1. 常规>>附加包含目录:添加 C:systemc-2.3.3src 项 2. 语言>>启用运行时类型信息:选择“是” 3. 命令行>>添加 /vmg /D_CRT_SECURE_NO_DEPRECATE
链接器:
- 常规>>附加库目录:添加 C:systemc-2.3.3msvc10SystemCDebug 项
- 输入>>附加依赖项:添加 SystemC.lib
以上内容直接从网上找来的。
Visual Studio Code的SystemC环境配置
我一般会使用Vscode查看代码,对于带有第三方库的工程,如systemC
等,如果不添加库的路径,无法在Vscode中查看函数的调用关系,我的解决方案是使用vcpkg
工具,方法: - git clone https://github.com/microsoft/vcpkg - 执行Vcpkg工程目录下的“bootstrap-vcpkg.bat”命令 - 查看Vcpkg支持的开源库列表: .vcpkg.exe search
- 安装库:.vcpkg.exe install libname
- 列出已经安装的开源库:.vcpkg.exe list
- 集成到全局:.vcpkg integrate install
- 在Vscode的configurations中添加包路径(贪图方法的方法,每一个包都要加路径有点麻烦,以后有空再研究)
VCD波形文件的使用
因为对modelsim比较熟悉,所以最先想到了使用modelsim来打开VCD格式的文件,方法如下: 1. 在Modelsim中的控制台输入:
vcd2wlf file1.vcd file2.wlf
其中,file1是要转换的.vcd文件名,file2是转换后的文件名。转换完成后该文件位于当前Modelsim工程目录下;
- 在Modelsim中File->Open->file2.wlf->在object标签中选取需要观察的信号添加到波形窗即可,后面同一般的Modelsim工程仿真
总结
这篇文章总结了我这几天从零接触SystemC的一些学习记录,个人觉得了解这些基本上可以看懂一些用SystemC写的工程了,但是如果要自己用SystemC写工程可能还需要详细看一看SystemC的具体语法。所以写这篇文章一是为了让自己学习过程有一个记录,同时又可以给有相同需求的做一个相关内容的整合。
参考内容
[1]SystemC入门笔记
最后
以上就是幸福烧鹅为你收集整理的c语言system_SystemC语言学习笔记的全部内容,希望文章能够帮你解决c语言system_SystemC语言学习笔记所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复