mirror of https://github.com/zachjs/sv2v.git
generate explicit sensitivity lists when necessary
This commit is contained in:
parent
9eceb55673
commit
e778a671e1
|
|
@ -29,6 +29,8 @@
|
|||
intentional width-extending operations such as `+ 0` and `* 1`
|
||||
* Fixed forced conversion to `reg` of data sensed in an edge-controlled
|
||||
procedural assignment
|
||||
* `always_comb` and `always_latch` now generate explicit sensitivity lists where
|
||||
necessary because of calls to functions which reference non-local data
|
||||
|
||||
## v0.0.9
|
||||
|
||||
|
|
|
|||
|
|
@ -3,24 +3,224 @@
|
|||
-
|
||||
- Conversion for `always_latch`, `always_comb`, and `always_ff`
|
||||
-
|
||||
- `always_latch` -> `always @*`
|
||||
- `always_comb` -> `always @*`
|
||||
- `always_ff` -> `always`
|
||||
- `always_comb` and `always_latch` become `always @*`, or produce an explicit
|
||||
- sensitivity list if they need to pick up sensitivities from the functions
|
||||
- they call. `always_ff` simply becomes `always`.
|
||||
-
|
||||
- TODO: `always_comb` blocks do not run at time zero
|
||||
-}
|
||||
|
||||
module Convert.AlwaysKW (convert) where
|
||||
|
||||
import Control.Monad.State.Strict
|
||||
import Control.Monad.Writer.Strict
|
||||
import Data.Maybe (fromMaybe, mapMaybe)
|
||||
|
||||
import Convert.Scoper
|
||||
import Convert.Traverse
|
||||
import Language.SystemVerilog.AST
|
||||
|
||||
convert :: [AST] -> [AST]
|
||||
convert = map $ traverseDescriptions $ traverseModuleItems replaceAlwaysKW
|
||||
convert = map $ traverseDescriptions traverseDescription
|
||||
|
||||
replaceAlwaysKW :: ModuleItem -> ModuleItem
|
||||
replaceAlwaysKW (AlwaysC AlwaysLatch stmt) =
|
||||
AlwaysC Always $ Timing (Event EventStar) stmt
|
||||
replaceAlwaysKW (AlwaysC AlwaysComb stmt) =
|
||||
AlwaysC Always $ Timing (Event EventStar) stmt
|
||||
replaceAlwaysKW (AlwaysC AlwaysFF stmt) =
|
||||
AlwaysC Always stmt
|
||||
replaceAlwaysKW other = other
|
||||
traverseDescription :: Description -> Description
|
||||
traverseDescription description@Part{} =
|
||||
evalState (evalScoperT $ scopePart op description) mempty
|
||||
where op = traverseModuleItem >=> scoper
|
||||
traverseDescription description = description
|
||||
|
||||
type SC = ScoperT Kind (State (Any, [Expr]))
|
||||
|
||||
type PortDir = (Identifier, Direction)
|
||||
|
||||
data Kind
|
||||
= Const Expr
|
||||
| Var
|
||||
| Proc [Expr] [PortDir]
|
||||
deriving Eq
|
||||
|
||||
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
|
||||
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
|
||||
traverseModuleItem (AlwaysC AlwaysLatch stmt) = do
|
||||
e <- fmap toEvent $ findNonLocals $ Initial stmt
|
||||
return $ AlwaysC Always $ Timing (Event e) stmt
|
||||
traverseModuleItem (AlwaysC AlwaysComb stmt) = do
|
||||
e <- fmap toEvent $ findNonLocals $ Initial stmt
|
||||
return $ AlwaysC Always $ Timing (Event e) stmt
|
||||
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
|
||||
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 (Net 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
|
||||
lift $ put mempty
|
||||
_ <- scoper item
|
||||
(anys, exprs) <- lift get
|
||||
let nonLocals = mapMaybe (longestStaticPrefix scopes) exprs
|
||||
return (getAny anys, nonLocals)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
`define ALWAYS(trigger) always_comb
|
||||
`include "always_prefix.vh"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
`define ALWAYS(trigger) always @(trigger)
|
||||
`include "always_prefix.vh"
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
module mod(
|
||||
input wire [3:0] idx,
|
||||
input wire [14:0] data
|
||||
);
|
||||
localparam Y = 2;
|
||||
localparam X = 10000;
|
||||
|
||||
`define TEST(expr, trigger, extra) \
|
||||
if (1) begin \
|
||||
function automatic f; \
|
||||
input reg ignored; \
|
||||
localparam X = Y + 1; \
|
||||
localparam THREE = X; \
|
||||
f = expr; \
|
||||
endfunction \
|
||||
`ALWAYS(trigger) begin : blk \
|
||||
localparam ZERO = 0; \
|
||||
$display(`"%2d %b expr trigger`", \
|
||||
$time, f(ZERO) extra); \
|
||||
end \
|
||||
end
|
||||
|
||||
`define TEST_SIMPLE(expr) `TEST(expr, expr, )
|
||||
|
||||
`TEST_SIMPLE(data)
|
||||
`TEST_SIMPLE(data[1])
|
||||
`TEST_SIMPLE(data[4])
|
||||
`TEST_SIMPLE(data[4:1])
|
||||
`TEST_SIMPLE(data[10:1])
|
||||
|
||||
localparam ONE = 1;
|
||||
parameter FOUR = 4;
|
||||
`TEST_SIMPLE(data[ONE])
|
||||
`TEST_SIMPLE(data[FOUR])
|
||||
`TEST_SIMPLE(data[FOUR:ONE])
|
||||
|
||||
`TEST(data[idx], data or idx, )
|
||||
`TEST(data[idx+:2], data or idx, )
|
||||
|
||||
`TEST(data[THREE], data[3], )
|
||||
`TEST(data[ignored], data, )
|
||||
`TEST(data[THREE], data[0] or data[3], & data[0])
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
module top;
|
||||
reg [3:0] idx;
|
||||
reg [14:0] data;
|
||||
mod m(idx, data);
|
||||
initial begin
|
||||
#1 data = 0;
|
||||
#1 idx = 0;
|
||||
#1 data[0] = 1;
|
||||
#1 data[4] = 1;
|
||||
#1 data[5] = 1;
|
||||
#1 data[3] = 1;
|
||||
#1 data[8] = 1;
|
||||
#1 idx = 0;
|
||||
#1 idx = 1;
|
||||
#1 data[0] = 0;
|
||||
#1 data[0] = 1;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
module mod(
|
||||
input wire inp1, inp2,
|
||||
output reg out1, out2, out3, out4, out5, out6, out7, out8, out9, outA, outB
|
||||
);
|
||||
localparam ZERO = 0;
|
||||
|
||||
task automatic t;
|
||||
output reg o;
|
||||
o = inp1;
|
||||
endtask
|
||||
|
||||
function automatic flop;
|
||||
input reg i;
|
||||
flop = i;
|
||||
endfunction
|
||||
function automatic flip;
|
||||
input reg i;
|
||||
flip = flop(~i);
|
||||
endfunction
|
||||
|
||||
function automatic f;
|
||||
input reg i; // ignored
|
||||
f = inp2;
|
||||
endfunction
|
||||
function automatic g;
|
||||
input reg inp1; // ignored
|
||||
g = f(ZERO) & mod.inp1;
|
||||
endfunction
|
||||
|
||||
function void u;
|
||||
output reg o;
|
||||
o = inp1;
|
||||
endfunction
|
||||
|
||||
task automatic asgn;
|
||||
output reg o;
|
||||
input reg i;
|
||||
o = i;
|
||||
endtask
|
||||
|
||||
always_comb
|
||||
t(out1);
|
||||
always_comb
|
||||
out2 = f(ZERO);
|
||||
always_comb
|
||||
out3 = f(ZERO) & inp1;
|
||||
always_comb
|
||||
out4 = g(ZERO);
|
||||
always_comb
|
||||
out5 = flip(inp1);
|
||||
always_comb begin
|
||||
reg x;
|
||||
x = g(ZERO);
|
||||
out6 = x;
|
||||
end
|
||||
always_comb
|
||||
u(out7);
|
||||
parameter ONE = 1;
|
||||
if (ONE)
|
||||
always_comb begin
|
||||
asgn(out8, flip(inp1));
|
||||
out9 = f(ZERO);
|
||||
end
|
||||
always_latch
|
||||
if (inp1)
|
||||
outA = f(ZERO);
|
||||
|
||||
struct packed {
|
||||
logic x, y;
|
||||
} s;
|
||||
assign s = {inp1, inp2};
|
||||
function automatic h;
|
||||
input reg i; // ignored
|
||||
h = s.y;
|
||||
endfunction
|
||||
always_comb
|
||||
outB = h(ZERO);
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
module mod(
|
||||
input wire inp1, inp2,
|
||||
output reg out1, out2, out3, out4, out5, out6, out7, out8, out9, outA, outB
|
||||
);
|
||||
localparam ZERO = 0;
|
||||
|
||||
task automatic t;
|
||||
output reg o;
|
||||
o = inp1;
|
||||
endtask
|
||||
|
||||
function automatic flop;
|
||||
input reg i;
|
||||
flop = i;
|
||||
endfunction
|
||||
function automatic flip;
|
||||
input reg i;
|
||||
flip = flop(~i);
|
||||
endfunction
|
||||
|
||||
function automatic f;
|
||||
input reg i; // ignored
|
||||
f = inp2;
|
||||
endfunction
|
||||
function automatic g;
|
||||
input reg i; // ignored
|
||||
g = f(ZERO) & inp1;
|
||||
endfunction
|
||||
|
||||
task automatic u;
|
||||
output reg o;
|
||||
o = inp1;
|
||||
endtask
|
||||
|
||||
task automatic asgn;
|
||||
output reg o;
|
||||
input reg i;
|
||||
o = i;
|
||||
endtask
|
||||
|
||||
always @*
|
||||
t(out1);
|
||||
always @(inp2)
|
||||
out2 = f(ZERO);
|
||||
always @(inp1, inp2)
|
||||
out3 = f(ZERO) & inp1;
|
||||
always @(inp1, inp2)
|
||||
out4 = g(ZERO);
|
||||
always @*
|
||||
out5 = flip(inp1);
|
||||
always @(inp1, inp2) begin : blk
|
||||
reg x;
|
||||
x = g(ZERO);
|
||||
out6 = x;
|
||||
end
|
||||
always @(inp1)
|
||||
u(out7);
|
||||
parameter ONE = 1;
|
||||
if (ONE)
|
||||
always @(inp1, inp2) begin
|
||||
asgn(out8, flip(inp1));
|
||||
out9 = f(ZERO);
|
||||
end
|
||||
always @(inp1, inp2)
|
||||
if (inp1)
|
||||
outA = f(ZERO);
|
||||
|
||||
wire [1:0] s;
|
||||
assign s = {inp1, inp2};
|
||||
function automatic h;
|
||||
input reg i; // ignored
|
||||
h = s[0];
|
||||
endfunction
|
||||
always @(s[0])
|
||||
outB = h(ZERO);
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
module top;
|
||||
reg inp1, inp2;
|
||||
wire out1, out2, out3, out4, out5, out6, out7, out8, out9, outA, outB;
|
||||
mod m(inp1, inp2, out1, out2, out3, out4, out5, out6, out7, out8, out9, outA, outB);
|
||||
initial begin
|
||||
$monitor(inp1, inp2,
|
||||
out1, out2, out3, out4, out5, out6, out7, out8, out9, outA, outB);
|
||||
repeat (2) begin
|
||||
#1 inp1 = 0;
|
||||
#1 inp2 = 0;
|
||||
#1 inp2 = 1;
|
||||
|
||||
#1 inp1 = 1;
|
||||
#1 inp2 = 0;
|
||||
#1 inp2 = 1;
|
||||
|
||||
#1 inp2 = 0;
|
||||
#1 inp1 = 0;
|
||||
#1 inp1 = 1;
|
||||
|
||||
#1 inp2 = 1;
|
||||
#1 inp1 = 0;
|
||||
#1 inp1 = 1;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue