Clash 1.10 released
Clash 1.10 is out! You can find the full list of changes in the CHANGELOG, but I want to use this post to highlight the parts that matter most when moving an existing design forward.
This is also a personal milestone: I joined QBayLogic as the new Clash lead in January 2026, and Clash 1.10 is the first release made in that role. My immediate focus is to keep Clash moving in a practical direction: maintaining the compiler, making the ecosystem easier to use, writing great documentation, and spending engineering time on the places where users currently lose the most time.
Highlights🔗️
Clash 1.10 adds support for GHC 9.12, removes support for GHC 9.4 and older, and contains a large set of fixes in the compiler and Prelude. A few additions are worth calling out:
Clash.Class.NumConvert, a new API for safe numeric conversions.- More time-related domain types in
Clash.Signal, such asSeconds,Milliseconds,DomainToHz,PeriodToCycles, andClockDivider. maybeResizeandmaybeTruncateB, for resizing while checking whether information would be lost.- Constant-friendly signal operators such as
(.==),(==.),(.<=), and(.&&). HasFieldsupport forSignal dom r, making record dot access work through signals.- Several new zero-width related instances, including support for
Index 0in more places.
The rest of this post focuses on the two larger API-level changes: users of
Clash.Prelude.DataFlow
must move to clash-protocols, and porting code may need small type fixes.
From Clash.Prelude.DataFlow to clash-protocols🔗️
The deprecated Clash.Prelude.DataFlow
module has been removed in Clash 1.10.
If you still depend on it, the recommended replacement is
clash-protocols.
This is more than a module rename. Clash.Prelude.DataFlow
was great for what it did, but we found that there were severe limitations in
the way it worked.
clash-protocols is a ground-up rethink of how to handle protocols in Clash.
People who actively follow the Clash ecosystem may know that this package has existed in an unreleased state for a few years already. We finally feel comfortable releasing it, as it has seen extensive usage in large Clash projects such as Bittide.
If you are a user of Clash.Prelude.DataFlow
and have not moved to clash-protocols,
please take the time to read the README.
The Df protocol will be of particular interest to you.
NumConvert🔗️
A release highlight is Clash.Class.NumConvert.
Haskell programmers usually reach for
fromIntegral
when converting between number types, but in Clash that can hide two different
issues.
First, fromIntegral goes through
Integer;
in generated HDL, Clash currently represents
Integer
and
Natural
as 64-bit values. That means a conversion such as
Unsigned
96 ->
Unsigned
128 can silently lose bits in hardware even though simulation looks fine.
Second, fromIntegral
allows conversions that cannot represent every input value, such as
Signed
8 ->
Unsigned
8.
NumConvert
makes the safe case explicit through
numConvert:
numConvert :: NumConvert a b => a -> b
If every value of the input type fits in the output type, the conversion
typechecks. If not, use the checked version,
maybeNumConvert,
with a
MaybeNumConvert
constraint:
maybeNumConvert :: MaybeNumConvert a b => a -> Maybe b
This deserves more space than a release announcement can give it. A dedicated
NumConvert
blog post is already in preparation, and will dig into the motivation, the
design, and how to add support for your own numeric types.
Porting notes🔗️
Most code should move to Clash 1.10 without major changes, but there are two type-level changes worth checking deliberately.
Index bit width🔗️
The bit width of Index
n is now defined using
CLogWZ 2 n 0
instead of
CLog 2 n.
CLogWZ
is a version of
CLog
that is well defined for zero.
CLog 2 0
does not reduce, whereas
CLogWZ 2 0 0
reduces to 0. The practical reason is zero-width support:
Index 0
now has a well-defined bit size and a wider set of instances.
Code that manually mirrored the old implementation may need to change:
-- Before
type IndexWidth n = CLog 2 n
-- After
type IndexWidth n = CLogWZ 2 n 0
ghc-typelits-natnormalise🔗️
A fix
in ghc-typelits-natnormalise can expose type errors in code that previously typechecked by accident.
The plugin used to unify under non-injective type families. In other words, from a constraint like:
CLog 2 n ~ CLog 2 q
it could conclude:
n ~ q
That conclusion is not valid.
CLog
is not injective: different inputs can have the same result. For example:
type CLogThree = CLog 2 3 -- 2
type CLogFour = CLog 2 4 -- 2
but 3 and 4 are still different type-level naturals.
So code relying on the plugin to recover n ~ q from a non-injective type family result may
now fail to typecheck. The fix is often to add type annotations, or to manually show
that the given n and q are equal.
Closing🔗️
It has been a while since the last major release. As the Clash team, we aim to release new major versions more frequently than in the past.
Thanks to everyone who contributed patches, reported issues, or maintained downstream packages. Those reports are what make it possible to turn compiler releases into steady improvements for real designs.