Merge upstream

This commit is contained in:
Akash Levy 2025-09-29 20:58:56 -07:00
commit 16215b8786
17 changed files with 526 additions and 163 deletions

View File

@ -180,7 +180,7 @@ ifeq ($(OS), Haiku)
CXXFLAGS += -D_DEFAULT_SOURCE
endif
YOSYS_VER := 0.57+218
YOSYS_VER := 0.57+244
YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1)
YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2)
YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'.' -f3)

View File

@ -105,6 +105,13 @@ struct Index {
if (allow_blackboxes) {
info.found_blackboxes.insert(cell);
} else {
// Even if we don't allow blackboxes these might still be
// present outside of any traversed input cones, so we
// can't bail at this point. If they are hit by a traversal
// (which can only really happen with $tribuf not
// $connect), we can still detect this as an error later.
if (cell->type == ID($connect) || (cell->type == ID($tribuf) && cell->has_attribute(ID(aiger2_zbuf))))
continue;
if (!submodule || submodule->get_blackbox_attribute())
log_warning("Unsupported cell type: %s (%s in %s)\n",
log_id(cell->type), log_id(cell), log_id(m));
@ -483,7 +490,8 @@ struct Index {
{
Design *design = index.design;
auto &minfo = leaf_minfo(index);
log_assert(minfo.suboffsets.count(cell));
if (!minfo.suboffsets.count(cell))
log_error("Reached unsupport cell %s (%s in %s)\n", log_id(cell->type), log_id(cell), log_id(cell->module));
Module *def = design->module(cell->type);
log_assert(def);
levels.push_back(Level(index.modules.at(def), cell));

View File

@ -205,8 +205,8 @@ YosysStreamCallBackHandler verific_read_cb;
// ==================================================================
VerificImporter::VerificImporter(bool mode_gates, bool mode_keep, bool mode_nosva, bool mode_names, bool mode_verific, bool mode_autocover, bool mode_fullinit) :
mode_gates(mode_gates), mode_keep(mode_keep), mode_nosva(mode_nosva),
VerificImporter::VerificImporter(bool mode_gates, bool mode_keep, bool mode_nosva, bool mode_sva_continue, bool mode_names, bool mode_verific, bool mode_autocover, bool mode_fullinit) :
mode_gates(mode_gates), mode_keep(mode_keep), mode_nosva(mode_nosva), mode_sva_continue(mode_sva_continue),
mode_names(mode_names), mode_verific(mode_verific), mode_autocover(mode_autocover),
mode_fullinit(mode_fullinit)
{
@ -2374,6 +2374,12 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::ma
wire->attributes.erase(ID::init);
}
}
if (num_sva_continue) {
log_warning("Encountered %d items containing unsupported SVA!\n", num_sva_continue);
log_warning("Unsupported SVA imported as 'x and marked using the `unsupported_sva' attribute due to -sva-continue-on-err.\n");
}
num_sva_continue = 0;
}
// ==================================================================
@ -3132,7 +3138,7 @@ std::string verific_import(Design *design, const std::map<std::string,std::strin
}
if (nl_done.count(it->first) == 0) {
VerificImporter importer(false, false, false, false, false, false, false);
VerificImporter importer(false, false, false, false, false, false, false, false);
nl_done[it->first] = it->second;
importer.import_netlist(design, nl, nl_todo, top_mod_names.count(nl->CellBaseName()));
}
@ -3369,6 +3375,11 @@ struct VerificPass : public Pass {
log(" -nosva\n");
log(" Ignore SVA properties, do not infer checker logic.\n");
log("\n");
log(" -sva-continue-on-err\n");
log(" Turns unsupported SVA from an error into a warning. Properties are imported\n");
log(" with their trigger condition replaced with 'x and with an `unsupported_sva'\n");
log(" attribute to produce a later error in SBY if they remain in the design.\n");
log("\n");
log(" -L <int>\n");
log(" Maximum number of ctrl bits for SVA checker FSMs (default=16).\n");
log("\n");
@ -4234,7 +4245,8 @@ struct VerificPass : public Pass {
{
std::map<std::string,Netlist*> nl_todo, nl_done;
bool mode_all = false, mode_gates = false, mode_keep = false;
bool mode_nosva = false, mode_names = false, mode_verific = false;
bool mode_nosva = false, mode_sva_continue = false;
bool mode_names = false, mode_verific = false;
bool mode_autocover = false, mode_fullinit = false;
bool flatten = false, extnets = false, mode_cells = false;
bool split_complex_ports = true;
@ -4272,6 +4284,10 @@ struct VerificPass : public Pass {
mode_nosva = true;
continue;
}
if (args[argidx] == "-sva-continue-on-err") {
mode_sva_continue = true;
continue;
}
if (args[argidx] == "-L" && argidx+1 < GetSize(args)) {
verific_sva_fsm_limit = atoi(args[++argidx].c_str());
continue;
@ -4402,7 +4418,7 @@ struct VerificPass : public Pass {
auto it = nl_todo.begin();
Netlist *nl = it->second;
if (nl_done.count(it->first) == 0) {
VerificImporter importer(mode_gates, mode_keep, mode_nosva,
VerificImporter importer(mode_gates, mode_keep, mode_nosva, mode_sva_continue,
mode_names, mode_verific, mode_autocover, mode_fullinit);
nl_done[it->first] = it->second;
importer.import_netlist(design, nl, nl_todo, top_mod_names.count(nl->CellBaseName()));

View File

@ -77,10 +77,12 @@ struct VerificImporter
std::map<Verific::Net*, Verific::Net*> sva_posedge_map;
pool<Verific::Net*> any_all_nets;
bool mode_gates, mode_keep, mode_nosva, mode_names, mode_verific;
bool mode_gates, mode_keep, mode_nosva, mode_sva_continue, mode_names, mode_verific;
bool mode_autocover, mode_fullinit;
VerificImporter(bool mode_gates, bool mode_keep, bool mode_nosva, bool mode_names, bool mode_verific, bool mode_autocover, bool mode_fullinit);
int num_sva_continue = 0;
VerificImporter(bool mode_gates, bool mode_keep, bool mode_nosva, bool mode_sva_continue, bool mode_names, bool mode_verific, bool mode_autocover, bool mode_fullinit);
RTLIL::SigBit net_map_at(Verific::Net *net);

View File

@ -124,6 +124,7 @@ struct SvaFsm
{
Module *module;
VerificClocking clocking;
std::function<void (std::string)> parser_error;
SigBit trigger_sig = State::S1, disable_sig;
SigBit throughout_sig = State::S1;
@ -148,6 +149,7 @@ struct SvaFsm
module = clking.module;
clocking = clking;
trigger_sig = trig;
parser_error = [](std::string msg){ log_error("%s", msg); };
startNode = createNode();
acceptNode = createNode();
@ -475,8 +477,8 @@ struct SvaFsm
dump();
log(" ctrl signal: %s\n", log_signal(dnode.ctrl));
}
log_error("SVA DFSM state ctrl signal has %d (>%d) bits. Stopping to prevent exponential design size explosion.\n",
GetSize(dnode.ctrl), verific_sva_fsm_limit);
parser_error(stringf("SVA DFSM state ctrl signal has %d (>%d) bits. Stopping to prevent exponential design size explosion.\n",
GetSize(dnode.ctrl), verific_sva_fsm_limit));
}
for (unsigned long long i = 0; i < (1ull << GetSize(dnode.ctrl)); i++)
@ -1023,7 +1025,7 @@ struct VerificSvaImporter
[[noreturn]] void parser_error(std::string errmsg)
{
if (!importer->mode_keep)
if (!importer->mode_keep && !importer->mode_sva_continue)
log_error("%s", errmsg);
log_warning("%s", errmsg);
throw ParserErrorException();
@ -1260,6 +1262,7 @@ struct VerificSvaImporter
if (inst->Type() == PRIM_SVA_FIRST_MATCH)
{
SvaFsm match_fsm(clocking);
match_fsm.parser_error = [&](std::string msg) { this->parser_error(msg); };
match_fsm.createLink(parse_sequence(match_fsm, match_fsm.createStartNode(), inst->GetInput()), match_fsm.acceptNode);
int node = fsm.createNode();
@ -1426,12 +1429,15 @@ struct VerificSvaImporter
if (inst->Type() == PRIM_SVA_SEQ_AND || inst->Type() == PRIM_SVA_AND)
{
SvaFsm fsm1(clocking);
fsm1.parser_error = [&](std::string msg) { this->parser_error(msg); };
fsm1.createLink(parse_sequence(fsm1, fsm1.createStartNode(), inst->GetInput1()), fsm1.acceptNode);
SvaFsm fsm2(clocking);
fsm2.parser_error = [&](std::string msg) { this->parser_error(msg); };
fsm2.createLink(parse_sequence(fsm2, fsm2.createStartNode(), inst->GetInput2()), fsm2.acceptNode);
SvaFsm combined_fsm(clocking);
combined_fsm.parser_error = [&](std::string msg) { this->parser_error(msg); };
fsm1.getDFsm(combined_fsm, combined_fsm.createStartNode(), -1, combined_fsm.acceptNode);
fsm2.getDFsm(combined_fsm, combined_fsm.createStartNode(), -1, combined_fsm.acceptNode);
@ -1456,6 +1462,7 @@ struct VerificSvaImporter
if (inst->Type() == PRIM_SVA_INTERSECT || inst->Type() == PRIM_SVA_WITHIN)
{
SvaFsm intersect_fsm(clocking);
intersect_fsm.parser_error = [&](std::string msg) { this->parser_error(msg); };
if (inst->Type() == PRIM_SVA_INTERSECT)
{
@ -1562,6 +1569,7 @@ struct VerificSvaImporter
int node;
SvaFsm antecedent_fsm(clocking, trig);
antecedent_fsm.parser_error = [&](std::string msg) { this->parser_error(msg); };
node = parse_sequence(antecedent_fsm, antecedent_fsm.createStartNode(), antecedent_net);
if (inst->Type() == PRIM_SVA_NON_OVERLAPPED_IMPLICATION) {
int next_node = antecedent_fsm.createNode();
@ -1623,6 +1631,7 @@ struct VerificSvaImporter
int node;
SvaFsm antecedent_fsm(clocking, trig);
antecedent_fsm.parser_error = [&](std::string msg) { this->parser_error(msg); };
node = parse_sequence(antecedent_fsm, antecedent_fsm.createStartNode(), antecedent_net);
if (inst->Type() == PRIM_SVA_NON_OVERLAPPED_IMPLICATION || inst->Type() == PRIM_SVA_NON_OVERLAPPED_FOLLOWED_BY) {
int next_node = antecedent_fsm.createNode();
@ -1677,6 +1686,7 @@ struct VerificSvaImporter
}
SvaFsm consequent_fsm(clocking, antecedent_match);
consequent_fsm.parser_error = [&](std::string msg) { this->parser_error(msg); };
node = parse_sequence(consequent_fsm, consequent_fsm.createStartNode(), consequent_net);
consequent_fsm.createLink(node, consequent_fsm.acceptNode);
@ -1696,6 +1706,7 @@ struct VerificSvaImporter
}
SvaFsm fsm(clocking, trig);
fsm.parser_error = [&](std::string msg) { this->parser_error(msg); };
int node = parse_sequence(fsm, fsm.createStartNode(), net);
fsm.createLink(node, fsm.acceptNode);
@ -1710,30 +1721,34 @@ struct VerificSvaImporter
void import()
{
try
{
module = importer->module;
netlist = root->Owner();
module = importer->module;
netlist = root->Owner();
if (verific_verbose)
log(" importing SVA property at root cell %s (%s) at %s:%d.\n", root->Name(), root->View()->Owner()->Name(),
LineFile::GetFileName(root->Linefile()), LineFile::GetLineNo(root->Linefile()));
int initial_cell_count = GetSize(module->cells_);
int initial_wire_count = GetSize(module->wires_);
int initial_connection_count = GetSize(module->connections_);
bool is_user_declared = root->IsUserDeclared();
if (verific_verbose)
log(" importing SVA property at root cell %s (%s) at %s:%d.\n", root->Name(), root->View()->Owner()->Name(),
LineFile::GetFileName(root->Linefile()), LineFile::GetLineNo(root->Linefile()));
// FIXME
if (!is_user_declared) {
const char *name = root->Name();
for (int i = 0; name[i]; i++) {
if (i ? (name[i] < '0' || name[i] > '9') : (name[i] != 'i')) {
is_user_declared = true;
break;
}
bool is_user_declared = root->IsUserDeclared();
// FIXME
if (!is_user_declared) {
const char *name = root->Name();
for (int i = 0; name[i]; i++) {
if (i ? (name[i] < '0' || name[i] > '9') : (name[i] != 'i')) {
is_user_declared = true;
break;
}
}
}
RTLIL::IdString root_name = module->uniquify(importer->mode_names || is_user_declared ? RTLIL::escape_id(root->Name()) : NEW_ID);
RTLIL::IdString root_name = module->uniquify(importer->mode_names || is_user_declared ? RTLIL::escape_id(root->Name()) : NEW_ID);
try
{
// parse SVA sequence into trigger signal
clocking = VerificClocking(importer, root->GetInput(), true);
@ -1836,6 +1851,36 @@ struct VerificSvaImporter
}
catch (ParserErrorException)
{
if (importer->mode_sva_continue) {
std::vector<Cell *> remove_cells;
pool<Wire *> remove_wires;
for (int i = 0, end = GetSize(module->cells_) - initial_cell_count; i != end; ++i)
remove_cells.push_back(module->cells_.element(i)->second);
for (int i = 0, end = GetSize(module->wires_) - initial_wire_count; i != end; ++i)
remove_wires.emplace(module->wires_.element(i)->second);
for (auto cell : remove_cells)
module->remove(cell);
module->remove(remove_wires);
module->connections_.resize(initial_connection_count);
RTLIL::Cell *c = nullptr;
if (mode_assert) c = module->addAssert(root_name, State::Sx, State::Sx);
if (mode_assume) c = module->addAssume(root_name, State::Sx, State::Sx);
if (mode_cover) c = module->addCover(root_name, State::Sx, State::Sx);
if (c) {
importer->import_attributes(c->attributes, root);
c->set_bool_attribute(ID(unsupported_sva));
}
importer->num_sva_continue++;
}
}
}
};

View File

@ -2845,6 +2845,13 @@ void RTLIL::Module::remove(const pool<RTLIL::Wire*> &wires)
delete_wire_worker.wires_p = &wires;
rewrite_sigspecs2(delete_wire_worker);
if (design->flagBufferedNormalized) {
for (auto wire : wires) {
buf_norm_wire_queue.erase(wire);
buf_norm_connect_index.erase(wire);
}
}
for (auto &it : wires) {
log_assert(wires_.count(it->name) != 0);
wires_.erase(it->name);

View File

@ -43,6 +43,7 @@ void RTLIL::Design::bufNormalize(bool enable)
wire->driverCell_ = nullptr;
wire->driverPort_ = IdString();
}
module->buf_norm_connect_index.clear();
}
flagBufferedNormalized = false;
@ -126,15 +127,19 @@ void RTLIL::Module::bufNormalize()
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.
// Wires with their unique driving cell port. We pick the first driver
// we encounter, with the exception that we ensure that a module input
// port can only get $input_port drivers and that $input_port drivers
// cannot drive any other modules. If we reject an $input_port driver
// because it's not driving an input port or because there already is
// another $input_port driver for the same port, we also delete that
// $input_port 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.
// Set of cell ports that need a fresh intermediate wire. These are all
// cell ports that drive non-full-wire sigspecs, cell ports driving
// module input ports, and cell ports driving wires that are already
// driven.
pool<std::pair<Cell *, IdString>> pending_ports;
// This helper will be called for every output/inout cell port that is
@ -149,27 +154,14 @@ void RTLIL::Module::bufNormalize()
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()) {
// Make sure all wires of the cell port are enqueued, ensuring we
// detect other connected drivers (output and inout).
for (auto chunk : sig.chunks())
if (chunk.is_wire())
wire_queue_entries(chunk.wire);
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
@ -206,47 +198,40 @@ void RTLIL::Module::bufNormalize()
if (!sig_y.empty())
connect(sig_y, sig_a);
remove(cell);
log_assert(GetSize(buf_norm_wire_queue) <= 1);
buf_norm_wire_queue.clear();
return;
} else if (cell->type == ID($input_port)) {
log_assert(port == ID::Y);
if (sig.is_wire()) {
Wire *w = sig.as_wire();
if (w->port_input && !w->port_output) {
// An $input_port cell can only drive a full wire module input port
auto [found, inserted] = direct_driven_wires.emplace(w, {cell, port});
if (!inserted || (found->second.first == cell && found->second.second == port))
return;
}
}
// If an `$input_port` cell isn't driving a full
// input port wire, we remove it since the wires are still the
// canonical source of module ports
buf_norm_cell_queue.insert(cell);
remove(cell);
log_assert(GetSize(buf_norm_wire_queue) <= 1);
buf_norm_wire_queue.clear();
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);
if (!w->port_input || w->port_output) {
// If the full cell port is connected to a full non-input-port wire, pick it as driver
auto [found, inserted] = direct_driven_wires.emplace(w, {cell, port});
if (inserted || (found->second.first == cell && found->second.second == port))
return;
}
}
@ -256,16 +241,22 @@ void RTLIL::Module::bufNormalize()
pending_ports.emplace(cell, port);
};
// We enqueue all enqueued 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();
// Only after clearing the `buf_norm_wire_queue` are we allowed to call
// enqueue_cell_port, since we're using assertions to check against
// unintended wires being enqueued into `buf_norm_wire_queue` that
// would prevent us from restoring the bufnorm invariants in a single
// pass.
// 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})
@ -302,8 +293,11 @@ void RTLIL::Module::bufNormalize()
if (chunk.wire)
wire_queue_entries(chunk.wire);
connect(sig_a, sig_b);
buf_norm_cell_queue.insert(connect_cell);
remove(connect_cell);
log_assert(GetSize(buf_norm_wire_queue) <= 2);
buf_norm_wire_queue.clear();
}
}
}
@ -315,6 +309,9 @@ void RTLIL::Module::bufNormalize()
// 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) {
log_assert(cell->type != ID($input_port));
log_assert(!cell->type.empty());
log_assert(!pending_deleted_cells.count(cell));
SigSpec const &sig = cell->getPort(port);
Wire *w = addWire(NEW_ID, GetSize(sig));
@ -323,16 +320,9 @@ void RTLIL::Module::bufNormalize()
// 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);
direct_driven_wires.emplace(w, {cell, port});
cell->setPort(port, w); // Hits the fast path that doesn't enqueue w
wire_queue_entries(w); // Needed so we pick up the sig <-> w connection
}
// At this point we're done with creating wires and know which ones are
@ -346,7 +336,7 @@ void RTLIL::Module::bufNormalize()
wire->driverPort_.clear();
}
// For the unique output cell ports fully connected to a full wire, we
// For the unique driving 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) {
@ -373,54 +363,55 @@ void RTLIL::Module::bufNormalize()
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}) {
// We pick SigMap representatives by prioritizing input ports over
// driven wires over other/unknown wires.
for (bool input_ports : {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 {
if ((wire->port_input && !wire->port_output) == input_ports) {
for (int i = 0; i != GetSize(wire); ++i) {
SigBit driver = SigBit(wire, i);
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;
// All three pool<SigBit> below are in terms of sigmapped bits
// Bits that are known to have a unique driver that is an unconditional driver or one or more inout drivers
pool<SigBit> driven;
// Bits that have multiple unconditional drivers, this forces the use of `$connect`
pool<SigBit> conflicted;
// Bits that are driven by an inout driver
pool<SigBit> weakly_driven;
for (auto const &[wire, cellport] : direct_driven_wires) {
auto const &[cell, port] = cellport;
for (int i = 0; i != GetSize(wire); ++i) {
SigBit driver = SigBit(wire, i);
SigBit repr = sigmap(driver);
if (driver != repr)
driven.erase(repr);
SigBit driver = sigmap(SigBit(wire, i));
if (cell->type == ID($tribuf) || cell->port_dir(port) == RTLIL::PD_INOUT) {
// We add inout drivers to `driven` in a separate loop below
weakly_driven.insert(driver);
} else {
// We remove driver conflicts from `driven` in a separate loop below
bool inserted = driven.insert(driver).second;
if (!inserted)
conflicted.insert(driver);
}
}
}
for (auto &bit : conflicted)
driven.erase(bit);
// If a wire has one or more inout drivers and an unconditional driver, that's still a conflict
for (auto driver : weakly_driven)
if (!driven.insert(driver).second)
conflicted.insert(driver);
// This only leaves the drivers matching `driven`'s definition above
for (auto driver : conflicted)
driven.erase(driver);
// Having picked representatives and checked whether they are unique
// drivers, we can turn the connecitivty of our sigmap back into $buf
// and $connect cells.
// Module level bitwise connections not representable by `$buf` cells
pool<pair<SigBit, SigBit>> undirected_connections;
@ -561,6 +552,8 @@ void RTLIL::Cell::unsetPort(const RTLIL::IdString& portname)
w->driverCell_ = nullptr;
w->driverPort_ = IdString();
module->buf_norm_wire_queue.insert(w);
} else if (w->driverCell_) {
log_assert(w->driverCell_->getPort(w->driverPort_) == w);
}
}
@ -630,7 +623,8 @@ void RTLIL::Cell::setPort(const RTLIL::IdString& portname, RTLIL::SigSpec signal
// bufNormalize call
if ((dir == RTLIL::PD_OUTPUT || dir == RTLIL::PD_INOUT) && signal.is_wire()) {
Wire *w = signal.as_wire();
if (w->driverCell_ == nullptr) {
if (w->driverCell_ == nullptr && (
(w->port_input && !w->port_output) == (type == ID($input_port)))) {
w->driverCell_ = this;
w->driverPort_ = portname;

View File

@ -124,7 +124,8 @@ struct PortarcsPass : Pass {
TopoSort<SigBit> sort;
for (auto cell : m->cells())
if (cell->type != ID($buf)) {
// Ignore all bufnorm helper cells
if (!cell->type.in(ID($buf), ID($input_port), ID($connect))) {
auto tdata = tinfo.find(cell->type);
if (tdata == tinfo.end())
log_cmd_error("Missing timing data for module '%s'.\n", log_id(cell->type));

View File

@ -1149,6 +1149,25 @@ struct HierarchyPass : public Pass {
}
}
if (flag_simcheck || flag_smtcheck) {
for (auto mod : design->modules()) {
for (auto cell : mod->cells()) {
if (!cell->type.in(ID($check), ID($assert), ID($assume), ID($live), ID($fair), ID($cover)))
continue;
if (!cell->has_attribute(ID(unsupported_sva)))
continue;
auto src = cell->get_src_attribute();
if (!src.empty())
src += ": ";
log_error("%sProperty `%s' in module `%s' uses unsupported SVA constructs. See frontend warnings for details, run `chformal -remove a:unsupported_sva' to ignore.\n",
src, log_id(cell->name), log_id(mod->name));
}
}
}
if (!keep_positionals)
{
std::set<RTLIL::Module*> pos_mods;

View File

@ -635,9 +635,17 @@ void rmunused_module(RTLIL::Module *module, bool purge_mode, bool unusedbitsattr
}
}
for (auto cell : delcells) {
if (verbose)
log_debug(" removing buffer cell `%s': %s = %s\n", cell->name,
log_signal(cell->getPort(ID::Y)), log_signal(cell->getPort(ID::A)));
if (verbose) {
if (cell->type == ID($connect))
log_debug(" removing connect cell `%s': %s <-> %s\n", cell->name,
log_signal(cell->getPort(ID::A)), log_signal(cell->getPort(ID::B)));
else if (cell->type == ID($input_port))
log_debug(" removing input port marker cell `%s': %s\n", cell->name,
log_signal(cell->getPort(ID::Y)));
else
log_debug(" removing buffer cell `%s': %s = %s\n", cell->name,
log_signal(cell->getPort(ID::Y)), log_signal(cell->getPort(ID::A)));
}
module->remove(cell);
}
if (!delcells.empty())

View File

@ -144,12 +144,12 @@ struct ModuleIndex {
}
if (nunused > 0) {
log("Disconnected %d input bits of instance '%s' of '%s' in '%s'\n",
log("Disconnected %d input bits of instance '%s' (type '%s') in '%s'\n",
nunused, log_id(instantiation), log_id(instantiation->type), log_id(parent.module));
changed = true;
}
if (nconstants > 0) {
log("Substituting constant for %d output bits of instance '%s' of '%s' in '%s'\n",
log("Substituting constant for %d output bits of instance '%s' (type '%s') in '%s'\n",
nconstants, log_id(instantiation), log_id(instantiation->type), log_id(parent.module));
changed = true;
}
@ -310,7 +310,7 @@ struct UsageData {
refine_tie_togethers(inputs);
}
bool apply_changes() {
bool apply_changes(ModuleIndex &index) {
bool did_something = false;
if (module->get_blackbox_attribute()) {
@ -374,8 +374,16 @@ struct UsageData {
// Propagate tied-together inputs
dict<SigBit, SigBit> ties;
for (auto group : tie_together_inputs) {
for (int i = 1; i < group.size(); i++)
ties[group[i]] = group[0];
// Only consider used inputs for a tie-together group.
// ModuleIndex::apply_changes might have disconnected
// unused inputs.
SigSpec filtered_group;
for (auto bit : group) {
if (index.used.check(bit))
filtered_group.append(bit);
}
for (int i = 1; i < filtered_group.size(); i++)
ties[filtered_group[i]] = filtered_group[0];
}
SigPool applied_ties;
auto ties_rewrite = [&](SigSpec &signal) {
@ -449,12 +457,13 @@ struct OptHierPass : Pass {
bool did_something = false;
for (auto module : d->selected_modules(RTLIL::SELECT_WHOLE_ONLY, RTLIL::SB_UNBOXED_CMDERR)) {
ModuleIndex &parent_index = indices.at(module->name);
if (usage_datas.count(module->name)) {
log_debug("Applying usage data changes to %s\n", log_id(module));
did_something |= usage_datas.at(module->name).apply_changes();
did_something |= usage_datas.at(module->name).apply_changes(parent_index);
}
ModuleIndex &parent_index = indices.at(module->name);
for (auto cell : module->cells()) {
if (indices.count(cell->type)) {
log_debug("Applying changes to instance %s of %s in %s\n", log_id(cell), log_id(cell->type), log_id(module));

View File

@ -246,10 +246,15 @@ struct OptMergeWorker
// mem can have an excessively large parameter holding the init data
continue;
}
if (cell->type == ID($scopeinfo))
continue;
if (mode_keepdc && has_dont_care_initval(cell))
continue;
if (ct.cell_known(cell->type) || (mode_share_all && cell->known()))
cells.push_back(cell);
if (!cell->known())
continue;
if (!mode_share_all && !ct.cell_known(cell->type))
continue;
cells.push_back(cell);
}
did_something = false;
@ -278,12 +283,6 @@ struct OptMergeWorker
for (auto cell : cells)
{
if ((!mode_share_all && !ct.cell_known(cell->type)) || !cell->known())
continue;
if (cell->type == ID($scopeinfo))
continue;
auto [cell_in_map, inserted] = known_cells.insert(cell);
if (!inserted) {
// We've failed to insert since we already have an equivalent cell

23
tests/opt/bug5398.ys Normal file
View File

@ -0,0 +1,23 @@
read_verilog <<EOF
module tag_2x4(
input R0_clk,
input W0_clk,
output x,
);
assign x = !W0_clk;
endmodule
module top(input clock, output x, output flag);
tag_2x4 tag_ext(
.R0_clk (clock),
.W0_clk (clock),
.x (x)
);
assign flag = x ^ clock;
endmodule
EOF
hierarchy -top top
opt_hier
flatten
sat -verify -prove flag 1

View File

@ -0,0 +1,154 @@
read_aiger <<EOF
aag 124 11 9 2 104
2
4
6
8
10
12
14
16
18
20
22
24 42
26 57
28 58
30 68
32 86
34 113
36 114
38 105
40 133
228
248
42 26 5
44 35 33
46 36 3
48 46 44
50 36 31
52 28 25
54 53 50
56 55 49
58 38 25
60 40 28
62 60 25
64 37 30
66 64 24
68 66 62
70 29 7
72 40 37
74 7 2
76 74 73
78 76 12
80 78 71
82 76 13
84 83 14
86 84 80
88 34 31
90 89 58
92 40 37
94 39 26
96 94 93
98 96 91
100 98 14
102 39 6
104 40 27
106 104 103
108 107 55
110 109 98
112 110 100
114 24 10
116 26 8
118 7 5
120 119 117
122 5 3
124 27 25
126 125 123
128 126 120
130 129 83
132 131 111
134 48 12
136 121 48
138 137 135
140 139 130
142 138 81
144 114 91
146 145 57
148 38 29
150 148 25
152 151 48
154 153 69
156 155 147
158 157 143
160 159 140
162 147 81
164 163 86
166 165 21
168 167 161
170 169 22
172 113 19
174 133 113
176 174 172
178 108 83
180 179 155
182 181 19
184 182 140
186 185 177
188 112 21
190 189 176
192 146 140
194 192 164
196 112 86
198 196 165
200 198 194
202 181 156
204 145 83
206 205 17
208 178 138
210 209 206
212 140 132
214 212 210
216 214 202
218 194 161
220 218 216
222 220 201
224 222 190
226 224 186
228 226 170
230 167 157
232 182 172
234 193 21
236 235 232
238 202 197
240 238 185
242 238 188
244 242 240
246 244 237
248 246 231
c
aigfuzz -s -1 1145242652
seed 1145242652
fuzzer layers
depth 9
width 19
input fraction 13%
latch fraction 84%
lower fraction 20%
monotonicity -1
and closure
EOF
bufnorm -update
delete -output o:* %R1
opt_clean
bufnorm -update
delete -output o:* %R1
tee -q debug opt_clean
bufnorm -update
bufnorm -reset
opt_clean

View File

@ -0,0 +1,38 @@
verific -sv <<EOF
module top(input clk, input a, input b);
prop_supported: assert property (@(posedge clk) a ##1 b |=> b);
prop_unsupported1: assert property (@(posedge clk) a ##1 b #=# b);
prop_unsupported2: assert property (@(posedge clk) a ##1 @(posedge b) ##1 a);
sequence local_var_seq;
logic v;
(1, v = a) ##1 b ##1 (v == a);
endsequence
prop_unsupported3: assert property (@(posedge clk) local_var_seq);
endmodule
EOF
logger -expect warning "Mixed clocking is currently not supported" 1
logger -expect warning "Verific SVA primitive sva_non_overlapped_followed_by .* is currently unsupported in this context" 1
logger -expect warning "SVA sequences with local variable assignments are currently not supported" 1
logger -expect warning "Encountered 3 items containing unsupported SVA" 1
verific -import -sva-continue-on-err top
logger -check-expected
select -assert-count 4 top/t:$assert
select -assert-count 4 top/a:unsupported_sva top/prop_supported %% top/t:$assert %i
select -assert-count 3 top/a:unsupported_sva
select -assert-count 3 top/a:unsupported_sva top/prop_unsupported* %i
select -assert-count 1 top/a:unsupported_sva top/prop_unsupported1 %i
select -assert-count 1 top/a:unsupported_sva top/prop_unsupported2 %i
select -assert-count 1 top/a:unsupported_sva top/prop_unsupported3 %i
select -assert-count 0 top/a:unsupported_sva top/prop_supported %i
select -assert-count 1 top/prop_supported
logger -expect error "uses unsupported SVA constructs." 1
hierarchy -smtcheck -top top
logger -check-expected

View File

@ -0,0 +1,31 @@
verific -sv <<EOF
module top(input clk, input a, input b);
prop_supported1: assert property (@(posedge clk) a ##1 b |=> b);
prop_exploding: assert property (@(posedge clk)
((a [*7] ##1 b) [*11]) and
((a [*11] ##1 b) [*7]) and
((a [*13] ##1 b) [*5]) and
((a [*5] ##1 b) [*13])
);
prop_supported2: assert property (@(posedge clk) a [*5] ##1 b |=> b);
endmodule
EOF
logger -expect warning "Stopping to prevent exponential design size explosion." 1
verific -import -sva-continue-on-err top
logger -check-expected
select -assert-count 3 top/t:$assert
select -assert-count 1 top/a:unsupported_sva top/prop_exploding %% top/t:$assert %i
select -assert-count 0 top/a:unsupported_sva top/prop_supported1 %i
select -assert-count 0 top/a:unsupported_sva top/prop_supported2 %i
select -assert-count 2 top/prop_supported*
logger -expect error "uses unsupported SVA constructs." 1
hierarchy -smtcheck -top top
logger -check-expected

View File

@ -0,0 +1,9 @@
verific -sv <<EOF
module top(input clk, input a, input b);
prop_unsupported: assert property (@(posedge clk) a ##1 @(posedge b) ##1 a);
endmodule;
EOF
logger -expect error "Mixed clocking is currently not supported" 1
verific -import top