这次进行一些systemC的尝试。

systemC对于数字集成电路设计来讲不很重要,但是对于数字集成电路的验证(Digital.IC.Verification)以及系统架构architecture来说,还是有一定地位的。

在寄存器传输级的设计中,systemC虽然有HLS技术,但是systemC在时序和面积优化还是不如systemverilog细腻,而且现有EDA和IP库基本都是verilog/systemverilog构建的,但是到了系统架构的层面上,systemverilog的仿真就太慢了,就好像是你不可能用显微镜看遍地球。

systemC有抽象的建模和性能预测,可以快速完成系统的评估。

为什么在验证这个领域systemC依然有很大作用呢?因为systemC有两个比较好用的地方,一个是参考模型(reference model),用systemC写一个算法级的模型,就可以把RTL的结果和systemC的结果实时对比;另一个是软硬件协同验证,systemC可以跑真正的固件(firmware),也就是说硬件还没有做出来的时候软件工程师就已经可以开始动工写代码了。

本次参考资料:


set up

systemC实际上是C++的一个扩展模块,所以我们首先需要一个C++ compiler,这个非常简单,大家应该也都有,下载一个gcc或者clang就可以了。

systemC由于需要指定很多头文件路径,预编译路径,链接特定的库文件,还需要多模块协作和管理等,最好也下载一个cmake方便管理。

1
sudo pacman -S gcc cmake

然后我们要下载systemC的库,

1
git clone https://github.com/accellera-official/systemc.git

进入systemc/build,完成systemC构建:

1
2
3
cmake .. -DCMAKE_INSTALL_PREFIX=../install
make -j$(nproc)
make install

然后在../install也就是systemc/install目录下完成了安装。

之后需要将这个路径添加到.zshrc中方便使用。为了防止误操作,我把install这个文件夹移动到.config下改名为systemc:

1
2
export SYSTEMC_HOME=~/.config/systemc/
export LD_LIBRARY_PATH=$SYSTEMC_HOME/lib:$LD_LIBRARY_PATH

简单验证一下系统是否成功识别目录:

1
2
3
ls $SYSTEMC_HOME/include
ls $SYSTEMC_HOME/lib

然后可以写一个hello_world确认安装成功:

1
2
3
4
5
6
7
//sc.cpp
#include "systemc.h"

int sc_main(int argc, char* argv[]){
cout<<"hello world!"<<endl;
return 0;
}

使用g++进行编译。g++手动编译的时候,需要手动告诉编译器头文件和库文件的地址,因为LD_LIBRARY_PATH只在运行时起作用,对编译过程没有直接帮助。

1
g++ -I$SYSTEMC_HOME/include -L$SYSTEMC_HOME/lib -lsystemc sc.cpp -o sc

-I指定头文件systemc.h路径,-L指定库文件路径,-lsystemc连接名为libsystemc.o的库。

运行./sc出现:

1
2
3
4
        SystemC 3.0.2-Accellera --- Mar  6 2026 13:43:39
Copyright (c) 1996-2025 by all Contributors,
ALL RIGHTS RESERVED
hello world!

说明安装成功。

做出几个可能不需要说的说明:

  • #include "systemc.h":
    在使用systemc时,需要引入systemc.h这个头文件。不过还有相对更加现代的做法,#include <systemc>,更加具有cpp风格。

  • int sc_main(int argc, char* argv[]): 看起来还算经典的cpp入口。不过在systemc中,systemc library中有一个main()函数,他会把经典的command line arguments传给sc_main(),sc_main()才是user code的entry point.

  • return 0;: 依旧是起到一个结束main()的作用。

  • cin, cout, cerr, clog:经典交互,(多数情况下)分别是读取,输出信息,输出error,输出log.


systemc data type

经典的C/C++简单数据类型就略过了。

fixed-precision integer

这个fixed-precision,含义为可以自己指定位数。

主要是sc_intsc_uint两个,自然是一个signed,一个是unsigned,两者都是上限位宽64bits.

两种数据支持bit select, part select, reduction, concatenation.

1
2
3
4
sc_int<w>/sc_uint<w> : w -> width.

sc_int<4> a;
sc_uint<6> b;

bit select:

1
2
sc_uint<8> data=0b10110100;
bool bit3=data[3];// |

part select:

1
2
sc_uint<8> data=0b10110100;
sc_uint<4> nibble=data.range(7,4); //选取从7到4的部分,包含7和4.

concatenation:

1
2
3
4
5
sc_uint<4> high=0b1011;
sc_uint<4> low=0b0100;
se_uint<8> result;

result=(high, low); //result = 0b10110100;

reduction:

1
2
3
4
sc_uint<4> val=0b1111;
bool and_result=val.and_reduce();
bool or_result=val.or_reduce();
bool xor_result=val.xor_reduce();

和C++原生数据相比,这几个数据支持位操作,溢出默认截断到w位,更加贴近RTL硬件的行为。

0b是C++的二进制前缀,含义就是后面的数字是二进制的,类似于0x.

reduction操作是从最高两位开始,一直做一个操作,比如上面的and_reduce,可以用来检测是不是全1序列,or_reduce可以检测是不是全0序列,xor_reduce检测序列中1的和数是奇数个(1)还是偶数个(0)。

arbitary-precision integer

arrbitary的含义就是不限制最大位宽是64位,其他和fixed-precision integer差不多。

本质是牺牲一定运算速度,让计算不受位宽限制,当模型需要超过64位数据的时候只能选择arbitary-precision.

1
2
sc_bigint<2048> a;
sc_biguint<4096> b;

logic

logic是一个支持四态的数据类型,同样支持以上四种操作。

1
2
3
4
5
6
7
8
sc_logic a;

sc_logic b=SC_LOGIC_0;
sc_logic c=SC_LOGIC_1;
sc_logic d=SC_LOGIC_X;
sc_logic e=SC_LOGIC_Z;

//或者其实用字符 '0', '1', 'X', 'Z'也是可以的

sc_logic支持&, |, ^, ~的操作。

sc_logic只能表示单根信号线,如果需要多位,可以使用sc_lv<w>.

1
sc_lv<8> bus; //8位总线,每一位都支持四态

systemc modules

在systemc中,module是继承自sc_module的,本质上(可以)是class(也可以是struct).
可以通过继承的方式创建module:

1
class module_name:public sc_module{};

也可以通过SC_MODULE macro创建:

1
SC_MODULE(module_name){};

对于一个class还说还需要constructor,可以通过SC_CTOR创建。

1
2
3
SC_MODULE(module_name){
SC_CTOR(module_name){}
};

创建和UVM有点像,一般是:module_name instant_name("instant_name"),一般传入参数和module实例相同方便管理和使用。

在module内的通信一般通过interface实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<systemc>
using namespace sc_core; // 不写这个需要给所有systemc要用的东西加上sc_core::

SC_MODULE(MODULE_A){
SC_CTOR(MODULE_A){
std::cout<<name()<<std::endl;
}
};

int sc_main(int, char**[]){
MODULE_A module_a("module_a");

sc_start();
return 0;
}

运行结果:

1
2
3
4
        SystemC 3.0.2-Accellera --- Mar  6 2026 13:43:39
Copyright (c) 1996-2025 by all Contributors,
ALL RIGHTS RESERVED
module_a constructor

sc_start()用于启动仿真。

sc_start()

sc_start()可以指定运行时间和单位:

1
2
3
sc_start(100, SC_NS); //运行100ns后停止
sc_start(10, SC_US);
sc_start(1, SC_MS);

sc_start()什么都没写就是一直运行仿真,直到没有任何事件。

sc_start()是可以分段运行的,可以运行一段时间中断之后再次运行。


从零开始的DFT工程师! homepage

project homepage is here.