diff --git a/CHANGELOG.md b/CHANGELOG.md index fc9fb90..b33041b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Added elaboration for accesses to fields of struct constants, which can substantially improve performance on some designs +* Added constant folding for comparisons involving string literals ## v0.0.10 diff --git a/src/Convert/Cast.hs b/src/Convert/Cast.hs index da1c23e..9c4c46d 100644 --- a/src/Convert/Cast.hs +++ b/src/Convert/Cast.hs @@ -99,7 +99,7 @@ traverseStmtM stmt = do traverseExprM :: Expr -> SC Expr traverseExprM (Cast (Left (IntegerVector kw sg rs)) value) | kw /= TBit = do - value' <- traverseExprM value + value' <- fmap simplify $ traverseExprM value size' <- traverseExprM size convertCastM size' value' signed where @@ -121,8 +121,9 @@ convertCastM (Number size) (Number value) signed = return $ Number $ numberCast signed (fromIntegral size') value where Just size' = numberToInteger size +convertCastM size@Number{} (String str) signed = + convertCastM size (stringToNumber str) signed convertCastM size value signed = do - value' <- traverseExprM value sizeUsesLocalVars <- embedScopes usesLocalVars size inProcedure <- withinProcedureM if not sizeUsesLocalVars || not inProcedure then do @@ -135,12 +136,12 @@ convertCastM size value signed = do else do details <- lookupElemM name when (details == Nothing) (injectTopItem item) - return $ Call (Ident name) (Args [value'] []) + return $ Call (Ident name) (Args [value] []) else do name <- castDeclName 0 insertElem name () useVar <- withinStmt - injectDecl $ castDecl useVar name value' size signed + injectDecl $ castDecl useVar name value size signed return $ Ident name -- checks if a cast size references any vars not defined at the top level scope diff --git a/src/Convert/ExprUtils.hs b/src/Convert/ExprUtils.hs index c4df4bc..a5ccf2b 100644 --- a/src/Convert/ExprUtils.hs +++ b/src/Convert/ExprUtils.hs @@ -13,9 +13,11 @@ module Convert.ExprUtils , endianCondExpr , endianCondRange , dimensionsSize + , stringToNumber ) where -import Data.Bits (shiftL, shiftR) +import Data.Bits ((.&.), (.|.), shiftL, shiftR) +import Data.Char (ord) import Convert.Traverse import Language.SystemVerilog.AST @@ -36,18 +38,13 @@ simplifyStep (UniOp LogNot (BinOp Ne a b)) = BinOp Eq a b simplifyStep (UniOp UniSub (UniOp UniSub e)) = e simplifyStep (UniOp UniSub (BinOp Sub e1 e2)) = BinOp Sub e2 e1 -simplifyStep (Concat (Number n1 : Number n2 : rest)) = - simplifyStep $ Concat $ Number n : rest - where n = n1 <> n2 - simplifyStep (Concat [Number (Decimal size _ value)]) = Number $ Decimal size False value simplifyStep (Concat [Number (Based size _ base value kinds)]) = Number $ Based size False base value kinds simplifyStep (Concat [e@Stream{}]) = e -simplifyStep (Concat [e@Concat{}]) = e simplifyStep (Concat [e@Repeat{}]) = e -simplifyStep (Concat es) = Concat $ filter (/= Concat []) es +simplifyStep (Concat es) = Concat $ flattenConcat es simplifyStep (Repeat (Dec 0) _) = Concat [] simplifyStep (Repeat (Dec 1) es) = Concat es simplifyStep (Mux (Number n) e1 e2) = @@ -73,6 +70,16 @@ simplifyStep e@(BinOp _ (BinOp _ Number{} Number{}) Number{}) = e simplifyStep (BinOp op e1 e2) = simplifyBinOp op e1 e2 simplifyStep other = other +-- flatten and coalesce concatenations +flattenConcat :: [Expr] -> [Expr] +flattenConcat (Number n1 : Number n2 : es) = + flattenConcat $ Number (n1 <> n2) : es +flattenConcat (Concat es1 : es2) = + flattenConcat $ es1 ++ es2 +flattenConcat (e : es) = + e : flattenConcat es +flattenConcat [] = [] + simplifyBinOp :: BinOp -> Expr -> Expr -> Expr @@ -107,10 +114,50 @@ simplifyBinOp Add (BinOp Sub n1@Number{} e) n2@Number{} = BinOp Sub (BinOp Add n1 n2) e simplifyBinOp Ge (BinOp Sub e (Dec 1)) (Dec 0) = BinOp Ge e (toDec 1) -simplifyBinOp ShiftAL (Dec x) (Dec y) = toDec $ shiftL x (fromIntegral y) -simplifyBinOp ShiftAR (Dec x) (Dec y) = toDec $ shiftR x (fromIntegral y) -simplifyBinOp ShiftL (Dec x) (Dec y) = toDec $ shiftL x (fromIntegral y) -simplifyBinOp ShiftR (Dec x) (Dec y) = toDec $ shiftR x (fromIntegral y) +-- simplify bit shifts of decimal literals +simplifyBinOp op (Dec x) (Number yRaw) + | ShiftAL <- op = decShift shiftL + | ShiftAR <- op = decShift shiftR + | ShiftL <- op = decShift shiftL + | ShiftR <- op = decShift shiftR + where + decShift shifter = + case numberToInteger yRaw of + Just y -> toDec $ shifter x (fromIntegral y) + Nothing -> constantFold undefined Div undefined 0 + +-- simply comparisons with string literals +simplifyBinOp op (Number n) (String s) | isCmpOp op = + simplifyBinOp op (Number n) (sizeStringAs s n) +simplifyBinOp op (String s) (Number n) | isCmpOp op = + simplifyBinOp op (sizeStringAs s n) (Number n) +simplifyBinOp op (String s1) (String s2) | isCmpOp op = + simplifyBinOp op (stringToNumber s1) (stringToNumber s2) + +-- simply basic arithmetic comparisons +simplifyBinOp op (Number n1) (Number n2) + | Eq <- op = cmp (==) + | Ne <- op = cmp (/=) + | Lt <- op = cmp (<) + | Le <- op = cmp (<=) + | Gt <- op = cmp (>) + | Ge <- op = cmp (>=) + where + cmp :: (Integer -> Integer -> Bool) -> Expr + cmp folder = + case (numberToInteger n1', numberToInteger n2') of + (Just i1, Just i2) -> bool $ folder i1 i2 + _ -> BinOp op (Number n1') (Number n2') + sg = numberIsSigned n1 && numberIsSigned n2 + sz = fromIntegral $ max (numberBitLength n1) (numberBitLength n2) + n1' = numberCast sg sz n1 + n2' = numberCast sg sz n2 + +-- simply comparisons with unbased unsized literals +simplifyBinOp op (Number n) (ConvertedUU sz v k) | isCmpOp op = + simplifyBinOp op (Number n) (uuExtend sz v k) +simplifyBinOp op (ConvertedUU sz v k) (Number n) | isCmpOp op = + simplifyBinOp op (uuExtend sz v k) (Number n) simplifyBinOp op e1 e2 = case (e1, e2) of @@ -142,6 +189,8 @@ constantFold _ Gt x y = bool $ x > y constantFold _ Ge x y = bool $ x >= y constantFold _ Lt x y = bool $ x < y constantFold _ Le x y = bool $ x <= y +constantFold _ BitAnd x y = toDec $ x .&. y +constantFold _ BitOr x y = toDec $ x .|. y constantFold fallback _ _ _ = fallback @@ -224,3 +273,49 @@ pattern SizedRange expr = (BinOp Sub expr (RawNum 1), RawNum 0) -- similar to the above pattern, we assume E >= 1 for any range like [0:E-1] pattern RevSzRange :: Expr -> Range pattern RevSzRange expr = (RawNum 0, BinOp Sub expr (RawNum 1)) + +-- convert a string to decimal number +stringToNumber :: String -> Expr +stringToNumber str = + Number $ Decimal size False value + where + size = 8 * length str + value = stringToInteger str + +-- convert a string to big integer +stringToInteger :: String -> Integer +stringToInteger [] = 0 +stringToInteger (x : xs) = + fromIntegral (ord x) + (256 :: Integer) * stringToInteger xs + +-- cast string to number at least as big as the width of the given number +sizeStringAs :: String -> Number -> Expr +sizeStringAs str num = + Cast (Left typ) (stringToNumber str) + where + typ = IntegerVector TReg Unspecified [(RawNum size, RawNum 1)] + size = max strSize numSize + strSize = fromIntegral $ 8 * length str + numSize = numberBitLength num + +-- excludes wildcard and strict comparison operators +isCmpOp :: BinOp -> Bool +isCmpOp Eq = True +isCmpOp Ne = True +isCmpOp Lt = True +isCmpOp Le = True +isCmpOp Gt = True +isCmpOp Ge = True +isCmpOp _ = False + +-- sign extend a converted unbased unsized literal into a based number +uuExtend :: Integer -> Integer -> Integer -> Expr +uuExtend sz v k = + Number $ + numberCast False (fromIntegral sz) $ + Based 1 True Hex v k + +pattern ConvertedUU :: Integer -> Integer -> Integer -> Expr +pattern ConvertedUU sz v k <- Repeat + (RawNum sz) + [Number (Based 1 True Binary v k)] diff --git a/test/core/constexpr.sv b/test/core/constexpr.sv new file mode 100644 index 0000000..94d4d65 --- /dev/null +++ b/test/core/constexpr.sv @@ -0,0 +1,46 @@ +// This verifies that sv2v can evaluate certain constant expressions by +// producing iverilog-incompatible code if the expression cannot be simplified +// or is evaluated incorrectly. +`define ASSERT_TRUE(expr) if (expr) begin end else begin shortreal x; end +`define ASSERT_FALSE(expr) if (expr) begin shortreal x; end + +module top; + `ASSERT_TRUE(1) + `ASSERT_FALSE(0) + + `ASSERT_TRUE("inv" == "inv") + `ASSERT_TRUE(32'("inv") == "inv") + `ASSERT_TRUE("inv" == 32'("inv")) + `ASSERT_TRUE(24'("inv") == "inv") + `ASSERT_TRUE("inv" == 24'("inv")) + + `ASSERT_FALSE("invv" == "inv") + `ASSERT_FALSE("0inv" == "inv") + `ASSERT_TRUE("invv" != "inv") + `ASSERT_TRUE("0inv" != "inv") + + `ASSERT_TRUE(24'("inv0") == "inv") + `ASSERT_TRUE(24'("0inv") != "inv") + `ASSERT_FALSE("inv" == 0) + `ASSERT_FALSE("inv" == '0) + `ASSERT_FALSE('0 == "inv") + `ASSERT_FALSE("inv" == 1'b0) + `ASSERT_FALSE("inv" == 2'd0) + `ASSERT_FALSE("inv" == 1'sb0) + + `ASSERT_TRUE(1'sb0 < 1'd1) + `ASSERT_TRUE(1'sb0 <= 1'd0) + `ASSERT_FALSE(1'sb0 > 1'd1) + `ASSERT_TRUE(1'sb0 >= 1'd0) + + `ASSERT_TRUE((1 | 2) == 3) + `ASSERT_TRUE((13 & 7) == 5) + + `ASSERT_TRUE((1 << 1) == 2) + `ASSERT_TRUE((3 >> 1) == 1) + `ASSERT_TRUE((1 <<< 1) == 2) + `ASSERT_TRUE((3 >>> 1) == 1) + + `ASSERT_TRUE(5'{4'hF, 3'{1'b1, 1'b1}} == 27) + `ASSERT_TRUE(5'{{{1'b1, 1'b1}, {1'b1, 1'b1}}} == 15) +endmodule diff --git a/test/core/constexpr.v b/test/core/constexpr.v new file mode 100644 index 0000000..dabb9b7 --- /dev/null +++ b/test/core/constexpr.v @@ -0,0 +1 @@ +module top; endmodule diff --git a/test/error/size_cast_xpr_lit.sv b/test/error/size_cast_xpr_lit.sv new file mode 100644 index 0000000..3897eda --- /dev/null +++ b/test/error/size_cast_xpr_lit.sv @@ -0,0 +1,5 @@ +// pattern: size cast width 'shxxxxxxxx is not an integer +// location: size_cast_xpr_lit.sv:4:13 +module top; + initial $display((1 << 'x)'(2)); +endmodule diff --git a/test/error/size_cast_xpr_var.sv b/test/error/size_cast_xpr_var.sv new file mode 100644 index 0000000..80d753d --- /dev/null +++ b/test/error/size_cast_xpr_var.sv @@ -0,0 +1,6 @@ +// pattern: size cast width 'shxxxxxxxx is not an integer +// location: size_cast_xpr_var.sv:5:13 +module top; + wire x = 0; + initial $display((1 << 'x)'(x)); +endmodule