A simple custom instruction for the vexriscv, deploy- and debuggable on a ulx3s board.
You got yourself a ULX3S FPGA Board and want to try out
- building a Vexriscv from SpinalHDL.
- including a custom instruction for the Vexriscv.
- generate the bitstream for the ULX3S.
- upload the bitstream to the ULX3S.
- build a C-Project to test the custom instruction.
- upload the ELF-file via a JTAG-Adapter, using GDB.
- seeing the UART output in a console.
That all is include in this Repository.
-
The neccessary tools are contained in this Virtual Machine (VM):
Download the VM and follow the documentation for starting it up.
-
An ULX3S FPGA Board (85k Version):
-
A JTAG Adapter (TF232H) from Adafruit:
Wire the ULX3S FPGA Board and the TF232H together with 5 jumper cables:

Your first step must be to start up the Virtual Machine. After starting the VM, the rest of this tutorial happens completely inside the VM.
Clone this Repositoy to a place of your choice inside the VM via:
git clone https://github.com/ThorKn/vexriscv-ulx3s-simple-plugin.git
The repository contains three main folders:
|-- vexriscv
|-- ulx3s
|-- c_project
|-- images
vexriscv contains the spinalHDL code for generating the Vexriscv CPU as a single Verilog file, named Murax.v. The custom instruction (a Vexriscv plugin inside the pipeline) is also already contained in Murax.v.
ulx3s contains the makefile and the constraint file ulx3s_v20_constraints.lpf for building the bitstream out of the Verilog. The bitstream will be named Murax.bit and can directly be uploaded to the ULX3S Board.
c_project contains the whole C project to build the example ELF file simple_plugin.elf to interact with the custom instruction inside the Vexriscv. This ELF file can be uploaded to the Vexriscv (on the ULX3S Board) via Openocd and Riscv-GDB.
images contains the pictures for the readme.
We'll start with the Vexriscv in spinalHDL and generate the Verilog from it:
cd vexriscv
sbt "runMain vexriscv.demo.Murax"
Then copy the Verilog file Murax.v to the directory ulx3s and leave the directory:
cp Murax.v ../ulx3s/
cp cpu0.yaml ../c_project/
cd ..
Get into the ulx3s directory and build the bitstream:
cd ulx3s
make Murax.bit
The bitstream got generated as the file Murax.bit. This bitstream file can now be uploaded to the ULX3S FPGA board via:
ujprog Murax.bit
It might be, that you have to execute ujprog as sudo.
After programming the FPGA, leave the directory:
cd ..
Get into the c_project directory and build the ELF-file:
cd c_project
make
The ELF-file simple_plugin.elf got generated inside the build directory.
The next step is to connect the TF232H adapter to the Vexriscv via openocd-vexriscv (sudo needed):
sudo openocd-vexriscv -f ft2323h_openocd.cfg
This should give you the console output of connecting openocd to the vexriscv. After connecting openocd should listen for a GDB connection on port 3333 (search for this in the console output).
After connecting openocd you must open up a new console window (the last one is idle now) and navigate to the build folder:
cd c_project/build
Now you can upload the ELF-file simple_plugin.elf to the vexriscv by using the Riscv-GDB:
riscv64-unknown-elf-gdb -tui -ex 'set remotetimeout 10' -ex 'target remote :3333' -ex load -ex 'break main.c:43' simple_plugin.elf
Now the Vexriscv is running on the FPGA and the compiled c-code is loaded into the Vexriscv CPU. To see the results of the execution of the custom instruction, you must open up a serial connection to the Vexriscv. Therefore we'll use GTKTerm. Again you need to open up a new console window for that (as the last one is idle again). Then start GTKTerm (sudo needed):
sudo gtkterm
Configure GTKTerm for the fitting ttyUSBX Interface (mostly ttyUSB0 or ttyUSB1) and set the protocol to 115200 - 8 - N - 1. Additional you want to see the output as ASCII.
When you step through the c-program with using the GDB (c is for continue), you can see the printouts of the Vexriscv in GTKTerm.
That is it.
Todo