Verification Martial Arts: A Verification Methodology Blog

Archive for the 'Reuse' Category

Connecting Multiple Analysis Ports to a Single Analysis Export

Posted by JL Gray on 9th February 2010

Today’s post was written by my colleague Asif Jafri. Enjoy! JL

by Asif Jafri

Asif Jafri is a verification engineer at Verilab.

This post introduces the VMM implementation of the Transaction Level Modeling (TLM) 2.0 specification of how you can connect multiple broadcasting ports to the same receiving export using peer ID’s. Figure 1 shows multiple initiators communicating with the same target. The initiators can be monitors on either side of your DUT passing transaction to a single scoreboard which keeps track of the transactions and does various checks. In TLM 2.0 message broadcast is accomplished through write function calls from the initiator which are then implemented in the target. image

Figure 1: Connecting using ID

Read the rest of this entry »

Posted in Communication, Reuse, Transaction Level Modeling (TLM), VMM, VMM infrastructure | No Comments »

You get real hierarchy with VMM1.2

Posted by Wei-Hua Han on 9th February 2010

If you look at VMM1.2 classes, you may find that almost all new() functions have an argument, vmm_object parent. The purpose of this argument is to build a parent-child hierarchy within a VMM1.2 based environment, so that VMM1.2 can provide an infrastructure where users can access the components inside the environment through hierarchical path and name. And this parent-child hierarchy also contributes to the implicit phasing implementation.

Here is a small example to illustrate how a hierarchy can be built with VMM1.2:

  1. class mike_c extends vmm_object;
  2. function new(vmm_object parent=null, string name=”");
  3. super.new(parent,name);
  4. endfunction
  5. endclass
  6. class ben_c extends vmm_object;
  7. function new(vmm_object parent=null, string name=”");
  8. super.new(parent,name);
  9. endfunction
  10. endclass
  11. class jason_c extends vmm_object;
  12. mike_c Mike;
  13. ben_c Ben;
  14. int weight;
  15. function new(vmm_object parent=null, string name=”");
  16. bit is_set;
  17. super.new(parent,name);
  18. weight=vmm_opts::get_object_int(is_set,this, “weight”,0, “set weight”);
  19. endfunction
  20. function void build();
  21. Mike = new(this,”Mike”);
  22. Ben = new(this,”Ben”);
  23. endfunction
  24. endclass
  25. program p1;
  26. jason_c Jason;
  27. initial begin
  28. vmm_opts::set_int(“Jason:weight”,10);
  29. Jason=new(null,”Jason”);
  30. Jason.build();
  31. vmm_object::print_hierarchy(Jason);
  32. $display(“Jason has %0d children”,Jason.get_num_children());
  33. $display(Jason.Mike.get_object_name());
  34. $display(Jason.Ben.get_object_hiername());
  35. $display(Jason.weight);
  36. end
  37. endprogram

In this small example, line 30 creates an object (Jason) for jason_c and its parent is “null”, so Jason is a root component in the hierarchy. When Jason.build() is called in line 31, object Mike and Ben are created and their parent is set to Jason. So in this small system we build the following hierarchy:

[Jason]

|–[Mike]

|–[Ben]

Jason has 2 children

Mike

Jason:Ben

This hierarchy can be printed by vmm_object method print_hierarchy().

Please note that unlike Verilog modules and instances where the hierarchy is defined as per the Verilog LRM, the VMM1.2 parent-child hierarchy is really user defined. It depends on how “parent” argument is specified when the object is created, and not on where the object variable is declared or created.

As for the component name, although you may choose to specify a different name as the variable name, it is a good practice to keep it consistent, which makes the code more readable and avoids confusion.

From the above example, you can find that the hierarchical name for object Jason.Ben is “Jason:Ben”. VMM1.2 uses “:” as the hierarchical separator instead of “.”. The reason is that this hierarchical name is actually a made-up name, and we want to differentiate it from the semantic hierarchical reference name specified in Verilog/SystemVerilog which uses “.” as the separator.

There are many methods provided in VMM1.2 which help users to work with the parent-child hierarchy. Some of these methods are:

  • find_child_by_name(): finds the named object relative to this object
  • get_num_children(): gets the total number of children for this object
  • get_nth_child(): returns the nth child of this object
  • get_object_hiername(): gets the complete hierarchical name of this object
  • get_parent_object():returns the parent of this object
  • get_root_object(): gets the root parent of this object
  • get_typename(): returns the name of the actual type of this object
  • is_parent_of(): returns true, if the specified object is a parent of this object
  • print_hierarchy(): prints the object hierarchy
  • Set_parent_object(): sets or replaces the parent of this object

Dr. Ambar Sarkar has explained how users can traverse the hierarchy in his blog post.

This parent-child hierarchical infrastructure is one of the most important mechanisms in VMM1.2. Many other VMM1.2 features rely on this infrastructure:

1.   Implicit phasing

Implicit phasing is new in VMM1.2. In implicit phasing, structural components (transactors) are aligned with each other automatically. The phase specific methods are called automatically throughout the whole hierarchy in a top-down (for functions) or forked (for tasks) mode. Thus implicit phasing makes integration of Verification IPs into the simulation environment or other structural components a lot easier. Other VMM1.2 users also benefit from implicit phasing when building complicated verification environments.

2.   Factory replacement

Factory is an important feature that enables flexibility and reuse inside a verification environment. Because of the parent-child hierarchy, users can replace components, generated transactions or scenarios with their extension type or other objects by specifying hierarchy path and names. Support for regular expression for specifying hierarchies and names make this utility very powerful.

For example, in the following code segment, we override the type mike_c for Mike with mike_ext :

mike_c::override_with_new(“@%Jason:Mike”,mike_ext::this_type,log);

3.   Hierarchical configuration

In addition to supporting runtime configuration through command-line options or files, using the parent-child hierarchy VMM1.2 also supports configuration of components by specifying their hierarchical path and name. All these configuration utilities are provided through vmm_opts.

For example, in the following code segment, we set the property weight of object Jason to 10 using hierarchical configuration:

vmm_opts::set_int(“Jason:weight”,10);

Like factory, users can also use regular expression with hierarchical configuration.

If you have watched “Growing Pains”, you know that I am not quite accurate when I say

Jason has 2 children

He indeed has three…

Have fun with VMM1.2. J

Posted in Debug, Reuse, Tutorial, VMM infrastructure | No Comments »

Leverage on the built-in callback inside vmm_atomic_gen and be productive with DVE features for VMM debug

Posted by Srinivasan Venkataramanan on 7th February 2010

Srinivasan Venkataramanan, CVC Pvt. Ltd.

Rashmi Talanki, Sasken

John Paul Hirudayasamy, Synopsys

During a recent Verification environment creation for a customer we had to tap an additional copy/reference of the generated transaction to another component in the environment without affecting the flow. So one producer gets more than one consumer (here 2 consumers). As a first time VMM coder the customer tried using “vmm_channel::peek” on the channel that was connecting GEN to BFM. Initially it seemed to work, but with some more complex code being added across the 2 consumers for the channel, things started getting funny – one of the consumers received the transactions more than once for instance.

The log file looked like:

@ (N-1) ns the transaction was peeked by Master_BFM  0.0.0

@ (N-1) ns the transaction was peeked by Slave_BFM 0.0.0

.

.(perform the task)

.

@N ns the Master_BFM  get the transaction 0.0.0

@N ns the transaction was peeked by Slave_BFM 0.0.0

@N ns the transaction was peeked by Master_BFM 0.0.1

@N ns the transaction was peeked by Slave_BFM 0.0.1

With little reasoning from CVC team, the customer understood the issue quickly to be classical race condition of 2 consumers waiting for same transaction. What are the options, well several indeed:

1. Use vmm_channel::tee() (See our VMM Adoption book http://systemverilog.us/vmm_info.html for an example)

2. Use callbacks – a flexible, robust means to provide extensions for any such future requirements

3. Use vmm_broadcaster

4. Use the new VMM 1.2 Analysis Ports (See a good thread on this: http://www.vmmcentral.org/vmartialarts/?p=860 )

The customer liked the callbacks route but was hesitant to move towards the lengthy route of callbacks – for few reasons (valid for first timers).

1. Coding callbacks takes more time than simple chan.peek(), especially the facade class & inserting at the right place

2. She was using the built-in `vmm_atomic_gen macro to create the generator and didn’t know exactly how to add the callbacks there as it is pre-coded!

Up for review, we discussed the pros and cons of the approaches and when I mentioned about the built-in post_inst_gen callback inside the vmm_atomic_gen she got a pleasant surprise – that takes care of 2 of the 4 steps in the typical callbacks addition step as being recommended by CVC’s popular DR-VMM course (http://www.cvcblr.com/trng_profiles/CVC_DR_VMM_profile.pdf).

Step-1: Declaring a facade class with needed tasks/methods

Step-2: Inserting the callback at “strategic” location inside the component (in this case generator)

This leaves only the Steps 3 & 4 for the end user – not bad for a robust solution (especially given that the Step-4 is more of formality of registration). Now that the customer is convinced, it is time to move to coding desk to get it working. She opened up vmm.sv and got trapped in the multitude of `define vmm_atomic_gen_* macros with all those nice looking “ \ “ at the end – thanks to SV’s style of creating macros with arguments. Though powerful, it is not the easiest one to read and decipher – again for a first time SV/VMM user.

Now comes the rescue in terms of well proven DVE – the VCS’s robust GUI front end. Its macro expansion feature that works as cleanly as it can get is at times hard to locate. But with our toolsmiths ready for assistance at CVC, it took hardly a few clicks to reveal the magic behind the `vmm_atomic_gen(icu_xfer). Here is a first look at the atomic gen code inside DVE.

clip_image002

Once the desired text macro is selected, DVE has a “CSM – Context Sensitive Menu” to expand the macro with arguments. It is “Show à Macro”, as seen below in the screenshot.

clip_image004

With a quick bang go on DVE – the Macros expander popped up revealing the nicely expanded, with all class name argument substituted source code for the actual atomic_generator that gets created by the one liner macro. Along with clearly visible were the facade class name and the actual callback task with clear argument list (something that’s not obvious by looking at standard vmm.sv).

clip_image006

Now, what’s more – in DVE, you can bind such “nice feature” to a convenient hot-key if you like (say if you intend to use this feature often). Here is the trick:

Add the following to your $HOME/.synopsys_dve_usersetup.tcl

gui_set_hotkey -menu “Scope->Show->Macro” -hot_key “F6″

Now when you select a macro and type “F6” – the macro expands, no rocket science, but a cool convenient feature indeed!

Voila – learnt 2 things today – the built-in callback inside the vmm_atomic_gen can save more than 50% of coding and can match up to the effort (or the lack of) of using simple chan.peek(). The second one being DVE’s macro expansion feature that makes debugging a real fun!

Kudos to VMM and the ever improving DVE!

Posted in Callbacks, Debug, Reuse, Stimulus Generation, VMM, VMM infrastructure | No Comments »

Verification in the trenches: Implementing Complex Synchronization Between Components Using VMM1.2

Posted by Ambar Sarkar on 5th February 2010

ambar Dr. Ambar Sarkar, Chief Verification Technologist, Paradigm Works Inc.

Why is it tricky to get transactors and other verification components to work in sync with each other, especially  if they come from different projects?  It is likely that they worked well within their source projects,  but their phases (build, configure, reset, start, shutdown etc) were implemented quite differently compared to other components. These differences are usually driven by the inherent protocol requirements or team preferences. For example, consider the verification of an SOC with an AXI  host interface and a PCIe Root Complex. You will likely get your host interface transactor out of reset and execute a configuration sequence before you let your PCIe end point transactor send in requests. So you would not want to run the phases of these two transactors in lock step.

While there are countless ways to implement the phases and their sequencing, one can broadly classify a component  as being either explicitly or implicitly driven, depending on how its phases are invoked.

Implicit phasing: In my earlier post, we discussed how one can often easily coordinate the execution of various verification components. Simply put, as long as one is able to distribute the execution of the component between predetermined methods (called phases), the components can execute in lock-step with one another without requiring any additional coding by the verification engineer. This is called implicit phasing. Implicit phasing may suffice in many cases, but the challenge is to agree on the same set of phases and their sequencing. You basically will need a way to define additional  phases and potentially even rearrange their implicit calling sequence.

Explicit phasing: In contrast, explicit phasing requires the environment writer to explicitly call and synchronize the phases of the components. Typically, it takes some work to get such components to play well with one another.  This happens more often for legacy or externally developed components. In such cases, the  developers may not have known about the predetermined phases so they could not have broken down the implementation quite the way the target environment expects. Explicit phasing is often unavoidable in environments with components from multiple sources, since you may need to carefully control and coordinate the phases by hand to accommodate their differing implementation assumptions.

So the challenge we are discussing today is really about making these explicit and implicit phased components get their phases to match and cooperate during their phase transitions.

This is where vmm_timeline helps. Simply put, vmm_timeline object encapsulates your implicitly phased object and allows it to be called as an explicitly phased object.  It lets you define your own phases and the sequence in which you want to execute them. The ability to customize phases is critical, as you may need to define additional phases to fit in with the way the explicitly phased target  environment expects its phases to execute.

Here is an example that shows how an implicitly-phased component(my_implicit_comp) is being executed within an explicitly-phased my_env. Notice how the my_tl(derived from vmm_timeline) is used.

Step a. Create a vmm_timeline object and instantiate the components

// Implicitly phased comp
class my_implicit_comp extends vmm_group;
`vmm_typename(my_implicit_comp)

function new(string name = “”,
vmm_object parent = null);
super.new(“my_implicit_comp”, name, null);
super.set_parent_object(parent);
endfunctionvirtual function void build_ph();
super.build_ph();

endfunction

endclass

// Create a vmm_timeline class to wrap this implicitly phased component

class my_tl extends vmm_timeline;
`vmm_typename(my_tl)
my_implicit_comp comp1;

function new(string name = “”,
vmm_object parent = null);
super.new(“my_tl”, name, parent);
endfunction

virtual function void build_ph();
super.build_ph();

// Create an instance
this.comp1 = my_implicit_comp::create_instance(this, “comp1”);
endfunction

endclass

Step b. Instantiate in top-level vmm_env and call out the implicit methods

// Instantiate the vmm_timeline object in the top environment and call its phases explicitly.class my_env extends vmm_env;
`vmm_typename(my_env)
my_tl tl;

function new();
super.new(“env”);
endfunction

virtual function void build();
super.build();
this.tl = new(“tl”, this);
endfunction

virtual task start();
super.start();
tl.run_phase(“start”);
`vmm_note(log, “Started…”);
endtask

virtual task wait_for_end();
super.wait_for_end();
fork
// run_test phase corresponds best here
tl.run_phase(“run_test”);
begin
`vmm_note(log, “Running…”);
#100;
end
join
endtask

virtual task stop();
super.stop();

// shutdown phase corresponds best here
tl.run_phase(“shutdown”);
`vmm_note(log, “Stopped…”);
endtask

Note that the converse is also true. Explicitly phased components can be incorporated into implicitly driven environments. You need to encapsulate them in a parent class derived from the vmm_subenv class and define how each implicit phase of the parent class can be mapped to the proper explicit phase(s) of the original component. Then you can simply instantiate this parent class in the target environment. For further details, search the string “Mixed Phasing” in the VMM 1.2 User Guide.

In summary, vmm_timeline helps you manage different phasing and sequencing needs of verification components by making it easier for explicitly and implicitly phased components to interact. No wonder that under the hood of VMM1.2, vmm_timeline is used to implement advanced features such as multi-test concatenation.

This article is the 4th in the Verification in the trenches series. Hope you found this article useful. If you would like to hear about any other related topic, please comment or drop me a line at ambar.sarkar@paradigm-works.com. Also, if you are starting out fresh, please check out the free VMM1.2 environment generator at http://resourceworks.paradigm-works.com/svftg/vmm .

Posted in Communication, Modeling, Phasing, Reuse | No Comments »

Moving from Block levels tests to a Chip level in a reusable world…

Posted by Srivatsa Vasudevan on 29th January 2010

In modern designs, one observes a fairly high degree of reuse in designs and verification environments. Cores and blocks are regularly reused from one design to another and increasing demands are being placed on quickly getting a functional chip out of the door to meet a customer need.

In such a world, schedule and functional chips are key to success.

At the block level, the design and verification engineers use a white box testing methodology to write tests that will completely test out the block under test. Usually in these environments, various blocks that interact with the design under test will usually be stubbed out or replaced with transactors as the case may be.

In an ideal world, the tests which were written once for the block level verification if reused 100% at the chip level would mean that the reuse was at 100%. That never usually happens.

Let’s look at the illustration between a verification engineer who’s busy building cores at the block level, comes in to talk to the verification lead of a chip he’s delivering to.

method_2

It is obvious that the requirements and goals for any tests at chip level are quite different. Most chip leads would first like to ensure that the core in question is indeed integrated at the chip level properly and the core does indeed talk to other cores properly.   The core ideally is a black box that would perform the functionality expected of it. The perspective on design verification maintained by the chip level verification engineer is usually vastly different from the block level perspective

Usually interconnect, clocking, reset, power-up/down, programming sequences, power domains, timing etc would be some examples  of things that come to mind when writing tests at the chip level. Once these major goals are met, one looks at other optimizations while attempting to verify the device.

That said, how does one now take the tests that are written for the block and re-use them at the top level especially if the tests are intended for different reasons? Is it even possible to minimize the effort in doing so? Don’t we need a mechanism to ensure that things that weren’t completely exercised at the block/subsystem level are tested at core level? What do we need for the handoff? How do the various verification class libraries now play a part in this picture?

The series herein explores how this is all done. A proper top/down methodology Coupled to a bottom’s up methodology can yield excellent results with minimal overlap.

Stay Tuned….

Posted in Reuse, Verification Planning & Management | 1 Comment »

Moving from Block levels tests to a Chip level in a reusable world…

Posted by Srivatsa Vasudevan on 29th January 2010

In modern designs, one observes a fairly high degree of reuse in designs and verification environments. Cores and blocks are regularly reused from one design to another and increasing demands are being placed on quickly getting a functional chip out of the door to meet a customer need.

In such a world, schedule and functional chips are key to success.

At the block level, the design and verification engineers use a white box testing methodology to write tests that will completely test out the block under test. Usually in these environments, various blocks that interact with the design under test will usually be stubbed out or replaced with transactors as the case may be.

In an ideal world, the tests which were written once for the block level verification if reused 100% at the chip level would mean that the reuse was at 100%. That never usually happens.

Let’s look at the illustration between a verification engineer who’s busy building cores at the block level, comes in to talk to the verification lead of a chip he’s delivering to.

image

It is obvious that the requirements and goals for any tests at chip level are quite different. Most chip leads would first like to ensure that the core in question is indeed integrated at the chip level properly and the core does indeed talk to other cores properly.   The core ideally is a black box that would perform the functionality expected of it. The perspective on design verification maintained by the chip level verification engineer is usually vastly different from the block level perspective

Usually interconnect, clocking, reset, power-up/down, programming sequences, power domains, timing etc would be some examples  of things that come to mind when writing tests at the chip level. Once these major goals are met, one looks at other optimizations while attempting to verify the device.

That said, how does one now take the tests that are written for the block and re-use them at the top level especially if the tests are intended for different reasons? Is it even possible to minimize the effort in doing so? Don’t we need a mechanism to ensure that things that weren’t completely exercised at the block/subsystem level are tested at core level? What do we need for the handoff? How do the various verification class libraries now play a part in this picture?

The series herein explores how this is all done. A proper top/down methodology Coupled to a bottom’s up methodology can yeild excellent results with minimal overlap.

Stay Tuned….

Posted in Reuse, Verification Planning & Management | No Comments »

Should you use VMM Callbacks or TLM Analysis ports?

Posted by S. Prashanth on 27th January 2010

With the addition of OSCI TLM2.0 features in VMM1.2, it is now becoming possible to use analysis ports for broadcasting transaction information from a transactor to any components such as scoreboard, functional coverage models, etc…

But, does it mean that VMM callbacks which are traditionally used for this purpose are not required anymore?

In short, the answer is no: both analysis port and callback have their own advantages. Before getting into more details, let me show you two simple examples I’ve created to encompass the analysis port and callback usage.

Communication through analysis port

Step1: In my transactor, I’ve simply instantiate a vmm_tlm_analysis_port instance [line 2]and invoke the analysis_port.write() method [line 8] to post the tr object to multiple subscribers.

1. class cpu_driver extends vmm_xactor;

2. vmm_tlm_analysis_port#(cpu_driver, cpu_trans) analysis_port;

3.    virtual function void build_ph();

analysis_port = new(this, “cpu_analysys_port”);

4.    endfunction

5.    virtual task main();

6.       cpu_trans tr;

7.       …

8. analysis_port.write(tr);

9.     endtask

10. endclass

Step2: Let’s now see how to model a subscriber such as a coverage model. In this model, I’ve simply instantiated a vmm_tlm_analysis_export instance [Line 3] and provided the implementation of the write method() [Line 4]. Once the transactor posts a transaction to the write method, the coverage model write_CPU gets called as well and receives this transaction, which can be sampled and covered.

1. class cntrlr_cov extends vmm_object;

2.    vmm_tlm_analysis_export #(cntrlr_cov, cpu_trans)

3.    cpu_export = new(this, “CpuAnExPort”);

4.    virtual function void write(int id=-1, cpu_trans tr);

5.        this.cpu_tr = tr;

6.        CG_CPU.sample();

7.   endfunction

8. endclass

Step3: The last important part is to bind my transactor and my subscribers. This is simply done by using the transactor tlm_bind() method. Of course we should be invoked for all subscribers.

1. class tb_env extends vmm_group;

2.    cpu_driver drv;

3.    cntrlr_cov cov;

4.    function void connect_ph();

5.       drv.analysis_port.tlm_bind(cov.cpu_export);

6.    endfunction

7. endclass

As you can see in these examples, analysis ports are easy to use. But they are not meant to allow subscribers to modify the transaction and are very much restricted to only one method with only one argument, i.e. write().

Communication through callback

Step1: I’ve created a generic callback that is nothing but a container that extends the vmm_xactor_callbacks base class with empty virtual methods [Line 1-3]. I have deliberately left these methods empty so that they can overridden for any particular usage such as a coverage model, scoreboard, etc.Next step is have my transactor calling this callback once the transaction is available [Line 10]

1. class cpu_driver_callbacks extends vmm_xactor_callbacks;

2.    virtual function void write(cpu_trans tr); endtask

3. endclass

4.

5. class cpu_driver extends vmm_xactor;

6.

7.    virtual task main();

8.       cpu_trans tr;

9.       …

10.      `vmm_callback(cpu_driver_callbacks, write(tr));

11.   endtask

12. endclass

Step2: Now, I can extend the previous class and provide the implementation the coverage model directly in the callback

1. cpu2cov_callback extends cpu_driver_callbacks;

2.    cntrlr_cov cov;

3.    virtual function void write(cpu_trans tr);

4.       cov.cpu_tr = tr;

5.       cov.CG_CPU.sample()

6.    endfunction

7. endclass

Step3: Once both transactor and subscribers are implemented, I can simply instantiate them in my implicitly phased environment [Line2-3]. Then, I can register the extended callback instance to the transactor by using append_callback() method Line[6]. Note that this registration happens in the connect phase.

1. class tb_env extends vmm_group;

2.    cpu_driver drv;

3.    cntrlr_cov cov;

4.    function void connect_ph();

5.       cpu2cov_callback cbk = new(cov);

6.       drv.append_callback(cbk);

7.    endfunction

8. endclass

As you can see in above examples, callbacks are also easy to use. As opposed to analysis port, their subscribers can possibly modify the transaction and can contain multiple methods with any kind of arguments.

Comparison

1. Analysis ports follow OSCI TLM2.0 standard.

2. Unlike callbacks, user do not need to create class with empty virtual methods, instead pre-defined method write() is used

3. Analysis ports can only broadcast one transaction as opposed to callbacks where you can define the method arguments.

4. Analysis port method write() is a function whereas callback class can model its empty virtual methods as tasks or void functions which gives you flexibility to control the transactor as well (like inserting delays, injecting error mechanism, etc)

5. The `vmm_tlm_analysis_export() macro must be used to create a new analysis façade when a class need to have more than one analysis export

6. Analysis ports can only be used by classes based on vmm_object. Callbacks can be used by any class.

In summary

- If a transactor needs to broadcast only one transaction, then analysis port can be used. If a transactor needs to send different kinds of information at different points, and provide some hooks for modeling variant functionality (like inserting delays, error injections, etc), then callbacks is the way to go. Both analysis port and callback can also be provided, publishing analysis port after callbacks.

Posted in Callbacks, Communication, Reuse, Transaction Level Modeling (TLM) | 2 Comments »

Using factories in an implicitly-phased environment

Posted by Kiran Maiya on 25th January 2010

You might have read the neat article posted by Wei-Hua about the class factory in this forum. You may wonder what could be the best way to code this in an implicitly-phased environment. In this article I will be discussing how transactions based on vmm_data can be made factory enabled using VMM-1.2 based factory utility in an implicitly-phased environment. We will see how transaction objects can be replaced using these factory utilities. I’ve deliberately attempted not to introduce too many concepts such as macros to implement default methods. I will post an article on replacing transactors shortly.

As a test bed let us take a simple concept of generating raw pixels using generators. These raw pixels are modeled as vmm_data objects. This pixel object contains RGB color information in the variable clr which is a rand property. A discriminant member called type_e determines the color that this pixel object can take.


/* 1 */ `include “vmm.sv”
/* 2 */ program main ;
/* 3 */
/* 4 */ //// Class: Pixel ////
/* 5 */
/* 6 */ class Pixel extends vmm_data ;
/* 7 */ `vmm_typename(Pixel)
/* 8 */ static vmm_log log = new(“Pixel”, “class”) ;
/* 9 */ typedef enum {Red, Green, Blue, ANY} clr_type_e ;
/* 10 */
/* 11 */ typedef enum {
/* 12 */         R1, R2, R3, R4, R5,
/* 13 */         G1, G2, G3, G4, G5,
/* 14 */         B1, B2, B3, B4, B5
/* 15 */         } clrcode_e ;
/* 16 */ rand clrcode_e       clr ;
/* 17 */ clr_type_e type_e = ANY ;
/* 18 */
/* 19 */ constraint con
/* 20 */ {
/* 21 */ if(type_e == Red)
/* 22 */    clr inside {R1, R2, R3} ;
/* 23 */ if(type_e == Green)
/* 24 */    clr inside {G1, G2, G3} ;
/* 25 */ if(type_e == Blue)
/* 26 */    clr inside {B1, B2, B3} ;
/* 27 */ }

In addition to the constructor, the factory override methods needs to implement two predefined methods of vmm_data class, copy() and allocate(). These methods return the right type of the object that was enabled as factory. This can also be implemented using vmm_data short-hand macros, but for the purpose of clarity the full implementation is shown.

/* 28 */

/* 29 */ /// Function: new()

/* 30 */ function new(string name = “”, vmm_object parent = null) ;

/* 31 */ super.new(log) ;

/* 32 */ endfunction: new

/* 33 */

/* 34 */ /// Function: copy()

/* 35 */ virtual function vmm_data copy(vmm_data to = null) ;

/* 36 */ Pixel me ;

/* 37 */ if(to == null)

/* 38 */ me = new (this.get_object_name(), this.get_parent_object()) ;

/* 39 */ else if(!$cast(me, to))

/* 40 */ `vmm_fatal(log, “Can’t copy to non-Pixel instance”) ;

/* 41 */

/* 42 */ /// Copy members here

/* 43 */ me.clr = this.clr ;

/* 44 */ me.type_e = this.type_e ;

/* 45 */ return me ;

/* 46 */ endfunction: copy

/* 47 */

/* 48 */ /// Function: allocate()

/* 49 */ virtual function vmm_data allocate() ;

/* 50 */ Pixel me = new () ;

/* 51 */ return me ;

/* 52 */ endfunction: allocate

It is a good practice to have the psdisplay() method implemented. This will be handy for debugging the simulation and/or for a better report log.

/* 53 */

/* 54 */ /// psdisplay()

/* 55 */ virtual function string psdisplay(string prefix = “”);

/* 56 */ $sformat(psdisplay, “%s clr = %s, type_e = %s”,

/* 57 */ prefix, clr.name(),type_e.name()) ;

/* 58 */ endfunction: psdisplay

Finally make this Pixel class to be factory enabled by using the factory utility macro as shown below.

/* 59 */

/* 60 */ `vmm_class_factory(Pixel) // Introduce factory API

/* 61 */

/* 62 */ endclass: Pixel

This base pixel class can be customized to different variants by forcing the discriminant type as shown below. Objects of these types can be later used to override the factory. These class extensions should also be enabled as factories using the factory utilities as shown below.

/* 63 */

/* 64 */ //// GreenPixel ////

/* 65 */

/* 66 */ class GreenPixel extends Pixel ;

/* 67 */ /// new()

/* 68 */ function new(string name = “”, vmm_object parent = null) ;

/* 69 */ super.new(name, parent) ;

/* 70 */ type_e = Green ;

/* 71 */ endfunction: new

/* 72 */

/* 73 */ constraint con_GreenPixel

/* 74 */ {

/* 75 */ type_e == Green ;

/* 76 */ }

/* 77 */

/* 78 */ /// copy()

/* 79 */ virtual function vmm_data copy(vmm_data to = null) ;

/* 80 */ GreenPixel me ;

/* 81 */

/* 82 */ if(to == null)

/* 83 */ me = new ;

/* 84 */ else if(!$cast(me, to))

/* 85 */ `vmm_fatal(log, “Can’t copy to non-Green Pixel instance”) ;

/* 86 */

/* 87 */ /// Copy members here

/* 88 */ return super.copy(me) ;

/* 89 */ endfunction: copy

/* 90 */

/* 91 */ /// allocate()

/* 92 */ virtual function vmm_data allocate() ;

/* 93 */ GreenPixel me = new() ;

/* 94 */ return me ;

/* 95 */ endfunction: allocate

/* 96 */

/* 97 */ `vmm_class_factory(GreenPixel)

/* 98 */

/* 99 */ endclass: GreenPixel

In real environment, pixels streams are generated using a custom pixel generators based of vmm_xactors or use one of the pre-defined generators. I will not discuss the generators in this article for the sake of simplicity. However a simplistic approach is taken here to show the generation in an implicit environment by utilizing the run_ph() of a timeline class. The driver logic is mimicked in the run_ph() method of PixelTimeline which is an extension of vmm_timeline. This method it is implicitly triggered during the run phase of the timeline. Transaction object tr which is a Pixel object, is instanced at run-time using the create_instance() method rather than the traditional constructor new(). Although the object instantiation via the factory can also be carried out in the constructor of the transactor or the build phase of the generator (timeline here), this has a disadvantage. With the create_instance() called inside pre-run phase of the generator, it allows only one opportunity for effectively replacing the factory instance before it is created. By calling the create_instance() factory method repeatedly at run-time, it allows the factory to override dynamically as required by testcases.

/* 100 */

/* 101 */ //// PixelTimeline ////

/* 102 */

/* 103 */ class PixelTimeline extends vmm_timeline ;

/* 104 */ `vmm_typename(PixelTimeline)

/* 105 */ Pixel tr ;

/* 106 */ bit done = 0 ;

/* 107 */

/* 108 */ /// Function: new()

/* 109 */

/* 110 */ function new(string name = “”, string inst = “”, vmm_object parent = null) ;

/* 111 */ super.new(get_typename(), name) ;

/* 112 */ //tr = Pixel::create_instance(this, “Fact”) ; // Not advisable

/* 113 */ endfunction : new

/* 114 */

/* 115 */ virtual function void build_ph() ;

/* 116 */ // Not recommended to call create_instace

/* 117 */ endfunction: build_ph

/* 118 */

/* 119 */ /// Task: myrun

/* 120 */

/* 121 */ task run_ph() ;

/* 122 */ tr = Pixel::create_instance(this, “Fact”) ; // This is recommended

/* 123 */ repeat(5)

/* 124 */ begin

/* 125 */ tr.randomize() ;

/* 126 */ tr.display(“::”) ;

/* 127 */ end

/* 128 */ $display(“”);

/* 129 */ endtask: run_ph

/* 130 */

/* 131 */ endclass: PixelTimeline

Having the environment ready, let us see how the objects can be replaced using the factory utility. A typical environment will be created by extending vmm_group. Environment creation is out of scope of this topic and will not be discussed here; instead a simple program block is shown creating the relevant blocks.

/* 132 */

/* 133 */ //// Program

/* 134 */ initial

/* 135 */ begin

/* 136 */ PixelTimeline tl ;

/* 137 */ Pixel tr = new ;

/* 138 */ GreenPixel gpxl ;

/* 139 */ vmm_log log = new(“A”, “B”) ;

/* 140 */

/* 141 */ tl = new (“tl”, “Main”, null) ;

Following code shows a free running generator with no customization on the pixels.

/* 142 */

/* 143 */ /// Free running simulation, any color

/* 144 */ tl.run_phase() ;

The above code will generate pixels of all colors and the output will be as follows

:: clr = R3 :: type_e = ANY

:: clr = G3 :: type_e = ANY

:: clr = B3 :: type_e = ANY

:: clr = G3 :: type_e = ANY

:: clr = B5 :: type_e = ANY

Factory replacement can be done in two ways,

  1. override by transaction copy using a reference object or
  2. override by transaction type

While overriding by transaction copy the factory method override_with_copy() should be used. This will call the copy() method to return the right type to be overridden. Here one needs to have a reference copy that will be passed as an argument while replacing factory. In the following code snippet it is shown that a local transaction copy is pixilated to Red using the type discriminant. This copy is used to override the transaction object as shown below.

/* 145 */

/* 146 */ /// Override with a transaction object copy of type Red

/* 147 */ tr.type_e = Pixel::Red ;

/* 148 */ Pixel::override_with_copy(“@%*”, tr, tr.log, `__FILE__, `__LINE__) ;

/* 149 */ tl.reset_to_phase(“start_of_sim”) ;

/* 150 */ tl.run_phase() ;

/* 151 */

This part of the code will generate the red pixels and the output will be as follows

:: clr = R1 :: type_e = Red

:: clr = R3 :: type_e = Red

:: clr = R2 :: type_e = Red

:: clr = R3 :: type_e = Red

:: clr = R1 :: type_e = Red

While overriding by transaction type the factory method override_with_new() should be used. This will call the allocate() method to return the right type. To specify the type of object to be manufactured one can use the predefined method this_type() which returns the type of the specified object. Here in the following example GreenPixel type is used to override the factory.

/* 152 */

/* 153 */ /// Override with new type-Green

/* 154 */ Pixel::override_with_new(“@%*”, GreenPixel::this_type,

/* 155 */ log, `__FILE__, `__LINE__) ; // Strongly typed

/* 156 */ tl.reset_to_phase(“configure”) ;

/* 157 */ tl.run_phase() ;

/* 158 */

/* 159 */ #100 $finish ;

/* 160 */ end

/* 161 */ endprogram: main

/* 162 */

The above code will generate the green pixels and the output will be as follows

:: clr = G2 :: type_e = Green

:: clr = G2 :: type_e = Green

:: clr = G1 :: type_e = Green

:: clr = G3 :: type_e = Green

:: clr = G3 :: type_e = Green

It is worthwhile to notice that the factory override methods are strongly typed. There isn’t any room for accidental type mismatch. Tool will automatically error out during compilation. For example, the following code is illegal and the tool throws a compilation error.

Data::override_with_new(“@%*”, GreenPixel::this_type,

log, `__FILE__, `__LINE__) ; // Strongly typed

Where Data is not an extension of type Pixel.

Hope you all will have a colorful experience with VMM-1.2 factory utility (no pun intended).

Posted in Phasing, Reuse, VMM infrastructure | No Comments »

Transaction Level Modeling – Value add in different languages.

Posted by Nasib Naser on 14th December 2009

Nasib_NaserNasib Naser, PhD

Sr. Staff Corporate Applications Engineer – Synopsys

One of the driving factors of creating SystemVerilog is to raise the design verification abstraction level. The reason for such a move is described in the SV LRM Abstract. It says: “A set of extensions to the IEEE 1364-2001 Verilog Hardware Description Language to aid in the creation and verification of abstract architectural level models.” So why and how? The “Why” is obvious. A number of reasons come to mind:

  1. Faster development and simulation to enable reaching design and verification goals sooner than later.
  2. Achieve early closure on architectural decisions without early commitment to implementation details.
  3. Enable running “some” software within the complete system context.
  4. Create an environment in which verification methodologies could be developed that creates true verification IP re-usability and models interoperability.

This blog will discuss the methodology behind the “How.” The technology will be explained in subsequent blogs. Up until vmm 1.2 was released Transaction based models were created by utilizing VMM constructs such as vmm_channel(), vmm_xactor(), and vmm_data() . With all VMM strengths this use model succeeded only on in-house designs and didn’t gain wide modeling adoption. The TLM methodology built into VMM lacked common practices to enable interoperability – emphasis on common practice.

Meanwhile, driven by users, the Open SystemC Initiative aka OSCI TLM community managed to create a standard on which models could be developed for re-use and interoperability. Without dwelling in the past I’d like to give a brief history on the evolution that led to the TLM standard. In the beginning there was C, then C++. For obvious reasons these widely known software languages were used to develop in-house system level models for doing high level performance and architectural analysis. The use model was very limited as the simulation behavior was very far from the actual hardware. It lacks concurrency and timing. C++ extensions augmented with a “proof of concept” simulator was created and called SystemC that enable modeling these hardware behaviors in C++, and more. That worked very well. Failing to replace Verilog and vhdl for design and verification SystemC found its niche use model in the architectural modeling domain. At that point architects using SystemC started to demand a standard that enables interoperability for fast platform composition, ease of use, and re-usability. Hence, the OSCI SystemC Transaction Level Modeling 1.0 and later 2.0 standards were created.

So why re-invent the wheel when it comes to SystemVerilog TLM? And why not adopt a powerful and robust verification methodology such as VMM standard to enable seamless integration between SystemC and SystemVerilog? That’s why features described in the OSCI TLM standard found its way into the SystemVerilog/VMM world. These features are available in the newly released VMM 1.2. Subsequent blogs I will explain these features and the value add they bring into a true IP re-use and interoperable verification methodologies.

Posted in Interoperability, Reuse, SystemC/C/C++, Transaction Level Modeling (TLM) | No Comments »

Hitting the “Playback” button on VMM transactions

Posted by Avinash Agrawal on 11th December 2009

Avinash Agrawal

Avinash Agrawal, Corporate Applications, Synopsys




Often verification engineers face the challenge of recording transactions in one simulation, and wanting to replay the same set of transactions in the same sequence of transactions in a different simulation, and try different ways to make this happen.

Well, there is some good news !!!

VMM provides a facility where you can record the transactions going through a VMM channel and save it into a file. This can be done through the record method of the VMM channel. Later, for replay, disconnect the producer of the channel (may be generator/transactor, etc which sends transactions to the channel) and use playback method to load the channel with the transactions from the file in the same order.

Here the saved transaction file acts as a virtual producer. This way random stability is guaranteed. Note that byte_pack() or save() method of transaction (vmm_data) class must be implemented to use record mechanism and byte_unpack() or load() method of transaction (vmm_data) class must be implemented to use playback mechanism. Since playback avoids randomization of the transaction/corresponding scenarios, performance can be improved in case of complex transaction/scenario constraints. Also generation is not scheduling-dependent and will work with different versions of the simulator as well as with different simulators.

This record/replay mechanism can also be used to go through known states at one interface while stressing another interface with random scenarios within the same simulation itself.

The code below snippet shows how to use VMM record/playback.

task start();

`ifdef RECORD_MODE
    fork
      chan.record("Chan.dat"); //call record method of the channel
                               //with a filename
    join_none
    gen.start_xactor();       //start the generator (producer) of the  channel
                              // if it is record mode.
 `endif
 `ifdef PLAYBACK_MODE    // In playback mode, make sure that the
                         // generator (producer) is not started.
     fork begin
         bit success;
         trans tr = new;
         chan.playback(success, "Chan.dat", tr);  // call playback method of
                                               // the channel with the same file
         if (!success) begin
            `vmm_error(log, "Playback mode failed for channel");
         end
     end join_none
`endif

endtask

Posted in Optimization/Performance, Reuse | No Comments »

Using Explicitly-Phased Components in an Implicitly-Phased Testbench

Posted by JL Gray on 11th December 2009

In my last post, I described the new VMM 1.2 implicit phasing capabilities.  I also recommended developing any new code based off of implicit phasing.  Obviously, though, companies that have been using the VMM for quite some time will have developed all of their existing testbench components using explicit phasing.  It is relatively straightforward (and in some sense almost trivial) to use an explicitly phased component in an implicitly phased testbench.

Remember that the whole point of explicit phasing is that users cycle components through the desired phases by manually calling functions and tasks within the component itself. vmm_env contains the following methods:

  • gen_cfg
  • build
  • reset
  • config_dut
  • start
  • wait_for_end
  • stop
  • cleanup
  • report

vmm_subenv contains the following relevant methods:

  • new
  • configure
  • start
  • stop
  • cleanup
  • report

In an explicitly-phased environment, subenv methods are called manually by integrators, usually from the equivalent method in vmm_env. There are two approaches for instantiating a vmm_subenv-based component in an implicitly-phased testbench. The default approach is to simply allow the implicit phasing mechanism to call these explicit phases for you. Explicitly phased components are identified by the implicit phasing mechanism, and methods are called using a standard (and not entirely unexpected) mapping:

Implicit Phase Explicit Phase Called
build_ph vmm_subenv::new[1]
configure_ph vmm_subenv::configure
start_ph vmm_subenv::start
stop_ph vmm_subenv::stop
cleanup_ph vmm_subenv::cleanup
report_ph vmm_subenv::report

[1] Users must call vmm_subenv::new manually.

Now, you might want to phase your vmm_subenv in a non-standard way. If that’s the case, the first thing you’ll need to do is disable the automatic phasing. Here’s how. First, instantiate a null phase:

vmm_null_phase_def null_ph = new();

Next, override the phases you don’t want to start automatically. For example:

my_group.override_phase(“start”, null_ph);
my_group.override_phase(“stop”, null_ph);

Finally, call the explicit phases from the parent object’s implicit phases.  A complete example is shown below.

class testbench_top extends vmm_group;
bus_master_subenv bus_master;
vmm_null_phase_def null_ph = new();

function void build_ph();
bus_master = bus_master_subenv::create_instance(this, “bus_master”);
bus_master.override_phase(“start”, null_ph);
bus_master.override_phase(“stop”, null_ph);
endfunction: build_ph

task reset_ph();
bus_master.start();
// wait 1000 clocks…
bus_master.stop();
endtask: reset_ph

endclass: testbench_top

Posted in Communication, Modeling, Phasing, Reuse, VMM | No Comments »

Blocking Transport Communication in VMM 1.2

Posted by John Aynsley on 10th December 2009

image John Aynsley, CTO, Doulos

One of the new features in VMM 1.2 is the blocking transport interface, borrowed from the SystemC TLM-2.0 standard. This interface provides an alternative to the using the put and get methods of the vmm_channel class when communicating between a producer and consumer. There are a couple of reasons why you might consider using the blocking transport interface in VMM: perhaps you are trying to interface to a SystemC reference model, or perhaps you want to write a transactor that has very clean semantics when it comes to determining the start and end of a transaction. In either case, the new blocking transport interface can help.

A blocking transport call is a method call that carries with it a transaction object and is expected not to return until the transaction is complete. The status of the transaction, that is, whether the transaction succeeded or failed, is expected to be carried within the transaction object itself. You call blocking transport as follows:

class my_tx extends vmm_data; // A user-defined transaction class

enum {OKAY, FAIL} status; // Status flag embedded in the transaction itself
endclass

my_tx tx;
int delay;

$cast(tx, randomized_tx.copy()); // Create and randomize a transaction object

m_port.b_transport(tx, delay);

if( tx.m_status != OKAY ) // Check the response status
`vmm_warning(log, “Transaction failed”);

Since this is SystemVerilog, the transaction object itself is passed by reference. The delay argument instructs the recipient of the transaction to process the transaction after the given delay has elapsed, and can be set by both the caller of and the implementation of the b_transport method.

The blocking transport method needs to be implemented by the transactor that will receive or consume the transaction:

task b_transport(int id = -1, my_tx tx, ref int delay);

// Process transaction

// Pause for given delay and then reset the delay argument
#(delay);
delay = 0;

// Set the response status in the transaction object
tx.m_status = OKAY;
endtask : b_transport

By definition, blocking transport “blocks” until the transaction has been fully processed. It is coded as a SystemVerilog task so that it is allowed to execute timing controls such as the #(delay) in this example. The task is not obliged to suspend for the given delay in this manner; it could have simply returned leaving the delay without modification, or even have increased the value of the delay, leaving it for the caller to realize the delay later. The task is obliged to set the response status flag within the transaction object before returning, and the caller must check the response status on return.

One significant thing that is happening here is that communication between a producer and a consumer (or in TLM-2.0 jargon, an initiator and a target) is being accomplished without having any intervening channel to buffer the transactions. The b_transport method is called by the producer, provided by the consumer, and only returns when the transaction is complete. The advantage of not having an intervening channel is that it can help increase execution speed in the context of a very fast simulation model. The disadvantage is that it is not possible to have multiple transactions in progress given only a single execution thread in the initiator; therefore, it is a good idea to add a semaphore in the implementation of the b_transport method in the target transactor

class target_xactor extends vmm_xactor;

local semaphore m_sem = new(1);

task b_transport(int id = -1, my_tx tx, ref int delay);
m_sem.get(1);
// Process transaction

m_sem.put(1);
endtask : b_transport

This is not the whole story, because we also need to explain how to connect the producer to the consumer. Look out for future blog posts.

Posted in Communication, Reuse, SystemC/C/C++, Transaction Level Modeling (TLM) | 2 Comments »

VMM scenario generators and dependent scenarios

Posted by Avinash Agrawal on 4th December 2009

Avinash Agrawal

Avinash Agrawal, Corporate Applications, Synopsys

Often folks wonder if it possible to have a VMM scenario generator, where one scenario is dependent on another scenario.

The answer is “Yes.”

Consider the testcase below. You can define two scenarios, scn_a and scn_b, both of which have their own set of constraints. The variables generated in scn_b are a multiple of the values that were set when scn_a was generated previously, in this case by variable “ratio”. For more details on how VMM scenario generators work, refer to the VMM user guide.

Systemverilog testcase:

——————————————————————————–

class packet extends vmm_data;

rand int sa;

rand int da;

`vmm_data_member_begin(packet)

`vmm_data_member_scalar(sa, DO_ALL)

`vmm_data_member_scalar(da, DO_ALL)

`vmm_data_member_end(packet)

endclass

`vmm_channel(packet)

`vmm_scenario_gen(packet, “packet”)

class a_scenario extends packet_scenario;

int unsigned scn_a;

rand int ratio;

function new();

scn_a = define_scenario(“scn_a”, 5);

endfunction

constraint cst_a {

$void(scenario_kind) == scn_a ->  {

foreach(items[i]) {

this.items[i].sa inside {[0:100]};

this.items[i].da inside {[0:100]};

}

ratio inside {[1:5]};

}

}

endclass

class b_scenario extends packet_scenario;

int unsigned scn_b;

function new();

scn_b = define_scenario(“scn_b”, 10);

endfunction

constraint cst_b {

$void(scenario_kind) == scn_b -> {

foreach(items[i]) {

this.items[i].sa inside {[100:300]};

this.items[i].da inside {[100:300]};

}

}

}

endclass

class hier_scenario extends packet_scenario;

rand a_scenario scn_a;

rand b_scenario scn_b;

function new();

this.scn_a = new();

this.scn_b = new();

this.scn_a.set_parent_scenario(this);

this.scn_b.set_parent_scenario(this);

endfunction

virtual task apply(packet_channel channel, ref int unsigned n_insts);

this.scn_a.apply(channel, n_insts);

this.scn_b.apply(channel, n_insts);

endtask

constraint cst_hier {

foreach(scn_b.items[i]) {

// Create a scenario ‘scn_b’ depending upon ‘scn_a’

scn_b.items[i].sa inside {[scn_a.ratio*100:scn_a.ratio*800]};

scn_b.items[i].da inside {[scn_a.ratio*100:scn_a.ratio*800]};

}

}

endclass

program automatic test;

packet_scenario_gen scn_gen;

packet_channel      pkt_chan;

packet pkt;

hier_scenario scn_hier;

initial begin

scn_hier = new();

scn_gen = new(“scn_gen”, -1, pkt_chan);

scn_gen.register_scenario(“scn_hier”, scn_hier);

scn_gen.unregister_scenario_by_name(“Atomic”);

$display(“Size of the scn is %0d”, scn_gen.scenario_set.size());

scn_gen.stop_after_n_insts = 15;

scn_gen.start_xactor();

while(1) begin

#10 scn_gen.out_chan.get(pkt);

$display(“id is %0d , %0d %0d”, pkt.data_id, pkt.sa, pkt.da);

end

end

endprogram

——————————————————————————–

Posted in Reuse, Stimulus Generation, VMM | No Comments »

Verification in the trenches: Traversing object hierarchies using VMM1.2

Posted by Shankar Hemmady on 25th November 2009

AmbarSarkar Dr. Ambar Sarkar, Chief Verification Technologist, Paradigm Works Inc.

Ever get stuck trying to configure an object deep inside a verification environment? More likely than not, someone else created the environment, changed it drastically over time, and did not leave a description of the object hierarchy. It is quite time-consuming to unravel the whole hierarchical path from the root to that object. The phrase “needle in a haystack” comes to mind.

I will share some tips on how the vmm_object class, recently added to VMM1.2, can help.

As you are most likely aware, a typical verification environment today contains hundreds of classes defined by the user. Many of these are related to each other in parent/child relationships, creating several hierarchies.

As you are most likely aware, a typical verification environment today contains hundreds of classes defined by the user. Many of these are related to each other in parent/child relationships, creating several hierarchies.

Ambar_Sarkar_blog2_fig1

So how does the vmm_object class help in traversing these hierarchies? In a nutshell, it is the base class for all classes defined in the VMM library. This means that for every object derived from a class in the VMM library, you can use the same API for:

  • Finding its type
  • Finding its ancestors and children
  • Avoiding name clashes ( a new feature called namespace and we will save this topic for another post)
  • Displaying the hierarchy
  • Iterate over the objects
  • Find object by a regular expression search

The last two bullets above are what I feel help me most, because:
a. I do not have to write separate code for traversing the hierarchy of different types of objects.
b. I can locate an object easily without having to remember or figure out the exact path to it.

Here is an example. I wanted to access all the atomic generators in my environment and they were all named with the suffix GEN.

There are at least two ways to accomplish this:

1. Use the `foreach_vmm_object macro:

// Configuration phasefor test

virtual function void configure_ph();

string pattern = “@%*GEN”; // regular expression used to search for generator name

// Just iterate over all `VMM_ATOMIC_GEN objects that end with “GEN”
`foreach_vmm_object(`VMM_ATOMIC_GEN, pattern, env)
begin
// Do something with this generator
`vmm_note(log, $psprintf(“Found %s\n”, obj.get_object_name()));
end

When applicable, this approach seems like a quite concise way to get things done. Do note that the implementation of `foreach_vmm_object macro requires it to be the first statement following the declarations in a method or following a begin keyword due to the way it is implemented.

Of course, you can avoid the macro if you are so inclined as shown below. Sometimes I do this to get better visibility while debugging.

2. Use iteration class vmm_object_iter provided with this release:


vmm_object_iter iter; // iterator object
vmm_object obj;
`VMM_ATOMIC_GEN at_gen;
string pattern = “@%*GEN”; // regular expression used to search for generator name

// Just iterate over all `VMM_ATOMIC_GEN objects that end with “GEN”
iter = new(env, pattern); // Get a pointer to the iterator

obj = iter.first(); // Get the first in the list
while (obj != null) begin
// Always check type!
if (!$cast(at_gen, obj))
`vmm_error(log, $psprintf(“Unexpected type for %s. \n”, obj.get_object_name()));

// Do something with this generator
`vmm_note(log, $psprintf(“Found %s\n”, obj.get_object_name()));

obj = iter.next(); // Get the next one

end

Pretty much similar to what I expected as a use model for an iterator class.

Of course, your mileage/opinion may differ from mine. Please share how you plan on using the common vmm_object base class in your daily verification tasks.

This article is the 2nd in the Verification in the trenches series. Hope you found this article useful. If you would like to hear about any other related topic, please comment or drop me a line at ambar.sarkar@paradigm-works.com. Also, if you are starting out fresh, please check out the free VMM1.2 environment generator at http://resourceworks.paradigm-works.com/svftg/vmm .

Posted in Debug, Phasing, Reuse, VMM infrastructure | No Comments »

What Has TLM-2.0 Got To Do With It?

Posted by John Aynsley on 17th November 2009

JohnAynsley

John Aynsley, CTO, Doulos

You may have noticed that the public release of VMM 1.2 is just around the corner, and with this new version of VMM comes the introduction of features inspired by the SystemC TLM-2.0 standard.

Excuse me! TLM-2.0? What? Why do we need features from SystemC in VMM?

I will set out to answer that question fully in a series of blog posts over the coming months. But first off I will remark that the idea is not so strange. After all, VMM has always been transaction-level (with a small ‘t’ and ‘I’). Communication within a VMM verification environment exploits transaction-level modeling for speed and productivity, because “TLM” is about abstracting the model of communication used in a simulation. If we can adopt a common standard for transaction-level modeling across both SystemC and SystemVerilog, that has to be a good thing for everyone. It is evident that the design and verification community demands more than one language standard (witness VHDL, SystemVerilog, C/C++, and SystemC). Each individual language standards has progressed over time by borrowing the best features from the others. Having VMM borrow features from SystemC makes it easier to learn and work with both standards.

The other natural link between VMM and SystemC is that mixed-language simulation environments and C/C++ reference models are not unusual. Virtual platform models, as used for software development and architectural exploration, are growing in importance, and the SystemC TLM-2.0 standard is used to achieve interoperability between the components of a virtual platform model. If a constrained random VMM environment is to be used with a reference model that consists of a virtual platform adhering to the SystemC TLM-2.0 standard, then having TLM-2.0 support within VMM promises to make life easier for the VMM programmer.

Besides interoperability, the other main objective of the SystemC TLM-2.0 standard is simulation speed. The combination of speed and interoperability is achieved by the technical details of the ways in which transactions are passed between components. Fortunately, those technical details are a good fit with the way communication has always worked in VMM. In particular, both VMM and TLM-2.0 support the idea that each transaction has a finite lifetime with a well-defined time-of-birth and time-of-death.

The SystemC TLM-2.0 standard is based on C++. Unfortunately, not all C++ coding idioms translate naturally into SystemVerilog, so the transaction-level communication in VMM 1.2 is “inspired by” the TLM-2.0 standard rather than being a literal rendition of it.

So what are these new features? If you are already a SystemC user you may recognize ports and exports, borrowed directly from the SystemC standard, and analysis ports, transport interfaces, sockets and the generic payload, borrowed from the TLM-2.0 standard. I will explain how VMM is able to exploit each of these features in future blog posts, so watch this space…

Posted in Interoperability, Reuse, SystemC/C/C++, Transaction Level Modeling (TLM) | No Comments »

A generic functional coverage solution based on vmm_notify

Posted by Wei-Hua Han on 15th June 2009

Weihua Han, CAE, Synopsys

Functional coverage plays an essential role in Coverage Driven Verification. In this blog, I’ll explain a modular way of modeling and implementing  functional coverage models.

SystemVerilog users can take the advantage of  the “covergroup” construct to implement functional coverage. However this is not enough. The VMM methodology provides some important design-independent guidelines on how to implement functional coverage in a reusable verification environment.

Here are a few guidelines  from the VMM book that I consider very important for implementing a functional coverage model:

“Stimulus coverage shall be sampled after submission to the design” because in some cases it is possible that not all generated transactions will be applied to DUT.

“Stimulus coverage should be sampled via a passive transactor stack” for the purpose of reuse so that the same implementation can be used in a different stimulus generation structure. For example in another verification environment, the stimulus may be generated by a design block instead of testbench component.

“Functional coverage should be associated with testbench components with a static lifetime” to avoid creating a large number of coverage group instances. So “Coverage groups should not be added to vmm_data class extensions”. These static testbench components include monitors, generators, self-checking structure, etc.

“The comment option in covergroup, coverpoint, and cross should be used to document the corresponding verification requirements”.
You can find some more details on these and other functional coverage related guidelines in the VMM book, pages 263-277.

In an earlier post “Did you notice vmm_notify?”, Janick showed how vmm_notify can be used to connect a transactor to a passive observer like a functional coverage model. Here, let me borrow his idea to implement the following VMM-based functional coverage example.

1.    Transaction class

1. class eth_frame extends vmm_data;
2.    typedef enum {UNTAGGED, TAGGED, CONTROL} frame_formats_e;
3.    rand frame_formats_e format;
4.    rand bit[3:0] src;
5.     …
6. endclass

There is some random property defined in the transaction class.  We would like to collect the coverage information for these properties in our coverage class, for example “format” and “src”.

2.    A generic subscribe class

1.  class subscribe #(type T = int) extends vmm_notify_callbacks;
2.     local T obs;
3.     function new(T obs, vmm_notify ntfy, int id);
4.        this.obs = obs;
5.        ntfy.append_callback(id, this);
6.     endfunction
7.     virtual function void indicated(vmm_data status);
8.          this.obs.observe(status);
9.       endfunction
10. endclass

The generic subscribe class specifies the behavior for “indicated”method which will be called when vmm_notify “indicate” method is called. Then with using this generic subscribe class, functional coverage and other observer models only need to implement the desired behavior with “observe” method. Please note that in line 8 the vmm_data object is passed to “observe” method through vmm notification status information.

3.    Coverage class

1.   class eth_cov;
2.      eth_frame tr;

3.      covergroup cg_eth_frame(string cg_name,string cg_comment,int cg_at_least);
4.         type_option.comment = “eth frame coverage”;
5.         option.at_least = cg_at_least;
6.         option.name=cg_name;
7.         option.comment = cg_comment;
8.         option.per_instance=1;
9.         cp_format:coverpoint tr.format {
10.         type_option.weight=10;
11.       }
12.       cp_src: coverpoint tr.src {
13.          illegal_bins ilg = {4′b0000};
14.         wildcard bins src0[] = {4′b0???};
15.         wildcard bins src1[] = {4′b1???};
16.         wildcard bins src2[] = (4′b0??? => 4′b1???);
17.      }
18.       format_src_crs: cross cp_format, cp_src {
19.          bins c1 = !binsof(cp_src) intersect {4’b0000,4’b1111 };
20.      }
21.    endgroup

22.    function new(string name=”eth_cov”, vmm_notify notify, int id);
23.       subscribe #(eth_cov) cb=new(this,notify,id);
24.       cg_eth_frame = new(“cg_eth_frame”,”eth frame coverage”,1);
25.    endfunction
26.    function void observe (vmm_data tr);
27.       $cast(this.tr,tr);
28.       cg_eth_frame.sample();
29.    endfunction
30. endclass

The coverage group is implemented in coverage class eth_cov. And this coverage class is registered to one vmm_notify service through scubscribe class. The coverage group is sampled in “observe” method so when the notification is indicated the interesting properties will be sampled.

4.    Monitor

1.   class eth_mon extends vmm_xactor;
2.      int OBSERVED;
3.      eth_frame tr; // Contains reassembled eth_frame transaction

4.      function new(…)
5.         OBSERVED = this.notify.configure();
6.         …
7.      endfunction
8.      protected virtual task main();
9.         forever begin
10.          tr = new;
11.          //catch transaction from interface
12.         …
13.         this.notify.indicate(OBSERVED,tr);
14.         …
15.       end
16.    endtask
17. endclass

The monitor extracts the transaction from the interface then it indicates the notification with the transaction as the status information (line 13).

5.    Connect monitor and coverage object

1.   class eth_env extends vmm_env;
2.      eth_mon mon;
3.      eth_cov cov;
4.      …
5.      virtual function void build();
6.         …
7.         mon = new(…);
8.         cov = new(“eth_cov”,mon.notify,mon.OBSERVED);
9.         …
10.    endfunction
11.     …
12. endclass

In the verification environment, the coverage object is created with the monitor notification service interface and the proper notification ID.

There are other ways to implement functional coverage in VMM based environments.  For example a callback-based implementation is used in the example located under sv/examples/std_lib/vmm_test in the VMM package which can be downloaded from www.vmmcentral.org.

I haven’t discussed assertion coverage in this post, which are another important type of “functional coverage”.  If you are interested in using assertions for functional coverage please check out chapter 3 and chapter 6 in the VMM book for its suggestions.

Posted in Communication, Coverage, Metrics, Register Abstraction Model with RAL, Reuse, SystemVerilog, VMM | 4 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, Register Abstraction Model with RAL, Reuse, Structural Components, VMM | 8 Comments »

Multi-stream Scenario Generator (MSS)

Posted by Amit Sharma on 15th May 2009

Amit  Sharma, CAE Manager, Synopsys

Multi-stream scenario generation is an important feature recently added to VMM. It targets generation and coordination of stimulus across multiple interfaces. It also allows hierarchical layering of scenarios and the reuse of block level scenarios at system level.

Multi-stream scenarios are described by extending the vmm_ms_scenario class. They can be composed of individual transactions, procedural code, existing single-stream scenarios as well as other multi-stream scenarios. Depending on your requirements, a multi-stream scenario can be either single threaded, multi threaded or reactive.

In a nutshell, you simply need to provide an implementation of the vmm_ms_scenario::execute() task. Whatever the behavior of this method is, it is what defines the multi-stream scenario. This method can execute single/multiple transactions/scenarios. It can execute procedural code. It can read a file. It can do anything SystemVerilog code can do.

Let’s take a look at the example that comes with the VMM 1.1 release (available in sv/examples/std_lib/mss_simple directory). This example has a multi-stream scenario controlling the execution of two independent single stream scenarios: my_atm_scenario and my_cpu_scenario. Let’s see how the multi-stream scenario called my_scenario controls the execution of these two scenarios.

1. class my_scenario extends vmm_ms_scenario;

2.    my_atm_cell_scenario atm_scenario;

3.    my_cpu_scenario cpu_scenario;

4.    atm_cell_channel atm_chan;

5.    cpu_channel cpu_chan;

6.

7.    int MSC = this.define_scenario(“Multistream SCENARIO”, 0);

8.    local bit [7:0] id;

9.

10.   function new(bit [7:0] id);

11.       super.new(null);

12.       atm_scenario = new(id);

13.       cpu_scenario = new();

14.       this.id = id;

15.   endfunction: new

16.

17.   task execute(ref int n);

18.       fork

19.         begin

20.            atm_cell_channel atm_chan;

21.            int unsigned nn = 0;

22.            $cast(atm_chan, this.get_channel(“ATM_SCENARIO_CHANNEL”));

23.             atm_scenario.randomize with {length == 1;};

24.             atm_scenario.apply(atm_chan, nn);

25.             n += nn;

26.         end

27.         begin

28.             cpu_channel cpu_chan;

29.             int unsigned nn = 0;

30.             $cast(cpu_chan,this.get_channel(“CPU_SCENARIO_CHANNEL”));

31.             cpu_scenario.randomize with {length == 1;};

32.             cpu_scenario.apply(cpu_chan, nn);

33.             n += nn;

34.         end

35.       join

36.   endtask: execute

37. endclass: my_scenario

In lines 1-3, my_scenario extends vmm_ms_scenario and declares single stream scenarios. These single-stream scenario members,if prefixed with the rand keyword, would be implicitly randomized before execution.

In lines 17-36, the virtual execute() method is overridden with the user code to implement the scenario. In this example, atm_scenario and cpu_scenario are randomized and executed in parallel threads. If VMM single stream scenarios are used (as in this example), then the apply()method of the scenarios is called to send the transactions of the scenario through the appropriate VMM channel. Thus there is a very close correlation between the procedural “apply()” method of single stream scenarios and the “execute()” method of multi-stream scenarios. If multi-stream sub-scenarios had been instantiated in the above scenario, the “execute()” method of the child scenarios would be invoked instead of the apply() method.

A multi-stream scenario can connect to any channel interface in the testbench environment. In lines 22 and 30, the get_channel() method is used to get the appropriate vmm_channel handle by specifying their logical names “ATM_SCENARIO_CHANNEL” and “CPU_SCENARIO_CHANNEL” . All you need to do is to register the different channels to the multi-stream generator under these logical names.

Now that we have created our multi-stream scenario, let’s see how to use it in our verification environment. The following code snippet shows how channels and scenarios are registered with the multi-stream scenario generator, how they are started and used by other transactors.

1. my_scenario sc0 = new(0);

2. ……

3. atm_cell_channel atm_chan = new(“ATM CELL CHANNEL”, “TEST”);

4. cpu_channel cpu_chan = new(“CPU CELL CHANNEL”, “TEST”);

5. ….

6. ….

7. gen.register_channel(“ATM_SCENARIO_CHANNEL”, atm_chan);

8. gen.register_channel(“CPU_SCENARIO_CHANNEL”, cpu_chan);

9. gen.register_ms_scenario(“SCENARIO_0″, sc0);

10. ….

11. `

12. gen.stop_after_n_scenarios = 10;

13.

14. …

15. gen.start_xactor();

16. gen.notify.wait_for(vmm_ms_scenario_gen::DONE);

17. …

In line 1-4, the multi-stream scenario and channels are created.

In lines 7-8, channels are registered with the generator through the register_channel() method under the logical names “ATM_SCENARIO_CHANNEL” and “CPU_SCENARIO_CHANNEL”. Scenarios will subsequently get the channel handles through the get_channel() method by specifying these names as discussed earlier.

In line 9, the scenario is registered with the generator through the register_ms_scenario() method. Any number of scenarios can be registered and their order of execution can be controlled. The number of scenarios to be executed is controlled through the stop_after_n_scenarios field of the generator.

With these simple steps, you can quickly implement interesting multiple stream scenarios to stress your DUT interfaces. Thanks to this functionality, you can easily reuse existing single stream scenarios and/or other multi-stream scenarios in a hierarchical way. You can instantiate other scenarios and ‘execute’ them. In case of a single stream scenario, its apply() method has to be called after it is randomized and in case of a multi stream scenario, execute() method has to be called after randomization. Any registered multi stream scenario can be obtained by another scenario by its logical name. And thus, a hierarchical scenario library with increasing complexity can be easily created.

Last but not least, multi-stream scenarios can be vertically reused, where coordination between various generators is a must have. VMM allows a multi-stream scenario generator to be registered with another generator and have that top-level generate execute the scenarios of its sub-generators. This allows top level generator to control and synchronize the execution of scenarios of other generators. I’ll address hierarchical multi-stream scenarios in my next blog. Stay tuned.

Posted in Reuse, Stimulus Generation | 13 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 »