Easily build your own SoC
This tutorial takes you through how to easily build your own SoC. We use the hardware IP library provided by OpenTitan because of its high maturity and because I am familiar with the codebase. Our initial SoC takes lowRISC's Ibex core and connects it up to a TileLink bus. This bus connects to an RAM and a UART device. The RAM contains code and data, while the UART is for serial communication.
Create a git repository
In this tutorial we use Git for our repository management.
I'm expecting you to start with an empty repository, but you can adapt the instructions for an existing repository as well.
Instead of manually writing the .gitignore file from scratch we can pull it from the SoC easy build repository that I prepared for this tutorial:
mkdir mysoc
cd mysoc
git init
echo "# My SoC" > README.md
wget https://codeberg.org/marno/soc-easy-build/raw/branch/main/.gitignore
git add README.md .gitignore
git commit -m "My first commit"
After each step, feel free to add your files using git add and committing them. I won't repeat those instructions through this tutorial. You can also push to a remote if you would like to back your repository up, for example:
git remote add origin [email protected]:marno/soc-easy-build.git
git push
Vendoring
There are many ways to include external sources in your project. For example, you can just copy the files or you can use Git submodules. In this tutorial we use vendoring, which is a way of copying files into your repository while still keeping track of where they came from. This flow allows you to easily pull in changes from upstream and also has a useful feature of being able to automatically patch changes that you might need to make. It's also the same flow that OpenTitan uses to pull in external sources, and it's nice to align with that. Let's copy the vendoring utility to your new repository and make it executable:
wget -P util https://github.com/lowRISC/opentitan/raw/205497fb5112fd99105be008913bb1cc71652a6e/util/vendor.py
chmod +x util/vendor.py
To run this script, we need to set up our Python environment:
wget https://codeberg.org/marno/soc-easy-build/raw/branch/main/python-requirements.txt
python -m venv .venv
source .venv/bin/activate
pip install -r python-requirements.txt
Getting the IP
First let's vendor in our CPU, which is Ibex.
To do this we need to tell our vendor script where to find it which I provide for you.
Have a look in lowrisc_ibex.vendor.hjson to see what repository it points to.
wget -P vendor https://codeberg.org/marno/soc-easy-build/raw/branch/main/vendor/lowrisc_ibex.vendor.hjson
cat vendor/lowrisc_ibex.vendor.hjson
git add --all
git commit -m "Commit before vendoring Ibex"
util/vendor.py --commit --update vendor/lowrisc_ibex.vendor.hjson
Then let's get the rest of our IP, this includes the TileLink bus, UART and UART DPI. We need a few patch files because our SoC is much simpler than that of OpenTitan. Luckily we can gather these patch files from other projects.
wget -P vendor https://codeberg.org/marno/soc-easy-build/raw/branch/main/vendor/lowrisc_ip.vendor.hjson
cat vendor/lowrisc_ip.vendor.hjson
wget -P vendor/patches/lowrisc_ip/reggen https://github.com/lowRISC/sonata-system/raw/6e54069435f1279dce83768ae23f00e05568eabb/vendor/patches/lowrisc_ip/reggen/0001-Remove-Integrity-From-Reg-Top.patch
wget -P vendor/patches/lowrisc_ip/tlgen https://github.com/lowRISC/sonata-system/raw/6e54069435f1279dce83768ae23f00e05568eabb/vendor/patches/lowrisc_ip/tlgen/0001-Crossbar-Core-Portability.patch
wget -P vendor/patches/lowrisc_ip/tlul https://github.com/lowRISC/sonata-system/raw/6e54069435f1279dce83768ae23f00e05568eabb/vendor/patches/lowrisc_ip/tlul/0001-Remove-LC-Control-As-TLUL-Dependency.patch
wget -P vendor/patches/lowrisc_ip/tlul https://github.com/lowRISC/sonata-system/raw/6e54069435f1279dce83768ae23f00e05568eabb/vendor/patches/lowrisc_ip/tlul/0002-Tighten-TLUL-Host-Adapter-Prim-Dependency.patch
wget -P vendor/patches/lowrisc_ip/tlul https://codeberg.org/marno/soc-easy-build/raw/branch/main/vendor/patches/lowrisc_ip/tlul/0003-Remove-Integrity.patch
wget -P vendor/patches/lowrisc_ip/uart https://github.com/lowRISC/sonata-system/raw/6e54069435f1279dce83768ae23f00e05568eabb/vendor/patches/lowrisc_ip/uart/0001-Remove-Alerts-And-Integrity.patch
wget -P vendor/patches/lowrisc_ip/uartdpi https://github.com/lowRISC/sonata-system/raw/6e54069435f1279dce83768ae23f00e05568eabb/vendor/patches/lowrisc_ip/uartdpi/0001-Ignore-Write-Errors.patch
wget -P vendor/patches/lowrisc_ip/uartdpi https://github.com/lowRISC/sonata-system/raw/6e54069435f1279dce83768ae23f00e05568eabb/vendor/patches/lowrisc_ip/uartdpi/0002-Simulator-Exit-Condition.patch
wget -P vendor/patches/lowrisc_ip/prim https://codeberg.org/marno/soc-easy-build/raw/branch/main/vendor/patches/lowrisc_ip/prim/0001-Alerts-Removed.patch
git add --all
git commit -m "Commit before vendoring IP"
util/vendor.py --commit --update vendor/lowrisc_ip.vendor.hjson
Have a look in lowrisc_ip.vendor.hjson to see that it points to the OpenTitan repository.
Also, look in the vendor/lowrisc_ip/ip directory and you'll find folders for the TileLink bus and the UART.
Generating the crossbar
Since the memory map is usually different per SoC, we'll need to specify our address map and generate the RTL for it.
We're specifying RAM to be located at 0x0010_0000 and UART at 0x8000_0000.
wget -P util https://codeberg.org/marno/soc-easy-build/raw/branch/main/util/xbar_main.hjson
mkdir -p rtl/autogen
vendor/lowrisc_ip/util/tlgen.py -t util/xbar_main.hjson -o rtl/autogen
rm -r rtl/autogen/dv
mv rtl/autogen/rtl/autogen/* rtl/autogen
Opening up rtl/autogen/tl_main_pkg.sv you can check that the UART and RAM are defined as devices and the Ibex is defined as a host.
Connecting everything up
Now we create a module that instantiates our Ibex core, the generated crossbar and a UART.
Most of this file is about connecting everything up.
Have a look inside the file and find the following blocks: ibex_top_tracing, uart, prim_ram_2p and xbar_main.
wget -P rtl https://codeberg.org/marno/soc-easy-build/raw/branch/main/rtl/soc_mod.sv
Making our simulator top-level
We would really like to simulate our design and we can do that by creating a wrapper that Verilator can understand. The SystemVerilog file instantiates our SoC module and the UART DPI. The C++ file tells Verilator how to run our design including where the memory is to load our ELF file in. Don't feel like you need to understand all of these files.
wget -P dv https://codeberg.org/marno/soc-easy-build/raw/branch/main/dv/top_verilator.sv
wget -P dv https://codeberg.org/marno/soc-easy-build/raw/branch/main/dv/top_verilator.cc
Building our SoC using FuseSoC
FuseSoC is an amazing build system for open-source RTL. To specify what FuseSoC should do you need a core file. To build the simulator, you'll need to install Verilator. Try the following commands:
wget https://codeberg.org/marno/soc-easy-build/raw/branch/main/soc.core
fusesoc --cores-root=. run --target=sim --tool=verilator --setup --build marno:soc:main
This should end by saying "Building simulation model" and then returning without an error.
Running some code
Now let's test our SoC by writing hello world from our Ibex over the bus to the UART. First of all get the RISC-V toolchain from here. Then compile and run the example hello world using the following commands:
wget -P sw https://codeberg.org/marno/soc-easy-build/raw/branch/main/sw/boot.S
wget -P sw https://codeberg.org/marno/soc-easy-build/raw/branch/main/sw/hello_world.c
wget -P sw https://codeberg.org/marno/soc-easy-build/raw/branch/main/sw/link.ld
wget -P sw https://codeberg.org/marno/soc-easy-build/raw/branch/main/sw/build.sh
cat sw/build.sh
chmod +x sw/build.sh
sw/build.sh
build/marno_soc_main_0/sim-verilator/Vtop_verilator -t -E hello_world.elf
After a little while press ctrl + c to exit the simulator. Then let's inspect our UART output:
cat uart0.log
If everything is successful, your log should say "Hello World!" I've created an example repository so you can double check that everything is still working correctly.
Now what?
Congratulations you can now build your own SoC! You can use it as it is and write software that outputs over the UART. For more inspiration: OpenTitan is a full chip design that contains even more IP, including security blocks AES, HMAC and an asymmetric crypto accelerator. There are many more blocks that you can make use of!
An example of a system you can build using this technique is Sonata. This has been the inspiration for this tutorial. Sonata system is an FPGA-based system that integrates many peripherals such as SPI, I2C, GPIO, ADC, USB device, etc.
I hope you've found this tutorial useful and thanks for trying it out!