Repository navigation
Conversation
record_dict_pkg uses VHDL-2019 reflection to convert a value of any record type to a dict_t, so no conversion function has to be written or generated for each record. It is opt-in through add_record_dict(), which requires VHDL-2019 and a simulator whose supports_vhdl_reflection() is true, currently NVC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
to_dict is now a generic function, instantiated once per record type
and called with the record:
function to_dict is new to_dict generic map (record_t => packet_t);
dict := to_dict(packet);
A function can't take packet'reflect directly. A mirror is an access
type, and an access type parameter must be of class variable, whose
actual must be a variable name, not an expression. Before, every call
needed a mirror variable and std.reflection. Now the function reflects
its own parameter, so each call is one expression and users never see
mirrors. The instantiation remains since VHDL has no implicit
instantiation of generic subprograms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Riviera-PRO supports VHDL-2019 reflection, so it reports supports_vhdl_reflection() and runs the acceptance test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Riviera-PRO rejects chained mirror calls, returns simple_name in lower case, gives time values in units of the resolution limit and indexes descending arrays by their actual index, where NVC indexes from 'left upwards. Handle each so the package runs on both simulators. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This adds
record_dict_pkg, which converts a value of any record type to adict_tusing VHDL-2019 reflection. It is opt-in throughvu.add_record_dict().Motivation
Records carry most testbench data, but VUnit's dynamic data structures don't take them. Transactions, register maps, configurations and expected results are records in almost every VHDL testbench.
dict_t,queue_tand the logging and check APIs work on scalars, vectors and strings. Today, turning a record into one of those means hand-writing a function per record type.Hand-written conversions go stale silently. When a field is added to a record, its
to_dict(orto_string) still compiles. The new field is just missing from every log line, comparison and model input until someone notices. A reflection-based conversion follows the record declaration automatically, including nested records.VHDL-2019 makes the generic version possible for the first time. Before 2019, a package couldn't inspect a record's elements, so the only options were per-type code or a code generator that parses the user's sources.
std.reflectionis the standard's answer to exactly this. Supporting it in VUnit is a reason to adopt the 2019 features that work today.A dict is a useful middle format. Once a record is a
dict_t, existing VUnit code can handle it:to_string.dict_targument to Python as adict, so a VHDL record can drive a Python reference model with no glue code per record.Why opt-in
std.reflection. NVC has had'reflectsince 1.11.0 (2023-12). GHDL 7.0.0-dev and Questa FSE 2025.3 don't providestd.reflection(checked locally). Riviera-PRO supports it, verified on a Riviera-PRO install.add_vhdl_builtins(), and nobody else compiles it.add_record_dict()fails early, with a clear message, when the standard is older than 2019 or the simulator lacks reflection.supports_vhdl_reflection()capability on the simulator interface,Falseby default andTruefor NVC and Riviera-PRO. Other simulators only need to flip the flag when they gain support.Behaviour
integer,real,boolean,bit,std_ulogic/std_logic,character,time,string,std_ulogic_vector/std_logic_vector,unsignedandsignedkeep their type (stored with the matchingset_*)."busy").dict_t(get_dict_t_ref).record_dict_loggerwith the element's name. Passing a value that isn't a record also fails there.from_dict.Why the API looks like this
The ideal call would be
to_dict(packet'reflect), but no subprogram can take a reflected value directly. I tried both ways of declaring the parameter on NVC, with and without--relaxed:value : value_mirrorformal parameter VALUE with access type must have class VARIABLEvariable value : value_mirroractual for formal VALUE with class VARIABLE must be a name denoting a variablepacket'reflectis an expression.Both are language rules, so the mirror has to live in a variable somewhere. A first version of this PR left that to the caller: every call needed
use std.reflection.all, avalue_mirrorvariable and two statements.to_dictis now a generic function over the record type, and it reflects its own parameter. The result:std.reflection.to_dict, so the call site stays the same for every type.The one line per record type remains because VHDL has no implicit instantiation of generic subprograms: NVC rejects a call to the uninstantiated function with
cannot call uninstantiated function. A generic package would also need one instantiation per type, and it couldn't be declared inside a process.Design notes
mirror_to_dict(variable value : inout value_mirror), which recurses for nested records.to_dict(value)as an actual ofset_dict_t_refinside the package. One local variable works around it, and users never see it.check_only_logcompares the location of the log item, so the failure tests passline_num/file_name, the same waytb_location.vhddoes.Testing
vunit/vhdl/record_dict/run.pyon NVC 1.23-devel with VHDL-2019: 6/6, withto_dictinstantiated for five types in one architecture (four records andinteger). It covers scalars, arrays (including ascending ranges, signed and unsigned), nested records, lower-case keys, and both failure messages, with the logger mocked.test_record_dict_vhdl_2019, skipped for simulators other than NVC and Riviera-PRO. The testbench also passes on Riviera-PRO (run on a separate machine with a license).tests/unitsuite passes (642 passed).tests/lintlicense and pycodestyle checks pass; pylint 10/10 on the changed modules.This PR was written with the help of an AI assistant (Claude).
🤖 Generated with Claude Code