wishbone.bus: add Interface.
authorwhitequark <whitequark@whitequark.org>
Fri, 25 Oct 2019 21:22:59 +0000 (21:22 +0000)
committerwhitequark <whitequark@whitequark.org>
Fri, 25 Oct 2019 21:30:56 +0000 (21:30 +0000)
nmigen_soc/csr/bus.py
nmigen_soc/test/test_csr_bus.py
nmigen_soc/test/test_wishbone_bus.py [new file with mode: 0644]
nmigen_soc/wishbone/__init__.py [new file with mode: 0644]
nmigen_soc/wishbone/bus.py [new file with mode: 0644]

index a8376a754dd8dbdef073efcd64e28fcb8b0a31f2..3e3e992d920223137896d13f87b106cc79098982 100644 (file)
@@ -105,7 +105,7 @@ class Interface(Record):
     data_width : int
         Data width. Registers are accessed in ``data_width`` sized chunks.
     alignment : int
-        Alignment. See :class:`MemoryMap`.
+        Register and window alignment. See :class:`MemoryMap`.
     name : str
         Name of the underlying record.
 
index 188bdabd66d5595774c07e335238bce4429a3f09..d2ce624113256c0d56208e850b9927356c60c1bf 100644 (file)
@@ -71,12 +71,12 @@ class InterfaceTestCase(unittest.TestCase):
             ("w_stb",   1),
         ]))
 
-    def test_addr_width_wrong(self):
+    def test_wrong_addr_width(self):
         with self.assertRaisesRegex(ValueError,
                 r"Address width must be a positive integer, not -1"):
             Interface(addr_width=-1, data_width=8)
 
-    def test_data_width_wrong(self):
+    def test_wrong_data_width(self):
         with self.assertRaisesRegex(ValueError,
                 r"Data width must be a positive integer, not -1"):
             Interface(addr_width=16, data_width=-1)
diff --git a/nmigen_soc/test/test_wishbone_bus.py b/nmigen_soc/test/test_wishbone_bus.py
new file mode 100644 (file)
index 0000000..0c8b6d8
--- /dev/null
@@ -0,0 +1,87 @@
+import unittest
+from nmigen import *
+from nmigen.hdl.rec import *
+
+from ..wishbone.bus import *
+
+
+class InterfaceTestCase(unittest.TestCase):
+    def test_simple(self):
+        iface = Interface(addr_width=32, data_width=8)
+        self.assertEqual(iface.addr_width, 32)
+        self.assertEqual(iface.data_width, 8)
+        self.assertEqual(iface.granularity, 8)
+        self.assertEqual(iface.memory_map.addr_width, 32)
+        self.assertEqual(iface.memory_map.data_width, 8)
+        self.assertEqual(iface.layout, Layout.cast([
+            ("adr",   32, DIR_FANOUT),
+            ("dat_w", 8,  DIR_FANOUT),
+            ("dat_r", 8,  DIR_FANIN),
+            ("sel",   1,  DIR_FANOUT),
+            ("cyc",   1,  DIR_FANOUT),
+            ("stb",   1,  DIR_FANOUT),
+            ("we",    1,  DIR_FANOUT),
+            ("ack",   1,  DIR_FANIN),
+        ]))
+
+    def test_granularity(self):
+        iface = Interface(addr_width=30, data_width=32, granularity=8)
+        self.assertEqual(iface.addr_width, 30)
+        self.assertEqual(iface.data_width, 32)
+        self.assertEqual(iface.granularity, 8)
+        self.assertEqual(iface.memory_map.addr_width, 32)
+        self.assertEqual(iface.memory_map.data_width, 8)
+        self.assertEqual(iface.layout, Layout.cast([
+            ("adr",   30, DIR_FANOUT),
+            ("dat_w", 32, DIR_FANOUT),
+            ("dat_r", 32, DIR_FANIN),
+            ("sel",   4,  DIR_FANOUT),
+            ("cyc",   1,  DIR_FANOUT),
+            ("stb",   1,  DIR_FANOUT),
+            ("we",    1,  DIR_FANOUT),
+            ("ack",   1,  DIR_FANIN),
+        ]))
+
+    def test_optional(self):
+        iface = Interface(addr_width=32, data_width=32,
+                          optional={"rty", "err", "stall", "cti", "bte"})
+        self.assertEqual(iface.layout, Layout.cast([
+            ("adr",   32, DIR_FANOUT),
+            ("dat_w", 32, DIR_FANOUT),
+            ("dat_r", 32, DIR_FANIN),
+            ("sel",   1,  DIR_FANOUT),
+            ("cyc",   1,  DIR_FANOUT),
+            ("stb",   1,  DIR_FANOUT),
+            ("we",    1,  DIR_FANOUT),
+            ("ack",   1,  DIR_FANIN),
+            ("err",   1,  DIR_FANIN),
+            ("rty",   1,  DIR_FANIN),
+            ("stall", 1,  DIR_FANIN),
+            ("cti",   CycleType,    DIR_FANOUT),
+            ("bte",   BurstTypeExt, DIR_FANOUT),
+        ]))
+
+    def test_wrong_addr_width(self):
+        with self.assertRaisesRegex(ValueError,
+                r"Address width must be a non-negative integer, not -1"):
+            Interface(addr_width=-1, data_width=8)
+
+    def test_wrong_data_width(self):
+        with self.assertRaisesRegex(ValueError,
+                r"Data width must be one of 8, 16, 32, 64, not 7"):
+            Interface(addr_width=0, data_width=7)
+
+    def test_wrong_granularity(self):
+        with self.assertRaisesRegex(ValueError,
+                r"Granularity must be one of 8, 16, 32, 64, not 7"):
+            Interface(addr_width=0, data_width=32, granularity=7)
+
+    def test_wrong_granularity(self):
+        with self.assertRaisesRegex(ValueError,
+                r"Granularity 32 may not be greater than data width 8"):
+            Interface(addr_width=0, data_width=8, granularity=32)
+
+    def test_wrong_optional(self):
+        with self.assertRaisesRegex(ValueError,
+                r"Optional signal\(s\) 'foo' are not supported"):
+            Interface(addr_width=0, data_width=8, optional={"foo"})
diff --git a/nmigen_soc/wishbone/__init__.py b/nmigen_soc/wishbone/__init__.py
new file mode 100644 (file)
index 0000000..3b2d416
--- /dev/null
@@ -0,0 +1 @@
+from .bus import *
diff --git a/nmigen_soc/wishbone/bus.py b/nmigen_soc/wishbone/bus.py
new file mode 100644 (file)
index 0000000..5ee845f
--- /dev/null
@@ -0,0 +1,137 @@
+from enum import Enum
+from nmigen import *
+from nmigen.hdl.rec import Direction
+from nmigen.utils import log2_int
+
+from ..memory import MemoryMap
+
+
+__all__ = ["CycleType", "BurstTypeExt", "Interface"]
+
+
+class CycleType(Enum):
+    """Wishbone Registered Feedback cycle type."""
+    CLASSIC      = 0b000
+    CONST_BURST  = 0b001
+    INCR_BURST   = 0b010
+    END_OF_BURST = 0b111
+
+
+class BurstTypeExt(Enum):
+    """Wishbone Registered Feedback burst type extension."""
+    LINEAR  = 0b00
+    WRAP_4  = 0b01
+    WRAP_8  = 0b10
+    WRAP_16 = 0b11
+
+
+class Interface(Record):
+    """Wishbone interface.
+
+    See the `Wishbone specification <https://opencores.org/howto/wishbone>`_ for description
+    of the Wishbone signals. The ``RST_I`` and ``CLK_I`` signals are provided as a part of
+    the clock domain that drives the interface.
+
+    Note that the data width of the underlying memory map of the interface is equal to port
+    granularity, not port size. If port granularity is less than port size, then the address width
+    of the underlying memory map is extended to reflect that.
+
+    Parameters
+    ----------
+    addr_width : int
+        Width of the address signal.
+    data_width : int
+        Width of the data signals ("port size" in Wishbone terminology).
+        One of 8, 16, 32, 64.
+    granularity : int
+        Granularity of select signals ("port granularity" in Wishbone terminology).
+        One of 8, 16, 32, 64.
+    optional : iter(str)
+        Selects the optional signals that will be a part of this interface.
+    alignment : int
+        Resource and window alignment. See :class:`MemoryMap`.
+    name : str
+        Name of the underlying record.
+
+    Attributes
+    ----------
+    The correspondence between the nMigen-SoC signals and the Wishbone signals changes depending
+    on whether the interface acts as an initiator or a target.
+
+    adr : Signal(addr_width)
+        Corresponds to Wishbone signal ``ADR_O`` (initiator) or ``ADR_I`` (target).
+    dat_w : Signal(data_width)
+        Corresponds to Wishbone signal ``DAT_O`` (initiator) or ``DAT_I`` (target).
+    dat_r : Signal(data_width)
+        Corresponds to Wishbone signal ``DAT_I`` (initiator) or ``DAT_O`` (target).
+    sel : Signal(data_width // granularity)
+        Corresponds to Wishbone signal ``SEL_O`` (initiator) or ``SEL_I`` (target).
+    cyc : Signal()
+        Corresponds to Wishbone signal ``CYC_O`` (initiator) or ``CYC_I`` (target).
+    stb : Signal()
+        Corresponds to Wishbone signal ``STB_O`` (initiator) or ``STB_I`` (target).
+    we : Signal()
+        Corresponds to Wishbone signal ``WE_O``  (initiator) or ``WE_I``  (target).
+    ack : Signal()
+        Corresponds to Wishbone signal ``ACK_I`` (initiator) or ``ACK_O`` (target).
+    err : Signal()
+        Optional. Corresponds to Wishbone signal ``ERR_I`` (initiator) or ``ERR_O`` (target).
+    rty : Signal()
+        Optional. Corresponds to Wishbone signal ``RTY_I`` (initiator) or ``RTY_O`` (target).
+    stall : Signal()
+        Optional. Corresponds to Wishbone signal ``STALL_I`` (initiator) or ``STALL_O`` (target).
+    cti : Signal()
+        Optional. Corresponds to Wishbone signal ``CTI_O`` (initiator) or ``CTI_I`` (target).
+    bte : Signal()
+        Optional. Corresponds to Wishbone signal ``BTE_O`` (initiator) or ``BTE_I`` (target).
+    """
+    def __init__(self, *, addr_width, data_width, granularity=None, optional=frozenset(),
+                 alignment=0, name=None):
+        if not isinstance(addr_width, int) or addr_width < 0:
+            raise ValueError("Address width must be a non-negative integer, not {!r}"
+                             .format(addr_width))
+        if data_width not in (8, 16, 32, 64):
+            raise ValueError("Data width must be one of 8, 16, 32, 64, not {!r}"
+                             .format(data_width))
+        if granularity is None:
+            granularity = data_width
+        elif granularity not in (8, 16, 32, 64):
+            raise ValueError("Granularity must be one of 8, 16, 32, 64, not {!r}"
+                             .format(granularity))
+        if granularity > data_width:
+            raise ValueError("Granularity {} may not be greater than data width {}"
+                             .format(granularity, data_width))
+        self.addr_width  = addr_width
+        self.data_width  = data_width
+        self.granularity = granularity
+        granularity_bits = log2_int(data_width // granularity)
+        self.memory_map  = MemoryMap(addr_width=max(1, addr_width + granularity_bits),
+                                     data_width=data_width >> granularity_bits,
+                                     alignment=alignment)
+
+        optional = set(optional)
+        unknown  = optional - {"rty", "err", "stall", "cti", "bte"}
+        if unknown:
+            raise ValueError("Optional signal(s) {} are not supported"
+                             .format(", ".join(map(repr, unknown))))
+        layout = [
+            ("adr",   addr_width, Direction.FANOUT),
+            ("dat_w", data_width, Direction.FANOUT),
+            ("dat_r", data_width, Direction.FANIN),
+            ("sel",   data_width // granularity, Direction.FANOUT),
+            ("cyc",   1, Direction.FANOUT),
+            ("stb",   1, Direction.FANOUT),
+            ("we",    1, Direction.FANOUT),
+            ("ack",   1, Direction.FANIN),
+        ]
+        if "err" in optional:
+            layout += [("err", 1, Direction.FANIN)]
+        if "rty" in optional:
+            layout += [("rty", 1, Direction.FANIN)]
+        if "stall" in optional:
+            layout += [("stall", 1, Direction.FANIN)]
+        if "cti" in optional:
+            layout += [("cti", CycleType,    Direction.FANOUT)]
+        if "bte" in optional:
+            layout += [("bte", BurstTypeExt, Direction.FANOUT)]
+        super().__init__(layout, name=name, src_loc_at=1)