fix(multibit): concat broadcast into parametric sub-circuit ports - #72
Merged
Conversation
`xor xn[4](a={c, c, c, c}, b=...)` and similar broadcast-via-concat
patterns into parametric macro/sub-circuit instance ports are now
accepted. Two papercuts converged to reject this pattern:
1. Validator (port_validation.zig): the concat width-sum check read
`Component.width` on the destination directly. For a sub_circuit_ref,
that field only reflects the type-position `[N]` annotation; the
instance-position `[N]` parses into `width_args` and is consumed by
the project resolver during specialization, leaving `Component.width`
at its default of 1. The local check now skips sub_circuit_ref
destinations and lets sub_circuit_validation.zig handle the cross-
boundary width check (which has the project context to read the
specialized target's input port width). `endpointWidth` is extended
to return `component.width` for slice/concat on the `.from` side so
that path correctly emits E014 for genuine mismatches.
2. Topology builder (serializer.zig, full_serializer.zig): the single
Pass 1 over `module.components` processed each in declaration order,
so recursion into a sub_circuit_ref tried to resolve a sibling concat
or slice declared later (and not yet in `local_to_global`), failing
with `error.InternalError`. Pass 1 now splits:
Pass 1a emits primitives/slices/concats.
Pass 1b recurses into sub_circuit_refs with every sibling already
registered.
This shifts global IDs: in-module primitives now precede sub-circuit
children in emit order. Truth-table simulation is unaffected; the
runtime's `nodes.items[id]` lookup still works because emit order
continues to match sequential ID assignment.
The one over-specified test, `full_walk_one_subcircuit_records_origin`,
was rewritten to find components by name rather than asserting their
positions in the emit array.
Goldens regenerated where ID ordering surfaces:
- 5 preview-layout/render goldens (ID shifts only; visual layout,
coordinates, and wiring stay identical).
- tests/fixtures/bench/engine.bench.golden: ~35 sub-circuit fixtures
show topology_hash shifts. Counters, vector counts, and component
counts are byte-identical, so the regression gate's intent (no
extra algorithmic work) holds.
Contributor
Engine bench: deltas vs
|
| Fixture | Counter | Before | After | Δ | Δ % |
|---|
Produced by zig build bench -- --output=json against main's engine.bench.golden, rendered by tools/bench/format-delta-comment.py. Wall-clock (drv_ms) is host-dependent and not asserted, so it isn't shown; see DOCS/benchmark.md for the bench's measurement model.
github-actions Bot
added a commit
that referenced
this pull request
May 25, 2026
…st into parametric sub-circuit ports (#72)
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.
Summary
Fixes the first of three multi-bit-language papercuts documented in #71's
fixtures:
xor xn[4](a={c, c, c, c}, b=...)is now accepted, removing theneed for the wire-buffer broadcast workaround in those fixtures.
Root cause
Two bugs converged to reject the pattern:
Validator —
port_validation.zigcheckedComponent.widthon thedestination directly. For a
sub_circuit_refthat field reflects onlythe type-position
[N]annotation; the instance-position[N](
xor xn[4]) parses intowidth_argsand is consumed by the projectresolver during specialization. Result:
Component.widthstayed atthe default 1, and the concat-width check spuriously errored with
"concat width 4 ... does not match destination width 1".
Topology builder —
{serializer,full_serializer}.zigwalkedmodule.componentsin a single declaration-order pass. Recursioninto a
sub_circuit_reftried to resolve sibling sources (e.g. aconcat declared after the instance) that weren't yet in
local_to_global, hittingerror.InternalError.Fix
port_validation.zig's concat-width check now skips destinations thatare
sub_circuit_ref. The cross-boundary E014 check insub_circuit_validation.zighas the project context to look up thespecialized target's input port width;
endpointWidthis extended sothe concat/slice
.fromside returns itsComponent.width,completing the path.
Topology Pass 1 is split into Pass 1a (emit primitives, slices,
concats; register in
local_to_global) and Pass 1b (recurse intosub-circuit instances). Every sibling is resolvable at recursion time.
The split shifts global IDs (in-module primitives precede sub-circuit
children in emit order). Truth-table simulation is unaffected — the
runtime's
nodes.items[id]lookup still matches emit-order IDassignment.
Test plan
zig build testpasses (one over-specified topology test wasrewritten to find components by name instead of by position).
zig build benchreports "golden matches".alu_4bit.truth.goldensemantics preserved.bit-for-bit against the regenerated outputs.
Goldens regenerated
coordinates, and wiring identical.
tests/fixtures/bench/engine.bench.golden— ~35 sub-circuitfixtures show
topology_hashshifts. Counters, vector counts,and component counts byte-identical.
Follow-up
The wire-buffer broadcast workarounds in #71's fixtures can now be
inlined to direct
{c, c, ...}concats. Leaving that cleanup for aseparate PR to keep this review focused on the engine change.