|
| 1 | +# Limitations of the compiler |
| 2 | + |
| 3 | +Here is a list of Haskell features for which the Clash compiler has only _limited_ support (for now): |
| 4 | + |
| 5 | +* __Recursively defined functions__ |
| 6 | + |
| 7 | + At first hand, it seems rather bad that a compiler for a functional language cannot synthesize recursively defined functions to circuits. |
| 8 | + However, when viewing your functions as a _structural_ specification of a circuit, this _feature_ of the Clash compiler makes sense. |
| 9 | + Also, only certain types of recursion are considered non-synthesizable; recursively defined values are for example synthesizable: they are (often) synthesized to feedback loops. |
| 10 | + |
| 11 | + Let us distinguish between three variants of recursion: |
| 12 | + |
| 13 | + * __Dynamic data-dependent recursion__ |
| 14 | + |
| 15 | + As demonstrated in this definition of a function that calculates the n'th Fibbonacci number: |
| 16 | + |
| 17 | + ``` haskell |
| 18 | + fibR 0 = 0 |
| 19 | + fibR 1 = 1 |
| 20 | + fibR n = fibR (n-1) + fibR (n-2) |
| 21 | + ``` |
| 22 | + |
| 23 | + To get the first 10 numbers, we do the following: |
| 24 | + |
| 25 | + ``` |
| 26 | + >>> import qualified Data.List as L |
| 27 | + >>> L.map fibR [0..9] |
| 28 | + [0,1,1,2,3,5,8,13,21,34] |
| 29 | + ``` |
| 30 | + |
| 31 | + The `fibR` function is not synthesizable by the Clash compiler, because, when we take a _structural_ view, `fibR` describes an infinitely deep structure. |
| 32 | + |
| 33 | + In principle, descriptions like the above could be synthesized to a circuit, but it would have to be a _sequential_ circuit. |
| 34 | + Where the most general synthesis would then require a stack. |
| 35 | + Such a synthesis approach is also known as _behavioral_ synthesis, something which the Clash compiler simply does not do. |
| 36 | + One reason that Clash does not do this is because it does not fit the paradigm that only functions working on values of type `Signal` result in sequential circuits, and all other (non higher-order) functions result in combinational circuits. |
| 37 | + This paradigm gives the designer the most straightforward mapping from the original Haskell description to generated circuit, and thus the greatest control over the eventual size of the circuit and longest propagation delay. |
| 38 | + |
| 39 | + * __Value-recursion__ |
| 40 | + |
| 41 | + As demonstrated in this definition of a function that calculates the n'th Fibbonaci number on the n'th clock cycle: |
| 42 | + |
| 43 | + ``` haskell |
| 44 | + fibS :: SystemClockResetEnable => Signal System (Unsigned 64) |
| 45 | + fibS = r |
| 46 | + where r = register 0 r + register 0 (register 1 r) |
| 47 | + ``` |
| 48 | + |
| 49 | + To get the first 10 numbers, we do the following: |
| 50 | + |
| 51 | + ``` |
| 52 | + >>> sampleN @System 11 fibS |
| 53 | + [0,0,1,1,2,3,5,8,13,21,34] |
| 54 | + ``` |
| 55 | + |
| 56 | + Unlike the `fibR` function, the above `fibS` function _is_ synthesizable by the Clash compiler. |
| 57 | + Where the recursively defined (non-function) value _r_ is synthesized to a feedback loop containing three registers and one adder. |
| 58 | + |
| 59 | + Note that not all recursively defined values result in a feedback loop. |
| 60 | + An example that uses recursively defined values which does not result in a feedback loop is the following function that performs one iteration of bubble sort: |
| 61 | + |
| 62 | + ``` haskell |
| 63 | + sortV xs = map fst sorted :< (snd (last sorted)) |
| 64 | + where |
| 65 | + lefts = head xs :> map snd (init sorted) |
| 66 | + rights = tail xs |
| 67 | + sorted = zipWith compareAndSwap (lazyV lefts) rights |
| 68 | + |
| 69 | + compareAndSwap a b = if a < b then (a,b) else (b,a) |
| 70 | + ``` |
| 71 | + |
| 72 | + Where we can clearly see that `lefts` and `sorted` are defined in terms of each other. |
| 73 | + Also the above `sortV` function _is_ synthesizable. |
| 74 | + |
| 75 | + * __Static/Structure-dependent recursion__ |
| 76 | + |
| 77 | + Static, or, structure-dependent recursion is a rather _vague_ concept. |
| 78 | + What we mean by this concept are recursive definitions where a user can sensibly imagine that the recursive definition can be completely unfolded (all recursion is eliminated) at compile-time in a finite amount of time. |
| 79 | + |
| 80 | + Such definitions would e.g. be: |
| 81 | + |
| 82 | + ``` haskell |
| 83 | + mapV :: (a -> b) -> Vec n a -> Vec n b |
| 84 | + mapV _ Nil = Nil |
| 85 | + mapV f (Cons x xs) = Cons (f x) (mapV f xs) |
| 86 | + |
| 87 | + topEntity :: Vec 4 Int -> Vec 4 Int |
| 88 | + topEntity = mapV (+1) |
| 89 | + ``` |
| 90 | + |
| 91 | + Where one can imagine that a compiler can unroll the definition of `mapV` four times, knowing that the `topEntity` function applies `mapV` to a `Vec` of length 4. |
| 92 | + Sadly, the compile-time evaluation mechanisms in the Clash compiler are very poor, and a user-defined function such as the `mapV` function defined above, is _currently_ not synthesizable. |
| 93 | + We _do_ plan to add support for this in the future. |
| 94 | + In the mean time, this poor support for user-defined recursive functions is amortized by the fact that the Clash compiler has built-in support for the higher-order functions defined in `Clash.Sized.Vector`. |
| 95 | + Most regular design patterns often encountered in circuit design are captured by the higher-order functions in `Clash.Sized.Vector`. |
| 96 | + |
| 97 | +* __Recursive datatypes__ |
| 98 | + |
| 99 | + The Clash compiler needs to be able to determine a bit-size for any value that will be represented in the eventual circuit. |
| 100 | + More specifically, we need to know the maximum number of bits needed to represent a value. |
| 101 | + While this is trivial for values of the elementary types, sum types, and product types, putting a fixed upper bound on recursive types is not (always) feasible. |
| 102 | + This means that the ubiquitous list type is unsupported! |
| 103 | + The only recursive types that are currently supported by the Clash compiler is the `Vec`tor and `RTree` types, for which the compiler has hard-coded knowledge. |
| 104 | + |
| 105 | + For "easy" `Vec`tor literals you should use Template Haskell splices and the `listToVecTH` _meta_-function. |
| 106 | + |
| 107 | +* __GADTs__ |
| 108 | + |
| 109 | + Clash has experimental support for GADTs. |
| 110 | + Similar to recursive types, Clash cannot determine bit-sizes of GADTs. |
| 111 | + Notable exceptions to this rule are `Vec` and `RTree`. |
| 112 | + You can still use your own GADTs, as long as they can be removed through static analysis. |
| 113 | + For example, the following case will be optimized away and is therefore fine to use: |
| 114 | + |
| 115 | + ``` haskell |
| 116 | + x = |
| 117 | + case resetKind @System of |
| 118 | + SAsynchronous -> 'a' |
| 119 | + SSynchronous -> 'b' |
| 120 | + ``` |
| 121 | + |
| 122 | +* __Floating point types__ |
| 123 | + |
| 124 | + There is no support for the `Float` and `Double` types, if you need numbers with a _fractional_ part you can use the `Fixed` point type. |
| 125 | + |
| 126 | + As to why there is no support for these floating point types: |
| 127 | + |
| 128 | + 1. In order to achieve reasonable operating frequencies, arithmetic circuits for floating point data types must be pipelined. |
| 129 | + 2. Haskell's primitive arithmetic operators on floating point data types, such as `plusFloat#` |
| 130 | + |
| 131 | + ``` haskell |
| 132 | + plusFloat# :: Float# -> Float# -> Float# |
| 133 | + ``` |
| 134 | + |
| 135 | + which underlie `Float`'s `Num` instance, must be implemented as purely combinational circuits according to their type. |
| 136 | + Remember, sequential circuits operate on values of type `Signal dom a`. |
| 137 | + |
| 138 | + Although it is possible to implement purely combinational (not pipelined) arithmetic circuits for floating point data types, the circuit would be unreasonable slow. |
| 139 | + So, without synthesis possibilities for the basic arithmetic operations, there is no point in supporting the floating point data types. |
| 140 | + |
| 141 | +* __Haskell primitive types__ |
| 142 | + |
| 143 | + Only the following primitive Haskell types are supported: |
| 144 | + |
| 145 | + * `Integer` |
| 146 | + * `Int` |
| 147 | + * `Int8` |
| 148 | + * `Int16` |
| 149 | + * `Int32` |
| 150 | + * `Int64` (not available when compiling with `-fclash-intwidth=32` on a 64-bit machine) |
| 151 | + * `Word` |
| 152 | + * `Word8` |
| 153 | + * `Word16` |
| 154 | + * `Word32` |
| 155 | + * `Word64` (not available when compiling with `-fclash-intwidth=32` on a 64-bit machine) |
| 156 | + * `Char` |
| 157 | + |
| 158 | + There are several aspects of which you should take note: |
| 159 | + |
| 160 | + * `Int` and `Word` are represented by the same number of bits as is native for the architecture of the computer on which the Clash compiler is executed. |
| 161 | + This means that if you are working on a 64-bit machine, `Int` and `Word` will be 64-bit. |
| 162 | + This might be problematic when you are working in a team, and one designer has a 32-bit machine, and the other has a 64-bit machine. |
| 163 | + In general, you should be avoiding 'Int' in such cases, but as a band-aid solution, you can force the Clash compiler to use a specific bit-width for `Int` and `Word` using the `-fclash-intwidth=N` flag, where _N_ must either be _32_ or _64_. |
| 164 | + |
| 165 | + * When you use the `-fclash-intwidth=32` flag on a _64-bit_ machine, the 'Word64' and 'Int64' types _cannot_ be translated. This restriction does _not_ apply to the other three combinations of `-fclash-intwidth` flag and machine type. |
| 166 | + |
| 167 | + * The translation of 'Integer' is not meaning-preserving. |
| 168 | + 'Integer' in Haskell is an arbitrary precision integer, something that cannot be represented in a statically known number of bits. |
| 169 | + In the Clash compiler, we chose to represent 'Integer' by the same number of bits as we do for `Int` and `Word`. |
| 170 | + As you have read in a previous bullet point, this number of bits is either 32 or 64, depending on the architecture of the machine the Clash compiler is running on, or the setting of the `-fclash-intwidth` flag. |
| 171 | + |
| 172 | + Consequently, you should use `Integer` with due diligence; be especially careful when using `fromIntegral` as it does a conversion via 'Integer'. |
| 173 | + For example: |
| 174 | + |
| 175 | + ``` haskell |
| 176 | + signedToUnsigned :: Signed 128 -> Unsigned 128 |
| 177 | + signedToUnsigned = fromIntegral |
| 178 | + ``` |
| 179 | + |
| 180 | + can either lose the top 64 or 96 bits depending on whether `Integer` is represented by 64 or 32 bits. |
| 181 | + Instead, when doing such conversions, you should use `bitCoerce`: |
| 182 | + |
| 183 | + ``` haskell |
| 184 | + signedToUnsigned :: Signed 128 -> Unsigned 128 |
| 185 | + signedToUnsigned = bitCoerce |
| 186 | + ``` |
| 187 | + |
| 188 | +* __Side-effects: `IO`, `ST`, etc.__ |
| 189 | + |
| 190 | + There is no support for side-effecting computations such as those in the `IO` or `ST` monad. |
| 191 | + There is also no support for Haskell's [FFI](http://www.haskell.org/haskellwiki/Foreign_Function_Interface). |
0 commit comments