Skip to content

Commit 5520e0d

Browse files
committed
csr.bus: split CSRMultiplexer to CSRInterface+CSRDecoder.
1 parent 4d7d78f commit 5520e0d

File tree

2 files changed

+146
-105
lines changed

2 files changed

+146
-105
lines changed

nmigen_soc/csr/bus.py

Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from nmigen import tracer
44

55

6-
__all__ = ["CSRElement", "CSRMultiplexer"]
6+
__all__ = ["CSRElement", "CSRInterface", "CSRDecoder"]
77

88

99
class CSRElement(Record):
@@ -40,8 +40,7 @@ def __init__(self, width, access, *, name=None, src_loc_at=0):
4040
if access not in ("r", "w", "rw"):
4141
raise ValueError("Access mode must be one of \"r\", \"w\", or \"rw\", not {!r}"
4242
.format(access))
43-
44-
self.width = int(width)
43+
self.width = width
4544
self.access = access
4645

4746
layout = []
@@ -58,31 +57,84 @@ def __init__(self, width, access, *, name=None, src_loc_at=0):
5857
super().__init__(layout, name=name, src_loc_at=1)
5958

6059

61-
class CSRMultiplexer(Elaboratable):
60+
class CSRInterface(Record):
6261
"""CPU-side CSR interface.
6362
64-
A low-level interface to a set of peripheral CSR registers that implements address-based
65-
multiplexing and atomic updates of wide registers.
63+
A low-level interface to a set of atomically readable and writable peripheral CSR registers.
6664
6765
Operation
6866
---------
6967
70-
The CSR multiplexer splits each CSR register into chunks according to its data width. Each
71-
chunk is assigned an address, and the first chunk of each register always has the provided
72-
minimum alignment. This allows accessing CSRs of any size using any datapath width.
68+
CSR registers mapped to the CSR bus are split into chunks according to the bus data width.
69+
Each chunk is assigned a consecutive address on the bus. This allows accessing CSRs of any
70+
size using any datapath width.
7371
7472
When the first chunk of a register is read, the value of a register is captured, and reads
7573
from subsequent chunks of the same register return the captured values. When any chunk except
7674
the last chunk of a register is written, the written value is captured; a write to the last
7775
chunk writes the captured value to the register. This allows atomically accessing CSRs larger
7876
than datapath width.
7977
80-
Reads to padding bytes return zeroes, and writes to padding bytes are ignored.
78+
Parameters
79+
----------
80+
addr_width : int
81+
Address width. At most ``(2 ** addr_width) * data_width`` register bits will be available.
82+
data_width : int
83+
Data width. Registers are accessed in ``data_width`` sized chunks.
84+
name : str
85+
Name of the underlying record.
8186
82-
Writes are registered, and add 1 cycle of latency.
87+
Attributes
88+
----------
89+
addr : Signal(addr_width)
90+
Address for reads and writes.
91+
r_data : Signal(data_width)
92+
Read data. Valid on the next cycle after ``r_stb`` is asserted.
93+
r_stb : Signal()
94+
Read strobe. If ``addr`` points to the first chunk of a register, captures register value
95+
and causes read side effects to be performed (if any). If ``addr`` points to any chunk
96+
of a register, latches the captured value to ``r_data``. Otherwise, latches zero
97+
to ``r_data``.
98+
w_data : Signal(data_width)
99+
Write data. Must be valid when ``w_stb`` is asserted.
100+
w_stb : Signal()
101+
Write strobe. If ``addr`` points to the last chunk of a register, writes captured value
102+
to the register and causes write side effects to be performed (if any). If ``addr`` points
103+
to any chunk of a register, latches ``w_data`` to the captured value. Otherwise, does
104+
nothing.
105+
"""
106+
107+
def __init__(self, *, addr_width, data_width, name=None):
108+
if not isinstance(addr_width, int) or addr_width <= 0:
109+
raise ValueError("Address width must be a positive integer, not {!r}"
110+
.format(addr_width))
111+
if not isinstance(data_width, int) or data_width <= 0:
112+
raise ValueError("Data width must be a positive integer, not {!r}"
113+
.format(data_width))
114+
self.addr_width = addr_width
115+
self.data_width = data_width
83116

84-
Wide registers
85-
--------------
117+
super().__init__([
118+
("addr", addr_width),
119+
("r_data", data_width),
120+
("r_stb", 1),
121+
("w_data", data_width),
122+
("w_stb", 1),
123+
], name=name, src_loc_at=1)
124+
125+
126+
class CSRDecoder(Elaboratable):
127+
"""CSR bus decoder.
128+
129+
An address-based multiplexer for CSR registers implementing atomic updates.
130+
131+
Latency
132+
-------
133+
134+
Writes are registered, and are performed 1 cycle after ``w_stb`` is asserted.
135+
136+
Alignment
137+
---------
86138
87139
Because the CSR bus conserves logic and routing resources, it is common to e.g. access
88140
a CSR bus with an *n*-bit data path from a CPU with a *k*-bit datapath (*k>n*) in cases
@@ -107,56 +159,29 @@ class CSRMultiplexer(Elaboratable):
107159
Parameters
108160
----------
109161
addr_width : int
110-
Address width. At most ``(2 ** addr_width) * data_width`` register bits will be available.
162+
Address width. See :class:`CSRInterface`.
111163
data_width : int
112-
Data width. Registers are accessed in ``data_width`` sized chunks.
164+
Data width. See :class:`CSRInterface`.
113165
alignment : int
114166
Register alignment. The address assigned to each register will be a multiple of
115167
``2 ** alignment``.
116168
117169
Attributes
118170
----------
119-
addr : Signal(addr_width)
120-
Address for reads and writes.
121-
r_data : Signal(data_width)
122-
Read data. Valid on the next cycle after ``r_stb`` is asserted.
123-
r_stb : Signal()
124-
Read strobe. If ``addr`` points to the first chunk of a register, captures register value
125-
and causes read side effects to be performed (if any). If ``addr`` points to any chunk
126-
of a register, latches the captured value to ``r_data``. Otherwise, latches zero
127-
to ``r_data``.
128-
w_data : Signal(data_width)
129-
Write data. Must be valid when ``w_stb`` is asserted.
130-
w_stb : Signal()
131-
Write strobe. If ``addr`` points to the last chunk of a register, writes captured value
132-
to the register and causes write side effects to be performed (if any). If ``addr`` points
133-
to any chunk of a register, latches ``w_data`` to the captured value. Otherwise, does
134-
nothing.
171+
bus : :class:`CSRInterface`
172+
CSR bus providing access to registers.
135173
"""
136174
def __init__(self, *, addr_width, data_width, alignment=0):
137-
if not isinstance(addr_width, int) or addr_width <= 0:
138-
raise ValueError("Address width must be a positive integer, not {!r}"
139-
.format(addr_width))
140-
if not isinstance(data_width, int) or data_width <= 0:
141-
raise ValueError("Data width must be a positive integer, not {!r}"
142-
.format(data_width))
175+
self.bus = CSRInterface(addr_width=addr_width, data_width=data_width)
176+
143177
if not isinstance(alignment, int) or alignment < 0:
144178
raise ValueError("Alignment must be a non-negative integer, not {!r}"
145179
.format(alignment))
146-
147-
self.addr_width = int(addr_width)
148-
self.data_width = int(data_width)
149-
self.alignment = alignment
180+
self.alignment = alignment
150181

151182
self._next_addr = 0
152183
self._elements = dict()
153184

154-
self.addr = Signal(addr_width)
155-
self.r_data = Signal(data_width)
156-
self.r_stb = Signal()
157-
self.w_data = Signal(data_width)
158-
self.w_stb = Signal()
159-
160185
def add(self, element):
161186
"""Add a register.
162187
@@ -176,7 +201,7 @@ def add(self, element):
176201
.format(element))
177202

178203
addr = self.align_to(self.alignment)
179-
self._next_addr += (element.width + self.data_width - 1) // self.data_width
204+
self._next_addr += (element.width + self.bus.data_width - 1) // self.bus.data_width
180205
size = self.align_to(self.alignment) - addr
181206
self._elements[addr] = element, size
182207
return addr, size
@@ -222,33 +247,33 @@ def elaborate(self, platform):
222247
# arithmetic comparisons, since some toolchains (e.g. Yosys) are too eager to infer
223248
# carry chains for comparisons, even with a constant. (Register sizes don't have
224249
# to be powers of 2.)
225-
with m.Switch(self.addr):
250+
with m.Switch(self.bus.addr):
226251
for chunk_offset in range(elem_size):
227-
chunk_slice = slice(chunk_offset * self.data_width,
228-
(chunk_offset + 1) * self.data_width)
252+
chunk_slice = slice(chunk_offset * self.bus.data_width,
253+
(chunk_offset + 1) * self.bus.data_width)
229254
with m.Case(elem_addr + chunk_offset):
230255
if "r" in elem.access:
231-
chunk_r_stb = Signal(self.data_width,
256+
chunk_r_stb = Signal(self.bus.data_width,
232257
name="{}__r_stb_{}".format(elem.name, chunk_offset))
233258
r_data_fanin |= Mux(chunk_r_stb, shadow[chunk_slice], 0)
234259
if chunk_offset == 0:
235-
m.d.comb += elem.r_stb.eq(self.r_stb)
236-
with m.If(self.r_stb):
260+
m.d.comb += elem.r_stb.eq(self.bus.r_stb)
261+
with m.If(self.bus.r_stb):
237262
m.d.sync += shadow.eq(elem.r_data)
238263
# Delay by 1 cycle, allowing reads to be pipelined.
239-
m.d.sync += chunk_r_stb.eq(self.r_stb)
264+
m.d.sync += chunk_r_stb.eq(self.bus.r_stb)
240265

241266
if "w" in elem.access:
242267
if chunk_offset == elem_size - 1:
243268
# Delay by 1 cycle, avoiding combinatorial paths through
244269
# the CSR bus and into CSR registers.
245-
m.d.sync += elem.w_stb.eq(self.w_stb)
246-
with m.If(self.w_stb):
247-
m.d.sync += shadow[chunk_slice].eq(self.w_data)
270+
m.d.sync += elem.w_stb.eq(self.bus.w_stb)
271+
with m.If(self.bus.w_stb):
272+
m.d.sync += shadow[chunk_slice].eq(self.bus.w_data)
248273

249274
with m.Default():
250275
m.d.sync += shadow.eq(0)
251276

252-
m.d.comb += self.r_data.eq(r_data_fanin)
277+
m.d.comb += self.bus.r_data.eq(r_data_fanin)
253278

254279
return m

0 commit comments

Comments
 (0)