AlwaysKW.hs 8.76 KB
Newer Older
1 2 3
{- sv2v
 - Author: Zachary Snow <zach@zachjs.com>
 -
4
 - Conversion for `always_latch`, `always_comb`, and `always_ff`
5
 -
6 7
 - `always_comb` and `always_latch` become `always @*`, or produce an explicit
 - sensitivity list if they need to pick up sensitivities from the functions
8 9 10
 - they call. These blocks are triggered at time zero by adding a no-op
 - statement reading from `_sv2v_0`, which is injected and updated in an
 - `initial` block. `always_ff` simply becomes `always`.
11 12 13 14
 -}

module Convert.AlwaysKW (convert) where

15 16 17 18 19
import Control.Monad.State.Strict
import Control.Monad.Writer.Strict
import Data.Maybe (fromMaybe, mapMaybe)

import Convert.Scoper
20
import Convert.Traverse
21 22
import Language.SystemVerilog.AST

23
convert :: [AST] -> [AST]
24 25 26
convert = map $ traverseDescriptions traverseDescription

traverseDescription :: Description -> Description
27 28 29 30 31 32 33 34 35
traverseDescription (Part att ext kw lif name pts items) =
    Part att ext kw lif name pts $
    if getAny anys && not (elem triggerDecl items')
        then triggerDecl : items' ++ [triggerFire]
        else items'
    where
        op = traverseModuleItem >=> scoper
        (items', (anys, _)) = flip runState mempty $ evalScoperT $
            insertElem triggerIdent Var >> scopeModuleItems op name items
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
traverseDescription description = description

type SC = ScoperT Kind (State (Any, [Expr]))

type PortDir = (Identifier, Direction)

data Kind
    = Const Expr
    | Var
    | Proc [Expr] [PortDir]

scoper :: ModuleItem -> SC ModuleItem
scoper = scopeModuleItem traverseDecl return traverseGenItem traverseStmt

-- track declarations and visit expressions they contain
traverseDecl :: Decl -> SC Decl
traverseDecl decl = do
    case decl of
        Param    s _ x e   -> do
            -- handle references to local constants
            e' <- if s == Localparam
                    then scopeExpr e
                    else return Nil
            insertElem x $ Const e'
        ParamType  _ x _   -> insertElem x $ Const Nil
        Variable _ _ x _ _ -> do
            -- don't let the second visit of a function or task overwrite the
            -- Proc entry that was just generated
            details <- lookupLocalIdentM x
            case details of
                Just (_, _, Proc{}) -> return ()
                _ -> insertElem x Var
        Net  _ _ _ _ x _ _ -> insertElem x Var
        CommentDecl{} -> return ()
    traverseDeclExprsM traverseExpr decl

-- track expressions and subroutines in a statement
traverseStmt :: Stmt -> SC Stmt
traverseStmt (Subroutine expr args) =
    traverseCall Subroutine expr args
traverseStmt stmt = traverseStmtExprsM traverseExpr stmt

-- visit tasks, functions, and always blocks in generate scopes
traverseGenItem :: GenItem -> SC GenItem
traverseGenItem (GenModuleItem item) =
    traverseModuleItem item >>= return . GenModuleItem
traverseGenItem other = return other

-- identify variables referenced within an expression
traverseExpr :: Expr -> SC Expr
traverseExpr (Call expr args) =
    traverseCall Call expr args
traverseExpr expr = do
    prefix <- embedScopes longestStaticPrefix expr
    case prefix of
        Just expr' -> push (Any False, [expr']) >> return expr
        _ -> traverseSinglyNestedExprsM traverseExpr expr

-- turn a reference to a variable into a canonicalized longest static prefix, if
-- possible, per IEEE 1800-2017 Section 11.5.3
longestStaticPrefix :: Scopes Kind -> Expr -> Maybe Expr
longestStaticPrefix scopes expr@Ident{} =
    asVar scopes expr
longestStaticPrefix scopes (Range expr mode (l, r)) = do
    expr' <- longestStaticPrefix scopes expr
    l' <- asConst scopes l
    r' <- asConst scopes r
    Just $ Range expr' mode (l', r')
longestStaticPrefix scopes (Bit expr idx) = do
    expr' <- longestStaticPrefix scopes expr
    idx' <- asConst scopes idx
    Just $ Bit expr' idx'
longestStaticPrefix scopes orig@(Dot expr field) =
    case asVar scopes orig of
        Just orig' -> Just orig'
        _ -> do
            expr' <- longestStaticPrefix scopes expr
            Just $ Dot expr' field
longestStaticPrefix _ _ =
    Nothing

-- lookup an expression as an outwardly-visible variable
asVar :: Scopes Kind -> Expr -> Maybe Expr
asVar scopes expr = do
    (accesses, _, Var) <- lookupElem scopes expr
    if visible accesses
        then Just $ accessesToExpr accesses
        else Nothing

visible :: [Access] -> Bool
visible = not . elem (Access "" Nil)

-- lookup an expression as a hoist-able constant
asConst :: Scopes Kind -> Expr -> Maybe Expr
asConst scopes expr =
    case runWriter $ asConstRaw scopes expr of
        (expr', Any False) -> Just expr'
        _ -> Nothing

asConstRaw :: Scopes Kind -> Expr -> Writer Any Expr
asConstRaw scopes expr =
    case lookupElem scopes expr of
138
        Just (accesses@[_, _], _, Const{}) -> return $ accessesToExpr accesses
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
        Just (_, _, Const Nil) -> recurse
        Just (_, _, Const expr') -> asConstRaw scopes expr'
        Just{} -> tell (Any True) >> return Nil
        Nothing -> recurse
    where
        recurse = traverseSinglyNestedExprsM (asConstRaw scopes) expr

-- special handling for subroutine invocations and function calls
traverseCall :: (Expr -> Args -> a) -> Expr -> Args -> SC a
traverseCall constructor expr args = do
    details <- lookupElemM expr
    expr' <- traverseExpr expr
    args' <- case details of
        Just (_, _, Proc exprs ps) -> do
            when (not $ null exprs) $
                push (Any True, exprs)
            traverseArgs ps args
        _ -> traverseArgs [] args
    return $ constructor expr' args'

-- treats output ports as assignment-like contexts
traverseArgs :: [PortDir] -> Args -> SC Args
traverseArgs ps (Args pnArgs kwArgs) = do
    pnArgs' <- zipWithM usingPN [0..] pnArgs
    kwArgs' <- mapM usingKW kwArgs
    return (Args pnArgs' kwArgs')
    where

        usingPN :: Int -> Expr -> SC Expr
        usingPN key val = do
            if dir == Output
                then return val
                else traverseExpr val
            where dir = if key < length ps
                            then snd $ ps !! key
                            else Input

        usingKW :: (Identifier, Expr) -> SC (Identifier, Expr)
        usingKW (key, val) = do
            val' <- if dir == Output
                        then return val
                        else traverseExpr val
            return (key, val')
            where dir = fromMaybe Input $ lookup key ps

-- append to the non-local expression state
push :: (Any, [Expr]) -> SC ()
push x = lift $ modify' (x <>)

-- custom traversal which converts SystemVerilog `always` keywords and tracks
-- information about task and functions
traverseModuleItem :: ModuleItem -> SC ModuleItem
191 192
traverseModuleItem (AlwaysC AlwaysLatch stmt) =
    traverseModuleItem $ AlwaysC AlwaysComb stmt
193
traverseModuleItem (AlwaysC AlwaysComb stmt) = do
194 195 196 197
    push (Any True, [])
    e <- fmap toEvent $ findNonLocals $ Initial stmt'
    return $ AlwaysC Always $ Timing (Event e) stmt'
    where stmt' = addTriggerStmt stmt
198 199 200 201 202 203 204 205 206
traverseModuleItem (AlwaysC AlwaysFF stmt) =
    return $ AlwaysC Always stmt
traverseModuleItem item@(MIPackageItem (Function _ _ x decls _)) = do
    (_, s) <- findNonLocals item
    insertElem x $ Proc s (ports decls)
    return item
traverseModuleItem item@(MIPackageItem (Task _ x decls _)) = do
    insertElem x $ Proc [] (ports decls)
    return item
207 208
traverseModuleItem (MIAttr attr item) =
    MIAttr attr <$> traverseModuleItem item
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
traverseModuleItem other = return other

toEvent :: (Bool, [Expr]) -> Event
toEvent (False, _) = EventStar
toEvent (True, exprs) =
    EventExpr $ foldl1 EventExprOr $ map (EventExprEdge NoEdge) exprs

-- turn a list of port declarations into a port direction map
ports :: [Decl] -> [PortDir]
ports = filter ((/= Local) . snd) . map port

port :: Decl -> PortDir
port (Variable d _ x _ _) = (x, d)
port _ = ("", Local)

-- get a list of non-local variables referenced within a module item, and
-- whether or not this module item references any functions which themselves
-- reference non-local variables
findNonLocals :: ModuleItem -> SC (Bool, [Expr])
findNonLocals item = do
    scopes <- get
230
    prev <- lift get
231 232 233
    lift $ put mempty
    _ <- scoper item
    (anys, exprs) <- lift get
234
    lift $ put prev
235 236
    let nonLocals = mapMaybe (longestStaticPrefix scopes) exprs
    return (getAny anys, nonLocals)
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254

triggerIdent :: Identifier
triggerIdent = "_sv2v_0"

triggerDecl :: ModuleItem
triggerDecl = MIPackageItem $ Decl $ Variable Local t triggerIdent [] Nil
    where t = IntegerVector TReg Unspecified []

triggerFire :: ModuleItem
triggerFire = Initial $ Asgn AsgnOpEq Nothing (LHSIdent triggerIdent) (RawNum 0)

triggerStmt :: Stmt
triggerStmt = If NoCheck (Ident triggerIdent) Null Null

addTriggerStmt :: Stmt -> Stmt
addTriggerStmt (Block Seq name decls stmts) =
    Block Seq name decls $ triggerStmt : stmts
addTriggerStmt stmt = Block Seq "" [] [triggerStmt, stmt]