我是靠谱客的博主 生动鸡翅,最近开发中收集的这篇文章主要介绍[SystemC]SystemC中的模块和程序SystemC中的模块和程序,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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 将在时钟沿出现时运行。上述问题是由于多种情况造成的:

  1. sc_clock 语句在时间 0 产生一个上升沿,因此监视器和激励进程都将运行(以未定义的顺序,不知道哪个将首先运行)。
  2. C++ 中的变量并不总是具有定义的初始值(除非它们被声明为静态)。所以 F 持有的数据值恰好从 1 开始(真)。
  3. 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中的模块和程序所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(56)

评论列表共有 0 条评论

立即
投稿
返回
顶部