Skip to content

Tutorial 2 - LiteX on Real Hardware

Goal

By the end of this tutorial, you should be able to: build the same SoC from Tutorial 1 targeting your real Tang Nano 9K, synthesize and load it with the same OSS CAD Suite tools from Lab 2, and run both the pre-built BIOS and your own C program on physical hardware, talking to the board over its USB-serial connection.

Prerequisites

  • Tutorial 1 completed (LiteX installed, comfortable with the simulation flow).
  • Tang Nano 9K connected via USB-C, detected by openFPGALoader --detect (per Lab 2).
  • Your Lab 2 constraint file conventions are not reused here - LiteX's board target (below) already knows the Tang Nano 9K's pinout.

1. Build for the real board

litex-boards (installed alongside LiteX in Tutorial 1) includes a target definition for the Sipeed Tang Nano 9K:

1
2
3
4
python3 -m litex_boards.targets.sipeed_tang_nano_9k \
    --cpu-type=vexriscv --cpu-variant=standard \
    --toolchain=apicula --integrated-main-ram-size=0x2000 \
    --build --load

Two flags here are not optional, unlike what you might guess from LiteX's own --help output:

  • --toolchain=apicula is required. The board target's default toolchain (gowin) invokes the proprietary Gowin IDE's own TCL shell (gw_sh) to run synthesis - not part of this course's toolchain, and not installed. apicula is LiteX's name for the open-source path: it resolves to nextpnr-himbaechel, the same tool Lab 2 already has you using. Confirm this in the build log (nextpnr-himbaechel should appear, not gw_sh).
  • --integrated-main-ram-size=0x2000 avoids an unpinned download. Leaving this unset makes the board target fall back to configuring the Tang Nano 9K's external PSRAM (HyperRAM) - which, as of this writing, triggers an unauthenticated wget of a Python file from a GitHub issue attachment as part of the build, and executes it. 0x2000 (8 KB) puts main_ram on-chip (BRAM) instead, sidestepping that path entirely - increase it if your program needs more RAM than that, but always set it explicitly rather than leaving PSRAM as an implicit default.

  • --build runs the full flow: Migen elaboration → Verilog generation → Yosys synthesis → nextpnr-himbaechel place & route → gowin_pack bitstream - the same tools from Lab 2, just invoked by LiteX instead of by hand.

  • --load uploads the resulting bitstream via openFPGALoader, same as Lab 2's manual step.

2. Connect and boot the BIOS on real hardware

Same idea as Tutorial 1's simulated UART, now over the real USB-serial connection:

litex_term /dev/ttyUSBx   # or the equivalent port name on your OS

Power-cycle or reset the board; you should see the same LiteX BIOS banner and litex> prompt as in simulation, now printed from real hardware. If the board is already running (you attached after it booted, rather than catching the very first boot), it won't spontaneously reprint its banner - type reboot at the point you'd expect a litex> prompt.

If --load fails with an openFPGALoader/cable error, check for a stray litex_term process still holding the USB device open from an earlier attempt (pkill -f litex_term, then retry) before assuming a hardware problem - openFPGALoader and litex_term both need exclusive access to the same USB-serial adapter.

3. Load and run your own program

Reuse the C program from Tutorial 1 - the point of building it in simulation first is that it should need no code changes to run on real hardware, only a different load step:

litex_term --kernel firmware.bin /dev/ttyUSBx

Confirm your program's printf output appears over the real serial connection. If it doesn't, compare carefully against Tutorial 1's simulation behavior - a mismatch between simulation and hardware here is exactly the kind of discrepancy worth debugging methodically (same principle as Project 1's testbench-vs-hardware validation).

4. Optional: observe something physical

For a more satisfying confirmation than serial text, extend your program to toggle one of the board's LEDs via its CSR-mapped GPIO register (LiteX's generated csr.h will include this if your SoC build included a GPIO peripheral) - similar in spirit to Lab 2's blink exercise, but through LiteX's abstraction instead of a hand-written register.

Checklist

  • SoC built and loaded onto the Tang Nano 9K via --toolchain=apicula --integrated-main-ram-size=0x2000 --build --load.
  • Confirmed the build log shows nextpnr-himbaechel (OSS CAD Suite), not gw_sh (proprietary Gowin IDE).
  • BIOS banner and prompt visible over the real serial connection.
  • Your own program from Tutorial 1 runs unmodified on real hardware, with observable output.

Next

Tutorial 3 adds a peripheral you write yourself, in SystemVerilog, onto this same SoC's Wishbone bus.