In this post I’ll show how to avoid parameter creep when writing parameterizable agents and interfaces. As an example, let’s consider a hypothetical protocol which is used to send a number of data items to a certain address. Here it is a short excerpt from the agent, which has three parameters:
class my_agent#(parameter addr_width = 1, parameter data_width = 1, parameter payload_length = 1) extends uvm_agent;
`uvm_component_param_utils(my_agent#(addr_width, data_width, payload_length))
virtual my_interface#(addr_width, data_width, payload_length) vif;
my_driver#(addr_width, data_width, payload_length) driver;
................................
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual my_interface#(addr_width, data_width, payload_length))::get(this, "", "if", vif))
`uvm_fatal(get_name(), "Error retrieving the virtual interface handle!")
driver = my_driver#(addr_width, data_width, payload_length)::type_id::create("driver", this);
................................
endfunction
endclass
Try to imagine how the code will look like if the number of parameters would increase to 10 or 15! The bigger the number of parameters, the harder it will be to read and maintain the code.
Solution
To avoid the problem, a single packed structure can be used as a parameter instead of a large number of different parameters. A possible implementation of the structure is the following:
typedef struct packed {
byte unsigned addr_width;
byte unsigned data_width;
} layer1_t;
typedef struct packed {
int unsigned payload_length;
} layer2_t;
typedef struct packed {
layer1_t layer1;
layer2_t layer2;
} my_config_t;
We can now create a few configurations, which can be used for parameterizing the agents and the interfaces:
parameter my_config_t cfg_a = '{ '{ addr_width: 4, data_width: 8 }, '{ payload_length: 2 } };
parameter my_config_t cfg_b = '{ '{ addr_width: 8, data_width: 16 }, '{ payload_length: 4 } };
The interface and the sequence item can then be implemented. Note that a default value for the configuration must be supplied for the interface and for any parameterized class definition that is related to the agent.
interface my_if#(parameter my_config_t cfg = cfg_a) (input logic clk);
logic valid;
logic [cfg.layer1.addr_width-1:0] addr;
logic [cfg.layer1.data_width-1:0] data;
endinterface
class my_packet#(parameter my_config_t cfg = cfg_a) extends uvm_sequence_item;
`uvm_object_param_utils(my_packet#(cfg))
rand bit [cfg.layer1.addr_width-1:0] addr;
rand bit [cfg.layer1.data_width-1:0] payload[cfg.layer2.payload_length];
function new(string name = "");
super.new(name);
endfunction
endclass
The code for the agent is much cleaner. If more configuration options are needed, the structure has to be updated, but no changes will be required in the agent.
class my_agent#(parameter my_config_t cfg = cfg_a) extends uvm_agent;
`uvm_component_param_utils(my_agent#(cfg))
virtual my_if#(cfg) vif;
my_driver#(cfg) driver;
................................
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual my_if#(cfg))::get(this, "", "if", vif))
`uvm_fatal(get_name(), "Error retrieving the virtual interface handle!")
driver = my_driver#(cfg)::type_id::create("driver", this);
................................
endfunction
endclass
Using the agent is straightforward. First, it needs to be created:
my_agent#(cfg_b) agent_b;
................................
agent_b = my_agent#(cfg_b)::type_id::create("agent_b", this);
Then, the interface must be instantiated and set in the UVM config db:
my_if#(cfg_b) if_b(clk);
initial
uvm_config_db#(virtual my_if#(cfg_b))::set(null, "*agent_b*", "if", if_b);
Finally, a sequence can be started on the agent’s sequencer:
my_sequence#(if_cfg_b) seq_b = my_sequence#(if_cfg_b)::type_id::create("seq_b");
seq_b.start(agent_b.sequencer);
Comparison with Other Methods
Here it is a comparison between the classic way (using multiple parameters), the method I propose in this article and the accessor-class based solution.
Feature name | Multiple parameters method | Proposed method | Accessor class method | |
---|---|---|---|---|
1. | Simulation speed impact | None | None | Requires config_db accesses |
2. | Impact of adding/removing parameters | Very High | Low | Medium |
3. | Support for arbitrary sized data | Yes | Yes | No |
4. | Effort to add support for protocol layering | High | Low | Medium |
5. | Debugging effort | Hard | Easy | Medium |
6. | Requires proxy-entities | No | No | Yes |
As you can see, there are a few advantages when writing the agent and the interface using the proposed method:
1. Any of the agent’s parameterized classes has direct access to all configuration options, eliminating the need of accessing the UVM config db, which may affect the simulation speed. In contrast, the accessor class method needs a configuration object which is retrieved from the UVM config db.
2. Adding or removing configuration options will only require updating the structure and the actual piece of code which uses these configuration options. The accessor class method also requires updates to the accessor class. For the multiple parameters method, updates are needed throughout the environment, wherever the parameters are used.
3. No maximum size is required for the bus, allowing future updates. In comparison, the accessor class method has a maximum bus width defined in the accessor class which will need to be changed.
4. Protocol layering can be supported by nesting structures, allowing a clear separation between the physical bus properties and the packets’ structure. This is hard to do for the multiple parameters method, where the layer separation can’t be easily determined.
5. Errors are easy to spot: trying to drive a packet having the wrong configuration will result in a compilation error. When using the accessor class method, these checks are only performed at runtime. For the multiple parameters method a compilation error will also be triggered, but the large number of parameters can make debugging a hopeless task.
6. No proxy entities (such as accessor classes) are needed, reducing the necessary amount of code.
The Complete Code Example
If you want to run a simulation, you can download a complete working example from article’s GitHub repository.
Read, post and discuss to keep the community alive!
7 Responses
Great method, Horia-Răzvan.
Thanks for the tip!
Thank you, I’m glad you find it useful!
Great! I have been researching all the possible methods and yours seem to be the best. You should also try contacting Dave Rich about his inputs on this method (he published more elaborate ways to do the same). You can also publish this as a small paper in DVCON.
Hi Kaushik, I’m glad it helped you! I will consider your suggestions.
Hi Horia Razvan,
Thanks This was helpful.
I would also like to know how to set multiple instances of the interface to each agent.
For example arb_req_agent_0 has req
So in the top module I have
genvar i;
generate
for(i=0;i<= `NUM_INST i++) begin: req_inst
arb_req_if req_if();
initial begin
uvm_config_db#(virtual arb_req_if)::set(uvm_root::get(),*,$sformatf(req_if_%0d,i),req_if);
end
end
endgenerate
1.Now I want that each agent has access to its particular interface. For example arb_req_agent_0 has access to req_if_0 and so on. How do I modify my path in the * portion of the config db to achieve this? Instead of * can i write $sformatf("*.arb_req_agent_%0d",i) to set the interface for the proper agent.
Note that in the env I have the agents created arb_req_agent_%0d
2. How do I pass each interface to each agent created
Hi Roopa,
Please check again the code example on the GitHub repository, which is now updated with an array of agents and their corresponding interfaces.
The changes required for an array of agents are minimal:
Hi Horia-Răzvan Enescu,
I used similar method in my TB but i’m dealing with a case where I’m having to maintain an array of agents with different parameterizations. This method doesn’t work in that case. I have some ideas of my own but wondering your take on this problem.