SystemVerilog学习 (10)——线程控制

一、概述

在实际硬件中,时序逻辑通过时钟沿来激活,组合逻辑的输出则随着输人的变化而变化。所有这些并发的活动在Verilog 的寄存器传输级上是通过initial和 always块语句、实例化和连续赋值语句来模拟的。为了模拟和检验这些语句块,测试平台使用许多并发执行的线程。在测试平台的环境里,大多数语句块被模拟成事务处理器,并运行在各自的线程里。
SystemVerilog 的调度器就像一个交通警察,总是不停地选择下一个要运行的线程。
每个线程总是会跟相邻的线程通信。在下图中,发生器把激励传递给代理。环境类需要知道发生器什么时候完成任务,以便及时终止测试平台中还在运行的线程。这个过程需要借助线程间的通信(IPC)来完成。常见的线程间通信有标准的Verilog事件、事件控制、wait语句、SystemVerilog信箱和旗语等。

二、线程的使用

虽然所有的线程结构都可以用在模块和程序块中,但实际上测试平台隶属于程序块。结果是,我们的代码总是以initial块启动,从时刻0开始执行。虽然 always 块不能被放在程序块中,但是,通过在initial块内引入forever循环便可轻松地解决这个问题。
标准的 Verilog对语句有两种分组方式------使用begin...end或fork...join。begin. ..end中的语句以顺序方式执行,而fork...join中的语句则以并发方式执行。后者的不足是必须等fork...join内的所有语句都执行完后才能继续块内后续的处理。因此,在 Verilog的测试平台中很少用到它。
SystemVerilog引人了两种新的创建线程的方法------使用fork...join_none 和fork...join_any语句,如图下图所示:

  • fork...join需要所有并行的线程都结束以后才会继续执行
  • fork...join_any则会等到任何一个线程结束以后就继续执行
  • fork...join_none则不会等待其子线程而继续执行

三、进程间同步与通信

测试平台中的所有线程都需要同步并交换数据。 一个线程等待另外一个,例如验证环境需要等待所有激励结束、比较结束才可以结束仿真。 比如监测器需要将监测到的数据发送至比较器,比较器又需要从不同的缓存获取数据进行比较。

1、事件event

Verilog事件可以实现线程的同步。就像在打电话时一个人等待另一个人的呼叫,在Verilog中,一个线程总是要等待一个带@操作符的事件。这个操作符是边沿敏感的,所以它总是阻塞着,等待事件的变化。其他的线程可以通过->操作符来触发事件,解除对第一个线程的阻塞。

System Verilog 从几个方面对 Verilog事件做了增强。事件现在成为了同步对象的句柄,可以传递给子程序。这个特点允许你在对象间共享事件,而不用把事件定义成全局的。最常见的方式是把事件传递到一个对象的构造器中。

在Verilog中,当一个线程在一个事件上发生阻塞的同时,正好另一个线程触发了这个事件,则竞争的可能性便出现了。如果触发线程先于阻塞线程执行,则触发无效。SystemVerilog引A triggered ()函数,可用于查询某个事件是否已被触发,包括在当前时刻。线程可以等待这个函数的结果,而不用在@操作符上阻塞。

可以通过event来声明一个命名event变量,并且去触发它,这个命名event可以用来控制进程的执行;可以通过->来触发事件,其它等待该事件的进程可以通过@操作符或者wait()来检查 event触发状态来完成。

复制代码
event done, blast; // declare two new events 
event done_too = done; // done_too as alias to done 
task trigger( event ev ); 
    -> ev; 
endtask 
... 

fork 
    @ done_too; // wait for done through done_too 
    #1 trigger( done ); // trigger done 
join 

fork 
    -> blast; 
    wait ( blast.triggered ); 
join

2、wait_order()

wait_order可以使得进程保持等待,直到在参数列表中的事件event按照顺序从左到右依次完成。 如果参数列表中的事件被触发但是没有按照要求的顺序,那么会使得等待操作失败

3、旗语(semaphore)

使用旗语可以实现对同一资源的访问控制。想象一下你和你爱人共享一辆汽车的情形。显然,每次只能有一个人可以开车。为应对这种情况﹐你们可以约定谁持有钥匙谁开车。当你用完车以后,你会让出车子以便对方使用。车钥匙就是旗语,它确保了只有一个人可以使用汽车。在操作系统的术语里,这就是大家所熟知的"互斥访问",所以旗语可被视为一个互斥体,用于实现对同一资源的访问控制。

当测试平台中存在一个资源,如一条总线,对应着多个请求方,而实际物理设计中又只允许单一驱动时,便可使用旗语。在SystemVerilog 中,一个线程如果请求"钥匙"而得不到,则会一直阻塞,多个阻塞的线程会以先进先出(FIFO)的方式进行排队。

旗语从概念上讲,是一个容器, 在创建旗语的时候,会为其分配固定的钥匙数量, 使用旗语的进程必须先获得其钥匙,才可以继续执行。旗语的钥匙数量可以有多个,等待旗语钥匙的进程也可同时有多个,旗语通常用于互斥,对共享资源的访问控制,以及基本的同步。

  • 创建旗语,并为其分配钥匙的方式如下: semaphore sm; sm = new();
  • 创建一个具有固定钥匙数量的旗语:new(N = 0)
  • 从旗语那里获取一个或多个钥匙(阻塞型):get(N = 1)
  • 将一个或多个钥匙返回到旗语中:put(N = 1)
  • 尝试获取一个或多个钥匙而不会阻塞(非阻塞型):try_get(N = 1)

4、信箱(mailbox)

如何在两个线程之间传递信息呢?考虑发生器需要创建很多事务并传递给驱动器的情况。你可能会认为仅仅使用发生器线程去调用驱动器中的任务便可以了。但如果这样做,发生器需要知道到达驱动器任务的层次化路径,这会降低代码的可重用性。此外,这种代码风格还会迫使发生器与驱动器以同一速率运行,在一个发生器需要控制多个驱动器的情况下会引发同步问题。

解决的办法是使用SystemVerilog 中的信箱。从硬件角度出发,对信箱的最简单的理解是把它看成一个具有源端和收端的FIFO。源端把数据放进信箱,收端则从信箱中获取数据。信箱可以有容量上的限制,也可以没有。当源端线程试图向一个容量固定并且已经饱和的信箱里放入数值时,会发生阻塞直到信箱里的数据被移走。同样地,如果收端线程试图从一个空信箱里移走数据,它也会被阻塞直到有数据放入信箱里。下图展示了一个连接了发生器和驱动器的信箱。

  • 创建信箱:new()
  • 将信息写入信箱:put()
  • 试着写入信箱但不会阻塞:try_put()
  • 获取信息:get()同时会取出数据,peek()不会取出数据
  • 试着从信箱取出数据但不会阻塞:try_get()/try_peek()
  • 获取信箱信息的数目:num()

四、总结

你的设计可以用很多并发运行的独立块来建模,所以测试平台也必须能够产生很多激励流并检验并发线程的反应。所有这些都被组织在一个层次化的测试平台中,并在顶层环境里得到统一。SystemVerilog在标准的fork...join 之外,引人了诸如fork...join_none和fork...join_any这些用于动态创建线程的功能强大的结构。线程间可以使用事件,旗语、信箱,以及经典的@事件控制和 wait语句来实现通信和同步。最后,disable命令可以用来中止线程。

这些线程和相关的控制结构对OOP(面向对象编程)的动态特性形成了很好的补充。由于对象可以被创建和删除,所以它们可以运行在独立的线程里,这使得你能够构筑强大而灵活的测试平台环境。

相关推荐
FPGA_小田老师1 小时前
FPGA基础知识(十):深入理解建立时间与保持时间违例
fpga开发·时序约束·建立时间违例·保持时间违例
贝塔实验室3 小时前
LDPC 码的度分布
线性代数·算法·数学建模·fpga开发·硬件工程·信息与通信·信号处理
ALINX技术博客4 小时前
算力跃升!解析可嵌入整机的 6U VPX 异构高性能射频信号处理平台 AXW23
射频工程·fpga·基带工程
javajenius19 小时前
Quartus II下载安装教程Quartus II 18保姆级安装步骤(附安装包)
其他·fpga开发
颜子鱼20 小时前
FPGA中复位信号的省略
fpga开发
cycf21 小时前
面向模块的综合技术之过约束(十)
fpga开发
颜子鱼1 天前
FPGA-状态机架构
fpga开发
颜子鱼1 天前
FPGA-状态机
fpga开发
GateWorld1 天前
FPGA设计中的“幽灵信号:一条走线,两种命运——浅析路径延迟导致的逻辑错误
fpga开发
简简单单做算法1 天前
【第1章】基于FPGA的图像形态学处理学习教程——目录
fpga·图像形态学处理·膨胀·腐蚀·开运算·闭运算