Skip to content

Commit 3913be3

Browse files
Merge pull request #3136 from clash-lang/martijn/add-numconvert-through
Fix overlapping instances in `NumConvert` with `Canonical`
2 parents 93ceb2e + 3623249 commit 3913be3

File tree

9 files changed

+382
-193
lines changed

9 files changed

+382
-193
lines changed

clash-prelude/clash-prelude.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ Library
188188
Clash.Class.NumConvert
189189
Clash.Class.NumConvert.Internal.NumConvert
190190
Clash.Class.NumConvert.Internal.MaybeNumConvert
191+
Clash.Class.NumConvert.Internal.Canonical
191192
Clash.Class.Parity
192193
Clash.Class.Resize
193194

clash-prelude/src/Clash/Class/NumConvert.hs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,23 @@ ways:
2222
1. It offers no partial functions
2323
2. All its conversions are translatable to synthesizable HDL
2424
3. It is focused on (Clash's) number types
25+
26+
== __Implementing 'NumConvert' for your own type__
27+
If you want to implement 'NumConvert' and 'MaybeNumConvert' for your own type,
28+
see 'NumConvertCanonical'.
2529
-}
2630
module Clash.Class.NumConvert (
27-
NumConvert (..),
28-
MaybeNumConvert (..),
31+
NumConvert,
32+
numConvert,
33+
MaybeNumConvert,
34+
maybeNumConvert,
35+
36+
-- * For implementers only
37+
NumConvertCanonical(..),
38+
MaybeNumConvertCanonical(..),
39+
Canonical,
2940
) where
3041

3142
import Clash.Class.NumConvert.Internal.MaybeNumConvert
3243
import Clash.Class.NumConvert.Internal.NumConvert
44+
import Clash.Class.NumConvert.Internal.Canonical
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{-# LANGUAGE CPP #-}
2+
{-# LANGUAGE TypeFamilies #-}
3+
{-# OPTIONS_HADDOCK hide #-}
4+
5+
#include "MachDeps.h"
6+
7+
{- |
8+
Copyright : (C) 2026 , Martijn Bastiaan
9+
License : BSD2 (see the file LICENSE)
10+
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>
11+
-}
12+
module Clash.Class.NumConvert.Internal.Canonical where
13+
14+
import Clash.Sized.BitVector
15+
import Clash.Sized.Index
16+
import Clash.Sized.Signed
17+
import Clash.Sized.Unsigned
18+
19+
import Data.Int (Int16, Int32, Int64, Int8)
20+
import Data.Word (Word16, Word32, Word64, Word8)
21+
22+
-- | Type family mapping types to their canonical \"unwrapped\" Clash form.
23+
-- This is used by 'Clash.Class.NumConvert.numConvert' and
24+
-- 'Clash.Class.NumConvert.maybeNumConvert' to determine the intermediate type.
25+
type family Canonical a
26+
27+
-- Instances for Data.Word types
28+
type instance Canonical Word = Unsigned WORD_SIZE_IN_BITS
29+
type instance Canonical Word64 = Unsigned 64
30+
type instance Canonical Word32 = Unsigned 32
31+
type instance Canonical Word16 = Unsigned 16
32+
type instance Canonical Word8 = Unsigned 8
33+
34+
-- Instances for Data.Int types
35+
type instance Canonical Int = Signed WORD_SIZE_IN_BITS
36+
type instance Canonical Int64 = Signed 64
37+
type instance Canonical Int32 = Signed 32
38+
type instance Canonical Int16 = Signed 16
39+
type instance Canonical Int8 = Signed 8
40+
41+
-- Bit
42+
type instance Canonical Bit = BitVector 1
43+
44+
-- Clash types are already in canonical form
45+
type instance Canonical (Unsigned n) = Unsigned n
46+
type instance Canonical (Signed n) = Signed n
47+
type instance Canonical (BitVector n) = BitVector n
48+
type instance Canonical (Index n) = Index n
Lines changed: 71 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE ConstraintKinds #-}
12
{-# LANGUAGE FlexibleContexts #-}
23
{-# LANGUAGE FlexibleInstances #-}
34
{-# LANGUAGE GADTs #-}
@@ -16,6 +17,8 @@ Maintainer : QBayLogic B.V. <devops@qbaylogic.com>
1617
module Clash.Class.NumConvert.Internal.MaybeNumConvert where
1718

1819
import Clash.Class.BitPack
20+
import Clash.Class.NumConvert.Internal.NumConvert (NumConvertCanonical (..))
21+
import Clash.Class.NumConvert.Internal.Canonical (Canonical)
1922
import Clash.Class.Resize
2023
import Clash.Sized.BitVector
2124
import Clash.Sized.Index
@@ -25,29 +28,35 @@ import Clash.Sized.Unsigned
2528
import GHC.TypeLits (KnownNat, type (+), type (<=), type (^))
2629
import GHC.TypeLits.Extra (CLog)
2730

28-
import Data.Int (Int16, Int32, Int64, Int8)
29-
import Data.Word (Word16, Word32, Word64, Word8)
30-
3131
{- $setup
3232
>>> import Clash.Prelude
3333
>>> import Clash.Class.NumConvert
34+
>>> import Data.Word
35+
>>> import Data.Int
36+
-}
37+
38+
{- | Internal class for concrete conversions that may fail. This class is used
39+
internally by 'MaybeNumConvert' and should not be used directly. Use
40+
'MaybeNumConvert' instead.
3441
-}
42+
class MaybeNumConvertCanonical a b where
43+
maybeNumConvertCanonical :: a -> Maybe b
3544

3645
{- | Conversions that may fail for some values. A successful conversion retains
3746
the numerical value interpretation of the source type in the target type. A
3847
failure is expressed by returning 'Nothing', never by an 'Clash.XException.XException'.
3948
4049
== __Laws__
4150
A conversion is either successful or it fails gracefully. I.e., it does not
42-
produces produce errors (also see "Clash.XException"). I.e.,
51+
produce errors (also see "Clash.XException"). I.e.,
4352
4453
> x == fromMaybe x (maybeNumConvert @a @b x >>= maybeNumConvert @b @a)
4554
4655
for all values @x@ of type @a@. It should also preserve the numerical value
4756
interpretation of the bits. For types that have an @Integral@ instance, this
4857
intuition is captured by:
4958
50-
> toInteger x == fromMaybe (toInteger x) (toInteger (numConvert @a @b x))
59+
> toInteger x == fromMaybe (toInteger x) (toInteger <$> maybeNumConvert @a @b x)
5160
5261
If a conversion succeeds one way, it should also succeed the other way. I.e.,
5362
@@ -65,117 +74,79 @@ All implementations should be total, i.e., they should not produce \"bottoms\".
6574
6675
Additionally, any implementation should be translatable to synthesizable HDL.
6776
-}
68-
class MaybeNumConvert a b where
69-
{- | Convert a supplied value of type @a@ to a value of type @b@. If the value
70-
cannot be represented in the target type, 'Nothing' is returned.
71-
72-
>>> maybeNumConvert (1 :: Index 8) :: Maybe (Unsigned 2)
73-
Just 1
74-
>>> maybeNumConvert (7 :: Index 8) :: Maybe (Unsigned 2)
75-
Nothing
76-
77-
For the time being, if the input is an 'Clash.XException.XException', then
78-
the output is too. This property might be relaxed in the future.
79-
-}
80-
maybeNumConvert :: a -> Maybe b
77+
type MaybeNumConvert a b =
78+
( NumConvertCanonical a (Canonical a)
79+
, MaybeNumConvertCanonical (Canonical a) (Canonical b)
80+
, NumConvertCanonical (Canonical b) b
81+
)
82+
83+
{- | Convert a supplied value of type @a@ to a value of type @b@. If the value
84+
cannot be represented in the target type, 'Nothing' is returned.
85+
86+
>>> maybeNumConvert (1 :: Index 8) :: Maybe (Unsigned 2)
87+
Just 1
88+
>>> maybeNumConvert (7 :: Index 8) :: Maybe (Unsigned 2)
89+
Nothing
90+
91+
For the time being, if the input is an 'Clash.XException.XException', then
92+
the output is too. This property might be relaxed in the future.
93+
-}
94+
maybeNumConvert :: forall a b. MaybeNumConvert a b => a -> Maybe b
95+
maybeNumConvert a =
96+
fmap (numConvertCanonical @(Canonical b) @b)
97+
$ maybeNumConvertCanonical @(Canonical a) @(Canonical b)
98+
$ numConvertCanonical @a @(Canonical a) a
8199

82-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (Index n) (Index m) where
83-
maybeNumConvert !a = maybeResize a
100+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Index n) (Index m) where
101+
maybeNumConvertCanonical !a = maybeResize a
84102

85-
instance (KnownNat n, KnownNat m, 1 <= n) => MaybeNumConvert (Index n) (Unsigned m) where
86-
maybeNumConvert !a = maybeResize $ bitCoerce @_ @(Unsigned (CLog 2 n)) a
103+
instance (KnownNat n, KnownNat m, 1 <= n) => MaybeNumConvertCanonical (Index n) (Unsigned m) where
104+
maybeNumConvertCanonical !a = maybeResize $ bitCoerce @_ @(Unsigned (CLog 2 n)) a
87105

88-
instance (KnownNat n, KnownNat m, 1 <= n) => MaybeNumConvert (Index n) (Signed m) where
89-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Unsigned (CLog 2 n)) a
106+
instance (KnownNat n, KnownNat m, 1 <= n) => MaybeNumConvertCanonical (Index n) (Signed m) where
107+
maybeNumConvertCanonical !a = maybeNumConvertCanonical $ bitCoerce @_ @(Unsigned (CLog 2 n)) a
90108

91-
instance (KnownNat n, KnownNat m, 1 <= n) => MaybeNumConvert (Index n) (BitVector m) where
92-
maybeNumConvert !a = maybeResize $ pack a
109+
instance (KnownNat n, KnownNat m, 1 <= n) => MaybeNumConvertCanonical (Index n) (BitVector m) where
110+
maybeNumConvertCanonical !a = maybeResize $ pack a
93111

94-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (Unsigned n) (Index m) where
95-
maybeNumConvert !a = maybeResize $ bitCoerce @_ @(Index (2 ^ n)) a
112+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Unsigned n) (Index m) where
113+
maybeNumConvertCanonical !a = maybeResize $ bitCoerce @_ @(Index (2 ^ n)) a
96114

97-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (Unsigned n) (Unsigned m) where
98-
maybeNumConvert !a = maybeResize a
115+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Unsigned n) (Unsigned m) where
116+
maybeNumConvertCanonical !a = maybeResize a
99117

100-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (Unsigned n) (Signed m) where
101-
maybeNumConvert !a = maybeResize $ bitCoerce @(Unsigned (n + 1)) $ extend a
118+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Unsigned n) (Signed m) where
119+
maybeNumConvertCanonical !a = maybeResize $ bitCoerce @(Unsigned (n + 1)) $ extend a
102120

103-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (Unsigned n) (BitVector m) where
104-
maybeNumConvert !a = maybeResize $ pack a
121+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Unsigned n) (BitVector m) where
122+
maybeNumConvertCanonical !a = maybeResize $ pack a
105123

106-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (Signed n) (Index m) where
107-
maybeNumConvert n
124+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Signed n) (Index m) where
125+
maybeNumConvertCanonical n
108126
| n < 0 = Nothing
109127
| otherwise = maybeResize (bitCoerce @_ @(Index (2 ^ n)) (resize n))
110128

111-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (Signed n) (Unsigned m) where
112-
maybeNumConvert n
129+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Signed n) (Unsigned m) where
130+
maybeNumConvertCanonical n
113131
| n < 0 = Nothing
114132
| otherwise = maybeResize (bitCoerce @(Signed (n + 1)) (extend n))
115133

116-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (Signed n) (Signed m) where
117-
maybeNumConvert !a = maybeResize a
134+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Signed n) (Signed m) where
135+
maybeNumConvertCanonical !a = maybeResize a
118136

119-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (Signed n) (BitVector m) where
120-
maybeNumConvert n
137+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (Signed n) (BitVector m) where
138+
maybeNumConvertCanonical n
121139
| n < 0 = Nothing
122140
| otherwise = maybeResize (pack @(Signed (n + 1)) (extend n))
123141

124-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (BitVector n) (Index m) where
125-
maybeNumConvert !a = maybeResize $ unpack @(Index (2 ^ n)) a
126-
127-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (BitVector n) (Unsigned m) where
128-
maybeNumConvert !a = maybeResize $ unpack @(Unsigned n) a
129-
130-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (BitVector n) (Signed m) where
131-
maybeNumConvert !a = maybeResize $ unpack @(Signed (n + 1)) $ extend a
132-
133-
instance (KnownNat n, KnownNat m) => MaybeNumConvert (BitVector n) (BitVector m) where
134-
maybeNumConvert !a = maybeResize a
135-
136-
instance (MaybeNumConvert (Unsigned 64) a) => MaybeNumConvert Word a where
137-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Unsigned 64) a
138-
instance (MaybeNumConvert (Unsigned 64) a) => MaybeNumConvert Word64 a where
139-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Unsigned 64) a
140-
instance (MaybeNumConvert (Unsigned 32) a) => MaybeNumConvert Word32 a where
141-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Unsigned 32) a
142-
instance (MaybeNumConvert (Unsigned 16) a) => MaybeNumConvert Word16 a where
143-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Unsigned 16) a
144-
instance (MaybeNumConvert (Unsigned 8) a) => MaybeNumConvert Word8 a where
145-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Unsigned 8) a
146-
147-
instance (MaybeNumConvert (Signed 64) a) => MaybeNumConvert Int a where
148-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Signed 64) a
149-
instance (MaybeNumConvert (Signed 64) a) => MaybeNumConvert Int64 a where
150-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Signed 64) a
151-
instance (MaybeNumConvert (Signed 32) a) => MaybeNumConvert Int32 a where
152-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Signed 32) a
153-
instance (MaybeNumConvert (Signed 16) a) => MaybeNumConvert Int16 a where
154-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Signed 16) a
155-
instance (MaybeNumConvert (Signed 8) a) => MaybeNumConvert Int8 a where
156-
maybeNumConvert !a = maybeNumConvert $ bitCoerce @_ @(Signed 8) a
157-
158-
instance (MaybeNumConvert a (Unsigned 64)) => MaybeNumConvert a Word where
159-
maybeNumConvert !a = fmap (bitCoerce @(Unsigned 64)) $ maybeNumConvert a
160-
instance (MaybeNumConvert a (Unsigned 64)) => MaybeNumConvert a Word64 where
161-
maybeNumConvert !a = fmap (bitCoerce @(Unsigned 64)) $ maybeNumConvert a
162-
instance (MaybeNumConvert a (Unsigned 32)) => MaybeNumConvert a Word32 where
163-
maybeNumConvert !a = fmap (bitCoerce @(Unsigned 32)) $ maybeNumConvert a
164-
instance (MaybeNumConvert a (Unsigned 16)) => MaybeNumConvert a Word16 where
165-
maybeNumConvert !a = fmap (bitCoerce @(Unsigned 16)) $ maybeNumConvert a
166-
instance (MaybeNumConvert a (Unsigned 8)) => MaybeNumConvert a Word8 where
167-
maybeNumConvert !a = fmap (bitCoerce @(Unsigned 8)) $ maybeNumConvert a
168-
169-
instance (MaybeNumConvert a (Signed 64)) => MaybeNumConvert a Int64 where
170-
maybeNumConvert !a = fmap (bitCoerce @(Signed 64)) $ maybeNumConvert a
171-
instance (MaybeNumConvert a (Signed 32)) => MaybeNumConvert a Int32 where
172-
maybeNumConvert !a = fmap (bitCoerce @(Signed 32)) $ maybeNumConvert a
173-
instance (MaybeNumConvert a (Signed 16)) => MaybeNumConvert a Int16 where
174-
maybeNumConvert !a = fmap (bitCoerce @(Signed 16)) $ maybeNumConvert a
175-
instance (MaybeNumConvert a (Signed 8)) => MaybeNumConvert a Int8 where
176-
maybeNumConvert !a = fmap (bitCoerce @(Signed 8)) $ maybeNumConvert a
177-
178-
instance (MaybeNumConvert a (BitVector 1)) => MaybeNumConvert a Bit where
179-
maybeNumConvert !a = unpack <$> maybeNumConvert a
180-
instance (MaybeNumConvert (BitVector 1) a) => MaybeNumConvert Bit a where
181-
maybeNumConvert !a = maybeNumConvert (pack a)
142+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (BitVector n) (Index m) where
143+
maybeNumConvertCanonical !a = maybeResize $ unpack @(Index (2 ^ n)) a
144+
145+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (BitVector n) (Unsigned m) where
146+
maybeNumConvertCanonical !a = maybeResize $ unpack @(Unsigned n) a
147+
148+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (BitVector n) (Signed m) where
149+
maybeNumConvertCanonical !a = maybeResize $ unpack @(Signed (n + 1)) $ extend a
150+
151+
instance (KnownNat n, KnownNat m) => MaybeNumConvertCanonical (BitVector n) (BitVector m) where
152+
maybeNumConvertCanonical !a = maybeResize a

0 commit comments

Comments
 (0)