Tuesday, October 7, 2014

A New Twist on SystemVerilog Enumerated Types

A well known SystemVerilog limitation is that the same literal cannot appear in more enumerated types within a package (or more precisely within a scope).

Let's look at a concrete example. We'll assume that we're verifying a DUT that can receive data from the outside world, perform some mathematical operations on it and sends it back. We want to model the operations that our DUT performs and the best way to do that is by using two enumerated types:

package my_pkg;

  typedef enum { NONE, TX, RX } comm_action_t;
  typedef enum { NONE, ADD, SUB } math_action_t;
  
  // code that uses the enums
  // ...
endpackage

The DUT doesn't continuously crunch data, so we want to add a literal for each enum to represent it not doing anything. Let's use the value NONE (I know that for math operations the value NOP would have been more appropriate, but please bear with me, I'm trying to illustrate a point). As discussed above, this code won't compile, because NONE is declared in both types.

What I've seen people do in this case is try to uniquify the names by adding either prefixes or suffixes. I, too, plead guilty to this. For example, the math_action_t type would contain the value NONE2, in order not to clash with the NONE from comm_action_t. This solution seems clumsy to me. Not only that, but if you're trying to connect to a VHDL DUT one "Big Three" simulator is going to complain because the literals don't exactly match (note: VHDL allows the same literal to be present in multiple types).

A very naïve solution would be to define each type in its own package. In the main package we would then import both of these packages:

package pkg1;
  typedef enum { NONE, TX, RX } comm_action_t;
endpackage

package pkg2;
  typedef enum { NONE, ADD, SUB } math_action_t;
endpackage


package my_pkg;
  import pkg1::*;
  import pkg2::*;
  
  class model;
    comm_action_t comm_action;
    math_action_t math_action;
    
    // ...
  endclass
endpackage

We've solved the collision problem, because each type is now defined in its own scope. We can have our cake and eat it too! Or can we? Let's see what happens if we try to use the NONE literal in some procedural code:

class model;
  // ...
  
  function new();    
    comm_action = NONE;
    math_action = NONE;
  endfunction
endclass

Your simulator should, at this point, cowardly refuse to compile the code above, because the literal NONE was imported via wildcards multiple times and it's ambiguous. The correct way to do it is to qualify it with the appropriate packages:

class model;
  // ...
  
  function new();    
    comm_action = pkg1::NONE;
    math_action = pkg2::NONE;
  endfunction
endclass

This solution works as well as the first one. In some respects it's more elegant, but it's also clumsier. Creating a package for each enum will pollute our work library. Also, when using literals in our code, the values ADD and TX, for example, can be written as-is, but we have to write pkg1::NONE. This isn't uniform at all. In addition, think of what would happen if we had to add a new type to a third package that doubled up the value ADD. We'd have to go and scope all of the existing references to ADD with pkg2.

Let's take a step back and consider another situation. What if we want to model each area of functionality separately? What I mean is, instead of creating a big model class that can handle everything, let's assume that we can create a clean split between the communication side and the math side. In this case, each model would be an own class. This means that each class can just embed the enumerated variable definition:

class comm_model;
  enum { NONE, TX, RX } comm_action;
  
  // ...
endclass


class math_model;
  enum { NONE, ADD, SUB } math_action;
  
  // ...
endclass

Since each class represents a different scope, we don't have any problem with the definition of NONE. What we defined here for comm_model, for example, is just a variable called comm_action that can take any of the three values. We can use this as we would any enumerated variable. Hey, you know what else we can define inside a class? An actual type. I guess you already know why I made this little detour...

What if we mixed the two approaches and just defined each type in its own class instead of in its own package? Here's how this would look like:

package my_pkg;
  virtual class comm_action_wrap;
    typedef enum { NONE, TX, RX } t;
  endclass
  
  virtual class math_action_wrap;
    typedef enum { NONE, ADD, SUB } t;
  endclass
  
  // ...
endpackage

Again, since each class is its own scope, we don't have any problem with collisions. Also, to prevent anyone from instantiating these classes, we define them virtual; their only purpose is to wrap the type definitions. If we want to use these wrapped enumerations, we have to scope them with their containing classes:

class model;
  comm_action_wrap::t comm_action;
  math_action_wrap::t math_action;
  
  function new();
    comm_action = comm_action_wrap::NONE;
    math_action = math_action_wrap::NONE;
  endfunction
    
  // ...
endclass

Notice that we also need to scope the enum literals. This solution is more uniform, in that it treats all literals the same (we have to scope all of them). It's also better encapsulated, since we only create extra class types inside our own package. It is, of course, more verbose, but we can't getting something for nothing.

C++ had the same problem with enumerated types, but the C++11 standard fixed that by adding a new construct, the enum class. This is basically the same thing as we just saw above, but it is a first class construct in the language. If you're curious about the topic, you can read more about it here.

I for one would like to see a similar enhancement to SystemVerilog in the future. What I would avoid, however, is calling it an enum class, as I think the word "class" carries a different connotation and would just confuse users (we anyway have the problem that the word "interface" has a double meaning). What might be practical though is to add scoping to the current construct and to allow the compiler to determine the type of a literal based on the context (à la VHDL or e). Here's what I mean:

typedef enum { NONE, TX, RX } comm_action_t;

class some_class;
  function void do_stuff();
    comm_action_t comm_action = TX;
    // ...
    
    if (comm_action == RX) begin
      // ...
    end
  endfunction
  
  function void do_other_stuff();
    int some_int = int'(comm_action_t::NONE);
  endfunction
endclass

In the code snippet above, when assigning a value to comm_action we can just omit the scope operator, because from the context we know that the left hand side of the expression is of type comm_action_t, so we would expect the right hand side to be of the same type. The situation is similar for the logical operator inside the if statement. If however we want to do a cast, we can't figure out from the context what enum the value NONE belongs to (as it could be multiply defined), so we have to use the :: operator. This solution would mean that code written in SV2012 could potentially be incompatible to the SV2099 version (that includes this proposal), but I would expect the occurrences of the third construct (the casting) to be far fewer than those of the first two constructs (where we can determine the type from the context).

In the meantime, our best bet is to just wrap enumerated types in virtual classes to avoid name collisions between literals. I hope you found this post useful. See you next time!

11 comments:

  1. Unfortunately many synthesis tools simply barf when they see the class keyword, so using a class to create a scope, or create a parameterised function (as per http://stackoverflow.com/a/22711967/579887) doesn't work.

    Sadly with SystemVerilog we seem to have ended up with a language that combines "design" and "verification" but where nobody knows which parts they are supposed to ignore...

    ReplyDelete
    Replies
    1. Interesting point. I usually write the posts from a verification perspective, so I don't really think about synthesis tools. All the more reason then for a standard update.

      Regarding mixing in design and verification in the same language, the only thing I think is actually useful is being able to specify assertions side-by-side with the verification code. Everything else is just marketing mumbo jumbo.

      Delete
    2. SystemVerilog comes close to enabling quite a decent level of abstraction for synthesis, however many of the capabilities are unusable (despite being theoretically synthesisable) because the tool vendors assume they are just for verification.

      One of my biggest gripes with SystemVerilog is the confusion caused by trying to cram so much into it. It would have been much better if they'd enabled functionality by relaxing rules instead of adding new features. For example the capabilities provided by interfaces could be achieved by allowing module and package instances to be passed around as first class objects, which would also enable many other use models that are currently prohibited.

      Sadly the industry has instead created a complex monstrosity of a language and invested huge amount of effort implementing the tools. In practice all we've actually achieved is bringing Verilog up to a par with VHDL for synthesis and created a new and rather poor domain specific software language for verification.

      On the verification front - if UVM really is the best we can come up with then I despair. It's ultimately just a collection of macros and helper factories required to work around the fact that SystemVerilog simply isn't a very good language for writing software.

      The vendors now need to re-coup their investments in SV and of course it's the users who will pay for this inefficiency. We're therefore stuck with SV for both design and verification for the foreseeable future.

      We missed an opportunity to actually raise the abstraction for synthesis and we'll have to wait another decade for the next round of innovation unless something like OpenCL manages to disrupt things.

      Delete
    3. Much agreement with Chiggs here. Very well said.

      Delete
    4. +1 with Chiggs. IMO we need a well-designed language for unified verification and design. SV/UVM are not good candidates. I wonder if we can use a general-purpose language like JavaScript or Python someday.

      Delete
    5. @Debamitro I'm a big fan of Python for verification (hence developing Cocotb). There is also the option of MyHDL for design/verification using Python although I don't personally use it.

      Delete
  2. I'm not clear on what is wrong with creating a package for each (I mean, given what we have to work with). You should never do an import package_name::* (if you are familiar with C++ that's the same as saying don't do using namespace namespace_name;) and so you will consistently use the scope operator before your enum member name (just like you did with your class). You mentioned work library pollution as a downside to creating packages, why is that a problem?

    Your class solution is clever, but it seems unnecessarily complicated when packages exist and can do essentially the same thing.

    And now I will admit that I have almost given up on using enums altogether because of issues like this. Python doesn't have enums, so maybe my SystemVerilog doesn't need them either. Strings FTW! :-)

    ReplyDelete
    Replies
    1. Either wrapping in a package or in a class results in an extra level of hierarchy, but IMO the class is nicer because it's better encapsulated. I know it's not nice to use pkg::* (saving this for a different blog post), but that doesn't mean others won't use it, so the class forces a little more discipline.

      Also, regarding classes, something one could add is nice utility methods such as from_string() which converts from a string to the enum literal. I found the idea on http://www.verilab.com/blog/2007/10/casting-strings-to-enums-in-systemverilog/ (also look at the comments). I was thinking of a nice way to integrate it with defining the enum inside the class, that's why it didn't make the cut. Again, something for a future post.

      Delete
    2. One of the main reasons for using classes instead of packages, in my opinion, is that in a modern TB, you are already going to have a class that encapsulates the information that the enum pertains to. I.e. in case of NOP, ADD, SUB, you will certainly have an "instruction"-like class where this enum naturally belongs.

      You now get two benefits -- namespace protection for your enum, and you get to refer to it without class_name::value construct within your instruction class which should, presumably, be the place where the enum is used the most.

      I consider this technique a part of a proper coding standard for SV TBs :).

      Delete
    3. UVM 1.2 has added a uvm_enum_wrapper#(T) class that provides the ability to convert strings to enums. It's more or less the same code as the one from the quoted 2007 verilab post :).

      Delete
    4. Hi Stanislav,

      Welcome to VGM! Thanks for pointing out the uvm_enum_wrapper class. The functionality of this class is still orthogonal to a "wrapper" class that defines the enum inside it, as it takes the enum type as a parameter.

      My idea was that since we anyway wrap the enums in a class, we could somehow also define the utility methods inside those classes. The only way I can think up now is by having some instance of the utility class (uvm_enum_wrapper) inside the class that defines the enum do the work or maybe even better, a mixin.

      Delete