VexRiscv introduction
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.
Setup
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
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.
Supplementary materials
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.
Suggestions
- 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.
CPU pipeline
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.
Plugins
The top-level CPU definition only declares which stages are present and some default Stageable
objects. The remaining functionality consists almost entirely of Plugin
s (see below). Plugin
s 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.
Side effects
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.
Repository structure
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.