Commit 306d7133 by Zachary Snow

refactor sizing and truncation of integer literals

- use iverilog's -gstrict-expr-width throughout test suite
- add warnings for excess bits or padding zeroes in number literals
- add new --oversized-numbers parameter to retain support for unsized
  numbers wider than 32 bits
- localized use of oversized numbers to new truncate test suite which
  verifies the behavior of both modes, and compares against the known
  behavior of iverilog
parent 5e5ddca4
## Unreleased
* Explicitly-sized number literals with non-zero bits exceeding the given width
(e.g., `1'b11`, `3'sd8`, `2'o7`) are truncated and produce a warning, rather
than yielding a cryptic error
* Unsized number literals exceeding the maximum width of 32 bits (e.g.,
`'h1_ffff_ffff`, `4294967296`) are truncated and produce a warning, rather
than being silently extended
* Support for unsized number literals exceeding the standard-imposed 32-bit
limit can be re-enabled with `--oversized-numbers`
* Number literals with leading zeroes which extend beyond the width of the
literal (e.g., `1'b01`, `'h0_FFFF_FFFF`) now produce a warning
* Non-positive integer size casts are now detected and forbidden
* Negative indices in struct pattern literals are now detected and forbidden
......
......@@ -101,6 +101,8 @@ Conversion:
'adjacent' to create a .v file next to each input;
use a path ending in .v to write to a file
Other:
--oversized-numbers Disable standard-imposed 32-bit limit on unsized
number literals (e.g., 'h1_ffff_ffff, 4294967296)
--help Display help message
--version Print version information
--numeric-version Print just the version number
......
......@@ -43,6 +43,7 @@ data Job = Job
, verbose :: Bool
, write :: Write
, writeRaw :: String
, oversizedNumbers :: Bool
} deriving (Typeable, Data)
version :: String
......@@ -71,13 +72,17 @@ defaultJob = Job
&= help ("How to write output; default is 'stdout'; use 'adjacent' to"
++ " create a .v file next to each input; use a path ending in .v"
++ " to write to a file")
, oversizedNumbers = nam_ "oversized-numbers"
&= help ("Disable standard-imposed 32-bit limit on unsized number"
++ " literals (e.g., 'h1_ffff_ffff, 4294967296)")
&= groupname "Other"
}
&= program "sv2v"
&= summary ("sv2v " ++ version)
&= details [ "sv2v converts SystemVerilog to Verilog."
, "More info: https://github.com/zachjs/sv2v"
, "(C) 2019-2021 Zachary Snow, 2011-2015 Tom Hawkins" ]
&= helpArg [explicit, name "help", groupname "Other"]
&= helpArg [explicit, name "help"]
&= versionArg [explicit, name "version"]
&= verbosityArgs [ignore] [ignore]
where
......
{-# LANGUAGE TupleSections #-}
{- sv2v
- Author: Zachary Snow <zach@zachjs.com>
-
......@@ -22,36 +23,127 @@ import Data.Char (digitToInt, intToDigit, toLower)
import Data.List (elemIndex)
import Text.Read (readMaybe)
{-# NOINLINE parseNumber #-}
parseNumber :: Bool -> String -> (Number, String)
parseNumber oversizedNumbers =
(parseNormalized oversizedNumbers) . normalizeNumber
-- normalize the number first, making everything lowercase and removing
-- visual niceties like spaces and underscores
parseNumber :: String -> Number
parseNumber = parseNumber' . map toLower . filter (not . isPad)
normalizeNumber :: String -> String
normalizeNumber = map toLower . filter (not . isPad)
where isPad = flip elem "_ \n\t"
parseNumber' :: String -> Number
parseNumber' "'0" = UnbasedUnsized Bit0
parseNumber' "'1" = UnbasedUnsized Bit1
parseNumber' "'x" = UnbasedUnsized BitX
parseNumber' "'z" = UnbasedUnsized BitZ
parseNumber' str =
-- truncate the given decimal number literal, if necessary
validateDecimal :: Bool -> String -> Int -> Bool -> Integer -> (Number, String)
validateDecimal oversizedNumbers str sz sg v
| sz < -32 && oversizedNumbers =
valid $ Decimal sz sg v
| sz < -32 =
addTruncateMessage str $
if sg && v' > widthMask 31
-- avoid zero-pad on signed decimals
then Based (-32) sg Hex v' 0
else Decimal (-32) sg v'
| sz > 0 && v > widthMask sz =
addTruncateMessage str $
Decimal sz sg v'
| otherwise =
valid $ Decimal sz sg v
where
v' = v .&. widthMask truncWidth
truncWidth = if sz < -32 then -32 else sz
-- produce a warning message describing the applied truncation
addTruncateMessage :: String -> Number -> (Number, String)
addTruncateMessage orig trunc = (trunc, msg)
where
width = show $ numberBitLength trunc
bit = if width == "1" then "bit" else "bits"
msg = "Number literal " ++ orig ++ " exceeds " ++ width ++ " " ++ bit
++ "; truncating to " ++ show trunc ++ "."
-- when extending a wide unsized number, we use the bit width if the digits
-- cover exactly that many bits, and add an extra 0 padding bit otherwise
extendUnsizedBased :: Int -> Number -> Number
extendUnsizedBased sizeByDigits n
| size > -32 || sizeByBits < 32 = n
| sizeByBits == sizeByDigits = useWidth sizeByBits
| otherwise = useWidth $ sizeByBits + 1
where
Based size sg base vals knds = n
sizeByBits = fromIntegral $ max (bits vals) (bits knds)
useWidth sz = Based (negate sz) sg base vals knds
-- truncate the given based number literal, if necessary
validateBased :: String -> Int -> Int -> Number -> (Number, String)
validateBased orig sizeIfPadded sizeByDigits n
-- more digits than the size would allow for, regardless of their values
| sizeIfPadded < sizeByDigits = truncated
-- unsized literal with fewer than 32 bits
| 0 > size && size > -32 = validated
-- no padding bits are present
| abs size >= sizeIfPadded = validated
-- check the padding bits in the leading digit, if there are any
| all (isLegalPad sizethBit) paddingBits = validated
-- some of the padding bits aren't legal
| otherwise = truncated
where
Based size sg base vals knds = n
n' = Based size sg base' vals' knds'
validated = valid n
truncated = addTruncateMessage orig n'
-- checking padding bits
sizethBit = getBit $ abs size - 1
paddingBits = map getBit [abs size..sizeIfPadded - 1]
getBit = getVKBit vals knds
-- truncated the number, and selected a valid new base
vals' = vals .&. widthMask size
knds' = knds .&. widthMask size
base' = if size == -32 && baseSelect == Octal
then Binary
else baseSelect
baseSelect = selectBase base vals' knds'
-- if the MSB post-truncation is X or Z, then any padding bits must match; if
-- the MSB post-truncation is 0 or 1, then non-zero padding bits are forbidden
isLegalPad :: Bit -> Bit -> Bool
isLegalPad Bit1 = (== Bit0)
isLegalPad bit = (== bit)
parseNormalized :: Bool -> String -> (Number, String)
parseNormalized _ "'0" = valid $ UnbasedUnsized Bit0
parseNormalized _ "'1" = valid $ UnbasedUnsized Bit1
parseNormalized _ "'x" = valid $ UnbasedUnsized BitX
parseNormalized _ "'z" = valid $ UnbasedUnsized BitZ
parseNormalized oversizedNumbers str =
-- simple decimal number
if maybeIdx == Nothing then
let n = readDecimal str
in Decimal (negate $ decimalSize True n) True n
let num = readDecimal str
sz = negate (decimalSize True num)
in decimal sz True num
-- non-decimal based integral number
else if maybeBase /= Nothing then
let (values, kinds) = parseBasedDigits (baseSize base) digitsExtended
in Based size signed base values kinds
number = Based size signed base values kinds
sizeIfPadded = sizeDigits * bitsPerDigit
sizeByDigits = length digitsExtended * bitsPerDigit
in if oversizedNumbers && size < 0
then valid $ extendUnsizedBased sizeByDigits number
else validateBased str sizeIfPadded sizeByDigits number
-- decimal X or Z literal
else if numDigits == 1 && elem leadDigit xzDigits then
let (vals, knds) = parseBasedDigits 2 $ replicate (abs size) leadDigit
in Based size signed Binary vals knds
else if numDigits == 1 && leadDigitIsXZ then
let vals = if elem leadDigit zDigits then widthMask size else 0
knds = widthMask size
in valid $ Based size signed Binary vals knds
-- explicitly-based decimal number
else
let num = readDecimal digits
in if rawSize == 0
then Decimal (negate $ decimalSize signed num) signed num
else Decimal size signed num
sz = if rawSize == 0 then negate (decimalSize signed num) else size
in decimal sz signed num
where
-- pull out the components of the literals
maybeIdx = elemIndex '\'' str
......@@ -63,8 +155,9 @@ parseNumber' str =
-- high-order X or Z is extended up to the size of the literal
leadDigit = head digits
numDigits = length digits
leadDigitIsXZ = elem leadDigit xzDigits
digitsExtended =
if elem leadDigit xzDigits
if leadDigitIsXZ
then replicate (sizeDigits - numDigits) leadDigit ++ digits
else digits
......@@ -84,12 +177,24 @@ parseNumber' str =
size =
if rawSize /= 0 then
rawSize
else if maybeBase /= Nothing then
negate $ bitsPerDigit * numDigits
else
else if maybeBase == Nothing || leadDigitIsXZ then
-32
else
negate $ min 32 $ bitsPerDigit * numDigits
bitsPerDigit = bits $ baseSize base - 1
-- shortcut for decimal outputs
decimal :: Int -> Bool -> Integer -> (Number, String)
decimal = validateDecimal oversizedNumbers str
-- shorthand denoting a valid number literal
valid :: Number -> (Number, String)
valid = (, "")
-- mask with the lowest N bits set
widthMask :: Int -> Integer
widthMask pow = 2 ^ (abs pow) - 1
-- read a simple unsigned decimal number
readDecimal :: Read a => String -> a
readDecimal str =
......@@ -171,6 +276,16 @@ bitToVK Bit1 = (1, 0)
bitToVK BitX = (0, 1)
bitToVK BitZ = (1, 1)
-- get the logical bit value at the given index in a (values, kinds) pair
getVKBit :: Integer -> Integer -> Int -> Bit
getVKBit v k i =
case (v .&. select, k .&. select) of
(0, 0) -> Bit0
(_, 0) -> Bit1
(0, _) -> BitX
(_, _) -> BitZ
where select = 2 ^ i
data Base
= Binary
| Octal
......
......@@ -22,6 +22,7 @@ data Config = Config
, cfIncludePaths :: [FilePath]
, cfSiloed :: Bool
, cfSkipPreprocessor :: Bool
, cfOversizedNumbers :: Bool
}
-- parse CLI macro definitions into the internal macro environment format
......@@ -48,7 +49,7 @@ parseFile :: Config -> FilePath -> ExceptT String IO (Config, AST)
parseFile config path = do
(config', contents) <- preprocessFile config path
tokens <- liftEither $ runExcept $ lexStr contents
ast <- parse tokens
ast <- parse (cfOversizedNumbers config) tokens
return (config', ast)
-- preprocess an individual file, potentially updating the configuration
......
......@@ -19,6 +19,7 @@ module Language.SystemVerilog.Parser.Parse (parse) where
import Control.Monad.Except
import Control.Monad.State.Strict
import Data.Maybe (catMaybes, fromMaybe)
import System.IO (hPutStrLn, stderr)
import Language.SystemVerilog.AST
import Language.SystemVerilog.Parser.ParseDecl
import Language.SystemVerilog.Parser.Tokens
......@@ -1206,7 +1207,7 @@ Real :: { String }
: real { tokenString $1 }
Number :: { Number }
: number { parseNumber $ tokenString $1 }
: number {% readNumber (tokenPosition $1) (tokenString $1) }
String :: { String }
: string { tail $ init $ tokenString $1 }
......@@ -1463,25 +1464,26 @@ join : "join" {} | error {% missingToken "join" }
data ParseData = ParseData
{ pPosition :: Position
, pTokens :: [Token]
, pOversizedNumbers :: Bool
}
type ParseState = StateT ParseData (ExceptT String IO)
parse :: [Token] -> ExceptT String IO AST
parse [] = return []
parse tokens =
parse :: Bool -> [Token] -> ExceptT String IO AST
parse _ [] = return []
parse oversizedNumbers tokens =
evalStateT parseMain initialState
where
position = tokenPosition $ head tokens
initialState = ParseData position tokens
initialState = ParseData position tokens oversizedNumbers
positionKeep :: (Token -> ParseState a) -> ParseState a
positionKeep cont = do
tokens <- gets pTokens
ParseData _ tokens oversizedNumbers <- get
case tokens of
[] -> cont TokenEOF
tok : toks -> do
put $ ParseData (tokenPosition tok) toks
put $ ParseData (tokenPosition tok) toks oversizedNumbers
cont tok
parseErrorTok :: Token -> ParseState a
......@@ -1672,4 +1674,12 @@ splitInit decl =
(Variable d t ident a Nil, Just (LHSIdent ident, e))
where Variable d t ident a e = decl
readNumber :: Position -> String -> ParseState Number
readNumber pos str = do
oversizedNumbers <- gets pOversizedNumbers
let (num, msg) = parseNumber oversizedNumbers str
when (not $ null msg) $ lift $ lift $
hPutStrLn stderr $ show pos ++ ": Warning: " ++ msg
return num
}
......@@ -80,6 +80,7 @@ main = do
, cfIncludePaths = incdir job
, cfSiloed = siloed job
, cfSkipPreprocessor = skipPreprocessor job
, cfOversizedNumbers = oversizedNumbers job
}
result <- runExceptT $ parseFiles config (files job)
case result of
......
......@@ -26,25 +26,5 @@ module top;
`TEST(2, 1);
`TEST(2, 2);
`TEST(2, 3);
`TEST(-8589934592, 0);
`TEST(-8589934592, 1);
`TEST(-8589934592, 2);
`TEST(-8589934592, 3);
`TEST(-8589934593, 0);
`TEST(-8589934593, 1);
`TEST(-8589934593, 2);
`TEST(-8589934593, 3);
`TEST(8589934592, 0);
`TEST(8589934592, 1);
`TEST(8589934592, 2);
`TEST(8589934592, 3);
`TEST(8589934593, 0);
`TEST(8589934593, 1);
`TEST(8589934593, 2);
`TEST(8589934593, 3);
end
endmodule
......@@ -29,10 +29,6 @@ module top;
$display("%b", 32'(4));
$display("%b", 33'(4));
$display("%b", 33'(64'hFFFF_FFFF_FFFF_FFFF));
$display("%b", 32'(4294967296));
$display("%b", 33'(4294967296));
$display("%b", 32'(4294967297));
$display("%b", 33'(4294967297));
end
localparam bit foo = '0;
......
......@@ -32,10 +32,6 @@ module top;
$display("%b", 32'd4);
$display("%b", 33'd4);
$display("%b", 33'h1_FFFF_FFFF);
$display("%b", 32'd0);
$display("%b", 33'd4294967296);
$display("%b", 32'd1);
$display("%b", 33'd4294967297);
end
localparam [0:0] foo = 0;
......
module top;
initial begin
$display("A %0d", 32);
$display("B %0d", 10);
$display("C %0d", 32);
$display("D %0d", 6);
$display("E %0d", 32);
end
endmodule
module top;
initial begin
$display(signed'(4294967295));
$display(unsigned'(4294967295));
$display(signed'(1'b1));
$display(unsigned'(1'sb1));
$display(signed'(1));
$display(unsigned'(1));
$display(signed'(-1));
$display(unsigned'(-1));
end
......
module top;
initial begin
$display($signed(4294967295));
$display($unsigned(4294967295));
$display($signed(1'b1));
$display($unsigned(1'sb1));
$display($signed(1));
$display($unsigned(1));
$display($signed(-1));
$display($unsigned(-1));
end
......
......@@ -10,7 +10,7 @@ module top;
initial begin
$monitor("%d: %01b %04b %02b", select, a, b, c);
in = 'b01111011011011101111100111110111001010001011100110101000;
in = 56'b01111011011011101111100111110111001010001011100110101000;
select = 0; #1;
select = 1; #1;
select = 2; #1;
......
......@@ -15,13 +15,8 @@ module top;
`TEST(1'SOZ) `TEST(2'SOZ) `TEST(3'SOZ) `TEST(7'SOZ) `TEST(8'SOZ) `TEST(9'SOZ) `TEST(9'SOZZ) `TEST(10'SOZZ)
`TEST(1234_5678) `TEST('h1234_5678) `TEST('o1234_5677) `TEST('b0101_1100)
`TEST('d4294967295) `TEST('d4294967296) `TEST('d4294967297) `TEST('d4294967298) `TEST('d4294967299)
`TEST('d004294967295) `TEST('d004294967296) `TEST('d004294967297) `TEST('d004294967298) `TEST('d004294967299)
`TEST(4294967295) `TEST(4294967296) `TEST(4294967297) `TEST(4294967298) `TEST(4294967299)
`TEST(-4294967295) `TEST(-4294967297) `TEST(-4294967298) `TEST(-4294967299)
`TEST(-8589934593) `TEST(8589934592) `TEST(8589934593)
// iverlog does weird things with these: `TEST(-4294967296) `TEST(-8589934592)
`TEST('d4294967294) `TEST('d4294967295)
`TEST('d004294967294) `TEST('d004294967295)
`TEST(659) `TEST('h 837FF) `TEST('o7460)
`TEST(4'b1001) `TEST(5 'D 3) `TEST(3'b01x) `TEST(12'hx) `TEST(16'hz)
......@@ -31,10 +26,9 @@ module top;
`TEST(3'bx) `TEST(3'b1x) `TEST(3'bx1) `TEST('b1x) `TEST('bx1) `TEST(3'b0x1) `TEST(3'b0z1)
`TEST('hf & 10'hf) `TEST(7'hf & 10'hf)
`TEST('b01xz01xz01xz01xz01xz01xz01xz01xz01xz) `TEST('b101xz01xz01xz01xz01xz01xz01xz01xz01xz)
`TEST(36'b01xz01xz01xz01xz01xz01xz01xz01xz01xz) `TEST(37'b01xz01xz01xz01xz01xz01xz01xz01xz01xz)
`TEST(36'sb01xz01xz01xz01xz01xz01xz01xz01xz01xz) `TEST(37'sb01xz01xz01xz01xz01xz01xz01xz01xz01xz)
`TEST('h01xz01xz) `TEST('h101xz01xz)
`TEST('h01xz01xz)
`TEST(36'h01xz01xz) `TEST(37'h01xz01xz)
`TEST(36'hb01xz01xz) `TEST(37'hb01xz01xz)
......
......@@ -24,12 +24,20 @@ simulate() {
-Wno-portbind \
-o $sim_prog \
-g2005 \
-gstrict-expr-width \
-DTEST_VCD="\"$sim_vcd_tmp\"" \
-DTEST_TOP=$sim_top \
"$@" \
$SCRIPT_DIR/tb_dumper.v \
"$@" 2>&1`
assertTrue "iverilog on $1 failed" $?
assertNull "iverilog emitted warnings:\n$iv_output" "$iv_output"
2>&1`
if [ $? -ne 0 ]; then
fail "iverilog on $1 failed:\n$iv_output"
return
elif [ "$EXPECT_IVERILOG_WARNINGS" != "0" ]; then
assertNull "iverilog on $1 emitted warnings:\n$iv_output" "$iv_output"
else
assertNotNull "iverilog on $1 did not emit any warnings" "$iv_output"
fi
# run the simulation
$sim_prog > $sim_log
assertTrue "simulating $1 failed" $?
......
'dX
'dZ
'sdX
'sdZ
'b0
'b1
'bx
'bz
'sb0
'sb1
'sbx
'sbz
1'b0
1'b1
1'bx
1'bz
1'sb0
1'sb1
1'sbx
1'sbz
1'hX
1'hZ
2'hX
2'hZ
3'hX
3'hZ
4'hX
4'hZ
5'hX
5'hZ
1'oX
1'oZ
2'oX
2'oZ
3'oX
3'oZ
4'oX
4'oZ
1'sd1
2'sd2
2'sd3
32'sd4294967294
'o1xz01xz01xz
'o2xz01xz01xz
32'o1xz01xz01xz
32'o2xz01xz01xz
#!/bin/bash
testNumber() {
mode=$1
number=$2
ve=$SHUNIT_TMPDIR/ve.v
cs=$SHUNIT_TMPDIR/cs.v
ve_log=$ve.log
cs_log=$cs.log
# substitute the current number literal into a copy of the test template
sed -e "s/NUM/$number/" < template.v > $ve
# convert in strict mode
runAndCapture $ve
assertTrue "conversion should succeed" $result
assertNotNull "stdout should not be empty" "$stdout"
if [ $mode = no_trunc ]; then
assertNull "stderr should be empty:\n$stderr" "$stderr"
else
assertNotNull "stderr should not be empty" "$stderr"
fi
# convert result in strict mode
mv -f $SHUNIT_TMPDIR/stdout $cs
runAndCapture $cs
assertTrue "conversion should succeed" $result
assertNotNull "stdout should not be empty" "$stdout"
assertNull "stderr should be empty:\n$stderr" "$stderr"
# simulate and compare in strict mode
EXPECT_IVERILOG_WARNINGS=`[ $mode = trunc_ivl_warns ]; echo $?` \
simulate /dev/null $ve_log top $ve
simulate /dev/null $cs_log top $cs
output=`diff $ve_log $cs_log`
assertTrue "number literals differ:\n$output" $?
# convert in lax mode
runAndCapture --oversized-numbers $ve
assertTrue "conversion should succeed" $result
assertNotNull "stdout should not be empty" "$stdout"
if [ $mode = no_trunc ] || [[ ! "$number" =~ .\' ]]; then
assertNull "stderr should be empty:\n$stderr" "$stderr"
else
assertNotNull "stderr should not be empty" "$stderr"
fi
# convert result in lax mode
mv -f $SHUNIT_TMPDIR/stdout $cs
runAndCapture --oversized-numbers $cs
assertTrue "conversion should succeed" $result
assertNotNull "stdout should not be empty" "$stdout"
assertNull "stderr should be empty:\n$stderr" "$stderr"
# simulate and compare in lax mode
EXPECT_IVERILOG_WARNINGS=`[[ "$number" =~ .\' ]] && [ $mode = trunc_ivl_warns ]; echo $?` \
simulate /dev/null $ve_log top -gno-strict-expr-width $ve
simulate /dev/null $cs_log top -gno-strict-expr-width $cs
output=`diff $ve_log $cs_log`
assertTrue "number literals differ:\n$output" $?
}
addTest() {
mode=$1
number=$2
test="${mode}_${number//\'/_}"
eval "$test() { testNumber $mode \"$number\"; }"
suite_addTest $test
}
suite() {
modes=(no_trunc trunc_ivl_warns trunc_ivl_silent)
for mode in ${modes[@]}; do
while read number; do
[ -n "$number" ] && \
addTest $mode "$number"
done < $mode.txt
done
}
source ../lib/functions.sh
. shunit2
module top;
localparam X = NUM;
localparam [63:0] Y = X;
initial $display("%0d %b %b", $bits(X), X, Y);
endmodule
'h0_FFFF_FFFF
'o00xz01xz01xz
'o01xz01xz01xz
'bz01xz01xz01xz01xz01xz01xz01xz01xz
'hZ_ZZZZ_ZZZZ
'hz01xz01xz
4294967294
4294967295
1'b01
1'b001
1'bxx
1'b0x
1'd2
1'sd2
1'd3
1'sd3
3'sd8
'b101xz01xz01xz01xz01xz01xz01xz01xz
'bxz01xz01xz01xz01xz01xz01xz01xz01xz
'b1xz01xz01xz01xz01xz01xz01xz01xz01xz
32'bz01xz01xz01xz01xz01xz01xz01xz01xz
32'bxz01xz01xz01xz01xz01xz01xz01xz01xz
32'b1xz01xz01xz01xz01xz01xz01xz01xz01xz
2'o4
2'o7
'h1_FFFF_FFFF
'hF_FFFF_FFFF
'o4xz01xz01xz
'oz1xz01xz01xz
'h101xz01xz
'hxz01xz01xz
'h0xz01xz01xz
'd4294967296
'd4294967297
'd04294967296
'd04294967297
'd8589934590
'd8589934591
'd8589934592
'd8589934593
4294967296
4294967297
8589934590
8589934591
8589934592
8589934593
1'd111
1'b111
1'o111
1'h111
2'd111
2'b111
2'o111
2'h111
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment