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

NextPNR routing a Yosys netlist onto a Lattice iCE40 FPGA die, generating a bitstream 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.

Who is this guide for? Engineers who've already synthesized a design with Yosys (see Part 2) and now want to map that netlist onto a real FPGA chip, generate a bitstream, and flash actual hardware.

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.

Diagram showing logic cells on an FPGA grid being placed and routed by NextPNR

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.

Recommended Resource If you don't already own one, the Lattice iCEstick is the cheapest way to follow this exact tutorial with zero pin-mapping guesswork, since the PCF below is written for it. Check price on Amazon →

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
Pro tip Pin numbers are package-specific, not chip-specific — a TQ144 iCE40-HX1K and a CT256 iCE40-HX8K use completely different pin numbers for the same physical function. Always pull pin numbers from your exact board's schematic or datasheet, never copy them from a different board's tutorial.

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.

Pro tip Add --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
Important WSL note By default, WSL 2 does not have access to your Windows USB ports. To use iceprog from inside WSL, you have two choices:
  1. The Native Way: Copy the glitch_filter.bin file to your Windows desktop and run the Windows version of iceprog (or a GUI tool like Icestudio) directly from Windows.
  2. The USB/IP Way: Install usbipd-win on 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 / SymptomLikely CauseFix
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.

ItemWhy It's Worth ItLink
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.

A flashed iCEstick FPGA board with an LED lit, completing the open-source FPGA design flow

Stay Kaotic,
The AppliedKaos Team

Missed a step? Revisit Part 2: Synthesizing with Yosys →

View Companion Code on GitHub

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

Popular Posts