1
0
mirror of synced 2026-01-12 00:42:47 +00:00

Merge pull request #5564 from rocallahan/pass-fuzz

Add support for fuzz-test comparison of two passes intended to give identical RTLIL results
This commit is contained in:
Emil J 2026-01-06 20:07:31 +01:00 committed by GitHub
commit 0ab967b036
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 733 additions and 20 deletions

View File

@ -40,6 +40,7 @@ struct RTLILFrontendWorker {
bool flag_nooverwrite = false; bool flag_nooverwrite = false;
bool flag_overwrite = false; bool flag_overwrite = false;
bool flag_lib = false; bool flag_lib = false;
bool flag_legalize = false;
int line_num; int line_num;
std::string line_buf; std::string line_buf;
@ -322,6 +323,17 @@ struct RTLILFrontendWorker {
return val; return val;
} }
RTLIL::Wire *legalize_wire(RTLIL::IdString id)
{
int wires_size = current_module->wires_size();
if (wires_size == 0)
error("No wires found for legalization");
int hash = hash_ops<RTLIL::IdString>::hash(id).yield();
RTLIL::Wire *wire = current_module->wire_at(abs(hash % wires_size));
log("Legalizing wire `%s' to `%s'.\n", log_id(id), log_id(wire->name));
return wire;
}
RTLIL::SigSpec parse_sigspec() RTLIL::SigSpec parse_sigspec()
{ {
RTLIL::SigSpec sig; RTLIL::SigSpec sig;
@ -339,8 +351,12 @@ struct RTLILFrontendWorker {
std::optional<RTLIL::IdString> id = try_parse_id(); std::optional<RTLIL::IdString> id = try_parse_id();
if (id.has_value()) { if (id.has_value()) {
RTLIL::Wire *wire = current_module->wire(*id); RTLIL::Wire *wire = current_module->wire(*id);
if (wire == nullptr) if (wire == nullptr) {
error("Wire `%s' not found.", *id); if (flag_legalize)
wire = legalize_wire(*id);
else
error("Wire `%s' not found.", *id);
}
sig = RTLIL::SigSpec(wire); sig = RTLIL::SigSpec(wire);
} else { } else {
sig = RTLIL::SigSpec(parse_const()); sig = RTLIL::SigSpec(parse_const());
@ -349,17 +365,44 @@ struct RTLILFrontendWorker {
while (try_parse_char('[')) { while (try_parse_char('[')) {
int left = parse_integer(); int left = parse_integer();
if (left >= sig.size() || left < 0) if (left >= sig.size() || left < 0) {
error("bit index %d out of range", left); if (flag_legalize) {
int legalized;
if (sig.size() == 0)
legalized = 0;
else
legalized = std::max(0, std::min(left, sig.size() - 1));
log("Legalizing bit index %d to %d.\n", left, legalized);
left = legalized;
} else {
error("bit index %d out of range", left);
}
}
if (try_parse_char(':')) { if (try_parse_char(':')) {
int right = parse_integer(); int right = parse_integer();
if (right < 0) if (right < 0) {
error("bit index %d out of range", right); if (flag_legalize) {
if (left < right) log("Legalizing bit index %d to %d.\n", right, 0);
error("invalid slice [%d:%d]", left, right); right = 0;
sig = sig.extract(right, left-right+1); } else
error("bit index %d out of range", right);
}
if (left < right) {
if (flag_legalize) {
log("Legalizing bit index %d to %d.\n", left, right);
left = right;
} else
error("invalid slice [%d:%d]", left, right);
}
if (flag_legalize && left >= sig.size())
log("Legalizing slice %d:%d by igoring it\n", left, right);
else
sig = sig.extract(right, left - right + 1);
} else { } else {
sig = sig.extract(left); if (flag_legalize && left >= sig.size())
log("Legalizing slice %d by igoring it\n", left);
else
sig = sig.extract(left);
} }
expect_char(']'); expect_char(']');
} }
@ -476,8 +519,14 @@ struct RTLILFrontendWorker {
{ {
std::optional<RTLIL::IdString> id = try_parse_id(); std::optional<RTLIL::IdString> id = try_parse_id();
if (id.has_value()) { if (id.has_value()) {
if (current_module->wire(*id) != nullptr) if (current_module->wire(*id) != nullptr) {
error("RTLIL error: redefinition of wire %s.", *id); if (flag_legalize) {
log("Legalizing redefinition of wire %s.\n", *id);
pool<RTLIL::Wire*> wires = {current_module->wire(*id)};
current_module->remove(wires);
} else
error("RTLIL error: redefinition of wire %s.", *id);
}
wire = current_module->addWire(std::move(*id)); wire = current_module->addWire(std::move(*id));
break; break;
} }
@ -528,8 +577,13 @@ struct RTLILFrontendWorker {
{ {
std::optional<RTLIL::IdString> id = try_parse_id(); std::optional<RTLIL::IdString> id = try_parse_id();
if (id.has_value()) { if (id.has_value()) {
if (current_module->memories.count(*id) != 0) if (current_module->memories.count(*id) != 0) {
error("RTLIL error: redefinition of memory %s.", *id); if (flag_legalize) {
log("Legalizing redefinition of memory %s.\n", *id);
current_module->remove(current_module->memories.at(*id));
} else
error("RTLIL error: redefinition of memory %s.", *id);
}
memory->name = std::move(*id); memory->name = std::move(*id);
break; break;
} }
@ -551,14 +605,36 @@ struct RTLILFrontendWorker {
expect_eol(); expect_eol();
} }
void legalize_width_parameter(RTLIL::Cell *cell, RTLIL::IdString port_name)
{
std::string width_param_name = port_name.str() + "_WIDTH";
if (cell->parameters.count(width_param_name) == 0)
return;
RTLIL::Const &param = cell->parameters.at(width_param_name);
if (param.as_int() != 0)
return;
cell->parameters[width_param_name] = RTLIL::Const(cell->getPort(port_name).size());
}
void parse_cell() void parse_cell()
{ {
RTLIL::IdString cell_type = parse_id(); RTLIL::IdString cell_type = parse_id();
RTLIL::IdString cell_name = parse_id(); RTLIL::IdString cell_name = parse_id();
expect_eol(); expect_eol();
if (current_module->cell(cell_name) != nullptr) if (current_module->cell(cell_name) != nullptr) {
error("RTLIL error: redefinition of cell %s.", cell_name); if (flag_legalize) {
RTLIL::IdString new_name;
int suffix = 1;
do {
new_name = RTLIL::IdString(cell_name.str() + "_" + std::to_string(suffix));
++suffix;
} while (current_module->cell(new_name) != nullptr);
log("Legalizing redefinition of cell %s by renaming to %s.\n", cell_name, new_name);
cell_name = new_name;
} else
error("RTLIL error: redefinition of cell %s.", cell_name);
}
RTLIL::Cell *cell = current_module->addCell(cell_name, cell_type); RTLIL::Cell *cell = current_module->addCell(cell_name, cell_type);
cell->attributes = std::move(attrbuf); cell->attributes = std::move(attrbuf);
@ -587,9 +663,15 @@ struct RTLILFrontendWorker {
expect_eol(); expect_eol();
} else if (try_parse_keyword("connect")) { } else if (try_parse_keyword("connect")) {
RTLIL::IdString port_name = parse_id(); RTLIL::IdString port_name = parse_id();
if (cell->hasPort(port_name)) if (cell->hasPort(port_name)) {
error("RTLIL error: redefinition of cell port %s.", port_name); if (flag_legalize)
log("Legalizing redefinition of cell port %s.", port_name);
else
error("RTLIL error: redefinition of cell port %s.", port_name);
}
cell->setPort(std::move(port_name), parse_sigspec()); cell->setPort(std::move(port_name), parse_sigspec());
if (flag_legalize)
legalize_width_parameter(cell, port_name);
expect_eol(); expect_eol();
} else if (try_parse_keyword("end")) { } else if (try_parse_keyword("end")) {
expect_eol(); expect_eol();
@ -606,6 +688,11 @@ struct RTLILFrontendWorker {
error("dangling attribute"); error("dangling attribute");
RTLIL::SigSpec s1 = parse_sigspec(); RTLIL::SigSpec s1 = parse_sigspec();
RTLIL::SigSpec s2 = parse_sigspec(); RTLIL::SigSpec s2 = parse_sigspec();
if (flag_legalize) {
int min_size = std::min(s1.size(), s2.size());
s1 = s1.extract(0, min_size);
s2 = s2.extract(0, min_size);
}
current_module->connect(std::move(s1), std::move(s2)); current_module->connect(std::move(s1), std::move(s2));
expect_eol(); expect_eol();
} }
@ -682,8 +769,13 @@ struct RTLILFrontendWorker {
RTLIL::IdString proc_name = parse_id(); RTLIL::IdString proc_name = parse_id();
expect_eol(); expect_eol();
if (current_module->processes.count(proc_name) != 0) if (current_module->processes.count(proc_name) != 0) {
error("RTLIL error: redefinition of process %s.", proc_name); if (flag_legalize) {
log("Legalizing redefinition of process %s.\n", proc_name);
current_module->remove(current_module->processes.at(proc_name));
} else
error("RTLIL error: redefinition of process %s.", proc_name);
}
RTLIL::Process *proc = current_module->addProcess(std::move(proc_name)); RTLIL::Process *proc = current_module->addProcess(std::move(proc_name));
proc->attributes = std::move(attrbuf); proc->attributes = std::move(attrbuf);
@ -804,6 +896,11 @@ struct RTLILFrontend : public Frontend {
log(" -lib\n"); log(" -lib\n");
log(" only create empty blackbox modules\n"); log(" only create empty blackbox modules\n");
log("\n"); log("\n");
log(" -legalize\n");
log(" prevent semantic errors (e.g. reference to unknown wire, redefinition of wire/cell)\n");
log(" by deterministically rewriting the input into something valid. Useful when using\n");
log(" fuzzing to generate random but valid RTLIL.\n");
log("\n");
} }
void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
{ {
@ -828,6 +925,10 @@ struct RTLILFrontend : public Frontend {
worker.flag_lib = true; worker.flag_lib = true;
continue; continue;
} }
if (arg == "-legalize") {
worker.flag_legalize = true;
continue;
}
break; break;
} }
extra_args(f, filename, args, argidx); extra_args(f, filename, args, argidx);

View File

@ -3039,6 +3039,13 @@ void RTLIL::Module::remove(RTLIL::Cell *cell)
} }
} }
void RTLIL::Module::remove(RTLIL::Memory *memory)
{
log_assert(memories.count(memory->name) != 0);
memories.erase(memory->name);
delete memory;
}
void RTLIL::Module::remove(RTLIL::Process *process) void RTLIL::Module::remove(RTLIL::Process *process)
{ {
log_assert(processes.count(process->name) != 0); log_assert(processes.count(process->name) != 0);

View File

@ -2137,6 +2137,8 @@ public:
} }
RTLIL::ObjRange<RTLIL::Wire*> wires() { return RTLIL::ObjRange<RTLIL::Wire*>(&wires_, &refcount_wires_); } RTLIL::ObjRange<RTLIL::Wire*> wires() { return RTLIL::ObjRange<RTLIL::Wire*>(&wires_, &refcount_wires_); }
int wires_size() const { return wires_.size(); }
RTLIL::Wire* wire_at(int index) const { return wires_.element(index)->second; }
RTLIL::ObjRange<RTLIL::Cell*> cells() { return RTLIL::ObjRange<RTLIL::Cell*>(&cells_, &refcount_cells_); } RTLIL::ObjRange<RTLIL::Cell*> cells() { return RTLIL::ObjRange<RTLIL::Cell*>(&cells_, &refcount_cells_); }
void add(RTLIL::Binding *binding); void add(RTLIL::Binding *binding);
@ -2144,6 +2146,7 @@ public:
// Removing wires is expensive. If you have to remove wires, remove them all at once. // Removing wires is expensive. If you have to remove wires, remove them all at once.
void remove(const pool<RTLIL::Wire*> &wires); void remove(const pool<RTLIL::Wire*> &wires);
void remove(RTLIL::Cell *cell); void remove(RTLIL::Cell *cell);
void remove(RTLIL::Memory *memory);
void remove(RTLIL::Process *process); void remove(RTLIL::Process *process);
void rename(RTLIL::Wire *wire, RTLIL::IdString new_name); void rename(RTLIL::Wire *wire, RTLIL::IdString new_name);

View File

@ -5,6 +5,7 @@ endif
OBJS += passes/cmds/add.o OBJS += passes/cmds/add.o
OBJS += passes/cmds/delete.o OBJS += passes/cmds/delete.o
OBJS += passes/cmds/design.o OBJS += passes/cmds/design.o
OBJS += passes/cmds/design_equal.o
OBJS += passes/cmds/select.o OBJS += passes/cmds/select.o
OBJS += passes/cmds/show.o OBJS += passes/cmds/show.o
OBJS += passes/cmds/viz.o OBJS += passes/cmds/viz.o

352
passes/cmds/design_equal.cc Normal file
View File

@ -0,0 +1,352 @@
/*
* 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/rtlil.h"
YOSYS_NAMESPACE_BEGIN
class ModuleComparator
{
RTLIL::Module *mod_a;
RTLIL::Module *mod_b;
public:
ModuleComparator(RTLIL::Module *mod_a, RTLIL::Module *mod_b) : mod_a(mod_a), mod_b(mod_b) {}
bool compare_sigbit(const RTLIL::SigBit &a, const RTLIL::SigBit &b)
{
if (a.wire == nullptr && b.wire == nullptr)
return a.data == b.data;
if (a.wire != nullptr && b.wire != nullptr)
return a.wire->name == b.wire->name && a.offset == b.offset;
return false;
}
bool compare_sigspec(const RTLIL::SigSpec &a, const RTLIL::SigSpec &b)
{
if (a.size() != b.size()) return false;
auto it_a = a.begin(), it_b = b.begin();
for (; it_a != a.end(); ++it_a, ++it_b) {
if (!compare_sigbit(*it_a, *it_b)) return false;
}
return true;
}
std::string compare_attributes(const RTLIL::AttrObject *a, const RTLIL::AttrObject *b)
{
for (const auto &it : a->attributes) {
if (b->attributes.count(it.first) == 0)
return "missing attribute " + std::string(log_id(it.first)) + " in second design";
if (it.second != b->attributes.at(it.first))
return "attribute " + std::string(log_id(it.first)) + " mismatch: " + log_const(it.second) + " != " + log_const(b->attributes.at(it.first));
}
for (const auto &it : b->attributes)
if (a->attributes.count(it.first) == 0)
return "missing attribute " + std::string(log_id(it.first)) + " in first design";
return "";
}
std::string compare_wires(const RTLIL::Wire *a, const RTLIL::Wire *b)
{
if (a->name != b->name)
return "name mismatch: " + std::string(log_id(a->name)) + " != " + log_id(b->name);
if (a->width != b->width)
return "width mismatch: " + std::to_string(a->width) + " != " + std::to_string(b->width);
if (a->start_offset != b->start_offset)
return "start_offset mismatch: " + std::to_string(a->start_offset) + " != " + std::to_string(b->start_offset);
if (a->port_id != b->port_id)
return "port_id mismatch: " + std::to_string(a->port_id) + " != " + std::to_string(b->port_id);
if (a->port_input != b->port_input)
return "port_input mismatch: " + std::to_string(a->port_input) + " != " + std::to_string(b->port_input);
if (a->port_output != b->port_output)
return "port_output mismatch: " + std::to_string(a->port_output) + " != " + std::to_string(b->port_output);
if (a->upto != b->upto)
return "upto mismatch: " + std::to_string(a->upto) + " != " + std::to_string(b->upto);
if (a->is_signed != b->is_signed)
return "is_signed mismatch: " + std::to_string(a->is_signed) + " != " + std::to_string(b->is_signed);
if (std::string mismatch = compare_attributes(a, b); !mismatch.empty())
return mismatch;
return "";
}
void check_wires()
{
for (const auto &it : mod_a->wires_) {
if (mod_b->wires_.count(it.first) == 0)
log_error("Module %s missing wire %s in second design.\n", log_id(mod_a->name), log_id(it.first));
if (std::string mismatch = compare_wires(it.second, mod_b->wires_.at(it.first)); !mismatch.empty())
log_error("Module %s wire %s %s.\n", log_id(mod_a->name), log_id(it.first), mismatch);
}
for (const auto &it : mod_b->wires_)
if (mod_a->wires_.count(it.first) == 0)
log_error("Module %s missing wire %s in first design.\n", log_id(mod_b->name), log_id(it.first));
}
std::string compare_memories(const RTLIL::Memory *a, const RTLIL::Memory *b)
{
if (a->name != b->name)
return "name mismatch: " + std::string(log_id(a->name)) + " != " + log_id(b->name);
if (a->width != b->width)
return "width mismatch: " + std::to_string(a->width) + " != " + std::to_string(b->width);
if (a->start_offset != b->start_offset)
return "start_offset mismatch: " + std::to_string(a->start_offset) + " != " + std::to_string(b->start_offset);
if (a->size != b->size)
return "size mismatch: " + std::to_string(a->size) + " != " + std::to_string(b->size);
if (std::string mismatch = compare_attributes(a, b); !mismatch.empty())
return mismatch;
return "";
}
std::string compare_cells(const RTLIL::Cell *a, const RTLIL::Cell *b)
{
if (a->name != b->name)
return "name mismatch: " + std::string(log_id(a->name)) + " != " + log_id(b->name);
if (a->type != b->type)
return "type mismatch: " + std::string(log_id(a->type)) + " != " + log_id(b->type);
if (std::string mismatch = compare_attributes(a, b); !mismatch.empty())
return mismatch;
for (const auto &it : a->parameters) {
if (b->parameters.count(it.first) == 0)
return "parameter mismatch: missing parameter " + std::string(log_id(it.first)) + " in second design";
if (it.second != b->parameters.at(it.first))
return "parameter mismatch: " + std::string(log_id(it.first)) + " mismatch: " + log_const(it.second) + " != " + log_const(b->parameters.at(it.first));
}
for (const auto &it : b->parameters)
if (a->parameters.count(it.first) == 0)
return "parameter mismatch: missing parameter " + std::string(log_id(it.first)) + " in first design";
for (const auto &it : a->connections()) {
if (b->connections().count(it.first) == 0)
return "connection mismatch: missing connection " + std::string(log_id(it.first)) + " in second design";
if (!compare_sigspec(it.second, b->connections().at(it.first)))
return "connection " + std::string(log_id(it.first)) + " mismatch: " + log_signal(it.second) + " != " + log_signal(b->connections().at(it.first));
}
for (const auto &it : b->connections())
if (a->connections().count(it.first) == 0)
return "connection mismatch: missing connection " + std::string(log_id(it.first)) + " in first design";
return "";
}
void check_cells()
{
for (const auto &it : mod_a->cells_) {
if (mod_b->cells_.count(it.first) == 0)
log_error("Module %s missing cell %s in second design.\n", log_id(mod_a->name), log_id(it.first));
if (std::string mismatch = compare_cells(it.second, mod_b->cells_.at(it.first)); !mismatch.empty())
log_error("Module %s cell %s %s.\n", log_id(mod_a->name), log_id(it.first), mismatch);
}
for (const auto &it : mod_b->cells_)
if (mod_a->cells_.count(it.first) == 0)
log_error("Module %s missing cell %s in first design.\n", log_id(mod_b->name), log_id(it.first));
}
void check_memories()
{
for (const auto &it : mod_a->memories) {
if (mod_b->memories.count(it.first) == 0)
log_error("Module %s missing memory %s in second design.\n", log_id(mod_a->name), log_id(it.first));
if (std::string mismatch = compare_memories(it.second, mod_b->memories.at(it.first)); !mismatch.empty())
log_error("Module %s memory %s %s.\n", log_id(mod_a->name), log_id(it.first), mismatch);
}
for (const auto &it : mod_b->memories)
if (mod_a->memories.count(it.first) == 0)
log_error("Module %s missing memory %s in first design.\n", log_id(mod_b->name), log_id(it.first));
}
std::string compare_case_rules(const RTLIL::CaseRule *a, const RTLIL::CaseRule *b)
{
if (std::string mismatch = compare_attributes(a, b); !mismatch.empty()) return mismatch;
if (a->compare.size() != b->compare.size())
return "compare size mismatch: " + std::to_string(a->compare.size()) + " != " + std::to_string(b->compare.size());
for (size_t i = 0; i < a->compare.size(); i++)
if (!compare_sigspec(a->compare[i], b->compare[i]))
return "compare " + std::to_string(i) + " mismatch: " + log_signal(a->compare[i]) + " != " + log_signal(b->compare[i]);
if (a->actions.size() != b->actions.size())
return "actions size mismatch: " + std::to_string(a->actions.size()) + " != " + std::to_string(b->actions.size());
for (size_t i = 0; i < a->actions.size(); i++) {
if (!compare_sigspec(a->actions[i].first, b->actions[i].first))
return "action " + std::to_string(i) + " first mismatch: " + log_signal(a->actions[i].first) + " != " + log_signal(b->actions[i].first);
if (!compare_sigspec(a->actions[i].second, b->actions[i].second))
return "action " + std::to_string(i) + " second mismatch: " + log_signal(a->actions[i].second) + " != " + log_signal(b->actions[i].second);
}
if (a->switches.size() != b->switches.size())
return "switches size mismatch: " + std::to_string(a->switches.size()) + " != " + std::to_string(b->switches.size());
for (size_t i = 0; i < a->switches.size(); i++)
if (std::string mismatch = compare_switch_rules(a->switches[i], b->switches[i]); !mismatch.empty())
return "switch " + std::to_string(i) + " " + mismatch;
return "";
}
std::string compare_switch_rules(const RTLIL::SwitchRule *a, const RTLIL::SwitchRule *b)
{
if (std::string mismatch = compare_attributes(a, b); !mismatch.empty())
return mismatch;
if (!compare_sigspec(a->signal, b->signal))
return "signal mismatch: " + log_signal(a->signal) + " != " + log_signal(b->signal);
if (a->cases.size() != b->cases.size())
return "cases size mismatch: " + std::to_string(a->cases.size()) + " != " + std::to_string(b->cases.size());
for (size_t i = 0; i < a->cases.size(); i++)
if (std::string mismatch = compare_case_rules(a->cases[i], b->cases[i]); !mismatch.empty())
return "case " + std::to_string(i) + " " + mismatch;
return "";
}
std::string compare_sync_rules(const RTLIL::SyncRule *a, const RTLIL::SyncRule *b)
{
if (a->type != b->type)
return "type mismatch: " + std::to_string(a->type) + " != " + std::to_string(b->type);
if (!compare_sigspec(a->signal, b->signal))
return "signal mismatch: " + log_signal(a->signal) + " != " + log_signal(b->signal);
if (a->actions.size() != b->actions.size())
return "actions size mismatch: " + std::to_string(a->actions.size()) + " != " + std::to_string(b->actions.size());
for (size_t i = 0; i < a->actions.size(); i++) {
if (!compare_sigspec(a->actions[i].first, b->actions[i].first))
return "action " + std::to_string(i) + " first mismatch: " + log_signal(a->actions[i].first) + " != " + log_signal(b->actions[i].first);
if (!compare_sigspec(a->actions[i].second, b->actions[i].second))
return "action " + std::to_string(i) + " second mismatch: " + log_signal(a->actions[i].second) + " != " + log_signal(b->actions[i].second);
}
if (a->mem_write_actions.size() != b->mem_write_actions.size())
return "mem_write_actions size mismatch: " + std::to_string(a->mem_write_actions.size()) + " != " + std::to_string(b->mem_write_actions.size());
for (size_t i = 0; i < a->mem_write_actions.size(); i++) {
const auto &ma = a->mem_write_actions[i];
const auto &mb = b->mem_write_actions[i];
if (ma.memid != mb.memid)
return "mem_write_actions " + std::to_string(i) + " memid mismatch: " + log_id(ma.memid) + " != " + log_id(mb.memid);
if (!compare_sigspec(ma.address, mb.address))
return "mem_write_actions " + std::to_string(i) + " address mismatch: " + log_signal(ma.address) + " != " + log_signal(mb.address);
if (!compare_sigspec(ma.data, mb.data))
return "mem_write_actions " + std::to_string(i) + " data mismatch: " + log_signal(ma.data) + " != " + log_signal(mb.data);
if (!compare_sigspec(ma.enable, mb.enable))
return "mem_write_actions " + std::to_string(i) + " enable mismatch: " + log_signal(ma.enable) + " != " + log_signal(mb.enable);
if (ma.priority_mask != mb.priority_mask)
return "mem_write_actions " + std::to_string(i) + " priority_mask mismatch: " + log_const(ma.priority_mask) + " != " + log_const(mb.priority_mask);
if (std::string mismatch = compare_attributes(&ma, &mb); !mismatch.empty())
return "mem_write_actions " + std::to_string(i) + " " + mismatch;
}
return "";
}
std::string compare_processes(const RTLIL::Process *a, const RTLIL::Process *b)
{
if (a->name != b->name) return "name mismatch: " + std::string(log_id(a->name)) + " != " + log_id(b->name);
if (std::string mismatch = compare_attributes(a, b); !mismatch.empty())
return mismatch;
if (std::string mismatch = compare_case_rules(&a->root_case, &b->root_case); !mismatch.empty())
return "case rule " + mismatch;
if (a->syncs.size() != b->syncs.size())
return "sync count mismatch: " + std::to_string(a->syncs.size()) + " != " + std::to_string(b->syncs.size());
for (size_t i = 0; i < a->syncs.size(); i++)
if (std::string mismatch = compare_sync_rules(a->syncs[i], b->syncs[i]); !mismatch.empty())
return "sync " + std::to_string(i) + " " + mismatch;
return "";
}
void check_processes()
{
for (auto &it : mod_a->processes) {
if (mod_b->processes.count(it.first) == 0)
log_error("Module %s missing process %s in second design.\n", log_id(mod_a->name), log_id(it.first));
if (std::string mismatch = compare_processes(it.second, mod_b->processes.at(it.first)); !mismatch.empty())
log_error("Module %s process %s %s.\n", log_id(mod_a->name), log_id(it.first), mismatch.c_str());
}
for (auto &it : mod_b->processes)
if (mod_a->processes.count(it.first) == 0)
log_error("Module %s missing process %s in first design.\n", log_id(mod_b->name), log_id(it.first));
}
void check_connections()
{
const auto &conns_a = mod_a->connections();
const auto &conns_b = mod_b->connections();
if (conns_a.size() != conns_b.size()) {
log_error("Module %s connection count differs: %zu != %zu\n", log_id(mod_a->name), conns_a.size(), conns_b.size());
} else {
for (size_t i = 0; i < conns_a.size(); i++) {
if (!compare_sigspec(conns_a[i].first, conns_b[i].first))
log_error("Module %s connection %zu LHS %s != %s.\n", log_id(mod_a->name), i, log_signal(conns_a[i].first), log_signal(conns_b[i].first));
if (!compare_sigspec(conns_a[i].second, conns_b[i].second))
log_error("Module %s connection %zu RHS %s != %s.\n", log_id(mod_a->name), i, log_signal(conns_a[i].second), log_signal(conns_b[i].second));
}
}
}
void check()
{
if (mod_a->name != mod_b->name)
log_error("Modules have different names: %s != %s\n", log_id(mod_a->name), log_id(mod_b->name));
if (std::string mismatch = compare_attributes(mod_a, mod_b); !mismatch.empty())
log_error("Module %s %s.\n", log_id(mod_a->name), mismatch);
check_wires();
check_cells();
check_memories();
check_connections();
check_processes();
}
};
struct DesignEqualPass : public Pass {
DesignEqualPass() : Pass("design_equal", "check if two designs are the same") { }
void help() override
{
log("\n");
log(" design_equal <name>\n");
log("\n");
log("Compare the current design with the design previously saved under the given\n");
log("name. Abort with an error if the designs are different.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
if (args.size() != 2)
log_cmd_error("Missing argument.\n");
std::string check_name = args[1];
if (saved_designs.count(check_name) == 0)
log_cmd_error("No saved design '%s' found!\n", check_name.c_str());
RTLIL::Design *other = saved_designs.at(check_name);
for (auto &it : design->modules_) {
RTLIL::Module *mod = it.second;
if (!other->has(mod->name))
log_error("Second design missing module %s.\n", log_id(mod->name));
ModuleComparator cmp(mod, other->module(mod->name));
cmp.check();
}
for (auto &it : other->modules_) {
RTLIL::Module *mod = it.second;
if (!design->has(mod->name))
log_error("First design missing module %s.\n", log_id(mod->name));
}
log("Designs are identical.\n");
}
} DesignEqualPass;
YOSYS_NAMESPACE_END

106
tests/pass-fuzzing.md Normal file
View File

@ -0,0 +1,106 @@
Suppose you're making significant changes to a pass that should not change
the pass's output in any way. It might be useful to run a large number of
automatically generated tests to try to find bugs where the output has
changed. This document describes how to do that.
Basically we're going to use [AFL++](https://github.com/AFLplusplus/AFLplusplus) with the
[Grammar-Mutator](https://github.com/AFLplusplus/Grammar-Mutator) plugin to generate
RTLIL testcases. For each testcase, we run a Yosys script that applies both the old and new
implementation of the pass to the same design and compares the results. Testcase
generation is coverage-guided, i.e. the fuzzer will try to find testcases that exercise all
code in the old and new implementation of the pass (and in the RTLIL parser).
## Setup
These instructions clone tools into subdirectories of your home directory. They assume
you have a Yosys checkout under `$HOME/yosys`, and that you're testing the `opt_merge` pass.
They have been tested with AFL++ revision 68b492b2c7725816068718ef9437b72b40e67519 and Grammar-Mutator revision 05d8f537f8d656f0754e7ad5dcc653c42cb4f8ff.
Clone and build AFL++ and Grammar-Mutator:
```
cd $HOME
git clone https://github.com/AFLplusplus/AFLplusplus.git
git -C AFLplusplus checkout stable
git clone https://github.com/AFLplusplus/Grammar-Mutator.git
git -C Grammar-Mutator checkout stable
```
Check that `rtlil-fuzz-grammar.json` generates RTLIL constructs relevant to your pass.
Currently it's quite simple and generates a limited set of cells and wires; you may need to
extend it to generate different kinds of cells and other RTLIL constructs (e.g. `proc`).
Build AFL++ and Grammar-Mutator:
```
make -C $HOME/AFLplusplus -j all
make -C $HOME/Grammar-Mutator -j GRAMMAR_FILE=$HOME/yosys/tests/tools/rtlil-fuzz-grammar.json
```
Create a Yosys commit that adds the old version of your pass as a new command, e.g. copy
`opt_merge.cc` into `old_opt_merge.cc` and change the name of the command to `old_opt_merge`.
[Here's](https://github.com/YosysHQ/yosys/commit/827cd8c998f3e455b14ac990a3159030ddc19b21) an example.
You may also need to patch in [this commit](https://github.com/YosysHQ/yosys/commit/121c52f514c4ca282b4e6b3b14f71184f3849ddf) to work around a bug involving `std::reverse` on
empty vectors in the RTLIL parser when building with fuzzing instrumentation.
I think this is a clang++ bug so hopefully it will get fixed eventually and that patch will not be
necessary.
Rebuild Yosys with the AFL++ compiler wrapper. This assumes your config builds Yosys with clang++.
```
(cd $HOME/yosys; patch -lp1 << EOF)
diff --git a/Makefile b/Makefile
index 9c361294d..c9a98f74c 100644
--- a/Makefile
+++ b/Makefile
@@ -238,7 +238,7 @@
LTOFLAGS := $(GCC_LTO)
ifeq ($(CONFIG),clang)
-CXX = clang++
+CXX = $(HOME)/AFLplusplus/afl-c++
CXXFLAGS += -std=$(CXXSTD) $(OPT_LEVEL)
ifeq ($(ENABLE_LTO),1)
LINKFLAGS += -fuse-ld=lld
EOF
make -C yosys clean && make -C yosys -j
```
You probably need to configure coredumps to work normally instead of going through some OS service:
```
echo core | sudo tee /proc/sys/kernel/core_pattern
```
## Running the fuzzer
Generate some initial testcases using Grammar-Mutator:
```
(cd $HOME/Grammar-Mutator; rm -rf seeds trees; ./grammar_generator-rtlil 100 1000 ./seeds ./trees)
```
Now run AFL++.
```
(cd $HOME/Grammar-Mutator; \
AFL_CUSTOM_MUTATOR_LIBRARY=./libgrammarmutator-rtlil.so \
AFL_CUSTOM_MUTATOR_ONLY=1 \
AFL_BENCH_UNTIL_CRASH=1 \
YOSYS_WORK_UNITS_PER_THREAD=1 \
YOSYS_ABORT_ON_LOG_ERROR=1 \
$HOME/AFLplusplus/afl-fuzz -t 5000 -m none -i seeds -o out -- \
$HOME/yosys/yosys -p 'read_rtlil -legalize @@; design -save init; old_opt_merge; design -save old; design -load init; opt_merge; design_equal old' \
)
```
This will run the fuzzer until the first crash (including any pass output mismatches) and then stop.
Or if you're lucky, the fuzzer will run indefinitely. This uses very little parallelism; if it doesn't find any errors right away, you can increase the test throughput by running AFL++ in parallel using the instructions [here](https://aflplus.plus/docs/parallel_fuzzing).
## Working with fuzz test failures
Any failing testcases will be dropped in `$HOME/Grammar-Mutator/out/default/crashes`.
Run `yosys -p 'read_rtlil -legalize ... ; dump'` to get the testcase as legalized RTLIL.
## Notes on generating semantically valid RTLIL
`Grammar-Mutator` generates RTLIL files according to the context-free grammar in `rtlil-fuzz-grammar.json`.
However, the testcases must also be semantically valid, e.g. references to wires should only refer to
wires that actually exist. These constraints cannot reasonably be expresed in a CFG. Therefore we
have added a `-legalize` option to the `read_rtlil` command. When `-legalize` is set, when `read_rtlil`
detects a failed semantic check, instead of erroring out it emits a warning and patches the incoming RTLIL
to make it valid.

View File

@ -0,0 +1,104 @@
{
"<MODULE>": [
[
"module \\test\n",
"<WIRE>", "<WIRES>",
"<CELLS>",
"<CONNECTS>",
"end\n"
]
],
"<WIRE>": [ [ " wire width ", "<WIDTH>", " ", "<WIRE_MODE>", " ", "<WIRE_ID>", "\n" ] ],
"<WIDTH>": [ [ "1" ], [ "2" ], [ "3" ], [ "4" ], [ "32" ], [ "128" ] ],
"<WIRE_MODE>": [ [ "input ", "<PORT_ID>" ], [ "output ", "<PORT_ID>" ], [ "inout ", "<PORT_ID>" ], [] ],
"<CELL>": [
[
" cell $not ", "<CELL_ID>", "\n",
" parameter \\A_SIGNED 0\n",
" parameter \\A_WIDTH 0\n",
" parameter \\Y_WIDTH 0\n",
" connect \\A ", "<SIGSPEC>", "\n",
" connect \\Y ", "<SIGSPEC>", "\n",
" end\n"
],
[
" cell $and ", "<CELL_ID>", "\n",
" parameter \\A_SIGNED 0\n",
" parameter \\B_SIGNED 0\n",
" parameter \\A_WIDTH 0\n",
" parameter \\B_WIDTH 0\n",
" parameter \\Y_WIDTH 0\n",
" connect \\A ", "<SIGSPEC>", "\n",
" connect \\B ", "<SIGSPEC>", "\n",
" connect \\Y ", "<SIGSPEC>", "\n",
" end\n"
],
[
" cell $or ", "<CELL_ID>", "\n",
" parameter \\A_SIGNED 0\n",
" parameter \\B_SIGNED 0\n",
" parameter \\A_WIDTH 0\n",
" parameter \\B_WIDTH 0\n",
" parameter \\Y_WIDTH 0\n",
" connect \\A ", "<SIGSPEC>", "\n",
" connect \\B ", "<SIGSPEC>", "\n",
" connect \\Y ", "<SIGSPEC>", "\n",
" end\n"
],
[
" cell $xor ", "<CELL_ID>", "\n",
" parameter \\A_SIGNED 0\n",
" parameter \\B_SIGNED 0\n",
" parameter \\A_WIDTH 0\n",
" parameter \\B_WIDTH 0\n",
" parameter \\Y_WIDTH 0\n",
" connect \\A ", "<SIGSPEC>", "\n",
" connect \\B ", "<SIGSPEC>", "\n",
" connect \\Y ", "<SIGSPEC>", "\n",
" end\n"
],
[
" cell ", "<BLACKBOX_CELL>", " ", "<CELL_ID>", "\n",
" connect \\A ", "<SIGSPEC>", "\n",
" connect \\Y ", "<SIGSPEC>", "\n",
" end\n"
],
[
" cell ", "<BLACKBOX_CELL>", " ", "<CELL_ID>", "\n",
" connect \\A ", "<SIGSPEC>", "\n",
" connect \\B ", "<SIGSPEC>", "\n",
" connect \\Y ", "<SIGSPEC>", "\n",
" end\n"
]
],
"<WIRE_ID>": [ [ "\\wire_a" ], [ "\\wire_b" ], [ "\\wire_c" ], [ "\\wire_d" ], [ "\\wire_e" ], [ "\\wire_f" ], [ "\\wire_g" ], [ "\\wire_h" ], [ "\\wire_i" ], [ "\\wire_j" ] ],
"<CELL_ID>": [ [ "\\cell_a" ], [ "\\cell_b" ], [ "\\cell_c" ], [ "\\cell_d" ], [ "\\cell_e" ], [ "\\cell_f" ], [ "\\cell_g" ], [ "\\cell_h" ], [ "\\cell_i" ], [ "\\cell_j" ] ],
"<BLACKBOX_CELL>": [ [ "\\bb1" ], [ "\\bb2" ] ],
"<SIGSPEC>": [
[ "<WIRE_ID>", " " ],
[ "{", "<SIGSPEC>", " ", "<SIGSPECS>", "}" ],
[ "<CONST>" ],
[ "<SIGSPEC>", "[", "<BIT_OFFSET>", "]" ],
[ "<SIGSPEC>", "[", "<BIT_OFFSET>", ":", "<BIT_OFFSET>", "]" ]
],
"<CONST>": [
[ "0'", "<BITS>" ],
[ "1'", "<BITS>" ],
[ "2'", "<BITS>" ],
[ "3'", "<BITS>" ],
[ "4'", "<BITS>" ],
[ "31'", "<BITS>" ],
[ "32'", "<BITS>" ],
[ "128'", "<BITS>" ]
],
"<BIT>": [ [ "0" ], [ "1" ], [ "x" ], [ "z" ], [ "-" ], [ "m" ] ],
"<BIT_OFFSET>": [ "0", "1", "2", "3", "31", "32" ],
"<PORT_ID>": [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" ],
"<CONNECT>": [ [ " connect ", "<SIGSPEC>", " ", "<SIGSPEC>", "\n" ] ],
"<WIRES>": [ [ ], [ "<WIRE>", "<WIRES>" ] ],
"<CELLS>": [ [ ], [ "<CELL>", "<CELLS>" ] ],
"<BITS>": [ [ ], [ "<BIT>", "<BITS>" ] ],
"<CONNECTS>": [ [ ], [ "<CONNECT>", "<CONNECTS>" ] ],
"<SIGSPECS>": [ [ ], [ "<SIGSPEC>", " ", "<SIGSPECS>" ] ]
}

View File

@ -0,0 +1,22 @@
logger -expect error "Second design missing module top_renamed" 1
read_rtlil <<EOT
module \top
wire width 1 input 1 \a
wire width 1 output 2 \y
connect \y \a
end
EOT
design -save golden
design -reset
read_rtlil <<EOT
module \top_renamed
wire width 1 input 1 \a
wire width 1 output 2 \y
connect \y \a
end
EOT
design_equal golden

View File

@ -0,0 +1,17 @@
read_rtlil <<EOT
module \top
wire width 1 input 1 \a
wire width 1 output 2 \y
connect \y \a
end
EOT
design -save golden
design_equal golden
design -save copy
design_equal copy
design -load golden
design_equal golden
design_equal copy