Verification Martial Arts: A Verification Methodology Blog

Archive for 2009

Navigate through sea of log messages in a SoC env – smart application of vmm_log

Posted by Shankar Hemmady on 13th August 2009

srinivasan_VenkataramanBagath_SinghSrinivasan Venkataramanan & Bagath Singh, CVC Pvt. Ltd. Jaya Chelani, Quartics Technologies

Consider an SoC with several interfaces being verified.  It is quite common to have each interface report its activity via display messages.  Now let’s take up a case where-in a specific user is debugging say the AXI interface for failure, performance analysis etc.  While the full log file is providing the overall picture, it is quite possible to get lost quickly in the sea of messages.

Won’t it be nice to have AXI report all its activities to its own log file? This would greatly reduce the analysis/debug time for a given task.  However doing a change inside the testbench is not a good practice, if one were to do it via `ifdef, config etc.  Also, it may be restricted to only few tests/runs, and not for entire regression.

Huh, such a common scenario, you wonder.  Yes, and that’s why vmm_log has that capability built-in.  By default, loggers direct messages to STDOUT, but can be easily asked to direct them to a specific file. The function vmm_log::log_start(file_pointer) performs just this and can be used at will during run time.

1. program automatic sep_log_files;

2.   `include “vmm.sv

3.   vmm_log axi_logger, cvc_prop_if_logger;

4.   int axi_fp;

5.   initial begin : b1

6.     axi_fp = $fopen (“axi.log”, “w”);

7.     axi_logger = new (“AXI Log”, “0″);

8.     cvc_prop_if_logger = new (“CVC Log”, “0″); // Defaults to STDOUT

9.     axi_logger.log_start(axi_fp); // AXI alone is being sent to a separate LOG file

10.    `vmm_note (axi_logger, “Message from AXI Interface”);

11.    `vmm_note (cvc_prop_if_logger, “Messages from CVC Proprietary Interface”);

12.   end : b1

13.  endprogram : sep_log_files

Line 6 opens a file named “axi.log” for writing. Line 9 ensures that all messages form axi_log to the new log file pointer (created in Line 6). Note that one can point to any logger inside the env via hierarchical path and hence this can be done at a testcase level, if desired.

A few additional notes

1. As these methods work on specific log instance, they ought to be used once the log instance is constructed, typically after the vmm_env::build() phase.

2. There is also a counterpart to stop logging vmm_log::log_stop(file_pointer) to a separate file (it continues sending messages to STDOUT).

3. By design, the vmm_log::log_start() sends a copy of the message to the specified file in addition to STDOUT. This is done so that the complete log (sent to STDOUT) is intact from all loggers in the environment.

4. If it is desired by the user to avoid the duplication of log messages, the user can use an explicit call to vmm_log::log_stop(STDOUT).

5. These two methods can also be useful in performance measurement during a specific time window in a simulation run. One can setup notifiers to indicate the start & stop of the time window, and run a parallel thread to correspondingly perform log_start()  log_stop(). More on this in another blog post.

Posted in Debug, Messaging, SystemVerilog, Tutorial | 2 Comments »

Give Me Some Space, Man!

Posted by Shankar Hemmady on 11th August 2009

andrew_piziali2 Andrew Piziali, Independent Consultant

A question I am often asked is “When and where should I use functional coverage and code coverage?” Since the purpose of coverage is to quantify verification progress, the answer lies in understanding the coverage spaces implemented by these two kinds of coverage.

A coverage space represents a subset of the behavior of your DUV (design under verification), usually of a particular feature. It is defined by a set of metrics, each a parameter or attribute of the feature quantified by the space. For example, the coverage space for the ADD instruction of a processor may be defined by the product of the absolute values of ranges of the operands (remember “addends?”) and their respective signs. In order to understand the four kinds of coverage metrics, we need to discuss the coverage spaces from which they are constructed.

A coverage metric is determined by its source—implementation or specification—and its author—explicit or implicit. An implementation metric is derived from the implementation of the DUV or verification environment. Hence, the width of a data bus is an implementation metric, as is the module defining a class. Conversely, a specification metric is derived from the DUV functional or design specification. A good example is the registers and their characteristics defined in a specification.

The complementary coverage metric classification is determined by whether the metric is explicitly chosen by an engineer or implicit in the metric source. Hence, an explicit metric is chosen or invented by the verification engineer in order to quantify some aspect of a DUV feature. For example, processor execution mode might be chosen for a coverage metric. Alternatively, an implicit metric is inherent in the source from which the metric value is recorded. This means things like module name, line number and Boolean expression term are implicit metrics from a DUV or verification environment implementation. Likewise, chapter, paragraph, line, table and figure are implicit metrics from a natural language document, such as a specification.

Combining the two metric kinds—source and author—leads to four kinds of coverage metrics, each defining a corresponding kind of coverage space:

  1. Implicit implementation metric

  2. Implicit specification metric

  3. Explicit implementation metric

  4. Explicit specification metric

An example of an implicit implementation metric is a VHDL statement number. The register types and numbers defined by a functional specification are an implicit specification metric. Instruction decode interval is an explicit implementation metric. Finally, key pressed-to-character displayed latency is an example of an explicit specification coverage metric.

Each metric kind may be used to define an associated kind of coverage space. The astute reader may also wonder about coverage spaces defined by a mix of the above metric kinds. If such a hybrid space more precisely quantifies the verification progress of a particular feature, use it! To the best of my knowledge, you’d have to design and implement this space in much the same way as any functional coverage space because no commercial tool I am aware of offers this kind of coverage.

With an understanding of the kinds of coverage spaces, we can now classify functional and code coverage and figure out where they ought to be used. Functional coverage, making use of explicit coverage metrics—independent of their source—defines either an explicit implementation space or an explicit specification space. Code coverage tools provide a plethora of built-in implicit metric choices. Hence, it defines implicit implementation spaces. Where you want to measure verification progress relative to the DUV functional specification, where features are categorized and defined, functional coverage is the appropriate tool. Where you want to make sure all implemented features of the DUV have been exercised, you should use code coverage. Lastly, when your code coverage tool does not provide sufficient insight, resolution or fidelity into the behavior of the DUV implementation, functional coverage is required to complement the implicit spaces it does offer.

Functional coverage can tell you the DUV is incomplete, missing logic required to implement a feature or a particular corner case, whereas code coverage cannot. On the other hand, code coverage can easily identify unexercised RTL, while functional coverage cannot. Functional coverage requires a substantial up-front investment for specification analysis, design and implementation yet relieves the engineer of much back-end analysis. Code coverage, on the other hand, may be enabled at the flip of a switch but usually requires a lot of back-end analysis to sift the false positives from the meaningful coverage holes. Both are required—and complementary—but their deployment must be aligned with the stage of the project and DUV stability.

Some smart alec will point out that you can’t measure verification progress using coverage alone, and you’re right! Throughout this discussion I assume each feature, with its associated metrics, has corresponding checkers that pipe up when the DUV behavior differs from the specified behavior. (I’ll leave the topic of concurrent behavior recording and checking for another day.)

If you’d like to learn much more about designing, implementing, using and analyzing coverage, the following books delve much more deeply into verification planning, management and coverage model design:

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

Address alignment in Memory Allocation Manager

Posted by Janick Bergeron on 10th August 2009

jb_blog

Janick Bergeron, Synopsys Fellow

The VMM Memory Allocation Manager (vmm_mam) can be used to manage a shared address space. It does not physically allocate memory but dishes out address ranges that are guaranteed to be previously unallocated.

By default, the only constraints on the Memory Allocation Manager are 1) the entire address range must not be already allocated, 2) the starting offset must be greater than or equal the base offset of the memory and 3) the ending offset must be less than or equal to the offset limit.

That’s it.

So randomly allocating memory will result in memory regions starting at random positions. For example, the following code allocates ten 4-byte regions in a 256-byte memory:

program test;

`include “vmm.sv”
`include “vmm_ral.sv”

initial
begin
vmm_mam_cfg    cfg;
vmm_mam        mgr;
vmm_mam_region bfr;

cfg = new;
cfg.n_bytes      = 1;
cfg.start_offset = 8′h00;
cfg.end_offset   = 8′hFF;

mgr = new(“Mem Mgr”, cfg);

repeat (10) begin
bfr = mgr.request_region(4);
$write(“%s\n”, bfr.psdisplay());
end
endprogram

And produce the following results:

['h0000000000000080:'h0000000000000083]
['h0000000000000015:'h0000000000000018]
['h00000000000000bd:'h00000000000000c0]
['h00000000000000b9:'h00000000000000bc]
['h00000000000000e6:'h00000000000000e9]
['h000000000000005f:'h0000000000000062]
['h000000000000008d:'h0000000000000090]
['h0000000000000025:'h0000000000000028]
['h0000000000000051:'h0000000000000054]
['h0000000000000087:'h000000000000008a]

but what if you needed the region to be aligned on quad-word boundaries?

Simple!

You can add constraints by extending the vmm_mam_allocator object and supplying the new allocator to the request_region() call or making it the default allocation policy on the Memory Allocation Manager instance:

program test;

`include “vmm.sv”
`include “vmm_ral.sv”

class qword_aligned_allocator extends vmm_mam_allocator;
constraint qword_aligned {
start_offset[1:0] == 0;
}
endclass

initial
begin
vmm_mam_cfg    cfg;
vmm_mam        mgr;
vmm_mam_region bfr;

cfg = new;
cfg.n_bytes      = 1;
cfg.start_offset = 8′h00;
cfg.end_offset   = 8′hFF;

mgr = new(“Mem Mgr”, cfg);

begin
qword_aligned_allocator alloc = new;

repeat (5) begin
bfr = mgr.request_region(4, alloc);
$write(“%s\n”, bfr.psdisplay());
end

mgr.default_alloc = alloc;

repeat (5) begin
bfr = mgr.request_region(4);
$write(“%s\n”, bfr.psdisplay());
end
end

end

endprogram

The resulting regions are now QWORD aligned:

['h00000000000000b4:'h00000000000000b7]
['h0000000000000064:'h0000000000000067]
['h00000000000000c0:'h00000000000000c3]
['h0000000000000008:'h000000000000000b]
['h00000000000000b8:'h00000000000000bb]
['h0000000000000048:'h000000000000004b]
['h0000000000000010:'h0000000000000013]
['h0000000000000018:'h000000000000001b]
['h00000000000000d4:'h00000000000000d7]
['h00000000000000fc:'h00000000000000ff]

Other kind of constraints can you add via a customized allocator include:

  • Region must be within a 1-k address page
  • 75% of regions must be in the upper half of the address space
  • Regions must be allocated from a pool of pre-allocated regions

Remember that regions are allocated by randomizing the allocator object. That means that a highly directed and procedural allocation policy (such as the last one mentioned above) can be implemented using the post_randomize() method.

Once you have a region, if you have associated the Memory Allocation Manager with a memory RAL abstraction class, you may access it as if it were a tiny memory in and of itself. The integration of MAm and RAL effectively implements a mini virtual-memory system.

Posted in Customization, Tutorial | No Comments »

Introducing VMM 1.2

Posted by Fabian Delguste on 27th July 2009

Fabian Delguste / Synopsys Verification Group

I’m very pleased to announce that VMM 1.2 beta is now available. You’re welcome to enroll our VMM 1.2 Beta program by signing up the form on VMM Central at:
http://www.vmmcentral.org/cgi-bin/beta/reg1.cgi

As you know, the VMM methodology defines industry best practices for creating robust, reusable and scalable verification environments using SystemVerilog. Introduced in 2005, it is the industry’s most proven verification methodology for SystemVerilog, with over 500 successful tape outs and over 50 SNUG papers. The VMM methodology enables verification novices and experts alike to develop powerful transaction-level, constrained-random verification environments. A comprehensive set of guidelines, recommendations and rules help engineers avoid common mistakes while creating interoperable verification components. The VMM Standard Library provides the foundation base classes for building advanced testbenches, while VMM Applications like the Register Abstraction Layer, Performance Analyzer and Hardware Abstraction Layer provide higher-level functions for improved productivity.

We’ve gained valuable feedback and insight after working with customers on hundreds of production verification projects using VMM. From time to time, we incorporate these findings back into VMM so the broad VMM community can take advantage of them. Such is the case with VMM 1.2, where we’ve made some great enhancements to improve productivity and ease of use. These changes, like those in last year’s VMM 1.1 update are backward compatible with earlier releases so you won’t have to change your existing code to take advantage of the new features.

Here are some of new features in VMM 1.2:

  • SystemC/SystemVerilog TLM 2.0 support
    • We have added TLM-2.0 support, which adds remote procedure call functionality between components and extends support to SystemC modules
    • TLM-2.0 can connect to VMM channels and notification. The conjunction of both interfaces creates a robust completion model. You can now easily integrate reference models written in SystemC with TLM-2.0 transport interface directly in your VMM testbench
  • Enhanced block-to-top reuse
    • Hierarchical phasing: we have added the concept of phasing and timelines for enhanced flexibility and reuse of verification components. You can now control execution order directly from transactors and get all phase to be coordinated in upper layers
    • Class Factory: this allows for faster stimulus configuration and reuse. It’s now possible to replace any kind of transaction, scenario, transactor and class
  • Enhanced ease-of-use
    • Implicit phasing: as each transactor comes with its own pre-defined phasing, You can use these phases to ensure my transactor are fully controlled and do follow some pre-defined phases conventions. Each transactor controls its own status. Serial phasing supports multiple timelines within a simulation, improving regression productivity. This way, you can run multiple tests one after the other in the same simulation run
    • Parameterization: VMM 1.2 adds new classes and concepts to provide additional functionality and flexibility. We have added parameterization support to many existing classes including channels, generators, scoreboard, notify and new base classes for making it easy to connect transactors together
    • Configuration Options: VMM 1.2 adds a rich set of methods for controlling testbench functionality from the runtime command line
    • Common Base Class: VMM 1.2 make possible multiple name spaces, hierarchical naming along with enhanced search functionality RTL configuration insures the same configuration is shared between RTL and testbench.

We’ll cover these new features in more detail in subsequent blogs.

If you are at DAC, you can pick up a copy of the new Doulos VMM Golden Reference Guide, which contains more details on the VMM 1.2 enhancements. This will be available in the VCS suite at the Synopsys booth, at the Synopsys Standards booth while Doulos is presenting, and at the Synopsys Interoperability Breakfast on Wednesday morning.

VMM 1.2 is available today for beta review. You’re welcome to sign up for our beta program. Please note that the beta program runs from today until August 31, 2009.

I look forward to hearing what you think about the new VMM 1.2 features!


Posted in Announcements, SystemVerilog, VMM, VMM infrastructure | No Comments »

How to connect your SystemC reference model to your verification framework

Posted by Janick Bergeron on 8th July 2009

Nasib Naser, Phd, CAE, Synopsys

Architects and designers have many reasons to start using the SystemC language to describe their high level specifications for their target System-on-Chip (SoC). In this blog I will not discuss the “why” the SystemC selection BUT the “how” to bring these models into the team’s design verification flow.

Mixed abstractions come from the fact that designers often develop models at the transaction level to capture the correct architecture and analyze the system performance and do so fairly early in the design cycle. Various components of a SoC developed with high level languages such as C, C++, SystemC, and SystemVerilog are increasingly in demand and for obvious reasons. They are faster to write and faster to simulate. High level reference models became increasingly important in verifying the implementation details at the RTL level.

Although SystemVerilog provides different methods (PLI, VPI, DPI, etc) to support communications between SystemVerilog and C/C++ domains, it is not so straight forward for SystemC interface methods. One important reason is that these SystemC methods can be “blocking”, i.e. these methods can “consume” time. And these “blocking” SystemC methods can return value as well. Users should build a very elaborate mechanism to maintain synchronization between the simulations running in the SystemVerilog and the SystemC domains.

Synopsys’ VCS functional verification solution addresses the challenge of this use model with its SystemC-SystemVerilog Transaction-Level Interface (TLI). With TLI, VCS automatically generates adapters to be instantiated in each domain to support SystemVerilog block calls of SystemC methods, or vice versa. These TLI adapters take care of the synchronization between both domains.

To demonstrate the ease of use of this unique interface we start by identifying the SystemC interface methods that SystemVerilog require accessibility to. In the following example we define Read and Write transactions as SC Interface methods as such:

#include<systemc.h>
class Mem_if : virtual public sc_interface {
public :

   //do the pure virtual function declaration here
  
virtual void rd_data(int addr, int &data)=0;
  
virtual void wr_data(int addr, int data)=0;
};

Manually we create an interface configuration file that declares the methods required for the interface. These methods are declared in a file called Mem_if.h. The content of this configuration file is as follows:

interface Mem_if
direction sv_calls_sc
verilog_adapter Mem_if_adapt_vlog
systemc_adapter Mem_if_adapt_sc

#include "Mem_if.h"

task rd_data
input int addVal
inout& int dataVal

task wr_data
input int addVal
input int dataVal

#endif
};

VCS uses this configuration file to generate the necessary SystemC and SystemVerilog code that enables complete synchronization. The command line is as follows:

%syscan -idf Mem_if.idf

This operation creates the following helper files:

Mem_if_adapt_sc.cpp
Mem_if_adapt_sc.h
Mem_if_adapt_vlog.sv

The helper file Mem_if_adapt_vlog.sv contains a SystemVerilog Interface called Mem_if_adapt_vlog which declares the following tasks:

task wr_data(input int addVal, input int dataVal);
task rd_data(input int addVal, inout int dataVal);

These tasks could then be wrapped and used in the SystemVerilog testbench to access SystemC methods as demonstrated in the following code:

module testbench;
reg clk;
int data;
int cnt;

// Instantiate the helper interface
Mem_if_adapt_vlog #(0) mem_if_adapt_vlog_inst();

// THE CODE BELOW IS WHERE ALL THE SC TLI FUNCTIONS ARE CALLED.
// VERILOG TASKS ARE WRITTEN AS A WRAPPER FOR READABILITY.
// THESE WRAPPER TASKS ARE INSTANTIATED IN THE VERILOG CODE.

// Calling SC TLI function **Write Data**
task
sv_wr_data(input int addr, input int data);
  
mem_if_adapt_vlog_inst.wr_data(addr,data);
endtask

// Calling SC TLI function **Read Data**
task
sv_rd_data(input int addr, output int data);
  
mem_if_adapt_vlog_inst.rd_data(addr,data);
endtask

. . .

// Use the wrapper code
always @(posedge clk)
begin
   if(cnt < 100)
   begin
      #8
sv_wr_data(cnt,cnt+100);
     
cnt = cnt+1;
   end
end

always @(posedge clk)
begin
   if(cnt < 100)
   begin
      #3
sv_rd_data(cnt-1,data);
  
end
end

endmodule

Posted in SystemC/C/C++, SystemVerilog, Tutorial, VMM | 1 Comment »

A generic functional coverage solution based on vmm_notify

Posted by Wei-Hua Han on 15th June 2009

Weihua Han, CAE, Synopsys

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

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

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

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

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

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

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

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

1.    Transaction class

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

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

2.    A generic subscribe class

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

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

3.    Coverage class

1.   class eth_cov;
2.      eth_frame tr;

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

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

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

4.    Monitor

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

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

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

5.    Connect monitor and coverage object

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

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

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

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

Posted in Communication, Coverage, Metrics, Register Abstraction Model with RAL, Reuse, SystemVerilog, VMM | 4 Comments »

Protocol Layering Using Transactors

Posted by Janick Bergeron on 9th June 2009

jb_blog Janick Bergeron
Synopsys Fellow

Bus protocols, such as AHB, are ubiquitous and often used in examples because they are simple to use: some control algorithm decides which address to read or write and what value to expect or to write. Pretty simple.

But data protocols can be made a lot more complex because they can often be layered arbitrarily. For example, an ethernet frame may contain an IP segment of an IP frame that contains a TCP packet which carries an FTP frame. Some ethernet frames in that same stream may contain HDLC-encapsulated ATM cells carrying encrypted PPP packets.

How would one generate stimulus for these protocol layers?

One way would be to generate a hierarchy of protocol descriptors representing the layering of the protocol. For example, for an ethernet frame carrying an IP frame, you could do:

class eth_frame extends vmm_data;
rand bit [47:0] da;
rand bit [47:0] sa;
rand bit [15:0] len_typ;
rand ip_frame payload;
rand bit [31:0] fcs;

endclass

class ip_frame extends vmm_data;
eth_frame transport;
rand bit [3:0] version;
rand bit [3:0] IHL;

rand bit [7:0] data;
endclass

That works if you have exactly one IP frame per ethernet frame. But what if your IP frame does not fit into the ethernet frame and it needs to be segmented? This approach works when you have a one-to-one layering granularity, but not when you have to deal with one-to-many (i.e. segmentation), many-to-one (i.e. reassembly, concatenation) or plesio-synchronous (e.g. justification) payloads.

This approach also limits the reusability of the protocol transactions: the ethernet frame above can only carry an IP frame. How could it carry other protocols? or random bytes? How could the IP frame above be transported by another protocol?

And let’s not even start to think about error injection…

One solution is to use transactors to perform the encapsulation. The encapsulator would have an input channel for the higher layer protocol and an output channel for the lower layer protocol.

class ip_on_ethernet extends vmm_xactor;
ip_frame_channel in_chan;
eth_frame_channel out_chan;

endclass

The protocol transactions are generic and may contain generic references to their payload or transport layers.

class eth_frame extends vmm_data;
vmm_data transport[$];
vmm_data payload[$];

rand bit [47:0] da;
rand bit [47:0] sa;
rand bit [15:0] len_typ;
rand bit [  7:0] data[];
rand bit [31:0] fcs;

endclass

class ip_frame extends vmm_data;

vmm_data transport[$];
vmm_data payload[$];

rand bit [3:0] version;
rand bit [3:0] IHL;

rand bit [7:0] data;
endclass

The transactor main() task, simply waits for higher-layer protocol transactions, packs them into a byte stream, then lays the byte stream into the payload portion of new instances of the lower-layer protocol.

virtual task main();
super.main();

forever begin
bit [7:0] bytes[];
ip_frame ip;
eth_frame eth;

this.wait_if_stopped_or_empty(this.in_chan);
this.in_chan.activate(ip);

// Pre-encapsulation callbacks (for delay & error injection)…

this.in_chan.start();
ip.byte_pack(bytes, 0);
if (bytes.size() > 1500) begin

`vmm_error(log, “IP packet is too large for Ethernet frame”);
continue;
end

eth = new(); // Should really use a factory here

eth.da = …;
eth.sa = …;
eth.len_typ = ‘h0800;  // Indicate IP payload

eth.data = bytes;
eth.fcs = 32’h0000_0000;

ip.transport.push_back(eth);
eth.payload.push_back(ip);

// Pre-tx callbacks (for delay and ethernet-level error injection)…

this.out_chan.put(eth);
eth.notify.wait_for(vmm_data::ENDED);

this.in_chan.complete();

// Post-encapsulation callbacks (for functional coverage)…

this.in_chan.remove();
end
endtask

When setting the header fields in the lower-layer protocol, you can use values from the higher-layer protocols (like setting the len_typ field to 0×0800 above, indicating an IP payload), you can use values configured in the encapsulator (e.g. a routing table) or they can be randomly generated with appropriate constraints:

if (!route.exists(ip.da)) begin
bit [47:0] da = {$urandom, $urandom};  // $urandom is only 32-bit

da[41:40] = 2’b00; // Unicast, global address
route[ip.da] = da;
end
eth.da = route[ip.da];

The protocol layers observed by your DUT are then defined by the combination and order of these encapsulation transactor.

vmm_scheducler instances may also be used at various points in the layering to combine multiple streams (maybe carrying different protocol stacks and layers) into a single stream.

Posted in Modeling, Modeling Transactions, Phasing, Structural Components, SystemVerilog, Tutorial | 3 Comments »

How VMM can help controlling transactors easily?

Posted by Fabian Delguste on 29th May 2009

Fabian Delguste / Synopsys Verification Group

Controlling VMM transactors can sometimes be a bit hectic. A typical situation I see is when I have registered a list of transactors for driving some DUT interfaces but only want to start a few of them. Another common situation is when I want to turn off scenario generators and replay transactions directly from a file. Yet another task I often face is registering transactor callbacks without knowing where they are exactly located in the environment.

As you can see, there are many situations where fine-grain functional control of transactors is necessary.

Since VMM 1.1 came out, I have been using a new base class called vmm_xactor_iter that allows accessing any transactor directly by name. In this case all I need to do is to construct a vmm_xactor_iter with regular expression and use the iterator to loop thru all matching transactors.

To understand better how this base class works, I’ll show you a real life example. The scope of this example is to show how to start generators only when vmm_channel playback has not been turned on. As you know vmm_channel can be used to replay transactions directly from files that contain transactions that were recorded in a previous session. This can speed up simulation by turning off constraint solving.

1. string match_xactors = (cfg.mode == tb_cfg::PLAYBACK) ? /Drivers/” : “/./”;

2.

3. `foreach_vmm_xactor(vmm_xactor, “/./”, match_xactors)

4. begin

5.  `vmm_note(log, $psprintf(“Starting %s”, xact.get_instance()));

6.   xact.start_xactor();

7. end

  • In line 1, match_xactors string takes “Drivers*” value when playback mode is selected otherwise it takes “.” when no this mode is not selected. In the first case, transactors named “Drivers” match otherwise all transactors, including generators match
  • In line 3, `foreach_vmm_xactor macro is used to create a vmm_xactor_iter using previous regular expression. This macro can be used to traverse and start all matching objects by using the xact object to access transactors

In case you’d like to have more control over vmm_xactor_iter, it’s possible to use its first() / next() / xactor() methods to traverse matching transactors. Also it’s possible to ensure the regular expression returns at least one transactor. Here is the same example written using these methods.

1. string match_xactors = (cfg.mode == tb_cfg::PLAYBACK) ? “/Drivers/” : “/./”;

3. vmm_xactor_iter iter = new(“/./”, match_xactors);

4. if(iter.xactor()==null)

5. `vmm_fatal(log, $psprintf(“No matching transactors for ‘%s’”, match_xactors));

7. while(iter.xactor()!=null) begin

8.   xact = iter.xactor();

9.   xact.start_xactor();

10.end

Should you need to reclaim the memory allocation required to store all transactors, it’s possible to enable garbage collection by invoking vmm_xactor::kill().

The good news is that vmm_xactor_iter allows me to:

  • Configure the transactor without knowing its hierarchy
  • Provide dynamic access to transactors
  • Reduce code for multiple configurations and callback extensions
  • Use powerful regular expressions for name matching
  • Reuse transactors: no need to modify code when changing the environment content

I hope you find vmm_xactor_iter, and all of the other VMM features, as useful as I do

Posted in Configuration, Structural Components, SystemVerilog, Tutorial | No Comments »

VMM VIP’s on multiple buses

Posted by Adiel Khan on 27th May 2009

image

Adiel Khan, Synopsys CAE

Increasingly, more design-oriented engineers are writing VMM code. Some are trying to map typically good design architecture practices to verification development.

A dangerous mapping is parameterization, from modules to classes.

In my old Verilog testbenches I would develop reusable modules and use #parameters extensively to control the settings of the modules I was instantiating. (It was a sad day when I heard IEEE was deprecating my friend the defparam).

1. module vip #(parameter int data_width = 16,

2. parameter int addr_width = 16)

3. (addr, data);

4. output [addr_width-1:0] addr;

5. inout [data_width-1:0] data;

6.

7. endmodule

This would allow me to instantiate this VIP for many bus variants.

8. vip #(64, 32) vip_inst1(…);

9. vip #(32, 128) vip_inst2(…);

Mapping the approach from modules to classes, I could end up with:

1. class pkt_c #( parameter int data_size=16,

2. parameter int addr_size=16)

3. extends vmm_data;

4. rand bit [addr_size-1:0] addr;

5. rand bit [data_size-1:0] data;

6.

7. endclass

8. //specialized class with 64 & 32 sizes

9. pkt_c #(64, 32) pkt1=new();

10. //specialized class with 32 & 128 sizes

11. pkt_c #(32, 128) pkt2=new();


Be warned, in the SystemVerilog testbench centric view of VIP reusability, parameterization of classes leads to a dead-end path. Moving one layer of abstraction up, I really don’t care if it is a 32/64/128 bits wide interfaces. What I want to do is use pkt_c around the verification environment. The simplest case is creating a reusable driver using pkt_c to drive any bus-width interface.

However, if I try to use a generic class instantiation, I will get a specialization with parameters = 16&16. I cannot perform the $cast() to put the right pkt_c type onto the bus.


1. class pkt_driver_c extends vmm_xactor;

2. virtual protected task main();

3. forever begin : GET_OBJ_TO_SEND

4. pkt_c pkt_to_send; //default class instance

5. pkt_c #(64, 32) pkt_created;

6. randomize(pkt_created);// generator code

7. $cast(pkt_to_send, pkt_created); //FAILS !!!!!

If you are using VMM channels, they must similarly be specialized and cannot carry generic parameterized classes:

8. `vmm_channel(pkt_c)

9. class pkt_driver_c extends vmm_xactor;

10. pkt_c_channel in_chan; //Can only carry pkt_c#(16,16)!!!

Or you must upfront select which specialization you want for use with a parameterized channel.

8. class pkt_driver_c extends vmm_xactor;

9. vmm_channel_typed #(pkt_c#(64, 32)) in_chan;


Hence, for the driver to operate on the correct object type, I need to instantiate the exact specialization throughout my entire environment and make the driver itself parameterized. Now you can clearly see instantiating a specific specialization in the driver (or monitor, scoreboard etc) stops the code from being really reusable for other bus_widths.


1. pkt_c #(64, 32) pkt_to_send;

2. pkt_driver_c #(64, 32) driver;


A better approach is one that was described by Janick in the “Size Does Matter” blog of using `define. Let’s expand on this and see how it works for reusable VIPs. Well, the first thing that comes to my mind is that a `define is a global namespace macro with a single value, whereas I am using my VIP with 2 different bus architectures. Therefore, the `define alone is not enough: you also need a local constant to be able to exclude unwanted bits when you have a VIP instantiated for various bus widths.

1. //default define values

2. `define MAX_DATA_SIZE 16

3. `define MAX_ADDR_SIZE 16

4. class pkt_c extends vmm_data;

5. static vmm_log log = new(“Pkt”, “class”);

6. //instance constant to control actual bus sizes

7. const int addr_size;

8. logic [`MAX_ADDR_SIZE:0] addr;

9. logic [`MAX_DATA_SIZE:0] data;

10. // pass a_size as arg to coverage

11. // ensuring valid coverage ranges.

12. covergroup cg (int a_size);

13. coverpoint addr

14. {bins ad_bin[] = {[0:a_size]};}

15. endgroup

16. // sizes specialized at construction for pkts

17. // on buses less than MAX bus widths

18. function new(int a_s=`MAX_ADDR_SIZE);

19. addr_size = a_s;

20. cg = new(addr_size);

21. `vmm_note(log, $psprintf(“\nADDR_TYPE: “,$typename(addr),

22. “\nDATA_TYPE: “,$typename(data),

23. “\nMAX_BUS_SIZE: “, addr_size));

24. endfunction

The code above allows for a default implementation and all the user needs to do is set the `MAX_ADDR_SIZE and `MAX_DATA_SIZE symbols and all the code will be fully reusable across drivers, monitors, subenv, SoC etc.

For situations where two VIP’s of differing bus architectures are used, the compiler symbols need to be set to the biggest bus architecture in the system; smaller bus-widths are set using addr_size. It is not necessary for addr_size variable to be an instance constant or set during construction. By using instance constants, this ensures the bus-widths are not changed at runtime by users. Having the value of addr_size set during construction gives the users the flexibility to setup the object as they want. For pseudo-static objects such as drivers, monitors, subenvs, masters, slaves, scoreboards etc you should check the construction of verification modules for your particular design architecture during the vmm_env::start phase.

N.B not shown above, but assumed, is that the addr_size variable would be used to ensure correct masking occurs when performing do_pack(), do_unpack() compare() etc.

Just to wrap up some loose ends…

I’m not totally discounting the merits of parameterized classes just insuring people look at all the options. For instance you could parameterize everything and then set the SIZE at the vmm_subenv level and map the SIZE parameters to all other objects. At some point you will want to monitor or scoreboard across different bus-widths and then the parameterized class casting will bite you, reducing you to manually mapping the members within the comparison objects. There is a time and place for everything, so probably need another blog showing merits and where to use parameterized classes.

The vmm_data class is not the only place you might need to know the size of the bus, the same `define & instance constant technique can be used throughout your VIP classes.

This blog does not discuss the pros and cons of putting coverage groups in your data object class. I merely used the covergroup in the data-object as a vehicle to demonstrate how you can make your classes more reusable. I think a separate blog about where best to put coverage will clarify the usage models.

All the code snippets can be run with VCS-2009.06 & VMM1.1. Contact me for more complete code examples and bugs or issues you find.

Posted in Coding Style, Configuration, Register Abstraction Model with RAL, Reuse, Structural Components, VMM | 8 Comments »

Did you notice vmm_notify?

Posted by Janick Bergeron on 20th May 2009

jb_blog Janick Bergeron, Synopsys Fellow

In the recent past, we’ve had several customers state that VMM did not have a one-to-many passive transaction-level interface akin to Analysis Ports in some other methodology (whose name escapes me right now :-) .

Not true. Always had. From day one. Since March 2006 to be exact.

The VMM Notification Service (vmm_notify) can be used to connect a transactor (or channels or any other testbench component) to a scoreboard or functional coverage collector or any other passive observer. There can be multiple observers, and they will all see the same transaction stream.

There are pre-defined notification in vmm_xactor and vmm_channel: you might want to look them up as they may already provide all the information you are looking for. But if you need to define your own, here’s how you can do it.

First, you need to configure a new indication:

class monitor extends vmm_xactor;
int OBSERVED;

function new(string name);
super.new(“monitor”, name);
this.OBSERVED = this.notify.configure();
endfunction

endclass

then you simply indicate the notification whenever appropriate, supplying a transaction descriptor as status:

class monitor extends vmm_xactor;
int OBSERVED;


task main();
int n = 0;
super.main();
forever begin
trans tr = new;
tr.data_id = n++;

this.notify.indicate(this.OBSERVED, tr);

end
endtask
endclass

Any number of observers can now monitor the transactions through the indication via the vmm_notify::wait_for() method:

class observer;
function new(string name, vmm_notify ntfy, int id);
fork
forever begin
vmm_data tr;
ntfy.wait_for(id);
tr = ntfy.status(id);
$write(“At %0d on %s: %s\n”, $time, name, tr.psdisplay());
end
join_none
endfunction
endclass

program tb_env;
initial
begin
monitor  mon  = new(“tb_env.mon”);
observer sb1  = new(“[sb1] “, mon.notify, mon.OBSERVED);
observer sb2  = new(“[sb2] “, mon.notify, mon.OBSERVED);

end
endprogram

There is only one problem with using the vmm_notify::wait_for() method: it won’t catch multiple indications with no delays between them. If there can be multiple indications in the same simulation cycle, you must use the vmm_notify_callbacks::indicated() method to observe them. This complicates the coding of the observer slightly but provides a more reliable observability:

typedef class observer;
class subscribe extends vmm_notify_callbacks;
local observer obs;
function new(observer obs);
this.obs = obs;
endfunction
virtual function void indicated(vmm_data status);
this.obs.observe(status);
endfunction

endclass

class observer;
string name;
function new(string name, vmm_notify ntfy, int id);
subscribe cb = new(this);
ntfy.append_callback(id, cb);
this.name = name;
endfunction

function void observe(vmm_data tr);
$write(“At %0d on %s: %s\n”, $time, name, tr.psdisplay());
endfunction
endclass

If you are going to have a lot of different observer classes, this additional code can become a burden. It can be greatly simplified by using a template and agreeing on a method name within the observer class to do the observation:

class subscribe #(type T = int) extends vmm_notify_callbacks;
local T obs;
function new(T obs, vmm_notify ntfy, int id);
this.obs = obs;
ntfy.append_callback(id, this);
endfunction
virtual function void indicated(vmm_data status);
this.obs.observe(status);
endfunction
endclass

class observer;
string name;
function new(string name, vmm_notify ntfy, int id);
subscribe#(observer) cb = new(this, ntfy, id);
this.name = name;
endfunction
function void observe(vmm_data tr);
$write(“At %0d on %s: %s\n”, $time, name, tr.psdisplay());
endfunction
endclass

The parameterized class subscribe needs to be written only once, and can then be used with any class that implements an observe() function:

class yet_another_observer;
string name;
function new(string name, vmm_notify ntfy, int id);
subscribe#(yet_another_observer) cb = new(this, ntfy, id);
this.name = name;
endfunction
function void observe(vmm_data tr);
$write(“At %0d on %s: %s\n”, $time, name, tr.psdisplay());
endfunction
endclass

Look for a pre-defined vmm_notify callback subscription class template in the next VMM release…

Posted in Communication | 3 Comments »

Multi-stream Scenario Generator (MSS)

Posted by Amit Sharma on 15th May 2009

Amit  Sharma, CAE Manager, Synopsys

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

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

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

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

1. class my_scenario extends vmm_ms_scenario;

2.    my_atm_cell_scenario atm_scenario;

3.    my_cpu_scenario cpu_scenario;

4.    atm_cell_channel atm_chan;

5.    cpu_channel cpu_chan;

6.

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

8.    local bit [7:0] id;

9.

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

11.       super.new(null);

12.       atm_scenario = new(id);

13.       cpu_scenario = new();

14.       this.id = id;

15.   endfunction: new

16.

17.   task execute(ref int n);

18.       fork

19.         begin

20.            atm_cell_channel atm_chan;

21.            int unsigned nn = 0;

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

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

24.             atm_scenario.apply(atm_chan, nn);

25.             n += nn;

26.         end

27.         begin

28.             cpu_channel cpu_chan;

29.             int unsigned nn = 0;

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

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

32.             cpu_scenario.apply(cpu_chan, nn);

33.             n += nn;

34.         end

35.       join

36.   endtask: execute

37. endclass: my_scenario

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

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

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

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

1. my_scenario sc0 = new(0);

2. ……

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

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

5. ….

6. ….

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

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

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

10. ….

11. `

12. gen.stop_after_n_scenarios = 10;

13.

14. …

15. gen.start_xactor();

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

17. …

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

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

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

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

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

Posted in Reuse, Stimulus Generation | 13 Comments »

The hidden pitfalls of type name hiding in a derived class

Posted by Wei-Hua Han on 9th May 2009

Weihua Han, CAE, Synopsys

Here I describe one major difference between virtual and non-virtual methods, and how type name hiding can collide with these methods. This is commonly used OOP feature in SystemVerilog.

“type name hiding” here refers to the situation where a user type definition in the derived class uses the same type name defined in base class, i.e, the new type definition in derived class “hides” the type definition in base class.

SystemVerilog LRM (Language Reference Manual) does not forbid hiding base class type definition in a derived class.  Here’s a typical example:

  1. class company_xactor_c;
  2.         typedef enum { SOFT, HARD } reset_e;
  3.         function void do_reset (reset_e rst);
  4.                 if(rst==SOFT)
  5.                         $display("Do SOFT reset");
  6.                 else
  7.                         $display("Do HARD reset");
  8.         endfunction
  9. endclass
  10. class pci_prj_xactor_c extend company_xactor_c;
  11.                 typedef enum { SOFT, HARD, PCI } reset_e; 
  12.                 function void do_reset(reset_e rst);
  13.                         if(rst==SOFT)..
  14.                         else if(rst==HARD)…
  15.                         else ….
  16.                 endfunction
  17. endclass

Here we intend to have a new “reset_e” definition in PCI project by adding a new PCI specific label called “PCI”.  Next, we also rewrite the “do_reset” method which now comes with a specific “reset” behavior for PCI project.  Then, all transactors derived from “pci_prj_xactor_c” for this PCI project can use this “PCI” label to define PCI specific behaviors.

It seems everything works fine until now. But we will see problems crop up when virtual function (polymorphism) comes into the picture. 

Below is a simplified but realistic scenario.

As you know, VMM allows you to extend the base class library. It’s not uncommon for you to add some specific methods and to create your own base class library, such as:

  1. class company_xactor extends vmm_xactor;
  2.         virtual function void tb_start(int nth_run,
  3. reset_e  reset_e_list[$]);
  4.         …
  5.         endfunction
  6. endclass

Here a company-wide “tb_start” method is added to the company-wide transactor class. And “reset_e” here is derived from vmm_xactor. We may also have a project specific transactor base class like:

  1. class project_xactor extends company_xactor; 
  2.                 typedef enum int { 
  3.                          SOFT_RST, 
  4.                          PROTOCOL_RST, 
  5.                          FIRM_RST, 
  6.                          HARD_RST, 
  7.                          PON, 
  8.                          PCIE 
  9.                 } reset_e; 
  10.     virtual function void tb_start(int nth_run,  reset_e  reset_e_list[$]); 
  11.             … 
  12.             endfunction
  13. endclass

And we have a new “reset_e” definition with some new project specific labels.  “tb_start” is also refined for the project.

Although this may seem fine, there is a fairly serious error in this code: the virtual function in SystemVerilog class follows similar common OOP polymorphism requirement.

These OOP requirements are described in SystemVerilog LRM. Here is an excerpt:

“Virtual methods provide prototypes for the methods that later override them, i.e., all of the information generally found on the first line of a method declaration: the encapsulation criteria, the type and number of arguments, and the return type if it is needed. Later, when subclasses override virtual methods, they shall follow the prototype exactly by having matching return types and matching argument names, types, and directions. It is not necessary to have matching default expressions, but the presence of a default shall match. ”

In above code, “reset_e” definition in derived project_xactor class actually defines a new type, i.e, project_xactor::reset_e, which is a different one as the “reset_e” defined in vmm_xactor.  Now the prototypes of tb_start in company_xactor is
      void company_xactor::tb_reset(int nth_run, vmm_xactor::reset_e reset_e_list[$]).
And in project_xactor it is:
      void project_xactor::tb_reset(int nth_run, project_xactor::reset_e reset_e_list[$]).
These are different.  They are not in compliance with the LRM. Sure, a user can omit to make “tb_start” virtual.  Technically, this will work fine.  However, in this case, all the benefits of using polymorphism will be lost.

Although type name hiding is allowed, we need to be careful in how it is used, since polymorphism is quite commonly employed in VMM and in most testbench environments. This is a very important aspect of reuse and extendibility.

In my next blog post, I will discuss more about what is overriding, what is hiding, what are overridden and what are hiden in SystemVerilog.

Posted in SystemVerilog, VMM | 1 Comment »

Performance and statistical analysis from HDL simulations using the VMM Performance Analyzer

Posted by Badri Gopalan on 30th April 2009

Badri Gopalan, Principal Engineer, Synopsys

There are several situations where RTL-level performance measurements are needed to validate micro-architectural and architectural assumptions, as well as to tune the RTL for optimal performance.

A few examples of these low-level measurement requirements are:

· Throughput, latency and effects of configuration parameters of a memory or DMA controller under different traffic scenarios

· The statistical distribution from a prediction scheme for various workloads

· Latency of a complex multi-level arbiter under different conditions

· End-to-end latency, throughput, QoS of a network switch for various types of data, control traffic

The VMM Performance Analyzer application provides a flexible and powerful framework to collect, analyze and visualize such performance statistics from their design and verification environments. It consists of a few base classes which allows you to define the performance metrics to be tracked, and collect run-time information for these performance metrics into tables in an SQL database. The data can be collected over multiple simulation runs. You then interact with the database with your favorite database analysis tool or spreadsheet. The SQL language itself offers simple yet powerful data query capabilities, which can be run either interactively or scripted for batch-mode. Alternatively, you can load the data into a spreadsheet and perform your analysis and visualization there.

At a conceptual level, you first identify the different atomic performance samples to be collected for analysis. These are referred to as “tenures”. For example, a memory transaction on a bus (from a specific master to a slave) is a tenure. The VMM-PA does the work of assigning an ID, collecting and tracking attributes such as the start time, end time, initiator and target IDs, and other associated information (suspended states, aborts, completions etc.,) as rows in a table. Each table corresponds to an instance of vmm_perf_analyzer object. You can (and probably will) have multiple tables (and thus multiple instances of the vmm_perf_analyzer object) in your simulation, dumping performance data into the database.

Here is a code snippet which illustrates the process of creating a vmm_perf_tenure tenure (a row in a table), a vmm_perf_analyzer table, and a vmm_sql_db_ascii (or vmm_sql_db_sqlite) database (a collection of tables), with some explanations following the code:

1. class my_env extends vmm_env;

2.    vmm_sql_db_sqlite db;                     //the database itself

3. vmm_perf_analyzer mem_perf_an; //One table in the database

4.    virtual function void build;

5.       super.build();

6. this.db = new(“perf_data.db”); //”perf_data.db” created on disk

7. this.mem_perf_an = new(“Mem_performance”, this.db);

8.    endfunction: build

9. endclass: my_env

10.

11. //Now, start a thread which will dump performance data to

12. // Mem_performance table in the database. Any event can be used to

13. // start or terminate tenures: it is left to the user control

14. initial begin

15. vmm_perf_tenure mem_perf_tenure = new();

16.    forever begin: mem_perf_thread

17.       this.mem_mon.notify.wait_for(mem_monitor::STARTED);

18. this.mem_perf_an.start_tenure(mem_perf_tenure);

19.       this.mem_mon.notify.wait_for(mem_monitor::ENDED);

20. this.mem_perf_an.end_tenure(mem_perf_tenure);

21.    end: mem_perf_thread

22. end

23. virtual task my_env::report;            //report is part of the environment class.. only the PA relevant code is presented

24. this.mem_perf_an.save_db(); //write any buffered data to disk

25. this.mem_perf_an.report();    //simple pre-defined performance report

26. endtask: report

· In lines 2 and 7, the SQLite database is created using vmm_sql_db_sqlite base class. You could create a different flavor of database, for instance, a plain text database, in which case, a list of SQL commands is created, which could then be replayed on your SQL data engine of choice. (See the Reference guide for more details). Typically you have one SQL database per test, however, you certainly can open multiple databases in the same test.

· In lines 3 and 8, a table in the database is created using vmm_perf_analyzer base class, which will help track statistics related to resource, in this case, a memory interface. Typically you will have multiple tables in a test, which correspond to tracking of statistics of multiple resources in the DUT or environment. This would correspond to multiple instances of the vmm_perf_analyzer base class.

· In lines 15, 18 and 20, one transaction item (“tenure”) is created and stored in the table. The transaction item is created by the vmm_perf_tenure base class. The tenure management methods such as start_tenure(), end_tenure(), suspend_tenure(), resume_tenure(), abort_tenure() allow you to express the state of the monitored tenure and reflect those in the performance tables. You can of course control when to execute these methods from your test, either timing control statements, events, callbacks, or whatever. Callbacks registered to the various vmm_xactor classes in your environment are the most scalable way to hook these into your environment, but it is your choice.

· In lines 24 and 25, data is flushed into the database at the end of simulation (in the vmm_env::report phase, to be more precise), and a basic / sample report is generated. It is important to note that you will in all likelihood be generating custom reports from the SQL database itself. That is explored further below.

Now that you have a code snippet showing you the process of monitoring statistics on shared resources in the design, you want to be able to write your custom queries, reports, and charts off the database. One could do this in a few ways:

1. Connect a spreadsheet to the database, and use the spreadsheet capabilities to generate statistics, charts etc. There was an earlier blog post on how to accomplish this: refer to “Analyzing results of the Performance Analyzer with Excel” (http://www.vmmcentral.org/vmartialarts/?p=23)

2. Use a SQL engine such as SQLite, MySQL, PostGreSQL, or any others to read in the SQL commands and generate custom query scripts which can then be used in batch mode. SQLite (http://www.sqlite.org), for example, has various plugins, such as Perl, TCL, C/C++ etc., so you can write scripts or queries in your favorite languages. There are several publicly available and commercial front ends you can use to read in the SQL data and perform your analyses (I’ve used SqliteSPY http://www.yunqa.de/delphi/doku.php/products/sqlitespy/index in the past). There are several quick-start tutorials for the SQL syntax available on the internet which should get you up and running with SQL in short order. To generate plots, one could use applications such as gnuplot, R, octave etc. It is probably more convenient to use spreadsheets to create graphs of various kinds.

In the next blog item related to the VMM Performance Analyzer, I will discuss some other aspects of the Performance Analyzer application (all of which is available by reading the User Guide: http://vmmcentral.com/resources.html#docs). I will also provide some examples of SQL code which demonstrate the analyses you can perform.

Posted in Debug, Optimization/Performance, Performance Analyzer | 8 Comments »

Using vmm_test Base Class

Posted by Fabian Delguste on 24th April 2009

Fabian Delguste, Snr CAE Manager, Synopsys

VMM 1.1 comes with a new base class called vmm_test that can be used for all tests. The main motivation for developing this base class was to enable a single compile-elaboration-simulate step for all tests rather than multiple ones. It is recommended to implement tests in a program thread as this provides a good way of encapsulating a testbench, and reduces races between design and testbench code. All test examples showed the tests implemented directly in program blocks. The drawback of this technique is that the user needs to recompile, elaborate and simulate each test individually. When dealing with large regressions consisting of 1000s of tests, multiple elaborations can waste a significant amount of time whereas tests using vmm_test only requires one elaboration. A given test can be selected at run-time using a command-line option. Switches like +ntb_random_seed can be used in conjunction with these tests.

To understand better how this base class works, let’s look at the example that ships with the VMM 1.1 release in the sv/examples/std_lib/vmm_test directory. This example shows how to constrain some ALU transactions. These transactions, modelled by the alu_data class , are randomly generated by an instance of a vmm_atomic_gen and passed to an ALU driver using a vmm_channel.

Before digging in the gory details of vmm_test, let’s see how tests are traditionally written:

1. class add_test_data extends alu_data;

2.   constraint cst_test {

3.      kind == ADD;

4.    }

5. endclass

6.

7. program alu_test();

8.    alu_env env;

9.    initial begin

10.     add_test_data tdata = new;

11.     env.build();

12.     env.gen.randomized_obj = tdata;

13.     env.run();

14.   end

15.endprogram

· In lines 1-4, the alu_data class is extended to the new class add_test_data which contains test-specific constraints. In this test, only ADD operations are carried modelled by this class.

· In line 7, a program block is used to instantiate the environment alu_env based on vmm_env.

· In line 10-13, the environment is built and the new transaction add_test_data is used as the factory instance for our vmm_atomic_gen transactor.

Of course, this test is very specific and a user clearly needs to create a similar program block with other constraints to fulfill the corresponding test plan. For instance, this test can be duplicated many times to send {MUL, SUB, DIV} ALU operations to the ALU driver. In this case, multiple program blocks are required, so are multiple elaborations and binaries to simulate these tests.

VMM 1.1 provides a way to include all test files in a single compilation. The previous test can now be written like this:

1. class add_test_data extends alu_data;

2.   constraint cst_test {

3.     kind == ADD;

4.   }

5. endclass

6.

7.  `vmm_test_begin(test_add, alu_env, “Addition”)

8.  env.build();

9.   begin

10.   add_test_data tdata = new;

11.   env.gen.randomized_obj = tdata;

12. end

13. env.run();

14. `vmm_test_end(test_add)

· In line 7, the vmm_test short hand macro `vmm_test_begin is used to declare the test name (test_add in our example), the name of the vmm_env where all transactors reside (alu_env in our example) and a label that is used to tag this particular test.

· In lines 8-13, users can build the environment, insert the factory instance and kick off the test.

· In line 14, the vmm_test short hand macro `vmm_test_end is used to terminate this test declaration.

Of course, other tests with variations in constraints can be written in the same way. Since the environment is exposed after the `vmm_test_begin short hand macro, it is possible to register callbacks, replace generators or do any other operation which is traditionally done in the VMM program block.

An important aspect of these tests is that whenever they are included, they become statically declared and visible to the environment.

Now let’s see how to include these tests in VMM program block:

1. `include “test_add.sv”

2. `include “test_sub.sv”

3. `include “test_mul.sv”

4. `include “test_ls.sv”

5. `include “test_rs.sv”

6.

7. program alu_test();

8.    alu_env env;

9.    initial begin

10.    vmm_test_registry registry = new;

11.    env = new(alu_drv_port, alu_mon_port);

12.    registry.run(env);

13.  end

14. endprogram

· In line 1-5, all tests are simply included

· In line 10, registry, which is an instance of vmm_test_registry,is constructed. This object contains all tests implemented using vmm_test_begin that have been previously included

· In line 12, registry can be run and a handle to the environment is passed as an argument to this class. This is how all vmm_tests can have access to the environment

Running a specific test is achieved by providing the test name on the command line, for example:

simv +vmm_test=test_add

simv +vmm_test=test_sub

Note that calling simv without the +vmm_test switch returns a FATAL error and lists all registered tests. This is a good way to document the available tests.

Posted in Creating tests, Optimization/Performance | 11 Comments »

Analyzing results of the Performance Analyzer with Excel

Posted by Janick Bergeron on 13th April 2009

jb_blog

Janick Bergeron, Synopsys Fellow

The VMM Performance Analyzer stores its performance-related data in a SQL database. SQL was chosen because it is an IEEEANSI/ISO standard with a multiplicity of implementation, from powerful enterprise systems like Oracle, to open source versions like MySQL to simple file-based like SQLite. SQL offers the power of a rich query and analysis language to generate the reports that are relevant to your application.

But not everyone knows SQL. You need an SQL-aware application to do fancier stuff. And guess what! Excel can import SQL data! And everyone knows Excel!

In this post, I will show how you can get VMM Performance Analyzer data from a SQLite database into an Excel spreadsheet. A similar mechanism can be used if you are using MySQL or any other SQL implementation that offers an ODBC (Open Database Conduit).

First, download and install the SQLite OBDC from http://www.ch-werner.de/sqliteodbc/sqliteodbc.exe on your PC and install.

Next, create a new OBDC Data source by opening the OBDC Data Source Administrator by navigating to Start -> Settings -> Control Panel -> Administrative Tools -> Data Sources (OBDC).

sqlite1

Click the “Add…” button to add a new data source. Scroll down to select the “SQLite3 OBDC Driver” entry.

sqlite2

C lick the “Finish” button. This will then open the OBDC data source configuration window. Specify a name for the data source and browse to the SQLite database file. A SQLite ODBC data source connects to a single database file. It is therefore a good idea to name this new data source according to the performance data it contains. Leave all other options to their default value.

sqlite3

Click “OK” to complete the creation of the new data source on your computer.

sqlite4

Repeat these steps for each SQLite database that will need to be analyzed using Microsoft Excel on your computer. Click “OK” when all data connections have been defined.

Start Excel with a blank workbook, then Select Data -> From Other Sources -> From Microsoft Query.

sqlite11
This will bring up a dialog box to select a data source. Select the data source corresponding to the desired SQLite database.

sqlite6

Click “OK”. The following error pops up. Don’t worry! The next step is to correct that error.

sqlite7

Click “OK” to close the error pop-up. This will bring up the Microsoft Query Wizard.

sqlite8

Click on “Options”. Modify any of the check boxes but make sure the “Tables” option is (or remains) checked. You must make a modification to at least one check box.

sqlite92

Click OK.  The Microsoft Query Wizard should now be populated with the list of tables found in the SQLite database file. From this point on, which table and which columns you select depend on the analysis you wish to perform.

For example, to perform an arbiter fairness analysis, you would select the “InitiatorID” and “active” columns of the “Arb” table.

sqlite10

Click “Next >” three times then return to Microsoft Excel by clicking “Finish”. The Import Data dialog box will appear.

sqlite13Click “OK” to insert the data as a table and voila! You now have a table populated from the data dynamically extracted from the SQLite database.

sqlite111

From there, a PivotChart can be used to display the average and maximum arbiter response time for each initiator. As you can see, the arbiter appears to be fair, given the small number of samples collected.

sqlite12

I hope you’ll find this step-by-step guide useful.

What cool thing have you done with the Performance Analyzer?

Posted in Debug, Optimization/Performance, Performance Analyzer | 5 Comments »