diff --git a/Makefile b/Makefile index ef63f23ad..3e0849c09 100644 --- a/Makefile +++ b/Makefile @@ -644,6 +644,7 @@ $(eval $(call add_include_file,frontends/blif/blifparse.h)) $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h)) OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o kernel/io.o kernel/gzip.o +OBJS += kernel/rtlil_bufnorm.o OBJS += kernel/log_help.o ifeq ($(ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS),1) OBJS += kernel/log_compat.o diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 3858a3372..4f34d6f7b 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -2844,7 +2844,13 @@ void RTLIL::Module::remove(RTLIL::Cell *cell) log_assert(cells_.count(cell->name) != 0); log_assert(refcount_cells_ == 0); cells_.erase(cell->name); - delete cell; + if (design && design->flagBufferedNormalized && buf_norm_cell_queue.count(cell)) { + cell->type.clear(); + cell->name.clear(); + pending_deleted_cells.insert(cell); + } else { + delete cell; + } } void RTLIL::Module::remove(RTLIL::Process *process) @@ -3019,6 +3025,14 @@ void RTLIL::Module::fixup_ports() std::sort(all_ports.begin(), all_ports.end(), fixup_ports_compare); + if (design && design->flagBufferedNormalized) { + for (auto &w : wires_) + if (w.second->driverCell_ && w.second->driverCell_->type == ID($input_port)) + buf_norm_wire_queue.insert(w.second); + + buf_norm_wire_queue.insert(all_ports.begin(), all_ports.end()); + } + ports.clear(); for (size_t i = 0; i < all_ports.size(); i++) { ports.push_back(all_ports[i]->name); @@ -4163,188 +4177,7 @@ bool RTLIL::Cell::hasPort(const RTLIL::IdString& portname) const return connections_.count(portname) != 0; } -void RTLIL::Cell::unsetPort(const RTLIL::IdString& portname) -{ - RTLIL::SigSpec signal; - auto conn_it = connections_.find(portname); - - if (conn_it != connections_.end()) - { - for (auto mon : module->monitors) - mon->notify_connect(this, conn_it->first, conn_it->second, signal); - - if (module->design) - for (auto mon : module->design->monitors) - mon->notify_connect(this, conn_it->first, conn_it->second, signal); - - if (yosys_xtrace) { - log("#X# Unconnect %s.%s.%s\n", log_id(this->module), log_id(this), log_id(portname)); - log_backtrace("-X- ", yosys_xtrace-1); - } - - connections_.erase(conn_it); - } -} - -void RTLIL::Design::bufNormalize(bool enable) -{ - if (!enable) - { - if (!flagBufferedNormalized) - return; - - for (auto module : modules()) { - module->bufNormQueue.clear(); - for (auto wire : module->wires()) { - wire->driverCell_ = nullptr; - wire->driverPort_ = IdString(); - } - } - - flagBufferedNormalized = false; - return; - } - - if (!flagBufferedNormalized) - { - for (auto module : modules()) - { - for (auto cell : module->cells()) - for (auto &conn : cell->connections()) { - if (!cell->output(conn.first) || GetSize(conn.second) == 0) - continue; - if (conn.second.is_wire()) { - Wire *wire = conn.second.as_wire(); - log_assert(wire->driverCell_ == nullptr); - wire->driverCell_ = cell; - wire->driverPort_ = conn.first; - } else { - pair key(cell, conn.first); - module->bufNormQueue.insert(key); - } - } - } - - flagBufferedNormalized = true; - } - - for (auto module : modules()) - module->bufNormalize(); -} - -void RTLIL::Module::bufNormalize() -{ - if (!design->flagBufferedNormalized) - return; - - while (GetSize(bufNormQueue) || !connections_.empty()) - { - pool> queue; - bufNormQueue.swap(queue); - - pool outWires; - for (auto &conn : connections()) - for (auto &chunk : conn.first.chunks()) - if (chunk.wire) outWires.insert(chunk.wire); - - SigMap sigmap(this); - new_connections({}); - - for (auto &key : queue) - { - Cell *cell = key.first; - const IdString &portname = key.second; - const SigSpec &sig = cell->getPort(portname); - if (GetSize(sig) == 0) continue; - - if (sig.is_wire()) { - Wire *wire = sig.as_wire(); - if (wire->driverCell_) { - log_error("Conflict between %s %s in module %s\n", - log_id(cell), log_id(wire->driverCell_), log_id(this)); - } - log_assert(wire->driverCell_ == nullptr); - wire->driverCell_ = cell; - wire->driverPort_ = portname; - continue; - } - - for (auto &chunk : sig.chunks()) - if (chunk.wire) outWires.insert(chunk.wire); - - Wire *wire = addWire(NEW_ID, GetSize(sig)); - sigmap.add(sig, wire); - cell->setPort(portname, wire); - - // FIXME: Move init attributes from old 'sig' to new 'wire' - } - - for (auto wire : outWires) - { - SigSpec outsig = wire, insig = sigmap(wire); - for (int i = 0; i < GetSize(wire); i++) - if (insig[i] == outsig[i]) - insig[i] = State::Sx; - addBuf(NEW_ID, insig, outsig); - } - } -} - -void RTLIL::Cell::setPort(const RTLIL::IdString& portname, RTLIL::SigSpec signal) -{ - auto r = connections_.insert(portname); - auto conn_it = r.first; - if (!r.second && conn_it->second == signal) - return; - - for (auto mon : module->monitors) - mon->notify_connect(this, conn_it->first, conn_it->second, signal); - - if (module->design) - for (auto mon : module->design->monitors) - mon->notify_connect(this, conn_it->first, conn_it->second, signal); - - if (yosys_xtrace) { - log("#X# Connect %s.%s.%s = %s (%d)\n", log_id(this->module), log_id(this), log_id(portname), log_signal(signal), GetSize(signal)); - log_backtrace("-X- ", yosys_xtrace-1); - } - - while (module->design && module->design->flagBufferedNormalized && output(portname)) - { - pair key(this, portname); - - 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(); - } - } - - if (GetSize(signal) == 0) { - module->bufNormQueue.erase(key); - break; - } - - if (!signal.is_wire()) { - module->bufNormQueue.insert(key); - break; - } - - Wire *w = signal.as_wire(); - if (w->driverCell_ != nullptr) { - pair other_key(w->driverCell_, w->driverPort_); - module->bufNormQueue.insert(other_key); - } - w->driverCell_ = this; - w->driverPort_ = portname; - - module->bufNormQueue.erase(key); - break; - } - - conn_it->second = std::move(signal); -} +// bufnorm const RTLIL::SigSpec &RTLIL::Cell::getPort(const RTLIL::IdString& portname) const { @@ -5638,6 +5471,18 @@ bool RTLIL::SigSpec::has_const() const return false; } +bool RTLIL::SigSpec::has_const(State state) const +{ + cover("kernel.rtlil.sigspec.has_const"); + + pack(); + for (auto it = chunks_.begin(); it != chunks_.end(); it++) + if (it->width > 0 && it->wire == NULL && std::find(it->data.begin(), it->data.end(), state) != it->data.end()) + return true; + return false; +} + + bool RTLIL::SigSpec::has_marked_bits() const { cover("kernel.rtlil.sigspec.has_marked_bits"); diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 15154eb64..096d1dfcf 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -1337,6 +1337,7 @@ public: bool is_fully_def() const; bool is_fully_undef() const; bool has_const() const; + bool has_const(State state) const; bool has_marked_bits() const; bool is_onehot(int *pos = nullptr) const; @@ -1728,7 +1729,11 @@ public: std::vector ports; void fixup_ports(); - pool> bufNormQueue; + pool buf_norm_cell_queue; + pool> buf_norm_cell_port_queue; + pool buf_norm_wire_queue; + pool pending_deleted_cells; + dict> buf_norm_connect_index; void bufNormalize(); template void rewrite_sigspecs(T &functor); diff --git a/kernel/rtlil_bufnorm.cc b/kernel/rtlil_bufnorm.cc new file mode 100644 index 000000000..6d619d9e6 --- /dev/null +++ b/kernel/rtlil_bufnorm.cc @@ -0,0 +1,679 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" +#include "kernel/modtools.h" + +#include +#include +#include + +YOSYS_NAMESPACE_BEGIN + + +void RTLIL::Design::bufNormalize(bool enable) +{ + if (!enable) + { + if (!flagBufferedNormalized) + return; + + for (auto module : modules()) { + module->buf_norm_cell_queue.clear(); + module->buf_norm_wire_queue.clear(); + module->buf_norm_cell_port_queue.clear(); + for (auto wire : module->wires()) { + wire->driverCell_ = nullptr; + wire->driverPort_ = IdString(); + } + } + + flagBufferedNormalized = false; + return; + } + + if (!flagBufferedNormalized) + { + for (auto module : modules()) + { + // When entering buf normalized mode, we need the first module-level bufNormalize + // call to know about all drivers, about all module ports (whether represented by + // a cell or not) and about all used but undriven wires (whether represented by a + // cell or not). We ensure this by enqueing all cell output ports and all wires. + + for (auto cell : module->cells()) + for (auto &conn : cell->connections()) { + if (GetSize(conn.second) == 0 || (cell->port_dir(conn.first) != RTLIL::PD_OUTPUT && cell->port_dir(conn.first) != RTLIL::PD_INOUT)) + continue; + module->buf_norm_cell_queue.insert(cell); + module->buf_norm_cell_port_queue.emplace(cell, conn.first); + } + for (auto wire : module->wires()) + module->buf_norm_wire_queue.insert(wire); + + } + + flagBufferedNormalized = true; + } + + for (auto module : modules()) + module->bufNormalize(); +} + +struct bit_drive_data_t { + int drivers = 0; + int inout = 0; + int users = 0; +}; + +typedef ModWalker::PortBit PortBit; + +void RTLIL::Module::bufNormalize() +{ + // Since this is kernel code, we only log with yosys_xtrace set to not get + // in the way when using `debug` to debug specific passes.q +#define xlog(...) do { if (yosys_xtrace) log("#X [bufnorm] " __VA_ARGS__); } while (0) + + if (!design->flagBufferedNormalized) + return; + + if (!buf_norm_cell_queue.empty() || !buf_norm_wire_queue.empty() || !connections_.empty()) + { + // Ensure that every enqueued input port is represented by a cell + for (auto wire : buf_norm_wire_queue) { + if (wire->port_input && !wire->port_output) { + if (wire->driverCell_ != nullptr && wire->driverCell_->type != ID($input_port)) { + wire->driverCell_ = nullptr; + wire->driverPort_.clear(); + } + if (wire->driverCell_ == nullptr) { + Cell *input_port_cell = addCell(NEW_ID, ID($input_port)); + input_port_cell->setParam(ID::WIDTH, GetSize(wire)); + input_port_cell->setPort(ID::Y, wire); // this hits the fast path that doesn't mutate the queues + } + } + } + + // Next we will temporarily undo buf normalization locally for + // everything enqueued. This means we will turn $buf and $connect back + // into connections. When doing this we also need to enqueue the other + // end of $buf and $connect cells, so we use a queue and do this until + // reaching a fixed point. + + // While doing this, we will also discover all drivers fully connected + // to enqueued wires. We keep track of which wires are driven by a + // unique and full cell ports (in which case the wire can stay + // connected to the port) and which cell ports will need to be + // reconnected to a fresh intermediate wire to re-normalize the module. + + idict wire_queue_entries; // Ordered queue of wires to process + int wire_queue_pos = 0; // Index up to which we processed the wires + + // Wires with their unique driving cell port. If we know a wire is + // driven by multiple (potential) drivers, this is indicated by a + // nullptr as cell. + dict> direct_driven_wires; + + // Set of non-unique or driving cell ports for each processed wire. + dict>> direct_driven_wires_conflicts; + + // Set of cell ports that need a fresh intermediate wire. + pool> pending_ports; + + // This helper will be called for every output/inout cell port that is + // already enqueued or becomes reachable when denormalizing $buf or + // $connect cells. + auto enqueue_cell_port = [&](Cell *cell, IdString port) { + xlog("processing cell port %s.%s\n", log_id(cell), log_id(port)); + + // An empty cell type means the cell got removed + if (cell->type.empty()) + return; + + + SigSpec const &sig = cell->getPort(port); + if (cell->type == ID($input_port)) { + // If an `$input_port` cell isn't fully connected to a full + // input port wire, we remove it since the wires are still the + // canonical source of module ports and the `$input_port` cells + // are just helpers to simplfiy the bufnorm invariant. + log_assert(port == ID::Y); + if (!sig.is_wire()) { + buf_norm_cell_queue.insert(cell); + remove(cell); + return; + } + + Wire *w = sig.as_wire(); + if (!w->port_input || w->port_output) { + buf_norm_cell_queue.insert(cell); + remove(cell); + return; + } + w->driverCell_ = cell; + w->driverPort_ = ID::Y; + } else if (cell->type == ID($buf) && cell->attributes.empty() && !cell->name.isPublic()) { + // For a plain `$buf` cell, we enqueue all wires on its input + // side, bypass it using module level connections (skipping 'z + // bits) and then remove the cell. Eventually the module level + // connections will turn back into `$buf` and `$connect` cells, + // but since we also need to handle externally added module + // level connections, turning everything into connections first + // simplifies the logic for doing so. + + // TODO: We could defer removing the $buf cells here, and + // re-use them in case we would create a new identical cell + // later. + log_assert(port == ID::Y); + SigSpec sig_a = cell->getPort(ID::A); + SigSpec sig_y = sig; + + for (auto const &s : {sig_a, sig}) + for (auto const &chunk : s.chunks()) + if (chunk.wire) + wire_queue_entries(chunk.wire); + + if (sig_a.has_const(State::Sz)) { + SigSpec new_a; + SigSpec new_y; + for (int i = 0; i < GetSize(sig_a); ++i) { + SigBit b = sig_a[i]; + if (b == State::Sz) + continue; + new_a.append(b); + new_y.append(sig_y[i]); + } + sig_a = std::move(new_a); + sig_y = std::move(new_y); + } + + if (!sig_y.empty()) + connect(sig_y, sig_a); + buf_norm_cell_queue.insert(cell); + remove(cell); + return; + } + + // Make sure all wires of the cell port are enqueued, ensuring we + // detect other connected drivers (output and inout). + for (auto const &chunk : sig.chunks()) + if (chunk.wire) + wire_queue_entries(chunk.wire); + + if (sig.is_wire()) { + // If the full cell port is connected to a full wire, we might be + // able to keep that connection if this is a unique output port driving that wire + Wire *w = sig.as_wire(); + + // We try to store the current port as unique driver, if this + // succeeds we're done with the port. + auto [found, inserted] = direct_driven_wires.emplace(w, {cell, port}); + if (inserted || (found->second.first == cell && found->second.second == port)) + return; + + // When this failed, we store this port as a conflict. If we + // had already stored a candidate for a unique driver, we also + // move it to the conflicts, leaving a nullptr marker. + + auto &conflicts = direct_driven_wires_conflicts[w]; + if (Cell *other_cell = found->second.first) { + if (other_cell->type == ID($input_port)) { + // Multiple input port cells + log_assert(cell->type != ID($input_port)); + } else { + pending_ports.insert(found->second); + conflicts.emplace(found->second); + found->second = {nullptr, {}}; + } + } + if (cell->type == ID($input_port)) { + found->second = {cell, port}; + } else { + conflicts.emplace(cell, port); + } + } + + // Adds this port to the ports that need a fresh intermediate wire. + // For full wires uniquely driven by a full output port, this isn't + // reached due to the `return` above. + pending_ports.emplace(cell, port); + }; + + // We process all explicitly enqueued cell ports (clearing the module level queue). + for (auto const &[cell, port_name] : buf_norm_cell_port_queue) + enqueue_cell_port(cell, port_name); + buf_norm_cell_port_queue.clear(); + + // And enqueue all wires for `$buf`/`$connect` processing (clearing the module level queue). + for (auto wire : buf_norm_wire_queue) + wire_queue_entries(wire); + buf_norm_wire_queue.clear(); + + // We also enqueue all wires that saw newly added module level connections. + for (auto &[a, b] : connections_) + for (auto &sig : {a, b}) + for (auto const &chunk : sig.chunks()) + if (chunk.wire) + wire_queue_entries(chunk.wire); + + // We then process all wires by processing known driving cell ports + // (previously buf normalized) and following all `$connect` cells (that + // have a dedicated module level index while the design is in buf + // normalized mode). + while (wire_queue_pos < GetSize(wire_queue_entries)) { + auto wire = wire_queue_entries[wire_queue_pos++]; + xlog("processing wire %s\n", log_id(wire)); + + if (wire->driverCell_) { + Cell *cell = wire->driverCell_; + IdString port = wire->driverPort_; + enqueue_cell_port(cell, port); + } + + while (true) { + auto found = buf_norm_connect_index.find(wire); + if (found == buf_norm_connect_index.end()) + break; + while (!found->second.empty()) { + Cell *connect_cell = *found->second.begin(); + log_assert(connect_cell->type == ID($connect)); + SigSpec const &sig_a = connect_cell->getPort(ID::A); + SigSpec const &sig_b = connect_cell->getPort(ID::B); + xlog("found $connect cell %s: %s <-> %s\n", log_id(connect_cell), log_signal(sig_a), log_signal(sig_b)); + for (auto &side : {sig_a, sig_b}) + for (auto chunk : side.chunks()) + if (chunk.wire) + wire_queue_entries(chunk.wire); + connect(sig_a, sig_b); + buf_norm_cell_queue.insert(connect_cell); + remove(connect_cell); + } + } + } + + // At this point we know all cell ports and wires that need to be + // re-normalized and know their connectivity is represented by module + // level connections. + + // As a first step for re-normalization we add all require intermediate + // wires for cell output and inout ports. + for (auto &[cell, port] : pending_ports) { + SigSpec const &sig = cell->getPort(port); + Wire *w = addWire(NEW_ID, GetSize(sig)); + + // We update the module level connections, `direct_driven_wires` + // and `direct_driven_wires_conflicts` in such a way that they + // correspond to what you would get if the intermediate wires had + // been in place from the beginning. + connect(sig, w); + auto port_dir = cell->port_dir(port); + if (port_dir == RTLIL::PD_INOUT || port_dir == RTLIL::PD_UNKNOWN) { + direct_driven_wires.emplace(w, {nullptr, {}}); + direct_driven_wires_conflicts[w].emplace(cell, port); + } else { + direct_driven_wires.emplace(w, {cell, port}); + } + + cell->setPort(port, w); + wire_queue_entries(w); + } + + // At this point we're done with creating wires and know which ones are + // fully driven by full output ports of existing cells. + + // First we clear the bufnorm data for all processed wires, all of + // these will be reassigned later, but we use `driverCell_ == nullptr` + // to keep track of the wires that we still have to update. + for (auto wire : wire_queue_entries) { + wire->driverCell_ = nullptr; + wire->driverPort_.clear(); + } + + // For the unique output cell ports fully connected to a full wire, we + // can update the bufnorm data right away. For all other wires we will + // have to create new `$buf` cells. + for (auto const &[wire, cellport] : direct_driven_wires) { + wire->driverCell_ = cellport.first; + wire->driverPort_ = cellport.second; + } + + + // To create fresh `$buf` cells for all remaining wires, we need to + // process the module level connectivity to figure out what the input + // of those `$buf` cells should be and to figure out whether we need + // any `$connect` cells to represent bidirectional inout connections + // (or driver conflicts). + + if (yosys_xtrace) + for (auto const &[lhs, rhs] : connections_) + xlog("connection %s <-> %s\n", log_signal(lhs), log_signal(rhs)); + + + // We transfer the connectivity into a sigmap and then clear the module + // level connections. This forgets about the structure of module level + // connections, but bufnorm only guarantees that the connectivity as + // maintained by a `SigMap` is preserved. + SigMap sigmap(this); + new_connections({}); + + pool conflicted; + pool driven; + + // We iterate over all direct driven wires and try to make that wire's + // sigbits the representative sigbit for the net. We do a second pass + // to detect conflicts to then remove the conflicts from `driven`. + for (bool check : {false, true}) { + for (auto const &[wire, cellport] : direct_driven_wires) { + if (cellport.first == nullptr) + continue; + auto const &[cell, port] = cellport; + + SigSpec z_mask; + if (cell->type == ID($buf)) + z_mask = cell->getPort(ID::A); + + for (int i = 0; i != GetSize(wire); ++i) { + SigBit driver = SigBit(wire, i); + if (!z_mask.empty() && z_mask[i] == State::Sz) + continue; + if (check) { + SigBit repr = sigmap(driver); + if (repr != driver) + conflicted.insert(repr); + else + driven.insert(repr); + } else { + sigmap.database.promote(driver); + } + } + } + } + + // Ensure that module level inout ports are directly driven or + // connected using `$connect` cells and never `$buf`fered. + for (auto wire : wire_queue_entries) { + if (!wire->port_input || !wire->port_output) + continue; + for (int i = 0; i != GetSize(wire); ++i) { + SigBit driver = SigBit(wire, i); + SigBit repr = sigmap(driver); + if (driver != repr) + driven.erase(repr); + } + } + + for (auto &bit : conflicted) + driven.erase(bit); + + // Module level bitwise connections not representable by `$buf` cells + pool> undirected_connections; + + // Starts out empty but is updated with the connectivity realized by freshly added `$buf` cells + SigMap buf_connected; + + // For every enqueued wire, we compute a SigSpec of representative + // drivers. If there are any bits without a unique driver we represent + // that with `Sz`. If there are multiple drivers for a net, they become + // connected via `$connect` cells but every wire of the net has the + // corresponding bit still driven by a buffered `Sz`. + for (auto wire : wire_queue_entries) { + SigSpec wire_drivers; + for (int i = 0; i < GetSize(wire); ++i) { + SigBit bit(wire, i); + SigBit mapped = sigmap(bit); + xlog("bit %s -> mapped %s\n", log_signal(bit), log_signal(mapped)); + + + buf_connected.apply(bit); + buf_connected.add(bit, mapped); + buf_connected.database.promote(mapped); + + if (wire->driverCell_ == nullptr) { + if (!mapped.is_wire() || driven.count(mapped)) { + wire_drivers.append(mapped); + continue; + } else { + wire_drivers.append(State::Sz); + } + } + + if (bit < mapped) + undirected_connections.emplace(bit, mapped); + else if (mapped < bit) + undirected_connections.emplace(mapped, bit); + } + + if (wire->driverCell_ == nullptr) { + xlog("wire %s drivers %s\n", log_id(wire), log_signal(wire_drivers)); + addBuf(NEW_ID, wire_drivers, wire); + } + } + + // Finally we group the bitwise connections to emit word-level $connect cells + + static auto sort_key = [](std::pair const &p) { + int first_offset = p.first.is_wire() ? p.first.offset : 0; + int second_offset = p.second.is_wire() ? p.second.offset : 0; + return std::make_tuple(p.first.wire, p.second.wire, first_offset - second_offset, p); + }; + + undirected_connections.sort([](std::pair const &p, std::pair const &q) { + return sort_key(p) < sort_key(q); + }); + + SigSpec tmp_a, tmp_b; + + for (auto &[bit_a, bit_b] : undirected_connections) { + tmp_a.append(bit_a); + tmp_b.append(bit_b); + } + + xlog("LHS: %s\n", log_signal(tmp_a)); + xlog("RHS: %s\n", log_signal(tmp_b)); + + + SigSpec sig_a, sig_b; + SigBit next_a, next_b; + + auto emit_connect_cell = [&]() { + if (sig_a.empty()) + return; + xlog("connect %s <-> %s\n", log_signal(sig_a), log_signal(sig_b)); + Cell *connect_cell = addCell(NEW_ID, ID($connect)); + connect_cell->setParam(ID::WIDTH, GetSize(sig_a)); + connect_cell->setPort(ID::A, sig_a); + connect_cell->setPort(ID::B, sig_b); + sig_a = SigSpec(); + sig_b = SigSpec(); + }; + + for (auto &[bit_a, bit_b] : undirected_connections) { + if (bit_a == bit_b) + continue; + if (bit_a != next_a || bit_b != next_b) + emit_connect_cell(); + + sig_a.append(bit_a); + sig_b.append(bit_b); + next_a = bit_a; + next_b = bit_b; + if (next_a.is_wire()) + next_a.offset++; + if (next_b.is_wire()) + next_b.offset++; + + } + emit_connect_cell(); + + buf_norm_cell_queue.clear(); + + log_assert(buf_norm_cell_port_queue.empty()); + log_assert(buf_norm_wire_queue.empty()); + log_assert(connections_.empty()); + } + + for (auto cell : pending_deleted_cells) { + delete cell; + } + pending_deleted_cells.clear(); +} + +void RTLIL::Cell::unsetPort(const RTLIL::IdString& portname) +{ + RTLIL::SigSpec signal; + auto conn_it = connections_.find(portname); + + if (conn_it != connections_.end()) + { + for (auto mon : module->monitors) + mon->notify_connect(this, conn_it->first, conn_it->second, signal); + + if (module->design) + for (auto mon : module->design->monitors) + mon->notify_connect(this, conn_it->first, conn_it->second, signal); + + if (yosys_xtrace) { + log("#X# Unconnect %s.%s.%s\n", log_id(this->module), log_id(this), log_id(portname)); + log_backtrace("-X- ", yosys_xtrace-1); + } + + if (module->design && module->design->flagBufferedNormalized) { + 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); + } + } + + 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); + } + } + connections_.erase(conn_it); + for (auto &[port, sig] : connections_) { + for (auto &chunk : sig.chunks()) { + if (!chunk.wire) + continue; + module->buf_norm_connect_index[chunk.wire].insert(this); + } + } + return; + } + } + + connections_.erase(conn_it); + } +} + +void RTLIL::Cell::setPort(const RTLIL::IdString& portname, RTLIL::SigSpec signal) +{ + auto r = connections_.insert(portname); + auto conn_it = r.first; + if (!r.second && conn_it->second == signal) + return; + + for (auto mon : module->monitors) + mon->notify_connect(this, conn_it->first, conn_it->second, signal); + + if (module->design) + for (auto mon : module->design->monitors) + mon->notify_connect(this, conn_it->first, conn_it->second, signal); + + if (yosys_xtrace) { + log("#X# Connect %s.%s.%s = %s (%d)\n", log_id(this->module), log_id(this), log_id(portname), log_signal(signal), GetSize(signal)); + log_backtrace("-X- ", yosys_xtrace-1); + } + + if (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->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); + } + } + return; + } + } + conn_it->second = std::move(signal); + +} + +YOSYS_NAMESPACE_END diff --git a/tests/various/aiger2.ys b/tests/various/aiger2.ys index d2b024d2f..6f6958141 100644 --- a/tests/various/aiger2.ys +++ b/tests/various/aiger2.ys @@ -157,6 +157,7 @@ splitnets -ports copy test gold flatten gold techmap submodule1 +opt_clean select test write_aiger2 -flatten aiger2_ops.aig select -clear @@ -216,6 +217,7 @@ prep -top top techmap t:$add splitnets -ports top +opt_clean write_aiger2 -flatten aiger2_flatten.aig flatten rename top gold