From Netlist to Silicon: Place and Route with NextPNR on WSL
FPGA · Digital Logic · Open-Source Toolchain
From Netlist to Silicon: Place and Route with NextPNR on WSL
Place and route: mapping a generic netlist onto real silicon
Welcome back to the final installment of our AppliedKaos beginner series on low-latency FPGA design!
In Part 1, we wrote and simulated a SystemVerilog glitch filter. In Part 2, we used Yosys to synthesize that code into a generic hardware netlist — proving our logic was physically buildable.
Today, we cross the finish line. We are going to take that generic netlist, map it to a specific physical FPGA chip, and generate the final binary file (the bitstream) that makes the hardware tick. To do this, we'll use NextPNR and the Project IceStorm tools, remaining entirely within our open-source WSL Ubuntu 22.04 ecosystem.
📌 This is Part 3, the final part, of the AppliedKaos FPGA flow series. New here? Start with Part 2: Synthesizing with Yosys.
The Final Hurdles: PnR and Bitstreams
Before we dive into the commands, let's clarify what is actually happening in this final stage.
What is Place and Route (PnR)?
Yosys gave us a netlist of logic gates and flip-flops, but it didn't tell us where those components should physically sit on the FPGA die. FPGAs are grids of thousands of identical Logic Cells (LCs) and routing switches.
Placement
Assigning each gate from your netlist to a specific physical Logic Cell on the chip.
Routing
Activating the microscopic silicon switches to connect the wires between those specific cells, ensuring signals arrive on time.
What is a Bitstream?
FPGAs are SRAM-based, meaning they forget their configuration when powered off. A bitstream is the literal .bin file containing the millions of 1s and 0s that tell the FPGA exactly how to configure its internal switches on boot-up.
Our Target: The Lattice iCE40
For this tutorial, we will target the Lattice iCE40 family — specifically, the popular and affordable iCEstick development board (which houses an iCE40-HX1K). The iCE40 was the first FPGA to be fully reverse-engineered, giving birth to the robust open-source toolchain we are using today.
Step-by-Step Instructions
Ensure your OSS CAD Suite is still active in your WSL terminal. If you open a new terminal, remember to run:
export PATH="$HOME/tools/oss-cad-suite/bin:$PATH"
Step 1: Create a Physical Constraints File (PCF)
Your SystemVerilog module has generic inputs and outputs (clk, rst_n, glitchy_in, clean_out). The FPGA doesn't know which physical metal pins on the outside of the chip correspond to these names. We define this mapping in a PCF (Physical Constraints File).
Create a new file in your ~/kaos_fpga_filter directory named glitch_filter.pcf:
# glitch_filter.pcf - Pin mapping for Lattice iCEstick (HX1K TQ144 package)
# The onboard 12MHz clock
set_io clk 21
# Mapping our inputs to the expansion header pins
set_io rst_n 78
set_io glitchy_in 112
# Mapping our output to one of the onboard green LEDs
set_io clean_out 95
Step 2: Place and Route with NextPNR
Now we feed our Yosys-generated JSON netlist and our PCF constraints file into NextPNR.
Run the following command in your terminal:
nextpnr-ice40 --hx1k --package tq144 --json glitch_filter_netlist.json --pcf glitch_filter.pcf --asc glitch_filter.asc
Let's break this down:
nextpnr-ice40: The specific NextPNR executable for the iCE40 architecture.--hx1k --package tq144: Tells the router exactly which silicon die and plastic package we are using.--json: The input netlist from Yosys.--pcf: Our pin mapping file.--asc: The output file format (an ASCII text representation of the routed chip).
Note: You will see an output log detailing the device utilization. Since our glitch filter is tiny, it will likely use less than 1% of the available logic cells! NextPNR will also report the maximum estimated clock frequency.
--freq 12 to the command above to explicitly tell NextPNR your target clock frequency (12 MHz for the onboard iCEstick oscillator). NextPNR will then report whether your design actually meets timing at that frequency, instead of just its best-effort estimate.
nextpnr-ice40 --hx1k --package tq144 --freq 12 \
--json glitch_filter_netlist.json \
--pcf glitch_filter.pcf \
--asc glitch_filter.asc
Step 3: Generate the Bitstream with Icepack
The .asc file is human-readable, but the FPGA needs binary. We use a tool from Project IceStorm called Icepack to compress and pack the ASCII file into the final bitstream.
Run this command:
icepack glitch_filter.asc glitch_filter.bin
Congratulations! You now have a glitch_filter.bin file. This is the custom hardware personality you just created.
Step 4: Flashing the FPGA (The WSL Caveat)
The final step is to flash the .bin file to the FPGA using a tool called Iceprog:
iceprog glitch_filter.bin
iceprog from inside WSL, you have two choices:
- The Native Way: Copy the
glitch_filter.binfile to your Windows desktop and run the Windows version oficeprog(or a GUI tool like Icestudio) directly from Windows. - The USB/IP Way: Install
usbipd-winon Windows, which allows you to physically pass the USB connection of your FPGA board through the hypervisor and into your Ubuntu 22.04 environment.
# Windows PowerShell (run as Administrator), USB/IP method:
usbipd list
usbipd bind --busid <BUSID>
usbipd attach --wsl --busid <BUSID>
# Then back inside WSL:
lsusb # confirm the FPGA programmer shows up
iceprog glitch_filter.bin
Bonus: One-Command Full Pipeline
Once every stage works individually, chain them into a single Makefile target that goes straight from synthesis to a flashed chip:
# Makefile - AppliedKaos full FPGA pipeline (synth -> PnR -> bitstream -> flash)
TOP = glitch_filter
PCF = glitch_filter.pcf
.PHONY: all pnr bitstream flash clean
all: flash
pnr:
nextpnr-ice40 --hx1k --package tq144 --freq 12 \
--json $(TOP)_netlist.json --pcf $(PCF) --asc $(TOP).asc
bitstream: pnr
icepack $(TOP).asc $(TOP).bin
flash: bitstream
iceprog $(TOP).bin
clean:
rm -f *.asc *.bin
make flash
Troubleshooting Common PnR Errors
| Error / Symptom | Likely Cause | Fix |
|---|---|---|
ERROR: Unable to place cell ... exhausted |
Design is too large for the target chip, or a port isn't matching the PCF | Double-check stat output from Yosys against the chip's actual logic cell count |
| NextPNR reports Fmax far below your target | Logic is too deep between registers (long combinational paths) | Pipeline the design with additional register stages, or lower --freq |
iceprog can't find the device |
USB not passed through to WSL, or wrong permissions | Use usbipd passthrough (Step 4) or flash from native Windows instead |
PCF pin errors on set_io |
Pin numbers copied from a different board/package | Confirm your exact board + package (e.g. HX1K TQ144) against its official pinout |
Recommended Gear
Affiliate disclosure: AppliedKaos is a participant in the Amazon Associates program. If you buy through these links, I may earn a small commission at no extra cost to you — it helps keep this blog running. I only recommend gear I've actually used or would use myself.
| Item | Why It's Worth It | Link |
|---|---|---|
| Lattice iCEstick (iCE40-HX1K) | The exact board this tutorial's PCF file targets — no pin-mapping guesswork. | Check price → |
| Spare micro-USB cable | iCEstick boards are notorious for shipping without one, or with a charge-only cable. | Check price → |
| FPGA Prototyping by SystemVerilog Examples — Pong P. Chu | Good next step once you're flashing real bitstreams and want more project ideas. | Check price → |
FAQ
Do I have to use WSL, or can I run this on native Linux?
Everything except the USB flashing step is identical on native Linux or macOS. Native Linux users skip the usbipd workaround entirely — iceprog just talks to the USB device directly.
What does the PCF file actually do?
It maps the generic port names in your Verilog/SystemVerilog module to the physical pin numbers of your specific FPGA package, so NextPNR knows which physical pin each signal belongs to.
Can I target a different iCE40 board than the iCEstick?
Yes — change --hx1k --package tq144 to match your chip and package (e.g. --hx8k --package ct256 for an iCE40-HX8K breakout board), and rewrite the PCF pin numbers for that board's schematic.
Why does NextPNR report a maximum clock frequency?
It's estimating the longest signal delay path (critical path) after routing. If that estimate is below your actual clock speed, your design may not function reliably at that frequency without further optimization.
What if my design doesn't fit on the chip?
NextPNR will report a placement failure. Check the stat output from your Yosys synthesis step to see your actual cell count versus the target chip's available logic cells.
Conclusion
You've done it! You have traversed the complete open-source FPGA pipeline: from simulating deterministic, low-latency logic in SystemVerilog, to synthesizing gates with Yosys, and finally routing physical silicon paths with NextPNR.
By mastering this workflow, you aren't just writing software; you are sculpting bespoke hardware architectures tailored perfectly to your high-speed, low-latency needs.
Stay Kaotic,
The AppliedKaos Team
Missed a step? Revisit Part 2: Synthesizing with Yosys →
Disclosure: This post contains affiliate links. If you make a purchase through them, AppliedKaos may earn a small commission at no extra cost to you. All recommendations are based on genuine use and opinion.
Comments
Post a Comment