Skip to content

Value circuits new#36

Draft
cchalmers wants to merge 13 commits into
masterfrom
value-circuits-new
Draft

Value circuits new#36
cchalmers wants to merge 13 commits into
masterfrom
value-circuits-new

Conversation

@cchalmers

@cchalmers cchalmers commented Jun 10, 2026

Copy link
Copy Markdown
Owner

The value level circuit syntax I originally started work on 6 years ago! master...value-circuits

This was written with the anthropic's new fable model. I also got it to add significantly more testing.

This gets you pretty close to being able to write everything with circuit syntax! Mealy machines can be done with a registerC:

accum :: Circuit (Signal dom Int) (Signal dom Int)
accum = circuit \(SignalV i) -> do
  SignalV acc <- registerC 0 -< SignalV acc'
  let acc' = acc + i
  idC -< SignalV acc'

It also supports multiple clock domains (although you don't get a great error when you mix them up):

dualCounter :: Circuit (Signal domA Bool, Signal domB Bool) (Signal domA Int, Signal domB Int)
dualCounter = circuit \(SignalV enA, SignalV enB) -> do
  SignalV n <- registerC 0 -< SignalV (if enA then n + 1 else n)
  SignalV m <- registerC 0 -< SignalV (if enB then m + 1 else m)
  idC -< (SignalV n, SignalV m)

and DSignalC support (makes sure all groups have the same delay):

  dpipeC :: Circuit (DSignal dom d Int) (DSignal dom (d + 1) Int)
  dpipeC = circuit \(DSignalV i) -> do
    DSignalV a <- dregisterC 0 -< DSignalV (i + 1)
    idC -< DSignalV (a * 2)

Since #14 the error locations often pointed to the end of the circuit instead of the correct location. That should now be fixed, along with a regression test.
@cchalmers cchalmers force-pushed the value-circuits-new branch from f847f89 to b2f0f98 Compare June 10, 2026 21:55
@martijnbastiaan

Copy link
Copy Markdown
Collaborator

RIP Fable..

I find that I typically mix and match Circuit and non-Circuit constructs, maybe it makes sense to also allow this in lets? Perhaps then split Fwd and Signal (de)construction? E.g., something like:

accum :: Circuit (Signal dom Int) (Signal dom Int)
accum = circuit \(Fwd (Values i)) -> do
  Fwd (Values acc) <- registerC 0 -< Fwd (Values acc')
  let acc' = acc + i
  idC -< Fwd (Values acc')

~

accum :: Circuit (Signal dom Int) (Signal dom Int)
accum = circuit \(Fwd (Values i)) -> do
  let
    Values acc = register 0 (Values acc')
    acc' = acc + i
  idC -< Fwd (Values acc')

Not sure about any of this, just floating an idea.

@cchalmers

cchalmers commented Jun 14, 2026

Copy link
Copy Markdown
Owner Author

I'll think about it but adding support in general Haskell expressions and bindings might open a whole can of worms. Right now, since it's limited to the "arrow land", it's relatively simple without much in the way of edge cases (I hope).

I'm generally in favour of keeping the plugin simple and predictable at the cost of a bit of verbosity. You can always use helpers (something like onCSignal :: (Signal dom a -> Signal dom b) -> Circuit (CSignal dom a) (CSignal dom b)) to promote something to Circuit land, or use idC to bridge in or out of value land.

@martijnbastiaan

Copy link
Copy Markdown
Collaborator

Yeah, makes sense. I'd rather have less magic too.

cchalmers and others added 12 commits June 23, 2026 10:11
The error-location test shells out to ghc to compile a fixture with the
plugin enabled. During the package's own nix check phase circuit-notation
isn't registered in any package database, so that compile cannot succeed and
the test would fail the build. Probe ghc first and skip (rather than fail)
when it can't compile against the plugin; the test still runs for real under
cabal test, where the package environment file makes the plugin available.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Value-group logic is lifted to the signal level with `fmap` over a
`bundle` of the group's inputs. Two strictness points there can make
combinational feedback between value groups deadlock simulation:

  * `bundle` lifts its first element with `fmap` / `mapSignal#`, which
    forces that element's spine. Prepend a lazy unit so no real input
    sits in that spine-forcing head slot.

  * The logic function matched its inputs strictly, so a constructor
    pattern at the boundary (destructuring the sampled value) forced its
    input to produce *any* of the group's outputs -- even outputs that do
    not use it. That deadlocks when the input depends, through the
    circuit, on such an output. Match each value input lazily
    (irrefutable), as the bus-level plumbing already does, so an output
    only forces the inputs it actually uses.

Both keep value-group feedback well founded without changing the lifted
logic. The library and error-location test suites still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cchalmers cchalmers force-pushed the value-circuits-new branch from 83565d7 to cd19ade Compare June 23, 2026 09:21
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.

2 participants