线程间同步和通信,event semaphore mailbox
- 1. 概述
- 2. 事件event
- 3. wait_order()
- 4. 旗语(semaphore)
- 5. semaphore::get();
- 6. semaphore::try_get()
- 7. 信箱mailbox
1. 概述
- 测试平台中的所有线程都需要同步并交换数据
- 一个线程等待另外一个,例如验证环境需要等待所有激励结束、比较结束才可以结束仿真
- 比如检测器需要将将测到的数据发送至比较器,比较器又需要从不同的缓存获取数据进行比较
2. 事件event
- 可以通过
event
来声明一个命名event变量,并且去触发它 - 这个命名event可以用来控制进程的执行
- 可以通过
->
来触发事件 - 其他等待该事件的进程可以通过@操作符或者wait()来检查event触发状态来完成
`timescale 1ns/1ns
module tb7;
event e1, e2, e3;
task automatic wait_event(event e, string name);
$display("@%t start waiting event %s", $time, name);
@e;
$display("@%t finish 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 #30ns -> e3; end
join
end
endmodule
module tb8;
bit e1, e2, e3;
task automatic wait_event(ref bit e, input string name);
$display("@%t start waiting event %s", $time, name);
@e;
$display("@%t finish 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
$display("before fork, e1 = %x", e1);
fork
begin #10ns e1 = !e1; end
begin #20ns e2 = !e2; end
begin #30ns e3 = !e3; end
join
$display("after fork, e1 = %x", e1);
end
endmodule
3. wait_order()
wait_order
可以使得进程保持等待,直到在参数列表中的事件event按照顺序从左到右依次完成- 如果参数列中的事件被触发但是没有按照要求的顺序,那么使得等待操作失败
wait_order(a, b, c);
wait_order(a, b, c) else $display("Error: events out of order");
bit success;
wait_order(a, b, c) success = 1; esle success = 0;
【Q】下面关于event的说法哪些是正确的?
A: event之间也可以拷贝
B: 被拷贝的event与拷贝的event指向同一个event
C: 如果event先发生,再@event,不会被阻塞
D: 如果event先发生,再wait(event.triggered),不会被阻塞
【An】
event之间做拷贝,拷贝的是句柄
@是边沿触发
wait是电平触发
4. 旗语(semaphore)
- 旗语从概念上讲,是一个容器
- 在创建旗语的时候,会为其分配固定的钥匙数量
- 使用旗语的进程必须先获得其钥匙,才可以继续执行
- 旗语的钥匙数量可以有很多个,等待旗语钥匙的进程也可同时有很多个
- 旗语通常用于互斥,对共享资源的访问控制,以及基本的同步
- 目的是为了保护共享资源,规定访问资源的钥匙·1
- 创建旗语,并为其分配钥匙,
semaphore sm;
sm = new(); - 创建一个具有固定钥匙数量的旗语,
new (N = 0) - 从旗语那里获取一个或多个钥匙(阻塞型),
get (N = 1) - 将一个或多个钥匙返回到旗语中,
put (N = 1) - 尝试获取一个或多个钥匙而不会阻塞(非阻塞型),
try_get (N = 1)
module tb9;
semaphore mem_acc_key;
int unsigned mem[int unsigned];
task automatic write_mem(int unsigned addr, int unsigned data);
mem_acc_key.get();
#1ns;
mem[addr] = data;
mem_acc_key.put();
endtask
task automatic read_mem(int unsigned addr, output int unsigned data);
mem_acc_key.get();
#1ns;
if(mem.exists(addr))
data = mem[addr];
else
data = 'x;
mem_acc_key.put();
endtask
initial begin
int unsigned data = 100;
mem_acc_key = new(1);
forever begin
fork
begin
#10ns;
write_mem('h10, data+100);
$display("@%t write with data %d", $time, data);
end
begin
#10ns;
read_mem('h10, data);
$display("@%t read with data %d", $time, data);
end
join
end
end
endmodule
5. semaphore::get();
- get()方法用于从旗语中获取指定数量的钥匙
- get()原型,
task get(int ketCount = 1); - keyCount指定从旗语获取所需的钥匙数,默认值为1
- 如果指定数量的钥匙可用,则该方法返回并继续执行
- 如果指定数量的钥匙不足,进程将阻塞,知道钥匙数目充足
- 旗语的等待队列是先进先出(FIFO),即先排队等待旗语的将优先得到钥匙
6. semaphore::try_get()
- try_get()方法用于从信号量中获取指定数量的钥匙,但不会被阻塞
- try_get()原型,
function int try_get(int keyCount = 1); - keyCount指定从旗语处获取所需的钥匙数目,默认值为1
- 如果指定数量的钥匙可用,则该方法返回正数并继续执行
- 如果指定数量的钥匙不足,则该方法返回0
7. 信箱mailbox
- 信箱mailbox可以使得进程之间的信息的以交换,数据可以由一个进程写入信箱,再又另外一个进程获得
- 信箱在创建时可以限制其容量,或者不限制
- 当 信箱容量写满时,后续再写入的动作会被挂起,直到信箱的数据从中读取,使得信箱有空间以后才可以继续写入
- 不限制容量的信箱则不会被挂起写入信箱的动作
7.1. 信箱的内建方法
- 创建信箱:new()
- 将信息写入信箱:put()
- 试着写入信箱但不会阻塞:try_put()
- 获取信息:get()同时会取出数据,peek()不会取出数据
- 试着从信箱中取出数据但不会阻塞:try_get() / try_peek()
- 获取信箱信息的数据:num()
7.2. mailbox::new()
- 可以在创建信箱的时候限定或者不限定其大小,
function new(int bound = 0); - 默认情况下,如果不传入参数,bound默认值为0,表示不限定信箱大小,如果传入的数值大于0,那么表示信箱的最大容量
- bound应为正数,如果为负数的话,系统会提示警告和出现无预期的行为
7.3. mailbox::num()
- num()会但会信箱目前的消息数目
- 可以结合num()和get()或者put(),防止get() / put()方法在信箱为空或者为满的时候被阻塞
module tb10;
mailbox #(int) mb;
initial begin
int data;
mb = new(8);
forever begin
case($urandom_range(0, 1))
0: begin
if(mb.num() < 8) begin
data = $urandom_range(0, 10);
mb.put(data);
$display("mb put data %0d", data);
end
end
1: begin
if(mb.num() > 0) begin
mb.get(data);
$display("mb get data %0d", data);
end
end
endcase
#1ns;
end
end
endmodule
7.4. 参数化信箱
- 默认的信箱,在没有指定存储类型的情况下,可以存储任何类型的数据(程序也可能会为此受伤)
- 为了避免运行时错误和类型不匹配,建议在声明信箱的时候为其指定存储类型
【Q】关于信箱的说法,正确的是?
A: 可以指定信箱存储的数据类型
B: 可以不限制信箱的容量大小
C: 在信箱写满时,继续写入,数据会溢出丢失
D: 在信箱为空时,调用get()方法将返回数值0
【An】
信箱写满继续写会等待
信箱为空,用get()会阻塞,用try_get()返回0