Record Introspection
Proposal Details
Current Situation
VHDL2008 added support for type generics allowing entities to be designed with ports of arbitrary types. The lack of introspection limits the usefulness of type generics when used with records because the entity still needs to know the names of record elements to be able to access/assign them.
Requirement
Enable conversion (implicit or otherwise) between records and vectors (single dimensional array) and therefore unions between two records (or a record and a vector).
- To facilitate this, provide a language/syntax/synthesisable (and also useful for testbenches) mechanism for:
- iterating over the elements of a record in a deterministic order.
- assigning the value of a record element where the name of the element is stored in a variable.
- determining the type of an element (record, scalar, vector, enumeration).
- reaching the type of an object from an instance.
Support abstract register interfaces as either a record or vector
Implementation details
We want to be able to perform the following tasks implicitly or using functions:
- Convert a record to a vector (see universal function to_vector below)
- Convert a vector to a record (see universal function to_record below)
- Determine the equivalent vector length of a record type
Use Cases
Record FIFO
An obvious use case example is a record FIFO implementation whose in/out ports are a record type passed in by type generic. To store the record in the underlying memory implementation it is necessary to convert the record into a vector on write, and from vector back the record on read.
Register File Mapping
An ideal use case example for this proposal is the memory mapping of software accessible registers in a design. Using an I2C controller peripheral as an example:
- A VHDL registers package defines i2c_write_registers_type record and i2c_read_registers_type record
- The I2C controller is instantiated in the SoC passing
- SoC interconnect command/response channel records as a type generics
- The base offset address at which the I2C controller lives on the bus
- Depending on the interconnect type generics, the I2C controller instantiates the appropriate register mapping entity passing
- i2c_write_registers_type as type generic
- 2c_read_registers_type as type generic
- The base offset address
- The register mapper memory maps the records using introspection by converting to a vector and indexing by the address
- A single mapper exists each supported bus (Wishbone, AXI, proprietary, etc.)
- I2C controller can be instantiated in any SoC using any bus interconnect
- Register definition is identical regardless of bus standard
- Memory mapping of the software accessible registers is automatic
- Deterministic and therefore equivalent C structure of the records can be generated
Related Issues:
Pruned from VHDL-2008 effort - has some elements of this proposal
http://www.eda.org/vhdl-200x/vhdl-200x-ft/proposals/ft15_multidim_slices.txt
Code Examples
In the example below, the functions are explicit, though an alternative implementation could provide this functionality implicitly.
entity record_sync_fifo is
generic (
depth : integer := 16;
type record_data_t
);
port (
clk : in std_ulogic;
srst : in std_ulogic;
full : out std_ulogic;
data_in : in record_data_t;
wr_en : in std_ulogic;
empty : out std_ulogic;
data_out : out record_data_t;
rd_en : out std_ulogic
);
end record_sync_fifo;
architecture rtl of record_sync_fifo is
-- Determine the length of a vector, using recursion for nested records
function vector_length_of_record(type record_type) return integer is
variable vlen : integer;
begin
assert is_record(record_type)
report "Attempt to determine the vector length of a type that isn't a record" severity FAILURE;
vlen := 0;
for element in record_type'elements loop
if is_record(element) then
vlen := vlen + vector_length_of_record(element);
elif is_enumeration(element) then
-- Compile time error if enumeration doesn't provide
-- to_vector functions for enumerations
vlen := vlen + element'length;
elif is_vector(element) then
vlen := vlen + element'length;
elif is_scalar(element) then
vlen := vlen + 1;
end if;
end loop;
return vlen;
end function;
constant DATA_T_VECTOR_LENGTH : integer := vector_length_of_record(record_data_t);
-- Convert a record to a vector, using recursion for nested records
function to_vector(instance record_instance) return std_ulogic_vector is
variable len : integer;
variable ptr : integer;
variable vec : std_ulogic_vector(DATA_T_VECTOR_LENGTH-1 downto 0);
begin
assert is_record(record_instance'type)
report "Attemp to determine the vector length of a type that isn't a record" severity FAILURE;
for element in input'elements loop
if is_record(element'type) then
len := vector_length_of_record(element'type);
vec(len-1 downto ptr) := to_vector(element);
elif is_enumeration(element) then
-- Compile time error if enumeration doesn't provide
-- to_vector functions for enumerations
len := element'length;
vec(len-1 downto ptr) := to_vector(element);
elif is_vector(element'type) then
len := element'length;
vec(len-1 downto ptr) := std_ulogic_vector(element);
elif is_scalar(element'type) then
len := 1;
vec(ptr) := std_ulogic(element);
end if;
ptr := ptr + len;
end loop;
return vec;
end function;
-- Convert a vector to a record, using recursion for nested records
function to_record(vec : std_ulogic_vector(DATA_T_VECTOR_LENGTH-1 downto 0), type record_type) return record_type is
variable len : integer;
variable ptr : integer;
variable rec : record_type;
begin
for element in record_type'elements loop
if is_record(element'type) then
len := vector_length_of_record(element'type);
rec'element := to_record(vec(len-1 downto ptr), element'type);
elif is_enumeration(element) then
-- Compile time error if enumeration doesn't provide
-- to_enum functions for enumerations
len := element'length;
rec'element := element'type(vec(len-1 downto ptr));
elif is_vector(element'type) then
len := element'length;
rec'element := vec(len-1 downto ptr);
elif is_scalar(element'type) then
len := 1;
rec'element := vec(ptr);
end if;
ptr := ptr + len;
end loop;
return rec;
end function;
signal vector_in : std_ulogic_vector(vector_length_of_record(record_data_t)-1 downto 0);
signal vector_out : std_ulogic_vector(vector_length_of_record(record_data_t)-1 downto 0);
begin
vector_in <= to_vector(data_in);
data_out <= to_record(vector_out, record_data_t);
i_fifo : entity fifos.vector_sync_fifo
generic map (
length => length
) port map (
clk => clk,
srst => srst,
full => full,
data_in => vector_in,
wr_en => wr_en,
empty => empty,
data_out => vector_out,
rd_en => rd_en
);
end;
Alternatively, using implicit conversion functions:
type state_t is (IDLE, BUSY);
-- This function must be defined because it is not possible to automatically
-- determine how to convert an arbitrary type into a vector
function to_vector(i : state_t) return std_logic_vector is
variable v : std_logic_vector(0 downto 0); begin
if state = IDLE then
v := '0';
else
v := '1';
end if;
return v;
end function;
type my_record_t is record
state : state_t;
some_vec : std_logic_vector(SOME_GENERIC-1 downto 0);
some_record : another_record_t;
end record;
signal my_record : my_record_t;
-- We have some new attribute to determine the size of a vector
signal my_record_vector : std_logic_vector(my_record_t'vlength downto 0);
-- These functions are implicit based on some defined algorithm to convert
-- between a record and vector
my_record_vector <= to_vector(my_record);
my_record <= from_vector(my_record_vector);
Could the conversion of enum s to vectors not be done implicitly via the
'pos
attribute followed by conversion to unsigned vector? The bit-wise representation should not be of interest to the user, hence it wouldn't whether it matched any potential state-machine encoding performed. --
MartinThompson - 2011-07-14
Agree - enumeration to vector could be inferred, although a mechanism to optionally override them as there are situations where the bit-wise representation does matter, for example if it has to match a software interpretation. --
ChrisHiggs - 2014-08-04
Arguments FOR
- Facilitate a higher level of abstraction with many potential use cases
- Memory mapping records as software accessible registers
- Allow creation of universal entities using type generics
- Deterministic conversion of a record to vector makes software integration easier
- Records can be represented as structs in C header files
- Memory mapping of record types can be determined algorithmically
- Allow a single definition of software registers using record types (IP-XACT/SystemRDL/VHDL)
- Defining conversion functions manually has many disadvantages
- Tedious for large records
- Auto-generating conversion functions is error prone
- Have to edit both type definition and conversion function when changing the type
- Non-deterministic ie software must be told how a record is mapped to memory
- Have to ensure VHDL conversion function is identical to software memory mapping
- SystemVerilog introduced support for structs, unions and packed structs
- Structs provide equivalent of VHDL records
- This proposal would enhance VHDL to provide functionality provided by SystemVerilog unions and packed structs
Arguments AGAINST
- Users could explicitly define their own to_vector/to_record conversions for every record type they define
This is possible now, but very inconvenient and if done manually, tedious and error prone. IMHO the need to revert to autogenerating code (and the only sensible way is to auto-generate the to_vector/to_record conversion functions) simply highlights a missing feature in the underlying language --
ChrisHiggs - 2014-08-04
Although a interesting solution to a common problem, there are a number of problems:
- How to deal with state encoding? Mapping abstract types such as enumation to bit-level representation belong to the RTL synthesis domain.
- How to deal with endianness? Assuming that the record type is converted to an N-bit vector, which is accessed via an 8-bit bus, how should the automated conversion deal with multi-byte words?
- Deterministic order of record iterator does not solve the problem that C structs are not guaranteed to be deterministic due to padding and alignment.
- How to deal with sparse memory maps? When dealing with legacy interfaces and due to alignment on word boundaries memory and register maps frequently contain empty bits or words. Manually padding the records to match these specifications is cumbersome.
NB the last three arguments against listed above seem to be more generally referring to the specific use-case rather than the concept, thus I don't want to delve into the details of the example. The fundamental feature is enabling a programatic mechanism for inspecting records. Flattening to a vector is simply one example of using this new mechanism, but there are many other possible uses so don't get too distracted by the use-case. --
ChrisHiggs - 2014-08-04
The VHPI provides inspection of records --
PeterFlake - 2014-12-18
Common C compiler don't reorder members to save padding space. This would break many union types. Record compaction must be explicitly enabled. --
PatrickLehmann - 2016-02-11
General Comments
Biggest difficulty seems to be dealing with enumerated types, any suggestions on how to best deal with this?
Supporters
Add your signature here to indicate your support for the proposal
--
ChrisHiggs - 2011-07-06
--
MartinThompson - 2011-07-14
--
CharlesGardiner - 2011-08-17
--
MatthiasAlles - 2011-10-27
--
DanielKho - 2012-04-26
--
MortenZilmer - 2014-01-27
--
PatrickLehmann - 2016-02-11