Skip to content

Commit 8662815

Browse files
committed
wishbone.bus: add Interface.
1 parent 7d76126 commit 8662815

File tree

5 files changed

+228
-3
lines changed

5 files changed

+228
-3
lines changed

nmigen_soc/csr/bus.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class Interface(Record):
105105
data_width : int
106106
Data width. Registers are accessed in ``data_width`` sized chunks.
107107
alignment : int
108-
Alignment. See :class:`MemoryMap`.
108+
Register and window alignment. See :class:`MemoryMap`.
109109
name : str
110110
Name of the underlying record.
111111

nmigen_soc/test/test_csr_bus.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ def test_layout(self):
7171
("w_stb", 1),
7272
]))
7373

74-
def test_addr_width_wrong(self):
74+
def test_wrong_addr_width(self):
7575
with self.assertRaisesRegex(ValueError,
7676
r"Address width must be a positive integer, not -1"):
7777
Interface(addr_width=-1, data_width=8)
7878

79-
def test_data_width_wrong(self):
79+
def test_wrong_data_width(self):
8080
with self.assertRaisesRegex(ValueError,
8181
r"Data width must be a positive integer, not -1"):
8282
Interface(addr_width=16, data_width=-1)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import unittest
2+
from nmigen import *
3+
from nmigen.hdl.rec import *
4+
5+
from ..wishbone.bus import *
6+
7+
8+
class InterfaceTestCase(unittest.TestCase):
9+
def test_simple(self):
10+
iface = Interface(addr_width=32, data_width=8)
11+
self.assertEqual(iface.addr_width, 32)
12+
self.assertEqual(iface.data_width, 8)
13+
self.assertEqual(iface.granularity, 8)
14+
self.assertEqual(iface.memory_map.addr_width, 32)
15+
self.assertEqual(iface.memory_map.data_width, 8)
16+
self.assertEqual(iface.layout, Layout.cast([
17+
("adr", 32, DIR_FANOUT),
18+
("dat_w", 8, DIR_FANOUT),
19+
("dat_r", 8, DIR_FANIN),
20+
("sel", 1, DIR_FANOUT),
21+
("cyc", 1, DIR_FANOUT),
22+
("stb", 1, DIR_FANOUT),
23+
("we", 1, DIR_FANOUT),
24+
("ack", 1, DIR_FANIN),
25+
]))
26+
27+
def test_granularity(self):
28+
iface = Interface(addr_width=30, data_width=32, granularity=8)
29+
self.assertEqual(iface.addr_width, 30)
30+
self.assertEqual(iface.data_width, 32)
31+
self.assertEqual(iface.granularity, 8)
32+
self.assertEqual(iface.memory_map.addr_width, 32)
33+
self.assertEqual(iface.memory_map.data_width, 8)
34+
self.assertEqual(iface.layout, Layout.cast([
35+
("adr", 30, DIR_FANOUT),
36+
("dat_w", 32, DIR_FANOUT),
37+
("dat_r", 32, DIR_FANIN),
38+
("sel", 4, DIR_FANOUT),
39+
("cyc", 1, DIR_FANOUT),
40+
("stb", 1, DIR_FANOUT),
41+
("we", 1, DIR_FANOUT),
42+
("ack", 1, DIR_FANIN),
43+
]))
44+
45+
def test_optional(self):
46+
iface = Interface(addr_width=32, data_width=32,
47+
optional={"rty", "err", "stall", "cti", "bte"})
48+
self.assertEqual(iface.layout, Layout.cast([
49+
("adr", 32, DIR_FANOUT),
50+
("dat_w", 32, DIR_FANOUT),
51+
("dat_r", 32, DIR_FANIN),
52+
("sel", 1, DIR_FANOUT),
53+
("cyc", 1, DIR_FANOUT),
54+
("stb", 1, DIR_FANOUT),
55+
("we", 1, DIR_FANOUT),
56+
("ack", 1, DIR_FANIN),
57+
("err", 1, DIR_FANIN),
58+
("rty", 1, DIR_FANIN),
59+
("stall", 1, DIR_FANIN),
60+
("cti", CycleType, DIR_FANOUT),
61+
("bte", BurstTypeExt, DIR_FANOUT),
62+
]))
63+
64+
def test_wrong_addr_width(self):
65+
with self.assertRaisesRegex(ValueError,
66+
r"Address width must be a non-negative integer, not -1"):
67+
Interface(addr_width=-1, data_width=8)
68+
69+
def test_wrong_data_width(self):
70+
with self.assertRaisesRegex(ValueError,
71+
r"Data width must be one of 8, 16, 32, 64, not 7"):
72+
Interface(addr_width=0, data_width=7)
73+
74+
def test_wrong_granularity(self):
75+
with self.assertRaisesRegex(ValueError,
76+
r"Granularity must be one of 8, 16, 32, 64, not 7"):
77+
Interface(addr_width=0, data_width=32, granularity=7)
78+
79+
def test_wrong_granularity(self):
80+
with self.assertRaisesRegex(ValueError,
81+
r"Granularity 32 may not be greater than data width 8"):
82+
Interface(addr_width=0, data_width=8, granularity=32)
83+
84+
def test_wrong_optional(self):
85+
with self.assertRaisesRegex(ValueError,
86+
r"Optional signal\(s\) 'foo' are not supported"):
87+
Interface(addr_width=0, data_width=8, optional={"foo"})

nmigen_soc/wishbone/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .bus import *

nmigen_soc/wishbone/bus.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from enum import Enum
2+
from nmigen import *
3+
from nmigen.hdl.rec import Direction
4+
from nmigen.utils import log2_int
5+
6+
from ..memory import MemoryMap
7+
8+
9+
__all__ = ["CycleType", "BurstTypeExt", "Interface"]
10+
11+
12+
class CycleType(Enum):
13+
"""Wishbone Registered Feedback cycle type."""
14+
CLASSIC = 0b000
15+
CONST_BURST = 0b001
16+
INCR_BURST = 0b010
17+
END_OF_BURST = 0b111
18+
19+
20+
class BurstTypeExt(Enum):
21+
"""Wishbone Registered Feedback burst type extension."""
22+
LINEAR = 0b00
23+
WRAP_4 = 0b01
24+
WRAP_8 = 0b10
25+
WRAP_16 = 0b11
26+
27+
28+
class Interface(Record):
29+
"""Wishbone interface.
30+
31+
See the `Wishbone specification <https://opencores.org/howto/wishbone>`_ for description
32+
of the Wishbone signals. The ``RST_I`` and ``CLK_I`` signals are provided as a part of
33+
the clock domain that drives the interface.
34+
35+
Note that the data width of the underlying memory map of the interface is equal to port
36+
granularity, not port size. If port granularity is less than port size, then the address width
37+
of the underlying memory map is extended to reflect that.
38+
39+
Parameters
40+
----------
41+
addr_width : int
42+
Width of the address signal.
43+
data_width : int
44+
Width of the data signals ("port size" in Wishbone terminology).
45+
One of 8, 16, 32, 64.
46+
granularity : int
47+
Granularity of select signals ("port granularity" in Wishbone terminology).
48+
One of 8, 16, 32, 64.
49+
optional : iter(str)
50+
Selects the optional signals that will be a part of this interface.
51+
alignment : int
52+
Resource and window alignment. See :class:`MemoryMap`.
53+
name : str
54+
Name of the underlying record.
55+
56+
Attributes
57+
----------
58+
The correspondence between the nMigen-SoC signals and the Wishbone signals changes depending
59+
on whether the interface acts as an initiator or a target.
60+
61+
adr : Signal(addr_width)
62+
Corresponds to Wishbone signal ``ADR_O`` (initiator) or ``ADR_I`` (target).
63+
dat_w : Signal(data_width)
64+
Corresponds to Wishbone signal ``DAT_O`` (initiator) or ``DAT_I`` (target).
65+
dat_r : Signal(data_width)
66+
Corresponds to Wishbone signal ``DAT_I`` (initiator) or ``DAT_O`` (target).
67+
sel : Signal(data_width // granularity)
68+
Corresponds to Wishbone signal ``SEL_O`` (initiator) or ``SEL_I`` (target).
69+
cyc : Signal()
70+
Corresponds to Wishbone signal ``CYC_O`` (initiator) or ``CYC_I`` (target).
71+
stb : Signal()
72+
Corresponds to Wishbone signal ``STB_O`` (initiator) or ``STB_I`` (target).
73+
we : Signal()
74+
Corresponds to Wishbone signal ``WE_O`` (initiator) or ``WE_I`` (target).
75+
ack : Signal()
76+
Corresponds to Wishbone signal ``ACK_I`` (initiator) or ``ACK_O`` (target).
77+
err : Signal()
78+
Optional. Corresponds to Wishbone signal ``ERR_I`` (initiator) or ``ERR_O`` (target).
79+
rty : Signal()
80+
Optional. Corresponds to Wishbone signal ``RTY_I`` (initiator) or ``RTY_O`` (target).
81+
stall : Signal()
82+
Optional. Corresponds to Wishbone signal ``STALL_I`` (initiator) or ``STALL_O`` (target).
83+
cti : Signal()
84+
Optional. Corresponds to Wishbone signal ``CTI_O`` (initiator) or ``CTI_I`` (target).
85+
bte : Signal()
86+
Optional. Corresponds to Wishbone signal ``BTE_O`` (initiator) or ``BTE_I`` (target).
87+
"""
88+
def __init__(self, *, addr_width, data_width, granularity=None, optional=frozenset(),
89+
alignment=0, name=None):
90+
if not isinstance(addr_width, int) or addr_width < 0:
91+
raise ValueError("Address width must be a non-negative integer, not {!r}"
92+
.format(addr_width))
93+
if data_width not in (8, 16, 32, 64):
94+
raise ValueError("Data width must be one of 8, 16, 32, 64, not {!r}"
95+
.format(data_width))
96+
if granularity is None:
97+
granularity = data_width
98+
elif granularity not in (8, 16, 32, 64):
99+
raise ValueError("Granularity must be one of 8, 16, 32, 64, not {!r}"
100+
.format(granularity))
101+
if granularity > data_width:
102+
raise ValueError("Granularity {} may not be greater than data width {}"
103+
.format(granularity, data_width))
104+
self.addr_width = addr_width
105+
self.data_width = data_width
106+
self.granularity = granularity
107+
granularity_bits = log2_int(data_width // granularity)
108+
self.memory_map = MemoryMap(addr_width=max(1, addr_width + granularity_bits),
109+
data_width=data_width >> granularity_bits,
110+
alignment=alignment)
111+
112+
optional = set(optional)
113+
unknown = optional - {"rty", "err", "stall", "cti", "bte"}
114+
if unknown:
115+
raise ValueError("Optional signal(s) {} are not supported"
116+
.format(", ".join(map(repr, unknown))))
117+
layout = [
118+
("adr", addr_width, Direction.FANOUT),
119+
("dat_w", data_width, Direction.FANOUT),
120+
("dat_r", data_width, Direction.FANIN),
121+
("sel", data_width // granularity, Direction.FANOUT),
122+
("cyc", 1, Direction.FANOUT),
123+
("stb", 1, Direction.FANOUT),
124+
("we", 1, Direction.FANOUT),
125+
("ack", 1, Direction.FANIN),
126+
]
127+
if "err" in optional:
128+
layout += [("err", 1, Direction.FANIN)]
129+
if "rty" in optional:
130+
layout += [("rty", 1, Direction.FANIN)]
131+
if "stall" in optional:
132+
layout += [("stall", 1, Direction.FANIN)]
133+
if "cti" in optional:
134+
layout += [("cti", CycleType, Direction.FANOUT)]
135+
if "bte" in optional:
136+
layout += [("bte", BurstTypeExt, Direction.FANOUT)]
137+
super().__init__(layout, name=name, src_loc_at=1)

0 commit comments

Comments
 (0)