From da2d4117f2a1b2b3c28d3bd0bec47f69d72ab669 Mon Sep 17 00:00:00 2001 From: Zachary Snow Date: Fri, 20 Aug 2021 18:08:14 -0600 Subject: [PATCH] specialized parsing for parameter port lists This adds support for typed valued parameters declared in parameter port lists without explicitly providing a leading `parameter` or `localparam` marker. --- CHANGELOG.md | 2 + src/Language/SystemVerilog/Parser/Parse.y | 36 +++--- .../SystemVerilog/Parser/ParseDecl.hs | 120 ++++++++++++++++-- test/core/param_list.sv | 35 +++++ test/core/param_list.v | 26 ++++ test/core/param_list_unpacked.sv | 19 +++ test/core/param_list_unpacked.v | 14 ++ test/error/parameter_list_not_type.sv | 2 + 8 files changed, 226 insertions(+), 28 deletions(-) create mode 100644 test/core/param_list.sv create mode 100644 test/core/param_list.v create mode 100644 test/core/param_list_unpacked.sv create mode 100644 test/core/param_list_unpacked.v create mode 100644 test/error/parameter_list_not_type.sv diff --git a/CHANGELOG.md b/CHANGELOG.md index cb94a5e..e1a7f53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ * Fix parsing of sized ports with implicit directions * Ensure arrays used in nested ternary expressions are properly flattened * Support parameters which use a type-of as the data type +* Support typed valued parameters declared in parameter port lists without + explicitly providing a leading `parameter` or `localparam` marker ## v0.0.8 diff --git a/src/Language/SystemVerilog/Parser/Parse.y b/src/Language/SystemVerilog/Parser/Parse.y index 2adf06d..bde5c48 100644 --- a/src/Language/SystemVerilog/Parser/Parse.y +++ b/src/Language/SystemVerilog/Parser/Parse.y @@ -570,21 +570,9 @@ PackageImportDeclaration :: { [ModuleItem] } Params :: { [ModuleItem] } : PIParams { map (MIPackageItem . Decl) $1 } PIParams :: { [Decl] } - : {- empty -} { [] } - | "#" "(" ")" { [] } - | "#" "(" ParamsFollow { $3 } -ParamsFollow :: { [Decl] } - : ParamAsgn ParamsEnd { $1 } - | ParamAsgn "," ParamsFollow { $1 ++ $3 } - | ParamsDecl { $1 } -ParamsDecl :: { [Decl] } - : ModuleParameterDecl(ParamsEnd) { $1 } - | ModuleParameterDecl(",") ParamsDecl { $1 ++ $2 } -ParamAsgn :: { [Decl] } - : DeclTrace Identifier "=" Expr { [$1, Param Parameter (Implicit Unspecified []) $2 $4] } -ParamsEnd - : ")" {} - | "," ")" {} + : {- empty -} { [] } + | "#" "(" ")" { [] } + | "#" "(" ParamDeclTokens(")") { parseDTsAsParams $3 } PortDecls :: { ([Identifier], [ModuleItem]) } : "(" PortDeclTokens(")") { parseDTsAsPortDecls $2 } @@ -658,8 +646,9 @@ DeclToken :: { DeclToken } DTDelim(delim) :: { DeclToken } : delim { DTEnd (tokenPosition $1) (head $ tokenString $1) } DeclTokenAsgn :: { DeclToken } - : "=" opt(DelayOrEvent) Expr { DTAsgn (tokenPosition $1) AsgnOpEq $2 $3 } - | AsgnBinOpP Expr { uncurry DTAsgn $1 Nothing $2 } + : "=" DelayOrEvent Expr { DTAsgn (tokenPosition $1) AsgnOpEq (Just $2) $3 } + | "=" Expr { DTAsgn (tokenPosition $1) AsgnOpEq Nothing $2 } + | AsgnBinOpP Expr { uncurry DTAsgn $1 Nothing $2 } | "<=" opt(DelayOrEvent) Expr { DTAsgn (tokenPosition $1) AsgnOpNonBlocking $2 $3 } PortDeclTokens(delim) :: { [DeclToken] } : DeclTokensBase(PortDeclTokens(delim), delim) { $1 } @@ -673,6 +662,16 @@ ModuleDeclTokens(delim) :: { [DeclToken] } GenericInterfaceDecl :: { [DeclToken] } : "interface" IdentifierP { [DTType (tokenPosition $1) (\Unspecified -> InterfaceT "" ""), uncurry DTIdent $2] } +ParamDeclTokens(delim) :: { [DeclToken] } + : DeclTokensBase(ParamDeclTokens(delim), delim) { $1 } + | DeclTokenAsgn "," DTDelim(delim) { [$1, DTComma (tokenPosition $2), $3] } + | ParamDeclToken ParamDeclTokens(delim) { $1 ++ $2 } + | ParamDeclToken DTDelim(delim) { $1 ++ [$2] } +ParamDeclToken :: { [DeclToken] } + : "=" PartialTypeP { [DTTypeAsgn (tokenPosition $1), uncurry DTType $2] } + | "type" IdentifierP { [DTTypeDecl (tokenPosition $1), uncurry DTIdent $2] } + | ParameterDeclKW { [uncurry DTParamKW $1] } + VariablePortIdentifiers :: { [(Identifier, Expr)] } : VariablePortIdentifier { [$1] } | VariablePortIdentifiers "," VariablePortIdentifier { $1 ++ [$3] } @@ -1130,9 +1129,6 @@ DeclOrStmt :: { ([Decl], [Stmt]) } : DeclTokens(";") { parseDTsAsDeclOrStmt $1 } | ParameterDecl(";") { ($1, []) } -ModuleParameterDecl(delim) :: { [Decl] } - : ParameterDecl(delim) { $1 } - | DeclTrace "type" TypeAsgns delim { $1 : map (uncurry $ ParamType Parameter) $3 } ParameterDecl(delim) :: { [Decl] } : ParameterDeclKW DeclAsgns delim { makeParamDecls $1 (Implicit Unspecified []) $2 } | ParameterDeclKW ParamType DeclAsgns delim { makeParamDecls $1 $2 $3 } diff --git a/src/Language/SystemVerilog/Parser/ParseDecl.hs b/src/Language/SystemVerilog/Parser/ParseDecl.hs index ca7f9d1..9c490f1 100644 --- a/src/Language/SystemVerilog/Parser/ParseDecl.hs +++ b/src/Language/SystemVerilog/Parser/ParseDecl.hs @@ -29,6 +29,12 @@ - portion of a for loop also allows for declarations and assignments, and so a - similar interface is provided for this case. - + - Parameter port lists allow the omission of the `parameter` and `localparam` + - keywords, and so require special handling in the same vein as other + - declarations. However, this custom parsing is not necessary for parameter + - declarations outside of parameter port lists because the leading keyword is + - otherwise required. + - - This parser is very liberal, and so accepts some syntactically invalid files. - In the future, we may add some basic type-checking to complain about - malformed input files. However, we generally assume that users have tested @@ -43,6 +49,7 @@ module Language.SystemVerilog.Parser.ParseDecl , parseDTsAsDecl , parseDTsAsDeclOrStmt , parseDTsAsDeclsOrAsgns +, parseDTsAsParams ) where import Data.List (findIndex, partition, uncons) @@ -73,17 +80,22 @@ data DeclToken | DTLifetime Position Lifetime | DTAttr Position Attr | DTEnd Position Char + | DTParamKW Position ParamScope + | DTTypeDecl Position + | DTTypeAsgn Position -- [PUBLIC]: parser for module port declarations, including interface ports -- Example: `input foo, bar, One inst` parseDTsAsPortDecls :: [DeclToken] -> ([Identifier], [ModuleItem]) parseDTsAsPortDecls = parseDTsAsPortDecls' . dropTrailingComma - where - dropTrailingComma :: [DeclToken] -> [DeclToken] - dropTrailingComma [] = [] - dropTrailingComma [DTComma{}, end@DTEnd{}] = [end] - dropTrailingComma (tok : toks) = tok : dropTrailingComma toks + +-- internal utility to drop a single trailing comma in port or parameter lists, +-- which we allow for compatibility with Yosys +dropTrailingComma :: [DeclToken] -> [DeclToken] +dropTrailingComma [] = [] +dropTrailingComma [DTComma{}, end@DTEnd{}] = [end] +dropTrailingComma (tok : toks) = tok : dropTrailingComma toks -- internal parseDTsAsPortDecls after the removal of an optional trailing comma parseDTsAsPortDecls' :: [DeclToken] -> ([Identifier], [ModuleItem]) @@ -333,7 +345,6 @@ takeLHSStep curr (DTRange _ m r : toks) = takeLHSStep (LHSRange curr m r) toks takeLHSStep curr (DTDot _ x : toks) = takeLHSStep (LHSDot curr x ) toks takeLHSStep lhs toks = (lhs, toks) - type DeclBase = Identifier -> [Range] -> Expr -> Decl type Triplet = (Identifier, [Range], Expr) @@ -369,15 +380,17 @@ parseDTsAsDecls backupDir mode l0 = (tf , l5) = takeType l4 (rs , l6) = takeRanges l5 (tps, l7) = takeTrips l6 initReason - pos = tokPos $ head l0 base = von dir t t = case (dir, tf rs) of (Output, Implicit sg _) -> IntegerVector TLogic sg rs (_, typ) -> typ decls = - CommentDecl ("Trace: " ++ show pos) : + traceComment l0 : map (\(x, a, e) -> base x a e) tps +traceComment :: [DeclToken] -> Decl +traceComment = CommentDecl . ("Trace: " ++) . show . tokPos . head + hasDriveStrength :: DeclToken -> Bool hasDriveStrength (DTNet _ _ DriveStrength{}) = True hasDriveStrength _ = False @@ -413,6 +426,85 @@ tripLookahead l0 = (_, l2) = takeRanges l1 (_, l3) = takeAsgn l2 "" + +-- [PUBLIC]: parser for parameter lists in headers of modules, interfaces, etc. +parseDTsAsParams :: [DeclToken] -> [Decl] +parseDTsAsParams = parseDTsAsParams' Parameter . dropTrailingComma + +-- internal variant of the above, used recursively after the optional trailing +-- comma has been removed +parseDTsAsParams' :: ParamScope -> [DeclToken] -> [Decl] +parseDTsAsParams' _ [] = [] +parseDTsAsParams' prevParamScope l0 = + nextStep paramScope l2 + where + nextStep = if isParamType + then parseDTsAsParamType + else parseDTsAsParam + (paramScope , l1) = takeParamScope l0 prevParamScope + (isParamType, l2) = takeParamType l1 + +-- parse one or more regular parameter declarations at the beginning of the decl +-- token list before continuing on to subsequent declarations +parseDTsAsParam :: ParamScope -> [DeclToken] -> [Decl] +parseDTsAsParam paramScope l0 = + traceComment l0 : + map base tps ++ + parseDTsAsParams' paramScope l3 + where + (tfO, l1) = takeType l0 + (rsO, l2) = takeRanges l1 + (tps, l3) = takeTrips l2 "" + base :: Triplet -> Decl + (tf, rs) = typeRanges $ tfO rsO -- for packing tolerance + base (x, a, e) = Param paramScope (tf $ a ++ rs) x e + +-- parse a single type parameter declaration at the beginning of the decl token +-- list before continuing on to subsequent declarations +parseDTsAsParamType :: ParamScope -> [DeclToken] -> [Decl] +parseDTsAsParamType paramScope l0 = + traceComment l0 : + ParamType paramScope x t : + nextStep paramScope l3 + where + nextStep = if typeAsgnLookahead l3 + then parseDTsAsParamType + else parseDTsAsParams' + (x, l1) = takeIdent l0 + (t, l2) = takeTypeAsgn l1 + l3 = takeCommaOrEnd l2 + +-- take the optional default value assignment for a type parameter declaration; +-- note that aliases are parsed as regular assignments to avoid conflicts, and +-- so are converted to types when encountered +takeTypeAsgn :: [DeclToken] -> (Type, [DeclToken]) +takeTypeAsgn (DTTypeAsgn _ : l0) = + (tf rs, l2) + where + (tf, l1) = takeType l0 + (rs, l2) = takeRanges l1 +takeTypeAsgn (DTAsgn pos AsgnOpEq Nothing expr : toks) = + case exprToType expr of + Nothing -> parseError pos "unexpected non-type parameter assignment" + Just t -> (t, toks) +takeTypeAsgn toks = (UnknownType, toks) + +-- check whether the front of the tokens could plausibly form a type parameter +-- declaration and optional assignment; type parameter declaration assignments +-- can only be "escaped" using an explicit non-type parameter/localparam keyword +typeAsgnLookahead :: [DeclToken] -> Bool +typeAsgnLookahead l0 = + not (null l0) && + -- every type assignment *must* begin with an identifier + isIdent (head l0) && + -- expecting to see a comma or the ending token after the identifier and + -- optional assignment + isCommaOrEnd (head l2) + where + (_, l1) = takeIdent l0 + (_, l2) = takeTypeAsgn l1 + + takeDir :: [DeclToken] -> Direction -> (Direction, [DeclToken]) takeDir (DTDir _ dir : rest) _ = (dir, rest) takeDir rest dir = (dir, rest) @@ -523,6 +615,15 @@ takeIdent (DTIdent _ x : rest) = (x, rest) takeIdent tokens = parseError (head tokens) "expected identifier" +takeParamScope :: [DeclToken] -> ParamScope -> (ParamScope, [DeclToken]) +takeParamScope (DTParamKW _ psc : toks) _ = (psc, toks) +takeParamScope toks psc = (psc, toks) + +takeParamType :: [DeclToken] -> (Bool, [DeclToken]) +takeParamType (DTTypeDecl _ : toks) = (True , toks) +takeParamType toks = (False, toks) + + isAttr :: DeclToken -> Bool isAttr DTAttr{} = True isAttr _ = False @@ -573,6 +674,9 @@ tokPos (DTSigning p _) = p tokPos (DTLifetime p _) = p tokPos (DTAttr p _) = p tokPos (DTEnd p _) = p +tokPos (DTParamKW p _) = p +tokPos (DTTypeDecl p) = p +tokPos (DTTypeAsgn p) = p class Loc t where parseError :: t -> String -> a diff --git a/test/core/param_list.sv b/test/core/param_list.sv new file mode 100644 index 0000000..d6c49a0 --- /dev/null +++ b/test/core/param_list.sv @@ -0,0 +1,35 @@ +typedef integer integer_t; +typedef shortint shortint_t; +typedef byte byte_t; + +localparam value_a = 1; +localparam value_b = 2; +localparam value_c = 3; + +`define DUMP_V(X) initial $display(`"V: X %b %0d %0d`", X, X, $bits(X)); +`define DUMP_T(X) initial begin : \dump``X \ + localparam X x = '1; \ + $display(`"T: X %0d %b`", $bits(X), x); \ + end + +module mod #( + localparam integer_t LV1 = value_a, LV2 = value_b, + parameter type PT1 = logic, + integer PV1 = 100, + parameter type(PV1) PV2 = PV1 + value_c, + parameter type PT2 = byte_t, + shortint_t PV3 = 4, + localparam LV3 = 5, + type LT1 = byte_t, LT2 = shortint_t +); + `DUMP_V(LV1) `DUMP_V(LV2) `DUMP_V(LV3) + `DUMP_T(LT1) `DUMP_T(LT2) + `DUMP_V(PV1) `DUMP_V(PV2) `DUMP_V(PV3) + `DUMP_T(PT1) `DUMP_T(PT2) +endmodule + +module top; + mod m1(); + mod #(byte, 7, 10, logic [3:0], 5) m2(); + mod #(reg [8:0], 8, 11, integer, 6) m3(); +endmodule diff --git a/test/core/param_list.v b/test/core/param_list.v new file mode 100644 index 0000000..6c0c39e --- /dev/null +++ b/test/core/param_list.v @@ -0,0 +1,26 @@ +`define DUMP_V(X) initial $display(`"V: X %b %0d %0d`", X, X, $bits(X)); +`define DUMP_T(X) initial begin : \dump``X \ + localparam [X - 1:0] x = 1'sb1; \ + $display(`"T: X %0d %b`", X, x); \ + end + +module mod; + localparam integer LV1 = 1, LV2 = 2; + parameter PT1 = 1; + parameter integer PV1 = 100; + parameter integer PV2 = PV1 + 3; + parameter PT2 = 8; + parameter [15:0] PV3 = 4; + localparam LV3 = 5; + localparam LT1 = 8, LT2 = 16; + `DUMP_V(LV1) `DUMP_V(LV2) `DUMP_V(LV3) + `DUMP_T(LT1) `DUMP_T(LT2) + `DUMP_V(PV1) `DUMP_V(PV2) `DUMP_V(PV3) + `DUMP_T(PT1) `DUMP_T(PT2) +endmodule + +module top; + mod m1(); + mod #(8, 7, 10, 4, 5) m2(); + mod #(9, 8, 11, 32, 6) m3(); +endmodule diff --git a/test/core/param_list_unpacked.sv b/test/core/param_list_unpacked.sv new file mode 100644 index 0000000..079e6c3 --- /dev/null +++ b/test/core/param_list_unpacked.sv @@ -0,0 +1,19 @@ +module fibA #( + parameter integer N = 1, + parameter integer W [2] = '{ 0, 1 } +); + initial $display("fibA(%0d) = %0d", N, W[0]); + if (N < 11) + fibA #(N + 1, '{ W[1], W[0] + W[1] }) f(); +endmodule +module fibB; + parameter integer N = 1; + parameter integer W [2] = '{ 0, 1 }; + initial $display("fibB(%0d) = %0d", N, W[0]); + if (N < 11) + fibB #(N + 1, '{ W[1], W[0] + W[1] }) f(); +endmodule +module top; + fibA fA(); + fibB fB(); +endmodule diff --git a/test/core/param_list_unpacked.v b/test/core/param_list_unpacked.v new file mode 100644 index 0000000..0e3b09c --- /dev/null +++ b/test/core/param_list_unpacked.v @@ -0,0 +1,14 @@ +module fib #( + parameter VARIANT = "", + parameter N = 1, + parameter W0 = 0, + parameter W1 = 1 +); + initial $display("fib%s(%0d) = %0d", VARIANT, N, W0); + if (N < 11) + fib #(VARIANT, N + 1, W1, W0 + W1) f(); +endmodule +module top; + fib #("A") fA(); + fib #("B") fB(); +endmodule diff --git a/test/error/parameter_list_not_type.sv b/test/error/parameter_list_not_type.sv new file mode 100644 index 0000000..2a7d049 --- /dev/null +++ b/test/error/parameter_list_not_type.sv @@ -0,0 +1,2 @@ +// pattern: parameter_list_not_type\.sv:2:31: Parse error: unexpected non-type parameter assignment +module top #(parameter type X = 1); endmodule