This is a basic starter RV32I core. It is my first large hardware design project.
| Category | Component / Tool |
|---|---|
| Target Hardware | Sipeed Tang Nano 20K (Gowin GW2AR-LV18) |
| HDL Language | SystemVerilog |
| Simulation | Verilator |
| Waveform Viewer | Surfer |
| Synthesis | Yosys |
| Place & Route | NextPNR-Gowin |
| Bitstream Tool | Project Apicula |
| Formal Verification | riscv-formal and SymbiYosys |
| CI/CD | GitHub Actions |
- Target: This core is targeted for the Sipeed Tang Nano 20K
- Toolchain: I use SystemVerilog, Verilator, Yosys, NextPNR, and Project Apicula.
- Architecture: Uses the RV32I Base Unprivileged Architecture
- Microarchitecture: Uses a primarily Single-Cycle architecture except for
loadinstructions (see below). - Memory: Uses synchronous Dual-Ported BRAM for a seamless Von Neumann experience while maintaining the simplicity of a Harvard architecture in hardware.
- Fetching: Calculates the next PC combinationally before feeding it into the synchronous BRAM. This eliminates the need for a dedicated fetch cycle while working with FPGA primitives.
- Loads & Stores: Because memory is synchronous, loads and stores present a challenge. Stores calculate the address and present the data on the memory port. It is committed on the rising edge of the clock. Loads calculate the address and present it to the memory address port. On the next cycle the result is read from the data port, it is shifted/masked (if necessary), and is written back to the register file. The load delay is managed by a FSM in the decoder (the decoder is combinational with an external 1-bit register for a load flag).
- Tested and Verified: Processor fully passes riscv-tests, riscv-formal, and the Verilator linter. Commits and PRs are tested using GitHub Actions.
- Misaligned Memory Accesses: Currently, the core does not support misaligned memory accesses (
addr % 4 != 0for words,addr % 2 != 0for halfwords). Since I am implementing an Unprivileged Architecture, I cannot currently add a Trap Handler to handle misaligned accesses.
- To use this processor, first set up the environment on your device. It is highly recommended to use the VSCode docker extension or GitHub Codespaces. If you have any difficulties setting up the environment, let me know.
- Note: I do not have a setup for running this on anything but x86 Linux currently. Sorry.
- Put your executable RISC-V binary in the
build/target/bootloader.hexfor synthesis. Due to a silent bug ingowin_packto initialize dual-ported Block RAM with data, this is currently the only way to run code on the physical FPGA. Note that the bootloader does not have rw privileges, only x priveleges, so the .data or .rodata sections should be excluded from this part. - Run the
synthesizecommand. It should generate a bitstream.fs file in your repository root directory. - Flash the FPGA
- Download or transfer the bitstream.fs file to your device with the USB port to flash the FPGA.
- Install
openFPGALoaderon your device and run this command:openFPGALoader -b tangnano20k [path/to/bitstream.fs]This loads the bitstream onto the FPGA's SRAM. To flash the FPGA so that the design stays on permanently, add the -f flag.
- To formally verify the processor, run the
test-formalcommand. To run riscv-tests, run thetest-dynamiccommand. To check linting, run thelintcommand.
- Recently Accomplished:
- Adding an automated riscv-tests script
- Incorporating riscv-formal for formal verification of my processor.
- Automating tests and verification via GitHub Actions
- In Progress:
- Assembly and C FSM blinker (see
linuxuser314/baremetal-blinkerfor a similar example)
- Assembly and C FSM blinker (see
- Future Plans:
- Pipeline the processor
- Integrate a high performance tightly-coupled memory
- Add a bootloader
- Add exception handling and the Zicsr extension
- Add compressed instructions
- Add multiplication extension
Throughout this process, I have spent many hours debugging weird quirks and trying to get my toolchain to work.
- Learning SystemVerilog: This is my first real SystemVerilog project, so I was getting familiar with the syntax of the language and its various oddities.
- Icarus Verilog Limitations: I had to modify various parts of my code so I could successfully use Icarus Verilog (
iverilog) for simulation, since it does not support full modern SystemVerilog. - RISC-V GNU Toolchain: Overcoming the complications of preprocessors, linker scripts, macros, and more.
- RISC-V Tests: Getting valid Verilog .hex files from assembly using the GNU toolchain and overcoming complex macro headers.
$readmemhand OSS CAD: OSS CAD (specifically Project Apicula (gowin_pack) is apparently finicky when it comes to loading data into dual-ported Block RAM at startup. This caused hours of frustration and the classic "it works in simulation, but not in synthesis" error not because my code was incorrect, but because my toolchain was silently deleting my code and causing my processor to go into an error state when it fetched the first instruction from zeroed-out BRAM.- Toolchain Stress: I had to take a week off because of the stress of trying to build a Python toolchain system. There were so many things going wrong... I wanted to write RTL, not navigate the UNIX tax of OSS EDA tools.
I learned about RISC-V using Sarah and David Harris's Digital Design and Computer Architecture, RISC-V Edition (2022). I used their SystemVerilog references, schematics, and appendices to design this core, expanding where they missed instructions and optimizing for synchronous memory.
Throughout this process I have used Google Gemini AI and GitHub Copilot for brainstorming, debugging, rubber-ducking, improvement suggestion, testbenches, and toolchain management (build scripts, linker scripts, quick assembly tests). 95% of the RTL is my own, but AI proved to be an invaluable tutor.

