Register Generation

Nomenclature

There are three constructs you need to know about when working with registers:

Field

A field is the atomic unit of a register. A field can have a minimum size of 1 and a maximum size of the register width. Every field has an associated access level that defines both hardware and software access.

Register

A register is a collection of fields. A register has an associated address that can be accessed over a connected bus (if the access policy allows it).

Register Map

A register map is a collection of registers.

Basics

Corsair allows you to define your registers in a human-readable format (.json in our case) and generate all sorts of outputs from that single source of truth.

Configuration File

The configuration file defines how corsair behaves when creating a register map from a register file and which outputs should be generated from the register map.

The global configuration section (globcfg) gives information about the general configuration of the corsair run. The sections below specify the generation targets, where each section defines information needed for the corresponding generator.

Global Configuration Options

Parameter

Default value

Description

base_address

0

Register map base address in global address map

data_width

32

Width of all data buses and registers

address_width

16

Address bus width (capacity of the register map)

register_reset

sync_pos

Flip-flop reset style

sync_pos

Synchronous active high reset

sync_neg

Synchronous active low reset

async_pos

Asynchronous active high reset

async_neg

Asynchronous active low reset

address_increment

none

Address auto increment mode, if no address is provided for a register

none

Address auto increment mode is disabled

data_width

Enable address auto increment with value based on globcfg.data_width

<positive-integer>

Enable address auto increment with provided number of bytes, e.g 4

address_alignment

data_width

Check for address alignment of registers.

none

No check of address alignment

data_width

Enable check of address alignment based on globcfg.data_width

<positive-integer>

Enable check of address alignment based on provided number of bytes, e.g 4

force_name_case

none

Force case for all the names (registers, bit fields, enums)

none

Names used as they are

lower

Force names to have lowercase

upper

Force names to have uppercase

A configuration file may look like this:

[globcfg]
base_address = 0                    # register addressing starts at 0
data_width = 32                     # registers are 32 bit wide
address_width = 10                  # 10 bit wide addresses => max # registers = 1024
register_reset = sync_neg           # synchronous active-low reset
address_increment = data_width      # auto-increment unspecified addresses by data width (32 / 8 = 4)
address_alignment = data_width      # addresses must be aligned to data width (0, 4, 8, ...)
force_name_case = none              # do not force any naming convention
regmap_path = xibif-regs.json       # default register map file path

[vhdl]
path = src/XiBIF_regs.vhd           # output file path
read_filler = 0                     # return '0' as data on an invalid read
interface = axil                    # define AXI-Lite as the interface to the registers
generator = Vhdl                    # generate the registers as a VHDL-file

[py]
path = ../sw/python/regs.py         # output file path
generator = Python                  # generate a Python class corresponding to the registers

Note

You must not put comments in the actual configuration file. The ones shown here are only for documentation.

Warning

Data widths exceeding 64 bits are not supported!

Register File

The register file contains the actual information about the register to create. Below you can see the default register file that is provided after project creation.

{
    "regmap": [
        {
            "name": "version",
            "description": "Version Register",
            "address": 0,
            "bitfields": [
                {
                    "name": "maj",
                    "description": "Major Version ID",
                    "reset": 3,
                    "width": 8,
                    "lsb": 24,
                    "access": "ro",
                    "hardware": "n"
                },
                {
                    "name": "min",
                    "description": "Minor Version ID",
                    "reset": 1,
                    "width": 8,
                    "lsb": 16,
                    "access": "ro",
                    "hardware": "n"
                },
                {
                    "name": "patch",
                    "description": "Patch ID",
                    "reset": 0,
                    "width": 8,
                    "lsb": 8,
                    "access": "ro",
                    "hardware": "n"
                },
                {
                    "name": "rev",
                    "description": "Revision ID",
                    "reset": 0,
                    "width": 8,
                    "lsb": 0,
                    "access": "ro",
                    "hardware": "n"
                }
            ]
        },
        {
            "name": "UUID",
            "description": "Unique ID",
            "bitfields": [
                {
                    "name": "uuid",
                    "description": "Unique ID",
                    "reset": 32,
                    "width": 32,
                    "lsb": 0,
                    "access": "ro",
                    "hardware": "n"
                }
            ]
        },
        {
            "name": "register_1",
            "description": "Demo Register 1",
            "bitfields": [
                {
                    "name": "demo",
                    "description": "Demo Register 1",
                    "reset": 0,
                    "width": 32,
                    "lsb": 0,
                    "access": "ro",
                    "hardware": "i"
                }
            ]
        },
        {
            "name": "register_2",
            "description": "Demo Register 2",
            "bitfields": [
                {
                    "name": "demo",
                    "description": "Demo Register 2",
                    "reset": 0,
                    "width": 32,
                    "lsb": 0,
                    "access": "rw",
                    "hardware": "o"
                }
            ]
        }
    ]
}

There are four registers being generated: version, UUID, register_1, and register_2. The version and the UUID registers are special as they will always be created regardless of whether you specify them or not. If they are not present in the register file, they will be prepended during building automatically. These registers are needed to perform some run-time checks during the start-up and connection set-up phase.

The other two generated registers are read-write and read-only respectively. They serve the purpose to show you that you can specify different access permissions to the registers.

Warning

The field name must only contain alphanumeric characters and underlines. Spaces and other characters are not supported! Use the description field to be more verbose.

Generators

A generator is an instance for some output that will be generated from a register map, dependent on some configuration in the configuration file.

The available generators are:

  • Json

  • Yaml

  • Txt

  • Verilog

  • Vhdl

  • VerilogHeader

  • CHeader

  • SystemVerilogPackage

  • Markdown

  • Asciidoc

  • Latex

  • Python

  • Matlab

  • GuiPython (requires Gtk3)

If you want to know more about the specifics of how this all works, you can come ask us. For your project, you will probably be fine with the generators that are pre-configured for you. You can modify the configuration file however you want.

Register Access

Addressing

Since the registers in XiBIF are connected to the AXI bus, they must be compliant with how registers are addressed. The AXI bus addresses data byte-wise, meaning that a register with a width of 32 bits requires 4 bytes of address space.

There are many reasons that the AXI bus is byte-addressed instead of register-addressed; the important thing is that this has to be taken into account when assigning register addresses. Luckily, there is an option in the config file for Corsair that checks the address alignment upon register creation. The setting is called address_alignment and is set to data_width by default.

Warning

It may be fun to increase the register width and/or address width to insane numbers, but this will consume considerable resources on the FPGA.

For convenience, the setting address_increment is also set to data_width. This means you only have to give an address to the first register in the register file, and the rest is automatically addressed correctly. If you want to manually assign addresses, you can set this to none.

Software Access

Software access mode defines how the registers behave upon read and write operations from software.

Access modes

Description

rw

Read and Write. The field can be read or written.

rw1c

Read and Write 1 to Clear. The field can be read, and when 1 is written, the field is cleared.

rw1s

Read and Write 1 to Set. The field can be read, and when 1 is written, the field is set.

ro

Read Only. Write has no effect.

roc

Read Only to Clear. The field is cleared after every read.

roll

Read Only / Latch Low. The field captures a hardware active low pulse signal and is stuck at 0. The field is set after every read.

rolh

Read Only / Latch High. The field captures a hardware active high pulse signal and is stuck at 1. Read the field to clear it.

wo

Write Only. Zeros are always read.

wosc

Write Only / Self Clear. The field is cleared on the next clock tick after write.

Hardware Access

Hardware options are used to define how the bit field will interact with HDL logic. Most of the hardware options generate inputs/outputs on the register block design, allowing for a variety of hardware interactions with your data.

Hardware options

Description

i

Input. Use input value from hardware to update the field.

o

Output. Enable output value from the field to be accessed by hardware.

c

Clear. Add signal to clear the field (fill with all zeros).

s

Set. Add signal to set the field (fill with all ones).

e

Enable. Add signal to enable the field to capture input value (must be used with i).

l

Lock. Add signal to lock the field (to prevent any changes).

a

Access. Add signals to notify when bus access to the field is performed.

q

Queue. Enable queue (LIFO, FIFO) access.

f

Fixed. Enable fixed mode (field is a constant).

n

None. No hardware access to the field.

Enumerations

A bit field may contain values corresponding to specific modes/settings, called enumerated values, aka enums. You can assign enums to a bit field in the register configuration file.

Example Register File

{
    "regmap":[
        {
            "name": "CTRL",
            "description": "Control register",
            "address": 8,
            "bitfields": [
                {
                    "name": "BAUD",
                    "description": "Baudrate value",
                    "reset": 0,
                    "width": 2,
                    "lsb": 0,
                    "access": "rw",
                    "hardware": "o",
                    "enums": [
                        {
                            "name": "B9600",
                            "description": "9600 baud",
                            "value": 0
                        },
                        {
                            "name": "B38400",
                            "description": "38400 baud",
                            "value": 1
                        },
                        {
                            "name": "B115200",
                            "description": "115200 baud",
                            "value": 2
                        }
                    ]
                }
            ]
        }
    ]
}

The above register configuration generates a register called CTRL with a single-bit field called BAUD. Three enumerations are defined called B9600, B38400, and B115200, corresponding to the register values 0, 1, and 2, respectively.

Warning

Since the bit field is 2 bits wide, value 3 does not have an associated enumeration and should be considered invalid or reserved. Do not make fields that have values that should be valid but are not enumerated. In the above example, if the register containing value 3 would correspond to a baud rate of 123456 baud, but would not be written out as an enumeration, a lot of confusion could ensue.

Best Practices for using Enums

Enumerations are a great tool for improving the readability of your code, however, they also come with some pitfalls. Usually, it is best to have descriptive names for enums, such as POWER_MODE_SLEEP or ADC_DISABLE. Simply using ON or OFF is not descriptive enough. Using enums is recommended for fields that have a fixed set of possible values, for example operation modes, error codes or configuration. They should be avoided for values that do not benefit from being enumerated. For example, one could enumerate all integers as 1=ONE, 2=TWO, etc…

Generating the Registers

Once you have specified your register, you will somehow need to incorporate them into the hardware. To do this, use:

xibif register --user

The --user flag specifies that you only want to generate the so-called user-registers. There is a whole set of registers to which you do not have access that take care of many things in the background. Assuming you have not made any mistakes while typing out the register file, HDL descriptions, documentation and drivers of your registers which will be created and the registers will be automatically inserted into the existing block design.

You should then be able to see the changes you have made to the registers in the block design. Depending on the options you specified, you will see more or less inputs/outputs on the register block.

Warning

If you have the opened the block design in Vivado, do not edit it until the command has finished executing. You might need to reload the view after generation, but usually Vivado indicates this visually.

Custom Generators

You might want to create an additional set of registers that do not have anything to do with the XiBIF core. For this, you can create your own generator instance in the configuration file.

To enable custom generators for your XiBIF project, use the following command:

xibif register --enable-custom <path-to-config-file>

The path to the config file is expected to be in the hw/regs folder of the project.

To generate the custom registers, use:

xibif register --custom

If you wish to disable custom generators, use:

xibif register --disable-custom

Register Access Policies Examples

Software Access

rw

This mode allows both reading and writing to the register field. It is commonly used for configuration registers where software needs to set and modify values dynamically, such as enabling or disabling features or adjusting parameters.

rw1c

A field with this mode can be read normally, but writing a 1 clears the field. This is useful for status registers where a flag should be cleared after acknowledging an event, such as clearing an interrupt request after it has been handled.

rw1s

In this mode, writing a 1 sets the field without affecting other bits, while a 0 has no effect. This is useful for setting control flags that should remain active until cleared by another mechanism.

ro

The register can be read but not written. This is typically used for status registers that reflect hardware state, such as sensor readings, error codes, or system status indicators.

roc

This mode means the field is automatically cleared when read. It is useful for event-driven registers where reading the value acknowledges and clears the event, preventing stale data from being processed.

roll

The register captures and holds a low signal when detected, preserving its state until explicitly reset. This is often used for hardware event logging, such as detecting the occurrence of a transient fault condition.

rolh

Similar to roll, but latches a high signal when detected. It is commonly used in scenarios where the system must record the occurrence of an over-threshold event, such as detecting when a voltage or temperature exceeds a critical limit.

wo

The register can only be written to; reads always return zero or undefined data. This is typically used for control registers where writing a value triggers an action, such as starting a DMA transfer or resetting a peripheral.

wosc

A write operation updates the field, but it automatically clears itself on the next clock cycle. This is useful for momentary control signals, such as issuing a reset or triggering a one-shot operation without requiring manual clearing by software.

Hardware Access

input

Use input value from hardware to update the field. This mode is used when the field should reflect a hardware-driven signal, such as sensor readings or external control signals.

output

Enable output value from the field to be accessed by hardware. This is typically used to expose internal register values to external hardware components.

clear

Add signal to clear the field (fill with all zeros). Often used in control or status registers where a specific event or condition should reset the value.

set

Add signal to set the field (fill with all ones). Used for flags or conditions that require explicit activation through hardware control.

enable

Add signal to enable the field to capture an input value (must be used with input). This is useful for gating mechanisms that require explicit enabling before updating.

lock

Add signal to lock the field (to prevent any changes). Used for security-sensitive or configuration registers that should not be altered after initialization.

access

Add signals to notify when bus access to the field is performed. Useful for tracking register accesses or triggering auxiliary logic based on register interaction.

queue

Enable queue (LIFO, FIFO) access. Used for buffering mechanisms where multiple values need to be stored and accessed in sequence.

fixed

Enable fixed mode (field is a constant). Typically used for hardcoded configuration values that must remain unchanged.

none

No hardware access to the field. Used when a field is meant to be software-only, with no interaction from hardware components.