From b019db1f373e6bfe2b55363d28b9b6828a1cca6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marcelina=20Ko=C5=9Bcielnicka?= <mwk@0x04.net>
Date: Wed, 26 May 2021 03:07:51 +0200
Subject: [PATCH] kernel/mem: Add helpers for write port widening.

---
 kernel/mem.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 kernel/mem.h  | 11 +++++++++++
 2 files changed, 57 insertions(+)

diff --git a/kernel/mem.cc b/kernel/mem.cc
index a7ee1f2d6..021e1991e 100644
--- a/kernel/mem.cc
+++ b/kernel/mem.cc
@@ -798,3 +798,49 @@ void Mem::prepare_wr_merge(int idx1, int idx2) {
 			oport.priority_mask[idx1] = true;
 	}
 }
+
+void Mem::widen_prep(int wide_log2) {
+	// Make sure start_offset and size are aligned to the port width,
+	// adjust if necessary.
+	int mask = ((1 << wide_log2) - 1);
+	int delta = start_offset & mask;
+	start_offset -= delta;
+	size += delta;
+	if (size & mask) {
+		size |= mask;
+		size++;
+	}
+}
+
+void Mem::widen_wr_port(int idx, int wide_log2) {
+	widen_prep(wide_log2);
+	auto &port = wr_ports[idx];
+	log_assert(port.wide_log2 <= wide_log2);
+	if (port.wide_log2 < wide_log2) {
+		SigSpec new_data, new_en;
+		SigSpec addr_lo = port.addr.extract(0, wide_log2);
+		for (int sub = 0; sub < (1 << wide_log2); sub += (1 << port.wide_log2))
+		{
+			Const cur_addr_lo(sub, wide_log2);
+			if (addr_lo == cur_addr_lo) {
+				// Always writes to this subword.
+				new_data.append(port.data);
+				new_en.append(port.en);
+			} else if (addr_lo.is_fully_const()) {
+				// Never writes to this subword.
+				new_data.append(Const(State::Sx, GetSize(port.data)));
+				new_en.append(Const(State::S0, GetSize(port.data)));
+			} else {
+				// May or may not write to this subword.
+				new_data.append(port.data);
+				SigSpec addr_eq = module->Eq(NEW_ID, addr_lo, cur_addr_lo);
+				SigSpec en = module->Mux(NEW_ID, Const(State::S0, GetSize(port.data)), port.en, addr_eq);
+				new_en.append(en);
+			}
+		}
+		port.addr.replace(port.wide_log2, Const(State::S0, wide_log2 - port.wide_log2));
+		port.data = new_data;
+		port.en = new_en;
+		port.wide_log2 = wide_log2;
+	}
+}
diff --git a/kernel/mem.h b/kernel/mem.h
index 82eb0f488..b4a9cb695 100644
--- a/kernel/mem.h
+++ b/kernel/mem.h
@@ -109,6 +109,17 @@ struct Mem {
 	// is called.
 	void prepare_wr_merge(int idx1, int idx2);
 
+	// Prepares the memory for widening a port to a given width.  This
+	// involves ensuring that start_offset and size are aligned to the
+	// target width.
+	void widen_prep(int wide_log2);
+
+	// Widens a write port up to a given width.  The newly port is
+	// equivalent to the original, made by replicating enable/data bits
+	// and masking enable bits with decoders on the low part of the
+	// original address.
+	void widen_wr_port(int idx, int wide_log2);
+
 	Mem(Module *module, IdString memid, int width, int start_offset, int size) : module(module), memid(memid), packed(false), mem(nullptr), cell(nullptr), width(width), start_offset(start_offset), size(size) {}
 };