diff --git a/CHANGELOG b/CHANGELOG index 6fb7a92e5..716939992 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,9 +2,26 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.50 .. Yosys 0.51-dev +Yosys 0.51 .. Yosys 0.52-dev -------------------------- +Yosys 0.50 .. Yosys 0.51 +-------------------------- + * New commands and options + - Added "abstract" pass to allow reducing and never increasing + the constraints on a circuit's behavior in a formal verification setting. + + * Various + - "splitcells" pass now splits "aldff" cells. + - FunctionalIR documentation + + * QuickLogic support + - Added IOFF inference for qlf_k6n10f + + * Intel support + - Fixed RAM and DSP support. + - Overall performance improvement for "synth_intel". + Yosys 0.49 .. Yosys 0.50 -------------------------- * Various diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..6c4376cc4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,73 @@ +# Introduction + +Thanks for thinking about contributing to the Yosys project. If this is your +first time contributing to an open source project, please take a look at the +following guide: +https://opensource.guide/how-to-contribute/#orienting-yourself-to-a-new-project. + +Information about the Yosys coding style is available on our Read the Docs: +https://yosys.readthedocs.io/en/latest/yosys_internals/extending_yosys/contributing.html. + +# Using the issue tracker + +The [issue tracker](https://github.com/YosysHQ/yosys/issues) is used for +tracking bugs or other problems with Yosys or its documentation. It is also the +place to go for requesting new features. +When [creating a new issue](https://github.com/YosysHQ/yosys/issues/new/choose), +we have a few templates available. Please make use of these! It will make it +much easier for someone to respond and help. + +### Bug reports + +Before you submit an issue, please have a search of the existing issues in case +one already exists. Making sure that you have a minimal, complete and +verifiable example (MVCE) is a great way to quickly check an existing issue +against a new one. Stack overflow has a guide on [how to create an +MVCE](https://stackoverflow.com/help/minimal-reproducible-example). The +[`bugpoint` +command](https://yosyshq.readthedocs.io/projects/yosys/en/latest/cmd/bugpoint.html) +in Yosys can be helpful for this process. + + +# Using pull requests + +If you are working on something to add to Yosys, or fix something that isn't +working quite right, make a [PR](https://github.com/YosysHQ/yosys/pulls)! An +open PR, even as a draft, tells everyone that you're working on it and they +don't have to. It can also be a useful way to solicit feedback on in-progress +changes. See below to find the best way to [ask us +questions](#asking-questions). + +In general, all changes to the code are done as a PR, with [Continuous +Integration (CI)](https://github.com/YosysHQ/yosys/actions) tools that +automatically run the full suite of tests compiling and running Yosys. Please +make use of this! If you're adding a feature: add a test! Not only does it +verify that your feature is working as expected, but it can also be a handy way +for people to see how the feature is used. If you're fixing a bug: add a test! +If you can, do this first; it's okay if the test starts off failing - you +already know there is a bug. CI also helps to make sure that your changes still +work under a range of compilers, settings, and targets. + + +### Labels + +We use [labels](https://github.com/YosysHQ/yosys/labels) to help categorise +issues and PRs. If a label seems relevant to your work, please do add it; this +also includes the labels beggining with 'status-'. The 'merge-' labels are used +by maintainers for tracking and communicating which PRs are ready and pending +merge; please do not use these labels if you are not a maintainer. + + +# Asking questions + +If you have a question about how to use Yosys, please ask on our [discussions +page](https://github.com/YosysHQ/yosys/discussions) or in our [community +slack](https://join.slack.com/t/yosyshq/shared_invite/zt-1aopkns2q-EiQ97BeQDt_pwvE41sGSuA). +The slack is also a great place to ask questions about developing or +contributing to Yosys. + +We have open dev 'jour fixe' (JF) meetings where developers from YosysHQ and the +community come together to discuss open issues and PRs. This is also a good +place to talk to us about how to implement larger PRs. Please join the +community slack if you would like to join the next meeting, the link is +available in the description of the #devel-discuss channel. diff --git a/Makefile b/Makefile index 0f76f4f67..8f0355c8e 100644 --- a/Makefile +++ b/Makefile @@ -123,8 +123,8 @@ ifneq ($(shell :; command -v brew),) BREW_PREFIX := $(shell brew --prefix)/opt $(info $$BREW_PREFIX is [${BREW_PREFIX}]) ifeq ($(ENABLE_PYOSYS),1) -CXXFLAGS += -I$(BREW_PREFIX)/boost/include/boost -LINKFLAGS += -L$(BREW_PREFIX)/boost/lib +CXXFLAGS += -I$(BREW_PREFIX)/boost/include +LINKFLAGS += -L$(BREW_PREFIX)/boost/lib -L$(BREW_PREFIX)/boost-python3/lib endif CXXFLAGS += -I$(BREW_PREFIX)/readline/include LINKFLAGS += -L$(BREW_PREFIX)/readline/lib @@ -153,7 +153,14 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.50+0 +YOSYS_VER := 0.51+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) +CXXFLAGS += -DYOSYS_VER=\\"$(YOSYS_VER)\\" \ + -DYOSYS_MAJOR=$(YOSYS_MAJOR) \ + -DYOSYS_MINOR=$(YOSYS_MINOR) \ + -DYOSYS_COMMIT=$(YOSYS_COMMIT) # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo @@ -169,7 +176,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline b5170e1.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline c4b5190.. | wc -l`/;" Makefile ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q) @@ -330,8 +337,13 @@ TARGETS += libyosys.so endif ifeq ($(ENABLE_PYOSYS),1) +# python-config --ldflags includes -l and -L, but LINKFLAGS is only -L +LINKFLAGS += $(filter-out -l%,$(shell $(PYTHON_CONFIG) --ldflags)) +LIBS += $(shell $(PYTHON_CONFIG) --libs) +CXXFLAGS += $(shell $(PYTHON_CONFIG) --includes) -DWITH_PYTHON + # Detect name of boost_python library. Some distros use boost_python-py, other boost_python, some only use the major version number, some a concatenation of major and minor version numbers -CHECK_BOOST_PYTHON = (echo "int main(int argc, char ** argv) {return 0;}" | $(CXX) -xc -o /dev/null $(shell $(PYTHON_CONFIG) --ldflags) -l$(1) - > /dev/null 2>&1 && echo "-l$(1)") +CHECK_BOOST_PYTHON = (echo "int main(int argc, char ** argv) {return 0;}" | $(CXX) -xc -o /dev/null $(LINKFLAGS) $(LIBS) -l$(1) - > /dev/null 2>&1 && echo "-l$(1)") BOOST_PYTHON_LIB ?= $(shell \ $(call CHECK_BOOST_PYTHON,boost_python-py$(subst .,,$(PYTHON_VERSION))) || \ $(call CHECK_BOOST_PYTHON,boost_python-py$(PYTHON_MAJOR_VERSION)) || \ @@ -343,11 +355,7 @@ ifeq ($(BOOST_PYTHON_LIB),) $(error BOOST_PYTHON_LIB could not be detected. Please define manually) endif -LIBS += $(shell $(PYTHON_CONFIG) --libs) $(BOOST_PYTHON_LIB) -lboost_system -lboost_filesystem -# python-config --ldflags includes LIBS for some reason -LINKFLAGS += $(filter-out -l%,$(shell $(PYTHON_CONFIG) --ldflags)) -CXXFLAGS += $(shell $(PYTHON_CONFIG) --includes) -DWITH_PYTHON - +LIBS += $(BOOST_PYTHON_LIB) -lboost_system -lboost_filesystem PY_WRAPPER_FILE = kernel/python_wrappers OBJS += $(PY_WRAPPER_FILE).o PY_GEN_SCRIPT= py_wrap_generator @@ -651,6 +659,9 @@ OBJS += libs/fst/fastlz.o OBJS += libs/fst/lz4.o endif +techlibs/%_pm.h: passes/pmgen/pmgen.py techlibs/%.pmg + $(P) mkdir -p $(dir $@) && $(PYTHON_EXECUTABLE) $< -o $@ -p $(notdir $*) $(filter-out $<,$^) + ifneq ($(SMALL),1) OBJS += libs/subcircuit/subcircuit.o @@ -775,6 +786,15 @@ check-git-abc: elif [ -f "$(YOSYS_SRC)/abc/.gitcommit" ] && ! grep -q '\$$Format:%[hH]\$$' "$(YOSYS_SRC)/abc/.gitcommit"; then \ echo "'abc' comes from a tarball. Continuing."; \ exit 0; \ + elif git -C "$(YOSYS_SRC)" submodule status abc 2>/dev/null | grep -q '^+'; then \ + echo "'abc' submodule does not match expected commit."; \ + echo "Run 'git submodule update' to check out the correct version."; \ + echo "Note: If testing a different version of abc, call 'git commit abc' in the Yosys source directory to update the expected commit."; \ + exit 1; \ + elif git -C "$(YOSYS_SRC)" submodule status abc 2>/dev/null | grep -q '^U'; then \ + echo "'abc' submodule has merge conflicts."; \ + echo "Please resolve merge conflicts before continuing."; \ + exit 1; \ elif [ -f "$(YOSYS_SRC)/abc/.gitcommit" ] && grep -q '\$$Format:%[hH]\$$' "$(YOSYS_SRC)/abc/.gitcommit"; then \ echo "Error: 'abc' is not configured as a git submodule."; \ echo "To resolve this:"; \ @@ -997,6 +1017,18 @@ docs/source/cell/word_add.rst: $(TARGETS) $(EXTRA_TARGETS) docs/source/generated/cells.json: docs/source/generated $(TARGETS) $(EXTRA_TARGETS) $(Q) ./$(PROGRAM_PREFIX)yosys -p 'help -dump-cells-json $@' +docs/source/generated/%.cc: backends/%.cc + $(Q) mkdir -p $(@D) + $(Q) cp $< $@ + +# diff returns exit code 1 if the files are different, but it's not an error +docs/source/generated/functional/rosette.diff: backends/functional/smtlib.cc backends/functional/smtlib_rosette.cc + $(Q) mkdir -p $(@D) + $(Q) diff -U 20 $^ > $@ || exit 0 + +PHONY: docs/gen/functional_ir +docs/gen/functional_ir: docs/source/generated/functional/smtlib.cc docs/source/generated/functional/rosette.diff + PHONY: docs/gen docs/usage docs/reqs docs/gen: $(TARGETS) $(Q) $(MAKE) -C docs gen @@ -1032,7 +1064,7 @@ docs/reqs: $(Q) $(MAKE) -C docs reqs .PHONY: docs/prep -docs/prep: docs/source/cmd/abc.rst docs/source/generated/cells.json docs/gen docs/usage +docs/prep: docs/source/cmd/abc.rst docs/source/generated/cells.json docs/gen docs/usage docs/gen/functional_ir DOC_TARGET ?= html docs: docs/prep @@ -1053,6 +1085,7 @@ clean: rm -rf vloghtb/Makefile vloghtb/refdat vloghtb/rtl vloghtb/scripts vloghtb/spec vloghtb/check_yosys vloghtb/vloghammer_tb.tar.bz2 vloghtb/temp vloghtb/log_test_* rm -f tests/svinterfaces/*.log_stdout tests/svinterfaces/*.log_stderr tests/svinterfaces/dut_result.txt tests/svinterfaces/reference_result.txt tests/svinterfaces/a.out tests/svinterfaces/*_syn.v tests/svinterfaces/*.diff rm -f tests/tools/cmp_tbdata + rm -f $(addsuffix /run-test.mk,$(MK_TEST_DIRS)) -$(MAKE) -C docs clean rm -rf docs/source/cmd docs/util/__pycache__ diff --git a/abc b/abc index cac8f99ea..f2d68d590 160000 --- a/abc +++ b/abc @@ -1 +1 @@ -Subproject commit cac8f99eaa220a5e3db5caeb87cef0a975c953a2 +Subproject commit f2d68d590fa6f8fc32295a2edd79afc0d14a1414 diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index ea14da854..c9e737d19 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -210,14 +210,8 @@ struct SmtrModule { state_struct.insert(state->name, state->sort); } - void write(std::ostream &out) - { - SExprWriter w(out); - - input_struct.write_definition(w); - output_struct.write_definition(w); - state_struct.write_definition(w); - + void write_eval(SExprWriter &w) + { w.push(); w.open(list("define", list(name, "inputs", "state"))); auto inlined = [&](Functional::Node n) { @@ -240,7 +234,10 @@ struct SmtrModule { output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.output(name).value()); }); state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.state(name).next_value()); }); w.pop(); + } + void write_initial(SExprWriter &w) + { w.push(); auto initial = name + "_initial"; w.open(list("define", initial)); @@ -259,6 +256,18 @@ struct SmtrModule { } w.pop(); } + + void write(std::ostream &out) + { + SExprWriter w(out); + + input_struct.write_definition(w); + output_struct.write_definition(w); + state_struct.write_definition(w); + + write_eval(w); + write_initial(w); + } }; struct FunctionalSmtrBackend : public Backend { @@ -267,7 +276,7 @@ struct FunctionalSmtrBackend : public Backend { void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); - log(" write_functional_rosette [options] [selection] [filename]\n"); + log(" write_functional_rosette [options] [filename]\n"); log("\n"); log("Functional Rosette Backend.\n"); log("\n"); diff --git a/docs/source/code_examples/functional/dummy.cc b/docs/source/code_examples/functional/dummy.cc new file mode 100644 index 000000000..3d84b84ba --- /dev/null +++ b/docs/source/code_examples/functional/dummy.cc @@ -0,0 +1,44 @@ +#include "kernel/functional.h" +#include "kernel/yosys.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct FunctionalDummyBackend : public Backend { + FunctionalDummyBackend() : Backend("functional_dummy", "dump generated Functional IR") {} + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + // backend pass boiler plate + log_header(design, "Executing dummy functional backend.\n"); + + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); + + for (auto module : design->selected_modules()) + { + log("Processing module `%s`.\n", module->name.c_str()); + + // convert module to FunctionalIR + auto ir = Functional::IR::from_module(module); + *f << "module " << module->name.c_str() << "\n"; + + // write node functions + for (auto node : ir) + *f << " assign " << id2cstr(node.name()) + << " = " << node.to_string() << "\n"; + *f << "\n"; + + // write outputs and next state + for (auto output : ir.outputs()) + *f << " " << id2cstr(output->kind) + << " " << id2cstr(output->name) + << " = " << id2cstr(output->value().name()) << "\n"; + for (auto state : ir.states()) + *f << " " << id2cstr(state->kind) + << " " << id2cstr(state->name) + << " = " << id2cstr(state->next_value().name()) << "\n"; + } + } +} FunctionalDummyBackend; + +PRIVATE_NAMESPACE_END diff --git a/docs/source/conf.py b/docs/source/conf.py index f6c4b307a..607e088da 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.50" +yosys_ver = "0.51" # select HTML theme html_theme = 'furo-ys' diff --git a/docs/source/yosys_internals/extending_yosys/contributing.rst b/docs/source/yosys_internals/extending_yosys/contributing.rst index e06de61b1..69258aa5f 100644 --- a/docs/source/yosys_internals/extending_yosys/contributing.rst +++ b/docs/source/yosys_internals/extending_yosys/contributing.rst @@ -1,6 +1,14 @@ Contributing to Yosys ===================== +.. note:: + + For information on making a pull request on github, refer to our + |CONTRIBUTING|_ file. + +.. |CONTRIBUTING| replace:: :file:`CONTRIBUTING.md` +.. _CONTRIBUTING: https://github.com/YosysHQ/yosys/CONTRIBUTING.md + Coding Style ------------ diff --git a/docs/source/yosys_internals/extending_yosys/functional_ir.rst b/docs/source/yosys_internals/extending_yosys/functional_ir.rst index a763e0508..4f363623e 100644 --- a/docs/source/yosys_internals/extending_yosys/functional_ir.rst +++ b/docs/source/yosys_internals/extending_yosys/functional_ir.rst @@ -1,94 +1,524 @@ Writing a new backend using FunctionalIR -=========================================== +======================================== -To simplify the writing of backends for functional languages or similar targets, Yosys provides an alternative intermediate representation called FunctionalIR which maps more directly on those targets. +What is FunctionalIR +-------------------- -FunctionalIR represents the design as a function ``(inputs, current_state) -> (outputs, next_state)``. -This function is broken down into a series of assignments to variables. -Each assignment is a simple operation, such as an addition. -Complex operations are broken up into multiple steps. -For example, an RTLIL addition will be translated into a sign/zero extension of the inputs, followed by an addition. +To simplify the writing of backends for functional languages or similar targets, +Yosys provides an alternative intermediate representation called FunctionalIR +which maps more directly on those targets. -Like SSA form, each variable is assigned to exactly once. -We can thus treat variables and assignments as equivalent and, since this is a graph-like representation, those variables are also called "nodes". -Unlike RTLIL's cells and wires representation, this representation is strictly ordered (topologically sorted) with definitions preceding their use. +FunctionalIR represents the design as a function ``(inputs, current_state) -> +(outputs, next_state)``. This function is broken down into a series of +assignments to variables. Each assignment is a simple operation, such as an +addition. Complex operations are broken up into multiple steps. For example, an +RTLIL addition will be translated into a sign/zero extension of the inputs, +followed by an addition. -Every node has a "sort" (the FunctionalIR term for what might otherwise be called a "type"). The sorts available are +Like SSA form, each variable is assigned to exactly once. We can thus treat +variables and assignments as equivalent and, since this is a graph-like +representation, those variables are also called "nodes". Unlike RTLIL's cells +and wires representation, this representation is strictly ordered (topologically +sorted) with definitions preceding their use. + +Every node has a "sort" (the FunctionalIR term for what might otherwise be +called a "type"). The sorts available are - ``bit[n]`` for an ``n``-bit bitvector, and - ``memory[n,m]`` for an immutable array of ``2**n`` values of sort ``bit[m]``. -In terms of actual code, Yosys provides a class ``Functional::IR`` that represents a design in FunctionalIR. -``Functional::IR::from_module`` generates an instance from an RTLIL module. -The entire design is stored as a whole in an internal data structure. -To access the design, the ``Functional::Node`` class provides a reference to a particular node in the design. -The ``Functional::IR`` class supports the syntax ``for(auto node : ir)`` to iterate over every node. +In terms of actual code, Yosys provides a class ``Functional::IR`` that +represents a design in FunctionalIR. ``Functional::IR::from_module`` generates +an instance from an RTLIL module. The entire design is stored as a whole in an +internal data structure. To access the design, the ``Functional::Node`` class +provides a reference to a particular node in the design. The ``Functional::IR`` +class supports the syntax ``for(auto node : ir)`` to iterate over every node. -``Functional::IR`` also keeps track of inputs, outputs and states. -By a "state" we mean a pair of a "current state" input and a "next state" output. -One such pair is created for every register and for every memory. -Every input, output and state has a name (equal to their name in RTLIL), a sort and a kind. -The kind field usually remains as the default value ``$input``, ``$output`` or ``$state``, however some RTLIL cells such as ``$assert`` or ``$anyseq`` generate auxiliary inputs/outputs/states that are given a different kind to distinguish them from ordinary RTLIL inputs/outputs/states. +``Functional::IR`` also keeps track of inputs, outputs and states. By a "state" +we mean a pair of a "current state" input and a "next state" output. One such +pair is created for every register and for every memory. Every input, output and +state has a name (equal to their name in RTLIL), a sort and a kind. The kind +field usually remains as the default value ``$input``, ``$output`` or +``$state``, however some RTLIL cells such as ``$assert`` or ``$anyseq`` generate +auxiliary inputs/outputs/states that are given a different kind to distinguish +them from ordinary RTLIL inputs/outputs/states. -- To access an individual input/output/state, use ``ir.input(name, kind)``, ``ir.output(name, kind)`` or ``ir.state(name, kind)``. ``kind`` defaults to the default kind. -- To iterate over all inputs/outputs/states of a certain kind, methods ``ir.inputs``, ``ir.outputs``, ``ir.states`` are provided. Their argument defaults to the default kinds mentioned. -- To iterate over inputs/outputs/states of any kind, use ``ir.all_inputs``, ``ir.all_outputs`` and ``ir.all_states``. -- Outputs have a node that indicate the value of the output, this can be retrieved via ``output.value()``. -- States have a node that indicate the next value of the state, this can be retrieved via ``state.next_value()``. - They also have an initial value that is accessed as either ``state.initial_value_signal()`` or ``state.initial_value_memory()``, depending on their sort. +- To access an individual input/output/state, use ``ir.input(name, kind)``, + ``ir.output(name, kind)`` or ``ir.state(name, kind)``. ``kind`` defaults to + the default kind. +- To iterate over all inputs/outputs/states of a certain kind, methods + ``ir.inputs``, ``ir.outputs``, ``ir.states`` are provided. Their argument + defaults to the default kinds mentioned. +- To iterate over inputs/outputs/states of any kind, use ``ir.all_inputs``, + ``ir.all_outputs`` and ``ir.all_states``. +- Outputs have a node that indicate the value of the output, this can be + retrieved via ``output.value()``. +- States have a node that indicate the next value of the state, this can be + retrieved via ``state.next_value()``. They also have an initial value that is + accessed as either ``state.initial_value_signal()`` or + ``state.initial_value_memory()``, depending on their sort. -Each node has a "function", which defines its operation (for a complete list of functions and a specification of their operation, see ``functional.h``). -Functions are represented as an enum ``Functional::Fn`` and the function field can be accessed as ``node.fn()``. -Since the most common operation is a switch over the function that also accesses the arguments, the ``Node`` class provides a method ``visit`` that implements the visitor pattern. -For example, for an addition node ``node`` with arguments ``n1`` and ``n2``, ``node.visit(visitor)`` would call ``visitor.add(node, n1, n2)``. -Thus typically one would implement a class with a method for every function. -Visitors should inherit from either ``Functional::AbstractVisitor`` or ``Functional::DefaultVisitor``. -The former will produce a compiler error if a case is unhandled, the latter will call ``default_handler(node)`` instead. -Visitor methods should be marked as ``override`` to provide compiler errors if the arguments are wrong. +Each node has a "function", which defines its operation (for a complete list of +functions and a specification of their operation, see ``functional.h``). +Functions are represented as an enum ``Functional::Fn`` and the function field +can be accessed as ``node.fn()``. Since the most common operation is a switch +over the function that also accesses the arguments, the ``Node`` class provides +a method ``visit`` that implements the visitor pattern. For example, for an +addition node ``node`` with arguments ``n1`` and ``n2``, ``node.visit(visitor)`` +would call ``visitor.add(node, n1, n2)``. Thus typically one would implement a +class with a method for every function. Visitors should inherit from either +``Functional::AbstractVisitor`` or +``Functional::DefaultVisitor``. The former will produce a compiler +error if a case is unhandled, the latter will call ``default_handler(node)`` +instead. Visitor methods should be marked as ``override`` to provide compiler +errors if the arguments are wrong. Utility classes ------------------ +~~~~~~~~~~~~~~~ -``functional.h`` also provides utility classes that are independent of the main FunctionalIR representation but are likely to be useful for backends. +``functional.h`` also provides utility classes that are independent of the main +FunctionalIR representation but are likely to be useful for backends. -``Functional::Writer`` provides a simple formatting class that wraps a ``std::ostream`` and provides the following methods: +``Functional::Writer`` provides a simple formatting class that wraps a +``std::ostream`` and provides the following methods: - ``writer << value`` wraps ``os << value``. -- ``writer.print(fmt, value0, value1, value2, ...)`` replaces ``{0}``, ``{1}``, ``{2}``, etc in the string ``fmt`` with ``value0``, ``value1``, ``value2``, resp. - Each value is formatted using ``os << value``. - It is also possible to write ``{}`` to refer to one past the last index, i.e. ``{1} {} {} {7} {}`` is equivalent to ``{1} {2} {3} {7} {8}``. -- ``writer.print_with(fn, fmt, value0, value1, value2, ...)`` functions much the same as ``print`` but it uses ``os << fn(value)`` to print each value and falls back to ``os << value`` if ``fn(value)`` is not legal. +- ``writer.print(fmt, value0, value1, value2, ...)`` replaces ``{0}``, ``{1}``, + ``{2}``, etc in the string ``fmt`` with ``value0``, ``value1``, ``value2``, + resp. Each value is formatted using ``os << value``. It is also possible to + write ``{}`` to refer to one past the last index, i.e. ``{1} {} {} {7} {}`` is + equivalent to ``{1} {2} {3} {7} {8}``. +- ``writer.print_with(fn, fmt, value0, value1, value2, ...)`` functions much the + same as ``print`` but it uses ``os << fn(value)`` to print each value and + falls back to ``os << value`` if ``fn(value)`` is not legal. -``Functional::Scope`` keeps track of variable names in a target language. -It is used to translate between different sets of legal characters and to avoid accidentally re-defining identifiers. -Users should derive a class from ``Scope`` and supply the following: +``Functional::Scope`` keeps track of variable names in a target language. It is +used to translate between different sets of legal characters and to avoid +accidentally re-defining identifiers. Users should derive a class from ``Scope`` +and supply the following: -- ``Scope`` takes a template argument that specifies a type that's used to uniquely distinguish variables. - Typically this would be ``int`` (if variables are used for ``Functional::IR`` nodes) or ``IdString``. -- The derived class should provide a constructor that calls ``reserve`` for every reserved word in the target language. -- A method ``bool is_legal_character(char c, int index)`` has to be provided that returns ``true`` iff ``c`` is legal in an identifier at position ``index``. +- ``Scope`` takes a template argument that specifies a type that's used to + uniquely distinguish variables. Typically this would be ``int`` (if variables + are used for ``Functional::IR`` nodes) or ``IdString``. +- The derived class should provide a constructor that calls ``reserve`` for + every reserved word in the target language. +- A method ``bool is_character_legal(char c, int index)`` has to be provided + that returns ``true`` iff ``c`` is legal in an identifier at position + ``index``. -Given an instance ``scope`` of the derived class, the following methods are then available: +Given an instance ``scope`` of the derived class, the following methods are then +available: - ``scope.reserve(std::string name)`` marks the given name as being in-use -- ``scope.unique_name(IdString suggestion)`` generates a previously unused name and attempts to make it similar to ``suggestion``. -- ``scope(Id id, IdString suggestion)`` functions similar to ``unique_name``, except that multiple calls with the same ``id`` are guaranteed to retrieve the same name (independent of ``suggestion``). +- ``scope.unique_name(IdString suggestion)`` generates a previously unused name + and attempts to make it similar to ``suggestion``. +- ``scope(Id id, IdString suggestion)`` functions similar to ``unique_name``, + except that multiple calls with the same ``id`` are guaranteed to retrieve the + same name (independent of ``suggestion``). ``sexpr.h`` provides classes that represent and pretty-print s-expressions. -S-expressions can be constructed with ``SExpr::list``, for example ``SExpr expr = SExpr::list("add", "x", SExpr::list("mul", "y", "z"))`` represents ``(add x (mul y z))`` -(by adding ``using SExprUtil::list`` to the top of the file, ``list`` can be used as shorthand for ``SExpr::list``). -For prettyprinting, ``SExprWriter`` wraps an ``std::ostream`` and provides the following methods: +S-expressions can be constructed with ``SExpr::list``, for example ``SExpr expr += SExpr::list("add", "x", SExpr::list("mul", "y", "z"))`` represents ``(add x +(mul y z))`` (by adding ``using SExprUtil::list`` to the top of the file, +``list`` can be used as shorthand for ``SExpr::list``). For prettyprinting, +``SExprWriter`` wraps an ``std::ostream`` and provides the following methods: -- ``writer << sexpr`` writes the provided expression to the output, breaking long lines and adding appropriate indentation. -- ``writer.open(sexpr)`` is similar to ``writer << sexpr`` but will omit the last closing parenthesis. - Further arguments can then be added separately with ``<<`` or ``open``. - This allows for printing large s-expressions without needing to construct the whole expression in memory first. -- ``writer.open(sexpr, false)`` is similar to ``writer.open(sexpr)`` but further arguments will not be indented. - This is used to avoid unlimited indentation on structures with unlimited nesting. +- ``writer << sexpr`` writes the provided expression to the output, breaking + long lines and adding appropriate indentation. +- ``writer.open(sexpr)`` is similar to ``writer << sexpr`` but will omit the + last closing parenthesis. Further arguments can then be added separately with + ``<<`` or ``open``. This allows for printing large s-expressions without + needing to construct the whole expression in memory first. +- ``writer.open(sexpr, false)`` is similar to ``writer.open(sexpr)`` but further + arguments will not be indented. This is used to avoid unlimited indentation on + structures with unlimited nesting. - ``writer.close(n = 1)`` closes the last ``n`` open s-expressions. -- ``writer.push()`` and ``writer.pop()`` are used to automatically close s-expressions. - ``writer.pop()`` closes all s-expressions opened since the last call to ``writer.push()``. +- ``writer.push()`` and ``writer.pop()`` are used to automatically close + s-expressions. ``writer.pop()`` closes all s-expressions opened since the last + call to ``writer.push()``. - ``writer.comment(string)`` writes a comment on a separate-line. - ``writer.comment(string, true)`` appends a comment to the last printed s-expression. -- ``writer.flush()`` flushes any buffering and should be called before any direct access to the underlying ``std::ostream``. It does not close unclosed parentheses. + ``writer.comment(string, true)`` appends a comment to the last printed + s-expression. +- ``writer.flush()`` flushes any buffering and should be called before any + direct access to the underlying ``std::ostream``. It does not close unclosed + parentheses. - The destructor calls ``flush`` but also closes all unclosed parentheses. + +.. _minimal backend: + +Example: A minimal functional backend +------------------------------------- + +At its most basic, there are three steps we need to accomplish for a minimal +functional backend. First, we need to convert our design into FunctionalIR. +This is most easily done by calling the ``Functional::IR::from_module()`` static +method with our top-level module, or iterating over and converting each of the +modules in our design. Second, we need to handle each of the +``Functional::Node``\ s in our design. Iterating over the ``Functional::IR`` +includes reading the module inputs and current state, but not writing the +results. So our final step is to handle the outputs and next state. + +In order to add an output command to Yosys, we implement the ``Yosys::Backend`` +class and provide an instance of it: + +.. literalinclude:: /code_examples/functional/dummy.cc + :language: c++ + :caption: Example source code for a minimal functional backend, ``dummy.cc`` + +Because we are using the ``Backend`` class, our ``"functional_dummy"`` is +registered as the ``write_functional_dummy`` command. The ``execute`` method is +the part that runs when the user calls the command, handling any options, +preparing the output file for writing, and iterating over selected modules in +the design. Since we don't have any options here, we set ``argidx = 1`` and +call the ``extra_args()`` method. This method will read the command arguments, +raising an error if there are any unexpected ones. It will also assign the +pointer ``f`` to the output file, or stdout if none is given. + +.. note:: + + For more on adding new commands to Yosys and how they work, refer to + :doc:`/yosys_internals/extending_yosys/extensions`. + +For this minimal example all we are doing is printing out each node. The +``node.name()`` method returns an ``RTLIL::IdString``, which we convert for +printing with ``id2cstr()``. Then, to print the function of the node, we use +``node.to_string()`` which gives us a string of the form ``function(args)``. The +``function`` part is the result of ``Functional::IR::fn_to_string(node.fn())``; +while ``args`` is the zero or more arguments passed to the function, most +commonly the name of another node. Behind the scenes, the ``node.to_string()`` +method actually wraps ``node.visit(visitor)`` with a private visitor whose +return type is ``std::string``. + +Finally we iterate over the module's outputs and states, using +``Functional::IROutput::value()`` and ``Functional::IRState::next_value()`` +respectively in order to get the results of the transfer function. + +Example: Adapting SMT-LIB backend for Rosette +--------------------------------------------- + +This section will introduce the SMT-LIB functional backend +(`write_functional_smt2`) and what changes are needed to work with another +s-expression target, `Rosette`_ (`write_functional_rosette`). + +.. _Rosette: http://emina.github.io/rosette/ + +Overview +~~~~~~~~ + + Rosette is a solver-aided programming language that extends `Racket`_ with + language constructs for program synthesis, verification, and more. To verify + or synthesize code, Rosette compiles it to logical constraints solved with + off-the-shelf `SMT`_ solvers. + + -- https://emina.github.io/rosette/ + +.. _Racket: http://racket-lang.org/ +.. _SMT: http://smtlib.cs.uiowa.edu/ + +Rosette, being backed by SMT solvers and written with s-expressions, uses code +very similar to the `write_functional_smt2` output. As a result, the SMT-LIB +functional backend can be used as a starting point for implementing a Rosette +backend. + +Full code listings for the initial SMT-LIB backend and the converted Rosette +backend are included in the Yosys source repository under +:file:`backends/functional` as ``smtlib.cc`` and ``smtlib_rosette.cc`` +respectively. Note that the Rosette language is an extension of the Racket +language; this guide tends to refer to Racket when talking about the underlying +semantics/syntax of the language. + +The major changes from the SMT-LIB backend are as follows: + +- all of the ``Smt`` prefixes in names are replaced with ``Smtr`` to mean + ``smtlib_rosette``; +- syntax is adjusted for Racket; +- data structures for input/output/state are changed from using + ``declare-datatype`` with statically typed fields, to using ``struct`` with no + static typing; +- the transfer function also loses its static typing; +- sign/zero extension in Rosette use the output width instead of the number of + extra bits, gaining static typing; +- the single scope is traded for a global scope with local scope for each + struct; +- initial state is provided as a constant value instead of a set of assertions; +- and the ``-provides`` option is introduced to more easily use generated code + within Rosette based applications. + +Scope +~~~~~ + +Our first addition to the `minimal backend`_ above is that for both SMT-LIB and +Rosette backends, we are now targetting real languages which bring with them +their own sets of constraints with what we can use as identifiers. This is +where the ``Functional::Scope`` class described above comes in; by using this +class we can safely rename our identifiers in the generated output without +worrying about collisions or illegal names/characters. + +In the SMT-LIB version, the ``SmtScope`` class implements ``Scope``; +provides a constructor that iterates over a list of reserved keywords, calling +``reserve`` on each; and defines the ``is_character_legal`` method to reject any +characters which are not allowed in SMT-LIB variable names to then be replaced +with underscores in the output. To use this scope we create an instance of it, +and call the ``Scope::unique_name()`` method to generate a unique and legal name +for each of our identifiers. + +In the Rosette version we update the list of legal ascii characters in the +``is_character_legal`` method to only those allowed in Racket variable names. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: diff of ``Scope`` class + :start-at: -struct SmtScope : public Functional::Scope { + :end-at: }; + +For the reserved keywords we trade the SMT-LIB specification for Racket to +prevent parts of our design from accidentally being treated as Racket code. We +also no longer need to reserve ``pair``, ``first``, and ``second``. In +`write_functional_smt2` these are used for combining the ``(inputs, +current_state)`` and ``(outputs, next_state)`` into a single variable. Racket +provides this functionality natively with ``cons``, which we will see later. + +.. inlined diff for skipping the actual lists +.. code-block:: diff + :caption: diff of ``reserved_keywords`` list + + const char *reserved_keywords[] = { + - // reserved keywords from the smtlib spec + - ... + + // reserved keywords from the racket spec + + ... + + // reserved for our own purposes + - "pair", "Pair", "first", "second", + - "inputs", "state", + + "inputs", "state", "name", + nullptr + }; + +.. note:: We skip over the actual list of reserved keywords from both the smtlib + and racket specifications to save on space in this document. + +Sort +~~~~ + +Next up in `write_functional_smt2` we see the ``Sort`` class. This is a wrapper +for the ``Functional::Sort`` class, providing the additional functionality of +mapping variable declarations to s-expressions with the ``to_sexpr()`` method. +The main change from ``SmtSort`` to ``SmtrSort`` is a syntactical one with +signals represented as ``bitvector``\ s, and memories as ``list``\ s of signals. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: diff of ``Sort`` wrapper + :start-at: SExpr to_sexpr() const { + :end-before: }; + +Struct +~~~~~~ + +As we saw in the `minimal backend`_ above, the ``Functional::IR`` class tracks +the set of inputs, the set of outputs, and the set of "state" variables. The +SMT-LIB backend maps each of these sets into its own ``SmtStruct``, with each +variable getting a corresponding field in the struct and a specified `Sort`_. +`write_functional_smt2` then defines each of these structs as a new +``datatype``, with each element being strongly-typed. + +In Rosette, rather than defining new datatypes for our structs, we use the +native ``struct``. We also only declare each field by name because Racket +provides less static typing. For ease of use, we provide the expected type for +each field as comments. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: diff of ``write_definition`` method + :start-at: void write_definition + :end-before: template void write_value + +Each field is added to the ``SmtStruct`` with the ``insert`` method, which also +reserves a unique name (or accessor) within the `Scope`_. These accessors +combine the struct name and field name and are globally unique, being used in +the ``access`` method for reading values from the input/current state. + +.. literalinclude:: /generated/functional/smtlib.cc + :language: c++ + :caption: ``Struct::access()`` method + :start-at: SExpr access( + :end-before: }; + +In Rosette, struct fields are accessed as ``-`` so +including the struct name in the field name would be redundant. For +`write_functional_rosette` we instead choose to make field names unique only +within the struct, while accessors are unique across the whole module. We thus +modify the class constructor and ``insert`` method to support this; providing +one scope that is local to the struct (``local_scope``) and one which is shared +across the whole module (``global_scope``), leaving the ``access`` method +unchanged. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: diff of struct constructor + :start-at: SmtStruct(std::string name, SmtScope &scope) + :end-before: void write_definition + +Finally, ``SmtStruct`` also provides a ``write_value`` template method which +calls a provided function on each element in the struct. This is used later for +assigning values to the output/next state pair. The only change here is to +remove the check for zero-argument constructors since this is not necessary with +Rosette ``struct``\ s. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: diff of ``write_value`` method + :start-at: template void write_value + :end-before: SExpr access + +PrintVisitor +~~~~~~~~~~~~ + +Remember in the `minimal backend`_ we converted nodes into strings for writing +using the ``node.to_string()`` method, which wrapped ``node.visit()`` with a +private visitor. We now want a custom visitor which can convert nodes into +s-expressions. This is where the ``PrintVisitor`` comes in, implementing the +abstract ``Functional::AbstractVisitor`` class with a return type of ``SExpr``. +For most functions, the Rosette output is very similar to the corresponding +SMT-LIB function with minor adjustments for syntax. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: portion of ``Functional::AbstractVisitor`` implementation diff showing similarities + :start-at: SExpr logical_shift_left + :end-at: "list-set-bv" + +However there are some differences in the two formats with regards to how +booleans are handled, with Rosette providing built-in functions for conversion. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: portion of ``Functional::AbstractVisitor`` implementation diff showing differences + :start-at: SExpr from_bool + :end-before: SExpr extract + +Of note here is the rare instance of the Rosette implementation *gaining* static +typing rather than losing it. Where SMT_LIB calls zero/sign extension with the +number of extra bits needed (given by ``out_width - a.width()``), Rosette +instead specifies the type of the output (given by ``list("bitvector", +out_width)``). + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: zero/sign extension implementation diff + :start-after: SExpr buf( + :end-before: SExpr concat( + :lines: 2-3, 5-6 + +.. note:: Be sure to check the source code for the full list of differences here. + +Module +~~~~~~ + +With most of the supporting classes out of the way, we now reach our three main +steps from the `minimal backend`_. These are all handled by the ``SmtModule`` +class, with the mapping from RTLIL module to FunctionalIR happening in the +constructor. Each of the three ``SmtStruct``\ s; inputs, outputs, and state; +are also created in the constructor, with each value in the corresponding lists +in the IR being ``insert``\ ed. + +.. literalinclude:: /generated/functional/smtlib.cc + :language: c++ + :caption: ``SmtModule`` constructor + :start-at: SmtModule(Module + :end-at: } + +Since Racket uses the ``-`` to access struct fields, the ``SmtrModule`` instead +uses an underscore for the name of the initial state. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: diff of ``Module`` constructor + :start-at: scope.reserve(name + :end-before: for (auto input + +The ``write`` method is then responsible for writing the FunctionalIR to the +output file, formatted for the corresponding backend. ``SmtModule::write()`` +breaks the output file down into four parts: defining the three structs, +declaring the ``pair`` datatype, defining the transfer function ``(inputs, +current_state) -> (outputs, next_state)`` with ``write_eval``, and declaring the +initial state with ``write_initial``. The only change for the ``SmtrModule`` is +that the ``pair`` declaration isn't needed. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: diff of ``Module::write()`` method + :start-at: void write(std::ostream &out) + :end-at: } + +The ``write_eval`` method is where the FunctionalIR nodes, outputs, and next +state are handled. Just as with the `minimal backend`_, we iterate over the +nodes with ``for(auto n : ir)``, and then use the ``Struct::write_value()`` +method for the ``output_struct`` and ``state_struct`` to iterate over the +outputs and next state respectively. + +.. literalinclude:: /generated/functional/smtlib.cc + :language: c++ + :caption: iterating over FunctionalIR nodes in ``SmtModule::write_eval()`` + :start-at: for(auto n : ir) + :end-at: } + +The main differences between our two backends here are syntactical. First we +change the ``define-fun`` for the Racket style ``define`` which drops the +explicitly typed inputs/outputs. And then we change the final result from a +``pair`` to the native ``cons`` which acts in much the same way, returning both +the ``outputs`` and the ``next_state`` in a single variable. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: diff of ``Module::write_eval()`` transfer function declaration + :start-at: w.open(list("define-fun" + :end-at: w.open(list("define" + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: diff of output/next state handling ``Module::write_eval()`` + :start-at: w.open(list("pair" + :end-at: w.pop(); + +For the ``write_initial`` method, the SMT-LIB backend uses ``declare-const`` and +``assert``\ s which must always hold true. For Rosette we instead define the +initial state as any other variable that can be used by external code. This +variable, ``[name]_initial``, can then be used in the ``[name]`` function call; +allowing the Rosette code to be used in the generation of the ``next_state``, +whereas the SMT-LIB code can only verify that a given ``next_state`` is correct. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: diff of ``Module::write_initial()`` method + :start-at: void write_initial + :end-before: void write + +Backend +~~~~~~~ + +The final part is the ``Backend`` itself, with much of the same boiler plate as +the `minimal backend`_. The main difference is that we use the `Module`_ to +perform the actual processing. + +.. literalinclude:: /generated/functional/smtlib.cc + :language: c++ + :caption: The ``FunctionalSmtBackend`` + :start-at: struct FunctionalSmtBackend + :end-at: } FunctionalSmtBackend; + +There are two additions here for Rosette. The first is that the output file +needs to start with the ``#lang`` definition which tells the +compiler/interpreter that we want to use the Rosette language module. The +second is that the `write_functional_rosette` command takes an optional +argument, ``-provides``. If this argument is given, then the output file gets +an additional line declaring that everything in the file should be exported for +use; allowing the file to be treated as a Racket package with structs and +mapping function available for use externally. + +.. literalinclude:: /generated/functional/rosette.diff + :language: diff + :caption: relevant portion of diff of ``Backend::execute()`` method + :start-at: lang rosette/safe + :end-before: for (auto module diff --git a/frontends/ast/dpicall.cc b/frontends/ast/dpicall.cc index 12a7e1183..d6fcc26bd 100644 --- a/frontends/ast/dpicall.cc +++ b/frontends/ast/dpicall.cc @@ -67,9 +67,10 @@ static ffi_fptr resolve_fn (std::string symbol_name) AST::AstNode *AST::dpi_call(const std::string &rtype, const std::string &fname, const std::vector &argtypes, const std::vector &args) { AST::AstNode *newNode = nullptr; - union { double f64; float f32; int32_t i32; void *ptr; } value_store [args.size() + 1]; - ffi_type *types [args.size() + 1]; - void *values [args.size() + 1]; + union value { double f64; float f32; int32_t i32; void *ptr; }; + std::vector value_store(args.size() + 1); + std::vector types(args.size() + 1); + std::vector values(args.size() + 1); ffi_cif cif; int status; @@ -118,10 +119,10 @@ AST::AstNode *AST::dpi_call(const std::string &rtype, const std::string &fname, log_error("invalid rtype '%s'.\n", rtype.c_str()); } - if ((status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, args.size(), types[args.size()], types)) != FFI_OK) + if ((status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, args.size(), types[args.size()], types.data())) != FFI_OK) log_error("ffi_prep_cif failed: status %d.\n", status); - ffi_call(&cif, resolve_fn(fname.c_str()), values[args.size()], values); + ffi_call(&cif, resolve_fn(fname.c_str()), values[args.size()], values.data()); if (rtype == "real") { newNode = new AstNode(AST_REALVALUE); diff --git a/frontends/liberty/liberty.cc b/frontends/liberty/liberty.cc index b29984ecd..cda13ff8b 100644 --- a/frontends/liberty/liberty.cc +++ b/frontends/liberty/liberty.cc @@ -348,7 +348,7 @@ static bool create_latch(RTLIL::Module *module, const LibertyAst *node, bool fla RTLIL::Cell *enable_gate = module->addCell(NEW_ID, enable_polarity ? ID($_OR_) : ID($_AND_)); enable_gate->setPort(ID::A, enable_sig); enable_gate->setPort(ID::B, clear_enable); - enable_gate->setPort(ID::Y, data_sig = module->addWire(NEW_ID)); + enable_gate->setPort(ID::Y, enable_sig = module->addWire(NEW_ID)); } if (preset_sig.size() == 1) @@ -376,7 +376,7 @@ static bool create_latch(RTLIL::Module *module, const LibertyAst *node, bool fla RTLIL::Cell *enable_gate = module->addCell(NEW_ID, enable_polarity ? ID($_OR_) : ID($_AND_)); enable_gate->setPort(ID::A, enable_sig); enable_gate->setPort(ID::B, preset_enable); - enable_gate->setPort(ID::Y, data_sig = module->addWire(NEW_ID)); + enable_gate->setPort(ID::Y, enable_sig = module->addWire(NEW_ID)); } cell = module->addCell(NEW_ID, stringf("$_DLATCH_%c_", enable_polarity ? 'P' : 'N')); diff --git a/kernel/rtlil.h b/kernel/rtlil.h index f9cacd151..54735bacb 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -1673,6 +1673,19 @@ public: RTLIL::Cell *driverCell() const { log_assert(driverCell_); return driverCell_; }; RTLIL::IdString driverPort() const { log_assert(driverCell_); return driverPort_; }; + int from_hdl_index(int hdl_index) { + int zero_index = hdl_index - start_offset; + int rtlil_index = upto ? width - 1 - zero_index : zero_index; + return rtlil_index >= 0 && rtlil_index < width ? rtlil_index : INT_MIN; + } + + int to_hdl_index(int rtlil_index) { + if (rtlil_index < 0 || rtlil_index >= width) + return INT_MIN; + int zero_index = upto ? width - 1 - rtlil_index : rtlil_index; + return zero_index + start_offset; + } + #ifdef WITH_PYTHON static std::map *get_all_wires(void); #endif diff --git a/kernel/yosys.cc b/kernel/yosys.cc index de25d20e2..d39acf9d9 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -413,8 +413,11 @@ std::string make_temp_dir(std::string template_str) # endif char *p = strdup(template_str.c_str()); + log_assert(p); char *res = mkdtemp(p); - log_assert(res != NULL); + if (!res) + log_error("mkdtemp failed for '%s': %s [Error %d]\n", + p, strerror(errno), errno); template_str = p; free(p); @@ -754,9 +757,10 @@ struct TclPass : public Pass { log("If any arguments are specified, these arguments are provided to the script via\n"); log("the standard $argc and $argv variables.\n"); log("\n"); - log("Note, tcl will not recieve the output of any yosys command. If the output\n"); + log("Note, tcl will not receive the output of any yosys command. If the output\n"); log("of the tcl commands are needed, use the yosys command 'tee -s result.string'\n"); log("to redirect yosys's output to the 'result.string' scratchpad value.\n"); + log("The 'result.string' value is then used as the tcl output value of the command.\n"); log("\n"); } void execute(std::vector args, RTLIL::Design *) override { diff --git a/libs/fst/00_PATCH_wx_len_overread.patch b/libs/fst/00_PATCH_wx_len_overread.patch new file mode 100644 index 000000000..7fba1c2d6 --- /dev/null +++ b/libs/fst/00_PATCH_wx_len_overread.patch @@ -0,0 +1,10 @@ +--- fstapi.cc ++++ fstapi.cc +@@ -6072,6 +6072,7 @@ for(;;) + } + + wx_len = snprintf(wx_buf, 32, "r%.16g", d); ++ if (wx_len > 32 || wx_len < 0) wx_len = 32; + fstWritex(xc, wx_buf, wx_len); + } + } diff --git a/libs/fst/00_UPDATE.sh b/libs/fst/00_UPDATE.sh index 7ab74d7cd..66a0fd8df 100755 --- a/libs/fst/00_UPDATE.sh +++ b/libs/fst/00_UPDATE.sh @@ -18,3 +18,4 @@ sed -i -e 's,"fastlz.c","fastlz.cc",' *.cc *.h patch -p0 < 00_PATCH_win_zlib.patch patch -p0 < 00_PATCH_win_io.patch patch -p1 < 00_PATCH_strict_alignment.patch +patch -p0 < 00_PATCH_wx_len_overread.patch diff --git a/libs/fst/fstapi.cc b/libs/fst/fstapi.cc index a4329cf32..ab3c54469 100644 --- a/libs/fst/fstapi.cc +++ b/libs/fst/fstapi.cc @@ -3907,16 +3907,18 @@ while (value) static int fstVcdIDForFwrite(char *buf, unsigned int value) { char *pnt = buf; + int len = 0; /* zero is illegal for a value...it is assumed they start at one */ -while (value) +while (value && len < 14) { value--; + ++len; *(pnt++) = (char)('!' + value % 94); value = value / 94; } -return(pnt - buf); +return len; } @@ -6070,6 +6072,7 @@ for(;;) } wx_len = snprintf(wx_buf, 32, "r%.16g", d); + if (wx_len > 32 || wx_len < 0) wx_len = 32; fstWritex(xc, wx_buf, wx_len); } } diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 5a2af4df5..b15e91edb 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -53,3 +53,4 @@ OBJS += passes/cmds/example_dt.o OBJS += passes/cmds/portarcs.o OBJS += passes/cmds/wrapcell.o OBJS += passes/cmds/setenv.o +OBJS += passes/cmds/abstract.o diff --git a/passes/cmds/abstract.cc b/passes/cmds/abstract.cc new file mode 100644 index 000000000..1e67dcd88 --- /dev/null +++ b/passes/cmds/abstract.cc @@ -0,0 +1,491 @@ +#include "kernel/yosys.h" +#include "kernel/celltypes.h" +#include "kernel/ff.h" +#include "kernel/ffinit.h" +#include +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct EnableLogic { + SigBit bit; + bool pol; +}; + +enum SliceIndices { + RtlilSlice, + HdlSlice, +}; + +struct Slice { + SliceIndices indices; + int first; + int last; + + Slice(SliceIndices indices, const std::string &slice) : + indices(indices) + { + if (slice.empty()) + syntax_error(slice); + auto sep = slice.find(':'); + const char *first_begin, *first_end, *last_begin, *last_end; + if (sep == std::string::npos) { + first_begin = last_begin = slice.c_str(); + first_end = last_end = slice.c_str() + slice.length(); + } else { + first_begin = slice.c_str(); + first_end = first_begin + sep; + + last_begin = first_end + 1; + last_end = slice.c_str() + slice.length(); + } + first = parse_index(first_begin, first_end, slice); + last = parse_index(last_begin, last_end, slice); + } + + static int parse_index(const char *begin, const char *end, const std::string &slice) { + int value; + auto result = std::from_chars(begin, end, value, 10); + if (result.ptr != end || result.ptr == begin) + syntax_error(slice); + return value; + } + + static void syntax_error(const std::string &slice) { + log_cmd_error("Invalid slice '%s', expected ':' or ''", slice.c_str()); + } + + std::string to_string() const { + const char *option = indices == RtlilSlice ? "-rtlilslice" : "-slice"; + if (first == last) + return stringf("%s %d", option, first); + else + return stringf("%s %d:%d", option, first, last); + } + + int wire_offset(RTLIL::Wire *wire, int index) const { + int rtl_offset = indices == RtlilSlice ? index : wire->from_hdl_index(index); + if (rtl_offset < 0 || rtl_offset >= wire->width) { + log_error("Slice %s is out of bounds for wire %s in module %s", to_string().c_str(), log_id(wire), log_id(wire->module)); + } + return rtl_offset; + } + + std::pair wire_range(RTLIL::Wire *wire) const { + int rtl_first = wire_offset(wire, first); + int rtl_last = wire_offset(wire, last); + if (rtl_first > rtl_last) + std::swap(rtl_first, rtl_last); + return {rtl_first, rtl_last + 1}; + } +}; + +void emit_mux_anyseq(Module* mod, const SigSpec& mux_input, const SigSpec& mux_output, EnableLogic enable) { + auto anyseq = mod->Anyseq(NEW_ID, mux_input.size()); + if (enable.bit == (enable.pol ? State::S1 : State::S0)) { + mod->connect(mux_output, anyseq); + } + SigSpec mux_a, mux_b; + if (enable.pol) { + mux_a = mux_input; + mux_b = anyseq; + } else { + mux_a = anyseq; + mux_b = mux_input; + } + (void)mod->addMux(NEW_ID, + mux_a, + mux_b, + enable.bit, + mux_output); +} + +bool abstract_state_port(FfData& ff, SigSpec& port_sig, std::set offsets, EnableLogic enable) { + Wire* abstracted = ff.module->addWire(NEW_ID, offsets.size()); + SigSpec mux_input; + int abstracted_idx = 0; + for (int d_idx = 0; d_idx < ff.width; d_idx++) { + if (offsets.count(d_idx)) { + mux_input.append(port_sig[d_idx]); + port_sig[d_idx].wire = abstracted; + port_sig[d_idx].offset = abstracted_idx; + log_assert(abstracted_idx < abstracted->width); + abstracted_idx++; + } + } + emit_mux_anyseq(ff.module, mux_input, abstracted, enable); + (void)ff.emit(); + return true; +} + +using SelReason=std::variant; + +dict> gather_selected_reps(Module* mod, const std::vector &slices, SigMap& sigmap) { + dict> selected_reps; + + if (slices.empty()) { + // Collect reps for all wire bits of selected wires + for (auto wire : mod->selected_wires()) + for (auto bit : sigmap(wire)) + selected_reps.insert(bit).first->second.push_back(wire); + + // Collect reps for all output wire bits of selected cells + for (auto cell : mod->selected_cells()) + for (auto conn : cell->connections()) + if (cell->output(conn.first)) + for (auto bit : conn.second.bits()) + selected_reps.insert(sigmap(bit)).first->second.push_back(cell); + } else { + if (mod->selected_wires().size() != 1 || !mod->selected_cells().empty()) + log_error("Slices are only supported for single-wire selections\n"); + + auto wire = mod->selected_wires()[0]; + + for (auto slice : slices) { + auto [begin, end] = slice.wire_range(wire); + for (int i = begin; i < end; i++) { + selected_reps.insert(sigmap(SigBit(wire, i))).first->second.push_back(wire); + } + } + + } + return selected_reps; +} + +void explain_selections(const std::vector& reasons) { + for (std::variant reason : reasons) { + if (Cell** cell_reason = std::get_if(&reason)) + log_debug("\tcell %s\n", (*cell_reason)->name.c_str()); + else if (Wire** wire_reason = std::get_if(&reason)) + log_debug("\twire %s\n", (*wire_reason)->name.c_str()); + else + log_assert(false && "insane reason variant\n"); + } +} + +unsigned int abstract_state(Module* mod, EnableLogic enable, const std::vector &slices) { + CellTypes ct; + ct.setup_internals_ff(); + SigMap sigmap(mod); + dict> selected_reps = gather_selected_reps(mod, slices, sigmap); + + unsigned int changed = 0; + std::vector ffs; + // Abstract flop inputs if they're driving a selected output rep + for (auto cell : mod->cells()) { + if (!ct.cell_types.count(cell->type)) + continue; + FfData ff(nullptr, cell); + if (ff.has_sr) + log_cmd_error("SR not supported\n"); + ffs.push_back(ff); + } + for (auto ff : ffs) { + // A bit inefficient + std::set offsets_to_abstract; + for (int i = 0; i < GetSize(ff.sig_q); i++) { + SigBit bit = ff.sig_q[i]; + if (selected_reps.count(sigmap(bit))) { + log_debug("Abstracting state for %s.Q[%i] in module %s due to selections:\n", log_id(ff.cell), i, log_id(mod)); + explain_selections(selected_reps.at(sigmap(bit))); + offsets_to_abstract.insert(i); + } + } + + if (offsets_to_abstract.empty()) + continue; + + // Normalize to simpler FF + ff.unmap_ce(); + ff.unmap_srst(); + if (ff.has_arst) + ff.arst_to_aload(); + + bool cell_changed = false; + + if (ff.has_aload) + cell_changed = abstract_state_port(ff, ff.sig_ad, offsets_to_abstract, enable); + cell_changed |= abstract_state_port(ff, ff.sig_d, offsets_to_abstract, enable); + changed += cell_changed; + } + return changed; +} + +bool abstract_value_cell_port(Module* mod, Cell* cell, std::set offsets, IdString port_name, EnableLogic enable) { + Wire* to_abstract = mod->addWire(NEW_ID, offsets.size()); + SigSpec mux_input; + SigSpec mux_output; + const SigSpec& old_port = cell->getPort(port_name); + SigSpec new_port = old_port; + int to_abstract_idx = 0; + for (int port_idx = 0; port_idx < old_port.size(); port_idx++) { + if (offsets.count(port_idx)) { + mux_output.append(old_port[port_idx]); + SigBit in_bit {to_abstract, to_abstract_idx}; + new_port.replace(port_idx, in_bit); + mux_input.append(in_bit); + log_assert(to_abstract_idx < to_abstract->width); + to_abstract_idx++; + } + } + cell->setPort(port_name, new_port); + emit_mux_anyseq(mod, mux_input, mux_output, enable); + return true; +} + +bool abstract_value_mod_port(Module* mod, Wire* wire, std::set offsets, EnableLogic enable) { + Wire* to_abstract = mod->addWire(NEW_ID, wire); + to_abstract->port_input = true; + to_abstract->port_id = wire->port_id; + wire->port_input = false; + wire->port_id = 0; + mod->swap_names(wire, to_abstract); + SigSpec mux_input; + SigSpec mux_output; + SigSpec direct_lhs; + SigSpec direct_rhs; + for (int port_idx = 0; port_idx < wire->width; port_idx++) { + if (offsets.count(port_idx)) { + mux_output.append(SigBit(wire, port_idx)); + mux_input.append(SigBit(to_abstract, port_idx)); + } else { + direct_lhs.append(SigBit(wire, port_idx)); + direct_rhs.append(SigBit(to_abstract, port_idx)); + } + } + mod->connections_.push_back(SigSig(direct_lhs, direct_rhs)); + emit_mux_anyseq(mod, mux_input, mux_output, enable); + return true; +} + +unsigned int abstract_value(Module* mod, EnableLogic enable, const std::vector &slices) { + SigMap sigmap(mod); + dict> selected_reps = gather_selected_reps(mod, slices, sigmap); + unsigned int changed = 0; + std::vector cells_snapshot = mod->cells(); + for (auto cell : cells_snapshot) { + for (auto conn : cell->connections()) + if (cell->output(conn.first)) { + std::set offsets_to_abstract; + for (int i = 0; i < conn.second.size(); i++) { + if (selected_reps.count(sigmap(conn.second[i]))) { + log_debug("Abstracting value for %s.%s[%i] in module %s due to selections:\n", + log_id(cell), log_id(conn.first), i, log_id(mod)); + explain_selections(selected_reps.at(sigmap(conn.second[i]))); + offsets_to_abstract.insert(i); + } + } + if (offsets_to_abstract.empty()) + continue; + + changed += abstract_value_cell_port(mod, cell, offsets_to_abstract, conn.first, enable); + } + } + std::vector wires_snapshot = mod->wires(); + for (auto wire : wires_snapshot) + if (wire->port_input) { + std::set offsets_to_abstract; + for (auto bit : SigSpec(wire)) + if (selected_reps.count(sigmap(bit))) { + log_debug("Abstracting value for module input port bit %s in module %s due to selections:\n", + log_signal(bit), log_id(mod)); + explain_selections(selected_reps.at(sigmap(bit))); + offsets_to_abstract.insert(bit.offset); + } + if (offsets_to_abstract.empty()) + continue; + + changed += abstract_value_mod_port(mod, wire, offsets_to_abstract, enable); + } + return changed; +} + +unsigned int abstract_init(Module* mod, const std::vector &slices) { + unsigned int changed = 0; + FfInitVals initvals; + SigMap sigmap(mod); + dict> selected_reps = gather_selected_reps(mod, slices, sigmap); + initvals.set(&sigmap, mod); + for (auto bit : selected_reps) { + log_debug("Removing init bit on %s due to selections:\n", log_signal(bit.first)); + explain_selections(bit.second); + initvals.remove_init(bit.first); + changed++; + } + return changed; +} + +struct AbstractPass : public Pass { + AbstractPass() : Pass("abstract", "replace signals with abstract values during formal verification") { } + void help() override { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" abstract [mode] [options] [selection]\n"); + log("\n"); + log("Perform abstraction of signals within the design. Abstraction replaces a signal\n"); + log("with an unconstrained abstract value that can take an arbitrary concrete value\n"); + log("during formal verification. The mode and options control when a signal should\n"); + log("be abstracted and how it should affect FFs present in the design.\n"); + log("\n"); + log("Modes:"); + log("\n"); + log(" -state\n"); + log(" The selected FFs will be modified to load a new abstract value on every\n"); + log(" active clock edge, async reset or async load. This is independent of any\n"); + log(" clock enable that may be present on the FF cell. Conditional abstraction\n"); + log(" is supported with the -enable/-enabeln options. If present, the condition\n"); + log(" is sampled at the same time as the FF would smaple the correspnding data\n"); + log(" or async-data input whose value will be replaced with an abstract value.\n"); + log("\n"); + log(" The selection can be used to specify which state bits to abstract. For\n"); + log(" each selected wire, any state bits that the wire is driven by will be\n"); + log(" abstracted. For a selected FF cell, all of its state is abstracted.\n"); + log(" Individual bits of a single wire can be abtracted using the -slice and\n"); + log(" -rtlilslice options.\n"); + log("\n"); + log(" -init\n"); + log(" The selected FFs will be modified to have an abstract initial value.\n"); + log(" The -enable/-enablen options are not supported in this mode.\n"); + log(" \n"); + log(" The selection is used in the same way as it is for the -state mode.\n"); + log("\n"); + log(" -value\n"); + log(" The drivers of the selected signals will be replaced with an abstract\n"); + log(" value. In this mode, the abstract value can change at any time and is\n"); + log(" not synchronized to any clock or other signal. Conditional abstraction\n"); + log(" is supported with the -enable/-enablen options. The condition will\n"); + log(" combinationally select between the original driver and the abstract\n"); + log(" value.\n"); + log("\n"); + log(" The selection can be used to specify which output bits of which drivers\n"); + log(" to abtract. For a selected cell, all its output bits will be abstracted.\n"); + log(" For a selected wire, every output bit that is driving the wire will be\n"); + log(" abstracted. Individual bits of a single wire can be abstracted using the\n"); + log(" -slice and -rtlilslice options.\n"); + log("\n"); + log(" -enable \n"); + log(" -enablen \n"); + log(" Perform conditional abstraction with a named single bit wire as\n"); + log(" condition. For -enable the wire is used as an active-high condition and\n"); + log(" for -enablen as an active-low condition. See the description of the\n"); + log(" -state and -value modes for details on how the condition affects the\n"); + log(" abstractions performed by either mode. This option is not supported in\n"); + log(" the -init mode.\n"); + log("\n"); + log(" -slice :\n"); + log(" -slice \n"); + log(" -rtlilslice :\n"); + log(" -rtlilslice \n"); + log(" Limit the abstraction to a slice of a single selected wire. The targeted\n"); + log(" bits of the wire can be given as an inclusive range of indices or as a\n"); + log(" single index. When using the -slice option, the indices are interpreted\n"); + log(" following the source level declaration of the wire. This means the\n"); + log(" -slice option will respect declarations with a non-zero-based index range\n"); + log(" or with reversed bitorder. The -rtlilslice options will always use\n"); + log(" zero-based indexing where index 0 corresponds to the least significant\n"); + log(" bit of the wire.\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *design) override { + log_header(design, "Executing ABSTRACT pass.\n"); + + size_t argidx; + enum Mode { + None, + State, + Initial, + Value, + }; + Mode mode = Mode::None; + enum Enable { + Always = -1, + ActiveLow = false, // ensuring we can use bool(enable) + ActiveHigh = true, + }; + Enable enable = Enable::Always; + std::string enable_name; + std::vector slices; + for (argidx = 1; argidx < args.size(); argidx++) + { + std::string arg = args[argidx]; + if (arg == "-state") { + mode = State; + continue; + } + if (arg == "-init") { + mode = Initial; + continue; + } + if (arg == "-value") { + mode = Value; + continue; + } + if (arg == "-enable" && argidx + 1 < args.size()) { + if (enable != Enable::Always) + log_cmd_error("Multiple enable condition are not supported\n"); + enable_name = args[++argidx]; + enable = Enable::ActiveHigh; + continue; + } + if (arg == "-enablen" && argidx + 1 < args.size()) { + if (enable != Enable::Always) + log_cmd_error("Multiple enable condition are not supported\n"); + enable_name = args[++argidx]; + enable = Enable::ActiveLow; + continue; + } + if (arg == "-slice" && argidx + 1 < args.size()) { + slices.emplace_back(SliceIndices::HdlSlice, args[++argidx]); + continue; + } + if (arg == "-rtlilslice" && argidx + 1 < args.size()) { + slices.emplace_back(SliceIndices::RtlilSlice, args[++argidx]); + continue; + } + break; + } + extra_args(args, argidx, design); + + if (enable != Enable::Always) { + if (mode == Mode::Initial) + log_cmd_error("Conditional initial value abstraction is not supported\n"); + + if (enable_name.empty()) + log_cmd_error("Unspecified enable wire\n"); + } + + unsigned int changed = 0; + if ((mode == State) || (mode == Value)) { + for (auto mod : design->selected_modules()) { + EnableLogic enable_logic = { State::S1, true }; + if (enable != Enable::Always) { + Wire *enable_wire = mod->wire("\\" + enable_name); + if (!enable_wire) + log_cmd_error("Enable wire %s not found in module %s\n", enable_name.c_str(), mod->name.c_str()); + if (GetSize(enable_wire) != 1) + log_cmd_error("Enable wire %s must have width 1 but has width %d in module %s\n", + enable_name.c_str(), GetSize(enable_wire), mod->name.c_str()); + enable_logic = { enable_wire, enable == Enable::ActiveHigh }; + } + if (mode == State) + changed += abstract_state(mod, enable_logic, slices); + else + changed += abstract_value(mod, enable_logic, slices); + } + if (mode == State) + log("Abstracted %d stateful cells.\n", changed); + else + log("Abstracted %d driver ports.\n", changed); + } else if (mode == Initial) { + for (auto mod : design->selected_modules()) { + changed += abstract_init(mod, slices); + } + log("Abstracted %d init bits.\n", changed); + } else { + log_cmd_error("No mode selected, see help message\n"); + } + } +} AbstractPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/splitcells.cc b/passes/cmds/splitcells.cc index 82ed49074..074bf0f64 100644 --- a/passes/cmds/splitcells.cc +++ b/passes/cmds/splitcells.cc @@ -134,7 +134,7 @@ struct SplitcellsWorker return GetSize(slices)-1; } - if (cell->type.in("$ff", "$dff", "$dffe", "$dffsr", "$dffsre", "$adff", "$adffe", "$aldffe", + if (cell->type.in("$ff", "$dff", "$dffe", "$dffsr", "$dffsre", "$adff", "$adffe", "$aldff", "$aldffe", "$sdff", "$sdffce", "$sdffe", "$dlatch", "$dlatchsr", "$adlatch")) { auto splitports = {ID::D, ID::Q, ID::AD, ID::SET, ID::CLR}; diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc index cc8cb046d..b0d0498ea 100644 --- a/passes/memory/memory_libmap.cc +++ b/passes/memory/memory_libmap.cc @@ -2258,6 +2258,8 @@ struct MemoryLibMapPass : public Pass { log("using FF mapping for memory %s.%s\n", log_id(module->name), log_id(mem.memid)); } else { map.emit(map.cfgs[idx]); + // Rebuild indices after modifying module + worker = MapWorker(module); } } } diff --git a/passes/opt/.gitignore b/passes/opt/.gitignore new file mode 100644 index 000000000..4b40d3505 --- /dev/null +++ b/passes/opt/.gitignore @@ -0,0 +1 @@ +/peepopt*_pm.h \ No newline at end of file diff --git a/passes/opt/Makefile.inc b/passes/opt/Makefile.inc index 76bf8a84e..b796535e3 100644 --- a/passes/opt/Makefile.inc +++ b/passes/opt/Makefile.inc @@ -22,4 +22,18 @@ OBJS += passes/opt/opt_lut_ins.o OBJS += passes/opt/opt_ffinv.o OBJS += passes/opt/pmux2shiftx.o OBJS += passes/opt/muxpack.o + +OBJS += passes/opt/peepopt.o +GENFILES += passes/opt/peepopt_pm.h +passes/opt/peepopt.o: passes/opt/peepopt_pm.h +$(eval $(call add_extra_objs,passes/opt/peepopt_pm.h)) + +PEEPOPT_PATTERN = passes/opt/peepopt_shiftmul_right.pmg +PEEPOPT_PATTERN += passes/opt/peepopt_shiftmul_left.pmg +PEEPOPT_PATTERN += passes/opt/peepopt_shiftadd.pmg +PEEPOPT_PATTERN += passes/opt/peepopt_muldiv.pmg +PEEPOPT_PATTERN += passes/opt/peepopt_formal_clockgateff.pmg + +passes/opt/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN) + $(P) mkdir -p $(dir $@) && $(PYTHON_EXECUTABLE) $< -o $@ -p peepopt $(filter-out $<,$^) endif diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc index 51212fa0e..c37c03607 100644 --- a/passes/opt/opt_clean.cc +++ b/passes/opt/opt_clean.cc @@ -601,7 +601,7 @@ void rmunused_module(RTLIL::Module *module, bool purge_mode, bool verbose, bool std::vector delcells; for (auto cell : module->cells()) - if (cell->type.in(ID($pos), ID($_BUF_)) && !cell->has_keep_attr()) { + if (cell->type.in(ID($pos), ID($_BUF_), ID($buf)) && !cell->has_keep_attr()) { bool is_signed = cell->type == ID($pos) && cell->getParam(ID::A_SIGNED).as_bool(); RTLIL::SigSpec a = cell->getPort(ID::A); RTLIL::SigSpec y = cell->getPort(ID::Y); diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index ac4a65156..62a0ffc48 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -1315,6 +1315,10 @@ skip_fine_alu: RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); RTLIL::SigSpec sig_y(cell->type == ID($shiftx) ? RTLIL::State::Sx : RTLIL::State::S0, cell->getParam(ID::Y_WIDTH).as_int()); + // Limit indexing to the size of a, which is behaviourally identical (result is all 0) + // and avoids integer overflow of i + shift_bits when e.g. ID::B == INT_MAX + shift_bits = min(shift_bits, GetSize(sig_a)); + if (cell->type != ID($shiftx) && GetSize(sig_a) < GetSize(sig_y)) sig_a.extend_u0(GetSize(sig_y), cell->getParam(ID::A_SIGNED).as_bool()); diff --git a/passes/pmgen/peepopt.cc b/passes/opt/peepopt.cc similarity index 99% rename from passes/pmgen/peepopt.cc rename to passes/opt/peepopt.cc index 5b678ee55..b12f4777e 100644 --- a/passes/pmgen/peepopt.cc +++ b/passes/opt/peepopt.cc @@ -28,7 +28,7 @@ bool did_something; // scratchpad configurations for pmgen int shiftadd_max_ratio; -#include "passes/pmgen/peepopt_pm.h" +#include "passes/opt/peepopt_pm.h" struct PeepoptPass : public Pass { PeepoptPass() : Pass("peepopt", "collection of peephole optimizers") { } diff --git a/passes/pmgen/peepopt_formal_clockgateff.pmg b/passes/opt/peepopt_formal_clockgateff.pmg similarity index 100% rename from passes/pmgen/peepopt_formal_clockgateff.pmg rename to passes/opt/peepopt_formal_clockgateff.pmg diff --git a/passes/pmgen/peepopt_muldiv.pmg b/passes/opt/peepopt_muldiv.pmg similarity index 100% rename from passes/pmgen/peepopt_muldiv.pmg rename to passes/opt/peepopt_muldiv.pmg diff --git a/passes/pmgen/peepopt_shiftadd.pmg b/passes/opt/peepopt_shiftadd.pmg similarity index 100% rename from passes/pmgen/peepopt_shiftadd.pmg rename to passes/opt/peepopt_shiftadd.pmg diff --git a/passes/pmgen/peepopt_shiftmul_left.pmg b/passes/opt/peepopt_shiftmul_left.pmg similarity index 100% rename from passes/pmgen/peepopt_shiftmul_left.pmg rename to passes/opt/peepopt_shiftmul_left.pmg diff --git a/passes/pmgen/peepopt_shiftmul_right.pmg b/passes/opt/peepopt_shiftmul_right.pmg similarity index 100% rename from passes/pmgen/peepopt_shiftmul_right.pmg rename to passes/opt/peepopt_shiftmul_right.pmg diff --git a/passes/opt/share.cc b/passes/opt/share.cc index 6081d140e..40d6d1d20 100644 --- a/passes/opt/share.cc +++ b/passes/opt/share.cc @@ -87,7 +87,7 @@ struct ShareWorker queue_bits.clear(); for (auto &pbit : portbits) { - if (pbit.cell->type == ID($mux) || pbit.cell->type == ID($pmux)) { + if ((pbit.cell->type == ID($mux) || pbit.cell->type == ID($pmux)) && visited_cells.count(pbit.cell) == 0) { pool bits = modwalker.sigmap(pbit.cell->getPort(ID::S)).to_sigbit_pool(); terminal_bits.insert(bits.begin(), bits.end()); queue_bits.insert(bits.begin(), bits.end()); diff --git a/passes/pmgen/Makefile.inc b/passes/pmgen/Makefile.inc index 6fa7d1fd7..17abfd43e 100644 --- a/passes/pmgen/Makefile.inc +++ b/passes/pmgen/Makefile.inc @@ -1,70 +1,10 @@ -%_pm.h: passes/pmgen/pmgen.py %.pmg - $(P) mkdir -p $(dir $@) && $(PYTHON_EXECUTABLE) $< -o $@ -p $(subst _pm.h,,$(notdir $@)) $(filter-out $<,$^) +passes/pmgen/%_pm.h: passes/pmgen/pmgen.py passes/pmgen/%.pmg + $(P) mkdir -p $(dir $@) && $(PYTHON_EXECUTABLE) $< -o $@ -p $(notdir $*) $(filter-out $<,$^) # -------------------------------------- OBJS += passes/pmgen/test_pmgen.o GENFILES += passes/pmgen/test_pmgen_pm.h -passes/pmgen/test_pmgen.o: passes/pmgen/test_pmgen_pm.h passes/pmgen/ice40_dsp_pm.h passes/pmgen/peepopt_pm.h passes/pmgen/xilinx_srl_pm.h +passes/pmgen/test_pmgen.o: passes/pmgen/test_pmgen_pm.h techlibs/ice40/ice40_dsp_pm.h techlibs/xilinx/xilinx_srl_pm.h $(eval $(call add_extra_objs,passes/pmgen/test_pmgen_pm.h)) -# -------------------------------------- - -OBJS += passes/pmgen/ice40_dsp.o -GENFILES += passes/pmgen/ice40_dsp_pm.h -passes/pmgen/ice40_dsp.o: passes/pmgen/ice40_dsp_pm.h -$(eval $(call add_extra_objs,passes/pmgen/ice40_dsp_pm.h)) - -# -------------------------------------- - -OBJS += passes/pmgen/ice40_wrapcarry.o -GENFILES += passes/pmgen/ice40_wrapcarry_pm.h -passes/pmgen/ice40_wrapcarry.o: passes/pmgen/ice40_wrapcarry_pm.h -$(eval $(call add_extra_objs,passes/pmgen/ice40_wrapcarry_pm.h)) - -# -------------------------------------- - -OBJS += passes/pmgen/xilinx_dsp.o -GENFILES += passes/pmgen/xilinx_dsp_pm.h -GENFILES += passes/pmgen/xilinx_dsp48a_pm.h -GENFILES += passes/pmgen/xilinx_dsp_CREG_pm.h -GENFILES += passes/pmgen/xilinx_dsp_cascade_pm.h -passes/pmgen/xilinx_dsp.o: passes/pmgen/xilinx_dsp_pm.h passes/pmgen/xilinx_dsp48a_pm.h passes/pmgen/xilinx_dsp_CREG_pm.h passes/pmgen/xilinx_dsp_cascade_pm.h -$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_pm.h)) -$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp48a_pm.h)) -$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_CREG_pm.h)) -$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_cascade_pm.h)) - -# -------------------------------------- - -OBJS += passes/pmgen/microchip_dsp.o -GENFILES += passes/pmgen/microchip_dsp_pm.h -GENFILES += passes/pmgen/microchip_dsp_CREG_pm.h -GENFILES += passes/pmgen/microchip_dsp_cascade_pm.h -passes/pmgen/microchip_dsp.o: passes/pmgen/microchip_dsp_pm.h passes/pmgen/microchip_dsp_CREG_pm.h passes/pmgen/microchip_dsp_cascade_pm.h -$(eval $(call add_extra_objs,passes/pmgen/microchip_dsp_pm.h)) -$(eval $(call add_extra_objs,passes/pmgen/microchip_dsp_CREG_pm.h)) -$(eval $(call add_extra_objs,passes/pmgen/microchip_dsp_cascade_pm.h)) - -# -------------------------------------- - -OBJS += passes/pmgen/peepopt.o -GENFILES += passes/pmgen/peepopt_pm.h -passes/pmgen/peepopt.o: passes/pmgen/peepopt_pm.h -$(eval $(call add_extra_objs,passes/pmgen/peepopt_pm.h)) - -PEEPOPT_PATTERN = passes/pmgen/peepopt_shiftmul_right.pmg -PEEPOPT_PATTERN += passes/pmgen/peepopt_shiftmul_left.pmg -PEEPOPT_PATTERN += passes/pmgen/peepopt_shiftadd.pmg -PEEPOPT_PATTERN += passes/pmgen/peepopt_muldiv.pmg -PEEPOPT_PATTERN += passes/pmgen/peepopt_formal_clockgateff.pmg - -passes/pmgen/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN) - $(P) mkdir -p passes/pmgen && $(PYTHON_EXECUTABLE) $< -o $@ -p peepopt $(filter-out $<,$^) - -# -------------------------------------- - -OBJS += passes/pmgen/xilinx_srl.o -GENFILES += passes/pmgen/xilinx_srl_pm.h -passes/pmgen/xilinx_srl.o: passes/pmgen/xilinx_srl_pm.h -$(eval $(call add_extra_objs,passes/pmgen/xilinx_srl_pm.h)) diff --git a/passes/pmgen/README.md b/passes/pmgen/README.md index 3205be1b5..15569ebfc 100644 --- a/passes/pmgen/README.md +++ b/passes/pmgen/README.md @@ -22,7 +22,7 @@ list of cells from that module: foobar_pm pm(module, module->selected_cells()); The caller must make sure that none of the cells in the 2nd argument are -deleted for as long as the patter matcher instance is used. +deleted for as long as the pattern matcher instance is used. At any time it is possible to disable cells, preventing them from showing up in any future matches: diff --git a/passes/pmgen/test_pmgen.cc b/passes/pmgen/test_pmgen.cc index beff59778..fc41848f7 100644 --- a/passes/pmgen/test_pmgen.cc +++ b/passes/pmgen/test_pmgen.cc @@ -24,8 +24,8 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN #include "passes/pmgen/test_pmgen_pm.h" -#include "passes/pmgen/ice40_dsp_pm.h" -#include "passes/pmgen/xilinx_srl_pm.h" +#include "techlibs/ice40/ice40_dsp_pm.h" +#include "techlibs/xilinx/xilinx_srl_pm.h" #include "generate.h" diff --git a/passes/techmap/simplemap.cc b/passes/techmap/simplemap.cc index 7461460fe..b23985770 100644 --- a/passes/techmap/simplemap.cc +++ b/passes/techmap/simplemap.cc @@ -42,6 +42,14 @@ void simplemap_not(RTLIL::Module *module, RTLIL::Cell *cell) } } +void simplemap_buf(RTLIL::Module *module, RTLIL::Cell *cell) +{ + RTLIL::SigSpec sig_a = cell->getPort(ID::A); + RTLIL::SigSpec sig_y = cell->getPort(ID::Y); + + module->connect(RTLIL::SigSig(sig_y, sig_a)); +} + void simplemap_pos(RTLIL::Module *module, RTLIL::Cell *cell) { RTLIL::SigSpec sig_a = cell->getPort(ID::A); @@ -411,6 +419,7 @@ void simplemap_get_mappers(dict { mappers[ID($not)] = simplemap_not; mappers[ID($pos)] = simplemap_pos; + mappers[ID($buf)] = simplemap_buf; mappers[ID($and)] = simplemap_bitop; mappers[ID($or)] = simplemap_bitop; mappers[ID($xor)] = simplemap_bitop; diff --git a/techlibs/.gitignore b/techlibs/.gitignore index e81e4e4b8..ddb102bec 100644 --- a/techlibs/.gitignore +++ b/techlibs/.gitignore @@ -1 +1,2 @@ blackbox.v +*_pm.h diff --git a/techlibs/common/techmap.v b/techlibs/common/techmap.v index 119296147..6cf4f3415 100644 --- a/techlibs/common/techmap.v +++ b/techlibs/common/techmap.v @@ -59,7 +59,7 @@ module _90_simplemap_compare_ops; endmodule (* techmap_simplemap *) -(* techmap_celltype = "$pos $slice $concat $mux $tribuf $bmux $bwmux $bweqx" *) +(* techmap_celltype = "$buf $pos $slice $concat $mux $tribuf $bmux $bwmux $bweqx" *) module _90_simplemap_various; endmodule diff --git a/techlibs/ice40/Makefile.inc b/techlibs/ice40/Makefile.inc index 4bf8e4e86..027a0dd33 100644 --- a/techlibs/ice40/Makefile.inc +++ b/techlibs/ice40/Makefile.inc @@ -14,3 +14,13 @@ $(eval $(call add_share_file,share/ice40,techlibs/ice40/spram.txt)) $(eval $(call add_share_file,share/ice40,techlibs/ice40/spram_map.v)) $(eval $(call add_share_file,share/ice40,techlibs/ice40/dsp_map.v)) $(eval $(call add_share_file,share/ice40,techlibs/ice40/abc9_model.v)) + +OBJS += techlibs/ice40/ice40_dsp.o +GENFILES += techlibs/ice40/ice40_dsp_pm.h +techlibs/ice40/ice40_dsp.o: techlibs/ice40/ice40_dsp_pm.h +$(eval $(call add_extra_objs,techlibs/ice40/ice40_dsp_pm.h)) + +OBJS += techlibs/ice40/ice40_wrapcarry.o +GENFILES += techlibs/ice40/ice40_wrapcarry_pm.h +techlibs/ice40/ice40_wrapcarry.o: techlibs/ice40/ice40_wrapcarry_pm.h +$(eval $(call add_extra_objs,techlibs/ice40/ice40_wrapcarry_pm.h)) diff --git a/passes/pmgen/ice40_dsp.cc b/techlibs/ice40/ice40_dsp.cc similarity index 99% rename from passes/pmgen/ice40_dsp.cc rename to techlibs/ice40/ice40_dsp.cc index 5720c9b1e..3f00028c3 100644 --- a/passes/pmgen/ice40_dsp.cc +++ b/techlibs/ice40/ice40_dsp.cc @@ -23,7 +23,7 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -#include "passes/pmgen/ice40_dsp_pm.h" +#include "techlibs/ice40/ice40_dsp_pm.h" void create_ice40_dsp(ice40_dsp_pm &pm) { diff --git a/passes/pmgen/ice40_dsp.pmg b/techlibs/ice40/ice40_dsp.pmg similarity index 100% rename from passes/pmgen/ice40_dsp.pmg rename to techlibs/ice40/ice40_dsp.pmg diff --git a/passes/pmgen/ice40_wrapcarry.cc b/techlibs/ice40/ice40_wrapcarry.cc similarity index 99% rename from passes/pmgen/ice40_wrapcarry.cc rename to techlibs/ice40/ice40_wrapcarry.cc index c936d02dc..014490564 100644 --- a/passes/pmgen/ice40_wrapcarry.cc +++ b/techlibs/ice40/ice40_wrapcarry.cc @@ -23,7 +23,7 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -#include "passes/pmgen/ice40_wrapcarry_pm.h" +#include "techlibs/ice40/ice40_wrapcarry_pm.h" void create_ice40_wrapcarry(ice40_wrapcarry_pm &pm) { diff --git a/passes/pmgen/ice40_wrapcarry.pmg b/techlibs/ice40/ice40_wrapcarry.pmg similarity index 100% rename from passes/pmgen/ice40_wrapcarry.pmg rename to techlibs/ice40/ice40_wrapcarry.pmg diff --git a/techlibs/intel/common/brams_map_m9k.v b/techlibs/intel/common/brams_map_m9k.v index d0f07c1de..c80ebe3f4 100644 --- a/techlibs/intel/common/brams_map_m9k.v +++ b/techlibs/intel/common/brams_map_m9k.v @@ -63,10 +63,10 @@ module \$__M9K_ALTSYNCRAM_SINGLEPORT_FULL (CLK2, CLK3, A1ADDR, A1DATA, A1EN, B1A .width_byteena_a (1), // Forced value .numwords_b ( NUMWORDS ), .numwords_a ( NUMWORDS ), - .widthad_b ( CFG_DBITS ), - .width_b ( CFG_ABITS ), - .widthad_a ( CFG_DBITS ), - .width_a ( CFG_ABITS ) + .widthad_b ( CFG_ABITS ), + .width_b ( CFG_DBITS ), + .widthad_a ( CFG_ABITS ), + .width_a ( CFG_DBITS ) ) _TECHMAP_REPLACE_ ( .data_a(B1DATA), .address_a(B1ADDR), diff --git a/techlibs/intel/max10/cells_sim.v b/techlibs/intel/max10/cells_sim.v index 7705fa27a..b163aac40 100644 --- a/techlibs/intel/max10/cells_sim.v +++ b/techlibs/intel/max10/cells_sim.v @@ -2,6 +2,7 @@ * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf + * Copyright (C) 2024 Richard Herveille * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -289,4 +290,56 @@ module fiftyfivenm_pll output vcooverrange; output vcounderrange; -endmodule // cycloneive_pll +endmodule // max10_pll + + +/* MAX10 MULT clearbox model */ +(* blackbox *) +module fiftyfivenm_mac_mult ( + dataa, + datab, + dataout, + signa, + signb, + + aclr, + clk, + ena +); + parameter dataa_clock = "none"; + parameter dataa_width = 18; + parameter datab_clock = "none"; + parameter datab_width = 18; + parameter signa_clock = "none"; + parameter signb_clock = "none"; + parameter lpm_type = "fiftyfivenm_mac_mult"; + + input [dataa_width -1:0] dataa; + input [datab_width -1:0] datab; + output [(dataa_width+datab_width)-1:0] dataout; + input signa; + input signb; + input aclr; + input clk; + input ena; +endmodule //fiftyfivenm_mac_mult + +module fiftyfivenm_mac_out ( + dataa, + dataout, + + aclr, + clk, + ena +); + + parameter dataa_width = 38; + parameter output_clock = "none"; + parameter lpm_type = "fiftyfivenm_mac_out"; + + input [dataa_width-1:0] dataa; + output [dataa_width-1:0] dataout; + input aclr; + input clk; + input ena; +endmodule //fiftyfivenm_mac_out diff --git a/techlibs/intel/max10/dsp_map.v b/techlibs/intel/max10/dsp_map.v new file mode 100644 index 000000000..1c7827b5e --- /dev/null +++ b/techlibs/intel/max10/dsp_map.v @@ -0,0 +1,73 @@ +module \$__MUL18X18 (input [17:0] A, input [17:0] B, output [35:0] Y); + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + parameter A_WIDTH = 0; + parameter B_WIDTH = 0; + parameter Y_WIDTH = 0; + + wire [A_WIDTH+B_WIDTH-1:0] mult_result; + + fiftyfivenm_mac_mult #( + .dataa_clock ("none"), + .datab_clock ("none"), + .signa_clock ("none"), + .signb_clock ("none"), + .dataa_width (A_WIDTH), + .datab_width (B_WIDTH), + .lpm_type ("fiftyfivenm_mac_mult") + ) _TECHMAP_REPLACE_mac_mult ( + //Data path + .dataa ( A ), + .datab ( B ), + .dataout( mult_result ), + .signa ( A_SIGNED != 0 ? 1'b1 : 1'b0), + .signb ( B_SIGNED != 0 ? 1'b1 : 1'b0) + ); + + fiftyfivenm_mac_out #( + .dataa_width (A_WIDTH + B_WIDTH), + .output_clock ("none"), + .lpm_type ("fiftyfivenm_mac_out") + ) _TECHMAP_REPLACE_mac_out ( + .dataa (mult_result), + .dataout (Y) + ); +endmodule + + +module \$__MUL9X9 (input [8:0] A, input [8:0] B, output [17:0] Y); + parameter A_SIGNED = 0; + parameter B_SIGNED = 0; + parameter A_WIDTH = 0; + parameter B_WIDTH = 0; + parameter Y_WIDTH = 0; + + wire [A_WIDTH+B_WIDTH-1:0] mult_result; + + fiftyfivenm_mac_mult #( + .dataa_clock ("none"), + .datab_clock ("none"), + .signa_clock ("none"), + .signb_clock ("none"), + .dataa_width (A_WIDTH), + .datab_width (B_WIDTH), + .lpm_type ("fiftyfivenm_mac_mult") + ) _TECHMAP_REPLACE_mac_mult ( + //Data path + .dataa ( A ), + .datab ( B ), + .dataout( mult_result ), + .signa ( A_SIGNED != 0 ? 1'b1 : 1'b0), + .signb ( B_SIGNED != 0 ? 1'b1 : 1'b0) + ); + + fiftyfivenm_mac_out #( + .dataa_width (A_WIDTH + B_WIDTH), + .output_clock ("none"), + .lpm_type ("fiftyfivenm_mac_out") + ) _TECHMAP_REPLACE_mac_out ( + .dataa (mult_result), + .dataout (Y) + ); +endmodule + diff --git a/techlibs/intel/synth_intel.cc b/techlibs/intel/synth_intel.cc index e9594e6d8..11567ece9 100644 --- a/techlibs/intel/synth_intel.cc +++ b/techlibs/intel/synth_intel.cc @@ -2,6 +2,7 @@ * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia Wolf + * Copyright (C) 2024 Richard Herveille * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -62,12 +63,19 @@ struct SynthIntelPass : public ScriptPass { log(" from label is synonymous to 'begin', and empty to label is\n"); log(" synonymous to the end of the command list.\n"); log("\n"); + log(" -dff\n"); + log(" pass DFFs to ABC to perform sequential logic optimisations\n"); + log(" (EXPERIMENTAL)\n"); + log("\n"); log(" -iopads\n"); log(" use IO pad cells in output netlist\n"); log("\n"); log(" -nobram\n"); log(" do not use block RAM cells in output netlist\n"); log("\n"); + log(" -nodsp\n"); + log(" do not map multipliers to MUL18/MUL9 cells\n"); + log("\n"); log(" -noflatten\n"); log(" do not flatten design before synthesis\n"); log("\n"); @@ -80,7 +88,7 @@ struct SynthIntelPass : public ScriptPass { } string top_opt, family_opt, vout_file, blif_file; - bool retime, flatten, nobram, iopads; + bool retime, flatten, nobram, dff, nodsp, iopads; void clear_flags() override { @@ -91,6 +99,8 @@ struct SynthIntelPass : public ScriptPass { retime = false; flatten = true; nobram = false; + dff = false; + nodsp = false; iopads = false; } @@ -130,6 +140,14 @@ struct SynthIntelPass : public ScriptPass { iopads = true; continue; } + if (args[argidx] == "-dff") { + dff = true; + continue; + } + if (args[argidx] == "-nodsp") { + nodsp = true; + continue; + } if (args[argidx] == "-nobram") { nobram = true; continue; @@ -178,15 +196,42 @@ struct SynthIntelPass : public ScriptPass { run(stringf("hierarchy -check %s", help_mode ? "-top " : top_opt.c_str())); } - if (flatten && check_label("flatten", "(unless -noflatten)")) { - run("proc"); - run("flatten"); - run("tribuf -logic"); - run("deminout"); - } - if (check_label("coarse")) { - run("synth -run coarse"); + run("proc"); + if (flatten || help_mode) + run("flatten", "(skip if -noflatten)"); + run("tribuf -logic"); + run("deminout"); + run("opt_expr"); + run("opt_clean"); + run("check"); + run("opt -nodffe -nosdff"); + run("fsm"); + run("opt"); + run("wreduce"); + run("peepopt"); + run("opt_clean"); + run("techmap -map +/cmp2lut.v -D LUT_WIDTH=4"); + run("opt_expr"); + run("opt_clean"); + + if (help_mode) { + run("techmap -map +mul2dsp.v [...]", "(unless -nodsp)"); + } else if (!nodsp) { + run("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=18 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=10 -D DSP_B_MINWIDTH=4 -D DSP_NAME=$__MUL18X18"); + run("chtype -set $mul t:$__soft_mul"); + run("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=18 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=4 -D DSP_B_MINWIDTH=10 -D DSP_NAME=$__MUL18X18"); + run("chtype -set $mul t:$__soft_mul"); + run("techmap -map +/mul2dsp.v -D DSP_A_MAXWIDTH=9 -D DSP_B_MAXWIDTH=9 -D DSP_A_MINWIDTH=4 -D DSP_B_MINWIDTH=4 -D DSP_NAME=$__MUL9X9"); + run("chtype -set $mul t:$__soft_mul"); + run("alumacc"); + run(stringf("techmap -map +/intel/%s/dsp_map.v", family_opt.c_str())); + } else { + run("alumacc"); + } + run("opt"); + run("memory -nomap"); + run("opt_clean"); } if (!nobram && check_label("map_bram", "(skip if -nobram)")) { @@ -219,7 +264,10 @@ struct SynthIntelPass : public ScriptPass { } if (check_label("map_luts")) { - run("abc -lut 4" + string(retime ? " -dff" : "")); + run("abc9 -lut 4 -W 300" + string(dff ? " -dff" : "")); + run("clean"); + run("opt -fast"); + run("autoname"); run("clean"); } diff --git a/techlibs/microchip/Makefile.inc b/techlibs/microchip/Makefile.inc index b1905e2cf..ab294b465 100644 --- a/techlibs/microchip/Makefile.inc +++ b/techlibs/microchip/Makefile.inc @@ -29,3 +29,12 @@ $(eval $(call add_share_file,share/microchip,techlibs/microchip/LSRAM_map.v)) $(eval $(call add_share_file,share/microchip,techlibs/microchip/LSRAM.txt)) $(eval $(call add_share_file,share/microchip,techlibs/microchip/uSRAM_map.v)) $(eval $(call add_share_file,share/microchip,techlibs/microchip/uSRAM.txt)) + +OBJS += techlibs/microchip/microchip_dsp.o +GENFILES += techlibs/microchip/microchip_dsp_pm.h +GENFILES += techlibs/microchip/microchip_dsp_CREG_pm.h +GENFILES += techlibs/microchip/microchip_dsp_cascade_pm.h +techlibs/microchip/microchip_dsp.o: techlibs/microchip/microchip_dsp_pm.h techlibs/microchip/microchip_dsp_CREG_pm.h techlibs/microchip/microchip_dsp_cascade_pm.h +$(eval $(call add_extra_objs,techlibs/microchip/microchip_dsp_pm.h)) +$(eval $(call add_extra_objs,techlibs/microchip/microchip_dsp_CREG_pm.h)) +$(eval $(call add_extra_objs,techlibs/microchip/microchip_dsp_cascade_pm.h)) diff --git a/passes/pmgen/microchip_dsp.cc b/techlibs/microchip/microchip_dsp.cc similarity index 98% rename from passes/pmgen/microchip_dsp.cc rename to techlibs/microchip/microchip_dsp.cc index 31fd24194..3dfcb8905 100644 --- a/passes/pmgen/microchip_dsp.cc +++ b/techlibs/microchip/microchip_dsp.cc @@ -23,9 +23,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -#include "passes/pmgen/microchip_dsp_CREG_pm.h" -#include "passes/pmgen/microchip_dsp_cascade_pm.h" -#include "passes/pmgen/microchip_dsp_pm.h" +#include "techlibs/microchip/microchip_dsp_CREG_pm.h" +#include "techlibs/microchip/microchip_dsp_cascade_pm.h" +#include "techlibs/microchip/microchip_dsp_pm.h" void microchip_dsp_pack(microchip_dsp_pm &pm) { diff --git a/passes/pmgen/microchip_dsp.pmg b/techlibs/microchip/microchip_dsp.pmg similarity index 100% rename from passes/pmgen/microchip_dsp.pmg rename to techlibs/microchip/microchip_dsp.pmg diff --git a/passes/pmgen/microchip_dsp_CREG.pmg b/techlibs/microchip/microchip_dsp_CREG.pmg similarity index 100% rename from passes/pmgen/microchip_dsp_CREG.pmg rename to techlibs/microchip/microchip_dsp_CREG.pmg diff --git a/passes/pmgen/microchip_dsp_cascade.pmg b/techlibs/microchip/microchip_dsp_cascade.pmg similarity index 100% rename from passes/pmgen/microchip_dsp_cascade.pmg rename to techlibs/microchip/microchip_dsp_cascade.pmg diff --git a/techlibs/quicklogic/Makefile.inc b/techlibs/quicklogic/Makefile.inc index ade144371..a54a7ec03 100644 --- a/techlibs/quicklogic/Makefile.inc +++ b/techlibs/quicklogic/Makefile.inc @@ -6,6 +6,7 @@ OBJS += techlibs/quicklogic/ql_bram_merge.o OBJS += techlibs/quicklogic/ql_bram_types.o OBJS += techlibs/quicklogic/ql_dsp_simd.o OBJS += techlibs/quicklogic/ql_dsp_io_regs.o +OBJS += techlibs/quicklogic/ql_ioff.o # -------------------------------------- @@ -40,4 +41,4 @@ $(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf $(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/dsp_final_map.v)) $(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/TDP18K_FIFO.v)) $(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/ufifo_ctl.v)) -$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/sram1024x18_mem.v)) \ No newline at end of file +$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/sram1024x18_mem.v)) diff --git a/techlibs/quicklogic/ql_ioff.cc b/techlibs/quicklogic/ql_ioff.cc new file mode 100644 index 000000000..87b62e855 --- /dev/null +++ b/techlibs/quicklogic/ql_ioff.cc @@ -0,0 +1,125 @@ +#include "kernel/log.h" +#include "kernel/modtools.h" +#include "kernel/register.h" +#include "kernel/rtlil.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct QlIoffPass : public Pass { + QlIoffPass() : Pass("ql_ioff", "Infer I/O FFs for qlf_k6n10f architecture") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" ql_ioff [selection]\n"); + log("\n"); + log("This pass promotes qlf_k6n10f registers directly connected to a top-level I/O\n"); + log("port to I/O FFs.\n"); + log("\n"); + } + + void execute(std::vector, RTLIL::Design *design) override + { + log_header(design, "Executing QL_IOFF pass.\n"); + + ModWalker modwalker(design); + Module *module = design->top_module(); + if (!module) + return; + modwalker.setup(module); + pool input_ffs; + dict> output_ffs; + dict> output_bit_aliases; + + for (Wire* wire : module->wires()) + if (wire->port_output) { + output_ffs[wire].resize(wire->width, nullptr); + for (SigBit bit : SigSpec(wire)) + output_bit_aliases[modwalker.sigmap(bit)].insert(bit); + } + + for (auto cell : module->selected_cells()) { + if (cell->type.in(ID(dffsre), ID(sdffsre))) { + log_debug("Checking cell %s.\n", cell->name.c_str()); + bool e_const = cell->getPort(ID::E).is_fully_ones(); + bool r_const = cell->getPort(ID::R).is_fully_ones(); + bool s_const = cell->getPort(ID::S).is_fully_ones(); + + if (!(e_const && r_const && s_const)) { + log_debug("not promoting: E, R, or S is used\n"); + continue; + } + + SigSpec d = cell->getPort(ID::D); + log_assert(GetSize(d) == 1); + if (modwalker.has_inputs(d)) { + log_debug("Cell %s is potentially eligible for promotion to input IOFF.\n", cell->name.c_str()); + // check that d_sig has no other consumers + pool portbits; + modwalker.get_consumers(portbits, d); + if (GetSize(portbits) > 1) { + log_debug("not promoting: D has other consumers\n"); + continue; + } + input_ffs.insert(cell); + continue; // prefer input FFs over output FFs + } + + SigSpec q = cell->getPort(ID::Q); + log_assert(GetSize(q) == 1); + if (modwalker.has_outputs(q) && !modwalker.has_consumers(q)) { + log_debug("Cell %s is potentially eligible for promotion to output IOFF.\n", cell->name.c_str()); + for (SigBit bit : output_bit_aliases[modwalker.sigmap(q)]) { + log_assert(bit.is_wire()); + output_ffs[bit.wire][bit.offset] = cell; + } + + } + } + } + + for (auto cell : input_ffs) { + log("Promoting register %s to input IOFF.\n", log_signal(cell->getPort(ID::Q))); + cell->type = ID(dff); + cell->unsetPort(ID::E); + cell->unsetPort(ID::R); + cell->unsetPort(ID::S); + } + for (auto & [old_port_output, ioff_cells] : output_ffs) { + if (std::any_of(ioff_cells.begin(), ioff_cells.end(), [](Cell * c) { return c != nullptr; })) + { + // create replacement output wire + RTLIL::Wire* new_port_output = module->addWire(NEW_ID, old_port_output->width); + new_port_output->start_offset = old_port_output->start_offset; + module->swap_names(old_port_output, new_port_output); + std::swap(old_port_output->port_id, new_port_output->port_id); + std::swap(old_port_output->port_input, new_port_output->port_input); + std::swap(old_port_output->port_output, new_port_output->port_output); + std::swap(old_port_output->upto, new_port_output->upto); + std::swap(old_port_output->is_signed, new_port_output->is_signed); + std::swap(old_port_output->attributes, new_port_output->attributes); + + // create new output FFs + SigSpec sig_o(old_port_output); + SigSpec sig_n(new_port_output); + for (int i = 0; i < new_port_output->width; i++) { + if (ioff_cells[i]) { + log("Promoting %s to output IOFF.\n", log_signal(sig_n[i])); + + RTLIL::Cell *new_cell = module->addCell(NEW_ID, ID(dff)); + new_cell->setPort(ID::C, ioff_cells[i]->getPort(ID::C)); + new_cell->setPort(ID::D, ioff_cells[i]->getPort(ID::D)); + new_cell->setPort(ID::Q, sig_n[i]); + new_cell->set_bool_attribute(ID::keep); + } else { + module->connect(sig_n[i], sig_o[i]); + } + } + } + } + } +} QlIoffPass; + +PRIVATE_NAMESPACE_END diff --git a/techlibs/quicklogic/synth_quicklogic.cc b/techlibs/quicklogic/synth_quicklogic.cc index 76ef44570..07ec769b5 100644 --- a/techlibs/quicklogic/synth_quicklogic.cc +++ b/techlibs/quicklogic/synth_quicklogic.cc @@ -78,7 +78,7 @@ struct SynthQuickLogicPass : public ScriptPass { } string top_opt, blif_file, edif_file, family, currmodule, verilog_file, lib_path; - bool abc9, inferAdder, nobram, bramTypes, dsp; + bool abc9, inferAdder, nobram, bramTypes, dsp, ioff; void clear_flags() override { @@ -94,6 +94,7 @@ struct SynthQuickLogicPass : public ScriptPass { bramTypes = false; lib_path = "+/quicklogic/"; dsp = true; + ioff = true; } void set_scratchpad_defaults(RTLIL::Design *design) { @@ -158,6 +159,10 @@ struct SynthQuickLogicPass : public ScriptPass { dsp = false; continue; } + if (args[argidx] == "-noioff") { + ioff = false; + continue; + } break; } extra_args(args, argidx, design); @@ -328,6 +333,13 @@ struct SynthQuickLogicPass : public ScriptPass { run("clean"); run("opt_lut"); } + + if (check_label("iomap", "(for qlf_k6n10f, skip if -noioff)") && (family == "qlf_k6n10f" || help_mode)) { + if (ioff || help_mode) { + run("ql_ioff"); + run("opt_clean"); + } + } if (check_label("check")) { run("autoname"); diff --git a/techlibs/xilinx/Makefile.inc b/techlibs/xilinx/Makefile.inc index 2d3d0f63c..a9ca35233 100644 --- a/techlibs/xilinx/Makefile.inc +++ b/techlibs/xilinx/Makefile.inc @@ -46,3 +46,19 @@ $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/xc7_dsp_map.v)) $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/xcu_dsp_map.v)) $(eval $(call add_share_file,share/xilinx,techlibs/xilinx/abc9_model.v)) + +OBJS += techlibs/xilinx/xilinx_dsp.o +GENFILES += techlibs/xilinx/xilinx_dsp_pm.h +GENFILES += techlibs/xilinx/xilinx_dsp48a_pm.h +GENFILES += techlibs/xilinx/xilinx_dsp_CREG_pm.h +GENFILES += techlibs/xilinx/xilinx_dsp_cascade_pm.h +techlibs/xilinx/xilinx_dsp.o: techlibs/xilinx/xilinx_dsp_pm.h techlibs/xilinx/xilinx_dsp48a_pm.h techlibs/xilinx/xilinx_dsp_CREG_pm.h techlibs/xilinx/xilinx_dsp_cascade_pm.h +$(eval $(call add_extra_objs,techlibs/xilinx/xilinx_dsp_pm.h)) +$(eval $(call add_extra_objs,techlibs/xilinx/xilinx_dsp48a_pm.h)) +$(eval $(call add_extra_objs,techlibs/xilinx/xilinx_dsp_CREG_pm.h)) +$(eval $(call add_extra_objs,techlibs/xilinx/xilinx_dsp_cascade_pm.h)) + +OBJS += techlibs/xilinx/xilinx_srl.o +GENFILES += techlibs/xilinx/xilinx_srl_pm.h +techlibs/xilinx/xilinx_srl.o: techlibs/xilinx/xilinx_srl_pm.h +$(eval $(call add_extra_objs,techlibs/xilinx/xilinx_srl_pm.h)) diff --git a/passes/pmgen/xilinx_dsp.cc b/techlibs/xilinx/xilinx_dsp.cc similarity index 99% rename from passes/pmgen/xilinx_dsp.cc rename to techlibs/xilinx/xilinx_dsp.cc index 9af7d1eec..f95930a90 100644 --- a/passes/pmgen/xilinx_dsp.cc +++ b/techlibs/xilinx/xilinx_dsp.cc @@ -25,10 +25,10 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -#include "passes/pmgen/xilinx_dsp_pm.h" -#include "passes/pmgen/xilinx_dsp48a_pm.h" -#include "passes/pmgen/xilinx_dsp_CREG_pm.h" -#include "passes/pmgen/xilinx_dsp_cascade_pm.h" +#include "techlibs/xilinx/xilinx_dsp_pm.h" +#include "techlibs/xilinx/xilinx_dsp48a_pm.h" +#include "techlibs/xilinx/xilinx_dsp_CREG_pm.h" +#include "techlibs/xilinx/xilinx_dsp_cascade_pm.h" static Cell* addDsp(Module *module) { Cell *cell = module->addCell(NEW_ID, ID(DSP48E1)); diff --git a/passes/pmgen/xilinx_dsp.pmg b/techlibs/xilinx/xilinx_dsp.pmg similarity index 100% rename from passes/pmgen/xilinx_dsp.pmg rename to techlibs/xilinx/xilinx_dsp.pmg diff --git a/passes/pmgen/xilinx_dsp48a.pmg b/techlibs/xilinx/xilinx_dsp48a.pmg similarity index 100% rename from passes/pmgen/xilinx_dsp48a.pmg rename to techlibs/xilinx/xilinx_dsp48a.pmg diff --git a/passes/pmgen/xilinx_dsp_CREG.pmg b/techlibs/xilinx/xilinx_dsp_CREG.pmg similarity index 100% rename from passes/pmgen/xilinx_dsp_CREG.pmg rename to techlibs/xilinx/xilinx_dsp_CREG.pmg diff --git a/passes/pmgen/xilinx_dsp_cascade.pmg b/techlibs/xilinx/xilinx_dsp_cascade.pmg similarity index 100% rename from passes/pmgen/xilinx_dsp_cascade.pmg rename to techlibs/xilinx/xilinx_dsp_cascade.pmg diff --git a/passes/pmgen/xilinx_srl.cc b/techlibs/xilinx/xilinx_srl.cc similarity index 99% rename from passes/pmgen/xilinx_srl.cc rename to techlibs/xilinx/xilinx_srl.cc index 7b20a7d40..32a0064a6 100644 --- a/passes/pmgen/xilinx_srl.cc +++ b/techlibs/xilinx/xilinx_srl.cc @@ -24,7 +24,7 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -#include "passes/pmgen/xilinx_srl_pm.h" +#include "techlibs/xilinx/xilinx_srl_pm.h" void run_fixed(xilinx_srl_pm &pm) { diff --git a/passes/pmgen/xilinx_srl.pmg b/techlibs/xilinx/xilinx_srl.pmg similarity index 100% rename from passes/pmgen/xilinx_srl.pmg rename to techlibs/xilinx/xilinx_srl.pmg diff --git a/tests/arch/quicklogic/qlf_k6n10f/counter.ys b/tests/arch/quicklogic/qlf_k6n10f/counter.ys index ebb6ce243..d1a9ec692 100644 --- a/tests/arch/quicklogic/qlf_k6n10f/counter.ys +++ b/tests/arch/quicklogic/qlf_k6n10f/counter.ys @@ -2,7 +2,7 @@ read_verilog ../../common/counter.v hierarchy -top top proc flatten -equiv_opt -assert -multiclock -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f # equivalency check +equiv_opt -assert -multiclock -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f -noioff # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd top # Constrain all select calls below inside the top module select -assert-count 4 t:$lut diff --git a/tests/arch/quicklogic/qlf_k6n10f/dffs.ys b/tests/arch/quicklogic/qlf_k6n10f/dffs.ys index 79a16c941..e12963ae6 100644 --- a/tests/arch/quicklogic/qlf_k6n10f/dffs.ys +++ b/tests/arch/quicklogic/qlf_k6n10f/dffs.ys @@ -5,7 +5,7 @@ design -save read hierarchy -top my_dff proc -equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f # equivalency check +equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f -noioff # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd my_dff # Constrain all select calls below inside the top module select -assert-count 1 t:sdffsre @@ -14,7 +14,7 @@ select -assert-none t:sdffsre %% t:* %D design -load read hierarchy -top my_dffe proc -equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f # equivalency check +equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f -noioff # equivalency check design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd my_dffe # Constrain all select calls below inside the top module select -assert-count 1 t:sdffsre diff --git a/tests/arch/quicklogic/qlf_k6n10f/ioff.ys b/tests/arch/quicklogic/qlf_k6n10f/ioff.ys new file mode 100644 index 000000000..da1fb2946 --- /dev/null +++ b/tests/arch/quicklogic/qlf_k6n10f/ioff.ys @@ -0,0 +1,209 @@ +# test: acceptable for output IOFF promotion +read_verilog <> ~31'b0; // shift by INT_MAX +endmodule +EOF + +# This should succeed, even with UBSAN halt_on_error +opt_expr diff --git a/tests/techmap/buf.ys b/tests/techmap/buf.ys new file mode 100644 index 000000000..2b2534fab --- /dev/null +++ b/tests/techmap/buf.ys @@ -0,0 +1,13 @@ +read_verilog -icells < + std::string operator()(const testing::TestParamInfo& info) const { + std::string name = testing::PrintToString(info.param); + for (auto &c : name) + if (!('0' <= c && c <= '9') && !('a' <= c && c <= 'z') && !('A' <= c && c <= 'Z') ) c = '_'; + return name; + } + }; + + class KernelRtlilTest : public testing::Test { + protected: + KernelRtlilTest() { + if (log_files.empty()) log_files.emplace_back(stdout); + } + }; TEST_F(KernelRtlilTest, ConstAssignCompare) { @@ -74,6 +89,83 @@ namespace RTLIL { } } + + class WireRtlVsHdlIndexConversionTest : + public KernelRtlilTest, + public testing::WithParamInterface> + {}; + + INSTANTIATE_TEST_SUITE_P( + WireRtlVsHdlIndexConversionInstance, + WireRtlVsHdlIndexConversionTest, + testing::Values( + std::make_tuple(false, 0, 10), + std::make_tuple(true, 0, 10), + std::make_tuple(false, 4, 10), + std::make_tuple(true, 4, 10), + std::make_tuple(false, -4, 10), + std::make_tuple(true, -4, 10), + std::make_tuple(false, 0, 1), + std::make_tuple(true, 0, 1), + std::make_tuple(false, 4, 1), + std::make_tuple(true, 4, 1), + std::make_tuple(false, -4, 1), + std::make_tuple(true, -4, 1) + ), + SafePrintToStringParamName() + ); + + TEST_P(WireRtlVsHdlIndexConversionTest, WireRtlVsHdlIndexConversion) { + std::unique_ptr mod = std::make_unique(); + Wire *wire = mod->addWire(ID(test), 10); + + auto [upto, start_offset, width] = GetParam(); + + wire->upto = upto; + wire->start_offset = start_offset; + wire->width = width; + + int smallest = INT_MAX; + int largest = INT_MIN; + + for (int i = 0; i < wire->width; i++) { + int j = wire->to_hdl_index(i); + smallest = std::min(smallest, j); + largest = std::max(largest, j); + EXPECT_EQ( + std::make_pair(wire->from_hdl_index(j), j), + std::make_pair(i, wire->to_hdl_index(i)) + ); + } + + EXPECT_EQ(smallest, start_offset); + EXPECT_EQ(largest, start_offset + wire->width - 1); + + for (int i = 1; i < wire->width; i++) { + EXPECT_EQ( + wire->to_hdl_index(i) - wire->to_hdl_index(i - 1), + upto ? -1 : 1 + ); + } + + for (int j = smallest; j < largest; j++) { + int i = wire->from_hdl_index(j); + EXPECT_EQ( + std::make_pair(wire->from_hdl_index(j), j), + std::make_pair(i, wire->to_hdl_index(i)) + ); + } + + for (int i = -10; i < 0; i++) + EXPECT_EQ(wire->to_hdl_index(i), INT_MIN); + for (int i = wire->width; i < wire->width + 10; i++) + EXPECT_EQ(wire->to_hdl_index(i), INT_MIN); + for (int j = smallest - 10; j < smallest; j++) + EXPECT_EQ(wire->from_hdl_index(j), INT_MIN); + for (int j = largest + 1; j < largest + 11; j++) + EXPECT_EQ(wire->from_hdl_index(j), INT_MIN); + } + } YOSYS_NAMESPACE_END diff --git a/tests/various/abstract_init.ys b/tests/various/abstract_init.ys new file mode 100644 index 000000000..2e0cf2e64 --- /dev/null +++ b/tests/various/abstract_init.ys @@ -0,0 +1,121 @@ +design -reset +read_verilog < muxes on inputs and outputs +design -load split_output +select -assert-count 0 t:$mux +abstract -value -enable magic w:* w:magic %d +select -assert-count 3 t:$mux +# All cells selected -> muxes on outputs only +design -load split_output +select -assert-count 0 t:$mux +abstract -value -enable magic t:* +select -assert-count 1 t:$mux +# -----------------------------------------------------------------------------