Skip to content

Commit c03e450

Browse files
wanda-phiwhitequark
authored andcommitted
hdl.mem: mask initial value to shape.
Fixes #1492.
1 parent fff8f0b commit c03e450

File tree

4 files changed

+84
-50
lines changed

4 files changed

+84
-50
lines changed

amaranth/hdl/_ast.py

Lines changed: 52 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1946,6 +1946,57 @@ def __call__(cls, shape=None, src_loc_at=0, **kwargs):
19461946
return signal
19471947

19481948

1949+
# also used for MemoryData.Init
1950+
def _get_init_value(init, shape, what="signal"):
1951+
orig_init = init
1952+
orig_shape = shape
1953+
shape = Shape.cast(shape)
1954+
if isinstance(orig_shape, ShapeCastable):
1955+
try:
1956+
init = Const.cast(orig_shape.const(init))
1957+
except Exception:
1958+
raise TypeError(f"Initial value must be a constant initializer of {orig_shape!r}")
1959+
if init.shape() != Shape.cast(shape):
1960+
raise ValueError(f"Constant returned by {orig_shape!r}.const() must have the shape "
1961+
f"that it casts to, {shape!r}, and not {init.shape()!r}")
1962+
return init.value
1963+
else:
1964+
if init is None:
1965+
init = 0
1966+
try:
1967+
init = Const.cast(init)
1968+
except TypeError:
1969+
raise TypeError("Initial value must be a constant-castable expression, not {!r}"
1970+
.format(orig_init))
1971+
# Avoid false positives for all-zeroes and all-ones
1972+
if orig_init is not None and not (isinstance(orig_init, int) and orig_init in (0, -1)):
1973+
if init.shape().signed and not shape.signed:
1974+
warnings.warn(
1975+
message=f"Initial value {orig_init!r} is signed, "
1976+
f"but the {what} shape is {shape!r}",
1977+
category=SyntaxWarning,
1978+
stacklevel=2)
1979+
elif (init.shape().width > shape.width or
1980+
init.shape().width == shape.width and
1981+
shape.signed and not init.shape().signed):
1982+
warnings.warn(
1983+
message=f"Initial value {orig_init!r} will be truncated to "
1984+
f"the {what} shape {shape!r}",
1985+
category=SyntaxWarning,
1986+
stacklevel=2)
1987+
1988+
if isinstance(orig_shape, range) and orig_init is not None and orig_init not in orig_shape:
1989+
if orig_init == orig_shape.stop:
1990+
raise SyntaxError(
1991+
f"Initial value {orig_init!r} equals the non-inclusive end of the {what} "
1992+
f"shape {orig_shape!r}; this is likely an off-by-one error")
1993+
else:
1994+
raise SyntaxError(
1995+
f"Initial value {orig_init!r} is not within the {what} shape {orig_shape!r}")
1996+
1997+
return Const(init.value, shape).value
1998+
1999+
19492000
@final
19502001
class Signal(Value, DUID, metaclass=_SignalMeta):
19512002
"""A varying integer value.
@@ -2016,54 +2067,9 @@ def __init__(self, shape=None, *, name=None, init=None, reset=None, reset_less=F
20162067
DeprecationWarning, stacklevel=2)
20172068
init = reset
20182069

2019-
orig_init = init
2020-
if isinstance(orig_shape, ShapeCastable):
2021-
try:
2022-
init = Const.cast(orig_shape.const(init))
2023-
except Exception:
2024-
raise TypeError("Initial value must be a constant initializer of {!r}"
2025-
.format(orig_shape))
2026-
if init.shape() != Shape.cast(orig_shape):
2027-
raise ValueError("Constant returned by {!r}.const() must have the shape that "
2028-
"it casts to, {!r}, and not {!r}"
2029-
.format(orig_shape, Shape.cast(orig_shape),
2030-
init.shape()))
2031-
else:
2032-
if init is None:
2033-
init = 0
2034-
try:
2035-
init = Const.cast(init)
2036-
except TypeError:
2037-
raise TypeError("Initial value must be a constant-castable expression, not {!r}"
2038-
.format(orig_init))
2039-
# Avoid false positives for all-zeroes and all-ones
2040-
if orig_init is not None and not (isinstance(orig_init, int) and orig_init in (0, -1)):
2041-
if init.shape().signed and not self._signed:
2042-
warnings.warn(
2043-
message="Initial value {!r} is signed, but the signal shape is {!r}"
2044-
.format(orig_init, shape),
2045-
category=SyntaxWarning,
2046-
stacklevel=2)
2047-
elif (init.shape().width > self._width or
2048-
init.shape().width == self._width and
2049-
self._signed and not init.shape().signed):
2050-
warnings.warn(
2051-
message="Initial value {!r} will be truncated to the signal shape {!r}"
2052-
.format(orig_init, shape),
2053-
category=SyntaxWarning,
2054-
stacklevel=2)
2055-
self._init = Const(init.value, shape).value
2070+
self._init = _get_init_value(init, unsigned(1) if orig_shape is None else orig_shape)
20562071
self._reset_less = bool(reset_less)
20572072

2058-
if isinstance(orig_shape, range) and orig_init is not None and orig_init not in orig_shape:
2059-
if orig_init == orig_shape.stop:
2060-
raise SyntaxError(
2061-
f"Initial value {orig_init!r} equals the non-inclusive end of the signal "
2062-
f"shape {orig_shape!r}; this is likely an off-by-one error")
2063-
else:
2064-
raise SyntaxError(
2065-
f"Initial value {orig_init!r} is not within the signal shape {orig_shape!r}")
2066-
20672073
self._attrs = OrderedDict(() if attrs is None else attrs)
20682074

20692075
if isinstance(orig_shape, ShapeCastable):

amaranth/hdl/_mem.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from .. import tracer
55
from ._ast import *
6+
from ._ast import _get_init_value
67
from ._ir import Fragment, AlreadyElaborated
78
from ..utils import ceil_log2
89
from .._utils import final
@@ -105,10 +106,11 @@ def __setitem__(self, index, value):
105106
for actual_index, actual_value in zip(indices, value):
106107
self[actual_index] = actual_value
107108
else:
109+
raw = _get_init_value(value, self._shape, "memory")
108110
if isinstance(self._shape, ShapeCastable):
109-
self._raw[index] = Const.cast(Const(value, self._shape)).value
111+
self._raw[index] = raw
110112
else:
111-
value = operator.index(value)
113+
value = raw
112114
# self._raw[index] assigned by the following line
113115
self._elems[index] = value
114116

tests/test_hdl_mem.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,29 @@ def test_row_elab(self):
3131
with self.assertRaisesRegex(ValueError,
3232
r"^Value \(memory-row \(memory-data data\) 0\) can only be used in simulator processes$"):
3333
m.d.comb += data[0].eq(1)
34+
35+
36+
class InitTestCase(FHDLTestCase):
37+
def test_ones(self):
38+
init = MemoryData.Init([-1, 12], shape=8, depth=2)
39+
self.assertEqual(list(init), [0xff, 12])
40+
init = MemoryData.Init([-1, -12], shape=signed(8), depth=2)
41+
self.assertEqual(list(init), [-1, -12])
42+
43+
def test_trunc(self):
44+
with self.assertWarnsRegex(SyntaxWarning,
45+
r"^Initial value -2 is signed, but the memory shape is unsigned\(8\)$"):
46+
init = MemoryData.Init([-2, 12], shape=8, depth=2)
47+
self.assertEqual(list(init), [0xfe, 12])
48+
with self.assertWarnsRegex(SyntaxWarning,
49+
r"^Initial value 258 will be truncated to the memory shape unsigned\(8\)$"):
50+
init = MemoryData.Init([258, 129], shape=8, depth=2)
51+
self.assertEqual(list(init), [2, 129])
52+
with self.assertWarnsRegex(SyntaxWarning,
53+
r"^Initial value 128 will be truncated to the memory shape signed\(8\)$"):
54+
init = MemoryData.Init([128], shape=signed(8), depth=1)
55+
self.assertEqual(list(init), [-128])
56+
with self.assertWarnsRegex(SyntaxWarning,
57+
r"^Initial value -129 will be truncated to the memory shape signed\(8\)$"):
58+
init = MemoryData.Init([-129], shape=signed(8), depth=1)
59+
self.assertEqual(list(init), [127])

tests/test_lib_memory.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ def test_constructor_wrong(self):
329329
memory.Memory(shape="a", depth=3, init=[])
330330
with self.assertRaisesRegex(TypeError,
331331
(r"^Memory initialization value at address 1: "
332-
r"'str' object cannot be interpreted as an integer$")):
332+
r"Initial value must be a constant-castable expression, not '0'$")):
333333
memory.Memory(shape=8, depth=4, init=[1, "0"])
334334
with self.assertRaisesRegex(ValueError,
335335
r"^Either 'data' or 'shape' needs to be given$"):
@@ -373,7 +373,7 @@ def test_init_set_shapecastable(self):
373373
def test_init_set_wrong(self):
374374
m = memory.Memory(shape=8, depth=4, init=[])
375375
with self.assertRaisesRegex(TypeError,
376-
r"^'str' object cannot be interpreted as an integer$"):
376+
r"^Initial value must be a constant-castable expression, not 'a'$"):
377377
m.init[0] = "a"
378378
m = memory.Memory(shape=MyStruct, depth=4, init=[])
379379
# underlying TypeError message differs between PyPy and CPython

0 commit comments

Comments
 (0)