Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,54 @@
# 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.

A block can span several clock domains: the value-level bindings are
split into groups connected by shared variables and each group is lifted
with its own `fmap`/`bundle`/`unbundle`, so only buses whose values
actually meet must share a domain. Sharing a value across domains is
rejected by the type checker. Lets that don't touch value land stay at
the bus level, so let-bound sub-circuits can be used with `-<`.

The value markers have distinct semantics: `SignalV x` asserts the
bus is a `Signal` (best inference — it works against fully generic
sub-circuits); `FwdV x` samples/drives the forward channel of any
signal-like bus via the new `SignalBus` class (`Signal`s, `Vec`s and
tuples of them, custom buses) but needs the bus type determined by
context; and `DSignalV x` is `SignalV` for delayed signals — the delay
index is part of the bus type, so a logic group's values must all sit at
the same pipeline depth, and its outputs are produced at that depth.
Mixing plain and delayed markers in one group is reported by the plugin.

The value boundary is generated with the new `SigTag`, `FwdTag` and
`DSigTag` pattern synonyms (`Circuit` module); `SigTag`/`DSigTag` pin the
bus type so that type inference survives nested circuits (the `Fwd`
family is not injective) and "too shallow" `SignalV` markers report a
direct `Vec`-vs-`Signal` style mismatch. **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.

## 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).
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