diff options
Diffstat (limited to 'kernel/mem.cc')
-rw-r--r-- | kernel/mem.cc | 1386 |
1 files changed, 1308 insertions, 78 deletions
diff --git a/kernel/mem.cc b/kernel/mem.cc index 0301a913c..059f8f934 100644 --- a/kernel/mem.cc +++ b/kernel/mem.cc @@ -18,6 +18,7 @@ */ #include "kernel/mem.h" +#include "kernel/ff.h" USING_YOSYS_NAMESPACE @@ -52,6 +53,67 @@ void Mem::remove() { } void Mem::emit() { + check(); + std::vector<int> rd_left; + for (int i = 0; i < GetSize(rd_ports); i++) { + auto &port = rd_ports[i]; + if (port.removed) { + if (port.cell) { + module->remove(port.cell); + } + } else { + rd_left.push_back(i); + } + } + std::vector<int> wr_left; + for (int i = 0; i < GetSize(wr_ports); i++) { + auto &port = wr_ports[i]; + if (port.removed) { + if (port.cell) { + module->remove(port.cell); + } + } else { + wr_left.push_back(i); + } + } + std::vector<int> init_left; + for (int i = 0; i < GetSize(inits); i++) { + auto &init = inits[i]; + if (init.removed) { + if (init.cell) { + module->remove(init.cell); + } + } else { + init_left.push_back(i); + } + } + for (int i = 0; i < GetSize(rd_left); i++) + if (i != rd_left[i]) + std::swap(rd_ports[i], rd_ports[rd_left[i]]); + rd_ports.resize(GetSize(rd_left)); + for (int i = 0; i < GetSize(wr_left); i++) + if (i != wr_left[i]) + std::swap(wr_ports[i], wr_ports[wr_left[i]]); + wr_ports.resize(GetSize(wr_left)); + for (int i = 0; i < GetSize(init_left); i++) + if (i != init_left[i]) + std::swap(inits[i], inits[init_left[i]]); + inits.resize(GetSize(init_left)); + + for (auto &port : rd_ports) { + for (int i = 0; i < GetSize(wr_left); i++) { + port.transparency_mask[i] = port.transparency_mask[wr_left[i]]; + port.collision_x_mask[i] = port.collision_x_mask[wr_left[i]]; + } + port.transparency_mask.resize(GetSize(wr_left)); + port.collision_x_mask.resize(GetSize(wr_left)); + } + for (auto &port : wr_ports) { + for (int i = 0; i < GetSize(wr_left); i++) + port.priority_mask[i] = port.priority_mask[wr_left[i]]; + port.priority_mask.resize(GetSize(wr_left)); + } + if (packed) { if (mem) { module->memories.erase(mem->name); @@ -61,54 +123,90 @@ void Mem::emit() { if (!cell) { if (memid.empty()) memid = NEW_ID; - cell = module->addCell(memid, ID($mem)); + cell = module->addCell(memid, ID($mem_v2)); } + cell->type = ID($mem_v2); cell->attributes = attributes; cell->parameters[ID::MEMID] = Const(memid.str()); cell->parameters[ID::WIDTH] = Const(width); cell->parameters[ID::OFFSET] = Const(start_offset); cell->parameters[ID::SIZE] = Const(size); - cell->parameters[ID::RD_PORTS] = Const(GetSize(rd_ports)); - cell->parameters[ID::WR_PORTS] = Const(GetSize(wr_ports)); - Const rd_clk_enable, rd_clk_polarity, rd_transparent; - Const wr_clk_enable, wr_clk_polarity; + Const rd_wide_continuation, rd_clk_enable, rd_clk_polarity, rd_transparency_mask, rd_collision_x_mask; + Const wr_wide_continuation, wr_clk_enable, wr_clk_polarity, wr_priority_mask; + Const rd_ce_over_srst, rd_arst_value, rd_srst_value, rd_init_value; SigSpec rd_clk, rd_en, rd_addr, rd_data; SigSpec wr_clk, wr_en, wr_addr, wr_data; + SigSpec rd_arst, rd_srst; int abits = 0; for (auto &port : rd_ports) abits = std::max(abits, GetSize(port.addr)); for (auto &port : wr_ports) abits = std::max(abits, GetSize(port.addr)); cell->parameters[ID::ABITS] = Const(abits); + std::vector<int> wr_port_xlat; + for (int i = 0; i < GetSize(wr_ports); i++) + for (int j = 0; j < (1 << wr_ports[i].wide_log2); j++) + wr_port_xlat.push_back(i); for (auto &port : rd_ports) { if (port.cell) { module->remove(port.cell); port.cell = nullptr; } - rd_clk_enable.bits.push_back(State(port.clk_enable)); - rd_clk_polarity.bits.push_back(State(port.clk_polarity)); - rd_transparent.bits.push_back(State(port.transparent)); - rd_clk.append(port.clk); - log_assert(GetSize(port.clk) == 1); - rd_en.append(port.en); - log_assert(GetSize(port.en) == 1); - SigSpec addr = port.addr; - addr.extend_u0(abits, false); - rd_addr.append(addr); - log_assert(GetSize(addr) == abits); + for (int sub = 0; sub < (1 << port.wide_log2); sub++) + { + rd_wide_continuation.bits.push_back(State(sub != 0)); + rd_clk_enable.bits.push_back(State(port.clk_enable)); + rd_clk_polarity.bits.push_back(State(port.clk_polarity)); + rd_ce_over_srst.bits.push_back(State(port.ce_over_srst)); + rd_clk.append(port.clk); + rd_arst.append(port.arst); + rd_srst.append(port.srst); + rd_en.append(port.en); + SigSpec addr = port.sub_addr(sub); + addr.extend_u0(abits, false); + rd_addr.append(addr); + log_assert(GetSize(addr) == abits); + for (auto idx : wr_port_xlat) { + rd_transparency_mask.bits.push_back(State(bool(port.transparency_mask[idx]))); + rd_collision_x_mask.bits.push_back(State(bool(port.collision_x_mask[idx]))); + } + } rd_data.append(port.data); - log_assert(GetSize(port.data) == width); + for (auto &bit : port.arst_value) + rd_arst_value.bits.push_back(bit); + for (auto &bit : port.srst_value) + rd_srst_value.bits.push_back(bit); + for (auto &bit : port.init_value) + rd_init_value.bits.push_back(bit); } if (rd_ports.empty()) { + rd_wide_continuation = State::S0; rd_clk_enable = State::S0; rd_clk_polarity = State::S0; - rd_transparent = State::S0; + rd_ce_over_srst = State::S0; + rd_arst_value = State::S0; + rd_srst_value = State::S0; + rd_init_value = State::S0; + } + if (rd_ports.empty() || wr_ports.empty()) { + rd_transparency_mask = State::S0; + rd_collision_x_mask = State::S0; } + cell->parameters[ID::RD_PORTS] = Const(GetSize(rd_clk)); cell->parameters[ID::RD_CLK_ENABLE] = rd_clk_enable; cell->parameters[ID::RD_CLK_POLARITY] = rd_clk_polarity; - cell->parameters[ID::RD_TRANSPARENT] = rd_transparent; + cell->parameters[ID::RD_TRANSPARENCY_MASK] = rd_transparency_mask; + cell->parameters[ID::RD_COLLISION_X_MASK] = rd_collision_x_mask; + cell->parameters[ID::RD_WIDE_CONTINUATION] = rd_wide_continuation; + cell->parameters[ID::RD_CE_OVER_SRST] = rd_ce_over_srst; + cell->parameters[ID::RD_ARST_VALUE] = rd_arst_value; + cell->parameters[ID::RD_SRST_VALUE] = rd_srst_value; + cell->parameters[ID::RD_INIT_VALUE] = rd_init_value; + cell->parameters.erase(ID::RD_TRANSPARENT); cell->setPort(ID::RD_CLK, rd_clk); cell->setPort(ID::RD_EN, rd_en); + cell->setPort(ID::RD_ARST, rd_arst); + cell->setPort(ID::RD_SRST, rd_srst); cell->setPort(ID::RD_ADDR, rd_addr); cell->setPort(ID::RD_DATA, rd_data); for (auto &port : wr_ports) { @@ -116,25 +214,33 @@ void Mem::emit() { module->remove(port.cell); port.cell = nullptr; } - wr_clk_enable.bits.push_back(State(port.clk_enable)); - wr_clk_polarity.bits.push_back(State(port.clk_polarity)); - wr_clk.append(port.clk); - log_assert(GetSize(port.clk) == 1); + for (int sub = 0; sub < (1 << port.wide_log2); sub++) + { + wr_wide_continuation.bits.push_back(State(sub != 0)); + wr_clk_enable.bits.push_back(State(port.clk_enable)); + wr_clk_polarity.bits.push_back(State(port.clk_polarity)); + wr_clk.append(port.clk); + for (auto idx : wr_port_xlat) + wr_priority_mask.bits.push_back(State(bool(port.priority_mask[idx]))); + SigSpec addr = port.sub_addr(sub); + addr.extend_u0(abits, false); + wr_addr.append(addr); + log_assert(GetSize(addr) == abits); + } wr_en.append(port.en); - log_assert(GetSize(port.en) == width); - SigSpec addr = port.addr; - addr.extend_u0(abits, false); - wr_addr.append(addr); - log_assert(GetSize(addr) == abits); wr_data.append(port.data); - log_assert(GetSize(port.data) == width); } if (wr_ports.empty()) { + wr_wide_continuation = State::S0; wr_clk_enable = State::S0; wr_clk_polarity = State::S0; + wr_priority_mask = State::S0; } + cell->parameters[ID::WR_PORTS] = Const(GetSize(wr_clk)); cell->parameters[ID::WR_CLK_ENABLE] = wr_clk_enable; cell->parameters[ID::WR_CLK_POLARITY] = wr_clk_polarity; + cell->parameters[ID::WR_PRIORITY_MASK] = wr_priority_mask; + cell->parameters[ID::WR_WIDE_CONTINUATION] = wr_wide_continuation; cell->setPort(ID::WR_CLK, wr_clk); cell->setPort(ID::WR_EN, wr_en); cell->setPort(ID::WR_ADDR, wr_addr); @@ -161,30 +267,46 @@ void Mem::emit() { mem->width = width; mem->start_offset = start_offset; mem->size = size; + mem->attributes = attributes; for (auto &port : rd_ports) { if (!port.cell) - port.cell = module->addCell(NEW_ID, ID($memrd)); + port.cell = module->addCell(NEW_ID, ID($memrd_v2)); + port.cell->type = ID($memrd_v2); + port.cell->attributes = port.attributes; port.cell->parameters[ID::MEMID] = memid.str(); port.cell->parameters[ID::ABITS] = GetSize(port.addr); - port.cell->parameters[ID::WIDTH] = width; + port.cell->parameters[ID::WIDTH] = width << port.wide_log2; port.cell->parameters[ID::CLK_ENABLE] = port.clk_enable; port.cell->parameters[ID::CLK_POLARITY] = port.clk_polarity; - port.cell->parameters[ID::TRANSPARENT] = port.transparent; + port.cell->parameters[ID::CE_OVER_SRST] = port.ce_over_srst; + port.cell->parameters[ID::ARST_VALUE] = port.arst_value; + port.cell->parameters[ID::SRST_VALUE] = port.srst_value; + port.cell->parameters[ID::INIT_VALUE] = port.init_value; + port.cell->parameters[ID::TRANSPARENCY_MASK] = port.transparency_mask; + port.cell->parameters[ID::COLLISION_X_MASK] = port.collision_x_mask; + port.cell->parameters.erase(ID::TRANSPARENT); port.cell->setPort(ID::CLK, port.clk); port.cell->setPort(ID::EN, port.en); + port.cell->setPort(ID::ARST, port.arst); + port.cell->setPort(ID::SRST, port.srst); port.cell->setPort(ID::ADDR, port.addr); port.cell->setPort(ID::DATA, port.data); } int idx = 0; for (auto &port : wr_ports) { if (!port.cell) - port.cell = module->addCell(NEW_ID, ID($memwr)); + port.cell = module->addCell(NEW_ID, ID($memwr_v2)); + port.cell->type = ID($memwr_v2); + port.cell->attributes = port.attributes; + if (port.cell->parameters.count(ID::PRIORITY)) + port.cell->parameters.erase(ID::PRIORITY); port.cell->parameters[ID::MEMID] = memid.str(); port.cell->parameters[ID::ABITS] = GetSize(port.addr); - port.cell->parameters[ID::WIDTH] = width; + port.cell->parameters[ID::WIDTH] = width << port.wide_log2; port.cell->parameters[ID::CLK_ENABLE] = port.clk_enable; port.cell->parameters[ID::CLK_POLARITY] = port.clk_polarity; - port.cell->parameters[ID::PRIORITY] = idx++; + port.cell->parameters[ID::PORTID] = idx++; + port.cell->parameters[ID::PRIORITY_MASK] = port.priority_mask; port.cell->setPort(ID::CLK, port.clk); port.cell->setPort(ID::EN, port.en); port.cell->setPort(ID::ADDR, port.addr); @@ -192,8 +314,12 @@ void Mem::emit() { } idx = 0; for (auto &init : inits) { + bool v2 = !init.en.is_fully_ones(); if (!init.cell) - init.cell = module->addCell(NEW_ID, ID($meminit)); + init.cell = module->addCell(NEW_ID, v2 ? ID($meminit_v2) : ID($meminit)); + else + init.cell->type = v2 ? ID($meminit_v2) : ID($meminit); + init.cell->attributes = init.attributes; init.cell->parameters[ID::MEMID] = memid.str(); init.cell->parameters[ID::ABITS] = GetSize(init.addr); init.cell->parameters[ID::WIDTH] = width; @@ -201,42 +327,185 @@ void Mem::emit() { init.cell->parameters[ID::PRIORITY] = idx++; init.cell->setPort(ID::ADDR, init.addr); init.cell->setPort(ID::DATA, init.data); + if (v2) + init.cell->setPort(ID::EN, init.en); + else + init.cell->unsetPort(ID::EN); } } } -void Mem::remove_wr_port(int idx) { - if (wr_ports[idx].cell) { - module->remove(wr_ports[idx].cell); - } - wr_ports.erase(wr_ports.begin() + idx); +void Mem::clear_inits() { + for (auto &init : inits) + init.removed = true; } -void Mem::remove_rd_port(int idx) { - if (rd_ports[idx].cell) { - module->remove(rd_ports[idx].cell); +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; + bool valid = false; + for (auto bit : init.en) + if (bit == State::S1) + valid = true; + if (!valid) { + init.removed = true; + 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) { + auto &init = inits[chunk_inits[0]]; + if (!init.en.is_fully_ones()) { + for (int i = 0; i < GetSize(init.data); i++) + if (init.en[i % width] != State::S1) + init.data[i] = State::Sx; + init.en = Const(State::S1, width); + } + 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++) + if (init.en[i % width] == State::S1) + cdata.bits[i+offset] = init.data.bits[i]; + init.removed = true; + } + MemInit new_init; + new_init.addr = caddr; + new_init.data = cdata; + new_init.en = Const(State::S1, width); + inits.push_back(new_init); } - rd_ports.erase(rd_ports.begin() + idx); -} - -void Mem::clear_inits() { - for (auto &init : inits) - if (init.cell) - module->remove(init.cell); - inits.clear(); } Const Mem::get_init_data() const { Const init_data(State::Sx, width * size); for (auto &init : inits) { + if (init.removed) + continue; int offset = (init.addr.as_int() - start_offset) * width; for (int i = 0; i < GetSize(init.data); i++) - if (0 <= i+offset && i+offset < GetSize(init_data)) + if (0 <= i+offset && i+offset < GetSize(init_data) && init.en[i % width] == State::S1) init_data.bits[i+offset] = init.data.bits[i]; } return init_data; } +void Mem::check() { + int max_wide_log2 = 0; + for (auto &port : rd_ports) { + if (port.removed) + continue; + log_assert(GetSize(port.clk) == 1); + log_assert(GetSize(port.en) == 1); + log_assert(GetSize(port.arst) == 1); + log_assert(GetSize(port.srst) == 1); + log_assert(GetSize(port.data) == (width << port.wide_log2)); + log_assert(GetSize(port.init_value) == (width << port.wide_log2)); + log_assert(GetSize(port.arst_value) == (width << port.wide_log2)); + log_assert(GetSize(port.srst_value) == (width << port.wide_log2)); + if (!port.clk_enable) { + log_assert(port.en == State::S1); + log_assert(port.arst == State::S0); + log_assert(port.srst == State::S0); + } + for (int j = 0; j < port.wide_log2; j++) { + log_assert(port.addr[j] == State::S0); + } + max_wide_log2 = std::max(max_wide_log2, port.wide_log2); + log_assert(GetSize(port.transparency_mask) == GetSize(wr_ports)); + log_assert(GetSize(port.collision_x_mask) == GetSize(wr_ports)); + for (int j = 0; j < GetSize(wr_ports); j++) { + auto &wport = wr_ports[j]; + if ((port.transparency_mask[j] || port.collision_x_mask[j]) && !wport.removed) { + log_assert(port.clk_enable); + log_assert(wport.clk_enable); + log_assert(port.clk == wport.clk); + log_assert(port.clk_polarity == wport.clk_polarity); + } + log_assert(!port.transparency_mask[j] || !port.collision_x_mask[j]); + } + } + for (int i = 0; i < GetSize(wr_ports); i++) { + auto &port = wr_ports[i]; + if (port.removed) + continue; + log_assert(GetSize(port.clk) == 1); + log_assert(GetSize(port.en) == (width << port.wide_log2)); + log_assert(GetSize(port.data) == (width << port.wide_log2)); + for (int j = 0; j < port.wide_log2; j++) { + log_assert(port.addr[j] == State::S0); + } + max_wide_log2 = std::max(max_wide_log2, port.wide_log2); + log_assert(GetSize(port.priority_mask) == GetSize(wr_ports)); + for (int j = 0; j < GetSize(wr_ports); j++) { + auto &wport = wr_ports[j]; + if (port.priority_mask[j] && !wport.removed) { + log_assert(j < i); + log_assert(port.clk_enable == wport.clk_enable); + if (port.clk_enable) { + log_assert(port.clk == wport.clk); + log_assert(port.clk_polarity == wport.clk_polarity); + } + } + } + } + int mask = (1 << max_wide_log2) - 1; + log_assert(!(start_offset & mask)); + log_assert(!(size & mask)); +} + namespace { struct MemIndex { @@ -245,11 +514,11 @@ namespace { dict<IdString, pool<Cell *>> inits; MemIndex (Module *module) { for (auto cell: module->cells()) { - if (cell->type == ID($memwr)) + if (cell->type.in(ID($memwr), ID($memwr_v2))) wr_ports[cell->parameters.at(ID::MEMID).decode_string()].insert(cell); - else if (cell->type == ID($memrd)) + else if (cell->type.in(ID($memrd), ID($memrd_v2))) rd_ports[cell->parameters.at(ID::MEMID).decode_string()].insert(cell); - else if (cell->type == ID($meminit)) + else if (cell->type.in(ID($meminit), ID($meminit_v2))) inits[cell->parameters.at(ID::MEMID).decode_string()].insert(cell); } } @@ -260,25 +529,56 @@ namespace { res.packed = false; res.mem = mem; res.attributes = mem->attributes; + std::vector<bool> rd_transparent; + std::vector<int> wr_portid; if (index.rd_ports.count(mem->name)) { for (auto cell : index.rd_ports.at(mem->name)) { MemRd mrd; + bool is_compat = cell->type == ID($memrd); mrd.cell = cell; mrd.attributes = cell->attributes; mrd.clk_enable = cell->parameters.at(ID::CLK_ENABLE).as_bool(); mrd.clk_polarity = cell->parameters.at(ID::CLK_POLARITY).as_bool(); - mrd.transparent = cell->parameters.at(ID::TRANSPARENT).as_bool(); mrd.clk = cell->getPort(ID::CLK); mrd.en = cell->getPort(ID::EN); mrd.addr = cell->getPort(ID::ADDR); mrd.data = cell->getPort(ID::DATA); + mrd.wide_log2 = ceil_log2(GetSize(mrd.data) / mem->width); + bool transparent = false; + if (is_compat) { + transparent = cell->parameters.at(ID::TRANSPARENT).as_bool(); + mrd.ce_over_srst = false; + mrd.arst_value = Const(State::Sx, mem->width << mrd.wide_log2); + mrd.srst_value = Const(State::Sx, mem->width << mrd.wide_log2); + mrd.init_value = Const(State::Sx, mem->width << mrd.wide_log2); + mrd.srst = State::S0; + mrd.arst = State::S0; + if (!mrd.clk_enable) { + // Fix some patterns that we'll allow for backwards compatibility, + // but don't want to see moving forwards: async transparent + // ports (inherently meaningless) and async ports without + // const 1 tied to EN bit (which may mean a latch in the future). + transparent = false; + if (mrd.en == State::Sx) + mrd.en = State::S1; + } + } else { + mrd.ce_over_srst = cell->parameters.at(ID::CE_OVER_SRST).as_bool(); + mrd.arst_value = cell->parameters.at(ID::ARST_VALUE); + mrd.srst_value = cell->parameters.at(ID::SRST_VALUE); + mrd.init_value = cell->parameters.at(ID::INIT_VALUE); + mrd.arst = cell->getPort(ID::ARST); + mrd.srst = cell->getPort(ID::SRST); + } res.rd_ports.push_back(mrd); + rd_transparent.push_back(transparent); } } if (index.wr_ports.count(mem->name)) { std::vector<std::pair<int, MemWr>> ports; for (auto cell : index.wr_ports.at(mem->name)) { MemWr mwr; + bool is_compat = cell->type == ID($memwr); mwr.cell = cell; mwr.attributes = cell->attributes; mwr.clk_enable = cell->parameters.at(ID::CLK_ENABLE).as_bool(); @@ -287,11 +587,37 @@ namespace { mwr.en = cell->getPort(ID::EN); mwr.addr = cell->getPort(ID::ADDR); mwr.data = cell->getPort(ID::DATA); - ports.push_back(std::make_pair(cell->parameters.at(ID::PRIORITY).as_int(), mwr)); + mwr.wide_log2 = ceil_log2(GetSize(mwr.data) / mem->width); + ports.push_back(std::make_pair(cell->parameters.at(is_compat ? ID::PRIORITY : ID::PORTID).as_int(), mwr)); } std::sort(ports.begin(), ports.end(), [](const std::pair<int, MemWr> &a, const std::pair<int, MemWr> &b) { return a.first < b.first; }); - for (auto &it : ports) + for (auto &it : ports) { res.wr_ports.push_back(it.second); + wr_portid.push_back(it.first); + } + for (int i = 0; i < GetSize(res.wr_ports); i++) { + auto &port = res.wr_ports[i]; + bool is_compat = port.cell->type == ID($memwr); + if (is_compat) { + port.priority_mask.resize(GetSize(res.wr_ports)); + for (int j = 0; j < i; j++) { + auto &oport = res.wr_ports[j]; + if (port.clk_enable != oport.clk_enable) + continue; + if (port.clk_enable && port.clk != oport.clk) + continue; + if (port.clk_enable && port.clk_polarity != oport.clk_polarity) + continue; + port.priority_mask[j] = true; + } + } else { + Const orig_prio_mask = port.cell->parameters.at(ID::PRIORITY_MASK); + for (int orig_portid : wr_portid) { + bool has_prio = orig_portid < GetSize(orig_prio_mask) && orig_prio_mask[orig_portid] == State::S1; + port.priority_mask.push_back(has_prio); + } + } + } } if (index.inits.count(mem->name)) { std::vector<std::pair<int, MemInit>> inits; @@ -307,12 +633,50 @@ namespace { log_error("Non-constant data %s in memory initialization %s.\n", log_signal(data), log_id(cell)); init.addr = addr.as_const(); init.data = data.as_const(); + if (cell->type == ID($meminit_v2)) { + auto en = cell->getPort(ID::EN); + if (!en.is_fully_const()) + log_error("Non-constant enable %s in memory initialization %s.\n", log_signal(en), log_id(cell)); + init.en = en.as_const(); + } else { + init.en = RTLIL::Const(State::S1, mem->width); + } inits.push_back(std::make_pair(cell->parameters.at(ID::PRIORITY).as_int(), init)); } std::sort(inits.begin(), inits.end(), [](const std::pair<int, MemInit> &a, const std::pair<int, MemInit> &b) { return a.first < b.first; }); for (auto &it : inits) res.inits.push_back(it.second); } + for (int i = 0; i < GetSize(res.rd_ports); i++) { + auto &port = res.rd_ports[i]; + bool is_compat = port.cell->type == ID($memrd); + if (is_compat) { + port.transparency_mask.resize(GetSize(res.wr_ports)); + port.collision_x_mask.resize(GetSize(res.wr_ports)); + if (!rd_transparent[i]) + continue; + if (!port.clk_enable) + continue; + for (int j = 0; j < GetSize(res.wr_ports); j++) { + auto &wport = res.wr_ports[j]; + if (!wport.clk_enable) + continue; + if (port.clk != wport.clk) + continue; + if (port.clk_polarity != wport.clk_polarity) + continue; + port.transparency_mask[j] = true; + } + } else { + Const orig_trans_mask = port.cell->parameters.at(ID::TRANSPARENCY_MASK); + Const orig_cx_mask = port.cell->parameters.at(ID::COLLISION_X_MASK); + for (int orig_portid : wr_portid) { + port.transparency_mask.push_back(orig_portid < GetSize(orig_trans_mask) && orig_trans_mask[orig_portid] == State::S1); + port.collision_x_mask.push_back(orig_portid < GetSize(orig_cx_mask) && orig_cx_mask[orig_portid] == State::S1); + } + } + } + res.check(); return res; } @@ -322,6 +686,7 @@ namespace { cell->parameters.at(ID::OFFSET).as_int(), cell->parameters.at(ID::SIZE).as_int() ); + bool is_compat = cell->type == ID($mem); int abits = cell->parameters.at(ID::ABITS).as_int(); res.packed = true; res.cell = cell; @@ -343,32 +708,112 @@ namespace { MemInit minit; minit.addr = res.start_offset + pos; minit.data = init.extract(pos * res.width, (epos - pos) * res.width, State::Sx); + minit.en = RTLIL::Const(State::S1, res.width); res.inits.push_back(minit); pos = epos; } } } - for (int i = 0; i < cell->parameters.at(ID::RD_PORTS).as_int(); i++) { + int n_rd_ports = cell->parameters.at(ID::RD_PORTS).as_int(); + int n_wr_ports = cell->parameters.at(ID::WR_PORTS).as_int(); + Const rd_wide_continuation = is_compat ? Const(State::S0, n_rd_ports) : cell->parameters.at(ID::RD_WIDE_CONTINUATION); + Const wr_wide_continuation = is_compat ? Const(State::S0, n_wr_ports) : cell->parameters.at(ID::WR_WIDE_CONTINUATION); + for (int i = 0, ni; i < n_rd_ports; i = ni) { + ni = i + 1; + while (ni < n_rd_ports && rd_wide_continuation[ni] == State::S1) + ni++; MemRd mrd; + mrd.wide_log2 = ceil_log2(ni - i); + log_assert(ni - i == (1 << mrd.wide_log2)); mrd.clk_enable = cell->parameters.at(ID::RD_CLK_ENABLE).extract(i, 1).as_bool(); mrd.clk_polarity = cell->parameters.at(ID::RD_CLK_POLARITY).extract(i, 1).as_bool(); - mrd.transparent = cell->parameters.at(ID::RD_TRANSPARENT).extract(i, 1).as_bool(); mrd.clk = cell->getPort(ID::RD_CLK).extract(i, 1); mrd.en = cell->getPort(ID::RD_EN).extract(i, 1); mrd.addr = cell->getPort(ID::RD_ADDR).extract(i * abits, abits); - mrd.data = cell->getPort(ID::RD_DATA).extract(i * res.width, res.width); + mrd.data = cell->getPort(ID::RD_DATA).extract(i * res.width, (ni - i) * res.width); + if (is_compat) { + mrd.ce_over_srst = false; + mrd.arst_value = Const(State::Sx, res.width << mrd.wide_log2); + mrd.srst_value = Const(State::Sx, res.width << mrd.wide_log2); + mrd.init_value = Const(State::Sx, res.width << mrd.wide_log2); + mrd.arst = State::S0; + mrd.srst = State::S0; + } else { + mrd.ce_over_srst = cell->parameters.at(ID::RD_CE_OVER_SRST).extract(i, 1).as_bool(); + mrd.arst_value = cell->parameters.at(ID::RD_ARST_VALUE).extract(i * res.width, (ni - i) * res.width); + mrd.srst_value = cell->parameters.at(ID::RD_SRST_VALUE).extract(i * res.width, (ni - i) * res.width); + mrd.init_value = cell->parameters.at(ID::RD_INIT_VALUE).extract(i * res.width, (ni - i) * res.width); + mrd.arst = cell->getPort(ID::RD_ARST).extract(i, 1); + mrd.srst = cell->getPort(ID::RD_SRST).extract(i, 1); + } + if (!is_compat) { + Const transparency_mask = cell->parameters.at(ID::RD_TRANSPARENCY_MASK).extract(i * n_wr_ports, n_wr_ports); + Const collision_x_mask = cell->parameters.at(ID::RD_COLLISION_X_MASK).extract(i * n_wr_ports, n_wr_ports); + for (int j = 0; j < n_wr_ports; j++) + if (wr_wide_continuation[j] != State::S1) { + mrd.transparency_mask.push_back(transparency_mask[j] == State::S1); + mrd.collision_x_mask.push_back(collision_x_mask[j] == State::S1); + } + } res.rd_ports.push_back(mrd); } - for (int i = 0; i < cell->parameters.at(ID::WR_PORTS).as_int(); i++) { + for (int i = 0, ni; i < n_wr_ports; i = ni) { + ni = i + 1; + while (ni < n_wr_ports && wr_wide_continuation[ni] == State::S1) + ni++; MemWr mwr; + mwr.wide_log2 = ceil_log2(ni - i); + log_assert(ni - i == (1 << mwr.wide_log2)); mwr.clk_enable = cell->parameters.at(ID::WR_CLK_ENABLE).extract(i, 1).as_bool(); mwr.clk_polarity = cell->parameters.at(ID::WR_CLK_POLARITY).extract(i, 1).as_bool(); mwr.clk = cell->getPort(ID::WR_CLK).extract(i, 1); - mwr.en = cell->getPort(ID::WR_EN).extract(i * res.width, res.width); + mwr.en = cell->getPort(ID::WR_EN).extract(i * res.width, (ni - i) * res.width); mwr.addr = cell->getPort(ID::WR_ADDR).extract(i * abits, abits); - mwr.data = cell->getPort(ID::WR_DATA).extract(i * res.width, res.width); + mwr.data = cell->getPort(ID::WR_DATA).extract(i * res.width, (ni - i) * res.width); + if (!is_compat) { + Const priority_mask = cell->parameters.at(ID::WR_PRIORITY_MASK).extract(i * n_wr_ports, n_wr_ports); + for (int j = 0; j < n_wr_ports; j++) + if (wr_wide_continuation[j] != State::S1) + mwr.priority_mask.push_back(priority_mask[j] == State::S1); + } res.wr_ports.push_back(mwr); } + if (is_compat) { + for (int i = 0; i < GetSize(res.wr_ports); i++) { + auto &port = res.wr_ports[i]; + port.priority_mask.resize(GetSize(res.wr_ports)); + for (int j = 0; j < i; j++) { + auto &oport = res.wr_ports[j]; + if (port.clk_enable != oport.clk_enable) + continue; + if (port.clk_enable && port.clk != oport.clk) + continue; + if (port.clk_enable && port.clk_polarity != oport.clk_polarity) + continue; + port.priority_mask[j] = true; + } + } + for (int i = 0; i < GetSize(res.rd_ports); i++) { + auto &port = res.rd_ports[i]; + port.transparency_mask.resize(GetSize(res.wr_ports)); + port.collision_x_mask.resize(GetSize(res.wr_ports)); + if (!cell->parameters.at(ID::RD_TRANSPARENT).extract(i, 1).as_bool()) + continue; + if (!port.clk_enable) + continue; + for (int j = 0; j < GetSize(res.wr_ports); j++) { + auto &wport = res.wr_ports[j]; + if (!wport.clk_enable) + continue; + if (port.clk != wport.clk) + continue; + if (port.clk_polarity != wport.clk_polarity) + continue; + port.transparency_mask[j] = true; + } + } + } + res.check(); return res; } @@ -381,7 +826,7 @@ std::vector<Mem> Mem::get_all_memories(Module *module) { res.push_back(mem_from_memory(module, it.second, index)); } for (auto cell: module->cells()) { - if (cell->type == ID($mem)) + if (cell->type.in(ID($mem), ID($mem_v2))) res.push_back(mem_from_cell(cell)); } return res; @@ -395,13 +840,13 @@ std::vector<Mem> Mem::get_selected_memories(Module *module) { res.push_back(mem_from_memory(module, it.second, index)); } for (auto cell: module->selected_cells()) { - if (cell->type == ID($mem)) + if (cell->type.in(ID($mem), ID($mem_v2))) res.push_back(mem_from_cell(cell)); } return res; } -Cell *Mem::extract_rdff(int idx) { +Cell *Mem::extract_rdff(int idx, FfInitVals *initvals) { MemRd &port = rd_ports[idx]; if (!port.clk_enable) @@ -409,28 +854,813 @@ Cell *Mem::extract_rdff(int idx) { Cell *c; - if (port.transparent) + // 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 = true; + for (int i = 0; i < GetSize(wr_ports); i++) + if (!port.transparency_mask[i] && !wr_ports[i].removed) + trans_use_addr = false; + + // 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) { - SigSpec sig_q = module->addWire(stringf("%s$rdreg[%d]$q", memid.c_str(), idx), GetSize(port.addr)); - SigSpec sig_d = port.addr; - port.addr = sig_q; - c = module->addDffe(stringf("%s$rdreg[%d]", memid.c_str(), idx), port.clk, port.en, sig_d, sig_q, port.clk_polarity, true); + // Do not put a register in front of constant address bits — this is both + // unnecessary and will break wide ports. + int width = 0; + for (int i = 0; i < GetSize(port.addr); i++) + if (port.addr[i].wire) + width++; + + 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++]; + } + + c = module->addDff(stringf("$%s$rdreg[%d]", memid.c_str(), idx), port.clk, sig_d, sig_q, port.clk_polarity); + } else { + c = nullptr; + } } else { - SigSpec sig_d = module->addWire(stringf("%s$rdreg[%d]$d", memid.c_str(), idx), width); - SigSpec sig_q = port.data; - port.data = sig_d; - c = module->addDffe(stringf("%s$rdreg[%d]", memid.c_str(), idx), port.clk, port.en, sig_d, sig_q, port.clk_polarity, true); + log_assert(port.arst == State::S0 || port.srst == State::S0); + + 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 (wport.removed) + continue; + if (port.transparency_mask[i] || port.collision_x_mask[i]) { + 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; + if (wide_write) + waddr = wport.sub_addr(sub); + else + raddr = port.sub_addr(sub); + 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 = port.transparency_mask[i] ? wport.data.extract(pos + wsub * width, epos-pos) : Const(State::Sx, 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(module, initvals, name); + ff.width = GetSize(port.data); + ff.has_clk = true; + ff.sig_clk = port.clk; + ff.pol_clk = port.clk_polarity; + if (port.en != State::S1) { + ff.has_ce = true; + ff.pol_ce = true; + ff.sig_ce = port.en; + } + if (port.arst != State::S0) { + ff.has_arst = true; + ff.pol_arst = true; + ff.sig_arst = port.arst; + ff.val_arst = port.arst_value; + } + if (port.srst != State::S0) { + ff.has_srst = true; + ff.pol_srst = true; + ff.sig_srst = port.srst; + ff.val_srst = port.srst_value; + ff.ce_over_srst = ff.has_ce && port.ce_over_srst; + } + ff.sig_d = sig_d; + ff.sig_q = port.data; + ff.val_init = port.init_value; + port.data = async_d; + c = ff.emit(); } - log("Extracted %s FF from read port %d of %s.%s: %s\n", port.transparent ? "addr" : "data", + if (c) + 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; port.clk = State::S0; + port.arst = State::S0; + port.srst = State::S0; port.clk_enable = false; port.clk_polarity = true; + port.ce_over_srst = false; + port.arst_value = Const(State::Sx, GetSize(port.data)); + port.srst_value = Const(State::Sx, GetSize(port.data)); + port.init_value = Const(State::Sx, GetSize(port.data)); + + for (int i = 0; i < GetSize(wr_ports); i++) { + port.transparency_mask[i] = false; + port.collision_x_mask[i] = false; + } return c; } + +void Mem::narrow() { + // NOTE: several passes depend on this function not modifying + // the design at all until (and unless) emit() is called. + // Be careful to preserve this. + std::vector<MemRd> new_rd_ports; + std::vector<MemWr> new_wr_ports; + std::vector<std::pair<int, int>> new_rd_map; + std::vector<std::pair<int, int>> new_wr_map; + for (int i = 0; i < GetSize(rd_ports); i++) { + auto &port = rd_ports[i]; + for (int sub = 0; sub < (1 << port.wide_log2); sub++) { + new_rd_map.push_back(std::make_pair(i, sub)); + } + } + for (int i = 0; i < GetSize(wr_ports); i++) { + auto &port = wr_ports[i]; + for (int sub = 0; sub < (1 << port.wide_log2); sub++) { + new_wr_map.push_back(std::make_pair(i, sub)); + } + } + for (auto &it : new_rd_map) { + MemRd &orig = rd_ports[it.first]; + MemRd port = orig; + if (it.second != 0) + port.cell = nullptr; + if (port.wide_log2) { + port.data = port.data.extract(it.second * width, width); + port.init_value = port.init_value.extract(it.second * width, width); + port.arst_value = port.arst_value.extract(it.second * width, width); + port.srst_value = port.srst_value.extract(it.second * width, width); + port.addr = port.sub_addr(it.second); + port.wide_log2 = 0; + } + port.transparency_mask.clear(); + port.collision_x_mask.clear(); + for (auto &it2 : new_wr_map) + port.transparency_mask.push_back(orig.transparency_mask[it2.first]); + for (auto &it2 : new_wr_map) + port.collision_x_mask.push_back(orig.collision_x_mask[it2.first]); + new_rd_ports.push_back(port); + } + for (auto &it : new_wr_map) { + MemWr &orig = wr_ports[it.first]; + MemWr port = orig; + if (it.second != 0) + port.cell = nullptr; + if (port.wide_log2) { + port.data = port.data.extract(it.second * width, width); + port.en = port.en.extract(it.second * width, width); + port.addr = port.sub_addr(it.second); + port.wide_log2 = 0; + } + port.priority_mask.clear(); + for (auto &it2 : new_wr_map) + port.priority_mask.push_back(orig.priority_mask[it2.first]); + new_wr_ports.push_back(port); + } + std::swap(rd_ports, new_rd_ports); + std::swap(wr_ports, new_wr_ports); +} + +void Mem::emulate_priority(int idx1, int idx2, FfInitVals *initvals) +{ + auto &port1 = wr_ports[idx1]; + auto &port2 = wr_ports[idx2]; + if (!port2.priority_mask[idx1]) + return; + for (int i = 0; i < GetSize(rd_ports); i++) { + auto &rport = rd_ports[i]; + if (rport.removed) + continue; + if (rport.transparency_mask[idx1] && !(rport.transparency_mask[idx2] || rport.collision_x_mask[idx2])) + emulate_transparency(idx1, i, initvals); + } + int min_wide_log2 = std::min(port1.wide_log2, port2.wide_log2); + int max_wide_log2 = std::max(port1.wide_log2, port2.wide_log2); + bool wide1 = port1.wide_log2 > port2.wide_log2; + for (int sub = 0; sub < (1 << max_wide_log2); sub += (1 << min_wide_log2)) { + SigSpec addr1 = port1.addr; + SigSpec addr2 = port2.addr; + if (wide1) + addr1 = port1.sub_addr(sub); + else + addr2 = port2.sub_addr(sub); + SigSpec addr_eq = module->Eq(NEW_ID, addr1, addr2); + int ewidth = width << min_wide_log2; + int sub1 = wide1 ? sub : 0; + int sub2 = wide1 ? 0 : sub; + dict<std::pair<SigBit, SigBit>, SigBit> cache; + for (int pos = 0; pos < ewidth; pos++) { + SigBit &en1 = port1.en[pos + sub1 * width]; + SigBit &en2 = port2.en[pos + sub2 * width]; + std::pair<SigBit, SigBit> key(en1, en2); + if (cache.count(key)) { + en1 = cache[key]; + } else { + SigBit active2 = module->And(NEW_ID, addr_eq, en2); + SigBit nactive2 = module->Not(NEW_ID, active2); + en1 = cache[key] = module->And(NEW_ID, en1, nactive2); + } + } + } + port2.priority_mask[idx1] = false; +} + +void Mem::emulate_transparency(int widx, int ridx, FfInitVals *initvals) { + auto &wport = wr_ports[widx]; + auto &rport = rd_ports[ridx]; + log_assert(rport.transparency_mask[widx]); + // If other write ports have priority over this one, emulate their transparency too. + for (int i = GetSize(wr_ports) - 1; i > widx; i--) { + if (wr_ports[i].removed) + continue; + if (rport.transparency_mask[i] && wr_ports[i].priority_mask[widx]) + emulate_transparency(i, ridx, initvals); + } + int min_wide_log2 = std::min(rport.wide_log2, wport.wide_log2); + int max_wide_log2 = std::max(rport.wide_log2, wport.wide_log2); + bool wide_write = wport.wide_log2 > rport.wide_log2; + // The write data FF doesn't need full reset/init behavior, as it'll be masked by + // the mux whenever this would be relevant. It does, however, need to have the same + // clock enable signal as the read port. + SigSpec wdata_q = module->addWire(NEW_ID, GetSize(wport.data)); + module->addDffe(NEW_ID, rport.clk, rport.en, wport.data, wdata_q, rport.clk_polarity, true); + for (int sub = 0; sub < (1 << max_wide_log2); sub += (1 << min_wide_log2)) { + SigSpec raddr = rport.addr; + SigSpec waddr = wport.addr; + for (int j = min_wide_log2; j < max_wide_log2; j++) + if (wide_write) + waddr = wport.sub_addr(sub); + else + raddr = rport.sub_addr(sub); + SigSpec addr_eq; + if (raddr != waddr) + addr_eq = module->Eq(NEW_ID, raddr, waddr); + int pos = 0; + int ewidth = width << min_wide_log2; + int wsub = wide_write ? sub : 0; + int rsub = wide_write ? 0 : sub; + SigSpec rdata_a = module->addWire(NEW_ID, ewidth); + while (pos < ewidth) { + int epos = pos; + while (epos < ewidth && wport.en[epos + wsub * width] == wport.en[pos + wsub * width]) + epos++; + SigSpec cond; + if (raddr != waddr) + cond = module->And(NEW_ID, wport.en[pos + wsub * width], addr_eq); + else + cond = wport.en[pos + wsub * width]; + SigSpec cond_q = module->addWire(NEW_ID); + // The FF for storing the bypass enable signal must be carefully + // constructed to preserve the overall init/reset/enable behavior + // of the whole port. + FfData ff(module, initvals, NEW_ID); + ff.width = 1; + ff.sig_q = cond_q; + ff.sig_d = cond; + ff.has_clk = true; + ff.sig_clk = rport.clk; + ff.pol_clk = rport.clk_polarity; + if (rport.en != State::S1) { + ff.has_ce = true; + ff.sig_ce = rport.en; + ff.pol_ce = true; + } + if (rport.arst != State::S0) { + ff.has_arst = true; + ff.sig_arst = rport.arst; + ff.pol_arst = true; + ff.val_arst = State::S0; + } + if (rport.srst != State::S0) { + ff.has_srst = true; + ff.sig_srst = rport.srst; + ff.pol_srst = true; + ff.val_srst = State::S0; + ff.ce_over_srst = rport.ce_over_srst; + } + if (!rport.init_value.is_fully_undef()) + ff.val_init = State::S0; + else + ff.val_init = State::Sx; + ff.emit(); + // And the final bypass mux. + SigSpec cur = rdata_a.extract(pos, epos-pos); + SigSpec other = wdata_q.extract(pos + wsub * width, epos-pos); + SigSpec dest = rport.data.extract(pos + rsub * width, epos-pos); + module->addMux(NEW_ID, cur, other, cond_q, dest); + pos = epos; + } + rport.data.replace(rsub * width, rdata_a); + } + rport.transparency_mask[widx] = false; + rport.collision_x_mask[widx] = true; +} + +void Mem::prepare_wr_merge(int idx1, int idx2, FfInitVals *initvals) { + log_assert(idx1 < idx2); + auto &port1 = wr_ports[idx1]; + auto &port2 = wr_ports[idx2]; + // If port 2 has priority over a port before port 1, make port 1 have priority too. + for (int i = 0; i < idx1; i++) + if (port2.priority_mask[i]) + port1.priority_mask[i] = true; + // If port 2 has priority over a port after port 1, emulate it. + for (int i = idx1 + 1; i < idx2; i++) + if (port2.priority_mask[i] && !wr_ports[i].removed) + emulate_priority(i, idx2, initvals); + // If some port had priority over port 2, make it have priority over the merged port too. + for (int i = idx2 + 1; i < GetSize(wr_ports); i++) { + auto &oport = wr_ports[i]; + if (oport.priority_mask[idx2]) + oport.priority_mask[idx1] = true; + } + // Make sure all read ports have identical collision/transparency behavior wrt both + // ports. + for (int i = 0; i < GetSize(rd_ports); i++) { + auto &rport = rd_ports[i]; + if (rport.removed) + continue; + // If collision already undefined with both ports, it's fine. + if (rport.collision_x_mask[idx1] && rport.collision_x_mask[idx2]) + continue; + // If one port has undefined collision, change it to the behavior + // of the other port. + if (rport.collision_x_mask[idx1]) { + rport.collision_x_mask[idx1] = false; + rport.transparency_mask[idx1] = rport.transparency_mask[idx2]; + continue; + } + if (rport.collision_x_mask[idx2]) { + rport.collision_x_mask[idx2] = false; + rport.transparency_mask[idx2] = rport.transparency_mask[idx1]; + continue; + } + // If transparent with both ports, also fine. + if (rport.transparency_mask[idx1] && rport.transparency_mask[idx2]) + continue; + // If transparent with only one, emulate it, and remove the collision-X + // flag that emulate_transparency will set (to align with the other port). + if (rport.transparency_mask[idx1]) { + emulate_transparency(i, idx1, initvals); + rport.collision_x_mask[idx1] = false; + continue; + } + if (rport.transparency_mask[idx2]) { + emulate_transparency(i, idx2, initvals); + rport.collision_x_mask[idx2] = false; + continue; + } + // If we got here, it's transparent with neither port, which is fine. + } +} + +void Mem::prepare_rd_merge(int idx1, int idx2, FfInitVals *initvals) { + auto &port1 = rd_ports[idx1]; + auto &port2 = rd_ports[idx2]; + // Note that going through write ports in order is important, since + // emulating transparency of a write port can change transparency + // mask for higher-numbered ports (due to transitive transparency + // emulation needed because of write port priority). + for (int i = 0; i < GetSize(wr_ports); i++) { + if (wr_ports[i].removed) + continue; + // Both ports undefined, OK. + if (port1.collision_x_mask[i] && port2.collision_x_mask[i]) + continue; + // Only one port undefined — change its behavior + // to align with the other port. + if (port1.collision_x_mask[i]) { + port1.collision_x_mask[i] = false; + port1.transparency_mask[i] = port2.transparency_mask[i]; + continue; + } + if (port2.collision_x_mask[i]) { + port2.collision_x_mask[i] = false; + port2.transparency_mask[i] = port1.transparency_mask[i]; + continue; + } + // Both ports transparent, OK. + if (port1.transparency_mask[i] && port2.transparency_mask[i]) + continue; + // Only one port transparent — emulate transparency + // on the other. + if (port1.transparency_mask[i]) { + emulate_transparency(i, idx1, initvals); + port1.collision_x_mask[i] = false; + continue; + } + if (port2.transparency_mask[i]) { + emulate_transparency(i, idx2, initvals); + port2.collision_x_mask[i] = false; + continue; + } + // No ports transparent, OK. + } + +} + +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; + } +} + +void Mem::emulate_rden(int idx, FfInitVals *initvals) { + auto &port = rd_ports[idx]; + log_assert(port.clk_enable); + emulate_rd_ce_over_srst(idx); + Wire *new_data = module->addWire(NEW_ID, GetSize(port.data)); + Wire *prev_data = module->addWire(NEW_ID, GetSize(port.data)); + Wire *sel = module->addWire(NEW_ID); + FfData ff_sel(module, initvals, NEW_ID); + FfData ff_data(module, initvals, NEW_ID); + ff_sel.width = 1; + ff_sel.has_clk = true; + ff_sel.sig_clk = port.clk; + ff_sel.pol_clk = port.clk_polarity; + ff_sel.sig_d = port.en; + ff_sel.sig_q = sel; + ff_data.width = GetSize(port.data); + ff_data.has_clk = true; + ff_data.sig_clk = port.clk; + ff_data.pol_clk = port.clk_polarity; + ff_data.sig_d = port.data; + ff_data.sig_q = prev_data; + if (!port.init_value.is_fully_undef()) { + ff_sel.val_init = State::S0; + ff_data.val_init = port.init_value; + port.init_value = Const(State::Sx, GetSize(port.data)); + } else { + ff_sel.val_init = State::Sx; + ff_data.val_init = Const(State::Sx, GetSize(port.data)); + } + if (port.arst != State::S0) { + ff_sel.has_arst = true; + ff_sel.val_arst = State::S0; + ff_sel.sig_arst = port.arst; + ff_sel.pol_arst = true; + ff_data.has_arst = true; + ff_data.val_arst = port.arst_value; + ff_data.sig_arst = port.arst; + ff_data.pol_arst = true; + port.arst = State::S0; + } + if (port.srst != State::S0) { + log_assert(!port.ce_over_srst); + ff_sel.has_srst = true; + ff_sel.val_srst = State::S0; + ff_sel.sig_srst = port.srst; + ff_sel.pol_srst = true; + ff_sel.ce_over_srst = false; + ff_data.has_srst = true; + ff_data.val_srst = port.srst_value; + ff_data.sig_srst = port.srst; + ff_data.pol_srst = true; + ff_data.ce_over_srst = false; + port.srst = State::S0; + } + ff_sel.emit(); + ff_data.emit(); + module->addMux(NEW_ID, prev_data, new_data, sel, port.data); + port.data = new_data; + port.en = State::S1; +} + +void Mem::emulate_reset(int idx, bool emu_init, bool emu_arst, bool emu_srst, FfInitVals *initvals) { + auto &port = rd_ports[idx]; + if (emu_init && !port.init_value.is_fully_undef()) { + Wire *sel = module->addWire(NEW_ID); + FfData ff_sel(module, initvals, NEW_ID); + Wire *new_data = module->addWire(NEW_ID, GetSize(port.data)); + ff_sel.width = 1; + ff_sel.has_clk = true; + ff_sel.sig_clk = port.clk; + ff_sel.pol_clk = port.clk_polarity; + ff_sel.sig_d = State::S1; + ff_sel.sig_q = sel; + ff_sel.val_init = State::S0; + if (port.en != State::S1) { + ff_sel.has_ce = true; + ff_sel.sig_ce = port.en; + ff_sel.pol_ce = true; + ff_sel.ce_over_srst = port.ce_over_srst; + } + if (port.arst != State::S0) { + ff_sel.has_arst = true; + ff_sel.sig_arst = port.arst; + ff_sel.pol_arst = true; + if (emu_arst && port.arst_value == port.init_value) { + // If we're going to emulate async reset anyway, and the reset + // value is the same as init value, reuse the same mux. + ff_sel.val_arst = State::S0; + port.arst = State::S0; + } else { + ff_sel.val_arst = State::S1; + } + } + if (port.srst != State::S0) { + ff_sel.has_srst = true; + ff_sel.sig_srst = port.srst; + ff_sel.pol_srst = true; + if (emu_srst && port.srst_value == port.init_value) { + ff_sel.val_srst = State::S0; + port.srst = State::S0; + } else { + ff_sel.val_srst = State::S1; + } + } + ff_sel.emit(); + module->addMux(NEW_ID, port.init_value, new_data, sel, port.data); + port.data = new_data; + port.init_value = Const(State::Sx, GetSize(port.data)); + } + if (emu_arst && port.arst != State::S0) { + Wire *sel = module->addWire(NEW_ID); + FfData ff_sel(module, initvals, NEW_ID); + Wire *new_data = module->addWire(NEW_ID, GetSize(port.data)); + ff_sel.width = 1; + ff_sel.has_clk = true; + ff_sel.sig_clk = port.clk; + ff_sel.pol_clk = port.clk_polarity; + ff_sel.sig_d = State::S1; + ff_sel.sig_q = sel; + if (port.init_value.is_fully_undef()) + ff_sel.val_init = State::Sx; + else + ff_sel.val_init = State::S1; + if (port.en != State::S1) { + ff_sel.has_ce = true; + ff_sel.sig_ce = port.en; + ff_sel.pol_ce = true; + ff_sel.ce_over_srst = port.ce_over_srst; + } + ff_sel.has_arst = true; + ff_sel.sig_arst = port.arst; + ff_sel.pol_arst = true; + ff_sel.val_arst = State::S0; + if (port.srst != State::S0) { + ff_sel.has_srst = true; + ff_sel.sig_srst = port.srst; + ff_sel.pol_srst = true; + if (emu_srst && port.srst_value == port.arst_value) { + ff_sel.val_srst = State::S0; + port.srst = State::S0; + } else { + ff_sel.val_srst = State::S1; + } + } + ff_sel.emit(); + module->addMux(NEW_ID, port.arst_value, new_data, sel, port.data); + port.data = new_data; + port.arst = State::S0; + } + if (emu_srst && port.srst != State::S0) { + Wire *sel = module->addWire(NEW_ID); + FfData ff_sel(module, initvals, NEW_ID); + Wire *new_data = module->addWire(NEW_ID, GetSize(port.data)); + ff_sel.width = 1; + ff_sel.has_clk = true; + ff_sel.sig_clk = port.clk; + ff_sel.pol_clk = port.clk_polarity; + ff_sel.sig_d = State::S1; + ff_sel.sig_q = sel; + if (port.init_value.is_fully_undef()) + ff_sel.val_init = State::Sx; + else + ff_sel.val_init = State::S1; + if (port.en != State::S1) { + ff_sel.has_ce = true; + ff_sel.sig_ce = port.en; + ff_sel.pol_ce = true; + ff_sel.ce_over_srst = port.ce_over_srst; + } + ff_sel.has_srst = true; + ff_sel.sig_srst = port.srst; + ff_sel.pol_srst = true; + ff_sel.val_srst = State::S0; + if (port.arst != State::S0) { + ff_sel.has_arst = true; + ff_sel.sig_arst = port.arst; + ff_sel.pol_arst = true; + ff_sel.val_arst = State::S1; + } + ff_sel.emit(); + module->addMux(NEW_ID, port.srst_value, new_data, sel, port.data); + port.data = new_data; + port.srst = State::S0; + } +} + +void Mem::emulate_rd_ce_over_srst(int idx) { + auto &port = rd_ports[idx]; + log_assert(port.clk_enable); + if (port.en == State::S1 || port.srst == State::S0 || !port.ce_over_srst) { + port.ce_over_srst = false; + return; + } + port.ce_over_srst = false; + port.srst = module->And(NEW_ID, port.en, port.srst); +} + +void Mem::emulate_rd_srst_over_ce(int idx) { + auto &port = rd_ports[idx]; + log_assert(port.clk_enable); + if (port.en == State::S1 || port.srst == State::S0 || port.ce_over_srst) { + port.ce_over_srst = true; + return; + } + port.ce_over_srst = true; + port.en = module->Or(NEW_ID, port.en, port.srst); +} + +bool Mem::emulate_read_first_ok() { + if (wr_ports.empty()) + return false; + SigSpec clk = wr_ports[0].clk; + bool clk_polarity = wr_ports[0].clk_polarity; + for (auto &port: wr_ports) { + if (!port.clk_enable) + return false; + if (port.clk != clk) + return false; + if (port.clk_polarity != clk_polarity) + return false; + } + bool found_read_first = false; + for (auto &port: rd_ports) { + if (!port.clk_enable) + return false; + if (port.clk != clk) + return false; + if (port.clk_polarity != clk_polarity) + return false; + // No point doing this operation if there is no read-first relationship + // in the first place. + for (int j = 0; j < GetSize(wr_ports); j++) + if (!port.transparency_mask[j] && !port.collision_x_mask[j]) + found_read_first = true; + } + return found_read_first; +} + +void Mem::emulate_read_first(FfInitVals *initvals) { + log_assert(emulate_read_first_ok()); + for (int i = 0; i < GetSize(rd_ports); i++) + for (int j = 0; j < GetSize(wr_ports); j++) + if (rd_ports[i].transparency_mask[j]) + emulate_transparency(j, i, initvals); + for (int i = 0; i < GetSize(rd_ports); i++) + for (int j = 0; j < GetSize(wr_ports); j++) { + log_assert(!rd_ports[i].transparency_mask[j]); + rd_ports[i].collision_x_mask[j] = false; + rd_ports[i].transparency_mask[j] = true; + } + for (auto &port: wr_ports) { + Wire *new_data = module->addWire(NEW_ID, GetSize(port.data)); + Wire *new_addr = module->addWire(NEW_ID, GetSize(port.addr)); + auto compressed = port.compress_en(); + Wire *new_en = module->addWire(NEW_ID, GetSize(compressed.first)); + FfData ff_data(module, initvals, NEW_ID); + FfData ff_addr(module, initvals, NEW_ID); + FfData ff_en(module, initvals, NEW_ID); + ff_data.width = GetSize(port.data); + ff_data.has_clk = true; + ff_data.sig_clk = port.clk; + ff_data.pol_clk = port.clk_polarity; + ff_data.sig_d = port.data; + ff_data.sig_q = new_data;; + ff_data.val_init = Const(State::Sx, ff_data.width); + ff_data.emit(); + ff_addr.width = GetSize(port.addr); + ff_addr.has_clk = true; + ff_addr.sig_clk = port.clk; + ff_addr.pol_clk = port.clk_polarity; + ff_addr.sig_d = port.addr; + ff_addr.sig_q = new_addr;; + ff_addr.val_init = Const(State::Sx, ff_addr.width); + ff_addr.emit(); + ff_en.width = GetSize(compressed.first); + ff_en.has_clk = true; + ff_en.sig_clk = port.clk; + ff_en.pol_clk = port.clk_polarity; + ff_en.sig_d = compressed.first; + ff_en.sig_q = new_en;; + ff_en.val_init = Const(State::S0, ff_en.width); + ff_en.emit(); + port.data = new_data; + port.addr = new_addr; + port.en = port.decompress_en(compressed.second, new_en); + } +} + +std::pair<SigSpec, std::vector<int>> MemWr::compress_en() { + SigSpec sig = en[0]; + std::vector<int> swizzle; + SigBit prev_bit = en[0]; + int idx = 0; + for (auto &bit: en) { + if (bit != prev_bit) { + sig.append(bit); + prev_bit = bit; + idx++; + } + swizzle.push_back(idx); + } + log_assert(idx + 1 == GetSize(sig)); + return {sig, swizzle}; +} + +SigSpec MemWr::decompress_en(const std::vector<int> &swizzle, SigSpec sig) { + SigSpec res; + for (int i: swizzle) + res.append(sig[i]); + return res; +} |