Skip to Content

Offsite blog roll

Posted on  by  from the site Verification Martial Arts
A two-part articles by Cisco and Synopsys engineers in IC Design and Verification Journal explains how complex constraints can be better managed to simplify the solving process, yet obtain high-quality results. Part1 deals with solutions spaces and constraint partitions. Part2 introduces the concept of soft constraint in e and default constraints in OpenVera. You can read part1 and part2 here. ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
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! ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
  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. ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
Srinivasan Venkataramanan, CVC Pvt. Ltd. Vishal Namshiker, Brocade Communications India 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). Recently during a brainstorming session we sought for common issues seen by users at Brocade. One of the engineers mentioned that he has abandoned the usage of `vmm_verbose, `vmm_debug macros altogether, when probed why, an interesting problem emerged. As we know the usage of them requires the +vmm_log_default=VERBOSE” flag at run time. However when used this way, there are tons of messages coming from VMM base classes too – as they are under the VERBOSE/DEBUG severity. Sure they 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 the ones from VMM base classes. 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” J ). ShareThis...
Debug, SV, VMM, vmm_log
Posted on  by  from the site Verification Martial Arts
  Varun S, CAE, Synopsys 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 data stream 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 multiple 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 verifications tasks simpler. For more information about the complete list of methods, see the VMM Datastream Scoreboard User Guide. ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
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. 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… ShareThis...
Debug
Posted on  by  from the site Verification Martial Arts
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. ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
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 typedef enum {R, RW, RSVD} mem_acc_t; typedef enum {NONE, Above4GB, Below4GB, PreFetch, Cacheable} mem_attr_t; typedef enum {PCIE, SATA, MAC, USB, USB3} mem_owner_t; typedef enum {BYTE_ALIGN, WORD_ALIGN, CACHE_ALIGN, PAGE_ALIGN}address_align_t; typedef enum {BIT32, BIT40, BIT64} address_mode_t; class nv_mam_allocator extends vmm_mam_allocator;    address_align_t addr_align; . . .    // system memory allocation    constraint sys_mem_alloc_cons {       this.start_offset > 64′hFFFF; // reserved memory area below FFFF       (addr_align == WORD_ALIGN) -> {this.start_offset[1:0] == 2′b0}; // Allocate only word (4B) aligned addresses       (addr_align == CACHE_ALIGN) -> {this.start_offset[7:0] == 8′b0}; // Allocate only cacheline (256B) aligned addresses    } 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. class nv_mam_region extends vmm_mam_region;    mem_owner_t mem_owner;    function new(mem_addr_t start_offset, end_offset, offset_range, int unsigned n_bytes, nv_mam parent);       super.new(start_offset, end_offset, offset_range, n_bytes, parent);       this.n_bytes = n_bytes;    endfunction    function mem_owner_t get_owner();       return this.mem_owner;    endfunction 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. class nv_mam extends vmm_mam;    nv_mam_allocator default_nv_alloc;    vmm_mam_region in_use[$];    nv_mam_region nv_in_use[$];    address_mode_t amode;    // request region    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);       vmm_mam_region region;       nv_mam_allocator alloc;       alloc=new(addr_align, this.amode, mem_type);       region=super.request_region(n_bytes, alloc);       nv_request_region = new(region.get_start_offset(),region.get_end_offset(), region.get_len(),region.get_n_bytes(),this);       nv_request_region.mem_owner = mem_owner;       this.in_use.push_back(region);       this.nv_in_use.push_back(nv_request_region);    endfunction // release OWNER specific regions    function void release_mem_region(mem_owner_t mem_owner=UNKNOWN);       vmm_mam_region region;       `vmm_note(this.log, $psprintf("Releasing all regions Owned by %s mem owner", mem_owner.name()));       foreach(this.nv_in_use[i]) begin          `vmm_verbose(this.log,$psprintf("REGION OWNER",this.nv_in_use[i].mem_owner.name()));          if(this.nv_in_use[i].mem_owner==mem_owner) begin             super.release_region(this.in_use[i]);             this.in_use.delete(i);             this.nv_in_use.delete(i);             break;          end       end    endfunction . . 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! class sys_mam_c extends vmm_subenv;    rand vmm_mam_cfg cfg;    nv_mam_allocator sys_malloc;    nv_mam mam;    // setting up memory traps    event indicate_rd, indicate_wr;    bit rd_access[*];    bit wr_access[*];    // memory model – theh memory is implemented as an associative array of class objects    mem_loc sys_mem[*];    // constraint memory configuration as per the selected addressing mode    constraint sys_mam_valid_cons {       // 40 bit address space each address pointing to 4 bytes.       cfg.n_bytes == 1; //no of bytes each address points to.       cfg.start_offset == 0; //       (amode == BIT32) -> {cfg.end_offset == 64′hFFFFFFFF};       (amode == BIT64) -> {cfg.end_offset == 64′hFFFFFFFFFFFFFF};       cfg.mode == vmm_mam::GREEDY;       cfg.locality == vmm_mam::BROAD;    } . . .    // debug/monitor/notification function    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);    // indicate mem read/write notifications       if (rw) begin          rd_access[addr] = 1;          -> indicate_rd;       end else begin          wr_access[addr] = 1;          -> indicate_wr;       end    // printing a range of addresses       if (mem_trace_en | wr_trace_en | rd_trace_en) begin          if (trace_sa < trace_ea) begin             if ((addr >= trace_sa) && (addr < trace_ea+1)) begin                // `vmm_debug(this.log, $psprintf("Memory Trace Enabled [trace_sa=%h:trace_ea=%h]", trace_sa, trace_ea));                // run time print of memory read/writes                if (!rw && (wr_trace_en | mem_trace_en)) begin                  `vmm_note(this.log, $psprintf("MEM_WRITE: addr %h, data :%h byte_en :%b requester %s:", addr, data, byte_en, requester));                end else if (rw && (rd_trace_en | mem_trace_en)) begin                  `vmm_note(this.log, $psprintf("MEM_READ: addr %h, data :%h, requester %s:", addr, data, requester));                end             end          end else begin             // memory read/writes trace             if (!rw && (wr_trace_en | mem_trace_en)) begin               `vmm_note(this.log, $psprintf("MEM_WRITE: addr %h, data :%h byte_en :%b requester %s:", addr, data, byte_en, requester));             end else if (rw && (rd_trace_en | mem_trace_en)) begin `              vmm_note(this.log, $psprintf("MEM_READ: addr %h, data :%h, requester %s:", addr, data, requester));             end          end       end    endfunction : sys_mam_trace    // trap memory read accesses    task sys_mam_c :: wait_for_rd(bit [63:0] mem_addr);       `vmm_note(this.log, $psprintf("Waiting For a Read Access to Mem Addr #%0h", mem_addr));       while (!this.rd_access.exists(mem_addr)) begin          @(indicate_rd);       end       this.rd_access.delete(mem_addr);    endtask: wait_for_rd    // trap memory write accesses    task sys_mam_c :: wait_for_wr(bit [63:0] mem_addr);       `vmm_note(this.log, $psprintf("Waiting For a Write Access to Mem Addr #%0h", mem_addr));       while (!this.wr_access.exists(mem_addr)) begin          @(indicate_wr);       end       this.wr_access.delete(mem_addr);    endtask: wait_for_wr . . . // load fw in memory with an img file    function void sys_mam_c::load_fw(string fw_file_name);       bit [31:0] mem [*];       bit [63:0] addr;       static bit addr_reserved[*];       mem_loc load_loc;       $readmemh(fw_file_name, mem);       foreach (mem[i]) begin          // i is word aligned address. Change it to byte aligned          addr = i<<2;          // reserved the address if not already reserved          if (!addr_reserved[addr]) begin             this.mam.nv_reserve_region(addr, 4);             addr_reserved[addr] = 1;          end          write_dw(addr, mem[i], 4′b1111, "load_fw");          `vmm_note(this.log, $psprintf("MEM i=%0h and ADDR=%0h Contains Data %0h", i, addr, mem[i]));       end    endfunction : load_fw endclass ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
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.                                                         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.                     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. ShareThis...
Uncategorized
Posted on  by  from the site Verification Martial Arts
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. Figure 1: Connecting using ID Both the monitors call the same write function in the scoreboard, but the implementation for each needs to be different. This is where the use of the ID field is useful. // filename: monitor.sv class monitor extends vmm_xactor; vmm_tlm_analysis_port# (monitor, my_trans)    analysis_port = new(this, “monitor_analysis_port”); // (parent, name)    …    virtual task run_ph();       analysis_port.write(trans);    endtask endclass: monitor Once the two monitors call the write function in the run_ph phase, an appropriate id field is attached to them when the implementing function is called in the scoreboard. The case statement then calls the appropriate compare function. // filename: scoreboard.sv class scoreboard extends vmm_object; vmm_tlm_analysis_export# (scoreboard, my_trans)    analysis_export = new(this,             “scoreboard_analysis_export”,             2, 0); // (parent,name,max_binds, min_binds)    …    virtual function write(int id = -1, my_trans trans);       case(id)          0: do_compare_from_port0(trans);          1: do_compare_from_port1(trans);       endcase    endfunction endclass: scoreboard If you are wondering where the id field came from, look at the two binds in the connect_ph phase of my_env. The tlm_bind function connects the analysis ports of the two monitors to the single analysis export of the scoreboard and attaches the user defined id field to each of them // filename: my_env.sv class my_env extends vmm_group; monitor mon[2]; scoreboard scb;    …    virtual function void connect_ph();       …          mon[0].analysis_port.tlm_bind(scb.analysis_export, 0);          mon[1].analysis_port.tlm_bind(scb.analysis_export, 1);    endfunction: connect_ph endclass: my_env By following this scheme we have made use of only one analysis_export, which will make your code cleaner and readable. The same can be accomplished using macros. Figure 2 shows how to connect the two monitors to analysis exports of the scoreboard. Figure 2: Connecting using macros When you use the available ‘vmm_tlm_analysis_export(Identifier) macro, the identifier gets appended to the write function. This is necessary because in SystemVerilog you cannot have two functions of the same name in the same class, but your two monitors need to have different write function implementations. You will continue to call the write function in the run_ph phase of the monitor code as always, but the macro appended _mon1 and _mon2 identifiers will make sure that the appropriate write function is called. // filename: scoreboard.sv class scoreboard extends vmm_object;    `vmm_tlm_analysis_export(_mon1)    `vmm_tlm_analysis_export(_mon2)    vmm_tlm_analysis_export_mon1#(scoreboard, my_trans)       analysis_export_mon1 = new(this, “scb_analysis_1”);    vmm_tlm_analysis_export_mon2#(scoreboard, my_trans)       analysis_export_mon2 = new(this, “scb_analysis_2”);    virtual function void write_mon_1(int id = -1, my_trans trans);       …    endfunction: write_mon_1    virtual function void write_mon_2(int id = -1, my_trans trans);       …    endfunction: write_mon_2 endclass: scoreboard Either scheme works well. ShareThis...
Uncategorized, VMM, VMM 1.2