Skip to content

Commit 9a7f4f2

Browse files
wanda-phiwhitequark
authored andcommitted
hdl.mem: mask initial value to shape.
Fixes #1492.
1 parent 6082ce9 commit 9a7f4f2

File tree

4 files changed

+86
-51
lines changed

4 files changed

+86
-51
lines changed

amaranth/hdl/_ast.py

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

19781978

1979+
# also used for MemoryData.Init
1980+
def _get_init_value(init, shape, what="signal"):
1981+
orig_init = init
1982+
orig_shape = shape
1983+
shape = Shape.cast(shape)
1984+
if isinstance(orig_shape, ShapeCastable):
1985+
try:
1986+
init = Const.cast(orig_shape.const(init))
1987+
except Exception:
1988+
raise TypeError(f"Initial value must be a constant initializer of {orig_shape!r}")
1989+
if init.shape() != Shape.cast(shape):
1990+
raise ValueError(f"Constant returned by {orig_shape!r}.const() must have the shape "
1991+
f"that it casts to, {shape!r}, and not {init.shape()!r}")
1992+
return init.value
1993+
else:
1994+
if init is None:
1995+
init = 0
1996+
try:
1997+
init = Const.cast(init)
1998+
except TypeError:
1999+
raise TypeError("Initial value must be a constant-castable expression, not {!r}"
2000+
.format(orig_init))
2001+
# Avoid false positives for all-zeroes and all-ones
2002+
if orig_init is not None and not (isinstance(orig_init, int) and orig_init in (0, -1)):
2003+
if init.shape().signed and not shape.signed:
2004+
warnings.warn(
2005+
message=f"Initial value {orig_init!r} is signed, "
2006+
f"but the {what} shape is {shape!r}",
2007+
category=SyntaxWarning,
2008+
stacklevel=2)
2009+
elif (init.shape().width > shape.width or
2010+
init.shape().width == shape.width and
2011+
shape.signed and not init.shape().signed):
2012+
warnings.warn(
2013+
message=f"Initial value {orig_init!r} will be truncated to "
2014+
f"the {what} shape {shape!r}",
2015+
category=SyntaxWarning,
2016+
stacklevel=2)
2017+
2018+
if isinstance(orig_shape, range) and orig_init is not None and orig_init not in orig_shape:
2019+
if orig_init == orig_shape.stop:
2020+
raise SyntaxError(
2021+
f"Initial value {orig_init!r} equals the non-inclusive end of the {what} "
2022+
f"shape {orig_shape!r}; this is likely an off-by-one error")
2023+
else:
2024+
raise SyntaxError(
2025+
f"Initial value {orig_init!r} is not within the {what} shape {orig_shape!r}")
2026+
2027+
return Const(init.value, shape).value
2028+
2029+
19792030
@final
19802031
class Signal(Value, DUID, metaclass=_SignalMeta):
19812032
"""A varying integer value.
@@ -2046,54 +2097,9 @@ def __init__(self, shape=None, *, name=None, init=None, reset=None, reset_less=F
20462097
DeprecationWarning, stacklevel=2)
20472098
init = reset
20482099

2049-
orig_init = init
2050-
if isinstance(orig_shape, ShapeCastable):
2051-
try:
2052-
init = Const.cast(orig_shape.const(init))
2053-
except Exception:
2054-
raise TypeError("Initial value must be a constant initializer of {!r}"
2055-
.format(orig_shape))
2056-
if init.shape() != Shape.cast(orig_shape):
2057-
raise ValueError("Constant returned by {!r}.const() must have the shape that "
2058-
"it casts to, {!r}, and not {!r}"
2059-
.format(orig_shape, Shape.cast(orig_shape),
2060-
init.shape()))
2061-
else:
2062-
if init is None:
2063-
init = 0
2064-
try:
2065-
init = Const.cast(init)
2066-
except TypeError:
2067-
raise TypeError("Initial value must be a constant-castable expression, not {!r}"
2068-
.format(orig_init))
2069-
# Avoid false positives for all-zeroes and all-ones
2070-
if orig_init is not None and not (isinstance(orig_init, int) and orig_init in (0, -1)):
2071-
if init.shape().signed and not self._signed:
2072-
warnings.warn(
2073-
message="Initial value {!r} is signed, but the signal shape is {!r}"
2074-
.format(orig_init, shape),
2075-
category=SyntaxWarning,
2076-
stacklevel=2)
2077-
elif (init.shape().width > self._width or
2078-
init.shape().width == self._width and
2079-
self._signed and not init.shape().signed):
2080-
warnings.warn(
2081-
message="Initial value {!r} will be truncated to the signal shape {!r}"
2082-
.format(orig_init, shape),
2083-
category=SyntaxWarning,
2084-
stacklevel=2)
2085-
self._init = Const(init.value, shape).value
2100+
self._init = _get_init_value(init, unsigned(1) if orig_shape is None else orig_shape)
20862101
self._reset_less = bool(reset_less)
20872102

2088-
if isinstance(orig_shape, range) and orig_init is not None and orig_init not in orig_shape:
2089-
if orig_init == orig_shape.stop:
2090-
raise SyntaxError(
2091-
f"Initial value {orig_init!r} equals the non-inclusive end of the signal "
2092-
f"shape {orig_shape!r}; this is likely an off-by-one error")
2093-
else:
2094-
raise SyntaxError(
2095-
f"Initial value {orig_init!r} is not within the signal shape {orig_shape!r}")
2096-
20972103
self._attrs = OrderedDict(() if attrs is None else attrs)
20982104

20992105
if isinstance(orig_shape, ShapeCastable):

amaranth/hdl/_mem.py

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

55
from .. import tracer
66
from ._ast import *
7+
from ._ast import _get_init_value
78
from ._ir import Elaboratable, Fragment, AlreadyElaborated
89
from ..utils import ceil_log2
910
from .._utils import deprecated, final
@@ -106,10 +107,11 @@ def __setitem__(self, index, value):
106107
for actual_index, actual_value in zip(indices, value):
107108
self[actual_index] = actual_value
108109
else:
110+
raw = _get_init_value(value, self._shape, "memory")
109111
if isinstance(self._shape, ShapeCastable):
110-
self._raw[index] = Const.cast(Const(value, self._shape)).value
112+
self._raw[index] = raw
111113
else:
112-
value = operator.index(value)
114+
value = raw
113115
# self._raw[index] assigned by the following line
114116
self._elems[index] = value
115117

tests/test_hdl_mem.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,33 @@ def test_row_elab(self):
3333
r"^Value \(memory-row \(memory-data data\) 0\) can only be used in simulator processes$"):
3434
m.d.comb += data[0].eq(1)
3535

36+
37+
class InitTestCase(FHDLTestCase):
38+
def test_ones(self):
39+
init = MemoryData.Init([-1, 12], shape=8, depth=2)
40+
self.assertEqual(list(init), [0xff, 12])
41+
init = MemoryData.Init([-1, -12], shape=signed(8), depth=2)
42+
self.assertEqual(list(init), [-1, -12])
43+
44+
def test_trunc(self):
45+
with self.assertWarnsRegex(SyntaxWarning,
46+
r"^Initial value -2 is signed, but the memory shape is unsigned\(8\)$"):
47+
init = MemoryData.Init([-2, 12], shape=8, depth=2)
48+
self.assertEqual(list(init), [0xfe, 12])
49+
with self.assertWarnsRegex(SyntaxWarning,
50+
r"^Initial value 258 will be truncated to the memory shape unsigned\(8\)$"):
51+
init = MemoryData.Init([258, 129], shape=8, depth=2)
52+
self.assertEqual(list(init), [2, 129])
53+
with self.assertWarnsRegex(SyntaxWarning,
54+
r"^Initial value 128 will be truncated to the memory shape signed\(8\)$"):
55+
init = MemoryData.Init([128], shape=signed(8), depth=1)
56+
self.assertEqual(list(init), [-128])
57+
with self.assertWarnsRegex(SyntaxWarning,
58+
r"^Initial value -129 will be truncated to the memory shape signed\(8\)$"):
59+
init = MemoryData.Init([-129], shape=signed(8), depth=1)
60+
self.assertEqual(list(init), [127])
61+
62+
3663
class MemoryTestCase(FHDLTestCase):
3764
def test_name(self):
3865
with _ignore_deprecated():
@@ -73,7 +100,7 @@ def test_init_wrong_type(self):
73100
with _ignore_deprecated():
74101
with self.assertRaisesRegex(TypeError,
75102
(r"^Memory initialization value at address 1: "
76-
r"'str' object cannot be interpreted as an integer$")):
103+
r"Initial value must be a constant-castable expression, not '0'$")):
77104
m = Memory(width=8, depth=4, init=[1, "0"])
78105

79106
def test_attrs(self):

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)