SystemVerilog学习笔记(九)


本文思维导图如下:

1. 线程控制

1.1 并行线程

Verilog中与顺序线程begin…end相对的是并行线程fork…join。

1.1.1创建线程

SV引入了两种新新的创建线程的方法,fork…join_nonefork…join_any

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

1.1.2子程序等待/停止

需要注意的是,fork…join_any和fork. . .join_none继续执行后,其一些未完成的子程序仍将在后台运行。如果要等待这些子程序全部完成,或者停止这些子程序,可以使用wait fork或者disable fork
(1)wait fork

**(2)disable fork**
## 1.2 时序控制 SV可以通过延迟控制或者事件等待来对过程块完成时序控制。 ### 1.2.1 延迟控制 延迟控制即通过#来完成。 >#10 rega = regb;

1.2.2 事件控制

事件(event)控制即通过@来完成。

@r rega = regb ;
@(posedge clock) rega = regb;

1.2.3 事件等待

wait语句也可以与事件或者表达式结合来完成。

real AOR[] ;
initial wait(AOR.size() > 0) …. ;

2. 进程间同步和通信

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

2.1 事件

  • 声明
    可以通过event来声明一个命名event变量,并且去触发它。这个命名event可以用来控制进程的执行。
  • 触发
    可以通过->来触发事件。
    其它等待该事件的进程可以通过@操作符或者wait()来检查event触发状态来完成。
  • 等待
    wait__order可以使得进程保持等待,直到在参数列表中的事件event按照顺序从左到右依次完成。如果参数列表中的事件被触发但是没有按照要求的顺序,那么会使得等待操作失败。

    wait_order ( a, b, c);

    wait order( a, b, c ) else $display ( “Error: eventsout of order” );

    bit success ;
    wait order( a, b, c ) success = 1; else success = 0 ;

2.2 旗语

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

### 2.2.1用法 * 创建旗语,并为其分配钥匙的方式如下: >semaphore sm; >sm = new ();
  • 创建一个具有固定钥匙数量的旗语:

    new (N = 0)

  • 从旗语那里获取一个或多个钥匙(阻塞型) :

    get (N = 1)

  • 将一个或多个钥匙返回到旗语中:

    put (N = 1)

  • 尝试获取一个或多个钥匙而不会阻塞(非阻塞型) :

    try_get (N = 1)

2.2.2基本操作

senmaphore: :new()

new ())的原型如下:

function new (int keyCount = 0) ;

keyCount指定最初分配给旗语的钥匙数目。keyCount的默认值为0。当更多钥匙放入旗语时,钥匙数目可以超出初始时的keyCount数量,而不是删除。

semaphore: :put()

put ()方法用于将钥匙数量返回给旗语。
put ()的原型如下:

function void put (int keyCount = 1) ;

keycount指定返回到旗语的钥匙数量。默认值为1。调用semaphore.put ()函数时,指定数量的钥匙将返回到旗语。如果其它进程已经在等待旗语,则该进程应在有足够数量钥匙的情况下返回。

semaphore: :get()

get ()方法用于从旗语中获取指定数量的钥匙。get ()的原型如下:

task get (int keycount = 1) ;

keyCount指定从旗语获取所需的钥匙数,默认值为1。如果指定数量的钥匙可用,则该方法返回并继续执行。如果指定数量的钥匙不足,进程将阻塞,直到钥匙数目充足。旗语的等待队列是先进先出(FIFO),即先排队等待旗语的将优先得到钥匙。

semaphore : :try_get()

try get ()方法用于从信号量中获取指定数量的钥匙,但不会被阻塞。try_get ()的原型如下:

function int try_get (int keyCount = 1) ;

keyCount指定从旗语处获取所需的钥匙数目,默认值为1。如果指定数量的钥匙可用,则该方法返回正数并继续执行。如果指定数量的钥匙不足,则该方法返回0。

2.2.3实例

## 2.3 信箱 信箱mailbox可以使得进程之间的信息得以交换,数据可以由一个进程写入信箱,再由另外一个进程获得。
信箱在创建时可以限制其容量,或者不限制。当信箱容量写满时,后续再写入的动作会被挂起,直到信箱的数据从中读取,使得信箱有空间以后才可以继续写入。 不限制容量的信箱则不会挂起写入信箱的动作。 ### 2.3.1 内建方法 #### new() new()用于创建信箱。可以在创建信箱的时候限定或者不限定其大小默认情况下,如果不传入参数,bound默认值为0,表示不限定信箱大小,如果传入的数值大于0,那么表示信箱的最大容量。bound应为正数,如果未负数的话,系统会提示警告和出现无法预期的行为。 >function new (int bound = 0) ;

put()

put()会将信息按照FIFO的顺序写入到信箱中,如果信箱此时已满,则put()任务会挂起,直到信箱有新的空间可以容纳消息。

try_put()

try put()试着写入信箱但不会阻塞。try put()也会按照FIFO顺序写入信箱,不会发生阻塞。如果信箱已满,则写入失败,返回0;如果信箱未满,则写入成功,返回1。

get()/peek()

获取信息: get()同时会取出数据,peek()不会取出数据。get()会将信息从信箱中取出,如果信箱此时为空,则get()任务会挂起,直到信箱中有消息可以读取,任务才会返回。该方法会将读取到的消息从信箱中移除。

try_get()/try_peek()

试着从信箱取出数据但不会阻塞。try get()也会将信息从信箱中取出,只是该函数不会发生阻塞。如果信箱为空,则读取失败,返回0;如果信箱不为空,则读取成功,返回1。该方法也会将读取到的消息从信箱中移除。

num()

num()会返回信箱目前的消息数目。可以结合num()与get()或者put(),防止get()/put()方法在信箱为空或者为满的时候被阻塞。

2.3.2 实例

mailbox #(int) mb;//声明mailbox句柄,并且指明该mailbox中只能存放int类型的数据

2.4虚方法

给pringtB添加关键词virtual,P1扩大查找范围,扩大到子类对象里面。如果子类里面有同名的方法,若有,调用子类里面同名的方法(以子类的实现优先)。
子类不添加virtual无影响,父类添加即可。

# 3. 类型转换 ## 3.1 静态转换 静态转换操作符不对转换值进行检查。 转换时指定目标类型,并在要转换的表达式前加上单引号即可。Verilog对整数和实数类型,或者不同位宽的向量之间进行隐式转换。 >int i; >real r; >i = int ' (10.0 - 0.1); //cast is optional >r= real'(42);// cast is optional ## 3.2 动态转换 我们总是可以将子类的句柄赋值给父类的句柄。但是在我们将父类的句柄赋值给子类的句柄时,编译将会报错。 $cast()系统函数可以将父类句柄转换为子类句柄,只要该父类句柄指向的是一个子类的对象。 >function int $cast( singular dest_var, singularsource_exp ) ;

或者

task $cast( singular dest_var, singular source_exp );

4. 问答题

1. 用bit key =0来实现的类似旗语(semaphore)的功能,来保护对共享资源的访问,是否有不安全的地方?示例代码如下(包含自然语言表征)。如果有不安全的地方,请用文字表述,并且对以下代码进行修改,并且进行测试、编译和仿真打印消息。

bit key = 1;//创建钥匙
task get_key();
wait(key > 0);
key = 0;//获得钥匙
…//省略对于共享资源的处理
key = 1; /还回钥匙
endtask

:当多个task并行执行时,只要key=1,满足wait(key>0)即可执行,无法实现资源同时只能被指定个数的进程访问的目的。

2. SV的原生旗语semaphore在例化以后,并不能从其获取哪一个对象(字符串stringID)获取了钥匙,那么请你将semaphore封装到一个类中,重新定义get()/put(方法,并且通过最后的一个打印函数来得知当前获取钥匙的string ID是谁?示例代码大致如下,请完成提示需要完成的地方,对你的代码进行编译和仿真,展示你的代码和仿真的打印消息。
:sv代码如下

module tb;

class smart_key;
    semaphore s;//创建旗语
    string holder;

    function new(N= 1);
        s = new(N);//分配1把钥匙
    endfunction
    
    task get(int N= 1,string str);//请添加需要的参数,并且实现该方法
        s.get();//拿到钥匙
        holder = str;
        #1ns;
        //$display("%0t: %s get the key",$time,str);
    endtask
    task put(int N= 1,string str);//请添加需要的参数,并且实现该方法
        s.put();//放回钥匙
        #1ns;
        //$display("%0t: %s put the key",$time,str);
    endtask
    function void print_key_holders();//请事先该方法,使其能够打印出当前获得钥匙的ID
        $display("%0t: now %s hold the key",$time,holder);
    endfunction
        
endclass: smart_key
    
initial begin
    smart_key skey = new();
    
    string string1 = "A";
    string string2 = "B";
    string string3 = "C";
    
    fork 
        begin 
            skey.get(1,string1);
            skey.print_key_holders();
            skey.put(1,string1);
        end
        begin 
            skey.get(1,string2);
            skey.print_key_holders();
            skey.put(1,string2);
        end
        begin 
            skey.get(1,string3);
            skey.print_key_holders();
            skey.put(1,string3);
        end
    join
end
    
endmodule: tb

仿真结果:

1000: now A hold the key
2000: now B hold the key
3000: now C hold the key

3. 我们在使用信箱mailbox时,如何可以限定其容量为8,并且只能存放string类型呢?那么,是否类似的方法也可以限定一个存放字符串的队列其最大容量为8呢?
:声明方法如下

mailbox #(int) mb;
mb = new(8);

队列空间是动态的,不能限定其最大容量。

写的不错,可以参考SV学习笔记—线程之间的通信(事件event、信箱mailbox、旗语semaphore)_Verification_White的博客-CSDN博客_sv event


文章作者: DPH
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 DPH !
  目录