Custom RISC-V Soft Cores on FPGAs for Low-Latency Robotics

FPGA · RISC-V · Embedded Robotics

Custom RISC-V Soft Cores on FPGAs for Low-Latency Robotics

By Rainer Rodrigues · June 6, 2026 · 14 min read FPGA Robotics

Why ARM MCUs are hitting a wall for advanced robotics — and how open-source RISC-V soft cores on mid-range FPGAs unlock sub-microsecond, royalty-free motor control.

Custom RISC-V soft core on FPGA for low-latency robotics motor control
TL;DR — What you'll learn
  • Why ARM MCUs introduce non-deterministic jitter that breaks high-speed motor control loops
  • How a RISC-V soft core on an FPGA gives you cycle-accurate, royalty-free, sub-microsecond control
  • How to implement a minimalist memory-mapped peripheral interface in SystemVerilog with the open-source toolchain
Ad · Top of post

Why Traditional ARM Solutions Are Hitting a Wall

For decades, the standard playbook for embedding intelligence into a robot was simple: drop in an ARM-based microcontroller, write some C++, and call it done. But as we push deeper into the era of Physical AI, advanced multi-axis manipulation, and agile legged robots, that playbook is hitting a critical technical and financial wall.

Two bottlenecks are forcing the industry to rethink its entire compute fabric:

1. The Financial Gatekeeping of Proprietary ISAs

For an early-stage robotics startup, capital is oxygen. Navigating upfront architectural licensing fees to customise a proprietary ARM core — or adapting a rigid off-the-shelf SoC to unique pin constraints — can drain thousands of dollars before a single prototype walks or rolls.

RISC-V, being an open-standard Instruction Set Architecture (ISA), requires zero royalty fees, completely changing the economics of custom hardware design.

Affiliate

Recommended reading: "The RISC-V Reader: An Open Architecture Atlas" by Patterson and Waterman — the definitive guide to the RISC-V ISA written by its creators. Essential reading before implementing any soft core.

View on Amazon India →

2. The Non-Deterministic Jitter of Sequential CPUs

Even if you write incredibly tight interrupt service routines (ISRs) on a traditional MCU, your code still executes sequentially. If the processor is busy parsing an SPI packet from an IMU or managing a Wi-Fi connection, your field-oriented control (FOC) loop for a high-torque BLDC motor faces scheduling delays.

In high-speed robotics, latency spikes of just a few microseconds can: Destabilise a control trajectory, cause mechanical chatter in a joint, or trigger a safety fault that shuts down the entire system.

Enter the FPGA Soft Core: Hardware-Level Customisation

By deploying a Soft Core — a processor block implemented entirely using the programmable logic fabric of an FPGA — you merge the software programmability of an MCU with the true hardware parallelism of programmable logic.

What is a Soft Core? Unlike a fixed "hard" processor baked permanently into silicon, a soft core is written in an HDL like Verilog or VHDL. It can be reconfigured, optimised, or completely wiped from the chip with a firmware flash.

Because RISC-V is modular, you don't need a massive desktop-class CPU. For real-time motor actuation, deploy a minimalist 3-stage RV32I (Base 32-bit Integer) core, strip out everything else, and append your custom execution instructions directly into the hardware pipeline.

Replace with diagram
Architecture diagram: Encoder/Sensor → Custom Verilog Math Blocks → RISC-V Core → PWM Output. Create in app.diagrams.net, export 1200×600 PNG, upload via Insert Image.
Ad · Mid-article

Architectural Layout: The Low-Latency Motor Control Loop

To handle a 100 kHz space vector PWM generation loop, the RISC-V core acts as orchestrator while dedicated Verilog modules handle the heavy arithmetic. Rather than forcing the CPU to compute Clarke or Park transforms in software, you write dedicated parallel pipelines in Verilog:

┌─────────────────────────────────────────────────────────────┐ │ Low-Latency Motor Control Architecture │ └─────────────────────────────────────────────────────────────┘ [ Encoders / Current Sensors ] │ ▼ ← raw sensor data, every clock cycle [ Custom Verilog Math Blocks ] ← Clarke & Park transforms (parallel hardware pipelines) run in pure hardware │ │ Low-latency internal data bus[ Minimalist RISC-V RV32I Core ] ← reads final state values, makes control decisions[ PWM Actuation Signals ] ──────────────────────────▶ Motor
Why this is faster than an MCU: The Clarke and Park transforms execute in hardware every single clock cycle with zero latency. The RISC-V core only reads final computed values — it never blocks on math. Total control loop latency: single-digit nanoseconds vs. 2–10 µs on a sequential MCU.

Selecting Your Mid-Range FPGA Silicon

For professional, industrial-grade robotics controllers, you need mid-range programmable logic that balances logic cell density, power consumption, and modern toolchain support:

FPGA Platform Soft Core Best For
AMD Kria K26 (Zynq MPSoC) MicroBlaze V (Native RISC-V) Hardware acceleration pipelines; native colcon build system integration with ROS 2
Lattice Avant-E / Avant-G RISC-V MC CPU IP Core Ultra-low power; multi-axis motor control in compact housings
Efinix Trion / Titanium Sapphire RISC-V Core Compact footprints; tight physical AI sensor-fusion processing
Lattice iCE40 (beginner) PicoRV32 Learning RISC-V soft cores — cheapest entry point, full open-source toolchain
Affiliate

Hardware recommendation: The Lattice iCE40 UltraPlus (iCEBreaker board) is the ideal beginner FPGA for running PicoRV32. Open-source toolchain (Yosys + nextpnr), USB-C powered, ships to India.

Check price on Amazon India →

Step-by-Step: Implementing a Minimalist Peripheral Interface

When configuring an open-source core like NEORV32 or VexRiscv in Verilog, you must hook up custom memory-mapped registers so your application can interact with physical robotic pins. Here is a complete, working example:

Step 1 — Install the Open-Source Toolchain on WSL

We use the OSS CAD Suite — a pre-packaged bundle of Yosys, Icarus Verilog, Verilator, and GTKWave. Open your WSL Ubuntu terminal:

terminal (WSL Ubuntu 22.04) bash
# 1. Install WSL if not already set up (PowerShell as Admin)
#    wsl --install

# 2. Download OSS CAD Suite (latest release)
wget https://github.com/YosysHQ/oss-cad-suite-build/releases/latest/download/oss-cad-suite-linux-x64.tgz

# 3. Extract and add to PATH
tar -xzf oss-cad-suite-linux-x64.tgz
export PATH="$HOME/oss-cad-suite/bin:$PATH"

# 4. Add to .bashrc so it persists across sessions
echo 'export PATH="$HOME/oss-cad-suite/bin:$PATH"' >> ~/.bashrc

# 5. Verify all tools are present
iverilog --version   # Icarus Verilog — simulation
yosys --version      # Yosys — synthesis
verilator --version  # Verilator — fast simulation

Step 2 — Write the Memory-Mapped Motor Peripheral in SystemVerilog

This module is the hardware bridge between your RISC-V soft core and the physical motor direction pin. When the processor writes to address 0x40001000, the hardware instantly updates the pin — no ISR, no scheduling, no jitter:

motor_peripheral_interface.sv systemverilog
// ─────────────────────────────────────────────────────────────
//  motor_peripheral_interface.sv
//  Memory-mapped motor control register for RISC-V soft core
//
//  Address map:
//    0x40001000  [0]  → motor_direction_pin  (0=FWD, 1=REV)
//    0x40001000  [1]  → motor_enable_pin     (0=off, 1=on)
//    0x40001000  [7:2] → reserved
// ─────────────────────────────────────────────────────────────
module motor_peripheral_interface (
    input  wire        clk,
    input  wire        reset_n,          // active-low reset
    input  wire [31:0] bus_address,
    input  wire [31:0] bus_write_data,
    input  wire        bus_write_enable,
    output reg         motor_direction_pin,
    output reg         motor_enable_pin
);

    // Memory-mapped base address for motor controller block
    localparam logic [31:0] MOTOR_CTRL_REG = 32'h4000_1000;

    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            // Safe state on reset: motor off, forward direction
            motor_direction_pin <= 1'b0;
            motor_enable_pin    <= 1'b0;

        end else if (bus_write_enable && (bus_address == MOTOR_CTRL_REG)) begin
            // Write to motor control register
            motor_direction_pin <= bus_write_data[0]; // bit 0 = direction
            motor_enable_pin    <= bus_write_data[1]; // bit 1 = enable
        end
    end

endmodule
Why always_ff instead of always @? The always_ff procedural block is a SystemVerilog construct that tells synthesis tools (and other engineers reading your code) that this block must infer flip-flop registers. It catches common mistakes at lint time that plain always blocks would silently accept.

Step 3 — Write the Simulation Testbench

Never write hardware without a testbench. This verifies your peripheral responds correctly to the processor's bus writes before you ever touch real silicon:

tb_motor_peripheral.sv systemverilog
`timescale 1ns/1ps

module tb_motor_peripheral;

    // ── DUT signal declarations ──────────────────────────────
    logic        clk, reset_n;
    logic [31:0] bus_address, bus_write_data;
    logic        bus_write_enable;
    wire         motor_direction_pin, motor_enable_pin;

    // ── Instantiate the design under test ───────────────────
    motor_peripheral_interface dut (.*);

    // ── 10ns clock (100 MHz) ────────────────────────────────
    initial clk = 0;
    always #5 clk = ~clk;

    // ── Dump waveforms for GTKWave ──────────────────────────
    initial begin
        $dumpfile("motor_tb.vcd");
        $dumpvars(0, tb_motor_peripheral);
    end

    // ── Stimulus ─────────────────────────────────────────────
    initial begin
        // Apply reset
        reset_n = 0; bus_write_enable = 0;
        bus_address = 32'h0; bus_write_data = 32'h0;
        @(posedge clk); @(posedge clk);
        reset_n = 1;

        // Test 1: Enable motor, set FORWARD direction
        @(posedge clk);
        bus_address      = 32'h4000_1000;
        bus_write_data   = 32'h0000_0002; // bit1=enable, bit0=forward
        bus_write_enable = 1;
        @(posedge clk); bus_write_enable = 0;

        // Test 2: Enable motor, set REVERSE direction
        @(posedge clk);
        bus_write_data   = 32'h0000_0003; // bit1=enable, bit0=reverse
        bus_write_enable = 1;
        @(posedge clk); bus_write_enable = 0;

        // Test 3: Disable motor (safety)
        @(posedge clk);
        bus_write_data   = 32'h0000_0000;
        bus_write_enable = 1;
        @(posedge clk); bus_write_enable = 0;

        @(posedge clk); @(posedge clk);
        $display("SIMULATION COMPLETE");
        $finish;
    end

endmodule

Step 4 — Simulate with Icarus Verilog and View Waveforms in GTKWave

  1. Compile both files with Icarus Verilog: iverilog -g2012 -o motor_sim motor_peripheral_interface.sv tb_motor_peripheral.sv
  2. Run the simulation to generate the VCD waveform file: vvp motor_sim — you should see SIMULATION COMPLETE in the terminal
  3. Open GTKWave to inspect the signal waveforms: gtkwave motor_tb.vcd — drag motor_direction_pin and motor_enable_pin into the signal view
  4. Verify the timing: the output pins should toggle exactly one clock cycle after each write, confirming zero-latency register updates
📈
Replace with screenshot
Screenshot of GTKWave showing motor_direction_pin and motor_enable_pin toggling in sync with bus_write_enable. Capture via File → Print to PNG in GTKWave.
Gist tip — embed these code blocks properly: Create three public Gists at gist.github.com — name them motor_peripheral_interface.sv, tb_motor_peripheral.sv, and motor_toolchain_setup.sh. Then replace each code block above with its Gist embed script tag for syntax highlighting that works on all devices.
Ad · Second mid-article

Common Gotchas and How to Fix Them

  • iverilog: error: Unknown module type: motor_peripheral_interface — You're compiling the testbench without including the design file. Fix: always compile both files together as shown in Step 4.
  • Signals in GTKWave show as "X" (unknown) — Your testbench isn't properly applying reset. Make sure reset_n = 0 for at least 2 clock cycles before releasing it.
  • always_ff not recognised — You're using Verilog-2001 mode. Add the -g2012 flag to iverilog to enable SystemVerilog-2012 syntax.
  • OSS CAD Suite: command not found after restarting WSL — The export PATH line in your .bashrc didn't take effect. Run source ~/.bashrc or close and reopen your WSL terminal.

Next Steps: Building the Full RISC-V Control Stack

This peripheral interface is the foundation. The next logical steps are:

  1. Instantiate a full RISC-V core — integrate NEORV32 or VexRiscv from GitHub and connect it to your peripheral via its Wishbone or AXI4-Lite bus
  2. Add a PWM generation module — write a Verilog module that generates 100 kHz PWM signals controlled by the same memory-mapped register approach
  3. Implement the Clarke and Park transforms — write them as parallel hardware pipelines feeding the RISC-V core results via status registers
  4. Target real silicon — use the Yosys + nextpnr flow to synthesise and place-and-route your design onto a Lattice iCE40 or Efinix Trion board
Affiliate

Debugging hardware? A Rigol DS1054Z 4-channel 50 MHz oscilloscope is the standard recommendation for verifying FPGA output signals and PWM waveforms. Widely available in India via Amazon.

Check price on Amazon India →

Disclosure: This post contains affiliate links. If you purchase through them, AppliedKaos may earn a small commission at no extra cost to you. All recommendations are based on genuine use and research.

Comments

Popular Posts