Updated Example Code from DVCon Paper: The Missing Link: The Testbench to DUT Connection

I’ve attached an updated version of code from my DVCon paper:
The Missing Link: The Testbench to DUT Connection
that works with both UVM 1.1d and UVM 1.2

// $Id: probe.sv,v 1.4 2018/04/01 14:34:38 drich Exp $
//----------------------------------------------------------------------
// Dave Rich dave_rich@mentor.com
// Copyright 2007-2018 Mentor Graphics Corporation
//   All Rights Reserved Worldwide
//
// Licensed under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of
//   the License at
//
// http://www.apache.org/licenses/LICENSE-2.0 
//
// Unless required by applicable law or agreed to in
// writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See
// the License for the specific language governing
// permissions and limitations under the License. 
//----------------------------------------------------------------------

// ----------------------------------------------------------------------------------- 
// file RTL.sv

// Package of parameters to be shared by DUT and Testbench 
package common_pkg;
   parameter WordSize1 = 32;
   parameter WordSize2 = 16;
endpackage
   // simple DUT containing two sub models.
module DUT (input    wire CLK,CS,WE);
   
   import common_pkg::*;
   wire CS_L = !CS;
   model   #(WordSize1) sub1( .CLK, .CS, .WE);
   model   #(WordSize2) sub2(.CLK, .CS(CS_L), .WE);
   
endmodule


// simple lower level modules internal to the DUT
module model (input wire CLK, CS, WE);
   parameter WordSize = 1;
   reg [WordSize-1:0] Mem;
   wire [WordSize-1:0] InternalBus;
   always @(posedge CLK)
     if (CS && WE)
       begin
	  Mem = InternalBus;
	  $display("%m Wrote %h at %t",InternalBus,$time);
       end
   assign InternalBus = (CS && !WE) ? Mem : 'z;
endmodule
// ----------------------------------------------------------------------------------- 
// file probe_pkg.sv
//

// abstract class interface
package probe_pkg;
   import uvm_pkg::*;
   virtual class probe_abstract #(type T=int) extends uvm_object;
      function new(string name="");
         super.new(name);
      endfunction
      // the API for the internal probe
      pure virtual function T get_probe();
      pure virtual function void set_probe(T Data ); pure virtual task edge_probe(bit Edge=1);
   endclass : probe_abstract
endpackage : probe_pkg
   // This interface will be bound inside the DUT and provides the concrete class defintion. 
interface probe_itf #(int WIDTH) (inout wire [WIDTH-1:0] WData);
   import uvm_pkg::*;
   typedef logic [WIDTH-1:0] T;
   T Data_reg = 'z;
   assign WData = Data_reg;
   import probe_pkg::*;
   // String used for factory by_name registration
   localparam string 	     PATH = $sformatf("%m");
   // concrete class
class probe extends probe_abstract #(T);
   function new(string name="");
      super.new(name);
   endfunction // new
   typedef uvm_object_registry #(probe,{"probe_",PATH}) type_id;
   static function type_id get_type(); return type_id::get();
   endfunction
   // provide the implementations for the pure methods
   function T get_probe();
      return WData;
   endfunction
   function void set_probe(T Data );
      Data_reg = Data;
   endfunction
   task edge_probe(bit Edge=1);
      @(WData iff (WData === Edge));
   endtask
endclass : probe
endinterface : probe_itf
// ----------------------------------------------------------------------------------- 
// file test_pkg.sv
//
// This package defines the UVM test environment
`include "uvm_macros.svh"
package test_pkg;
   import uvm_pkg::*;
   import common_pkg::*;
   import probe_pkg::*;
   //My top level UVM test class
class my_driver extends uvm_component;
   function new(string name="",uvm_component parent=null);
      super.new(name,parent);
   endfunction
   typedef uvm_component_registry #(my_driver,"my_driver") type_id;
   // Virtual interface for accessing top-level DUT signals
   typedef virtual DUT_itf vi_itf_t;
   vi_itf_t vi_itf_h;
   
   // abstract class variables that will hold handles to concrete classes built by the factory
   // These handle names shouldn't be tied to actual bind instance location - just doing it to help 
   // follow the example. You could use config strings to set the factory names.
   probe_abstract #(logic [WordSize1-1:0]) sub1_InternalBus_h;
   probe_abstract #(logic [WordSize2-1:0]) sub2_InternalBus_h;
   probe_abstract #(logic [0:0]) sub1_ChipSelect_h;
   function void build_phase(uvm_phase phase);
      if (!uvm_config_db#(vi_itf_t)::get(this,"","DUT_itf",vi_itf_h))
	uvm_report_fatal("NOVITF","No DUT_itf instance set",,`__FILE__,`__LINE__);
      $cast(sub1_InternalBus_h, create_object("probe_testbench.dut.sub1.m1_1","sub1_InternalBus_h"));
      $cast(sub2_InternalBus_h, create_object("probe_testbench.dut.sub2.m1_2","sub2_InternalBus_h"));
      $cast(sub1_ChipSelect_h,  create_object("probe_testbench.dut.sub1.m1_3","sub1_ChipSelect_h"));
   endfunction : build_phase
   // simple driver routine just for testing probe class
   task run_phase(uvm_phase phase);
      phase.raise_objection( this );
      vi_itf_h.WriteEnable <= 1;
      vi_itf_h.ChipSelect <= 0;
      fork
	 process1: forever begin
	    @(posedge vi_itf_h.Clock); 
	    `uvm_info("GET1",$sformatf("%h",sub1_InternalBus_h.get_probe()),UVM_LOW)
	    `uvm_info("GET2",$sformatf("%h",sub2_InternalBus_h.get_probe()),UVM_LOW)
	 end
	 process2: begin
	    sub1_ChipSelect_h.edge_probe(); 
	    `uvm_info("EDGE3","CS had a posedge",UVM_LOW)
	    sub1_ChipSelect_h.edge_probe(0); 
	    `uvm_info("EDGE3","CS had a negedge",UVM_LOW)
	 end
	 process3: begin
	    @(posedge vi_itf_h.Clock); 
	    vi_itf_h.ChipSelect <= 0; 
	    sub2_InternalBus_h.set_probe('1);
	    @(posedge vi_itf_h.Clock);
	    vi_itf_h.ChipSelect <= 1;
	    sub1_InternalBus_h.set_probe('1);
	    @(posedge vi_itf_h.Clock);
	    vi_itf_h.ChipSelect <= 0;
	    sub2_InternalBus_h.set_probe('0);
	    @(posedge vi_itf_h.Clock);
	    @(posedge vi_itf_h.Clock);
	 end join_any
      phase.drop_objection( this );
   endtask : run_phase
endclass : my_driver
class my_test extends uvm_test;
   function new(string name="",uvm_component parent=null);
      super.new(name,parent);
   endfunction
   typedef uvm_component_registry #(my_test,"my_test") type_id;
   my_driver my_drv_h;
   function void build_phase(uvm_phase phase);
      my_drv_h = my_driver::type_id::create("my_drv_h",this);
   endfunction : build_phase
endclass : my_test
endpackage : test_pkg

// ----------------------------------------------------------------------------------- // file testbench.sv
//
interface DUT_itf(input bit Clock);
   logic             ChipSelect;
   logic             WriteEnable;
endinterface : DUT_itf
module testbench;
   import common_pkg::*;
   import uvm_pkg::*;
   import test_pkg::*;
   bit SystemCLK=1;
   always #5 SystemCLK++;
// The DUT interface;
DUT_itf itf(.Clock(SystemCLK)); typedef virtual DUT_itf vi_itf_t;
// The DUT
DUT dut(.CLK(itf.Clock), .CS(itf.ChipSelect), .WE(itf.WriteEnable));
// instantiate interfaces internal to DUT
   bind model : dut.sub1 probe_itf #(.WIDTH(common_pkg::WordSize1)) m1_1(InternalBus);
   bind model : dut.sub2 probe_itf #(.WIDTH(common_pkg::WordSize2)) m1_2(InternalBus);
   bind model : dut.sub1 probe_itf #(.WIDTH(1)) m1_3(CS);
   initial begin 
      uvm_config_db#(vi_itf_t)::set(null,"","DUT_itf",itf);
      run_test("my_test");
   end
endmodule : testbench
1 Like

In reply to dave_59:

thanks, just what I was looking for.