Skip to content

Commit 3b8ff38

Browse files
committed
Implement RFC 61: Minimal streams.
1 parent f820e8e commit 3b8ff38

File tree

4 files changed

+189
-1
lines changed

4 files changed

+189
-1
lines changed

amaranth/lib/stream.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from ..hdl import *
2+
from .._utils import final
3+
from . import wiring
4+
from .wiring import In, Out
5+
6+
7+
@final
8+
class Signature(wiring.Signature):
9+
def __init__(self, payload_shape: ShapeLike, *, always_valid=False, always_ready=False):
10+
Shape.cast(payload_shape)
11+
self._payload_shape = payload_shape
12+
self._always_valid = bool(always_valid)
13+
self._always_ready = bool(always_ready)
14+
15+
super().__init__({
16+
"payload": Out(payload_shape),
17+
"valid": Out(1),
18+
"ready": In(1)
19+
})
20+
21+
# payload_shape intentionally not introspectable (for now)
22+
23+
@property
24+
def always_valid(self):
25+
return self._always_valid
26+
27+
@property
28+
def always_ready(self):
29+
return self._always_ready
30+
31+
def __eq__(self, other):
32+
return (type(other) is type(self) and
33+
other._payload_shape == self._payload_shape and
34+
other.always_valid == self.always_valid and
35+
other.always_ready == self.always_ready)
36+
37+
def create(self, *, path=None, src_loc_at=0):
38+
return Interface(self, path=path, src_loc_at=1 + src_loc_at)
39+
40+
def __repr__(self):
41+
always_valid_repr = "" if not self._always_valid else ", always_valid=True"
42+
always_ready_repr = "" if not self._always_ready else ", always_ready=True"
43+
return f"stream.Signature({self._payload_shape!r}{always_valid_repr}{always_ready_repr})"
44+
45+
46+
@final
47+
class Interface:
48+
payload: Signal
49+
valid: 'Signal | Const'
50+
ready: 'Signal | Const'
51+
52+
def __init__(self, signature: Signature, *, path=None, src_loc_at=0):
53+
if not isinstance(signature, Signature):
54+
raise TypeError(f"Signature of stream.Interface must be a stream.Signature, not "
55+
f"{signature!r}")
56+
self._signature = signature
57+
self.__dict__.update(signature.members.create(path=path, src_loc_at=1 + src_loc_at))
58+
if signature.always_valid:
59+
self.valid = Const(1)
60+
if signature.always_ready:
61+
self.ready = Const(1)
62+
63+
@property
64+
def signature(self):
65+
return self._signature
66+
67+
@property
68+
def p(self):
69+
return self.payload
70+
71+
def __repr__(self):
72+
return f"stream.Interface(payload={self.payload!r}, valid={self.valid!r}, ready={self.ready!r})"

docs/stdlib.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Standard library
44
The :mod:`amaranth.lib` module, also known as the standard library, provides modules that falls into one of the three categories:
55

66
1. Modules that will used by essentially all idiomatic Amaranth code, and are necessary for interoperability. This includes :mod:`amaranth.lib.enum` (enumerations), :mod:`amaranth.lib.data` (data structures), and :mod:`amaranth.lib.wiring` (interfaces and components).
7-
2. Modules that abstract common functionality whose implementation differs between hardware platforms. This includes :mod:`amaranth.lib.cdc`, :mod:`amaranth.lib.memory`.
7+
2. Modules that abstract common functionality whose implementation differs between hardware platforms. This includes :mod:`amaranth.lib.memory`, :mod:`amaranth.lib.stream`, :mod:`amaranth.lib.cdc`.
88
3. Modules that have essentially one correct implementation and are of broad utility in digital designs. This includes :mod:`amaranth.lib.coding`, :mod:`amaranth.lib.fifo`, and :mod:`amaranth.lib.crc`.
99

1010
As part of the Amaranth backwards compatibility guarantee, any behaviors described in these documents will not change from a version to another without at least one version including a warning about the impending change. Any nontrivial change to these behaviors must also go through the public review as a part of the `Amaranth Request for Comments process <https://amaranth-lang.org/rfcs/>`_.
@@ -18,6 +18,7 @@ The Amaranth standard library is separate from the Amaranth language: everything
1818
stdlib/data
1919
stdlib/wiring
2020
stdlib/memory
21+
stdlib/stream
2122
stdlib/cdc
2223
stdlib/coding
2324
stdlib/fifo

docs/stdlib/stream.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Data streams
2+
------------
3+
4+
.. py:module:: amaranth.lib.stream
5+
6+
.. todo::
7+
8+
Write this section.

tests/test_lib_stream.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
from amaranth.hdl import *
2+
from amaranth.lib import stream, wiring
3+
from amaranth.lib.wiring import In, Out
4+
5+
from .utils import *
6+
7+
8+
class StreamTestCase(FHDLTestCase):
9+
def test_nav_nar(self):
10+
sig = stream.Signature(2)
11+
self.assertRepr(sig, f"stream.Signature(2)")
12+
self.assertEqual(sig.always_valid, False)
13+
self.assertEqual(sig.always_ready, False)
14+
self.assertEqual(sig.members, wiring.SignatureMembers({
15+
"payload": Out(2),
16+
"valid": Out(1),
17+
"ready": In(1)
18+
}))
19+
intf = sig.create()
20+
self.assertRepr(intf, f"stream.Interface(payload=(sig intf__payload), valid=(sig intf__valid), ready=(sig intf__ready))")
21+
self.assertIs(intf.signature, sig)
22+
self.assertIsInstance(intf.payload, Signal)
23+
self.assertIs(intf.p, intf.payload)
24+
self.assertIsInstance(intf.valid, Signal)
25+
self.assertIsInstance(intf.ready, Signal)
26+
27+
def test_av_nar(self):
28+
sig = stream.Signature(2, always_valid=True)
29+
self.assertRepr(sig, f"stream.Signature(2, always_valid=True)")
30+
self.assertEqual(sig.always_valid, True)
31+
self.assertEqual(sig.always_ready, False)
32+
self.assertEqual(sig.members, wiring.SignatureMembers({
33+
"payload": Out(2),
34+
"valid": Out(1),
35+
"ready": In(1)
36+
}))
37+
intf = sig.create()
38+
self.assertRepr(intf, f"stream.Interface(payload=(sig intf__payload), valid=(const 1'd1), ready=(sig intf__ready))")
39+
self.assertIs(intf.signature, sig)
40+
self.assertIsInstance(intf.payload, Signal)
41+
self.assertIs(intf.p, intf.payload)
42+
self.assertIsInstance(intf.valid, Const)
43+
self.assertEqual(intf.valid.value, 1)
44+
self.assertIsInstance(intf.ready, Signal)
45+
46+
def test_nav_ar(self):
47+
sig = stream.Signature(2, always_ready=True)
48+
self.assertRepr(sig, f"stream.Signature(2, always_ready=True)")
49+
self.assertEqual(sig.always_valid, False)
50+
self.assertEqual(sig.always_ready, True)
51+
self.assertEqual(sig.members, wiring.SignatureMembers({
52+
"payload": Out(2),
53+
"valid": Out(1),
54+
"ready": In(1)
55+
}))
56+
intf = sig.create()
57+
self.assertRepr(intf, f"stream.Interface(payload=(sig intf__payload), valid=(sig intf__valid), ready=(const 1'd1))")
58+
self.assertIs(intf.signature, sig)
59+
self.assertIsInstance(intf.payload, Signal)
60+
self.assertIs(intf.p, intf.payload)
61+
self.assertIsInstance(intf.valid, Signal)
62+
self.assertIsInstance(intf.ready, Const)
63+
self.assertEqual(intf.ready.value, 1)
64+
65+
def test_av_ar(self):
66+
sig = stream.Signature(2, always_valid=True, always_ready=True)
67+
self.assertRepr(sig, f"stream.Signature(2, always_valid=True, always_ready=True)")
68+
self.assertEqual(sig.always_valid, True)
69+
self.assertEqual(sig.always_ready, True)
70+
self.assertEqual(sig.members, wiring.SignatureMembers({
71+
"payload": Out(2),
72+
"valid": Out(1),
73+
"ready": In(1)
74+
}))
75+
intf = sig.create()
76+
self.assertRepr(intf, f"stream.Interface(payload=(sig intf__payload), valid=(const 1'd1), ready=(const 1'd1))")
77+
self.assertIs(intf.signature, sig)
78+
self.assertIsInstance(intf.payload, Signal)
79+
self.assertIs(intf.p, intf.payload)
80+
self.assertIsInstance(intf.valid, Const)
81+
self.assertEqual(intf.valid.value, 1)
82+
self.assertIsInstance(intf.ready, Const)
83+
self.assertEqual(intf.ready.value, 1)
84+
85+
def test_eq(self):
86+
sig_nav_nar = stream.Signature(2)
87+
sig_av_nar = stream.Signature(2, always_valid=True)
88+
sig_nav_ar = stream.Signature(2, always_ready=True)
89+
sig_av_ar = stream.Signature(2, always_valid=True, always_ready=True)
90+
sig_av_ar2 = stream.Signature(3, always_valid=True, always_ready=True)
91+
self.assertNotEqual(sig_nav_nar, None)
92+
self.assertEqual(sig_nav_nar, sig_nav_nar)
93+
self.assertEqual(sig_av_nar, sig_av_nar)
94+
self.assertEqual(sig_nav_ar, sig_nav_ar)
95+
self.assertEqual(sig_av_ar, sig_av_ar)
96+
self.assertEqual(sig_av_ar2, sig_av_ar2)
97+
self.assertNotEqual(sig_nav_nar, sig_av_nar)
98+
self.assertNotEqual(sig_av_nar, sig_nav_ar)
99+
self.assertNotEqual(sig_nav_ar, sig_av_ar)
100+
self.assertNotEqual(sig_av_ar, sig_nav_nar)
101+
self.assertNotEqual(sig_av_ar, sig_av_ar2)
102+
103+
def test_interface_create_bad(self):
104+
with self.assertRaisesRegex(TypeError,
105+
r"^Signature of stream\.Interface must be a stream\.Signature, not "
106+
r"Signature\(\{\}\)$"):
107+
stream.Interface(wiring.Signature({}))

0 commit comments

Comments
 (0)