Skip to content

Add push and pop for values of any type on a queue_t - #1243

Closed
ru551n wants to merge 9 commits into
VUnit:masterfrom
ru551n:reflection-queue-push
Closed

ru551n wants to merge 9 commits into
VUnit:masterfrom
ru551n:reflection-queue-push

Conversation

@ru551n

@ru551n ru551n commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Draft, depends on #1239. Until #1239 merges, the diff also shows its commits. The changes of this PR are the last three commits. "Don't treat a use of a local package instance as a library" is the same scanner fix as in #1242, cherry-picked so this branch builds without warnings on its own.

This adds push and pop for values of any type on a queue_t:

package packet_queue is new vunit_lib.reflection_queue_pkg generic map (t => packet_t);
use packet_queue.all;
...
push(queue, packet);
packet := pop(queue);

How it works

Reflection can read a value but not create one, and t'image/t'value aren't allowed on a generic type. Both NVC and Riviera-PRO reject them: "non-representable type T" and "Illegal prefix for attribute 'IMAGE". So a value can't be rebuilt from data on the queue.

Instead, push copies the value into a store owned by the package instance and pushes two integers: an instance id and a handle. pop pops both, checks the id, takes the value out of the store and frees the handle for reuse. The store is a protected type with a growing array of pointers to t, so unconstrained types such as integer_vector work too.

  • Values can be mixed with other types and with the queue_pkg types on the same queue, in order.
  • Popping a value pushed by another instance fails on the vunit_lib:reflection_queue_pkg logger, with the location of the pop call.
  • The instance id is the number of a pointer each instance allocates. I first used 'path_name of the store, but NVC (1.23-devel and master) gives the same path for every instance of the generic package (:tb:store), while Riviera-PRO includes the instance (:work:a_pkg:store).

Open questions for review

  1. It doesn't use reflection. It needs VHDL-2008 generic packages and protected types, nothing from 2019. With VHDL-2008 a small testbench passes on GHDL 7.0.0-dev and NVC. Questa FSE 26.1 compiles it, but its license on this machine doesn't allow simulation, so it isn't verified there. It could go into the data types library for all simulators instead of the reflection opt-in. In that case the name reflection_queue_pkg should change.
  2. The queue holds handles, not data. encode/decode or a copy of the queue doesn't copy the values, the Python bridge would only see two integers, and flush leaves the values in the store. Is that acceptable, or should these be documented harder or guarded?
  3. Name and placement. It's in vunit/vhdl/reflection/ and enabled by add_reflection() for now.

Testing

  • vunit/vhdl/reflection/run.py with VHDL-2019: 12/12 on NVC 1.23-devel and Riviera-PRO 2026.04. That's 6 new tests plus the 6 record_dict_pkg tests. They cover:
    • a record round trip with every element type record_dict_pkg supports;
    • an unconstrained type (integer_vector);
    • order when mixed with push_integer/push_string and another type;
    • 100 values, which grows the store past its initial size and then reuses the freed handles;
    • pushing in one process and popping in another;
    • the error for a value of another instance.
  • VHDL-2008 outside the opt-in: GHDL and NVC pass. Questa compiles, but simulation is blocked by its license.

This PR was written with the help of an AI assistant (Claude).

🤖 Generated with Claude Code

ru551n and others added 9 commits October 17, 2026 18:01
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>
Move the library to vunit/vhdl/reflection and rename add_record_dict() to
add_reflection(), so the opt-in covers further packages built on VHDL-2019
reflection rather than only record_dict_pkg. The package name and its API
are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A package instantiated in an architecture is made visible with a use
clause naming the instance, such as use my_inst.all. The dependency
scanner read my_inst as a library and warned "failed to find library
'my_inst'". Skip use clauses that name a package instantiated in the same
file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
reflection_queue_pkg is a generic package with push(queue, value) and
pop(queue) for its type t. A value can't be rebuilt from its parts in
VHDL, so push copies the value into a store owned by the instance and
pushes an instance id and a handle onto the queue. pop checks the id,
takes the value out of the store and frees its handle for reuse.

The instance id is the number of a pointer that each instance allocates,
since 'path_name of the store is the same for all instances on NVC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ru551n

ru551n commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Closing: a generic, symmetric push and pop can't be built on reflection. VHDL-2019 reflection can read a value but not create one, and neither NVC nor Riviera-PRO allows 'image/'value on a generic type, so pop can't rebuild a t from data on the queue. The store-and-handle approach here works, but it doesn't use reflection and the queue wouldn't carry the data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant