Skip to content

Commit

Permalink
timeest: Add command for critical path estimation
Browse files Browse the repository at this point in the history
  • Loading branch information
povik committed May 31, 2024
1 parent a84e4f4 commit eac2411
Show file tree
Hide file tree
Showing 2 changed files with 362 additions and 0 deletions.
1 change: 1 addition & 0 deletions passes/cmds/Makefile.inc
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ OBJS += passes/cmds/clean_zerowidth.o
OBJS += passes/cmds/xprop.o
OBJS += passes/cmds/dft_tag.o
OBJS += passes/cmds/future.o
OBJS += passes/cmds/timeest.o
361 changes: 361 additions & 0 deletions passes/cmds/timeest.cc
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

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, gcc-10)

explicit specialization of ‘template<class T> struct Yosys::hashlib::hash_ops’ outside its namespace must use a nested-name-specifier [-fpermissive]

Check failure on line 30 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, gcc)

explicit specialization of ‘template<class T> struct Yosys::hashlib::hash_ops’ outside its namespace must use a nested-name-specifier [-fpermissive]

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

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, clang-14)

no member named 'numeric_limits' in namespace 'std'

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, clang-14)

unexpected type name 'arrivalint': expected expression

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, clang-14)

no matching function for call to 'min'

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, clang)

no member named 'numeric_limits' in namespace 'std'

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, clang)

unexpected type name 'arrivalint': expected expression

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, clang)

no matching function for call to 'min'

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-22.04, clang-11)

no member named 'numeric_limits' in namespace 'std'

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-22.04, clang-11)

unexpected type name 'arrivalint': expected expression

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-22.04, clang-11)

no matching function for call to 'min'

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, gcc)

‘numeric_limits’ is not a member of ‘std’

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, gcc)

expected primary-expression before ‘>’ token

Check failure on line 35 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (ubuntu-latest, gcc)

no matching function for call to ‘min()’

// 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)

Check failure on line 93 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (macos-13, clang)

use of overloaded operator '!=' is ambiguous (with operand types 'Yosys::RTLIL::SigSpec' and 'Yosys::RTLIL::SigBit')
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)

Check failure on line 133 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (macos-13, clang)

use of overloaded operator '!=' is ambiguous (with operand types 'RTLIL::SigSpec' and 'Yosys::RTLIL::SigBit')
continue;
add_seq(rd.cell, rd.data, {rd.addr, rd.srst, rd.en});
}
for (auto &wr : mem.wr_ports) {
if (sigmap(wr.clk) != clk)

Check failure on line 138 in passes/cmds/timeest.cc

View workflow job for this annotation

GitHub Actions / test-compile (macos-13, clang)

use of overloaded operator '!=' is ambiguous (with operand types 'RTLIL::SigSpec' and 'Yosys::RTLIL::SigBit')
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

0 comments on commit eac2411

Please sign in to comment.