From: whitequark Date: Fri, 25 Oct 2019 21:22:59 +0000 (+0000) Subject: wishbone.bus: add Interface. X-Git-Tag: 24jan2021_ls180~33 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=8662815e1ea67341cdbcb3fdb77faccd1ed73cfd;p=nmigen-soc.git wishbone.bus: add Interface. --- diff --git a/nmigen_soc/csr/bus.py b/nmigen_soc/csr/bus.py index a8376a7..3e3e992 100644 --- a/nmigen_soc/csr/bus.py +++ b/nmigen_soc/csr/bus.py @@ -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. diff --git a/nmigen_soc/test/test_csr_bus.py b/nmigen_soc/test/test_csr_bus.py index 188bdab..d2ce624 100644 --- a/nmigen_soc/test/test_csr_bus.py +++ b/nmigen_soc/test/test_csr_bus.py @@ -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 index 0000000..0c8b6d8 --- /dev/null +++ b/nmigen_soc/test/test_wishbone_bus.py @@ -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 index 0000000..3b2d416 --- /dev/null +++ b/nmigen_soc/wishbone/__init__.py @@ -0,0 +1 @@ +from .bus import * diff --git a/nmigen_soc/wishbone/bus.py b/nmigen_soc/wishbone/bus.py new file mode 100644 index 0000000..5ee845f --- /dev/null +++ b/nmigen_soc/wishbone/bus.py @@ -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 `_ 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)