VMM VIP’s on multiple buses
Posted by Adiel Khan on 27th May 2009
Adiel Khan, Synopsys CAE
Increasingly, more design-oriented engineers are writing VMM code. Some are trying to map typically good design architecture practices to verification development.
A dangerous mapping is parameterization, from modules to classes.
In my old Verilog testbenches I would develop reusable modules and use #parameters extensively to control the settings of the modules I was instantiating. (It was a sad day when I heard IEEE was deprecating my friend the defparam).
1. module vip #(parameter int data_width = 16,
2. parameter int addr_width = 16)
3. (addr, data);
4. output [addr_width-1:0] addr;
5. inout [data_width-1:0] data;
6. …
7. endmodule
This would allow me to instantiate this VIP for many bus variants.
8. vip #(64, 32) vip_inst1(…);
9. vip #(32, 128) vip_inst2(…);
Mapping the approach from modules to classes, I could end up with:
1. class pkt_c #( parameter int data_size=16,
2. parameter int addr_size=16)
3. extends vmm_data;
4. rand bit [addr_size-1:0] addr;
5. rand bit [data_size-1:0] data;
6. …
7. endclass
8. //specialized class with 64 & 32 sizes
9. pkt_c #(64, 32) pkt1=new();
10. //specialized class with 32 & 128 sizes
11. pkt_c #(32, 128) pkt2=new();
Be warned, in the SystemVerilog testbench centric view of VIP reusability, parameterization of classes leads to a dead-end path. Moving one layer of abstraction up, I really don’t care if it is a 32/64/128 bits wide interfaces. What I want to do is use pkt_c around the verification environment. The simplest case is creating a reusable driver using pkt_c to drive any bus-width interface.
However, if I try to use a generic class instantiation, I will get a specialization with parameters = 16&16. I cannot perform the $cast() to put the right pkt_c type onto the bus.
1. class pkt_driver_c extends vmm_xactor;
2. virtual protected task main();
3. forever begin : GET_OBJ_TO_SEND
4. pkt_c pkt_to_send; //default class instance
5. pkt_c #(64, 32) pkt_created;
6. randomize(pkt_created);// generator code
7. $cast(pkt_to_send, pkt_created); //FAILS !!!!!
If you are using VMM channels, they must similarly be specialized and cannot carry generic parameterized classes:
8. `vmm_channel(pkt_c)
9. class pkt_driver_c extends vmm_xactor;
10. pkt_c_channel in_chan; //Can only carry pkt_c#(16,16)!!!
Or you must upfront select which specialization you want for use with a parameterized channel.
8. class pkt_driver_c extends vmm_xactor;
9. vmm_channel_typed #(pkt_c#(64, 32)) in_chan;
Hence, for the driver to operate on the correct object type, I need to instantiate the exact specialization throughout my entire environment and make the driver itself parameterized. Now you can clearly see instantiating a specific specialization in the driver (or monitor, scoreboard etc) stops the code from being really reusable for other bus_widths.
1. pkt_c #(64, 32) pkt_to_send;
2. pkt_driver_c #(64, 32) driver;
A better approach is one that was described by Janick in the “Size Does Matter” blog of using `define. Let’s expand on this and see how it works for reusable VIPs. Well, the first thing that comes to my mind is that a `define is a global namespace macro with a single value, whereas I am using my VIP with 2 different bus architectures. Therefore, the `define alone is not enough: you also need a local constant to be able to exclude unwanted bits when you have a VIP instantiated for various bus widths.
1. //default define values
2. `define MAX_DATA_SIZE 16
3. `define MAX_ADDR_SIZE 16
4. class pkt_c extends vmm_data;
5. static vmm_log log = new(“Pkt”, “class”);
6. //instance constant to control actual bus sizes
7. const int addr_size;
8. logic [`MAX_ADDR_SIZE:0] addr;
9. logic [`MAX_DATA_SIZE:0] data;
10. // pass a_size as arg to coverage
11. // ensuring valid coverage ranges.
12. covergroup cg (int a_size);
13. coverpoint addr
14. {bins ad_bin[] = {[0:a_size]};}
15. endgroup
16. // sizes specialized at construction for pkts
17. // on buses less than MAX bus widths
18. function new(int a_s=`MAX_ADDR_SIZE);
19. addr_size = a_s;
20. cg = new(addr_size);
21. `vmm_note(log, $psprintf(“\nADDR_TYPE: “,$typename(addr),
22. “\nDATA_TYPE: “,$typename(data),
23. “\nMAX_BUS_SIZE: “, addr_size));
24. endfunction
The code above allows for a default implementation and all the user needs to do is set the `MAX_ADDR_SIZE and `MAX_DATA_SIZE symbols and all the code will be fully reusable across drivers, monitors, subenv, SoC etc.
For situations where two VIP’s of differing bus architectures are used, the compiler symbols need to be set to the biggest bus architecture in the system; smaller bus-widths are set using addr_size. It is not necessary for addr_size variable to be an instance constant or set during construction. By using instance constants, this ensures the bus-widths are not changed at runtime by users. Having the value of addr_size set during construction gives the users the flexibility to setup the object as they want. For pseudo-static objects such as drivers, monitors, subenvs, masters, slaves, scoreboards etc you should check the construction of verification modules for your particular design architecture during the vmm_env::start phase.
N.B not shown above, but assumed, is that the addr_size variable would be used to ensure correct masking occurs when performing do_pack(), do_unpack() compare() etc.
Just to wrap up some loose ends…
I’m not totally discounting the merits of parameterized classes just insuring people look at all the options. For instance you could parameterize everything and then set the SIZE at the vmm_subenv level and map the SIZE parameters to all other objects. At some point you will want to monitor or scoreboard across different bus-widths and then the parameterized class casting will bite you, reducing you to manually mapping the members within the comparison objects. There is a time and place for everything, so probably need another blog showing merits and where to use parameterized classes.
The vmm_data class is not the only place you might need to know the size of the bus, the same `define & instance constant technique can be used throughout your VIP classes.
This blog does not discuss the pros and cons of putting coverage groups in your data object class. I merely used the covergroup in the data-object as a vehicle to demonstrate how you can make your classes more reusable. I think a separate blog about where best to put coverage will clarify the usage models.
All the code snippets can be run with VCS-2009.06 & VMM1.1. Contact me for more complete code examples and bugs or issues you find.
Posted in Coding Style, Configuration, MODs, Register Abstraction Model with RAL, Reuse, Structural Components, VMM | 8 Comments »