Dissecting SystemVerilog Packages

From SystemVerilog_Wiki

Jump to: navigation, search

<meta name="description" content="An introduction to SystemVerilog package construct -- its use and abuse"></meta> <meta name="keywords" content="SystemVerilog, package, tutorial, P1800"></meta>

Contents

Why Packages are important?

The previous SystemVerilog standard (P1800 - 2005) had some ambiguities (see the sub-topic on Package Chaining down below) with respect to the package construct. As a result, use of SystemVerilog package construct never picked up. These ambiguities have been taken care of in the IEEE standard released in 2009-10. Yet another confusion (see Verification Guild<ref>Verification Guild Topic, "Use of Packages in SV?"</ref>) is created because of s construct in VHDL by the same name, but different semantics.

In this article, we take a look at the construct and see how the construct can be useful while creating verification IPs in SystemVerilog. Why the package construct is so important for SystemVerilog? The answer lies in the encapsulation model that SystemVerilog standards committee adopted.

In general the package construct provides encapsulation in a way that is complementary to the encapsulation provided by classes. Using packages in object-oriented programming requires a discipline. Let us first take a look at that discipline in the generic OOP paradigm.

Packages and OOP

Before the concept of a package came in, OOP programmers had only the class construct for providing encapsulation. But when you think of any practical object-oriented design, you find that certain groups of classes demonstrate strong cohesion. For example, within the verification domain, you can think about a transaction and the related set of transactors (generator, drive, monitor etc). You will find that any transactor component class strongly depends on a transaction class.

SystemVerilog Package

It is because of this strong cohesion only, in practice, SystemVerilog programmers make all the data members of a transaction class public. C++ has a different solution to offer in form of friend classes. A class in C++, that has been declared as friend by another class, gets access to all the data and methods of that class. In other words, a class can offer complete access to another class by declaring that class a friend. Other modern languages provide more practical solutions. The D Programming Language for instance, makes private data members of a class visible to the other classes that are part of the same module (the module construct in D is more like Python modules, not to be confused with Verilog's module construct).

Generally as a rule, classes with strong cohesion between them should be included in one package. And a package formed in this manner should be considered as a reuse unit. On the other hand a package should demonstrate weak cohesion with respect to other packages. There are three fundamental principles guiding the use of packages in the OOP paradigm. Together these are known together as Package Cohesion Principles. The principles provide a broad guideline to the programmers, on how to package there applications.

Package Cohesion Principles

The Release Reuse Equivalency Principle

The granule of reuse is the granule of release.

When you create a software component for reuse, consider making its release cycle independent. When a new release is made, a customer may not pick that release immediately. For a customer to reuse a package effectively, it is imperative that a reusable package is released as one unit and a release version tracking system be in place to maintain the package.

The Common Closure Principal

Classes that change together, should be grouped in the same package.

When a class strongly relates to other classes, it will need changes every time any other class it relates to changes. In fact this ripple effect (changes in one class triggers changes in another) is often used as a metrics measure cohesiveness between two classes.

Since a package is used as a software release unit, it is imperative that classes that change together should be part of the same release package. Splitting such cluster of classes (that change together) into multiple packages, would necessitate that a new version of all these packages be released every-time there is a change in the cluster of classes, thus putting additional burden on the teams making the releases and on the integration teams.

The Common Reuse Principle

Classes that aren't reused together, should not be grouped in the same package.

Suppose you get a package that contains many classes that you have no use of. Changes in any of these redundant classes would necessitate a new release of the package. If these redundant classes were not part of the package, you would not have to deal with these releases. More frequent package releases result in additional burden since the whole application goes through a cycle of tests every-time a new release of a package is integrated in the application.

The SystemVerilog Perspective

In SystemVerilog the package construct gets its name from Java, but the semantics are more like C++ namespace semantics. In Java (and in some other modern languages like C# and D), programmers can specify access specifiers while declaring classes or objects inside a package. Thus classes in Java can be made not accessible to the outside world by declaring them private.

SystemVerilog does not provide for access specifiers inside packages. Thus all the classes defined inside SystemVerilog packages remain visible to outside world. This is weak encapsulation. A client is only informed (because the client has to explicitly provide the scope resolution) that he is breaching an encapsulation boundary. In other languages like Java, the package construct provides strong encapsulation.

Let us look at some specifics of the SystemVerilog package construct.

Declaring a Package

The listing below illustrates a typical package declaration:

package Foo;

    class #(int DW=32) Bar;
      // ....
    endclass: Bar

    typedef Bar #(64) Bar64;

    // Other class/type  definitions

endpackage: Foo

Warning

Unlike namespaces in C++, SystemVerilog packages can not be nested, thus making the following code illegal:

package P1;
  class Foo;
    // ....
  endclass: Foo

  package P2;
    // Error: nested package not allowed
  endpackage: P2
endpackage: P1

Importing a Package

Classes and other types inside a package can be imported into other SystemVerilog scopes. For this purpose SystemVerilog provides for the keyword import. Given below is a typical usage of the import construct:

package ahb_pkg;
   typedef enum bit[2:0] {SINGLE, INCR, WRAP4, INCR4,
                          WRAP8, INCR8, WRAP16, INCR16} BURST_E;
   typedef enum bit {READ, WRITE} KIND_E;
   // Other typedefs
endpackage: ahb_pkg

class ahb_xactn;
  import ahb_pkg::BURST_E;
  import ahb_pkg::KIND_E;

  rand BURST_E hburst;
endclass: ahb_xactn

class ahb_driver;
  // AVOID
  import ahb_pkg::*;

  rand BURST_E hburst;
endclass: ahb_driver

The listing illustrates one typical use of the package construct in SystemVerilog. All the type definitions useful for a number of components are declared in a package. These types then become universally accessible to all the components that need them.

Such use of SystemVerilog package construct is quite prevalent in the industry and is in fact motivated by the semantics of package construct in VHDL. From OOP software architecture perspective, this is an unusual way of using the package construct. It is much better to encapsulate these type definitions (defined here inside the ahb_pkg package) as part of the transaction class. These types can then be used by the other classes by putting the scope resolution operator to use. Here is how:

class ahb_xactn;
   typedef enum bit[2:0] {SINGLE, INCR, WRAP4, INCR4,
                          WRAP8, INCR8, WRAP16, INCR16} BURST_E;
   typedef enum bit {READ, WRITE} KIND_E;
   // Other typedefs

  rand BURST_E hburst;
endclass: ahb_xactn

class ahb_driver;
  typedef ahb_xactn::BURST_E BURST_E;
  typedef ahb_xactn::KIND_E KIND_E;

  rand BURST_E hburst;
endclass: ahb_driver

Note the use of typedefs on the line 11 and 12. If we had used a package instead, we would be importing these types using the import statement.

Warning

The statement import ahb_pkg::*; in the above listing imports everything in the package namespace and thereby destroys whatever encapsulation the ahb_pkg package provides. Whenever possible, such import statements should be avoided altogether.

Package Chaining

When you import a package contents into another package, the imported contents become part of the sourcing package's namespace. But what happens to these imported contents when a third package imports the sourcing package?

This scenario was inadvertently left out of the scope of the previously released SystemVerilog standard. This led to an ambiguity and different implementations of the SystemVerilog compilers diverged.

The new standard P1800-2009, introduces a new construct (export) for this purpose. Unless an imported content is not explicitly exported, the content would not become a part of any subsequent (chained) import.

Mixing package construct with `include

What we discuss now is not so much a weakness of the package construct itself, it is an idiosyncrasy of the `include pre-processor directive.

The `include directive is not specific to SystemVerilog. C and C++ programmers have been using #include directive to include files for a few decades. But with continual use over the period C programmers have formed a discipline for proper use of #include directive.

The problem with `include (or #include in C/C++) is that the pre-processor works independent of the language compiler. It works blindly and does not understand the semantics of the constructs in the context in which the `include directive is invoked.

If you look at C/C++ usage, include files are often called header files. This is because as a discipline, C/C++ programmers include these files on the top of encapsulating source code files. This makes sure that inclusion of code always happens in the global context or namespace.

The Verilog legacy has been somewhat different. Verilog coders have been using `include arbitrarily. More so because structural code in verilog often grows too big to manage and `include often comes in handy to make such code manageable.

With introduction of the package construct in SystemVerilog, sometimes such habits give rise to difficult situations. Consider the following code:

Mixing `include with package construct
`include code expanded code
// File foo.sv

class foo;
  // class body
endclass: foo

// File p1.sv
package p1;
`include "foo.sv"
endpackage: p1

// File p2.sv
package p2;
`include "foo.sv"
endpackage: p2

// File p1.sv
package p1;
class foo;
  // class body
endclass: foo
endpackage: p1

// File p2.sv
package p2;
class foo;
  // class body
endclass: foo
endpackage: p2

As the pre-processor runs and the `include directives get expanded, The foo class gets defined twice over. But since packages p1 and p2 provide segregated namespaces, p1:foo and p2::foo become two different types (also see Mentor Blog<ref>Dave Rich, Mentor Blog, "SystemVerilog Coding Guidelines: Package import versus `include"</ref>). This is probably not what the programmer would have intended.

This leads to major issues. For instance the previous SystemVerilog standard provided for linked-list as a data-structure. Unlike dynamic arrays and queues (which are a native feature of the language), linked lists were implemented as a library (coded as SystemVerilog class). Unfortunately, the standard implementation of List.svh, puts the library in the global namespace. Now as SystemVerilog programmers started using this data-structure, they `included the linked-list implementation in there own packages; sometimes multiply, in different packages, as with the foo class in the listing above. As we saw, when that happens, it results in creation of two distinct types. And these types are not compatible with each other, leaving the programmer stranded (see SystemVerilog Mantis<ref>SystemVerilog Mantis, 2006, "Issues with List.vh"</ref>). The linked list was later removed from the SystemVerilog standard (IEEE P1800-2009).

As suggested on the SystemVerilog Mantis, such problems can be completely avoided by enclosing the `included files in their own packages. Since nested packages are not allowed in SystemVerilog, this would ensure that it becomes illegal to `include such a package from inside another package, thus completely avoiding creation of parallel but incompatible data types.

Conclusion

The package construct in SystemVerilog provides a form of encapsulation that is complimentary to the encapsulation provided by the class construct. In Object-Oriented paradigm, packages are tied with reuse and software release. As a usage guideline, if you are creating a reusable component (such as a Verification IP), encapsulate it in a package of its own before you release it to others.

Complications arising out of `include vs package usage can also be completely kept at bay by making sure that whenever you create a reuse component that has to be `included, completely encapsulate such a component inside a package of its own.

A popular use of the package construct in SystemVerilog is motivated by VHDL package semantics, wherein enumeration/constant/typedef declarations, common to a set of classes, are enclosed in a package. In SystemVerilog, such use of package construct is completely avoidable by making these declarations inside the class these declarations belong to, and using them inside other classes with help of the scope resolution operator.

References

<references/>