Skip to content

Commit be47958

Browse files
wanda-phiwhitequark
authored andcommitted
lib.data: bring Const's ArrayLayout capabilities up to par with views.
Fixes #1486.
1 parent 74a4096 commit be47958

File tree

3 files changed

+72
-6
lines changed

3 files changed

+72
-6
lines changed

amaranth/lib/data.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ def __getitem__(self, key):
814814
key += self.__layout.length
815815
shape = self.__layout.elem_shape
816816
value = self.__target[key * elem_width:(key + 1) * elem_width]
817-
elif isinstance(key, (int, Value, ValueCastable)):
817+
elif isinstance(key, (Value, ValueCastable)):
818818
shape = self.__layout.elem_shape
819819
value = self.__target.word_select(key, elem_width)
820820
else:
@@ -1044,16 +1044,38 @@ def __getitem__(self, key):
10441044
:meth:`.ShapeCastable.from_bits`. Usually this will be a :exc:`ValueError`.
10451045
"""
10461046
if isinstance(self.__layout, ArrayLayout):
1047-
if isinstance(key, (Value, ValueCastable)):
1047+
elem_width = Shape.cast(self.__layout.elem_shape).width
1048+
if isinstance(key, slice):
1049+
start, stop, stride = key.indices(self.__layout.length)
1050+
shape = ArrayLayout(self.__layout.elem_shape, len(range(start, stop, stride)))
1051+
if stride == 1:
1052+
value = (self.__target >> start * elem_width) & ((1 << elem_width * (stop - start)) - 1)
1053+
else:
1054+
value = 0
1055+
pos = 0
1056+
for index in range(start, stop, stride):
1057+
elem_value = (self.__target >> index * elem_width) & ((1 << elem_width) - 1)
1058+
value |= elem_value << pos
1059+
pos += elem_width
1060+
elif isinstance(key, int):
1061+
if key not in range(-self.__layout.length, self.__layout.length):
1062+
raise IndexError(f"Index {key} is out of range for array layout of length "
1063+
f"{self.__layout.length}")
1064+
if key < 0:
1065+
key += self.__layout.length
1066+
shape = self.__layout.elem_shape
1067+
value = (self.__target >> key * elem_width) & ((1 << elem_width) - 1)
1068+
elif isinstance(key, (Value, ValueCastable)):
10481069
return View(self.__layout, self.as_value())[key]
1049-
if not isinstance(key, int):
1070+
else:
10501071
raise TypeError(
10511072
f"Constant with array layout may only be indexed with an integer or a value, "
10521073
f"not {key!r}")
1053-
shape = self.__layout.elem_shape
1054-
elem_width = Shape.cast(self.__layout.elem_shape).width
1055-
value = (self.__target >> key * elem_width) & ((1 << elem_width) - 1)
10561074
else:
1075+
if isinstance(key, slice):
1076+
raise TypeError(
1077+
"Non-array constant cannot be indexed with a slice; did you mean to call "
1078+
"`.as_value()` first?")
10571079
if isinstance(key, (Value, ValueCastable)):
10581080
raise TypeError(
10591081
f"Only constants with array layout, not {self.__layout!r}, may be indexed with "
@@ -1096,6 +1118,12 @@ def __getattr__(self, name):
10961118
f"may only be accessed by indexing")
10971119
return item
10981120

1121+
def __len__(self):
1122+
if not isinstance(self.__layout, ArrayLayout):
1123+
raise TypeError(
1124+
f"`len()` can only be used on constants of array layout, not {self.__layout!r}")
1125+
return self.__layout.length
1126+
10991127
def __eq__(self, other):
11001128
if isinstance(other, View) and self.__layout == other._View__layout:
11011129
return self.as_value() == other._View__target

docs/changes.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,20 @@ Platform integration changes
6666
* Changed: the Gowin platform now uses ``nextpnr-himbaechel`` rather than ``nextpnr-gowin``.
6767

6868

69+
Version 0.5.2 (unreleased)
70+
==========================
71+
72+
73+
Standard library changes
74+
------------------------
75+
76+
.. currentmodule:: amaranth.lib
77+
78+
* Added: constants of :class:`amaranth.lib.data.ArrayLayout` can be indexed with negative integers or slices.
79+
* Added: :py:`len()` works on constants of :class:`amaranth.lib.data.ArrayLayout`.
80+
* Added: constants of :class:`amaranth.lib.data.ArrayLayout` are iterable.
81+
82+
6983
Version 0.5.1
7084
=============
7185

tests/test_lib_data.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,12 +939,28 @@ def test_getitem(self):
939939
self.assertEqual(v["q"], -1)
940940
self.assertEqual(v["r"][0], 3)
941941
self.assertEqual(v["r"][1], 2)
942+
self.assertEqual(v["r"][-2], 3)
943+
self.assertEqual(v["r"][-1], 2)
942944
self.assertRepr(v["r"][i], "(part (const 4'd11) (sig i) 2 2)")
943945
self.assertEqual(v["t"][0], data.Const(l, 2))
944946
self.assertEqual(v["t"][1], data.Const(l, 2))
945947
self.assertEqual(v["t"][0]["u"], 0)
946948
self.assertEqual(v["t"][1]["v"], 1)
947949

950+
def test_getitem_slice(self):
951+
def A(n):
952+
return data.ArrayLayout(unsigned(4), n)
953+
v = data.Const(data.ArrayLayout(unsigned(4), 5), 0xabcde)
954+
self.assertEqual(v[1:3], data.Const(A(2), 0xcd))
955+
self.assertEqual(v[2:], data.Const(A(3), 0xabc))
956+
self.assertEqual(v[:-2], data.Const(A(3), 0xcde))
957+
self.assertEqual(v[-1:], data.Const(A(1), 0xa))
958+
self.assertEqual(v[::-1], data.Const(A(5), 0xedcba))
959+
960+
def test_array_iter(self):
961+
v = data.Const(data.ArrayLayout(unsigned(4), 5), 0xabcde)
962+
self.assertEqual(list(v), [0xe, 0xd, 0xc, 0xb, 0xa])
963+
948964
def test_getitem_custom_call(self):
949965
class Reverser(ShapeCastable):
950966
def as_shape(self):
@@ -1105,6 +1121,14 @@ def test_compare(self):
11051121
r"a constant with the same layout, not .*$"):
11061122
c5 != [0,1,2,3,4]
11071123

1124+
def test_len(self):
1125+
c1 = data.Const(data.StructLayout({"a": unsigned(2)}), 2)
1126+
with self.assertRaisesRegex(TypeError,
1127+
r"^`len\(\)` can only be used on constants of array layout, not StructLayout.*$"):
1128+
len(c1)
1129+
c2 = data.Const(data.ArrayLayout(2, 3), 0x12)
1130+
self.assertEqual(len(c2), 3)
1131+
11081132
def test_operator(self):
11091133
s1 = data.Const(data.StructLayout({"a": unsigned(2)}), 2)
11101134
s2 = Signal(unsigned(2))

0 commit comments

Comments
 (0)