No Simulation

Topics on Hardware Description and Verification Languages, Design and Verification Methodologies

25 September, 2009

Another installment of “Longwinded Answers to Frequent SystemVerilog Questions: $root versus $unit”

Believe me – I tried to make this shorter. It’s difficult for me to explain things without a historical perspective.

Verilog was invented to be an interpreted language. Verilog-XL was (and still is) an interpretive engine with single compilation unit use model. In an interpreted engine, all of the source code is parsed and loaded into memory. This means you have to specify all the source files of a design, including the source files of any required libraries, within a single command line before simulating.

VCS (Verilog Compiled Simulator) continued this single compilation use model even though it compiled the code into a machine object saved on disk. Later, it introduced an incremental compile feature that only compiled certain files that needed it, but you still had to specify all the source files on the command line. This is not the same as separate compilation available in most software programming languages where source code can be converted into machine code independently.

Tools such as NCsim and Modelsim introduced the concept of separate compilation, loosely based on the work library concept from VHDL. This is relatively easy to do in Verilog because module definitions are self contained.  It turns out that module instantiation syntax is easy to recognize, so the compiler does not need to see the definition of module that is being instantiating beforehand. However, parameter overrides and hierarchical references limit the amount of machine code you can generate in the compilation step. The elaboration step that follows does a lot of the work of generating machine code to handle this.

Superlog, the predecessor to SystemVerilog, was also invented as an interpreted language. It introduced the concept of $root as a global scope that allowed any kind of declaration (data types, classes, variables) along with module definitions nested in that global scope. Any uninstantiated module becomes implicitly instantiated in $root. That’s fine for a single compilation unit, but you can no longer separately compile every  module because they may have dependencies on declarations outside their scope. For example

class C;
module top;
C c_h;

There’s no problem if this is compiled as a single file, but if module top were to be compiled separately from the class C definition, it wouldn’t know what the identifier C was supposed to represent, and wouldn’t be able to parse the file.

So the IEEE committee borrowed the concept of packages from VHDL and standardized the concept of a compilation unit. A package allows you to compile definitions in a separate step and import those definitions into another compilation step. Packages create separate namespaces for those definitions as wall as imposing compilation order dependencies.

A compilation unit formalizes a scope that represents what is visible in a compilation step – called $unit in SystemVerilog. If you have a design that is compiled as a single compilation unit, there is really no conceptual difference between $unit and $root. However, once you have a design with multiple compilation units, then $unit represents the top level of each compilation unit, and there is nothing in $root except for the implicitly instantiated module instances. The only time you need to use $root or $unit is when a local name in the current scope hides a name in a higher level scope. For example

Compilation unit 1

function void print;
module mod1;
  mod2 m2();

 function void print;
  initial $unit::print(); // prints “comp1”
          //print() would print “mod1”)

Compilation unit 2

function void print;
module mod2;
  mod3 mod1(); // same name as top-level module
  function void print;
  initial $root.mod1.print(); // print “mod1”
          // mod1.print() would print “mod3”
module mod3;
 function void print;

This example prints “comp1” and “mod1” in either order. Note that there is no way for compilation unit 1 to directly refer to anything in compilation unit 2, or the other way around.

I hope this clears up some of the confusion between $root and $unit in SystemVerilog.

Dave Rich


11 September, 2009

I have lots of blog entries about 95% ready to publish. This entry is from an e-mail I wrote a few months ago when somebody asked about SystemVerilog coding guidelines. I thought it would make a good article. It’s been sitting as a draft because I always have trouble finding the right title or opening words to catch people’s attention. I couldn’t come up with anything better, so here it is: SystemVerilog Coding Guidelines

My official position about this is that you pick a style and stick to it.

Which style you pick is far less important than developing the required culture that will follow it. People can spend hours arguing over the merits of camelCaps versus under_score, but once the decision is made, people on the project need to document it, adhere to it, and police it. Otherwise you’re just wasting everyone’s time.

Why are coding guidelines so important? So someone else can read your code, or maybe you can read your own code six months after you wrote it. Good comments and documentation are an import part, but you still need to be able to read the code.

This concept of reading someone else’s code without understanding the underlying conventions reminds me of the classic short article “Meihem In Ce Klasrum” by Dolton Edwards. The article was written as a satirical response to George Bernhard Shaw’s final wishes to simplify the English language. See how easy the last paragraph is to read once you understand the new writing conventions. Reading your code should be just like that (although not as cryptic).

My advice would be to pick a style from industry and adapt it to fit SystemVerilog. For example, from Google: or from the Free Software Foundation:

All of these rules for naming conventions, indentation, and file organization still apply to SystemVerilog. Additional rules would be for unsupported constructs, which should be in the release notes for the tool(s) you are using. Finally, there are a few constructs to avoid, like program blocks and wildcard indexes for associative arrays, most of which might be worth hours of debate, each.

Dave Rich

, ,

7 July, 2009

I’ve been around simulation and synthesis languages for a while; back when you needed an NDA to see the Verilog LRM, and again with SUPERLOG, the predecessor to SystemVerilog.  It’s easy for those like me to get caught up in the features of the language and forget that any programming language is just a tool. With any technology, people pick the tools they think will get the job finished most effectively. Tools evolve to meet the challenges and requirements of their users. Verilog and VHDL have clearly evolved to become the prevailing languages for hardware design.

But before the language wars came the methodology wars. At the time when Verilog and VHDL were being introduced in the late 1980s, most hardware design was by schematic gale-level entry. We would come to our clients with our simulators and synthesis tools and try to change their design methodology by writing RTL. They would bring their best engineers to compete with our tools – and the engineer would always win by producing a design with better area and timing! However, once the productivity of synthesizing large designs with practical quality of results prevailed over the manual effort, the methodology shift was an easier sell.

Fast forward a decade – although designs have increased exponentially as predicted by Moore’s Law, RTL design has not changed in any significant way during that time. Why? Because it takes the same number of lines of RTL code to write an 8-bit adder as it does a 64-bit adder. :) OK, so that’s an over-simplification, but number of lines of RTL code written by a single design engineer has remained manageable. However, for every registered bit added to the design, the state space doubles, and the transition space for testing all permutations of the state space quadruples. Again that’s a simplification, but the correct order of magnitude.

During that period, a number of technologies have addressed the increasing complexities of verification, such as constrained random generation, coverage driven verification, and object-oriented programming. These technologies require a change in verification methodology from writing a linear set of test patterns.

Success in captivating verification engineers to these new methodologies is taking the same path of those earlier design engineers. A single verification engineer may find a single test to exercise a specific piece of functionally much quicker than it takes a constrained random test to reach that same function. But eventually, a constrained random test will exercise more functionality faster than an engineer can write individual tests. Functional Coverage fits into Constrained Random generation to help measure the quality of your tests by telling you if your constraints are working to exercise the functionality you are required to hit.  It takes the randomness out of Constrained Random generation.

Since the verification environment is more software design than hardware, Object-Oriented Programming is a technology that helps you write re-usable code, which in turn keeps your verification code manageable.

SystemVerilog has become the prevailing language that incorporates all these technologies. But does that mean the need for writing directed tests goes away. No. Designers still layout transistors or gates by hand where it’s critical to their project. Verification engineers should use the technology that is best suited to verify their design.

Firmware tests will continue to be written in C/C++ and SystemVerilog’s DPI can help link C based tests to the RTL. SystemC has become the prevailing modeling language for DSP and algorithmic based design. Most simulation tools can seamlessly link those models to RTL to either drive the test or compare results.

So next time you’re thinking about which language to choose to verify your design, step back and think about the methodology first.

Dave Rich

, , ,

7 May, 2009

That’s a frequent SystemVerilog question I’m asked (and asked).

Program blocks came directly from donation of the Vera language to SystemVerilog by Synopsys , and try to mimic the scheduling semantics that a PLI application has interacting with a Verilog simulator.  So coming from a Vera background, program blocks make perfect sense and do help people transitioning from Vera to SV. But looking at SV from scratch, they are just extra language baggage.

Who would have ever thought we’d be having language wars within the same language! :eek:

As far as I can tell, a program block by itself only addresses two race conditions between the testbench and DUT, both of which are covered by using a clocking block by itself.

  1. Erroneous use of blocking assignments for sequential logic. You have a race within your DUT regardless of the race between your testbench and DUT.
  2. Erroneous use of non-blocking assignments in combinational gated clock logic. You may have a race within your DUT regardless of the race between your testbench and DUT.

As a user, if you don’t understand why these create races within your DUT, you’re going to have the same races within your testbench, and there’s nothing a program block does that prevent races within your testbench. There lays the false sense of security of having a race-free testbench.

Using a clocking block by itself takes care of the same testbench to DUT races that a program block addresses, plus it takes care of the races caused by non-zero delay skews introduced by gate-level propagation. It does this by the use of the input skews for sampling and output skews for driving.

Now, in addition to the false sense of security, and the redundancy with clocking blocks, here are some additional reasons why I don’t recommend using program blocks

  1. If you have legacy Verilog testbench code, sometimes you want to share legacy BFM tasks by having your “class” based testbench call those BFM tasks. You’re going to run into nasty timing problems if that task was designed to be scheduled in the active region, and now is scheduled in the re-active. Sampling will be off by a clock cycle. You’ll have even nastier problems if some tasks are called from a program block, and other are still called from a module.
  2. One person’s Design IP is another person’s Verification IP. At the system level (ESL), there is less of a distinction between models written to represent higher level abstractions of the design, versus part of a testbench. You can’t have differences in scheduling just because one time it’s called from a program, and another time it’s called from a module. Same problem with C code called from a program block or module.
  3. Unless you’re an experienced Vera user, there is the unexpected surprise that your simulation exits immediately after the thread in your program block ends. Again this is an issue with mixing legacy testbenches, or mixed-language testbenches.
  4. Most advanced users can barely understand the scheduling semantics of SystemVerilog even without using program blocks. Why introduce unnecessary complexity. Many other enviroments, like SystemC and VHDL have been in production for years without needing the kind of scheduling semantics the program block introduces. Quick quiz: How can you get an assertion pass and fail in the same time slot?

The SystemVerilog language has had many hands involved with its development, including yours and mine. I’m not dismissing anyone efforts, but sometimes you have to take a step back and realize how bloated the language has become. Just because some feature exists in the LRM doesn’t justify that it needs to be used. Let me tell you about virtual interfaces… :evil:

Dave Rich