t27.aiРусский

The VCD format

You will learn

What a .vcd file actually contains, and how to read its dump of every signal.

A waveform file is text, and reading it as text is a skill. A .vcd declares its timescale, names its scopes and variables, then records only changes: a timestamp, an identifier, a value. The lesson's spec, vcd_trace, models exactly that -- headers, changes, durations -- with tests over earliest and latest change. The widget is a real trace wrapped up as a card: the testbench toggled clk 585 times and left 4 signals at X for the whole run, both facts counted from the file, not remembered from the screen.

Try it

Read the wrapped trace card: 585 toggles, 4 signals at X; then find in vcd_trace the function that counts changes at one timestamp.

Open the interactive lesson →

VCD wrapped: your simulation, summed up on one card
VCD wrapped: your simulation, summed up on one card ↗

Drop a .vcd from any simulator and get a Wrapped-style card: value changes, the busiest net, signals stuck at X or Z, the quietest ones and the time span, read in your browser.

specs/fpga/vcd_trace.t27

// SPDX-License-Identifier: Apache-2.0
// t27/specs/fpga/vcd_trace.t27
// T27 VCD Trace Emission Specification
// Emits Value Change Dump traces from HIR simulation
// IEEE 1364-2001 VCD format with variable sections
// Uses flat arrays + count fields (parser-compatible)
// phi^2 + 1/phi^2 = 3 | TRINITY

module VcdTrace {

    // === VCD variable kind ===

    pub const VcdVarKind = enum(i8) {
        wire = 0,
        reg = 1,
        integer = 2,
        parameter = 3,
    }

    // === VCD scope kind ===

    pub const VcdScopeKind = enum(i8) {
        module_scope = 0,
        task_scope = 1,
        function_scope = 2,
    }

    // === VCD variable entry ===

    pub struct VcdVar {
        kind : i8,
        size : u32,
        name : &str,
        ident : &str,
    }

    fn vcd_var(kind: i8, size: u32, name: &str, ident: &str) -> VcdVar {
        return VcdVar{
            .kind = kind,
            .size = size,
            .name = name,
            .ident = ident,
        };
    }

    fn var_wire(size: u32, name: &str, ident: &str) -> VcdVar {
        return vcd_var(0, size, name, ident);
    }

    fn var_reg(size: u32, name: &str, ident: &str) -> VcdVar {
        return vcd_var(1, size, name, ident);
    }

    // === VCD value change entry ===

    pub struct VcdChange {
        timestamp_ps : u64,
        ident : &str,
        value : u32,
        bit_width : u32,
    }

    fn vcd_change(ts: u64, ident: &str, value: u32, width: u32) -> VcdChange {
        return VcdChange{
            .timestamp_ps = ts,
            .ident = ident,
            .value = value,
            .bit_width = width,
        };
    }

    // === VCD header config ===

pub struct VcdHeader {
    date : &str,
    version : &str,
    timescale : &str,
    comment : &str,
    _dummy : u32,
}

    fn vcd_header(version: &str, timescale: &str) -> VcdHeader {
        return VcdHeader{
            .date = "2026-04-10",
            .version = version,
            .timescale = timescale,
            .comment = "T27 Trinity VCD",
        };
    }

    // === VCD trace ===

    pub struct VcdTrace {
        header : VcdHeader,
        end_time_ps : u64,
    }

    fn vcd_trace(version: &str) -> VcdTrace {
        return VcdTrace{
            .header = vcd_header(version, "1 ps"),
            .end_time_ps = 0,
        };
    }

    // === Query functions ===

    fn var_ident_index(idx: u32) -> &str {
        return "!";
    }

    fn format_binary(value: u32, width: u32) -> &str {
        return "b0";
    }

    fn changes_at_timestamp(changes: [VcdChange], count: u32, ts: u64) -> u32 {
        var found : u32 = 0;
        var i : u32 = 0;
        while i < count {
            if changes[i].timestamp_ps == ts {
                found = found + 1;
            }
            i = i + 1;
        }
        return found;
    }

    fn earliest_change(changes: [VcdChange], count: u32) -> u64 {
        if count == 0 {
            return 0;
        }
        var min_ts : u64 = changes[0].timestamp_ps;
        var i : u32 = 1;
        while i < count {
            if changes[i].timestamp_ps < min_ts {
                min_ts = changes[i].timestamp_ps;
            }
            i = i + 1;
        }
        return min_ts;
    }

    fn latest_change(changes: [VcdChange], count: u32) -> u64 {
        if count == 0 {
            return 0;
        }
        var max_ts : u64 = changes[0].timestamp_ps;
        var i : u32 = 1;
        while i < count {
            if changes[i].timestamp_ps > max_ts {
                max_ts = changes[i].timestamp_ps;
            }
            i = i + 1;
        }
        return max_ts;
    }

    fn trace_duration_ps(changes: [VcdChange], count: u32) -> u64 {
        if count == 0 {
            return 0;
        }
        return latest_change(changes, count) - earliest_change(changes, count);
    }

    // === Validation ===

    fn validate_var(v: VcdVar) -> u32 {
        var errors : u32 = 0;
        if v.name == "" {
            errors = errors + 1;
        }
        if v.ident == "" {
            errors = errors + 1;
        }
        return errors;
    }

    fn validate_change(c: VcdChange) -> u32 {
        var errors : u32 = 0;
        if c.ident == "" {
            errors = errors + 1;
        }
        if c.bit_width == 0 {
            errors = errors + 1;
        }
        return errors;
    }

    // === Tests ===

    test vcd_var_creation
        given v = var_wire(32, "counter", "!")
        then v.kind == 0
        and v.size == 32
        and v.name == "counter"

    test var_wire_creation
        given v = var_wire(1, "clk", "!")
        then v.kind == 0

    test var_reg_creation
        given v = var_reg(8, "data", "!")
        then v.kind == 1

    test vcd_change_creation
        given c = vcd_change(1000, "!", 1, 1)
        then c.timestamp_ps == 1000
        and c.ident == "!"
        and c.value == 1

    test vcd_header_creation
        given h = vcd_header("t27c v0.1", "1 ps")
        then h.version == "t27c v0.1"
        and h.timescale == "1 ps"

    test vcd_trace_creation
        given t = vcd_trace("t27c v0.1")
        then t.end_time_ps == 0
        and t.header.version == "t27c v0.1"

    test changes_at_timestamp
        given c1 = vcd_change(100, "!", 0, 1)
        and c2 = vcd_change(100, "!", 1, 1)
        and c3 = vcd_change(200, "!", 1, 1)
        then changes_at_timestamp([c1, c2, c3], 3, 100) == 2

    test changes_at_timestamp_none
        given c1 = vcd_change(100, "!", 0, 1)
        then changes_at_timestamp([c1], 1, 999) == 0

    test earliest_change
        given c1 = vcd_change(500, "!", 0, 1)
        and c2 = vcd_change(100, "!", 1, 1)
        and c3 = vcd_change(300, "!", 0, 1)
        then earliest_change([c1, c2, c3], 3) == 100

    test latest_change
        given c1 = vcd_change(500, "!", 0, 1)
        and c2 = vcd_change(100, "!", 1, 1)
        and c3 = vcd_change(300, "!", 0, 1)
        then latest_change([c1, c2, c3], 3) == 500

    test trace_duration
        given c1 = vcd_change(100, "!", 0, 1)
        and c2 = vcd_change(500, "!", 1, 1)
        then trace_duration_ps([c1, c2], 2) == 400

    test trace_duration_empty
        then trace_duration_ps([], 0) == 0

    test validate_var_ok
        given v = var_wire(1, "sig", "!")
        then validate_var(v) == 0

    test validate_var_empty_name
        given v = VcdVar{.kind = 0, .size = 1, .name = "", .ident = "!"}
        then validate_var(v) > 0

    test validate_var_empty_ident
        given v = VcdVar{.kind = 0, .size = 1, .name = "sig", .ident = ""}
        then validate_var(v) > 0

    test validate_change_ok
        given c = vcd_change(0, "!", 0, 1)
        then validate_change(c) == 0

    test validate_change_empty_ident
        given c = VcdChange{.timestamp_ps = 0, .ident = "", .value = 0, .bit_width = 1}
        then validate_change(c) > 0

    test validate_change_zero_width
        given c = VcdChange{.timestamp_ps = 0, .ident = "!", .value = 0, .bit_width = 0}
        then validate_change(c) > 0

    // === Invariants ===

    invariant trace_duration_non_negative
        given c1 = vcd_change(100, "!", 0, 1)
        and c2 = vcd_change(500, "!", 1, 1)
        assert trace_duration_ps([c1, c2], 2) >= 0

    invariant validate_non_negative
        given v = var_wire(1, "sig", "!")
        assert validate_var(v) >= 0

    // === Benchmarks ===

    bench vcd_emit_latency
        measure: nanoseconds for vcd_change(1000, "!", 42, 32)
        target: < 50ns
}

// phi^2 + 1/phi^2 = 3 | TRINITY

Open the lesson's spec in the player ↗

All lessons