Last active
May 23, 2022 18:26
-
-
Save bit-hack/2cd54cdf955f0e8909dab0c26a165745 to your computer and use it in GitHub Desktop.
VCD file writer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "vcd.h" | |
#include <cstdlib> | |
int main(int argc, char **args) { | |
vcd::vcd_t vcd; | |
if (!vcd.begin("test.vcd", 1, vcd::timescale_ps)) { | |
return 1; | |
} | |
vcd::var_t var_ice40_clk; | |
vcd::var_t var_ice40_addr; | |
vcd::var_t var_perfect6581_clk; | |
vcd.defs_begin(); | |
vcd.module_begin("ice40"); | |
vcd.def(var_ice40_clk, "clk", vcd::var_type_wire, 1); | |
vcd.module_begin("bus"); | |
vcd.def(var_ice40_addr, "addr", vcd::var_type_wire, 8); | |
vcd.module_end(); | |
vcd.module_end(); | |
vcd.module_begin("perfect6581"); | |
vcd.def(var_perfect6581_clk, "clk", vcd::var_type_wire, 1); | |
vcd.module_end(); | |
vcd.defs_end(); | |
for (int i = 0; i < 100; ++i) { | |
vcd.timestamp(i); | |
vcd.set(var_ice40_clk, i & 1); | |
vcd.set(var_ice40_addr, i & 0xff); | |
vcd.set(var_perfect6581_clk, i & 1); | |
} | |
vcd.end(); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#define _CRT_SECURE_NO_WARNINGS | |
#include <cstdint> | |
#include <cstdio> | |
#include <cassert> | |
namespace vcd { | |
enum var_type_t { | |
var_type_wire, | |
var_type_reg, | |
var_type_parameter, | |
var_type_integer, | |
}; | |
enum timescale_t { | |
timescale_s, | |
timescale_ms, | |
timescale_us, | |
timescale_ns, | |
timescale_ps, | |
timescale_fs, | |
}; | |
struct var_t { | |
var_t() | |
: id (~0u) | |
, bits (0) | |
, value(0xdeadbeef) | |
{ | |
} | |
uint32_t id; | |
uint32_t value; | |
uint16_t bits; | |
}; | |
struct vcd_t { | |
vcd_t() | |
: fd (nullptr) | |
, id_next (0) | |
, time_val (~0u) | |
, depth_module(0) | |
, seq_defs (false) | |
, seq_began (false) | |
{ | |
} | |
~vcd_t() { | |
if (fd) { | |
fclose(fd); | |
} | |
fd = nullptr; | |
} | |
bool begin(const char *fileName, uint32_t quant, timescale_t timescale) { | |
assert(!fd && "fd != nullptr"); | |
assert(!seq_began); | |
fd = fopen(fileName, "w"); | |
if (!fd) { | |
return false; | |
} | |
const char *scale = to_string(timescale); | |
fprintf(fd, "$timescale %u %s $end\n", quant, scale); | |
seq_began = true; | |
return true; | |
} | |
void date(const char *date) { | |
assert(fd && "fd == nullptr"); | |
assert(seq_defs == 1 && "only valid in defs scope"); | |
fprintf(fd, "$date %s $end\n", date); | |
} | |
void end() { | |
assert(fd && "fd == nullptr"); | |
assert(seq_began); | |
fclose(fd); | |
fd = nullptr; | |
seq_began = false; | |
} | |
void defs_begin() { | |
assert(fd && "fd == nullptr"); | |
assert(seq_began); | |
assert(!seq_defs); | |
seq_defs = true; | |
} | |
void defs_end() { | |
assert(fd && "fd == nullptr"); | |
assert(seq_began); | |
assert(seq_defs); | |
assert(depth_module == 0 && "module defines out of order"); | |
fprintf(fd, "$enddefinitions $end\n"); | |
seq_defs = false; | |
} | |
void module_begin(const char *name) { | |
assert(fd && "fd == nullptr"); | |
assert(seq_began); | |
fprintf(fd, "$scope module %s $end\n", name); | |
++depth_module; | |
} | |
void module_end() { | |
assert(fd && "fd == nullptr"); | |
assert(seq_began); | |
assert(depth_module && "invalid module end"); | |
--depth_module; | |
fprintf(fd, "$upscope $end\n"); | |
} | |
void def(var_t &var, const char *name, var_type_t type, uint32_t bits) { | |
assert(fd && "fd == nullptr"); | |
assert(bits && "invalid bits"); | |
assert(seq_began); | |
assert(seq_defs && "only valid in defs scope"); | |
const char *type_str = to_string(type); | |
var.id = id_next++; | |
var.bits = bits; | |
fprintf(fd, "$var %s %u %u %s $end\n", type_str, bits, var.id, name); | |
} | |
void set(var_t &var, uint32_t data) { | |
assert(fd && "fd == nullptr"); | |
assert(var.bits && "invalid var bits"); | |
assert((var.id != ~0u) && "invalid var id"); | |
assert(seq_began); | |
assert(seq_defs == 0 && "only valid out of defs scope"); | |
if (var.value != data) { | |
var.value = data; | |
uint32_t mask = 1u << (var.bits - 1); | |
fputc('b', fd); | |
for (uint32_t i = 0; i < var.bits; ++i) { | |
fputc((data & mask) ? '1' : '0', fd); | |
data <<= 1; | |
} | |
fprintf(fd, " %u\n", var.id); | |
} | |
} | |
void comment(const char *str) { | |
assert(fd && "fd == nullptr"); | |
assert(seq_began); | |
assert(!seq_defs && "only valid out of defs scope"); | |
fprintf(fd, "$comment %s $end\n", str); | |
} | |
void timestamp(uint32_t time) { | |
assert(fd && "fd == nullptr"); | |
assert(seq_began); | |
assert(!seq_defs && "only valid out of defs scope"); | |
if (time != time_val) { | |
fprintf(fd, "#%u\n", time); | |
} | |
time_val = time; | |
} | |
protected: | |
const char *to_string(timescale_t timescale) const { | |
switch (timescale) { | |
case timescale_s: return "s"; | |
case timescale_ms: return "ms"; | |
case timescale_us: return "us"; | |
case timescale_ns: return "ns"; | |
case timescale_ps: return "ps"; | |
case timescale_fs: return "fs"; | |
default: return "unknown_timescale"; | |
} | |
} | |
const char *to_string(var_type_t type) const { | |
switch (type) { | |
case var_type_wire: return "wire"; | |
case var_type_reg: return "reg"; | |
case var_type_parameter: return "parameter"; | |
case var_type_integer: return "integer"; | |
default: return "unknown_type"; | |
} | |
} | |
FILE *fd; | |
uint32_t id_next; | |
uint32_t time_val; | |
uint32_t depth_module; | |
bool seq_defs; | |
bool seq_began; | |
}; | |
} // namespace vcd |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment