diff --git a/.github/workflows/vivado-build.yml b/.github/workflows/vivado-build.yml new file mode 100644 index 00000000..9c0eba0f --- /dev/null +++ b/.github/workflows/vivado-build.yml @@ -0,0 +1,87 @@ +# .github/workflows/vivado-build.yml +# +# Vivado FPGA Build — gf16_heartbeat_uart_top +# Target: xc7a100tfgg676-1 (QMTech Wukong V1) +# +# Runs on a self-hosted runner with Vivado 2023.2 installed locally. +# The runner must carry labels: [self-hosted, linux, vivado]. +# Configure XILINX_VIVADO in the runner's environment (see SETUP_RUNNER.md). + +name: Vivado FPGA Build + +on: + push: + branches: + - feat/vivado-ci + pull_request: + types: [labeled] + workflow_dispatch: + +jobs: + build-bitstream: + # Only run PR builds when the "build:vivado" label is attached. + # Push and workflow_dispatch runs always proceed. + if: > + github.event_name != 'pull_request' || + contains(github.event.pull_request.labels.*.name, 'build:vivado') + + name: Build .bit (Vivado 2023.2) + runs-on: [self-hosted, linux, vivado] + timeout-minutes: 30 + + steps: + # ── 1. Checkout ────────────────────────────────────────────────────────── + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + # ── 2. Vivado on PATH ──────────────────────────────────────────────────── + # + # XILINX_VIVADO must be set in the runner's systemd unit or .env file, + # e.g.: XILINX_VIVADO=/opt/Xilinx/Vivado/2023.2 + # + # We extend PATH here rather than sourcing settings64.sh to avoid + # polluting the environment with every Vivado variable (and because + # .sh files are disallowed in this repository). + - name: Add Vivado to PATH + run: echo "$XILINX_VIVADO/bin" >> "$GITHUB_PATH" + env: + XILINX_VIVADO: ${{ env.XILINX_VIVADO }} + + # ── 3. Vivado batch build ──────────────────────────────────────────────── + - name: Run Vivado build + working-directory: fpga/vivado/uart + run: > + vivado + -mode batch + -nojournal + -nolog + -source build.tcl + env: + XILINX_VIVADO: ${{ env.XILINX_VIVADO }} + + # ── 4. Upload artefacts ────────────────────────────────────────────────── + - name: Upload bitstream and reports + uses: actions/upload-artifact@v4 + with: + name: vivado-output-${{ github.sha }} + path: | + fpga/vivado/uart/build/output/gf16_heartbeat_uart_top.bit + fpga/vivado/uart/build/output/utilization.rpt + fpga/vivado/uart/build/output/timing.rpt + if-no-files-found: error + retention-days: 30 + + # ── 5. SHA-256 summary ─────────────────────────────────────────────────── + - name: Write SHA-256 to step summary + run: | + BIT=fpga/vivado/uart/build/output/gf16_heartbeat_uart_top.bit + SHA=$(sha256sum "$BIT" | awk '{print $1}') + echo "## Bitstream" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Field | Value |" >> "$GITHUB_STEP_SUMMARY" + echo "|-------|-------|" >> "$GITHUB_STEP_SUMMARY" + echo "| File | \`gf16_heartbeat_uart_top.bit\` |" >> "$GITHUB_STEP_SUMMARY" + echo "| SHA-256 | \`$SHA\` |" >> "$GITHUB_STEP_SUMMARY" + echo "| Commit | \`${{ github.sha }}\` |" >> "$GITHUB_STEP_SUMMARY" diff --git a/fpga/vivado/uart/Dockerfile b/fpga/vivado/uart/Dockerfile new file mode 100644 index 00000000..b14d017a --- /dev/null +++ b/fpga/vivado/uart/Dockerfile @@ -0,0 +1,62 @@ +# Dockerfile — self-hosted GitHub Actions runner base image +# +# Does NOT install Vivado. Vivado is expected to be bind-mounted from the +# host at /tools/Xilinx (or /opt/Xilinx) at container start, matching a +# typical self-hosted setup where the installer was run once on bare metal. +# +# Build: +# docker build -t ghactions-vivado-runner . +# +# Run example (Vivado on host at /opt/Xilinx): +# docker run --rm \ +# -v /opt/Xilinx:/opt/Xilinx:ro \ +# -e XILINX_VIVADO=/opt/Xilinx/Vivado/2023.2 \ +# -e RUNNER_TOKEN= \ +# ghactions-vivado-runner +# +# The RUNNER_TOKEN must be obtained fresh from: +# https://github.com///settings/actions/runners/new + +FROM ubuntu:22.04 + +LABEL org.opencontainers.image.description="GitHub Actions self-hosted runner base for Vivado (Vivado volume-mounted from host)" + +ENV DEBIAN_FRONTEND=noninteractive + +# Runtime dependencies required by Vivado on Ubuntu 22.04. +# libtinfo5 is not in 22.04 main repos; libncurses5 pulls it transitively +# via the compat package. Install both to be safe. +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + git \ + jq \ + libc6 \ + libglib2.0-0 \ + libgtk2.0-0 \ + libncurses5 \ + libstdc++6 \ + libtinfo5 \ + libxext6 \ + libxrender1 \ + libxtst6 \ + locales \ + sudo \ + unzip \ + && rm -rf /var/lib/apt/lists/* + +# Generate en_US.UTF-8 locale (Vivado log parser expects it) +RUN locale-gen en_US.UTF-8 +ENV LANG=en_US.UTF-8 + +# Create a non-root runner user +RUN useradd -m -s /bin/bash runner && \ + echo "runner ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +USER runner +WORKDIR /home/runner + +# The entrypoint should configure and start the Actions runner. +# Configuration is left to the operator (token rotation, repo URL, labels). +# See SETUP_RUNNER.md for details. +CMD ["/bin/bash"] diff --git a/fpga/vivado/uart/README.md b/fpga/vivado/uart/README.md new file mode 100644 index 00000000..dadcc16e --- /dev/null +++ b/fpga/vivado/uart/README.md @@ -0,0 +1,84 @@ +# fpga/vivado/uart — Vivado CI + +Continuous integration pipeline for building the `gf16_heartbeat_uart_top` +bitstream via Vivado 2023.2 on a self-hosted GitHub Actions runner. + +## Contents + +| File | Purpose | +|------|---------| +| `build.tcl` | Vivado batch build script — synth → opt → place → route → write_bitstream | +| `vivado-build.yml` | GitHub Actions workflow (copy to `.github/workflows/`) | +| `Dockerfile` | Optional base image for containerised runner (Vivado volume-mounted from host) | +| `SETUP_RUNNER.md` | Step-by-step guide: install Vivado, register runner, configure labels | +| `README.md` | This file | +| `SUMMARY.md` | What was scaffolded and what you need to do next | + +## Quick Start + +### Prerequisites + +- A Linux machine with **Vivado 2023.2 Standard Edition** installed + (see `SETUP_RUNNER.md` for download and licence instructions) +- GitHub Actions self-hosted runner registered to this repo + with labels `self-hosted`, `linux`, `vivado` +- `XILINX_VIVADO=/opt/Xilinx/Vivado/2023.2` in the runner's environment + +### Repository layout expected by the build + +``` +fpga/ + vsa/ + gf16_dot4.v ← shared GF(2^4) dot-product module + uart/ + build.tcl ← this directory + gf16_heartbeat_uart_top.v + gf16_heartbeat_uart_top.xdc + build/output/ ← created by build.tcl + gf16_heartbeat_uart_top.bit + utilization.rpt + timing.rpt +``` + +### Trigger a build + +**Via push:** any push to `feat/vivado-ci` triggers the workflow automatically. + +**Via UI:** Actions → *Vivado FPGA Build* → *Run workflow*. + +**Via PR:** attach the label `build:vivado` to a pull request. + +### Workflow file placement + +Copy (or symlink) `vivado-build.yml` to `.github/workflows/vivado-build.yml` +in the repository root before merging. + +--- + +## Reference Design + +The **current working heartbeat bitstream** (no UART) is: + +``` +fpga/vivado/gf16_heartbeat_top.bit +``` + +It was built from `gf16_heartbeat_top.v` + `gf16_heartbeat_top.xdc` against +the same xc7a100tfgg676-1 target and is already confirmed functional on the +QMTech Wukong V1 board. + +The UART variant (`gf16_heartbeat_uart_top`) extends that design with a +115200-baud TX telemetry output on pin K20. + +--- + +## Target + +| Parameter | Value | +|-----------|-------| +| FPGA | xc7a100tfgg676-1 | +| Board | QMTech Wukong V1 | +| Top module | `gf16_heartbeat_uart_top` | +| Clock | CFGMCLK ≈ 65 MHz (STARTUPE2) | +| UART TX pin | K20 (115200 8N1) | +| Tool | Vivado 2023.2 Standard Edition | diff --git a/fpga/vivado/uart/SETUP_RUNNER.md b/fpga/vivado/uart/SETUP_RUNNER.md new file mode 100644 index 00000000..edac75d9 --- /dev/null +++ b/fpga/vivado/uart/SETUP_RUNNER.md @@ -0,0 +1,186 @@ +# Setting Up a Self-Hosted GitHub Actions Runner with Vivado + +This guide walks through the complete setup of a Linux machine as a +self-hosted GitHub Actions runner capable of building Vivado bitstreams +for the `gHashTag/t27` project. + +> **Platform note:** Vivado is supported only on **Linux and Windows**. +> macOS is not supported by AMD Vivado — do not attempt to use a macOS runner. + +--- + +## Step 1 — Create an AMD/Xilinx Account + +Go to and +register. The same account is used for the download portal and the licence +manager. If you already have an AMD account, skip this step. + +--- + +## Step 2 — Download Vivado 2023.2 + +1. Open the AMD Downloads page: +2. Select **2023.2** from the archive list. +3. Download the **AMD Unified Installer for FPGAs & Adaptive SoCs 2023.2 — + Linux Self Extracting Web Installer** (≈ 300 MB launcher; the full + installation is ~ 35 GB after component selection). + +> **Alternatively**, Vivado 2024.2 can be used; the build script and +> workflow are version-agnostic. Adjust `XILINX_VIVADO` accordingly. + +--- + +## Step 3 — Install Vivado + +```bash +chmod +x FPGAs_AdaptiveSoCs_Unified_2023.2_*.bin +sudo ./FPGAs_AdaptiveSoCs_Unified_2023.2_*.bin +``` + +At the installer UI: + +- **Edition:** Vivado Standard (free — see Step 4) +- **Products:** check *Vivado Design Suite* only (uncheck Vitis / Vitis HLS to + save disk space) +- **Devices:** check *7 Series* at minimum (covers xc7a100t) +- **Destination:** accept the default `/opt/Xilinx` or specify `/tools/Xilinx` + +After installation the Vivado binary will be at: + +``` +/opt/Xilinx/Vivado/2023.2/bin/vivado +``` + +--- + +## Step 4 — Obtain a Vivado Standard Edition Licence + +**Vivado Standard Edition is free of charge.** A licence file is still +required for certain 7-series devices. + +1. Open Xilinx Licence Manager (installed with Vivado): + ```bash + /opt/Xilinx/Vivado/2023.2/bin/vlm + ``` +2. Choose **Get Free Licences → Certificate Based Licence** → Vivado ML + Standard. +3. AMD will e-mail a `.lic` file. Place it at `~/.Xilinx/Xilinx.lic` or + point `XILINXD_LICENSE_FILE` to its location. + +> **Why not WebPACK?** The WebPACK tier was deprecated in Vivado 2022.x and +> replaced by **Standard Edition** (also free, but slightly broader device +> coverage). If the installer still shows WebPACK, treat it as equivalent. + +--- + +## Step 5 — Register the GitHub Actions Runner + +1. Navigate to: + ``` + https://github.com/gHashTag/t27/settings/actions/runners/new + ``` + (requires repo Admin permissions) + +2. Select **Linux** / **x64**. + +3. Follow the displayed commands to download and configure the runner agent: + ```bash + mkdir -p ~/actions-runner && cd ~/actions-runner + curl -o actions-runner-linux-x64.tar.gz -L \ + https://github.com/actions/runner/releases/download/v2.x.x/actions-runner-linux-x64-2.x.x.tar.gz + tar xzf actions-runner-linux-x64.tar.gz + ./config.sh --url https://github.com/gHashTag/t27 --token + ``` + +4. When prompted for **labels**, enter: + ``` + self-hosted,linux,vivado + ``` + +5. Install and start as a systemd service: + ```bash + sudo ./svc.sh install + sudo ./svc.sh start + ``` + +--- + +## Step 6 — Expose `XILINX_VIVADO` to the Runner + +The workflow reads `XILINX_VIVADO` from the runner environment. The +recommended approach is to add it to the runner's systemd override: + +```bash +sudo systemctl edit actions.runner.gHashTag-t27. +``` + +Add: +```ini +[Service] +Environment="XILINX_VIVADO=/opt/Xilinx/Vivado/2023.2" +``` + +Then reload: +```bash +sudo systemctl daemon-reload +sudo systemctl restart actions.runner.gHashTag-t27. +``` + +Alternatively, add to `/home//.profile` or the runner's `.env` +file (`~/actions-runner/.env`): +``` +XILINX_VIVADO=/opt/Xilinx/Vivado/2023.2 +``` + +--- + +## Step 7 — Test with `workflow_dispatch` + +1. Push `feat/vivado-ci` to GitHub (the branch already contains the workflow). +2. Go to **Actions → Vivado FPGA Build → Run workflow**. +3. Select branch `feat/vivado-ci` and click **Run workflow**. +4. The job should complete in under 30 minutes and upload: + - `gf16_heartbeat_uart_top.bit` + - `utilization.rpt` + - `timing.rpt` +5. The SHA-256 of the bitstream is printed in the step summary. + +--- + +## Frequently Asked Questions + +**Q: Why can't I use a GitHub-hosted runner?** +A: GitHub-hosted runners (`ubuntu-latest`) have only **14 GB** of total disk +space. A minimal Vivado 2023.2 installation with 7-series support requires +~ 35 GB. Self-hosted runners on a machine with adequate storage are required. + +**Q: Why not use the WebPACK edition?** +A: AMD deprecated the WebPACK product tier in Vivado 2022.x. It was replaced +by **Standard Edition**, which is also free and covers 7-series devices +including the xc7a100t used in this project. + +**Q: Does `write_bitstream` require a full licence?** +A: Bitstream generation for supported 7-series devices is included in the +Standard Edition licence. The Artix-7 xc7a100t is within that scope. + +--- + +## Known Issue: `libtinfo5` Missing on Ubuntu 22.04+ + +Ubuntu 22.04 ships `libtinfo6` but Vivado 2023.x links against `libtinfo5`. +Install the compatibility package: + +```bash +sudo apt install libtinfo5 +``` + +If `libtinfo5` is not available in your mirror (it was removed from Ubuntu +22.04 main), add the universe repository first: + +```bash +sudo add-apt-repository universe +sudo apt update +sudo apt install libtinfo5 +``` + +Or install the `.deb` directly from the Ubuntu 20.04 archive as a workaround. diff --git a/fpga/vivado/uart/SUMMARY.md b/fpga/vivado/uart/SUMMARY.md new file mode 100644 index 00000000..694257fd --- /dev/null +++ b/fpga/vivado/uart/SUMMARY.md @@ -0,0 +1,76 @@ +# Vivado CI Scaffolding — Summary + +## What Was Prepared + +Five files have been placed under `fpga/vivado/uart/` (mapped from workspace +`vivado_ci/`) and committed to branch `feat/vivado-ci` of `gHashTag/t27`: + +| File | Description | +|------|-------------| +| `build.tcl` | Vivado 2023.2 batch TCL script: in-memory project → synth → opt → place → route → bitstream + reports. Gracefully handles a missing `gf16_dot4.v` with a warning rather than a hard abort. | +| `Dockerfile` | Minimal Ubuntu 22.04 base image for a containerised runner. Does NOT install Vivado — expects Vivado bind-mounted from the host at `/opt/Xilinx`. | +| `vivado-build.yml` | GitHub Actions workflow. Triggers on push to `feat/vivado-ci`, `workflow_dispatch`, and PRs with label `build:vivado`. Uses runner labels `[self-hosted, linux, vivado]`, 30-minute timeout, uploads `.bit` + `.rpt` as artifacts, and writes SHA-256 to the step summary. | +| `SETUP_RUNNER.md` | End-to-end runner setup guide: AMD account → Vivado download (Standard Edition, free) → installation → licence → runner registration → label configuration → first test run. Includes FAQ and known Ubuntu 22.04 `libtinfo5` fix. | +| `README.md` | Directory overview, quick-start, repository layout diagram, and reference to the existing `gf16_heartbeat_top.bit` baseline bitstream. | + +## What You Need to Do + +### 1 — Place the workflow file + +Copy `vivado-build.yml` from `fpga/vivado/uart/` to `.github/workflows/`: + +```bash +cp fpga/vivado/uart/vivado-build.yml .github/workflows/vivado-build.yml +git add .github/workflows/vivado-build.yml +git commit -m "ci: add Vivado build workflow" +git push origin feat/vivado-ci +``` + +GitHub Actions only picks up workflow files from `.github/workflows/`. + +### 2 — Verify the repository layout + +Confirm these paths exist before triggering a build: + +``` +fpga/vivado/gf16_dot4.v +fpga/vivado/uart/gf16_heartbeat_uart_top.v +fpga/vivado/uart/gf16_heartbeat_uart_top.xdc +fpga/vivado/uart/build.tcl +``` + +If `gf16_dot4.v` is elsewhere, update the `src_dot4` variable in `build.tcl`. + +### 3 — Set up the self-hosted runner + +Follow `SETUP_RUNNER.md` in full. Key checklist: + +- [ ] Vivado 2023.2 installed at `/opt/Xilinx/Vivado/2023.2` +- [ ] Vivado Standard Edition licence in `~/.Xilinx/Xilinx.lic` +- [ ] Runner registered at `https://github.com/gHashTag/t27/settings/actions/runners` +- [ ] Runner labels include `vivado` (in addition to `self-hosted` and `linux`) +- [ ] `XILINX_VIVADO=/opt/Xilinx/Vivado/2023.2` in runner environment +- [ ] `libtinfo5` installed if runner OS is Ubuntu 22.04 + +### 4 — Trigger a test build + +``` +Actions → Vivado FPGA Build → Run workflow → feat/vivado-ci +``` + +Expected output artifact: `vivado-output-/gf16_heartbeat_uart_top.bit` + +## What Is NOT Included + +- **Vivado itself** — must be installed by you (free download, ~35 GB) +- **Licence file** — generated once through AMD Licence Manager at no cost +- **gf16_dot4.v** — already in the repo at `fpga/vivado/gf16_dot4.v`; the TCL + script references it automatically + +## References + +- Vivado 2023.2 archive downloads: +- Vivado Standard Edition (free): +- AMD account registration: +- GitHub self-hosted runner docs: +- Runner registration page: diff --git a/fpga/vivado/uart/build.tcl b/fpga/vivado/uart/build.tcl new file mode 100644 index 00000000..98ef6269 --- /dev/null +++ b/fpga/vivado/uart/build.tcl @@ -0,0 +1,102 @@ +# build.tcl — Vivado 2023.2 batch build script +# +# Target: xc7a100tfgg676-1 (QMTech Wukong V1) +# Top: gf16_heartbeat_uart_top +# Output: build/output/gf16_heartbeat_uart_top.bit +# +# Usage (from fpga/vsa/uart/): +# vivado -mode batch -nojournal -nolog -source build.tcl +# +# The script resolves source paths relative to the directory it lives in, +# so it can be launched from any working directory as long as -source +# receives the full path (the workflow does: cd fpga/vsa/uart && vivado ... -source build.tcl). + +# ── Resolve script directory ─────────────────────────────────────────────────── +set script_dir [file dirname [file normalize [info script]]] + +# ── Output directory ─────────────────────────────────────────────────────────── +set output_dir [file join $script_dir build output] +file mkdir $output_dir + +# ── Source files ────────────────────────────────────────────────────────────── +# +# gf16_dot4.v lives one level up in fpga/vsa/ (shared module used by heartbeat +# and other designs). The path is relative to this script. +set src_top [file join $script_dir gf16_heartbeat_uart_top.v] +set src_dot4 [file join $script_dir .. gf16_dot4.v] +set xdc_file [file join $script_dir gf16_heartbeat_uart_top.xdc] + +# ── Validate that mandatory files are present ───────────────────────────────── +foreach f [list $src_top $xdc_file] { + if {![file exists $f]} { + puts "ERROR: Required file not found: $f" + exit 1 + } +} + +# gf16_dot4.v is sourced from the parent directory; warn and continue if absent +# (Vivado will report it as a missing module during synthesis, which is caught +# below as a non-fatal warning so the build can still generate timing/util reports +# from whatever was successfully elaborated). +set dot4_present [file exists $src_dot4] +if {!$dot4_present} { + puts "WARNING: gf16_dot4.v not found at $src_dot4 — synthesis will proceed" + puts " with a black-box stub. Bitstream will not be functionally" + puts " correct but reports will still be generated." +} + +# ── Create in-memory project ────────────────────────────────────────────────── +create_project -in_memory -part xc7a100tfgg676-1 + +set_property target_language Verilog [current_project] + +# ── Read sources ────────────────────────────────────────────────────────────── +read_verilog $src_top +if {$dot4_present} { + read_verilog $src_dot4 +} +read_xdc $xdc_file + +# ── Synthesis ───────────────────────────────────────────────────────────────── +puts "INFO: Starting synthesis..." +synth_design \ + -top gf16_heartbeat_uart_top \ + -part xc7a100tfgg676-1 \ + -flatten_hierarchy rebuilt + +# Check for critical synthesis errors before proceeding +set synth_msgs [get_msg_config -severity ERROR -count] +if {$synth_msgs > 0} { + puts "ERROR: Synthesis completed with $synth_msgs error(s). Aborting." + report_utilization -file [file join $output_dir utilization_synth.rpt] + exit 1 +} + +# ── Optimisation ────────────────────────────────────────────────────────────── +puts "INFO: Running opt_design..." +opt_design + +# ── Placement ───────────────────────────────────────────────────────────────── +puts "INFO: Running place_design..." +place_design + +# ── Routing ─────────────────────────────────────────────────────────────────── +puts "INFO: Running route_design..." +route_design + +# ── Reports ─────────────────────────────────────────────────────────────────── +puts "INFO: Writing reports..." +report_utilization \ + -file [file join $output_dir utilization.rpt] + +report_timing_summary \ + -max_paths 10 \ + -file [file join $output_dir timing.rpt] + +# ── Bitstream ───────────────────────────────────────────────────────────────── +puts "INFO: Writing bitstream..." +write_bitstream -force \ + [file join $output_dir gf16_heartbeat_uart_top.bit] + +puts "INFO: Build complete." +puts "INFO: Bitstream: [file join $output_dir gf16_heartbeat_uart_top.bit]" diff --git a/fpga/vivado/uart/gf16_heartbeat_uart_top.v b/fpga/vivado/uart/gf16_heartbeat_uart_top.v new file mode 100644 index 00000000..ef94f29b --- /dev/null +++ b/fpga/vivado/uart/gf16_heartbeat_uart_top.v @@ -0,0 +1,281 @@ +`default_nettype none + +// gf16_heartbeat_uart_top — heartbeat bitstream with UART telemetry +// +// Adds 115200-baud UART TX output to the existing gf16_heartbeat_top design. +// Every layer-transition (~0.82 Hz at 66 MHz CFGMCLK) emits a one-line ASCII +// frame on uart_tx so the host can measure live throughput (tok/s) and +// observe the GF(2^4) dot4 result over time. +// +// Frame format (12 bytes incl. CR/LF): +// "T:HH R:HHHH\r\n" +// T = temporal_layer (0..3, hex) +// R = dot4_result (16-bit, hex) +// +// At 0.82 layer/s × 12 bytes × 10 bits/byte = ~98 baud average traffic, +// well within 115200. + +module gf16_heartbeat_uart_top ( + output wire led_d5, + output wire led_d6, + output wire led_j26, + output wire uart_tx +); + + // ─────────────────────────────────────────────────────────────────────── + // STARTUPE2 — primary clock source on Wukong V1 (no external osc on + // QMTech core board; CFGMCLK is ~66 MHz internal config oscillator). + // ─────────────────────────────────────────────────────────────────────── + wire cfgmclk; + STARTUPE2 #( + .PROG_USR("FALSE"), + .SIM_CCLK_FREQ(10.0) + ) startup ( + .CFGCLK(), + .CFGMCLK(cfgmclk), + .EOS(), + .PREQ(), + .CLK(1'b0), + .GSR(1'b0), + .GTS(1'b0), + .KEYCLEARB(1'b0), + .PACK(1'b0), + .USRCCLKO(1'b0), + .USRCCLKTS(1'b0), + .USRDONEO(1'b1), + .USRDONETS(1'b1) + ); + + // ─────────────────────────────────────────────────────────────────────── + // φ-cycle and temporal layer counter (unchanged from original heartbeat) + // ─────────────────────────────────────────────────────────────────────── + localparam PHI_CYCLE = 27'd80_901_699; + localparam GF16_ONE = 16'h3E00; + localparam GF16_PHI_FRAC = 16'h3D9E; + localparam GF16_HALF = 16'h3C00; + localparam GF16_QUARTER = 16'h3A00; + + reg [26:0] phi_counter = 0; + reg [1:0] temporal_layer = 0; + reg [24:0] blink_counter = 0; + reg layer_changed = 1'b0; + + always @(posedge cfgmclk) begin + blink_counter <= blink_counter + 1'b1; + phi_counter <= phi_counter + 1'b1; + layer_changed <= 1'b0; + if (phi_counter >= PHI_CYCLE) begin + phi_counter <= 0; + temporal_layer <= temporal_layer + 1'b1; + layer_changed <= 1'b1; // one-cycle pulse for UART trigger + end + end + + // ─────────────────────────────────────────────────────────────────────── + // GF(2^4) dot4 (unchanged) + // ─────────────────────────────────────────────────────────────────────── + reg [15:0] vec_a0, vec_a1, vec_a2, vec_a3; + reg [15:0] vec_b0, vec_b1, vec_b2, vec_b3; + + always @(posedge cfgmclk) begin + vec_a0 <= GF16_ONE; + vec_a1 <= GF16_PHI_FRAC; + vec_a2 <= GF16_HALF; + vec_a3 <= GF16_QUARTER; + case (temporal_layer) + 2'd0: begin + vec_b0 <= GF16_ONE; + vec_b1 <= GF16_ONE; + vec_b2 <= GF16_ONE; + vec_b3 <= GF16_ONE; + end + 2'd1: begin + vec_b0 <= GF16_PHI_FRAC; + vec_b1 <= GF16_PHI_FRAC; + vec_b2 <= GF16_PHI_FRAC; + vec_b3 <= GF16_PHI_FRAC; + end + 2'd2: begin + vec_b0 <= GF16_HALF; + vec_b1 <= GF16_HALF; + vec_b2 <= GF16_HALF; + vec_b3 <= GF16_HALF; + end + default: begin + vec_b0 <= GF16_QUARTER; + vec_b1 <= GF16_QUARTER; + vec_b2 <= GF16_QUARTER; + vec_b3 <= GF16_QUARTER; + end + endcase + end + + wire [15:0] dot4_result; + gf16_dot4 dot4 ( + .a0(vec_a0), .a1(vec_a1), .a2(vec_a2), .a3(vec_a3), + .b0(vec_b0), .b1(vec_b1), .b2(vec_b2), .b3(vec_b3), + .result(dot4_result) + ); + + wire dot4_is_positive = ~dot4_result[15]; + wire [5:0] dot4_exp = dot4_result[14:9]; + wire dot4_is_nonzero = (dot4_exp != 6'd0); + + // ─────────────────────────────────────────────────────────────────────── + // LED outputs (unchanged from heartbeat) + // ─────────────────────────────────────────────────────────────────────── + reg d5_out, d6_out, j26_out; + always @(*) begin + d5_out = 1'b0; + d6_out = 1'b0; + j26_out = 1'b0; + case (temporal_layer) + 2'd0: begin + d5_out = blink_counter[24]; + d6_out = blink_counter[24]; + j26_out = dot4_is_nonzero ? blink_counter[20] : 1'b0; + end + 2'd1: begin + d5_out = 1'b1; + d6_out = 1'b1; + j26_out = dot4_is_positive ? blink_counter[19] : 1'b0; + end + 2'd2: begin + d5_out = blink_counter[22]; + d6_out = blink_counter[22]; + j26_out = dot4_is_positive ? blink_counter[18] : 1'b0; + end + default: begin + d5_out = 1'b0; + d6_out = 1'b0; + j26_out = 1'b0; + end + endcase + end + + assign led_d5 = d5_out; + assign led_d6 = d6_out; + assign led_j26 = j26_out; + + // ─────────────────────────────────────────────────────────────────────── + // UART telemetry — emit "T:HH R:HHHH\r\n" on every layer transition + // ─────────────────────────────────────────────────────────────────────── + // + // Baud divider: + // CFGMCLK is nominally ~65 MHz on -1 speed grade XC7A100T. + // For 115200 baud: divisor = 65_000_000 / 115_200 ≈ 564. + // We use 564 — within ±3% of true rate, well within UART tolerance (±2.5%). + // + localparam integer BAUD_DIVISOR = 564; + + // ── nibble-to-ASCII-hex helper ── + function [7:0] hex_nibble; + input [3:0] nib; + begin + hex_nibble = (nib < 4'd10) ? (8'h30 + {4'b0, nib}) : (8'h41 + {4'b0, nib} - 8'd10); + end + endfunction + + // ── TX byte stream ROM (12 bytes per frame) ── + reg [7:0] tx_byte; + reg tx_start; + wire tx_busy; + + reg [3:0] tx_idx = 4'd0; // 0..11 = frame bytes, 12 = idle + reg [15:0] latched_dot4 = 16'd0; + reg [1:0] latched_layer = 2'd0; + + // Frame builder + always @(posedge cfgmclk) begin + tx_start <= 1'b0; + if (layer_changed && tx_idx == 4'd12) begin + latched_dot4 <= dot4_result; + latched_layer <= temporal_layer; + tx_idx <= 4'd0; + tx_byte <= 8'h54; // 'T' + tx_start <= 1'b1; + end else if (!tx_busy && tx_idx != 4'd12 && !tx_start) begin + // Advance to next byte after previous transmit finished + case (tx_idx) + 4'd0: begin tx_byte <= 8'h3A; tx_start <= 1'b1; tx_idx <= 4'd1; end // ':' + 4'd1: begin tx_byte <= hex_nibble({2'b00, latched_layer}); tx_start <= 1'b1; tx_idx <= 4'd2; end // layer hi (zero) + 4'd2: begin tx_byte <= hex_nibble({2'b00, latched_layer}); tx_start <= 1'b1; tx_idx <= 4'd3; end // layer lo + 4'd3: begin tx_byte <= 8'h20; tx_start <= 1'b1; tx_idx <= 4'd4; end // ' ' + 4'd4: begin tx_byte <= 8'h52; tx_start <= 1'b1; tx_idx <= 4'd5; end // 'R' + 4'd5: begin tx_byte <= 8'h3A; tx_start <= 1'b1; tx_idx <= 4'd6; end // ':' + 4'd6: begin tx_byte <= hex_nibble(latched_dot4[15:12]); tx_start <= 1'b1; tx_idx <= 4'd7; end + 4'd7: begin tx_byte <= hex_nibble(latched_dot4[11:8]); tx_start <= 1'b1; tx_idx <= 4'd8; end + 4'd8: begin tx_byte <= hex_nibble(latched_dot4[7:4]); tx_start <= 1'b1; tx_idx <= 4'd9; end + 4'd9: begin tx_byte <= hex_nibble(latched_dot4[3:0]); tx_start <= 1'b1; tx_idx <= 4'd10; end + 4'd10: begin tx_byte <= 8'h0D; tx_start <= 1'b1; tx_idx <= 4'd11; end // CR + 4'd11: begin tx_byte <= 8'h0A; tx_start <= 1'b1; tx_idx <= 4'd12; end // LF + default: ; + endcase + end + end + + // ── UART transmitter (115200 8N1) ── + uart_tx_8n1 #( + .CLK_HZ(65_000_000), + .BAUD(115_200) + ) tx ( + .clk (cfgmclk), + .data (tx_byte), + .start (tx_start), + .tx (uart_tx), + .busy (tx_busy) + ); + +endmodule + +// ────────────────────────────────────────────────────────────────────────── +// Standard 8N1 UART transmitter +// ────────────────────────────────────────────────────────────────────────── +module uart_tx_8n1 #( + parameter integer CLK_HZ = 65_000_000, + parameter integer BAUD = 115_200 +) ( + input wire clk, + input wire [7:0] data, + input wire start, + output reg tx, + output wire busy +); + localparam integer DIV = CLK_HZ / BAUD; + + reg [15:0] tick_cnt = 16'd0; + reg [3:0] bit_idx = 4'd0; + reg [9:0] shifter = 10'b1111111111; // idle high + reg active = 1'b0; + + assign busy = active; + + initial tx = 1'b1; + + always @(posedge clk) begin + if (!active) begin + tx <= 1'b1; + if (start) begin + // 10-bit frame: start(0) + data[0..7] + stop(1) + shifter <= {1'b1, data, 1'b0}; + bit_idx <= 4'd0; + tick_cnt <= 16'd0; + active <= 1'b1; + tx <= 1'b0; // start bit immediately + end + end else begin + if (tick_cnt + 1 >= DIV) begin + tick_cnt <= 16'd0; + shifter <= {1'b1, shifter[9:1]}; + tx <= shifter[1]; + if (bit_idx == 4'd9) begin + active <= 1'b0; + end else begin + bit_idx <= bit_idx + 4'd1; + end + end else begin + tick_cnt <= tick_cnt + 16'd1; + end + end + end +endmodule diff --git a/fpga/vivado/uart/gf16_heartbeat_uart_top.xdc b/fpga/vivado/uart/gf16_heartbeat_uart_top.xdc new file mode 100644 index 00000000..447be05f --- /dev/null +++ b/fpga/vivado/uart/gf16_heartbeat_uart_top.xdc @@ -0,0 +1,28 @@ +# QMTech Wukong V1 — XC7A100T-FGG676 +# heartbeat + UART telemetry pin assignment + +# LEDs (active-low on V1 base board) +set_property LOC R23 [get_ports led_d5] +set_property IOSTANDARD LVCMOS33 [get_ports led_d5] + +set_property LOC T23 [get_ports led_d6] +set_property IOSTANDARD LVCMOS33 [get_ports led_d6] + +set_property LOC J26 [get_ports led_j26] +set_property IOSTANDARD LVCMOS33 [get_ports led_j26] + +# UART TX → J2 pin 5 (K20) — drives FT232RL RX +set_property LOC K20 [get_ports uart_tx] +set_property IOSTANDARD LVCMOS33 [get_ports uart_tx] +set_property DRIVE 8 [get_ports uart_tx] +set_property SLEW SLOW [get_ports uart_tx] + +# Bitstream config (matches existing gf16_heartbeat_top.xdc + STARTUPCLK) +set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design] +set_property BITSTREAM.CONFIG.UNUSEDPIN PULLDOWN [current_design] +set_property CFGBVS VCCO [current_design] +set_property CONFIG_VOLTAGE 3.3 [current_design] +set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design] +set_property BITSTREAM.CONFIG.CONFIGRATE 33 [current_design] +# StartupClk literal MUST be 'JtagClk' (case-sensitive; Vivado silently rejects 'JTAGCLK') +set_property BITSTREAM.STARTUP.STARTUPCLK JtagClk [current_design]