系统级设计描述语言
语言架构
SystemC本质上是在C++的基础上添加的硬件扩展库和仿真核。
1 | graph LR |
- 方法学库
- Master Lib
- Slave Lib
- 层次(Layered)库
- Verification Lib
- Static Data Flow
- 基本通道
- 信号
- 互斥
- 信号量
- FIFO
- SystemC 核心
- 模块(Module)
- 端口(Port)
- 进程(Process)
- 接口(Interface)
- 通道(Channel)
- 事件(Event)
- 基于事件的仿真核
- 数据类型
- 4值逻辑数据类型
- 4值逻辑向量类型
- 比特和比特向量
- 任意精度整数型
- 定点数据类型
- C++用户自定义数据类型
- C++语言标准
描述层次
SystemC 不仅仅是一种新的硬件描述语言,更是一种系统描述语言。
- 寄存器传输级(RTL)
- 时钟周期精确级
- 带时间信息的编程级(PVT)
- 编程级(PV)
- 算法级
- 高层
- 底层
基本语法
- 缺省时间单位为 ns,缺省时间分辨率为 1ps
- 模块用
SC_MODULE(module_name){...}
来声明 - 一个模块实际上是一个类,拥有构造函数和析构函数
- 最顶层的函数是
sc_main
全局函数
sc_version()
sc_copyright()
T sc_abs(const T& val)
T sc_max(const T& a, const T& b)
sc_start()
:开始运行仿真核sc_top()
:停止运行
示例:与非门
1 |
|
仿真过程
仿真过程是基于事件的,时间只前进,不后退。前进的尺度与仿真时间分辨率和时间单位有关。
执行过程分三个阶段:
目标描述
- 模块实例和连接模块的通道的初始化
- 设置缺省时间单位和仿真分辨率
- sc_clock 的初始化
- sc_time 类型数据的初始化
初始化
整个 SystemC 仿真的执行过程由 SystemC 调度器控制,初始化是其执行的第一步
SystemC 核心语言库定义了三种进程:
SC_METHOD
SC_THREAD
SC_CTHREAD
在初始化阶段,缺省情况下每一个进程都被执行一次,
THREAD
进程被执行到第一个wait()
语句通过
don't_initialize()
函数可以关闭对进程的初始化在初始化阶段,进程的初始化顺序是不确定的;但不同次执行中进程的初始化顺序是确定的。因此用不同编译器可能产生不同的运行结果。
仿真
从第一次遇到
sc_start()
开始到预先设定的仿真时间结束或者遇到sc_stop()
预先设定的仿真时间由
sc_start()
确定,如:1
2
3
4
5SC_MODULE(Example) {
/* ... */
sc_start(500);
/* ... */
}如果缺省的时间单位为 ns 且代码中没有使用
sc_stop()
,则仿真进行 500ns如果
sc_start()
的参数为空,则仿真进行到遇到sc_stop()
SystemC 调度器
sc_start()
函数激活调度器,第一个工作是对进程的初始化。
调度器控制:
- 仿真时序
- 进程的执行顺序
- 处理仿真过程中的事件
- 更新信号的值
SystemC 调度器也是基于 Delta 周期的,一个 Delta 周期包括求值和更新两个阶段。
SystemC 模块
模块是最基本的单位,包含一些其他元素如:端口、内部信号、内部数据、子模块、进程、构造函数和析构函数等。这些元素共同定义模块所表达的功能。
使用关键字 SC_MODULE
来声明一个模块,也可以用 C++ 的类来定义模块。
模块的端口
模块间的端口使数据能够在模块间通过,模块之间通过信号将端口连接起来。
端口分为三种类型:
- in
- out
- inout
如果需要将某一端口的数据赋给模块自身的其他信号,那么该端口就应该是 inout 类型。
你也可以指定端口的数据类型,允许的数据类型包括C++基本数据类型如 bool、int、short、char 等或者是 SystemC 专有数据类型如 sc_int、sc_unit、sc_logic 等或用户定义的任何数据类型。
下面是定义端口的示例:
1 | sc_in<packet> pkt_in; // 一个输入端口 |
抽象端口
SystemC 为了支持交易级建模,还支持抽象端口,示例:
1 | class direct_if : public virtual sc_interface |
1 | sc_port<direct_if> arbiter_port; // 定义类一个抽象端口 |
端口的读写
1 | // 定义输入端口 |
1 | // 定义输出端口 |
端口和信号的多驱动处理
0 | 1 | Z | X | |
---|---|---|---|---|
0 | 0 | X | 0 | X |
1 | X | 1 | 1 | X |
Z | 0 | 1 | Z | X |
X | X | X | X | X |
普通的信号是不允许多驱动的。SystemC 中引入了解析逻辑向量信号(Resolved LogicalVector signal)来解决多驱动的问题。可以使用下面的方法定义解析型端口:
1 | sc_in_rv<n> x; // n 比特宽的解析逻辑向量型输入端口 |
信号和变量
信号不能用 in、out 或 inout 来声明,信号的传输方向取决于连接部分的端口状态。
信号常常被用来连接模块和用于进程间通信,变量则用于进程和模块的本地存储。
变量仿真的赋值是立刻发生的,没有 delta 延时;而信号和端口的值刷新要经过一个 delta 延时。
信号应常被综合为逻辑块间的连线;变量常被综合为逻辑块,可以是组合或者时序逻辑。
信号和端口的关联
关联(Association)基本等于连接(Connect),也成为了绑定(Bind)-
关联分为位置关联和名字关联。
1
2sc_signal<bool> a, b, f;
sc_clock clk("Clk", 20, SC_NS);位置关联:按照端口定义的顺序一一对应
适合少量端口的模块,但在大量端口的模块中非常危险,因为可能不经意间修改了端口顺序1
2
3
4
5
6
7
8
9
10nand2 N2("Nand2");
N2.A(a);
N2.B(b);
N2.F(f);
tb tb1("tb");
tb1.clk(clk);
tb1.a(a);
tb1.b(b);
tb1.f(f);名字关联:按照名字一一对应
对于大的 SystemC 项目,一般建议统一使用名字关联1
2
3
4
5nand2 N2("Nand2");
N2(a, b, f);
tb tb1("tb");
tb1(clk, a, b, f);
构造函数
使用 SC_CTOR
标识,构造函数的名字必须与模块的名字相同,用于初始化进程的类型并创建进程的敏感表。
1 | SC_MODULE(Tb) |
时钟模型
在 SystemC 中,时钟被作为一个特殊的对象处理,它就是 sc_clock
类。
时钟端口作为一个特殊的端口,如:
1 | sc_in_clk clk1; |
在 SystemC2.0.1 中,sc_clock 一共有 6 个重载的构造函数,如:
1 | sc_clock(sc_module_name name, const sc_time& period, double duty_cycle = 0.5, |
定义实例:
1 | sc_clock clk1("clk1", 20, 0.5 5, true); |
1 | sc_clock clk2("clk2", 20, 0.5, 0, true); |
另一种定义时钟的办法:
1 | sc_signal<bool> clock; |
采用这种方法初始化时钟的好处是可以同时插入对其他信号的初始化:
1 | clock = 1; |
等价于:
1 | sc_clock clk("main clock", 10, 0.5); |
时间单位
时间单位 | 英文 | 时间长度(秒) |
---|---|---|
SC_SEC | Second | 1 |
SC_MS | Millisecond | 10-3 |
SC_US | Microsecond | 10-6 |
SC_NS | Nanosecond | 10-9 |
SC_PS | Picosecond | 10-12 |
SC_FS | Femtosecond | 10-15 |
时间模型
SystemC 采用基于整数的时间模型,系统时间采用一个 64 位无符号整数来表示。
时间分辨率是仿真系统能够处理的时间的最小精度,比时间分辨率更精细的时间将被四舍五入。
假设系统的时间分辨率为 10ps,则 wait(33.667, SC_NS)
实际上等效于 wait(33.67, SC_NS)
。
SystemC 缺省时间分辨率为 1ps,同时提供了 sc_set_time_resolution(double, sc_time_unit)
函数来修改系统的时间分辨率。如下面的代码将系统的时间分辨率设置为 10ps:
1 | sc_set_time_resolution(10, SC_PS); |
SystemC 缺省时间单位是 SC_NS,同时允许通过 sc_set_default_time_unit(double, sc_time_unit)
来修改缺省的时间单位。如下面的代码将时间单位设置为 100ps:
1 | sc_set_default_time_unit(100, SC_PS); |
SystemC 对时间分辨率和时间单位的设置有以下的要求:
- 必须是 10 的幂
- 只能在仿真开始之前设置
- 只能设置一次
- 时间单位必须大于等于时间分辨率
- 时间分辨率必须在任何的非零的
sc_time
声明之前设置
在时间单位设置为 100ps
的情况下,下面的 clk1 的周期为 100*100ps=1ns
:
1 | sc_clock clk1("clk1", 10); |
数据类型
基本数据类型
类型名 | 类型说明 |
---|---|
sc_bit | 2 值比特数据类型 |
sc_logic | 4 值比特数据类型 |
sc_int | 1 到 64 比特有符号整型数据类型 |
sc_uint | 1 到 64 比特无符号整型数据类型 |
sc_bigint | 任意宽度的有符号整型数据类型 |
sc_biguint | 任意宽度的无符号整型数据类型 |
sc_bv | 任意宽度的 2 值比特向量数据类型 |
sc_lv | 任意宽度的 4 值比特向量数据类型 |
sc_fixed | 模板类有符号定点数据类型 |
sc_ufixed | 模板类无符号定点数据类型 |
sc_fix | 非模板类有符号定点数据类型 |
sc_ufix | 非模板类无符号定点数据类型 |
四值逻辑 sc_logic
数字系统中最常见的四个逻辑为:
值 | 表示 | 描述 |
---|---|---|
0 | SC_LOGIC_0 | 逻辑低电平 |
1 | SC_LOGIC_1 | 逻辑高电平 |
Z | SC_LOGIC_Z | 高阻态 |
X | SC_LOGIC_X | 不定值 |
可以对 sc_bit
/ sc_logic
类型进行赋值。
在进行代数操作时 sc_bit
可与 C++ 的 bool 类型混合使用,但推荐的做法是多使用 bool 型。
sc_bit
只有 0 和 1 两个值。
sc_logic
数据类型比 sc_bit
多两个值 X 和 Z,它所支持的运算与 sc_bit
一样,如下:
- 位操作
- 与
&
- 或
|
- 异或
^
- 取反
~
(没有取反赋值)
- 与
- 赋值操作
- 与赋值
&=
- 或赋值
|=
- 异或赋值
^=
- 直接赋值
=
- 与赋值
- 逻辑操作
- 等于
==
- 不等于
!=
- 等于
任意宽度整型 sc_int
SystemC 中引入了 sc_int<W>
和 sc_uint<W>
来实现 1 到 64 比特中任意宽度的整型数据类型,W<=64。以及 sc_bigint
和 sc_biguint
来实现任意宽度的整型操作。
1 | sc_int<34> a; // 34 位有符号整数型 |
除最基本操作外,还支持以下操作:
操作 | 语法 | 说明 |
---|---|---|
串联 | (, ) |
(a,b) 将 a 和 b 串联起来构造更大的数 |
范围选择 | range(left, right) |
a,range(x, y) 选择了 a 的右数第 y+1 到第 x+1 位。Y 可以是 0 |
位选择 | [x] |
a[x] 选择了 a 的右数第 x+1 位 |
自动增加 | ++ |
|
自动减少 | -- |
|
位减操作 | 如下表 |
位减操作:
语法 | 说明 |
---|---|
a.and_reduce() |
返回 a 的所有位相与后得到的 bool 型数 |
a.nand_reduce() |
返回 a 的所有位相与后取反得到的 bool 型数 |
a.or_reduce() |
返回 a 的所有位相或后得到的 bool 型数 |
a.nor_reduce() |
返回 a 的所有位相或后取反得到的 bool 型数 |
a.xor_reduce() |
返回 a 的所有位相异或后得到的 bool 型数 |
1 | sc_int<4> x, y; |
1 | sc_biguint<128> b1; |
当一个无符号整数 sc_uint<M>
被赋值给有符号整数 sc_int<N>
时,uint 首先被扩展为 64 位(高位直接填零),然后从低位开始取 N 位赋值给 sc_int。
当 sc_int 被赋值给 sc_uint 时,系统首先将它按符号(负数高位填 1,正数填 0)扩展为 64 位,然后从低位开始取 M 位赋值给 sc_uint。
用户自定义类型
1 |
|
定点数据类型
四种基本定点数据类型:
sc_fixed
sc_ufixed
sc_fix
sc_ufix
sc_fixed
和 sc_ufixed
的参数是静态的,在程序中设定后不能再修改,而 sc_fix
和 sc_ufix
的参数是非静态的,其字长和整数部分长度可以是变量。
定点数据类型的定义方法如下:
1 | sc_fixed<wl, iwl, q_mode, o_mode, n_bits> x; |
- wl:字长,总比特数,必须大于 0
- iwl:整数部分字长,可以是正数、负数,也可以大于总字长
- q_mode:量化模式。超出精度时根据量化模式对尾数进行取舍
- o_mode:溢出模式。超出范围时根据溢出模式对数据进行处理
- n_bits:饱和比特的尾数。仅用于具有饱和行为的溢出模式下饱和比特的位数
量化模式:
量化模式 | 含义 |
---|---|
SC_RND | 向正无穷舍入 |
SC_RND_ZERO | 向 0 舍入 |
SC_RND_MIN_INF | 向负无穷舍入 |
SC_RND_INF | 向无穷舍入 |
SC_RND_CONV | 收敛舍入 |
SC_TRN | 删除舍入 |
SC_TRN_ZERO | 向 0 删除舍入 |
定点数据类型的量化示例:
1 | sc_fixed<4, 2> x; |
x 的值:二进制为 01.01,十进制为 1.25
y 的值:二进制为 01.0,十进制为 1.0,q=0.5,x=2.5q
饱和模式:
溢出模式 | 意义 |
---|---|
SC_SAT | 饱和为最大最小值 |
SC_SAT_ZERO | 饱和为 0 |
SC_SAT_SYM | 对称饱和 |
SC_WRAP | 循环饱和 |
SC_WRAP_SM | 符号幅度循环饱和 |
定点数据类型饱和示例:
1 | sc_fixed<4, 4> x; |
x 的值:二进制为 0101,十进制为 5
y 的值:二进制为 011,十进制为 3,q=1
SystemC 的进程
在 SystemC 中,进程是程序在并发环境中的执行过程,也是一个基本执行单位,具有动态性、并发性、独立性、异步性和结构性五大特征。
基本进程有三种:
SC_METHOD
SC_THREAD
SC_CTHREAD
SC_SLAVE
(在 Master/Slave 库中定义的第四种进程类型)
进程不是层次化的,不能包含或直接调用其它进程,但可以调用非进程的函数和方法。
进程通常会有一个敏感表,当在敏感表中的信号上有事件发生时,进程就会被激活。信号上的事件是指信号的值的变化,如时钟的上升沿就是时钟信号从 0 变为 1。当信号上的事件发生,所有对该事件敏感的进程对会被激活。
进程的敏感表在模块的构造函数内设定。
方法进程 SC_METHOD
方法进程 SC_METHOD
是唯一的可以综合的寄存器传输级(RTL)进程。
特点是当敏感表上有事件发生,它就会被调用,调用后应该立刻返回。只有该类进程返回后仿真系统的事件才有可能前进,因此该类进程中不能使用 wait()
这样的语句。
示例:全加器
1 |
|
线程进程 SC_THREAD
特点是它能够被挂起和重新激活。线程进程使用 wait()
挂起,当敏感表中有事件发生,线程进程被重新激活运行到遇到新的 wait()
语句再重新挂起。
线程进程不是寄存器传输级进程,一个方便的用途就是用来描述验证平台的输入激励和输出获取。
显示全加器的输入和输出结果
1 | SC_MODULE(Monitor) |
当敏感表中的信号至少有一个值发生变化时,prc_monitor
就会被激活显示这时全加器的输入和输出结果。
钟控线程进程 SC_CTHREAD
SC_CTHREAD
继承于 SC_THREAD
,只能在时钟的上升沿或者下降沿被触发或者激活,这种行为更加接近实际硬件的行为。
SC_CTHREAD
的敏感表与其他类型的线程不同,它必须在指定线程名字的同时指定时钟和它的边沿。
产生全加器的激励
1 | SC_MODULE(Driver) |
有限状态机
通常意义上的有限状态机要明确定义系统的状态,一般要使用 case 语句来实现状态转移。
隐式有限状态机
指编程中不现实定义状态机的状态,而是通过程序中的 wait()
语句和 wait()
语句中间的赋值语句来完成对状态机的描述。
钟控线程进程最适合来描述隐式有限状态机。
挂起
wiat_until()
将进程挂起直到指定的表达式的值为真,只能用于线程进程和钟控线程进程。
1 | wait_until(data.delayed() == true); |
该语句中的 delayed()
是必须的!
参数必须是 bool
型,如:
1 | wait_until(clock.delayed()== true |
wait()
wait()
:等待敏感表中有事件发生wait(const sc_event&)
:等待事件发生1
2sc_event e1;
wait(e1);wait(sc_event_or_list&)
:等待事件之一发生1
2sc_event e1, e2, e3;
wait(e1 | e2 | e3);wait(sc_event_and_list&)
:等待事件全部发生
next_trigger()
只能用于 SC_METHOD
类进程。
参数与 wait()
的参数相同,只是分别用于不同类型的进程。
next_trigger()
调用后进程立即返回。
watching()
只能用于 SC_CTHREAD
进程。
SC_CTHREAD
进程中通常有一个死循环,但有时候需要初始化一些变量和信号,或者当某些条件满足的时候能够让进程从循环中跳出来。使用 watching
结构可以跳出循环。
watching
结构会不停地监视某一个条件,一旦该条件发生,则 SC_CTHREAD
进程就会跳出循环从进程的开始处重新执行。
1 | SC_MODULE(Driver) |
局部 watching()
在前面的 watching 中,当 watching 的条件成立的时候,整个进程就会重新开始运行,但有的时候会只需要局部代码重新运行,这时候就需要使用局部 watching。
dont_initialize()
dont_initialize()是希望进程在仿真的0时不被执行,sensitive是敏感表。
仿真与波形跟踪
支持以下三种标准的波形格式:
- VCD (Value Change Dump)
- WIF (Waveform Intermediate Format)
- ISDB (Integrated Signal Data Base) (可能会被淘汰)
只有在整个仿真期间都存在的信号和变量才能被跟踪,这与多数仿真器是一样的。它能够保证模块中的所有信号和数据成员都被跟踪。函数的本地变量只有在函数被调用期间才存在,所以不能跟踪。
任何类型的信号和变量包括标量、数组和其它聚合类型(如结构 struct 类型)都能被跟踪。
不同格式的波形文件可以在同一次仿真过程中同时产生,任何一个信号和变量都可以在不同格式的波形文件中不限制次数的被跟踪。
创建和关闭波形跟踪文件
以创建 vcd 波形文件为例。下面代码生成 Wave.vcd
文件:
1 | sc_trace_file* my_trace_file; |
sc_main()
函数调用 return
之前必须关闭波形文件:
1 | sc_close_vcd_trace_file(my_trace_file); |
跟踪标量型变量和信号
创建了波形跟踪文件后,还必须告诉 SystemC 调度器到底要跟踪那些信号和变量以及被跟踪的信号和变量在波形文件中保存的名字:
1 | sc_in<int>datain; |
trace()
函数只能在所有的信号和模块已经例化、波形跟踪文件已经产生后才能调用。
跟踪聚合型变量和信号
sc_trace()
函数只能跟踪标量类型的信号和变量,为了跟踪聚合类型的变量和信号,你需要重载 sc_trace
函数。所谓聚合类型可以是数组、向量和结构等。
假设我们在设计中定义了下面的结构:
1 | struct packet |
为了跟踪 packet,我们要重载 sc_trace()
函数。
1 | void sc_trace(sc_trace_file *tf, cont packet& v, const sc_string& name) |
行为建模
系统抽象的三个关键元素:
- 行为:算法(运算、控制……)
- 通信:各个算法模块之间的数据交互,控制配合
- 时序:行为和通信在时间域上的协调
在 SystemC 中,模块是行为的主要载体,通道是通信的主要载体,时序隐含在模块和通道的描述中。
- 行为和通信分开
- 支持接口方法调用
端口
端口连接模块内的进程(行为)和通道(通信)。
基本的 SystemC 端口类型:
sc_in<T>
sc_out<T>
sc_inout<T>
为了满足行为建模的需要,允许用户自定义端口类型:
1 | sc_port<InterfaceType, ChannelNumber = 1> |
一些端口定义的示例:
1 | sc_port<ram_if> ram_port1; // 连接到一个RAM上 |
可以通过 ram_port0.size()
得到实际连接到 ram_port0 的通道 RAM 的数量。
端口必须与特定的通道接口相连,或者同父模块的端口相连。一个模块的端口连接到零个、一个或者多个通道,或者零个、一个或者多个父模块的端口,但必须至少连接到1个通道或者父模块的端口上。
类 sc_port<IF,N>
是所有端口的基类,它是一个模板类。IF
是接口类型,N
是所连接的同一类型的通道数目,也就是接口数,它的缺省值是 1
。
示例:RAM读写端口
1 | SC_MODULE(Master) |
通道
在SystemC中,接口本身只是定义了一组通信方法,而不具体负责这些方法如何实现。通道才是这些接口方法的实现者。
通道可以实现一个或者多个接口,也连接一个或者多个模块。
SystemC中通道分为两种:基本通道和分层通道
- 基本通道不包含任何进程,也不对外展现出任何可见结构,它们也不能够直接的或者间接的调用其它基本通道。
- 分层通道本身是一个模块,可以包含进程、子模块,也可以包含和调用其它通道。
1 | // 端口与通道的关联 |
基本通道
基本通道不包含任何进程,也不对外展现出任何可见结构,它们也不能够直接的或者间接的调用其它基本通道。
SystemC2.01中定义了若干基本通道类型,它们是:
sc_signal<T>
sc_signal_rv<N>
sc_mutex
sc_fifo<T>
sc_semaphore
sc_buffer<T>
sc_signal
sc_signal<T>
是最基本的通道,它用于连接模块的基本端口sc_in<T>
、sc_out<T>
和sc_inout<T>
。- 最多只有一个
sc_out<T>
或者sc_inout<T>
可以连接到sc_signal<T>
,否则就会产生典型的多驱动情况。 - 可以有多个
sc_in<T>
同时连接到sc_signal<T>
。 sc_signal<T>
继承于基本通道类,并实现了sc_signal_inout_if<T>
接口。sc_signal_inout_if<T>
接口的最重要成员函数read()
和write()
。
sc_signal_rt
sc_signal_rv<T>
是所谓“解析的”信号通道,与 sc_signal<T>
的不同之处是它允许同时有多个端口连接到其上并进行写操作。
sc_buffer
sc_buffer<T>
继承于 sc_signal<T>
,并重载了 write()
和 update()
函数。
sc_buffer<T>
不管 write()
写的数据是否与原数据相同,都要求进行数据更新;而 sc_signal<T>
首先要检查新数据是否与原数据相同,如果不同才进行更新。
sc_fifo
sc_fifo<T>
是SystemC核心语言库中已经实现了的 FIFO 通道write(&T)
代表写 FIFO 的方法。read()
是读 FIFO 的方法,它返回队头单元的数据。num_free()
用于查询FIFO还有多少空单元。num_available()
查询FIFO还有多少个数据可以读。Size
代表FIFO的总单元数,对于sc_fifo
,Size
的默认值为16
。
1 | sc_fifo<int> fifo1; // 默认深度为16 |
示例:信源和信宿模块通过FIFO通信
1 |
|
从本例看模块、接口、端口、通道之间的关系:
- 接口是一个C++抽象类,它定义了一组抽象方法,但不定义这些方法的具体实现。
- 通道实现一个或者多个接口。也就是说,通道必须继承一个或者多个接口,这些接口中定义的抽象方法必须在通道中实现。
- 端口总是与一定的接口类型相关联的,端口只能连接到实现了该类接口的通道上。
- 通过端口,模块中的进程可以连接到通道并使用通道提供的方法
sc_semaphore
通常翻译为信号量。
信号量代表可用资源实体的数量,所以可以认为信号量就是一个资源计数器,它限制的是同时使用某共享资源(也称为临界资源)的进程的数量。信号量计数的值代表的就是当前仍然可用的共享资源的数量。
sc_semaphore
实现的是 sc_semaphore_if
接口。
其中,wait()
方法获得一个信号量,其作用效果是获得一份资源的使用权,使信号量计数减一,如下面的实现代码。
sc_mutex
具有锁定和非锁定两种状态。当互斥(器)已经由另外的进程锁定,这时申请互斥的进程就会被阻塞,直到锁定互斥的进程将互斥解锁。
直接通道调用
在同一模块内,各个进程之间也需要通信。它们可以通过共享变量、握手信号、模块内通道等方式通信。如果它们之间的通信是通过模块内通道,则此时需要进行直接通道调用。
使用 sc_mutex
的直接通道调用的实例:
1 | sc_in_clk clk; |
另一种形式:不通过端口直接调用通道实现:
1 | ram<int>* ram0; // 通道实例的指针 |
分层通道
分层通道具有可见结构,可以包含进程,可以直接调用其它通道,它是一个实现了一个或者多个接口的模块。
常见的分层通道有两种:
- 一是在一个通道中直接例化并使用其它通道,被例化的通道可以是分层通道,也可以是基本通道。这种类型的通道比较常见,被称为一般分层通道;
- 二是一个通道利用端口进行间接通道调用,调用穿越了一个以上的通道。被穿过的通道似乎被“合成”到了一起,这种通道是一种特殊的分层通道,称作合成通道(compositechannel)。合成通道间接的体现了通道的层次性。
一般分层通道
凡是例化了其它通道的通道都可以归入一般分层通道之列。如下例:
合成通道
一个模块可以利用端口穿越了一个以上的通道进行间接通道调用。被穿过的通道似乎被“合成“到了一起。
我们假定设计好了一个接口类 GetFIFO_if
,它的一个成员函数 getWriteFIFO()
返回的是一个 FIFO 通道的指针,如返回上节中定义的 tlm_fifo 的指针 tlm_fifo,再假设又我们实现了一个 GetFIFO 通道,data 是一个初始化了的 char 型数据。那么在 Source 模块中就可以通过下面的方式来写 FIFO:
1 | read_port->getWriteFIFO()->write(data); |
交易级建模
SoC设计中的通信体系结构的抽象层次图:
屏蔽的细节 | ||
---|---|---|
L3 | 消息层 | 资源共享,时序 |
L2 | 交易层 | 时钟,协议 |
L1 | 传输层 | 连线,寄存器 |
L0 | 寄存器传输层 | 门,门/连线延时 |