diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 2951122d4..5698441df 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -2146,6 +2146,15 @@ private: struct ConstructToken { explicit ConstructToken() = default; }; friend struct RTLIL::Module; friend struct RTLIL::Patch; + + // Push existing port connections into signorm/bufnorm indices after module assignment. + // Assumes signals are already in normalized form. + void initIndex(); + + // Signorm index helpers (used by setPort/unsetPort/initIndex) + void signorm_index_remove(RTLIL::IdString portname, const RTLIL::SigSpec &old_signal, bool is_input); + void signorm_index_add(RTLIL::IdString portname, const RTLIL::SigSpec &new_signal, bool is_input); + bool bufnorm_handle_setPort(RTLIL::IdString portname, RTLIL::SigSpec &signal, dict::iterator conn_it); public: Hasher::hash_t hashidx_; [[nodiscard]] Hasher hash_into(Hasher h) const { h.eat(hashidx_); return h; } diff --git a/kernel/rtlil_bufnorm.cc b/kernel/rtlil_bufnorm.cc index 93f800f4f..3d5b86064 100644 --- a/kernel/rtlil_bufnorm.cc +++ b/kernel/rtlil_bufnorm.cc @@ -1087,15 +1087,161 @@ static bool ignored_cell(const RTLIL::IdString& type) return type == ID($specify2) || type == ID($specify3) || type == ID($specrule); } +void RTLIL::Cell::signorm_index_remove(IdString portname, const SigSpec &old_signal, bool is_input) +{ + auto &index = *module->sig_norm_index; + index.dirty.insert(this); + if (is_input) { + int i = 0; + for (auto bit : old_signal) { + if (bit.is_wire()) { + auto found = index.fanout.find(bit); + log_assert(found != index.fanout.end()); + int erased = found->second.erase(PortBit(this, portname, i)); + log_assert(erased); + if (found->second.empty()) + index.fanout.erase(found); + } + i++; + } + } else { + Wire *w = old_signal.as_wire(); + log_assert(w->driverCell_ == this); + log_assert(w->driverPort_ == portname); + w->driverCell_ = nullptr; + w->driverPort_ = IdString(); + } +} + +void RTLIL::Cell::signorm_index_add(IdString portname, const SigSpec &new_signal, bool is_input) +{ + auto &index = *module->sig_norm_index; + index.dirty.insert(this); + if (is_input) { + int i = 0; + for (auto bit : new_signal) { + if (bit.is_wire()) + index.fanout[bit].insert(PortBit(this, portname, i)); + i++; + } + } else if (GetSize(new_signal)) { + Wire *w = new_signal.as_wire(); + log_assert(w->driverCell_ == nullptr); + log_assert(w->driverPort_.empty()); + w->driverCell_ = this; + w->driverPort_ = portname; + } +} + +// Handles the bufnorm part of setPort. Updates conn_it->second and returns true if the +// connection was stored (fast path or $connect cell). If false, caller must store signal. +bool RTLIL::Cell::bufnorm_handle_setPort(IdString portname, SigSpec &signal, dict::iterator conn_it) +{ + // Eagerly clear a driver that got disconnected by changing this port connection + if (conn_it->second.is_wire()) { + Wire *w = conn_it->second.as_wire(); + if (w->driverCell_ == this && w->driverPort_ == portname) { + w->driverCell_ = nullptr; + w->driverPort_ = IdString(); + module->buf_norm_wire_queue.insert(w); + } + } + + auto dir = port_dir(portname); + // Fast path: connecting a full driverless wire to an output port — everything else + // goes through the bufnorm queues and is handled during the next bufNormalize call + if ((dir == RTLIL::PD_OUTPUT || dir == RTLIL::PD_INOUT) && signal.is_wire()) { + Wire *w = signal.as_wire(); + if (w->driverCell_ == nullptr && + (w->port_input && !w->port_output) == (type == ID($input_port))) { + w->driverCell_ = this; + w->driverPort_ = portname; + conn_it->second = std::move(signal); + return true; + } + } + + if (dir == RTLIL::PD_OUTPUT || dir == RTLIL::PD_INOUT) { + module->buf_norm_cell_queue.insert(this); + module->buf_norm_cell_port_queue.emplace(this, portname); + } else { + for (auto &chunk : signal.chunks()) + if (chunk.wire != nullptr && chunk.wire->driverCell_ == nullptr) + module->buf_norm_wire_queue.insert(chunk.wire); + } + + if (type == ID($connect)) { + for (auto &[port, sig] : connections_) { + for (auto &chunk : sig.chunks()) { + if (!chunk.wire) continue; + auto it = module->buf_norm_connect_index.find(chunk.wire); + if (it == module->buf_norm_connect_index.end()) continue; + it->second.erase(this); + if (it->second.empty()) + module->buf_norm_connect_index.erase(it); + } + } + conn_it->second = std::move(signal); + for (auto &[port, sig] : connections_) { + for (auto &chunk : sig.chunks()) { + if (!chunk.wire) continue; + module->buf_norm_connect_index[chunk.wire].insert(this); + } + } + return true; + } + + return false; +} + +// Called after the cell's module pointer has been set to push all existing port connections +// into the signorm and bufnorm indices. Assumes signals are already in normalized form. +void RTLIL::Cell::initIndex() +{ + log_assert(module != nullptr); + if (ignored_cell(type)) + return; + + if (module->sig_norm_index != nullptr) { + for (auto &[portname, signal] : connections_) { + bool is_input = port_dir(portname) == RTLIL::PD_INPUT; + signorm_index_add(portname, signal, is_input); + } + } + + if (module->design && module->design->flagBufferedNormalized) { + for (auto &[portname, signal] : connections_) { + auto dir = port_dir(portname); + if ((dir == RTLIL::PD_OUTPUT || dir == RTLIL::PD_INOUT) && signal.is_wire()) { + Wire *w = signal.as_wire(); + if (w->driverCell_ == nullptr && + (w->port_input && !w->port_output) == (type == ID($input_port))) { + w->driverCell_ = this; + w->driverPort_ = portname; + continue; + } + } + if (dir == RTLIL::PD_OUTPUT || dir == RTLIL::PD_INOUT) { + module->buf_norm_cell_queue.insert(this); + module->buf_norm_cell_port_queue.emplace(this, portname); + } else { + for (auto &chunk : signal.chunks()) + if (chunk.wire != nullptr && chunk.wire->driverCell_ == nullptr) + module->buf_norm_wire_queue.insert(chunk.wire); + } + } + } +} + void RTLIL::Cell::setPort(RTLIL::IdString portname, RTLIL::SigSpec signal) { - bool is_input_port = false; + bool is_input = false; if (module && module->sig_norm_index != nullptr && !ignored_cell(type)) { module->sig_norm_index->sigmap.apply(signal); auto dir = port_dir(portname); if (dir == RTLIL::PD_INPUT) { - is_input_port = true; + is_input = true; } else { Wire *wire = nullptr; if (signal.is_wire() && (wire = signal.as_wire())->driverCell_ != nullptr) @@ -1128,111 +1274,17 @@ void RTLIL::Cell::setPort(RTLIL::IdString portname, RTLIL::SigSpec signal) } if (module && module->sig_norm_index != nullptr && !ignored_cell(type)) { - module->sig_norm_index->dirty.insert(this); - if (!r.second) { - if (is_input_port) { - auto &fanout = module->sig_norm_index->fanout; - int i = 0; - for (auto bit : conn_it->second) { - if (bit.is_wire()) { - auto found = fanout.find(bit); - log_assert(found != fanout.end()); - int erased = found->second.erase(PortBit(this, portname, i)); - log_assert(erased); - if (found->second.empty()) - fanout.erase(found); - } - i++; - } - } else { - Wire *w = conn_it->second.as_wire(); - log_assert(w->driverCell_ == this); - log_assert(w->driverPort_ == portname); - w->driverCell_ = nullptr; - w->driverPort_ = IdString(); - } - } - - if (is_input_port) { - auto &fanout = module->sig_norm_index->fanout; - int i = 0; - for (auto bit : signal) { - if (bit.is_wire()) - fanout[bit].insert(PortBit(this, portname, i)); - i++; - } - } else if (GetSize(signal)) { - Wire *w = signal.as_wire(); - log_assert(w->driverCell_ == nullptr); - log_assert(w->driverPort_.empty()); - w->driverCell_ = this; - w->driverPort_ = portname; - } + if (!r.second) + signorm_index_remove(portname, conn_it->second, is_input); + signorm_index_add(portname, signal, is_input); } - if (module && module->design && module->design->flagBufferedNormalized) - { - // We eagerly clear a driver that got disconnected by changing this port connection - if (conn_it->second.is_wire()) { - Wire *w = conn_it->second.as_wire(); - if (w->driverCell_ == this && w->driverPort_ == portname) { - w->driverCell_ = nullptr; - w->driverPort_ = IdString(); - module->buf_norm_wire_queue.insert(w); - } - } - - auto dir = port_dir(portname); - // This is a fast path that handles connecting a full driverless wire to an output port, - // everything else is goes through the bufnorm queues and is handled during the next - // bufNormalize call - if ((dir == RTLIL::PD_OUTPUT || dir == RTLIL::PD_INOUT) && signal.is_wire()) { - Wire *w = signal.as_wire(); - if (w->driverCell_ == nullptr && ( - (w->port_input && !w->port_output) == (type == ID($input_port)))) { - w->driverCell_ = this; - w->driverPort_ = portname; - - conn_it->second = std::move(signal); - return; - } - } - - if (dir == RTLIL::PD_OUTPUT || dir == RTLIL::PD_INOUT) { - module->buf_norm_cell_queue.insert(this); - module->buf_norm_cell_port_queue.emplace(this, portname); - } else { - for (auto &chunk : signal.chunks()) - if (chunk.wire != nullptr && chunk.wire->driverCell_ == nullptr) - module->buf_norm_wire_queue.insert(chunk.wire); - } - - if (type == ID($connect)) { - for (auto &[port, sig] : connections_) { - for (auto &chunk : sig.chunks()) { - if (!chunk.wire) - continue; - auto it = module->buf_norm_connect_index.find(chunk.wire); - if (it == module->buf_norm_connect_index.end()) - continue; - it->second.erase(this); - if (it->second.empty()) - module->buf_norm_connect_index.erase(it); - } - } - conn_it->second = std::move(signal); - for (auto &[port, sig] : connections_) { - for (auto &chunk : sig.chunks()) { - if (!chunk.wire) - continue; - module->buf_norm_connect_index[chunk.wire].insert(this); - } - } + if (module && module->design && module->design->flagBufferedNormalized) { + if (bufnorm_handle_setPort(portname, signal, conn_it)) return; - } } - conn_it->second = std::move(signal); + conn_it->second = std::move(signal); } void RTLIL::Design::add(RTLIL::Module *module) diff --git a/kernel/unstable/patch.cc b/kernel/unstable/patch.cc index 0ee521fd0..5c8e066a3 100644 --- a/kernel/unstable/patch.cc +++ b/kernel/unstable/patch.cc @@ -118,24 +118,17 @@ void Patch::patch(Cell* old_cell, Cell* new_cell) { auto dir = raw->port_dir(port_name); log_assert(dir != PD_UNKNOWN); if (dir == PD_OUTPUT || dir == PD_INOUT) { - SigSpec sig_to_fix = sig; if (raw == new_cell) { // RAUW - // TODO optimized implementation for signorm fanout transfer that avoids expensive(?) setPort? auto yoink = old_cell->getPort(port_name); log(">>>> RAUW %s to %s\n", port_name, log_signal(yoink)); new_cell->setPort(port_name, yoink); old_cell->setPort(port_name, mod->addWire(NEW_ID, yoink.size())); - sig_to_fix = yoink; - } - if (sig_to_fix.size()) { - auto* wire = sig_to_fix.as_wire(); - wire->driverCell_ = raw; - wire->driverPort_ = port_name; } } } raw->module = mod; + raw->initIndex(); raw->fixup_parameters(); } log_module(mod, "");