VexRiscv Documentation

repository·master·Indexed 25 days ago

https://github.com/spinalhdl/vexriscv

A highly configurable RISC-V CPU implementation written in SpinalHDL and optimized for FPGA deployment. The documentation covers the plugin-based architecture, implementing hardware peripherals (such as a GCD peripheral) for the Murax SoC, using the SpinalHDL FSM library, and performing simulations with Verilator. It also provides guides on integrating APB3 peripherals, configuring Xilinx Vivado for Arty A7, setting up OpenOCD for JTAG debugging, and using Intel VJTAG in Briey.scala.

Tokens
16.9K
Snippets
41
Records
88
Agent score
83%

What's inside VexRiscv

  1. Overview of VexRiscv Specifications

    master

    VexRiscv is a RISC-V implementation written in SpinalHDL optimized for FPGAs. Key specifications include:

    • Instruction Set: RV32I[M][A][F[D]][C]
    • Pipeline: 2 to 5+ stages (Fetch, Decode, Execute, Memory, WriteBack).
    • Performance: Up to 1.44 DMIPS/MHz (no-inline) or 1.57 DMIPS/MHz (with divider lookup table).
    • Bus Interfaces: AXI4, Avalon, and Wishbone ready.
    • Features:
      • Optional MUL/DIV extensions.
      • Optional F32/F64 FPU (requires data cache).
      • Optional instruction and data caches.
      • Optional hardware refilled MMU.
      • Optional debug extension (GDB via OpenOCD/JTAG).
      • Optional interrupts/exception handling (Machine, Supervisor, and User modes).
      • Support for tightly coupled memory on I$ and D$.
    • Compatibility: Linux and Zephyr compatible; FreeRTOS port available.
  2. Understand the Coherent Interface Specification

    master

    The coherent interface is designed for multi-level coherent interconnects, supporting both cache-full and cache-less agents. It uses three primary sub-interfaces composed of 7 streams to manage memory coherency between Masters (e.g., CPU caches) and Slaves (e.g., main memory or interconnects).

    Key Features:

    • Three Interfaces: Write, Read, and Probe.
    • Two Data Paths: Read and Write paths, with the ability to reuse the write data path for dirty/clean sharing.
    • Ordering: No strict ordering is enforced, but barrier transactions are provided.
    • Stream Primitive: All communication uses a valid/ready handshake mechanism.
  3. Implement a GCD Peripheral for Murax SoC

    master

    This tutorial demonstrates how to implement a Greatest Common Divisor (GCD) hardware peripheral and integrate it into the Murax SoC (a lightweight VexRiscv platform) using SpinalHDL.

    Implementation Strategy

    The tutorial follows a standard RTL design pattern by separating the logic into two distinct paths:

    1. Data Path (GCDData): Handles the movement and calculation of data using multiplexers, subtractors, comparators, and registers (DFFs). It processes the operands A and B and produces the result.
    2. Control Path (GCDCtrl): Implements a Finite State Machine (FSM) to orchestrate the data path. It manages the valid and ready signals and uses comparison results from the data path to drive state transitions.

    Hardware Interface Specification

    The module uses a 32-bit integer interface with a valid-ready handshake mechanism:

    • Inputs: A (32-bit), B (32-bit), valid (control signal to start calculation).
    • Outputs: result (32-bit), ready (control signal indicating completion).

    Integration Workflow

    To integrate this peripheral into the Murax SoC, the tutorial covers:

    • Mapping control and data ports to memory-mapped registers via the Apb3 peripheral bus.
    • Defining memory addresses and access modes (read/write/clear).
    • Extending baremetal software to interact with the new hardware module.
  4. Perform interactive debug via GDB, OpenOCD, and Verilator

    master

    To debug the simulated CPU interactively, use the GenFull configuration (note: GenSmallest lacks a debug module) and add DEBUG_PLUGIN_EXTERNAL=yes to the make arguments. Follow these steps:

    1. Start the simulation in the VexRiscv repository.
    2. Start the OpenOCD RISC-V server pointing to the generated CPU YAML file.
    3. Run a GDB session with your RISC-V ELF executable.
    # 1. Run simulation
    sbt "runMain vexriscv.demo.GenFull"
    cd src/test/cpp/regression
    make run DEBUG_PLUGIN_EXTERNAL=yes
    
    # 2. Start OpenOCD (in the openocd git directory)
    src/openocd -c "set VEXRISCV_YAML PATH_TO_THE_GENERATED_CPU0_YAML_FILE" -f tcl/target/vexriscv_sim.cfg
    
    # 3. Run GDB session
    YourRiscvToolsPath/bin/riscv32-unknown-elf-gdb VexRiscvRepo/src/test/resources/elf/uart.elf
    target remote localhost:3333
    monitor reset halt
    load
    continue
  5. Build and Run GCD Software on Murax Simulation

    master

    Follow these steps to compile your software and run it in the VexRiscv simulation:

    1. Compile Software: Navigate to your software directory (e.g., src/main/c/murax/gcd_world) and run make.
    2. Update SoC Configuration: In Murax.scala, update the MuraxWithRamInit object to point onChipRamHexFile to your compiled .hex file.
      onChipRamHexFile = "src/main/c/murax/gcd_world/build/gcd_world.hex"
    3. Generate Hardware: From the root directory, run the SBT command:
      sbt "runMain vexriscv.demo.MuraxWithRamInit"
    4. Run Simulation: Navigate to the simulation directory (e.g., src/test/cpp/murax) and run:
      make clean run

    Note: Simulation does not auto-shutdown; use CTRL+C to stop.

    sbt "runMain vexriscv.demo.MuraxWithRamInit"
    
    # Then in src/test/cpp/murax:
    make clean run
  6. Define component logic and registers in SpinalHDL

    master

    Components (modules) in SpinalHDL are defined by extending Component.

    • IO: Define inputs and outputs using a Bundle with in() and out() modifiers.
    • Registers: Use Reg(Type) to define a register. You can provide an initial value using .init(value).
    • Control Signals: Use when(condition) { ... } blocks to define enable signals or conditional logic for registers and signals.
    • Directional Bundles: When using a shared IMasterSlave bundle, use master() or slave() to tell SpinalHDL to infer directions based on the asMaster() definition.
    class GCDData() extends Component {
      val io = new Bundle {
        val a = in(UInt(32 bits))
        val b = in(UInt(32 bits))
        val res = out(UInt(32 bits))
        val dataCtrl = slave(GCDDataControl())
      }
      
      val regA = Reg(UInt(32 bits)) init(0)
      val regB = Reg(UInt(32 bits)) init(0)
    
      when(io.dataCtrl.init){
        regA := io.a
        regB := io.b
      }
      // ...
    }
  7. Configure IDE support for Mill

    master

    You can generate configuration files for your IDE using Mill:

    • Build Server Protocol (BSP): ./mill mill.bsp.BSP/install
    • IntelliJ IDEA Support: ./mill mill.idea.GenIdea/idea
    ./mill mill.bsp.BSP/install
    ./mill mill.idea.GenIdea/idea
  8. Implement a C Software Driver for the GCD Peripheral

    master

    To interact with the GCD peripheral in C, define a register structure and map it to the peripheral's base address.

    1. Define the Register Structure: Create a struct with volatile uint32_t members corresponding to the hardware registers.
    2. Map the Base Address: In your hardware abstraction header (e.g., murax.h), define a macro that casts the peripheral's base address to your register structure pointer.
    3. Access via Pointer: Use the macro to access registers directly.

    Example structure and mapping:

    // gcd.h
    typedef struct {
      volatile uint32_t A;
      volatile uint32_t B;
      volatile uint32_t RES;
      volatile uint32_t READY;
      volatile uint32_t VALID;
    } Gcd_Reg;
    
    // murax.h
    #define GCD ((Gcd_Reg*)(0xF0030000))
    typedef struct
    {
      volatile uint32_t A;
      volatile uint32_t B;
      volatile uint32_t RES;
      volatile uint32_t READY;
      volatile uint32_t VALID;
    } Gcd_Reg;
    
    #define GCD ((Gcd_Reg*)(0xF0030000))
  9. Integrate an APB3 Peripheral into Murax SoC

    master

    To integrate a new peripheral into the Murax SoC, you must wrap your module in an APB3 slave interface and register it in the SoC configuration.

    1. Create an APB3 Wrapper: Use Apb3SlaveFactory to map your module's IO signals to memory-mapped registers. Use driveAndRead() for R/W registers, read() for read-only registers, and setOnSet() for write-only signals that should clear after being set.
    2. Modify Murax SoC: In src/main/scala/vexriscv/demo/Murax.scala, locate the //******** APB peripherals ********* section. Instantiate your peripheral and add it to the apbMapping with a designated base address and size.

    Example of adding a GCD peripheral:

    val gcd = new Apb3GCDCtrl(
      apb3Config = Apb3Config(
        addressWidth = 20,
        dataWidth = 32
      )
    )
    apbMapping += gcd.io.apb -> (0x30000, 1 kB)
    val gcd = new Apb3GCDCtrl(
      apb3Config = Apb3Config(
        addressWidth = 20,
        dataWidth = 32
      )
    )
    apbMapping += gcd.io.apb -> (0x30000, 1 kB)
  10. Generate and simulate the Briey SoC

    master

    Briey is a demonstration SoC. To use it:

    1. Install required system dependencies (build-essential, xorg-dev, etc.).
    2. Generate the hardware using sbt.
    3. Run the Verilator simulation from the src/test/cpp/briey directory.
    4. Connect OpenOCD to the simulation using the jtag_tcp.cfg interface.