线程控制
并行线程
- Verilog中与顺序线程begin…end相对的是并行线程fork…join。
- SV引入了两种新新的创建线程的方法,fork..join_none和fork…join_any。
fork…join需要所有并行的线程都结束以后才会继续执行。
fork…join_any则会等到任何一个线程结束以后就继续执行。
fork…join_none则不会等待其子线程而继续执行。
需要注意的是,fork..…join any和fork…join_none执行后,其一些未完成的子程序仍将在后台运行。
如果要等待这些子程序全部完成,或者停止这些子程序可以使用wait fork或者disable fork。
- wait fork 是等待当前语境所有的的fork语句块全部结束(并非放行)之后就可以执行。
- disable fork 是关闭当前语境的所有未执行完的fork语句块。
1 | initial begin:ini procl |
时序控制
SV可以通过延迟控制或者事件等待来对过程块完成时序控制。
- 延迟控制即通过#来完成。
- 事件(event)控制即通过@来完成。
- wait语句也可以与事件或者表达式结合来完成。
1
2
3
4
5
6
7#10 rega=regb; //延时控制
@r rega=regb; //时间控制
@(posedge clock) rega=regb;
real AOR[]; //wait表达式控制
initial wait(AOR. size()>0)....;
进程间同步和通信
概述
- 测试平台中的所有线程都需要同步并交换数据。
- 一个线程等待另外一个,例如验证环境需要等待所有激励结束、比较结束才可以结束仿真。比如监测器需要将监测到的数据发送至比较器,比较器又需要不同的缓存获取数据进行比较。
事件(event)
- 可以通过event来声明一个命名event变量,并且去触发它。
- 这个命名event可以用来控制进程的执行。
- 可以通过”->”来触发事件。
- 其它等待该事件的进程可以通过**@**操作符(边沿触发)或者
wait()
(电平触发)来检查event触发状态来完成。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22event 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 8s",$time,name);
endtask
initial begin
fork
wait_event(el,"el"); //开始时 等待事件1
wait_event(e2,"e2"); //开始时 等待事件2
wait_event(e3,"e3"); //开始时 等待事件3
join
end
initial begin
fork
begin #10ns->e1 end; //开始10ns时刻 触发事件1
begin #20ns->e2 end; //开始20ns时刻 触发事件2
begin #30ns->e3 end; //开始30ns时刻 触发事件3
join
end
//如果将event换成bit,每次触发的时候反向,那么传入参数需要变成(ref bit e)实时监控信号变化event类型传递的类似于句柄,自带实时监测功能。
@和wait一般没有区别,但同一时刻既触发又等待是时候,两者有区别。
旗语(semaphore)
- 旗语从概念上讲,是一个容器。
- 在创建旗语的时候,会为其分配固定的钥匙数量。使用旗语的进程必须先获得其钥匙,才可以继续执行。
- 旗语的钥匙数量可以有多个,等待旗语钥匙的进程也可同时有多个。
- 旗语通常用于互斥,对共享资源的访问控制,以及基本的同步。
1
2
3
4
5
6
7//创建旗语,并为其分配钥匙的方式如下:
semaphore sm;
sm=new();
new(N=0); //创建一个具有固定钥匙数量的旗语,并生成N(默认为0)把钥匙
get(N=1); //从旗语那里获取一个(默认)或多个钥匙(阻塞型),钥匙可用返回1继续执行,钥匙不足,进程阻塞,排队拿取钥匙,等待队列先进先出(FIFO)
put(N=1); //将一个或多个钥匙返回到旗语中
try_get(N=1);//尝试获取一个或多个钥匙而不会阻塞(非阻塞型),钥匙可用,返回1继续执行,要是不足,返回0,但不阻塞。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
34semaphore mem_acc_key;
int unsigned mem [int unsigned];
task automatic write(int unsigned addr, int unsigned data); //写函数
mem_acc_key.get(); //获取钥匙
#1ns; //写1ns
mem[addr]=data; //写入数据
mem_acc_key.put(); //还钥匙
endtask
task automatic read(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('h10, data+100);
$display("@%t write with data %0d", $time, data);
end
begin
#10ns;
read('h10, data);
$display("@%t read with data %0d", $time, data);
end
join
end
end
信箱(mailbox)
- 信箱mailbox可以使得进程之间的信息得以交换,数据可以由一个进程写入信箱,再由另外一个进程获得。信箱在创建时可以限制其容量,或者不限制。
- 当信箱容量写满时,后续再写入的动作会被挂起,直到信箱自据从中读取,使得信箱有空间以后才可以继续写入。不限制容量的信箱则不会挂起写入信箱的动作。
1
2
3
4
5
6
7
8//创建信箱的方式如下:
new(N=0); //创建信箱,默认不限定大小(即N=0),传如正参数表示限定信箱容量
put(); //将信息按照FIFO顺序写入信箱,如果信箱满了,则put任务挂起,直到有新空间
try_put(); //试着按照FIFO写入信箱,但不会阻塞,写入成功返回1,写入失败返回0
get(); //获取信息同时取出数据,若信箱为空,get任务挂起,直到有信息可以读取,读取的信息会同时从信箱移除
peek(); //获取信息但不会取出数据,更像是一种拷贝,信箱中的信息不会被移除
try_get()/try_peek(); //试着取出数据但不会阻塞,若读取成功返回1.读取失败返回0
num(); //获取信箱信息的数目,通常结合读写信函数,防止被阻塞。
参数化信箱
- 默认的信箱,在没有指定类型的情况下,可以储存任何类型的数据,但为了避免运行错误和类型不匹配,建议在声明时指定储存类型。
- 这种参数化信箱的方式可以在编译的时候检查类型不匹配的情况。
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
28module tb;
mailbox #(int) mb; //限定信箱内容类型
initial begin
int data;
mb=new(8); //限定信箱容量
forever begin
case($urandom()%2)
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.try_get(data);
$display("mb get data %0d", data);
end
end
endcase
end
end endmodule
typedef mailbox #(string) s_mb; //限定信箱内容为字符串
s_mb mb = new(8);
虚方法
概述
- 类的成员方法(变量不行)可加以修饰词virtual(虚方法)。
- 虚方法是一种基本的多态(polymorphic)结构。
- 一个虚方法可以覆盖基类的同名方法。
- 在父类和子类中声明的虚方法,其方法名、参数名、参数方向等都应该保持一致。
- 在调用虚方法时,它将调用句柄指向对象的方法,而不受句柄类型的影响。
应用
1 | class BasePacket; |
类型转换
静态转换
- 静态转换操作符不对转换值进行检查。
- 转换时指定目标类型,并在要转换的表达式前加上单引号’
- Verilog对整数和实数类型,或者不同位宽的向量之间进行隐式转换。
1
2
3
4int i;
real r;
i=int'(10.0-0.1);//cast is optional
r=real'(42);//cast is optional
动态转换
- 将子类的句柄赋值给父类的句柄可以(子->父),在我们将父类的句柄赋值给子类的句柄时编译将会报错。
- $cast()系统函数可以将父类句柄转换为子类句柄,只要该柄指向的是一个子类的对象。(在父类需要访问子类变量的时候用到,父类句柄访问子类方法可以使用虚方法)