KILN

A Cargo-style CLI for SystemVerilog

tl;dr

Install

kiln itself only needs a Rust toolchain. The runtime tools (Slang, Verilator, etc.) are installed separately via kiln install-tools.

# macOS and Linux (no Rust required)
curl -fsSL https://raw.githubusercontent.com/tejasprabhune/kiln/main/install.sh | sh

# or via cargo
cargo install kiln-sv

# install the runtime tools (slang, verilator, verible, surfer, bender)
kiln install-tools

# or build slang and verilator from source (needs cmake + C++17)
kiln install-tools --build-from-source

New project

kiln new counter
cd counter
kiln check    # elaboration via slang
kiln build    # compile via verilator
kiln test     # run testbenches under tests/
kiln doctor   # env probe + project sanity checks

Verify your environment

kiln env prints every external tool kiln drives, the version it found, and where it lives on PATH. kiln doctor runs the same probe and additionally validates your manifest, lockfile, vendor source globs, and firmware paths. Useful for filing bug reports and diagnosing CI environments.

kiln env       # tools + versions only
kiln doctor    # tools + project-level health

Edit-test loop with kiln watch

kiln watch watches the project tree (excluding target/ and .git/) and re-runs a subcommand on every relevant change, debounced 200 ms so a single editor save fires once. It runs the subcommand once immediately so you see current state without waiting for an edit.

kiln watch check          # fast slang elaboration on every save
kiln watch test           # full test sweep
kiln watch test alu mul   # filter to substring matches
kiln watch build          # rebuild via verilator
kiln watch fmt            # `kiln fmt --check` (CI gate)

CI determinism: --locked and --frozen

Two global flags gate dependency resolution against drift, mirroring cargo and uv. --locked errors if a refresh would change Kiln.lock; --frozen implies --locked and additionally refuses to make any network request during dependency resolution.

kiln test --locked              # CI: fail if Kiln.lock drifted from Kiln.toml
kiln build --frozen             # CI: also no network; use existing Kiln.lock as-is
kiln update --locked            # no-op; verify the lockfile is current
kiln update --frozen            # error: contradictory

Both flags compose with every plan-producing subcommand (build, run, check, test, doc, tree). They also compose with --features and --profile.

Test reporters for CI

kiln test --reporter junit writes target/kiln/junit.xml in the de-facto Jenkins / GitLab JUnit XML dialect and suppresses human output. GitHub Actions, GitLab CI, Buildkite, Jenkins, and CircleCI all consume this format directly.

kiln test --reporter junit --locked
# target/kiln/junit.xml — one <testcase> per discovered test

Pass -v to also include captured stdout in the XML as <system-out>. Failures and timeouts emit <failure type="failure"> and <failure type="timeout"> respectively, with the captured stderr inside.

Migrate an existing project

Run kiln init in the project root. It creates a Kiln.toml and a .gitignore entry for build artifacts. Then point [design] at your existing source tree:

cd my_existing_project
kiln init

# edit Kiln.toml: set top module and adjust sources glob
[package]
name    = "my_design"
version = "0.1.0"

[design]
top     = "my_top"
sources = ["rtl/**/*.sv", "rtl/**/*.v"]

If you have existing bender or git dependencies, add them under [dependencies] — see the Dependencies section.

Editor setup

kiln lsp wraps slang-server, giving you diagnostics, hover, go-to-definition (including into dependencies), completions, references, and inlay hints. Install it first:

kiln install-tools --tools slang-server

The binary lands at ~/.local/share/kiln/bin/. Make sure that's on your PATH, or set KILN_SLANG_SERVER_PATH to override discovery.

Neovim ≥ 0.11

-- ~/.config/nvim/lsp/kiln.lua
return {
  cmd          = { 'kiln', 'lsp' },
  filetypes    = { 'systemverilog', 'verilog' },
  root_markers = { 'Kiln.toml' },
}
-- ~/.config/nvim/init.lua
vim.lsp.enable('kiln')

Open any .sv file inside a project. Neovim launches kiln lsp from the project root and diagnostics appear within a second. To restart after editing Kiln.toml: :LspRestart.

Neovim with nvim-lspconfig

require('lspconfig.configs').kiln = {
  default_config = {
    cmd       = { 'kiln', 'lsp' },
    filetypes = { 'systemverilog', 'verilog' },
    root_dir  = require('lspconfig.util').root_pattern('Kiln.toml'),
  },
}
require('lspconfig').kiln.setup {}

Autoformat on save (Neovim)

kiln fmt wraps Verible. Wire it as a format command so your editor calls it on save:

-- format .sv files with kiln fmt on save
vim.api.nvim_create_autocmd('BufWritePost', {
  pattern  = { '*.sv', '*.svh', '*.v' },
  callback = function()
    local file = vim.fn.expand('%:p')
    vim.fn.jobstart({ 'kiln', 'fmt', file }, {
      on_exit = function() vim.cmd('edit') end,
    })
  end,
})

Or use kiln fmt --check in CI to fail on unformatted files without modifying anything.

Build, test, and waveforms

Check and build

kiln check           # fast: slang elaboration only, no simulation
kiln build           # compile with verilator (debug profile)
kiln build --release
kiln run             # build + execute the simulator binary
kiln clean           # remove target/kiln/

Builds are content-hash cached. A recompile only happens when source files, defines, or the build profile change.

Tests

Place testbenches under tests/. Each .sv file is a test; its filename stem is the top module name. A test passes when it prints PASS to stdout and exits 0.

kiln test                  # run all tests in parallel
kiln test smoke            # substring filter
kiln test --jobs 1         # serial
kiln test --fail-fast      # stop on first failure
kiln test --list           # print test names and exit

Waveforms

kiln test --trace          # dump FST files to target/kiln/waves/
kiln wave                  # open the most recent FST in surfer
kiln wave smoke            # open a specific test's FST
kiln wave --print-path     # print the path without opening surfer

Dependencies

kiln manages dependencies via Bender. Resolved versions are locked in Kiln.lock, which should be committed.

# add a git dependency
kiln add axi --git https://github.com/pulp-platform/axi.git --version 0.39

# add a path dependency
kiln add local_ip --path ../local_ip

# remove a dependency
kiln remove axi

# update all deps to the latest matching versions
kiln update

# print the dependency graph
kiln tree

Dependency sources are picked up automatically by kiln build, kiln check, and kiln lsp — no manual include path management.

Kiln.toml

A minimal manifest. The only required fields are name, version, and top.

[package]
name        = "counter"
version     = "0.1.0"
description = "A simple counter"

[design]
top          = "counter"
sources      = ["src/**/*.sv"]     # default: src/**/*.sv + .v + .svh
include_dirs = ["include"]         # passed as -I to slang and verilator
defines      = { SIMULATION = "" } # +define+ flags

[dependencies]
axi = { git = "https://github.com/pulp-platform/axi.git", version = "0.39" }

[lint]
width-trunc  = "error"   # promote to error
unused-net   = "warn"    # emit as warning
implicit-net = "allow"   # suppress entirely

Lint rule keys are slang's optionName strings. Unknown keys anywhere in the file are rejected to catch typos early.