-
Notifications
You must be signed in to change notification settings - Fork 896
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,361 @@ | ||
/* | ||
* yosys -- Yosys Open SYnthesis Suite | ||
* | ||
* Copyright (C) 2024 Martin Povišer <[email protected]> | ||
* | ||
* 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/sigtools.h" | ||
#include "kernel/register.h" | ||
#include "kernel/cellaigs.h" | ||
#include "kernel/utils.h" | ||
#include "kernel/ff.h" | ||
#include "kernel/mem.h" | ||
|
||
#include <assert.h> | ||
|
||
USING_YOSYS_NAMESPACE | ||
template<> struct hash_ops<AigNode *> : hash_ptr_ops {}; | ||
Check failure on line 30 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-latest, gcc-10)
|
||
|
||
PRIVATE_NAMESPACE_BEGIN | ||
|
||
typedef long int arrivalint; | ||
const arrivalint INF_PAST = std::numeric_limits<arrivalint>::min(); | ||
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-latest, clang-14)
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-latest, clang-14)
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-latest, clang-14)
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-latest, clang)
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-latest, clang)
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-latest, clang)
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-22.04, clang-11)
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-22.04, clang-11)
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-22.04, clang-11)
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-latest, gcc)
Check failure on line 35 in passes/cmds/timeest.cc GitHub Actions / test-compile (ubuntu-latest, gcc)
|
||
|
||
// each clock domain must have its own EstimateSta structure | ||
struct EstimateSta { | ||
SigMap sigmap; | ||
Module *m; | ||
SigBit clk; | ||
|
||
dict<std::pair<RTLIL::IdString, dict<RTLIL::IdString, RTLIL::Const>>, Aig> aigs; | ||
dict<Cell *, Aig *> cell_aigs; | ||
|
||
std::vector<std::pair<Cell *, SigBit>> launchers; | ||
std::vector<std::pair<Cell *, SigBit>> samplers; | ||
bool all_paths = false; | ||
|
||
void add_seq(Cell *cell, SigSpec launch, SigSpec sample) | ||
{ | ||
sigmap.apply(launch); | ||
sigmap.apply(sample); | ||
launch.sort_and_unify(); | ||
sample.sort_and_unify(); | ||
for (auto bit : launch) | ||
launchers.push_back(std::make_pair(cell, bit)); | ||
for (auto bit : sample) | ||
samplers.push_back(std::make_pair(cell, bit)); | ||
} | ||
|
||
int cell_type_factor(IdString type) | ||
{ | ||
if (type.in(ID($gt), ID($ge), ID($lt), ID($le), ID($add), ID($sub), | ||
ID($logic_not), ID($reduce_and), ID($reduce_or), ID($eq))) | ||
return 1; | ||
else | ||
return 2; | ||
} | ||
|
||
// TODO: ignores clock polarity | ||
EstimateSta(Module *m, SigBit clk) | ||
: sigmap(m), m(m), clk(clk) | ||
{ | ||
sigmap.apply(clk); | ||
} | ||
|
||
void run() | ||
{ | ||
log("Domain %s\n", log_signal(clk)); | ||
|
||
std::vector<Cell *> combinational; | ||
|
||
for (auto cell : m->cells()) { | ||
SigSpec launch, sample; | ||
if (RTLIL::builtin_ff_cell_types().count(cell->type)) { | ||
FfData ff(nullptr, cell); | ||
if (!ff.has_clk) { | ||
log_warning("Ignoring unsupported storage element '%s' (%s)\n", | ||
log_id(cell), log_id(cell->type)); | ||
continue; | ||
} | ||
if (ff.sig_clk != clk) | ||
continue; | ||
launch.append(ff.sig_q); | ||
sample.append(ff.sig_d); | ||
if (ff.has_ce) | ||
sample.append(ff.sig_ce); | ||
if (ff.has_srst) | ||
sample.append(ff.sig_srst); | ||
add_seq(cell, launch, sample); | ||
} else if (cell->is_mem_cell()) { | ||
// memories handled separately | ||
continue; | ||
} else if (cell->type == ID($scopeinfo)) { | ||
continue; | ||
} else { | ||
auto fingerprint = std::make_pair(cell->type, cell->parameters); | ||
if (!aigs.count(fingerprint)) { | ||
aigs.emplace(fingerprint, Aig(cell)); | ||
if (aigs.at(fingerprint).name.empty()) { | ||
log_error("Unsupported cell '%s' in module '%s'", | ||
log_id(cell->type), log_id(m)); | ||
} | ||
} | ||
|
||
combinational.push_back(cell); | ||
continue; | ||
} | ||
} | ||
|
||
for (auto cell : combinational) { | ||
auto fingerprint = std::make_pair(cell->type, cell->parameters); | ||
cell_aigs.emplace(cell, &aigs.at(fingerprint)); | ||
} | ||
|
||
for (auto &mem : Mem::get_all_memories(m)) { | ||
for (auto &rd : mem.rd_ports) { | ||
if (!rd.clk_enable) { | ||
log_error("Unsupported async memory port '%s'\n", log_id(rd.cell)); | ||
continue; | ||
} | ||
if (sigmap(rd.clk) != clk) | ||
continue; | ||
add_seq(rd.cell, rd.data, {rd.addr, rd.srst, rd.en}); | ||
} | ||
for (auto &wr : mem.wr_ports) { | ||
if (sigmap(wr.clk) != clk) | ||
continue; | ||
add_seq(wr.cell, {}, {wr.en, wr.addr, wr.data}); | ||
} | ||
} | ||
|
||
TopoSort<std::tuple<SigBit, Cell *, AigNode *>> topo; | ||
|
||
auto desc_aig = [&](Cell *cell, AigNode &node) { | ||
return std::make_tuple(RTLIL::S0, cell, &node); | ||
}; | ||
auto desc_sig = [&](SigBit bit) { | ||
return std::make_tuple(sigmap(bit), (Cell *) NULL, (AigNode *) NULL); | ||
}; | ||
|
||
for (auto cell : combinational) { | ||
assert(cell_aigs.count(cell)); | ||
Aig &aig = *cell_aigs.at(cell); | ||
for (auto &node : aig.nodes) { | ||
if (!node.portname.empty()) { | ||
topo.edge( | ||
desc_sig(cell->getPort(node.portname)[node.portbit]), | ||
desc_aig(cell, node) | ||
); | ||
} else if (node.left_parent < 0 && node.right_parent < 0) { | ||
// constant, nothing to do | ||
} else { | ||
topo.edge( | ||
desc_aig(cell, aig.nodes[node.left_parent]), | ||
desc_aig(cell, node) | ||
); | ||
topo.edge( | ||
desc_aig(cell, aig.nodes[node.right_parent]), | ||
desc_aig(cell, node) | ||
); | ||
} | ||
|
||
for (auto &oport : node.outports) { | ||
topo.edge( | ||
desc_aig(cell, node), | ||
desc_sig(cell->getPort(oport.first)[oport.second]) | ||
); | ||
} | ||
} | ||
} | ||
|
||
if (!topo.sort()) | ||
log_error("Module '%s' contains combinational loops", log_id(m)); | ||
|
||
dict<std::tuple<SigBit, Cell *, AigNode *>, arrivalint> levels; | ||
|
||
for (auto node : topo.sorted) | ||
levels[node] = INF_PAST; | ||
|
||
for (auto pair : launchers) | ||
levels[desc_sig(pair.second)] = 0; | ||
|
||
for (auto node : topo.sorted) { | ||
AigNode *aig_node = std::get<2>(node); | ||
if (aig_node) { | ||
Cell *cell = std::get<1>(node); | ||
Aig &aig = *cell_aigs.at(cell); | ||
if (!aig_node->portname.empty()) { | ||
SigBit bit = cell->getPort(aig_node->portname)[aig_node->portbit]; | ||
levels[node] = levels[desc_sig(bit)]; | ||
} else if (aig_node->left_parent < 0 && aig_node->right_parent < 0) { | ||
// constant, nothing to do | ||
} else { | ||
int left = levels[desc_aig(cell, aig.nodes[aig_node->left_parent])]; | ||
int right = levels[desc_aig(cell, aig.nodes[aig_node->right_parent])]; | ||
levels[node] = (std::max(left, right) + cell_type_factor(cell->type)); | ||
} | ||
|
||
for (auto &oport : aig_node->outports) { | ||
levels[desc_sig(cell->getPort(oport.first)[oport.second])] = levels[node]; | ||
} | ||
} | ||
} | ||
|
||
arrivalint crit = INF_PAST; | ||
for (auto pair : samplers) | ||
if (levels[desc_sig(pair.second)] > crit) | ||
crit = levels[desc_sig(pair.second)]; | ||
|
||
if (crit < 0) { | ||
log("No paths found\n"); | ||
return; | ||
} | ||
|
||
log("Critical path is %ld nodes long:\n\n", crit); | ||
|
||
// we use dict instead of pool because dict gives us | ||
// some compile-time errors related to hashing | ||
dict<std::tuple<SigBit, Cell *, AigNode *>, bool> critical; | ||
|
||
for (auto pair : samplers) { | ||
if (levels[desc_sig(pair.second)] == crit) { | ||
critical[desc_sig(pair.second)] = true; | ||
if (!all_paths) | ||
break; | ||
} | ||
} | ||
|
||
for (auto it = topo.sorted.rbegin(); it != topo.sorted.rend(); it++) { | ||
auto node = *it; | ||
AigNode *aig_node = std::get<2>(node); | ||
if (aig_node) { | ||
Cell *cell = std::get<1>(node); | ||
Aig &aig = *cell_aigs.at(cell); | ||
|
||
for (auto &oport : aig_node->outports) { | ||
//levels[desc_sig(cell->getPort(oport.first)[oport.second])] = levels[node]; | ||
if (critical.count(desc_sig(cell->getPort(oport.first)[oport.second]))) | ||
critical[node] = true; | ||
} | ||
|
||
if (!aig_node->portname.empty()) { | ||
SigBit bit = cell->getPort(aig_node->portname)[aig_node->portbit]; | ||
//levels[node] = levels[desc_sig(bit)]; | ||
if (critical.count(node)) | ||
critical[desc_sig(bit)] = true; | ||
} else if (aig_node->left_parent < 0 && aig_node->right_parent < 0) { | ||
// constant, nothing to do | ||
} else { | ||
auto left = desc_aig(cell, aig.nodes[aig_node->left_parent]); | ||
auto right = desc_aig(cell, aig.nodes[aig_node->right_parent]); | ||
//levels[node] = (std::max(left, right) + 1); | ||
int crit_input_lvl = levels[node] - cell_type_factor(cell->type); | ||
if (critical.count(node)) { | ||
bool left_critical = (levels[left] == crit_input_lvl); | ||
bool right_critical = (levels[right] == crit_input_lvl); | ||
if (all_paths) { | ||
if (left_critical) | ||
critical[left] = true; | ||
if (right_critical) | ||
critical[right] = true; | ||
} else { | ||
if (left_critical) | ||
critical[left] = true; | ||
else if (right_critical) | ||
critical[right] = true; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
pool<Cell *> printed; | ||
for (auto node : topo.sorted) { | ||
if (!critical.count(node)) | ||
continue; | ||
AigNode *aig_node = std::get<2>(node); | ||
if (aig_node) { | ||
Cell *cell = std::get<1>(node); | ||
if (!printed.count(cell)) { | ||
std::string cell_src; | ||
if (cell->has_attribute(ID::src)) { | ||
std::string src_attr = cell->get_src_attribute(); | ||
cell_src = stringf(" source: %s", src_attr.c_str()); | ||
} | ||
log(" cell %s (%s)%s\n", log_id(cell), log_id(cell->type), cell_src.c_str()); | ||
printed.insert(cell); | ||
} | ||
} else { | ||
SigBit bit = std::get<0>(node); | ||
std::string wire_src; | ||
if (bit.wire && bit.wire->has_attribute(ID::src)) { | ||
std::string src_attr = bit.wire->get_src_attribute(); | ||
wire_src = stringf(" source: %s", src_attr.c_str()); | ||
} | ||
log(" wire %s%s (level %ld)\n", log_signal(bit), wire_src.c_str(), levels[node]); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
struct TimeestPass : Pass { | ||
TimeestPass() : Pass("timeest", "estimate timing") {} | ||
void help() override | ||
{ | ||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| | ||
log("\n"); | ||
log(" timeest [-clk <clk_signal>] [selection]\n"); | ||
log("\n"); | ||
log("Estimate the critical path in clock domain <clk_signal> by counting AIG nodes.\n"); | ||
log("\n"); | ||
} | ||
void execute(std::vector<std::string> args, RTLIL::Design *d) override | ||
{ | ||
log_header(d, "Executing TIMEEST pass. (estimate timing)\n"); | ||
|
||
std::string clk; | ||
bool all_paths = false; | ||
size_t argidx; | ||
for (argidx = 1; argidx < args.size(); argidx++) { | ||
if (args[argidx] == "-all_paths") { | ||
all_paths = true; | ||
continue; | ||
} | ||
if (args[argidx] == "-clk" && argidx + 1 < args.size()) { | ||
clk = args[++argidx]; | ||
continue; | ||
} | ||
break; | ||
} | ||
extra_args(args, argidx, d); | ||
|
||
if (clk.empty()) | ||
log_cmd_error("No -clk argument provided\n"); | ||
|
||
for (auto m : d->selected_modules()) { | ||
if (!m->wire(RTLIL::escape_id(clk))) { | ||
log_warning("No domain '%s' in module %s\n", clk.c_str(), log_id(m)); | ||
continue; | ||
} | ||
|
||
EstimateSta sta(m, SigBit(m->wire(RTLIL::escape_id(clk)), 0)); | ||
sta.all_paths = all_paths; | ||
sta.run(); | ||
} | ||
} | ||
} TimeestPass; | ||
|
||
PRIVATE_NAMESPACE_END |