Skip to content

Change ESI wrapper ports#129

Merged
teqdruid merged 5 commits into
mainfrom
teqdruid/callback-outputs
May 18, 2026
Merged

Change ESI wrapper ports#129
teqdruid merged 5 commits into
mainfrom
teqdruid/callback-outputs

Conversation

@teqdruid
Copy link
Copy Markdown
Contributor

After some experience importing and using ESI ports, I've changed my mind about a few things: callbacks should be outputs rather than inputs and [[async]] functions should be channels rather than bundles since bundles are kind of a pain.

Also, ESI recently added a "ValidOnly" signaling standard so we can use that for [[no_backpressure]] functions and callbacks.

Assisted-by: vscode:Claude Opus 4.7

teqdruid added 3 commits May 13, 2026 21:48
Bundle ports for external functions and export-class callbacks are now
declared as kanagawa.port.output instead of kanagawa.port.input. The
underlying scalar data direction is unchanged: channel directions inside
the bundle flip ('from' <-> 'to') so that the wrapper still produces
arguments and consumes any results.

EmitEsiWrapper takes the new direction via PushPopEsiBundle's
isOutputBundle flag and switches between the input-bundle pattern
(InputPort + Read + Unpack) and the output-bundle pattern (Pack +
OutputPort + Write).

Export function bundles continue to be emitted as input bundles.
[[async]] export functions and [[async]] callbacks (with backpressure)
each produce exactly one ESI channel — there is no return-value channel
because [[async]] functions and callbacks are always void. Wrapping such
a single channel in a one-element bundle is unnecessary noise for PyCDE
consumers, so EmitEsiWrapper now exposes them directly as ports of
!esi.channel<...> type:
- async export functions become input channel ports
- async callbacks become output channel ports

Multi-channel bundles (regular functions, regular callbacks) still go
through the bundle/pack/unpack path.
[[no_backpressure]] functions and callbacks were previously kept out of
the ESI bundle/channel path entirely and exposed as raw scalar valid +
payload ports. Now that CIRCT supports ChannelSignaling::ValidOnly along
with esi.wrap.vo / esi.unwrap.vo, those ports go through the same
EmitEsiWrapper path as backpressured ones and are emitted as
!esi.channel<..., ValidOnly>.

Specifically:
- Free [[no_backpressure]] [[async]] callbacks: bare ValidOnly output
  channel.
- Export functions that are no_backpressure but not fixed-latency: input
  bundle whose arg and result channels are both ValidOnly.
- Fixed-latency export results have no Valid signal at all and remain
  raw scalars (no ESI wrapping possible).

The signaling decision in EmitEsiWrapper now infers the protocol from
which control signals are present per direction:
  * Ready                  -> ValidReady
  * ReadEnable / Empty     -> FIFO
  * Valid only             -> ValidOnly

Adds an interface.circt.esi_wrapper_ports test under test/interface/circt/
that compiles a small program exercising every combination of {export,
callback} x {regular, async, no_backpressure}, then runs a python script
that asserts each port lowers to the expected EsiWrapper shape and
forbids the pre-refactor shapes (input callback bundles, single-channel
async bundles).
@teqdruid teqdruid requested review from blakepelton and Copilot May 14, 2026 22:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Reworks ESI wrapper port shapes so that callbacks become outputs, [[async]] functions become bare ESI channels (instead of single-element bundles), and [[no_backpressure]] functions/callbacks use the new ESI ValidOnly signaling protocol. A new test harness compiles a representative source file with --skip-circt-lowering and greps the emitted CIRCT MLIR to validate every port shape combination.

Changes:

  • Extend EmitEsiWrapper with output-bundle, bare-channel, and ValidOnly (wrap/unwrap) lowering paths, and let BeginEsiBundle/PushPopEsiBundle carry an output-bundle flag.
  • In verilog.cpp, callbacks are always emitted into a bundle and registered as output bundles, and exports get a bundle whenever ESI can describe the signaling (hasBackpressure || !fixedLatency).
  • Add a CMake-driven CIRCT MLIR check that compiles esi_wrapper_ports.k and validates each port declaration via check_esi_wrapper_ports.py.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
compiler/cpp/circt_util.h Adds isOutputBundle parameter and EsiBundleInfo struct to track per-bundle direction.
compiler/cpp/circt_util.cpp Implements ValidOnly wrap/unwrap, output-bundle pack path, and bare-channel ports.
compiler/cpp/verilog.cpp Updates export/callback bundle decisions and tags callback bundles as output.
test/interface/circt/esi_wrapper_ports.k New Kanagawa source covering all 6 export/callback × signaling combinations.
test/interface/circt/check_esi_wrapper_ports.py Regex-based MLIR checker for the expected and forbidden port shapes.
test/interface/circt/CMakeLists.txt New add_circt_mlir_test helper wiring prepare/mkdir/compile/test fixtures.
test/interface/CMakeLists.txt Hooks the new circt subdirectory into the interface test suite.
Comments suppressed due to low confidence (1)

test/interface/circt/check_esi_wrapper_ports.py:1

  • Only the first .mlir file (alphabetically) is inspected. If the compiler emits more than one .mlir file into the output directory, port-shape regressions in the other files will be silently ignored. Consider concatenating the contents of all matched files (or iterating over them) before running the substring/forbidden checks, or asserting that exactly one file is expected.
#!/usr/bin/env python3

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread compiler/cpp/circt_util.cpp Outdated
Comment thread compiler/cpp/circt_util.cpp
- circt_util.cpp: replace literal 0/1 indices in the bare-channel branch
  with named constants (kFromGeneratedHwIdx / kToGeneratedHwIdx) and
  assert(valid) immediately before WrapValidOnlyOp::create to enforce
  the invariant at the point of use (Copilot review comments on #129).
- verilog.cpp: minor formatting tweak for GetBasicBlockInstanceName.
- test/interface/circt: trim duplicated comments and add a TODO noting
  that the python checker should ideally use the CIRCT Python API.
@teqdruid teqdruid force-pushed the teqdruid/callback-outputs branch from 79f1c78 to 86a3952 Compare May 15, 2026 20:21
Copy link
Copy Markdown
Collaborator

@blakepelton blakepelton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. At some point, consider adding documentation in the doc directory for what the ESI interface looks like for an exported class.

Comment thread compiler/cpp/circt_util.cpp Outdated
@teqdruid teqdruid merged commit 8e0a3fb into main May 18, 2026
24 checks passed
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.

3 participants