sv2v/src/Convert/Logic.hs

213 lines
9.0 KiB
Haskell

{- sv2v
- Author: Zachary Snow <zach@zachjs.com>
-
- Conversion from `logic` to `wire` or `reg`
-
- We convert a module-level logic to a reg if it is assigned to in an always or
- initial block. Other module-level logics become wires. All other logics
- (i.e., in a function) become regs.
-
- Parameters and localparams with integer vector types become implicit.
-
- The struct conversion and Verilog-2005's lack of permissive net vs. variable
- resolution leads to some interesting special cases for this conversion, as
- parts of a struct may be used as a variable, while other parts may be used as
- a net.
-
- 1) If a reg, or a portion thereof, is assigned by a continuous assignment
- item, then that assignment is converted to a procedural assignment within an
- added `always_comb` item.
-
- 2) If a reg, or a portion thereof, is bound to an output port, then that
- binding is replaced by a temporary net declaration, and a procedural
- assignment is added which updates the reg to the value of the new net.
-}
module Convert.Logic (convert) where
import Control.Monad (when)
import Control.Monad.Writer.Strict
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Convert.Scoper
import Convert.Traverse
import Language.SystemVerilog.AST
type Ports = Map.Map Identifier [(Identifier, Direction)]
type Location = [Identifier]
type Locations = Set.Set Location
type ST = ScoperT Type (Writer Locations)
convert :: [AST] -> [AST]
convert =
traverseFiles
(collectDescriptionsM collectPortsM)
(traverseDescriptions . convertDescription)
where
collectPortsM :: Description -> Writer Ports ()
collectPortsM orig@(Part _ _ _ _ name portNames _) =
tell $ Map.singleton name ports
where
ports = zip portNames (map lookupDir portNames)
dirs = execWriter $ collectModuleItemsM collectDeclDirsM orig
lookupDir :: Identifier -> Direction
lookupDir portName =
case lookup portName dirs of
Just dir -> dir
Nothing -> Inout
collectPortsM _ = return ()
collectDeclDirsM :: ModuleItem -> Writer [(Identifier, Direction)] ()
collectDeclDirsM (MIPackageItem (Decl (Variable dir _ ident _ _))) =
when (dir /= Local) $ tell [(ident, dir)]
collectDeclDirsM (MIPackageItem (Decl (Net dir _ _ _ ident _ _))) =
when (dir /= Local) $ tell [(ident, dir)]
collectDeclDirsM _ = return ()
convertDescription :: Ports -> Description -> Description
convertDescription ports description =
evalScoper $ scopeModule conScoper description
where
locations = execWriter $ evalScoperT $ scopePart locScoper description
-- write down which vars are procedurally assigned
locScoper = scopeModuleItem traverseDeclM return return traverseStmtM
-- rewrite reg continuous assignments and output port connections
conScoper = scopeModuleItem
(rewriteDeclM locations) (traverseModuleItemM ports) return return
traverseModuleItemM :: Ports -> ModuleItem -> Scoper Type ModuleItem
traverseModuleItemM ports = embedScopes $ traverseModuleItem ports
traverseModuleItem :: Ports -> Scopes Type -> ModuleItem -> ModuleItem
traverseModuleItem ports scopes =
fixModuleItem
where
isReg :: LHS -> Bool
isReg =
or . execWriter . collectNestedLHSsM isReg'
where
isRegType :: Type -> Bool
isRegType (IntegerVector TReg _ _) = True
isRegType _ = False
isReg' :: LHS -> Writer [Bool] ()
isReg' lhs =
case lookupElem scopes lhs of
Just (_, _, t) -> tell [isRegType t]
_ -> tell [False]
always_comb = AlwaysC Always . Timing (Event EventStar)
fixModuleItem :: ModuleItem -> ModuleItem
-- rewrite bad continuous assignments to use procedural assignments
fixModuleItem (Assign AssignOptionNone lhs expr) =
if not (isReg lhs)
then Assign AssignOptionNone lhs expr
else
Generate $ map GenModuleItem
[ MIPackageItem $ Decl decl
, Assign AssignOptionNone (LHSIdent x) expr
, always_comb $ Asgn AsgnOpEq Nothing lhs (Ident x)
]
where
decl = Net Local TWire DefaultStrength t x [] Nil
t = Implicit Unspecified [r]
r = (DimsFn FnBits $ Right $ lhsToExpr lhs, RawNum 1)
x = "sv2v_tmp_" ++ shortHash (lhs, expr)
-- rewrite port bindings to use temporary nets where necessary
fixModuleItem (Instance moduleName params instanceName rs bindings) =
if null newItems
then Instance moduleName params instanceName rs bindings
else Generate $ map GenModuleItem $
comment : newItems ++
[Instance moduleName params instanceName rs bindings']
where
comment = MIPackageItem $ Decl $ CommentDecl
"rewrote reg-to-output bindings"
(bindings', newItemsList) = unzip $ map fixBinding bindings
newItems = concat newItemsList
fixBinding :: PortBinding -> (PortBinding, [ModuleItem])
fixBinding (portName, expr) =
if not outputBound || not usesReg
then ((portName, expr), [])
else ((portName, tmpExpr), items)
where
outputBound = portDir == Just Output
usesReg = Just True == fmap isReg (exprToLHS expr)
portDir = maybeModulePorts >>= lookup portName
tmp = "sv2v_tmp_" ++ instanceName ++ "_" ++ portName
tmpExpr = Ident tmp
decl = Net Local TWire DefaultStrength t tmp [] Nil
t = Implicit Unspecified [r]
r = (DimsFn FnBits $ Right expr, RawNum 1)
items =
[ MIPackageItem $ Decl decl
, always_comb $ Asgn AsgnOpEq Nothing lhs tmpExpr]
Just lhs = exprToLHS expr
maybeModulePorts = Map.lookup moduleName ports
fixModuleItem other = other
traverseDeclM :: Decl -> ST Decl
traverseDeclM decl@(Variable _ t x _ _) =
insertElem x t >> return decl
traverseDeclM decl@(Net _ _ _ t x _ _) =
insertElem x t >> return decl
traverseDeclM decl = return decl
rewriteDeclM :: Locations -> Decl -> Scoper Type Decl
rewriteDeclM locations (Variable d (IntegerVector TLogic sg rs) x a e) = do
accesses <- localAccessesM x
let location = map accessName accesses
let usedAsReg = Set.member location locations
blockLogic <- withinProcedureM
if blockLogic || usedAsReg || e /= Nil
then do
let d' = if d == Inout then Output else d
let t' = IntegerVector TReg sg rs
insertElem accesses t'
return $ Variable d' t' x a e
else do
let t' = Implicit sg rs
insertElem accesses t'
return $ Net d TWire DefaultStrength t' x a e
rewriteDeclM locations decl@(Variable d t x a e) = do
inProcedure <- withinProcedureM
case (d, t, inProcedure) of
-- Reinterpret `input reg` module ports as `input logic`. We still don't
-- treat `logic` and `reg` as the same keyword, as specifying `reg`
-- explicitly is typically expected to flow downstream.
(Input, IntegerVector TReg sg rs, False) ->
rewriteDeclM locations $ Variable Input t' x a e
where t' = IntegerVector TLogic sg rs
_ -> insertElem x t >> return decl
rewriteDeclM _ (Net d n s (IntegerVector _ sg rs) x a e) =
insertElem x t >> return (Net d n s t x a e)
where t = Implicit sg rs
rewriteDeclM _ decl@(Net _ _ _ t x _ _) =
insertElem x t >> return decl
rewriteDeclM _ (Param s (IntegerVector _ sg []) x e) =
return $ Param s (Implicit sg [(zero, zero)]) x e
where zero = RawNum 0
rewriteDeclM _ (Param s (IntegerVector _ sg rs) x e) =
return $ Param s (Implicit sg rs) x e
rewriteDeclM _ decl = return decl
traverseStmtM :: Stmt -> ST Stmt
traverseStmtM (Asgn op Just{} lhs expr) =
-- ignore the timing LHSs
traverseStmtM $ Asgn op Nothing lhs expr
traverseStmtM stmt@(Subroutine (Ident f) (Args (_ : Ident x : _) [])) =
when (f == "$readmemh" || f == "$readmemb") (collectLHSM $ LHSIdent x)
>> return stmt
traverseStmtM stmt =
collectStmtLHSsM (collectNestedLHSsM collectLHSM) stmt
>> return stmt
collectLHSM :: LHS -> ST ()
collectLHSM lhs = do
details <- lookupElemM lhs
case details of
Just (accesses, _, _) ->
lift $ tell $ Set.singleton location
where location = map accessName accesses
Nothing -> return ()