Simulation runners¶
The runner is the boundary between a human command line and a cocotb testbench. Keep it small: select the DUT, source tree, test module and configuration here; keep stimulus, expected data and bus setup in the testbench package.
All runners select verilator unless SIM is set. Use SIM=questa for an
interactive debug session or a generated design that requires Questa libraries.
Choose the entry point¶
| Entry point | Use it for |
|---|---|
rtl_test_cocotb() |
Plain Verilog or SystemVerilog sources. |
intel_component_test_cocotb() |
A component description that must be generated first. |
platform_test_cocotb() |
An already generated Platform Designer simulation tree. |
run_rtl_test() |
A concise command-line wrapper around the RTL runner. |
run_intel_component_test() |
A concise command-line wrapper around the component runner. |
Low-level functions are useful from pytest or a Python tool. run_* wrappers
are normally the right choice for simulation/<name>/run_test.py: they prepare
paths and logs, accept -g, and ignore unrelated pytest options.
Plain RTL¶
rtl_test_cocotb() either receives explicit sources or recursively discovers
.v and .sv files in source_dirs. It creates sim_build_<SIM>, forwards
HDL parameters, runs the cocotb module, and treats a failed results XML as a
failed Python process.
import os
from pathlib import Path
from fpga_verification.sim.runners import rtl_test_cocotb
def run_plain_rtl():
os.environ["SIM"] = "verilator"
rtl_test_cocotb(
project_root=Path("."),
hdl_toplevel="stream_component",
test_module="stream.test_pyuvm",
sources=[Path("src/stream_component.sv")],
parameters={"DATA_WIDTH": 32},
compile_log=Path("logs/verilator_compile.log"),
)
A typical source layout is intentionally boring:
Use explicit sources when source order matters. Otherwise pass source directories and let the runner discover HDL files.
Generated component¶
intel_component_test_cocotb() runs ip-generate, reads the generated .spd
file, joins generated composition HDL with source HDL, adds selected tool
models, then calls the RTL runner. The normal generated directory is temporary.
from pathlib import Path
from fpga_verification.sim.runners import intel_component_test_cocotb
def run_generated_component(generate_only=False):
return intel_component_test_cocotb(
project_root=Path("."),
component_file=Path("src/component_hw.tcl"),
source_dirs=[Path("src/hw")],
hdl_toplevel="component",
test_module="stream.test_pyuvm",
component_parameters={"DATA_WIDTH": 32},
build_args=["-Wno-PARAMNODEFAULT"],
generate_only=generate_only,
ip_generate_log=Path("logs/ip_generate.log"),
compile_log=Path("logs/verilator_compile.log"),
)
Use generate_only=True to retain and return the generated directory without
starting simulation. Use retain_generated=True after a normal run when a
failed generated tree needs investigation. Generated trees are build artifacts,
not source files: they depend on the selected tool version and simulator.
The generation flow is:
source_dirs
-> ip-make-ipx --thorough-descent
-> generated components catalogue
-> ip-generate
-> generated composition HDL + original source HDL
-> rtl_test_cocotb
Useful component options include part, project_directory,
quartus_model_files, ip_search_paths and make_ipx.
One configuration for HDL and cocotb¶
For a generated DUT, use one resolved ComponentConfig. Fields marked with
hdl_parameter() are passed to generation; the same resolved object is
serialised into the cocotb environment. The testbench restores it with
load_runtime_config().
from dataclasses import dataclass
from fpga_verification.sim import ComponentConfig, hdl_parameter
@dataclass(frozen=True)
class TestConfig(ComponentConfig):
data_width: int = hdl_parameter(32, name="DATA_WIDTH")
queue_depth: int = hdl_parameter(8, name="QUEUE_DEPTH")
case_note: str = "smoke"
# Host side, in run_test.py
run_intel_component_test(..., config=get_test_config())
# Cocotb side, in test_pyuvm.py
cfg = load_runtime_config(TestConfig)
Do not also pass a conflicting component_parameters mapping. A missing,
stale or malformed runtime configuration is an error by design: creating a
new default config in cocotb would hide a mismatch with generated HDL. See
Project style for the ownership rule.
Platform Designer system¶
platform_test_cocotb() targets an existing generated simulation tree.
With Questa it compiles through msim_setup.tcl and uses the generated
libraries. With Verilator it extracts a compatible source list from that setup.
Other simulator values are rejected.
from fpga_verification.sim.platform_designer import platform_test_cocotb
platform_test_cocotb(
project_root="platforms/system/sim",
hdl_toplevel="system",
test_module="system.test_pyuvm",
debug=False,
)
Keep the project root, top-level name, selected simulator and installed tool version consistent with the generated tree.
A human-readable run_test.py¶
A command-line runner should be readable without opening the environment. It may contain documented simulator workarounds; it must not calculate expected results or construct agents.
import os
from pathlib import Path
from fpga_verification.sim.runners import run_intel_component_test
from .config import get_test_config
if __name__ == "__main__":
sim = os.getenv("SIM", "verilator")
build_args = ["-Wno-PARAMNODEFAULT"] if sim == "verilator" else []
run_intel_component_test(
project_root=Path(__file__).resolve().parent,
component_file="../../src/component_hw.tcl",
hdl_toplevel="component",
test_module="stream.test_pyuvm",
config=get_test_config(),
enable_questa_acc=True,
build_args=build_args,
)
Run it with:
SIM=verilator python -m simulation.stream.run_test
SIM=questa python -m simulation.stream.run_test -g
Debugging¶
-g enables debug mode in both script wrappers.
| Simulator | Debug result |
|---|---|
| Verilator | Enables tracing and creates dump.vcd; there is no GUI. |
| Questa | Starts the GUI, enables signal access, writes waves and runs wave.do when present. |
For Questa without the GUI, set QUESTA_ACC=1 to retain signal visibility.
compile_log and ip_generate_log keep tool output outside the source tree.