Attributes for Enumerated Types
Proposal Editing Information
- Who Updates: JimLewis, PatrickLehmann, <Add YourName >, ...
- Date Proposed: 2012-08-17
- Date Last Updated: 2016-12-29
- Priority:
- Complexity:
- Focus: Testbench and Synthesis
Requirement Summary
Additional attributes for enumerated types
Related and/or Competing Issues: none
Proposal
Basic Capability
It would be appropriate to allow these to apply to objects, however,
in the "Basic" part of this proposal, I am only applying the prefix to
types and subtypes since this is consistend with LRM 16.2.2.
I would like these to apply to:
Prefix: Any prefix T that is appropriate for a discrete or physical type or subtype,
or an object with a discrete or physical type or subtype,
or an alias thereof.
However, many of the other attributes in the LRM that operate on subtypes are of the form:
Prefix: Any discrete or physical type or subtype T.
We need consistency, so I am limiting the current proposal to the existing LRM text.
T'LENGTH
Kind: function.
Prefix: Any discrete or physical type or subtype T.
Result type: universal_integer.
Result: T'LENGTH = T'POS(T'HIGH) - T'POS(T'LOW) + 1
T'POS_RANGE
Kind: range.
Prefix: Any discrete or physical type or subtype T.
Result type: Type integer.
Result: For an ascending 0 to T'POS(T'RIGHT)
type state_type is (S0, S1, S2) ;
signal state : state_type ;
...
state_type'range = S0 to S2 -- see extended proposal
state_type'pos_range = 0 to 2
Do we really need 'pos_range? The proposal adds 'LENGTH and
we know that for enumerated types the range is 0 to state_type'LENGTH.
Furthermore, the Extended capability adds 'RANGE. For integer based values,
'RANGE and 'POS_RANGE produce the same range.
Extended capability
<Brent Hayhoe> suggested the supporting T'RANGE as follows:
P'RANGE
Kind: range.
Prefix: Any prefix P that is appropriate for an object with a discrete or physical type or subtype T,
or an alias thereof, or
a discrete or physical type or subtype T.
. . .
If we do this, then we would need to support scalar_object'range, as otherwise, it
would be confusing that arrays support array_object'range. However, if we do this,
then we also need to add attributes for all objects of a scalar type.
type state_type is (S0, S1, S2) ;
signal state : state_type ;
...
state_type'range = S0 to S2
state_type'pos_range = 0 to 2
state_type'image(state) ;
state'image ;
Similarly for integers:
subtype int10 is integer range 1 to 10 ;
signal A : int10 ;
signal B : integer range 1 to 10 ;
. . .
int10'range = 1 to 10 -- subtype
A'range = 1 to 10 -- object
B'range = 1 to 10 -- object
Note the only reason to do T'Range is for consistency (and there is alot of value to this). We already have
O'subtype which does something similar (and more). Also for any discrete type,
type'range is the same as using the type name so if this is not expanded to include objects, there is no reason for it.
Expand where appropriate Type attributes to objects having that type.
For example, with T'POS_RANGE, change the prefix to:
Prefix: Any prefix T that is appropriate for a discrete or physical type or
subtype, or an object with a discrete or physical type or subtype, or an
alias thereof.
We would need to review LRM section 16.2.2 for attributes for which
it is appropriate to expand to objects. Some, such as type'val(integer)
may be confusing if they are applied to a object name.
Advanced capability
T'SERIALIZE(value : T)
Kind: function.
Prefix: Converts an enum value into a commonly exchangeable binary representation.
Parameter 'value': Value that is to be serialized.
Result type: Type STD_LOGIC_VECTOR.
Result: Binary representation of value
T'DESERIALIZE(value : STD_LOGIC_VECTOR)
Kind: function.
Prefix: Converts a binary representation of an enum value into a concrete enum member.
Parameter 'value': Value that is to be deserialized.
Result type: Type T.
Result: Enum member of it's binary representation
Use Models
Serialization and de-serialization of enumerated types into binary representations is a common use case in simulation and synthesis of HDL code. While VHDL does not support type generics to describe generic RAMs, FIFOs or cross-clock synchronizers. A 'serialization' functionality is needed to convert enum values into a common binary representation. Such converted values (e.g. of type STD_LOGIC_VECTOR) can be passed to a FIFO. Later it's also possible to convert the STD_LOGIC_VECTOR value back to an enum value.
The knowledge of the required bit count in the resulting bit vector, is a major step in enum value serialization into binary representations. Currently the count of declared enum members can only be retrieved by
T'POS(T'RIGHT)+1
or
T'POS(T'HIGH)+1
. An initial step towards serialization is a VHDL attribute to get the count of enum members. Such an attribute could be named
'LENGTH
- as known from array types - or
'COUNT
to state the difference between array types and scalar types.
Some code snippets, which are used by the next examples:
type T_STATUS is (
STATUS_IDLE,
STATUS_TRANSMITTING,
STATUS_COMPLETE,
STATUS_ERROR
);
function log2(value : NATURAL) return POSITIVE; -- returns log2 of value
function str_len(value : STRING) return NATURAL; -- returns the length of a NUL terminated string
function str_trim(value : STRING) return STRING; -- returns a trimmed string without trailing NUL characters
Use Case 1 - Serializing enum values to STD_LOGIC_VECTOR and vice versa for using with generic VHDL components
At the moment it's not possible to store enum vales in a generic FIFO or RAM. Here are several solutions for this scenario:
- give up on using enums
- implement a FIFO or RAM for each enum type
- convert enum values to STD_LOGIC_VECTOR and vice versa
- use type generics (see RyanHinton comment in General Comments)
The latter solution needs one conversion function per type and direction.
function serializeStatus(value : T_STATUS) return STD_LOGIC_VECTOR is
begin
return std_logic_vector(to_unsigned(T_STATUS'pos(value), log2(T_STATUS'pos(T_STATUS'high) + 1)));
end function;
function deserializeStatus(slv : STD_LOGIC_VECTOR) return T_STATUS is
begin
if (to_integer(unsigned(slv)) <= T_STATUS'pos(T_STATUS'high)) then
return T_STATUS'val(to_integer(unsigned(slv)));
else
return STATUS_ERROR;
end if;
end function;
This could be shortened to this, if enum types support
'COUNT
:
function serializeStatus(value : T_STATUS) return STD_LOGIC_VECTOR is
begin
return std_logic_vector(to_unsigned(T_STATUS'pos(value), log2(T_STATUS'count)));
end function;
function deserializeStatus(slv : STD_LOGIC_VECTOR) return T_STATUS is
begin
if (to_integer(unsigned(slv)) < T_STATUS'count) then
return T_STATUS'val(to_integer(unsigned(slv)));
else
return STATUS_ERROR;
end if;
end function;
All these function could be spared if enum types support a
'SERIALZE
and a
'DESERIALZE
attribute:
signal Status : T_STATUS;
signal Debugger_Data : STD_LOGIC_VECTOR(15 downto 0);
Debugger_Data(1 downto 0) <= serializeStatus(Status)
Or shorter with support for
'serialize
:
Debugger_Data(1 downto 0) <= T_STATUS'serialize(Status);
Use Case 2 - Exporting/writing enum values and member names into files for external tools
Some simulation and synthesis tools support I/O functions to read and write files from the host's file system. While this feature is massively used in simulation environments it's rarely used in synthesis environments. Synthesis tools generate large reports, which include state encodings for enum based FSMs. But it's not common to report other encodings for e.g. enum based signals. There are several solutions to this scenario:
- give up on using enums
- force a specific enum encoding by using a vendor specific encoding attribute
- export enum values and it's internal value (position) into a string representation
These string representations can be read back by external tools like waveform viewers or debuggers to show captured signals with it's member name instead of it's binary representation.
A simple export format could be:
- one member per line
- separated by semi-colons
Example of a simple format:
STATUS_IDLE;STATUS_TRANSMITTING;STATUS_COMPLETE;STATUS_ERROR;
procedure str_append(StringBuffer : inout STRING; value : STRING) is
constant BufferEnd : POSITIVE := str_len(StringBuffer);
begin
StringBuffer(BufferEnd + 1 to BufferEnd + str_len(value)) := value;
end procedure;
function encodeStatus return string is
variable StringBuffer : STRING(1 to (64 * T_STATUS'count)) := (others => NUL);
begin
for member in T_STATUS loop
str_append(StringBuffer, (T_STATUS'image(member) & ';');
end loop;
return str_trim(StringBuffer);
end function;
A external script can transform this enum member list into other file formats like
ChipScope 's token files (*.tok) or GTKwaves data format filter files. It's even possible to export these file formats directly. An advantage is to generate up-to-date encoding files for external tools without complex synthesis report parsing.
Questions
- <Brent Hayhoe> 2015-06-30 - Do we really want T'RANGE to return an integer range? Should it not return the enumerated range T'LEFT to T'RIGHT? For example, in Std_logic_1164 we already use the range keyword to define subtypes:
-------------------------------------------------------------------
-- common subtypes
-------------------------------------------------------------------
subtype X01 is resolved STD_ULOGIC range 'X' to '1'; -- ('X','0','1')
subtype X01Z is resolved STD_ULOGIC range 'X' to 'Z'; -- ('X','0','1','Z')
subtype UX01 is resolved STD_ULOGIC range 'U' to '1'; -- ('U','X','0','1')
subtype UX01Z is resolved STD_ULOGIC range 'U' to 'Z'; -- ('U','X','0','1','Z')
I think that we should match T'range to return the base type range, in the same manner as it is used in defining a subtype.
</Brent Hayhoe>
- Main.JimLewis - 2016-12-30 - In response to Brent's suggestion to modify 'Range to return the range of the type. For a type, you don't need this as the "the range of a type" is a subtype and you can use the subtype just like you use array_object'range (see my example below). If we expanded these attributes (in a consistent fashion with array attributes) and applied this to an object of a type, then 'Range provides something useful. If we want 'Range on an object, then we need to modify all of the attributes for types so they apply to objects as well - this is not necessarily a bad thing. Hence, I will take the action to rename the attribute I want to 'POS_RANGE. Going further, to support my claim about types/subtypes, try the following:
entity for_subtype is
end for_subtype ;
use std.textio.all ;
architecture test of for_subtype is
subtype R_T_RANGE is integer range 1 to 10 ;
begin
process
begin
for i in R_T_RANGE loop
write(std.textio.OUTPUT, integer'image(i) & LF ) ;
end loop ;
wait ;
end process ;
end test ;
- <Brent Hayhoe> 2015-06-30 - Prefixes should be 'any enumerated type or subtype T' </Brent Hayhoe>
- JimLewis 2016-12-30: Yes. Just updated it to something appropriate (borrowing LRM text from other attributes)
- PatrickLehmann - 2015-06-26: Use name T'COUNT instead of T'LENGTH:
- JimLewis 2016-12-30: No. We need something that is intuitively related to other VHDL attributes and this overloads the existing attribute for arrays. I did rename the RANGE to POS_RANGE to allow for future changes. Perhaps this should be POS_LENGTH?
- The extension is trivial, but where and how would this be used? -- ErnstChristen - 2015-01-27
- <Brent Hayhoe> 2015-06-30 - I think 'serialize' and 'deserialize' are application specific and should remain as functions in an (open source?) package. The problem here is not so much language design deficiency as a limitation in synthesis. We could build a FIFO model to handle enumerated types and would work in simulation, but would a synthesis tool be able to map it to Std_Logic. My guess is that it will complain at the compilation stage? </Brent Hayhoe>
General Comments
(
RyanHinton - 2016-12-29:) I agree that
'range
and
'length
are easy, and I agree with Brent's amendment to
'range
. The risk I see with
'serialize
and
'deserialize
is that your simulator likely won't implement them the same way your synthesizer encodes them. What are you really trying to accomplish?
Regarding writing a list of enum values to file, you showed how it's already possible. For working with external tools, it seems like you're going to need a customized format for each tool. (You mentioned the
ChipScope .TOK
format and GTKwaves filter file format.) I don't think these formats should be standardized. With some kind of type
annotation similar to that being proposed for array type generics, this procedure could be written once for each desired file format, and instantiated for each desired enumerated type.
Regarding RAMs, the synthesis tool we use supports inferring a RAM (which can be used as a FIFO) where the element type is enumerated, or a record, or another array, or whatever. (Haven't tried access types yet....) This is legal in VHDL-87, and handles the case of an inferred FIFO inside an architecture.
Our synthesis tool also supports type generics (as far as we've tested...). So we're upgrading our standard FIFO components to take a type generic, use this type for the
DIN
and
DOUT
ports, and create an array type with this type for an element internally for the RAM. If your synthesis tool supports VHDL-2008, this covers the FIFO use case.
Is there another use case you have for
'serialize
? (
/Main.RyanHinton)
Supporters
--
Brent Hayhoe - 2015-06-30 (with the provisos of
range based on base enumerated type and that
serialize and
deserialize attributes are not approved.
--
PatrickLehmann - 2015-06-26
Add your signature here to indicate your support for the proposal