3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-09-18 15:41:29 +00:00

kernel: Rewrite bufNormalize

This is a complete rewrite of the RTLIL-kernel-side bufnorm code. This
is done to support inout ports and undirected connections as well as to
allow removal of cells while in bufnorm mode.

This doesn't yet update the (experimental) `bufnorm` pass, so to
manually test the new kernel functionality, it is important to only use
`bufnorm -update` and `bufnorm -reset` which rely entirely on the kernel
functionality. Other modes of the `bufnorm` pass may still fail in the
presence of inout ports or undirected connections.
This commit is contained in:
Jannis Harder 2025-09-03 15:34:51 +02:00
parent 1251e92e3a
commit d88d6fce87
5 changed files with 716 additions and 184 deletions

View file

@ -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

View file

@ -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<RTLIL::Cell*, RTLIL::IdString> 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<pair<RTLIL::Cell*, RTLIL::IdString>> queue;
bufNormQueue.swap(queue);
pool<Wire*> 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<RTLIL::Cell*, RTLIL::IdString> 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<RTLIL::Cell*, RTLIL::IdString> 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");

View file

@ -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<RTLIL::IdString> ports;
void fixup_ports();
pool<pair<RTLIL::Cell*, RTLIL::IdString>> bufNormQueue;
pool<RTLIL::Cell *> buf_norm_cell_queue;
pool<pair<RTLIL::Cell *, RTLIL::IdString>> buf_norm_cell_port_queue;
pool<RTLIL::Wire *> buf_norm_wire_queue;
pool<RTLIL::Cell *> pending_deleted_cells;
dict<RTLIL::Wire *, pool<RTLIL::Cell *>> buf_norm_connect_index;
void bufNormalize();
template<typename T> void rewrite_sigspecs(T &functor);

679
kernel/rtlil_bufnorm.cc Normal file
View file

@ -0,0 +1,679 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* 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 <string.h>
#include <algorithm>
#include <optional>
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 *> 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<Wire *, std::pair<Cell *, IdString>> direct_driven_wires;
// Set of non-unique or driving cell ports for each processed wire.
dict<Wire *, pool<std::pair<Cell *, IdString>>> direct_driven_wires_conflicts;
// Set of cell ports that need a fresh intermediate wire.
pool<std::pair<Cell *, IdString>> 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<SigBit> conflicted;
pool<SigBit> 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<pair<SigBit, SigBit>> 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<SigBit, SigBit> 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<SigBit, SigBit> const &p, std::pair<SigBit, SigBit> 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

View file

@ -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