SystemC 三大通信机制实践:sc_signal /sc_buffer/sc_event 核心区别与适用场景详解

前言

做 SystemC 仿真、模块间通信时,新手最容易踩坑的就是三种核心交互方式:sc_signalsc_buffersc_event

很多人疑惑:

  • 为什么sc_signal跨模块传信号永远晚一拍
  • 换成sc_buffer明明值立即更新,为什么线程wait还是有延迟?
  • 想要完全零延迟握手、跨模块唤醒,该用什么?

本文从底层调度机制、读写行为、事件触发、模块互联方式、适用场景五个维度,彻底讲透三者区别,同时给出工程选型标准,告别仿真时序错乱、握手延迟问题。

一、底层核心基础:SystemC Delta 周期机制

先铺垫最关键的底层逻辑,所有差异都源于它:SystemC 仿真采用两阶段调度

  1. 评估阶段:所有 SC_THREAD/SC_METHOD 并发执行,执行赋值、逻辑运算;
  2. 更新阶段 :统一刷新sc_signal暂存值、触发信号变化事件。

物理时间不变时,Delta 周期是仿真最小时序单位,也是 "晚一拍" 的根本来源。

二、sc_signal<T>:标准硬件信号(带时序延迟)

1. 核心行为

  1. 执行sig.write(val)时,不会立即更新当前值,仅把新值存入内部缓存;
  2. 必须等到当前 Delta 周期结束、进入更新阶段才刷新信号真实值;
  3. 同 Delta 周期内,写完立刻读,读到的还是旧值
  4. 信号变化事件default_event()/posedge_event()/negedge_event(),仅在更新阶段才触发。

2. 特点

  • 严格遵循硬件时序,天然模拟寄存器、时序逻辑、时钟同步信号
  • 支持sc_trace抓取波形,可在 GTKWave 查看时序;
  • 遵守单写者规则 :同一 Delta 周期仅允许一个进程写,开启DEBUG_SYSTEMC会自动报冲突错误;
  • 跨模块端口互联友好:可直接搭配sc_in/sc_out端口连线。

3. 致命短板

跨模块、跨线程通信必然晚 1 个 Delta 周期,做即时握手、立即响应场景完全不适用。

4. 适用场景

  • 时钟、复位等时序同步信号;
  • 模拟寄存器、流水线级间时序传递;
  • 需要严格贴合真实硬件一拍一节奏的设计。

5. 实践

假设有以下这个场景,模块A有一个输出作为请求,模块B有从模块A接受请求,当收到请求后模块B会返回一个响应,模块A撤销请求。

cpp 复制代码
#include <systemc.h>
using namespace sc_core;

// ==============================================
// 模块 B:接收请求 → 回传响应
// ==============================================
SC_MODULE(ModuleB) {
    sc_in<bool>  req;    // 来自 A 的请求
    sc_out<bool> ack;    // 发往 A 的响应

    SC_CTOR(ModuleB) {
        SC_THREAD(handle_req);
        sensitive << req;
    }

    void handle_req() {
        while (true) {
            // 等待 req 上升沿(收到请求)
            while(!req.read()){
                wait(10,SC_NS);
            }
            cout << sc_time_stamp() << " ModuleB:收到请求,回复 ack" << endl;

            // 回复响应
            ack.write(true);
            wait(10, SC_NS);   // 模拟处理延迟

            // 等待 A 撤销请求
            while(req.read()){
                wait(10,SC_NS);
            }
            cout << sc_time_stamp() << " ModuleB:请求已撤销,拉低 ack" << endl;
            ack.write(false);
        }
    }
};

// ==============================================
// 模块 A:发送请求 → 等待响应 → 撤销请求
// ==============================================
SC_MODULE(ModuleA) {
    sc_out<bool> req;
    sc_in<bool>  ack;

    SC_CTOR(ModuleA) {
        req.initialize(false);
        SC_THREAD(send_request);
    }

    void send_request() {
        // 发送请求
        wait(10, SC_NS);
        cout << sc_time_stamp() << " ModuleA:发送请求 req=1" << endl;
        req.write(true);

        // 等待响应
        while(!ack.read()){
            wait(10,SC_NS);
        }
        cout << sc_time_stamp() << " ModuleA:收到响应,撤销请求 req=0" << endl;

        // 撤销请求
        req.write(false);
    }
};

// ==============================================
// 顶层:连接信号
// ==============================================
SC_MODULE(Top) {
    sc_signal<bool> req_sig;
    sc_signal<bool> ack_sig;

    ModuleA* a;
    ModuleB* b;

    SC_CTOR(Top) {
        a = new ModuleA("ModuleA");
        b = new ModuleB("ModuleB");

        a->req(req_sig);
        a->ack(ack_sig);
        b->req(req_sig);
        b->ack(ack_sig);
    }
};

// ==============================================
// 主函数
// ==============================================
int sc_main(int, char**) {
    Top top("top");
    sc_start(200, SC_NS);
    return 0;
}

运行结果:

SystemC 3.0.2-Accellera --- Mar 27 2026 21:51:17

Copyright (c) 1996-2025 by all Contributors,

ALL RIGHTS RESERVED

10 ns ModuleA:发送请求 req=1

20 ns ModuleB:收到请求,回复 ack

30 ns ModuleA:收到响应,撤销请求 req=0

40 ns ModuleB:请求已撤销,拉低 ack

虽然A在10ns的时候发起的请求,但是B在20ns才真正的感知到,相当于数据晚了一拍。

上面的握手行为修改为sc_signal的event:

cpp 复制代码
#include <systemc.h>
using namespace sc_core;

// ==============================================
// 模块 B:接收请求 → 回传响应
// ==============================================
SC_MODULE(ModuleB) {
    sc_in<bool>  req;    // 来自 A 的请求
    sc_out<bool> ack;    // 发往 A 的响应

    SC_CTOR(ModuleB) {
        SC_THREAD(handle_req);
        sensitive << req;
    }

    void handle_req() {
        while (true) {
            // 等待 req 上升沿(收到请求)
            wait(req.posedge_event());
            cout << sc_time_stamp() << " ModuleB:收到请求 "<< req.read() <<",回复 ack" << endl;

            // 回复响应
            ack.write(true);

            // 等待 A 撤销请求
            wait(req.negedge_event());
            cout << sc_time_stamp() << " ModuleB:请求已撤销,拉低 ack" << endl;
            ack.write(false);
        }
    }
};

// ==============================================
// 模块 A:发送请求 → 等待响应 → 撤销请求
// ==============================================
SC_MODULE(ModuleA) {
    sc_out<bool> req;
    sc_in<bool>  ack;

    SC_CTOR(ModuleA) {
        req.initialize(false);
        SC_THREAD(send_request);
    }

    void send_request() {
        // 发送请求
        wait(10, SC_NS);
        cout << sc_time_stamp() << " ModuleA:发送请求 req=1" << endl;
        req.write(true);

        // 等待响应
        wait(ack.posedge_event());
        cout << sc_time_stamp() << " ModuleA:收到响应"<< ack <<",撤销请求 req=0" << endl;

        // 撤销请求
        req.write(false);
    }
};

// ==============================================
// 顶层:连接信号
// ==============================================
SC_MODULE(Top) {
    sc_signal<bool> req_sig;
    sc_signal<bool> ack_sig;

    ModuleA* a;
    ModuleB* b;

    SC_CTOR(Top) {
        a = new ModuleA("ModuleA");
        b = new ModuleB("ModuleB");

        a->req(req_sig);
        a->ack(ack_sig);
        b->req(req_sig);
        b->ack(ack_sig);
    }
};

// ==============================================
// 主函数
// ==============================================
int sc_main(int, char**) {
    Top top("top");
    sc_start(200, SC_NS);
    return 0;
}

SystemC 3.0.2-Accellera --- Mar 27 2026 21:51:17

Copyright (c) 1996-2025 by all Contributors,

ALL RIGHTS RESERVED

10 ns ModuleA:发送请求 req=1

10 ns ModuleB:收到请求 1,回复 ack

10 ns ModuleA:收到响应1,撤销请求 req=0

10 ns ModuleB:请求已撤销,拉低 ack

可以发现所有的事件都是在10ns的时候完成的,req(A)->ack(B)->撤销req(A)->撤销ack(B)

其实这个结论是个悖论,因为中间只是单纯的握手,没有延时,时间当然不会推进。但是其实仿真器底层已经执行过多次评估更新的过程了。

三、sc_buffer<T>:无延迟数值通道(值即时、事件仍延迟)

1. 核心行为

  1. 执行buf.write(val)立即刷新内部真实值,当前评估阶段马上生效;
  2. 同进程内写后立刻读,能马上拿到新值,无 Delta 延迟;
  3. 关键点:值是即时的,但信号变化事件依旧要等到更新阶段触发
  4. 线程wait(buf.default_event()),依然会晚一个 Delta 周期唤醒。

2. 特点

  • 接口和sc_signal完全兼容,可无缝替换;
  • 支持sc_trace抓波形,不影响仿真调试;
  • 同样遵守单写者规则,多进程同周期写会触发竞态;
  • 解决了数值读取延迟 ,但线程事件唤醒延迟依旧存在

3. 常见误区

很多人以为换了sc_buffer就能彻底零延迟,实际上只能解决同进程即时读值 ,解决不了跨线程即时唤醒

4. 适用场景

  • 组合逻辑直通连线、模块间纯数值实时传递;
  • 握手信号(req/grant)需要即时读值,但不要求线程立刻唤醒;
  • 不想改架构,仅消除数值读取一拍延迟的场景。

四、sc_event:纯事件同步(真正零延迟唤醒)

1. 核心行为

  1. sc_event不存储任何数值 ,只做线程唤醒同步
  2. 调用evt.notify()支持立即通知,无需等待 Delta 周期、无需进入更新阶段;
  3. 只要触发通知,等待该事件的SC_THREAD立刻被唤醒,无任何仿真延迟;
  4. 无数值属性,不能单独传递数据,只能搭配sc_buffer/ 普通变量存业务值。

2. 特点

  • 真正零 Delta 延迟,是 SystemC 最快的跨线程同步方式;
  • 不能用 sc_in/sc_out 端口互联,没有端口接口;
  • 无法sc_trace抓取波形,只能靠日志打印调试;
  • 支持一对多广播:一个事件通知,多个线程同时唤醒。

3. 模块间传递方式

sc_event不能像信号一样端口连线,工程标准做法:

  • 顶层模块定义 sc_event
  • 通过构造函数引用 / 指针传递给各个子模块;
  • 子模块保存事件引用,实现跨模块事件同步。

4. 适用场景

  • 要求跨模块、跨线程立即唤醒的握手逻辑;
  • 调度器、状态机即时跳转、异步中断通知;
  • 彻底消除 Delta 延迟,实现仿真层面即时交互。

五、三者核心对比汇总表

表格

特性 sc_signal sc_buffer sc_event
数值更新时机 更新阶段,晚 1Delta 评估阶段,立即更新 不保存数值
同进程写后读 读到旧值 读到新值 无读值概念
事件唤醒延迟 晚 1Delta 仍晚 1Delta 无延迟,立即唤醒
模块端口互联 支持 sc_in/sc_out 支持 sc_in/sc_out 不支持端口,只能传引用
波形 Dump 支持 sc_trace 支持 sc_trace 不支持波形抓取
单写者规则 遵守 遵守 无数值,无写冲突
核心定位 时序硬件信号 即时数值通道 纯异步同步事件

六、工程选型最佳实践

  1. 要模拟硬件时序、寄存器一拍延迟 → 直接用 sc_signal
  2. 只要数值即时传递,不需要线程立刻唤醒 → 换 sc_buffer
  3. 既要即时读值,又要跨模块零延迟唤醒sc_buffer 存数值 + sc_event 做事件通知 组合使用
  4. 异步中断、即时握手、调度器跳转 → 优先用 sc_event
  5. 追求代码规范、可综合风格:时序用sc_signal,组合用sc_buffer,异步同步用sc_event,各司其职不混用。

七、总结

  1. sc_signal带时序延迟,贴合真实硬件,适合时序逻辑;
  2. sc_buffer值即时、事件不即时,解决数值一拍延迟,解决不了线程唤醒延迟;
  3. sc_event无数值、零延迟唤醒,是跨模块即时握手的终极方案;

看懂三者底层 Delta 周期的差异,就不会再被 "晚一拍、仿真时序错乱" 困扰,写 SystemC 模块互联、调度器、握手逻辑时,选型再也不纠结。

相关推荐
Zephyr_01 小时前
c++数据结构
数据结构·c++
故事和你911 小时前
蓝桥杯-2026年C++B组省赛
开发语言·数据结构·c++·算法·蓝桥杯·动态规划·图论
小则又沐风a1 小时前
C++模板进阶
java·服务器·前端·c++
计算机安禾1 小时前
【c++面向对象编程】第3篇:类与对象(二):构造函数与析构函数
开发语言·c++·算法
小年糕是糕手1 小时前
【C++】vector 不踩坑指南:用法、底层实现与迭代器失效解析
c++·算法
顾温9 小时前
default——C#/C++
java·c++·c#
凉茶钱10 小时前
【c语言】动态内存管理:malloc,calloc,realloc,柔性数组
c语言·c++·vscode·柔性数组
脏脏a10 小时前
【C++模版】泛型编程:代码复用的终极利器
开发语言·c++·c++模版
island131410 小时前
【C++仿Muduo库#3】Server 服务器模块实现上
服务器·开发语言·c++