In this blog post we are going to discuss 3 different cases where coding style can help you debug easier:
2. Method Call Depth
Declarative vs. Sequential Coding
When modeling your testbench you will need to write code that describes time-consuming or complex steps of some intended behavior. This will be considered sequential code.
You will also most likely need code that keeps the current state of your object accessible to other objects, as well as tracking these object states over time. This will be considered declarative code.
While developing your testbench you will find yourself asking quite often: "Should I make this declarative or sequential?" As a general guideline, reducing the number of sequential lines of code and keeping as much information as possible in the declarative code will help you to debug a lot quicker.
Specman has a superb data-browser and step-debugger and it is important to keep this in mind. When debugging code you usually want to see where the testbench and the RTL go out of sync. The common approach would be to fire up your failing simulation and set the breakpoints:
Specman> break on error
Specman> break on gen err
This will make your simulation stop on an error and open up the debugger, highlighting the precise line where the error occurred. In an ideal case, you can already tell from your error description what went wrong. This however is rather rare and you should get acquainted with the current state of your testbench and gather all declarative information you can get through Specman's data browser. This will give you an understanding of where the simulation has headed, and chances that you will understand the error are pretty good if you tracked enough information in your declarative code. If you are fortunate enough you can already resolve the error or at least have a conversation with the person that might have to fix this scenario.
However, there are still quite a few cases where you need to rerun the simulation. Here you will have to dive into the remaining sequential code and do a step-by-step debugging session. Step-debugging is very tedious and will get more cumbersome the more sequential code you have to examine. It gets even more cumbersome if you are relying on a lot of temporary variables inside your methods.
Method Call Depth
While developing sequential code you will be defining and implement a bunch of methods. Crafting methods is usually a straightforward process. In verification you need to think aboiut whether you need a time-consuming method (TCM) or a timeless method. As a general rule, if you are modeling event-based models or checks, then you need a TCM -- otherwise try to stick with a regular, timeless method.
Mostly you will be developing methods for:
Due to this separation of functionality, given by the UVM-e, there is already a given blueprint for how to integrate these methods with each other.
One issue that may come from developing methods is that one may feel tempted to create methods for reusability and hence encapsulate even trivial steps in a method and have that method called, instead of typing out these steps. The problem with debugging code that relies heavily on method calls is that you always step into a new method and lose the scope of your method's callee.
The opposite problem of creating too many methods implements mega-monolithic methods. These kinds of methods are hard to debug and understand as well, since they usually not only carry the context of one specific modeled aspect.
Pre-Calculating if-else Conditions
The essence of code execution is handling conditional branching constructs. Generally there is nothing wrong about simply writing a condition into your if-else actions. However, complex Boolean expressions should be evaluated before entering the expression query. By creating a temporary variable and assigning a Boolean evaluation to it, you will gain two advantages:
To read part 1 of this blog series, click here.