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'

Read-only mode.

W = 'w'

Write-only mode.

RW = 'rw'

Read/write mode.

NC = 'nc'

Not connected.

readable()

Readable access mode.

Returns:

True if equal to R or RW.

Return type:

bool

writable()

Writable access mode.

Returns:

True if equal to W or RW.

Return type:

bool

class FieldPort.Signature

CSR register field port signature.

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

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

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

  • w_stb (Out(1)) – 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 amaranth.lib.wiring.Signature.create() for details.

Return type:

FieldPort

__eq__(other)

Compare signatures.

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

class amaranth_soc.csr.reg.FieldPort

CSR register field port.

An interface between a Register and one of its fields.

Parameters:
class amaranth_soc.csr.reg.Field

Description of a CSR register field.

Parameters:
  • action_cls (subclass of FieldAction) – 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__.

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:
Members:

port (In(csr.reg.FieldPort.Signature(shape, access))) – Field port.

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:

__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:

__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()

Recursively iterate over the field array.

Yields:

Registers

class amaranth_soc.csr.reg.Register

A CSR register.

Parameters:
Members:

element (In(csr.Element.Signature(shape, access))) – Interface between this Register and a CSR bus primitive.

Raises:
field

Collection of field instances.

Return type:

FieldActionMap or FieldActionArray or FieldAction

f

Shorthand for Register.field.

Return type:

FieldActionMap or FieldActionArray or FieldAction

__iter__()

Recursively iterate over the field collection.

Yields:
class amaranth_soc.csr.reg.Builder

CSR builder.

A CSR builder collects a group of Registers within an address range with the goal of producing a MemoryMap of the resulting layout.

Parameters:
  • addr_width (int) – Address width.

  • data_width (int) – Data width.

  • granularity (int, optional) – Granularity. Defaults to 8 bits.

Raises:

ValueError – If data_width is not a multiple of granularity.

freeze()

Freeze the builder.

Once the builder is frozen, Registers cannot be added anymore.

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

Add a 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.

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

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

Cluster(name)

Define a cluster.

Parameters:

name (str) – Cluster name.

Index(index)

Define an array index.

Parameters:

index (int) – Array index.

as_memory_map()

Build a memory map.

If a register was added without an explicit offset, the implicit next address of the memory map is used. Otherwise, the register address is offset * granularity // data_width.

Registers are added to the memory map in the same order as they were added to the builder.

Return type:

MemoryMap.

class amaranth_soc.csr.reg.Bridge

CSR bridge.

Parameters:

memory_map (MemoryMap) – Memory map of Registers.

Members:

bus (In(csr.Signature(memory_map.addr_width, memory_map.data_width))) – CSR bus providing access to the contents of memory_map.

Raises:

ValueError – If memory_map has windows.