# 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 environment | We 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 block | At 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 A | First 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 B | Then, define the VMM IP block, which instantiates the OVM wrapper and other VMM components.
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.
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).
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.
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.

### 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.