A Functional-Logic Library for Wired
A Functional-Logic Library for Wired
Matthew Naylor and Collin Runciman – University of York
Emil Axelsson – Chalmers University
Haskell Workshop 2007
Acknowledgement
This project recieves Intel-custom funding from the Semiconductor Research Corporation
Overview
Background: Digital circuit design
Wired: Low-level hardware design in Haskell
LP: Logic programming in Haskell
Wired implemented using LP
Circuit abstraction levels
Just like software, digital circuits can be described at various levels of abstraction:
Netlist: Directed graph with simple Boolean gates as nodes
Placed netlist: Netlist where every gate has been assigned a unique position on the chip
Final layout: Placed netlist where every connection has been assigned a unique route in the metal layers
Netlist-to-layout is normally done by automatic tools
The problem…
Integrated circuit performance is largely determined by wire properties
Long wire longer signal delay, more power dissipationMany long wires bigger chip area
The abstract netlist view leaves wires implicit!
Conclusion: Hard to reason about performance at the netlist level
Evidence: Large parts of Intel’s chips are designed through manual layout editing
Can Haskell help?
Haskell has already proven to be useful for hardware description at the (placed) netlist level (Lava, Hydra)
Idea: Take the Lava approach further and describe the full layout, including wiring in Haskell
Result: Wired
Wired example
bitMult = row (and2 *=* wiring) where wiring = (wire *||* wireT0 *||* wire) `withConstraintW` (<== sigStruct)
Netlist and layout
described simultaneously
Netlist and layout
described simultaneously
Wires and gates are conceptually the same
thing!
Wires and gates are conceptually the same
thing!
Functional circuit model
Lava represents circuits as functions
Inputs and outputs are determined syntactically
But where is the input here?
Relational circuit model
One relational combinator captures a whole family of functional ones (signal-flow abstraction)
Relational circuit model
One relational wire captures a whole family of functional ones (signal-flow abstraction)
Relational circuit model
Size inference
x
64
LP: Logic programming in Haskell
Old Wired implementation very ad-hoc and inefficient
Matthew Naylor spotted that Curry was better suited for implementing Wired
This led to the birth of the library LPInspired by Curry and Typed logical variables in Haskell. Claessen and Ljunglöf (HW’2000)Easy construction/deconstruction of logical termsSupports residuation (heavily used in Wired)Supports non-determinism
Logical terms
data Uni = Var VarID | Ctr Int [Uni] | Int Int
newtype Term a = Term { uni :: Uni }
type VarID = Int
nil :: Term [a]nil = Term (Ctr 0 [])
(|>) :: Term a -> Term [a] -> Term [a]a |> as = Term (Ctr 1 [uni a, uni as])
int :: Int -> Term Intint n = Term (Int n)...
Definition
Constructors
Automating data type creation
(nil ::: (|>)) = datatype (cons0 [] \/ cons2 (:))
(true ::: false) = datatype (cons0 True \/ cons0 False)
Logic programs
data LP a = LP { runLP :: State -> [(a, State)] }
type State = (Map VarID Val, VarID)
type Residual = Uni -> LP ()data Val = Bound Uni | Unbound [Residual]
Possibly bound to a
term
Possibly bound to a
term
Current variable mapping, and a
fresh ID
Current variable mapping, and a
fresh ID
Non-determinism + stateNon-determinism + state
Actual implementation uses Hinze’s two-continuation monad transformer and
IORef’s
Actual implementation uses Hinze’s two-continuation monad transformer and
IORef’s
Basic LP interface
instance Monad LP ...
instance MonadPlus LP ...
(?) :: LP a -> LP a -> LP a(?) = mplus
newVar :: Val -> LP VarID
readVar :: VarID -> LP Val
writeVar :: VarID -> Val -> LP ()
(>>) interpreted as conjunction
(>>) interpreted as conjunction
Disjunction + failure
Disjunction + failure
Unification
unify :: Uni -> Uni -> LP ()unify a b = do ra <- root a; rb <- root b unif ra rb where unif (Var v) (Var w) | v == w = return () unif (Var v) b = bindVar v b unif a (Var w) = bindVar w a unif (Int a) (Int b) | a == b = return () unif (Ctr n as) (Ctr m bs) | n == m = unif' as bs unif _ _ = mzero
unif' [] [] = return () unif' (a:as) (b:bs) = unify a b >> unif' as bs
Logical class
class Logical a where free :: LP a (===) :: a -> a -> LP ()
instance Logical (Term a) where free = return . Term . Var =<< unboundVar a === b = unify (uni a) (uni b)
instance (Logical a, Logical b) => Logical (a,b) where free = liftM2 (,) free free (a0,a1) === (b0,b1) = a0 === b0 >> a1 === b1
Example: Append
app :: Term [a] -> Term [a] -> Term [a] -> LP ()
app as bs cs = do as===nil bs===cs ? do ((a,as'),cs’) <- free a|>as' === as a|>cs' === cs app as' bs cs'
testApp = run $ do as' <- free let as = 1|>2|>3|> as' cs = 1|>2|>3|>4|>5|> nil bs <- free app as bs cs return bs
*Main> testApp [[4,5],[5],[]]
Pattern matching
pat --> rhs = return (pat,rhs)
caseOf a as = do (pat,rhs) <- free >>= as pat === a rhs
app as bs cs = caseOf (as,cs) alts where alts ((a,as'),cs')
= (nil,cs') --> (bs===cs) ? (a|>as', a|>cs') --> app as' bs cs'
Residuation
rigid :: Logical b => (Uni -> LP b) -> (Uni -> LP b)...
Does not accept variables
(pure function)
Does not accept variables
(pure function)
Accepts variablesAccepts variables
Residuation
rigid :: Logical b => (Uni -> LP b) -> (Uni -> LP b)...
data Val = Bound Uni | Unbound [Residual]type Residual = Uni -> LP ()
bindVar :: VarID -> Uni -> LP ()bindVar v a = do Unbound rs <- readVar v writeVar v (Bound a) rs `resumeOn` a
Used by unify to instantiate a variable
Used by unify to instantiate a variable
Rigid deconstruction/arithmetic
unint :: Logical b => Term Int -> (Int -> LP b) -> LP bunint a f = rigid (\(Int a) -> f a) (uni a)
liftInt2 f a b = unint a (\a -> unint b (\b -> return (int (f a b))))
(|+|), (|-|), (|*|) :: Term Int -> Term Int -> LP (Term Int)
(|+|) = liftInt2 (+)(|-|) = liftInt2 (-)(|*|) = liftInt2 (*)
Residuation example
(<==) :: Logical a => a -> LP a -> LP ()a <== m = (a===) =<< m
testResid = run $ do (res,x) <- free res <== x |-| 3 x <== 5 |*| 2 return res
*Main> testResid [7]
Determistic relations
a <+> b = do c <- a |+| b b <== c |-| a a <== c |-| b return c
testPlus = run $ do a <- free 20 <== ((1<+>) =<< (2<+>) =<< (3<+>a)) return a
*Main> testPlus[14]
If any two of a, b and c are known, the third one will be
inferred
If any two of a, b and c are known, the third one will be
inferred
• Completely deterministic• Abstract information flow
Exactly what Wired needs!
• Completely deterministic• Abstract information flow
Exactly what Wired needs!
Wired
n
w
s
e
data Circuit w n s e = Circ { west :: w , north :: n , south :: s , east :: e , sizeX :: Term Size , sizeY :: Term Size , layout :: Term Layout }
type Circ w n s e = LP (Circuit w n s e)
Ports :Contain geometry
and signals of each side
Ports :Contain geometry
and signals of each side
Wired: Below composition
(*=*) :: (...) => Circ wL x sL eL -> Circ wH nH x eH -> Circ (wL,wH) nH sL (eL,eH)
circL *=* circH = do Circ wL nL sL eL szxL szyL layL <- circL Circ wH nH sH eH szxH szyH layH <- circH
nL === sH szy <- szyL <+> szyH let lay = (term (layL :=: layH))
return $ Circ (wL,wH) nH sL (eL,eH) szxL szy lay
nL
wL
sL
eL
nH
wH
sL
eH
sL
nH
(wL,wH)
(eL,eH)
Connects netlists and exchanges geometrical info
Connects netlists and exchanges geometrical info
*=*
Size inference revisited
x
64
Connection pattern: row
rowN :: (...) => Term Int -> Circ x n s x -> Circ x [n] [s] xrowN n circ = unint n rowNN where rowNN 0 = nilY rowNN n = circ *||~ rowNN (n-1)
row :: (...) => Circ y n s y -> Circ y [n] [s] yrow circ = do cR <- free l <- lengthR (north cR) l <== lengthR (south cR) cR <== rowN l circ return cR
Length determined by context.
lengthR is a rigid version of length.
Length determined by context.
lengthR is a rigid version of length.
Bit multiplier revisited
bitMult = row (and2 *=* wiring) where wiring = (wire *||* wireT0 *||* wire) `withConstraintW` (<== sigStruct)
*Main> renderCircuit "circ" (bitMult `ofLengthX` 6)
Example: Sklansky
sklansky 0 op opFO = rowN 1 $ nilX `withConstraint` \c -> north c <== (structure =<< asLP south =<< op)
sklansky d op opFO = consW (joinL *=* skl) ~||~ consE (joinR *=* skl) where skl = sklansky (d-1) op opFO op' = op *=*alignLeft opFO' = opFO*=*alignLeft joinL = row (busY*=*busY) ~||* (busT1*=*busY) joinR = row opFO' ~||* op'
Example: Sklansky
*Main> renderCircuit "circ" $ sklansky 3 dot dotFO
Related work
Lava:
Claessen, Sheeran, Singh. CHARME’2001.
Logic programming in Haskell:
Typed logical variables in Haskell. Claessen and Ljunglöf, HW’2000.
Backtracking, interleaving and terminating monad transformers. Kiselyov, Shan, Friedman and Sabry, ICFP’2005.
SparseCheck (Matthew Naylor 2007)
Future work
LP:More forms of non-determinism (breadth-first, fair backtracking…)Lazy narrowing
Wired:Working on new much simplified version (might not need LP…)Producing real layout for evaluationExamples…
Unification
unboundVar :: LP VarIDunboundVar = newVar (Unbound [])
bindVar :: VarID -> Uni -> LP ()bindVar v a = writeVar v (Bound a)
ifBound :: VarID -> (Uni -> LP b) -> LP b -> LP bifBound v t f = readVar v >>= decons where decons (Bound a) = t a decons (Unbound _) = f
root :: Uni -> LP Uniroot (Var v) = ifBound v root (return (Var v))root a = return a
Residuation
rigid' :: Logical b => (Uni -> LP b) -> (Uni -> LP b)...
data Val = Bound Uni | Unbound [Residual]type Residual = Uni -> LP ()
bindVar :: VarID -> Uni -> LP ()bindVar v a = do Unbound rs <- readVar v writeVar v (Bound a) rs `resumeOn` a
resumeOn :: [Residual] -> Uni -> LP ()
resumeOn rs (Var v) = do Unbound ss <- readVar v writeVar v (Unbound (rs ++ ss))
resumeOn rs a = mapM_ (resume a) rs where resume a g = g aUsed by unify to
instantiate a variableUsed by unify to
instantiate a variable
Residuation
rigid' :: Logical b => (Uni -> LP b) -> (Uni -> LP b)
rigid' f a = do ar <- root a b <- free let g x = f x >>= (===b) [g] `resumeOn` ar return b
rigid :: Logical b => (Term a -> LP b) -> (Term a -> LP b)rigid f a = rigid' (f . Term) (uni a)
b gives access to the (future) result of the
computation
b gives access to the (future) result of the
computation
Top Related