The Return of `timescale
If you thought that timescale didn’t matter in SystemVerilog – think again!
And truth be told – I thought that timescale was obviated with SystemVerilog’s new features – and I’m having to think again!
So I had thought that we had finally rid ourselves of the `timescale albatross with SystemVerilog.
If you don’t remember the (old) issue here’s a refresher:
`timescale is a compiler directive used to configure the units and precision of a # delay in Verilog. The syntax is something like this:
`timescale 1ns / 10ps
which means that the time unit of a delay is 1ns and the precision is 10ps (or two decimals of precision in ns as x.xx ns).
Since a delay in Verilog doesn’t have units it relies on the timescale to determine how long the delay will last in simulation.
No problem, right?
Here’s the issue: a `timescale directive has file scope and is “sticky” – once a `timescale is found while compiling everything in that file and following files has that timeunit and timeprecision value until the next `timescale is encountered.
And that was the issue – if you didn’t include a `timescale in your file then you relied on compilation order to get your timescale. If the compilation order changed or the upstream file changed it’s unit then all of a sudden your delays were wrong.
Introducing SystemVerilog:
SystemVerilog solved this problem in two ways:
- delays now have units! So I no longer rely on the timescale time unit to determine how long my delay is. Instead of #1; I now say #1ns.
- introduction of timeunit and timeprecision keywords – these allow the specification of a timescale within the declaration body of a module, interface, or package; no longer do we rely on a `timescale compiler directive.
Unfortunately the problem wasn’t solved…
While delays can be specified for units – effectively obviating the need for a timescale – there’s a (minor) catch.
If a delay is specified that is more precise than a previously declared timescale then it is rounded to match the timescale precision.
Example:
`timescale 1ns / 100ps ... #1.75ns // rounded to 1.8ns
But that’s a minor hiccup – compared to this:
The LRM doesn’t explicitly define what happens when the timeunits aren’t aligned. It seems that when a time (or realtime) typed variable is passed from one timescale domain to another the units are CHANGED to the destination timescale.
This is crazy. If I define a realtime variable in a package with a timescale and then pass that variable to a function that is *defined* in another package (or file) timescale the time *units* are changed to match the destination timescale!
What does that mean?
It means that if I have 1.75ns in a 1ns/1ps timescale and pass it to a class declared in a 1ms/1us timescale that the time will be changed to 1.75ms.
An example follows – after the break.
Example: (from the code posted here)
package pkg2; timeunit 1us; timeprecision 1ns; class myclass7; function new(); $printtimescale; endfunction: new task dodelay(realtime delay); realtime predelay; realtime postdelay; realtime elapsed; predelay = $realtime; #delay; postdelay = $realtime; elapsed = postdelay - predelay; $display("Delay = %t, Actual Delay = %t, Pre = %t, Post = %t", delay, elapsed, predelay, postdelay); $printtimescale; endtask:dodelay endclass: myclass7 endpackage: pkg2 program main; timeunit 1ns; timeprecision 100ps; import pkg2::*; initial begin:testsequence realtime delay; realtime predelay; realtime postdelay; realtime elapsed; myclass7 mc7; $timeformat(-9, // units_number: -9 is ns 5, // precision_number: number of ints after decimal " ns", // suffix_string: display this suffix after showing realtime value 10 // minimum_field_width: ); $display("Construct and show timescale"); $printtimescale; mc7 = new(); $display("Delay and show timescale"); delay = 2.75ns; predelay = $realtime; #delay; postdelay = $realtime; elapsed = postdelay - predelay; $display("Delay = %t, Actual Delay = %t, Pre = %t, Post = %t", delay, elapsed, predelay, postdelay); mc7.dodelay(2.75ns); $finish; end:testsequence endprogram:main
Output:
Construct and show timescale TimeScale of main is 1 ns / 100 ps TimeScale of pkg2 is 1 us / 1 ns Delay and show timescale Delay = 2.75000 ns, Actual Delay = 2.80000 ns, Pre = 0.00000 ns, Post = 2.80000 ns TimeScale of $unit is 1 ms / 100 us Delay = 2750.00000 ns, Actual Delay = 2750.00000 ns, Pre = 16800003.80275 ns, Post = 16802753.80275 ns TimeScale of pkg2 is 1 us / 1 ns
So units only have meaning within the timescale where the delay is defined. If you pass that realtime (or time) variable to a scope with different timeunits then you won’t be getting the time you expected. And the fix? Apparently you’ll need to make sure that everything in your design has the same timeunits and precision! Or, at a minimum, ensure that anywhere you are passing time/realtime variables that you are passing them between scopes with the same timeunit.
This is something that should be quickly corrected in the LRM and simulators.
If you use a simulator other than VCS – please give the posted example a try. Let me know if this issue is in other simulators as well.
Complete example with makefile – here.
Another issue – classes and timescale:
A task in a class can have delays – but the LRM doesn’t define how delays are handled in a class. It appears that the only way to set the timescale of a class is with the `timescale compiler directive within the file that contains the class. Further, some classes (like the timer classes provided here) the only delays are specified at construction. (The IDV SystemVerilog Timers have only one # delay – the countdown time. This delay is specified, with units, on construction of the timer. This delay needs to be only as precise as the user constructing the timer requires.) Meaning that the timescale shouldn’t be attached to the class – but to the object that is the constructed class. In this case it would be preferred that the timescale should be inherited from the constructing parent class / program / module. In the posted example you’ll see that I tried to set the timeunit/timescale of the class at construction with little luck. If you know how to do this – please let me know.
So guess what: `timescale is back! And with a vengeance! Don’t be lured into thinking your units are being adhered to…
-`dang
June 23rd, 2009 at 1:17 am
Competitor Simulator 1:
# Construct and show timescale
# Time scale of (main) is 1ns / 100ps
# Time scale of (pkg2) is 1us / 1ns
# Delay and show timescale
# Delay = 2.80000 ns, Actual Delay = 2.80000 ns, Pre = 0.00000 ns, Post = 2.80000 ns
# Delay = 2800.00000 ns, Actual Delay = 2800.00000 ns, Pre = 2.80000 ns, Post = 2802.80000 ns
# Time scale of (pkg2) is 1us / 1ns
Competitor Simulator 2:
Construct and show timescale
Time scale of (main) is 1ns / 100ps
Time scale of (pkg2) is 1us / 1ns
Delay and show timescale
Delay = 2.80000 ns, Actual Delay = 2.80000 ns, Pre = 0.00000 ns, Post = 2.80000 ns
Delay = 2800.00000 ns, Actual Delay = 2800.00000 ns, Pre = 2.80000 ns, Post = 2802.80000 ns
Time scale of (pkg2) is 1us / 1ns
June 23rd, 2009 at 10:52 pm
@hempi
Thanks for the reply!
It looks like all of the (major) simulators have the same behavior: units are converted to the destination scope units.
-Sean
IDV
October 29th, 2009 at 1:05 am
Here is a Mentor statement about this issue:
The issue about communicating time information across module
boundaries is independent of `timescale versus timeunit. I responded
to this issue a while back:
Communicating time values across multiple timescale domains has been a
‘gotcha’ in Verilog since day one. Timescales only apply to scaling of
literal time values, not values of variables or passed arguments.
Therefore, you should always use a literal time value in any
expression involving time to a normalized time unit – 1ns, for example.
–file with timescale/timeunit X
time tv;
tv = 10ns / 1ns; // 10 – regardless of the current timescale
delay_task(tv); // wait 10 ns
delay_task(100ms/1ns); // wait 100ms
————————–
– file with timescale/timeunit Y
task delay_task(time t);
#(1ns * t); // will scale to the correct time delay endtask
Even if the language were enhanced to provide ‘raw’ simulation time
conversion function, both the call site and the callee would have to
perform the conversion and agree that the timeout argument was in ‘raw’
time units. That’s no different in terms of complexity/typing than
agreeing on 1ns, but the latter is already supported in SV.
December 30th, 2009 at 10:07 pm
@Petr
This is similar to the convention that I’ve taken. But rather than pass values as ‘time’ I pass them as either ‘int’ or ‘real’ depending on what I need. I normalize on a per method basis – and comment that method accordingly.
BUT – the SystemVerilog gotcha is a new one! Since I can now apply the units on assignment it’s important to know that the units are applied on assignment in the current timescape and NOT stored with the value.
It sure would be nice if the units were stored independently – such that the time could be passed between timescale domains without any unexpected corruption.
-Sean