The UVM Primer -- Chapte 16 Using Analysis ports

本文详细介绍了如何在UVM测试平台中使用分析端口(analysis ports),以减少重复代码并提高模块复用性。通过BFM、command_monitor、coverage和scoreboard模块的处理,展示了如何通过analysis port将数据从驱动层传输到分析层,从而简化环境组件之间的连接和通信。uvm_tlm_analysis_fifo的使用允许在同一subscriber中处理来自多个analysis port的数据,实现更灵活的数据整合。

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

Chapte 16 – Using Analysis ports in a testbench

测试平台中所有的组件可以分为两种:驱动DUT运行 & 观察DUT变化。
我们此前的测试平台中,tester_h就是用于驱动仿真平台运行,scoreboard_h和coverage_h用于监控DUT的行为。从UVM的角度来看,scoreboard和coverage组成了测试平台的_analysis layer_.所以,用于传输数据的端口被称作uvm_analysis_port.

16.1 重复代码

如果两段代码在实现一致的功能,就像是露营时打开帐篷:You are inviting bugs in。
此前,我们的测试平台就具有这样的代码,在covereage和scoreboard中都需要从BFM收集cmd信号,但是这样的工作其实做一次就足够了。
我们希望可以在BFM的pins上,检测cmd的变化,并将数据送至coverage和scoreboard。因为scoreboard需要计算结果,所以需要在上收集result,并送至scoreboard。

16.2 将analysis port用于测试平台

测试平台的整体框架图如下:
在这里插入图片描述
在此前的测试平台,tester_h中包含BFM的句柄,现在BFM有了两个新的句柄,command_monitor和result_monitor. BFM识别出TinyALU的command和results并将其送至monitor,在monitor中调用analysis的write函数,将数据送至coverage_h和scoreboard_h中。

16.2.1 bfm模块的处理

OOP编程最大的优势就是,我们可以通过共享对象句柄为测试平台的各个模块增加功能。现在我们使用tinyalu_bfm将测试平台的其余部分连接起来。

  • 以下是tinyalu_bfm模块的部分代码:
interface tinyalu_bfm;
   import tinyalu_pkg::*;
   
   command_monitor command_monitor_h;         //声明两个Monitor的句柄
   result_monitor  result_monitor_h;
   
   assign op = op_set;

   always @(posedge clk) begin : cmd_monitor
      bit new_command;
      if (!start) 
        new_command = 1;
      else
        if (new_command) begin                               //监控ALU的commands
           command_monitor_h.write_to_monitor(A, B, op);     //每当有新的command到来,调用cmd_monitor的write_to_monitor函数将数据送至command_monitor
           new_command = (op == 3'b000); // handle no_op
        end 
   end : cmd_monitor
   
   always @(negedge reset_n) begin : rst_monitor             //监控ALU的commands
      command_monitor_h.write_to_monitor(A, B, rst_op);      //当reset_n的信号有效时,调用cmd_monitor的write_to_monitor函数,将reset_op送至command_monitor
   end : rst_monitor
                                                             //监控ALU的results
   always @(posedge clk) begin : rslt_monitor                //当操作结束,done信号有效时,调用result_monitor的write_to_monitor函数,将result数据送至result_monitor
         if (done) 
           result_monitor_h.write_to_monitor(result);
   end : rslt_monitor

endinterface : tinyalu_bfm

16.2.2 command_monitor模块的处理 (monitor)

在command_monitor的build_phase中,声明virtual interface bfm,在仿真开始时,通过uvm_cfgdb机制get bfm,然后将自己的句柄传输给bfm中的command_monitor_h。
(result_monitor以同样的方式实现此功能,这里就不再贴代码了)

  • command_s数据结构如下图:
    在这里插入图片描述
  • 以下是command_monitor模块的代码:(analysis port就在monitor中)
class command_monitor extends uvm_component;
   `uvm_component_utils(command_monitor);

  uvm_analysis_port #(command_s) ap;    //注意,analysis的参数类型是command_s,这是我们自定义的数据结构,如上图。
  
  function void build_phase(uvm_phase phase);
    virtual tinyalu_bfm bfm;
    if(!uvm_config_db #(virtual tinyalu_bfm)::get(null, "*","bfm", bfm))
       $fatal("Failed to get BFM");    //build phase top down开始,先执行bfm的bp,然后执行monitor的build phase
    bfm.command_monitor_h = this;      //将自己的句柄连接至BFM的cmd_monitor,这样bfm就可以通过command\_monitor向测试平台传递数据
    ap  = new("ap",this);              //analysis port在build phase例化
  endfunction : build_phase

  function void write_to_monitor(byte A, byte B, bit[2:0] op);
    command_s cmd;
    cmd.A = A;
    cmd.B = B;
    cmd.op = op2enum(op);
    $display("COMMAND MONITOR: A:0x%2h B:0x%2h op: %s", A, B, cmd.op.name());
    ap.write(cmd);                     //在bfm调用monitor的write_to_monitor函数时,同时调用了analysis port的write函数,将数据送出,
                                       //其他功能模块就可以从analysis_export端口接收到command_s类型的数据
  endfunction : write_to_monitor

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

endclass : command_monitor

16.2.3 coverage模块的处理(analysis layer)

随着我们将复杂的操作分为一个个更小的单元,简单地小单元可以使我们更容易构建其他环境组件。这些拆分的小模块都成为了后续的资源。
作为subscriber(subscriber和analysis port是一对哦)的coverage,就可以利用以上的小功能单元,通过简单地调用实现同样的功能,并且易于debug和重用,其部分代码如下:

class coverage extends uvm_subscriber #(command_s);   //subscriber是参数化的类,使用时需要制定analysis export传输的数据类型
   `uvm_component_utils(coverage)

   byte         unsigned        A;
   byte         unsigned        B;
   operation_t  op_set;

   covergroup op_cov;
   function new (string name, uvm_component parent);
      super.new(name, parent);
      op_cov = new();
      zeros_or_ones_on_ops = new();
   endfunction : new

   function void write(command_s t);   //1. uvm_subscriber提供的analysis export接收analysis port传过来的数据
         A = t.A;                      //2. 但是哪里能只占便宜呢?
         B = t.B;                      //3. 接收到的数据必须在名称为write()的函数中进行处理
         op_set = t.op;                //4. 使用uvm_subscriber必须要重载write函数。
         op_cov.sample();              //5. write函数的参数类型必须和export传输的数据类型一致
         zeros_or_ones_on_ops.sample();
   endfunction : write

endclass : coverage

coverage函数不再需要自己处理信号级别的数据,如clock,bits等。他可以直接接收monitor通过analysis port传输的整洁的command_s类型的数据。

16.2.4 scoreboard模块的处理(analysis layer)

  • 基础的uvm_analysis port机制允许uvm_subscriber监控一个analysis port, 但是在类似scoreboard中我们希望可以在一个uvm_subscriber同时从多个analysis port中获取数据。(当然我们在声明一个subscriber就可以解决问题,但是却增加了环境复杂度)
  • UVM的开发人员,为我们提供了uvm_tlm_analysis_fifo来解决这个问题,我们在scoreboard中使用这种机制实现同一个subscriber中获取不同参数类型的analysis port的数据:
  • uvm_tlm_analysi_fifo是一个参数化的类,此fifo在一侧实现了analysis_export,在另一侧使用try_get函数。我们通过analysis_export从analysis_port获得数据,通过try_get从FIFO中吧数据取出来
class scoreboard extends uvm_subscriber #(shortint);   //subscriber 传入shortint类型
   `uvm_component_utils(scoreboard);                   //1. wirte函数的参数类型必须是shortint
                                                       //2. scoreboard继承了shorint类型的export  
   uvm_tlm_analysis_fifo #(command_s) cmd_f;           //声明command_s类型的uvm_tlm_analysis_fifo
                                                       //这样就可以实现对两种不同类型的analysis port的订阅
   function void build_phase(uvm_phase phase);
      cmd_f = new ("cmd_f", this);
   endfunction : build_phase

   function void write(shortint t);   //假设我们确定只有在收到command后才会收到result                 
      shortint predicted_result;      //scoreboard通过result analysis port的write函数触发检查
      command_s cmd; 
      cmd.op = no_op;                 //1. 有result到达意味着此前一定有command进入了FIFO
      do                              //2. 接收到result之后,用do while循环找到上次的非rst操作command
       if (!cmd_f.try_get(cmd)) $fatal(1, "No command in self checker");  //如果FIFO为空,try_get返回0,但是没有cmd不会有result,所以此时进入fatal错误
      while ((cmd.op == no_op) || (cmd.op == rst_op));
      
      case (cmd.op)                   //3. 找到对应的command_s类型的数据后,根据cmd和A B计算预测值
        add_op: predicted_result = cmd.A + cmd.B;
        and_op: predicted_result = cmd.A & cmd.B;
        xor_op: predicted_result = cmd.A ^ cmd.B;
        mul_op: predicted_result = cmd.A * cmd.B;
      endcase // case (op_set)

      if (predicted_result != t)      //4. 如果预测值和收到的result不一致,说明DUT计算错误
       $error (                       
     "FAILED: A: %2h  B: %2h  op: %s actual result: %4h   expected: %4h",
         cmd.A, cmd.B, cmd.op.name(), t,  predicted_result);
   endfunction : write         

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

endclass : scoreboard

16.2.5 env模块的处理

通过上面的处理,我们已经将测试平台按照功能分割开来,那么在验证环境中需要在env中将个组件连接起来。

  • BFM和monitor之间:BFM内部声明monitor类型的句柄,monitor在build phase中将this传递给BFM中对应的monitor句柄;
  • monitor和coverage&scoreboard之间:analysis port和subscriber的关系,通过analysis port的write函数和subscriber的export,或FIFO的export连接起来。
    在测试环境组件env的connect phase中完成上述连接:
class env extends uvm_env;
   `uvm_component_utils(env);

   random_tester   random_tester_h;     //对象声明
   coverage        coverage_h;
   scoreboard      scoreboard_h;
   command_monitor command_monitor_h;
   result_monitor  result_monitor_h;
   
   function new (string name, uvm_component parent);
      super.new(name,parent);
   endfunction : new

   function void build_phase(uvm_phase phase);   //在build phase例化
      random_tester_h    = random_tester::type_id::create("random_tester_h",this);
      coverage_h  =  coverage::type_id::create ("coverage_h",this);
      scoreboard_h = scoreboard::type_id::create("scoreboard_h",this);
      command_monitor_h   = command_monitor::type_id::create("command_monitor_h",this);
      result_monitor_h= result_monitor::type_id::create("result_monitor_h",this);
   endfunction : build_phase

   function void connect_phase(uvm_phase phase);  //connect phase调用analysis port的connect函数
      result_monitor_h.ap.connect(scoreboard_h.analysis_export);         //连接至subscriber默认的export
      command_monitor_h.ap.connect(scoreboard_h.cmd_f.analysis_export);  //连接至subscriber中的FIFO上的export
      command_monitor_h.ap.connect(coverage_h.analysis_export);          //连接至subscriber默认的export 
   endfunction : connect_phase
   
endclass
  • 本节中,我们使用uvm_analysis_port实现了testbench中Observer design pattern,我们从信号级监控转至使用monitor class和analysis port实现数据从monitor -> analysis class的传输;
  • analysis port是单线程通信的有效机制,所有的功能都发生在单线程内。当monitor中在调用analysis port的write函数时,其实调用了线程内所有subscriber的write函数。
  • analysis port在单线程通信中是十分有效的机制。但是当我们需要在多线程间进行数据传输时,UVM为我们提供了新的机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值