一、线程间的通信
- 测试平台中的所有线程都需要同步并交换数据。
- 多个线程可能会同时访问同一资源。
- 在最高的层面上,线程需要彼此交换数据,例如从发生器传递给代理的事务对象。
- 所有这些数据交换和控制的同步被称为线程间通信(IPC),在SV中可以使用事件、旗语和信箱来完成。
二、事件
Verilog事件可以实现线程的同步,一个线程总是要等待一个带@操作符的事件。这个操作符是边沿敏感的,其他的线程可以通过->操作符来触发事件,解除对第一个线程的阻塞。
在SV中,事件成为了同步对象的句柄,可以传递给子程序。允许在对象间共享事件,而不用把事件定义为全局的。
1、在事件的边沿阻塞
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
输出结果:
@0: 1: before trigger
@0: 2: before trigger
@0: 1: after trigger
/*
第一个初始化块启动,触发e1事件,然后阻塞在另一个事件上,
第二个初始化块启动,触发e2事件(唤醒第一个块),然后阻塞在第一个事件上。
但是因为第一个事件是一个零宽度的脉冲,所以第二个线程会因为错过第一个事件而被锁住。
*/
2、等待事件的触发
可以使用电平敏感的wait(e1.triggered())来替代边沿敏感的阻塞语句@e1.如果事件在当前时间步已经被触发,则不会引起阻塞。否则会一直等到事件被触发为止。
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
3、在循环中使用事件
可以使用事件来实现两个线程的同步,如果在循环中使用wait(handshake.triggered()),一定确保在下次等待之前时间可以向前推进。否则你的代码将进入一个零时延循环,原因是wait会在单个事件触发器上反复执行。
等待事件导致零时延循环
forever begin
//这是一个零时延循环
wait(handshake.triggered());
$display("Received next event");
process_in_zero_time();
end
等待事件的边沿
forever begin
//这里避免了零时延循环
@handshake;
$display("Received next event");
process_in_zero_time();
end
如果需要在同一时刻发送多个通告,那就不应该使用事件,而应该使用其他内嵌排队机制的线程通信(IPC)方法,如旗语或者信箱。
4、传递事件
SV中的事件可以像参数一样传递给子程序。
把事件传递给构造器
class Generator;
event done;
function new(event done); //从测试平台中传来事件
this.done = done;
endfunction
task run();
fork
begin
... //创建事务
->done; //告知测试程序任务已完成
end
join_none
endtask
endclass
program automatic test;
event gen_done;
Generator gen;
initial begin
gen = new(gen_event); //测试程序实例化
gen.run(); //运行事务处理器
wait(gen_done.triggered()); //等待任务结束
end
endprogram
5、等待多个事件
使用wait fork等待多个线程
event done[N_GENERATORS];
initial begin
foreach(gen[i]) begin
gen[i] = new(); //创建N个发生器
gen[i].run(done[i]) //使它们开始运行
end
//通过等待每个事件来等待所有发生器完成
foreach(gen[i])
fork
automatic int k = i;
wait(done[k].triggered());
join_none
wait fork; //等待所有触发事件完成
end
通过对触发事件进行计数来等待多个线程
event done[N_GENERATORS];
int done_count;
initial begin
foreach(gen[i]) begin
gen[i] = new(); //创建N个发生器
gen[i].run(done[i]) //使它们开始运行
end
//通过等待每个事件来等待所有发生器完成
foreach(gen[i])
fork
automatic int k = i;
begin
wait(done[k].triggered());
done_count++;
end
join_none
wait (done_count == N_GENERATORS); //等待所有触发事件完成
end
使用线程计数来等待多个线程
class Generator;
static int thread_count = 0;
task run();
thread_count++; //启动另一个线程
fork
begin
...
//当工作完成时,对线程数目减少计数
thread_count--;
end
join_none
endtask
endclass
Generator gen[N_GENERATORS];
initial begin
foreach(gen[i]) begin
gen[i] = new();
gen[i].run();
end
wait(Generator::thread_count == 0);
end