diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d6675..a2643e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ explicitly providing a leading `parameter` or `localparam` marker * Use UTF-8 on all platforms and tolerate transcoding failures, enabling reading files encoding using Latin-1 with special characters in comments +* Support for non-ANSI style port declarations where the port declaration is + separate from the corresponding net or variable declaration ## v0.0.8 diff --git a/src/Convert.hs b/src/Convert.hs index 62e236e..fa593ae 100644 --- a/src/Convert.hs +++ b/src/Convert.hs @@ -38,6 +38,7 @@ import qualified Convert.NamedBlock import qualified Convert.Package import qualified Convert.ParamNoDefault import qualified Convert.ParamType +import qualified Convert.PortDecl import qualified Convert.RemoveComments import qualified Convert.ResolveBindings import qualified Convert.Simplify @@ -107,6 +108,7 @@ initialPhases selectExclude = , selectExclude Job.Assert Convert.Assertion.convert , selectExclude Job.Always Convert.AlwaysKW.convert , Convert.Package.convert + , Convert.PortDecl.convert , Convert.ParamNoDefault.convert , Convert.ResolveBindings.convert , Convert.UnnamedGenBlock.convert diff --git a/src/Convert/PortDecl.hs b/src/Convert/PortDecl.hs new file mode 100644 index 0000000..0561f10 --- /dev/null +++ b/src/Convert/PortDecl.hs @@ -0,0 +1,127 @@ +{- sv2v + - Author: Zachary Snow + - + - Conversion for checking and standardizing port declarations + - + - Non-ANSI style port declarations can be split into two separate declarations. + - Section 23.2.2.1 of IEEE 1800-2017 defines rules for determining the + - resulting details of such declarations. This conversion is part of the + - initial phases to avoid requiring that downstream conversions handle these + - unusual otherwise conflicting declarations. + - + - To avoid creating spurious conflicts for redeclared ports, this conversion is + - also responsible for defaulting variable ports to `logic`. + -} + +module Convert.PortDecl (convert) where + +import Data.List (intercalate, (\\)) +import Data.Maybe (mapMaybe) + +import Convert.Traverse +import Language.SystemVerilog.AST + +convert :: [AST] -> [AST] +convert = map $ traverseDescriptions traverseDescription + +traverseDescription :: Description -> Description +traverseDescription (Part attrs extern kw liftetime name ports items) = + Part attrs extern kw liftetime name ports items' + where items' = convertPorts name ports items +traverseDescription other = other + +convertPorts :: Identifier -> [Identifier] -> [ModuleItem] -> [ModuleItem] +convertPorts name ports items + | not (null extraPorts) = + error $ "declared ports " ++ intercalate ", " extraPorts + ++ " are not in the port list of " ++ name + | otherwise = + map traverseItem items + where + portDecls = mapMaybe (findDecl True ) items + dataDecls = mapMaybe (findDecl False) items + extraPorts = map fst portDecls \\ ports + + -- rewrite a declaration if necessary + traverseItem :: ModuleItem -> ModuleItem + traverseItem (MIPackageItem (Decl decl)) + | Variable d _ x _ e <- decl = rewrite decl (combineIdent x) d x e + | Net d _ _ _ x _ e <- decl = rewrite decl (combineIdent x) d x e + | otherwise = MIPackageItem $ Decl decl + traverseItem other = other + + -- produce the combined declaration for a port, if it has one + combineIdent :: Identifier -> Maybe Decl + combineIdent x = do + portDecl <- lookup x portDecls + dataDecl <- lookup x dataDecls + Just $ combineDecls portDecl dataDecl + +-- given helpfully extracted information, update the given declaration +rewrite :: Decl -> Maybe Decl -> Direction -> Identifier -> Expr -> ModuleItem +-- implicitly-typed ports default to `logic` in SystemVerilog +rewrite (Variable d (Implicit sg rs) x a e) Nothing _ _ _ = + MIPackageItem $ Decl $ Variable d (IntegerVector TLogic sg rs) x a e +-- not a relevant port declaration +rewrite decl Nothing _ _ _ = + MIPackageItem $ Decl decl +-- turn the non-ANSI style port and data declarations into fully-specified ports +-- and optional continuous assignments, respectively +rewrite _ (Just combined) d x e + | d /= Local = + MIPackageItem $ Decl combined + | e /= Nil = + Assign AssignOptionNone (LHSIdent x) e + | otherwise = + MIPackageItem $ Decl $ CommentDecl $ "combined with " ++ x + +-- combine the two declarations defining a non-ANSI style port +combineDecls :: Decl -> Decl -> Decl +combineDecls portDecl dataDecl + | eP /= Nil = + mismatch "invalid initialization at port declaration" + | aP /= aD = + mismatch "different unpacked dimensions" + | rsP /= rsD = + mismatch "different packed dimensions" + | otherwise = + base (tf rsD) ident aD Nil + where + + -- signed if *either* declaration is marked signed + sg = if sgP == Signed || sgD == Signed + then Signed + else Unspecified + + -- the port cannot have a variable or net type + Implicit sgP rsP = case tP of + Implicit{} -> tP + _ -> mismatch "redeclaration" + + -- pull out the base type, signedness, and packed dimensions + (tf, sgD, rsD) = case tD of + Implicit s r -> (IntegerVector TLogic sg, s, r ) + IntegerVector k s r -> (IntegerVector k sg, s, r ) + IntegerAtom k s -> (\[] -> IntegerAtom k s , s, []) + -- TODO: other basic types may be worth supporting here + _ -> mismatch "non-ANSI port declaration with unsupported data type" + + -- extract the core components of each declaration + Variable dir tP ident aP eP = portDecl + (base, tD, aD) = case dataDecl of + Variable Local t _ a _ -> (Variable dir, t, a) + Net Local n s t _ a _ -> (Net dir n s, t, a) + _ -> undefined -- not possible given findDecl + + -- helpful error message utility + mismatch :: String -> a + mismatch msg = error $ "declarations `" ++ p portDecl ++ "` and `" + ++ p dataDecl ++ "` are incompatible due to " ++ msg + where p = init . show + +-- used to build independent lists of port and data declarations +findDecl :: Bool -> ModuleItem -> Maybe (Identifier, Decl) +findDecl isPort (MIPackageItem (Decl decl)) + | Variable d _ x _ _ <- decl, (d /= Local) == isPort = Just (x, decl) + | Net d _ _ _ x _ _ <- decl, (d /= Local) == isPort = Just (x, decl) +findDecl _ _ = Nothing diff --git a/src/Language/SystemVerilog/Parser/ParseDecl.hs b/src/Language/SystemVerilog/Parser/ParseDecl.hs index 9c490f1..a3d908e 100644 --- a/src/Language/SystemVerilog/Parser/ParseDecl.hs +++ b/src/Language/SystemVerilog/Parser/ParseDecl.hs @@ -381,9 +381,7 @@ parseDTsAsDecls backupDir mode l0 = (rs , l6) = takeRanges l5 (tps, l7) = takeTrips l6 initReason base = von dir t - t = case (dir, tf rs) of - (Output, Implicit sg _) -> IntegerVector TLogic sg rs - (_, typ) -> typ + t = tf rs decls = traceComment l0 : map (\(x, a, e) -> base x a e) tps diff --git a/sv2v.cabal b/sv2v.cabal index ceaa45b..eb34cca 100644 --- a/sv2v.cabal +++ b/sv2v.cabal @@ -90,6 +90,7 @@ executable sv2v Convert.Package Convert.ParamNoDefault Convert.ParamType + Convert.PortDecl Convert.RemoveComments Convert.ResolveBindings Convert.Scoper diff --git a/test/core/non_ansi_port_decl.sv b/test/core/non_ansi_port_decl.sv new file mode 100644 index 0000000..f460bae --- /dev/null +++ b/test/core/non_ansi_port_decl.sv @@ -0,0 +1,39 @@ +`define TEST_SG(port_sg, data_sg, exp_u, exp_s) \ + `TEST_RAW(o_``port_sg``_``data_sg``_one, port_sg , data_sg , [ 0:0], exp_u) \ + `TEST_RAW(o_``port_sg``_``data_sg``_two, port_sg [1:0], data_sg [1:0] , [ 1:0], exp_u) \ + `TEST_RAW(o_``port_sg``_``data_sg``_int, port_sg , integer data_sg, [31:0], exp_s) + +`define TEST \ + `TEST_SG( , , 0, 1) \ + `TEST_SG( , signed, 1, 1) \ + `TEST_SG( , unsigned, 0, 0) \ + `TEST_SG( signed, , 1, 1) \ + `TEST_SG( signed, signed, 1, 1) \ + `TEST_SG( signed, unsigned, 1, 0) \ + `TEST_SG(unsigned, , 0, 1) \ + `TEST_SG(unsigned, signed, 1, 1) \ + `TEST_SG(unsigned, unsigned, 0, 0) + +`define TEST_RAW(name, a, b, c, d) name, + +module top( + `TEST + foo +); + output wire foo; + assign foo = 1; + +`undef TEST_RAW +`define TEST_RAW(name, port, data, range, exp) \ + `ifdef REF \ + output wire range name; \ + initial #1 $display(`"name %b %b`", name, 1'b``exp); \ + `else \ + output port name; \ + wire data name; \ + initial #1 $display(`"name %b %b`", name, name < 0); \ + `endif \ + assign name = 1'sb1; \ + + `TEST +endmodule diff --git a/test/core/non_ansi_port_decl.v b/test/core/non_ansi_port_decl.v new file mode 100644 index 0000000..b18ecca --- /dev/null +++ b/test/core/non_ansi_port_decl.v @@ -0,0 +1,2 @@ +`define REF +`include "non_ansi_port_decl.sv" diff --git a/test/core/non_ansi_port_decl_order.sv b/test/core/non_ansi_port_decl_order.sv new file mode 100644 index 0000000..ca6529c --- /dev/null +++ b/test/core/non_ansi_port_decl_order.sv @@ -0,0 +1,12 @@ +module mod(a, b, c, d); + input a; + wire a; + output b; + reg b; + always @* + b = ~a; + input [7:0] c; + wire integer d = 10 + c; + logic [7:0] c; + output d; +endmodule diff --git a/test/core/non_ansi_port_decl_order.v b/test/core/non_ansi_port_decl_order.v new file mode 100644 index 0000000..4019b1a --- /dev/null +++ b/test/core/non_ansi_port_decl_order.v @@ -0,0 +1,9 @@ +module mod(a, b, c, d); + input wire a; + output reg b; + always @* + b = ~a; + input wire [7:0] c; + output wire [31:0] d; + assign d = 10 + c; +endmodule diff --git a/test/core/non_ansi_port_decl_order_tb.v b/test/core/non_ansi_port_decl_order_tb.v new file mode 100644 index 0000000..db5e14b --- /dev/null +++ b/test/core/non_ansi_port_decl_order_tb.v @@ -0,0 +1,14 @@ +module top; + reg a; + wire b; + reg [7:0] c; + wire [31:0] d; + mod m(a, b, c, d); + initial begin + $monitor("%2d %b %b %b %b", $time, a, b, c, d); + #1 a = 0; + #1 a = 1; + for (c = 0; c < 10; c = c + 1) + #1; + end +endmodule diff --git a/test/error/port_init_early.sv b/test/error/port_init_early.sv new file mode 100644 index 0000000..e72d9bc --- /dev/null +++ b/test/error/port_init_early.sv @@ -0,0 +1,5 @@ +// pattern: declarations `output a = 1` and `logic a` are incompatible due to invalid initialization at port declaration +module top(a); + output a = 1; + logic a; +endmodule diff --git a/test/error/port_not_in_header.sv b/test/error/port_not_in_header.sv new file mode 100644 index 0000000..42838b6 --- /dev/null +++ b/test/error/port_not_in_header.sv @@ -0,0 +1,4 @@ +// pattern: declared ports w, z are not in the port list of top +module top(x, y); + output w, x, y, z; +endmodule diff --git a/test/error/port_packed_first.sv b/test/error/port_packed_first.sv new file mode 100644 index 0000000..50c342e --- /dev/null +++ b/test/error/port_packed_first.sv @@ -0,0 +1,5 @@ +// pattern: declarations `output \[1:0\] x` and `wire x` are incompatible due to different packed dimensions +module top(x); + output [1:0] x; + wire x; +endmodule diff --git a/test/error/port_packed_second.sv b/test/error/port_packed_second.sv new file mode 100644 index 0000000..93abb4a --- /dev/null +++ b/test/error/port_packed_second.sv @@ -0,0 +1,5 @@ +// pattern: declarations `output x` and `wire \[1:0\] x` are incompatible due to different packed dimensions +module top(x); + output x; + wire [1:0] x; +endmodule diff --git a/test/error/port_redeclare.sv b/test/error/port_redeclare.sv new file mode 100644 index 0000000..7f78735 --- /dev/null +++ b/test/error/port_redeclare.sv @@ -0,0 +1,5 @@ +// pattern: declarations `output logic a` and `logic a` are incompatible due to redeclaration +module top(a); + output logic a; + logic a; +endmodule diff --git a/test/error/port_unpacked_first.sv b/test/error/port_unpacked_first.sv new file mode 100644 index 0000000..2f5006d --- /dev/null +++ b/test/error/port_unpacked_first.sv @@ -0,0 +1,5 @@ +// pattern: declarations `output x \[1:0\]` and `wire x` are incompatible due to different unpacked dimensions +module top(x); + output x [1:0]; + wire x; +endmodule diff --git a/test/error/port_unpacked_second.sv b/test/error/port_unpacked_second.sv new file mode 100644 index 0000000..5b7e922 --- /dev/null +++ b/test/error/port_unpacked_second.sv @@ -0,0 +1,5 @@ +// pattern: declarations `output x` and `wire x \[1:0\]` are incompatible due to different unpacked dimensions +module top(x); + output x; + wire x [1:0]; +endmodule