KILN

A Cargo-style CLI for SystemVerilog

Editor autocomplete: a JSON Schema for Kiln.toml is published at https://tejasprabhune.github.io/kiln/kiln-schema.json. Add #:schema https://tejasprabhune.github.io/kiln/kiln-schema.json as the first line of your Kiln.toml and any TOML LSP that honours the #:schema directive (taplo, helix, Even Better TOML, etc.) will load it. Run kiln schema to inspect the schema offline.

[package]

KeyTypeNotes
name required string Valid SystemVerilog identifier: starts with a letter or _, followed by letters, digits, or _.
version required string Semver string, e.g. "0.1.0".
authors optional list of strings Free-form. E.g. ["Jane <jane@example.com>"].
description optional string Single-line description.
license optional string SPDX expression, e.g. "MIT OR Apache-2.0".

[design]

KeyTypeDefault
top required string
aux_tops optional list of strings []
sources optional list of glob strings ["src/**/*.sv", "src/**/*.svh", "src/**/*.v"]
timescale optional string none
language optional "sv2005" | "sv2009" | "sv2012" | "sv2017" | "sv2023" tool default
include_dirs optional list of paths []
defines optional string-to-string map {}
libraries optional list of strings []
test_sources optional list of glob strings [] (falls back to tests/*.sv)

timescale passes --timescale 1ns/1ps to both slang and verilator — useful for designs that mix files with and without `timescale directives. language maps to --std (slang) and --default-language (verilator). defines entries with an empty string become +define+FOO; non-empty values become +define+FOO=bar. include_dirs entries must exist on disk when loading an existing project. aux_tops lists additional top modules that slang elaborates alongside top (useful for non-instantiated helpers like Xilinx's glbl); Verilator only supports a single --top-module and ignores this list.

[design]
top          = "soc_top"
sources      = ["rtl/**/*.sv", "ip/**/*.sv"]
timescale    = "1ns/1ps"
language     = "sv2017"
include_dirs = ["rtl/include", "ip/include"]
defines      = { SIMULATION = "", WIDTH = "8" }

[dependencies]

Each entry is a named dependency in one of three forms:

[dependencies]
# git dep with semver constraint (matched against repo tags)
axi = { git = "https://github.com/pulp-platform/axi.git", version = "0.39" }

# git dep pinned to a specific tag or commit SHA
common_cells = { git = "https://github.com/pulp-platform/common_cells.git", rev = "v1.32.0" }

# path dep relative to the project root (or absolute)
local_ip = { path = "../local_ip" }

Resolved versions are written to Kiln.lock, which should be committed. Use kiln update to refresh it.

[lint]

The lint table has three layers. Canonical rules (top-level keys) are kiln-defined names that map to equivalents in both slang and verilator. [lint.slang] and [lint.verilator] accept tool-native names and overlay on top of the canonical rules, winning on conflict.

Severity values: "error", "warn", "off", "deny".

[lint]
width-trunc     = "error"   # promote truncation warnings to errors
case-incomplete = "warn"
unused          = "warn"

[lint.slang]
relax-enum-conversions = "off"   # slang-only option

[lint.verilator]
GENUNNAMED   = "warn"   # verilator-only warning code
DECLFILENAME = "off"

If you're unsure of the name for a specific warning, kiln check prints it in brackets after the message — e.g. warning: ... [width-trunc]. Run kiln lint list to see all known canonical rules and kiln lint explain <name> for a description.

Canonical lint rules

NameDefaultWhat it covers
width-truncwarnImplicit truncation when assigning a wider value to a narrower type.
case-incompletewarncase statement does not cover all values and has no default.
unusedwarnNet or variable declared but never driven or read.
implicit-netwarnNet used without an explicit declaration.
port-coercionwarnType coercion at a port connection boundary.

Rules not in this table must be specified under [lint.slang] or [lint.verilator] using tool-native names. For slang, any name accepted by slang -W<name> is valid; run slang --help-diagnostics for the full list.

[tool.slang]

KeyTypeDefault
path optional path found on PATH
extra_args optional list of strings []

[tool.verilator]

KeyTypeDefault
path optional path found on PATH
threads optional integer none
trace optional false | "vcd" | "fst" false
trace_structs optional bool false
trace_params optional bool false
trace_depth optional integer none
coverage optional bool false
timing optional bool false
x_assign optional "0" | "1" | "fast" | "unique" none (release profile keeps "0")
bbox_unsup optional bool false
extra_args optional list of strings []

trace_structs, trace_params and trace_depth are only emitted when trace is "vcd" or "fst". timing is required for designs that use delays or event control. bbox_unsup black-boxes unsupported constructs (vendor primitives like Xilinx PLLE2_ADV) so verilator can elaborate the rest of the design.

[tool.verible]

KeyTypeDefault
path optional path found on PATH
extra_args optional list of strings []

extra_args is a permanent escape hatch — not a deprecation target. The typed fields cover the common cases; extra_args handles the rest. At -v (verbose), kiln logs the fully resolved command line for each subprocess invocation so you can verify what extra_args produces.

[profile.<name>]

Profiles override design, lint, and tool settings per build context. Built-in names: dev (default for kiln build/check), release (--release shorthand), test (default for kiln test). Custom names are allowed.

Vec fields in profile overrides replace the base value (not append) — this makes it possible to remove a flag in one profile that exists in another. Map fields merge with the overlay winning on key conflicts.

[profile.release.tool.verilator]
extra_args = ["-O3"]

[profile.test.tool.verilator]
trace    = "fst"
coverage = true

[profile.test.lint]
unused = "error"

Select a profile with --profile <name> or --release.

[features]

Cargo-shaped conditional compilation. Each named feature contributes additional +define+ flags and source globs when active. Feature names must be valid SystemVerilog identifiers.

[features]
default = ["sim"]

[features.sim]
defines = ["SIM"]

[features.debug]
defines = ["DEBUG=1", "VERBOSITY=2"]
sources = ["src/debug/**/*.sv"]
KeyTypeDefault
default optional list of strings []

Each [features.<name>] block accepts:

KeyTypeDefault
defines optional list of strings []
sources optional list of glob strings []

Each defines entry is either NAME (becomes +define+NAME) or NAME=VALUE (+define+NAME=VALUE). CLI flags mirror cargo: --features sim,debug activates listed features on top of the default set; --all-features activates every defined feature; --no-default-features starts from an empty selection.

[vendor.<name>]

Group vendor libraries (Xilinx unisims, Altera megafunctions, custom stub sets) under a named block. Each block contributes sim-model sources, synth-only stubs, and verilator blackbox names.

[vendor.xilinx]
sim_models       = ["hardware/sim_models/BUFG.sv", "hardware/sim_models/glbl.sv"]
stubs            = ["hardware/stubs/PLLE2_ADV.sv"]
blackbox_modules = ["MMCME2_ADV", "PLLE2_ADV"]
KeyTypeDefault
sim_models optional list of glob strings []
stubs optional list of glob strings []
blackbox_modules optional list of strings []

sim_models and stubs globs are appended to the resolved source set today; the field names telegraph that a future synthesis backend will keep stubs out of simulation. Each entry in blackbox_modules becomes --bbox <name> to verilator so the module body is not compiled.

[[firmware]]

Embedded firmware artifacts produced by an external build system and consumed by RTL tests. kiln test runs every declared firmware build once (deduped by (path, build)) before any per-test prebuild.

[[firmware]]
name = "isa_tests"
path = "software/riscv-isa-tests"
build = "make"
artifacts = "*.hex"
KeyTypeNotes
name required string Free-form identifier, must be a valid SystemVerilog identifier.
path required path Directory the build runs in, relative to project root.
build required string Shell command run inside path.
artifacts optional string Glob (relative to path) describing produced files.

[hooks]

Project-level shell escapes for kiln subcommand lifecycle phases. Each value is a single shell line executed at the project root. Empty strings are treated as unset.

[hooks]
pre-check  = ""
pre-build  = "make -C ip/"
pre-test   = "git submodule update --init"
post-test  = "echo done"
PhaseFires
pre-checkBefore slang elaboration in kiln check.
pre-buildBefore verilator in kiln build (and the build phase of kiln run / kiln test).
pre-testBefore any testbench is started by kiln test.
post-testAfter kiln test finishes (regardless of pass/fail).

Pre-* hook failures abort the parent subcommand. post-test failures are logged but never change the test outcome.

[wave]

KeyTypeDefault
format optional "fst" | "vcd" "fst"
enabled_by_default optional bool false

When enabled_by_default = true, every kiln test run behaves as if --trace was passed. FST is smaller and faster than VCD; prefer it unless your waveform viewer requires VCD.

Complete example

[package]
name        = "my_soc"
version     = "0.1.0"
authors     = ["Jane <jane@example.com>"]
description = "A parameterized SoC"
license     = "MIT OR Apache-2.0"

[design]
top          = "soc_top"
sources      = ["rtl/**/*.sv"]
timescale    = "1ns/1ps"
language     = "sv2017"
include_dirs = ["rtl/include"]
defines      = { SIMULATION = "", WIDTH = "8" }

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

[lint]
width-trunc     = "error"
case-incomplete = "warn"

[lint.slang]
relax-enum-conversions = "off"

[tool.slang]
extra_args = ["--allow-hierarchical-const"]

[tool.verilator]
threads    = 4
trace      = false
extra_args = ["--x-assign", "0"]

[profile.release.tool.verilator]
extra_args = ["-O3"]

[profile.test.tool.verilator]
trace    = "fst"
coverage = true

[profile.test.lint]
unused = "error"

[wave]
format             = "fst"
enabled_by_default = false