How to integrate the interface of AHB VIP into SoC level?

Not sure whether this question is suitable for this forum or not but if not please let me know I will remove it.

Most examples of the UVM that I have been studying are built with a simple component. So I could make an interface, driver, sequencer and also I could declare an interface as “virtual add_sub_if m_if;” for a DUT interfacing such as Simple UVM Testbench - EDA Playground .
Fortunately, I found AHB VIP from AHB verification using UVM | Verification Academy someone introduce his AHB VIP. I’d like to use this AHB VIP for test my SoC DUT.

Now I’m trying to understand it on the SoC level and I found most of them on the BUS line such as AXI, AHB… So when I check them for making an interface to integrate UVM components such as my sequencer, driver,…,.
I couldn’t find where to integrate the AHB interface with SoC DUT.

For example, let’s say there is one Cortex m3 CPU, Multi layer AHB matrix and 3 slave. They are on the AHB bus. That CPU works with small GPIO control firmware. That master and slave are connected by AHB BUS Protocol signals correctly means that there is nowhere to interface with my UVM Testbench with AHB VIP.

I was thinking of that I can make a UVM Testbench for each SoC component separately such as just for one master or for one slave like this then I can build up my UVM Testbench for each one component. But it can’t test at SoC level. So, If I want to test master and slaves with VIP in a SoC level, How do I integrate AHB VIP on my SoC architecture?

In reply to UVM_LOVE:

When you reuse a block-level environment in an SOC environment, your agents are changed to passive mode since the stimulus will be generated from another source (CPU, Agent acting as CPU, etc).

You will instantiate the agent’s interface in the testbench and assign the signals from the DUT’s connections where appropriate.

In reply to cgales:

If the CPU or Agent acting like a CPU generates the stimulus, then I think there’s not much chance to work from my reused block-level.
So I’m wondering how you suggest to engineers such as my case?

In reply to UVM_LOVE:

You absolutely can (and should) reuse a block-level environment in a system-level environment.

The function of an block-level environment is to verify that the functionality of the block using monitors, checkers and scoreboards. You want to encapsulate a block-level environment into your system-level environment so that you can reuse this verification capability.

The function of a test is to generate stimulus by running sequences on agent sequencers. With properly written block-level test sequences (using the RAL), these test sequences can easily be reused by the system-level environment.

Even if you change your stimulus approach by using a real CPU and code, you still make use of the functionality checking provided by the block-level environment.

In reply to cgales:

Hi cgales ,

I was wondering how the interface would be passed to the block-level monitor when re-using block-level environment in system-level environment .

Typically at block - level the TB would instantiate the Interface , connect it’s instance to the DUT , set it as virtual interface to config_db to be fetched via components .

At system-level the interface would be already present , so how would the monitor be able to fetch it ?

Also how would it set as virtual interface at system-level ?

In reply to Have_A_Doubt:

To facilitate reuse, the following requirements should be used:

  • Each agent shall have a corresponding configuration object which contains the handle to the required virtual interface.
  • Each environment shall have a corresponding environment configuration object which contains the required agent configuration object(s).
  • Each environment will use the config_db to retrieve it’s configuration object. The environment will then utilize the config_db to pass the agent’s configuration object to the agent.
  • Any upper-level environment configuration object will contain sub-environment configuration objects.

To use this structure:

  • The top-level TB instantiates all required interfaces and connects them to the DUT at the appropriate locations (direct port connections, assigns, bind statements, etc).
  • The top-level TB adds all interfaces to the config_db, using a unique name for each instance and targeting ‘uvm_test_top’.
  • The test will create the top-level environment configuration object, which will recursively create all sub-configuration objects.
  • The test will retrieve all interface handles into each agent’s configuration object.
  • The test will use the config_db to pass the environment configuration to the environment.
  • The environment will retrieve it’s configuration object and in turn pass the sub-environment or agent configuration objects as required.

In reply to cgales:

The top-level TB instantiates all required interfaces and connects them to the DUT at the appropriate locations (direct port connections, assigns, bind statements, etc).

I am still a little confused about this .

Here is my interpretation ::


//  block_top_tb where Agent Works in Active Mode i.e Driver-Sequencer pair are present 
    
     apb_intf   intf  ( clk , reset ) ; // Driven  via  Driver  at  Block-level

     Block_DUT  block_top ( intf ) ;

     initial  begin
// Fetched in  Test where Agent config object is created and assigned to Env's config  object  
     uvm_config_db#( virtual apb_intf ) :: set( null , "uvm_test_top" , "agent_vif_intf" , intf );

        run_test("sanity_test"); 

      end

 //  Within  Monitor  at  block-level
   
     virtual  apb_intf   vif_intf  ; //  Agent could directly assign it after creating monitor component

     //  [ OR ]
      
     //  Within  any  phase  prior  to  run-time  phase   ::
     
//  Agent  first  fetches  it's  Config  and  then  sets  it  again  via  "mon_vif_intf" for  the  monitor  to fetch  it  ::  
    uvm_config_db#( virtual apb_intf ) :: get( this , "" , "mon_vif_intf" , vif_intf );
 

Then when trying to re-use at system-level where the Block-level’s Agent will work in Passive Mode ::


 //  Within  System-level  top_tb ::

  axi_top     system_intf  ( clk , reset ) ; // Driven  via  Driver  at  System-level

  system_top   chip_top( axi_top );


Now ’ Block_DUT ’ would exist multiple levels of hierarchy below ’ system_top ’

Eg : top_tb.chip_top.subsystem_a.block_a where the interface signals would be connected during instantiation of block_a ( block_a is instance name of Block_DUT within the System-level DUT )

So how should we set the virtual interface apb_intf for the passive agent to fetch it via it’s Config object ?

The following comes to my mind ::


   //   Within  System-level  top_tb  create  an  additional  instance  of  apb_intf  ::
       
       assign  blk_clk =  top_tb.chip_top.subsystem_a.block_a.clk ;

       assign  blk_rst =  top_tb.chip_top.subsystem_a.block_a.rst ;

       apb_intf  intf_block ( blk_clk , blk_rst );
       
     initial  begin

       force  intf_block.sig1  = top_tb.chip_top.subsystem_a.block_a.sig1 ;
       force  intf_block.sig2  = top_tb.chip_top.subsystem_a.block_a.sig2 ;
       force  ...............  =  ....................................... ;
     
      end

      initial  begin

uvm_config_db#( virtual apb_intf ) :: set( null , "uvm_test_top" , "block_vif_intf" , intf_block );
 
       end
      
 

Would there a better solution possible using bind construct ?

In reply to Have_A_Doubt:

It appears that you are using interfaces as port connections in your RTL. If this is the case, it is highly recommended that you use an RTL interface for the design, and a separate verification interface for the UVM agents. The reason for this is that the RTL interface acts as a simple wire bundle, while the verification interface will include all of your BFM tasks/functions which aren’t synthesizable.

In the reuse case, where you want to reuse the apb agent, your system-level top_tb would look like:


  apb_intf intf(chip_top.subsystem_a.block_a.clk,
                chip_top.subsystem_a.block_a.reset);
  // Assign DUT signals to apb_intf. Note this is monitor only
  assign intf.SIGNAL_A = chip_top.subsystem_a.block_a.SIGNAL_A; 
  assign intf.SIGNAL_B = chip_top.subsystem_a.block_a.SIGNAL_B; 

  axi_intf system_intf( clk , reset );
 
  system_top   chip_top( system_intf );

  initial begin
    // Fetched in  Test where Agent config object is created and assigned to Env's config  object  
    uvm_config_db#( virtual apb_intf ) :: set( null , "uvm_test_top" , "apb_intf_name" , intf );
    uvm_config_db#( virtual axi_intf ) :: set( null , "uvm_test_top" , "axi_intf_name" , system_intf );
    run_test("sanity_test"); 
  end

You should only get() the virtual interface handles in the test as it will know the mapping of VIFs to agents. Agents should only get() their associated configuration object which contains the required VIF handle and never get a VIF handle from the config_db directly.

In reply to cgales:

Thanks cgales .

it is highly recommended that you use an RTL interface for the design, and a separate verification interface for the UVM agents.

(1) Do you mean we should define 2 interfaces ::


     interface  apb_intf  (  input  clk , reset ) ; // RTL interface
         wire  [ 2:0 ] siga ;
         wire  [ 1:0 ] sigb ;
         ....................
     endinterface

     interface  verif_apb_intf  (  input  clk , reset ) ; //  Separate  verif interface
          wire  [ 2:0 ] siga ;
          wire  [ 1:0 ] sigb ;
         ....................
           //  Additional  task and  function  definitions 
     endinterface
     
 Since  these  are  **2 separate non-compatible type interface**, 

how would we connect the RTL interface ( apb_intf ) signals with the separate verification interface ( verif_apb_intf ) signals ?

 i.e  In  Block-level's  Top_tb  we  would  instantiate  RTL  interface ' apb_intf ' and  connect  it  to  port  of  DUT's  instance 

     However  we  would  need  to  set  the  **separate  verification  interface**  via  config_db . 

Eg :  config_db #( virtual verif_apb_intf ) :: set ( null , "uvm_test_top"  , "apb_intf_name" , ?? ); 
     What  would  the  4th  argument  be  to  call  to  set  ?

Would there a better solution possible using bind construct ?

(2) Using bind construct what if I were to try ::


   //  Assume  Block-level  DUT  is  defined  as  ::  

       module Block_DUT ( apb_intf  intf ) ;
           ...............
       endmodule
         
 interface  verif_apb_intf  (  input  clk  ,  reset );  //  Separate  Verification  Interface 

       wire  [ 2:0 ] siga ;
       wire  [ 1:0 ] sigb ;

      //  Using  "hierarchical upward name reference"  as  discussed  with  Dave  ::
     //   verificationacademy.com/forums/systemverilog/assigning-module-variables-interface                                 
 
       assign  siga  =  Block_DUT.intf.siga ; 
       assign  sigb  =  Block_DUT.intf.sigb ; 
       .....................................

  endinterface

   Then  within  System-level  top_tb  we  simply  write  :: 


      axi_top     system_intf  ( clk , reset ) ; // Driven  via  Driver  at  System-level
 
      system_top  chip_top( axi_top );
      
      //  intf.clk  and  intf.reset  are  Interface  Signals  of  Block_DUT  ::

      bind  Block_DUT  verif_apb_intf  bind_inst ( intf.clk  , intf.reset ) ;

      initial  begin
     
       //  One  inconvenience  is  to  pass  full  hierarchy  of  Interface  ::

uvm_config_db#( virtual verif_apb_intf ) :: set( null , "uvm_test_top" , "block_vif_intf" , chip_top.subsystem_a.block_a.bind_inst );
 
       end    

Would it be a valid alternative ( instead of instantiating apb_intf in System-level Tb ) ?

In reply to Have_A_Doubt:

There should be complete separation between design RTL code and verification code. An interface is required for verification, but is optional for RTL. If an interface is used in RTL (instead of individual signals), it should be an RTL only interface with the appropriate modports.

Where you instantiate the verification interface depends on your design. You can instantiate it at the top-level testbench and connect it to the top-level ports of the DUT, or use assign statements to connect lower-level signals. You can also use a bind construct if your design supports it, and when adding to the config_db(), you use the hierarchical path to the interface instance.