{{% lowrisc-doc-hdr Comportability Definition and Specification }}

Document Goals

This document is aimed at laying out the definition of a comportable IP design, i.e. one that is ported to conform to the framework of lowRISC ecosystem IP, suitable for inclusion in compliant designs. This is mostly a technical discussion and specification of interface compliance within the framework. Separate documents contain or will contain critical elements like coding style, verification, and documentation, but are not the purview of this specification.

A good definition of Comportable can be found in Johnson's Dictionary of the English Language. The 1808 miniature edition gives “Comportable, a. consistent, suitable, fit”

scan of definition on page 45

{{% toc 3 }}

Definitions

The table below lists some keywords used in this specification.

KeywordDefinition
alertsInterrupt-type outputs of IP designs that are classified as security critical. These have special handling in the outer chip framework.
comportableA definition of compliance on the part of IP that is able to plug and play with other IP to form the full chip framework.
CSRsControl and Status Registers; loosely the collection of registers within a peripheral which are addressable by the (local) host processor via a chip-wide address map. Special care will be dedicated to the definition and handling of CSRs to maximize software uniformity and re-use, as well as documentation consistency.
frameworklowRISC will concern itself primarily with compliant IP, but will provide a fullchip framework suitable at least for FPGA implementation, and prepared to be the foundation for a full silicon implementation. This could roughly be translated to Top Level Netlist.
interruptsNon-security critical signals from peripheral devices to the local host processor within the framework SOC.
MIOMultiplexable IO; a pad at the top chip level which can be connected to one of the peripherals' MIO-ready inputs or outputs.
peripheralAny comportable IP that is part of the library, outside of the local host processor.

Non-Technical Comportability Requirements

All comportable IP must adhere to a few requirements, briefly discussed here.

License and copyright

All files should include a comment with a copyright message. This is normally the “lowRISC contributors”. The style is to not include a year in the notice. Files adapted from other sources should retain any copyright messages and include details of the upstream location.

The Apache License, Version 2.0 is the default for all files in the repository. Use of other licenses must be noted (and care is needed to ensure compatibility with the rest of the code). All files should include a comment line with the SPDX-License-Identifier: tag and the Identifier from the License List. An additional “Licensed under” line may be used to give a more human readable version. If the file is not covered by a SPDX license then the “Licensed under” line is required (note that such files are unlikely to be permitted in the main open source repository).

All files that use the default copyright and license should therefore include the following header (change the comment character as appropriate):

// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

The project has adopted Hjson for json files, this extends json to allow comments. Thus the Hjson files can include the header above. If pure json must be used for some reason, the “SPDX-License-Identifier:” can be added as the first key after the opening “{”. Tools developed by the project should accept and ignore this key.

Coding Style

All IP must follow the lowRISC Verilog Coding Style Guide. This style guide sets the definition of agreed-upon SystemVerilog style, requirements and preferences. See that document for details. It is the goal of lowRISC to create technical collateral to inform when an IP does not conform, as well as assist in the formating of Verilog to this style. The methods and details for this collateral have not been agreed upon yet.

Documentation

All lowRISC IP must conform to a common specification and documentation format. lowRISC will release a template for IP specifications in a separate document for reference. It is notable that register tooling will auto-create documentation for register definitions, address maps, hardware interfaces, etc. The hardware interfaces of this process will be discussed later in this document.

Comportable Peripheral Definition

All comportable IP peripherals must adhere to a minimum set of functionality in order to be compliant with the framework that is going to be set around it. This includes several mandatory features as well as several optional ones. It is notable that the eventual framework will contain designs that are neither the local host processor nor peripherals - for example the power management unit, clock generators. These will be handled as special case designs with their own specifications. Similarly the memory domain will be handled separately and in its own specification.

Examples of peripherals that are expected to be in this category include ones with primary inputs and outputs (SPI, I2C, etc); offload and specialty units (crypto, TRNG, key manager); timers; analog designs (temperature sensor); as well as bus hosts1 (DMA).

1lowRISC is avoiding the fraught terms master/slave and defaulting to host/device where applicable.

Feature List

All comportable designs must specify and conform to a list of mandatory features, and can optionally specify and conform to a list of optional features. These are briefly summarized in the table below, and will be covered individually in the sections that follow. For most of these, the definition of the feature will be in the form of a configuration file. This file is specified later within this document.

FeatureMand/OptDescription
ClockingmandatoryEach peripheral must specify what its primary functional clock is, and any other clocks needed. The primary clock is the one driving the bus the peripheral is receiving. The clocking section lists the available clocks. Other clocks can be designated as needed.
Bus DevicemandatoryAll peripherals are assumed to have registers, and are thus required to be a device on the chip bus. More details in the bus definition section.
Bus HostoptionalPeripherals can act as a bus host on some occasion, though for fullchip simplicity the preferred model is for the processor to be primary host. An example would be a DMA unit. More details in the bus definition section.
Available IOoptionalPeripherals can optionally make connections to dedicated or multiplexed IO pins and the chip peripheral needs to indicate its module inputs and outputs that are available for this purpose. Details in the peripheral IO section below.
RegistersmandatoryEach peripheral must define its collection of registers in the specified register format. The registers are automatically generated in the form of hardware, software, and documentation collateral. Details in the register section.
InterruptsoptionalPeripherals have the option of generating signals that can be used to interrupt the primary processor. These will be designated as a list of signals, and each will result in a single wire or bused output that will be sent to the processor to be gathered as part of its interrupt vector input. Details in the interrupt and alert section.
AlertsoptionalPeripherals have the option of generating signals that indicate a potential security threat. These will be designated as a list of signals, and each will result in a complementary signal pair that will be sent to an alert handling module. Details in the interrupt and alert section.
(more)More will come later, including special handling for testability, power management, device entropy, etc.

Typical Peripheral Block Diagram

Figure 1: Typical peripheral communication channels within full chip framework.

In this diagram the auto-generated register submodule is shown within the peripheral IP, communicating with the rest of the chip framework using the TL-UL (see below) bus protocol. This register block communicates with the rest of the peripheral logic to manage configuration and status communication with software. Also shown is the mandatory clock, and the optional bus (TL-UL) host, interrupts, alerts, and chip IO.

Additionally an optional input devmode is shown which represents an indication to the peripheral what mode the SOC is operating in. For now this includes only two modes: development (devmode = 1) and production (devmode = 0). This is the beginning of a security feature that will convey the full life cycle mode status to the peripheral. In its current form, only the distinction of development mode vs. production mode is required in order to determine how to handle software errors (see the Register Tooling documentation for details). The full definition of life cycle modes will come upon further refinement of the security properties of the SOC.

Peripheral Feature Details

Configuration File

Each peripheral will contain a configuration file that describes the peripheral features that are mandatory and optional in the above comportability feature list. The configuration file format is given below.

Clocking

Each peripheral must define at least one clock: the primary clock. This is defined as clock_primary in the configuration file, and must be equal to one of the known clock names. This primary clock is defined as the one used to clock the bus device, indicating to the top level if asynchronous handling of the bus interface is needed.

Optionally the peripheral can request other clocks that it needs for internal use. These would create asynchronous clock domains within the IP that are handled by the design. They are defined under other_clock_list in the configuration file.

Reset

At this time, no additional information is required to indicate the reset scheme for the peripheral IP. It is assumed that each clock will come with its related reset pins targeted for that clock domain. Resets within the design are asynchronous active low (see below). Special care will be required for security sensitive storage elements. Further instructions on the handling of these storage elements will come at a later date.

Details and rationale for asynchronous active low reset strategy

Resets within the design are asynchronous active low, where the assertion of the reset is asynchronous to any clock, but deassertion is synchronized to the clock of the associated storage element. The selection of asynchronous active low (as opposed to say synchronous active high) was made based upon a survey of existing design IP, comfort level of project team members, and through security analysis. The conclusion roughly was the following:

  1. Security storage elements might “leak” sensitive state content, and should be handled with care regardless of reset methodology. By “care” an example would be to reset their value synchronously at a time after chip-wide reset, to a value that is randomized so that the hamming distance between the register value and all zeros cannot produce information available to an attacker.
  2. For control path and other storage elements, the selection of asynchronous active low vs. synchronous active high is often a “religious” topic, with both presenting pros and cons.
  3. Asynchronous active low incurs slightly more area and requires more hand-holding, but is more common.
  4. Synchronous active high is slightly more efficient, but requires the existence of a clock edge to take effect.

Based upon this and the fact that much of the team history was with asynchronous active low reset, we chose that methodology with added requirements that special care be applied for security state, the details of which will come at a later date.

Bus Device

All peripheral devices will use TileLink-UL (TileLink-Uncached-Lite, aka TL-UL) as their interface to the framework. As of this writing, there are no options, but the configuration file designates the protocol with the bus_device keyword. The only acceptable value at this time is tlul.

The address map for peripheral devices is not determined by the peripheral itself, or its configuration file. The working assumption is that a higher level full-chip configuration file will distribute address ranges to all of the included bus peripheral devices.

The TileLink-UL protocol and its usage within lowRISC devices is given in the TileLink-UL Bus Specification.

Bus Host

Peripherals have the option of declaring themselves as bus hosts. This is done in the configuration file with bus_host keyword. This is optional, or the configuration can indicate none (via an empty string), or else the bus host protocol (only tlul allowed at this time). All bus hosts must use the same clock as the defined primary host clock.

Each bus host will be provided a 4-bit host ID to distinguish hosts within the system. This will be done by the framework in order to ensure uniqueness. The use of the ID within the bus fabric will be discussed in the bus specification (TBD).

Available IO

Each peripheral has the option of designating signals (inputs, outputs, or inouts) available to be used for chip IO. The framework will determine for each signal if it goes directly to dedicated chip pin or is multiplexed with signal(s) from other peripherals before reaching a pin.

Designation of available IO is given with the configuration file entries of available_input_list, available_output_list, and available_inout_list. These can be skipped, or contain an empty list [], or a comma-separated list of signal names. Items on the input list of the form name will incur a module input of the form cio_name_i. Items on the output list of the form name will incur a module output of the form cio_name_o as well as an output enable cio_name_en_o. Items on the inout list of the form name will incur all three.

Multiplexing Feature and Pad Control

Eventually there will be a pin multiplexing unit (pinmux) which provides flexible assignment to/from peripheral IO and chip pin IO. Comportable peripherals do not designate whether their available IO are hardwired to chip IO, or available for multiplexing. That is done at the top level with a peripheral configuration file (not yet defined).

In addition, full pad control is not done by the peripheral logic, but will be done by the pinmux or associated pad control module (to be defined). This module will provide software configuration control over pad drive strength, pin mapping, pad type (push/pull, open drain, etc). This will be specified in the future.

Interrupts

Each peripheral has the option of designating output signals as interrupts destined for the local host processor. These are non-security-critical signals sent to the processor for it to handle with its interrupt service routines. The peripheral lists its collection of interrupts with the interrupt_list attribute in the configuration file. Each item of the form name in the interrupt list will expect a module output named intr_name_o.

See the section on Interrupt Handling below, which defines details on register, hardware, and software uniformity for interrupts within the project.

Alerts

Each peripheral has the option of designating output signals as security critical alerts destined for the hardware alert handler module. These are differential signals (to avoid single point of failure) sent to the alert handler for it to send to the processor for first-line defense handling, or hardware security response if the processor does not act. The peripheral lists its collection of alerts with the alert_list attribute in the configuration file. Each item of the form name in the alert list will expect two module outputs of the form alert_name_po, and alert_name_no.

See the section on Alert Handling below, which defines details on register, hardware, and software uniformity for alerts within the project.

Register Handling

The definition and handling of registers is a topic all on its own, and is specified in its own document. All lowRISC peripheral designs must conform to this register specification.

Configuration description Hjson

The description of the IP block and its registers is done in an Hjson file that is specified in the Register Tool document. All lowRISC peripheral designs must conform to this configuration and register specification.

A description of Hjson (a variant of json) and the recommended style is in the Hjson Usage and Style Guide.

Configuration information in the file

The configuration part of the file has the following elements, with a comment as to if required or optional. In this example, the IP name is uart, though the other configuration fields are contrived and not in-line with the expected functionality of a UART but are shown for edification.

  {
    name: "uart",
    clock_primary: "clk_fixed",      // optional; default "clk"
    other_clock_list: [ "clk", "clk_lowpower" ], // optional; default []
    bus_device: "tlul",
    bus_host: "",                    // optional; default undefined
    available_input_list: [          // optional; default []
      { name: "rx", desc: "Receive bit" }
    ],
    available_output_list: [         // optional; default []
      { name: "tx", desc: "Transmit bit" }
    ],
    available_inout_list: [],        // optional; default []
    interrupt_list: [                // optional; default []
      { name: "tx_watermark",  desc: "raised if the transmit FIFO..."}
      { name: "rx_watermark",  desc: "raised if the receive FIFO..."}
      { name: "tx_overflow",   desc: "raised if the transmit FIFO..."}
      { name: "rx_overflow",   desc: "raised if the receive FIFO..."}
      { name: "rx_frame_err",  desc: "raised if a framing error..."}
      { name: "rx_break_err",  desc: "raised if break condition..."}
      { name: "rx_timeout",    desc: "raised if the receiver..."}
      { name: "rx_parity_err", desc: "raised if the receiver..."}
    ],
    alert_list: [                    // optional; default []
      { name: "uart_breach", desc: "Someone has attacked the ..."}
      { name: "uart_frozen", desc: "The UART lines are frozen..." }
    ],
    regwidth: "32", // standard register width
    register: [
      // Register information...
    ]
  }

Documentation Output

The following shows the expected documentation format for this example.

Primary Clock: clk_fixed

Other clocks: clk, clk_lowpower

Bus Device Interface: tlul

Bus Host Interface: none

Peripheral Pins available for chip-level IO:

Pin namedirectionDescription
txoutputTransmit bit
rxinputReceive bit

Interrupts:

Intr NameDescription
tx_watermarkRaised if the transmit FIFO is past the high water mark
rx_watermarkRaised if the receive FIFO is past the high water mark
tx_overflowRaised if the transmit FIFO has overflowed
rx_overflowRaised if the receive FIFO has overflowed
rx_frame_errRaised if a framing error has been detected on receive
rx_break_errRaised if a break condition is detected on receive
rx_timeoutRaised if the receiver has not received any characters programmable time period
rx_parity_errRaised if the receiver has detected a parity error

Security alerts:

Alert nameDescription
uart_breachSomeone has attacked the UART module
uart_frozenThe UART lines are frozen and might be under attack

Interrupt Handling

Interrupts are critical and common enough to attempt to standardize across the project. Where possible (exceptions for inherited IP that is too tricky to convert) all interrupts will have common naming, hardware interface, and software interface. These are described in this section.

Interrupts are latched indications of defined peripheral events that have occurred and not yet been addressed by the local processor. All interrupts are sent to the processor as active-high level (as opposed to edge) interrupts. Events themselves can be edge or level, active high or low, as defined by the associated peripheral. For instance, the GPIO module might detect the rising or falling edge of one its input bits as an interrupt event.

The latching of the event is done by the auto-generated register file as described below. The clearing of the event is done by a processor write when the handling of the event is completed. The waveform below shows the timing of the event occurrence, its latched value, and the clearing by the processor. More details follow.

{
  signal: [
    { name: 'Clock',             wave: 'p.............' },
    { name: 'event',             wave: '0..10.........' },
    { name: 'INTR_ENABLE',       wave: '1.............' },
    { name: 'INTR_STATE',        wave: '0...1....0....' },
    { name: 'intr_o',            wave: '0...1....0....' },
    { name: 'SW write to clear', wave: '0.......10....' },
  ],
  head: {
    text: 'Interrupt Latching and Clearing',
  },
  foot: {
    text: 'event signaled at cycle 3, state bit cleared in cycle 8',
    tock: 0
  },
}

Interrupts per module

A peripheral will generate a separate interrupt for each event and send them all as bundle to the local processor's interrupt module. “Disambiguation”, or the determining of which interrupt has woken the processor, is done at the processor in its handler (to be specified eventually in the core processor specification). This is as distinct from a model in which each peripheral would send only one interrupt, and the processor would disambiguate by querying the peripheral to figure out which interrupt was triggered.

Defining Interrupts

The configuration file defined above specifies all that needs to be known about the interrupts in the standard case. The following sections will specify what comes out of various tools based upon the simple list defined in the above example.

Register Creation

For every peripheral, by default, three registers are automatically created to manage each of the interrupts for that peripheral (as defined in the interrupt_list portion of the HJSON file). This can be overridden within the reggen tool by specifying no_auto_intr_regs = true. Every interrupt has one field bit for each of three registers. (It is an error condition if there are more than 32 interrupts per peripheral.) The three registers are the INTR_STATE register, the INTR_ENABLE register, and the INTR_TEST register. They are placed at the top of the peripheral's address map in that order automatically by the reggen tool.

The INTR_ENABLE register is readable and writeable by the CPU (rw), with one bit per interrupt which, when true, enables the interrupt of the module to be reported to the output to the processor. The INTR_STATE register is readable by the CPU and each bit may be written with 1 to clear it (rw1c), so that a read of the register indicates the current state of all latched interrupts, and a write of 1 to any field clears the state of the corresponding interrupt. INTR_TEST is a write-only (wo) register that allows software to test the reporting of the interrupt, simulating a trigger of the original event, the setting of the INTR_STATE register, and the raised level of the interrupt output to the processor (modulo the effect of INTR_ENABLE). No modifications to other portions of the hardware (eg. clearing of FIFO pointers) occurs. See the next section for the hardware implementation.

The contents of the INTR_STATE register do not take into consideration the enable value, but rather show the raw state of all latched hardware interrupt events. The output interrupt to the processor ANDs the interrupt state with the interrupt enable register before sending to the processor for consideration.

Interrupt Hardware Implementation

All interrupts as sent to the processor are active-high level interrupts of equal severity3. Taking an interrupt foo as an example, the block diagram below shows the hardware implementation. The assumption is that there is an internal signal (call it event_foo) that indicates the detection of the event that is to trigger the interrupt. The block diagram shows the interaction between that event, the three defining software-facing registers, and the output interrupt intr_foo_o.

3 Higher priority interrupts in the form of a Non-Maskable Interrupt (NMI) are expected to be overlaid in the near future.

Example Interrupt HW

Figure 2: Example interrupt foo with its three registers and associated HW

In this figure the event is shown coming in from another part of the peripheral hardware. The assumption is this event foo is one of multiple interrupt events in the design. Within the register file, the event triggers the setting of the associated bit in the INTR_STATE register to 1. Additionally, a write of 1 of the associated foo bit of the INTR_TEST register can set the corresponding INTR_STATE bit. The output of the INTR_STATE register becomes the outgoing interrupt to the processor after masking (ANDing) with the value of INTR_ENABLE.

Note that the handling of the ro/rw1c functionality of the INTR_STATE register allows software to control the clearing of the INTR_STATE content. A write of 1 to the corresponding bit of INTR_STATE clears the latched value, but if the event itself is still active, the INTR_STATE register will return to true. The hardware does not have the ability to clear the latched interrupt state, only software does.

Interrupts sent to the processor will be handled by its interrupt controller. Within that logic there may be another level of control for enabling, prioritizing, and enumeration. Specification of this control will be defined in the interrupt handling section of the processor specification.

Alert Handling

Alerts are another critical and common implementation to standardize for all peripherals. Unlike interrupts, there is no software component to alerts at the peripheral, though there is at the hardware alert handler. (See that specification when available, hints here.) But the handling of alerts at the hardware level is described here.

Alerts per module

Alerts are sent as a bundled output from a peripheral to the hardware alert handler. This will have its own implementation document when available, but is briefly described here. Each peripheral can send zero or more alerts, where each is a distinguishable security threat. Each alert originates in some internal event, and must be specially handled within the peripheral, and then within the alert handler module.

Alert Hardware Implementation

Internal events are sent active-high to a piece of ip within the peripheral called the alert_sender. One alert_sender must be instantiated per distinct alert event type. It is up to the peripheral owner to determine what are distinct alert events; multiple ones can be bundled depending upon the distinction required within the module (i.e. high priority threat vs. low level threat). The alert_sender converts the event into a differentially encoded signal pair to be routed to the hardware alert handler, as shown in the figure below.

Example Alert HW

Figure 3: Example alert name with its differentially encoded signals sent to hardware alert handler.

In this figure the event is shown coming in from the other part of the design. The alert_sender converts the event into the differentially encoded signals sent to the handler. The differential encoding contains more than just the event information. It also conveys a health check in the form of a heartbeat signal when there are no alert events. In this way the alert handler can be sure that all alert senders are alive and well. In addition, the differential signaling ensures no single point of failure. If the signals are ever not differential, that is itself another potential system failure. Therefore the alert receiver can detect three failures: the alert event itself; the lack of an alert heartbeat indicating possible detection silencing; integrity failure on the signaling indicating potential signal corruption. These three are shown as outputs of the alert_receiver module within the alert handler.

All alerts must be sent using the same clock throughout the chip. At this time the chip-level clocks are not disclosed, but eventually a clock will be designated as the chip-wide security clock. This clock must be used for signaling all alert events. If the native event is in a different domain, it must be synchronized before being sent to the alert_sender ip instantiation.

The implementation of the signaling is not finalized at this time, but the waveform below gives an example of the relationships.

{
  signal: [
    { name: 'Clock',                 wave: 'p....................' },
    { name: 'event_name',            wave: '0|...|.1.........0...' },
    { },
    { name: 'alert_name_po',         wave: '0|.10|..1.0.1.0.1.0..' },
    { name: 'alert_name_no',         wave: '1|.01|..0.1.0.1.0.1..' },
    { },
    { name: 'alert_name_triggered',  wave: '0|...|...1..........0' },
  ],
  head: {
    text: 'Alert Signaling Transmission',
  },
  foot: {
    text: 'heartbeat signaled at cycle 3, alert event signaled starting at cycle 8',
    tock: 0
  },
}

Figure 4: Alert event signaling and timing