truncating time in VHDL
A physical literal is comprised of an abstract literal and a unit with at least one separator. The abstract literal, here a decimal literal is either of type universal integer or universal real depending on the presence of a (decimal) point. For an initial value expression of a variable of type TIME that would occur during elaboration.
Note that t1 85.84999999999999 ms got rounded up to 85.84 ms when evaluated. If you trimmed two of the nines off the right end of the fraction part limiting expressing time to an accuracy of femtoseconds there'd be no rounding here.
The mantissa of an IEEE 64 bit floating point value for universal real (supported by all current implementations) is also a limiting factor on accuracy. A 64 bit value of type universal integer (no point) in be more accurate. The standard is carefully worded to make this behavior acceptable.
How can you preserve all the precision possible? Perform truncation textually.
In this answer a modified write procedure is provided capable of specifying the number of digits after the point. It uses the existing write procedure and calls a justify function (introduced in -2008) to provide full features.
It's compatible with the existing write procedure with the extra parameter digits set to 0. Here in it's own library it doesn't share the existing write procedure's name to avoid ambiguity with default values for a different number of parameters when both declarations are made directly visible through use clauses.
use std.textio.all;
package write_time_pkg is
-- write TIME value to a line at an arbitrary precision
procedure write_precision (
l: inout line;
value: in time;
justified: in side := right;
field: in width := 0;
unit: in time := ns;
digits: in natural := 0 -- digits to right of decimal point
);
end package;
package body write_time_pkg is
function zeros (num: natural) return string is
variable rets: string (1 to num) := (others => '0');
begin
return rets;
end function;
function spaces (num: natural) return string is
variable rets: string (1 to num) := (others => ' ');
begin
return rets;
end function;
function justify (
value: string;
justified: side := right;
field: width := 0
) return string is
constant len : width := value'length;
begin
if field <= len then
return value;
else
case justified is
when right =>
return spaces (field - len) & value;
when left =>
return value & spaces (field - len);
end case;
end if;
end function justify;
procedure write_precision (
l: inout line;
value: in time;
justified: in side := right;
field: in width := 0;
unit: in time := ns;
digits: in natural := 0 -- to right of decimal point
) is
variable editbuf: line;
variable point: natural;
variable separator: natural;
begin
if digits = 0 then -- No decimal place precision specified
write (l, value, justified, field, unit); -- package std.textio
return;
else
write (editbuf, VALUE, unit => unit); -- defaults, field = 0
end if;
for i in 1 to editbuf.all'length loop -- find point and units
if editbuf.all(i) = '.' then
point := i;
elsif editbuf.all(i) = ' ' then
separator := i; -- unit immediately following space
exit; -- in string representation
end if;
end loop;
if point = 0 then -- No fraction part present
write (l,
justify (editbuf.all(1 to separator - 1) &
'.' & zeros(digits) & -- added fraction part
editbuf.all(separator to editbuf.all'length), -- unit
justified, field
)
);
elsif separator - 1 - point < digits then -- Not enough fraction part
write (l,
justify (editbuf.all (1 to separator - 1) &
zeros (digits - (separator - 1 - point)) & -- fill
editbuf.all(separator to editbuf.all'length), -- unit
justified, field
)
);
else -- Truncate including to same length
write (l,
justify (editbuf.all (1 to point + digits) &
editbuf.all(separator to editbuf.all'length), -- unit
justified, field
)
);
end if;
deallocate (editbuf);
end procedure write_precision;
end package body;
It's functionality can be demonstrated:
use std.TEXTIO.all;
use work.write_time_pkg.all;
entity truncate is
end entity;
architecture foo of truncate is
constant t1: time := 85.849_999_999_999 ms; -- WAS 85.849_999_999_999_99
-- ms us ns ps fs ms us ns ps fs ?
-- trimmed fraction of fs so evaluation of physical literal doesn't round up
constant t2: time := 1 ms;
constant t3: time := 85.849_999_999_999_99 ms; -- fractions of femtosec
begin
process
variable buf: line;
begin
write_precision (buf, t1, unit => ms, digits => 2);
report LF & HT & "t1 = " & time'image(t1);
report LF & HT & "t1 ms, truncated to 1/100 ms = " & string'(buf.all);
deallocate (buf);
write_precision (buf, t2, unit => ms, digits => 2);
report LF & HT & "t2 = " & time'image(t2);
report LF & HT & "t2 ms, truncated to 1/100 ms = " & string'(buf.all);
deallocate (buf);
write_precision (buf, t3, unit => ms, digits => 4);
report LF & HT & "t3 = " & time'image(t3);
report LF & HT & "t3 ms, truncated to 1/10000 ms = " & string'(buf.all);
deallocate (buf);
write_precision (buf, t1, justified => right, field => 24,
unit => ms, digits => 2);
report LF & HT & "t1 = " & time'image(t1);
report LF & HT & "t1 ms, to 1/100 ms, justified - " & string'(buf.all);
deallocate (buf);
write_precision (buf, t1, justified => right, field => 20,
unit => ms, digits => 0);
report LF & HT & "t1 = " & time'image(t1);
report LF & HT & "t1 ms, std.textio.write justified - " & string'(buf.all);
deallocate (buf);
wait;
end process;
end architecture;
This produces:
/usr/local/bin/ghdl -r truncate
truncate.vhdl:18:9:@0ms:(report note):
t1 = 85849999999999 fs
truncate.vhdl:19:9:@0ms:(report note):
t1 ms, truncated to 1/100 ms = 85.84 ms
truncate.vhdl:22:9:@0ms:(report note):
t2 = 1000000000000 fs
truncate.vhdl:23:9:@0ms:(report note):
t2 ms, truncated to 1/100 ms = 1.00 ms
truncate.vhdl:26:9:@0ms:(report note):
t3 = 85850000000000 fs
truncate.vhdl:27:9:@0ms:(report note):
t3 ms, truncated to 1/10000 ms = 85.8500 ms
truncate.vhdl:31:9:@0ms:(report note):
t1 = 85849999999999 fs
truncate.vhdl:32:9:@0ms:(report note):
t1 ms, to 1/100 ms, justified - 85.84 ms
truncate.vhdl:36:9:@0ms:(report note):
t1 = 85849999999999 fs
truncate.vhdl:37:9:@0ms:(report note):
t1 ms, std.textio.write justified - 85.849999999999 ms
%:
All the added arithmetic is character array string index manipulation in one of three cases.