From 48e6bb99651bfc50ec6e292e8f29707872babd56 Mon Sep 17 00:00:00 2001 From: clemens Date: Sat, 9 Aug 2025 15:38:59 +0200 Subject: [PATCH 1/4] New Sta command called sta2 that can use delays form config files(liberty files) to determine paths lengths. --- passes/cmds/Makefile.inc | 1 + passes/cmds/sta2.cc | 571 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 572 insertions(+) create mode 100644 passes/cmds/sta2.cc diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 9bf615a7ed4..5d4a508b6a8 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -57,3 +57,4 @@ OBJS += passes/cmds/abstract.o OBJS += passes/cmds/test_select.o OBJS += passes/cmds/timeest.o OBJS += passes/cmds/linecoverage.o +OBJS += passes/cmds/sta2.o diff --git a/passes/cmds/sta2.cc b/passes/cmds/sta2.cc new file mode 100644 index 00000000000..a5b780c605b --- /dev/null +++ b/passes/cmds/sta2.cc @@ -0,0 +1,571 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Clifford Wolf + * (C) 2019 Eddie Hung + * + * 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/cost.h" +#include "kernel/ff.h" +#include "kernel/gzip.h" +#include "kernel/modtools.h" +#include "kernel/sigtools.h" +#include "kernel/timinginfo.h" +#include "kernel/yosys.h" +#include "libs/json11/json11.hpp" +#include "passes/techmap/libparse.h" +#include +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct cell_delay_t { + double delay; + bool is_flipflop; + vector single_parameter_delay; + vector> double_parameter_delay; + vector parameter_names; +}; + +struct Sta_Path { + double delay; + RTLIL::IdString wire; + vector cells; + vector delays; // delays of the cells in the path, used for debugging and analysis. + Sta_Path(double d, RTLIL::IdString p, vector c, vector ds) : delay(d), wire(p), cells(c), delays(ds) {} + Sta_Path() : delay(0), wire(), cells(), delays() {} + Sta_Path(const Sta_Path &other) : delay(other.delay), wire(other.wire), cells(other.cells), delays(other.delays) {} + Hasher hash_into(Hasher h) const + { + // the delay is not required for the hash, as it's a result of the cells. + + for (auto &cell : cells) + h.eat(cell->name); + return h; + } + void add_delay(Cell *cell, dict cell_delays) + { + if (cell == nullptr) { + log_error("Cell is null, cannot add delay.\n"); + } + if (!cell_delays.count(cell->type)) { + return; // return current delay if cell type not found + } + auto cell_delay = cell_delays.at(cell->type); + // deal with parameterized delays. + // extract width of ports from the cells: + vector widths; + if (cell_delay.parameter_names.size() > 0) { + for (auto &it : cell_delay.parameter_names) { + RTLIL::IdString port_name; + // TODO: there has to be a better way to do this + if (it == "A") { + port_name = ID::A; + } else if (it == "B") { + port_name = ID::B; + } else if (it == "Y") { + port_name = ID::Y; + } else if (it == "Q") { + port_name = ID::Q; + } else if (it == "S") { + port_name = ID::S; + } else { + port_name = ID(it); + } + if (cell->hasPort(port_name)) { + int width = GetSize(cell->getPort(port_name)); + widths.push_back(width); + } else { + widths.push_back(0); + } + } + } else { + int width_a = cell->hasPort(ID::A) ? GetSize(cell->getPort(ID::A)) : 0; + int width_b = cell->hasPort(ID::B) ? GetSize(cell->getPort(ID::B)) : 0; + int width_y = cell->hasPort(ID::Y) ? GetSize(cell->getPort(ID::Y)) : 0; + int width_q = cell->hasPort(ID::Q) ? GetSize(cell->getPort(ID::Q)) : 0; + int max_width = max({width_a, width_b, width_y, width_q}); + widths.push_back(max_width); + } + if (cell_delay.single_parameter_delay.size() > 0) { + if (widths.size() != 1) { + // we need to have exactly one parameter for single parameter delay. + log_error("Cell %s has single parameter delay, but has %d parameters.\n", cell->name.c_str(), widths.size()); + } + if (cell_delay.single_parameter_delay.size() > widths.at(0) - 1) { + delay += cell_delay.single_parameter_delay.at(widths.at(0) - 1); + delays.push_back(cell_delay.single_parameter_delay.at(widths.at(0) - 1)); + } else { + delay += cell_delay.single_parameter_delay.back(); + delays.push_back(cell_delay.single_parameter_delay.back()); + } + } else if (cell_delay.double_parameter_delay.size() > 0) { + if (widths.size() != 2) { + // we need to have exactly two parameters for double parameter delay. + log_error("Cell %s has double parameter delay, but has %d parameters.\n", cell->name.c_str(), widths.size()); + } + int width_a = widths.at(1); + int width_b = widths.at(0); + if (width_a > 0 && width_b > 0) { + if (cell_delay.double_parameter_delay.size() > width_a - 1 && + cell_delay.double_parameter_delay.at(width_a - 1).size() > width_b - 1) { + delay += cell_delay.double_parameter_delay.at(width_a - 1).at(width_b - 1); + delays.push_back(cell_delay.double_parameter_delay.at(width_a - 1).at(width_b - 1)); + } else { + delay += cell_delay.double_parameter_delay.back().back(); + delays.push_back(cell_delay.double_parameter_delay.back().back()); + } + } else { + log_error("Cell %s has double parameter delay, but has zero width parameters.\n", cell->name.c_str()); + } + } else { + delay += cell_delay.delay; + delays.push_back(cell_delay.delay); + } + + return; + } + bool operator==(const Sta_Path &other) const { return delay == other.delay && wire == other.wire && cells == other.cells; } +}; + +struct Sta2Worker { + Design *design; + std::deque queue; + dict cell_delays; + std::map> analysed; + pool analysed_max_paths; + pool analysed_min_paths; + std::map cell_max_analysed; + std::map cell_min_analysed; + std::map modwalkers; + + Sta2Worker(RTLIL::Design *design, dict cell_delay) : design(design), cell_delays(cell_delay) {} + + void run(RTLIL::Module *module, bool hold_violations, bool setup_violations) + { + auto modules = design->modules().to_vector(); + if (module != nullptr) { + modules = {module}; + } + for (auto module : modules) { + SigMap sigmap(module); + ModWalker modwalker(design); + modwalker.setup(module); + for (auto port : module->ports) { + auto wire = module->wire(port); + if (wire->port_input) { + for (auto &bit : sigmap(wire)) { + + auto first_cells = modwalker.signal_consumers[bit]; + for (auto &f_cell : first_cells) { + if (f_cell.cell == nullptr) { + continue; + } + + vector new_vector = {f_cell.cell}; + Sta_Path p(0, f_cell.cell->name, new_vector, vector()); + p.add_delay(f_cell.cell, cell_delays); + queue.push_back(p); + } + } + } + } + for (auto cell : module->cells()) { + if (RTLIL::builtin_ff_cell_types().count(cell->type) || + (cell_delays.count(cell->type) && cell_delays.at(cell->type).is_flipflop)) { + vector new_vector; + new_vector.push_back(cell); + Sta_Path p(0, cell->name, new_vector, vector()); + p.add_delay(cell, cell_delays); + queue.push_back(p); + } else { + continue; + } + + while (queue.size() > 0) { + auto entry = queue.back(); + queue.pop_back(); + auto cell = entry.cells.back(); + + if (design->module(cell->type) == nullptr) { + + } else if (design->module(cell->type)->get_blackbox_attribute()) { + + if (!cell_delays.count(cell->type)) { + continue; + } + } + pool analysed_cells; + auto signals = modwalker.cell_outputs[cell]; + for (auto sig : signals) { + auto consumers = modwalker.signal_consumers[sig]; + + // figure out if we have reached a output cell. + if (sig.wire->port_output) { + // if we have reached a output port, print it. + if (analysed.count(sig.wire->name)) { + if (analysed[sig.wire->name].second.delay < entry.delay) { + // reached new maximum delay for this cell. + analysed[sig.wire->name].second = entry; + } else if (analysed[sig.wire->name].first.delay > entry.delay) { + // reached new minimum delay for this cell. + analysed[sig.wire->name].first = entry; + } + } else { + analysed[sig.wire->name] = pair(entry, entry); + } + } + + if (consumers.empty()) { + + continue; + } + Yosys::RTLIL::Cell *last_consumer = nullptr; + for (auto &consumer_bit : consumers) { + auto consumer = consumer_bit.cell; + if (analysed_cells.count(consumer->name)) { + continue; + } + + analysed_cells.insert(consumer->name); + Sta_Path new_entry(entry.delay, entry.wire, entry.cells, entry.delays); + new_entry.cells.push_back(consumer); + new_entry.add_delay(consumer, cell_delays); + double delay = new_entry.delays.back(); + // if we have already reached this cell skip it if between max or min. + if (cell_max_analysed.count(consumer->name) && + cell_max_analysed[consumer->name] > entry.delay) { + if (!hold_violations) + continue; // if we are not looking for hold violations, skip this. + if (cell_min_analysed.count(consumer->name) && + cell_min_analysed[consumer->name] < entry.delay) { + continue; + } else { + cell_min_analysed[consumer->name] = delay; + } + } else { + cell_max_analysed[consumer->name] = delay; + } + + // we have found a cell that is connected. + if (RTLIL::builtin_ff_cell_types().count(consumer->type) || + (cell_delays.count(consumer->type) && cell_delays.at(consumer->type).is_flipflop)) { + // We have a flip-flop. This is the end of this path. + // check that we don't have a clk or rst port. In that case we don't want to include + // this. + if (RTLIL::builtin_ff_cell_types().count(consumer->type)) { + FfData ff(nullptr, consumer); + if (sigmap(sig) == sigmap(ff.sig_clk)) { + continue; + } + if (sigmap(ff.sig_clr).size() == 1 && sigmap(sig) == sigmap(ff.sig_clr)) { + continue; + } + } + if (analysed.count(entry.cells.front()->name)) { + + if (analysed[consumer->name].second.delay < new_entry.delay) { + // reached new maximum delay for this cell. + analysed[consumer->name].second = new_entry; + continue; + } else if (analysed[consumer->name].first.delay > new_entry.delay) { + // reached new minimum delay for this cell. + analysed[consumer->name].first = new_entry; + continue; + } + } else { + + analysed[consumer->name] = pair(new_entry, new_entry); + } + } else { + // check for loops. + // a loop here does not mean a loop in the circuit. + // therefore we just print a warning. + auto loop = false; + for (auto &it : entry.cells) { + if (it->name == consumer->name) { + log_warning("warning: potential loop detected: %s %s \n", + cell->name.c_str(), consumer->name.c_str()); + loop = true; + break; + } + } + if (loop) + continue; + + queue.push_back(new_entry); + } + } + } + } + } + } + } +}; + +void read_liberty_celldelay(dict &cell_delay, string liberty_file) +{ + std::istream *f = uncompressed(liberty_file.c_str()); + yosys_input_files.insert(liberty_file); + LibertyParser libparser(*f, liberty_file); + delete f; + + for (auto cell : libparser.ast->children) { + if (cell->id != "cell" || cell->args.size() != 1) + continue; + + const LibertyAst *ar = cell->find("delay"); + bool is_flip_flop = cell->find("ff") != nullptr; + vector single_parameter_delay; + vector> double_parameter_delay; + vector port_names; + const LibertyAst *sar = cell->find("single_delay_parameterised"); + if (sar != nullptr) { + for (const auto &s : sar->args) { + double value = 0; + auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); + // ec != std::errc() means parse error, or ptr didn't consume entire string + if (ec != std::errc() || ptr != s.data() + s.size()) + break; + single_parameter_delay.push_back(value); + } + if (single_parameter_delay.size() == 0) + log_error("single delay parameterisation array does not contain values: %s\n", + sar->args[single_parameter_delay.size() - 1].c_str()); + // check if it is a double parameterised delay + } + const LibertyAst *dar = cell->find("double_delay_parameterised"); + if (dar != nullptr) { + for (const auto &s : dar->args) { + + vector sub_array; + std::string::size_type start = 0; + std::string::size_type end = s.find_first_of(",", start); + while (end != std::string::npos) { + sub_array.push_back(s.substr(start, end - start)); + start = end + 1; + end = s.find_first_of(",", start); + } + sub_array.push_back(s.substr(start, end)); + + vector cast_sub_array; + for (const auto &s : sub_array) { + double value = 0; + auto [ptr, ec] = std::from_chars(s.data() + 1, s.data() + s.size(), value); + if (ec != std::errc() || ptr != s.data() + s.size()) + break; + cast_sub_array.push_back(value); + } + double_parameter_delay.push_back(cast_sub_array); + if (cast_sub_array.size() == 0) + log_error("double delay parameterisation array does not contain values: %s\n", + dar->args[double_parameter_delay.size() - 1].c_str()); + } + } + const LibertyAst *par = cell->find("port_names"); + if (par != nullptr) { + for (const auto &s : par->args) { + port_names.push_back(s); + } + } + + if (ar != nullptr && !ar->value.empty()) { + string prefix = cell->args[0].substr(0, 1) == "$" ? "" : "\\"; + cell_delay[prefix + cell->args[0]] = {atof(ar->value.c_str()), is_flip_flop, single_parameter_delay, double_parameter_delay, + port_names}; + } + } +} + +void generate_timing_report(const pool &max_paths, const pool &min_paths, double max_delay, double min_delay, bool names, + bool paths) +{ + log("Number of paths longer than max delay %f: %d \n", max_delay, max_paths.size()); + log("Number of paths shorter than min delay %f: %d \n", min_delay, min_paths.size()); + + // Create ordered versions of max_delays and min_delays + auto ordered_max_paths = vector(max_paths.begin(), max_paths.end()); + auto ordered_min_paths = vector(min_paths.begin(), min_paths.end()); + std::sort(ordered_max_paths.begin(), ordered_max_paths.end(), [](const Sta_Path &a, const Sta_Path &b) { return a.delay > b.delay; }); + std::sort(ordered_min_paths.begin(), ordered_min_paths.end(), [](const Sta_Path &a, const Sta_Path &b) { return a.delay < b.delay; }); + + if (names) { + for (auto &it : ordered_max_paths) { + log("max_path: start: %s, end: %s, delay: %f, cells: %u \n", it.cells.front()->name.c_str(), it.cells.back()->name.c_str(), + it.delay, it.cells.size()); + } + for (auto &it : ordered_min_paths) { + log("min_path: start: %s, end: %s, delay: %f, cells: %u \n", it.cells.front()->name.c_str(), it.cells.back()->name.c_str(), + it.delay, it.cells.size()); + } + } + + if (paths) { + for (auto &it : ordered_max_paths) { + log("max_path: start: %s, end: %s, delay: %f, cells: %u , delays_length: %u\n", it.cells.front()->name.c_str(), + it.cells.back()->name.c_str(), it.delay, it.cells.size(), it.delays.size()); + int i = 0; + for (auto &cell : it.cells) { + if (i < 0 || i >= it.delays.size()) { + log(" cell: %s , delay: %d-> \n", cell->name.c_str(), 0); + } else { + log(" cell: %s , delay: %f -> \n", cell->name.c_str(), it.delays.at(i)); + } + i++; + } + } + for (auto &it : ordered_min_paths) { + log("min_path: start: %s, end: %s, delay: %f, cells: %u \n", it.cells.front()->name.c_str(), it.cells.back()->name.c_str(), + it.delay, it.cells.size()); + for (auto &cell : it.cells) { + log(" cell: %s -> \n", cell->name.c_str()); + } + } + } +} +struct Sta2Pass : public Pass { + Sta2Pass() : Pass("sta2", "perform static timing analysis") {} + + void help() override +{ + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" sta2 [options] [selection]\n"); + log("\n"); + log("This command performs static timing analysis on the design.\n\n"); + log("config file content: \n\n"); + log("It needs a config file to read the cell delay information.\n"); + log("The config file should be in the same format as the liberty file.\n"); + log("each cell needs to have a delay parameter.\n"); + log("It cannot parse the timing() section of the liberty file.\n"); + log("there is the option to use parameterised delays. This allows STA to run early on in the flow.\n"); + log("the delay can be parameterised by the width of the ports of the cells.\n"); + log("There is the option to use single parameter delays, which are indexed by max port width of a cell.\n"); + log("There is the option to use double parameter delays, here the two ports that are used for indexing can be specified.\n"); + log("\n"); + log("Extended liberty file fields for STA:\n"); + log(" ff: true; - marks cells that terminate timing paths\n"); + log(" delay: ; - basic delay for non-parameterized cells\n"); + log(" single_delay_parameterised(...); - delay values indexed by max port width\n"); + log(" port_names(\"A\", \"B\"); - specifies ports for multi-dimensional arrays\n"); + log(" double_delay_parameterised(...); - 2D delay array (requires port_names)\n"); + log(" Format: (\"val1,val2,val3,\", \"val4,val5,val6,\", ...) - strings delimit dimensions\n"); + log("\n"); + log("\n"); + log(" -config \n"); + log(" read cell delay information from config file\n"); + log("\n"); + log(" -top \n"); + log(" use the specified module as the top level module\n"); + log("\n"); + log(" -max_delay \n"); + log(" report paths longer than the specified maximum delay\n"); + log("\n"); + log(" -min_delay \n"); + log(" report paths shorter than the specified minimum delay\n"); + log("\n"); + log(" -v\n"); + log(" verbose mode - show path start/end names and basic statistics\n"); + log("\n"); + log(" -vv\n"); + log(" very verbose mode - show detailed path information including\n"); + log(" individual cell delays\n"); + log("\n"); +} + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing STA pass (static timing analysis).\n"); + RTLIL::Module *top_mod = design->top_module(); + dict cell_delay; + bool names = false; + bool paths = false; + double max_delay = 0; + double min_delay = 0; + size_t argidx; + + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-config" && argidx + 1 < args.size()) { + string liberty_file = args[++argidx]; + rewrite_filename(liberty_file); + read_liberty_celldelay(cell_delay, liberty_file); + continue; + } + if (args[argidx] == "-top" && argidx + 1 < args.size()) { + if (design->module(RTLIL::escape_id(args[argidx + 1])) == nullptr) + log_cmd_error("Can't find module %s.\n", args[argidx + 1].c_str()); + top_mod = design->module(RTLIL::escape_id(args[++argidx])); + continue; + } + if (args[argidx] == "-max_delay" && argidx + 1 < args.size()) { + argidx++; + max_delay = atof(args[argidx].c_str()); + continue; + } + if (args[argidx] == "-min_delay" && argidx + 1 < args.size()) { + argidx++; + min_delay = atof(args[argidx].c_str()); + continue; + } + if (args[argidx] == "-v") { + log("verbose mode enabled.\n"); + names = true; + continue; + } + if (args[argidx] == "-vv") { + log("very verbose mode enabled.\n"); + names = true; + paths = true; + continue; + } + break; + } + extra_args(args, argidx, design); + + RTLIL::Design *new_design = new RTLIL::Design; + auto modules = design->modules().to_vector(); + for (auto &mod : modules) { + new_design->add(mod->clone()); + auto cells = new_design->module(mod->name)->cells().to_vector(); + for (auto &cell : cells) { + cell->set_bool_attribute(ID::keep_hierarchy, false); + } + } + Pass::call(new_design, "hierarchy -check -top " + top_mod->name.str()); + Pass::call(new_design, "flatten"); + Pass::call(new_design, "dump -m -o \"out/poststa.dump\""); + + Sta2Worker sta(new_design, cell_delay); + sta.run(new_design->module(top_mod->name), min_delay, 1); + + // Extract all the paths longer than max_delay and shorter than min_delay + auto max_paths = pool(); + auto min_paths = pool(); + for (auto &it : sta.analysed) { + if (it.second.second.delay > max_delay) { + max_paths.insert(it.second.second); + } + if (it.second.first.delay < min_delay) { + min_paths.insert(it.second.first); + } + } + + // Generate the timing analysis report + generate_timing_report(max_paths, min_paths, max_delay, min_delay, names, paths); + } +} Sta2Pass; + +PRIVATE_NAMESPACE_END From 91951cbb3b464c35f223e179e6c40a991c28eb44 Mon Sep 17 00:00:00 2001 From: clemens Date: Mon, 11 Aug 2025 15:09:51 +0200 Subject: [PATCH 2/4] fixed min_path printing fixed compiler warnings --- passes/cmds/sta2.cc | 186 +++++++++++++++++++++++++------------------- 1 file changed, 106 insertions(+), 80 deletions(-) diff --git a/passes/cmds/sta2.cc b/passes/cmds/sta2.cc index a5b780c605b..768b978844d 100644 --- a/passes/cmds/sta2.cc +++ b/passes/cmds/sta2.cc @@ -104,7 +104,7 @@ struct Sta_Path { if (cell_delay.single_parameter_delay.size() > 0) { if (widths.size() != 1) { // we need to have exactly one parameter for single parameter delay. - log_error("Cell %s has single parameter delay, but has %d parameters.\n", cell->name.c_str(), widths.size()); + log_error("Cell %s has single parameter delay, but has %zu parameters.\n", cell->name.c_str(), widths.size()); } if (cell_delay.single_parameter_delay.size() > widths.at(0) - 1) { delay += cell_delay.single_parameter_delay.at(widths.at(0) - 1); @@ -116,13 +116,13 @@ struct Sta_Path { } else if (cell_delay.double_parameter_delay.size() > 0) { if (widths.size() != 2) { // we need to have exactly two parameters for double parameter delay. - log_error("Cell %s has double parameter delay, but has %d parameters.\n", cell->name.c_str(), widths.size()); + log_error("Cell %s has double parameter delay, but has %zu parameters.\n", cell->name.c_str(), widths.size()); } - int width_a = widths.at(1); - int width_b = widths.at(0); + unsigned long width_a = widths.at(1); + unsigned long width_b = widths.at(0); if (width_a > 0 && width_b > 0) { - if (cell_delay.double_parameter_delay.size() > width_a - 1 && - cell_delay.double_parameter_delay.at(width_a - 1).size() > width_b - 1) { + if (cell_delay.double_parameter_delay.size() > width_a - 1u && + cell_delay.double_parameter_delay.at(width_a - 1).size() > width_b - 1u) { delay += cell_delay.double_parameter_delay.at(width_a - 1).at(width_b - 1); delays.push_back(cell_delay.double_parameter_delay.at(width_a - 1).at(width_b - 1)); } else { @@ -155,7 +155,7 @@ struct Sta2Worker { Sta2Worker(RTLIL::Design *design, dict cell_delay) : design(design), cell_delays(cell_delay) {} - void run(RTLIL::Module *module, bool hold_violations, bool setup_violations) + void run(RTLIL::Module *module, bool hold_violations) { auto modules = design->modules().to_vector(); if (module != nullptr) { @@ -195,12 +195,10 @@ struct Sta2Worker { } else { continue; } - while (queue.size() > 0) { auto entry = queue.back(); queue.pop_back(); auto cell = entry.cells.back(); - if (design->module(cell->type) == nullptr) { } else if (design->module(cell->type)->get_blackbox_attribute()) { @@ -234,7 +232,6 @@ struct Sta2Worker { continue; } - Yosys::RTLIL::Cell *last_consumer = nullptr; for (auto &consumer_bit : consumers) { auto consumer = consumer_bit.cell; if (analysed_cells.count(consumer->name)) { @@ -245,8 +242,9 @@ struct Sta2Worker { Sta_Path new_entry(entry.delay, entry.wire, entry.cells, entry.delays); new_entry.cells.push_back(consumer); new_entry.add_delay(consumer, cell_delays); - double delay = new_entry.delays.back(); - // if we have already reached this cell skip it if between max or min. + double delay = new_entry.delay + 0.0001; // add a small value as tie breaker for equal delays. + // printf("consumer %s with delay %f\n", consumer->name.c_str(), delay); + // if we have already reached this cell skip it if between max or min. if (cell_max_analysed.count(consumer->name) && cell_max_analysed[consumer->name] > entry.delay) { if (!hold_violations) @@ -265,6 +263,18 @@ struct Sta2Worker { if (RTLIL::builtin_ff_cell_types().count(consumer->type) || (cell_delays.count(consumer->type) && cell_delays.at(consumer->type).is_flipflop)) { // We have a flip-flop. This is the end of this path. + + auto loop = false; + for (auto &it : entry.cells) { + if (it->name == consumer->name) { + + loop = true; + break; + } + } + if (loop) + continue; + // check that we don't have a clk or rst port. In that case we don't want to include // this. if (RTLIL::builtin_ff_cell_types().count(consumer->type)) { @@ -298,8 +308,7 @@ struct Sta2Worker { auto loop = false; for (auto &it : entry.cells) { if (it->name == consumer->name) { - log_warning("warning: potential loop detected: %s %s \n", - cell->name.c_str(), consumer->name.c_str()); + loop = true; break; } @@ -394,8 +403,8 @@ void read_liberty_celldelay(dict &cell_delay, string lib void generate_timing_report(const pool &max_paths, const pool &min_paths, double max_delay, double min_delay, bool names, bool paths) { - log("Number of paths longer than max delay %f: %d \n", max_delay, max_paths.size()); - log("Number of paths shorter than min delay %f: %d \n", min_delay, min_paths.size()); + log("Number of paths longer than max delay %f: %zu \n", max_delay, max_paths.size()); + log("Number of paths shorter than min delay %f: %zu \n", min_delay, min_paths.size()); // Create ordered versions of max_delays and min_delays auto ordered_max_paths = vector(max_paths.begin(), max_paths.end()); @@ -405,34 +414,50 @@ void generate_timing_report(const pool &max_paths, const poolname.c_str(), it.cells.back()->name.c_str(), - it.delay, it.cells.size()); + if (!it.cells.empty()) { + log("max_path: start: %s, end: %s, delay: %f, cells: %zu \n", it.cells.front()->name.c_str(), + it.cells.back()->name.c_str(), it.delay, it.cells.size()); + } else { + log("max_path: empty path, delay: %f, cells: 0 \n", it.delay); + } } for (auto &it : ordered_min_paths) { - log("min_path: start: %s, end: %s, delay: %f, cells: %u \n", it.cells.front()->name.c_str(), it.cells.back()->name.c_str(), - it.delay, it.cells.size()); + if (!it.cells.empty()) { + log("min_path: start: %s, end: %s, delay: %f, cells: %zu \n", it.cells.front()->name.c_str(), + it.cells.back()->name.c_str(), it.delay, it.cells.size()); + } else { + log("min_path: empty path, delay: %f, cells: 0 \n", it.delay); + } } } if (paths) { for (auto &it : ordered_max_paths) { - log("max_path: start: %s, end: %s, delay: %f, cells: %u , delays_length: %u\n", it.cells.front()->name.c_str(), - it.cells.back()->name.c_str(), it.delay, it.cells.size(), it.delays.size()); - int i = 0; - for (auto &cell : it.cells) { - if (i < 0 || i >= it.delays.size()) { - log(" cell: %s , delay: %d-> \n", cell->name.c_str(), 0); - } else { - log(" cell: %s , delay: %f -> \n", cell->name.c_str(), it.delays.at(i)); + if (!it.cells.empty()) { + log("max_path: start: %s, end: %s, delay: %f, cells: %zu , delays_length: %zu\n", it.cells.front()->name.c_str(), + it.cells.back()->name.c_str(), it.delay, it.cells.size(), it.delays.size()); + unsigned long i = 0; + for (auto &cell : it.cells) { + if (i >= it.delays.size()) { + log(" cell: %s , delay: 0.0 -> \n", cell->name.c_str()); + } else { + log(" cell: %s , delay: %f -> \n", cell->name.c_str(), it.delays.at(i)); + } + i++; } - i++; + } else { + log("max_path: empty path, delay: %f, cells: 0, delays_length: %zu\n", it.delay, it.delays.size()); } } for (auto &it : ordered_min_paths) { - log("min_path: start: %s, end: %s, delay: %f, cells: %u \n", it.cells.front()->name.c_str(), it.cells.back()->name.c_str(), - it.delay, it.cells.size()); - for (auto &cell : it.cells) { - log(" cell: %s -> \n", cell->name.c_str()); + if (!it.cells.empty()) { + log("min_path: start: %s, end: %s, delay: %f, cells: %zu \n", it.cells.front()->name.c_str(), + it.cells.back()->name.c_str(), it.delay, it.cells.size()); + for (auto &cell : it.cells) { + log(" cell: %s -> \n", cell->name.c_str()); + } + } else { + log("min_path: empty path, delay: %f, cells: 0 \n", it.delay); } } } @@ -441,51 +466,51 @@ struct Sta2Pass : public Pass { Sta2Pass() : Pass("sta2", "perform static timing analysis") {} void help() override -{ - // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| - log("\n"); - log(" sta2 [options] [selection]\n"); - log("\n"); - log("This command performs static timing analysis on the design.\n\n"); - log("config file content: \n\n"); - log("It needs a config file to read the cell delay information.\n"); - log("The config file should be in the same format as the liberty file.\n"); - log("each cell needs to have a delay parameter.\n"); - log("It cannot parse the timing() section of the liberty file.\n"); - log("there is the option to use parameterised delays. This allows STA to run early on in the flow.\n"); - log("the delay can be parameterised by the width of the ports of the cells.\n"); - log("There is the option to use single parameter delays, which are indexed by max port width of a cell.\n"); - log("There is the option to use double parameter delays, here the two ports that are used for indexing can be specified.\n"); - log("\n"); - log("Extended liberty file fields for STA:\n"); - log(" ff: true; - marks cells that terminate timing paths\n"); - log(" delay: ; - basic delay for non-parameterized cells\n"); - log(" single_delay_parameterised(...); - delay values indexed by max port width\n"); - log(" port_names(\"A\", \"B\"); - specifies ports for multi-dimensional arrays\n"); - log(" double_delay_parameterised(...); - 2D delay array (requires port_names)\n"); - log(" Format: (\"val1,val2,val3,\", \"val4,val5,val6,\", ...) - strings delimit dimensions\n"); - log("\n"); - log("\n"); - log(" -config \n"); - log(" read cell delay information from config file\n"); - log("\n"); - log(" -top \n"); - log(" use the specified module as the top level module\n"); - log("\n"); - log(" -max_delay \n"); - log(" report paths longer than the specified maximum delay\n"); - log("\n"); - log(" -min_delay \n"); - log(" report paths shorter than the specified minimum delay\n"); - log("\n"); - log(" -v\n"); - log(" verbose mode - show path start/end names and basic statistics\n"); - log("\n"); - log(" -vv\n"); - log(" very verbose mode - show detailed path information including\n"); - log(" individual cell delays\n"); - log("\n"); -} + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" sta2 [options] [selection]\n"); + log("\n"); + log("This command performs static timing analysis on the design.\n\n"); + log("config file content: \n\n"); + log("It needs a config file to read the cell delay information.\n"); + log("The config file should be in the same format as the liberty file.\n"); + log("each cell needs to have a delay parameter.\n"); + log("It cannot parse the timing() section of the liberty file.\n"); + log("there is the option to use parameterised delays. This allows STA to run early on in the flow.\n"); + log("the delay can be parameterised by the width of the ports of the cells.\n"); + log("There is the option to use single parameter delays, which are indexed by max port width of a cell.\n"); + log("There is the option to use double parameter delays, here the two ports that are used for indexing can be specified.\n"); + log("\n"); + log("Extended liberty file fields for STA:\n"); + log(" ff: true; - marks cells that terminate timing paths\n"); + log(" delay: ; - basic delay for non-parameterized cells\n"); + log(" single_delay_parameterised(...); - delay values indexed by max port width\n"); + log(" port_names(\"A\", \"B\"); - specifies ports for multi-dimensional arrays\n"); + log(" double_delay_parameterised(...); - 2D delay array (requires port_names)\n"); + log(" Format: (\"val1,val2,val3,\", \"val4,val5,val6,\", ...) - strings delimit dimensions\n"); + log("\n"); + log("\n"); + log(" -config \n"); + log(" read cell delay information from config file\n"); + log("\n"); + log(" -top \n"); + log(" use the specified module as the top level module\n"); + log("\n"); + log(" -max_delay \n"); + log(" report paths longer than the specified maximum delay\n"); + log("\n"); + log(" -min_delay \n"); + log(" report paths shorter than the specified minimum delay\n"); + log("\n"); + log(" -v\n"); + log(" verbose mode - show path start/end names and basic statistics\n"); + log("\n"); + log(" -vv\n"); + log(" very verbose mode - show detailed path information including\n"); + log(" individual cell delays\n"); + log("\n"); + } void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing STA pass (static timing analysis).\n"); @@ -544,12 +569,13 @@ struct Sta2Pass : public Pass { cell->set_bool_attribute(ID::keep_hierarchy, false); } } + Pass::call(new_design, "hierarchy -check -top " + top_mod->name.str()); Pass::call(new_design, "flatten"); Pass::call(new_design, "dump -m -o \"out/poststa.dump\""); - + printf("Running STA on module %s with max delay %f and min delay %f\n", top_mod->name.c_str(), max_delay, min_delay); Sta2Worker sta(new_design, cell_delay); - sta.run(new_design->module(top_mod->name), min_delay, 1); + sta.run(new_design->module(top_mod->name), min_delay); // Extract all the paths longer than max_delay and shorter than min_delay auto max_paths = pool(); From b790a04746a5f3dd3fcff3c28ad14a9b83d7546b Mon Sep 17 00:00:00 2001 From: clemens Date: Mon, 11 Aug 2025 15:18:24 +0200 Subject: [PATCH 3/4] removed leftover debug line --- passes/cmds/sta2.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/passes/cmds/sta2.cc b/passes/cmds/sta2.cc index 768b978844d..bbd31f164e5 100644 --- a/passes/cmds/sta2.cc +++ b/passes/cmds/sta2.cc @@ -572,7 +572,6 @@ struct Sta2Pass : public Pass { Pass::call(new_design, "hierarchy -check -top " + top_mod->name.str()); Pass::call(new_design, "flatten"); - Pass::call(new_design, "dump -m -o \"out/poststa.dump\""); printf("Running STA on module %s with max delay %f and min delay %f\n", top_mod->name.c_str(), max_delay, min_delay); Sta2Worker sta(new_design, cell_delay); sta.run(new_design->module(top_mod->name), min_delay); From 2afe550461b6f1a8963da2f19d4540451634261e Mon Sep 17 00:00:00 2001 From: clemens Date: Mon, 11 Aug 2025 16:24:02 +0200 Subject: [PATCH 4/4] improve paramter list parsing --- passes/cmds/sta2.cc | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/passes/cmds/sta2.cc b/passes/cmds/sta2.cc index bbd31f164e5..b8ddff1b2b3 100644 --- a/passes/cmds/sta2.cc +++ b/passes/cmds/sta2.cc @@ -345,12 +345,12 @@ void read_liberty_celldelay(dict &cell_delay, string lib const LibertyAst *sar = cell->find("single_delay_parameterised"); if (sar != nullptr) { for (const auto &s : sar->args) { - double value = 0; - auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); - // ec != std::errc() means parse error, or ptr didn't consume entire string - if (ec != std::errc() || ptr != s.data() + s.size()) - break; - single_parameter_delay.push_back(value); + try { + double value = std::stod(s); + single_parameter_delay.push_back(value); + } catch (const std::exception &e) { + log_error("Failed to parse single parameter delay value '%s': %s\n", s.c_str(), e.what()); + } } if (single_parameter_delay.size() == 0) log_error("single delay parameterisation array does not contain values: %s\n", @@ -374,10 +374,12 @@ void read_liberty_celldelay(dict &cell_delay, string lib vector cast_sub_array; for (const auto &s : sub_array) { double value = 0; - auto [ptr, ec] = std::from_chars(s.data() + 1, s.data() + s.size(), value); - if (ec != std::errc() || ptr != s.data() + s.size()) - break; - cast_sub_array.push_back(value); + try { + value = std::stod(s); + cast_sub_array.push_back(value); + } catch (const std::exception &e) { + log_error("Failed to parse double parameter delay value '%s': %s\n", s.c_str(), e.what()); + } } double_parameter_delay.push_back(cast_sub_array); if (cast_sub_array.size() == 0)