From b3ead7e47dfb42aa2c35457bfccb7496618a8c85 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 16 Jul 2025 23:15:43 +0200 Subject: [PATCH 01/45] icell_liberty: start --- passes/cmds/Makefile.inc | 1 + passes/cmds/icell_liberty.cc | 138 +++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 passes/cmds/icell_liberty.cc diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 2dbeadac2..bb726aa7d 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -58,3 +58,4 @@ OBJS += passes/cmds/test_select.o OBJS += passes/cmds/timeest.o OBJS += passes/cmds/linecoverage.o OBJS += passes/cmds/sort.o +OBJS += passes/cmds/icell_liberty.o diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc new file mode 100644 index 000000000..19692fa01 --- /dev/null +++ b/passes/cmds/icell_liberty.cc @@ -0,0 +1,138 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Martin Povišer + * + * 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/celltypes.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct LibertyStubber { + CellTypes ct; + CellTypes ct_ff; + LibertyStubber() { + ct.setup(); + ct.setup_internals_ff(); + } + void liberty_prefix(std::ostream& f) + { + f << "library (yosys) {\n"; + f << "\tinput_threshold_pct_fall : 50;\n"; + f << "\tinput_threshold_pct_rise : 50;\n"; + f << "\toutput_threshold_pct_fall : 50;\n"; + f << "\toutput_threshold_pct_rise : 50;\n"; + f << "\tslew_lower_threshold_pct_fall : 1;\n"; + f << "\tslew_lower_threshold_pct_rise : 1;\n"; + f << "\tslew_upper_threshold_pct_fall : 99;\n"; + f << "\tslew_upper_threshold_pct_rise : 99;\n"; + } + void liberty_suffix(std::ostream& f) + { + f << "}\n"; + } + void liberty_cell(Module* base, Module* derived, std::ostream& f) + { + auto base_name = base->name.str().substr(1); + auto derived_name = derived->name.str().substr(1); + if (!ct.cell_types.count(base_name)) { + log_debug("skip skeleton for %s\n", base_name.c_str()); + return; + } + auto& base_type = ct.cell_types[base_name]; + f << "\tcell (\"" << derived_name << "\") {\n"; + for (auto x : derived->ports) { + bool is_input = base_type.inputs.count(x); + bool is_output = base_type.outputs.count(x); + f << "\t\tpin (" << RTLIL::unescape_id(x.str()) << ") {\n"; + if (is_input && !is_output) { + f << "\t\t\tdirection : input;\n"; + } else if (!is_input && is_output) { + f << "\t\t\tdirection : output;\n"; + } else { + f << "\t\t\tdirection : inout;\n"; + } + f << "\t\t}\n"; + } + f << "\t}\n"; + } +}; + +struct IcellLiberty : Pass { + IcellLiberty() : Pass("icell_liberty", "derive box modules") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" icell_liberty \n"); // TODO + log("\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *d) override + { + log_header(d, "Executing ICELL_LIBERTY pass.\n"); + + size_t argidx; + IdString naming_attr; + std::string liberty_filename; + std::ofstream* liberty_file = new std::ofstream; + + for (argidx = 1; argidx < args.size(); argidx++) { + break; + } + if (argidx < args.size()) + liberty_filename = args[argidx++]; + else + log_error("no Liberty filename specified\n"); + + // extra_args(args, argidx, d); + + if (liberty_filename.size()) { + liberty_file->open(liberty_filename.c_str()); + if (liberty_file->fail()) { + delete liberty_file; + log_error("Can't open file `%s' for writing: %s\n", liberty_filename.c_str(), strerror(errno)); + } + } + + pool done; + LibertyStubber stubber = {}; + + if (liberty_file) + stubber.liberty_prefix(*liberty_file); + + for (auto module : d->selected_modules()) { + for (auto cell : module->selected_cells()) { + Module *inst_module = d->module(cell->type); + if (!inst_module || !inst_module->get_blackbox_attribute()) + continue; + Module *base = inst_module; + if (!done.count(base->name)) { + stubber.liberty_cell(base, base, *liberty_file); + done.insert(base->name); + } + } + } + + if (liberty_file) { + stubber.liberty_suffix(*liberty_file); + delete liberty_file; + } + } +} IcellLiberty; + +PRIVATE_NAMESPACE_END From 9e81db43731bc90f42612191f33f63af509774d1 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 18 Jul 2025 12:34:10 +0200 Subject: [PATCH 02/45] ff: split out type-only information --- kernel/ff.cc | 489 +++++++++++++++++++++++++++++---------------------- kernel/ff.h | 78 ++++---- 2 files changed, 323 insertions(+), 244 deletions(-) diff --git a/kernel/ff.cc b/kernel/ff.cc index a72e6a65c..7dd5e24ac 100644 --- a/kernel/ff.cc +++ b/kernel/ff.cc @@ -21,245 +21,316 @@ USING_YOSYS_NAMESPACE -FfData::FfData(FfInitVals *initvals, Cell *cell_) : FfData(cell_->module, initvals, cell_->name) -{ - cell = cell_; - sig_q = cell->getPort(ID::Q); - width = GetSize(sig_q); - attributes = cell->attributes; +// sorry +template>> +void manufacture_info(InputType flop, OutputType& info, FfInitVals *initvals) { + Cell* cell = nullptr; + IdString type; + constexpr bool have_cell = std::is_same_v; + if constexpr (std::is_same_v) { + type = flop; + } else { + static_assert(std::is_same_v); + cell = flop; + type = flop->type; + } + if constexpr (have_cell) { + info.sig_q = cell->getPort(ID::Q); + info.width = GetSize(info.sig_q); + info.attributes = cell->attributes; + if (initvals) + info.val_init = (*initvals)(info.sig_q); + } - if (initvals) - val_init = (*initvals)(sig_q); - std::string type_str = cell->type.str(); + std::string type_str = type.str(); - if (cell->type.in(ID($anyinit), ID($ff), ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($sdff), ID($sdffe), ID($sdffce), ID($dlatch), ID($adlatch), ID($dlatchsr), ID($sr))) { - if (cell->type.in(ID($anyinit), ID($ff))) { - has_gclk = true; - sig_d = cell->getPort(ID::D); - if (cell->type == ID($anyinit)) { - is_anyinit = true; - log_assert(val_init.is_fully_undef()); + if (type.in(ID($anyinit), ID($ff), ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($sdff), ID($sdffe), ID($sdffce), ID($dlatch), ID($adlatch), ID($dlatchsr), ID($sr))) { + if (type.in(ID($anyinit), ID($ff))) { + info.has_gclk = true; + if constexpr (have_cell) + info.sig_d = cell->getPort(ID::D); + if (type == ID($anyinit)) { + info.is_anyinit = true; + if constexpr (have_cell) + log_assert(info.val_init.is_fully_undef()); } - } else if (cell->type == ID($sr)) { + } else if (type == ID($sr)) { // No data input at all. - } else if (cell->type.in(ID($dlatch), ID($adlatch), ID($dlatchsr))) { - has_aload = true; - sig_aload = cell->getPort(ID::EN); - pol_aload = cell->getParam(ID::EN_POLARITY).as_bool(); - sig_ad = cell->getPort(ID::D); + } else if (type.in(ID($dlatch), ID($adlatch), ID($dlatchsr))) { + info.has_aload = true; + if constexpr (have_cell) { + info.sig_aload = cell->getPort(ID::EN); + info.pol_aload = cell->getParam(ID::EN_POLARITY).as_bool(); + info.sig_ad = cell->getPort(ID::D); + } } else { - has_clk = true; - sig_clk = cell->getPort(ID::CLK); - pol_clk = cell->getParam(ID::CLK_POLARITY).as_bool(); - sig_d = cell->getPort(ID::D); + info.has_clk = true; + if constexpr (have_cell) { + info.sig_clk = cell->getPort(ID::CLK); + info.pol_clk = cell->getParam(ID::CLK_POLARITY).as_bool(); + info.sig_d = cell->getPort(ID::D); + } } - if (cell->type.in(ID($dffe), ID($dffsre), ID($adffe), ID($aldffe), ID($sdffe), ID($sdffce))) { - has_ce = true; - sig_ce = cell->getPort(ID::EN); - pol_ce = cell->getParam(ID::EN_POLARITY).as_bool(); + if (type.in(ID($dffe), ID($dffsre), ID($adffe), ID($aldffe), ID($sdffe), ID($sdffce))) { + info.has_ce = true; + if constexpr (have_cell) { + info.sig_ce = cell->getPort(ID::EN); + info.pol_ce = cell->getParam(ID::EN_POLARITY).as_bool(); + } } - if (cell->type.in(ID($dffsr), ID($dffsre), ID($dlatchsr), ID($sr))) { - has_sr = true; - sig_clr = cell->getPort(ID::CLR); - sig_set = cell->getPort(ID::SET); - pol_clr = cell->getParam(ID::CLR_POLARITY).as_bool(); - pol_set = cell->getParam(ID::SET_POLARITY).as_bool(); + if (type.in(ID($dffsr), ID($dffsre), ID($dlatchsr), ID($sr))) { + info.has_sr = true; + if constexpr (have_cell) { + info.sig_clr = cell->getPort(ID::CLR); + info.sig_set = cell->getPort(ID::SET); + info.pol_clr = cell->getParam(ID::CLR_POLARITY).as_bool(); + info.pol_set = cell->getParam(ID::SET_POLARITY).as_bool(); + } } - if (cell->type.in(ID($aldff), ID($aldffe))) { - has_aload = true; - sig_aload = cell->getPort(ID::ALOAD); - pol_aload = cell->getParam(ID::ALOAD_POLARITY).as_bool(); - sig_ad = cell->getPort(ID::AD); + if (type.in(ID($aldff), ID($aldffe))) { + info.has_aload = true; + if constexpr (have_cell) { + info.sig_aload = cell->getPort(ID::ALOAD); + info.pol_aload = cell->getParam(ID::ALOAD_POLARITY).as_bool(); + info.sig_ad = cell->getPort(ID::AD); + } } - if (cell->type.in(ID($adff), ID($adffe), ID($adlatch))) { - has_arst = true; - sig_arst = cell->getPort(ID::ARST); - pol_arst = cell->getParam(ID::ARST_POLARITY).as_bool(); - val_arst = cell->getParam(ID::ARST_VALUE); + if (type.in(ID($adff), ID($adffe), ID($adlatch))) { + info.has_arst = true; + if constexpr (have_cell) { + info.sig_arst = cell->getPort(ID::ARST); + info.pol_arst = cell->getParam(ID::ARST_POLARITY).as_bool(); + info.val_arst = cell->getParam(ID::ARST_VALUE); + } } - if (cell->type.in(ID($sdff), ID($sdffe), ID($sdffce))) { - has_srst = true; - sig_srst = cell->getPort(ID::SRST); - pol_srst = cell->getParam(ID::SRST_POLARITY).as_bool(); - val_srst = cell->getParam(ID::SRST_VALUE); - ce_over_srst = cell->type == ID($sdffce); + if (type.in(ID($sdff), ID($sdffe), ID($sdffce))) { + info.has_srst = true; + if constexpr (have_cell) { + info.sig_srst = cell->getPort(ID::SRST); + info.pol_srst = cell->getParam(ID::SRST_POLARITY).as_bool(); + info.val_srst = cell->getParam(ID::SRST_VALUE); + } + info.ce_over_srst = type == ID($sdffce); } - } else if (cell->type == ID($_FF_)) { - is_fine = true; - has_gclk = true; - sig_d = cell->getPort(ID::D); + } else if (type == ID($_FF_)) { + info.is_fine = true; + info.has_gclk = true; + if constexpr (have_cell) + info.sig_d = cell->getPort(ID::D); } else if (type_str.substr(0, 5) == "$_SR_") { - is_fine = true; - has_sr = true; - pol_set = type_str[5] == 'P'; - pol_clr = type_str[6] == 'P'; - sig_set = cell->getPort(ID::S); - sig_clr = cell->getPort(ID::R); + info.is_fine = true; + info.has_sr = true; + info.pol_set = type_str[5] == 'P'; + info.pol_clr = type_str[6] == 'P'; + if constexpr (have_cell) { + info.sig_set = cell->getPort(ID::S); + info.sig_clr = cell->getPort(ID::R); + } } else if (type_str.substr(0, 6) == "$_DFF_" && type_str.size() == 8) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[6] == 'P'; - sig_clk = cell->getPort(ID::C); + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[6] == 'P'; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + } } else if (type_str.substr(0, 7) == "$_DFFE_" && type_str.size() == 10) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[7] == 'P'; - sig_clk = cell->getPort(ID::C); - has_ce = true; - pol_ce = type_str[8] == 'P'; - sig_ce = cell->getPort(ID::E); + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[7] == 'P'; + info.has_ce = true; + info.pol_ce = type_str[8] == 'P'; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + info.sig_ce = cell->getPort(ID::E); + } } else if (type_str.substr(0, 6) == "$_DFF_" && type_str.size() == 10) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[6] == 'P'; - sig_clk = cell->getPort(ID::C); - has_arst = true; - pol_arst = type_str[7] == 'P'; - sig_arst = cell->getPort(ID::R); - val_arst = type_str[8] == '1' ? State::S1 : State::S0; + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[6] == 'P'; + info.has_arst = true; + info.pol_arst = type_str[7] == 'P'; + info.val_arst = type_str[8] == '1' ? State::S1 : State::S0; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + info.sig_arst = cell->getPort(ID::R); + } } else if (type_str.substr(0, 7) == "$_DFFE_" && type_str.size() == 12) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[7] == 'P'; - sig_clk = cell->getPort(ID::C); - has_arst = true; - pol_arst = type_str[8] == 'P'; - sig_arst = cell->getPort(ID::R); - val_arst = type_str[9] == '1' ? State::S1 : State::S0; - has_ce = true; - pol_ce = type_str[10] == 'P'; - sig_ce = cell->getPort(ID::E); + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[7] == 'P'; + info.has_arst = true; + info.pol_arst = type_str[8] == 'P'; + info.val_arst = type_str[9] == '1' ? State::S1 : State::S0; + info.has_ce = true; + info.pol_ce = type_str[10] == 'P'; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + info.sig_arst = cell->getPort(ID::R); + info.sig_ce = cell->getPort(ID::E); + } } else if (type_str.substr(0, 8) == "$_ALDFF_" && type_str.size() == 11) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[8] == 'P'; - sig_clk = cell->getPort(ID::C); - has_aload = true; - pol_aload = type_str[9] == 'P'; - sig_aload = cell->getPort(ID::L); - sig_ad = cell->getPort(ID::AD); + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[8] == 'P'; + info.has_aload = true; + info.pol_aload = type_str[9] == 'P'; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + info.sig_aload = cell->getPort(ID::L); + info.sig_ad = cell->getPort(ID::AD); + } } else if (type_str.substr(0, 9) == "$_ALDFFE_" && type_str.size() == 13) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[9] == 'P'; - sig_clk = cell->getPort(ID::C); - has_aload = true; - pol_aload = type_str[10] == 'P'; - sig_aload = cell->getPort(ID::L); - sig_ad = cell->getPort(ID::AD); - has_ce = true; - pol_ce = type_str[11] == 'P'; - sig_ce = cell->getPort(ID::E); + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[9] == 'P'; + info.has_aload = true; + info.pol_aload = type_str[10] == 'P'; + info.has_ce = true; + info.pol_ce = type_str[11] == 'P'; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + info.sig_aload = cell->getPort(ID::L); + info.sig_ad = cell->getPort(ID::AD); + info.sig_ce = cell->getPort(ID::E); + } } else if (type_str.substr(0, 8) == "$_DFFSR_" && type_str.size() == 12) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[8] == 'P'; - sig_clk = cell->getPort(ID::C); - has_sr = true; - pol_set = type_str[9] == 'P'; - pol_clr = type_str[10] == 'P'; - sig_set = cell->getPort(ID::S); - sig_clr = cell->getPort(ID::R); + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[8] == 'P'; + info.has_sr = true; + info.pol_set = type_str[9] == 'P'; + info.pol_clr = type_str[10] == 'P'; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + info.sig_set = cell->getPort(ID::S); + info.sig_clr = cell->getPort(ID::R); + } } else if (type_str.substr(0, 9) == "$_DFFSRE_" && type_str.size() == 14) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[9] == 'P'; - sig_clk = cell->getPort(ID::C); - has_sr = true; - pol_set = type_str[10] == 'P'; - pol_clr = type_str[11] == 'P'; - sig_set = cell->getPort(ID::S); - sig_clr = cell->getPort(ID::R); - has_ce = true; - pol_ce = type_str[12] == 'P'; - sig_ce = cell->getPort(ID::E); + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[9] == 'P'; + info.has_sr = true; + info.pol_set = type_str[10] == 'P'; + info.pol_clr = type_str[11] == 'P'; + info.has_ce = true; + info.pol_ce = type_str[12] == 'P'; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + info.sig_set = cell->getPort(ID::S); + info.sig_clr = cell->getPort(ID::R); + info.sig_ce = cell->getPort(ID::E); + } } else if (type_str.substr(0, 7) == "$_SDFF_" && type_str.size() == 11) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[7] == 'P'; - sig_clk = cell->getPort(ID::C); - has_srst = true; - pol_srst = type_str[8] == 'P'; - sig_srst = cell->getPort(ID::R); - val_srst = type_str[9] == '1' ? State::S1 : State::S0; + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[7] == 'P'; + info.has_srst = true; + info.pol_srst = type_str[8] == 'P'; + info.val_srst = type_str[9] == '1' ? State::S1 : State::S0; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + info.sig_srst = cell->getPort(ID::R); + } } else if (type_str.substr(0, 8) == "$_SDFFE_" && type_str.size() == 13) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[8] == 'P'; - sig_clk = cell->getPort(ID::C); - has_srst = true; - pol_srst = type_str[9] == 'P'; - sig_srst = cell->getPort(ID::R); - val_srst = type_str[10] == '1' ? State::S1 : State::S0; - has_ce = true; - pol_ce = type_str[11] == 'P'; - sig_ce = cell->getPort(ID::E); + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[8] == 'P'; + info.has_srst = true; + info.pol_srst = type_str[9] == 'P'; + info.val_srst = type_str[10] == '1' ? State::S1 : State::S0; + info.has_ce = true; + info.pol_ce = type_str[11] == 'P'; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + info.sig_srst = cell->getPort(ID::R); + info.sig_ce = cell->getPort(ID::E); + } } else if (type_str.substr(0, 9) == "$_SDFFCE_" && type_str.size() == 14) { - is_fine = true; - sig_d = cell->getPort(ID::D); - has_clk = true; - pol_clk = type_str[9] == 'P'; - sig_clk = cell->getPort(ID::C); - has_srst = true; - pol_srst = type_str[10] == 'P'; - sig_srst = cell->getPort(ID::R); - val_srst = type_str[11] == '1' ? State::S1 : State::S0; - has_ce = true; - pol_ce = type_str[12] == 'P'; - sig_ce = cell->getPort(ID::E); - ce_over_srst = true; + info.is_fine = true; + info.has_clk = true; + info.pol_clk = type_str[9] == 'P'; + info.has_srst = true; + info.pol_srst = type_str[10] == 'P'; + info.val_srst = type_str[11] == '1' ? State::S1 : State::S0; + info.has_ce = true; + info.pol_ce = type_str[12] == 'P'; + info.ce_over_srst = true; + if constexpr (have_cell) { + info.sig_d = cell->getPort(ID::D); + info.sig_clk = cell->getPort(ID::C); + info.sig_srst = cell->getPort(ID::R); + info.sig_ce = cell->getPort(ID::E); + } } else if (type_str.substr(0, 9) == "$_DLATCH_" && type_str.size() == 11) { - is_fine = true; - has_aload = true; - sig_ad = cell->getPort(ID::D); - has_aload = true; - pol_aload = type_str[9] == 'P'; - sig_aload = cell->getPort(ID::E); + info.is_fine = true; + info.has_aload = true; + info.has_aload = true; + info.pol_aload = type_str[9] == 'P'; + if constexpr (have_cell) { + info.sig_ad = cell->getPort(ID::D); + info.sig_aload = cell->getPort(ID::E); + } } else if (type_str.substr(0, 9) == "$_DLATCH_" && type_str.size() == 13) { - is_fine = true; - has_aload = true; - sig_ad = cell->getPort(ID::D); - has_aload = true; - pol_aload = type_str[9] == 'P'; - sig_aload = cell->getPort(ID::E); - has_arst = true; - pol_arst = type_str[10] == 'P'; - sig_arst = cell->getPort(ID::R); - val_arst = type_str[11] == '1' ? State::S1 : State::S0; + info.is_fine = true; + info.has_aload = true; + info.has_aload = true; + info.pol_aload = type_str[9] == 'P'; + info.has_arst = true; + info.pol_arst = type_str[10] == 'P'; + info.val_arst = type_str[11] == '1' ? State::S1 : State::S0; + if constexpr (have_cell) { + info.sig_ad = cell->getPort(ID::D); + info.sig_aload = cell->getPort(ID::E); + info.sig_arst = cell->getPort(ID::R); + } } else if (type_str.substr(0, 11) == "$_DLATCHSR_" && type_str.size() == 15) { - is_fine = true; - has_aload = true; - sig_ad = cell->getPort(ID::D); - has_aload = true; - pol_aload = type_str[11] == 'P'; - sig_aload = cell->getPort(ID::E); - has_sr = true; - pol_set = type_str[12] == 'P'; - pol_clr = type_str[13] == 'P'; - sig_set = cell->getPort(ID::S); - sig_clr = cell->getPort(ID::R); + info.is_fine = true; + info.has_aload = true; + info.has_aload = true; + info.pol_aload = type_str[11] == 'P'; + info.has_sr = true; + info.pol_set = type_str[12] == 'P'; + info.pol_clr = type_str[13] == 'P'; + if constexpr (have_cell) { + info.sig_ad = cell->getPort(ID::D); + info.sig_aload = cell->getPort(ID::E); + info.sig_set = cell->getPort(ID::S); + info.sig_clr = cell->getPort(ID::R); + } } else { log_assert(0); } - if (has_aload && !has_clk && !has_sr && !has_arst && sig_ad.is_fully_const()) { - // Plain D latches with const D treated specially. - has_aload = false; - has_arst = true; - sig_arst = sig_aload; - pol_arst = pol_aload; - val_arst = sig_ad.as_const(); - } + if constexpr (have_cell) + if (info.has_aload && !info.has_clk && !info.has_sr && !info.has_arst && info.sig_ad.is_fully_const()) { + // Plain D latches with const D treated specially. + info.has_aload = false; + info.has_arst = true; + info.sig_arst = info.sig_aload; + info.pol_arst = info.pol_aload; + info.val_arst = info.sig_ad.as_const(); + } +} + +FfTypeData::FfTypeData(IdString type) : FfTypeData() +{ + manufacture_info(type, *this, nullptr); +} + +FfData::FfData(FfInitVals *initvals, Cell *cell_) : FfData(cell_->module, initvals, cell_->name) +{ + cell = cell_; + manufacture_info(cell, *this, initvals); } FfData FfData::slice(const std::vector &bits) { diff --git a/kernel/ff.h b/kernel/ff.h index d6cf99b9b..217658d35 100644 --- a/kernel/ff.h +++ b/kernel/ff.h @@ -78,31 +78,20 @@ YOSYS_NAMESPACE_BEGIN // - has_arst [does not correspond to a native cell, represented as dlatch with const D input] // - empty set [not a cell — will be emitted as a simple direct connection] -struct FfData { - Module *module; - FfInitVals *initvals; - Cell *cell; - IdString name; - // The FF output. - SigSpec sig_q; - // The sync data input, present if has_clk or has_gclk. - SigSpec sig_d; - // The async data input, present if has_aload. - SigSpec sig_ad; - // The sync clock, present if has_clk. - SigSpec sig_clk; - // The clock enable, present if has_ce. - SigSpec sig_ce; - // The async load enable, present if has_aload. - SigSpec sig_aload; - // The async reset, preset if has_arst. - SigSpec sig_arst; - // The sync reset, preset if has_srst. - SigSpec sig_srst; - // The async clear (per-lane), present if has_sr. - SigSpec sig_clr; - // The async set (per-lane), present if has_sr. - SigSpec sig_set; +struct FfTypeData { + FfTypeData(IdString type); + FfTypeData() { + has_clk = false; + has_gclk = false; + has_ce = false; + has_aload = false; + has_srst = false; + has_arst = false; + has_sr = false; + ce_over_srst = false; + is_fine = false; + is_anyinit = false; + } // True if this is a clocked (edge-sensitive) flip-flop. bool has_clk; // True if this is a $ff, exclusive with every other has_*. @@ -143,9 +132,38 @@ struct FfData { bool pol_clr; bool pol_set; // The value loaded by sig_arst. + // Zero length if unknowable from just type Const val_arst; // The value loaded by sig_srst. + // Zero length if unknowable from just type Const val_srst; +}; + +struct FfData : FfTypeData { + Module *module; + FfInitVals *initvals; + Cell *cell; + IdString name; + // The FF output. + SigSpec sig_q; + // The sync data input, present if has_clk or has_gclk. + SigSpec sig_d; + // The async data input, present if has_aload. + SigSpec sig_ad; + // The sync clock, present if has_clk. + SigSpec sig_clk; + // The clock enable, present if has_ce. + SigSpec sig_ce; + // The async load enable, present if has_aload. + SigSpec sig_aload; + // The async reset, preset if has_arst. + SigSpec sig_arst; + // The sync reset, preset if has_srst. + SigSpec sig_srst; + // The async clear (per-lane), present if has_sr. + SigSpec sig_clr; + // The async set (per-lane), present if has_sr. + SigSpec sig_set; // The initial value at power-up. Const val_init; // The FF data width in bits. @@ -154,16 +172,6 @@ struct FfData { FfData(Module *module = nullptr, FfInitVals *initvals = nullptr, IdString name = IdString()) : module(module), initvals(initvals), cell(nullptr), name(name) { width = 0; - has_clk = false; - has_gclk = false; - has_ce = false; - has_aload = false; - has_srst = false; - has_arst = false; - has_sr = false; - ce_over_srst = false; - is_fine = false; - is_anyinit = false; pol_clk = false; pol_aload = false; pol_ce = false; From c2c9506f4f0bd3d0d0a59bc904258d1c24cd9341 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 18 Jul 2025 13:32:49 +0200 Subject: [PATCH 03/45] icell_liberty: flops --- passes/cmds/icell_liberty.cc | 86 +++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc index 19692fa01..9b89c1125 100644 --- a/passes/cmds/icell_liberty.cc +++ b/passes/cmds/icell_liberty.cc @@ -18,19 +18,27 @@ */ #include "kernel/yosys.h" #include "kernel/celltypes.h" +#include "kernel/ff.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct LibertyStubber { CellTypes ct; - CellTypes ct_ff; LibertyStubber() { ct.setup(); ct.setup_internals_ff(); } void liberty_prefix(std::ostream& f) { + f << "/*\n"; + f << stringf("Models interfaces of select Yosys internal cell.\n"); + f << stringf("Likely contains INCORRECT POLARITIES.\n"); + f << stringf("Impractical for any simulation, synthesis, or timing.\n"); + f << stringf("Intended purely for SDC expansion.\n"); + f << stringf("Do not microwave or tumble dry.\n"); + f << stringf("Generated by %s\n", yosys_maybe_version()); + f << "*/\n"; f << "library (yosys) {\n"; f << "\tinput_threshold_pct_fall : 50;\n"; f << "\tinput_threshold_pct_rise : 50;\n"; @@ -45,6 +53,76 @@ struct LibertyStubber { { f << "}\n"; } + struct LibertyItemizer { + std::ostream& f; + int indent; + LibertyItemizer(std::ostream& f) : f(f), indent(0) {}; + void item(std::string key, std::string val) + { + f << std::string(indent, '\t') << key << " : \"" << val << "\";\n"; + } + }; + void liberty_flop(Module* base, Module* derived, std::ostream& f) + { + auto base_name = base->name.str().substr(1); + auto derived_name = derived->name.str().substr(1); + + FfTypeData ffType(base_name); + LibertyItemizer i(f); + + if (ffType.has_gclk) { + log_warning("Formal flip flop %s can't be modeled\n", base_name.c_str()); + return; + } + if (ffType.has_ce) { + log_warning("DFFE %s can't be modeled\n", base_name.c_str()); + return; + } + + f << "\tcell (\"" << derived_name << "\") {\n"; + auto& base_type = ct.cell_types[base_name]; + i.indent = 3; + for (auto x : derived->ports) { + bool is_input = base_type.inputs.count(x); + bool is_output = base_type.outputs.count(x); + f << "\t\tpin (" << RTLIL::unescape_id(x.str()) << ") {\n"; + if (is_input && !is_output) { + i.item("direction", "input"); + } else if (!is_input && is_output) { + i.item("direction", "output"); + } else { + i.item("direction", "inout"); + } + if (RTLIL::unescape_id(x) == "CLK" || RTLIL::unescape_id(x) == "C") + i.item("clock", "true"); + if (RTLIL::unescape_id(x) == "Q") + i.item("function", "IQ"); + f << "\t\t}\n"; + } + + f << "\t\tff (\"IQ\",\"IQ_N\") {\n"; + i.indent = 3; + // TODO polarities? + if (ffType.has_clk) { + auto pin = ffType.is_fine ? "C" : "CLK"; + i.item("clocked_on", pin); + } + if (ffType.has_arst) { + auto meaning = (ffType.val_arst == State::S1) ? "preset" : "clear"; + auto pin = ffType.is_fine ? "R" : "ARST"; + i.item(meaning, pin); + } + auto next_state = ffType.has_ce ? "D & EN" : "D"; + i.item("next_state", next_state); + f << "\t\t}\n"; + + + // bool has_aload; + // bool has_srst; + // bool has_arst; + // bool has_sr; + f << "\t}\n"; + } void liberty_cell(Module* base, Module* derived, std::ostream& f) { auto base_name = base->name.str().substr(1); @@ -53,6 +131,10 @@ struct LibertyStubber { log_debug("skip skeleton for %s\n", base_name.c_str()); return; } + + if (RTLIL::builtin_ff_cell_types().count(base_name)) + return liberty_flop(base, derived, f); + auto& base_type = ct.cell_types[base_name]; f << "\tcell (\"" << derived_name << "\") {\n"; for (auto x : derived->ports) { @@ -73,7 +155,7 @@ struct LibertyStubber { }; struct IcellLiberty : Pass { - IcellLiberty() : Pass("icell_liberty", "derive box modules") {} + IcellLiberty() : Pass("icell_liberty", "write Liberty interfaces for used internal cells") {} void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| From e4e32d7966865f107ba63349d6551ce39f6c6ff4 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 18 Jul 2025 22:27:11 +0200 Subject: [PATCH 04/45] icell_liberty: flop harder --- passes/cmds/icell_liberty.cc | 51 +++++++++++++++++------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc index 9b89c1125..83252ffb4 100644 --- a/passes/cmds/icell_liberty.cc +++ b/passes/cmds/icell_liberty.cc @@ -1,21 +1,3 @@ -/* - * yosys -- Yosys Open SYnthesis Suite - * - * Copyright (C) 2024 Martin Povišer - * - * 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/celltypes.h" #include "kernel/ff.h" @@ -32,12 +14,12 @@ struct LibertyStubber { void liberty_prefix(std::ostream& f) { f << "/*\n"; - f << stringf("Models interfaces of select Yosys internal cell.\n"); - f << stringf("Likely contains INCORRECT POLARITIES.\n"); - f << stringf("Impractical for any simulation, synthesis, or timing.\n"); - f << stringf("Intended purely for SDC expansion.\n"); - f << stringf("Do not microwave or tumble dry.\n"); - f << stringf("Generated by %s\n", yosys_maybe_version()); + f << stringf("\tModels interfaces of select Yosys internal cell.\n"); + f << stringf("\tLikely contains INCORRECT POLARITIES.\n"); + f << stringf("\tImpractical for any simulation, synthesis, or timing.\n"); + f << stringf("\tIntended purely for SDC expansion.\n"); + f << stringf("\tDo not microwave or tumble dry.\n"); + f << stringf("\tGenerated by %s\n", yosys_maybe_version()); f << "*/\n"; f << "library (yosys) {\n"; f << "\tinput_threshold_pct_fall : 50;\n"; @@ -82,7 +64,13 @@ struct LibertyStubber { f << "\tcell (\"" << derived_name << "\") {\n"; auto& base_type = ct.cell_types[base_name]; i.indent = 3; - for (auto x : derived->ports) { + auto sorted_ports = derived->ports; + // Hack for CLK and C coming before Q does + auto cmp = [](IdString l, IdString r) { return l.str() < r.str(); }; + std::sort(sorted_ports.begin(), sorted_ports.end(), cmp); + std::string clock_pin_name = ""; + for (auto x : sorted_ports) { + std::string port_name = RTLIL::unescape_id(x); bool is_input = base_type.inputs.count(x); bool is_output = base_type.outputs.count(x); f << "\t\tpin (" << RTLIL::unescape_id(x.str()) << ") {\n"; @@ -93,10 +81,19 @@ struct LibertyStubber { } else { i.item("direction", "inout"); } - if (RTLIL::unescape_id(x) == "CLK" || RTLIL::unescape_id(x) == "C") + if (port_name == "CLK" || port_name == "C") { i.item("clock", "true"); - if (RTLIL::unescape_id(x) == "Q") + clock_pin_name = port_name; + } + if (port_name == "Q") { i.item("function", "IQ"); + f << "\t\t\ttiming () {\n"; + i.indent++; + log_assert(clock_pin_name.size()); + i.item("related_pin", clock_pin_name); + i.indent--; + f << "\t\t\t}\n"; + } f << "\t\t}\n"; } From bbf1e4bca2a1e55ad2534efb35196a3150b7c341 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 18 Jul 2025 22:45:31 +0200 Subject: [PATCH 05/45] sdc_expand, opensta: start --- techlibs/common/Makefile.inc | 2 + techlibs/common/opensta.cc | 118 ++++++++++++++++++++++++++ techlibs/common/sdc_expand.cc | 150 ++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 techlibs/common/opensta.cc create mode 100644 techlibs/common/sdc_expand.cc diff --git a/techlibs/common/Makefile.inc b/techlibs/common/Makefile.inc index 22b119800..df96fdd46 100644 --- a/techlibs/common/Makefile.inc +++ b/techlibs/common/Makefile.inc @@ -2,6 +2,8 @@ ifneq ($(SMALL),1) OBJS += techlibs/common/synth.o OBJS += techlibs/common/prep.o +OBJS += techlibs/common/opensta.o +OBJS += techlibs/common/sdc_expand.o endif GENFILES += techlibs/common/simlib_help.inc diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc new file mode 100644 index 000000000..1b3571973 --- /dev/null +++ b/techlibs/common/opensta.cc @@ -0,0 +1,118 @@ +#include "kernel/rtlil.h" +#include "kernel/log.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +#if !defined(YOSYS_DISABLE_SPAWN) +struct OpenstaPass : public Pass +{ + const char* default_sta_cmd = "sta"; + OpenstaPass() : Pass("opensta", "run OpenSTA") { } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" opensta [options]\n"); + log("\n"); + // TOOD + log("\n"); + log(" -exe \n"); + log(" use to run OpenSTA instead of \"%s\"\n", default_sta_cmd); + log("\n"); + log(" -sdc-in \n"); + log(" expand SDC input file \n"); + log("\n"); + log(" -sdc-out \n"); + log(" expand SDC file to output file \n"); + log("\n"); + log(" -nocleanup\n"); + log("\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + string run_from, run_to; + string opensta_exe = "sta"; + string sdc_filename, sdc_expanded_filename; + string tempdir_name, script_filename; + string verilog_filename, liberty_filename; + bool cleanup = true; + + log_header(design, "Executing OPENSTA pass.\n"); + log_push(); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-exe" && argidx+1 < args.size()) { + opensta_exe = args[++argidx]; + continue; + } + if (args[argidx] == "-sdc-in" && argidx+1 < args.size()) { + sdc_filename = args[++argidx]; + continue; + } + if (args[argidx] == "-sdc-out" && argidx+1 < args.size()) { + sdc_expanded_filename = args[++argidx]; + continue; + } + if (args[argidx] == "-verilog" && argidx+1 < args.size()) { + verilog_filename = args[++argidx]; + continue; + } + if (args[argidx] == "-liberty" && argidx+1 < args.size()) { + liberty_filename = args[++argidx]; + continue; + } + if (args[argidx] == "-nocleanup") { + cleanup = false; + continue; + } + break; + } + extra_args(args, argidx, design); + if (!design->full_selection()) + log_cmd_error("This command only operates on fully selected designs!\n"); + + if (cleanup) + tempdir_name = get_base_tmpdir() + "/"; + else + tempdir_name = "_tmp_"; + tempdir_name += proc_program_prefix() + "yosys-opensta-XXXXXX"; + tempdir_name = make_temp_dir(tempdir_name); + script_filename = tempdir_name + "/opensta.tcl"; + // script_filename + + auto top_mod = design->top_module(); + if (!top_mod) + log_error("Can't find top module in current design!\n"); + + std::ofstream f_script; + f_script.open(script_filename); + + f_script << "read_verilog " << verilog_filename << "\n"; + f_script << "read_lib " << liberty_filename << "\n"; + f_script << "link_design " << RTLIL::unescape_id(top_mod->name) << "\n"; + f_script << "read_sdc " << sdc_filename << "\n"; + f_script << "write_sdc " << sdc_expanded_filename << "\n"; + f_script.close(); + std::string command = opensta_exe + " -exit " + script_filename; + int ret = run_command(command); + if (ret) + log_error("OpenSTA return %d (error)\n", ret); + else + log("sdc_expanded_filename: %s\n", sdc_expanded_filename.c_str()); + + if (cleanup) { + log("Removing temp directory.\n"); + remove_directory(tempdir_name); + } + log_pop(); + } +} OpenstaPass; +#endif + +PRIVATE_NAMESPACE_END diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc new file mode 100644 index 000000000..7efaf8c0d --- /dev/null +++ b/techlibs/common/sdc_expand.cc @@ -0,0 +1,150 @@ +#include "kernel/rtlil.h" +#include "kernel/log.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct SdcexpandPass : public ScriptPass +{ + const char* default_sta_cmd = "sta"; + SdcexpandPass() : ScriptPass("sdc_expand", "run OpenSTA") { } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" sdc_expand [options]\n"); + log("\n"); + // TODO + log("\n"); + log(" -exe \n"); + log(" use to run OpenSTA instead of \"%s\"\n", default_sta_cmd); + log("\n"); + log(" -sdc-in \n"); + log(" expand SDC file \n"); + log("\n"); + log(" -nocleanup\n"); + log("\n"); + log("\n"); + log("The following commands are executed by this synthesis command:\n"); + help_script(); + log("\n"); + } + + string opensta_exe, sdc_filename, sdc_expanded_filename; + bool cleanup; + void execute(std::vector args, RTLIL::Design *design) override + { + string run_from, run_to; + cleanup = true; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-exe" && argidx+1 < args.size()) { + opensta_exe = args[++argidx]; + continue; + } + if (args[argidx] == "-sdc-in" && argidx+1 < args.size()) { + sdc_filename = args[++argidx]; + continue; + } + if (args[argidx] == "-sdc-out" && argidx+1 < args.size()) { + sdc_expanded_filename = args[++argidx]; + continue; + } + if (args[argidx] == "-nocleanup") { + cleanup = false; + continue; + } + // repetitive boring bit + if (args[argidx] == "-run" && argidx+1 < args.size()) { + size_t pos = args[argidx+1].find(':'); + if (pos == std::string::npos) { + run_from = args[++argidx]; + run_to = args[argidx]; + } else { + run_from = args[++argidx].substr(0, pos); + run_to = args[argidx].substr(pos+1); + } + continue; + } + break; + } + extra_args(args, argidx, design); + + if (!design->full_selection()) + log_cmd_error("This command only operates on fully selected designs!\n"); + + log_header(design, "Executing OPENSTA pass.\n"); + log_push(); + + run_script(design, run_from, run_to); + + log_pop(); + } + + void script() override + { + std::string tempdir_name; + std::string liberty_path; + std::string verilog_path; + + run("design -save pre_expand"); + run("proc"); + run("memory"); + // run("dfflegalize -cell $dff"); + + std::string write_verilog_cmd = "write_verilog -norename -noexpr -attr2comment -defparam "; + if (help_mode) { + run(write_verilog_cmd + "/elaborated.v"); + } else { + if (cleanup) + tempdir_name = get_base_tmpdir() + "/"; + else + tempdir_name = "_tmp_"; + tempdir_name += proc_program_prefix() + "yosys-sdc_expand-XXXXXX"; + tempdir_name = make_temp_dir(tempdir_name); + verilog_path = tempdir_name + "/elaborated.v"; + run(write_verilog_cmd + verilog_path); + } + + run("read_verilog -setattr whitebox -defer -DSIMLIB_NOCHECKS +/simlib.v"); + run("proc"); + run("hierarchy -auto-top"); + run("chtype -publish_icells"); + + if (help_mode) { + run("icell_liberty /yosys.lib"); + } else { + liberty_path = tempdir_name + "/yosys.lib"; + run(stringf("icell_liberty %s", liberty_path.c_str())); + } + + std::string opensta_pass_call = "opensta -exe "; + opensta_pass_call += help_mode ? "" : opensta_exe; + opensta_pass_call += " -sdc-in "; + opensta_pass_call += help_mode ? "" : sdc_filename; + opensta_pass_call += " -sdc-out "; + opensta_pass_call += help_mode ? "" : sdc_expanded_filename; + opensta_pass_call += " -verilog "; + opensta_pass_call += help_mode ? "" : verilog_path; + opensta_pass_call += " -liberty "; + opensta_pass_call += help_mode ? "/yosys.lib" : liberty_path; + if (!cleanup) + opensta_pass_call += " -nocleanup"; + run(opensta_pass_call.c_str()); + + if (!help_mode) { + if (cleanup) { + log("Removing temp directory.\n"); + remove_directory(tempdir_name); + } else { + log("Keeping temp directory %s\n", tempdir_name.c_str()); + } + } + run("design -load pre_expand"); + } +} SdcexpandPass; + +PRIVATE_NAMESPACE_END From 0c4105d72c3d5b512176b75a8c825307cd95debb Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 31 Jul 2025 15:38:45 +0200 Subject: [PATCH 06/45] opensta: quiet net width mismatch warning --- techlibs/common/opensta.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 1b3571973..bfd36c380 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -99,8 +99,13 @@ struct OpenstaPass : public Pass f_script << "read_sdc " << sdc_filename << "\n"; f_script << "write_sdc " << sdc_expanded_filename << "\n"; f_script.close(); - std::string command = opensta_exe + " -exit " + script_filename; - int ret = run_command(command); + std::string command = opensta_exe + " -exit " + script_filename; + auto process_line = [](const std::string &line) { + if (line.find("does not match net size") != std::string::npos) + return; + log("OpenSTA: %s", line.c_str()); + }; + int ret = run_command(command, process_line); if (ret) log_error("OpenSTA return %d (error)\n", ret); else From 7bed6ec658e2f9137131dca03b8a7b55a5d843a6 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 2 Oct 2025 15:26:24 +0200 Subject: [PATCH 07/45] opensta: quiet blackbox warning --- techlibs/common/opensta.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index bfd36c380..0a93a915c 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -101,6 +101,8 @@ struct OpenstaPass : public Pass f_script.close(); std::string command = opensta_exe + " -exit " + script_filename; auto process_line = [](const std::string &line) { + if (line.find("Creating black box") != std::string::npos) + return; if (line.find("does not match net size") != std::string::npos) return; log("OpenSTA: %s", line.c_str()); From 793594bd5973d2f48100aaec7c67c93d439d9956 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 2 Oct 2025 16:45:32 +0200 Subject: [PATCH 08/45] sdc_expand: log header --- techlibs/common/sdc_expand.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index 7efaf8c0d..baa281dd7 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -35,6 +35,7 @@ struct SdcexpandPass : public ScriptPass bool cleanup; void execute(std::vector args, RTLIL::Design *design) override { + log_header(design, "Executing SDC_EXPAND pass.\n"); string run_from, run_to; cleanup = true; From 7bc88d5c4098255a8be65cba16c0e12b35c8d0e5 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 16:11:03 +0200 Subject: [PATCH 09/45] sdc_expand: cleanup --- techlibs/common/sdc_expand.cc | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index baa281dd7..2fd5f380e 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -32,12 +32,12 @@ struct SdcexpandPass : public ScriptPass } string opensta_exe, sdc_filename, sdc_expanded_filename; - bool cleanup; + bool cleanup = true; void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC_EXPAND pass.\n"); string run_from, run_to; - cleanup = true; + opensta_exe = "sta"; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) @@ -58,7 +58,6 @@ struct SdcexpandPass : public ScriptPass cleanup = false; continue; } - // repetitive boring bit if (args[argidx] == "-run" && argidx+1 < args.size()) { size_t pos = args[argidx+1].find(':'); if (pos == std::string::npos) { @@ -72,6 +71,12 @@ struct SdcexpandPass : public ScriptPass } break; } + + if (sdc_filename.empty()) + log_cmd_error("Missing -sdc-in argument\n"); + if (sdc_expanded_filename.empty()) + log_cmd_error("Missing -sdc-out argument\n"); + extra_args(args, argidx, design); if (!design->full_selection()) @@ -88,17 +93,14 @@ struct SdcexpandPass : public ScriptPass void script() override { std::string tempdir_name; - std::string liberty_path; - std::string verilog_path; run("design -save pre_expand"); run("proc"); run("memory"); // run("dfflegalize -cell $dff"); - std::string write_verilog_cmd = "write_verilog -norename -noexpr -attr2comment -defparam "; if (help_mode) { - run(write_verilog_cmd + "/elaborated.v"); + tempdir_name = ""; } else { if (cleanup) tempdir_name = get_base_tmpdir() + "/"; @@ -106,21 +108,18 @@ struct SdcexpandPass : public ScriptPass tempdir_name = "_tmp_"; tempdir_name += proc_program_prefix() + "yosys-sdc_expand-XXXXXX"; tempdir_name = make_temp_dir(tempdir_name); - verilog_path = tempdir_name + "/elaborated.v"; - run(write_verilog_cmd + verilog_path); } + std::string verilog_path = tempdir_name + "/elaborated.v"; + std::string write_verilog_cmd = "write_verilog -norename -noexpr -attr2comment -defparam "; + run(write_verilog_cmd + verilog_path); run("read_verilog -setattr whitebox -defer -DSIMLIB_NOCHECKS +/simlib.v"); run("proc"); run("hierarchy -auto-top"); run("chtype -publish_icells"); - if (help_mode) { - run("icell_liberty /yosys.lib"); - } else { - liberty_path = tempdir_name + "/yosys.lib"; - run(stringf("icell_liberty %s", liberty_path.c_str())); - } + std::string liberty_path = tempdir_name + "/yosys.lib"; + run("icell_liberty " + liberty_path); std::string opensta_pass_call = "opensta -exe "; opensta_pass_call += help_mode ? "" : opensta_exe; @@ -134,7 +133,7 @@ struct SdcexpandPass : public ScriptPass opensta_pass_call += help_mode ? "/yosys.lib" : liberty_path; if (!cleanup) opensta_pass_call += " -nocleanup"; - run(opensta_pass_call.c_str()); + run(opensta_pass_call); if (!help_mode) { if (cleanup) { From 9f07e21e35558200fc180c98bf301942bdc6861c Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 16:25:47 +0200 Subject: [PATCH 10/45] rtlil: undeprecate builtin_ff_cell_types --- kernel/rtlil.h | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/rtlil.h b/kernel/rtlil.h index a8e43fe1d..4da030e8d 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -728,7 +728,6 @@ template <> struct IDMacroHelper<-1> { namespace RTLIL { extern dict constpad; - [[deprecated("Call cell->is_builtin_ff() instead")]] const pool &builtin_ff_cell_types(); static inline std::string escape_id(const std::string &str) { From cee3d0b598046f6410f417891f5942372a1a09d6 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 13:23:14 +0100 Subject: [PATCH 11/45] icell_liberty: refactor and add help --- passes/cmds/icell_liberty.cc | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc index 83252ffb4..01e25b9e9 100644 --- a/passes/cmds/icell_liberty.cc +++ b/passes/cmds/icell_liberty.cc @@ -112,12 +112,6 @@ struct LibertyStubber { auto next_state = ffType.has_ce ? "D & EN" : "D"; i.item("next_state", next_state); f << "\t\t}\n"; - - - // bool has_aload; - // bool has_srst; - // bool has_arst; - // bool has_sr; f << "\t}\n"; } void liberty_cell(Module* base, Module* derived, std::ostream& f) @@ -157,8 +151,11 @@ struct IcellLiberty : Pass { { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); - log(" icell_liberty \n"); // TODO + log(" icell_liberty \n"); log("\n"); + log("Write Liberty files modeling the interfaces of used internal cells.\n"); + log("\n"); + log("Models are not guaranteed to be logically sound.\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *d) override @@ -168,7 +165,7 @@ struct IcellLiberty : Pass { size_t argidx; IdString naming_attr; std::string liberty_filename; - std::ofstream* liberty_file = new std::ofstream; + auto liberty_file = std::make_unique(); for (argidx = 1; argidx < args.size(); argidx++) { break; @@ -176,15 +173,12 @@ struct IcellLiberty : Pass { if (argidx < args.size()) liberty_filename = args[argidx++]; else - log_error("no Liberty filename specified\n"); - - // extra_args(args, argidx, d); + log_cmd_error("no Liberty filename specified\n"); if (liberty_filename.size()) { liberty_file->open(liberty_filename.c_str()); if (liberty_file->fail()) { - delete liberty_file; - log_error("Can't open file `%s' for writing: %s\n", liberty_filename.c_str(), strerror(errno)); + log_cmd_error("Can't open file `%s' for writing: %s\n", liberty_filename.c_str(), strerror(errno)); } } @@ -209,7 +203,6 @@ struct IcellLiberty : Pass { if (liberty_file) { stubber.liberty_suffix(*liberty_file); - delete liberty_file; } } } IcellLiberty; From 5acb77cab1a9b1c67f5f978771c9d336756f4293 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 13:30:07 +0100 Subject: [PATCH 12/45] sdc_expand, opensta: typos --- techlibs/common/opensta.cc | 2 +- techlibs/common/sdc_expand.cc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 0a93a915c..7f2b15243 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -109,7 +109,7 @@ struct OpenstaPass : public Pass }; int ret = run_command(command, process_line); if (ret) - log_error("OpenSTA return %d (error)\n", ret); + log_error("OpenSTA returned %d (error)\n", ret); else log("sdc_expanded_filename: %s\n", sdc_expanded_filename.c_str()); diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index 2fd5f380e..1b13923f0 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -4,10 +4,10 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -struct SdcexpandPass : public ScriptPass +struct SdcExpandPass : public ScriptPass { const char* default_sta_cmd = "sta"; - SdcexpandPass() : ScriptPass("sdc_expand", "run OpenSTA") { } + SdcExpandPass() : ScriptPass("sdc_expand", "run OpenSTA") { } void help() override { @@ -145,6 +145,6 @@ struct SdcexpandPass : public ScriptPass } run("design -load pre_expand"); } -} SdcexpandPass; +} SdcExpandPass; PRIVATE_NAMESPACE_END From 6846168db3e634a737cd1349458de35d55a5e8c2 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 13:35:51 +0100 Subject: [PATCH 13/45] opensta: opensta.exe scratchpad variable --- techlibs/common/opensta.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 7f2b15243..fbdf27304 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -35,7 +35,7 @@ struct OpenstaPass : public Pass void execute(std::vector args, RTLIL::Design *design) override { string run_from, run_to; - string opensta_exe = "sta"; + string opensta_exe = design->scratchpad_get_string("opensta.exe", "sta"); string sdc_filename, sdc_expanded_filename; string tempdir_name, script_filename; string verilog_filename, liberty_filename; From 9a5465bc83c568b7890bbd5b506a642bba8ebe5c Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 13:37:19 +0100 Subject: [PATCH 14/45] icell_liberty: simplify --- passes/cmds/icell_liberty.cc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc index 01e25b9e9..d49cd360a 100644 --- a/passes/cmds/icell_liberty.cc +++ b/passes/cmds/icell_liberty.cc @@ -185,8 +185,7 @@ struct IcellLiberty : Pass { pool done; LibertyStubber stubber = {}; - if (liberty_file) - stubber.liberty_prefix(*liberty_file); + stubber.liberty_prefix(*liberty_file); for (auto module : d->selected_modules()) { for (auto cell : module->selected_cells()) { @@ -201,9 +200,7 @@ struct IcellLiberty : Pass { } } - if (liberty_file) { - stubber.liberty_suffix(*liberty_file); - } + stubber.liberty_suffix(*liberty_file); } } IcellLiberty; From a5b6c3cc191c2f18f52f45c1e34588a7bbe639b4 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 13:44:12 +0100 Subject: [PATCH 15/45] opensta, sdc_expand: more scratchpad --- techlibs/common/opensta.cc | 2 +- techlibs/common/sdc_expand.cc | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index fbdf27304..6d793b3e1 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -39,7 +39,7 @@ struct OpenstaPass : public Pass string sdc_filename, sdc_expanded_filename; string tempdir_name, script_filename; string verilog_filename, liberty_filename; - bool cleanup = true; + bool cleanup = design->scratchpad_get_bool("opensta.cleanup", true); log_header(design, "Executing OPENSTA pass.\n"); log_push(); diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index 1b13923f0..cec5e8c1e 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -32,12 +32,13 @@ struct SdcExpandPass : public ScriptPass } string opensta_exe, sdc_filename, sdc_expanded_filename; - bool cleanup = true; + bool cleanup; void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC_EXPAND pass.\n"); string run_from, run_to; - opensta_exe = "sta"; + opensta_exe = ""; + cleanup = design->scratchpad_get_bool("sdc_expand.cleanup", true); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) @@ -121,8 +122,11 @@ struct SdcExpandPass : public ScriptPass std::string liberty_path = tempdir_name + "/yosys.lib"; run("icell_liberty " + liberty_path); - std::string opensta_pass_call = "opensta -exe "; - opensta_pass_call += help_mode ? "" : opensta_exe; + std::string opensta_pass_call = "opensta "; + if (opensta_exe.length()) { + opensta_pass_call += "-exe "; + opensta_pass_call += help_mode ? "" : opensta_exe; + } opensta_pass_call += " -sdc-in "; opensta_pass_call += help_mode ? "" : sdc_filename; opensta_pass_call += " -sdc-out "; From 411fc149df9d727f561ca04e2149e1dc4af2abf3 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 16:06:46 +0100 Subject: [PATCH 16/45] opensta: refactor default command --- techlibs/common/opensta.cc | 6 +++--- techlibs/common/opensta.h | 8 ++++++++ techlibs/common/sdc_expand.cc | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 techlibs/common/opensta.h diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 6d793b3e1..2a806edf6 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -1,5 +1,6 @@ #include "kernel/rtlil.h" #include "kernel/log.h" +#include "techlibs/common/opensta.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -7,7 +8,6 @@ PRIVATE_NAMESPACE_BEGIN #if !defined(YOSYS_DISABLE_SPAWN) struct OpenstaPass : public Pass { - const char* default_sta_cmd = "sta"; OpenstaPass() : Pass("opensta", "run OpenSTA") { } void help() override @@ -19,7 +19,7 @@ struct OpenstaPass : public Pass // TOOD log("\n"); log(" -exe \n"); - log(" use to run OpenSTA instead of \"%s\"\n", default_sta_cmd); + log(" use to run OpenSTA instead of \"%s\"\n", default_opensta_cmd); log("\n"); log(" -sdc-in \n"); log(" expand SDC input file \n"); @@ -35,7 +35,7 @@ struct OpenstaPass : public Pass void execute(std::vector args, RTLIL::Design *design) override { string run_from, run_to; - string opensta_exe = design->scratchpad_get_string("opensta.exe", "sta"); + string opensta_exe = design->scratchpad_get_string("opensta.exe", default_opensta_cmd); string sdc_filename, sdc_expanded_filename; string tempdir_name, script_filename; string verilog_filename, liberty_filename; diff --git a/techlibs/common/opensta.h b/techlibs/common/opensta.h new file mode 100644 index 000000000..4c5ac871f --- /dev/null +++ b/techlibs/common/opensta.h @@ -0,0 +1,8 @@ +#include "kernel/yosys_common.h" +#ifndef OPENSTA_H +YOSYS_NAMESPACE_BEGIN + +static const char* default_opensta_cmd = "sta"; + +YOSYS_NAMESPACE_END +#endif /* OPENSTA_H */ diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index cec5e8c1e..95254d1f4 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -1,12 +1,12 @@ #include "kernel/rtlil.h" #include "kernel/log.h" +#include "techlibs/common/opensta.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct SdcExpandPass : public ScriptPass { - const char* default_sta_cmd = "sta"; SdcExpandPass() : ScriptPass("sdc_expand", "run OpenSTA") { } void help() override @@ -18,7 +18,7 @@ struct SdcExpandPass : public ScriptPass // TODO log("\n"); log(" -exe \n"); - log(" use to run OpenSTA instead of \"%s\"\n", default_sta_cmd); + log(" use to run OpenSTA instead of \"%s\"\n", default_opensta_cmd); log("\n"); log(" -sdc-in \n"); log(" expand SDC file \n"); From 85d2702ef6b78dbcc6138cc8745f6bd796fa2d23 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 16:16:33 +0100 Subject: [PATCH 17/45] opensta, sdc_expand: fix help --- techlibs/common/opensta.cc | 4 +++- techlibs/common/sdc_expand.cc | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 2a806edf6..282481953 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -16,7 +16,9 @@ struct OpenstaPass : public Pass log("\n"); log(" opensta [options]\n"); log("\n"); - // TOOD + log("Expand SDC file with OpenSTA.\n"); + log("Internal command like abc. Requires a well-formed design.\n"); + log("For general SDC expansion with OpenSTA, use the sdc_expand command.\n"); log("\n"); log(" -exe \n"); log(" use to run OpenSTA instead of \"%s\"\n", default_opensta_cmd); diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index 95254d1f4..fe8f20aa9 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -7,7 +7,7 @@ PRIVATE_NAMESPACE_BEGIN struct SdcExpandPass : public ScriptPass { - SdcExpandPass() : ScriptPass("sdc_expand", "run OpenSTA") { } + SdcExpandPass() : ScriptPass("sdc_expand", "expand SDC design with opensta") { } void help() override { @@ -15,7 +15,8 @@ struct SdcExpandPass : public ScriptPass log("\n"); log(" sdc_expand [options]\n"); log("\n"); - // TODO + log("Expand SDC file with opensta based on arbitrary current design.\n"); + log("Changes the design but only temporarily.\n"); log("\n"); log(" -exe \n"); log(" use to run OpenSTA instead of \"%s\"\n", default_opensta_cmd); From c281bac461ee64157e66134cee3828856d99c5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Ji=C5=99=C3=AD=20Tywoniak?= Date: Fri, 2 May 2025 17:48:33 +0200 Subject: [PATCH 18/45] sdc: add initial stubbed demo --- kernel/driver.cc | 4 ++-- kernel/tclapi.cc | 60 +++++++++++++++++++++++++++++++++++++++++++++++- kernel/yosys.cc | 34 +++++++++++++++++++++++++-- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/kernel/driver.cc b/kernel/driver.cc index 726e25302..4097411b4 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -188,7 +188,7 @@ extern char yosys_path[PATH_MAX]; #endif #ifdef YOSYS_ENABLE_TCL namespace Yosys { - extern int yosys_tcl_iterp_init(Tcl_Interp *interp); + extern int yosys_tcl_interp_init(Tcl_Interp *interp); extern void yosys_tcl_activate_repl(); }; #endif @@ -610,7 +610,7 @@ int main(int argc, char **argv) if (run_tcl_shell) { #ifdef YOSYS_ENABLE_TCL yosys_tcl_activate_repl(); - Tcl_Main(argc, argv, yosys_tcl_iterp_init); + Tcl_Main(argc, argv, yosys_tcl_interp_init); #else log_error("Can't exectue TCL shell: this version of yosys is not built with TCL support enabled.\n"); #endif diff --git a/kernel/tclapi.cc b/kernel/tclapi.cc index a2ebaffa2..a25aa4c0c 100644 --- a/kernel/tclapi.cc +++ b/kernel/tclapi.cc @@ -533,7 +533,65 @@ static int tcl_set_param(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *cons return TCL_OK; } -int yosys_tcl_iterp_init(Tcl_Interp *interp) +static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + (void)interp; + bool hierarchical_flag = false; + bool regexp_flag = false; + bool nocase_flag = false; + std::string separator = "/"; + Tcl_Obj* of_objects; + std::vector patterns; + int i = 1; + for (; i < objc; i++) { + FLAG2(hierarchical) + FLAG2(regexp) + FLAG2(nocase) + if (!strcmp(Tcl_GetString(objv[i]), "-hsc")) { + i++; + separator = Tcl_GetString(objv[i]); + continue; + } + if (!strcmp(Tcl_GetString(objv[i]), "-of_objects")) { + i++; + of_objects = objv[i]; + continue; + } + // Onto the next loop + break; + } + for (; i < objc; i++) { + patterns.push_back(Tcl_GetString(objv[i])); + } + log("get_pins patterns:\n"); + for (auto pat : patterns) { + log("\t%s\n", pat.c_str()); + } + (void)hierarchical_flag; + (void)regexp_flag; + (void)nocase_flag; + (void)separator; + (void)of_objects; + return TCL_OK; +} +static int sdc_dummy_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + (void)interp; + (void)objc; + log("Ignoring known SDC command %s\n", Tcl_GetString(objv[0])); + return TCL_OK; +} + +int yosys_sdc_interp_init(Tcl_Interp *interp) +{ + if (Tcl_Init(interp)!=TCL_OK) + log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); + Tcl_CreateObjCommand(interp, "set_false_path", sdc_dummy_cmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); + return TCL_OK; +} + +int yosys_tcl_interp_init(Tcl_Interp *interp) { if (Tcl_Init(interp)!=TCL_OK) log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); diff --git a/kernel/yosys.cc b/kernel/yosys.cc index 4e4abed20..cab1e3ee7 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -95,6 +95,7 @@ CellTypes yosys_celltypes; #ifdef YOSYS_ENABLE_TCL Tcl_Interp *yosys_tcl_interp = NULL; +Tcl_Interp *yosys_sdc_interp = NULL; #endif std::set yosys_input_files, yosys_output_files; @@ -393,16 +394,25 @@ void rewrite_filename(std::string &filename) #ifdef YOSYS_ENABLE_TCL // defined in tclapi.cc -extern int yosys_tcl_iterp_init(Tcl_Interp *interp); +extern int yosys_tcl_interp_init(Tcl_Interp *interp); +extern int yosys_sdc_interp_init(Tcl_Interp *interp); extern Tcl_Interp *yosys_get_tcl_interp() { if (yosys_tcl_interp == NULL) { yosys_tcl_interp = Tcl_CreateInterp(); - yosys_tcl_iterp_init(yosys_tcl_interp); + yosys_tcl_interp_init(yosys_tcl_interp); } return yosys_tcl_interp; } +extern Tcl_Interp *yosys_get_sdc_interp() +{ + if (yosys_sdc_interp == NULL) { + yosys_sdc_interp = Tcl_CreateInterp(); + yosys_sdc_interp_init(yosys_sdc_interp); + } + return yosys_sdc_interp; +} struct TclPass : public Pass { TclPass() : Pass("tcl", "execute a TCL script file") { } @@ -446,6 +456,26 @@ struct TclPass : public Pass { Tcl_Release(interp); } } TclPass; +struct SdcPass : public Pass { + SdcPass() : Pass("sdc", "sniff at some SDC") { } + void execute(std::vector args, RTLIL::Design *) override { + if (args.size() < 2) + log_cmd_error("Missing script file.\n"); + + std::vector script_args; + for (auto it = args.begin() + 2; it != args.end(); ++it) + script_args.push_back(Tcl_NewStringObj((*it).c_str(), (*it).size())); + + Tcl_Interp *interp = yosys_get_sdc_interp(); + Tcl_Preserve(interp); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argc", 4), NULL, Tcl_NewIntObj(script_args.size()), 0); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv", 4), NULL, Tcl_NewListObj(script_args.size(), script_args.data()), 0); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv0", 5), NULL, Tcl_NewStringObj(args[1].c_str(), args[1].size()), 0); + if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) + log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); + Tcl_Release(interp); + } +} SdcPass; #endif #if defined(__linux__) || defined(__CYGWIN__) From 5a798b64ef19122c3539eb208afcbf757dff56f8 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Mon, 26 May 2025 22:53:16 +0200 Subject: [PATCH 19/45] sdc: separate out --- kernel/tclapi.cc | 58 -------------------- kernel/yosys.cc | 29 +--------- passes/cmds/Makefile.inc | 3 ++ passes/cmds/sdc.cc | 112 +++++++++++++++++++++++++++++++++++++++ passes/cmds/stubs.sdc | 1 + 5 files changed, 117 insertions(+), 86 deletions(-) create mode 100644 passes/cmds/sdc.cc create mode 100644 passes/cmds/stubs.sdc diff --git a/kernel/tclapi.cc b/kernel/tclapi.cc index a25aa4c0c..ec0483a4a 100644 --- a/kernel/tclapi.cc +++ b/kernel/tclapi.cc @@ -533,64 +533,6 @@ static int tcl_set_param(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *cons return TCL_OK; } -static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) -{ - (void)interp; - bool hierarchical_flag = false; - bool regexp_flag = false; - bool nocase_flag = false; - std::string separator = "/"; - Tcl_Obj* of_objects; - std::vector patterns; - int i = 1; - for (; i < objc; i++) { - FLAG2(hierarchical) - FLAG2(regexp) - FLAG2(nocase) - if (!strcmp(Tcl_GetString(objv[i]), "-hsc")) { - i++; - separator = Tcl_GetString(objv[i]); - continue; - } - if (!strcmp(Tcl_GetString(objv[i]), "-of_objects")) { - i++; - of_objects = objv[i]; - continue; - } - // Onto the next loop - break; - } - for (; i < objc; i++) { - patterns.push_back(Tcl_GetString(objv[i])); - } - log("get_pins patterns:\n"); - for (auto pat : patterns) { - log("\t%s\n", pat.c_str()); - } - (void)hierarchical_flag; - (void)regexp_flag; - (void)nocase_flag; - (void)separator; - (void)of_objects; - return TCL_OK; -} -static int sdc_dummy_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) -{ - (void)interp; - (void)objc; - log("Ignoring known SDC command %s\n", Tcl_GetString(objv[0])); - return TCL_OK; -} - -int yosys_sdc_interp_init(Tcl_Interp *interp) -{ - if (Tcl_Init(interp)!=TCL_OK) - log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); - Tcl_CreateObjCommand(interp, "set_false_path", sdc_dummy_cmd, NULL, NULL); - Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); - return TCL_OK; -} - int yosys_tcl_interp_init(Tcl_Interp *interp) { if (Tcl_Init(interp)!=TCL_OK) diff --git a/kernel/yosys.cc b/kernel/yosys.cc index cab1e3ee7..009da56b9 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -395,7 +395,6 @@ void rewrite_filename(std::string &filename) // defined in tclapi.cc extern int yosys_tcl_interp_init(Tcl_Interp *interp); -extern int yosys_sdc_interp_init(Tcl_Interp *interp); extern Tcl_Interp *yosys_get_tcl_interp() { @@ -405,15 +404,8 @@ extern Tcl_Interp *yosys_get_tcl_interp() } return yosys_tcl_interp; } -extern Tcl_Interp *yosys_get_sdc_interp() -{ - if (yosys_sdc_interp == NULL) { - yosys_sdc_interp = Tcl_CreateInterp(); - yosys_sdc_interp_init(yosys_sdc_interp); - } - return yosys_sdc_interp; -} +// Also see SdcPass struct TclPass : public Pass { TclPass() : Pass("tcl", "execute a TCL script file") { } void help() override { @@ -456,26 +448,7 @@ struct TclPass : public Pass { Tcl_Release(interp); } } TclPass; -struct SdcPass : public Pass { - SdcPass() : Pass("sdc", "sniff at some SDC") { } - void execute(std::vector args, RTLIL::Design *) override { - if (args.size() < 2) - log_cmd_error("Missing script file.\n"); - std::vector script_args; - for (auto it = args.begin() + 2; it != args.end(); ++it) - script_args.push_back(Tcl_NewStringObj((*it).c_str(), (*it).size())); - - Tcl_Interp *interp = yosys_get_sdc_interp(); - Tcl_Preserve(interp); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argc", 4), NULL, Tcl_NewIntObj(script_args.size()), 0); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv", 4), NULL, Tcl_NewListObj(script_args.size(), script_args.data()), 0); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv0", 5), NULL, Tcl_NewStringObj(args[1].c_str(), args[1].size()), 0); - if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) - log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); - Tcl_Release(interp); - } -} SdcPass; #endif #if defined(__linux__) || defined(__CYGWIN__) diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 2dbeadac2..f64fdea55 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -58,3 +58,6 @@ OBJS += passes/cmds/test_select.o OBJS += passes/cmds/timeest.o OBJS += passes/cmds/linecoverage.o OBJS += passes/cmds/sort.o +OBJS += passes/cmds/sdc.o + +$(eval $(call add_share_file,share/sdc,passes/cmds/stubs.sdc)) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc new file mode 100644 index 000000000..539b349f7 --- /dev/null +++ b/passes/cmds/sdc.cc @@ -0,0 +1,112 @@ +#include "kernel/register.h" +#include "kernel/celltypes.h" +#include "kernel/rtlil.h" +#include "kernel/log.h" +#include + + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +template +static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { + std::string expected = std::string("-") + flag_name; + if (expected == arg) { + flag_var = true; + return true; + } + return false; +} + +static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +{ + (void)interp; + bool hierarchical_flag = false; + bool regexp_flag = false; + bool nocase_flag = false; + std::string separator = "/"; + Tcl_Obj* of_objects; + std::vector patterns; + int i = 1; + for (; i < objc; i++) { + if (parse_flag(Tcl_GetString(objv[i]), "hierarchical", hierarchical_flag)) + if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; + if (!strcmp(Tcl_GetString(objv[i]), "-hsc")) { + separator = Tcl_GetString(objv[++i]); + continue; + } + if (!strcmp(Tcl_GetString(objv[i]), "-of_objects")) { + of_objects = objv[++i]; + continue; + } + // Onto the next loop + break; + } + for (; i < objc; i++) { + patterns.push_back(Tcl_GetString(objv[i])); + } + log("get_pins patterns:\n"); + for (auto pat : patterns) { + log("\t%s\n", pat.c_str()); + } + (void)hierarchical_flag; + (void)regexp_flag; + (void)nocase_flag; + (void)separator; + (void)of_objects; + return TCL_OK; +} + +class SDCInterpreter +{ +private: + Tcl_Interp* interp = nullptr; +public: + ~SDCInterpreter() { + if (interp) + Tcl_DeleteInterp(interp); + } + static SDCInterpreter& get() { + static SDCInterpreter instance; + return instance; + } + Tcl_Interp* fresh_interp() { + if (interp) + Tcl_DeleteInterp(interp); + interp = Tcl_CreateInterp(); + if (Tcl_Init(interp)!=TCL_OK) + log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); + Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); + return interp; + } +}; + +// Also see TclPass +struct SdcPass : public Pass { + SdcPass() : Pass("sdc", "sniff at some SDC") { } + void execute(std::vector args, RTLIL::Design *) override { + if (args.size() < 2) + log_cmd_error("Missing script file.\n"); + + std::vector script_args; + for (auto it = args.begin() + 2; it != args.end(); ++it) + script_args.push_back(Tcl_NewStringObj((*it).c_str(), (*it).size())); + + Tcl_Interp *interp = SDCInterpreter::get().fresh_interp(); + Tcl_Preserve(interp); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argc", 4), NULL, Tcl_NewIntObj(script_args.size()), 0); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv", 4), NULL, Tcl_NewListObj(script_args.size(), script_args.data()), 0); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv0", 5), NULL, Tcl_NewStringObj(args[1].c_str(), args[1].size()), 0); + + std::string stub_path = "+/sdc/stubs.sdc"; + rewrite_filename(stub_path); + if (Tcl_EvalFile(interp, stub_path.c_str()) != TCL_OK) + log_cmd_error("SDC interpreter returned an error in stub file: %s\n", Tcl_GetStringResult(interp)); + if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) + log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); + Tcl_Release(interp); + } +} SdcPass; + +YOSYS_NAMESPACE_END diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc new file mode 100644 index 000000000..bf41c4620 --- /dev/null +++ b/passes/cmds/stubs.sdc @@ -0,0 +1 @@ +proc set_false_path {args} {} \ No newline at end of file From c1c6ec12662c7a3330910f035c647fe6bce2df42 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 27 May 2025 11:33:32 +0200 Subject: [PATCH 20/45] sdc: stubs SDC commands supported by Vivado --- passes/cmds/stubs.sdc | 106 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index bf41c4620..9f5d03b87 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -1 +1,105 @@ -proc set_false_path {args} {} \ No newline at end of file +proc stub {function_name} { + proc $function_name {args} {} +} + +# Vivado UG903 +stub add_cells_to_pblock +stub add_to_power_rail +stub all_clocks +stub all_cpus +stub all_dsps +stub all_fanin +stub all_fanout +stub all_ffs +stub all_hsios +stub all_inputs +stub all_latches +stub all_outputs +stub all_rams +stub all_registers +stub connect_debug_port +stub create_clock +stub create_debug_core +stub create_debug_port +stub create_generated_clock +stub create_macro +stub create_pblock +stub create_power_rail +stub create_property +stub create_waiver +stub current_design +stub current_instance +stub delete_macros +stub delete_pblock +stub delete_power_rails +stub endgroup +stub get_bel_pins +stub get_bels +stub get_cells +stub get_clocks +stub get_debug_cores +stub get_debug_ports +stub get_generated_clocks +stub get_hierarchy_separator +stub get_iobanks +stub get_macros +stub get_nets +stub get_nodes +stub get_package_pins +stub get_path_groups +stub get_pblocks +stub get_pins +stub get_pips +stub get_pkgpin_bytegroups +stub get_pkgpin_nibbles +stub get_ports +stub get_power_rails +stub get_property +stub get_site_pins +stub get_site_pips +stub get_sites +stub get_slrs +stub get_speed_models +stub get_tiles +stub get_timing_arcs +stub get_wires +stub group_path +stub make_diff_pair_ports +stub remove_cells_from_pblock +stub remove_from_power_rail +stub reset_operating_conditions +stub reset_switching_activity +stub resize_pblock +stub set_bus_skew +stub set_case_analysis +stub set_clock_groups +stub set_clock_latency +stub set_clock_sense +stub set_clock_uncertainty +stub set_data_check +stub set_disable_timing +stub set_external_delay +stub set_false_path +stub set_hierarchy_separator +stub set_input_delay +stub set_input_jitter +stub set_load +stub set_logic_dc +stub set_logic_one +stub set_logic_unconnected +stub set_logic_zero +stub set_max_delay +stub set_max_time_borrow +stub set_min_delay +stub set_multicycle_path +stub set_operating_conditions +stub set_output_delay +stub set_package_pin_val +stub set_power_opt +stub set_propagated_clock +stub set_property +stub set_switching_activity +stub set_system_jitter +stub set_units +stub startgroup +stub update_macro From c26aa3186de8b7ccb9b27985580790b441fc6ff7 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 27 May 2025 17:14:23 +0200 Subject: [PATCH 21/45] sdc: collect design objects --- passes/cmds/sdc.cc | 202 +++++++++++++++++++++++++++++++++--------- passes/cmds/stubs.sdc | 14 +-- tests/various/sdc.sdc | 5 ++ 3 files changed, 174 insertions(+), 47 deletions(-) create mode 100644 tests/various/sdc.sdc diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 539b349f7..0343c6b09 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -3,6 +3,7 @@ #include "kernel/rtlil.h" #include "kernel/log.h" #include +#include USING_YOSYS_NAMESPACE @@ -10,17 +11,22 @@ PRIVATE_NAMESPACE_BEGIN template static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { - std::string expected = std::string("-") + flag_name; - if (expected == arg) { - flag_var = true; - return true; - } - return false; + std::string expected = std::string("-") + flag_name; + if (expected == arg) { + flag_var = true; + return true; + } + return false; } +// TODO return values like json_to_tcl on result.json? +// TODO vectors +// TODO cell arrays? + static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { (void)interp; + // When this flag is present, the search for the pattern is made in all positions in the hierarchy. bool hierarchical_flag = false; bool regexp_flag = false; bool nocase_flag = false; @@ -29,9 +35,10 @@ static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* c std::vector patterns; int i = 1; for (; i < objc; i++) { - if (parse_flag(Tcl_GetString(objv[i]), "hierarchical", hierarchical_flag)) - if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; - if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "hierarchical", hierarchical_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "hier", hierarchical_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; if (!strcmp(Tcl_GetString(objv[i]), "-hsc")) { separator = Tcl_GetString(objv[++i]); continue; @@ -53,56 +60,167 @@ static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* c (void)hierarchical_flag; (void)regexp_flag; (void)nocase_flag; - (void)separator; (void)of_objects; + if (separator != "/") { + Tcl_SetObjResult(interp, Tcl_NewStringObj("Only '/' accepted as separator", -1)); + return TCL_ERROR; + } + return TCL_OK; +} + +static int sdc_get_ports_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +{ + (void)interp; + bool regexp_flag = false; + bool nocase_flag = false; + std::vector patterns; + int i = 1; + for (; i < objc; i++) { + if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; + // Onto the next loop + break; + } + for (; i < objc; i++) { + patterns.push_back(Tcl_GetString(objv[i])); + } + log("get_ports patterns:\n"); + for (auto pat : patterns) { + log("\t%s\n", pat.c_str()); + } + (void)regexp_flag; + (void)nocase_flag; return TCL_OK; } class SDCInterpreter { private: - Tcl_Interp* interp = nullptr; + Tcl_Interp* interp = nullptr; public: - ~SDCInterpreter() { - if (interp) - Tcl_DeleteInterp(interp); - } - static SDCInterpreter& get() { - static SDCInterpreter instance; - return instance; - } - Tcl_Interp* fresh_interp() { - if (interp) - Tcl_DeleteInterp(interp); - interp = Tcl_CreateInterp(); - if (Tcl_Init(interp)!=TCL_OK) - log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); - Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); - return interp; - } + ~SDCInterpreter() { + if (interp) + Tcl_DeleteInterp(interp); + } + static SDCInterpreter& get() { + static SDCInterpreter instance; + return instance; + } + Tcl_Interp* fresh_interp() { + if (interp) + Tcl_DeleteInterp(interp); + interp = Tcl_CreateInterp(); + if (Tcl_Init(interp)!=TCL_OK) + log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); + Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, NULL, NULL); + return interp; + } +}; + +struct SdcObjects { + std::vector ports; + std::vector> cells; + std::vector> pins; + std::vector> nets; + + void sniff_module(std::list& hierarchy, Module* mod) { + log_debug("sniffing module %s\n", mod->name.c_str()); + + std::string prefix; + for (auto mod_name : hierarchy) { + if (prefix.length()) + prefix += "/"; // TODO seperator? + prefix += mod_name; + } + + for (auto* wire : mod->wires()) { + std::string name = wire->name.str(); + log_assert(name.length()); + // TODO: really skip internal wires? + if (name[0] == '$') + continue; + name = name.substr(1); + std::string path = prefix; + if (path.length()) + path += "/"; + path += name; + nets.push_back(std::make_pair(path, wire)); + } + + for (auto* cell : mod->cells()) { + std::string name = cell->name.str(); + // TODO: really skip internal cells? + if (name[0] == '$') + continue; + name = name.substr(1); + std::string path = prefix; + if (path.length()) + path += "/"; + path += name; + cells.push_back(std::make_pair(path, cell)); + for (auto pin : cell->connections()) { + std::string pin_name = path + "/" + pin.first.str().substr(1); + pins.push_back(std::make_pair(pin_name, cell)); + } + if (auto sub_mod = mod->design->module(cell->type)) { + hierarchy.push_back(name); + sniff_module(hierarchy, sub_mod); + hierarchy.pop_back(); + } + } + } + SdcObjects(Design* design) { + Module* top = design->top_module(); + if (!top) + log_error("Top module couldn't be determined. Check 'top' attribute usage"); + for (auto port : top->ports) { + ports.push_back(port.str().substr(1)); + } + std::list hierarchy{}; + sniff_module(hierarchy, top); + } + void dump() { + log("Dumping detected design objects visible to SDC constraints\n"); + log("Ports:\n"); + for (auto name : ports) { + log("\t%s\n", name.c_str()); + } + log("Cells:\n"); + for (auto [name, cell] : cells) { + (void)cell; + log("\t%s\n", name.c_str()); + } + log("Pins:\n"); + for (auto [name, pin] : pins) { + (void)pin; + log("\t%s\n", name.c_str()); + } + log("Nets:\n"); + for (auto [name, net] : nets) { + (void)net; + log("\t%s\n", name.c_str()); + } + log("\n"); + } }; // Also see TclPass struct SdcPass : public Pass { + // TODO help SdcPass() : Pass("sdc", "sniff at some SDC") { } - void execute(std::vector args, RTLIL::Design *) override { + void execute(std::vector args, RTLIL::Design *design) override { if (args.size() < 2) - log_cmd_error("Missing script file.\n"); - - std::vector script_args; - for (auto it = args.begin() + 2; it != args.end(); ++it) - script_args.push_back(Tcl_NewStringObj((*it).c_str(), (*it).size())); - + log_cmd_error("Missing SDC file.\n"); + // TODO optional extra stub file Tcl_Interp *interp = SDCInterpreter::get().fresh_interp(); Tcl_Preserve(interp); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argc", 4), NULL, Tcl_NewIntObj(script_args.size()), 0); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv", 4), NULL, Tcl_NewListObj(script_args.size(), script_args.data()), 0); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv0", 5), NULL, Tcl_NewStringObj(args[1].c_str(), args[1].size()), 0); - - std::string stub_path = "+/sdc/stubs.sdc"; - rewrite_filename(stub_path); + std::string stub_path = "+/sdc/stubs.sdc"; + rewrite_filename(stub_path); + SdcObjects objects(design); + objects.dump(); if (Tcl_EvalFile(interp, stub_path.c_str()) != TCL_OK) - log_cmd_error("SDC interpreter returned an error in stub file: %s\n", Tcl_GetStringResult(interp)); + log_cmd_error("SDC interpreter returned an error in stub file: %s\n", Tcl_GetStringResult(interp)); if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); Tcl_Release(interp); diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index 9f5d03b87..8659ca392 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -1,7 +1,16 @@ +# with Tcl's eager evaluation, we will still eval args if they're unused by a stub proc stub {function_name} { proc $function_name {args} {} } +# OpenROAD +proc get_name {thing} { + return thing +} +proc get_full_name {thing} { + return thing +} + # Vivado UG903 stub add_cells_to_pblock stub add_to_power_rail @@ -35,7 +44,6 @@ stub delete_power_rails stub endgroup stub get_bel_pins stub get_bels -stub get_cells stub get_clocks stub get_debug_cores stub get_debug_ports @@ -43,16 +51,13 @@ stub get_generated_clocks stub get_hierarchy_separator stub get_iobanks stub get_macros -stub get_nets stub get_nodes stub get_package_pins stub get_path_groups stub get_pblocks -stub get_pins stub get_pips stub get_pkgpin_bytegroups stub get_pkgpin_nibbles -stub get_ports stub get_power_rails stub get_property stub get_site_pins @@ -62,7 +67,6 @@ stub get_slrs stub get_speed_models stub get_tiles stub get_timing_arcs -stub get_wires stub group_path stub make_diff_pair_ports stub remove_cells_from_pblock diff --git a/tests/various/sdc.sdc b/tests/various/sdc.sdc new file mode 100644 index 000000000..69efcb40a --- /dev/null +++ b/tests/various/sdc.sdc @@ -0,0 +1,5 @@ +puts "SDC constraints file says hello from arbitrary Tcl execution" +set_false_path -from [get_pins s1/sa] -to [get_pins s1/sb] +puts "This should print something:" +puts [get_ports {slink_clk_o slink_*_o}] +puts "Did it?" \ No newline at end of file From 9a3c7f70adaa1b9f7bebde3befd82701324a1804 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 27 May 2025 17:47:26 +0200 Subject: [PATCH 22/45] sdc: stubs SDC commands supported by OpenSTA --- passes/cmds/stubs.sdc | 125 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index 8659ca392..ea1600139 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -107,3 +107,128 @@ stub set_system_jitter stub set_units stub startgroup stub update_macro + +# OpenSTA +stub source_ +stub write_sdc +stub current_instance +stub set_hierarchy_separator +stub check_path_divider +stub set_units +stub check_unit +stub unit_prefix_scale +stub check_unit_scale +stub all_clocks +stub all_inputs +stub all_outputs +stub all_registers +stub current_design +stub filter_objs +stub check_nocase_flag +stub find_liberty_libraries_matching +stub create_clock +stub delete_clock +stub create_generated_clock +stub delete_generated_clock +stub remove_gclk_cmd +stub group_path +stub check_exception_pins +stub set_clock_gating_check +stub set_clock_gating_check1 +stub set_clock_groups +stub unset_clock_groups +stub unset_clk_groups_cmd +stub set_clock_latency +stub unset_clock_latency +stub unset_clk_latency_cmd +stub set_sense +stub set_clock_sense +stub set_clock_sense_cmd1 +stub set_clock_transition +stub unset_clock_transition +stub set_clock_uncertainty +stub unset_clock_uncertainty +stub unset_clk_uncertainty_cmd +stub set_data_check +stub unset_data_check +stub unset_data_checks_cmd +stub set_disable_timing +stub set_disable_timing_instance +stub parse_disable_inst_ports +stub port_members +stub set_disable_timing_cell +stub parse_disable_cell_ports +stub unset_disable_timing +stub unset_disable_cmd +stub unset_disable_timing_cell +stub unset_disable_timing_instance +stub set_false_path +stub set_ideal_latency +stub set_ideal_network +stub set_ideal_transition +stub set_input_delay +stub set_port_delay +stub unset_input_delay +stub set_max_delay +stub set_path_delay +stub set_max_time_borrow +stub set_min_delay +stub set_min_pulse_width +stub set_multicycle_path +stub unset_path_exceptions +stub unset_path_exceptions_cmd +stub set_output_delay +stub unset_output_delay +stub unset_port_delay +stub set_propagated_clock +stub unset_propagated_clock +stub set_case_analysis +stub unset_case_analysis +stub set_drive +stub set_driving_cell +stub port_direction_any_output +stub set_fanout_load +stub set_input_transition +stub set_load +stub set_logic_dc +stub set_logic_value +stub set_logic_one +stub set_logic_zero +stub set_max_area +stub set_max_capacitance +stub set_capacitance_limit +stub set_max_fanout +stub set_fanout_limit +stub set_max_transition +stub set_port_fanout_number +stub set_resistance +stub set_timing_derate +stub unset_timing_derate +stub parse_from_arg +stub parse_thrus_arg +stub parse_to_arg +stub parse_to_arg1 +stub delete_from_thrus_to +stub parse_comment_key +stub set_min_capacitance +stub set_operating_conditions +stub parse_op_cond +stub parse_op_cond_analysis_type +stub set_wire_load_min_block_size +stub set_wire_load_mode +stub set_wire_load_model +stub set_wire_load_selection_group +stub set_voltage +stub create_voltage_area +stub set_level_shifter_strategy +stub set_level_shifter_threshold +stub set_max_dynamic_power +stub set_max_leakage_power +stub define_corners +stub set_pvt +stub set_pvt_min_max +stub default_operating_conditions +stub cell_regexp +stub cell_regexp_hsc +stub port_regexp +stub port_regexp_hsc \ No newline at end of file From dc48ceadd9510bbb7b628901b1badf41400f264c Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 27 May 2025 18:00:30 +0200 Subject: [PATCH 23/45] sdc: collect strictly matching objects --- passes/cmds/sdc.cc | 245 +++++++++++++++++++++++++----------------- tests/various/sdc.sdc | 2 +- 2 files changed, 146 insertions(+), 101 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 0343c6b09..4d7347f54 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -9,6 +9,117 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN + +struct SdcObjects { + std::vector design_ports; + std::vector> design_cells; + std::vector> design_pins; + std::vector> design_nets; + + pool constrained_ports; + pool> constrained_cells; + pool> constrained_pins; + pool> constrained_nets; + + void sniff_module(std::list& hierarchy, Module* mod) { + std::string prefix; + for (auto mod_name : hierarchy) { + if (prefix.length()) + prefix += "/"; // TODO seperator? + prefix += mod_name; + } + + for (auto* wire : mod->wires()) { + std::string name = wire->name.str(); + log_assert(name.length()); + // TODO: really skip internal wires? + if (name[0] == '$') + continue; + name = name.substr(1); + std::string path = prefix; + if (path.length()) + path += "/"; + path += name; + design_nets.push_back(std::make_pair(path, wire)); + } + + for (auto* cell : mod->cells()) { + std::string name = cell->name.str(); + // TODO: really skip internal cells? + if (name[0] == '$') + continue; + name = name.substr(1); + std::string path = prefix; + if (path.length()) + path += "/"; + path += name; + design_cells.push_back(std::make_pair(path, cell)); + for (auto pin : cell->connections()) { + std::string pin_name = path + "/" + pin.first.str().substr(1); + design_pins.push_back(std::make_pair(pin_name, cell)); + } + if (auto sub_mod = mod->design->module(cell->type)) { + hierarchy.push_back(name); + sniff_module(hierarchy, sub_mod); + hierarchy.pop_back(); + } + } + } + SdcObjects(Design* design) { + Module* top = design->top_module(); + if (!top) + log_error("Top module couldn't be determined. Check 'top' attribute usage"); + for (auto port : top->ports) { + design_ports.push_back(port.str().substr(1)); + } + std::list hierarchy{}; + sniff_module(hierarchy, top); + } + ~SdcObjects() = default; + void dump() { + log("Design ports:\n"); + for (auto name : design_ports) { + log("\t%s\n", name.c_str()); + } + log("Design cells:\n"); + for (auto [name, cell] : design_cells) { + (void)cell; + log("\t%s\n", name.c_str()); + } + log("Design pins:\n"); + for (auto [name, pin] : design_pins) { + (void)pin; + log("\t%s\n", name.c_str()); + } + log("Design nets:\n"); + for (auto [name, net] : design_nets) { + (void)net; + log("\t%s\n", name.c_str()); + } + log("\n"); + log("Constrained ports:\n"); + for (auto name : constrained_ports) { + log("\t%s\n", name.c_str()); + } + log("Constrained cells:\n"); + for (auto [name, cell] : constrained_cells) { + (void)cell; + log("\t%s\n", name.c_str()); + } + log("Constrained pins:\n"); + for (auto [name, pin] : constrained_pins) { + (void)pin; + log("\t%s\n", name.c_str()); + } + log("Constrained nets:\n"); + for (auto [name, net] : constrained_nets) { + (void)net; + log("\t%s\n", name.c_str()); + } + log("\n"); + } +}; + template static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { std::string expected = std::string("-") + flag_name; @@ -23,9 +134,10 @@ static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { // TODO vectors // TODO cell arrays? -static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { (void)interp; + auto* objects = (SdcObjects*)data; // When this flag is present, the search for the pattern is made in all positions in the hierarchy. bool hierarchical_flag = false; bool regexp_flag = false; @@ -53,9 +165,16 @@ static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* c for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } - log("get_pins patterns:\n"); for (auto pat : patterns) { - log("\t%s\n", pat.c_str()); + bool found = false; + for (auto [name, pin] : objects->design_pins) { + if (name == pat) { + found = true; + objects->constrained_pins.insert(std::make_pair(name, pin)); + } + } + if (!found) + log_warning("No matches in design for pattern %s\n", pat.c_str()); } (void)hierarchical_flag; (void)regexp_flag; @@ -68,9 +187,10 @@ static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* c return TCL_OK; } -static int sdc_get_ports_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { (void)interp; + auto* objects = (SdcObjects*)data; bool regexp_flag = false; bool nocase_flag = false; std::vector patterns; @@ -84,20 +204,29 @@ static int sdc_get_ports_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } - log("get_ports patterns:\n"); for (auto pat : patterns) { - log("\t%s\n", pat.c_str()); + bool found = false; + for (auto name : objects->design_ports) { + if (name == pat) { + found = true; + objects->constrained_ports.insert(name); + } + } + if (!found) + log_warning("No matches in design for pattern %s\n", pat.c_str()); } (void)regexp_flag; (void)nocase_flag; return TCL_OK; } + class SDCInterpreter { private: Tcl_Interp* interp = nullptr; public: + std::unique_ptr objects; ~SDCInterpreter() { if (interp) Tcl_DeleteInterp(interp); @@ -106,105 +235,21 @@ public: static SDCInterpreter instance; return instance; } - Tcl_Interp* fresh_interp() { + Tcl_Interp* fresh_interp(Design* design) { if (interp) Tcl_DeleteInterp(interp); + interp = Tcl_CreateInterp(); if (Tcl_Init(interp)!=TCL_OK) - log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); - Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); - Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, NULL, NULL); + log_error("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); + + objects = std::make_unique(design); + Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); + Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); return interp; } }; -struct SdcObjects { - std::vector ports; - std::vector> cells; - std::vector> pins; - std::vector> nets; - - void sniff_module(std::list& hierarchy, Module* mod) { - log_debug("sniffing module %s\n", mod->name.c_str()); - - std::string prefix; - for (auto mod_name : hierarchy) { - if (prefix.length()) - prefix += "/"; // TODO seperator? - prefix += mod_name; - } - - for (auto* wire : mod->wires()) { - std::string name = wire->name.str(); - log_assert(name.length()); - // TODO: really skip internal wires? - if (name[0] == '$') - continue; - name = name.substr(1); - std::string path = prefix; - if (path.length()) - path += "/"; - path += name; - nets.push_back(std::make_pair(path, wire)); - } - - for (auto* cell : mod->cells()) { - std::string name = cell->name.str(); - // TODO: really skip internal cells? - if (name[0] == '$') - continue; - name = name.substr(1); - std::string path = prefix; - if (path.length()) - path += "/"; - path += name; - cells.push_back(std::make_pair(path, cell)); - for (auto pin : cell->connections()) { - std::string pin_name = path + "/" + pin.first.str().substr(1); - pins.push_back(std::make_pair(pin_name, cell)); - } - if (auto sub_mod = mod->design->module(cell->type)) { - hierarchy.push_back(name); - sniff_module(hierarchy, sub_mod); - hierarchy.pop_back(); - } - } - } - SdcObjects(Design* design) { - Module* top = design->top_module(); - if (!top) - log_error("Top module couldn't be determined. Check 'top' attribute usage"); - for (auto port : top->ports) { - ports.push_back(port.str().substr(1)); - } - std::list hierarchy{}; - sniff_module(hierarchy, top); - } - void dump() { - log("Dumping detected design objects visible to SDC constraints\n"); - log("Ports:\n"); - for (auto name : ports) { - log("\t%s\n", name.c_str()); - } - log("Cells:\n"); - for (auto [name, cell] : cells) { - (void)cell; - log("\t%s\n", name.c_str()); - } - log("Pins:\n"); - for (auto [name, pin] : pins) { - (void)pin; - log("\t%s\n", name.c_str()); - } - log("Nets:\n"); - for (auto [name, net] : nets) { - (void)net; - log("\t%s\n", name.c_str()); - } - log("\n"); - } -}; - // Also see TclPass struct SdcPass : public Pass { // TODO help @@ -213,16 +258,16 @@ struct SdcPass : public Pass { if (args.size() < 2) log_cmd_error("Missing SDC file.\n"); // TODO optional extra stub file - Tcl_Interp *interp = SDCInterpreter::get().fresh_interp(); + SDCInterpreter& sdc = SDCInterpreter::get(); + Tcl_Interp *interp = sdc.fresh_interp(design); Tcl_Preserve(interp); std::string stub_path = "+/sdc/stubs.sdc"; rewrite_filename(stub_path); - SdcObjects objects(design); - objects.dump(); if (Tcl_EvalFile(interp, stub_path.c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error in stub file: %s\n", Tcl_GetStringResult(interp)); if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); + sdc.objects->dump(); Tcl_Release(interp); } } SdcPass; diff --git a/tests/various/sdc.sdc b/tests/various/sdc.sdc index 69efcb40a..2143b34d7 100644 --- a/tests/various/sdc.sdc +++ b/tests/various/sdc.sdc @@ -1,5 +1,5 @@ puts "SDC constraints file says hello from arbitrary Tcl execution" set_false_path -from [get_pins s1/sa] -to [get_pins s1/sb] puts "This should print something:" -puts [get_ports {slink_clk_o slink_*_o}] +puts [get_ports a b] puts "Did it?" \ No newline at end of file From 834125a0763be101cac5bcd3242f89ab09328055 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 27 May 2025 18:29:59 +0200 Subject: [PATCH 24/45] sdc: return resolved patterns --- passes/cmds/sdc.cc | 62 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 4d7347f54..dcbd60ce5 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -4,6 +4,7 @@ #include "kernel/log.h" #include #include +#include USING_YOSYS_NAMESPACE @@ -77,6 +78,14 @@ struct SdcObjects { } ~SdcObjects() = default; void dump() { + std::sort(design_ports.begin(), design_ports.end()); + std::sort(design_cells.begin(), design_cells.end()); + std::sort(design_pins.begin(), design_pins.end()); + std::sort(design_nets.begin(), design_nets.end()); + constrained_ports.sort(); + constrained_cells.sort(); + constrained_pins.sort(); + constrained_nets.sort(); log("Design ports:\n"); for (auto name : design_ports) { log("\t%s\n", name.c_str()); @@ -130,9 +139,27 @@ static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { return false; } -// TODO return values like json_to_tcl on result.json? // TODO vectors // TODO cell arrays? +struct MatchConfig { + enum MatchMode { + WILDCARD, + REGEX, + } match; + bool match_case; + enum HierMode { + FLAT, + TREE, + } hier; + MatchConfig(bool regexp_flag, bool nocase_flag, bool hierarchical_flag) : + match(regexp_flag ? REGEX : WILDCARD), + match_case(!nocase_flag), + hier(hierarchical_flag ? FLAT : TREE) { } +}; + +static bool matches(std::string name, const std::string& pat, const MatchConfig& config) { + return name == pat; +} static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { @@ -165,25 +192,36 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } + + MatchConfig config(regexp_flag, nocase_flag, hierarchical_flag); + std::vector> resolved; for (auto pat : patterns) { bool found = false; for (auto [name, pin] : objects->design_pins) { - if (name == pat) { + if (matches(name, pat, config)) { found = true; - objects->constrained_pins.insert(std::make_pair(name, pin)); + resolved.push_back(std::make_pair(name, pin)); } } if (!found) - log_warning("No matches in design for pattern %s\n", pat.c_str()); + log_warning("No matches in design for pin %s\n", pat.c_str()); + } + Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); + for (auto obj : resolved) { + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.first.c_str(), obj.first.size())); + objects->constrained_pins.insert(obj); } (void)hierarchical_flag; (void)regexp_flag; (void)nocase_flag; (void)of_objects; + if (separator != "/") { - Tcl_SetObjResult(interp, Tcl_NewStringObj("Only '/' accepted as separator", -1)); + Tcl_SetResult(interp, (char *)"Only '/' accepted as separator", TCL_STATIC); return TCL_ERROR; } + + Tcl_SetObjResult(interp, result); return TCL_OK; } @@ -204,19 +242,27 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } + MatchConfig config(regexp_flag, nocase_flag, false); + std::vector resolved; for (auto pat : patterns) { bool found = false; for (auto name : objects->design_ports) { - if (name == pat) { + if (matches(name, pat, config)) { found = true; - objects->constrained_ports.insert(name); + resolved.push_back(name); } } if (!found) - log_warning("No matches in design for pattern %s\n", pat.c_str()); + log_warning("No matches in design for port %s\n", pat.c_str()); + } + Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); + for (auto obj : resolved) { + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.c_str(), obj.size())); + objects->constrained_ports.insert(obj); } (void)regexp_flag; (void)nocase_flag; + Tcl_SetObjResult(interp, result); return TCL_OK; } From 420a083d9b0dbd46e1dd7602a54ee88d8907b9e8 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 10 Jun 2025 16:34:04 +0200 Subject: [PATCH 25/45] sdc: simple mode, remove per-tool stubs --- passes/cmds/sdc.cc | 180 +++++++++++++++++++++++++------ passes/cmds/stubs.sdc | 241 +++--------------------------------------- 2 files changed, 160 insertions(+), 261 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index dcbd60ce5..b01cbe025 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -5,6 +5,7 @@ #include #include #include +#include USING_YOSYS_NAMESPACE @@ -12,6 +13,11 @@ PRIVATE_NAMESPACE_BEGIN struct SdcObjects { + enum CollectMode { + SimpleGetter, + FullGetter, + FullConstraint, + } collect_mode; std::vector design_ports; std::vector> design_cells; std::vector> design_pins; @@ -86,26 +92,26 @@ struct SdcObjects { constrained_cells.sort(); constrained_pins.sort(); constrained_nets.sort(); - log("Design ports:\n"); - for (auto name : design_ports) { - log("\t%s\n", name.c_str()); - } - log("Design cells:\n"); - for (auto [name, cell] : design_cells) { - (void)cell; - log("\t%s\n", name.c_str()); - } - log("Design pins:\n"); - for (auto [name, pin] : design_pins) { - (void)pin; - log("\t%s\n", name.c_str()); - } - log("Design nets:\n"); - for (auto [name, net] : design_nets) { - (void)net; - log("\t%s\n", name.c_str()); - } - log("\n"); + // log("Design ports:\n"); + // for (auto name : design_ports) { + // log("\t%s\n", name.c_str()); + // } + // log("Design cells:\n"); + // for (auto [name, cell] : design_cells) { + // (void)cell; + // log("\t%s\n", name.c_str()); + // } + // log("Design pins:\n"); + // for (auto [name, pin] : design_pins) { + // (void)pin; + // log("\t%s\n", name.c_str()); + // } + // log("Design nets:\n"); + // for (auto [name, net] : design_nets) { + // (void)net; + // log("\t%s\n", name.c_str()); + // } + // log("\n"); log("Constrained ports:\n"); for (auto name : constrained_ports) { log("\t%s\n", name.c_str()); @@ -158,12 +164,13 @@ struct MatchConfig { }; static bool matches(std::string name, const std::string& pat, const MatchConfig& config) { + (void)config; + // TODO implement full mode return name == pat; } static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { - (void)interp; auto* objects = (SdcObjects*)data; // When this flag is present, the search for the pattern is made in all positions in the hierarchy. bool hierarchical_flag = false; @@ -192,6 +199,14 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } + if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) { + if (regexp_flag || hierarchical_flag || nocase_flag || separator != "/" || of_objects) { + log_error("get_pins got unexpected flags in simple mode\n"); + } + if (patterns.size() != 1) { + log_error("get_pins got unexpected number of patterns in simple mode\n"); + } + } MatchConfig config(regexp_flag, nocase_flag, hierarchical_flag); std::vector> resolved; @@ -209,12 +224,9 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); for (auto obj : resolved) { Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.first.c_str(), obj.first.size())); - objects->constrained_pins.insert(obj); + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + objects->constrained_pins.insert(obj); } - (void)hierarchical_flag; - (void)regexp_flag; - (void)nocase_flag; - (void)of_objects; if (separator != "/") { Tcl_SetResult(interp, (char *)"Only '/' accepted as separator", TCL_STATIC); @@ -227,7 +239,6 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { - (void)interp; auto* objects = (SdcObjects*)data; bool regexp_flag = false; bool nocase_flag = false; @@ -242,6 +253,14 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } + if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) { + if (regexp_flag || nocase_flag) { + log_error("get_ports got unexpected flags in simple mode\n"); + } + if (patterns.size() != 1) { + log_error("get_ports got unexpected number of patterns in simple mode\n"); + } + } MatchConfig config(regexp_flag, nocase_flag, false); std::vector resolved; for (auto pat : patterns) { @@ -258,14 +277,89 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); for (auto obj : resolved) { Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.c_str(), obj.size())); - objects->constrained_ports.insert(obj); + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + objects->constrained_ports.insert(obj); } - (void)regexp_flag; - (void)nocase_flag; + Tcl_SetObjResult(interp, result); return TCL_OK; } +std::optional> split_at(std::string s) +{ + size_t pos = s.find('@'); + if (pos == std::string::npos) + return std::nullopt; + return std::make_tuple(s.substr(0, pos), s.substr(pos + 1)); +} + +// Whether string or list of strings, apply op to each string +void apply_args(Tcl_Interp *interp, std::function op, Tcl_Obj* obj) +{ + int length; + Tcl_Obj **value_list; + if (Tcl_ListObjGetElements(interp, obj, &length, &value_list) == TCL_OK) { + for (int i = 0; i < length; i++) { + op(Tcl_GetString(value_list[i])); + } + } else { + op(Tcl_GetString(obj)); + } +} + +static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +{ + log("ys_track_typed_key_cmd\n"); + auto* objects = (SdcObjects*)data; + if (objc != 5) + log_error("ys_track_typed_key: Unexpected number of arguments: %d (expected 5)\n", objc); + + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + return TCL_OK; + + std::string key_name = Tcl_GetString(objv[1]); + Tcl_Obj* key_value = objv[2]; + std::string key_expect_type = Tcl_GetString(objv[3]); + std::string proc_name = Tcl_GetString(objv[4]); + + auto track_typed = [key_expect_type, objects, proc_name, key_name](const char* str) -> void { + auto split = split_at(str); + if (!split) + log_error("%s: key %s should be a typed SDC object, but is something weird: %s\n", + proc_name.c_str(), key_name.c_str(), str); + + if (key_expect_type == "pin") { + log("PIN! %s\n", str); + bool found = false; + for (auto [name, pin] : objects->design_pins) { + if (name + "/" + pin->name.str() == str) { + found = true; + objects->constrained_pins.insert(std::make_pair(name, pin)); + break; // resolved, expected unique + } + } + if (!found) + log_error("%s: pin %s not found\n", proc_name.c_str(), str); + } else if (key_expect_type == "port") { + bool found = false; + for (auto name : objects->design_ports) { + if (name == str) { + found = true; + objects->constrained_ports.insert(name); + break; // resolved, expected unique + } + } + if (!found) + log_error("%s: port %s not found\n", proc_name.c_str(), str); + } else { + // TODO + log_warning("%s: unsupported type %s\n", proc_name.c_str(), key_expect_type.c_str()); + } + }; + apply_args(interp, track_typed, key_value); + return TCL_OK; +} + class SDCInterpreter { @@ -290,8 +384,10 @@ public: log_error("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); objects = std::make_unique(design); + objects->collect_mode = SdcObjects::CollectMode::FullConstraint; Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); + Tcl_CreateObjCommand(interp, "ys_track_typed_key", ys_track_typed_key_cmd, (ClientData) objects.get(), NULL); return interp; } }; @@ -301,17 +397,33 @@ struct SdcPass : public Pass { // TODO help SdcPass() : Pass("sdc", "sniff at some SDC") { } void execute(std::vector args, RTLIL::Design *design) override { - if (args.size() < 2) - log_cmd_error("Missing SDC file.\n"); + // if (args.size() < 2) + // log_cmd_error("Missing SDC file.\n"); // TODO optional extra stub file + size_t argidx; + std::vector opensta_stubs_paths; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-stubs" && argidx+1 < args.size()) { + opensta_stubs_paths.push_back(args[++argidx]); + continue; + } + break; + } + if (argidx >= args.size()) + log_cmd_error("Missing SDC file.\n"); + + std::string sdc_path = args[argidx]; SDCInterpreter& sdc = SDCInterpreter::get(); Tcl_Interp *interp = sdc.fresh_interp(design); Tcl_Preserve(interp); std::string stub_path = "+/sdc/stubs.sdc"; rewrite_filename(stub_path); if (Tcl_EvalFile(interp, stub_path.c_str()) != TCL_OK) - log_cmd_error("SDC interpreter returned an error in stub file: %s\n", Tcl_GetStringResult(interp)); - if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) + log_cmd_error("SDC interpreter returned an error in stub preamble file: %s\n", Tcl_GetStringResult(interp)); + for (auto path : opensta_stubs_paths) + if (Tcl_EvalFile(interp, path.c_str()) != TCL_OK) + log_cmd_error("SDC interpreter returned an error in OpenSTA stub file %s: %s\n", path.c_str(), Tcl_GetStringResult(interp)); + if (Tcl_EvalFile(interp, sdc_path.c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); sdc.objects->dump(); Tcl_Release(interp); diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index ea1600139..3466981c9 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -1,234 +1,21 @@ # with Tcl's eager evaluation, we will still eval args if they're unused by a stub proc stub {function_name} { - proc $function_name {args} {} + proc $function_name {args} "puts \"stubbed $function_name\"" } -# OpenROAD -proc get_name {thing} { - return thing -} -proc get_full_name {thing} { - return thing +proc is_suppressed {args} { + return 0 +} + +proc create_clock {args} { + return "CLOCK@" +} +proc get_clocks {args} { + return "CLOCK@" } -# Vivado UG903 -stub add_cells_to_pblock -stub add_to_power_rail -stub all_clocks -stub all_cpus -stub all_dsps -stub all_fanin -stub all_fanout -stub all_ffs -stub all_hsios -stub all_inputs -stub all_latches -stub all_outputs -stub all_rams -stub all_registers -stub connect_debug_port -stub create_clock -stub create_debug_core -stub create_debug_port -stub create_generated_clock -stub create_macro -stub create_pblock -stub create_power_rail -stub create_property -stub create_waiver stub current_design -stub current_instance -stub delete_macros -stub delete_pblock -stub delete_power_rails -stub endgroup -stub get_bel_pins -stub get_bels -stub get_clocks -stub get_debug_cores -stub get_debug_ports -stub get_generated_clocks -stub get_hierarchy_separator -stub get_iobanks -stub get_macros -stub get_nodes -stub get_package_pins -stub get_path_groups -stub get_pblocks -stub get_pips -stub get_pkgpin_bytegroups -stub get_pkgpin_nibbles -stub get_power_rails -stub get_property -stub get_site_pins -stub get_site_pips -stub get_sites -stub get_slrs -stub get_speed_models -stub get_tiles -stub get_timing_arcs -stub group_path -stub make_diff_pair_ports -stub remove_cells_from_pblock -stub remove_from_power_rail -stub reset_operating_conditions -stub reset_switching_activity -stub resize_pblock -stub set_bus_skew -stub set_case_analysis -stub set_clock_groups -stub set_clock_latency -stub set_clock_sense -stub set_clock_uncertainty -stub set_data_check -stub set_disable_timing -stub set_external_delay -stub set_false_path -stub set_hierarchy_separator -stub set_input_delay -stub set_input_jitter -stub set_load -stub set_logic_dc -stub set_logic_one -stub set_logic_unconnected -stub set_logic_zero -stub set_max_delay -stub set_max_time_borrow -stub set_min_delay -stub set_multicycle_path -stub set_operating_conditions -stub set_output_delay -stub set_package_pin_val -stub set_power_opt -stub set_propagated_clock -stub set_property -stub set_switching_activity -stub set_system_jitter -stub set_units -stub startgroup -stub update_macro - -# OpenSTA -stub source_ -stub write_sdc -stub current_instance -stub set_hierarchy_separator -stub check_path_divider -stub set_units -stub check_unit -stub unit_prefix_scale -stub check_unit_scale -stub all_clocks -stub all_inputs -stub all_outputs -stub all_registers -stub current_design -stub filter_objs -stub check_nocase_flag -stub find_liberty_libraries_matching -stub create_clock -stub delete_clock -stub create_generated_clock -stub delete_generated_clock -stub remove_gclk_cmd -stub group_path -stub check_exception_pins -stub set_clock_gating_check -stub set_clock_gating_check1 -stub set_clock_groups -stub unset_clock_groups -stub unset_clk_groups_cmd -stub set_clock_latency -stub unset_clock_latency -stub unset_clk_latency_cmd -stub set_sense -stub set_clock_sense -stub set_clock_sense_cmd1 -stub set_clock_transition -stub unset_clock_transition -stub set_clock_uncertainty -stub unset_clock_uncertainty -stub unset_clk_uncertainty_cmd -stub set_data_check -stub unset_data_check -stub unset_data_checks_cmd -stub set_disable_timing -stub set_disable_timing_instance -stub parse_disable_inst_ports -stub port_members -stub set_disable_timing_cell -stub parse_disable_cell_ports -stub unset_disable_timing -stub unset_disable_cmd -stub unset_disable_timing_cell -stub unset_disable_timing_instance -stub set_false_path -stub set_ideal_latency -stub set_ideal_network -stub set_ideal_transition -stub set_input_delay -stub set_port_delay -stub unset_input_delay -stub set_max_delay -stub set_path_delay -stub set_max_time_borrow -stub set_min_delay -stub set_min_pulse_width -stub set_multicycle_path -stub unset_path_exceptions -stub unset_path_exceptions_cmd -stub set_output_delay -stub unset_output_delay -stub unset_port_delay -stub set_propagated_clock -stub unset_propagated_clock -stub set_case_analysis -stub unset_case_analysis -stub set_drive -stub set_driving_cell -stub port_direction_any_output -stub set_fanout_load -stub set_input_transition -stub set_load -stub set_logic_dc -stub set_logic_value -stub set_logic_one -stub set_logic_zero -stub set_max_area -stub set_max_capacitance -stub set_capacitance_limit -stub set_max_fanout -stub set_fanout_limit -stub set_max_transition -stub set_port_fanout_number -stub set_resistance -stub set_timing_derate -stub unset_timing_derate -stub parse_from_arg -stub parse_thrus_arg -stub parse_to_arg -stub parse_to_arg1 -stub delete_from_thrus_to -stub parse_comment_key -stub set_min_capacitance -stub set_operating_conditions -stub parse_op_cond -stub parse_op_cond_analysis_type -stub set_wire_load_min_block_size -stub set_wire_load_mode -stub set_wire_load_model -stub set_wire_load_selection_group -stub set_voltage -stub create_voltage_area -stub set_level_shifter_strategy -stub set_level_shifter_threshold -stub set_max_dynamic_power -stub set_max_leakage_power -stub define_corners -stub set_pvt -stub set_pvt_min_max -stub default_operating_conditions -stub cell_regexp -stub cell_regexp_hsc -stub port_regexp -stub port_regexp_hsc \ No newline at end of file +#stub ys_track_typed_key +stub ys_track_untyped_key +stub ys_err_key +stub ys_err_flag From 21c68e0022680a6f6268368d21c1eeb0604e4112 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 30 Jul 2025 18:51:14 +0200 Subject: [PATCH 26/45] sdc: unknown handler experiment --- passes/cmds/sdc.cc | 5 +++-- passes/cmds/stubs.sdc | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index b01cbe025..76b9b95ff 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -384,7 +384,7 @@ public: log_error("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); objects = std::make_unique(design); - objects->collect_mode = SdcObjects::CollectMode::FullConstraint; + objects->collect_mode = SdcObjects::CollectMode::SimpleGetter; Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "ys_track_typed_key", ys_track_typed_key_cmd, (ClientData) objects.get(), NULL); @@ -396,7 +396,8 @@ public: struct SdcPass : public Pass { // TODO help SdcPass() : Pass("sdc", "sniff at some SDC") { } - void execute(std::vector args, RTLIL::Design *design) override { +void execute(std::vector args, RTLIL::Design *design) override { + log_header(design, "Executing SDC pass.\n"); // if (args.size() < 2) // log_cmd_error("Missing SDC file.\n"); // TODO optional extra stub file diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index 3466981c9..a7845b02d 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -7,15 +7,22 @@ proc is_suppressed {args} { return 0 } -proc create_clock {args} { - return "CLOCK@" -} -proc get_clocks {args} { - return "CLOCK@" -} - -stub current_design +# stub current_design #stub ys_track_typed_key stub ys_track_untyped_key stub ys_err_key stub ys_err_flag + +proc unknown {args} { + global sdc_call_index + global sdc_calls + if {![info exists sdc_call_index]} { + set sdc_call_index 0 + } + if {![info exists sdc_calls]} { + set sdc_calls {} + } + incr sdc_call_index + lappend sdc_calls $args + return $sdc_call_index +} \ No newline at end of file From 24a6412ea801b9f399bbb9dc94d89b9a5a309ab6 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 31 Jul 2025 15:31:36 +0200 Subject: [PATCH 27/45] sdc: bit selections --- passes/cmds/sdc.cc | 176 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 142 insertions(+), 34 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 76b9b95ff..c5dbbc7f5 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -18,15 +18,77 @@ struct SdcObjects { FullGetter, FullConstraint, } collect_mode; + struct BitSelection { + bool all = false; + std::vector bits = {}; + void set_all() { + bits.clear(); + all = true; + } + void clear() { + bits.clear(); + all = false; + } + void set(size_t idx) { + if (all) + return; + if (idx >= bits.size()) + bits.resize(idx + 1); + bits[idx] = true; + } + void merge(const BitSelection& other) { + if (all) + return; + if (other.all) { + set_all(); + return; + } + if (other.bits.size() > bits.size()) + bits.resize(other.bits.size()); + for (size_t other_idx = 0; other_idx < other.bits.size(); other_idx++) { + bool other_bit = other.bits[other_idx]; + if (other_bit) + set(other_idx); + } + } + void dump() { + if (!all) { + for (size_t i = 0; i < bits.size(); i++) + if (bits[i]) + log("\t\t [%zu]\n", i); + } else { + log("\t\t FULL\n"); + } + } + bool is_set(size_t idx) { + if (all) + return true; + if (idx >= bits.size()) + return false; + return bits[idx]; + } + // TODO actually use this + void compress(size_t size) { + if (bits.size() < size) + return; + for (size_t i = 0; i < size; i++) + if (!bits[i]) + return; + bits.clear(); + bits.shrink_to_fit(); + all = true; + } + }; + using CellPin = std::pair; std::vector design_ports; std::vector> design_cells; - std::vector> design_pins; + std::vector> design_pins; std::vector> design_nets; - pool constrained_ports; + dict constrained_ports; pool> constrained_cells; - pool> constrained_pins; - pool> constrained_nets; + dict, BitSelection> constrained_pins; + dict, BitSelection> constrained_nets; void sniff_module(std::list& hierarchy, Module* mod) { std::string prefix; @@ -62,8 +124,9 @@ struct SdcObjects { path += name; design_cells.push_back(std::make_pair(path, cell)); for (auto pin : cell->connections()) { - std::string pin_name = path + "/" + pin.first.str().substr(1); - design_pins.push_back(std::make_pair(pin_name, cell)); + IdString pin_name = pin.first; + std::string pin_name_sdc = path + "/" + pin.first.str().substr(1); + design_pins.push_back(std::make_pair(pin_name_sdc, std::make_pair(cell, pin_name))); } if (auto sub_mod = mod->design->module(cell->type)) { hierarchy.push_back(name); @@ -113,23 +176,28 @@ struct SdcObjects { // } // log("\n"); log("Constrained ports:\n"); - for (auto name : constrained_ports) { + for (auto [name, bits] : constrained_ports) { log("\t%s\n", name.c_str()); + bits.dump(); } log("Constrained cells:\n"); - for (auto [name, cell] : constrained_cells) { + for (auto& [name, cell] : constrained_cells) { (void)cell; log("\t%s\n", name.c_str()); } log("Constrained pins:\n"); - for (auto [name, pin] : constrained_pins) { + for (auto& [ref, bits] : constrained_pins) { + auto [name, pin] = ref; (void)pin; log("\t%s\n", name.c_str()); + bits.dump(); } log("Constrained nets:\n"); - for (auto [name, net] : constrained_nets) { + for (auto& [ref, bits] : constrained_nets) { + auto [name, net] = ref; (void)net; log("\t%s\n", name.c_str()); + bits.dump(); } log("\n"); } @@ -163,10 +231,36 @@ struct MatchConfig { hier(hierarchical_flag ? FLAT : TREE) { } }; -static bool matches(std::string name, const std::string& pat, const MatchConfig& config) { +static std::pair matches(std::string name, const std::string& pat, const MatchConfig& config) { (void)config; - // TODO implement full mode - return name == pat; + bool got_bit_index = false;; + int bit_idx; + std::string pat_base = pat; + size_t pos = pat.rfind('['); + if (pos != std::string::npos) { + got_bit_index = true; + pat_base = pat.substr(0, pos); + std::string bit_selector = pat.substr(pos + 1, pat.rfind(']') - pos - 1); + for (auto c : bit_selector) + if (!std::isdigit(c)) + log_error("Unsupported bit selector %s in SDC pattern %s\n", + bit_selector.c_str(), pat.c_str()); + bit_idx = std::stoi(bit_selector); + + } + SdcObjects::BitSelection bits = {}; + if (name == pat_base) { + if (got_bit_index) { + bits.set(bit_idx); + return std::make_pair(true, bits); + } else { + bits.set_all(); + return std::make_pair(true, bits); + + } + } else { + return std::make_pair(false, bits); + } } static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) @@ -177,7 +271,7 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O bool regexp_flag = false; bool nocase_flag = false; std::string separator = "/"; - Tcl_Obj* of_objects; + Tcl_Obj* of_objects = nullptr; std::vector patterns; int i = 1; for (; i < objc; i++) { @@ -209,30 +303,37 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O } MatchConfig config(regexp_flag, nocase_flag, hierarchical_flag); - std::vector> resolved; + std::vector> resolved; for (auto pat : patterns) { bool found = false; for (auto [name, pin] : objects->design_pins) { - if (matches(name, pat, config)) { + auto [does_match, matching_bits] = matches(name, pat, config); + if (does_match) { found = true; - resolved.push_back(std::make_pair(name, pin)); + resolved.push_back(std::make_tuple(name, pin, matching_bits)); } } if (!found) log_warning("No matches in design for pin %s\n", pat.c_str()); } - Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); - for (auto obj : resolved) { - Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.first.c_str(), obj.first.size())); - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) - objects->constrained_pins.insert(obj); - } if (separator != "/") { Tcl_SetResult(interp, (char *)"Only '/' accepted as separator", TCL_STATIC); return TCL_ERROR; } + Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); + for (auto [name, pin, matching_bits] : resolved) { + // TODO change this to graph tracking if desired + size_t width = (size_t)pin.first->getPort(pin.second).size(); + for (size_t i = 0; i < width; i++) + if (matching_bits.is_set(i)) + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); + + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + objects->constrained_pins[std::make_pair(name, pin)].merge(matching_bits); + } + Tcl_SetObjResult(interp, result); return TCL_OK; } @@ -262,23 +363,29 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ } } MatchConfig config(regexp_flag, nocase_flag, false); - std::vector resolved; + std::vector> resolved; for (auto pat : patterns) { bool found = false; for (auto name : objects->design_ports) { - if (matches(name, pat, config)) { + auto [does_match, matching_bits] = matches(name, pat, config); + if (does_match) { found = true; - resolved.push_back(name); + resolved.push_back(std::make_tuple(name, matching_bits)); } } if (!found) log_warning("No matches in design for port %s\n", pat.c_str()); } Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); - for (auto obj : resolved) { + for (auto [obj, matching_bits] : resolved) { Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.c_str(), obj.size())); - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) - objects->constrained_ports.insert(obj); + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) { + if (objects->constrained_ports.count(obj) == 0) { + objects->constrained_ports[obj] = matching_bits; + } else { + objects->constrained_ports[obj].merge(matching_bits); + } + } } Tcl_SetObjResult(interp, result); @@ -332,11 +439,12 @@ static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, log("PIN! %s\n", str); bool found = false; for (auto [name, pin] : objects->design_pins) { - if (name + "/" + pin->name.str() == str) { - found = true; - objects->constrained_pins.insert(std::make_pair(name, pin)); - break; // resolved, expected unique - } + log_error("TODO temporarily disabled due to working on a different flow\n"); + // if (name + "/" + pin->name.str() == str) { + // found = true; + // objects->constrained_pins.insert(std::make_pair(name, pin)); + // break; // resolved, expected unique + // } } if (!found) log_error("%s: pin %s not found\n", proc_name.c_str(), str); From c6491629d87f3aa2dac76f7f2db7105ca649f93c Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 31 Jul 2025 18:33:20 +0200 Subject: [PATCH 28/45] sdc: start graph --- passes/cmds/sdc.cc | 111 +++++++++++++++++++++++++++++++++++++++--- passes/cmds/stubs.sdc | 7 ++- 2 files changed, 110 insertions(+), 8 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index c5dbbc7f5..cc95302cc 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -14,10 +14,19 @@ PRIVATE_NAMESPACE_BEGIN struct SdcObjects { enum CollectMode { + // getter-side object tracking with minimal features SimpleGetter, + // getter-side object tracking with everything FullGetter, + // constraint-side tracking FullConstraint, } collect_mode; + enum ValueMode { + // return something sensible and error on unknown + Normal, + // return a new graph node assuming unknown is overridden + Graph, + } value_mode; struct BitSelection { bool all = false; std::vector bits = {}; @@ -263,6 +272,85 @@ static std::pair matches(std::string name, const } } + +static int redirect_unknown(Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { + Tcl_Obj *newCmd = Tcl_NewStringObj("unknown", -1); + Tcl_Obj **newObjv = new Tcl_Obj*[objc+1]; + newObjv[0] = newCmd; + for (int i = 1; i < objc + 1; i++) { + newObjv[i] = objv[i - 1]; + } + int result = Tcl_EvalObjv(interp, objc, newObjv, 0); + Tcl_DecrRefCount(newCmd); + delete[] newObjv; + return result; +} + +void inspect_globals(Tcl_Interp* interp) { + auto get_var = [&](const char* name) -> const char* { + return Tcl_GetVar(interp, name, TCL_GLOBAL_ONLY); + }; + const char* idx_s = get_var("sdc_call_index"); + log_assert(idx_s); + size_t node_count = std::stoi(idx_s); + + // else + // printf("sdc_call_index: unset\n"); + // if (auto calls = get_var("sdc_calls")) + // printf("sdc_calls: %s\n", calls); + // else + // printf("sdc_calls: unset\n"); + Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY); + int listLength; + + // Get list length first + std::vector> nestedData; + if (Tcl_ListObjLength(interp, listObj, &listLength) == TCL_OK) { + for (int i = 0; i < listLength; i++) { + Tcl_Obj* subListObj; + std::vector subList; + if (Tcl_ListObjIndex(interp, listObj, i, &subListObj) != TCL_OK) { + log_error("broken list of lists\n"); + } + int subListLength; + if (Tcl_ListObjLength(interp, subListObj, &subListLength) == TCL_OK) { + // It's a valid list - extract elements + for (int j = 0; j < subListLength; j++) { + Tcl_Obj* elementObj; + if (Tcl_ListObjIndex(interp, subListObj, j, &elementObj) == TCL_OK) { + const char* elementStr = Tcl_GetString(elementObj); + subList.push_back(std::string(elementStr)); + } + } + } else { + // It's a single element, not a list + const char* elementStr = Tcl_GetString(subListObj); + subList.push_back(std::string(elementStr)); + } + nestedData.push_back(subList); + } + } + std::vector has_parent; + log_assert(nestedData.size() == node_count); + has_parent.resize(node_count); + for (size_t i = 0; i < node_count; i++) { + for (size_t j = 0; j < nestedData[i].size(); j++) { + auto arg = nestedData[i][j]; + auto pos = arg.find("YOSYS_SDC_MAGIC_NODE_"); + if (pos == std::string::npos) + continue; + std::string rest = arg.substr(pos); + for (auto c : rest) + if (!std::isdigit(c)) + log_error("weird thing %s\n", rest.c_str()); + size_t arg_node_idx = std::stoi(rest); + log("%zu %zu %s IDX %zu\n", i, j, arg.c_str(), arg_node_idx); + // log("%zu %zu %s\n", i, j, arg.c_str()); + + } + } +} + static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { auto* objects = (SdcObjects*)data; @@ -322,19 +410,26 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O return TCL_ERROR; } - Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); + Tcl_Obj *result = nullptr; for (auto [name, pin, matching_bits] : resolved) { - // TODO change this to graph tracking if desired - size_t width = (size_t)pin.first->getPort(pin.second).size(); - for (size_t i = 0; i < width; i++) - if (matching_bits.is_set(i)) - Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); + if (objects->value_mode == SdcObjects::ValueMode::Normal) { + if (!result) + result = Tcl_NewListObj(resolved.size(), nullptr); + size_t width = (size_t)pin.first->getPort(pin.second).size(); + for (size_t i = 0; i < width; i++) + if (matching_bits.is_set(i)) + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); + } if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) objects->constrained_pins[std::make_pair(name, pin)].merge(matching_bits); } - Tcl_SetObjResult(interp, result); + if (objects->value_mode == SdcObjects::ValueMode::Graph) { + return redirect_unknown(interp, objc, objv); + } + if (result) + Tcl_SetObjResult(interp, result); return TCL_OK; } @@ -493,6 +588,7 @@ public: objects = std::make_unique(design); objects->collect_mode = SdcObjects::CollectMode::SimpleGetter; + objects->value_mode = SdcObjects::ValueMode::Graph; Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "ys_track_typed_key", ys_track_typed_key_cmd, (ClientData) objects.get(), NULL); @@ -535,6 +631,7 @@ void execute(std::vector args, RTLIL::Design *design) override { if (Tcl_EvalFile(interp, sdc_path.c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); sdc.objects->dump(); + inspect_globals(interp); Tcl_Release(interp); } } SdcPass; diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index a7845b02d..467ce70c6 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -13,6 +13,7 @@ stub ys_track_untyped_key stub ys_err_key stub ys_err_flag +# TODO move to separate file and tie to graph value mode proc unknown {args} { global sdc_call_index global sdc_calls @@ -24,5 +25,9 @@ proc unknown {args} { } incr sdc_call_index lappend sdc_calls $args - return $sdc_call_index + puts "unknown $args, returning YOSYS_SDC_MAGIC_NODE_$sdc_call_index" + return "YOSYS_SDC_MAGIC_NODE_$sdc_call_index" +} +proc list {args} { + unknown "list" $args } \ No newline at end of file From ae1235210da6f4eb3995c96f8333f259357e38c0 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 1 Aug 2025 16:33:10 +0200 Subject: [PATCH 29/45] sdc: functional graph --- passes/cmds/sdc.cc | 178 ++++++++++++++++++++++++++++++------------ passes/cmds/stubs.sdc | 5 +- 2 files changed, 129 insertions(+), 54 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index cc95302cc..f0d7175c5 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -6,6 +6,7 @@ #include #include #include +#include USING_YOSYS_NAMESPACE @@ -89,7 +90,7 @@ struct SdcObjects { } }; using CellPin = std::pair; - std::vector design_ports; + std::vector> design_ports; std::vector> design_cells; std::vector> design_pins; std::vector> design_nets; @@ -149,7 +150,7 @@ struct SdcObjects { if (!top) log_error("Top module couldn't be determined. Check 'top' attribute usage"); for (auto port : top->ports) { - design_ports.push_back(port.str().substr(1)); + design_ports.push_back(std::make_pair(port.str().substr(1), top->wire(port))); } std::list hierarchy{}; sniff_module(hierarchy, top); @@ -275,36 +276,59 @@ static std::pair matches(std::string name, const static int redirect_unknown(Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { Tcl_Obj *newCmd = Tcl_NewStringObj("unknown", -1); - Tcl_Obj **newObjv = new Tcl_Obj*[objc+1]; + auto newObjc = objc + 1; + Tcl_Obj **newObjv = new Tcl_Obj*[newObjc]; newObjv[0] = newCmd; - for (int i = 1; i < objc + 1; i++) { + for (int i = 1; i < newObjc; i++) { newObjv[i] = objv[i - 1]; + log("newObjv %s\n", Tcl_GetString(newObjv[i])); } - int result = Tcl_EvalObjv(interp, objc, newObjv, 0); + int result = Tcl_EvalObjv(interp, newObjc, newObjv, 0); Tcl_DecrRefCount(newCmd); delete[] newObjv; return result; } -void inspect_globals(Tcl_Interp* interp) { - auto get_var = [&](const char* name) -> const char* { - return Tcl_GetVar(interp, name, TCL_GLOBAL_ONLY); - }; - const char* idx_s = get_var("sdc_call_index"); +struct SdcGraphNode { + using Child = std::variant; + std::vector children; + SdcGraphNode() = default; + void addChild(SdcGraphNode* child) { + children.push_back(child); + } + void addChild(std::string tcl_string) { + children.push_back(tcl_string); + } + void dump(std::ostream& os) const { + bool first = true; + for (auto child : children) { + if (first) { + first = false; + } else { + os << " "; + } + if (auto* s = std::get_if(&child)) + os << *s; + else if (SdcGraphNode*& c = *std::get_if(&child)) { + os << "["; + c->dump(os); + os << "]"; + } else { + log_assert(false); + } + } + } +}; + +std::vector> gather_nested_calls(Tcl_Interp* interp) { + const char* idx_s = Tcl_GetVar(interp, "sdc_call_index", TCL_GLOBAL_ONLY); log_assert(idx_s); size_t node_count = std::stoi(idx_s); - // else - // printf("sdc_call_index: unset\n"); - // if (auto calls = get_var("sdc_calls")) - // printf("sdc_calls: %s\n", calls); - // else - // printf("sdc_calls: unset\n"); Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY); int listLength; - // Get list length first - std::vector> nestedData; + std::vector> sdc_calls; if (Tcl_ListObjLength(interp, listObj, &listLength) == TCL_OK) { for (int i = 0; i < listLength; i++) { Tcl_Obj* subListObj; @@ -314,7 +338,7 @@ void inspect_globals(Tcl_Interp* interp) { } int subListLength; if (Tcl_ListObjLength(interp, subListObj, &subListLength) == TCL_OK) { - // It's a valid list - extract elements + // Valid list - extract elements for (int j = 0; j < subListLength; j++) { Tcl_Obj* elementObj; if (Tcl_ListObjIndex(interp, subListObj, j, &elementObj) == TCL_OK) { @@ -323,32 +347,70 @@ void inspect_globals(Tcl_Interp* interp) { } } } else { - // It's a single element, not a list + // Single element, not a list const char* elementStr = Tcl_GetString(subListObj); subList.push_back(std::string(elementStr)); } - nestedData.push_back(subList); + sdc_calls.push_back(subList); } } - std::vector has_parent; - log_assert(nestedData.size() == node_count); - has_parent.resize(node_count); + log_assert(sdc_calls.size() == node_count); + return sdc_calls; +} + +std::vector build_graph(const std::vector>& sdc_calls) { + size_t node_count = sdc_calls.size(); + std::vector graph(node_count); for (size_t i = 0; i < node_count; i++) { - for (size_t j = 0; j < nestedData[i].size(); j++) { - auto arg = nestedData[i][j]; - auto pos = arg.find("YOSYS_SDC_MAGIC_NODE_"); - if (pos == std::string::npos) - continue; - std::string rest = arg.substr(pos); - for (auto c : rest) - if (!std::isdigit(c)) - log_error("weird thing %s\n", rest.c_str()); - size_t arg_node_idx = std::stoi(rest); - log("%zu %zu %s IDX %zu\n", i, j, arg.c_str(), arg_node_idx); - // log("%zu %zu %s\n", i, j, arg.c_str()); + auto& new_node = graph[i]; + for (size_t j = 0; j < sdc_calls[i].size(); j++) { + auto arg = sdc_calls[i][j]; + const std::string prefix = "YOSYS_SDC_MAGIC_NODE_"; + auto pos = arg.find(prefix); + if (pos != std::string::npos) { + std::string rest = arg.substr(pos + prefix.length()); + for (auto c : rest) + if (!std::isdigit(c)) + log_error("weird thing %s in %s\n", rest.c_str(), arg.c_str()); + size_t arg_node_idx = std::stoi(rest); + log_assert(arg_node_idx < graph.size()); + new_node.addChild(&graph[arg_node_idx]); + } else { + new_node.addChild(arg); + } } } + return graph; +} + +std::vector node_ownership(const std::vector& graph) { + std::vector has_parent(graph.size()); + for (auto node : graph) { + for (auto child : node.children) { + if (SdcGraphNode** pp = std::get_if(&child)) { + size_t idx = std::distance(&graph.front(), (const SdcGraphNode*)*pp); + log_assert(idx < has_parent.size()); + has_parent[idx] = true; + } + } + } + return has_parent; +} + +void dump_sdc_graph(const std::vector& graph, const std::vector& has_parent) { + for (size_t i = 0; i < graph.size(); i++) { + if (!has_parent[i]) { + graph[i].dump(std::cout); + std::cout << "\n"; + } + } +} + +void inspect_globals(Tcl_Interp* interp) { + std::vector> sdc_calls = gather_nested_calls(interp); + std::vector graph = build_graph(sdc_calls); + dump_sdc_graph(graph, node_ownership(graph)); } static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) @@ -458,31 +520,42 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ } } MatchConfig config(regexp_flag, nocase_flag, false); - std::vector> resolved; + std::vector> resolved; for (auto pat : patterns) { bool found = false; - for (auto name : objects->design_ports) { + for (auto [name, wire] : objects->design_ports) { auto [does_match, matching_bits] = matches(name, pat, config); if (does_match) { found = true; - resolved.push_back(std::make_tuple(name, matching_bits)); + resolved.push_back(std::make_tuple(name, wire, matching_bits)); } } if (!found) log_warning("No matches in design for port %s\n", pat.c_str()); } - Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); - for (auto [obj, matching_bits] : resolved) { - Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.c_str(), obj.size())); + Tcl_Obj *result = nullptr; + for (auto [name, wire, matching_bits] : resolved) { + if (objects->value_mode == SdcObjects::ValueMode::Normal) { + if (!result) + result = Tcl_NewListObj(resolved.size(), nullptr); + size_t width = wire->width; + for (size_t i = 0; i < width; i++) + if (matching_bits.is_set(i)) + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); + } if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) { - if (objects->constrained_ports.count(obj) == 0) { - objects->constrained_ports[obj] = matching_bits; + if (objects->constrained_ports.count(name) == 0) { + objects->constrained_ports[name] = matching_bits; } else { - objects->constrained_ports[obj].merge(matching_bits); + objects->constrained_ports[name].merge(matching_bits); } } } + if (objects->value_mode == SdcObjects::ValueMode::Graph) { + return redirect_unknown(interp, objc, objv); + } + Tcl_SetObjResult(interp, result); return TCL_OK; } @@ -545,13 +618,14 @@ static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, log_error("%s: pin %s not found\n", proc_name.c_str(), str); } else if (key_expect_type == "port") { bool found = false; - for (auto name : objects->design_ports) { - if (name == str) { - found = true; - objects->constrained_ports.insert(name); - break; // resolved, expected unique - } - } + log_error("TODO temporarily disabled due to working on a different flow\n"); + // for (auto [name, ] : objects->design_ports) { + // if (name == str) { + // found = true; + // objects->constrained_ports.insert(name); + // break; // resolved, expected unique + // } + // } if (!found) log_error("%s: port %s not found\n", proc_name.c_str(), str); } else { diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index 467ce70c6..f89050f6f 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -23,11 +23,12 @@ proc unknown {args} { if {![info exists sdc_calls]} { set sdc_calls {} } + set ret "YOSYS_SDC_MAGIC_NODE_$sdc_call_index" incr sdc_call_index lappend sdc_calls $args puts "unknown $args, returning YOSYS_SDC_MAGIC_NODE_$sdc_call_index" - return "YOSYS_SDC_MAGIC_NODE_$sdc_call_index" + return $ret } proc list {args} { - unknown "list" $args + return [unknown "list" {*}$args] } \ No newline at end of file From 48ff9d4950e850632c2987c29aa029a370bb0d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Ji=C5=99=C3=AD=20Tywoniak?= Date: Mon, 4 Aug 2025 13:27:42 +0200 Subject: [PATCH 30/45] sdc: refactor find_matching --- passes/cmds/sdc.cc | 53 ++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index f0d7175c5..0c00a4466 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -412,7 +412,25 @@ void inspect_globals(Tcl_Interp* interp) { std::vector graph = build_graph(sdc_calls); dump_sdc_graph(graph, node_ownership(graph)); } - +template +std::vector> +find_matching(U objects, const MatchConfig& config, const std::vector &patterns, const char* obj_type) +{ + std::vector> resolved; + for (auto pat : patterns) { + bool found = false; + for (auto [name, obj] : objects) { + auto [does_match, matching_bits] = matches(name, pat, config); + if (does_match) { + found = true; + resolved.push_back(std::make_tuple(name, obj, matching_bits)); + } + } + if (!found) + log_warning("No matches in design for %s %s\n", obj_type, pat.c_str()); + } + return resolved; +} static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { auto* objects = (SdcObjects*)data; @@ -454,18 +472,8 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O MatchConfig config(regexp_flag, nocase_flag, hierarchical_flag); std::vector> resolved; - for (auto pat : patterns) { - bool found = false; - for (auto [name, pin] : objects->design_pins) { - auto [does_match, matching_bits] = matches(name, pat, config); - if (does_match) { - found = true; - resolved.push_back(std::make_tuple(name, pin, matching_bits)); - } - } - if (!found) - log_warning("No matches in design for pin %s\n", pat.c_str()); - } + const auto& pins = objects->design_pins; + resolved = find_matching(pins, config, patterns, "pin"); if (separator != "/") { Tcl_SetResult(interp, (char *)"Only '/' accepted as separator", TCL_STATIC); @@ -519,20 +527,12 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ log_error("get_ports got unexpected number of patterns in simple mode\n"); } } + MatchConfig config(regexp_flag, nocase_flag, false); std::vector> resolved; - for (auto pat : patterns) { - bool found = false; - for (auto [name, wire] : objects->design_ports) { - auto [does_match, matching_bits] = matches(name, pat, config); - if (does_match) { - found = true; - resolved.push_back(std::make_tuple(name, wire, matching_bits)); - } - } - if (!found) - log_warning("No matches in design for port %s\n", pat.c_str()); - } + const auto& ports = objects->design_ports; + resolved = find_matching(ports, config, patterns, "port"); + Tcl_Obj *result = nullptr; for (auto [name, wire, matching_bits] : resolved) { if (objects->value_mode == SdcObjects::ValueMode::Normal) { @@ -676,9 +676,6 @@ struct SdcPass : public Pass { SdcPass() : Pass("sdc", "sniff at some SDC") { } void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC pass.\n"); - // if (args.size() < 2) - // log_cmd_error("Missing SDC file.\n"); - // TODO optional extra stub file size_t argidx; std::vector opensta_stubs_paths; for (argidx = 1; argidx < args.size(); argidx++) { From 075237f73b3cadd4e4e29d18a096bdfbe590c4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Ji=C5=99=C3=AD=20Tywoniak?= Date: Mon, 4 Aug 2025 14:22:23 +0200 Subject: [PATCH 31/45] sdc: refactor more --- passes/cmds/sdc.cc | 474 ++++++++++++++++++++++++++------------------- 1 file changed, 270 insertions(+), 204 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 0c00a4466..9a2a5b1ed 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -1,10 +1,8 @@ #include "kernel/register.h" -#include "kernel/celltypes.h" #include "kernel/rtlil.h" #include "kernel/log.h" #include #include -#include #include #include @@ -12,6 +10,76 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +struct TclCall { + Tcl_Interp *interp; + int objc; + Tcl_Obj* const* objv; +}; + +static int redirect_unknown(TclCall call); +static size_t get_node_count(Tcl_Interp* interp); + +struct BitSelection { + bool all = false; + std::vector bits = {}; + void set_all() { + bits.clear(); + all = true; + } + void clear() { + bits.clear(); + all = false; + } + void set(size_t idx) { + if (all) + return; + if (idx >= bits.size()) + bits.resize(idx + 1); + bits[idx] = true; + } + void merge(const BitSelection& other) { + if (all) + return; + if (other.all) { + set_all(); + return; + } + if (other.bits.size() > bits.size()) + bits.resize(other.bits.size()); + for (size_t other_idx = 0; other_idx < other.bits.size(); other_idx++) { + bool other_bit = other.bits[other_idx]; + if (other_bit) + set(other_idx); + } + } + void dump() { + if (!all) { + for (size_t i = 0; i < bits.size(); i++) + if (bits[i]) + log("\t\t [%zu]\n", i); + } else { + log("\t\t FULL\n"); + } + } + bool is_set(size_t idx) const { + if (all) + return true; + if (idx >= bits.size()) + return false; + return bits[idx]; + } + // TODO actually use this + void compress(size_t size) { + if (bits.size() < size) + return; + for (size_t i = 0; i < size; i++) + if (!bits[i]) + return; + bits.clear(); + bits.shrink_to_fit(); + all = true; + } +}; struct SdcObjects { enum CollectMode { @@ -28,74 +96,19 @@ struct SdcObjects { // return a new graph node assuming unknown is overridden Graph, } value_mode; - struct BitSelection { - bool all = false; - std::vector bits = {}; - void set_all() { - bits.clear(); - all = true; - } - void clear() { - bits.clear(); - all = false; - } - void set(size_t idx) { - if (all) - return; - if (idx >= bits.size()) - bits.resize(idx + 1); - bits[idx] = true; - } - void merge(const BitSelection& other) { - if (all) - return; - if (other.all) { - set_all(); - return; - } - if (other.bits.size() > bits.size()) - bits.resize(other.bits.size()); - for (size_t other_idx = 0; other_idx < other.bits.size(); other_idx++) { - bool other_bit = other.bits[other_idx]; - if (other_bit) - set(other_idx); - } - } - void dump() { - if (!all) { - for (size_t i = 0; i < bits.size(); i++) - if (bits[i]) - log("\t\t [%zu]\n", i); - } else { - log("\t\t FULL\n"); - } - } - bool is_set(size_t idx) { - if (all) - return true; - if (idx >= bits.size()) - return false; - return bits[idx]; - } - // TODO actually use this - void compress(size_t size) { - if (bits.size() < size) - return; - for (size_t i = 0; i < size; i++) - if (!bits[i]) - return; - bits.clear(); - bits.shrink_to_fit(); - all = true; - } - }; using CellPin = std::pair; std::vector> design_ports; std::vector> design_cells; std::vector> design_pins; std::vector> design_nets; - dict constrained_ports; + using PortPattern = std::tuple; + using PinPattern = std::tuple; + std::vector> resolved_port_pattern_sets; + std::vector> resolved_pin_pattern_sets; + // TODO + + dict, BitSelection> constrained_ports; pool> constrained_cells; dict, BitSelection> constrained_pins; dict, BitSelection> constrained_nets; @@ -156,6 +169,30 @@ struct SdcObjects { sniff_module(hierarchy, top); } ~SdcObjects() = default; + + template + void build_normal_result(Tcl_Interp* interp, std::vector>&& resolved, U& tgt, std::function width, Tcl_Obj*& result) { + if (!result) + result = Tcl_NewListObj(resolved.size(), nullptr); + for (auto [name, obj, matching_bits] : resolved) { + for (size_t i = 0; i < width(obj); i++) + if (matching_bits.is_set(i)) { + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); + break; + } + + } + size_t node_count = get_node_count(interp); + tgt.emplace_back(std::move(resolved)); + log("%zu %zu\n", node_count, tgt.size()); + log_assert(node_count == tgt.size()); + } + template + void merge_as_constrained(std::vector>&& resolved) { + for (auto [name, obj, matching_bits] : resolved) { + merge_or_init(std::make_pair(name, obj), constrained_pins, matching_bits); + } + } void dump() { std::sort(design_ports.begin(), design_ports.end()); std::sort(design_cells.begin(), design_cells.end()); @@ -186,7 +223,9 @@ struct SdcObjects { // } // log("\n"); log("Constrained ports:\n"); - for (auto [name, bits] : constrained_ports) { + for (auto [ref, bits] : constrained_ports) { + auto [name, port] = ref; + (void)port; log("\t%s\n", name.c_str()); bits.dump(); } @@ -213,16 +252,6 @@ struct SdcObjects { } }; -template -static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { - std::string expected = std::string("-") + flag_name; - if (expected == arg) { - flag_var = true; - return true; - } - return false; -} - // TODO vectors // TODO cell arrays? struct MatchConfig { @@ -241,7 +270,7 @@ struct MatchConfig { hier(hierarchical_flag ? FLAT : TREE) { } }; -static std::pair matches(std::string name, const std::string& pat, const MatchConfig& config) { +static std::pair matches(std::string name, const std::string& pat, const MatchConfig& config) { (void)config; bool got_bit_index = false;; int bit_idx; @@ -258,7 +287,7 @@ static std::pair matches(std::string name, const bit_idx = std::stoi(bit_selector); } - SdcObjects::BitSelection bits = {}; + BitSelection bits = {}; if (name == pat_base) { if (got_bit_index) { bits.set(bit_idx); @@ -273,33 +302,38 @@ static std::pair matches(std::string name, const } } +static int graph_node(TclCall call) { + // TODO is that it? + return redirect_unknown(call); +} -static int redirect_unknown(Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { - Tcl_Obj *newCmd = Tcl_NewStringObj("unknown", -1); - auto newObjc = objc + 1; - Tcl_Obj **newObjv = new Tcl_Obj*[newObjc]; - newObjv[0] = newCmd; - for (int i = 1; i < newObjc; i++) { - newObjv[i] = objv[i - 1]; - log("newObjv %s\n", Tcl_GetString(newObjv[i])); - } - int result = Tcl_EvalObjv(interp, newObjc, newObjv, 0); - Tcl_DecrRefCount(newCmd); - delete[] newObjv; +static int redirect_unknown(TclCall call) { + // TODO redirect to different command + Tcl_Obj *newCmd = Tcl_NewStringObj("unknown", -1); + auto newObjc = call.objc + 1; + Tcl_Obj **newObjv = new Tcl_Obj*[newObjc]; + newObjv[0] = newCmd; + for (int i = 1; i < newObjc; i++) { + newObjv[i] = call.objv[i - 1]; + } + int result = Tcl_EvalObjv(call.interp, newObjc, newObjv, 0); + Tcl_DecrRefCount(newCmd); + delete[] newObjv; return result; } + struct SdcGraphNode { - using Child = std::variant; - std::vector children; - SdcGraphNode() = default; - void addChild(SdcGraphNode* child) { - children.push_back(child); - } - void addChild(std::string tcl_string) { - children.push_back(tcl_string); - } - void dump(std::ostream& os) const { + using Child = std::variant; + std::vector children; + SdcGraphNode() = default; + void addChild(SdcGraphNode* child) { + children.push_back(child); + } + void addChild(std::string tcl_string) { + children.push_back(tcl_string); + } + void dump(std::ostream& os) const { bool first = true; for (auto child : children) { if (first) { @@ -317,13 +351,20 @@ struct SdcGraphNode { log_assert(false); } } - } + } }; +static size_t get_node_count(Tcl_Interp* interp) { + const char* idx_raw = Tcl_GetVar(interp, "sdc_call_index", TCL_GLOBAL_ONLY); + log_assert(idx_raw); + std::string idx(idx_raw); + for (auto c : idx) + if (!std::isdigit(c)) + log_error("sdc_call_index non-numeric value %s\n", idx.c_str()); + return std::stoi(idx); +} + std::vector> gather_nested_calls(Tcl_Interp* interp) { - const char* idx_s = Tcl_GetVar(interp, "sdc_call_index", TCL_GLOBAL_ONLY); - log_assert(idx_s); - size_t node_count = std::stoi(idx_s); Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY); int listLength; @@ -354,7 +395,7 @@ std::vector> gather_nested_calls(Tcl_Interp* interp) { sdc_calls.push_back(subList); } } - log_assert(sdc_calls.size() == node_count); + log_assert(sdc_calls.size() == get_node_count(interp)); return sdc_calls; } @@ -412,11 +453,13 @@ void inspect_globals(Tcl_Interp* interp) { std::vector graph = build_graph(sdc_calls); dump_sdc_graph(graph, node_ownership(graph)); } + +// patterns -> (pattern-object-bit)s template -std::vector> +std::vector> find_matching(U objects, const MatchConfig& config, const std::vector &patterns, const char* obj_type) { - std::vector> resolved; + std::vector> resolved; for (auto pat : patterns) { bool found = false; for (auto [name, obj] : objects) { @@ -424,6 +467,7 @@ find_matching(U objects, const MatchConfig& config, const std::vector patterns; - int i = 1; - for (; i < objc; i++) { - if (parse_flag(Tcl_GetString(objv[i]), "hierarchical", hierarchical_flag)) continue; - if (parse_flag(Tcl_GetString(objv[i]), "hier", hierarchical_flag)) continue; - if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; - if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; - if (!strcmp(Tcl_GetString(objv[i]), "-hsc")) { - separator = Tcl_GetString(objv[++i]); - continue; + std::vector patterns = {}; + std::initializer_list legals; + const char* name; + GetterOpts(const char* name, std::initializer_list legals) : legals(legals), name(name) {} + bool parse_opt(Tcl_Obj* obj, const char* opt_name) { + char* arg = Tcl_GetString(obj); + std::string expected = std::string("-") + opt_name; + if (expected == arg) { + if (!std::find_if(legals.begin(), legals.end(), + [&opt_name](const char* str) { return opt_name == str; })) + log_cmd_error("Illegal argument %s for %s.\n", expected.c_str(), name); + return true; } - if (!strcmp(Tcl_GetString(objv[i]), "-of_objects")) { - of_objects = objv[++i]; - continue; + return false; + } + template + bool parse_flag(Tcl_Obj* obj, const char* flag_name, T& flag_var) { + bool ret = parse_opt(obj, flag_name); + if (ret) + flag_var = true; + return ret; + } + void parse(int objc, Tcl_Obj* const objv[]) { + int i = 1; + for (; i < objc; i++) { + if (parse_flag(objv[i], "hierarchical", hierarchical_flag)) continue; + if (parse_flag(objv[i], "hier", hierarchical_flag)) continue; + if (parse_flag(objv[i], "regexp", regexp_flag)) continue; + if (parse_flag(objv[i], "nocase", nocase_flag)) continue; + if (parse_opt(objv[i], "hsc")) { + log_assert(i + 1 < objc); + separator = Tcl_GetString(objv[++i]); + continue; + } + if (parse_opt(objv[i], "of_objects")) { + log_assert(i + 1 < objc); + of_objects = objv[++i]; + continue; + } + break; } - // Onto the next loop - break; - } - for (; i < objc; i++) { - patterns.push_back(Tcl_GetString(objv[i])); - } - if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) { + for (; i < objc; i++) { + patterns.push_back(Tcl_GetString(objv[i])); + } + }; + void check_simple() { if (regexp_flag || hierarchical_flag || nocase_flag || separator != "/" || of_objects) { - log_error("get_pins got unexpected flags in simple mode\n"); - } - if (patterns.size() != 1) { - log_error("get_pins got unexpected number of patterns in simple mode\n"); + log_error("%s got unexpected flags in simple mode\n", name); } + if (patterns.size() != 1) + log_error("%s got unexpected number of patterns in simple mode: %zu\n", name, patterns.size()); } + void check_simple_sep() { + if (separator != "/") + log_error("Only '/' accepted as separator"); + } +}; - MatchConfig config(regexp_flag, nocase_flag, hierarchical_flag); - std::vector> resolved; +// void build_normal_result(Tcl_Interp* interp, size_t list_len, size_t width, const std::string& name, Tcl_Obj*& result, const BitSelection& matching_bits) { +// if (!result) +// result = Tcl_NewListObj(list_len, nullptr); +// for (size_t i = 0; i < width; i++) +// if (matching_bits.is_set(i)) +// Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); +// } + +template +void merge_or_init(const T& key, dict& dst, const BitSelection& src) { + if (dst.count(key) == 0) { + dst[key] = src; + } else { + dst[key].merge(src); + } +} + +static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +{ + auto* objects = (SdcObjects*)data; + GetterOpts opts("get_pins", {"hierarchical", "hier", "regexp", "nocase", "hsc", "of_objects"}); + opts.parse(objc, objv); + if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) + opts.check_simple(); + opts.check_simple_sep(); + + MatchConfig config(opts.regexp_flag, opts.nocase_flag, opts.hierarchical_flag); + std::vector> resolved; const auto& pins = objects->design_pins; - resolved = find_matching(pins, config, patterns, "pin"); - - if (separator != "/") { - Tcl_SetResult(interp, (char *)"Only '/' accepted as separator", TCL_STATIC); - return TCL_ERROR; - } + resolved = find_matching(pins, config, opts.patterns, "pin"); Tcl_Obj *result = nullptr; - for (auto [name, pin, matching_bits] : resolved) { - if (objects->value_mode == SdcObjects::ValueMode::Normal) { - if (!result) - result = Tcl_NewListObj(resolved.size(), nullptr); - size_t width = (size_t)pin.first->getPort(pin.second).size(); - for (size_t i = 0; i < width; i++) - if (matching_bits.is_set(i)) - Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); - } + if (objects->value_mode == SdcObjects::ValueMode::Normal) { + log_error("TODO normal\n"); + auto width = [](SdcObjects::CellPin& pin) -> size_t { + return (size_t)pin.first->getPort(pin.second).size(); + }; + objects->build_normal_resultresolved_pin_pattern_sets)>(interp, std::move(resolved), objects->resolved_pin_pattern_sets, width, result); + } else if (objects->value_mode == SdcObjects::ValueMode::Graph) + return graph_node(TclCall{interp, objc, objv}); - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) - objects->constrained_pins[std::make_pair(name, pin)].merge(matching_bits); - } + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + objects->merge_as_constrained(std::move(resolved)); + // merge_or_init(std::make_pair(name, pin), objects->constrained_pins, matching_bits); + // TODO + // } + + + // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_pin_pattern_sets); - if (objects->value_mode == SdcObjects::ValueMode::Graph) { - return redirect_unknown(interp, objc, objv); - } if (result) Tcl_SetObjResult(interp, result); return TCL_OK; @@ -506,57 +597,32 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { auto* objects = (SdcObjects*)data; - bool regexp_flag = false; - bool nocase_flag = false; - std::vector patterns; - int i = 1; - for (; i < objc; i++) { - if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; - if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; - // Onto the next loop - break; - } - for (; i < objc; i++) { - patterns.push_back(Tcl_GetString(objv[i])); - } - if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) { - if (regexp_flag || nocase_flag) { - log_error("get_ports got unexpected flags in simple mode\n"); - } - if (patterns.size() != 1) { - log_error("get_ports got unexpected number of patterns in simple mode\n"); - } - } + GetterOpts opts("get_ports", {"regexp", "nocase"}); + opts.parse(objc, objv); + if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) + opts.check_simple(); - MatchConfig config(regexp_flag, nocase_flag, false); - std::vector> resolved; + MatchConfig config(opts.regexp_flag, opts.nocase_flag, false); + std::vector> resolved; const auto& ports = objects->design_ports; - resolved = find_matching(ports, config, patterns, "port"); + resolved = find_matching(ports, config, opts.patterns, "port"); Tcl_Obj *result = nullptr; for (auto [name, wire, matching_bits] : resolved) { - if (objects->value_mode == SdcObjects::ValueMode::Normal) { - if (!result) - result = Tcl_NewListObj(resolved.size(), nullptr); - size_t width = wire->width; - for (size_t i = 0; i < width; i++) - if (matching_bits.is_set(i)) - Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); - } - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) { - if (objects->constrained_ports.count(name) == 0) { - objects->constrained_ports[name] = matching_bits; - } else { - objects->constrained_ports[name].merge(matching_bits); - } - } + if (objects->value_mode == SdcObjects::ValueMode::Normal) + log_error("TODO normal\n"); + // objects->build_normal_result(interp, resolved.size(), wire->width, name, result, matching_bits); + + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + merge_or_init(std::make_pair(name, wire), objects->constrained_ports, matching_bits); } if (objects->value_mode == SdcObjects::ValueMode::Graph) { - return redirect_unknown(interp, objc, objv); + return graph_node(TclCall{interp, objc, objv}); + // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_port_pattern_sets); } - - Tcl_SetObjResult(interp, result); + if (result) + Tcl_SetObjResult(interp, result); return TCL_OK; } @@ -592,10 +658,10 @@ static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) return TCL_OK; - std::string key_name = Tcl_GetString(objv[1]); - Tcl_Obj* key_value = objv[2]; + std::string key_name = Tcl_GetString(objv[1]); + Tcl_Obj* key_value = objv[2]; std::string key_expect_type = Tcl_GetString(objv[3]); - std::string proc_name = Tcl_GetString(objv[4]); + std::string proc_name = Tcl_GetString(objv[4]); auto track_typed = [key_expect_type, objects, proc_name, key_name](const char* str) -> void { auto split = split_at(str); @@ -608,7 +674,7 @@ static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, bool found = false; for (auto [name, pin] : objects->design_pins) { log_error("TODO temporarily disabled due to working on a different flow\n"); - // if (name + "/" + pin->name.str() == str) { + // if (name + "/" + pin.name.str() == str) { // found = true; // objects->constrained_pins.insert(std::make_pair(name, pin)); // break; // resolved, expected unique @@ -674,7 +740,7 @@ public: struct SdcPass : public Pass { // TODO help SdcPass() : Pass("sdc", "sniff at some SDC") { } -void execute(std::vector args, RTLIL::Design *design) override { + void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC pass.\n"); size_t argidx; std::vector opensta_stubs_paths; From d4228efae84cf0f9bbdab5f1150a07489d1dae19 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 2 Oct 2025 16:45:41 +0200 Subject: [PATCH 32/45] sdc: keep_hiearchy --- passes/cmds/sdc.cc | 70 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 9a2a5b1ed..c3cd0ed0a 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -97,6 +97,7 @@ struct SdcObjects { Graph, } value_mode; using CellPin = std::pair; + Design* design; std::vector> design_ports; std::vector> design_cells; std::vector> design_pins; @@ -158,7 +159,7 @@ struct SdcObjects { } } } - SdcObjects(Design* design) { + SdcObjects(Design* design) : design(design) { Module* top = design->top_module(); if (!top) log_error("Top module couldn't be determined. Check 'top' attribute usage"); @@ -250,6 +251,47 @@ struct SdcObjects { } log("\n"); } + + class KeepHierarchyWorker { + std::unordered_set tracked_modules = {}; + Design* design = nullptr; + bool mark(Module* mod) { + for (auto* cell : mod->cells()) { + if (auto* submod = design->module(cell->type)) + if (mark(submod)) + return true; + } + + if (tracked_modules.count(mod)) + return true; + + return false; + } + public: + KeepHierarchyWorker(SdcObjects* objects, Design* d) : design(d) { + for (auto [ref, _] : objects->constrained_ports) { + tracked_modules.insert(ref.second->module); + } + for (auto& [_, cell] : objects->constrained_cells) { + tracked_modules.insert(cell->module); + } + for (auto& [ref, _] : objects->constrained_pins) { + tracked_modules.insert(ref.second.first->module); + } + for (auto& [ref, _] : objects->constrained_nets) { + tracked_modules.insert(ref.second->module); + } + log_debug("keep_hierarchy tracked modules:\n"); + for (auto* mod : tracked_modules) + log_debug("%s", mod->name); + } + bool mark() { + return mark(design->top_module()); + } + }; + void keep_hierarchy() { + (void)KeepHierarchyWorker(this, design).mark(); + } }; // TODO vectors @@ -448,10 +490,11 @@ void dump_sdc_graph(const std::vector& graph, const std::vector> sdc_calls = gather_nested_calls(interp); std::vector graph = build_graph(sdc_calls); - dump_sdc_graph(graph, node_ownership(graph)); + if (dump_mode) + dump_sdc_graph(graph, node_ownership(graph)); } // patterns -> (pattern-object-bit)s @@ -743,9 +786,21 @@ struct SdcPass : public Pass { void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC pass.\n"); size_t argidx; + bool graph_mode = false; + bool dump_mode = false; + bool keep_hierarchy_mode = false; std::vector opensta_stubs_paths; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-stubs" && argidx+1 < args.size()) { + if (args[argidx] == "-graph") { + graph_mode = true; + continue; + } else if (args[argidx] == "-dump") { + dump_mode = true; + continue; + } else if (args[argidx] == "-keep_hierarchy") { + keep_hierarchy_mode = true; + continue; + } else if (args[argidx] == "-stubs" && argidx+1 < args.size()) { opensta_stubs_paths.push_back(args[++argidx]); continue; } @@ -767,8 +822,11 @@ struct SdcPass : public Pass { log_cmd_error("SDC interpreter returned an error in OpenSTA stub file %s: %s\n", path.c_str(), Tcl_GetStringResult(interp)); if (Tcl_EvalFile(interp, sdc_path.c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); - sdc.objects->dump(); - inspect_globals(interp); + if (dump_mode) + sdc.objects->dump(); + if (keep_hierarchy_mode) + sdc.objects->keep_hierarchy(); + inspect_globals(interp, graph_mode); Tcl_Release(interp); } } SdcPass; From 8a54e513008e9d6739da1240ca00e19a45a4baf1 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 2 Oct 2025 16:52:46 +0200 Subject: [PATCH 33/45] sdc: add -keep_hierarchy --- passes/cmds/sdc.cc | 43 ++++++++++++++++++++++++++++++++++++++++--- passes/cmds/stubs.sdc | 2 +- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index c3cd0ed0a..719cbef64 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -258,12 +258,16 @@ struct SdcObjects { bool mark(Module* mod) { for (auto* cell : mod->cells()) { if (auto* submod = design->module(cell->type)) - if (mark(submod)) + if (mark(submod)) { + mod->set_bool_attribute(ID::keep_hierarchy); return true; + } } - if (tracked_modules.count(mod)) + if (tracked_modules.count(mod)) { + mod->set_bool_attribute(ID::keep_hierarchy); return true; + } return false; } @@ -283,7 +287,7 @@ struct SdcObjects { } log_debug("keep_hierarchy tracked modules:\n"); for (auto* mod : tracked_modules) - log_debug("%s", mod->name); + log_debug("\t%s\n", mod->name); } bool mark() { return mark(design->top_module()); @@ -669,6 +673,38 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ return TCL_OK; } +static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +{ + auto* objects = (SdcObjects*)data; + GetterOpts opts("get_nets", {"hierarchical", "hier", "regexp", "nocase", "hsc", "of_objects"}); + opts.parse(objc, objv); + if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) + opts.check_simple(); + + MatchConfig config(opts.regexp_flag, opts.nocase_flag, false); + std::vector> resolved; + const auto& ports = objects->design_nets; + resolved = find_matching(ports, config, opts.patterns, "net"); + + Tcl_Obj *result = nullptr; + for (auto [name, wire, matching_bits] : resolved) { + if (objects->value_mode == SdcObjects::ValueMode::Normal) + log_error("TODO normal\n"); + // objects->build_normal_result(interp, resolved.size(), wire->width, name, result, matching_bits); + + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + merge_or_init(std::make_pair(name, wire), objects->constrained_nets, matching_bits); + } + + if (objects->value_mode == SdcObjects::ValueMode::Graph) { + return graph_node(TclCall{interp, objc, objv}); + // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_port_pattern_sets); + } + if (result) + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + std::optional> split_at(std::string s) { size_t pos = s.find('@'); @@ -773,6 +809,7 @@ public: objects->collect_mode = SdcObjects::CollectMode::SimpleGetter; objects->value_mode = SdcObjects::ValueMode::Graph; Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); + Tcl_CreateObjCommand(interp, "get_nets", sdc_get_nets_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "ys_track_typed_key", ys_track_typed_key_cmd, (ClientData) objects.get(), NULL); return interp; diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index f89050f6f..591d4d6cd 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -26,7 +26,7 @@ proc unknown {args} { set ret "YOSYS_SDC_MAGIC_NODE_$sdc_call_index" incr sdc_call_index lappend sdc_calls $args - puts "unknown $args, returning YOSYS_SDC_MAGIC_NODE_$sdc_call_index" + # puts "unknown $args, returning YOSYS_SDC_MAGIC_NODE_$sdc_call_index" return $ret } proc list {args} { From 94dd248dfb0ce9d7a022ce4876fba2e5288dd7dd Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 7 Oct 2025 16:41:31 +0200 Subject: [PATCH 34/45] sdc: graph mode only --- passes/cmds/sdc.cc | 91 ++++++++++++---------------------------------- 1 file changed, 23 insertions(+), 68 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 719cbef64..f21973480 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -90,12 +90,6 @@ struct SdcObjects { // constraint-side tracking FullConstraint, } collect_mode; - enum ValueMode { - // return something sensible and error on unknown - Normal, - // return a new graph node assuming unknown is overridden - Graph, - } value_mode; using CellPin = std::pair; Design* design; std::vector> design_ports; @@ -522,16 +516,11 @@ find_matching(U objects, const MatchConfig& config, const std::vector patterns = {}; - std::initializer_list legals; + +struct TclOpts { const char* name; - GetterOpts(const char* name, std::initializer_list legals) : legals(legals), name(name) {} + std::initializer_list legals; + TclOpts(const char* name, std::initializer_list legals) : name(name), legals(legals) {} bool parse_opt(Tcl_Obj* obj, const char* opt_name) { char* arg = Tcl_GetString(obj); std::string expected = std::string("-") + opt_name; @@ -543,6 +532,16 @@ struct GetterOpts { } return false; } +}; + +struct GetterOpts : TclOpts { + bool hierarchical_flag = false; + bool regexp_flag = false; + bool nocase_flag = false; + std::string separator = "/"; + Tcl_Obj* of_objects = nullptr; + std::vector patterns = {}; + GetterOpts(const char* name, std::initializer_list legals) : TclOpts(name, legals) {} template bool parse_flag(Tcl_Obj* obj, const char* flag_name, T& flag_var) { bool ret = parse_opt(obj, flag_name); @@ -617,28 +616,7 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O const auto& pins = objects->design_pins; resolved = find_matching(pins, config, opts.patterns, "pin"); - Tcl_Obj *result = nullptr; - if (objects->value_mode == SdcObjects::ValueMode::Normal) { - log_error("TODO normal\n"); - auto width = [](SdcObjects::CellPin& pin) -> size_t { - return (size_t)pin.first->getPort(pin.second).size(); - }; - objects->build_normal_resultresolved_pin_pattern_sets)>(interp, std::move(resolved), objects->resolved_pin_pattern_sets, width, result); - } else if (objects->value_mode == SdcObjects::ValueMode::Graph) - return graph_node(TclCall{interp, objc, objv}); - - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) - objects->merge_as_constrained(std::move(resolved)); - // merge_or_init(std::make_pair(name, pin), objects->constrained_pins, matching_bits); - // TODO - // } - - - // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_pin_pattern_sets); - - if (result) - Tcl_SetObjResult(interp, result); - return TCL_OK; + return graph_node(TclCall{interp, objc, objv}); } static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) @@ -654,23 +632,12 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ const auto& ports = objects->design_ports; resolved = find_matching(ports, config, opts.patterns, "port"); - Tcl_Obj *result = nullptr; for (auto [name, wire, matching_bits] : resolved) { - if (objects->value_mode == SdcObjects::ValueMode::Normal) - log_error("TODO normal\n"); - // objects->build_normal_result(interp, resolved.size(), wire->width, name, result, matching_bits); - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) merge_or_init(std::make_pair(name, wire), objects->constrained_ports, matching_bits); } - if (objects->value_mode == SdcObjects::ValueMode::Graph) { - return graph_node(TclCall{interp, objc, objv}); - // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_port_pattern_sets); - } - if (result) - Tcl_SetObjResult(interp, result); - return TCL_OK; + return graph_node(TclCall{interp, objc, objv}); } static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) @@ -686,23 +653,12 @@ static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O const auto& ports = objects->design_nets; resolved = find_matching(ports, config, opts.patterns, "net"); - Tcl_Obj *result = nullptr; for (auto [name, wire, matching_bits] : resolved) { - if (objects->value_mode == SdcObjects::ValueMode::Normal) - log_error("TODO normal\n"); - // objects->build_normal_result(interp, resolved.size(), wire->width, name, result, matching_bits); - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) merge_or_init(std::make_pair(name, wire), objects->constrained_nets, matching_bits); } - if (objects->value_mode == SdcObjects::ValueMode::Graph) { - return graph_node(TclCall{interp, objc, objv}); - // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_port_pattern_sets); - } - if (result) - Tcl_SetObjResult(interp, result); - return TCL_OK; + return graph_node(TclCall{interp, objc, objv}); } std::optional> split_at(std::string s) @@ -807,7 +763,6 @@ public: objects = std::make_unique(design); objects->collect_mode = SdcObjects::CollectMode::SimpleGetter; - objects->value_mode = SdcObjects::ValueMode::Graph; Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_nets", sdc_get_nets_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); @@ -823,17 +778,17 @@ struct SdcPass : public Pass { void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC pass.\n"); size_t argidx; - bool graph_mode = false; bool dump_mode = false; + bool dump_graph_mode = false; bool keep_hierarchy_mode = false; std::vector opensta_stubs_paths; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-graph") { - graph_mode = true; - continue; - } else if (args[argidx] == "-dump") { + if (args[argidx] == "-dump") { dump_mode = true; continue; + } else if (args[argidx] == "-dump-graph") { + dump_graph_mode = true; + continue; } else if (args[argidx] == "-keep_hierarchy") { keep_hierarchy_mode = true; continue; @@ -863,7 +818,7 @@ struct SdcPass : public Pass { sdc.objects->dump(); if (keep_hierarchy_mode) sdc.objects->keep_hierarchy(); - inspect_globals(interp, graph_mode); + inspect_globals(interp, dump_graph_mode); Tcl_Release(interp); } } SdcPass; From 224ed524fa6effede43b004221f95783faad8446 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 7 Oct 2025 18:09:12 +0200 Subject: [PATCH 35/45] sdc: move to directory --- passes/cmds/Makefile.inc | 3 +-- passes/cmds/sdc/Makefile.inc | 3 +++ passes/cmds/{ => sdc}/sdc.cc | 0 passes/cmds/{ => sdc}/stubs.sdc | 1 - 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 passes/cmds/sdc/Makefile.inc rename passes/cmds/{ => sdc}/sdc.cc (100%) rename passes/cmds/{ => sdc}/stubs.sdc (93%) diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index f64fdea55..c4d43fd70 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -58,6 +58,5 @@ OBJS += passes/cmds/test_select.o OBJS += passes/cmds/timeest.o OBJS += passes/cmds/linecoverage.o OBJS += passes/cmds/sort.o -OBJS += passes/cmds/sdc.o -$(eval $(call add_share_file,share/sdc,passes/cmds/stubs.sdc)) +include $(YOSYS_SRC)/passes/cmds/sdc/Makefile.inc diff --git a/passes/cmds/sdc/Makefile.inc b/passes/cmds/sdc/Makefile.inc new file mode 100644 index 000000000..1962f622f --- /dev/null +++ b/passes/cmds/sdc/Makefile.inc @@ -0,0 +1,3 @@ +OBJS += passes/cmds/sdc/sdc.o + +$(eval $(call add_share_file,share/sdc,passes/cmds/sdc/stubs.sdc)) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc/sdc.cc similarity index 100% rename from passes/cmds/sdc.cc rename to passes/cmds/sdc/sdc.cc diff --git a/passes/cmds/stubs.sdc b/passes/cmds/sdc/stubs.sdc similarity index 93% rename from passes/cmds/stubs.sdc rename to passes/cmds/sdc/stubs.sdc index 591d4d6cd..247fe0abd 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/sdc/stubs.sdc @@ -13,7 +13,6 @@ stub ys_track_untyped_key stub ys_err_key stub ys_err_flag -# TODO move to separate file and tie to graph value mode proc unknown {args} { global sdc_call_index global sdc_calls From 07de7509bf0bff0ec59787235f978fcb134188d0 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 12:51:56 +0200 Subject: [PATCH 36/45] sdc: add -keep_hierarchy test --- Makefile | 1 + tests/sdc/alu_sub.sdc | 70 ++++++++++++++++++++++++++++++++++++++ tests/sdc/alu_sub.v | 62 +++++++++++++++++++++++++++++++++ tests/sdc/alu_sub.ys | 14 ++++++++ tests/sdc/run-test.sh | 4 +++ tests/sdc/side-effects.sdc | 2 ++ tests/sdc/side-effects.sh | 4 +++ tests/various/sdc.sdc | 5 --- 8 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 tests/sdc/alu_sub.sdc create mode 100644 tests/sdc/alu_sub.v create mode 100644 tests/sdc/alu_sub.ys create mode 100755 tests/sdc/run-test.sh create mode 100644 tests/sdc/side-effects.sdc create mode 100755 tests/sdc/side-effects.sh delete mode 100644 tests/various/sdc.sdc diff --git a/Makefile b/Makefile index 6984ba5f1..c16dbf175 100644 --- a/Makefile +++ b/Makefile @@ -902,6 +902,7 @@ MK_TEST_DIRS += tests/arch/xilinx MK_TEST_DIRS += tests/bugpoint MK_TEST_DIRS += tests/opt MK_TEST_DIRS += tests/sat +MK_TEST_DIRS += tests/sdc MK_TEST_DIRS += tests/sim MK_TEST_DIRS += tests/svtypes MK_TEST_DIRS += tests/techmap diff --git a/tests/sdc/alu_sub.sdc b/tests/sdc/alu_sub.sdc new file mode 100644 index 000000000..f298d20bc --- /dev/null +++ b/tests/sdc/alu_sub.sdc @@ -0,0 +1,70 @@ +############################################################################### +# Created by write_sdc +# Fri Oct 3 11:26:00 2025 +############################################################################### +current_design wrapper +############################################################################### +# Timing Constraints +############################################################################### +create_clock -name this_clk -period 1.0000 [get_ports {clk}] +create_clock -name that_clk -period 2.0000 +create_clock -name another_clk -period 2.0000 \ + [list [get_ports {A[0]}]\ + [get_ports {A[1]}]\ + [get_ports {A[2]}]\ + [get_ports {A[3]}]\ + [get_ports {A[4]}]\ + [get_ports {A[5]}]\ + [get_ports {A[6]}]\ + [get_ports {A[7]}]\ + [get_ports {B[0]}]\ + [get_ports {B[1]}]\ + [get_ports {B[2]}]\ + [get_ports {B[3]}]\ + [get_ports {B[4]}]\ + [get_ports {B[5]}]\ + [get_ports {B[6]}]\ + [get_ports {B[7]}]] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[0]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[0]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[1]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[1]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[2]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[2]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[3]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[3]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[4]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[4]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[5]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[5]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[6]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[6]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[7]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[7]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[0]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[0]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[1]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[1]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[2]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[2]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[3]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[3]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[4]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[4]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[5]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[5]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[6]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[6]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[7]}] +set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[7]}] +group_path -name operation_group\ + -through [list [get_nets {alu/operation[0]}]\ + [get_nets {alu/operation[1]}]\ + [get_nets {alu/operation[2]}]\ + [get_nets {alu/operation[3]}]] +############################################################################### +# Environment +############################################################################### +############################################################################### +# Design Rules +############################################################################### diff --git a/tests/sdc/alu_sub.v b/tests/sdc/alu_sub.v new file mode 100644 index 000000000..d66cad18e --- /dev/null +++ b/tests/sdc/alu_sub.v @@ -0,0 +1,62 @@ +module adder( + input [7:0] a, input [7:0] b, output [7:0] y +); + assign y = a + b; +endmodule + +module wrapper( + input clk, + input [7:0] A, + input [7:0] B, + input [3:0] op, + output reg [7:0] result +); + wire CF, ZF, SF; + alu alu( + .clk(clk), + .A(A), + .B(B), + .operation(op), + .result(result), + .CF(CF), + .ZF(ZF), + .SF(SF) + ); +endmodule + +module alu( + input clk, + input [7:0] A, + input [7:0] B, + input [3:0] operation, + output reg [7:0] result, + output reg CF, + output reg ZF, + output reg SF +); + + localparam ALU_OP_ADD /* verilator public_flat */ = 4'b0000; + localparam ALU_OP_SUB /* verilator public_flat */ = 4'b0001; + + reg [8:0] tmp; + reg [7:0] added; + + adder adder(.a(A), .b(B), .y(added)); + + always @(posedge clk) + begin + case (operation) + ALU_OP_ADD : + tmp = added; + ALU_OP_SUB : + tmp = A - B; + endcase + + CF <= tmp[8]; + ZF <= tmp[7:0] == 0; + SF <= tmp[7]; + + result <= tmp[7:0]; + end +endmodule + diff --git a/tests/sdc/alu_sub.ys b/tests/sdc/alu_sub.ys new file mode 100644 index 000000000..7e804e7a0 --- /dev/null +++ b/tests/sdc/alu_sub.ys @@ -0,0 +1,14 @@ +read_verilog alu_sub.v +proc +hierarchy -auto-top + +select -assert-mod-count 1 adder +select -assert-mod-count 1 wrapper +select -assert-mod-count 1 alu + +sdc -keep_hierarchy alu_sub.sdc +flatten + +select -assert-mod-count 0 adder +select -assert-mod-count 1 wrapper +select -assert-mod-count 1 alu diff --git a/tests/sdc/run-test.sh b/tests/sdc/run-test.sh new file mode 100755 index 000000000..971664bdb --- /dev/null +++ b/tests/sdc/run-test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -eu +source ../gen-tests-makefile.sh +generate_mk --yosys-scripts --bash \ No newline at end of file diff --git a/tests/sdc/side-effects.sdc b/tests/sdc/side-effects.sdc new file mode 100644 index 000000000..2c2126f84 --- /dev/null +++ b/tests/sdc/side-effects.sdc @@ -0,0 +1,2 @@ +puts "This should print something:" +puts [get_ports {A[0]}] \ No newline at end of file diff --git a/tests/sdc/side-effects.sh b/tests/sdc/side-effects.sh new file mode 100755 index 000000000..88d6154a1 --- /dev/null +++ b/tests/sdc/side-effects.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +../../yosys -p 'read_verilog alu_sub.v; proc; hierarchy -auto-top; sdc side-effects.sdc' | grep 'This should print something: +YOSYS_SDC_MAGIC_NODE_0' diff --git a/tests/various/sdc.sdc b/tests/various/sdc.sdc deleted file mode 100644 index 2143b34d7..000000000 --- a/tests/various/sdc.sdc +++ /dev/null @@ -1,5 +0,0 @@ -puts "SDC constraints file says hello from arbitrary Tcl execution" -set_false_path -from [get_pins s1/sa] -to [get_pins s1/sb] -puts "This should print something:" -puts [get_ports a b] -puts "Did it?" \ No newline at end of file From 650392d4eca33298aa836b2e01360f0f4cc9f3da Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 13:49:53 +0200 Subject: [PATCH 37/45] sdc: specialize stubs for the call graph --- passes/cmds/sdc/Makefile.inc | 2 +- passes/cmds/sdc/{stubs.sdc => graph-stubs.sdc} | 15 --------------- passes/cmds/sdc/sdc.cc | 2 +- 3 files changed, 2 insertions(+), 17 deletions(-) rename passes/cmds/sdc/{stubs.sdc => graph-stubs.sdc} (58%) diff --git a/passes/cmds/sdc/Makefile.inc b/passes/cmds/sdc/Makefile.inc index 1962f622f..7c8036550 100644 --- a/passes/cmds/sdc/Makefile.inc +++ b/passes/cmds/sdc/Makefile.inc @@ -1,3 +1,3 @@ OBJS += passes/cmds/sdc/sdc.o -$(eval $(call add_share_file,share/sdc,passes/cmds/sdc/stubs.sdc)) +$(eval $(call add_share_file,share/sdc,passes/cmds/sdc/graph-stubs.sdc)) diff --git a/passes/cmds/sdc/stubs.sdc b/passes/cmds/sdc/graph-stubs.sdc similarity index 58% rename from passes/cmds/sdc/stubs.sdc rename to passes/cmds/sdc/graph-stubs.sdc index 247fe0abd..b668f4d18 100644 --- a/passes/cmds/sdc/stubs.sdc +++ b/passes/cmds/sdc/graph-stubs.sdc @@ -1,18 +1,3 @@ -# with Tcl's eager evaluation, we will still eval args if they're unused by a stub -proc stub {function_name} { - proc $function_name {args} "puts \"stubbed $function_name\"" -} - -proc is_suppressed {args} { - return 0 -} - -# stub current_design -#stub ys_track_typed_key -stub ys_track_untyped_key -stub ys_err_key -stub ys_err_flag - proc unknown {args} { global sdc_call_index global sdc_calls diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index f21973480..4df256a77 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -805,7 +805,7 @@ struct SdcPass : public Pass { SDCInterpreter& sdc = SDCInterpreter::get(); Tcl_Interp *interp = sdc.fresh_interp(design); Tcl_Preserve(interp); - std::string stub_path = "+/sdc/stubs.sdc"; + std::string stub_path = "+/sdc/graph-stubs.sdc"; rewrite_filename(stub_path); if (Tcl_EvalFile(interp, stub_path.c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error in stub preamble file: %s\n", Tcl_GetStringResult(interp)); From f56e121ddbb1ff6a8832fa4d561bfd77ed1213fa Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 14:32:21 +0200 Subject: [PATCH 38/45] sdc: add help --- passes/cmds/sdc/sdc.cc | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index 4df256a77..b77ee3ebf 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -773,15 +773,33 @@ public: // Also see TclPass struct SdcPass : public Pass { - // TODO help - SdcPass() : Pass("sdc", "sniff at some SDC") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" sdc [options] file\n"); + log("\n"); + log("Read the SDC file for the current design.\n"); + log("\n"); + log(" -dump\n"); + log(" Dump the referenced design objects.\n"); + log("\n"); + log(" -dump-graph\n"); + log(" Dump the uninterpreted call graph.\n"); + log("\n"); + log(" -keep_hierarchy\n"); + log(" Add keep_hierarchy attributes while retaining SDC validity.\n"); + log("\n"); + } + SdcPass() : Pass("sdc", "Read an SDC file") { } void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC pass.\n"); + log_experimental("sdc"); size_t argidx; bool dump_mode = false; bool dump_graph_mode = false; bool keep_hierarchy_mode = false; - std::vector opensta_stubs_paths; + std::vector stubs_paths; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-dump") { dump_mode = true; @@ -793,7 +811,7 @@ struct SdcPass : public Pass { keep_hierarchy_mode = true; continue; } else if (args[argidx] == "-stubs" && argidx+1 < args.size()) { - opensta_stubs_paths.push_back(args[++argidx]); + stubs_paths.push_back(args[++argidx]); continue; } break; @@ -801,7 +819,9 @@ struct SdcPass : public Pass { if (argidx >= args.size()) log_cmd_error("Missing SDC file.\n"); - std::string sdc_path = args[argidx]; + std::string sdc_path = args[argidx++]; + if (argidx < args.size()) + log_cmd_error("Unexpected extra positional argument %s after SDC file %s.\n", args[argidx], sdc_path); SDCInterpreter& sdc = SDCInterpreter::get(); Tcl_Interp *interp = sdc.fresh_interp(design); Tcl_Preserve(interp); @@ -809,7 +829,7 @@ struct SdcPass : public Pass { rewrite_filename(stub_path); if (Tcl_EvalFile(interp, stub_path.c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error in stub preamble file: %s\n", Tcl_GetStringResult(interp)); - for (auto path : opensta_stubs_paths) + for (auto path : stubs_paths) if (Tcl_EvalFile(interp, path.c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error in OpenSTA stub file %s: %s\n", path.c_str(), Tcl_GetStringResult(interp)); if (Tcl_EvalFile(interp, sdc_path.c_str()) != TCL_OK) From 033a2d5a673d33461a497e1ab0496680d16f8717 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 14:45:36 +0200 Subject: [PATCH 39/45] sdc: remove vestigial code for tracked constraint followup work --- passes/cmds/sdc/sdc.cc | 87 ------------------------------------------ 1 file changed, 87 deletions(-) diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index b77ee3ebf..07040184e 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -585,14 +585,6 @@ struct GetterOpts : TclOpts { } }; -// void build_normal_result(Tcl_Interp* interp, size_t list_len, size_t width, const std::string& name, Tcl_Obj*& result, const BitSelection& matching_bits) { -// if (!result) -// result = Tcl_NewListObj(list_len, nullptr); -// for (size_t i = 0; i < width; i++) -// if (matching_bits.is_set(i)) -// Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); -// } - template void merge_or_init(const T& key, dict& dst, const BitSelection& src) { if (dst.count(key) == 0) { @@ -661,84 +653,6 @@ static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O return graph_node(TclCall{interp, objc, objv}); } -std::optional> split_at(std::string s) -{ - size_t pos = s.find('@'); - if (pos == std::string::npos) - return std::nullopt; - return std::make_tuple(s.substr(0, pos), s.substr(pos + 1)); -} - -// Whether string or list of strings, apply op to each string -void apply_args(Tcl_Interp *interp, std::function op, Tcl_Obj* obj) -{ - int length; - Tcl_Obj **value_list; - if (Tcl_ListObjGetElements(interp, obj, &length, &value_list) == TCL_OK) { - for (int i = 0; i < length; i++) { - op(Tcl_GetString(value_list[i])); - } - } else { - op(Tcl_GetString(obj)); - } -} - -static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) -{ - log("ys_track_typed_key_cmd\n"); - auto* objects = (SdcObjects*)data; - if (objc != 5) - log_error("ys_track_typed_key: Unexpected number of arguments: %d (expected 5)\n", objc); - - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) - return TCL_OK; - - std::string key_name = Tcl_GetString(objv[1]); - Tcl_Obj* key_value = objv[2]; - std::string key_expect_type = Tcl_GetString(objv[3]); - std::string proc_name = Tcl_GetString(objv[4]); - - auto track_typed = [key_expect_type, objects, proc_name, key_name](const char* str) -> void { - auto split = split_at(str); - if (!split) - log_error("%s: key %s should be a typed SDC object, but is something weird: %s\n", - proc_name.c_str(), key_name.c_str(), str); - - if (key_expect_type == "pin") { - log("PIN! %s\n", str); - bool found = false; - for (auto [name, pin] : objects->design_pins) { - log_error("TODO temporarily disabled due to working on a different flow\n"); - // if (name + "/" + pin.name.str() == str) { - // found = true; - // objects->constrained_pins.insert(std::make_pair(name, pin)); - // break; // resolved, expected unique - // } - } - if (!found) - log_error("%s: pin %s not found\n", proc_name.c_str(), str); - } else if (key_expect_type == "port") { - bool found = false; - log_error("TODO temporarily disabled due to working on a different flow\n"); - // for (auto [name, ] : objects->design_ports) { - // if (name == str) { - // found = true; - // objects->constrained_ports.insert(name); - // break; // resolved, expected unique - // } - // } - if (!found) - log_error("%s: port %s not found\n", proc_name.c_str(), str); - } else { - // TODO - log_warning("%s: unsupported type %s\n", proc_name.c_str(), key_expect_type.c_str()); - } - }; - apply_args(interp, track_typed, key_value); - return TCL_OK; -} - - class SDCInterpreter { private: @@ -766,7 +680,6 @@ public: Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_nets", sdc_get_nets_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); - Tcl_CreateObjCommand(interp, "ys_track_typed_key", ys_track_typed_key_cmd, (ClientData) objects.get(), NULL); return interp; } }; From 229123eb87a7ab2aa51ad5f1006a69af4f168f0e Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 14:57:28 +0200 Subject: [PATCH 40/45] sdc: disable without YOSYS_ENABLE_TCL --- passes/cmds/sdc/sdc.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index 07040184e..37af8a51c 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -1,3 +1,5 @@ +#ifdef YOSYS_ENABLE_TCL + #include "kernel/register.h" #include "kernel/rtlil.h" #include "kernel/log.h" @@ -757,3 +759,4 @@ struct SdcPass : public Pass { } SdcPass; YOSYS_NAMESPACE_END +#endif From 920f4793fb68d6421016d3e0acd0f86f9d3a3597 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 9 Oct 2025 20:55:15 +0200 Subject: [PATCH 41/45] sdc: error on unknown getters --- passes/cmds/sdc/graph-stubs.sdc | 26 +++++++++++++++++++++++- passes/cmds/sdc/sdc.cc | 35 +++++++++++++++++++++++++++------ tests/sdc/get_foo.sdc | 1 + tests/sdc/unknown-getter.sh | 5 +++++ 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 tests/sdc/get_foo.sdc create mode 100755 tests/sdc/unknown-getter.sh diff --git a/passes/cmds/sdc/graph-stubs.sdc b/passes/cmds/sdc/graph-stubs.sdc index b668f4d18..a6601f7f8 100644 --- a/passes/cmds/sdc/graph-stubs.sdc +++ b/passes/cmds/sdc/graph-stubs.sdc @@ -1,4 +1,21 @@ proc unknown {args} { + # Check if it's a getter + if {[llength $args] > 0} { + set first_arg [lindex $args 0] + if {[string match "get_*" $first_arg]} { + # It's a getter, has it been redirected from specialized C++ code? + if {[llength $args] > 1} { + set second_arg [lindex $args 1] + if {$second_arg ne "-getter-validated"} { + error "Unknown getter: $first_arg" + } + } else { + error "Unknown getter: $first_arg" + } + } + } + # TODO this safety feature could be optional via a global + global sdc_call_index global sdc_calls if {![info exists sdc_call_index]} { @@ -15,4 +32,11 @@ proc unknown {args} { } proc list {args} { return [unknown "list" {*}$args] -} \ No newline at end of file +} +proc get_clocks {args} { + # get_clocks isn't a design object getter + # because clocks aren't design objects, just aliases + # so the referred to clock pin already are being tracked + # as arguments of uninterpreted create_clock command or similar + return [unknown "get_clocks" "-getter-validated" {*}$args] +} diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index 37af8a51c..17c0f0be6 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -344,9 +344,32 @@ static std::pair matches(std::string name, const std::string } } -static int graph_node(TclCall call) { - // TODO is that it? - return redirect_unknown(call); +static int getter_graph_node(TclCall call) { + // Insert -getter-validated as first argument for passing to unknown + // to distinguish resolved and unknown getters. + // For example, if call is ["get_foo", "-bar"] + // then newCall is ["get_foo", "-getter-validated", "-bar"] + Tcl_Obj* validity_tag = Tcl_NewStringObj("-getter-validated", -1); + Tcl_IncrRefCount(validity_tag); + std::vector newObjv(call.objc + 1); + log_assert(call.objc > 0); + newObjv[0] = call.objv[0]; + newObjv[1] = validity_tag; + for (int i = 1; i < call.objc; ++i) { + newObjv[i + 1] = call.objv[i]; + } + // Send the vector to the Tcl land + Tcl_Obj** allocatedObjv = new Tcl_Obj*[call.objc + 1]; + for (int i = 0; i < call.objc + 1; ++i) { + allocatedObjv[i] = newObjv[i]; + } + TclCall newCall { + .interp = call.interp, + .objc = call.objc + 1, + .objv = allocatedObjv + }; + // Finally, redirect to unknown handler + return redirect_unknown(newCall); } static int redirect_unknown(TclCall call) { @@ -610,7 +633,7 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O const auto& pins = objects->design_pins; resolved = find_matching(pins, config, opts.patterns, "pin"); - return graph_node(TclCall{interp, objc, objv}); + return getter_graph_node(TclCall{interp, objc, objv}); } static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) @@ -631,7 +654,7 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ merge_or_init(std::make_pair(name, wire), objects->constrained_ports, matching_bits); } - return graph_node(TclCall{interp, objc, objv}); + return getter_graph_node(TclCall{interp, objc, objv}); } static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) @@ -652,7 +675,7 @@ static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O merge_or_init(std::make_pair(name, wire), objects->constrained_nets, matching_bits); } - return graph_node(TclCall{interp, objc, objv}); + return getter_graph_node(TclCall{interp, objc, objv}); } class SDCInterpreter diff --git a/tests/sdc/get_foo.sdc b/tests/sdc/get_foo.sdc new file mode 100644 index 000000000..0ea5ea34a --- /dev/null +++ b/tests/sdc/get_foo.sdc @@ -0,0 +1 @@ +get_foo -bar 1 diff --git a/tests/sdc/unknown-getter.sh b/tests/sdc/unknown-getter.sh new file mode 100755 index 000000000..9038834c6 --- /dev/null +++ b/tests/sdc/unknown-getter.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -euo pipefail + +! ../../yosys -p 'read_verilog alu_sub.v; proc; hierarchy -auto-top; sdc get_foo.sdc' 2>&1 | grep 'Unknown getter' From 1edc32dcd03a6c20ef2389aa3591cfbda8b1080f Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 19 Nov 2025 15:31:17 +0100 Subject: [PATCH 42/45] opensta, sdc_expand: mark as experimental --- techlibs/common/opensta.cc | 1 + techlibs/common/sdc_expand.cc | 1 + 2 files changed, 2 insertions(+) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 282481953..655fdbf2d 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -44,6 +44,7 @@ struct OpenstaPass : public Pass bool cleanup = design->scratchpad_get_bool("opensta.cleanup", true); log_header(design, "Executing OPENSTA pass.\n"); + log_experimental("opensta"); log_push(); size_t argidx; diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index fe8f20aa9..39c50875f 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -37,6 +37,7 @@ struct SdcExpandPass : public ScriptPass void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC_EXPAND pass.\n"); + log_experimental("sdc_expand"); string run_from, run_to; opensta_exe = ""; cleanup = design->scratchpad_get_bool("sdc_expand.cleanup", true); From 58e831486d3a4e2193a3d1b7ad3c3f4870767871 Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Wed, 19 Nov 2025 03:42:32 +0200 Subject: [PATCH 43/45] pyosys: __getitem__ for supported classes - functions that have a const `[]` operator method now support `__getitem__` in Python - fields of a pointer type now return a `reference_internal` instead of a `copy` because classes referenced to by pointers typically aren't copyable (e.g. RTLIL::Wire, RTLIL::Module, etc) - removed duplicate of test_script.py --- pyosys/generator.py | 11 ++++++-- tests/arch/ecp5/add_sub.py | 20 ------------- .../{test_script.py => test_ecp5_addsub.py} | 1 - tests/pyosys/test_sigspec_it.py | 28 +++++++++++++++++++ 4 files changed, 36 insertions(+), 24 deletions(-) delete mode 100644 tests/arch/ecp5/add_sub.py rename tests/pyosys/{test_script.py => test_ecp5_addsub.py} (99%) create mode 100644 tests/pyosys/test_sigspec_it.py diff --git a/pyosys/generator.py b/pyosys/generator.py index c0ccdac5a..25f87d570 100644 --- a/pyosys/generator.py +++ b/pyosys/generator.py @@ -193,7 +193,7 @@ pyosys_headers = [ ), PyosysClass("SigChunk"), PyosysClass("SigBit", hash_expr="s"), - PyosysClass("SigSpec", hash_expr="s", denylist={"chunks"}), + PyosysClass("SigSpec", hash_expr="s", denylist=frozenset({"chunks"})), PyosysClass( "Cell", ref_only=True, @@ -539,6 +539,8 @@ class PyosysWrapperGenerator(object): python_name_override = "__ne__" elif function.operator == "<": python_name_override = "__lt__" + elif function.operator == "[]" and function.const: + python_name_override = "__getitem__" else: return @@ -592,7 +594,10 @@ class PyosysWrapperGenerator(object): # care return - has_containers = self.register_containers(field) + self.register_containers(field) + rvp = "py::return_value_policy::copy" + if isinstance(field.type, Pointer): + rvp = "py::return_value_policy::reference_internal" definition_fn = f"def_{'readonly' if field.type.const else 'readwrite'}" if field.static: @@ -604,7 +609,7 @@ class PyosysWrapperGenerator(object): f'"{field_python_basename}"', f"&{metadata.name}::{field.name}", ] - def_args.append("py::return_value_policy::copy") + def_args.append(rvp) print( f"\t\t\t.{definition_fn}({', '.join(def_args)})", file=self.f, diff --git a/tests/arch/ecp5/add_sub.py b/tests/arch/ecp5/add_sub.py deleted file mode 100644 index 0232ac1db..000000000 --- a/tests/arch/ecp5/add_sub.py +++ /dev/null @@ -1,20 +0,0 @@ -import os -from pyosys import libyosys as ys - -__dir__ = os.path.dirname(os.path.abspath(__file__)) -add_sub = os.path.join(__dir__, "..", "common", "add_sub.v") - -base = ys.Design() -ys.run_pass(f"read_verilog {add_sub}", base) -ys.run_pass("hierarchy -top top", base) -ys.run_pass("proc", base) -ys.run_pass("equiv_opt -assert -map +/ecp5/cells_sim.v synth_ecp5", base) - -postopt = ys.Design() -ys.run_pass("design -load postopt", postopt) -ys.run_pass("cd top", postopt) -ys.run_pass("select -assert-min 25 t:LUT4", postopt) -ys.run_pass("select -assert-max 26 t:LUT4", postopt) -ys.run_pass("select -assert-count 10 t:PFUMX", postopt) -ys.run_pass("select -assert-count 6 t:L6MUX21", postopt) -ys.run_pass("select -assert-none t:LUT4 t:PFUMX t:L6MUX21 %% t:* %D", postopt) diff --git a/tests/pyosys/test_script.py b/tests/pyosys/test_ecp5_addsub.py similarity index 99% rename from tests/pyosys/test_script.py rename to tests/pyosys/test_ecp5_addsub.py index 7c3ec96ef..ddc50b775 100644 --- a/tests/pyosys/test_script.py +++ b/tests/pyosys/test_ecp5_addsub.py @@ -1,7 +1,6 @@ from pathlib import Path from pyosys import libyosys as ys - __file_dir__ = Path(__file__).absolute().parent add_sub = __file_dir__.parent / "arch" / "common" / "add_sub.v" diff --git a/tests/pyosys/test_sigspec_it.py b/tests/pyosys/test_sigspec_it.py new file mode 100644 index 000000000..2876e7725 --- /dev/null +++ b/tests/pyosys/test_sigspec_it.py @@ -0,0 +1,28 @@ +from pyosys import libyosys as ys +from pathlib import Path + +__file_dir__ = Path(__file__).absolute().parent + +def _dump_sigbit(bit): + if bit.is_wire(): + if bit.wire.width == 1: + return bit.wire.name.str() + else: + return f"{bit.wire.name} [{bit.offset}]" + else: + if bit.data == ys.State.S1: + return 1 + elif bit.data == ys.State.S0: + return 0 + else: + assert "unknown constants not supported" + +d = ys.Design() + +ys.run_pass(f"read_verilog {__file_dir__ / 'spm.cut.v.gz'}", d) +ys.run_pass(f"hierarchy -top spm", d) +module = d.module(r"\spm") +for conn_from, conn_to in module.connections_: + for bit_from, bit_to in zip(conn_from, conn_to): + print(f"assign {_dump_sigbit(bit_from)} = {_dump_sigbit(bit_to)};") + From 6eb9e823e0e080624bc261382418dd3880b0ee45 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 20 Nov 2025 00:21:15 +0100 Subject: [PATCH 44/45] sdc: use Tcl memory management functionality --- passes/cmds/sdc/sdc.cc | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index 17c0f0be6..eb1951665 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -349,20 +349,20 @@ static int getter_graph_node(TclCall call) { // to distinguish resolved and unknown getters. // For example, if call is ["get_foo", "-bar"] // then newCall is ["get_foo", "-getter-validated", "-bar"] - Tcl_Obj* validity_tag = Tcl_NewStringObj("-getter-validated", -1); - Tcl_IncrRefCount(validity_tag); + Tcl_Obj* validity_tag = Tcl_NewStringObj("-getter-validated", -1); + Tcl_IncrRefCount(validity_tag); std::vector newObjv(call.objc + 1); - log_assert(call.objc > 0); + log_assert(call.objc > 0); newObjv[0] = call.objv[0]; - newObjv[1] = validity_tag; - for (int i = 1; i < call.objc; ++i) { - newObjv[i + 1] = call.objv[i]; - } + newObjv[1] = validity_tag; + for (int i = 1; i < call.objc; ++i) { + newObjv[i + 1] = call.objv[i]; + } // Send the vector to the Tcl land - Tcl_Obj** allocatedObjv = new Tcl_Obj*[call.objc + 1]; - for (int i = 0; i < call.objc + 1; ++i) { - allocatedObjv[i] = newObjv[i]; - } + Tcl_Obj** allocatedObjv = (Tcl_Obj**)Tcl_Alloc((call.objc + 1) * sizeof(Tcl_Obj*)); + for (int i = 0; i < call.objc + 1; ++i) { + allocatedObjv[i] = newObjv[i]; + } TclCall newCall { .interp = call.interp, .objc = call.objc + 1, @@ -376,14 +376,14 @@ static int redirect_unknown(TclCall call) { // TODO redirect to different command Tcl_Obj *newCmd = Tcl_NewStringObj("unknown", -1); auto newObjc = call.objc + 1; - Tcl_Obj **newObjv = new Tcl_Obj*[newObjc]; + Tcl_Obj** newObjv = (Tcl_Obj**)Tcl_Alloc(newObjc * sizeof(Tcl_Obj*)); newObjv[0] = newCmd; for (int i = 1; i < newObjc; i++) { newObjv[i] = call.objv[i - 1]; } int result = Tcl_EvalObjv(call.interp, newObjc, newObjv, 0); Tcl_DecrRefCount(newCmd); - delete[] newObjv; + Tcl_Free((char*) newObjv); return result; } @@ -551,7 +551,7 @@ struct TclOpts { std::string expected = std::string("-") + opt_name; if (expected == arg) { if (!std::find_if(legals.begin(), legals.end(), - [&opt_name](const char* str) { return opt_name == str; })) + [&opt_name](const char* str) { return opt_name == str; })) log_cmd_error("Illegal argument %s for %s.\n", expected.c_str(), name); return true; } From 25ba41f42419c2ee6f0499d5c74bf0c6c56e0b63 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 00:24:25 +0000 Subject: [PATCH 45/45] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c16dbf175..0c673cdf8 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+62 +YOSYS_VER := 0.59+110 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2)