SystemVerilog 线程通信:事件、旗语与信箱
本文将深入探讨 SV 原生的三大通信神器:事件(Event)、旗语(Semaphore)和信箱(Mailbox)
·
在构建复杂的 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
更多推荐


所有评论(0)