Language Reference
The purpose of this section is to provide an overview of the ReWire language as it relates to vanilla Haskell. Readers unfamiliar with Haskell may wish to consult an introductory text such as Programming in Haskell by Graham Hutton or Learn You a Haskell for Great Good! by Miran Lipovača before diving into ReWire.
ReWire is a subset of Haskell: all ReWire programs are Haskell programs, but not all Haskell programs are ReWire programs. The main difference between ReWire and Haskell is that ReWire places limits on the use of higher order functions, polymorphism, and recursion. Higher order functions and polymorphism are not allowed except where they can be eliminated via inlining. Recursion is only allowed for functions and computations in a certain class of monads, and such computations must be guarded and tail recursive.
Reactive Resumption Monads
The fundamental abstraction in ReWire is a class of monads called reactive resumption monads. Reactive resumption monads allow us to give a functional semantics to clocked, reactive processes like hardware circuits.
While reactive resumption monads are treated as a primitive in ReWire, their semantics can be defined in terms of Haskell. We will start with a simple case, then generalize to a monad transformer. The type of the reactive resumption monad Re is defined in Haskell as follows.
data Re i o a = Done a
| Pause o (i -> Re i o a)
Think of the type Re i o a as representing a computation that is exchanging input and output signals (of types i and o respectively) with an external environment, and will return a value of type a if and when it terminates. Formally, a computation in Re i o a is either in a Done state, representing a finished computation that has returned a value of type a, or in a Pause state, yielding an output of type o and a function (i.e., a continuation) of type i -> Re i o a that is waiting for the next input from the environment. The type Re i o is a monad as follows:
instance Monad (Re i o) where
return = Done
Done v >>= f = f v
Pause o k >>= f = Pause o (k >=> f)
where >=> is the left-to-right Kleisli composition operator.
We can generalize Re to a monad transformer ReacT, which allows us to mix other kinds of effects with reactivity. (ReacT is the type you actually import from the ReWire standard library; it was called ReT in older versions of ReWire.)
newtype ReacT i o m a =
ReacT { deReacT :: m (Either a (o, i -> ReacT i o m a)) }
instance Monad m => Monad (ReacT i o m) where
return = ReacT . return . Left
ReacT m >>= f = ReacT $ do
r <- m
case r of
Left v -> deReacT (f v)
Right (o,k) -> return (Right (o,k >=> f))
instance MonadTrans (ReacT i o) where
lift m = ReacT (m >>= return . Left)
One particularly useful operation in ReacT, which we will actually take as a primitive in ReWire, is called signal.
signal :: Monad m => o -> ReacT i o m i
signal o = ReacT (return (Right (o,return)))
Think of signal o as meaning “yield the output o to the environment, wait for a new input i, then return i”.
Layered State Monads
ReWire also contains built-in support for layered state monads, which enable us to describe circuits with mutable state. Formally, a layered state monad is any monad composed from zero or more applications of the state monad transformer StateT to the base identity monad Identity. ReWire uses the same StateT and Identity as Haskell’s standard libraries; for reference, they can be defined as follows:
newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }
instance Monad m => Monad (StateT s m) where
return x = StateT $ \ s -> return (x,s)
StateT m >>= f = StateT $ \ s -> m s >>= \ (x,s) -> runStateT (f x) s
instance MonadTrans (StateT s) where
lift m = StateT $ \ s -> m >>= \ x -> return (x,s)
newtype Identity a = Identity { runIdentity :: a }
instance Monad Identity where
return = Identity
Identity x >>= f = f x
A ReWire device thus has the type ReacT i o (StateT s1 (StateT s2 (... Identity))) (), with one StateT layer per register bank; with no state at all it is just ReacT i o Identity (), for which the ReWire standard library provides the synonym Dev i o. The extrude operation lowers a StateT layer by supplying its initial value, turning a stateful device into one ready for synthesis:
extrude :: Monad m => ReacT i o (StateT s m) a -> s -> ReacT i o m a
Restrictions
The ReWire language is essentially a subset of Haskell. The major restrictions are as follows.
- Type classes are not implemented: programs may not declare their own
classes orinstances. - Polymorphic and higher-order functions are only allowed if they can be eliminated via inlining.
- For example, the polymorphic function
fst :: (a,b) -> acan always be inlined, as can the higher-order (and polymorphic) function(.) :: (b -> c) -> (a -> b) -> (a -> c).
- For example, the polymorphic function
datatypes are allowed, including parametric data types likeMaybe, but only if they are first-order (i.e., do not contain any function- or monad-typed fields) and non-recursive.typesynonyms are supported (including type-level naturals, e.g.type W n = Vec n Bool).- Recursive function definitions are allowed, but only in a reactive resumption monad. Such definitions must be tail recursive and guarded (by a
signal).- For instance,
loop n = signal n >>= \ i -> loop (n + i)is fine, but a non-reactive recursion likecount n = count (n + 1)is rejected, as is a recursivedatatype.
- For instance,
A misuse of any of these is reported as a compile-time error rather than producing incorrect hardware.
Interfacing with HDL
- Bit-level types.
import ReWireandimport ReWire.BitsprovideBit(a single bit) and the fixed-width word typeW n, ann-bit word indexed by a type-level natural, together with arithmetic, comparison, bitwise, shift, slicing, and reduction operations. More generally, any first-orderdatatype is laid out automatically as a bit vector. TheVec n atype (fromReWire.Vectors) gives fixed-length vectors. -
Externs. When you need to drop down to a hand-written HDL module (a primitive the compiler doesn’t generate, an IP block, a clocked component), use
extern(orexternWithSigfor full control over port names, parameters, and clock/reset):extern :: String -> a -> a externWithSig :: [(String, Integer)] -- module parameters -> String -- clock signal name (or "") -> String -- reset signal name (or "") -> [(String, Integer)] -- input ports (name, width) -> [(String, Integer)] -- output ports (name, width) -> String -- module name -> a -- Haskell model (for interpreting/Cryptol) -> String -- instance name -> aThe next-to-last argument is a Haskell definition used as the extern’s model when interpreting or generating Cryptol; you supply the matching HDL module to the synthesis/simulation toolchain yourself. (
externreplaces the oldnativeVhdlprimitive.) start. Every program designates a top-level device namedstart(override with--start), whose type fixes the widths of the generated module’s input and output ports.signal.signal oyields outputofor one clock cycle and returns the input sampled on the next cycle—the basic unit of clocked I/O.