路科V0P23P25线程控制-线程同步-线程通信

线程控制

什么是线程

  • 线程是独立运行的程序;
  • 需要被触发,可以结束或者不结束;
  • 在module中的initial和always,都可以看做独立的线程,会在仿真0时刻开始,选择结束或者不结束;
  • 硬件模型中,由于都是always语句块,所以可以看成是多个独立运行的线程,他们一直占用着仿真资源,因为不会结束
  • 验证环境中需要由initial语句块去创建,在仿真过程中,验证环境中的对象可以创建和销毁,因此验证环境中的资源是动态的。
  • 验证环境中的initial语句块有两种分组方式,begin..end和fork..join
    • begin..end : 顺序方式执行
    • fork..join : 并发方式执行
    • 与fork..join类似的,还有fork..join_any和fork..join_none
  • 线程的执行轨迹是树状结构,任何线程都有父线程;
  • 父线程可以开辟若干个子线程,父线程可以暂停或者终止子线程;
  • 子线程终止时,父线程可以继续执行;
  • 父线程终止时,它的所有子线程都会终止

并行线程

  • fork..join : 等待所有子线程结束,才会继续执行后面的;
  • fork..join_any : 只要有一个子线程结束(最短的先结束),就会继续执行后面的;
  • fork..join_none : 不需要等待任何子线程结束,就可以继续执行后面的;
  • 注意 : 虽然无需等待,但是fork..join_any和fork..join_none执行后面的时候,前面的子线程还在执行;
  • 如果要等待这些子线程都完成,或者停止这些子线程,可以使用wait fork或者disable fork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fork
begin
$display("First Block\n");
# 20ns;
end
begin
$display("Second Block\n");
@eventA;
end
join

task do_test;
fork
exec1();
exec2();
join_any
fork
exec3();
exec4();
join_none
wait fork //等待所有子线程结束
endtask

时序控制

  • SV中可以通过延迟控制或者事件等待来完成时序控制;
  • 延迟控制通过#完成; #10 rega = regb;
  • 事件控制(event)通过@完成;
    • @r rega = regb;
    • @(posedge clock) rega = regb;
  • wait语句可以与事件或者表达式结合,来完成时序控制;
    • real AOR[]; initial wait(AOR.size() > 0) ...;

线程同步

概述

  • 测试平台中所有线程都需要同步并交换数据;
  • 一个线程等待另外一个,比如验证环境需要等待所有激励结束,比较结束才能结束仿真;
  • 比如监测器需要将数据发送到检查器,检查器又需要从不同的缓存获取数据进行比较;

线程同步的第一个类型:event事件

  • 可以通过event声明一个event变量,并且触发它;
  • 这个event变量可以用来控制多个线程间的同步;一端触发,另一端阻塞等待;
  • 通过->操作符触发事件;
  • 其他等待该事件的线程可以通过@操作符或者wait()来检查event的触发状态;
  • wait_order()方法: 可以使线程保持等待,直到在参数列表中的事件event按照从左到右的顺序依次完成
  • 如果参数列表中的事件被触发,但是没有按照要求顺序,也会失败;
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
event done, blast;
event done_too = done;
task trigger(event ev);
-> ev;
endtask

fork
@ done_too;
#1 trigger(done);
join

fork
->blast;
wait(blast.triggered);
join


// wait_order
wait_order(a,b,c) else
$display("Error: events out of order");

bit success;
wait_order(a,b,c)
success = 1;
else
success = 0;

线程同步的第二个类型:旗语semaphore

  • 旗语可以看做打开共享资源大门的钥匙;用于访问控制保护;
  • 创建旗语时,会为其分配固定的钥匙数量
  • 使用旗语时,必须先获得钥匙,才能继续执行;
  • 旗语的钥匙数量可以有多个,等待旗语钥匙的线程也可以有多个;
  • 旗语通常用于互斥,对共享资源的访问控制,以及基本的同步

旗语的创建

  • 创建旗语,并为他分配钥匙的方式:
    • semaphore sm; sm = new();
  • 创建一个固定钥匙数量的旗语,new(N)
  • 从旗语那里获取一个或者多个钥匙(阻塞型): get(N=1)
  • 将一个或者多个钥匙返回到旗语中: put(N=1)
  • 尝试获取一个或者多个钥匙,而不阻塞try_get(N=1)
  • new函数默认为0,但是可以put超过开始的数量值;
  • new()返回旗语的句柄;
  • put() : 如果其他进程在等待旗语,则应该在该进程有足够数量钥匙的情况下返回;
  • get() : 如果指定数量的钥匙可用,则方法返回并继续执行,如果不足,进程阻塞直到钥匙数目充足;
  • 旗语的等待队列是FIFO的,先排队的先获得;
  • try_get() : 如果指定数目可用,返回正数并继续执行,否则,返回0;

线程通信

除了event、semaphore之外,还有mailbox信箱

信箱可以放置任何类型,可以设置尺寸大小,防止存储数据过多占用资源,信箱也是FIFO的

信箱的内建方法

  • new() : 创建信箱,默认尺寸bound为0,表示不限制大小,否则限制为最大值N;
  • put() : 将信息写入信箱,如果信箱已满,则put被挂起,直到可以有新的空间;
  • try_put() : 尝试写入信箱,不发生阻塞;如果满,不阻塞,返回0,否则成功返回1;
  • get() : 从信箱中获取信息,并取出
  • peek() : 获取信息,但是不取出,只是拷贝,如果信箱为空,peek会挂起,直到有消息;
  • try_get() try_peek() : 非阻塞取出
  • num() : 获取信箱信息的数目;

参数化信箱

  • 虽然信箱可以存放各种数据类型,但是为了之后用的方便,在声明时最好指定存储类型;
  • 这种参数化信箱的方式可以在编译时就能检查出类型不匹配的情况;
1
2
3
4
5
6
typedef mailbox #(string) s_box;
s_box sm = new;
string s;
sm.put("hello");

sm.get(s);

信箱和队列的区别

  • 信箱必须通过new例化,但是队列只需要声明即可;
  • 信箱的存取方法put()和get()是阻塞方法,但是队列的存取方法,push_back()和pop_front()方法是非阻塞的,会立即返回;
  • 在传递形参时,如果是input方向,信箱类型传递到是句柄,而队列类型则是完成的队列内容的拷贝;

线程通信的比较

  • event : 最小信息量触发,即单一通知功能,可以用来做事件的触发,也可以多个事件组合起来用作线程同步;
  • semaphore : 共享资源的卫士,如果多线程对某一共享资源做访问,则可以使用这个要素;
  • mailbox : 精小的SV原生FIFO,在线程之间做数据通信或者内部数据缓存时考虑使用这个元素;
作者

Gavin

发布于

2022-05-15

更新于

2022-05-15

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×