In the actual hardware, calculations are performed concurrently. In Verilog, it is simulated through initial, always, and continuous assignment. In the test platform, in order to simulate and test these statement blocks in Verilog, tb uses many concurrent threads.
1. Definition and use of threads
1.1 Define threads
initial, always, and assign are all processes. Besides the first time, there are:
-
fork join
The statements in it are concurrent, and the following statements are executed after the fork-join block is executed.
-
fork join_none
The statements in it are concurrent and will not block the statements after the block. The statements in the block and the statements after the block are concurrent.
initial begin
语句1;
#10;
fork
语句2;
语句3;
join_none
语句4;
语句5;
end
// 执行顺序
// #0 语句1
// #10 语句2,语句3,语句4并发执行
// #10 语句4执行完之后才执行语句5。4执行完之后,即使2,3没执行完,也会接着执行5,因为fork块内语句与之后的语句是并行的,不会阻塞之后的语句
-
fork join_any
Similar to fork Join_none, except that the process in a block must be executed before continuing to execute the statement block after fork..join_any.
initial begin
fork
#20 $display("@ %0t [line1] seq 20",$time);
#10 $display("@ %0t [line2] seq 10",$time);
join_any
#15 $display("@ %0t [line3] after seq 15",$time);
end
//结果
@ 10 [line2] seq 10
@ 20 [line1] seq 20
@ 25 [line3] after seq 15
Analysis: The statement in fork must be executed first, line1 and line2 are executed in parallel, and line2 is executed first, and the simulation time at this time is 10ns. From now on, the statement after the fork block can also be executed, that is, line3, line3 still needs 15ns to output, and at this time line1 has been simulated for 10ns, and it takes another 10ns to output, so output line1 first, then line3
1.2 Dynamic threads
To create a thread in a class, use the above three fork blocks.
Each fork block can be regarded as starting a thread.
If you start threads in a loop, you need to use the automatic keyword to automatically create variables, so that memory is allocated separately for each thread.
initial begin
for(int i=0;i<3;i++)
fork
automatic int k=i;
$display(k);
join_none
end
1.3 Wait for all child threads to end
In SV, the simulation ends after all the initials are executed. Some threads take a long time to execute, and the simulation may end before they are executed.
initial begin
....
run_fork(); //调用run_fork()之后,继续执行a<=b,最后一条语句结束,仿真结束,此时
// run_fork()可能还没执行完。
a <= b;
....
end
wait fork to wait for the threads to finish executing.
task run();
fork:fork_all
run_fork0(); //线程0 ;任务内有fork
run_fork1();//任务内有fork
run_fork2();//任务内有fork
...
join_none
wait fork; // 等待fork_all中的所有线程都执行完
endtask
1.4 stop thread disable
-
stop a single thread
initial begin fork:timeout_fork //fork有标识符 run_fork0();//任务内有fork run_fork1();//任务内有fork join_none #100; disable timeout_fork; // disable+标识符 end
-
stop multiple threads
initial begin run_fork2(); fork // timeout_fork begin run_fork0();//任务内有fork run_fork1();//任务内有fork #100 disable fork;// 将disable_fork进程中所有的子进程都停止,不需要标识符。 end join end
The timeout_fork block is used to limit the range of the fork to be disabled, and the disable in the above code has no effect on run_fork2().
-
Disable tasks that are called multiple times
If a process is started in a task, when the task is disabled, all processes started by the task will be stopped; if the task is called elsewhere, other processes will also be disabled.
task 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
At 2ns, stop the time_out(0) task, and at this time, the other two tasks will also be disabled, so that they cannot be executed.
Be careful with all disable tasks.
2. Inter-thread communication
All threads in the test platform need to transfer data, and multiple threads may access the same data at the same time. The code of the test platform is such that only one thread can access it at the same time.
This synchronization of data exchange and control is called inter-thread communication (IPC).
3. Event event
The event in Verilog has been extended in SV:
1. event can be passed as a parameter to the method.
2. Introduced the triggered function
In Verilog, @, -> operators are used to block and trigger events. If one thread is blocking an event while another thread fires an event at the same time, a race can occur, and if the fire precedes the block, the fire is missed.
The triggered function is introduced in SV, which can query whether an event is triggered, including the current time slot trigger (time slot). Triggered to return 1. In this way, you can use wait to wait for the result of this function without using @ to block.
@e1 is an edge-sensitive blocking statement; wait(e1.triggered()) is level-sensitive.
event e1,e2;
initial begin:i1
@e1; // 先执行i1块,发现阻塞
$display(....);
->e2; //执行完代码后触发e2,开始执行i2
end
initial begin:i2
#1; // 1ns后触发e1,并且阻塞在e2
->e1;
@e2;
$display(...);
end
event e1,e2;
initial begin
wait(e1.triggered());
$display(....);
->e2;
end
initial begin
#1;
->e1;
wait(e2.triggered());
$display(...);
end
3.1 Using events in loops
@e1 is an edge-sensitive blocking statement; wait(e1.triggered()) is level-sensitive.
Using events in a loop, if the loop is 0 delay, then there will be a bit of a problem:
-
level sensitive blocking
initial begin forever begin wait(e1.triggered()); $display(...); end end
The wait will fire continuously, and the simulation time will not advance . Because wait is triggered, after executing a loop, e1.triggered() still returns 1 in the current time slice, and wait continues to trigger.
Improvement: added delay in loop.
-
edge sensitive blocking
initial begin forever begin @e1; $display(...); end end
Edge triggering, even with 0 delay, only triggers once.
3.2 Events as parameters
class Generator;
event e;
function new(event e1) //传入事件
this.e = e1;
endfunction
task run()
...
->e; //触发
endtask
endclass
class Driver;
event e;
function new(event e2);//传入事件
this.e=e2;
endfunction
task run();
@e; //等待触发
// wait(e.triggered());
...
endtask
endclass
program test;
Generator gen;
Driver drv;
event e;
initial begin
gen=new(e);
drv=new(e);
fork
gen.run();
drv.run();
join
end
endpragram
3.3 Waiting for multiple events
If there are multiple generators, it is necessary to wait for the threads of all generators to finish executing.
Method 1. Use wait fork
event done[N];// N是发生器数目
initial begin
foreach (gen[i])begin
gen[i]=new(done[i]);
gen[i].run();
end
foreach(gen[i]) fork
automatic int k=i;
wait(done[k].triggered());
join_none
wait fork; //等待所有的fork执行完
end
Method 2: Use a counter
event done[N];// N是发生器数目
int cnt;
initial begin
foreach (gen[i])begin
gen[i]=new(done[i]);
gen[i].run();
end
foreach(gen[i]) fork
automatic int k=i;
begin //begin块
wait(done[k].triggered());
cnt++; //触发一个,计数加一。
end
join_none
wait(cnt==N); //等待计数到N。说明所有的fork执行完毕,所有的事件都触发
end
Method 3. Get rid of events and only use static variables to count
class Generator ;
static int cnt=0;
task run();
cnt++; // 调用run计数加一
fork
begin
....
cnt--; //代码执行完毕,cnt减一。
end
join_none
endtask
endclass
initial begin
foreach (gen[i]) gen[i]=new();
foreach (gen[i]) gen[i].run();
wait(Generator::cnt == 0); //gen启动时都+1,结束时都-1,最终结果0.
end