Specman Verification
Another good blog with a bad name 
Thursday, July 2, 2009, 08:48 AM - Hardcore verification
Ray Salemi, is a colleague from Mentor, and the author of the highly recommended www.fpgasimulation.com, which as far as I could tell, has only little to do with simulation, not more to do with FPGA than with ASIC, and in any case much more to do with OVM and verification than with both. As the author of www.specman-verification.com which for the last year and a half had almost nothing to do with Specman I would like to welcome Ray to the time-honoured club of good blogs with bad names.

Ray’s blog shares more than just a bad name with this blog. He also shares a focus on clearly explained, well illustrated technical tips, accompanied by running examples. So, I guess there’s a good chance that if you liked mine, you’ll like his. Go give it a look.


[ add comment ]   |  permalink  |  related link  |   ( 3.5 / 11 )
Quick intro to OVM ports, exports and imps 
Monday, June 29, 2009, 07:04 PM - Hardcore verification
I can’t really tell why, but there’s something about connecting things in OVM, which many newbies, and also veteran users of OVM, find very confusing. Maybe it is because of the multitude of classes and interfaces that exists in OVM for connecting things, maybe it is because all of this part in OVM is implemented with SV templates, which are not the simplest thing in the world, and definitely not the most readable one, or maybe it is because of words like “export”, which don’t really make a lot of sense outside the world of international commerce. Long long time ago, I promised to provide a very simple example that will help beginners put some order into all of this, and I finally got around to do it.

The main thing you have to remember is that every connection in OVM begins with a port and ends with an imp(lementation). On the way between those two ends you might create a chain of intermediate ports and exports of any size you want. These are normally used to go up and down the hierarchy of components. Translated into the language of regular expressions this looks like:

Port[Port]*[Export]*Imp

The most elementary connection is therefore a port that is connected to an imp. This is shown below:
package vip_pkg;

import ovm_pkg::*;
`include "ovm_macros.svh"

class tr extends ovm_object;
   int serial_num;
   
   `ovm_object_utils_begin(tr)
   `ovm_field_int(serial_num, OVM_ALL_ON);
   `ovm_object_utils_end

   function new(string name ="");
      super.new(name);
   endfunction
endclass // tr

// a : the port at the beginning
// b : the implementation at the end

// The port at the beginning
class a extends ovm_component;
   `ovm_component_utils(a)

   ovm_blocking_put_port#(tr) put_port;

   function new(string name, ovm_component parent);
      super.new(name,parent);
      put_port = new("put_port", this);
   endfunction

   task run();
      int serial_num;
      forever begin
         tr tr_i;
                  
         $cast(tr_i, create_object("tr", "tr_i"));
         tr_i.serial_num = serial_num++;      
         put_port.put(tr_i);
         #1;
      end
   endtask
endclass 

// The implementation at the end
class b extends ovm_component;
   `ovm_component_utils(b)
   
   ovm_blocking_put_imp#(tr,b) put_imp;

   function new(string name, ovm_component parent);
      super.new(name,parent);
      put_imp = new("put_imp",this);
   endfunction

   task put(tr tr_i);
      tr_i.print();    
   endtask
endclass

// The env connects the port to the export
class env extends ovm_env;
   `ovm_component_utils(env)

   a a_i;
   b b_i;

   function new(string name, ovm_component parent);
      super.new(name,parent);
   endfunction

   function void build();
      super.build();
      $cast(a_i, create_component("a", "a_i"));
      $cast(b_i, create_component("b", "b_i"));      
   endfunction // void

   function void connect();
      super.connect();
      a_i.put_port.connect(b_i.put_imp);
   endfunction
endclass // env

endpackage // vip

package tests;

import ovm_pkg::*;
import vip_pkg::*;
`include "ovm_macros.svh"

class test extends ovm_test;
   `ovm_component_utils(test);

   env env_i;
   
   function new(string name = "test", ovm_component parent=null);
      super.new(name,parent);
   endfunction // new

   function void build();      
      super.build();
      $cast(env_i, create_component("env", "env_i"));
   endfunction
endclass
endpackage // z

module top();
   import ovm_pkg::*;
   import vip_pkg::*;
   import tests::*;
   

   initial
     run_test();
endmodule


Complicating things just a little bit, we now add an intermediate port and export in the middle, which are used, correspondingly, to go up and down the component hierarchy.

package vip_pkg;

import ovm_pkg::*;
`include "ovm_macros.svh"

class tr extends ovm_object;
   int serial_num;
   
   `ovm_object_utils_begin(tr)
   `ovm_field_int(serial_num, OVM_ALL_ON);
   `ovm_object_utils_end

   function new(string name ="");
      super.new(name);
   endfunction
endclass // tr

// a : the port at the beginning
// up : a connecting port for going up the hierarchy
// down : a connecting export for going down the hierarchy
// b : the implementation at the end

// The port at the beginning
class a extends ovm_component;
   `ovm_component_utils(a)

   ovm_blocking_put_port#(tr) put_port;

   function new(string name, ovm_component parent);
      super.new(name,parent);
      put_port = new("put_port", this);
   endfunction

   task run();
      int serial_num;
      forever begin
         tr tr_i;         
         
         $cast(tr_i, create_object("tr", "tr_i"));
         tr_i.serial_num = serial_num++;      
         put_port.put(tr_i);
         #1;
      end
   endtask
endclass 

// The implementation at the end
class b extends ovm_component;
   `ovm_component_utils(b)
   
   ovm_blocking_put_imp#(tr,b) put_imp;

   function new(string name, ovm_component parent);
      super.new(name,parent);
      put_imp = new("put_imp",this);
   endfunction

   task put(tr tr_i);
      tr_i.print();    
   endtask
endclass

// a connecting port for going up the hierarchy
class up extends ovm_component;
   `ovm_component_utils(up)

   a a_i;
     
   ovm_blocking_put_port#(tr) put_port;

   function new(string name, ovm_component parent);
      super.new(name,parent);
      put_port = new("put_port", this);
   endfunction

   function void build();
      super.build();
      $cast(a_i, create_component("a", "a_i"));
   endfunction // void

   function void connect();
      super.connect();
      a_i.put_port.connect(put_port);
   endfunction // void
endclass // up

// a connecting export for going down the hierarchy
class down extends ovm_component;
   `ovm_component_utils(down)

   b b_i;
     
   ovm_blocking_put_export#(tr) put_export;

   function new(string name, ovm_component parent);
      super.new(name,parent);
      put_export = new("put_export", this);
   endfunction

   function void build();
      super.build();
      $cast(b_i, create_component("b", "b_i"));
   endfunction // void

   function void connect();
      super.connect();
      put_export.connect(b_i.put_imp);
   endfunction // void
endclass // b

// The env connects the port to the export
class env extends ovm_env;
   `ovm_component_utils(env)

   up up_i;
   down down_i;

   function new(string name, ovm_component parent);
      super.new(name,parent);
   endfunction

   function void build();
      super.build();
      $cast(up_i, create_component("up", "up_i"));
      $cast(down_i, create_component("down", "down_i"));      
   endfunction // void

   function void connect();
      super.connect();
      up_i.put_port.connect(down_i.put_export);
   endfunction
endclass // env

endpackage // vip

package tests;

import ovm_pkg::*;
import vip_pkg::*;
`include "ovm_macros.svh"

class test extends ovm_test;
   `ovm_component_utils(test);

   env env_i;
   
   function new(string name = "test", ovm_component parent=null);
      super.new(name,parent);
   endfunction // new

   function void build();      
      super.build();
      $cast(env_i, create_component("env", "env_i"));
   endfunction
endclass
endpackage // z

module top();
   import ovm_pkg::*;
   import vip_pkg::*;
   import tests::*;
   

   initial
     run_test();
endmodule


This shows the standard OVM way of connecting things: Assume an environment has a component a.b.c.d which has a port and a component x.y.z which has a corresponding implementation. According to the OVM, you are not supposed to connect a.b.c.d directly to x.y.z, although this might save you plenty of lines of code. Instead you should float d’s port all the way up to a, by using intermediate ports, and you should float z’s implementation all the way up to a x. Then you should use the environment connect method, to connect a to x. What will happen if you connect a.b.c.d directly to x.y.z? Well nothing really, everything will work just as before, but you’ll not be following the OVM guidelines to the latter anymore. It is your call to tell if it’s a reasonable price to pay (IMHO, in some cases, it is). The example below shows such direct connection:

package vip_pkg;

import ovm_pkg::*;
`include "ovm_macros.svh"

class tr extends ovm_object;
   int serial_num;
   
   `ovm_object_utils_begin(tr)
   `ovm_field_int(serial_num, OVM_ALL_ON);
   `ovm_object_utils_end

   function new(string name ="");
      super.new(name);
   endfunction
endclass // tr

// a : the port at the beginning
// b : the implementation at the end
// up and down are skipped in this example,
// and a is connected directly to b

// The port at the beginning
class a extends ovm_component;
   `ovm_component_utils(a)

   ovm_blocking_put_port#(tr) put_port;

   function new(string name, ovm_component parent);
      super.new(name,parent);
      put_port = new("put_port", this);
   endfunction

   task run();
      int serial_num;
      forever begin
         tr tr_i;         
         
         $cast(tr_i, create_object("tr", "tr_i"));
         tr_i.serial_num = serial_num++;      
         put_port.put(tr_i);
         #1;
      end
   endtask
endclass 

// The implementation at the end
class b extends ovm_component;
   `ovm_component_utils(b)
   
   ovm_blocking_put_imp#(tr,b) put_imp;

   function new(string name, ovm_component parent);
      super.new(name,parent);
      put_imp = new("put_imp",this);
   endfunction

   task put(tr tr_i);
      tr_i.print();    
   endtask
endclass

class up extends ovm_component;
   `ovm_component_utils(up)

   a a_i;
     
   function new(string name, ovm_component parent);
      super.new(name,parent);
   endfunction

   function void build();
      super.build();
      $cast(a_i, create_component("a", "a_i"));
   endfunction // void
endclass // up

class down extends ovm_component;
   `ovm_component_utils(down)

   b b_i;

   function new(string name, ovm_component parent);
      super.new(name,parent);
   endfunction

   function void build();
      super.build();
      $cast(b_i, create_component("b", "b_i"));
   endfunction // void
endclass // b

class env extends ovm_env;
   `ovm_component_utils(env)

   up up_i;
   down down_i;

   function new(string name, ovm_component parent);
      super.new(name,parent);
   endfunction

   function void build();
      super.build();
      $cast(up_i, create_component("up", "up_i"));
      $cast(down_i, create_component("down", "down_i"));      
   endfunction // void

   function void connect();
      super.connect();
      // note that we are connecting elements that are several
      // levels down the hierarchy now.
      up_i.a_i.put_port.connect(down_i.b_i.put_imp);
   endfunction
endclass // env

endpackage // vip

package tests;

import ovm_pkg::*;
import vip_pkg::*;
`include "ovm_macros.svh"

class test extends ovm_test;
   `ovm_component_utils(test);

   env env_i;
   
   function new(string name = "test", ovm_component parent=null);
      super.new(name,parent);
   endfunction // new

   function void build();      
      super.build();
      $cast(env_i, create_component("env", "env_i"));
   endfunction
endclass
endpackage // z

module top();
   import ovm_pkg::*;
   import vip_pkg::*;
   import tests::*;
   

   initial
     run_test();
endmodule


It is also interesting to note that although OVM recommends that you use ports for going up, and exports for going down, there’s no real functional difference between ports and exports, and wherever you use a port you could use an export, and vice versa. To convince the sceptics, here is the code that defines the blocking put port and export in the OVM:

class ovm_blocking_put_port #(type T=int)
  extends ovm_port_base #(tlm_if_base #(T,T));
  `OVM_PORT_COMMON(`TLM_BLOCKING_PUT_MASK,"ovm_blocking_put_port")
  `BLOCKING_PUT_IMP (this.m_if, T, t)
endclass 

class ovm_blocking_put_export #(type T=int)
  extends ovm_port_base #(tlm_if_base #(T,T));
  `OVM_EXPORT_COMMON(`TLM_BLOCKING_PUT_MASK,"ovm_blocking_put_export")
  `BLOCKING_PUT_IMP (this.m_if, T, t)
endclass


As you can see, except for the name string there’s no difference at all. The only reason for drawing a distinction between ports and exports is so that you can immediately know which side of the connection provides the implementation, and which side connects to it.



[ add comment ]   |  permalink  |   ( 2.8 / 20 )
Avoiding races between driver and monitor 
Thursday, December 25, 2008, 12:58 PM - Hardcore verification
According to the eRM and OVM monitors and drivers should always be completely separate. This approach was adopted mainly in order to facilitate reuse of block level agents in a top level testbench: at block level both driver and monitor are used, while at top level only the monitor is used. If the code for both is mixed, then you might run into some nasty surprises when you try to rerun your code at top level with the driver disabled. For example, you might suddenly discover that your monitor is not working properly when it is not you, but some HDL block, that is driving the signals.

Having separate monitor and driver means that they will also run in independent threads of execution, and this means races between the two might occur. A race is one of these bugs that can make you eat your nails to the bone and pull out all your body hair – it normally appears all of a sudden, just at the very moment when you think your code is stable and working great. The extremely simple example below shows an example of a race, and the relatively minor code modification that can make it show its ugly face:


// The driver drives the data signal at each positive clock edge
interface driver_if(input rst, input clk, output reg [7:0] data);

always @(posedge clk or negedge rst)
  if (!rst)
    data = 0;
  else
    data = data + 1;
   
endinterface

// The monitor samples the data signal at each positive clock edge
interface monitor_if(input clk, input reg [7:0] data);

always @(posedge clk)
  $display("data value from monitor at time %d is %d", $time(), data);

endinterface

// testbench generates clock and reset and does the connections
module tb;

   reg rst;   
   reg clk = 0;
   reg [7:0] data;

   initial
     begin
        rst = 0;
        #10;
        rst = 1;
     end
   
   initial
     forever
       begin
          #20;
          clk = !clk;
       end

   monitor_if monitor_if_i(clk,data);   
   driver_if driver_if_i(rst,clk,data);   

endmodule


output:


# data value from monitor at time 20 is 0
# data value from monitor at time 60 is 1
# data value from monitor at time 100 is 2
...


If you run the code above with Questa you will see that it always runs the monitor code first (simply place a breakpoint on both driver and monitor). This, however, is just a coincidence and not anything you can rely on. If you modify the code from:

<   monitor_if monitor_if_i(clk,data);   
<   driver_if driver_if_i(rst,clk,data); 


to:

>   driver_if driver_if_i(rst,clk,data);
>   monitor_if monitor_if_i(clk,data);  


Questa will run the driver code first, and suddenly you’ll see different results:


# data value from monitor at time 20 is 1
# data value from monitor at time 60 is 2
# data value from monitor at time 100 is 3


This can also happen because you have used a different compiler – the SV standard or the plain Verilog standard don’t say anything about the order in which pending threads should be run.

Solving such races in SV is relatively easy*. As any Verilog designer knows, in the case above all you have to do to solve the race is modify the assignments from “blocking” to “non blocking”. This will postpone the assignments to the end of the delta, so the monitor will never see the new value assigned by the driver, even if its thread is the last to run. Here's the original code with the required modification:


interface driver_if(input rst, input clk, output reg [7:0] data);

always @(posedge clk or negedge rst)
  if (!rst)
    data <= 0; // modified to non-blocking
  else
    data <= data + 1; // modified to non-blocking

   
endinterface

interface monitor_if(input clk, input reg [7:0] data);

always @(posedge clk)
  $display("data value from monitor at time %d is %d", $time(), data);

endinterface

module tb;

   reg rst;   
   reg clk = 0;
   reg [7:0] data;

   initial
     begin
        rst = 0;
        #10;
        rst = 1;
     end
   
   initial
     forever
       begin
          #20;
          clk = !clk;
       end

   monitor_if monitor_if_i(clk,data);   
   driver_if driver_if_i(rst,clk,data);   

endmodule



output (will be the same independent of instansiation order):


# data value from monitor at time 20 is 0
# data value from monitor at time 60 is 1
# data value from monitor at time 100 is 2
...


So, the bottom line of this entry is: When you’re writing a driver in SV always use “non blocking assignments”. And in a more general form: “always ask yourself how designers solve this problem and try to do the same”.

*In e this might be a little bit more complex because all e assignments are “blocking”, i.e. they are executed immediately. As soon as I have access to Specman license again I’ll try to put up an e example as well.

Update: Yaron Ilani has just reminded me that the same affect is achieved in e by setting the port attribute "verilog_delta_delay" to TRUE. This will make the port always sample the previous value instead of the current one, which means that any value driven by the driver in this delta, will not be visible to the monitor.

keep unit.port.verilog_delta_delay() == TRUE;

Thanks!


[ 2 comments ] ( 37 views )   |  permalink  |   ( 3 / 1440 )

Next