Verification Martial Arts: A Verification Methodology Blog

Archive for February, 2010

Using the Datastream Scoreboard Iterators

Posted by Amit Sharma on 23rd February 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 Reuse, Scoreboarding | 2 Comments »

Drivers on a Tristate bus….

Posted by Srivatsa Vasudevan on 18th February 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 »

Handling Incoming Transactions from Multiple Sources in VMM 1.2

Posted by John Aynsley on 16th February 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 Communication, Reuse, Transaction Level Modeling (TLM), VMM 1.2, VMM infrastructure | No Comments »

Customizing VMM Memory Allocation Manager to cater to your requirements

Posted by Amit Sharma on 11th February 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 Customization, Memory Management, Reuse | 1 Comment »

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

Posted by Srivatsa Vasudevan on 9th February 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 Register Abstraction Model with RAL, Reuse | No Comments »

Connecting Multiple Analysis Ports to a Single Analysis Export

Posted by JL Gray on 9th February 2010

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

by Asif Jafri

Asif Jafri is a verification engineer at Verilab.

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

Figure 1: Connecting using ID

Read the rest of this entry »

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

You get real hierarchy with VMM1.2

Posted by Wei-Hua Han on 9th February 2010

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

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

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

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

[Jason]

|–[Mike]

|–[Ben]

Jason has 2 children

Mike

Jason:Ben

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

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

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

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

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

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

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

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

1.   Implicit phasing

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

2.   Factory replacement

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

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

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

3.   Hierarchical configuration

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

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

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

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

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

Jason has 2 children

He indeed has three…

Have fun with VMM1.2. J

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

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

Posted by Srinivasan Venkataramanan on 7th February 2010

Srinivasan Venkataramanan, CVC Pvt. Ltd.

Rashmi Talanki, Sasken

John Paul Hirudayasamy, Synopsys

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

The log file looked like:

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

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

.

.(perform the task)

.

@N ns the Master_BFM  get the transaction 0.0.0

@N ns the transaction was peeked by Slave_BFM 0.0.0

@N ns the transaction was peeked by Master_BFM 0.0.1

@N ns the transaction was peeked by Slave_BFM 0.0.1

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

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

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

3. Use vmm_broadcaster

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

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

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

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

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

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

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

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

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

clip_image002

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

clip_image004

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

clip_image006

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

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

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

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

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

Kudos to VMM and the ever improving DVE!

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

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

Posted by Ambar Sarkar on 5th February 2010

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

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

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

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

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

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

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

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

Step a. Create a vmm_timeline object and instantiate the components

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

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

endfunction

endclass

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

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

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

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

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

endclass

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

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

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

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

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

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

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

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

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

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

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

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

The Curious World of Ports and Exports in VMM 1.2

Posted by John Aynsley on 4th February 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 SystemC/C/C++, Transaction Level Modeling (TLM) | 1 Comment »

Shorthand macros with user defined implementation

Posted by Vidyashankar Ramaswamy on 3rd February 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 Automation, Customization, Modeling Transactions | No Comments »