SV——线程及线程间的通信(一)
实际硬件中,计算是并发进行的,在Verilog中通过initial、always、连续赋值来模拟,在测试平台中为了模拟、检验Verilog中的这些语句块,tb使用许多并发的线程。
1. 线程的定义和使用
1.1 定义线程
initial 、always、assign都是进程,初次之外还有:
fork join
其内的语句并发,fork-join块执行完才执行后面的语句。
fork join_none
其内 的语句并发,并且不会阻塞块之后的语句,块内语句与块之后的语句是并发关系。
1 |
|
fork join_any
与fork Join_none类似,只是,先要执行一条块内的进程。
1 |
|
分析:先要执行fork内的语句,line1与line2的并行,先执行完line2,此时仿真时间是10ns。从现在开始也可以执行fork块之后的语句,也就是line3,line3还需要15ns才输出,而此时line1已经仿真的10ns,再需要10ns就可以输出了,所以先输出line1,然后是line3
1.2 动态线程
在类中创建线程,用上面的三个fork块。
每个fork块都可以看成启动了一个线程。
如果循环启动线程,需要用automatic关键字,来自动创建变量,这样为每个线程单独分配内存。
1 |
|
1.3 等待所有子线程结束
在SV,所有的initial都执行完就结束仿真了,有的线程执行时间长,可能还没执行完,仿真就结束了。
1 |
|
wait fork来等待线程都执行完。
1 |
|
1.4 停止线程 disable
停止单个线程
1
2
3
4
5
6
7
8initial begin
fork:timeout_fork //fork有标识符
run_fork0();//任务内有fork
run_fork1();//任务内有fork
join_none
#100;
disable timeout_fork; // disable+标识符
end停止多个线程
1
2
3
4
5
6
7
8
9
10initial begin
run_fork2();
fork // timeout_fork
begin
run_fork0();//任务内有fork
run_fork1();//任务内有fork
#100 disable fork;// 将disable_fork进程中所有的子进程都停止,不需要标识符。
end
join
endtimeout_fork块用来限制要disable的fork的范围,上方代码中的disable对run_fork2()没影响。
禁止多次被调用的任务
如果在任务中启动了进程,当禁用这个任务的时候,会停止所有由该任务启动的进程;在其他地方也调用了该任务,那么其他的那些进程也会被disable。
1
2
3
4
5
6
7
8
9
10task time_out(input int i);
if(i==0)
#2 disable time_out; // 如果i等于0,那么2ns之后停止任务
....
endtaks
initial begin
time_out(0);
time_out(1);
time_out(2);
end在2ns时,停止time_out(0)任务,此时也会导致另外两个任务也disable,使它们不能执行完。
所有disable任务要慎重。
2. 线程间通信
测试平台中所有的线程需要传递数据,可能多个线程同时要访问同一个数据,测试平台的代码是使得同一时间只有一个线程能访问。
这些数据交换和控制的同步叫做线程间的通信(IPC)。
3. 事件 event
SV中对Verilog中的event做了扩展:
1. event可以作为参数传递给方法。
2. 引入了triggered函数
Verilog中由@,->操作符来阻塞和触发事件。如果一个线程在阻塞事件的同时,另一个线程同时触发了事件,那么可能发生竞争,如果触发先于阻塞,那么错过触发。
SV中引入了triggered函数,它可以查询事件是否被触发,包括当前时间片触发(time slot)。触发了返回1.这样可以用wait来等待这个函数的结果,而不必用@来阻塞。
@e1是边沿敏感的阻塞语句;wait(e1.triggered())是电平敏感的。
1 |
|
1 |
|
3.1 在循环中使用事件
@e1是边沿敏感的阻塞语句;wait(e1.triggered())是电平敏感的。
在循环中使用事件,如果循环是0延时的,那么会有点问题:
电平敏感的阻塞
1
2
3
4
5
6initial begin
forever begin
wait(e1.triggered());
$display(...);
end
endwait会持续地触发,仿真时间不会向前推进。因为wait触发,执行了一个循环之后,还在当前时间片,e1.triggered()还是返回1,wait继续触发。
改进:在循环中加入延迟。
边沿敏感的阻塞
1
2
3
4
5
6initial begin
forever begin
@e1;
$display(...);
end
end边沿的触发,即使0延迟,只触发一次。
3.2 事件作为参数
1 |
|
3.3 等待多个事件
如果有多个发生器,那么需要等待所有的发生器的线程都执行完。
方法一、用wait fork
1 |
|
方法二、用计数器
1 |
|
方法三、摆脱事件,只用静态变量来计数
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!