Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

36 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tang_computer

English · Українська

A self-contained RISC-V Linux machine built from a Sipeed Tang Primer 20K + Dock — a computer whose CPU is synthesized into the FPGA, with its console on the dock's little LCD and a USB keyboard as its only input.

The machine: Tang Primer 20K, the 4.3" LCD on its ribbon, and the USB keyboard

No hard CPU, no vendor SoC — the processor is a VexRiscv-SMP soft core (rv32ima + MMU) generated by LiteX into a Gowin GW2A-18 fabric. It runs mainline 32-bit RISC-V Linux out of the SOM's 128 MB DDR3, paints a framebuffer console on the 480×272 panel, and takes keystrokes from a USB keyboard plugged into the dock's USB-C OTG port. The last part is the interesting one: the dock's USB3317 PHY is wired for 6-pin ULPI serial mode, so the USB host is driven by a hand-written serial-mode shim in front of a LiteX OHCI core — as far as I can tell, the first working USB host through this dock's PHY.

root@buildroot:~# help
Built-in commands:
    . : [ [[ alias bg break cd chdir command continue echo eval exec
    exit export false fg getopts hash help history jobs kill let ...
root@buildroot:~# uname -m
riscv32

It works

Booted, logged in, running a shell — typed on the USB keyboard, shown on the LCD
LiteX/VexRiscv-SMP banner, buildroot login, and a clean interactive shell on the LCD

The panel is a real Linux virtual terminal: fbcon over a simple-framebuffer, a getty login, and the Tux boot logo. The keyboard enumerates as a normal USB HID device through the custom host and feeds the console:

Tux logo and the USB keyboard enumerating on the panel

Before / after

Most of the effort was in two places: getting DDR3 + the framebuffer to coexist without starving the CPU, and getting the panel to actually show Linux rather than raw memory. During bring-up I drove the debug loop with a webcam pointed at the LCD:

Before — gateware scanning raw DRAM (wrong/absent DTB) After — Linux framebuffer console
garbage vertical comb on the panel Tux logo and kernel boot console

The "before" is the scanout faithfully displaying uninitialized DRAM: the display path was fine, but Linux hadn't been told about the framebuffer (the SD held a DTB from a USB-only build with no simple-framebuffer node). Pairing the bitstream with its matching device tree fixed it.


How it works

System architecture: VexRiscv-SMP over a Wishbone bus to LiteDRAM/GW2DDRPHY (DDR3), the custom LCDFramebuffer scanout + VideoLCDPHY (panel), and a LiteX OHCI host through the custom ULPI serial-mode shim to the USB3317 PHY; booting from an SD card

  • CPU / SoC — VexRiscv-SMP rv32ima+MMU (single core, coherent DMA), assembled by LiteX.
  • DRAM — the SOM's 128 MB DDR3 via LiteDRAM + GW2DDRPHY. This is why the build uses the Gowin toolchain: the open apicula flow has no DQS/DLL primitives, so it cannot build the DDR3 PHY.
  • Display — a custom scanout (gateware/lcd.py) reads RGB565 from DRAM over a Wishbone DMA master and drives the panel through a VideoLCDPHY (DCLK inverted so the panel latches mid-eye, per the sibling tang_monitor). Linux sees it as a simple-framebuffer; fbcon gives it a console.
  • USB host — a LiteX/SpinalHDL OHCI core at 0xc0000000 (IRQ 16, DMA master), driven in Linux by the stock ohci-platform driver. Between OHCI and the USB3317 PHY sits the novel part (gateware/usb_host.py): an init FSM that walks the PHY into 6-pin serial mode by writing its ULPI Function/OTG/Interface-Control registers, plus a combinational shim that maps OHCI's dp/dm drive/receive onto the PHY's serial pins. No custom kernel driver — Linux thinks it's an ordinary OHCI controller.
  • Boot — LiteX BIOS → sdcardboot reads boot.json from the FAT card → OpenSBI → Linux → Buildroot initramfs (root=/dev/ram0). Console on both the LCD (console=tty0) and the BL702 UART.

See docs/pinmap.md for the audited LCD + ULPI ball map (the ULPI map was recovered from the dock schematic and cross-checked against Sipeed's own USB example), and docs/usb3317.md for the serial-mode register sequence.

What's novel here

The individual pieces are open source — LiteX, VexRiscv, a LiteX OHCI core, the USB3317 PHY. What this project puts together, and what (as far as I can find) hasn't been shown before, is:

  • A USB host through the Tang Primer 20K dock's USB3317 PHY. The dock wires the PHY for the 6-pin ULPI FS/LS serial mode (only the serial data/SE0 lines are routed, not the full 8-bit ULPI bus), and Sipeed's own examples use it only as a USB device. Here it runs as a host: a small init FSM walks the PHY into serial mode over ULPI, and a combinational shim maps a stock LiteX OHCI core's dp/dm onto the PHY's serial pins. Linux drives it with the in-tree ohci-platform driver — no out-of-tree kernel code.
  • 6-pin serial mode, between the two usual approaches. FPGA USB hosts normally either speak the full 8-bit ULPI protocol or bit-bang raw D+/D−. This sits in between: the PHY does the analog FS/LS signaling and the fabric only has to bridge OHCI to six pins.
  • The whole machine from open tooling. A CPU you synthesize yourself, running mainline 32-bit RISC-V Linux, with its console on the dock's LCD and a USB keyboard as input — no hard CPU, no vendor SoC. (The one closed piece is DDR3: the open apicula flow has no DQS/DLL primitives, so the DDR3 PHY is built with Gowin EDA.)
  • A reproducible bring-up trail. Every milestone was verified on real hardware and logged — including the bug that made it work: the serial-mode ULPI register write was missing its terminating STP, found by synthesizing a LiteScope logic analyzer directly onto the ULPI wire.

The build, milestone by milestone

Each step was verified on hardware; the gory details and dead-ends are in docs/bringup_log.md.

# Milestone The hard part
M0 Toolchain + DDR3 memtest apicula can't build GW2DDRPHY → Gowin EDA runs headless in Docker under Rosetta.
M1 Custom SoC boots the LiteX BIOS Reuse the board's CRG; add LCD + USB clock domains.
M2 Linux to serial console from SD DRAM aliased at +128 MiB (1 Gbit chip); OpenSBI hardcodes the UART at 0xf0001000.
M3 LCD framebuffer console Native scanout DMA deadlocked the CPU on the DRAM crossbar → switched to a Wishbone fetch at a lower pixel clock.
M4 ULPI register access to the USB3317 Confirmed PHY vendor ID 0x0424 and the serial-mode register bits over the BIOS console.
M5 USB keyboard types into Linux The serial-mode register write was missing its terminating STP — found by synthesizing a LiteScope logic analyzer onto the ULPI wire. A free-running RESETB pulse recovers the PHY from a stuck SE1 state.

The turning point at M5 was LiteScope: register polling could never have shown that the PHY never committed the serial-mode switch because the ULPI write never asserted STP. With that fixed, a Keychron K3 Pro enumerated and typed hello into /dev/input/event0.

Hardware setup

  • Dock switch SW2host position (straps the USB3317 ID pin; host mode is otherwise pure gateware).
  • Keyboard on a USB-C(male) → USB-A(female) OTG adapter into the dock's OTG port. VBUS is hardwired to +5 V on the dock, so no power bodge is needed.
  • Dock DIP switch #1 DOWN for SRAM (JTAG) configuration.
  • microSD in the SOM slot.

Build & flash

. ./env.sh                 # PATH: oss-cad-suite, openFPGALoader, LiteX venv, riscv gcc
make doctor                # sanity-check tools + board

# 1. Gateware — the full SoC (VexRiscv-SMP + framebuffer + USB host). Uses the Gowin
#    toolchain (DDR3); runs in Docker/Rosetta via scripts/gw_sh.
make soc-full              # -> build/soc-full/gateware/sipeed_tang_primer_20k.fs

# 2. Linux — kernel + OpenSBI + Buildroot rootfs, built in a container.
linux/build_linux.sh       # -> linux/out/{Image,opensbi.bin,rootfs.cpio}

# 3. Device tree — adds console=tty0 + loglevel=3, then compiles.
make dtb                   # soc/make_dtb.sh build/soc-full -> images/rv32.dtb

# 4. SD image, then write it to a card (dd the image to your reader).
make sd                    # -> linux/out/sdcard.img

# 5. Configure the FPGA over JTAG and open the console.
make load-full             # SRAM-load the bitstream (DIP #1 DOWN)
make term                  # serial console

Updating just the DTB / kernel on a card that's still in the SOM: scripts/push_to_sd.sh <file> (ZMODEM over the console) or scripts/push_to_sd.py <file> rv32.dtb (base64, no host deps).

Repository layout

gateware/          LiteX SoC + the custom gateware
  target.py          SoC top / build target (--with-video-fb, --with-usb-host, ...)
  lcd.py             LCD scanout (Wishbone DMA) + VideoLCDPHY (inverted DCLK)
  usb_host.py        USB3317 ULPI 6-pin serial-mode shim + OHCI integration  ← the novel part
  ulpi.py            ULPI register-access core (M4 PoC + init FSM)
  tc_ios.py          audited LCD + ULPI pin map
soc/make_dtb.sh    generate + patch (console=tty0, loglevel=3) + compile the DTB
linux/             Buildroot defconfig, rootfs overlay (panel getty), build driver, Dockerfiles
scripts/           board.py, lxterm, make_sd_image.sh, push_to_sd.*, panel_photo.sh, ulpi_probe.py
  diagnostics/       one-off serial/VT bring-up scripts (reference only)
docs/              pinmap.md, usb3317.md, bringup_log.md, images/
Makefile env.sh

Known limitations / future work

  • USB typing reliability under load. Typing is clean on a fresh boot, but heavy framebuffer DMA competes with the OHCI controller for DRAM bandwidth (boot is noticeably slower with the panel active than in the USB-only build). Giving the OHCI DMA higher crossbar priority — or adding a line buffer so the framebuffer bursts less often — is the next gateware step. A powered USB hub also helps in the meantime.
  • The bitstream is volatile. It's SRAM-loaded over JTAG (make load-full), so a cold power cycle loses it. Writing it to the dock's SPI config flash (openFPGALoader -f, boot-mode DIP in the flash position) would make the machine boot to Linux on power-up with no laptop.
  • Boot is slow (a few minutes) — a direct consequence of the framebuffer sharing DRAM with the CPU.

Credits

Built on LiteX, VexRiscv, linux-on-litex-vexriscv, and the open FPGA toolchain (yosys, nextpnr, apicula). The audited LCD pin map and DCLK-inversion trick come from the sibling project tang_monitor. DDR3 gateware is built with Gowin EDA (Education).

License

MIT.

About

Self-contained RISC-V Linux machine on a Sipeed Tang Primer 20K FPGA — a VexRiscv-SMP soft CPU (LiteX) running mainline 32-bit Linux, with an LCD framebuffer console and a USB keyboard driven through the dock's USB3317 PHY in 6-pin ULPI serial mode.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages