SpinalHDL Windows Setup Guide
SpinalHDL is great for quickly building FPGA hardware verification demos, but setting up the environment has its pitfalls — especially on Windows. This guide is a distilled walkthrough from my own recent setup experience.
The toolchain breaks down into three parts:
- Compile & Run: JDK + sbt — compiles and runs Scala/SpinalHDL code
- RTL Generation: SpinalHDL itself — converts Scala descriptions to Verilog
- Simulation: including the lightweight iverilog (my go-to), the mainstream Verilator (compiles to C++, much faster), and the waveform viewer GTKWave
One key detail: SpinalSim co-simulation requires g++ to compile VPI plugins. On Windows, going through WSL to call Verilator and friends adds unnecessary complexity, so this guide uses MSYS2 as a unified solution for the C++ toolchain.
Target scope: a fast academic-demo SpinalHDL environment — sufficient for small-to-medium design verification, not necessarily for large-scale production flows. All tool installation is done through Scoop for easy command-line deployment and package management.
This guide also walks through a complete demo project setup, which you can follow to verify the entire toolchain is working correctly.
The complete toolchain:
| Stage | Tool | Purpose |
|---|---|---|
| Compile & Run | JDK 17 + sbt | Compile and run Scala/SpinalHDL code |
| RTL Generation | SpinalHDL 1.12.0 | Scala → Verilog |
| Simulation | iverilog + SpinalSim | Verilog simulation with Scala co-simulation |
| High-Perf Sim | Verilator | Compiles to C++, much faster |
| Waveform Viewer | GTKWave | Open VCD/FST waveform files |
| Build Toolchain | MSYS2 (g++ / Boost) | Required for SpinalSim VPI plugin compilation |
1. Prerequisites
Before installing the toolchain, ensure the following are in place:
| Dependency | Notes | Check Command |
|---|---|---|
| Scoop | Package manager | scoop --version |
| Git | Required by Scoop for bucket management | git --version |
Install Scoop
Scoop defaults to ~\scoop\ (i.e. C:\Users\<username>\scoop\). To use a custom path (e.g. D: drive), set the environment variable before installing:
$env:SCOOP = 'D:\Scoop'Install Scoop (run in PowerShell):
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
irm get.scoop.sh | iexVerify:
scoop --versionAll tools are installed under the
apps/subdirectory.
Install JDK 17, sbt and iverilog
scoop bucket add java
scoop install openjdk17 sbt iverilogMSYS2 Toolchain
scoop install msys2
$bash = "$env:USERPROFILE\scoop\apps\msys2\current\usr\bin\bash.exe"
& $bash -lc "pacman -Syu --noconfirm"
& $bash -lc "pacman -Su --noconfirm"
& $bash -lc "pacman -S --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-boost mingw-w64-x86_64-verilator perl-Pod-Parser"Configure PATH
$msys2bin = "$env:USERPROFILE\scoop\apps\msys2\current\mingw64\bin"
$currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
[System.Environment]::SetEnvironmentVariable('Path', "$currentPath;$msys2bin", 'User')Reopen terminal, then verify:
java -version && sbt --version && iverilog -V && gtkwave --version && g++ --version && verilator --version2. Project Structure
mkdir -p src/main/scala src/test/scala projectproject/build.properties
sbt.version=2.0.6build.sbt
ThisBuild / version := "0.1.0"
ThisBuild / scalaVersion := "2.13.16"
ThisBuild / organization := "com.example"
val spinalVersion = "1.12.0"
lazy val root = (project in file("."))
.settings(
name := "spinal-project",
libraryDependencies ++= Seq(
"com.github.spinalhdl" %% "spinalhdl-core" % spinalVersion,
"com.github.spinalhdl" %% "spinalhdl-lib" % spinalVersion,
compilerPlugin("com.github.spinalhdl" %% "spinalhdl-idsl-plugin" % spinalVersion),
"com.github.spinalhdl" %% "spinalhdl-sim" % spinalVersion,
"org.scalatest" %% "scalatest" % "3.2.19" % Test,
),
Test / fork := true,
Test / javaOptions += "-Dspinal.sim.defaultBackend=iverilog",
)src/main/scala/Adder.scala
import spinal.core._
import spinal.lib._
case class Adder(width: Int) extends Component {
val io = new Bundle {
val a = in UInt(width bits)
val b = in UInt(width bits)
val result = out UInt(width bits)
}
io.result := io.a + io.b
}
object AdderMain extends App {
SpinalVerilog(Adder(8))
println("Verilog generated!")
}src/test/scala/AdderSim.scala
import spinal.core._
import spinal.core.sim._
import scala.util.Random
object AdderSim extends App {
SimConfig
.withIVerilog // Use iverilog backend (capital V)
.withWave // Generate VCD waveform
.withConfig(SpinalConfig(defaultConfigForClockDomains = ClockDomainConfig(resetKind = SYNC)))
.compile(Adder(8))
.doSim { dut =>
dut.clockDomain.forkStimulus(10)
for (_ <- 0 until 100) {
val a = Random.nextInt(256)
val b = Random.nextInt(256)
dut.io.a #= a
dut.io.b #= b
dut.clockDomain.waitRisingEdge()
assert(dut.io.result.toLong == ((a + b) & 0xFF),
s"FAIL: $a + $b != ${dut.io.result.toLong}")
}
println("ALL 100 TESTS PASSED!")
}
}3. Build & Run
# Generate Verilog
sbt "runMain AdderMain"
# Run SpinalSim simulation (sbt 2.x syntax)
sbt "Test / runMain AdderSim"
# View waveform
gtkwave simWorkspace\Adder\test\wave.vcd