program interface module class 區別

來自於大牛 Specman Verification 

SV is a fact of life and is here to stay. You can feel happy or sad about it, think that it’s a big step to humanity, or a very small step towards standardization, either way, if you want to make money in today’s verification world, you will not be able to avoid it for long (that is, unless you become a manager within the next 6-12 months). Even if you have a website that you once stupidly named www.specman-verification.com, you’ll have to understand at some point, that unless you have a decent SV tutorial online, in one or two years you’re passé.

So, this is the first step in a thousand mile way. More parts will be added in the future. When exactly? That’s up to god and/or my boss.

SV building blocks

Working with SV often feels like a trainer trying to make the most out of a team whose members are suffering from various disabilities: one is missing a leg, another is blind, a third can’t talk. Somehow, you must find the right position for each player, one where his gain will compensate for the pain. Obviously, some players, such as the one who has no legs, no hands and a very big nose, will have to warm up the bench.

You’ll often find yourself facing difficult choices: should you let the blind guy with the powerful right leg, or the deaf guy with the weak left leg shoot the decisive ball? In SV, you always have more than one way of doing something, but almost never the right way of doing something. Pest vs. cholera decisions are a daily matter.

Here’s a quick look at the super powers and weaknesses of the first five: a class supports inheritance, can be instantiated and passed around as reference, but can’t instantiate any static units such as a module, interface or program. This means it can’t be used as a top level container. It also can’t hold assertions.An interface, a module and a program do not support inheritance but can be used as top level entities. All of them can contain assertions.An interface can be somewhat clumsily passed around as a reference, which is quite important, and which none of the other static elements can. However, it can’t instantiate a module.A module can’t be passed around as reference but can be synthesized, so if you have a Verilog design, you’ll always have one of those.A program is more or less identical to a module, except that it can’t be synthesized and that it samples signals at the end of the delta instead of at the beginning. For me, that’s the one player I like to have right next to me, that is, on the bench. If you’re using VMM, however, you’ll probably have it on the field as well. Finally,a package, is a convenient way of sharing user defined classes and types between modules, programs, interfaces and other classes. Unlike the others, it can’t be instantiated, and is used only for type resolution at compile time. Like them, however, it has its own annoying limitations. For example, it can’t contain interface, program or module definitions. These definitions must always contaminate the global namespace.

As mentioned above, normally I use four players: the class, the module, the interface and the package, and leave the program out. The class is the natural place for most of the data and functionality of my testbench because it can be derived from and extended later on. The module is an almost unique choice for a top level container because it is the only one that can instantiate the DUT modules. I’m saying almost because you can also use an interface or program as top level modules and instantiate your testbench in parallel to the DUT. If you go for this option, then you can probably leave modules out of your testbench as well. The interface is used as a channel of communication between the class base world and static elements such as modules and interfaces. The package is used as storage for all my classes and user defined types.

Confused? Hopefully the following sections, which are dedicated to each of these five building blocks, will make things somewhat clearer.

 

Interfaces

Here’s my own short take on the essence of interfaces: interfaces are the one and only means available in SV for any type of communication between classes that are defined in packages and static objects. In other words, if you would like a code written in a class that is part of a package to look at any kind of data in the static world (such as a reg or wire inside a module), the only way to do so is by using an interface. This goes also for the case where you would like to call a function defined in the static world from your class. The snippet below will clarify this point:

package vip;
// Inside this class I would like to
// monitor the top module signal x.
// Since this class is defined inside a
// package, I can't do so simply by
// saying top.x (see below). The only way
// to do so is by passing an interface
// that connects the module to the class
// instance
  class vip_env;
     
     virtual top_if m_top_if;
     
     function new(virtual top_if top_if );
        m_top_if = top_if;
     endfunction
     
     task run();
        // the following line will give a compilation error
        // "hierarchical access is not allowed from within a pacakge"
        //$display(top.x);
        
        // but this one will work
        $display(m_top_if.x);
        // and this one as well
        m_top_if.print_x();
     endtask  
  endclass
endpackage

// The interface is defined in order to
// connect the static world (the module)
// and the class based world
interface top_if;
  wire x;

  function void print_x();
     top.print_x();
  endfunction
endinterface

module top;

   // Import the pacakge so that
   // I can create class instances
   import vip::*;

   reg x;

   // class instance
   vip_env vip_env_i;

   // interface instance
   top_if top_if_i();

   // Connect the interface x
   // signal to the top x signal.
   assign top_if_i.x = x;
   
   initial 
     begin
        vip_env_i = new(top_if_i);
        x = 1;
        // note that the assign above
        // takes a delta to execute.
        // Therefore, without the #0
        // this code will print x. With
        // the #0, it prints 1 as expeted.
        #0;
        vip_env_i.run();
     end // initial begin

   function void print_x();
      $display(x);
   endfunction
endmodule

Note that if your class is defined inside a module, then you can have a hierarchical path inside it. However, defining classes directly inside a module would prevent you from using them anywhere else except for that specific module, which is not very useful. Just for the record, such access is shown below:

module top;

reg x;

class vip;
  task run();
    $display(top.x);
  endtask  
endclass

initial 
  begin
    vip vip_i;
    vip_i = new();
    vip_i.run();
  end

endmodule

When you are using an interface to connect a DUT to the class based world the most important thing to remember is that an interface is just a bunch of signals that could be passed around as a reference. You can choose the signals in the interface to be whatever you want – input, output or inout, wire, reg or bit – and in each of these cases you will have the good old Verilog limitations on the driving side and on the monitoring side. For example, if your interface has a signal defined as wire, you will only be able to drive it using a continuous assignment, and not from procedural code. The simple examples below show two options of monitoring and driving a DUT output from the class based world. In the first, which is more frequent in real life, the DUT pins are defined as wires. In the second, they are defined as reg.

// dut inputs are defined as wire
// the class drives reg (always true)
// the top level does the connection

// wire is the default net type but I'm 
// writing it explicitly only for clarity
module dut(input wire dut_in, output wire dut_out);
   assign dut_out = ~dut_in;
endmodule

interface dut_if();
  // see just below why this signal is reg
  // and not wire
  reg dut_in;
  wire dut_out;
endinterface

class vip;
   virtual interface dut_if m_dut_if;
   
   function new(virtual interface dut_if dut_if);
      m_dut_if = dut_if;
   endfunction
   
   // You can't have "assign" statements in a class.
   // Therefore, when you drive a signal from the
   // class based world, you always do it from procedural
   // code. This means the signal you're driving always 
   // has to be defined as "reg" in the interface.
   task run();
      m_dut_if.dut_in = 0;
      forever
        begin
           #1;
           m_dut_if.dut_in = ~m_dut_if.dut_in;
        end
   endtask
endclass 

module tb_top();
   wire dut_in;
   wire dut_out;
   
   dut dut_i(dut_in,dut_out);
   dut_if dut_if_i();
   vip vip_i;
   
   assign dut_i.dut_in = dut_if_i.dut_in;
   assign dut_if_i.dut_out = dut_i.dut_out;
   
   initial begin
      vip_i = new(dut_if_i);
      vip_i.run();
   end
   
endmodule

And, as promised, an example with IOs defined as regs:

// dut inputs are defined as reg
// the class drives reg (always true)
// the top level does the connection

module dut(input reg dut_in, output reg dut_out); 
  always @(dut_in)
    dut_out = dut_in;
endmodule

interface dut_if();
  reg dut_in;
  reg dut_out;
endinterface

class vip;
  virtual interface dut_if m_dut_if;

  function new(virtual interface dut_if dut_if);
    m_dut_if = dut_if;
  endfunction
  
  task run();
    m_dut_if.dut_in = 0;
    forever
      begin
        #1;
        m_dut_if.dut_in = ~m_dut_if.dut_in;
      end
  endtask
endclass 

module tb_top();

  dut dut_i();
  dut_if dut_if_i();
  vip vip_i;
  
  always @(dut_if_i.dut_in)
    dut_i.dut_in = dut_if_i.dut_in;
    
  always @(dut_i.dut_out)
    dut_if_i.dut_out = dut_i.dut_out;
  
  initial begin
    vip_i = new(dut_if_i);
    vip_i.run();
  end
    
endmodule

 

Programs

The SV standard sells programs as a solution to races between the DUT and the testbench. Before I go into why solving this problem is, IMHO, of minor or no importance, here's a short snippet that demonstrate's the standard's intention:

// The following example shows how
// a program always samples signals
// defined in modules (and interfaces)
// after they are stable. Signals a and b
// can change multiple times for a single
// delta, but in the program you'll
// always get the value at the end of the
// delta.

program pr(input a, input b);
initial
  $display("a is %d, b is %d", a, b);
endprogram

module block(output a, output b);

   wire x = 0;
   
   reg a_p = 0;
   reg b_p = 0;

   int tick_counter;

   always @(x,b)
     begin
        if (tick_counter < 5) 
          begin    
             a_p = ~a_p;
             tick_counter++;
          end
     end

   assign a = a_p;
   
   always @(a)
     b_p = ~b_p;

   assign b = b_p;
endmodule // block1


module top();
   wire a,b;
   pr pr_i(a,b);
   block block_i(a,b);
endmodule   



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章