当前位置:网站首页>【数字IC验证快速入门】19、SystemVerilog学习之基本语法6(线程内部通信...内含实践练习)
【数字IC验证快速入门】19、SystemVerilog学习之基本语法6(线程内部通信...内含实践练习)
2022-07-07 13:22:00 【luoganttcc】
导读:作者有幸在中国电子信息领域的排头兵院校“电子科技大学”攻读研究生期间,接触到前沿的数字IC验证知识,旁听到诸如华为海思、清华紫光、联发科技等业界顶尖集成电路相关企业面授课程,对数字IC验证有了一些知识积累和学习心得。为帮助想入门前端IC验证的朋友,思忱一二后,特开此专栏,以期花最短的时间,走最少的弯路,学最多的IC验证技术知识。
一、内容概述
- 事件 event
- 线程之间执行先后时间顺序的控制
- 旗语(信号量)
- 线程之间共享区域的管理
- 邮箱:mailbox
- 线程之间数据传输使用
注:三种机制应用场景不一样,不能相互替代。
二、内部线程通信机制:Verilog event
2.1、Verilog event
Verilog 语言中使用event同步线程
触发事件的操作符:
->
- 不会阻塞线程的执行
等待事件被触发:
@
- 边沿敏感,总会阻塞线程执行
- 只有当事件发生变化时,线程才会继续执行
当阻塞线程之前发生触发线程时,可能引起竞争现象
- 触发晚于等待
2.2、SystemVerilog event
- 同步线程
- event 是一个同步对象的句柄,可以党组作参数传递给子程序
- 不需要声明为全局变量,就可以将event作为共享资源使用
- 触发一个事件的操作符:
->
和->>
->>
触发需要时钟的边沿完成
- 等待一个事件被触发的操作符:
@
和wait()
@
是等待一个边沿;wait
是等待 一个状态;@
等待一个边沿容易产生永远等不到触发的情况,SV做了改进,引入了triggered
.
triggered
函数用于检查一个事件是否被触发过,返回值是一个状态wait(event_variable.triggered)
- 如果在当前的仿真时间范围内,事件曾经被触发过,语句不会被阻塞
- 否则,
wait(event.triggered)
语句会一直等待被触发 - 所以,它是最安全最保险的等待语句
2.3、事件 event :语句阻塞在事件的边沿
- Verilog 语言中使用event同步线程,事件持续时间是整个事件触发的那个时间点。
- 触发事件的操作符:
->
(不会阻塞线程的执行) - 等待事件被触发:
@
(边沿触发)
initial
在软件上来看是并行的,但是实际运行也是有时间差的,所以第一个initial会先执行,第二个initial会后执行,不过是两个相差时间特别短!- 故,e1在第一个initial中先被触发,在第二个initial中再等待触发,显然e1等待不到;e2在第一个initial中先等待,在第二个initial中再被触发,所以e2可以被触发。
2.4、事件event:等待一个事件触发
- event 是边沿敏感
- wait(event_variable.triggered) 是电平敏感
- 与触发的时间没有先后
- 与触发的时间没有先后
2.5、事件中的循环
- 在两个线程之间通过事件同步时必须注意
- 如果在循环中使用
wait(evnet.triggered)
,必须保证在等待下一个循环之前更新仿真时间 - 否则,代码将进入
0
延迟的循环,将一次又一次的在单一事件触发时进行等待
- 如果在循环中使用
- 一定要小心零延时循环,建议不要用
wait(event_variable.triggered)
这种用法,它不会阻塞线程;建议还是用@ event_variable
,它会阻塞线程。
2.6、event 可以用作参数
- program 里面的变量默认是静态的,加了automatic之后就不是静态的了。
this.done
是类里面实例化对象的done
2.7、内部线程通信机制:event
阻塞事件触发(立即生效)
- 使用
->
操作符 - 触发一个事件不会阻塞当前等待该事件的所有进程
- 触发事件的行为边沿敏感信号
- 使用
非阻塞事件触发(等待到非阻塞触发区间,如上升沿)
- 使用
->>
操作符 - 在事件发生的时间点创建一个非阻塞赋值
- 在仿真时间的非阻塞区(如上升沿)更新事件
- 使用
等待一个事件(SV & Verilog 都有的)
@event_name
@
操作符将会阻止进程执行,直到事件被阻塞- 触发进程执行触发行为之前,等待进程必须执行
@
语句,触发行为不会阻止进程等待事件- 如果先发生了触发行为,那么等待进程将继续等待
持久触发:
triggered
(SV独有)- 在当前的仿真时间区间内,一个事件被触发过,那么triggered事件属性为真,否则为假。
wait(event_name.triggered)
举例:
注:
event done_too = done;
:称为事件的合并(双向)。操作done就相当于操作done_too,同理操作done_too也相当于操作done;
- 事件序列:
wait_order()
- 当指定的一系列事件按照从左到右的顺序发生时,
wait_order
进程开始执行 - 如果指定的一系列事件是乱序执行的,
wait_order
进程将不会执行
- 当指定的一系列事件按照从左到右的顺序发生时,
event a, b, c;
wait_order(a, b, c); // 必须按照 a->b->c 的顺序
- 1
- 2
- 3
- 4
- 5
- 事件变量:合并变量
- event 是一个独立的数据类型,可以进行赋值
- 当把一个事件赋值给另一个事件时,原事件与目的事件共享原事件
- 当把一个事件赋值给另一个事件时,两个事件合并为一个事件
- 事件变量
- 当事件合并时,赋值操作仅仅会影响目的事件的执行或等待操作
- 当把event赋值给event1时,如果一个线程正在等待event1,那么当前的等待线程将被阻塞,无法执行。
由于
E2 = E1
导致T1这个线程长期阻塞无法执行。解决办法:在fork外面先赋值,再去等待E2的触发,如下图:
注:while这里没有时间更新,所以是有问题,跑仿真会一直block
- 取消事件
- 当一个事件赋值为null时,与该时间变量同步的进程无效
- 比较事件
- 不同的事件可以进行比较
- 等于
==
- 不等于
!=
- 全等于
===
(和等于==
并无区别) - 不全等于
!==
- 如果一个事件为null,其他事件位1,那布尔表达式为0
- 等于
- 不同的事件可以进行比较
三、内部线程通信机制:semaphore
3.1、旗语(信号量) semaphore
- Semaphore 通常用于对共享资源的分配和同步
- 共享资源在不同进程中是互斥使用
- 在内存中创建
semaphore
时,类似于创建一个篮子(bucket
),篮子中包含一定数量的钥匙(keys
) - 进程在执行之前必须从篮子中获取一个钥匙
- 当一个特定的进程需要钥匙时,只有一定数量的进程在同时运行
- Semaphore 是SV内建的类,提供以下方法(
function / task
)- 创建 semaphore:
new(keys_numbers)
- 获取一个或多个钥匙:
get()
- 返还一个或多个钥匙:
put()
- 非阻塞性的获取一个或多个钥匙:
try_get()
- 创建 semaphore:
- 在验证平台中,常常使用
semaphore
对共享资源进行分配,比如系统总线,在同一个时间点,只能由一个驱动器使用总线
注意:上述函数,如果不写参数,它是具有默认参数的!
3.2、使用new
函数,创建semaphore buckets
- 注意:
sem_a = new[4];
仅仅是声明了数组,每个信号量还没有指明钥匙数量,所以是无法put
的! - 类创建(new)实体用的是小括号:
sem = new(2);
;数组创建(new)实体用的是方括号:sem_a = new[4];
3.3、信号量获取与释放以及try_get
与get
释义
try_get
取不到就返回0;get
取不到就一直等
四、内部线程通信机制:mailbox
4.1、邮箱 mailbox
- Mailbox 是SV不同进程间的通信方式,通过mailbox可以在不同进程之间传递信息
- 将一个进程中的数据,通过mailbox传递给另外一个进程;当mailbox中没有数据时,线程将等待
- mailbox类似于一个FIFO,可以设置一定的深度 queue size
- 当邮箱中的信息数量达到邮箱深度时,邮箱为满
- 如果邮箱已经为满,进程就不能再往邮箱中存放信息,直到邮箱中的信息被取走,邮箱不再为满
- 邮箱是SV内建的类,提供以下方法
- 创建邮箱:
new()
- 将信息放入邮箱:
put()
- 非阻塞性将信息试着放入邮箱:
try_put()
- 从邮箱中取出信息:
get()
或peek()
- 非阻塞性从邮箱中取出信息:
try_get()
或try_peek()
- 创建邮箱:
4.2、使用new新建邮箱
4.3、验证平台中的邮箱
- 如何在两个线程之间传递信息?
- 生成器(
generator
)生成事务数据包,然后传递给驱动器(driver
)driver
将数据包传递给DUT
,driver
也要做一些跟时序相关的工作!
- 生成器和驱动器必须是异步操作(邮箱是个异步FIFO)
- 同步操作还得握手,异步操作的发和取是没有约束关系的
- 生成器(
program mailbox_example(bus_if.TB bus, ...);
class Generator
Transaction tr; //类里面嵌套类,tr 是句柄
mailbox mbx; //***** 定义邮箱
function new(mailbox mbx); // 这个new是generator去做初始化的new函数,在外面初始化函数的时候会去创建一个邮箱,也就是在new的时候会创建邮箱,然后mbx传给this.mbx. 注意里面的参数是形式参数,跟上面的mbx可以不一样
this.mbx = mbx;
endfunction
task run; // 作用是产生随机的激励,然后把随机的激励放到邮箱里面去,class Driver中会去get这个激励
repeat(10) begin
tr = new;
assert(tr.randomize); //为所有的变量进行随机化赋值
mbx.put(tr); //***** tr放入邮箱
end
entask
endclass
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
class Driver
Transaction tr;
mailbox mbx; //***** 定义邮箱
function new(mailbox mbx);
this.mbx = mbx;
endfunction
task run;
repeat(10) begin
mbx.get(tr); //***** 获取邮箱中的tr
@(posedge busif.cb.ack)
... //实际的物理时序
end
endtask
endclass
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
这两个类仅仅是定义好了Generator和Driver,相当于图纸,要想使用起来,还需要代码讲其实例化,代码如下:
mailbox mbx; //***** 在同一个mbx里面传递数据
Generator gen;
Driver drv;
initial begin
mbx = new; //***** 创建深度无限大的邮箱
gen = new(mbx); //***** 创建两个对象,也就是实例化,在创建对象的时候把mbx传递进来
drv = new(mbx); //*****
end
fork
gen.run(); // 产生随机激励
drv.run();
join
endprogram
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- Generator 产生的消息通常定义为Transaction
小结:比如generator要向driver发送数据,首先会在gen里面创建一个邮箱,然后put放入数据,再在driver里面创建一个邮箱,通过get来获取数据。最后需要在env里面进行实例化,将两个邮箱连接起来。
4.4、验证平台中非同步线程之间的通信
- 如果生成器和驱动器之间需要同步,则需要添加额外的握手信息
4.5、验证平台中同步线程之间的通信
- 如果生成器和驱动器之间需要同步,则需要添加额外的握手消息
4.6、验证平台中的线程和内部通信
- 很多组件,组件之间的通信就是通过邮箱
环境ENV,初次了解邮箱使用
- 下面这个也不用太关注,不是实际完整的代码,意义不大,仅仅了解邮箱本节!
x、实践练习
X.1、event 练习
x.1.1、线程等待@
和触发->
demo
thread_communication.sv
module thread_com();
event e1, e2;
initial begin
fork
begin
$display("******@%0d, event1 before trigger", $time);
->e1;
@e2;
$display("******@%0d, event1 after trigger", $time);
end
begin
$display("******@%0d, event2 before trigger", $time);
->e2;
@e1;
$display("******@%0d, event2 after trigger", $time);
end
join
end
endmodule
******@0, event1 before trigger
******@0, event2 before trigger
******@0, event1 after trigger
- 并发线程的并发行为是仿真工具的行为,在零时刻同时发生,但是实际上在指令执行的时候,还是先执行的上面的那一个线程,两者有微小的
δ
时间差 - 上述代码中的
e1
由于是先触发再等待,所以是等不到的!
x.1.2、线程等待wait(event_name.triggered)
和触发->
demo
thread_communication.sv
module thread_com(); event e1, e2; initial begin fork begin $display("******@%0d, event1 before trigger", $time); ->e1; //@e2; wait(e2.triggered) $display("******@%0d, event1 after trigger", $time); end begin $display("******@%0d, event2 before trigger", $time); ->e2; //@e1; wait(e2.triggered) $display("******@%0d, event2 after trigger", $time); end join end endmodule
******@0, event1 before trigger ******@0, event2 before trigger ******@0, event2 after trigger ******@0, event1 after trigger
wait(event_name.triggered)
等待,只要之前触发过就可以!
x.1.3、event
作为参数传递demo
thread_communication.sv
class trigger_gen; event done; function new(input event ext_done); this.done = ext_done; endfunction task run(); #10; $display("******@%0d:trigger done by -> done", $time); ->done; endtask endclass //Parameter event event ext_done; trigger_gen trg_gen; initial begin #5; fork begin trg_gen = new(ext_done); trg_gen.run; end begin $display("******@%0d:wait for ext_done trg_gen", $time); @(ext_done); $display("******@%0d:after ext_done trigger_gen by gen.run", $time); end join end
******@5:wait for ext_done trg_gen ******@15:trigger done by -> done ******@15:after ext_done trigger_gen by gen.run
- 同时,这里也注意体会双向赋值(事件的合并)
X.2、semaphore 练习
thread_communication.sv
//Semaphore semaphore sem[]; task send(input int sem_num, thread_num); sem[sem_num].get(1); $display("******@%0d:thread(%0d) get the key", $time, thread_num); sem[sem_num].put(1); endtask initial begin #50; sem = new[4]; foreach(sem[i]) sem[i] = new(1); repeat(3) begin fork send(0, 0); send(0, 1); join #1; end end
rslt.log
******@50:thread(0) get the key ******@50:thread(1) get the key ******@51:thread(0) get the key ******@51:thread(1) get the key ******@52:thread(0) get the key ******@52:thread(1) get the key
- 如果相加延时,但是不可以直接放到
send
中的,应该在fork...join
外面
X.3、mailbox 练习
X.3.1、基本整型传递(邮箱异步)Demo
thread_communication.sv
mailbox mb; int mb_in; int mb_out; initial begin #80; mb = new(20); fork for(int i; i<8; i++) begin #1; mb_in = 2*i+1; $display("******@%0d, mb in(%0d) : %0d", $time, i, mb_in); mb.put(mb_in); end for(int i; i<8; i++) begin #2; mb.get(mb_out); $display("******@%0d, mb out(%0d) : %0d", $time, i, mb_out); end join end
rslt.log
******@81, mb in(0) : 1 ******@82, mb out(0) : 1 ******@82, mb in(1) : 3 ******@83, mb in(2) : 5 ******@84, mb out(1) : 3 ******@84, mb in(3) : 7 ******@85, mb in(4) : 9 ******@86, mb out(2) : 5 ******@86, mb in(5) : 11 ******@87, mb in(6) : 13 ******@88, mb out(3) : 7 ******@88, mb in(7) : 15 ******@90, mb out(4) : 9 ******@92, mb out(5) : 11 ******@94, mb out(6) : 13 ******@96, mb out(7) : 15
X.3.2、基本整型传递(邮箱同步)Demo
thread_communication.sv
mailbox mb; int mb_in; int mb_out; event mb_e; initial begin #80; mb = new(20); fork for(int i; i<8; i++) begin #1; mb_in = 2*i+1; $display("******@%0d, mb in(%0d) : %0d", $time, i, mb_in); mb.put(mb_in); @mb_e; end for(int i; i<8; i++) begin #2; mb.get(mb_out); $display("******@%0d, mb out(%0d) : %0d", $time, i, mb_out); ->mb_e; end join end
******@81, mb in(0) : 1 ******@82, mb out(0) : 1 ******@83, mb in(1) : 3 ******@84, mb out(1) : 3 ******@85, mb in(2) : 5 ******@86, mb out(2) : 5 ******@87, mb in(3) : 7 ******@88, mb out(3) : 7 ******@89, mb in(4) : 9 ******@90, mb out(4) : 9 ******@91, mb in(5) : 11 ******@92, mb out(5) : 11 ******@93, mb in(6) : 13 ******@94, mb out(6) : 13 ******@95, mb in(7) : 15 ******@96, mb out(7) : 15
X.3.3、传递 class 数据
thread_communication.sv
class transaction; rand bit [15:0] addr; rand bit [31:0] data; constraint c1{ addr inside { [0:100], [1000:2000]}; data inside { 32'h5a5a_5a5a, 32'ha5a5a5, 32'hFFFF_FFFF}; } endclass class generator; transaction tr; mailbox mbx; int i=0; function new(input mailbox mb); this.mbx = mb; endfunction task run(); repeat(10) begin tr = new(); tr.randomize(); $display("******@%0d:generator %0d th (addr=%h, data=%h)", $time, i, tr.addr, tr.data); mbx.put(tr); i++; end endtask endclass class driver; transaction tr; mailbox mbx; int i=0; function new(input mailbox mb); this.mbx = mb; endfunction task run(); repeat(10) begin mbx.get(tr); $display("******@%0d:driver %0d th (addr=%h, data=%h)", $time, i, tr.addr, tr.data); i++; end endtask endclass mailbox mb1 = new(20); generator gen = new(mb1); driver drv = new(mb1); initial begin #100; fork gen.run(); drv.run(); join end
rslt.log
******@100:generator 0 th (addr=047f, data=ffffffff) ******@100:generator 1 th (addr=04b9, data=5a5a5a5a) ******@100:generator 2 th (addr=06e0, data=00a5a5a5) ******@100:generator 3 th (addr=0489, data=5a5a5a5a) ******@100:generator 4 th (addr=005e, data=5a5a5a5a) ******@100:generator 5 th (addr=0567, data=5a5a5a5a) ******@100:generator 6 th (addr=05ca, data=ffffffff) ******@100:generator 7 th (addr=040d, data=ffffffff) ******@100:generator 8 th (addr=0490, data=ffffffff) ******@100:generator 9 th (addr=05fe, data=00a5a5a5) ******@100:driver 0 th (addr=047f, data=ffffffff) ******@100:driver 1 th (addr=04b9, data=5a5a5a5a) ******@100:driver 2 th (addr=06e0, data=00a5a5a5) ******@100:driver 3 th (addr=0489, data=5a5a5a5a) ******@100:driver 4 th (addr=005e, data=5a5a5a5a) ******@100:driver 5 th (addr=0567, data=5a5a5a5a) ******@100:driver 6 th (addr=05ca, data=ffffffff) ******@100:driver 7 th (addr=040d, data=ffffffff) ******@100:driver 8 th (addr=0490, data=ffffffff) ******@100:driver 9 th (addr=05fe, data=00a5a5a5)
32'ha5a5a5
即32'h00a5_a5a5
class driver
中的tr
并没有new
,这里我们就要再来深入理解一下new
的功效了;tr = new;
的方式实际上是系统随机产生的一个地址(内存空间实体)赋给了tr
,这里尽管没有使用new
但是我们使用了邮箱的get
表示从外部获取地址(内存空间实体)!
边栏推荐
猜你喜欢
CTFshow,信息搜集:web12
Niuke real problem programming - Day17
Ctfshow, information collection: web6
Summer safety is very important! Emergency safety education enters kindergarten
[deep learning] image hyperspectral experiment: srcnn/fsrcnn
Niuke real problem programming - day20
Ctfshow, information collection: web1
Configure mongodb database in window environment
Bye, Dachang! I'm going to the factory today
HW初级流量监控,到底该怎么做
随机推荐
Ctfshow, information collection: web6
CTFshow,信息搜集:web5
How to release NFT in batches in opensea (rinkeby test network)
【OBS】RTMPSockBuf_ Fill, remote host closed connection.
Nacos一致性协议 CP/AP/JRaft/Distro协议
Niuke real problem programming - Day10
leetcode 241. Different Ways to Add Parentheses 为运算表达式设计优先级(中等)
[deep learning] semantic segmentation experiment: UNET network /msrc2 dataset
@Introduction and three usages of controlleradvice
【Markdown语法高级】让你的博客更精彩(四:设置字体样式以及颜色对照表)
Protection strategy of server area based on Firewall
Notes HCIA
Window环境下配置Mongodb数据库
知否|两大风控最重要指标与客群好坏的关系分析
使用cpolar建立一个商业网站(2)
With 8 modules and 40 thinking models, you can break the shackles of thinking and meet the thinking needs of different stages and scenes of your work. Collect it quickly and learn it slowly
【数字IC验证快速入门】23、SystemVerilog项目实践之AHB-SRAMC(3)(AHB协议基本要点)
"Baidu Cup" CTF competition 2017 February, web:include
Promoted to P8 successfully in the first half of the year, and bought a villa!
buffer overflow protection