are convenient for describing hardware and can contain special
constructs that allows our hardware descriptions to be more powerful or
concise.
are convenient for describing hardware and can contain special
constructs that allows our hardware descriptions to be more powerful or
concise.
}
Considering that we required a prototype which should be working quickly,
and that implementing parsers, semantic checkers and especially
}
Considering that we required a prototype which should be working quickly,
and that implementing parsers, semantic checkers and especially
- typcheckers is not exactly the Core of this research (but it is lots and
- lots of work!), using an existing language is the obvious choice. This
+ type-checkers is not exactly the core of this research (but it is lots and
+ lots of work, using an existing language is the obvious choice. This
also has the advantage that a large set of language features is available
to experiment with and it is easy to find which features apply well and
also has the advantage that a large set of language features is available
to experiment with and it is easy to find which features apply well and
that simulation of the code becomes trivial. Since there are existing
compilers and interpreters that can run the hardware description directly,
it can be simulated without also having to write an interpreter for the
that simulation of the code becomes trivial. Since there are existing
compilers and interpreters that can run the hardware description directly,
it can be simulated without also having to write an interpreter for the
and Verilog are on the higher level, while we will be using \small{VHDL}
mainly to write low level, netlist-like descriptions anyway.
and Verilog are on the higher level, while we will be using \small{VHDL}
mainly to write low level, netlist-like descriptions anyway.
- An added advantage of using VHDL is that we can profit from existing
- optimizations in VHDL synthesizers. A lot of optimizations are done on the
- VHDL level by existing tools. These tools have been under
+ An added advantage of using \VHDL\ is that we can profit from existing
+ optimizations in \VHDL\ synthesizers. A lot of optimizations are done on the
+ \VHDL\ level by existing tools. These tools have been under
development for years, so it would not be reasonable to assume we
could achieve a similar amount of optimization in our prototype (nor
should it be a goal, considering this is just a prototype).
development for years, so it would not be reasonable to assume we
could achieve a similar amount of optimization in our prototype (nor
should it be a goal, considering this is just a prototype).
Note that we will be using \small{VHDL} as our output language, but will
not use its full expressive power. Our output will be limited to using
Note that we will be using \small{VHDL} as our output language, but will
not use its full expressive power. Our output will be limited to using
descriptions like arbitrary sequential statements (which might not
be supported by all tools). This ensures that any tool that works
descriptions like arbitrary sequential statements (which might not
be supported by all tools). This ensures that any tool that works
- with \VHDL\ will understand our output (most tools do not support
- synthesis of more complex \VHDL). This also leaves open the option
- to switch to \small{EDIF} in the future, with minimal changes to the
- prototype.
+ with \VHDL\ will understand our output. This also leaves open the
+ option to switch to \small{EDIF} in the future, with minimal changes
+ to the prototype.
\section{Simulation and synthesis}
As mentioned above, by using the Haskell language, we get simulation of
\section{Simulation and synthesis}
As mentioned above, by using the Haskell language, we get simulation of
\section[sec:prototype:design]{Prototype design}
As suggested above, we will use the Glasgow Haskell Compiler (\small{GHC}) to
implement our prototype compiler. To understand the design of the
\section[sec:prototype:design]{Prototype design}
As suggested above, we will use the Glasgow Haskell Compiler (\small{GHC}) to
implement our prototype compiler. To understand the design of the
- compiler, we will first dive into the \small{GHC} compiler a bit. Its
- compilation consists of the following steps (slightly simplified):
+ prototype, we will first dive into the \small{GHC} compiler a bit. Its
+ compilatprototype consists of the following steps (slightly simplified):
complete Haskell language and is thus a very complex one (in contrast
with the Core \small{AST}, later on). All identifiers in this
\small{AST} are resolved by the renamer and all types are checked by the
complete Haskell language and is thus a very complex one (in contrast
with the Core \small{AST}, later on). All identifiers in this
\small{AST} are resolved by the renamer and all types are checked by the
\emph{Core} language. Core is a very small functional language with lazy
semantics, that can still express everything Haskell can express. Its
simpleness makes Core very suitable for further simplification and
\emph{Core} language. Core is a very small functional language with lazy
semantics, that can still express everything Haskell can express. Its
simpleness makes Core very suitable for further simplification and
\stopdesc
\startdesc{Simplification}
Through a number of simplification steps (such as inlining, common
\stopdesc
\startdesc{Simplification}
Through a number of simplification steps (such as inlining, common
it faster or easier to process further.
\stopdesc
\startdesc{Backend}
This step takes the simplified Core program and generates an actual
runnable program for it. This is a big and complicated step we will not
it faster or easier to process further.
\stopdesc
\startdesc{Backend}
This step takes the simplified Core program and generates an actual
runnable program for it. This is a big and complicated step we will not
- Assuming that we do not want to deal with (or modify) parsing, typechecking
- and other frontend business and that native code is not really a useful
+ Assuming that we do not want to deal with (or modify) parsing, type-checking
+ and other front end business and that native code is not really a useful
format anymore, we are left with the choice between the full Haskell
\small{AST}, or the smaller (simplified) Core representation.
The advantage of taking the full \small{AST} is that the exact structure
of the source program is preserved. We can see exactly what the hardware
description looks like and which syntax constructs were used. However,
format anymore, we are left with the choice between the full Haskell
\small{AST}, or the smaller (simplified) Core representation.
The advantage of taking the full \small{AST} is that the exact structure
of the source program is preserved. We can see exactly what the hardware
description looks like and which syntax constructs were used. However,
(a Core expression only uses 9 constructors). Note that this does not mean
that the Core representation itself is smaller, on the contrary.
Since the Core language has less constructs, most Core expressions
(a Core expression only uses 9 constructors). Note that this does not mean
that the Core representation itself is smaller, on the contrary.
Since the Core language has less constructs, most Core expressions
\startuseMPgraphic{clash-pipeline}
% Create objects
save inp, front, norm, vhdl, out;
newEmptyBox.inp(0,0);
\startuseMPgraphic{clash-pipeline}
% Create objects
save inp, front, norm, vhdl, out;
newEmptyBox.inp(0,0);
binder name should of course be bound in a containing scope
(including top level scope, so a reference to a top level function
is also a variable reference). Additionally, constructors from
binder name should of course be bound in a containing scope
(including top level scope, so a reference to a top level function
is also a variable reference). Additionally, constructors from
In our examples, binders will commonly consist of a single
characters, but they can have any length.
In our examples, binders will commonly consist of a single
characters, but they can have any length.
Each binder also carries around its type (explicitly shown above), but
this is usually not shown in the Core expressions. Only when the type is
relevant (when a new binder is introduced, for example) will it be
Each binder also carries around its type (explicitly shown above), but
this is usually not shown in the Core expressions. Only when the type is
relevant (when a new binder is introduced, for example) will it be
\quote{primitive}, unboxed versions, like \lam{Char\#} and \lam{Word\#}, not the
normal Haskell versions (but there are built-in conversion
functions). Without going into detail about these types, note that
a few conversion functions exist to convert these to the normal
\quote{primitive}, unboxed versions, like \lam{Char\#} and \lam{Word\#}, not the
normal Haskell versions (but there are built-in conversion
functions). Without going into detail about these types, note that
a few conversion functions exist to convert these to the normal
for normal function \quote{calls}, but also for applying type
abstractions and data constructors.
for normal function \quote{calls}, but also for applying type
abstractions and data constructors.
In Core, there is no distinction between an operator and a
function. This means that, for example the addition of two numbers
looks like the following in Core:
In Core, there is no distinction between an operator and a
function. This means that, for example the addition of two numbers
looks like the following in Core:
\stoplambda
A let expression allows you to bind a binder to some value, while
evaluating to some other value (for which that binder is in scope). This
\stoplambda
A let expression allows you to bind a binder to some value, while
evaluating to some other value (for which that binder is in scope). This
explicit \quote{naming} of arbitrary expressions. A binder is not
in scope in the value bound it is bound to, so it is not possible
to make recursive definitions with a non-recursive let expression
explicit \quote{naming} of arbitrary expressions. A binder is not
in scope in the value bound it is bound to, so it is not possible
to make recursive definitions with a non-recursive let expression
Without going into detail about the differences with head
normal form and normal form, note that evaluating the scrutinee
Without going into detail about the differences with head
normal form and normal form, note that evaluating the scrutinee
A case expression is the only way in Core to choose between values. All
\hs{if} expressions and pattern matchings from the original Haskell
A case expression is the only way in Core to choose between values. All
\hs{if} expressions and pattern matchings from the original Haskell
A case expression evaluates its scrutinee, which should have an
algebraic datatype, into weak head normal form (\small{WHNF}) and
A case expression evaluates its scrutinee, which should have an
algebraic datatype, into weak head normal form (\small{WHNF}) and
This is best illustrated with an example. Assume
there is an algebraic datatype declared as follows\footnote{This
This is best illustrated with an example. Assume
there is an algebraic datatype declared as follows\footnote{This
To support strictness, the scrutinee is always evaluated into
\small{WHNF}, even when there is only a \lam{DEFAULT} alternative. This
To support strictness, the scrutinee is always evaluated into
\small{WHNF}, even when there is only a \lam{DEFAULT} alternative. This
in Haskell. Only the constructor of an expression can be matched,
complex patterns are implemented using multiple nested case expressions.
in Haskell. Only the constructor of an expression can be matched,
complex patterns are implemented using multiple nested case expressions.
when there is only a single constructor. For examples, to add the elements
of a tuple, the following Core is generated:
when there is only a single constructor. For examples, to add the elements
of a tuple, the following Core is generated:
different types (so a cast is needed) with the same representation (but
no work is done by the cast).
different types (so a cast is needed) with the same representation (but
no work is done by the cast).
- More complex are types that are proven to be equal by the typechecker,
- but look different at first glance. To ensure that, once the typechecker
+ More complex are types that are proven to be equal by the type-checker,
+ but look different at first glance. To ensure that, once the type-checker
has proven equality, this information sticks around, explicit casts are
added. In our notation we only write the target type, but in reality a
cast expressions carries around a \emph{coercion}, which can be seen as a
has proven equality, this information sticks around, explicit casts are
added. In our notation we only write the target type, but in reality a
cast expressions carries around a \emph{coercion}, which can be seen as a
The type of \lam{fst} has two universally quantified type variables. When
\lam{fst} is applied in \lam{fstint}, it is first applied to two types.
The type of \lam{fst} has two universally quantified type variables. When
\lam{fst} is applied in \lam{fstint}, it is first applied to two types.
the actual type of arguments and result of \lam{fst} can be found:
\lam{fst @Int @Int :: (Int, Int) -> Int}).
\stopdesc
the actual type of arguments and result of \lam{fst} can be found:
\lam{fst @Int @Int :: (Int, Int) -> Int}).
\stopdesc
\startframedtext[width=8cm,background=box,frame=no]
\startalignment[center]
{\tfa The \hs{id} function}
\startframedtext[width=8cm,background=box,frame=no]
\startalignment[center]
{\tfa The \hs{id} function}
When using a value with a forall type, the actual type
used must be applied first. For example Haskell expression \hs{id
When using a value with a forall type, the actual type
used must be applied first. For example Haskell expression \hs{id
A predicate type introduces a constraint on a type variable introduced
by a forall type (or type lambda). In the example above, the type
variable \lam{t} can only contain types that are an \emph{instance} of
A predicate type introduces a constraint on a type variable introduced
by a forall type (or type lambda). In the example above, the type
variable \lam{t} can only contain types that are an \emph{instance} of
There are other sorts of predicate types, used for the type families
extension, which we will not discuss here.
There are other sorts of predicate types, used for the type families
extension, which we will not discuss here.
Haskell provides type synonyms as a way to declare a new type that is
equal to an existing type (or rather, a new name for an existing type).
This allows both the original type and the synonym to be used
Haskell provides type synonyms as a way to declare a new type that is
equal to an existing type (or rather, a new name for an existing type).
This allows both the original type and the synonym to be used
% section headings.
\subsection{Type renaming (\type{newtype})}
Haskell also supports type renamings as a way to declare a new type that
% section headings.
\subsection{Type renaming (\type{newtype})}
Haskell also supports type renamings as a way to declare a new type that
- has the same (runtime) representation as an existing type (but is in
- fact a different type to the typechecker). With type renaming,
+ has the same (run-time) representation as an existing type (but is in
+ fact a different type to the type-checker). With type renaming,
We cannot leave all these \hs{State} type constructors out, since that
would change the type (unlike when using type synonyms). However, when
We cannot leave all these \hs{State} type constructors out, since that
would change the type (unlike when using type synonyms). However, when
\in{section}[sec:prototype:substatesynonyms] below), this
disadvantage should be limited.
\in{section}[sec:prototype:substatesynonyms] below), this
disadvantage should be limited.
-- here just for clarity.
newtype StateIn s = StateIn s
newtype StateOut s = StateOut s
-- here just for clarity.
newtype StateIn s = StateIn s
newtype StateOut s = StateOut s
descriptions less error-prone (you can no longer \quote{forget} to
unpack and repack a state variable and just return it directly, which
can be a problem in the current prototype). However, it also means we
descriptions less error-prone (you can no longer \quote{forget} to
unpack and repack a state variable and just return it directly, which
can be a problem in the current prototype). However, it also means we
approach a bit cumbersome. It also makes it harder to compare input
and output state types, possible reducing the type-safety of the
descriptions.
approach a bit cumbersome. It also makes it harder to compare input
and output state types, possible reducing the type-safety of the
descriptions.
As noted above, when using nested (hierarchical) states, the state types
of the \quote{upper} functions (those that call other functions, which
call other functions, etc.) quickly become complicated. Also, when the
As noted above, when using nested (hierarchical) states, the state types
of the \quote{upper} functions (those that call other functions, which
call other functions, etc.) quickly become complicated. Also, when the
\subsubsection{Example}
As an example of the used approach, a simple averaging circuit
is shown in \in{example}[ex:AvgState]. This circuit lets the
\subsubsection{Example}
As an example of the used approach, a simple averaging circuit
is shown in \in{example}[ex:AvgState]. This circuit lets the
but keeps a count of value accumulated in its own
state.\footnote{Currently, the prototype is not able to compile
this example, since there is no built-in function for division.}
but keeps a count of value accumulated in its own
state.\footnote{Currently, the prototype is not able to compile
this example, since there is no built-in function for division.}
If the result of this unpacking does not have a state type and does
not contain state variables, there are no limitations on its
use (this is the function's own state). Otherwise if it does
If the result of this unpacking does not have a state type and does
not contain state variables, there are no limitations on its
use (this is the function's own state). Otherwise if it does
as a \emph{state-containing input variable} and the limitations
below apply. If it has a state type itself, we refer to it as an
as a \emph{state-containing input variable} and the limitations
below apply. If it has a state type itself, we refer to it as an
as well.
It may seem strange to consider a variable that still has a state
type directly after unpacking, but consider the case where a
function does not have any state of its own, but does call a single
stateful function. This means it must have a state argument that
as well.
It may seem strange to consider a variable that still has a state
type directly after unpacking, but consider the case where a
function does not have any state of its own, but does call a single
stateful function. This means it must have a state argument that
- multiple elements (like the current function's state, substates or
- more tuples containing substates). All of these can be extracted
+ multiple elements (like the current function's state, sub-states or
+ more tuples containing sub-states). All of these can be extracted
from an input variable using an extractor case (or possibly
multiple, when the input variable is nested).
from an input variable using an extractor case (or possibly
multiple, when the input variable is nested).
type but does contain state variables we refer to it as a
\emph{state-containing input variable} and this limitation keeps
applying. If the variable has a state type itself, we refer to
type but does contain state variables we refer to it as a
\emph{state-containing input variable} and this limitation keeps
applying. If the variable has a state type itself, we refer to
- An input substate variable can (only) be passed to a function.
- Additionally, every input substate variable must be used in exactly
+ An input sub-state variable can (only) be passed to a function.
+ Additionally, every input sub-state variable must be used in exactly
\emph{one} application, no more and no less.
The function result should contain exactly one state variable, which
can be extracted using (multiple) case expressions. The extracted
\emph{one} application, no more and no less.
The function result should contain exactly one state variable, which
can be extracted using (multiple) case expressions. The extracted
- The type of this output substate must be identical to the type of
- the input substate passed to the function.
+ The type of this output sub-state must be identical to the type of
+ the input sub-state passed to the function.
variables have been joined together, the resulting
state-containing output variable can be packed into an output
state variable. Packing is done by casting to a state type.
variables have been joined together, the resulting
state-containing output variable can be packed into an output
state variable. Packing is done by casting to a state type.
- itemization. Whenever substates are extracted from the input state
- to be passed to functions, the corresponding output substates
+ itemization. Whenever sub-states are extracted from the input state
+ to be passed to functions, the corresponding output sub-states
\in{section}[sec:normalization:stateproblems].
This approach seems simple enough, but will this also work for more
\in{section}[sec:normalization:stateproblems].
This approach seems simple enough, but will this also work for more
- complex stateful functions involving substates? Observe that any
- component of a function's state that is a substate, \ie\ passed on as
+ complex stateful functions involving sub-states? Observe that any
+ component of a function's state that is a sub-state, \ie\ passed on as
the state of another function, should have no influence on the
hardware generated for the calling function. Any state-specific
\small{VHDL} for this component can be generated entirely within the
the state of another function, should have no influence on the
hardware generated for the calling function. Any state-specific
\small{VHDL} for this component can be generated entirely within the
state components which are actual states of the current function.
While doing this would not remove any information needed to
generate \small{VHDL} from the function, it would cause the
function definition to become invalid (since we will not have any
state components which are actual states of the current function.
While doing this would not remove any information needed to
generate \small{VHDL} from the function, it would cause the
function definition to become invalid (since we will not have any
syntactic problems by passing \type{undefined} for state
variables, but that would still break the code on the semantic
level (\ie, the function would no longer be semantically
syntactic problems by passing \type{undefined} for state
variables, but that would still break the code on the semantic
level (\ie, the function would no longer be semantically
To keep the function definition correct until the very end of the
process, we will not deal with (sub)states until we get to the
\small{VHDL} generation. Then, we are translating from Core to
To keep the function definition correct until the very end of the
process, we will not deal with (sub)states until we get to the
\small{VHDL} generation. Then, we are translating from Core to
argument or return value that represents state must be of the
\type{State} type, we can look at the type of a value. However, we
argument or return value that represents state must be of the
\type{State} type, we can look at the type of a value. However, we
function's own state.
For \in{example}[ex:AvgStateNormal] above, we should generate a register
with its output connected to \lam{s} and its input connected
to \lam{s'}. However, \lam{s'} is build up from both \lam{accs'} and
\lam{count'}, while only \lam{count'} should end up in the register.
function's own state.
For \in{example}[ex:AvgStateNormal] above, we should generate a register
with its output connected to \lam{s} and its input connected
to \lam{s'}. However, \lam{s'} is build up from both \lam{accs'} and
\lam{count'}, while only \lam{count'} should end up in the register.
property that we can easily check: it has a \lam{State} type. This
means that whenever \VHDL\ is generated for a tuple (or other
algebraic type), we can simply leave out all elements that have a
property that we can easily check: it has a \lam{State} type. This
means that whenever \VHDL\ is generated for a tuple (or other
algebraic type), we can simply leave out all elements that have a
new state.
\item Any values of a State type should not be translated to
\small{VHDL}. In particular, State elements should be removed from
new state.
\item Any values of a State type should not be translated to
\small{VHDL}. In particular, State elements should be removed from
not generate ports.
\item To make the state actually work, a simple \small{VHDL}
(sequential) process should be generated. This process updates
not generate ports.
\item To make the state actually work, a simple \small{VHDL}
(sequential) process should be generated. This process updates
When we actually leave out the crossed out parts, we get a slightly
weird program: there is a variable \lam{s} which has no value, and there
When we actually leave out the crossed out parts, we get a slightly
weird program: there is a variable \lam{s} which has no value, and there
a custom \GHC\ build, however.
The latest version and all history of the Cλash code can be browsed
a custom \GHC\ build, however.
The latest version and all history of the Cλash code can be browsed