verilator / verilator

Verilator open-source SystemVerilog simulator and lint system

Home Page:https://verilator.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

VPI objects architectural issues

ktbarrett opened this issue · comments

Brain dump inbound.

Currently (v5.025) whenever a VPI object is acquired via vpi_handle_by_name, vpi_handle_by_index and friends, a new VerilatedVpi* object is created and returned. This means that getting the same object more than once returns two different VerilatedVpi* objects.

The way value change is detected (old values are only stored on the VerilatedVpiVar object) and callbacks are handled, it is possible to put a value on one instance of an object and register the callback on the other instance of that same object and never the two will meet.

The fundamental issues is that the VerilatedVpi* objects aren't singletons like the Verilated* objects. There should be only one VerilatedVpi* object per simulation object. Children should be found by interrogating parents, like the Verilated* objects, rather than make "free" objects stitching together the Verilated* objects.

The issue is vpi_release_handle. The solutions are:

  1. Just ignore it and never free once allocated.
  2. Use ref counting. Children hold strong refs to parents and parents weak refs to children, so that if the user releases a child, it is collected, but releasing a parent with living children never collects the parent.

Solving this issue also solves the big issue with #5138, where VerilatedVpioVar is copied in VerilatedVpiPutHolder because vpi_release_handle simply frees the object invalidating a pointer the PutHolder would have instead of a copy of the whole object.

Solving this issue also allows there to be references from children to parents, meaning things like fullname can be computed on demand instead of allocating on every new object creation. Also, it makes implementation of vpi_compare_objects trivial.

Finally, copying a VerilatedVpioVar for VerilatedVpiPutHolder is suspect because m_prevDatap is shared between the two copies and releasing one copy does a delete of that member. If there ever was an inertial write that outlived the handle, it would result in a use-after-free.

If you look at other simulators is it true that they return the same handle for the same variable iterated or found by two separate means?

Just ignore it and never free once allocated.

We can't accept runtime leaks.

If you look at other simulators is it true that they return the same handle for the same variable iterated or found by two separate means?

Generally, no; but it works regardless (AFAIK). At least in Verilator with the way things are architected now, it causes issues. Making the calls return the same handle is just the easiest way to go about resolving those issues IMO.

We can't accept runtime leaks.

They aren't leaks. They will still be referenced by parent and child objects; they just won't ever be freed until program termination. So vpi_release_handle is just a no-op. Successive vpi_handle_* calls will just return that same handle that was never actually freed.

I see this as a 2 step refactor: do solution 1, then solution 2 if memory consumption becomes an issue. (At least with cocotb solution 2 doesn't add anything since we just store every handle until program termination anyways).

If an entire model is created, runs, and destroyed, every newed object that is part of that model must get deleted. This allows end-to-end loops in an main() app. If this does not leak in that case it is ok, but sounds it would.

This allows end-to-end loops in an main() app.

I'm not sure what you mean? But we can go straight for solution 2 if object lifetimes are a serious concern.

See t_leak.cpp

but with that test extended to have the model use VPI. (A good test improvement... welcome a pull if you want to.)