🛠 Mastering I/O on Sapphire RV64: GPIO and Protocols
Objectives
This tutorial aims to implement a data acquisition chain using the native interfaces of the Sapphire RV64 SoC. We will explore GPIO configuration for simple device control and the use of I2C/SPI masters for communicating with sensors or displays. The focus is on optimizing memory access via the AXI4 bus and interrupt handling.
Required Hardware
- Efinix FPGA Board (Titanium, Topaz, or Trion T20+ series)
- Efinity 2026.1 or later software
- Sapphire RV64 SoC configured with at least 1 core, A extension (atomics) enabled
- External peripherals: I2C sensor (e.g., BME680) or SPI display
- JTAG wiring for debugging
Steps
- SoC Configuration in Efinity: Open your project and access the VexiiRiscv core configuration. Enable the desired interfaces. Note that Sapphire RV64 supports up to 32 GPIOs, 5 I2C masters, and 3 SPI masters. Ensure peripheral base addresses (e.g., 0x01000000 for GPIO) are defined without memory map conflicts.
- Bitstream Generation: Compile the project to generate the bitstream. Verify that the synthesizer reports no pin conflicts. If using external DDR3 or LPDDR4x memory, ensure the memory controller is correctly initialized in the bootloader.
- GPIO Driver Development: In your C/C++ code, define control registers. For example, to configure a pin as output:
#define GPIO_BASE 0x01000000
#define GPIO_DIR (GPIO_BASE + 0x00)
#define GPIO_OUT (GPIO_BASE + 0x04)
void gpio_set_output(int pin) {
*(volatile uint32_t*)GPIO_DIR |= (1 << pin);
}
Use atomic instructions (A extension) if multiple cores access the same GPIOs to avoid race conditions.
- I2C/SPI Protocol Implementation: For an I2C sensor, configure the I2C master via its status and data registers. The AXI4/APB3 bus handles transactions. Example of writing a byte:
#define I2C_BASE 0x02000000
void i2c_write(uint8_t addr, uint8_t data) {
// Wait for bus to be free
while (*(volatile uint32_t*)(I2C_BASE + 0x04) & 0x01);
*(volatile uint32_t*)(I2C_BASE + 0x08) = addr;
*(volatile uint32_t*)(I2C_BASE + 0x0C) = data;
*(volatile uint32_t*)(I2C_BASE + 0x00) = 0x01; // Start
}
- Interrupt Handling: Configure the interrupt controller to react to hardware events (e.g., SPI transfer completion). This frees the CPU for other tasks, crucial with the 7-stage pipeline.
Verification
Use a logic analyzer to verify signals on physical pins. In software, print sensor values via UART. Verify that hardware breakpoints (up to 4 available) work correctly if debugging the driver code.
Further Reading
Explore using the shared L2 cache to speed up data buffer access. Test the SV39 MMU to isolate drivers in user space under Linux if the OS is deployed.
Other tutorials for this board
Code examples for this board
gpio_blink_riscv.inoC (RISC-V)uart_echo_riscv.inoC (RISC-V)spi_sensor_read.inoC (RISC-V)i2c_master_write.inoC (RISC-V)timer_interrupt_handler.inoC (RISC-V)i2c-bmp280-temperature-6.inoC