• [SystemC]SystemC Hierarchical Channels


    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

           栈通道覆盖栈接口中声明的纯虚方法。 这是代码:

    1. #include "systemc.h"
    2. #include "stack_if.h"
    3. // this class implements the virtual functions
    4. // in the interfaces
    5. class stack
    6. : public sc_module,
    7. public stack_write_if, public stack_read_if
    8. {
    9. private:
    10. char data[20];
    11. int top; // pointer to top of stack
    12. public:
    13. // constructor
    14. stack(sc_module_name nm) : sc_module(nm), top(0)
    15. {
    16. }
    17. bool stack::nb_write(char c)
    18. {
    19. if (top < 20)
    20. {
    21. data[top++] = c;
    22. return true;
    23. }
    24. return false;
    25. }
    26. void stack::reset()
    27. {
    28. top = 0;
    29. }
    30. bool stack::nb_read(char& c)
    31. {
    32. if (top > 0)
    33. {
    34. c = data[--top];
    35. return true;
    36. }
    37. return false;
    38. }
    39. void stack::register_port(sc_port_base& port_,
    40. const char* if_typename_)
    41. {
    42. cout << "binding " << port_.name() << " to "
    43. << "interface: " << if_typename_ << endl;
    44. }
    45. };

           有一些本地(私有)数据来存储堆栈,还有一个整数表示堆栈数组中的当前位置。nb_write 和 nb_read 函数在这里定义。 还有一个 reset() 函数,它简单地将堆栈指针设置为 0。

           最后,您会注意到一个名为 register_port 的函数——它是从哪里来的?它在 sc_interface 本身中定义,并且可以在通道中被覆盖。 通常,它用于检查端口和接口何时绑定在一起。 例如,原始通道 sc_fifo 使用 register_port 检查最多 1 个接口可以连接到 FIFO 读取或写入端口。 这个例子只是在绑定过程中打印出一些信息。

    三、Create PORT

           要使用堆栈,它必须被实例化。 在这个例子中,有两个模块,一个生产者和一个消费者。 这是生产者模块:

    1. #include "systemc.h"
    2. #include "stack_if.h"
    3. class producer : public sc_module
    4. {
    5. public:
    6. sc_port out;
    7. sc_in<bool> Clock;
    8. void do_writes()
    9. {
    10. int i = 0;
    11. char* TestString = "Hallo, This Will Be Reversed";
    12. while (true)
    13. {
    14. wait(); // for clock edge
    15. if (out->nb_write(TestString[i]))
    16. cout << "W " << TestString[i] << " at "
    17. << sc_time_stamp() << endl;
    18. i = (i+1) % 32;
    19. }
    20. }
    21. SC_CTOR(producer)
    22. {
    23. SC_THREAD(do_writes);
    24. sensitive << Clock.pos();
    25. }
    26. };

           生产者模块声明了一个与堆栈接口的端口。 这是通过以下行完成的:

    sc_port out;
    • 它声明了一个可以绑定到 stack_write_if 的端口,并且有一个名字 out。
    • 您可以将多个接口副本绑定到一个端口,并且您可以指定可以绑定的最大接口数。 例如,如果我们想允许绑定 10 个接口,我们可以将端口声明为
    sc_port10> out;
    • 如果省略数字,则默认值为 1。
    • 要实际写入堆栈,请通过端口调用方法 nb_write:
    out->nb_write('A');
    • 这通过 stack_write_if 调用 nb_write('A')。 执行此操作时必须使用指针符号 ->。
    • 从堆栈读取的消费者模块看起来非常相似,除了它声明了一个读取端口
    sc_port 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 

    1. #include "systemc.h"
    2. #include "producer.h"
    3. #include "consumer.h"
    4. #include "stack.h"
    5. int sc_main(int argc, char* argv[])
    6. {
    7. sc_clock ClkFast("ClkFast", 100, SC_NS);
    8. sc_clock ClkSlow("ClkSlow", 50, SC_NS);
    9. stack Stack1("S1");
    10. producer P1("P1");
    11. P1.out(Stack1);
    12. P1.Clock(ClkFast);
    13. consumer C1("C1");
    14. C1.in(Stack1);
    15. C1.Clock(ClkSlow);
    16. sc_start(5000, SC_NS);
    17. return 0;
    18. };
    • 注意栈的写接口是如何隐式绑定在线网中的
    P1.out(Stack1);

           这个例子有点奇怪(但完全合法!)因为堆栈本身没有端口。 它只是导致第一个 stack_write_if 被绑定在这一行中。 如果有多个stack_write_if,它们将被依次绑定。

           这是运行代码的结果:

    1. binding C1.port_0 to interface: 13stack_read_if
    2. binding P1.port_0 to interface: 14stack_write_if
    3. W H at 0 s
    4. R H at 0 s
    5. W a at 100 ns
    6. R a at 150 ns
    7. W l at 200 ns
    8. // and so on for 5000 ns
    • 关于绑定的消息来自 stack.h 中的 register_port 函数。 端口的名称 (C1.port_0...) 由 SystemC 内核制造,接口名称 (13stack_read_if...) 也是如此。

    五、结论

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

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

  • 相关阅读:
    nodejs 启动第三方exe
    2023亚太地区数学建模竞赛A题B题C题思路+模型+代码
    解决 docker 容器无法正常解析域名
    Spring boot 实践Rabbitmq消息防丢失
    SAP-QM-采购过程模式与特性检验不匹配QD244
    一行行的代码解密马尔可夫链
    Hadoop生态圈中的数据同步工具SQOOP
    React Native V0.74 — 稳定版已发布
    TiDB在线修改集群配置
    android studio启动虚拟器失败
  • 原文地址:https://blog.csdn.net/gsjthxy/article/details/126682398