1、 SystemVerilog 旗语Semaphore

1.1 什么是旗语?为什么需要旗语?

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

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

1.2 语法

semaphore [identifier_name];

        旗语是一种对象,必须使用new()函数来进行实例化。使用new方法可以创建一个带单个或多个钥匙的旗语:

semaphore sem;      //创建一个旗语
initial begin
    sem = new(1);   //分配一个钥匙
    ...
end

1.3 旗语的方法

        semaphore是一个内建的类,它提供了下列方法:
 

方法 描述
function new (int KeyCount=0);

KeyCount指定了最初被分配到semaphore桶中的键值的数目。当进入桶中的键值比移出桶中的键值的数目多的时候,桶中键值的数目可以超过KeyCount。KeyCount的缺省值为0。

new()函数返回semaphore的句柄,如果没有产生semaphore则返回null。

function void put (int KeyCount=1);

keyCount指定了返回到semaphore中的键值的数目,它的缺省值为1。

当调用semaphore.put()任务的时候,指定数目的键值被返回到semaphore中。如果一个进程已经被挂起来等待一个键值的话,当返回了足够的键值后这个进程应该继续执行。

task get (int KeyCount=1);

keyCount指定了需要从semaphore中获得的键值的数目,它的缺省值为1。

如果指定的键值数目有效,那么方法返回并且进程会继续执行;如果指定的键值数目无效,进程会阻塞直到键值变成有效。

semaphore等待队列是先进先出(FIFO)。这种方式不能保证进程到达队列的顺序,semaphore仅仅能够保持进程到达的顺序。

function int try_get (int KeyCount=1);

keyCount指定了需要从semaphore中获得的键值的数目,它的缺省值为1。

如果指定的键值数目有效,那么try_get()方法返回1并且进程会继续执行;如果指定的键值数目无效,那么try_get()方法返回0。

1.4 带多个钥匙的旗语

        使用旗语时有两个地方需要小心:

        你返回的钥匙可以比你取出来的多。你可能突然之间有两把钥匙而实际上只有一辆汽车。
当你的测试程序需要获取和返回多个钥匙时,务必谨慎。假设你剩下一把钥匙,有一个线程请求两把而被阻塞,这时第二个线程出现,它只请求一把,那么在SystemVerilog中,第二个请求get(1)会悄悄地排到第一个请求get(2)的前面,先进先出的规则在这里会被忽略掉。

2 SystemVerilog 信箱Mailbox

2.1 什么是信箱?

        信箱是一种允许不同的线程之间互相交换数据的方式。它类似于一个真实的邮箱,信件可以被放入其中,之后也可以被取出。

2.2 为什么需要信箱?

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

        把发生器和驱动器想象成具备自治能力的事务处理器对象,它们通过信道交换数据。每个对象从它的上游对象中得到事务(如果对象本身是发生器,则创建事务),进行一些处理,然后把它们传递给下游对象。这里的信道必须允许驱动器和接收器异步操作。你可能倾向于仅仅使用一个共享的数组或队列,但这样一来编写实现线程间读写和阻塞的代码却会很困难。

        解决方法是使用SystemVerilog中的信箱。

2.3 信箱的理解

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

信箱是一种对象,必须调用new()函数来进行实例化。

2.4 SystemVerilog信箱的方法与函数

函数 描述
function new (int bound=0); 返回一个信箱句柄,bound>0表明信箱的容量。如果bound是0或者没有指定,则表明信箱是无限大的,可以容纳任意多的条目。
function int num (); 返回目前信箱中的条目数。
task put (singular meassage); 阻塞方法,将数据以FIFO的顺序存入到信箱,这里的数据指的单个的值(例:可以是整数或任意宽度的logic。可以放句柄但不能放对象。)。
function int try_put (singular meassage); 非阻塞方法,如果信箱没满则存储数据,如果成功则返回一个正整数,否则返回0。
task get (ref singular meaasge); 阻塞方法,从信箱中取回一个数据,如果信箱为空,则阻塞。
function int try_get (ref singular message); 非阻塞方法,从信箱中取回一个数据,信箱为空则返回0。
task peek (ref singular message); 阻塞方法,从一个mailbox中拷贝一个消息但不会将其从队列中删除。如果mailbox是空的,那么当前的进程会阻塞直到一个消息被放置到mailbox中。如果在消息变量和mailbox中的消息间存在类型不匹配,那么会产生一个运行时错误。
function int try_peek (ref singular message); 非阻塞方法,尝试从信箱中拷贝一个数据,而不将它从信箱队列中取出,如果信箱为空,则返回0。

2.5 两种信箱 Generic Mailbox vs Parameterized Mailbox

        Generic Mailbox可以接受所有数据类型
        缺省情况下,信箱没有类型(Generic Mailbox),所以允许在其中放入任何混合类型的数据。但不要这样做!务必在一个信箱中只放一种类型的数据。

mailbox mbx;
  • Parameterized Mailbox只能接受一种特定的数据类型

        尽管无数据类型约束的信箱具有很好的收发数据特性,它可能导致仿真过程中的数据类型失配并造成结果错误。Parameterized Mailbox约束了信箱接受和发送特定类型的数据。

mailbox #(string) mbx1;   // fixed-type string
mailbox #(byte)   mbx2;   // fixed-type byte

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐