Verification Martial Arts: A Verification Methodology Blog

Archive for September, 2010

The Blind Leading the Deaf

Posted by Andrew Piziali on 28th September 2010

The Wall of Separation Between Design and Verification

by Andrew Piziali, Independent Consultant

I remember a time in my zealous past, leading a large microprocessor verification team, where one my junior engineers related how they had forcefully resisted examining the floating point unit RTL, explaining to the designer that they did not want to become tainted by the design! The engineer insisted on a dialog with the designer rather than reviewing the RTL. Their position was mine: we must maintain some semblance of separation between the design and verification engineers.

There has been an age old debate between whether or not there ought to be a “wall” between the design and verification teams. “Wall” in this context refers to an information barrier between the teams that minimizes the verification engineer’s familiarity with the details of the implementation (but not the specification!) of the design component being verified. Similarly, the wall minimizes the designer’s familiarity with the verification environment for their implementation.

Re-convergence Model

Re-Convergence Model

The intent of the wall is to allow two pairs of eyes (and ears!)—those of the designer and those of the verification engineer—to independently interpret the specification for a common component and then compare their results. The hypothesis is that if they reach the same conclusion, they are likely to have correctly interpreted the specification. If they do not, one or both are in error. This process is an example of the re-convergence model[1], where a design transformation is verified by performing a second parallel transformation and then comparing the two results. What are the pros and cons of the wall?

The argument in favor of the wall depends upon what we might call original impressions, the fresh insight provided by a person unfamiliar with a concept upon initial exposure. In this context, the verification engineer reading the specification will acquire an understanding of the design intent, independent of the designer, but only if study of its implementation is postponed. Why? Because nearly any implementation will be a plausible interpretation of the specification. The objective is to acquire two independent interpretations for comparison. Hence, influencing a second understanding with an initial implementation would defeat the purpose. What is the opposite position?

The argument against the wall is that a verification engineer and designer, working closely together, are more likely to gain a more precise understanding of the specification than either one working alone. The interactive exploration of possible specification interpretations, each implementing their understanding—the designer the RTL and software, the verification engineer the properties, stimulus, checking and coverage aspects of their verification environment—is argued to lead to convergence more quickly than each party working alone. Well, what should it be? Should verification engineers and designers scrupulously avoid one another, should they collaborate or should they find some intermediate interaction?

Pondering the answer brings to mind the metaphor of the blind leading the deaf, where each of two parties is crippled in a different way such that neither is able to grasp the whole picture. Nevertheless, working together they are able to progress further than working alone. Are the verification engineer and designer the blind leading the deaf? Before I weigh in with my opinion, I’d like to read yours. Type in the “Leave a Reply” box below to respond. Thanks!

—————————
[1] Writing Testbenches Using SystemVerilog, Janick Bergeron, 2006, Springer Science+Business Media, Inc.

Posted in Coverage, Metrics, Verification Planning & Management | 4 Comments »

Performance verification of a complex bus arbiter using the VMM Performance Analyzer

Posted by Shankar Hemmady on 20th September 2010

Performance verification of system bus fabrics is an increasingly complex problem. An article in EE Times by Kelly Larson, John Dickol and Kari O’Brien of MediaTek Wireless describes how they used the VMM Performance Analyzer to complete performance validation for an AXI bus arbiter:

http://www.eetimes.com/design/eda-design/4207764/Performance-verification-of-a-complex-bus-arbiter-using-the-VMM-Performance-Analyzer

Posted in Performance Analyzer, VMM infrastructure | No Comments »

Shared Register Access in RAL though multiple physical interfaces

Posted by Amit Sharma on 19th September 2010

Amit Sharma, Synopsys

Usually, designs have a single interface but some designs may have more than one physical interface, each with accessible registers or memories. RAL supports designs with multiple physical interfaces, as well as registers and memories shared across multiple interfaces.

In RAL, a physical interface is called a domain. Only blocks and systems can have domains. Domains can contain registers and memories.

image

Now, how do you enable a register or a memory to be shared across multiple physical interfaces or ‘domains’ in RAL? This is done by declaring the register/memory as ‘shared’ in the RALF file and instantiating it in more than one domain. Once this is done, the functionality will be enabled in the generated RAL model.

Let us take an example. Suppose the design block has two domains or interfaces (say pci/ahb) which can have a shared access to the register STATUS. This can be specified through the ‘shared’ keyword in the register description in RALF file, generating a RAL block model, as follows.

image

In the example above, the text box on the left shows a snippet of the generated code. You can see that ‘domain’s do not create an additional level of hierarchy. The different registers belonging to the two domains in the block are available in the flat namespace in the block and can be accessed directly without specifying the domain name in the access hierarchy.

Once a register is defined as ‘shared’, I can go ahead and access that register through different domains. A typical usage scenario for a ‘shared’ register is that it can be read from one interface and written from another interface. The two different physical BFMs are mapped to the corresponding domains easily in the SV environment.

The following snippet of code how this is done:

this.ahb = new(…); // AHB BFM instantiation

this.ral.add_xactor(this.ahb, “ahb”); //tying the AHB BFM to the “ahb” domain

this.pci = new(…); // PCI BFM instantiation

this.ral.add_xactor(this.pci, “pci”); //tying the PCI BFM to the “pci” domain

Once the mapping is done, the way you access a specific ‘shared’ register is quite simple. You need to additionally specify the ‘domain’ name in your read/write task

image

When you make the writes/reads as described above, the accesses would be routed through the different BFMs corresponding to the different domains as specified through the ‘domain’ argument in the read/write access tasks as seen in the code specified in the textbox above.

In the test environment, if simulation is run with ‘trace’ or ‘debug’ verbosity , details of the access to the shared register through different domains can be observed.

The following lines shows a snippet of messages going to STDOUT

//———

Trace[DEBUG] on RVM RAL Access(Main) at 135:

Writing ‘h00000000000000aa at ‘h0000000000000A004 via domain “pci”…

Trace[DEBUG] on RAL(register) at 245:

Wrote register “ral_model.INT_MASK” via frontdoor with: ‘h00000000000000aa

Trace[DEBUG] on RVM RAL Access(Main) at 355:

Read ‘h00000000000000aa from ‘h0000000000000002 via domain “ahb”…

Trace[DEBUG] on RAL(register) at 355:

Read register “ral_model.INT_MASK” via frontdoor: ‘h00000000000000aa

//———

By the way, one of the pre-defined tests that ship with the VMM RAL is the “shared_access” test. It exercises all shared registers and memories using the following process for each domain (requires at least one domain with READ capability or backdoor access).

- Write a random value via one domain

- Check the content via all other domains.

Hence, without writing a single line of test code, you can verify the basic functionality of all shared registers/memories of your DUT through this test.


Posted in Register Abstraction Model with RAL | 1 Comment »

Using the Factory Infrastructure in VMM

Posted by Avinash Agrawal on 15th September 2010

Avinash Agrawal, Corporate Applications, Synopsys

As a well-known Object-Oriented technique, the factory concept has been applied in VMM ever since inception.

For example, if you assign different blueprints to randomized_obj and scenario_set[]
in the VMM atomic and scenario generators, these generators can generate transactions
with user specified patterns. By using the class factory pattern, you can create an
instance with a pre-defined method such as allocate() or copy() instead of the
constructor.

VMM now simplifies the application of the class factory pattern within the complete
verification environment that helps you to easily replace any kind of object,
transaction, scenario and transactor by a similar object. 

The following simple procedure helps you to apply the class factory pattern within
the verification environment:

1. Define "new", "allocate" and "copy" methods for a class and use the factory macros
   to create the necessary infrastructure for factory replacement.

class vehicle_c extends vmm_object;
`vmm_typename(vehicle_c);
//defines the new function. each argument should have default values
   function new(string name="",vmm_object parent=null);
      super.new(parent,name);
   endfunction

//defines allocate and copy methods, you can use the VMM Data shorthand macros for
the same if classes are extended from vmm_data.

   virtual function vehicle_c allocate();
      vehicle_c it;
      it = new(this.get_object_name,get_parent_object());
      allocate = it;
   endfunction

   virtual function vehicle_c copy();
      vehicle_c it;
      it = new this;
      copy = it;
   endfunction

//`vmm_typename and `vmm_class_factory will  define necessary infrastructure
 `vmm_class_factory(vehicle_c);
endclass

2. Create an instance using the pre-defined "create_instance()" method. To use the
   class factory, the class instance should be created with the pre-defined
   create_instance() method instead of the constructor. 

For example,

class driver_c extends vmm_xactor;
   vehicle_c myvehicle;
   function new(string name="",vmm_object parent=null);
      super.new(parent,name);
   endfunction

   task drive();
   //create an instance from create_instance method
      myvehicle = vehicle_c::create_instance(this,"myvehicle");
      $display("%s is driving %s(%s)", this.get_object_name(),
       myvehicle.get_object_name(),myvehicle.get_typename());
   endtask
endclass

program p1;
      driver_c Tom=new("Tom",null);
      initial begin
         tom.drive();
     end
endprogram

The output for the example will be,
Tom is driving myvehicle(class $unit::vehicle_c)

3.  Create a new class which should replace the existing one as shown in the
    following example:

class sedan_c extends vehicle_c;
   `vmm_typename(sedan_c);
    function new(string name="",vmm_object parent=null);
       super.new(name,parent);
    endfunction

    virtual function vehicle_c allocate();
       sedan_c it;
       it = new(this.get_object_name,get_parent_object());
       allocate = it;
    endfunction

    virtual function vehicle_c copy();
       sedan_c it;
       it = new this;
      copy = it;
    endfunction
    `vmm_class_factory(sedan_c);
endclass

You are now required to create a 'myvehicle' instance from this new class without
modifying the driver_c class.

4. Override the original instance or type with the new class. VMM provides two
   methods for you to override the original instances or type.

- override_with_new(string name, new_class factory, vmm_log   log,string
  fname="",int lineno=0);
  With this method, when create_instance() is called, a new instance of new_class
  will be created through facory.allocate() and returned.

- override_with_copy(string name, new_class factory,vmm_log log, string
  fname="", int lineno=0);
  With this method, when create_instance() is called, a new instanced of new_class
  will be created through factory.copy() and returned.

For both the methods, the first argument is the instance name, as specified in
the create_instance() method, which you would want to override with the type of
new_class. You can use the powerful pattern matching mechanism defined in VMM to
specify the override happens on specified instances or all the instances of one
class in the whole verification environment.

The code below will override all vehicle_c instances with sedan_c type in the
environment:

program p1;
   driver_c Tom=new("Tom",null);
   vmm_log log;
   initial begin
      //override all vehicle_c instances with type of sedan_c
      vehicle_c::override_with_new("@%*",sedan_c::this_type,log);
      Tom.drive();
    end
endprogram

And the output of the above code is:

  Tom is driving myvehicle(class $unit::sedan_c)

If you only want to override one dedicated instance with a copy of another
instance, you can call override_with_copy by using the following code:

vehicle_c::override_with_copy("@%Tom:myvehicle",another_sedan_c_instance,log);

As the above example shows, with the class factory, match patterns and shorthand
macros provided with VMM, you can easily use class factories patterns to replace
transactors, transactions and other verification components without modifying the
testbench code.

Posted in VMM | 6 Comments »

RAL: Using TCL to conditionally generate registers

Posted by Vidyashankar Ramaswamy on 12th September 2010

 

Let us say you have a DMA RTL block and its registers are specified using RALF format. Now you want to instantiate it 4 times at the system level. The most common approach is to use array notation. What if you want to include one (or many) extra registers only for one of the instances ? The trivial approach is to manually create two block instances with appropriate registers. What if I want to create 5 different flavors with a very small addition or deletion of a register ? Do I have to create 5 of them by duplicating the same content ? No! This can be done automatically using the ralgen tool and its built-in tcl interpreter. Let us take a look at the following simple example.

# File: dma_registers.ralf

# Register definitions

register device_id {

   bytes 4;
 
 field device_id {
      
bits   32;
 
     access   ro;
 
     soft_reset   32′h0;
   
}
}

register command_status {

   field cmd_status {
      bits 31;
     
access rw;
      reset 32′h0;
   }
}

# DMA Block code generation

for {set i 0} {$i < 2} {incr i} {
   block dma_$i {
      bytes 4;
      register device_id@’h100;?
      if {$i == 1} {
         register command_status @’h124;
      }
   }
}

# System top level

system SYSTEM_TOP {

   bytes 4;
   block dma_0 @’h0000 ;
   block dma_1[3] @’h1000 + ‘h1000;
}

This example shows a DMA block which contains 2 registers. Say I want to create two different DMA blocks out of which one instance has the command status register. Let us call them dma_0 and dma_1. This can be easily achieved using a for loop construct to conditionally include the register as shown above. The system top instantiates one dma_0 type and three dma_1 type blocks. The following is the output from the ralgen tool.

class ral_sys_SYSTEM_TOP extends vmm_ral_sys;

   rand ral_block_dma_0 dma_0;
   rand ral_block_dma_1 dma_1[3];
   . . .

endclass : ral_sys_SYSTEM_TOP

Can I use the loop construct even at the system level ? Yes, you can replace the array notation with the loop construct. Though this is bit more complex, you will have better control over the instantiation names generated by the tool. Following code demonstrates the concept.

 system SYSTEM_TOP {

   bytes 4;

   for {set i 0} {$i < 2} {incr i} {
      set OFFSET [expr $i * 2048];
      if {$i == 1} {
        for {set j 0} {$j < 3} {incr j} {
           set OFFSET_1 [expr $OFFSET + ($j+1) * 2048];
           block dma_$i=dma_$i$j @$OFFSET_1;
        }
      }
      else {
            block dma_$i @$OFFSET;
      }
   }
}

 

The tool output is shown below.

 class ral_sys_SYSTEM_TOP extends vmm_ral_sys;

   rand ral_block_dma_0 dma_0;
   rand ral_block_dma_1 dma_10;
   rand ral_block_dma_1 dma_11;
   rand ral_block_dma_1 dma_12;
   . . .

endclass : ral_sys_SYSTEM_TOP

 Please consider creating separate files for block and systems which will help in block to top reuse of RALF.

 

 

Posted in Register Abstraction Model with RAL | No Comments »

Example of Transaction-Level Communication in VMM 1.2

Posted by John Aynsley on 9th September 2010

John Aynsley, CTO, Doulos

Having introduced many of the technical details of transaction-level communication in VMM 1.2 in previous posts on this blog, we will now look at an example highlighting the various transaction-level communication mechanisms available in VMM 1.2 so that we can get a feel for the roles they perform and how they work together.
Let’s start with a producer that generates a stream of transactions:

 

class  my_gen  extends  vmm_xactor;

  vmm_tlm_b_transport_port #(my_gen, my_tx)  m_port;
  vmm_tlm_analysis_port    #(my_gen, my_tx)  m_ap;
  …

  begin: loop
    assert( randomized_tx.randomize() ) …
    $cast(tx, randomized_tx.copy());

    `vmm_callback(my_gen_callbacks,  pre_trans(this, tx));

    m_port.b_transport(tx, delay);

    `vmm_callback(my_gen_callbacks,  post_trans(this, tx));
    m_ap.write(tx);
  end
  …

The example above makes use of a blocking transport port, an analysis port, and callbacks. After randomizing and copying the next transaction in the stream, the producer offers the transaction to the pre_trans callback, giving a test the chance to modify the transaction before it is sent downstream. The transaction is then sent through the blocking transport port, with the advantage that the simple completion semantics of b_transport makes the producer code very clean. b_transport only returns when the transaction is finished, at which point the transaction is offered to another callback post_trans, which gets the chance to modify the transaction once more. Finally the transaction is sent out through the analysis port for broadcast to passive verification components for checking and coverage collection. The analysis port may be connected to zero, one, or many such passive components.
Now for the consumer that receives the transaction:

 


class  my_bfm  extends  vmm_xactor;
  my_channel  m_chan;
  vmm_tlm_analysis_port  #(my_bfm, my_tx)  m_ap;
  …
  begin: loop
    `vmm_callback(my_bfm_callbacks, pre_trans(this, tx));

    m_chan.activate(tx);
    m_chan.start();
    @(i_f.bus_cb); …
    m_chan.complete();
    m_chan.remove();

    `vmm_callback(my_bfm_callbacks, post_trans(this, tx));
    m_ap.write(tx);
  end


The consumer transactor makes use of callbacks and an analysis port in exactly the same way as the consumer, but does not use b_transport. Rather, it receives the incoming transaction using a vmm_channel, which gives more flexibility in interacting with the transaction during its lifetime. VMM recommends the use of vmm_channel when modeling slave-like transactors. The start and complete methods each cause notifications within the transaction, which may be significant for transaction recording or debug. The remove method removes the transaction from the channel, which in this example will have the effect of unblocking the call to b_transport from the producer.
Finally, the producer and consumer are connected together in the environment:

 


class  tb_env  extends  vmm_group;

  virtual function void  build_ph;
    m_tx_chan  = new( “my_channel”, “m_tx_chan” );
    m_tx_chan.reconfigure(1);
    m_gen      = new( “m_gen”, this );
    m_bfm      = new( “m_bfm”, this );
  endfunction

  function void  connect_ph();
    vmm_connect #(.D(my_tx))::tlm_bind(m_tx_chan, m_gen.m_port,
                              vmm_tlm::TLM_BLOCKING_EXPORT );
    m_bfm.m_chan = m_tx_chan;
  endfunction
  …


It is important to note that the channel is reconfigured with a full level of exactly 1 so that the blocking transport call will indeed block until the one-and-only transaction is removed from the channel by the consumer. If the full level were greater than 1, b_transport would return immediately and the communication scheme would be broken.
In order to bind the blocking transport port of the producer to the input channel of the consumer, it is necessary to use the tlm_bind method of the vmm_connect utility, as discussed in previous posts.

So, we have seen how the transaction-level ports, analysis ports, and callbacks each have their own role to play in constructing a VMM transactor.

 

 

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

Blocking/Non-blocking Transport Adaption in VMM 1.2

Posted by John Aynsley on 1st September 2010

John Aynsley, CTO, Doulos

One neat feature of the SystemC TLM-2.0 standard is the ability provided by the so-called simple target socket to perform automatic adaption between the blocking and non-blocking transport calls; an initiator that calls b_transport can be connected to a target that implements nb_transport, and vice-versa. VMM 1.2 provides similar functionality using the vmm_connect utility.

vmm_connect serves four distinct purposes in VMM 1.2.

•    Connecting one channel port to another channel port, taking account of whether each channel port is null or actually refers to an existing channel object
•    Connecting a notify observer to a notification
•    Binding channels to transaction-level ports and exports
•    Binding a transaction-level port to a transaction-level export where one uses the blocking
transport interface and the other the non-blocking transport interface. This is the case we are considering here.

When making transaction-level connections I think of vmm_connect as covering the “funny cases”.

To illustrate, suppose we have a transactor that acts as an initiator and calls b_transport. The following code fragment also serves as a reminder of how to use the blocking transport interface in VMM:

class initiator extends vmm_xactor;
`vmm_typename(initiator)

vmm_tlm_b_transport_port  #(initiator, vmm_tlm_generic_payload) m_b_port;

virtual task run_ph;
forever begin: loop
vmm_tlm_generic_payload tx;

// Create valid generic payload
assert( randomized_tx.randomize() with {…} )
else `vmm_error(log, “tx.randomize() failed”);

$cast(tx, randomized_tx.copy());

// Send copy through port
m_b_port.b_transport(tx, delay);

// Check response status
assert( tx.m_response_status == vmm_tlm_generic_payload::TLM_OK_RESPONSE );


Further, suppose we have another transactor that acts as a target for nb_transport calls, and which therefore implements nb_transport_fw to receive the request and subsequently calls nb_transport_bw to send the response. The code fragment below is just an outline, but it does show the main idea that non-blocking transport allows timing points to be modeled by having multiple method calls in both directions (as opposed to a single method call in one direction for b_transport):

class target extends vmm_xactor;
`vmm_typename(target)

vmm_tlm_nb_transport_export #(target, vmm_tlm_generic_payload) m_nb_export;

// Implementation of nb_transport method
virtual function vmm_tlm::sync_e nb_transport_fw(
int id=-1, vmm_tlm_generic_payload trans,
ref vmm_tlm::phase_e ph, ref int delay);

-> ev;
return vmm_tlm::TLM_ACCEPTED;
endfunction : nb_transport_fw

// Process to send response by calling nb_transport on backward path
virtual task run_ph;
forever  begin: loop
vmm_tlm::phase_e phase = vmm_tlm::BEGIN_RESP;
@(ev);
tx.m_response_status = vmm_tlm_generic_payload::TLM_OK_RESPONSE;
status = m_nb_export.nb_transport_bw(tx, phase, delay);
end
endtask: run_ph

Now for the main point. We can use tlm_connect to bind the two components together, despite the fact that one uses blocking calls and the other non-blocking calls:

virtual function void build_ph;
m_initiator = new( “m_initiator”, this );
m_target    = new( “m_target”,    this );
endfunction: build_ph

virtual function void connect_ph;

vmm_connect #(.D(vmm_tlm_generic_payload))::tlm_transport_interconnect(
m_initiator.m_b_port, m_target.m_nb_export, vmm_tlm::TLM_NONBLOCKING_EXPORT);

endfunction: connect_ph

That’s all there is to it! Notice that tlm_transport_interconnect is a static method of vmm_connect so its name is prefixed by the scope resolution operator and an instantiation of the vmm_connect parameterized class with the appropriate transaction type, namely the generic payload. The first argument is the port, the second argument the export, and the third argument indicates that it is the export that is non-blocking.
It is also possible to connect a non-blocking initiator to a blocking target: you would simply replace TLM_NONBLOCKING_EXPORT with TLM_NONBLOCKING_PORT. The objective of this post has been to show that VMM has all the bases covered when it comes to connecting together transaction-level ports.

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