diff --git a/.gitignore b/.gitignore index 5c3d8f4e9..239ae742b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.gcno *~ __pycache__ +/.cache /.cproject /.project /.settings @@ -15,6 +16,7 @@ __pycache__ /qtcreator.config /qtcreator.creator /qtcreator.creator.user +/compile_commands.json /coverage.info /coverage_html /Makefile.conf 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 0249038e5..820ab83c7 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+1 +YOSYS_VER := 0.51+17 +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:"; \ @@ -1001,6 +1021,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 @@ -1036,7 +1068,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 @@ -1057,6 +1089,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/aiger/xaiger.cc b/backends/aiger/xaiger.cc index 3ca8b205a..baf504ba2 100644 --- a/backends/aiger/xaiger.cc +++ b/backends/aiger/xaiger.cc @@ -53,6 +53,8 @@ struct XAigerWriter dict arrival_times; vector> aig_gates; + vector bit2aig_stack; + int next_loop_check = 1024; vector aig_outputs; int aig_m = 0, aig_i = 0, aig_l = 0, aig_o = 0, aig_a = 0; @@ -76,6 +78,24 @@ struct XAigerWriter return it->second; } + if (GetSize(bit2aig_stack)== next_loop_check) { + for (int i = 0; i < next_loop_check; ++i) + { + SigBit report_bit = bit2aig_stack[i]; + if (report_bit != bit) + continue; + for (int j = i; j < next_loop_check; ++j) { + report_bit = bit2aig_stack[j]; + if (report_bit.is_wire() && report_bit.wire->name.isPublic()) + break; + } + log_error("Found combinatorial logic loop while processing signal %s.\n", log_signal(report_bit)); + } + next_loop_check *= 2; + } + + bit2aig_stack.push_back(bit); + // NB: Cannot use iterator returned from aig_map.insert() // since this function is called recursively @@ -93,6 +113,8 @@ struct XAigerWriter a = bit2aig(alias_map.at(bit)); } + bit2aig_stack.pop_back(); + if (bit == State::Sx || bit == State::Sz) { log_debug("Design contains 'x' or 'z' bits. Treating as 1'b0.\n"); a = aig_map.at(State::S0); diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 931454ada..b9958c5fb 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -616,7 +616,7 @@ std::string escape_c_string(const std::string &input) output.push_back('\\'); output.push_back(c); } else { - char l = c & 0x3, m = (c >> 3) & 0x3, h = (c >> 6) & 0x3; + char l = c & 0x7, m = (c >> 3) & 0x7, h = (c >> 6) & 0x3; output.append("\\"); output.push_back('0' + h); output.push_back('0' + m); 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/cell/word_arith.rst b/docs/source/cell/word_arith.rst index 49070814a..8e3daa27f 100644 --- a/docs/source/cell/word_arith.rst +++ b/docs/source/cell/word_arith.rst @@ -1,7 +1,7 @@ Coarse arithmetics ------------------ -.. todo:: Add information about `$alu`, `$fa`, and `$lcu` cells. +.. todo:: Add information about `$alu`, `$fa`, `$macc_v2`, and `$lcu` cells. The `$macc` cell type represents a generalized multiply and accumulate operation. The cell is purely combinational. It outputs the result of summing up diff --git a/docs/source/code_examples/fifo/fifo.ys b/docs/source/code_examples/fifo/fifo.ys index 57a28e63e..e6b9bf69d 100644 --- a/docs/source/code_examples/fifo/fifo.ys +++ b/docs/source/code_examples/fifo/fifo.ys @@ -67,7 +67,7 @@ show -color maroon3 @new_cells -notitle -format dot -prefix rdata_memrdv2 o:rdat # ======================================================== alumacc -select -set new_cells t:$alu t:$macc +select -set new_cells t:$alu t:$macc_v2 show -color maroon3 @new_cells -notitle -format dot -prefix rdata_alumacc o:rdata %ci* # ======================================================== 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..6a0e92167 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' @@ -93,6 +93,9 @@ bibtex_bibfiles = ['literature.bib'] latex_elements = { 'releasename': 'Version', 'preamble': r''' +\pdfinfoomitdate 1 +\pdfsuppressptexinfo 1 +\pdftrailerid{} \usepackage{lmodern} \usepackage{comment} diff --git a/docs/source/getting_started/example_synth.rst b/docs/source/getting_started/example_synth.rst index 189eaddfa..e215586cc 100644 --- a/docs/source/getting_started/example_synth.rst +++ b/docs/source/getting_started/example_synth.rst @@ -523,7 +523,7 @@ That brings us to the fourth and final part for the iCE40 synthesis flow: :name: synth_coarse4 Where before each type of arithmetic operation had its own cell, e.g. `$add`, we -now want to extract these into `$alu` and `$macc` cells which can help identify +now want to extract these into `$alu` and `$macc_v2` cells which can help identify opportunities for reusing logic. We do this by running `alumacc`, which we can see produce the following changes in our example design: 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/frontends/verific/verific.cc b/frontends/verific/verific.cc index cdd0ed802..a320a6efd 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -4298,7 +4298,7 @@ struct ReadPass : public Pass { log("\n"); log(" read {-f|-F} \n"); log("\n"); - log("Load and execute the specified command file. (Requires Verific.)\n"); + log("Load and execute the specified command file.\n"); log("Check verific command for more information about supported commands in file.\n"); log("\n"); log("\n"); @@ -4412,10 +4412,14 @@ struct ReadPass : public Pass { if (args[1] == "-f" || args[1] == "-F") { if (use_verific) { args[0] = "verific"; - Pass::call(design, args); } else { - cmd_error(args, 1, "This version of Yosys is built without Verific support.\n"); +#if !defined(__wasm) + args[0] = "read_verilog_file_list"; +#else + cmd_error(args, 1, "Command files are not supported on this platform.\n"); +#endif } + Pass::call(design, args); return; } diff --git a/frontends/verilog/verilog_frontend.cc b/frontends/verilog/verilog_frontend.cc index d363d71fb..e4e705c39 100644 --- a/frontends/verilog/verilog_frontend.cc +++ b/frontends/verilog/verilog_frontend.cc @@ -26,6 +26,10 @@ * */ +#if !defined(__wasm) +#include +#endif + #include "verilog_frontend.h" #include "preproc.h" #include "kernel/yosys.h" @@ -672,6 +676,89 @@ struct VerilogDefines : public Pass { } } VerilogDefines; +#if !defined(__wasm) + +static void parse_file_list(const std::string &file_list_path, RTLIL::Design *design, bool relative_to_file_list_path) +{ + std::ifstream flist(file_list_path); + if (!flist.is_open()) { + log_error("Verilog file list file does not exist"); + exit(1); + } + + std::filesystem::path file_list_parent_dir = std::filesystem::path(file_list_path).parent_path(); + + std::string v_file_name; + while (std::getline(flist, v_file_name)) { + if (v_file_name.empty()) { + continue; + } + + std::filesystem::path verilog_file_path; + if (relative_to_file_list_path) { + verilog_file_path = file_list_parent_dir / v_file_name; + } else { + verilog_file_path = std::filesystem::current_path() / v_file_name; + } + + bool is_sv = (verilog_file_path.extension() == ".sv"); + + std::vector read_verilog_cmd = {"read_verilog", "-defer"}; + if (is_sv) { + read_verilog_cmd.push_back("-sv"); + } + read_verilog_cmd.push_back(verilog_file_path.string()); + Pass::call(design, read_verilog_cmd); + } + + flist.close(); +} + +struct VerilogFileList : public Pass { + VerilogFileList() : Pass("read_verilog_file_list", "Parse a Verilog file list") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" read_verilog_file_list [options]\n"); + log("\n"); + log("Parse a Verilog file list, and pass the list of Verilog files to read_verilog\n"); + log("command\n"); + log("\n"); + log(" -F file_list_path\n"); + log(" File list file contains list of Verilog files to be parsed, any path is\n"); + log(" treated relative to the file list file\n"); + log("\n"); + log(" -f file_list_path\n"); + log(" File list file contains list of Verilog files to be parsed, any path is\n"); + log(" treated relative to current working directroy\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + std::string arg = args[argidx]; + if (arg == "-F" && argidx + 1 < args.size()) { + std::string file_list_path = args[++argidx]; + parse_file_list(file_list_path, design, true); + continue; + } + if (arg == "-f" && argidx + 1 < args.size()) { + std::string file_list_path = args[++argidx]; + parse_file_list(file_list_path, design, false); + continue; + } + break; + } + + extra_args(args, argidx, design); + } +} VerilogFilelist; + +#endif + YOSYS_NAMESPACE_END // the yyerror function used by bison to report parser errors diff --git a/kernel/celledges.cc b/kernel/celledges.cc index 68e55db02..8129e6b1b 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -453,7 +453,7 @@ bool YOSYS_NAMESPACE_PREFIX AbstractCellEdgesDatabase::add_edges_from_cell(RTLIL } // FIXME: $mul $div $mod $divfloor $modfloor $slice $concat - // FIXME: $lut $sop $alu $lcu $macc $fa + // FIXME: $lut $sop $alu $lcu $macc $macc_v2 $fa // FIXME: $mul $div $mod $divfloor $modfloor $pow $slice $concat $bweqx // FIXME: $lut $sop $alu $lcu $macc $fa $logic_and $logic_or $bwmux diff --git a/kernel/celltypes.h b/kernel/celltypes.h index 3167a9add..0ce5db54d 100644 --- a/kernel/celltypes.h +++ b/kernel/celltypes.h @@ -144,6 +144,7 @@ struct CellTypes setup_type(ID($lcu), {ID::P, ID::G, ID::CI}, {ID::CO}, true); setup_type(ID($alu), {ID::A, ID::B, ID::CI, ID::BI}, {ID::X, ID::Y, ID::CO}, true); + setup_type(ID($macc_v2), {ID::A, ID::B, ID::C}, {ID::Y}, true); setup_type(ID($fa), {ID::A, ID::B, ID::C}, {ID::X, ID::Y}, true); } diff --git a/kernel/consteval.h b/kernel/consteval.h index 331d8f128..05e94ab86 100644 --- a/kernel/consteval.h +++ b/kernel/consteval.h @@ -310,7 +310,7 @@ struct ConstEval } } } - else if (cell->type == ID($macc)) + else if (cell->type.in(ID($macc), ID($macc_v2))) { Macc macc; macc.from_cell(cell); diff --git a/kernel/constids.inc b/kernel/constids.inc index d68e2dfe6..4fdbb3dc8 100644 --- a/kernel/constids.inc +++ b/kernel/constids.inc @@ -276,3 +276,11 @@ X(Y) X(Y_WIDTH) X(area) X(capacitance) +X(NPRODUCTS) +X(NADDENDS) +X(PRODUCT_NEGATED) +X(ADDEND_NEGATED) +X(A_WIDTHS) +X(B_WIDTHS) +X(C_WIDTHS) +X(C_SIGNED) diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 6ba1150ce..6e9f2924a 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -634,10 +634,11 @@ std::string escape_cxx_string(const std::string &input) output.push_back('\\'); output.push_back(c); } else { - char l = c & 0xf, h = (c >> 4) & 0xf; - output.append("\\x"); - output.push_back((h < 10 ? '0' + h : 'a' + h - 10)); - output.push_back((l < 10 ? '0' + l : 'a' + l - 10)); + char l = c & 0x7, m = (c >> 3) & 0x7, h = (c >> 6) & 0x3; + output.push_back('\\'); + output.push_back('0' + h); + output.push_back('0' + m); + output.push_back('0' + l); } } output.push_back('"'); diff --git a/kernel/macc.h b/kernel/macc.h index 55940769d..5fc6036ed 100644 --- a/kernel/macc.h +++ b/kernel/macc.h @@ -82,7 +82,7 @@ struct Macc new_ports.swap(ports); } - void from_cell(RTLIL::Cell *cell) + void from_cell_v1(RTLIL::Cell *cell) { RTLIL::SigSpec port_a = cell->getPort(ID::A); @@ -136,52 +136,128 @@ struct Macc log_assert(port_a_cursor == GetSize(port_a)); } - void to_cell(RTLIL::Cell *cell) const + void from_cell(RTLIL::Cell *cell) { - RTLIL::SigSpec port_a; - std::vector config_bits; - int max_size = 0, num_bits = 0; + if (cell->type == ID($macc)) { + from_cell_v1(cell); + return; + } + log_assert(cell->type == ID($macc_v2)); - for (auto &port : ports) { - max_size = max(max_size, GetSize(port.in_a)); - max_size = max(max_size, GetSize(port.in_b)); + RTLIL::SigSpec port_a = cell->getPort(ID::A); + RTLIL::SigSpec port_b = cell->getPort(ID::B); + RTLIL::SigSpec port_c = cell->getPort(ID::C); + + ports.clear(); + + int nproducts = cell->getParam(ID::NPRODUCTS).as_int(); + const Const &product_neg = cell->getParam(ID::PRODUCT_NEGATED); + const Const &a_widths = cell->getParam(ID::A_WIDTHS); + const Const &b_widths = cell->getParam(ID::B_WIDTHS); + const Const &a_signed = cell->getParam(ID::A_SIGNED); + const Const &b_signed = cell->getParam(ID::B_SIGNED); + int ai = 0, bi = 0; + for (int i = 0; i < nproducts; i++) { + port_t term; + + log_assert(a_signed[i] == b_signed[i]); + term.is_signed = (a_signed[i] == State::S1); + int a_width = a_widths.extract(16 * i, 16).as_int(false); + int b_width = b_widths.extract(16 * i, 16).as_int(false); + + term.in_a = port_a.extract(ai, a_width); + ai += a_width; + term.in_b = port_b.extract(bi, b_width); + bi += b_width; + term.do_subtract = (product_neg[i] == State::S1); + + ports.push_back(term); + } + log_assert(port_a.size() == ai); + log_assert(port_b.size() == bi); + + int naddends = cell->getParam(ID::NADDENDS).as_int(); + const Const &addend_neg = cell->getParam(ID::ADDEND_NEGATED); + const Const &c_widths = cell->getParam(ID::C_WIDTHS); + const Const &c_signed = cell->getParam(ID::C_SIGNED); + int ci = 0; + for (int i = 0; i < naddends; i++) { + port_t term; + + term.is_signed = (c_signed[i] == State::S1); + int c_width = c_widths.extract(16 * i, 16).as_int(false); + + term.in_a = port_c.extract(ci, c_width); + ci += c_width; + term.do_subtract = (addend_neg[i] == State::S1); + + ports.push_back(term); + } + log_assert(port_c.size() == ci); + } + + void to_cell(RTLIL::Cell *cell) + { + cell->type = ID($macc_v2); + + int nproducts = 0, naddends = 0; + Const a_signed, b_signed, a_widths, b_widths, product_negated; + Const c_signed, c_widths, addend_negated; + SigSpec a, b, c; + + for (int i = 0; i < (int) ports.size(); i++) { + SigSpec term_a = ports[i].in_a, term_b = ports[i].in_b; + + if (term_b.empty()) { + // addend + c_widths.append(Const(term_a.size(), 16)); + c_signed.append(ports[i].is_signed ? RTLIL::S1 : RTLIL::S0); + addend_negated.append(ports[i].do_subtract ? RTLIL::S1 : RTLIL::S0); + c.append(term_a); + naddends++; + } else { + // product + a_widths.append(Const(term_a.size(), 16)); + b_widths.append(Const(term_b.size(), 16)); + a_signed.append(ports[i].is_signed ? RTLIL::S1 : RTLIL::S0); + b_signed.append(ports[i].is_signed ? RTLIL::S1 : RTLIL::S0); + product_negated.append(ports[i].do_subtract ? RTLIL::S1 : RTLIL::S0); + a.append(term_a); + b.append(term_b); + nproducts++; + } } - while (max_size) - num_bits++, max_size /= 2; + if (a_signed.empty()) + a_signed = {RTLIL::Sx}; + if (b_signed.empty()) + b_signed = {RTLIL::Sx}; + if (c_signed.empty()) + c_signed = {RTLIL::Sx}; + if (a_widths.empty()) + a_widths = {RTLIL::Sx}; + if (b_widths.empty()) + b_widths = {RTLIL::Sx}; + if (c_widths.empty()) + c_widths = {RTLIL::Sx}; + if (product_negated.empty()) + product_negated = {RTLIL::Sx}; + if (addend_negated.empty()) + addend_negated = {RTLIL::Sx}; - log_assert(num_bits < 16); - config_bits.push_back(num_bits & 1 ? State::S1 : State::S0); - config_bits.push_back(num_bits & 2 ? State::S1 : State::S0); - config_bits.push_back(num_bits & 4 ? State::S1 : State::S0); - config_bits.push_back(num_bits & 8 ? State::S1 : State::S0); - - for (auto &port : ports) - { - if (GetSize(port.in_a) == 0) - continue; - - config_bits.push_back(port.is_signed ? State::S1 : State::S0); - config_bits.push_back(port.do_subtract ? State::S1 : State::S0); - - int size_a = GetSize(port.in_a); - for (int i = 0; i < num_bits; i++) - config_bits.push_back(size_a & (1 << i) ? State::S1 : State::S0); - - int size_b = GetSize(port.in_b); - for (int i = 0; i < num_bits; i++) - config_bits.push_back(size_b & (1 << i) ? State::S1 : State::S0); - - port_a.append(port.in_a); - port_a.append(port.in_b); - } - - cell->setPort(ID::A, port_a); - cell->setPort(ID::B, {}); - cell->setParam(ID::CONFIG, config_bits); - cell->setParam(ID::CONFIG_WIDTH, GetSize(config_bits)); - cell->setParam(ID::A_WIDTH, GetSize(port_a)); - cell->setParam(ID::B_WIDTH, 0); + cell->setParam(ID::NPRODUCTS, nproducts); + cell->setParam(ID::PRODUCT_NEGATED, product_negated); + cell->setParam(ID::NADDENDS, naddends); + cell->setParam(ID::ADDEND_NEGATED, addend_negated); + cell->setParam(ID::A_SIGNED, a_signed); + cell->setParam(ID::B_SIGNED, b_signed); + cell->setParam(ID::C_SIGNED, c_signed); + cell->setParam(ID::A_WIDTHS, a_widths); + cell->setParam(ID::B_WIDTHS, b_widths); + cell->setParam(ID::C_WIDTHS, c_widths); + cell->setPort(ID::A, a); + cell->setPort(ID::B, b); + cell->setPort(ID::C, c); } bool eval(RTLIL::Const &result) const diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index cb0f7da78..3b9a4a8b1 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -540,6 +540,12 @@ void RTLIL::Const::bitvectorize() const { } } +void RTLIL::Const::append(const RTLIL::Const &other) { + bitvectorize(); + bitvectype& bv = get_bits(); + bv.insert(bv.end(), other.begin(), other.end()); +} + RTLIL::State RTLIL::Const::const_iterator::operator*() const { if (auto bv = parent.get_if_bits()) return (*bv)[idx]; @@ -1461,6 +1467,40 @@ namespace { return; } + if (cell->type == ID($macc_v2)) { + if (param(ID::NPRODUCTS) < 0) + error(__LINE__); + if (param(ID::NADDENDS) < 0) + error(__LINE__); + param_bits(ID::PRODUCT_NEGATED, max(param(ID::NPRODUCTS), 1)); + param_bits(ID::ADDEND_NEGATED, max(param(ID::NADDENDS), 1)); + param_bits(ID::A_SIGNED, max(param(ID::NPRODUCTS), 1)); + param_bits(ID::B_SIGNED, max(param(ID::NPRODUCTS), 1)); + param_bits(ID::C_SIGNED, max(param(ID::NADDENDS), 1)); + if (cell->getParam(ID::A_SIGNED) != cell->getParam(ID::B_SIGNED)) + error(__LINE__); + param_bits(ID::A_WIDTHS, max(param(ID::NPRODUCTS) * 16, 1)); + param_bits(ID::B_WIDTHS, max(param(ID::NPRODUCTS) * 16, 1)); + param_bits(ID::C_WIDTHS, max(param(ID::NADDENDS) * 16, 1)); + const Const &a_width = cell->getParam(ID::A_WIDTHS); + const Const &b_width = cell->getParam(ID::B_WIDTHS); + const Const &c_width = cell->getParam(ID::C_WIDTHS); + int a_width_sum = 0, b_width_sum = 0, c_width_sum = 0; + for (int i = 0; i < param(ID::NPRODUCTS); i++) { + a_width_sum += a_width.extract(16 * i, 16).as_int(false); + b_width_sum += b_width.extract(16 * i, 16).as_int(false); + } + for (int i = 0; i < param(ID::NADDENDS); i++) { + c_width_sum += c_width.extract(16 * i, 16).as_int(false); + } + port(ID::A, a_width_sum); + port(ID::B, b_width_sum); + port(ID::C, c_width_sum); + port(ID::Y, param(ID::Y_WIDTH)); + check_expected(); + return; + } + if (cell->type == ID($logic_not)) { param_bool(ID::A_SIGNED); port(ID::A, param(ID::A_WIDTH)); @@ -4093,6 +4133,11 @@ void RTLIL::Cell::fixup_parameters(bool set_a_signed, bool set_b_signed) return; } + if (type == ID($macc_v2)) { + parameters[ID::Y_WIDTH] = GetSize(connections_[ID::Y]); + return; + } + bool signedness_ab = !type.in(ID($slice), ID($concat), ID($macc)); if (connections_.count(ID::A)) { diff --git a/kernel/rtlil.h b/kernel/rtlil.h index f9cacd151..f1bd96be7 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -738,6 +738,8 @@ public: bool empty() const; void bitvectorize() const; + void append(const RTLIL::Const &other); + class const_iterator { private: const Const& parent; @@ -1673,6 +1675,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/satgen.cc b/kernel/satgen.cc index dd15b51f3..9e5fa9111 100644 --- a/kernel/satgen.cc +++ b/kernel/satgen.cc @@ -740,7 +740,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) return true; } - if (cell->type == ID($macc)) + if (cell->type.in(ID($macc), ID($macc_v2))) { std::vector a = importDefSigSpec(cell->getPort(ID::A), timestep); std::vector y = importDefSigSpec(cell->getPort(ID::Y), timestep); 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..d2063a0c8 100644 --- a/passes/cmds/splitcells.cc +++ b/passes/cmds/splitcells.cc @@ -103,8 +103,7 @@ struct SplitcellsWorker auto slice_signal = [&](SigSpec old_sig) -> SigSpec { SigSpec new_sig; - for (int i = 0; i < GetSize(old_sig); i += GetSize(outsig)) { - int offset = i+slice_lsb; + for (int offset = slice_lsb; offset < GetSize(old_sig); offset += GetSize(outsig)) { int length = std::min(GetSize(old_sig)-offset, slice_msb-slice_lsb+1); new_sig.append(old_sig.extract(offset, length)); } @@ -134,7 +133,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/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..57a52969b 100644 --- a/passes/opt/share.cc +++ b/passes/opt/share.cc @@ -23,6 +23,7 @@ #include "kernel/modtools.h" #include "kernel/utils.h" #include "kernel/macc.h" +#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -33,6 +34,7 @@ typedef std::pair ssc_pair_t; struct ShareWorkerConfig { int limit; + size_t pattern_limit; bool opt_force; bool opt_aggressive; bool opt_fast; @@ -87,7 +89,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()); @@ -853,6 +855,23 @@ struct ShareWorker optimize_activation_patterns(patterns); } + template + bool insert_capped(pool& cache, Iterator begin_pattern, Iterator end_pattern) + { + if (cache.size() + std::distance(begin_pattern, end_pattern) > config.pattern_limit) { + cache.clear(); + cache.insert(ssc_pair_t()); + return false; + } else { + cache.insert(begin_pattern, end_pattern); + } + return true; + } + bool insert_capped(pool& cache, ssc_pair_t pattern) + { + return insert_capped(cache, &pattern, &pattern + 1); + } + const pool &find_cell_activation_patterns(RTLIL::Cell *cell, const char *indent) { if (recursion_state.count(cell)) { @@ -909,20 +928,29 @@ struct ShareWorker for (int i = 0; i < GetSize(sig_s); i++) p.first.append(sig_s[i]), p.second.bits().push_back(RTLIL::State::S0); if (sort_check_activation_pattern(p)) - activation_patterns_cache[cell].insert(p); + if (!insert_capped(activation_patterns_cache[cell], p)) { + recursion_state.erase(cell); + return activation_patterns_cache[cell]; + } } for (int idx : used_in_b_parts) for (auto p : c_patterns) { p.first.append(sig_s[idx]), p.second.bits().push_back(RTLIL::State::S1); if (sort_check_activation_pattern(p)) - activation_patterns_cache[cell].insert(p); + if (!insert_capped(activation_patterns_cache[cell], p)) { + recursion_state.erase(cell); + return activation_patterns_cache[cell]; + } } } for (auto c : driven_cells) { const pool &c_patterns = find_cell_activation_patterns(c, indent); - activation_patterns_cache[cell].insert(c_patterns.begin(), c_patterns.end()); + if (!insert_capped(activation_patterns_cache[cell], c_patterns.begin(), c_patterns.end())) { + recursion_state.erase(cell); + return activation_patterns_cache[cell]; + } } log_assert(recursion_state.count(cell) != 0); @@ -1438,12 +1466,18 @@ struct SharePass : public Pass { log(" -limit N\n"); log(" Only perform the first N merges, then stop. This is useful for debugging.\n"); log("\n"); + log(" -pattern-limit N\n"); + log(" Only analyze up to N activation patterns per cell, otherwise assume active.\n"); + log(" N is 1000 by default. Higher values may merge more resources at the cost of\n"); + log(" more runtime and memory consumption.\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { ShareWorkerConfig config; config.limit = -1; + config.pattern_limit = design->scratchpad_get_int("share.pattern_limit", 1000); config.opt_force = false; config.opt_aggressive = false; config.opt_fast = false; @@ -1508,6 +1542,10 @@ struct SharePass : public Pass { config.limit = atoi(args[++argidx].c_str()); continue; } + if (args[argidx] == "-pattern-limit" && argidx+1 < args.size()) { + config.pattern_limit = atoi(args[++argidx].c_str()); + continue; + } break; } extra_args(args, argidx, design); 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/booth.cc b/passes/techmap/booth.cc index dce7da486..cd9012e6c 100644 --- a/passes/techmap/booth.cc +++ b/passes/techmap/booth.cc @@ -218,7 +218,7 @@ struct BoothPassWorker { log_assert(cell->getParam(ID::A_SIGNED).as_bool() == cell->getParam(ID::B_SIGNED).as_bool()); is_signed = cell->getParam(ID::A_SIGNED).as_bool(); - } else if (cell->type == ID($macc)) { + } else if (cell->type.in(ID($macc), ID($macc_v2))) { Macc macc; macc.from_cell(cell); diff --git a/passes/techmap/maccmap.cc b/passes/techmap/maccmap.cc index 911d66cfa..3dde92438 100644 --- a/passes/techmap/maccmap.cc +++ b/passes/techmap/maccmap.cc @@ -403,7 +403,7 @@ struct MaccmapPass : public Pass { for (auto mod : design->selected_modules()) for (auto cell : mod->selected_cells()) - if (cell->type == ID($macc)) { + if (cell->type.in(ID($macc), ID($macc_v2))) { log("Mapping %s.%s (%s).\n", log_id(mod), log_id(cell), log_id(cell->type)); maccmap(mod, cell, unmap_mode); mod->remove(cell); 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/passes/techmap/techmap.cc b/passes/techmap/techmap.cc index b77bab69e..95c733f62 100644 --- a/passes/techmap/techmap.cc +++ b/passes/techmap/techmap.cc @@ -554,8 +554,8 @@ struct TechmapWorker if (extmapper_name == "maccmap") { log("Creating %s with maccmap.\n", log_id(extmapper_module)); - if (extmapper_cell->type != ID($macc)) - log_error("The maccmap mapper can only map $macc (not %s) cells!\n", log_id(extmapper_cell->type)); + if (!extmapper_cell->type.in(ID($macc), ID($macc_v2))) + log_error("The maccmap mapper can only map $macc/$macc_v2 (not %s) cells!\n", log_id(extmapper_cell->type)); maccmap(extmapper_module, extmapper_cell); extmapper_module->remove(extmapper_cell); } @@ -600,8 +600,8 @@ struct TechmapWorker } if (extmapper_name == "maccmap") { - if (cell->type != ID($macc)) - log_error("The maccmap mapper can only map $macc (not %s) cells!\n", log_id(cell->type)); + if (!cell->type.in(ID($macc), ID($macc_v2))) + log_error("The maccmap mapper can only map $macc/$macc_v2 (not %s) cells!\n", log_id(cell->type)); maccmap(module, cell); } 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/simlib.v b/techlibs/common/simlib.v index df9e1feb1..028e4e5a9 100644 --- a/techlibs/common/simlib.v +++ b/techlibs/common/simlib.v @@ -1207,6 +1207,120 @@ end endmodule +// -------------------------------------------------------- +// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| +//- +//- $macc_v2 (A, B, C, Y) +//* group arith +//- +//- Multiply and add. +//- This cell represents a generic fused multiply-add operation, it supersedes the +//- earlier $macc cell. +//- +module \$macc_v2 (A, B, C, Y); + +parameter NPRODUCTS = 0; +parameter NADDENDS = 0; +parameter A_WIDTHS = 16'h0000; +parameter B_WIDTHS = 16'h0000; +parameter C_WIDTHS = 16'h0000; +parameter Y_WIDTH = 0; + +parameter PRODUCT_NEGATED = 1'bx; +parameter ADDEND_NEGATED = 1'bx; +parameter A_SIGNED = 1'bx; +parameter B_SIGNED = 1'bx; +parameter C_SIGNED = 1'bx; + +function integer sum_widths1; + input [(16*NPRODUCTS)-1:0] widths; + integer i; + begin + sum_widths1 = 0; + for (i = 0; i < NPRODUCTS; i++) begin + sum_widths1 = sum_widths1 + widths[16*i+:16]; + end + end +endfunction + +function integer sum_widths2; + input [(16*NADDENDS)-1:0] widths; + integer i; + begin + sum_widths2 = 0; + for (i = 0; i < NADDENDS; i++) begin + sum_widths2 = sum_widths2 + widths[16*i+:16]; + end + end +endfunction + +input [sum_widths1(A_WIDTHS)-1:0] A; // concatenation of LHS factors +input [sum_widths1(B_WIDTHS)-1:0] B; // concatenation of RHS factors +input [sum_widths2(C_WIDTHS)-1:0] C; // concatenation of summands +output reg [Y_WIDTH-1:0] Y; // output sum + +integer i, j, ai, bi, ci, aw, bw, cw; +reg [Y_WIDTH-1:0] product; +reg [Y_WIDTH-1:0] addend, oper_a, oper_b; + +always @* begin + Y = 0; + ai = 0; + bi = 0; + for (i = 0; i < NPRODUCTS; i = i+1) + begin + aw = A_WIDTHS[16*i+:16]; + bw = B_WIDTHS[16*i+:16]; + + oper_a = 0; + oper_b = 0; + for (j = 0; j < Y_WIDTH && j < aw; j = j + 1) + oper_a[j] = A[ai + j]; + for (j = 0; j < Y_WIDTH && j < bw; j = j + 1) + oper_b[j] = B[bi + j]; + // A_SIGNED[i] == B_SIGNED[i] as RTLIL invariant + if (A_SIGNED[i] && B_SIGNED[i]) begin + for (j = aw; j > 0 && j < Y_WIDTH; j = j + 1) + oper_a[j] = oper_a[j - 1]; + for (j = bw; j > 0 && j < Y_WIDTH; j = j + 1) + oper_b[j] = oper_b[j - 1]; + end + + product = oper_a * oper_b; + + if (PRODUCT_NEGATED[i]) + Y = Y - product; + else + Y = Y + product; + + ai = ai + aw; + bi = bi + bw; + end + + ci = 0; + for (i = 0; i < NADDENDS; i = i+1) + begin + cw = C_WIDTHS[16*i+:16]; + + addend = 0; + for (j = 0; j < Y_WIDTH && j < cw; j = j + 1) + addend[j] = C[ci + j]; + if (C_SIGNED[i]) begin + for (j = cw; j > 0 && j < Y_WIDTH; j = j + 1) + addend[j] = addend[j - 1]; + end + + if (ADDEND_NEGATED[i]) + Y = Y - addend; + else + Y = Y + addend; + + ci = ci + cw; + end +end + +endmodule + // -------------------------------------------------------- //* ver 2 //* title Divider diff --git a/techlibs/common/techmap.v b/techlibs/common/techmap.v index 119296147..fdf11904b 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 @@ -290,7 +290,7 @@ module _90_alu (A, B, CI, BI, X, Y, CO); endmodule (* techmap_maccmap *) -(* techmap_celltype = "$macc" *) +(* techmap_celltype = "$macc $macc_v2" *) module _90_macc; endmodule diff --git a/techlibs/gowin/cells_sim.v b/techlibs/gowin/cells_sim.v index 76635355d..58a2f52e6 100644 --- a/techlibs/gowin/cells_sim.v +++ b/techlibs/gowin/cells_sim.v @@ -1005,7 +1005,7 @@ always @* begin C = I0; end MULT: begin - S = I0 & I1; + S = (I0 & I1) ^ I3; C = I0 & I1; end endcase 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/alumacc/basic.ys b/tests/alumacc/basic.ys new file mode 100644 index 000000000..cdd20c472 --- /dev/null +++ b/tests/alumacc/basic.ys @@ -0,0 +1,61 @@ +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 +# ----------------------------------------------------------------------------- diff --git a/tests/various/bug4909.ys b/tests/various/bug4909.ys new file mode 100644 index 000000000..bf8cfb45b --- /dev/null +++ b/tests/various/bug4909.ys @@ -0,0 +1,44 @@ +read_rtlil << EOF +autoidx 20 +attribute \src "3510.v:2.1-26.10" +attribute \cells_not_processed 1 +attribute \tamara_triplicate 1 +module \top + attribute \src "3510.v:14.3-17.8" + wire width 4 $0\reg5[3:0] + attribute $bugpoint 1 + wire width 4 $auto$bugpoint.cc:258:simplify_something$12 + wire $delete_wire$14 + attribute \src "3510.v:13.19-13.59" + wire width 4 $xnor$3510.v:13$1_Y + attribute \src "3510.v:11.23-11.27" + wire width 4 \reg5 + attribute \src "3510.v:8.24-8.29" + wire width 3 \wire4 + attribute \src "3510.v:3.33-3.34" + wire width 12 output 1 \y + attribute \src "3510.v:13.19-13.59" + cell $xnor $xnor$3510.v:13$1 + parameter \A_SIGNED 0 + parameter \A_WIDTH 3 + parameter \B_SIGNED 0 + parameter \B_WIDTH 4 + parameter \Y_WIDTH 4 + connect \A 3'x + connect \B $auto$bugpoint.cc:258:simplify_something$12 + connect \Y $xnor$3510.v:13$1_Y + end + attribute \src "3510.v:14.3-17.8" + process $proc$3510.v:14$2 + assign $0\reg5[3:0] { \wire4 [2] \wire4 } + sync posedge $delete_wire$14 + update \reg5 $0\reg5[3:0] + end + connect \y [4:0] { \reg5 1'0 } + connect \wire4 $xnor$3510.v:13$1_Y [2:0] +end +EOF + +prep +splitcells +