Skip to content

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:

  1. Compile & Run: JDK + sbt — compiles and runs Scala/SpinalHDL code
  2. RTL Generation: SpinalHDL itself — converts Scala descriptions to Verilog
  3. 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:

StageToolPurpose
Compile & RunJDK 17 + sbtCompile and run Scala/SpinalHDL code
RTL GenerationSpinalHDL 1.12.0Scala → Verilog
Simulationiverilog + SpinalSimVerilog simulation with Scala co-simulation
High-Perf SimVerilatorCompiles to C++, much faster
Waveform ViewerGTKWaveOpen VCD/FST waveform files
Build ToolchainMSYS2 (g++ / Boost)Required for SpinalSim VPI plugin compilation

1. Prerequisites

Before installing the toolchain, ensure the following are in place:

DependencyNotesCheck Command
ScoopPackage managerscoop --version
GitRequired by Scoop for bucket managementgit --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 | iex

Verify:

scoop --version

All tools are installed under the apps/ subdirectory.

Install JDK 17, sbt and iverilog

scoop bucket add java
scoop install openjdk17 sbt iverilog

MSYS2 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 --version

2. Project Structure

mkdir -p src/main/scala src/test/scala project

project/build.properties

sbt.version=2.0.6

build.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
Last updated on