摘要:分层通道构成了 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 被声明为纯虚拟 - 这意味着它们必须在任何派生类中实现。
栈通道覆盖栈接口中声明的纯虚方法。 这是代码:
- #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 读取或写入端口。 这个例子只是在绑定过程中打印出一些信息。
要使用堆栈,它必须被实例化。 在这个例子中,有两个模块,一个生产者和一个消费者。 这是生产者模块:
- #include "systemc.h"
- #include "stack_if.h"
-
- class producer : public sc_module
- {
- public:
-
- sc_port
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 out;
sc_port10> out;
out->nb_write('A');
sc_port in;
in->nb_read(c);
也许最有趣的是函数 nb_write 和 nb_read 在调用者的上下文中执行。 换句话说,它们作为在生产者和消费者模块中声明的 SC_THREAD 的一部分执行。
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
上面显示的示例非常简单,但有很多东西需要学习和理解。 它让您快速浏览定义和编写自己的分层通道的过程。 要记住的关键点是: