390 likes | 480 Views
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.
E N D
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 dissipation • Many 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 Wires and gates are conceptually the same thing! bitMult = row (and2 *=* wiring) where wiring = (wire *||* wireT0 *||* wire) `withConstraintW` (<== sigStruct) Netlist and layout described simultaneously
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 LP • Inspired by Curry and Typed logical variables in Haskell. Claessen and Ljunglöf (HW’2000) • Easy construction/deconstruction of logical terms • Supports residuation (heavily used in Wired) • Supports non-determinism
Logical terms • Definition • Constructors 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 Int int n = Term (Int n) ...
Automating data type creation (nil ::: (|>)) = datatype (cons0 [] \/ cons2 (:)) (true ::: false) = datatype (cons0 True \/ cons0 False)
Logic programs Non-determinism + state data LP a = LP { runLP :: State -> [(a, State)] } type State = (Map VarID Val, VarID) type Residual = Uni -> LP () data Val = Bound Uni | Unbound [Residual] Current variable mapping, and a fresh ID Possibly bound to a term Actual implementation uses Hinze’s two-continuation monad transformer and IORef’s
Basic LP interface (>>) interpreted as conjunction 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 () 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) ... Accepts variables Does not accept variables (pure function)
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
Rigid deconstruction/arithmetic unint :: Logical b => Term Int -> (Int -> LP b) -> LP b unint 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 If any two of a, b and c are known, the third one will be inferred 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] • Completely deterministic • Abstract information flow • Exactly what Wired needs!
n w e s Wired 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
nH (wL,wH) (eL,eH) nL nH wL wH eL eH sL sL sL 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 *=* Connects netlists and exchanges geometrical info
Connection pattern: row rowN :: (...) => Term Int -> Circ x n s x -> Circ x [n] [s] x rowN 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] y row 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.
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 evaluation • Examples…
Unification unboundVar :: LP VarID unboundVar = newVar (Unbound []) bindVar :: VarID -> Uni -> LP () bindVar v a = writeVar v (Bound a) ifBound :: VarID -> (Uni -> LP b) -> LP b -> LP b ifBound v t f = readVar v >>= decons where decons (Bound a) = t a decons (Unbound _) = f root :: Uni -> LP Uni root (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 a Used 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