Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/vivado-bitstream.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Reusable workflow: build an FPGA bitstream on a remote BuildKit builder inside
# the Zubax toolchain container. A consuming repo calls it from a tiny
# workflow_dispatch workflow and runs it locally with `act`.
#
# BuildKit variant of the remote build: no ssh/rsync — buildx talks to buildkitd
# on the build host, which reuses the mega base from its containerd store (no
# re-pull). Synthesis only; board programming stays in the project.
#
# No `actions/checkout`: these are act-only (LAN build host), so they rely on
# `act --bind`, which mounts the real project dir as the workspace — and keeps
# locally-generated, gitignored inputs present. Run any codegen locally BEFORE act.
#
# NOTE (additional path): the remote builder points at an INSECURE buildkitd
# (LAN, no TLS). If docker/setup-buildx-action rejects the plain-tcp endpoint,
# add TLS to buildkitd — or just use `make bit` (buildctl), which is the primary
# path and needs no runner at all.

name: Containerized bitstream (buildkit)

on:
workflow_call:
inputs:
buildkit_endpoint:
description: "buildkitd remote endpoint, e.g. tcp://host:1234 (set per-project — no default here)"
required: true
type: string
context:
description: "build context dir (must hold the Dockerfile)"
required: false
type: string
default: '.'
target:
description: "Dockerfile stage to export"
required: false
type: string
default: 'artifact'
outputs:
description: "buildx --output (e.g. type=local,dest=out or type=image,...,push=true)"
required: false
type: string
default: 'type=local,dest=out'

jobs:
bitstream:
runs-on: ubuntu-latest
steps:
- name: Set up buildx (remote builder)
uses: docker/setup-buildx-action@v4
with:
driver: remote
endpoint: ${{ inputs.buildkit_endpoint }}

- name: Build bitstream
uses: docker/build-push-action@v7
with:
context: ${{ inputs.context }}
target: ${{ inputs.target }}
outputs: ${{ inputs.outputs }}
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,59 @@ docker run --rm --cap-add NET_ADMIN \
...
```

## Remote bitstream CI (BuildKit)

Build FPGA bitstreams on a remote build host **inside the mega image**, driven
from your laptop. `buildkitd` on the build host reuses the mega base from its
containerd image store (no re-pull), runs Vivado in a Dockerfile `RUN`, and
exports just the bitstream. Synthesis only — board programming stays in the
project.

**Build host, once** (LAN, insecure — fine on a trusted network). Extract the
buildkit binaries from the `moby/buildkit` image, then run `buildkitd` with a
containerd worker sharing docker's image store (namespace `moby`):

```sh
buildkitd --addr tcp://0.0.0.0:1234 \
--oci-worker=false --containerd-worker=true \
--containerd-worker-addr=/run/containerd/containerd.sock \
--containerd-worker-namespace=moby --containerd-worker-snapshotter=overlayfs
```

**Project setup** — copy `ci/Dockerfile.template` → `Dockerfile` (adjust the
`build.tcl` and artifact path), vendor `ci/bitstream.mk` → your `ci/bitstream.mk`,
add a `.dockerignore` that keeps the context small, and a `Makefile`:

```makefile
# GEN = <codegen> # optional local codegen; omit if none
include ci/bitstream.mk
```

**Build** (primary path — no runner):

```sh
make bit # buildctl … --output type=local -> out/<name>.bit
make bit-push PUSH_REF=<registry>/proj/bitstream:$(git rev-parse --short HEAD)
```

Set `BUILDKIT_ADDR` (e.g. `tcp://host:1234`) in the project Makefile or the
environment (`BUILDKIT_HOST`); override `TARGET`, `OUT` as needed. This repo ships
no infrastructure defaults — projects pin their own.

**Or via act** — `.github/workflows/vivado-bitstream.yml` is a reusable workflow
(`docker/setup-buildx-action` remote driver + `docker/build-push-action`) doing
the same build. A project calls it from a tiny `workflow_dispatch`:

```yaml
jobs:
bitstream:
uses: Zubax/fpga-toolchain-docker/.github/workflows/vivado-bitstream.yml@main
with: { buildkit_endpoint: tcp://<host>:1234, target: artifact, outputs: type=local,dest=out }
```

Run codegen locally first, then
`act workflow_dispatch -s GITHUB_TOKEN="$(gh auth token)"`.

## License

The container glue in this repository is MIT licensed.
Expand Down
19 changes: 19 additions & 0 deletions ci/Dockerfile.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# syntax=docker/dockerfile:1
# Template for remote BuildKit bitstream builds. Copy to your project as
# `Dockerfile` and adjust the RUN (your build.tcl) and the artifact COPY path.
# Build with `make bit` (see the shared ci/bitstream.mk). buildkitd on the build
# host reuses the mega base from its containerd store — no re-pull.

ARG IMAGE=containers.zubax.com/zubax-fpga-toolchain-mega:2026-07-02

FROM ${IMAGE} AS build
# COPY is root-owned and WORKDIR creates root-owned dirs, but the mega base runs
# as user `ubuntu` — build as root so /work stays writable.
USER root
WORKDIR /work
COPY . .
RUN vivado -mode batch -source vivado/build.tcl -nolog -nojournal

# Thin artifact stage: only the bitstream, so --output/registry stays tiny.
FROM scratch AS artifact
COPY --from=build /work/build/YOUR_BITSTREAM.bit /
50 changes: 50 additions & 0 deletions ci/bitstream.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Shared make logic for remote containerized FPGA bitstream builds via BuildKit.
# Vendor this file into a project (ci/bitstream.mk) and `include` it from the
# project Makefile — keep the copy in sync with Zubax/fpga-toolchain-docker.
#
# The project Makefile sets these and includes this file:
# BUILDKIT_ADDR buildkitd endpoint, e.g. tcp://HOST:1234
# (or leave empty and set BUILDKIT_HOST in the environment)
# GEN optional codegen command to run before the build (blank = none)
# CONTEXT build context dir (default .)
# TARGET Dockerfile stage (default artifact)
# OUT local output dir (default out)
# PUSH_REF registry ref for bit-push, e.g. <registry>/<proj>/bitstream:<tag>
#
# Targets:
# make bit build + export the bitstream to $(OUT)/
# make bit-push build + push a thin bitstream image to $(PUSH_REF)

BUILDKIT_ADDR ?=
CONTEXT ?= .
TARGET ?= artifact
OUT ?= out

# Pass --addr only when BUILDKIT_ADDR is set; otherwise buildctl uses $BUILDKIT_HOST.
_addr := $(if $(BUILDKIT_ADDR),--addr $(BUILDKIT_ADDR),)

.PHONY: bit bit-push _gen _need-endpoint

_need-endpoint:
@test -n "$(BUILDKIT_ADDR)$(BUILDKIT_HOST)" || \
{ echo "error: set BUILDKIT_ADDR (e.g. tcp://host:1234) or BUILDKIT_HOST"; exit 1; }

_gen:
@$(if $(GEN),$(GEN),true)

bit: _need-endpoint _gen
buildctl $(_addr) build \
--frontend dockerfile.v0 \
--local context=$(CONTEXT) --local dockerfile=$(CONTEXT) \
--opt target=$(TARGET) --opt image-resolve-mode=local \
--output type=local,dest=$(OUT)
@echo "==> bitstream exported to $(OUT)/"

bit-push: _need-endpoint _gen
@test -n "$(PUSH_REF)" || { echo "error: set PUSH_REF=<registry>/<proj>/bitstream:<tag>"; exit 1; }
buildctl $(_addr) build \
--frontend dockerfile.v0 \
--local context=$(CONTEXT) --local dockerfile=$(CONTEXT) \
--opt target=$(TARGET) --opt image-resolve-mode=local \
--output type=image,name=$(PUSH_REF),push=true
@echo "==> pushed $(PUSH_REF)"
Loading