diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,72 @@
 # Changelog for the Clash project
 
+## 1.10.1 *Aug 27th, 2026*
+
+Highlights:
+ * Very significant performance improvements, further detailed below. We also created a site https://clash-lang.github.io/clash-benchmarks/ where we track performance improvements. These graphs are for the current `master` branch, not for released Clash versions.
+ * We have added support for the `checked-literals` package. Numeric literals (e.g., `5` or `3.2`) can now be checked at compile time. That is, you'll get a compile error if the literal does not fit the target type -- even in polymorphic contexts! You can enable the checker by adding the following to your project's Cabal file:
+
+    ```
+    library
+      [..]
+
+      build-depends:
+        [..]
+        checked-literals
+
+      ghc-options: -fplugin=CheckedLiterals
+    ```
+
+    See https://clash-lang.org/blog/2026-04-07-checked-literals/.
+
+Performance:
+* `clash` and `clashi` now use GHC-style RTS defaults through executable `-with-rtsopts` flags instead of linking RTS hooks. The existing unused hooks have been removed. **Existing users should set `-with-rtsopts` to `-K512M -H -I5 -T` if they use their own Clash executable. These flags has been shown to give a ~25% compilation speed up in larger designs.** If you used a Clash starter project to initialize your design, you should change:
+
+    ```haskell
+    executable clash
+      [..]
+    ```
+
+    To:
+
+    ```haskell
+    executable clash
+      [..]
+      ghc-options: "-with-rtsopts=-K512M -H -I5 -T"
+    ```
+
+    in the cabal file. See [#3252](https://github.com/clash-lang/clash-compiler/issues/3252).
+* Improved normalization performance for repeated top-down rewrite loops by avoiding full root restarts after every successful rewrite. This gives a ~17% performance improvements on larger designs [#3249](https://github.com/clash-lang/clash-compiler/issues/3249).
+* Clash now converts GHC Core to its own Clash Core in parallel. For larger projects, this can shave off ~40% of load times. See [#3299](https://github.com/clash-lang/clash-compiler/issues/3299).
+* Clash now prunes the binders it collects from top modules when loading from precompiled core. For larger projects, this can shave off 15% of package loading times. See [#3298](https://github.com/clash-lang/clash-compiler/issues/3298).
+* `flattenCallTree` now caches intermediate results, reducing Clash normalization by 10% for realistic designs. Fixes #[3246](https://github.com/clash-lang/clash-compiler/issues/3246).
+
+Added:
+* `-fclash-debug-manifest-hash`. When enabled, Clash emits a `__debug_hash` object in `clash-manifest.json` listing the SHA256 of each input that feeds into the top-level `hash` (`tops`, `prim_map`, `clash_mod_date`, `call_graph`, `opts`). As the name implies, this should only be used to debug and should not be relied upon by tooling. See [#3280](https://github.com/clash-lang/clash-compiler/pull/3280).
+* `SaturatingNum` instances for `Erroring`, `Overflowing`, `Saturating`, `Wrapping`, and `Zeroing`.
+
+Changed:
+* Replaced the deprecated `data-binary-ieee754` dependency with `castDoubleToWord64`, `castFloatToWord32`, `castWord32ToFloat`, and `castWord64ToDouble` from `GHC.Float`. See [#3174](https://github.com/clash-lang/clash-compiler/issues/3174).
+* `flake.nix` now advertises the [clash-lang Cachix binary cache](https://clash-lang.cachix.org) via `nixConfig.extra-substituters`. Running `nix develop` will prompt you to trust the cache, avoiding having to build Clash from source. See [#3213](https://github.com/clash-lang/clash-compiler/issues/3213).
+
+Fixed:
+* Clash no longer crashes for self-recursive global binders in very specific circumstances. See [#3311](https://github.com/clash-lang/clash-compiler/issues/3311).
+* Clash no longer crashes upon calling `sequenceA`/`traverse#` on a zero-sized `Vec`. See [#3290](https://github.com/clash-lang/clash-compiler/issues/3290).
+* Clash no longer crashes when constant-folding partial primitives in the GHC evaluator. This covers `shiftL`/`shiftR`/`rotateL`/`rotateR` with negative shift amounts on `BitVector`/`Signed`/`Unsigned`, `(^)` with negative exponents, `chr` on out-of-range integers, and `quot`/`rem`/`div`/`mod` by zero on `Int`/`Word`/`Int{8,16,32,64}`/`Word{8,16,32,64}`. Such expressions now fold to `undefined` (rather than crashing the compiler). Fixes [#3234](https://github.com/clash-lang/clash-compiler/issues/3234).
+* Clash no longer crashes with `mkVecNil: failed to instantiate Nil DC` when unfolding `traverse#` in very specific circumstances. See [#3291](https://github.com/clash-lang/clash-compiler/issues/3291).
+* Clash no longer errors when two `-i` import paths refer to the same directory via different syntactic spellings (e.g. `-isrc -isrc/.`) and a data file lives in that directory. See [#3142](https://github.com/clash-lang/clash-compiler/issues/3142).
+* Clash no longer produces an error when using `dataToTag` in combination with custom bit representations. See [#2724](https://github.com/clash-lang/clash-compiler/issues/2724).
+* Clash no longer takes exponential time and memory normalizing `foldl`/`scanl`/`splitAt` over a vector when the result keeps the (co-recursively defined) vector spine alive, e.g. `snd (foldl const (z, s) (xs :: Vec n a))`. The evaluator's `zipWith` and `splitAt` reductions now share their arguments instead of duplicating the vector spine on every peel. See [#3308](https://github.com/clash-lang/clash-compiler/issues/3308).
+* Clash now recognizes `~` when solving GADT arms. Fixes [#3232](https://github.com/clash-lang/clash-compiler/issues/3232).
+* Clash will no longer generate duplicate `attribute` when they're both used for top entity ports and internal signals. See [#3218](https://github.com/clash-lang/clash-compiler/issues/3218). The VHDL backend now also reports a clearer error when a synthesis attribute is declared with conflicting types in the same design.
+* Due to HDL standards, when the result of a top entity is read by another binder Clash is forced to introduce an internal indirection signal. Clash no longer attaches the top entity's `Annotate` synthesis attributes to that internal signal, preventing duplicate synthesis attributes in the generated HDL. See [#3224](https://github.com/clash-lang/clash-compiler/issues/3224).
+* Errors in `Synthesize` port annotations (e.g. a `PortProduct` on a non-product port) are now reported before a design is normalized, instead of afterwards. This avoids waiting for a potentially long normalization only to get a trivial port error. See [#3305](https://github.com/clash-lang/clash-compiler/issues/3305).
+* Run `bindConstantVar` after post-normalization `inlineCleanup`/`caseCon` so constant let-bindings exposed late are inlined before netlist generation [#3041](https://github.com/clash-lang/clash-compiler/issues/3041).
+* The VHDL primitives for `integerToNaturalThrow` and `integerToNaturalClamp` no longer contain syntax errors. See [#3315](https://github.com/clash-lang/clash-compiler/pull/3315).
+* The internal function `Clash.Util.Interpolate.i` in `clash-lib` no longer inserts an extra blank line after a paragraph that gets reflowed onto multiple lines. See [#2753](https://github.com/clash-lang/clash-compiler/issues/2753).
+* `deriveAutoReg` no longer fails on GHC versions where `KnownNat` lives in `GHC.Internal.TypeNats` rather than `GHC.TypeNats`. See [#3100](https://github.com/clash-lang/clash-compiler/issues/3100).
+* `makeTopEntity` now accounts for unary product types. See [#3066](https://github.com/clash-lang/clash-compiler/issues/3066).
+
 ## 1.10.0 *Apr 23rd, 2026*
 
 Release highlight:
diff --git a/clash-prelude.cabal b/clash-prelude.cabal
--- a/clash-prelude.cabal
+++ b/clash-prelude.cabal
@@ -1,6 +1,6 @@
 Cabal-version:        2.2
 Name:                 clash-prelude
-Version:              1.10.0
+Version:              1.10.1
 Synopsis:             Clash: a functional hardware description language - Prelude library
 Description:
   Clash is a functional hardware description language that borrows both its
@@ -277,6 +277,7 @@
                       Clash.Sized.Vector.ToTuple.TH
 
                       Clash.Sized.Internal.BitVector
+                      Clash.Sized.Internal.CheckedLiterals
                       Clash.Sized.Internal.Index
                       Clash.Sized.Internal.Mod
                       Clash.Sized.Internal.Signed
@@ -328,7 +329,6 @@
                       bytestring                >= 0.10.8  && < 0.13,
                       constraints               >= 0.9     && < 1.0,
                       containers                >= 0.4.0   && < 0.9,
-                      data-binary-ieee754       >= 0.4.4   && < 0.6,
                       data-default              >= 0.7     && < 0.9,
                       deepseq                   >= 1.4.1.0 && < 1.6,
                       distributive              >= 0.1     && < 1.0,
@@ -342,18 +342,19 @@
                       half                      >= 0.2.2.3 && < 1.0,
                       infinite-list           ^>= 0.1,
                       lens                      >= 4.10    && < 5.4,
-                      QuickCheck                >= 2.7     && < 2.16,
+                      QuickCheck                >= 2.7     && < 2.19,
                       recursion-schemes         >= 5.1     && < 5.3,
                       reflection                >= 2       && < 2.2,
+                      checked-literals          >= 0.1.1   && < 0.2,
                       singletons                >= 2.0     && < 3.1,
-                      string-interpolate        ^>= 0.3,
+                      string-interpolate        >= 0.3     && < 1.1,
                       tagged                    >= 0.8     && < 0.9,
                       template-haskell          >= 2.20.0.0 && < 2.24,
                       th-abstraction            >= 0.3.0 && < 0.8.0,
                       th-lift                   >= 0.7.0    && < 0.9,
                       th-orphans                >= 0.13.1   && < 1.0,
                       text                      >= 0.11.3.1 && < 2.2,
-                      time                      >= 1.8     && < 1.15,
+                      time                      >= 1.8     && < 1.17,
                       transformers              >= 0.5.2.0 && < 0.7,
                       type-errors               >= 0.2.0.0 && < 0.3,
                       uniplate                  >= 1.6.12  && < 1.7,
@@ -402,6 +403,7 @@
   else
     build-depends:
       clash-prelude,
+      checked-literals,
 
       ghc-typelits-knownnat,
       ghc-typelits-natnormalise,
@@ -412,16 +414,19 @@
       bytestring,
       containers,
       deepseq,
-      hedgehog      >= 1.0.3    && < 1.6,
+      hedgehog      >= 1.0.3    && < 1.8,
       hint          >= 0.7      && < 0.10,
       mmorph        >= 1.1.5    && < 1.3,
+      process,
       quickcheck-classes-base >= 0.6 && < 1.0,
+      string-interpolate >=0.3.4.0,
       tasty         >= 1.2      && < 1.6,
       tasty-hedgehog >= 1.2.0,
       tasty-hunit,
       tasty-th,
       tasty-quickcheck,
       template-haskell,
+      temporary,
       text
 
   Other-Modules:
@@ -431,6 +436,15 @@
                  Clash.Tests.BitVector
                  Clash.Tests.BlockRam
                  Clash.Tests.BlockRam.Blob
+                 Clash.Tests.CheckedLiterals
+                 Clash.Tests.CheckedLiterals.BitVector
+                 Clash.Tests.CheckedLiterals.Common
+                 Clash.Tests.CheckedLiterals.Fixed
+                 Clash.Tests.CheckedLiterals.Index
+                 Clash.Tests.CheckedLiterals.Signals
+                 Clash.Tests.CheckedLiterals.Signed
+                 Clash.Tests.CheckedLiterals.Unsigned
+                 Clash.Tests.CheckedLiterals.Wrappers
                  Clash.Tests.Clocks
                  Clash.Tests.Counter
                  Clash.Tests.DumpVCD
@@ -461,6 +475,7 @@
 
                  Hedgehog.Extra
 
+                 Test.Tasty.AssertGhc
                  Test.Tasty.HUnit.Extra
                  Test.Tasty.Hedgehog.Extra
                  Test.QuickCheck.Extra
diff --git a/src/Clash/Annotations/TH.hs b/src/Clash/Annotations/TH.hs
--- a/src/Clash/Annotations/TH.hs
+++ b/src/Clash/Annotations/TH.hs
@@ -255,6 +255,7 @@
   = c >>= \case
     Complete []  -> return $ Complete [PortName name]
     Complete [PortName n2] -> return $ Complete [PortName (name ++ "_" ++ n2)]
+    Complete [PortProduct "" ys] -> return $ Complete [PortProduct name ys]
     Complete xs  -> return $ Complete [PortProduct name xs]
     x            -> return x
 
diff --git a/src/Clash/Class/AutoReg/Internal.hs b/src/Clash/Class/AutoReg/Internal.hs
--- a/src/Clash/Class/AutoReg/Internal.hs
+++ b/src/Clash/Class/AutoReg/Internal.hs
@@ -341,7 +341,7 @@
 
 constraintsWantedFor :: Name -> [Type] -> Q Cxt
 constraintsWantedFor clsNm tys
-  | show clsNm == "GHC.TypeNats.KnownNat" = do
+  | clsNm == ''KnownNat = do
   -- KnownNat is special, you can't just lookup instances with reifyInstances.
   -- So we just pass KnownNat constraints.
   -- This will most likely require UndecidableInstances.
diff --git a/src/Clash/Class/BitPack/Internal.hs b/src/Clash/Class/BitPack/Internal.hs
--- a/src/Clash/Class/BitPack/Internal.hs
+++ b/src/Clash/Class/BitPack/Internal.hs
@@ -27,8 +27,8 @@
 
 import Prelude                        hiding (map)
 
-import Data.Binary.IEEE754            (doubleToWord, floatToWord, wordToDouble,
-                                       wordToFloat)
+import GHC.Float                      (castDoubleToWord64, castFloatToWord32,
+                                       castWord32ToFloat, castWord64ToDouble)
 
 import Data.Char                      (chr, ord)
 import Data.Complex                   (Complex)
@@ -398,12 +398,12 @@
   unpack = checkUnpackUndef unpackFloat#
 
 packFloat# :: Float -> BitVector 32
-packFloat# = fromIntegral . floatToWord
+packFloat# = fromIntegral . castFloatToWord32
 {-# OPAQUE packFloat# #-}
 {-# ANN packFloat# hasBlackBox #-}
 
 unpackFloat# :: BitVector 32 -> Float
-unpackFloat# (unsafeToNatural -> w) = wordToFloat (fromIntegral w)
+unpackFloat# (unsafeToNatural -> w) = castWord32ToFloat (fromIntegral w)
 {-# OPAQUE unpackFloat# #-}
 {-# ANN unpackFloat# hasBlackBox #-}
 
@@ -413,12 +413,12 @@
   unpack = checkUnpackUndef unpackDouble#
 
 packDouble# :: Double -> BitVector 64
-packDouble# = fromIntegral . doubleToWord
+packDouble# = fromIntegral . castDoubleToWord64
 {-# OPAQUE packDouble# #-}
 {-# ANN packDouble# hasBlackBox #-}
 
 unpackDouble# :: BitVector 64 -> Double
-unpackDouble# (unsafeToNatural -> w) = wordToDouble (fromIntegral w)
+unpackDouble# (unsafeToNatural -> w) = castWord64ToDouble (fromIntegral w)
 {-# OPAQUE unpackDouble# #-}
 {-# ANN unpackDouble# hasBlackBox #-}
 
diff --git a/src/Clash/Num/Erroring.hs b/src/Clash/Num/Erroring.hs
--- a/src/Clash/Num/Erroring.hs
+++ b/src/Clash/Num/Erroring.hs
@@ -1,11 +1,12 @@
-{-
-Copyright   : (C) 2021, QBayLogic B.V.
+{-|
+Copyright   : (C) 2021-2026, QBayLogic B.V.
 License     : BSD2 (see the file LICENSE)
 Maintainer  : QBayLogic B.V. <devops@qbaylogic.com>
 -}
 
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 
 module Clash.Num.Erroring
   ( Erroring
@@ -20,6 +21,14 @@
 import Data.Functor.Compose (Compose(..))
 import Data.Hashable (Hashable)
 import GHC.TypeLits (KnownNat, type (+))
+import CheckedLiterals.Class.Integer
+  ( CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
+import CheckedLiterals.Class.Rational
+  ( CheckedNegativeRationalLiteral
+  , CheckedPositiveRationalLiteral
+  )
 import Test.QuickCheck (Arbitrary)
 
 import Clash.Class.BitPack (BitPack)
@@ -57,6 +66,22 @@
 toErroring :: (SaturatingNum a) => a -> Erroring a
 toErroring = Erroring
 
+instance
+  (CheckedPositiveIntegerLiteral lit a) =>
+  CheckedPositiveIntegerLiteral lit (Erroring a)
+
+instance
+  (CheckedNegativeIntegerLiteral lit a) =>
+  CheckedNegativeIntegerLiteral lit (Erroring a)
+
+instance
+  (CheckedPositiveRationalLiteral str num den a) =>
+  CheckedPositiveRationalLiteral str num den (Erroring a)
+
+instance
+  (CheckedNegativeRationalLiteral str num den a) =>
+  CheckedNegativeRationalLiteral str num den (Erroring a)
+
 instance (Resize f) => Resize (Compose Erroring f) where
   {-# INLINE resize #-}
   resize
@@ -110,6 +135,22 @@
   -- is not in range (typically wrapping). It would be better if this also
   -- threw an XException, but in a way which remained synthesizable.
   fromInteger = coerce (fromInteger @a)
+
+instance (Ord a, SaturatingNum a) => SaturatingNum (Erroring a) where
+  {-# INLINE satAdd #-}
+  satAdd mode (Erroring a) (Erroring b) = Erroring $ satAdd mode a b
+
+  {-# INLINE satSub #-}
+  satSub mode (Erroring a) (Erroring b) = Erroring $ satSub mode a b
+
+  {-# INLINE satMul #-}
+  satMul mode (Erroring a) (Erroring b) = Erroring $ satMul mode a b
+
+  {-# INLINE satSucc #-}
+  satSucc mode (Erroring a) = Erroring $ satSucc mode a
+
+  {-# INLINE satPred #-}
+  satPred mode (Erroring a) = Erroring $ satPred mode a
 
 instance (Enum a, SaturatingNum a) => Enum (Erroring a) where
   {-# INLINE succ #-}
diff --git a/src/Clash/Num/Overflowing.hs b/src/Clash/Num/Overflowing.hs
--- a/src/Clash/Num/Overflowing.hs
+++ b/src/Clash/Num/Overflowing.hs
@@ -1,11 +1,13 @@
 {-|
-Copyright  :  (C) 2021-2022, QBayLogic B.V.
+Copyright  :  (C) 2021-2026, QBayLogic B.V.
 License    :  BSD2 (see the file LICENSE)
 Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
 -}
 
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -25,6 +27,14 @@
 import Data.Hashable (Hashable)
 import GHC.Generics (Generic)
 import GHC.TypeLits (KnownNat, type (+))
+import CheckedLiterals.Class.Integer
+  ( CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
+import CheckedLiterals.Class.Rational
+  ( CheckedNegativeRationalLiteral
+  , CheckedPositiveRationalLiteral
+  )
 
 import Clash.Class.BitPack (BitPack(..))
 import Clash.Class.Num (SaturationMode(SatWrap, SatZero), SaturatingNum(..))
@@ -55,6 +65,22 @@
 clearOverflow :: Overflowing a -> Overflowing a
 clearOverflow x = x { hasOverflowed = False }
 
+instance
+  (CheckedPositiveIntegerLiteral lit a) =>
+  CheckedPositiveIntegerLiteral lit (Overflowing a)
+
+instance
+  (CheckedNegativeIntegerLiteral lit a) =>
+  CheckedNegativeIntegerLiteral lit (Overflowing a)
+
+instance
+  (CheckedPositiveRationalLiteral str num den a) =>
+  CheckedPositiveRationalLiteral str num den (Overflowing a)
+
+instance
+  (CheckedNegativeRationalLiteral str num den a) =>
+  CheckedNegativeRationalLiteral str num den (Overflowing a)
+
 instance (Eq a) => Eq (Overflowing a) where
   {-# INLINE (==) #-}
   (==) = (==) `on` fromOverflowing
@@ -138,6 +164,75 @@
 instance (Bounded a) => Bounded (Overflowing a) where
   minBound = Overflowing minBound False
   maxBound = Overflowing maxBound False
+
+-- | NOTE: Usage of this instance might introduce redundant circuitry
+-- for the overflow check (depending on the utilized 'SaturationMode'
+-- or inner type), which is not guaranteed to be optimized away at a
+-- later synthesis stage.
+--
+-- The 'hasOverflowed' flag still will be set even when the 'SaturatingNum'
+-- operation has prevented the overflow:
+-- @hasOverflowed (satSucc SatBound maxBound) == True@
+instance (Ord a, SaturatingNum a) => SaturatingNum (Overflowing a) where
+  {-# INLINE satAdd #-}
+  satAdd mode (Overflowing x a) (Overflowing y b)
+    | y > 0
+    , x > satSub SatWrap maxBound y
+    = withOverflow True
+
+    | y < 0
+    , x < satSub SatWrap minBound y
+    = withOverflow True
+
+    | otherwise
+    = withOverflow (a || b)
+   where
+    withOverflow =
+      let r = satAdd mode x y
+       in seq r $ Overflowing r
+
+  {-# INLINE satSub #-}
+  satSub mode (Overflowing x a) (Overflowing y b)
+    | y < 0
+    , x > satAdd SatWrap maxBound y
+    = withOverflow True
+
+    | y > 0
+    , x < satAdd SatWrap minBound y
+    = withOverflow True
+
+    | otherwise
+    = withOverflow (a || b)
+   where
+    withOverflow =
+      let r = satSub mode x y
+       in seq r $ Overflowing r
+
+  {-# INLINE satMul #-}
+  satMul mode (Overflowing x a) (Overflowing y b)
+    | x /= 0
+    , y /= 0
+    , satMul SatZero x y == 0
+    = withOverflow True
+
+    | otherwise
+    = withOverflow (a || b)
+   where
+    withOverflow =
+      let r = satMul mode x y
+       in seq r $ Overflowing r
+
+  {-# INLINE satSucc #-}
+  satSucc mode (Overflowing x a) =
+    let xSucc = satSucc mode x
+     in seq xSucc
+      $ Overflowing xSucc (a || x == maxBound)
+
+  {-# INLINE satPred #-}
+  satPred mode (Overflowing x a) =
+    let xPred = satPred mode x
+     in seq xPred
+      $ Overflowing (satPred mode x) (a || x == minBound)
 
 instance (Enum a, Eq a, SaturatingNum a) => Enum (Overflowing a) where
   succ (Overflowing x a)
diff --git a/src/Clash/Num/Saturating.hs b/src/Clash/Num/Saturating.hs
--- a/src/Clash/Num/Saturating.hs
+++ b/src/Clash/Num/Saturating.hs
@@ -1,11 +1,12 @@
-{-
-Copyright   : (C) 2021, QBayLogic B.V.
+{-|
+Copyright   : (C) 2021-2026, QBayLogic B.V.
 License     : BSD2 (see the file LICENSE)
 Maintainer  : QBayLogic B.V. <devops@qbaylogic.com>
 -}
 
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 
 module Clash.Num.Saturating
   ( Saturating
@@ -20,6 +21,14 @@
 import Data.Functor.Compose (Compose(..))
 import Data.Hashable (Hashable)
 import GHC.TypeLits (KnownNat, type (+))
+import CheckedLiterals.Class.Integer
+  ( CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
+import CheckedLiterals.Class.Rational
+  ( CheckedNegativeRationalLiteral
+  , CheckedPositiveRationalLiteral
+  )
 import Test.QuickCheck (Arbitrary)
 
 import Clash.Class.BitPack (BitPack)
@@ -57,6 +66,22 @@
 toSaturating :: (SaturatingNum a) => a -> Saturating a
 toSaturating = Saturating
 
+instance
+  (CheckedPositiveIntegerLiteral lit a) =>
+  CheckedPositiveIntegerLiteral lit (Saturating a)
+
+instance
+  (CheckedNegativeIntegerLiteral lit a) =>
+  CheckedNegativeIntegerLiteral lit (Saturating a)
+
+instance
+  (CheckedPositiveRationalLiteral str num den a) =>
+  CheckedPositiveRationalLiteral str num den (Saturating a)
+
+instance
+  (CheckedNegativeRationalLiteral str num den a) =>
+  CheckedNegativeRationalLiteral str num den (Saturating a)
+
 instance (Resize f) => Resize (Compose Saturating f) where
   {-# INLINE resize #-}
   resize
@@ -109,6 +134,22 @@
   -- is not in range (typically wrapping). It would be better if this also
   -- saturated, but in a way which remained synthesizable.
   fromInteger = coerce (fromInteger @a)
+
+instance (Ord a, SaturatingNum a) => SaturatingNum (Saturating a) where
+  {-# INLINE satAdd #-}
+  satAdd mode (Saturating a) (Saturating b) = Saturating $ satAdd mode a b
+
+  {-# INLINE satSub #-}
+  satSub mode (Saturating a) (Saturating b) = Saturating $ satSub mode a b
+
+  {-# INLINE satMul #-}
+  satMul mode (Saturating a) (Saturating b) = Saturating $ satMul mode a b
+
+  {-# INLINE satSucc #-}
+  satSucc mode (Saturating a) = Saturating $ satSucc mode a
+
+  {-# INLINE satPred #-}
+  satPred mode (Saturating a) = Saturating $ satPred mode a
 
 instance (Enum a, SaturatingNum a) => Enum (Saturating a) where
   {-# INLINE succ #-}
diff --git a/src/Clash/Num/Wrapping.hs b/src/Clash/Num/Wrapping.hs
--- a/src/Clash/Num/Wrapping.hs
+++ b/src/Clash/Num/Wrapping.hs
@@ -6,6 +6,7 @@
 
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 
 module Clash.Num.Wrapping
   ( Wrapping(..)
@@ -19,6 +20,14 @@
 import Data.Functor.Compose (Compose(..))
 import Data.Hashable (Hashable)
 import GHC.TypeLits (KnownNat, type (+))
+import CheckedLiterals.Class.Integer
+  ( CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
+import CheckedLiterals.Class.Rational
+  ( CheckedNegativeRationalLiteral
+  , CheckedPositiveRationalLiteral
+  )
 import Test.QuickCheck (Arbitrary)
 
 import Clash.Class.BitPack (BitPack)
@@ -56,6 +65,22 @@
 toWrapping :: (SaturatingNum a) => a -> Wrapping a
 toWrapping = Wrapping
 
+instance
+  (CheckedPositiveIntegerLiteral lit a) =>
+  CheckedPositiveIntegerLiteral lit (Wrapping a)
+
+instance
+  (CheckedNegativeIntegerLiteral lit a) =>
+  CheckedNegativeIntegerLiteral lit (Wrapping a)
+
+instance
+  (CheckedPositiveRationalLiteral str num den a) =>
+  CheckedPositiveRationalLiteral str num den (Wrapping a)
+
+instance
+  (CheckedNegativeRationalLiteral str num den a) =>
+  CheckedNegativeRationalLiteral str num den (Wrapping a)
+
 instance (Resize f) => Resize (Compose Wrapping f) where
   {-# INLINE resize #-}
   resize
@@ -107,6 +132,22 @@
   -- is not in range (typically wrapping). It would be better if this also
   -- definitely wrapped, but in a way which remained synthesizable.
   fromInteger = coerce (fromInteger @a)
+
+instance (Bounded a, SaturatingNum a) => SaturatingNum (Wrapping a) where
+  {-# INLINE satAdd #-}
+  satAdd mode (Wrapping a) (Wrapping b) = Wrapping $ satAdd mode a b
+
+  {-# INLINE satSub #-}
+  satSub mode (Wrapping a) (Wrapping b) = Wrapping $ satSub mode a b
+
+  {-# INLINE satMul #-}
+  satMul mode (Wrapping a) (Wrapping b) = Wrapping $ satMul mode a b
+
+  {-# INLINE satSucc #-}
+  satSucc mode (Wrapping a) = Wrapping $ satSucc mode a
+
+  {-# INLINE satPred #-}
+  satPred mode (Wrapping a) = Wrapping $ satPred mode a
 
 instance (Enum a, SaturatingNum a) => Enum (Wrapping a) where
   {-# INLINE succ #-}
diff --git a/src/Clash/Num/Zeroing.hs b/src/Clash/Num/Zeroing.hs
--- a/src/Clash/Num/Zeroing.hs
+++ b/src/Clash/Num/Zeroing.hs
@@ -1,11 +1,12 @@
-{-
-Copyright   : (C) 2021, QBayLogic B.V.
+{-|
+Copyright   : (C) 2021-2026, QBayLogic B.V.
 License     : BSD2 (see the file LICENSE)
 Maintainer  : QBayLogic B.V. <devops@qbaylogic.com>
 -}
 
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 
 module Clash.Num.Zeroing
   ( Zeroing
@@ -20,6 +21,14 @@
 import Data.Functor.Compose (Compose(..))
 import Data.Hashable (Hashable)
 import GHC.TypeLits (KnownNat, type (+))
+import CheckedLiterals.Class.Integer
+  ( CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
+import CheckedLiterals.Class.Rational
+  ( CheckedNegativeRationalLiteral
+  , CheckedPositiveRationalLiteral
+  )
 import Test.QuickCheck (Arbitrary)
 
 import Clash.Class.BitPack (BitPack)
@@ -56,6 +65,22 @@
 toZeroing :: (SaturatingNum a) => a -> Zeroing a
 toZeroing = Zeroing
 
+instance
+  (CheckedPositiveIntegerLiteral lit a) =>
+  CheckedPositiveIntegerLiteral lit (Zeroing a)
+
+instance
+  (CheckedNegativeIntegerLiteral lit a) =>
+  CheckedNegativeIntegerLiteral lit (Zeroing a)
+
+instance
+  (CheckedPositiveRationalLiteral str num den a) =>
+  CheckedPositiveRationalLiteral str num den (Zeroing a)
+
+instance
+  (CheckedNegativeRationalLiteral str num den a) =>
+  CheckedNegativeRationalLiteral str num den (Zeroing a)
+
 instance (Resize f) => Resize (Compose Zeroing f) where
   {-# INLINE resize #-}
   resize
@@ -108,6 +133,22 @@
   -- is not in range (typically wrapping). It would be better if this also
   -- returned zero, but in a way which remained synthesizable.
   fromInteger = coerce (fromInteger @a)
+
+instance (Ord a, SaturatingNum a) => SaturatingNum (Zeroing a) where
+  {-# INLINE satAdd #-}
+  satAdd mode (Zeroing a) (Zeroing b) = Zeroing $ satAdd mode a b
+
+  {-# INLINE satSub #-}
+  satSub mode (Zeroing a) (Zeroing b) = Zeroing $ satSub mode a b
+
+  {-# INLINE satMul #-}
+  satMul mode (Zeroing a) (Zeroing b) = Zeroing $ satMul mode a b
+
+  {-# INLINE satSucc #-}
+  satSucc mode (Zeroing a) = Zeroing $ satSucc mode a
+
+  {-# INLINE satPred #-}
+  satPred mode (Zeroing a) = Zeroing $ satPred mode a
 
 instance (Enum a, SaturatingNum a) => Enum (Zeroing a) where
   {-# INLINE succ #-}
diff --git a/src/Clash/Signal/Delayed/Internal.hs b/src/Clash/Signal/Delayed/Internal.hs
--- a/src/Clash/Signal/Delayed/Internal.hs
+++ b/src/Clash/Signal/Delayed/Internal.hs
@@ -7,6 +7,7 @@
   Maintainer  :  QBayLogic B.V. <devops@qbaylogic.com>
 -}
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE RoleAnnotations #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -38,6 +39,14 @@
 import Data.Default               (Default(..))
 import GHC.TypeLits               (Nat, type (+))
 import Language.Haskell.TH.Syntax (Lift)
+import CheckedLiterals.Class.Integer
+  ( CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
+import CheckedLiterals.Class.Rational
+  ( CheckedNegativeRationalLiteral
+  , CheckedPositiveRationalLiteral
+  )
 import Test.QuickCheck            (Arbitrary, CoArbitrary)
 
 import Clash.Promoted.Nat         (SNat)
@@ -106,6 +115,22 @@
             }
   deriving ( Show, Default, Functor, Applicative, Num, Fractional
            , Foldable, Traversable, Arbitrary, CoArbitrary, Lift )
+
+instance
+  (CheckedPositiveIntegerLiteral lit a) =>
+  CheckedPositiveIntegerLiteral lit (DSignal dom delay a)
+
+instance
+  (CheckedNegativeIntegerLiteral lit a) =>
+  CheckedNegativeIntegerLiteral lit (DSignal dom delay a)
+
+instance
+  (CheckedPositiveRationalLiteral str num den a) =>
+  CheckedPositiveRationalLiteral str num den (DSignal dom delay a)
+
+instance
+  (CheckedNegativeRationalLiteral str num den a) =>
+  CheckedNegativeRationalLiteral str num den (DSignal dom delay a)
 
 -- | Create a 'DSignal' from a list
 --
diff --git a/src/Clash/Signal/Internal.hs b/src/Clash/Signal/Internal.hs
--- a/src/Clash/Signal/Internal.hs
+++ b/src/Clash/Signal/Internal.hs
@@ -15,6 +15,7 @@
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE RoleAnnotations #-}
@@ -210,6 +211,14 @@
 import Language.Haskell.TH.Syntax -- (Lift (..), Q, Dec)
 import Language.Haskell.TH.Compat
 import Numeric.Natural            (Natural)
+import CheckedLiterals.Class.Integer
+  ( CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
+import CheckedLiterals.Class.Rational
+  ( CheckedNegativeRationalLiteral
+  , CheckedPositiveRationalLiteral
+  )
 import System.IO.Unsafe           (unsafeInterleaveIO, unsafePerformIO)
 import Test.QuickCheck            (Arbitrary (..), CoArbitrary(..), Property,
                                    property)
@@ -915,6 +924,22 @@
   abs         = fmap abs
   signum      = fmap signum
   fromInteger = signal# . fromInteger
+
+instance
+  (CheckedPositiveIntegerLiteral lit a) =>
+  CheckedPositiveIntegerLiteral lit (Signal dom a)
+
+instance
+  (CheckedNegativeIntegerLiteral lit a) =>
+  CheckedNegativeIntegerLiteral lit (Signal dom a)
+
+instance
+  (CheckedPositiveRationalLiteral str num den a) =>
+  CheckedPositiveRationalLiteral str num den (Signal dom a)
+
+instance
+  (CheckedNegativeRationalLiteral str num den a) =>
+  CheckedNegativeRationalLiteral str num den (Signal dom a)
 
 instance Bounded a => Bounded (Signal dom a) where
   minBound = pure minBound
diff --git a/src/Clash/Sized/BitVector.hs b/src/Clash/Sized/BitVector.hs
--- a/src/Clash/Sized/BitVector.hs
+++ b/src/Clash/Sized/BitVector.hs
@@ -34,6 +34,9 @@
   , (.<<+)
     -- ** Pattern matching
   , bitPattern
+    -- * Type-level error messages
+  , BitPositiveLiteralError
+  , BitVectorPositiveLiteralError
   )
 where
 
diff --git a/src/Clash/Sized/Fixed.hs b/src/Clash/Sized/Fixed.hs
--- a/src/Clash/Sized/Fixed.hs
+++ b/src/Clash/Sized/Fixed.hs
@@ -68,6 +68,7 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE NegativeLiterals #-}
@@ -106,6 +107,20 @@
   , NumFixedC, ENumFixedC, FracFixedC, ResizeFC, DivideC
     -- * Proxy
   , asRepProxy, asIntProxy
+    -- * Type-level error messages
+  , UFixedPositiveLiteralError
+  , UFixedNegativeLiteralError
+  , SFixedPositiveLiteralError
+  , SFixedNegativeLiteralError
+  , SFixedPositiveRationalRequiredIntBits
+  , SFixedNegativeRationalRequiredIntBits
+  , SFixedPositiveRationalLiteralError
+  , SFixedNegativeRationalLiteralError
+  , FitsPositiveSFixedRational
+  , FitsNegativeSFixedRational
+  , CheckFixedFrac
+  , FixedPointNotPow2Error
+  , FixedPointNotEnoughFracError
   )
 where
 
@@ -115,14 +130,27 @@
 import Data.Data                  (Data)
 import Data.Default               (Default (..))
 import Data.Either                (isLeft)
-import Data.Kind                  (Type)
+import Data.Kind                  (Constraint, Type)
 import Text.Read                  (Read(..))
 import Data.List                  (find)
 import Data.Proxy                 (Proxy (..))
 import Data.Ratio                 ((%), denominator, numerator)
+import Data.Type.Bool             (If)
 import Data.Typeable              (Typeable, TypeRep, typeRep, typeOf)
-import GHC.TypeLits               (KnownNat, Nat, type (+), natVal)
-import GHC.TypeLits.Extra         (Max)
+import GHC.TypeError
+  ( Assert
+  , ErrorMessage (ShowType, Text, (:<>:))
+  )
+import GHC.TypeLits
+  ( Div
+  , KnownNat
+  , Nat
+  , natVal
+  , type (+)
+  , type (-)
+  , type (<=?)
+  )
+import GHC.TypeLits.Extra         (CLog, Max)
 import Language.Haskell.TH        (Q, appT, conT, litT, mkName,
                                    numTyLit, sigE)
 import Language.Haskell.TH.Syntax (Lift(..))
@@ -141,7 +169,25 @@
 import Clash.Class.BitPack.BitReduction (reduceAnd, reduceOr)
 import Clash.Sized.BitVector      (BitVector, (++#))
 import Clash.Sized.Signed         (Signed)
+import Clash.Sized.Internal.CheckedLiterals
+  ( FractionalBitsNote
+  , IntegerBitsNote
+  , NotExactlyRepresentable
+  , NotExactlyRepresentableWithConstraint
+  , OutOfBoundsBecause
+  , PotentiallyOutOfBounds
+  , SignedIntegerBitsNote
+  )
 import Clash.Sized.Unsigned       (Unsigned)
+import CheckedLiterals.Class.Integer
+  ( CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
+import CheckedLiterals.Class.Rational
+  ( CheckedNegativeRationalLiteral
+  , CheckedPositiveRationalLiteral
+  )
+import CheckedLiterals.Class.Rational.TypeNats (IsPowerOfTwo)
 import Clash.XException
   (ShowX (..), NFDataX (..), isX, errorX, showsPrecXWith, fromJustX)
 
@@ -223,6 +269,21 @@
 -- -5.0
 type SFixed = Fixed Signed
 
+type SFixedPositiveRationalRequiredIntBits (num :: Nat) (den :: Nat) =
+  CLog 2 (num + 1) + 1 - CLog 2 den
+
+type SFixedNegativeRationalRequiredIntBits (num :: Nat) (den :: Nat) =
+  CLog 2 num + 1 - CLog 2 den
+
+type family FitsPositiveSFixedRational (num :: Nat) (den :: Nat) (int :: Nat) :: Bool where
+  FitsPositiveSFixedRational 0 den int = 'True
+  FitsPositiveSFixedRational num den int =
+    SFixedPositiveRationalRequiredIntBits num den <=? int
+
+type family FitsNegativeSFixedRational (num :: Nat) (den :: Nat) (int :: Nat) :: Bool where
+  FitsNegativeSFixedRational num den int =
+    SFixedNegativeRationalRequiredIntBits num den <=? int
+
 -- | Unsigned 'Fixed'-point number, with @int@ integer bits and @frac@
 -- fractional bits
 --
@@ -253,6 +314,126 @@
 -- >>> (1 :: UFixed 3 4) `sub` (3 :: UFixed 3 4) :: UFixed 4 4
 -- 14.0
 type UFixed = Fixed Unsigned
+
+type UFixedPositiveLiteralError strLit lit int =
+  PotentiallyOutOfBounds
+    strLit
+    (IntegerBitsNote (CLog 2 (lit + 1)))
+    (CLog 2 (lit + 1))
+    int
+
+instance
+  ( Assert
+      (If (lit <=? 0) (lit <=? 0) (CLog 2 (lit + 1) <=? int))
+      (UFixedPositiveLiteralError (ShowType lit) lit int)
+  ) =>
+  CheckedPositiveIntegerLiteral lit (UFixed int frac)
+
+type UFixedNegativeLiteralError strLit =
+  OutOfBoundsBecause
+    strLit
+    ('Text "UFixed cannot represent negative numbers.")
+
+instance
+  (UFixedNegativeLiteralError ('Text "-" ':<>: 'ShowType lit)) =>
+  CheckedNegativeIntegerLiteral lit (UFixed int frac)
+
+type FixedPointNotPow2Error strLit den typ =
+  NotExactlyRepresentable
+    strLit
+    typ
+    ( 'Text "The reduced denominator "
+        ':<>: 'ShowType den
+        ':<>: 'Text " is not a power of 2."
+    )
+
+type FixedPointNotEnoughFracError strLit den frac typ =
+  NotExactlyRepresentableWithConstraint
+    strLit
+    typ
+    (FractionalBitsNote (CLog 2 den))
+    (CLog 2 den)
+    frac
+
+type family
+  CheckFixedFrac (isPow2 :: Bool) (strLit :: ErrorMessage) (den :: Nat) (frac :: Nat) (typ :: Type) ::
+    Constraint
+  where
+  CheckFixedFrac 'False strLit den frac typ = FixedPointNotPow2Error strLit den typ
+  CheckFixedFrac 'True strLit den frac typ =
+    Assert
+      (CLog 2 den <=? frac)
+      (FixedPointNotEnoughFracError strLit den frac typ)
+
+instance
+  ( Assert
+      (If (Div num den <=? 0) (Div num den <=? 0) (CLog 2 (Div num den + 1) <=? int))
+      (UFixedPositiveLiteralError ('Text str) (Div num den) int)
+  , CheckFixedFrac (IsPowerOfTwo den) ('Text str) den frac (UFixed int frac)
+  ) =>
+  CheckedPositiveRationalLiteral str num den (UFixed int frac)
+
+instance
+  (UFixedNegativeLiteralError ('Text str)) =>
+  CheckedNegativeRationalLiteral str num den (UFixed int frac)
+
+type SFixedPositiveLiteralError strLit lit int =
+  PotentiallyOutOfBounds
+    strLit
+    (SignedIntegerBitsNote (CLog 2 (lit + 1) + 1))
+    (CLog 2 (lit + 1) + 1)
+    int
+
+instance
+  ( Assert
+      (If (lit <=? 0) (lit <=? 0) (CLog 2 (lit + 1) + 1 <=? int))
+      (SFixedPositiveLiteralError (ShowType lit) lit int)
+  ) =>
+  CheckedPositiveIntegerLiteral lit (SFixed int frac)
+
+type SFixedNegativeLiteralError strLit lit int =
+  PotentiallyOutOfBounds
+    strLit
+    (SignedIntegerBitsNote (CLog 2 lit + 1))
+    (CLog 2 lit + 1)
+    int
+
+type SFixedPositiveRationalLiteralError strLit num den int =
+  PotentiallyOutOfBounds
+    strLit
+    (SignedIntegerBitsNote (SFixedPositiveRationalRequiredIntBits num den))
+    (SFixedPositiveRationalRequiredIntBits num den)
+    int
+
+type SFixedNegativeRationalLiteralError strLit num den int =
+  PotentiallyOutOfBounds
+    strLit
+    (SignedIntegerBitsNote (SFixedNegativeRationalRequiredIntBits num den))
+    (SFixedNegativeRationalRequiredIntBits num den)
+    int
+
+instance
+  ( Assert
+      (If (lit <=? 0) (lit <=? 0) (CLog 2 lit + 1 <=? int))
+      (SFixedNegativeLiteralError ('Text "-" ':<>: 'ShowType lit) lit int)
+  ) =>
+  CheckedNegativeIntegerLiteral lit (SFixed int frac)
+
+instance
+  ( CheckFixedFrac (IsPowerOfTwo den) ('Text str) den frac (SFixed int frac)
+  , Assert
+      (FitsPositiveSFixedRational num den int)
+      (SFixedPositiveRationalLiteralError ('Text str) num den int)
+  ) =>
+  CheckedPositiveRationalLiteral str num den (SFixed int frac)
+
+instance
+  ( CheckFixedFrac (IsPowerOfTwo den) ('Text str) den frac (SFixed int frac)
+  , Assert
+      (FitsNegativeSFixedRational num den int)
+      (SFixedNegativeRationalLiteralError ('Text str) num den int)
+  ) =>
+  CheckedNegativeRationalLiteral str num den (SFixed int frac)
 
 {-# INLINE sf #-}
 -- | Treat a 'Signed' integer as a @Signed@ 'Fixed'-@point@ integer
diff --git a/src/Clash/Sized/Index.hs b/src/Clash/Sized/Index.hs
--- a/src/Clash/Sized/Index.hs
+++ b/src/Clash/Sized/Index.hs
@@ -13,7 +13,10 @@
 {-# OPTIONS_HADDOCK show-extensions #-}
 
 module Clash.Sized.Index
-  (Index, bv2i, fromSNat)
+  ( Index, bv2i, fromSNat
+    -- * Type-level error messages
+  , IndexPositiveLiteralError
+  )
 where
 
 import GHC.TypeLits (KnownNat, type (^))
diff --git a/src/Clash/Sized/Internal/BitVector.hs b/src/Clash/Sized/Internal/BitVector.hs
--- a/src/Clash/Sized/Internal/BitVector.hs
+++ b/src/Clash/Sized/Internal/BitVector.hs
@@ -11,6 +11,7 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE RoleAnnotations #-}
@@ -30,6 +31,8 @@
     -- ** Construction
   , high
   , low
+    -- ** Type-level error messages
+  , BitPositiveLiteralError
     -- ** Type classes
     -- *** Eq
   , eq##
@@ -127,6 +130,8 @@
   , truncateB#
     -- *** QuickCheck
   , shrinkSizedUnsigned
+    -- ** Type-level error messages
+  , BitVectorPositiveLiteralError
   -- ** Other
   , undefError
   , checkUnpackUndef
@@ -143,6 +148,7 @@
 import Data.Default               (Default (..))
 import Data.Either                (isLeft)
 import Data.Proxy                 (Proxy (..))
+import Data.Type.Bool             (If)
 import Data.Typeable              (Typeable, typeOf)
 import GHC.Generics               (Generic)
 import Data.Maybe                 (fromMaybe)
@@ -156,9 +162,20 @@
   (Natural (..), naturalFromWord, naturalShiftL, naturalShiftR, naturalToWord)
 import GHC.Natural                (naturalToInteger)
 import GHC.Stack                  (withFrozenCallStack)
-import GHC.TypeLits               (KnownNat, Nat, type (+), type (-))
+import GHC.TypeError
+  ( Assert
+  , ErrorMessage (ShowType)
+  )
+import GHC.TypeLits
+  ( KnownNat
+  , Nat
+  , type (+)
+  , type (-)
+  , type (<=?)
+  , type (^)
+  )
 import GHC.TypeNats               (natVal)
-import GHC.TypeLits.Extra         (Max)
+import GHC.TypeLits.Extra         (CLog, Max)
 import Language.Haskell.TH
   (Lit (..), ExpQ, Type(ConT, AppT, LitT), Exp(VarE, AppE, SigE, LitE),
    TyLit(NumTyLit), Pat, Q, appT, conT, litE, litP, litT, mkName, numTyLit,
@@ -178,10 +195,20 @@
 import Clash.Promoted.Nat
   (SNat (..), SNatLE (..), compareSNat, snatToInteger, snatToNum, natToNum)
 import Clash.Sized.Internal (formatRange)
+import Clash.Sized.Internal.CheckedLiterals
+  ( OutOfBounds
+  , PotentiallyOutOfBounds
+  , UnsignedBounds
+  )
 import Clash.XException
   (ShowX (..), NFDataX (..), errorX, isX, showsPrecXWith, rwhnfX, XException(..))
 
 import Clash.Sized.Internal.Mod
+import CheckedLiterals.Class.Integer
+  ( NegativeUnsignedError
+  , CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
 
 import {-# SOURCE #-} qualified Clash.Sized.Vector         as V
 import {-# SOURCE #-} qualified Clash.Sized.Internal.Index as I
@@ -390,6 +417,17 @@
   countLeadingZeros b  = if eq## b low then 1 else 0
   countTrailingZeros b = if eq## b low then 1 else 0
 
+type BitPositiveLiteralError lit =
+  OutOfBounds ('ShowType lit) (UnsignedBounds Bit 1)
+
+instance
+  (Assert (lit <=? 1) (BitPositiveLiteralError lit)) =>
+  CheckedPositiveIntegerLiteral lit Bit
+
+instance
+  (NegativeUnsignedError lit Bit 1) =>
+  CheckedNegativeIntegerLiteral lit Bit
+
 and##, or##, xor## :: Bit -> Bit -> Bit
 and## (Bit m1 v1) (Bit m2 v2) = Bit mask (v1 .&. v2 .&. complement mask)
   where mask = (m1.&.v2 .|. m1.&.m2 .|. m2.&.v1)
@@ -855,6 +893,24 @@
   finiteBitSize       = size#
   countLeadingZeros   = fromInteger . I.toInteger# . countLeadingZerosBV
   countTrailingZeros  = fromInteger . I.toInteger# . countTrailingZerosBV
+
+type BitVectorPositiveLiteralError lit n =
+  PotentiallyOutOfBounds
+    ('ShowType lit)
+    (UnsignedBounds (BitVector n) ((2 ^ n) - 1))
+    (CLog 2 (lit + 1))
+    n
+
+instance
+  ( Assert
+      (If (lit <=? 0) (lit <=? 0) (CLog 2 (lit + 1) <=? n))
+      (BitVectorPositiveLiteralError lit n)
+  ) =>
+  CheckedPositiveIntegerLiteral lit (BitVector n)
+
+instance
+  (NegativeUnsignedError lit (BitVector n) ((2 ^ n) - 1)) =>
+  CheckedNegativeIntegerLiteral lit (BitVector n)
 
 countLeadingZerosBV :: KnownNat n => BitVector n -> I.Index (n+1)
 countLeadingZerosBV = V.foldr (\l r -> if eq## l low then 1 + r else 0) 0 . V.bv2v
diff --git a/src/Clash/Sized/Internal/CheckedLiterals.hs b/src/Clash/Sized/Internal/CheckedLiterals.hs
new file mode 100644
--- /dev/null
+++ b/src/Clash/Sized/Internal/CheckedLiterals.hs
@@ -0,0 +1,101 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE NoStarIsType #-}
+{-# LANGUAGE TypeOperators #-}
+
+module Clash.Sized.Internal.CheckedLiterals where
+
+import Data.Kind (Type)
+import GHC.TypeError
+  ( ErrorMessage (ShowType, Text, (:$$:), (:<>:))
+  , TypeError
+  )
+
+type CheckedLiteralUncheckedFix =
+  'Text "Possible fix: use 'uncheckedLiteral' from 'CheckedLiterals' to bypass this check."
+
+type AddConstraintFix required actual =
+  'Text "Possible fix: add a constraint: "
+    ':<>: 'ShowType required
+    ':<>: 'Text " <= "
+    ':<>: 'ShowType actual
+    ':<>: 'Text "."
+
+type UnsignedBounds (typ :: Type) maxVal =
+  'ShowType typ
+    ':<>: 'Text " has bounds: [0 .. "
+    ':<>: 'ShowType maxVal
+    ':<>: 'Text "]."
+
+type SignedBounds (typ :: Type) minVal maxVal =
+  'ShowType typ
+    ':<>: 'Text " has bounds: [-"
+    ':<>: 'ShowType minVal
+    ':<>: 'Text " .. "
+    ':<>: 'ShowType maxVal
+    ':<>: 'Text "]."
+
+type IntegerBitsNote bits =
+  'Text "Note: integer part needs at least "
+    ':<>: 'ShowType bits
+    ':<>: 'Text " bit(s)."
+
+type SignedIntegerBitsNote bits =
+  'Text "Note: integer part needs at least "
+    ':<>: 'ShowType bits
+    ':<>: 'Text " bit(s), including sign bit."
+
+type FractionalBitsNote bits =
+  'Text "The fractional part needs at least "
+    ':<>: 'ShowType bits
+    ':<>: 'Text " bit(s)."
+
+type OutOfBounds strLit details =
+  TypeError
+    ( 'Text "Literal "
+        ':<>: strLit
+        ':<>: 'Text " is out of bounds."
+        ':$$: details
+        ':$$: CheckedLiteralUncheckedFix
+    )
+
+type PotentiallyOutOfBounds strLit details required actual =
+  TypeError
+    ( 'Text "Literal "
+        ':<>: strLit
+        ':<>: 'Text " is (potentially) out of bounds."
+        ':$$: details
+        ':$$: AddConstraintFix required actual
+        ':$$: CheckedLiteralUncheckedFix
+    )
+
+type OutOfBoundsBecause strLit reason =
+  TypeError
+    ( 'Text "Literal "
+        ':<>: strLit
+        ':<>: 'Text " is out of bounds, because "
+        ':<>: reason
+        ':$$: CheckedLiteralUncheckedFix
+    )
+
+type NotExactlyRepresentable strLit (typ :: Type) reason =
+  TypeError
+    ( 'Text "Literal "
+        ':<>: strLit
+        ':<>: 'Text " cannot be represented exactly by "
+        ':<>: 'ShowType typ
+        ':<>: 'Text "."
+        ':$$: reason
+        ':$$: CheckedLiteralUncheckedFix
+    )
+
+type NotExactlyRepresentableWithConstraint strLit (typ :: Type) reason required actual =
+  TypeError
+    ( 'Text "Literal "
+        ':<>: strLit
+        ':<>: 'Text " cannot be represented exactly by "
+        ':<>: 'ShowType typ
+        ':<>: 'Text "."
+        ':$$: reason
+        ':$$: AddConstraintFix required actual
+        ':$$: CheckedLiteralUncheckedFix
+    )
diff --git a/src/Clash/Sized/Internal/Index.hs b/src/Clash/Sized/Internal/Index.hs
--- a/src/Clash/Sized/Internal/Index.hs
+++ b/src/Clash/Sized/Internal/Index.hs
@@ -9,6 +9,7 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE RoleAnnotations #-}
 {-# LANGUAGE TemplateHaskell #-}
@@ -68,6 +69,8 @@
   , toInteger#
     -- ** Resize
   , resize#
+    -- * Type-level error messages
+  , IndexPositiveLiteralError
   )
 where
 
@@ -88,8 +91,20 @@
 import GHC.Natural                (Natural, naturalFromInteger)
 import GHC.Natural                (naturalToInteger)
 import GHC.Stack                  (HasCallStack)
-import GHC.TypeLits               (KnownNat, Nat, type (+), type (-),
-                                   type (*), type (<=), natVal)
+import GHC.TypeError
+  ( Assert
+  , ErrorMessage (ShowType)
+  )
+import GHC.TypeLits
+  ( KnownNat
+  , Nat
+  , natVal
+  , type (+)
+  , type (-)
+  , type (*)
+  , type (<=)
+  , type (<=?)
+  )
 import GHC.TypeLits.Extra         (CLogWZ)
 import Test.QuickCheck.Arbitrary  (Arbitrary (..), CoArbitrary (..),
                                    arbitraryBoundedIntegral,
@@ -103,9 +118,18 @@
 import Clash.Class.Resize         (Resize (..))
 import Clash.Class.BitPack.BitIndex (replaceBit)
 import Clash.Sized.Internal       (formatRange)
+import Clash.Sized.Internal.CheckedLiterals
+  ( PotentiallyOutOfBounds
+  , UnsignedBounds
+  )
 import {-# SOURCE #-} Clash.Sized.Internal.BitVector (BitVector (BV), high, low, undefError)
 import qualified Clash.Sized.Internal.BitVector as BV
 import Clash.Promoted.Nat         (SNat(..), UNat(..), toUNat, snatToNum, natToInteger)
+import CheckedLiterals.Class.Integer
+  ( NegativeUnsignedError
+  , CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
 import Clash.XException
   (ShowX (..), NFDataX (..), errorX, showsPrecXWith, rwhnfX, seqX)
 
@@ -287,6 +311,24 @@
   abs         = id
   signum i    = if i == 0 then 0 else 1
   fromInteger = fromInteger#
+
+type IndexPositiveLiteralError lit n =
+  PotentiallyOutOfBounds
+    ('ShowType lit)
+    (UnsignedBounds (Index n) (n - 1))
+    (lit + 1)
+    n
+
+instance
+  ( Assert
+      (lit + 1 <=? n)
+      (IndexPositiveLiteralError lit n)
+  ) =>
+  CheckedPositiveIntegerLiteral lit (Index n)
+
+instance
+  (NegativeUnsignedError lit (Index n) (n - 1)) =>
+  CheckedNegativeIntegerLiteral lit (Index n)
 
 (+#),(-#),(*#) :: KnownNat n => Index n -> Index n -> Index n
 {-# OPAQUE (+#) #-}
diff --git a/src/Clash/Sized/Internal/Signed.hs b/src/Clash/Sized/Internal/Signed.hs
--- a/src/Clash/Sized/Internal/Signed.hs
+++ b/src/Clash/Sized/Internal/Signed.hs
@@ -8,6 +8,7 @@
 
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE RoleAnnotations #-}
@@ -81,6 +82,9 @@
   , truncateB#
     -- ** SaturatingNum
   , minBoundSym#
+    -- * Type-level error messages
+  , SignedPositiveLiteralError
+  , SignedNegativeLiteralError
   )
 where
 
@@ -92,13 +96,26 @@
 import Data.Data                      (Data)
 import Data.Default                   (Default (..))
 import Data.Proxy                     (Proxy (..))
+import Data.Type.Bool                 (If)
 import Text.Read                      (Read (..), ReadPrec)
 import Text.Printf                    (PrintfArg (..), printf)
 import GHC.Generics                   (Generic)
 import GHC.Natural                    (naturalFromInteger, naturalToInteger)
 
-import GHC.TypeLits                   (KnownNat, Nat, type (+), natVal)
-import GHC.TypeLits.Extra             (Max)
+import GHC.TypeError
+  ( Assert
+  , ErrorMessage (ShowType, Text, (:<>:))
+  )
+import GHC.TypeLits
+  ( KnownNat
+  , Nat
+  , natVal
+  , type (+)
+  , type (-)
+  , type (<=?)
+  , type (^)
+  )
+import GHC.TypeLits.Extra             (CLog, Max)
 import Data.Ix                        (Ix(..))
 import Language.Haskell.TH            (appT, conT, litT, numTyLit, sigE)
 import Language.Haskell.TH.Syntax     (Lift(..))
@@ -118,7 +135,15 @@
 import Clash.Class.BitPack.BitReduction (reduceAnd, reduceOr)
 import Clash.Promoted.Nat             (natToNatural)
 import Clash.Sized.Internal.BitVector (BitVector (BV), Bit, (++#), high, low, undefError)
+import Clash.Sized.Internal.CheckedLiterals
+  ( PotentiallyOutOfBounds
+  , SignedBounds
+  )
 import qualified Clash.Sized.Internal.BitVector as BV
+import CheckedLiterals.Class.Integer
+  ( CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
 import Clash.XException
   (ShowX (..), NFDataX (..), errorX, showsPrecXWith, rwhnfX)
 
@@ -643,6 +668,34 @@
   finiteBitSize        = size#
   countLeadingZeros  s = countLeadingZeros  (pack# s)
   countTrailingZeros s = countTrailingZeros (pack# s)
+
+type SignedPositiveLiteralError lit n =
+  PotentiallyOutOfBounds
+    ('ShowType lit)
+    (SignedBounds (Signed n) (2 ^ (n - 1)) ((2 ^ (n - 1)) - 1))
+    (CLog 2 (lit + 1) + 1)
+    n
+
+instance
+  ( Assert
+      (If (lit <=? 0) (lit <=? 0) (CLog 2 (lit + 1) + 1 <=? n))
+      (SignedPositiveLiteralError lit n)
+  ) =>
+  CheckedPositiveIntegerLiteral lit (Signed n)
+
+type SignedNegativeLiteralError lit n =
+  PotentiallyOutOfBounds
+    ('Text "-" ':<>: 'ShowType lit)
+    (SignedBounds (Signed n) (2 ^ (n - 1)) ((2 ^ (n - 1)) - 1))
+    (CLog 2 lit + 1)
+    n
+
+instance
+  ( Assert
+      (If (lit <=? 0) (lit <=? 0) (CLog 2 lit + 1 <=? n))
+      (SignedNegativeLiteralError lit n)
+  ) =>
+  CheckedNegativeIntegerLiteral lit (Signed n)
 
 instance Resize Signed where
   resize       = resize#
diff --git a/src/Clash/Sized/Internal/Unsigned.hs b/src/Clash/Sized/Internal/Unsigned.hs
--- a/src/Clash/Sized/Internal/Unsigned.hs
+++ b/src/Clash/Sized/Internal/Unsigned.hs
@@ -8,6 +8,7 @@
 
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE RoleAnnotations #-}
@@ -79,6 +80,8 @@
   , unsigned8toWord8
   , unsigned16toWord16
   , unsigned32toWord32
+    -- * Type-level error messages
+  , UnsignedPositiveLiteralError
   )
 where
 
@@ -90,6 +93,7 @@
 import Data.Data                      (Data)
 import Data.Default                   (Default (..))
 import Data.Proxy                     (Proxy (..))
+import Data.Type.Bool                 (If)
 import Text.Read                      (Read (..), ReadPrec)
 import Text.Printf                    (PrintfArg (..), printf)
 import GHC.Exts                       (wordToWord8#, wordToWord16#, wordToWord32#)
@@ -100,9 +104,20 @@
 import GHC.Num.Natural
   (Natural (..), naturalShiftL, naturalShiftR, naturalToWord)
 import GHC.Natural                    (naturalToInteger)
-import GHC.TypeLits                   (KnownNat, Nat, type (+))
+import GHC.TypeError
+  ( Assert
+  , ErrorMessage (ShowType)
+  )
+import GHC.TypeLits
+  ( KnownNat
+  , Nat
+  , type (+)
+  , type (-)
+  , type (<=?)
+  , type (^)
+  )
 import GHC.TypeNats                   (natVal)
-import GHC.TypeLits.Extra             (Max)
+import GHC.TypeLits.Extra             (CLog, Max)
 import GHC.Word                       (Word (..), Word8 (..), Word16 (..), Word32 (..))
 import Data.Ix                        (Ix(..))
 import Language.Haskell.TH            (appT, conT, litT, numTyLit, sigE)
@@ -123,8 +138,17 @@
 import Clash.Class.BitPack.BitReduction (reduceOr)
 import Clash.Promoted.Nat             (natToNum, natToNatural)
 import Clash.Sized.Internal.BitVector (BitVector (BV), Bit, high, low, undefError)
+import Clash.Sized.Internal.CheckedLiterals
+  ( PotentiallyOutOfBounds
+  , UnsignedBounds
+  )
 import qualified Clash.Sized.Internal.BitVector as BV
 import Clash.Sized.Internal.Mod
+import CheckedLiterals.Class.Integer
+  ( NegativeUnsignedError
+  , CheckedNegativeIntegerLiteral
+  , CheckedPositiveIntegerLiteral
+  )
 import Clash.XException
   (ShowX (..), NFDataX (..), errorX, showsPrecXWith, rwhnfX)
 
@@ -544,6 +568,24 @@
   finiteBitSize        = size#
   countLeadingZeros  u = countLeadingZeros  (pack# u)
   countTrailingZeros u = countTrailingZeros (pack# u)
+
+type UnsignedPositiveLiteralError lit n =
+  PotentiallyOutOfBounds
+    ('ShowType lit)
+    (UnsignedBounds (Unsigned n) ((2 ^ n) - 1))
+    (CLog 2 (lit + 1))
+    n
+
+instance
+  ( Assert
+      (If (lit <=? 0) (lit <=? 0) (CLog 2 (lit + 1) <=? n))
+      (UnsignedPositiveLiteralError lit n)
+  ) =>
+  CheckedPositiveIntegerLiteral lit (Unsigned n)
+
+instance
+  (NegativeUnsignedError lit (Unsigned n) ((2 ^ n) - 1)) =>
+  CheckedNegativeIntegerLiteral lit (Unsigned n)
 
 instance Resize Unsigned where
   resize     = resize#
diff --git a/src/Clash/Sized/Signed.hs b/src/Clash/Sized/Signed.hs
--- a/src/Clash/Sized/Signed.hs
+++ b/src/Clash/Sized/Signed.hs
@@ -8,6 +8,9 @@
 
 module Clash.Sized.Signed
   ( Signed
+    -- * Type-level error messages
+  , SignedPositiveLiteralError
+  , SignedNegativeLiteralError
   )
 where
 
diff --git a/src/Clash/Sized/Unsigned.hs b/src/Clash/Sized/Unsigned.hs
--- a/src/Clash/Sized/Unsigned.hs
+++ b/src/Clash/Sized/Unsigned.hs
@@ -7,7 +7,10 @@
 {-# LANGUAGE Trustworthy #-}
 
 module Clash.Sized.Unsigned
-  (Unsigned)
+  ( Unsigned
+    -- * Type-level error messages
+  , UnsignedPositiveLiteralError
+  )
 where
 
 import Clash.Sized.Internal.Unsigned
diff --git a/tests/Clash/Tests/CheckedLiterals.hs b/tests/Clash/Tests/CheckedLiterals.hs
new file mode 100644
--- /dev/null
+++ b/tests/Clash/Tests/CheckedLiterals.hs
@@ -0,0 +1,28 @@
+{-|
+Copyright  :  (C) 2026, QBayLogic B.V.
+License    :  BSD2 (see the file LICENSE)
+Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
+-}
+
+module Clash.Tests.CheckedLiterals (tests) where
+
+import Test.Tasty (TestTree, testGroup)
+
+import qualified Clash.Tests.CheckedLiterals.BitVector
+import qualified Clash.Tests.CheckedLiterals.Fixed
+import qualified Clash.Tests.CheckedLiterals.Index
+import qualified Clash.Tests.CheckedLiterals.Signals
+import qualified Clash.Tests.CheckedLiterals.Signed
+import qualified Clash.Tests.CheckedLiterals.Unsigned
+import qualified Clash.Tests.CheckedLiterals.Wrappers
+
+tests :: TestTree
+tests = testGroup "CheckedLiterals"
+  [ Clash.Tests.CheckedLiterals.BitVector.tests
+  , Clash.Tests.CheckedLiterals.Unsigned.tests
+  , Clash.Tests.CheckedLiterals.Signed.tests
+  , Clash.Tests.CheckedLiterals.Index.tests
+  , Clash.Tests.CheckedLiterals.Fixed.tests
+  , Clash.Tests.CheckedLiterals.Wrappers.tests
+  , Clash.Tests.CheckedLiterals.Signals.tests
+  ]
diff --git a/tests/Clash/Tests/CheckedLiterals/BitVector.hs b/tests/Clash/Tests/CheckedLiterals/BitVector.hs
new file mode 100644
--- /dev/null
+++ b/tests/Clash/Tests/CheckedLiterals/BitVector.hs
@@ -0,0 +1,54 @@
+{-|
+Copyright  :  (C) 2026, QBayLogic B.V.
+License    :  BSD2 (see the file LICENSE)
+Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
+-}
+
+module Clash.Tests.CheckedLiterals.BitVector (tests) where
+
+import Prelude
+
+import Test.Tasty (TestTree, testGroup)
+import Clash.Tests.CheckedLiterals.Common (toTestCases)
+
+bvMod :: String
+bvMod = "Clash.Sized.BitVector"
+
+bitTests :: TestTree
+bitTests = testGroup "Bit" $ toTestCases
+  [ (bvMod, "Bit", "0",  [])
+  , (bvMod, "Bit", "1",  [])
+  , (bvMod, "Bit", "-1", ["Literal -1 is out of bounds.", "Bit has bounds: [0 .. 1]."])
+  , (bvMod, "Bit", "2",  ["Literal 2 is out of bounds.", "Bit has bounds: [0 .. 1]."])
+  ]
+
+bitVectorTests :: TestTree
+bitVectorTests = testGroup "BitVector" $ toTestCases
+  [ (bvMod, "BitVector 0", "0",                            [])
+  , (bvMod, "BitVector 0", "-1",                           ["Literal -1 is out of bounds.", "BitVector 0 has bounds: [0 .. 0]."])
+  , (bvMod, "BitVector 0", "1",                            ["Literal 1 is (potentially) out of bounds.", "BitVector 0 has bounds: [0 .. 0]."])
+  , (bvMod, "BitVector 1", "0",                            [])
+  , (bvMod, "BitVector 1", "-1",                           ["Literal -1 is out of bounds.", "BitVector 1 has bounds: [0 .. 1]."])
+  , (bvMod, "BitVector 1", "1",                            [])
+  , (bvMod, "BitVector 1", "2",                            ["Literal 2 is (potentially) out of bounds.", "BitVector 1 has bounds: [0 .. 1]."])
+  , (bvMod, "BitVector 2", "0",                            [])
+  , (bvMod, "BitVector 2", "-1",                           ["Literal -1 is out of bounds.", "BitVector 2 has bounds: [0 .. 3]."])
+  , (bvMod, "BitVector 2", "1",                            [])
+  , (bvMod, "BitVector 2", "2",                            [])
+  , (bvMod, "BitVector 2", "3",                            [])
+  , (bvMod, "BitVector 2", "4",                            ["Literal 4 is (potentially) out of bounds.", "BitVector 2 has bounds: [0 .. 3]."])
+  , (bvMod, "(KnownNat n) => BitVector n", "0",            [])
+  , (bvMod, "(KnownNat n) => BitVector n", "1",            ["Literal 1 is (potentially) out of bounds.", "BitVector n has bounds: [0 .. (2 ^ n) - 1]", "Possible fix: add a constraint: 1 <= n."])
+  , (bvMod, "(KnownNat n) => BitVector n", "-1",           ["Literal -1 is out of bounds.", "BitVector n has bounds: [0 .. (2 ^ n) - 1]."])
+  , (bvMod, "(KnownNat n, 1 <= n) => BitVector n", "0",    [])
+  , (bvMod, "(KnownNat n, 1 <= n) => BitVector n", "1",    [])
+  , (bvMod, "(KnownNat n, 1 <= n) => BitVector n", "-1",   ["Literal -1 is out of bounds.", "BitVector n has bounds: [0 .. (2 ^ n) - 1]."])
+  , (bvMod, "(KnownNat n, 2 <= n) => BitVector n", "0",    [])
+  , (bvMod, "(KnownNat n, 2 <= n) => BitVector n", "1",    [])
+  , (bvMod, "(KnownNat n, 2 <= n) => BitVector n", "-1",   ["Literal -1 is out of bounds.", "BitVector n has bounds: [0 .. (2 ^ n) - 1]."])
+  , (bvMod, "(KnownNat n, 7 <= n) => BitVector n", "255",  ["Literal 255 is (potentially) out of bounds.", "BitVector n has bounds: [0 .. (2 ^ n) - 1].", "Possible fix: add a constraint: 8 <= n."])
+  , (bvMod, "(KnownNat n, 8 <= n) => BitVector n", "256",  ["Literal 256 is (potentially) out of bounds.", "BitVector n has bounds: [0 .. (2 ^ n) - 1].", "Possible fix: add a constraint: 9 <= n."])
+  ]
+
+tests :: TestTree
+tests = testGroup "BitVector" [bitTests, bitVectorTests]
diff --git a/tests/Clash/Tests/CheckedLiterals/Common.hs b/tests/Clash/Tests/CheckedLiterals/Common.hs
new file mode 100644
--- /dev/null
+++ b/tests/Clash/Tests/CheckedLiterals/Common.hs
@@ -0,0 +1,46 @@
+{-|
+Copyright  :  (C) 2026, QBayLogic B.V.
+License    :  BSD2 (see the file LICENSE)
+Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
+-}
+
+{-# LANGUAGE QuasiQuotes #-}
+
+module Clash.Tests.CheckedLiterals.Common where
+
+import Data.List (intercalate)
+import Data.String.Interpolate (__i)
+import Test.Tasty (TestTree)
+import Test.Tasty.AssertGhc (Expected (..), testCaseGhc)
+
+-- | First tuple element may list several modules separated by @", "@; each one
+-- gets its own @import@ line in the generated snippet. This lets a test case
+-- reference types that live in different modules (e.g. a @Clash.Num.*@ wrapper
+-- around a @Clash.Sized.*@ type).
+toTestCases :: [(String, String, String, [String])] -> [TestTree]
+toTestCases = map toTestCase
+
+toTestCase :: (String, String, String, [String]) -> TestTree
+toTestCase (moduleNames, typeName, literal, expectedErrors) =
+  testCaseGhc
+    ((if null expectedErrors then "OK , " else "NOK, ") ++ typeName ++ ", " ++ literal)
+    [__i|
+      import Prelude
+      #{imports}
+      import GHC.TypeNats
+      test :: #{typeName}
+      test = #{literal}
+    |]
+    ( if null expectedErrors
+        then ExpectSuccess
+        else ExpectFailure expectedErrors
+    )
+ where
+  imports = intercalate "\n" ["import " ++ m | m <- splitOnCommaSpace moduleNames]
+
+  splitOnCommaSpace :: String -> [String]
+  splitOnCommaSpace s = case break (== ',') s of
+    (x, "")       -> [x]
+    (x, ',':' ':r) -> x : splitOnCommaSpace r
+    (x, ',':r)    -> x : splitOnCommaSpace r
+    (x, _)        -> [x]
diff --git a/tests/Clash/Tests/CheckedLiterals/Fixed.hs b/tests/Clash/Tests/CheckedLiterals/Fixed.hs
new file mode 100644
--- /dev/null
+++ b/tests/Clash/Tests/CheckedLiterals/Fixed.hs
@@ -0,0 +1,143 @@
+{-|
+Copyright  :  (C) 2026, QBayLogic B.V.
+License    :  BSD2 (see the file LICENSE)
+Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
+-}
+
+module Clash.Tests.CheckedLiterals.Fixed (tests) where
+
+import Prelude
+
+import Test.Tasty (TestTree, testGroup)
+import Clash.Tests.CheckedLiterals.Common (toTestCases)
+
+fMod :: String
+fMod = "Clash.Sized.Fixed"
+
+integerTests :: TestTree
+integerTests = testGroup "Integer" $ toTestCases
+  [ (fMod, "(KnownNat f) => UFixed 0 f", "0",                      [])
+  , (fMod, "(KnownNat f) => UFixed 0 f", "-1",                     ["Literal -1 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (fMod, "(KnownNat f) => UFixed 0 f", "1",                      ["Literal 1 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s)."])
+  , (fMod, "(KnownNat f) => UFixed 1 f", "0",                      [])
+  , (fMod, "(KnownNat f) => UFixed 1 f", "-1",                     ["Literal -1 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (fMod, "(KnownNat f) => UFixed 1 f", "1",                      [])
+  , (fMod, "(KnownNat f) => UFixed 1 f", "2",                      ["Literal 2 is (potentially) out of bounds.", "Note: integer part needs at least 2 bit(s)."])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "0",                      [])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "-1",                     ["Literal -1 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "1",                      [])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "2",                      [])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "3",                      [])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "4",                      ["Literal 4 is (potentially) out of bounds.", "Note: integer part needs at least 3 bit(s)."])
+  , (fMod, "(KnownNat f, KnownNat n) => UFixed n f", "0",          [])
+  , (fMod, "(KnownNat f, KnownNat n) => UFixed n f", "1",          ["Literal 1 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s).", "Possible fix: add a constraint: 1 <= n."])
+  , (fMod, "(KnownNat f, KnownNat n) => UFixed n f", "-1",         ["Literal -1 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => UFixed n f", "0",  [])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => UFixed n f", "1",  [])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => UFixed n f", "-1", ["Literal -1 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => UFixed n f", "0",  [])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => UFixed n f", "1",  [])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => UFixed n f", "-1", ["Literal -1 is out of bounds, because UFixed cannot represent negative numbers."])
+
+  , (fMod, "(KnownNat f) => SFixed 0 f", "0",                      [])
+  , (fMod, "(KnownNat f) => SFixed 0 f", "-1",                     ["Literal -1 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 0 f", "1",                      ["Literal 1 is (potentially) out of bounds.", "Note: integer part needs at least 2 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 1 f", "0",                      [])
+  , (fMod, "(KnownNat f) => SFixed 1 f", "-1",                     [])
+  , (fMod, "(KnownNat f) => SFixed 1 f", "1",                      ["Literal 1 is (potentially) out of bounds.", "Note: integer part needs at least 2 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 1 f", "2",                      ["Literal 2 is (potentially) out of bounds.", "Note: integer part needs at least 3 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "0",                      [])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "-1",                     [])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "-2",                     [])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "-3",                     ["Literal -3 is (potentially) out of bounds.", "Note: integer part needs at least 3 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "1",                      [])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "2",                      ["Literal 2 is (potentially) out of bounds.", "Note: integer part needs at least 3 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "4",                      ["Literal 4 is (potentially) out of bounds.", "Note: integer part needs at least 4 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f, KnownNat n) => SFixed n f", "0",          [])
+  , (fMod, "(KnownNat f, KnownNat n) => SFixed n f", "1",          ["Literal 1 is (potentially) out of bounds.", "Note: integer part needs at least 2 bit(s), including sign bit.", "Possible fix: add a constraint: 2 <= n."])
+  , (fMod, "(KnownNat f, KnownNat n) => SFixed n f", "-1",         ["Literal -1 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s), including sign bit.", "Possible fix: add a constraint: 1 <= n."])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => SFixed n f", "0",  [])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => SFixed n f", "1",  ["Literal 1 is (potentially) out of bounds.", "Note: integer part needs at least 2 bit(s), including sign bit.", "Possible fix: add a constraint: 2 <= n."])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => SFixed n f", "-1", [])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => SFixed n f", "0",  [])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => SFixed n f", "1",  [])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => SFixed n f", "-1", [])
+  ]
+
+rationalTests :: TestTree
+rationalTests = testGroup "Rational" $ toTestCases
+  [ (fMod, "(KnownNat f) => UFixed 0 f", "0.0",                              [])
+  , (fMod, "(KnownNat f) => UFixed 0 f", "-1.0",                             ["Literal -1.0 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (fMod, "(KnownNat f) => UFixed 0 f", "1.0",                              ["Literal 1.0 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s)."])
+  , (fMod, "(KnownNat f) => UFixed 1 f", "0.0",                              [])
+  , (fMod, "(KnownNat f) => UFixed 1 f", "-1.0",                             ["Literal -1.0 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (fMod, "(KnownNat f) => UFixed 1 f", "1.0",                              [])
+  , (fMod, "(KnownNat f) => UFixed 1 f", "2.0",                              ["Literal 2.0 is (potentially) out of bounds.", "Note: integer part needs at least 2 bit(s)."])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "0.0",                              [])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "-1.0",                             ["Literal -1.0 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "1.0",                              [])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "2.0",                              [])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "3.0",                              [])
+  , (fMod, "(KnownNat f) => UFixed 2 f", "4.0",                              ["Literal 4.0 is (potentially) out of bounds.", "Note: integer part needs at least 3 bit(s)."])
+  , (fMod, "(KnownNat f, KnownNat n) => UFixed n f", "0.0",                  [])
+  , (fMod, "(KnownNat f, KnownNat n) => UFixed n f", "1.0",                  ["Literal 1.0 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s).", "Possible fix: add a constraint: 1 <= n."])
+  , (fMod, "(KnownNat f, KnownNat n) => UFixed n f", "-1.0",                 ["Literal -1.0 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => UFixed n f", "0.0",          [])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => UFixed n f", "1.0",          [])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => UFixed n f", "-1.0",         ["Literal -1.0 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => UFixed n f", "0.0",          [])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => UFixed n f", "1.0",          [])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => UFixed n f", "-1.0",         ["Literal -1.0 is out of bounds, because UFixed cannot represent negative numbers."])
+
+  , (fMod, "(KnownNat f) => SFixed 0 f", "0.0",                              [])
+  , (fMod, "(KnownNat f) => SFixed 0 f", "-1.0",                             ["Literal -1.0 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 0 f", "1.0",                              ["Literal 1.0 is (potentially) out of bounds.", "Note: integer part needs at least 2 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 1 f", "0.0",                              [])
+  , (fMod, "(KnownNat f) => SFixed 1 f", "-1.0",                             [])
+  , (fMod, "(KnownNat f) => SFixed 1 f", "1.0",                              ["Literal 1.0 is (potentially) out of bounds.", "Note: integer part needs at least 2 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 1 f", "2.0",                              ["Literal 2.0 is (potentially) out of bounds.", "Note: integer part needs at least 3 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "0.0",                              [])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "-1.0",                             [])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "-2.0",                             [])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "-3.0",                             ["Literal -3.0 is (potentially) out of bounds.", "Note: integer part needs at least 3 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "1.0",                              [])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "2.0",                              ["Literal 2.0 is (potentially) out of bounds.", "Note: integer part needs at least 3 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f) => SFixed 2 f", "4.0",                              ["Literal 4.0 is (potentially) out of bounds.", "Note: integer part needs at least 4 bit(s), including sign bit."])
+  , (fMod, "(KnownNat f, KnownNat n) => SFixed n f", "0.0",                  [])
+  , (fMod, "(KnownNat f, KnownNat n) => SFixed n f", "1.0",                  ["Literal 1.0 is (potentially) out of bounds.", "Note: integer part needs at least 2 bit(s), including sign bit.", "Possible fix: add a constraint: 2 <= n."])
+  , (fMod, "(KnownNat f, KnownNat n) => SFixed n f", "-1.0",                 ["Literal -1.0 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s), including sign bit.", "Possible fix: add a constraint: 1 <= n."])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => SFixed n f", "0.0",          [])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => SFixed n f", "1.0",          ["Literal 1.0 is (potentially) out of bounds.", "Note: integer part needs at least 2 bit(s), including sign bit.", "Possible fix: add a constraint: 2 <= n."])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n) => SFixed n f", "-1.0",         [])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => SFixed n f", "0.0",          [])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => SFixed n f", "1.0",          [])
+  , (fMod, "(KnownNat f, KnownNat n, 2 <= n) => SFixed n f", "-1.0",         [])
+
+  , (fMod, "UFixed 0 0", "0.5",                                              ["Literal 0.5 cannot be represented exactly by Fixed", "The fractional part needs at least 1 bit(s)."])
+  , (fMod, "UFixed 0 1", "0.5",                                              [])
+  , (fMod, "UFixed 0 1", "0.75",                                             ["Literal 0.75 cannot be represented exactly by Fixed", "The fractional part needs at least 2 bit(s)."])
+  , (fMod, "UFixed 0 1", "0.1",                                              ["Literal 0.1 cannot be represented exactly by", "The reduced denominator 10 is not a power of 2."])
+  , (fMod, "(KnownNat f, 1 <= f) => UFixed 0 f", "0.5",                      [])
+  , (fMod, "(KnownNat f, 1 <= f) => UFixed 0 f", "0.75",                     ["Literal 0.75 cannot be represented exactly by Fixed", "The fractional part needs at least 2 bit(s).", "Possible fix: add a constraint: 2 <= f."])
+  , (fMod, "(KnownNat f, 2 <= f) => UFixed 0 f", "0.75",                     [])
+  , (fMod, "(KnownNat f, 3 <= f) => UFixed 0 f", "0.75",                     [])
+
+  , (fMod, "SFixed 0 0", "0.5",                                              ["Literal 0.5 cannot be represented exactly by Fixed", "The fractional part needs at least 1 bit(s)."])
+  , (fMod, "SFixed 0 1", "0.5",                                              ["Literal 0.5 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s), including sign bit.", "Possible fix: add a constraint: 1 <= 0."])
+  , (fMod, "SFixed 0 1", "0.75",                                             ["Literal 0.75 cannot be represented exactly by Fixed", "The fractional part needs at least 2 bit(s)."])
+  , (fMod, "SFixed 0 1", "0.1",                                              ["Literal 0.1 cannot be represented exactly by", "The reduced denominator 10 is not a power of 2."])
+  , (fMod, "(KnownNat f, 1 <= f) => SFixed 0 f", "0.5",                      ["Literal 0.5 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s), including sign bit.", "Possible fix: add a constraint: 1 <= 0."])
+  , (fMod, "(KnownNat f, 1 <= f) => SFixed 0 f", "0.75",                     ["Literal 0.75 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s), including sign bit.", "Possible fix: add a constraint: 1 <= 0."])
+  , (fMod, "(KnownNat f, 2 <= f) => SFixed 0 f", "0.75",                     ["Literal 0.75 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s), including sign bit.", "Possible fix: add a constraint: 1 <= 0."])
+  , (fMod, "(KnownNat f, 3 <= f) => SFixed 0 f", "0.75",                     ["Literal 0.75 is (potentially) out of bounds.", "Note: integer part needs at least 1 bit(s), including sign bit.", "Possible fix: add a constraint: 1 <= 0."])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n, 1 <= f) => SFixed n f", "0.5",  [])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n, 1 <= f) => SFixed n f", "0.75", ["Literal 0.75 cannot be represented exactly by Fixed", "The fractional part needs at least 2 bit(s).", "Possible fix: add a constraint: 2 <= f."])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n, 2 <= f) => SFixed n f", "0.75", [])
+  , (fMod, "(KnownNat f, KnownNat n, 1 <= n, 3 <= f) => SFixed n f", "0.75", [])
+
+  , (fMod, "(KnownNat f, KnownNat n) => UFixed n f", "255.0",                ["Literal 255.0 is (potentially) out of bounds.", "Note: integer part needs at least 8 bit(s).", "Possible fix: add a constraint: 8 <= n."])
+  , (fMod, "(KnownNat f, KnownNat n) => UFixed n f", "256.0",                ["Literal 256.0 is (potentially) out of bounds.", "Note: integer part needs at least 9 bit(s).", "Possible fix: add a constraint: 9 <= n."])
+  ]
+
+tests :: TestTree
+tests = testGroup "Fixed" [integerTests, rationalTests]
diff --git a/tests/Clash/Tests/CheckedLiterals/Index.hs b/tests/Clash/Tests/CheckedLiterals/Index.hs
new file mode 100644
--- /dev/null
+++ b/tests/Clash/Tests/CheckedLiterals/Index.hs
@@ -0,0 +1,44 @@
+{-|
+Copyright  :  (C) 2026, QBayLogic B.V.
+License    :  BSD2 (see the file LICENSE)
+Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
+
+The type argument of 'Index' is the exclusive upper bound, not a bit width,
+so @Index 4@ has bounds @[0 .. 3]@ (the same range as @Unsigned 2@). The
+polymorphic bounds message therefore reads @Index n has bounds: [0 .. n - 1]@
+rather than @[0 .. (2 ^ n) - 1]@.
+-}
+
+module Clash.Tests.CheckedLiterals.Index (tests) where
+
+import Prelude
+
+import Test.Tasty (TestTree, testGroup)
+import Clash.Tests.CheckedLiterals.Common (toTestCases)
+
+iMod :: String
+iMod = "Clash.Sized.Index"
+
+tests :: TestTree
+tests = testGroup "Index" $ toTestCases
+  [ (iMod, "Index 1", "0",                            [])
+  , (iMod, "Index 1", "-1",                           ["Literal -1 is out of bounds.", "Index 1 has bounds: [0 .. 0]."])
+  , (iMod, "Index 1", "1",                            ["Literal 1 is (potentially) out of bounds.", "Index 1 has bounds: [0 .. 0]."])
+  , (iMod, "Index 2", "0",                            [])
+  , (iMod, "Index 2", "-1",                           ["Literal -1 is out of bounds.", "Index 2 has bounds: [0 .. 1]."])
+  , (iMod, "Index 2", "1",                            [])
+  , (iMod, "Index 2", "2",                            ["Literal 2 is (potentially) out of bounds.", "Index 2 has bounds: [0 .. 1]."])
+  , (iMod, "Index 4", "0",                            [])
+  , (iMod, "Index 4", "-1",                           ["Literal -1 is out of bounds.", "Index 4 has bounds: [0 .. 3]."])
+  , (iMod, "Index 4", "1",                            [])
+  , (iMod, "Index 4", "3",                            [])
+  , (iMod, "Index 4", "4",                            ["Literal 4 is (potentially) out of bounds.", "Index 4 has bounds: [0 .. 3]."])
+  , (iMod, "(KnownNat n) => Index n", "0",            ["Literal 0 is (potentially) out of bounds.", "Index n has bounds: [0 .. n - 1].", "Possible fix: add a constraint: 1 <= n."])
+  , (iMod, "(KnownNat n) => Index n", "1",            ["Literal 1 is (potentially) out of bounds.", "Index n has bounds: [0 .. n - 1]", "Possible fix: add a constraint: 2 <= n."])
+  , (iMod, "(KnownNat n) => Index n", "-1",           ["Literal -1 is out of bounds.", "Index n has bounds: [0 .. n - 1]."])
+  , (iMod, "(KnownNat n, 1 <= n) => Index n", "0",    [])
+  , (iMod, "(KnownNat n, 2 <= n) => Index n", "1",    [])
+  , (iMod, "(KnownNat n, 1 <= n) => Index n", "-1",   ["Literal -1 is out of bounds.", "Index n has bounds: [0 .. n - 1]."])
+  , (iMod, "(KnownNat n, 3 <= n) => Index n", "2",    [])
+  , (iMod, "(KnownNat n, 3 <= n) => Index n", "3",    ["Literal 3 is (potentially) out of bounds.", "Index n has bounds: [0 .. n - 1].", "Possible fix: add a constraint: 4 <= n."])
+  ]
diff --git a/tests/Clash/Tests/CheckedLiterals/Signals.hs b/tests/Clash/Tests/CheckedLiterals/Signals.hs
new file mode 100644
--- /dev/null
+++ b/tests/Clash/Tests/CheckedLiterals/Signals.hs
@@ -0,0 +1,42 @@
+{-|
+Copyright  :  (C) 2026, QBayLogic B.V.
+License    :  BSD2 (see the file LICENSE)
+Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
+
+Smoke tests for the passthrough @CheckedLiteral@ instances on 'Signal' and
+'DSignal': they forward the check to the signal's element type.
+
+The @toTestCases@ helper only injects one module import, so we rely on
+'System' being re-exported from 'Clash.Signal' and 'Clash.Signal.Delayed'.
+-}
+
+module Clash.Tests.CheckedLiterals.Signals (tests) where
+
+import Prelude
+
+import Test.Tasty (TestTree, testGroup)
+import Clash.Tests.CheckedLiterals.Common (toTestCases)
+
+pMod :: String
+pMod = "Clash.Prelude"
+
+signalTests :: TestTree
+signalTests = testGroup "Signal" $ toTestCases
+  [ (pMod, "Signal System (Unsigned 2)", "3",  [])
+  , (pMod, "Signal System (Unsigned 2)", "4",  ["Literal 4 is (potentially) out of bounds.", "Unsigned 2 has bounds: [0 .. 3]."])
+  , (pMod, "Signal System (Signed 2)",   "-2", [])
+  , (pMod, "Signal System (Signed 2)",   "2",  ["Signed 2 has bounds: [-2 .. 1]"])
+  , (pMod, "Signal System (UFixed 1 2)", "0.75", [])
+  , (pMod, "Signal System (UFixed 1 2)", "-1.0", ["Literal -1.0 is out of bounds, because UFixed cannot represent negative numbers."])
+  ]
+
+dsignalTests :: TestTree
+dsignalTests = testGroup "DSignal" $ toTestCases
+  [ (pMod, "DSignal System 0 (Unsigned 2)", "3", [])
+  , (pMod, "DSignal System 0 (Unsigned 2)", "4", ["Literal 4 is (potentially) out of bounds.", "Unsigned 2 has bounds: [0 .. 3]."])
+  , (pMod, "DSignal System 0 (Signed 2)", "-2",  [])
+  , (pMod, "DSignal System 0 (Signed 2)", "2",   ["Signed 2 has bounds: [-2 .. 1]"])
+  ]
+
+tests :: TestTree
+tests = testGroup "Signals" [signalTests, dsignalTests]
diff --git a/tests/Clash/Tests/CheckedLiterals/Signed.hs b/tests/Clash/Tests/CheckedLiterals/Signed.hs
new file mode 100644
--- /dev/null
+++ b/tests/Clash/Tests/CheckedLiterals/Signed.hs
@@ -0,0 +1,43 @@
+{-|
+Copyright  :  (C) 2026, QBayLogic B.V.
+License    :  BSD2 (see the file LICENSE)
+Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
+-}
+
+module Clash.Tests.CheckedLiterals.Signed (tests) where
+
+import Prelude
+
+import Test.Tasty (TestTree, testGroup)
+import Clash.Tests.CheckedLiterals.Common (toTestCases)
+
+sMod :: String
+sMod = "Clash.Sized.Signed"
+
+tests :: TestTree
+tests = testGroup "Signed" $ toTestCases
+  [ (sMod, "Signed 0", "0",                          [])
+  , (sMod, "Signed 0", "-1",                         ["Literal -1 is (potentially) out of bounds."])
+  , (sMod, "Signed 0", "1",                          ["Literal 1 is (potentially) out of bounds."])
+  , (sMod, "Signed 1", "0",                          [])
+  , (sMod, "Signed 1", "-1",                         [])
+  , (sMod, "Signed 1", "1",                          ["Literal 1 is (potentially) out of bounds.", "Signed 1 has bounds: [-1 .. 0]"])
+  , (sMod, "Signed 2", "0",                          [])
+  , (sMod, "Signed 2", "-1",                         [])
+  , (sMod, "Signed 2", "-2",                         [])
+  , (sMod, "Signed 2", "-3",                         ["Literal -3 is (potentially) out of bounds.", "Signed 2 has bounds: [-2 .. 1]"])
+  , (sMod, "Signed 2", "1",                          [])
+  , (sMod, "Signed 2", "2",                          ["Signed 2 has bounds: [-2 .. 1]"])
+  , (sMod, "(KnownNat n) => Signed n", "0",          [])
+  , (sMod, "(KnownNat n) => Signed n", "1",          ["Signed n has bounds: [-2 ^ (n - 1) .. (2 ^ (n - 1)) - 1]", "Possible fix: add a constraint: 2 <= n."])
+  , (sMod, "(KnownNat n) => Signed n", "-1",         ["Literal -1 is (potentially) out of bounds.", "Signed n has bounds: [-2 ^ (n - 1) .. (2 ^ (n - 1)) - 1]", "Possible fix: add a constraint: 1 <= n."])
+  , (sMod, "(KnownNat n, 1 <= n) => Signed n", "0",  [])
+  , (sMod, "(KnownNat n, 1 <= n) => Signed n", "1",  ["Signed n has bounds: [-2 ^ (n - 1) .. (2 ^ (n - 1)) - 1]", "Possible fix: add a constraint: 2 <= n."])
+  , (sMod, "(KnownNat n, 1 <= n) => Signed n", "-1", [])
+  , (sMod, "(KnownNat n, 2 <= n) => Signed n", "0",  [])
+  , (sMod, "(KnownNat n, 2 <= n) => Signed n", "1",  [])
+  , (sMod, "(KnownNat n, 2 <= n) => Signed n", "-1", [])
+  , (sMod, "(KnownNat n, 3 <= n) => Signed n", "0",  [])
+  , (sMod, "(KnownNat n, 3 <= n) => Signed n", "1",  [])
+  , (sMod, "(KnownNat n, 3 <= n) => Signed n", "-1", [])
+  ]
diff --git a/tests/Clash/Tests/CheckedLiterals/Unsigned.hs b/tests/Clash/Tests/CheckedLiterals/Unsigned.hs
new file mode 100644
--- /dev/null
+++ b/tests/Clash/Tests/CheckedLiterals/Unsigned.hs
@@ -0,0 +1,43 @@
+{-|
+Copyright  :  (C) 2026, QBayLogic B.V.
+License    :  BSD2 (see the file LICENSE)
+Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
+-}
+
+module Clash.Tests.CheckedLiterals.Unsigned (tests) where
+
+import Prelude
+
+import Test.Tasty (TestTree, testGroup)
+import Clash.Tests.CheckedLiterals.Common (toTestCases)
+
+uMod :: String
+uMod = "Clash.Sized.Unsigned"
+
+tests :: TestTree
+tests = testGroup "Unsigned" $ toTestCases
+  [ (uMod, "Unsigned 0", "0",                           [])
+  , (uMod, "Unsigned 0", "-1",                          ["Literal -1 is out of bounds.", "Unsigned 0 has bounds: [0 .. 0]."])
+  , (uMod, "Unsigned 0", "1",                           ["Literal 1 is (potentially) out of bounds.", "Unsigned 0 has bounds: [0 .. 0]."])
+  , (uMod, "Unsigned 1", "0",                           [])
+  , (uMod, "Unsigned 1", "-1",                          ["Literal -1 is out of bounds.", "Unsigned 1 has bounds: [0 .. 1]."])
+  , (uMod, "Unsigned 1", "1",                           [])
+  , (uMod, "Unsigned 1", "2",                           ["Literal 2 is (potentially) out of bounds.", "Unsigned 1 has bounds: [0 .. 1]."])
+  , (uMod, "Unsigned 2", "0",                           [])
+  , (uMod, "Unsigned 2", "-1",                          ["Literal -1 is out of bounds.", "Unsigned 2 has bounds: [0 .. 3]."])
+  , (uMod, "Unsigned 2", "1",                           [])
+  , (uMod, "Unsigned 2", "2",                           [])
+  , (uMod, "Unsigned 2", "3",                           [])
+  , (uMod, "Unsigned 2", "4",                           ["Literal 4 is (potentially) out of bounds.", "Unsigned 2 has bounds: [0 .. 3]."])
+  , (uMod, "(KnownNat n) => Unsigned n", "0",           [])
+  , (uMod, "(KnownNat n) => Unsigned n", "1",           ["Literal 1 is (potentially) out of bounds.", "Unsigned n has bounds: [0 .. (2 ^ n) - 1]", "Possible fix: add a constraint: 1 <= n."])
+  , (uMod, "(KnownNat n) => Unsigned n", "-1",          ["Literal -1 is out of bounds.", "Unsigned n has bounds: [0 .. (2 ^ n) - 1]."])
+  , (uMod, "(KnownNat n, 1 <= n) => Unsigned n", "0",   [])
+  , (uMod, "(KnownNat n, 1 <= n) => Unsigned n", "1",   [])
+  , (uMod, "(KnownNat n, 1 <= n) => Unsigned n", "-1",  ["Literal -1 is out of bounds.", "Unsigned n has bounds: [0 .. (2 ^ n) - 1]."])
+  , (uMod, "(KnownNat n, 2 <= n) => Unsigned n", "0",   [])
+  , (uMod, "(KnownNat n, 2 <= n) => Unsigned n", "1",   [])
+  , (uMod, "(KnownNat n, 2 <= n) => Unsigned n", "-1",  ["Literal -1 is out of bounds.", "Unsigned n has bounds: [0 .. (2 ^ n) - 1]."])
+  , (uMod, "(KnownNat n, 7 <= n) => Unsigned n", "255", ["Literal 255 is (potentially) out of bounds.", "Unsigned n has bounds: [0 .. (2 ^ n) - 1].", "Possible fix: add a constraint: 8 <= n."])
+  , (uMod, "(KnownNat n, 8 <= n) => Unsigned n", "256", ["Literal 256 is (potentially) out of bounds.", "Unsigned n has bounds: [0 .. (2 ^ n) - 1].", "Possible fix: add a constraint: 9 <= n."])
+  ]
diff --git a/tests/Clash/Tests/CheckedLiterals/Wrappers.hs b/tests/Clash/Tests/CheckedLiterals/Wrappers.hs
new file mode 100644
--- /dev/null
+++ b/tests/Clash/Tests/CheckedLiterals/Wrappers.hs
@@ -0,0 +1,43 @@
+{-|
+Copyright  :  (C) 2026, QBayLogic B.V.
+License    :  BSD2 (see the file LICENSE)
+Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
+
+Smoke tests for the passthrough @CheckedLiteral@ instances on the numeric
+wrapper newtypes: they should forward the check to the underlying type —
+good literals type-check, bad ones surface the underlying type's error.
+-}
+
+module Clash.Tests.CheckedLiterals.Wrappers (tests) where
+
+import Prelude
+
+import Test.Tasty (TestTree, testGroup)
+import Clash.Tests.CheckedLiterals.Common (toTestCases)
+
+wrapU, satS, ovU, zerS, errUF :: String
+wrapU = "Clash.Num.Wrapping, Clash.Sized.Unsigned"
+satS  = "Clash.Num.Saturating, Clash.Sized.Signed"
+ovU   = "Clash.Num.Overflowing, Clash.Sized.Unsigned"
+zerS  = "Clash.Num.Zeroing, Clash.Sized.Signed"
+errUF = "Clash.Num.Erroring, Clash.Sized.Fixed"
+
+tests :: TestTree
+tests = testGroup "Wrappers" $ toTestCases
+  [ (wrapU, "Wrapping (Unsigned 2)",   "3",    [])
+  , (wrapU, "Wrapping (Unsigned 2)",   "4",    ["Literal 4 is (potentially) out of bounds.", "Unsigned 2 has bounds: [0 .. 3]."])
+  , (wrapU, "Wrapping (Unsigned 2)",   "-1",   ["Literal -1 is out of bounds.", "Unsigned 2 has bounds: [0 .. 3]."])
+
+  , (satS,  "Saturating (Signed 2)",   "-2",   [])
+  , (satS,  "Saturating (Signed 2)",   "2",    ["Signed 2 has bounds: [-2 .. 1]"])
+
+  , (ovU,   "Overflowing (Unsigned 2)", "3",   [])
+  , (ovU,   "Overflowing (Unsigned 2)", "4",   ["Literal 4 is (potentially) out of bounds.", "Unsigned 2 has bounds: [0 .. 3]."])
+
+  , (zerS,  "Zeroing (Signed 2)",      "-1",   [])
+  , (zerS,  "Zeroing (Signed 2)",      "2",    ["Signed 2 has bounds: [-2 .. 1]"])
+
+  , (errUF, "Erroring (UFixed 1 2)",   "0.75", [])
+  , (errUF, "Erroring (UFixed 1 2)",   "-1.0", ["Literal -1.0 is out of bounds, because UFixed cannot represent negative numbers."])
+  , (errUF, "Erroring (UFixed 1 2)",   "0.1",  ["Literal 0.1 cannot be represented exactly by", "The reduced denominator 10 is not a power of 2."])
+  ]
diff --git a/tests/Clash/Tests/Laws/SaturatingNum.hs b/tests/Clash/Tests/Laws/SaturatingNum.hs
--- a/tests/Clash/Tests/Laws/SaturatingNum.hs
+++ b/tests/Clash/Tests/Laws/SaturatingNum.hs
@@ -21,6 +21,7 @@
 import Test.Tasty.HUnit.Extra
 
 import Clash.Class.Num
+import Clash.Num.Overflowing (Overflowing, toOverflowing)
 import Clash.Sized.Index (Index)
 import Clash.Sized.Signed (Signed)
 import Clash.Sized.Fixed (SFixed, UFixed)
@@ -151,12 +152,21 @@
 genIndex :: forall n. KnownNat n => Gen (Index n)
 genIndex = genBoundedIntegral
 
+genOIndex :: forall n. KnownNat n => Gen (Overflowing (Index n))
+genOIndex = toOverflowing <$> genIndex
+
 genUnsigned :: forall n. KnownNat n => Gen (Unsigned n)
 genUnsigned = genBoundedIntegral
 
+genOUnsigned :: forall n. KnownNat n => Gen (Overflowing (Unsigned n))
+genOUnsigned = toOverflowing <$> genUnsigned
+
 genSigned :: forall n. KnownNat n => Gen (Signed n)
 genSigned = genBoundedIntegral
 
+genOSigned :: forall n. KnownNat n => Gen (Overflowing (Signed n))
+genOSigned = toOverflowing <$> genSigned
+
 -- | Generates a bounded fractional with a bias towards extreme values:
 --
 --      5%: minBound
@@ -179,9 +189,15 @@
 genSFixed :: forall a b. (KnownNat a, KnownNat b) => Gen (SFixed a b)
 genSFixed = genBoundedFractional
 
+genOSFixed :: forall a b. (KnownNat a, KnownNat b) => Gen (Overflowing (SFixed a b))
+genOSFixed = toOverflowing <$> genSFixed
+
 genUFixed :: forall a b. (KnownNat a, KnownNat b) => Gen (UFixed a b)
 genUFixed = genBoundedFractional
 
+genOUFixed :: forall a b. (KnownNat a, KnownNat b) => Gen (Overflowing (UFixed a b))
+genOUFixed = toOverflowing <$> genUFixed
+
 tests :: TestTree
 tests = testGroup "SaturatingNum"
   [ testSaturationLaws True "Index 1" (genIndex @1)
@@ -225,4 +241,20 @@
   , testSaturationLaws False "UFixed 7 7" (genUFixed @7 @7)
   , testSaturationLaws False "UFixed 121 121" (genUFixed @121 @121)
   , testSaturationLaws False "UFixed 128 128" (genUFixed @128 @128)
+
+  , testSaturationLaws True "Overflowing (Index 1)" (genOIndex @1)
+  , testSaturationLaws True "Overflowing (Index 10)" (genOIndex @10)
+  , testSaturationLaws True "Overflowing (Signed 0)" (genOSigned @0)
+  , testSaturationLaws True "Overflowing (Signed 8)" (genOSigned @8)
+  , testSaturationLaws True "Overflowing (Unsigned 0)" (genOUnsigned @0)
+  , testSaturationLaws True "Overflowing (Unsigned 8)" (genOUnsigned @8)
+  , testSaturationLaws False "Overflowing (UFixed 7 7)" (genOUFixed @7 @7)
+  , testSaturationLaws False "Overflowing (SFixed 0 0)" (genOSFixed @0 @0)
+  , testSaturationLaws False "Overflowing (SFixed 0 1)" (genOSFixed @0 @1)
+  , testSaturationLaws False "Overflowing (SFixed 1 0)" (genOSFixed @1 @0)
+  , testSaturationLaws False "Overflowing (SFixed 7 7)" (genOSFixed @7 @7)
+  , testSaturationLaws False "Overflowing (UFixed 0 0)" (genOUFixed @0 @0)
+  , testSaturationLaws False "Overflowing (UFixed 0 1)" (genOUFixed @0 @1)
+  , testSaturationLaws False "Overflowing (UFixed 1 0)" (genOUFixed @1 @0)
+  , testSaturationLaws False "Overflowing (UFixed 7 7)" (genOUFixed @7 @7)
   ]
diff --git a/tests/Clash/Tests/TopEntityGeneration.hs b/tests/Clash/Tests/TopEntityGeneration.hs
--- a/tests/Clash/Tests/TopEntityGeneration.hs
+++ b/tests/Clash/Tests/TopEntityGeneration.hs
@@ -73,7 +73,15 @@
   , someOtherField :: "c" ::: Signal System ("wobble" ::: Int)
   }
 
+newtype N a = N a
 
+data NewtypedRecord =
+  NewtypedRecord
+    { nrA :: "a" ::: Bool
+    , nrB :: "b" ::: Bool
+    }
+
+
 topEntity1 :: "in1" ::: Signal System Int
            -> "in2" ::: Signal System Bool
            -> "out" ::: Signal System Int
@@ -261,6 +269,17 @@
     ]
     (PortName "out")
 
+-- See https://github.com/clash-lang/clash-compiler/issues/3066
+topEntity11 :: "o" ::: N NewtypedRecord
+topEntity11 = undefined
+makeTopEntity 'topEntity11
+
+expectedTopEntity11 :: TopEntity
+expectedTopEntity11 =
+  Synthesize "topEntity11"
+    []
+    (PortProduct "o" [PortName "a", PortName "b"])
+
 topEntityFailure1
   :: "int"     ::: Signal System Int
   -> "tuple"   ::: ("tup1" ::: Signal System (BitVector 7), "tup2" ::: Signal System (BitVector 9))
@@ -342,6 +361,9 @@
       , testCase "topEntity10" $
           $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity10)
           @?= Just expectedTopEntity10
+      , testCase "topEntity11" $
+          $(unTypeQ $ maybeBuildTopEntity Nothing 'topEntity11)
+          @?= Just expectedTopEntity11
       ]
     , testGroup "Expected failures"
       [ testCase "topEntityFailure1" $
diff --git a/tests/Test/Tasty/AssertGhc.hs b/tests/Test/Tasty/AssertGhc.hs
new file mode 100644
--- /dev/null
+++ b/tests/Test/Tasty/AssertGhc.hs
@@ -0,0 +1,127 @@
+{-|
+Copyright  :  (C) 2026, QBayLogic B.V.
+License    :  BSD2 (see the file LICENSE)
+Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>
+
+Compile-time assertion helpers for @checked-literals@ tests: spawns a
+subprocess GHC on a generated module and checks whether it succeeds or
+fails with the expected error substrings.
+
+Ported from
+<https://github.com/clash-lang/checked-literals/blob/main/tests/Test/Tasty/AssertGhc.hs>.
+-}
+
+{-# LANGUAGE CPP #-}
+
+module Test.Tasty.AssertGhc where
+
+import Prelude
+
+import Data.List (isInfixOf)
+import Data.Maybe (fromMaybe)
+import System.Environment (lookupEnv)
+import System.Exit
+import System.IO
+import System.IO.Temp
+import System.Process
+import Test.Tasty (TestTree, askOption)
+import Test.Tasty.HUnit
+import Test.Tasty.Options
+import Text.Read (readMaybe)
+
+data Expected = ExpectFailure [String] | ExpectSuccess
+
+-- | Option to enable debug output of GHC error messages
+newtype DebugGhc = DebugGhc Bool
+  deriving (Show, Read)
+
+instance IsOption DebugGhc where
+  defaultValue = DebugGhc False
+  parseValue = fmap DebugGhc . readMaybe
+  optionName = return "debug-ghc"
+  optionHelp = return "Print full GHC output for error test cases"
+  optionCLParser = flagCLParser Nothing (DebugGhc True)
+
+testCaseGhc :: String -> String -> Expected -> TestTree
+testCaseGhc name source expected =
+  askOption $ \(DebugGhc debugGhc) ->
+    testCaseInfo name $ do
+      debugOutput <- assertGhc source expected
+      if debugGhc then return debugOutput else return ""
+
+{- | Assert that a Haskell code snippet fails to compile with expected error messages
+Returns the GHC output for display in test results if debug flag is set.
+
+The subprocess GHC picks up @clash-prelude@ and @checked-literals@ via the
+@.ghc.environment.*@ file that cabal drops next to the test binary. This means
+the test binary must be run from (or below) the project root where cabal
+generates the environment file; @cabal run clash-prelude:unittests@ satisfies
+that.
+-}
+assertGhc :: String -> Expected -> IO String
+assertGhc source expected = do
+  -- XXX: This will pick the wrong GHC if the HC environment variable (as seen on CI)
+  --      isn't set and the test suite is compiled with a GHC compiler other than the
+  --      system's default.
+  hc <- fromMaybe "ghc" <$> lookupEnv "HC"
+  withSystemTempFile "ShouldError.hs" $ \tempFile tempHandle -> do
+    -- Write source with proper Main module structure
+    hPutStr tempHandle "module Main where\n"
+    hPutStr tempHandle source
+    hPutStr tempHandle "\nmain :: IO ()\nmain = return ()\n"
+    hClose tempHandle
+    (exitCode, _, stderrOutput) <-
+      readProcessWithExitCode
+        hc
+        [ "-XCPP"
+        , "-XDataKinds"
+        , "-XTypeOperators"
+        , "-XTypeApplications"
+        , "-XTypeFamilies"
+        , "-XFlexibleContexts"
+        , "-XUndecidableInstances"
+        , "-XNoStarIsType"
+        , "-XViewPatterns"
+        , "-XNoImplicitPrelude"
+        , "-fno-code"
+        , "-package", "clash-prelude"
+        , "-package", "checked-literals"
+        , "-fplugin=GHC.TypeLits.KnownNat.Solver"
+        , "-fplugin=GHC.TypeLits.Normalise"
+        , "-fplugin=GHC.TypeLits.Extra.Solver"
+        , "-fplugin=CheckedLiterals"
+        , tempFile
+        ]
+        ""
+    case (exitCode, expected) of
+      (ExitSuccess, ExpectSuccess) ->
+        return ""
+      (ExitSuccess, ExpectFailure _) ->
+        assertFailure "Expected compilation to fail but it succeeded" >> return ""
+      (ExitFailure _, ExpectSuccess) ->
+        assertFailure ("Expected compilation to succeed but it failed with error:\n" ++ stderrOutput)
+          >> return ""
+      (ExitFailure _, ExpectFailure expectedErrors) ->
+        let cleanedStderr = removeProblemChars stderrOutput
+            cleanedExpected = map removeProblemChars expectedErrors
+         in if all (`isInfixOf` cleanedStderr) cleanedExpected
+              then return stderrOutput
+              else do
+                _ <-
+                  assertFailure $
+                    "Error message mismatch:\n"
+                      ++ "Expected substrings: "
+                      ++ show expectedErrors
+                      ++ "\n"
+                      ++ "Actual output:\n"
+                      ++ stderrOutput
+                return stderrOutput
+
+{- | Remove problematic characters that vary depending on locale
+The kind and amount of quotes in GHC error messages changes depending on
+whether or not our locale supports unicode.
+-}
+removeProblemChars :: String -> String
+removeProblemChars = filter (`notElem` problemChars)
+ where
+  problemChars = "\x2018\x2019`'"
diff --git a/tests/unittests.hs b/tests/unittests.hs
--- a/tests/unittests.hs
+++ b/tests/unittests.hs
@@ -8,6 +8,7 @@
 import qualified Clash.Tests.BitVector
 import qualified Clash.Tests.BlockRam
 import qualified Clash.Tests.BlockRam.Blob
+import qualified Clash.Tests.CheckedLiterals
 import qualified Clash.Tests.Clocks
 import qualified Clash.Tests.Counter
 import qualified Clash.Tests.DerivingDataRepr
@@ -42,6 +43,7 @@
   , Clash.Tests.BitVector.tests
   , Clash.Tests.BlockRam.Blob.tests
   , Clash.Tests.BlockRam.tests
+  , Clash.Tests.CheckedLiterals.tests
   , Clash.Tests.Clocks.tests
   , Clash.Tests.Counter.tests
   , Clash.Tests.DerivingDataRepr.tests
