Hardware/Software Interfaces

Rodolfo Azevedo

Institute of Computing, University of Campinas (UNICAMP), Brazil

rodolfo.azevedo@unicamp.br

http://www.ic.unicamp.br/~rodolfo/mo801

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Goal of this class

Module 3, Class 1: why interfaces matter, communication vs. computation, and the memory-mapped I/O model.

Project 1 gave you a processor that runs programs. Now we add peripherals — and the question is: how does software reach into hardware? This module is about that boundary: the choices you make here determine whether Project 3's accelerator will be fast or slow.

At the end of this class, you should be able to:

  • Compare memory-mapped registers, custom instructions, and DMA/FIFO interface styles for hardware accelerators.
  • Explain the memory-mapped I/O model and how a RV32I load/store instruction reaches a peripheral.
  • Design a register map for a hardware accelerator, including control, status, and data registers.
  • Write a C software driver using volatile pointer-based memory-mapped register access.
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

The interface problem

Your RV32I core can execute instructions. The keyword-spotting model needs to:

  1. Read 1,960 int8 MFCC values from somewhere.
  2. Run matrix multiplications.
  3. Write the result (classification: yes/no/unknown/silence) somewhere.

"Somewhere" requires a hardware/software interface. The interface design choices are:

  • Where do shared data live? (registers? shared memory? FIFO?)
  • How does software initiate computation? (write a register? execute a custom instruction?)
  • How does software know when hardware is done? (polling? interrupt? DMA completion?)

Each choice has different latency, throughput, and implementation complexity trade-offs. We will explore all of them this module.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Communication-constrained vs. computation-constrained

From Schaumont's framework: a system is either bottlenecked by compute (not enough arithmetic units) or by communication (not enough bandwidth to move data to/from those units).

Time = max(T_compute, T_communicate)

For KWS dot products:

  • T_compute: 1,960 × 9 MACs per layer × ~4 cycles/MAC (with M ext.) ≈ 70,000 cycles
  • T_communicate: 1,960 bytes from PSRAM via SPI at 27 MHz ≈ millions of cycles

The communication cost dwarfs the compute cost. Adding more MAC units does not help if the bottleneck is getting data to them.

This is the central insight driving Project 3's design space — we will revisit it with the roofline model in Module 5.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Memory-mapped I/O — the universal interface

The simplest interface: treat hardware registers as memory addresses. Software reads and writes them with normal load/store instructions.

The CPU does not need to know what is at each address — it just issues lw/sw. The bus routes the transaction to the right peripheral.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

The memory map is a design decision

Constraints to keep in mind:

  • Alignment: 32-bit registers should be 4-byte aligned (address divisible by 4). Most bus protocols enforce this.
  • Address decoding granularity: how many bits do you decode? Decoding only the top bits gives each peripheral a large address range (simpler hardware, some waste). Decoding all bits is precise but more complex.
  • Reserving space for future peripherals: Project 2 adds UART+timer+GPIO; Project 3 adds the accelerator. Design the map so both fit without conflicting.

A simple rule: assign each peripheral a 256-byte (0x100) aligned block. With 4-byte registers, that gives each peripheral up to 64 registers — far more than any of ours will need.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

The on-chip bus: signal contract

A minimal bus for a single-master (CPU) system needs:

Signal Direction Width Meaning
addr M→S 32 Target address
wdata M→S 32 Write data
rdata S→M 32 Read data
we M→S 1 Write enable
re M→S 1 Read enable
sel M→S 4 Byte enables
valid M→S 1 Transaction active
ready S→M 1 Peripheral ready (handshake)

The valid/ready handshake: the master asserts valid and holds the transaction until the slave asserts ready. Peripherals that respond in one cycle can tie ready to 1. Slower peripherals (e.g., PSRAM access) deassert ready for the required number of cycles.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Bus timing: one-cycle vs. multi-cycle transactions

The CPU stalls (holds PC, does not fetch next instruction) until ready = 1. This is how your multicycle datapath already works for memory accesses — the bus extends that concept to all peripherals.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Address decoding with casez

The bus decoder routes transactions to the correct peripheral:

module bus_decoder (
    input  logic [31:0] addr,
    output logic        sel_imem, sel_dmem,
                        sel_uart, sel_timer,
                        sel_gpio, sel_accel
);
    always_comb begin
        {sel_imem, sel_dmem, sel_uart,
         sel_timer, sel_gpio, sel_accel} = 6'b0;
        casez (addr[31:8])
            24'h000000: sel_imem  = 1;   // 0x0000_00xx
            24'h000100: sel_dmem  = 1;   // 0x0001_00xx (×16 blocks)
            24'h000200: sel_uart  = 1;   // 0x0002_00xx
            24'h000201: sel_timer = 1;   // 0x0002_01xx
            24'h000202: sel_gpio  = 1;   // 0x0002_02xx
            24'h000203: sel_accel = 1;   // 0x0002_03xx
            default: ;
        endcase
    end
endmodule

Each sel_* signal enables one peripheral's ready and rdata to connect to the bus.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Interface design alternatives — a preview

We will explore all four styles this module and in Project 3:

Style How software activates hardware Latency Complexity
Memory-mapped registers sw to control reg, poll status Medium Low
ISA extension / custom instr. Execute MUL rd, rs1, rs2 or CMAC rs1, rs2 Low (1 instr) Medium (ISA change)
DMA / shared memory Write descriptor, hardware fetches Low (async) High
Streaming (FIFO) Push input, pop output Very low Medium

Project 2 builds the memory-mapped register infrastructure. Project 3 uses it to attach the accelerator — and you will measure whether it is fast enough or whether a different interface style would help.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Polling vs. interrupts — how software waits for hardware

Two strategies for software to know when a peripheral is ready:

Polling — software loops reading a status register:

// Wait until UART TX is ready
while (!(UART_STATUS & UART_TX_READY));
UART_DATA = byte;

Interrupt-driven — hardware signals the CPU when ready:

// ISR (Interrupt Service Routine) — called automatically
void uart_tx_isr(void) {
    if (tx_pending) UART_DATA = *tx_ptr++;
}
// Main thread: set up and forget
uart_enable_tx_irq();
Polling Interrupt
CPU utilization 100% (busy-wait) Low (CPU free while waiting)
Latency Very low One interrupt-entry overhead
Implementation Simple — no ISR needed Requires interrupt controller
When to use Short waits, time-critical Long waits, multi-task systems

Rule of thumb: poll if you expect to wait < 10 cycles; use interrupts if you expect to wait > 100 cycles. For KWS inference (millions of cycles), interrupts are the right answer — but we use polling in Project 2 for simplicity.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Designing a register map — a worked example

A register map is the hardware/software contract for a peripheral. Example: a simple hardware timer:

Offset Register Access Fields
0x00 CTRL R/W [0] EN: enable counting; [1] RELOAD: auto-reload on overflow; [2] IRQ_EN: interrupt on overflow
0x04 RELOAD R/W [31:0] reload value (loaded into COUNT on overflow when RELOAD=1)
0x08 COUNT R only [31:0] current counter value
0x0C STATUS R/W1C [0] OVF: overflow flag; write 1 to clear

Design principles applied here:

  • Control separate from status: CTRL (write) and STATUS (read/clear) — avoids a read-modify-write hazard on status bits.
  • R/W1C (write-1-to-clear): software clears the overflow flag by writing a 1 to bit 0 of STATUS — writing 0 has no effect. This is an atomic clear without needing a separate "clear" register.
  • 4-byte aligned, sequential offsets: each register is at a 4-byte boundary — hardware decodes only addr[3:2]; addr[1:0] are always 0 (word-aligned access).

The C driver then writes:

#define TIMER_BASE  0x00020100
#define TIMER_CTRL   (*(volatile uint32_t *)(TIMER_BASE + 0x00))
#define TIMER_RELOAD (*(volatile uint32_t *)(TIMER_BASE + 0x04))
#define TIMER_COUNT  (*(volatile uint32_t *)(TIMER_BASE + 0x08))
#define TIMER_STATUS (*(volatile uint32_t *)(TIMER_BASE + 0x0C))
MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Bus handshake trace — a transaction with stall

The valid/ready handshake in action for a slow peripheral (e.g., PSRAM access taking 3 cycles):

Cycle:   1        2        3        4        5
addr:    [  0x4000_0010 (PSRAM addr)  ]  [  next  ]
valid:   ─────────────────────────────────  ___
ready:   ___      ___      ___      ─────────────
rdata:   X        X        X        [  data  ]  X
  • CPU asserts valid in cycle 1 and holds addr, we, wdata until ready goes high.
  • The peripheral (PSRAM controller) deasserts ready for cycles 1–3 while it completes the access.
  • In cycle 4, ready goes high — the CPU latches rdata and releases valid.
  • From the CPU datapath perspective: this is exactly the "stall" signal that holds PC and IR during a multi-cycle memory access (M02A02's MEM_READ state) — the bus extends this concept to all peripherals.

Any peripheral that responds in one cycle can tie ready = 1 permanently — the handshake collapses to zero overhead. Only slow peripherals need to deassert ready.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

In-class exercise — design the accelerator register map

Your KWS accelerator (Project 3) needs a memory-mapped interface. Design the register map:

Requirements:

  • Software must be able to start an inference.
  • Software must be able to poll for completion (or receive an interrupt).
  • Software must be able to read the result (one of: YES / NO / UNKNOWN / SILENCE — 2 bits).
  • The accelerator reads its input MFCC data from a buffer in DMEM — software must tell it where the buffer is.
  • Stretch: software must be able to read the inference latency (cycle count).

Design your register map (15 min):

Offset Register name Access Fields
0x00
0x04
0x08
0x0C

Questions:

  1. Which register triggers the start — and is it a dedicated bit in CTRL or a separate "command" register?
  2. How does software distinguish "not started yet", "running", and "done"?
  3. If you add interrupt support, which register holds the interrupt enable bit?

Expected: CTRL (start, irq_en), STATUS (done, result[1:0]), INPUT_ADDR (pointer to MFCC buffer), LATENCY (read-only cycle count). Many valid designs exist — justify your choices.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0

Next class

ISA Extensions, Custom Instructions & the On-Chip Bus: how to add Zmmul and CMAC to the RV32I decoder and datapath, and the full SystemVerilog implementation of the minimal bus protocol.

MO801/MC972 · Topics in Computer Architecture and Hardware · Rodolfo Azevedo · CC BY-SA 4.0