From e2db7ca40c250e19dc797959303f73e482fa211f Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 15 Apr 2019 16:27:23 +0000 Subject: [PATCH] lib.io: rework TSTriple/Tristate interface to use pin_layout/Pin. --- nmigen/__init__.py | 2 +- nmigen/compat/fhdl/specials.py | 57 +++++++++++------ nmigen/lib/io.py | 113 +++++++++++++++++++++++---------- nmigen/test/test_lib_io.py | 98 ++++++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 54 deletions(-) create mode 100644 nmigen/test/test_lib_io.py diff --git a/nmigen/__init__.py b/nmigen/__init__.py index 88596a5..bc1560d 100644 --- a/nmigen/__init__.py +++ b/nmigen/__init__.py @@ -7,4 +7,4 @@ from .hdl.rec import Record from .hdl.xfrm import ResetInserter, CEInserter from .lib.cdc import MultiReg -from .lib.io import TSTriple +# from .lib.io import diff --git a/nmigen/compat/fhdl/specials.py b/nmigen/compat/fhdl/specials.py index aa9c77f..acd521b 100644 --- a/nmigen/compat/fhdl/specials.py +++ b/nmigen/compat/fhdl/specials.py @@ -4,38 +4,57 @@ from ...tools import deprecated, extend from ...hdl.ast import * from ...hdl.mem import Memory as NativeMemory from ...hdl.ir import Fragment, Instance -from ...lib.io import TSTriple as NativeTSTriple, Tristate as NativeTristate from .module import Module as CompatModule __all__ = ["TSTriple", "Instance", "Memory", "READ_FIRST", "WRITE_FIRST", "NO_CHANGE"] -class CompatTSTriple(NativeTSTriple): +class TSTriple: def __init__(self, bits_sign=None, min=None, max=None, reset_o=0, reset_oe=0, reset_i=0, name=None): - super().__init__(shape=bits_sign, min=min, max=max, - reset_o=reset_o, reset_oe=reset_oe, reset_i=reset_i, - name=name) + self.o = Signal(shape, min=min, max=max, reset=reset_o, + name=None if name is None else name + "_o") + self.oe = Signal(reset=reset_oe, + name=None if name is None else name + "_oe") + self.i = Signal(shape, min=min, max=max, reset=reset_i, + name=None if name is None else name + "_i") + def __len__(self): + return len(self.o) -class CompatTristate(NativeTristate): - def __init__(self, target, o, oe, i=None): - triple = TSTriple() - triple.o = o - triple.oe = oe - if i is not None: - triple.i = i - super().__init__(triple, target) + def elaborate(self, platform): + return Fragment() - @property - @deprecated("instead of `Tristate.target`, use `Tristate.io`") - def target(self): - return self.io + def get_tristate(self, io): + return Tristate(io, self.o, self.oe, self.i) -TSTriple = CompatTSTriple -Tristate = CompatTristate +class Tristate: + def __init__(self, target, o, oe, i=None): + self.target = target + self.triple = TSTriple() + self.triple.o = o + self.triple.oe = oe + if i is not None: + self.triple.i = i + + def elaborate(self, platform): + if hasattr(platform, "get_tristate"): + return platform.get_tristate(self.triple, self.io) + + m = Module() + m.d.comb += self.triple.i.eq(self.io) + m.submodules += Instance("$tribuf", + p_WIDTH=len(self.io), + i_EN=self.triple.oe, + i_A=self.triple.o, + o_Y=self.io, + ) + + f = m.elaborate(platform) + f.flatten = True + return f (READ_FIRST, WRITE_FIRST, NO_CHANGE) = range(3) diff --git a/nmigen/lib/io.py b/nmigen/lib/io.py index 9c4600e..e8f67f1 100644 --- a/nmigen/lib/io.py +++ b/nmigen/lib/io.py @@ -1,47 +1,92 @@ from .. import * +from ..hdl.rec import * -__all__ = ["TSTriple", "Tristate"] +__all__ = ["pin_layout", "Pin"] -class TSTriple: - def __init__(self, shape=None, min=None, max=None, reset_o=0, reset_oe=0, reset_i=0, - name=None): - self.o = Signal(shape, min=min, max=max, reset=reset_o, - name=None if name is None else name + "_o") - self.oe = Signal(reset=reset_oe, - name=None if name is None else name + "_oe") - self.i = Signal(shape, min=min, max=max, reset=reset_i, - name=None if name is None else name + "_i") +def pin_layout(width, dir, xdr=1): + """ + Layout of the platform interface of a pin or several pins, which may be used inside + user-defined records. - def __len__(self): - return len(self.o) + See :class:`Pin` for details. + """ + if not isinstance(width, int) or width < 1: + raise TypeError("Width must be a positive integer, not '{!r}'" + .format(width)) + if dir not in ("i", "o", "io"): + raise TypeError("Direction must be one of \"i\", \"o\" or \"io\", not '{!r}'""" + .format(dir)) + if not isinstance(xdr, int) or xdr < 1: + raise TypeError("Gearing ratio must be a positive integer, not '{!r}'" + .format(xdr)) - def elaborate(self, platform): - return Fragment() + fields = [] + if dir in ("i", "io"): + if xdr == 1: + fields.append(("i", width)) + else: + for n in range(xdr): + fields.append(("i{}".format(n), width)) + if dir in ("o", "io"): + if xdr == 1: + fields.append(("o", width)) + else: + for n in range(xdr): + fields.append(("o{}".format(n), width)) + if dir == "io": + fields.append(("oe", 1)) + return Layout(fields) - def get_tristate(self, io): - return Tristate(self, io) +class Pin(Record): + """ + An interface to an I/O buffer or a group of them that provides uniform access to input, output, + or tristate buffers that may include a 1:n gearbox. (A 1:2 gearbox is typically called "DDR".) -class Tristate: - def __init__(self, triple, io): - self.triple = triple - self.io = io + A :class:`Pin` is identical to a :class:`Record` that uses the corresponding :meth:`pin_layout` + except that it allos accessing the parameters like ``width`` as attributes. It is legal to use + a plain :class:`Record` anywhere a :class:`Pin` is used, provided that these attributes are + not necessary. - def elaborate(self, platform): - if hasattr(platform, "get_tristate"): - return platform.get_tristate(self.triple, self.io) + Parameters + ---------- + width : int + Width of the ``i``/``iN`` and ``o``/``oN`` signals. + dir : ``"i"``, ``"o"``, ``"io"`` + Direction of the buffers. If ``"i"`` is specified, only the ``i``/``iN`` signals are + present. If ``"o"`` is specified, only the ``o``/``oN`` signals are present. If ``"io"`` + is specified, both the ``i``/``iN`` and ``o``/``oN`` signals are present, and an ``oe`` + signal is present. + xdr : int + Gearbox ratio. If equal to 1, the I/O buffer is SDR, and only ``i``/``o`` signals are + present. If greater than 1, the I/O buffer includes a gearbox, and ``iN``/``oN`` signals + are present instead, where ``N in range(0, N)``. For example, if ``xdr=2``, the I/O buffer + is DDR; the signal ``i0`` reflects the value at the rising edge, and the signal ``i1`` + reflects the value at the falling edge. - m = Module() - m.d.comb += self.triple.i.eq(self.io) - m.submodules += Instance("$tribuf", - p_WIDTH=len(self.io), - i_EN=self.triple.oe, - i_A=self.triple.o, - o_Y=self.io, - ) + Attributes + ---------- + i : Signal, out + I/O buffer input, without gearing. Present if ``dir="i"`` or ``dir="io"``, and ``xdr`` is + equal to 1. + i0, i1, ... : Signal, out + I/O buffer inputs, with gearing. Present if ``dir="i"`` or ``dir="io"``, and ``xdr`` is + greater than 1. + o : Signal, in + I/O buffer output, without gearing. Present if ``dir="o"`` or ``dir="io"``, and ``xdr`` is + equal to 1. + o0, o1, ... : Signal, in + I/O buffer outputs, with gearing. Present if ``dir="o"`` or ``dir="io"``, and ``xdr`` is + greater than 1. + oe : Signal, in + I/O buffer output enable. Present if ``dir="io"``. Buffers generally cannot change + direction more than once per cycle, so at most one output enable signal is present. + """ + def __init__(self, width, dir, xdr=1): + self.width = width + self.dir = dir + self.xdr = xdr - f = m.elaborate(platform) - f.flatten = True - return f + super().__init__(pin_layout(self.width, self.dir, self.xdr)) diff --git a/nmigen/test/test_lib_io.py b/nmigen/test/test_lib_io.py new file mode 100644 index 0000000..57371ef --- /dev/null +++ b/nmigen/test/test_lib_io.py @@ -0,0 +1,98 @@ +from .tools import * +from ..hdl.ast import * +from ..hdl.rec import * +from ..lib.io import * + + +class PinLayoutSDRTestCase(FHDLTestCase): + def test_pin_layout_i(self): + layout_1 = pin_layout(1, dir="i") + self.assertEqual(layout_1.fields, { + "i": (1, DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="i") + self.assertEqual(layout_2.fields, { + "i": (2, DIR_NONE), + }) + + def test_pin_layout_o(self): + layout_1 = pin_layout(1, dir="o") + self.assertEqual(layout_1.fields, { + "o": (1, DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="o") + self.assertEqual(layout_2.fields, { + "o": (2, DIR_NONE), + }) + + def test_pin_layout_io(self): + layout_1 = pin_layout(1, dir="io") + self.assertEqual(layout_1.fields, { + "i": (1, DIR_NONE), + "o": (1, DIR_NONE), + "oe": (1, DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="io") + self.assertEqual(layout_2.fields, { + "i": (2, DIR_NONE), + "o": (2, DIR_NONE), + "oe": (1, DIR_NONE), + }) + + +class PinLayoutDDRTestCase(FHDLTestCase): + def test_pin_layout_i(self): + layout_1 = pin_layout(1, dir="i", xdr=2) + self.assertEqual(layout_1.fields, { + "i0": (1, DIR_NONE), + "i1": (1, DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="i", xdr=2) + self.assertEqual(layout_2.fields, { + "i0": (2, DIR_NONE), + "i1": (2, DIR_NONE), + }) + + def test_pin_layout_o(self): + layout_1 = pin_layout(1, dir="o", xdr=2) + self.assertEqual(layout_1.fields, { + "o0": (1, DIR_NONE), + "o1": (1, DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="o", xdr=2) + self.assertEqual(layout_2.fields, { + "o0": (2, DIR_NONE), + "o1": (2, DIR_NONE), + }) + + def test_pin_layout_io(self): + layout_1 = pin_layout(1, dir="io", xdr=2) + self.assertEqual(layout_1.fields, { + "i0": (1, DIR_NONE), + "i1": (1, DIR_NONE), + "o0": (1, DIR_NONE), + "o1": (1, DIR_NONE), + "oe": (1, DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="io", xdr=2) + self.assertEqual(layout_2.fields, { + "i0": (2, DIR_NONE), + "i1": (2, DIR_NONE), + "o0": (2, DIR_NONE), + "o1": (2, DIR_NONE), + "oe": (1, DIR_NONE), + }) + + +class PinTestCase(FHDLTestCase): + def test_attributes(self): + pin = Pin(2, dir="io", xdr=2) + self.assertEqual(pin.width, 2) + self.assertEqual(pin.dir, "io") + self.assertEqual(pin.xdr, 2) -- 2.30.2