在实际硬件中,时序逻辑通过时钟沿来激活,组合逻辑的输出则是随着输入的变化而变化。所有这些并发的活动在Verilog的寄存器传输级上是通过initial和always块语句、实例化和连续赋值语句来模拟的。为了模拟和检验这些语句块,测试平台使用许多并发执行的线程。在测试平台的环境里,大多数语句块被模拟成事务处理器,并运行在各自的线程里。
测试平台环境的组成模块:
发生器把激励传递给代理,环境类需要知道发生器什么时候完成任务,以便及时终止测试平台还在运行的线程。这个过程需要借助线程间的通信(IPC)来完成。
一、线程的使用
标准的Verilog中对语句分为两种方式——使用begin…end和fork…join。
begin…end顺序方式执行,fork…join以并发方式执行,后者的不足在于必须等fork块内的所有语句都执行完后才能继续块内后续的处理。
SV中引入了两种新的创建线程的方法——使用fork…join_none和fork…join_any
测试平台通过已有的结构如事件、@事件控制、wait和disable语句,以及新的语言元素如旗语和信箱,来实现线程间的通信、同步以及对程序的控制。
1、使用fork…join和begin…end
fork…join和begin…end的相互作用
initial begin
$display("@ % 0t: start fork...join example", $time);
# 10 $display("@ % 0t: sequence after # 10", $time);
fork
$display("@ % 0t: parallel start", $time);
# 50 $display("@ % 0t: parallel after # 50", $time);
# 10 $display("@ % 0t: parallel after # 10", $time);
begin
# 30 $display("@ % 0t: sequence after # 30", $time);
# 10 $display("@ % 0t: sequence after # 10", $time);
end
join
$display("@ % 0t: after join", $time);
# 80 $display("@ % 0t: finish after # 80", $time);
end
仿真结果:
2、使用fork…join_none来产生线程
fork…join_none块在调度其块内语句时,父线程继续执行。
initial begin
$display("@ % 0t: start fork...join_none example", $time);
# 10 $display("@ % 0t: sequence after # 10", $time);
fork
$display("@ % 0t: parallel start", $time);
# 50 $display("@ % 0t: parallel after # 50", $time);
# 10 $display("@ % 0t: parallel after # 10", $time);
begin
# 30 $display("@ % 0t: sequence after # 30", $time);
# 10 $display("@ % 0t: sequence after # 10", $time);
end
join_none
$display("@ % 0t: after join", $time);
# 80 $display("@ % 0t: finish after # 80", $time);
end
仿真结果:
3、使用fork…join_any实现线程同步
fork…join_any块对块内语句进行调度,当第一个语句完成后,父进程才继续执行,其他停顿的线程也得以继续。
initial
begin
$display("@%0t: start fork...join_any example", $time);
# 10 $display("@%0t: sequence after # 10", $time);
fork
$display("@%0t: parallel start", $time);
# 50 $display("@%0t: parallel after # 50", $time);
# 10 $display("@%0t: parallel after # 10", $time);
begin
# 30 $display("@%0t: sequence after # 30", $time);
# 10 $display("@%0t: sequence after # 10", $time);
end
join_any
$display("@%0t: after join", $time);
# 80 $display("@%0t: finish after # 80", $time);
end
仿真结果:
4、动态线程
动态线程的创建
program automatic test(bus_ifc.TB bus);
task check_trans(Transaction tr); //当被调用时,它便产生一个线程用来检测总线以获取匹配的事务地址
fork
begin
wait(bus.cb.addr == tr.addr);
$display("@%0t": Addr match %d, $time, tr.addr);
end
join_none
endtask
Transaction tr;
initial begin
repeat(10) begin
tr = new(); //创建一个随机事务
assert(tr.randomize());
transmit(tr); //把事务发送到被测设计中
check_trans(tr); //等待被测设计的回复
end
# 100; //等待事务的最终完成
endprogram
5、线程中的自动变量
fork…join_none里的自动变量
initial begin
for(int j = 0; j < 3; j++)
fork
automatic int k = j; //创建索引的拷贝
$write(k);
join_none
# 0 $display;
end
/*
在每轮循环中,k的一个拷贝被创建并且被设置为j的当前值,
然后fork...join_none($write)被调度,包括k的拷贝,
在循环完成后,#0阻塞了当前进程,因此三个进程一起运行,打印出各自拷贝值k,
当线程运行完成后,在当前时间片已经没有其他事件残留,这时SV就会前进到下一个语句执行$display
*/
6、等待所有衍生线程
在SV中,当程序中的initial块全部执行完毕,仿真器也就退出了。有些线程运行时间比较长时,可以使用wait fork语句来等待所有子线程结束。
task run_threads;
... //创建一些事务
fork
check_trans(tr1); //产生第一个线程
check_trans(tr2); //产生第二个线程
check_trans(tr3); //产生第三个线程
join_none
...
//在这里等待上述线程结束
wait fork;
endtask
二、停止线程
1、停止单个线程
/*
fork...join_any里包含了两个线程:一个是简单的wait,另一个是带时延的显示,这两个线程并发执行。
如果正确的总线地址来得足够早,则wait结构先完成,fork...join_any得以执行,之后的disable结束剩余的线程。
但是如果在TIME_OUT时延完成前总线地址还没有得到正确值,那么错误警告的信息就会被打印出来,join_any被执行,而后的disable将结束wait线程。
*/
parameter TIME_OUT = 1000;
task check_trans(Transaction tr);
fork
begin
//等待回应,或者达到某个最大时延
fork : timeout_block //用disable停止的署名块
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
disable timeout_block;
end
join_none
endtask
2、停止多个线程
Verilog中disable来停止一个署名块中的所有线程,而SV中disable fork语句可以停止从当前线程中衍生出来的所有子线程。
initial begin
check_trans(tr0); //线程0
//创建一个线程来限制disable fork的作用范围
fork : threads_inner //线程1
begin
check_trans(tr1); //线程2
fork //线程3
check_trans(tr2); //线程4
join
//停止线程1-4,单独保留线程0
# (TIME_OUT / 2) disable threads_inner;
end
join
end
3、禁止被多次调用的任务
如果在某个任务内部禁止该任务,这就像时任务的返回语句,但是这也会停止所有由该任务启动的线程。如果该任务已经被多个线程调用,禁止其中的一个将导致它们全部被禁止
使用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
$display("@%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); //衍生线程0
wait_for_time_out(1); //衍生线程1
wait_for_time_out(2); //衍生线程2
# (TIME_OUT * 2) $display("@%0t: All done", $time);
end
/*
任务被调用三次,衍生了三个线程,线程0在#2延时之后禁止了该任务,导致三个线程最终都没完成。
*/
---------------------
作者:煎丶包
来源:CSDN
原文:https://blog.csdn.net/qq_39794062/article/details/113090988
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件