Skip to content
Closed
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
102 changes: 102 additions & 0 deletions .github/workflows/vivado-self-hosted.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Vivado Bitstream (self-hosted)

on:
workflow_dispatch:
inputs:
design:
description: 'Design to build (blinky | gf16 | phi_heartbeat)'
required: false
default: 'gf16'
uart:
description: 'Include UART telemetry block'
required: false
default: 'true'
type: boolean
pull_request:
paths:
- 'fpga/vivado/**'
- 'specs/fpga/**'
- 'bootstrap/src/**'
- '.github/workflows/vivado-self-hosted.yml'

jobs:
vivado-build:
runs-on: [self-hosted, vivado, x86_64, linux]
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Print runner info
run: |
uname -a
which vivado || echo "vivado not on PATH"
df -h /opt/Xilinx || true

- name: Build t27c
run: |
cd bootstrap
cargo build --release --bin t27c
echo "T27C_BIN=$PWD/target/release/t27c" >> "$GITHUB_ENV"

- name: Generate Verilog from .t27 specs
run: |
DESIGN="${{ github.event.inputs.design || 'gf16' }}"
"$T27C_BIN" fpga-build --smoke --device xc7a100tcsg324-1 --top "${DESIGN}_top" --output build/fpga
ls -la build/fpga/generated/

- name: Synthesize with Vivado
run: |
DESIGN="${{ github.event.inputs.design || 'gf16' }}"
UART_FLAG="${{ github.event.inputs.uart || 'true' }}"
cd fpga/vivado
vivado -mode batch -nojournal -nolog \
-source "build_${DESIGN}.tcl" \
-tclargs --uart "$UART_FLAG" --device xc7a100tcsg324-1 \
2>&1 | tee vivado.log

- name: Run UART smoke (capture tok/s)
run: |
# Requires DLC-10 JTAG attached on runner host (optional path).
# Skipped when LANES_ATTACHED=0; only metadata-extract from Vivado log.
if [ "${LANES_ATTACHED:-0}" = "1" ]; then
cd fpga/vivado
timeout 30 cargo run --release --bin dlc10 -- program *.bit
timeout 30 cargo run --release --bin uart-smoke -- --port /dev/ttyUSB1 --duration 10 | tee tok_s.txt
else
echo "UART/JTAG hardware not attached on this runner; bit-only build."
fi

- name: Extract utilization summary
run: |
cd fpga/vivado
awk '
/Slice LUTs/ {print}
/Slice Registers/ {print}
/Block RAM Tile/ {print}
/DSPs/ {print}
/WNS\(ns\)/ {getline; print}
' vivado.log > utilization_summary.txt || true
cat utilization_summary.txt

- name: Upload bitstream
uses: actions/upload-artifact@v4
if: always()
with:
name: vivado-bitstream-${{ github.event.inputs.design || 'gf16' }}
path: |
fpga/vivado/*.bit
fpga/vivado/*.ltx
retention-days: 30

- name: Upload Vivado logs & utilization
uses: actions/upload-artifact@v4
if: always()
with:
name: vivado-log-${{ github.event.inputs.design || 'gf16' }}
path: |
fpga/vivado/vivado.log
fpga/vivado/utilization_summary.txt
fpga/vivado/tok_s.txt
retention-days: 30
11 changes: 11 additions & 0 deletions infra/railway-vivado-runner/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "railway-vivado-entrypoint"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "entrypoint"
path = "src/main.rs"

[dependencies]
anyhow = "1.0"
47 changes: 47 additions & 0 deletions infra/railway-vivado-runner/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Railway Vivado Self-Hosted Runner
# Base: Ubuntu 22.04 x86_64 (Vivado 2025.2 requirement)
# Final image size: ~150 GB (Vivado install ~110 GB + Ubuntu + runner)
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=UTC

# System deps for Vivado + GH Actions runner
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl wget jq git xz-utils \
libc6 libstdc++6 libtinfo5 libncurses5 libx11-6 libxext6 libxrender1 libxtst6 libxi6 \
libfontconfig1 libfreetype6 libgtk2.0-0 libcanberra-gtk-module libgomp1 \
libusb-1.0-0 libssl-dev \
build-essential pkg-config \
sudo locales \
&& locale-gen en_US.UTF-8 \
&& rm -rf /var/lib/apt/lists/*

ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8

# Install Rust (for t27c build inside runner)
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable \
&& ln -s /root/.cargo/bin/* /usr/local/bin/

# Vivado installer is mounted from a Railway volume at /opt/installer
# Run scripts/install_vivado.sh after first container start (or in build stage with the installer COPY'd)
RUN mkdir -p /opt/Xilinx /opt/installer /actions-runner

# GH Actions runner v2.319.1 (x64 Linux)
WORKDIR /actions-runner
RUN curl -sL https://github.com/actions/runner/releases/download/v2.319.1/actions-runner-linux-x64-2.319.1.tar.gz \
| tar xz \
&& ./bin/installdependencies.sh

# Build Rust entrypoint (project policy: no .sh / .py — Rust only)
COPY Cargo.toml /entrypoint-src/Cargo.toml
COPY src /entrypoint-src/src
RUN cd /entrypoint-src \
&& cargo build --release --bin entrypoint \
&& cp target/release/entrypoint /usr/local/bin/runner-entrypoint \
&& rm -rf /entrypoint-src /root/.cargo/registry /root/.cargo/git

# Vivado settings sourcing
ENV PATH="/opt/Xilinx/Vivado/2025.2/bin:${PATH}"

ENTRYPOINT ["/usr/local/bin/runner-entrypoint"]
130 changes: 130 additions & 0 deletions infra/railway-vivado-runner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Railway Vivado Self-Hosted Runner

Production runner host for Xilinx Vivado 2025.2 (x86_64 Linux), exposed to t27 via GitHub Actions self-hosted runner. Unblocks PR #604 Vivado-CI pipeline and tok/s measurement on silicon.

## Why Railway

- M4 Mac (228 GB SSD, 11 GB free, ARM64) cannot host Vivado: needs ~110 GB and x86_64 Linux.
- Railway provides managed x86_64 Linux containers with persistent volumes — fits Vivado install footprint without on-prem hardware.
- Tailscale already configured on Mac for local-bridge access (`https://gaia-macbook-air.tail2c3a29.ts.net`); Railway is the build/synth-side counterpart.

## One-time setup

### 1. Install Railway CLI

```bash
cargo install railway-cli # or use the npm install if preferred
railway login
```

### 2. Create a new Railway project

```bash
cd infra/railway-vivado-runner
railway init --name t27-vivado-runner
railway link
```

### 3. Provision the 3 volumes

In Railway UI → Project → Volumes:

| Mount path | Size | Purpose |
| ------------------------- | ------- | ------------------------------------ |
| `/opt/Xilinx` | 130 GB | Vivado install (survives redeploys) |
| `/opt/installer` | 1 GB | Drop Vivado_2025.2_Lin64.bin once |
| `/actions-runner/_work` | 20 GB | Per-job build cache |

### 4. Upload Vivado installer

From the Mac (where Vivado_2025.2_Lin64.bin already lives in ~/Downloads):

```bash
# 347 MB installer — Railway volume sync via SSH or s3-style endpoint
railway volume push ~/Downloads/Vivado_2025.2_Lin64.bin /opt/installer/
```

### 5. Obtain a GH Actions registration token

GitHub → t27 → Settings → Actions → Runners → New self-hosted runner → Linux x64 → copy the token from the `./config.sh --token <TOKEN>` line.

Token expires in ~1 hour; you can re-fetch and `railway redeploy` if it lapses before first successful registration.

### 6. Set Railway env vars

```bash
railway variables set GH_RUNNER_TOKEN=<token-from-step-5>
railway variables set GH_REPO_URL=https://github.com/gHashTag/t27
railway variables set RUNNER_NAME=railway-vivado-prod
railway variables set RUNNER_LABELS=vivado,x86_64,linux,railway
```

### 7. Deploy

```bash
railway up
```

First boot will:
1. Build Dockerfile (~5 min)
2. Register the runner with GitHub
3. Wait for jobs

### 8. Install Vivado (first deploy only)

Open Railway shell into the running container and run the silent installer once:

```bash
railway run bash
cd /opt/installer
./Vivado_2025.2_Lin64.bin --batch Install \
--product Vivado \
--edition "Vivado ML Standard" \
--location /opt/Xilinx \
--agree XilinxEULA,3rdPartyEULA \
--batch CONFIG \
--installconfig /opt/installer/install_config.txt
```

(Generate `install_config.txt` first by running `./Vivado_2025.2_Lin64.bin -- ConfigGen` interactively on a workstation, then `railway volume push` it.)

Persistent volume means this is one-time only; subsequent redeploys reuse `/opt/Xilinx`.

### 9. Verify

```bash
railway logs --tail
# Should show:
# [entrypoint] Vivado env sourced from /opt/Xilinx/Vivado/2025.2/settings64.sh
# [entrypoint] Launching ./run.sh (GH Actions runner)
# √ Connected to GitHub
# Listening for Jobs
```

GitHub → t27 → Settings → Actions → Runners → should list `railway-vivado-prod` as Idle / Online.

## CI integration

`.github/workflows/vivado-self-hosted.yml` (added in this PR) targets `runs-on: [self-hosted, vivado, x86_64]`. Existing `.github/workflows/vivado-bitstream.yml` is left intact (uses Docker on github-hosted runner, but is unreliable due to disk constraints).

## Cost estimate

Railway Pro plan, 8 vCPU / 32 GB RAM, 150 GB volumes:
- Compute: ~$20–40/month idle (auto-scales down between jobs)
- Storage: ~$15/month for 150 GB
- Estimated total: **$35–55/month** active development

For lower cost, scale down to 4 vCPU / 16 GB outside synth windows.

## Maintenance

- **Vivado upgrade**: stop the service, mount `/opt/Xilinx` to a temporary container, run `./xinstall.sh -m upgrade --product Vivado`.
- **Token rotation**: GitHub registration tokens expire after registration; the runner stays connected via long-lived auth. To re-register, delete `.runner` file in `/actions-runner/_work/` parent dir and redeploy with a fresh `GH_RUNNER_TOKEN`.
- **Graceful shutdown**: `railway down` → entrypoint trap removes the runner registration before exit.

## Security

- `GH_RUNNER_TOKEN` is a short-lived registration token; no long-term secrets in image.
- Runner is scoped to a single repo (not org-wide).
- Tailscale not required for Railway → GitHub direction (outbound HTTPS only).
- Vivado license: project relies on Vivado ML Standard (free) feature set; no paid license file shipped.
35 changes: 35 additions & 0 deletions infra/railway-vivado-runner/railway.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Railway service definition for Vivado self-hosted GitHub Actions runner.
# Deploy: `railway up --service vivado-runner` from this directory.
# Required Railway env vars:
# GH_RUNNER_TOKEN — registration token from Settings -> Actions -> Runners -> New self-hosted (expires ~1h, re-deploy to refresh)
# GH_REPO_URL — e.g. https://github.com/gHashTag/t27
# RUNNER_NAME — optional, defaults to railway-vivado
# RUNNER_LABELS — optional, defaults to "vivado,x86_64,linux"

[build]
builder = "DOCKERFILE"
dockerfilePath = "Dockerfile"

[deploy]
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 3

# Required: persistent volume for Vivado install (~110 GB) + runner work dir
# Mount paths inside container:
# /opt/Xilinx — Vivado install (survives redeploys)
# /opt/installer — drop Vivado_2025.2_Lin64.bin here once via railway volume sync
# /actions-runner/_work — runner build cache
[[volumes]]
mountPath = "/opt/Xilinx"
sizeGB = 130

[[volumes]]
mountPath = "/opt/installer"
sizeGB = 1

[[volumes]]
mountPath = "/actions-runner/_work"
sizeGB = 20

# Resource hints (Railway Pro plan recommended; Vivado synth needs RAM)
# Set in Railway UI: 8 vCPU / 32 GB RAM for full synth, 4 vCPU / 16 GB for smoke
Loading
Loading