X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=nmigen_soc%2Fcsr%2Fbus.py;h=7bf5694c2de045d7f8ec9fb4299368cb703d82c9;hb=9b791169dd148c71ca91164077c690c5f4e3b048;hp=f65f7e8a82705a6b08dbbbce23d7e8184243cd22;hpb=c4b340e85090604cbbba2417ccbbac19cac43068;p=nmigen-soc.git diff --git a/nmigen_soc/csr/bus.py b/nmigen_soc/csr/bus.py index f65f7e8..7bf5694 100644 --- a/nmigen_soc/csr/bus.py +++ b/nmigen_soc/csr/bus.py @@ -1,19 +1,23 @@ import enum from nmigen import * +from nmigen.utils import log2_int +from ..memory import MemoryMap -__all__ = ["Element", "Interface", "Decoder"] + +__all__ = ["Element", "Interface", "Decoder", "Multiplexer"] 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. + 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" + R = "r" + W = "w" RW = "rw" def readable(self): @@ -24,9 +28,10 @@ class Element(Record): """Peripheral-side CSR interface. - A low-level interface to a single atomically readable and writable register in a peripheral. - This interface supports any register width and semantics, provided that both reads and writes - always succeed and complete in one cycle. + A low-level interface to a single atomically readable and writable + register in a peripheral. This interface supports any register + width and semantics, provided that both reads and writes always + succeed and complete in one cycle. Parameters ---------- @@ -40,88 +45,107 @@ class Element(Record): Attributes ---------- r_data : Signal(width) - Read data. Must be always valid, and is sampled when ``r_stb`` is asserted. + Read data. Must be always valid, and is sampled when ``r_stb`` + is asserted. r_stb : Signal() - Read strobe. Registers with read side effects should perform the read side effect when this - strobe is asserted. + Read strobe. Registers with read side effects should perform + the read side effect when this strobe is asserted. w_data : Signal(width) Write data. Valid only when ``w_stb`` is asserted. w_stb : Signal() - Write strobe. Registers should update their value or perform the write side effect when - this strobe is asserted. + Write strobe. Registers should update their value or perform + the write side effect when this strobe is asserted. """ + 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 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}" + 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 = width + self.width = width self.access = Element.Access(access) layout = [] if self.access.readable(): layout += [ ("r_data", width), - ("r_stb", 1), + ("r_stb", 1), ] if self.access.writable(): layout += [ ("w_data", width), - ("w_stb", 1), + ("w_stb", 1), ] super().__init__(layout, name=name, src_loc_at=1) + # FIXME: get rid of this + __hash__ = object.__hash__ + class Interface(Record): """CPU-side CSR interface. - A low-level interface to a set of atomically readable and writable peripheral CSR registers. + A low-level interface to a set of atomically readable and writable + peripheral CSR registers. Operation --------- - 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. + 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. + 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. Parameters ---------- addr_width : int - Address width. At most ``(2 ** addr_width) * data_width`` register bits will be available. + 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. + alignment : int + Register and window alignment. See :class:`MemoryMap`. name : str Name of the underlying record. Attributes ---------- + memory_map : MemoryMap + Map of the bus. 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. + Read data. Valid on the next cycle after ``r_stb`` is + asserted. Otherwise, zero. (Keeping read data of an unused + interface at zero simplifies multiplexers.) 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``. + 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. + 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): + def __init__(self, *, addr_width, data_width, alignment=0, 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)) @@ -130,48 +154,59 @@ class Interface(Record): .format(data_width)) self.addr_width = addr_width self.data_width = data_width + self.memory_map = MemoryMap(addr_width=addr_width, + data_width=data_width, + alignment=alignment) super().__init__([ - ("addr", addr_width), - ("r_data", data_width), - ("r_stb", 1), - ("w_data", data_width), - ("w_stb", 1), + ("addr", addr_width), + ("r_data", data_width), + ("r_stb", 1), + ("w_data", data_width), + ("w_stb", 1), ], name=name, src_loc_at=1) -class Decoder(Elaboratable): - """CSR bus decoder. +class Multiplexer(Elaboratable): + """CSR register multiplexer. An address-based multiplexer for CSR registers implementing atomic updates. Latency ------- - Writes are registered, and are performed 1 cycle after ``w_stb`` is asserted. + 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 - where CSR access latency is less important than resource usage. In this case, two strategies + 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 where CSR access latency + is less important than resource usage. In this case, two strategies are possible for connecting the CSR bus to the CPU: - * The CPU could access the CSR bus directly (with no intervening logic other than simple - translation of control signals). In this case, the register alignment should be set - to 1, and each *w*-bit register would occupy *ceil(w/n)* addresses from the CPU - perspective, requiring the same amount of memory instructions to access. - * The CPU could also access the CSR bus through a width down-converter, which would issue - *k/n* CSR accesses for each CPU access. In this case, the register alignment should be - set to *k/n*, and each *w*-bit register would occupy *ceil(w/k)* addresses from the CPU - perspective, requiring the same amount of memory instructions to access. - - If alignment is greater than 1, it affects which CSR bus write is considered a write to - the last register chunk. For example, if a 24-bit register is used with a 8-bit CSR bus and - a CPU with a 32-bit datapath, a write to this register requires 4 CSR bus writes to complete - and the 4th write is the one that actually writes the value to the register. This allows - determining write latency solely from the amount of addresses the register occupies in - the CPU address space, and the width of the CSR bus. + * The CPU could access the CSR bus directly (with no intervening + logic other than simple translation of control signals). In + this case, the register alignment should be set to 1, and each + *w*-bit register would occupy *ceil(w/n)* addresses from the CPU + perspective, requiring the same amount of memory instructions + to access. + * The CPU could also access the CSR bus through a width + down-converter, which would issue *k/n* CSR accesses for each + CPU access. In this case, the register alignment should be set + to *k/n*, and each *w*-bit register would occupy *ceil(w/k)* + addresses from the CPU perspective, requiring the same amount + of memory instructions to access. + + If alignment is greater than 1, it affects which CSR bus write + is considered a write to the last register chunk. For example, + if a 24-bit register is used with a 8-bit CSR bus and a CPU with a + 32-bit datapath, a write to this register requires 4 CSR bus writes + to complete and the 4th write is the one that actually writes the + value to the register. This allows determining write latency solely + from the amount of addresses the register occupies in the CPU address + space, and the width of the CSR bus. Parameters ---------- @@ -180,70 +215,40 @@ class Decoder(Elaboratable): data_width : int Data width. See :class:`Interface`. alignment : int - Register alignment. The address assigned to each register will be a multiple of - ``2 ** alignment``. + Register alignment. See :class:`Interface`. Attributes ---------- bus : :class:`Interface` CSR bus providing access to registers. """ + def __init__(self, *, addr_width, data_width, alignment=0): - self.bus = Interface(addr_width=addr_width, data_width=data_width) + self.bus = Interface(addr_width=addr_width, + data_width=data_width, + alignment=alignment, + name="csr") + self._map = self.bus.memory_map - if not isinstance(alignment, int) or alignment < 0: - raise ValueError("Alignment must be a non-negative integer, not {!r}" - .format(alignment)) - self.alignment = alignment + def align_to(self, alignment): + """Align the implicit address of the next register. - self._next_addr = 0 - self._elements = dict() + See :meth:`MemoryMap.align_to` for details. + """ + return self._map.align_to(alignment) - def add(self, element): + def add(self, element, *, addr=None, alignment=None): """Add a register. - Arguments - --------- - element : :class:`Element` - Interface of the register. - - Return value - ------------ - An ``(addr, size)`` tuple, where ``addr`` is the address assigned to the first chunk of - the register, and ``size`` is the amount of chunks it takes, which may be greater than - ``element.size // self.data_width`` due to alignment. + See :meth:`MemoryMap.add_resource` for details. """ if not isinstance(element, Element): - raise TypeError("Element must be an instance of csr.Element, not {!r}" - .format(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.bus.data_width - 1) // self.bus.data_width - size = self.align_to(self.alignment) - addr - self._elements[addr] = element, size - return addr, size - - def align_to(self, alignment): - """Align the next register explicitly. - - Arguments - --------- - alignment : int - Register alignment. The address assigned to the next register will be a multiple of - ``2 ** alignment`` or ``2 ** self.alignment``, whichever is greater. - - Return value - ------------ - Address of the next register. - """ - if not isinstance(alignment, int) or alignment < 0: - raise ValueError("Alignment must be a non-negative integer, not {!r}" - .format(alignment)) - - align_chunks = 1 << alignment - if self._next_addr % align_chunks != 0: - self._next_addr += align_chunks - (self._next_addr % align_chunks) - return self._next_addr + size = (element.width + self.bus.data_width - 1) // self.bus.data_width + return self._map.add_resource( + element, size=size, addr=addr, alignment=alignment) def elaborate(self, platform): m = Module() @@ -255,41 +260,139 @@ class Decoder(Elaboratable): # 2-AND or 2-OR gates. r_data_fanin = 0 - for elem_addr, (elem, elem_size) in self._elements.items(): + for elem, (elem_start, elem_end) in self._map.resources(): shadow = Signal(elem.width, name="{}__shadow".format(elem.name)) + if elem.access.readable(): + shadow_en = Signal( + elem_end - elem_start, + name="{}__shadow_en".format( + elem.name)) + m.d.sync += shadow_en.eq(0) if elem.access.writable(): m.d.comb += elem.w_data.eq(shadow) + m.d.sync += elem.w_stb.eq(0) # 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.bus.addr): - for chunk_offset in range(elem_size): - chunk_slice = slice(chunk_offset * self.bus.data_width, - (chunk_offset + 1) * self.bus.data_width) - with m.Case(elem_addr + chunk_offset): + for chunk_offset, chunk_addr in enumerate( + range(elem_start, elem_end)): + shadow_slice = shadow.word_select( + chunk_offset, self.bus.data_width) + + with m.Case(chunk_addr): 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: + r_data_fanin |= Mux( + shadow_en[chunk_offset], shadow_slice, 0) + if chunk_addr == elem_start: 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.bus.r_stb) + m.d.sync += shadow_en.eq(self.bus.r_stb << + chunk_offset) if elem.access.writable(): - if chunk_offset == elem_size - 1: + if chunk_addr == elem_end - 1: # Delay by 1 cycle, avoiding combinatorial paths through # the CSR bus and into CSR registers. 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) + m.d.sync += shadow_slice.eq(self.bus.w_data) + + m.d.comb += self.bus.r_data.eq(r_data_fanin) + + return m + + +class Decoder(Elaboratable): + """CSR bus decoder. + + An address decoder for subordinate CSR buses. + + Usage + ----- + + Although there is no functional difference between adding a set of + registers directly to a :class:`Multiplexer` and adding a set of + registers to multiple :class:`Multiplexer`s that are aggregated with + a :class:`Decoder`, hierarchical CSR buses are useful for organizing + a hierarchical design. If many peripherals are directly served by + a single :class:`Multiplexer`, a very large amount of ports will + connect the peripheral registers with the decoder, and the cost of + decoding logic would not be attributed to specific peripherals. + With a decoder, only five signals per peripheral will be used, + and the logic could be kept together with the peripheral. + + Parameters + ---------- + addr_width : int + Address width. See :class:`Interface`. + data_width : int + Data width. See :class:`Interface`. + alignment : int + Window alignment. See :class:`Interface`. + + Attributes + ---------- + bus : :class:`Interface` + CSR bus providing access to subordinate buses. + """ + + def __init__(self, *, addr_width, data_width, alignment=0): + self.bus = Interface(addr_width=addr_width, + data_width=data_width, + alignment=alignment, + name="csr") + self._map = self.bus.memory_map + self._subs = dict() + + def align_to(self, alignment): + """Align the implicit address of the next window. + + See :meth:`MemoryMap.align_to` for details. + """ + return self._map.align_to(alignment) + + def add(self, sub_bus, *, addr=None): + """Add a window to a subordinate bus. + + See :meth:`MemoryMap.add_resource` for details. + """ + if not isinstance(sub_bus, Interface): + raise TypeError("Subordinate bus must be an instance of " + "csr.Interface, not {!r}" .format(sub_bus)) + if sub_bus.data_width != self.bus.data_width: + raise ValueError("Subordinate bus has data width {}, " + "which is not the same as " + "decoder data width {}" + .format(sub_bus.data_width, self.bus.data_width)) + self._subs[sub_bus.memory_map] = sub_bus + return self._map.add_window(sub_bus.memory_map, addr=addr) + + def elaborate(self, platform): + m = Module() + + # See Multiplexer.elaborate above. + r_data_fanin = 0 + + with m.Switch(self.bus.addr): + for sub_map, (sub_pat, sub_ratio) in self._map.window_patterns(): + assert sub_ratio == 1 + + sub_bus = self._subs[sub_map] + m.d.comb += sub_bus.addr.eq(self.bus.addr[:sub_bus.addr_width]) + + # The CSR bus interface is defined to output zero when idle, allowing us to avoid + # adding a multiplexer here. + r_data_fanin |= sub_bus.r_data + m.d.comb += sub_bus.w_data.eq(self.bus.w_data) - with m.Default(): - m.d.sync += shadow.eq(0) + with m.Case(sub_pat): + m.d.comb += sub_bus.r_stb.eq(self.bus.r_stb) + m.d.comb += sub_bus.w_stb.eq(self.bus.w_stb) m.d.comb += self.bus.r_data.eq(r_data_fanin)