一.线程的使用
创建线程有三种方式
(1)fork…join
(2)fork…join_any
(3)fork…join_none
从图中可以看出
- fork…join表示一个并行执行语句,不同与begin…end的顺序执行,在数电中理解为与的概念
- fork…join_any表示有一个执行完成就可以,在数电中可以理解为或的概念
- fork…join_none表示一个都不执行,在数电中可以理解为非的概念
二.线程的控制
1.wait
- wait语句可以等待所有语句执行完毕才退出程序,wait语句使用时加上等待线程名称
- fork…join_any本身是只执行一句的,如果加入了wait语句,会将三个语句全部执行完毕才会退出整个initial语句,如下代码:
task run_threads;
...
fork
check_trans(tr1);
check_trans(tr2);
check_trans(tr3);
join_any
...
wait fork //等待三个check执行完毕再退出task
endtask
2.disable
- disable可以停止单个线程也可以停止多个线程,都需要加上停止进行的名字
(1)结束单个进程 - 停止单个线程,dsiable timeout_block这个语句可以停止frok…join_any中的time_out进程
parameter TIME_OUT = 1000;
task check_trans(Transaction tr);
fork
begin
fork:timeout_block
begin
wait(bus.cb.addr == tr.addr)
$display("@%0t:Addr match %d",$time,tr.addr);
end
#TIME_OUT $display("@%0t:Error:timeout",$time);
join_any
//停止TIME_OUT语句
disable timeout_block;
end
join_none
endtask
(2)停止多个进程
- 在停止多个线程时候,需要加上名字以表区别比如:disable fork_a,如果只写fork,disable会将所有的fork都停止,所以写为disable fork_a以示区分,如下:
initial begin
check_trans(tr0);
fork
begin
check_trans(tr1);
fork
check_trans(tr2);
join
//这里有两个fork,如果不指明,会将两个fork都需要停止,所以需要区分
#(TIME_OUT/2) disable fork;
end
join
end
(3)停止被多次调用的任务
- disable比想象的更强大,如果你给某一个任务或者线程指明标号,那么当这个线程被调用多次以后,如果通过disable去禁止这个线程标号,所有衍生的同名线程都将被禁止。
task wait_for_time_out(int id);
if(id==0)
fork
begin
#2
$display("@%0t:disable wait_for_time_out",$time);
disable wait_for_time_out;
end
join_none
fork:just_a_little
begin
$diaplay("@%0t:%m:%0d entering thread",$time,id);
#TIME_OUT;
$display("@%0t:%m:%0d done",$time,id);
end
join_none
endtask
initial begin
wait_for_time_out(0);//被停止
wait_for_time_out(1);//被停止
wait_for_time_out(2);//被停止
#(TIME_OUT*2)$display("@%0t:All done",$time);
end
- 任务wait_for_time_out被调用了三次,从而衍生了三个线程
- 线程0在2#延时之后禁止了该任务,而由于三个线程均是“同名”线程,因此这些线程都被禁止了,最终也都没有完成。
三.线程的通信
1.事件
- 作用:事件可以实现线程的同步
- 用法:@用来等待事件,边沿敏感,所有总是阻塞的,等待事件的变化,->用来触发事件,用来解除线程的阻塞,wait用来等待事件,电平敏感。如:
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
- 这里的打印输出缺少了2的after trigger,其实本身有两种情况,也有可能打印的少的是1的after trigger。事件开始前,首先会打印两个before,然后触发事件1和事件2,但是由于有delta cycle时间差,所有不能同一时刻进行初始化,有先后顺序,会导致有一个没有打印出来
使用triggered()方法,在事件触发后,会保留触发状态,使用wait就能保证只要是触发过,就能接收到触发信号。
event e1, e2; //注意event 类不需要new
initial begin //线程1
$display("@%0t: (1)before trigger", $time);
-> e1; //触发事件e1
wait(e2.triggered()); //等待事件e2的触发
$display("@%0t: (1)after trigger", $time);
end
initial begin //线程2
$display("@%0t: (2)before trigger", $time);
-> e2; //触发事件e2
wait(e1.triggered()); //等待事件e1触发
$display("@%0t: (2)after trigger", $time);
end
2.旗语
- 作用:可以实现不同的主体对同一资源的访问控制,但是同一个时间,只能是一个主体来控制。
- 方法:旗语有3种基本操作,new()方法可以创建一个带单个或者多个钥匙的 semaphore ,使用 get() 方法可以获取一个或者多个钥匙,而put() 方法返回一个或者多个钥匙。使用get()如果拿不到钥匙,会被阻塞,而使用try_get()方法拿不到钥匙,会返回一个0,不会被阻塞。
program automatic test(bus.ifc.TB bus);
semaphore sem; //创建一个旗语
initila begin
sem = new(1); //分配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
- 使用旗语时候,(1)返回的钥匙可能比你取出来的要多,(2)当你的程序需要获取和返回多个钥匙时,务必谨慎
3.信箱
- 作用:信箱可以实现线程之间的信息传递
- 方法:mailbox 类有以下方法,new() 创建一个mailbox,同时设置信箱大小,put() 方法,可以把数据放入到mailbox中,使用get() 方法可以从信箱中拿到数据,且移除数据。如果信箱满了,使用put() 方法会被阻塞住,如果信箱是空的,使用get() 方法会被阻塞住。peek() 方法可以获取信箱中的数据·,且不会移除数据。另外非阻塞方法有:try_put() try_get() try_peek()
program automatic bounded;
mailbox mbx;
initial begin
mbx=new(1); //信箱容量为1
fork
for(int i=1; i<4; i++) begin //启动三个线程,这三个线程是顺序的,i依次是1 2 3
$display("before 语句:%0d", i);
mbx.put(i); //给信箱放数
$display("after 语句:%0d", i);
end
repeat(4) begin
int j;
#ins mbx.get(j); //同时延迟1ns,从信箱中拿数
$display("get data: %0d", j);
end
join
end
endprogram
4.总结
- event:最小信息量的触发,即单一的通知功能。可以用来做事件的触发,也可以多个event组合起来用做线程的同步
- semaphore:公共资源的安全卫士。如果多线程间要对某一个公共资源做访问,即可以使用这个元素
- mailbox:精小的SV原生FIFO。在线程之间做数据通信或者内部数据缓存时可以考虑使用此元素