Clash 1.10 released

Page content

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:

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.

About Rowan Goemans
Rowan Goemans has been the Clash lead at QBayLogic since 2026, after working with Clash for years in industry.