Designing FPGA Circuits in Lava

Designing FPGA Circuits in Lava
1
Satnam Singh
Department of Computing Science
University of Glasgow
Glasgow, UK
Mary Sheeran
Department of Computer Science
Chalmers University of Technology
Gothenberg, Sweden
[email protected]
[email protected]
Introduction
This paper explores the potential of the lazy functional programming language Haskell for the specification, synthesis and
verification of digital circuits. Circuits are described using combinators that combine behaviour (allowing circuit simulation, symbolic evaluation and partial evaluation) and layout (allowing the generation of output suitable for entry into VLSI
CAD tools). We call the software that provides this functionality Lava. Note that Lava is not a new language: it is simply
a collection of Haskell modules. Circuits described in Lava can be transformed into a format that can be using with the commercial propositional logic theorem prover NP-Tools for hardware verification.
In recent years many more circuit design projects are undertaken using hardware description languages like VHDL (mandated for US military projects and based on Ada) or in Verilog (a popular rival to VHDL with C inspired syntax). Describing
hardware symbolically rather than pictorially affords many advantages, including the opportunity to use programming language constructs like repetition, hierarchy and data abstraction to describe circuits in a high level manner and to capture
common patterns of computation. For example, it is difficult to express a generic n-bit adder composed of n full-adder cells
in a picture, but it is relatively easy to describe this using programming constructs supported in VHDL:
entity adder is
generic (n : integer) ;
port (a, b: in bit_vector (1 to n) ;
sum : out bit_vector (1 to n)) ;
end entity adder ;
architecture structural of adder is
signal c : bit_vector (0 to n) ;
begin
c(0) <= ‘0’ ;
FAs : for i in 1 to n generate
FAi : fullAdder port map
(a(i), b(i), sum(i), c(i-1), c(i))
;
end generate FAs ;
end architecture structural ;
Figure 1: Structural VHDL description of an n-bit adder
The interface of the adder component is shown on the left. This description is parameterised on the size of the adder (the
number of bits to add n). The adder circuit has two inputs a and b which are bit vectors of length n and the adder circuit has
one output sum which is the n-bit sum of a and b. The right hand side shows one implementation which assumes the availability of a full-adder circuit. This implementation creates n instances of a full-adder circuit and wires them together by
signal-name association (the normal method of circuit composition in VHDL). The fourth and fifth arguments of the fulladder cell are the carry in and carry out values, which are represented with a local signal declaration.
The VHDL language is based on the Ada programming language. But why not just use a conventional programming language like Ada for hardware description? The trend has been to develop hardware specific languages to capture the concurrent nature of hardware; to allow the specification of multiple implementations for one interface; and to focus on the
structural representation of circuits. We argue that it should be possible to use conventional programming languages for the
description of hardware since concurrency, multiple implementations and sufficiently powerful data-types are features of
many languages. In particular, the structural description of circuits corresponds to just a graph (the components are the
nodes and the wires are the vertices), and structural VHDL can be viewed as a graph description language. The standard
hardware design interchange format EDIF is just a LISP-style graph description.
The VHDL style of circuit composition is rather clumsy and it is difficult to understand how components are connected
together. Many researchers have found the notion of function composition very appropriate for describing how two circuits
are composed. By modelling each circuit as a time-varying function from inputs to outputs, one can compose circuits without mentioning intermediate signal names. One can also write higher order combinators that take circuits as arguments and
compose them in interesting ways e.g. a row of circuits all composed together. Much research work has been reported in
this area by Steve Johnson (Daisy)[3], Mary Sheeran and Geraint Jones (µFP and Ruby)[8], John O’Donnell (Hydra)[6]
and Miriam Lesser (HML)[5]. One of the early hardware description languages to attract industrial interest was ELLA
which is functional. This paper builds on this existing base of work, but shows how far we can go using just Haskell.
A major weakness of almost all hardware description languages is their inability to succinctly describe geometry (or layout)
Designing FPGA Circuits in Lava
2
as well as structure and behaviour. Even if there is a way to describe the location or orientation of a component, it
is often impossible to describe transformations on circuits such as translation and rotation. The usual method for
finding a layout for a given graph is to employ an automatic place and route system that typically uses simulated
annealing to find out where to place the nodes and then how to connected them together. This is a lengthy and very
computationally intensive task, and often the layout or routing delivered is very sub-optimal.
In the Lava system circuits can be composed using higher order combinators (functions) that take circuits as parameters and return circuits as results. The first class status of circuits in Lava permits the definition of powerful
circuit transformation functions that may compose or lay out circuits in interesting ways. In standard hardware description languages circuits cannot be passed as parameters and there is no standard way of specifying layout (and
even if there were, there is no convenient way of transforming circuit layouts).
The authors are particularly concerned with the design of circuits for realisation on Field Programmable Logic Arrays (FPGAs). FPGAs are Lego-like chips which comprise of a grid of processing cells which are nearest neighbour
as well as being connected to other cells via some hierarchical routing system as shown in Figure 2(a).
2
2
1
AND
AND
1
0
XOR
XOR
0
0
(a)
OR
1
(b)
2
3
0
AND
DFF
1
2
3
(c)
Figure 2: (a) FPGA grid (b) one configuration of six cells (c) the same cells reconfigured
An interesting feature of FPGAs is that the configuration of each cell can be rapidly changed, allowing circuits to
be modified as they run. At one instant the bottom 12 cells of the FPGA may have the configuration shown in Figure 2(b), but very rapidly these same cells can be reprogrammed to assume the circuit shown in Figure 2(c). The
ability to dynamically reconfigure circuits as they run is a powerful design technique. However current hardware
description languages are geared towards the description of static graphs that represent conventional circuits in
standard VLSI technologies that do not allow the circuit to change. We will show how the technique of partial evaluation provides a suitable framework for describing an important class of dynamically reconfigurable circuits. This
represents one of the major advantages of using a functional hardware description language like Haskell instead of
VHDL which provides no support for dynamic reconfiguration.
FPGAs have quite a small grid of cells, with only very simple processing cells (2-input gates, multiplexers and flipflops). The FPGA chip used in this paper is the Xilinx XC6216 which has a grid of 64 by 64 cells which can realise
circuits containing around 16,000 gates. To make the best use of the small number of cells available, one has to be
very careful about how circuits are placed and routed. A key advantage of the approach that we present is that we
employ combinators that specify layout and behaviour, so we can calculate the position of each cell. This allows
the designer to express a good architecture (layout) for his design that makes the best use of the FPGAs facilities
without being a hostage to the automatic place and route system over which he has very little control. Finally, it is
very difficult to transform or reason about VHDL circuit descriptions.
Lava provides functions for outputing Haskell circuits in VHDL or EDIF. Leaf cells can also be imported from
structural VHDL descriptions. This reflects one of our design goals: that Lava should not be a closed system but
instead should be used alongside real VLSI CAD software. The system can take cells designed using conventional
techniques, and then use the power of a functional language to compose these cells in interesting ways and then
deliver the result in VHDL which can be pushed back into the standard design flow for digital circuits.
Designing FPGA Circuits in Lava
2
3
A Trivial Lava Circuit
First we present an absolutely trivial circuit in the Lava system and demonstrate how Lava can be used to describe,
simulate, symbolically evaluate and translate this circuit for use with FPGA CAD tools. The remaining sections
introduce more realistic circuit designs in Lava. Many real circuits have been designed in Lava, including a reconfigurable multiplier [4], several kinds of adders (carry look-ahead, carry skip) and finite impulse filters (with multiply-accumulate units). However, to concentrate on the language support provided by Lava we shall only present
small circuits in this paper.
Circuits descriptions in Lava are overloaded using the Haskell class system. Consider one of the simplest circuits
that can be described in Lava, a NAND gate built out of an AND gate and an inverter (INV). To allow comparison
with VHDL we show a typical VHDL description of a NAND gate built out of two simpler gates:
entity nandGate is
port (signal a, b : in bit ;
c : out bit) ;
end entity nandGate ;
library xilinx ;
use xilinx.xc6200.aall ;
architecture structural of nandGate is
signal intermediate_signal : bit ;
begin
and_gate_1 : and2 port map (a, b, intermediate_signal) ;
inv_gate_1 : inv port map (intermediate_signal, c) ;
end architecture structural ;
The entity declaration declares an interface or “specification” for the NAND gate design. It is possible to give multiple implementations (called architectures) for the same entity. The architecture shown above is called “structural”
and shows how to build a NAND gate by wiring together an instance of an AND2 gate and an inverter. Notice how
signal name association is used to compose smaller circuits to form larger circuits. This VHDL description can be
simulated with any VHDL simulator that conforms to the IEEE standard for VHDL-93. It can also be automatically
translated into a programming stream for an FPGA which will configure a portion of the FPGA to realise a NAND
gate.
The Haskell text for this circuit is shown below using the Lava system:
>
>
>
>
>
>
>
module NandGate where
import Lava
nandGate_A :: Circuit state => (Bit, Bit) -> STrans state Bit
nandGate_A (a, b) = do intermediate_signal <- and2 (a, b)
result_signal <- inv intermediate_signal
return result_signal
All lines in a literate Haskell script are comments unless they start with a ‘>’ symbol. The NAND gate design resides in a module called NandGate, and this module imports definitions from the Lava module (which is itself
made up of a large number of modules). First the type of the NAND gate design is given after the double colons
“::”. The type states that the definition of the circuit is overloaded on the type ‘state’. This circuit can be executed
for any type belonging to the class Circuit. We shall specify different states for the circuit, depending on how we
wish to interpret the circuit description. Examples of states include state types suitable for simulation, symbolic
evaluation, layout generation or VHDL output. We call this technique non-standard interpretation [9]. The portion
of the type that reads Circuit state => is called the context clause for the type that follows. In this case, the
type says that this circuit takes a pair of bits as input and delivers a bit as the output of a stateful computation.
State based calculations in the pure lazy functional language Haskell are best expressed using monads which help
to sequence calculations and implicitly plumb state information. Our circuit descriptions are cast in the state transformer (STrans) monad which is overloaded with the state type. This is a rather heavyweight type scheme for describing circuits compared to the equivalent VHDL, but this power also buys us many advantages. In normal lazy
functional language expressions one can assume very little about the order in which expressions are evaluated (the
lack of side effects means that the compiler or run-time system can choose between many evaluation orders as it
sees fit). The monadic expressions that follow the do are executed in the order shown, with the result of each cal-
Designing FPGA Circuits in Lava
4
culation bound to the variable or pattern to the left hand side of the <-. The state is implicitly singly threaded
through these sequenced calculations, and calculations that only operate on the state without returning a result are
written without the <- binding. A circuit with input of type a and an output of type b has its type written as Circuit state => a -> STrans state b in Lava. We shall say a “a circuit of result type b” and implicitly
assume that the result is in the state transformer monad.
The definition of the NAND gate design names the two bits a and b by pattern matching and then a sequence of
operations follow the do command which create an AND gate and an inverter (the do syntax is used to combine
monadic calculations). First an instance of an AND gate (named and2 in the Lava system) is created and supplied
a and b as its inputs. The result of this AND gate is bound to the variable intermediate_signal. The signal is
then used as the input to an instance of an inverter whose output is bound to the signal result_signal. The result
of running the NAND gate circuit is the value of the result_signal i.e. the NAND of a and b. This style of
description has little to recommend itself over the equivalent VHDL. However, we shall see that it is possible to
transform this description to lay out the nand gate as we wish. Alternatively, we can give a more concise description
using higher order circuit combinators.
One very useful combinator is serial composition. This combinator is similar to function composition in mathematics or in functional languages. The Lava serial composition combinator is written as an infix operator with the symbols >->. The symbols indicate that the data flow is from left to right and that the composed circuit is laid out flat
with the intermediate signals connected together. The type of the serial composition combinator is:
> (>->) :: Circuit state => (a -> STrans state b) -> (b -> STrans state c) -> a ->
>
(a -> STrans state c)
This type is overloaded on the type of state (which must belong to the Circuit class). The combinator takes two
circuits and one data value as inputs and returns a circuit. The first circuit maps an input of type a to a result of type
b. The second circuit must map a circuit that takes an input of type b to a result of type c. The third argument must
be of type a. The serial composition combinator simply runs the first circuit with the value a, and then feeds the
result of this into the second circuit. The output of the composed circuit is the output of the second circuit. The
serial composition combinator may be defined as:
> (>->) circuit1 circuit2 input1
>
= do input2 <- circuit1 input1
>
result <- circuit2 input2
>
return result
The actual definition is somewhat more complicated because it also specifies the layout. This layout combinator
ensures that circuit2 is laid out to the right of circuit1. Note that one combinator is used to specify how to
compose behaviour and layout. Serial composition is an example of a higher order combinator which takes two
circuits as arguments and returns a circuit as a result. This kind of combinator cannot be expressed in a conventional
language like VHDL.
Using the serial composition combinator we can respecify the NAND gate design as:
> nandGate (a, b) = (and2 >-> inv) (a, b)
It is somewhat more pleasing to eliminate the (a, b) from both sides:
> nandGate = and2 >-> inv.
The combinator style of circuit description avoids naming signals, especially internal signals. VHDL descriptions
are often complicated by the need to name all internal signals, since signal name association is the only glue available for composing circuits. Lava provides richer kinds of glue which include signal name association as well as
combinators.
Many combinators have useful algebraic properties that allow us to rewrite our cicuits to make them smaller or
faster. A basic property of the serial composition combinator is that:
(a >-> b) >-> c
3
=
a -> (b >-> c)
Lava with Hugs
The Lava software can be compiled to produce a binary with any Haskell compiler that accepts version 1.3 of the
Designing FPGA Circuits in Lava
5
language. Alternatively, the system can be loaded up in the Hugs Haskell interpreter and the user can interactively
execute circuits. Lava provides a function simulate which takes a circuit, names for the arguments and a list of
values to be applied in sequence to the inputs of the circuit and returns the corresponding sequence of values computed at the output of the circuit. The text below shows the output from a Hugs session where the user asks for the
exhaustive simulation of the NAND gate design.
? simulate nandGate (a, b) [(L,L), (L,H), (H,L), (H,H)]
1 1 1 0
It is also possible to symbolically evaluate a circuit:
? symbolic_eval nandGate (a,b)
~(a & b)
Lava provides support for reading in structural VHDL and for generating VHDL from Lava descriptions, making
it suitable for use in the standard hardware design flow. For example, combinator based description of the NAND
gate design can be automatically translated into VHDL by using the writeCircuitAsVHDL function:
? writeCircuitAsVHDL "nandgate" c (nandGate (a, b))
This generates the following VHDL which includes layout hints about the positions of the AND gate and the inverter.
entity nandgate is
port (
signal a :
signal b :
signal c :
) ;
end entity nandgate
in bit ;
in bit ;
out bit
;
library xilinx ;
use xilinx.xc6200.all ;
architecture generated of nandgate is
signal const0 : bit ;
signal const1 : bit ;
signal v : bit_vector(0 to 1) ;
begin
gnd0 : gnd port map (const0) ;
vcc1 : vcc port map (const1) ;
block_nst_1 : block
attribute relative_location of inst_1 : label is (0, 0) ;
begin
inst_1 : and2 port map (a, b, v(0)) ;
end block block_nst_1 ;
block_nst_2 : block
attribute relative_location of inst_2 : label is (1, 0) ;
begin
inst_2 : inv port map (v(0), c) ;
end block block_nst_2 ;
end architecture generated ;
This description can be simulated or translated into a form that can be placed and routed on an FPGA. The Lava
system provides another translator that converts a Lava circuit into an EDIF description of the same circuit. EDIF
is a design interchange format used by many VLSI CAD tools, including the Xilinx tools for the XC6200 FPGAs.
Clearly, the Lava description of even a simple NAND gate is far more elegant than the corresponding VHDL. The
VHDL code has to talk about the internal nets, and the composition of the AND gate and the inverter is not obvious.
The position of each gate has to be stated explicitly. The internal signals and cell location is implicit in the Lava
description. The layout of the nandgate on a XC6216 FPGA is shown in Figure 3. This picture was produced by
feeding the generated EDIF into the Xilinx XACTstep 6000 software.
The figure shows the bottom left-hand corner of a 64 by 64 cell FPGA. The cell in the bottom left corner [at position
(0,0)] is configured to be an AND gate. The cell at position (1,0) is configured to be an inverter. A light coloured
wire shows that the output of the AND gate is connected to the input of the inverter. The blocks outwith the main
grid are I/O blocks and pads which are used to communicate signals over the chip pins.
Designing FPGA Circuits in Lava
6
Figure 3: The FPGA layout of the NAND gate
4
Binary Adders in Lava
Another module shipped with Lava contains several binary adder circuits. The binary adders in this paper are made
up of full-adder circuits laid out vertically with the least significant bit at the bottom. A horizontal adder can be
made by rotating a vertical adder. A straight forward way of making an adder is to use two half adders and a multiplexor. A half adder can be described with the following Lava code in which the relative locations of the AND
and XOR gates are explicitly specified.
> halfAdd2 :: Circuit state => (Bit, Bit) -> STrans state (Bit, Bit)
> halfAdd2 (a, b)
>
= do partCarry <- at (0,1) (and2 (a, b))
>
partSum
<- at (0,0) (xor2 (a, b))
>
return (partCarry, partSum)
The at combinator takes a displacement (a pair of integers) and a fully applied circuit (a circuit with all its inputs)
and returns the same circuit except translated by the given displacement (x displacement and y displacement respectively). The half adder circuits supplies both the AND gate and the OR gate with same inputs and returns a
result pair containing the part carry and the part sum.
A more elegant description that avoids the explicit mention of layout coordinates can be cast using a combinator
style description. This description uses the parallel composition combinator <|> which places one circuit below
another. In this case, an AND gate is placed below an XOR gate.
> halfAdd3 :: Circuit state => (Bit, Bit) -> STrans state (Bit, Bit)
> halfAdd3 (a, b) = (and2 <|> xor2) ((a, b), (a, b))
The duplicated input to this circuit is formed simply by making a pair in which both elements are (a, b). The
parallel composition combinator takes the first element of the pair and uses it as the input to the first (lower circuit).
It also takes the second element of the pair and uses it as the input to the second (upper) circuit. The result of the
parallel composition operator is a pair in which the first element is drawn from the lower circuit and the second
element is drawn from the upper circuit. The type of the parallel composition operator is:
> (<|>) :: Circuit state => (a -> STrans state b) -> (c -> STrans state d) -> (a, c) ->
>
STrans state (b, d)
An easily proved and useful property of serial and parallel composition is:
(a <|> b) >-> (c <|> d)
=
(a >-> c) <|> (b >-> d)
Wiring combinations themselves are worthy of expression as circuits. Unfortunately, in many hardware description
languages, circuits that are composed of just wires cannot be expressed (e.g. VHDL or EDIF). In Lava, wiring circuits has the same status as circuits containing components. For example, a circuit that duplicates its input to form
Designing FPGA Circuits in Lava
7
a pair (with two identical elements) is defined in the Lava prelude as:
> mdup :: Monad m => a -> m (a, a)
> mdup a = return (a, a)
Informally, this declaration takes a value of type a as input and returns a value of type pair (both elements of type
a) as its result. Using this combinator we can cast an even more elegant data-flow style description of the half
adder:
> halfAdd :: Circuit state => (Bit, Bit) -> STrans state (Bit, Bit)
> halfAdd = mdup >-> (and2 <|> xor2)
A full adder can now be build using two half adders and an OR gate. This description explicitly places the subcircuits. A combinator style description is possible, but the data-flow in this circuit is too awkward to make such a
description much more readable then the version shown below.
> big_FA :: Circuit state => ((Bit, Bit), In Bit) -> STrans state (Bit, Bit)
> big_FA ((a,b), carryIn)
>
= do (partCarry,partSum) <- at (0,0) (halfAdd (a, b))
>
(partCarry2,sum)
<- at (1,0) (halfAdd (partSum, carryIn))
>
carryOut
<- at (2,0) (or2 (partCarry, partCarry2))
>
return (carryOut,sum)
The layout produced by this full adder description is shown in Figure 1 (a). Note how the second full adder that
contained cells at (0,0) and (0,1) has been translated to locations (1,0) and (1,1) by the at combinator.
(a)
(b)
Figure 4: (a) A big full adder cell; (b) a compact full adder cell
The XC6200 FPGAs can implement a 2-to-1 multiplexor in one cell:
sum (a, b, carryin) = a ⊕ b ⊕ carryin
carryout (a, b, carryin) = if a ⊕ b then carryin else b
which affords a much more compact realisation of the full adder circuit:
> compact_FA :: Circuit state => ((Bit, Bit), In Bit) -> STrans state (Bit, Bit)
> compact_FA ((a,b), carryIn)
>
= do partSum
<- at (0,0) (xor2 (a, b))
>
sumOut
<- at (1,0) (xor2 (partSum, carryIn))
>
carryOut <- at (1,1) (mux2 (partSum, a, carryIn))
>
return (carryOut, sumOut)
The layout produced by the compact full adder is shown in Figure 4 (b).
An ripple-carry adder is now described using a combinator to lay out the full adders in a column. The XC6200
Designing FPGA Circuits in Lava
8
architecture makes it easy for software to access the cells in a column of the FPGA, which encourages some designs
to be vertically orientated. Rather than repeating the code for making a ripple-carry adder using the fat and compact
adders, we shall abstract over the full adder circuit itself by making it a higher order parameter to the vertical ripplecarry circuit.
> vertical_adder :: Circuit state =>
>
(((Bit, Bit), Bit) -> STrans state (Bit, Bit)) ->
>
Int -> ([Bit], [Bit]) -> STrans state [Bit]
> vertical_adder full_adder n (aBits, bBits)
>
= do let zs = zip aBits bBits
>
(cout, sums) <- column n full_adder (zs, L)
>
return (sums++[cout])
The first argument to this higher order circuit is a full adder circuit. Any circuit that maps a tuple of the type
((Bit, Bit), Bit) to a result of type (Bit, Bit) can be passed as an argument. Furthermore, one would
hope that only circuits that behaved as full adders were supplied, although the vertical_adder declaration does
check this property. The vertical ripple-carry adder is also parameterised on the number of bits to add and it assumed that the lists aBits and bBits contain n elements. The type identifies that aBits is a list of bits (written
[Bit]) but the Haskell type system does not specify the length of a list type.
The declaration of vertical_adder joins the two lists of bits to be added pairwise (using the Haskell list processing function zip) and binds the result to zs. Then a column of n full adders is made using the column combinator,
which also plumbs the carry out of one full adder into the carry in of the full adder above. The declaration of the
column combinator is essentially:
> column :: Circuit c =>
>
Int -> ((a,b) -> STrans c (b,d)) -> ([a],b) ->
>
STrans c (b,[d])
> column 0 f ([],b) = return (b,[])
> column n f ((a:as), b)
>
= do (intermediate_signal, c) <- f (a,b)
>
(last_signal, cs) <- column (n-1) f (as, intermediate_signal)
>
return (last_signal, c:cs)
The column combinator knows nothing about full adders; all it expects of its argument circuit is that it has a type
of the shape (_, _) -> STrans state (_, _) i.e. that it be a pair to pair circuit. The plumbing works by
taking the first element of the result pair and feeding it in as the second element of the circuit above.The column
combinator takes a list of input values and the initial value to feed into the first cell as its last argument. The result
of making a column of full adders is to return a pair containing the carry out and a list of bits that form the sum of
aBits and bBits. The adder circuit returns a result of size n+1 by adding the carry bit onto the top of the sum bits.
The higher order vertical adder circuit can be specialised to two 4-bit adders:
> big_adder_8
= vertical_adder big_FA 8
> compact_adder_8 = vertical_adder compact_FA 8
The layout for the 4-bit adder based on the big full adder cell is shown in Figure 5 (a); part (b) shows the layout for
the vertical adder using the compact full adder cell. Figure 5 (c) shows how the automatic place and route CAD
tools would have laid out the adder in part (a) in the absence of the layout information inferred from the combinators. This layout in part (c) is not a very desirable form e.g. it is not easy to juxtapose with a vertically orientated
register. Our experience has shown that for larger circuits, the automatic place and route tools either fail to find a
placement or they fail to route a circuit. Using Lava, a designer can choose to specify the layout of part of a design.
The designer can use layout combinators to influence the floor plan of the design, and by choosing a good layout
one can be more certain that the automatic routing tool will find a good route. Currently Lava only gives control
over layout and does not allows the designer to specify the route of a wire.
Designing FPGA Circuits in Lava
(a)
9
(b)
(c)
Figure 5: (a) Ripple-carry adder based on big full adder; (b) based on a compact full adder; and (c)
the layout without using any information from combinators
In this section we presented two circuits that have different implementations but should have the same behaviour.
Program verification techniques could be used to prove that these two full adder circuits behave identically. For
such a small circuit, exhaustive verification could also be used. Alternatively, one could use proved correct program transformation to derive one full adder from the other. Formal transformation and verification techniques are
more suitable for use in Lava than VHDL because Lava has a much simpler semantics. Lava provides another approach for verifying that two circuits are the same by generating input for the commercial model checker NP-Tools.
Using a form of symbolic evaluation, we automatically produce boolean formulae from circuit descriptions. These
formulae are then input to the commercial propositional logic theorem prover NP-Tools. We can either ask if two
circuits are equivalent (have the same behaviour) or we can ask if a circuit satisfies a requirement, which must also
be expressed in propositional logic. Here, we have found it useful to have Haskell available for constructing these
question formulae. The fact that NP-Tools also implements arithmetic on finite domains allows us to verify across
levels of abstraction, something which is vital in large scale examples. The screen shot in Figure 6 shows the NPTools application at work on this rather easy example, which can prove equivalent immediately.
The success of the verification is declared by the text “Valid” in the bottom left hand corner of the main window.
The NP-Tools software can verify that the two adder designs are equivalent in less than a second.
Designing FPGA Circuits in Lava
10
Figure 6: Formal verification of the two full adder designs
5
A Sequential Example: A Synchronous Counter
Sequential circuits can be built using a D-type positive edge triggered flip flop. The type of the D-type flip-flop is:
> fdc :: Circuit state => Bit -> (Bit, Bit, But) -> STrans Bit
> fdc initial_value (d, clk, clr) = ... { primitive cell }
This flip flop corresponds to the primitive flip-flop available in each cell on the XC6200 FPGA, has an asynchronous clear signal (clr) and it updates its state (by reading in d) on the rising edge of the clock (clk) (otherwise it
retains its state value). The first argument specifies the initial value of the flip-flop and must be given statically (at
compile type).
Lava provides a toggle flip-flop which just inverts its state on every clock tick. This circuit generates the output
sequence {0, 1, 0, 1, ...}. Sequential circuits with feedback can be expressed by using the Lava loop combinator:
> mloop :: Circuit state => (a -> STrans state a) -> STrans state a
This mloop combinator takes a circuit whose input and output types are the same and returns the same circuit with
the output wires connected to the input wires. As an example of mloop consider the toggle flip-flop as defined in
the Lava prelude:
> ftc :: Circuit state => (Bit, Bit) -> STrans state Bit
> ftc (t, clk)
>
= mloop (\ previousState
>
-> do invPreviousState <- at (0,0) $ xor2 (t, previousState)
>
nextState <- at (0,0) $ fd0 (invPreviousState, clk)
>
return nextState
>
)
where fd0 is a version of fdc specialised with the initial value 0. A lambda abstraction is used to define an “inline” function that takes the previous state, inverts it and then feeds in into a flip-flop. The mloop combinator joins
the signals previousState and nextState, making sure they get realised with the same wire. It is possible to
realise a flip-flop and an inverter in one cell, which is why both of these circuits have the same location. Here the
functional composition operator $ has been used to avoid writing extra brackets. Writing f (g a b) is the same as
writing f $ g a b where f and g are functions.
Designing FPGA Circuits in Lava
11
A synchronous counter stage can be built in the standard way:
> counterStage :: Circuit a =>
>
Bit -> (Bit, Bit) -> STrans a (Bit, Bit)
> counterStage clk (lastStage, lastLastStage)
>
= do t <- at (0,0) (and2 (lastStage, lastLastStage))
>
output <- at (1,0) (ftc (t, clk))
>
return (output, t)
This can be composed vertically using a variant of column:
> synchronousCounter :: Circuit a => Int -> Bit -> STrans a [Bit]
> synchronousCounter size clk = column_of size (counterStage clk) (H,H)
This counter can be simulated, symbolically evaluated or converted into VHDL. The layout of this counter is
shown in Figure 7. This also shows the pin through which the clock signal arrives. The automatic place and route
tools would not have nicely laid out the counter in this fashion: the layout combinators have allowed us to capture
vertically replicated cells without explicitly mentioning cell locations.
Figure 7: Layout of the synchronous counter.
6
Lava Implementation
Lava is designed around a systematic approach for building VLSI CAD tools using the technique of non-standard
interpretation [9]. The basic circuits are parameterised on the type of data flow through the ‘wires’. If the data along
the wires is bits, then the Haskell class system is used to bind a simulation interpretation to the basic gates. If it is
symbols then a symbolic evaluation interpretation is used.
Overloading in Haskell is controlled by a class system rather than being ad hoc like in C++. The class Circuit
contains the signatures of overloaded circuits (or member functions) that are said to belong to the Circuit class.
A part of the Circuit class looks like:
> class Circuit state where
>
inv :: Bit -> STrans state Bit
>
and2, or2, xor2 :: (Bit, Bit) -> STrans Bit
>
mux2 :: (Bit, Bit, Bit) -> STrans state Bit
For each type of state that we wish to interpret our circuit we must make an instance of the Circuit class. One of
the simplest interpretations is the simulation interpretation in which no state information is recorded. In this
case, an instance of Circuit is created with the empty type and the simulation definitions of the member functions is given:
Designing FPGA Circuits in Lava
12
> instance Circuit () where
>
inv L = H
>
inv H = L
>
...
To define symbolic simulation we declare a suitable type Symbol for representing symbolic expessions. We then
create an instance of Circuit at the Symbol type that builds up a symbol tree for the circuit.
> data Symbol = Low | High | Var String | Inv Symbol | And2 Symbol Symbol deriving
(Eq, Show)
> instance Circuit Symbol where
>
inv a = Inv a
>
and2 (a,b) = And2 (a, b)
>
...
Thus when one writes nandGate it is not known exactly what code should be executed for this circuit, hence the
need for a context clauses. However, once an argument is supplied the Haskell class system can dispatch the appropriate function. For example:
? exec nandGate (L, H)
H
? exec nandGate (Var “a”, “b”)
Inv (And2 (Var “a”, Var “b”))
Note that the same description has multiple interpretations, depending on the type of the data supplied to the circuit.
Basic interpretations can be combined to form powerful composite interpretations. This provides a systematic approach to building CAD tools and avoids re-specification of the circuit in different notations.
7
Summary
Lava is not a new language, but rather a system that demonstrates that Haskell, used in a particular style, makes a
good hardware description language. It allows elegant specification of requirements at a high level of abstraction.
It allows compact readable descriptions of circuits at various levels of abstraction, and these descriptions can be
translated automatically into VHDL or EDIF code. So there is a link to real circuits. We consider this to be very
important. All of the circuits in this paper and many others have actually been realised on FPGAs.
In addition, the Lava system allows a circuit description to be analysed in many different ways. For example, symbolic evaluation often allows the user to quickly examine a design; it can be seen as an alternative to user-guided
proof in certain cases. An adaption of symbolic evaluation gives us access to automatic verification of gate-level
circuits using the NP-Tools system. Performance analysis (such as timing analysis) and testability analysis can also
be implemented in this way.
Lava is still under development at the time of writing. It is used to design circuits on several projects, including the
hardware design of a PostScript accelerator chip and the description of circuits that are dynamically reconfigured
at run-time by using partial evaluation. It has been particularly effective as a rapid proto-typing tool, allowing circuits to be designed and laid out far more quickly than was possible using VHDL and layout pragmas. The Lava
website is at http://www.dcs.gla.ac.uk/~satnam/lava.
8
Acknowledgements
We would like to acknowledge the support of Xilinx Development Corp. for the provision of FPGA hardware,
CAD tools and support for the development of the Lava interface to their tools. Loggikonsult provided the NPTools software which allowed us to formally verify Lava circuits. Logikkonsult also supported Mary Sheeran’s
work on the formal verification of circuits described in Haskell. The design of Lava was heavily influenced by the
Hawk system developed at the Oregon Graduate Institute by John Launchbury and John Mathews. This work is
part funded by the United Kingdom EPSRC grants GR/K82055 and GR/L38530.
9
Note to Referees
Due to space constraints we have not been able to include a derivation of a reconfigurable multiplier from its specification. This example illustrates design by formal transformation in Lava and if the referees agree to a 15 page
paper we are willing to compact some of the earlier sections to make way for the multiplier derivation.
Designing FPGA Circuits in Lava
13
References
[1] M. Fourman. Formal Design in Formal Design Methods for VLSI Design, ed. J. Stanstrup, North Holland,
1992.
[2] F. K. Hanna. Implementation of the Veritas Design Logic in IFIP Conf. on Theorem Provers in Circuit Design,
1992.
[3] S. D. Johnson. Applicative Programming and Digital Design, 11th ACM Symp. on Principles of Programming
Languages (POPL), 1984.
[4] T. Kean, B. New, B. Slous. A Multiplier for the XC6200. Sixth International Workshop on Field Programmable Logic and Applications. Darmstadt, 1996.
[5] Y. Li and Miriam Lesser. HML: An innovative Hardware Design Language and Its Translation to VHDL.
Proc.
CHDL’95, 1995.
[6] John O’Donnell. Hydra: Hardware descriptions in a functional language using recursion equations and higher order combining forms, The Fusion of Hardware Design and Verification, G. J. Milne (ed), North-Holland,
1988.
[7] John O’Donnell. Bidirectional fold and scan. Functional Programming: Glasgow 1993, Springer, 1994.
[8] M. Sheeran, G. Jones. Circuit Design in Ruby . Formal Methods for VLSI Design, J. Stanstrup, North Holland,
1992.
[9] S. Singh. Circuit Analysis by Non-Standard Interpretation. IFIP Transactions on Designing Correct Circuits,
J. Staunstrup, R. Sharp (Eds), North-Holland, 1992.