Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/nmigen-soc
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 4d7d78f23ea6
Choose a base ref
...
head repository: m-labs/nmigen-soc
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: c4b340e85090
Choose a head ref
  • 3 commits
  • 3 files changed
  • 1 contributor

Commits on Oct 25, 2019

  1. Copy the full SHA
    5520e0d View commit details
  2. csr.bus: drop CSR prefix from class names.

    Application code should use Python imports similar to:
    
        from nmigen_soc import csr
        ...
        decoder = csr.Decoder(...)
    
    thus achieving the same effect with less namespace pollution.
    whitequark committed Oct 25, 2019
    Copy the full SHA
    d8f5c4c View commit details
  3. Copy the full SHA
    c4b340e View commit details
Showing with 209 additions and 150 deletions.
  1. +1 −0 nmigen_soc/csr/__init__.py
  2. +114 −72 nmigen_soc/csr/bus.py
  3. +94 −78 nmigen_soc/test/test_csr_bus.py
1 change: 1 addition & 0 deletions nmigen_soc/csr/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .bus import *
186 changes: 114 additions & 72 deletions nmigen_soc/csr/bus.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
from functools import reduce
import enum
from nmigen import *
from nmigen import tracer


__all__ = ["CSRElement", "CSRMultiplexer"]
__all__ = ["Element", "Interface", "Decoder"]


class CSRElement(Record):
class Element(Record):
class Access(enum.Enum):
"""Register access mode.
Coarse access mode for the entire register. Individual fields can have more restrictive
access mode, e.g. R/O fields can be a part of an R/W register.
"""
R = "r"
W = "w"
RW = "rw"

def readable(self):
return self == self.R or self == self.RW

def writable(self):
return self == self.W or self == self.RW

"""Peripheral-side CSR interface.
A low-level interface to a single atomically readable and writable register in a peripheral.
@@ -17,6 +32,8 @@ class CSRElement(Record):
----------
width : int
Width of the register.
access : :class:`Access`
Register access mode.
name : str
Name of the underlying record.
@@ -37,52 +54,104 @@ def __init__(self, width, access, *, name=None, src_loc_at=0):
if not isinstance(width, int) or width < 0:
raise ValueError("Width must be a non-negative integer, not {!r}"
.format(width))
if access not in ("r", "w", "rw"):
if not isinstance(access, Element.Access) and access not in ("r", "w", "rw"):
raise ValueError("Access mode must be one of \"r\", \"w\", or \"rw\", not {!r}"
.format(access))

self.width = int(width)
self.access = access
self.width = width
self.access = Element.Access(access)

layout = []
if "r" in self.access:
if self.access.readable():
layout += [
("r_data", width),
("r_stb", 1),
]
if "w" in self.access:
if self.access.writable():
layout += [
("w_data", width),
("w_stb", 1),
]
super().__init__(layout, name=name, src_loc_at=1)


class CSRMultiplexer(Elaboratable):
class Interface(Record):
"""CPU-side CSR interface.
A low-level interface to a set of peripheral CSR registers that implements address-based
multiplexing and atomic updates of wide registers.
A low-level interface to a set of atomically readable and writable peripheral CSR registers.
Operation
---------
The CSR multiplexer splits each CSR register into chunks according to its data width. Each
chunk is assigned an address, and the first chunk of each register always has the provided
minimum alignment. This allows accessing CSRs of any size using any datapath width.
CSR registers mapped to the CSR bus are split into chunks according to the bus data width.
Each chunk is assigned a consecutive address on the bus. This allows accessing CSRs of any
size using any datapath width.
When the first chunk of a register is read, the value of a register is captured, and reads
from subsequent chunks of the same register return the captured values. When any chunk except
the last chunk of a register is written, the written value is captured; a write to the last
chunk writes the captured value to the register. This allows atomically accessing CSRs larger
than datapath width.
Reads to padding bytes return zeroes, and writes to padding bytes are ignored.
Parameters
----------
addr_width : int
Address width. At most ``(2 ** addr_width) * data_width`` register bits will be available.
data_width : int
Data width. Registers are accessed in ``data_width`` sized chunks.
name : str
Name of the underlying record.
Attributes
----------
addr : Signal(addr_width)
Address for reads and writes.
r_data : Signal(data_width)
Read data. Valid on the next cycle after ``r_stb`` is asserted.
r_stb : Signal()
Read strobe. If ``addr`` points to the first chunk of a register, captures register value
and causes read side effects to be performed (if any). If ``addr`` points to any chunk
of a register, latches the captured value to ``r_data``. Otherwise, latches zero
to ``r_data``.
w_data : Signal(data_width)
Write data. Must be valid when ``w_stb`` is asserted.
w_stb : Signal()
Write strobe. If ``addr`` points to the last chunk of a register, writes captured value
to the register and causes write side effects to be performed (if any). If ``addr`` points
to any chunk of a register, latches ``w_data`` to the captured value. Otherwise, does
nothing.
"""

def __init__(self, *, addr_width, data_width, name=None):
if not isinstance(addr_width, int) or addr_width <= 0:
raise ValueError("Address width must be a positive integer, not {!r}"
.format(addr_width))
if not isinstance(data_width, int) or data_width <= 0:
raise ValueError("Data width must be a positive integer, not {!r}"
.format(data_width))
self.addr_width = addr_width
self.data_width = data_width

super().__init__([
("addr", addr_width),
("r_data", data_width),
("r_stb", 1),
("w_data", data_width),
("w_stb", 1),
], name=name, src_loc_at=1)

Writes are registered, and add 1 cycle of latency.

Wide registers
--------------
class Decoder(Elaboratable):
"""CSR bus decoder.
An address-based multiplexer for CSR registers implementing atomic updates.
Latency
-------
Writes are registered, and are performed 1 cycle after ``w_stb`` is asserted.
Alignment
---------
Because the CSR bus conserves logic and routing resources, it is common to e.g. access
a CSR bus with an *n*-bit data path from a CPU with a *k*-bit datapath (*k>n*) in cases
@@ -107,62 +176,35 @@ class CSRMultiplexer(Elaboratable):
Parameters
----------
addr_width : int
Address width. At most ``(2 ** addr_width) * data_width`` register bits will be available.
Address width. See :class:`Interface`.
data_width : int
Data width. Registers are accessed in ``data_width`` sized chunks.
Data width. See :class:`Interface`.
alignment : int
Register alignment. The address assigned to each register will be a multiple of
``2 ** alignment``.
Attributes
----------
addr : Signal(addr_width)
Address for reads and writes.
r_data : Signal(data_width)
Read data. Valid on the next cycle after ``r_stb`` is asserted.
r_stb : Signal()
Read strobe. If ``addr`` points to the first chunk of a register, captures register value
and causes read side effects to be performed (if any). If ``addr`` points to any chunk
of a register, latches the captured value to ``r_data``. Otherwise, latches zero
to ``r_data``.
w_data : Signal(data_width)
Write data. Must be valid when ``w_stb`` is asserted.
w_stb : Signal()
Write strobe. If ``addr`` points to the last chunk of a register, writes captured value
to the register and causes write side effects to be performed (if any). If ``addr`` points
to any chunk of a register, latches ``w_data`` to the captured value. Otherwise, does
nothing.
bus : :class:`Interface`
CSR bus providing access to registers.
"""
def __init__(self, *, addr_width, data_width, alignment=0):
if not isinstance(addr_width, int) or addr_width <= 0:
raise ValueError("Address width must be a positive integer, not {!r}"
.format(addr_width))
if not isinstance(data_width, int) or data_width <= 0:
raise ValueError("Data width must be a positive integer, not {!r}"
.format(data_width))
self.bus = Interface(addr_width=addr_width, data_width=data_width)

if not isinstance(alignment, int) or alignment < 0:
raise ValueError("Alignment must be a non-negative integer, not {!r}"
.format(alignment))

self.addr_width = int(addr_width)
self.data_width = int(data_width)
self.alignment = alignment
self.alignment = alignment

self._next_addr = 0
self._elements = dict()

self.addr = Signal(addr_width)
self.r_data = Signal(data_width)
self.r_stb = Signal()
self.w_data = Signal(data_width)
self.w_stb = Signal()

def add(self, element):
"""Add a register.
Arguments
---------
element : CSRElement
element : :class:`Element`
Interface of the register.
Return value
@@ -171,12 +213,12 @@ def add(self, element):
the register, and ``size`` is the amount of chunks it takes, which may be greater than
``element.size // self.data_width`` due to alignment.
"""
if not isinstance(element, CSRElement):
raise TypeError("Element must be an instance of CSRElement, not {!r}"
if not isinstance(element, Element):
raise TypeError("Element must be an instance of csr.Element, not {!r}"
.format(element))

addr = self.align_to(self.alignment)
self._next_addr += (element.width + self.data_width - 1) // self.data_width
self._next_addr += (element.width + self.bus.data_width - 1) // self.bus.data_width
size = self.align_to(self.alignment) - addr
self._elements[addr] = element, size
return addr, size
@@ -215,40 +257,40 @@ def elaborate(self, platform):

for elem_addr, (elem, elem_size) in self._elements.items():
shadow = Signal(elem.width, name="{}__shadow".format(elem.name))
if "w" in elem.access:
if elem.access.writable():
m.d.comb += elem.w_data.eq(shadow)

# Enumerate every address used by the register explicitly, rather than using
# arithmetic comparisons, since some toolchains (e.g. Yosys) are too eager to infer
# carry chains for comparisons, even with a constant. (Register sizes don't have
# to be powers of 2.)
with m.Switch(self.addr):
with m.Switch(self.bus.addr):
for chunk_offset in range(elem_size):
chunk_slice = slice(chunk_offset * self.data_width,
(chunk_offset + 1) * self.data_width)
chunk_slice = slice(chunk_offset * self.bus.data_width,
(chunk_offset + 1) * self.bus.data_width)
with m.Case(elem_addr + chunk_offset):
if "r" in elem.access:
chunk_r_stb = Signal(self.data_width,
if elem.access.readable():
chunk_r_stb = Signal(self.bus.data_width,
name="{}__r_stb_{}".format(elem.name, chunk_offset))
r_data_fanin |= Mux(chunk_r_stb, shadow[chunk_slice], 0)
if chunk_offset == 0:
m.d.comb += elem.r_stb.eq(self.r_stb)
with m.If(self.r_stb):
m.d.comb += elem.r_stb.eq(self.bus.r_stb)
with m.If(self.bus.r_stb):
m.d.sync += shadow.eq(elem.r_data)
# Delay by 1 cycle, allowing reads to be pipelined.
m.d.sync += chunk_r_stb.eq(self.r_stb)
m.d.sync += chunk_r_stb.eq(self.bus.r_stb)

if "w" in elem.access:
if elem.access.writable():
if chunk_offset == elem_size - 1:
# Delay by 1 cycle, avoiding combinatorial paths through
# the CSR bus and into CSR registers.
m.d.sync += elem.w_stb.eq(self.w_stb)
with m.If(self.w_stb):
m.d.sync += shadow[chunk_slice].eq(self.w_data)
m.d.sync += elem.w_stb.eq(self.bus.w_stb)
with m.If(self.bus.w_stb):
m.d.sync += shadow[chunk_slice].eq(self.bus.w_data)

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

m.d.comb += self.r_data.eq(r_data_fanin)
m.d.comb += self.bus.r_data.eq(r_data_fanin)

return m
Loading