diff --git a/.github/workflows/test-verific.yml b/.github/workflows/test-verific.yml index 503d6c31b..6619e1124 100644 --- a/.github/workflows/test-verific.yml +++ b/.github/workflows/test-verific.yml @@ -83,3 +83,42 @@ jobs: shell: bash run: | make -j$procs unit-test ENABLE_LTO=1 ENABLE_LIBYOSYS=1 + + test-pyosys: + needs: pre-job + if: ${{ needs.pre-job.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }} + runs-on: [self-hosted, linux, x64, fast] + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + persist-credentials: false + submodules: true + - name: Install UV + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + - name: Runtime environment + run: | + echo "procs=$(nproc)" >> $GITHUB_ENV + echo "${{ github.workspace }}/.local/bin" >> $GITHUB_PATH + + - name: Build pyosys + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 1" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + echo "ENABLE_PYOSYS := 1" >> Makefile.conf + echo "PYTHON_DESTDIR := /usr/lib/python3/site-packages" >> Makefile.conf + make -j$procs + + - name: Install pyosys + run: | + make install DESTDIR=${GITHUB_WORKSPACE}/.local PREFIX= + + - name: Run pyosys tests + run: | + export PYTHONPATH=${GITHUB_WORKSPACE}/.local/usr/lib/python3/site-packages:$PYTHONPATH + python3 tests/pyosys/run_tests.py diff --git a/CHANGELOG b/CHANGELOG index 6cefcc3ac..69f8ab1ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,9 +2,20 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.59 .. Yosys 0.60-dev +Yosys 0.60 .. Yosys 0.61-dev -------------------------- +Yosys 0.59 .. Yosys 0.60 +-------------------------- + * Various + - read_verilog: suport unsized parameters. + - Added static library compile option. + + * New commands and options + - Added "sdc" pass for reading SDC files. + - Added experimental "sdc_expand" and "opensta" for OpenSTA integration. + - Added "icell_liberty" pass for used internal cells. + Yosys 0.58 .. Yosys 0.59 -------------------------- * Various diff --git a/Makefile b/Makefile index 09dcd0639..15aaf870c 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ ENABLE_VERIFIC_EDIF := 0 ENABLE_VERIFIC_LIBERTY := 0 ENABLE_COVER := 1 ENABLE_LIBYOSYS := 0 +ENABLE_LIBYOSYS_STATIC := 0 ENABLE_ZLIB := 1 ENABLE_HELP_SOURCE := 0 @@ -161,7 +162,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+0 +YOSYS_VER := 0.60+0 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) @@ -184,7 +185,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 03eb220.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 5bafeb7.. | wc -l`/;" Makefile ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q) @@ -342,6 +343,9 @@ endif ifeq ($(ENABLE_LIBYOSYS),1) TARGETS += libyosys.so +ifeq ($(ENABLE_LIBYOSYS_STATIC),1) +TARGETS += libyosys.a +endif endif PY_WRAPPER_FILE = pyosys/wrappers @@ -474,6 +478,9 @@ else ifeq ($(ABCEXTERNAL),) TARGETS := $(PROGRAM_PREFIX)yosys-abc$(EXE) $(TARGETS) endif +ifeq ($(DISABLE_SPAWN),1) +$(error ENABLE_ABC=1 requires either LINK_ABC=1 or DISABLE_SPAWN=0) +endif endif endif @@ -772,6 +779,9 @@ else $(P) $(CXX) -o libyosys.so -shared -Wl,-soname,libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) endif +libyosys.a: $(filter-out kernel/driver.o,$(OBJS)) + $(P) $(AR) rcs $@ $^ + %.o: %.cc $(Q) mkdir -p $(dir $@) $(P) $(CXX) -o $@ -c $(CPPFLAGS) $(CXXFLAGS) $< @@ -902,6 +912,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 @@ -1022,7 +1033,7 @@ install-dev: $(PROGRAM_PREFIX)yosys-config share install: $(TARGETS) $(EXTRA_TARGETS) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(BINDIR) - $(INSTALL_SUDO) cp $(filter-out libyosys.so,$(TARGETS)) $(DESTDIR)$(BINDIR) + $(INSTALL_SUDO) cp $(filter-out libyosys.so libyosys.a,$(TARGETS)) $(DESTDIR)$(BINDIR) ifneq ($(filter $(PROGRAM_PREFIX)yosys,$(TARGETS)),) if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys; fi endif @@ -1038,9 +1049,12 @@ ifeq ($(ENABLE_LIBYOSYS),1) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(LIBDIR) $(INSTALL_SUDO) cp libyosys.so $(DESTDIR)$(LIBDIR)/ if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(LIBDIR)/libyosys.so; fi +ifeq ($(ENABLE_LIBYOSYS_STATIC),1) + $(INSTALL_SUDO) cp libyosys.a $(DESTDIR)$(LIBDIR)/ +endif ifeq ($(ENABLE_PYOSYS),1) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys - $(INSTALL_SUDO) cp pyosys/__init__.py $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py + $(INSTALL_SUDO) cp $(YOSYS_SRC)/pyosys/__init__.py $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py $(INSTALL_SUDO) cp libyosys.so $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/libyosys.so $(INSTALL_SUDO) cp -r share $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys ifeq ($(ENABLE_ABC),1) @@ -1060,6 +1074,9 @@ uninstall: $(INSTALL_SUDO) rm -rvf $(DESTDIR)$(DATDIR) ifeq ($(ENABLE_LIBYOSYS),1) $(INSTALL_SUDO) rm -vf $(DESTDIR)$(LIBDIR)/libyosys.so +ifeq ($(ENABLE_LIBYOSYS_STATIC),1) + $(INSTALL_SUDO) rm -vf $(DESTDIR)$(LIBDIR)/libyosys.a +endif ifeq ($(ENABLE_PYOSYS),1) $(INSTALL_SUDO) rm -vf $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/libyosys.so $(INSTALL_SUDO) rm -vf $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py @@ -1158,7 +1175,7 @@ clean-py: rm -f $(PY_WRAPPER_FILE).inc.cc $(PY_WRAPPER_FILE).cc rm -f $(PYTHON_OBJECTS) rm -f *.whl - rm -f libyosys.so + rm -f libyosys.so libyosys.a rm -rf kernel/*.pyh clean-abc: diff --git a/abc b/abc index 1c5ed1ce3..131a50dd7 160000 --- a/abc +++ b/abc @@ -1 +1 @@ -Subproject commit 1c5ed1ce378cc04beac30bb31abc4c37c8467042 +Subproject commit 131a50dd773f21ebbfc51da1d182438382a04209 diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 8abfe3e41..73e1b48c6 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -188,20 +188,27 @@ struct SmtrModule { Functional::IR ir; SmtrScope scope; std::string name; - + bool use_assoc_list_helpers; + std::optional input_helper_name; + std::optional output_helper_name; + SmtrStruct input_struct; SmtrStruct output_struct; SmtrStruct state_struct; - SmtrModule(Module *module) - : ir(Functional::IR::from_module(module)) - , scope() - , name(scope.unique_name(module->name)) - , input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope) - , output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope) - , state_struct(scope.unique_name(module->name.str() + "_State"), scope) + SmtrModule(Module *module, bool assoc_list_helpers) + : ir(Functional::IR::from_module(module)), scope(), name(scope.unique_name(module->name)), use_assoc_list_helpers(assoc_list_helpers), + input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope), + output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope), + state_struct(scope.unique_name(module->name.str() + "_State"), scope) { scope.reserve(name + "_initial"); + if (assoc_list_helpers) { + input_helper_name = scope.unique_name(module->name.str() + "_inputs_helper"); + scope.reserve(*input_helper_name); + output_helper_name = scope.unique_name(module->name.str() + "_outputs_helper"); + scope.reserve(*output_helper_name); + } for (auto input : ir.inputs()) input_struct.insert(input->name, input->sort); for (auto output : ir.outputs()) @@ -257,6 +264,45 @@ struct SmtrModule { w.pop(); } + void write_assoc_list_helpers(SExprWriter &w) + { + log_assert(output_helper_name && input_helper_name); + + // Input struct keyword-based constructor. + w.push(); + w.open(list("define")); + const auto inputs_name = "inputs"; + w.open(list(*input_helper_name, inputs_name)); + w.close(); + w.open(list(input_struct.name)); + for (auto input : ir.inputs()) { + w.push(); + w.open(list("let")); + w.push(); + w.open(list()); + w.open(list("assoc-result")); + w << list("assoc", "\"" + RTLIL::unescape_id(input->name) + "\"", inputs_name); + w.pop(); + w.open(list("if", "assoc-result")); + w << list("cdr", "assoc-result"); + w.open(list("begin")); + w << list("fprintf", list("current-error-port"), "\"%s not found in inputs\""); + w << "'not-found"; + w.pop(); + } + w.pop(); + // Output struct keyword-based destructuring + w.push(); + w.open(list("define")); + const auto outputs_name = "outputs"; + w << list(*output_helper_name, outputs_name); + w.open(list("list")); + for (auto output : ir.outputs()) { + w << list("cons", "\"" + RTLIL::unescape_id(output->name) + "\"", output_struct.access("outputs", output->name)); + } + w.pop(); + } + void write(std::ostream &out) { SExprWriter w(out); @@ -265,6 +311,10 @@ struct SmtrModule { output_struct.write_definition(w); state_struct.write_definition(w); + if (use_assoc_list_helpers) { + write_assoc_list_helpers(w); + } + write_eval(w); write_initial(w); } @@ -282,12 +332,16 @@ struct FunctionalSmtrBackend : public Backend { log("\n"); log(" -provides\n"); log(" include 'provide' statement(s) for loading output as a module\n"); + log(" -assoc-list-helpers\n"); + log(" provide helper functions which convert inputs/outputs from/to association lists\n"); + log(" \n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { auto provides = false; + auto assoc_list_helpers = false; log_header(design, "Executing Functional Rosette Backend.\n"); @@ -296,6 +350,8 @@ struct FunctionalSmtrBackend : public Backend { { if (args[argidx] == "-provides") provides = true; + else if (args[argidx] == "-assoc-list-helpers") + assoc_list_helpers = true; else break; } @@ -307,8 +363,8 @@ struct FunctionalSmtrBackend : public Backend { } for (auto module : design->selected_modules()) { - log("Processing module `%s`.\n", module->name); - SmtrModule smtr(module); + log("Processing module `%s`.\n", module->name.c_str()); + SmtrModule smtr(module, assoc_list_helpers); smtr.write(*f); } } diff --git a/backends/rtlil/rtlil_backend.cc b/backends/rtlil/rtlil_backend.cc index 057edc584..d47bebf82 100644 --- a/backends/rtlil/rtlil_backend.cc +++ b/backends/rtlil/rtlil_backend.cc @@ -61,7 +61,9 @@ void RTLIL_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int wi return; } } - f << stringf("%d'", width); + if ((data.flags & RTLIL::CONST_FLAG_UNSIZED) == 0) { + f << stringf("%d'", width); + } if (data.flags & RTLIL::CONST_FLAG_SIGNED) { f << stringf("s"); } @@ -173,9 +175,10 @@ void RTLIL_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL:: dump_attributes(f, indent, cell); f << stringf("%s" "cell %s %s\n", indent, cell->type, cell->name); for (const auto& [name, param] : reversed(cell->parameters)) { - f << stringf("%s parameter%s%s %s ", indent, + f << stringf("%s parameter%s%s%s %s ", indent, (param.flags & RTLIL::CONST_FLAG_SIGNED) != 0 ? " signed" : "", (param.flags & RTLIL::CONST_FLAG_REAL) != 0 ? " real" : "", + (param.flags & RTLIL::CONST_FLAG_UNSIZED) != 0 ? " unsized" : "", name); dump_const(f, param); f << stringf("\n"); diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index faeb2cd0b..8d77160fd 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -108,22 +108,30 @@ IdString initial_id; void reset_auto_counter_id(RTLIL::IdString id, bool may_rename) { - const char *str = id.c_str(); - - if (*str == '$' && may_rename && !norename) - auto_name_map[id] = auto_name_counter++; - - if (str[0] != '\\' || str[1] != '_' || str[2] == 0) + auto it = id.begin(); + auto it_end = id.end(); + if (it == it_end) return; - for (int i = 2; str[i] != 0; i++) { - if (str[i] == '_' && str[i+1] == 0) - continue; - if (str[i] < '0' || str[i] > '9') + if (*it == '$' && may_rename && !norename) + auto_name_map[id] = auto_name_counter++; + + if (*it != '\\' || (it + 1) == it_end || *(it + 1) != '_' || (it + 2) == it_end) + return; + + std::string s; + it += 2; + while (it != it_end) { + char ch = *it; + if (ch == '_' && (it + 1) == it_end) + break; + if (ch < '0' || ch > '9') return; + s.push_back(ch); + ++it; } - int num = atoi(str+2); + int num = atoi(s.c_str()); if (num >= auto_name_offset) auto_name_offset = num + 1; } diff --git a/docs/source/conf.py b/docs/source/conf.py index fb106bddb..01bb620ea 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,7 +6,7 @@ import os project = 'YosysHQ Yosys' author = 'YosysHQ GmbH' copyright ='2025 YosysHQ GmbH' -yosys_ver = "0.59" +yosys_ver = "0.60" # select HTML theme html_theme = 'furo-ys' diff --git a/frontends/aiger2/xaiger.cc b/frontends/aiger2/xaiger.cc index d983f8c41..510da0be8 100644 --- a/frontends/aiger2/xaiger.cc +++ b/frontends/aiger2/xaiger.cc @@ -110,7 +110,8 @@ struct Xaiger2Frontend : public Frontend { for (int i = 0; i < (int) O; i++) { int po; *f >> po; - log_assert(f->get() == '\n'); + int c = f->get(); + log_assert(c == '\n'); outputs.push_back(po); } diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 4a16abee9..984c4294c 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -993,6 +993,8 @@ RTLIL::Const AstNode::asParaConst() const RTLIL::Const val = asAttrConst(); if (is_signed) val.flags |= RTLIL::CONST_FLAG_SIGNED; + if (is_unsized) + val.flags |= RTLIL::CONST_FLAG_UNSIZED; return val; } @@ -1766,7 +1768,10 @@ static std::string serialize_param_value(const RTLIL::Const &val) { res.push_back('s'); if (val.flags & RTLIL::ConstFlags::CONST_FLAG_REAL) res.push_back('r'); - res += stringf("%d", GetSize(val)); + if (val.flags & RTLIL::ConstFlags::CONST_FLAG_UNSIZED) + res.push_back('u'); + else + res += stringf("%d", GetSize(val)); res.push_back('\''); res.append(val.as_string("?")); return res; @@ -1860,7 +1865,7 @@ std::string AstModule::derive_common(RTLIL::Design *design, const dictsecond.flags & RTLIL::CONST_FLAG_STRING) != 0) child->children[0] = AstNode::mkconst_str(loc, it->second.decode_string()); else - child->children[0] = AstNode::mkconst_bits(loc, it->second.to_bits(), (it->second.flags & RTLIL::CONST_FLAG_SIGNED) != 0); + child->children[0] = AstNode::mkconst_bits(loc, it->second.to_bits(), (it->second.flags & RTLIL::CONST_FLAG_SIGNED) != 0, (it->second.flags & RTLIL::CONST_FLAG_UNSIZED) != 0); rewritten.insert(it->first); } diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 308406591..83174e963 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -2265,9 +2265,13 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin } if (children[0]->type == AST_CONSTANT) { if (width != int(children[0]->bits.size())) { - RTLIL::SigSpec sig(children[0]->bits); - sig.extend_u0(width, children[0]->is_signed); - children[0] = mkconst_bits(location, sig.as_const().to_bits(), is_signed); + RTLIL::Const val; + if (children[0]->is_unsized) { + val = children[0]->bitsAsUnsizedConst(width); + } else { + val = children[0]->bitsAsConst(width); + } + children[0] = mkconst_bits(location, val.to_bits(), is_signed); fixup_hierarchy_flags(); } children[0]->is_signed = is_signed; diff --git a/frontends/blif/blifparse.cc b/frontends/blif/blifparse.cc index 0aa3a173d..bff347ea2 100644 --- a/frontends/blif/blifparse.cc +++ b/frontends/blif/blifparse.cc @@ -245,7 +245,7 @@ void parse_blif(RTLIL::Design *design, std::istream &f, IdString dff_name, bool if (undef_wire != nullptr) module->rename(undef_wire, stringf("$undef$%d", ++blif_maxnum)); - autoidx = std::max(autoidx, blif_maxnum+1); + autoidx.ensure_at_least(blif_maxnum+1); blif_maxnum = 0; } diff --git a/frontends/liberty/liberty.cc b/frontends/liberty/liberty.cc index 80553347c..0aa1cee09 100644 --- a/frontends/liberty/liberty.cc +++ b/frontends/liberty/liberty.cc @@ -40,14 +40,14 @@ static RTLIL::SigSpec parse_func_identifier(RTLIL::Module *module, const char *& expr[id_len] == '_' || expr[id_len] == '[' || expr[id_len] == ']') id_len++; if (id_len == 0) - log_error("Expected identifier at `%s'.\n", expr); + log_error("Expected identifier at `%s' in %s.\n", expr, RTLIL::unescape_id(module->name)); if (id_len == 1 && (*expr == '0' || *expr == '1')) return *(expr++) == '0' ? RTLIL::State::S0 : RTLIL::State::S1; std::string id = RTLIL::escape_id(std::string(expr, id_len)); if (!module->wires_.count(id)) - log_error("Can't resolve wire name %s.\n", RTLIL::unescape_id(id)); + log_error("Can't resolve wire name %s in %s.\n", RTLIL::unescape_id(id), RTLIL::unescape_id(module->name)); expr += id_len; return module->wires_.at(id); @@ -174,7 +174,7 @@ static RTLIL::SigSpec parse_func_expr(RTLIL::Module *module, const char *expr) #endif if (stack.size() != 1 || stack.back().type != 3) - log_error("Parser error in function expr `%s'.\n", orig_expr); + log_error("Parser error in function expr `%s'in %s.\n", orig_expr, RTLIL::unescape_id(module->name)); return stack.back().sig; } @@ -191,11 +191,23 @@ static RTLIL::SigSpec create_tristate(RTLIL::Module *module, RTLIL::SigSpec func return cell->getPort(ID::Y); } +static void create_latch_ff_wires(RTLIL::Module *module, const LibertyAst *node) +{ + module->addWire(RTLIL::escape_id(node->args.at(0))); + module->addWire(RTLIL::escape_id(node->args.at(1))); +} + +static std::pair find_latch_ff_wires(RTLIL::Module *module, const LibertyAst *node) +{ + auto* iq_wire = module->wire(RTLIL::escape_id(node->args.at(0))); + auto* iqn_wire = module->wire(RTLIL::escape_id(node->args.at(1))); + log_assert(iq_wire && iqn_wire); + return std::make_pair(iq_wire, iqn_wire); +} + static void create_ff(RTLIL::Module *module, const LibertyAst *node) { - RTLIL::SigSpec iq_sig(module->addWire(RTLIL::escape_id(node->args.at(0)))); - RTLIL::SigSpec iqn_sig(module->addWire(RTLIL::escape_id(node->args.at(1)))); - + auto [iq_sig, iqn_sig] = find_latch_ff_wires(module, node); RTLIL::SigSpec clk_sig, data_sig, clear_sig, preset_sig; bool clk_polarity = true, clear_polarity = true, preset_polarity = true; @@ -211,7 +223,7 @@ static void create_ff(RTLIL::Module *module, const LibertyAst *node) } if (clk_sig.size() == 0 || data_sig.size() == 0) - log_error("FF cell %s has no next_state and/or clocked_on attribute.\n", log_id(module->name)); + log_error("FF cell %s has no next_state and/or clocked_on attribute.\n", RTLIL::unescape_id(module->name)); for (bool rerun_invert_rollback = true; rerun_invert_rollback;) { @@ -270,9 +282,7 @@ static void create_ff(RTLIL::Module *module, const LibertyAst *node) static bool create_latch(RTLIL::Module *module, const LibertyAst *node, bool flag_ignore_miss_data_latch) { - RTLIL::SigSpec iq_sig(module->addWire(RTLIL::escape_id(node->args.at(0)))); - RTLIL::SigSpec iqn_sig(module->addWire(RTLIL::escape_id(node->args.at(1)))); - + auto [iq_sig, iqn_sig] = find_latch_ff_wires(module, node); RTLIL::SigSpec enable_sig, data_sig, clear_sig, preset_sig; bool enable_polarity = true, clear_polarity = true, preset_polarity = true; @@ -289,9 +299,9 @@ static bool create_latch(RTLIL::Module *module, const LibertyAst *node, bool fla if (enable_sig.size() == 0 || data_sig.size() == 0) { if (!flag_ignore_miss_data_latch) - log_error("Latch cell %s has no data_in and/or enable attribute.\n", log_id(module->name)); + log_error("Latch cell %s has no data_in and/or enable attribute.\n", RTLIL::unescape_id(module->name)); else - log("Ignored latch cell %s with no data_in and/or enable attribute.\n", log_id(module->name)); + log("Ignored latch cell %s with no data_in and/or enable attribute.\n", RTLIL::unescape_id(module->name)); return false; } @@ -582,9 +592,9 @@ struct LibertyFrontend : public Frontend { { if (!flag_ignore_miss_dir) { - log_error("Missing or invalid direction for pin %s on cell %s.\n", node->args.at(0), log_id(module->name)); + log_error("Missing or invalid direction for pin %s on cell %s.\n", node->args.at(0), RTLIL::unescape_id(module->name)); } else { - log("Ignoring cell %s with missing or invalid direction for pin %s.\n", log_id(module->name), node->args.at(0)); + log("Ignoring cell %s with missing or invalid direction for pin %s.\n", RTLIL::unescape_id(module->name), node->args.at(0)); delete module; goto skip_cell; } @@ -596,13 +606,13 @@ struct LibertyFrontend : public Frontend { if (node->id == "bus" && node->args.size() == 1) { if (flag_ignore_buses) { - log("Ignoring cell %s with a bus interface %s.\n", log_id(module->name), node->args.at(0)); + log("Ignoring cell %s with a bus interface %s.\n", RTLIL::unescape_id(module->name), node->args.at(0)); delete module; goto skip_cell; } if (!flag_lib) - log_error("Error in cell %s: bus interfaces are only supported in -lib mode.\n", log_id(cell_name)); + log_error("Error in cell %s: bus interfaces are only supported in -lib mode.\n", RTLIL::unescape_id(cell_name)); const LibertyAst *dir = node->find("direction"); @@ -613,7 +623,7 @@ struct LibertyFrontend : public Frontend { } if (!dir || (dir->value != "input" && dir->value != "output" && dir->value != "inout" && dir->value != "internal")) - log_error("Missing or invalid direction for bus %s on cell %s.\n", node->args.at(0), log_id(module->name)); + log_error("Missing or invalid direction for bus %s on cell %s.\n", node->args.at(0), RTLIL::unescape_id(module->name)); simple_comb_cell = false; @@ -624,7 +634,7 @@ struct LibertyFrontend : public Frontend { if (!bus_type_node || !type_map.count(bus_type_node->value)) log_error("Unknown or unsupported type for bus interface %s on cell %s.\n", - node->args.at(0).c_str(), log_id(cell_name)); + node->args.at(0).c_str(), RTLIL::unescape_id(cell_name)); int bus_type_width = std::get<0>(type_map.at(bus_type_node->value)); int bus_type_offset = std::get<1>(type_map.at(bus_type_node->value)); @@ -646,6 +656,13 @@ struct LibertyFrontend : public Frontend { { // some liberty files do not put ff/latch at the beginning of a cell // try to find "ff" or "latch" and create FF/latch _before_ processing all other nodes + // but first, in case of balloon retention cells, we need all ff/latch output wires + // defined before we add ff/latch cells + for (auto node : cell->children) + { + if ((node->id == "ff" && node->args.size() == 2) || (node->id == "latch" && node->args.size() == 2)) + create_latch_ff_wires(module, node); + } for (auto node : cell->children) { if (node->id == "ff" && node->args.size() == 2) @@ -701,9 +718,9 @@ struct LibertyFrontend : public Frontend { if (dir->value != "inout") { // allow inout with missing function, can be used for power pins if (!flag_ignore_miss_func) { - log_error("Missing function on output %s of cell %s.\n", log_id(wire->name), log_id(module->name)); + log_error("Missing function on output %s of cell %s.\n", RTLIL::unescape_id(wire->name), RTLIL::unescape_id(module->name)); } else { - log("Ignoring cell %s with missing function on output %s.\n", log_id(module->name), log_id(wire->name)); + log("Ignoring cell %s with missing function on output %s.\n", RTLIL::unescape_id(module->name), RTLIL::unescape_id(wire->name)); delete module; goto skip_cell; } @@ -757,13 +774,13 @@ struct LibertyFrontend : public Frontend { if (design->has(cell_name)) { Module *existing_mod = design->module(cell_name); if (!flag_nooverwrite && !flag_overwrite && !existing_mod->get_bool_attribute(ID::blackbox)) { - log_error("Re-definition of cell/module %s!\n", log_id(cell_name)); + log_error("Re-definition of cell/module %s!\n", RTLIL::unescape_id(cell_name)); } else if (flag_nooverwrite) { - log("Ignoring re-definition of module %s.\n", log_id(cell_name)); + log("Ignoring re-definition of module %s.\n", RTLIL::unescape_id(cell_name)); delete module; goto skip_cell; } else { - log("Replacing existing%s module %s.\n", existing_mod->get_bool_attribute(ID::blackbox) ? " blackbox" : "", log_id(cell_name)); + log("Replacing existing%s module %s.\n", existing_mod->get_bool_attribute(ID::blackbox) ? " blackbox" : "", RTLIL::unescape_id(cell_name)); design->remove(existing_mod); } } diff --git a/frontends/rtlil/rtlil_frontend.cc b/frontends/rtlil/rtlil_frontend.cc index 04d01fc93..271962725 100644 --- a/frontends/rtlil/rtlil_frontend.cc +++ b/frontends/rtlil/rtlil_frontend.cc @@ -567,10 +567,13 @@ struct RTLILFrontendWorker { if (try_parse_keyword("parameter")) { bool is_signed = false; bool is_real = false; + bool is_unsized = false; if (try_parse_keyword("signed")) { is_signed = true; } else if (try_parse_keyword("real")) { is_real = true; + } else if (try_parse_keyword("unsized")) { + is_unsized = true; } RTLIL::IdString param_name = parse_id(); RTLIL::Const val = parse_const(); @@ -578,6 +581,8 @@ struct RTLILFrontendWorker { val.flags |= RTLIL::CONST_FLAG_SIGNED; if (is_real) val.flags |= RTLIL::CONST_FLAG_REAL; + if (is_unsized) + val.flags |= RTLIL::CONST_FLAG_UNSIZED; cell->parameters.insert({std::move(param_name), std::move(val)}); expect_eol(); } else if (try_parse_keyword("connect")) { diff --git a/kernel/constids.inc b/kernel/constids.inc index 7aed897e1..c99aa788d 100644 --- a/kernel/constids.inc +++ b/kernel/constids.inc @@ -196,6 +196,7 @@ X($bweqx) X($bwmux) X($check) X($concat) +X($connect) X($cover) X($demux) X($dff) @@ -222,6 +223,7 @@ X($get_tag) X($gt) X($initstate) X($input) +X($input_port) X($lcu) X($le) X($live) diff --git a/kernel/driver.cc b/kernel/driver.cc index 795fd9fc5..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 @@ -706,9 +706,16 @@ int main(int argc, char **argv) for (auto &it : pass_register) if (it.second->call_counter) { - total_ns += it.second->runtime_ns + 1; - timedat.insert(make_tuple(it.second->runtime_ns + 1, it.second->call_counter, it.first)); + auto pass_ns = it.second->runtime_ns + 1; + total_ns += pass_ns; + timedat.insert(make_tuple(pass_ns, it.second->call_counter, it.first)); } + { + auto gc_ns = RTLIL::OwningIdString::garbage_collection_ns() + 1; + total_ns += gc_ns; + timedat.insert(make_tuple(gc_ns, + RTLIL::OwningIdString::garbage_collection_count(), "id_gc")); + } if (timing_details) { 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; diff --git a/kernel/io.cc b/kernel/io.cc index e9801f63e..4f805e43b 100644 --- a/kernel/io.cc +++ b/kernel/io.cc @@ -606,7 +606,7 @@ void format_emit_idstring(std::string &result, std::string_view spec, int *dynam { if (spec == "%s") { // Format checking will have guaranteed num_dynamic_ints == 0. - result += arg.c_str(); + arg.append_to(&result); return; } format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg.c_str()); diff --git a/kernel/log.cc b/kernel/log.cc index 34e56f8ac..d712eda2c 100644 --- a/kernel/log.cc +++ b/kernel/log.cc @@ -720,7 +720,7 @@ dict> get_coverage_data() if (coverage_data.count(p->id)) log_warning("found duplicate coverage id \"%s\".\n", p->id); coverage_data[p->id].first = stringf("%s:%d:%s", p->file, p->line, p->func); - coverage_data[p->id].second += p->counter; + coverage_data[p->id].second += p->counter.load(std::memory_order_relaxed); } for (auto &it : coverage_data) diff --git a/kernel/log.h b/kernel/log.h index 144570026..197cfab8d 100644 --- a/kernel/log.h +++ b/kernel/log.h @@ -24,6 +24,7 @@ #include +#include #include #define YS_REGEX_COMPILE(param) std::regex(param, \ std::regex_constants::nosubs | \ @@ -298,15 +299,16 @@ void log_abort_internal(const char *file, int line); #define cover(_id) do { \ static CoverData __d __attribute__((section("yosys_cover_list"), aligned(1), used)) = { __FILE__, __FUNCTION__, _id, __LINE__, 0 }; \ - __d.counter++; \ + __d.counter.fetch_add(1, std::memory_order_relaxed); \ } while (0) struct CoverData { const char *file, *func, *id; - int line, counter; -} YS_ATTRIBUTE(packed); + int line; + std::atomic counter; +}; -// this two symbols are created by the linker for the "yosys_cover_list" ELF section +// this two symbols are created by the linker __start_yosys_cover_listfor the "yosys_cover_list" ELF section extern "C" struct CoverData __start_yosys_cover_list[]; extern "C" struct CoverData __stop_yosys_cover_list[]; diff --git a/kernel/register.cc b/kernel/register.cc index bd12dcc38..3f5aa49ca 100644 --- a/kernel/register.cc +++ b/kernel/register.cc @@ -42,6 +42,23 @@ std::map backend_register; std::vector Frontend::next_args; +bool GarbageCollectionGuard::is_enabled_ = true; + +static bool garbage_collection_requested = false; + +void request_garbage_collection() +{ + garbage_collection_requested = true; +} + +void try_collect_garbage() +{ + if (!GarbageCollectionGuard::is_enabled() || !garbage_collection_requested) + return; + garbage_collection_requested = false; + RTLIL::OwningIdString::collect_garbage(); +} + Pass::Pass(std::string name, std::string short_help, source_location location) : pass_name(name), short_help(short_help), location(location) { @@ -112,6 +129,11 @@ void Pass::post_execute(Pass::pre_post_exec_state_t state) int64_t time_ns = PerformanceTimer::query() - state.begin_ns; runtime_ns += time_ns; current_pass = state.parent_pass; + subtract_from_current_runtime_ns(time_ns); +} + +void Pass::subtract_from_current_runtime_ns(int64_t time_ns) +{ if (current_pass) current_pass->runtime_ns -= time_ns; } @@ -263,14 +285,19 @@ void Pass::call(RTLIL::Design *design, std::vector args) if (pass_register.count(args[0]) == 0) log_cmd_error("No such command: %s (type 'help' for a command overview)\n", args[0]); + Pass *pass = pass_register[args[0]]; - if (pass_register[args[0]]->experimental_flag) + // Collect garbage before the next pass if requested. No need to collect garbage after the last pass. + try_collect_garbage(); + GarbageCollectionGuard gc_guard(pass->allow_garbage_collection_during_pass()); + + if (pass->experimental_flag) log_experimental(args[0]); size_t orig_sel_stack_pos = design->selection_stack.size(); - auto state = pass_register[args[0]]->pre_execute(); - pass_register[args[0]]->execute(args, design); - pass_register[args[0]]->post_execute(state); + auto state = pass->pre_execute(); + pass->execute(args, design); + pass->post_execute(state); while (design->selection_stack.size() > orig_sel_stack_pos) design->pop_selection(); } diff --git a/kernel/register.h b/kernel/register.h index 534cfbc28..78f9b430d 100644 --- a/kernel/register.h +++ b/kernel/register.h @@ -50,6 +50,30 @@ struct source_location { // dummy placeholder YOSYS_NAMESPACE_BEGIN +// Track whether garbage collection is enabled. Garbage collection must be disabled +// while any RTLIL objects (e.g. non-owning non-immortal IdStrings) exist outside Designs. +// Garbage collection is disabled whenever any GarbageCollectionGuard(false) is on the +// stack. These objects must be stack-allocated on the main thread. +class GarbageCollectionGuard +{ + bool was_enabled; + static bool is_enabled_; +public: + GarbageCollectionGuard(bool allow) : was_enabled(is_enabled_) { + is_enabled_ &= allow; + } + ~GarbageCollectionGuard() { + is_enabled_ = was_enabled; + } + static bool is_enabled() { return is_enabled_; } +}; + +// Call from anywhere to request GC at the next safe point. +void request_garbage_collection(); + +// GC if GarbageCollectionGuard::is_enabled() and GC was requested. +void try_collect_garbage(); + struct Pass { std::string pass_name, short_help; @@ -71,6 +95,8 @@ struct Pass bool experimental_flag = false; bool internal_flag = false; + static void subtract_from_current_runtime_ns(int64_t time_ns); + void experimental() { experimental_flag = true; } @@ -108,6 +134,10 @@ struct Pass virtual void on_register(); virtual void on_shutdown(); virtual bool replace_existing_pass() const { return false; } + + // This should return false if the pass holds onto RTLIL objects outside a Design while it + // calls nested passes. For safety, we default to assuming the worst. + virtual bool allow_garbage_collection_during_pass() const { return false; } }; struct ScriptPass : Pass @@ -126,6 +156,8 @@ struct ScriptPass : Pass void run_nocheck(std::string command, std::string info = std::string()); void run_script(RTLIL::Design *design, std::string run_from = std::string(), std::string run_to = std::string()); void help_script(); + + bool allow_garbage_collection_during_pass() const override { return true; } }; struct Frontend : Pass diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index a58487216..6960b7620 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -35,20 +36,11 @@ YOSYS_NAMESPACE_BEGIN bool RTLIL::IdString::destruct_guard_ok = false; RTLIL::IdString::destruct_guard_t RTLIL::IdString::destruct_guard; -std::vector RTLIL::IdString::global_id_storage_; +std::vector RTLIL::IdString::global_id_storage_; std::unordered_map RTLIL::IdString::global_id_index_; -#ifndef YOSYS_NO_IDS_REFCNT -std::vector RTLIL::IdString::global_refcount_storage_; +std::unordered_map RTLIL::IdString::global_autoidx_id_storage_; +std::unordered_map RTLIL::IdString::global_refcount_storage_; std::vector RTLIL::IdString::global_free_idx_list_; -#endif -#ifdef YOSYS_USE_STICKY_IDS -int RTLIL::IdString::last_created_idx_[8]; -int RTLIL::IdString::last_created_idx_ptr_; -#endif - -#define X(_id) const RTLIL::IdString RTLIL::IDInternal::_id(RTLIL::StaticId::_id); -#include "kernel/constids.inc" -#undef X static void populate(std::string_view name) { @@ -57,21 +49,85 @@ static void populate(std::string_view name) name = name.substr(1); } RTLIL::IdString::global_id_index_.insert({name, GetSize(RTLIL::IdString::global_id_storage_)}); - RTLIL::IdString::global_id_storage_.push_back(const_cast(name.data())); + RTLIL::IdString::global_id_storage_.push_back({const_cast(name.data()), GetSize(name)}); } void RTLIL::IdString::prepopulate() { int size = static_cast(RTLIL::StaticId::STATIC_ID_END); global_id_storage_.reserve(size); - RTLIL::IdString::global_id_storage_.push_back(const_cast("")); global_id_index_.reserve(size); - global_refcount_storage_.resize(size, 1); + RTLIL::IdString::global_id_index_.insert({"", 0}); + RTLIL::IdString::global_id_storage_.push_back({const_cast(""), 0}); #define X(N) populate("\\" #N); #include "kernel/constids.inc" #undef X } +static std::optional parse_autoidx(std::string_view v) +{ + // autoidx values can never be <= 0, so there can never be a leading 0 digit. + if (v.empty() || v[0] == '0') + return std::nullopt; + for (char ch : v) { + if (ch < '0' || ch > '9') + return std::nullopt; + } + int p_autoidx; + if (std::from_chars(v.data(), v.data() + v.size(), p_autoidx).ec != std::errc()) + return std::nullopt; + return p_autoidx; +} + +int RTLIL::IdString::really_insert(std::string_view p, std::unordered_map::iterator &it) +{ + ensure_prepopulated(); + + log_assert(p[0] == '$' || p[0] == '\\'); + for (char ch : p) + if ((unsigned)ch <= (unsigned)' ') + log_error("Found control character or space (0x%02x) in string '%s' which is not allowed in RTLIL identifiers\n", ch, std::string(p).c_str()); + + if (p.substr(0, 6) == "$auto$") { + size_t autoidx_pos = p.find_last_of('$') + 1; + std::optional p_autoidx = parse_autoidx(p.substr(autoidx_pos)); + if (p_autoidx.has_value()) { + auto autoidx_it = global_autoidx_id_storage_.find(-*p_autoidx); + if (autoidx_it != global_autoidx_id_storage_.end() && + p.substr(0, autoidx_pos) == *autoidx_it->second.prefix) + return -*p_autoidx; + // Ensure NEW_ID/NEW_ID_SUFFIX will not create collisions with the ID + // we're about to create. + autoidx.ensure_at_least(*p_autoidx + 1); + } + } + + if (global_free_idx_list_.empty()) { + log_assert(global_id_storage_.size() < 0x40000000); + global_free_idx_list_.push_back(global_id_storage_.size()); + global_id_storage_.push_back({nullptr, 0}); + } + + int idx = global_free_idx_list_.back(); + global_free_idx_list_.pop_back(); + char* buf = static_cast(malloc(p.size() + 1)); + memcpy(buf, p.data(), p.size()); + buf[p.size()] = 0; + global_id_storage_.at(idx) = {buf, GetSize(p)}; + global_id_index_.insert(it, {std::string_view(buf, p.size()), idx}); + + if (yosys_xtrace) { + log("#X# New IdString '%s' with index %d.\n", global_id_storage_.at(idx).buf, idx); + log_backtrace("-X- ", yosys_xtrace-1); + } + +#ifdef YOSYS_XTRACE_GET_PUT + if (yosys_xtrace) + log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx).buf, idx, refcount(idx)); +#endif + return idx; +} + static constexpr bool check_well_known_id_order() { int size = sizeof(IdTable) / sizeof(IdTable[0]); @@ -85,6 +141,150 @@ static constexpr bool check_well_known_id_order() // and in sorted ascii order, as required by the ID macro. static_assert(check_well_known_id_order()); +struct IdStringCollector { + void trace(IdString id) { + live.insert(id.index_); + } + template void trace(const T* v) { + trace(*v); + } + template void trace(const std::vector &v) { + for (const auto &element : v) + trace(element); + } + template void trace(const pool &p) { + for (const auto &element : p) + trace(element); + } + template void trace(const dict &d) { + for (const auto &[key, value] : d) { + trace(key); + trace(value); + } + } + template void trace_keys(const dict &d) { + for (const auto &[key, value] : d) { + trace(key); + } + } + template void trace_values(const dict &d) { + for (const auto &[key, value] : d) { + trace(value); + } + } + template void trace(const idict &d) { + for (const auto &element : d) + trace(element); + } + + void trace(const RTLIL::Design &design) { + trace_values(design.modules_); + trace(design.selection_vars); + } + void trace(const RTLIL::Selection &selection_var) { + trace(selection_var.selected_modules); + trace(selection_var.selected_members); + } + void trace_named(const RTLIL::NamedObject named) { + trace_keys(named.attributes); + trace(named.name); + } + void trace(const RTLIL::Module &module) { + trace_named(module); + trace_values(module.wires_); + trace_values(module.cells_); + trace(module.avail_parameters); + trace_keys(module.parameter_default_values); + trace_values(module.memories); + trace_values(module.processes); + } + void trace(const RTLIL::Wire &wire) { + trace_named(wire); + if (wire.known_driver()) + trace(wire.driverPort()); + } + void trace(const RTLIL::Cell &cell) { + trace_named(cell); + trace(cell.type); + trace_keys(cell.connections_); + trace_keys(cell.parameters); + } + void trace(const RTLIL::Memory &mem) { + trace_named(mem); + } + void trace(const RTLIL::Process &proc) { + trace_named(proc); + trace(proc.root_case); + trace(proc.syncs); + } + void trace(const RTLIL::CaseRule &rule) { + trace_keys(rule.attributes); + trace(rule.switches); + } + void trace(const RTLIL::SwitchRule &rule) { + trace_keys(rule.attributes); + trace(rule.cases); + } + void trace(const RTLIL::SyncRule &rule) { + trace(rule.mem_write_actions); + } + void trace(const RTLIL::MemWriteAction &action) { + trace_keys(action.attributes); + trace(action.memid); + } + + std::unordered_set live; +}; + +int64_t RTLIL::OwningIdString::gc_ns; +int RTLIL::OwningIdString::gc_count; + +void RTLIL::OwningIdString::collect_garbage() +{ + int64_t start = PerformanceTimer::query(); + IdStringCollector collector; + for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) { + collector.trace(*design); + } + int size = GetSize(global_id_storage_); + for (int i = static_cast(StaticId::STATIC_ID_END); i < size; ++i) { + RTLIL::IdString::Storage &storage = global_id_storage_.at(i); + if (storage.buf == nullptr) + continue; + if (collector.live.find(i) != collector.live.end()) + continue; + if (global_refcount_storage_.find(i) != global_refcount_storage_.end()) + continue; + + if (yosys_xtrace) { + log("#X# Removed IdString '%s' with index %d.\n", storage.buf, i); + log_backtrace("-X- ", yosys_xtrace-1); + } + + global_id_index_.erase(std::string_view(storage.buf, storage.size)); + free(storage.buf); + storage = {nullptr, 0}; + global_free_idx_list_.push_back(i); + } + + for (auto it = global_autoidx_id_storage_.begin(); it != global_autoidx_id_storage_.end();) { + if (collector.live.find(it->first) != collector.live.end()) { + ++it; + continue; + } + if (global_refcount_storage_.find(it->first) != global_refcount_storage_.end()) { + ++it; + continue; + } + it = global_autoidx_id_storage_.erase(it); + } + + int64_t time_ns = PerformanceTimer::query() - start; + Pass::subtract_from_current_runtime_ns(time_ns); + gc_ns += time_ns; + ++gc_count; +} + dict RTLIL::constpad; static const pool &builtin_ff_cell_types_internal() { @@ -1083,9 +1283,7 @@ RTLIL::Design::Design() refcount_modules_ = 0; push_full_selection(); -#ifdef YOSYS_ENABLE_PYTHON RTLIL::Design::get_all_designs()->insert(std::pair(hashidx_, this)); -#endif } RTLIL::Design::~Design() @@ -1094,18 +1292,14 @@ RTLIL::Design::~Design() delete pr.second; for (auto n : bindings_) delete n; -#ifdef YOSYS_ENABLE_PYTHON RTLIL::Design::get_all_designs()->erase(hashidx_); -#endif } -#ifdef YOSYS_ENABLE_PYTHON static std::map all_designs; std::map *RTLIL::Design::get_all_designs(void) { return &all_designs; } -#endif RTLIL::ObjRange RTLIL::Design::modules() { diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 79cc73e45..f841df1ed 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -53,10 +53,11 @@ namespace RTLIL // Semantic metadata - how can this constant be interpreted? // Values may be generally non-exclusive enum ConstFlags : unsigned char { - CONST_FLAG_NONE = 0, - CONST_FLAG_STRING = 1, - CONST_FLAG_SIGNED = 2, // only used for parameters - CONST_FLAG_REAL = 4 // only used for parameters + CONST_FLAG_NONE = 0, + CONST_FLAG_STRING = 1, + CONST_FLAG_SIGNED = 2, // only used for parameters + CONST_FLAG_REAL = 4, // only used for parameters + CONST_FLAG_UNSIZED = 8, // only used for parameters }; enum SelectPartials : unsigned char { @@ -120,27 +121,30 @@ namespace RTLIL struct Process; struct Binding; struct IdString; - struct StaticIdString; + struct OwningIdString; typedef std::pair SigSig; - - struct StaticIdString { - constexpr StaticIdString(StaticId id, const IdString &id_str) : id_str(id_str), id(id) {} - constexpr inline operator const IdString &() const { return id_str; } - constexpr inline int index() const { return static_cast(id); } - constexpr inline const IdString &id_string() const { return id_str; } - - const IdString &id_str; - const StaticId id; - }; }; struct RTLIL::IdString { - #undef YOSYS_XTRACE_GET_PUT - #undef YOSYS_SORT_ID_FREE_LIST - #undef YOSYS_USE_STICKY_IDS - #undef YOSYS_NO_IDS_REFCNT + struct Storage { + char *buf; + int size; + + std::string_view str_view() const { return {buf, static_cast(size)}; } + }; + struct AutoidxStorage { + // Append the negated (i.e. positive) ID to this string to get + // the real string. The prefix strings must live forever. + const std::string *prefix; + // Cache of the full string, or nullptr if not cached yet. + std::atomic full_str; + + AutoidxStorage(const std::string *prefix) : prefix(prefix), full_str(nullptr) {} + AutoidxStorage(AutoidxStorage&& other) : prefix(other.prefix), full_str(other.full_str.exchange(nullptr, std::memory_order_relaxed)) {} + ~AutoidxStorage() { delete[] full_str.load(std::memory_order_acquire); } + }; // the global id string cache @@ -150,200 +154,82 @@ struct RTLIL::IdString ~destruct_guard_t() { destruct_guard_ok = false; } } destruct_guard; - static std::vector global_id_storage_; + // String storage for non-autoidx IDs + static std::vector global_id_storage_; + // Lookup table for non-autoidx IDs static std::unordered_map global_id_index_; -#ifndef YOSYS_NO_IDS_REFCNT - // For prepopulated IdStrings, the refcount is meaningless since they - // are never freed even if the refcount is zero. For code efficiency - // we increment the refcount of prepopulated IdStrings like any other string, - // but we never decrement the refcount or check whether it's zero. - // So, make this unsigned because refcounts of preopulated IdStrings may overflow - // and overflow of signed integers is undefined behavior. - static std::vector global_refcount_storage_; + // Storage for autoidx IDs, which have negative indices, i.e. all entries in this + // map have negative keys. + static std::unordered_map global_autoidx_id_storage_; + // All (index, refcount) pairs in this map have refcount > 0. + static std::unordered_map global_refcount_storage_; static std::vector global_free_idx_list_; -#endif -#ifdef YOSYS_USE_STICKY_IDS - static int last_created_idx_ptr_; - static int last_created_idx_[8]; -#endif + static int refcount(int idx) { + auto it = global_refcount_storage_.find(idx); + if (it == global_refcount_storage_.end()) + return 0; + return it->second; + } static inline void xtrace_db_dump() { #ifdef YOSYS_XTRACE_GET_PUT for (int idx = 0; idx < GetSize(global_id_storage_); idx++) { - if (global_id_storage_.at(idx) == nullptr) + if (global_id_storage_.at(idx).buf == nullptr) log("#X# DB-DUMP index %d: FREE\n", idx); else - log("#X# DB-DUMP index %d: '%s' (ref %u)\n", idx, global_id_storage_.at(idx), global_refcount_storage_.at(idx)); + log("#X# DB-DUMP index %d: '%s' (ref %u)\n", idx, global_id_storage_.at(idx).buf, refcount(idx)); } #endif } static inline void checkpoint() { - #ifdef YOSYS_USE_STICKY_IDS - last_created_idx_ptr_ = 0; - for (int i = 0; i < 8; i++) { - if (last_created_idx_[i]) - put_reference(last_created_idx_[i]); - last_created_idx_[i] = 0; - } - #endif #ifdef YOSYS_SORT_ID_FREE_LIST std::sort(global_free_idx_list_.begin(), global_free_idx_list_.end(), std::greater()); #endif } - static inline int get_reference(int idx) - { - #ifndef YOSYS_NO_IDS_REFCNT - global_refcount_storage_[idx]++; - #endif - #ifdef YOSYS_XTRACE_GET_PUT - if (yosys_xtrace && idx >= static_cast(StaticId::STATIC_ID_END)) - log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, global_refcount_storage_.at(idx)); - #endif - return idx; - } - - static int get_reference(const char *p) - { - return get_reference(std::string_view(p)); - } - - static int get_reference(std::string_view p) + static int insert(std::string_view p) { log_assert(destruct_guard_ok); + log_assert(!Multithreading::active()); auto it = global_id_index_.find(p); if (it != global_id_index_.end()) { - #ifndef YOSYS_NO_IDS_REFCNT - global_refcount_storage_.at(it->second)++; - #endif #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace) - log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(it->second), it->second, global_refcount_storage_.at(it->second)); + log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(it->second).buf, it->second, refcount(it->second)); #endif return it->second; } - - ensure_prepopulated(); - - if (p.empty()) - return 0; - - log_assert(p[0] == '$' || p[0] == '\\'); - for (char ch : p) - if ((unsigned)ch <= (unsigned)' ') - log_error("Found control character or space (0x%02x) in string '%s' which is not allowed in RTLIL identifiers\n", ch, std::string(p).c_str()); - - #ifndef YOSYS_NO_IDS_REFCNT - if (global_free_idx_list_.empty()) { - log_assert(global_id_storage_.size() < 0x40000000); - global_free_idx_list_.push_back(global_id_storage_.size()); - global_id_storage_.push_back(nullptr); - global_refcount_storage_.push_back(0); - } - - int idx = global_free_idx_list_.back(); - global_free_idx_list_.pop_back(); - char* buf = static_cast(malloc(p.size() + 1)); - memcpy(buf, p.data(), p.size()); - buf[p.size()] = 0; - global_id_storage_.at(idx) = buf; - global_id_index_.insert(it, {std::string_view(buf, p.size()), idx}); - global_refcount_storage_.at(idx)++; - #else - int idx = global_id_storage_.size(); - global_id_storage_.push_back(strdup(p)); - global_id_index_[global_id_storage_.back()] = idx; - #endif - - if (yosys_xtrace) { - log("#X# New IdString '%s' with index %d.\n", global_id_storage_.at(idx), idx); - log_backtrace("-X- ", yosys_xtrace-1); - } - - #ifdef YOSYS_XTRACE_GET_PUT - if (yosys_xtrace) - log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, global_refcount_storage_.at(idx)); - #endif - - #ifdef YOSYS_USE_STICKY_IDS - // Avoid Create->Delete->Create pattern - if (last_created_idx_[last_created_idx_ptr_]) - put_reference(last_created_idx_[last_created_idx_ptr_]); - last_created_idx_[last_created_idx_ptr_] = idx; - get_reference(last_created_idx_[last_created_idx_ptr_]); - last_created_idx_ptr_ = (last_created_idx_ptr_ + 1) & 7; - #endif - - return idx; + return really_insert(p, it); } -#ifndef YOSYS_NO_IDS_REFCNT - static inline void put_reference(int idx) - { - // put_reference() may be called from destructors after the destructor of - // global_refcount_storage_ has been run. in this case we simply do nothing. - if (idx < static_cast(StaticId::STATIC_ID_END) || !destruct_guard_ok) - return; - - #ifdef YOSYS_XTRACE_GET_PUT - if (yosys_xtrace) { - log("#X# PUT '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, global_refcount_storage_.at(idx)); - } - #endif - - uint32_t &refcount = global_refcount_storage_[idx]; - - if (--refcount > 0) - return; - - free_reference(idx); + // Inserts an ID with string `prefix + autoidx', incrementing autoidx. + // `prefix` must start with '$auto$', end with '$', and live forever. + static IdString new_autoidx_with_prefix(const std::string *prefix) { + log_assert(!Multithreading::active()); + int index = -(autoidx++); + global_autoidx_id_storage_.insert({index, prefix}); + return from_index(index); } - static inline void free_reference(int idx) - { - if (yosys_xtrace) { - log("#X# Removed IdString '%s' with index %d.\n", global_id_storage_.at(idx), idx); - log_backtrace("-X- ", yosys_xtrace-1); - } - log_assert(idx >= static_cast(StaticId::STATIC_ID_END)); - - global_id_index_.erase(global_id_storage_.at(idx)); - free(global_id_storage_.at(idx)); - global_id_storage_.at(idx) = nullptr; - global_free_idx_list_.push_back(idx); - } -#else - static inline void put_reference(int) { } -#endif // the actual IdString object is just is a single int int index_; - inline IdString() : index_(0) { } - inline IdString(const char *str) : index_(get_reference(str)) { } - inline IdString(const IdString &str) : index_(get_reference(str.index_)) { } + constexpr inline IdString() : index_(0) { } + inline IdString(const char *str) : index_(insert(std::string_view(str))) { } + constexpr inline IdString(const IdString &str) : index_(str.index_) { } inline IdString(IdString &&str) : index_(str.index_) { str.index_ = 0; } - inline IdString(const std::string &str) : index_(get_reference(std::string_view(str))) { } - inline IdString(std::string_view str) : index_(get_reference(str)) { } - inline IdString(StaticId id) : index_(static_cast(id)) {} - inline ~IdString() { put_reference(index_); } + inline IdString(const std::string &str) : index_(insert(std::string_view(str))) { } + inline IdString(std::string_view str) : index_(insert(str)) { } + constexpr inline IdString(StaticId id) : index_(static_cast(id)) {} - inline void operator=(const IdString &rhs) { - put_reference(index_); - index_ = get_reference(rhs.index_); - } - - inline void operator=(IdString &&rhs) { - put_reference(index_); - index_ = rhs.index_; - rhs.index_ = 0; - } + IdString &operator=(const IdString &rhs) = default; inline void operator=(const char *rhs) { IdString id(rhs); @@ -358,11 +244,159 @@ struct RTLIL::IdString constexpr inline const IdString &id_string() const { return *this; } inline const char *c_str() const { - return global_id_storage_.at(index_); + if (index_ >= 0) + return global_id_storage_.at(index_).buf; + + AutoidxStorage &s = global_autoidx_id_storage_.at(index_); + char *full_str = s.full_str.load(std::memory_order_acquire); + if (full_str != nullptr) + return full_str; + const std::string &prefix = *s.prefix; + std::string suffix = std::to_string(-index_); + char *c = new char[prefix.size() + suffix.size() + 1]; + memcpy(c, prefix.data(), prefix.size()); + memcpy(c + prefix.size(), suffix.c_str(), suffix.size() + 1); + if (s.full_str.compare_exchange_strong(full_str, c, std::memory_order_acq_rel)) + return c; + delete[] c; + return full_str; } inline std::string str() const { - return std::string(global_id_storage_.at(index_)); + std::string result; + append_to(&result); + return result; + } + + inline void append_to(std::string *out) const { + if (index_ >= 0) { + *out += global_id_storage_.at(index_).str_view(); + return; + } + *out += *global_autoidx_id_storage_.at(index_).prefix; + *out += std::to_string(-index_); + } + + class Substrings { + std::string_view first_; + int suffix_number; + char buf[10]; + public: + Substrings(const Storage &storage) : first_(storage.str_view()), suffix_number(-1) {} + // suffix_number must be non-negative + Substrings(const std::string *prefix, int suffix_number) + : first_(*prefix), suffix_number(suffix_number) {} + std::string_view first() { return first_; } + std::optional next() { + if (suffix_number < 0) + return std::nullopt; + int i = sizeof(buf); + do { + --i; + buf[i] = (suffix_number % 10) + '0'; + suffix_number /= 10; + } while (suffix_number > 0); + suffix_number = -1; + return std::string_view(buf + i, sizeof(buf) - i); + } + }; + + class const_iterator { + const std::string *prefix; + std::string suffix; + const char *c_str; + int c_str_len; + // When this is INT_MAX it's the generic "end" value. + int index; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = char; + using difference_type = std::ptrdiff_t; + using pointer = const char*; + using reference = const char&; + + const_iterator(const Storage &storage) : prefix(nullptr), c_str(storage.buf), c_str_len(storage.size), index(0) {} + const_iterator(const std::string *prefix, int number) : + prefix(prefix), suffix(std::to_string(number)), c_str(nullptr), c_str_len(0), index(0) {} + // Construct end-marker + const_iterator() : prefix(nullptr), c_str(nullptr), c_str_len(0), index(INT_MAX) {} + + int size() const { + if (c_str != nullptr) + return c_str_len; + return GetSize(*prefix) + GetSize(suffix); + } + + char operator*() const { + if (c_str != nullptr) + return c_str[index]; + int prefix_size = GetSize(*prefix); + if (index < prefix_size) + return prefix->at(index); + return suffix[index - prefix_size]; + } + + const_iterator& operator++() { ++index; return *this; } + const_iterator operator++(int) { const_iterator result(*this); ++index; return result; } + const_iterator& operator+=(int i) { index += i; return *this; } + + const_iterator operator+(int add) { + const_iterator result = *this; + result += add; + return result; + } + + bool operator==(const const_iterator& other) const { + return index == other.index || (other.index == INT_MAX && index == size()) + || (index == INT_MAX && other.index == other.size()); + } + bool operator!=(const const_iterator& other) const { + return !(*this == other); + } + }; + const_iterator begin() const { + if (index_ >= 0) { + return const_iterator(global_id_storage_.at(index_)); + } + return const_iterator(global_autoidx_id_storage_.at(index_).prefix, -index_); + } + const_iterator end() const { + return const_iterator(); + } + + Substrings substrings() const { + if (index_ >= 0) { + return Substrings(global_id_storage_.at(index_)); + } + return Substrings(global_autoidx_id_storage_.at(index_).prefix, -index_); + } + + inline bool lt_by_name(const IdString &rhs) const { + Substrings lhs_it = substrings(); + Substrings rhs_it = rhs.substrings(); + std::string_view lhs_substr = lhs_it.first(); + std::string_view rhs_substr = rhs_it.first(); + while (true) { + int min = std::min(GetSize(lhs_substr), GetSize(rhs_substr)); + int diff = memcmp(lhs_substr.data(), rhs_substr.data(), min); + if (diff != 0) + return diff < 0; + lhs_substr = lhs_substr.substr(min); + rhs_substr = rhs_substr.substr(min); + if (rhs_substr.empty()) { + if (std::optional s = rhs_it.next()) + rhs_substr = *s; + else + return false; + } + if (lhs_substr.empty()) { + if (std::optional s = lhs_it.next()) + lhs_substr = *s; + else + return true; + } + } } inline bool operator<(const IdString &rhs) const { @@ -371,8 +405,6 @@ struct RTLIL::IdString inline bool operator==(const IdString &rhs) const { return index_ == rhs.index_; } inline bool operator!=(const IdString &rhs) const { return index_ != rhs.index_; } - inline bool operator==(const StaticIdString &rhs) const; - inline bool operator!=(const StaticIdString &rhs) const; // The methods below are just convenience functions for better compatibility with std::string. @@ -383,45 +415,84 @@ struct RTLIL::IdString bool operator!=(const char *rhs) const { return strcmp(c_str(), rhs) != 0; } char operator[](size_t i) const { - const char *p = c_str(); + if (index_ >= 0) { + const Storage &storage = global_id_storage_.at(index_); #ifndef NDEBUG - for (; i != 0; i--, p++) - log_assert(*p != 0); - return *p; -#else - return *(p + i); + log_assert(static_cast(i) < storage.size); #endif + return *(storage.buf + i); + } + const std::string &id_start = *global_autoidx_id_storage_.at(index_).prefix; + if (i < id_start.size()) + return id_start[i]; + i -= id_start.size(); + std::string suffix = std::to_string(-index_); +#ifndef NDEBUG + // Allow indexing to access the trailing null. + log_assert(i <= suffix.size()); +#endif + return suffix[i]; } std::string substr(size_t pos = 0, size_t len = std::string::npos) const { - if (len == std::string::npos || len >= strlen(c_str() + pos)) - return std::string(c_str() + pos); - else - return std::string(c_str() + pos, len); + std::string result; + const_iterator it = begin() + pos; + const_iterator end_it = end(); + if (len != std::string::npos && len < it.size() - pos) { + end_it = it + len; + } + std::copy(it, end_it, std::back_inserter(result)); + return result; } int compare(size_t pos, size_t len, const char* s) const { - return strncmp(c_str()+pos, s, len); + const_iterator it = begin() + pos; + const_iterator end_it = end(); + while (len > 0 && *s != 0 && it != end_it) { + int diff = *it - *s; + if (diff != 0) + return diff; + ++it; + ++s; + --len; + } + return 0; } - bool begins_with(const char* prefix) const { - size_t len = strlen(prefix); - if (size() < len) return false; - return compare(0, len, prefix) == 0; + bool begins_with(std::string_view prefix) const { + Substrings it = substrings(); + std::string_view substr = it.first(); + while (true) { + int min = std::min(GetSize(substr), GetSize(prefix)); + if (memcmp(substr.data(), prefix.data(), min) != 0) + return false; + prefix = prefix.substr(min); + if (prefix.empty()) + return true; + substr = substr.substr(min); + if (substr.empty()) { + if (std::optional s = it.next()) + substr = *s; + else + return false; + } + } } - bool ends_with(const char* suffix) const { - size_t len = strlen(suffix); - if (size() < len) return false; - return compare(size()-len, len, suffix) == 0; + bool ends_with(std::string_view suffix) const { + size_t sz = size(); + if (sz < suffix.size()) return false; + return compare(sz - suffix.size(), suffix.size(), suffix.data()) == 0; } - bool contains(const char* str) const { - return strstr(c_str(), str); + bool contains(std::string_view s) const { + if (index_ >= 0) + return global_id_storage_.at(index_).str_view().find(s) != std::string::npos; + return str().find(s) != std::string::npos; } size_t size() const { - return strlen(c_str()); + return begin().size(); } bool empty() const { @@ -458,7 +529,6 @@ struct RTLIL::IdString } bool in(const IdString &rhs) const { return *this == rhs; } - bool in(const StaticIdString &rhs) const { return *this == rhs; } bool in(const char *rhs) const { return *this == rhs; } bool in(const std::string &rhs) const { return *this == rhs; } inline bool in(const pool &rhs) const; @@ -468,6 +538,14 @@ struct RTLIL::IdString private: static void prepopulate(); + static int really_insert(std::string_view p, std::unordered_map::iterator &it); + +protected: + static IdString from_index(int index) { + IdString result; + result.index_ = index; + return result; + } public: static void ensure_prepopulated() { @@ -476,6 +554,95 @@ public: } }; +struct RTLIL::OwningIdString : public RTLIL::IdString { + inline OwningIdString() { } + inline OwningIdString(const OwningIdString &str) : IdString(str) { get_reference(); } + inline OwningIdString(const char *str) : IdString(str) { get_reference(); } + inline OwningIdString(const IdString &str) : IdString(str) { get_reference(); } + inline OwningIdString(IdString &&str) : IdString(str) { get_reference(); } + inline OwningIdString(const std::string &str) : IdString(str) { get_reference(); } + inline OwningIdString(std::string_view str) : IdString(str) { get_reference(); } + inline OwningIdString(StaticId id) : IdString(id) {} + inline ~OwningIdString() { + put_reference(); + } + + inline OwningIdString &operator=(const OwningIdString &rhs) { + put_reference(); + index_ = rhs.index_; + get_reference(); + return *this; + } + inline OwningIdString &operator=(const IdString &rhs) { + put_reference(); + index_ = rhs.index_; + get_reference(); + return *this; + } + inline OwningIdString &operator=(OwningIdString &&rhs) { + std::swap(index_, rhs.index_); + return *this; + } + + // Collect all non-owning references. + static void collect_garbage(); + static int64_t garbage_collection_ns() { return gc_ns; } + static int garbage_collection_count() { return gc_count; } + + // Used by the ID() macro to create an IdString with no destructor whose string will + // never be released. If ID() creates a closure-static `OwningIdString` then + // initialization of the static registers its destructor to run at exit, which is + // wasteful. + static IdString immortal(const char* str) { + IdString result(str); + get_reference(result.index_); + return result; + } +private: + static int64_t gc_ns; + static int gc_count; + + void get_reference() + { + get_reference(index_); + } + static void get_reference(int idx) + { + log_assert(!Multithreading::active()); + + if (idx < static_cast(StaticId::STATIC_ID_END)) + return; + auto it = global_refcount_storage_.find(idx); + if (it == global_refcount_storage_.end()) + global_refcount_storage_.insert(it, {idx, 1}); + else + ++it->second; + #ifdef YOSYS_XTRACE_GET_PUT + if (yosys_xtrace && idx >= static_cast(StaticId::STATIC_ID_END)) + log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", from_index(idx), idx, refcount(idx)); + #endif + } + + void put_reference() + { + log_assert(!Multithreading::active()); + + // put_reference() may be called from destructors after the destructor of + // global_refcount_storage_ has been run. in this case we simply do nothing. + if (index_ < static_cast(StaticId::STATIC_ID_END) || !destruct_guard_ok) + return; + #ifdef YOSYS_XTRACE_GET_PUT + if (yosys_xtrace) + log("#X# PUT '%s' (index %d, refcount %u)\n", from_index(index_), index_, refcount(index_)); + #endif + auto it = global_refcount_storage_.find(index_); + log_assert(it != global_refcount_storage_.end() && it->second >= 1); + if (--it->second == 0) { + global_refcount_storage_.erase(it); + } + } +}; + namespace hashlib { template <> struct hash_ops { @@ -501,21 +668,9 @@ inline bool RTLIL::IdString::in(const pool &rhs) const { return rhs.co [[deprecated]] inline bool RTLIL::IdString::in(const pool &&rhs) const { return rhs.count(*this) != 0; } -inline bool RTLIL::IdString::operator==(const RTLIL::StaticIdString &rhs) const { - return index_ == rhs.index(); -} -inline bool RTLIL::IdString::operator!=(const RTLIL::StaticIdString &rhs) const { - return index_ != rhs.index(); -} - namespace RTLIL { - namespace IDInternal { -#define X(_id) extern const IdString _id; -#include "kernel/constids.inc" -#undef X - } namespace ID { -#define X(_id) constexpr StaticIdString _id(StaticId::_id, IDInternal::_id); +#define X(_id) constexpr IdString _id(StaticId::_id); #include "kernel/constids.inc" #undef X } @@ -523,7 +678,7 @@ namespace RTLIL { struct IdTableEntry { const std::string_view name; - const RTLIL::StaticIdString static_id; + const RTLIL::IdString static_id; }; constexpr IdTableEntry IdTable[] = { @@ -556,15 +711,15 @@ constexpr int lookup_well_known_id(std::string_view name) // // sed -i.orig -r 's/"\\\\([a-zA-Z0-9_]+)"/ID(\1)/g; s/"(\$[a-zA-Z0-9_]+)"/ID(\1)/g;' // -typedef const RTLIL::IdString &IDMacroHelperFunc(); +typedef RTLIL::IdString IDMacroHelperFunc(); template struct IDMacroHelper { - static constexpr RTLIL::StaticIdString eval(IDMacroHelperFunc) { + static constexpr RTLIL::IdString eval(IDMacroHelperFunc) { return IdTable[IdTableIndex].static_id; } }; template <> struct IDMacroHelper<-1> { - static constexpr const RTLIL::IdString &eval(IDMacroHelperFunc func) { + static constexpr RTLIL::IdString eval(IDMacroHelperFunc func) { return func(); } }; @@ -574,16 +729,16 @@ template <> struct IDMacroHelper<-1> { YOSYS_NAMESPACE_PREFIX IDMacroHelper< \ YOSYS_NAMESPACE_PREFIX lookup_well_known_id(#_id) \ >::eval([]() \ - -> const YOSYS_NAMESPACE_PREFIX RTLIL::IdString & { \ + -> YOSYS_NAMESPACE_PREFIX RTLIL::IdString { \ const char *p = "\\" #_id, *q = p[1] == '$' ? p+1 : p; \ - static const YOSYS_NAMESPACE_PREFIX RTLIL::IdString id(q); \ + static const YOSYS_NAMESPACE_PREFIX RTLIL::IdString id = \ + YOSYS_NAMESPACE_PREFIX RTLIL::OwningIdString::immortal(q); \ return id; \ }) 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) { @@ -620,13 +775,13 @@ namespace RTLIL { template struct sort_by_name_str { bool operator()(T *a, T *b) const { - return strcmp(a->name.c_str(), b->name.c_str()) < 0; + return a->name.lt_by_name(b->name); } }; struct sort_by_id_str { bool operator()(const RTLIL::IdString &a, const RTLIL::IdString &b) const { - return strcmp(a.c_str(), b.c_str()) < 0; + return a.lt_by_name(b); } }; @@ -1581,6 +1736,8 @@ public: operator std::vector() const; operator std::vector() const { return to_sigbit_vector(); } const RTLIL::SigBit &at(int offset, const RTLIL::SigBit &defval) { return offset < size() ? (*this)[offset] : defval; } + RTLIL::SigBit& at(int offset) { return (*this)[offset]; } + RTLIL::SigBit at(int offset) const { return (*this)[offset]; } [[nodiscard]] Hasher hash_into(Hasher h) const { Hasher::hash_t val; @@ -1876,9 +2033,7 @@ struct RTLIL::Design // returns all selected unboxed whole modules, warning the user if any // partially selected or boxed modules have been ignored std::vector selected_unboxed_whole_modules_warn() const { return selected_modules(SELECT_WHOLE_WARN, SB_UNBOXED_WARN); } -#ifdef YOSYS_ENABLE_PYTHON static std::map *get_all_designs(void); -#endif }; struct RTLIL::Module : public RTLIL::NamedObject diff --git a/kernel/tclapi.cc b/kernel/tclapi.cc index a2ebaffa2..ec0483a4a 100644 --- a/kernel/tclapi.cc +++ b/kernel/tclapi.cc @@ -533,7 +533,7 @@ 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) +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 ca1db3aa6..2c9b8304d 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -19,6 +19,7 @@ #include "kernel/yosys.h" #include "kernel/celltypes.h" +#include "kernel/log.h" #ifdef YOSYS_ENABLE_READLINE # include @@ -80,7 +81,7 @@ extern "C" PyObject* PyInit_pyosys(); YOSYS_NAMESPACE_BEGIN -int autoidx = 1; +Autoidx autoidx(1); int yosys_xtrace = 0; bool yosys_write_versions = true; const char* yosys_maybe_version() { @@ -95,6 +96,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; @@ -107,9 +109,30 @@ uint32_t Hasher::fudge = 0; std::string yosys_share_dirname; std::string yosys_abc_executable; +bool Multithreading::active_ = false; + void init_share_dirname(); void init_abc_executable_name(); +Multithreading::Multithreading() { + log_assert(!active_); + active_ = true; +} + +Multithreading::~Multithreading() { + log_assert(active_); + active_ = false; +} + +void Autoidx::ensure_at_least(int v) { + value = std::max(value, v); +} + +int Autoidx::operator++(int) { + log_assert(!Multithreading::active()); + return value++; +} + void memhasher_on() { #if defined(__linux__) || defined(__FreeBSD__) @@ -260,6 +283,7 @@ void yosys_shutdown() delete yosys_design; yosys_design = NULL; + RTLIL::OwningIdString::collect_garbage(); for (auto f : log_files) if (f != stderr) @@ -295,35 +319,35 @@ void yosys_shutdown() #endif } -RTLIL::IdString new_id(std::string file, int line, std::string func) +const std::string *create_id_prefix(std::string_view file, int line, std::string_view func) { #ifdef _WIN32 size_t pos = file.find_last_of("/\\"); #else size_t pos = file.find_last_of('/'); #endif - if (pos != std::string::npos) + if (pos != std::string_view::npos) file = file.substr(pos+1); pos = func.find_last_of(':'); - if (pos != std::string::npos) + if (pos != std::string_view::npos) func = func.substr(pos+1); - return stringf("$auto$%s:%d:%s$%d", file, line, func, autoidx++); + return new std::string(stringf("$auto$%s:%d:%s$", file, line, func)); } -RTLIL::IdString new_id_suffix(std::string file, int line, std::string func, std::string suffix) +RTLIL::IdString new_id_suffix(std::string_view file, int line, std::string_view func, std::string_view suffix) { #ifdef _WIN32 size_t pos = file.find_last_of("/\\"); #else size_t pos = file.find_last_of('/'); #endif - if (pos != std::string::npos) + if (pos != std::string_view::npos) file = file.substr(pos+1); pos = func.find_last_of(':'); - if (pos != std::string::npos) + if (pos != std::string_view::npos) func = func.substr(pos+1); return stringf("$auto$%s:%d:%s$%s$%d", file, line, func, suffix, autoidx++); @@ -392,17 +416,18 @@ 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 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; } +// Also see SdcPass struct TclPass : public Pass { TclPass() : Pass("tcl", "execute a TCL script file") { } void help() override { @@ -445,6 +470,7 @@ struct TclPass : public Pass { Tcl_Release(interp); } } TclPass; + #endif #if defined(__linux__) || defined(__CYGWIN__) diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index 4794b5618..55e7b71eb 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -267,15 +267,41 @@ int ceil_log2(int x) YS_ATTRIBUTE(const); template int GetSize(const T &obj) { return obj.size(); } inline int GetSize(RTLIL::Wire *wire); -extern int autoidx; +// When multiple threads are accessing RTLIL, one of these guard objects +// must exist. +struct Multithreading +{ + Multithreading(); + ~Multithreading(); + // Returns true when multiple threads are accessing RTLIL. + // autoidx cannot be used during such times. + // IdStrings cannot be created during such times. + static bool active() { return active_; } +private: + static bool active_; +}; + +struct Autoidx { + Autoidx(int value) : value(value) {} + operator int() const { return value; } + void ensure_at_least(int v); + int operator++(int); +private: + int value; +}; + +extern Autoidx autoidx; extern int yosys_xtrace; extern bool yosys_write_versions; -RTLIL::IdString new_id(std::string file, int line, std::string func); -RTLIL::IdString new_id_suffix(std::string file, int line, std::string func, std::string suffix); +const std::string *create_id_prefix(std::string_view file, int line, std::string_view func); +RTLIL::IdString new_id_suffix(std::string_view file, int line, std::string_view func, std::string_view suffix); #define NEW_ID \ - YOSYS_NAMESPACE_PREFIX new_id(__FILE__, __LINE__, __FUNCTION__) + YOSYS_NAMESPACE_PREFIX RTLIL::IdString::new_autoidx_with_prefix([](std::string_view func) -> const std::string * { \ + static const std::string *prefix = YOSYS_NAMESPACE_PREFIX create_id_prefix(__FILE__, __LINE__, func); \ + return prefix; \ + }(__FUNCTION__)) #define NEW_ID_SUFFIX(suffix) \ YOSYS_NAMESPACE_PREFIX new_id_suffix(__FILE__, __LINE__, __FUNCTION__, suffix) diff --git a/libs/subcircuit/subcircuit.cc b/libs/subcircuit/subcircuit.cc index 60f27fd55..dfec80254 100644 --- a/libs/subcircuit/subcircuit.cc +++ b/libs/subcircuit/subcircuit.cc @@ -912,6 +912,10 @@ class SubCircuit::SolverWorker bool pruneEnumerationMatrix(std::vector> &enumerationMatrix, const GraphData &needle, const GraphData &haystack, int &nextRow, bool allowOverlap) { bool didSomething = true; + + // Map of j:[i where j is used] + std::map> usedNodes; + while (didSomething) { nextRow = -1; @@ -923,13 +927,23 @@ class SubCircuit::SolverWorker didSomething = true; else if (!allowOverlap && haystack.usedNodes[j]) didSomething = true; - else + else { newRow.insert(j); + usedNodes[j].insert(i); // Store the needle index by haystack node index + } } + + // This indicates there are no available haystack nodes to assign to the needle if (newRow.size() == 0) return false; + + // If there are multiple needles assigned to the haystack node, the solution is invalid + if (newRow.size() == 1 && usedNodes[*newRow.begin()].size() > 1) + return false; + if (newRow.size() >= 2 && (nextRow < 0 || needle.adjMatrix.at(nextRow).size() < needle.adjMatrix.at(i).size())) nextRow = i; + enumerationMatrix[i].swap(newRow); } } diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 2dbeadac2..14ea9c52a 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/icell_liberty.o + +include $(YOSYS_SRC)/passes/cmds/sdc/Makefile.inc diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc new file mode 100644 index 000000000..d49cd360a --- /dev/null +++ b/passes/cmds/icell_liberty.cc @@ -0,0 +1,207 @@ +#include "kernel/yosys.h" +#include "kernel/celltypes.h" +#include "kernel/ff.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct LibertyStubber { + CellTypes ct; + LibertyStubber() { + ct.setup(); + ct.setup_internals_ff(); + } + void liberty_prefix(std::ostream& f) + { + f << "/*\n"; + 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"; + 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"; + } + 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; + 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"; + 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 (port_name == "CLK" || port_name == "C") { + i.item("clock", "true"); + 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"; + } + + 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"; + f << "\t}\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; + } + + 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) { + 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", "write Liberty interfaces for used internal cells") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + 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 + { + log_header(d, "Executing ICELL_LIBERTY pass.\n"); + + size_t argidx; + IdString naming_attr; + std::string liberty_filename; + auto liberty_file = std::make_unique(); + + for (argidx = 1; argidx < args.size(); argidx++) { + break; + } + if (argidx < args.size()) + liberty_filename = args[argidx++]; + else + log_cmd_error("no Liberty filename specified\n"); + + if (liberty_filename.size()) { + liberty_file->open(liberty_filename.c_str()); + if (liberty_file->fail()) { + log_cmd_error("Can't open file `%s' for writing: %s\n", liberty_filename.c_str(), strerror(errno)); + } + } + + pool done; + LibertyStubber stubber = {}; + + 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); + } + } + } + + stubber.liberty_suffix(*liberty_file); + } +} IcellLiberty; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/sdc/Makefile.inc b/passes/cmds/sdc/Makefile.inc new file mode 100644 index 000000000..7c8036550 --- /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/graph-stubs.sdc)) diff --git a/passes/cmds/sdc/graph-stubs.sdc b/passes/cmds/sdc/graph-stubs.sdc new file mode 100644 index 000000000..a6601f7f8 --- /dev/null +++ b/passes/cmds/sdc/graph-stubs.sdc @@ -0,0 +1,42 @@ +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]} { + set sdc_call_index 0 + } + 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 $ret +} +proc list {args} { + return [unknown "list" {*}$args] +} +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 new file mode 100644 index 000000000..fad001e50 --- /dev/null +++ b/passes/cmds/sdc/sdc.cc @@ -0,0 +1,790 @@ +#ifdef YOSYS_ENABLE_TCL + +#include "kernel/register.h" +#include "kernel/rtlil.h" +#include "kernel/log.h" +#include +#include +#include +#include + +#if TCL_MAJOR_VERSION < 9 +typedef int YS_Tcl_Size; +#else +typedef Tcl_Size YS_Tcl_Size; +#endif + +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 { + // getter-side object tracking with minimal features + SimpleGetter, + // getter-side object tracking with everything + FullGetter, + // constraint-side tracking + FullConstraint, + } collect_mode; + using CellPin = std::pair; + Design* design; + std::vector> design_ports; + std::vector> design_cells; + std::vector> design_pins; + std::vector> design_nets; + + 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; + + 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()) { + 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); + sniff_module(hierarchy, sub_mod); + hierarchy.pop_back(); + } + } + } + SdcObjects(Design* design) : 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(std::make_pair(port.str().substr(1), top->wire(port))); + } + std::list hierarchy{}; + 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()); + 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()); + // } + // 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 [ref, bits] : constrained_ports) { + auto [name, port] = ref; + (void)port; + log("\t%s\n", name.c_str()); + bits.dump(); + } + 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& [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& [ref, bits] : constrained_nets) { + auto [name, net] = ref; + (void)net; + log("\t%s\n", name.c_str()); + bits.dump(); + } + 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)) { + mod->set_bool_attribute(ID::keep_hierarchy); + return true; + } + } + + if (tracked_modules.count(mod)) { + mod->set_bool_attribute(ID::keep_hierarchy); + 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("\t%s\n", mod->name); + } + bool mark() { + return mark(design->top_module()); + } + }; + void keep_hierarchy() { + (void)KeepHierarchyWorker(this, design).mark(); + } +}; + +// 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 std::pair matches(std::string name, const std::string& pat, const MatchConfig& config) { + (void)config; + 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); + + } + 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 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 = (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, + .objv = allocatedObjv + }; + // Finally, redirect to unknown handler + return redirect_unknown(newCall); +} + +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 = (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); + Tcl_Free((char*) 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 { + 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); + } + } + } +}; + +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) { + + Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY); + YS_Tcl_Size listLength; + + std::vector> sdc_calls; + 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"); + } + YS_Tcl_Size subListLength; + if (Tcl_ListObjLength(interp, subListObj, &subListLength) == TCL_OK) { + // 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 { + // Single element, not a list + const char* elementStr = Tcl_GetString(subListObj); + subList.push_back(std::string(elementStr)); + } + sdc_calls.push_back(subList); + } + } + log_assert(sdc_calls.size() == get_node_count(interp)); + 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++) { + 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, bool dump_mode) { + std::vector> sdc_calls = gather_nested_calls(interp); + std::vector graph = build_graph(sdc_calls); + if (dump_mode) + dump_sdc_graph(graph, node_ownership(graph)); +} + +// patterns -> (pattern-object-bit)s +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)); + // TODO add a continue statement, conditional on config + } + } + if (!found) + log_warning("No matches in design for %s %s\n", obj_type, pat.c_str()); + } + return resolved; +} + +struct TclOpts { + const char* 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; + 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; + } + 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); + 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; + } + 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("%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"); + } +}; + +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, opts.patterns, "pin"); + + 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[]) +{ + auto* objects = (SdcObjects*)data; + GetterOpts opts("get_ports", {"regexp", "nocase"}); + 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_ports; + resolved = find_matching(ports, config, opts.patterns, "port"); + + for (auto [name, wire, matching_bits] : resolved) { + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + merge_or_init(std::make_pair(name, wire), objects->constrained_ports, matching_bits); + } + + 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[]) +{ + 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"); + + for (auto [name, wire, matching_bits] : resolved) { + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + merge_or_init(std::make_pair(name, wire), objects->constrained_nets, matching_bits); + } + + return getter_graph_node(TclCall{interp, objc, objv}); +} + +class SDCInterpreter +{ +private: + Tcl_Interp* interp = nullptr; +public: + std::unique_ptr objects; + ~SDCInterpreter() { + if (interp) + Tcl_DeleteInterp(interp); + } + static SDCInterpreter& get() { + static SDCInterpreter instance; + return instance; + } + Tcl_Interp* fresh_interp(Design* design) { + if (interp) + Tcl_DeleteInterp(interp); + + interp = Tcl_CreateInterp(); + if (Tcl_Init(interp)!=TCL_OK) + log_error("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); + + objects = std::make_unique(design); + objects->collect_mode = SdcObjects::CollectMode::SimpleGetter; + 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); + return interp; + } +}; + +// Also see TclPass +struct SdcPass : public Pass { + 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 stubs_paths; + for (argidx = 1; argidx < args.size(); argidx++) { + 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; + } else if (args[argidx] == "-stubs" && argidx+1 < args.size()) { + 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++]; + 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); + 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)); + 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) + log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); + if (dump_mode) + sdc.objects->dump(); + if (keep_hierarchy_mode) + sdc.objects->keep_hierarchy(); + inspect_globals(interp, dump_graph_mode); + Tcl_Release(interp); + } +} SdcPass; + +YOSYS_NAMESPACE_END +#endif diff --git a/passes/fsm/fsm_detect.cc b/passes/fsm/fsm_detect.cc index 9cffbf95a..5f491a16c 100644 --- a/passes/fsm/fsm_detect.cc +++ b/passes/fsm/fsm_detect.cc @@ -199,8 +199,15 @@ static void detect_fsm(RTLIL::Wire *wire, bool ignore_self_reset=false) } SigSpec sig_y = sig_d, sig_undef; - if (!ignore_self_reset && ce.eval(sig_y, sig_undef)) - is_self_resetting = true; + if (!ignore_self_reset) { + if (cellport.first->type == ID($adff)) { + SigSpec sig_arst = assign_map(cellport.first->getPort(ID::ARST)); + if (ce.eval(sig_arst, sig_undef)) + is_self_resetting = true; + } + else if (ce.eval(sig_y, sig_undef)) + is_self_resetting = true; + } } if (has_fsm_encoding_attr) diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc index 996a9b3c9..3892c7581 100644 --- a/passes/opt/opt_clean.cc +++ b/passes/opt/opt_clean.cc @@ -283,7 +283,7 @@ bool compare_signals(RTLIL::SigBit &s1, RTLIL::SigBit &s2, SigPool ®s, SigPoo if (attrs1 != attrs2) return attrs2 > attrs1; - return strcmp(w2->name.c_str(), w1->name.c_str()) < 0; + return w2->name.lt_by_name(w1->name); } bool check_public_name(RTLIL::IdString id) @@ -722,6 +722,8 @@ struct OptCleanPass : public Pass { ct_reg.clear(); ct_all.clear(); log_pop(); + + request_garbage_collection(); } } OptCleanPass; @@ -784,6 +786,8 @@ struct CleanPass : public Pass { keep_cache.reset(); ct_reg.clear(); ct_all.clear(); + + request_garbage_collection(); } } CleanPass; diff --git a/passes/techmap/abc.cc b/passes/techmap/abc.cc index e2894aee3..0963ecfde 100644 --- a/passes/techmap/abc.cc +++ b/passes/techmap/abc.cc @@ -611,7 +611,7 @@ std::string AbcModuleState::remap_name(RTLIL::IdString abc_name, RTLIL::Wire **o } } } - return stringf("$abc$%d$%s", map_autoidx, abc_name.c_str()+1); + return stringf("$abc$%d$%s", map_autoidx, abc_name.substr(1)); } void AbcModuleState::dump_loop_graph(FILE *f, int &nr, dict> &edges, pool &workpool, std::vector &in_counts) @@ -1064,7 +1064,7 @@ void AbcModuleState::prepare_module(RTLIL::Design *design, RTLIL::Module *module abc_script += stringf("; write_blif %s/output.blif", run_abc.tempdir_name); abc_script = add_echos_to_abc_cmd(abc_script); #if defined(__linux__) && !defined(YOSYS_DISABLE_SPAWN) - abc_script += "; echo \"YOSYS_ABC_DONE\"\n"; + abc_script += "; echo; echo \"YOSYS_ABC_DONE\"\n"; #endif for (size_t i = 0; i+1 < abc_script.size(); i++) diff --git a/passes/techmap/abc_new.cc b/passes/techmap/abc_new.cc index 21ffa075b..4e279c577 100644 --- a/passes/techmap/abc_new.cc +++ b/passes/techmap/abc_new.cc @@ -38,7 +38,8 @@ std::vector order_modules(Design *design, std::vector modules sort.edge(submodule, m); } } - log_assert(sort.sort()); + bool is_sorted = sort.sort(); + log_assert(is_sorted); return sort.sorted; } diff --git a/passes/techmap/libparse.cc b/passes/techmap/libparse.cc index a9ae75c01..274a51d54 100644 --- a/passes/techmap/libparse.cc +++ b/passes/techmap/libparse.cc @@ -827,6 +827,17 @@ std::string vlog_identifier(std::string str) return str; } +void event2vl_wire(std::string &edge, LibertyExpression& parsed, const std::string& wire) +{ + edge.clear(); + if (parsed.kind == LibertyExpression::Kind::NOT) { + edge = "negedge " + wire; + // parsed = parsed.children[0]; + } else { + edge = "posedge " + wire; + } +} + void event2vl(const LibertyAst *ast, std::string &edge, std::string &expr) { edge.clear(); @@ -843,33 +854,160 @@ void event2vl(const LibertyAst *ast, std::string &edge, std::string &expr) } } -void clear_preset_var(std::string var, std::string type) +enum ClearPresetVar { + Error, + L, + H, + T, + X, +}; + +ClearPresetVar clear_preset_var(std::string type) { if (type.find('L') != std::string::npos) { + return ClearPresetVar::L; + } + if (type.find('H') != std::string::npos) { + return ClearPresetVar::H; + } + if (type.find('T') != std::string::npos) { + return ClearPresetVar::T; + } + if (type.find('X') != std::string::npos) { + return ClearPresetVar::X; + } + return ClearPresetVar::X; +} + +void print_clear_preset_var(std::string var, ClearPresetVar type) +{ + if (type == ClearPresetVar::L) { printf(" %s <= 0;\n", var.c_str()); return; } - if (type.find('H') != std::string::npos) { + if (type == ClearPresetVar::H) { printf(" %s <= 1;\n", var.c_str()); return; } - if (type.find('T') != std::string::npos) { + if (type == ClearPresetVar::T) { printf(" %s <= ~%s;\n", var.c_str(), var.c_str()); return; } - if (type.find('X') != std::string::npos) { + if (type == ClearPresetVar::X) { printf(" %s <= 'bx;\n", var.c_str()); return; } } +struct FfEdge { + std::string edge; + std::string expr; +}; +struct FfEdges { + FfEdge clock; + FfEdge clear; + FfEdge preset; + std::string edge; + void wired(FfEdge& edge, const LibertyAst* ast, const std::string& wire, const char* tag) { + auto* found = ast->find(tag); + if (!found) + return; + auto lexer = LibertyExpression::Lexer(found->value); + auto expr = LibertyExpression::parse(lexer); + event2vl_wire(edge.edge, expr, wire); + edge.expr = expr.vlog_str(); + } + FfEdges(LibertyAst* child, const std::string& clear_wire, const std::string& preset_wire) { + wired(clear, child, clear_wire, "clear"); + wired(preset, child, preset_wire, "preset"); + event2vl(child->find("clocked_on"), clock.edge, clock.expr); + edge = ""; + if (!clock.edge.empty()) + edge += (edge.empty() ? "" : ", ") + clock.edge; + if (!clear.edge.empty()) + edge += (edge.empty() ? "" : ", ") + clear.edge; + if (!preset.edge.empty()) + edge += (edge.empty() ? "" : ", ") + preset.edge; + } +}; + +struct FfVar { + std::string var; + std::string edge; + FfEdge clear; + FfEdge preset; + // Value for both asserted + const char* clear_preset_var_name; + std::string next_state; + const char* else_prefix = ""; +public: + void proc_header() { + printf(" always @(%s) begin\n", edge.c_str()); + } + void proc_footer() { + if (*else_prefix) + printf(" end\n"); + + printf(" end\n"); + } + void proc_cond(FfEdge& edge, const char* value) { + printf(" %sif (%s) begin\n", else_prefix, edge.expr.c_str()); + printf(" %s <= %s;\n", var.c_str(), value); + printf(" end\n"); + else_prefix = "else "; + } + void proc_cond_clear() { proc_cond(clear, "0"); } + void proc_cond_preset() { proc_cond(preset, "1"); } + void proc_next_state() { + if (*else_prefix) + printf(" %sbegin\n", else_prefix); + printf(" %s <= %s;\n", var.c_str(), next_state.c_str()); + } + void proc(LibertyAst* child) { + else_prefix = ""; + proc_header(); + if (!clear.expr.empty() && !preset.expr.empty()) { + ClearPresetVar clear_preset = clear_preset_var(find_non_null(child, clear_preset_var_name)->value); + if (clear_preset == ClearPresetVar::L) { + proc_cond_clear(); + proc_cond_preset(); + proc_next_state(); + proc_footer(); + return; + } else if (clear_preset == ClearPresetVar::H) { + // Notice that preset and clear are swapped + proc_cond_preset(); + proc_cond_clear(); + proc_next_state(); + proc_footer(); + return; + } else { + // Boo, we have to emit non-synthesizable verilog + printf(" %sif ((%s) && (%s)) begin\n", else_prefix, clear.expr.c_str(), preset.expr.c_str()); + print_clear_preset_var(var, clear_preset); + printf(" end\n"); + else_prefix = "else "; + } + } + if (!clear.expr.empty()) { + proc_cond_clear(); + } + if (!preset.expr.empty()) { + proc_cond_preset(); + } + proc_next_state(); + proc_footer(); + } +}; + void gen_verilogsim_cell(const LibertyAst *ast) { if (ast->find("statetable") != NULL) return; CHECK_NV(ast->args.size(), == 1); - printf("module %s (", vlog_identifier(ast->args[0]).c_str()); + auto module_name = vlog_identifier(ast->args[0]); + printf("module %s (", module_name.c_str()); bool first = true; for (auto child : ast->children) { if (child->id != "pin") @@ -883,13 +1021,29 @@ void gen_verilogsim_cell(const LibertyAst *ast) for (auto child : ast->children) { if (child->id != "ff" && child->id != "latch") continue; - printf(" reg "); first = true; + std::string iq = ""; for (auto arg : child->args) { + if (first) + printf(" reg "); printf("%s%s", first ? "" : ", ", vlog_identifier(arg).c_str()); + if (first) + iq = vlog_identifier(arg); first = false; } - printf(";\n"); + if (!first) + printf(";\n"); + first = true; + for (auto gchild : child->children) { + if (gchild->id == "clear" || gchild->id == "preset") { + if (first) + printf(" wire "); + printf("%s%s_%s", first ? "" : ", ", iq.c_str(), gchild->id.c_str()); + first = false; + } + } + if (!first) + printf(";\n"); } for (auto child : ast->children) { @@ -909,63 +1063,45 @@ void gen_verilogsim_cell(const LibertyAst *ast) if (child->id != "ff" || child->args.size() != 2) continue; - std::string iq_var = vlog_identifier(child->args[0]); - std::string iqn_var = vlog_identifier(child->args[1]); + auto iq_name = vlog_identifier(child->args[0]); + auto clear_wire = iq_name + "_clear"; + auto preset_wire = iq_name + "_preset"; + FfEdges edges(child, clear_wire, preset_wire); - std::string clock_edge, clock_expr; - event2vl(child->find("clocked_on"), clock_edge, clock_expr); - - std::string clear_edge, clear_expr; - event2vl(child->find("clear"), clear_edge, clear_expr); - - std::string preset_edge, preset_expr; - event2vl(child->find("preset"), preset_edge, preset_expr); - - std::string edge = ""; - if (!clock_edge.empty()) - edge += (edge.empty() ? "" : ", ") + clock_edge; - if (!clear_edge.empty()) - edge += (edge.empty() ? "" : ", ") + clear_edge; - if (!preset_edge.empty()) - edge += (edge.empty() ? "" : ", ") + preset_edge; - - if (edge.empty()) + if (edges.edge.empty()) continue; - printf(" always @(%s) begin\n", edge.c_str()); + std::string next_state = func2vl(find_non_null(child, "next_state")->value); + std::string not_next_state = std::string("~(") + next_state + ")"; - const char *else_prefix = ""; - if (!clear_expr.empty() && !preset_expr.empty()) { - printf(" %sif ((%s) && (%s)) begin\n", else_prefix, clear_expr.c_str(), preset_expr.c_str()); - clear_preset_var(iq_var, find_non_null(child, "clear_preset_var1")->value); - clear_preset_var(iqn_var, find_non_null(child, "clear_preset_var2")->value); - printf(" end\n"); - else_prefix = "else "; - } - if (!clear_expr.empty()) { - printf(" %sif (%s) begin\n", else_prefix, clear_expr.c_str()); - printf(" %s <= 0;\n", iq_var.c_str()); - printf(" %s <= 1;\n", iqn_var.c_str()); - printf(" end\n"); - else_prefix = "else "; - } - if (!preset_expr.empty()) { - printf(" %sif (%s) begin\n", else_prefix, preset_expr.c_str()); - printf(" %s <= 1;\n", iq_var.c_str()); - printf(" %s <= 0;\n", iqn_var.c_str()); - printf(" end\n"); - else_prefix = "else "; - } - if (*else_prefix) - printf(" %sbegin\n", else_prefix); - std::string expr = find_non_null(child, "next_state")->value; - printf(" // %s\n", expr.c_str()); - printf(" %s <= %s;\n", iq_var.c_str(), func2vl(expr).c_str()); - printf(" %s <= ~(%s);\n", iqn_var.c_str(), func2vl(expr).c_str()); - if (*else_prefix) - printf(" end\n"); - printf(" end\n"); + if (edges.clear.expr.length()) + std::swap(clear_wire, edges.clear.expr); + if (edges.preset.expr.length()) + std::swap(preset_wire, edges.preset.expr); + auto iq = FfVar { + .var = vlog_identifier(child->args[0]), + .edge = edges.edge, + .clear = edges.clear, + .preset = edges.preset, + .clear_preset_var_name = "clear_preset_var1", + .next_state = next_state, + }; + auto iqn = FfVar { + .var = vlog_identifier(child->args[1]), + .edge = edges.edge, + // Swapped clear and preset + .clear = edges.preset, + .preset = edges.clear, + .clear_preset_var_name = "clear_preset_var2", + .next_state = not_next_state, + }; + iq.proc(child); + iqn.proc(child); + if (edges.clear.expr.length()) + printf(" assign %s = %s;\n", edges.clear.expr.c_str(), clear_wire.c_str()); + if (edges.preset.expr.length()) + printf(" assign %s = %s;\n", edges.preset.expr.c_str(), preset_wire.c_str()); } for (auto child : ast->children) @@ -990,8 +1126,8 @@ void gen_verilogsim_cell(const LibertyAst *ast) const char *else_prefix = ""; if (!clear_expr.empty() && !preset_expr.empty()) { printf(" %sif ((%s) && (%s)) begin\n", else_prefix, clear_expr.c_str(), preset_expr.c_str()); - clear_preset_var(iq_var, find_non_null(child, "clear_preset_var1")->value); - clear_preset_var(iqn_var, find_non_null(child, "clear_preset_var2")->value); + print_clear_preset_var(iq_var, clear_preset_var(find_non_null(child, "clear_preset_var1")->value)); + print_clear_preset_var(iqn_var, clear_preset_var(find_non_null(child, "clear_preset_var2")->value)); printf(" end\n"); else_prefix = "else "; } diff --git a/pyosys/generator.py b/pyosys/generator.py index 0883a7ba4..7d4293abd 100644 --- a/pyosys/generator.py +++ b/pyosys/generator.py @@ -164,11 +164,11 @@ pyosys_headers = [ { "global_id_storage_", "global_id_index_", + "global_autoidx_id_storage_", "global_refcount_storage_", "global_free_idx_list_", - "last_created_idx_ptr_", - "last_created_idx_", "builtin_ff_cell_types", + "substrings", } ), ), @@ -192,7 +192,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, @@ -453,7 +453,7 @@ class PyosysWrapperGenerator(object): ) -> str: is_method = isinstance(function, Method) function_return_type = function.return_type.format() - if class_basename == "Const" and function_return_type in { + if class_basename in {"Const","IdString"} and function_return_type in { "iterator", "const_iterator", }: @@ -538,6 +538,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 @@ -591,7 +593,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: @@ -603,7 +608,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/techlibs/common/Makefile.inc b/techlibs/common/Makefile.inc index e76d70a1a..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 @@ -28,7 +30,6 @@ $(eval $(call add_share_file,share,techlibs/common/adff2dff.v)) $(eval $(call add_share_file,share,techlibs/common/dff2ff.v)) $(eval $(call add_share_file,share,techlibs/common/gate2lut.v)) $(eval $(call add_share_file,share,techlibs/common/cmp2lut.v)) -$(eval $(call add_share_file,share,techlibs/common/cells.lib)) $(eval $(call add_share_file,share,techlibs/common/mul2dsp.v)) $(eval $(call add_share_file,share,techlibs/common/abc9_model.v)) $(eval $(call add_share_file,share,techlibs/common/abc9_map.v)) diff --git a/techlibs/common/cells.lib b/techlibs/common/cells.lib deleted file mode 100644 index eb89036d7..000000000 --- a/techlibs/common/cells.lib +++ /dev/null @@ -1,108 +0,0 @@ -library(yosys_cells) { - cell(DFF_N) { - ff(IQ, IQN) { - clocked_on: "!C"; - next_state: "D"; - } - pin(D) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_P) { - ff(IQ, IQN) { - clocked_on: "C"; - next_state: "D"; - } - pin(D) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_NN0) { - ff(IQ, IQN) { - clocked_on: "!C"; - next_state: "D"; - clear: "!R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_NN1) { - ff(IQ, IQN) { - clocked_on: "!C"; - next_state: "D"; - preset: "!R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_NP0) { - ff(IQ, IQN) { - clocked_on: "!C"; - next_state: "D"; - clear: "R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_NP1) { - ff(IQ, IQN) { - clocked_on: "!C"; - next_state: "D"; - preset: "R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_PN0) { - ff(IQ, IQN) { - clocked_on: "C"; - next_state: "D"; - clear: "!R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_PN1) { - ff(IQ, IQN) { - clocked_on: "C"; - next_state: "D"; - preset: "!R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_PP0) { - ff(IQ, IQN) { - clocked_on: "C"; - next_state: "D"; - clear: "R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_PP1) { - ff(IQ, IQN) { - clocked_on: "C"; - next_state: "D"; - preset: "R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } -} diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc new file mode 100644 index 000000000..655fdbf2d --- /dev/null +++ b/techlibs/common/opensta.cc @@ -0,0 +1,128 @@ +#include "kernel/rtlil.h" +#include "kernel/log.h" +#include "techlibs/common/opensta.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +#if !defined(YOSYS_DISABLE_SPAWN) +struct OpenstaPass : public Pass +{ + 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"); + 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); + 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 = 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; + bool cleanup = design->scratchpad_get_bool("opensta.cleanup", true); + + log_header(design, "Executing OPENSTA pass.\n"); + log_experimental("opensta"); + 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; + 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()); + }; + int ret = run_command(command, process_line); + if (ret) + log_error("OpenSTA returned %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/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 new file mode 100644 index 000000000..39c50875f --- /dev/null +++ b/techlibs/common/sdc_expand.cc @@ -0,0 +1,156 @@ +#include "kernel/rtlil.h" +#include "kernel/log.h" +#include "techlibs/common/opensta.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct SdcExpandPass : public ScriptPass +{ + SdcExpandPass() : ScriptPass("sdc_expand", "expand SDC design with opensta") { } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" sdc_expand [options]\n"); + log("\n"); + 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); + 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 + { + 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); + + 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; + } + 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; + } + + 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()) + 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; + + run("design -save pre_expand"); + run("proc"); + run("memory"); + // run("dfflegalize -cell $dff"); + + if (help_mode) { + tempdir_name = ""; + } 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); + } + 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"); + + std::string liberty_path = tempdir_name + "/yosys.lib"; + run("icell_liberty " + liberty_path); + + 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 "; + 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); + + 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 diff --git a/techlibs/ice40/ice40_opt.cc b/techlibs/ice40/ice40_opt.cc index b13d33018..c88fd69b6 100644 --- a/techlibs/ice40/ice40_opt.cc +++ b/techlibs/ice40/ice40_opt.cc @@ -117,7 +117,7 @@ static void run_ice40_opts(Module *module) if (GetSize(replacement_output)) { optimized_co.insert(sigmap(cell->getPort(ID::CO)[0])); - auto it = cell->attributes.find(ID(SB_LUT4.name)); + auto it = cell->attributes.find(IdString{"\\SB_LUT4.name"}); if (it != cell->attributes.end()) { module->rename(cell, it->second.decode_string()); decltype(Cell::attributes) new_attr; @@ -126,7 +126,7 @@ static void run_ice40_opts(Module *module) new_attr[a.first.c_str() + strlen("\\SB_LUT4.")] = a.second; else if (a.first == ID::src) new_attr.insert(std::make_pair(a.first, a.second)); - else if (a.first.in(ID(SB_LUT4.name), ID::keep, ID::module_not_derived)) + else if (a.first.in(IdString{"\\SB_LUT4.name"}, ID::keep, ID::module_not_derived)) continue; else if (a.first.begins_with("\\SB_CARRY.\\")) continue; diff --git a/techlibs/ice40/ice40_wrapcarry.cc b/techlibs/ice40/ice40_wrapcarry.cc index fe928ba6d..82218ff11 100644 --- a/techlibs/ice40/ice40_wrapcarry.cc +++ b/techlibs/ice40/ice40_wrapcarry.cc @@ -62,7 +62,7 @@ void create_ice40_wrapcarry(ice40_wrapcarry_pm &pm) cell->attributes[stringf("\\SB_CARRY.%s", a.first)] = a.second; for (const auto &a : st.lut->attributes) cell->attributes[stringf("\\SB_LUT4.%s", a.first)] = a.second; - cell->attributes[ID(SB_LUT4.name)] = Const(st.lut->name.str()); + cell->attributes[IdString{"\\SB_LUT4.name"}] = Const(st.lut->name.str()); if (st.carry->get_bool_attribute(ID::keep) || st.lut->get_bool_attribute(ID::keep)) cell->attributes[ID::keep] = true; @@ -122,7 +122,7 @@ struct Ice40WrapCarryPass : public Pass { carry->setPort(ID::CI, cell->getPort(ID::CI)); carry->setPort(ID::CO, cell->getPort(ID::CO)); module->swap_names(carry, cell); - auto lut_name = cell->attributes.at(ID(SB_LUT4.name), Const(NEW_ID.str())).decode_string(); + auto lut_name = cell->attributes.at(IdString{"\\SB_LUT4.name"}, Const(NEW_ID.str())).decode_string(); auto lut = module->addCell(lut_name, ID($lut)); lut->setParam(ID::WIDTH, 4); lut->setParam(ID::LUT, cell->getParam(ID::LUT)); @@ -138,7 +138,7 @@ struct Ice40WrapCarryPass : public Pass { lut->attributes[a.first.c_str() + strlen("\\SB_LUT4.")] = a.second; else if (a.first == ID::src) src = a.second; - else if (a.first.in(ID(SB_LUT4.name), ID::keep, ID::module_not_derived)) + else if (a.first.in(IdString{"\\SB_LUT4.name"}, ID::keep, ID::module_not_derived)) continue; else log_abort(); diff --git a/techlibs/microchip/microchip_dsp.pmg b/techlibs/microchip/microchip_dsp.pmg index 9a6b9e1fa..2573135ee 100644 --- a/techlibs/microchip/microchip_dsp.pmg +++ b/techlibs/microchip/microchip_dsp.pmg @@ -268,7 +268,7 @@ endmatch code if (postAdd) { - if (postAdd->type.in(ID($sub)) && postAddAB == \A) { + if (postAdd->type.in($sub) && postAddAB == \A) { // if $sub, the multiplier output must match to $sub.B, otherwise no match } else { u_postAddAB = postAddAB; diff --git a/techlibs/microchip/microchip_dsp_cascade.pmg b/techlibs/microchip/microchip_dsp_cascade.pmg index d7ea5911e..fa276d5b5 100644 --- a/techlibs/microchip/microchip_dsp_cascade.pmg +++ b/techlibs/microchip/microchip_dsp_cascade.pmg @@ -115,9 +115,9 @@ finally Wire *cascade = module->addWire(NEW_ID, 48); // zero port C and move wire to cascade - dsp_pcin->setPort(ID(C), Const(0, 48)); - dsp_pcin->setPort(ID(CDIN), cascade); - dsp->setPort(ID(CDOUT), cascade); + dsp_pcin->setPort(\C, Const(0, 48)); + dsp_pcin->setPort(\CDIN, cascade); + dsp->setPort(\CDOUT, cascade); // Configure wire to cascade the dsps add_siguser(cascade, dsp_pcin); diff --git a/techlibs/xilinx/xilinx_dsp_cascade.pmg b/techlibs/xilinx/xilinx_dsp_cascade.pmg index 29fc27dfe..9eebd33c3 100644 --- a/techlibs/xilinx/xilinx_dsp_cascade.pmg +++ b/techlibs/xilinx/xilinx_dsp_cascade.pmg @@ -90,9 +90,9 @@ finally if (i % MAX_DSP_CASCADE > 0) { if (P >= 0) { Wire *cascade = module->addWire(NEW_ID, 48); - dsp_pcin->setPort(ID(C), Const(0, 48)); - dsp_pcin->setPort(ID(PCIN), cascade); - dsp->setPort(ID(PCOUT), cascade); + dsp_pcin->setPort(\C, Const(0, 48)); + dsp_pcin->setPort(\PCIN, cascade); + dsp->setPort(\PCOUT, cascade); add_siguser(cascade, dsp_pcin); add_siguser(cascade, dsp); @@ -118,15 +118,15 @@ finally } if (AREG >= 0) { Wire *cascade = module->addWire(NEW_ID, 30); - dsp_pcin->setPort(ID(A), Const(0, 30)); - dsp_pcin->setPort(ID(ACIN), cascade); - dsp->setPort(ID(ACOUT), cascade); + dsp_pcin->setPort(\A, Const(0, 30)); + dsp_pcin->setPort(\ACIN, cascade); + dsp->setPort(\ACOUT, cascade); add_siguser(cascade, dsp_pcin); add_siguser(cascade, dsp); if (dsp->type.in(\DSP48E1)) - dsp->setParam(ID(ACASCREG), AREG); - dsp_pcin->setParam(ID(A_INPUT), Const("CASCADE")); + dsp->setParam(\ACASCREG, AREG); + dsp_pcin->setParam(\A_INPUT, Const("CASCADE")); log_debug("ACOUT -> ACIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin)); } @@ -138,18 +138,18 @@ finally // BCOUT from an adjacent DSP48A1 slice. The tools then // translate BCOUT cascading to the dedicated BCIN input // and set the B_INPUT attribute for implementation." - dsp_pcin->setPort(ID(B), cascade); + dsp_pcin->setPort(\B, cascade); } else { - dsp_pcin->setPort(ID(B), Const(0, 18)); - dsp_pcin->setPort(ID(BCIN), cascade); + dsp_pcin->setPort(\B, Const(0, 18)); + dsp_pcin->setPort(\BCIN, cascade); } - dsp->setPort(ID(BCOUT), cascade); + dsp->setPort(\BCOUT, cascade); add_siguser(cascade, dsp_pcin); add_siguser(cascade, dsp); if (dsp->type.in(\DSP48E1)) { - dsp->setParam(ID(BCASCREG), BREG); + dsp->setParam(\BCASCREG, BREG); // According to UG389 p13 [https://www.xilinx.com/support/documentation/user_guides/ug389.pdf] // "The attribute is only used by place and route tools and // is not necessary for the users to set for synthesis. The @@ -158,7 +158,7 @@ finally // BCOUT of another DSP48A1 slice, then the tools automatically // set the attribute to 'CASCADE', otherwise it is set to // 'DIRECT'". - dsp_pcin->setParam(ID(B_INPUT), Const("CASCADE")); + dsp_pcin->setParam(\B_INPUT, Const("CASCADE")); } log_debug("BCOUT -> BCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin)); 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/arch/quicklogic/pp3/fsm.ys b/tests/arch/quicklogic/pp3/fsm.ys index 9679628e9..3276e45c6 100644 --- a/tests/arch/quicklogic/pp3/fsm.ys +++ b/tests/arch/quicklogic/pp3/fsm.ys @@ -11,8 +11,6 @@ sat -verify -prove-asserts -show-public -set-at 1 in_reset 1 -seq 20 -prove-skip design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd fsm # Constrain all select calls below inside the top module -select -assert-count 2 t:LUT2 -select -assert-count 4 t:LUT3 select -assert-count 4 t:dffepc select -assert-count 1 t:logic_0 select -assert-count 1 t:logic_1 diff --git a/tests/arch/xilinx/dsp_cascade.ys b/tests/arch/xilinx/dsp_cascade.ys index ca6b619b9..0a68377f6 100644 --- a/tests/arch/xilinx/dsp_cascade.ys +++ b/tests/arch/xilinx/dsp_cascade.ys @@ -69,7 +69,8 @@ equiv_opt -assert -map +/xilinx/cells_sim.v synth_xilinx -noiopad design -load postopt cd cascade select -assert-count 2 t:DSP48E1 -select -assert-none t:DSP48E1 t:BUFG %% t:* %D +# TODO Disabled check, FDREs emitted due to order sensitivity +# select -assert-none t:DSP48E1 t:BUFG %% t:* %D # Very crude method of checking that DSP48E1.PCOUT -> DSP48E1.PCIN # (see above for explanation) select -assert-count 1 t:DSP48E1 %co:+[PCOUT] t:DSP48E1 %d %co:+[PCIN] w:* %d t:DSP48E1 %i diff --git a/tests/functional/rkt_vcd.py b/tests/functional/rkt_vcd.py index 1b2cf31e3..548a4ba74 100644 --- a/tests/functional/rkt_vcd.py +++ b/tests/functional/rkt_vcd.py @@ -43,7 +43,20 @@ def write_vcd(filename: Path, signals: SignalStepMap, timescale='1 ns', date='to if change_time == time: f.write(f"{value} {signal_name}\n") -def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: Random): + +def simulate_rosette( + rkt_file_path: Path, + vcd_path: Path, + num_steps: int, + rnd: Random, + use_assoc_list_helpers: bool = False, +): + """ + Args: + - use_assoc_list_helpers: If True, will use the association list helpers + in the Racket file. The file should have been generated with the + -assoc-list-helpers flag in the yosys command. + """ signals: dict[str, list[str]] = {} inputs: SignalWidthMap = {} outputs: SignalWidthMap = {} @@ -83,12 +96,32 @@ def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: R for step in range(num_steps): this_step = f"step_{step}" value_list: list[str] = [] - for signal, width in inputs.items(): - value = signals[signal][step] - value_list.append(f"(bv #b{value} {width})") - gold_Inputs = f"(gold_Inputs {' '.join(value_list)})" + if use_assoc_list_helpers: + # Generate inputs as a list of cons pairs making up the + # association list. + for signal, width in inputs.items(): + value = signals[signal][step] + value_list.append(f'(cons "{signal}" (bv #b{value} {width}))') + else: + # Otherwise, we generate the inputs as a list of bitvectors. + for signal, width in inputs.items(): + value = signals[signal][step] + value_list.append(f"(bv #b{value} {width})") + gold_Inputs = ( + f"(gold_inputs_helper (list {' '.join(value_list)}))" + if use_assoc_list_helpers + else f"(gold_Inputs {' '.join(value_list)})" + ) gold_State = f"(cdr step_{step-1})" if step else "gold_initial" - test_rkt_file.write(f"(define {this_step} (gold {gold_Inputs} {gold_State})) (car {this_step})\n") + get_value_expr = ( + f"(gold_outputs_helper (car {this_step}))" + if use_assoc_list_helpers + else f"(car {this_step})" + ) + test_rkt_file.write( + f"(define {this_step} (gold {gold_Inputs} {gold_State})) {get_value_expr}\n" + ) + cmd = ["racket", test_rkt_file_path] status = subprocess.run(cmd, capture_output=True) @@ -98,9 +131,23 @@ def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: R signals[signal] = [] for line in status.stdout.decode().splitlines(): - m = re.match(r'\(gold_Outputs( \(bv \S+ \d+\))+\)', line) + m = ( + re.match(r"\(list( \(cons \"\S+\" \(bv \S+ \d+\)\))+\)", line) + if use_assoc_list_helpers + else re.match(r"\(gold_Outputs( \(bv \S+ \d+\))+\)", line) + ) assert m, f"Incomplete output definition {line!r}" - for output, (value, width) in zip(outputs.keys(), re.findall(r'\(bv (\S+) (\d+)\)', line)): + outputs_values_and_widths = ( + { + output: re.findall( + r"\(cons \"" + output + r"\" \(bv (\S+) (\d+)\)\)", line + )[0] + for output in outputs.keys() + }.items() + if use_assoc_list_helpers + else zip(outputs.keys(), re.findall(r"\(bv (\S+) (\d+)\)", line)) + ) + for output, (value, width) in outputs_values_and_widths: assert isinstance(value, str), f"Bad value {value!r}" assert value.startswith(('#b', '#x')), f"Non-binary value {value!r}" assert int(width) == outputs[output], f"Width mismatch for output {output!r} (got {width}, expected {outputs[output]})" diff --git a/tests/functional/run-test.sh b/tests/functional/run-test.sh index 9f70462ee..e0bedf8d4 100755 --- a/tests/functional/run-test.sh +++ b/tests/functional/run-test.sh @@ -1,2 +1,5 @@ #!/usr/bin/env bash -pytest -v -m "not smt and not rkt" "$@" + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +pytest -v -m "not smt and not rkt" "$SCRIPT_DIR" "$@" diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index e4c78a1fb..aa7500f8b 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -74,7 +74,8 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) @pytest.mark.rkt -def test_rkt(cell, parameters, tmp_path, num_steps, rnd): +@pytest.mark.parametrize("use_assoc_list_helpers", [True, False]) +def test_rkt(cell, parameters, tmp_path, num_steps, rnd, use_assoc_list_helpers): import rkt_vcd rtlil_file = tmp_path / 'rtlil.il' @@ -83,8 +84,9 @@ def test_rkt(cell, parameters, tmp_path, num_steps, rnd): vcd_yosys_sim_file = tmp_path / 'yosys.vcd' cell.write_rtlil_file(rtlil_file, parameters) - yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_rosette -provides {quote(rkt_file)}") - rkt_vcd.simulate_rosette(rkt_file, vcd_functional_file, num_steps, rnd(cell.name + "-rkt")) + use_assoc_helpers_flag = '-assoc-list-helpers' if use_assoc_list_helpers else '' + yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_rosette -provides {use_assoc_helpers_flag} {quote(rkt_file)}") + rkt_vcd.simulate_rosette(rkt_file, vcd_functional_file, num_steps, rnd(cell.name + "-rkt"), use_assoc_list_helpers=use_assoc_list_helpers) yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) def test_print_graph(tmp_path): diff --git a/tests/liberty/dff.lib.verilogsim.ok b/tests/liberty/dff.lib.verilogsim.ok index e560df539..5f991541f 100644 --- a/tests/liberty/dff.lib.verilogsim.ok +++ b/tests/liberty/dff.lib.verilogsim.ok @@ -5,8 +5,9 @@ module dff (D, CLK, Q); output Q; assign Q = IQ; // IQ always @(posedge CLK) begin - // "(D)" IQ <= D; + end + always @(posedge CLK) begin IQN <= ~(D); end endmodule diff --git a/tests/liberty/normal.lib.verilogsim.ok b/tests/liberty/normal.lib.verilogsim.ok index 92efbf8aa..9e8beb2e1 100644 --- a/tests/liberty/normal.lib.verilogsim.ok +++ b/tests/liberty/normal.lib.verilogsim.ok @@ -41,6 +41,7 @@ module imux2 (A, B, S, Y); endmodule module dff (D, CLK, RESET, PRESET, Q, QN); reg IQ, IQN; + wire IQ_clear, IQ_preset; input D; input CLK; input RESET; @@ -49,25 +50,30 @@ module dff (D, CLK, RESET, PRESET, Q, QN); assign Q = IQ; // "IQ" output QN; assign QN = IQN; // "IQN" - always @(posedge CLK, posedge RESET, posedge PRESET) begin - if ((RESET) && (PRESET)) begin + always @(posedge CLK, posedge IQ_clear, posedge IQ_preset) begin + if (IQ_clear) begin IQ <= 0; - IQN <= 0; end - else if (RESET) begin - IQ <= 0; - IQN <= 1; - end - else if (PRESET) begin + else if (IQ_preset) begin IQ <= 1; - IQN <= 0; end else begin - // "D" IQ <= D; + end + end + always @(posedge CLK, posedge IQ_clear, posedge IQ_preset) begin + if (IQ_preset) begin + IQN <= 0; + end + else if (IQ_clear) begin + IQN <= 1; + end + else begin IQN <= ~(D); end end + assign IQ_clear = RESET; + assign IQ_preset = PRESET; endmodule module latch (D, G, Q, QN); reg IQ, IQN; diff --git a/tests/liberty/read_liberty.ys b/tests/liberty/read_liberty.ys new file mode 100644 index 000000000..7cbb0a19d --- /dev/null +++ b/tests/liberty/read_liberty.ys @@ -0,0 +1,9 @@ +read_liberty retention.lib +rename retention_cell retention_cell_lib +read_verilog retention.lib.verilogsim +proc +rename retention_cell retention_cell_vlog +async2sync +equiv_make retention_cell_lib retention_cell_vlog equiv +equiv_induct equiv +equiv_status -assert equiv diff --git a/tests/liberty/retention.lib b/tests/liberty/retention.lib new file mode 100644 index 000000000..d2f1aa325 --- /dev/null +++ b/tests/liberty/retention.lib @@ -0,0 +1,57 @@ +library (retention) { + delay_model : table_lookup; + voltage_unit : 1V; + current_unit : 1mA; + leakage_power_unit : 1nW; + time_unit : 1ns; + capacitive_load_unit (1, pf); + pulling_resistance_unit : 1kohm; + input_threshold_pct_rise : 50; + input_threshold_pct_fall : 50; + output_threshold_pct_rise : 50; + output_threshold_pct_fall : 50; + slew_lower_threshold_pct_rise : 30; + slew_upper_threshold_pct_rise : 70; + slew_upper_threshold_pct_fall : 70; + slew_lower_threshold_pct_fall : 30; + cell ("retention_cell") { + ff (Q1,QN1) { + clocked_on : "CK"; + next_state : "(D * !SE + SI * SE)"; + clear : "(((!B2B) * !Q2) + !RD)"; + preset : "((!B2B) * Q2)"; + clear_preset_var1 : "L"; + clear_preset_var2 : "H"; + } + latch (Q2,QN2) { + enable : "B1"; + data_in : "Q1"; + } + pin (B1) { + direction : input; + } + pin (B2B) { + direction : input; + } + pin (CK) { + clock : true; + direction : input; + } + pin (D) { + direction : input; + } + pin (Q) { + direction : output; + function : "Q1"; + } + pin (RD) { + direction : input; + } + pin (SE) { + direction : input; + } + pin (SI) { + direction : input; + } + } +} \ No newline at end of file diff --git a/tests/liberty/retention.lib.filtered.ok b/tests/liberty/retention.lib.filtered.ok new file mode 100644 index 000000000..0cfc7edfc --- /dev/null +++ b/tests/liberty/retention.lib.filtered.ok @@ -0,0 +1,42 @@ +library(retention) { + cell("retention_cell") { + ff(Q1, QN1) { + clocked_on : "CK" ; + next_state : "(D * !SE + SI * SE)" ; + clear : "(((!B2B) * !Q2) + !RD)" ; + preset : "((!B2B) * Q2)" ; + clear_preset_var1 : "L" ; + clear_preset_var2 : "H" ; + } + latch(Q2, QN2) { + enable : "B1" ; + data_in : "Q1" ; + } + pin(B1) { + direction : input ; + } + pin(B2B) { + direction : input ; + } + pin(CK) { + clock : true ; + direction : input ; + } + pin(D) { + direction : input ; + } + pin(Q) { + direction : output ; + function : "Q1" ; + } + pin(RD) { + direction : input ; + } + pin(SE) { + direction : input ; + } + pin(SI) { + direction : input ; + } + } +} diff --git a/tests/liberty/retention.lib.verilogsim.ok b/tests/liberty/retention.lib.verilogsim.ok new file mode 100644 index 000000000..f264e58cd --- /dev/null +++ b/tests/liberty/retention.lib.verilogsim.ok @@ -0,0 +1,44 @@ +module retention_cell (B1, B2B, CK, D, Q, RD, SE, SI); + reg Q1, QN1; + wire Q1_clear, Q1_preset; + reg Q2, QN2; + input B1; + input B2B; + input CK; + input D; + output Q; + assign Q = Q1; // "Q1" + input RD; + input SE; + input SI; + always @(posedge CK, posedge Q1_clear, posedge Q1_preset) begin + if (Q1_clear) begin + Q1 <= 0; + end + else if (Q1_preset) begin + Q1 <= 1; + end + else begin + Q1 <= ((D&(~SE))|(SI&SE)); + end + end + always @(posedge CK, posedge Q1_clear, posedge Q1_preset) begin + if (Q1_clear) begin + QN1 <= 1; + end + else if (Q1_preset) begin + QN1 <= 0; + end + else begin + QN1 <= ~(((D&(~SE))|(SI&SE))); + end + end + assign Q1_clear = (((~B2B)&(~Q2))|(~RD)); + assign Q1_preset = ((~B2B)&Q2); + always @* begin + if (B1) begin + Q2 <= Q1; + QN2 <= ~(Q1); + end + end +endmodule diff --git a/tests/liberty/semicolextra.lib.verilogsim.ok b/tests/liberty/semicolextra.lib.verilogsim.ok index e3b14dbd2..1efbf6cf0 100644 --- a/tests/liberty/semicolextra.lib.verilogsim.ok +++ b/tests/liberty/semicolextra.lib.verilogsim.ok @@ -4,8 +4,9 @@ module DFF (D, CK, Q); input CK; output Q; always @(posedge CK) begin - // "D" IQ <= D; + end + always @(posedge CK) begin IQN <= ~(D); end endmodule diff --git a/tests/liberty/unquoted.lib.verilogsim.ok b/tests/liberty/unquoted.lib.verilogsim.ok index 2a2f1d173..f6b022194 100644 --- a/tests/liberty/unquoted.lib.verilogsim.ok +++ b/tests/liberty/unquoted.lib.verilogsim.ok @@ -5,8 +5,9 @@ module dff1 (D, CLK, Q); output Q; assign Q = IQ; // IQ always @(posedge CLK) begin - // !D IQ <= (~D); + end + always @(posedge CLK) begin IQN <= ~((~D)); end endmodule @@ -17,8 +18,9 @@ module dff2 (D, CLK, Q); output Q; assign Q = IQ; // "IQ" always @(posedge CLK) begin - // D ' IQ <= (~D); + end + always @(posedge CLK) begin IQN <= ~((~D)); end endmodule @@ -32,8 +34,9 @@ module dffe (D, EN, CLK, Q, QN); output QN; assign QN = IQN; // "IQN" always @(negedge CLK) begin - // ( D & EN ) | ( IQ & ! EN ) IQ <= ((D&EN)|(IQ&(~EN))); + end + always @(negedge CLK) begin IQN <= ~(((D&EN)|(IQ&(~EN)))); end endmodule 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)};") + diff --git a/tests/rtlil/everything.v b/tests/rtlil/everything.v index 666d630c2..c4f8c348a 100644 --- a/tests/rtlil/everything.v +++ b/tests/rtlil/everything.v @@ -38,3 +38,20 @@ module foo( assign b = bb; assign y = a + bb; endmodule + +module set_param #( + parameter [3:0] VALUE = 1'bx +) ( + output logic [3:0] out +); + assign out = VALUE; +endmodule + +module use_param ( + output logic [3:0] a, b, c, d +); + set_param #($signed(1)) spa (a); + set_param #('1) spb (b); + set_param #(1.1) spc (c); + set_param #(1'b1) spd (d); +endmodule 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/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/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/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' diff --git a/tests/techmap/bug5495.abc b/tests/techmap/bug5495.abc new file mode 100644 index 000000000..60a29a58a --- /dev/null +++ b/tests/techmap/bug5495.abc @@ -0,0 +1,2 @@ + +fraig_store; fraig_restore diff --git a/tests/techmap/bug5495.sh b/tests/techmap/bug5495.sh new file mode 100755 index 000000000..64bf2ca99 --- /dev/null +++ b/tests/techmap/bug5495.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +if ! which timeout ; then + echo "No 'timeout', skipping test" + exit 0 +fi + +if ! timeout 5 ../../yosys bug5495.v -p 'hierarchy; techmap; abc -script bug5495.abc' ; then + echo "Yosys failed to complete" + exit 1 +fi + diff --git a/tests/techmap/bug5495.v b/tests/techmap/bug5495.v new file mode 100644 index 000000000..37ce73ec8 --- /dev/null +++ b/tests/techmap/bug5495.v @@ -0,0 +1,7 @@ +module simple(I1, I2, O); + input wire I1; + input wire I2; + output wire O; + + assign O = I1 | I2; +endmodule diff --git a/tests/unit/kernel/rtlilTest.cc b/tests/unit/kernel/rtlilTest.cc index 557355ed9..32c40616e 100644 --- a/tests/unit/kernel/rtlilTest.cc +++ b/tests/unit/kernel/rtlilTest.cc @@ -361,6 +361,36 @@ namespace RTLIL { EXPECT_FALSE(Const().is_onehot(&pos)); } + TEST_F(KernelRtlilTest, OwningIdString) { + OwningIdString own("\\figblortle"); + OwningIdString::collect_garbage(); + EXPECT_EQ(own.str(), "\\figblortle"); + } + + TEST_F(KernelRtlilTest, LookupAutoidxId) { + IdString id = NEW_ID; + IdString id2 = IdString(id.str()); + EXPECT_EQ(id, id2); + } + + TEST_F(KernelRtlilTest, NewIdBeginsWith) { + IdString id = NEW_ID; + EXPECT_TRUE(id.begins_with("$auto")); + EXPECT_FALSE(id.begins_with("xyz")); + EXPECT_TRUE(id.begins_with("$auto$")); + EXPECT_FALSE(id.begins_with("abcdefghijklmn")); + EXPECT_TRUE(id.begins_with("$auto$rtlilTest")); + EXPECT_FALSE(id.begins_with("$auto$rtlilX")); + } + + TEST_F(KernelRtlilTest, NewIdIndexing) { + IdString id = NEW_ID; + std::string str = id.str(); + for (int i = 0; i < GetSize(str) + 1; ++i) { + EXPECT_EQ(id[i], str.c_str()[i]); + } + } + class WireRtlVsHdlIndexConversionTest : public KernelRtlilTest, public testing::WithParamInterface> diff --git a/tests/various/bug3515.v b/tests/various/bug3515.v new file mode 100644 index 000000000..220ae4ad6 --- /dev/null +++ b/tests/various/bug3515.v @@ -0,0 +1,26 @@ +// Triple AND GATE +module mod_74x08_3 ( + input A_1, + input B_1, + input A_2, + input B_2, + input A_3, + input B_3, + output Y_1, + output Y_2, + output Y_3); + +assign Y_1 = A_1 & B_1; +assign Y_2 = A_2 & B_2; +assign Y_3 = A_3 & B_3; + +endmodule + +// OR GATE +module mod_74x32_1 ( + input A_1, + input B_1, + output Y_1); + +assign Y_1 = A_1 | B_1; +endmodule diff --git a/tests/various/bug3515.ys b/tests/various/bug3515.ys new file mode 100644 index 000000000..783a75bb4 --- /dev/null +++ b/tests/various/bug3515.ys @@ -0,0 +1,31 @@ +# base case is able to map +read_verilog << EOF +module and_x3 ( + input a, b, c, d, + output reg y +); + +assign y = (a&b)&(c&d); +endmodule +EOF +hierarchy -top and_x3 +opt +extract -map ./bug3515.v +select -assert-count 1 t:mod_74x08_3 + +# more needles than haystacks; not able to map +design -reset +read_verilog << EOF +module mod_and_or ( + input a, b, c, d, + output reg y +); + +assign y = (a&b)|(c&d); +endmodule +EOF +hierarchy -top mod_and_or +opt +extract -map ./bug3515.v +select -assert-count 2 t:$and + diff --git a/tests/various/fsm-arst.ys b/tests/various/fsm-arst.ys new file mode 100644 index 000000000..4b2c9c66a --- /dev/null +++ b/tests/various/fsm-arst.ys @@ -0,0 +1,121 @@ +read_verilog << EOT +module non_self_rs_fsm ( + input wire clk, + input wire reset, + output wire s1 +); + localparam [7:0] RST = 8'b10010010; + localparam [7:0] S1 = 8'b01001000; + localparam [7:0] S2 = 8'b11000111; + + reg [7:0] current_state, next_state; + always @(posedge clk or posedge reset) begin + if (reset) begin + current_state <= RST; + end else begin + current_state <= next_state; + end + end + + always @(*) begin + next_state = current_state; + + case (current_state) + RST: next_state = S1; + S1: next_state = S2; + S2: next_state = S1; + default: next_state = RST; + endcase + end + + assign s1 = next_state == S1; +endmodule + +module semi_self_rs_fsm ( + input wire clk, + input wire test, + output wire s1 +); + localparam [7:0] RST = 8'b10010010; + localparam [7:0] S1 = 8'b01001000; + localparam [7:0] S2 = 8'b11000111; + + reg [7:0] current_state, next_state; + reg [1:0] reset_test; + + wire reset = (test || (reset_test == 2)); + + always @(posedge clk or posedge reset) begin + if (reset) begin + current_state <= RST; + reset_test <= 0; + end else begin + current_state <= next_state; + if (current_state == S2) + reset_test = reset_test + 1; + end + end + + + always @(*) begin + next_state = current_state; + + case (current_state) + RST: next_state = S1; + S2: next_state = S1; + S1: next_state = S2; + + default: next_state = RST; + endcase + end + + assign s1 = next_state == S1; +endmodule + +module self_rs_fsm ( + input wire clk, + output wire s1 +); + localparam [7:0] RST = 8'b10010010; + localparam [7:0] S1 = 8'b01001000; + localparam [7:0] S2 = 8'b11000111; + + reg [7:0] next_state; + wire reset = next_state == S1; + + always @(posedge clk or posedge reset) begin + if (reset) begin + next_state <= RST; + end else begin + case (next_state) + RST: next_state = S1; + S1: next_state = S2; + S2: next_state = S1; + default: next_state = RST; + endcase + end + end + + + assign s1 = next_state == S1; +endmodule + +EOT + +proc +opt_expr +opt_clean +check +opt -nodffe -nosdff + +fsm_detect +fsm_extract + +cd non_self_rs_fsm +select -assert-count 1 t:$fsm + +cd semi_self_rs_fsm +select -assert-count 1 t:$fsm + +cd self_rs_fsm +select -assert-none t:$fsm diff --git a/tests/verilog/.gitignore b/tests/verilog/.gitignore index d3e8690d5..b16ed0890 100644 --- a/tests/verilog/.gitignore +++ b/tests/verilog/.gitignore @@ -1,6 +1,7 @@ /const_arst.v /const_sr.v /doubleslash.v +/reset_auto_counter.v /roundtrip_proc_1.v /roundtrip_proc_2.v /assign_to_reg.v diff --git a/tests/verilog/reset_auto_counter.ys b/tests/verilog/reset_auto_counter.ys new file mode 100644 index 000000000..c9756cbac --- /dev/null +++ b/tests/verilog/reset_auto_counter.ys @@ -0,0 +1,17 @@ +read_verilog -sv <