The UVM Primer -- Chapter 17 Interthread Communicaton

Chapter 17 Interthread Communication

上一章介绍了单线程内的各组件间的传输过程,本章介绍线程间的通信,以生产者-消费者为例:

module producer(output byte shared, input bit put_it, output bit get_it);
   initial     //线程1
     repeat(3) begin 
        $display("Sent %0d", ++shared);     //shared共享变量的改变源头
        get_it = ~get_it;                   //送至consumer的get_it信号,告诉consumer数据已经更新
        @(put_it);                          //来自consumer的put_it信号,表示producer可以生产下一个数据的信号
     end
endmodule : producer

module consumer(input byte shared,  output bit put_it, input bit get_it);
   initial     //线程2
      forever begin
         @(get_it);                   //来自producer的get_it信号,consumer可以接受数据的信号
         $display("Received: %0d", shared);   
         put_it = ~put_it;            //送至producer的put_it信号,告诉producer数据已经明接收        
      end
endmodule : consumer

module top;                           //在顶层将两个模块的ports连接起来
   byte shared;
   producer p (shared, put_it, get_it);
   consumer c (shared, put_it, get_it);
endmodule : top

17.1 使用UVM的Object完成线程间的通信

  • 上面的例子中,我们用过verilog的ports,将producer和consumer链接起来。systerm verilog中没有模块端口,但提供共享对象句柄的方式,可以传输线程间的信息比如semaphor和mailboxes。这样不同的验证人员写出的代码就不一致,UVM使用通用的方法解决这个问题,解决方案共包括两个部分:
    • Ports: 在uvm_component中例化,并在run_phase中与其他线程通信;使用put ports送数据,使用get ports收数据。
    • TLM FIFO:Transaction Level Modeling是包含一个put export和一个get export的类,可以传输任何类型的数据。
  • TLM FIFO深度为1,只能存储一个元素。这样的容量也许很鸡肋,但是在多线程通信中很有用。

17.1.1 在producer模块内使用put ports

上面的例子中,producer和consumer之间的通信需要三个信号的交互,包括数据shared,put_it 和 get_it,使用uvm_put_port之后,只需要传递一个参数,producer模块代码如下:

class producer extends uvm_component;
   `uvm_component_utils(producer);

   int shared;
   uvm_put_port #(int) put_port_h;                 //定义uvm_put_port,参数化类,需要指定传输数据类型

   function void build_phase(uvm_phase phase);
      put_port_h = new("put_port_h", this);        //在build phase中例化
   endfunction : build_phase
   
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction : new
   
   task run_phase(uvm_phase phase);           //不需要处理put_it和get_it信号,交给put port就好
      phase.raise_objection(this);
      repeat (3) begin
         put_port_h.put(++shared);            //在run phase中调用put port的put函数将数据送出
         $display("Sent %0d", shared);        //调用put后,数据进入FIFO,此时FIFO已满阻塞下次数据put,直到consumer将数据从FIFO中取出
      end
      phase.drop_objection(this);
   endtask : run_phase
endclass : producer

17.1.2 在consumer模块内使用get ports

  • 和producer相反,consumer是通过get port将数据从FIFO取出,排空FIFO的过程,consumer模块的代码如下:
class consumer extends uvm_component;
   `uvm_component_utils(consumer);

   uvm_get_port #(int) get_port_h;      //参数化的get port,需要指定传输数据类型,和put类型一致
   int shared;

   function void build_phase(uvm_phase phase);
      get_port_h = new("get_port_h", this);   //在build phase中例化get port
   endfunction : build_phase

   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction : new

   task run_phase(uvm_phase phase);          //在run phase中从FIFO取出数据
      forever begin
         get_port_h.get(shared);             //调用get port端口的get函数,将数据从FIFO中取出,释放producer处的阻塞
         $display("Received: %0d", shared);  //forever循环中,当FIFO为空,get函数会阻塞consumer
      end
   endtask : run_phase
endclass : consumer
  • 到目前为止,producer和consumer之间的运行状态为:
    producer填满FIFO,挂起等待 -> consumer清空FIFO,挂起等待 -> producer填满FIFO,挂起等待 …

17.1.3 在test模块内使用将port连接起来

目前,producer和consumer分别包含put port和get port,但是两者还没有连接起来,我们需要在test模块,或在env模块将两个port连接起来(使用TLM FIFO):

class communication_test extends uvm_test;
   `uvm_component_utils(communication_test)
   
   producer producer_h;
   consumer consumer_h;                   
   uvm_tlm_fifo #(int) fifo_h;             //除了声明producer和consumer,需要声明TLM FIFO
                                           //TLM FIFO是参数化的类,传递的参数类型必须和put get port一致
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction : new

   function void build_phase(uvm_phase phase);  //build phase top-down,所以先例化FIFO,在调用TLM FIFO的build phase
      producer_h = new("producer_h", this);
      consumer_h = new("consumer_h", this);
      fifo_h = new("fifo_h",this);              //build phase中例化TLM FIFO
   endfunction : build_phase

   function void connect_phase(uvm_phase phase); //build phase结束以后开始connect phase,bottom-up
      producer_h.put_port_h.connect(fifo_h.put_export);  //将FIFO的put_export传递给producer中的put port的connect函数(类似于analysis export传给analysis port的connect函数)
      consumer_h.get_port_h.connect(fifo_h.get_export);  //将FIFO的get_export传递给consumer中的get port的connect函数
   endfunction : connect_phase

endclass : communication_test

17.1.4 线程间的非阻塞通信

  • 如果两个线程之间通信不管时间延迟,即producer发了一个包之后,无论等待多长时间再发下一个包都可以的话,上面的通信机制永远不会有问题。如果producer在每过两个时钟沿,都需要发包,但是consumer获取数据报的速度不一致,有时候每一个时钟沿就收包,有时候需要等多个周期,这时候producer就会因为consumer而阻塞。
  • 为了解决这种场景,UVM提供非阻塞版本的put函数和get函数: try_put & try_get,这两个函数除了不阻塞之外,其他的和put&get完全一致。try函数返回一个单bit的数据,表示put&get是否成功。
  • 我们将non-blocking的机制加入测试平台,此时consumer中不再调用get函数,而是调用try_get函数:
   task run_phase(uvm_phase phase);          //在run phase中从FIFO取出数据
      forever begin
         if (get_port_h.try_get(shared))     //get port的非阻塞try_get函数
             $display("Received: %0d", shared); 
      end
   endtask : run_phase

17.1.5 加入put&get port后的验证环境框图

  • 使用正方形代表put/get port,仍然使用圆形作为put/get export,此时验证环境框图如下:
    在这里插入图片描述
  • 其中put port和get port之间为TLM FIFO,可以理解为数据的中转站。(在环境框图上不区分put/get和try_put/try_get函数)。通过uvm_put_ports和uvm_get_ports以及连接两种port的uvm_tlm_fifo,实现线程间的通信。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值