1. SystemC Module
systemC module具有如下特点:
- 是系统层次中的最小组成单元
- 一般是一个c++类,继承自systenC的sc_module类
- 是systemC的绑定块
- 用于表示真实系统中的一个组件
SystemC module具有三种定义方式:
#方式1
SC_MODULE(MODULE_A) {
SC_CTOR(MODULE_A) {
cout << name() << " constructor " << endl;
}
};
#方式2
struct MODULE_B : public sc_module {
SC_CTOR(MODULE_B) {
cout << name() << " constructor " << endl;
}
};
#方式3,该定义方式最接近C++的语法
class MODULE_C : public sc_module {
public:
SC_CTOR(MODULE_C) {
cout << name() << " constructor " << endl;
}
};
2. SystemC SC_CTOR与SC_HAS_PROCESS
每一个module内部必须要有一个构造器
- SC_STOR:若进程不需要额外的参数进行实例化时,采用SC_STOR构造器。
- 若需要额外的参数进行实例化,则有两种方法。一种是采用方式2SC_STOR或方式3的SC_HAS_PROCESS。(在这里更推荐使用SC_HAS_PROCESS方式)
#方式1
SC_MODULE(MODULE_A) {
SC_CTOR(MODULE_A) {
SC_METHOD(func_a);
}
void func_a();
};
#方式2
SC_MODULE(MODULE_B) {
const int i;
SC_CTOR(MODULE_B);
MODULE_B(sc_module_name name, int i) : sc_module(name), i(i) {
SC_METHOD(func_b);
}
void func_b() {
std::cout << name() << ", i = " << i << std::endl;
}
SC_MODULE(MODULE_C ) {
const int i;
SC_HAS_PROCESS(module_c);
module_c(sc_module_name name, int i) : i(i) {
SC_METHOD(func_c);
}
void func_c() {
cout << name() << ", addithonal input argument" << endl;
}
};
};
int sc_main(int, char*[]) {
MODULE_A module_a("module_a");
MODULE_B module_b("module_b",1);
MODULE_C module_c("module_c", 1);
sc_start();
return 0;
}
3. Simulation Process
SC的进程有三种:
- SC_METHOD(func):并没有自己的执行线程,不花费仿真时间,不能暂停也不能调用调用wait函数的代码
- SC_THREAD(func):有自己的执行线程,可能花费仿真时间,可以暂停,也可以调用调用wait函数的代码
- SC_CTHREAD(func, event):一个特殊的SC_THREAD,仅能有一个时钟边沿事件
注意:
- 为了使一个SC_THREAD或者SC_CTHREAD过程被再次调用,需要使用一个while循环以确保其已经释放,并在后面放置一个wait,以遍能够再次触发;如果想让 SC_METHOD再次被调用,则可以采用next_trigger()再次调用
- SC_CTHREAD注册为进程时,需要指明时钟,只有在时钟沿到来时才会执行,不能添加其他敏感列表。
- SC_THREAD和 SC_METHOD在注册的时候会默认的执行一次,如果想让其不执行,则可以在构造时后面添加dont_initialize;SC_CTHREAD则不会在注册时默认执行
SC_CTOR(Process) : clk("clk", 1, SC_SEC) {
SC_METHOD(method);
SC_THREAD(thread);
SC_CTHREAD(cthread, clk);
}
void method(void) {
cout << "method triggered @ " << sc_time_stamp() << endl;
next_trigger(sc_time(1, SC_SEC));
}
void thread() {
while (true) {
cout << "thread triggered @ " << sc_time_stamp() << endl;
wait(1, sc_core::SC_SEC);
}
}
void cthread() {
while (true) {
cout << "cthread triggered @ " << sc_time_stamp() << endl;
wait();
}
}
3.1 Initialization
initialization是execution阶段的一部分,在sc_start()执行后开始,在initialization阶段会按顺序完成以下功能(相当于在一开始就自动跑一遍进程)。
- 执行update阶段,但是并不执行下一个delta阶段
- 将所有的method和thread process实例加入到执行进程集合,除了
- 不需要进行初始化的进程实例
- 时钟进程
- 执行delta notification时,在该阶段的最后,会转变为evaluation阶段
4. Simulation Stage
systemc应用操作一般有三个phases/stages
-
elaboration:主要的目的是创建内部数据结构以支持仿真语义,在elaboration阶段中,module层次中的各个部分(modules,ports,primitive channels,processes)被创建,且ports和exports绑定到对应的channels
-
execution:可进一步划分为两个阶段
- initialization:仿真内核识别所有的仿真过程并将它们放置到一个runnable或waiting process集合,一般来说所有的仿真过程均在runnable集合中,除了其中要求不进行初始化的过程。
- simulation:一般被描述为状态机,负责调度process运行和高级放置时间,包含两个内部阶段
- evaluete:同时运行所有的runnable processes,每个process遇到wait或返回则停止,同时当所有的process运行结束后此阶段停止
- advance-time:当没有runnable processes时,仿真进入此阶段,主要完成三件事:使用事件机制将仿真事件转变为closest time;将等待特定时机的processes移动到runnable set;返回evaluate阶段
-
cleanup or post-processing:销毁对象,释放内存,关闭打开的文件等
在elaboration和simulation的各个阶段中,以下四个回调函数被仿真内核调用,四个函数都是虚类函数,可以根据实际需求定义内部执行过程。对应的声明如下
在module层次构建结束后调用
virtual void before_end_of_elaboration()
在elaboration阶段的最后调用,即before_end_of_elaboration调用完成,且所有的实例化工作完成后,在开始simulation阶段之前
virtual void end_of_elaboration()
当应用第一次调用sc_start或者在simulation阶段的一开始;在end_of_elaboration 调用之后,在调度器调用initialization阶段之前
virtual void start_of_simulation()
当调度器由于sc_stop停止时或者在simulation阶段的最后;若sc_stop被调用多次,仅在第一次调用
virtual void end_of_simulation()
实例代码
SC_MODULE(Stage) {
SC_CTOR(Stage) {
cout << sc_time_stamp() << ": Elaboration: constructor" << endl;
SC_THREAD(thread);
}
~Stage() {
cout << sc_time_stamp() << ": Cleanup: desctructor" << endl;
}
void thread() {
cout << sc_time_stamp() << ": Execution.initialization" << endl;
int i = 0;
while (true) {
wait(1, SC_SEC);
cout << sc_time_stamp() << ": Execution.simulation" << endl;
if (++i >= 2) {
sc_stop();
}
}
}
void before_end_of_elaboration() {
cout << "before end of elaboration" << endl;
}
void end_of_elaboration() {
cout << "end of elaboration" << endl;
}
void start_of_simulation() {
cout << "start of simulation" << endl;
}
void end_of_simulation() {
cout << "end of simulation" << endl;
}
};
5. Time Notation
- 时间类型:SC_SEC, SC_MS, SC_US, SC_NS, SC_PS, SC_FS
- 获取时间:sc_time_stamp()
- 设置仿真精度 sc_set_time_resolution(1, SC_FS);
- 设置默认的时间单位sc_set_default_time_unit(1, SC_SEC);
- 对时间进行转换:to_default_time_units(),此时会把时间转换为设置的默认时间单位;to_seconds()将时间转换为秒
//eg
sc_time(1, SC_MS).to_default_time_units() ;
sc_time_stamp().to_seconds();
6. Event&Combined Events
6.1 Event
SystemC中event是类sc_event的一个对象,用于进程间同步。
一个进程的实例可能由event并发触发或唤醒,即当事件通知时,可以触发或唤醒一个仿真进程。
一个sc_event具有以下方法:
- void notify():创建一个即时通知 void notify(const sc_time&)void
notify(double, sc_time_unit) - zero time:构建一个delta通知 non-zero time:在给定的时间创建一个时间通知,一般是相对于notify调用时的仿真时间
- cancel():删除本event的任意挂起的通知
- 任意给定事件最多可以存在一个挂起的通知
- 即时通知不能被cancel
- 当创建了一个通知时间更早的通知,通知时间更晚的通知会被自动取消,哪怕这个通知时间创建的更早
- 一个即时通知相比于一个delta通知优先级更高,一个delta相比于时间通知优先级高。这不考虑函数通知的调用顺序
- cancel的优先级更好,比如设定在4sec出发起一个通知,但是如果存在当到达4sec时,取消发起的通知,那么这个通知就不会发出。
SC_MODULE(Event) {
sc_event e;
SC_CTOR(Event) {
SC_THREAD(trigger);
SC_THREAD(catcher);
}
void trigger() {
while (true) {
e.notify(1, sc_core::SC_SEC);
if (sc_time_stamp() == sc_time(4, SC_SEC)) {
e.cancel();
}
wait(2, SC_SEC);
}
}
void catcher() {
while (true) {
wait(e);
cout << "Event cateched at " << sc_time_stamp() << endl;
}
}
};
6.2 Combined Events
wait函数支持以下形式
wait():等待敏感列表中的事件
wait(e1):等待event e1
wait(e1|e2|e3):等待事件e1,e2 or e3
wait(e1&e2&e3):等待事件e1,e2 and e3
wait(200, SC_NS):等待200ns
wait(200, SC_NS, e1):等待event e1,最多等待200ns
wait(200, SC_NS, e1|e2|e3):等待event e1,e2 or e3,最多等待200ns
wait(200, SC_NS, e1&e2&e3):等待e1,e2 and e3,最多等待200ns
wait(sc_time(200, SC_NS)):等待200ns
wait(sc_time(200, SC_NS), e1):等待event e1,最多等待200ns
wait(sc_time(200, SC_NS), e1|e2|e3):等待event e1,e2 or e3,最多等待200ns
wait(sc_time(200, SC_NS), e1&e2&e3):等待e1,e2 and e3,最多等待200ns
wait(200):等待200个时钟周期,仅限于SC_CTHREAD(SystemC 1.0)
wait(0, SC_NS):等待一个delta 周期
wait(SC_ZERO_TIME):等待一个delta周期(常用于获取最新的赋值后的变量)
6.3 Event Queue
事件队列(event queue)基础知识
- 有成员函数notify,同event一样
- 是一个层次化的通道,可以有多个挂起的通知,即通知不会被覆盖,这不同于一个event
- 仅能在elaboration阶段构建
- 不支持即时通知(immediate notification)
成员函数
- void notify(double, sc_time_unit) or void notify(const sc_time&):
- SC_ZERO_TIME:指一个delta notification
- non-zero time:相对于调用时仿真时间的notification
- void cancel_all():立即删除所有的挂起notification,包括delta和timed notification
void trigger() {
while (true) {
//e只会在1秒后发起通知
e.notify(2, sc_core::SC_SEC);
e.notify(1, SC_SEC);
//eq会分别在一秒和两秒后发起通知
eq.notify(2, SC_SEC);
eq.notify(1, sc_core::SC_SEC);
wait(10, SC_SEC);
}
}
6.4 Combined Event Queue
多个事件队列可以使用OR来构成进程的静态敏感表,但是不能使用AND
事件队列不能作为wait的输入,因此不能用于动态敏感表
SC_MODULE(CombinedQueue) {
sc_event_queue eq1, eq2;
SC_CTOR(CombinedQueue) {
SC_THREAD(trigger);
SC_THREAD(catcher);
sensitive << eq1 << eq2;
dont_initialize();
}
void trigger() {
eq1.notify(1, sc_core::SC_SEC);
eq1.notify(2, sc_core::SC_SEC);
eq2.notify(2, sc_core::SC_SEC);
eq2.notify(3, sc_core::SC_SEC);
}
void catcher() {
while (true) {
cout << sc_time_stamp() << ": catches trigger" << endl;
wait();
}
}
};
7. Sensitivity
SystemC有两种敏感表
- Static Sensitivity:静态敏感在elaboration阶段确定下来,每个module的每个进程可有自己的敏感表。
- Dynamic Sensitivity:动态敏感表随着时间变化,由进程自己控制,thread使用wait(),method则使用next_trigger()
8.Clock
sc_clock是一个预定义的基本通道,派生自sc_signal类,用于对数字时钟信号进行建模。可以通过sc_signal_in_if接口访问时钟的值和事件
构造器如下:
sc_clock(
constchar*name_, //信号名
double period_v_, // 指定时钟的周期,即连续上升沿(即从假转为真的转变)之间以及连续下降沿(即从真转为假的转变)之间的时间间隔。该值必须大于零,默认情况下是1纳秒
sc_time_unit period_tu_, //周期的时间单位,与period_v_结合使用,定义了周期的确切时间长度。
double duty_cycle_, // 占空比,指定在一个周期内,时钟信号为真(高电平)的比例。值域在0.0到1.0之间,但不包括0.0和1.0。默认值为0.5,即高电平和低电平各占一半的周期。
double start_time_v_, // 时钟的第一次跳变(从假转为真或从真转为假)的绝对时间。默认值为零。
sc_time_unit start_time_tu_,//第一次跳变的时间单位,与start_time_v_结合使用,定义了第一次跳变的确切时间点。
bool posedge_first_ = true ); //如果为真,则时钟初始化为假(低电平),并在开始时间点跳变为真(高电平)。如果为假,则相反。默认值为真。
示例代码如下
#include "sysc/communication/sc_clock.h"
#include "sysc/communication/sc_port.h"
#include "sysc/kernel/sc_externs.h"
#include "sysc/kernel/sc_module.h"
#include "sysc/kernel/sc_module_name.h"
#include "sysc/kernel/sc_sensitive.h"
#include "sysc/kernel/sc_simcontext.h"
#include "sysc/kernel/sc_time.h"
#include <systemc>
using namespace sc_core;
using namespace std;
class Clock : public sc_module {
public:
SC_HAS_PROCESS(Clock);
sc_port<sc_signal_in_if<bool>> clk;
Clock(const sc_module_name& name) {
SC_THREAD(thread);
sensitive << clk;
dont_initialize();
}
void thread() {
while (true) {
cout << sc_time_stamp() << ", value = " << clk->read() << endl;
wait();
}
}
};
int sc_main(int argc, char **argv){
sc_clock clk("clk", 10, SC_SEC, 0.2, 10, SC_SEC, false);
Clock clock("clock");
clock.clk(clk);
sc_start(31, SC_SEC);
return 0;
}
9. Mutex
mutex基础知识
- 是一个预定义的通道,用于模拟互斥锁,以控制并发进程访问共享资源
- 有两个状态:unlocked or locked
- 同一时间仅有一个进程可以给互斥锁上锁
- 当一个进程给互斥锁上锁时,该锁必须是unlocked状态。当再次转变为unlocked状态时,互斥锁可能由另一个不同的进程再次上锁。
成员函数
- int lock()
- 若互斥锁为unlocked状态,应上锁并返回
- 若互斥锁为locked状态,应将上锁动作挂起,直到该锁转变为unlocked状态(会在unlocked后的下一个周期,自动调用这个进程对互斥锁上锁,并且不会处理进程后续的操作,直到该锁转变为unlocked状态)
- 若多个进程试图在相同的delta周期给互斥锁上锁,最终成功上锁的对象是不确定的
应无条件的返回0
- int trylock()
- 若互斥锁为unlocked状态,该函数会上锁并返回0(该函数也有上锁功能)
- 若互斥锁为locked状态,该函数应立即返回-1。互斥锁继续保持locked状态
- int unlock()
- 若互斥锁为unlocked状态,该函数应立即返回-1。互斥锁应保持unlocked
- 若互斥锁被进程实例上锁,而不是调用进程,unlock()应返回-1,互斥锁继续保持locked状态(这个保证了只有给互斥锁上锁的进程才能对该互斥锁解锁,其他进程尝试解锁就会返回-1)
- 若互斥锁被调用进程上锁,成员函数可对该互斥锁解锁,并返回0
immediate notification被用于向其它进程发送解锁行为信号
SC_MODULE(Mutex) {
sc_mutex m;
SC_CTOR(Mutex) {
SC_THREAD(thread_1);
SC_THREAD(thread_2);
}
void thread_1() {
while (true) {
if (m.trylock() == -1) {
m.lock();
cout << sc_time_stamp() << ": thread_1 obtained resource by lock()" << endl;
} else {
cout << sc_time_stamp() << ": thread_1 obtained resource by trylock()" << endl;
}
wait(1, sc_core::SC_SEC);
m.unlock();
cout << sc_time_stamp() << ": unlocked by thread_1" << endl;
wait(SC_ZERO_TIME);//这一行必须要加,否则解锁后在同一个delta时间内又会回到开头的if判断,然后对互斥锁上锁,这会导致thread_2永远无法获得锁
}
}
void thread_2() {
while (true) {
if (m.trylock() == -1) {
m.lock();
cout << sc_time_stamp() << ": thread_2 obtained resource by lock()" << endl;
} else {
cout << sc_time_stamp() << ": thread_2 obtained resource by trylock()" << endl;
}
wait(1, sc_core::SC_SEC);
m.unlock();
cout << sc_time_stamp() << ": unlocked by thread_2" << endl;
wait(SC_ZERO_TIME);
}
}
};
10. Semaphore
semaphore基础知识(信号量)
- 是一个预定义的通道用于对软件信号量进行建模,软件信号量一般用于限制并发访问共享资源
- 有一个整数值,该值被设置为指定允许并发的数量,该值在构造时赋给信号量 若初始值为1,则信号量和互斥锁逻辑上想等
成员函数
- int wait()
- 若一个信号量的值大于0,wait()应对该值减一,并返回
- 若信号量的值等于0,wait()应挂起,直到信号量值增加应无条件返回0
- int trywait()
- 若信号量的值大于0,应对该值减一,并返回0
- 若信号量的值等于0,应立即返回值-1,不改变信号量的值
- int post()
- 应增加信号量的值
- 应使用immediate notification来通知等待进程正在进行增加信号量值的动作
- 应无条件返回0
- int get_value()
- 应返回信号量的真值
while (true) {
if (s.trywait() == -1) {
s.wait();
}
cout << sc_time_stamp() << ": locked by thread_1, value is " << s.get_value() << endl;
wait(1, sc_core::SC_SEC);
s.post();
cout << sc_time_stamp() << ": unlocked by thread_1, value is " << s.get_value() << endl;
wait(SC_ZERO_TIME);
}
}
void thread_2() {
while (true) {
if (s.trywait() == -1) {
s.wait();
}
cout << sc_time_stamp() << ": locked by thread_2, value is " << s.get_value() << endl;
wait(1, sc_core::SC_SEC);
s.post();
cout << sc_time_stamp() << ": unlocked by thread_2, value is " << s.get_value() << endl;
wait(SC_ZERO_TIME);
}
}
11. FIFO
sc_fifo基础知识
- 有一定数量的slot用于存储值,slot的数量在对象构造是确定
- 实现了sc_fifo_in_if<T>接口和sc_fifo_out_if<T>接口
构造器
- 显式调用sc_fifo(const char* name_, int size_ = 16):调用基本构造器,初始化列表为:sc_prim_channel(name_)
用于读的成员函数
- void read(T&), T read():
- 返回最近写入fifo的值,并将其移出fifo
- 从fifo中读取数据的顺序会精确匹配值写入fifo的顺序
- 当前delta周期写入FIFO的值,在当前delta周期不能读取,但是在下一个delta周期可读取。
- 若fifo为空,读操作会挂起,直到数据写入事件发生
- bool nb_read(T&):
- nb_read具有和read相同的前三个特性
- 若fifo为空,成员函数nb_read应立刻返回,同时并不改变fifo的状态,也不会调用request_update,返回值为false。除此之外,若fifo中有一个值可用于读,nb_read的返回值为true
用于写的成员函数
- write(const T&):
- 以参数形式写入,并传递到fifo中
- 可能在单个delta周期中写入多个值
- 若在当前周期中从fifo中读取值,读取后的空槽(empty slot)在当前周期中不可用,在下个delta周期中才可用
- 若fifo为满,write会挂起,直到data-read事件通知
- bool nb_write(const T&):
- 前三个特点同write相同
- 若fifo为满,nb_write() 应立即返回,并且不改变fifo的状态,不调用request_update,返回false。除此之外,nb_write返回值应为true
用于event的成员函数
- sc_event& data_written_event():应返回data-written event的引用,这会在delta周期的最后notification阶段返回
- sc_event& data_read_event():应返回data-read event的引用,这会在delta周期的最后notification阶段返回
用于获取值和释放slot的成员函数
- int num_available():返回当前delta周期已用的slot的数,应减去本delta周期读取的值的数量,且不加入本周期写入的值的数量
例如,如果FIFO中原本有3个数据项,而在当前delta周期有一个数据项被读取,那么
num_available()将返回2。如果在同一个delta周期内还有一个数据项被写入,此时
num_available()仍然返回2,因为新写入的数据只能在下一个delta周期被读取。
- int num_free():返回当前delta周期可用的空slot的数量,应减去本周期写入的slot的数量,且不加上本周期通过读释放的slot的数量
如果FIFO的总大小是5,当前有2个数据项,那么开始时num_free()将返回3。
如果在当前delta周期内写入了一个新的数据项,那么num_free()将返回2,
即使在同一delta周期内有数据项被读取并释放了一个槽位,
该释放的槽位也不会在num_free()的返回值中反映出来。
12.Signal
12.1 read and write
sc_signal基础知识
- 是一个预定义的通道,用于对单根携带数字信号的导线进行建模仿真
- 使用evaluete-update机制确保仿真时读写动作的确定行为。SystemC维护了当前值和新值。
- 其write()方法会发出一个update请求(若新值不同于当前值)
- 实现了sc_signal_inout_if\接口
成员函数
- T& read() or operator const T& ():返回signal当前值的引用,但是并不改变signal的状态
- void write(const T&):更改signal的值,以使其拥有新值,但是同样的,会在下一个delta周期实现
- sc_event& default_event(), sc_event& value_changed_event():返回一个value-change event的引用
- bool event():当且仅当signal的值在当前delta周期的update阶段发生变化时,返回true
12.2 detect event
sc_event& default_event, sc_event& value_changed_event():返回一个value-changed event的引用
bool event():当且仅当signal的值在当前delta周期的update阶段发生变化时,返回true
注:需要确保对应信号已经被加入到敏感列表了,否则无法触发
SC_MODULE(SignalEvent) {
sc_signal<int> s1, s2;
SC_CTOR(SignalEvent) {
SC_THREAD(producer1);
SC_THREAD(producer2);
SC_THREAD(consumer);
sensitive << s1 << s2;
dont_initialize();
}
void producer1() {
int v = 1;
while (true) {
s1.write(v++);
wait(2, sc_core::SC_SEC);
}
}
void producer2() {
int v = 1;
while (true) {
s2 = v++;
wait(3, sc_core::SC_SEC);
}
}
void consumer() {
while (true) {
if (s1.event() == true && s2.event() == true) {
cout << sc_time_stamp() << ": s1 & s2 triggered" << endl;
} else if (s1.event() == true) {
cout << sc_time_stamp() << ": s1 triggered" << endl;
} else {
cout << sc_time_stamp() << ": s2 triggered" << endl;
}
wait();
}
}
};
12.3 many writers
sc_signal类定义如下
template <class T, sc_writer_policy WRITER_POLICY = SC_ONE_WRITER> class sc_signal: public sc_signal_inout_if<T>, public sc_prim_channel {}
- 若WRITER_POLICY=SC_ONE_WRITER,多个进程实例在仿真阶段写给定的信号,会报错
- 若WRITER_POLICY == SC_MANY_WRITERS
- 在任何给定的evaluation 阶段(即同一个delta周期内),超过一个进程实例写给定的信号会报错
- 但是不同进程实例可以在不同的delta周期写给定信号
因此,默认的sc_signal仅有一个writer,当时设置为SC_MANY_WRITERS时,这些writer可在不同的时间写signal
SC_MODULE(Multi) {
sc_signal<int> s1;
sc_signal<int, SC_MANY_WRITERS> s2;
SC_CTOR(Multi) {
SC_THREAD(writer1);
SC_THREAD(writer2);
}
void writer1() {
int v = 1;
while (true) {
s1.write(v);
s2.write(v);
cout << sc_time_stamp() << ": writer1 writes " << v++ << endl;
wait(1, sc_core::SC_SEC);
}
}
void writer2() {
int v = -1;
while (true) {
wait(SC_ZERO_TIME);
s2.write(v);
cout << sc_time_stamp() << ": writer2 writes " << v-- << endl;
wait(1, sc_core::SC_SEC);
}
}
}
13. sc_signal
sc_signal_in_if\ 和 sc_signal_in_if<:sc_logic>是提供额外成员函数的接口,适用于二值信号,sc_signal实现了以下函数
- posedge_event():返回一个event的引用,该event在通道值改变时通知,通道的新值为true或1
- negedge_event():返回一个event的引用,该event在通道值改变时通知,通道的新值为false或0
- posedge():当且仅当通道的值在当前仿真时间的即时delta周期的update阶段改变值时返回true,且要求新值为true或1
- negedge():当且仅当通道的值在当前仿真时间的即时delta周期的update阶段改变值时返回true,且要求新值为false或0
SC_MODULE(SignalBool) {
sc_signal<bool> b;
SC_CTOR(SignalBool) {
SC_THREAD(writer);
SC_THREAD(consumer);
sensitive << b;
dont_initialize();
SC_THREAD(consumer_pos);
sensitive << b.posedge_event();
dont_initialize();
SC_THREAD(consumer_neg);
sensitive << b.negedge_event();
dont_initialize();
}
void writer() {
bool v = true;
while (true) {
b.write(v);
v = !v;
wait(1, sc_core::SC_SEC);
}
}
void consumer() {
while (true) {
if (b.posedge()) {
cout << sc_time_stamp() << ": consumer receives posedge, b = " << b << endl;
} else {
cout << sc_time_stamp() << ": consumer receives negedge, b = " << b << endl;
}
wait();
}
}
void consumer_pos() {
while (true) {
cout << sc_time_stamp() << ": consumer_pos receives posedge, b = " << b << endl;
wait();
}
}
void consumer_neg() {
while (true) {
cout << sc_time_stamp() << ": consumer_neg receives negedge, b = " << b << endl;
wait();
}
}
};
14.Buffer
buffer与signal类似,主要的区别在于event上,只要buffer发生了写入,就会触发event进行通知
- 若当前一个signal的值为1,将1写到该signal不会触发值更新事件
- 若当前一个buffer的值为1,将1写道该buffer会触发值更新事件
15. Communication
communication的重要概念
- 接口(Interface,定义服务)
- 派生自sc_interface的抽象类(不派生自sc_object)
- 包括一组虚拟函数集合,这组虚拟函数应被派生自该接口的一个或多个通道定义
- 端口(Port,提供服务)
- 负责提供向module写数据的方法,因此一般同具体的实例独立
- 正向接口方法调用会转到对应的通道,该通道绑定了对应的端口
- 定义了一组服务(由端口的类型决定),这组服务由包含该端口的module要求- 实现
- 通道(Channel,实现服务)
- sc_prim_channel是所有primitive channel的基类
- 通道可能提供public成员函数,这些函数可以通过接口方法调用
- 一个primitive channel应该实现一个或多个接口
何时使用端口(port)
15.1 port
- 若一个module调用外部通道的一个成员函数时,该调用应使用接口方法通过module的端口实现。
- 调用一个属于当前module内部通道的成员函数可以直接调用。这一般称为portless channel access
- 若一个module调用子module的通道的成员函数,这应该通过子模块export实现。
SC_MODULE(Module1) {
sc_signal<int> s;
sc_port<sc_signal_out_if<int>> p;
SC_CTOR(Module1){
SC_THREAD(selfWrite);
SC_THREAD(selfRead);
sensitive << s;
dont_initialize();
SC_THREAD(outsideWrite);
}
void selfWrite() {
int val = 1;
while (true) {
s.write(val++);
wait(1, sc_core::SC_SEC);
}
}
void selfRead() {
while (true) {
cout << sc_time_stamp() << ": reads from own channel, val = " << s.read() << endl;
wait();
}
}
void outsideWrite() {
int val = 1;
while (true) {
p->write(val++);
wait(1, sc_core::SC_SEC);
}
}
};
SC_MODULE(Module2) {
sc_port<sc_signal_in_if<int>> p;
SC_CTOR(Module2){
SC_THREAD(outsideRead);
sensitive << p;
dont_initialize();
}
void outsideRead() {
while (true) {
cout << sc_time_stamp() << ": reads from outside channel, val = " << p->read() << endl;
wait();
}
}
};
int sc_main(int argc, char **argv){
Module1 module1("module1");
Module2 module2("module2");
sc_signal<int> s;
module1.p(s);
module2.p(s);
sc_start(2, sc_core::SC_SEC);
return 0;
}
15.2 export
export基础知识
- 允许module为其父module提供一个接口
- 正向接口方法调用会调用export绑定的通道
- 定义了一组服务,由包括export的module提供
何时使用
- 通过export提供一个接口对module来说是可选的
- 显式export的使用允许但一个module实例以结构化方法提供多个接口
- 若一个module调用一个子module内通道的成员函数,这应该通过子module的export实现
SC_MODULE(Module1) {
sc_export<sc_signal<int>> p;
sc_signal<int> s;
SC_CTOR(Module1) {
p(s);
SC_THREAD(writer);
}
void writer() {
int val = 1;
while (true) {
s.write(val++);
wait(1, sc_core::SC_SEC);
}
}
};
SC_MODULE(Module2) {
sc_port<sc_signal_in_if<int>> p;
SC_CTOR(Module2) {
SC_THREAD(reader);
sensitive << p;
dont_initialize();
}
void reader() {
while (true) {
cout << sc_time_stamp() << ": reads from outside channel, val = " << p->read() << endl;
wait();
}
}
};
15.3 specialized ports
除了使用基本的sc_port类来声明端口,systemC还提供了多个专用端口类,以便于使用不同的通道类型,或者提供额外的功能
sc_in:使用多个信号的专用端口类
sc_fifo_in:用于fifo读的专用端口类
sc_fifo_out:用于fifo写的专用端口类
sc_in<bool> and sc_in<sc_dt::sc_logic>:包括value_changed(), pos(), neg()方法
sc_inout:使用多个信号的专用端口类,提供value_change(), initialize()方法
sc_inout<bool> and sc_inout<sc_dt::sc_logic>:专用端口类,为二值信号提供了额外的成员函数,包括value_changed(), pos(), neg(), initialize()方法
sc_out:派生自sc_inout类,同sc_inout类恒等,除了内部基础函数不同,如构造器和赋值操作符
sc_in_resolved:使用解析信号的特殊端口类。同其父类sc_in<sc_dt::sc_logic>十分相似。唯一的不同在于sc_in_resolved的端口应绑定在sc_signal_resolved的通道上;对应的sc_in<sc_dt::sc_logic>的端口,应绑定在sc_signal<sc_dt::sc_logic,WRITER_POLICY>或者sc_signal_resolved的通道上。
sc_inout_resolved使用解析信号的特殊端口类。同其父类sc_inout<sc_dt::sc_logic>十分相似。唯一的不同在于sc_inout_resolved的端口应绑定在sc_signal_resolved的通道上;对应的sc_inout<sc_dt::sc_logic>的端口,应绑定在sc_signal<sc_dt::sc_logic,WRITER_POLICY>或者sc_signal_resolved的通道上。
sc_out_resolved:派生自sc_inout_resolved类,等同于sc_inout_resolved类,除了内部函数的不同,如构造器和赋值操作符
sc_in_rv:使用解析信号的专用端口类。其行为同其派生自的sc_in<sc_dt::sc_lv<W>>十分相似,唯一的不同在于sc_in_rv的端口应绑定到类sc_signal_rv的通道上;对应的sc_in<sc_dt::sc_lv<W>>的端口可以绑定到sc_signal<sc_dt::sc_lv<W>,WRITER_POLICY>或sc_signal_rv的通道上
sc_inout_rv:使用解析信号的专用端口类。其行为同其派生自的sc_inout<sc_dt::sc_lv<W>>十分相似,唯一的不同在于sc_inout_rv的端口应绑定到类sc_signal_rv的通道上;对应的sc_inout<sc_dt::sc_lv<W>>的端口可以绑定到sc_signal<sc_dt::sc_lv<W>,WRITER_POLICY>或sc_signal_rv的通道上
sc_out_rv:派生自sc_inout_rv,等同于sc_inout_rv,除了内部函数的不同,如构造器和赋值操作符
一个基本的sc_port>仅能访问由信号通道提供的成员函数
1.read()
2.write()
3.default_event():当一个端口用作静态敏感表时,即使用<<操作符时调用
4.event():检查是否有event发生,返回true或false
5.value_changed_event():value change event
一个sc_port>具有以下额外的成员函数,这些成员函数由signal通道提供
6.posedge():若值从false转变为true,返回true
7.posedge_event():对应的事件
8.negedge():若值从true转变为false,返回true
9.negedge_event():对应的事件
一个sc_inout<>的专用端口类提供了如下额外成员函数
10.initialize():在端口绑定到通道之前初始化其值
11.value_changed():用于在端口绑定到通道之前构建敏感表
当信号通道类型为bool 或sc_logic时,sc_inout会提供两个额外的成员函数
12.pos():在端口绑定之前创建敏感表
13.neg():在端口绑定之前创建敏感表
总结:
1-9由signal channel提供,可以通过"port->method()"调用
10=13由专用端口提供,可通过"port.methdo()"调用
15.4 port array
当声明一个端口时
- 第一个参数是接口名,同时也是端口的类型
- 端口只能绑定到从端口相同类型派生的通道或另一个端口,或者使用从端口类型派生的类型导出。
- 第二个参数是一个整数,用于指明端口可绑定的通道的最大数目默认的值为1
- 若值为0,该端口可绑定到任意数量的通道实例上
- 当将端口绑定到多余该整数的通道上时会报错
- 第三个参数是端口策略,决定了绑定多个端口的规则和解绑规则
- [default] SC_ONE_OR_MORE_BOUND:端口可绑定到一个或多个通道,最大数量由第二个参数决定,若在end_of_elaboration时,端口还没有绑定通道会报错
- SC_ZERO_OR_MORE_BOUND:端口可绑定到0或多个通道,最大数量由第二个参数决定,可在end_of_elaboration时,仍不绑定。
- SC_ALL_BOUND:端口应绑定到固定数目的通道,数量由第二个参数确定,不多不少,通道的数量应大于0
- 若此时第二个参数为0,SC_ALL_BOUND的效果和SC_ZERO_OR_MORE_BOUN等同
- 若在end_of_elaboration时,端口还没有绑定通道会报错;或者绑定少于第二个参数规定的通道时,也会报错
- 将一个端口多次绑定同一个通道会报错,不论是直接绑定还是通过其它端口绑定。
示例
1. sc_port<IF> // Bound to exactly 1 channel instance
2. sc_port<IF,0> // Bound to 1 or more channel instances, with no upper limit
3. sc_port<IF,3> // Bound to 1, 2, or 3 channel instances
4. sc_port<IF,0,SC_ZERO_OR_MORE_BOUND> // Bound to 0 or more channel instances, with no upper limit
5. sc_port<IF,1,SC_ZERO_OR_MORE_BOUND> // Bound to 0 or 1 channel instances
6. sc_port<IF,3,SC_ZERO_OR_MORE_BOUND> // Bound to 0, 1, 2, or 3 channel instances
7. sc_port<IF,3,SC_ALL_BOUND> // Bound to exactly 3 channel instances
8. sc_port<IF, 3> // an array of 3 ports, each binds to exactly 1 channel instance
9. vector<sc_port<IF>> p(3) // an array of 3 ports, each binds to exactly 1 channel instance
代码示例
class Writer : public sc_module {
public:
SC_HAS_PROCESS(Writer);
sc_port<sc_signal_out_if<int>> p1{"p1"};
sc_port<sc_signal_out_if<int>, 0> p2{"p2"};
sc_port<sc_signal_out_if<int>, 3> p3{"p3"};
sc_port<sc_signal_out_if<int>, 0, SC_ZERO_OR_MORE_BOUND> p4{"p4"};
sc_port<sc_signal_out_if<int>, 1, SC_ZERO_OR_MORE_BOUND> p5{"p5"};
sc_port<sc_signal_out_if<int>, 3, SC_ZERO_OR_MORE_BOUND> p6{"p6"};
sc_port<sc_signal_out_if<int>, 3, SC_ALL_BOUND> p7{"p7"};
vector<sc_port<sc_signal_out_if<int>>> p9;
// typedef Writer SC_CURRENT_USER_MODULE;
Writer(const sc_module_name& name) : p9(3){
SC_THREAD(writer);
}
void writer() {
int v = 1;
while (true) {
p9[0]->write(v);
p7[1]->write(v++);
wait(1, sc_core::SC_SEC);
}
}
};
class Reader : public sc_module {
public:
SC_HAS_PROCESS(Reader);
sc_port<sc_signal_in_if<int>> p1{"p1"};
sc_port<sc_signal_in_if<int>, 0> p2{"p2"};
sc_port<sc_signal_in_if<int>, 3> p3{"p3"};
sc_port<sc_signal_in_if<int>, 0, SC_ZERO_OR_MORE_BOUND> p4{"p4"};
sc_port<sc_signal_in_if<int>, 1, SC_ZERO_OR_MORE_BOUND> p5{"p5"};
sc_port<sc_signal_in_if<int>, 3, SC_ZERO_OR_MORE_BOUND> p6{"p6"};
sc_port<sc_signal_in_if<int>, 3, SC_ALL_BOUND> p7{"p7"};
vector<sc_port<sc_signal_in_if<int>>> p9;
// typedef Reader SC_CURRENT_USER_MODULE;
Reader(const sc_module_name& name) : p9(3) {
SC_THREAD(reader7);
sensitive << p7;
dont_initialize();
SC_THREAD(reader9);
sensitive << p9[0] << p9[1] << p9[2];
dont_initialize();
}
void reader7() {
while (true) {
cout << sc_time_stamp() << ": reader7, port 0/1/2 = " << p7[0]->read() << "/" << p7[1]->read() << "/"
<< p7[2]->read() << endl;
wait();
}
}
void reader9() {
while (true) {
cout << sc_time_stamp() << ": reader9, port 0/1/2 = " << p9[0]->read() << "/" << p9[1]->read() << "/"
<< p9[2]->read() << endl;
wait();
}
}
};
int sc_main(int argc, char **argv){
Writer writer("writer");
Reader reader("reader");
sc_signal<int> s1;
vector<sc_signal<int>> s2(10);
vector<sc_signal<int>> s3(2);
sc_signal<int> s5;
vector<sc_signal<int>> s6(2);
vector<sc_signal<int>> s7(3);
vector<sc_signal<int>> s9(3);
writer.p1(s1);
reader.p1(s1);
for (unsigned int i = 0; i < s2.size(); ++i) {
writer.p2(s2[i]);
reader.p2(s2[i]);
}
for (unsigned int i = 0; i < s3.size(); ++i) {
writer.p3(s3[i]);
reader.p3(s3[i]);
}
writer.p5(s5);
reader.p5(s5);
for (unsigned int i = 0; i < s6.size(); ++i) {
writer.p6(s6[i]);
reader.p6(s6[i]);
}
for (unsigned int i = 0; i < s7.size(); ++i) {
writer.p7(s7[i]);
reader.p7(s7[i]);
}
for (unsigned int i = 0; i < s9.size(); ++i) {
writer.p9[i](s9[i]);
reader.p9[i](s9[i]);
}
sc_start(2, sc_core::SC_SEC);
return 0;
}
16. Trace File
trace file基础
- 记录了仿真过程中值变化的时序信息
- 使用VCD(Value change dump)文件格式
- 仅能通过sc_create_vcd_trace_file创建和打开
- 可能在elaboration阶段或仿真的其它时间打开,包括可以被sc_trace跟踪的值
- 应在跟踪值之前打开文件,且若打开文件后已经过了一个或多个delta周期
- 应通过sc_close_vcd_trace_file关闭,一个trace文件不应再最后一个delta周期之前关闭
#include "sysc/communication/sc_port.h"
#include "sysc/communication/sc_signal.h"
#include "sysc/kernel/sc_externs.h"
#include "sysc/kernel/sc_module.h"
#include "sysc/kernel/sc_module_name.h"
#include "sysc/kernel/sc_simcontext.h"
#include "sysc/kernel/sc_time.h"
#include "sysc/tracing/sc_trace.h"
#include <systemc>
using namespace sc_core;
using namespace std;
class Module : public sc_module {
public:
SC_HAS_PROCESS(Module);
sc_port<sc_signal<int>> p;
sc_port<sc_signal<bool>> b;
Module(const sc_module_name& name) {
SC_THREAD(writer);
}
void writer() {
int v = 1;
int clk = true;
while (true) {
p->write(v++);
b->write(clk);
clk = !clk;
wait(1, sc_core::SC_SEC);
}
}
};
int sc_main(int argc, char **argv){
Module module("module");
sc_signal<int> s;
sc_signal<bool> clk;
module.p(s);
module.b(clk);
sc_trace_file * file = sc_create_vcd_trace_file("trace");
sc_trace(file, s, "signal");
sc_trace(file, clk, "clock");
sc_start(5, sc_core::SC_SEC);
sc_close_vcd_trace_file(file);
return 0;
}
运行后会生成trace.vcd文件,可通过gtkwave打开看波形,如下(可通过apt install 安装gtkwave,命令为gtkwave trace.vcd)
杂项
1.SC的执行并不是并发的,在同一个delta周期内的执行顺序是不一定的。但实际上一开始的执行顺序是按照进程创建的顺序执行的。
2.对一个sc_signal进行数据的写入,并立即通过read读出,读出的数据还是旧的数据,经过一个delta延时后,读出的数据是新的数据。
代码解释
关于CTHREAD与CLOCK
代码如下
#include "sysc/communication/sc_clock.h"
#include "sysc/communication/sc_signal_ports.h"
#include "sysc/kernel/sc_externs.h"
#include "sysc/kernel/sc_module.h"
#include "sysc/kernel/sc_process.h"
#include "sysc/kernel/sc_simcontext.h"
#include "sysc/kernel/sc_time.h"
#include <systemc>
using namespace sc_core;
using namespace std;
SC_MODULE(Module){
sc_in<bool> clk;
SC_CTOR(Module) {
SC_CTHREAD(cthread1, clk);
SC_CTHREAD(cthread2, clk.pos());
SC_CTHREAD(cthread3, clk.neg());
}
void cthread1() {
while (true) {
wait();
cout << sc_time_stamp() << ", cthread1, value = " << clk->read() << endl;
}
}
void cthread2() {
while (true) {
wait();
cout << sc_time_stamp() << ", cthread2, value = " << clk->read() << endl;
}
}
void cthread3() {
while (true) {
wait();
cout << sc_time_stamp() << ", cthread3, value = " << clk->read() << endl;
}
}
};
int sc_main(int argc, char **argv){
sc_clock clk("clk", 10, SC_SEC, 0.2, 10, SC_SEC, false);
Module module("module");
module.clk(clk);
sc_start(31, SC_SEC);
return 0;
}
解释:
- 设置的时钟周期在30s内的波形如下(因为设置初始电平为高电平,且第一次跳变发生在10 sec,占空比为0.2)
- 当仿真开始时,由于cthread线程并不会被初始化执行一次,因此只有等到10sec时,才开始执行了由敏感列表为时钟下降沿的cthread3,并触发了wait,此时没有任何输出。
- 同理当等到18s时,执行了由敏感列表为时钟上升沿的cthread1和cthread2,并触发了wait,此时没有任何输出。
- 当等到20s时,又开始执行cthread3线程,并输出数据,然后继续等待下一个下降沿到来
- 当等到28s时,又开始执行cthread1和cthread2线程,并输出数据,然后继续等待下一个上升沿到来
- 当等到30s时,又开始执行cthread3线程,并输出数据
参考文献
本文章仅用于学习记录,大多内容参考知乎链接。
1.systemc学习手册
2.来自知乎的中文学习手册