Firmware Development

Firmware Development

Guide for developing RISC-V firmware for the SCuM-V chip.

RISC-V Toolchain Setup

Windows Setup

Install MSYS2

Download from https://www.msys2.org/ and follow installation instructions

Install RISC-V Toolchain

Download xpack distribution: RISC-V GCC xPack Extract to desired location (e.g., C:\riscv\)

Configure MSYS2 Environment

export PATH=/c/riscv/xpack-riscv-none-elf-gcc-13.2.0-2/bin:$PATH

Linux Setup

Follow the comprehensive setup guide from the Baremetal-IDE documentation:

Chipyard RISC-V Toolchain Setup

This guide covers:

  • Ubuntu/Debian package installation
  • macOS Homebrew setup
  • Building from source
  • Environment configuration

Mac Setup

Credit to: https://ucb-bar.gitbook.io/chipyard/quickstart/setting-up-risc-v-toolchain/setting-up-risc-v-toolchain-mac

Install dependencies with brew

First, we need to install the following dependencies.

brew install python3 gawk gnu-sed gmp mpfr libmpc isl zlib expat texinfo flock

Clone the RISC-V GNU Toolchain repo.

cd ~/Downloads
git clone https://github.com/riscv-collab/riscv-gnu-toolchain.git
cd ~/Downloads/riscv-gnu-toolchain/

Run configuration.

The prefix is where we want to install the toolchain. Here, we will be installing under the riscv64-unknown-toolchain directory.

⚠️
  1. NOTE: MAKE SURE TO ADD --with-languages="c,c++,fortran", this differs from the Chipyard documentation linked above (it is outdated).
  2. You will need to change /home/tk/Documents/RISCV/riscv64-unknown-toolchain/ to your own preferred installation directory.
./configure --prefix=/home/tk/Documents/RISCV/riscv64-unknown-toolchain/ --with-multilib-generator="rv32i-ilp32--;rv32im-ilp32--;rv32iac-ilp32--;rv32imac-ilp32--;rv32imafc-ilp32f--;rv64imac-lp64--;rv64imafdc-lp64d--" --with-languages="c,c++,fortran"

Build the toolchain

make -j8

Firmware Architecture

Project Structure

    • Makefile

Build System

The firmware uses a Makefile-based build system optimized for RISC-V cross-compilation.

Basic Build Commands:

cd sw/scum_firmware
make                    # Build default target (BRINGUP mode)
make clean             # Clean build artifacts  
make objdump           # Generate disassembly

Build Modes

The build system supports two different modes optimized for different environments:

Build ModeUsageDescription
BRINGUPBUILD_MODE=BRINGUP (default)Post-silicon hardware testing
SIMBUILD_MODE=SIMRTL simulation environment

Mode-Specific Build Commands:

# Explicit mode specification
make BUILD_MODE=SIM
make BUILD_MODE=BRINGUP
 
# Convenience targets
make simple-sim         # Build simple.c for simulation
make simple-bringup     # Build simple.c for hardware

Build Configuration (build_config.h)

Each build mode uses different system parameters defined in core/inc/build_config.h:

SIM Mode Configuration:

  • Clock frequency: 200 MHz (fast simulation)
  • UART baud rate: 921600 (high-speed simulation)
  • UART stop bits: 2 (simulation robustness)

BRINGUP Mode Configuration:

  • Clock frequency: 1 MHz (conservative hardware startup)
  • UART baud rate: 115200 (standard hardware rate)
  • UART stop bits: 1 (standard hardware setting)

Clock Frequency Tuning: The SYS_CLK_FREQ setting in build_config.h must match your hardware’s actual clock configuration. Update this value when changing PLL or oscillator settings.

Board Support Package (BSP)

Core BSP Modules

ModuleDescription
scum_halHardware abstraction layer
scum_hal_clintCore Local Interrupt Controller
scum_hal_coreRISC-V core utilities
scum_hal_gpioGeneral purpose I/O
scum_hal_plicPlatform-Level Interrupt Controller
scum_hal_rccReset and clock control
scum_hal_uartUART communication

Peripheral Drivers

DriverHeaderDescription
AFEafe.hAnalog front-end control
Basebandbaseband.hBaseband processing
Sensor ADCsensor_adc.hHigh-precision ADC interface
RTC Timerrtc_timer.hReal-time clock and timers
SCuM-V Tuningscumvtuning.hChip tuning parameters

All BSP headers include comprehensive register definitions and utility macros for efficient hardware interaction.

Application Development

Available Test Applications

The firmware includes several test and example applications:

ApplicationHeaderPurpose
AFE Testafe_test.hAnalog front-end validation
BLE Loopbackble_loopback.hBluetooth Low Energy testing
LRWPAN Loopbacklrwpan_loopback.h802.15.4 protocol testing
NFC Testnfc_test.hNear-field communication
Power Testpower_test.hPower system validation
Sensor ADC Testsensor_adc_test.hADC calibration and testing
RTC Timer Testrtc_timer_test.hTimer and clock testing

Creating New Applications

Create Source File

// core/src/my_application.c
#include "scum_hal.h"
#include "my_application.h"
 
int main(void) {
    // Initialize hardware
    scum_hal_init();
    
    // Your application logic
    while(1) {
        // Main loop
    }
    
    return 0;
}

Create Header File

// core/inc/my_application.h
#ifndef MY_APPLICATION_H
#define MY_APPLICATION_H
 
#include "scum_hal.h"
 
// Function declarations
void my_function(void);
 
#endif

Update Makefile

Add your source files to the build system configuration.

Hardware Abstraction Layer (HAL)

Core HAL Functions

// System initialization
void HAL_init(void);
uint64_t HAL_getTick(void);
void HAL_delay(uint64_t time_us);
void HAL_delay_cycles(uint64_t cycles);
 
// GPIO operations  
void HAL_GPIO_init(GPIO_TypeDef *GPIOx, GPIO_Pin pin);
void HAL_GPIO_writePin(GPIO_TypeDef *GPIOx, GPIO_Pin pin, uint8_t value);
uint8_t HAL_GPIO_readPin(GPIO_TypeDef *GPIOx, GPIO_Pin pin);
 
// UART communication
void HAL_UART_init(UART_TypeDef *UARTx, UART_InitTypeDef *UART_init);
Status HAL_UART_transmit(UART_TypeDef *UARTx, uint8_t *data, uint16_t size, uint32_t timeout);
Status HAL_UART_receive(UART_TypeDef *UARTx, uint8_t *data, uint16_t size, uint32_t timeout);
void HAL_UART_finishTX(UART_TypeDef *UARTx);
 
// RTC Timer operations (low-level)
int32_t rtc_timer_get_coutner(void);
void rtc_timer_set_prescaler(int16_t prescaler);
void rtc_timer_set_cc0(int32_t cc0);

Memory-Mapped I/O (MMIO)

Direct register access utilities:

#include "mmio.h"
 
// 32-bit register access
uint32_t reg_value = reg_read32(BASE_ADDR + OFFSET);
reg_write32(BASE_ADDR + OFFSET, value);
 
// 16-bit register access  
uint16_t reg_value = reg_read16(BASE_ADDR + OFFSET);
reg_write16(BASE_ADDR + OFFSET, value);
 
// 8-bit register access
uint8_t reg_value = reg_read8(BASE_ADDR + OFFSET);
reg_write8(BASE_ADDR + OFFSET, value);

Communication Interfaces

High-level TileLink transactions:

// Read from address
uint32_t data = tl_read32(address);
 
// Write to address  
tl_write32(address, data);
 
// Burst operations
tl_burst_read(start_addr, buffer, length);
tl_burst_write(start_addr, buffer, length);

Analog Scan Chain

Access analog configuration registers:

// Configure analog block
asc_write_register(block_id, reg_addr, value);
 
// Read analog status
uint32_t status = asc_read_register(block_id, reg_addr);

Debugging and Testing

Printf Support

⚠️

BUILD_MODE Compatibility: printf is ONLY supported in BUILD_MODE=SIM. Using printf in BUILD_MODE=BRINGUP will lock up the CPU because HTIF is not available on hardware.

The firmware includes printf support via HTIF (Host-Target Interface) for simulation only:

#include "htif.h"
 
// Only available in BUILD_MODE=SIM
printf("Debug: value = %d\n", variable);
printf("Status: 0x%08x\n", register_value);

Simulation Utilities

BUILD_MODE Compatibility: sim_utils functions are ONLY available in BUILD_MODE=SIM. These are not accessible in BUILD_MODE=BRINGUP.

#include "sim_utils.h"
 
// Simulation-specific functions (SIM mode only)
sim_exit(exit_code);
sim_checkpoint("test_point_1");

Memory Dump and Analysis

Generate firmware analysis:

make objdump          # Create disassembly
cat objdump.txt       # Review assembly output

Best Practices

Memory Management

⚠️

SCuM-V has limited memory resources. Optimize data structures and avoid dynamic allocation where possible.

  • Use fixed-size buffers
  • Minimize stack usage in interrupt handlers
  • Leverage const data placement in ROM

Power Optimization

// Use interrupt-driven UART I/O
HAL_UART_enableRXInterrupt(UART0, 1);  // Enable RX interrupt when 1 byte available
HAL_UART_enableTXInterrupt(UART0, 1);  // Enable TX interrupt when TX FIFO has space
 
// Direct register access for power management
reg_write32(PERIPHERAL_BASE + POWER_CTRL, POWER_DOWN_MODE);

Interrupt Handling

void uart_rx_handler(void) __attribute__((interrupt("machine")));
 
void uart_rx_handler(void) {
    // Check if RX data is available
    if (HAL_UART_getRXFIFODepth(UART0) > 0) {
        uint8_t data;
        HAL_UART_receive(UART0, &data, 1, 0);  // Non-blocking read
        
        // Process received data
        process_uart_data(data);
    }
}

Deployment and Programming

Programming via FPGA Controller

Build Firmware

cd sw/scum_firmware
make

Program via SerialTL

cd sw  
python tl_host.py --program firmware.bin

Verification

  • Monitor UART output for debug messages
  • Use logic analyzer to verify communication protocols
  • Check power consumption during operation

Next Steps: API Reference →