Makefile vs YAML: Modernizing Verification Simulation Flows
Introduction
Automation has become the backbone of modern
SystemVerilog/UVM verification environments. As designs scale from block-level
modules to full SoCs, engineers rely heavily on scripts to orchestrate
compilation, simulation, and regression. The effectiveness of these automation
flows directly impacts verification quality, turnaround time, and team
productivity.
For many years, the Makefile has been the tool of choice for
managing these tasks. With its rule-based structure and wide availability,
Makefiles offered a straightforward way to compile RTL, run simulations, and
execute regressions. This approach served well when testbenches were relatively
small and configurations were simple.
However, as verification
complexity has exploded, the limitations of Makefiles have become increasingly
apparent. Mixing execution rules with hardcoded test configurations leads to
fragile scripts that are difficult to scale or reuse across projects. Debugging
syntax-heavy Makefiles often takes more effort than writing new tests,
diverting attention from coverage and functional goals.
These challenges point toward the need for a more modular and
human-readable alternative. YAML, a structured configuration language,
addresses many of these shortcomings when paired with Python for execution.
Before diving into this solution, it is important to first examine how today’s
flows operate and where they struggle.
Current Scenario and Challenges
In most verification environments today, Makefiles remain the default choice for controlling compilation, simulation, and regression. A single Makefile often governs the entire flow—compiling RTL and testbench sources, invoking the simulator with tool-specific options, and managing regressions across multiple testcases. While this approach has been serviceable for smaller projects, it shows clear limitations as complexity increases.
- Configuration Management: Testlists are commonly hardcoded in text or CSV files, with seeds, defines, and tool flags scattered across multiple scripts. Updating or reusing these settings across projects is cumbersome.
- Readability and Debugging: Makefile syntax is compact but cryptic, which makes debugging errors non-trivial. Even small changes can cascade into build failures, demanding significant engineering time.
- Scalability: As testbenches grow, adding new test cases or regression suites quickly bloats the Makefile. Managing hundreds of tests or regression campaigns becomes unwieldy.
- Tool Dependence: Each Makefile is typically tied to a specific simulator (e.g., VCS, Questa, Xcelium). Porting the flow to a different tool requires major rewrites.
- Limited Reusability: Teams often reinvent similar flows for different projects, with little opportunity to share or reuse scripts.
These
challenges shift the engineer’s focus away from verification quality and
coverage goals toward the mechanics of scripting and tool debugging. As a
result, the industry needs a cleaner, modular, and more portable way to manage
verification flows.
Makefile-Based Flow
A traditional Makefile-based verification
flow centers around a single file containing multiple targets that handle
compilation, simulation, and regression tasks. Consider this representative
structure:
compile:
vcs +define+UVM_NO_DPI -timescale=1ns/1ps \
-sverilog +incdir+./include \
./rtl/*.sv ./tb/*.sv -o simv
simulate:
./simv +UVM_TESTNAME=basic_test
+UVM_VERBOSITY=LOW \
+seed=12345 +ntb_random_seed=67890
regress: compile
@for test in basic_test advanced_test
stress_test; do \
./simv
+UVM_TESTNAME=$$test +seed=`date +%s`; \
done
This approach offers clear strengths:
immediate familiarity to software engineers, no additional tool requirements,
and straightforward dependency management. For small teams with stable tool
chains, this simplicity remains compelling.
However, significant challenges emerge with
scale. Cryptic syntax becomes problematic—escaped backslashes, shell
expansions, and dependencies create arcane scripting rather than readable
configuration. Debug cycles lengthen with cryptic error messages, and
modifications require deep Make expertise.
Tool coupling is evident above—compilation
flags, executable names, and runtime arguments are VCS-specific. Supporting
Questa requires duplicating rules with different syntax, creating
synchronization challenges.
Maintenance overhead grows exponentially.
Adding tests requires multiple modifications, parameter changes demand careful
shell escaping, and regression management quickly outgrows Make's capabilities,
forcing hybrid scripting solutions.
These drawbacks motivate the search for a
more human-readable, reusable configuration approach—which is where YAML's
structured, declarative format offers compelling advantages for modern
verification flows.
YAML-Based Flow
YAML (YAML Ain't Markup Language) provides a
human-readable data serialization format that transforms verification flow
management through structured configuration files. Unlike Makefiles' imperative
commands, YAML uses declarative key-value pairs with intuitive
indentation-based hierarchy.
Consider this YAML configuration structure
that replaces complex Makefile logic:
# build.yml - Compilation Configuration
project:
name: "soc_verification"
tool: "vcs"
compile:
files:
- "includes.svh"
- "design.sv"
- "testbench.sv"
defines:
- "UVM_NO_DPI"
flags:
- "-timescale=1ns/1ps"
- "-sverilog"
includes:
- "./include"
# sim.yml - Simulation Configuration
simulation:
testname: "basic_test"
verbosity: "LOW"
seed: 12345
waves: true
coverage: true
plusargs:
- "+UVM_TESTNAME=${testname}"
- "+UVM_VERBOSITY=${verbosity}"
The modular structure becomes immediately
apparent through organized directory hierarchies. As shown in Figure 1, a
well-structured YAML-based verification environment separates configurations by
function and scope, enabling different team members to modify their respective
domains without conflicts. Block-level engineers manage component-specific test
configurations (IP1, IP2),[GU1] [MS2] while integration teams focus on pipeline
and regression management. Instead of monolithic Makefiles, teams can organize
configurations across focused files: build.yml for compilation settings,
sim.yml for simulation parameters, and various test-specific YAML files grouped
by functionality.
Figure 1: YAML-Based Verification Directory Structure
Advanced YAML features like anchors and
aliases eliminate configuration duplication using the DRY (Don't Repeat
Yourself) principle:
# Common configuration anchor
common_flags: &default_flags
- "-timescale=1ns/1ps"
- "-sverilog"
# Reuse across
multiple builds
debug_build:
flags:
- *default_flags
- "+define+DEBUG"
release_build:
flags:
- *default_flags
- "+define+SYNTHESIS"
Tool independence emerges naturally since
YAML contains only configuration data, not tool-specific commands. The same
YAML files can drive VCS, Questa, or XSIM simulations through appropriate
Python parsing scripts, eliminating the need for multiple Makefiles per tool.
Of course, YAML alone doesn't execute
simulations—it needs a bridge to EDA tools. This is achieved by pairing YAML
with lightweight Python scripts that parse configurations and generate
appropriate tool commands.
Implementation of YAML-Based Flow
The transition from YAML configuration to
actual EDA tool execution follows a systematic four-stage process, as
illustrated in Figure 2[GU3] [MS4] . This implementation addresses the
traditional verification challenge where engineers spend excessive time writing
complex Makefiles and managing tool commands instead of focusing on
verification quality.
Figure 2: YAML to EDA Tool
Bridge
YAML files serve as comprehensive configuration containers supporting diverse verification needs:
- Project Metadata: Project name, descriptions, version control
- Tool Configuration: EDA tool selection, licenses, version specifications
- Compilation Settings: Source files, include directories, defines, timescale, tool-specific flags
- Simulation Parameters: Tool flags, snapshot paths, log directory structures
- Test Specifications: Test names, seeds, plusargs, coverage options
- Regression Management: Test lists, reporting formats, parallel execution settings
Figure 3: Python YAML parsing workflow phases
The Python implementation demonstrates
the complete flow pipeline. Starting with a simple YAML configuration:
#
Build and sim YAML
project:
name: "soc_verification"
tool: "vcs"
compile:
files:
- "rtl/design.sv"
- "tb/testbench.sv"
defines: ["UVM_NO_DPI"]
flags:
- ["-timescale=1ns/1ps",
"-sverilog"]
simulation:
testname: "basic_test"
seed: 12345
coverage: true
The Python script loads and processes
this configuration:
import
yaml
def
load_yaml_config(config_file):
"""Load and parse YAML
configuration"""
with open(config_file, 'r') as f:
config = yaml.safe_load(f)
return config
def
build_compile_command(config):
"""Build compilation command
from YAML config"""
tool = config['project']['tool']
files = '
'.join(config['compile']['files'])
defines = ' '.join([f'+define+{d}' for d in
config['compile']['defines']])
flags = '
'.join(config['compile']['flags'])
cmd = f"{tool} {flags} {defines}
{files} -o simv"
return cmd
def
build_simulation_command(config):
"""Build simulation command
from YAML config"""
testname = config['simulation']['testname']
seed = config['simulation']['seed']
coverage = "+coverage" if
config['simulation']['coverage'] else ""
cmd = f"./simv
+UVM_TESTNAME={testname} +seed={seed} {coverage}"
return cmd
# Main
execution flow
if
__name__ == "__main__":
config = load_yaml_config('build.yml')
print("=== YAML to Command Translation
===")
print(f"Project:
{config['project']['name']}")
print(f"Tool:
{config['project']['tool']}")
# Display compile command
compile_cmd = build_compile_command(config)
print("Generated Commands:")
print(f" {compile_cmd}")
# Display simulation command
sim_cmd = build_simulation_command(config)
print(f" {sim_cmd}")
When executed, the Python script produces clear output
showing the command translation:
$
python yaml_flow.py
===
YAML to Command Translation ===
Project:
soc_verification
Tool:
vcs
Generated
Commands:
vcs -timescale=1ns/1ps -sverilog
+define+UVM_NO_DPI rtl/design.sv tb/testbench.sv -o simv
./simv +UVM_TESTNAME=basic_test +seed=12345
+coverage
The complete processing workflow operates in four systematic phases, as detailed in Figure 3:
1. Load & Parse: The PyYAML library converts YAML file content into
native Python dictionaries and lists, making configuration data accessible
through standard Python operations.
2. Extract: The script accesses configuration values using dictionary keys,
retrieving tool names, file lists, compilation flags, and simulation parameters
from the structured data.
3. Build Commands: The parser intelligently constructs tool-specific shell
commands by combining extracted values with appropriate syntax for the target
simulator (VCS or Xcelium).
4. Display/Execute: Generated commands are shown for verification or
directly executed through subprocess calls, launching the actual EDA tool
operations.
This implementation creates true tool-agnostic operation.
The same YAML configuration generates VCS, Questa, or XSIM commands by simply
updating the tool specification. The Python translation layer handles all
syntax differences, making flows portable across EDA environments without
configuration changes.
The complete pipeline—from human-readable YAML to
executable simulation commands—demonstrates how modern verification flows can
prioritize engineering productivity over infrastructure complexity, enabling
teams to focus on test quality rather than tool mechanics.
Comparison: Makefile vs YAML
Both
approaches have clear strengths and weaknesses that teams should evaluate based
on their specific needs and constraints. Table 1 provides a systematic
comparison across key evaluation criteria.
Table 1: Makefile
vs YAML Flow Comparison
|
Aspect |
Makefile-Based
Flow |
YAML-Based Flow |
|
Readability |
Complex syntax with shell commands and
escaping |
Clean, human-readable key-value format |
|
Maintainability |
Difficult - mixed logic and configuration |
Easy - clear separation of config and logic |
|
Ease of Modification |
Requires scripting knowledge for changes |
Simple text edits in configuration files |
|
Reusability |
Hard to port across projects/tools |
Highly reusable across different
environments |
|
Scalability |
Becomes unwieldy with large test suites |
Scales naturally with structured
hierarchies |
|
Tool Independence |
Tool-specific rules and commands |
Abstract configs work with multiple tools |
|
Setup Complexity |
No additional dependencies required |
Requires Python and parsing infrastructure |
Where Makefiles Work Better:
- Simple projects with stable, unchanging requirements
- Small teams already familiar with Make syntax
- Legacy environments where changing infrastructure is risky
- Direct execution needs for quick debugging without intermediate layers
- Incremental builds where dependency tracking is crucial
Where YAML Excels:
- Growing complexity with multiple test configurations
- Multi-tool environments supporting different simulators
- Team collaboration where readability matters
- Frequent modifications to test parameters and configurations
- Long-term maintenance across multiple projects
The Reality:
Most teams start with Makefiles for simplicity but eventually hit scalability
walls. YAML approaches require more initial setup but pay dividends as projects
grow. The decision often comes down to whether you're optimizing for immediate
simplicity or long-term scalability.
For
established teams managing complex verification environments, YAML-based flows
typically provide better returns on investment. However, teams should consider
practical factors like migration effort and existing tool integration before
making the transition.
Conclusion
The
challenges with traditional Makefile flows are clear: cryptic syntax that's
hard to read and modify, tool-specific configurations that don't port between
projects, and maintenance overhead that grows with complexity. As verification
environments become more sophisticated, these limitations consume valuable
engineering time that should focus on actual test development and coverage
goals.
YAM_L-based
flows address these fundamental issues through human-readable configurations,
tool-independent designs, and modular structures that scale naturally. Teams
can describe verification intent simply—"run 100 iterations with
coverage"—while the flow engine handles all tool complexity automatically.
The same approach works from block-level testing to full-chip regression
suites.
Key Benefits Realized:
- Faster onboarding: New team members understand YAML configurations immediately
- Reduced maintenance: Configuration changes require simple text edits, not scripting
- Better collaboration: Clear syntax eliminates the "Makefile expert" bottleneck
- Tool flexibility: Switch between VCS, Questa, or XSIM without rewriting flows
- Project portability: YAML configurations move cleanly between different projects
The choice between Makefile and YAML approaches ultimately depends on project complexity and team goals. Simple, stable projects may continue benefiting from Makefile simplicity. However, teams managing growing test suites, multiple tools, or frequent configuration changes will find YAML-based flows provide better long-term returns on their infrastructure investment.