WEFT
by FPGArtktic
README.md
<!-- SPDX-License-Identifier: GPL-3.0-only -->
# WEFT — WEFT Elaborates FPGA Toolchains
[](https://github.com/FPGArtktic/weft-mcp/actions/workflows/ci.yml)
[](https://github.com/FPGArtktic/weft-mcp/actions/workflows/container.yml)
[](https://weft-mcp.readthedocs.io/en/main/)
[](pyproject.toml)
[](https://github.com/FPGArtktic/weft-mcp/tags)
[](COPYING)
[](CODE_OF_CONDUCT.md)
An MCP server that gives an LLM client a safe, structured interface to an
Intel Quartus Prime 25.1 FPGA flow: lint and simulate in seconds, compile
asynchronously, and read results back as JSON instead of megabytes of log.
The name is a GNU-style recursive acronym. The *weft* is the thread woven
across the warp to make **fabric**, and routing logic into FPGA fabric is
precisely the job.
## Status
WEFT is under construction, milestone by milestone. What is finished today:
| Tool | State |
|---|---|
| `lint` | working — Verilator for Verilog and SystemVerilog, GHDL for VHDL |
| `simulate` | working — Verilator, Icarus or GHDL in the container, host Questa for mixed-language designs, with waveform capture |
| Quartus projects | working — `create_project`, `set_assignments`, `get_project_info`, `list_projects` |
| Quartus compilation | working — `start_compile` as a persistent job, `get_job_status`, `get_job_log`, `cancel_job` |
| `parse_reports` | working — resources, timing per clock, ranked messages |
| Source indexing | working — `index_project`, `get_module_info`, `get_hierarchy`, `search_code` |
| Document RAG with OCR | working — `index_document`, `search_docs`, `list_indexed_docs`; clause-level citations |
| Documentation generation | working — `generate_docs`, `generate_module_doc`; Markdown and HTML, auto-indexed |
| Device programming | not yet — and see *What has not been tested on hardware* below |
Both transports work: stdio for a local client, Streamable HTTP behind a
static bearer token for a client on the LAN.
## What this is for, if MCP is new to you
Say a testbench fails and you want a model's help with it. Today you copy the
file into a chat window, run Verilator yourself, paste a screenful of
`%Warning-WIDTHEXPAND` after it, read the answer, apply the fix by hand, and go
round again. The design is three files deep, so either you paste all three or
the model guesses at the two you left out — and it will guess, confidently. The
answer you get is about the text you pasted, which is not necessarily what is on
disk.
MCP, the Model Context Protocol, removes the ferrying. A server advertises a
list of tools and the arguments each one takes. An LLM client — Claude Desktop,
Claude Code, or anything else that speaks the protocol — puts that list in front
of the model. You keep typing prose. The model picks a tool, fills in the
arguments, and the client sends the call. WEFT is the server on the far end. It
holds no model, runs no inference, and makes no network calls at runtime.
When the model calls `lint`, WEFT resolves every path against your workspace
root, refuses anything that escapes it, and runs roughly this:
```bash
podman run --rm --network=none -v <workspace>:/work -w /work weft-tools \
verilator --lint-only -Isrc src/updown_counter.sv
```
Verilator prints what it always prints. WEFT turns that into records — file,
line, severity, message — and the container is gone. `simulate` is the same loop
around Verilator, Icarus or GHDL, handing back pass/fail, a tail of the log and
the path to the waveform.
The boundary matters more than the plumbing: the model chooses what to attempt,
WEFT chooses what may execute. There is no shell on the far end. The model
cannot invent a flag, cannot reach a path you have not opened to it, and cannot
run anything that is not on the list.
The other reason to wrap the tools is size. A Quartus compile leaves megabytes
of `.rpt` behind, and what you wanted from it was a resource line, an Fmax per
clock domain, and the two warnings that mattered. Tool results here are a few
kilobytes of JSON; the raw logs stay on disk and are fetched by name when
something actually needs them.
None of this designs anything. It will not write your RTL, close your timing, or
know which board is on your desk. It runs the commands you would have run, and
hands back something small enough to reason about.
## How it is put together
Quartus runs **on the host** — WEFT drives the installation you already have
and never tries to install or containerise it. Everything else WEFT executes
lives in one Podman image, `weft-tools`: Verilator, Icarus Verilog, GHDL and
Verible for HDL work, Tesseract and Poppler for reading documents. The
container runs with `--network=none` and sees nothing but your workspace.
Every path an MCP client supplies is resolved and checked against the
configured workspace root before it reaches the filesystem, on the host and
inside the container alike.
Nothing reaches the network at runtime, and nothing reports telemetry.
The full design — every tool's arguments and return shape, the milestones,
and the reasoning behind the awkward parts — is in [PROJECT.md](PROJECT.md).
The manual is at **[weft-mcp.readthedocs.io](https://weft-mcp.readthedocs.io/en/main/)**,
built with Sphinx from [`Documentation/`](Documentation/). Its tool reference
and configuration reference are generated from the code on every build, so
neither can drift from what the server actually accepts — a key added without
a description fails the build. Build it yourself with
`pip install -e '.[docs]' && sphinx-build -W Documentation _build`.
## Requirements
- **Quartus Prime 25.1** (Lite, Standard or Pro) installed and licensed by you
- **Questa - Altera Starter FPGA Edition**, optional; it ships beside Quartus and is
the only way to simulate a design that mixes Verilog and VHDL in one run
- **Podman**, rootless
- **Python 3.11 or newer**
- `jtagd` for programming hardware, once that milestone lands
## Quick start
### Arch Linux
```bash
sudo pacman -S --needed podman python git
git clone https://github.com/FPGArtktic/weft-mcp.git
cd weft-mcp
./setup.sh
```
`setup.sh` builds the image, installs the package, finds your Quartus and
Questa, writes a starter configuration and prints the command to register the
server. `./setup.sh --check` reports what it would find and changes nothing.
The manual equivalent is:
```bash
podman build -t weft-tools -f containers/Containerfile.weft-tools .
pip install --user .
```
### Ubuntu 24.04 LTS
```bash
sudo apt update
sudo apt install podman uidmap python3 python3-pip git
git clone https://github.com/FPGArtktic/weft-mcp.git
cd weft-mcp
./setup.sh
```
`uidmap` is only a *Recommends* of `podman`, so a plain `apt install` pulls it
in but `--no-install-recommends` does not. Rootless Podman needs it.
**Ubuntu 22.04 ships Python 3.10**, which is below what WEFT needs. Either
move to 24.04 or install a newer interpreter, for instance with
[uv](https://github.com/astral-sh/uv):
```bash
uv venv --python 3.12 && uv pip install .
```
### Building the image
The image is **never distributed** — you build it, which keeps WEFT's own
distribution to GPL-3.0-only code and avoids shipping an aggregate of
third-party binaries under mixed licences. `podman build` is the only step
that needs network access; everything afterwards runs offline.
GHDL is compiled from source during the build, so expect it to take a while
the first time.
## Configuring
WEFT reads one TOML file, found at `--config`, then `$WEFT_CONFIG`, then
`~/.config/weft/weft.toml`. Only `[workspace]` is required. Every key is
documented in the
[configuration reference](https://weft-mcp.readthedocs.io/en/main/configuration.html),
generated from the loader itself.
```toml
[workspace]
# Nothing outside this directory can be read or written.
root = "/home/you/fpga"
[container]
image = "weft-tools"
[quartus]
edition = "lite" # omit when only one edition is configured
[quartus.lite]
root = "/home/you/intelFPGA_lite/25.1std/quartus"
[quartus.pro]
root = "/opt/intelFPGA_pro/25.1/quartus"
# FlexLM variables are passed through to every Pro invocation.
env = { LM_LICENSE_FILE = "1800@licence-server" }
[questa]
# Ships beside Quartus. The only simulator here that reads Verilog,
# SystemVerilog and VHDL in one run. Never a default — ask for it by name.
root = "/home/you/altera_lite/25.1std/questa_fse"
env = { SALT_LICENSE_SERVER = ";/home/you/.altera.quartus/questa_lic.dat" }
[jobs]
timeout_s = 7200
[rag]
# Where your PDFs live. Mounted read-only, and separate from the workspace
# because a document collection outlives any one project. Defaults to the
# workspace when omitted.
library = "/home/you/Documents/fpga-docs"
# Local BGE-M3 weights in ONNX form. Omit the key and document search still
# works, by text rather than by meaning.
model_path = "/home/you/.local/share/weft/models/bge-m3"
# The index itself. Defaults to <workspace>/.weft/documents.sqlite; put it
# somewhere shared to index a library once and use it from every project.
database = "/home/you/.local/share/weft/documents.sqlite"
[http]
host = "127.0.0.1"
port = 8080
token = "put-a-long-random-string-here"
```
Quartus paths always come from here. WEFT never guesses them and never
searches `PATH`. A machine with no Quartus simply omits the section — lint and
simulate do not need it.
Unknown keys are refused rather than ignored, so a typo fails at startup
instead of silently doing nothing.
## Running
The fastest way in, if you cloned the repository:
```bash
./setup.sh
```
It checks what is on the machine, builds the container image, installs the
package, writes a starter `weft.toml` with whatever Quartus and Questa it
found, and prints the exact command to register the server. It never installs
Quartus — that is yours to install and licence — and `./setup.sh --check`
reports what is present without changing anything.
Or by hand:
```bash
weft --transport stdio # a local client
weft --transport http # on the LAN, behind a bearer token
```
### Registering it with a client
Running `weft` in a terminal on its own does nothing useful. It speaks MCP over
its standard input, so a client has to start it. Where you put that instruction
depends on the client.
**Claude Code**, the CLI. One command, no file to edit:
```bash
claude mcp add --scope user weft -- \
~/.local/bin/weft --transport stdio --config ~/.config/weft/weft.toml
```
The `--` matters: everything after it is the command Claude Code runs, not an
argument to `claude mcp add`. `--scope user` makes the server available in every
project; `--scope project` writes a `.mcp.json` in the current directory instead,
which is what you commit so your colleagues get the same server. Then:
```bash
claude mcp list # is it there, and did it connect
claude mcp get weft # why did it not
```
Inside a session, `/mcp` shows the same thing.
**Claude Desktop**. Edit its configuration file — the app does not have a
command for this:
| | |
|---|---|
| Linux | `~/.config/Claude/claude_desktop_config.json` |
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
```json
{
"mcpServers": {
"weft": {
"command": "/home/you/.local/bin/weft",
"args": ["--transport", "stdio", "--config", "/home/you/.config/weft/weft.toml"]
}
}
}
```
Use the **absolute path** to the binary. Claude Desktop does not inherit your
shell's `PATH`, so `"command": "weft"` works from your terminal and fails from
the app, which is the single most common reason a server never appears. Restart
the app after editing; it reads the file at startup and not again.
**Over HTTP**, once the server is running somewhere with a token configured:
```bash
claude mcp add --transport http --header "Authorization: Bearer YOUR_TOKEN" \
weft https://your-host.example.com/mcp
```
Every request must carry that header; anything else gets a 401. Set a real
token in the configuration — the HTTP transport refuses to start without one.
### When the server does not appear
In order of how often it is actually the cause:
1. **The path is not absolute**, and the client's `PATH` does not have
`~/.local/bin` in it. Run `command -v weft` and paste what it prints.
2. **You added it in the wrong scope.** `claude mcp add` without `--scope`
registers it for the current directory only. `claude mcp list` from another
project will not show it.
3. **The configuration is wrong**, and WEFT said so and exited. Run the same
command by hand — `weft --transport stdio --config ...` — and read the error.
A misspelled key is refused by name, not ignored.
4. **The JSON is malformed.** Claude Desktop fails silently on this. Its logs
are in `~/Library/Logs/Claude/` on macOS and `%APPDATA%\Claude\logs` on
Windows.
### Why there is an HTTP transport at all
A local client does not need one; stdio is simpler and has no token to leak.
HTTP exists because it is the hand-off point for running this behind a model
you host yourself, on a network with no way out. Such a model, served behind
an OpenAI-compatible endpoint, talks to the same `/mcp` endpoint, and nothing
on the server side changes. WEFT already makes no network calls at runtime, so
an installed server needs nothing further.
Building that deployment — the inference cluster, the serving stack, carrying
the image and the wheels across the gap — is not part of this repository.
Appendix A of [PROJECT.md](PROJECT.md) records what it would take and stops
there, deliberately.
## Your first session, if this is all new
You do not type tool calls. This trips up everybody once, so let us get it out
of the way: you talk to the model in ordinary sentences, the model decides
which of WEFT's tools to call and with what arguments, and the client sends the
call. The JSON in this README is what goes over the wire. You never write it.
What follows is a real first session against the project bundled in this
repository. It needs no FPGA board and, for the first half, no Quartus either.
### 1. Point the workspace at something
```toml
[workspace]
root = "/home/you/weft-mcp/examples/counter"
[container]
image = "weft-tools"
```
That directory is the entire world as far as WEFT is concerned. A path that
resolves outside it is refused — symlinks resolved first, so the obvious trick
does not work either. If you get "path escapes the workspace", that is not a
bug, that is the sandbox doing the one job it has.
### 2. Ask for something
Start your client and type:
> Lint the counter design.
The model calls `lint` with the source list. On this project Verilator answers:
```
error MODMISSING: Cannot find file containing module: 'seven_seg_decoder'
excluded: ["src/seven_seg_decoder.vhd"]
```
Read that carefully, because it teaches you more than a clean run would. The
module is not missing. It is written in VHDL, Verilator does not read VHDL, and
WEFT told you exactly which file it had to leave out instead of pretending the
question was answered. If you have Questa configured, say *lint it with Questa*
and the whole thing checks clean, both languages at once.
### 3. Run a testbench
> Run the up/down counter testbench.
```
passed: true simulator: verilator
[155000] PASS: counts down when up is low
[165000] PASS: clear beats enable
[175000] PASS: wraps downwards
=== TEST PASSED ===
waveform: .weft/waves/updown_counter_tb.vcd
```
The waveform is a real file on your disk. Open it in GTKWave. WEFT does not
pretend to show it to you.
Now try the top-level one:
> Run the counter_top testbench.
```
passed: false
excluded: ["src/seven_seg_decoder.vhd"]
%Error: ... /tmp/weft-build/seven_seg_decoder.sv
```
It fails, and it fails honestly. Verilator was handed a design whose display
decoder is VHDL, could not read it, said which file it dropped, and then
tripped over the module that was consequently missing. Ask for it *with Questa*
and it passes, all three languages elaborated together, `excluded` empty. That
is the one thing a proprietary simulator buys you here and the reason it is
wired in at all.
### 4. Ask about the design
> What does the debouncer's rise_pulse port do?
The model calls `index_project` once, then `get_module_info`. The answer —
"one cycle high on every 0 -> 1 edge of clean_out" — is not the model's guess.
It is the line the author wrote in the header comment above the module, parsed
out of the source. That is the whole point of the exercise: you can tell the
difference between what was read and what was invented, because WEFT only
returns the former.
### 5. Compile it (this part needs Quartus)
> Compile the counter project.
Compilation does not block. You get a job id back immediately, because a
Quartus compile takes minutes and a tool call that sits there for four of them
is a tool call that times out. Ask *how is it going* and the model calls
`get_job_status`. Kill the server mid-compile and restart it — the job is still
there, and its final status is still correct. That was not free to build, and
it is the difference between a toy and something you can leave running.
When it finishes:
> Summarise the compilation.
```
Status: Successful. Timing met on 1 of 1 clock. 2 critical warnings to look at.
Device 10M04SAE144A7G
Fullest resource 20 %
Worst slack 0.144 ns (Hold, clk)
Lowest Fmax 154.23 MHz (clk)
```
Every one of those numbers came out of a `.rpt` file. None was rounded, guessed
or summarised by a language model on the way.
### Four things that will bite you
**Nothing happens automatically.** No watcher, no background indexing, no scan
at startup. `index_project` reads a directory when you ask. `index_document`
reads one PDF when you ask. If a search comes back empty, the usual reason is
that you never indexed the thing. Ask *what documents are indexed* — the answer
also lists what is sitting in your library that is not.
**Paths are workspace-relative, always.** `src/counter_top.sv`, not
`/home/you/...`. Both work, the second only because it is checked and rewritten,
and if it lands outside the root it is refused.
**The model is still a model.** WEFT does not make it right. It makes it
*checkable*: every claim it repeats back to you came from a tool, and you can
run the same tool yourself and get the same answer. When it tells you your
timing closed, that came from the timing analyser. When it tells you your design
is elegant, that came from nowhere.
**Read the `excluded` and the `retrieval` fields.** They are there because a
result that quietly answered a narrower question than you asked is worse than
no result. `excluded` names sources a tool could not read. `retrieval` says
whether a document search was semantic or a substring match — those rank
differently, and a ranking you misread is a citation you will misuse.
### When it goes wrong
Nothing here needs the network at runtime, so a hang is local. In order of how
often it is actually the cause:
- **The container is not built.** `podman build -t weft-tools -f containers/Containerfile.weft-tools .`
- **The workspace root does not exist**, or the path you gave is outside it.
- **Quartus is not configured.** Lint and simulate do not need it. Everything
with `compile` or `project` in the name does, and WEFT will not go looking
for it on `PATH` — the configuration says where it is or it does not run.
- **A key in the config is misspelled.** WEFT refuses unknown keys at startup
rather than ignoring them, so you get told, with the key name.
Raw logs stay on disk. `get_job_log` fetches the tail of one by name. If you
want the whole thing, it is in `output_files/` where Quartus left it.
## Simulating a mixed-language design
Verilator, Icarus and GHDL each read one language. A design written in more
than one can therefore only be simulated a module at a time, with the other
language's blocks left out — which is exactly the part a hierarchy test exists
to exercise. `simulate` says so honestly: the sources it dropped come back in
`excluded`.
Questa reads all three at once. If the configuration names it, ask for it:
```jsonc
simulate {
"files": ["src/counter_top.sv", "src/debouncer.sv", "src/updown_counter.sv",
"src/clk_tick.v", "src/seven_seg_decoder.vhd"],
"top": "counter_top_tb",
"testbench": "tb/counter_top_tb.sv",
"simulator": "questa"
}
```
`excluded` comes back empty: the VHDL decoder is elaborated inside the
SystemVerilog top, and the whole hierarchy runs. Questa is proprietary and
licensed, so it runs on the host like Quartus does and is never picked by
default — you name it.
One thing worth knowing, since it decides whether the result means anything:
`vsim -c` exits 0 after a `$fatal`, so a run that stopped on a failed
assertion looks exactly like one that passed. WEFT reads Questa's own
`TESTSTATUS` after the run instead, and reports a warning as a pass, an error
or a fatal as a failure.
## Reading your own documents
Your PDFs go in the directory `[rag] library` points at — **not** in the
workspace. Standards and vendor handbooks are a personal library that outlives
any one project, and forcing a copy into every project tree would be absurd.
That directory is mounted **read-only**, so indexing cannot write anything into
your collection, and the index lands wherever `[rag] database` says. Point the
database at a shared path and one library serves every project.
**Nothing is indexed on its own.** There is no watcher, no scan at startup, no
background pass over the library — exactly as for source code, which
`index_project` reads only when asked. `index_document` reads one document when
you name it, and never before. `list_indexed_docs` shows what is indexed *and*
what is sitting in the library un-indexed, because otherwise a model has no way
to ask for a document whose filename it has never seen.
`index_document` takes a PDF from the library and makes it searchable. Pages
with a text layer are extracted directly; only pages that come back empty — a
scan — are rendered
and passed to Tesseract, so a born-digital 1300-page standard is indexed in
seconds rather than an hour of pointless OCR.
The document is cut at its **own headings**, not into fixed-size windows, so a
result cites `1800-2017 §9.2.2.4` and you can look that up. A window number
could not be checked against anything.
Search is semantic when an embedding model is configured and textual when it is
not, and the result says which one answered — a ranking that came from a
substring match must not be mistaken for one that came from meaning. The
difference is real: against the SystemVerilog standard, "weighted random
selection" finds §18.16 *randcase* by meaning and finds nothing at all by text,
because those three words do not appear in it.
**No model ships with WEFT.** BGE-M3 is MIT-licensed and about 2 GB; you fetch
it once, point `model_path` at it, and nothing touches the network again:
```bash
DIR=~/.local/share/weft/models/bge-m3
mkdir -p $DIR/onnx
for f in onnx/model.onnx onnx/model.onnx_data tokenizer.json; do
curl -Lo $DIR/$f https://huggingface.co/BAAI/bge-m3/resolve/main/$f
done
```
Point `model_path` at `$DIR`. The graph is small; `model.onnx_data` beside it
holds the weights and is the 2 GB.
Documents are yours and stay yours. WEFT ships no standards, no handbooks and
no vendor documentation, and the index it builds never leaves your workspace.
## Generated documentation
`generate_docs` writes a reference for a project and `generate_module_doc` for
a single module. Everything in them was computed by something: port and
parameter tables from the Verible and GHDL syntax trees, per-port descriptions
from the kernel-doc headers in your sources, the pin map from the fitter's own
`.pin` report, the resource and timing figures from the compilation reports.
What it does **not** do is describe what your module is for. A generator that
invents a sentence about a port produces a document that reads like a reference
and is not one, and you cannot tell which lines were computed and which were
guessed. An undocumented port gets a dash. Prose is the client model's job —
it can read the same facts and write them up, and then you know who said what.
[`examples/counter/docs/counter.md`](examples/counter/docs/counter.md) is
committed output, not a mock-up: 177 logic elements and 154.23 MHz from a real
compilation, and GitHub draws the hierarchy diagram.
Markdown gets the hierarchy as Mermaid, which GitHub and most Markdown viewers
render. HTML gets the same tree as inline SVG instead: a browser draws no
Mermaid, and a server that makes no network calls cannot hand it a renderer, so
an HTML page carrying Mermaid source would show the source. Generated documents
are indexed for retrieval as they are written, under `doc_type: "generated"`.
## The demo project
[`examples/counter/`](examples/counter/) is a small MAX 10 counter written in
SystemVerilog, Verilog-2001 and VHDL at once. The three languages are the
point: no open-source simulator reads more than one, so the project is a fair
test of whether a tool really handles a mixed hierarchy or only claims to.
## What has not been tested on hardware
I have no FPGA board. Everything above was developed and verified against a
real Quartus Prime 25.1 Lite installation and a real Questa - Altera Starter
FPGA Edition, on real sources — the demonstration project compiles, fits,
reports 177 logic elements and 154.23 MHz, and its testbenches run — but the
bitstream has never been loaded into a device, because there is no device here
to load it into.
Concretely, that means:
- **`program_device` and `list_devices` are unverified against hardware.** When
they land, what I can check is that WEFT reaches `quartus_pgm` and
`jtagconfig` with the right arguments and reports what they say, including
reporting an empty JTAG chain as empty rather than as an error. Whether a
board on the far end actually accepts the bitstream, I cannot tell you.
- **The `.sof` and `.pof` a compilation of the demo project produces were
written by the assembler and never programmed.** They are evidence that the
flow completed, not that the design works on silicon. They are build output
and are not committed.
- **Timing closure is reported, not proven.** WEFT reads back what the timing
analyser computed. Nothing here has been correlated with a scope.
The parts that touch a board are also the parts that break in ways no unit
test finds — a cable that enumerates differently, a JTAG chain with something
unexpected on it, a device the `.pof` does not suit. If you run this against
hardware and it misbehaves, that is a bug report I want, and I will not be
able to reproduce it.
Everything else — lint, simulate, compile, parse, index, retrieve, document —
was run against the actual tools, not mocked, before it was called working.
## What CI checks
Every push runs three jobs: `ruff` for style, `pytest` on Python 3.11, 3.12 and
3.13, and a Sphinx build with warnings as errors — which is what makes the
generated tool and configuration references worth anything, since a key added
without a description fails the build rather than shipping a reference that is
quietly incomplete.
The eighteen tests that need the `weft-tools` image are not in that run — the
second badge above is theirs. They skip themselves when the image is absent,
and building it compiles GHDL from source, which measured at eight minutes on
a runner. A separate `container` workflow builds it for real and runs them where
a break would mean something: when the Containerfile or the code driving it
changes, weekly because Arch is a rolling release, and on demand. It also
asserts that the tests actually *ran* — a run where they all skipped would
otherwise pass having tested nothing.
## Contributing
Patches are welcome. WEFT follows the Linux kernel's habits: one logical
change per commit, `subsystem: summary` subjects, a body that explains *why*,
rebase rather than merge, and a `Signed-off-by:` line on everything. See
[CONTRIBUTING.md](CONTRIBUTING.md).
## Author
WEFT is written and maintained by **Mateusz Okulanis** —
[fpgartktic.github.io](https://fpgartktic.github.io/about/),
[@FPGArtktic](https://github.com/FPGArtktic), FPGArtktic@outlook.com.
Bug reports, patches and disagreements are all welcome — the last of those
especially, if you have driven this toolchain harder than I have.
## Licence
Copyright (C) 2026 Mateusz Okulanis.
GPL-3.0-only. The full text is in [COPYING](COPYING).
WEFT invokes Quartus and the containerised tools as separate programs and
distributes none of them.
## Trademarks
Intel, Altera and Quartus are trademarks of their respective owners. This
project is not affiliated with, endorsed by, or sponsored by Intel or Altera.
It contains no Intel or Altera code, files or documentation, and it neither
installs nor redistributes their software.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues