Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FPGAFlow

Program an FPGA, wait for it to boot, find its UART again, send a command, check the answer, and keep the whole thing as a reproducible run.

FPGAFlow does not implement JTAG. It drives openFPGALoader for programming and pySerial for the UART, and joins those two halves into one workflow.

Requirements

  • Python 3.9 or newer
  • openFPGALoader on PATH, or passed explicitly with --loader /path/to/openFPGALoader
  • A board openFPGALoader supports

Install

git clone git@github.com:delaidam/FPGAFlow.git
cd FPGAFlow
pip install -e .

That installs two commands, fpgaflow and fpgaflow-gui. Check the bench:

fpgaflow doctor

Usage

Every command defaults to the tangnano20k profile. Pass --board for anything else.

Look around

fpgaflow boards          # boards openFPGALoader knows; * marks a local profile
fpgaflow cables          # cables openFPGALoader knows
fpgaflow detect          # serial ports, ranked UART candidates, USB scan
fpgaflow detect --json   # the same, machine-readable

Program

fpgaflow flash top.fs                          # into SRAM, gone on power loss
fpgaflow flash top.fs --target flash --verify  # into flash, persistent

Talk to a design that is already running

fpgaflow send '$1'

Flash & Run

Program, wait for the board to boot, find the UART again, send commands, and write a run directory:

fpgaflow run top.fs '$1'
fpgaflow run top.fs '$1' '$2' --logs runs

Reconnecting after programming

Boards differ in what a fresh bitstream does to the UART. An FT2232 carries JTAG and UART on one USB device that never re-enumerates, so the port stays put and only the design needs time to start. A design that implements its own USB serial instead drops off the bus and comes back as a new device.

FPGAFlow spends the boot delay watching the bus rather than sleeping through it. If the UART disappears, its return is the signal to reconnect and the run continues as soon as it is back, instead of waiting out a delay that was only ever a guess. If it never disappears, the delay is honoured as before. The watching comes out of the delay, so nothing is waited for twice.

--boot-wait sets the upper bound; a profile's reset_delay_s is the default.

fpgaflow run top.fs '$1' --boot-wait 5

Check the answer

--expect takes a regular expression the response must contain. Repeat it to check further commands: the Nth pattern is matched against the response to the Nth command, and commands without a pattern are left unchecked.

fpgaflow run top.fs '$1' --expect 'v1\.2'
fpgaflow run top.fs '$1' '$2' --expect 'v1\.2' --expect '^ok'

Patterns are compiled before the programmer runs, so a typo in a regex cannot leave you with a reprogrammed board and no verdict.

Exit codes

run and send report through their exit status:

Code Meaning
0 Everything ran, and every expectation matched
1 The board answered, but a response did not match its --expect pattern
2 The run could not be completed: bad arguments, programming failed, no UART found

1 and 2 are kept apart on purpose. 1 is a regression in the design, 2 is a problem with the bench, and a script can tell them apart without parsing output:

fpgaflow run top.fs '$1' --expect 'v1\.2'
case $? in
  0) echo "pass" ;;
  1) echo "wrong answer from the design" ;;
  2) echo "bench is broken" ;;
esac

doctor follows the same convention: 0 when the bench is ready, 2 when a check fails.

Run directory

Each fpgaflow run writes runs/<timestamp>/:

File Contents
metadata.json timestamp, OS, board, target, bitstream path, SHA-256 and MD5, baudrate, commands, expectations, and the verdict for each response
programmer.log everything openFPGALoader printed
serial.log each command with its response as text and hex, and whether it matched
result.json success flag, responses, unmet expectations

The bitstream is hashed before it is programmed, so a run directory records exactly which build produced which response.

Hardware discovery

Serial device names are not stable. The same interface is /dev/ttyUSB1 on one machine and COM5 on another, and an FT2232 presents JTAG and UART as two interfaces of a single chip. FPGAFlow ranks the available ports on USB metadata — VID, PID, serial number, location, interface — and selects one only when a single candidate clearly wins. Otherwise it lists the candidates and stops rather than guessing wrong:

fpgaflow detect

When the ranking needs help:

fpgaflow run top.fs '$1' --port /dev/ttyUSB1
fpgaflow run top.fs '$1' --vid 0x0403 --pid 0x6010 --interface 1
fpgaflow run top.fs '$1' --serial BOARD-B

Two boards of the same model

Identical boards share a VID and PID, so USB metadata alone cannot separate them and FPGAFlow stops rather than guessing. --serial names one by its USB serial number, which fpgaflow detect prints for every port.

A serial number identifies one specific board, so it filters rather than ranks: if the named board is not attached, the run fails instead of quietly falling back to another board of the same model. Naming a device with --port overrides it.

Board profiles

openFPGALoader is the source of truth for board and cable names, so FPGAFlow keeps no copy of that list. A local profile only adds what openFPGALoader does not know: UART baudrate, USB matching rules, interface hints, and how long the board takes to boot.

From src/fpgaflow/profiles/boards.json:

{
  "boards": {
    "tangnano20k": {
      "description": "Sipeed Tang Nano 20K / Gowin GW2AR",
      "programmer": { "backend": "openfpgaloader", "board": "tangnano20k" },
      "serial": {
        "vid": "0x0403",
        "pid": "0x6010",
        "interface": 1,
        "baudrate": 115200,
        "line_ending": "lf",
        "reset_delay_s": 2.0
      }
    }
  }
}

A board with no local profile still programs normally; it simply has no UART hints. Point at your own file with --profile-file boards.json.

Only tangnano20k has been checked against real hardware. The others follow from the cable each board uses: every Tang board here drives the same FT2232 as the Tang Nano 20K, and the ULX3S carries a single-channel FT231X. If a profile is wrong for your board, fpgaflow detect shows what is actually attached and --vid, --pid and --interface override the profile:

fpgaflow detect
fpgaflow run top.fs '$1' --board tangnano9k --interface 0

GUI

fpgaflow-gui

Select a bitstream, pick SRAM or flash, fill in an expect pattern if you want one, and run it. The GUI shells out to the same CLI, so there is no second implementation of the programming logic: the EXPECT ... -> match lines and the exit code in its output come from fpgaflow run itself.

Tests

pip install -e ".[dev]"
pytest -q

The suite needs no hardware. Both the programmer backend and pySerial are faked, so the full flash-and-talk workflow is exercised on any machine.

Background

This project started from my own use of openFPGALoader on Linux and the simple fact that I did not want to be bothered with typing the same FPGA programming and UART commands over and over again.

The workflow was always basically the same. I had to choose the correct bitstream, program the FPGA through JTAG, wait for the board to reset, find the correct serial interface, open the UART connection, send a command, and then inspect the response. None of these steps are particularly difficult on their own, but repeating them manually every time quickly becomes annoying and error-prone.

The problem is also that JTAG programming and UART communication are two separate things. openFPGALoader handles the FPGA programming side, while a serial tool is needed to communicate with the design after it has been loaded. FPGAFlow exists to connect those two parts into one workflow. It is not trying to replace openFPGALoader or implement its own JTAG programmer.

The raw UART output is intentional. FPGAFlow does not immediately assume that the response follows the documentation perfectly or that the protocol is implemented exactly as expected. It shows both the decoded text and the raw hexadecimal bytes, which makes it useful for debugging embedded protocols and investigating mismatches between a specification and the actual hardware.

The scope is deliberately limited. FPGAFlow is not a synthesis tool, a place-and-route tool, a Verilog editor, a waveform viewer, or a replacement for Gowin, Vivado, Quartus, or openFPGALoader itself. It is a workflow tool that connects programming, hardware discovery, serial communication, and repeatable testing into one place.

The reason for building it is straightforward: FPGA development should not require manually repeating the same sequence of low-level commands every time a bitstream changes. If a board can be detected, programmed, reconnected, queried, and logged through one consistent workflow, that is already a useful improvement.

License

MIT. See LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages