CSR registers

The amaranth_soc.csr.reg module provides a way to define and create CSR registers and register fields.

Introduction

Control and Status registers are commonly used as an interface between SoC peripherals and the firmware that operates them.

This module provides the following functionality:

  1. Register field description and implementation via the Field and FieldAction classes. The amaranth_soc.csr.action module provides a built-in FieldAction subclasses for common use cases. If needed, users can implement their own subclasses.

  2. Composable layouts of register fields via FieldActionMap and FieldActionArray. These classes are not meant to be instantiated directly, but are useful when introspecting the layout of a register.

  3. Register definitions via the Register class. The fields of a register can be provided as variable annotations or as instance parameters.

  4. A Builder class to organize registers of a peripheral into a hierarchy of clusters and arrays, to be converted into a MemoryMap.

  5. A bridge between a CSR bus interface and the registers of a peripheral, via the Bridge class.

Examples

Defining a register declaratively

If its layout and access mode are known in advance, a register can be concisely defined using variable annotations:

class Status(csr.Register, access="rw"):
    rdy:    csr.Field(csr.action.R,       1)
    err:    csr.Field(csr.action.RW1C,    1)
    _unimp: csr.Field(csr.action.ResR0W0, 6)

Note

By convention, names of reserved fields (such as _unimp in the above example) should begin with an underscore.

Defining a register with instance parameters

If the layout or access mode of a register aren’t known until instantiation, a Register subclass can override them in __init__:

class Data(csr.Register):
    def __init__(self, width=8, access="w"):
        super().__init__(fields={"data": csr.Field(csr.action.W, width)},
                         access=access)

Defining a single-field register

In the previous example, the Data register has a single field named "Data.data", which is redundant.

If no other fields are expected to be added in future revisions of the peripheral (or forward compatibility is not a concern), the field name can be omitted like so:

class Data(csr.Register, access="w"):
    def __init__(self):
        super().__init__(csr.Field(csr.action.W, 8))

Defining a register with nested fields

Hierarchical layouts of register fields can be expressed using lists and dicts:

class SetClr(csr.Register, access="r"):
    pin: [{"set": csr.Field(csr.action.W, 1),
           "clr": csr.Field(csr.action.W, 1)} for _ in range(8)]

Connecting registers to a CSR bus

In this example, the registers of FooPeripheral are added to a Builder to produce a memory map, and then bridged to a bus interface:

class FooPeripheral(wiring.Component):
    class Ctrl(csr.Register, access="rw"):
        enable: csr.Field(csr.action.RW, 1)
        _unimp: csr.Field(csr.action.ResR0W0, 7)

    class Data(csr.Register, access="r"):
        def __init__(self, width):
            super().__init__(csr.Field(csr.action.R, width))

    def __init__(self):
        regs = csr.Builder(addr_width=4, data_width=8)

        reg_ctrl = regs.add("Ctrl", Ctrl())
        reg_data = regs.add("Data", Data(width=32), offset=4)

        self._bridge = csr.Bridge(regs.as_memory_map())

        super().__init__({"csr_bus": In(csr.Signature(addr_width=4, data_width=8))})
        self.csr_bus.memory_map = self._bridge.bus.memory_map

    def elaborate(self, platform):
        return Module() # ...

Defining a custom field action

If amaranth_soc.csr.action built-ins do not cover a desired use case, a custom FieldAction may provide an alternative.

This example shows a “read/write-0-to-set” field action:

class RW0S(csr.FieldAction):
    def __init__(self, shape, init=0):
        super().__init__(shape, access="rw", members={
            "data":  Out(shape),
            "clear": In(shape),
        })
        self._storage = Signal(shape, init=init)
        self._init    = init

    @property
    def init(self):
        return self._init

    def elaborate(self, platform):
        m = Module()

        for i, storage_bit in enumerate(self._storage):
            with m.If(self.clear[i]):
                m.d.sync += storage_bit.eq(0)
            with m.If(self.port.w_stb & ~self.port.w_data[i]):
                m.d.sync += storage_bit.eq(1)

        m.d.comb += [
            self.port.r_data.eq(self._storage),
            self.data.eq(self._storage),
        ]

        return m

RW0S can then be passed to Field:

class Foo(csr.Register, access="rw"):
    mask: csr.Field(RW0S, 8)
    data: csr.Field(csr.action.RW, 8)

Fields

class FieldPort.Access

Field access mode.

R = 'r'
W = 'w'
RW = 'rw'
NC = 'nc'
readable()
writable()
class FieldPort.Signature

CSR register field port signature.

Parameters:
  • shape (shape-like object) – Shape of the field.

  • access (FieldPort.Access) – Field access mode.

  • attributes (Interface)

  • --------------------

  • r_data (Signal(shape)) – Read data. Must always be valid, and is sampled when r_stb is asserted.

  • r_stb (Signal()) – Read strobe. Fields with read side effects should perform them when this strobe is asserted.

  • w_data (Signal(shape)) – Write data. Valid only when w_stb is asserted.

  • w_stb (Signal()) – Write strobe. Fields should update their value or perform the write side effect when this strobe is asserted.

create(*, path=None, src_loc_at=0)

Create a compatible interface.

See wiring.Signature.create() for details.

Return type:

A FieldPort object using this signature.

__eq__(other)

Compare signatures.

Two signatures are equal if they have the same shape and field access mode.

class amaranth_soc.csr.reg.FieldPort
class amaranth_soc.csr.reg.Field

Description of a CSR register field.

Parameters:
  • action_cls (FieldAction subclass) – The type of field action to be instantiated by Field.create().

  • *args (tuple) – Positional arguments passed to action_cls.__init__.

  • **kwargs (dict) – Keyword arguments passed to action_cls.__init__.

Raises:

TypeError – If action_cls is not a subclass of FieldAction.

create()

Instantiate a field action.

Returns:

The object returned by action_cls(*args, **kwargs).

Return type:

FieldAction

Field actions

class amaranth_soc.csr.reg.FieldAction

CSR register field action.

A Component mediating access between a CSR bus and a range of bits within a Register.

Parameters:
Raises:

ValueError – If the key ‘port’ is used in members.

class amaranth_soc.csr.reg.FieldActionMap

A mapping of field actions.

Parameters:

fields (dict of str to (Field or dict or list)) –

Register fields. Fields are instantiated according to their type:

Raises:
  • TypeError – If fields is not a dict, or is empty.

  • TypeError – If fields has a key that is not a string, or is empty.

  • TypeError – If fields has a value that is neither a Field object, a dict or a list of Field objects.

__getitem__(key)

Access a field by name or index.

Returns:

The field instance associated with key.

Return type:

FieldAction or FieldActionMap or FieldActionArray

Raises:

KeyError – If there is no field instance associated with key.

__getattr__(name)

Access a field by name.

Returns:

The field instance associated with name.

Return type:

FieldAction or FieldActionMap or FieldActionArray

Raises:
  • AttributeError – If the field map does not have a field instance associated with name.

  • AttributeError – If name is reserved (i.e. starts with an underscore).

__iter__()

Iterate over the field map.

Yields:

str – Key (name) for accessing the field.

__len__()

Field map size.

Returns:

The number of items in the map.

Return type:

int

flatten()

Recursively iterate over the field map.

Yields:
class amaranth_soc.csr.reg.FieldActionArray

An array of CSR register fields.

Parameters:

fields (list of (Field or dict or list)) –

Register fields. Fields are instantiated according to their type:

Raises:
  • TypeError – If fields is not a list, or is empty.

  • TypeError – If fields has an item that is neither a Field object, a dict or a list of Field objects.

__getitem__(key)

Access a field by index.

Returns:

The field instance associated with key.

Return type:

FieldAction or FieldActionMap or FieldActionArray

__len__()

Field array size.

Returns:

The number of items in the array.

Return type:

int

flatten()

Iterate recursively over the field array.

Yields:

Registers

class amaranth_soc.csr.reg.Register

A CSR register.

Parameters:
  • fields (dict or list or Field) – Collection of register fields. If None (default), a dict is populated from Python variable annotations. fields is used to create a FieldActionMap, FieldActionArray, or FieldAction, depending on its type (dict, list, or Field).

  • access (Element.Access) – Element access mode.

Interface attributes

elementElement

Interface between this register and a CSR bus primitive.

Attributes:
raises TypeError:

If fields is neither None, a dict, a list, or a Field.

raises ValueError:

If fields is not None and at least one variable annotation is a Field.

raises ValueError:

If element.access is not readable and at least one field is readable.

raises ValueError:

If element.access is not writable and at least one field is writable.

field
f
__iter__()

Recursively iterate over the field collection.

Yields:
class amaranth_soc.csr.reg.Builder

CSR builder.

A CSR builder can organize a group of registers within an address range and convert it to a MemoryMap for consumption by other SoC primitives.

Parameters:
  • addr_width (int) – Address width.

  • data_width (int) – Data width.

  • granularity (int) – Granularity. Optional, defaults to 8 bits.

Raises:
  • TypeError – If addr_width is not a positive integer.

  • TypeError – If data_width is not a positive integer.

  • TypeError – If granularity is not a positive integer.

  • ValueError – If granularity is not a divisor of data_width

freeze()

Freeze the builder.

Once the builder is frozen, CSR registers cannot be added anymore.

add(name, reg, *, offset=None)

Add a CSR register.

Parameters:
  • name (str) – Register name.

  • reg (Register) – Register.

  • offset (int) – Register offset. Optional.

Returns:

reg, which is added to the builder. Its name is name, prefixed by the names and indices of any parent Cluster() and Index().

Return type:

Register

Raises:
  • ValueError – If the builder is frozen.

  • TypeError – If name is not a string, or is empty.

  • TypeError – If ``reg` is not an instance of Register.

  • ValueError – If reg is already added to the builder.

  • TypeError – If offset is not an integer, or is negative.

  • ValueError – If offset is not a multiple of self.data_width // self.granularity.

Cluster(name)

Define a cluster.

Parameters:

name (str) – Cluster name.

Raises:

TypeError – If name is not a string, or is empty.

Index(index)

Define an array index.

Parameters:

index (int) – Array index.

Raises:

TypeError – If index is not an integer, or is negative.

as_memory_map()
class amaranth_soc.csr.reg.Bridge

CSR bridge.

Parameters:
  • memory_map (MemoryMap) – Memory map of CSR registers.

  • attributes (Interface)

  • --------------------

  • bus (Interface) – CSR bus providing access to the contents of memory_map.

Raises: