hdl: deprecate {Const,Signal}.{width,signed} accessors.

These accessors used to be necessary (in addition to `.shape()`) while
the AST nodes were mutable. However, after commit 2bf1b4da that made
AST nodes immutable, there is no technical requirement to keep them
around. Additionally:

- `len(value)` is shorter than `value.width` and works with any `value`
- `value.shape().signed` is longer than `value.signed` but works with
  any `value`
This commit is contained in:
Catherine 2024-03-26 22:28:51 +00:00
parent 0c041f2602
commit 738d8b7764
6 changed files with 56 additions and 47 deletions

View file

@ -24,7 +24,7 @@ def _signed(value):
elif isinstance(value, int): elif isinstance(value, int):
return value < 0 return value < 0
elif isinstance(value, _ast.Const): elif isinstance(value, _ast.Const):
return value.signed return value.shape().signed
else: else:
assert False, f"Invalid constant {value!r}" assert False, f"Invalid constant {value!r}"
@ -41,8 +41,8 @@ def _const(value):
width = max(32, bits_for(value)) width = max(32, bits_for(value))
return _const(_ast.Const(value, width)) return _const(_ast.Const(value, width))
elif isinstance(value, _ast.Const): elif isinstance(value, _ast.Const):
value_twos_compl = value.value & ((1 << value.width) - 1) value_twos_compl = value.value & ((1 << len(value)) - 1)
return "{}'{:0{}b}".format(value.width, value_twos_compl, value.width) return "{}'{:0{}b}".format(len(value), value_twos_compl, len(value))
else: else:
assert False, f"Invalid constant {value!r}" assert False, f"Invalid constant {value!r}"
@ -389,9 +389,10 @@ class ModuleEmitter:
assert value == port_value assert value == port_value
self.name_map[signal] = (*self.module.name, name) self.name_map[signal] = (*self.module.name, name)
else: else:
wire = self.builder.wire(width=signal.width, signed=signal.signed, shape = signal.shape()
name=name, attrs=attrs, wire = self.builder.wire(width=shape.width, signed=shape.signed,
src=_src(signal.src_loc)) name=name, attrs=attrs,
src=_src(signal.src_loc))
self.sigport_wires[name] = (wire, value) self.sigport_wires[name] = (wire, value)
self.name_map[signal] = (*self.module.name, wire[1:]) self.name_map[signal] = (*self.module.name, wire[1:])
@ -400,7 +401,7 @@ class ModuleEmitter:
for port_id, (name, (value, flow)) in enumerate(self.module.ports.items()): for port_id, (name, (value, flow)) in enumerate(self.module.ports.items()):
signed = False signed = False
if name in named_signals: if name in named_signals:
signed = named_signals[name].signed signed = named_signals[name].shape().signed
wire = self.builder.wire(width=len(value), signed=signed, wire = self.builder.wire(width=len(value), signed=signed,
port_id=port_id, port_kind=flow.value, port_id=port_id, port_kind=flow.value,
name=name, attrs=self.value_attrs.get(value, {}), name=name, attrs=self.value_attrs.get(value, {}),

View file

@ -1542,7 +1542,7 @@ class Const(Value, metaclass=_ConstMeta):
width = 0 width = 0
for part in obj.parts: for part in obj.parts:
const = Const.cast(part) const = Const.cast(part)
part_value = Const(const.value, unsigned(const.width)).value part_value = Const(const.value, unsigned(len(const))).value
value |= part_value << width value |= part_value << width
width += len(const) width += len(const)
return Const(value, width) return Const(value, width)
@ -1567,34 +1567,40 @@ class Const(Value, metaclass=_ConstMeta):
category=SyntaxWarning, category=SyntaxWarning,
stacklevel=3) stacklevel=3)
shape = Shape.cast(shape, src_loc_at=1 + src_loc_at) shape = Shape.cast(shape, src_loc_at=1 + src_loc_at)
self._width = shape.width
self._signed = shape.signed
if shape.signed and value >> (shape.width - 1) & 1: if shape.signed and value >> (shape.width - 1) & 1:
value |= -(1 << shape.width) value |= -(1 << shape.width)
else: else:
value &= (1 << shape.width) - 1 value &= (1 << shape.width) - 1
self._shape = shape
self._value = value self._value = value
def shape(self):
return self._shape
@property @property
def value(self): def value(self):
return self._value return self._value
# TODO(amaranth-0.6): remove
@property @property
@deprecated("`const.width` is deprecated and will be removed in Amaranth 0.6; use `len(const)` instead")
def width(self): def width(self):
return self._width return self.shape().width
# TODO(amaranth-0.6): remove
@property @property
@deprecated("`const.signed` is deprecated and will be removed in Amaranth 0.6; use `const.shape().signed` instead")
def signed(self): def signed(self):
return self._signed return self.shape().signed
def shape(self):
return Shape(self.width, self.signed)
def _rhs_signals(self): def _rhs_signals(self):
return SignalSet() return SignalSet()
def __repr__(self): def __repr__(self):
return "(const {}'{}d{})".format(self.width, "s" if self.signed else "", self.value) if self._shape.signed:
return f"(const {self._shape.width}'sd{self._value})"
else:
return f"(const {self._shape.width}'d{self._value})"
C = Const # shorthand C = Const # shorthand
@ -1964,15 +1970,15 @@ class Signal(Value, DUID, metaclass=_SignalMeta):
.format(orig_init)) .format(orig_init))
# Avoid false positives for all-zeroes and all-ones # Avoid false positives for all-zeroes and all-ones
if orig_init is not None and not (isinstance(orig_init, int) and orig_init in (0, -1)): if orig_init is not None and not (isinstance(orig_init, int) and orig_init in (0, -1)):
if init.shape().signed and not self.signed: if init.shape().signed and not self._signed:
warnings.warn( warnings.warn(
message="Initial value {!r} is signed, but the signal shape is {!r}" message="Initial value {!r} is signed, but the signal shape is {!r}"
.format(orig_init, shape), .format(orig_init, shape),
category=SyntaxWarning, category=SyntaxWarning,
stacklevel=2) stacklevel=2)
elif (init.shape().width > self.width or elif (init.shape().width > self._width or
init.shape().width == self.width and init.shape().width == self._width and
self.signed and not init.shape().signed): self._signed and not init.shape().signed):
warnings.warn( warnings.warn(
message="Initial value {!r} will be truncated to the signal shape {!r}" message="Initial value {!r} will be truncated to the signal shape {!r}"
.format(orig_init, shape), .format(orig_init, shape),
@ -2030,13 +2036,20 @@ class Signal(Value, DUID, metaclass=_SignalMeta):
else: else:
self._decoder = decoder self._decoder = decoder
@property def shape(self):
def width(self): return Shape(self._width, self._signed)
return self._width
# TODO(amaranth-0.6): remove
@property @property
@deprecated("`signal.width` is deprecated and will be removed in Amaranth 0.6; use `len(signal)` instead")
def width(self):
return self.shape().width
# TODO(amaranth-0.6): remove
@property
@deprecated("`signal.signed` is deprecated and will be removed in Amaranth 0.6; use `signal.shape().signed` instead")
def signed(self): def signed(self):
return self._signed return self.shape().signed
@property @property
def init(self): def init(self):
@ -2096,9 +2109,6 @@ class Signal(Value, DUID, metaclass=_SignalMeta):
kw["init"] = init kw["init"] = init
return cls(**kw, src_loc_at=1 + src_loc_at) return cls(**kw, src_loc_at=1 + src_loc_at)
def shape(self):
return Shape(self.width, self.signed)
def _lhs_signals(self): def _lhs_signals(self):
return SignalSet((self,)) return SignalSet((self,))

View file

@ -625,7 +625,7 @@ class NetlistDriver:
def emit_value(self, builder): def emit_value(self, builder):
if self.domain is None: if self.domain is None:
init = _ast.Const(self.signal.init, self.signal.width) init = _ast.Const(self.signal.init, len(self.signal))
default, _signed = builder.emit_rhs(self.module_idx, init) default, _signed = builder.emit_rhs(self.module_idx, init)
else: else:
default = builder.emit_signal(self.signal) default = builder.emit_signal(self.signal)
@ -740,11 +740,13 @@ class NetlistEmitter:
except KeyError: except KeyError:
pass pass
if isinstance(value, _ast.Const): if isinstance(value, _ast.Const):
result = _nir.Value.from_const(value.value, value.width) shape = value.shape()
signed = value.signed result = _nir.Value.from_const(value.value, shape.width)
signed = shape.signed
elif isinstance(value, _ast.Signal): elif isinstance(value, _ast.Signal):
shape = value.shape()
result = self.emit_signal(value) result = self.emit_signal(value)
signed = value.signed signed = shape.signed
elif isinstance(value, _ast.Operator): elif isinstance(value, _ast.Operator):
if len(value.operands) == 1: if len(value.operands) == 1:
operand_a, signed_a = self.emit_rhs(module_idx, value.operands[0]) operand_a, signed_a = self.emit_rhs(module_idx, value.operands[0])
@ -1247,12 +1249,12 @@ class NetlistEmitter:
else: else:
dir = PortDirection.Input dir = PortDirection.Input
if dir == PortDirection.Input: if dir == PortDirection.Input:
top.ports_i[name] = (next_input_bit, signal.width) top.ports_i[name] = (next_input_bit, len(signal))
value = _nir.Value( value = _nir.Value(
_nir.Net.from_cell(0, bit) _nir.Net.from_cell(0, bit)
for bit in range(next_input_bit, next_input_bit + signal.width) for bit in range(next_input_bit, next_input_bit + len(signal))
) )
next_input_bit += signal.width next_input_bit += len(signal)
self.connect(signal_value, value, src_loc=signal.src_loc) self.connect(signal_value, value, src_loc=signal.src_loc)
elif dir == PortDirection.Output: elif dir == PortDirection.Output:
top.ports_o[name] = signal_value top.ports_o[name] = signal_value
@ -1276,7 +1278,7 @@ class NetlistEmitter:
inputs=_nir.Value(cond), inputs=_nir.Value(cond),
src_loc=driver.domain.rst.src_loc) src_loc=driver.domain.rst.src_loc)
cond, = self.netlist.add_value_cell(1, cell) cond, = self.netlist.add_value_cell(1, cell)
init = _nir.Value.from_const(driver.signal.init, driver.signal.width) init = _nir.Value.from_const(driver.signal.init, len(driver.signal))
driver.assignments.append(_nir.Assignment(cond=cond, start=0, driver.assignments.append(_nir.Assignment(cond=cond, start=0,
value=init, src_loc=driver.signal.src_loc)) value=init, src_loc=driver.signal.src_loc))
value = driver.emit_value(self) value = driver.emit_value(self)

View file

@ -44,7 +44,7 @@ class MemoryInstance(Fragment):
assert len(self._en) == 1 assert len(self._en) == 1
if domain == "comb": if domain == "comb":
assert isinstance(self._en, Const) assert isinstance(self._en, Const)
assert self._en.width == 1 assert self._en.shape() == unsigned(1)
assert self._en.value == 1 assert self._en.value == 1
assert not self._transparent_for assert not self._transparent_for

View file

@ -602,7 +602,7 @@ class _ControlInserter(FragmentTransformer):
class ResetInserter(_ControlInserter): class ResetInserter(_ControlInserter):
def _insert_control(self, fragment, domain, signals): def _insert_control(self, fragment, domain, signals):
stmts = [s.eq(Const(s.init, s.width)) for s in signals if not s.reset_less] stmts = [s.eq(Const(s.init, s.shape())) for s in signals if not s.reset_less]
fragment.add_statements(domain, Switch(self.controls[domain], {1: stmts}, src_loc=self.src_loc)) fragment.add_statements(domain, Switch(self.controls[domain], {1: stmts}, src_loc=self.src_loc))

View file

@ -897,18 +897,16 @@ class SliceTestCase(FHDLTestCase):
def test_const(self): def test_const(self):
a = Const.cast(Const(0x1234, 16)[4:12]) a = Const.cast(Const(0x1234, 16)[4:12])
self.assertEqual(a.value, 0x23) self.assertEqual(a.value, 0x23)
self.assertEqual(a.width, 8) self.assertEqual(a.shape(), unsigned(8))
self.assertEqual(a.signed, False)
a = Const.cast(Const(-4, signed(8))[1:6]) a = Const.cast(Const(-4, signed(8))[1:6])
self.assertEqual(a.value, 0x1e) self.assertEqual(a.value, 0x1e)
self.assertEqual(a.width, 5) self.assertEqual(a.shape(), unsigned(5))
self.assertEqual(a.signed, False)
class BitSelectTestCase(FHDLTestCase): class BitSelectTestCase(FHDLTestCase):
def setUp(self): def setUp(self):
self.c = Const(0, 8) self.c = Const(0, 8)
self.s = Signal(range(self.c.width)) self.s = Signal(range(len(self.c)))
def test_shape(self): def test_shape(self):
s1 = self.c.bit_select(self.s, 2) s1 = self.c.bit_select(self.s, 2)
@ -946,7 +944,7 @@ class BitSelectTestCase(FHDLTestCase):
class WordSelectTestCase(FHDLTestCase): class WordSelectTestCase(FHDLTestCase):
def setUp(self): def setUp(self):
self.c = Const(0, 8) self.c = Const(0, 8)
self.s = Signal(range(self.c.width)) self.s = Signal(range(len(self.c)))
def test_shape(self): def test_shape(self):
s1 = self.c.word_select(self.s, 2) s1 = self.c.word_select(self.s, 2)
@ -1043,12 +1041,10 @@ class CatTestCase(FHDLTestCase):
def test_const(self): def test_const(self):
a = Const.cast(Cat(Const(1, 1), Const(0, 1), Const(3, 2), Const(2, 2))) a = Const.cast(Cat(Const(1, 1), Const(0, 1), Const(3, 2), Const(2, 2)))
self.assertEqual(a.value, 0x2d) self.assertEqual(a.value, 0x2d)
self.assertEqual(a.width, 6) self.assertEqual(a.shape(), unsigned(6))
self.assertEqual(a.signed, False)
a = Const.cast(Cat(Const(-4, 8), Const(-3, 8))) a = Const.cast(Cat(Const(-4, 8), Const(-3, 8)))
self.assertEqual(a.value, 0xfdfc) self.assertEqual(a.value, 0xfdfc)
self.assertEqual(a.width, 16) self.assertEqual(a.shape(), unsigned(16))
self.assertEqual(a.signed, False)
class ArrayTestCase(FHDLTestCase): class ArrayTestCase(FHDLTestCase):