mirror of
https://github.com/YosysHQ/yosys
synced 2026-07-15 03:35:40 +00:00
patch: split into single-output patch + multi-output patch_ports; drop input-cone gc
This commit is contained in:
parent
61b0dfd3bf
commit
7656347b44
7 changed files with 220 additions and 256 deletions
|
|
@ -54,7 +54,6 @@ struct RTLIL::SigNormIndex
|
|||
module->fixup_ports();
|
||||
setup_module_inputs();
|
||||
setup_driven_wires();
|
||||
setup_module_outputs_and_publics();
|
||||
setup_fanout();
|
||||
}
|
||||
|
||||
|
|
@ -111,72 +110,6 @@ struct RTLIL::SigNormIndex
|
|||
}
|
||||
}
|
||||
|
||||
// Creates $output_port cells consuming each pure-output module port wire
|
||||
// and $public cells consuming each public-named wire that isn't already
|
||||
// covered by an $input_port or $output_port. These act as fanout sentinels
|
||||
// so local GC (e.g. in Patch) won't remove driver cells whose only purpose
|
||||
// is to feed a port or a user-visible wire.
|
||||
void setup_module_outputs_and_publics() {
|
||||
std::vector<Cell *> cells_to_remove;
|
||||
dict<Wire *, Cell *> output_port_cells;
|
||||
dict<Wire *, Cell *> public_cells;
|
||||
|
||||
for (auto cell : module->cells()) {
|
||||
if (cell->type != ID($output_port) && cell->type != ID($public))
|
||||
continue;
|
||||
|
||||
auto const &sig_a = cell->getPort(ID::A);
|
||||
Wire *wire = nullptr;
|
||||
if (!sig_a.is_wire()) {
|
||||
cells_to_remove.push_back(cell);
|
||||
continue;
|
||||
}
|
||||
wire = sig_a.as_wire();
|
||||
|
||||
if (cell->type == ID($output_port)) {
|
||||
if (wire->port_output && !wire->port_input && !output_port_cells.count(wire))
|
||||
output_port_cells.emplace(wire, cell);
|
||||
else
|
||||
cells_to_remove.push_back(cell);
|
||||
} else { // $public
|
||||
bool is_pure_input = wire->port_input && !wire->port_output;
|
||||
bool is_pure_output = wire->port_output && !wire->port_input;
|
||||
if (wire->name.isPublic() && !is_pure_input && !is_pure_output && !public_cells.count(wire))
|
||||
public_cells.emplace(wire, cell);
|
||||
else
|
||||
cells_to_remove.push_back(cell);
|
||||
}
|
||||
}
|
||||
for (auto cell : cells_to_remove)
|
||||
module->remove(cell);
|
||||
|
||||
for (auto portname : module->ports) {
|
||||
Wire *wire = module->wire(portname);
|
||||
if (wire->port_output && !wire->port_input && !output_port_cells.count(wire)) {
|
||||
Cell *cell = module->addCell(NEW_ID, ID($output_port));
|
||||
cell->setParam(ID::WIDTH, GetSize(wire));
|
||||
cell->setPort(ID::A, wire);
|
||||
output_port_cells.emplace(wire, cell);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &it : module->wires_) {
|
||||
Wire *wire = it.second;
|
||||
if (!wire->name.isPublic())
|
||||
continue;
|
||||
bool is_pure_input = wire->port_input && !wire->port_output;
|
||||
bool is_pure_output = wire->port_output && !wire->port_input;
|
||||
if (is_pure_input || is_pure_output)
|
||||
continue;
|
||||
if (public_cells.count(wire))
|
||||
continue;
|
||||
Cell *cell = module->addCell(NEW_ID, ID($public));
|
||||
cell->setParam(ID::WIDTH, GetSize(wire));
|
||||
cell->setPort(ID::A, wire);
|
||||
public_cells.emplace(wire, cell);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_driven_wires() {
|
||||
for (auto cell : module->cells()) {
|
||||
xlog("setup_driven_wires cell %s %s\n", cell->type, cell->name);
|
||||
|
|
@ -421,7 +354,7 @@ void RTLIL::Design::sigNormalize(bool enable)
|
|||
// TODO inefficient?
|
||||
std::vector<Cell*> cells_snapshot = module->cells();
|
||||
for (auto cell : cells_snapshot) {
|
||||
if (cell->type.in(ID($input_port), ID($output_port), ID($public)))
|
||||
if (cell->type == ID($input_port))
|
||||
module->remove(cell);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "kernel/unstable/patch.h"
|
||||
#include "kernel/celltypes.h"
|
||||
#include "kernel/log.h"
|
||||
#include "kernel/newcelltypes.h"
|
||||
#include "kernel/rtlil.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
|
@ -45,56 +46,6 @@ RTLIL::Wire *RTLIL::Patch::addWire(RTLIL::IdString name, const RTLIL::Wire *othe
|
|||
return wire;
|
||||
}
|
||||
|
||||
void Patch::gc(Cell* old_cell, bool track, pool<string>* src_pool) {
|
||||
log_debug("gc %s\n", old_cell->name);
|
||||
if (old_cell->type.in(ID($input_port), ID($output_port), ID($public)))
|
||||
return;
|
||||
pool<Cell*> inputs;
|
||||
for (auto [port_name, sig] : old_cell->connections()) {
|
||||
auto dir = old_cell->port_dir(port_name);
|
||||
// Unknown port direction (e.g. user module instance whose interface
|
||||
// isn't registered): can't decide input vs output, so don't gc it.
|
||||
// TODO: should be log_assert once PD_UNKNOWN is fixed at the source
|
||||
// (see claude-notes.md).
|
||||
if (dir == PD_UNKNOWN)
|
||||
return;
|
||||
// TODO only running GC through whole connections?
|
||||
log_debug("\tport %s\n", port_name);
|
||||
if (sig.size() && sig.is_wire()) {
|
||||
if (dir == PD_OUTPUT || dir == PD_INOUT) {
|
||||
for (auto bit : sig) {
|
||||
// Reject GC if used
|
||||
if (!mod->fanout(bit).empty()) {
|
||||
log_debug("\treject fanout\n");
|
||||
return;
|
||||
} else
|
||||
log_debug("\tok\n");
|
||||
}
|
||||
}
|
||||
if (dir == PD_INPUT || dir == PD_INOUT) {
|
||||
Wire* in_wire = sig.as_wire();
|
||||
log_assert(in_wire);
|
||||
log_debug("\twire %s\n", in_wire->name);
|
||||
if (in_wire->known_driver() && !leaves.count(in_wire))
|
||||
inputs.insert(in_wire->driverCell());
|
||||
}
|
||||
}
|
||||
}
|
||||
log_debug("\tremove %s\n", old_cell->name);
|
||||
// Only track recursively-removed cells. The top-level patched cell is the
|
||||
// caller's current iteration variable and won't be re-encountered.
|
||||
if (track && removed_cells)
|
||||
removed_cells->insert(old_cell);
|
||||
// The cell about to be removed contributes its src so all the cells gc'd
|
||||
// in this patch (top-level + input cone) get merged into the new cells'
|
||||
// src — that's the "transfer src automatically" guarantee.
|
||||
if (src_pool)
|
||||
src_pool->insert(old_cell->get_src_attribute());
|
||||
old_cell->module->remove(old_cell);
|
||||
for (auto input : inputs)
|
||||
gc(input, /*track=*/true, src_pool);
|
||||
}
|
||||
|
||||
Wire* Patch::commit_wire(std::unique_ptr<Wire> wire) {
|
||||
Wire* raw = wire.release();
|
||||
mod->wires_[raw->name] = raw;
|
||||
|
|
@ -110,63 +61,142 @@ Cell* Patch::commit_cell(std::unique_ptr<Cell> cell) {
|
|||
return raw;
|
||||
}
|
||||
|
||||
void Patch::patch(Cell* old_cell, IdString old_port, SigSpec new_sig) {
|
||||
patch(old_cell, {{old_port, new_sig}}, nullptr);
|
||||
std::vector<Cell*> Patch::commit_staged() {
|
||||
std::vector<Cell*> committed;
|
||||
committed.reserve(cells_.size());
|
||||
for (auto& cell : cells_) {
|
||||
cell->fixup_parameters();
|
||||
committed.push_back(commit_cell(std::move(cell)));
|
||||
}
|
||||
for (auto& wire : wires_)
|
||||
commit_wire(std::move(wire));
|
||||
cells_.clear();
|
||||
wires_.clear();
|
||||
return committed;
|
||||
}
|
||||
|
||||
void Patch::patch(Cell* old_cell, const std::vector<std::pair<IdString, SigSpec>> &port_replacements) {
|
||||
patch(old_cell, port_replacements, nullptr);
|
||||
}
|
||||
|
||||
void Patch::patch(Cell* old_cell, IdString old_port, SigSpec new_sig, Cell* merge_src_into) {
|
||||
patch(old_cell, {{old_port, new_sig}}, merge_src_into);
|
||||
}
|
||||
|
||||
void Patch::patch(Cell* old_cell, const std::vector<std::pair<IdString, SigSpec>> &port_replacements, Cell* merge_src_into) {
|
||||
std::vector<SigSpec> old_sigs;
|
||||
for (auto &[port, new_sig] : port_replacements) {
|
||||
SigSpec old_sig = old_cell->getPort(port);
|
||||
if (old_sig.size() != new_sig.size())
|
||||
log_error("patch size mismatch on cell %s port %s: old %d (%s) vs new %d (%s)\n",
|
||||
log_id(old_cell->name), log_id(port), old_sig.size(), log_signal(old_sig),
|
||||
new_sig.size(), log_signal(new_sig));
|
||||
log_debug("patching %s %s which is %s with %s:\n", old_cell->name, port, log_signal(old_sig), log_signal(new_sig));
|
||||
old_sigs.push_back(old_sig);
|
||||
namespace {
|
||||
void collect_src(Cell* root, const std::vector<Cell*>& extras,
|
||||
Cell* merge_src_into, pool<std::string>& out)
|
||||
{
|
||||
out.insert(root->get_src_attribute());
|
||||
for (Cell* c : extras)
|
||||
if (c)
|
||||
out.insert(c->get_src_attribute());
|
||||
if (merge_src_into)
|
||||
out.insert(merge_src_into->get_src_attribute());
|
||||
}
|
||||
|
||||
// Record leaves (existing wires consumed as inputs by the new cells) so
|
||||
// gc() stops at them. Detected via bit.wire->module being non-null:
|
||||
// uncommitted wires belong to no module yet.
|
||||
for (auto& cell : cells_) {
|
||||
for (auto& [port_name, sig] : cell->connections()) {
|
||||
auto dir = cell->port_dir(port_name);
|
||||
if (dir == PD_INPUT || dir == PD_INOUT) {
|
||||
for (auto bit : sig) {
|
||||
if (bit.is_wire() && bit.wire->module)
|
||||
leaves.insert(bit.wire);
|
||||
}
|
||||
void apply_src(const pool<std::string>& src_pool,
|
||||
const std::vector<Cell*>& targets, Cell* merge_src_into)
|
||||
{
|
||||
std::string src_str = AttrObject::strpool_attribute_to_str(src_pool);
|
||||
for (Cell* c : targets)
|
||||
c->set_src_attribute(src_str);
|
||||
if (merge_src_into)
|
||||
merge_src_into->set_src_attribute(src_str);
|
||||
}
|
||||
|
||||
// Verifies via newcelltypes that root_cell has exactly one output port
|
||||
// and that it matches `expected_port`.
|
||||
void assert_single_output(Cell* root_cell, IdString expected_port) {
|
||||
int count = 0;
|
||||
IdString found;
|
||||
for (auto &[port, sig] : root_cell->connections()) {
|
||||
if (root_cell->output(port)) {
|
||||
found = port;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count != 1)
|
||||
log_error("Patch: cell %s of type %s has %d output ports, expected exactly one\n",
|
||||
log_id(root_cell->name), log_id(root_cell->type), count);
|
||||
if (found != expected_port)
|
||||
log_error("Patch: cell %s of type %s sole output port %s does not match patched port %s\n",
|
||||
log_id(root_cell->name), log_id(root_cell->type),
|
||||
log_id(found), log_id(expected_port));
|
||||
}
|
||||
}
|
||||
|
||||
// Commit new cells/wires first so new_sig becomes a driven signal in the
|
||||
// signorm index before we merge. Track raw pointers so we can update
|
||||
// their src attribute after gc finishes collecting from removed cells.
|
||||
std::vector<Cell*> committed_new_cells;
|
||||
committed_new_cells.reserve(cells_.size());
|
||||
for (auto& cell: cells_) {
|
||||
cell->fixup_parameters();
|
||||
committed_new_cells.push_back(commit_cell(std::move(cell)));
|
||||
void Patch::patch(Cell* root_cell, IdString old_port, SigSpec new_sig,
|
||||
const std::vector<Cell*>& extras, Cell* merge_src_into)
|
||||
{
|
||||
assert_single_output(root_cell, old_port);
|
||||
|
||||
SigSpec old_sig = root_cell->getPort(old_port);
|
||||
if (old_sig.size() != new_sig.size())
|
||||
log_error("patch size mismatch on cell %s port %s: old %d (%s) vs new %d (%s)\n",
|
||||
log_id(root_cell->name), log_id(old_port),
|
||||
old_sig.size(), log_signal(old_sig),
|
||||
new_sig.size(), log_signal(new_sig));
|
||||
log_debug("patching %s %s which is %s with %s\n",
|
||||
log_id(root_cell->name), log_id(old_port),
|
||||
log_signal(old_sig), log_signal(new_sig));
|
||||
|
||||
pool<std::string> src_pool;
|
||||
collect_src(root_cell, extras, merge_src_into, src_pool);
|
||||
|
||||
std::vector<Cell*> committed = commit_staged();
|
||||
apply_src(src_pool, committed, merge_src_into);
|
||||
|
||||
// Drop root_cell's driver on the output port BEFORE wiring old_sig to
|
||||
// new_sig — otherwise old_sig would briefly have two drivers (root_cell
|
||||
// and new_sig) which signorm flags as conflicting.
|
||||
root_cell->unsetPort(old_port);
|
||||
|
||||
if (map)
|
||||
map->add(old_sig, new_sig);
|
||||
mod->connect_incremental(old_sig, new_sig);
|
||||
|
||||
// Remove root cell only — no input-cone walk.
|
||||
mod->remove(root_cell);
|
||||
}
|
||||
|
||||
void Patch::patch_ports(Cell* root_cell,
|
||||
const std::vector<std::pair<IdString, SigSpec>>& port_replacements,
|
||||
const std::vector<Cell*>& extras, Cell* merge_src_into)
|
||||
{
|
||||
// Verify each listed port is an output of root_cell and that the
|
||||
// replacements cover every output port of root_cell.
|
||||
pool<IdString> listed;
|
||||
std::vector<SigSpec> old_sigs;
|
||||
old_sigs.reserve(port_replacements.size());
|
||||
for (auto &[port, new_sig] : port_replacements) {
|
||||
if (!root_cell->output(port))
|
||||
log_error("patch_ports: cell %s of type %s port %s is not an output\n",
|
||||
log_id(root_cell->name), log_id(root_cell->type), log_id(port));
|
||||
SigSpec old_sig = root_cell->getPort(port);
|
||||
if (old_sig.size() != new_sig.size())
|
||||
log_error("patch_ports size mismatch on cell %s port %s: old %d (%s) vs new %d (%s)\n",
|
||||
log_id(root_cell->name), log_id(port),
|
||||
old_sig.size(), log_signal(old_sig),
|
||||
new_sig.size(), log_signal(new_sig));
|
||||
listed.insert(port);
|
||||
old_sigs.push_back(old_sig);
|
||||
}
|
||||
for (auto &[port, sig] : root_cell->connections())
|
||||
if (root_cell->output(port) && !listed.count(port))
|
||||
log_error("patch_ports: cell %s of type %s has output port %s not in port_replacements\n",
|
||||
log_id(root_cell->name), log_id(root_cell->type), log_id(port));
|
||||
|
||||
for (auto& wire: wires_)
|
||||
commit_wire(std::move(wire));
|
||||
pool<std::string> src_pool;
|
||||
collect_src(root_cell, extras, merge_src_into, src_pool);
|
||||
|
||||
std::vector<Cell*> committed = commit_staged();
|
||||
apply_src(src_pool, committed, merge_src_into);
|
||||
|
||||
// Drop every port (inputs included) so root_cell becomes a disconnected
|
||||
// shell before we wire old_sigs to new_sigs. Doing this first ensures
|
||||
// the old port signals are not briefly double-driven by root_cell and
|
||||
// the new connection.
|
||||
std::vector<IdString> all_ports;
|
||||
all_ports.reserve(root_cell->connections().size());
|
||||
for (auto &[port, sig] : root_cell->connections())
|
||||
all_ports.push_back(port);
|
||||
for (auto port : all_ports)
|
||||
root_cell->unsetPort(port);
|
||||
log_assert(root_cell->connections().empty());
|
||||
|
||||
// Now drop old_cell's drivers so old_sigs are undriven, then merge each
|
||||
// into its new_sig. connect_incremental updates sigmap and re-normalizes
|
||||
// fanout consumers in place — no full sigNormalize needed.
|
||||
for (auto &[port, new_sig] : port_replacements)
|
||||
old_cell->setPort(port, SigSpec());
|
||||
for (size_t i = 0; i < port_replacements.size(); i++) {
|
||||
auto &[port, new_sig] = port_replacements[i];
|
||||
if (map)
|
||||
|
|
@ -174,24 +204,7 @@ void Patch::patch(Cell* old_cell, const std::vector<std::pair<IdString, SigSpec>
|
|||
mod->connect_incremental(old_sigs[i], new_sig);
|
||||
}
|
||||
|
||||
// gc removes old_cell AND any newly-dead input-cone cells, contributing
|
||||
// each removed cell's src into the pool. The merged-into cell (e.g. an
|
||||
// opt_merge keep_cell) and any caller-bequeathed pool entries also get
|
||||
// folded in here.
|
||||
pool<string> src_pool;
|
||||
if (merge_src_into)
|
||||
src_pool.insert(merge_src_into->get_src_attribute());
|
||||
gc(old_cell, /*track=*/false, &src_pool);
|
||||
|
||||
std::string src_str = AttrObject::strpool_attribute_to_str(src_pool);
|
||||
for (Cell* c : committed_new_cells)
|
||||
c->set_src_attribute(src_str);
|
||||
if (merge_src_into)
|
||||
merge_src_into->set_src_attribute(src_str);
|
||||
|
||||
cells_.clear();
|
||||
wires_.clear();
|
||||
leaves.clear();
|
||||
mod->remove(root_cell);
|
||||
}
|
||||
|
||||
void Patch::commit_inheriting_src(Cell* src_source) {
|
||||
|
|
@ -207,4 +220,4 @@ void Patch::commit_inheriting_src(Cell* src_source) {
|
|||
wires_.clear();
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
YOSYS_NAMESPACE_END
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ YOSYS_NAMESPACE_BEGIN
|
|||
// No virtual methods — subclasses cannot be dispatched through a Patch pointer.
|
||||
struct RTLIL::Patch : public CellAdderMixin<RTLIL::Patch>
|
||||
{
|
||||
private:
|
||||
void gc(Cell* old_cell, bool track = false, pool<std::string>* src_pool = nullptr);
|
||||
|
||||
protected:
|
||||
void add(RTLIL::Wire *wire);
|
||||
void add(RTLIL::Cell *cell);
|
||||
|
|
@ -20,12 +17,13 @@ protected:
|
|||
Cell* commit_cell(std::unique_ptr<Cell> cell);
|
||||
Wire* commit_wire(std::unique_ptr<Wire> wire);
|
||||
|
||||
pool<Wire*> leaves = {};
|
||||
// Move staged cells_/wires_ into the module. Returns raw pointers to
|
||||
// the committed new cells in insertion order.
|
||||
std::vector<Cell*> commit_staged();
|
||||
|
||||
public:
|
||||
Module* mod;
|
||||
SigMap* map;
|
||||
pool<Cell*>* removed_cells = nullptr;
|
||||
vector<std::unique_ptr<Wire>> wires_ = {};
|
||||
vector<std::unique_ptr<Cell>> cells_ = {};
|
||||
|
||||
|
|
@ -33,22 +31,36 @@ public:
|
|||
void connect(const RTLIL::SigSpec &lhs, const RTLIL::SigSpec &rhs);
|
||||
const std::vector<RTLIL::SigSig> &connections() const;
|
||||
|
||||
void patch(Cell* old_cell, IdString old_port, SigSpec new_sig);
|
||||
void patch(Cell* old_cell, const std::vector<std::pair<IdString, SigSpec>> &port_replacements);
|
||||
// Compatible rewrite: root_cell's type has exactly one output port
|
||||
// (asserted via kernel/newcelltypes.h). Rewires that output's signal to
|
||||
// new_sig, auto-merges src from root_cell + each cell in `extras` (and
|
||||
// merge_src_into if set) into every staged new cell and into
|
||||
// merge_src_into, then removes root_cell from the module. No input-cone
|
||||
// walk: only root_cell is removed.
|
||||
void patch(Cell* root_cell, IdString old_port, SigSpec new_sig,
|
||||
const std::vector<Cell*>& extras = {},
|
||||
Cell* merge_src_into = nullptr);
|
||||
|
||||
// Variants for "merge old_cell into an existing keep_cell" (e.g.
|
||||
// opt_merge): the old_cell's src attribute is collected and combined
|
||||
// with merge_src_into's existing src, and the result is set on
|
||||
// merge_src_into. Any new cells in cells_ also receive the combined src.
|
||||
void patch(Cell* old_cell, IdString old_port, SigSpec new_sig, Cell* merge_src_into);
|
||||
void patch(Cell* old_cell, const std::vector<std::pair<IdString, SigSpec>> &port_replacements, Cell* merge_src_into);
|
||||
// Multi-output rewrite: transfer a list of output ports to a list of
|
||||
// new sigs. Every entry in `port_replacements` must name an output port
|
||||
// of root_cell, and the list must cover ALL of root_cell's output ports
|
||||
// (both verified via kernel/newcelltypes.h). For each (port, new_sig)
|
||||
// pair the original port signal is connected to new_sig at the module
|
||||
// level. All of root_cell's ports are then unset and the cell is
|
||||
// removed (asserted: no connections remain at the point of removal).
|
||||
// Src is auto-merged from root_cell + extras + merge_src_into into
|
||||
// every staged new cell and into merge_src_into.
|
||||
void patch_ports(Cell* root_cell,
|
||||
const std::vector<std::pair<IdString, SigSpec>>& port_replacements,
|
||||
const std::vector<Cell*>& extras = {},
|
||||
Cell* merge_src_into = nullptr);
|
||||
|
||||
// Flush staged cells_ / wires_ into the module without doing any
|
||||
// connect_incremental or gc. Each committed cell's src attribute is
|
||||
// pulled from `src_source` (typically the cell that's being expanded /
|
||||
// unmapped into the staged helpers, so source-location tracking carries
|
||||
// through transparently). Pass nullptr for src_source if the staged
|
||||
// helpers have no natural ancestor.
|
||||
// connect_incremental or port rewiring. Each committed cell's src
|
||||
// attribute is pulled from `src_source` (typically the cell that's
|
||||
// being expanded / unmapped into the staged helpers, so source-location
|
||||
// tracking carries through transparently). Pass nullptr for src_source
|
||||
// if the staged helpers have no natural ancestor.
|
||||
void commit_inheriting_src(Cell* src_source);
|
||||
RTLIL::Wire *addWire(RTLIL::IdString name, int width = 1);
|
||||
RTLIL::Wire *addWire(RTLIL::IdString name, const RTLIL::Wire *other);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue