BL616 DirtyJTAG

February 26, 2026 · View on GitHub

Open-source replacement JTAG/UART firmware for the Tang Nano 20K's onboard BL616 MCU.

Provides a DirtyJTAG v2 compatible JTAG interface alongside a CDC-ACM UART bridge as a composite USB device. Works with openFPGALoader out of the box.

Why?

The Tang Nano 20K ships with Sipeed's proprietary BL616 firmware that emulates an FTDI FT2232. This firmware has known reliability issues with JTAG programming. Since the BL616 has eFuse secure boot enabled at the factory, you cannot simply replace the firmware at address 0x0.

This project works around the limitation by using Sipeed's partner firmware at flash address 0x0 (which handles secure boot) and installs DirtyJTAG at address 0x40000. The partner firmware hands off to the custom firmware when no USB host is initially detected at boot.

Features

  • DirtyJTAG v2 protocol — compatible with openFPGALoader (-c dirtyJtag)
  • CDC-ACM UART bridge — serial port on UART1 (GPIO11 TX, GPIO13 RX) with configurable baud rate
  • Composite USB device — JTAG and UART available simultaneously
  • Minimal footprint — ~76 KB firmware, runs on FreeRTOS

Hardware

Pin assignments

FunctionGPIOBL616 Pin
JTAG TMSGPIO16To FPGA
JTAG TCKGPIO10To FPGA
JTAG TDIGPIO12To FPGA
JTAG TDOGPIO14To FPGA
UART1 TXGPIO11To FPGA
UART1 RXGPIO13To FPGA

USB identity

FieldValue
VID0x1209
PID0xC0CA
ProductDirtyJTAG

Registered via pid.codes (the DirtyJTAG project allocation).

Prerequisites

  • Bouffalo SDK (tested with v2.3.16)
  • Sipeed partner firmware binary: bl616_fpga_partner_20kNano.bin
    • Download from Sipeed
    • Place in the project root directory

Building

Clone the SDK (or symlink) into the project directory:

git clone https://github.com/bouffalolab/bouffalo_sdk.git
# or: ln -s /path/to/bouffalo_sdk .

Build:

export PATH=$(pwd)/bouffalo_sdk/tools/toolchain_gcc_t-head_linux/bin:$PATH
make CHIP=bl616 BOARD=bl616dk

Flashing

The flash configuration (flash_prog_cfg.ini) writes two images:

  1. Sipeed partner firmware at 0x0 — encrypted first-stage that handles secure boot
  2. DirtyJTAG firmware at 0x40000 — this project's firmware

To flash:

  1. Hold the BOOT button (next to the HDMI connector) while plugging in USB
  2. The BL616 will appear as Bouffalo CDC DEMO on a virtual COM port
  3. Run:
make flash CHIP=bl616 COMX=/dev/ttyACM0

Boot sequence

The Tang Nano 20K's BL616 has eFuse secure boot enabled by Sipeed at the factory. This means:

  1. The ROM bootloader only executes AES-128 encrypted firmware at flash address 0x0
  2. The partner firmware at 0x0 runs first
  3. If no USB host is detected at boot, the partner firmware loads and executes the firmware at 0x40000
  4. If USB is detected, the partner firmware stays in FTDI bridge mode

Important: power-on sequence

Because the handoff only occurs when no USB is initially detected, you need to:

  1. Power the Tang Nano 20K without a USB data connection (e.g., power-only cable, external supply, or USB hub with data lines disconnected)
  2. After the DirtyJTAG firmware boots, connect the USB data lines
  3. The device will enumerate as 1209:c0ca DirtyJTAG

Alternatively, if the FPGA is configured to not pull the USB detect lines, the handoff may occur automatically. The exact detection mechanism is internal to Sipeed's encrypted partner firmware.

Usage

JTAG (openFPGALoader)

# Detect FPGA
openFPGALoader -c dirtyJtag --detect

# Program bitstream
openFPGALoader -c dirtyJtag -b tangnano20k bitstream.fs

# Program to flash
openFPGALoader -c dirtyJtag -b tangnano20k -f bitstream.fs

UART

The CDC-ACM interface appears as /dev/ttyACMx. Connect with any serial terminal:

picocom /dev/ttyACM1 -b 115200

The baud rate is configurable via the standard CDC SET_LINE_CODING request (most terminal programs handle this automatically).

Project structure

├── Makefile              # SDK build system entry point
├── CMakeLists.txt        # Project config (FreeRTOS + CherryUSB)
├── FreeRTOSConfig.h      # FreeRTOS configuration
├── usb_config.h          # CherryUSB device configuration
├── flash_prog_cfg.ini    # Flash programming layout
└── src/
    ├── main.c            # Entry point, USB + FreeRTOS init
    ├── usb_descriptors.c # Composite USB descriptors
    ├── dirtyjtag.c/h     # DirtyJTAG v2 protocol handler
    ├── jtag_gpio.c/h     # GPIO bit-bang JTAG engine
    └── uart_bridge.c/h   # CDC-ACM to UART1 bridge

Known limitations

  • The partner firmware handoff requires initial boot without USB data connection
  • Bulk endpoint max packet size is 64 bytes (openFPGALoader expects this, but Linux logs warnings about HS compliance)
  • JTAG frequency control is approximate (delay loop calibration)
  • No TRST/SRST support (not connected on Tang Nano 20K)

Background: Tang Nano 20K secure boot

Starting around early 2024, Sipeed began shipping Tang Nano 20K boards with eFuse secure boot enabled on the BL616 MCU. The eFuses contain an AES key and the secure boot flag is permanently blown, meaning:

  • The ROM bootloader at address 0x0 only accepts encrypted firmware
  • You cannot flash custom unencrypted firmware directly
  • The encryption key is not publicly available

The partner firmware (bl616_fpga_partner_20kNano.bin) is a Sipeed-signed encrypted binary that:

  • Provides FTDI FT2232 emulation for JTAG/UART (the factory default behavior)
  • Supports loading a secondary unencrypted firmware from flash address 0x40000
  • Decides at boot whether to run as FTDI bridge (USB detected) or hand off to 0x40000 (standalone)

For more details, see:

Credits

License

MIT