【学习笔记】SystemC学习记录

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;
}

在这里插入图片描述
解释:

  1. 设置的时钟周期在30s内的波形如下(因为设置初始电平为高电平,且第一次跳变发生在10 sec,占空比为0.2)
    请添加图片描述
  2. 当仿真开始时,由于cthread线程并不会被初始化执行一次,因此只有等到10sec时,才开始执行了由敏感列表为时钟下降沿的cthread3,并触发了wait,此时没有任何输出。
  3. 同理当等到18s时,执行了由敏感列表为时钟上升沿的cthread1和cthread2,并触发了wait,此时没有任何输出。
  4. 当等到20s时,又开始执行cthread3线程,并输出数据,然后继续等待下一个下降沿到来
  5. 当等到28s时,又开始执行cthread1和cthread2线程,并输出数据,然后继续等待下一个上升沿到来
  6. 当等到30s时,又开始执行cthread3线程,并输出数据

参考文献

本文章仅用于学习记录,大多内容参考知乎链接。
1.systemc学习手册
2.来自知乎的中文学习手册

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值