System overview

The rhea package provides frameworks for building complex digital circuits. These include modular and scalable interfaces and blocks. The following describes the specification for the frameworks being developed in the rhea package.

Control and status

One of the goals of the rhea package is to simplify the assembly of systems. In a complex digital system majority of the blocks will have two interfaces. One being the streaming data in and out of the module and the other a control and status interface. The control and status provides a lower-bandwidth interface into the component (block).

Defining a peripheral specific control-status object (CSO).

import rhea.system import ControlStatusBase

# define the control and status signals for a peripheral
class ControlStatus(ControlStatusBase):
    modes = enum("counting", "walking", "strobing")
    def __init__(self)
        self.enable = Signal(bool(0))
        self.pause = Signal(bool(0))
        self.mode = Signal(self.modes.counting)

In a peripheral either the default (defined) control-status object CSO can be used and added to the control-status interface.

@myhdl.block
def led_blinker(glbl, led, cso):
    # the cso interface provides the control and status for
    # this module
    assert isinstance(ControlStatus)
    clock, reset = glbl.clock, glbl.reset
    modes = ControlStatus.modes
    enabled = Signal(bool(0))

    @always_comb
    def beh_enabled():
        enabled.next = glbl.enable and cso.enable

    @always_seq(clock.posedge, reset=reset)
    def beh_blink():
        if enabled and not cso.pause:
            nextled = 0
            if cso.mode == modes.counting:
                # counting logic
            elif cso.mode == modes.walking:
                # walking logic
            elif cso.mode == modes.strobing
                # strobing logic

            led.next = nextled

    return beh_enable, beh_blink

led_blinker.cso = ControlStatus

The non-global control and status, i.e. the module specific control-status is accessed via the cso. This provides a clean encapsulation to the block (module). The cso can also include transactors to assist testing and verification.

Creating control status objects

The above example shows how a collections of control and status signals are defined in a class. To help guide the the tools, some additional information can be defined:

  • driven: set the signal driven attribute to true to indicate a read-only (status) attribute.
  • Use the hardware-types Bit and Byte to help drive the how the attributes are organized in a register-file. The Bit and Byte are only used to give hints to the register-file builder, if memory-mapped access is secondary use the standard :myhdl:class:`Signal`.
  • Use initial_value property to overwrite the signals initial value, this is useful is static configurations.

Register files

When creating components for a design often a register file is included The register file is used for the control and status access (CSR) of the component. A register file is simply a collection of `registers`_ that are used to control the component and read status. The register file is accessed by a memory-mapped bus. The register file provides dynamic control and status of the component.

The objects to create a register file encapsulate much of the detail required for typical register-file definition. In addition provides a mechanism for static definition (no bus present).

The following is a short example building a simple register file. Note the following is the manaul method to the example being used in this document. Utilizing the ControlStatusBase is an automated process, in majority of the cases register-files should not be explicitly defined but rather build from a CSO.

from rhea.system import RegisterFile, Register

# create a register file
regfile = RegisterFile()

# create a status register and add it to the register file
reg = Register('status', width=8, access='ro', default=0)
regfile.add_register(reg)

# create a control register with named bits and add
reg = Register('control', width=8, access='rw', default=1)
reg.add_named_bits('enable', bits=0, comment="enable the component")
reg.add_named_bits('pause', bits=1, comment="pause current operation")
reg.add_named_bits('mode', bits=(4, 2), comment="select mode")
regfile.add_register(reg)

Note

The current implementation requires all the register in a register file to be the same width.

The above example defines a register file to be used. This can be used in a new component/peripheral.

@myhdl.block
def led_blinker(glbl, membus, leds):
    clock = glbl.clock
    # instantiate the register interface module and add the
    # register file to the list of memory-spaces
    regfile.base_address = 0x8240
    regfile_inst = membus.add(glbl, regfile)

    # instantiate different LED blinking modules
    led_modules = (led_stroby, led_dance, led_count,)
    led_drivers = [Signal(leds.val) for _ in led_modules]
    mod_inst = []
    for ii, ledmod in enumerate(led_modules):
        mod_inst.append(ledmod(glbl, led_drivers[ii]))

    @always(clock.posedge)
    def beh_led_assign():
        leds.next = led_drivers[regfile.mode]

    return regfile_inst, mod_inst, beh_led_assign

The led_blinker() module demonstrates how to add the created RegisterFile to the memory-mapped bus and get a myhdl instance that provides the logic to read and write the register file from the bus interface passed to the module.

Note, in the above example a base_address was set. If the base_address attribute is not present the :class:`MemoryMapped

Memory map interfaces

The :Register Files: section examples eluded to the memory-map (or CSR) interfaces and how they can be connected to register file. The rhea project contains the following memory-map interfaces:

  • Barebone
  • Wishbone
  • AvalonMM
  • AXI4Lite

Each of these implement a memory-map bus type/specification and each can be passed as and interface to a module. Each of the specific memory-mapped bus classes inherit the MemoryMapped class. The MemoryMapped defines the attributes and methods the memory-mapped buses have in common.

When interfacing to a register file, the register file is added to the bus as shown in the previous example with the MemoryMapped.add() function. The register file covers many use cases for adding control and status interfaces to different components. Each interface also contains a module to adapt the memory-map interface to a generic interface. In this case each bus is mapped to the Barebone bus with the MemoryMapped.map_to_generic() function / myhdl module.

The next section outlines how the RegisterFile and the corresponding registers is typically not used as defined above. Rather, an automated mapping of the control-status object is mapped to the memory-space. Software is used to encapsulate all the memory-based accesses.

From attributes to bus cycles

When designing a complex digital system with the rhea components we don’t want to deal with creating explict memory-maps. We want to interface with various modules through their control-status attributes.

As defined in the above first example, for our simple LED blinker module there are a couple control signals defined. The module can be stimulated and controlled via this interface. We might have some external logic, or simply tie the module controls to physical inputs.

If we want to tie the controls to a register-file accessed by a memory-mapped this

@myhdl.block
def led_blinker(glbl, leds, membus=None, cso=None):

    if cso is None
        cso = led_blinker.cso()

    if membus is not None:
        rf = cso.get_register_file()
        membus.add(rf)

    # get any cso specific logic (if any)
    cso_inst = cso.get_generators()

    # ...

This gives a flexible mechanism to connect the module to a memory-mapped bus or simply control the module through some other mechanism (e.g. directly driven by the logic).

In the previous example all the explict addresses are hidden. The control-status attributes are accessed via the attributes (in simulation and host software) and all the memory-mapped bus accesses are hidden. The MemoryMap has utilities to export the memory-map.

Static configuration

The previous example demonstrated how the module can select to use the external cso object, default cso