From 772d821fb0f68bcf0c2baf321ebd1aa64965fe42 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 19 Dec 2025 18:30:17 +0100 Subject: [PATCH 01/12] opt_expr: reindent test --- tests/opt/opt_expr_combined_assign.ys | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/opt/opt_expr_combined_assign.ys b/tests/opt/opt_expr_combined_assign.ys index b18923c7b..f84978a0a 100644 --- a/tests/opt/opt_expr_combined_assign.ys +++ b/tests/opt/opt_expr_combined_assign.ys @@ -5,7 +5,7 @@ initial begin a |= i; a |= j; end - assign o = a; + assign o = a; endmodule EOT proc @@ -19,10 +19,10 @@ read_verilog -sv < Date: Fri, 19 Dec 2025 18:32:06 +0100 Subject: [PATCH 02/12] opt_expr: avoid multiple drivers issue #4792 in combined assign tests --- tests/opt/opt_expr_combined_assign.ys | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/opt/opt_expr_combined_assign.ys b/tests/opt/opt_expr_combined_assign.ys index f84978a0a..823ca959a 100644 --- a/tests/opt/opt_expr_combined_assign.ys +++ b/tests/opt/opt_expr_combined_assign.ys @@ -1,7 +1,8 @@ read_verilog -sv < Date: Tue, 16 Dec 2025 01:39:39 +0000 Subject: [PATCH 03/12] Implement design_equal command --- passes/cmds/Makefile.inc | 1 + passes/cmds/design_equal.cc | 352 +++++++++++++++++++++++++++++ tests/various/design_equal_fail.ys | 22 ++ tests/various/design_equal_pass.ys | 17 ++ 4 files changed, 392 insertions(+) create mode 100644 passes/cmds/design_equal.cc create mode 100644 tests/various/design_equal_fail.ys create mode 100644 tests/various/design_equal_pass.ys diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index b1b1383b1..dc12c92c2 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -5,6 +5,7 @@ endif OBJS += passes/cmds/add.o OBJS += passes/cmds/delete.o OBJS += passes/cmds/design.o +OBJS += passes/cmds/design_equal.o OBJS += passes/cmds/select.o OBJS += passes/cmds/show.o OBJS += passes/cmds/viz.o diff --git a/passes/cmds/design_equal.cc b/passes/cmds/design_equal.cc new file mode 100644 index 000000000..a949db9ff --- /dev/null +++ b/passes/cmds/design_equal.cc @@ -0,0 +1,352 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * 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 \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 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 diff --git a/tests/various/design_equal_fail.ys b/tests/various/design_equal_fail.ys new file mode 100644 index 000000000..330d8d838 --- /dev/null +++ b/tests/various/design_equal_fail.ys @@ -0,0 +1,22 @@ +logger -expect error "Second design missing module top_renamed" 1 + +read_rtlil < Date: Wed, 17 Dec 2025 03:11:06 +0000 Subject: [PATCH 04/12] Add -legalize option to read_rtlil --- frontends/rtlil/rtlil_frontend.cc | 141 +++++++++++++++++++++++++----- kernel/rtlil.cc | 7 ++ kernel/rtlil.h | 3 + 3 files changed, 131 insertions(+), 20 deletions(-) diff --git a/frontends/rtlil/rtlil_frontend.cc b/frontends/rtlil/rtlil_frontend.cc index 271962725..a1412d983 100644 --- a/frontends/rtlil/rtlil_frontend.cc +++ b/frontends/rtlil/rtlil_frontend.cc @@ -40,6 +40,7 @@ struct RTLILFrontendWorker { bool flag_nooverwrite = false; bool flag_overwrite = false; bool flag_lib = false; + bool flag_legalize = false; int line_num; std::string line_buf; @@ -322,6 +323,17 @@ struct RTLILFrontendWorker { 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::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 sig; @@ -339,8 +351,12 @@ struct RTLILFrontendWorker { std::optional id = try_parse_id(); if (id.has_value()) { RTLIL::Wire *wire = current_module->wire(*id); - if (wire == nullptr) - error("Wire `%s' not found.", *id); + if (wire == nullptr) { + if (flag_legalize) + wire = legalize_wire(*id); + else + error("Wire `%s' not found.", *id); + } sig = RTLIL::SigSpec(wire); } else { sig = RTLIL::SigSpec(parse_const()); @@ -349,17 +365,44 @@ struct RTLILFrontendWorker { while (try_parse_char('[')) { int left = parse_integer(); - if (left >= sig.size() || left < 0) - error("bit index %d out of range", left); + if (left >= sig.size() || left < 0) { + 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(':')) { int right = parse_integer(); - if (right < 0) - error("bit index %d out of range", right); - if (left < right) - error("invalid slice [%d:%d]", left, right); - sig = sig.extract(right, left-right+1); + if (right < 0) { + if (flag_legalize) { + log("Legalizing bit index %d to %d.\n", right, 0); + right = 0; + } 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 { - 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(']'); } @@ -476,8 +519,14 @@ struct RTLILFrontendWorker { { std::optional id = try_parse_id(); if (id.has_value()) { - if (current_module->wire(*id) != nullptr) - error("RTLIL error: redefinition of wire %s.", *id); + if (current_module->wire(*id) != nullptr) { + if (flag_legalize) { + log("Legalizing redefinition of wire %s.\n", *id); + pool 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)); break; } @@ -528,8 +577,13 @@ struct RTLILFrontendWorker { { std::optional id = try_parse_id(); if (id.has_value()) { - if (current_module->memories.count(*id) != 0) - error("RTLIL error: redefinition of memory %s.", *id); + if (current_module->memories.count(*id) != 0) { + 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); break; } @@ -551,14 +605,36 @@ struct RTLILFrontendWorker { 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 ¶m = 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() { RTLIL::IdString cell_type = parse_id(); RTLIL::IdString cell_name = parse_id(); expect_eol(); - if (current_module->cell(cell_name) != nullptr) - error("RTLIL error: redefinition of cell %s.", cell_name); + if (current_module->cell(cell_name) != nullptr) { + 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); cell->attributes = std::move(attrbuf); @@ -587,9 +663,15 @@ struct RTLILFrontendWorker { expect_eol(); } else if (try_parse_keyword("connect")) { RTLIL::IdString port_name = parse_id(); - if (cell->hasPort(port_name)) - error("RTLIL error: redefinition of cell port %s.", port_name); + if (cell->hasPort(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()); + if (flag_legalize) + legalize_width_parameter(cell, port_name); expect_eol(); } else if (try_parse_keyword("end")) { expect_eol(); @@ -606,6 +688,11 @@ struct RTLILFrontendWorker { error("dangling attribute"); RTLIL::SigSpec s1 = 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)); expect_eol(); } @@ -682,8 +769,13 @@ struct RTLILFrontendWorker { RTLIL::IdString proc_name = parse_id(); expect_eol(); - if (current_module->processes.count(proc_name) != 0) - error("RTLIL error: redefinition of process %s.", proc_name); + if (current_module->processes.count(proc_name) != 0) { + 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)); proc->attributes = std::move(attrbuf); @@ -804,6 +896,11 @@ struct RTLILFrontend : public Frontend { log(" -lib\n"); log(" only create empty blackbox modules\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 args, RTLIL::Design *design) override { @@ -828,6 +925,10 @@ struct RTLILFrontend : public Frontend { worker.flag_lib = true; continue; } + if (arg == "-legalize") { + worker.flag_legalize = true; + continue; + } break; } extra_args(f, filename, args, argidx); diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index d92aec73b..a09f9497a 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -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) { log_assert(processes.count(process->name) != 0); diff --git a/kernel/rtlil.h b/kernel/rtlil.h index f841df1ed..163dbe5a8 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -2139,6 +2139,8 @@ public: } RTLIL::ObjRange wires() { return RTLIL::ObjRange(&wires_, &refcount_wires_); } + int wires_size() const { return wires_.size(); } + RTLIL::Wire* wire_at(int index) const { return wires_.element(index)->second; } RTLIL::ObjRange cells() { return RTLIL::ObjRange(&cells_, &refcount_cells_); } void add(RTLIL::Binding *binding); @@ -2146,6 +2148,7 @@ public: // Removing wires is expensive. If you have to remove wires, remove them all at once. void remove(const pool &wires); void remove(RTLIL::Cell *cell); + void remove(RTLIL::Memory *memory); void remove(RTLIL::Process *process); void rename(RTLIL::Wire *wire, RTLIL::IdString new_name); From 9ee51c8f27164e85c15ccbfc18910169f6d647b8 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Sun, 21 Dec 2025 22:45:06 +0000 Subject: [PATCH 05/12] Add AFL++ Grammar-Generator grammar for RTLIL fuzzing, and instructions for how to use it. --- tests/pass-fuzzing.md | 106 ++++++++++++++++++++++++++++ tests/tools/rtlil-fuzz-grammar.json | 104 +++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 tests/pass-fuzzing.md create mode 100644 tests/tools/rtlil-fuzz-grammar.json diff --git a/tests/pass-fuzzing.md b/tests/pass-fuzzing.md new file mode 100644 index 000000000..993f36078 --- /dev/null +++ b/tests/pass-fuzzing.md @@ -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. diff --git a/tests/tools/rtlil-fuzz-grammar.json b/tests/tools/rtlil-fuzz-grammar.json new file mode 100644 index 000000000..c27b160f4 --- /dev/null +++ b/tests/tools/rtlil-fuzz-grammar.json @@ -0,0 +1,104 @@ +{ + "": [ + [ + "module \\test\n", + "", "", + "", + "", + "end\n" + ] + ], + "": [ [ " wire width ", "", " ", "", " ", "", "\n" ] ], + "": [ [ "1" ], [ "2" ], [ "3" ], [ "4" ], [ "32" ], [ "128" ] ], + "": [ [ "input ", "" ], [ "output ", "" ], [ "inout ", "" ], [] ], + "": [ + [ + " cell $not ", "", "\n", + " parameter \\A_SIGNED 0\n", + " parameter \\A_WIDTH 0\n", + " parameter \\Y_WIDTH 0\n", + " connect \\A ", "", "\n", + " connect \\Y ", "", "\n", + " end\n" + ], + [ + " cell $and ", "", "\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 ", "", "\n", + " connect \\B ", "", "\n", + " connect \\Y ", "", "\n", + " end\n" + ], + [ + " cell $or ", "", "\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 ", "", "\n", + " connect \\B ", "", "\n", + " connect \\Y ", "", "\n", + " end\n" + ], + [ + " cell $xor ", "", "\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 ", "", "\n", + " connect \\B ", "", "\n", + " connect \\Y ", "", "\n", + " end\n" + ], + [ + " cell ", "", " ", "", "\n", + " connect \\A ", "", "\n", + " connect \\Y ", "", "\n", + " end\n" + ], + [ + " cell ", "", " ", "", "\n", + " connect \\A ", "", "\n", + " connect \\B ", "", "\n", + " connect \\Y ", "", "\n", + " end\n" + ] + ], + "": [ [ "\\wire_a" ], [ "\\wire_b" ], [ "\\wire_c" ], [ "\\wire_d" ], [ "\\wire_e" ], [ "\\wire_f" ], [ "\\wire_g" ], [ "\\wire_h" ], [ "\\wire_i" ], [ "\\wire_j" ] ], + "": [ [ "\\cell_a" ], [ "\\cell_b" ], [ "\\cell_c" ], [ "\\cell_d" ], [ "\\cell_e" ], [ "\\cell_f" ], [ "\\cell_g" ], [ "\\cell_h" ], [ "\\cell_i" ], [ "\\cell_j" ] ], + "": [ [ "\\bb1" ], [ "\\bb2" ] ], + "": [ + [ "", " " ], + [ "{", "", " ", "", "}" ], + [ "" ], + [ "", "[", "", "]" ], + [ "", "[", "", ":", "", "]" ] + ], + "": [ + [ "0'", "" ], + [ "1'", "" ], + [ "2'", "" ], + [ "3'", "" ], + [ "4'", "" ], + [ "31'", "" ], + [ "32'", "" ], + [ "128'", "" ] + ], + "": [ [ "0" ], [ "1" ], [ "x" ], [ "z" ], [ "-" ], [ "m" ] ], + "": [ "0", "1", "2", "3", "31", "32" ], + "": [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" ], + "": [ [ " connect ", "", " ", "", "\n" ] ], + + "": [ [ ], [ "", "" ] ], + "": [ [ ], [ "", "" ] ], + "": [ [ ], [ "", "" ] ], + "": [ [ ], [ "", "" ] ], + "": [ [ ], [ "", " ", "" ] ] +} From a6d696ba2b4abaf2e3941d884f7db989d6b09e8b Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 30 Dec 2025 03:53:02 +0000 Subject: [PATCH 06/12] Give `IdString` a default move constructor and make it a POD type. Now that we're not refcounting `IdString`, it can use the default move constructor. This lets us make `IdString` a POD type so it can be passed in registers in the standard C++ ABI. --- kernel/rtlil.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/rtlil.h b/kernel/rtlil.h index ec47adb0e..6549c1760 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -223,8 +223,8 @@ struct RTLIL::IdString constexpr inline IdString() : index_(0) { } inline IdString(const char *str) : index_(insert(std::string_view(str))) { } - constexpr inline IdString(const IdString &str) : index_(str.index_) { } - inline IdString(IdString &&str) : index_(str.index_) { str.index_ = 0; } + constexpr IdString(const IdString &str) = default; + IdString(IdString &&str) = default; inline IdString(const std::string &str) : index_(insert(std::string_view(str))) { } inline IdString(std::string_view str) : index_(insert(str)) { } constexpr inline IdString(StaticId id) : index_(static_cast(id)) {} From ea90f54783ce8170b8485b9bf012c8de519635dd Mon Sep 17 00:00:00 2001 From: YRabbit Date: Sat, 3 Jan 2026 17:42:49 +1000 Subject: [PATCH 07/12] Gowin. Implement byte enable. Enable write port with byte enables for BSRAM primitives. Signed-off-by: YRabbit --- techlibs/gowin/brams.txt | 6 ++++++ techlibs/gowin/brams_map.v | 18 +++++++++++++----- techlibs/gowin/brams_map_gw5a.v | 18 +++++++++++++----- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/techlibs/gowin/brams.txt b/techlibs/gowin/brams.txt index ee76dd73a..6898c9bd9 100644 --- a/techlibs/gowin/brams.txt +++ b/techlibs/gowin/brams.txt @@ -2,6 +2,7 @@ ram block $__GOWIN_SP_ { abits 14; widths 1 2 4 9 18 36 per_port; cost 128; + byte 9; init no_undef; port srsw "A" { clock posedge; @@ -24,6 +25,7 @@ ram block $__GOWIN_SP_ { rdwr old; } } + wrbe_separate; } } @@ -31,6 +33,7 @@ ram block $__GOWIN_DP_ { abits 14; widths 1 2 4 9 18 per_port; cost 128; + byte 9; init no_undef; port srsw "A" "B" { clock posedge; @@ -53,6 +56,7 @@ ram block $__GOWIN_DP_ { rdwr old; } } + wrbe_separate; } } @@ -60,6 +64,7 @@ ram block $__GOWIN_SDP_ { abits 14; widths 1 2 4 9 18 36 per_port; cost 128; + byte 9; init no_undef; port sr "R" { clock posedge; @@ -75,5 +80,6 @@ ram block $__GOWIN_SDP_ { port sw "W" { clock posedge; clken; + wrbe_separate; } } diff --git a/techlibs/gowin/brams_map.v b/techlibs/gowin/brams_map.v index 8e6cc6140..5ffe13e11 100644 --- a/techlibs/gowin/brams_map.v +++ b/techlibs/gowin/brams_map.v @@ -14,7 +14,7 @@ `define x8_width(width) (width / 9 * 8 + width % 9) `define x8_rd_data(data) {1'bx, data[31:24], 1'bx, data[23:16], 1'bx, data[15:8], 1'bx, data[7:0]} `define x8_wr_data(data) {data[34:27], data[25:18], data[16:9], data[7:0]} -`define addrbe_always(width, addr) (width < 18 ? addr : width == 18 ? {addr[13:4], 4'b0011} : {addr[13:5], 5'b01111}) +`define addrbe(width, addr, w_be) (width < 18 ? addr : width == 18 ? {addr[13:4], 2'b00, w_be[1:0]} : {addr[13:5], 1'b0, w_be[3:0]}) `define INIT(func) \ @@ -90,6 +90,7 @@ parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_A_WIDTH = 36; parameter PORT_A_OPTION_WRITE_MODE = 0; +parameter PORT_A_WR_BE_WIDTH = 4; input PORT_A_CLK; input PORT_A_CLK_EN; @@ -97,13 +98,14 @@ input PORT_A_WR_EN; input PORT_A_RD_SRST; input PORT_A_RD_ARST; input [13:0] PORT_A_ADDR; +input [PORT_A_WR_BE_WIDTH-1:0] PORT_A_WR_BE; input [PORT_A_WIDTH-1:0] PORT_A_WR_DATA; output [PORT_A_WIDTH-1:0] PORT_A_RD_DATA; `DEF_FUNCS wire RST = OPTION_RESET_MODE == "SYNC" ? PORT_A_RD_SRST : PORT_A_RD_ARST; -wire [13:0] AD = `addrbe_always(PORT_A_WIDTH, PORT_A_ADDR); +wire [13:0] AD = `addrbe(PORT_A_WIDTH, PORT_A_ADDR, PORT_A_WR_BE); generate @@ -173,9 +175,11 @@ parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_A_WIDTH = 18; parameter PORT_A_OPTION_WRITE_MODE = 0; +parameter PORT_A_WR_BE_WIDTH = 4; parameter PORT_B_WIDTH = 18; parameter PORT_B_OPTION_WRITE_MODE = 0; +parameter PORT_B_WR_BE_WIDTH = 4; input PORT_A_CLK; input PORT_A_CLK_EN; @@ -183,6 +187,7 @@ input PORT_A_WR_EN; input PORT_A_RD_SRST; input PORT_A_RD_ARST; input [13:0] PORT_A_ADDR; +input [PORT_A_WR_BE_WIDTH-1:0] PORT_A_WR_BE; input [PORT_A_WIDTH-1:0] PORT_A_WR_DATA; output [PORT_A_WIDTH-1:0] PORT_A_RD_DATA; @@ -192,6 +197,7 @@ input PORT_B_WR_EN; input PORT_B_RD_SRST; input PORT_B_RD_ARST; input [13:0] PORT_B_ADDR; +input [PORT_B_WR_BE_WIDTH-1:0] PORT_B_WR_BE; input [PORT_A_WIDTH-1:0] PORT_B_WR_DATA; output [PORT_A_WIDTH-1:0] PORT_B_RD_DATA; @@ -199,8 +205,8 @@ output [PORT_A_WIDTH-1:0] PORT_B_RD_DATA; wire RSTA = OPTION_RESET_MODE == "SYNC" ? PORT_A_RD_SRST : PORT_A_RD_ARST; wire RSTB = OPTION_RESET_MODE == "SYNC" ? PORT_B_RD_SRST : PORT_B_RD_ARST; -wire [13:0] ADA = `addrbe_always(PORT_A_WIDTH, PORT_A_ADDR); -wire [13:0] ADB = `addrbe_always(PORT_B_WIDTH, PORT_B_ADDR); +wire [13:0] ADA = `addrbe(PORT_A_WIDTH, PORT_A_ADDR, PORT_B_WR_BE); +wire [13:0] ADB = `addrbe(PORT_B_WIDTH, PORT_B_ADDR, PORT_B_WR_BE); generate @@ -306,6 +312,7 @@ parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_R_WIDTH = 18; parameter PORT_W_WIDTH = 18; +parameter PORT_W_WR_BE_WIDTH=4; input PORT_R_CLK; input PORT_R_CLK_EN; @@ -318,12 +325,13 @@ input PORT_W_CLK; input PORT_W_CLK_EN; input PORT_W_WR_EN; input [13:0] PORT_W_ADDR; +input [PORT_W_WR_BE_WIDTH-1:0] PORT_W_WR_BE; input [PORT_W_WIDTH-1:0] PORT_W_WR_DATA; `DEF_FUNCS wire RST = OPTION_RESET_MODE == "SYNC" ? PORT_R_RD_SRST : PORT_R_RD_ARST; -wire [13:0] ADW = `addrbe_always(PORT_W_WIDTH, PORT_W_ADDR); +wire [13:0] ADW = `addrbe(PORT_W_WIDTH, PORT_W_ADDR, PORT_W_WR_BE); wire WRE = PORT_W_CLK_EN & PORT_W_WR_EN; generate diff --git a/techlibs/gowin/brams_map_gw5a.v b/techlibs/gowin/brams_map_gw5a.v index 246146ee5..812f04edf 100644 --- a/techlibs/gowin/brams_map_gw5a.v +++ b/techlibs/gowin/brams_map_gw5a.v @@ -14,7 +14,7 @@ `define x8_width(width) (width / 9 * 8 + width % 9) `define x8_rd_data(data) {1'bx, data[31:24], 1'bx, data[23:16], 1'bx, data[15:8], 1'bx, data[7:0]} `define x8_wr_data(data) {data[34:27], data[25:18], data[16:9], data[7:0]} -`define addrbe_always(width, addr) (width < 18 ? addr : width == 18 ? {addr[13:4], 4'b0011} : {addr[13:5], 5'b01111}) +`define addrbe(width, addr, w_be) (width < 18 ? addr : width == 18 ? {addr[13:4], 2'b00, w_be[1:0]} : {addr[13:5], 1'b0, w_be[3:0]}) `define INIT(func) \ @@ -90,6 +90,7 @@ parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_A_WIDTH = 36; parameter PORT_A_OPTION_WRITE_MODE = 0; +parameter PORT_A_WR_BE_WIDTH = 4; input PORT_A_CLK; input PORT_A_CLK_EN; @@ -97,13 +98,14 @@ input PORT_A_WR_EN; input PORT_A_RD_SRST; input PORT_A_RD_ARST; input [13:0] PORT_A_ADDR; +input [PORT_A_WR_BE_WIDTH-1:0] PORT_A_WR_BE; input [PORT_A_WIDTH-1:0] PORT_A_WR_DATA; output [PORT_A_WIDTH-1:0] PORT_A_RD_DATA; `DEF_FUNCS wire RST = OPTION_RESET_MODE == "SYNC" ? PORT_A_RD_SRST : PORT_A_RD_ARST; -wire [13:0] AD = `addrbe_always(PORT_A_WIDTH, PORT_A_ADDR); +wire [13:0] AD = `addrbe(PORT_A_WIDTH, PORT_A_ADDR, PORT_A_WR_BE); generate @@ -173,9 +175,11 @@ parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_A_WIDTH = 18; parameter PORT_A_OPTION_WRITE_MODE = 0; +parameter PORT_A_WR_BE_WIDTH = 4; parameter PORT_B_WIDTH = 18; parameter PORT_B_OPTION_WRITE_MODE = 0; +parameter PORT_B_WR_BE_WIDTH = 4; input PORT_A_CLK; input PORT_A_CLK_EN; @@ -183,6 +187,7 @@ input PORT_A_WR_EN; input PORT_A_RD_SRST; input PORT_A_RD_ARST; input [13:0] PORT_A_ADDR; +input [PORT_A_WR_BE_WIDTH-1:0] PORT_A_WR_BE; input [PORT_A_WIDTH-1:0] PORT_A_WR_DATA; output [PORT_A_WIDTH-1:0] PORT_A_RD_DATA; @@ -192,6 +197,7 @@ input PORT_B_WR_EN; input PORT_B_RD_SRST; input PORT_B_RD_ARST; input [13:0] PORT_B_ADDR; +input [PORT_B_WR_BE_WIDTH-1:0] PORT_B_WR_BE; input [PORT_A_WIDTH-1:0] PORT_B_WR_DATA; output [PORT_A_WIDTH-1:0] PORT_B_RD_DATA; @@ -199,8 +205,8 @@ output [PORT_A_WIDTH-1:0] PORT_B_RD_DATA; wire RSTA = OPTION_RESET_MODE == "SYNC" ? PORT_A_RD_SRST : PORT_A_RD_ARST; wire RSTB = OPTION_RESET_MODE == "SYNC" ? PORT_B_RD_SRST : PORT_B_RD_ARST; -wire [13:0] ADA = `addrbe_always(PORT_A_WIDTH, PORT_A_ADDR); -wire [13:0] ADB = `addrbe_always(PORT_B_WIDTH, PORT_B_ADDR); +wire [13:0] ADA = `addrbe(PORT_A_WIDTH, PORT_A_ADDR, PORT_B_WR_BE); +wire [13:0] ADB = `addrbe(PORT_B_WIDTH, PORT_B_ADDR, PORT_B_WR_BE); generate @@ -306,6 +312,7 @@ parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_R_WIDTH = 18; parameter PORT_W_WIDTH = 18; +parameter PORT_W_WR_BE_WIDTH=4; input PORT_R_CLK; input PORT_R_CLK_EN; @@ -318,12 +325,13 @@ input PORT_W_CLK; input PORT_W_CLK_EN; input PORT_W_WR_EN; input [13:0] PORT_W_ADDR; +input [PORT_W_WR_BE_WIDTH-1:0] PORT_W_WR_BE; input [PORT_W_WIDTH-1:0] PORT_W_WR_DATA; `DEF_FUNCS wire RST = OPTION_RESET_MODE == "SYNC" ? PORT_R_RD_SRST : PORT_R_RD_ARST; -wire [13:0] ADW = `addrbe_always(PORT_W_WIDTH, PORT_W_ADDR); +wire [13:0] ADW = `addrbe(PORT_W_WIDTH, PORT_W_ADDR, PORT_W_WR_BE); wire WRE = PORT_W_CLK_EN & PORT_W_WR_EN; generate From 8a78f2f7c594932769c6881f6460df5abed8d29f Mon Sep 17 00:00:00 2001 From: YRabbit Date: Mon, 5 Jan 2026 20:07:31 +1000 Subject: [PATCH 08/12] Gowin. Fix style. Signed-off-by: YRabbit --- techlibs/gowin/brams_map.v | 2 +- techlibs/gowin/brams_map_gw5a.v | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/techlibs/gowin/brams_map.v b/techlibs/gowin/brams_map.v index 5ffe13e11..774896e79 100644 --- a/techlibs/gowin/brams_map.v +++ b/techlibs/gowin/brams_map.v @@ -312,7 +312,7 @@ parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_R_WIDTH = 18; parameter PORT_W_WIDTH = 18; -parameter PORT_W_WR_BE_WIDTH=4; +parameter PORT_W_WR_BE_WIDTH = 4; input PORT_R_CLK; input PORT_R_CLK_EN; diff --git a/techlibs/gowin/brams_map_gw5a.v b/techlibs/gowin/brams_map_gw5a.v index 812f04edf..547b0d1d1 100644 --- a/techlibs/gowin/brams_map_gw5a.v +++ b/techlibs/gowin/brams_map_gw5a.v @@ -312,7 +312,7 @@ parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_R_WIDTH = 18; parameter PORT_W_WIDTH = 18; -parameter PORT_W_WR_BE_WIDTH=4; +parameter PORT_W_WR_BE_WIDTH = 4; input PORT_R_CLK; input PORT_R_CLK_EN; From 6e5a5160518d422cee7cae922e930b3dc27c124c Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Mon, 5 Jan 2026 16:34:45 +0100 Subject: [PATCH 09/12] Update ABC as per 2026-01-05 --- abc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abc b/abc index ef74590eb..799ba6322 160000 --- a/abc +++ b/abc @@ -1 +1 @@ -Subproject commit ef74590ebd78b3b707eeba56d8284faf018affa6 +Subproject commit 799ba632239b2a4db2bacda81de4e6efdc486b0c From 1567526954905120629315c807957938721871e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:26:49 +0000 Subject: [PATCH 10/12] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0008025ee..c859422cd 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.60+70 +YOSYS_VER := 0.60+78 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From 042ec1cf6076155e6a8ffa9d006fac789e12145a Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Fri, 19 Dec 2025 00:38:47 +0000 Subject: [PATCH 11/12] Defer redirecting cell outputs when merging cells in `opt_merge` until after we've done a full pass over the cells. This avoids changing `assign_map` and `initvals`, which are inputs to the hash function for `known_cells`, while `known_cells` exists. Changing the hash function for a hashtable while it exists leads to confusing behavior. That also means the exact behavior of `opt_merge` cannot be reproduced by a parallel implementation. --- passes/opt/opt_merge.cc | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index 6cdcbc822..2914debbc 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -284,6 +284,7 @@ struct OptMergeWorker CellPtrHash, CellPtrEqual> known_cells (0, CellPtrHash(*this), CellPtrEqual(*this)); + std::vector redirects; for (auto cell : cells) { auto [cell_in_map, inserted] = known_cells.insert(cell); @@ -305,12 +306,7 @@ struct OptMergeWorker RTLIL::SigSpec other_sig = other_cell->getPort(it.first); log_debug(" Redirecting output %s: %s = %s\n", it.first, log_signal(it.second), log_signal(other_sig)); - Const init = initvals(other_sig); - initvals.remove_init(it.second); - initvals.remove_init(other_sig); - module->connect(RTLIL::SigSig(it.second, other_sig)); - assign_map.add(it.second, other_sig); - initvals.set_init(other_sig, init); + redirects.push_back(RTLIL::SigSig(it.second, std::move(other_sig))); } } log_debug(" Removing %s cell `%s' from module `%s'.\n", cell->type, cell->name, module->name); @@ -318,6 +314,14 @@ struct OptMergeWorker total_count++; } } + for (const RTLIL::SigSig &redirect : redirects) { + module->connect(redirect); + Const init = initvals(redirect.second); + initvals.remove_init(redirect.first); + initvals.remove_init(redirect.second); + assign_map.add(redirect.first, redirect.second); + initvals.set_init(redirect.second, init); + } } log_suppressed(); From 35321cd292075daabe847589d32d1f8a6c4224ba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:25:36 +0000 Subject: [PATCH 12/12] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c859422cd..3dc5f0fe0 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.60+78 +YOSYS_VER := 0.60+88 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2)