SystemC Hierarchical Channels
摘要:分层通道构成了 SystemC 系统级建模能力的基础。它们基于通道可能包含相当复杂的行为的想法——例如,它可能是一个完整的片上总线。
另一方面,原始通道不能包含内部结构,因此通常更简单(例如,您可以认为 sc_signal 的行为有点像一根电线)。
为了构建复杂的系统级模型,SystemC 使用将通道定义为实现接口的事物的想法。接口是访问给定通道的可用方法的声明。例如,在 sc_signal 的情况下,接口由两个类 sc_signal_in_if 和 sc_signal_out_if 声明,它们声明访问方法(例如 read() 和 write())。
通过将接口的声明与其方法的实现区分开来,SystemC 促进了一种将通信与行为分离的编码风格,这是促进从一个抽象级别到另一个抽象级别的改进的关键特征。
SystemC 的一项附加功能是,如果您希望模块通过通道进行通信,则必须使用模块上的端口来访问这些通道。端口充当代理,代表调用模块将方法调用转发到通道。
分层通道在 SystemC 中作为模块实现,实际上它们是从 sc_module 派生的。原始通道有自己的基类 sc_prim_channel。总结一下:
- 将沟通与行为分开,便于细化
- 接口声明了一组访问通道的方法
- 分层通道是实现接口方法的模块
- 端口允许模块调用接口中声明的方法
编写分层通道是一个很大的话题,所以本章将只展示一个基本示例,一个堆栈模型。
一、定义接口
堆栈将提供一个读取方法和一个写入方法。 这些方法是非阻塞的(它们会立即返回而无需等待)。 为了使它可用,每个方法都返回一个布尔值,说明它是否成功。 例如,如果读取时堆栈为空,则 nb_read()(读取方法)返回 false。
这些方法在两个单独的接口中声明,一个写接口 stack_write_if 和一个读接口 stack_read_if。 这是包含声明的文件 stack_if.h。
代码到这里但不起作用?
这些接口派生自所有接口的基类 sc_interface。 它们应该使用关键字 virtual 派生,如上所示。
方法 nb_write、nb_read 和 reset 被声明为纯虚拟 - 这意味着它们必须在任何派生类中实现。
二、Stack Channel
栈通道覆盖栈接口中声明的纯虚方法。 这是代码:
#include "systemc.h"
#include "stack_if.h"
// this class implements the virtual functions
// in the interfaces
class stack
: public sc_module,
public stack_write_if, public stack_read_if
{
private:
char data[20];
int top; // pointer to top of stack
public:
// constructor
stack(sc_module_name nm) : sc_module(nm), top(0)
{
}
bool stack::nb_write(char c)
{
if (top < 20)
{
data[top++] = c;
return true;
}
return false;
}
void stack::reset()
{
top = 0;
}
bool stack::nb_read(char& c)
{
if (top > 0)
{
c = data[--top];
return true;
}
return false;
}
void stack::register_port(sc_port_base& port_,
const char* if_typename_)
{
cout << "binding " << port_.name() << " to "
<< "interface: " << if_typename_ << endl;
}
};
有一些本地(私有)数据来存储堆栈,还有一个整数表示堆栈数组中的当前位置。nb_write 和 nb_read 函数在这里定义。 还有一个 reset() 函数,它简单地将堆栈指针设置为 0。
最后,您会注意到一个名为 register_port 的函数——它是从哪里来的?它在 sc_interface 本身中定义,并且可以在通道中被覆盖。 通常,它用于检查端口和接口何时绑定在一起。 例如,原始通道 sc_fifo 使用 register_port 检查最多 1 个接口可以连接到 FIFO 读取或写入端口。 这个例子只是在绑定过程中打印出一些信息。
三、Create PORT
要使用堆栈,它必须被实例化。 在这个例子中,有两个模块,一个生产者和一个消费者。 这是生产者模块:
#include "systemc.h"
#include "stack_if.h"
class producer : public sc_module
{
public:
sc_port<stack_write_if> out;
sc_in<bool> Clock;
void do_writes()
{
int i = 0;
char* TestString = "Hallo, This Will Be Reversed";
while (true)
{
wait(); // for clock edge
if (out->nb_write(TestString[i]))
cout << "W " << TestString[i] << " at "
<< sc_time_stamp() << endl;
i = (i+1) % 32;
}
}
SC_CTOR(producer)
{
SC_THREAD(do_writes);
sensitive << Clock.pos();
}
};
生产者模块声明了一个与堆栈接口的端口。 这是通过以下行完成的:
sc_port<stack_write_if> out;
- 它声明了一个可以绑定到 stack_write_if 的端口,并且有一个名字 out。
- 您可以将多个接口副本绑定到一个端口,并且您可以指定可以绑定的最大接口数。 例如,如果我们想允许绑定 10 个接口,我们可以将端口声明为
sc_port<stack_write_if,10> out;
- 如果省略数字,则默认值为 1。
- 要实际写入堆栈,请通过端口调用方法 nb_write:
out->nb_write('A');
- 这通过 stack_write_if 调用 nb_write('A')。 执行此操作时必须使用指针符号 ->。
- 从堆栈读取的消费者模块看起来非常相似,除了它声明了一个读取端口
sc_port<stack_read_if> in;
- and calls nb_read, e.g.
in->nb_read(c);
- 其中 c 是 char 类型。
- 请注意,如果成功,nb_read 和 nb_write 都会返回值 true。
也许最有趣的是函数 nb_write 和 nb_read 在调用者的上下文中执行。 换句话说,它们作为在生产者和消费者模块中声明的 SC_THREAD 的一部分执行。
四、Binding Ports and Interfaces
Here is the code for sc_main, the top level of the design
#include "systemc.h"
#include "producer.h"
#include "consumer.h"
#include "stack.h"
int sc_main(int argc, char* argv[])
{
sc_clock ClkFast("ClkFast", 100, SC_NS);
sc_clock ClkSlow("ClkSlow", 50, SC_NS);
stack Stack1("S1");
producer P1("P1");
P1.out(Stack1);
P1.Clock(ClkFast);
consumer C1("C1");
C1.in(Stack1);
C1.Clock(ClkSlow);
sc_start(5000, SC_NS);
return 0;
};
- 注意栈的写接口是如何隐式绑定在线网中的
P1.out(Stack1);
这个例子有点奇怪(但完全合法!)因为堆栈本身没有端口。 它只是导致第一个 stack_write_if 被绑定在这一行中。 如果有多个stack_write_if,它们将被依次绑定。
这是运行代码的结果:
binding C1.port_0 to interface: 13stack_read_if
binding P1.port_0 to interface: 14stack_write_if
W H at 0 s
R H at 0 s
W a at 100 ns
R a at 150 ns
W l at 200 ns
// and so on for 5000 ns
- 关于绑定的消息来自 stack.h 中的 register_port 函数。 端口的名称 (C1.port_0...) 由 SystemC 内核制造,接口名称 (13stack_read_if...) 也是如此。
五、结论
上面显示的示例非常简单,但有很多东西需要学习和理解。 它让您快速浏览定义和编写自己的分层通道的过程。 要记住的关键点是:
- 分层通道允许复杂的总线模型
- 使用接口将通信和行为分开
- 可以使用端口从模块外部访问接口
- 当你通过接口调用通道中的方法时,你不必知道它是如何实现的——你只需要知道接口调用的声明
- 当通道中定义的方法被执行时,它们会在调用者的上下文中执行