the guide · 3How do you run CS223 labs on a Mac without Vivado?

How do you build the chain by hand?

These are the commands the installer runs, at the same pinned commits. Vivado does five jobs for a lab; six free tools do them here, two of them for the bitstream:

your_design.sv
  |-> Icarus Verilog   -> simulation
  |-> Yosys            -> synthesis (reads SystemVerilog through the yosys-slang plugin)
  |-> nextpnr-xilinx   -> place and route
  |-> fasm2frames      -> configuration frames
  |-> xc7frames2bit    -> your_design.bit
  `-> openFPGALoader   -> the board, over USB

Three come from Homebrew, three you compile (nextpnr-xilinx, prjxray, and the yosys-slang plugin that Yosys loads to read SystemVerilog), and fasm2frames is a Python script inside the prjxray checkout. nextpnr also needs a chip database, generated once by another Python script. Everything lands in ~/fpga: about 0.3 GB with the installer (237 MB measured on 7 October; it deletes the clones and build trees after each build; the optional agent SDK adds 51 MB), 1.76 GB with the full clones below until section 3.7 removes what the builds no longer need. The Makefile in section 4 expects exactly this layout: ~/fpga/venv, ~/fpga/nextpnr-xilinx/build, ~/fpga/chipdb/xc7a35t.bin, ~/fpga/prjxray, ~/fpga/yosys-slang/build/slang.so.

3.1 Homebrew packages

brew install yosys openfpgaloader icarus-verilog cmake ninja eigen pkg-config python@3.14

The first three are tools from the diagram; the rest are what the two builds below need. A few hundred MB. Check:

yosys -V                   # Yosys 0.69+post
openFPGALoader --Version   # v1.1.1
iverilog -V | head -1      # Icarus Verilog version 13.0 (stable)

Newer numbers are expected; only the ones in section 9 were tested.

3.2 Python venv

fasm2frames (3.5) and the chip database generator (3.4) are Python scripts. Homebrew's Python refuses a plain pip install (externally-managed-environment), so they get a private one.

mkdir -p ~/fpga
"$(brew --prefix)/opt/python@3.14/bin/python3.14" -m venv ~/fpga/venv
~/fpga/venv/bin/pip install fasm==0.0.2.post88 pyyaml==6.0.3 textx==4.4.0 simplejson==4.1.2 intervaltree==3.2.1 numpy==2.5.3 pyjson5==2.0.1

Check (prints nothing when it works):

~/fpga/venv/bin/python -c "import fasm, yaml, textx, simplejson, intervaltree"

3.3 nextpnr-xilinx

Place and route. Homebrew has no macOS build, so you compile it. The clone is most of the download; the compile is about 1.5 minutes on this machine.

cd ~/fpga
git clone --recursive https://github.com/openXC7/nextpnr-xilinx.git
cd nextpnr-xilinx
git checkout 3fd78784c7788f93f276358edf5477221cc6c179
git submodule update --init --recursive
cmake -B build -G Ninja \
  -DARCH=xilinx -DCMAKE_BUILD_TYPE=Release \
  -DUSE_OPENMP=OFF -DBUILD_GUI=OFF -DBUILD_PYTHON=OFF -DBUILD_TESTS=OFF
ninja -C build -j3 nextpnr-xilinx bbasm

-DUSE_OPENMP=OFF is required: Apple's compiler has no OpenMP (fopenmp). -j3 fits 8 GB of RAM; with 16 GB or more use -j$(sysctl -n hw.ncpu). Check:

~/fpga/nextpnr-xilinx/build/nextpnr-xilinx --version
# "nextpnr-xilinx" -- Next Generation Place and Route (Version 0.9.6)

3.4 Chip database

nextpnr needs a description of the XC7A35T. It is not in the download; you generate it once (no-chipdb). About 35 seconds and 860 MB of RAM while it runs.

mkdir -p ~/fpga/chipdb
cd ~/fpga/nextpnr-xilinx
~/fpga/venv/bin/python xilinx/python/bbaexport.py \
  --xray xilinx/external/prjxray-db/artix7 \
  --metadata xilinx/external/nextpnr-xilinx-meta/artix7 \
  --device xc7a35tcpg236-1 \
  --constids xilinx/constids.inc \
  --bba ~/fpga/chipdb/xc7a35t.bba
build/bbasm -l ~/fpga/chipdb/xc7a35t.bba ~/fpga/chipdb/xc7a35t.bin
rm ~/fpga/chipdb/xc7a35t.bba

Check:

ls -lh ~/fpga/chipdb/xc7a35t.bin
# about 90 MB

Another 7-series board would need its own --device. Untested.

3.5 prjxray

Turns nextpnr's text output into the binary .bit: one Python script, one compiled tool. About 2 minutes.

cd ~/fpga
git clone --recursive https://github.com/f4pga/prjxray.git
cd prjxray
git checkout c9f02d8576042325425824647ab5555b1bc77833
git submodule update --init --recursive
~/fpga/venv/bin/pip install --no-deps -e .
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DPRJXRAY_BUILD_TESTING=OFF
cmake --build build --target xc7frames2bit -j3

--recursive is required (yaml-cpp-not-built). -DCMAKE_POLICY_VERSION_MINIMUM=3.5 is required (cmake-policy-version-minimum). prjxray's own README says to install Vivado 2017.2; that is for mapping a new chip, and ours is mapped. Ignore it. Check:

ls ~/fpga/prjxray/build/tools/xc7frames2bit ~/fpga/prjxray/utils/fasm2frames.py

Both paths print back. The full check is make check in section 4, one line per tool.

3.6 yosys-slang

The SystemVerilog reader. On its own page: 3.6 yosys-slang.

3.7 What can go after the builds?

The chain that runs is 237 MB (measured with du on 7 October 2026); the rest of the 1.76 GB is clones and build trees. The installer deletes them after each build. By hand, keep these and delete the rest of the four checkouts (every .git, the build/ trees around the files below, the other devices under prjxray-db):

The Makefile of section 4 points at nothing else, so the hand-built chain lands at the same size.


previous: 2 How do you install it in one line?
next: 3.6 yosys-slang
This section in the whole guide

Stuck on an error? Every error from this setup, filed under the exact line it printed.