clash-prelude
Copyright(C) 2025 Martijn Bastiaan
2026 QBayLogic B.V.
LicenseBSD2 (see the file LICENSE)
MaintainerQBayLogic B.V. <devops@qbaylogic.com>
Safe HaskellSafe-Inferred
LanguageHaskell2010

Clash.Class.NumConvert

Description

Utilities for converting between Clash number types in a non-erroring way. Its existence is motivated by the observation that Clash users often need to convert between different number types (e.g., Unsigned to Signed) and that it is not always clear how to do so properly. Two classes are exported:

  • NumConvert: for conversions that, based on types, are guaranteed to succeed.
  • MaybeNumConvert: for conversions that may fail for some values.

As opposed to fromIntegral, no conversion silently changes the value.

Choosing a conversion

Expand

Clash offers several ways to convert between number types such as BitVector, Unsigned, Signed, Index, Int and Word8. Which one to use depends on what should happen to the value.

Keep the value, and the target type can hold every value of the source type

Use numConvert. If the target type cannot represent every value of the source type, the conversion is rejected at compile time.

>>> numConvert (3 :: Index 8) :: Unsigned 3
3
>>> numConvert (200 :: Unsigned 8) :: Signed 9
200
>>> numConvert (-3 :: Signed 4) :: Int
-3

Keep the value, but the target type might not hold it

Use maybeNumConvert. It returns Nothing for values the target type cannot represent.

>>> maybeNumConvert (-3 :: Signed 8) :: Maybe (Unsigned 8)
Nothing
>>> maybeNumConvert (100 :: Unsigned 8) :: Maybe (Index 128)
Just 100

Change the width, keep the type

Use resize, zeroExtend, signExtend or truncateB from the Resize class. Making a number narrower with truncateB drops its most significant bits. resize does the same, except for signed numbers: it keeps the sign bit and drops the bits next to it. Index is an exception: a value that does not fit results in an XException.

>>> resize (-3 :: Signed 4) :: Signed 8
-3
>>> truncateB (300 :: Unsigned 16) :: Unsigned 8
44
>>> resize (7 :: Signed 4) :: Signed 3
3
>>> truncateB (7 :: Signed 4) :: Signed 3
-1

If you know a value fits, use checkedResize or checkedTruncateB to have simulation check that assumption. To handle values that do not fit, use maybeNumConvert.

Keep the bits, change the type

Use bitCoerce to convert between types of the same bit width, or pack and unpack to convert to and from BitVector. These keep the bit pattern, not the value: a negative Signed number becomes a large Unsigned one.

>>> bitCoerce (-5 :: Signed 6) :: Unsigned 6
59
>>> pack (5 :: Unsigned 4)
0b0101

Not every bit pattern represents an Index. Use maybeUnpack to reject such patterns, or bv2i to pick an Index that can hold all of them:

>>> maybeUnpack (12 :: BitVector 4) :: Maybe (Index 10)
Nothing
>>> bv2i (12 :: BitVector 4)
12

Change the type and drop bits

Combine the conversions above. For example, to keep the 8 least significant bits of a Signed 16 as an Unsigned 8:

>>> truncateB (bitCoerce (-1 :: Signed 16) :: Unsigned 16) :: Unsigned 8
255

Why not fromIntegral?

fromIntegral converts through Integer, which has a fixed width in HDL (64 bits by default, see -fclash-intwidth). Bits above that width are lost. It also hides what happens to values the target type cannot hold: Unsigned and Signed wrap around, while Index results in an XException. The functions above make that choice explicit.

Relation to convertible

Expand

Type classes exported here are similar to the convertible package in that it aims to facilitate conversions between different types. It is different in three ways:

  1. It offers no partial functions
  2. All its conversions are translatable to synthesizable HDL
  3. It is focused on (Clash's) number types

Implementing NumConvert for your own type

Expand

If you want to implement NumConvert and MaybeNumConvert for your own type, see NumConvertCanonical.

Synopsis

Documentation

type NumConvert a b = (NumConvertCanonical a (Canonical a), NumConvertCanonical (Canonical a) (Canonical b), NumConvertCanonical (Canonical b) b) Source #

Conversions that are, based on their types, guaranteed to succeed. A successful conversion retains the numerical value interpretation of the source type in the target type and does not produce errors.

Laws

Expand

A conversion is successful if a round trip conversion is lossless. I.e.,

Just x == maybeNumConvert (numConvert @a @b x)

for all values x of type a. It should also preserve the numerical value interpretation of the bits. For types that have an Integral instance, this intuition is captured by:

toInteger x == toInteger (numConvert @a @b x)

Instances should make sure their constraints are as "tight" as possible. I.e., if an instance's constraints cannot be satisfied, then for the same types maybeNumConvert should return Nothing for one or more values in the domain of the source type a:

L.any isNothing (L.map (maybeNumConvert @a @b) [minBound ..])

All implementations should be total, i.e., they should not produce "bottoms".

Additionally, any implementation should be translatable to synthesizable HDL.

numConvert :: forall a b. NumConvert a b => a -> b Source #

Convert a supplied value of type a to a value of type b. The conversion is guaranteed to succeed.

>>> numConvert (3 :: Index 8) :: Unsigned 8
3

The following will fail with a type error, as we cannot prove that all values of Index 8 can be represented by an Unsigned 2:

>>> numConvert (3 :: Index 8) :: Unsigned 2
...

For the time being, if the input is an XException, then the output is too. This property might be relaxed in the future.

type MaybeNumConvert a b = (NumConvertCanonical a (Canonical a), MaybeNumConvertCanonical (Canonical a) (Canonical b), NumConvertCanonical (Canonical b) b) Source #

Conversions that may fail for some values. A successful conversion retains the numerical value interpretation of the source type in the target type. A failure is expressed by returning Nothing, never by an XException.

Laws

Expand

A conversion is either successful or it fails gracefully. I.e., it does not produce errors (also see Clash.XException). I.e.,

x == fromMaybe x (maybeNumConvert @a @b x >>= maybeNumConvert @b @a)

for all values x of type a. It should also preserve the numerical value interpretation of the bits. For types that have an Integral instance, this intuition is captured by:

toInteger x == fromMaybe (toInteger x) (toInteger <$> maybeNumConvert @a @b x)

If a conversion succeeds one way, it should also succeed the other way. I.e.,

isJust (maybeNumConvert @a @b x) `implies` isJust (maybeNumConvert @a @b x >>= maybeNumConvert @b @a)

A conversion should succeed if and only if the value is representable in the target type. For types that have a Bounded and Integral instance, this intuition is captured by:

isJust (maybeNumConvert @a @b x) == (i x >= i (minBound @b) && i x <= i (maxBound @b))

where i = toInteger.

All implementations should be total, i.e., they should not produce "bottoms".

Additionally, any implementation should be translatable to synthesizable HDL.

maybeNumConvert :: forall a b. MaybeNumConvert a b => a -> Maybe b Source #

Convert a supplied value of type a to a value of type b. If the value cannot be represented in the target type, Nothing is returned.

>>> maybeNumConvert (1 :: Index 8) :: Maybe (Unsigned 2)
Just 1
>>> maybeNumConvert (7 :: Index 8) :: Maybe (Unsigned 2)
Nothing

For the time being, if the input is an XException, then the output is too. This property might be relaxed in the future.

For implementers only

class NumConvertCanonical a b where Source #

Internal class for concrete conversions. This class is used internally by NumConvert and should not be used directly. Use NumConvert instead.

If you want to provide an instance for your own type, please only make an instance for a direct mapping from and to Clash types and tell the instance what Clash type it corresponds to. For example, implementing Int16 looks like:

instance NumConvertCanonical Int16 (Signed 16) where
  numConvertCanonical = bitCoerce

instance NumConvertCanonical (Signed 16) Int16 where
  numConvertCanonical = bitCoerce

type instance Canonical Int16 = Signed 16

By doing this, your type will be convertable from and to any other type. For example:

>>> numConvert (10 :: Int16) :: Int32
10
>>> numConvert (15 :: Signed 8) :: Int16
15

Methods

numConvertCanonical :: a -> b Source #

Instances

Instances details
NumConvertCanonical Int16 (Signed 16) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical Int32 (Signed 32) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical Int64 (Signed 64) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical Int8 (Signed 8) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical Word16 (Unsigned 16) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical Word32 (Unsigned 32) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical Word64 (Unsigned 64) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical Word8 (Unsigned 8) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical Bit (BitVector 1) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical Int (Signed 64) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical Word (Unsigned 64) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (BitVector 1) Bit Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (Signed 8) Int8 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (Signed 16) Int16 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (Signed 32) Int32 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (Signed 64) Int64 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (Signed 64) Int Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (Unsigned 8) Word8 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (Unsigned 16) Word16 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (Unsigned 32) Word32 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (Unsigned 64) Word64 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

NumConvertCanonical (Unsigned 64) Word Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

m ~ (n + 1) => NumConvertCanonical (SNat n) (Index m) Source #

Concrete unidirectional instance for converting a type level SNat to a term level number.

Note: This is intentionally unidirectional: a reverse instance maintaining the lossless round trip conversion law would require an existential (producing a type level n that is not determined by the call site) which the type of numConvertCanonical cannot express.

Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, n <= m) => NumConvertCanonical (BitVector n) (BitVector m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, (2 ^ n) <= m) => NumConvertCanonical (BitVector n) (Index m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, (n + 1) <= m) => NumConvertCanonical (BitVector n) (Signed m) Source #

Note: Conversion from BitVector 0 to Signed 0 is lossless, but not within the constraints of the instance.

Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, n <= m) => NumConvertCanonical (BitVector n) (Unsigned m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, n <= (2 ^ m)) => NumConvertCanonical (Index n) (BitVector m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, n <= m) => NumConvertCanonical (Index n) (Index m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, (CLogWZ 2 n 0 + 1) <= m) => NumConvertCanonical (Index n) (Signed m) Source #

Note: Conversion from Index 1 to Signed 0 is lossless, but not within the constraints of the instance.

Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, n <= (2 ^ m)) => NumConvertCanonical (Index n) (Unsigned m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, n <= m) => NumConvertCanonical (Signed n) (Signed m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, n <= m) => NumConvertCanonical (Unsigned n) (BitVector m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, (2 ^ n) <= m) => NumConvertCanonical (Unsigned n) (Index m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, (n + 1) <= m) => NumConvertCanonical (Unsigned n) (Signed m) Source #

Note: Conversion from Unsigned 0 to Signed 0 is lossless, but not within the constraints of the instance.

Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

(KnownNat n, KnownNat m, n <= m) => NumConvertCanonical (Unsigned n) (Unsigned m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.NumConvert

class MaybeNumConvertCanonical a b where Source #

Internal class for concrete conversions that may fail. This class is used internally by MaybeNumConvert and should not be used directly. Use MaybeNumConvert instead.

Instances

Instances details
(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (BitVector n) (BitVector m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (BitVector n) (Index m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (BitVector n) (Signed m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (BitVector n) (Unsigned m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Index n) (BitVector m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Index n) (Index m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Index n) (Signed m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Index n) (Unsigned m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Signed n) (BitVector m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Signed n) (Index m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Signed n) (Signed m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Signed n) (Unsigned m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Unsigned n) (BitVector m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Unsigned n) (Index m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Unsigned n) (Signed m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

(KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Unsigned n) (Unsigned m) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.MaybeNumConvert

type family Canonical a Source #

Type family mapping types to their canonical "unwrapped" Clash form. This is used by numConvert and maybeNumConvert to determine the intermediate type.

Instances

Instances details
type Canonical Int16 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical Int32 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical Int64 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical Int8 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical Word16 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical Word32 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical Word64 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical Word8 Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical Bit Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical Int Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical Int = Signed 64
type Canonical Word Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical (SNat n) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical (SNat n) = Index (n + 1)
type Canonical (BitVector n) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical (Index n) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical (Index n) = Index n
type Canonical (Signed n) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical

type Canonical (Signed n) = Signed n
type Canonical (Unsigned n) Source # 
Instance details

Defined in Clash.Class.NumConvert.Internal.Canonical