aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/mem.cc
diff options
context:
space:
mode:
authorMarcelina Kościelnicka <mwk@0x04.net>2021-05-25 20:42:34 +0200
committerMarcelina Kościelnicka <mwk@0x04.net>2021-05-25 21:38:23 +0200
commit3514c92dc42644d64dca2167c05096d10891de69 (patch)
tree61b294a7b812d93496f2d9551742dc7fa1a7b21e /kernel/mem.cc
parente6b078d156f8690ab06d342da9be9af02cbcc3aa (diff)
downloadyosys-3514c92dc42644d64dca2167c05096d10891de69.tar.gz
yosys-3514c92dc42644d64dca2167c05096d10891de69.tar.bz2
yosys-3514c92dc42644d64dca2167c05096d10891de69.zip
mem/extract_rdff: Add alternate transparency handling.
When extracting read register from a transparent port that has an enable, reset, or initial value, the usual trick of putting a register on the address instead of data doesn't work. In this case, create soft transparency logic instead. When transparency masks land, this will also be used to handle ports that are transparent to only a subset of write ports.
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;