我是靠谱客的博主 危机冰淇淋,最近开发中收集的这篇文章主要介绍从零开始,搭建一个简单的UVM验证平台(二)interface & virtual interfaceconfig_db机制阶段代码总结添加transaction,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

前言:

        这篇系列将从0开始搭建一个UVM验证平台,来帮助一些学习了SV和UVM知识,但对搭建完整的验证环境没有概念的朋友。

UVM前置基础:

1.UVM基础-factory机制、phase机制

2.UVM基础-组件(driver、monitor、agent...)

3.UVM基础-TLM通信机制(一)

4.UVM基础-TLM通信机制(二)

...还在更新

从零搭建一个UVM验证平台:

从零开始,搭建一个简单的UVM验证平台(一)

从零开始,搭建一个简单的UVM验证平台(二)

从零开始,搭建一个简单的UVM验证平台(三)

从零开始,搭建一个简单的UVM验证平台(四)

...还在更新


目录

interface & virtual interface

config_db机制

阶段代码总结

dut.sv

driver.sv

my_if.sv

top_tb.sv

添加transaction

transaction定义

基于transaction的driver


        在上个上个文章中,已经搭建了一个只有driver的验证平台,涉及到了类的继承与派生、factory工厂机制、phase机制、objection机制。下面我们要对之前的平台进行进一步完善。

interface & virtual interface

        在前面的代码中,driver的等待时钟事件@(posedge top_tb.clk)以及DUT中输入端口赋值(top_tb.data_i_valid <= 1'b1)都是使用的绝对路径。假如clk信号的层次从top_tb.clk变成了top_tb.clk_inst.clk,那么就需要对原来的代码进行大量修改。

        避免使用绝对路径的方式就是interface。在SystemVerilog中使用interface来链接验证平台与DUT的端口,interface的定义如下:

interface my_if(input clk, input rstn);
  logic [7:0] data;
  logic valid;
endinterface

定义了interface之后,在top_tb中例化DUT时,就可以直接使用:

my_if input_if(clk,rstn);
my_if output_if(clk,rstn);

dut my_dut(
  .clk            (clk)               ,
  .data_i         (input_if.data_if)  ,
  .data_o         (output_if.data_if) ,
  .data_i_valid   (input_if.valid)    ,
  .data_o_valid   (output_if.valid)
);

        在module中可以声明interface,但是如果要在driver中使用interface因为driver是一个类,所以不能直接声明interface(语法报错),在类中使用的是virtual interface

class my_driver extends uvm_driver;
    virtual my_if vif;

        因为driver类定义了interface,所以我们还需要将my_driver类中的main_phase任务中的data和valid信号用interface中的信号,修改后的含有interface的driver.sv为:

`include "uvm_macros.svh"

import uvm_pkg::*;

class my_driver extends uvm_driver;
  `uvm_component_utils(my_driver)  //注册
  virtual my_if vif;
  function new(string name = "my_driver", uvm_component parent = null);
    super.new(name, parent);
    `uvm_info("my_driver", "new is called", UVM_LOW)
  endfunction
  extern virtual task main_phase(uvm_phase phase);
endclass

task my_driver::main_phase(uvm_phase phase);
  phase.raise_objection(this);
  `uvm_info("my_driver", "main phase is called", UVM_LOW);
  vif.data_if       <= 8'd0;
  vif.valid <= 1'b0;
  while(!vif.rstn)
    @(posedge vif.clk);
  for(int i = 0; i < 256; i = i+1)begin
    @(posedge vif.clk)
    vif.data_if <= $urandom_range(0,255);
    vif.valid <= 1'b1;
    `uvm_info("my_driver", "data is drived", UVM_LOW) 
  end
  @(posedge vif.clk);
  vif.valid <= 1'b0;
  phase.drop_objection(this);
endtask

 

        现在my_drive中的绝对路径就消除了,提高了代码的复用性。但是目前还存在一个问题:如何将top_tb中的input_if和my_driver中的vif对应起来呢?

config_db机制

        在top_tb里,我们生成激励是用driver类来生成,driver生成激励后送到driver中的virtual interface中的vif.data_if,如何把driver中的vif.data_if赋值给top_tb中的input_if.data_if呢?如果直接赋值:

input_if.data_if = top_tb.driver.vif.data_if;

        会直接报错,因为driver是UVM通过run_test语句实例化了一个脱离top_tb层次结构的实例,建立了一个新的结构。对于这种脱离top_tb层次结构,又希望在top_tb中对其进行某些操作的实例,需要用到UVM的config_db机制。

        config_db机制分为set和get两个操作

在top_tb中执行set操作:

initial begin
  uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end

 在my_driver中执行get操作:

virtual function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  `uvm_info("my_driver", "build phase is called", UVM_LOW);
  if(`uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
    `uvm_fatal("my_driver", "virtual interface must be set for vif")
    //若执行uvm_fatal,则打印完第二个参数的信息后,会自动调用finish函数来终止仿真
endfunction

        这里引用了build_phase,与main_phase一样,是UVM内建的一个phase,当UVM启动后,会自动执行build_phase。build_phase在new函数之后,main_phase之前执行

        build_phase主要的作用就是通过config_db的set和get操作来做一些数据的配置和传递,以及实例化成员变量等。值得注意的是,super.build_phase这条语句是必须的,在父类uvm_build_phase 中执行了一些必要的操作,必须显式地调用并执行它。

build_phase和main_phase的区别

        build_phase与main_phase的不同点在于,build_phase是一个function而main_phase是一个task。我们知道task和function的最大区别就在于function是没有延时的,即不消耗仿真时间,在仿真开始的瞬间就执行了;而task类型的main_phase中则可以添加一些带有延时的操作,譬如@(posedge clk)、wait等操作。

        在build_phase中出现的uvm_fatal宏,这个宏和uvm_info的功能是差不多的,都是打印一些信息,它们的区别在于:uvm_fatal在打印第二个参数所示的信息后,会直接调用verilog 的finish函数来终止仿真uvm_fatal的出现,表示验证平台出现了重大问题而无法继续下去,必须停止仿真并做相应的检查,只要是uvm_fatal打印的信息,就一定是非常关键的。

uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if)
uvm_config_db#(virtual my_if)::get(this, ""            , "vif", vif)

        config_db的set和get都有四个参数,在set/get之前,有一个#(virtual my_if),这里跟的是我们需要传输的数据类型,因为我们要传输的是一个接口类型,所以填virtual my_if,如果要传输一个整型则写config_db#(int)。

        set和get的第一和第二个参数后面再说,先讲第三第四个参数。set的第二个参数是路径索引,索引到操作的目标所在的实例名称;set和get的第三个参数必须完全一致(可以为变量名);set的第四个参数表示要把哪个interface传递给my_driver;get的第四个参数表示要把接收到的interface传递给my_driver的哪个成员变量。

阶段代码总结

        到此为止,我们已经写了四个模块,分别是:driver.sv、my_if.sv、top_tb.sv和dut.sv,代码分别为:

dut.sv

module dut(
  input             clk           , 
  input             rstn          ,
  input      [7:0]  data_i        ,
  input             data_i_valid  ,
  output reg [7:0]  data_o        ,
  output reg        data_o_valid
);

always @(posedge clk)begin
  if(!rstn)begin
    data_o       <= 8'd0;
    data_o_valid <= 1'b0;
  end
  else begin
    data_o       <= data_i;
    data_o_valid <= data_i_valid;
  end
end
endmodule 

driver.sv

`include "uvm_macros.svh"

import uvm_pkg::*;

class my_driver extends uvm_driver;
  `uvm_component_utils(my_driver)  //注册
  virtual my_if vif;
  
  virtual function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  `uvm_info("my_driver", "build phase is called", UVM_LOW);
  if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
    `uvm_fatal("my_driver", "virtual interface must be set for vif");
endfunction

  function new(string name = "my_driver", uvm_component parent = null);
    super.new(name, parent);
    `uvm_info("my_driver", "new is called", UVM_LOW)
  endfunction
  extern virtual task main_phase(uvm_phase phase);
endclass

task my_driver::main_phase(uvm_phase phase);
  phase.raise_objection(this);
  `uvm_info("my_driver", "main phase is called", UVM_LOW);
  vif.data_if       <= 8'd0;
  vif.valid <= 1'b0;
  while(!vif.rstn)
    @(posedge vif.clk);
  for(int i = 0; i < 256; i = i+1)begin
    @(posedge vif.clk)
    vif.data_if <= $urandom_range(0,255);
    vif.valid <= 1'b1;
    `uvm_info("my_driver", "data is drived", UVM_LOW) 
  end
  @(posedge vif.clk);
  vif.valid <= 1'b0;
  phase.drop_objection(this);
endtask

my_if.sv

interface my_if(input clk, input rst_n);
   logic [7:0] data_if;
   logic valid;
endinterface

top_tb.sv

`timescale 1ns/1ps

`include "uvm_macros.svh"
import uvm_pkg::*;

`include "my_driver.sv"
`include "my_if.sv"

module top_tb;

reg clk,rstn;
reg  [7:0] data_i;
reg  data_i_valid;
wire [7:0] data_o;
wire data_o_valid;

my_if input_if(clk,rstn);
my_if output_if(clk,rstn);

dut my_dut(
  .clk            (clk)               ,
  .data_i         (input_if.data_if)  ,
  .data_o         (output_if.data_if) ,
  .data_i_valid   (input_if.valid)    ,
  .data_o_valid   (output_if.valid)
);

initial begin
  run_test("my_driver");
end

initial begin
  uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end

initial begin
  clk = 0;
  forever begin
    #100 clk = ~clk;
  end
end

initial begin
  rstn = 1'b0;
  #1000
  rstn = 1'b1;
end

endmodule

        下面我们在上面的基础上进行进一步的扩充和拓展。

添加transaction

        接下来我们还要为我们的验证平台添加monitor、scoreboard、reference model等验证平台组件。这些组件之间的信息传递是基于transaction的,因此我们在添加组件之前,先要添加transaction。

        transaction是一个抽象的概念。一般来说,物理协议中的数据交换都是以帧或者包为单位的,通常在一帧或者一个包中要定义好各项参数,一笔transactiin就是一个包。在不同的验证平台中会有不同的transaction。

transaction定义

class my_transaction extends uvm_sequence_item;
  
  rand bit [63:0] password; //assume there is a 64bits password
  
  `uvm_object_utils(my_transaction);
  
  function new(string name = "my_transaction");
    super.new(name);
  endfunction

endclass

        在transaction定义中,有两点值得注意:一是my_transaction的基类是uvm_sequence_item。在UVM中,所有的transaction都要从uvm_sequence_item派生,只有从uvm_sequence_item派生的transaction才可以使用UVM中强大的sequence机制;二是transaciton在使用factory机制时是使用uvm_object_utils,my_transaction和my_driver是由区别的:在整个仿真期间,driver是一直存在的,transaction不同,它在仿真的某一时间产生,经过driver驱动,再经过reference model处理,最终由scoreboard比较完成后,其生命周期就结束了。一般来说,这种类都是派生自uvm_object或者uvm_object的派生类。UVM中具有这种特征的类都要使用uvm_object_utils宏来注册

基于transaction的driver

        完成transaction.sv的定义后,就可以在my_driver中实现基于transaction的驱动。

`include "uvm_macros.svh"
`include "my_transaction.sv"

import uvm_pkg::*;

class my_driver extends uvm_driver;

  `uvm_component_utils(my_driver)  //注册
  virtual my_if vif;
  
  function new(string name = "my_driver", uvm_component parent = null);
    super.new(name, parent);
    `uvm_info("my_driver", "new is called", UVM_LOW)
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("my_driver", "build phase is called", UVM_LOW);
    if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
      `uvm_fatal("my_driver", "virtual interface must be set for vif");
  endfunction
  extern task main_phase(uvm_phase phase);
  extern task drive_password_trans(my_transaction tr);

endclass

task my_driver::main_phase(uvm_phase phase);
  my_transaction tr;
  phase.raise_objection(this);
  vif.data_if <= 8'd0;
  vif.valid   <= 1'b0;
  for (int i=0; i<8; i=i+1)begin
    tr = new("tr");
    assert(tr.randomize());
    drive_password_trans(tr);
  end
  repeat(5) @(posedge vif.clk); //delay
  phase.drop_objection(this);
endtask

task my_driver::drive_password_trans(my_transaction tr);
  bit [7:0] data_q[$];
  bit [63:0] temp_password;

  //push password to data_q
  temp_password = tr.password;
  for (int i=0; i<8; i++)begin
    data_q.push_back(temp_password[7:0]);
    temp_password = (temp_password >> 8);
  end

  `uvm_info("my_driver", "main phase is called", UVM_LOW);
  repeat(3) @(posedge vif.clk); //delay
  
  while(data_q.size()>0)begin
    @(posedge vif.clk);
    vif.valid <= 1'b1;
    vif.data_if <= data_q.pop_front();
  end

  @(posedge vif.clk);
   vif.valid <= 1'b0;
  `uvm_info("my_driver", "end drive one_serial password", UVM_LOW);
endtask

仿真结果: 

在transaction中循环了8次drive_password_trans任务。

  for (int i=0; i<8; i=i+1)begin
    tr = new("tr");
    assert(tr.randomize());
    drive_password_trans(tr);
  end

在drive_password_trans任务中,我们传输一次password

task my_driver::drive_password_trans(my_transaction tr);
  bit [7:0] data_q[$];
  bit [63:0] temp_password;

  //push password to data_q
  temp_password = tr.password;
  for (int i=0; i<8; i++)begin
    data_q.push_back(temp_password[7:0]);
    temp_password = (temp_password >> 8);
  end

  `uvm_info("my_driver", "main phase is called", UVM_LOW);
  repeat(3) @(posedge vif.clk); //delay
  
  while(data_q.size()>0)begin
    @(posedge vif.clk);
    vif.valid <= 1'b1;
    vif.data_if <= data_q.pop_front();
  end

drive_password_trans任务功能描述

        这里因为data_q设置的是8bit,而password一次是64bit的,所以设置data_q为队列,需要入队8次才能把password完整的存下来,所以我们for循环循环8次,每次让8bit数据从队列的尾部进入,也即push_back。

        之后等待三个时钟上升沿,然后把队列中的首部数据(pop_front)排出送给my_driver的interface “vif”,直到队列排空。


        至此,我们就完成了添加transaction的driver设计,涉及到的知识有interface、virtual interface、config_db机制、transaction设计等内容。至此不要忘了,我们加入transaction的目的是因为monitor、scoreboard等验证组件之间的交互是通过transaction的,因此,接下来我们就要添加UVM的其他组件了。        

最后

以上就是危机冰淇淋为你收集整理的从零开始,搭建一个简单的UVM验证平台(二)interface & virtual interfaceconfig_db机制阶段代码总结添加transaction的全部内容,希望文章能够帮你解决从零开始,搭建一个简单的UVM验证平台(二)interface & virtual interfaceconfig_db机制阶段代码总结添加transaction所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部