Mixed Hierarchy

This example demonstrates how testbench environments can evolve to become mixed component hierarchies, where OVM instantiates VMM that instantiates OVM and so on.  Such hierarchies are referred as sandwiched or layered.

The following figure represents what is meant by mixed hierarchy

Such hierarchies are not typically designed from scratch like this.  They typically evolve to something like this as block-level testing progresses to system-level, where integration of existing IP and legacy environments are commonly employed to maximize reuse.

When integrating OVM components as children of envs, subenvs, and xactors, care must be taken to ensure the hierarchical name of every OVM component is unique and deterministic.  The OVM’s configuration and factory interfaces require this.  It is not enough to simply pass a null handle to each OVM component’s parent constructor argument.  Such components end up as children of ovm_top, the implicit top-level OVM component, and there can not be any two children by the same name in ovm_top.

Therefore, to meet this requirement, these guidelines should be followed to emulate hierarchical naming in an environment containing OVM components:

  • The instance name of a VMM child of an OVM parent should be prefixed with the full name of the OVM parent:
OVM instantiating VMM in build method:

virtual function build();
  env    = new({get_full_name(),".env"},...);
  subenv = new({get_full_name(),".subenv"},...);
  xactor = new("typename",{get_full_name(),".xactor"},...);
  • The instance name of an OVM child of an VMM parent should be prefixed with the instance name of the VMM parent.  The instance name of a VMM child of a VMM parent should be prefixed in the same manner if the VMM child contains any OVM components, directly or indirectly.  This ensures that the name of the OVM descendants receive a unique name.
VMM env instantiating OVM and VMM children in build method:

virtual function build();
  o_comp = new({log.get_name(),".o_comp"},this);
  xactor = new("Xactor Name","Xactor Inst",...);

VMM subenv/xactor instantiating OVM and VMM children in
constructor:

function new(string name, string inst, ...);
  super.new(name,inst,...);
  o_comp = new({inst,".o_comp"},this);
  xactor = new("Xactor Name","Xactor Inst",...);

The instance names given any component containing any OVM component, directly or indirectly, should contain only alpha-numerics and underscores.

We now present the example in series of steps that might emulate how a mixed testbench environment evolves.

Summary
Mixed Hierarchy
This example demonstrates how testbench environments can evolve to become mixed component hierarchies, where OVM instantiates VMM that instantiates OVM and so on.
Step 1 - Simple block-level, mixed environmentWe start with a simple mixed, block-level testbench that connects a VMM generator to on OVM driver via the avt_channel2tlm adapter.
Step 2 - Define an OVM IP blockAt some point, we may decide to encapsulate the block-level testbench in an OVM IP block for reuse at the system level.
Step 3 - Define a VMM IP block - Part AFirst encapsulate all OVM children with an OVM component wrapper that can make all port connections at the appropriate time.
Step 3 - Define a VMM IP block - Part BThen, define the VMM IP block, which instantiates the OVM wrapper and other VMM components.
Step 4 - VMM top-levelLet us now consider a vmm_env that contains our vmm_ip block and, optionally, other instances of OVM IP and VMM IP.
Step 5 - OVM sub-component levelWe can easily encapsulate our tb_env above for reuse as an ovm_component by extending a avt_ovm_vmm_env specialization, avt_ovm_vmm_env #(tb_env).
Step 6 - OVM top-levelLet us now consider an ovm_env that contains our ovm_subcomp block and, optionally, other instances of OVM IP and VMM IP.
Step 7 - ModuleAlthough we can continue the sandwiching of OVM and VMM components indefinitely, we stop at this point by instantiating the ovm_env as a top-level component.

Step 1 - Simple block-level, mixed environment

We start with a simple mixed, block-level testbench that connects a VMM generator to on OVM driver via the avt_channel2tlm adapter.

class block_env extends ovm_component;

  `ovm_component_utils(block_env)

  vmm_apb_rw_atomic_gen gen;
  apb_channel2tlm       adapt;
  ovm_driver_req        drv;
  apb_scoreboard        compare;

  bit PASS  = 0;

  function new (string name="block_env",ovm_component parent=null);
    super.new(name,parent);
  endfunction

  virtual function void build();
    gen   = new("gen", 0);
    adapt = new("adapt", this, gen.out_chan);
    drv   = new("drv", this);
    void'(get_config_int("max_trans",drv.max_trans));
    compare = new("comparator", this, gen.out_chan);
    gen.out_chan.tee_mode(1);
  endfunction

  virtual function void connect();
    drv.seq_item_port.connect(adapt.seq_item_export);
    drv.ap.connect(compare.ovm_in);
  endfunction

  virtual task run();
    gen.start_xactor();
    gen.notify.wait_for(vmm_apb_rw_atomic_gen::DONE);
    ovm_top.stop_request();
  endtask

  virtual function void check();
    if(compare.m_matches >= 1 && compare.m_mismatches == 0)
      PASS  = 1;
  endfunction // check

  virtual function void report();
    if(PASS == 1) begin
      `OVM_REPORT_INFO("PASS","Test PASSED");
    end
    else begin
      `OVM_REPORT_ERROR("FAIL","Test FAILED");
    end
  endfunction // report
endclass

Step 2 - Define an OVM IP block

At some point, we may decide to encapsulate the block-level testbench in an OVM IP block for reuse at the system level.  Doing this does not take much effort, as we can simply extend the existing block-level env to change its role from top-level env to a reusable subcomponent as follows:

  • promote the underlying driver’s analysis port to the IP block-level for possible connection by external components.
  • override the run task to not govern the end-of-test via stop_request.  The IP is now a mere participant.
class ovm_ip extends block_env;

  `ovm_component_utils(ovm_ip)

  ovm_analysis_port #(ovm_apb_rw) ap;

  function new(string name, ovm_component parent=null);
    super.new(name,parent);
  endfunction

  function void build();
    super.build();
    ap = new ("ap",this);
  endfunction

  function void connect();
    super.connect();
    drv.ap.connect(ap);
  endfunction

  virtual task run();
    gen.start_xactor();
  endtask

endclass

Step 3 - Define a VMM IP block - Part A

First encapsulate all OVM children with an OVM component wrapper that can make all port connections at the appropriate time.

As we define new environments from pieces of existing IP and block- level components, we may find ourselves wanting to integrate the ovm_ip with a VMM xactor and enclose that in a VMM subenv IP block.  Such a pairing requires a connection from the ovm_ip’s analysis export to the analysis export of a <vmm_analysis_adapter>.  The adapter and VMM consumer will then share a vmm_channel to complete the connection.

But there’s one small catch.  The ovm_ip analysis port will not exist until after its build phase, which means we can not attempt an analysis port connection in the constructor of the VMM subenv.

The best approach is to enclose all sibling OVM components with a parent OVM component whose job is to build and connect the OVM components per the OVM use model.

The OVM wrapper, after making all OVM port connections between sibling components, would present only the vmm_channel connection to its VMM parent.

In this example, the wrapper will make the connection from the ovm_ip analysis port to the analysis2notify adapter’s analysis export during the connect phase, thus relieving the VMM parent from that responsibility.  As with most VMM components, the wrapper can either be assigned a channel via its constructor or provide the channel via its out_chan property.

class ovm_private_wrapper extends ovm_component;

  `ovm_component_utils(ovm_private_wrapper)

  ovm_ip o_ip;
  apb_analysis_channel v_ap_adapter;

  vmm_channel_typed #(vmm_apb_rw) out_chan;

  function new(string name,
               ovm_component parent=null,
               vmm_channel_typed#(vmm_apb_rw) out_chan=null);
    super.new(name,parent);
    this.out_chan = out_chan;
  endfunction

  virtual function void build();
    o_ip = new("o_ip",this);
    v_ap_adapter = new("v_ap_adapter", this, out_chan);
    if (out_chan == null)
      out_chan = v_ap_adapter.chan;
  endfunction

  virtual function void connect();
    o_ip.ap.connect(v_ap_adapter.analysis_export);
  endfunction

endclass

Step 3 - Define a VMM IP block - Part B

Then, define the VMM IP block, which instantiates the OVM wrapper and other VMM components.

In this example, we instantiate the VMM consumer and OVM wrapper.  We connect them by making sure they share the same vmm_channel.

In compliance with the VMM use model, the subenv builds itself solely in its constructor.  We also define the configure, start, stop, and cleanup methods, per methodology requirements.

When naming the VMM consumer and OVM wrapper, we include the instance name of the vmm_ip parent.  This ensures that any OVM sub-components all receive a unique hierarchical name.

We also employ the OVM set_config interface to map the max_trans VMM config parameter from the vmm_ip_cfg object to an OVM configuration parameter used by the underlying OVM ip.  Here, too, we prepend the vmm_ip’s instance name when making the call.

class vmm_ip_cfg;
  int max_trans=0;
endclass


class vmm_ip extends vmm_subenv;

  ovm_private_wrapper o_wrapper;
  vmm_consumer #(vmm_apb_rw) v_consumer;

  vmm_consensus end_vote;

  function new(string inst, vmm_ip_cfg cfg, vmm_consensus end_vote);

    super.new("vmm_ip", inst, end_vote);
    this.end_vote = end_vote;

    v_consumer = new({inst,".v_consumer"},0);
    o_wrapper  = new({inst,".o_wrapper"},,v_consumer.in_chan);

    v_consumer.stop_after_n_insts = cfg.max_trans;
    set_config_int({inst,".o_wrapper.o_ip"},"max_trans",cfg.max_trans);

    end_vote.register_notification(v_consumer.notify,v_consumer.DONE);

  endfunction

  task configure();
    super.configured();
  endtask

  virtual task start();
    super.start();
    v_consumer.start_xactor();
  endtask

  virtual task stop();
    super.stop();
  endtask

  virtual task cleanup();
    super.cleanup();
  endtask

endclass

Step 4 - VMM top-level

Let us now consider a vmm_env that contains our vmm_ip block and, optionally, other instances of OVM IP and VMM IP.  If our env had OVM children needing port connections, we would wrap them as we did for the ovm_private_wrapper above.

When we instantiate the VMM IP in the build method, we provide an instance name that includes the env’s name, which is retrieved from its log object.  We do this to ensure the embedded OVM IP gets unique name.

class tb_env extends vmm_env;

  vmm_ip_cfg cfg;

  vmm_ip v_ip;
  ovm_ip o_ip;
  //vmm_ip2 v_ip2;

  function new(string name="");
    super.new(name==""?"mixed_tb_env":name);
  endfunction

  virtual function void gen_cfg();
    super.gen_cfg();
    cfg = new;
    // we could randomize, but we won't
    cfg.max_trans = 10;
  endfunction

  virtual function void build();
    super.build();
    v_ip = new({log.get_name(),".v_ip"},cfg,end_vote);
    o_ip = new({log.get_name(),".env_o_ip"});
  endfunction

  virtual task cfg_dut();
    super.cfg_dut();
    v_ip.configure();
  endtask

  virtual task start();
    super.start();
    v_ip.start();
  endtask

  task wait_for_end();
    super.wait_for_end();
    fork
      end_vote.wait_for_consensus();
    join
    global_stop_request();
  endtask

endclass

Step 5 - OVM sub-component level

We can easily encapsulate our tb_env above for reuse as an ovm_component by extending a avt_ovm_vmm_env specialization, avt_ovm_vmm_env #(tb_env).

In this example, we wrap the tb_env to provide OVM users access to the env’s config object via the OVM configuration mechanism.

Our new ovm_subcomp can now be used like any other OVM component

class ovm_subcomp extends avt_ovm_vmm_env_named #(tb_env);

  `ovm_component_utils(ovm_subcomp)

  function new (string name="ovm_subcomp", ovm_component parent=null);
      super.new(name,parent);
  endfunction

  virtual function void vmm_gen_cfg();
    ovm_object obj;
    super.vmm_gen_cfg();
    if (get_config_object("cfg",obj,0)) begin
      ovm_container #(vmm_ip_cfg) v_cfg;
      assert($cast(v_cfg,obj));
      env.cfg = v_cfg.obj;
    end
  endfunction

endclass

Step 6 - OVM top-level

Let us now consider an ovm_env that contains our ovm_subcomp block and, optionally, other instances of OVM IP and VMM IP.  Because the parent (this) and child (subcomp) are both OVM components, we do not need to include the parent’s full name in the name we give the child.

class ovm_env extends ovm_component;

  `ovm_component_utils(ovm_env)

  ovm_subcomp subcomp;

  function new (string name="ovm_env", ovm_component parent=null);
    super.new(name,parent);
  endfunction

  virtual function void build();
    super.build();
    subcomp = new("subcomp",this);
  endfunction

endclass

Step 7 - Module

Although we can continue the sandwiching of OVM and VMM components indefinitely, we stop at this point by instantiating the ovm_env as a top-level component.  Future integrations could reuse the ovm_env as a child or grandchild of larger system-level environment, but we have to stop somewhere.

As part of configuration of the ovm_env, we use the OVM’s configuration mechanism to set the configuration object that will be used by the underlying vmm_subenv.  We also use it to set each generator’s max number of transactions.

module example_05_mixed_hierarchy;

  ovm_env top = new("top");

  vmm_ip_cfg v_cfg = new;
  ovm_container #(vmm_ip_cfg) cfg = new(v_cfg);

  initial begin

    // configure the vmm consumer in the vmm_subenv instance
    // to finish after 2 transactions.
    v_cfg.max_trans = 2;
    set_config_object("top.subcomp","cfg",cfg,0);

    // configure the vmm generator in the ovm_ip instance
    // in top.subcomp to finish in 2 transactions.
    set_config_int("top.subcomp.v_ip.o_wrapper.o_ip","max_trans",2);

    // configure embedded generator in ovm ip to
    // finish in 3 transactions
    set_config_int("top.subcomp.env_o_ip","max_trans",3);

    run_test();
  end

endmodule
class avt_channel2tlm #(
   type VMM_REQ =  int,
    OVM_REQ =  int,
    VMM2OVM_REQ =  int,
    OVM_RSP =  OVM_REQ,
    VMM_RSP =  VMM_REQ,
    OVM2VMM_RSP =  avt_converter #(OVM_RSP,VMM_RSP),
    OVM_MATCH_REQ_RSP = avt_match_ovm_id
) extends ovm_component
Use this class to connect a VMM producer to an OVM consumer.
class avt_ovm_vmm_env #(type ENV = vmm_env) extends avt_ovm_vmm_env_base
Use this class to wrap (contain) an existing VMM env whose constructor does not have a name argument.