diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc
index ddb954073..70a3add5d 100644
--- a/backends/cxxrtl/cxxrtl_backend.cc
+++ b/backends/cxxrtl/cxxrtl_backend.cc
@@ -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) {
diff --git a/kernel/mem.cc b/kernel/mem.cc
index 96f71428d..e95118d4c 100644
--- a/kernel/mem.cc
+++ b/kernel/mem.cc
@@ -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) {
diff --git a/kernel/mem.h b/kernel/mem.h
index 6ea18f26f..62403e00c 100644
--- a/kernel/mem.h
+++ b/kernel/mem.h
@@ -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();