Interface.hs 36.6 KB
Newer Older
1 2 3 4 5 6
{- sv2v
 - Author: Zachary Snow <zach@zachjs.com>
 -
 - Conversion for interfaces
 -}

7
module Convert.Interface (convert, disambiguate) where
8

9
import Data.List (intercalate, (\\))
10
import Data.Maybe (isJust, isNothing, mapMaybe)
11
import Control.Monad.Writer.Strict
12
import Text.Read (readMaybe)
13 14
import qualified Data.Map.Strict as Map

15
import Convert.ExprUtils (endianCondExpr)
16
import Convert.Scoper
17 18 19
import Convert.Traverse
import Language.SystemVerilog.AST

20 21 22 23
data PartInfo = PartInfo
    { pKind :: PartKW
    , pPorts :: [Identifier]
    , pItems :: [ModuleItem]
24
    }
25 26
type PartInfos = Map.Map Identifier PartInfo

27
type ModportInstances = [(Identifier, (Identifier, Identifier))]
28 29
type ModportBinding = (Identifier, (Substitutions, Expr))
type Substitutions = [(Expr, Expr)]
30

31 32
convert :: [Identifier] -> [AST] -> [AST]
convert tops files =
33 34 35 36
    if needsFlattening
        then files
        else traverseFiles
            (collectDescriptionsM collectPart)
37
            (map . convertDescription tops)
38
            files
39
    where
40 41 42 43 44 45 46 47 48 49 50
        -- multidimensional instances need to be flattened before this
        -- conversion can proceed
        needsFlattening =
            getAny $ execWriter $ mapM (collectDescriptionsM checkPart) files
        checkPart :: Description -> Writer Any ()
        checkPart (Part _ _ _ _ _ _ items) =
            mapM (collectNestedModuleItemsM checkItem) items >> return ()
        checkPart _ = return ()
        checkItem :: ModuleItem -> Writer Any ()
        checkItem (Instance _ _ _ rs _) = when (length rs > 1) $ tell $ Any True
        checkItem _ = return ()
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
-- we can only collect/map non-extern interfaces and modules
collectPart :: Description -> Writer PartInfos ()
collectPart (Part _ False kw _ name ports items) =
    tell $ Map.singleton name $ PartInfo kw ports items
collectPart _ = return ()

-- disambiguate typenames from interface names
disambiguate :: [AST] -> [AST]
disambiguate = traverseFiles
    (collectDescriptionsM collectPart)
    (map . disambiguateDescription)

-- disambiguate any typenames within a description
disambiguateDescription :: PartInfos -> Description -> Description
disambiguateDescription parts (Part att ext kw lif name ports items) =
    Part att ext kw lif name ports $ map traverseModuleItem items
    where
        typeNames = getTypeNames items

        traverseModuleItem :: ModuleItem -> ModuleItem
        traverseModuleItem (MIAttr attr item) =
            MIAttr attr $ traverseModuleItem item
        traverseModuleItem (MIPackageItem (Decl (Variable d t x a e))) =
            MIPackageItem $ Decl $ Variable d (traverseType t) x a e
        traverseModuleItem other = other

        traverseType :: Type -> Type
        traverseType (Alias interfaceName rs) =
            if isInterface interfaceName && not (elem interfaceName typeNames)
                then InterfaceT interfaceName "" rs
                else Alias interfaceName rs
        traverseType orig@(InterfaceT interfaceName _ _) =
            if null interfaceName || isInterface interfaceName
                then orig
                else error $ "declaration type " ++ show orig ++ " appears to "
                    ++ "refer to an interface that isn't defined"
        traverseType other = other

        isInterface :: Identifier -> Bool
        isInterface partName =
            fmap pKind (Map.lookup partName parts) == Just Interface

disambiguateDescription _ other = other

-- get all of the typenames declared anywhere in the top-level module items
getTypeNames :: [ModuleItem] -> [Identifier]
getTypeNames (MIAttr _ item : rest) = getTypeNames $ item : rest
getTypeNames (Generate genItems : rest) =
    getTypeNames $ genModuleItems genItems ++ rest
getTypeNames (MIPackageItem (Decl (ParamType _ name _)) : rest) =
    name : getTypeNames rest
getTypeNames (_ : rest) = getTypeNames rest
getTypeNames [] = []

-- get the top-level (i.e., un-scoped) module items within a generate block
genModuleItems :: [GenItem] -> [ModuleItem]
genModuleItems (GenModuleItem item : rest) =
    item : genModuleItems rest
genModuleItems (_ : rest) = genModuleItems rest
genModuleItems [] = []

113 114 115 116 117 118 119 120 121 122 123 124
topInterfaceError :: String -> String -> a
topInterfaceError name issue = error $
    "Specified top module " ++ name ++ " " ++ issue ++ ". Please " ++
    "instantiate it somewhere and use that as your top module instead."

convertDescription :: [Identifier] -> PartInfos -> Description -> Description
convertDescription tops _ (Part _ _ Interface _ name _ _)
    | elem name tops =
        topInterfaceError name "is an interface"
    | otherwise =
        PackageItem $ Decl $ CommentDecl $ "removed interface: " ++ name
convertDescription tops parts (Part att ext Module lif name ports items) =
125
    if null $ extractModportInstances name $ PartInfo Module ports items then
126 127 128
        Part att ext Module lif name ports items'
    else if elem name tops then
        topInterfaceError name "has interface ports"
129 130
    else
        PackageItem $ Decl $ CommentDecl $
131
            "removed module with interface ports: " ++ name
132
    where
133 134
        items' = evalScoper $ scopeModuleItems scoper name items
        scoper = scopeModuleItem traverseDeclM traverseModuleItemM return return
135 136 137 138

        traverseDeclM :: Decl -> Scoper [ModportDecl] Decl
        traverseDeclM decl = do
            case decl of
139 140 141
                Variable  _ t x _ _ -> checkDeclType t x >> insertElem x DeclVal
                Net   _ _ _ t x _ _ -> checkDeclType t x >> insertElem x DeclVal
                Param     _ t x _   -> checkDeclType t x >> insertElem x DeclVal
142 143 144 145
                ParamType _   x _   -> insertElem x DeclVal
                CommentDecl{} -> return ()
            return decl

146
        -- check for module or interface names used as type names
147 148 149 150 151 152 153 154 155
        checkDeclType :: Type -> Identifier -> Scoper a ()
        checkDeclType (Alias typeName _) declName
            | isNothing (readMaybe declName :: Maybe Int)
            , Just part <- Map.lookup typeName parts = do
                maybeType <- lookupElemM typeName
                when (isNothing maybeType) $ scopedErrorM $
                    "declaration " ++ declName ++ " uses " ++ show (pKind part)
                    ++ " name " ++ typeName ++ " where a type name is expected"
        checkDeclType _ _ = return ()
156

157 158 159 160 161
        lookupIntfElem :: Scopes [ModportDecl] -> Expr -> LookupResult [ModportDecl]
        lookupIntfElem modports expr =
            case lookupElem modports expr of
                Just (_, _, DeclVal) -> Nothing
                other -> other
162 163 164 165

        traverseModuleItemM :: ModuleItem -> Scoper [ModportDecl] ModuleItem
        traverseModuleItemM (Modport modportName modportDecls) =
            insertElem modportName modportDecls >> return (Generate [])
166
        traverseModuleItemM instanceItem@Instance{} = do
167
            modports <- embedScopes (\l () -> l) ()
168
            if isNothing maybePartInfo then
169 170 171
                return instanceItem
            else if partKind == Interface then
                -- inline instantiation of an interface
172
                scoper $ Generate $ map GenModuleItem $
173
                    inlineInstance modports rs []
174
                    partItems part instanceName paramBindings portBindings
175
            else if null modportInstances then
176
                return instanceItem
177 178
            else do
                -- inline instantiation of a module
179
                let modportBindings = getModportBindings modports
180 181 182 183 184 185
                let unconnected = map fst modportInstances \\
                                    map fst modportBindings
                if not (null unconnected)
                    then scopedErrorM $ "instance " ++ instanceName ++ " of "
                            ++ part ++ " has unconnected interface ports: "
                            ++ intercalate ", " unconnected
186
                    else scoper $ Generate $ map GenModuleItem $
187
                            inlineInstance modports rs modportBindings partItems
188
                            part instanceName paramBindings portBindings
189
            where
190
                Instance part paramBindings instanceName rs portBindings =
191 192 193
                    instanceItem
                maybePartInfo = Map.lookup part parts
                Just partInfo = maybePartInfo
194
                PartInfo partKind _ partItems = partInfo
195

196
                modportInstances = extractModportInstances part partInfo
197
                getModportBindings modports = mapMaybe
198 199 200
                    (inferModportBinding modports modportInstances) $
                    map (second $ addImpliedSlice modports) portBindings
                second f = \(a, b) -> (a, f b)
201 202 203

        traverseModuleItemM other = return other

204 205
        -- add explicit slices for bindings of entire modport instance arrays
        addImpliedSlice :: Scopes [ModportDecl] -> Expr -> Expr
206
        addImpliedSlice modports orig@(Dot expr modportName) =
207
            case lookupIntfElem modports (InstArrKey expr) of
208 209 210 211
                Just (_, _, InstArrVal l r) ->
                    Dot (Range expr NonIndexed (l, r)) modportName
                _ -> orig
        addImpliedSlice modports expr =
212
            case lookupIntfElem modports (InstArrKey expr) of
213 214 215 216 217
                Just (_, _, InstArrVal l r) ->
                    Range expr NonIndexed (l, r)
                _ -> expr

        -- elaborates and resolves provided modport bindings
218 219 220
        inferModportBinding :: Scopes [ModportDecl] -> ModportInstances ->
            PortBinding -> Maybe ModportBinding
        inferModportBinding modports modportInstances (portName, expr) =
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
            if maybeInfo == Nothing
                then Nothing
                else Just (portName, modportBinding)
            where
                modportBinding = (substitutions, replaceBit modportE)
                substitutions =
                    genSubstitutions modports base instanceE modportE
                maybeInfo =
                    lookupModportBinding modports modportInstances portName bitd
                Just (instanceE, modportE) = maybeInfo

                (exprUndot, bitd) = case expr of
                    Dot subExpr x -> (subExpr, Dot bitdUndot x)
                    _ -> (expr, bitdUndot)
                bitdUndot = case exprUndot of
                    Range subExpr _ _ -> Bit subExpr taggedOffset
                    Bit subExpr _ -> Bit subExpr untaggedOffset
                    _ -> exprUndot
                bitReplacement = case exprUndot of
                    Range _ mode range -> \e -> Range e mode range
                    Bit _ idx -> flip Bit idx
                    _ -> id
                base = case exprUndot of
                    Range{} -> Bit (Ident portName) Tag
                    _ -> Ident portName

                untaggedOffset = Ident $ modportBaseName portName
                taggedOffset = BinOp Add Tag untaggedOffset

                replaceBit :: Expr -> Expr
                replaceBit (Bit subExpr idx) =
                    if idx == untaggedOffset || idx == taggedOffset
                        then bitReplacement subExpr
                        else Bit subExpr idx
                replaceBit (Dot subExpr x) =
                    Dot (replaceBit subExpr) x
                replaceBit (Ident x) = Ident x
                replaceBit _ = error "replaceBit invariant violated"

        -- determines the underlying modport and interface instances associated
        -- with the given port binding, if it is a modport binding
        lookupModportBinding :: Scopes [ModportDecl] -> ModportInstances
            -> Identifier -> Expr -> Maybe (Expr, Expr)
        lookupModportBinding modports modportInstances portName expr =
265 266 267 268 269 270 271 272 273
            if bindingIsModport then
                -- provided specific instance modport
                foundModport expr
            else if bindingIsBundle && portIsBundle then
                -- bundle bound to a generic bundle
                foundModport expr
            else if bindingIsBundle && not portIsBundle then
                -- given entire interface, but just bound to a modport
                foundModport $ Dot expr modportName
274
            else if modportInstance /= Nothing then
275 276
                scopedError modports $ "could not resolve modport binding "
                    ++ show expr ++ " for port " ++ portName ++ " of type "
277
                    ++ showModportType interfaceName modportName
278 279
            else
                Nothing
280
            where
281 282
                bindingIsModport = lookupIntfElem modports expr /= Nothing
                bindingIsBundle = lookupIntfElem modports (Dot expr "") /= Nothing
283
                portIsBundle = null modportName
284 285 286 287
                modportInstance = lookup portName modportInstances
                (interfaceName, modportName) =
                    case modportInstance of
                        Just x -> x
288 289 290
                        Nothing -> scopedError modports $
                            "can't deduce modport for interface " ++ show expr
                            ++ " bound to port " ++ portName
291 292

                foundModport modportE =
293 294 295
                    if (null interfaceName || bInterfaceName == interfaceName)
                        && (null modportName || bModportName == modportName)
                        then Just (instanceE, qualifyModport modportE)
296
                        else scopedError modports msg
297 298 299 300 301 302 303 304 305 306 307 308 309
                    where
                        bModportName =
                            case modportE of
                                Dot _ x -> x
                                _ -> ""
                        instanceE = findInstance modportE
                        Just (_, _, InterfaceTypeVal bInterfaceName) =
                            lookupIntfElem modports $ InterfaceTypeKey
                                (findInstance modportE)
                        msg = "port " ++ portName ++ " has type "
                            ++ showModportType interfaceName modportName
                            ++ ", but the binding " ++ show expr ++ " has type "
                            ++ showModportType bInterfaceName bModportName
310

311 312
                findInstance :: Expr -> Expr
                findInstance e =
313
                    case lookupIntfElem modports (Dot e "") of
314 315 316 317
                        Nothing -> case e of
                            Bit e' _ -> findInstance e'
                            Dot e' _ -> findInstance e'
                            _ -> error "internal invariant violated"
318 319 320
                        Just (accesses, _, _) -> accessesToExpr $ init accesses
                qualifyModport :: Expr -> Expr
                qualifyModport e =
321
                    accessesToExpr $
322
                    case lookupIntfElem modports e of
323 324 325 326
                        Just (accesses, _, _) -> accesses
                        Nothing ->
                            case lookupIntfElem modports (Dot e "") of
                                Just (accesses, _, _) -> init accesses
327 328
                                Nothing -> scopedError modports $
                                    "could not find modport " ++ show e
329 330 331 332 333

        showModportType :: Identifier -> Identifier -> String
        showModportType "" "" = "generic interface"
        showModportType intf "" = intf
        showModportType intf modp = intf ++ '.' : modp
334 335

        -- expand a modport binding into a series of expression substitutions
336 337 338 339
        genSubstitutions :: Scopes [ModportDecl] -> Expr -> Expr -> Expr
            -> [(Expr, Expr)]
        genSubstitutions modports baseE instanceE modportE =
            (baseE, instanceE) :
340
            map toPortBinding modportDecls
341
            where
342 343
                a = lookupIntfElem modports modportE
                b = lookupIntfElem modports (Dot modportE "")
344 345 346
                Just (_, replacements, modportDecls) =
                    if a == Nothing then b else a
                toPortBinding (_, x, e) = (x', e')
347
                    where
348
                        x' = Dot baseE x
349
                        e' = replaceInExpr replacements e
350 351

        -- association list of modport instances in the given module body
352 353 354
        extractModportInstances :: Identifier -> PartInfo -> ModportInstances
        extractModportInstances part partInfo =
            execWriter $ runScoperT $ scopeModuleItems collector part decls
355
            where
356 357 358 359
                collector = scopeModuleItem checkDecl return return return
                decls = filter isDecl $ pItems partInfo
                checkDecl :: Decl -> ScoperT () (Writer ModportInstances) Decl
                checkDecl decl@(Variable _ t x _ _) =
360
                    if maybeInfo == Nothing then
361
                        return decl
362
                    else if elem x (pPorts partInfo) then
363
                        tell [(x, info)] >> return decl
364
                    else
365 366
                        scopedErrorM $
                            "Modport not in port list: " ++ show t ++ " " ++ x
367
                            ++ ". Is this an interface missing a port list?"
368
                    where
369 370
                        maybeInfo = extractModportInfo t
                        Just info = maybeInfo
371
                checkDecl decl = return decl
372

373
        extractModportInfo :: Type -> Maybe (Identifier, Identifier)
374
        extractModportInfo (InterfaceT interfaceName modportName _) =
375
            Just (interfaceName, modportName)
376 377
        extractModportInfo _ = Nothing

378
convertDescription _ _ other = other
379

380 381 382 383
isDecl :: ModuleItem -> Bool
isDecl (MIPackageItem Decl{}) = True
isDecl _ = False

384 385 386
-- produce the implicit modport decls for an interface bundle
impliedModport :: [ModuleItem] -> [ModportDecl]
impliedModport =
387 388
    execWriter . mapM
        (collectNestedModuleItemsM $ collectDeclsM collectModportDecls)
389
    where
390 391 392 393
        collectModportDecls :: Decl -> Writer [ModportDecl] ()
        collectModportDecls (Variable _ _ x _ _) =
            tell [(Inout, x, Ident x)]
        collectModportDecls (Net  _ _ _ _ x _ _) =
394
            tell [(Inout, x, Ident x)]
395 396 397 398
        collectModportDecls _ = return ()

-- convert an interface-bound module instantiation or an interface instantiation
-- into a series of equivalent inlined module items
399 400 401 402
inlineInstance :: Scopes [ModportDecl] -> [Range] -> [ModportBinding]
    -> [ModuleItem] -> Identifier -> Identifier -> [ParamBinding]
    -> [PortBinding] -> [ModuleItem]
inlineInstance global ranges modportBindings items partName
403 404
    instanceName instanceParams instancePorts =
    comment :
405
    map (MIPackageItem . Decl) bindingBaseParams ++
406
    map (MIPackageItem . Decl) parameterBinds ++
407
    wrapInstance instanceName items'
408 409
    : portBindings
    where
410
        items' = evalScoper $ scopeModuleItems scoper partName $
411
            map (traverseNestedModuleItems rewriteItem) $
412
            if null modportBindings
413 414 415 416
                then itemsChecked ++ infoModports
                else itemsChecked
        itemsChecked = checkBeforeInline global partName items checkErrMsg
        infoModports = [typeModport, dimensionModport, bundleModport]
417 418
        scoper = scopeModuleItem
            traverseDeclM traverseModuleItemM traverseGenItemM traverseStmtM
419

420 421
        key = shortHash (partName, instanceName)

422 423 424
        -- synthetic modports to be collected and removed after inlining
        bundleModport = Modport "" (impliedModport items)
        dimensionModport = if not isArray
425
            then Generate []
426
            else InstArrEncoded arrayLeft arrayRight
427
        typeModport = InterfaceTypeEncoded partName
428

429 430 431
        inlineKind =
            if null modportBindings
                then "interface"
432
                else "module"
433

434 435
        comment = MIPackageItem $ Decl $ CommentDecl $
            "expanded " ++ inlineKind ++ " instance: " ++ instanceName
436 437 438 439
        portBindings =
            wrapPortBindings $
            map portBindingItem $
            filter ((/= Nil) . snd) $
440 441 442
            filter notSubstituted instancePorts
        notSubstituted :: PortBinding -> Bool
        notSubstituted (portName, _) =
443
            lookup portName modportBindings == Nothing
444 445 446 447 448 449
        wrapPortBindings :: [ModuleItem] -> [ModuleItem]
        wrapPortBindings =
            if isArray
                then (\x -> [x]) . wrapInstance blockName
                else id
            where blockName = instanceName ++ "_port_bindings"
450 451 452

        rewriteItem :: ModuleItem -> ModuleItem
        rewriteItem =
453
            traverseDecls $
454 455
            removeModportInstance .
            removeDeclDir .
456
            overrideParam
457

458
        traverseDeclM :: Decl -> Scoper () Decl
459
        traverseDeclM decl = do
460 461 462 463 464
            case decl of
                Variable  _ _ x _ _ -> insertElem x ()
                Net   _ _ _ _ x _ _ -> insertElem x ()
                Param     _ _ x _   -> insertElem x ()
                ParamType _   x _   -> insertElem x ()
465
                CommentDecl{} -> return ()
466
            traverseDeclExprsM traverseExprM decl
467

468
        traverseModuleItemM :: ModuleItem -> Scoper () ModuleItem
469
        traverseModuleItemM item@Modport{} =
470
            traverseExprsM (scopeExpr >=> traverseExprM) item
471 472
        traverseModuleItemM item@(Instance _ _ x _ _) =
            insertElem x () >> traverseExprsM traverseExprM item
473 474 475 476
        traverseModuleItemM item =
            traverseExprsM traverseExprM item >>=
            traverseLHSsM  traverseLHSM

477
        traverseGenItemM :: GenItem -> Scoper () GenItem
478 479 480 481 482 483 484 485
        traverseGenItemM item@(GenFor (x, _) _ _ _) = do
            -- don't want to be scoped in modports
            insertElem x ()
            item' <- traverseGenItemExprsM traverseExprM item
            removeElem x
            return item'
        traverseGenItemM item =
            traverseGenItemExprsM traverseExprM item
486

487
        traverseStmtM :: Stmt -> Scoper () Stmt
488 489 490 491 492
        traverseStmtM =
            traverseStmtExprsM traverseExprM >=>
            traverseStmtLHSsM  traverseLHSM

        -- used for replacing usages of modports in the module being inlined
493
        modportSubstitutions = concatMap (fst . snd) modportBindings
494 495 496
        lhsReplacements = map (\(x, y) -> (toLHS x, toLHS y)) exprReplacements
        exprReplacements = filter ((/= Nil) . snd) modportSubstitutions
        -- LHSs are replaced using simple substitutions
497
        traverseLHSM :: LHS -> Scoper () LHS
498
        traverseLHSM =
499 500
            fmap replaceLHS .
            embedScopes tagLHS
501
        tagLHS :: Scopes () -> LHS -> LHS
502 503 504 505 506 507 508
        tagLHS scopes lhs
            | lookupElem scopes lhs /= Nothing =
                LHSDot (renamePartLHS lhs) "@"
            | Just portName <- partScopedModportRef $ lhsToExpr lhs =
                LHSIdent portName
            | otherwise =
                traverseSinglyNestedLHSs (tagLHS scopes) lhs
509 510 511 512 513 514
        renamePartLHS :: LHS -> LHS
        renamePartLHS (LHSDot (LHSIdent x) y) =
            if x == partName
                then LHSDot scopedInstanceLHS y
                else LHSDot (LHSIdent x) y
        renamePartLHS lhs = traverseSinglyNestedLHSs renamePartLHS lhs
515 516 517
        replaceLHS :: LHS -> LHS
        replaceLHS (LHSDot lhs "@") = lhs
        replaceLHS (LHSDot (LHSBit lhs elt) field) =
518 519
            case lookup (LHSDot (LHSBit lhs Tag) field) lhsReplacements of
                Just resolved -> replaceLHSArrTag elt resolved
520 521
                Nothing -> LHSDot (replaceLHS $ LHSBit lhs elt) field
        replaceLHS lhs =
522 523
            case lookup lhs lhsReplacements of
                Just lhs' -> lhs'
524
                Nothing -> traverseSinglyNestedLHSs replaceLHS lhs
525 526 527
        replaceLHSArrTag :: Expr -> LHS -> LHS
        replaceLHSArrTag =
            traverseNestedLHSs . (traverseLHSExprs . replaceArrTag)
528
        -- top-level expressions may be modports bound to other modports
529
        traverseExprM :: Expr -> Scoper () Expr
530
        traverseExprM =
531 532
            fmap replaceExpr .
            embedScopes tagExpr
533
        tagExpr :: Scopes () -> Expr -> Expr
534 535 536 537 538 539 540
        tagExpr scopes expr
            | lookupElem scopes expr /= Nothing =
                Dot (renamePartExpr expr) "@"
            | Just portName <- partScopedModportRef expr =
                Ident portName
            | otherwise =
                visitExprsStep (tagExpr scopes) expr
541 542 543 544 545
        renamePartExpr :: Expr -> Expr
        renamePartExpr (Dot (Ident x) y) =
            if x == partName
                then Dot scopedInstanceExpr y
                else Dot (Ident x) y
546
        renamePartExpr expr = visitExprsStep renamePartExpr expr
547 548 549
        replaceExpr :: Expr -> Expr
        replaceExpr (Dot expr "@") = expr
        replaceExpr (Ident x) =
550 551
            case lookup x modportBindings of
                Just (_, m) -> m
552 553 554 555 556 557
                Nothing -> Ident x
        replaceExpr expr =
            replaceExpr' expr
        replaceExpr' :: Expr -> Expr
        replaceExpr' (Dot expr "@") = expr
        replaceExpr' (Dot (Bit expr elt) field) =
558
            case lookup (Dot (Bit expr Tag) field) exprReplacements of
559 560 561
                Just resolved -> replaceArrTag (replaceExpr' elt) resolved
                Nothing -> Dot (replaceExpr' $ Bit expr elt) field
        replaceExpr' (Bit expr elt) =
562
            case lookup (Bit expr Tag) exprReplacements of
563 564 565
                Just resolved -> replaceArrTag (replaceExpr' elt) resolved
                Nothing -> Bit (replaceExpr' expr) (replaceExpr' elt)
        replaceExpr' expr@(Dot Ident{} _) =
566 567
            case lookup expr exprReplacements of
                Just expr' -> expr'
568 569 570 571 572
                Nothing -> visitExprsStep replaceExprAny expr
        replaceExpr' (Ident x) = Ident x
        replaceExpr' expr = replaceExprAny expr
        replaceExprAny :: Expr -> Expr
        replaceExprAny expr =
573 574
            case lookup expr exprReplacements of
                Just expr' -> expr'
575
                Nothing -> visitExprsStep replaceExpr' expr
576 577 578
        replaceArrTag :: Expr -> Expr -> Expr
        replaceArrTag replacement Tag = replacement
        replaceArrTag replacement expr =
579 580 581 582 583 584 585 586 587 588 589 590 591 592
            visitExprsStep (replaceArrTag replacement) expr

        partScopedModportRef :: Expr -> Maybe Identifier
        partScopedModportRef (Dot (Ident x) y) =
            if x == partName && lookup y modportBindings /= Nothing
                then Just y
                else Nothing
        partScopedModportRef _ = Nothing

        visitExprsStep :: (Expr -> Expr) -> Expr -> Expr
        visitExprsStep exprMapper =
            traverseSinglyNestedExprs exprMapper
            . traverseExprTypes (traverseNestedTypes typeMapper)
            where typeMapper = traverseTypeExprs exprMapper
593

594 595 596 597 598
        checkErrMsg :: String -> String
        checkErrMsg exprStr = "inlining instance \"" ++ instanceName
            ++ "\" of " ++ inlineKind ++ " \"" ++ partName
            ++ "\" would make expression \"" ++ exprStr ++ "\" used in \""
            ++ instanceName ++ "\" resolvable when it wasn't previously"
599

600 601 602 603 604 605 606 607
        -- unambiguous reference to the current instance
        scopedInstanceRaw = accessesToExpr $ localAccesses global instanceName
        scopedInstanceExpr =
            if isArray
                then Bit scopedInstanceRaw (Ident loopVar)
                else scopedInstanceRaw
        Just scopedInstanceLHS = exprToLHS scopedInstanceExpr

608 609
        removeModportInstance :: Decl -> Decl
        removeModportInstance (Variable d t x a e) =
610 611 612 613
            if maybeModportBinding == Nothing then
                Variable d t x a e
            else if makeBindingBaseExpr modportE == Nothing then
                CommentDecl $ "removed modport instance " ++ x
614
            else if null modportDims then
615 616 617
                localparam (modportBaseName x) bindingBaseExpr
            else
                localparam (modportBaseName x) $
618
                    BinOp Sub bindingBaseExpr (sliceLo NonIndexed modportDim)
619 620 621 622
            where
                maybeModportBinding = lookup x modportBindings
                Just (_, modportE) = maybeModportBinding
                bindingBaseExpr = Ident $ bindingBaseName ++ x
623 624
                modportDims = a ++ snd (typeRanges t)
                [modportDim] = modportDims
625
        removeModportInstance other = other
626

627 628 629
        removeDeclDir :: Decl -> Decl
        removeDeclDir (Variable _ t x a e) =
            Variable Local t' x a e
630 631 632 633
            where t' = case t of
                    Implicit Unspecified rs ->
                        IntegerVector TLogic Unspecified rs
                    _ -> t
634
        removeDeclDir decl@Net{} =
635
            traverseNetAsVar removeDeclDir decl
636
        removeDeclDir other = other
637

638
        -- capture the lower bound for each modport array binding
639 640
        bindingBaseParams = mapMaybe makeBindingBaseParam modportBindings
        makeBindingBaseParam :: ModportBinding -> Maybe Decl
641
        makeBindingBaseParam (portName, (_, modportE)) =
642 643
            fmap (localparam $ bindingBaseName ++ portName) $
                makeBindingBaseExpr modportE
644
        bindingBaseName = "_bbase_" ++ key ++ "_"
645 646 647 648 649 650 651 652 653 654 655 656
        makeBindingBaseExpr :: Expr -> Maybe Expr
        makeBindingBaseExpr modportE =
            case modportE of
                Dot (Range _ mode range) _ -> Just $ sliceLo mode range
                Range      _ mode range    -> Just $ sliceLo mode range
                Dot (Bit _ idx) _ -> Just idx
                Bit      _ idx    -> Just idx
                _ -> Nothing

        localparam :: Identifier -> Expr -> Decl
        localparam = Param Localparam (Implicit Unspecified [])

657
        paramTmp = "_param_" ++ key ++ "_"
658 659 660 661

        parameterBinds = map makeParameterBind instanceParams
        makeParameterBind :: ParamBinding -> Decl
        makeParameterBind (x, Left t) =
662
            ParamType Localparam (paramTmp ++ x) t
663
        makeParameterBind (x, Right e) =
664
            Param Localparam UnknownType (paramTmp ++ x) e
665

666 667
        overrideParam :: Decl -> Decl
        overrideParam (Param Parameter t x e) =
668
            Param Localparam t x $
669
            case lookup x instanceParams of
670 671
                Nothing -> e
                Just _  -> Ident $ paramTmp ++ x
672
        overrideParam (ParamType Parameter x t) =
673
            ParamType Localparam x $
674
            case lookup x instanceParams of
675 676
                Nothing -> t
                Just _  -> Alias (paramTmp ++ x) []
677 678
        overrideParam other = other

679
        portBindingItem :: PortBinding -> ModuleItem
Zachary Snow committed
680
        portBindingItem (ident, expr) =
681
            if findDeclDir ident == Input
682 683 684 685 686 687 688
                then bind (LHSDot (inj LHSBit LHSIdent) ident) expr
                else bind (toLHS expr) (Dot (inj Bit Ident) ident)
            where
                bind = Assign AssignOptionNone
                inj bit idn = if null ranges
                    then idn instanceName
                    else bit (idn instanceName) (Ident loopVar)
689 690

        declDirs = execWriter $
691
            mapM (collectDeclsM collectDeclDir) items
692 693
        collectDeclDir :: Decl -> Writer (Map.Map Identifier Direction) ()
        collectDeclDir (Variable dir _ ident _ _) =
694 695
            when (dir /= Local) $
                tell $ Map.singleton ident dir
696
        collectDeclDir net@Net{} =
697
            collectNetAsVarM collectDeclDir net
698
        collectDeclDir _ = return ()
699 700 701 702 703 704
        findDeclDir :: Identifier -> Direction
        findDeclDir ident =
            case Map.lookup ident declDirs of
                Nothing -> error $ "could not find decl dir of " ++ ident
                    ++ " among " ++ show declDirs
                Just dir -> dir
705

706 707 708 709
        toLHS :: Expr -> LHS
        toLHS expr =
            case exprToLHS expr of
                Just lhs -> lhs
710 711 712
                Nothing  -> error $ "trying to bind an " ++ inlineKind
                    ++ " output to " ++ show expr ++ " but that can't be an LHS"

713
        -- for instance arrays, a unique identifier to be used as a genvar
714
        loopVar = "_arr_" ++ key
715 716

        isArray = not $ null ranges
717
        [arrayRange@(arrayLeft, arrayRight)] = ranges
718 719

        -- wrap the given item in a generate loop if necessary
720 721
        wrapInstance :: Identifier -> [ModuleItem] -> ModuleItem
        wrapInstance blockName moduleItems =
722 723 724 725 726 727 728 729
            Generate $
            if not isArray then
                [item]
            else
                [ GenModuleItem (Genvar loopVar)
                , GenFor inits cond incr item
                ]
            where
730
                item = GenBlock blockName $ map GenModuleItem moduleItems
731 732 733 734 735 736 737 738 739 740 741 742 743
                inits = (loopVar, arrayLeft)
                cond = endianCondExpr arrayRange
                    (BinOp Ge (Ident loopVar) arrayRight)
                    (BinOp Le (Ident loopVar) arrayRight)
                incr = (loopVar, AsgnOp Add, step)
                step = endianCondExpr arrayRange
                    (UniOp UniSub $ RawNum 1) (RawNum 1)

-- used for modport array binding offset placeholders
pattern Tag :: Expr
pattern Tag = Ident "%"

modportBaseName :: Identifier -> Identifier
744
modportBaseName = (++) "_mbase_"
745 746 747 748 749 750 751 752 753 754 755 756 757

-- the dimensions of interface instance arrays are encoded as synthetic modports
-- during inlining, enabling subsequent modport bindings to implicitly use the
-- bounds of the interface instance array when the bounds are unspecified
pattern InstArrName :: Identifier
pattern InstArrName = "~instance_array_dimensions~"
pattern InstArrVal :: Expr -> Expr -> [ModportDecl]
pattern InstArrVal l r = [(Local, "l", l), (Local, "r", r)]
pattern InstArrKey :: Expr -> Expr
pattern InstArrKey expr = Dot (Bit expr (RawNum 0)) InstArrName
pattern InstArrEncoded :: Expr -> Expr -> ModuleItem
pattern InstArrEncoded l r = Modport InstArrName (InstArrVal l r)

758 759 760 761
-- encoding for normal declarations in the current module
pattern DeclVal :: [ModportDecl]
pattern DeclVal = [(Local, "~decl~", Nil)]

762 763 764 765 766 767 768 769 770 771
-- encoding for the interface type of an interface instantiation
pattern InterfaceTypeName :: Identifier
pattern InterfaceTypeName = "~interface_type~"
pattern InterfaceTypeVal :: Identifier -> [ModportDecl]
pattern InterfaceTypeVal x = [(Local, "~interface~type~", Ident x)]
pattern InterfaceTypeKey :: Expr -> Expr
pattern InterfaceTypeKey e = Dot e InterfaceTypeName
pattern InterfaceTypeEncoded :: Identifier -> ModuleItem
pattern InterfaceTypeEncoded x = Modport InterfaceTypeName (InterfaceTypeVal x)

772 773 774 775 776
-- determines the lower bound for the given slice
sliceLo :: PartSelectMode -> Range -> Expr
sliceLo NonIndexed (l, r) = endianCondExpr (l, r) r l
sliceLo IndexedPlus (base, _) = base
sliceLo IndexedMinus (base, len) = BinOp Add (BinOp Sub base len) (RawNum 1)
777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847

-- check for cases where an expression in an inlined part only resolves after
-- inlining, potentially hiding a design error
checkBeforeInline :: Scopes a -> Identifier -> [ModuleItem]
    -> (String -> String) -> [ModuleItem]
checkBeforeInline global partName items checkErrMsg =
    evalScoper $ scopeModuleItems scoper partName $ items
    where
        scoper = scopeModuleItem
            checkDecl checkModuleItem checkGenItem checkStmt

        checkDecl :: Decl -> Scoper () Decl
        checkDecl decl = do
            case decl of
                Variable  _ _ x _ _ -> insertElem x ()
                Net   _ _ _ _ x _ _ -> insertElem x ()
                Param     _ _ x _   -> insertElem x ()
                ParamType _   x _   -> insertElem x ()
                CommentDecl{} -> return ()
            traverseDeclExprsM checkExpr decl

        checkModuleItem :: ModuleItem -> Scoper () ModuleItem
        checkModuleItem item@(Instance _ _ x _ _) =
            insertElem x () >> traverseExprsM checkExpr item
        checkModuleItem item =
            traverseExprsM checkExpr item >>=
            traverseLHSsM  checkLHS

        checkGenItem :: GenItem -> Scoper () GenItem
        checkGenItem = traverseGenItemExprsM checkExpr

        checkStmt :: Stmt -> Scoper () Stmt
        checkStmt =
            traverseStmtExprsM checkExpr >=>
            traverseStmtLHSsM  checkLHS

        checkExpr :: Expr -> Scoper () Expr
        checkExpr = embedScopes checkExprResolutionId

        checkLHS :: LHS -> Scoper () LHS
        checkLHS = embedScopes checkLHSResolutionId

        checkLHSResolutionId :: Scopes () -> LHS -> LHS
        checkLHSResolutionId local lhs = checkExprResolution local expr lhs
            where expr = lhsToExpr lhs

        checkExprResolutionId :: Scopes () -> Expr -> Expr
        checkExprResolutionId local expr = checkExprResolution local expr expr

        -- error if the given expression resolves globally but not locally
        checkExprResolution :: Scopes () -> Expr -> a -> a
        checkExprResolution local expr =
            if exprResolves global expr && not (anyPrefixResolves local expr)
                then scopedError local $ checkErrMsg $ show expr
                else id

        -- check if hierarchical prefix of an expr exists in the given scope
        anyPrefixResolves :: Scopes () -> Expr -> Bool
        anyPrefixResolves local expr =
            exprResolves local expr ||
            case expr of
                Dot inner _ -> anyPrefixResolves local inner
                Bit inner _ -> anyPrefixResolves local inner
                _ -> False

        -- check if expr exists in the given scope
        exprResolves :: Scopes a -> Expr -> Bool
        exprResolves local (Ident x) =
            isJust (lookupElem local x) || isLoopVar local x
        exprResolves local expr =
            isJust (lookupElem local expr)