Purpose
A framework for structural pattern matching in hardware netlists. SVQL enables security analysts and hardware designers to algorithmically identify potential security vulnerabilities, design patterns, and structural anomalies in RTL designs using a Rust-based domain-specific language (DSL) and graph-based matching algorithms.
SVQL addresses the challenge of identifying hardware security vulnerabilities early in the design cycle when they are least expensive to fix. Unlike traditional static analysis tools that operate on abstract syntax trees (ASTs) and are limited by syntactic representations, SVQL operates on flattened gate-level netlists, enabling it to recognize structurally equivalent but syntactically distinct implementations of vulnerable patterns.
Key Innovations
- DSL-Based Query Authoring: Define patterns as Algebraic Data Types (ADTs) rather than hand-written traversal algorithms.
- Graph-Based Matching: Subgraph isomorphism engine that recognizes structural equivalence across different coding styles.
- Hierarchical Composition: Build complex queries by composing atomic patterns (Netlists) into hierarchical structures (Composites) and polymorphic unions (Variants).
- Production Performance: Parallel execution with intelligent join planning and caching, capable of analyzing million-gate designs.
Data Flow
- Ingestion:
svql_driverloads Verilog/RTLIL via Yosys, buildsGraphIndex(topological order + connectivity maps). - Definition: User defines patterns using
svql_macrosDSL (#[derive(Netlist)],#[derive(Composite)]). - Compilation: Macros generate trait implementations for
svql_querytraits (schema, search logic, rehydration). - Execution:
svql_querybuilds execution DAG, schedules parallel search viasvql_subgraph(for Netlists) or direct indexing (for Primitives). - Storage: Results stored in columnar
Table<T>structures withinStore, enabling cross-pattern references. - Reporting:
svql_clirenders hierarchical match trees, CSV exports, or LaTeX tables.
| Pattern Kind | Description | Use Case |
|---|---|---|
| Netlist | Atomic pattern matching an external Verilog/RTLIL module via subgraph isomorphism. | Standard cells, specific module instances. |
| Primitive | Direct cell-type match using design index (bypasses subgraph search). | AND gates, DFFs, basic logic elements. |
| Composite | Hierarchical composition of sub-patterns with connectivity constraints. | CWEs spanning multiple modules, complex structures. |
| Variant | Polymorphic union matching any of several alternative implementations. | DFF-with-enable (primitive OR mux-based). |
| Recursive | Self-referential tree patterns built via fixpoint iteration. | Logic cones, gate trees, arbitrary-depth chains. |
Patterns are declared as Rust types with attributes:
// 1. Atomic primitive
#[derive(Netlist)]
#[netlist(file = "gates.v", module = "and_gate")]
struct AndGate {
#[port(input)] a: Wire,
#[port(input)] b: Wire,
#[port(output)] y: Wire,
}
// 2. Hierarchical composition
#[derive(Composite)]
#[connection(from = ["unlock", "y"], to = ["reg", "write_en"])]
struct Cwe1234 {
#[submodule] unlock: UnlockLogic,
#[submodule] reg: DffEnable,
}
// 3. Polymorphic choice
#[derive(Variant)]
#[variant_ports(input(clk), output(q))]
enum AnyDff {
#[map(clk = ["clk"], q = ["q"])] Basic(Dff),
#[map(clk = ["clk"], q = ["q"])] WithEnable(DffEn),
}- Columnar Storage: Results stored in
Table<T>with columns for wires (Wire), submodules (Ref<T>), and metadata. - Lazy Evaluation: Dependency DAG ensures each sub-query executes exactly once; parallel dispatch via
rayon. - Connectivity Indexing: Composite queries use pre-computed bipartite indices to avoid Cartesian products.
| Crate | Purpose | Key Types |
|---|---|---|
svql_common |
Foundational types and Yosys integration | Wire, CellKind, GraphIndex, YosysModule, Config |
svql_driver |
Design lifecycle management and caching | Driver, DriverKey, DesignContainer |
svql_macros |
DSL code generation | #[derive(Netlist)], #[derive(Composite)], #[derive(Variant)] |
svql_query |
Query execution engine and storage | Table<T>, Store, ExecutionPlan, Pattern trait |
svql_subgraph |
Subgraph isomorphism algorithm | SubgraphMatcher, SingleAssignment, AssignmentSet |
svql_query_lib |
Production pattern library | Cwe1234, Cwe1271, AndGate, DffEnable, LogicCone |
svql_cli |
Command-line interface | Args, QueryArg, QueryMetrics |
# Clone repository
git clone https://github.com/nickrallison/svql
cd svql
# Build CLI (requires Yosys in PATH)
cargo build --release -p svql_cli
# Run tests
cargo test --workspace# Run all registered queries on default design
./target/release/svql_cli
# Run specific CWE query on custom design
./target/release/svql_cli \
-d "design.json --module top_module" \
-q cwe1234 \
--profile
# Export results to CSV and LaTeX
./target/release/svql_cli \
-d "design.json -m top" \
-q cwe1234 -q cwe1280 \
--output-csv results.csv \
--output-latex results.texuse svql_query::prelude::*;
use svql_query_lib::security::cwe1234::Cwe1234;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize driver with workspace root
let driver = Driver::new_workspace()?;
// Define target design
let key = DriverKey::new("design.json", "top_module");
let config = Config::builder()
.parallel(true)
.match_length(MatchLength::NeedleSubsetHaystack)
.build();
// Execute query
let store = svql_query::run_query::<Cwe1234>(&driver, &key, &config)?;
// Process results
if let Some(table) = store.get::<Cwe1234>() {
println!("Found {} matches", table.len());
for (ref, row) in table.rows() {
let instance = Cwe1234::rehydrate(&row, &store, &driver, &key, &config)?;
println!("Match: unlock logic drives {:?}", instance.dff_enable.write_en());
}
}
Ok(())
}- Scalability: Tested on designs up to 350k+ gates (HummingbirdV2, Hack@DAC).
- Parallelism: Multi-threaded execution at query, design, and algorithm levels.
-
Caching: Design loading and indexing cached per
DriverKey; query results cached inTableSlot. -
Optimization: Single-cell primitives bypass subgraph isomorphism; composite queries use connectivity indices to avoid
$O(n^k)$ joins.
Example performance (AMD Ryzen 9 7950X, 16 cores):
| Design | Gates | Query | Time (MT) | Memory |
|---|---|---|---|---|
| e203_soc_top | 354k | Cwe1234 | 54ms | 37MB |
| tile (Hack@DAC21) | 124k | Cwe1280 | 209ms | 65MB |
Requirements:
- Rust 1.91+ (2024 edition)
- Yosys (for Verilog/RTLIL synthesis)
- Optional: TikV-Jemalloc (enabled in release profile)
# Debug build
cargo build
# Release build with optimizations
cargo build --release
# Run specific crate tests
cargo test -p svql_subgraph
cargo test -p svql_query- Define pattern in
svql_query_libor new crate:
#[derive(Composite)]
#[connection(from = ["src", "y"], to = ["dst", "a"])]
struct MyPattern {
#[submodule] src: AndGate,
#[submodule] dst: OrGate,
}- Register in
svql_cli/src/args.rs:
register_queries!(QueryArg {
// ... existing queries
MyPattern => crate::my_pattern::MyPattern,
});- Rebuild CLI and run:
cargo run -p svql_cli -- -q my_patternSVQL fits into a hardware security workflow as an early-stage screening tool:
- RTL Development: Designer writes SystemVerilog.
- Synthesis: Yosys flattens and optimizes design.
- Query Execution: SVQL identifies structural patterns matching CWE signatures.
- Manual Review: Analysts examine reported matches for true positives.
- Formal Verification: Confirmed vulnerabilities undergo formal property checking.
This approach narrows the search space for manual review without requiring formal assertions early in the design cycle.