当前位置:网站首页>【数字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) : 15X.3.2、基本整型传递(邮箱同步)Demo
thread_communication.svmailbox 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) : 15X.3.3、传递 class 数据
thread_communication.svclass 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_a5a5class driver中的tr并没有new,这里我们就要再来深入理解一下new的功效了;tr = new;的方式实际上是系统随机产生的一个地址(内存空间实体)赋给了tr,这里尽管没有使用new但是我们使用了邮箱的get表示从外部获取地址(内存空间实体)!
边栏推荐
- MySQL installation configuration 2021 in Windows Environment
- [follow Jiangke University STM32] stm32f103c8t6_ PWM controlled DC motor_ code
- 拜拜了,大厂!今天我就要去厂里
- Change win10 Screensaver
- Yyds dry goods inventory # solve the real problem of famous enterprises: cross line
- Qu'est - ce qu'une violation de données
- 【数字IC验证快速入门】22、SystemVerilog项目实践之AHB-SRAMC(2)(AMBA总线介绍)
- There is a cow, which gives birth to a heifer at the beginning of each year. Each heifer has a heifer at the beginning of each year since the fourth year. Please program how many cows are there in the
- Ctfshow, information collection: web5
- 避坑:Sql中 in 和not in中有null值的情况说明
猜你喜欢

Typescript release 4.8 beta

Briefly describe the working principle of kept

2022全开源企业发卡网修复短网址等BUG_2022企业级多商户发卡平台源码

拜拜了,大厂!今天我就要去厂里

CTFshow,信息搜集:web1

JSON parsing instance (QT including source code)

CTFshow,信息搜集:web8

Unity's ASE realizes cartoon flame

【服务器数据恢复】戴尔某型号服务器raid故障的数据恢复案例

【数字IC验证快速入门】25、SystemVerilog项目实践之AHB-SRAMC(5)(AHB 重点回顾,要点提炼)
随机推荐
Niuke real problem programming - day16
@ComponentScan
Mathematical modeling -- what is mathematical modeling
【数字IC验证快速入门】26、SystemVerilog项目实践之AHB-SRAMC(6)(APB协议基本要点)
[quick start of Digital IC Verification] 29. Ahb-sramc (9) (ahb-sramc svtb overview) of SystemVerilog project practice
Win10 or win11 taskbar, automatically hidden and transparent
There is a cow, which gives birth to a heifer at the beginning of each year. Each heifer has a heifer at the beginning of each year since the fourth year. Please program how many cows are there in the
Excerpted words
Niuke real problem programming - day15
如何在opensea批量发布NFT(Rinkeby测试网)
【数字IC验证快速入门】24、SystemVerilog项目实践之AHB-SRAMC(4)(AHB继续深入)
Read PG in data warehouse in one article_ stat
MySQL bit type resolution
什么是pv和uv? pv、uv
一个需求温习到的所有知识,h5的表单被键盘遮挡,事件代理,事件委托
Use cpolar to build a business website (2)
MySQL installation configuration 2021 in Windows Environment
Niuke real problem programming - day14
【目标检测】YOLOv5跑通VOC2007数据集
Stm32f103c8t6 PWM drive steering gear (sg90)