Previous versions of VHDL provided fairly basic forms of binary and text I/O. In this chapter, we describe additions to the I/O and string conversion features provided by VHDL-2008.
In previous versions of VHDL, converting a value to a string required the use of the 'image
attribute or the write
procedures from the textio
package. These options were very limiting. The 'image
attribute was only defined for scalar types, and the write
procedures were only defined for the predefined scalar types and for bit_vector.
VHDL-2008 adds predefined to_string
operations as a flexible alternative for string conversion. As a function, to_string
is overloadable, supports scalar and composite types, and can have multiple parameters. In addition, for all bit-based array types, octal and hexadecimal string conversion functions are defined: to_ostring
and to_hstring
, respectively (see Section 7.1.3).
Example 7.1. Writing messages using to_string
One use of these functions is in assert statements, for example:
assert expected_val = read_val report "Expected Val /= Actual Val." & "Expected = " & to_string(expected_va1) & "Actual = " & to_string(read_val) severity error;
Another use is with VHDL’s built-in write
procedure (not std.textio.write)
as follows:
if expected_val = read_val then err_cnt := err_cnt + 1; write(output, "%%%Error: Expected Val /= Actual Val. " & " Expected = " & to_string(expected_val) & " Actual = " & to_string(read_val) & " at time: " & to_string(now)); end if;
This call to the write
procedure has a similar effect to a sequence of calls to the write
procedures defined in the textio
package, followed by a call to the writeline
procedure. However, it is clearly much more concise.
A basic form of to_string
is predefined with the following signature.
to_string [AType return string]
AType includes all scalar types and single dimensional array types whose element types contain only character literals. The string return value for various types is formed as follows:
For a value of an enumeration type other than character
, if the value is a character literal, to_string
returns the value as a single-element string; otherwise, the function returns the name of the identifier in lower case letters in a string. For example,
to_string(bit'('0'))
returns the string "0"
, and
to_string(file_open_status '(OPEN_OK))
returns the string "open_ok"
For a character
value, to_string
returns the character in a single-element string. Note that this may be different from the result for non-character types. In the case of control-character values, the result is a single-element string containing the control character, not a string containing the name of the control character.
For a one-dimensional array value containing only character literals, to_string
returns a string of the same length as the array containing the element values converted to type character.
For example, to_string(bit_vector'("0110"))
returns the string of characters "0110".
For a value of an integer type, to_string
returns the decimal literal. There is no exponent and no insignificant leading zeros. If the result is negative, the decimal literal is preceded immediately by a minus sign without any intervening space.
For a value of a floating point type, including real, to_string
returns the decimal literal in standard form consisting of a normalized fraction and an exponent in which the sign is present and the “e” is in lowercase. There are no insignificant leading or trailing zeros. (Note that the floating point types referred to here are the abstract numeric types, not the types defined in the new floating-point packages described in Section 8.5.)
For a value of a physical type, to_string
returns the decimal literal as an integer, a space, and the unit name. There is no exponent and no insignificant leading zeros. If the result is negative, the decimal literal is preceded immediately by a minus sign without any intervening space.
The basic to_string
operations handle most of the required cases. However, there are additional predefined forms of to_string
for values of types time
and real.
First, the following predefined forms provide textio-style
formatting:
function to_string ( value: time; unit : time ) return string; function to_string ( value: real; digits: natural ) return string;
The version for type time
formats the value as an integer or real literal in multiples of unit.
In the version for type real, digits
specifies the number of digits that are to appear to the right of the decimal point.
Second, for type real
, the following predefined form of to_string
provides C-style formatting of the value:
function to_string ( value: real; format: string )
return string;
The format
parameter should contain a C-style format specification, such as is used in the C printf
command. For example, assuming the variable x
has the value 52.5:
• to_string (value => x, format => "%f") returns "52.5" • to_string (value => x, format => "%5.2f") returns "52.50" • to_string (value => x, format => "%E") returns "5.250000E+01" • to_string (value => x, format => "%6.2e")returns "5.25e+0lW • to_string (value => (x*10.0), format => "%g")returns "525"
The basic to_string
operations are predefined for the vector types defined in packages std_logic-1 1164
, numeric_bit
and numeric_std
, since the types just contain character elements ('0', 'I', 'L', 'II'
, and so on). However, the fixed-point and floating-point packages described in Chapter 8 overload the basic operations to provide more useful results. In particular, the overloaded versions defined for the fixed-point types ufixed
and sfixed
return a string with a radix point (a '.'
character) at the appropriate position, for example, "100 1.00 10".
The overloaded version for the floating-point type float
returns a string with colon characters between the sign, exponent, and fraction bits, for example, "0:111011:00010001110".
In addition to binary string conversion, VHDL-2008 also adds octal and hexadecimal string conversion functions, to_ostring
and to_hstring
, respectively, for all bit-based array types. The signatures for these functions are:
to_ostring [BitArrayType return string] to_hstring [BitArrayType return string]
BitArrayType includes bit_vector
defined in the package standard, std_ulogic_vector
defined in std_logic_1164, unsigned
and signed
defined in numeric_std
, and unsigned
and signed
defined in numeric_bit.
For these types, characters are implicitly added to the left of the array value to make the length a multiple of 3 (for to_ostring
) or 4 (for to_hstring
), so that complete octal or hexadecimal digits can be formed. The characters are added as follows:
For an array of type bit_vector
, ‘0’ characters are added.
For an array of type std_ulogic_vector, std_logic_vector
, or unsigned
, if the leftmost element of the array is ‘Z’ or ‘XI, then ‘Z’ or ‘XI characters, respectively, are added; otherwise, ‘0’ characters are added.
For an array of type signed
, the characters added are the same as the leftmost element of the array.
For array types based on either std_ulogic
or std_logic
, if all of the elements corresponding to an octal or hexadecimal digit contain ‘Z’ then the resulting character is ‘Z’. Otherwise, to_X01
is applied to the group of elements. If the result contains an ‘XI, the octal or hexadecimal digit is ‘X’. If the result contains only ‘0’ and ‘1’ values, they are converted to a normal octal or hexadecimal digit in upper case.
BitArrayType is supported for the fixed-point types ufixed
and sfixed
defined in the fixed-point package (see Section 8.4). For these types, characters before the radix point are handled as for unsigned
and signed
, respectively. Then the radix point is included in the string. For characters following the radix point, ‘0’ characters are added to the right of the array value to make the length of the fractional part (after the radix point) a multiple of 3 (for to_ostring)
or 4 (for to_hstring).
The fractional part is then converted as for unsigned
values. For values in which the radix-point position lies outside the index range, to_ostring
and to_hstring
extend the value to include the radix point in the result. For example, a to_hstring
operation for the value “10100” with index range 7 down to 3 would result in the string "A0.0"
, corresponding to the binary number 10100000.0. Similarly, a to_hstring
operation for the value “10100” with index range -3 down to -7 would result in “0.28” (0.0010100 in binary).
BitArrayType is also supported for type float
in the floating-point package (see Section 8.5). For this type, characters are added to the left of the array value string to make the length a multiple of 3 (for to_ostring)
or 4 (for to_hstring).
If the left most element of the array is ‘Z’ then ‘Z’ characters are added; otherwise, ‘0’ characters are added.
Note that for consistency, there is also a to_bstring
operation defined for each of the types. However, it is simply an alias for the to_string
function. There are further aliases defined for the operations: to_binary_string, to_octal_string
, and to_hex_string.
Some designers may consider these to be more readable than the shorter function names.
One facility provided by the write operations in the textio
package but not provided by the to_string
functions is the ability to justify a string representation in a field of a given width. This is useful for tabular formatting of output. VHDL-2008 adds a justify
function to the textio
package to provide fixed-width formatting for string values. The function is defined as follows:
function justify ( value : string; justified : side := right; field : width := 0 ) return string;
The value
parameter contains the string value to be formatted, and the justified
and field
parameters are used in the same way as in the write
procedures in textio.
When the field
parameter is greater than the length of value
, the value is justified within the string by adding spaces to the left or right of the result for right, depending on the justified
parameter. If the field
parameter is less than or equal to the length of value
, the value is returned unchanged.
Example 7.2. Tabular formatting of trace output
Suppose we wish to write trace output from a model and have it formatted in fixed-width columns. Each line of output consists of the current simulation time, a 16-bit unsigned
value in hexadecimal format, and an integer counter value. We can write the values using the following write
procedure call:
write( output, justify(to_string(now, ns), width => 10), justify(to_hstring(out_vec), width => 6), justify(to_string(count), width => 10) );
Successive calls might yield the following output:
20 ns XXXX 0 120 ns ZZOO 1 220 ns FFCO 10 320 ns 0000 31
In some applications, we need to create a message string that would be too long to fit on a single line of output. If we are using write
or writeline
operations, we could split the message and write each line with a separate operation. However, VHDL does not guarantee that lines written by different processes during the same simulation cycle will not be interleaved. If we are generating a message using an assert or report statement, we would not want the additional text associated with an assertion violation to be included with each line of the message.
VHDL-2008 allows us to create multiline messages in these cases by using the linefeed character (LF) as a newline character. This interpretation applies to a report string in an assert or report statement and to a string written to a file of type text
using a write, writeline
, or tee
procedure (see Section 7.5). The host operating system translates the LF character to whatever convention is used to represent a new line. For example, a UNIX based system would represent the new line using just the LF character, whereas a Windows system would represent it using a carriage return (CR) followed by a LF.
Example 7.3. Multiline output to a text file
We can use LF characters in a multiline message written to the standard output
text file using a write
procedure call:
write(output, "%%%ERROR data value miscompare." & LF & " Actual value = " & to_hstring(data) & LF & " Expected value = "& to_hstring(expdata) & LF & " at time: " & to_string(now) );
In earlier versions of VHDL, textual I/O operations were limited to values of predefined types, for which read
and write
operations were defined in the standard textio
package. VHDL-2008 broadens the support for textual I/O by adding operations for all of the standard types in their respective packages. It also adds octal and hexadecimal I/O and enhances the string I/O capability.
The basic I/O operations added in each package are:
procedure write ( L : inout line; value : in AType ; justified : in side := right; field : in width := 0 ); procedure read ( L : inout line; value : out AType ; good : out boolean ); procedure read ( L : inout line; value : out AType );
These operations behave in the same way as the corresponding operations for predefined types in the textio
package. The write
operation executes as if the following call to the textio
package write
operation were executed with the to_string
operation (see Section 7.1) applied to the parameter:
write (L, to_string(value), justified, field);
The new write
operations are defined for std_ulogic_vector
and std_logic_vector
in the std_logic_1164
package, for unsigned
and signed
in numeric_std
, for unsigned
and signed
in numeric_bit
, for ufixed
and sfixed
in the fixed-point packages (see Section 8.4), and for float
in the floating-point packages (see Section 8.5).
Each read
procedure skips white space, and then reads std_logic
values until it encounters white space or a non_std_ulogic
value, or until it has read value'length
characters. Underscore characters ("-"
) embedded within the value are skipped, though it is an error if two underscores appear consecutively. The procedure must read enough characters to fill all of the elements of the value
array, so it is an error if a space or an invalid character is encountered before value'length
characters are read. The read
procedures for ufixed
and sfixed
also accept a radix point ("."
) in the input, though it is an error if the radix point is not at the appropriate position. Specifically, the characters before the radix point must fill elements of the value
parameter with non-negative indices, and the characters after the radix point must fill elements with negative indices. An error occurs if the radix point is encountered at a position other than between the characters corresponding to indices 0 and -1. Similarly, the read
procedures for float
accept ":"
and "."
delimiters between the sign, exponent, and fraction parts of the input, though it is an error if they are not at the appropriate positions.
The support for octal and hexadecimal I/O takes the form of the following procedures:
procedure owrite ( L : inout line; value : in AType ; justified : in side := right; field : in width := 0 ); procedure hwrite ( L : inout line; value : in AType ; justified : in side := right; field : in width := 0 ); procedure oread ( L : inout line; value : out AType; good : out boolean ); procedure oread ( L : inout line; value : out AType ); procedure hread ( L : inout line; value : out AType; good : out boolean ); procedure hread ( L: inout line; value : out AType);
These operations also behave in the same way as the corresponding operations for predefined types in the textio
package, but with octal or hexadecimal conversion applied. The owrite
and hwrite
operations execute as if the following calls to the textio
package write
operation were executed with the to_ostring
and to_hstring
operations (see Section 7.1) applied to the parameters:
write (L, to_ostring (value), justified, field); -- owrite write (L, to_hstring (value), justified, field); -- hwrite
The operations are defined for the predefined type bit_vector
, for std_ulogic_vector
and std_logic_vector
defined in the std_logic_1164
package, for unsigned
and signed
in numeric_std
, and for unsigned
and signed
in numeric_bit.
The behavior of the oread
and hread
operations in these cases is as follows. Each operation must read sufficient characters to fill the value
argument, or an error occurs. Since value
need not be a multiple of 3 (for oread)
or 4 (for hread)
in length, the length is rounded up to the nearest multiple of 3 or 4 to determine how many characters to read. Oread (hread)
starts by skipping white space. It then reads octal (hexadecimal) digits until it encounters white space or a non-octal (non-hexadecimal) character other than "-"
, or until it has read sufficient characters to fill the value
argument. Underscore characters embedded within the octal (hexadecimal) value are skipped. Oread
converts each octal digit (0-7) to its 3-bit representation, and hread
converts each hexadecimal digit (0-9, a-f, or A-F) to its 4-bit representation. For array types based on std_ulogic
or std_logic
, the characters ‘XI and ‘Z’ are also permitted. For octal, these characters are repeated 3 times in the result; hence, a ‘Z’ input is expanded to “ZZZ”. Similarly, for hexadecimal, these characters are repeated 4 times in the result; hence, a ‘Z’ input is expanded to “ZZZZ”. If conversion of characters to groups of 3 or 4 elements result in more elements than the length of the value
argument, only the rightmost elements are used. Depending on the values of the discarded elements, an error may occur. If the type of the value
argument is bit_vector, std_ulogic_vector, std_logic_vector
, or unsigned
, an error occurs if any of the discarded elements are ‘1’. For example, an hread
that reads the characters “82” (”10000010’’ in binary) into a 6-bit unsigned
value produces an error, since the two discarded bits are “10”. If the type of the value argument is signed
, an error occurs if the discarded elements are not all the same as the leftmost element used for the value
argument. For example, an hread
that read the characters “7F” into a 6-bit signed
value produces an error, since the two discarded bits are “01”, and the leftmost bit used for value
is ‘1’.
The octal and hexadecimal write and read operations are also defined for types ufixed
and sfixed
in the fixed-point packages (see Section 8.4). The behavior of the oread
and hread
operations in these packages is as follows. Oread
and hread
each reads the value prior to the radix point as described above for unsigned
or signed
(depending on whether the value
parameter is ufixed
or sfixed
, respectively). For the characters following the radix point, oread
and hread
each reads the value as described above for std_ulogic_vector;
however, instead of discarding elements on the left, the operations discard elements on the right. An error occurs if an element discarded on the right is a ‘1’. The radix point may be explicitly included in the input, but an error occurs if it is not at the appropriate position (that is, between the characters corresponding to indices 0 and -
1 of the value
parameter). The radix point may also be omitted, in which case it is assumed at the appropriate position.
Finally, the octal and hexadecimal write and read operations are defined for the type float
in the floating-point packages (see Section 8.5). The behavior of the oread
and hread
operations depends on whether ":"
or "."
delimiters are used in the input to separate the sign, exponent, and fraction parts of a floating-point number. When ":"
delimiters are used (with the input formatted as “S:EEEE:FFFFFFFF”), the sign bit, the exponent, and the fraction are each read as separate octal or hexadecimal values using the same rules as described above for std_ulogic_vector
values. When a delimiter is used (with the input formatted as “SEEEE.FFFFFFFF”), the rules described above for reading ufixed
values are used. The value read before the radix point forms the part of the result comprising the sign and exponent elements, and the value read after the radix point forms the fraction part of the result. When no delimiters are used in the input, the entire float
value is read as a single hexadecimal value as described above for std_ulogic_vector
values.
Note that, for consistency, there are also definitions of binary I/O operations in each of the packages. However, they are simply aliases for the basic operations, defined as follows:
alias bwrite is write [line, AType, side, width]; alias bread is read [line, AType, boolean]; alias bread is read [line, AType ];
In addition to the enhanced I/O operations for binary, octal, and hexadecimal values, VHDL-2008 adds enhanced I/O operations for character strings. The new swrite
procedure in package textio
writes a string value in the same way as the write
procedure overloaded for a string
value parameter. The difference is that there are no other overloaded versions of swrite
, so we do not have to use type qualification when writing a string
literal. We can write a call such as:
swri te(L, "The answer is: ");
Compare this with a call to the write
operation:
write(L, string' ("The answer is: "));
We need to use the type qualification to distinguish the type of the string from other character array types (such as bit_vector)
for which write
is defined. Use of swrite
to write string
literals makes the model much clearer.
The new sread
procedure in package textio
reads string-based tokens. It is defined as follows:
procedure sread ( L : inout line; value : out string; strlen : out natural );
The procedure skips leading white space and then reads consecutive non-white space characters, up to as many as will fit in the value
parameter. The number of characters read is returned in the strlen
parameter. If a white-space character stops reading before value
is filled, the result in strlen
will be less than the length of value.
The remaining unfilled characters in value
are not specified and should not be used. If no valid characters are read (for example, if the input is blank up to the end of the line), the strlen
result value is 0.
The textio
package in previous versions of VHDL provided the file output
for displaying messages to a user-interface. If we also wanted to log the messages in a separate file for subsequent analysis, we had to duplicate the write operations: once to output
and once to the separate file. VHDL-2008 adds a tee
procedure to the textio
package that writes a line both to the file output and to a separate named file. This allows us to avoid replicated write operations. The definition of tee is:
procedure tee (file F : text; L : inout line);
The effect of TEE is the same as the statements:
write (output, L.all & LF); writeline (F, L) ;
Example 7.4. Logging output messages
Suppose we wish to write trace messages to a simulator’s user interface and to log the messages to a file named trace.log. We can do this using calls to tee in place of writeline, as follows:
use std.textio.al1 ; file tracefile : text open write_mode is "trace.log"; variable L : line; . . . swrite(L, justify(to_string(now, ns), field => 10) & " starting operation "); tee(tracefile, L); . . .
VHDL-2008 adds a predefined file flush procedure that requests that the effect of all previous calls to the write procedure for a file be completed. The procedure flush is predefined for all file types as follows:
procedure flush ( file F : FT );
When the flush procedure is called, the file must be opened in write or append mode; otherwise, an error occurs.
One use of flush is to ensure that all outstanding write operations to an external file are completed before read operations are performed on a separate file object associated with the same external file. Another use is to ensure that prompt messages written to a user interface appear before read operations take input from the user interface. It is important to note, however, that the flush operation simply requests that the host operating system complete the outstanding writes. It does not guarantee that the request will be met. In particular, host systems that use distributed network file systems may find it difficult to reliably honor flush requests.