Skip to main content

qmtech-artix-7-workspace: a reproducible build and verification pipeline for the QMTECH Artix-7

·718 words·4 mins
Mohamed Ihab Eddine ARDJOUNE
Author
Mohamed Ihab Eddine ARDJOUNE
Also published as M. I. E. ARDJOUNE / mieardjoune. FPGA engineer and ASIC designer specializing in cryptographic hardware, hardware security, open-source silicon drivers and frameworks.

mieardjoune/qmtech-artix-7-workspace · A containerized build and verification workspace for the QMTECH Artix-7 (xc7a100tfgg676-1) · VHDL, SystemVerilog, Tcl, Bash

The QMTECH Artix-7 is cheap, and has a lot of logic cells for that price, and comes with none of the tooling to go with it. You get a chip, a JTAG header, and silence. No reference build flow, no CI story, nothing that tells you whether your bitstream is going to work before you find out the hard way on hardware. So this repo is that missing piece: one Makefile, four stages, same flow whether the design underneath is eight lines of VHDL or something bigger.

qmtech-artix-7-workspace build pipeline

Every project is a self-contained bundle: src/, tb/, constraints/, a TOP file naming the top module, and an optional params.txt for generics. Point the Makefile at one with PRJ= and it runs simulation, synthesis, gate-level timing, and deployment, in that order, without you having to remember which tool wants which flag this week.

Language isolation
#

Two example projects live here for now: ram_test, a synchronous single-port RAM in VHDL that can pre-load itself from mem_content.txt, and sv_test, a SystemVerilog LED blink-rate divider. Neither is exciting. That’s deliberate, this repo is about the flow, the RTL is just along for the ride.

The Makefile enforces one rule GHDL and Icarus don’t enforce themselves: a project can be VHDL or SystemVerilog, never both. Mix languages in one src/ directory and sim refuses to run, with an error telling you to split the project, instead of letting an open-source simulator quietly choke on a mixed-language elaboration. Vivado doesn’t share this problem and reads both without complaint, so the restriction only applies to the open-source simulation path.

The tool’s bug, not the design’s
#

Gate-level simulation is the step that actually earns its keep: synthesize and implement in Vivado, export a post-route netlist with write_verilog -mode timesim -sdf_anno true, then re-run the testbench against that netlist in XSIM with the SDF file applied, so timing reflects the routed design instead of RTL’s zero-delay fiction.

Here’s the part nobody puts in the manual. The Vivado container image has a broken OS-string lookup, and write_verilog’s auto-generated header ends up splicing in raw, uncommented /etc/os-release content: bare KEY="value" lines, no // in front of them. XVLOG reads that as Verilog syntax it’s never seen and refuses to compile. The fix is a one-line sed that strips any bare KEY="value"-shaped line from the first 20 lines of the netlist before it ever reaches the compiler. Your design didn’t do anything wrong here, the tool’s own header generation did, and it’s a very specific afternoon to lose if you don’t already know to look for it.

Getting bits onto actual silicon
#

Deployment goes over JTAG via xc3sprog, not openFPGALoader. The Xilinx Platform Cable USB II (DLC10) has no openFPGALoader driver, but xc3sprog knows it under the cable name xpc. The DLC10 also needs its FX2 firmware loaded before Linux enumerates it as the right device: lsusb shows 03fd:0013 fresh out of the box, and should flip to 03fd:0008 once the firmware and udev rule are in place. If deploy fails and lsusb still says 0013, that’s a driver problem wearing a JTAG costume, not an actual JTAG chain issue.

Worth sitting with for a second: an unrestricted JTAG port is also an unrestricted way onto the chip. Convenient for me right now, and a problem I’m choosing to come back to on purpose rather than by accident.

CI, so the board isn’t the test bench
#

.github/workflows/fpga-ci.yml runs simbuildsim-gate on a self-hosted runner, for every project, on every push to main, and archives the bitstream, waveform, and reports as artifacts. GitHub-hosted runners are out for the obvious reasons, Vivado licensing and an actual JTAG cable don’t live in the cloud, so the runner sits on the same machine as the board. A broken constraint or a timing regression now shows up as a red X, not as a board that mysteriously stopped working three weeks after I forgot what I changed.

What’s next
#

The next project through this workspace is a framework. That’s the whole hint.

To run this one yourself: clone the repo, install GHDL/Icarus, Docker, and Vivado 2025.2, then

make sim PRJ=ram_test
make build PRJ=ram_test
make sim-gate PRJ=ram_test
make deploy PRJ=ram_test

MIT licensed.