当前位置: 首页 > news >正文

[SystemC]Primitive Channels and the Kernel

SystemC Primitive Channels and the Kernel

       摘要:本节介绍了 SystemC 仿真内核的一些操作,然后将其与原始通道的行为联系起来。

一、Simulation Kernels

       大多数建模语言,例如 VHDL,都使用仿真内核。 内核的目的是确保并行活动(并发)被正确建模。 在 VHDL 的情况下,做出了一个基本决定,即模拟的行为不应依赖于在模拟时间的每个步骤中执行过程的顺序。 本节首先描述 VHDL 中发生的事情,因为 SystemC 模仿了 VHDL 仿真内核的一些关键方面,但也允许定义其他计算模型。 一旦理解了 VHDL,就可以扩展讨论以结合更通用的 SystemC 仿真内核。

       例如,假设在 SystemC 中有两个 SC_THREAD,都对触发器敏感。

SC_THREAD(proc_1);
sensitive << Trig.pos();
SC_THREAD(proc_2);
sensitive << Trig.pos();

       当触发器由低变高时,哪个进程会先运行?更重要的是,这重要吗?在使用 VHDL 的类似情况下,您真的不在乎。这是因为在 VHDL 中,进程之间的通信是通过信号完成的,并且进程执行和信号更新分为两个独立的阶段。

       VHDL 仿真内核依次执行每个进程,但任何由此产生的信号变化都不会立即发生。 SystemC 和 sc_signal 也是如此。准确地说,分配计划在将来发生,这意味着当所有当前活动的进程都已被评估并达到需要暂停并等待某些事件发生的点时。

       可能没有经过模拟时间。如果是这种情况,并且信号有待处理的更新,则对这些信号做出反应的进程将再次运行,而不会经过时间。这被称为“增量周期”,并且具有在没有模拟时间经过的情况下明确确定通信进程执行顺序的效果。

       SystemC 也可以做到这一点,但也可以以其他方式对并发、通信和时间进行建模。

二、Non-determinism

       当 VHDL 中的信号或 SystemC 中的 sc_signals 用于进程间通信时,仿真是确定性的; 它在任何模拟工具上的行为都相同。

       然而,在 SystemC 中,该语言允许不确定性。 例如,假设在一个类中声明的变量从两个不同的 SC_THREAD 访问,如上所述。 这是一个例子:

SC_MODULE(nondet)
{
  sc_in Trig;

  int SharedVariable;
  void proc_1()
  {
    SharedVariable = 1;
    cout << SharedVariable << endl;
  }

  void proc_2()
  {
    SharedVariable = 2;
    cout << SharedVariable << endl;
  }

  SC_CTOR(nondet)
  {
    SC_THREAD(proc_1);
    sensitive << Trig.pos();
    SC_THREAD(proc_2);
    sensitive << Trig.pos();
  }
};
  • 在此示例中,哪个 SC_THREAD 将首先运行是未定义的 - 无法确定哪个将首先运行。
  • 对于硬件建模,这是不可接受的。但是对于软件建模,这可能代表一个使用共享变量的系统,并且非确定性并不重要——所需要的只是保证两个进程不能同时访问该变量。
  • 软件工程师使用互斥(mutex)或信号量等概念来应对这种情况。

 

三、Events and Notifications

       看完背景,现在可以总结一下SystemC仿真内核的操作了。

       SystemC 仿真内核支持增量周期的概念。增量周期由评估阶段和更新阶段组成。这通常用于对无法立即更改的原始通道进行建模,例如 sc_signal。通过分离评估和更新两个阶段,可以保证确定性行为(因为原始通道在更新阶段发生之前不会改变值 - 在评估阶段它不能立即改变)。

       但是,SystemC 可以对软件进行建模,在这种情况下,能够使进程在没有增量周期的情况下运行(即不执行更新阶段)很有用。这需要立即通知事件(立即通知)。立即通知可能会导致不确定的行为。

       事件的通知是通过调用 sc_event 类的 notify() 方法来实现的。

       对于 notify 方法,需要考虑三种情况。

  • 不带参数的 notify():立即通知。对事件敏感的进程将在当前评估阶段运行
  • 带有零时间参数的 notify():增量通知。对事件敏感的进程将在下一个增量周期的评估阶段运行
  • 带有非零时间参数的 notify():定时通知。对事件敏感的进程将在未来某个模拟时间的评估阶段运行

       notify() 方法取消任何未决通知,并对现有通知的状态进行各种检查。

       现在可以描述模拟内核的行为:

  1. 初始化:以未指定的顺序执行所有进程(SC_CTHREADs 除外)。
  2. 评估:选择一个准备好运行并恢复其执行的进程。这可能会导致立即发生事件通知,这可能会导致其他进程准备好在同一阶段运行。
  3. 重复步骤 2,直到没有准备好运行的进程。
  4. 更新:执行步骤 2 中对 request_update() 的调用导致的所有挂起的 update() 调用。
  5. 如果在第 2 步或第 4 步期间发出任何增量事件通知,请确定哪些进程由于所有这些事件而准备好运行并返回到第 2 步。
  6. 如果没有定时事件,则模拟结束。
  7. 将模拟时间提前到最早挂起的定时事件通知的时间。
  8. 由于当前时间的所有定时事件,确定哪些进程已准备好运行,然后返回步骤 2。
     

       注意函数 update() 和 request_update()。内核提供这些函数专门用于对原始通道(如 sc_signal)进行建模。如果在评估阶段通过调用 request_update() 请求 update() 实际上在更新阶段运行。

 

四、A primitive channel

       那么如何编写原始通道呢?它实际上非常简单!首先,所有原始通道都基于类 sc_prim_channel - 您可以将其视为分层通道的 sc_module 的原始通道等效项。

       这是 FIFO 通道的代码,它是“内置”sc_fifo 通道的一个大大简化的版本。它的简化之处在于它只提供了阻塞方法,并且它不是模板类(它只适用于 char 类型)。

       这是接口fifo_if.h

#include "systemc.h"
class fifo_out_if :  virtual public sc_interface
{
public:
  virtual void write(char) = 0;          // blocking write
  virtual int num_free() const = 0;      // free entries
protected:
  fifo_out_if()
  {
  };
private:
  fifo_out_if (const fifo_out_if&);      // disable copy
  fifo_out_if& operator= (const fifo_out_if&); // disable
};

class fifo_in_if :  virtual public sc_interface
{
public:
  virtual void read(char&) = 0;          // blocking read
  virtual char read() = 0;
  virtual int num_available() const = 0; // available
                                         // entries
protected:
  fifo_in_if()
  {
  };
private:
  fifo_in_if(const fifo_in_if&);            // disable copy
  fifo_in_if& operator= (const fifo_in_if&); // disable =
};
  • 基本上,有一种读写方法,这两种方法都是阻塞的,即如果 FIFO 为空(读)或满(写),它们就会挂起。

       这是频道第一部分的代码。

#include "systemc.h"
#include "fifo_if.h"

class fifo
: public sc_prim_channel, public fifo_out_if,
  public fifo_in_if
{
protected:
  int size;                 // size
  char* buf;                // fifo buffer
  int free;                 // free space
  int ri;                   // read index
  int wi;                   // write index
  int num_readable;
  int num_read;
  int num_written;

  sc_event data_read_event;
  sc_event data_written_event;

public:
  // constructor
  explicit fifo(int size_ = 16)
  : sc_prim_channel(sc_gen_unique_name("myfifo"))
  {
    size = size_;
    buf = new char[size];
    reset();
  }

  ~fifo()                   //destructor
  {
    delete [] buf;
  };

注意:

  • 通道来自 sc_prim_channel,而不是 sc_module
  • 构造函数自动生成一个内部名称,因此用户不必指定一个
  • 构造函数使用动态内存分配,所以还必须有析构函数来删除声明的内存
  • 创建了两个 sc_event 对象。 当空间变得可用(如果写入被阻止)或数据变得可用(当读取被阻止)时,这些用于向被阻止的读取和写入进程发出信号
     

       接下来的几个函数用于计算是否有可用空间以及有多少可用空间。 该算法使用循环缓冲区,由写入索引 (wi) 和读取索引 (ri) 访问。

  int num_available() const
  {
    return num_readable - num_read;
  }

  int num_free() const
  {
    return size - num_readable - num_written;
  }

       这是阻塞写入功能。 请注意,如果 num_free() 返回零,则函数调用 wait(data_read_event)。 这是动态灵敏度的一个例子。 调用 write 的线程将被挂起,直到通知 data_read_event。

void write(char c)        // blocking write
  {
    if (num_free() == 0)
      wait(data_read_event);
    num_written++;
    buf[wi] = c;
    wi = (wi + 1) % size;
    free--;
    request_update();
  }
  • 一旦进程在 data_read_event 之后恢复,它将字符存储在循环缓冲区中,然后调用 request_update()。
  • request_update() 确保模拟内核在内核更新阶段调用 update()。

       这是清除 FIFO 的复位函数。

void reset()
  {
    free = size;
    ri = 0;
    wi = 0;
  }

       这是读取功能。 行为类似于 write 函数,只是这次如果没有可用空间(FIFO 已满)则进程阻塞。

  void read(char& c)        // blocking read
  {
    if (num_available() == 0)
      wait(data_written_event);
    num_read++;
    c = buf[ri];
    ri = (ri + 1) % size;
    free++;
    request_update();
  }

       为方便起见,这里有一个 read 的“快捷”版本,所以我们可以使用

char c = portname->read();
  • 语法:
  char read()                // shortcut read function
  {
    char c;
    read(c);
    return c;
  }

       最后是 update() 方法本身。 这在模拟内核的更新阶段被调用。 它检查在评估阶段是否读取或写入了数据,然后酌情调用 notify(SC_ZERO_TIME) 以告知阻塞的 read() 或 write() 函数它们可以继续。

void update()
  {
    if (num_read > 0)
      data_read_event.notify(SC_ZERO_TIME);
    if (num_written > 0)
      data_written_event.notify(SC_ZERO_TIME);
    num_readable = size - free;
    num_read = 0;
    num_written = 0;
  }
};

       设计的顶层看起来与分层通道非常相似。 这是(来自main.cpp)

#include "systemc.h"
#include "producer.h"
#include "consumer.h"
#include "fifo.h"

int sc_main(int argc, char* argv[])
{
  sc_clock ClkFast("ClkFast", 1, SC_NS);
  sc_clock ClkSlow("ClkSlow", 500, SC_NS);

  fifo fifo1;

  producer P1("P1");
  P1.out(fifo1);
  P1.Clock(ClkFast);

  consumer C1("C1");
  C1.in(fifo1);
  C1.Clock(ClkSlow);

  sc_start(5000, SC_NS);

  return 0;
};
  •  请注意,由于使用了 sc_gen_unique_name(),因此无需为 FIFO 原始通道命名。

五、结论

       本章展示了编写原始通道的一瞥。 还有更多详细信息,您可能需要查看 sc_signal 或 sc_fifo 的源代码以了解更多信息。

       需要特别注意的是动态灵敏度的使用——注意阻塞的读写功能实际上是如何覆盖对时钟信号的静态灵敏度的。

相关文章:

  • Java feign方式对同一个服务编写多个远程调用实例报错及3种解决办法
  • kubernetes API Server 没有 bind 0.0.0.0
  • 猿创征文|深度剖析复杂的菱形继承与菱形虚拟继承
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • 想进大厂?这份面试真题你刷了吗?
  • CentOS 7最小化安装没有ifconfig
  • 小功能⭐️Unity快捷键、路径及常用特性
  • 备份和恢复Gitlab数据
  • Kali在线安装包一些小问题
  • vue中常用的修饰符
  • 骨架图算法
  • Git --》如何在IDEA中玩转Git与GitHub?
  • C++中的继承(继承基本概念、菱形虚拟继承内存模型)
  • 怎样从零开始训练一个AI车手?
  • 【Spring Cloud】新闻头条微服务项目:文章内容安全审核(新增DFA+OCR过滤敏感词需求)
  • @angular/forms 源码解析之双向绑定
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 【node学习】协程
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • C++类中的特殊成员函数
  • CSS 提示工具(Tooltip)
  • ES6语法详解(一)
  • Java程序员幽默爆笑锦集
  • Linux学习笔记6-使用fdisk进行磁盘管理
  • Mybatis初体验
  • React Transition Group -- Transition 组件
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • vue-cli在webpack的配置文件探究
  • 前端js -- this指向总结。
  • 如何设计一个比特币钱包服务
  • 深入浅出webpack学习(1)--核心概念
  • 数组大概知多少
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • 与 ConTeXt MkIV 官方文档的接驳
  • 正则表达式
  • PostgreSQL之连接数修改
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • ​Java并发新构件之Exchanger
  • ​ubuntu下安装kvm虚拟机
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (八十八)VFL语言初步 - 实现布局
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (转)winform之ListView
  • .net 反编译_.net反编译的相关问题
  • .net2005怎么读string形的xml,不是xml文件。
  • .NET中使用Protobuffer 实现序列化和反序列化
  • @ 代码随想录算法训练营第8周(C语言)|Day57(动态规划)
  • @property @synthesize @dynamic 及相关属性作用探究
  • @vue/cli脚手架
  • @四年级家长,这条香港优才计划+华侨生联考捷径,一定要看!