Speed up automatic module pin handling - #637
Merged
Merged
Conversation
Before changing how module pins are recomputed, pin down the behavior that has to survive. The existing check_pins test covers the manual pin API but almost nothing of the automatic path, and enable_automatic_net_checks() and update_nets() had no test at all. Six tests are added: * check_pins_on_gate_assignment: pins appear and disappear as gates enter and leave a module, including the generated names and the one-pin group per pin. * check_pin_direction_transitions: the direction of a pin follows the connectivity of its net through input, inout and output, the pin keeps its ID across those transitions, and it is removed once the net is fully internal. * check_pins_of_parent_modules: a boundary net creates a pin in the module and in every ancestor, and re-parenting a module moves the pin with it. * check_pins_on_global_net_marking: marking a net global input or output adds an external endpoint and therefore creates, changes, or removes a pin. * check_pins_with_disabled_net_checks: with the automatic checks disabled nothing is updated, update_nets() restores the net sets but deliberately creates no pin, re-enabling repairs nothing retroactively, and a manually created pin survives the next automatic check. * check_manual_pin_creation_requires_disabled_checks: create_pin() is rejected while the automatic checks are enabled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
get_pin_by_net() did a linear scan over all pins of a module. It sits on the hot path of Module::check_net(), which runs for every net of every gate that gets assigned to or removed from a module. The net of a pin never changes, so the index only has to be maintained when a pin is created or deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Assigning gates to a module emits three pin events per created pin and two per deleted pin. Creating and deleting 24 modules out of the 7144 top level gates of a sha256 netlist therefore emitted 43040 pin_changed events. Every event that a python script triggers is handed to the GUI thread over a blocking queued connection, so the cost of an event is a thread round trip rather than the handler itself. Measured with `sample` on the workload above, 91% of the script runtime was the python thread waiting: 68% in the qApp->processEvents() that follows each emit and 23% blocked on the semaphore of the blocking connection. The netlist work itself accounted for 5%. A bulk operation changes the pins of a module wholesale and a listener has to re-read them anyway, so PinChangedBulkScope now collects the pin events of all affected modules and sends a single PinEvent::PinsReload per module instead. That takes the workload from 43040 to 48 pin events. Interactive pin changes such as renaming or reordering a pin group are not covered by a bulk scope and keep their fine grained events, so the pins tree still updates incrementally when the user edits pins. ModulePinsTree preserves expanded pin groups and the current selection across the reload, so a bulk change no longer collapses the tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
joern274
approved these changes
Aug 14, 2026
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.
Assigning gates to a module recomputes its pins, and both halves of that were slower than they needed to be: the lookup that decides whether a net already has a pin scanned linearly, and every individual pin change was announced to the GUI as its own event.
Measured on a sha256 netlist with 7144 top level gates, creating and deleting 24 modules emitted 43040
pin_changedevents. Because every event a Python script triggers is handed to the GUI thread over a blocking queued connection, the cost of an event is a thread round trip rather than the handler itself. Asampleof that workload showed 91% of the script runtime was the Python thread waiting — 68% in theqApp->processEvents()that follows each emit, 23% blocked on the semaphore of the blocking connection. The netlist work itself was 5%.What is in here
Characterization tests first.
enable_automatic_net_checks()andupdate_nets()had no test at all, and the existingcheck_pinstest covered the manual pin API but almost none of the automatic path. Six tests pin down the behavior that has to survive the change: pins appearing and disappearing as gates enter and leave a module, direction transitions through input/inout/output with a stable pin ID, pins propagating to ancestor modules and moving on re-parenting, the effect of marking a net global, and what happens with the automatic checks switched off.Index module pins by net.
get_pin_by_net()scanned all pins of a module, on the hot path ofModule::check_net(), which runs for every net of every gate entering or leaving a module. The net of a pin never changes, so the index only needs maintaining when a pin is created or deleted.Coalesce the events of bulk operations. A bulk operation changes a module's pins wholesale and a listener has to re-read them anyway, so
PinChangedBulkScopecollects the pin events of all affected modules and sends a singlePinEvent::PinsReloadper module. That takes the workload above from 43040 to 48 pin events.Interactive pin changes such as renaming or reordering a pin group are deliberately not covered by a bulk scope and keep their fine grained events, so the pins tree still updates incrementally while the user edits pins.
ModulePinsTreepreserves expanded pin groups and the current selection across a reload, so a bulk change no longer collapses the tree.Notes for review
PinEvent::PinsReloadwidens the pin event encoding; the 4-bit packing is now guarded by astatic_assert.🤖 Generated with Claude Code