Skip to content

Tutorial 1 - LiteX in Simulation

Goal

By the end of this tutorial, you should be able to: install LiteX, build a small RISC-V SoC (CPU + UART), boot it in Verilator-based simulation, and run two programs on it - a pre-built example, and a small C program you write yourself - entirely in simulation, before touching any hardware.

Working in simulation first (same principle as Lab 2's Verilator testbench) lets you debug the software/toolchain flow without the extra variables a real board introduces (USB drivers, pin mapping, physical timing).

Background: what LiteX gives you for free

Your Project 1-2 core is built one instruction, one peripheral, at a time. LiteX takes the opposite approach: it is a SoC generator - a Python library (built on Migen, a hardware-description DSL embedded in Python) that assembles a working system from existing, tested components:

  • A RISC-V CPU (this tutorial uses VexRiscv, LiteX's most common default softcore).
  • A Wishbone-based interconnect connecting the CPU to memory and peripherals - conceptually the same idea as the memory-mapped bus you build in Project 2, just already built and tested.
  • Standard peripherals (UART, timer, GPIO) with C driver headers auto-generated from the SoC's actual register map (no hand-written memory map like Project 2's - LiteX generates csr.h for you from the Python SoC description).
  • A BIOS that boots over the UART, prints a banner, and can load and run a separate firmware image sent over serial - this is what "boots" when you power on a LiteX design, before your own program runs.

1. Install LiteX

LiteX is installed via its own bootstrap script (not a package manager package - it clones several repositories: LiteX itself, litex-boards, Migen, and the VexRiscv CPU sources):

1
2
3
4
mkdir -p ~/litex && cd ~/litex
wget https://raw.githubusercontent.com/enjoy-digital/litex/master/litex_setup.py
chmod +x litex_setup.py
./litex_setup.py --init --install --user --config=standard

This installs LiteX and its dependencies into your user Python environment. Note: the rest of this course uses uv for all Python tooling (see Toolchain) - litex_setup.py does not use uv natively. Check with the instructor whether a uv-compatible install path is documented by the time you read this; if not, an isolated venv/pipx install (not a bare system-wide pip install) is the safer fallback.

Confirm the install:

python3 -c "import litex; print(litex.__file__)"
litex_sim --help

2. Confirm your RISC-V cross-compiler works - and pin it explicitly

LiteX needs a RISC-V GCC toolchain to compile firmware. You likely already have one from Lab 2/Project 1 (riscv-none-elf-gcc, xPack). Check it's on your PATH:

riscv-none-elf-gcc --version

This check alone isn't enough. LiteX auto-detects a toolchain by trying a fixed list of common prefixes (riscv64-unknown-elf-, riscv-none-elf-, and others) and silently uses the first one it finds on your PATH - if you have more than one RISC-V GCC installed (easy to end up with, e.g. one from Homebrew and one from xPack), LiteX may pick a different one than you expect, with no warning. Pin it explicitly instead:

export LITEX_ENV_CC_TRIPLE=riscv-none-elf

This forces LiteX to use the xPack toolchain you already have from Lab 2 - no second RISC-V GCC install needed for this track.

If you activated OSS CAD Suite via its environment/activate script (rather than the plain PATH export Lab 2 recommends), double-check python3 still resolves to the Python environment where you installed LiteX (step 1) - that script bundles its own Python, which can shadow the one with litex installed if its bin/ directory ends up ahead of your Python on PATH. If litex_sim --help (step 1) stops working after activating OSS CAD Suite, this is the first thing to check: make sure your LiteX-capable Python resolves before OSS CAD Suite's bin/ directory on PATH, not after.

3. Build and boot a SoC in simulation

LiteX ships a generic simulation target that doesn't require any specific board:

python3 -m litex.tools.litex_sim --cpu-type=vexriscv --cpu-variant=standard

This should: elaborate the SoC in Migen, generate Verilog, compile it with Verilator (the same simulator you used in Lab 2, driven by LiteX instead of your own testbench), and boot it. You should see the LiteX BIOS banner print directly in your terminal, followed by a boot sequence and a litex> prompt.

This BIOS is your first "ready-made example program" - explore it a little:

litex> help
litex> reboot

4. Write and run your own program

Now replace the BIOS's default behavior with a small C program of your own. LiteX's build process generates a software/ directory containing csr.h (register definitions for every peripheral in your SoC) and a demo main.c template you can start from.

Write a minimal program (a "hello world" is enough) using LiteX's libbase for serial output:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <libbase/uart.h>
#include <libbase/console.h>

int main(void) {
    printf("Hello from my own firmware!\n");
    while (1) {}
    return 0;
}

(#include <stdio.h> is required for printf - easy to forget since the other two headers look like they'd be enough.)

Build it against the generated headers (LiteX's software Makefile under the build output directory handles the cross-compilation flags).

Loading it into a running simulation needs one extra piece. The plain litex_sim command from step 3 wires the simulated UART directly to your terminal's own stdin/stdout - there's no separate "port" a second tool could attach to, so a bare litex_term --kernel firmware.bin <port> has nothing to point at. Instead, run the simulation with its UART exposed over a TCP socket, using litex_sim_tcp_serial.py (a copy of LiteX's own simulation target with one addition: a --serial-tcp-port option) - download it into your working directory, then:

1
2
3
4
5
6
# Terminal 1 - runs the simulation, keep it running
python3 litex_sim_tcp_serial.py --cpu-type=vexriscv --cpu-variant=standard \
    --serial-tcp-port=1234 --non-interactive

# Terminal 2 - attach independently, any time after terminal 1 is listening
litex_term socket://localhost:1234

If you attach after the simulation has already booted (likely, since it doesn't wait for a client), you won't see the original boot banner - type reboot at the point you'd expect a litex> prompt to trigger a fresh boot you can actually watch. Then load your program the same way as before:

litex_term --kernel firmware.bin socket://localhost:1234

Confirm your own printf output appears in the terminal, replacing the BIOS prompt.

Checklist

  • LiteX installed; litex_sim --help runs.
  • LITEX_ENV_CC_TRIPLE=riscv-none-elf set, confirmed via the build log showing riscv-none-elf-gcc (not a different toolchain) as the compiler used.
  • A VexRiscv-based SoC boots in simulation; the BIOS banner and litex> prompt appear.
  • A new C program you wrote is compiled and loaded into the running simulation via litex_sim_tcp_serial.py + litex_term socket://....
  • Your program's own output (not the BIOS's) is visible in the terminal.

Next

Tutorial 2 repeats both programs - the BIOS and your own - on your real Tang Nano 9K.