线程控制
并行线程
-
fork…join 需要所有并行的线程都结束以后才会继续执行
-
fork…join_any 等到任何一个线程结束后继续执行
-
fork…join_none 不会等待子线程,直接继续往下执行
fork begin $display("First Block\n"); # 20ns; end begin $display("Second Block\n"); @eventA; end join
-
Verilog中与顺序线程begin…end相同的是并行线程fork…join
-
SV引入了两种新的创建线程的方式,fork…join_any和fork…join_none
-
示例
task automatic my_func_fork(int id, int t); $display("func_id is %0d, start.....", id); #(t*10ns); $display("func_id is %0d, end.....", id); endtask
不加线程
$display("start......"); my_func_fork(1, 10); my_func_fork(2, 20); my_func_fork(3, 30); my_func_fork(4, 40); $display("end......"); // 输出结果 # start...... # func_id is 1, start..... # func_id is 1, end..... # func_id is 2, start..... # func_id is 2, end..... # func_id is 3, start..... # func_id is 3, end..... # func_id is 4, start..... # func_id is 4, end..... # end......
fork…join
$display("start......"); fork my_func_fork(1, 10); my_func_fork(2, 20); my_func_fork(3, 30); my_func_fork(4, 40); join $display("end......"); // 输出结果 # start...... # func_id is 1, start..... # func_id is 2, start..... # func_id is 3, start..... # func_id is 4, start..... # func_id is 1, end..... # func_id is 2, end..... # func_id is 3, end..... # func_id is 4, end..... # end......
fork…join_any
$display("start......"); fork my_func_fork(1, 10); my_func_fork(2, 20); my_func_fork(3, 30); my_func_fork(4, 40); join_any $display("end......"); // 输出结果 # start...... # func_id is 1, start..... # func_id is 2, start..... # func_id is 3, start..... # func_id is 4, start..... # func_id is 1, end..... # end...... # func_id is 2, end..... # func_id is 3, end..... # func_id is 4, end.....
fork…join_none
$display("start......"); fork my_func_fork(1, 10); my_func_fork(2, 20); my_func_fork(3, 30); my_func_fork(4, 40); join_none $display("end......"); // 输出结果 # start...... # end...... # func_id is 1, start..... # func_id is 2, start..... # func_id is 3, start..... # func_id is 4, start..... # func_id is 1, end..... # func_id is 2, end..... # func_id is 3, end..... # func_id is 4, end.....
-
fork…join_any和fork…join_none,会有未完成的子线程仍在后台运行
-
wait fork
等待子线程全部完成 -
disable fork
停止未完成的子线程
时序控制
-
SV可以通过延时控制或者事件等待来对过程块完成时序控制
-
延迟控制即通过
#
来完成#10;
-
事件(event)控制即通过
@
来完成@(posedge clock);
-
wait
语句也可以与时间或者表达式结合来完成real AOR[]; initial wait(AOR.size() > 0) ......;
进程间的同步和通信
- 测试平台中的所有线程都需要同步并交换数据
- 一个线程等待另一个,例如验证环境需要等待所有激励结束,比较结束才可以结束仿真
- 比如监测器需要监测到的数据发送至比较器,比较器又需要从不同的缓存获取数据进行比较
事件event
-
可以通过
event
来声明一个命名event变量并且去触发它 -
这个命名event可以用来控制进程的执行
-
可以通过
->
来触发事件 -
其他等待该事件的进程可以通过
@
操作符或者wait()
来检查event触发状态来完成@
边沿触发wait()
电频触发
`timescale 1ns/1ps module tb; event e1,e2,e3; // 声明三个事件 task automatic wait_event(event e, string name); $display("@%t start waiting event %s", $time, name); @e; // 等待事件 方式一 wait(e.triggered); // 等待事件 方式二 $display("@%t end waiting event %s", $time, name); endtask initial begin fork wait_event(e1, "e1"); // 调用任务 wait_event(e2, "e2"); wait_event(e3, "e3"); join end initial begin fork begin #10ns -> e1; end // 触发事件 begin #20ns -> e2; end begin #40ns -> e3; end join end endmodule // 执行结果 # @ 0 start waiting event e1 # @ 0 start waiting event e2 # @ 0 start waiting event e3 # @ 10000 end waiting event e1 # @ 20000 end waiting event e2 # @ 40000 end waiting event e3
如果等待的是信号而非事件
`timescale 1ns/1ps module tb; bit e1,e2,e3; // 声明三个比特信号 task automatic wait_event(ref bit e, input string name); // 这里要监控e的变化,因此要用ref $display("@%t start waiting event %s", $time, name); @e; // 监测变化 $display("@%t end waiting event %s", $time, name); endtask initial begin fork wait_event(e1, "e1"); // 调用任务 wait_event(e2, "e2"); wait_event(e3, "e3"); join end initial begin fork begin #10ns e1 = !e1; end // bit是二值逻辑,初值为0,取反变为1 begin #20ns e2 = !e2; end begin #40ns e3 = !e3; end join end endmodule
-
wait_order()
可以是的进程保持等待,直到再参数列表中的事件event按照顺序从左到右依次完成 -
如果参数列表中的事件被触发但是没有按照要求的顺序,那么会使得等待操作失败
wait_order(a, b, c); wait_order(a, b, c) else $display("Error: event out of order"); bit success; wait_order(a, b, c) success = 1; else success = 0;
旗语(semaphore)
-
旗语从概念上讲,是一个容器
-
在创建旗语的时候,会为其分配固定的钥匙数量
-
使用旗语的进程必须先获得其钥匙,才可以继续执行
-
旗语的钥匙数量可以有多个,等待旗语钥匙的进程也可同时有多个
-
旗语通常用于互斥,对共享资源的访问控制,以及基本的同步
-
创建旗语并为其分配钥匙的方式如下:
-
创建旗语,并为其分配钥匙
semaphore sm; sm = new(x); // 有多少钥匙x就给多少
-
创建一个有固定钥匙数量的旗语:new(N)
-
从旗语那里获取一个或多个钥匙(阻塞型):sm.get(N)
-
将一个或多个钥匙返回到旗语中:sm.put(N)
-
尝试获取一个或多个钥匙不会阻塞(非阻塞型):sm.try_get(N)
-
信箱(mailbox)
-
信箱mailbox可以使得进程之间的信息得以交换,数据可以由一个进程写入信箱,再由另一个进程获取
-
信箱在创建时可以限制容量,也可以不限制
-
当信箱容量写满时,后续再写入的动作会被挂起,直到信箱中数据被读取,使得信箱有空间以后才可以继续写入
-
不限制容量的信箱则不会挂起写入信箱的动作
-
创建信箱
-
信箱的内建方法
-
创建信箱:new()
-
将信息写入信箱,信箱满了会阻塞:put(data),将data写到信箱中
-
试着写入信箱,不会阻塞:try_put()
-
从信箱中取出数据:get(data),从信箱中取出数据赋值给data
-
信箱数据快照,能拿到信箱内的数据,但信箱内的数据不会减少:peek()
-
试着读取数据:try_get()、try_peek()
-
获取信箱信息的数据数目:num()
-
可以结合num()与get()或者put(),防止信箱为空或者为满的时候阻塞
mailbox #(int) mb; // 定义一个信箱,#(int)表示只能存储int类型的数据 initial begin int val; // 定义一个int类型变量 mb = new(8); // 例化信箱,指定可存储数据8个 forever begin case($urandom() % 2) 0: begin val = $urandom_range(0,10); mb.try_put(val); $display("mb put value is %0d", val); end 1: begin mb.try_get(val); $display("mb get value is %0d", val); end endcase end end
-