🛠 Mastering I/O on Sapphire RV64: GPIO and Protocols

Efinix Sapphire RV64 : Configurable 64-bit RISC-V SoC for FPGAs · avance · Configurez et pilotez les interfaces périphériques avancées (GPIO, I2C, SPI) du SoC Sapphire RV64 pour des interactions matérielles complexes.

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

Steps

  1. 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.
  1. 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.
  1. 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.

  1. 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
}
  1. 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

← Back to the board 🛠 All tutorials