Bare-Metal Starter Project
← Back to book page

RISC-V Bare-Metal I/O Starter

Supplementary material for Computer Architecture: Design and Analysis (2026 edition) · Krerk Piromsopa, Ph.D. · Chulalongkorn University

This starter project provides the scaffolding for the I/O Activity (Appendix D.4). You will implement a UART driver for a bare-metal RISC-V system — first using a polling approach, then converting it to an interrupt-driven design — and measure the CPU utilization difference between them.

The target is the virt machine in QEMU, which includes a NS16550A-compatible UART and a PLIC (Platform-Level Interrupt Controller). No physical hardware is required.

Downloads

⚙️ startup.S Reset vector, BSS init, trap entry Download
📜 link.ld Linker script (DRAM @ 0x80000000) Download
📋 platform.h UART, PLIC & CLINT register map Download
📄 uart.h Driver interface Download
✏️ uart.c Driver skeleton (fill in TODOs) Download
✏️ main.c Entry point skeleton (fill in TODOs) Download
🔨 Makefile Build & run with make / make run Download
📖 README.md Full setup & exercise guide Download

Prerequisites

1 — RISC-V toolchain

OSCommand
Debian / Ubuntu sudo apt install gcc-riscv64-unknown-elf (use with 32-bit flags below)
macOS brew install riscv-gnu-toolchain
Windows Use WSL2 (Ubuntu) and follow the Debian instructions
Note: If you only have riscv64-unknown-elf-gcc, edit the Makefile and set CROSS = riscv64-unknown-elf. The flags -march=rv32ima -mabi=ilp32 already force 32-bit output, so the 64-bit toolchain works fine.

2 — QEMU

# Debian / Ubuntu sudo apt install qemu-system-misc # macOS brew install qemu # Verify qemu-system-riscv32 --version

Quick Start

# Download all files into a new directory, then: make # compile → app.elf make run # launch QEMU (type characters; Ctrl-A X to quit) make dump # disassemble with interleaved C source

Expected output once Exercise 1 is complete and QEMU is running:

$ make run Launching QEMU (press Ctrl-A then X to exit) === Polling echo ready (press keys, Ctrl-A X to exit QEMU) === H ← you type 'H', it echoes back

Platform Memory Map (QEMU virt)

DeviceBase AddressNotes
DRAM 0x8000_0000128 MB default; ELF loaded here
UART0 (NS16550A)0x1000_00008-bit registers; byte-wide access
PLIC 0x0C00_0000UART0 = source 10
CLINT 0x0200_0000mtime, mtimecmp, msip

UART Register Summary

OffsetNameKey Bits
+0RBR (read) / THR (write)bits 7:0 = received / transmit data
+1IERbit 0 = ERBFI (RX interrupt enable)
+3LCRbit 7 = DLAB; 0x03 = 8N1
+5LSRbit 0 = DR (RX data ready); bit 5 = THRE (TX ready)

Exercise Overview

Exercise 1 — Polling Driver

Implement uart_init(), uart_putc(), and uart_getc() in uart.c. The driver loops on the LSR status bits. Measure the fraction of CPU time spent busy-waiting vs.\ transferring data.

Exercise 2 — Interrupt-Driven Driver

Enable the UART RX interrupt via the PLIC, write a trap handler, and implement a ring-buffer ISR. The main loop computes Fibonacci numbers while the ISR enqueues received characters. Compare CPU cycles per character with Exercise 1.

Exercise 3 — Analysis

Fill in the comparison table in the textbook: CPU cycles per character, average CPU utilization at 5 char/s and 100 kchar/s, worst-case RX latency, and code size.

Exercise 4 (optional) — DMA

If your platform includes a DMA controller, modify the TX path to use bulk DMA transfers and repeat the comparison. Determine at what input rate DMA becomes more efficient than the interrupt-driven approach.

File Structure

RISCV-IO/ ├── Makefile ← build & run (make / make run / make dump) ├── README.md ← setup guide and exercise walkthrough ├── link.ld ← linker script (DRAM @ 0x80000000) ├── startup.S ← _start, BSS init, trap_entry ├── platform.h ← register addresses and CSR helpers ├── uart.h ← driver interface (already complete) ├── uart.c ← *** fill in the TODOs *** └── main.c ← *** fill in the TODOs ***
Tip: Start by reading platform.h to understand the register layout, then work through uart.c top-to-bottom following the TODO comments. Each TODO includes a suggested implementation in comments so you can check your reasoning.