aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/mem.cc
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/mem.cc')
-rw-r--r--kernel/mem.cc98
1 files changed, 80 insertions, 18 deletions
diff --git a/kernel/mem.cc b/kernel/mem.cc
index 6d6778b57..5d0a01dd2 100644
--- a/kernel/mem.cc
+++ b/kernel/mem.cc
@@ -550,13 +550,28 @@ Cell *Mem::extract_rdff(int idx, FfInitVals *initvals) {
Cell *c;
- if (port.transparent)
- {
- log_assert(port.en == State::S1);
- log_assert(port.srst == State::S0);
- log_assert(port.arst == State::S0);
- log_assert(port.init_value.is_fully_undef());
+ // There are two ways to handle rdff extraction when transparency is involved:
+ //
+ // - if all of the following conditions are true, put the FF on address input:
+ //
+ // - the port has no clock enable, no reset, and no initial value
+ // - the port is transparent wrt all write ports (implying they also share
+ // the clock domain)
+ //
+ // - otherwise, put the FF on the data output, and make bypass paths for
+ // all write ports wrt which this port is transparent
+ bool trans_use_addr = port.transparent;
+
+ // If there are no write ports at all, we could possibly use either way; do data
+ // FF in this case.
+ if (GetSize(wr_ports) == 0)
+ trans_use_addr = false;
+ if (port.en != State::S1 || port.srst != State::S0 || port.arst != State::S0 || !port.init_value.is_fully_undef())
+ trans_use_addr = false;
+
+ if (trans_use_addr)
+ {
// Do not put a register in front of constant address bits — this is both
// unnecessary and will break wide ports.
int width = 0;
@@ -564,23 +579,70 @@ Cell *Mem::extract_rdff(int idx, FfInitVals *initvals) {
if (port.addr[i].wire)
width++;
- SigSpec sig_q = module->addWire(stringf("$%s$rdreg[%d]$q", memid.c_str(), idx), width);
- SigSpec sig_d;
+ if (width) {
+ SigSpec sig_q = module->addWire(stringf("$%s$rdreg[%d]$q", memid.c_str(), idx), width);
+ SigSpec sig_d;
- int pos = 0;
- for (int i = 0; i < GetSize(port.addr); i++)
- if (port.addr[i].wire) {
- sig_d.append(port.addr[i]);
- port.addr[i] = sig_q[pos++];
- }
+ int pos = 0;
+ for (int i = 0; i < GetSize(port.addr); i++)
+ if (port.addr[i].wire) {
+ sig_d.append(port.addr[i]);
+ port.addr[i] = sig_q[pos++];
+ }
- c = module->addDffe(stringf("$%s$rdreg[%d]", memid.c_str(), idx), port.clk, State::S1, sig_d, sig_q, port.clk_polarity, true);
+ c = module->addDff(stringf("$%s$rdreg[%d]", memid.c_str(), idx), port.clk, sig_d, sig_q, port.clk_polarity);
+ }
}
else
{
log_assert(port.arst == State::S0 || port.srst == State::S0);
- SigSpec sig_d = module->addWire(stringf("$%s$rdreg[%d]$d", memid.c_str(), idx), GetSize(port.data));
+ SigSpec async_d = module->addWire(stringf("$%s$rdreg[%d]$d", memid.c_str(), idx), GetSize(port.data));
+ SigSpec sig_d = async_d;
+
+ for (int i = 0; i < GetSize(wr_ports); i++) {
+ auto &wport = wr_ports[i];
+ if (port.transparent) {
+ log_assert(wport.clk_enable);
+ log_assert(wport.clk == port.clk);
+ log_assert(wport.clk_enable == port.clk_enable);
+ int min_wide_log2 = std::min(port.wide_log2, wport.wide_log2);
+ int max_wide_log2 = std::max(port.wide_log2, wport.wide_log2);
+ bool wide_write = wport.wide_log2 > port.wide_log2;
+ for (int sub = 0; sub < (1 << max_wide_log2); sub += (1 << min_wide_log2)) {
+ SigSpec raddr = port.addr;
+ SigSpec waddr = wport.addr;
+ for (int j = min_wide_log2; j < max_wide_log2; j++)
+ if (wide_write)
+ waddr[j] = State(sub >> j & 1);
+ else
+ raddr[j] = State(sub >> j & 1);
+ SigSpec addr_eq;
+ if (raddr != waddr)
+ addr_eq = module->Eq(stringf("$%s$rdtransen[%d][%d][%d]$d", memid.c_str(), idx, i, sub), raddr, waddr);
+ int pos = 0;
+ int ewidth = width << min_wide_log2;
+ int wsub = wide_write ? sub : 0;
+ int rsub = wide_write ? 0 : sub;
+ while (pos < ewidth) {
+ int epos = pos;
+ while (epos < ewidth && wport.en[epos + wsub * width] == wport.en[pos + wsub * width])
+ epos++;
+ SigSpec cur = sig_d.extract(pos + rsub * width, epos-pos);
+ SigSpec other = wport.data.extract(pos + wsub * width, epos-pos);
+ SigSpec cond;
+ if (raddr != waddr)
+ cond = module->And(stringf("$%s$rdtransgate[%d][%d][%d][%d]$d", memid.c_str(), idx, i, sub, pos), wport.en[pos + wsub * width], addr_eq);
+ else
+ cond = wport.en[pos + wsub * width];
+ SigSpec merged = module->Mux(stringf("$%s$rdtransmux[%d][%d][%d][%d]$d", memid.c_str(), idx, i, sub, pos), cur, other, cond);
+ sig_d.replace(pos + rsub * width, merged);
+ pos = epos;
+ }
+ }
+ }
+ }
+
IdString name = stringf("$%s$rdreg[%d]", memid.c_str(), idx);
FfData ff(initvals);
ff.width = GetSize(port.data);
@@ -608,11 +670,11 @@ Cell *Mem::extract_rdff(int idx, FfInitVals *initvals) {
ff.sig_d = sig_d;
ff.sig_q = port.data;
ff.val_init = port.init_value;
- port.data = sig_d;
+ port.data = async_d;
c = ff.emit(module, name);
}
- log("Extracted %s FF from read port %d of %s.%s: %s\n", port.transparent ? "addr" : "data",
+ log("Extracted %s FF from read port %d of %s.%s: %s\n", trans_use_addr ? "addr" : "data",
idx, log_id(module), log_id(memid), log_id(c));
port.en = State::S1;