From 9c361026698eb52ebbeda8cf39f9d907bb640f39 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 6 Jun 2020 20:37:29 +0000 Subject: [PATCH] cxxrtl: add a VCD writer using debug information. --- backends/cxxrtl/cxxrtl_vcd.h | 194 +++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 backends/cxxrtl/cxxrtl_vcd.h diff --git a/backends/cxxrtl/cxxrtl_vcd.h b/backends/cxxrtl/cxxrtl_vcd.h new file mode 100644 index 000000000..537bd5661 --- /dev/null +++ b/backends/cxxrtl/cxxrtl_vcd.h @@ -0,0 +1,194 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 whitequark + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef CXXRTL_VCD_H +#define CXXRTL_VCD_H + +#include + +namespace cxxrtl { + +class vcd_writer { + struct variable { + size_t ident; + size_t width; + chunk_t *curr; + }; + + std::vector current_scope; + std::vector variables; + bool streaming = false; + + void emit_timescale(unsigned number, const std::string &unit) { + assert(!streaming); + assert(number == 1 || number == 10 || number == 100); + assert(unit == "s" || unit == "ms" || unit == "us" || + unit == "ns" || unit == "ps" || unit == "fs"); + buffer += "$timescale " + std::to_string(number) + " " + unit + " $end\n"; + } + + void emit_scope(const std::vector &scope) { + assert(!streaming); + while (current_scope.size() > scope.size() || + (current_scope.size() > 0 && + current_scope[current_scope.size() - 1] != scope[current_scope.size() - 1])) { + buffer += "$upscope $end\n"; + current_scope.pop_back(); + } + while (current_scope.size() < scope.size()) { + buffer += "$scope module " + scope[current_scope.size()] + " $end\n"; + current_scope.push_back(scope[current_scope.size()]); + } + } + + void emit_ident(size_t ident) { + do { + buffer += '!' + ident % 94; // "base94" + ident /= 94; + } while (ident != 0); + } + + void emit_var(const variable &var, const std::string &type, const std::string &name) { + assert(!streaming); + buffer += "$var " + type + " " + std::to_string(var.width) + " "; + emit_ident(var.ident); + buffer += " " + name + " $end\n"; + } + + void emit_enddefinitions() { + assert(!streaming); + buffer += "$enddefinitions $end\n"; + streaming = true; + } + + void emit_time(uint64_t timestamp) { + assert(streaming); + buffer += "#" + std::to_string(timestamp) + "\n"; + } + + void emit_scalar(const variable &var) { + assert(streaming); + assert(var.width == 1); + buffer += (*var.curr ? '1' : '0'); + emit_ident(var.ident); + buffer += '\n'; + } + + void emit_vector(const variable &var) { + assert(streaming); + buffer += 'b'; + for (size_t bit = var.width - 1; bit != (size_t)-1; bit--) { + bool bit_curr = var.curr[bit / (8 * sizeof(chunk_t))] & (1 << (bit % (8 * sizeof(chunk_t)))); + buffer += (bit_curr ? '1' : '0'); + } + buffer += ' '; + emit_ident(var.ident); + buffer += '\n'; + } + + static std::vector split_hierarchy(const std::string &hier_name) { + std::vector hierarchy; + size_t prev = 0; + while (true) { + size_t curr = hier_name.find_first_of(' ', prev + 1); + if (curr > hier_name.size()) + curr = hier_name.size(); + if (curr > prev + 1) + hierarchy.push_back(hier_name.substr(prev, curr - prev)); + if (curr == hier_name.size()) + break; + prev = curr + 1; + } + return hierarchy; + } + +public: + std::string buffer; + + void timescale(unsigned number, const std::string &unit) { + emit_timescale(number, unit); + } + + void add(const std::string &hier_name, const debug_item &item) { + std::vector scope = split_hierarchy(hier_name); + std::string name = scope.back(); + scope.pop_back(); + + emit_scope(scope); + switch (item.type) { + // Not the best naming but oh well... + case debug_item::VALUE: + variables.emplace_back(variable { variables.size(), item.width, item.curr }); + emit_var(variables.back(), "wire", name); + break; + case debug_item::WIRE: + variables.emplace_back(variable { variables.size(), item.width, item.curr }); + emit_var(variables.back(), "reg", name); + break; + case debug_item::MEMORY: { + const size_t stride = (item.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); + for (size_t index = 0; index < item.depth; index++) { + chunk_t *nth_curr = &item.curr[stride * index]; + std::string nth_name = name + '[' + std::to_string(index) + ']'; + variables.emplace_back(variable { variables.size(), item.width, nth_curr }); + emit_var(variables.back(), "reg", nth_name); + } + break; + } + } + } + + template + void add(const debug_items &items, const Filter &filter) { + // `debug_items` is a map, so the items are already sorted in an order optimal for emitting + // VCD scope sections. + for (auto &it : items) + if (filter(it.first, it.second)) + add(it.first, it.second); + } + + void add(const debug_items &items) { + this->template add(items, [](const std::string &, const debug_item &) { + return true; + }); + } + + void add_without_memories(const debug_items &items) { + this->template add(items, [](const std::string &, const debug_item &item) { + return item.type == debug_item::VALUE || item.type == debug_item::WIRE; + }); + } + + void sample(uint64_t timestamp) { + if (!streaming) { + emit_scope({}); + emit_enddefinitions(); + } + emit_time(timestamp); + for (auto var : variables) { + if (var.width == 1) + emit_scalar(var); + else + emit_vector(var); + } + } +}; + +} + +#endif -- 2.30.2