Modelsim搭建具有各组件的UVM验证平台

本篇博客详细介绍了如何在《UVM实战(卷I)》中,通过加入transaction、env、monitor、agent、referencemodel和scoreboard,以及field_automation机制,实现DUT驱动和验证的模块化设计。重点讲解了组件的组织结构和交互流程,以及如何利用UVM的工厂机制和TLM通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

记录一下《UVM实战(卷I)》学习笔记。

这节的 dut 与第一节是一样的,直接对输入打拍输出:

module dut(clk,
           rst_n, 
           rxd,
           rx_dv,
           txd,
           tx_en);
input clk;
input rst_n;
input[7:0] rxd;
input rx_dv;
output [7:0] txd;
output tx_en;

reg[7:0] txd;
reg tx_en;

always @(posedge clk) begin
   if(!rst_n) begin
      txd <= 8'b0;
      tx_en <= 1'b0;
   end
   else begin
      txd <= rxd;
      tx_en <= rx_dv;
   end
end
endmodule

1 加入 transaction

一般物理协议传输都是以帧或包为单位,很少以bit或byte为单位进行数据交换。

transaction 用于模拟这种实际情况,一笔 transaction 就是一个包。

注意:

  • my_transaction的基类是uvm_sequence_item。只有从uvm_sequence_item派生的transaction才可以使用UVM中强大的sequence机制。
  • 没有使用uvm_component_utils宏来实现factory机制,而是使用了uvm_object_utils。

书本例程 my_transaction.sv 定义了一个简单的以太网的 transaction 。

在my_driver中实现基于transaction的驱动。

my_driver中定义的 main_phase 实现 transaction 的例化,然后 drive_one_pkt 任务的作用是将这个包按 byte 传输出去。

添加相关源文件,然后定义 modelsim do文件:

cd D:/prj/uvm_prj/demo2.3.1   
vlib work  
set UVM_PATH C:/software/FPGA/modeltech64_10.5/verilog_src/uvm-1.1d/src
set SRC_PATH . 
vlog +incdir+$UVM_PATH $UVM_PATH/uvm_pkg.sv $SRC_PATH/top_tb.sv $SRC_PATH/dut.sv 
vsim -novopt -sv_lib C:/software/FPGA/modeltech64_10.5/uvm-1.1d/win64/uvm_dpi work.top_tb 
add wave -position insertpoint sim:/top_tb/*
run 2us

定义批处理文件 :

vsim -do demo.do

文件列表:
在这里插入图片描述

仿真结果:
在这里插入图片描述

在 my_driver 的 main_phase 可以看到,产生了两个随机包,然后发送出去,这与仿真结果相符合。

2 加入 env

定义好了组件,验证平台的什么位置对它们进行实例化?

引入一个容器类,在这个容器类中实例化 driver、monitor、reference model 和 scoreboard 等。在调用 run_test 时,传递的参数不再是 my_driver ,而是这个容器类,即让UVM自动创建这个容器类的实例。在UVM中,这个容器类称为 uvm_env。

注意:

  • 所有的env应该派生自uvm_env, 且与my_driver一样, 容器类在仿真中也是一直存在的, 使用uvm_component_utils宏来实现factory的注册。
  • build_phase的执行遵照从树根到树叶的顺序。
`ifndef MY_ENV__SV
`define MY_ENV__SV

class my_env extends uvm_env;

   my_driver drv; 

   // 
   function new(string name = "my_env", uvm_component parent); 
      super.new(name, parent);
   endfunction

   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase); 

      // 例化是 type_name::type_id::create 的方式 而不是 调用 my_driver 的 new 函数。只有使用factory机制注册过的类才能使用这种方式实例化,只有使用这种方式实例化的实例, 才能使用后文要讲述的factory机制中最为强大的重载功能。
      drv = my_driver::type_id::create("drv", this); 
   endfunction

    // 容器类在仿真过程中一直存在,所以用 uvm_component_utils 宏实现 factory 的注册。  
   `uvm_component_utils(my_env)
endclass
`endif

由于my_driver在uvm_env中实例化,所以my_driver的父结点(parent)就是my_env。所以例化的时候传递的参数为 this 指针。通过parent的形式,UVM建立起了树形的组织结构。在这种树形的组织结构中,由run_test创建的实例是树根(这里是my_env),并且树根的名字是固定的,为uvm_test_top,在树根之后会生长出枝叶(这里只有my_driver),长出枝叶的过程需要在my_env的build_phase中手动实现。无论是树根还是树叶,都必须由uvm_component或者其派生类继承而来。

在这里插入图片描述

修改 Modelsim do文件,如果像我这样吧批处理文件放在工程中,则第一行切换工作路径可以删除,这也就可以通用了。另外,作者的代码都有$finish,所有仿真时间可以改为 run -all

cd .   
vlib work  
set UVM_PATH C:/software/FPGA/modeltech64_10.5/verilog_src/uvm-1.1d/src
set SRC_PATH . 
vlog +incdir+$UVM_PATH $UVM_PATH/uvm_pkg.sv $SRC_PATH/top_tb.sv $SRC_PATH/dut.sv 
vsim -novopt -sv_lib C:/software/FPGA/modeltech64_10.5/uvm-1.1d/win64/uvm_dpi work.top_tb 
add wave -position insertpoint sim:/top_tb/* sim:/top_tb/input_if/*
run -all

文件结构:
在这里插入图片描述

仿真结果:
在这里插入图片描述

3 加入 monitor

driver 把 transaction 级数据转变成 DUT 的输入数据。
monitor 把 DUT 的输出数据 转变成 transaction 数据。
monitor 输出的 transaction 数据交给后续组件(如referance model、scoreboard等)处理。

注意:

  • 所有的monitor类应该派生自uvm_monitor。
  • uvm_monitor在整个仿真中是一直存在的, 所以它是一个component, 要使用uvm_component_utils宏注册。
  • 由于monitor需要时刻收集数据, 永不停歇, 所以在main_phase中使用while( 1) 循环来实现这一目的。

monitor的定义:

`ifndef MY_MONITOR__SV
`define MY_MONITOR__SV 

// 所有的 monitor 派生自 uvm_monitor  
class my_monitor extends uvm_monitor;

   virtual my_if vif;

    // uvm_monitor在整个仿真中是一直存在的
   `uvm_component_utils(my_monitor)
   function new(string name = "my_monitor", uvm_component parent = null);
      super.new(name, parent);
   endfunction

    // 当UVM启动后, 会自动执行 build_phase。 build_phase在new函数之后main_phase之前执行。 
   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
         `uvm_fatal("my_monitor", "virtual interface must be set for vif!!!")
   endfunction

   extern task main_phase(uvm_phase phase);
   extern task collect_one_pkt(my_transaction tr);
endclass

task my_monitor::main_phase(uvm_phase phase);
   my_transaction tr;

   // monitor需要时刻收集数据, 永不停歇
   while(1) begin
      tr = new("tr");
      collect_one_pkt(tr);
   end
endtask

task my_monitor::collect_one_pkt(my_transaction tr);
   // ...
endtask

`endif

在env对monitor实例化:

// ...
class my_env extends uvm_env;
   my_driver drv;
   my_monitor i_mon;
   my_monitor o_mon;
   // ...
   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      drv = my_driver::type_id::create("drv", this);  

      // 同样的,要传递 this 指针作为 monitor 的 parent。  
      i_mon = my_monitor::type_id::create("i_mon", this);
      o_mon = my_monitor::type_id::create("o_mon", this);
   endfunction
   `uvm_component_utils(my_env)
endclass
// ...

DUT 输入口添加 monitor 的原因:

  • driver根据某一协议发送数据, 而monitor根据这种协议收集数据, 如果driver和monitor由不同人员实现, 那么可以大大减少其中任何一方对协议理解的错误。
  • 实现代码重用。

文件结构:
在这里插入图片描述

仿真结果:

在这里插入图片描述

4 封装成agent

driver和monitor处理同一种协议,代码高度相似,UVM通常将二者封装在一起,称为一个agent。
因此,不同agent 代表不同协议。

注意:

  • 所有的agent都要派生自uvm_agent类。
  • 其本身是一个component, 应该使用uvm_component_utils宏来实现factory注册。
//...
// 所有 agent 都要派生自 uvm_agent   
class my_agent extends uvm_agent ;
   my_driver     drv;
   my_monitor    mon;
   
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction 
   
   extern virtual function void build_phase(uvm_phase phase);
   extern virtual function void connect_phase(uvm_phase phase);

   `uvm_component_utils(my_agent)
endclass 


function void my_agent::build_phase(uvm_phase phase);
   super.build_phase(phase); 

   //  is_active是uvm_agent的一个成员变量
   if (is_active == UVM_ACTIVE) begin
       drv = my_driver::type_id::create("drv", this);
   end
   mon = my_monitor::type_id::create("mon", this);
endfunction 

function void my_agent::connect_phase(uvm_phase phase);
   super.connect_phase(phase);
endfunction
//...

在env例化agent 而不需要直接例化 driver 和 monitor。

UVM树层次结构:

在这里插入图片描述

5 加入 reference model

reference model用于完成和DUT相同的功能。
reference model的输出被scoreboard接收,用于和DUT的输出相比较。

reference model 单纯地复制一份从i_agt得到的tr, 并传递给后级的scoreboard中。在UVM中,通常使用TLM( Transaction Level Modeling)实现component之间transaction级别的通信。

在UVM的transaction级别的通信中,数据的发送有多种方式,其中一种是使用uvm_analysis_port。这个是在monitor中发送的;数据接收方式也有多种,其中一种就是使用uvm_blocking_get_port,在model中接收。最后还要在env中使用fifo将两个端口连接在一起。

连接需要在env使用UVM内建 phase :connect_phase。在build_phase执行完成之后马上执行。执行顺序:从树叶到树根。

6 加入 scoreboard

my_scoreboard要比较的数据一是来源于reference model,二是来源于o_agt的monitor。都可以通过 uvm_blocking_get_port 来实现。

收到参考模型的数据先存到队列中,等DUT输出数据,再弹出来进行比对。

7 加入 field_automation 机制

引入my_mointor时, 在my_transaction中加入了my_print函数;引入reference model时, 加入了my_copy
函数;引入scoreboard时, 加入了my_compare函数。函数不同,但对于不同 transaction 来说类似,都是逐字段处理。

UVM中的field_automation机制, 使用uvm_field系列宏实现。通过定义某些规则自动实现这三个函数。

my_transaction.sv:

// ...
class my_transaction extends uvm_sequence_item;
   rand bit[47:0] dmac;
   rand bit[47:0] smac;
   rand bit[15:0] ether_type;
   rand byte      pload[];
   rand bit[31:0] crc;
   //...
   `uvm_object_utils_begin(my_transaction)
      `uvm_field_int(dmac, UVM_ALL_ON)
      `uvm_field_int(smac, UVM_ALL_ON)
      `uvm_field_int(ether_type, UVM_ALL_ON)
      `uvm_field_array_int(pload, UVM_ALL_ON)
      `uvm_field_int(crc, UVM_ALL_ON)
   `uvm_object_utils_end
   //...
endclass
//...
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ChipWeaver

觉得有用的话点个赞吧 :)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值