Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
36c873b
Fix dropping of error locations
cchalmers Jun 23, 2026
6b88435
Add error-location regression test
cchalmers Jun 23, 2026
4849612
First version of value circuits
cchalmers Jun 9, 2026
7a02cb3
More testing of value circuits (with a bug fix)
cchalmers Jun 9, 2026
7b428ff
Make sure the tests pass on all GHC versions
cchalmers Jun 10, 2026
e99bc83
Add the multi-domain to circuitS by minimally grouping
cchalmers Jun 10, 2026
2aa3f11
Change name to circuitV
cchalmers Jun 10, 2026
2a1bb94
Make a new SignalBus class for handling Fwd with circuitV
cchalmers Jun 10, 2026
0e6d5d8
Fix poly-kinded issue
cchalmers Jun 10, 2026
f0c5fa0
Switch to using SignalV at the pattern instead of circuitV
cchalmers Jun 10, 2026
0a88fdd
Add support for DSignalV
cchalmers Jun 10, 2026
f2643a8
Make Signal and DSignal enforce types
cchalmers Jun 10, 2026
10ba3c1
Lower value-group inputs lazily
cchalmers Jun 19, 2026
611c6e0
Trim the value-level ports changelog entry
cchalmers Jul 2, 2026
58ff61d
Extend SignalBus and supporting instances to 12-tuples
cchalmers Jul 2, 2026
17d3436
Blame whole-bundle type errors on the ports' span
cchalmers Jul 2, 2026
ff4075d
Add write-ghc-environment-files to cabal.project
cchalmers Jul 11, 2026
d69c300
Use register instead of Signal internals in error fixtures
cchalmers Jul 11, 2026
231cf56
Refine value ports: dead groups, let pragmas, as-patterns
cchalmers Jul 11, 2026
34d8e80
Fix AsPat arity for GHC < 9.12
cchalmers Jul 11, 2026
89d84d0
Correct AsPat arity boundary to GHC 9.10
cchalmers Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ jobs:
cabal build all --write-ghc-environment-files=always
ghc -Wall -Werror -iexample Example
ghc -Wall -Werror -iexample Testing
ghc -Wall -Werror -iexample ValueCircuits
- name: Test
run: |
Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Revision history for `circuit-notations`

## 0.3.0.0 -- Unreleased

* Add value-level ports via the new `SignalV` and `FwdV` markers in
`circuit` blocks. The circuit's logic is written over the values sampled
each clock cycle; the plugin lifts it back to the signal level with
`fmap`/`bundle`/`unbundle` and ties feedback loops with a lazy let
binding. The bus-level markers still bind the raw forward channel and mix
freely with value markers in one block; `Fwd` works on any bus, while the
bus-level `Signal` (and new `DSignal`) markers now additionally enforce
the bus type, which also drives type inference. See the README and
example/ValueCircuits.hs.

**Breaking**: `ExternalNames` gained `signalTagName`, `fwdTagName` and
`dSignalTagName` fields, so custom plugins (e.g. clash-protocols style)
need to supply them — `defExternalNames` is now exported so they can be
record updates of the defaults.
* Add a per-GHC `checks` output to the flake, so `nix flake check` (or
`nix build .#checks.<system>.<ghc>`) builds the package and runs all test
suites against every supported GHC. The CI nix job now uses it. The
error-location test suite skips itself (with a message) when the ambient
`ghc` has no circuit-notation package registered — as during a plain nix
build of the package, where it previously failed.
* Fix the source location of type errors on a bus. Since bus tagging was
introduced, such errors pointed at the end of the `circuit` block rather than
at the offending statement. Generated bindings are now located at their
circuit expression so GHC blames the right line. Generated bundle patterns
and expressions also take the span of the ports they bundle, so
whole-bundle errors (e.g. sharing a value-level variable across clock
domains) are blamed on the ports rather than on the head of the circuit.

## 0.2.0.0 -- 2026-04-23

* Start of the changelog
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,77 @@

This is a plugin for manipulating circuits in clash with arrow notation. See example/Example.hs for
example usage. Also see [clash-protocols](https://github.com/clash-lang/clash-protocols#).

## Value-level ports (`SignalV` / `FwdV`)

The `SignalV` and `FwdV` markers describe a circuit's logic over the *values
sampled each clock cycle* instead of over whole buses, right inside an
ordinary `circuit` block:

- `SignalV n <- … -< …` binds `n` to the per-cycle value carried on that bus.
- `… -< SignalV e` injects the per-cycle value `e` back onto a bus.

(The bus-level markers, which bind the raw forward channel, still exist and
can be mixed freely with value markers in one block: `Fwd` works on any bus,
while `Signal` and `DSignal` additionally enforce that the bus is a `Signal`
or `DSignal` — which also helps type inference, since it pins the bus type.)

The two value markers differ in what buses they accept:

- `SignalV x` asserts the bus *is* a `Signal dom a`; it pins the bus type
and so gives the best type inference (it works against fully generic
sub-circuits like `idC`).
- `FwdV x` samples (or drives) the forward channel of *any* signal-like
bus — any `SignalBus` instance: `Signal`s, `Vec`s and tuples of
signal-like buses (sampled as `Vec`s/tuples of values), and custom buses
given a one-line instance. In exchange, the bus type must be determined by
context (the circuit's signature or a concretely typed sub-circuit), and
pattern uses need a trivial backwards channel (`TrivialBwd (Bwd t)`).
- `DSignalV x` is `SignalV` for delayed signals (`DSignal dom d a`). The
delay index is part of the bus type, so everything in one logic group must
sit at the *same pipeline depth* (combining values from different stages
is a type error, like mixing clock domains), and since the lifted logic is
combinational, a group's outputs are produced at the delay its inputs are
sampled at. Groups at different depths can coexist in one block. Plain and
delayed values can't meet in one group; the plugin reports mixing them.

Everything in between — the `let` bindings of the do block — is ordinary pure
Haskell, and feedback loops are written as ordinary recursive `let`s:

```haskell
counter3 :: Circuit () (Signal dom Int)
counter3 = circuit do
SignalV n <- registerC 0 -< SignalV n' -- n :: Int (this cycle's value)
SignalV m <- registerC 8 -< SignalV m' -- m :: Int
let n' = n + 1 -- pure, value-level
m' = m + 1
idC -< SignalV (n' + m')
```

The plugin collects the value-level bindings into pure functions, lifts them
to the signal level with `fmap` (using `bundle`/`unbundle` to group the
buses), and ties feedback knots with lazy let bindings. See
example/ValueCircuits.hs for more examples and the expansion of `counter3`.

A single block can span several clock domains: the value-level bindings are
split into groups connected by shared variables, and each group is lifted
independently, so only buses whose values actually meet must share a clock
domain. Two independent counters on two different domains can live in one
block; making their values meet (e.g. `SignalV (n + m)`) is an
unsynchronized clock domain crossing and is rejected by the type checker
(cross between domains with explicit bus-level synchronizer circuits
instead).

Notes:

- Pattern match down to *exactly* the signal layer, no shallower; the
plugin cannot (yet) know which types contain signals, so the boundary has
to be explicit. Marking a bus with `SignalV` when it is not a `Signal`
(e.g. a `Vec` of signals) is a type error on the offending statement —
use `FwdV` to sample such buses whole.
- `let` statements that use value-level variables form the bodies of the
generated logic functions; `let`s that don't touch value land (e.g. a
let-bound sub-circuit) stay at the bus level and can be used with `-<`.
- The grouping is syntactic and conservative: shadowing a value-level name
inside a `let` can merge groups that wouldn't strictly need to share a
domain (never the other way around).
5 changes: 5 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
packages: .

-- So that a plain `cabal build && cabal test` drops a .ghc.environment file,
-- letting the error-location test's bare `ghc` find the circuit-notation
-- plugin (the same mechanism CI uses to compile the Example module).
write-ghc-environment-files: always

21 changes: 19 additions & 2 deletions circuit-notation.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cabal-version: 2.4
name: circuit-notation
version: 0.2.0.0
version: 0.3.0.0
synopsis: Source plugin for manipulating circuits in Clash using arrow notation
description:
Source plugin for manipulating circuits in Clash using arrow notation.
Expand Down Expand Up @@ -62,7 +62,9 @@ test-suite library-testsuite
default-language: Haskell2010
type: exitcode-stdio-1.0
main-is: unittests.hs
other-modules: Example
other-modules:
Example
ValueCircuits
hs-source-dirs:
tests
example
Expand All @@ -71,3 +73,18 @@ test-suite library-testsuite
base,
circuit-notation,
clash-prelude >=1.0,

-- Checks that type errors on a bus point at the offending statement rather than
-- at the end of the circuit (see tests/fixtures/BusError.hs).
test-suite error-location
default-language: Haskell2010
type: exitcode-stdio-1.0
main-is: error-location.hs
hs-source-dirs: tests
build-depends:
base,
circuit-notation,
clash-prelude >=1.0,
directory,
filepath,
process,
Loading
Loading