Wednesday, September 3, 2014

Fake It 'til You Make It - Emulating Multiple Inheritance in SystemVerilog

In the last post I talked about interface classes and how they can be used to separate what an object "can" do from "how" it does it. While using interface classes helps a lot in developing code that is modular and reusable, there are still things that are missing. In this discussion on LinkedIn, Ilia makes a very good observation with respect to extendibility. If we extend a base class (in our case, uvm_component), how can we propagate all of these changes to its subclasses (namely uvm_monitor, uvm_driver, etc.)? This is exactly the kind of problem which could be solved with multiple inheritance. Unfortunately, SystemVerilog doesn't support multiple inheritance, so I guess we're out of luck...

Just kidding, as you could probably tell from the title. We can use the tools at our disposal (i.e. single inheritance) to emulate multiple inheritance. The pattern we're going to look at is called a 'mixin'. What better way to illustrate what a mixin is than by looking at an example.

Let's say we want to enforce a rule that all ports of our UVM components are connected before the run phase starts. We create our own super-library based on UVM that contains this addition (and potentially more). We can add such a check to uvm_component by creating a subclass:

class vgm_uvm_component extends uvm_component;
  
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  function void start_of_simulation_phase(uvm_phase phase);
    check_ports();
  endfunction
  
  function void check_ports();
    `uvm_info("VGM", "I'm checking if all of my ports are connected", UVM_LOW);
  endfunction

endclass

We added the check_ports() function that makes sure that all ports are connected and we called it during the start_of_simulation phase. This will work fine for any class that extends directly from vgm_uvm_component. However, we also want to be able to extend from uvm_driver, uvm_monitor, uvm_agent, etc. These are subclasses of uvm_component, which may not provide too much now, but could potentially do more in future versions of UVM. It also makes our code's intent much clearer. What we need are vgm versions of these classes as well. The simplest way we could do this is by creating one subclass for each, where we copy the additions we made to uvm_component:

class vgm_uvm_monitor extends uvm_monitor;
  
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  function void start_of_simulation_phase(uvm_phase phase);
    check_ports();
  endfunction
  
  function void check_ports();
    `uvm_info("VGM", "I'm checking if all of my ports are connected", UVM_LOW);
  endfunction
  
endclass

We'd have to do something similar for each uvm_* class, which you'll probably agree is a lot of work. Not only that, but should we find a bug in our code (maybe the check_ports() function isn't working properly), we'd have a lot of places to patch to fix it. This is basically why copy-pasting code is a cardinal sin in the software development world. Sure, we could define the added code as a macro and just include it in every class, but excessive preprocessor use is also something to be avoided.

It sure would be nice if we could make uvm_monitor inherit the changes as well, wouldn't it? It turns out that this is possible. As mentioned above, what we have to do is use a 'mixin'. What we were basically doing above was inheriting from each member of the UVM component class family. Well, why not just make the base class a parameter? Something like:

class vgm_check_ports_mixin #(type T = uvm_component) extends T;
  
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  
  function void start_of_simulation_phase(uvm_phase phase);
    check_ports();
  endfunction
  
  function void check_ports();
    `uvm_info("VGM", "I'm checking if all of my ports are connected", UVM_LOW);
  endfunction
  
endclass

We can then create our vgm_* class family by just parameterizing this mixin with the appropriate base class:

typedef vgm_check_ports_mixin #(uvm_component) vgm_uvm_component;
typedef vgm_check_ports_mixin #(uvm_monitor) vgm_uvm_monitor;

We can do this for any UVM component subclass. In our business code, instead of inheriting from uvm_monitor we'll inherit from vgm_uvm_monitor and so on.

I want to take a short break here and give credit where credit is due. The idea isn't mine, as mixins are already used in the software development world. I first heard of them and saw them implemented in SystemVerilog in this thread in the UVM forums. Reading it made me really excited as this technique opens up a whole new world of possibilities.

We've seen how we can use mixins to propagate additions to a base class down along its inheritance hierarchy. Let's now have a look at a slightly different example. In the last post I mentioned how interface classes can be used to write a much cleaner implementation of TLM. The code I showed was:

class uvm_blocking_get_port #(type T=int) implements
  uvm_blocking_get_if #(T);
  // ...
  
  virtual task get(output T t);
    // ...
  endtask
  
  // other uvm_blocking_get_if interface methods ...
endclass


class uvm_nonblocking_get_port #(type T=int) implements
  uvm_nonblocking_get_if #(T);
  // ...
  
  virtual function bit try_get(output T t);
    // ...
  endfunction
  
  // other uvm_nonblocking_get_if interface methods ...
endclass


class uvm_get_port #(type T=int) implements
  uvm_get_if #(T);
  // ...
  
  // uvm_blocking_get_if interface methods ...
  
  // uvm_nonblocking_get_if interface methods ...
endclass

This snippet describes "what" each port can do (by enumerating the methods it has), but side-steps "how" it does it. Some of you may have probably been asking yourselves the following question: "Won't we have to repeat the same implementation of get(...) (for example) in both uvm_blocking_get_port and in uvm_get_port?". The answer is "Yes, we could, but as before, there's a better way". Let's do like in the previous case and start with the "bad" solution and refine it as we go.

A TLM port per definition forwards a method call on it to its connected implementation. This might be another port (which in turn forwards) or an end component which provides the actual implementation. What we first need is some solid ground to stand on. Let's define a base class that handles connecting and holding the implementation:

virtual class tlm_get_port_base #(type IF);
  protected IF m_imp;
  
  function void connect(IF imp);
    m_imp = imp;
  endfunction
endclass

The implementation can be either blocking, non-blocking or both, so we'll leave it as a parameter. Based on this, here's how the blocking get port would look like:

class tlm_blocking_get_port #(type T = int)
  extends tlm_get_port_base #(tlm_blocking_get_if #(T))
  implements tlm_blocking_get_if #(T);
  
  virtual task get(output T t);
    m_imp.get(t);
  endtask
  
  virtual task peek(output T t);
    m_imp.peek(t);
  endtask
  
endclass

It's nothing special, we're just extending the base (parameterized to accept a blocking interface) and defining the implementations of the interface methods. The non-blocking port will look similar:

class tlm_nonblocking_get_port #(type T = int)
  extends tlm_get_port_base #(tlm_nonblocking_get_if #(T))
  implements tlm_nonblocking_get_if #(T);
  
  virtual function bit can_get();
    return m_imp.can_get();
  endfunction
  
  virtual function bit can_peek();
    return m_imp.can_peek();
  endfunction
  
  virtual function bit try_get(output T t);
    if (m_imp.try_get(t))
      return 1;
    return 0;
  endfunction
  
  virtual function bit try_peek(output T t);
    if (m_imp.try_peek(t))
      return 1;
    return 0;
  endfunction
  
endclass

For the "full" get port we have to implement all of these methods. This means copy-pasting the code from both of these classes:

class tlm_get_port #(type T = int)
  extends tlm_get_port_base #(tlm_get_if #(T))
  implements tlm_get_if #(T);
    
  //--------------------------------------------------
  // methods copied from 'tlm_blocking_get_port'
  //--------------------------------------------------
  
  virtual task get(output T t);
    m_imp.get(t);
  endtask
  
  virtual task peek(output T t);
    m_imp.peek(t);
  endtask
  
  
  //--------------------------------------------------
  // methods copied from 'tlm_blocking_get_port'
  //--------------------------------------------------
  
  virtual function bit can_get();
    return m_imp.can_get();
  endfunction
  
  virtual function bit can_peek();
    return m_imp.can_peek();
  endfunction
  
  virtual function bit try_get(output T t);
    if (m_imp.try_peek(t))
      return 1;
    return 0;
  endfunction
  
  virtual function bit try_peek(output T t);
    if (m_imp.can_peek())
      return 1;
    return 0;
  endfunction
  
endclass

This code may not be much (and it will also most likely not change over time), so it might seem harmless. I might even tend to agree in this case, but just think what it would mean if the method definitions were more complicated...

This is another typical case where multiple inheritance would be extremely useful. If we could inherit from both the blocking and the non-blocking ports, we could have our "full" get port without any code duplication. Mixins helped us last time, so let's use them here as well.

Similarly to what we did for the UVM components, we'll implement the additions we make to the tlm_get_port_base class as mixins. First, let's implement the blocking get mixin:

class tlm_blocking_get_mixin #(type T = int, type BASE = tlm_get_port_base #(tlm_blocking_get_if #(T)))
  extends BASE
  implements tlm_blocking_get_if #(T);
  
  virtual task get(output T t);
    m_imp.get(t);
  endtask
  
  virtual task peek(output T t);
    m_imp.peek(t);
  endtask
  
endclass

This is pretty much the blocking get port class, where we've made the base class a parameter. We do the same for the non-blocking port:

class tlm_nonblocking_get_mixin #(type T = int, type BASE = tlm_get_port_base #(tlm_nonblocking_get_if #(T)))
  extends BASE
  implements tlm_nonblocking_get_if #(T);
  
  virtual function bit can_get();
    return m_imp.can_get();
  endfunction
  
  virtual function bit can_peek();
    return m_imp.can_peek();
  endfunction
  
  virtual function bit try_get(output T t);
    if (m_imp.try_get(t))
      return 1;
    return 0;
  endfunction
  
  virtual function bit try_peek(output T t);
    if (m_imp.try_peek(t))
      return 1;
    return 0;
  endfunction
  
endclass

Again, this is pretty much the non-blocking get port class, but with a parameterized base class. Implementing the get ports becomes pretty trivial; we just need to add our mixins to the appropriate base classes:

class tlm_blocking_get_port #(type T=int)
  extends tlm_blocking_get_mixin #(T);
endclass

class tlm_nonblocking_get_port #(type T=int)
  extends tlm_nonblocking_get_mixin #(T);
endclass

class tlm_get_port #(type T=int)
  extends tlm_nonblocking_get_mixin #(T, tlm_blocking_get_mixin #(T, tlm_get_port_base #(tlm_get_if #(T))));
endclass

For the blocking and non-blocking ports we didn't even need to specify the base class, as we used the default values of the parameters. The "full" get port is just a blocking get port with a non-blocking mixin on top (or the other way around; either works). We just have to be careful that the class at the very base is parameterized with the proper implementation type (i.e. tlm_get_if). This is pretty much it. No duplication, less code to maintain and (pretty) easy to understand as well.

Let's summarize what we've learned. Even though SystemVerilog doesn't provide multiple inheritance, using the mixin pattern we can emulate it by using only single inheritance. Mixins can help us propagate extensions down along a class hierarchy (as we did for the UVM component class family). They can also be used to create a class that inherits from two (or even more) parallel sub-classes while avoiding the diamond problem. I for one intend to use more mixins in my code and maybe so should you.

I've added the mixin code, together with some test harnesses and the "bad" code to the blog repository, for those who want to check it out.

See you next time for more SystemVerilog tips and tricks!

3 comments:

  1. Another good post! I'll pack this technique into my little bag of tricks. :)

    ReplyDelete
  2. hi Tudor, we've used mixins for a bunch of purposes (thanks to you for reminding us of the possibility) but I thought your readers might be interested in a potential problem with it. This problem caught me by surprise at first, although I suppose it should have been obvious. It definitely somewhat restricts the applicability of the mixin trick.

    The problem is that the mixin's constructor must call super.new() on its parameter/ancestor class, and therefore must know the signature of that class's constructor. This, of course, severely restricts the set of classes to which the mixin can be applied. Similarly, the mixin's constructor must have a signature matching its parameter/ancestor, otherwise anything derived from the mixin will get a nasty surprise when trying to call super.new().

    For your example, this is straightforward. You always expect the mixin to be applied to some kind of uvm_component, so its constructor should always look like the usual uvm_component derivative:
    -- function new(string name, uvm_component parent = null);
    -- super.new(name, parent);
    But suppose you have a mixin that could be applied equally to a uvm_object, a uvm_component, or some user-defined base class with a completely different constructor? Now you're messed-up, because (a) the mixin's constructor may call super.new() with the wrong arguments, and (b) the mixin's constructor may have a signature that its child classes neither want nor expect.

    There is, I believe, nothing that can be done about this. It's just a fact of life in SystemVerilog, and it means that we need to write different mixins to suit different base classes. (Macros might help with that.) But it's a limitation that users need to be aware of before they start work on it.

    ReplyDelete
    Replies
    1. Yep, that's true. I usually implement mixins for constraints (declarative code), so they're targeted to a certain sequence item family. Luckily, the constructor for uvm_objects is fixed (which is a blessing in this case, but a curse in others), so this doesn't cause problems.

      For extending procedural code, I'm investigating callbacks more and more (a topic for a future post).

      Delete