be converted to an actual netlist format by an (optimizing) \VHDL\ synthesis
tool.
-Besides trivial circuits such as variants of both the FIR filter and the
-simple CPU shown in \Cref{sec:usecases}, the \CLaSH\ compiler has also been
-shown to work for non-trivial descriptions. \CLaSH\ has been able to
+Besides trivial circuits such as variants of both the \acro{FIR} filter and
+the simple \acro{CPU} shown in \Cref{sec:usecases}, the \CLaSH\ compiler has
+also been shown to work for non-trivial descriptions. \CLaSH\ has been able to
successfully translate the functional description of a streaming reduction
circuit~\cite{reductioncircuit} for floating point numbers.
\item function applications are translated to component instantiations.
\end{inparaenum}
The output port can have a structured type (such as a tuple), so having
- just a single output port does not pose any limitation. The actual arguments of a
- function application are assigned to signals, which are then mapped to
- the corresponding input ports of the component. The output port of the
- function is also mapped to a signal, which is used as the result of the
- application itself.
+ just a single output port does not pose any limitation. The actual
+ arguments of a function application are assigned to signals, which are
+ then mapped to the corresponding input ports of the component. The output
+ port of the function is also mapped to a signal, which is used as the
+ result of the application itself.
Since every top level function generates its own component, the
hierarchy of function calls is reflected in the final netlist,% aswell,
\end{figure}
\subsection{Choice}
- In Haskell, choice can be achieved by a large set of syntacic elements,
+ In Haskell, choice can be achieved by a large set of syntactic elements,
consisting of: \hs{case} expressions, \hs{if-then-else} expressions,
pattern matching, and guards. The most general of these are the \hs{case}
expressions (\hs{if} expressions can be very directly translated to
compiler. The \CLaSH\ compiler has generic translation rules to
translated the user-defined types described below.
- The \CLaSH compiler is able to infer unspecified types,
+ The \CLaSH\ compiler is able to infer unspecified types,
meaning that a developer does not have to annotate every function with a
- type signature (though it is good practice to do so anyway).
+ type signature (even if it is good practice to do so).
% Translation of two most basic functional concepts has been
% discussed: function application and choice. Before looking further
the rest of paper is: \hs{[a|n]}. Where the \hs{a} is the element
type, and \hs{n} is the length of the vector. Note that this is
a notation used in this paper only, vectors are slightly more
- elaborate in real \CLaSH programs.
+ elaborate in real \CLaSH\ programs.
% The state type of an 8 element register bank would then for example
% be:
Algebraic datatypes with multiple constructors, but without any
fields are essentially a way to get an enumeration-like type
containing alternatives. Note that Haskell's \hs{Bool} type is also
- defined as an enumeration type, but that there is a fixed translation for
- that type within the \CLaSH\ compiler. An example of such an
+ defined as an enumeration type, but that there is a fixed translation
+ for that type within the \CLaSH\ compiler. An example of such an
enumeration type is the type that represents the colors in a traffic
light:
\begin{code}
As an example of a parametric polymorphic function, consider the type of
the following \hs{append} function, which appends an element to a
vector:\footnote{The \hs{::} operator is used to annotate a function
- with its type in \CLaSH}
+ with its type.}
\begin{code}
append :: [a|n] -> a -> [a|n + 1]
The \hs{map} function is called a higher-order function, since it takes
another function as an argument. Also note that \hs{map} is again a
parametric polymorphic function: it does not pose any constraints on the
- type of the input vector, other than that its elements must have the same type as
- the first argument of the function passed to \hs{map}. The element type of the
- resulting vector is equal to the return type of the function passed, which
- need not necessarily be the same as the element type of the input vector.
- All of these characteristics can readily be inferred from the type
- signature belonging to \hs{map}:
+ type of the input vector, other than that its elements must have the same
+ type as the first argument of the function passed to \hs{map}. The element
+ type of the resulting vector is equal to the return type of the function
+ passed, which need not necessarily be the same as the element type of the
+ input vector. All of these characteristics can readily be inferred from
+ the type signature belonging to \hs{map}:
\begin{code}
map :: (a -> b) -> [a|n] -> [b|n]
expression, that adds one to every element of a vector:
\begin{code}
- map ((+) 1) xs
+ map (+ 1) xs
\end{code}
- Here, the expression \hs{(+) 1} is the partial application of the
+ Here, the expression \hs{(+ 1)} is the partial application of the
plus operator to the value \hs{1}, which is again a function that
- adds one to its (next) argument. A lambda expression allows one to introduce an
- anonymous function in any expression. Consider the following expression,
- which again adds one to every element of a vector:
+ adds one to its (next) argument. A lambda expression allows one to
+ introduce an anonymous function in any expression. Consider the following
+ expression, which again adds one to every element of a vector:
\begin{code}
map (\x -> x + 1) xs
% This purity property is important for functional languages, since it
% enables all kinds of mathematical reasoning that could not be guaranteed
% correct for impure functions.
- Pure functions are as such a perfect match for combinaionial circuits,
+ Pure functions are as such a perfect match for combinational circuits,
where the output solely depends on the inputs. When a circuit has state
however, it can no longer be simply described by a pure function.
% Simply removing the purity property is not a valid option, as the
In \CLaSH\ we deal with the concept of state in pure functions by making
current value of the state an additional argument of the function and the
updated state part of result. In this sense the descriptions made in
- \CLaSH\ are the combinaionial parts of a mealy machine.
+ \CLaSH\ are the combinational parts of a mealy machine.
A simple example is adding an accumulator register to the earlier
multiply-accumulate circuit, of which the resulting netlist can be seen in
first input value, \hs{i}. The result is the first output value, \hs{o},
and the updated state \hs{s'}. The next iteration of the \hs{run} function
is then called with the updated state, \hs{s'}, and the rest of the
- inputs, \hs{inps}. Each value in the input list corresponds to exactly one
- cycle of the (implicit) clock.
+ inputs, \hs{inps}. It is assumed that there is one input per clock cycle.
+ Also note how the order of the input, output, and state in the \hs{run}
+ function corresponds with the order of the input, output and state of the
+ \hs{macS} function described earlier.
As both the \hs{run} function, the hardware description, and the test
inputs are plain Haskell, the complete simulation can be compiled to an
simulation, where the executable binary has an additional simulation speed
bonus in case there is a large set of test inputs.
-\section{\CLaSH\ prototype}
-
-The \CLaSH\ language as presented above can be translated to \VHDL\ using
-the prototype \CLaSH\ compiler. This compiler allows experimentation with
-the \CLaSH\ language and allows for running \CLaSH\ designs on actual FPGA
-hardware.
+\section{\CLaSH\ compiler}
+An important aspect in this research is the creation of the prototype
+compiler, which allows us to translate descriptions made in the \CLaSH\
+language as described in the previous section to synthesizable \VHDL, allowing
+a designer to actually run a \CLaSH\ design on an \acro{FPGA}.
+
+The Glasgow Haskell Compiler (\GHC) is an open-source Haskell compiler that
+also provides a high level API to most of its internals. The availability of
+this high-level API obviated the need to design many of the tedious parts of
+the prototype compiler, such as the parser, semantic checker, and especially
+the type-checker. The parser, semantic checker, and type-checker together form
+the front-end of the prototype compiler pipeline, as depicted in
+\Cref{img:compilerpipeline}.
\begin{figure}
\centerline{\includegraphics{compilerpipeline.svg}}
\label{img:compilerpipeline}
\end{figure}
-The prototype heavily uses \GHC, the Glasgow Haskell Compiler.
-\Cref{img:compilerpipeline} shows the \CLaSH\ compiler pipeline. As you can
-see, the front-end is completely reused from \GHC, which allows the \CLaSH\
-prototype to support most of the Haskell Language. The \GHC\ front-end
-produces the program in the \emph{Core} format, which is a very small,
-typed, functional language which is relatively easy to process.
-
-The second step in the compilation process is \emph{normalization}. This
-step runs a number of \emph{meaning preserving} transformations on the
-Core program, to bring it into a \emph{normal form}. This normal form
-has a number of restrictions that make the program similar to hardware.
-In particular, a program in normal form no longer has any polymorphism
-or higher order functions.
-
-The final step is a simple translation to \VHDL.
+The output of the \GHC\ front-end is the original Haskell description
+translated to \emph{Core}~\cite{Sulzmann2007}, which is smaller, typed,
+functional language that is relatively easier to process than the larger
+Haskell language. A description in \emph{Core} can still contain properties
+which have no direct translation to hardware, such as polymorphic types and
+function-valued arguments. Such a description needs to be transformed to a
+\emph{normal form}, which only contains properties that have a direct
+translation. The second stage of the compiler, the \emph{normalization} phase,
+exhaustively applies a set of \emph{meaning-preserving} transformations on the
+\emph{Core} description until this description is in a \emph{normal form}.
+This set of transformations includes transformations typically found in
+reduction systems for lambda calculus~\cite{lambdacalculus}, such a
+$\beta$-reduction and $\eta$-expansion, but also includes self-defined
+transformations that are responsible for the reduction of higher-order
+functions to `regular' first-order functions.
+
+The final step in the compiler pipeline is the translation to a \VHDL\
+\emph{netlist}, which is a straightforward process due to resemblance of a
+normalized description and a set of concurrent signal assignments. We call the
+end-product of the \CLaSH\ compiler a \VHDL\ \emph{netlist} as the resulting
+\VHDL\ resembles an actual netlist description and not idiomatic \VHDL.
\section{Use cases}
\label{sec:usecases}
+\subsection{FIR Filter}
As an example of a common hardware design where the use of higher-order
-functions leads to a very natural description is a FIR filter, which is
+functions leads to a very natural description is a \acro{FIR} filter, which is
basically the dot-product of two vectors:
\begin{equation}
y_t = \sum\nolimits_{i = 0}^{n - 1} {x_{t - i} \cdot h_i }
\end{equation}
-A FIR filter multiplies fixed constants ($h$) with the current
+A \acro{FIR} filter multiplies fixed constants ($h$) with the current
and a few previous input samples ($x$). Each of these multiplications
-are summed, to produce the result at time $t$. The equation of a FIR
+are summed, to produce the result at time $t$. The equation of a \acro{FIR}
filter is indeed equivalent to the equation of the dot-product, which is
shown below:
The \hs{zipWith} function is very similar to the \hs{map} function seen
earlier: It takes a function, two vectors, and then applies the function to
each of the elements in the two vectors pairwise (\emph{e.g.}, \hs{zipWith (*)
-[1, 2] [3, 4]} becomes \hs{[1 * 3, 2 * 4]} $\equiv$ \hs{[3,8]}).
+[1, 2] [3, 4]} becomes \hs{[1 * 3, 2 * 4]}).
-The \hs{foldl1} function takes a function, a single vector, and applies
+The \hs{foldl1} function takes a binary function, a single vector, and applies
the function to the first two elements of the vector. It then applies the
-function to the result of the first application and the next element from
-the vector. This continues until the end of the vector is reached. The
-result of the \hs{foldl1} function is the result of the last application.
-As you can see, the \hs{zipWith (*)} function is pairwise
-multiplication and the \hs{foldl1 (+)} function is summation.
-
-Returning to the actual FIR filter, we will slightly change the
-equation belong to it, so as to make the translation to code more obvious.
-What we will do is change the definition of the vector of input samples.
-So, instead of having the input sample received at time
-$t$ stored in $x_t$, $x_0$ now always stores the current sample, and $x_i$
-stores the $ith$ previous sample. This changes the equation to the
-following (Note that this is completely equivalent to the original
-equation, just with a different definition of $x$ that will better suit
-the transformation to code):
+function to the result of the first application and the next element in the
+vector. This continues until the end of the vector is reached. The result of
+the \hs{foldl1} function is the result of the last application. It is obvious
+that the \hs{zipWith (*)} function is pairwise multiplication and that the
+\hs{foldl1 (+)} function is summation.
+
+Returning to the actual \acro{FIR} filter, we will slightly change the
+equation describing it, so as to make the translation to code more obvious and
+concise. What we do is change the definition of the vector of input samples
+and delay the computation by one sample. Instead of having the input sample
+received at time $t$ stored in $x_t$, $x_0$ now always stores the newest
+sample, and $x_i$ stores the $ith$ previous sample. This changes the equation
+to the following (note that this is completely equivalent to the original
+equation, just with a different definition of $x$ that will better suit the
+transformation to code):
\begin{equation}
y_t = \sum\nolimits_{i = 0}^{n - 1} {x_i \cdot h_i }
\end{equation}
-Consider that the vector \hs{hs} contains the FIR coefficients and the
-vector \hs{xs} contains the current input sample in front and older
-samples behind. The function that shifts the input samples is shown below:
+The complete definition of the \acro{FIR} filter in code then becomes:
\begin{code}
-x >> xs = x +> tail xs
+fir (State (xs,hs)) x = (State (x >> xs,hs), xs *+* hs)
\end{code}
-Where the \hs{tail} function returns all but the first element of a
-vector, and the concatenate operator ($\succ$) adds a new element to the
-front of a vector. The complete definition of the FIR filter then becomes:
+Where the vector \hs{hs} contains the \acro{FIR} coefficients and the vector
+\hs{xs} contains the previous input sample in front and older samples behind.
+The code for the shift (\hs{>>}) operator, that adds the new input sample
+(\hs{x}) to the list of previous input samples (\hs{xs}) and removes the
+oldest sample, is shown below:
\begin{code}
-fir (State (xs,hs)) x = (State (x >> xs,hs), xs *+* hs)
+x >> xs = x +> init xs
\end{code}
-The resulting netlist of a 4-taps FIR filter based on the above definition
-is depicted in \Cref{img:4tapfir}.
+The \hs{init} function returns all but the last element of a vector, and the
+concatenate operator (\hs{+>}) adds a new element to the front of a vector.
+The resulting netlist of a 4-taps \acro{FIR} filter, created by specializing
+the vectors of the \acro{FIR} code to a length of 4, is depicted in
+\Cref{img:4tapfir}.
\begin{figure}
\centerline{\includegraphics{4tapfir.svg}}
\label{img:4tapfir}
\end{figure}
-
\subsection{Higher order CPU}
-
\begin{code}
-type FuState = State Word
-fu :: (a -> a -> a)
- -> [a]:n
- -> (RangedWord n, RangedWord n)
- -> FuState
- -> (FuState, a)
fu op inputs (addr1, addr2) (State out) =
(State out', out)
where
\end{code}
\begin{code}
-type CpuState = State [FuState]:4
-cpu :: Word
- -> [(RangedWord 7, RangedWord 7)]:4
- -> CpuState
- -> (CpuState, Word)
-cpu input addrs (State fuss) =
- (State fuss', out)
+cpu :: Word -> [(Index 6, Index 6) | 4]
+ -> State [Word | 4] -> (State [Word | 4], Word)
+cpu input addrs (State fuss) = (State fuss', out)
where
- fures = [ fu const inputs!0 fuss!0
- , fu (+) inputs!1 fuss!1
- , fu (-) inputs!2 fuss!2
- , fu (*) inputs!3 fuss!3
- ]
- (fuss', outputs) = unzip fures
- inputs = 0 +> 1 +> input +> outputs
- out = head outputs
+ fures = [ fu const inputs (addrs!0) (fuss!0)
+ , fu (+) inputs (addrs!1) (fuss!1)
+ , fu (-) inputs (addrs!2) (fuss!2)
+ , fu (*) inputs (addrs!3) (fuss!3)
+ ]
+ (fuss', outputs) = unzip fures
+ inputs = 0 +> (1 +> (input +> outputs))
+ out = head outputs
\end{code}
\section{Related work}
+This section describes the features of existing (functional) hardware
+description languages and highlights the advantages that this research has
+over existing work.
+
Many functional hardware description languages have been developed over the
years. Early work includes such languages as $\mu$\acro{FP}~\cite{muFP}, an
extension of Backus' \acro{FP} language to synchronous streams, designed
functional language \acro{ML}, and has support for polymorphic types and
higher-order functions. Published work suggests that there is no direct
simulation support for \acro{HML}, but that a description in \acro{HML} has to
-be translated to \VHDL\ and that the translated description can than be
+be translated to \VHDL\ and that the translated description can then be
simulated in a \VHDL\ simulator. Also not all of the mentioned language
features of \acro{HML} could be translated to hardware. The \CLaSH\ compiler
on the other hand can correctly translate all of the language constructs
The merits of polymorphic typing, combined with higher-order functions, are
now also recognized in the `main-stream' hardware description languages,
-exemplified by the new \VHDL-2008 standard~\cite{VHDL2008}. \VHDL-2008 support for generics has been extended to types, allowing a developer to describe
+exemplified by the new \VHDL-2008 standard~\cite{VHDL2008}. \VHDL-2008 support
+for generics has been extended to types, allowing a developer to describe
polymorphic components. Note that those types still require an explicit
-generic map, whereas types can be automatically inferred in \CLaSH.
+generic map, whereas types can be automatically inferred in \CLaSH. There are
+also no (generally available) \VHDL\ synthesis tools that currently support
+the \VHDL-2008 standard, and thus the synthesis of polymorphic types.
% Wired~\cite{Wired},, T-Ruby~\cite{T-Ruby}, Hydra~\cite{Hydra}.
%
% use section* for acknowledgement
-\section*{Acknowledgment}
-
-
-The authors would like to thank...
-
-
-
-
+% \section*{Acknowledgment}
+%
+% The authors would like to thank...
% trigger a \newpage just before the given reference
% number - used to balance the columns on the last page