yosys/kernel/rtlil_bufnorm.cc

680 lines
22 KiB
C++

/*
* 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