概述
SystemC中的模块和程序
摘要:本节包含一个完整的简单设计,用于演示 SystemC 中模块和流程的使用。 为简单起见,它是非常低级的 - 不是您通常在系统级设计语言中期望的编码风格!主要有以下几个部分:
- Creating hierarchy
- The sc_signal primitive channel
- (Specialized) ports
- Processes (SC_METHOD, SC_THREAD, SC_CTHREAD)
- A simple test bench
一、SystemC Background
为什么要看模块和流程? 原因是 SystemC 旨在同时处理硬件和软件,并允许对大型系统进行建模。
进程是与其他进程同时运行的一小段代码。 几乎所有已开发的高级系统级设计 (SLD) 工具都使用流程网络的底层模型。 SystemC 提供了支持构建独立(并发/并行)代码段网络的过程。
SLD 需要处理大型设计。 为了解决这个问题,通常使用层次结构。 层次结构在 SystemC 中通过使用模块实现,该类可以使用端口链接到其他模块。 模块允许单独处理一个设计。 模块可能包含进程和其他模块的实例。
二、设计实例分析
该设计包括一个用四个 NAND 门实现的 EXOR 门。 同样,重要的是要注意这不是典型的设计风格 - 但它很好而且易于理解。 设计如下所示:
第一步是对与非门进行建模。 与非门是组合电路; 它的输出纯粹是输入值的函数。 它没有记忆,也不需要时钟。 因此,模型可以使用最简单的 SystemC 进程,即 SC_METHOD。
SC_METHOD 只是 C++ 函数。 因此,SystemC 类库必须使它们表现得像进程。 尤其是:
- SystemC 类库包含一个模拟内核——一段模拟时间流逝的代码,并在输入发生变化时调用函数来计算其输出。
- 该函数必须声明为 SC_METHOD 并对其输入敏感。
这是与非门的代码,在一个文件中,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_port<sc_signal_in_if<bool>,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 计算一个新值)。
三、Hierarchy
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();
}
};
- 请注意对 sc_stop() 的最终调用,它使模拟停止。 监视器代码看起来非常相似,因此被省略 - 它位于文件 mon.h 中。
这是顶层 - 它位于包含上述所有子模块的文件 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
- 如果你看它,你会注意到一些非常奇怪的东西——时间 0 的第一行说 F 是 1(真),而 A 和 B 是 0——这不是一个非常令人信服的异或门!到 10 ns,一切正常。时间 0 发生了什么?
五、仿真
SystemC 库包含一个仿真内核。这决定了运行哪些进程(软件线程)。在时间 0,所有 SC_METHOD 和 SC_THREAD 将以未定义的顺序运行,直到它们挂起。然后 SC_CTHREADs 将在时钟沿出现时运行。上述问题是由于多种情况造成的:
- sc_clock 语句在时间 0 产生一个上升沿,因此监视器和激励进程都将运行(以未定义的顺序,不知道哪个将首先运行)。
- C++ 中的变量并不总是具有定义的初始值(除非它们被声明为静态)。所以 F 持有的数据值恰好从 1 开始(真)。
- do_nand2 SC_METHOD在时间0运行,调度F更新,但是F是一个信号,不能瞬间更新,所以monitor进程运行时值1仍然存在。
为了证明是这样,可以修改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
- 现在你可以看到 F 总是正确的。
六、结论
对模块和流程的快速浏览到此结束。 您已经看到了解 SystemC 仿真内核的并发特性以及 sc_signal 原始通道的行为的重要性。
您还看到了在顶级模块中实例化较低级别模块的一些基本示例,以及如何使用 sc_main。
最后
以上就是生动鸡翅为你收集整理的[SystemC]SystemC中的模块和程序SystemC中的模块和程序的全部内容,希望文章能够帮你解决[SystemC]SystemC中的模块和程序SystemC中的模块和程序所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复