Library / SDK
YosysHQ/apicula avatar
YosysHQ/apicula

Apicula: an open bitstream toolchain for Gowin FPGAs

Project Apicula 🐝: bitstream documentation for Gowin FPGAs

700 stars111 forksPythonMIT

At a glance

What is it?
Project Apicula is a set of open source Python tools plus documentation for the Gowin FPGA bitstream format, built by fuzzing the vendor binaries rather than by reading a specification. It plugs into Yosys and nextpnr to turn a Verilog design into a file openFPGALoader can write, and it covers a specific list of boards with uneven support.
Who is it for?
Apicula fits a hardware team that wants an open flow for Gowin devices, with Yosys, nextpnr-himbaechel and openFPGALoader on the front and a documented bitstream format underneath. It does not fit a project that needs every device family covered, because the supported board list is fixed and the two newest boards are marked as initial or work in progress.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The bitstream format was reverse engineered by fuzzing the vendor tools

Apicula is a YosysHQ project providing open source tools and documentation for the Gowin FPGA bitstream format, and the method behind it is unusual enough to state first. The project uses a combination of fuzzing and parsing of the vendor data files to provide Python tools for generating bitstreams.

That means the knowledge encoded in this repository was not obtained from a published specification. It was obtained by feeding inputs to the vendor's own programs and watching what came out, and by reading the data files those programs read. The doc folder holds the resulting documentation about the FPGA architecture, the vendor file structure and the bitstream structure, and the README links a wiki for the same purpose.

The audience is two groups at once. The first is the hobbyist or engineer with a cheap Gowin board, mostly from the Sipeed Tang family, who wants an open toolchain instead of a vendor IDE. The second is the reverse engineering and tooling community, since the project publishes the internals of the format rather than only the tool.

The project grew out of an internship, and the README keeps the traces of that: an internship report, presentations at FPT2020 and RC3, livestreams and a set of Jupyter notebooks of the explorations, in condensed form, next to the videos. The funding came through NLnet's NGI0 PET and NGI Zero Entrust programmes.

Four tools make up the flow, and only one of them is Apicula

The getting started section lists what you need, and it is worth noticing that Apicula is one part of a chain rather than a replacement for the others. You need the latest git build of Yosys, the nextpnr-himbaechel fork with its Gowin support, openFPGALoader, and Python 3.9 or higher. Yowasp versions of Yosys and nextpnr are also supported, which matters if you would rather not build the native tools yourself.

Each piece has a distinct job. Yosys reads the Verilog and synthesises it into a JSON netlist with the Gowin cells. nextpnr-himbaechel places and routes that netlist against the device and the board constraints. Apicula packs the placed and routed design into the Gowin bitstream, and openFPGALoader writes the bitstream to the board over USB.

That division of labour is the practical difference between Apicula and the vendor toolchain. The vendor IDE does all of it with a closed binary and a licence, and the open flow does it with components that are individually replaceable.

The Python side of Apicula installs with pip, and the package requires msgspec, numpy and fastcrc, with a pure extra that adds msgpack and cattrs instead. On platforms where the default C extension dependencies are not available, the README gives a way to install the pure Python alternatives.

The tools are installed as console scripts, so the three entry points you get are the packer, the unpacker and a PLL helper.

Building a blinky, from pip install to openFPGALoader

The install is a single pip line.

bash
pip install apycula

If the C extension dependencies will not build on your platform, the pure Python route is documented too.

bash
pip install --no-deps apycula msgpack cattrs

There is one environment wrinkle the README flags: on some systems the installed binaries are not on the path, and running the source files instead leads to import errors. The check is short.

bash
which gowin_pack # check if binaries are on the path
python -m site --user-base # find the site packages base directory
ls $HOME/.local/bin # confirm the binaries are installed in this folder
export PATH="$HOME/.local/bin:$PATH" # add binaries to the path

The blinky build then runs through the four tools in order, and the README presents it as a shell sequence with the device and board as variables.

bash
cd examples
yosys -D LEDS_NR=8 -p "read_verilog blinky.v; synth_gowin -json blinky.json"
DEVICE='GW1NR-UV9QN881C6/I5'  # change to your device
BOARD='tec0117' # change to your board
nextpnr-himbaechel --json blinky.json \
                   --write pnrblinky.json \
                   --device $DEVICE \
                   --vopt cst=$BOARD.cst
gowin_pack -d $DEVICE -o pack.fs pnrblinky.json # chango to your device
openFPGALoader -b $BOARD pack.fs

Two things to notice. The number of LEDs is compiled into the Verilog with a define, and the README gives the count per board, eight for tec0117 and runber, three for honeycomb and tangnano. And the constraint file is chosen by board name through the nextpnr option, so the board selection is a constraint file rather than a board database entry. There is also a Makefile in the examples folder that builds the same thing, and the example set includes a range of DPB and SP video modules, which tells you the intended audience is beyond a single blinky.

The family letter hides in the date code, so the commands diverge per device

The single most confusing part of the documented flow is the device identifier, and the README explains it in a footnote. For C devices you need to pass the --vopt family flag as well as --device to Nextpnr, and you have to state the family in place of the device when passing -d to gowin_pack, because the C is not part of the device ID but is only present in the date code.

So a part number such as GW1NSR-LV4CQN48PC7/I6 carries two pieces of information that the tools need separately, the device and the family, and the tooling takes them in different ways. The Tang Nano 9K is the worked example.

bash
nextpnr-himbaechel --json blinky.json \
                   --write pnrblinky.json \
                   --device $DEVICE \
                   --vopt family=GW1N-9C \
                   --vopt cst=$BOARD.cst
gowin_pack -d GW1N-9C -o pack.fs pnrblinky.json

And the Tang Nano 20K needs the family pushed into the synthesis step instead.

bash
yosys -D LEDS_NR=8 -p "read_verilog blinky.v; synth_gowin -json blinky.json -family gw2a"

The same split exists for the chip database, where the Makefile lists separate entries for a family and its C variant, with GW1N-9C alongside GW1N-9, and GW2A-18C alongside GW2A-18. The C is treated as a first class family everywhere in the tooling, which is consistent with how the silicon is marked but easy to get wrong from the sticker on the board. The README points at the examples Makefile for the correct command per device, which is the file to trust when a variant is not covered in the prose.

gowin_pack, gowin_unpack and gowin_pll are the visible surface of the format

The Python package installs three console scripts, and they map directly onto the three pieces of the format that the project exposes.

python
    'console_scripts': [
        'gowin_pack=apycula.gowin_pack:main',
        'gowin_unpack=apycula.gowin_unpack:main',
        'gowin_pll=apycula.gowin_pll:main',
    ],

The packer turns the placed and routed JSON into the bitstream that goes onto the board. The unpacker goes the other way, and the README shows it commented out in the blinky example, which is the more interesting half for anyone reverse engineering the format, because it turns a bitstream back into something you can read with Yosys.

bash
# gowin_unpack -d $DEVICE -o unpack.v pack.fs
# yosys -p "read_verilog -lib +/gowin/cells_sim.v; clean -purge; show" unpack.v

The round trip is the clearest demonstration of what the project achieved: a bitstream produced by the vendor tools can be unpacked into Verilog and read back, which is the practical definition of having documented the format.

The PLL helper covers the clock configuration part of the device, and it is listed separately because clocking is where the device configuration and the design meet. The vendor data files that all of this reads are shipped with the package as compressed msgpack data, and the Makefile in the repository shows the list of what gets built.

The chip database is generated, so reproducing it needs the vendor tools

The data that makes the tools work is not hand written. Each supported device family has a msgpack file, and the Makefile lists the families explicitly, from GW1N-1 and GW1N-2 through the GW5A and GW5AST parts.

make
all: apycula/GW1N-1.msgpack.xz apycula/GW1N-9.msgpack.xz apycula/GW1N-4.msgpack.xz \
	 apycula/GW1NS-4.msgpack.xz apycula/GW1N-9C.msgpack.xz apycula/GW1NZ-1.msgpack.xz \
	 apycula/GW1N-2.msgpack.xz \
	 apycula/GW2A-18.msgpack.xz apycula/GW2A-18C.msgpack.xz apycula/GW5A-25A.msgpack.xz \
	 apycula/GW5AST-138C.msgpack.xz apycula/GW5AT-60B.msgpack.xz

The rule that builds each file invokes the chipdb builder, and the builder depends on the parsers, so the database is derived from the vendor data files by parsing and fuzzing them. Running the fuzzers needs the vendor tools installed, and the Makefile refuses to run without the GOWINHOME variable pointing at a Gowin EDA Tools installation.

bash
pip install -e .
export GOWINHOME=/gowin/installation
make

The README names a specific version of the vendor tools, 1.9.10.03, and says newer versions may work but have not been tested. That is an important detail for reproducibility: the data files are produced by running vendor binaries, so a different vendor version may produce different parses, and a contributor needs the same version to get the same database.

For contributors who do not want to install the vendor tools locally, the Dockerfile takes the other route. It starts from python:3, downloads the Gowin Linux archive for a version passed in as a build argument, patches the gw_sh binary with a known byte sequence using xxd, installs the Python dependencies and ends with the make command as its default.

Board support is a fixed list, and the newest two boards are marked as rough

The supported boards are listed in full, and the entries carry device identifiers, which is the list to check before buying hardware. It covers the Trenz TEC0117, the Sipeed Tang Nano in 1K, 4K, 9K and 20K variants, the Tang Primer 20K and 25K, the Tang Mega 138K, the Tang Console 60K, the Seeed RUNBER and the Szfpga GW1NR-UV9LQ144PC6/I5.

The footnotes are the interesting part, because they say what is not finished. The Tang Mega 138K is marked as initial support with basic clock functionality and no HCLK or segments, with volunteers welcome. The Tang Console 60K is marked as work in progress, where very simple designs work.

So the support is uneven in a specific way. The older Tang boards and the RUNBER have a mature path, and the two newest boards are at the stage where a blinky is the realistic target. That is a normal shape for reverse engineering work, and it is also a trap for anyone who reads a board list as a compatibility guarantee.

The upstream tools have the same shape. Yosys has to synthesise for the Gowin cell library, nextpnr needs a supported device, and the constraint files are per board. A board that is missing from the list has no constraint file, no database entry and no nextpnr support, and adding one is a research task rather than a configuration change.

MIT licensed, released every couple of months, still documenting itself

The licence is MIT, which is the permissive end of the scale and means the tools and the documentation can be used, modified and redistributed inside a commercial product without a licensing conversation. The package metadata also confirms the operating system independence and the Python 3 only classifier.

The release cadence is steady. Version 0.34 was published on 2026-10-02, 0.33 on 2026-08-25 and 0.32 on 2026-04-07, the repository is not archived, and the last push was on 2026-10-20. The version is still in the zero series, which fits a project whose output is a bitstream format that changes with the silicon.

The documentation is the deliverable that outlives any particular release. The doc folder covers the FPGA architecture, the vendor file structure and the bitstream structure, and the wiki links from the README carry the living version. The examples folder is the other half of the documentation, with a range of DPB and SP video modules in Verilog, which is a working set of designs rather than a single blinky.

The upgrade cost for an adopting team is mostly in the device identifiers, not the code. When the toolchain moves, the family naming can change with it, and the device list in the README is the thing to re-check when you pick up a new release.

Editorial conclusion

Apicula fits a hardware team that wants an open flow for Gowin devices, with Yosys, nextpnr-himbaechel and openFPGALoader on the front and a documented bitstream format underneath. It does not fit a project that needs every device family covered, because the supported board list is fixed and the two newest boards are marked as initial or work in progress. Verify two things before you plan a board around it: that your exact part number appears in the supported list with the right family letter, since the C variants are not part of the device ID and need the extra family flag, and that the vendored tool data for your device was built from a vendor tool version you can reproduce, since the chip database is generated by fuzzing Gowin EDA Tools and the project names one tested version. If you need the commercial Gowin toolchain working today, the vendor path remains the fallback.

Frequently asked questions

What tools do I need to use Apicula with Gowin FPGAs?

The latest git build of Yosys, nextpnr-himbaechel with its Gowin support, openFPGALoader, and Python 3.9 or higher, plus Apicula itself installed with pip. Yowasp builds of Yosys and nextpnr are also supported.

How do I install Apicula without the C extension dependencies?

Install the pure Python alternatives instead with pip install --no-deps apycula msgpack cattrs. The pure extra in the package metadata provides the same msgpack and cattrs dependencies.

What does the family flag do for C variant Gowin devices?

On C devices the C is not part of the device ID and appears only in the date code, so you have to pass the family separately: the --vopt family flag to nextpnr, and the family in place of the device when passing -d to gowin_pack. The examples Makefile has the correct command for each device.

Can I unpack a Gowin bitstream with Apicula?

Yes. The gowin_unpack script takes a packed bitstream and writes it back out, and the README shows the flow of unpacking to a Verilog file and then reading it in Yosys with the Gowin cells library.

What do I need to build the Apicula chip database from scratch?

A checkout of the repository, the Gowin vendor EDA tools, and the GOWINHOME variable pointing at that installation, with version 1.9.10.03 named as the tested version. The Makefile builds the per family msgpack data files, and a Dockerfile is provided to run the fuzzers in a container instead.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. YosysHQ/apicula on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yosyshq-apicula.svg)](https://hysenlabs.com/projects/yosyshq-apicula)

Was this useful?