VMM Central Verification Martial Arts
  • Recent Posts

  • About

    Janick Bergeron


    I joined Synopsys in 2003 where I am now a Fellow, responsible for the development of functional verification techniques and methodologies. I’m the author of the Verification Methodology Manual for SystemVerilog and of the Writing Testbenches book series. In addition, I’m the founder and moderator of the Verification Guild. I have focused on verification since 1992.

    Fabian Delguste


    I joined Synopsys in 2003. I am currently responsible for deploying verification tools and methodologies to key accounts in Europe. Prior to Synopsys, I co-founded Qualis Europe. Over the past 15 years, I have held various positions in software development and SoC design.

    Badri Gopalan


    I am a Principal Engineer in the Verification Group at Synopsys. Since 1997, I have been an RTL Designer; Verification Lead; and Hardware Design, Verification and Modeling Manager at several chip and system design companies. I am interested in building and creatively using EDA tools, technology, and methodology to help Verification Engineers' work more productively and efficiently.

    Weihua Han


    I joined Synopsys in 1999 as a System-Level Design Specialist and initially focused on telecommunication system design with C/SystemC at the algorithm and transaction levels. Since 2002 I began focusing on ASIC/SoC functional verification with SystemVerilog, SystemC and VMM. Prior to Synopsys, I led the Basestation baseband R&D team for the Chinese National 863 CDMA project.

    Shankar Hemmady


    At Synopsys, I am responsible for verification and debug automation solutions that include planning, management, methodology and power-aware verification. Over the past 18 years, I have designed, verified and tested, or managed the functional closure of over 25 commercial chips at 12 companies, including AMD, Cirrus Logic, Fujitsu, Hewlett Packard, Intel, S3, Sun and Xerox.

    Adiel Khan


    I have been a Verification Specialist in the FPGA and ASIC industry since 2000. I have worked on several interesting projects ranging from packet-based FPGA verification through micro-controller devices, to complex multiple CPU SoC architecture verification. At Synopsys, I work with key customers, support them with their verification strategies and help develop and expand new and existing verification methodologies.

    Mehdi Mohtashemi


    I have worked in the electronics industry for over 20 years. Much of my work was on electronic circuits, system design, and validation spanning ASIC design and implementation, computer system design, verification and software development. At Synopsys, I now focus on test-bench and verification automation tools and methodologies. Prior to Synopsys, I worked at AMD, Western Digital, Sun Microsystems and Mylex.

    Dr. Nasib Naser


    I am a Senior Staff Corporate Applications Engineer in the Verification Group at Synopsys. For the past ten years I have been working in system-on-chip design and verification, embedded systems design, computer architecture and real-time operating systems. I enjoy working closely with several semiconductor companies and providing my expertise in System-Level Design technologies and Methodologies, initially with tools based on C/C++ and, more recently, on Open Source SystemC and SystemVerilog.

    Amit Sharma


    I joined Synopsys over eight years ago. Initially, I worked extensively on architecting and implementing testbenches in Vera, and later, in SystemVerilog. Over time, I honed my engineering skills and gained a business background. Currently, I manage several key customers with whom I share my experience in testbenches, verification methodologies and simulation technologies.

    Alexander Wakefield


    I am a Principal Corporate Applications Engineer at Synopsys. During the last 10 years at Synopsys I have worked as a verification consultant on various processor and SoC projects and specialized in verification with the VCS functional verification solution. My main focus is on constrained-random validation and testbench methodology. I work with several key semiconductor companies and have published several papers.

A generic functional coverage solution based on vmm_notify

Posted by Wei-Hua Han on June 15th, 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 MODs, RAL, SV, VMM | 2 Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Protocol Layering Using Transactors

Posted by Janick Bergeron on June 9th, 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 Uncategorized | 1 Comment »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

How VMM can help controlling transactors easily?

Posted by Fabian Delguste on May 29th, 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 Uncategorized | No Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

VMM VIP’s on multiple buses

Posted by Adiel Khan on May 27th, 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, MODs, RAL, SV, Uncategorized, VMM | 4 Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

Did you notice vmm_notify?

Posted by Janick Bergeron on May 20th, 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 Uncategorized | 2 Comments »

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...