当前位置:网站首页>【数字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对共享资源进行分配,比如系统总线,在同一个时间点,只能由一个驱动器使用总线

在这里插入图片描述

在这里插入图片描述

注意:上述函数,如果不写参数,它是具有默认参数的!

3.2、使用new函数,创建semaphore buckets

在这里插入图片描述

  • 注意:sem_a = new[4];仅仅是声明了数组,每个信号量还没有指明钥匙数量,所以是无法put的!
  • 类创建(new)实体用的是小括号:sem = new(2);;数组创建(new)实体用的是方括号:sem_a = new[4];

3.3、信号量获取与释放以及try_getget释义

在这里插入图片描述

  • 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将数据包传递给DUTdriver也要做一些跟时序相关的工作!
    • 生成器和驱动器必须是异步操作(邮箱是个异步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'ha5a5a532'h00a5_a5a5
  • class driver中的tr并没有new,这里我们就要再来深入理解一下new的功效了;tr = new;的方式实际上是系统随机产生的一个地址(内存空间实体)赋给了tr,这里尽管没有使用new但是我们使用了邮箱的get表示从外部获取地址(内存空间实体)!
原网站

版权声明
本文为[luoganttcc]所创,转载请带上原文链接,感谢
https://luogantt.blog.csdn.net/article/details/125645153