在构建复杂的 SystemVerilog 验证环境时,测试平台通常由许多并发执行的线程组成 。为了让这些线程能够协同工作,我们需要一套高效的线程间通信(IPC, Interprocess Communication)机制来实现同步和数据交换 。


前言

本文将深入探讨 SV 原生的三大通信神器:事件(Event)、旗语(Semaphore)和信箱(Mailbox)

三种方法的比较

  • Event(事件):最小信息量的触发,即单一的通知功能。可以用来做事件的触发,也可以多个 event 组合起来用来做线程之间的同步。
  • Semaphore(旗语):共享资源的安全卫士。如果多线程间要对某一公共资源做访问,即可以使用这个要素。
  • Mailbox(信箱):精小的 SV 原生 FIFO。在线程之间做数据通信或者内部数据缓存时可以考虑使用此元素。

一、事件(Event)

事件是 SystemVerilog 中最简单的同步机制,它主要用于单一的通知功能 。

  • 基本概念:事件可以看作是一种最小信息量的触发,用于线程间的同步或触发 。

  • 触发与等待:

    • 通过 -> 操作符来触发事件,从而结束对另一个线程的阻塞 。

    • 在 Verilog 中,通常使用 @ 操作符等待事件,它是边沿敏感的 。

    • SystemVerilog 引入了更为安全的 triggered() 方法,它是电平敏感的 。

  • 独特优势:事件是这三种机制中唯一不需要使用 new() 函数进行实例化的对象 。

事件的边沿触发

event e1,e2;

initial begin
    $display("@%0t: 1: before trigger", $time);
    -> e1;
    @e2;
    $display("%0t: 1: after trigger", $time);
end

initial begin
    $display("@%0t: 2: before trigger", $time);
    -> e2;
    @e1;
    $display("@%0t: 2: after trigger",$time);
end

第一个初始化块启动,触发e1事件,然后阻塞在e2上;第二个初始化块启动,触发e2事件,然后阻塞在e1上;e1 和 e2 在同一个时刻被触发,但由于 delta cycle 的时间差使得两个初始化块可能无法等到 e1 或者 e2。
最终打印结果为:

@0: 1: before trigger
@0: 2: before trigger

所以,更安全的方式可以使用event的方法 triggered ( )。

等待事件的触发

  • 可以使用电平敏感的 wait (e1.triggered ( ) ) 来替代边沿敏感的阻塞语句 @e1 。
  • 如果事件在当前时刻已经被触发,则不会引起阻塞。否则,会一直等到事件被触发为止。
  • 这个方法比起 @ 而言,更有能力保证,只要 event 被触发过,就可以防止引起阻塞。
event e1,e2;

initial begin
    $display("@%0t: 1: before trigger",$time);
    -> e1;
    wait( e2.triggered() );
    $display("@%0t: 1: after trigger" ,$time);
end

initial begin
    $display("@%0t: 2: before trigger", $time);
    -> e2;
    wait( e1.triggered() );
    $display("@%0t: 2: after trigger", $time);
end

最终打印结果为:

@0: 1: before trigger
@0: 2: before trigger
@0: 1: after trigger
@0: 2: after trigger

旗语(semaphore)

  • 当多个线程需要同时访问同一个公共资源时,旗语便充当了“资源管家”的角色 。
  • 工作机制:旗语类似于带有“钥匙”的房间。线程必须先获取钥匙才能进入,用完后再归还 。
  • 核心操作:
    • new():创建一个带有一个或多个钥匙的旗语对象 。
    • get():获取一个或多个钥匙;如果钥匙不够,线程将阻塞 。
    • put():返回一个或多个钥匙 。
    • try_get():试图获取钥匙但不引起阻塞,成功返回 1,失败返回 0 。
  • 应用场景:最常用于对同一公共资源做访问控制,防止多线程冲突 。
program automatic test(bus_ifc.TB bus) ;
    semaphore semn; //创建一个semaphore

    initial begin
        sem = new(1); //分配一个钥匙
        fork
            sequencer(); //产生两个总线事务线程
            sequencer();
        join
    end

    task sequencer;
        repeat($urandom%10) //随机等待0-9个周期
        @bus.cb;
        sendTrans();       //执行总线事务
    endtask

    task sendTrans;
        sem.get(1); //获取总线钥匙
        @bus.cb;    //把信号驱动到总线上
        bus.cb.addr <= t.addr;
        ...
        sem.put(1); //处理完成时把钥匙返回
    endtask
endprogram

信箱mailbox

  • 信箱是 SV 原生的同步 FIFO,非常适合在线程之间传输数据或进行内部数据缓存 。
  • 基本特性:信箱是一种对象,必须使用 new() 例化 。它支持在线程间安全地交换数据 。
  • 容量控制:例化时可以通过 size 参数限制存储的最大数量;若不指定或设为 0,则信箱容量无限大 。
  • 关键方法:
    • put():将数据放入信箱;若信箱已满,则该操作会阻塞 。
    • get():从信箱中移除并获取数据;若信箱为空,则会阻塞 。
    • peek():获取数据的拷贝而不将其从信箱中移除 。
program automatic bounded;
    mailbox mbx;

    initial begin
        mbx = new(1); // 容器大小为1
        fork
            // Producer线程
            for(int i=1; i<4; i++) begin
                $display("Producer: before put (%0d)", i);
                mbx.put(i);
                $display("Producer : after put (%0d)", i);
            end

            // Consumer线程
            repeat(4) begin
                int j;
                #1ns mbx.get(j);
                $display("consumer : after get (%0d)", j);
            end
        join
    end
endprogram

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐