lib.io: make Pin an interface object.

Tracking #879.

The directions of signals in `Pin` make it convenient to use a pin
signature in a component, such as in:

    class LEDDriver(Component):
        pins: Out(Signature({"o": Out(1)}))

    led_driver = LEDDriver()
    connect(led_driver.pins, platform.request("led"))

The `platform.request` call, correspondingly, returns a flipped `Pin`
object.
This commit is contained in:
Catherine 2023-09-01 03:27:31 +00:00
parent 33c2246311
commit 4e078322a0
4 changed files with 51 additions and 30 deletions

View file

@ -122,29 +122,36 @@ class ResourceManager:
fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name], fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name],
name="{}__{}".format(name, sub.name), name="{}__{}".format(name, sub.name),
attrs={**attrs, **sub.attrs}) attrs={**attrs, **sub.attrs})
return Record([ rec = Record([
(f_name, f.layout) for (f_name, f) in fields.items() (f_name, f.layout) for (f_name, f) in fields.items()
], fields=fields, name=name) ], fields=fields, name=name)
rec.signature = wiring.Signature({
f_name: wiring.Out(f.signature) for (f_name, f) in fields.items()
})
return rec
elif isinstance(resource.ios[0], (Pins, DiffPairs)): elif isinstance(resource.ios[0], (Pins, DiffPairs)):
phys = resource.ios[0] phys = resource.ios[0]
# The flow is `In` below regardless of requested pin direction. The flow should
# never be used as it's not used internally and anyone using `dir="-"` should
# ignore it as well.
if isinstance(phys, Pins): if isinstance(phys, Pins):
phys_names = phys.names phys_names = phys.names
port = Record([("io", len(phys))], name=name) port = wiring.Signature({"io": wiring.In(len(phys))}).create(path=(name,))
if isinstance(phys, DiffPairs): if isinstance(phys, DiffPairs):
phys_names = [] phys_names = []
record_fields = [] members = {}
if not self.should_skip_port_component(None, attrs, "p"): if not self.should_skip_port_component(None, attrs, "p"):
phys_names += phys.p.names phys_names += phys.p.names
record_fields.append(("p", len(phys))) members["p"] = wiring.In(len(phys))
if not self.should_skip_port_component(None, attrs, "n"): if not self.should_skip_port_component(None, attrs, "n"):
phys_names += phys.n.names phys_names += phys.n.names
record_fields.append(("n", len(phys))) members["n"] = wiring.In(len(phys))
port = Record(record_fields, name=name) port = wiring.Signature(members).create(path=(name,))
if dir == "-": if dir == "-":
pin = None pin = None
else: else:
pin = Pin(len(phys), dir, xdr=xdr, name=name) pin = wiring.flipped(Pin(len(phys), dir, xdr=xdr, name=name))
for phys_name in phys_names: for phys_name in phys_names:
if phys_name in self._phys_reqd: if phys_name in self._phys_reqd:

View file

@ -7,6 +7,7 @@ from functools import reduce, wraps
from .. import tracer from .. import tracer
from .._utils import union from .._utils import union
from .ast import * from .ast import *
from ..lib import wiring
__all__ = ["Direction", "DIR_NONE", "DIR_FANOUT", "DIR_FANIN", "Layout", "Record"] __all__ = ["Direction", "DIR_NONE", "DIR_FANOUT", "DIR_FANIN", "Layout", "Record"]
@ -132,6 +133,8 @@ class Record(ValueCastable):
if fields is not None and field_name in fields: if fields is not None and field_name in fields:
field = fields[field_name] field = fields[field_name]
if isinstance(field_shape, Layout): if isinstance(field_shape, Layout):
if isinstance(field, wiring.FlippedInterface):
field = wiring.flipped(field)
assert isinstance(field, Record) and field_shape == field.layout assert isinstance(field, Record) and field_shape == field.layout
else: else:
assert isinstance(field, Signal) and Shape.cast(field_shape) == field.shape() assert isinstance(field, Signal) and Shape.cast(field_shape) == field.shape()

View file

@ -4,18 +4,13 @@ from .. import *
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.filterwarnings(action="ignore", category=DeprecationWarning) warnings.filterwarnings(action="ignore", category=DeprecationWarning)
from ..hdl.rec import * from ..hdl.rec import *
from ..lib.wiring import In, Out, Signature
__all__ = ["pin_layout", "Pin"] __all__ = ["pin_layout", "Pin"]
def pin_layout(width, dir, xdr=0): def _pin_signature(width, dir, xdr=0):
"""
Layout of the platform interface of a pin or several pins, which may be used inside
user-defined records.
See :class:`Pin` for details.
"""
if not isinstance(width, int) or width < 0: if not isinstance(width, int) or width < 0:
raise TypeError("Width must be a non-negative integer, not {!r}" raise TypeError("Width must be a non-negative integer, not {!r}"
.format(width)) .format(width))
@ -26,29 +21,42 @@ def pin_layout(width, dir, xdr=0):
raise TypeError("Gearing ratio must be a non-negative integer, not {!r}" raise TypeError("Gearing ratio must be a non-negative integer, not {!r}"
.format(xdr)) .format(xdr))
fields = [] members = {}
if dir in ("i", "io"): if dir in ("i", "io"):
if xdr > 0: if xdr > 0:
fields.append(("i_clk", 1)) members["i_clk"] = In(1)
if xdr > 2: if xdr > 2:
fields.append(("i_fclk", 1)) members["i_fclk"] = In(1)
if xdr in (0, 1): if xdr in (0, 1):
fields.append(("i", width)) members["i"] = In(width)
else: else:
for n in range(xdr): for n in range(xdr):
fields.append(("i{}".format(n), width)) members["i{}".format(n)] = In(width)
if dir in ("o", "oe", "io"): if dir in ("o", "oe", "io"):
if xdr > 0: if xdr > 0:
fields.append(("o_clk", 1)) members["o_clk"] = Out(1)
if xdr > 2: if xdr > 2:
fields.append(("o_fclk", 1)) members["o_fclk"] = Out(1)
if xdr in (0, 1): if xdr in (0, 1):
fields.append(("o", width)) members["o"] = Out(width)
else: else:
for n in range(xdr): for n in range(xdr):
fields.append(("o{}".format(n), width)) members["o{}".format(n)] = Out(width)
if dir in ("oe", "io"): if dir in ("oe", "io"):
fields.append(("oe", 1)) members["oe"] = In(1)
return Signature(members)
def pin_layout(width, dir, xdr=0):
"""
Layout of the platform interface of a pin or several pins, which may be used inside
user-defined records.
See :class:`Pin` for details.
"""
fields = []
for name, member in _pin_signature(width, dir, xdr).members.items():
fields.append((name, member.shape))
return Layout(fields) return Layout(fields)
@ -118,3 +126,7 @@ class Pin(Record):
super().__init__(pin_layout(self.width, self.dir, self.xdr), super().__init__(pin_layout(self.width, self.dir, self.xdr),
name=name, src_loc_at=src_loc_at + 1) name=name, src_loc_at=src_loc_at + 1)
@property
def signature(self):
return _pin_signature(self.width, self.dir, self.xdr)

View file

@ -6,6 +6,7 @@ from amaranth import *
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.filterwarnings(action="ignore", category=DeprecationWarning) warnings.filterwarnings(action="ignore", category=DeprecationWarning)
from amaranth.hdl.rec import * from amaranth.hdl.rec import *
from amaranth.lib.wiring import *
from amaranth.lib.io import * from amaranth.lib.io import *
from amaranth.build.dsl import * from amaranth.build.dsl import *
from amaranth.build.res import * from amaranth.build.res import *
@ -62,7 +63,7 @@ class ResourceManagerTestCase(FHDLTestCase):
r = self.cm.lookup("user_led", 0) r = self.cm.lookup("user_led", 0)
user_led = self.cm.request("user_led", 0) user_led = self.cm.request("user_led", 0)
self.assertIsInstance(user_led, Pin) self.assertIsInstance(flipped(user_led), Pin)
self.assertEqual(user_led.name, "user_led_0") self.assertEqual(user_led.name, "user_led_0")
self.assertEqual(user_led.width, 1) self.assertEqual(user_led.width, 1)
self.assertEqual(user_led.dir, "o") self.assertEqual(user_led.dir, "o")
@ -91,11 +92,11 @@ class ResourceManagerTestCase(FHDLTestCase):
self.assertEqual(ports[1].width, 1) self.assertEqual(ports[1].width, 1)
scl_info, sda_info = self.cm.iter_single_ended_pins() scl_info, sda_info = self.cm.iter_single_ended_pins()
self.assertIs(scl_info[0], i2c.scl) self.assertIs(flipped(scl_info[0]), i2c.scl)
self.assertIs(scl_info[1].io, scl) self.assertIs(scl_info[1].io, scl)
self.assertEqual(scl_info[2], {}) self.assertEqual(scl_info[2], {})
self.assertEqual(scl_info[3], False) self.assertEqual(scl_info[3], False)
self.assertIs(sda_info[0], i2c.sda) self.assertIs(flipped(sda_info[0]), i2c.sda)
self.assertIs(sda_info[1].io, sda) self.assertIs(sda_info[1].io, sda)
self.assertEqual(list(self.cm.iter_port_constraints()), [ self.assertEqual(list(self.cm.iter_port_constraints()), [
@ -105,7 +106,7 @@ class ResourceManagerTestCase(FHDLTestCase):
def test_request_diffpairs(self): def test_request_diffpairs(self):
clk100 = self.cm.request("clk100", 0) clk100 = self.cm.request("clk100", 0)
self.assertIsInstance(clk100, Pin) self.assertIsInstance(flipped(clk100), Pin)
self.assertEqual(clk100.dir, "i") self.assertEqual(clk100.dir, "i")
self.assertEqual(clk100.width, 1) self.assertEqual(clk100.width, 1)
@ -155,7 +156,6 @@ class ResourceManagerTestCase(FHDLTestCase):
def test_request_raw(self): def test_request_raw(self):
clk50 = self.cm.request("clk50", 0, dir="-") clk50 = self.cm.request("clk50", 0, dir="-")
self.assertIsInstance(clk50, Record)
self.assertIsInstance(clk50.io, Signal) self.assertIsInstance(clk50.io, Signal)
ports = list(self.cm.iter_ports()) ports = list(self.cm.iter_ports())
@ -164,7 +164,6 @@ class ResourceManagerTestCase(FHDLTestCase):
def test_request_raw_diffpairs(self): def test_request_raw_diffpairs(self):
clk100 = self.cm.request("clk100", 0, dir="-") clk100 = self.cm.request("clk100", 0, dir="-")
self.assertIsInstance(clk100, Record)
self.assertIsInstance(clk100.p, Signal) self.assertIsInstance(clk100.p, Signal)
self.assertIsInstance(clk100.n, Signal) self.assertIsInstance(clk100.n, Signal)