mirror of https://github.com/YosysHQ/yosys.git
Merge branch 'YosysHQ:main' into main
This commit is contained in:
commit
3cfbc0d7af
2
Makefile
2
Makefile
|
|
@ -176,7 +176,7 @@ ifeq ($(OS), Haiku)
|
|||
CXXFLAGS += -D_DEFAULT_SOURCE
|
||||
endif
|
||||
|
||||
YOSYS_VER := 0.55+36
|
||||
YOSYS_VER := 0.55+46
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ do
|
|||
opt_merge
|
||||
opt_share (-full only)
|
||||
opt_dff (except when called with -noff)
|
||||
opt_hier (-hier only)
|
||||
opt_clean
|
||||
opt_expr
|
||||
while <changed design>
|
||||
|
|
|
|||
|
|
@ -192,6 +192,13 @@ control inputs.
|
|||
Called with ``-nodffe`` and ``-nosdff``, this pass is used to prepare a design
|
||||
for :doc:`/using_yosys/synthesis/fsm`.
|
||||
|
||||
Hierarchical optimization - `opt_hier` pass
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This pass considers the design hierarchy and propagates unused signals, constant
|
||||
signals, and tied-together signals across module boundaries to facilitate
|
||||
optimization by other passes.
|
||||
|
||||
Removing unused cells and wires - `opt_clean` pass
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -56,3 +56,4 @@ OBJS += passes/cmds/setenv.o
|
|||
OBJS += passes/cmds/abstract.o
|
||||
OBJS += passes/cmds/test_select.o
|
||||
OBJS += passes/cmds/timeest.o
|
||||
OBJS += passes/cmds/linecoverage.o
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/sigtools.h"
|
||||
|
||||
#include <regex>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
static const std::regex src_re("(.*):(\\d+)\\.(\\d+)-(\\d+)\\.(\\d+)");
|
||||
|
||||
struct CoveragePass : public Pass {
|
||||
CoveragePass() : Pass("linecoverage", "report coverage information") { }
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" linecoverage [options] [selection]\n");
|
||||
log("\n");
|
||||
log("This command prints coverage information on the design based on the current\n");
|
||||
log("selection, where items in the selection are considered covered and items not in\n");
|
||||
log("the selection are considered uncovered. If the same source location is found\n");
|
||||
log("both on items inside and out of the selection, it is considered uncovered.\n");
|
||||
log("\n");
|
||||
log(" -lcov <filename>\n");
|
||||
log(" write coverage information in lcov format to this file\n");
|
||||
log("\n");
|
||||
}
|
||||
|
||||
std::string extract_src_filename(std::string src) const
|
||||
{
|
||||
std::smatch m;
|
||||
if (std::regex_match(src, m, src_re)) {
|
||||
return m[1].str();
|
||||
};
|
||||
return "";
|
||||
}
|
||||
|
||||
std::pair<int, int> extract_src_lines(std::string src) const
|
||||
{
|
||||
std::smatch m;
|
||||
if (std::regex_match(src, m, src_re)) {
|
||||
return std::make_pair(stoi(m[2].str()), stoi(m[4].str()));
|
||||
};
|
||||
return std::make_pair(0,0);
|
||||
}
|
||||
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
std::string ofile;
|
||||
|
||||
log_header(design, "Executing linecoverage pass.\n");
|
||||
|
||||
size_t argidx;
|
||||
for (argidx = 1; argidx < args.size(); argidx++)
|
||||
{
|
||||
if (args[argidx] == "-lcov" && argidx+1 < args.size()) {
|
||||
ofile = args[++argidx];
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
std::ofstream fout;
|
||||
if (!ofile.empty()) {
|
||||
fout.open(ofile, std::ios::out | std::ios::trunc);
|
||||
if (!fout.is_open())
|
||||
log_error("Could not open file \"%s\" with write access.\n", ofile.c_str());
|
||||
}
|
||||
|
||||
std::map<std::string, std::set<int>> uncovered_lines;
|
||||
std::map<std::string, std::set<int>> all_lines;
|
||||
|
||||
for (auto module : design->modules())
|
||||
{
|
||||
log_debug("Module %s:\n", log_id(module));
|
||||
for (auto wire: module->wires()) {
|
||||
log_debug("%s\t%s\t%s\n", module->selected(wire) ? "*" : " ", wire->get_src_attribute().c_str(), log_id(wire->name));
|
||||
for (auto src: wire->get_strpool_attribute(ID::src)) {
|
||||
auto filename = extract_src_filename(src);
|
||||
if (filename.empty()) continue;
|
||||
auto [begin, end] = extract_src_lines(src);
|
||||
for (int l = begin; l <=end; l++) {
|
||||
if (l == 0) continue;
|
||||
all_lines[filename].insert(l);
|
||||
if (!module->selected(wire))
|
||||
uncovered_lines[filename].insert(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto cell: module->cells()) {
|
||||
log_debug("%s\t%s\t%s\n", module->selected(cell) ? "*" : " ", cell->get_src_attribute().c_str(), log_id(cell->name));
|
||||
for (auto src: cell->get_strpool_attribute(ID::src)) {
|
||||
auto filename = extract_src_filename(src);
|
||||
if (filename.empty()) continue;
|
||||
auto [begin, end] = extract_src_lines(src);
|
||||
for (int l = begin; l <=end; l++) {
|
||||
if (l == 0) continue;
|
||||
all_lines[filename].insert(l);
|
||||
if (!module->selected(cell))
|
||||
uncovered_lines[filename].insert(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
log_debug("\n");
|
||||
}
|
||||
|
||||
for (const auto& file_entry : all_lines) {
|
||||
int lines_found = file_entry.second.size();
|
||||
int lines_hit = file_entry.second.size() - (uncovered_lines.count(file_entry.first) ? uncovered_lines[file_entry.first].size() : 0);
|
||||
log("File %s: %d/%d lines covered\n", file_entry.first.c_str(), lines_hit, lines_found);
|
||||
|
||||
if(!ofile.empty()) {
|
||||
fout << "SF:" << file_entry.first << "\n";
|
||||
for (int l : file_entry.second) {
|
||||
fout << "DA:" << l << ",";
|
||||
if (uncovered_lines.count(file_entry.first) && uncovered_lines[file_entry.first].count(l))
|
||||
fout << "0";
|
||||
else
|
||||
fout << "1";
|
||||
fout << "\n";
|
||||
}
|
||||
fout << "LF:" << lines_found << "\n";
|
||||
fout << "LH:" << lines_hit << "\n";
|
||||
fout << "end_of_record\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} CoveragePass;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
|
|
@ -11,6 +11,7 @@ OBJS += passes/opt/opt_dff.o
|
|||
OBJS += passes/opt/opt_share.o
|
||||
OBJS += passes/opt/opt_clean.o
|
||||
OBJS += passes/opt/opt_expr.o
|
||||
OBJS += passes/opt/opt_hier.o
|
||||
|
||||
OBJS += passes/opt/share.o
|
||||
OBJS += passes/opt/wreduce.o
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ struct OptPass : public Pass {
|
|||
log(" opt_merge [-share_all]\n");
|
||||
log(" opt_share (-full only)\n");
|
||||
log(" opt_dff [-nodffe] [-nosdff] [-keepdc] [-sat] (except when called with -noff)\n");
|
||||
log(" opt_hier (-hier only)\n");
|
||||
log(" opt_clean [-purge]\n");
|
||||
log(" opt_expr [-mux_undef] [-mux_bool] [-undriven] [-noclkinv] [-fine] [-full] [-keepdc]\n");
|
||||
log(" while <changed design>\n");
|
||||
|
|
@ -56,6 +57,7 @@ struct OptPass : public Pass {
|
|||
log(" opt_expr [-mux_undef] [-mux_bool] [-undriven] [-noclkinv] [-fine] [-full] [-keepdc]\n");
|
||||
log(" opt_merge [-share_all]\n");
|
||||
log(" opt_dff [-nodffe] [-nosdff] [-keepdc] [-sat] (except when called with -noff)\n");
|
||||
log(" opt_hier (-hier only)\n");
|
||||
log(" opt_clean [-purge]\n");
|
||||
log(" while <changed design in opt_dff>\n");
|
||||
log("\n");
|
||||
|
|
@ -74,6 +76,7 @@ struct OptPass : public Pass {
|
|||
bool opt_share = false;
|
||||
bool fast_mode = false;
|
||||
bool noff_mode = false;
|
||||
bool hier_mode = false;
|
||||
|
||||
log_header(design, "Executing OPT pass (performing simple optimizations).\n");
|
||||
log_push();
|
||||
|
|
@ -141,6 +144,10 @@ struct OptPass : public Pass {
|
|||
noff_mode = true;
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-hier") {
|
||||
hier_mode = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
|
@ -155,6 +162,8 @@ struct OptPass : public Pass {
|
|||
Pass::call(design, "opt_dff" + opt_dff_args);
|
||||
if (design->scratchpad_get_bool("opt.did_something") == false)
|
||||
break;
|
||||
if (hier_mode)
|
||||
Pass::call(design, "opt_hier");
|
||||
Pass::call(design, "opt_clean" + opt_clean_args);
|
||||
log_header(design, "Rerunning OPT passes. (Removed registers in this run.)\n");
|
||||
}
|
||||
|
|
@ -173,6 +182,8 @@ struct OptPass : public Pass {
|
|||
Pass::call(design, "opt_share");
|
||||
if (!noff_mode)
|
||||
Pass::call(design, "opt_dff" + opt_dff_args);
|
||||
if (hier_mode)
|
||||
Pass::call(design, "opt_hier");
|
||||
Pass::call(design, "opt_clean" + opt_clean_args);
|
||||
Pass::call(design, "opt_expr" + opt_expr_args);
|
||||
if (design->scratchpad_get_bool("opt.did_something") == false)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,470 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) Martin Povišer <povik@cutebit.org>
|
||||
*
|
||||
* 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/register.h"
|
||||
#include "kernel/rtlil.h"
|
||||
#include "kernel/sigtools.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
// Used to propagate information out of a module
|
||||
struct ModuleIndex {
|
||||
Module *module;
|
||||
SigMap sigmap;
|
||||
SigPool used;
|
||||
dict<SigBit, SigBit> constant_outputs;
|
||||
std::vector<SigSpec> tie_together_outputs;
|
||||
|
||||
ModuleIndex(Module *module)
|
||||
: module(module), sigmap(module)
|
||||
{
|
||||
if (module->get_blackbox_attribute()) {
|
||||
for (auto wire : module->wires()) {
|
||||
for (auto bit : SigSpec(wire))
|
||||
used.add(sigmap(bit));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto count_usage = [&](const SigSpec &signal) {
|
||||
for (auto bit : signal)
|
||||
used.add(sigmap(bit));
|
||||
};
|
||||
for (auto wire : module->wires()) {
|
||||
if (wire->port_output) {
|
||||
SigSpec wire1 = wire;
|
||||
count_usage(wire1);
|
||||
}
|
||||
}
|
||||
for (auto [_, process] : module->processes)
|
||||
process->rewrite_sigspecs(count_usage);
|
||||
for (auto cell : module->cells()) {
|
||||
bool known = cell->known();
|
||||
for (auto &conn : cell->connections()) {
|
||||
if (!known || cell->input(conn.first))
|
||||
count_usage(conn.second);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dict<SigBit, SigSpec> classes;
|
||||
for (auto &pair : module->connections_) {
|
||||
for (int i = 0; i < pair.first.size(); i++) {
|
||||
if (pair.first[i].wire
|
||||
&& pair.first[i].wire->port_output
|
||||
&& !pair.first[i].wire->port_input) {
|
||||
if (!pair.second[i].wire) {
|
||||
constant_outputs[pair.first[i]] = pair.second[i];
|
||||
} else {
|
||||
classes[pair.second[i]].append(pair.first[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto [key, new_class] : classes) {
|
||||
if (new_class.size() > 1) {
|
||||
new_class.sort_and_unify();
|
||||
tie_together_outputs.push_back(new_class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool apply_changes(ModuleIndex &parent, Cell *instantiation) {
|
||||
log_assert(instantiation->module == parent.module);
|
||||
|
||||
if (module->get_blackbox_attribute()) {
|
||||
// no propagating out of blackboxes
|
||||
return false;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
for (auto &[port_name, value] : instantiation->connections_) {
|
||||
Wire *port = module->wire(port_name);
|
||||
if (!port || (!port->port_input && !port->port_output) || port->width != value.size()) {
|
||||
log_error("Port %s connected on instance %s not found in module %s"
|
||||
" or width is not matching\n",
|
||||
log_id(port_name), log_id(instantiation), log_id(module));
|
||||
}
|
||||
|
||||
if (port->port_input && port->port_output) {
|
||||
// ignore bidirectional: hard to come up with sound handling
|
||||
continue;
|
||||
}
|
||||
|
||||
int nunused = 0, nconstants = 0;
|
||||
// disconnect unused inputs
|
||||
if (port->port_input) {
|
||||
for (int i = 0; i < port->width; i++) {
|
||||
if (value[i].is_wire() && !used.check(sigmap(SigBit(port, i)))) {
|
||||
value[i] = RTLIL::Sx;
|
||||
nunused++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// propagate constants
|
||||
if (port->port_output) {
|
||||
SigSpec port_new_const;
|
||||
|
||||
for (int i = 0; i < port->width; i++) {
|
||||
SigBit port_bit(port, i);
|
||||
if (value[i].is_wire() && constant_outputs.count(port_bit) && parent.used.check(value[i])) {
|
||||
port_new_const.append(port_bit);
|
||||
nconstants++;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto chunk : port_new_const.chunks()) {
|
||||
RTLIL::SigSpec rhs = chunk;
|
||||
rhs.replace(constant_outputs);
|
||||
log_assert(rhs.is_fully_const());
|
||||
parent.module->connect(value.extract(chunk.offset, chunk.width), rhs);
|
||||
SigSpec dummy = parent.module->addWire(NEW_ID_SUFFIX("const_output"), chunk.width);
|
||||
for (int i = 0; i < chunk.width; i++)
|
||||
value[chunk.offset + i] = dummy[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (nunused > 0) {
|
||||
log("Disconnected %d input bits of instance '%s' of '%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",
|
||||
nconstants, log_id(instantiation), log_id(instantiation->type), log_id(parent.module));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// propagate tie-togethers
|
||||
int ntie_togethers = 0;
|
||||
SigSpec severed_port_bits;
|
||||
for (auto class_ : tie_together_outputs) {
|
||||
// filtered class represented by bits on the two sides of boundary
|
||||
SigSpec new_tie;
|
||||
|
||||
for (auto port_bit : class_) {
|
||||
if (instantiation->connections_.count(port_bit.wire->name)) {
|
||||
SigBit bit = instantiation->connections_.at(port_bit.wire->name)[port_bit.offset];
|
||||
if (parent.used.check(bit)) {
|
||||
if (!new_tie.empty()) {
|
||||
severed_port_bits.append(port_bit);
|
||||
ntie_togethers++;
|
||||
}
|
||||
new_tie.append(bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (new_tie.size() > 1)
|
||||
parent.module->connect(new_tie.extract_end(1), SigSpec(new_tie[0]).repeat(new_tie.size() - 1));
|
||||
}
|
||||
|
||||
severed_port_bits.sort_and_unify();
|
||||
for (auto chunk : severed_port_bits.chunks()) {
|
||||
SigSpec &value = instantiation->connections_.at(chunk.wire->name);
|
||||
SigSpec dummy = parent.module->addWire(NEW_ID_SUFFIX("tie_together"), chunk.width);
|
||||
for (int i = 0; i < chunk.width; i++)
|
||||
value[chunk.offset + i] = dummy[i];
|
||||
}
|
||||
|
||||
if (ntie_togethers > 0) {
|
||||
log("Replacing %d output bits with tie-togethers on instance '%s' of '%s' in '%s'\n",
|
||||
ntie_togethers, log_id(instantiation), log_id(instantiation->type), log_id(parent.module));
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
};
|
||||
|
||||
// Used to propagate information into a module
|
||||
struct UsageData {
|
||||
Module *module;
|
||||
SigPool used_outputs;
|
||||
// Values are constant nets. We're not using `dict<SigBit, State>`
|
||||
// since we want to use this with `SigSpec::replace()`
|
||||
dict<SigBit, SigBit> constant_inputs;
|
||||
std::vector<SigSpec> tie_together_inputs;
|
||||
|
||||
SigSpec all_inputs;
|
||||
SigSpec all_outputs;
|
||||
|
||||
UsageData(Module *module)
|
||||
: module(module)
|
||||
{
|
||||
SigSpec all_inputs;
|
||||
|
||||
for (auto port_name : module->ports) {
|
||||
Wire *port = module->wire(port_name);
|
||||
log_assert(port);
|
||||
|
||||
if (port->port_input && port->port_output) {
|
||||
// ignore bidirectional: hard to come up with sound handling
|
||||
continue;
|
||||
}
|
||||
|
||||
if (port->port_input) {
|
||||
for (int i = 0; i < port->width; i++) {
|
||||
constant_inputs[SigBit(port, i)] = RTLIL::Sx;
|
||||
}
|
||||
all_inputs.append(port);
|
||||
} else {
|
||||
all_outputs.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
tie_together_inputs.push_back(all_inputs);
|
||||
}
|
||||
|
||||
void refine_used_outputs(Wire *port, SigSpec connection, ModuleIndex &index) {
|
||||
for (int i = 0; i < port->width; i++) {
|
||||
if (connection[i].is_wire() && index.used.check(index.sigmap(connection[i]))) {
|
||||
used_outputs.add(SigBit(port, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void refine_input_constants(Wire *port, SigSpec connection) {
|
||||
for (int i = 0; i < port->width; i++) {
|
||||
SigBit port_bit(port, i);
|
||||
// is connnected constant incompatible with candidate constant?
|
||||
if (connection[i] != RTLIL::Sx
|
||||
&& constant_inputs.count(port_bit)
|
||||
&& constant_inputs.at(port_bit) != connection[i]) {
|
||||
// we can go Sx -> S1/S0, otherwise erase the candidate constant
|
||||
if (constant_inputs.at(port_bit) == RTLIL::Sx && !connection[i].is_wire()) {
|
||||
constant_inputs[port_bit] = connection[i];
|
||||
} else {
|
||||
constant_inputs.erase(port_bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void refine_tie_togethers(const dict<SigBit, SigBit> &inputs) {
|
||||
std::vector<SigSpec> new_tie_togethers;
|
||||
|
||||
for (auto &class_ : tie_together_inputs) {
|
||||
dict<SigBit, SigSpec> new_classes;
|
||||
|
||||
for (auto bit : class_) {
|
||||
SigBit connected_bit = inputs.count(bit) ? inputs.at(bit) : RTLIL::Sx;
|
||||
new_classes[connected_bit].append(bit);
|
||||
}
|
||||
|
||||
for (auto [key, new_class] : new_classes) {
|
||||
if (new_class.size() > 1)
|
||||
new_tie_togethers.push_back(new_class);
|
||||
}
|
||||
}
|
||||
|
||||
new_tie_togethers.swap(tie_together_inputs);
|
||||
}
|
||||
|
||||
// inspect the given instantiation and refine usage data accordingly
|
||||
void refine(Cell *instance, ModuleIndex &index) {
|
||||
dict<SigBit, SigBit> inputs;
|
||||
|
||||
for (auto &[port_name, value] : instance->connections_) {
|
||||
Wire *port = module->wire(port_name);
|
||||
if (!port || (!port->port_input && !port->port_output) || port->width != value.size()) {
|
||||
log_error("Port %s connected on instance %s not found in module %s"
|
||||
" or width is not matching\n",
|
||||
log_id(port_name), log_id(instance), log_id(module));
|
||||
}
|
||||
|
||||
if (port->port_input && port->port_output) {
|
||||
// ignore bidirectional: hard to come up with sound handling
|
||||
continue;
|
||||
}
|
||||
|
||||
if (port->port_output) {
|
||||
refine_used_outputs(port, value, index);
|
||||
} else {
|
||||
refine_input_constants(port, value);
|
||||
for (int i = 0; i < port->width; i++)
|
||||
inputs[SigBit(port, i)] = value[i];
|
||||
}
|
||||
}
|
||||
|
||||
refine_tie_togethers(inputs);
|
||||
}
|
||||
|
||||
bool apply_changes() {
|
||||
bool did_something = false;
|
||||
|
||||
if (module->get_blackbox_attribute()) {
|
||||
// no propagating into blackboxes
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disconnect unused outputs
|
||||
for (auto &pair : module->connections_) {
|
||||
for (int i = 0; i < pair.first.size(); i++) {
|
||||
// If an output is constant there's no benefit to disconnecting
|
||||
// so consider it "used"
|
||||
if (pair.first[i].wire
|
||||
&& pair.first[i].wire->port_output
|
||||
&& !pair.second[i].wire)
|
||||
used_outputs.add(pair.first[i]);
|
||||
}
|
||||
}
|
||||
|
||||
SigSpec disconnect_outputs;
|
||||
for (auto bit : all_outputs) {
|
||||
if (!used_outputs.check(bit))
|
||||
disconnect_outputs.append(bit);
|
||||
}
|
||||
|
||||
dict<SigBit, SigBit> replacement_map;
|
||||
for (auto chunk : disconnect_outputs.chunks()) {
|
||||
Wire *repl_wire = module->addWire(module->uniquify(std::string("$") + chunk.wire->name.str()), chunk.size());
|
||||
for (int i = 0; i < repl_wire->width; i++)
|
||||
replacement_map[SigSpec(chunk)[i]] = SigBit(repl_wire, i);
|
||||
}
|
||||
auto disconnect_rewrite = [&](SigSpec &signal) {
|
||||
signal.replace(replacement_map);
|
||||
};
|
||||
module->rewrite_sigspecs(disconnect_rewrite);
|
||||
for (auto chunk : disconnect_outputs.chunks()) {
|
||||
log("Disconnected unused output terminal '%s' in module '%s'\n", log_signal(chunk), log_id(module));
|
||||
did_something = true;
|
||||
module->connect(chunk, SigSpec(RTLIL::Sx, chunk.size()));
|
||||
}
|
||||
|
||||
// Connect constant inputs
|
||||
SigPool applied_constants;
|
||||
auto constant_rewrite = [&](SigSpec &signal) {
|
||||
for (auto bit : signal) {
|
||||
if (constant_inputs.count(bit))
|
||||
applied_constants.add(bit);
|
||||
}
|
||||
signal.replace(constant_inputs);
|
||||
};
|
||||
module->rewrite_sigspecs(constant_rewrite);
|
||||
SigSpec applied_constants2 = applied_constants.export_all();
|
||||
applied_constants2.sort_and_unify();
|
||||
for (auto chunk : applied_constants2.chunks()) {
|
||||
SigSpec const_ = chunk;
|
||||
const_.replace(constant_inputs);
|
||||
log("Substituting constant %s for input terminal '%s' in module '%s'\n",
|
||||
log_signal(const_), log_signal(chunk), log_id(module));
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
SigPool applied_ties;
|
||||
auto ties_rewrite = [&](SigSpec &signal) {
|
||||
for (auto bit : signal) {
|
||||
if (ties.count(bit)) {
|
||||
applied_ties.add(bit);
|
||||
}
|
||||
}
|
||||
signal.replace(ties);
|
||||
};
|
||||
module->rewrite_sigspecs(ties_rewrite);
|
||||
if (applied_ties.size()) {
|
||||
log("Replacing %zu input terminal bits with tie-togethers in module '%s'\n",
|
||||
applied_ties.size(), log_id(module));
|
||||
}
|
||||
return did_something;
|
||||
}
|
||||
};
|
||||
|
||||
struct OptHierPass : Pass {
|
||||
OptHierPass() : Pass("opt_hier", "perform cross-boundary optimization") {}
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" opt_hier [selection]\n");
|
||||
log("\n");
|
||||
log("This pass considers the design hierarchy and propagates unused signals, constant\n");
|
||||
log("signals, and tied-together signals across module boundaries to facilitate\n");
|
||||
log("optimization. Only the selected modules are affected.\n");
|
||||
log("\n");
|
||||
log("Note this pass changes port semantics on modules which are not the top.\n");
|
||||
log("\n");
|
||||
}
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *d) override
|
||||
{
|
||||
log_header(d, "Executing OPT_HIER pass.\n");
|
||||
|
||||
size_t argidx;
|
||||
for (argidx = 1; argidx < args.size(); argidx++) {
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, d);
|
||||
|
||||
if (!d->top_module())
|
||||
log_cmd_error("Top module needs to be selected for opt_hier\n");
|
||||
|
||||
dict<IdString, ModuleIndex> indices;
|
||||
for (auto module : d->modules()) {
|
||||
log_debug("Building index for %s\n", log_id(module));
|
||||
indices.emplace(module->name, ModuleIndex(module));
|
||||
}
|
||||
|
||||
dict<IdString, UsageData> usage_datas;
|
||||
for (auto module : d->selected_modules(RTLIL::SELECT_WHOLE_ONLY, RTLIL::SB_UNBOXED_CMDERR)) {
|
||||
if (module->get_bool_attribute(ID::top))
|
||||
continue;
|
||||
|
||||
log_debug("Starting usage data for %s\n", log_id(module));
|
||||
usage_datas.emplace(module->name, UsageData(module));
|
||||
}
|
||||
|
||||
for (auto module : d->modules()) {
|
||||
for (auto cell : module->cells()) {
|
||||
if (usage_datas.count(cell->type)) {
|
||||
log_debug("Account for instance %s of %s in %s\n", log_id(cell), log_id(cell->type), log_id(module));
|
||||
usage_datas.at(cell->type).refine(cell, indices.at(module->name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool did_something = false;
|
||||
for (auto module : d->selected_modules(RTLIL::SELECT_WHOLE_ONLY, RTLIL::SB_UNBOXED_CMDERR)) {
|
||||
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();
|
||||
}
|
||||
|
||||
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));
|
||||
did_something |= indices.at(cell->type).apply_changes(parent_index, cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (did_something)
|
||||
d->scratchpad_set_bool("opt.did_something", true);
|
||||
}
|
||||
} OptHierPass;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
|
|
@ -47,6 +47,11 @@ struct SynthPass : public ScriptPass {
|
|||
log(" flatten the design before synthesis. this will pass '-auto-top' to\n");
|
||||
log(" 'hierarchy' if no top module is specified.\n");
|
||||
log("\n");
|
||||
log(" -hieropt\n");
|
||||
log(" enable hierarchical optimization. this option is useful when `-flatten'\n");
|
||||
log(" is not used, or when selected modules are marked with 'keep_hierarchy'\n.");
|
||||
log(" to prevent their dissolution.\n");
|
||||
log("\n");
|
||||
log(" -encfile <file>\n");
|
||||
log(" passed to 'fsm_recode' via 'fsm'\n");
|
||||
log("\n");
|
||||
|
|
@ -99,7 +104,7 @@ struct SynthPass : public ScriptPass {
|
|||
}
|
||||
|
||||
string top_module, fsm_opts, memory_opts, abc;
|
||||
bool autotop, flatten, noalumacc, nofsm, noabc, noshare, flowmap, booth;
|
||||
bool autotop, flatten, noalumacc, nofsm, noabc, noshare, flowmap, booth, hieropt;
|
||||
int lut;
|
||||
std::vector<std::string> techmap_maps;
|
||||
|
||||
|
|
@ -118,6 +123,7 @@ struct SynthPass : public ScriptPass {
|
|||
noshare = false;
|
||||
flowmap = false;
|
||||
booth = false;
|
||||
hieropt = false;
|
||||
abc = "abc";
|
||||
techmap_maps.clear();
|
||||
}
|
||||
|
|
@ -201,6 +207,10 @@ struct SynthPass : public ScriptPass {
|
|||
techmap_maps.push_back(args[++argidx]);
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-hieropt") {
|
||||
hieropt = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
|
@ -223,6 +233,12 @@ struct SynthPass : public ScriptPass {
|
|||
|
||||
void script() override
|
||||
{
|
||||
std::string hieropt_flag;
|
||||
if (help_mode)
|
||||
hieropt_flag = " [-hier]";
|
||||
else
|
||||
hieropt_flag = hieropt ? " -hier" : "";
|
||||
|
||||
if (check_label("begin")) {
|
||||
if (help_mode) {
|
||||
run("hierarchy -check [-top <top> | -auto-top]");
|
||||
|
|
@ -247,7 +263,7 @@ struct SynthPass : public ScriptPass {
|
|||
run("opt -nodffe -nosdff");
|
||||
if (!nofsm || help_mode)
|
||||
run("fsm" + fsm_opts, " (unless -nofsm)");
|
||||
run("opt");
|
||||
run("opt" + hieropt_flag);
|
||||
run("wreduce");
|
||||
run("peepopt");
|
||||
run("opt_clean");
|
||||
|
|
@ -261,13 +277,13 @@ struct SynthPass : public ScriptPass {
|
|||
run("alumacc", " (unless -noalumacc)");
|
||||
if (!noshare)
|
||||
run("share", " (unless -noshare)");
|
||||
run("opt");
|
||||
run("opt" + hieropt_flag);
|
||||
run("memory -nomap" + memory_opts);
|
||||
run("opt_clean");
|
||||
}
|
||||
|
||||
if (check_label("fine")) {
|
||||
run("opt -fast -full");
|
||||
run("opt -fast -full" + hieropt_flag);
|
||||
run("memory_map");
|
||||
run("opt -full");
|
||||
if (help_mode) {
|
||||
|
|
@ -291,7 +307,7 @@ struct SynthPass : public ScriptPass {
|
|||
} else if (flowmap) {
|
||||
run(stringf("flowmap -maxlut %d", lut));
|
||||
}
|
||||
run("opt -fast");
|
||||
run("opt -fast" + hieropt_flag);
|
||||
|
||||
if ((!noabc && !flowmap) || help_mode) {
|
||||
#ifdef YOSYS_ENABLE_ABC
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
yosys -import
|
||||
|
||||
# per each opt_hier_*.v source file, confirm flattening and hieropt+flattening
|
||||
# are combinationally equivalent
|
||||
foreach fn [glob opt_hier_*.v] {
|
||||
log -header "Test $fn"
|
||||
log -push
|
||||
design -reset
|
||||
|
||||
read_verilog $fn
|
||||
hierarchy -auto-top
|
||||
prep -top top
|
||||
design -save start
|
||||
flatten
|
||||
design -save gold
|
||||
design -load start
|
||||
opt -hier
|
||||
# check any instances marked `should_get_optimized_out` were
|
||||
# indeed optimized out
|
||||
select -assert-none a:should_get_optimized_out
|
||||
dump
|
||||
flatten
|
||||
design -save gate
|
||||
|
||||
design -reset
|
||||
design -copy-from gold -as gold A:top
|
||||
design -copy-from gate -as gate A:top
|
||||
yosys rename -hide
|
||||
equiv_make gold gate equiv
|
||||
equiv_induct equiv
|
||||
equiv_status -assert equiv
|
||||
|
||||
log -pop
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
module m(input a, output y1, output y2);
|
||||
assign y1 = a;
|
||||
assign y2 = a;
|
||||
endmodule
|
||||
|
||||
module top(input a, output y2, output y1);
|
||||
m inst(.a(a), .y1(y1), .y2(y2));
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module m(input [3:0] i, output [3:0] y);
|
||||
assign y = i + 1;
|
||||
endmodule
|
||||
|
||||
module top(output [3:0] y);
|
||||
m inst(.i(4), .y(y));
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
(* blackbox *)
|
||||
module bb(output y);
|
||||
endmodule
|
||||
|
||||
// all instances of `m` tie together a[1], a[2]
|
||||
// this can be used to conclude y[0]=0
|
||||
module m(input [3:0] a, output [1:0] y, output x);
|
||||
assign y[0] = a[1] != a[2];
|
||||
assign x = a[0] ^ a[3];
|
||||
(* should_get_optimized_out *)
|
||||
bb bb1(.y(y[1]));
|
||||
endmodule
|
||||
|
||||
module top(input j, output z, output [2:0] x);
|
||||
wire [1:0] y1;
|
||||
wire [1:0] y2;
|
||||
wire [1:0] y3;
|
||||
m inst1(.a(0), .y(y1), .x(x[0]));
|
||||
m inst2(.a(15), .y(y2), .x(x[1]));
|
||||
m inst3(.a({1'b1, j, j, 1'b0}), .y(y3), .x(x[2]));
|
||||
assign z = (&y1) ^ (&y2) ^ (&y3);
|
||||
endmodule
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
source ../gen-tests-makefile.sh
|
||||
generate_mk --yosys-scripts
|
||||
generate_mk --yosys-scripts --tcl-scripts
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
SF:lcov.v
|
||||
DA:2,1
|
||||
DA:3,1
|
||||
DA:4,1
|
||||
DA:5,1
|
||||
DA:6,1
|
||||
DA:7,1
|
||||
DA:8,1
|
||||
DA:9,0
|
||||
DA:13,1
|
||||
DA:14,1
|
||||
DA:17,1
|
||||
DA:18,1
|
||||
DA:19,1
|
||||
DA:21,0
|
||||
DA:22,0
|
||||
DA:23,0
|
||||
DA:24,0
|
||||
DA:25,0
|
||||
DA:26,0
|
||||
DA:27,0
|
||||
DA:28,0
|
||||
DA:29,0
|
||||
DA:30,0
|
||||
DA:32,1
|
||||
DA:33,1
|
||||
DA:36,0
|
||||
DA:37,0
|
||||
DA:38,0
|
||||
DA:40,0
|
||||
DA:41,0
|
||||
DA:42,0
|
||||
DA:43,0
|
||||
DA:44,0
|
||||
DA:45,0
|
||||
DA:46,0
|
||||
DA:48,0
|
||||
DA:49,0
|
||||
DA:52,1
|
||||
DA:53,0
|
||||
DA:56,1
|
||||
LF:40
|
||||
LH:16
|
||||
end_of_record
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
module top (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
input wire [7:0] a,
|
||||
input wire [7:0] b,
|
||||
input wire [3:0] c,
|
||||
input wire en,
|
||||
output wire [7:0] out1,
|
||||
output wire [7:0] out2
|
||||
);
|
||||
|
||||
// Shared intermediate signal
|
||||
wire [7:0] ab_sum;
|
||||
assign ab_sum = a + b;
|
||||
|
||||
// Logic cone for out1
|
||||
wire [7:0] cone1_1, cone1_2;
|
||||
assign cone1_1 = ab_sum ^ {4{c[1:0]}};
|
||||
assign cone1_2 = (a & b) | {4{c[3:2]}};
|
||||
|
||||
reg [7:0] reg1, reg2; // only reg1 feeds into out1, but both share a source location
|
||||
always @(posedge clk or posedge rst) begin
|
||||
if (rst) begin
|
||||
reg1 <= 8'h00;
|
||||
reg2 <= 8'hFF;
|
||||
end else if (en) begin
|
||||
reg1 <= cone1_1 + cone1_2;
|
||||
reg2 <= cone1_2 - cone1_1;
|
||||
end
|
||||
end
|
||||
|
||||
wire [7:0] cone1_3;
|
||||
assign cone1_3 = reg1 & ~a[0];
|
||||
|
||||
// Logic cone for out2
|
||||
wire [7:0] cone2_1, cone2_2;
|
||||
assign cone2_1 = (ab_sum << 1) | (a >> 2);
|
||||
assign cone2_2 = (b ^ {4{c[2:0]}}) & 8'hAA;
|
||||
|
||||
reg [7:0] reg3;
|
||||
always @(posedge clk or posedge rst) begin
|
||||
if (rst)
|
||||
reg3 <= 8'h0F;
|
||||
else
|
||||
reg3 <= cone2_1 ^ cone2_2 ^ reg1[7:0];
|
||||
end
|
||||
|
||||
wire [7:0] cone2_3;
|
||||
assign cone2_3 = reg3 | (reg2 ^ 8'h55);
|
||||
|
||||
// Outputs
|
||||
assign out1 = cone1_3 | (reg1 ^ 8'hA5);
|
||||
assign out2 = cone2_3 & (reg3 | 8'h5A);
|
||||
|
||||
always @(posedge clk) begin
|
||||
assert (out1 == 8'h42);
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
read_verilog -formal lcov.v
|
||||
prep -top top
|
||||
async2sync
|
||||
chformal -lower
|
||||
select -set covered t:$assert %ci*
|
||||
select -set irrelevant o:* %ci* %n
|
||||
linecoverage -lcov lcov.out @covered @irrelevant %u
|
||||
exec -expect-return 0 -- diff -q lcov.out lcov.gold
|
||||
Loading…
Reference in New Issue