Skip to content

Commit d218273

Browse files
charlottiawhitequark
authored andcommitted
hdl.ast: deprecate Repl and remove from AST; add Value.replicate.
1 parent b1cce87 commit d218273

File tree

13 files changed

+83
-95
lines changed

13 files changed

+83
-95
lines changed

amaranth/back/rtlil.py

-6
Original file line numberDiff line numberDiff line change
@@ -632,9 +632,6 @@ def on_Part(self, value):
632632
}, src=_src(value.src_loc))
633633
return res
634634

635-
def on_Repl(self, value):
636-
return "{{ {} }}".format(" ".join(self(value.value) for _ in range(value.count)))
637-
638635

639636
class _LHSValueCompiler(_ValueCompiler):
640637
def on_Const(self, value):
@@ -695,9 +692,6 @@ def on_Part(self, value):
695692
range(1 << len(value.offset))[:max_branches],
696693
value.src_loc)
697694

698-
def on_Repl(self, value):
699-
raise TypeError # :nocov:
700-
701695

702696
class _StatementCompiler(xfrm.StatementVisitor):
703697
def __init__(self, state, rhs_compiler, lhs_compiler):

amaranth/compat/fhdl/structure.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ def Constant(value, bits_sign=None):
6969
return Const(value, bits_sign)
7070

7171

72-
@deprecated("instead of `Replicate`, use `Repl`")
72+
@deprecated("instead of `Replicate(v, n)`, use `v.replicate(n)`")
7373
def Replicate(v, n):
74-
return Repl(v, n)
74+
return v.replicate(n)
7575

7676

7777
@extend(Const)

amaranth/hdl/ast.py

+33-29
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,29 @@ def rotate_right(self, amount):
539539
amount %= len(self)
540540
return Cat(self[amount:], self[:amount])
541541

542+
def replicate(self, count):
543+
"""Replication.
544+
545+
A ``Value`` is replicated (repeated) several times to be used
546+
on the RHS of assignments::
547+
548+
len(v.replicate(n)) == len(v) * n
549+
550+
Parameters
551+
----------
552+
count : int
553+
Number of replications.
554+
555+
Returns
556+
-------
557+
Value, out
558+
Replicated value.
559+
"""
560+
if not isinstance(count, int) or count < 0:
561+
raise TypeError("Replication count must be a non-negative integer, not {!r}"
562+
.format(count))
563+
return Cat(self for _ in range(count))
564+
542565
def eq(self, value):
543566
"""Assignment.
544567
@@ -914,8 +937,9 @@ def __repr__(self):
914937
return "(cat {})".format(" ".join(map(repr, self.parts)))
915938

916939

917-
@final
918-
class Repl(Value):
940+
# TODO(amaranth-0.5): remove
941+
@deprecated("instead of `Repl(value, count)`, use `value.replicate(count)`")
942+
def Repl(value, count):
919943
"""Replicate a value
920944
921945
An input value is replicated (repeated) several times
@@ -932,31 +956,16 @@ class Repl(Value):
932956
933957
Returns
934958
-------
935-
Repl, out
959+
Value, out
936960
Replicated value.
937961
"""
938-
def __init__(self, value, count, *, src_loc_at=0):
939-
if not isinstance(count, int) or count < 0:
940-
raise TypeError("Replication count must be a non-negative integer, not {!r}"
941-
.format(count))
962+
if isinstance(value, int) and value not in [0, 1]:
963+
warnings.warn("Value argument of Repl() is a bare integer {} used in bit vector "
964+
"context; consider specifying explicit width using C({}, {}) instead"
965+
.format(value, value, bits_for(value)),
966+
SyntaxWarning, stacklevel=3)
942967

943-
super().__init__(src_loc_at=src_loc_at)
944-
if isinstance(value, int) and value not in [0, 1]:
945-
warnings.warn("Value argument of Repl() is a bare integer {} used in bit vector "
946-
"context; consider specifying explicit width using C({}, {}) instead"
947-
.format(value, value, bits_for(value)),
948-
SyntaxWarning, stacklevel=2 + src_loc_at)
949-
self.value = Value.cast(value)
950-
self.count = count
951-
952-
def shape(self):
953-
return Shape(len(self.value) * self.count)
954-
955-
def _rhs_signals(self):
956-
return self.value._rhs_signals()
957-
958-
def __repr__(self):
959-
return "(repl {!r} {})".format(self.value, self.count)
968+
return Value.cast(value).replicate(count)
960969

961970

962971
class _SignalMeta(ABCMeta):
@@ -1728,8 +1737,6 @@ def __init__(self, value):
17281737
tuple(ValueKey(e) for e in self.value._iter_as_values())))
17291738
elif isinstance(self.value, Sample):
17301739
self._hash = hash((ValueKey(self.value.value), self.value.clocks, self.value.domain))
1731-
elif isinstance(self.value, Repl):
1732-
self._hash = hash((ValueKey(self.value.value), self.value.count))
17331740
elif isinstance(self.value, Initial):
17341741
self._hash = 0
17351742
else: # :nocov:
@@ -1769,9 +1776,6 @@ def __eq__(self, other):
17691776
return (len(self.value.parts) == len(other.value.parts) and
17701777
all(ValueKey(a) == ValueKey(b)
17711778
for a, b in zip(self.value.parts, other.value.parts)))
1772-
elif isinstance(self.value, Repl):
1773-
return (ValueKey(self.value.value) == ValueKey(other.value.value) and
1774-
self.value.count == other.value.count)
17751779
elif isinstance(self.value, ArrayProxy):
17761780
return (ValueKey(self.value.index) == ValueKey(other.value.index) and
17771781
len(self.value.elems) == len(other.value.elems) and

amaranth/hdl/mem.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ def elaborate(self, platform):
281281
p_CLK_POLARITY=1,
282282
p_PRIORITY=0,
283283
i_CLK=ClockSignal(self.domain),
284-
i_EN=Cat(Repl(en_bit, self.granularity) for en_bit in self.en),
284+
i_EN=Cat(en_bit.replicate(self.granularity) for en_bit in self.en),
285285
i_ADDR=self.addr,
286286
i_DATA=self.data,
287287
)

amaranth/hdl/xfrm.py

-12
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,6 @@ def on_Part(self, value):
6262
def on_Cat(self, value):
6363
pass # :nocov:
6464

65-
@abstractmethod
66-
def on_Repl(self, value):
67-
pass # :nocov:
68-
6965
@abstractmethod
7066
def on_ArrayProxy(self, value):
7167
pass # :nocov:
@@ -106,8 +102,6 @@ def on_value(self, value):
106102
new_value = self.on_Part(value)
107103
elif type(value) is Cat:
108104
new_value = self.on_Cat(value)
109-
elif type(value) is Repl:
110-
new_value = self.on_Repl(value)
111105
elif type(value) is ArrayProxy:
112106
new_value = self.on_ArrayProxy(value)
113107
elif type(value) is Sample:
@@ -156,9 +150,6 @@ def on_Part(self, value):
156150
def on_Cat(self, value):
157151
return Cat(self.on_value(o) for o in value.parts)
158152

159-
def on_Repl(self, value):
160-
return Repl(self.on_value(value.value), value.count)
161-
162153
def on_ArrayProxy(self, value):
163154
return ArrayProxy([self.on_value(elem) for elem in value._iter_as_values()],
164155
self.on_value(value.index))
@@ -374,9 +365,6 @@ def on_Cat(self, value):
374365
for o in value.parts:
375366
self.on_value(o)
376367

377-
def on_Repl(self, value):
378-
self.on_value(value.value)
379-
380368
def on_ArrayProxy(self, value):
381369
for elem in value._iter_as_values():
382370
self.on_value(elem)

amaranth/sim/_pyrtl.py

-15
Original file line numberDiff line numberDiff line change
@@ -213,18 +213,6 @@ def on_Cat(self, value):
213213
return f"({' | '.join(gen_parts)})"
214214
return f"0"
215215

216-
def on_Repl(self, value):
217-
part_mask = (1 << len(value.value)) - 1
218-
gen_part = self.emitter.def_var("repl", f"{part_mask:#x} & {self(value.value)}")
219-
gen_parts = []
220-
offset = 0
221-
for _ in range(value.count):
222-
gen_parts.append(f"({gen_part} << {offset})")
223-
offset += len(value.value)
224-
if gen_parts:
225-
return f"({' | '.join(gen_parts)})"
226-
return f"0"
227-
228216
def on_ArrayProxy(self, value):
229217
index_mask = (1 << len(value.index)) - 1
230218
gen_index = self.emitter.def_var("rhs_index", f"{index_mask:#x} & {self(value.index)}")
@@ -325,9 +313,6 @@ def gen(arg):
325313
offset += len(part)
326314
return gen
327315

328-
def on_Repl(self, value):
329-
raise TypeError # :nocov:
330-
331316
def on_ArrayProxy(self, value):
332317
def gen(arg):
333318
index_mask = (1 << len(value.index)) - 1

amaranth/vendor/intel.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ def get_oneg(o):
375375
def _get_oereg(m, pin):
376376
# altiobuf_ requires an output enable signal for each pin, but pin.oe is 1 bit wide.
377377
if pin.xdr == 0:
378-
return Repl(pin.oe, pin.width)
378+
return pin.oe.replicate(pin.width)
379379
elif pin.xdr in (1, 2):
380380
oe_reg = Signal(pin.width, name="{}_oe_reg".format(pin.name))
381381
oe_reg.attrs["useioff"] = "1"

amaranth/vendor/lattice_ecp5.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ def get_oneg(a, invert):
528528
if "o" in pin.dir:
529529
o = pin_o
530530
if pin.dir in ("oe", "io"):
531-
t = Repl(~pin.oe, pin.width)
531+
t = (~pin.oe).replicate(pin.width)
532532
elif pin.xdr == 1:
533533
if "i" in pin.dir:
534534
get_ireg(pin.i_clk, i, pin_i)

docs/changes.rst

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Implemented RFCs
3737
.. _RFC 5: https://amaranth-lang.org/rfcs/0005-remove-const-normalize.html
3838
.. _RFC 8: https://amaranth-lang.org/rfcs/0008-aggregate-extensibility.html
3939
.. _RFC 9: https://amaranth-lang.org/rfcs/0009-const-init-shape-castable.html
40+
.. _RFC 10: https://amaranth-lang.org/rfcs/0010-move-repl-to-value.html
4041
.. _RFC 15: https://amaranth-lang.org/rfcs/0015-lifting-shape-castables.html
4142

4243
* `RFC 1`_: Aggregate data structure library
@@ -45,6 +46,7 @@ Implemented RFCs
4546
* `RFC 5`_: Remove Const.normalize
4647
* `RFC 8`_: Aggregate extensibility
4748
* `RFC 9`_: Constant initialization for shape-castable objects
49+
* `RFC 10`_: Move Repl to Value.replicate
4850
* `RFC 15`_: Lifting shape-castable objects
4951

5052

@@ -57,12 +59,14 @@ Language changes
5759
* Added: :meth:`Value.as_signed` and :meth:`Value.as_unsigned` can be used on left-hand side of assignment (with no difference in behavior).
5860
* Added: :meth:`Const.cast`. (`RFC 4`_)
5961
* Added: :meth:`Value.matches` and ``with m.Case():`` accept any constant-castable objects. (`RFC 4`_)
62+
* Added: :meth:`Value.replicate`, superseding :class:`Repl`. (`RFC 10`_)
6063
* Changed: creating a :class:`Signal` with a shape that is a :class:`ShapeCastable` implementing :meth:`ShapeCastable.__call__` wraps the returned object using that method. (`RFC 15`_)
6164
* Changed: :meth:`Value.cast` casts :class:`ValueCastable` objects recursively.
6265
* Changed: :meth:`Value.cast` treats instances of classes derived from both :class:`enum.Enum` and :class:`int` (including :class:`enum.IntEnum`) as enumerations rather than integers.
6366
* Changed: :meth:`Value.matches` with an empty list of patterns returns ``Const(1)`` rather than ``Const(0)``, to match the behavior of ``with m.Case():``.
6467
* Changed: :class:`Cat` warns if an enumeration without an explicitly specified shape is used. (`RFC 3`_)
6568
* Deprecated: :meth:`Const.normalize`. (`RFC 5`_)
69+
* Deprecated: :class:`Repl`; use :meth:`Value.replicate` instead. (`RFC 10`_)
6670
* Removed: (deprecated in 0.1) casting of :class:`Shape` to and from a ``(width, signed)`` tuple.
6771
* Removed: (deprecated in 0.3) :class:`ast.UserValue`.
6872
* Removed: (deprecated in 0.3) support for ``# nmigen:`` linter instructions at the beginning of file.

docs/lang.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,7 @@ Operation Description Notes
705705
``a.bit_select(b, w)`` overlapping part select with variable offset
706706
``a.word_select(b, w)`` non-overlapping part select with variable offset
707707
``Cat(a, b)`` concatenation [#opS3]_
708-
``Repl(a, n)`` replication
708+
``a.replicate(n)`` replication
709709
======================= ================================================ ======
710710

711711
.. [#opS1] Words "length" and "width" have the same meaning when talking about Amaranth values. Conventionally, "width" is used.
@@ -718,7 +718,7 @@ For the operators introduced by Amaranth, the following table explains them in t
718718
Amaranth operation Equivalent Python code
719719
======================= ======================
720720
``Cat(a, b)`` ``a + b``
721-
``Repl(a, n)`` ``a * n``
721+
``a.replicate(n)`` ``a * n``
722722
``a.bit_select(b, w)`` ``a[b:b+w]``
723723
``a.word_select(b, w)`` ``a[b*w:b*w+w]``
724724
======================= ======================

tests/test_hdl_ast.py

+21-18
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,23 @@ def test_rotate_right_wrong(self):
353353
r"^Rotate amount must be an integer, not 'str'$"):
354354
Const(31).rotate_right("str")
355355

356+
def test_replicate_shape(self):
357+
s1 = Const(10).replicate(3)
358+
self.assertEqual(s1.shape(), unsigned(12))
359+
self.assertIsInstance(s1.shape(), Shape)
360+
s2 = Const(10).replicate(0)
361+
self.assertEqual(s2.shape(), unsigned(0))
362+
363+
def test_replicate_count_wrong(self):
364+
with self.assertRaises(TypeError):
365+
Const(10).replicate(-1)
366+
with self.assertRaises(TypeError):
367+
Const(10).replicate("str")
368+
369+
def test_replicate_repr(self):
370+
s = Const(10).replicate(3)
371+
self.assertEqual(repr(s), "(cat (const 4'd10) (const 4'd10) (const 4'd10))")
372+
356373

357374
class ConstTestCase(FHDLTestCase):
358375
def test_shape(self):
@@ -863,33 +880,19 @@ def test_int_wrong(self):
863880

864881

865882
class ReplTestCase(FHDLTestCase):
866-
def test_shape(self):
867-
s1 = Repl(Const(10), 3)
868-
self.assertEqual(s1.shape(), unsigned(12))
869-
self.assertIsInstance(s1.shape(), Shape)
870-
s2 = Repl(Const(10), 0)
871-
self.assertEqual(s2.shape(), unsigned(0))
872-
873-
def test_count_wrong(self):
874-
with self.assertRaises(TypeError):
875-
Repl(Const(10), -1)
876-
with self.assertRaises(TypeError):
877-
Repl(Const(10), "str")
878-
879-
def test_repr(self):
880-
s = Repl(Const(10), 3)
881-
self.assertEqual(repr(s), "(repl (const 4'd10) 3)")
882-
883+
@_ignore_deprecated
883884
def test_cast(self):
884885
r = Repl(0, 3)
885-
self.assertEqual(repr(r), "(repl (const 1'd0) 3)")
886+
self.assertEqual(repr(r), "(cat (const 1'd0) (const 1'd0) (const 1'd0))")
886887

888+
@_ignore_deprecated
887889
def test_int_01(self):
888890
with warnings.catch_warnings():
889891
warnings.filterwarnings(action="error", category=SyntaxWarning)
890892
Repl(0, 3)
891893
Repl(1, 3)
892894

895+
@_ignore_deprecated
893896
def test_int_wrong(self):
894897
with self.assertWarnsRegex(SyntaxWarning,
895898
r"^Value argument of Repl\(\) is a bare integer 2 used in bit vector context; "

tests/test_hdl_xfrm.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,22 @@ def test_enable_write_port(self):
556556
mem = Memory(width=8, depth=4)
557557
f = EnableInserter(self.c1)(mem.write_port()).elaborate(platform=None)
558558
self.assertRepr(f.named_ports["EN"][0], """
559-
(m (sig c1) (cat (repl (slice (sig mem_w_en) 0:1) 8)) (const 8'd0))
559+
(m
560+
(sig c1)
561+
(cat
562+
(cat
563+
(slice (sig mem_w_en) 0:1)
564+
(slice (sig mem_w_en) 0:1)
565+
(slice (sig mem_w_en) 0:1)
566+
(slice (sig mem_w_en) 0:1)
567+
(slice (sig mem_w_en) 0:1)
568+
(slice (sig mem_w_en) 0:1)
569+
(slice (sig mem_w_en) 0:1)
570+
(slice (sig mem_w_en) 0:1)
571+
)
572+
)
573+
(const 8'd0)
574+
)
560575
""")
561576

562577

tests/test_sim.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,8 @@ def test_record(self):
289289
stmt = lambda y, a: [rec.eq(a), y.eq(rec)]
290290
self.assertStatement(stmt, [C(0b101, 3)], C(0b101, 3))
291291

292-
def test_repl(self):
293-
stmt = lambda y, a: y.eq(Repl(a, 3))
292+
def test_replicate(self):
293+
stmt = lambda y, a: y.eq(a.replicate(3))
294294
self.assertStatement(stmt, [C(0b10, 2)], C(0b101010, 6))
295295

296296
def test_array(self):
@@ -879,11 +879,6 @@ def test_bug_325(self):
879879
dut.d.comb += Signal().eq(Cat())
880880
Simulator(dut).run()
881881

882-
def test_bug_325_bis(self):
883-
dut = Module()
884-
dut.d.comb += Signal().eq(Repl(Const(1), 0))
885-
Simulator(dut).run()
886-
887882
def test_bug_473(self):
888883
sim = Simulator(Module())
889884
def process():

0 commit comments

Comments
 (0)