Формат VCD
Вы научитесь
Что на самом деле лежит в .vcd-файле и как читать его дамп всех сигналов.
Файл временной диаграммы — это текст, и читать его как текст — навык. .vcd объявляет масштаб времени, называет области и переменные, а дальше записывает только изменения: метка времени, идентификатор, значение. Spec урока, vcd_trace, моделирует именно это — заголовки, изменения, длительности — с тестами на самое раннее и самое позднее изменение. Виджет — настоящая трасса, завёрнутая в карточку: тестбенч переключил clk 585 раз и оставил 4 сигнала в X на всём прогоне; оба факта посчитаны из файла, а не вспомнены с экрана.
Попробуйте
Прочитайте карточку обёрнутой трассы: 585 переключений, 4 сигнала в X; затем найдите в vcd_trace функцию, считающую изменения в одной метке времени.

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
Все уроки
Модуль 1 · Зачем проверять
Дизайны, которые компилируются и ошибаются; модель, которая выносит вердикт; план, записанный до кода.
Модуль 2 · Тестбенчи
Стимулы, проверки и вердикт, записанные как один spec рядом с дизайном, который они судят.
Модуль 3 · Временные диаграммы
Трасса каждого сигнала, прочитанная так, как её читает инженер по железу, и два прогона, сравнённые между собой.
Модуль 4 · Векторы соответствия
Случаи с ответом, записанным рядом, там, откуда их достанет компилятор.
Модуль 5 · Косимуляция
Spec, симулятор и плата сходятся в одном ответе на стенде Artix-7 XC7A200T, и что делать, когда не сходятся.
Модуль 6 · Покрытие
Чего коснулись тесты: строки, переключения, состояния — и что прячет это число.
Модуль 7 · Формальные методы
Ассерты, верные каждый такт; ограниченный поиск контрпримера; и почему доказательству нужна индукция.
Модуль 8 · Мутации
Ломайте дизайн нарочно и считайте, что заметили тесты.
Модуль 9 · Приёмка
Одна команда, все квитанции, чистый вердикт, который можно показать.