1.并发线程
1.fork-join、fork-join_any 和 fork-join_none
//父线程
fork
statement 1;//子线程
statement 2;//子线程
statement 3;//子线程
join | join_any |join_none
statement 4;
- join 子线程并行执行,必须在线程statement4执行之前完成
- join _any 子线程并行执行,必须有一个子线程完成,线程statement4才能执行
- join_none 线程statement4不会被阻塞,它与statement1,2,3同时执行
1.2 wait fork与disable fork
wait fork 等待所有子线程结束
在SV中,当程序中的initial块全部执行完毕,仿真器退出。这时fork-join_any和fork-join_none中的内容可能还没执行完。
如果希望等待fork块中所有线程执行完毕再退出结束initial块,我们可以使用wait fork来等待所有子线程结束。
task run_threads;
...
fork
check_trans(tr1);//线程1
check_trans(tr2);//线程2
join_none
...
//等待所有fork中的线程结束再退出
wait fork;
endtask
停止一个单线程
在使用了fork-join_any或者fork-join_none以后,我们可以使用disable来指定需要停止的线程
task check_trans(Transaction tr)
fork
begin
fork:timeout_block
begin
$diaplay("%0t:Addr match %d",$time,tr.addr);
end
join_any
//关掉timeout_block这个线程
disable timeout_block
end
join_none
endtask
停止多个线程
disable fork可以停止从当前线程中衍生出来的所有子线程
initial begin
check_trans(tr0);//线程0
//创建一个线程来限制disable fork的作用范围
fork //线程1
begin
check_trans(tr1);//线程2
fork//线程3
check_trans(tr2);//线程4
join
//停止线程1-4,单独保留线程0
#(TIME_OUT/2) disable fork;
end
join
end
2.事件
event事件是静态对象,用于线程之间的同步。
- 事件触发操作符:->
- 等待事件操作符 :@ 为边沿触发,wait() 为电平敏感;
- triggered函数,用于检查一个事件是否被触发过,而不要求事件等待与触发的先后顺序,是对 @ 等待事件的增强
- wait(event_name.triggered) 如果当前的仿真时间范围内,事件曾被触发过,则语句不会被阻塞,否则,该语句会一直等待事件被触发。
在event的边沿阻塞:
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。
所以更安全的方式可以使用event的方法triggered(),它比@更有能力保证,只要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
2.event的合并
当把一个事件赋值给另一个事件时,两个事件合并为一个事件,一个事件触发,另一个事件也会触发。
event a,b;
a=b;//事件a与事件b进行合并
-> a;//触发a时间的同时,b事件也会被触发
3.旗语
- semaphore可以实现对同一资源的访问控制
对于初学者,无论线程之间在共享什么资源都应该使用semaphone等资源访问控制的手段,以此避免可能出现的问题。
方法 | 释义 |
---|---|
new() | 创建一个带单个或多个钥匙的旗语,默认为0 |
get() | 获取一个或多个钥匙的旗语,默认为1 |
put() | 返回一个或多个钥匙的旗语,默认为1 |
try_get() | 获取一个旗语而不被阻塞,返回1表示有足够多的钥匙,返回0表示钥匙不够 |
对于线程间共享资源的使用方式,应该遵循互斥访问原则,来对访问进行控制。
class car;
semaphore key;
function new();
key = new(1);
endfunction
task get_on(string p);
$display("%s is waiting for the key",p);
key.get();
#1ns;
$display("%s got on the car",p);
endtask
task get_off(string p);
$display("%s got off the car",p);
key.put();
#1ns;
$display("%s returned the key",p);
endtask
endclass
module family;
car byd = new();
string p1 = "husband";
string p2 = "wife";
initial begin
fork
begin //丈夫开车
byd.get_on(p1);
byd.get_off(p1);
end
begin//妻子开车
byd.get_on(p2);
byd.get_off(p1);
end
join
end
endmodule
4.mailbox信箱
mailbox数据传输的媒介,mailbox可以在不同的线程之间传递信息,将一个线程中的数据,通过mailbox传递给另一个进程;当mailbox中没有数据时,线程将等待。
mailbox可以设置一定的深度,当信箱中的信息数量达到信箱的深度,信箱满,此时不能在信箱中存放东西。
- mailbox和队列queue有相近之处
- mailbox是一种对象,因此也需要使用new()来例化。如果size是0或者没有指定,则信箱是无限大的,可以容纳任意多的条目。
方法 | 释义 |
---|---|
put() | 把数据放入信箱 |
get() | 把数据从信箱移除 |
peek() | 可以获取对信箱里数据的拷贝而不移除它 |
try_put() | 把数据存入信箱而不发生阻塞 |
try_get() 或try_peek() | 试图获得信箱数据而不发生阻塞 |
num() | 返回信箱中的数据条目 |
//声明句柄
mailbox mailbox_name; //创建信箱
//创建对象
mailbox_name = new();//创建一个无容量限制的信箱
mailbox_name = new(m_size);//创建一个定容信箱
program automatic bounded;
mailbox mbox;
initial begin
mbx = new(1);
fork
for(int i=1;i<4;i++) begin
$display("Produce:before put(%0d)",i);
mbx.put(i);
$display("Produce:after put(%0d)",i);
end
repeat(4) begin
int j;
#1ns mbx.get(j);
$display("Consumer:after get(%0d)",j);
end
join
end
endprogram