Verification Martial Arts: A Verification Methodology Blog

Archive for the 'Coding Style' Category

Using vmm_opts to create a configurable environment

Posted by S. Prashanth on 19th July 2010

S. Prashanth, Verification & Design Engineer, LSI Logic

To accommodate changing specifications and to support different clusters/subsystems which would have multiple processors/memory connected through a bridge), I am building a reusable environment which can support any number of masters and slaves of any standard bus protocols.  The environment requires high level of configurability since it should work for different DUTs. So, I decided to use vmm_opts not just to set the switches globally/hierarchically, but also to specify the ranges (like setting address ranges in scenarios) from the test cases/command line.

I created a list of controls/switches that need to be provided as global options (like simulation timeout, scoreboard/coverage enable, etc)  and hierarchical options  to control instance specific behaviors (like number of transactions to be generated ,  burst enables, transaction speed, address ranges to be generated by a specific instance, set of instances, etc).  Let’s see how these options can be used and what all things are required for it through examples.

Global Options

Step 1:
Declare an integer, say simulation_timeout in the environment class or wherever it is required and use vmm_opts::get_int(..) method as shown below. Specify a default value as well just in case, if the variable is not set anywhere.

simulation_timeout = vmm_opts::get_int(“TIMEOUT”, 10000);

Step 2:
Then either at the test case or/and at the command line, I can override the default value using vmm_opts::set_int  or +vmm_opts+ runtime option.

Override from the test case.

vmm_opts::set_int(“%*:TIMEOUT”, 50000);

Override from the command line.

./simv +vmm_opts+TIMEOUT=80000

Options can also be overridden from a command file, if it is difficult to specify all options in the command line. Also, options can be a string or Boolean as well.

Hierarchical Options

In order to use hierarchical options, I am building the environment with parent/child hierarchy which is a very useful feature of vmm_object. This is required since the hierarchy specified externally (from test case/command line) will be used to map the hierarchy in the environment.

Step 1:
Declare options, say burst_enable and num_of_trans in a subenv class and use vmm_opts::get_object_bit(..) and vmm_opts::get_object_int(..) to retrieve appropriate values passing current hierarchy and default values as arguments. Also, for setting ranges, declare min_addr and max_addr, and use get_object_range().

class master_subenv extends vmm_subenv;
bit burst_enable;
int num_of_trans;
int min_addr;
int max_addr;

function void configure();
bit is_set; //to determine if the default value is overridden
burst_enable = vmm_opts::get_object_bit(is_set, this, “BURST_ENABLE”);
num_of_trans = vmm_opts::get_object_int(is_set, this, “NUM_TRANS”, 100);
vmm_opts::get_object_range(is_set, this, “ADDR_RANGE”, min_addr, max_addr, 0, 32’hFFFF_FFFF);
….
endfunction

endclass

Step 2:
Build the environment with parent/child hierarchy either by passing the parent handle through the constructor to every child, or using vmm_object::set_parent_object() method. This is easy as almost all base classes are extended from vmm_object by default.

class  dut_env extends vmm_env;
virtual function build();
…..
mst0 = new(“MST0”, ….);
mst0.set_parent_object(this);
mst1 = new(“MST1”, …);
mst1.set_parent_object(this);
….
endfunction
endclass

Step 3:
From the test case, I can override the default value using vmm_opts::set_bit or vmm_opts::set_int(..) specifying the hierarchy. I can use pattern matching as well to avoid specifying to every instance

vmm_opts::set_int(“%*:MST0:NUM_TRANS”, 50);
vmm_opts::set_int(“%*:MST1:NUM_TRANS”, 100);
vmm_opts::set_bit(“%*:burst_enable);
vmm_opts::set_range(“%*:MST1:ADDR_RANGE”, 32’h1000_0000, 32’h1000_FFFF);

I can also override the default value from the command line as well.
./simv +vmm_opts+NUM_TRANS=50@%*:MST0+NUM_TRANS=100@%*:MST1+burst_enable@%*


In summary, vmm_opts provides a powerful and user friendly way of configuring the environment from the command line or the test case. User can provide explanation of each of the options while calling get_object_*() and get_*() methods which will be displayed when vmm_opts::get_help() is called.

Posted in Coding Style, Configuration, Tutorial | No Comments »

VMM data macros are cool, but how do I customize the constructor?

Posted by Shankar Hemmady on 27th August 2009

Srinivasan VenkataramanPawan BellamkondaSrinivasan Venkataramanan, CVC

Pawan Bellamkonda, Brocade

During our recent VMM training at CVC, we learned about VMM data member macros, and our engineers liked it. Some of our teams at Brocade have started adopting it in their projects right away! We see that we can avoid much of the lengthy code and increase readability with these new macros. We will surely avoid making silly mistakes which might be hard to debug later.

However as with any built-in automation, there are always scenarios where-in user level customization of some or all of the methods is required. VMM provides this flexibility for overriding the default behavior of virtual methods of vmm_data class. In one of our blocks we needed to tweak the constructor of the transaction. One question that perplexed us was:

“I have built a transaction class extending from vmm_data. We have used the short hand macro `vmm_data_member…..  to get all the functions automatically. But while creating an object of this transaction, we want to pass a configuration class object as argument in the new function. How should we override the new() function alone when we use the short hand macros? When we tried using do_new() (like overriding other functions), it did not work.”

As we explored a bit, we found another macro specifically meant for this:

`vmm_data_new(<class_name>)

This macro should be used before the beginning of data-member macros. This lets the succeeding macros do all the work except the “new” function implementation.

class s2p_xactn extend vmm_data;
rand bit [7:0] pkt_len, pkt_pld;

`vmm_data_new(s2p_xactn)
function new(int my_own_arg = 2);
`vmm_note (log, $psprintf (“my val is %0d”, my_own_arg));
endfunction : new

`vmm_data_member_begin(s2p_xactn)
`vmm_data_member_scalar(pkt_len, DO_ALL)
`vmm_data_member_scalar(pkt_pld, DO_ALL)
`vmm_data_member_end(s2p_xactn)
endclass : s2p_xactn

As with traditional martial arts, functional verification too has some slightly different styles/requirements that makes each project interesting and unique. To its credit, we feel that VMM is as proven as traditional martial arts: it can be tailored to different requirements while providing a standardized means of combat.

Posted in Automation, Coding Style, Customization, Modeling | 2 Comments »

VMM VIP’s on multiple buses

Posted by Adiel Khan on 27th May 2009

image

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 »

Size does matter

Posted by Janick Bergeron on 6th July 2008

The VMM Register Abstract Abstraction layer is documented with a 64-bit data value system. For example, the write() method in the vmm_ral_field class is documented as:

task vmm_ral_field::write(output vmm_rw::status_e status,
                          input  bit [63:0]       value,
                          ...);

However, to be able to handle fields (and registers and virtual registers) that are wider than 64 bits, the implementation uses the VMM_RAL_DATA_WIDTH macro to define the maximum size of a field (and register and virtual register):

class vmm_ral_field;
   local bit [`VMM_RAL_DATA_WIDTH-1:0] value;
   ...
   task vmm_ral_field::write(output vmm_rw::status_e              status,
                             input  bit [`VMM_RAL_DATA_WIDTH-1:0] value,
                             ...);
   ...
endclass

Of course, the macro is defined by default to “64″, unless you define it otherwise:

`ifndef VMM_RAL_DATA_WIDTH
   `define VMM_RAL_DATA_WIDTH 64
`endif

Normally, you don’t really need to worry about the value of this macro, as SystemVerilog will silently extend or trim the value assigned to the value argument (or returned via that same argument in the read() method). That works fine if you know a priori the size of the field and use an appropriately sized expression or variable for it.

However, when writing generic code that needs to work with unknown field sizes, you must be careful to declare any temporary or working variable using:

bit [`VMM_RAL_DATA_WIDTH-1:0] tmp;

to avoid inadvertently losing data. You can get the actual size of the field by using the vmm_ral_field::get_n_bits() method. See the pre-defined register tests in $VMM_HOME/sv/RAL/tests for examples.

All of the above is documented in Chapter 16 “Maximum Data Size” of the RAL User’s Guide.

But why not make the size of the field a parameter and do away with the macro altogether?

You mean beside the fact that VCS did not support parameterized class at the time RAL was created? :-)

There are a few reasons…

One could be the potential memory saving by making the data members holding the mirrored field value match the actual size of the field. However, experiments on a customer RAL model with hundreds of thousands of fields (with a VMM_RAL_DATA_WIDTH defined to 512) did not show a significant memory saving (at least with VCS).

The other is that the notion of an absolute maximum field size would still be required to be able to write generic code, independent of their respective sizes. It is impossible to operate on a field through a parameterized class unless its size is known a priori because you must specialize1 a parameterized class whenever you use it. It would thus still be necessary to create a size-generic field base class that can deal with any size of fields (so a function such as vmm_ral_reg::get_fields() can be provided). As a user, you would still need to know about and use the VMM_RAL_DATA_WIDTH macro.

Finally, the automatic value resizing only works for input, output and inout arguments. A ref argument (such as the ones use in the field pre/post_read/write callback methods) require that a variable of the exact same size be used. Again, this would require knowing, a priori, how large a field is before you could write code for it.

So my current line of thinking is that class parameters are fine for generic types, but not that useful for generic sizes. Or am I all wet?

1and that specialization must be defined at compile-time because you can’t use a run-time expression to specialize a class. For example, you could not call get_n_bits() to obtain the size of the field then use the result to specialize a parameterized class to access it.

Posted in Coding Style, Register Abstraction Model with RAL, Reuse, SystemVerilog | 4 Comments »

Do they diss this “this”?

Posted by Janick Bergeron on 9th June 2008

One of the issues with open-sourcing, is that everyone gets to see the code you write.

So now that you have had the chance to look at the VMM source code, you probably have noticed what a former co-worker once described as “a compulsive use of “this” in method implementations“. Why is it that I indeed compulsively use “this” whenever I refer to a data member in a class? As with every coding guideline, there are pros and cons. This one is no exception.

First of all, a bit of background, for those of you who may not be familiar with “this”… Those of you who are, can skip forward…

When referring to a variable or function/task that is declared as a member of a class from within that same class, you can prefix the reference with “this.”. For example:

class packet;
bit [47:0] da;
function new(bit [47:0] da);
this.da = da;
endfunction
function void display();
$write("DA = %h\n", this.da);
endfunction
endclass

“this” is an implicitly defined handle to the object instance that is executing the procedural code. In the constructor of the above example, “this.da” is an explicit reference to the “packet::da” variable contained in the class. Using the “this.” prefix makes it clear that I am not referring to the “da” argument. In this case, the use of “this.” is not optional as it is needed to differentiate between the constructor argument and the data member. It also avoids having to come up with different names for the constructor argument and data member when in fact they both represent the same thing.

I choose to use the “this.” prefix even in cases where it is clearly optional. For example, in the “packet::display()” method, because there are no other variables named “da” in the scope of the function, it would be clear that a simple reference of “da” would refer to the packet::da variable.

I always use the “this.” prefix to document that the variable or method referred to is a member of the class and not some variable/task/function inherited from some larger scope. In the simple example above, it is easy to see that the “da” variable is a class data member. But what if the procedural code making use of the “packet::da” variable and the declaration of that variable where separated by several dozens — or hundreds — of lines? What if they were in different files? Consider the code below. Using “this.”, despite being option, clearly document the nature of the “stream_id” and “fcs” variables.

class bad_frames_in_stream_0 extends eth_frame;
rand bit is_bad;

constraint some_bad_frames {
if (this.stream_id == 0 && this.is_bad) this.fcs != 0;
}

endclass

Posted in Coding Style, Modeling Transactions, VMM | 4 Comments »