SV——线程及线程间的通信(二)

1. 旗语 semaphore

​ 如果把进程执行看成“开车”这个行为,开车需要钥匙,旗语就类似于“钥匙”。一个线程执行要先申请钥匙(旗语),如果只有一个钥匙(旗语)并且被其他线程申请了,那么这个线程阻塞。当那个线程执行完了,返回钥匙(旗语),被阻塞的线程可以再申请钥匙(旗语)。

旗语可以用在多个进程共享资源的时候。

  1. new方法创建一个或多个旗语
  2. get获取一个或多个旗语,获取失败阻塞
  3. put返回一个或多个旗语
  4. try_get()试图获取旗语,但不阻塞。返回1表示有足够旗语,返回0表示旗语不够。
1
2
3
4
5
6
7
8
9
10
11
12
program automatic test;
semaphore sem; //创建一个旗语
initial begin
sem = new(1); //分配一把钥匙
run();
end
task run();
sem.get(1); //申请一把钥匙;此时如果其他进程申请这个旗语的钥匙,则阻塞。
.... // 使用资源
sem.put(1); //执行结束,返回钥匙
endtask
endprogram

2. 信箱 mailbox

信箱用来再多个线程之间传递事务,比如生成器和驱动器之间。

信箱类似一个FIFO,先入先出。

如果向一个定容的满了的信箱里添加对象,会阻塞;向空的信箱取对象也会阻塞。

信箱是一种对象,需要实例化。

get、put、peak(取对象数据的拷贝,但不删除它)。

信箱中最好只放一种类型的数据。(虽然放不同的数据语法不会错)。

信箱其实是一个参数类,可以指定信箱中放数据的类型。

信箱中存的是句柄(指针),如果只new了一个对象,只是将它随机了许多次,那么存入mailbox的对象是一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
initial begin
mailbox mlb#(Transaction); //指定类型
Transaction tr,tmp;
repeat(10) begin
tr = new(); // 在循环内创建对象
assert(tr.randomize());
mlb.put(tr); //存入mlb
end
repeat(10) begin
mlb.get(tmp); //从mlb取
tmp.show();
end
end

2.1 使用定容信箱同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Transaction;
rand bit[7:0] data;
rand bit[7:0] addr;
endclass
class Generator;
mailbox mlb;
Transaction tr;
function new(mailbox m);
this.mlb= m;
endfunction
task run();
repeat(5) begin
tr=new();
assert(tr.randomize());
mlb.put(tr);
$display("@ %0t [gen] put a tr",$time);
end
endtask
endclass

class Driver;
mailbox mlb;
Transaction tr;
function new(mailbox m);
this.mlb= m;
endfunction
task run();
forever begin
#10; //每延迟10ns,driver取一个数据,也可以同步到时钟沿@(posedge clk);
mlb.get(tr);
$display("@ %0t [drv] get a tr",$time);
end
endtask
endclass

program automatic tb;
Generator gen;
Driver drv;
mailbox mlb;
initial begin
mlb=new(1);//定容信箱,容量1,如果参数为空,则mailbox不限容量
gen=new(mlb);
drv=new(mlb);
fork // 并发执行
gen.run();
drv.run();
join
end
endprogram
// output
@ 0 [gen] put a tr
@ 10 [drv] get a tr
@ 10 [gen] put a tr
@ 20 [drv] get a tr
@ 20 [gen] put a tr
@ 30 [drv] get a tr
@ 30 [gen] put a tr
@ 40 [drv] get a tr
@ 40 [gen] put a tr
@ 50 [drv] get a tr

从输出可以看到,put一个tr,阻塞put操作,然后get一个tr,再put如此循环。

2.2 使用信箱和事件来同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
program automatic tb;
event handshake; // 事件
mailbox mlb;
class Generator;
...
task run();
repeat(5) begin
...
mlb.put(tr);
$display("@ %0t [gen] put a tr",$time);
@handshake; //生成完一个tr后阻塞。
end
endtask
endclass

class Driver;
...
task run();
forever begin
#10;
mlb.get(tr);
$display("@ %0t [drv] get a tr",$time);
->handshake; //获取完一个tr后触发事件
end
endtask

endclass

Generator gen;
Driver drv;
initial begin
mlb=new(); //无限容量
gen=new(mlb);
drv=new(mlb);
fork ... join_none
end
endprogram
@ 0 [gen] put a tr
@ 10 [drv] get a tr
@ 10 [gen] put a tr
@ 20 [drv] get a tr
@ 20 [gen] put a tr
@ 30 [drv] get a tr
@ 30 [gen] put a tr
@ 40 [drv] get a tr
@ 40 [gen] put a tr
@ 50 [drv] get a tr

2.3 使用两个信箱同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
program automatic tb;
mailbox#(Transaction) mlb;
mailbox#(int) m; // 定义两个信箱
class Generator;
Transaction tr;
int i;
task run();
repeat(5) begin
tr=new();
assert(tr.randomize());
mlb.put(tr);
$display("@ %0t [gen] put a tr",$time);
m.get(i); //m为空,阻塞
end
endtask
endclass

class Driver;
Transaction tr;
int i;
task run();
forever begin
#10;
mlb.get(tr);
$display("@ %0t [drv] get a tr",$time);
m.put(i); // m不为空,触发了Generator中的语句。
end
endtask
endclass

Generator gen;
Driver drv;
initial begin
mlb=new();
m=new(); //新建信箱
gen=new();
drv=new();
fork
gen.run();
drv.run();
join
end
endprogram
@ 0 [gen] put a tr
@ 10 [drv] get a tr
@ 10 [gen] put a tr
@ 20 [drv] get a tr
@ 20 [gen] put a tr
@ 30 [drv] get a tr
@ 30 [gen] put a tr
@ 40 [drv] get a tr
@ 40 [gen] put a tr
@ 50 [drv] get a tr