Skip to content

Commit f524dd0

Browse files
wanda-phiwhitequark
authored andcommitted
lib.io, build.res: Make Pin and related objects interfaces.
Fixes #1040.
1 parent c30585b commit f524dd0

File tree

4 files changed

+249
-266
lines changed

4 files changed

+249
-266
lines changed

amaranth/build/res.py

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
from collections import OrderedDict
2-
import warnings
32

43
from ..hdl._ast import *
5-
with warnings.catch_warnings():
6-
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
7-
from ..hdl.rec import *
84
from ..lib.io import *
95
from ..lib import wiring
106

@@ -106,7 +102,7 @@ def merge_options(subsignal, dir, xdr):
106102
.format(subsignal.ios[0], xdr))
107103
return dir, xdr
108104

109-
def resolve(resource, dir, xdr, name, attrs):
105+
def resolve(resource, dir, xdr, path, attrs):
110106
for attr_key, attr_value in attrs.items():
111107
if hasattr(attr_value, "__call__"):
112108
attr_value = attr_value(self)
@@ -117,18 +113,21 @@ def resolve(resource, dir, xdr, name, attrs):
117113
attrs[attr_key] = attr_value
118114

119115
if isinstance(resource.ios[0], Subsignal):
120-
fields = OrderedDict()
116+
members = OrderedDict()
117+
sig_members = OrderedDict()
121118
for sub in resource.ios:
122-
fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name],
123-
name=f"{name}__{sub.name}",
119+
member = resolve(sub, dir[sub.name], xdr[sub.name],
120+
path=path + (sub.name,),
124121
attrs={**attrs, **sub.attrs})
125-
rec = Record([
126-
(f_name, f.layout) for (f_name, f) in fields.items()
127-
], fields=fields, name=name)
128-
rec.signature = wiring.Signature({
129-
f_name: wiring.Out(f.signature) for (f_name, f) in fields.items()
130-
})
131-
return rec
122+
members[sub.name] = member
123+
sig_members[sub.name] = wiring.Out(member.signature)
124+
signature = wiring.Signature(sig_members)
125+
# Provide members ourselves instead of having the constructor
126+
# create ones for us.
127+
intf = object.__new__(wiring.PureInterface)
128+
intf.signature = signature
129+
intf.__dict__.update(members)
130+
return intf
132131

133132
elif isinstance(resource.ios[0], (Pins, DiffPairs)):
134133
phys = resource.ios[0]
@@ -137,34 +136,30 @@ def resolve(resource, dir, xdr, name, attrs):
137136
# ignore it as well.
138137
if isinstance(phys, Pins):
139138
phys_names = phys.names
140-
port = Record([("io", len(phys))], name=name)
141-
port.signature = wiring.Signature({"io": wiring.In(len(phys))})
139+
port = wiring.Signature({"io": wiring.In(len(phys))}).create(path=path)
142140
if isinstance(phys, DiffPairs):
143141
phys_names = []
144-
rec_members = []
145142
sig_members = {}
146143
if not self.should_skip_port_component(None, attrs, "p"):
147144
phys_names += phys.p.names
148-
rec_members.append(("p", len(phys)))
149145
sig_members["p"] = wiring.In(len(phys))
150146
if not self.should_skip_port_component(None, attrs, "n"):
151147
phys_names += phys.n.names
152-
rec_members.append(("n", len(phys)))
153148
sig_members["n"] = wiring.In(len(phys))
154-
port = Record(rec_members, name=name)
155-
port.signature = wiring.Signature(sig_members)
149+
port = wiring.Signature(sig_members).create(path=path)
156150
if dir == "-":
157151
pin = None
158152
else:
159-
pin = wiring.flipped(Pin(len(phys), dir, xdr=xdr, name=name))
153+
pin = wiring.flipped(Pin(len(phys), dir, xdr=xdr, path=path))
160154

161155
for phys_name in phys_names:
162156
if phys_name in self._phys_reqd:
163157
raise ResourceError("Resource component {} uses physical pin {}, but it "
164158
"is already used by resource component {} that was "
165159
"requested earlier"
166-
.format(name, phys_name, self._phys_reqd[phys_name]))
167-
self._phys_reqd[phys_name] = name
160+
.format(".".join(path), phys_name,
161+
".".join(self._phys_reqd[phys_name])))
162+
self._phys_reqd[phys_name] = path
168163

169164
self._ports.append((resource, pin, port, attrs))
170165

@@ -178,7 +173,7 @@ def resolve(resource, dir, xdr, name, attrs):
178173

179174
value = resolve(resource,
180175
*merge_options(resource, dir, xdr),
181-
name=f"{resource.name}_{resource.number}",
176+
path=(f"{resource.name}_{resource.number}",),
182177
attrs=resource.attrs)
183178
self._requested[resource.name, resource.number] = value
184179
return value

amaranth/lib/io.py

Lines changed: 78 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,18 @@
1-
import warnings
2-
31
from .. import *
4-
with warnings.catch_warnings():
5-
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
6-
from ..hdl.rec import *
7-
from ..lib.wiring import In, Out, Signature, flipped, FlippedInterface
8-
9-
10-
__all__ = ["pin_layout", "Pin"]
11-
12-
13-
def _pin_signature(width, dir, xdr=0):
14-
if not isinstance(width, int) or width < 0:
15-
raise TypeError("Width must be a non-negative integer, not {!r}"
16-
.format(width))
17-
if dir not in ("i", "o", "oe", "io"):
18-
raise TypeError("Direction must be one of \"i\", \"o\", \"io\", or \"oe\", not {!r}"""
19-
.format(dir))
20-
if not isinstance(xdr, int) or xdr < 0:
21-
raise TypeError("Gearing ratio must be a non-negative integer, not {!r}"
22-
.format(xdr))
23-
24-
members = {}
25-
if dir in ("i", "io"):
26-
if xdr > 0:
27-
members["i_clk"] = In(1)
28-
if xdr > 2:
29-
members["i_fclk"] = In(1)
30-
if xdr in (0, 1):
31-
members["i"] = In(width)
32-
else:
33-
for n in range(xdr):
34-
members[f"i{n}"] = In(width)
35-
if dir in ("o", "oe", "io"):
36-
if xdr > 0:
37-
members["o_clk"] = Out(1)
38-
if xdr > 2:
39-
members["o_fclk"] = Out(1)
40-
if xdr in (0, 1):
41-
members["o"] = Out(width)
42-
else:
43-
for n in range(xdr):
44-
members[f"o{n}"] = Out(width)
45-
if dir in ("oe", "io"):
46-
members["oe"] = Out(1)
47-
return Signature(members)
2+
from ..lib import wiring
3+
from ..lib.wiring import In, Out
484

495

50-
def pin_layout(width, dir, xdr=0):
51-
"""
52-
Layout of the platform interface of a pin or several pins, which may be used inside
53-
user-defined records.
54-
55-
See :class:`Pin` for details.
56-
"""
57-
fields = []
58-
for name, member in _pin_signature(width, dir, xdr).members.items():
59-
fields.append((name, member.shape))
60-
return Layout(fields)
6+
__all__ = ["Pin"]
617

628

63-
class Pin(Record):
9+
class Pin(wiring.PureInterface):
6410
"""
6511
An interface to an I/O buffer or a group of them that provides uniform access to input, output,
6612
or tristate buffers that may include a 1:n gearbox. (A 1:2 gearbox is typically called "DDR".)
6713
68-
A :class:`Pin` is identical to a :class:`Record` that uses the corresponding :meth:`pin_layout`
69-
except that it allows accessing the parameters like ``width`` as attributes. It is legal to use
70-
a plain :class:`Record` anywhere a :class:`Pin` is used, provided that these attributes are
71-
not necessary.
14+
This is an interface object using :class:`Pin.Signature` as its signature. The signature flows
15+
are defined from the point of view of a component that drives the I/O buffer.
7216
7317
Parameters
7418
----------
@@ -87,8 +31,8 @@ class Pin(Record):
8731
are present instead, where ``N in range(0, N)``. For example, if ``xdr=2``, the I/O buffer
8832
is DDR; the signal ``i0`` reflects the value at the rising edge, and the signal ``i1``
8933
reflects the value at the falling edge.
90-
name : str
91-
Name of the underlying record.
34+
path : tuple of str
35+
As in :class:`PureInterface`, used to name the created signals.
9236
9337
Attributes
9438
----------
@@ -119,23 +63,76 @@ class Pin(Record):
11963
cannot change direction more than once per cycle, so at most one output enable signal
12064
is present.
12165
"""
122-
def __init__(self, width, dir, *, xdr=0, name=None, src_loc_at=0):
123-
self.width = width
124-
self.dir = dir
125-
self.xdr = xdr
12666

127-
super().__init__(pin_layout(self.width, self.dir, self.xdr),
128-
name=name, src_loc_at=src_loc_at + 1)
67+
class Signature(wiring.Signature):
68+
"""A signature for :class:`Pin`. The parameters are as defined on the ``Pin`` class,
69+
and are accessible as attributes.
70+
"""
71+
def __init__(self, width, dir, *, xdr=0):
72+
if not isinstance(width, int) or width < 0:
73+
raise TypeError("Width must be a non-negative integer, not {!r}"
74+
.format(width))
75+
if dir not in ("i", "o", "oe", "io"):
76+
raise TypeError("Direction must be one of \"i\", \"o\", \"io\", or \"oe\", not {!r}"""
77+
.format(dir))
78+
if not isinstance(xdr, int) or xdr < 0:
79+
raise TypeError("Gearing ratio must be a non-negative integer, not {!r}"
80+
.format(xdr))
81+
82+
self.width = width
83+
self.dir = dir
84+
self.xdr = xdr
85+
86+
members = {}
87+
if dir in ("i", "io"):
88+
if xdr > 0:
89+
members["i_clk"] = Out(1)
90+
if xdr > 2:
91+
members["i_fclk"] = Out(1)
92+
if xdr in (0, 1):
93+
members["i"] = In(width)
94+
else:
95+
for n in range(xdr):
96+
members[f"i{n}"] = In(width)
97+
if dir in ("o", "oe", "io"):
98+
if xdr > 0:
99+
members["o_clk"] = Out(1)
100+
if xdr > 2:
101+
members["o_fclk"] = Out(1)
102+
if xdr in (0, 1):
103+
members["o"] = Out(width)
104+
else:
105+
for n in range(xdr):
106+
members[f"o{n}"] = Out(width)
107+
if dir in ("oe", "io"):
108+
members["oe"] = Out(1)
109+
super().__init__(members)
110+
111+
def __eq__(self, other):
112+
return (type(self) is type(other) and
113+
self.width == other.width and
114+
self.dir == other.dir and
115+
self.xdr == other.xdr)
116+
117+
def create(self, *, path=None, src_loc_at=0):
118+
return Pin(self.width, self.dir, xdr=self.xdr, path=path, src_loc_at=1 + src_loc_at)
119+
120+
def __init__(self, width, dir, *, xdr=0, name=None, path=None, src_loc_at=0):
121+
if name is not None:
122+
if path is None:
123+
raise ValueError("Cannot pass both name and path")
124+
path = (name,)
125+
signature = Pin.Signature(width, dir, xdr=xdr)
126+
super().__init__(signature, path=path, src_loc_at=src_loc_at + 1)
129127

130128
@property
131-
def signature(self):
132-
return _pin_signature(self.width, self.dir, self.xdr)
133-
134-
def eq(self, other):
135-
first_field, _, _ = next(iter(Pin(1, dir="o").layout))
136-
warnings.warn(f"`pin.eq(...)` is deprecated; use `pin.{first_field}.eq(...)` here",
137-
DeprecationWarning, stacklevel=2)
138-
if isinstance(self, FlippedInterface):
139-
return Record.eq(flipped(self), other)
140-
else:
141-
return Record.eq(self, other)
129+
def width(self):
130+
return self.signature.width
131+
132+
@property
133+
def dir(self):
134+
return self.signature.dir
135+
136+
@property
137+
def xdr(self):
138+
return self.signature.xdr

tests/test_build_res.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def test_request_basic(self):
6464
user_led = self.cm.request("user_led", 0)
6565

6666
self.assertIsInstance(flipped(user_led), Pin)
67-
self.assertEqual(user_led.name, "user_led_0")
67+
self.assertEqual(user_led.o.name, "user_led_0__o")
6868
self.assertEqual(user_led.width, 1)
6969
self.assertEqual(user_led.dir, "o")
7070

@@ -77,12 +77,14 @@ def test_request_basic(self):
7777

7878
def test_request_with_dir(self):
7979
i2c = self.cm.request("i2c", 0, dir={"sda": "o"})
80-
self.assertIsInstance(i2c, Record)
81-
self.assertIsInstance(i2c.sda, Pin)
80+
self.assertIsInstance(i2c, PureInterface)
81+
self.assertTrue(i2c.signature.is_compliant(i2c))
82+
self.assertIsInstance(flipped(i2c.sda), Pin)
8283
self.assertEqual(i2c.sda.dir, "o")
8384

8485
def test_request_tristate(self):
8586
i2c = self.cm.request("i2c", 0)
87+
self.assertTrue(i2c.signature.is_compliant(i2c))
8688
self.assertEqual(i2c.sda.dir, "io")
8789

8890
ports = list(self.cm.iter_ports())
@@ -92,11 +94,11 @@ def test_request_tristate(self):
9294
self.assertEqual(ports[1].width, 1)
9395

9496
scl_info, sda_info = self.cm.iter_single_ended_pins()
95-
self.assertIs(flipped(scl_info[0]), i2c.scl)
97+
self.assertIs(scl_info[0], i2c.scl)
9698
self.assertIs(scl_info[1].io, scl)
9799
self.assertEqual(scl_info[2], {})
98100
self.assertEqual(scl_info[3], False)
99-
self.assertIs(flipped(sda_info[0]), i2c.sda)
101+
self.assertIs(sda_info[0], i2c.sda)
100102
self.assertIs(sda_info[1].io, sda)
101103

102104
self.assertEqual(list(self.cm.iter_port_constraints()), [
@@ -315,12 +317,3 @@ def test_wrong_clock_constraint_twice(self):
315317
(r"^Cannot add clock constraint on \(sig clk100_0__i\), which is already "
316318
r"constrained to 100000000\.0 Hz$")):
317319
self.cm.add_clock_constraint(clk100.i, 1e6)
318-
319-
def test_eq_deprecation(self):
320-
user_led = self.cm.request("user_led", 0)
321-
m = Module()
322-
with self.assertWarns(DeprecationWarning):
323-
m.d.sync += user_led.eq(1)
324-
p = Pin(4, "o")
325-
with self.assertWarns(DeprecationWarning):
326-
m.d.sync += p.eq(1)

0 commit comments

Comments
 (0)