预备打工人之SystemC学习(四) 行为建模语法
预备打工人之SystemC学习TLMTLM相关语法接口存储器接口实例接口基类sc_interface端口自定义端口一个端口实例TLMTLM是指事务处理级建模。TLM建模从整体考虑现代电子系统,从一开始就迅速完成的高层次系统行为的表述,确定最佳的系统架构。什么叫“事务”?事务是设计中给定两个时间点内发生的呗认为不可分割的活动。TLM相关语法SystemC定义了接口、端口和通道。接口是一个C++抽象类
预备打工人之SystemC学习
TLM
TLM是指事务处理级建模。TLM建模从整体考虑现代电子系统,从一开始就迅速完成的高层次系统行为的表述,确定最佳的系统架构。
什么叫“事务”?事务是设计中给定两个时间点内发生的呗认为不可分割的活动。
TLM相关语法
SystemC定义了接口、端口和通道。
- 接口是一个C++抽象类。
- 通道实现一个或者多个接口
- 端口。模块通过端口来使用通道。端口总是与一定的接口类型相关联,只能连接到该类型的端口上。
接口
接口是C++抽象类。在C++中,抽象类可以如下:
class Shape{
public:
virtual void ratate (int) =0;
virtual void draw(int) =0;
virtual void is_closed() =0;
}
所有方法用法“=0”来表示纯虚函数。C++不允许创建抽象类对象,所以下面的代码是错误的:
Shape S;
在SystemC中,sc_interface是所有接口的基类,任何一个接口必须是直接或者间接基础sc_interface。并且接口不包含任何数据成员。
存储器接口实例
我们首先定义一个存储器读接口mem_read_if,然后定义一个存储器的写接口mem_write_if,接着定义一个存储器的复位接口reset_if,最后利用前面的三个接口定义随机存取存储器接口ram_if。
# ifndef __MEM_IF_H
# define __MEM_IF_H
# include "systemc.h"
enum trasfer_status {TRANSFER_OK=0, TRANSFER_ERROT};
template <class T>
class mem_read_if:public sc_interface
{
public:
virtual transfer_status read(unsigned int address,T& data)=0;
};
template <class T>
class mem_write_if:public sc_interface
{
public:
virtual transfer_status write(unsigned int address,T& data)=0;
};
class reset_if:public sc_interface
{
public:
virtual bool reset()=0;
};
template <class T>
class ram_if:public mem_write_if<T>,mem_read_if<T>,reset_if<T>{
public:
virtual unsigned int start_address() const=0;
virtual unsigned int end_address() const=0;
};
#endif
从上面的存储器接口的定义可以看出,接口是分层的,复杂的接口可以 由多个简单的接口继承而得到的。
接口基类sc_interface
类sc_interface是所有接口类的父类,所有其他类都直接或者间接从类sc_interface继承而来。
端口
之前基本的SystemC端口类型,sc_in、sc_out、sc_inout。但是事务级可以让用户自定义端口类型。
自定义端口
端口与特定的通道接口相连,或者同父模块的端口相连。进程通过特定的端口调用通道的接口提供的方法。对于基本的端口类型,可以调用的接口方法有write和read,但这在有些情况下不满足要求。高层次需要一些复杂操作。
端口定义方法如下:
sc_port< InterfaceType,ChannelNumber=1>
例如:
sc_port< ram_if> ram_port1;
一个端口实例
首先定义一个通道ram。
# ifndef __RAM_H
# include "systemc.h"
# include "mem_if.h"
template<class T>
class ram:public sc_module,ram_if<T>{
public:
ram(sc_module_name name_,unsigned int start_address,unsigned int end_address):
sc_module(name_),
m_start_address(start_address),
m_end_address(end_address){
sc_assert(end_address>=start_address);
mem=new T[end_address-start_address];
}
~ram(){
if(mem){
delete mem;mem=0;
}
}
transfer_status read(unsigned address,T&data){
if(address<m_start_address || address>M_end_address){
data=0;
return TRASNFER_ERROR;
}
data=mem[address-m_start_address];
return TRANSFER_OK;
}
transfer_status write(unsigned address,T&data){
if(address<m_start_address || address>M_end_address){
data=0;
return TRASNFER_ERROR;
}
mem[address-m_start_address]=data;
return TRANSFER_OK;
}
bool reset(){
for (int i=0;i<(m_end_address-m_start_address);i++)
mem[i]=(T)0;
return true;
}
inline unsigned int start_address() const{
return m_start_address;
}
inline unsigned int end_address() const{
return m_end_address;
}
private:
T*mem;
unsigned int m_start_address,m_end_address;
}
#endif
定义好了通道ram,就可以定义一个模块通过端口来访问它了。首先定义一个master来访问RAM。
SC_MODULE(Master){
sc_in_clk clk;
sc_port<ram_if<int>> ram_port;//端口实例
void man_action;
int data;
unsigned int address;
SC_CTOR(){
SC_CTHREAD(main_action,clk.pos());
}
};
void Master::main_action(){
wait();
int i=0;
while(i++<100){//不知道为啥用个循环?
address=0;
if(transfer_status status=ram_port->write(address,data)){
cout<<"Write RAM at address"<<address<<"to RAM, data is"<<data<<endl;
}
else{
cout<<"Witre fail"<<endl;
}
if(transfer_status status=ram_port->resd(address,data)){
cout<<"Read RAM at address"<<address<<"to RAM, data is"<<data<<endl;
}
else{
cout<<"Read fail"<<endl;
}
wait();
address++;
}
sc_stop();
}
端口基类sc_port<IF,N>
sc_port<IF,N>是所有端口的基类,是一个模板类。IF是接口类型,N是所连接的同一个类型的通道数目,缺省值是1。
下面是准代码,看看有哪些成员:
template <class IF,int N=1> class sc_port: public sc_port_b<IF>{
public:
ac_port();
void operator() (IF & interface);
void operator() (sc_port_b <IF> & port);
IF * operator ->();
const IF * operator->() const;
int size() const;
IF *operator[] (int index);
const IF *operator[] (int index) const;
...
protected:
vector<IF *>m_interface_vec;
IF * m_interface;
...
};
重载操作符 operator() (IF & interface)用于将端口与接口捆绑起来,实现端口与通道的互通,这是端口的最基本操作。操作符operator() (sc_port_b& port)用于将端口与父模块的端口捆绑起来,使端口支持分层的模块化设计。IF *operator ->()和const IF * operator ->() const 实现接口方法的调用。利用size 可以查询接口数。下面给出一个多接口的例子。
一个连接到多个接口的端口实例
这是一个存储器管理单元(MMU)同时管理着三个RAM。主设备通过MMU提供的接口来读写RAM。
Master程序还是和之前一样,不再重复。
由于Master模块可能会单个复位某一个RAM。所以将ram_if扩展成为multi_ram_if,让它支持单个RAM的复位。而ram_if的方法reset()被用来复位全部的RAM。multi_ram_if的定义如下:
template<class T>
class multi_ram_if:public ram_if<T>{
virtual bool reset(unsigned int i)=0;
};
MMU提供给Master的接口仍然是multi_ram_if,而MMU看到的RAM接口是ram_if。下面我们给出的MMU的部分设计代码。
#ifndef _MMU_H
#define _MMU_H
#include <systemc.h>
class mmu:public sc_module,public multi_ram_if<int>{
public:
#ifdef MAX_IS_3
sc_port<ram_if<int>,3> ram_port;
#else
sc_port<ram_if<int>,0> ram_port;
#endif
transfer_status read(unsigned address,int &data){
for(int i=0;i<ram_port.size();i++){
if((address >=ram_portp[i]->start_address())&&(address <=ram_portp[i]->end_address()))
return ram_port[i]->read(address,data);
}
return TRANSFER_ERROR;
}
bool reset(unsigned int i){
if(i<ram_port.size())
return ram_port[i]->reser();
else
return false;
}
transfer_status write(unsigned address,int &data){
for(int i=0;i<ram_port.size();i++){
if((address >=ram_portp[i]->start_address())&&(address <=ram_portp[i]->end_address()))
return ram_port[i]->witre(address,data);
}
return TRANSFER_ERROR;
}
}
#endif
会通过size()函数获得所实际连接到自己的RAM的数量
直接通道调用
SystemC中并不允许直接调用通道,不同模块之间进行通信必须通过端口。
但是在同一模块里,各个进程之间需要通信,他们可以通过共享变量、握手信号、模块内通道等方式通信。如果他们之间的通信是通过模块内通道,则此时不需要通过端口,可以直接调用。
SystemC中预定义了许多通道,如sc_fifo
,sc_mutex等。给出一个sc_mutex的例子。
sc_mutex就像是多线程中的互斥锁。模块writer1和writer2在同一时钟工作,可能会同时写data。为了避免冲突,就要一个互斥锁。
#ifndef _MUTEX_EXAMPLE_H
#define _MUTEX_EXAMPLE_H
#include "systemc.h"
SC_MODULE(mutex_example){
sc_in_clk clk;
sc_out<int> data;
sc_mutex protect;
void writer1(){
wait();
while(true){
protect.lock();
data=rand();
cout<<"data is wirtten by 1"<<endl;
protect.unlock();
wait();
}
}
void writer2(){
wait();
while(true){
protect.lock();
data=rand();
cout<<"data is wirtten by 2"<<endl;
protect.unlock();
wait();
}
}
void reader(){
protect.lock();
cout<<"read"<<endl;
protect.unlock();
}
SC_CTOR(mutex_example){
SC_METHOD(reader);
sensitive<<data;
SC_CTHREAD(writer1,clk.pos());
SC_CTHREAD(writer2,clk.pos());
}
};
#endif
通道基础
系统抽象的三个关键是行为、时序和通讯。模块是系统行为的主要载体,通道是通讯的主要载体。
之前都是定义接口,也就是定义了一组通讯方法,而不是具体实现。通道就是具体实现。
SystemC中通道分为两种,基本通道和分层通道。
- 基本通道:不包含任何进程,也不对外展现任何可见结构,也不能直接地或者间接地调到其他基本通道。
- 分层通道:本身就是一个模块,包括进程、子模块,也可以调用其他通道。
端口与通道关联
端口与通道相互连接起来,以允许模块内的进程通过端口来调用所连接的通道。
下面是一个信源与信宿通过FIFO同学的简单系统框图。
首先定义一个FIFO接口。接口分为读接口与写接口,分别定义。
#inlcude "systemc.h"
template<class T>
class fifo_write_if:public sc_interface{
bool fifo_write(T data)=0;
unsigned int num_free()=0;
};
template<class T>
class fifo_read_if:public sc_interface{
bool fifo_read(T& data)=0;
unsigned int num_avaiable()=0;
};
假设已经设计好了一个FIFO通道fifo<T,D>,其中T代表存储的数据类型,D表示FIFO的深度。
SC_MODULE(Source){
sc_port< fifo<int> > fifo_write_port;
void write_process(){
int data=100;
if (fifo_write_port->number_free()!=0)
fifo_write_port->fifo_write(data);
}
...
};
write_process()是Source模块的一个进程,它通过端口fifo_write_port来检测fifo的状态。如果FIFO还有空位置,就将数据data写入FIFO。
我们假设TOP的设计如下:
#define DEPTH 64
SC_MODULE(Top){
fifo<int,DEPTH> fifo1;
Source Source1;
Sink Sink1;
...
SC_CTOR(Top){
Source1.fifi_write_port(fifo1);
Sink1.fifo_read_port(fifo1);
...
}
};
通道的同步规则
SystemC的通道允许并行的动作,这就设计同步的问题。一个通用的做法是将对通道的操作分为两个部分进行,即所谓的“求值——更新”过程。
设计规则检查
设计规则是指设计中不能违反的设计法则,如一个sc_signal只能被一个线程驱动,一个sc_fifo只能连接一个读端口与写端口等。
设计规则检查分为静态规则检查和动态规则检查。
-
静态规则检查是在系统运行前进行静态检查。SystemC约定:当一个端口与一个通道相连接,这是相应通道的register_port()函数就会被调用。用户可以自己重载这个函数进行专门的静态设计规则检查。
-
动态规则检查是系统运行时进行。
当sc_signal<T>的write()方法被调用时,它首先检查当前值与新值是否一样。
通道的属性
通道的属性描述的是通道的一些特征,比如优先级等。
基本通道
上面讲了,基本通道不包含任何进程,也不对外展现出任何可见结构,他们也不够直接的或者间接的调用其他基本通道。SystemC中定义了若干基本通道类型,他们是:
sc_signal<T>,sc_signal_rv<N>
sc_mutex
sc_fifo<T>
sc_semaphore//信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。
sc_buffer<T>
源码在SystemC2.3的src\sysc\sysc\communication目录下
sc_signal<T>、sc_signal_rv<T>和sc_buffer<T>
sc_signal是最近的通道,它用于连接基本端口sc_in、sc_out和sc_inout。注意:最多这有一个头连接 到signa,否则就会产生多驱动的错误。可以有多个sc_in同时连接到sc_signal。
sc_signal_rv是所谓解析的信号通道,允许同时多个端口连接到其上并进行操作。
sc_buffer继承于sc_signal,只是重载了write()和update()函数,代码如下:
template<class T> inline void sc_buffer<T>::write(const T& value_){
#include DEBUG_SYSTEMC
this->check_writer();
#endif
this->m_new_val=value_;
this->request_update();
}
template<class T> inline void sc_buffer<T>::update(){
this->m_cur_val=this->m_new_val;
this->m_value_changed_event.notify_delayed();
this->m_delta=this->simcontext()->delta_count();
}
sc_mutex
互斥是用来保护共享资源的,以防止多个进程同时读写共享资源,导致系统行为的不确定性。
- lock()进程可以锁定互斥。
- unlock()解锁
sc_fifo<T>
sc_fifo是SystemC库中已经实现好的FIFO通道,其中T为FIFO中存储的数据类型。FIFO的中文名字叫做先进先出队列。write(&T)代表写FIFO的方法,read()是读FIFO的方法,范围队头单元的书。Num_free()查询FIFO还有多少空单元,num_available()查询FIFO还有多少个数据可以读。对于fifo的size,默认深度为16.
sc_fifo<int> fifo1;//深度为16
sc_fifo<packet> fifo2(64);//深度为64
其中read、write为阻塞型
sc_semaphore
sc_semaphore是SystemC中定义的基本通道。也是用来保护共享资源的。信号量代表可用资源实体的数量,是一个资源计数器,限制的是同时使用摸个共享资源的进程的数量。信号量计数的值代表的就是当前仍然可用的共享资源的数量。
在SystemC中,sc_semaphore实现的是sc_semaphore_if接口,该接口的定义如下:
class sc_semaphore_if: virtual public sc_interface{
public:
virtual int wait()=0;
virtual int trywait()=0;
virtual int post()=0;
virtual int get_value() const=0;
protected:
sc_semaphore_if(){}
private:
sc_semaphore_if(const sc_semaphore_if &);
sc_semaphore_if & operator=(const sc_semaphore_if &);
};
其中wait()方法获得一个信号量,其作用高效果是活动一份资源的使用权,是信号量技术减一,如下面的代码实现。
int sc_semaphore::wait(){
while(in_use()){
sc_prim_channel::wait(m_free);
}
--m_value;
return 0;
}
这是一个阻塞函数,当信号量的计数已经为0的时候,这个函数就会被阻塞。in_use()检查的就是信号量的计数m_value是否为0。trywait()是对应的非阻塞函数,代码实现如下:
int sc_semaphore::trywait(){
if(in_use()){return -1;}
--m_value;
return 0;
}
post()是释放资源的函数,代码如下:
int sc_semaphore::post(){
++ m_value;
m_free.notify();
return 0;
}
sc_event
sc_event可以用来定义一个事件,SC_THREAD进程可以采用wait(const sc_event &)、wait(sc_event_or_list &)、wat(sc_event_and_list &)来将进程挂起等待时间发生后将进程重新激活。
当一个事件发生,它总是通过notify函数通知所有等待该事件的进程。notify表示时间立即发生即立刻发送事件通知。
实际上,一个事件只能有一个等待发送的通知,更晚发生的通知总是被更近发生的通知所取代。
sc_event_queue
sc_event_queue与sc_event的共同点都有notify的方法,而不同点是sc_event_queue实际上是一个分层通道,它可以有多个等待触发的通知,这些通知不会相互覆盖。
分层通道
定义
分层通道则具有可见结构,包含进程,可以直接操作其他通道,它是一个实现了一个或者多个接口的模块。
分层通道能够建模复杂的硬件模块。一个典型的可以用分层通道建模的就是片上总线,一个充分利用片上总线是一个抽象的行为模式,它与所采用的具体总线架构无关,具有很好的重用性。
一般可以在以下情况下可以考虑基本通道:
- 需要使用求值——更新策略时
- 当通道的功能特别基本,很难划分成独立的功能块时。
- 当仿真速度对于设计十分关键,使用基本通道能够减少△周期,从而节省仿真时间。
- 使用进程没有明显意义时,比如信号量和互斥这样的通道。
而以下情况建议使用分层通道:
- 当通道的结构层次很明显,用户可能需要了解通道的底层结构时。
- 当通道应该包含进程时。
分层通道的例子
这是一个FIFO嵌套的例子,一个寄存器传输机的FIFO被一个抽象FIFO包裹起来,它们整体对外显示的是抽象接口。
将RTL的FIFO命名为fifo_ca,这里ca的意思是精确到周期;将抽象FIFO(TLM)命名为fifo_hl,hl使high level。fifo_ca和fifo_hl之间通过具体的握手信号进行通信,而Source与fifo_hl、fifo_hl与Sink则通过抽象结构进行通信。
fifo_ca的行为仿真FPGA开发工具中的coregenerator的行为。假设存储器的单元数为m_size,写指针和读指针分别用于指定读写的位置,他们的值采用循环方式轮转。源代码如下:
#ifndef _FIFO_CA_H
#define _FIFO_CA_H
#include "systemc.h"
template <class T> class fifo_ca:public sc_module{
public:
sc_in<bool> clk;
sc_in<bool> rst;
sc_in<bool> wr_en;
sc_in<bool> rd_en;
sc_in<T> din;
sc_out<bool> full;
sc_out<bool> empty;
sc_out<T> dout;
sc_out<unsigned int> data_count;
SC_HAS_PROCESS(fifo_ca);//不知道在干什么
fifo_ca(sc_module_name name, unsigned size):sc_module(name),m_size(size){
assert(size>0);
m_read_pointer=m_write_pointer=0;
mem=new T[m_size];
SC_METHOD(main);
sensitive<<clk.pos()<<rst;
empty.initialize(true);
full.initialize(true);
}
~fifo_ca(){delte[]mem;}
protected:
void main(){
if(rst==true){
for(int i=0;i<m_size;i++){mem[i]=(T)0;}
m_read_pointer=0;
m_write_pointer=0;
full=false;
empty=true;
}
else{
if(wr_en.read()){
if(rd_en.read()||(full==false))
mem[m_write_pointer %m_size]=din.read();
m_write_pointer=(m_write_pointer+1)%m_size;
}
if(rd_en.read()){
if(wr_en.read()||(empty==false)){
dout=mem[m_read_pointer];
m_read_pointer=(m_read_pointer+1)%m_size;
}
}
data_count=(m_write_pointer-m_read_pointer)%m_size;
full=(m_read_pointer-m_write_pointer)==1;
empty=m_read_pointer==m_write_pointer;
}
}
private:
unsigned m_size;
unsigned m_read_pointer;
unsigned m_write_pointer;
T *mem;
};
#endif
大概能看懂,但是自己目前写不出来。
fifo_hl的代码如下:
#ifndef _FIFO_HL_H
#define _FIFO_HL_H
#include "systemc.h"
#include "fifo_ca.h"
#include "reset_if.h"
template <class T> class fifo_hl:public sc_module,public sc_fifo_in_if<T>,public sc_fifo_inout_if<T>,public reset_if{
public:
sc_in<bool> clk;
sc_signal<T> write_data;
sc_signal<bool>write_enable;
sc_signal<bool>rst;
sc_signal<T> read_data;
sc_signal<bool> read_enable;
sc_signal<bool> full,empty;
sc_signal<unsigned int> data_count;
fifo_ca<T> fifo_ca1;
private:
unsigned m_size;
public:
fifo_hl(sc_module_name name,unsigned size):sc_module(name),fifo_ca1("fifo_ca1",size+1),m_size(size){
assert(m_size>0);
fifo_ca1.clk(clk);
fifo_ca1.rst(rst);
fifo_ca1.din(write_data);
fifo_ca1.wr_en(write_enable);
fifo_ca1.dout(read_data);
fifo_ca1.rd_en(read_enable);
fifo_ca1.full(full);
fifo_ca1.empty(empty);
fifo_ca1.data_count(data_count);
write_enable.write(false);
read_enable.write(false);
}
virtual void write(const T& data){
write_data=data;
dp{wait(clk->posedge_event());}
while(full==true);
wait(clk->posedge_event());
write_enable=true;
wait(clk->posedge_event());
write_enable=false;
}
virtual T read(){
do{wait(clk->posedge_event());}
while(empty==true);
read_enable=true;
wait(clk->posedge_event());
read_enable=false;
wait(clk->posedge_event());
return read_data.read();
}
virtual void read(T& d){d=read();}
virtual bool nb_read(T& data){
wait(clk->posedge_event());
if(empty==true) return false;
else{
read_enable=true;
wait(clk->posedge_event());
wait(clk->posedge_event());
read_enable=false;
data=read_data.read();
return true;
}
}
virtual bool nb_write(const T& ){
if(full ==true) return false;
else{
write_enable=true;
wait(clk->posedge_event());
write_enable=false;
return true;
}
}
virtual int num_available() const{return (int) data_count;}
virtual int num_free() const{return m_size-data_count;}
virtual const sc_event &data_read_event() const{return read_enable.posedge_event();}
virtual const sc_event&data_written_event()const{
return write_enable.posedge_event();}
void reset(unsigned int interval){
rst=true;
wait(intercval);
rat=false;
}
};
#endif
Source模块的代码如下:
#define PERIOD 20
class source:public sc_module{
public:
sc_in_clk clk;
sc_port<sc_fifo_out_if<char>> write_port;
sc_port<reset_if> reset_port;
SC_HAS_PROCESS(source);
source(sc_module_name name):sc_module(name){
SC_CTHREAD(main,clk.neg());
}
void main(){
int i=0;
const char str[]="1234567890!\n";
reset_port->reset(75);
wait();
while(true){
if(rand()& 1){
if(str[i]){write_port0>write(str[i++];)}
wait();
}
}
}
};
这个模块输出一个字符串给fifo_hl.。
Sink模块的代码如下:
class sink:public sc_module{
public:
sc_in_clk clk;
sc_port<sc_fifo_in_if<char>> read_port;
SC_HAS_PROCESS(sink);
sink(sc_module_name name):sc_module(name){
SC_CTHREAD(main,clk,neg());
}
void main(){
char c;
while(true){
if(rand() &1){
read_port->read(c);cout<<c;wait();
}
}
}
};
导出端口(SC_EXPORT)
分层通道是例化其他通道的通道。如下图的例子。E例化了通道C1和通道D,而通道D又连接了通道C2。sc_export允许一个通道将接口导出到其父模块,从而是的连接到父模块的其他模块可以调用高接口的方法。
更多推荐
所有评论(0)