一、线程的使用
在System Verilog语言参考手册里的"线程(thread)"和"进程(process)"是可以互换的。如果按照软件的思维理解硬件仿真, 仿真中的各个模块首先是独立运行的线程. 模块(线程)在仿真一开始的时候便并行执行, 除了每个线程会按照自己内部产生的事件来触发过程语句块之外, 也同时依靠相邻模块间的信号变化来完成模块之间的线程同步。
什么是线程
-
线程即独立运行的程序。
-
线程需要被触发,可以结束也可以不结束。
-
在module中的initial和always,都可以看做独立的线程,它们会在仿真0时刻开始,而选择结束或者不结束。
-
硬件模型中由于都是always语句块,所以可以看成是多个独立运行的线程,而这些线程会一直占用仿真资源,因为它们并不会结束。
-
软件测试平台中的验证环境都需要由initial语句块去创建,而在仿真过程中,验证环境中的对象可以动态的创建和销毁,因此软件测试端的资源占用是动态的。
注:initial线程不一定会结束,若在initial语句块中生成clk信号,则该线程不会结束。在System Verilog中,除了集成自Verilog中的fork…join和begin…end之外,还新增了fork…join_any和fork…join_none。其中,begin…end语句块是按照顺序执行的,其余三种的执行顺序见下图:
线程的概念
- 线程的执行轨迹是树状结构的,任何线程都应该有父线程。
- 父线程可以开辟若干个子线程,父线程可以暂停或者终止子线程。
- 在SV中,父线程执行结束后(注: 不是使用disable等终止线程, 而是线程按顺序执行结束), 并不会主动回收其子线程,如果disable父线程,则该线程与其子线程会被一同关闭。
二、线程的控制
等待所有衍生的线程
- 在SV中,当程序中的initial块全部执行完毕,仿真器就退出了。
- 如果我们希望等待fork块中的所有线程执行完毕再退出initial块,可以使用wait fork;语句来等待所有子线程结束。
注:上述中提到,子线程不会随着父线程的结束而主动结束。那么这些在父线程结束后,没有结束的子线程,被称作僵尸线程。对于这种情况,建议使用fork-join_none、fork-join_any开辟的线程,一旦认为它没有必要了,一定要给他打上标签,方便后期主动disable
停止单个线程
parameter TIME_OUT = 1000;
task check_trans(Transaction tr);
fork
begin
// 等待回应,或者达到某个最大时延
fork: time_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
disable time_block // 在disable之后,结束线程time_block
end
join_none
endtask
停止多个线程
使用disable来停止多个线程
`timescale 1ns/1ns
module tb;
task automatic child_t(int t, string name);
forever #(t*1ns) $display("@%0t child thread [%s] say hello", $time, name);
endtask : child_t
task automatic parent_t(int t=3, string name="parent_thread");
fork
forever #(t*1ns) $display("@%0t parent thread [%s] say hello", $time, name);
child_t(4,"child_thread_A");
child_t(5,"child_thread_B");
child_t(6,"child_thread_C");
join_none
endtask
initial begin:parent_thread
parent_t();
end
initial begin
#20ns;
disable parent_thread;
$display("@%0t disable parent_thread", $time);
#20ns;
$display("@%0t finish current test", $time);
end
endmodule : tb
在tb中,disable父线程之后,连带其中的子线程也一同被终止了。运行结果如下图所示:
在如下代码中,如果父线程是自然执行结束的,子线程是不会结束的,直到手动终止。
`timescale 1ns/1ns
module tb2;
task automatic child_t(int t, string name);
forever #(t*1ns) $display("@%0t child thread [%s] say hello", $time, name);
endtask
task automatic parent_t(int t=3, string name="parent_thread", int loop=10);
fork : child_threads
child_t(4,"child_thread_A");
child_t(5,"child_thread_B");
child_t(6,"child_thread_C");
join_none
repeat(loop) #(t*1ns) $display("@%0t parent thread [%s] say hello", $time, name);
$display("@%0t finish %s", $time, name);
endtask
initial begin:parent_thread
parent_t();
end
initial begin
#50ns;
// disable parent_thread;
// $display("@%0t disable parent_thread", $time);
$display("@%0t finish current test", $time);
$finish;
end
endmodule : tb2
其运行结果如下图所示:
停止被多次调用的任务
- 如果给一个任务或者线程指明标号,那么当这个线程被调用多次以后,如果通过disabe去禁止这个线程标号,所有衍生的同名线程都将被禁止。
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); // Spawn thread 0
wait_for_time_out(1); // Spawn thread 1
wait_for_time_out(2); // Spawn thread 2
#(TIME_OUT*2) $display("@%0t: All done", $time);
end
- 任务wait_for_time_out被调用三次,从而衍生了三个线程。
- 线程0在#2延时之后禁止了该任务,而由于三个线程均是“同名”线程,因此这些线程都被禁止了,最终也都没有完成。