本文思维导图如下:
1. 线程控制
1.1 并行线程
Verilog中与顺序线程begin…end相对的是并行线程fork…join。
1.1.1创建线程
SV引入了两种新新的创建线程的方法,fork…join_none和fork…join_any。
- fork…join
fork…join需要所有并行的线程都结束以后才会继续执行。 - fork…join_any
fork…join_any则会等到任何一个线程结束以后就继续执行。 - fork…join_none
fork…join_none则不会等待其子线程而继续执行。 实例
1.1.2子程序等待/停止
需要注意的是,fork…join_any和fork. . .join_none继续执行后,其一些未完成的子程序仍将在后台运行。如果要等待这些子程序全部完成,或者停止这些子程序,可以使用wait fork或者disable fork。
(1)wait fork
1.2.2 事件控制
事件(event)控制即通过@来完成。
@r rega = regb ;
@(posedge clock) rega = regb;
1.2.3 事件等待
wait语句也可以与事件或者表达式结合来完成。
real AOR[] ;
initial wait(AOR.size() > 0) …. ;
2. 进程间同步和通信
测试平台中的所有线程都需要同步并交换数据。
一个线程等待另外一个,例如验证环境需要等待所有激励结束、比较结束才可以结束仿真。比如监测器需要将监测到的数据发送至比较器,比较器又需要从不同的缓存获取数据进行比较。
2.1 事件
- 声明
可以通过event来声明一个命名event变量,并且去触发它。这个命名event可以用来控制进程的执行。 - 触发
可以通过->来触发事件。
其它等待该事件的进程可以通过@操作符或者wait()来检查event触发状态来完成。 - 等待
wait__order可以使得进程保持等待,直到在参数列表中的事件event按照顺序从左到右依次完成。如果参数列表中的事件被触发但是没有按照要求的顺序,那么会使得等待操作失败。wait_order ( a, b, c);
wait order( a, b, c ) else $display ( “Error: eventsout of order” );
bit success ;
wait order( a, b, c ) success = 1; else success = 0 ;
2.2 旗语
旗语从概念上讲,是一个容器。在创建旗语的时候,会为其分配固定的钥匙数量。使用旗语的进程必须先获得其钥匙,才可以继续执行。旗语的钥匙数量可以有多个,等待旗语钥匙的进程也可同时有多个。旗语通常用于互斥,对共享资源的访问控制,以及基本的同步
### 2.2.1用法 * 创建旗语,并为其分配钥匙的方式如下: >semaphore sm; >sm = new ();创建一个具有固定钥匙数量的旗语:
new (N = 0)
从旗语那里获取一个或多个钥匙(阻塞型) :
get (N = 1)
将一个或多个钥匙返回到旗语中:
put (N = 1)
尝试获取一个或多个钥匙而不会阻塞(非阻塞型) :
try_get (N = 1)
2.2.2基本操作
senmaphore: :new()
new ())的原型如下:
function new (int keyCount = 0) ;
keyCount指定最初分配给旗语的钥匙数目。keyCount的默认值为0。当更多钥匙放入旗语时,钥匙数目可以超出初始时的keyCount数量,而不是删除。
semaphore: :put()
put ()方法用于将钥匙数量返回给旗语。
put ()的原型如下:
function void put (int keyCount = 1) ;
keycount指定返回到旗语的钥匙数量。默认值为1。调用semaphore.put ()函数时,指定数量的钥匙将返回到旗语。如果其它进程已经在等待旗语,则该进程应在有足够数量钥匙的情况下返回。
semaphore: :get()
get ()方法用于从旗语中获取指定数量的钥匙。get ()的原型如下:
task get (int keycount = 1) ;
keyCount指定从旗语获取所需的钥匙数,默认值为1。如果指定数量的钥匙可用,则该方法返回并继续执行。如果指定数量的钥匙不足,进程将阻塞,直到钥匙数目充足。旗语的等待队列是先进先出(FIFO),即先排队等待旗语的将优先得到钥匙。
semaphore : :try_get()
try get ()方法用于从信号量中获取指定数量的钥匙,但不会被阻塞。try_get ()的原型如下:
function int try_get (int keyCount = 1) ;
keyCount指定从旗语处获取所需的钥匙数目,默认值为1。如果指定数量的钥匙可用,则该方法返回正数并继续执行。如果指定数量的钥匙不足,则该方法返回0。
2.2.3实例
## 2.3 信箱 信箱mailbox可以使得进程之间的信息得以交换,数据可以由一个进程写入信箱,再由另外一个进程获得。 信箱在创建时可以限制其容量,或者不限制。当信箱容量写满时,后续再写入的动作会被挂起,直到信箱的数据从中读取,使得信箱有空间以后才可以继续写入。 不限制容量的信箱则不会挂起写入信箱的动作。 ### 2.3.1 内建方法 #### new() new()用于创建信箱。可以在创建信箱的时候限定或者不限定其大小。默认情况下,如果不传入参数,bound默认值为0,表示不限定信箱大小,如果传入的数值大于0,那么表示信箱的最大容量。bound应为正数,如果未负数的话,系统会提示警告和出现无法预期的行为。 >function new (int bound = 0) ;put()
put()会将信息按照FIFO的顺序写入到信箱中,如果信箱此时已满,则put()任务会挂起,直到信箱有新的空间可以容纳消息。
try_put()
try put()试着写入信箱但不会阻塞。try put()也会按照FIFO顺序写入信箱,不会发生阻塞。如果信箱已满,则写入失败,返回0;如果信箱未满,则写入成功,返回1。
get()/peek()
获取信息: get()同时会取出数据,peek()不会取出数据。get()会将信息从信箱中取出,如果信箱此时为空,则get()任务会挂起,直到信箱中有消息可以读取,任务才会返回。该方法会将读取到的消息从信箱中移除。
try_get()/try_peek()
试着从信箱取出数据但不会阻塞。try get()也会将信息从信箱中取出,只是该函数不会发生阻塞。如果信箱为空,则读取失败,返回0;如果信箱不为空,则读取成功,返回1。该方法也会将读取到的消息从信箱中移除。
num()
num()会返回信箱目前的消息数目。可以结合num()与get()或者put(),防止get()/put()方法在信箱为空或者为满的时候被阻塞。
2.3.2 实例
mailbox #(int) mb;//声明mailbox句柄,并且指明该mailbox中只能存放int类型的数据
2.4虚方法
给pringtB添加关键词virtual,P1扩大查找范围,扩大到子类对象里面。如果子类里面有同名的方法,若有,调用子类里面同名的方法(以子类的实现优先)。
子类不添加virtual无影响,父类添加即可。
或者
task $cast( singular dest_var, singular source_exp );
4. 问答题
1. 用bit key =0来实现的类似旗语(semaphore)的功能,来保护对共享资源的访问,是否有不安全的地方?示例代码如下(包含自然语言表征)。如果有不安全的地方,请用文字表述,并且对以下代码进行修改,并且进行测试、编译和仿真打印消息。
bit key = 1;//创建钥匙
task get_key();
wait(key > 0);
key = 0;//获得钥匙
…//省略对于共享资源的处理
key = 1; /还回钥匙
endtask
答:当多个task并行执行时,只要key=1,满足wait(key>0)即可执行,无法实现资源同时只能被指定个数的进程访问的目的。
2. SV的原生旗语semaphore在例化以后,并不能从其获取哪一个对象(字符串stringID)获取了钥匙,那么请你将semaphore封装到一个类中,重新定义get()/put(方法,并且通过最后的一个打印函数来得知当前获取钥匙的string ID是谁?示例代码大致如下,请完成提示需要完成的地方,对你的代码进行编译和仿真,展示你的代码和仿真的打印消息。
答:sv代码如下
module tb;
class smart_key;
semaphore s;//创建旗语
string holder;
function new(N= 1);
s = new(N);//分配1把钥匙
endfunction
task get(int N= 1,string str);//请添加需要的参数,并且实现该方法
s.get();//拿到钥匙
holder = str;
#1ns;
//$display("%0t: %s get the key",$time,str);
endtask
task put(int N= 1,string str);//请添加需要的参数,并且实现该方法
s.put();//放回钥匙
#1ns;
//$display("%0t: %s put the key",$time,str);
endtask
function void print_key_holders();//请事先该方法,使其能够打印出当前获得钥匙的ID
$display("%0t: now %s hold the key",$time,holder);
endfunction
endclass: smart_key
initial begin
smart_key skey = new();
string string1 = "A";
string string2 = "B";
string string3 = "C";
fork
begin
skey.get(1,string1);
skey.print_key_holders();
skey.put(1,string1);
end
begin
skey.get(1,string2);
skey.print_key_holders();
skey.put(1,string2);
end
begin
skey.get(1,string3);
skey.print_key_holders();
skey.put(1,string3);
end
join
end
endmodule: tb
仿真结果:
1000: now A hold the key
2000: now B hold the key
3000: now C hold the key
3. 我们在使用信箱mailbox时,如何可以限定其容量为8,并且只能存放string类型呢?那么,是否类似的方法也可以限定一个存放字符串的队列其最大容量为8呢?
答:声明方法如下
mailbox #(int) mb;
mb = new(8);
队列空间是动态的,不能限定其最大容量。
写的不错,可以参考:SV学习笔记—线程之间的通信(事件event、信箱mailbox、旗语semaphore)_Verification_White的博客-CSDN博客_sv event