Introspection for VHDL
This wiki page relates to the following IEEE 1076 proposals:
I think we should consider to rename "introspection" to "reflection", because other languages
already use this terminology for languages
reflecting on languages entities of itself.
I withdraw my renaming request. =>
Wikipedia:Type_introspection
Languages with reflection capabilities:
- .NET languages: C#, VB.NET, ...
- Java
- Python
- COM/ActiveX components compiled with C++/VB
- XML -> DOM
- ...
Which reflection capabilities are already present in VHDL?
VHDL has several attributes which can be used to reflect on language entities like types,
signals, variables and so on. For example:
- mySignal'length can be used to query the number of bits in a std_logic_vector,
- mySignal'range can be used to iterate over all vector members in a std_logic_vector or
generate a new signal with different type of the same range.
So I think it can be stated that existing reflection capabilities in VHDL are mostly (entirely?)
attribute based. This results a quite handy syntax and does not pollute the list of reserved words.
How do other languages handle reflection capabilities?
.NET language have a set of data structures (classes) and enums to represent all queriable
entities of a language. For example there is a Assembly, a
TypeInfo and a
MethodInfo class.
A
MethodInfo holds all information on a method (attributes, calling convention, parameters,
generic parameters, is_abstract, is_constructor, return_type, ...). It can be retrieved by
accessing an Assembly in a file (e.g. from a DLL file), searched for a type (e.g. class) by
name and that in turn can be searched for a method name. Finally the
MethodInfo can be
used to invoke a method.
How does it correlate to Prop1?
The proposed 'elements attribute (from Prop1) goes into that direction. It introduces an
enumerated type, which holds all record members. As that type is iterable, it can be used
in for/generate loops. Prop1 states that 'elements is an implicit type. I think that's no problem,
because all members of a record are known, while parsing the record declaration. Moreover
a simple implementation could just copy the VHDL syntax and rewrite it from record to
enum declaration. A smarter compiler generates 2 member elements while parsing the
record code: 1) the record member as usual and 2) a new enum member.
In Python-like pseudo-code, how a VHDL compiler could create the implicit type.
I don't want runtime created 'objects'.
newRecord = Record("std_record")
newImplicitEnum = Enum(newRecord.Name + "_members") # how should it be called. if needed for an error message or so.
for recordMember in ASTBranch:
newRecord.AddMember(recordMember.Name, recordMember.Type)
newImplicitEnum.AddMember(recordMember.Name)
newRecord.ImplicitEnumType = newImplicitEnum
CurrentScope.AddType(newRecord)
An example:
type T_MyRecord is
Valid : std_logic;
Data : std_logic_vector(7 downto 0);
SOF : std_logic;
EOF : std_logic;
end record;
The implicit type would be:
type T_MyRecord_Members is
Valid,
Data,
SOF,
EOF
);
As enumerated types are ordered (and the implicit enumerated type has the same member order
as the record type), it can be use to loop. The position 'pos in the enumerated type is equal to the
position in the record => T_MyRecord'members'pos(Valid) = T_MyRecord'pos(Valid)
Defining this enumerated types gives us already the possibility to report all record names:
for index in std_record'elements loop
report "Member " & integer'image(std_record'members'pos(index)) & " Name " & std_record'members'image(index);
end loop;
This results in
Member 0 Name Valid
Member 1 Name Data
Member 2 Name SOF
Member 3 Name EOF
More helper structures and enums:
I think we need further enumerated types, which could also be useful for Jim's "Anonymous
Types on Interfaces" proposal (Prop3), too. Currently VHDL has no mechanism to determine if a
signal/variable is of a given type class/category (scalar, array, enumerated, record, protected, ...).
So my suggestion is to introduce further predefined (not implicit) enumerated types. One in
my mind is a type_class enum (is there a better name?)
Possible problem: some members are keywords....
type type_class is (
scalar,
array,
enumeration,
record,
protected
);
If this enum exists, there would be no need for is_*** functions as proposed by Prop2. A
type class type check can be done by this short example:
if (mySignal'type_class = tc_array) then
-- body
end if;
So for a basic reflection support in VHDL, every type should have a 'type_class attribute.
An alternative implementation could be to define 5 new attributes:
- T'is_scalar
- T'is_array
- T'is_enumeration
- T'is_record
- T'is_protected
But this could not be reused with "Anonymous Types" (see Prop3) (=> type restrictions).
I would prefer the 'type_class solution, because it does not require 5 new attributes and it
does not rely on spicial is_*** functions.
Why did I name it type_class?
The LRM has alread a production rule called entity_class (§ 7.2 or page 484). I thought maybe
it could have a similar name => 'type_class. Martin Zabel suggested to use 'type_kind as
an alternative.
Thinking over it, it could also be 'type'kind.
Other sources: Wikipedia ...
The Wikipedia article on data types has a
heading for "classes of data types", listing:
Another article on
Kind in type theory. => "Kind is the type of a (data) type".
How can we check the type?
Porp2 suggest an atrribute to get the type of an instance which is compared to a type_mark.
if (my_sig'subtype'base = std_logic_vector) then
-- ...
end if;
I like this idea, but I see two possible problems:
- An attribute returns a type_mark. Currently VHDL has no capability to store such an
object in a variable or whatever.
- In a regular rexpression, two type_marks are compared. Currently VHDL does not
support to have type_marks in expressions.
Let's look to other languages: Some languges represent types as instances of Type (class).
Others have special operator to compare types => is, typeof, instanceof, ... and then there
are languages with built-in special functions like isinstanceof(X, std_logic). Personally, I
don't like the function based aproach, moreover it implies that type_marks can be passed
as parameters to functions ...
I see these solutions:
- Solution
A X'type attribute combinded with a new operator pair (is / is not) to compare types.
All needed keywords are already reserved. It does not touch common expressions.
- Solution
A X'instanceof(std_logic) attribute, which expects a type_mark as parameter and returns
a boolean.
- Solution
A built-in function to get the type gettype(X).
- Solution
Another built-in function isinstanceof(X, std_logic)
I would prefer the first solution, because I think it's more VHDL like.
Example:
if (X'type is std_logic) then
-- ...
end if;
Getting a type_mark from an attribute is a needed capability to do other usefull
stuf. E.g. if an intermediate signal is needed in a loop.
What attributes are needed?
Prop1 lists the following attributes:
- R'ELEMENTS returns the implicit enumerated type E.
- R'LEFT is the leftmost element of record R (or implicit enumerated type E).
- R'RIGHT is the rightmost element of record R (or implicit enumerated type E).
- R'HIGH is the highest element of record R (or implicit enumerated type E).
- R'LOW is the lowest element of record R (or implicit enumerated type E).
- R'RANGE is the range R'LEFT to R'RIGHT or R'LEFT downto R'RIGHT .
- R'REVERSE_RANGE is the range of R with to and downto reversed.
- R'LENGTH is the integer value of the number of elements in record R (or implicit enumerated type E).
I suggest to replace 'elements by 'members.
The count of record members can be retrieved by R'members'count (see Prop4).
Moreover, R could offer 'count by itself (this is a more orthogonal implementation).
From my previous paragraphs:
- 'type_class returns the class/kind of a type: scalar, array, record, enumerated, protected
- 'type returns the type_mark of a type
- needed to create local intermediate objects and
- needed to access the atributes of a type
How to access record members?
Prop1 uses the dot notation for signal.index, wherin member is a enumerated type
member used to access the record member.
I think this could be a big change in the language, that an enum member is allowed
to access a record member. Arrays already have such a feature, but the enum member
is used as an index: signal[index].
- Solution:
Allow enum members after the dot to access a record member: variable index : myRecordSignal'type'members;
-- ...
index := Valid; -- a literal is assigned
valid <= myRecordSignal.index; -- the record is access by the literal stored in index using the dot-notation
- Solution:
Add array index syntax to records:
variable index : myRecordSignal'type'members;
-- ...
index := Valid; -- a literal is assigned
valid <= myRecordSignal(index); -- the record is access by the literal stored in the index using the array element notation
- Solution:
Introduce a new attribute 'field (better name?; val/value):
variable index : myRecordSignal'type'members;
-- ...
index := Valid; -- a literal is assigned
valid <= myRecordSignal'field(index); -- the record is access by the literal stored in the index using a attribute notation
Other names for 'field could be 'value, ... ??
The third solution does not need any kind of dynamic typing or ananymous types.
I suggest to use 'field(enum_member) for the pseudo code in our use cases. It's explicit
and can not be confused with existing syntax. When the syntax is going to be written into
the LRM, we can discuss it again. It's also possible to see if it's intuitive to use it or solution
1 and 2 are better suited.
Why is this idea not using dynamic things?
Let's look into for...generate loops:
entity e is
port (
-- ...
DataIn : array(7 downto 0) of std_logic_vector(7 downto 0);
Valid : std_logic_vector(7 downto 0)
);
end entity;
architecture a of e is
begin
gen : for i in 0 to 7 generate
signal localValid : std_logic;
signal localData : std_logic_vector(7 downto 0);
begin
localValid <= Valid(i);
localData <= DataIn(i);
-- ...
end generate;
end architecture;
So what's (propably) going on in the compiler?
- The range for the generate is known (as for our implicit enum).
- The compiler generates an array of range 0 to 7 for each local signal
- The local signal is implicitely access by i in every iteration with a (propably) "anonymous typed pointer/reference"
Yes, it's anonymous in the compiler implementation, but not required for VHDL. So a compiler might internally use
something like "Dim current As Object", but VHDL does not need something like "variable index : anonymous".
I don't refuse the "Anonymous Types" proposal, I'm just say it might not be needed to implement introspection.
- i is of type integer and 0 to 7 is an iterable (the the Python name for things that can be used in for loops)
Let's look into iterating over record members:
entity myEntity is
port (
-- ...
DataIn : T_MyRecord
);
end entity;
architecture arch of myEntity is
function length(myRec : T_MyRecord) return integer is
variable result : integer;
begin
result := 0;
for i in T_MyRecord'members loop
if (T_MyRecord'type_class(i) = scalar) then
result = result + 1;
elsif (T_MyRecord'type_class(i) = array) then
result = result + T_MyRecord'field(i)'length;
else
report "Member not supported." severity error;
end if;
end loop;
return result;
end function;
function length_until(myRec : T_MyRecord; pos : T_MyRecord'members) return integer is
variable result : integer;
begin
result := 0;
for i in T_MyRecord'low to pos'pred loop
if (T_MyRecord'type_class(i) = scalar) then
result = result + 1;
elsif (T_MyRecord'type_class(i) = array) then
result = result + T_MyRecord'field(i)'length;
else
report "Member not supported." severity error;
end if;
end loop;
return result;
end function;
constant DataInLength : integer := length(DataIn);
signal flattenDataIn : std_logic_vector(DataInLength - 1 downto 0);
begin
genLoop : for i in DataIn'type'members generate
begin
genScalar : if DataIn'field(i)'type_class = scalar) generate
flattenDataIn(length_until(DataIn, i)) <= DataIn'field(i);
elsif DataIn'field(i)'type_class = array) generate
flattenDataIn(length_until(DataIn, i'succ)-1 downto length_until(DataIn, i)) <= DataIn'field(i);
end generate;
end generate;
end architecture;
What does the compiler know?
- All members are known at compile time.
- The bounds of the loop are static.
- The types of the members are known.
So iterating over record members is like in traditional for generate loops. The compiler
just needs to update an internal implicit pointer to the current member. So again, a compiler
can use some kind of anonymous pointer, but it does not require an anonymous typed iterator
in VHDL.
Going further:
subtyping
Prop2 introduces the idea of subtyping records. This is the reverse direction compared to
inheritance. Instead of adding new members to a class after inherting a set of members from
the ancestor, it gives the idea of reducing the amount of members like std_logic can be striped
to U01.
I'm not quite sure about it, but I have projects that feed configuration settings as generic
parameter down the instance hierachy. In my case not all information are needed at the leafs
instances. So it could be good to simplify the record from layer to layer.
I see the folowing problem:
- How to restrict the members. Regarding the enumerated type subtyping mechanism, its
only possible to redurce members at the head and/or tail of the list.
Does anyone like the idea of extending/inheriting enumerated types and records?
Please contact me !
Applying the concept to entities/architetures
Introspection for entities/architectures. Cuurently there is no way to instantiate architectures
by a string parameter or iterate over all architectures of a entity to test them all in the same
testbench (only useful for alternative implementations with similar input/output behavior).
So reusing the 'members idea, it's possible to have a E'architectures attribute.
Example 1 - test all implementations simultanious in the same testbench:
-- The DUT
entity myEntity is
port (
-- ...
);
end entity;
architecture arch1 of myEntity is
begin
end architecture;
architecture arch2 of myEntity is
begin
end architecture;
-- The testbench
entity test
end entity;
architecture tb of test is
begin
genArchs : for index in myEntity'architectures generate
arch : entity work.myEntity(index)
port map (
-- ...
);
end generate;
end architecture;
Example 2 - Instantiate by a generic parameter.
Our
PoC library has the goal if vendor indepency. For that purpose, we have several wrappers,
which select different implementations by a global constant called VENDOR or if necessary a FPGA
specific design by testing DEVICE, FAMILIY, SUBTYPE or even LUT_FAN_IN ...
For example if the module
PoC.io.ddrio.out could use this feature it could look like this:
entity ddrio_out is
generic (
NO_OUTPUT_ENABLE : BOOLEAN := false;
BITS : POSITIVE;
INIT_VALUE : BIT_VECTOR := x"FFFFFFFF"
);
port (
Clock : in STD_LOGIC;
ClockEnable : in STD_LOGIC := '1';
OutputEnable : in STD_LOGIC := '1';
DataOut_high : in STD_LOGIC_VECTOR(BITS - 1 downto 0);
DataOut_low : in STD_LOGIC_VECTOR(BITS - 1 downto 0);
Pad : out STD_LOGIC_VECTOR(BITS - 1 downto 0)
);
end entity;
architecture rtl of ddrio_out is
-- possible alias to shorten the code
alias archs is ddrio_out_vendor'architecture;
constant index : ddrio_out_vendor'architecture := ddrio_out_vendor'architecture'value(T_VENDOR'image(VENDOR));
begin
assert (VENDOR = VENDOR_XILINX) or (VENDOR = VENDOR_ALTERA)
report "PoC.io.ddrio.out is not implemented for given DEVICE."
severity FAILURE;
inst : ddrio_out_vendor(ddrio_out_vendor'architecture'value(T_VENDOR'image(VENDOR)))
generic map (
NO_OUTPUT_ENABLE => NO_OUTPUT_ENABLE,
BITS => BITS,
INIT_VALUE => INIT_VALUE
)
port map (
Clock => Clock,
ClockEnable => ClockEnable,
OutputEnable => OutputEnable,
DataOut_high => DataOut_high,
DataOut_low => DataOut_low,
Pad => Pad
);
end generate;
end architecture;
So this is a full feature reusage :).
Use Cases:
Here is a list of use cases:
- All kinds of data structure flattening:
- record => std_logic_vector
- record => JSON
- std_logic_vector => record
- record => string
Further improvements:
How can it be even nicer to use this feature?
- If a user could define user-defined attributes, with direct to a function/procedure
- attribute serialize is alias to_json
- attribute neededbits is aliad length
- If there are anonymous types
-
Open questions:
Should protected types be introspectable, too? Especially if they get public fields, signals
or events.
--
Patrick Lehmann - 2016-03-03
Comments