kernel/mem: Add a coalesce_inits helper.
authorMarcelina Kościelnicka <mwk@0x04.net>
Mon, 12 Jul 2021 18:04:59 +0000 (20:04 +0200)
committerMarcelina Kościelnicka <mwk@0x04.net>
Tue, 13 Jul 2021 13:59:11 +0000 (15:59 +0200)
While this helper is already useful to squash sequential initializations
into one in cxxrtl, its main purpose is to squash overlapping masked memory
initializations (when they land) and avoid having to deal with them in
cxxrtl runtime.

backends/cxxrtl/cxxrtl_backend.cc
kernel/mem.cc
kernel/mem.h

index ddb954073e60f264491e2fdc18ee2b426aed0e62..70a3add5d3852102f3f3463adca6dbc5ee717578 100644 (file)
@@ -1835,6 +1835,8 @@ struct CxxrtlWorker {
                        f << ",\n";
                        inc_indent();
                                for (auto &init : mem->inits) {
+                                       if (init.removed)
+                                               continue;
                                        dump_attrs(&init);
                                        int words = GetSize(init.data) / mem->width;
                                        f << indent << "memory<" << mem->width << ">::init<" << words << "> { "
@@ -2488,8 +2490,10 @@ struct CxxrtlWorker {
 
                        std::vector<Mem> &memories = mod_memories[module];
                        memories = Mem::get_all_memories(module);
-                       for (auto &mem : memories)
+                       for (auto &mem : memories) {
                                mem.narrow();
+                               mem.coalesce_inits();
+                       }
 
                        if (module->get_bool_attribute(ID(cxxrtl_blackbox))) {
                                for (auto port : module->ports) {
index 96f71428de6ec4f028d5883a0fd45c755e2f3bb3..e95118d4c66a3e54c885ee1ca85c526f4c26d4c6 100644 (file)
@@ -282,6 +282,78 @@ void Mem::clear_inits() {
                init.removed = true;
 }
 
+void Mem::coalesce_inits() {
+       // start address -> end address
+       std::map<int, int> chunks;
+       // Figure out chunk boundaries.
+       for (auto &init : inits) {
+               if (init.removed)
+                       continue;
+               int addr = init.addr.as_int();
+               int addr_e = addr + GetSize(init.data) / width;
+               auto it_e = chunks.upper_bound(addr_e);
+               auto it = it_e;
+               while (it != chunks.begin()) {
+                       --it;
+                       if (it->second < addr) {
+                               ++it;
+                               break;
+                       }
+               }
+               if (it == it_e) {
+                       // No overlapping inits — add this one to index.
+                       chunks[addr] = addr_e;
+               } else {
+                       // We have an overlap — all chunks in the [it, it_e)
+                       // range will be merged with this init.
+                       if (it->first < addr)
+                               addr = it->first;
+                       auto it_last = it_e;
+                       it_last--;
+                       if (it_last->second > addr_e)
+                               addr_e = it_last->second;
+                       chunks.erase(it, it_e);
+                       chunks[addr] = addr_e;
+               }
+       }
+       // Group inits by the chunk they belong to.
+       dict<int, std::vector<int>> inits_by_chunk;
+       for (int i = 0; i < GetSize(inits); i++) {
+               auto &init = inits[i];
+               if (init.removed)
+                       continue;
+               auto it = chunks.upper_bound(init.addr.as_int());
+               --it;
+               inits_by_chunk[it->first].push_back(i);
+               int addr = init.addr.as_int();
+               int addr_e = addr + GetSize(init.data) / width;
+               log_assert(addr >= it->first && addr_e <= it->second);
+       }
+       // Process each chunk.
+       for (auto &it : inits_by_chunk) {
+               int caddr = it.first;
+               int caddr_e = chunks[caddr];
+               auto &chunk_inits = it.second;
+               if (GetSize(chunk_inits) == 1) {
+                       continue;
+               }
+               Const cdata(State::Sx, (caddr_e - caddr) * width);
+               for (int idx : chunk_inits) {
+                       auto &init = inits[idx];
+                       int offset = (init.addr.as_int() - caddr) * width;
+                       log_assert(offset >= 0);
+                       log_assert(offset + GetSize(init.data) <= GetSize(cdata));
+                       for (int i = 0; i < GetSize(init.data); i++)
+                               cdata.bits[i+offset] = init.data.bits[i];
+                       init.removed = true;
+               }
+               MemInit new_init;
+               new_init.addr = caddr;
+               new_init.data = cdata;
+               inits.push_back(new_init);
+       }
+}
+
 Const Mem::get_init_data() const {
        Const init_data(State::Sx, width * size);
        for (auto &init : inits) {
index 6ea18f26f1a8d294415188ddf2da5aef875ae11e..62403e00c52090c3ef927bd91e01e5b03440a7c4 100644 (file)
@@ -97,6 +97,13 @@ struct Mem : RTLIL::AttrObject {
        // Marks all inits as removed.
        void clear_inits();
 
+       // Coalesces inits: whenever two inits have overlapping or touching
+       // address ranges, they are combined into one, with the higher-priority
+       // one's data overwriting the other.  Running this results in
+       // an inits list equivalent to the original, in which all entries
+       // cover disjoint (and non-touching) address ranges.
+       void coalesce_inits();
+
        // Checks consistency of this memory and all its ports/inits, using
        // log_assert.
        void check();