VexRiscv is an open-source RISC-V processor written in SpinalHDL. It was awarded first place in the RISC-V Foundation’s SoftCPU contest in 2018 for achieving the highest performance on both Lattice and Microsemi FPGAs. It can be scaled to fit a wide range of use cases, from highly constrained bare-metal embedded applications to multi-core Linux with the same code base.
VexRiscv takes a uniquely software-inspired approach to hardware description. Virtually every component is a plugin object connected to one or more stages in the pipeline. Plugins can insert data into the pipeline at one stage, and VexRiscv will automatically propagate those signals through down the pipeline to be accessed by later stages. Plugins can also provide services, which are essentially APIs for other plugins to interact with. The result is a highly configurable CPU where everything down to the register file is interchangeable.
- VexRiscv introduction
We recommend following the installation instructions from the VexRiscv README found here. The rest of the README requires an understanding of how the CPU is structured on a high level, which this page aims to provide.
SpinalHDL is a Scala-based hardware description language. Traditional HDLs offer few high-level abstractions, which leads to long and repetitive descriptions of wiring interconnects. Developing complex hardware in those languages is very time consuming, and maintenence can be difficult. SpinalHDL allows developers to leverage high-level abstractions, as well as object-oriented and functional programming patterns, to create concise self-documenting hardware.
A frequently asked question regarding SpinalHDL is how does it compare to Chisel? The language was inspired by, though not quite “forked” from, Chisel 2. The Chisel ecosystem exists to support ASIC design and manufacturing. At the time of this writing, it’s under active development with frequent API changes. SpinalHDL targets FPGAs, and is essentially “frozen” at this point. It is still actively maintained (i.e., bugs are fixed quickly and new features are added on occasion), but the API is fixed for the foreseeable future.
An overview of the language with illustrations is available here. A great starting point for learning is the workshop, and the docs are thorough and up-to-date.
- Start with the workshop and inspect the Verilog output for each task. It is important to have an intuition for how SpinalHDL translates to hardware when working with larger projects.
- Use GTKWave to inspect test traces in the workshop. This is an incredibly useful tool for debugging more complex hardware.
- Always be aware of what is pure Scala and what is part of the SpinalHDL libraries. It is sometimes desirable to specify a component and then duplicate or connect it with functional programming techniques. This can lead to unexpected results for the inexperienced user.
- You do not need to install SpinalHDL from source to work with VexRiscv. The Scala package manager will handle this automatically when building the CPU.
VexRiscv has a canonincal 5-stage RISC pipeline. In piplelined CPUs, the control signals are propagated through the stages through dedicated register files. For example, if the program counter (PC) signal at the fetch stage is
0xabcd on some cycle, then the the PC will be
0xabcd when accessed by the writeback stage four cycles later (assuming no jumps or stalls occur).
(Images taken from this SpinalHDL presentation.)
VexRiscv provides a convenient API for adding new signals of any data type to the CPU pipeline. This is, arguably, its greatest selling point. For example, the global declaration of PC is one line of code in the top-level CPU definition:
object PC extends Stageable(UInt(32 bits))
In the above illustration, the PC is given a value in the fetch stage by one plugin with
fetch.insert(PC) := X. Another plugin needs to check the PC in the writeback stage, which is done with
Y := writeback.input(PC). VexRiscv maintains internal consistency on its own; developers typically do not need to worry about how signals are propagated.
The top-level CPU definition only declares which stages are present and some default
Stageable objects. The remaining functionality consists almost entirely of
Plugins (see below).
Plugins can connect to one or more CPU stages. They interact with each other by reading and inserting
Stageable objects, providing a service API or by influencing the CPU arbitration. For example, a plugin can halt the pipeline at any stage and flush any instruction.
VexRiscv includes two simple examples of custom plugins as a reference, available here. These are a good starting point for new developers. More advanced developers can also implement their own instructions. An example of that is provided here.
Plugins that interact with memory or modify the CPU’s arbitration (such as PMP) must take into account side effects. This is relevant to more advanced developers. For example, a jump may originate from the fetch, decode or writeback stages, but CSR writes occur in the execute stage. This can lead to a hazard where the CSR write modifies the machine state, even though the instruction is going to be flushed. To avoid this, the
CsrPlugin halts the execute stage for two cycles before performing to allow memory and writeback to finish.
The Scala source files for the CPU itself are found under
src/main/scala/vexriscv. Within this directory, the
demo folder contains several files prefixed with
Gen*. These contain concrete CPU configurations and can be invoked to generate RTL. From the root directory of the repository, for example, run:
sbt "runMain vexriscv.demo.GenFull"
This will generate a
VexRiscv.v file containing the entire CPU. Note that these configurations are not used in LiteX; those require an additional plugin to interface with the SoC’s Wishbone bridge. More information on LiteX integration is available here.