Merge pull request #2480 from YosysHQ/dave/nexus-lram
[yosys.git] / backends / cxxrtl / cxxrtl_vcd.h
index 5706917caa309491247580b1d791c6d8d32e34c5..6ee98b4284db16080b45054f50b400be70e677bc 100644 (file)
@@ -28,12 +28,16 @@ class vcd_writer {
                size_t ident;
                size_t width;
                chunk_t *curr;
-               size_t prev_off;
+               size_t cache_offset;
+               debug_outline *outline;
+               bool *outline_warm;
        };
 
        std::vector<std::string> current_scope;
+       std::map<debug_outline*, bool> outlines;
        std::vector<variable> variables;
        std::vector<chunk_t> cache;
+       std::map<chunk_t*, size_t> aliases;
        bool streaming = false;
 
        void emit_timescale(unsigned number, const std::string &unit) {
@@ -65,11 +69,19 @@ class vcd_writer {
                } while (ident != 0);
        }
 
-       void emit_var(const variable &var, const std::string &type, const std::string &name) {
+       void emit_var(const variable &var, const std::string &type, const std::string &name,
+                     size_t lsb_at, bool multipart) {
                assert(!streaming);
                buffer += "$var " + type + " " + std::to_string(var.width) + " ";
                emit_ident(var.ident);
-               buffer += " " + name + " $end\n";
+               buffer += " " + name;
+               if (multipart || name.back() == ']' || lsb_at != 0) {
+                       if (var.width == 1)
+                               buffer += " [" + std::to_string(lsb_at) + "]";
+                       else
+                               buffer += " [" + std::to_string(lsb_at + var.width - 1) + ":" + std::to_string(lsb_at) + "]";
+               }
+               buffer += " $end\n";
        }
 
        void emit_enddefinitions() {
@@ -103,18 +115,40 @@ class vcd_writer {
                buffer += '\n';
        }
 
-       void append_variable(size_t width, chunk_t *curr) {
-               const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
-               variables.emplace_back(variable { variables.size(), width, curr, cache.size() });
-               cache.insert(cache.end(), &curr[0], &curr[chunks]);
+       void reset_outlines() {
+               for (auto &outline_it : outlines)
+                       outline_it.second = /*warm=*/(outline_it.first == nullptr);
+       }
+
+       variable &register_variable(size_t width, chunk_t *curr, bool constant = false, debug_outline *outline = nullptr) {
+               if (aliases.count(curr)) {
+                       return variables[aliases[curr]];
+               } else {
+                       auto outline_it = outlines.emplace(outline, /*warm=*/(outline == nullptr)).first;
+                       const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
+                       aliases[curr] = variables.size();
+                       if (constant) {
+                               variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1, outline_it->first, &outline_it->second });
+                       } else {
+                               variables.emplace_back(variable { variables.size(), width, curr, cache.size(), outline_it->first, &outline_it->second });
+                               cache.insert(cache.end(), &curr[0], &curr[chunks]);
+                       }
+                       return variables.back();
+               }
        }
 
        bool test_variable(const variable &var) {
+               if (var.cache_offset == (size_t)-1)
+                       return false; // constant
+               if (!*var.outline_warm) {
+                       var.outline->eval();
+                       *var.outline_warm = true;
+               }
                const size_t chunks = (var.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
-               if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.prev_off])) {
+               if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset])) {
                        return false;
                } else {
-                       std::copy(&var.curr[0], &var.curr[chunks], &cache[var.prev_off]);
+                       std::copy(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset]);
                        return true;
                }
        }
@@ -123,14 +157,14 @@ class vcd_writer {
                std::vector<std::string> 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())
+                       size_t curr = hier_name.find_first_of(' ', prev);
+                       if (curr == std::string::npos) {
+                               hierarchy.push_back(hier_name.substr(prev));
                                break;
-                       prev = curr + 1;
+                       } else {
+                               hierarchy.push_back(hier_name.substr(prev, curr - prev));
+                               prev = curr + 1;
+                       }
                }
                return hierarchy;
        }
@@ -142,7 +176,7 @@ public:
                emit_timescale(number, unit);
        }
 
-       void add(const std::string &hier_name, const debug_item &item) {
+       void add(const std::string &hier_name, const debug_item &item, bool multipart = false) {
                std::vector<std::string> scope = split_hierarchy(hier_name);
                std::string name = scope.back();
                scope.pop_back();
@@ -151,23 +185,35 @@ public:
                switch (item.type) {
                        // Not the best naming but oh well...
                        case debug_item::VALUE:
-                               append_variable(item.width, item.curr);
-                               emit_var(variables.back(), "wire", name);
+                               emit_var(register_variable(item.width, item.curr, /*constant=*/item.next == nullptr),
+                                        "wire", name, item.lsb_at, multipart);
                                break;
                        case debug_item::WIRE:
-                               append_variable(item.width, item.curr);
-                               emit_var(variables.back(), "reg", name);
+                               emit_var(register_variable(item.width, item.curr),
+                                        "reg", name, item.lsb_at, multipart);
                                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) + ']';
-                                       append_variable(item.width, nth_curr);
-                                       emit_var(variables.back(), "reg", nth_name);
+                                       emit_var(register_variable(item.width, nth_curr),
+                                                "reg", nth_name, item.lsb_at, multipart);
                                }
                                break;
                        }
+                       case debug_item::ALIAS:
+                               // Like VALUE, but, even though `item.next == nullptr` always holds, the underlying value
+                               // can actually change, and must be tracked. In most cases the VCD identifier will be
+                               // unified with the aliased reg, but we should handle the case where only the alias is
+                               // added to the VCD writer, too.
+                               emit_var(register_variable(item.width, item.curr),
+                                        "wire", name, item.lsb_at, multipart);
+                               break;
+                       case debug_item::OUTLINE:
+                               emit_var(register_variable(item.width, item.curr, /*constant=*/false, item.outline),
+                                        "wire", name, item.lsb_at, multipart);
+                               break;
                }
        }
 
@@ -175,9 +221,10 @@ public:
        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);
+               for (auto &it : items.table)
+                       for (auto &part : it.second)
+                               if (filter(it.first, part))
+                                       add(it.first, part, it.second.size() > 1);
        }
 
        void add(const debug_items &items) {
@@ -188,7 +235,7 @@ public:
 
        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;
+                       return item.type != debug_item::MEMORY;
                });
        }
 
@@ -198,6 +245,7 @@ public:
                        emit_scope({});
                        emit_enddefinitions();
                }
+               reset_outlines();
                emit_time(timestamp);
                for (auto var : variables)
                        if (test_variable(var) || first_sample) {