Skip to content

Commit f15f5d6

Browse files
committed
improvements to infer_val_and_bitwidth for supporting signed numbers
1 parent 1db101e commit f15f5d6

File tree

2 files changed

+69
-12
lines changed

2 files changed

+69
-12
lines changed

pyrtl/helperfuncs.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -428,21 +428,32 @@ class Ctl(Enum):
428428
ValueBitwidthTuple = collections.namedtuple('ValueBitwidthTuple', 'value bitwidth')
429429

430430

431-
def infer_val_and_bitwidth(rawinput, bitwidth=None):
431+
def infer_val_and_bitwidth(rawinput, bitwidth=None, signed=False):
432432
""" Return a tuple (value, bitwidth) infered from the specified input.
433433
434434
:param rawinput: a bool, int, or verilog-style string constant
435435
:param bitwidth: an integer bitwidth or (by default) None
436+
:param signed: a bool (by default set False) to include bits for proper twos complement
436437
:returns tuple of integers (value, bitwidth)
437438
438439
Given a boolean, integer, or verilog-style string constant, this function returns a
439440
tuple of two integers (value, bitwidth) which are infered from the specified rawinput.
440441
The tuple returned is, in fact, a named tuple with names .value and .bitwidth for feilds
441-
0 and 1 respectively. Error checks are performed that determine if the bitwidths specified
442-
are sufficient and appropriate for the values specified. Examples can be found below ::
442+
0 and 1 respectively. If signed is set to true, bits will be included to ensure a proper
443+
two's complement representation is possible, otherwise it is assume all bits can be used
444+
for standard unsigned representation. Error checks are performed that determine if the
445+
bitwidths specified are sufficient and appropriate for the values specified.
446+
Examples can be found below ::
443447
444448
infer_val_and_bitwidth(2, bitwidth=5) == (2, 5)
445449
infer_val_and_bitwidth(3) == (3, 2) # bitwidth infered from value
450+
infer_val_and_bitwidth(3, signed=True) == (3, 3) # need a bit for the leading zero
451+
infer_val_and_bitwidth(-3, signed=True) == (5, 3) # 5 = -3 & 0b111 = ..111101 & 0b111
452+
infer_val_and_bitwidth(-4, signed=True) == (4, 3) # 4 = -4 & 0b111 = ..111100 & 0b111
453+
infer_val_and_bitwidth(-3, bitwidth=5, signed=True) == (29, 5)
454+
infer_val_and_bitwidth(-3) ==> Error # negative numbers require bitwidth or signed=True
455+
infer_val_and_bitwidth(3, bitwidth=2) == (3, 2)
456+
infer_val_and_bitwidth(3, bitwidth=2, signed=True) ==> Error # need space for sign bit
446457
infer_val_and_bitwidth(True) == (1, 1)
447458
infer_val_and_bitwidth(False) == (0, 1)
448459
infer_val_and_bitwidth("5'd12") == (12, 5)
@@ -453,17 +464,19 @@ def infer_val_and_bitwidth(rawinput, bitwidth=None):
453464
"""
454465

455466
if isinstance(rawinput, bool):
456-
return _convert_bool(rawinput, bitwidth)
467+
return _convert_bool(rawinput, bitwidth, signed)
457468
elif isinstance(rawinput, numbers.Integral):
458-
return _convert_int(rawinput, bitwidth)
469+
return _convert_int(rawinput, bitwidth, signed)
459470
elif isinstance(rawinput, six.string_types):
460-
return _convert_verilog_str(rawinput, bitwidth)
471+
return _convert_verilog_str(rawinput, bitwidth, signed)
461472
else:
462473
raise PyrtlError('error, the value provided is of an improper type, "%s"'
463474
'proper types are bool, int, and string' % type(rawinput))
464475

465476

466-
def _convert_bool(bool_val, bitwidth=None):
477+
def _convert_bool(bool_val, bitwidth=None, signed=False):
478+
if signed:
479+
raise PyrtlError('error, booleans cannot be signed (covert to int first)')
467480
num = int(bool_val)
468481
if bitwidth is None:
469482
bitwidth = 1
@@ -472,23 +485,37 @@ def _convert_bool(bool_val, bitwidth=None):
472485
return ValueBitwidthTuple(num, bitwidth)
473486

474487

475-
def _convert_int(val, bitwidth=None):
488+
def _convert_int(val, bitwidth=None, signed=False):
476489
if val >= 0:
477490
num = val
478491
# infer bitwidth if it is not specified explicitly
492+
min_bitwidth = len(bin(num)) - 2 # the -2 for the "0b" at the start of the string
493+
if signed and val != 0:
494+
min_bitwidth += 1 # extra bit needed for the zero
495+
479496
if bitwidth is None:
480-
bitwidth = len(bin(num)) - 2 # the -2 for the "0b" at the start of the string
497+
bitwidth = min_bitwidth
498+
elif bitwidth < min_bitwidth:
499+
raise PyrtlError('bitwidth specified is insufficient to represent constant')
500+
481501
else: # val is negative
502+
if not signed and bitwidth is None:
503+
raise PyrtlError('negative constants require either signed=True or specified bitwidth')
504+
482505
if bitwidth is None:
483-
raise PyrtlError(
484-
'negative Const values must have bitwidth declared explicitly')
506+
bitwidth = 1 if val == -1 else len(bin(~val)) - 1
507+
485508
if (val >> bitwidth - 1) != -1:
486509
raise PyrtlError('insufficient bits for negative number')
510+
487511
num = val & ((1 << bitwidth) - 1) # result is a twos complement value
488512
return ValueBitwidthTuple(num, bitwidth)
489513

490514

491-
def _convert_verilog_str(val, bitwidth=None):
515+
def _convert_verilog_str(val, bitwidth=None, signed=False):
516+
if signed:
517+
raise PyrtlError('error, signed verilog-style string constants not supported currently')
518+
492519
bases = {'b': 2, 'o': 8, 'd': 10, 'h': 16, 'x': 16}
493520
passed_bitwidth = bitwidth
494521

tests/test_helperfuncs.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,36 @@ def test_infer_val_and_bitwidth(self):
113113
self.assertEqual(pyrtl.infer_val_and_bitwidth("5'b10"), (2, 5))
114114
self.assertEqual(pyrtl.infer_val_and_bitwidth("5'b10"), (2, 5))
115115
self.assertEqual(pyrtl.infer_val_and_bitwidth("8'B 0110_1100"), (108, 8))
116+
self.assertEqual(pyrtl.infer_val_and_bitwidth(-3, bitwidth=5), (0b11101, 5))
117+
self.assertEqual(pyrtl.infer_val_and_bitwidth(3, signed=True), (3, 3))
118+
self.assertEqual(pyrtl.infer_val_and_bitwidth(-3, signed=True), (5, 3))
119+
self.assertEqual(pyrtl.infer_val_and_bitwidth(-4, signed=True), (4, 3))
120+
self.assertEqual(pyrtl.infer_val_and_bitwidth(-3, bitwidth=5, signed=True), (29, 5))
121+
self.assertEqual(pyrtl.infer_val_and_bitwidth(3, bitwidth=2), (3, 2))
122+
123+
self.assertEqual(pyrtl.infer_val_and_bitwidth(0), (0, 1))
124+
self.assertEqual(pyrtl.infer_val_and_bitwidth(1), (1, 1))
125+
self.assertEqual(pyrtl.infer_val_and_bitwidth(2), (2, 2))
126+
self.assertEqual(pyrtl.infer_val_and_bitwidth(3), (3, 2))
127+
self.assertEqual(pyrtl.infer_val_and_bitwidth(4), (4, 3))
128+
129+
self.assertEqual(pyrtl.infer_val_and_bitwidth(0, signed=True), (0, 1))
130+
self.assertEqual(pyrtl.infer_val_and_bitwidth(1, signed=True), (1, 2))
131+
self.assertEqual(pyrtl.infer_val_and_bitwidth(2, signed=True), (2, 3))
132+
self.assertEqual(pyrtl.infer_val_and_bitwidth(3, signed=True), (3, 3))
133+
self.assertEqual(pyrtl.infer_val_and_bitwidth(4, signed=True), (4, 4))
134+
self.assertEqual(pyrtl.infer_val_and_bitwidth(-1, signed=True), (1, 1))
135+
self.assertEqual(pyrtl.infer_val_and_bitwidth(-2, signed=True), (2, 2))
136+
self.assertEqual(pyrtl.infer_val_and_bitwidth(-3, signed=True), (5, 3))
137+
self.assertEqual(pyrtl.infer_val_and_bitwidth(-4, signed=True), (4, 3))
138+
self.assertEqual(pyrtl.infer_val_and_bitwidth(-5, signed=True), (11, 4))
139+
140+
with self.assertRaises(pyrtl.PyrtlError):
141+
pyrtl.infer_val_and_bitwidth(-3)
142+
with self.assertRaises(pyrtl.PyrtlError):
143+
pyrtl.infer_val_and_bitwidth(True, signed=True)
144+
with self.assertRaises(pyrtl.PyrtlError):
145+
pyrtl.infer_val_and_bitwidth(3, bitwidth=2, signed=True)
116146

117147

118148
class TestChop(unittest.TestCase):

0 commit comments

Comments
 (0)