Let pret-lam support blocks of multiple lambda expressions.
[matthijs/master-project/report.git] / Chapters / HardwareDescription.tex
1 \chapter{Hardware description}
2   This chapter will provide an overview of the hardware description language
3   that was created and the issues that have arisen in the process. It will
4   focus on the issues of the language, not the implementation.
5
6   When translating Haskell to hardware, we need to make choices in what kind
7   of hardware to generate for what Haskell constructs. When faced with
8   choices, we've tried to stick with the most obvious choice wherever
9   possible. In a lot of cases, when you look at a hardware description it is
10   comletely clear what hardware is described. We want our translator to
11   generate exactly that hardware whenever possible, to minimize the amount of
12   surprise for people working with it.
13    
14   In this chapter we try to describe how we interpret a Haskell program from a
15   hardware perspective. We provide a description of each Haskell language
16   element that needs translation, to provide a clear picture of what is
17   supported and how.
18
19   \section{Function application}
20   The basic syntactic element of a functional program are functions and
21   function application. These have a single obvious VHDL translation: Each
22   function becomes a hardware component, where each argument is an input port
23   and the result value is the output port.
24
25   Each function application in turn becomes component instantiation. Here, the
26   result of each argument expression is assigned to a signal, which is mapped
27   to the corresponding input port. The output port of the function is also
28   mapped to a signal, which is used as the result of the application.
29
30   An example of a simple program using only function application would be:
31
32   \starthaskell
33   -- | A simple function that returns the and of three bits
34   and3 :: Bit -> Bit -> Bit -> Bit
35   and3 a b c = and (and a b) c
36   \stophaskell
37
38   This results in the following hardware:
39   
40   TODO: Pretty picture
41
42   \subsection{Partial application}
43   It should be obvious that we cannot generate hardware signals for all
44   expressions we can express in Haskell. The most obvious criterium for this
45   is the type of an expression. We will see more of this below, but for now it
46   should be obvious that any expression of a function type cannot be
47   represented as a signal or i/o port to a component.
48
49   From this, we can see that the above translation rules do not apply to a
50   partial application. Let's look at an example:
51
52   \starthaskell
53   -- | Multiply the input word by four.
54   quadruple :: Word -> Word
55   quadruple n = mul (mul n)
56     where
57       mul = (*) 2
58   \stophaskell
59
60   It should be clear that the above code describes the following hardware:
61
62   TODO: Pretty picture
63
64   Here, the definition of mul is a partial function application: It applies
65   \hs{2 :: Word} to the function \hs{(*) :: Word -> Word -> Word} resulting in
66   the expression \hs{(*) 2 :: Word -> Word}. Since this resulting expression
67   is again a function, we can't generate hardware for it directly. This is
68   because the hardware to generate for \hs{mul} depends completely on where
69   and how it is used. In this example, it is even used twice!
70
71   However, it is clear that the above hardware description actually describes
72   valid hardware. In general, we can see that any partial applied function
73   must eventually become completely applied, at which point we can generate
74   hardware for it using the rules for function application above. It might
75   mean that a partial application is passed around quite a bit (even beyond
76   function boundaries), but eventually, the partial application will become
77   completely applied.
78
79   \section{Recursion}
80   An import concept in functional languages is recursion. In it's most basic
81   form, recursion is a function that is defined in terms of itself. This
82   usually requires multiple evaluations of this function, with changing
83   arguments, until eventually an evaluation of the function no longer requires
84   itself.
85
86   Recursion in a hardware description is a bit of a funny thing. Usually,
87   recursion is associated with a lot of nondeterminism, stack overflows, but
88   also flexibility and expressive power.
89
90   Given the notion that each function application will translate to a
91   component instantiation, we are presented with a problem. A recursive
92   function would translate to a component that contains itself. Or, more
93   precisely, that contains an instance of itself. This instance would again
94   contain an instance of itself, and again, into infinity. This is obviously a
95   problem for generating hardware.
96
97   This is expected for functions that describe infinite recursion. In that
98   case, we can't generate hardware that shows correct behaviour in a single
99   cycle (at best, we could generate hardware that needs an infinite number of
100   cycles to complete).
101   
102   However, most recursive hardware descriptions will describe finite
103   recursion. This is because the recursive call is done conditionally. There
104   is usually a case statement where at least one alternative does not contain
105   the recursive call, which we call the "base case". If, for each call to the
106   recursive function, we would be able to detect which alternative applies,
107   we would be able to remove the case expression and leave only the base case
108   when it applies. This will ensure that expanding the recursive functions
109   will terminate after a bounded number of expansions.
110
111   This does imply the extra requirement that the base case is detectable at
112   compile time. In particular, this means that the decision between the base
113   case and the recursive case must not depend on runtime data.
114
115   \subsection{List recursion}
116   The most common deciding factor in recursion is the length of a list that is
117   passed in as an argument. Since we represent lists as vectors that encode
118   the length in the vector type, it seems easy to determine the base case. We
119   can simply look at the argument type for this. However, it turns out that
120   this is rather non-trivial to write down in Haskell in the first place. As
121   an example, we would like to write down something like this:
122  
123   \starthaskell
124     sum :: Vector n Word -> Word
125     sum xs = case null xs of
126       True -> 0
127       False -> head xs + sum (tail xs)
128   \stophaskell
129
130   However, the typechecker will now use the following reasoning (element type
131   of the vector is left out):
132
133   \startitemize
134   \item tail has the type \hs{(n > 0) => Vector n -> Vector (n - 1)}
135   \item This means that xs must have the type \hs{(n > 0) => Vector n}
136   \item This means that sum must have the type \hs{(n > 0) => Vector n -> a}
137   \item sum is called with the result of tail as an argument, which has the
138   type \hs{Vector n} (since \hs{(n > 0) => n - 1 == m}).
139   \item This means that sum must have the type \hs{Vector n -> a}
140   \item This is a contradiction between the type deduced from the body of sum
141   (the input vector must be non-empty) and the use of sum (the input vector
142   could have any length).
143   \stopitemize
144
145   As you can see, using a simple case at value level causes the type checker
146   to always typecheck both alternatives, which can't be done! This is a
147   fundamental problem, that would seem perfectly suited for a type class.
148   Considering that we need to switch between to implementations of the sum
149   function, based on the type of the argument, this sounds like the perfect
150   problem to solve with a type class. However, this approach has its own
151   problems (not the least of them that you need to define a new typeclass for
152   every recursive function you want to define).
153
154   Another approach tried involved using GADTs to be able to do pattern
155   matching on empty / non empty lists. While this worked partially, it also
156   created problems with more complex expressions.
157
158   TODO: How much detail should there be here? I can probably refer to
159   Christiaan instead.
160
161   Evaluating all possible (and non-possible) ways to add recursion to our
162   descriptions, it seems better to leave out list recursion alltogether. This
163   allows us to focus on other interesting areas instead. By including
164   (builtin) support for a number of higher order functions like map and fold,
165   we can still express most of the things we would use list recursion for.
166  
167   \subsection{General recursion}
168   Of course there are other forms of recursion, that do not depend on the
169   length (and thus type) of a list. For example, simple recursion using a
170   counter could be expressed, but only translated to hardware for a fixed
171   number of iterations. Also, this would require extensive support for compile
172   time simplification (constant propagation) and compile time evaluation
173   (evaluation constant comparisons), to ensure non-termination. Even then, it
174   is hard to really guarantee termination, since the user (or GHC desugarer)
175   might use some obscure notation that results in a corner case of the
176   simplifier that is not caught and thus non-termination.
177
178   Due to these complications, we leave other forms of recursion as
179   future work as well.