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]| Key | Type | Notes | |
|---|---|---|---|
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]| Key | Type | Default | |
|---|---|---|---|
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.
| Name | Default | What it covers |
|---|---|---|
width-trunc | warn | Implicit truncation when assigning a wider value to a narrower type. |
case-incomplete | warn | case statement does not cover all values and has no default. |
unused | warn | Net or variable declared but never driven or read. |
implicit-net | warn | Net used without an explicit declaration. |
port-coercion | warn | Type 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]| Key | Type | Default | |
|---|---|---|---|
path |
optional | path | found on PATH |
extra_args |
optional | list of strings | [] |
[tool.verilator]| Key | Type | Default | |
|---|---|---|---|
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]| Key | Type | Default | |
|---|---|---|---|
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"]
| Key | Type | Default | |
|---|---|---|---|
default |
optional | list of strings | [] |
Each [features.<name>] block accepts:
| Key | Type | Default | |
|---|---|---|---|
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"]
| Key | Type | Default | |
|---|---|---|---|
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"
| Key | Type | Notes | |
|---|---|---|---|
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"
| Phase | Fires |
|---|---|
pre-check | Before slang elaboration in kiln check. |
pre-build | Before verilator in kiln build (and the build phase of kiln run / kiln test). |
pre-test | Before any testbench is started by kiln test. |
post-test | After 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]| Key | Type | Default | |
|---|---|---|---|
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.
[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