SystemVerilog: How To Hide Your Fields So That Anyone Can Find Them

SystemVerilog: How To Hide Your Fields So That Anyone Can Find Them

Posted by

In this post I will talk about how I am using the OOP concept of Variable Shadowing to make the user interaction with VIPs much easier.

Every now and then I am asked to build an agent to be used by the entire team for some communication protocol, like the interface for accessing the registers of any module in a system.

First, I start from a very generic agent which implements all the common functionality of an agent (architecture, port connectivity, reset handling etc). You can take a look at such a generic agent I’ve implemented a couple of year ago here.
The classes of the actual agent I need to implement will extend from the generic agent classes. This saves me time for writing common agent code over and over again.

Let’s look at two components of this generic agent: agent configuration class and agent class.

The agent configuration class has some fields which are pretty common for any agent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//agent configuration class
class agt_agent_config #(type VIRTUAL_INTF_TYPE=int) extends uvm_component;
   ...
   //switch to determine the active or the passive aspect of the agent
   protected uvm_active_passive_enum active_passive;

   //switch to determine if to enable or not the coverage
   protected bit has_coverage;

   //switch to determine if to enable or not the checks
   protected bit has_checks;
   ...
   `uvm_component_param_utils(agt_agent_config#(VIRTUAL_INTF_TYPE))
   ...
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction
   ...
endclass

The agent class creates the actual instance of the agent configuration and makes use of some fields declared in the agent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//agent class
class agt_agent #(
      type VIRTUAL_INTF_TYPE=int,
      type MONITOR_ITEM=uvm_object,
      type DRIVER_ITEM_REQ=uvm_sequence_item,
      type DRIVER_ITEM_RSP=DRIVER_ITEM_REQ) extends uvm_agent;

   //agent configuration
   agt_agent_config#(VIRTUAL_INTF_TYPE) agent_config;

   ...
   
   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      ...
      agent_config = agt_agent_config#(VIRTUAL_INTF_TYPE)::type_id::create("agent_config ", this);
      ...
      if(agent_config.get_active_passive() == UVM_ACTIVE) begin
         driver = agt_driver#(VIRTUAL_INTF_TYPE, DRIVER_ITEM_REQ, DRIVER_ITEM_RSP)::type_id::create("driver", this);
         sequencer = agt_sequencer#(DRIVER_ITEM_REQ, DRIVER_ITEM_RSP)::type_id::create("sequencer", this);
      end

      if(agent_config.get_has_coverage() == 1) begin
         coverage = agt_coverage#(VIRTUAL_INTF_TYPE, MONITOR_ITEM)::type_id::create("coverage", this);
      end
      ...
   endfunction
   ...
endclass

This is pretty common code for any agent so it makes sense to have it written only once and reuse it over and over again.

Now let’s take a look at how the implementation for some specific protocol looks like.
First, there is the specific agent configuration class with the fields relavant for the physical protocol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class cfs_ahb_agent_config extends agt_agent_config #(cfs_ahb_vif_t);
   ...
   //Data bus width
   protected int unsigned data_width;
   ...
   `uvm_component_utils(cfs_ahb_agent_config)

   function new(string name = "", uvm_component parent);
      super.new(name, parent);
      ...
      data_width = 32;
   endfunction
   ...
endclass

In the agent class I am making use of UVM factory override to replace the type of agent configuration class:

1
2
3
4
5
6
7
8
9
10
11
12
13
class cfs_ahb_agent extends agt_agent#(
         .VIRTUAL_INTF_TYPE(cfs_ahb_vif_t),
         .MONITOR_ITEM(cfs_ahb_mon_item),
         .DRIVER_ITEM_REQ(cfs_ahb_drv_item));
   ...
   `uvm_component_utils(cfs_ahb_agent)
   
   function new(string name = "", uvm_component parent);
      super.new(name, parent);
      agt_agent_config::type_id::set_inst_override(cfs_ahb_agent_config::get_type(), "agent_config", this);
      ...
   endfunction
endclass

At this point I can safely deliver the agent to all the users in the team.

However, over the time I noticed a common complain from the users of my agents written in this way.

Here is a very common declaration of the agent and some configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class acme_env extends uvm_component;
   ...
   //Handler to the AHB agent
   cfs_ahb_agent ahb_agent;
   ...
   `uvm_component_utils(acme_env)

   virtual function void connect_phase(uvm_phase phase);
      super.connect_phase(phase);
      ...
      //this line works OK
      ahb_agent.agent_config.set_has_checks(1);
     
     //this line gives a compilation error
     ahb_agent.agent_config.set_data_width(16);
     ...
   endfunction
   ...
endclass

The problem with line 15 from above it that the declaration of agent_config is of type agt_agent_config#(cfs_ahb_vif) and only through UVM factory override it is allocated to a type of cfs_ahb_agent_config. This means that accessing any AHB specific configuration must be done after casting.

It is too annoying to have to do type casting every time I want to configure something for the AHB agent!

So my initial solution was to create a reference in the AHB agent of correct type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class cfs_ahb_agent extends agt_agent#(
         .VIRTUAL_INTF_TYPE(cfs_ahb_vif_t),
         .MONITOR_ITEM(cfs_ahb_mon_item),
         .DRIVER_ITEM_REQ(cfs_ahb_drv_item));
   ...
   cfs_ahb_agent_config casted_agent_config;
   ...
   `uvm_component_utils(cfs_ahb_agent)
   
   function new(string name = "", uvm_component parent);
      super.new(name, parent);
      agt_agent_config::type_id::set_inst_override(cfs_ahb_agent_config::get_type(), "agent_config", this);
      ...
   endfunction
   ...
   virtual function void connect_phase(uvm_phase phase);
      super.connect_phase(phase);
      ...
      if($cast(casted_agent_config, agent_config) == 0) begin
         `uvm_fatal("ALGORITHM_ISSUE", "Could not cast")
      end
      ...
   endfunction
endclass

Now the user code would simply work like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class acme_env extends uvm_component;
   ...
   //Handler to the AHB agent
   cfs_ahb_agent ahb_agent;
   ...
   `uvm_component_utils(acme_env)

   virtual function void connect_phase(uvm_phase phase);
      super.connect_phase(phase);
      ...
      ahb_agent.agent_config.set_has_checks(1);
     
     //AHB specific fields can be accessed through the new pointer
     ahb_agent.casted_agent_config.set_data_width(16);
     ...
   endfunction
   ...
endclass

That was a step forward but still, the general feedback I’ve got was that it was confusing to have agent_config and casted_agent_config. Some simulators will show in the UVM tree only the actual component name: “agent_config”. When to use agent_config and when to use casted_agent_config?

So at this point I realized that I can simply use the same name for my casted agent configuration declaration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class cfs_ahb_agent extends agt_agent#(
         .VIRTUAL_INTF_TYPE(cfs_ahb_vif_t),
         .MONITOR_ITEM(cfs_ahb_mon_item),
         .DRIVER_ITEM_REQ(cfs_ahb_drv_item));
   ...
   cfs_ahb_agent_config agent_config;
   ...
   `uvm_component_utils(cfs_ahb_agent)
   
   function new(string name = "", uvm_component parent);
      super.new(name, parent);
      agt_agent_config::type_id::set_inst_override(cfs_ahb_agent_config::get_type(), "agent_config", this);
      ...
   endfunction
   ...
   virtual function void connect_phase(uvm_phase phase);
      super.connect_phase(phase);
      ...
      if($cast(agent_config, super.agent_config) == 0) begin
         `uvm_fatal("ALGORITHM_ISSUE", "Could not cast")
      end
      ...
   endfunction
endclass

Until now I always payed attention to not have fields with the same name in the child class as in the parent class but for this use-case this works great. The user code gets simpler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class acme_env extends uvm_component;
   ...
   virtual function void connect_phase(uvm_phase phase);
      super.connect_phase(phase);
      ...
      //same as before
      ahb_agent.agent_config.set_has_checks(1);
     
     //this agent config field is the one declared in the AHB agent
     //but the user should not care about this
     ahb_agent.agent_config.set_data_width(16);
     ...
   endfunction
   ...
endclass

This technique of declaring two fields with the same name in parent and child class is called Variable Shadowing. So far I tried to stay away from it but for this scenario I think it works quite well.

Let me know your thoughts on the subject in a comment bellow.
Have you aver used variable shadowing in your verification environment? In which scenarios this was useful for you?

Cristian Slav

2 Comments

  • tudor.timi@verificationgentleman.com' Tudor Timi says:

    Better advice: avoid fields altogether.

    Instead of a public field for the agent config, you could use a public function to return it: ‘virtual function base_config config()’. In the extended class you can use covariant return types to change the return type to ‘virtual function extended _config config()’. This avoids the data hiding issue.

Leave a Reply

Your email address will not be published.