{- | Copyright : (C) 2025 , Martijn Bastiaan 2026 , QBayLogic B.V. License : BSD2 (see the file LICENSE) Maintainer : QBayLogic B.V. <devops@qbaylogic.com> 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 'Prelude.fromIntegral', no conversion silently changes the value. == __Choosing a conversion__ Clash offers several ways to convert between number types such as 'Clash.Sized.BitVector.BitVector', 'Clash.Sized.Unsigned.Unsigned', 'Clash.Sized.Signed.Signed', 'Clash.Sized.Index.Index', 'Int' and 'Data.Word.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 'Clash.Class.Resize.resize', 'Clash.Class.Resize.zeroExtend', 'Clash.Class.Resize.signExtend' or 'Clash.Class.Resize.truncateB' from the 'Clash.Class.Resize.Resize' class. Making a number narrower with 'Clash.Class.Resize.truncateB' drops its most significant bits. 'Clash.Class.Resize.resize' does the same, except for signed numbers: it keeps the sign bit and drops the bits next to it. 'Clash.Sized.Index.Index' is an exception: a value that does not fit results in an 'Clash.XException.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 'Clash.Class.Resize.checkedResize' or 'Clash.Class.Resize.checkedTruncateB' to have simulation check that assumption. To handle values that do not fit, use 'maybeNumConvert'. === Keep the bits, change the type Use 'Clash.Class.BitPack.bitCoerce' to convert between types of the same bit width, or 'Clash.Class.BitPack.pack' and 'Clash.Class.BitPack.unpack' to convert to and from 'Clash.Sized.BitVector.BitVector'. These keep the bit pattern, not the value: a negative 'Clash.Sized.Signed.Signed' number becomes a large 'Clash.Sized.Unsigned.Unsigned' one. >>> bitCoerce (-5 :: Signed 6) :: Unsigned 6 59 >>> pack (5 :: Unsigned 4) 0b0101 Not every bit pattern represents an 'Clash.Sized.Index.Index'. Use 'Clash.Class.BitPack.maybeUnpack' to reject such patterns, or 'Clash.Sized.Index.bv2i' to pick an 'Clash.Sized.Index.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: 'Clash.Sized.Unsigned.Unsigned' and 'Clash.Sized.Signed.Signed' wrap around, while 'Clash.Sized.Index.Index' results in an 'Clash.XException.XException'. The functions above make that choice explicit. == __Relation to @convertible@__ 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__ If you want to implement 'NumConvert' and 'MaybeNumConvert' for your own type, see 'NumConvertCanonical'. -} module Clash.Class.NumConvert ( NumConvert, numConvert, MaybeNumConvert, maybeNumConvert, -- * For implementers only NumConvertCanonical(..), MaybeNumConvertCanonical(..), Canonical, ) where import Clash.Class.NumConvert.Internal.MaybeNumConvert import Clash.Class.NumConvert.Internal.NumConvert import Clash.Class.NumConvert.Internal.Canonical {- $setup >>> :set -fplugin GHC.TypeLits.Extra.Solver >>> :set -fplugin GHC.TypeLits.KnownNat.Solver >>> :set -fplugin GHC.TypeLits.Normalise >>> import Clash.Prelude >>> import Clash.Class.NumConvert -}