Introduction
Hardware description languages present unique challenges when managing data storage and timing. Unlike conventional software languages that rely on stack-based memory allocation, hardware description languages were designed to model physical circuits with static structures. This fundamental difference creates potential pitfalls for verification engineers transitioning from software development backgrounds.
This article examines two critical aspects of SystemVerilog: automatic storage mechanisms for local variables and precise time value specifications. Readers will gain an understanding of why static storage can cause race conditions in testbenches, how the automatic keyword resolves these issues, and the proper methods for specifying time units and precision. Practical examples illustrate correct implementation patterns.
(toc) #title=(Table of Content)
What Is Static vs Automatic Storage in SystemVerilog?
When Verilog was originally developed in the 1980s, its primary objective was hardware description rather than software simulation. Consequently, all language objects received static allocation. Routine arguments and local variables occupied fixed storage locations throughout simulation, unlike stack-based languages such as C or C++.
The Problem with Static Storage in Testbenches
In Verilog-1995, calling a task from multiple locations caused all invocations to share the same static storage for local variables. Different threads overwrote each other's values unpredictably. Consider a memory monitoring task:
- First call begins waiting for a specific memory address
- Second call starts before the first completes
- Second call overwrites the address argument
- First call now monitors the wrong address
This behavior severely limited the ability to create complex testbenches with reusable routine libraries.
The Automatic Keyword Solution
Verilog-2001 introduced the ability to specify that tasks, functions, and modules use automatic storage. When a routine uses automatic storage, the simulator allocates local variables on a stack, providing each invocation with its own independent copy.
In SystemVerilog, routines default to static storage for both modules and program blocks. Best practice requires making program blocks automatic by placing the automatic keyword in the program statement.
Example: Automatic program block declaration
program automatic test;
task wait_for_mem(input [31:0] addr, expect_data,
output success);
while (bus.addr !== addr)
@(bus.addr);
success = (bus.data == expect_data);
endtask
endprogram
Without the automatic modifier, concurrent calls to wait_for_mem would overwrite the addr and expect_data arguments. With automatic storage, each call maintains separate copies.
Variable Initialization Pitfalls
A related issue occurs when initializing local variables within declarations. In static storage contexts, the variable initializes at simulation start rather than when execution enters the containing block.
The Static Initialization Bug
program initialization; // Buggy version
task check_bus;
repeat (5) @(posedge clock);
if (bus_cmd == 'READ) begin
reg [7:0] local_addr = addr << 2; // Bug
$display("Local Addr = %h", local_addr);
end
endtask
endprogram
In this example, local_addr initializes at simulation time zero, not when the begin...end block executes. The solution requires declaring the program as automatic:
program automatic initialization; // Correct version
task check_bus;
repeat (5) @(posedge clock);
if (bus_cmd == 'READ) begin
reg [7:0] local_addr = addr << 2; // Now correct
$display("Local Addr = %h", local_addr);
end
endtask
endprogram
Time Values and Precision in SystemVerilog
Accurate time specification is essential for hardware verification. SystemVerilog provides several constructs for unambiguous time value definition.
Time Units and Precision Declarations
Traditional Verilog relied on the `` timescale compiler directive, which required compiling files in specific order to ensure proper scaling and precision. SystemVerilog introduced timeunit and timeprecision declarations that eliminate this ambiguity.
Each module containing delays should include these declarations:
module timing;
timeunit 1ns;
timeprecision 1ps;
// Module contents
endmodule
Time Literals
SystemVerilog allows explicit time value specification with units:
0.1ns– one-tenth of a nanosecond20ps– twenty picoseconds2ns– two nanoseconds41ps– forty-one picoseconds
Using $timeformat and $realtime
The $timeformat system task controls time value display formatting:
module timing;
timeunit 1ns;
timeprecision 1ps;
initial begin
$timeformat(-9, 3, "ns", 8);
#1 $display("@%t", $realtime); // @1.000ns
#2ns $display("@%t", $realtime); // @3.000ns
#0.1ns $display("@%t", $realtime); // @3.100ns
#41ps $display("@%t", $realtime); // @3.141ns
end
endmodule
The $timeformat parameters specify:
- Unit number (-9 represents nanoseconds)
- Precision digits (3 decimal places)
- Unit string ("ns")
- Minimum field width (8 characters)
Best Practices for Automatic Storage
| Practice | Recommendation |
|---|---|
| Program blocks | Always declare as automatic |
| Task arguments | Use automatic storage for concurrent calls |
| Variable initialization | Avoid non-constant initializers in static blocks |
| Time specification | Use timeunit and timeprecision over `timescale |
Conclusion
The evolution from static to automatic storage in SystemVerilog represents a significant improvement for verification engineering. By understanding when static allocation causes race conditions and applying the automatic keyword appropriately, engineers can build more reliable and concurrent testbenches. Similarly, explicit time unit and precision declarations eliminate ambiguity in delay specifications, making code more portable and maintainable. As hardware verification complexity continues to increase, proper use of these language features becomes essential for robust testbench development.