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允许一个通道将接口导出到其父模块,从而是的连接到父模块的其他模块可以调用高接口的方法。

Logo

开源、云原生的融合云平台

更多推荐