TLM:事务处理级建模
什么是事务,事务的最确切定义为设计中给定的两个时间点内发生的被认为不可分割的活动
在一个总线系统中,总线的一次读或者一次写操作通常会被认为是事务
从硬件的角度,一个事务是两个实体(进程、模块、通道)间的一次控制或者数据传输
从软件的角度,一次事务通常被理解为一次函数的调用
为了支持 TLM 建模,systemc 定义了接口(Interface)、端口(Port)和通道(channel),它们的关系如下
在 C++ 中,接口是一个抽象类,不具有实际意义,只有被继承化作具体的类才有意义
在 SystemC 中,sc_interface 是所有接口的基类,任何一个接口必须是直接或者间接继承 sc_interface,一个复杂的接口可以由多个简单的接口继承而得到,sc_interface 的具体实现如下
class SC_API sc_interface
{
public:
// register a port with this interface (does nothing by default)
virtual void register_port( sc_port_base& port_,
const char* if_typename_ );
// get the default event
virtual const sc_event& default_event() const;
// destructor (does nothing)
virtual ~sc_interface();
protected:
// constructor (does nothing)
sc_interface();
private:
// disabled
sc_interface( const sc_interface& );
sc_interface& operator = ( const sc_interface& );
};
方法 register_port 完成了通道绑定时的静态规则检查,port_ 是接口所连接的端口名,if)typename_ 是接口名,缺省情况下这个函数不做任何事情
通道可以使用 default_event 来返回静态敏感表的缺省事件,default_event 中调用的 SC_REPORT_WARNING(SC_ID_NO_DEFAULT_EVENT_, 0) 用于产生警告信息
在之前有提到过一些 systemc 的基本端口,包括 sc_in、sc_out、sc_inout,另外为了满足事务处理级建模的需要,SystemC 允许用户自己定义端口类型
在 systemc 中一个端口可以同时连接到一个或者多个实现了同一接口的通道上,端口必须在模块中使用,端口的定义方法如下
sc_port
InterfaceType 是端口要连接的通道的接口类型
ChannelNumber 代表端口所要连接的最大通道数,默认为 1,当为 0 时表示不限制连接数量
设计中端口所实际连接的通道的数目可以通过 size 方法来访问
在 systemc 中规定了不同的模块之间进行通信必须通过端口,不能直接使用函数调用
系统抽象的三个关键是行为、时序和通信。从广义上讲,模块是系统行为的主要载体,通道则是通信的主要载体,而通道本身可能是模块,也表现出一定的行为
在 SystemC 中,接口本身只是定义了一组通信的方法,而不具体负责这些方法的实现,通道才是这些接口方法的实现者,通道可以实现一个或者多个接口,同时通道也可以连接两个或者多个模块,SystemC 允许用户自己定义通道,以实现多样的抽象系统模型
SystemC 通道分为两种,基本通道和分层通道。基本通道不包含任何的进程,也不对外展现出任何的可见结构,它们不能直接或者间接调用其它基本通道。而分层通道本身是一个模块,当然可以包含进程、子模块,也可以包含和调用其它通道
2.3.1 通道同步规则
SystemC 通道允许并行的操作,这就涉及同步的问题,比如在同一个时钟的上升沿既读又写,则读的结果应该是写入之前的值,但是怎样才能保证这些呢,一个通用的做法是将对通道的操作分为两个部分进行,即所谓的求值-更新过程,在求值阶段,新的结果被记录,同时保存原有的接口,如果是读操作,则在求值阶段将原有的结果返回,如果是写操作,则接着执行更新的过程,真正将新的数据写入,这里有点类似于 c++ 里面的 i++ 操作,也就是求值慢于更新一个时钟
所有的基本通道都是由 sc_prim_channel 继承而来,sc_prim_channel 完成的是对求值-更新过程的基本支持,如果我们要自定义实现一个基本通道,那么就要继承 sc_prim_channel
2.3.2 静态检查规则
设计规则是指设计中不能够违反的设计法则,比如一个 sc_signal
设计规则检查分为静态规则检查和动态规则检查,静态规则检查是指在系统运行前进行的静态检查,比如一个 sc_fifo
下面可以看一段静态规则检查的代码
void sc_fifo::register_port( sc_port_base& port_, const char* if_typename_ )
{
std::string nm( if_typename_ );
if( nm == typeid( sc_fifo_in_if ).name() ||
nm == typeid( sc_fifo_blocking_in_if ).name()
) {
// only one reader can be connected
if( m_reader != 0 ) {
SC_REPORT_ERROR( SC_ID_MORE_THAN_ONE_FIFO_READER_, 0 );
// may continue, if suppressed
}
m_reader = &port_;
} else if( nm == typeid( sc_fifo_out_if ).name() ||
nm == typeid( sc_fifo_blocking_out_if ).name()
) {
// only one writer can be connected
if( m_writer != 0 ) {
SC_REPORT_ERROR( SC_ID_MORE_THAN_ONE_FIFO_WRITER_, 0 );
// may continue, if suppressed
}
m_writer = &port_;
}
else
{
SC_REPORT_ERROR( SC_ID_BIND_IF_TO_PORT_,
"sc_fifo port not recognized" );
// may continue, if suppressed
}
}
当一个端口与一个通道相连时,这时 register_port 函数就会被调用
下面看一段动态规则检查的代码,sc_signal 只能被一个进程驱动,也就是说如果有两个地方进行写操作,那么就会报错
SC_TEMPLATE
inline void sc_signal >::check_writer()
{
sc_process_b* writer_p = sc_get_curr_process_handle();
if( m_writer_p == 0 )
{
m_writer_p = writer_p;
}
else if( m_writer_p != writer_p )
{
sc_signal_invalid_writer( name(), kind(),
m_writer_p->name(), writer_p->name() );
}
}
基本通道不包含任何进程,也不对外展现出任何的可见结构,它们也不能够直接或者间接的调用其它基本通道,在 systemc 中基本通道有以下这些
sc_signal
sc_signal_rv
sc_buffer
sc_fifo
sc_mutex
sc_semaphore
基本通道只实现了一个或者多个特定的接口,但是不具有任何的可见结构,而分层通道则具有可见结构,包含进程,可以直接操作其他通道,是一个实现了一个或者多个接口的模块
以下情况使用基本通道
当需要使用求值-更新策略时
当通道的功能很基本,很难划分为独立的功能模块时
当仿真速度对于设计非常关键,使用基本通道能够减少 Δ 周期,从而节省仿真时间
当使用进程没有明显的意义时,比如信号量互斥这样的通道
以下情况使用分层通道
当通道的结构层次很明显,用户可能需要了解通道的底层结构时
当通道应该包含进程时
常见的分层通道有两种情况,一是在一个通道中直接实例化并使用其它通道,被实例化的通道可以是基本通道,也可以是分层通道
二是一个通道利用端口进行间接通道调用,调用穿越了一个以上的通道
分层通道也是一个模块,也会继承 sc_module
分层通道是实例化了其它通道的通道,有时我们需要用到的分层通道如下图所示
模块 E 是一个通道,它实例化了通道 C1 和通道 D,通道 D 又例化了通道 C2
设计者希望将模块 D 中的模块 C2 和模块 C1 都导出到模块 E 中,从而对与模块 E 连接的模块 X 可见
sc_export 允许将一个通道将接口导入到其父模块,从而使得连接到父模块的其它模块可以调用该接口的方法