Verification Martial Arts: A Verification Methodology Blog

Archive for January, 2010

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

Posted by Srivatsa Vasudevan on 29th January 2010

In modern designs, one observes a fairly high degree of reuse in designs and verification environments. Cores and blocks are regularly reused from one design to another and increasing demands are being placed on quickly getting a functional chip out of the door to meet a customer need.

In such a world, schedule and functional chips are key to success.

At the block level, the design and verification engineers use a white box testing methodology to write tests that will completely test out the block under test. Usually in these environments, various blocks that interact with the design under test will usually be stubbed out or replaced with transactors as the case may be.

In an ideal world, the tests which were written once for the block level verification if reused 100% at the chip level would mean that the reuse was at 100%. That never usually happens.

Let’s look at the illustration between a verification engineer who’s busy building cores at the block level, comes in to talk to the verification lead of a chip he’s delivering to.

method_2

It is obvious that the requirements and goals for any tests at chip level are quite different. Most chip leads would first like to ensure that the core in question is indeed integrated at the chip level properly and the core does indeed talk to other cores properly.   The core ideally is a black box that would perform the functionality expected of it. The perspective on design verification maintained by the chip level verification engineer is usually vastly different from the block level perspective

Usually interconnect, clocking, reset, power-up/down, programming sequences, power domains, timing etc would be some examples  of things that come to mind when writing tests at the chip level. Once these major goals are met, one looks at other optimizations while attempting to verify the device.

That said, how does one now take the tests that are written for the block and re-use them at the top level especially if the tests are intended for different reasons? Is it even possible to minimize the effort in doing so? Don’t we need a mechanism to ensure that things that weren’t completely exercised at the block/subsystem level are tested at core level? What do we need for the handoff? How do the various verification class libraries now play a part in this picture?

The series herein explores how this is all done. A proper top/down methodology Coupled to a bottom’s up methodology can yield excellent results with minimal overlap.

Stay Tuned….

Posted in Reuse, Verification Planning & Management | 1 Comment »

Should you use VMM Callbacks or TLM Analysis ports?

Posted by S. Prashanth on 27th January 2010

With the addition of OSCI TLM2.0 features in VMM1.2, it is now becoming possible to use analysis ports for broadcasting transaction information from a transactor to any components such as scoreboard, functional coverage models, etc…

But, does it mean that VMM callbacks which are traditionally used for this purpose are not required anymore?

In short, the answer is no: both analysis port and callback have their own advantages. Before getting into more details, let me show you two simple examples I’ve created to encompass the analysis port and callback usage.

Communication through analysis port

Step1: In my transactor, I’ve simply instantiate a vmm_tlm_analysis_port instance [line 2]and invoke the analysis_port.write() method [line 8] to post the tr object to multiple subscribers.

1. class cpu_driver extends vmm_xactor;

2. vmm_tlm_analysis_port#(cpu_driver, cpu_trans) analysis_port;

3.    virtual function void build_ph();

analysis_port = new(this, “cpu_analysys_port”);

4.    endfunction

5.    virtual task main();

6.       cpu_trans tr;

7.       …

8. analysis_port.write(tr);

9.     endtask

10. endclass

Step2: Let’s now see how to model a subscriber such as a coverage model. In this model, I’ve simply instantiated a vmm_tlm_analysis_export instance [Line 3] and provided the implementation of the write method() [Line 4]. Once the transactor posts a transaction to the write method, the coverage model write_CPU gets called as well and receives this transaction, which can be sampled and covered.

1. class cntrlr_cov extends vmm_object;

2.    vmm_tlm_analysis_export #(cntrlr_cov, cpu_trans)

3.    cpu_export = new(this, “CpuAnExPort”);

4.    virtual function void write(int id=-1, cpu_trans tr);

5.        this.cpu_tr = tr;

6.        CG_CPU.sample();

7.   endfunction

8. endclass

Step3: The last important part is to bind my transactor and my subscribers. This is simply done by using the transactor tlm_bind() method. Of course we should be invoked for all subscribers.

1. class tb_env extends vmm_group;

2.    cpu_driver drv;

3.    cntrlr_cov cov;

4.    function void connect_ph();

5.       drv.analysis_port.tlm_bind(cov.cpu_export);

6.    endfunction

7. endclass

As you can see in these examples, analysis ports are easy to use. But they are not meant to allow subscribers to modify the transaction and are very much restricted to only one method with only one argument, i.e. write().

Communication through callback

Step1: I’ve created a generic callback that is nothing but a container that extends the vmm_xactor_callbacks base class with empty virtual methods [Line 1-3]. I have deliberately left these methods empty so that they can overridden for any particular usage such as a coverage model, scoreboard, etc.Next step is have my transactor calling this callback once the transaction is available [Line 10]

1. class cpu_driver_callbacks extends vmm_xactor_callbacks;

2.    virtual function void write(cpu_trans tr); endtask

3. endclass

4.

5. class cpu_driver extends vmm_xactor;

6.

7.    virtual task main();

8.       cpu_trans tr;

9.       …

10.      `vmm_callback(cpu_driver_callbacks, write(tr));

11.   endtask

12. endclass

Step2: Now, I can extend the previous class and provide the implementation the coverage model directly in the callback

1. cpu2cov_callback extends cpu_driver_callbacks;

2.    cntrlr_cov cov;

3.    virtual function void write(cpu_trans tr);

4.       cov.cpu_tr = tr;

5.       cov.CG_CPU.sample()

6.    endfunction

7. endclass

Step3: Once both transactor and subscribers are implemented, I can simply instantiate them in my implicitly phased environment [Line2-3]. Then, I can register the extended callback instance to the transactor by using append_callback() method Line[6]. Note that this registration happens in the connect phase.

1. class tb_env extends vmm_group;

2.    cpu_driver drv;

3.    cntrlr_cov cov;

4.    function void connect_ph();

5.       cpu2cov_callback cbk = new(cov);

6.       drv.append_callback(cbk);

7.    endfunction

8. endclass

As you can see in above examples, callbacks are also easy to use. As opposed to analysis port, their subscribers can possibly modify the transaction and can contain multiple methods with any kind of arguments.

Comparison

1. Analysis ports follow OSCI TLM2.0 standard.

2. Unlike callbacks, user do not need to create class with empty virtual methods, instead pre-defined method write() is used

3. Analysis ports can only broadcast one transaction as opposed to callbacks where you can define the method arguments.

4. Analysis port method write() is a function whereas callback class can model its empty virtual methods as tasks or void functions which gives you flexibility to control the transactor as well (like inserting delays, injecting error mechanism, etc)

5. The `vmm_tlm_analysis_export() macro must be used to create a new analysis façade when a class need to have more than one analysis export

6. Analysis ports can only be used by classes based on vmm_object. Callbacks can be used by any class.

In summary

- If a transactor needs to broadcast only one transaction, then analysis port can be used. If a transactor needs to send different kinds of information at different points, and provide some hooks for modeling variant functionality (like inserting delays, error injections, etc), then callbacks is the way to go. Both analysis port and callback can also be provided, publishing analysis port after callbacks.

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

Using factories in an implicitly-phased environment

Posted by Kiran Maiya on 25th January 2010

You might have read the neat article posted by Wei-Hua about the class factory in this forum. You may wonder what could be the best way to code this in an implicitly-phased environment. In this article I will be discussing how transactions based on vmm_data can be made factory enabled using VMM-1.2 based factory utility in an implicitly-phased environment. We will see how transaction objects can be replaced using these factory utilities. I’ve deliberately attempted not to introduce too many concepts such as macros to implement default methods. I will post an article on replacing transactors shortly.

As a test bed let us take a simple concept of generating raw pixels using generators. These raw pixels are modeled as vmm_data objects. This pixel object contains RGB color information in the variable clr which is a rand property. A discriminant member called type_e determines the color that this pixel object can take.


/* 1 */ `include “vmm.sv”
/* 2 */ program main ;
/* 3 */
/* 4 */ //// Class: Pixel ////
/* 5 */
/* 6 */ class Pixel extends vmm_data ;
/* 7 */ `vmm_typename(Pixel)
/* 8 */ static vmm_log log = new(“Pixel”, “class”) ;
/* 9 */ typedef enum {Red, Green, Blue, ANY} clr_type_e ;
/* 10 */
/* 11 */ typedef enum {
/* 12 */         R1, R2, R3, R4, R5,
/* 13 */         G1, G2, G3, G4, G5,
/* 14 */         B1, B2, B3, B4, B5
/* 15 */         } clrcode_e ;
/* 16 */ rand clrcode_e       clr ;
/* 17 */ clr_type_e type_e = ANY ;
/* 18 */
/* 19 */ constraint con
/* 20 */ {
/* 21 */ if(type_e == Red)
/* 22 */    clr inside {R1, R2, R3} ;
/* 23 */ if(type_e == Green)
/* 24 */    clr inside {G1, G2, G3} ;
/* 25 */ if(type_e == Blue)
/* 26 */    clr inside {B1, B2, B3} ;
/* 27 */ }

In addition to the constructor, the factory override methods needs to implement two predefined methods of vmm_data class, copy() and allocate(). These methods return the right type of the object that was enabled as factory. This can also be implemented using vmm_data short-hand macros, but for the purpose of clarity the full implementation is shown.

/* 28 */

/* 29 */ /// Function: new()

/* 30 */ function new(string name = “”, vmm_object parent = null) ;

/* 31 */ super.new(log) ;

/* 32 */ endfunction: new

/* 33 */

/* 34 */ /// Function: copy()

/* 35 */ virtual function vmm_data copy(vmm_data to = null) ;

/* 36 */ Pixel me ;

/* 37 */ if(to == null)

/* 38 */ me = new (this.get_object_name(), this.get_parent_object()) ;

/* 39 */ else if(!$cast(me, to))

/* 40 */ `vmm_fatal(log, “Can’t copy to non-Pixel instance”) ;

/* 41 */

/* 42 */ /// Copy members here

/* 43 */ me.clr = this.clr ;

/* 44 */ me.type_e = this.type_e ;

/* 45 */ return me ;

/* 46 */ endfunction: copy

/* 47 */

/* 48 */ /// Function: allocate()

/* 49 */ virtual function vmm_data allocate() ;

/* 50 */ Pixel me = new () ;

/* 51 */ return me ;

/* 52 */ endfunction: allocate

It is a good practice to have the psdisplay() method implemented. This will be handy for debugging the simulation and/or for a better report log.

/* 53 */

/* 54 */ /// psdisplay()

/* 55 */ virtual function string psdisplay(string prefix = “”);

/* 56 */ $sformat(psdisplay, “%s clr = %s, type_e = %s”,

/* 57 */ prefix, clr.name(),type_e.name()) ;

/* 58 */ endfunction: psdisplay

Finally make this Pixel class to be factory enabled by using the factory utility macro as shown below.

/* 59 */

/* 60 */ `vmm_class_factory(Pixel) // Introduce factory API

/* 61 */

/* 62 */ endclass: Pixel

This base pixel class can be customized to different variants by forcing the discriminant type as shown below. Objects of these types can be later used to override the factory. These class extensions should also be enabled as factories using the factory utilities as shown below.

/* 63 */

/* 64 */ //// GreenPixel ////

/* 65 */

/* 66 */ class GreenPixel extends Pixel ;

/* 67 */ /// new()

/* 68 */ function new(string name = “”, vmm_object parent = null) ;

/* 69 */ super.new(name, parent) ;

/* 70 */ type_e = Green ;

/* 71 */ endfunction: new

/* 72 */

/* 73 */ constraint con_GreenPixel

/* 74 */ {

/* 75 */ type_e == Green ;

/* 76 */ }

/* 77 */

/* 78 */ /// copy()

/* 79 */ virtual function vmm_data copy(vmm_data to = null) ;

/* 80 */ GreenPixel me ;

/* 81 */

/* 82 */ if(to == null)

/* 83 */ me = new ;

/* 84 */ else if(!$cast(me, to))

/* 85 */ `vmm_fatal(log, “Can’t copy to non-Green Pixel instance”) ;

/* 86 */

/* 87 */ /// Copy members here

/* 88 */ return super.copy(me) ;

/* 89 */ endfunction: copy

/* 90 */

/* 91 */ /// allocate()

/* 92 */ virtual function vmm_data allocate() ;

/* 93 */ GreenPixel me = new() ;

/* 94 */ return me ;

/* 95 */ endfunction: allocate

/* 96 */

/* 97 */ `vmm_class_factory(GreenPixel)

/* 98 */

/* 99 */ endclass: GreenPixel

In real environment, pixels streams are generated using a custom pixel generators based of vmm_xactors or use one of the pre-defined generators. I will not discuss the generators in this article for the sake of simplicity. However a simplistic approach is taken here to show the generation in an implicit environment by utilizing the run_ph() of a timeline class. The driver logic is mimicked in the run_ph() method of PixelTimeline which is an extension of vmm_timeline. This method it is implicitly triggered during the run phase of the timeline. Transaction object tr which is a Pixel object, is instanced at run-time using the create_instance() method rather than the traditional constructor new(). Although the object instantiation via the factory can also be carried out in the constructor of the transactor or the build phase of the generator (timeline here), this has a disadvantage. With the create_instance() called inside pre-run phase of the generator, it allows only one opportunity for effectively replacing the factory instance before it is created. By calling the create_instance() factory method repeatedly at run-time, it allows the factory to override dynamically as required by testcases.

/* 100 */

/* 101 */ //// PixelTimeline ////

/* 102 */

/* 103 */ class PixelTimeline extends vmm_timeline ;

/* 104 */ `vmm_typename(PixelTimeline)

/* 105 */ Pixel tr ;

/* 106 */ bit done = 0 ;

/* 107 */

/* 108 */ /// Function: new()

/* 109 */

/* 110 */ function new(string name = “”, string inst = “”, vmm_object parent = null) ;

/* 111 */ super.new(get_typename(), name) ;

/* 112 */ //tr = Pixel::create_instance(this, “Fact”) ; // Not advisable

/* 113 */ endfunction : new

/* 114 */

/* 115 */ virtual function void build_ph() ;

/* 116 */ // Not recommended to call create_instace

/* 117 */ endfunction: build_ph

/* 118 */

/* 119 */ /// Task: myrun

/* 120 */

/* 121 */ task run_ph() ;

/* 122 */ tr = Pixel::create_instance(this, “Fact”) ; // This is recommended

/* 123 */ repeat(5)

/* 124 */ begin

/* 125 */ tr.randomize() ;

/* 126 */ tr.display(“::”) ;

/* 127 */ end

/* 128 */ $display(“”);

/* 129 */ endtask: run_ph

/* 130 */

/* 131 */ endclass: PixelTimeline

Having the environment ready, let us see how the objects can be replaced using the factory utility. A typical environment will be created by extending vmm_group. Environment creation is out of scope of this topic and will not be discussed here; instead a simple program block is shown creating the relevant blocks.

/* 132 */

/* 133 */ //// Program

/* 134 */ initial

/* 135 */ begin

/* 136 */ PixelTimeline tl ;

/* 137 */ Pixel tr = new ;

/* 138 */ GreenPixel gpxl ;

/* 139 */ vmm_log log = new(“A”, “B”) ;

/* 140 */

/* 141 */ tl = new (“tl”, “Main”, null) ;

Following code shows a free running generator with no customization on the pixels.

/* 142 */

/* 143 */ /// Free running simulation, any color

/* 144 */ tl.run_phase() ;

The above code will generate pixels of all colors and the output will be as follows

:: clr = R3 :: type_e = ANY

:: clr = G3 :: type_e = ANY

:: clr = B3 :: type_e = ANY

:: clr = G3 :: type_e = ANY

:: clr = B5 :: type_e = ANY

Factory replacement can be done in two ways,

  1. override by transaction copy using a reference object or
  2. override by transaction type

While overriding by transaction copy the factory method override_with_copy() should be used. This will call the copy() method to return the right type to be overridden. Here one needs to have a reference copy that will be passed as an argument while replacing factory. In the following code snippet it is shown that a local transaction copy is pixilated to Red using the type discriminant. This copy is used to override the transaction object as shown below.

/* 145 */

/* 146 */ /// Override with a transaction object copy of type Red

/* 147 */ tr.type_e = Pixel::Red ;

/* 148 */ Pixel::override_with_copy(“@%*”, tr, tr.log, `__FILE__, `__LINE__) ;

/* 149 */ tl.reset_to_phase(“start_of_sim”) ;

/* 150 */ tl.run_phase() ;

/* 151 */

This part of the code will generate the red pixels and the output will be as follows

:: clr = R1 :: type_e = Red

:: clr = R3 :: type_e = Red

:: clr = R2 :: type_e = Red

:: clr = R3 :: type_e = Red

:: clr = R1 :: type_e = Red

While overriding by transaction type the factory method override_with_new() should be used. This will call the allocate() method to return the right type. To specify the type of object to be manufactured one can use the predefined method this_type() which returns the type of the specified object. Here in the following example GreenPixel type is used to override the factory.

/* 152 */

/* 153 */ /// Override with new type-Green

/* 154 */ Pixel::override_with_new(“@%*”, GreenPixel::this_type,

/* 155 */ log, `__FILE__, `__LINE__) ; // Strongly typed

/* 156 */ tl.reset_to_phase(“configure”) ;

/* 157 */ tl.run_phase() ;

/* 158 */

/* 159 */ #100 $finish ;

/* 160 */ end

/* 161 */ endprogram: main

/* 162 */

The above code will generate the green pixels and the output will be as follows

:: clr = G2 :: type_e = Green

:: clr = G2 :: type_e = Green

:: clr = G1 :: type_e = Green

:: clr = G3 :: type_e = Green

:: clr = G3 :: type_e = Green

It is worthwhile to notice that the factory override methods are strongly typed. There isn’t any room for accidental type mismatch. Tool will automatically error out during compilation. For example, the following code is illegal and the tool throws a compilation error.

Data::override_with_new(“@%*”, GreenPixel::this_type,

log, `__FILE__, `__LINE__) ; // Strongly typed

Where Data is not an extension of type Pixel.

Hope you all will have a colorful experience with VMM-1.2 factory utility (no pun intended).

Posted in Phasing, Reuse, VMM 1.2, VMM infrastructure | No Comments »

Proxy Coverage

Posted by Andrew Piziali on 22nd January 2010

Andy's Picture_1

Andrew Piziali, Independent Consultant

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

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

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

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

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

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

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

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

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

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

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

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

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

Transactor Interfaces [VMM 1.2 style]

Posted by Vidyashankar Ramaswamy on 18th January 2010

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

image

Analysis Port

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

Analysis port in the producer (Initiator) model

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

File: vip_slave.sv

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

class vip_slave extends vmm_xactor;
`vmm_typename(vip_slave)
// Variables declaration
. . .
// Analysis port
vmm_tlm_analysis_port#(vip_slave, vip_trans) analysis_port;
// TLM Blocking port
vmm_tlm_b_transport_port#(vip_slave, vip_trans) b_trans_resp_port;

// Component Phases
. . .
extern virtual function void build_ph();
extern virtual protected task main();
. . .
endclass: vip_slave
. . .
/////////////// Build Phase /////////////////
function void vip_slave::build_ph();
. . .
// Construct the analysis port for Observers
analysis_port = new(this, “analysis_port”);
// Construct the transport port for response modification
b_trans_resp_port = new(this, “b_trans_resp_port”);
. . .
endfunction: build_ph
. . .

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

Analysis export port in the target (consumer) model

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

File: observer.sv

class observer extends vmm_object;
`vmm_typename(observer)
vmm_tlm_analysis_export#(observer, vip_trans) obsrv ;
vmm_log log = new(“log”, this.get_object_hiername());

virtual function void write (int id = -1, vip_trans tr);
`vmm_note(log, ” … From Observer: Rcvd Transaction … “);
tr.display(“”);
endfunction: write

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

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

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

endclass: observer

Transport Interface

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

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

Response modifier Transactor

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

File: resp_modifier.sv
class resp_modifier extends vmm_xactor;
`vmm_typename(resp_modifier)
vmm_tlm_b_transport_export#(resp_modifier, vip_trans) b_trans_resp_export ;

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

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

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

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

endclass: resp_modifier

Connecting it all together

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

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

// VIP Instantiation
vip_slave slv;
. . .

// TLM Port Declaration
observer        obsrv_vip_trans;
resp_modifier trans_mod_xactor;
. . .

// Component Phases
extern virtual function void build_ph();
extern virtual function void connect_ph();
. . .
endclass: tb_env
. . .

////////// Build Phase ////////////////
function void tb_env::build_ph();
. . .
this.slv = vip_slave::create_instance(this, “slv”, `__FILE__, `__LINE__);

//Create observer component
obsrv_vip_trans = new(this, “TRANS_OBSVR”);
// Create the response modifier transactor
trans_mod_xactor = new(this, “RESP_MODIFIER”);
. . .
endfunction: build_ph

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

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

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

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

VMM 1.2 – The Movie

Posted by John Aynsley on 14th January 2010

JohnAynsley

John Aynsley, CTO, Doulos

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

Posted in Phasing, Structural Components, Transaction Level Modeling (TLM), VMM, VMM 1.2 | No Comments »

Transactors and Virtual Interface

Posted by Vidyashankar Ramaswamy on 8th January 2010

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

image

Step1. Developing the port object

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

File: master_port.sv
class master_port extends vmm_object;

virtual vip_if.master mstr_if;

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

endclass: master_port

Step2. Configuring the Virtual Interface

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

File: master.sv

//////////////////// Master Model //////////////
class master extends vmm_xactor;
`vmm_typename(master)
// Variables declaration
virtual vip_if.master mstr_if;

////////////// Connect_vitf method ////////////////
function void connect_vitf(vip_if.master mstr_if);
begin
if (mstr_if != null)
this.mstr_if = mstr_if;
else
`vmm_fatal(log, “Virtual port [Master] is not available”);
end
endfunction: connect_vitf

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

end
endfunction: connect_ph

endclass: master

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

File: vip_env.sv

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

`include “vmm.sv”

class vip_env extends vmm_groups;
`vmm_typename(vip_env)

// Variables declaration
master_port mstr_p0;

////////////// Build Phase ////////////////
function void build_ph();
begin

mstr_p0 = new(“master_port”, tb_top.master_if_p0);

end
endfunction: build_ph

////////////// Connect Phase ////////////////
function void connect_ph();
begin
bit is_set;

// Set the master port interface
vmm_opts::set_object(“VIP_MSTR:vip_mstr_port“, mstr_p0, env);

end
endfunction

endclass: vip_env

Explicit phasing environment:

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

File: vip_env.sv

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

class vip_env extends vmm_env;
`vmm_typename(vip_env)

// VIP’s used …
master mstr_drvr;

////////////// Build Phase ////////////////
function
void build();
begin
super.build();

// Set the master port interface
this.mstr_drvr.connect_vitf(tb_top.master_if_p0);

end
endfunction:
build

endclass: vip_env

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

Posted in Configuration, Structural Components, VMM 1.2 | 4 Comments »