VMM Central Verification Martial Arts

Improved productivity with VCS assertion indicators

Posted by Srinivasan Venkataramanan on March 10th, 2010

Srinivasan Venkataramanan, CVC Pvt. Ltd.

Abhishek Muchandikar, Sr RnD Engineer, Verification Group, Synopsys

Sadanand Gulwadi, Sr. Staff CAE, Verification Group, Synopsys Inc., Mt. View, CA, USA

Assertions for protocol checking has been popular for quite some time now. With several off-the-shelf assertion/monitor IPs available from EDA vendors and providers such as CVC (www.cvcblr.com), end users need not have to spend too much time thinking about what assertions to add in their designs, how to code them etc. All that users would need to do is to create suitable bind files and off they go!

While using assertions sounds simple and straightforward, there are scenarios for which users need to watch out. For example, if assertions did fire, then you have likely found a bug. However if assertions remained silent throughout a simulation, then you cannot afford to be too happy, for the assertions may have been “totally vacuous”. This is a term that my ex-colleague and I coined during our work at a large DSP customer here. They were adding assertions and looking for improved productivity early on rather than having to go through detailed assertion coverage metrics, various dump files, and so forth at a later stage While standard garden variety vacuity is quite well understood and explained in our regular SVA trainings (http://www.cvcblr.com/trng_profiles/CVC_LG_SVA_profile.pdf), the concept of “totally vacuous” is a step beyond that. Totally vacuous assertions are those that were *never* successful during the entire simulation run. This can be due to a few reasons:

1. The assertions were never attempted at all (perhaps the CLOCK was undriven, or was part of a gated clock of a low power domain etc.)

2. The antecedent of a property was never satisfied etc.

Further, observation of the assertion behaviour stated above might indicate the following potential issues in your design or verification plan:

1. Incorrect clock network connections, clock enables, and so forth.

2. Test-case is weak and does not address a key portion of your design (a coverage hole).

For instance, consider the following assertion (taken from our SVA handbook, www.systemverilog.us/sva_info.html)

/* Behavior: if the transfer type is BUSY then on the corresponding

data transfer , the slave must provide a zero wait state OKAY response

(default clock) */

property pResponseToBusyMustBeZeroWaitOKAY (hResp,hReady,hTrans);

@(posedge hclk) (((hTrans == BUSY) && (hReady == 1)) |->

##1 ((hResp == OKAY) && (hReady == 1)) );

endproperty: pResponseToBusyMustBeZeroWaitOKAY

Consider the case when the stimulus didn’t drive the hTrans to be BUSY throughout the simulation. This is strange and means that the stimulus is weak. However when (and how) does a user find this out? With the usual wisdom of “No news is GOOD news”, it is very easy to ignore this important coverage hole and move on with other work. However, the customer desired such strange assertion behaviour to be flagged as early as possible – at the end of every simulation run.

VCS does have the ability to catch such “totally vacuous” assertions and accordingly reports the following at the end of a simulation run:

**** Following assertions did not fire at all during simulation. *****

"/proj/cvc_mips/ahb_mip/master.sva ", 48:

a_pResponseToBusyMustBeZeroWaitOKAY: Antecedent was never satisfied

"/proj/cvc_mips/ahb_mip/slave.sva ", 32:

a_pLowPowerGclk: No attempt started

The above output is default in VCS without the need for any additional user-driven options and frees the user from the additional steps of enabling assertion coverage, debug, etc. Assertion coverage and debug are indeed powerful features for analyzing problems, but should be turned on only after ensuring the assertions are not “totally vacuous” – a glaring weakness that requires being flagged early on by default. It is all about productivity at the end of the day – if a tool can help improve productivity it is always welcome :-)

So the next time VCS prints such a message, you had better watch out before calling it a day!

Posted in Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Analysis Ports in VMM 1.2

Posted by John Aynsley on March 3rd, 2010

JohnAynsley

 

John Aynsley, CTO, Doulos

 

 

Analysis ports are another feature from the SystemC TLM-2.0 standard that has been incorporated into VMM 1.2. Analysis ports provide a mechanism for distributing transactions to passive components in a verification environment, such as checkers and scoreboards.

Analysis ports and exports are a variant on the TLM ports and exports that I have discussed in previous blog posts. The main difference between analysis ports and regular ports is that a single analysis port can be bound to multiple exports, in which case the same transaction is sent to each and every export or “subscriber” or “observer” connected to the analysis port. The terms subscriber and observer are used interchangeably in the VMM documentation.

Let us take a look at an example:

class my_tx extends vmm_data;  // User-defined transaction class
  …

class transactor extends vmm_xactor; 
  vmm_tlm_analysis_port #(transactor, my_tx) m_ap;   // The analysis port
  … 
  virtual task main;
    my_tx tx;
    …
    m_ap.write(tx);
    …

The transactor above sends a transaction tx out through an analysis port m_ap.

The type of the analysis port is parameterized with the type of the transactor and of the transaction my_tx. The call to write sends the transaction to any object that has registered itself with the analysis port. There could be zero, one, or many such observers registered with the analysis port.

To continue the example, let us look at one observer:

class observer extends vmm_object; 
  vmm_tlm_analysis_export #(observer, my_tx) m_export;
  function new (string inst, vmm_object parent = null);
    …
    m_export = new(this, "m_export");
    …

  function void write(int id, my_tx tx);
    …

The observer has an instance of an analysis export and must implement the write method that the export will provide to the transactors. Note that the observer extends vmm_object. Since an observer is passive, it need not extend vmm_xactor.

The analysis port may be bound to any number of observers in the surrounding environment:

class tb_env extends vmm_group; 
  transactor  m_transactor;
  observer    m_observer_1;
  another     m_observer_2;
  yet_another m_observer_3;
  virtual function void build_ph;
    m_transactor = new( "m_transactor", this );
    m_observer   = new( "m_observer",   this );
    …
  endfunction
  virtual function void connect_ph;  
    m_transactor.m_ap.tlm_bind( m_observer_1.m_export );
    m_transactor.m_ap.tlm_bind( m_observer_2.m_export );
    m_transactor.m_ap.tlm_bind( m_observer_3.m_export );
    …

Note the use of the predefined phase methods from VMM 1.2. Transactors are created during the build phase, and ports are connected during the connect phase.

Finally, let us compare analysis ports with VMM callbacks:

  m_ap.write(tx);
versus
  `vmm_callback(callback_facade, write(tx));

The effect is very similar, but there are differences. Unlike VMM callbacks, the name of the method called through an analysis port is fixed at write. A VMM callback method is permitted to modify the transaction object, whereas a transaction sent through an analysis port cannot be modified. When multiple callbacks are registered, the prepend_callback and append_callback methods allow you to determine the order in which the callbacks are made, whereas you have no control over the order in which write is called for multiple observers bound to an analysis port. Because of these differences, only VMM callbacks are appropriate for modifying the behavior of transactors. Analysis ports are only appropriate for sending transactions to passive components that will not attempt to modify the transaction object. On the other hand, that in itself is the feature and strength of analysis ports; they are only for analysis.

It can make sense to combine a VMM callback with an analysis port in the same transactor, using the callback to inject an error and the analysis port to send the modified transaction to a scoreboard, for example:

  `vmm_callback(callback_facade, inject_error(tx));
   m_ap.write(tx);

In this situation, the VMM recommendation is to make the analysis call after the callback, as shown here.

Posted in Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Managing VMM Log verbosity in a smart way

Posted by Srinivasan Venkataramanan on March 1st, 2010

Srinivasan Venkataramanan, CVC Pvt. Ltd.

Vishal Namshiker, Brocade Communications

Any complex system requires debugging at some point or the other. To ease the debug process, a good, proven coding practice is to add enough messages for the end user to aid in debug. However as systems become mature the messages tend to become too many and quickly users feel a need for controlling the messages. VMM provides a comprehensive log scheme that provides enough flexibility to let users control what-how-and-when to see certain messages (See: http://www.vmmcentral.org/vmartialarts/?p=259).

As we know the usage of `vmm_verbose/`vmm_debug macros requires the +vmm_log_default=VERBOSE run time argument. However when using this, there are tons of messages coming from VMM base classes too – as they are under the VERBOSE/DEBUG severity. Users at Brocade did not prefer to have these messages when debugging problems in user code. Parsing through these messages and staying focussed on debugging the problem at hand was tedious if post-processing of the log file was not implemented. Sure the messages from VMM base classes are useful to one set of/class of problems, but if the current problem is with user code, user would like to be able to exclude them easily. An interesting problem of contradictory requirements perhaps? Not really, VMM base class is well architected to handle this situation.

In VMM, there are two dimensions to control which messages user would like to see. The verbosity level specifies the minimum severity to display and you’ll see every message with a severity greater to equal to it. The other dimension/classification is based on TYPE. There are several values for the TYPE such as NOTE_TYP, DEBUG_TYP etc. Most relevant here is the INTERNAL_TYP – a special type intended to be used exclusively by VMM base class code. All debug related VMM library messages are classified under INTERNAL_TYP. You can use vmm_log::disable_types() method.

A quick example to do this inside the user_env is below:

virtual function void my_env::build();
  super.build();
  …
  this.log.disable_types(.typs(vmm_log::INTERNAL_TYP),
    .name(“/./”),.inst( “/./”) );
endfunction : build

This is a typical usage if everyone in the team agrees to such a change. However if a localized change is needed for few runs alone, one can combine the power of VCS’s Aspect Oriented Extensions (AOE) made to SystemVerilog. In this case, user supply a separate file as shown below:

///////////  disable_vmm_msg.sv
extends disable_log(vmm_log);
after function new(string name = “/./”,
    string instance = “/./”,
    vmm_log under = null);
  this.disable_types(.typs(vmm_log::INTERNAL_TYP));
endfunction:new

endextends

Add this file to the compile list and voila! BTW, during recent SystemVerilog extensions discussion at DVCon 2010, AOP extensions are being requested by more users to be added to the LRM standard. With its due process, a version of AOP is likely to be added to the LRM in the future (let’s hope in the “near future” :) ).

Posted in Debug, SV, VMM, vmm_log | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Using the Datastream Scoreboard Iterators

Posted by Amit Sharma on February 23rd, 2010

varVarun S, CAE, Synopsys


Datastream scoreboard iterators are objects that know how to traverse and navigate the
implementation of the scoreboard. They provide high level methods for moving through the
scoreboard and modifying its content at the location of the iterator.

The actual data structure used to implement the datastream scoreboard is entirely private
to the implementation of the foundation classes. However, for implementing user-defined
functionality, the entire content of the scoreboard should be made available to the user,
so that it can be searched and modified. You can do this using iterators without exposing
the  underlying implementation.

Logical streams:

Datastream applications involve transmission, multiplexing, prioritization, or transformation of
data items. A stream in general is a sequence of data elements. In VMM, a stream is composed of
transaction descriptors based on the vmm_data class. A stream of data elements flowing between two
transactors through a vmm_channel instance or to the DUT through a physical interface is a structural
stream. Through multiplexing, it maybe composed of data elements from different inputs or destined to
different outputs. If there is a way to identify the original input a data element came from or the
destination a data element is going to, a structural stream can be said to be composed of mutiple
logical substreams. It is up to you to define the logical streams based on the nature of the
application you are trying to verify.

For example, if you are verifying a router with 16 input and 16 output ports, all data packet going
from 0th input port to 0th output port can be viewed as belonging to the same logical stream giving
rise to a total of 256 logical streams.

Kinds of iterators:

1. Scoreboard iterators (vmm_sb_ds_iter class) - These move from one logic stream of expected data to
   another. An instance is created using vmm_sb_ds::new_sb_iter() method.

2. Stream iterators (vmm_sb_ds_stream_iter class) - These move from one data element to another on a
   single logical data stream. An instance is created using vmm_sb_ds_iter::new_stream_iter() method.

		-----------------------------------------
		|  d00	|  d01	|  d02	|	|  d0n	|	^
		-----------------------------------------	|
								|
		-----------------------------------------	|
		|  d10	|  d11	|  d12	|	|  d1n	|	|
		-----------------------------------------	|
								|vmm_sb_ds_iter
		-----------------------------------------	|
		|	|	|	|	|	|	|
		-----------------------------------------	|
								|
		-----------------------------------------	|
		|  dm0	|  dm1	|  dm2	|	|  dmn	|	v
		-----------------------------------------

			vmm_sb_ds_stream_iter

The figure above shows 'm' logical streams, each stream consisting of 'n' data elements. As previously
mentioned, logical streams are user-defined based on the application and within the scoreboard they
correspond to data queues into which data elements are stored. Once the queues are populated, if the
user wishes to modify a data element or delete a few of them, he can do so by the use of iterators.

A set of high level methods have been implemented within the iterator classes, which aid in navigating
through the data queued up in the scoreboard and modify it, if necessary. The methods first(), next(),
last(), prev(), length() are some of the methods that have been implemented within both the iterator
classes.

Note : Logical streams only exist if they contain(or have contained in the past) expected data and the
iterator will only iterate over logical streams that exist.

Sample code:

     class my_scoreboard extends vmm_sb_ds;
           /* creates a scoreboard iterator that iterates over different streams of data */
           vmm_sb_ds_iter iter_over_streams = this.new_sb_iter();  

           for(iter_over_streams.first(); iter_over_streams.is_ok(); iter_over_streams.next()) begin

               /* a stream iterator that scans the data within a stream */
               vmm_sb_sb_stream_iter scan_stream = iter_over_streams.new_stream_iter(); 

               if(scan_stream.find(pkt)) begin
                   repeat(scan_stream.pos()) begin
                       scan_stream.prev();
                       scan_stream.delete();
                   end
               end
           end
     endclass

The sample code shows a very simple scoreboard. Here, a scoreboard iterator "iter_over_streams" is
instantiated and set to iterate over all the streams using the for loop. 

The method first() sets the scoreboard iterator on the first stream in the scoreboard.

The method is_ok() returns TRUE if the iterator is currently positioned on a valid stream, returns
FALSE if the iterator has moved beyond the valid streams.

The method next() moves the iterator to the next applicable stream. A stream iterator "scan_stream"
is then created to iterate over the stream on which "iter_over_streams" is positioned. 

The find() method used in the if-statement is used to locate the next packet in the stream matching
the specified packet "pkt". The repeat loop is used to delete all the packets before the found packet. 

The method pos() returns the current position of the iterator, prev() moves the iterator to the
previous packet and delete() deletes the packet at the position where the iterator is positioned.

Hence, you can see that you can easily traverse across the different streams easily to find/alter any
specific packets  that you need to. Hope you find this useful and use these iterators to make your
verification tasks simpler.

For more information about the complete list of methods, see the VMM Datastream Scoreboard User Guide.

Posted in Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Drivers on a Tristate bus….

Posted by Srivatsa Vasudevan on February 18th, 2010

Tri-state busses are typically present in a verification environment when we have multiple drivers driving a bus. One of the drivers drives the bus and the rest of the drivers on the bus present high impedance to the bus. By far and large, it is preferred to have a single interface from the testbench side to deal with the tristate bus. This typically helps avoid bus contention.

In some circumstances, this may not be easily possible.

Why don’t you just imagine having to elaborate a design, run to a certain point and run a drivers() command? I’m not sure any of us is looking forward to that are we? Especially if we have to do it again and again.

dummy_signal

If you’re going to do have a tristate bus, one tip would be to add a simple internal signal to the driver that is asserted whenever you’re going to drive a tristate bus. In a dump, that will show up.

The only other alternative would be to run a trace drivers command with a debug switch (-debug_all) to find the contention.

The simple internal signal will save you a lot of debug time and will get thrown out by synthesis if you mark it with the appropriate pragma’s and do it right….

More later… Stay tuned…

Posted in Debug | 1 Comment »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Handling Incoming Transactions from Multiple Sources in VMM 1.2

Posted by John Aynsley on February 16th, 2010

JohnAynsley John Aynsley, CTO, Doulos

 

In the previous post I described TLM ports and exports from VMM 1.2. In this post, we will look at how to handle incoming transactions from multiple sources, that is, multiple producers connected to a single consumer. VMM 1.2 provides two separate mechanisms to handle this situation: peer ids, and shorthand macros. We will explore what these mechanisms have in common, and also the differences between them.

We are discussing the following situation, where two separate producer instances send transactions to a single consumer:

class producer extends vmm_xactor;

    vmm_tlm_b_transport_port #(producer, my_tx) m_port;
    …
    m_port.b_transport(tx, delay);
    …

class consumer extends vmm_xactor;

    vmm_tlm_b_transport_export #(consumer, my_tx) m_export;

    function new (string inst, vmm_object parent = null);
        super.new(get_typename(), inst, -1, parent);
        m_export = new(this, "m_export", 2); // 3rd argument = max # bindings
    endfunction

    function void start_of_sim_ph;
        vmm_note(log, $psprintf("Number of peers = %d", m_export.get_n_peers()));
    endfunction

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

class my_env extends vmm_group;

    producer m_producer_1;
    producer m_producer_2;
    consumer m_consumer;

    virtual function void connect_ph;
        m_producer_1.m_port.tlm_bind( m_consumer.m_export, 0 ); // 2nd argument = id
        m_producer_2.m_port.tlm_bind( m_consumer.m_export, 1 );
    endfunction
    …

The first thing to notice is the connect_ph method of the environment, which binds two separate ports to the same export. The tlm_bind method takes a second argument, the peer id, which allows transactions from the two ports to be distinguished.

The second thing to notice is that when the export is instantiated, the constructor new takes a third argument that specifies the maximum number of bindings to this export. The default value of 1 would be inadequate in this case, since the export is bound twice.

Thirdly, the method get_n_peers called from start_of_sim_ph returns the number of peers, which would be 2 in this case.

Finally, the first argument to the b_transport method implemented in the consumer is the peer id passed to the tlm_bind method. The implementation of b_transport can now use the peer id to distinguish between transactions from the two producers.

So much for peer ids. Now let us take a look at the alternative, that is, shorthand macros. Instead of binding two ports to a single export, we could have used the shorthand macros to create two separate exports:

class consumer extends vmm_xactor;

    `vmm_tlm_b_transport_export(_1) // Argument is suffix to name
    `vmm_tlm_b_transport_export(_2)
    vmm_tlm_b_transport_export_1 #(consumer, my_tx) m_export_1;
    vmm_tlm_b_transport_export_2 #(consumer, my_tx) m_export_2;

    task b_transport_1(int id = -1,
    my_tx trans, ref int delay);
    …

    task b_transport_2(int id = -1,
        my_tx trans, ref int delay);
    …

The argument passed to the macro is used as the suffix for a new type name and a new method name. Those new types are then used to create two separate exports, and the consumer contains two separate and differently named implementations of the b_transport method, one for each export. It is good practice to use the same suffix when naming the export members themselves (e.g. m_export_1), though this is not strictly necessary. Since peer ids are not being used, the id argument to b_transport will have the value 0 for both methods.

As usual, ports are bound to exports in the surrounding environment, but this time using separate exports rather than peer ids:

class my_env extends vmm_group;

    producer m_producer_1;
    producer m_producer_2;
    consumer m_consumer;

    virtual function void connect_ph;
        m_producer_1.m_port.tlm_bind( m_consumer.m_export_1 );
        m_producer_2.m_port.tlm_bind( m_consumer.m_export_2 );
    endfunction
    …

In conclusion, we have seen peer ids and shorthand macros used to accomplish the same thing, that is, multiple producers sending transactions to a single consumer. With peer ids we instantiate a single export and provide a single b_transport method, distinguishing between the incoming transactions using the peer id argument. With shorthand macros we instantiate two exports and provide two implementations of b_transport, distinguished by the suffix to their names.

Posted in Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Customizing VMM Memory Allocation Manager to cater to your requirements

Posted by Amit Sharma on February 11th, 2010

Rakshit Singhal, Nvidia, Amit Sharma, Synopsys

The VMM MAM (Memory Allocation Manager) package offers the capability to dynamically manage memory shared across multiple clients. It helps to simulate HW memory usage patterns and guarantees memory block allocation based on constraints.

This utility is centered on a set of four base classes with configurable memory address ranges and allocation schemes.

  • vmm_cfg: This class is used to specify the memory managed by an instance of a “vmm_mam” memory allocation manager class.
  • vmm_mam: This class is a memory allocation management utility similar to C’s malloc() and free(). A single instance of this class is used to manage a single, contiguous address space.
  • vmm_mam_allocator: An instance of this class is randomized to determine the starting offset of a randomly allocated memory region. This class can be extended to provide additional constraints on the starting offset.
  • vmm_mam_region: This class is used by the memory allocation manager to describe allocated memory regions. Instances of this class should not be created directly.

The mode of operation is as follows. An instance of “vmm_mam” class would use the “vmm_cfg” class handle to determine the minimum, maximum addresses and difference allocation schemes (mode and locality) of a memory space. A memory space can be reconfigured with new min, max and allocation scheme at run-time through the “vmm_cfg” class with the exception that number of bytes per memory location cannot be modified once a “vmm_mam” instance has been constructed. Additionally the currently allocated regions must fall within the new address space. On every call of request_region() function “vmm_mam” randomizes “vmm_mam_allocator” instance to determine the ‘start_offset’ of the randomly allocated memory region. This region is represented by an instance of “vmm_mam_region” class. Now if this “vmm_mam” is associated with a “vmm_ral_mem” instance, this “vmm_mam_region” can be used to perform read/write operation on the actual memory block through RAL. Thus we have a simple and easy-to-use framework to constrain different kinds of allocation requests based on specific requirements.

However, there are generally additional requirements in different verification environments. For example, a memory map can have a non-contiguous space allocated as device memory and various devices can simply request a portion of this region. Also for a PC memory model, it is required that each memory region has a clear ownership so that we can verify the memory accesses from various clients in system follow the design rules. Moreover some system would work in multiple address modes such as 32-bit or 64-bit mode and depending on which addressing mode is selected the complete memory management and allocation changes. The other requirements were to provide memory owners some control over the alignment of the base addresses of the requested regions. For example a USB driver in a system may ask for a memory region with its base address aligned to cacheline size or page size. It may also be required to make memory allocation and access management to be self checking for ease of verification. For example a cacheable region is accessed only by the designated owners or to build some backdoor polling on specific memory locations to trap memory accesses etc. Portability and extendibility of such environment was also a big concern.

To sum it up all we needed three primary extensions to VMM MAM; one, the ability to independently constrain each allocation/de-allocation request such that memory attributes could be set to the memory map; two, the flexibility to hook up any memory model with VMM MAM to achieve a centralized memory allocation management which could handle multiple memory implementations; three, a place holder to built self checking routines, polling routines and front-door/back-door memory accesses such that we could automate the whole memory verification.

To allow individual clients a control over the attributes set to the memory regions and to choose a specific type of memory we extended the following VMM MAM classes.

1. vmm_mam_allocator: – This has been extended to include features like addressing mode, memory type and address alignment by adding in new variables. Addressing mode specifies the address size i.e. if the unit/system requires a 32bit, 64bit etc addressing. Memory type attribute specifies the allocation of memory of the similar type within a specified range. Example: All the MMIO, write-cache, write-back etc addresses can be restricted to a specific region in memory and allocation can be done for each type from within their sub-regions. Address alignment specifies if the required allocation has to be a cache-aligned, word-aligned etc

  1. typedef enum {R, RW, RSVD} mem_acc_t;
  2. typedef enum {NONE, Above4GB, Below4GB, PreFetch, Cacheable} mem_attr_t;
  3. typedef enum {PCIE, SATA, MAC, USB, USB3} mem_owner_t;
  4. typedef enum {BYTE_ALIGN, WORD_ALIGN, CACHE_ALIGN, PAGE_ALIGN}address_align_t;
  5. typedef enum {BIT32, BIT40, BIT64} address_mode_t;
  6. class nv_mam_allocator extends vmm_mam_allocator;
  7.    address_align_t addr_align;
  8. .
  9. .
  10. .
  11.    // system memory allocation
  12.    constraint sys_mem_alloc_cons {
  13.       this.start_offset > 64′hFFFF; // reserved memory area below FFFF
  14.       (addr_align == WORD_ALIGN) -> {this.start_offset[1:0] == 2′b0}; // Allocate only word (4B) aligned addresses
  15.       (addr_align == CACHE_ALIGN) -> {this.start_offset[7:0] == 8′b0}; // Allocate only cacheline (256B) aligned addresses
  16.    }
  17. endclass : nv_mam_allocator

2. vmm_mam_region – this class has been extended to apply various user-defined attributes to memory regions and sub-regions. This has a customized vmm_mam::request_region() function which can take user variables as input arguments to constraint “vmm_mam_region” allocation. Addtionally, the psdisplay() of  “vmm_mam” and “vmm_mam_region” is overridden to print out values of these variable for debugging purpose. Ownership of a region could also be queried from testbench using an inbuilt function get_owner.

  1. class nv_mam_region extends vmm_mam_region;
  2.    mem_owner_t mem_owner;
  3.    function new(mem_addr_t start_offset, end_offset, offset_range, int unsigned n_bytes, nv_mam parent);
  4.       super.new(start_offset, end_offset, offset_range, n_bytes, parent);
  5.       this.n_bytes = n_bytes;
  6.    endfunction
  7.    function mem_owner_t get_owner();
  8.       return this.mem_owner;
  9.    endfunction
  10. endclass : nv_mam_region

3. vmm_mam – This class has been extended such that the features added to the above 2 classes are made use of in the allocate/de-allocate methods. The handles of the allocated regions are stored and used in the release regions method. There is also a provision to release the regions particular to an owner or a group of owners.

  1. class nv_mam extends vmm_mam;
  2.    nv_mam_allocator default_nv_alloc;
  3.    vmm_mam_region in_use[$];
  4.    nv_mam_region nv_in_use[$];
  5.    address_mode_t amode;
  6.    // request region
  7.    function nv_mam_region nv_request_region(int unsigned n_bytes, mem_owner_t mem_owner=UNKNOWN, address_align_t addr_align=WORD_ALIGN,mem_attr_t mem_type=NONE);
  8.       vmm_mam_region region;
  9.       nv_mam_allocator alloc;
  10.       alloc=new(addr_align, this.amode, mem_type);
  11.       region=super.request_region(n_bytes, alloc);
  12.       nv_request_region = new(region.get_start_offset(),region.get_end_offset(), region.get_len(),region.get_n_bytes(),this);
  13.       nv_request_region.mem_owner = mem_owner;
  14.       this.in_use.push_back(region);
  15.       this.nv_in_use.push_back(nv_request_region);
  16.    endfunction
  17. // release OWNER specific regions
  18.    function void release_mem_region(mem_owner_t mem_owner=UNKNOWN);
  19.       vmm_mam_region region;
  20.       `vmm_note(this.log, $psprintf("Releasing all regions Owned by %s mem owner", mem_owner.name()));
  21.       foreach(this.nv_in_use[i]) begin
  22.          `vmm_verbose(this.log,$psprintf("REGION OWNER",this.nv_in_use[i].mem_owner.name()));
  23.          if(this.nv_in_use[i].mem_owner==mem_owner) begin
  24.             super.release_region(this.in_use[i]);
  25.             this.in_use.delete(i);
  26.             this.nv_in_use.delete(i);
  27.             break;
  28.          end
  29.       end
  30.    endfunction
  31. .
  32. .
  33. endclass

Memory Model:

The above customized MAM can be wrapped around in a class in order to bind memory configuration class with specific HW/SW memory implementation, memory monitors, checker, fw_load functions and various read write tasks. In the code below,  sys_mam_trace could be called anytime an actual read/write operation is performed on the hw/sw memory. This function provides notifications to indicate read/write to a certain address location. Two race free blocking functions wait_for_rd and wait_for_wr are implemented around these which could be used anywhere in the verification system to trap memory accesses. Based on these trap a cache protocol checker or any other checker could be implemented easily. Function load_fw can read an image file and preload any firmware code at the desired location in system memory. It also reserves the memory region so as to prevent any read/writes or allocation of the same address range to some other client in the system.  Thus with the customizations, we made to the off-the-shelf application class, we were able to efficiently meet our verification requirements!

  1. class sys_mam_c extends vmm_subenv;
  2.    rand vmm_mam_cfg cfg;
  3.    nv_mam_allocator sys_malloc;
  4.    nv_mam mam;
  5.    // setting up memory traps
  6.    event indicate_rd, indicate_wr;
  7.    bit rd_access[*];
  8.    bit wr_access[*];
  9.    // memory model – theh memory is implemented as an associative array of class objects
  10.    mem_loc sys_mem[*];
  11.    // constraint memory configuration as per the selected addressing mode
  12.    constraint sys_mam_valid_cons {
  13.       // 40 bit address space each address pointing to 4 bytes.
  14.       cfg.n_bytes == 1; //no of bytes each address points to.
  15.       cfg.start_offset == 0; //
  16.       (amode == BIT32) -> {cfg.end_offset == 64′hFFFFFFFF};
  17.       (amode == BIT64) -> {cfg.end_offset == 64′hFFFFFFFFFFFFFF};
  18.       cfg.mode == vmm_mam::GREEDY;
  19.       cfg.locality == vmm_mam::BROAD;
  20.    }
  21. .
  22. .
  23. .
  24.    // debug/monitor/notification function
  25.    function void sys_mam_c :: sys_mam_trace(bit rw, bit [63:0] addr, bit[31:0] data, bit [3:0] byte_en, string requester);
  26.    // indicate mem read/write notifications
  27.       if (rw) begin
  28.          rd_access[addr] = 1;
  29.          -> indicate_rd;
  30.       end else begin
  31.          wr_access[addr] = 1;
  32.          -> indicate_wr;
  33.       end
  34.    // printing a range of addresses
  35.       if (mem_trace_en | wr_trace_en | rd_trace_en) begin
  36.          if (trace_sa < trace_ea) begin
  37.             if ((addr >= trace_sa) && (addr < trace_ea+1)) begin
  38.                // `vmm_debug(this.log, $psprintf("Memory Trace Enabled [trace_sa=%h:trace_ea=%h]", trace_sa, trace_ea));
  39.                // run time print of memory read/writes
  40.                if (!rw && (wr_trace_en | mem_trace_en)) begin
  41.                  `vmm_note(this.log, $psprintf("MEM_WRITE: addr %h, data :%h byte_en :%b requester %s:", addr, data, byte_en, requester));
  42.                end else if (rw && (rd_trace_en | mem_trace_en)) begin
  43.                  `vmm_note(this.log, $psprintf("MEM_READ: addr %h, data :%h, requester %s:", addr, data, requester));
  44.                end
  45.             end
  46.          end else begin
  47.             // memory read/writes trace
  48.             if (!rw && (wr_trace_en | mem_trace_en)) begin
  49.               `vmm_note(this.log, $psprintf("MEM_WRITE: addr %h, data :%h byte_en :%b requester %s:", addr, data, byte_en, requester));
  50.             end else if (rw && (rd_trace_en | mem_trace_en)) begin
  51. `              vmm_note(this.log, $psprintf("MEM_READ: addr %h, data :%h, requester %s:", addr, data, requester));
  52.             end
  53.          end
  54.       end
  55.    endfunction : sys_mam_trace
  56.    // trap memory read accesses
  57.    task sys_mam_c :: wait_for_rd(bit [63:0] mem_addr);
  58.       `vmm_note(this.log, $psprintf("Waiting For a Read Access to Mem Addr #%0h", mem_addr));
  59.       while (!this.rd_access.exists(mem_addr)) begin
  60.          @(indicate_rd);
  61.       end
  62.       this.rd_access.delete(mem_addr);
  63.    endtask: wait_for_rd
  64.    // trap memory write accesses
  65.    task sys_mam_c :: wait_for_wr(bit [63:0] mem_addr);
  66.       `vmm_note(this.log, $psprintf("Waiting For a Write Access to Mem Addr #%0h", mem_addr));
  67.       while (!this.wr_access.exists(mem_addr)) begin
  68.          @(indicate_wr);
  69.       end
  70.       this.wr_access.delete(mem_addr);
  71.    endtask: wait_for_wr
  72. .
  73. .
  74. .
  75. // load fw in memory with an img file
  76.    function void sys_mam_c::load_fw(string fw_file_name);
  77.       bit [31:0] mem [*];
  78.       bit [63:0] addr;
  79.       static bit addr_reserved[*];
  80.       mem_loc load_loc;
  81.       $readmemh(fw_file_name, mem);
  82.       foreach (mem[i]) begin
  83.          // i is word aligned address. Change it to byte aligned
  84.          addr = i<<2;
  85.          // reserved the address if not already reserved
  86.          if (!addr_reserved[addr]) begin
  87.             this.mam.nv_reserve_region(addr, 4);
  88.             addr_reserved[addr] = 1;
  89.          end
  90.          write_dw(addr, mem[i], 4′b1111, "load_fw");
  91.          `vmm_note(this.log, $psprintf("MEM i=%0h and ADDR=%0h Contains Data %0h", i, addr, mem[i]));
  92.       end
  93.    endfunction : load_fw
  94. endclass

Posted in Uncategorized | 1 Comment »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Using a RAL test to go from Block to Top quickly and effectively….

Posted by Srivatsa Vasudevan on February 9th, 2010

Using a RAL test to go from Block to Top quickly and effectively….

Often, I’ve been in situations where the chip lead came up to me asking me to write a test to makes sure that the block quickly integrates at the top, and had to pull some tricks out of a hat in fairly short order. I’m sure the below has happened to many of you way more than you want to count.

In order for the top level integration to run, I recommend a simple sanity register read/write test with all the blocks to make sure the integration went ok and the RTL that is available can be debugged further.

 ral-2

                                 

 

 

 

 

 

 

 

 

 

 

The point being, that if the CPU cannot read/write configuration registers from any one block on the SOC, the core will fail the integration test anyways.

Many folks typically wind up writing such a test using tasks in verilog, One of the challenges is that the test has to be continually kept up to date

As the memory map changes and the environment for that has to be updated as well.

However, using RAL allows both the Block owner and the chip top owner to both Tag off the same RAL File and get their work done. The core level person can continue on his path of verifying

His tests, while the top chip owner can continue on a separate path of writing top level tests.

image

 

 

 

 

 

 

 

 

 

 

All the core owner has to do is share the one RALF for now for each release of the core while he works on other integration tests.

In the coming posts, we’ll see how to cut the amount of work you do using these generated models.

My mantra has always been “ work less/verify more” We’ll explore sequences and user generated code and how to kill the documentation issues.

Till then, Stay tuned.

Posted in Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Connecting Multiple Analysis Ports to a Single Analysis Export

Posted by JL Gray on February 9th, 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 Uncategorized, VMM, VMM 1.2 | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

You get real hierarchy with VMM1.2

Posted by Wei-Hua Han on February 9th, 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 Tutorial, VMM 1.2 | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

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

Posted by Srinivasan Venkataramanan on February 7th, 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 Debug, VMM, vmm_channel, vmm_xactor | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5 out of 5)
Loading ... Loading ...

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

Posted by Ambar Sarkar on February 5th, 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);
  endfunction

  virtual 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 Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

The Curious World of Ports and Exports in VMM 1.2

Posted by John Aynsley on February 4th, 2010

JohnAynsley

John Aynsley, CTO, Doulos

 

 

In a previous post I discussed the new blocking transport interface of VMM 1.2, and described how a producer can call the b_transport method implemented by a consumer. In this post, I will describe how to connect the producer to the consumer using the new features of VMM 1.2.

The new features discussed here were inspired by the SystemC TLM-2.0 standard. In SystemC, two modules wishing to communicate get connected using so-called “ports” and “exports”. Although the underlying concept is both elegant and powerful, the terminology “port” and “export” often seems to cause confusion. Since this is now part of VMM 1.2, I will explain. Let us consider a simple example of a producer calling the b_transport method implemented in a consumer.

class my_tx extends vmm_data; // User-defined transaction class
    …

class producer extends vmm_xactor;
    vmm_tlm_b_transport_port #(producer, my_tx) m_port;
    my_tx tx;
    …

    m_port.b_transport(tx, delay);
    …

class consumer extends vmm_xactor;
    vmm_tlm_b_transport_export #(consumer, my_tx) m_export;
    task b_transport(int id = -1, my_tx trans, ref int delay);
    …

class my_env extends vmm_group;
    producer m_producer;
    consumer m_consumer;

    virtual function void connect_ph;
        m_producer.m_port.tlm_bind( m_consumer.m_export );
    endfunction
    …

What is happening here is that the producer is calling b_transport through a port, the consumer is providing an implementation of b_transport using an export, and the top-level environment is connecting (or “binding”) the port to the export. The tlm_bind method is creating the link between the port and the export such that when the producer calls b_transport, it is the implementation of b_transport within the consumer that actually gets called.

Both the port and the export declarations are parameterized with the type of the transactor (producer/consumer) and the type of the transaction (my_tx). You may notice that the implementation of b_transport has an extra int id argument. This can be used to distinguish between transactions arriving from different producers. I will discuss this in my next blog post.

The purpose of ports and exports is to provide a structured way of making method calls between VMM transactors (or SystemC modules) such that the dependencies between each transactor and its environment can be minimized. To call b_transport the code within the producer only need refer to the port and has no direct dependencies on any code outside that transactor. Similarly, to call the b_transport method implemented within the consumer, the environment only need refer to the export. It is only when the port and export are connected within the connect_ph method of the environment that a specific dependency is established between the producer and consumer transactors.

What about those terms “port” and “export”? The term port was originally borrowed from VHDL and Verilog. In SystemC, a port allows an interface method call to be made up-and-out-of a module. When SystemC was enhanced to add the mirror image construct that allows an interface method call to be made down-and-into a module the term “export” was chosen because an export provides or “exports” an interface, whereas a port “imports” an interface.

If we could re-write history, I guess we might have chosen the term “import” instead of “port”. Perhaps that would have caused less confusion. Or maybe not!

Posted in Uncategorized | 1 Comment »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Shorthand macros with user defined implementation

Posted by Vidyashankar Ramaswamy on February 3rd, 2010

 

The transaction class objects are created by extending the base class vmm_data. Vmm_data class has many virtual methods which need to be implemented by the extended class. This can become a laborious process as this is done for each extended class object. However, a set of shorthand macros available to help minimize the amount of code required to create these data class extensions. These shorthand macros can be used on per data member basis which provides a default implementation of all the require methods. Following is an example.

      . . . 
1    class apb_trans extends vmm_data;
2       `vmm_typename(apb_trans)

3       rand enum {READ, WRITE} kind;
4       rand bit [31:0] addr;
5       rand logic [31:0] data;

6       `vmm_data_member_begin(apb_trans)
7            `vmm_data_member_scalar(addr, DO_ALL)
8            `vmm_data_member_scalar(data, DO_ALL)
9            `vmm_data_member_enum(kind, DO_ALL)
10     `vmm_data_member_end(apb_trans)
11     …
12  endclass: apb_trans 
      . . .

 

The class properties are declared as shown in Line number 3 to 5. Line number 6 and 10 marks the start and end of the shorthand macro section. Based on the variable type , the appropriate macros are called (line number 7 to 9). As the name says “DO_ALL” means use this variable in all the virtual method implementations. Say if you want to exclude the “kind” property from printing, then you can use “DO_ALL – DO_PRINT”. Please refer to the VMM user guide for more details on this.

User defined implementation

Shorthand macros provide the default implementation for all the vmm_data virtual methods. If you want to override the default implementation of a method, then you have to implement the do_* method. For example say you want to change the implementation for byte_size, You can still use shorthand macros but need to explicitly implement the apb_trans::do_byte_size() method and force VMM not to provide the default implementation. The example code is shown below.

1   virtual function int unsigned do_byte_size (int kind = –1) ;
2       . . .
3       . . .
4   endfunction

 

Constructor replacement

In some cases a transaction class might need a custom constructor with different arguments. Please note that the explicit constructor implementation is done using the shorthand macro `vmm_data_new() as shown below (line 1). The new implementation should follow the macro definition (Line number 2 to 5). It is also important to provide default values for the arguments to make the transaction class factory-enabled (Line number 2).

1   `vmm_data_new(apb_trans)
2   function new (vmm_log log=null, vmm_object parent=null, string name=””);
3       super.new(. . ., . . .) ;
4       . . . 
5   endfunction

 

The shorthand macros are also available for messaging service (vmm_log), vmm_unit configuration, RTL configuration (vmm_rtl_config) and TLM ports. For the complete list, please refer to the VMM user guide.

Posted in Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5 out of 5)
Loading ... Loading ...

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

Posted by Srivatsa Vasudevan on January 29th, 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 Uncategorized | 1 Comment »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Should you use VMM Callbacks or TLM Analysis ports?

Posted by S. Prashanth on January 27th, 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 Uncategorized | 2 Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (3 votes, average: 3.67 out of 5)
Loading ... Loading ...

Using factories in an implicitly-phased environment

Posted by Kiran Maiya on January 25th, 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 Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Proxy Coverage

Posted by Andrew Piziali on January 22nd, 2010

Andy's Picture_1

Andrew Piziali, Independent Consultant

I was recently talking with a friend about a subject near and dear to my heart: functional coverage. Our topic was block level or subsystem coverage models that are not ultimately satisfied until paired with corresponding system level context. The question at hand was “Is it possible to use conditional block level coverage as a proxy for system level coverage by recording coverage points at the block level, conditioned upon subsequent observation of system level context?”

For example, let’s say I have an SoC DUV (design under verification) that contains an MPEG-4 AVC (H.264) video encoder with a variety of features enabled by configuration registers. I need to observe the encoder operating in a subset of modes—referred to as “profiles” in H.264 parlance—defined by permutations of the features. In addition, each profile is only stressed when the encoder is used by an application for which the profile was designed. For example, the “high profile” is intended for high definition television while the “scalable baseline profile” was designed for mobile and surveillance use. The profiles may be simulated at the block level but the applications are only simulated at the system level. How do I design, implement and fill this H.264 coverage model so that the model is populated using primarily block level simulations, with their inherent performance advantage, while depending upon system level application context? May I even use the block level coverage results as a proxy for the corresponding system level coverage?

I think this nut is tougher to crack than the black walnuts that recently fell from my tree. The last time I tried to crack those nuts I resorted to vise grips from my tool chest, leading to a number of unintended, but spectacular, results, but I digress. My friend and I were able to define a partial solution but it wasn’t at all clear whether or not a viable solution exists that leverages the speed of block level simulations. After you finish reading this post, I’d like to hear your thoughts on the matter. This is how far we got.

As with the top level design of any coverage model, we started with its semantic description (sometimes called a “story line”):

Record the encoder operating in each of its profiles while in use by the corresponding applications.

Next, we identified the attributes required by the model and their values:

Attribute Values
Profile BP, CBP, Hi10P, Hi422P, Hi444PP, HiP, HiSP, MP, XP
Application broadcast, disc_storage, interlaced_video, low_cost, mobile, stereo_video, streaming_video, video_conferencing
Feature 8×8_v_4×4_transform_adaptivity, Arbitrary_slice_ordering, B_slices, CABAC_entropy_coding, Chroma_formats, Data_partitioning, Flexible_macroblock_ordering, Interlaced_coding, Largest_sample_depth, Monochrome, Predictive_lossless_coding, Quantization_scaling_matrices, Redundant_slices, Separate_Cb_and_Cr_QP_control, Separate_color_plane_coding, SI_and_SP_slices

Then, we finished the top level design using a second order model (simplified somewhat for clarity):

clip_image001
The left column of the table serves as row headings: Attribute, Value, Sampling Time and Correlation Time. The remaining cells of each row contain values for the corresponding heading. For example, the “Attribute” names are “Profile,” “Application” and “Feature.” The values of attribute “Profile” are BP, CBP, Hi10P, etc. The time at which each attribute is to be sampled is recorded in the “Sampling Time” row. The time that the most recently sampled attribute values are to be recorded as a set is when the “application [is] started,” the correlation time. Finally, the magenta cells define the value tuples that compose the coverage points of the model.

The model is implemented in SystemVerilog, along with the rest of the verification environment and we begin running block level simulations. Since no actual H.264 application is run at the block level, we need to invent an expected application value whenever we record a coverage point defined by an attribute value set, lacking only an actual application. Why not substitute a proxy application value, to be replaced by an actual application value when a similar system level simulation is run? For example, if in a block level simulation we observe profile BP and feature ASO ({BP, *, ASO}), we could substitute the value “need_low_cost” for “low_cost” for the application value, recording the tuple {BP, need_low_cost, ASO}. This becomes a tentative proxy coverage point. Later on, when we are running a system level simulation with an H.264 application, whenever we observe a {BP, low_cost, ASO} event, we would substitute the much larger set of {BP, need_low_cost, ASO} block level events for this single system level event, replacing “need_low_cost” with “low_cost.” This would allow us to “observe” the larger set of system level {BP, low_cost, ASO} events from the higher performance block level simulations. How can we justify this substitution?

We could argue that a particular system level coverage point is an element of a set of block level coverage points because (1) it shares the same subset of attribute values with the system level coverage point and (2) the block level simulation is, in a sense, a superset of the system level simulation because it implicitly abstracts away the additional detail available in the system level simulation. Is there any argument that the profile value BP and feature value ASO are the same in the two environments? The second reason is clearly open to discussion.

This brings us back to the opening question, can we use conditional block level coverage as a proxy for system level coverage by recording coverage points at the block level, conditioned upon subsequent observation of system level context? If so, is this design approach feasible and reasonable? If not, why not? Have at it!

Posted in Coverage | 3 Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Transactor Interfaces [VMM 1.2 style]

Posted by Vidyashankar Ramaswamy on January 18th, 2010

In my earlier blog post  I have shown how to write a configurable physical interface. This time I shall look into the slave transactor and its interfaces. A slave transactor can have all or some of the interfaces shown in the following Figure. It solely depends on the nature of the protocol and its surrounding environment. If you are developing verification IP which is used across multiple projects or groups , then you must consider the entire interface requirement and develop the VIP accordingly. Please note that I will be discussing only the analysis port [No 5] and the transport port [No 6] interfaces which are developed using VMM 1.2 features.

  image

 

Analysis Port

Analysis ports are used to share the received transaction among the other testbench components. These components can also be referred to as listeners, subscribers, observers, target or sometimes passive component. As the name says , this port is used to distribute the transaction to a single or multiple listeners for analysis. The important feature of the analysis port is that a single port can be connected to multiple subscribers. The component which is broadcasting the transaction uses “analysis port” and the observers implement the analysis export port. Each subscriber must implement the “write”  method of the vmm_tlm_analysis_export class. Here the listeners can’t modify the transaction and can only copy it’s content for analysis. Most common testbench components which can use this port are scoreboard, functional coverage, debug and reference model.

Analysis port in the producer (Initiator) model

The analysis port is implemented as follows. The port object is constructed in the transactors’s build phase. As this is a slave model, the write method is called after responding to the observed request on the bus.

File: vip_slave.sv 

. . .
//////////// SLAVE MODEL ///////////////

class vip_slave extends vmm_xactor;
   `vmm_typename(vip_slave)
   // Variables declaration
   . . .
  // Analysis port
   vmm_tlm_analysis_port#(vip_slave, vip_trans) analysis_port;
   // TLM Blocking port
   vmm_tlm_b_transport_port#(vip_slave, vip_trans) b_trans_resp_port;
   
   // Component Phases
   . . .
   extern virtual function void build_ph();
   extern virtual protected task main();
   . . .
endclass: vip_slave
. . .
/////////////// Build Phase /////////////////
function void vip_slave::build_ph();
   . . .
   // Construct the analysis port for Observers
   analysis_port = new(this, "analysis_port");
   // Construct the transport port for response modification
   b_trans_resp_port = new(this, "b_trans_resp_port");
   . . .
endfunction: build_ph
. . .

////////////// Main Method////////////////
task vip_slave::main();
   super.main();
  forever begin 
      vip_trans tr;
      int dly = 0;
      . . . 
      if (tr.kind == vip_trans::READ) begin
         // Retrieve the data
        . . .  
         // Provide the handle to the modifying transactor
         b_trans_resp_port.b_transport(tr, dly);
         . . .
         // Finally drive the data onto the bus
         . . .
      end
      else begin
// WRITE
         // Assemble the trans properties
         . . .
         // Provide the handle to the modifying transactor
         b_trans_resp_port.b_transport(tr, dly);
         . . .
         // Store the data
        . . .
      end
      . . .
      // notify the observers about this transaction
      this.analysis_port.write(tr);
      . . . 
   end
endtask:
main

 

 Analysis export port in the target (consumer) model

Following is a very simple implementation of an observer. This observer class has an instance of the tlm analysis export class and overrides the virtual function  “write”  to operate on the received transaction. Here the write method displays the received transaction from the initiator.

File: observer.sv

class observer extends vmm_object;
   `vmm_typename(observer)
   vmm_tlm_analysis_export#(observer, vip_trans) obsrv ;
   vmm_log log = new("log", this.get_object_hiername());
  
   virtual function void write (int id = -1, vip_trans tr);
      `vmm_note(log, " … From Observer: Rcvd Transaction … ");
      tr.display("");
   endfunction: write

  function new(vmm_object parent = null, string inst=””);
      super.new(parent, get_typename());
   endfunction

  /////////////// Build Phase /////////////////
  function void build_ph();
     . . .
     // Construct the analysis export port 

     obsrv = new(this, “obsrv”); 
     . . . 
   endfunction

endclass: observer

 

 Transport Interface

The transactions received by a slave can be processed by a higher layer transactor before responding to the request. In these situations TLM transport ports are used for passing transactions in a blocking/non-blocking way. This slave model uses a blocking transport port to pass the transaction to another transactor for further processing. Blocking transport, completes the transaction within a single method call and uses the forward path from initiator to target. To use this interface, the slave model should implement vmm_tlm_b_transport_port for issuing transactions and the higher layer transactor implements vmm_tlm_b_transport_export for receiving transactions. Please refer to the code shown above (vip_slave.sv).  Also note that the transaction data modification (call to b_transport method) happens before storing the WRITE data  or responding to a READ request.

In a transactor implementation, It is ok to construct and call the analysis port’s “write” method without binding it in the environment. This is not true with the transport port’s “b_transport” method and should be bound in the environment. So it is a good practice to make this port configurable (enable/disable) using vmm options. 

 

Response modifier Transactor

This transactor shows how to instantiate the tlm export class object and the implementation of the b_transport method. Following is a very simple implementation of the transport method, where the received transaction is displayed. 

File: resp_modifier.sv

class resp_modifier extends vmm_xactor;
   `vmm_typename(resp_modifier)
   vmm_tlm_b_transport_export#(resp_modifier, vip_trans) b_trans_resp_export ;   

   virtual task b_transport (int id = -1, vip_trans tr, ref int dly);
      `vmm_note(log, " …. Resp Trans Modifier …. ");
       tr.display("");
   endtask: b_transport 
 

   function new(vmm_unit parent = null, string inst=””);
      super.new(get_typename(), inst, 0, parent);
   endfunction

   /////////////// Build Phase /////////////////
   function void build_ph();
      . . .
      // Construct the transport export port 

      b_trans_resp_export = new(this, “b_trans_resp_export”); 
      . . . 
   endfunction

endclass: resp_modifier

 

Connecting it all together

The last step is to create the environment. The required components are constructed in the build phase. Connect phase is used to bind the ports appropriately as shown in the code below.  Please refer to the VMM user guide for more information on TLM 2.0 interfaces.

File: tb_env.sv
. . .
`include "vip_slave.sv"
`include "observer.sv"
`include "resp_modifier.sv"
. . . 
////////// TB ENVIRONMENT //////////////// 
class tb_env extends vmm_group; 
   `vmm_typename(tb_env)

   // VIP Instantiation 
   vip_slave slv; 
   . . .
 
   // TLM Port Declaration 
   observer        obsrv_vip_trans;
   resp_modifier trans_mod_xactor; 
   . . .
 
   // Component Phases  
   extern virtual function void build_ph(); 
   extern virtual function void connect_ph();
   . . .
endclass: tb_env
. . .

////////// Build Phase //////////////// 
function void tb_env::build_ph();
   . . .
   this.slv = vip_slave::create_instance(this, "slv", `__FILE__, `__LINE__);
 
   //Create observer component
   obsrv_vip_trans = new(this, "TRANS_OBSVR");
   // Create the response modifier transactor
   trans_mod_xactor = new(this, "RESP_MODIFIER"); 
   . . . 
endfunction: build_ph

/////////// Connect Phase //////////// 
function void tb_env::connect_ph(); 
   . . .   
   // Bind the analysis Port 
   this.slv.analysis_port.tlm_bind(obsrv_vip_trans.obsrv);
   // Bind the transport port
   this.slv.b_trans_resp_port.tlm_bind

                            (trans_mod_xactor.b_trans_resp_export); 
   . . . 
endfunction: connect_ph 
. . .

 

I hope you find this article useful. Please feel free to send me your opinion on this.

 

 

 

 

Posted in Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

VMM 1.2 – The Movie

Posted by John Aynsley on January 14th, 2010

JohnAynsley

John Aynsley, CTO, Doulos

To celebrate the release of VMM 1.2 on VMM Central, I thought I would do something a little different and share with you a video giving a brief overview of the new features, including the implicit phasing and TLM-2 communication. So grab some popcorn, sit back, and enjoy…

Posted in VMM, VMM 1.2 | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Transactors and Virtual Interface

Posted by Vidyashankar Ramaswamy on January 8th, 2010

 

In my previous blog post I have shown high level view of a transactor and its properties. In this article I look into more details about developing a reusable transactor with a physical interface. There are many ways to connect a transactor to the physical interface. Everybody is aware of how we used do this in the object constructor. This is similar to hardwiring the connection in the environment. Another way is to make the interface configurable by the environment thus removing any dependency between the test, env and the DUT interface. This can be accomplished in two steps. The first step is to create an object wrapper for the virtual interface and make it as one of the properties of the transactor. The second step is to set this object using VMM configuration options either from the enclosing environment or from top level.

 

image

Step1. Developing the port object

VMM set/get options cannot be used directly to set a virtual interface. To make this work, we have to implement an object wrapper for the virtual interface. Sample code is shown below. Please note that the class name (master_port) and the interface name (vip_if) should be changed appropriately. EX – axi_master_port, axi_if… etc

File: master_port.sv
class master_port extends vmm_object;

   virtual vip_if.master mstr_if;

   function new (string name, virtual vip_if.master mstr_if);
      super.new(null, name);
      this.mstr_if = mstr_if;
      if (mstr_if != null)
         `vmm_note(log, "\n**** Master I/F created ****\n");
      else
         `vmm_error(log, "\n**** Master Interface is NULL ****\n");
   endfunction

endclass: master_port

Step2. Configuring the Virtual Interface

In the transactor’s connect phase, get a handle to the virtual interface using the vmm_opts get method. This establishes the connection between the transactor and the DUT pin interface. Please do not forget to check the object handles and print debug messages using VMM messaging service. This will greatly reduce the debug time if things are not connected properly in the environment.

File: master.sv

//////////////////// Master Model //////////////
class master extends vmm_xactor;
   `vmm_typename(master)
  
// Variables declaration
  virtual vip_if.master mstr_if;
   …
   ////////////// Connect_vitf method ////////////////
   function void connect_vitf(vip_if.master mstr_if);
   begin
      if (mstr_if != null)
         this.mstr_if = mstr_if;
      else
         `vmm_fatal(log, "Virtual port [Master] is not available"); 
   end
   endfunction: connect_vitf

   ////////////// Connect Phase ////////////////
   function void connect_ph();
   begin
      master_port mstr_port_obj;
      bit is_set;
      // Interface connection
      if ($cast(this.mstr_port_obj, vmm_opts::get_object_obj(is_set, this, "vip_mstr_port"))) begin
         if (mstr_port_obj != null)
            this.connect_vitf(mstr_port_obj.mstr_if);
         else
            `vmm_fatal(log, "Virtual port [Master] wrapper not initialized");
      end 
      …
   end
   endfunction: connect_ph
   …
endclass: master

Finally, the interface is set using vmm_opts set method in the environment. In VMM 1.2, the verification environment is created by extending the vmm_group base class object. VMM 1.2 supports both explicit and implicit phasing mechanism. In the implicit phasing mechanism, the connect phase is used to configure the virtual interface. Use the connect_vitf() method directly to have similar support in the explicit phasing mechanism. Sample code is shown below. Please note that the interface (master_if_p0) is instantiated in the top level test bench (tb_top).

File: vip_env.sv

//////////////////// Environment  //////////////

`include "vmm.sv”

class vip_env extends vmm_groups;
   `vmm_typename(vip_env)
   …
   // Variables declaration
   master_port mstr_p0;
   …
   ////////////// Build Phase ////////////////
   function void build_ph();
   begin
      …
      mstr_p0 = new("master_port", tb_top.master_if_p0);
      …
      end
   endfunction: build_ph

   ////////////// Connect Phase ////////////////
   function void connect_ph();
   begin
      bit is_set;
      …
      // Set the master port interface
      vmm_opts::set_object("VIP_MSTR:vip_mstr_port", mstr_p0, env);
      … 
   end
   endfunction
   …
endclass: vip_env

Explicit phasing environment:

Extend vmm_env to create the explicit phasing environment. The connect_vitf() method is called in the build phase of the environment. Sample code is shown below. Please note that one can use transactor iterater instead of using the object hierarchy to call the connect_vitf() method.

File: vip_env.sv

//////////////////// Environment  ////////////// 
`include "vmm.sv”

class vip_env extends vmm_env;
  
`vmm_typename(vip_env)
   …
   // VIP’s used …
   master mstr_drvr;
   …
   ////////////// Build Phase ////////////////
   function
void build();
   begin
      super.build();
      …
      // Set the master port interface
      this.mstr_drvr.connect_vitf(tb_top.master_if_p0);
      …
   end
   endfunction:
build
   …
endclass: vip_env

Please feel free to comment and share your opinion on this. In my next article I shall discuss more about the transactor’s interface and how to develop them using VMM1.2 features.

Posted in Uncategorized | 2 Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Verification in the trenches: Creating your verification components using VMM1.2

Posted by Ambar Sarkar on December 25th, 2009

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

Ever wonder why it is hard to mix and match verification components from different sources and have them play nicely with the one you created? You want all of these components to execute in sync with each other through the phases of their construction, configuration, shutdown, etc. For example, if the AXI slave transactor is executing its reset phase while the PCIe stimulus generator is sending in DMA read requests to the AXI interface, you have a problem. Often, you end up adding dedicated code or using explicit synchronization objects such as events to get the right coordination. Wouldn’t it be nice if this synchronization came automatically?

This is where the vmm_unit base class introduced in VMM1.2 comes in. The basic idea is to derive your verification component from this predefined class and you are guaranteed that the verification environment will automatically synchronize its execution with the others. While there is much offered by the vmm_unit class, the following statement summarizes its real benefit:

vmm_unit class comes with a rich set of built in synchronization points.

These synchronization points are represented as predefined tasks or functions called phases. The verification engineer provides the actual implementation of these phases. The environment makes sure that all the objects derived from the vmm_unit class get their phases called in a well defined order, so that once an object moves into a phase, it is guaranteed that all its siblings have completed the previous phase. For example, once a component enters reset, you know every other associated component is either being reset or about to enter the reset state.

Couple of things to note, however. First, you do not have to provide implementation for each and every phase. If you do not define them, the default action is that this object will wait for the others to finish this phase before moving to the next one. Second, you can override, replace, or even add your own phases to introduce a finer or different synchronization scheme altogether.

So how does the implementation end up looking like? Here is a snippet from something that I coded recently for a reusable module-level verification environment. Note that I only defined a few of the phases of my own and used the default implementation for the others.

Predefined phase

Sample Code Snippet

build_ph()

  …
  // Create various functional components of this environment
  pwr_hi = new(“subenv”, "PWR_HI", this);
  pwr_pi = new("subenv”,"_PWR_PI", this);
  // Instantiate a consensus manager
  cm = new(this, {this.get_object_name(), "_CM"}, pwr_port);
  ….

configure_ph()

  // If someone built me as a sub-environmnet, take appropriate action
  if (is_subenv) begin
    // Disable the host interface driver
    …

connect_ph()

  // Connect the components as needed
  pwr_hi.chk.ana_port.tlm_bind(sb.pwr_hi_sb_chk_ap);
  pwr_pi.has_generator) pwr_pi.gen.ana_port.tlm_bind(sb.pwr_pi_sb_post_ap);

start_of_sim_ph()

// Put a diagnostic message, otherwise leave empty
`vmm_verbose(log, "Starting simulation");

reset_ph()

  // Power_cycle
  pwr_port.dck.reset <= 0;
  @(pwr_port.dck);
  pwr_port.dck.reset <= 1;
  repeat (10) @(pwr_port.dck);
  pwr_port.dck.reset <= 0;
  repeat (2) @(pwr_port.dck);

training_ph()

  // Leave as default

config_dut_ph()

  // Sw initialization sequence
  // …

start_ph()

  // Leave as default

start_of_test_ph()

  // Leave as default

run()

  // All you need is to wait for the consensus manager to agree to shut down
  cm.wait_for_end_t();

shutdown_ph()

  // Leave as default

cleanup()

  // Leave as default

report()

  // Dump the final scoreboard status
  sb.report();

destruct()

  // Leave as default

The key is to make sure that your code is partitioned into the appropriate phases, as shown above. Also note that a bunch of the phases were left alone to their default implementation.

Okay, one minor detail. You do not directly derive from the vmm_unit base class. Instead, two classes, vmm_xactor and vmm_group have been provided. Both are derived from vmm_unit, so you have all the support for synchronization. vmm_xactor should be used as the base class for defining your individual transactors, whereas vmm_group should be used as the base class for components that put together several others into a single entity such as the top-level environment or a top-level interface vip.

Of course, the correctness of your synchronization  will still depend on what you end up implementing in the body of the pre-defined phases.  The names of the predefined phases give a hint. So you are still subject to what the other components implemented in their corresponding phases. Do follow the spirit of what each phase is supposed to do. Do not connect your components in build_ph()  phase even if the test passes. Do so in connect_ph(). A small price to pay for most cases, IMHO.

Do note that there often are scenarios where some components need to be synchronized separately from others. For example, in a PCIe based SOC, what if you need the OCP interface to be up and running before you bring your PCIe interface out of reset? In this case, you definitely do not want the OCP and the PCIe VIPs to run their configure and reset phases in lock-step with each other. This is where advanced synchronization features such as vmm_timelines come to play, but that’s a topic for the next post. Stay tuned.

This article is the 3rd 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 Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Viewing VMM log details in waveforms

Posted by Avinash Agrawal on December 17th, 2009


Avinash Agrawal, Corporate Applications, Synopsys

Often engineers need a combination of logfile outputs and waveforms, to look at their simulations. And they wonder if it is possible to look at waveforms and get information on the number of simulation errors that might have occurred in a simulation upto any particular point of simulation time.

The good news is that the VMM log service helps a user track the source of different messages to different verification components.

When VMM macros such as `vmm_error, `vmm_note etc are used at different places in the verification environment, the user is able to view the corresponding information in the simulation output. This information includes the time at which the message was logged, the verification component and the specified instance of which the message was issued from. However, it can be very useful if the timing of the errors or warnings in the simulation output can be correlated with waveforms in simulation. For example, if there is a protocol violation message issued from one of the testbench monitors, the user can map the time when the message was issued to the actual signals in the waveform. That way user can quickly uncover the relevant problem in the DUT.

The VMM message service vmm_log consists of the vmm_log_format object to control the format of the messages. The vmm_log_format object also gets the information of the type/severity of the messsages. The vmm_log class for each component uses the default implementation of all these methods. The user can easily extend the vmm_log_format class and add in his modifications. Modifications can be either to trigger an assertion or incrememt a variable which can be dumped into the waveform window. This way, the  engineer can correlate the errors with the change in the variable or an assertion in the waveform window.

The following code shows how this can be done:

module top();
  int error_count;

initial begin
 $vcdpluson();
end

endmodule

program P;

class env_format extends vmm_log_format;  //extending vmm_log_format
                                          // for adding in user modifications
      virtual function string format_msg(string name,
                                         string inst,
                                         string msg_typ,
                                         string severity,
                                         ref string lines[$]);
         if (msg_typ == "FAILURE" && severity == "!ERROR!") begin
            top.error_count++;  //incrementing error count for warnings and errors
         end
//or trigger an assertion which can also be seen in the waveform
        assert (~(msg_typ == "FAILURE" && severity == "!ERROR!"));
          format_msg = super.format_msg(name, inst, msg_typ, severity, lines);
      endfunction
endclass

class xactor extends vmm_xactor;

  int id;

  function new(int id, string instance);
    super.new("xactor", instance);
    this.id = id;
  endfunction

  virtual task main();
    super.main();
    `vmm_note(log, "This is a note message");
    #5;
    `vmm_error(log, "This is an error message");
    if (id == 0)
      #10 `vmm_error(log, "This is an error message");
    else
      #30 `vmm_error(log, "This is an error message");
  endtask
endclass

class env extends vmm_env;
  xactor x1;
  xactor x2;
  vmm_log log;

  function new();
    env_format fmt;
    log = new("env", "class");
    fmt = new();
    log.set_format(fmt);
  endfunction

  virtual function void build();
    super.build();
    x1 = new(0, "x1");
    x2 = new(1, "x2");
  endfunction

  virtual task start();
    super.start();
    x1.start_xactor();
    x2.start_xactor();
  endtask

  virtual task wait_for_end();
    super.wait_for_end();
    #1000;
  endtask

  virtual task stop();
    super.stop();
    x1.stop_xactor();
    x2.stop_xactor();
  endtask

endclass

initial begin
  env e = new;
  e.run();
end

endprogram

Posted in Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Just in time for the holidays: VMM 1.2!

Posted by Janick Bergeron on December 15th, 2009

Janick Bergeron
Synopsys Fellow

I am pleased to see that the OpenSource version of VMM 1.2 is finally released. It is the culmination of six months of hard work by the entire VMM teams and the hundreds of customers who have provided inputs on its requirements and the dozens of teams who have contributed their feedback during the beta period.

What is new in VMM 1.2 is a “secret de Polichinelle“. Ever since the start of the beta period, several VMM users and Synopsys engineers have published tutorials, seminar presentations and blog articles on many of its powerful aspects. Nonetheless, I would like to take this opportunity to give you the highlights and pointers to where you can find more information.

A new User’s Guide

One of the most important aspect of this release—and one that has not been mentioned so far—is the completely revamped and expanded User’s Guide. We have integrated the content of the VMM for SystemVerilog book, the book’s errata, and the previous User’s Guide into a single User Guide that completely documents all of the features of the class library. Furthermore, the body of this new User’s Guide has been expanded to present the methodology in a style that will be easier to learn, with many examples. Speaking of examples, this latest distribution contains a lot more examples (in $VMM_HOME/sv/examples), illustrating the many applications domains of the VMM and all of its new features.

Implicit Hierarchical Phasing

The original VMM used explicit phasing exclusively. With 1.2, VMM now supports implicit hierarchical phasing. With implicit phasing, transactors and environments need not be responsible for the phasing of the components they instantiate: that is taken care of automatically by the new vmm_timeline object. The implicit phasing is also hierarchical, meaning that an environment may contain more than one vmm_timeline instances. Sub-timelines limit the scope and interaction of user-defined phases when block-level environments are reused in a system context. Sub-timelines may also be rolled back if their portion of verification environment needs to be stalled or restarted, for example because its corresponding functionality in the DUT has been powered down. Furthermore, VMM allows implicit and explicit phasing to be arbitrarily mixed: instead of insisting that it be in control of every aspect of a verification environment, it can import portions of an environment described using an alternative phasing methodology and have it be explicitly phased using a different mechanism by encapsulating in a vmm_subenv instance. Similarly, any VMM environment can be subjugated to another phasing methodology by allowing vmm_timeline instances to be explicitly phased.

TLM 2.0

In addition to the vmm_channel, VMM 1.2 now offers an alternative transaction-level interface mechanism inspired by OSCI’s Transaction-Level Modeling standard version 2.0. I say “inspired” because it is not a direct translation of the SystemC TLM standard, as the SystemVerilog language does not support multi-inheritance used in the SystemC implementation. The TLM2 standard is radically different from TLM1 because the latter did not live up to its promises of model interoperability and simulation performance. In addition to specifying an interface and transport mechanism, TLM2 specifies clear transaction progress and completion models through phases and the Base Protocol. VMM has always provided similarly well-defined transport mechanism (vmm_channel) and completion models (see pp176-195 of the original VMM book). With the addition of TLM2 sockets, VMM can also be used to implement high-performance virtual prototyping models in SystemVerilog. Of course, we’ve made sure that you can attach a vmm_channel to an initiator or target blocking or nonblocking socket interface for maximum flexibility.

Object Hierarchy

Whereas modules form a strict hierarchy in SystemVerilog, class instances (also known as objects) do not – at least from a language standpoint. However, it is a common mental model even though it is not enforced by the language. VMM 1.2 has the ability to define parent-child relationships between any instances of the vmm_object class. And because that class is the base class for all other VMM classes, any instance of a VMM class or user-defined extensions thereof can have a parent and any number of children. This creates a user-defined hierarchy of objects. And because each object has a name, it implicitly creates a hierarchical naming structure. Furthermore, because this hierarchical and the name of its component is entirely user-defined, VMM 1.2 provides the concept of namespaces to create alternative object hierarchies and names, making it easy to create hierarchical registries or to map an object hierarchy to another one. Objects can easily be found by name or by traversing the hierarchy from parent to child or vice-versa.

Factory API

VMM always had the concept of class factories (see p217 in the original VMM book). It used the factory pattern in all of its pre-defined generators and recommended that it be used whenever transaction objects were created or randomized (see Rules 4-115 and 5-6 in the original VMM book). It simply did not provide any pre-defined utility to ease the implementation or overriding of class factory instances. VMM 1.2 remedies this situation by introducing a class factory API that makes it easier to replace class factory instances, as well as to build class factories. Furthermore, it provides two factory override mechanism: a fast one that creates class instances with default values, and a slower one that creates exact copies. And, being strongly typed, the new factory API will detect at compile time if you are attempting to replace a factory instance with an incompatible type.

And many more!

VMM 1.2 provides many more additional features, like hierarchical options, RTL configuration support, and test concatenation.

Learning more

You can download the OpenSource distribution here. You will also find VMM 1.2 in your VCS 2009.12-1 distribution (use the +define+VMM_12 compile-time command-line option to enable it!).

Visit this blog often, as many industry leaders and Synopsys engineers will continue to provide insights on the new features included in VMM 1.2

Also, stay tuned for a series of one-day VMM 1.2 seminars and workshops that will be touring the major semiconductor centers around the globe.

Posted in VMM | 2 Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5 out of 5)
Loading ... Loading ...

Transaction Level Modeling – Value add in different languages.

Posted by Nasib Naser on December 14th, 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 Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...