Saturday, 6 June 2015

Interface Class in SystemVerilog

An interface class has nothing to do with the interface construct. It represents the same concept as an interface in Java (a lot of SystemVerilog's object oriented programming constructs are pretty similar similar to Java's). What does an interface class do? It's basically a collection of method declarations Notice I've used the word 'declarations' and not 'definitions', as all methods of an interface class must be pure. Another class can implement an interface class, which requires it to implement all of the methods declared in that interface.

Why is this useful? I'll answer this question with the help of an example. Let's say I have my own library. In this library I expect to operate on a certain type of objects (by operating on objects I mean calling methods on them).

Concretely, let's say I have the 'drivable' interface, which defines the capabilities of an object that can be driven (I don't want 'car' here and you'll see why in just a bit). What can a drivable object do? Well, it can accelerate, it can turn and it can brake, to name a few things. We model these as functions that a drivable object has:

interface class drivable_if;
  pure virtual function void accelerate();
  pure virtual function void turn_left();
  pure virtual function void turn_right();
  pure virtual function void brake();
endclass

A driver can use these methods to drive a drivable object:

class driver;
  protected drivable_if m_drivable;
 
  function new(drivable_if drivable);
    m_drivable = drivable;
  endfunction

  function void drive();
    m_drivable.accelerate();
    m_drivable.turn_right();
    m_drivable.accelerate();
    m_drivable.turn_left();
    m_drivable.brake();
  endfunction
endclass
 
Our driver class can operate on any object that provides the methods of the drivable_if interface class, regardless of how these methods are implemented. In our code (outside of the library), we define the car class, that implements the drivable_if interface class:

class car implements drivable_if;
  //----------------------------------------
  // methods of drivable_if 
  //----------------------------------------

  virtual function void accelerate();
    $display("I'm accelerating");
  endfunction

  virtual function void turn_left();
    $display("I'm turning left");
  endfunction

  virtual function void turn_right();
    $display("I'm turning right");
  endfunction

  virtual function void brake();
    $display("I'm braking");
  endfunction
endclass
 
We can now use an instance of this class, together with an instance of the driver class:
 
module top;
  initial begin
    static car the_car = new();
    static driver the_driver = new(the_car);
    the_driver.drive();
  end
endmodule


//Output:
// I'm accelerating
// I'm turning right
// I'm accelerating
// I'm turning left
// I'm braking

Remember, the driver class and the drivable_if interface class are defined in an own package (that we downloaded, bought, etc.), which we'll assume we can't change. We could, however, let our own car object be driven by the driver object, even though the driver class did not know anything about the car class. This is because the car class provides the methods that the driver expects to be able to drive it. It doesn't matter how those methods were implemented, just that they were implemented.
What you're now probably going to ask is: "But why didn't you just implement a virtual class? You can essentially get the same thing: you define the methods and you can't create any instances of that class.". And you would be right, but what if we want our car class to implement another interface at the same time? If I use a virtual class, I'm in trouble, because you can only extend one base class. You can, however, implement as many interfaces as you want.
What else do you want to do with a car besides drive it? You want to insure it. For example the insurance premium depends on the size of the car's engine. It may also depend on is the accident history of the car (not technically true in the real world, but please bear with me on this one).
Insuring a car is a different aspect than driving it, so it makes sense to have a separate library the handles this topic.
Following the example from above, this is how the interface for an insurable object (notice I didn't say car) might look like:

interface class drivable_if;
  pure virtual function void accelerate();
  pure virtual function void turn_left();
  pure virtual function void turn_right();
  pure virtual function void brake();
endclass : drivable_if

 
class driver;
  protected drivable_if m_drivable;
 
  function new(drivable_if drivable);
    m_drivable = drivable;
  endfunction

  function void drive();
    m_drivable.accelerate();
    m_drivable.turn_right();
    m_drivable.accelerate();
    m_drivable.turn_left();
    m_drivable.brake();
  endfunction
endclass : driver

 
interface class insurable_if;
  pure virtual function int unsigned get_engine_size();
  pure virtual function int unsigned get_num_accidents();
  pure virtual function int unsigned get_damages(int unsigned accident_index);
endclass
Using these methods to query an object, an insurer could compute the premium for that object:

class insurer;
  virtual function int unsigned insure(insurable_if insurable);
    int unsigned engine_size = insurable.get_engine_size();
    int unsigned num_accidents = insurable.get_num_accidents();
    int unsigned damages;
    for (int unsigned i = 0; i < num_accidents; i++)
    begin
      damages += insurable.get_damages(i);
    end
    // do some bogus calculation
    return engine_size * 10 + damages * 100;
  endfunction : insure
endclass

Let's take our previous car class and expand it to be insurable. What we need to do is implement the insurable_if interface and define its methods:
 
class car implements drivable_if, insurable_if;
  protected int unsigned m_engine_size;
  protected int m_damages[];
 
  function new(int unsigned engine_size);
    m_engine_size = engine_size;
  endfunction

  function void crash(int unsigned damages);
    m_damages = new[m_damages.size() + 1] (m_damages);
    m_damages[m_damages.size() - 1] = damages;
  endfunction
 
  //----------------------------------------
  // methods of insurable_if 
  //----------------------------------------

  virtual function int unsigned get_engine_size();
    return m_engine_size;
  endfunction

  virtual function int unsigned get_num_accidents();
    return m_damages.size();
  endfunction

  virtual function int unsigned get_damages(int unsigned accident_index);
    assert (accident_index < get_num_accidents());
    return m_damages[accident_index];
  endfunction
 
  //----------------------------------------
  // methods of drivable_if 
  //----------------------------------------

  virtual function void accelerate();
    $display("I'm accelerating");
  endfunction

  virtual function void turn_left();
    $display("I'm turning left");
  endfunction

  virtual function void turn_right();
    $display("I'm turning right");
  endfunction

  virtual function void brake();
    $display("I'm braking");
  endfunction
endclass : car

I've added a crash() method to simulate an accident. Let's insure our car:

module top;
  initial begin
    static car the_car = new(3);
    static driver the_driver = new(the_car);
    static insurer the_insurer = new();
   
    the_driver.drive();
    the_car.crash(500);
    $display("The insurance premium is ", the_insurer.insure(the_car));
  end
endmodule

//Output:
// I'm accelerating
// I'm turning right
// I'm accelerating
// I'm turning left
// I'm braking
// The insurance premium is      50030

No comments:

Post a Comment