当前位置:网站首页>【数字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
表示从外部获取地址(内存空间实体)!
边栏推荐
- 【兰州大学】考研初试复试资料分享
- Comparable and comparator of sorting
- Lidar knowledge drops
- 暑期安全很重要!应急安全教育走进幼儿园
- What are the safest securities trading apps
- 【服务器数据恢复】某品牌StorageWorks服务器raid数据恢复案例
- Integer learning
- 【数字IC验证快速入门】26、SystemVerilog项目实践之AHB-SRAMC(6)(APB协议基本要点)
- Wechat applet 01
- 【数字IC验证快速入门】22、SystemVerilog项目实践之AHB-SRAMC(2)(AMBA总线介绍)
猜你喜欢
Unity's ASE realizes cartoon flame
Configure mongodb database in window environment
asp. Netnba information management system VS development SQLSERVER database web structure c programming computer web page source code project detailed design
如何在opensea批量发布NFT(Rinkeby测试网)
CTFshow,信息搜集:web8
Apache multiple component vulnerability disclosure (cve-2022-32533/cve-2022-33980/cve-2021-37839)
CTFshow,信息搜集:web3
Niuke real problem programming - Day12
Ctfshow, information collection: web12
什么是数据泄露
随机推荐
Stream learning notes
[make a boat diary] [shapr3d STL format to gcode]
避坑:Sql中 in 和not in中有null值的情况说明
Configure mongodb database in window environment
Unity之ASE实现全屏风沙效果
Protection strategy of server area based on Firewall
[target detection] yolov5 Runtong voc2007 data set
MongoD管理数据库的方法介绍
Discussion on CPU and chiplet Technology
Ctfshow, information collection: web7
使用cpolar建立一个商业网站(2)
MySQL bit type resolution
Nacos一致性协议 CP/AP/JRaft/Distro协议
【跟着江科大学Stm32】STM32F103C8T6_PWM控制直流电机_代码
Yyds dry goods inventory # solve the real problem of famous enterprises: cross line
【深度学习】图像超分实验:SRCNN/FSRCNN
@Introduction and three usages of controlleradvice
数学建模——什么是数学建模
【数字IC验证快速入门】23、SystemVerilog项目实践之AHB-SRAMC(3)(AHB协议基本要点)
Compile advanced notes