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