Tuesday, May 22, 2018

SystemVerilog Interfaces - An unpleasant construct

I haven't written in a while, but I just can't hold myself back. I have spent days writing some fancy little test bench code and because I can't fight my own nature, I just had to try and do it with the most advanced techniques possible - with the most elegant solutions.

So I tried to use interfaces... and it has proven to be a colossal failure.

Goals:
1. Use the "correct" construct in each situation
2. Minimal replication, both of signals and of code
3. Use arrays

Attempt:
I have a couple modules that I want to test, testing each separately and then together.
I have interconnect between these modules.
I have test bench components that can optionally be used to drive or receive one side of the interconnect. (An interface has signals that goes between a DRIVER and a RECEIVER, I have test bench components that can emulate the DRIVER and RECEIVER.)

An interface that declares a bunch of signals to connect two modules (DRIVER, RECEIVER) should have no clocking blocks - why? Because clocking blocks are only for test bench logic in the interface and would create multiple drive issues when you connect both a module and have a clocking block that can also drive the same signals. (Workarounds of course are possible, but trying to avoid those.)

First problem:
Cannot declare a clocking block in a modport to make it only exist within the context of a test bench component connecting to the modport (or not exist when connecting to a module). We are stuck creating additional interface components to attach on top:

I create an interface which declares the signals and the standard modports. Now this interface (SIGNAL_INTF) has no procedural test bench code.

I then create interfaces called DRIVER_INTF and RECEIVER_INTF where I do not declare any signals - and I just use them to attach to SIGNAL_INTF as a DRIVER or RECEIVER. These blocks get the interface as a port.

Now I have arrays of interfaces that I want to use in my code. So I write code to index into the interfaces.

Second problem:
SIGNAL_INTF intf [NUM];
always_comb begin
  sum = 0;
  for (int i = 0; i < NUM; i++)
   sum += intf[i].value;
end

Nope - this syntax is not allowed. Can't index into an interface array with a non-const expression. 'i' is not a constant. Why? Because unlike a regular array an interface array is non-homogeneous as it can have different types stored within it - has to do with defparams. Reference to discussion:
https://stackoverflow.com/questions/45058765/arrays-of-interface-instances-in-systemverilog-with-parametrized-number-of-eleme

And of course interfaces are not strongly typed (did I use that terminology wrong?), and it doesn't appear that they can be. For example, the simulation tool I'm using doesn't bother to check modports at all. Remove modports - same result, change interface name to generic 'interface' - same results. Interfaces are connected at elaboration time, but even then, they let you connect anything up. Makes modports useless. Because of this, parameterizing interfaces within a port list is unnecessary - but also not compilable. I wish it was, as I like to use strong typing. It helps me avoid bugs.

Third problem (not really a problem as modports don't really matter):
No syntax support for parametrizable interface on a port list
No way to pass in modport name for interface instance array in an instantiation connection

Yep, there are workarounds for all of this, but if you are like me, and you wish to code in the most advanced and "proper" way possible, I would avoid using interfaces too much. A little bit might be OK, but they are not well thought out and definitely not language ready.