3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-05-08 00:05:48 +00:00

Merge branch 'YosysHQ:main' into pyosys_copy_abc

This commit is contained in:
Akash Levy 2025-03-17 16:50:14 -07:00 committed by GitHub
commit 0abe8bfee8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
94 changed files with 2967 additions and 270 deletions

2
.gitignore vendored
View file

@ -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

View file

@ -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

73
CONTRIBUTING.md Normal file
View file

@ -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.

View file

@ -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<version>, other boost_python<version>, 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__

2
abc

@ -1 +1 @@
Subproject commit cac8f99eaa220a5e3db5caeb87cef0a975c953a2
Subproject commit f2d68d590fa6f8fc32295a2edd79afc0d14a1414

View file

@ -53,6 +53,8 @@ struct XAigerWriter
dict<SigBit, float> arrival_times;
vector<pair<int, int>> aig_gates;
vector<SigBit> bit2aig_stack;
int next_loop_check = 1024;
vector<int> 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);

View file

@ -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);

View file

@ -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");

View file

@ -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

View file

@ -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*
# ========================================================

View file

@ -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<std::string> 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

View file

@ -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}

View file

@ -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:

View file

@ -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
------------

View file

@ -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<ReturnType>`` or ``Functional::DefaultVisitor<ReturnType>``.
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<ReturnType>`` or
``Functional::DefaultVisitor<ReturnType>``. 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<Id>`` 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<Id>`` 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<int>``;
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<int> {
: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<typename Fn> 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 ``<struct_name>-<field_name>`` 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<typename Fn> 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

View file

@ -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<std::string> &argtypes, const std::vector<AstNode*> &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> value_store(args.size() + 1);
std::vector<ffi_type *> types(args.size() + 1);
std::vector<void *> 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);

View file

@ -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'));

View file

@ -4298,7 +4298,7 @@ struct ReadPass : public Pass {
log("\n");
log(" read {-f|-F} <command-file>\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;
}

View file

@ -26,6 +26,10 @@
*
*/
#if !defined(__wasm)
#include <filesystem>
#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<std::string> 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<std::string> 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

View file

@ -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

View file

@ -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);
}

View file

@ -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);

View file

@ -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)

View file

@ -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('"');

View file

@ -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<RTLIL::State> 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

View file

@ -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)) {

View file

@ -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<unsigned int, RTLIL::Wire*> *get_all_wires(void);
#endif

View file

@ -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<int> a = importDefSigSpec(cell->getPort(ID::A), timestep);
std::vector<int> y = importDefSigSpec(cell->getPort(ID::Y), timestep);

View file

@ -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<std::string> args, RTLIL::Design *) override {

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

491
passes/cmds/abstract.cc Normal file
View file

@ -0,0 +1,491 @@
#include "kernel/yosys.h"
#include "kernel/celltypes.h"
#include "kernel/ff.h"
#include "kernel/ffinit.h"
#include <variant>
#include <charconv>
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 '<first>:<last>' or '<single>'", 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<int, int> 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<int> 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<Wire*, Cell*>;
dict<SigBit, std::vector<SelReason>> gather_selected_reps(Module* mod, const std::vector<Slice> &slices, SigMap& sigmap) {
dict<SigBit, std::vector<SelReason>> 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<SelReason>& reasons) {
for (std::variant<Wire*, Cell*> reason : reasons) {
if (Cell** cell_reason = std::get_if<Cell*>(&reason))
log_debug("\tcell %s\n", (*cell_reason)->name.c_str());
else if (Wire** wire_reason = std::get_if<Wire*>(&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<Slice> &slices) {
CellTypes ct;
ct.setup_internals_ff();
SigMap sigmap(mod);
dict<SigBit, std::vector<SelReason>> selected_reps = gather_selected_reps(mod, slices, sigmap);
unsigned int changed = 0;
std::vector<FfData> 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<int> 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<int> 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<int> 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<Slice> &slices) {
SigMap sigmap(mod);
dict<SigBit, std::vector<SelReason>> selected_reps = gather_selected_reps(mod, slices, sigmap);
unsigned int changed = 0;
std::vector<Cell*> cells_snapshot = mod->cells();
for (auto cell : cells_snapshot) {
for (auto conn : cell->connections())
if (cell->output(conn.first)) {
std::set<int> 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<Wire*> wires_snapshot = mod->wires();
for (auto wire : wires_snapshot)
if (wire->port_input) {
std::set<int> 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<Slice> &slices) {
unsigned int changed = 0;
FfInitVals initvals;
SigMap sigmap(mod);
dict<SigBit, std::vector<SelReason>> 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 <wire-name>\n");
log(" -enablen <wire-name>\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 <lhs>:<rhs>\n");
log(" -slice <index>\n");
log(" -rtlilslice <lhs>:<rhs>\n");
log(" -rtlilslice <index>\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<std::string> 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<Slice> 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

View file

@ -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};

1
passes/opt/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/peepopt*_pm.h

View file

@ -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

View file

@ -601,7 +601,7 @@ void rmunused_module(RTLIL::Module *module, bool purge_mode, bool verbose, bool
std::vector<RTLIL::Cell*> 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);

View file

@ -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());

View file

@ -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") { }

View file

@ -23,6 +23,7 @@
#include "kernel/modtools.h"
#include "kernel/utils.h"
#include "kernel/macc.h"
#include <iterator>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
@ -33,6 +34,7 @@ typedef std::pair<RTLIL::SigSpec, RTLIL::Const> 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<RTLIL::SigBit> 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<typename Iterator>
bool insert_capped(pool<ssc_pair_t>& 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<ssc_pair_t>& cache, ssc_pair_t pattern)
{
return insert_capped(cache, &pattern, &pattern + 1);
}
const pool<ssc_pair_t> &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<ssc_pair_t> &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<std::string> 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);

View file

@ -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))

View file

@ -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:

View file

@ -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"

View file

@ -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);

View file

@ -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);

View file

@ -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<IdString, void(*)(RTLIL::Module*, RTLIL::Cell*)>
{
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;

View file

@ -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);
}

1
techlibs/.gitignore vendored
View file

@ -1 +1,2 @@
blackbox.v
*_pm.h

View file

@ -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

View file

@ -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

View file

@ -1005,7 +1005,7 @@ always @* begin
C = I0;
end
MULT: begin
S = I0 & I1;
S = (I0 & I1) ^ I3;
C = I0 & I1;
end
endcase

View file

@ -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))

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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),

View file

@ -2,6 +2,7 @@
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
* Copyright (C) 2024 Richard Herveille <richard.herveille@roalogic.com>
*
* 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

View file

@ -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

View file

@ -2,6 +2,7 @@
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
* Copyright (C) 2024 Richard Herveille <richard.herveille@roalogic.com>
*
* 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>" : 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");
}

View file

@ -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))

View file

@ -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)
{

View file

@ -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))
$(eval $(call add_share_file,share/quicklogic/qlf_k6n10f,techlibs/quicklogic/qlf_k6n10f/sram1024x18_mem.v))

View file

@ -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<std::string>, 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<RTLIL::Cell *> input_ffs;
dict<RTLIL::Wire *, std::vector<Cell*>> output_ffs;
dict<SigBit, pool<SigBit>> 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<ModWalker::PortBit> 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

View file

@ -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");

View file

@ -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))

View file

@ -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));

View file

@ -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)
{

61
tests/alumacc/basic.ys Normal file
View file

@ -0,0 +1,61 @@
read_verilog <<EOF
module gate(input signed [2:0] a1, input signed [2:0] b1,
input [1:0] a2, input [3:0] b2, input c, input d, output signed [3:0] y);
wire signed [3:0] ab1;
assign ab1 = a1 * b1;
assign y = ab1 + a2*b2 + c + d + 1;
endmodule
EOF
prep
equiv_opt -assert alumacc
design -load postopt
stat
design -save save
equiv_opt -assert maccmap
design -load save
equiv_opt -assert maccmap -unmap
design -reset
read_verilog <<EOF
module gate(input signed [2:0] a1, input signed [1:0] b1, output signed [3:0] y);
assign y = a1 * b1;
endmodule
EOF
prep
equiv_opt -assert alumacc
design -load postopt
stat
design -save save
equiv_opt -assert maccmap
design -load save
equiv_opt -assert maccmap -unmap
design -reset
read_verilog <<EOF
module gate(input [2:0] a, input [1:0] b, output [3:0] y);
assign y = a * b;
endmodule
EOF
prep
equiv_opt -assert alumacc
design -load postopt
stat
design -save save
equiv_opt -assert maccmap
design -load save
equiv_opt -assert maccmap -unmap
design -reset
read_verilog <<EOF
module gate(input [2:0] a, input [1:0] b, input [1:0] c, output [3:0] y);
assign y = a * b - c;
endmodule
EOF
prep
equiv_opt -assert alumacc
design -load postopt
stat
design -save save
equiv_opt -assert maccmap
design -load save
equiv_opt -assert maccmap -unmap

View file

@ -0,0 +1,19 @@
read_verilog <<EOF
module gate(input signed [2:0] a1, input signed [2:0] b1,
input [1:0] a2, input [3:0] b2, input c, input d, output signed [3:0] y);
wire signed [3:0] ab1;
assign ab1 = a1 * b1;
assign y = ab1 + a2*b2 + c + d + 1;
endmodule
EOF
prep
design -save gold
alumacc
opt_clean
select -assert-count 1 t:$macc_v2
maccmap -unmap
design -copy-from gold -as gold gate
equiv_make gold gate equiv
equiv_induct equiv
equiv_status -assert equiv

View file

@ -2,7 +2,7 @@ read_verilog ../../common/counter.v
hierarchy -top top
proc
flatten
equiv_opt -assert -multiclock -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f # equivalency check
equiv_opt -assert -multiclock -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f -noioff # equivalency check
design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design)
cd top # Constrain all select calls below inside the top module
select -assert-count 4 t:$lut

View file

@ -5,7 +5,7 @@ design -save read
hierarchy -top my_dff
proc
equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f # equivalency check
equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f -noioff # equivalency check
design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design)
cd my_dff # Constrain all select calls below inside the top module
select -assert-count 1 t:sdffsre
@ -14,7 +14,7 @@ select -assert-none t:sdffsre %% t:* %D
design -load read
hierarchy -top my_dffe
proc
equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f # equivalency check
equiv_opt -async2sync -assert -map +/quicklogic/qlf_k6n10f/cells_sim.v -map +/quicklogic/common/cells_sim.v synth_quicklogic -family qlf_k6n10f -noioff # equivalency check
design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design)
cd my_dffe # Constrain all select calls below inside the top module
select -assert-count 1 t:sdffsre

View file

@ -0,0 +1,209 @@
# test: acceptable for output IOFF promotion
read_verilog <<EOF
module top (input clk, input a, output reg o);
always @(posedge clk) begin
o <= ~a;
end
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 1 t:dff
design -reset
# test: acceptable for output IOFF promotion
read_verilog <<EOF
module top (input clk, input [3:0] a, output reg [3:0] o);
always @(posedge clk) begin
o <= ~a;
end
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 4 t:dff
design -reset
# test: acceptable for output IOFF promotion; duplicate output FF
read_verilog <<EOF
module top (input clk, input [3:0] a, output [3:0] o, output [3:0] p);
reg [3:0] r;
always @(posedge clk) begin
r <= ~a;
end
assign o = r;
assign p = r;
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 8 t:dff
design -reset
# test: acceptable for input IOFF promotion
read_verilog <<EOF
module top (input clk, input a, output o);
reg r;
always @(posedge clk) begin
r <= a;
end
assign o = ~r;
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 1 t:dff
design -reset
# test: acceptable for input IOFF promotion
read_verilog <<EOF
module top (input clk, input [3:0] a, output [3:0] o);
reg [3:0] r;
always @(posedge clk) begin
r <= a;
end
assign o = ~r;
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 4 t:dff
design -reset
# test: acceptable for either IOFF promotion
read_verilog <<EOF
module top (input clk, input a, output reg o);
always @(posedge clk) begin
o <= a;
end
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 1 t:dff
design -reset
# test: not acceptable for output IOFF promotion: output signal is used
read_verilog <<EOF
module top (input clk, input a, output reg o);
always @(posedge clk) begin
o <= ~a | o;
end
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 0 t:dff
design -reset
# test: not acceptable for output IOFF promotion: output signal is used
read_verilog <<EOF
module top (input clk, input [3:0] a, output reg [3:0] o);
always @(posedge clk) begin
o <= ~a | o;
end
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 0 t:dff
design -reset
# test: not acceptable for input IOFF promotion: input signal is used
read_verilog <<EOF
module top (input clk, input a, output o, p);
reg r;
always @(posedge clk) begin
r <= a;
end
assign o = ~r;
assign p = ~a;
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 0 t:dff
design -reset
# test: not acceptable for input IOFF promotion: input signal is used
read_verilog <<EOF
module top (input clk, input [3:0] a, output [3:0] o, output [3:0] p);
reg [3:0] r;
always @(posedge clk) begin
r <= a;
end
assign o = ~r;
assign p = ~a;
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 0 t:dff
design -reset
# test: not acceptable for IOFF promotion: FF has reset
read_verilog <<EOF
module top (input clk, input rst, input a, output reg o);
always @(posedge clk) begin
if (rst)
o <= 1'b0;
else
o <= a;
end
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 0 t:dff
design -reset
# test: not acceptable for IOFF promotion: FF has reset
read_verilog <<EOF
module top (input clk, input rst, input [3:0] a, output reg [3:0] o);
always @(posedge clk) begin
if (rst)
o <= 4'b0;
else
o <= a;
end
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 0 t:dff
design -reset
# test: not acceptable for IOFF promotion: FF has enable
read_verilog <<EOF
module top (input clk, input en, input a, output reg o);
always @(posedge clk) begin
if (en)
o <= a;
end
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 0 t:dff
design -reset
# test: not acceptable for IOFF promotion: FF has enable
read_verilog <<EOF
module top (input clk, input en, input [3:0] a, output reg [3:0] o);
always @(posedge clk) begin
if (en)
o <= a;
end
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 0 t:dff
design -reset
# test: duplicate registers driving multiple output ports
read_verilog <<EOF
module top (
input clk,
input en,
input [3:0] a,
output reg [3:0] o_1,
output wire [3:0] o_2
);
always @(posedge clk) begin
o_1[1:0] <= ~a[1:0];
if (en)
o_1[2] <= a[2];
end
always @(*) o_1[3] = a[3];
assign o_2 = o_1;
endmodule
EOF
synth_quicklogic -family qlf_k6n10f -top top
select -assert-count 4 t:dff

View file

@ -0,0 +1,9 @@
read_verilog << EOF
module uut_00034(b, y);
input signed [30:0] b;
output [11:0] y = b >> ~31'b0; // shift by INT_MAX
endmodule
EOF
# This should succeed, even with UBSAN halt_on_error
opt_expr

13
tests/techmap/buf.ys Normal file
View file

@ -0,0 +1,13 @@
read_verilog -icells <<EOF
module top(input wire [2:0] a, output wire [2:0] y);
\$buf #(.WIDTH(3)) b(.A(a), .Y(y));
endmodule
EOF
design -save save
opt_clean
select -assert-none t:$buf
design -load save
techmap
select -assert-none t:$buf

View file

@ -5,7 +5,22 @@ YOSYS_NAMESPACE_BEGIN
namespace RTLIL {
class KernelRtlilTest : public testing::Test {};
struct SafePrintToStringParamName {
template <class ParamType>
std::string operator()(const testing::TestParamInfo<ParamType>& 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<std::tuple<bool, int, int>>
{};
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<Module> mod = std::make_unique<Module>();
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

View file

@ -0,0 +1,121 @@
design -reset
read_verilog <<EOT
module foo (CLK, Q, QQQ);
input CLK;
output reg QQQ;
output reg Q = 1'b1;
assign QQQ = Q;
always @(posedge CLK)
Q <= ~Q;
endmodule
EOT
proc
opt_expr
opt_dff
select -assert-count 1 w:Q a:init %i
abstract -init w:QQQ
check -assert
select -assert-count 0 w:Q a:init %i
design -reset
read_verilog <<EOT
module foo (CLK, Q, QQQ);
input CLK;
output reg QQQ;
output reg [1:0] Q = 1'b1;
assign QQQ = Q;
always @(posedge CLK)
Q <= ~Q;
endmodule
EOT
proc
opt_expr
opt_dff
select -assert-count 1 w:Q a:init=2'b01 %i
abstract -init w:QQQ
check -assert
select -assert-count 1 w:Q a:init=2'b0x %i
design -reset
read_verilog <<EOT
module foo (CLK, Q);
input CLK;
// downto
output reg [1:0] Q = 1'b1;
always @(posedge CLK)
Q <= ~Q;
endmodule
EOT
proc
opt_expr
opt_dff
design -save basic
select -assert-count 1 w:Q a:init=2'b01 %i
abstract -init -slice 0 w:Q
check -assert
select -assert-count 1 w:Q a:init=2'b0x %i
design -load basic
select -assert-count 1 w:Q a:init=2'b01 %i
abstract -init -slice 0:1 w:Q
check -assert
select -assert-count 0 w:Q a:init %i
design -reset
read_verilog <<EOT
module foo (CLK, Q);
input CLK;
// downto
output reg [1:0] Q = 1'b1;
always @(posedge CLK)
Q <= ~Q;
endmodule
EOT
proc
opt_expr
opt_dff
select -assert-count 1 w:Q a:init=2'b01 %i
abstract -init -rtlilslice 0 w:Q
check -assert
select -assert-count 1 w:Q a:init=2'b0x %i
design -reset
read_verilog <<EOT
module foo (CLK, Q);
input CLK;
// upto
output reg [0:1] Q = 1'b1;
always @(posedge CLK)
Q <= ~Q;
endmodule
EOT
proc
opt_expr
opt_dff
select -assert-count 1 w:Q a:init=2'b01 %i
abstract -init -slice 0 w:Q
check -assert
select -assert-count 1 w:Q a:init=2'bx1 %i
design -reset
read_verilog <<EOT
module foo (CLK, Q);
input CLK;
// upto
output reg [0:1] Q = 1'b1;
always @(posedge CLK)
Q <= ~Q;
endmodule
EOT
proc
opt_expr
opt_dff
select -assert-count 1 w:Q a:init=2'b01 %i
abstract -init -rtlilslice 0 w:Q
check -assert
select -assert-count 1 w:Q a:init=2'b0x %i

View file

@ -0,0 +1,178 @@
read_verilog <<EOT
module half_clock (CLK, Q, magic);
input CLK;
output reg Q;
input magic;
always @(posedge CLK)
Q <= ~Q;
endmodule
EOT
proc
design -save half_clock
# -----------------------------------------------------------------------------
# An empty selection causes no change
select -none
logger -expect log "Abstracted 0 stateful cells" 1
abstract -state -enablen magic
logger -check-expected
logger -expect log "Abstracted 0 init bits" 1
abstract -init
logger -check-expected
logger -expect log "Abstracted 0 driver ports" 1
abstract -value -enablen magic
logger -check-expected
select -clear
# -----------------------------------------------------------------------------
design -load half_clock
# Basic -state test
abstract -state -enablen magic
check -assert
# Connections to dff D input port
select -set conn_to_d t:$dff %x:+[D] t:$dff %d
# The D input port is fed with a mux
select -set mux @conn_to_d %ci t:$mux %i
select -assert-count 1 @mux
# The S input port is fed with the magic wire
select -assert-count 1 @mux %x:+[S] w:magic %i
# The A input port is fed with an anyseq
select -assert-count 1 @mux %x:+[A] %ci t:$anyseq %i
# The B input port is fed with the negated Q
select -set not @mux %x:+[B] %ci t:$not %i
select -assert-count 1 @not %x:+[A] o:Q %i
design -load half_clock
# Same thing, inverted polarity
abstract -state -enable magic
check -assert
select -set conn_to_d t:$dff %x:+[D] t:$dff %d
select -set mux @conn_to_d %ci t:$mux %i
select -assert-count 1 @mux
select -assert-count 1 @mux %x:+[S] w:magic %i
# so we get swapped A and B
select -assert-count 1 @mux %x:+[B] %ci t:$anyseq %i
select -set not @mux %x:+[A] %ci t:$not %i
select -assert-count 1 @not %x:+[A] o:Q %i
# -----------------------------------------------------------------------------
design -reset
read_verilog <<EOT
module wide_flop_no_q (CLK, DDD, QQQ, magic);
input CLK;
input [2:0] DDD;
output reg [2:0] QQQ;
input magic;
always @(posedge CLK)
QQQ <= DDD;
endmodule
EOT
proc
opt_expr
opt_dff
dump
abstract -state -enablen magic -slice 0 w:QQQ
check -assert
# Connections to dff D input port
select -set conn_to_d t:$dff %x:+[D] t:$dff %d
# The D input port is partially fed with a mux
select -set mux @conn_to_d %ci t:$mux %i
select -assert-count 1 @mux
# and also the DDD input
select -assert-count 1 @conn_to_d w:DDD %i
# The S input port is fed with the magic wire
select -assert-count 1 @mux %x:+[S] w:magic %i
# The A input port is fed with an anyseq
select -assert-count 1 @mux %x:+[A] %ci t:$anyseq %i
# The B input port is fed with DDD
select -assert-count 1 @mux %x:+[B] %ci w:DDD %i
# -----------------------------------------------------------------------------
# Selecting wire Q connected to bit 0 of QQQ is the same as specifying
# QQQ with the -slice 0 argument
design -reset
read_verilog <<EOT
module wide_flop (CLK, DDD, QQQ, Q, magic);
input CLK;
input [2:0] DDD;
output reg [2:0] QQQ;
output reg Q;
input magic;
always @(posedge CLK)
QQQ <= DDD;
assign Q = QQQ[0];
endmodule
EOT
proc
design -save wide_flop
# Test that abstracting an output slice muxes an input slice
abstract -state -enablen magic w:Q
check -assert
# Same testing as previous case
select -set conn_to_d t:$dff %x:+[D] t:$dff %d
select -set mux @conn_to_d %ci t:$mux %i
select -assert-count 1 @mux
select -assert-count 1 @conn_to_d w:DDD %i
select -assert-count 1 @mux %x:+[S] w:magic %i
select -assert-count 1 @mux %x:+[A] %ci t:$anyseq %i
select -assert-count 1 @mux %x:+[B] %ci w:DDD %i
# -----------------------------------------------------------------------------
design -reset
read_verilog <<EOT
module half_clock_en (CLK, E, Q, magic);
input CLK;
input E;
output reg Q;
input magic;
always @(posedge CLK)
if (E)
Q <= ~Q;
endmodule
EOT
proc
opt_expr
opt_dff
design -save half_clock_en
# Test that abstracting a $dffe unmaps the enable
select -assert-count 1 t:$dffe
abstract -state -enablen magic
check -assert
select -assert-count 0 t:$dffe
select -assert-count 1 t:$dff
# -----------------------------------------------------------------------------
design -reset
read_verilog <<EOT
module top (CLK, E, Q, Q_EN);
input CLK;
input E;
output reg Q;
output reg Q_EN;
half_clock uut (CLK, Q, 1'b0);
half_clock_en uut_en (CLK, E, Q_EN, 1'b0);
endmodule
EOT
proc
design -import half_clock
design -import half_clock_en
hierarchy -check -top top
# Test when the abstraction is disabled (enable is inactive),
# the equivalence is preserved
rename top top_gold
design -save gold
abstract -state -enable magic half_clock half_clock_en
flatten
rename top_gold top_gate
design -save gate
design -load gold
flatten
design -import gate -as top_gate
equiv_make top_gold top_gate equiv
equiv_induct equiv
equiv_status -assert equiv
# The reader may verify that this model checking is functional
# by changing -enable to -enablen in the abstract pass invocation above
# -----------------------------------------------------------------------------

View file

@ -0,0 +1,95 @@
read_verilog <<EOT
module split_output (A, B, Y, magic);
input [1:0] A;
input [1:0] B;
output [1:0] Y;
input magic;
wire W;
assign Y = A + B;
assign W = Y[0]; // <--- look here
endmodule
EOT
proc
design -save split_output
# Basic -value test
abstract -value -enable magic w:W
check -assert
# Connections to $add Y output port
select -set conn_to_y t:$add %x:+[Y] t:$add %d
# The $add Y output port feeds partially into a mux
select -set mux @conn_to_y %ci t:$mux %i
select -assert-count 1 @mux
# and also the Y module output
select -assert-count 1 @conn_to_y %a o:Y %i
# The S input port is fed with the magic wire
select -assert-count 1 @mux %x:+[S] w:magic %i
# The B input port is fed with an anyseq
select -assert-count 1 @mux %x:+[B] %ci t:$anyseq %i
# The Y output port feeds into the Y module output
select -assert-count 1 @mux %x:+[Y] %co o:Y %i
# -----------------------------------------------------------------------------
# Same thing, but we use -slice instead of wire W
design -reset
read_verilog <<EOT
module split_output_no_w (A, B, Y, magic);
input [1:0] A;
input [1:0] B;
output [1:0] Y;
input magic;
assign Y = A + B;
endmodule
EOT
proc
# Same test as the previous case
abstract -value -enable magic -slice 0 w:Y
check -assert
select -set conn_to_y t:$add %x:+[Y] t:$add %d
select -set mux @conn_to_y %ci t:$mux %i
select -assert-count 1 @mux
select -assert-count 1 @conn_to_y %a o:Y %i
select -assert-count 1 @mux %x:+[S] w:magic %i
select -assert-count 1 @mux %x:+[B] %ci t:$anyseq %i
select -assert-count 1 @mux %x:+[Y] %co o:Y %i
# -----------------------------------------------------------------------------
design -reset
read_verilog <<EOT
module split_input (A, B, Y, magic);
input [1:0] A;
input [1:0] B;
output [1:0] Y;
input magic;
wire W;
assign Y = A + B;
assign W = A[0]; // <--- look here
endmodule
EOT
proc
design -save split_input
# The mux goes on an input this time
abstract -value -enable magic w:W
check -assert
# Connections to add A input port
select -set conn_to_a t:$add %x:+[A] t:$add %d
# The B input port is partially fed with a mux
select -set mux @conn_to_a %ci t:$mux %i
select -assert-count 1 @mux
# and also the A input
select -assert-count 1 @conn_to_a %a w:A %i
# The S input port is fed with the magic wire
select -assert-count 1 @mux %x:+[S] w:magic %i
# The A input port is fed with the module input A
select -assert-count 1 @mux %x:+[A] %ci i:A %i
# The B input port is fed with an anyseq
select -assert-count 1 @mux %x:+[B] %ci t:$anyseq %i
# -----------------------------------------------------------------------------
# All wires selected, excluding magic -> 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
# -----------------------------------------------------------------------------

44
tests/various/bug4909.ys Normal file
View file

@ -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