Verifrog.Vcd 1.2.0

dotnet add package Verifrog.Vcd --version 1.2.0
                    
NuGet\Install-Package Verifrog.Vcd -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Verifrog.Vcd" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Verifrog.Vcd" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Verifrog.Vcd" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Verifrog.Vcd --version 1.2.0
                    
#r "nuget: Verifrog.Vcd, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Verifrog.Vcd@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Verifrog.Vcd&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Verifrog.Vcd&version=1.2.0
                    
Install as a Cake Tool

VCD Parser Guide

Verifrog.Vcd is a standalone F# library for parsing and analyzing Value Change Dump (VCD) waveform files. It's independent of Verifrog.Sim — you can use it in any .NET project, not just Verifrog tests.

VCD files are the standard waveform format produced by Verilator (--trace), Icarus Verilog ($dumpfile/$dumpvars), and most other Verilog simulators. They record every signal transition during simulation.

When to use VCD analysis

  • Post-simulation assertions: Verify timing relationships, pulse counts, or signal sequences that are easier to check after the fact than cycle-by-cycle
  • Debugging: Find when a signal first went to an unexpected value, or what values it took over a time range
  • Regression checking: Compare waveform properties (transition counts, timing) across runs
  • Coverage analysis: Check which values a signal exercised

Quick start

Add a reference to Verifrog.Vcd in your .fsproj:

<ProjectReference Include="$(VERIFROG_ROOT)/src/Verifrog.Vcd/Verifrog.Vcd.fsproj" />

Parse a VCD file and query it:

open Verifrog.Vcd

// Parse the entire file
let vcd = VcdParser.parseAll "output/sim.vcd"

// What signals are in this dump?
printfn "Total signals: %d" vcd.Signals.Length
for s in vcd.Signals do
    printfn "  %s [%d bits]" s.FullPath s.Width

// Find signals by name
let clocks = VcdParser.findSignals vcd "clk"
let allCounters = VcdParser.findSignals vcd "count*"

// Get transitions for a specific signal
let trans = VcdParser.transitions vcd "counter.count"
printfn "count had %d transitions" trans.Length

// What was the value at a specific time?
match VcdParser.valueAtTime vcd "counter.count" 50000L with
| Some t -> printfn "count = %d at t=%d ps" t.IntVal t.Time
| None   -> printfn "No data for count at that time"

Parsing

Parse everything

let vcd = VcdParser.parseAll "sim.vcd"

Reads the entire file, tracking all signals. Fine for small-to-medium VCD files (up to a few hundred MB).

Parse with a time limit

let vcd = VcdParser.parse "sim.vcd" 1_000_000L    // Stop at t=1,000,000 ps (1 us)

For large dumps, stop parsing early. The time is in VCD time units — typically picoseconds (check your simulator's $timescale).

Parse with signal filtering

let vcd = VcdParser.parseFiltered "sim.vcd" ["count"; "overflow"; "fsm*"] 0L

Only track transitions for signals matching the patterns. This dramatically reduces memory usage for large designs where you only care about a few signals. The last argument is the time limit (0L = no limit).

Pattern matching

Signal patterns support three forms:

Pattern Matches
count Any signal with leaf name count, or full path ending in .count
counter.count Exact full path match
fsm* Any signal whose full path contains fsm or leaf name starts with fsm

The VcdFile type

After parsing, you get a VcdFile with three fields:

type VcdFile = {
    Signals: VcdSignal list                                    // All signals in the header
    SignalMap: IReadOnlyDictionary<string, VcdSignal>          // Signal ID -> metadata
    Transitions: IReadOnlyDictionary<string, VcdTransition list>  // Full path -> transitions
}

VcdSignal

type VcdSignal = {
    Id: string        // Short VCD identifier (e.g., "!")
    LeafName: string  // Just the signal name (e.g., "count")
    FullPath: string  // Hierarchical path (e.g., "counter.count")
    Width: int        // Bit width
}

VcdTransition

[<Struct>]
type VcdTransition = {
    Time: int64    // Time in VCD time units (typically picoseconds)
    Bits: string   // Raw bit string (e.g., "01101001")
    IntVal: int    // Integer value (-1 if x/z)
}

The IntVal field treats x and z bits as 0 for the integer conversion. If the signal contains unknown values, IntVal will be -1 only if the entire value string is unparseable. For mixed x/0/1 values, check the Bits string directly.

Query API

Find signals

// Find by name (leaf or full path)
let signals = VcdParser.findSignals vcd "count"

// Find by glob
let allFsm = VcdParser.findSignals vcd "fsm*"

// Result is a list of VcdSignal
for s in signals do
    printfn "%s [%d bits]" s.FullPath s.Width

Get transitions

// All transitions for a signal (time-ordered)
let trans = VcdParser.transitions vcd "counter.count"

for t in trans do
    printfn "  t=%d ps  value=%d  bits=%s" t.Time t.IntVal t.Bits

// Count transitions
let n = VcdParser.transitionCount vcd "counter.count"

Value at a specific time

Returns the last transition at or before the given time:

match VcdParser.valueAtTime vcd "counter.count" 100_000L with
| Some t ->
    printfn "count = %d at t=%d ps" t.IntVal t.Time
    // t.Time may be earlier than 100_000 if the signal hasn't changed since then
| None ->
    printfn "No transitions for this signal before t=100,000 ps"

Find first occurrence of a value

match VcdParser.firstTimeAtValue vcd "fsm_state" 5 with
| Some time -> printfn "FSM entered state 5 at t=%d ps (%.3f us)" time (VcdParser.timeToUs time)
| None      -> printfn "FSM never reached state 5"

Unique values

let states = VcdParser.uniqueValues vcd "fsm_state"
// e.g., [0; 1; 2; 3; 5]  — state 4 was never visited
printfn "FSM visited %d states: %A" states.Length states

High-pulse count

Count 0-to-1 transitions for a 1-bit signal:

let pulses = VcdParser.highPulseCount vcd "counter.overflow"
printfn "Overflow pulsed %d times" pulses

Helpers

// Parse a binary string to int (x/z treated as 0)
let v = VcdParser.parseBinValue "01101001"    // 105

// Convert picoseconds to microseconds
let us = VcdParser.timeToUs 50_000_000L       // 50.0

Examples in tests

Verify overflow pulse count

test "overflow pulses correct number of times" {
    use sim = SimFixture.create ()
    sim.Write("enable", 1L) |> ignore
    sim.Step(1000)

    let vcd = VcdParser.parseAll "output/sim.vcd"
    let pulses = VcdParser.highPulseCount vcd "counter.overflow"
    Expecto.Expect.equal pulses 3 "should overflow 3 times in 1000 cycles"
}

Check timing relationship

test "done asserts within 100 cycles of start" {
    use sim = SimFixture.create ()
    // ... run stimulus ...

    let vcd = VcdParser.parseAll "output/sim.vcd"
    let startTime =
        VcdParser.firstTimeAtValue vcd "start" 1
        |> Option.defaultWith (fun () -> failwith "start never asserted")
    let doneTime =
        VcdParser.firstTimeAtValue vcd "done" 1
        |> Option.defaultWith (fun () -> failwith "done never asserted")

    let cycleDelta = (doneTime - startTime) / 10_000L   // assuming 10ns clock period
    Expecto.Expect.isLessThan cycleDelta 100L "done should assert within 100 cycles"
}

Check FSM coverage

test "FSM visits all states" {
    use sim = SimFixture.create ()
    // ... run stimulus ...

    let vcd = VcdParser.parseAll "output/sim.vcd"
    let states = VcdParser.uniqueValues vcd "fsm_state"
    let expected = [0; 1; 2; 3; 4; 5]
    for s in expected do
        Expecto.Expect.contains states s $"FSM should visit state {s}"
}

Filtered parsing for large dumps

test "check only the signals we care about" {
    // Only track FSM and output signals — ignore everything else
    let vcd = VcdParser.parseFiltered "output/big_sim.vcd" ["fsm*"; "output*"] 0L
    let fsmSignals = VcdParser.findSignals vcd "fsm*"
    printfn "Tracking %d FSM signals out of %d total" fsmSignals.Length vcd.Signals.Length
    // ...
}

Command-line analysis

For quick VCD analysis without writing F# code, use the VCD CLI tool.

Performance notes

  • The parser reads files sequentially in a single pass (two passes: header, then transitions)
  • Signal filtering (parseFiltered) skips transition recording for non-matching signals, saving memory
  • Time limiting (parse with maxTime > 0) stops reading early
  • For very large VCD files (multi-GB), use both filtering and time limiting
  • VcdTransition is a value type ([<Struct>]) to minimize GC pressure
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 41 4/3/2026
1.1.0 49 4/1/2026
1.0.0 49 3/31/2026