[SystemC]SystemC Hierarchical Channels

本文深入探讨了SystemC中的分层通道,这些通道构成了系统级建模的基础,允许复杂的通信行为。通过接口声明和实现,SystemC实现了通信与行为的分离,促进模块间的抽象级别转换。分层通道作为实现接口的模块,通过端口进行访问。文中通过一个堆栈模型的示例展示了如何定义接口、创建分层通道并绑定端口。在实际应用中,生产者和消费者模块通过端口调用接口方法进行读写操作,实现了数据的传递。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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...) 也是如此。

五、结论

       上面显示的示例非常简单,但有很多东西需要学习和理解。 它让您快速浏览定义和编写自己的分层通道的过程。 要记住的关键点是:

  1. 分层通道允许复杂的总线模型
  2. 使用接口将通信和行为分开
  3. 可以使用端口从模块外部访问接口
  4. 当你通过接口调用通道中的方法时,你不必知道它是如何实现的——你只需要知道接口调用的声明
  5. 当通道中定义的方法被执行时,它们会在调用者的上下文中执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

元直数字电路验证

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值