lib.io: rework TSTriple/Tristate interface to use pin_layout/Pin.
authorwhitequark <cz@m-labs.hk>
Mon, 15 Apr 2019 16:27:23 +0000 (16:27 +0000)
committerwhitequark <cz@m-labs.hk>
Mon, 15 Apr 2019 16:27:23 +0000 (16:27 +0000)
nmigen/__init__.py
nmigen/compat/fhdl/specials.py
nmigen/lib/io.py
nmigen/test/test_lib_io.py [new file with mode: 0644]

index 88596a58c361f0595ee57d9fa1fbbddf9dc9d5c0..bc1560dee167293738b424fe0c025afe45f99669 100644 (file)
@@ -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
index aa9c77fc47ab783df5c3ff107c5f811a34f10e61..acd521b7bb5f24d0e576b33bdbf4e1511511db19 100644 (file)
@@ -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)
index 9c4600e09f1fe12d8cbd9c4828f4d96c7cf9fcd9..e8f67f1cd28261d31284b0dcf7c46d41dd5b7c37 100644 (file)
@@ -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 (file)
index 0000000..57371ef
--- /dev/null
@@ -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)