目录
0.前言
若多个线程之间想要进行数据交换或者知道彼此的状态以决定执行什么线程,SV中通过event、mailbox、semaphore来进行线程通信。其中event是2个线程之间的通信,semaphore是≥2个线程之间的通信。
1.事件event
当我们需要一个进程在另一个进程触发事件的时候运行该怎么办?SV中引入了event来解决这个问题。其语法如下:
触发:-> (非阻塞)(类比于接电话)
等待:@ or wait(阻塞)(类比于打电话)
注意:若用->和@搭配,一定要先@再->;若用->和wait搭配,则谁先谁后都可以。
为什么@和wait会有这种差别呢?因为@是等待边沿触发,而非1触发,因此必须先等,知道信号发生跳变,如果信号已经发生跳变了,@是察觉不到的;而wait是电平1触发,因此自然而然的弥补了@的缺点,因此也不用有先后顺序
->和@搭配:
program automatic test();
event e1,e2;
initial begin
$display("@%0d:1:before trigger",$time);
->e1;//->和@搭配,先->不行,非阻塞,去下一个
@e2;//等待e2被触发,阻塞,下面的不执行
$display("@%0d:1:after trigger",$time);
end
initial begin
$display("@%0d:2:before trigger",$time);
-> e2;//上面的等到了e2,此时上面的display运行
@e1;//阻塞,顺序错误,下面的display不会执行
$display("@%0d:2:after trigger",$time);
end
endprogram
/* 仿真结果
@0:1:before trigger
@0:2:before trigger
@0:1:after trigger */
->和wait搭配 :
program automatic test();
event e1,e2;
initial begin
$display("@%0d:1:before trigger",$time); //1
-> e1;//触发1
wait(e2.triggered);//阻塞,等待e2被触发
$display("@%0d:1:after trigger",$time);//4
end
initial begin//顺序执行完后,再回到上面执行wait后的display
$display("@%0d:2:before trigger",$time); //2
-> e2;//e2被触发
wait(e1.triggered);//e1已经被触发
$display("@%0d:2:after trigger",$time);//3
end
endprogram
/*仿真结果:
@0:1:before trigger
@0:2:before trigger
@0:2:after trigger
@0:1:after trigge */
wait_order:
wait_order阻塞等待多个事件的触发,并且要求这多个事件按照用户决定顺序触发。wait_order可以和else一同使用,当多个事件 按顺序触发时,执行wait_order后的语句,否则执行else后的语句。
module tb;
event a, b, c;
initial begin
#10 -> a;
#10 -> b;
#10 -> c;
end
initial begin
wait_order (a,b,c)
$display ("Events were executed in the correct order");
else
$display ("Events were NOT executed in the correct order !");
end
endmodule
2.信箱mailbox
mailbox是一种在进程之间交换消息的机制。数据可以通过一个进程发送到Mailbox, 然后由另一个进程获取。数据可以是任何有效的SystemVerilog数据类型,包括类 class数据类型。
比如在一个验证环境中,generator将激励给driver,往往不是直接发送给driver,而是发送给generator和driver直接的mailbox,driver获取数据的时候,从mailbox中直接获得。
mailbox有几种SV自带函数,如下所示:
new()是对mailbox进行实例化;
put(xxx)是将数据放入mailbox中,是阻塞的,若mailbox放满,放不进去,则会一直重复放的动作,程序被阻塞到这个语句;
try_put(xxx)功能与put一样,但其是非阻塞的,放一次,放不进去就不放了;
get(xxx)是从mailbox中取数据,也是阻塞语句;
peek(xxx)和get()功能一致,但是get()从mailbox取出数据后,mailbox的数据就没了,而peek相当于复制了一份出来;num()是用来计算mailbox中有几个数据的。
关于mailbox的具体应用可参考这篇文章:SV小项目—异步fifo的简单验证环境搭建。
下面看代码进一步理解mailbox的运用:
//============事务============
class transaction;
rand bit valid;
rand bit [7:0] data;
endclass
//=================generator================
class generator;
mailbox #(transaction) gen2drv;//声明mailbox句柄,并且指明该mailbox中只能存放transaction类型的数据
transaction tr;
function new (input mailbox #(transaction) gen2drv);//声明和外界通信的组件时,一定要new
this.gen2drv=gen2drv;
endfunction
task gen(input int num);//产生多少笔激励
for(int i=0;i<num;i++) begin
tr=new();
assert(tr.randomize)
gen2drv.put(tr);//把产生的一笔激励放入mailbox中
$display(“trans valid =%d”,tr.valid);
$display(“trans data =%d”,tr.data);
#1ns;
end
endtask
endclass
//=====================驱动========================
class driver;
mailbox #(transaction) gen2drv;
transaction tr;
function new (input mailbox #(transaction) gen2drv);
this.gen2drv=gen2drv;
endfunction
task run;
tr=new();
while(1) begin
gen2drv.get(tr);//从mailbox中取出数据
$display(“trans valid =%d”,tr.valid);
$display(“trans data =%d”,tr.data);
end
endtask
endclass
//==================test文件==================
program test;
mailbox #(transaction) gen2drv;
driver drv;
generator gen;
initial begin
gen2drv=new();
drv=new(gen2drv);
gen=new(gen2drv);
fork
gen.gen(5);
drv.run;
join_any
end
endprogram
3.旗语semaphore
当多个线程访问同一资源的时候,而这个资源只允许一个线程访问的时候,就可以用旗语来进行控制;如果把资源比作为一个仓库的话,旗语就相当于一把钥匙,每个线程需要拿到钥匙后才能对资源进行访问。当然也可以有多把钥匙,对应同一资源可以同时被访问的最大数量。
旗语有如下几个操作函数:
new(); //对“钥匙”进行实例化
get(); //线程获取钥匙,阻塞
put(); //线程将钥匙放回,阻塞
try_get(); //非阻塞
class bridge;
semaphore key;//声明一个旗语钥匙
function new ();
this.key=new(1);//实例化1把钥匙
endfunction
task go_on(string name);
$display(“@%0t: %s wants to go trough the brdige”,$time,name);
key.get();//拿到钥匙
#1ns;
$display(“@%0t: %s gets permission to go trough the brdige”,$time,name);
#10ns;
endtask
task go_off(string name);
$display(“@%0t: %s goes off the brdige”,$time,name);
key.put();//放回钥匙
#1ns;
$display(“@%0t: %s returned the key.”,$time,name);
endtask
endclass
program test;
bridge brg;
string name1=”Alex”;
string name2=”Bob”;
string name3=”Calvin”;
string name4=”Denise”;
initial begin
brg=new();
fork
begin
brg.go_on(name1);
brg.go_off(name1);
end
begin
brg.go_on(name2);
brg.go_off(name2);
end
begin
brg.go_on(name3);
brg.go_off(name3);
end
begin
brg.go_on(name4);
brg.go_off(name4);
end
join
end
endprogram
仿真结果如下:
可见若多个线程同时要拿钥匙,则按照先后顺序依次来;下一线程只有等到上一线程归还钥匙后才能拿到钥匙。