摘要:本节包含一个完整的简单设计,用于演示 SystemC 中模块和流程的使用。 为简单起见,它是非常低级的 - 不是您通常在系统级设计语言中期望的编码风格!主要有以下几个部分:
为什么要看模块和流程? 原因是 SystemC 旨在同时处理硬件和软件,并允许对大型系统进行建模。
进程是与其他进程同时运行的一小段代码。 几乎所有已开发的高级系统级设计 (SLD) 工具都使用流程网络的底层模型。 SystemC 提供了支持构建独立(并发/并行)代码段网络的过程。
SLD 需要处理大型设计。 为了解决这个问题,通常使用层次结构。 层次结构在 SystemC 中通过使用模块实现,该类可以使用端口链接到其他模块。 模块允许单独处理一个设计。 模块可能包含进程和其他模块的实例。
该设计包括一个用四个 NAND 门实现的 EXOR 门。 同样,重要的是要注意这不是典型的设计风格 - 但它很好而且易于理解。 设计如下所示:

第一步是对与非门进行建模。 与非门是组合电路; 它的输出纯粹是输入值的函数。 它没有记忆,也不需要时钟。 因此,模型可以使用最简单的 SystemC 进程,即 SC_METHOD。
SC_METHOD 只是 C++ 函数。 因此,SystemC 类库必须使它们表现得像进程。 尤其是:
这是与非门的代码,在一个文件中,nand.h
- #include "systemc.h"
- SC_MODULE(nand2) // declare nand2 sc_module
- {
- sc_in<bool> A, B; // input signal ports
- sc_out<bool> F; // output signal ports
-
- void do_nand2() // a C++ function
- {
- F.write( !(A.read() && B.read()) );
- }
-
- SC_CTOR(nand2) // constructor for nand2
- {
- SC_METHOD(do_nand2); // register do_nand2 with kernel
- sensitive << A << B; // sensitivity list
- }
- };
SystemC 中的层次结构是使用类 sc_module 创建的。 sc_module 可以直接使用,也可以使用宏 SC_MODULE “隐藏”。 上面的示例 SC_MODULE 创建了一个名为 nand2 的 sc_module 类对象。
接下来是声明的输入和输出端口。 通常,使用类 sc_port 声明端口。 例如,将声明使用 sc_signal 的输入端口:
sc_portbool>,1> A,B;
但正如你所看到的,这是很多打字。 为方便起见,还可以创建和使用专用端口。 sc_in 是 sc_signal 类的专用端口的示例。
端口可以是任何 C++ 或 SystemC 类型 - 该示例使用 bool,一种内置的 C++ 类型。
接下来,声明完成这项工作的函数。 输入和输出(专用)端口包括方法 read() 和 write() 以允许读取和写入端口。 读取 A 和 B,计算 NAND 函数,并使用 write() 方法将结果写入 F。
请注意,您通常可以在不使用 read() 和 write() 方法的情况下摆脱困境,因为 = 运算符和类型转换运算符已被重载。 所以你可以写:
F = !(A && B);
但是使用 read() 和 write() 是一个好习惯,因为它可以帮助 C++ 编译器消除表达式的歧义。
函数do_nand2()写完后,就有了sc_module实例nand2的构造函数。 SystemC 提供了一种简写方式,使用宏 SC_CTOR。 构造函数执行以下操作:
也可以在这里初始化任何需要初始化的东西——例如,可以初始化一个类数据成员。
在上面的示例中,构造函数声明 do_nand2 是一个 SC_METHOD,并表示端口 A 和 B 上的任何事件都必须使内核运行该函数(从而为 F 计算一个新值)。
EXOR 门由 NAND 门的四个副本(或实例)构成。 这是通过使用 EXOR 门构造函数连接 NAND 门实例来实现的。 这是 EXOR 门的代码:
- #include "systemc.h"
- #include "nand2.h"
- SC_MODULE(exor2)
- {
- sc_in<bool> A, B;
- sc_out<bool> F;
-
- nand2 n1, n2, n3, n4;
-
- sc_signal<bool> S1, S2, S3;
-
- SC_CTOR(exor2) : n1("N1"), n2("N2"), n3("N3"), n4("N4")
- {
- n1.A(A);
- n1.B(B);
- n1.F(S1);
-
- n2.A(A);
- n2.B(S1);
- n2.F(S2);
-
- n3.A(S1);
- n3.B(B);
- n3.F(S3);
-
- n4.A(S2);
- n4.B(S3);
- n4.F(F);
- }
- };
开头看起来与 NAND 门非常相似,但请注意它包含文件 nand2.h。这允许访问包含与非门的模块。
创建了模块 exor2,并声明了端口。请注意,允许重复使用名称 A、B 和 F,因为这是层次结构的不同级别。
原始图表显示了一些连接 NAND 门的“线”。这些是通过声明 sc_signals S1、S2 和 S3 创建的。 sc_signal 是一个带有模板参数的类,该参数指定信号可以保存的数据类型 - 在本例中为 bool。 sc_signal 是原始通道的示例,它是 SystemC 类库中的内置通道。它的行为类似于 VHDL 中的信号。
EXOR 门的构造函数比 NAND 门的构造函数更复杂,因为它必须有四个 nand2 实例。在端口声明之后,声明了 nand2 的四个实例:n1、n2、n3 和 n4。必须给每个实例一个标签。四个标签“N1”、“N2”、“N3”和“N4”通过使用 exor2 构造函数上的初始化列表传递给 nand2 实例的构造函数。
最后,将端口连接起来。如图所示,这是在构造函数中完成的。
为了测试设计,有一个刺激发生器。 这是另一个模块,与上面的非常相似。 唯一重要的一点是它使用了一个线程 (SC_THREAD),这是一种可以暂停的进程。 这是 stim.h 的代码:
- #include "systemc.h"
- SC_MODULE(stim)
- {
- sc_out<bool> A, B;
- sc_in<bool> Clk;
-
- void StimGen()
- {
- A.write(false);
- B.write(false);
- wait();
- A.write(false);
- B.write(true);
- wait();
- A.write(true);
- B.write(false);
- wait();
- A.write(true);
- B.write(true);
- wait();
- sc_stop();
- }
- SC_CTOR(stim)
- {
- SC_THREAD(StimGen);
- sensitive << Clk.pos();
- }
- };
这是顶层 - 它位于包含上述所有子模块的文件 main.cpp 中:
- #include "systemc.h"
- #include "stim.h"
- #include "exor2.h"
- #include "mon.h"
-
- int sc_main(int argc, char* argv[])
- {
- sc_signal<bool> ASig, BSig, FSig;
- sc_clock TestClk("TestClock", 10, SC_NS,0.5);
-
- stim Stim1("Stimulus");
- Stim1.A(ASig);
- Stim1.B(BSig);
- Stim1.Clk(TestClk);
-
- exor2 DUT("exor2");
- DUT.A(ASig);
- DUT.B(BSig);
- DUT.F(FSig);
-
- mon Monitor1("Monitor");
- Monitor1.A(ASig);
- Monitor1.B(BSig);
- Monitor1.F(FSig);
- Monitor1.Clk(TestClk);
-
- sc_start(); // run forever
-
- return 0;
-
- };
包括模块的头文件,声明用于布线的顶层信号,以及使用 sc_clock 创建的时钟; 然后每个模块都被实例化和连接。之后,调用 sc_start() 会启动模拟并永远运行(或者更确切地说,直到在刺激模块中遇到对 sc_stop() 的调用)。
- Time A B F
- 0 s 0 0 1
- 10 ns 0 0 0
- 20 ns 0 1 1
- 30 ns 1 0 1
- 40 ns 1 1 0
SystemC 库包含一个仿真内核。这决定了运行哪些进程(软件线程)。在时间 0,所有 SC_METHOD 和 SC_THREAD 将以未定义的顺序运行,直到它们挂起。然后 SC_CTHREADs 将在时钟沿出现时运行。上述问题是由于多种情况造成的:
为了证明是这样,可以修改sc_clock语句来延迟时钟的第一个边沿,如下:
sc_clock TestClk("TestClock", 10, SC_NS,0.5, 1, SC_NS);
最后的 1,SC_NS 参数指定第一个时钟边沿出现之前的 1 ns 延迟。 现在时间已经过去,所以 F 将被更新。 这是对应的输出:
- Time A B F
- 1 ns 0 0 0
- 11 ns 0 0 0
- 21 ns 0 1 1
- 31 ns 1 0 1
- 41 ns 1 1 0
对模块和流程的快速浏览到此结束。 您已经看到了解 SystemC 仿真内核的并发特性以及 sc_signal 原始通道的行为的重要性。
您还看到了在顶级模块中实例化较低级别模块的一些基本示例,以及如何使用 sc_main。