diff --git a/.editorconfig b/.editorconfig index f5444d81a74..572b73bd2ed 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,3 +10,7 @@ insert_final_newline = true indent_style = space indent_size = 2 trim_trailing_whitespace = false + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml index c5a0d14abdf..0c8cd281c2c 100644 --- a/.github/actions/setup-build-env/action.yml +++ b/.github/actions/setup-build-env/action.yml @@ -14,7 +14,8 @@ runs: if: runner.os == 'macOS' shell: bash run: | - HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install bison flex gawk libffi pkg-config bash autoconf llvm + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew update + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install bison flex gawk libffi pkg-config bash autoconf llvm lld - name: Linux runtime environment if: runner.os == 'Linux' diff --git a/.github/workflows/prepare-docs.yml b/.github/workflows/prepare-docs.yml index 9233e6295b4..e0388c7a35a 100644 --- a/.github/workflows/prepare-docs.yml +++ b/.github/workflows/prepare-docs.yml @@ -1,12 +1,32 @@ name: Build docs artifact with Verific -on: push +on: [push, pull_request] jobs: + check_docs_rebuild: + runs-on: ubuntu-latest + outputs: + skip_check: ${{ steps.skip_check.outputs.should_skip }} + docs_export: ${{ steps.docs_var.outputs.docs_export }} + env: + docs_export: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/docs-preview') || startsWith(github.ref, 'refs/tags/') }} + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@v5 + with: + paths_ignore: '["**/README.md"]' + # don't cancel in case we're updating docs + cancel_others: 'false' + # only run on push *or* pull_request, not both + concurrent_skipping: ${{ env.docs_export && 'never' || 'same_content_newer'}} + - id: docs_var + run: echo "docs_export=${{ env.docs_export }}" >> $GITHUB_OUTPUT + prepare-docs: # docs builds are needed for anything on main, any tagged versions, and any tag # or branch starting with docs-preview - if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/docs-preview') || startsWith(github.ref, 'refs/tags/') }} + needs: check_docs_rebuild + if: ${{ needs.check_docs_rebuild.outputs.should_skip != 'true' }} runs-on: [self-hosted, linux, x64, fast] steps: - name: Checkout Yosys @@ -32,7 +52,7 @@ jobs: - name: Prepare docs shell: bash run: - make docs/prep TARGETS= EXTRA_TARGETS= + make docs/prep -j${{ env.procs }} TARGETS= EXTRA_TARGETS= - name: Upload artifact uses: actions/upload-artifact@v4 @@ -44,7 +64,13 @@ jobs: docs/source/_images docs/source/code_examples + - name: Test build docs + shell: bash + run: | + make -C docs html -j${{ env.procs }} TARGETS= EXTRA_TARGETS= + - name: Trigger RTDs build + if: ${{ needs.check_docs_rebuild.outputs.docs_export == 'true' }} uses: dfm/rtds-action@v1.1.0 with: webhook_url: ${{ secrets.RTDS_WEBHOOK_URL }} diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 0eb2c65c51c..e5aed6af3f0 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -100,6 +100,16 @@ jobs: cd iverilog echo "IVERILOG_GIT=$(git rev-parse HEAD)" >> $GITHUB_ENV + - name: Get vcd2fst + shell: bash + run: | + git clone https://github.com/mmicko/libwave.git + mkdir -p ${{ github.workspace }}/.local/ + cd libwave + cmake . -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/.local + make -j$procs + make install + - name: Cache iverilog id: cache-iverilog uses: actions/cache@v4 diff --git a/.github/workflows/test-verific.yml b/.github/workflows/test-verific.yml index 43b68e06dca..98b05e8ddb8 100644 --- a/.github/workflows/test-verific.yml +++ b/.github/workflows/test-verific.yml @@ -39,6 +39,7 @@ jobs: echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 1" >> Makefile.conf echo "ENABLE_CCACHE := 1" >> Makefile.conf + echo "ENABLE_FUNCTIONAL_TESTS := 1" >> Makefile.conf make -j${{ env.procs }} ENABLE_LTO=1 - name: Install Yosys diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 00000000000..d59f8e1ec61 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,136 @@ +name: Build Wheels for PyPI +on: + workflow_dispatch: + +jobs: + build_wheels: + strategy: + fail-fast: false + matrix: + os: [ + { + name: "Ubuntu 22.04", + family: "linux", + runner: "ubuntu-22.04", + archs: "x86_64", + }, + ## Aarch64 is disabled for now: GitHub is committing to EOY + ## for free aarch64 runners for open-source projects and + ## emulation times out: + ## https://github.com/orgs/community/discussions/19197#discussioncomment-10550689 + # { + # name: "Ubuntu 22.04", + # family: "linux", + # runner: "ubuntu-22.04", + # archs: "aarch64", + # }, + { + name: "macOS 13", + family: "macos", + runner: "macos-13", + archs: "x86_64", + }, + { + name: "macOS 14", + family: "macos", + runner: "macos-14", + archs: "arm64", + }, + ## Windows is disabled because of an issue with compiling FFI as + ## under MinGW in the GitHub Actions environment (SHELL variable has + ## whitespace.) + # { + # name: "Windows Server 2019", + # family: "windows", + # runner: "windows-2019", + # archs: "AMD64", + # }, + ] + name: Build Wheels | ${{ matrix.os.name }} | ${{ matrix.os.archs }} + runs-on: ${{ matrix.os.runner }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + - if: ${{ matrix.os.family == 'linux' }} + name: "[Linux] Set up QEMU" + uses: docker/setup-qemu-action@v3 + - uses: actions/setup-python@v5 + - name: Get Boost Source + shell: bash + run: | + mkdir -p boost + curl -L https://github.com/boostorg/boost/releases/download/boost-1.86.0/boost-1.86.0-b2-nodocs.tar.gz | tar --strip-components=1 -xzC boost + - name: Get FFI + shell: bash + run: | + mkdir -p ffi + curl -L https://github.com/libffi/libffi/releases/download/v3.4.6/libffi-3.4.6.tar.gz | tar --strip-components=1 -xzC ffi + ## Software installed by default in GitHub Action Runner VMs: + ## https://github.com/actions/runner-images + - if: ${{ matrix.os.family == 'macos' }} + name: "[macOS] Flex/Bison" + run: | + brew install flex bison + echo "PATH=$(brew --prefix flex)/bin:$PATH" >> $GITHUB_ENV + echo "PATH=$(brew --prefix bison)/bin:$PATH" >> $GITHUB_ENV + - if: ${{ matrix.os.family == 'windows' }} + name: "[Windows] Flex/Bison" + run: | + choco install winflexbison3 + - if: ${{ matrix.os.family == 'macos' && matrix.os.archs == 'arm64' }} + name: "[macOS/arm64] Install Python 3.8 (see: https://cibuildwheel.pypa.io/en/stable/faq/#macos-building-cpython-38-wheels-on-arm64)" + uses: actions/setup-python@v5 + with: + python-version: 3.8 + - name: Build wheels + uses: pypa/cibuildwheel@v2.21.1 + env: + # * APIs not supported by PyPy + # * Musllinux disabled because it increases build time from 48m to ~3h + CIBW_SKIP: > + pp* + *musllinux* + CIBW_ARCHS: ${{ matrix.os.archs }} + CIBW_BUILD_VERBOSITY: "1" + # manylinux2014 (default) does not have a modern enough C++ compiler for Yosys + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28 + CIBW_BEFORE_ALL: bash ./.github/workflows/wheels/cibw_before_all.sh + CIBW_ENVIRONMENT: > + CXXFLAGS=-I./boost/pfx/include + LINKFLAGS=-L./boost/pfx/lib + PKG_CONFIG_PATH=./ffi/pfx/lib/pkgconfig + makeFlags='BOOST_PYTHON_LIB=./boost/pfx/lib/libboost_python*.a' + CIBW_ENVIRONMENT_MACOS: > + CXXFLAGS=-I./boost/pfx/include + LINKFLAGS=-L./boost/pfx/lib + PKG_CONFIG_PATH=./ffi/pfx/lib/pkgconfig + MACOSX_DEPLOYMENT_TARGET=11 + makeFlags='BOOST_PYTHON_LIB=./boost/pfx/lib/libboost_python*.a CONFIG=clang' + CIBW_BEFORE_BUILD: bash ./.github/workflows/wheels/cibw_before_build.sh + CIBW_TEST_COMMAND: python3 -c "from pyosys import libyosys as ys;d=ys.Design();ys.run_pass('help', d)" + - uses: actions/upload-artifact@v4 + with: + name: python-wheels-${{ matrix.os.runner }} + path: ./wheelhouse/*.whl + upload_wheels: + name: Upload Wheels + runs-on: ubuntu-latest + needs: build_wheels + steps: + - uses: actions/download-artifact@v4 + with: + path: "." + pattern: python-wheels-* + merge-multiple: true + - run: | + ls + mkdir -p ./dist + mv *.whl ./dist + - name: Publish + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_TOKEN }} + repository-url: ${{ vars.PYPI_INDEX || 'https://upload.pypi.org/legacy/' }} diff --git a/.github/workflows/wheels/_run_cibw_linux.py b/.github/workflows/wheels/_run_cibw_linux.py new file mode 100644 index 00000000000..894470a5a8d --- /dev/null +++ b/.github/workflows/wheels/_run_cibw_linux.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 Efabless Corporation +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" +This runs the cibuildwheel step from the wheels workflow locally. +""" + +import os +import yaml +import platform +import subprocess + +__dir__ = os.path.dirname(os.path.abspath(__file__)) + + +workflow = yaml.safe_load(open(os.path.join(os.path.dirname(__dir__), "wheels.yml"))) + +env = os.environ.copy() + +steps = workflow["jobs"]["build_wheels"]["steps"] +cibw_step = None +for step in steps: + if (step.get("uses") or "").startswith("pypa/cibuildwheel"): + cibw_step = step + break + +for key, value in cibw_step["env"].items(): + if key.endswith("WIN") or key.endswith("MAC"): + continue + env[key] = value + +env["CIBW_ARCHS"] = os.getenv("CIBW_ARCHS") or platform.machine() +subprocess.check_call(["cibuildwheel"], env=env) diff --git a/.github/workflows/wheels/cibw_before_all.sh b/.github/workflows/wheels/cibw_before_all.sh new file mode 100644 index 00000000000..fbb8dcad830 --- /dev/null +++ b/.github/workflows/wheels/cibw_before_all.sh @@ -0,0 +1,23 @@ +set -e +set -x + +# Build-time dependencies +## Linux Docker Images +if command -v yum &> /dev/null; then + yum install -y flex bison +fi + +if command -v apk &> /dev/null; then + apk add flex bison +fi + +## macOS/Windows -- installed in GitHub Action itself, not container + +# Build Static FFI (platform-dependent but not Python version dependent) +cd ffi +## Ultimate libyosys.so will be shared, so we need fPIC for the static libraries +CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=$PWD/pfx +## Without this, SHELL has a space in its path which breaks the makefile +make install -j$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu) +## Forces static library to be used in all situations +sed -i.bak 's@-L${toolexeclibdir} -lffi@${toolexeclibdir}/libffi.a@' ./pfx/lib/pkgconfig/libffi.pc diff --git a/.github/workflows/wheels/cibw_before_build.sh b/.github/workflows/wheels/cibw_before_build.sh new file mode 100644 index 00000000000..018b0e0df96 --- /dev/null +++ b/.github/workflows/wheels/cibw_before_build.sh @@ -0,0 +1,34 @@ +set -e +set -x + +# Don't use objects from previous compiles on Windows/macOS +make clean + +# DEBUG: show python3 and python3-config outputs +if [ "$(uname)" != "Linux" ]; then + # https://github.com/pypa/cibuildwheel/issues/2021 + ln -s $(dirname $(readlink -f $(which python3)))/python3-config $(dirname $(which python3))/python3-config +fi +python3 --version +python3-config --includes + +# Build boost +cd ./boost +## Delete the artefacts from previous builds (if any) +rm -rf ./pfx +## Bootstrap bjam +./bootstrap.sh --prefix=./pfx +## Build Boost against current version of Python, only for +## static linkage (Boost is statically linked because system boost packages +## wildly vary in versions, including the libboost_python3 version) +./b2\ + -j$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu)\ + --prefix=./pfx\ + --with-filesystem\ + --with-system\ + --with-python\ + cxxflags="$(python3-config --includes) -std=c++17 -fPIC"\ + cflags="$(python3-config --includes) -fPIC"\ + link=static\ + variant=release\ + install diff --git a/.gitignore b/.gitignore index e82343e8947..5c3d8f4e963 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,10 @@ __pycache__ /tests/unit/bintest/ /tests/unit/objtest/ /tests/ystests -/result \ No newline at end of file +/result +/dist +/*.egg-info +/build +/venv +/boost +/ffi diff --git a/CHANGELOG b/CHANGELOG index 03bf5ac57c3..4106885fadf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,9 +2,32 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.45 .. Yosys 0.46-dev +Yosys 0.46 .. Yosys 0.47-dev -------------------------- +Yosys 0.45 .. Yosys 0.46 +-------------------------- + * Various + - Added new "functional backend" infrastructure with three example + backends (C++, SMTLIB and Rosette). + - Added new coarse-grain buffer cell type "$buf" to RTLIL. + - Added "-y" command line option to execute a Python script with + libyosys available as a built-in module. + - Added support for casting to type in Verilog frontend. + + * New commands and options + - Added "clockgate" pass for automatic clock gating cell insertion. + - Added "bufnorm" experimental pass to convert design into + buffered-normalized form. + - Added experimental "aiger2" and "xaiger2" backends, and an + experimental "abc_new" command + - Added "-force-detailed-loop-check" option to "check" pass. + - Added "-unit_delay" option to "read_liberty" pass. + + * Verific support + - Added left and right bound properties to wires when using + specific VHDL types. + Yosys 0.44 .. Yosys 0.45 -------------------------- * Various diff --git a/Makefile b/Makefile index 61dc51bd529..f81e1fea200 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ ENABLE_LTO := 0 ENABLE_CCACHE := 0 # sccache is not always a drop-in replacement for ccache in practice ENABLE_SCCACHE := 0 +ENABLE_FUNCTIONAL_TESTS := 0 LINK_CURSES := 0 LINK_TERMCAP := 0 LINK_ABC := 0 @@ -153,7 +154,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.45+0 +YOSYS_VER := 0.46+0 # 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 +170,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 9ed031d.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline e97731b.. | wc -l`/;" Makefile ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q) @@ -598,6 +599,7 @@ $(eval $(call add_include_file,kernel/celltypes.h)) $(eval $(call add_include_file,kernel/consteval.h)) $(eval $(call add_include_file,kernel/constids.inc)) $(eval $(call add_include_file,kernel/cost.h)) +$(eval $(call add_include_file,kernel/drivertools.h)) $(eval $(call add_include_file,kernel/ff.h)) $(eval $(call add_include_file,kernel/ffinit.h)) $(eval $(call add_include_file,kernel/ffmerge.h)) @@ -616,6 +618,7 @@ $(eval $(call add_include_file,kernel/register.h)) $(eval $(call add_include_file,kernel/rtlil.h)) $(eval $(call add_include_file,kernel/satgen.h)) $(eval $(call add_include_file,kernel/scopeinfo.h)) +$(eval $(call add_include_file,kernel/sexpr.h)) $(eval $(call add_include_file,kernel/sigtools.h)) $(eval $(call add_include_file,kernel/timinginfo.h)) $(eval $(call add_include_file,kernel/utils.h)) @@ -637,7 +640,8 @@ $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h)) OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o OBJS += kernel/binding.o -OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o +OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o kernel/sexpr.o +OBJS += kernel/drivertools.o kernel/functional.o ifeq ($(ENABLE_ZLIB),1) OBJS += kernel/fstdata.o endif @@ -738,7 +742,7 @@ $(PROGRAM_PREFIX)yosys$(EXE): $(OBJS) libyosys.so: $(filter-out kernel/driver.o,$(OBJS)) ifeq ($(OS), Darwin) - $(P) $(CXX) -o libyosys.so -shared -Wl,-install_name,$(LIBDIR)/libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) + $(P) $(CXX) -o libyosys.so -shared -undefined dynamic_lookup -Wl,-install_name,$(LIBDIR)/libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) else $(P) $(CXX) -o libyosys.so -shared -Wl,-soname,$(LIBDIR)/libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) endif @@ -889,6 +893,9 @@ endif +cd tests/xprop && bash run-test.sh $(SEEDOPT) +cd tests/fmt && bash run-test.sh +cd tests/cxxrtl && bash run-test.sh +ifeq ($(ENABLE_FUNCTIONAL_TESTS),1) + +cd tests/functional && bash run-test.sh +endif @echo "" @echo " Passed \"make test\"." @echo "" @@ -972,16 +979,17 @@ docs/source/cmd/abc.rst: $(TARGETS) $(EXTRA_TARGETS) ./$(PROGRAM_PREFIX)yosys -p 'help -write-rst-command-reference-manual' PHONY: docs/gen_examples docs/gen_images docs/guidelines docs/usage docs/reqs -docs/gen_examples: +docs/gen_examples: $(TARGETS) $(Q) $(MAKE) -C docs examples -docs/gen_images: +docs/gen_images: $(TARGETS) $(Q) $(MAKE) -C docs images DOCS_GUIDELINE_FILES := GettingStarted CodingStyle -docs/guidelines docs/source/generated: +DOCS_GUIDELINE_SOURCE := $(addprefix guidelines/,$(DOCS_GUIDELINE_FILES)) +docs/guidelines docs/source/generated: $(DOCS_GUIDELINE_SOURCE) $(Q) mkdir -p docs/source/generated - $(Q) cp -f $(addprefix guidelines/,$(DOCS_GUIDELINE_FILES)) docs/source/generated + $(Q) cp -f $(DOCS_GUIDELINE_SOURCE) docs/source/generated # some commands return an error and print the usage text to stderr define DOC_USAGE_STDERR @@ -1049,6 +1057,16 @@ coverage: lcov --capture -d . --no-external -o coverage.info genhtml coverage.info --output-directory coverage_html +clean_coverage: + find . -name "*.gcda" -type f -delete + +FUNC_KERNEL := functional.cc functional.h sexpr.cc sexpr.h compute_graph.h +FUNC_INCLUDES := $(addprefix --include *,functional/* $(FUNC_KERNEL)) +coverage_functional: + rm -rf coverage.info coverage_html + lcov --capture -d backends/functional -d kernel $(FUNC_INCLUDES) --no-external -o coverage.info + genhtml coverage.info --output-directory coverage_html + qtcreator: echo "$(CXXFLAGS)" | grep -o '\-D[^ ]*' | tr ' ' '\n' | sed 's/-D/#define /' | sed 's/=/ /'> qtcreator.config { for file in $(basename $(OBJS)); do \ diff --git a/abc b/abc index 2188bc71228..cac8f99eaa2 160000 --- a/abc +++ b/abc @@ -1 +1 @@ -Subproject commit 2188bc71228b0788569d83ad2b7e7b91ca5dcc09 +Subproject commit cac8f99eaa220a5e3db5caeb87cef0a975c953a2 diff --git a/backends/aiger/xaiger.cc b/backends/aiger/xaiger.cc index 68c2ff52f55..3ca8b205a78 100644 --- a/backends/aiger/xaiger.cc +++ b/backends/aiger/xaiger.cc @@ -18,32 +18,6 @@ * */ -// https://stackoverflow.com/a/46137633 -#ifdef _MSC_VER -#include -#define bswap32 _byteswap_ulong -#elif defined(__APPLE__) -#include -#define bswap32 OSSwapInt32 -#elif defined(__GNUC__) -#define bswap32 __builtin_bswap32 -#else -#include -inline static uint32_t bswap32(uint32_t x) -{ - // https://stackoverflow.com/a/27796212 - register uint32_t value = number_to_be_reversed; - uint8_t lolo = (value >> 0) & 0xFF; - uint8_t lohi = (value >> 8) & 0xFF; - uint8_t hilo = (value >> 16) & 0xFF; - uint8_t hihi = (value >> 24) & 0xFF; - return (hihi << 24) - | (hilo << 16) - | (lohi << 8) - | (lolo << 0); -} -#endif - #include "kernel/yosys.h" #include "kernel/sigtools.h" #include "kernel/utils.h" @@ -52,16 +26,6 @@ inline static uint32_t bswap32(uint32_t x) USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -inline int32_t to_big_endian(int32_t i32) { -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - return bswap32(i32); -#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - return i32; -#else -#error "Unknown endianness" -#endif -} - void aiger_encode(std::ostream &f, int x) { log_assert(x >= 0); @@ -537,9 +501,12 @@ struct XAigerWriter f << "c"; - auto write_buffer = [](std::stringstream &buffer, int i32) { - int32_t i32_be = to_big_endian(i32); - buffer.write(reinterpret_cast(&i32_be), sizeof(i32_be)); + auto write_buffer = [](std::ostream &buffer, unsigned int u32) { + typedef unsigned char uchar; + unsigned char u32_be[4] = { + (uchar) (u32 >> 24), (uchar) (u32 >> 16), (uchar) (u32 >> 8), (uchar) u32 + }; + buffer.write((char *) u32_be, sizeof(u32_be)); }; std::stringstream h_buffer; auto write_h_buffer = std::bind(write_buffer, std::ref(h_buffer), std::placeholders::_1); @@ -640,14 +607,12 @@ struct XAigerWriter f << "r"; std::string buffer_str = r_buffer.str(); - int32_t buffer_size_be = to_big_endian(buffer_str.size()); - f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); + write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); f << "s"; buffer_str = s_buffer.str(); - buffer_size_be = to_big_endian(buffer_str.size()); - f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); + write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); RTLIL::Design *holes_design; @@ -664,22 +629,19 @@ struct XAigerWriter f << "a"; std::string buffer_str = a_buffer.str(); - int32_t buffer_size_be = to_big_endian(buffer_str.size()); - f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); + write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); } } f << "h"; std::string buffer_str = h_buffer.str(); - int32_t buffer_size_be = to_big_endian(buffer_str.size()); - f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); + write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); f << "i"; buffer_str = i_buffer.str(); - buffer_size_be = to_big_endian(buffer_str.size()); - f.write(reinterpret_cast(&buffer_size_be), sizeof(buffer_size_be)); + write_buffer(f, buffer_str.size()); f.write(buffer_str.data(), buffer_str.size()); //f << "o"; //buffer_str = o_buffer.str(); diff --git a/backends/aiger2/Makefile.inc b/backends/aiger2/Makefile.inc new file mode 100644 index 00000000000..494b8d6c657 --- /dev/null +++ b/backends/aiger2/Makefile.inc @@ -0,0 +1 @@ +OBJS += backends/aiger2/aiger.o diff --git a/backends/aiger2/aiger.cc b/backends/aiger2/aiger.cc new file mode 100644 index 00000000000..3fc6ccdbae3 --- /dev/null +++ b/backends/aiger2/aiger.cc @@ -0,0 +1,1471 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) Martin PoviĊĦer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +// TODOs: +// - gracefully handling inout ports (an error message probably) +// - undriven wires +// - zero-width operands + +#include "kernel/register.h" +#include "kernel/celltypes.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +#define BITWISE_OPS ID($buf), ID($not), ID($mux), ID($and), ID($or), ID($xor), ID($xnor), ID($fa), \ + ID($bwmux) + +#define REDUCE_OPS ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool) + +#define LOGIC_OPS ID($logic_and), ID($logic_or), ID($logic_not) + +#define GATE_OPS ID($_BUF_), ID($_NOT_), ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_), \ + ID($_XOR_), ID($_XNOR_), ID($_ANDNOT_), ID($_ORNOT_), ID($_MUX_), ID($_NMUX_), \ + ID($_AOI3_), ID($_OAI3_), ID($_AOI4_), ID($_OAI4_) + +#define CMP_OPS ID($eq), ID($ne), ID($lt), ID($le), ID($ge), ID($gt) + +// TODO +//#define ARITH_OPS ID($add), ID($sub), ID($neg) + +#define KNOWN_OPS BITWISE_OPS, REDUCE_OPS, LOGIC_OPS, GATE_OPS, ID($pos), CMP_OPS, \ + ID($pmux), ID($bmux) /*, ARITH_OPS*/ + +template +struct Index { + struct HierCursor; + struct ModuleInfo { + Module *module; + int len; + dict windices; + dict suboffsets; + pool found_blackboxes; + + bool indexing = false; + bool indexed = false; + }; + + dict modules; + + int index_wires(ModuleInfo &info, RTLIL::Module *m) + { + int sum = 0; + for (auto w : m->wires()) { + info.windices[w] = sum; + sum += w->width; + } + return sum; + } + + bool flatten = false; + bool inline_whiteboxes = false; + bool allow_blackboxes = false; + + int index_module(RTLIL::Module *m) + { + ModuleInfo &info = modules[m]; + + if (info.indexed) + return info.len; + + if (info.indexing && !info.indexed) + log_error("Hierarchy error\n"); + + info.module = m; + int pos = index_wires(info, m); + + for (auto cell : m->cells()) { + if (cell->type.in(KNOWN_OPS) || cell->type.in(ID($scopeinfo), ID($specify2), ID($specify3))) + continue; + + Module *submodule = m->design->module(cell->type); + + if (submodule && flatten && + !submodule->get_bool_attribute(ID::keep_hierarchy) && + !submodule->get_blackbox_attribute(inline_whiteboxes)) { + info.suboffsets[cell] = pos; + pos += index_module(submodule); + } else { + if (allow_blackboxes) { + info.found_blackboxes.insert(cell); + } else { + if (!submodule || submodule->get_blackbox_attribute()) + log_error("Unsupported cell type: %s (%s in %s)\n", + log_id(cell->type), log_id(cell), log_id(m)); + } + } + } + + info.len = pos; + return info.len; + } + + Design *design; + Module *top; + ModuleInfo *top_minfo; + std::vector lits; + + Index() + { + } + + void setup(RTLIL::Module *top) + { + design = top->design; + this->top = top; + + modules.reserve(top->design->modules().size()); + int nlits = index_module(top); + log_debug("allocating for %d literals\n", nlits); + lits.resize(nlits, Writer::EMPTY_LIT); + top_minfo = &modules.at(top); + } + + bool const_folding = true; + bool strashing = false; + dict, Lit> cache; + + Lit AND(Lit a, Lit b) + { + if (const_folding) { + if (a == CFALSE || b == CFALSE) + return CFALSE; + if (a == CTRUE) + return b; + if (b == CTRUE) + return a; + } + + if (!strashing) { + return (static_cast(this))->emit_gate(a, b); + } else { + if (a < b) std::swap(a, b); + auto pair = std::make_pair(a, b); + + if (!cache.count(pair)) { + Lit nl = (static_cast(this))->emit_gate(a, b); + cache[pair] = nl; + return nl; + } else { + return cache.at(pair); + } + } + } + + Lit NOT(Lit lit) + { + return Writer::negate(lit); + } + + Lit OR(Lit a, Lit b) + { + return NOT(AND(NOT(a), NOT(b))); + } + + Lit MUX(Lit a, Lit b, Lit s) + { + if (const_folding) { + if (a == b) + return a; + if (s == CFALSE) + return a; + if (s == CTRUE) + return b; + } + + return OR(AND(a, NOT(s)), AND(b, s)); + } + + Lit XOR(Lit a, Lit b) + { + return OR(AND(a, NOT(b)), AND(NOT(a), b)); + } + + Lit XNOR(Lit a, Lit b) + { + return NOT(OR(AND(a, NOT(b)), AND(NOT(a), b))); + } + + Lit CARRY(Lit a, Lit b, Lit c) + { + if (const_folding) { + if (c == CTRUE) { + return OR(a, b); + } else if (c == CFALSE) { + return AND(a, b); + } + } + return OR(AND(a, b), AND(c, OR(a, b))); + } + + Lit REDUCE(std::vector lits, bool op_xor=false) + { + std::vector next; + while (lits.size() > 1) { + next.clear(); + for (int i = 0; i < (int) lits.size(); i += 2) { + if (i + 1 >= (int) lits.size()) { + next.push_back(lits[i]); + } else { + Lit a = lits[i], b = lits[i + 1]; + next.push_back(op_xor ? XOR(a, b) : AND(a, b)); + } + } + next.swap(lits); + } + + if (lits.empty()) + return op_xor ? CFALSE : CTRUE; + else + return lits.front(); + } + + Lit impl_op(HierCursor &cursor, Cell *cell, IdString oport, int obit) + { + if (cell->type.in(REDUCE_OPS, LOGIC_OPS, CMP_OPS) && obit != 0) { + return CFALSE; + } else if (cell->type.in(CMP_OPS)) { + SigSpec aport = cell->getPort(ID::A); + bool asigned = cell->getParam(ID::A_SIGNED).as_bool(); + SigSpec bport = cell->getPort(ID::B); + bool bsigned = cell->getParam(ID::B_SIGNED).as_bool(); + + int width = std::max(aport.size(), bport.size()) + 1; + aport.extend_u0(width, asigned); + bport.extend_u0(width, bsigned); + + if (cell->type.in(ID($eq), ID($ne))) { + int carry = CTRUE; + for (int i = 0; i < width; i++) { + Lit a = visit(cursor, aport[i]); + Lit b = visit(cursor, bport[i]); + carry = AND(carry, XNOR(a, b)); + } + return (cell->type == ID($eq)) ? carry : /* $ne */ NOT(carry); + } else if (cell->type.in(ID($lt), ID($le), ID($gt), ID($ge))) { + if (cell->type.in(ID($gt), ID($ge))) + std::swap(aport, bport); + int carry = cell->type.in(ID($le), ID($ge)) ? CFALSE : CTRUE; + Lit a, b; + // TODO: this might not be the most economic structure; revisit at a later date + for (int i = 0; i < width; i++) { + a = visit(cursor, aport[i]); + b = visit(cursor, bport[i]); + if (i != width - 1) + carry = CARRY(a, NOT(b), carry); + } + return XOR(carry, XNOR(a, b)); + } else { + log_abort(); + } + } else if (cell->type.in(REDUCE_OPS, ID($logic_not))) { + SigSpec inport = cell->getPort(ID::A); + + std::vector lits; + for (int i = 0; i < inport.size(); i++) { + Lit lit = visit(cursor, inport[i]); + if (cell->type.in(ID($reduce_and), ID($reduce_xor), ID($reduce_xnor))) { + lits.push_back(lit); + } else if (cell->type.in(ID($reduce_or), ID($reduce_bool), ID($logic_not))) { + lits.push_back(NOT(lit)); + } else { + log_abort(); + } + } + + Lit acc = REDUCE(lits, cell->type.in(ID($reduce_xor), ID($reduce_xnor))); + + if (!cell->type.in(ID($reduce_xnor), ID($reduce_or), ID($reduce_bool))) + return acc; + else + return NOT(acc); + } else if (cell->type.in(ID($logic_and), ID($logic_or))) { + SigSpec aport = cell->getPort(ID::A); + SigSpec bport = cell->getPort(ID::B); + + log_assert(aport.size() > 0 && bport.size() > 0); // TODO + + Lit a = visit(cursor, aport[0]); + for (int i = 1; i < aport.size(); i++) { + Lit l = visit(cursor, aport[i]); + a = OR(a, l); + } + + Lit b = visit(cursor, bport[0]); + for (int i = 1; i < bport.size(); i++) { + Lit l = visit(cursor, bport[i]); + b = OR(b, l); + } + + if (cell->type == ID($logic_and)) + return AND(a, b); + else if (cell->type == ID($logic_or)) + return OR(a, b); + else + log_abort(); + } else if (cell->type.in(BITWISE_OPS, GATE_OPS, ID($pos))) { + SigSpec aport = cell->getPort(ID::A); + Lit a; + if (obit < aport.size()) { + a = visit(cursor, aport[obit]); + } else { + if (cell->getParam(ID::A_SIGNED).as_bool()) + a = visit(cursor, aport.msb()); + else + a = CFALSE; + } + + if (cell->type.in(ID($buf), ID($pos), ID($_BUF_))) { + return a; + } else if (cell->type.in(ID($not), ID($_NOT_))) { + return NOT(a); + } else { + SigSpec bport = cell->getPort(ID::B); + Lit b; + if (obit < bport.size()) { + b = visit(cursor, bport[obit]); + } else { + if (cell->getParam(ID::B_SIGNED).as_bool()) + b = visit(cursor, bport.msb()); + else + b = CFALSE; + } + + if (cell->type.in(ID($and), ID($_AND_))) { + return AND(a, b); + } else if (cell->type.in(ID($_NAND_))) { + return NOT(AND(a, b)); + } else if (cell->type.in(ID($or), ID($_OR_))) { + return OR(a, b); + } else if (cell->type.in(ID($_NOR_))) { + return NOT(OR(a, b)); + } else if (cell->type.in(ID($xor), ID($_XOR_))) { + return XOR(a, b); + } else if (cell->type.in(ID($xnor), ID($_XNOR_))) { + return NOT(XOR(a, b)); + } else if (cell->type.in(ID($_ANDNOT_))) { + return AND(a, NOT(b)); + } else if (cell->type.in(ID($_ORNOT_))) { + return OR(a, NOT(b)); + } else if (cell->type.in(ID($mux), ID($_MUX_))) { + Lit s = visit(cursor, cell->getPort(ID::S)); + return MUX(a, b, s); + } else if (cell->type.in(ID($bwmux))) { + Lit s = visit(cursor, cell->getPort(ID::S)[obit]); + return MUX(a, b, s); + } else if (cell->type.in(ID($_NMUX_))) { + Lit s = visit(cursor, cell->getPort(ID::S)[obit]); + return NOT(MUX(a, b, s)); + } else if (cell->type.in(ID($fa))) { + Lit c = visit(cursor, cell->getPort(ID::C)[obit]); + Lit ab = XOR(a, b); + if (oport == ID::Y) { + return XOR(ab, c); + } else /* oport == ID::X */ { + return OR(AND(a, b), AND(c, ab)); + } + } else if (cell->type.in(ID($_AOI3_), ID($_OAI3_), ID($_AOI4_), ID($_OAI4_))) { + Lit c, d; + + c = visit(cursor, cell->getPort(ID::C)[obit]); + if (/* 4 input types */ cell->type.in(ID($_AOI4_), ID($_OAI4_))) + d = visit(cursor, cell->getPort(ID::D)[obit]); + else + d = cell->type == ID($_AOI3_) ? 1 : 0; + + if (/* aoi */ cell->type.in(ID($_AOI3_), ID($_AOI4_))) + return NOT(OR(AND(a, b), AND(c, d))); + else + return NOT(AND(OR(a, b), OR(c, d))); + } else { + log_abort(); + } + } + } else if (cell->type == ID($pmux)) { + SigSpec aport = cell->getPort(ID::A); + SigSpec bport = cell->getPort(ID::B); + SigSpec sport = cell->getPort(ID::S); + int width = aport.size(); + + Lit a = visit(cursor, aport[obit]); + + std::vector bar, sels; + for (int i = 0; i < sport.size(); i++) { + Lit s = visit(cursor, sport[i]); + Lit b = visit(cursor, bport[width * i + obit]); + bar.push_back(NOT(AND(s, b))); + sels.push_back(NOT(s)); + } + + return OR(AND(REDUCE(sels), a), NOT(REDUCE(bar))); + } else if (cell->type == ID($bmux)) { + SigSpec aport = cell->getPort(ID::A); + SigSpec sport = cell->getPort(ID::S); + int width = cell->getParam(ID::WIDTH).as_int(); + + std::vector data; + for (int i = obit; i < aport.size(); i += width) + data.push_back(visit(cursor, aport[i])); + + std::vector next; + for (int i = 0; i < sport.size(); i++) { + Lit s = visit(cursor, sport[i]); + next.clear(); + for (int j = 0; j < (int) data.size(); j += 2) + next.push_back(MUX(data[j], data[j + 1], s)); + data.swap(next); + } + log_assert(data.size() == 1); + return data[0]; + } else { + log_abort(); + } + } + + struct HierCursor { + typedef std::pair Level; + std::vector levels; + int instance_offset = 0; + + HierCursor() + { + } + + ModuleInfo &leaf_minfo(Index &index) + { + if (levels.empty()) + return *index.top_minfo; + else + return levels.back().first; + } + + Module *leaf_module(Index &index) + { + return leaf_minfo(index).module; + } + + int bitwire_index(Index &index, SigBit bit) + { + log_assert(bit.wire != nullptr); + return instance_offset + leaf_minfo(index).windices[bit.wire] + bit.offset; + } + + Cell *exit(Index &index) + { + log_assert(!levels.empty()); + Cell *instance = levels.back().second; + + levels.pop_back(); + instance_offset -= leaf_minfo(index).suboffsets.at(instance); + + // return the instance we just exited + return instance; + } + + Module *enter(Index &index, Cell *cell) + { + Design *design = index.design; + auto &minfo = leaf_minfo(index); + log_assert(minfo.suboffsets.count(cell)); + Module *def = design->module(cell->type); + log_assert(def); + levels.push_back(Level(index.modules.at(def), cell)); + instance_offset += minfo.suboffsets.at(cell); + + // return the module definition we just entered + return def; + } + + bool is_top() + { + return levels.empty(); + } + + std::string path() + { + std::string ret; + bool first = true; + for (auto pair : levels) { + if (!first) + ret += "."; + if (!pair.second) + ret += RTLIL::unescape_id(pair.first.module->name); + else + ret += RTLIL::unescape_id(pair.second->name); + first = false; + } + return ret; + } + + int hash() const + { + int hash = 0; + for (auto pair : levels) + hash += (uintptr_t) pair.second; + return hash; + } + + bool operator==(const HierCursor &other) const + { + if (levels.size() != other.levels.size()) + return false; + + for (int i = 0; i < levels.size(); i++) + if (levels[i].second != other.levels[i].second) + return false; + + return true; + } + }; + + bool visit_hook(int, HierCursor&, SigBit) + { + return false; + } + + Lit visit(HierCursor &cursor, SigBit bit) + { + if (!bit.wire) { + if (bit == State::S1) + return CTRUE; + else if (bit == State::S0) + return CFALSE; + else if (bit == State::Sx) + return CFALSE; + else + log_error("Unhandled state %s\n", log_signal(bit)); + } + + int idx = cursor.bitwire_index(*this, bit); + if (lits[idx] != Writer::EMPTY_LIT) { + // literal already assigned + return lits[idx]; + } + + // provide means for the derived class to override + // the visit behavior + if ((static_cast(this))->visit_hook(idx, cursor, bit)) { + return lits[idx]; + } + + Lit ret; + if (!bit.wire->port_input) { + // an output of a cell + Cell *driver = bit.wire->driverCell(); + + if (driver->type.in(KNOWN_OPS)) { + ret = impl_op(cursor, driver, bit.wire->driverPort(), bit.offset); + } else { + Module *def = cursor.enter(*this, driver); + { + IdString portname = bit.wire->driverPort(); + Wire *w = def->wire(portname); + if (!w) + log_error("Output port %s on instance %s of %s doesn't exist\n", + log_id(portname), log_id(driver), log_id(def)); + if (bit.offset >= w->width) + log_error("Bit position %d of output port %s on instance %s of %s is out of range (port has width %d)\n", + bit.offset, log_id(portname), log_id(driver), log_id(def), w->width); + ret = visit(cursor, SigBit(w, bit.offset)); + } + cursor.exit(*this); + } + } else { + // a module input: we cannot be the top module, otherwise + // the branch for pre-existing literals would have been taken + log_assert(!cursor.is_top()); + + // step into the upper module + Cell *instance = cursor.exit(*this); + { + IdString portname = bit.wire->name; + if (!instance->hasPort(portname)) + log_error("Input port %s on instance %s of %s unconnected\n", + log_id(portname), log_id(instance), log_id(instance->type)); + auto &port = instance->getPort(portname); + if (bit.offset >= port.size()) + log_error("Bit %d of input port %s on instance %s of %s unconnected\n", + bit.offset, log_id(portname), log_id(instance), log_id(instance->type)); + ret = visit(cursor, port[bit.offset]); + } + cursor.enter(*this, instance); + } + + lits[idx] = ret; + return ret; + } + + Lit &pi_literal(SigBit bit, HierCursor *cursor=nullptr) + { + log_assert(bit.wire); + + if (!cursor) { + log_assert(bit.wire->module == top); + log_assert(bit.wire->port_input); + return lits[top_minfo->windices[bit.wire] + bit.offset]; + } else { + log_assert(bit.wire->module == cursor->leaf_module(*this)); + return lits[cursor->bitwire_index(*this, bit)]; + } + } + + Lit eval_po(SigBit bit, HierCursor *cursor=nullptr) + { + Lit ret; + if (!cursor) { + HierCursor cursor_; + ret = visit(cursor_, bit); + log_assert(cursor_.is_top()); + log_assert(cursor_.instance_offset == 0); + } else { + ret = visit(*cursor, bit); + } + return ret; + } + + void visit_hierarchy(std::function f, + HierCursor &cursor) + { + f(cursor); + + ModuleInfo &minfo = cursor.leaf_minfo(*this); + for (auto cell : minfo.module->cells()) { + if (minfo.suboffsets.count(cell)) { + cursor.enter(*this, cell); + visit_hierarchy(f, cursor); + cursor.exit(*this); + } + } + } + + void visit_hierarchy(std::function f) + { + HierCursor cursor; + visit_hierarchy(f, cursor); + } +}; + +struct AigerWriter : Index { + typedef unsigned int Lit; + + const static Lit CONST_FALSE = 0; + const static Lit CONST_TRUE = 1; + const static constexpr Lit EMPTY_LIT = std::numeric_limits::max(); + + static Lit negate(Lit lit) { + return lit ^ 1; + } + + std::ostream *f; + Lit lit_counter; + int ninputs, nlatches, noutputs, nands; + + void encode(int delta) + { + log_assert(delta >= 0); + unsigned int x = delta; + while (x & ~0x7f) { + f->put((x & 0x7f) | 0x80); + x = x >> 7; + } + f->put(x); + } + + Lit emit_gate(Lit a, Lit b) + { + Lit out = lit_counter; + nands++; + lit_counter += 2; + + if (a < b) std::swap(a, b); + encode(out - a); + encode(a - b); + return out; + } + + void reset_counters() + { + lit_counter = 2; + ninputs = nlatches = noutputs = nands = 0; + } + + void write_header() { + log_assert(lit_counter == (Lit) (ninputs + nlatches + nands) * 2 + 2); + + char buf[128]; + snprintf(buf, sizeof(buf) - 1, "aig %08d %08d %08d %08d %08d\n", + ninputs + nlatches + nands, ninputs, nlatches, noutputs, nands); + f->write(buf, strlen(buf)); + } + + void write(std::ostream *f) { + reset_counters(); + + auto file_start = f->tellp(); + + // populate inputs + std::vector inputs; + for (auto id : top->ports) { + Wire *w = top->wire(id); + log_assert(w); + if (w->port_input) + for (int i = 0; i < w->width; i++) { + pi_literal(SigBit(w, i)) = lit_counter; + inputs.push_back(SigBit(w, i)); + lit_counter += 2; + ninputs++; + } + } + + this->f = f; + // start with the header + write_header(); + // insert padding where output literals will go (once known) + for (auto id : top->ports) { + Wire *w = top->wire(id); + log_assert(w); + if (w->port_output) { + for (auto bit : SigSpec(w)) { + (void) bit; + char buf[16]; + snprintf(buf, sizeof(buf) - 1, "%08d\n", 0); + f->write(buf, strlen(buf)); + noutputs++; + } + } + } + auto data_start = f->tellp(); + + // now the guts + std::vector> outputs; + for (auto w : top->wires()) + if (w->port_output) { + for (auto bit : SigSpec(w)) + outputs.push_back({bit, eval_po(bit)}); + } + auto data_end = f->tellp(); + + // revisit header and the list of outputs + f->seekp(file_start); + write_header(); + for (auto pair : outputs) { + char buf[16]; + snprintf(buf, sizeof(buf) - 1, "%08d\n", pair.second); + f->write(buf, strlen(buf)); + } + // double check we arrived at the same offset for the + // main data section + log_assert(data_start == f->tellp()); + + f->seekp(data_end); + int i = 0; + for (auto pair : outputs) { + if (SigSpec(pair.first).is_wire()) { + char buf[32]; + snprintf(buf, sizeof(buf) - 1, "o%d ", i); + f->write(buf, strlen(buf)); + std::string name = RTLIL::unescape_id(pair.first.wire->name); + f->write(name.data(), name.size()); + f->put('\n'); + } + i++; + } + i = 0; + for (auto bit : inputs) { + if (SigSpec(bit).is_wire()) { + char buf[32]; + snprintf(buf, sizeof(buf) - 1, "i%d ", i); + f->write(buf, strlen(buf)); + std::string name = RTLIL::unescape_id(bit.wire->name); + f->write(name.data(), name.size()); + f->put('\n'); + } + i++; + } + } +}; + +struct XAigerAnalysis : Index { + const static int CONST_FALSE = 0; + const static int CONST_TRUE = 0; + const static constexpr int EMPTY_LIT = -1; + + XAigerAnalysis() + { + allow_blackboxes = true; + + // Disable const folding and strashing as literal values are not unique + const_folding = false; + strashing = false; + } + + static int negate(int lit) + { + return lit; + } + + int emit_gate(int a, int b) + { + return max(a, b) + 1; + } + + pool seen; + + bool visit_hook(int idx, HierCursor &cursor, SigBit bit) + { + log_assert(cursor.is_top()); // TOOD: fix analyzer to work with hierarchy + + if (bit.wire->port_input) + return false; + + Cell *driver = bit.wire->driverCell(); + if (!driver->type.isPublic()) + return false; + + Module *mod = design->module(driver->type); + log_assert(mod); + if (!mod->has_attribute(ID::abc9_box_id)) + return false; + + int max = 1; + for (auto wire : mod->wires()) + if (wire->port_input) + for (int i = 0; i < wire->width; i++) { + int ilevel = visit(cursor, driver->getPort(wire->name)[i]); + max = std::max(max, ilevel + 1); + } + lits[idx] = max; + + if (!seen.count(driver)) + seen.insert(driver); + + return true; + } + + void analyze(Module *top) + { + setup(top); + + for (auto id : top->ports) { + Wire *w = top->wire(id); + log_assert(w); + if (w->port_input) + for (int i = 0; i < w->width; i++) + pi_literal(SigBit(w, i)) = 0; + } + + HierCursor cursor; + for (auto box : top_minfo->found_blackboxes) { + Module *def = design->module(box->type); + if (!box->type.isPublic() || (def && !def->has_attribute(ID::abc9_box_id))) + for (auto &conn : box->connections_) + if (box->output(conn.first)) + for (auto bit : conn.second) + pi_literal(bit, &cursor) = 0; + } + + for (auto w : top->wires()) + if (w->port_output) { + for (auto bit : SigSpec(w)) + (void) eval_po(bit); + } + + for (auto box : top_minfo->found_blackboxes) { + Module *def = design->module(box->type); + if (!box->type.isPublic() || (def && !def->has_attribute(ID::abc9_box_id))) + for (auto &conn : box->connections_) + if (box->input(conn.first)) + for (auto bit : conn.second) + (void) eval_po(bit); + } + } +}; + +struct XAigerWriter : AigerWriter { + XAigerWriter() + { + allow_blackboxes = true; + } + + bool mapping_prep = false; + pool keep_wires; + std::ofstream map_file; + + typedef std::pair HierBit; + std::vector pos; + std::vector pis; + int proper_pos_counter = 0; + + pool driven_by_opaque_box; + + void ensure_pi(SigBit bit, HierCursor cursor={}, + bool box_port=false) + { + Lit &lit = pi_literal(bit, &cursor); + if (lit == EMPTY_LIT) { + lit = lit_counter; + pis.push_back(std::make_pair(bit, cursor)); + lit_counter += 2; + if (map_file.is_open() && !box_port) { + log_assert(cursor.is_top()); // TODO + driven_by_opaque_box.insert(bit); + map_file << "pi " << pis.size() - 1 << " " << bit.offset + << " " << bit.wire->name.c_str() << "\n"; + } + } else { + log_assert(!box_port); + } + } + + bool is_pi(SigBit bit, HierCursor cursor={}) + { + return pi_literal(bit, &cursor) != EMPTY_LIT; + } + + void pad_pi() + { + pis.push_back(std::make_pair(RTLIL::Sx, HierCursor{})); + lit_counter += 2; + } + + void append_box_ports(Cell *box, HierCursor &cursor, bool inputs) + { + for (auto &conn : box->connections_) { + bool is_input = box->input(conn.first); + bool is_output = box->output(conn.first); + + if (!(is_input || is_output) || (is_input && is_output)) + log_error("Ambiguous port direction on %s/%s\n", + log_id(box->type), log_id(conn.first)); + + if (is_input && inputs) { + int bitp = 0; + for (auto bit : conn.second) { + if (!bit.wire) { + bitp++; + continue; + } + + if (map_file.is_open()) { + log_assert(cursor.is_top()); + map_file << "pseudopo " << proper_pos_counter++ << " " << bitp + << " " << box->name.c_str() + << " " << conn.first.c_str() << "\n"; + } + + pos.push_back(std::make_pair(bit, cursor)); + + if (mapping_prep) + conn.second[bitp] = RTLIL::Sx; + + bitp++; + } + } else if (is_output && !inputs) { + for (auto &bit : conn.second) { + if (!bit.wire || bit.wire->port_input) + log_error("Bad connection"); + + + ensure_pi(bit, cursor); + keep_wires.insert(bit.wire); + } + } + } + } + + RTLIL::Module *holes_module; + + std::stringstream h_buffer; + static void write_be32(std::ostream &buffer, uint32_t u32) + { + typedef unsigned char uchar; + unsigned char u32_be[4] = { + (uchar) (u32 >> 24), (uchar) (u32 >> 16), (uchar) (u32 >> 8), (uchar) u32 + }; + buffer.write((char *) u32_be, sizeof(u32_be)); + } + + void prep_boxes(int pending_pos_num) + { + XAigerAnalysis analysis; + log_debug("preforming analysis on '%s'\n", log_id(top)); + analysis.analyze(top); + log_debug("analysis on '%s' done\n", log_id(top)); + + // boxes which have timing data, maybe a whitebox model + std::vector> nonopaque_boxes; + // boxes which are fully opaque + std::vector> opaque_boxes; + + log_debug("found boxes:\n"); + visit_hierarchy([&](HierCursor &cursor) { + auto &minfo = cursor.leaf_minfo(*this); + + for (auto box : minfo.found_blackboxes) { + log_debug(" - %s.%s (type %s): ", cursor.path().c_str(), + RTLIL::unescape_id(box->name).c_str(), + log_id(box->type)); + + Module *box_module = design->module(box->type), *box_derived; + + if (box_module && !box->parameters.empty()) { + // TODO: This is potentially costly even if a cached derivation exists + box_derived = design->module(box_module->derive(design, box->parameters)); + log_assert(box_derived); + } else { + box_derived = box_module; + } + + if (box_derived && box_derived->has_attribute(ID::abc9_box_id)) { + // This is an ABC9 box, we have timing data, maybe even a whitebox model + // These need to go last in the AIGER port list. + nonopaque_boxes.push_back(std::make_tuple(cursor, box, box_derived)); + log_debug("non-opaque\n"); + } else { + opaque_boxes.push_back(std::make_tuple(cursor, box, box_derived)); + log_debug("opaque\n"); + } + } + }); + + for (auto [cursor, box, def] : opaque_boxes) + append_box_ports(box, cursor, false); + + holes_module = design->addModule(NEW_ID); + std::vector holes_pis; + int boxes_ci_num = 0, boxes_co_num = 0; + + int box_seq = 0; + + std::vector boxes_order(analysis.seen.begin(), analysis.seen.end()); + std::reverse(boxes_order.begin(), boxes_order.end()); + + nonopaque_boxes.clear(); + for (auto box : boxes_order) { + HierCursor cursor; + Module *def = design->module(box->type); + nonopaque_boxes.push_back(std::make_tuple(cursor, box, def)); + } + + for (auto [cursor, box, def] : nonopaque_boxes) { + // use `def->name` not `box->type` as we want the derived type + Cell *holes_wb = holes_module->addCell(NEW_ID, def->name); + int holes_pi_idx = 0; + + if (map_file.is_open()) { + log_assert(cursor.is_top()); + map_file << "box " << box_seq << " " << box->name.c_str() << "\n"; + } + box_seq++; + + for (auto port_id : def->ports) { + Wire *port = def->wire(port_id); + log_assert(port); + + SigSpec conn = box->hasPort(port_id) ? box->getPort(port_id) : SigSpec{}; + + if (port->port_input && !port->port_output) { + // primary + for (int i = 0; i < port->width; i++) { + SigBit bit; + if (i < conn.size()) { + bit = conn[i]; + } else { + // FIXME: hierarchical path + log_warning("connection on port %s[%d] of instance %s (type %s) missing, using 1'bx\n", + log_id(port_id), i, log_id(box), log_id(box->type)); + bit = RTLIL::Sx; + } + + pos.push_back(std::make_pair(bit, cursor)); + } + boxes_co_num += port->width; + + if (mapping_prep && !conn.empty()) + box->setPort(port_id, SigSpec(RTLIL::Sx, conn.size())); + + // holes + SigSpec in_conn; + for (int i = 0; i < port->width; i++) { + while (holes_pi_idx >= (int) holes_pis.size()) { + Wire *w = holes_module->addWire(NEW_ID, 1); + w->port_input = true; + holes_module->ports.push_back(w->name); + holes_pis.push_back(w); + } + in_conn.append(holes_pis[i]); + holes_pi_idx++; + } + holes_wb->setPort(port_id, in_conn); + } else if (port->port_output && !port->port_input) { + // primary + for (int i = 0; i < port->width; i++) { + SigBit bit; + if (i < conn.size() && conn[i].is_wire()) { + bit = conn[i]; + } else { + // FIXME: hierarchical path + log_warning("connection on port %s[%d] of instance %s (type %s) missing\n", + log_id(port_id), i, log_id(box), log_id(box->type)); + pad_pi(); + continue; + } + + ensure_pi(bit, cursor, true); + keep_wires.insert(bit.wire); + } + boxes_ci_num += port->width; + + // holes + Wire *w = holes_module->addWire(NEW_ID, port->width); + w->port_output = true; + holes_module->ports.push_back(w->name); + holes_wb->setPort(port_id, w); + } else { + log_error("Ambiguous port direction on %s/%s\n", + log_id(box->type), log_id(port_id)); + } + } + } + + for (auto [cursor, box, def] : opaque_boxes) + append_box_ports(box, cursor, true); + + write_be32(h_buffer, 1); + write_be32(h_buffer, pis.size()); + log_debug("ciNum = %zu\n", pis.size()); + write_be32(h_buffer, pending_pos_num + pos.size()); + log_debug("coNum = %zu\n", pending_pos_num + pos.size()); + write_be32(h_buffer, pis.size() - boxes_ci_num); + log_debug("piNum = %zu\n", pis.size() - boxes_ci_num); + write_be32(h_buffer, pending_pos_num + pos.size() - boxes_co_num); + log_debug("poNum = %zu\n", pending_pos_num + pos.size() - boxes_co_num); + write_be32(h_buffer, nonopaque_boxes.size()); + + box_seq = 0; + for (auto [cursor, box, def] : nonopaque_boxes) { + int box_ci_num = 0, box_co_num = 0; + for (auto port_id : def->ports) { + Wire *port = def->wire(port_id); + log_assert(port); + if (port->port_input && !port->port_output) { + box_co_num += port->width; + } else if (port->port_output && !port->port_input) { + box_ci_num += port->width; + } else { + log_abort(); + } + } + + write_be32(h_buffer, box_co_num); + write_be32(h_buffer, box_ci_num); + write_be32(h_buffer, def->attributes.at(ID::abc9_box_id).as_int()); + write_be32(h_buffer, box_seq++); + } + } + + void clear_boxes() + { + design->remove(holes_module); + } + + void write(std::ostream *f) { + reset_counters(); + + for (auto w : top->wires()) + if (w->port_input) + for (int i = 0; i < w->width; i++) + ensure_pi(SigBit(w, i)); + + int proper_po_num = 0; + for (auto w : top->wires()) + if (w->port_output) + proper_po_num += w->width; + + prep_boxes(proper_po_num); + for (auto w : top->wires()) + if (w->port_output) + for (int i = 0; i < w->width; i++) { + if (map_file.is_open() && !driven_by_opaque_box.count(SigBit(w, i))) { + map_file << "po " << proper_pos_counter++ << " " << i + << " " << w->name.c_str() << "\n"; + } + pos.push_back(std::make_pair(SigBit(w, i), HierCursor{})); + } + + this->f = f; + // start with the header + ninputs = pis.size(); + noutputs = pos.size(); + write_header(); + // insert padding where output literals will go (once known) + for (auto _ : pos) { + char buf[16]; + snprintf(buf, sizeof(buf) - 1, "%08d\n", 0); + f->write(buf, strlen(buf)); + } + auto data_start = f->tellp(); + + // now the guts + std::vector outlits; + for (auto &pair : pos) + outlits.push_back(eval_po(pair.first, &pair.second)); + + // revisit header and the list of outputs + f->seekp(0); + ninputs = pis.size(); + noutputs = pos.size(); + write_header(); + for (auto lit : outlits) { + char buf[16]; + snprintf(buf, sizeof(buf) - 1, "%08d\n", lit); + f->write(buf, strlen(buf)); + } + // double check we arrived at the same offset for the + // main data section + log_assert(data_start == f->tellp()); + + // extensions + f->seekp(0, std::ios::end); + + f->put('c'); + + // insert empty 'r' and 's' sections (abc crashes if we provide 'a' without those) + f->put('r'); + write_be32(*f, 4); + write_be32(*f, 0); + f->put('s'); + write_be32(*f, 4); + write_be32(*f, 0); + + f->put('h'); + // TODO: get rid of std::string copy + std::string h_buffer_str = h_buffer.str(); + write_be32(*f, h_buffer_str.size()); + f->write(h_buffer_str.data(), h_buffer_str.size()); + +#if 1 + f->put('a'); + write_be32(*f, 0); // size to be filled later + auto holes_aiger_start = f->tellp(); + { + AigerWriter holes_writer; + holes_writer.flatten = true; + holes_writer.inline_whiteboxes = true; + holes_writer.setup(holes_module); + holes_writer.write(f); + } + auto holes_aiger_size = f->tellp() - holes_aiger_start; + f->seekp(holes_aiger_start, std::ios::beg); + f->seekp(-4, std::ios::cur); + write_be32(*f, holes_aiger_size); +#endif + f->seekp(0, std::ios::end); + + if (mapping_prep) { + std::vector to_remove_cells; + for (auto cell : top->cells()) + if (!top_minfo->found_blackboxes.count(cell)) + to_remove_cells.push_back(cell); + for (auto cell : to_remove_cells) + top->remove(cell); + pool to_remove; + for (auto wire : top->wires()) + if (!wire->port_input && !wire->port_output && !keep_wires.count(wire)) + to_remove.insert(wire); + top->remove(to_remove); + } + + clear_boxes(); + } +}; + +struct Aiger2Backend : Backend { + Aiger2Backend() : Backend("aiger2", "(experimental) write design to AIGER file") + { + experimental(); + } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" write_aiger2 [options] [filename]\n"); + log("\n"); + log("Write the selected module to an AIGER file.\n"); + log("\n"); + log(" -strash\n"); + log(" perform structural hashing while writing\n"); + log("\n"); + log(" -flatten\n"); + log(" allow descending into submodules and write a flattened view of the design\n"); + log(" hierarchy starting at the selected top\n"); + log("\n"); + log("This command is able to ingest all combinational cells except for:\n"); + log("\n"); + pool supported = {KNOWN_OPS}; + CellTypes ct; + ct.setup_internals_eval(); + log(" "); + int col = 0; + for (auto pair : ct.cell_types) + if (!supported.count(pair.first)) { + if (col + pair.first.size() + 2 > 72) { + log("\n "); + col = 0; + } + col += pair.first.size() + 2; + log("%s, ", log_id(pair.first)); + } + log("\n"); + log("\n"); + log("And all combinational gates except for:\n"); + log("\n"); + CellTypes ct2; + ct2.setup_stdcells(); + log(" "); + col = 0; + for (auto pair : ct2.cell_types) + if (!supported.count(pair.first)) { + if (col + pair.first.size() + 2 > 72) { + log("\n "); + col = 0; + } + col += pair.first.size() + 2; + log("%s, ", log_id(pair.first)); + } + log("\n"); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, Design *design) override + { + log_header(design, "Executing AIGER2 backend.\n"); + + size_t argidx; + AigerWriter writer; + writer.const_folding = true; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-strash") + writer.strashing = true; + else if (args[argidx] == "-flatten") + writer.flatten = true; + else + break; + } + extra_args(f, filename, args, argidx); + + Module *top = design->top_module(); + + if (!top || !design->selected_whole_module(top)) + log_cmd_error("No top module selected\n"); + + design->bufNormalize(true); + writer.setup(top); + writer.write(f); + + // we are leaving the sacred land, un-bufnormalize + // (if not, this will lead to bugs: the buf-normalized + // flag must not be kept on past the code that can work + // with it) + design->bufNormalize(false); + } +} Aiger2Backend; + +struct XAiger2Backend : Backend { + XAiger2Backend() : Backend("xaiger2", "(experimental) write module to XAIGER file") + { + experimental(); + } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" write_xaiger2 [options] [filename]\n"); + log("\n"); + log("Write the selected module to a XAIGER file including the 'h' and 'a' extensions\n"); + log("with box information for ABC.\n"); + log("\n"); + log(" -strash\n"); + log(" perform structural hashing while writing\n"); + log("\n"); + log(" -flatten\n"); + log(" allow descending into submodules and write a flattened view of the design\n"); + log(" hierarchy starting at the selected top\n"); + log("\n"); + log(" -mapping_prep\n"); + log(" after the file is written, prepare the module for reintegration of\n"); + log(" a mapping in a subsequent command. all cells which are not blackboxed nor\n"); + log(" whiteboxed are removed from the design as well as all wires which only\n"); + log(" connect to removed cells\n"); + log(" (conflicts with -flatten)\n"); + log("\n"); + log(" -map2 \n"); + log(" write a map2 file which 'read_xaiger2 -sc_mapping' can read to\n"); + log(" reintegrate a mapping\n"); + log(" (conflicts with -flatten)\n"); + log("\n"); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, Design *design) override + { + log_header(design, "Executing XAIGER2 backend.\n"); + + size_t argidx; + XAigerWriter writer; + std::string map_filename; + writer.const_folding = true; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-strash") + writer.strashing = true; + else if (args[argidx] == "-flatten") + writer.flatten = true; + else if (args[argidx] == "-mapping_prep") + writer.mapping_prep = true; + else if (args[argidx] == "-map2" && argidx + 1 < args.size()) + map_filename = args[++argidx]; + else + break; + } + extra_args(f, filename, args, argidx); + + Module *top = design->top_module(); + + if (!top || !design->selected_whole_module(top)) + log_cmd_error("No top module selected\n"); + + if (!map_filename.empty()) { + writer.map_file.open(map_filename); + if (!writer.map_file) + log_cmd_error("Failed to open '%s' for writing\n", map_filename.c_str()); + } + + design->bufNormalize(true); + writer.setup(top); + writer.write(f); + + // we are leaving the sacred land, un-bufnormalize + // (if not, this will lead to bugs: the buf-normalized + // flag must not be kept on past the code that can work + // with it) + design->bufNormalize(false); + } +} XAiger2Backend; + +PRIVATE_NAMESPACE_END diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index d1d6bd8dc68..9634b833b75 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1127,7 +1127,7 @@ struct fmt_part { } case UNICHAR: { - uint32_t codepoint = val.template get(); + uint32_t codepoint = val.template zcast<32>().template get(); if (codepoint >= 0x10000) buf += (char)(0xf0 | (codepoint >> 18)); else if (codepoint >= 0x800) diff --git a/backends/functional/Makefile.inc b/backends/functional/Makefile.inc new file mode 100644 index 00000000000..16d1c054273 --- /dev/null +++ b/backends/functional/Makefile.inc @@ -0,0 +1,4 @@ +OBJS += backends/functional/cxx.o +OBJS += backends/functional/smtlib.o +OBJS += backends/functional/smtlib_rosette.o +OBJS += backends/functional/test_generic.o diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc new file mode 100644 index 00000000000..1f677120aaa --- /dev/null +++ b/backends/functional/cxx.cc @@ -0,0 +1,277 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/functional.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +const char *reserved_keywords[] = { + "alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit", + "atomic_noexcept","auto","bitand","bitor","bool","break","case", + "catch","char","char16_t","char32_t","char8_t","class","co_await", + "co_return","co_yield","compl","concept","const","const_cast","consteval", + "constexpr","constinit","continue","decltype","default","delete", + "do","double","dynamic_cast","else","enum","explicit","export", + "extern","false","float","for","friend","goto","if","inline", + "int","long","mutable","namespace","new","noexcept","not","not_eq", + "nullptr","operator","or","or_eq","private","protected","public", + "reflexpr","register","reinterpret_cast","requires","return","short", + "signed","sizeof","static","static_log_assert","static_cast","struct", + "switch","synchronized","template","this","thread_local","throw", + "true","try","typedef","typeid","typename","union","unsigned", + "using","virtual","void","volatile","wchar_t","while","xor","xor_eq", + nullptr +}; + +template struct CxxScope : public Functional::Scope { + CxxScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + this->reserve(*p); + } + bool is_character_legal(char c, int index) override { + return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || c == '_' || c == '$'); + } +}; + +struct CxxType { + Functional::Sort sort; + CxxType(Functional::Sort sort) : sort(sort) {} + std::string to_string() const { + if(sort.is_memory()) { + return stringf("Memory<%d, %d>", sort.addr_width(), sort.data_width()); + } else if(sort.is_signal()) { + return stringf("Signal<%d>", sort.width()); + } else { + log_error("unknown sort"); + } + } +}; + +using CxxWriter = Functional::Writer; + +struct CxxStruct { + std::string name; + dict types; + CxxScope scope; + CxxStruct(std::string name) : name(name) + { + scope.reserve("fn"); + scope.reserve("visit"); + } + void insert(IdString name, CxxType type) { + scope(name, name); + types.insert({name, type}); + } + void print(CxxWriter &f) { + f.print("\tstruct {} {{\n", name); + for (auto p : types) { + f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first, p.first)); + } + f.print("\n\t\ttemplate void visit(T &&fn) {{\n"); + for (auto p : types) { + f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first, p.first)); + } + f.print("\t\t}}\n"); + f.print("\t}};\n\n"); + }; + std::string operator[](IdString field) { + return scope(field, field); + } +}; + +std::string cxx_const(RTLIL::Const const &value) { + std::stringstream ss; + ss << "Signal<" << value.size() << ">(" << std::hex << std::showbase; + if(value.size() > 32) ss << "{"; + for(int i = 0; i < value.size(); i += 32) { + if(i > 0) ss << ", "; + ss << value.extract(i, 32).as_int(); + } + if(value.size() > 32) ss << "}"; + ss << ")"; + return ss.str(); +} + +template struct CxxPrintVisitor : public Functional::AbstractVisitor { + using Node = Functional::Node; + CxxWriter &f; + NodePrinter np; + CxxStruct &input_struct; + CxxStruct &state_struct; + CxxPrintVisitor(CxxWriter &f, NodePrinter np, CxxStruct &input_struct, CxxStruct &state_struct) : f(f), np(np), input_struct(input_struct), state_struct(state_struct) { } + template void print(const char *fmt, Args&&... args) { + f.print_with(np, fmt, std::forward(args)...); + } + void buf(Node, Node n) override { print("{}", n); } + void slice(Node, Node a, int offset, int out_width) override { print("{0}.slice<{2}>({1})", a, offset, out_width); } + void zero_extend(Node, Node a, int out_width) override { print("{}.zero_extend<{}>()", a, out_width); } + void sign_extend(Node, Node a, int out_width) override { print("{}.sign_extend<{}>()", a, out_width); } + void concat(Node, Node a, Node b) override { print("{}.concat({})", a, b); } + void add(Node, Node a, Node b) override { print("{} + {}", a, b); } + void sub(Node, Node a, Node b) override { print("{} - {}", a, b); } + void mul(Node, Node a, Node b) override { print("{} * {}", a, b); } + void unsigned_div(Node, Node a, Node b) override { print("{} / {}", a, b); } + void unsigned_mod(Node, Node a, Node b) override { print("{} % {}", a, b); } + void bitwise_and(Node, Node a, Node b) override { print("{} & {}", a, b); } + void bitwise_or(Node, Node a, Node b) override { print("{} | {}", a, b); } + void bitwise_xor(Node, Node a, Node b) override { print("{} ^ {}", a, b); } + void bitwise_not(Node, Node a) override { print("~{}", a); } + void unary_minus(Node, Node a) override { print("-{}", a); } + void reduce_and(Node, Node a) override { print("{}.all()", a); } + void reduce_or(Node, Node a) override { print("{}.any()", a); } + void reduce_xor(Node, Node a) override { print("{}.parity()", a); } + void equal(Node, Node a, Node b) override { print("{} == {}", a, b); } + void not_equal(Node, Node a, Node b) override { print("{} != {}", a, b); } + void signed_greater_than(Node, Node a, Node b) override { print("{}.signed_greater_than({})", a, b); } + void signed_greater_equal(Node, Node a, Node b) override { print("{}.signed_greater_equal({})", a, b); } + void unsigned_greater_than(Node, Node a, Node b) override { print("{} > {}", a, b); } + void unsigned_greater_equal(Node, Node a, Node b) override { print("{} >= {}", a, b); } + void logical_shift_left(Node, Node a, Node b) override { print("{} << {}", a, b); } + void logical_shift_right(Node, Node a, Node b) override { print("{} >> {}", a, b); } + void arithmetic_shift_right(Node, Node a, Node b) override { print("{}.arithmetic_shift_right({})", a, b); } + void mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); } + void constant(Node, RTLIL::Const const & value) override { print("{}", cxx_const(value)); } + void input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); print("input.{}", input_struct[name]); } + void state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); print("current_state.{}", state_struct[name]); } + void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); } + void memory_write(Node, Node mem, Node addr, Node data) override { print("{}.write({}, {})", mem, addr, data); } +}; + +bool equal_def(RTLIL::Const const &a, RTLIL::Const const &b) { + if(a.size() != b.size()) return false; + for(int i = 0; i < a.size(); i++) + if((a[i] == State::S1) != (b[i] == State::S1)) + return false; + return true; +} + +struct CxxModule { + Functional::IR ir; + CxxStruct input_struct, output_struct, state_struct; + std::string module_name; + + explicit CxxModule(Module *module) : + ir(Functional::IR::from_module(module)), + input_struct("Inputs"), + output_struct("Outputs"), + state_struct("State") + { + for (auto input : ir.inputs()) + input_struct.insert(input->name, input->sort); + for (auto output : ir.outputs()) + output_struct.insert(output->name, output->sort); + for (auto state : ir.states()) + state_struct.insert(state->name, state->sort); + module_name = CxxScope().unique_name(module->name); + } + void write_header(CxxWriter &f) { + f.print("#include \"sim.h\"\n\n"); + } + void write_struct_def(CxxWriter &f) { + f.print("struct {} {{\n", module_name); + input_struct.print(f); + output_struct.print(f); + state_struct.print(f); + f.print("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n"); + f.print("\tstatic void initialize(State &);\n"); + f.print("}};\n\n"); + } + void write_initial_def(CxxWriter &f) { + f.print("void {0}::initialize({0}::State &state)\n{{\n", module_name); + for (auto state : ir.states()) { + if (state->sort.is_signal()) + f.print("\tstate.{} = {};\n", state_struct[state->name], cxx_const(state->initial_value_signal())); + else if (state->sort.is_memory()) { + f.print("\t{{\n"); + f.print("\t\tstd::array, {}> mem;\n", state->sort.data_width(), 1<sort.addr_width()); + const auto &contents = state->initial_value_memory(); + f.print("\t\tmem.fill({});\n", cxx_const(contents.default_value())); + for(auto range : contents) + for(auto addr = range.base(); addr < range.limit(); addr++) + if(!equal_def(range[addr], contents.default_value())) + f.print("\t\tmem[{}] = {};\n", addr, cxx_const(range[addr])); + f.print("\t\tstate.{} = mem;\n", state_struct[state->name]); + f.print("\t}}\n"); + } + } + f.print("}}\n\n"); + } + void write_eval_def(CxxWriter &f) { + f.print("void {0}::eval({0}::Inputs const &input, {0}::Outputs &output, {0}::State const ¤t_state, {0}::State &next_state)\n{{\n", module_name); + CxxScope locals; + locals.reserve("input"); + locals.reserve("output"); + locals.reserve("current_state"); + locals.reserve("next_state"); + auto node_name = [&](Functional::Node n) { return locals(n.id(), n.name()); }; + CxxPrintVisitor printVisitor(f, node_name, input_struct, state_struct); + for (auto node : ir) { + f.print("\t{} {} = ", CxxType(node.sort()).to_string(), node_name(node)); + node.visit(printVisitor); + f.print(";\n"); + } + for (auto state : ir.states()) + f.print("\tnext_state.{} = {};\n", state_struct[state->name], node_name(state->next_value())); + for (auto output : ir.outputs()) + f.print("\toutput.{} = {};\n", output_struct[output->name], node_name(output->value())); + f.print("}}\n\n"); + } +}; + +struct FunctionalCxxBackend : public Backend +{ + FunctionalCxxBackend() : Backend("functional_cxx", "convert design to C++ using the functional backend") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log("TODO: add help message\n"); + log("\n"); + } + + void printCxx(std::ostream &stream, std::string, Module *module) + { + CxxWriter f(stream); + CxxModule mod(module); + mod.write_header(f); + mod.write_struct_def(f); + mod.write_eval_def(f); + mod.write_initial_def(f); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Functional C++ backend.\n"); + + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); + + for (auto module : design->selected_modules()) { + log("Dumping module `%s'.\n", module->name.c_str()); + printCxx(*f, filename, module); + } + } +} FunctionalCxxBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h new file mode 100644 index 00000000000..941f067b930 --- /dev/null +++ b/backends/functional/cxx_runtime/sim.h @@ -0,0 +1,418 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef SIM_H +#define SIM_H + +#include +#include +#include +#include +#include + +template +class Signal { + template friend class Signal; + std::array _bits; +public: + Signal() { } + Signal(uint32_t val) + { + for(size_t i = 0; i < n; i++) + if(i < 32) + _bits[i] = val & (1< vals) + { + size_t k, i; + + k = 0; + for (auto val : vals) { + for(i = 0; i < 32; i++) + if(i + k < n) + _bits[i + k] = val & (1< + static Signal from_array(T vals) + { + size_t k, i; + Signal ret; + + k = 0; + for (auto val : vals) { + for(i = 0; i < 32; i++) + if(i + k < n) + ret._bits[i + k] = val & (1< ret; + for(size_t i = 0; i < n; i++) + if(i < 32) + ret._bits[i] = val & (1< ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = b; + return ret; + } + + int size() const { return n; } + bool operator[](int i) const { assert(n >= 0 && i < n); return _bits[i]; } + + template + Signal slice(size_t offset) const + { + Signal ret; + + assert(offset + m <= n); + std::copy(_bits.begin() + offset, _bits.begin() + offset + m, ret._bits.begin()); + return ret; + } + + bool any() const + { + for(int i = 0; i < n; i++) + if(_bits[i]) + return true; + return false; + } + + bool all() const + { + for(int i = 0; i < n; i++) + if(!_bits[i]) + return false; + return true; + } + + bool parity() const + { + bool result = false; + for(int i = 0; i < n; i++) + result ^= _bits[i]; + return result; + } + + bool sign() const { return _bits[n-1]; } + + template + T as_numeric() const + { + T ret = 0; + for(size_t i = 0; i < std::min(sizeof(T) * 8, n); i++) + if(_bits[i]) + ret |= ((T)1)< + T as_numeric_clamped() const + { + for(size_t i = sizeof(T) * 8; i < n; i++) + if(_bits[i]) + return ~((T)0); + return as_numeric(); + } + + uint32_t as_int() const { return as_numeric(); } + +private: + std::string as_string_p2(int b) const { + std::string ret; + for(int i = (n - 1) - (n - 1) % b; i >= 0; i -= b) + ret += "0123456789abcdef"[(*this >> Signal<32>(i)).as_int() & ((1< t = *this; + Signal b = 10; + do{ + ret += (char)('0' + (t % b).as_int()); + t = t / b; + }while(t.any()); + std::reverse(ret.begin(), ret.end()); + return ret; + } +public: + std::string as_string(int base = 16, bool showbase = true) const { + std::string ret; + if(showbase) { + ret += std::to_string(n); + switch(base) { + case 2: ret += "'b"; break; + case 8: ret += "'o"; break; + case 10: ret += "'d"; break; + case 16: ret += "'h"; break; + default: assert(0); + } + } + switch(base) { + case 2: return ret + as_string_p2(1); + case 8: return ret + as_string_p2(3); + case 10: return ret + as_string_b10(); + case 16: return ret + as_string_p2(4); + default: assert(0); + } + } + friend std::ostream &operator << (std::ostream &os, Signal const &s) { return os << s.as_string(); } + + Signal operator ~() const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = !_bits[i]; + return ret; + } + + Signal operator -() const + { + Signal ret; + int x = 1; + for(size_t i = 0; i < n; i++) { + x += (int)!_bits[i]; + ret._bits[i] = (x & 1) != 0; + x >>= 1; + } + return ret; + } + + Signal operator +(Signal const &b) const + { + Signal ret; + int x = 0; + for(size_t i = 0; i < n; i++){ + x += (int)_bits[i] + (int)b._bits[i]; + ret._bits[i] = x & 1; + x >>= 1; + } + return ret; + } + + Signal operator -(Signal const &b) const + { + Signal ret; + int x = 1; + for(size_t i = 0; i < n; i++){ + x += (int)_bits[i] + (int)!b._bits[i]; + ret._bits[i] = x & 1; + x >>= 1; + } + return ret; + } + + Signal operator *(Signal const &b) const + { + Signal ret; + int x = 0; + for(size_t i = 0; i < n; i++){ + for(size_t j = 0; j <= i; j++) + x += (int)_bits[j] & (int)b._bits[i-j]; + ret._bits[i] = x & 1; + x >>= 1; + } + return ret; + } + +private: + Signal divmod(Signal const &b, bool modulo) const + { + if(!b.any()) return 0; + Signal q = 0; + Signal r = 0; + for(size_t i = n; i-- != 0; ){ + r = r << Signal<1>(1); + r._bits[0] = _bits[i]; + if(r >= b){ + r = r - b; + q._bits[i] = true; + } + } + return modulo ? r : q; + } +public: + + Signal operator /(Signal const &b) const { return divmod(b, false); } + Signal operator %(Signal const &b) const { return divmod(b, true); } + + bool operator ==(Signal const &b) const + { + for(size_t i = 0; i < n; i++) + if(_bits[i] != b._bits[i]) + return false; + return true; + } + + bool operator >=(Signal const &b) const + { + for(size_t i = n; i-- != 0; ) + if(_bits[i] != b._bits[i]) + return _bits[i]; + return true; + } + + bool operator >(Signal const &b) const + { + for(size_t i = n; i-- != 0; ) + if(_bits[i] != b._bits[i]) + return _bits[i]; + return false; + } + + bool operator !=(Signal const &b) const { return !(*this == b); } + bool operator <=(Signal const &b) const { return b <= *this; } + bool operator <(Signal const &b) const { return b < *this; } + + bool signed_greater_than(Signal const &b) const + { + if(_bits[n-1] != b._bits[n-1]) + return b._bits[n-1]; + return *this > b; + } + + bool signed_greater_equal(Signal const &b) const + { + if(_bits[n-1] != b._bits[n-1]) + return b._bits[n-1]; + return *this >= b; + } + + Signal operator &(Signal const &b) const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = _bits[i] && b._bits[i]; + return ret; + } + + Signal operator |(Signal const &b) const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = _bits[i] || b._bits[i]; + return ret; + } + + Signal operator ^(Signal const &b) const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = _bits[i] != b._bits[i]; + return ret; + } + + template + Signal operator <<(Signal const &b) const + { + Signal ret = 0; + size_t amount = b.template as_numeric_clamped(); + if(amount < n) + std::copy(_bits.begin(), _bits.begin() + (n - amount), ret._bits.begin() + amount); + return ret; + } + + template + Signal operator >>(Signal const &b) const + { + Signal ret = 0; + size_t amount = b.template as_numeric_clamped(); + if(amount < n) + std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin()); + return ret; + } + + template + Signal arithmetic_shift_right(Signal const &b) const + { + Signal ret = Signal::repeat(sign()); + size_t amount = b.template as_numeric_clamped(); + if(amount < n) + std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin()); + return ret; + } + + template + Signal concat(Signal const& b) const + { + Signal ret; + std::copy(_bits.begin(), _bits.end(), ret._bits.begin()); + std::copy(b._bits.begin(), b._bits.end(), ret._bits.begin() + n); + return ret; + } + + template + Signal zero_extend() const + { + assert(m >= n); + Signal ret = 0; + std::copy(_bits.begin(), _bits.end(), ret._bits.begin()); + return ret; + } + + template + Signal sign_extend() const + { + assert(m >= n); + Signal ret = Signal::repeat(sign()); + std::copy(_bits.begin(), _bits.end(), ret._bits.begin()); + return ret; + } +}; + +template +class Memory { + std::array, 1< _contents; +public: + Memory() {} + Memory(std::array, 1< const &contents) : _contents(contents) {} + Signal read(Signal addr) const + { + return _contents[addr.template as_numeric()]; + } + Memory write(Signal addr, Signal data) const + { + Memory ret = *this; + ret._contents[addr.template as_numeric()] = data; + return ret; + } +}; + +#endif diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc new file mode 100644 index 00000000000..3eacf407cf2 --- /dev/null +++ b/backends/functional/smtlib.cc @@ -0,0 +1,295 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/functional.h" +#include "kernel/yosys.h" +#include "kernel/sexpr.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +using SExprUtil::list; + +const char *reserved_keywords[] = { + // reserved keywords from the smtlib spec + "BINARY", "DECIMAL", "HEXADECIMAL", "NUMERAL", "STRING", "_", "!", "as", "let", "exists", "forall", "match", "par", + "assert", "check-sat", "check-sat-assuming", "declare-const", "declare-datatype", "declare-datatypes", + "declare-fun", "declare-sort", "define-fun", "define-fun-rec", "define-funs-rec", "define-sort", + "exit", "get-assertions", "symbol", "sort", "get-assignment", "get-info", "get-model", + "get-option", "get-proof", "get-unsat-assumptions", "get-unsat-core", "get-value", + "pop", "push", "reset", "reset-assertions", "set-info", "set-logic", "set-option", + + // reserved for our own purposes + "pair", "Pair", "first", "second", + "inputs", "state", + nullptr +}; + +struct SmtScope : public Functional::Scope { + SmtScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + reserve(*p); + } + bool is_character_legal(char c, int index) override { + return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("~!@$%^&*_-+=<>.?/", c)); + } +}; + +struct SmtSort { + Functional::Sort sort; + SmtSort(Functional::Sort sort) : sort(sort) {} + SExpr to_sexpr() const { + if(sort.is_memory()) { + return list("Array", list("_", "BitVec", sort.addr_width()), list("_", "BitVec", sort.data_width())); + } else if(sort.is_signal()) { + return list("_", "BitVec", sort.width()); + } else { + log_error("unknown sort"); + } + } +}; + +class SmtStruct { + struct Field { + SmtSort sort; + std::string accessor; + }; + idict field_names; + vector fields; + SmtScope &scope; +public: + std::string name; + SmtStruct(std::string name, SmtScope &scope) : scope(scope), name(name) {} + void insert(IdString field_name, SmtSort sort) { + field_names(field_name); + auto accessor = scope.unique_name("\\" + name + "_" + RTLIL::unescape_id(field_name)); + fields.emplace_back(Field{sort, accessor}); + } + void write_definition(SExprWriter &w) { + w.open(list("declare-datatype", name)); + w.open(list()); + w.open(list(name)); + for(const auto &field : fields) + w << list(field.accessor, field.sort.to_sexpr()); + w.close(3); + } + template void write_value(SExprWriter &w, Fn fn) { + if(field_names.empty()) { + // Zero-argument constructors in SMTLIB must not be called as functions. + w << name; + } else { + w.open(list(name)); + for(auto field_name : field_names) { + w << fn(field_name); + w.comment(RTLIL::unescape_id(field_name), true); + } + w.close(); + } + } + SExpr access(SExpr record, IdString name) { + size_t i = field_names.at(name); + return list(fields[i].accessor, std::move(record)); + } +}; + +std::string smt_const(RTLIL::Const const &c) { + std::string s = "#b"; + for(int i = c.size(); i-- > 0; ) + s += c[i] == State::S1 ? '1' : '0'; + return s; +} + +struct SmtPrintVisitor : public Functional::AbstractVisitor { + using Node = Functional::Node; + std::function n; + SmtStruct &input_struct; + SmtStruct &state_struct; + + SmtPrintVisitor(SmtStruct &input_struct, SmtStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {} + + SExpr from_bool(SExpr &&arg) { + return list("ite", std::move(arg), "#b1", "#b0"); + } + SExpr to_bool(SExpr &&arg) { + return list("=", std::move(arg), "#b1"); + } + SExpr extract(SExpr &&arg, int offset, int out_width = 1) { + return list(list("_", "extract", offset + out_width - 1, offset), std::move(arg)); + } + + SExpr buf(Node, Node a) override { return n(a); } + SExpr slice(Node, Node a, int offset, int out_width) override { return extract(n(a), offset, out_width); } + SExpr zero_extend(Node, Node a, int out_width) override { return list(list("_", "zero_extend", out_width - a.width()), n(a)); } + SExpr sign_extend(Node, Node a, int out_width) override { return list(list("_", "sign_extend", out_width - a.width()), n(a)); } + SExpr concat(Node, Node a, Node b) override { return list("concat", n(b), n(a)); } + SExpr add(Node, Node a, Node b) override { return list("bvadd", n(a), n(b)); } + SExpr sub(Node, Node a, Node b) override { return list("bvsub", n(a), n(b)); } + SExpr mul(Node, Node a, Node b) override { return list("bvmul", n(a), n(b)); } + SExpr unsigned_div(Node, Node a, Node b) override { return list("bvudiv", n(a), n(b)); } + SExpr unsigned_mod(Node, Node a, Node b) override { return list("bvurem", n(a), n(b)); } + SExpr bitwise_and(Node, Node a, Node b) override { return list("bvand", n(a), n(b)); } + SExpr bitwise_or(Node, Node a, Node b) override { return list("bvor", n(a), n(b)); } + SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); } + SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); } + SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); } + SExpr reduce_and(Node, Node a) override { return from_bool(list("=", n(a), smt_const(RTLIL::Const(State::S1, a.width())))); } + SExpr reduce_or(Node, Node a) override { return from_bool(list("distinct", n(a), smt_const(RTLIL::Const(State::S0, a.width())))); } + SExpr reduce_xor(Node, Node a) override { + vector s { "bvxor" }; + for(int i = 0; i < a.width(); i++) + s.push_back(extract(n(a), i)); + return s; + } + SExpr equal(Node, Node a, Node b) override { return from_bool(list("=", n(a), n(b))); } + SExpr not_equal(Node, Node a, Node b) override { return from_bool(list("distinct", n(a), n(b))); } + SExpr signed_greater_than(Node, Node a, Node b) override { return from_bool(list("bvsgt", n(a), n(b))); } + SExpr signed_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvsge", n(a), n(b))); } + SExpr unsigned_greater_than(Node, Node a, Node b) override { return from_bool(list("bvugt", n(a), n(b))); } + SExpr unsigned_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvuge", n(a), n(b))); } + + SExpr extend(SExpr &&a, int in_width, int out_width) { + if(in_width < out_width) + return list(list("_", "zero_extend", out_width - in_width), std::move(a)); + else + return std::move(a); + } + SExpr logical_shift_left(Node, Node a, Node b) override { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); } + SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } + SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } + SExpr mux(Node, Node a, Node b, Node s) override { return list("ite", to_bool(n(s)), n(b), n(a)); } + SExpr constant(Node, RTLIL::Const const &value) override { return smt_const(value); } + SExpr memory_read(Node, Node mem, Node addr) override { return list("select", n(mem), n(addr)); } + SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("store", n(mem), n(addr), n(data)); } + + SExpr input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); return input_struct.access("inputs", name); } + SExpr state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); return state_struct.access("state", name); } +}; + +struct SmtModule { + Functional::IR ir; + SmtScope scope; + std::string name; + + SmtStruct input_struct; + SmtStruct output_struct; + SmtStruct state_struct; + + SmtModule(Module *module) + : ir(Functional::IR::from_module(module)) + , scope() + , name(scope.unique_name(module->name)) + , input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope) + , output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope) + , state_struct(scope.unique_name(module->name.str() + "_State"), scope) + { + scope.reserve(name + "-initial"); + for (auto input : ir.inputs()) + input_struct.insert(input->name, input->sort); + for (auto output : ir.outputs()) + output_struct.insert(output->name, output->sort); + for (auto state : ir.states()) + state_struct.insert(state->name, state->sort); + } + + void write_eval(SExprWriter &w) + { + w.push(); + w.open(list("define-fun", name, + list(list("inputs", input_struct.name), + list("state", state_struct.name)), + list("Pair", output_struct.name, state_struct.name))); + auto inlined = [&](Functional::Node n) { + return n.fn() == Functional::Fn::constant; + }; + SmtPrintVisitor visitor(input_struct, state_struct); + auto node_to_sexpr = [&](Functional::Node n) -> SExpr { + if(inlined(n)) + return n.visit(visitor); + else + return scope(n.id(), n.name()); + }; + visitor.n = node_to_sexpr; + for(auto n : ir) + if(!inlined(n)) { + w.open(list("let", list(list(node_to_sexpr(n), n.visit(visitor)))), false); + w.comment(SmtSort(n.sort()).to_sexpr().to_string(), true); + } + w.open(list("pair")); + 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) + { + std::string initial = name + "-initial"; + w << list("declare-const", initial, state_struct.name); + for (auto state : ir.states()) { + if(state->sort.is_signal()) + w << list("assert", list("=", state_struct.access(initial, state->name), smt_const(state->initial_value_signal()))); + else if(state->sort.is_memory()) { + const auto &contents = state->initial_value_memory(); + for(int i = 0; i < 1<sort.addr_width(); i++) { + auto addr = smt_const(RTLIL::Const(i, state->sort.addr_width())); + w << list("assert", list("=", list("select", state_struct.access(initial, state->name), addr), smt_const(contents[i]))); + } + } + } + } + + void write(std::ostream &out) + { + SExprWriter w(out); + + input_struct.write_definition(w); + output_struct.write_definition(w); + state_struct.write_definition(w); + + w << list("declare-datatypes", + list(list("Pair", 2)), + list(list("par", list("X", "Y"), list(list("pair", list("first", "X"), list("second", "Y")))))); + + write_eval(w); + write_initial(w); + } +}; + +struct FunctionalSmtBackend : public Backend { + FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {} + + void help() override { log("\nFunctional SMT Backend.\n\n"); } + + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Functional SMT 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()); + SmtModule smt(module); + smt.write(*f); + } + } +} FunctionalSmtBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc new file mode 100644 index 00000000000..ea14da85428 --- /dev/null +++ b/backends/functional/smtlib_rosette.cc @@ -0,0 +1,308 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/functional.h" +#include "kernel/yosys.h" +#include "kernel/sexpr.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +using SExprUtil::list; + +const char *reserved_keywords[] = { + // reserved keywords from the racket spec + "struct", "lambda", "values", "extract", "concat", "bv", "let", "define", "cons", "list", "read", "write", + "stream", "error", "raise", "exit", "for", "begin", "when", "unless", "module", "require", "provide", "apply", + "if", "cond", "even", "odd", "any", "and", "or", "match", "command-line", "ffi-lib", "thread", "kill", "sync", + "future", "touch", "subprocess", "make-custodian", "custodian-shutdown-all", "current-custodian", "make", "tcp", + "connect", "prepare", "malloc", "free", "_fun", "_cprocedure", "build", "path", "file", "peek", "bytes", + "flush", "with", "lexer", "parser", "syntax", "interface", "send", "make-object", "new", "instantiate", + "define-generics", "set", + + // reserved for our own purposes + "inputs", "state", "name", + nullptr +}; + +struct SmtrScope : public Functional::Scope { + SmtrScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + reserve(*p); + } + bool is_character_legal(char c, int index) override { + return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("@$%^&_+=.", c)); + } +}; + +struct SmtrSort { + Functional::Sort sort; + SmtrSort(Functional::Sort sort) : sort(sort) {} + SExpr to_sexpr() const { + if(sort.is_memory()) { + return list("list", list("bitvector", sort.addr_width()), list("bitvector", sort.data_width())); + } else if(sort.is_signal()) { + return list("bitvector", sort.width()); + } else { + log_error("unknown sort"); + } + } +}; + +class SmtrStruct { + struct Field { + SmtrSort sort; + std::string accessor; + std::string name; + }; + idict field_names; + vector fields; + SmtrScope &global_scope; + SmtrScope local_scope; +public: + std::string name; + SmtrStruct(std::string name, SmtrScope &scope) : global_scope(scope), local_scope(), name(name) {} + void insert(IdString field_name, SmtrSort sort) { + field_names(field_name); + auto base_name = local_scope.unique_name(field_name); + auto accessor = name + "-" + base_name; + global_scope.reserve(accessor); + fields.emplace_back(Field{sort, accessor, base_name}); + } + void write_definition(SExprWriter &w) { + vector field_list; + for(const auto &field : fields) { + field_list.emplace_back(field.name); + } + w.push(); + w.open(list("struct", name, field_list, "#:transparent")); + if (field_names.size()) { + for (const auto &field : fields) { + auto bv_type = field.sort.to_sexpr(); + w.comment(field.name + " " + bv_type.to_string()); + } + } + w.pop(); + } + template void write_value(SExprWriter &w, Fn fn) { + w.open(list(name)); + for(auto field_name : field_names) { + w << fn(field_name); + w.comment(RTLIL::unescape_id(field_name), true); + } + w.close(); + } + SExpr access(SExpr record, IdString name) { + size_t i = field_names.at(name); + return list(fields[i].accessor, std::move(record)); + } +}; + +std::string smt_const(RTLIL::Const const &c) { + std::string s = "#b"; + for(int i = c.size(); i-- > 0; ) + s += c[i] == State::S1 ? '1' : '0'; + return s; +} + +struct SmtrPrintVisitor : public Functional::AbstractVisitor { + using Node = Functional::Node; + std::function n; + SmtrStruct &input_struct; + SmtrStruct &state_struct; + + SmtrPrintVisitor(SmtrStruct &input_struct, SmtrStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {} + + SExpr from_bool(SExpr &&arg) { + return list("bool->bitvector", std::move(arg)); + } + SExpr to_bool(SExpr &&arg) { + return list("bitvector->bool", std::move(arg)); + } + SExpr to_list(SExpr &&arg) { + return list("bitvector->bits", std::move(arg)); + } + + SExpr buf(Node, Node a) override { return n(a); } + SExpr slice(Node, Node a, int offset, int out_width) override { return list("extract", offset + out_width - 1, offset, n(a)); } + SExpr zero_extend(Node, Node a, int out_width) override { return list("zero-extend", n(a), list("bitvector", out_width)); } + SExpr sign_extend(Node, Node a, int out_width) override { return list("sign-extend", n(a), list("bitvector", out_width)); } + SExpr concat(Node, Node a, Node b) override { return list("concat", n(b), n(a)); } + SExpr add(Node, Node a, Node b) override { return list("bvadd", n(a), n(b)); } + SExpr sub(Node, Node a, Node b) override { return list("bvsub", n(a), n(b)); } + SExpr mul(Node, Node a, Node b) override { return list("bvmul", n(a), n(b)); } + SExpr unsigned_div(Node, Node a, Node b) override { return list("bvudiv", n(a), n(b)); } + SExpr unsigned_mod(Node, Node a, Node b) override { return list("bvurem", n(a), n(b)); } + SExpr bitwise_and(Node, Node a, Node b) override { return list("bvand", n(a), n(b)); } + SExpr bitwise_or(Node, Node a, Node b) override { return list("bvor", n(a), n(b)); } + SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); } + SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); } + SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); } + SExpr reduce_and(Node, Node a) override { return list("apply", "bvand", to_list(n(a))); } + SExpr reduce_or(Node, Node a) override { return list("apply", "bvor", to_list(n(a))); } + SExpr reduce_xor(Node, Node a) override { return list("apply", "bvxor", to_list(n(a))); } + SExpr equal(Node, Node a, Node b) override { return from_bool(list("bveq", n(a), n(b))); } + SExpr not_equal(Node, Node a, Node b) override { return from_bool(list("not", list("bveq", n(a), n(b)))); } + SExpr signed_greater_than(Node, Node a, Node b) override { return from_bool(list("bvsgt", n(a), n(b))); } + SExpr signed_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvsge", n(a), n(b))); } + SExpr unsigned_greater_than(Node, Node a, Node b) override { return from_bool(list("bvugt", n(a), n(b))); } + SExpr unsigned_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvuge", n(a), n(b))); } + + SExpr extend(SExpr &&a, int in_width, int out_width) { + if(in_width < out_width) + return list("zero-extend", std::move(a), list("bitvector", out_width)); + else + return std::move(a); + } + SExpr logical_shift_left(Node, Node a, Node b) override { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); } + SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } + SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } + SExpr mux(Node, Node a, Node b, Node s) override { return list("if", to_bool(n(s)), n(b), n(a)); } + SExpr constant(Node, RTLIL::Const const& value) override { return list("bv", smt_const(value), value.size()); } + SExpr memory_read(Node, Node mem, Node addr) override { return list("list-ref-bv", n(mem), n(addr)); } + SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("list-set-bv", n(mem), n(addr), n(data)); } + + SExpr input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); return input_struct.access("inputs", name); } + SExpr state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); return state_struct.access("state", name); } +}; + +struct SmtrModule { + Functional::IR ir; + SmtrScope scope; + std::string name; + + SmtrStruct input_struct; + SmtrStruct output_struct; + SmtrStruct state_struct; + + SmtrModule(Module *module) + : ir(Functional::IR::from_module(module)) + , scope() + , name(scope.unique_name(module->name)) + , input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope) + , output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope) + , state_struct(scope.unique_name(module->name.str() + "_State"), scope) + { + scope.reserve(name + "_initial"); + for (auto input : ir.inputs()) + input_struct.insert(input->name, input->sort); + for (auto output : ir.outputs()) + output_struct.insert(output->name, output->sort); + for (auto state : ir.states()) + 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); + + w.push(); + w.open(list("define", list(name, "inputs", "state"))); + auto inlined = [&](Functional::Node n) { + return n.fn() == Functional::Fn::constant; + }; + SmtrPrintVisitor visitor(input_struct, state_struct); + auto node_to_sexpr = [&](Functional::Node n) -> SExpr { + if(inlined(n)) + return n.visit(visitor); + else + return scope(n.id(), n.name()); + }; + visitor.n = node_to_sexpr; + for(auto n : ir) + if(!inlined(n)) { + w.open(list("let", list(list(node_to_sexpr(n), n.visit(visitor)))), false); + w.comment(SmtrSort(n.sort()).to_sexpr().to_string(), true); + } + w.open(list("cons")); + 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(); + + w.push(); + auto initial = name + "_initial"; + w.open(list("define", initial)); + w.open(list(state_struct.name)); + for (auto state : ir.states()) { + if (state->sort.is_signal()) + w << list("bv", smt_const(state->initial_value_signal()), state->sort.width()); + else if (state->sort.is_memory()) { + const auto &contents = state->initial_value_memory(); + w.open(list("list")); + for(int i = 0; i < 1<sort.addr_width(); i++) { + w << list("bv", smt_const(contents[i]), state->sort.data_width()); + } + w.close(); + } + } + w.pop(); + } +}; + +struct FunctionalSmtrBackend : public Backend { + FunctionalSmtrBackend() : Backend("functional_rosette", "Generate Rosette compatible Racket from Functional IR") {} + + void help() override { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" write_functional_rosette [options] [selection] [filename]\n"); + log("\n"); + log("Functional Rosette Backend.\n"); + log("\n"); + log(" -provides\n"); + log(" include 'provide' statement(s) for loading output as a module\n"); + log("\n"); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + auto provides = false; + + log_header(design, "Executing Functional Rosette Backend.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-provides") + provides = true; + else + break; + } + extra_args(f, filename, args, argidx); + + *f << "#lang rosette/safe\n"; + if (provides) { + *f << "(provide (all-defined-out))\n"; + } + + for (auto module : design->selected_modules()) { + log("Processing module `%s`.\n", module->name.c_str()); + SmtrModule smtr(module); + smtr.write(*f); + } + } +} FunctionalSmtrBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/functional/test_generic.cc b/backends/functional/test_generic.cc new file mode 100644 index 00000000000..dc235d79a92 --- /dev/null +++ b/backends/functional/test_generic.cc @@ -0,0 +1,156 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/functional.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct MemContentsTest { + int addr_width, data_width; + MemContents state; + using addr_t = MemContents::addr_t; + std::map reference; + MemContentsTest(int addr_width, int data_width) : addr_width(addr_width), data_width(data_width), state(addr_width, data_width, RTLIL::Const(State::S0, data_width)) {} + void check() { + state.check(); + for(auto addr = 0; addr < (1<second != state[addr]) goto error; + } else { + if(state.count_range(addr, addr + 1) != 0) goto error; + } + } + return; + error: + printf("FAIL\n"); + int digits = (data_width + 3) / 4; + + for(auto addr = 0; addr < (1<second : state.default_value(); + std::string ref_string = stringf("%.*x", digits, ref_value.as_int()); + bool sta_def = state.count_range(addr, addr + 1) == 1; + RTLIL::Const sta_value = state[addr]; + std::string sta_string = stringf("%.*x", digits, sta_value.as_int()); + if(ref_def && sta_def) { + if(ref_value == sta_value) printf("%s%s", ref_string.c_str(), string(digits, ' ').c_str()); + else printf("%s%s", ref_string.c_str(), sta_string.c_str()); + } else if(ref_def) { + printf("%s%s", ref_string.c_str(), string(digits, 'M').c_str()); + } else if(sta_def) { + printf("%s%s", sta_string.c_str(), string(digits, 'X').c_str()); + } else { + printf("%s", string(2*digits, ' ').c_str()); + } + printf(" "); + if(addr % 8 == 7) printf("\n"); + } + printf("\n"); + //log_abort(); + } + void clear_range(addr_t begin_addr, addr_t end_addr) { + for(auto addr = begin_addr; addr != end_addr; addr++) + reference.erase(addr); + state.clear_range(begin_addr, end_addr); + check(); + } + void insert_concatenated(addr_t addr, RTLIL::Const const &values) { + addr_t words = ((addr_t) values.size() + data_width - 1) / data_width; + for(addr_t i = 0; i < words; i++) { + reference.erase(addr + i); + reference.emplace(addr + i, values.extract(i * data_width, data_width)); + } + state.insert_concatenated(addr, values); + check(); + } + template void run(Rnd &rnd, int n) { + std::uniform_int_distribution addr_dist(0, (1< length_dist(10); + std::uniform_int_distribution data_dist(0, ((uint64_t)1< 0) { + addr_t low = addr_dist(rnd); + //addr_t length = std::min((1< high) std::swap(low, high); + if((rnd() & 7) == 0) { + log_debug("clear %.2x to %.2x\n", (int)low, (int)high); + clear_range(low, high + 1); + } else { + log_debug("insert %.2x to %.2x\n", (int)low, (int)high); + RTLIL::Const values; + for(addr_t addr = low; addr <= high; addr++) { + RTLIL::Const word(data_dist(rnd), data_width); + values.bits.insert(values.bits.end(), word.bits.begin(), word.bits.end()); + } + insert_concatenated(low, values); + } + } + } + +}; + +struct FunctionalTestGeneric : public Pass +{ + FunctionalTestGeneric() : Pass("test_generic", "test the generic compute graph") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log("TODO: add help message\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Test Generic.\n"); + + size_t argidx = 1; + extra_args(args, argidx, design); +/* + MemContentsTest test(8, 16); + + std::random_device seed_dev; + std::mt19937 rnd(23); //seed_dev()); + test.run(rnd, 1000); +*/ + + for (auto module : design->selected_modules()) { + log("Dumping module `%s'.\n", module->name.c_str()); + auto fir = Functional::IR::from_module(module); + for(auto node : fir) + std::cout << RTLIL::unescape_id(node.name()) << " = " << node.to_string([](auto n) { return RTLIL::unescape_id(n.name()); }) << "\n"; + for(auto output : fir.all_outputs()) + std::cout << RTLIL::unescape_id(output->kind) << " " << RTLIL::unescape_id(output->name) << " = " << RTLIL::unescape_id(output->value().name()) << "\n"; + for(auto state : fir.all_states()) + std::cout << RTLIL::unescape_id(state->kind) << " " << RTLIL::unescape_id(state->name) << " = " << RTLIL::unescape_id(state->next_value().name()) << "\n"; + } + } +} FunctionalCxxBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/rtlil/rtlil_backend.cc b/backends/rtlil/rtlil_backend.cc index 032954d8cd6..434992cc7b3 100644 --- a/backends/rtlil/rtlil_backend.cc +++ b/backends/rtlil/rtlil_backend.cc @@ -125,6 +125,10 @@ void RTLIL_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL:: dump_const(f, it.second); f << stringf("\n"); } + if (wire->driverCell_) { + f << stringf("%s" "# driver %s %s\n", indent.c_str(), + wire->driverCell()->name.c_str(), wire->driverPort().c_str()); + } f << stringf("%s" "wire ", indent.c_str()); if (wire->width != 1) f << stringf("width %d ", wire->width); diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py index c3bdcebbe96..d17bad3fb96 100644 --- a/backends/smt2/smtbmc.py +++ b/backends/smt2/smtbmc.py @@ -1454,6 +1454,10 @@ def write_trace(steps_start, steps_stop, index, allregs=False): if outywfile is not None: write_yw_trace(steps, index, allregs) +def escape_path_segment(segment): + if "." in segment: + return f"\\{segment} " + return segment def print_failed_asserts_worker(mod, state, path, extrainfo, infomap, infokey=()): assert mod in smt.modinfo @@ -1464,7 +1468,8 @@ def print_failed_asserts_worker(mod, state, path, extrainfo, infomap, infokey=() for cellname, celltype in smt.modinfo[mod].cells.items(): cell_infokey = (mod, cellname, infokey) - if print_failed_asserts_worker(celltype, "(|%s_h %s| %s)" % (mod, cellname, state), path + "." + cellname, extrainfo, infomap, cell_infokey): + cell_path = path + "." + escape_path_segment(cellname) + if print_failed_asserts_worker(celltype, "(|%s_h %s| %s)" % (mod, cellname, state), cell_path, extrainfo, infomap, cell_infokey): found_failed_assert = True for assertfun, assertinfo in smt.modinfo[mod].asserts.items(): @@ -1497,7 +1502,7 @@ def print_anyconsts_worker(mod, state, path): assert mod in smt.modinfo for cellname, celltype in smt.modinfo[mod].cells.items(): - print_anyconsts_worker(celltype, "(|%s_h %s| %s)" % (mod, cellname, state), path + "." + cellname) + print_anyconsts_worker(celltype, "(|%s_h %s| %s)" % (mod, cellname, state), path + "." + escape_path_segment(cellname)) for fun, info in smt.modinfo[mod].anyconsts.items(): if info[1] is None: @@ -1517,18 +1522,21 @@ def print_anyconsts(state): print_anyconsts_worker(topmod, "s%d" % state, topmod) -def get_cover_list(mod, base): +def get_cover_list(mod, base, path=None): + path = path or mod assert mod in smt.modinfo cover_expr = list() + # A tuple of path and cell name cover_desc = list() for expr, desc in smt.modinfo[mod].covers.items(): cover_expr.append("(ite (|%s| %s) #b1 #b0)" % (expr, base)) - cover_desc.append(desc) + cover_desc.append((path, desc)) for cell, submod in smt.modinfo[mod].cells.items(): - e, d = get_cover_list(submod, "(|%s_h %s| %s)" % (mod, cell, base)) + cell_path = path + "." + escape_path_segment(cell) + e, d = get_cover_list(submod, "(|%s_h %s| %s)" % (mod, cell, base), cell_path) cover_expr += e cover_desc += d @@ -1544,7 +1552,8 @@ def get_assert_map(mod, base, path, key_base=()): assert_map[(expr, key_base)] = ("(|%s| %s)" % (expr, base), path, desc) for cell, submod in smt.modinfo[mod].cells.items(): - assert_map.update(get_assert_map(submod, "(|%s_h %s| %s)" % (mod, cell, base), path + "." + cell, (mod, cell, key_base))) + cell_path = path + "." + escape_path_segment(cell) + assert_map.update(get_assert_map(submod, "(|%s_h %s| %s)" % (mod, cell, base), cell_path, (mod, cell, key_base))) return assert_map @@ -1903,7 +1912,9 @@ def report_tracked_assumptions(msg): new_cover_mask.append(cover_mask[i]) continue - print_msg("Reached cover statement at %s in step %d." % (cover_desc[i], step)) + path = cover_desc[i][0] + name = cover_desc[i][1] + print_msg("Reached cover statement in step %d at %s: %s" % (step, path, name)) new_cover_mask.append("0") cover_mask = "".join(new_cover_mask) @@ -1933,7 +1944,7 @@ def report_tracked_assumptions(msg): if "1" in cover_mask: for i in range(len(cover_mask)): if cover_mask[i] == "1": - print_msg("Unreached cover statement at %s." % cover_desc[i]) + print_msg("Unreached cover statement at %s: %s" % (cover_desc[i][0], cover_desc[i][1])) else: # not tempind, covermode active_assert_keys = get_assert_keys() diff --git a/docs/Makefile b/docs/Makefile index 8be970391a3..6dbf6f4902b 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,7 +2,7 @@ # # You can set these variables from the command line. -SPHINXOPTS = +SPHINXOPTS = -W --keep-going SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build @@ -238,7 +238,7 @@ Makefile-%: FORCE $(MAKE) -C $(@D) $(*F) CODE_EXAMPLES := $(wildcard source/code_examples/*/Makefile) -TEST_EXAMPLES := $(addsuffix -all,$(CODE_EXAMPLES)) +TEST_EXAMPLES := $(addsuffix -examples,$(CODE_EXAMPLES)) CLEAN_EXAMPLES := $(addsuffix -clean,$(CODE_EXAMPLES)) test-examples: $(TEST_EXAMPLES) clean-examples: $(CLEAN_EXAMPLES) diff --git a/docs/source/_images/Makefile b/docs/source/_images/Makefile index 26cc47284c3..cc00050f1bc 100644 --- a/docs/source/_images/Makefile +++ b/docs/source/_images/Makefile @@ -1,4 +1,4 @@ -all: examples all_tex tidy +all: examples all_tex # set a fake time in pdf generation to prevent unnecessary differences in output FAKETIME := TZ='Z' faketime -f '2022-01-01 00:00:00 x0,001' diff --git a/docs/source/code_examples/.gitignore b/docs/source/code_examples/.gitignore index b4a858a0134..0eca4be8061 100644 --- a/docs/source/code_examples/.gitignore +++ b/docs/source/code_examples/.gitignore @@ -1,2 +1,5 @@ *.dot *.pdf +*.out +*.log +*.stat diff --git a/docs/source/code_examples/extensions/Makefile b/docs/source/code_examples/extensions/Makefile index 8b5fa106af1..2e621d70bcb 100644 --- a/docs/source/code_examples/extensions/Makefile +++ b/docs/source/code_examples/extensions/Makefile @@ -2,9 +2,10 @@ PROGRAM_PREFIX := YOSYS ?= ../../../../$(PROGRAM_PREFIX)yosys -.PHONY: all dots -all: dots test0.log test1.log test2.log +.PHONY: all dots examples +all: dots examples dots: test1.dot +examples: test0.log test1.log test2.log CXXFLAGS=$(shell $(YOSYS)-config --cxxflags) DATDIR=$(shell $(YOSYS)-config --datdir) diff --git a/docs/source/code_examples/fifo/Makefile b/docs/source/code_examples/fifo/Makefile index 0a1186a62d4..4836dac06b1 100644 --- a/docs/source/code_examples/fifo/Makefile +++ b/docs/source/code_examples/fifo/Makefile @@ -10,8 +10,10 @@ MAPDOT_NAMES += rdata_map_ffs rdata_map_luts rdata_map_cells DOTS := $(addsuffix .dot,$(DOT_NAMES)) MAPDOTS := $(addsuffix .dot,$(MAPDOT_NAMES)) -all: dots fifo.out fifo.stat +.PHONY: all dots examples +all: dots examples dots: $(DOTS) $(MAPDOTS) +examples: fifo.out fifo.stat $(DOTS) fifo.out: fifo.v fifo.ys $(YOSYS) fifo.ys -l fifo.out -Q -T @@ -22,3 +24,4 @@ $(MAPDOTS) fifo.stat: fifo.v fifo_map.ys .PHONY: clean clean: rm -f *.dot + rm -f fifo.out fifo.stat diff --git a/docs/source/code_examples/fifo/fifo.out b/docs/source/code_examples/fifo/fifo.out deleted file mode 100644 index ac132ee6c3d..00000000000 --- a/docs/source/code_examples/fifo/fifo.out +++ /dev/null @@ -1,425 +0,0 @@ - --- Executing script file `fifo.ys' -- -$ yosys fifo.v - --- Parsing `fifo.v' using frontend ` -vlog2k' -- - -1. Executing Verilog-2005 frontend: fifo.v -Parsing Verilog input from `fifo.v' to AST representation. -Storing AST representation for module `$abstract\addr_gen'. -Storing AST representation for module `$abstract\fifo'. -Successfully finished Verilog frontend. -echo on - -yosys> hierarchy -top addr_gen - -2. Executing HIERARCHY pass (managing design hierarchy). - -3. Executing AST frontend in derive mode using pre-parsed AST for module `\addr_gen'. -Generating RTLIL representation for module `\addr_gen'. - -3.1. Analyzing design hierarchy.. -Top module: \addr_gen - -3.2. Analyzing design hierarchy.. -Top module: \addr_gen -Removing unused module `$abstract\fifo'. -Removing unused module `$abstract\addr_gen'. -Removed 2 unused modules. - -yosys> select -module addr_gen - -yosys [addr_gen]> select -list -addr_gen -addr_gen/$1\addr[7:0] -addr_gen/$add$fifo.v:19$3_Y -addr_gen/$eq$fifo.v:16$2_Y -addr_gen/$0\addr[7:0] -addr_gen/addr -addr_gen/rst -addr_gen/clk -addr_gen/en -addr_gen/$add$fifo.v:19$3 -addr_gen/$eq$fifo.v:16$2 -addr_gen/$proc$fifo.v:0$4 -addr_gen/$proc$fifo.v:12$1 - -yosys [addr_gen]> select t:* - -yosys [addr_gen]*> select -list -addr_gen/$add$fifo.v:19$3 -addr_gen/$eq$fifo.v:16$2 - -yosys [addr_gen]*> select -set new_cells % - -yosys [addr_gen]*> select -clear - -yosys> show -format dot -prefix addr_gen_show addr_gen - -4. Generating Graphviz representation of design. -Writing dot description to `addr_gen_show.dot'. -Dumping module addr_gen to page 1. - -yosys> show -format dot -prefix new_cells_show -notitle @new_cells - -5. Generating Graphviz representation of design. -Writing dot description to `new_cells_show.dot'. -Dumping selected parts of module addr_gen to page 1. - -yosys> show -color maroon3 @new_cells -color cornflowerblue p:* -notitle -format dot -prefix addr_gen_hier - -6. Generating Graphviz representation of design. -Writing dot description to `addr_gen_hier.dot'. -Dumping module addr_gen to page 1. - -yosys> proc -noopt - -7. Executing PROC pass (convert processes to netlists). - -yosys> proc_clean - -7.1. Executing PROC_CLEAN pass (remove empty switches from decision trees). -Cleaned up 0 empty switches. - -yosys> proc_rmdead - -7.2. Executing PROC_RMDEAD pass (remove dead branches from decision trees). -Marked 2 switch rules as full_case in process $proc$fifo.v:12$1 in module addr_gen. -Removed a total of 0 dead cases. - -yosys> proc_prune - -7.3. Executing PROC_PRUNE pass (remove redundant assignments in processes). -Removed 0 redundant assignments. -Promoted 1 assignment to connection. - -yosys> proc_init - -7.4. Executing PROC_INIT pass (extract init attributes). -Found init rule in `\addr_gen.$proc$fifo.v:0$4'. - Set init value: \addr = 8'00000000 - -yosys> proc_arst - -7.5. Executing PROC_ARST pass (detect async resets in processes). -Found async reset \rst in `\addr_gen.$proc$fifo.v:12$1'. - -yosys> proc_rom - -7.6. Executing PROC_ROM pass (convert switches to ROMs). -Converted 0 switches. - - -yosys> proc_mux - -7.7. Executing PROC_MUX pass (convert decision trees to multiplexers). -Creating decoders for process `\addr_gen.$proc$fifo.v:0$4'. -Creating decoders for process `\addr_gen.$proc$fifo.v:12$1'. - 1/1: $0\addr[7:0] - -yosys> proc_dlatch - -7.8. Executing PROC_DLATCH pass (convert process syncs to latches). - -yosys> proc_dff - -7.9. Executing PROC_DFF pass (convert process syncs to FFs). -Creating register for signal `\addr_gen.\addr' using process `\addr_gen.$proc$fifo.v:12$1'. - created $adff cell `$procdff$10' with positive edge clock and positive level reset. - -yosys> proc_memwr - -7.10. Executing PROC_MEMWR pass (convert process memory writes to cells). - -yosys> proc_clean - -7.11. Executing PROC_CLEAN pass (remove empty switches from decision trees). -Removing empty process `addr_gen.$proc$fifo.v:0$4'. -Found and cleaned up 2 empty switches in `\addr_gen.$proc$fifo.v:12$1'. -Removing empty process `addr_gen.$proc$fifo.v:12$1'. -Cleaned up 2 empty switches. - -yosys> select -set new_cells t:$mux t:*dff - -yosys> show -color maroon3 @new_cells -notitle -format dot -prefix addr_gen_proc - -8. Generating Graphviz representation of design. -Writing dot description to `addr_gen_proc.dot'. -Dumping module addr_gen to page 1. - -yosys> opt_expr - -9. Executing OPT_EXPR pass (perform const folding). -Optimizing module addr_gen. - -yosys> clean -Removed 0 unused cells and 4 unused wires. - -yosys> select -set new_cells t:$eq - -yosys> show -color cornflowerblue @new_cells -notitle -format dot -prefix addr_gen_clean - -10. Generating Graphviz representation of design. -Writing dot description to `addr_gen_clean.dot'. -Dumping module addr_gen to page 1. - -yosys> design -reset - -yosys> read_verilog fifo.v - -11. Executing Verilog-2005 frontend: fifo.v -Parsing Verilog input from `fifo.v' to AST representation. -Generating RTLIL representation for module `\addr_gen'. -Generating RTLIL representation for module `\fifo'. -Successfully finished Verilog frontend. - -yosys> hierarchy -check -top fifo - -12. Executing HIERARCHY pass (managing design hierarchy). - -12.1. Analyzing design hierarchy.. -Top module: \fifo -Used module: \addr_gen -Parameter \MAX_DATA = 256 - -12.2. Executing AST frontend in derive mode using pre-parsed AST for module `\addr_gen'. -Parameter \MAX_DATA = 256 -Generating RTLIL representation for module `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000'. -Parameter \MAX_DATA = 256 -Found cached RTLIL representation for module `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000'. - -12.3. Analyzing design hierarchy.. -Top module: \fifo -Used module: $paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000 - -12.4. Analyzing design hierarchy.. -Top module: \fifo -Used module: $paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000 -Removing unused module `\addr_gen'. -Removed 1 unused modules. - -yosys> proc - -13. Executing PROC pass (convert processes to netlists). - -yosys> proc_clean - -13.1. Executing PROC_CLEAN pass (remove empty switches from decision trees). -Cleaned up 0 empty switches. - -yosys> proc_rmdead - -13.2. Executing PROC_RMDEAD pass (remove dead branches from decision trees). -Marked 2 switch rules as full_case in process $proc$fifo.v:62$24 in module fifo. -Marked 1 switch rules as full_case in process $proc$fifo.v:36$16 in module fifo. -Marked 2 switch rules as full_case in process $proc$fifo.v:12$32 in module $paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000. -Removed a total of 0 dead cases. - -yosys> proc_prune - -13.3. Executing PROC_PRUNE pass (remove redundant assignments in processes). -Removed 0 redundant assignments. -Promoted 6 assignments to connections. - -yosys> proc_init - -13.4. Executing PROC_INIT pass (extract init attributes). -Found init rule in `\fifo.$proc$fifo.v:0$31'. - Set init value: \count = 9'000000000 -Found init rule in `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000.$proc$fifo.v:0$35'. - Set init value: \addr = 8'00000000 - -yosys> proc_arst - -13.5. Executing PROC_ARST pass (detect async resets in processes). -Found async reset \rst in `\fifo.$proc$fifo.v:62$24'. -Found async reset \rst in `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000.$proc$fifo.v:12$32'. - -yosys> proc_rom - -13.6. Executing PROC_ROM pass (convert switches to ROMs). -Converted 0 switches. - - -yosys> proc_mux - -13.7. Executing PROC_MUX pass (convert decision trees to multiplexers). -Creating decoders for process `\fifo.$proc$fifo.v:0$31'. -Creating decoders for process `\fifo.$proc$fifo.v:62$24'. - 1/1: $0\count[8:0] -Creating decoders for process `\fifo.$proc$fifo.v:36$16'. - 1/3: $1$memwr$\data$fifo.v:38$15_EN[7:0]$22 - 2/3: $1$memwr$\data$fifo.v:38$15_DATA[7:0]$21 - 3/3: $1$memwr$\data$fifo.v:38$15_ADDR[7:0]$20 -Creating decoders for process `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000.$proc$fifo.v:0$35'. -Creating decoders for process `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000.$proc$fifo.v:12$32'. - 1/1: $0\addr[7:0] - -yosys> proc_dlatch - -13.8. Executing PROC_DLATCH pass (convert process syncs to latches). - -yosys> proc_dff - -13.9. Executing PROC_DFF pass (convert process syncs to FFs). -Creating register for signal `\fifo.\count' using process `\fifo.$proc$fifo.v:62$24'. - created $adff cell `$procdff$55' with positive edge clock and positive level reset. -Creating register for signal `\fifo.\rdata' using process `\fifo.$proc$fifo.v:36$16'. - created $dff cell `$procdff$56' with positive edge clock. -Creating register for signal `\fifo.$memwr$\data$fifo.v:38$15_ADDR' using process `\fifo.$proc$fifo.v:36$16'. - created $dff cell `$procdff$57' with positive edge clock. -Creating register for signal `\fifo.$memwr$\data$fifo.v:38$15_DATA' using process `\fifo.$proc$fifo.v:36$16'. - created $dff cell `$procdff$58' with positive edge clock. -Creating register for signal `\fifo.$memwr$\data$fifo.v:38$15_EN' using process `\fifo.$proc$fifo.v:36$16'. - created $dff cell `$procdff$59' with positive edge clock. -Creating register for signal `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000.\addr' using process `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000.$proc$fifo.v:12$32'. - created $adff cell `$procdff$60' with positive edge clock and positive level reset. - -yosys> proc_memwr - -13.10. Executing PROC_MEMWR pass (convert process memory writes to cells). - -yosys> proc_clean - -13.11. Executing PROC_CLEAN pass (remove empty switches from decision trees). -Removing empty process `fifo.$proc$fifo.v:0$31'. -Found and cleaned up 2 empty switches in `\fifo.$proc$fifo.v:62$24'. -Removing empty process `fifo.$proc$fifo.v:62$24'. -Found and cleaned up 1 empty switch in `\fifo.$proc$fifo.v:36$16'. -Removing empty process `fifo.$proc$fifo.v:36$16'. -Removing empty process `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000.$proc$fifo.v:0$35'. -Found and cleaned up 2 empty switches in `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000.$proc$fifo.v:12$32'. -Removing empty process `$paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000.$proc$fifo.v:12$32'. -Cleaned up 5 empty switches. - -yosys> opt_expr -keepdc - -13.12. Executing OPT_EXPR pass (perform const folding). -Optimizing module fifo. -Optimizing module $paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000. - -yosys> select -set new_cells t:$memrd - -yosys> show -color maroon3 c:fifo_reader -color cornflowerblue @new_cells -notitle -format dot -prefix rdata_proc o:rdata %ci* - -14. Generating Graphviz representation of design. -Writing dot description to `rdata_proc.dot'. -Dumping selected parts of module fifo to page 1. - -yosys> flatten - -15. Executing FLATTEN pass (flatten design). -Deleting now unused module $paramod\addr_gen\MAX_DATA=s32'00000000000000000000000100000000. - - -yosys> clean -Removed 3 unused cells and 25 unused wires. - -yosys> select -set rdata_path o:rdata %ci* - -yosys> select -set new_cells @rdata_path o:rdata %ci3 %d i:* %d - -yosys> show -color maroon3 @new_cells -notitle -format dot -prefix rdata_flat @rdata_path - -16. Generating Graphviz representation of design. -Writing dot description to `rdata_flat.dot'. -Dumping selected parts of module fifo to page 1. - -yosys> opt_dff - -17. Executing OPT_DFF pass (perform DFF optimizations). -Adding EN signal on $procdff$55 ($adff) from module fifo (D = $0\count[8:0], Q = \count). -Adding EN signal on $flatten\fifo_writer.$procdff$60 ($adff) from module fifo (D = $flatten\fifo_writer.$procmux$51_Y, Q = \fifo_writer.addr). -Adding EN signal on $flatten\fifo_reader.$procdff$60 ($adff) from module fifo (D = $flatten\fifo_reader.$procmux$51_Y, Q = \fifo_reader.addr). - -yosys> select -set new_cells t:$adffe - -yosys> show -color maroon3 @new_cells -notitle -format dot -prefix rdata_adffe o:rdata %ci* - -18. Generating Graphviz representation of design. -Writing dot description to `rdata_adffe.dot'. -Dumping selected parts of module fifo to page 1. - -yosys> wreduce - -19. Executing WREDUCE pass (reducing word size of cells). -Removed top 31 bits (of 32) from port B of cell fifo.$add$fifo.v:66$27 ($add). -Removed top 23 bits (of 32) from port Y of cell fifo.$add$fifo.v:66$27 ($add). -Removed top 31 bits (of 32) from port B of cell fifo.$sub$fifo.v:68$30 ($sub). -Removed top 23 bits (of 32) from port Y of cell fifo.$sub$fifo.v:68$30 ($sub). -Removed top 1 bits (of 2) from port B of cell fifo.$auto$opt_dff.cc:195:make_patterns_logic$66 ($ne). -Removed cell fifo.$flatten\fifo_writer.$procmux$53 ($mux). -Removed top 31 bits (of 32) from port B of cell fifo.$flatten\fifo_writer.$add$fifo.v:19$34 ($add). -Removed top 24 bits (of 32) from port Y of cell fifo.$flatten\fifo_writer.$add$fifo.v:19$34 ($add). -Removed cell fifo.$flatten\fifo_reader.$procmux$53 ($mux). -Removed top 31 bits (of 32) from port B of cell fifo.$flatten\fifo_reader.$add$fifo.v:19$34 ($add). -Removed top 24 bits (of 32) from port Y of cell fifo.$flatten\fifo_reader.$add$fifo.v:19$34 ($add). -Removed top 23 bits (of 32) from wire fifo.$add$fifo.v:66$27_Y. -Removed top 24 bits (of 32) from wire fifo.$flatten\fifo_reader.$add$fifo.v:19$34_Y. - -yosys> show -notitle -format dot -prefix rdata_wreduce o:rdata %ci* - -20. Generating Graphviz representation of design. -Writing dot description to `rdata_wreduce.dot'. -Dumping selected parts of module fifo to page 1. - -yosys> opt_clean - -21. Executing OPT_CLEAN pass (remove unused cells and wires). -Finding unused cells or wires in module \fifo.. -Removed 0 unused cells and 4 unused wires. - - -yosys> memory_dff - -22. Executing MEMORY_DFF pass (merging $dff cells to $memrd). -Checking read port `\data'[0] in module `\fifo': merging output FF to cell. - Write port 0: non-transparent. - -yosys> select -set new_cells t:$memrd_v2 - -yosys> show -color maroon3 @new_cells -notitle -format dot -prefix rdata_memrdv2 o:rdata %ci* - -23. Generating Graphviz representation of design. -Writing dot description to `rdata_memrdv2.dot'. -Dumping selected parts of module fifo to page 1. - -yosys> alumacc - -24. Executing ALUMACC pass (create $alu and $macc cells). -Extracting $alu and $macc cells in module fifo: - creating $macc model for $add$fifo.v:66$27 ($add). - creating $macc model for $flatten\fifo_reader.$add$fifo.v:19$34 ($add). - creating $macc model for $flatten\fifo_writer.$add$fifo.v:19$34 ($add). - creating $macc model for $sub$fifo.v:68$30 ($sub). - creating $alu model for $macc $sub$fifo.v:68$30. - creating $alu model for $macc $flatten\fifo_writer.$add$fifo.v:19$34. - creating $alu model for $macc $flatten\fifo_reader.$add$fifo.v:19$34. - creating $alu model for $macc $add$fifo.v:66$27. - creating $alu cell for $add$fifo.v:66$27: $auto$alumacc.cc:485:replace_alu$80 - creating $alu cell for $flatten\fifo_reader.$add$fifo.v:19$34: $auto$alumacc.cc:485:replace_alu$83 - creating $alu cell for $flatten\fifo_writer.$add$fifo.v:19$34: $auto$alumacc.cc:485:replace_alu$86 - creating $alu cell for $sub$fifo.v:68$30: $auto$alumacc.cc:485:replace_alu$89 - created 4 $alu and 0 $macc cells. - -yosys> select -set new_cells t:$alu t:$macc - -yosys> show -color maroon3 @new_cells -notitle -format dot -prefix rdata_alumacc o:rdata %ci* - -25. Generating Graphviz representation of design. -Writing dot description to `rdata_alumacc.dot'. -Dumping selected parts of module fifo to page 1. - -yosys> memory_collect - -26. Executing MEMORY_COLLECT pass (generating $mem cells). - -yosys> select -set new_cells t:$mem_v2 - -yosys> select -set rdata_path @new_cells %ci*:-$mem_v2[WR_DATA,WR_ADDR,WR_EN] @new_cells %co* %% - -yosys> show -color maroon3 @new_cells -notitle -format dot -prefix rdata_coarse @rdata_path - -27. Generating Graphviz representation of design. -Writing dot description to `rdata_coarse.dot'. -Dumping selected parts of module fifo to page 1. diff --git a/docs/source/code_examples/fifo/fifo.stat b/docs/source/code_examples/fifo/fifo.stat deleted file mode 100644 index 263c618e326..00000000000 --- a/docs/source/code_examples/fifo/fifo.stat +++ /dev/null @@ -1,57 +0,0 @@ - -yosys> stat - -2. Printing statistics. - -=== fifo === - - Number of wires: 28 - Number of wire bits: 219 - Number of public wires: 9 - Number of public wire bits: 45 - Number of memories: 1 - Number of memory bits: 2048 - Number of processes: 3 - Number of cells: 9 - $add 1 - $logic_and 2 - $logic_not 2 - $memrd 1 - $sub 1 - addr_gen 2 - -=== addr_gen === - - Number of wires: 8 - Number of wire bits: 60 - Number of public wires: 4 - Number of public wire bits: 11 - Number of memories: 0 - Number of memory bits: 0 - Number of processes: 2 - Number of cells: 2 - $add 1 - $eq 1 - - -yosys> stat -top fifo - -17. Printing statistics. - -=== fifo === - - Number of wires: 94 - Number of wire bits: 260 - Number of public wires: 94 - Number of public wire bits: 260 - Number of memories: 0 - Number of memory bits: 0 - Number of processes: 0 - Number of cells: 138 - $scopeinfo 2 - SB_CARRY 26 - SB_DFF 26 - SB_DFFER 25 - SB_LUT4 58 - SB_RAM40_4K 1 - diff --git a/docs/source/code_examples/intro/Makefile b/docs/source/code_examples/intro/Makefile index 009c82c6219..31df6b62327 100644 --- a/docs/source/code_examples/intro/Makefile +++ b/docs/source/code_examples/intro/Makefile @@ -4,8 +4,10 @@ YOSYS ?= ../../../../$(PROGRAM_PREFIX)yosys DOTS = counter_00.dot counter_01.dot counter_02.dot counter_03.dot -all: dots +.PHONY: all dots examples +all: dots examples dots: $(DOTS) +examples: $(DOTS): counter.v counter.ys mycells.lib $(YOSYS) counter.ys diff --git a/docs/source/code_examples/macc/Makefile b/docs/source/code_examples/macc/Makefile index e93fe065734..f06c623a531 100644 --- a/docs/source/code_examples/macc/Makefile +++ b/docs/source/code_examples/macc/Makefile @@ -4,8 +4,10 @@ YOSYS ?= ../../../../$(PROGRAM_PREFIX)yosys DOTS = macc_simple_xmap.dot macc_xilinx_xmap.dot -all: dots +.PHONY: all dots examples +all: dots examples dots: $(DOTS) +examples: macc_simple_xmap.dot: macc_simple_*.v macc_simple_test.ys $(YOSYS) macc_simple_test.ys diff --git a/docs/source/code_examples/opt/Makefile b/docs/source/code_examples/opt/Makefile index 12c1c93b1c8..aa7962ca5d9 100644 --- a/docs/source/code_examples/opt/Makefile +++ b/docs/source/code_examples/opt/Makefile @@ -6,13 +6,13 @@ DOT_NAMES = opt_share opt_muxtree opt_merge opt_expr DOTS := $(addsuffix .dot,$(DOT_NAMES)) -all: dots +.PHONY: all dots examples +all: dots examples dots: $(DOTS) +examples: -%_full.dot: %.ys +%.dot: %.ys $(YOSYS) $< - -%.dot: %_full.dot gvpack -u -o $@ $*_full.dot .PHONY: clean diff --git a/docs/source/code_examples/scrambler/Makefile b/docs/source/code_examples/scrambler/Makefile index de475b8b1a1..a9b56f8d350 100644 --- a/docs/source/code_examples/scrambler/Makefile +++ b/docs/source/code_examples/scrambler/Makefile @@ -2,9 +2,10 @@ PROGRAM_PREFIX := YOSYS ?= ../../../../$(PROGRAM_PREFIX)yosys -.PHONY: all dots -all: dots +.PHONY: all dots examples +all: dots examples dots: scrambler_p01.dot scrambler_p02.dot +examples: scrambler_p01.dot scrambler_p02.dot: scrambler.ys scrambler.v $(YOSYS) scrambler.ys diff --git a/docs/source/code_examples/selections/Makefile b/docs/source/code_examples/selections/Makefile index bb506ff38eb..46a09060a05 100644 --- a/docs/source/code_examples/selections/Makefile +++ b/docs/source/code_examples/selections/Makefile @@ -11,14 +11,15 @@ MEMDEMO_DOTS := $(addsuffix .dot,$(MEMDEMO)) SUBMOD = submod_00 submod_01 submod_02 submod_03 SUBMOD_DOTS := $(addsuffix .dot,$(SUBMOD)) -.PHONY: all dots -all: dots +.PHONY: all dots examples +all: dots examples dots: select.dot $(SUMPROD_DOTS) $(MEMDEMO_DOTS) $(SUBMOD_DOTS) +examples: sumprod.out select.dot: select.v select.ys $(YOSYS) select.ys -$(SUMPROD_DOTS): sumprod.v sumprod.ys +$(SUMPROD_DOTS) sumprod.out: sumprod.v sumprod.ys $(YOSYS) sumprod.ys $(MEMDEMO_DOTS): memdemo.v memdemo.ys @@ -30,3 +31,4 @@ $(SUBMOD_DOTS): memdemo.v submod.ys .PHONY: clean clean: rm -rf *.dot + rm -f sumprod.out diff --git a/docs/source/code_examples/selections/sumprod.out b/docs/source/code_examples/selections/sumprod.out deleted file mode 100644 index f7c2594994f..00000000000 --- a/docs/source/code_examples/selections/sumprod.out +++ /dev/null @@ -1,38 +0,0 @@ - - - attribute \src "sumprod.v:4.21-4.25" - wire width 8 output 5 \prod - - attribute \src "sumprod.v:10.17-10.26" - cell $mul $mul$sumprod.v:10$4 - parameter \A_SIGNED 0 - parameter \A_WIDTH 8 - parameter \B_SIGNED 0 - parameter \B_WIDTH 8 - parameter \Y_WIDTH 8 - connect \A $mul$sumprod.v:10$3_Y - connect \B \c - connect \Y \prod - end - - - attribute \src "sumprod.v:10.17-10.22" - wire width 8 $mul$sumprod.v:10$3_Y - - attribute \src "sumprod.v:3.21-3.22" - wire width 8 input 3 \c - - attribute \src "sumprod.v:4.21-4.25" - wire width 8 output 5 \prod - - attribute \src "sumprod.v:10.17-10.26" - cell $mul $mul$sumprod.v:10$4 - parameter \A_SIGNED 0 - parameter \A_WIDTH 8 - parameter \B_SIGNED 0 - parameter \B_WIDTH 8 - parameter \Y_WIDTH 8 - connect \A $mul$sumprod.v:10$3_Y - connect \B \c - connect \Y \prod - end diff --git a/docs/source/code_examples/show/Makefile b/docs/source/code_examples/show/Makefile index 4b269a4aba1..649691299cf 100644 --- a/docs/source/code_examples/show/Makefile +++ b/docs/source/code_examples/show/Makefile @@ -8,9 +8,10 @@ EXAMPLE_DOTS := $(addsuffix .dot,$(EXAMPLE)) CMOS = cmos_00 cmos_01 CMOS_DOTS := $(addsuffix .dot,$(CMOS)) -.PHONY: all dots -all: dots example.out +.PHONY: all dots examples +all: dots examples dots: splice.dot $(EXAMPLE_DOTS) $(CMOS_DOTS) +examples: example.out splice.dot: splice.v $(YOSYS) -p 'prep -top splice_demo; show -format dot -prefix splice' splice.v @@ -27,3 +28,4 @@ $(CMOS_DOTS): cmos.v cmos.ys .PHONY: clean clean: rm -rf *.dot + rm -f example.out diff --git a/docs/source/code_examples/show/example.out b/docs/source/code_examples/show/example.out deleted file mode 100644 index b8569e640d3..00000000000 --- a/docs/source/code_examples/show/example.out +++ /dev/null @@ -1,54 +0,0 @@ - --- Executing script file `example_lscd.ys' -- - -1. Executing Verilog-2005 frontend: example.v -Parsing Verilog input from `example.v' to AST representation. -Generating RTLIL representation for module `\example'. -Successfully finished Verilog frontend. -echo on - -yosys> ls - -1 modules: - example - -yosys> cd example - -yosys [example]> ls - -8 wires: - $0\y[1:0] - $add$example.v:5$2_Y - $ternary$example.v:5$3_Y - a - b - c - clk - y - -2 cells: - $add$example.v:5$2 - $ternary$example.v:5$3 - -1 processes: - $proc$example.v:3$1 - -yosys [example]> dump $2 - - - attribute \src "example.v:5.22-5.27" - cell $add $add$example.v:5$2 - parameter \Y_WIDTH 2 - parameter \B_WIDTH 1 - parameter \A_WIDTH 1 - parameter \B_SIGNED 0 - parameter \A_SIGNED 0 - connect \Y $add$example.v:5$2_Y - connect \B \b - connect \A \a - end - -yosys [example]> cd .. - -yosys> echo off -echo off diff --git a/docs/source/code_examples/stubnets/Makefile b/docs/source/code_examples/stubnets/Makefile index ec501f006ca..17f700c8825 100644 --- a/docs/source/code_examples/stubnets/Makefile +++ b/docs/source/code_examples/stubnets/Makefile @@ -1,6 +1,7 @@ -.PHONY: all dots -all: dots +.PHONY: all dots examples +all: dots examples dots: +examples: .PHONY: test test: stubnets.so diff --git a/docs/source/code_examples/synth_flow/Makefile b/docs/source/code_examples/synth_flow/Makefile index 7db1c12f490..0dd37ed4d37 100644 --- a/docs/source/code_examples/synth_flow/Makefile +++ b/docs/source/code_examples/synth_flow/Makefile @@ -9,9 +9,10 @@ YOSYS ?= ../../../../$(PROGRAM_PREFIX)yosys DOTS = $(addsuffix .dot,$(DOT_TARGETS)) -.PHONY: all dots -all: dots +.PHONY: all dots examples +all: dots examples dots: $(DOTS) +examples: %.dot: %.v %.ys $(YOSYS) -p 'script $*.ys; show -notitle -prefix $* -format dot' diff --git a/docs/source/code_examples/techmap/Makefile b/docs/source/code_examples/techmap/Makefile index e900fea4c5f..6736b0f1df0 100644 --- a/docs/source/code_examples/techmap/Makefile +++ b/docs/source/code_examples/techmap/Makefile @@ -2,9 +2,10 @@ PROGRAM_PREFIX := YOSYS ?= ../../../../$(PROGRAM_PREFIX)yosys -.PHONY: all dots -all: dots +.PHONY: all dots examples +all: dots examples dots: red_or3x1.dot sym_mul.dot mymul.dot mulshift.dot addshift.dot +examples: red_or3x1.dot: red_or3x1_* $(YOSYS) red_or3x1_test.ys diff --git a/docs/source/conf.py b/docs/source/conf.py index 4371b79f16a..8e30fcc7c03 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -5,7 +5,7 @@ project = 'YosysHQ Yosys' author = 'YosysHQ GmbH' copyright ='2024 YosysHQ GmbH' -yosys_ver = "0.45" +yosys_ver = "0.46" # select HTML theme html_theme = 'furo' diff --git a/docs/source/yosys_internals/extending_yosys/functional_ir.rst b/docs/source/yosys_internals/extending_yosys/functional_ir.rst new file mode 100644 index 00000000000..a763e050889 --- /dev/null +++ b/docs/source/yosys_internals/extending_yosys/functional_ir.rst @@ -0,0 +1,94 @@ +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. + +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. + +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. + +``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. + +Each node has a "function", which defines its operation (for a complete list of functions and a specification of their operation, see ``functional.h``). +Functions are represented as an enum ``Functional::Fn`` and the function field can be accessed as ``node.fn()``. +Since the most common operation is a switch over the function that also accesses the arguments, the ``Node`` class provides a method ``visit`` that implements the visitor pattern. +For example, for an addition node ``node`` with arguments ``n1`` and ``n2``, ``node.visit(visitor)`` would call ``visitor.add(node, n1, n2)``. +Thus typically one would implement a class with a method for every function. +Visitors should inherit from either ``Functional::AbstractVisitor`` or ``Functional::DefaultVisitor``. +The former will produce a compiler error if a case is unhandled, the latter will call ``default_handler(node)`` instead. +Visitor methods should be marked as ``override`` to provide compiler errors if the arguments are wrong. + +Utility classes +----------------- + +``functional.h`` also provides utility classes that are independent of the main FunctionalIR representation but are likely to be useful for backends. + +``Functional::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. + +``Functional::Scope`` keeps track of variable names in a target language. +It is used to translate between different sets of legal characters and to avoid accidentally re-defining identifiers. +Users should derive a class from ``Scope`` and supply the following: + +- ``Scope`` takes a template argument that specifies a type that's used to uniquely distinguish variables. + Typically this would be ``int`` (if variables are used for ``Functional::IR`` nodes) or ``IdString``. +- The derived class should provide a constructor that calls ``reserve`` for every reserved word in the target language. +- A method ``bool is_legal_character(char c, int index)`` has to be provided that returns ``true`` iff ``c`` is legal in an identifier at position ``index``. + +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``). + +``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: + +- ``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.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. +- The destructor calls ``flush`` but also closes all unclosed parentheses. diff --git a/docs/source/yosys_internals/extending_yosys/index.rst b/docs/source/yosys_internals/extending_yosys/index.rst index 2a4d2dfef04..88f36d5264e 100644 --- a/docs/source/yosys_internals/extending_yosys/index.rst +++ b/docs/source/yosys_internals/extending_yosys/index.rst @@ -10,5 +10,6 @@ of interest for developers looking to customise Yosys builds. extensions build_verific + functional_ir test_suites diff --git a/frontends/aiger2/Makefile.inc b/frontends/aiger2/Makefile.inc new file mode 100644 index 00000000000..b4a9f6b898b --- /dev/null +++ b/frontends/aiger2/Makefile.inc @@ -0,0 +1,2 @@ + +OBJS += frontends/aiger2/xaiger.o diff --git a/frontends/aiger2/xaiger.cc b/frontends/aiger2/xaiger.cc new file mode 100644 index 00000000000..04beee3965b --- /dev/null +++ b/frontends/aiger2/xaiger.cc @@ -0,0 +1,473 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) Martin PoviĊĦer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/register.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +uint32_t read_be32(std::istream &f) { + return ((uint32_t) f.get() << 24) | + ((uint32_t) f.get() << 16) | + ((uint32_t) f.get() << 8) | (uint32_t) f.get(); +} + +IdString read_idstring(std::istream &f) +{ + std::string str; + std::getline(f, str, '\0'); + if (!f.good()) + log_error("failed to read string\n"); + return RTLIL::escape_id(str); +} + +struct Xaiger2Frontend : public Frontend { + Xaiger2Frontend() : Frontend("xaiger2", "(experimental) read XAIGER file") + { + experimental(); + } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" read_xaiger2 -sc_mapping [options] \n"); + log("\n"); + log("Read a standard cell mapping from a XAIGER file into an existing module.\n"); + log("\n"); + log(" -module_name \n"); + log(" name of the target module\n"); + log("\n"); + log(" -map2 \n"); + log(" read file with symbol information\n"); + log("\n"); + } + + void read_sc_mapping(std::istream *&f, std::string filename, std::vector args, Design *design) + { + IdString module_name; + std::string map_filename; + + size_t argidx; + for (argidx = 2; argidx < args.size(); argidx++) { + std::string arg = args[argidx]; + if (arg == "-module_name" && argidx + 1 < args.size()) { + module_name = RTLIL::escape_id(args[++argidx]); + continue; + } + if (arg == "-map2" && argidx + 1 < args.size()) { + map_filename = args[++argidx]; + continue; + } + break; + } + extra_args(f, filename, args, argidx, true); + + if (map_filename.empty()) + log_error("A '-map2' argument required\n"); + if (module_name.empty()) + log_error("A '-module_name' argument required\n"); + + Module *module = design->module(module_name); + if (!module) + log_error("Module '%s' not found\n", log_id(module_name)); + + std::ifstream map_file; + map_file.open(map_filename); + if (!map_file) + log_error("Failed to open map file '%s'\n", map_filename.c_str()); + + unsigned int M, I, L, O, A; + std::string header; + if (!(*f >> header >> M >> I >> L >> O >> A) || header != "aig") + log_error("Bad header\n"); + std::string line; + std::getline(*f, line); + log_debug("M=%u I=%u L=%u O=%u A=%u\n", M, I, L, O, A); + + if (L != 0) + log_error("Latches unsupported\n"); + if (I + L + A != M) + log_error("Inconsistent header\n"); + + std::vector outputs; + for (int i = 0; i < (int) O; i++) { + int po; + *f >> po; + log_assert(f->get() == '\n'); + outputs.push_back(po); + } + + std::vector> boxes; + std::vector retained_boxes; + std::vector bits(2 + 2*M, RTLIL::Sm); + bits[0] = RTLIL::S0; + bits[1] = RTLIL::S1; + + std::string type; + while (map_file >> type) { + if (type == "pi") { + int pi_idx; + int woffset; + std::string name; + if (!(map_file >> pi_idx >> woffset >> name)) + log_error("Bad map file (1)\n"); + int lit = (2 * pi_idx) + 2; + if (lit < 0 || lit >= (int) bits.size()) + log_error("Bad map file (2)\n"); + Wire *w = module->wire(name); + if (!w || woffset < 0 || woffset >= w->width) + log_error("Map file references non-existent signal bit %s[%d]\n", + name.c_str(), woffset); + bits[lit] = SigBit(w, woffset); + } else if (type == "box") { + int box_seq; + std::string name; + if (!(map_file >> box_seq >> name)) + log_error("Bad map file (20)\n"); + if (box_seq < 0) + log_error("Bad map file (21)\n"); + + Cell *box = module->cell(RTLIL::escape_id(name)); + if (!box) + log_error("Map file references non-existent box %s\n", + name.c_str()); + + Module *def = design->module(box->type); + if (def && !box->parameters.empty()) { + // TODO: This is potentially costly even if a cached derivation exists + def = design->module(def->derive(design, box->parameters)); + log_assert(def); + } + + if (!def) + log_error("Bad map file (22)\n"); + + if (box_seq >= (int) boxes.size()) { + boxes.resize(box_seq + 1); + retained_boxes.resize(box_seq + 1); + } + boxes[box_seq] = std::make_pair(box, def); + } else { + std::string scratch; + std::getline(map_file, scratch); + } + } + + for (int i = 0; i < (int) A; i++) { + while (f->get() & 0x80 && !f->eof()); + while (f->get() & 0x80 && !f->eof()); + } + + if (f->get() != 'c') + log_error("Missing 'c' ahead of extensions\n"); + if (f->peek() == '\n') + f->get(); + auto extensions_start = f->tellg(); + + log_debug("reading 'h' (first pass)\n"); + for (int c = f->get(); c != EOF; c = f->get()) { + if (c == 'h') { + uint32_t len, ci_num, co_num, pi_num, po_num, no_boxes; + len = read_be32(*f); + read_be32(*f); + ci_num = read_be32(*f); + co_num = read_be32(*f); + pi_num = read_be32(*f); + po_num = read_be32(*f); + no_boxes = read_be32(*f); + + log_debug("len=%u ci_num=%u co_num=%u pi_num=%u po_nun=%u no_boxes=%u\n", + len, ci_num, co_num, pi_num, po_num, no_boxes); + + int ci_counter = 0; + for (uint32_t i = 0; i < no_boxes; i++) { + uint32_t box_inputs, box_outputs, box_id, box_seq; + box_inputs = read_be32(*f); + box_outputs = read_be32(*f); + box_id = read_be32(*f); + box_seq = read_be32(*f); + + log("box_seq=%d boxes.size=%d\n", box_seq, (int) boxes.size()); + log_assert(box_seq < boxes.size()); + + auto [cell, def] = boxes[box_seq]; + log_assert(cell && def); + retained_boxes[box_seq] = true; + + int box_ci_idx = 0; + for (auto port_id : def->ports) { + Wire *port = def->wire(port_id); + if (port->port_output) { + if (!cell->hasPort(port_id) || cell->getPort(port_id).size() != port->width) + log_error("Malformed design (1)\n"); + + SigSpec &conn = cell->connections_[port_id]; + for (int j = 0; j < port->width; j++) { + if (conn[j].wire && conn[j].wire->port_output) + conn[j] = module->addWire(module->uniquify( + stringf("$box$%s$%s$%d", + cell->name.isPublic() ? cell->name.c_str() + 1 : cell->name.c_str(), + port_id.isPublic() ? port_id.c_str() + 1 : port_id.c_str(), + j))); + + bits[2*(pi_num + ci_counter + box_ci_idx++) + 2] = conn[j]; + } + } + } + + log_assert(box_ci_idx == (int) box_outputs); + ci_counter += box_ci_idx; + } + log_assert(pi_num + ci_counter == ci_num); + } else if (c == '\n') { + break; + } else if (c == 'c') { + break; + } else { + uint32_t len = read_be32(*f); + f->ignore(len); + log_debug(" section '%c' (%d): ignoring %d bytes\n", c, c, len); + } + } + + log_debug("reading 'M' (second pass)\n"); + + f->seekg(extensions_start); + bool read_mapping = false; + uint32_t no_cells, no_instances; + for (int c = f->get(); c != EOF; c = f->get()) { + if (c == 'M') { + uint32_t len = read_be32(*f); + read_mapping = true; + + no_cells = read_be32(*f); + no_instances = read_be32(*f); + + log_debug("M: len=%u no_cells=%u no_instances=%u\n", len, no_cells, no_instances); + + struct MappingCell { + RTLIL::IdString type; + RTLIL::IdString out; + std::vector ins; + }; + std::vector cells; + cells.resize(no_cells); + + for (unsigned i = 0; i < no_cells; ++i) { + auto &cell = cells[i]; + cell.type = read_idstring(*f); + cell.out = read_idstring(*f); + uint32_t nins = read_be32(*f); + for (uint32_t j = 0; j < nins; j++) + cell.ins.push_back(read_idstring(*f)); + log_debug("M: Cell %s (out %s, ins", log_id(cell.type), log_id(cell.out)); + for (auto in : cell.ins) + log_debug(" %s", log_id(in)); + log_debug(")\n"); + } + + for (unsigned i = 0; i < no_instances; ++i) { + uint32_t cell_id = read_be32(*f); + uint32_t out_lit = read_be32(*f); + + log_assert(out_lit < bits.size()); + log_assert(bits[out_lit] == RTLIL::Sm); + log_assert(cell_id < cells.size()); + auto &cell = cells[cell_id]; + Cell *instance = module->addCell(module->uniquify(stringf("$sc%d", out_lit)), cell.type); + auto out_w = module->addWire(module->uniquify(stringf("$lit%d", out_lit))); + instance->setPort(cell.out, out_w); + bits[out_lit] = out_w; + for (auto in : cell.ins) { + uint32_t in_lit = read_be32(*f); + log_assert(out_lit < bits.size()); + log_assert(bits[in_lit] != RTLIL::Sm); + instance->setPort(in, bits[in_lit]); + } + } + } else if (c == '\n') { + break; + } else if (c == 'c') { + break; + } else { + uint32_t len = read_be32(*f); + f->ignore(len); + log_debug(" section '%c' (%d): ignoring %d bytes\n", c, c, len); + } + } + + if (!read_mapping) + log_error("Missing mapping (no 'M' section)\n"); + + log("Read %d instances with cell library of size %d.\n", + no_instances, no_cells); + + f->seekg(extensions_start); + log_debug("reading 'h' (second pass)\n"); + int co_counter = 0; + for (int c = f->get(); c != EOF; c = f->get()) { + if (c == 'h') { + uint32_t len, ci_num, co_num, pi_num, po_num, no_boxes; + len = read_be32(*f); + read_be32(*f); + ci_num = read_be32(*f); + co_num = read_be32(*f); + pi_num = read_be32(*f); + po_num = read_be32(*f); + no_boxes = read_be32(*f); + + log_debug("len=%u ci_num=%u co_num=%u pi_num=%u po_nun=%u no_boxes=%u\n", + len, ci_num, co_num, pi_num, po_num, no_boxes); + + for (uint32_t i = 0; i < no_boxes; i++) { + uint32_t box_inputs, box_outputs, box_id, box_seq; + box_inputs = read_be32(*f); + box_outputs = read_be32(*f); + box_id = read_be32(*f); + box_seq = read_be32(*f); + + log("box_seq=%d boxes.size=%d\n", box_seq, (int) boxes.size()); + log_assert(box_seq < boxes.size()); + + auto [cell, def] = boxes[box_seq]; + log_assert(cell && def); + + int box_co_idx = 0; + for (auto port_id : def->ports) { + Wire *port = def->wire(port_id); + SigSpec conn; + if (port->port_input) { + if (!cell->hasPort(port_id) || cell->getPort(port_id).size() != port->width) + log_error("Malformed design (2)\n"); + + SigSpec conn; + for (int j = 0; j < port->width; j++) { + log_assert(co_counter + box_co_idx < (int) outputs.size()); + int lit = outputs[co_counter + box_co_idx++]; + log_assert(lit >= 0 && lit < (int) bits.size()); + SigBit bit = bits[lit]; + if (bit == RTLIL::Sm) + log_error("Malformed mapping (1)\n"); + conn.append(bit); + } + cell->setPort(port_id, conn); + } + } + + log_assert(box_co_idx == (int) box_inputs); + co_counter += box_co_idx; + } + log_assert(po_num + co_counter == co_num); + } else if (c == '\n') { + break; + } else if (c == 'c') { + break; + } else { + uint32_t len = read_be32(*f); + f->ignore(len); + log_debug(" section '%c' (%d): ignoring %d bytes\n", c, c, len); + } + } + + while (true) { + std::string scratch; + std::getline(*f, scratch); + if (f->eof()) + break; + log_assert(!f->fail()); + log("input file: %s\n", scratch.c_str()); + } + + log_debug("co_counter=%d\n", co_counter); + + // TODO: seek without close/open + map_file.close(); + map_file.open(map_filename); + while (map_file >> type) { + if (type == "po") { + int po_idx; + int woffset; + std::string name; + if (!(map_file >> po_idx >> woffset >> name)) + log_error("Bad map file (3)\n"); + po_idx += co_counter; + if (po_idx < 0 || po_idx >= (int) outputs.size()) + log_error("Bad map file (4)\n"); + int lit = outputs[po_idx]; + if (lit < 0 || lit >= (int) bits.size()) + log_error("Bad map file (5)\n"); + if (bits[lit] == RTLIL::Sm) + log_error("Bad map file (6)\n"); + Wire *w = module->wire(name); + if (!w || woffset < 0 || woffset >= w->width) + log_error("Map file references non-existent signal bit %s[%d]\n", + name.c_str(), woffset); + module->connect(SigBit(w, woffset), bits[lit]); + } else if (type == "pseudopo") { + int po_idx; + int poffset; + std::string box_name; + std::string box_port; + if (!(map_file >> po_idx >> poffset >> box_name >> box_port)) + log_error("Bad map file (7)\n"); + po_idx += co_counter; + if (po_idx < 0 || po_idx >= (int) outputs.size()) + log_error("Bad map file (8)\n"); + int lit = outputs[po_idx]; + if (lit < 0 || lit >= (int) bits.size()) + log_error("Bad map file (9)\n"); + if (bits[lit] == RTLIL::Sm) + log_error("Bad map file (10)\n"); + Cell *cell = module->cell(box_name); + if (!cell || !cell->hasPort(box_port)) + log_error("Map file references non-existent box port %s/%s\n", + box_name.c_str(), box_port.c_str()); + SigSpec &port = cell->connections_[box_port]; + if (poffset < 0 || poffset >= port.size()) + log_error("Map file references non-existent box port bit %s/%s[%d]\n", + box_name.c_str(), box_port.c_str(), poffset); + port[poffset] = bits[lit]; + } else { + std::string scratch; + std::getline(map_file, scratch); + } + } + + int box_seq = 0; + for (auto [cell, def] : boxes) { + if (!retained_boxes[box_seq++]) + module->remove(cell); + } + } + + void execute(std::istream *&f, std::string filename, std::vector args, Design *design) override + { + log_header(design, "Executing XAIGER2 frontend.\n"); + + if (args.size() > 1 && args[1] == "-sc_mapping") { + read_sc_mapping(f, filename, args, design); + return; + } + + log_cmd_error("Mode '-sc_mapping' must be selected\n"); + } +} Xaiger2Frontend; + +PRIVATE_NAMESPACE_END diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index d21b8f02d90..127806fce69 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -41,6 +41,8 @@ namespace AST { std::string current_filename; void (*set_line_num)(int) = NULL; int (*get_line_num)() = NULL; + unsigned long long astnodes = 0; + unsigned long long astnode_count() { return astnodes; } } // instantiate global variables (private API) @@ -204,6 +206,7 @@ AstNode::AstNode(AstNodeType type, AstNode *child1, AstNode *child2, AstNode *ch static unsigned int hashidx_count = 123456789; hashidx_count = mkhash_xorshift(hashidx_count); hashidx_ = hashidx_count; + astnodes++; this->type = type; filename = current_filename; @@ -292,6 +295,7 @@ void AstNode::delete_children() // AstNode destructor AstNode::~AstNode() { + astnodes--; delete_children(); } @@ -474,6 +478,10 @@ void AstNode::dumpVlog(FILE *f, std::string indent) const fprintf(f, ";\n"); break; + case AST_WIRETYPE: + fprintf(f, "%s", id2vl(str).c_str()); + break; + case AST_MEMORY: fprintf(f, "%s" "memory", indent.c_str()); if (is_signed) @@ -690,7 +698,17 @@ void AstNode::dumpVlog(FILE *f, std::string indent) const break; case AST_CAST_SIZE: - children[0]->dumpVlog(f, ""); + switch (children[0]->type) + { + case AST_WIRE: + if (children[0]->children.size() > 0) + children[0]->children[0]->dumpVlog(f, ""); + else + fprintf(f, "%d'", children[0]->range_left - children[0]->range_right + 1); + break; + default: + children[0]->dumpVlog(f, ""); + } fprintf(f, "'("); children[1]->dumpVlog(f, ""); fprintf(f, ")"); diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index 6c4a1e15a65..bdff015e3f4 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -410,6 +410,9 @@ namespace AST extern void (*set_line_num)(int); extern int (*get_line_num)(); + // for stats + unsigned long long astnode_count(); + // set set_line_num and get_line_num to internal dummy functions (done by simplify() and AstModule::derive // to control the filename and linenum properties of new nodes not generated by a frontend parser) void use_internal_line_num(); diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 3d8478ef160..fbf5b90aadf 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -1500,11 +1500,69 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin } break; + case AST_CAST_SIZE: { + int width = 1; + AstNode *node; + AstNode *child = children[0]; + + if (child->type == AST_WIRE) { + if (child->children.size() == 0) { + // Base type (e.g., int) + width = child->range_left - child->range_right +1; + node = mkconst_int(width, child->is_signed); + } else { + // User defined type + log_assert(child->children[0]->type == AST_WIRETYPE); + + const std::string &type_name = child->children[0]->str; + if (!current_scope.count(type_name)) + input_error("Unknown identifier `%s' used as type name\n", type_name.c_str()); + AstNode *resolved_type_node = current_scope.at(type_name); + if (resolved_type_node->type != AST_TYPEDEF) + input_error("`%s' does not name a type\n", type_name.c_str()); + log_assert(resolved_type_node->children.size() == 1); + AstNode *template_node = resolved_type_node->children[0]; + + // Ensure typedef itself is fully simplified + while (template_node->simplify(const_fold, stage, width_hint, sign_hint)) {}; + + switch (template_node->type) + { + case AST_WIRE: { + if (template_node->children.size() > 0 && template_node->children[0]->type == AST_RANGE) + width = range_width(this, template_node->children[0]); + child->delete_children(); + node = mkconst_int(width, true); + break; + } + + case AST_STRUCT: + case AST_UNION: { + child->delete_children(); + width = size_packed_struct(template_node, 0); + node = mkconst_int(width, false); + break; + } + + default: + log_error("Don't know how to translate static cast of type %s\n", type2str(template_node->type).c_str()); + } + } + + delete child; + children.erase(children.begin()); + children.insert(children.begin(), node); + } + + detect_width_simple = true; + children_are_self_determined = true; + break; + } + case AST_TO_BITS: case AST_TO_SIGNED: case AST_TO_UNSIGNED: case AST_SELFSZ: - case AST_CAST_SIZE: case AST_CONCAT: case AST_REPLICATE: case AST_REDUCE_AND: diff --git a/frontends/liberty/liberty.cc b/frontends/liberty/liberty.cc index 16fed54f203..e183cbf1007 100644 --- a/frontends/liberty/liberty.cc +++ b/frontends/liberty/liberty.cc @@ -465,6 +465,9 @@ struct LibertyFrontend : public Frontend { log(" -setattr \n"); log(" set the specified attribute (to the value 1) on all loaded modules\n"); log("\n"); + log(" -unit_delay\n"); + log(" import combinational timing arcs under the unit delay model\n"); + log("\n"); } void execute(std::istream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { @@ -475,6 +478,7 @@ struct LibertyFrontend : public Frontend { bool flag_ignore_miss_func = false; bool flag_ignore_miss_dir = false; bool flag_ignore_miss_data_latch = false; + bool flag_unit_delay = false; std::vector attributes; size_t argidx; @@ -514,6 +518,10 @@ struct LibertyFrontend : public Frontend { attributes.push_back(RTLIL::escape_id(args[++argidx])); continue; } + if (arg == "-unit_delay") { + flag_unit_delay = true; + continue; + } break; } extra_args(f, filename, args, argidx); @@ -652,6 +660,7 @@ struct LibertyFrontend : public Frontend { continue; RTLIL::Wire *wire = module->wires_.at(RTLIL::escape_id(node->args.at(0))); + log_assert(wire); if (dir && dir->value == "inout") { wire->port_input = true; @@ -690,6 +699,43 @@ struct LibertyFrontend : public Frontend { } module->connect(RTLIL::SigSig(wire, out_sig)); } + + if (flag_unit_delay) { + pool done; + + for (auto timing : node->children) + if (timing->id == "timing" && timing->args.empty()) { + auto type = timing->find("timing_type"); + auto related_pin = timing->find("related_pin"); + if (!type || type->value != "combinational" || !related_pin) + continue; + + Wire *related = module->wire(RTLIL::escape_id(related_pin->value)); + if (!related) + log_error("Failed to find related pin %s for timing of pin %s on %s\n", + related_pin->value.c_str(), log_id(wire), log_id(module)); + + if (done.count(related)) + continue; + + RTLIL::Cell *spec = module->addCell(NEW_ID, ID($specify2)); + spec->setParam(ID::SRC_WIDTH, 1); + spec->setParam(ID::DST_WIDTH, 1); + spec->setParam(ID::T_FALL_MAX, 1000); + spec->setParam(ID::T_FALL_TYP, 1000); + spec->setParam(ID::T_FALL_MIN, 1000); + spec->setParam(ID::T_RISE_MAX, 1000); + spec->setParam(ID::T_RISE_TYP, 1000); + spec->setParam(ID::T_RISE_MIN, 1000); + spec->setParam(ID::SRC_DST_POL, false); + spec->setParam(ID::SRC_DST_PEN, false); + spec->setParam(ID::FULL, false); + spec->setPort(ID::EN, Const(1, 1)); + spec->setPort(ID::SRC, related); + spec->setPort(ID::DST, wire); + done.insert(related); + } + } } } diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index 2dd8aa09551..57c3ef14ead 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -450,6 +450,19 @@ void VerificImporter::import_attributes(dict &att auto type_range = nl->GetTypeRange(obj->Name()); if (!type_range) return; + if (type_range->IsTypeScalar()) { + const long long bottom_bound = type_range->GetScalarRangeLeftBound(); + const long long top_bound = type_range->GetScalarRangeRightBound(); + const unsigned bit_width = type_range->NumElements(); + RTLIL::Const bottom_const(bottom_bound, bit_width); + RTLIL::Const top_const(top_bound, bit_width); + if (bottom_bound < 0 || top_bound < 0) { + bottom_const.flags |= RTLIL::CONST_FLAG_SIGNED; + top_const.flags |= RTLIL::CONST_FLAG_SIGNED; + } + attributes.emplace(ID(bottom_bound), bottom_const); + attributes.emplace(ID(top_bound), top_const); + } if (!type_range->IsTypeEnum()) return; #ifdef VERIFIC_VHDL_SUPPORT diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y index 15a04eb2885..31d69ad0ca0 100644 --- a/frontends/verilog/verilog_parser.y +++ b/frontends/verilog/verilog_parser.y @@ -3506,6 +3506,12 @@ basic_expr: $$ = new AstNode(AST_CAST_SIZE, $1, $4); SET_AST_NODE_LOC($$, @1, @4); } | + typedef_base_type OP_CAST '(' expr ')' { + if (!sv_mode) + frontend_verilog_yyerror("Static cast is only supported in SystemVerilog mode."); + $$ = new AstNode(AST_CAST_SIZE, $1, $4); + SET_AST_NODE_LOC($$, @1, @4); + } | '(' expr '=' expr ')' { ensureAsgnExprAllowed(); AstNode *node = new AstNode(AST_ASSIGN_EQ, $2, $4); diff --git a/kernel/calc.cc b/kernel/calc.cc index 9b02a6e30c8..a7de08f8977 100644 --- a/kernel/calc.cc +++ b/kernel/calc.cc @@ -601,6 +601,14 @@ RTLIL::Const RTLIL::const_pos(const RTLIL::Const &arg1, const RTLIL::Const&, boo return arg1_ext; } +RTLIL::Const RTLIL::const_buf(const RTLIL::Const &arg1, const RTLIL::Const&, bool signed1, bool, int result_len) +{ + RTLIL::Const arg1_ext = arg1; + extend_u0(arg1_ext, result_len, signed1); + + return arg1_ext; +} + RTLIL::Const RTLIL::const_neg(const RTLIL::Const &arg1, const RTLIL::Const&, bool signed1, bool, int result_len) { RTLIL::Const arg1_ext = arg1; diff --git a/kernel/cellaigs.cc b/kernel/cellaigs.cc index 5dda4503fdd..de0a4939430 100644 --- a/kernel/cellaigs.cc +++ b/kernel/cellaigs.cc @@ -290,7 +290,7 @@ Aig::Aig(Cell *cell) } } - if (cell->type.in(ID($not), ID($_NOT_), ID($pos), ID($_BUF_))) + if (cell->type.in(ID($not), ID($_NOT_), ID($pos), ID($buf), ID($_BUF_))) { for (int i = 0; i < GetSize(cell->getPort(ID::Y)); i++) { int A = mk.inport(ID::A, i); diff --git a/kernel/celledges.cc b/kernel/celledges.cc index 09cdfd55b96..bad7124d9b1 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -24,7 +24,7 @@ PRIVATE_NAMESPACE_BEGIN void bitwise_unary_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) { - bool is_signed = cell->getParam(ID::A_SIGNED).as_bool(); + bool is_signed = (cell->type != ID($buf)) && cell->getParam(ID::A_SIGNED).as_bool(); int a_width = GetSize(cell->getPort(ID::A)); int y_width = GetSize(cell->getPort(ID::Y)); @@ -392,7 +392,7 @@ PRIVATE_NAMESPACE_END bool YOSYS_NAMESPACE_PREFIX AbstractCellEdgesDatabase::add_edges_from_cell(RTLIL::Cell *cell) { - if (cell->type.in(ID($not), ID($pos))) { + if (cell->type.in(ID($not), ID($pos), ID($buf))) { bitwise_unary_op(this, cell); return true; } diff --git a/kernel/celltypes.h b/kernel/celltypes.h index fde6624e179..b6d89a8051a 100644 --- a/kernel/celltypes.h +++ b/kernel/celltypes.h @@ -114,7 +114,7 @@ struct CellTypes void setup_internals_eval() { std::vector unary_ops = { - ID($not), ID($pos), ID($neg), + ID($not), ID($pos), ID($buf), ID($neg), ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool), ID($logic_not), ID($slice), ID($lut), ID($sop) }; @@ -339,7 +339,7 @@ struct CellTypes type = ID($shl); if (type != ID($sshr) && type != ID($sshl) && type != ID($shr) && type != ID($shl) && type != ID($shift) && type != ID($shiftx) && - type != ID($pos) && type != ID($neg) && type != ID($not)) { + type != ID($pos) && type != ID($buf) && type != ID($neg) && type != ID($not)) { if (!signed1 || !signed2) signed1 = false, signed2 = false; } @@ -384,7 +384,7 @@ struct CellTypes HANDLE_CELL_TYPE(neg) #undef HANDLE_CELL_TYPE - if (type == ID($_BUF_)) + if (type.in(ID($_BUF_), ID($buf))) return arg1; if (type == ID($_NOT_)) return eval_not(arg1); diff --git a/kernel/compute_graph.h b/kernel/compute_graph.h new file mode 100644 index 00000000000..aeba17f8c1f --- /dev/null +++ b/kernel/compute_graph.h @@ -0,0 +1,403 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Jannis Harder + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef COMPUTE_GRAPH_H +#define COMPUTE_GRAPH_H + +#include +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +template< + typename Fn, // Function type (deduplicated across whole graph) + typename Attr = std::tuple<>, // Call attributes (present in every node) + typename SparseAttr = std::tuple<>, // Sparse call attributes (optional per node) + typename Key = std::tuple<> // Stable keys to refer to nodes +> +struct ComputeGraph +{ + struct Ref; +private: + + // Functions are deduplicated by assigning unique ids + idict functions; + + struct Node { + int fn_index; + int arg_offset; + int arg_count; + Attr attr; + + Node(int fn_index, Attr &&attr, int arg_offset, int arg_count = 0) + : fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(std::move(attr)) {} + + Node(int fn_index, Attr const &attr, int arg_offset, int arg_count = 0) + : fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(attr) {} + }; + + + std::vector nodes; + std::vector args; + dict keys_; + dict sparse_attrs; + +public: + template + struct BaseRef + { + protected: + friend struct ComputeGraph; + Graph *graph_; + int index_; + BaseRef(Graph *graph, int index) : graph_(graph), index_(index) { + log_assert(index_ >= 0); + check(); + } + + void check() const { log_assert(index_ < graph_->size()); } + + Node const &deref() const { check(); return graph_->nodes[index_]; } + + public: + ComputeGraph const &graph() const { return graph_; } + int index() const { return index_; } + + int size() const { return deref().arg_count; } + + BaseRef arg(int n) const + { + Node const &node = deref(); + log_assert(n >= 0 && n < node.arg_count); + return BaseRef(graph_, graph_->args[node.arg_offset + n]); + } + + std::vector::const_iterator arg_indices_cbegin() const + { + Node const &node = deref(); + return graph_->args.cbegin() + node.arg_offset; + } + + std::vector::const_iterator arg_indices_cend() const + { + Node const &node = deref(); + return graph_->args.cbegin() + node.arg_offset + node.arg_count; + } + + Fn const &function() const { return graph_->functions[deref().fn_index]; } + Attr const &attr() const { return deref().attr; } + + bool has_sparse_attr() const { return graph_->sparse_attrs.count(index_); } + + SparseAttr const &sparse_attr() const + { + auto found = graph_->sparse_attrs.find(index_); + log_assert(found != graph_->sparse_attrs.end()); + return found->second; + } + }; + + using ConstRef = BaseRef; + + struct Ref : public BaseRef + { + private: + friend struct ComputeGraph; + Ref(ComputeGraph *graph, int index) : BaseRef(graph, index) {} + Node &deref() const { this->check(); return this->graph_->nodes[this->index_]; } + + public: + Ref(BaseRef ref) : Ref(ref.graph_, ref.index_) {} + + void set_function(Fn const &function) const + { + deref().fn_index = this->graph_->functions(function); + } + + Attr &attr() const { return deref().attr; } + + void append_arg(ConstRef arg) const + { + log_assert(arg.graph_ == this->graph_); + append_arg(arg.index()); + } + + void append_arg(int arg) const + { + log_assert(arg >= 0 && arg < this->graph_->size()); + Node &node = deref(); + if (node.arg_offset + node.arg_count != GetSize(this->graph_->args)) + move_args(node); + this->graph_->args.push_back(arg); + node.arg_count++; + } + + operator ConstRef() const + { + return ConstRef(this->graph_, this->index_); + } + + SparseAttr &sparse_attr() const + { + return this->graph_->sparse_attrs[this->index_]; + } + + void clear_sparse_attr() const + { + this->graph_->sparse_attrs.erase(this->index_); + } + + void assign_key(Key const &key) const + { + this->graph_->keys_.emplace(key, this->index_); + } + + private: + void move_args(Node &node) const + { + auto &args = this->graph_->args; + int old_offset = node.arg_offset; + node.arg_offset = GetSize(args); + for (int i = 0; i != node.arg_count; ++i) + args.push_back(args[old_offset + i]); + } + + }; + + bool has_key(Key const &key) const + { + return keys_.count(key); + } + + dict const &keys() const + { + return keys_; + } + + ConstRef operator()(Key const &key) const + { + auto it = keys_.find(key); + log_assert(it != keys_.end()); + return (*this)[it->second]; + } + + Ref operator()(Key const &key) + { + auto it = keys_.find(key); + log_assert(it != keys_.end()); + return (*this)[it->second]; + } + + int size() const { return GetSize(nodes); } + + ConstRef operator[](int index) const { return ConstRef(this, index); } + Ref operator[](int index) { return Ref(this, index); } + + Ref add(Fn const &function, Attr &&attr) + { + int index = GetSize(nodes); + int fn_index = functions(function); + nodes.emplace_back(fn_index, std::move(attr), GetSize(args)); + return Ref(this, index); + } + + Ref add(Fn const &function, Attr const &attr) + { + int index = GetSize(nodes); + int fn_index = functions(function); + nodes.emplace_back(fn_index, attr, GetSize(args)); + return Ref(this, index); + } + + template + Ref add(Fn const &function, Attr const &attr, T &&args) + { + Ref added = add(function, attr); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + template + Ref add(Fn const &function, Attr &&attr, T &&args) + { + Ref added = add(function, std::move(attr)); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + Ref add(Fn const &function, Attr const &attr, std::initializer_list args) + { + Ref added = add(function, attr); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + Ref add(Fn const &function, Attr &&attr, std::initializer_list args) + { + Ref added = add(function, std::move(attr)); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + template + Ref add(Fn const &function, Attr const &attr, T begin, T end) + { + Ref added = add(function, attr); + for (; begin != end; ++begin) + added.append_arg(*begin); + return added; + } + + void compact_args() + { + std::vector new_args; + for (auto &node : nodes) + { + int new_offset = GetSize(new_args); + for (int i = 0; i < node.arg_count; i++) + new_args.push_back(args[node.arg_offset + i]); + node.arg_offset = new_offset; + } + std::swap(args, new_args); + } + + void permute(std::vector const &perm) + { + log_assert(perm.size() <= nodes.size()); + std::vector inv_perm; + inv_perm.resize(nodes.size(), -1); + for (int i = 0; i < GetSize(perm); ++i) + { + int j = perm[i]; + log_assert(j >= 0 && j < GetSize(nodes)); + log_assert(inv_perm[j] == -1); + inv_perm[j] = i; + } + permute(perm, inv_perm); + } + + void permute(std::vector const &perm, std::vector const &inv_perm) + { + log_assert(inv_perm.size() == nodes.size()); + std::vector new_nodes; + new_nodes.reserve(perm.size()); + dict new_sparse_attrs; + for (int i : perm) + { + int j = GetSize(new_nodes); + new_nodes.emplace_back(std::move(nodes[i])); + auto found = sparse_attrs.find(i); + if (found != sparse_attrs.end()) + new_sparse_attrs.emplace(j, std::move(found->second)); + } + + std::swap(nodes, new_nodes); + std::swap(sparse_attrs, new_sparse_attrs); + + compact_args(); + for (int &arg : args) + { + log_assert(arg < GetSize(inv_perm)); + log_assert(inv_perm[arg] >= 0); + arg = inv_perm[arg]; + } + + for (auto &key : keys_) + { + log_assert(key.second < GetSize(inv_perm)); + log_assert(inv_perm[key.second] >= 0); + key.second = inv_perm[key.second]; + } + } + + struct SccAdaptor + { + private: + ComputeGraph const &graph_; + std::vector indices_; + public: + SccAdaptor(ComputeGraph const &graph) : graph_(graph) + { + indices_.resize(graph.size(), -1); + } + + + typedef int node_type; + + struct node_enumerator { + private: + friend struct SccAdaptor; + int current, end; + node_enumerator(int current, int end) : current(current), end(end) {} + + public: + + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current; + ++current; + return result; + } + }; + + node_enumerator enumerate_nodes() { + return node_enumerator(0, GetSize(indices_)); + } + + + struct successor_enumerator { + private: + friend struct SccAdaptor; + std::vector::const_iterator current, end; + successor_enumerator(std::vector::const_iterator current, std::vector::const_iterator end) : + current(current), end(end) {} + + public: + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = *current; + ++current; + return result; + } + }; + + successor_enumerator enumerate_successors(int index) const { + auto const &ref = graph_[index]; + return successor_enumerator(ref.arg_indices_cbegin(), ref.arg_indices_cend()); + } + + int &dfs_index(node_type const &node) { return indices_[node]; } + + std::vector const &dfs_indices() { return indices_; } + }; + +}; + + + +YOSYS_NAMESPACE_END + + +#endif diff --git a/kernel/constids.inc b/kernel/constids.inc index 00db94af441..3052afb49e1 100644 --- a/kernel/constids.inc +++ b/kernel/constids.inc @@ -43,6 +43,7 @@ X(CE_OVER_SRST) X(CFG_ABITS) X(CFG_DBITS) X(CFG_INIT) +X(chain) X(CI) X(CLK) X(clkbuf_driver) diff --git a/kernel/driver.cc b/kernel/driver.cc index 1e4cd005266..53608c260e4 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -241,6 +241,7 @@ int main(int argc, char **argv) std::string topmodule = ""; std::string perffile = ""; bool scriptfile_tcl = false; + bool scriptfile_python = false; bool print_banner = true; bool print_stats = true; bool call_abort = false; @@ -304,7 +305,12 @@ int main(int argc, char **argv) printf(" execute the commands in the tcl script file (see 'help tcl' for details)\n"); printf("\n"); printf(" -C\n"); - printf(" enters TCL interatcive shell mode\n"); + printf(" enters TCL interactive shell mode\n"); +#endif +#ifdef WITH_PYTHON + printf("\n"); + printf(" -y python_scriptfile\n"); + printf(" execute a python script with libyosys available as a built-in module\n"); #endif printf("\n"); printf(" -p command\n"); @@ -379,7 +385,7 @@ int main(int argc, char **argv) } int opt; - while ((opt = getopt(argc, argv, "MXAQTVCSgm:f:Hh:b:o:p:l:L:qv:tds:c:W:w:e:r:D:P:E:x:B:")) != -1) + while ((opt = getopt(argc, argv, "MXAQTVCSgm:f:Hh:b:o:p:l:L:qv:tds:c:y:W:w:e:r:D:P:E:x:B:")) != -1) { switch (opt) { @@ -464,11 +470,19 @@ int main(int argc, char **argv) case 's': scriptfile = optarg; scriptfile_tcl = false; + scriptfile_python = false; run_shell = false; break; case 'c': scriptfile = optarg; scriptfile_tcl = true; + scriptfile_python = false; + run_shell = false; + break; + case 'y': + scriptfile = optarg; + scriptfile_tcl = false; + scriptfile_python = true; run_shell = false; break; case 'W': @@ -607,8 +621,9 @@ int main(int argc, char **argv) run_pass(vdef_cmd); } - if (scriptfile.empty() || !scriptfile_tcl) { - // Without a TCL script, arguments following '--' are also treated as frontend files + if (scriptfile.empty() || (!scriptfile_tcl && !scriptfile_python)) { + // Without a TCL or Python script, arguments following '--' are also + // treated as frontend files for (int i = optind; i < argc; ++i) frontend_files.push_back(argv[i]); } @@ -636,7 +651,36 @@ int main(int argc, char **argv) if (Tcl_EvalFile(interp, scriptfile.c_str()) != TCL_OK) log_error("TCL interpreter returned an error: %s\n", Tcl_GetStringResult(yosys_get_tcl_interp())); #else - log_error("Can't exectue TCL script: this version of yosys is not built with TCL support enabled.\n"); + log_error("Can't execute TCL script: this version of yosys is not built with TCL support enabled.\n"); +#endif + } else if (scriptfile_python) { +#ifdef WITH_PYTHON + PyObject *sys = PyImport_ImportModule("sys"); + PyObject *new_argv = PyList_New(argc - optind + 1); + PyList_SetItem(new_argv, 0, PyUnicode_FromString(scriptfile.c_str())); + for (int i = optind; i < argc; ++i) + PyList_SetItem(new_argv, i - optind + 1, PyUnicode_FromString(argv[i])); + + PyObject *old_argv = PyObject_GetAttrString(sys, "argv"); + PyObject_SetAttrString(sys, "argv", new_argv); + Py_DECREF(old_argv); + + PyObject *py_path = PyUnicode_FromString(scriptfile.c_str()); + PyObject_SetAttrString(sys, "_yosys_script_path", py_path); + Py_DECREF(py_path); + PyRun_SimpleString("import os, sys; sys.path.insert(0, os.path.dirname(os.path.abspath(sys._yosys_script_path)))"); + + FILE *scriptfp = fopen(scriptfile.c_str(), "r"); + if (scriptfp == nullptr) { + log_error("Failed to open file '%s' for reading.\n", scriptfile.c_str()); + } + if (PyRun_SimpleFile(scriptfp, scriptfile.c_str()) != 0) { + log_flush(); + PyErr_Print(); + log_error("Python interpreter encountered an exception."); + } +#else + log_error("Can't execute Python script: this version of yosys is not built with Python support enabled.\n"); #endif } else run_frontend(scriptfile, "script"); diff --git a/kernel/drivertools.cc b/kernel/drivertools.cc new file mode 100644 index 00000000000..9627a467015 --- /dev/null +++ b/kernel/drivertools.cc @@ -0,0 +1,949 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Jannis Harder + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/drivertools.h" + +YOSYS_NAMESPACE_BEGIN + +DriveBit::DriveBit(SigBit const &bit) +{ + if (bit.is_wire()) + *this = DriveBitWire(bit.wire, bit.offset); + else + *this = bit.data; +} + +void DriveBit::merge(DriveBit const &other) +{ + if (other.type_ == DriveType::NONE) + return; + if (type_ == DriveType::NONE) { + *this = other; + return; + } + if (type_ != DriveType::MULTIPLE) { + DriveBitMultiple multi(std::move(*this)); + *this = std::move(multi); + } + multiple().merge(other); +} + + +void DriveBitMultiple::merge(DriveBit const &single) +{ + if (single.type() == DriveType::NONE) + return; + if (single.type() == DriveType::MULTIPLE) { + merge(single.multiple()); + return; + } + multiple_.emplace(single); +} + +void DriveBitMultiple::merge(DriveBit &&single) +{ + if (single.type() == DriveType::NONE) + return; + if (single.type() == DriveType::MULTIPLE) { + merge(std::move(single.multiple())); + return; + } + multiple_.emplace(std::move(single)); +} + +DriveBitMultiple DriveChunkMultiple::operator[](int i) const +{ + DriveBitMultiple result; + for (auto const &single : multiple_) + result.merge(single[i]); + return result; +} + + +bool DriveChunkWire::can_append(DriveBitWire const &bit) const +{ + return bit.wire == wire && bit.offset == offset + width; +} + +bool DriveChunkWire::try_append(DriveBitWire const &bit) +{ + if (!can_append(bit)) + return false; + width += 1; + return true; +} + +bool DriveChunkWire::try_append(DriveChunkWire const &chunk) +{ + if (chunk.wire != wire || chunk.offset != offset + width) + return false; + width += chunk.width; + return true; +} + +bool DriveChunkPort::can_append(DriveBitPort const &bit) const +{ + return bit.cell == cell && bit.port == port && bit.offset == offset + width; +} + +bool DriveChunkPort::try_append(DriveBitPort const &bit) +{ + if (!can_append(bit)) + return false; + width += 1; + return true; +} + +bool DriveChunkPort::try_append(DriveChunkPort const &chunk) +{ + if (chunk.cell != cell || chunk.port != port || chunk.offset != offset + width) + return false; + width += chunk.width; + return true; +} + +bool DriveChunkMarker::can_append(DriveBitMarker const &bit) const +{ + return bit.marker == marker && bit.offset == offset + width; +} + +bool DriveChunkMarker::try_append(DriveBitMarker const &bit) +{ + if (!can_append(bit)) + return false; + width += 1; + return true; +} + +bool DriveChunkMarker::try_append(DriveChunkMarker const &chunk) +{ + if (chunk.marker != marker || chunk.offset != offset + width) + return false; + width += chunk.width; + return true; +} + + +bool DriveChunkMultiple::can_append(DriveBitMultiple const &bit) const +{ + if (bit.multiple().size() != multiple_.size()) + return false; + + int const_drivers = 0; + for (DriveChunk const &single : multiple_) + if (single.is_constant()) + const_drivers += 1; + + if (const_drivers > 1) + return false; + + for (DriveBit const &single : bit.multiple()) + if (single.is_constant()) + const_drivers -= 1; + + if (const_drivers != 0) + return false; + + for (DriveChunk const &single : multiple_) + { + switch (single.type()) + { + case DriveType::CONSTANT: { + } break; + case DriveType::WIRE: { + auto const &wire = single.wire(); + DriveBit next = DriveBitWire(wire.wire, wire.offset + wire.width); + if (!bit.multiple().count(next)) + return false; + } break; + case DriveType::PORT: { + auto const &port = single.port(); + DriveBit next = DriveBitPort(port.cell, port.port, port.offset + port.width); + if (!bit.multiple().count(next)) + return false; + } break; + case DriveType::MARKER: { + auto const &marker = single.marker(); + DriveBit next = DriveBitMarker(marker.marker, marker.offset + marker.width); + if (!bit.multiple().count(next)) + return false; + } break; + default: + return false; + } + } + + return true; +} + +bool DriveChunkMultiple::can_append(DriveChunkMultiple const &chunk) const +{ + if (chunk.multiple().size() != multiple_.size()) + return false; + + int const_drivers = 0; + for (DriveChunk const &single : multiple_) + if (single.is_constant()) + const_drivers += 1; + + if (const_drivers > 1) + return false; + + for (DriveChunk const &single : chunk.multiple()) + if (single.is_constant()) + const_drivers -= 1; + + if (const_drivers != 0) + return false; + + for (DriveChunk const &single : multiple_) + { + switch (single.type()) + { + case DriveType::CONSTANT: { + } break; + case DriveType::WIRE: { + auto const &wire = single.wire(); + DriveChunk next = DriveChunkWire(wire.wire, wire.offset + wire.width, chunk.size()); + if (!chunk.multiple().count(next)) + return false; + } break; + case DriveType::PORT: { + auto const &port = single.port(); + DriveChunk next = DriveChunkPort(port.cell, port.port, port.offset + port.width, chunk.size()); + if (!chunk.multiple().count(next)) + return false; + } break; + case DriveType::MARKER: { + auto const &marker = single.marker(); + DriveChunk next = DriveChunkMarker(marker.marker, marker.offset + marker.width, chunk.size()); + if (!chunk.multiple().count(next)) + return false; + } break; + default: + return false; + } + } + + return true; +} + +bool DriveChunkMultiple::try_append(DriveBitMultiple const &bit) +{ + if (!can_append(bit)) + return false; + width_ += 1; + State constant; + + for (DriveBit const &single : bit.multiple()) + if (single.is_constant()) + constant = single.constant(); + + for (DriveChunk &single : multiple_) + { + switch (single.type()) + { + case DriveType::CONSTANT: { + single.constant().bits.push_back(constant); + } break; + case DriveType::WIRE: { + single.wire().width += 1; + } break; + case DriveType::PORT: { + single.port().width += 1; + } break; + case DriveType::MARKER: { + single.marker().width += 1; + } break; + default: + log_abort(); + } + } + return true; +} + +bool DriveChunkMultiple::try_append(DriveChunkMultiple const &chunk) +{ + if (!can_append(chunk)) + return false; + int width = chunk.size(); + width_ += width; + Const constant; + + for (DriveChunk const &single : chunk.multiple()) + if (single.is_constant()) + constant = single.constant(); + + for (DriveChunk &single : multiple_) + { + switch (single.type()) + { + case DriveType::CONSTANT: { + auto &bits = single.constant().bits; + bits.insert(bits.end(), constant.bits.begin(), constant.bits.end()); + } break; + case DriveType::WIRE: { + single.wire().width += width; + } break; + case DriveType::PORT: { + single.port().width += width; + } break; + case DriveType::MARKER: { + single.marker().width += width; + } break; + default: + log_abort(); + } + } + return true; +} + +bool DriveChunk::can_append(DriveBit const &bit) const +{ + if (size() == 0) + return true; + if (bit.type() != type_) + return false; + switch (type_) + { + case DriveType::NONE: + return true; + case DriveType::CONSTANT: + return true; + case DriveType::WIRE: + return wire_.can_append(bit.wire()); + case DriveType::PORT: + return port_.can_append(bit.port()); + case DriveType::MULTIPLE: + return multiple_.can_append(bit.multiple()); + default: + log_abort(); + } +} + +bool DriveChunk::try_append(DriveBit const &bit) +{ + if (size() == 0) + *this = bit; + if (bit.type() != type_) + return false; + switch (type_) + { + case DriveType::NONE: + none_ += 1; + return true; + case DriveType::CONSTANT: + constant_.bits.push_back(bit.constant()); + return true; + case DriveType::WIRE: + return wire_.try_append(bit.wire()); + case DriveType::PORT: + return port_.try_append(bit.port()); + case DriveType::MULTIPLE: + return multiple_.try_append(bit.multiple()); + default: + log_abort(); + } +} + + +bool DriveChunk::try_append(DriveChunk const &chunk) +{ + if (size() == 0) + *this = chunk; + if (chunk.type_ != type_) + return false; + switch (type_) + { + case DriveType::NONE: + none_ += chunk.none_; + return true; + case DriveType::CONSTANT: + constant_.bits.insert(constant_.bits.end(), chunk.constant_.bits.begin(), chunk.constant_.bits.end()); + return true; + case DriveType::WIRE: + return wire_.try_append(chunk.wire()); + case DriveType::PORT: + return port_.try_append(chunk.port()); + case DriveType::MARKER: + return marker_.try_append(chunk.marker()); + case DriveType::MULTIPLE: + return multiple_.try_append(chunk.multiple()); + } + log_abort(); +} + +void DriveSpec::append(DriveBit const &bit) +{ + hash_ = 0; + if (!packed()) { + bits_.push_back(bit); + width_ += 1; + return; + } + + if (chunks_.empty() || !chunks_.back().try_append(bit)) + chunks_.emplace_back(bit); + width_ += 1; +} + +void DriveSpec::append(DriveChunk const &chunk) +{ + hash_ = 0; + pack(); + if (chunks_.empty() || !chunks_.back().try_append(chunk)) + chunks_.emplace_back(chunk); + width_ += chunk.size(); +} + +void DriveSpec::pack() const { + if (bits_.empty()) + return; + std::vector bits(std::move(bits_)); + for (auto &bit : bits) + if (chunks_.empty() || !chunks_.back().try_append(bit)) + chunks_.emplace_back(std::move(bit)); +} + +void DriveSpec::unpack() const { + if (chunks_.empty()) + return; + for (auto &chunk : chunks_) + { + for (int i = 0, width = chunk.size(); i != width; ++i) + { + bits_.emplace_back(chunk[i]); + } + } + chunks_.clear(); +} + +void DriveSpec::compute_width() +{ + width_ = 0; + for (auto const &chunk : chunks_) + width_ += chunk.size(); +} + +void DriverMap::DriveBitGraph::add_edge(DriveBitId src, DriveBitId dst) +{ + if (first_edges.emplace(src, dst).first->second == dst) + return; + if (second_edges.emplace(src, dst).first->second == dst) + return; + more_edges[src].emplace(dst); +} + +DriverMap::DriveBitId DriverMap::DriveBitGraph::pop_edge(DriveBitId src) +{ + // TODO unused I think? + auto found_more = more_edges.find(src); + if (found_more != more_edges.end()) { + auto result = found_more->second.pop(); + if (found_more->second.empty()) + more_edges.erase(found_more); + return result; + } + + auto found_second = second_edges.find(src); + if (found_second != second_edges.end()) { + auto result = found_second->second; + second_edges.erase(found_second); + return result; + } + + auto found_first = first_edges.find(src); + if (found_first != first_edges.end()) { + auto result = found_first->second; + first_edges.erase(found_first); + return result; + } + + return DriveBitId(); +} + +void DriverMap::DriveBitGraph::clear(DriveBitId src) +{ + first_edges.erase(src); + second_edges.erase(src); + more_edges.erase(src); +} + +bool DriverMap::DriveBitGraph::contains(DriveBitId src) +{ + return first_edges.count(src); +} + +int DriverMap::DriveBitGraph::count(DriveBitId src) +{ + if (!first_edges.count(src)) + return 0; + if (!second_edges.count(src)) + return 1; + auto found = more_edges.find(src); + if (found == more_edges.end()) + return 2; + return GetSize(found->second) + 2; +} + +DriverMap::DriveBitId DriverMap::DriveBitGraph::at(DriveBitId src, int index) +{ + if (index == 0) + return first_edges.at(src); + else if (index == 1) + return second_edges.at(src); + else + return *more_edges.at(src).element(index - 2); +} + + +DriverMap::BitMode DriverMap::bit_mode(DriveBit const &bit) +{ + switch (bit.type()) + { + case DriveType::NONE: + return BitMode::NONE; + case DriveType::CONSTANT: + // TODO how to handle Sx here? + return bit.constant() == State::Sz ? BitMode::NONE : BitMode::DRIVER; + case DriveType::WIRE: { + auto const &wire = bit.wire(); + bool driver = wire.wire->port_input; + bool driven = wire.wire->port_output; + + if (driver && !driven) + return BitMode::DRIVER; + else if (driven && !driver) + return BitMode::DRIVEN; + else if (driver && driven) + return BitMode::TRISTATE; + else + return keep_wire(bit.wire().wire) ? BitMode::KEEP : BitMode::NONE; + } + case DriveType::PORT: { + auto const &port = bit.port(); + bool driver = celltypes.cell_output(port.cell->type, port.port); + bool driven = celltypes.cell_input(port.cell->type, port.port); + if (driver && !driven) + return BitMode::DRIVER; + else if (driven && !driver) + return BitMode::DRIVEN_UNIQUE; + else + return BitMode::TRISTATE; + } + case DriveType::MARKER: { + // TODO user supplied classification + log_abort(); + } + default: + log_abort(); + } +} + +DriverMap::DriveBitId DriverMap::id_from_drive_bit(DriveBit const &bit) +{ + switch (bit.type()) + { + case DriveType::NONE: + return -1; + case DriveType::CONSTANT: + return (int)bit.constant(); + case DriveType::WIRE: { + auto const &wire_bit = bit.wire(); + int offset = next_offset; + auto insertion = wire_offsets.emplace(wire_bit.wire, offset); + if (insertion.second) { + if (wire_bit.wire->width == 1) { + log_assert(wire_bit.offset == 0); + isolated_drive_bits.emplace(offset, bit); + } else + drive_bits.emplace(offset, DriveBitWire(wire_bit.wire, 0)); + next_offset += wire_bit.wire->width; + } + return insertion.first->second.id + wire_bit.offset; + } + case DriveType::PORT: { + auto const &port_bit = bit.port(); + auto key = std::make_pair(port_bit.cell, port_bit.port); + int offset = next_offset; + auto insertion = port_offsets.emplace(key, offset); + if (insertion.second) { + int width = port_bit.cell->connections().at(port_bit.port).size(); + if (width == 1 && offset == 0) { + log_assert(port_bit.offset == 0); + isolated_drive_bits.emplace(offset, bit); + } else + drive_bits.emplace(offset, DriveBitPort(port_bit.cell, port_bit.port, 0)); + next_offset += width; + } + return insertion.first->second.id + port_bit.offset; + } + default: + log_assert(false && "unsupported DriveType in DriverMap"); + } + log_abort(); +} + +DriveBit DriverMap::drive_bit_from_id(DriveBitId id) +{ + auto found_isolated = isolated_drive_bits.find(id); + if (found_isolated != isolated_drive_bits.end()) + return found_isolated->second; + + auto found = drive_bits.upper_bound(id); + if (found == drive_bits.begin()) { + return id < 0 ? DriveBit() : DriveBit((State) id.id); + } + --found; + DriveBit result = found->second; + if (result.is_wire()) { + result.wire().offset += id.id - found->first.id; + } else { + log_assert(result.is_port()); + result.port().offset += id.id - found->first.id; + } + return result; +} + +void DriverMap::connect_directed_merge(DriveBitId driven_id, DriveBitId driver_id) +{ + if (driven_id == driver_id) + return; + + same_driver.merge(driven_id, driver_id); + + for (int i = 0, end = connected_drivers.count(driven_id); i != end; ++i) + connected_drivers.add_edge(driver_id, connected_drivers.at(driven_id, i)); + + connected_drivers.clear(driven_id); + + for (int i = 0, end = connected_undirected.count(driven_id); i != end; ++i) + connected_undirected.add_edge(driver_id, connected_undirected.at(driven_id, i)); + + connected_undirected.clear(driven_id); +} + +void DriverMap::connect_directed_buffer(DriveBitId driven_id, DriveBitId driver_id) +{ + connected_drivers.add_edge(driven_id, driver_id); +} + +void DriverMap::connect_undirected(DriveBitId a_id, DriveBitId b_id) +{ + connected_undirected.add_edge(a_id, b_id); + connected_undirected.add_edge(b_id, a_id); +} + +void DriverMap::add(Module *module) +{ + for (auto const &conn : module->connections()) + add(conn.first, conn.second); + + for (auto cell : module->cells()) + for (auto const &conn : cell->connections()) + add_port(cell, conn.first, conn.second); +} + +// Add a single bit connection to the driver map. +void DriverMap::add(DriveBit const &a, DriveBit const &b) +{ + DriveBitId a_id = id_from_drive_bit(a); + DriveBitId b_id = id_from_drive_bit(b); + + DriveBitId orig_a_id = a_id; + DriveBitId orig_b_id = b_id; + + a_id = same_driver.find(a_id); + b_id = same_driver.find(b_id); + + if (a_id == b_id) + return; + + BitMode a_mode = bit_mode(orig_a_id == a_id ? a : drive_bit_from_id(a_id)); + BitMode b_mode = bit_mode(orig_b_id == b_id ? b : drive_bit_from_id(b_id)); + + // If either bit is just a wire that we don't need to keep, merge and + // use the other end as representative bit. + if (a_mode == BitMode::NONE && !(b_mode == BitMode::DRIVEN_UNIQUE || b_mode == BitMode::DRIVEN)) + connect_directed_merge(a_id, b_id); + else if (b_mode == BitMode::NONE && !(a_mode == BitMode::DRIVEN_UNIQUE || a_mode == BitMode::DRIVEN)) + connect_directed_merge(b_id, a_id); + // If either bit requires a driven value and has a unique driver, merge + // and use the other end as representative bit. + else if (a_mode == BitMode::DRIVEN_UNIQUE && !(b_mode == BitMode::DRIVEN_UNIQUE || b_mode == BitMode::DRIVEN)) + connect_directed_buffer(a_id, b_id); + else if (b_mode == BitMode::DRIVEN_UNIQUE && !(a_mode == BitMode::DRIVEN_UNIQUE || a_mode == BitMode::DRIVEN)) + connect_directed_buffer(b_id, a_id); + // If either bit only drives a value, store a directed connection from + // it to the other bit. + else if (a_mode == BitMode::DRIVER) + connect_directed_buffer(b_id, a_id); + else if (b_mode == BitMode::DRIVER) + connect_directed_buffer(a_id, b_id); + // Otherwise we store an undirected connection which we will resolve + // during querying. + else + connect_undirected(a_id, b_id); + + return; +} + +// Specialized version that avoids unpacking +void DriverMap::add(SigSpec const &a, SigSpec const &b) +{ + log_assert(a.size() == b.size()); + auto const &a_chunks = a.chunks(); + auto const &b_chunks = b.chunks(); + + auto a_chunk = a_chunks.begin(); + auto a_end = a_chunks.end(); + int a_offset = 0; + + auto b_chunk = b_chunks.begin(); + int b_offset = 0; + + SigChunk tmp_a, tmp_b; + while (a_chunk != a_end) { + int a_width = a_chunk->width - a_offset; + if (a_width == 0) { + a_offset = 0; + ++a_chunk; + continue; + } + int b_width = b_chunk->width - b_offset; + if (b_width == 0) { + b_offset = 0; + ++b_chunk; + continue; + } + int width = std::min(a_width, b_width); + log_assert(width > 0); + + SigChunk const &a_subchunk = + a_offset == 0 && a_width == width ? *a_chunk : a_chunk->extract(a_offset, width); + SigChunk const &b_subchunk = + b_offset == 0 && b_width == width ? *b_chunk : b_chunk->extract(b_offset, width); + + add(a_subchunk, b_subchunk); + + a_offset += width; + b_offset += width; + } +} + +void DriverMap::add_port(Cell *cell, IdString const &port, SigSpec const &b) +{ + int offset = 0; + for (auto const &chunk : b.chunks()) { + add(chunk, DriveChunkPort(cell, port, offset, chunk.width)); + offset += chunk.size(); + } +} + +void DriverMap::orient_undirected(DriveBitId id) +{ + pool &seen = orient_undirected_seen; + pool &drivers = orient_undirected_drivers; + dict &distance = orient_undirected_distance; + seen.clear(); + drivers.clear(); + + seen.emplace(id); + + for (int pos = 0; pos < GetSize(seen); ++pos) { + DriveBitId current = *seen.element(seen.size() - 1 - pos); + DriveBit bit = drive_bit_from_id(current); + + BitMode mode = bit_mode(bit); + + if (mode == BitMode::DRIVER || mode == BitMode::TRISTATE) + drivers.emplace(current); + + if (connected_drivers.contains(current)) + drivers.emplace(current); + + int undirected_driver_count = connected_undirected.count(current); + + for (int i = 0; i != undirected_driver_count; ++i) + seen.emplace(same_driver.find(connected_undirected.at(current, i))); + } + + if (drivers.empty()) + for (auto seen_id : seen) + drivers.emplace(seen_id); + + for (auto driver : drivers) + { + distance.clear(); + distance.emplace(driver, 0); + + for (int pos = 0; pos < GetSize(distance); ++pos) { + auto current_it = distance.element(distance.size() - 1 - pos); + + DriveBitId current = current_it->first; + int undirected_driver_count = connected_undirected.count(current); + + for (int i = 0; i != undirected_driver_count; ++i) + { + DriveBitId next = same_driver.find(connected_undirected.at(current, i)); + auto emplaced = distance.emplace(next, current_it->second + 1); + if (emplaced.first->second == current_it->second + 1) + connected_oriented.add_edge(next, current); + } + } + } + + for (auto seen_id : seen) + oriented_present.emplace(seen_id); +} + +DriveBit DriverMap::operator()(DriveBit const &bit) +{ + if (bit.type() == DriveType::MARKER || bit.type() == DriveType::NONE) + return bit; + if (bit.type() == DriveType::MULTIPLE) + { + DriveBit result; + for (auto const &inner : bit.multiple().multiple()) + result.merge((*this)(inner)); + return result; + } + + DriveBitId bit_id = id_from_drive_bit(bit); + + DriveBitId bit_repr_id = same_driver.find(bit_id); + + DriveBit bit_repr = drive_bit_from_id(bit_repr_id); + + BitMode mode = bit_mode(bit_repr); + + if (mode == BitMode::KEEP && bit_repr_id != bit_id) + return bit_repr; + + int implicit_driver_count = connected_drivers.count(bit_repr_id); + if (connected_undirected.contains(bit_repr_id) && !oriented_present.count(bit_repr_id)) + orient_undirected(bit_repr_id); + + DriveBit driver; + + if (mode == BitMode::DRIVER || mode == BitMode::TRISTATE) + driver = bit_repr; + + for (int i = 0; i != implicit_driver_count; ++i) + driver.merge(drive_bit_from_id(connected_drivers.at(bit_repr_id, i))); + + int oriented_driver_count = connected_oriented.count(bit_repr_id); + for (int i = 0; i != oriented_driver_count; ++i) + driver.merge(drive_bit_from_id(connected_oriented.at(bit_repr_id, i))); + + return driver; +} + +DriveSpec DriverMap::operator()(DriveSpec spec) +{ + DriveSpec result; + + for (int i = 0, width = spec.size(); i != width; ++i) + result.append((*this)(spec[i])); + + return result; +} + +const char *log_signal(DriveChunkWire const &chunk) +{ + const char *id = log_id(chunk.wire->name); + if (chunk.is_whole()) + return id; + if (chunk.width == 1) + return log_str(stringf("%s [%d]", id, chunk.offset)); + return log_str(stringf("%s [%d:%d]", id, chunk.offset + chunk.width - 1, chunk.offset)); +} + + +const char *log_signal(DriveChunkPort const &chunk) +{ + const char *cell_id = log_id(chunk.cell->name); + const char *port_id = log_id(chunk.port); + if (chunk.is_whole()) + return log_str(stringf("%s <%s>", cell_id, port_id)); + if (chunk.width == 1) + return log_str(stringf("%s <%s> [%d]", cell_id, port_id, chunk.offset)); + return log_str(stringf("%s <%s> [%d:%d]", cell_id, port_id, chunk.offset + chunk.width - 1, chunk.offset)); +} + +const char *log_signal(DriveChunkMarker const &chunk) +{ + if (chunk.width == 1) + return log_str(stringf(" [%d]", chunk.marker, chunk.offset)); + return log_str(stringf(" [%d:%d]", chunk.marker, chunk.offset + chunk.width - 1, chunk.offset)); +} + +const char *log_signal(DriveChunk const &chunk) +{ + switch (chunk.type()) + { + case DriveType::NONE: + return log_str(stringf("", chunk.size())); + case DriveType::CONSTANT: + return log_const(chunk.constant()); + case DriveType::WIRE: + return log_signal(chunk.wire()); + case DriveType::PORT: + return log_signal(chunk.port()); + case DriveType::MARKER: + return log_signal(chunk.marker()); + case DriveType::MULTIPLE: { + std::string str = " + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef DRIVERTOOLS_H +#define DRIVERTOOLS_H + +#include + +#include "kernel/rtlil.h" +#include "kernel/sigtools.h" +#include "kernel/celltypes.h" + +YOSYS_NAMESPACE_BEGIN + +// TODO move implementation into a .cc file + +struct DriveBit; + +struct DriveChunkWire; +struct DriveChunkPort; +struct DriveChunkMarker; +struct DriveChunk; + +struct DriveSpec; + +const char *log_signal(DriveChunkWire const &chunk); +const char *log_signal(DriveChunkPort const &chunk); +const char *log_signal(DriveChunkMarker const &chunk); +const char *log_signal(DriveChunk const &chunk); +const char *log_signal(DriveSpec const &chunk); + +enum class DriveType : unsigned char +{ + NONE, + CONSTANT, + WIRE, + PORT, + MULTIPLE, + MARKER, +}; + +struct DriveBitWire +{ + Wire *wire; + int offset; + + DriveBitWire(Wire *wire, int offset) : wire(wire), offset(offset) {} + + bool operator==(const DriveBitWire &other) const + { + return wire == other.wire && offset == other.offset; + } + + bool operator<(const DriveBitWire &other) const + { + if (wire != other.wire) + return wire->name < other.wire->name; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(wire->name.hash(), offset); + } + + operator SigBit() const + { + return SigBit(wire, offset); + } +}; + +struct DriveBitPort +{ + Cell *cell; + IdString port; + int offset; + + DriveBitPort(Cell *cell, IdString port, int offset) : cell(cell), port(port), offset(offset) {} + + bool operator==(const DriveBitPort &other) const + { + return cell == other.cell && port == other.port && offset == other.offset; + } + + bool operator<(const DriveBitPort &other) const + { + if (cell != other.cell) + return cell->name < other.cell->name; + if (port != other.port) + return port < other.port; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(mkhash(cell->name.hash(), port.hash()), offset); + } +}; + + +struct DriveBitMarker +{ + int marker; + int offset; + + DriveBitMarker(int marker, int offset) : marker(marker), offset(offset) {} + + bool operator==(const DriveBitMarker &other) const + { + return marker == other.marker && offset == other.offset; + } + + bool operator<(const DriveBitMarker &other) const + { + if (marker != other.marker) + return marker < other.marker; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(marker, offset); + } + +}; + +struct DriveBitMultiple +{ +private: + pool multiple_; + +public: + DriveBitMultiple(); + DriveBitMultiple(DriveBit const &single); + + pool const &multiple() const { return multiple_; } + + void merge(DriveBitMultiple const &other) + { + for (DriveBit const &single : other.multiple_) + merge(single); + } + + void merge(DriveBitMultiple &&other) + { + for (DriveBit &single : other.multiple_) + merge(std::move(single)); + } + + void merge(DriveBit const &single); + void merge(DriveBit &&single); + + bool operator==(const DriveBitMultiple &other) const + { + return multiple_ == other.multiple_; + } + + unsigned int hash() const + { + return multiple_.hash(); + } +}; + +struct DriveBit +{ +private: + DriveType type_ = DriveType::NONE; + union + { + State constant_; + DriveBitWire wire_; + DriveBitPort port_; + DriveBitMarker marker_; + DriveBitMultiple multiple_; + }; +public: + DriveBit() {} + + DriveBit(SigBit const &bit); + + DriveBit(DriveBit const &other) { *this = other; } + DriveBit(DriveBit &&other) { *this = other; } + + + DriveBit(State constant) { *this = constant; } + DriveBit(DriveBitWire const &wire) { *this = wire; } + DriveBit(DriveBitWire &&wire) { *this = wire; } + DriveBit(DriveBitPort const &port) { *this = port; } + DriveBit(DriveBitPort &&port) { *this = port; } + DriveBit(DriveBitMarker const &marker) { *this = marker; } + DriveBit(DriveBitMarker &&marker) { *this = marker; } + DriveBit(DriveBitMultiple const &multiple) { *this = multiple; } + DriveBit(DriveBitMultiple &&multiple) { *this = multiple; } + + ~DriveBit() { set_none(); } + + void set_none() + { + switch (type_) + { + case DriveType::NONE: + break; + case DriveType::CONSTANT: + break; + case DriveType::WIRE: + wire_.~DriveBitWire(); + break; + case DriveType::PORT: + port_.~DriveBitPort(); + break; + case DriveType::MARKER: + marker_.~DriveBitMarker(); + break; + case DriveType::MULTIPLE: + multiple_.~DriveBitMultiple(); + break; + } + type_ = DriveType::NONE; + } + + DriveBit &operator=(DriveBit const &other) + { + switch (other.type_) + { + case DriveType::NONE: + set_none(); + break; + case DriveType::CONSTANT: + *this = other.constant_; + break; + case DriveType::WIRE: + *this = other.wire_; + break; + case DriveType::PORT: + *this = other.port_; + break; + case DriveType::MARKER: + *this = other.marker_; + break; + case DriveType::MULTIPLE: + *this = other.multiple_; + break; + } + return *this; + } + + DriveBit &operator=(DriveBit &&other) + { + switch (other.type_) + { + case DriveType::NONE: + set_none(); + break; + case DriveType::CONSTANT: + *this = std::move(other.constant_); + break; + case DriveType::WIRE: + *this = std::move(other.wire_); + break; + case DriveType::PORT: + *this = std::move(other.port_); + break; + case DriveType::MARKER: + *this = std::move(other.marker_); + break; + case DriveType::MULTIPLE: + *this = std::move(other.multiple_); + break; + } + return *this; + } + + DriveBit &operator=(State constant) + { + set_none(); + constant_ = constant; + type_ = DriveType::CONSTANT; + return *this; + } + + DriveBit &operator=(DriveBitWire const &wire) + { + set_none(); + new (&wire_) DriveBitWire(wire); + type_ = DriveType::WIRE; + return *this; + } + + DriveBit &operator=(DriveBitWire &&wire) + { + set_none(); + new (&wire_) DriveBitWire(wire); + type_ = DriveType::WIRE; + return *this; + } + + DriveBit &operator=(DriveBitPort const &port) + { + set_none(); + new (&port_) DriveBitPort(port); + type_ = DriveType::PORT; + return *this; + } + + DriveBit &operator=(DriveBitPort &&port) + { + set_none(); + new (&port_) DriveBitPort(port); + type_ = DriveType::PORT; + return *this; + } + + DriveBit &operator=(DriveBitMarker const &marker) + { + set_none(); + new (&marker_) DriveBitMarker(marker); + type_ = DriveType::MARKER; + return *this; + } + + DriveBit &operator=(DriveBitMarker &&marker) + { + set_none(); + new (&marker_) DriveBitMarker(marker); + type_ = DriveType::MARKER; + return *this; + } + + DriveBit &operator=(DriveBitMultiple const &multiple) + { + set_none(); + if (multiple.multiple().empty()) + return *this; + new (&multiple_) DriveBitMultiple(multiple); + type_ = DriveType::MULTIPLE; + return *this; + } + + DriveBit &operator=(DriveBitMultiple &&multiple) + { + set_none(); + if (multiple.multiple().empty()) + return *this; + new (&multiple_) DriveBitMultiple(multiple); + type_ = DriveType::MULTIPLE; + return *this; + } + + unsigned int hash() const + { + unsigned int inner; + switch (type_) + { + case DriveType::NONE: + inner = 0; + break; + case DriveType::CONSTANT: + inner = constant_; + break; + case DriveType::WIRE: + inner = wire_.hash(); + break; + case DriveType::PORT: + inner = port_.hash(); + break; + case DriveType::MARKER: + inner = marker_.hash(); + break; + case DriveType::MULTIPLE: + inner = multiple_.hash(); + break; + } + return mkhash((unsigned int)type_, inner); + } + + bool operator==(const DriveBit &other) const + { + if (type_ != other.type_) + return false; + + switch (type_) + { + case DriveType::NONE: + return true; + case DriveType::CONSTANT: + return constant_ == other.constant_; + case DriveType::WIRE: + return wire_ == other.wire_; + case DriveType::PORT: + return port_ == other.port_; + case DriveType::MARKER: + return marker_ == other.marker_; + case DriveType::MULTIPLE: + return multiple_ == other.multiple_; + } + log_abort(); + } + + bool operator!=(const DriveBit &other) const + { + return !(*this == other); + } + + bool operator<(const DriveBit &other) const + { + if (type_ != other.type_) + return type_ < other.type_; + switch (type_) + { + case DriveType::NONE: + return false; + case DriveType::CONSTANT: + return constant_ < other.constant_; + case DriveType::WIRE: + return wire_ < other.wire_; + case DriveType::PORT: + return port_ < other.port_; + case DriveType::MARKER: + return marker_ < other.marker_; + case DriveType::MULTIPLE: + log_assert(!"TODO"); + } + log_abort(); + } + + + DriveType type() const { return type_; } + + bool is_none() const { return type_ == DriveType::NONE; } + bool is_constant() const { return type_ == DriveType::CONSTANT; } + bool is_wire() const { return type_ == DriveType::WIRE; } + bool is_port() const { return type_ == DriveType::PORT; } + bool is_marker() const { return type_ == DriveType::MARKER; } + bool is_multiple() const { return type_ == DriveType::MULTIPLE; } + + State &constant() { log_assert(is_constant()); return constant_; } + State const &constant() const { log_assert(is_constant()); return constant_; } + DriveBitWire &wire() { log_assert(is_wire()); return wire_; } + DriveBitWire const &wire() const { log_assert(is_wire()); return wire_; } + DriveBitPort &port() { log_assert(is_port()); return port_; } + DriveBitPort const &port() const { log_assert(is_port()); return port_; } + DriveBitMarker &marker() { log_assert(is_marker()); return marker_; } + DriveBitMarker const &marker() const { log_assert(is_marker()); return marker_; } + DriveBitMultiple &multiple() { log_assert(is_multiple()); return multiple_; } + DriveBitMultiple const &multiple() const { log_assert(is_multiple()); return multiple_; } + + void merge(DriveBit const &other); + +}; + +inline DriveBitMultiple::DriveBitMultiple() {} +inline DriveBitMultiple::DriveBitMultiple(DriveBit const &single) +{ + multiple_.emplace(single); +} + +struct DriveChunkWire +{ + Wire *wire; + int offset; + int width; + + DriveChunkWire(Wire *wire, int offset, int width) : wire(wire), offset(offset), width(width) {} + DriveChunkWire(DriveBitWire const &bit) : wire(bit.wire), offset(bit.offset), width(1) {} + + int size() const { return width; } + + DriveBitWire operator[](int i) const + { + log_assert(i >= 0 && i < width); + return DriveBitWire(wire, offset + i); + } + + bool can_append(DriveBitWire const &bit) const; + bool try_append(DriveBitWire const &bit); + bool try_append(DriveChunkWire const &chunk); + + // Whether this chunk is a whole wire + bool is_whole() const { return offset == 0 && width == wire->width; } + + bool operator==(const DriveChunkWire &other) const + { + return wire == other.wire && offset == other.offset && width == other.width; + } + + bool operator<(const DriveChunkWire &other) const + { + if (wire != other.wire) + return wire->name < other.wire->name; + if (width != other.width) + return width < other.width; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(mkhash(wire->name.hash(), width), offset); + } + + explicit operator SigChunk() const + { + return SigChunk(wire, offset, width); + } +}; + +struct DriveChunkPort +{ + Cell *cell; + IdString port; + int offset; + int width; + + DriveChunkPort(Cell *cell, IdString port, int offset, int width) : + cell(cell), port(port), offset(offset), width(width) { } + DriveChunkPort(Cell *cell, IdString port) : + cell(cell), port(port), offset(0), width(GetSize(cell->connections().at(port))) { } + DriveChunkPort(Cell *cell, std::pair const &conn) : + cell(cell), port(conn.first), offset(0), width(GetSize(conn.second)) { } + DriveChunkPort(DriveBitPort const &bit) : + cell(bit.cell), port(bit.port), offset(bit.offset), width(1) { } + + int size() const { return width; } + + DriveBitPort operator[](int i) const + { + log_assert(i >= 0 && i < width); + return DriveBitPort(cell, port, offset + i); + } + + bool can_append(DriveBitPort const &bit) const; + bool try_append(DriveBitPort const &bit); + bool try_append(DriveChunkPort const &chunk); + + // Whether this chunk is a whole port + bool is_whole() const + { + return offset == 0 && width == cell->connections().at(port).size(); + } + + bool operator==(const DriveChunkPort &other) const + { + return cell == other.cell && port == other.port && offset == other.offset && width == other.width; + } + + bool operator<(const DriveChunkPort &other) const + { + if (cell != other.cell) + return cell->name < other.cell->name; + if (port != other.port) + return port < other.port; + if (width != other.width) + return width < other.width; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(mkhash(mkhash(cell->name.hash(), port.hash()), width), offset); + } +}; + + +struct DriveChunkMarker +{ + int marker; + int offset; + int width; + + DriveChunkMarker(int marker, int offset, int width) : + marker(marker), offset(offset), width(width) {} + DriveChunkMarker(DriveBitMarker const &bit) : + marker(bit.marker), offset(bit.offset), width(1) {} + + int size() const { return width; } + + DriveBitMarker operator[](int i) const + { + log_assert(i >= 0 && i < width); + return DriveBitMarker(marker, offset + i); + } + + bool can_append(DriveBitMarker const &bit) const; + bool try_append(DriveBitMarker const &bit); + bool try_append(DriveChunkMarker const &chunk); + + bool operator==(const DriveChunkMarker &other) const + { + return marker == other.marker && offset == other.offset && width == other.width; + } + + bool operator<(const DriveChunkMarker &other) const + { + if (marker != other.marker) + return marker < other.marker; + if (width != other.width) + return width < other.width; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(mkhash(marker, width), offset); + } +}; + +struct DriveChunkMultiple +{ +private: + mutable pool multiple_; + int width_; + +public: + pool const &multiple() const { return multiple_; } + + DriveChunkMultiple(DriveBitMultiple const &bit); + + int size() const { return width_; } + + DriveBitMultiple operator[](int i) const; + + bool can_append(DriveBitMultiple const &bit) const; + + bool try_append(DriveBitMultiple const &bit); + + + bool can_append(DriveChunkMultiple const &bit) const; + + bool try_append(DriveChunkMultiple const &bit); + + bool operator==(const DriveChunkMultiple &other) const + { + return width_ == other.width_ && multiple_ == other.multiple_; + } + + bool operator<(const DriveChunkMultiple &other) const + { + if (multiple_.size() < other.multiple_.size()) + + multiple_.sort(); + return false; // TODO implement, canonicalize order + } + + unsigned int hash() const + { + return mkhash(width_, multiple_.hash()); + } +}; + +struct DriveChunk +{ +private: + DriveType type_ = DriveType::NONE; + union + { + int none_; + Const constant_; + DriveChunkWire wire_; + DriveChunkPort port_; + DriveChunkMarker marker_; + DriveChunkMultiple multiple_; + }; + +public: + DriveChunk() { set_none(); } + + DriveChunk(DriveChunk const &other) { *this = other; } + DriveChunk(DriveChunk &&other) { *this = other; } + + DriveChunk(DriveBit const &other) { *this = other; } + + DriveChunk(Const const &constant) { *this = constant; } + DriveChunk(Const &&constant) { *this = constant; } + DriveChunk(DriveChunkWire const &wire) { *this = wire; } + DriveChunk(DriveChunkWire &&wire) { *this = wire; } + DriveChunk(DriveChunkPort const &port) { *this = port; } + DriveChunk(DriveChunkPort &&port) { *this = port; } + DriveChunk(DriveChunkMarker const &marker) { *this = marker; } + DriveChunk(DriveChunkMarker &&marker) { *this = marker; } + DriveChunk(DriveChunkMultiple const &multiple) { *this = multiple; } + DriveChunk(DriveChunkMultiple &&multiple) { *this = multiple; } + + ~DriveChunk() { set_none(); } + + DriveBit operator[](int i) const + { + switch (type_) + { + case DriveType::NONE: + return DriveBit(); + case DriveType::CONSTANT: + return constant_[i]; + case DriveType::WIRE: + return wire_[i]; + case DriveType::PORT: + return port_[i]; + case DriveType::MARKER: + return marker_[i]; + case DriveType::MULTIPLE: + return multiple_[i]; + } + log_abort(); + } + + void set_none(int width = 0) + { + switch (type_) + { + case DriveType::NONE: + none_ = width; + break; + case DriveType::CONSTANT: + constant_.~Const(); + break; + case DriveType::WIRE: + wire_.~DriveChunkWire(); + break; + case DriveType::PORT: + port_.~DriveChunkPort(); + break; + case DriveType::MARKER: + marker_.~DriveChunkMarker(); + break; + case DriveType::MULTIPLE: + multiple_.~DriveChunkMultiple(); + break; + } + type_ = DriveType::NONE; + none_ = width; + } + + DriveChunk &operator=(DriveChunk const &other) + { + switch (other.type_) + { + case DriveType::NONE: + set_none(other.none_); + break; + case DriveType::CONSTANT: + *this = other.constant_; + break; + case DriveType::WIRE: + *this = other.wire_; + break; + case DriveType::PORT: + *this = other.port_; + break; + case DriveType::MARKER: + *this = other.marker_; + break; + case DriveType::MULTIPLE: + *this = other.multiple_; + break; + } + return *this; + } + + DriveChunk &operator=(DriveChunk &&other) + { + switch (other.type_) + { + case DriveType::NONE: + set_none(other.none_); + break; + case DriveType::CONSTANT: + *this = std::move(other.constant_); + break; + case DriveType::WIRE: + *this = std::move(other.wire_); + break; + case DriveType::PORT: + *this = std::move(other.port_); + break; + case DriveType::MARKER: + *this = std::move(other.marker_); + break; + case DriveType::MULTIPLE: + *this = std::move(other.multiple_); + break; + } + return *this; + } + + DriveChunk &operator=(Const const &constant) + { + set_none(); + new (&constant_) Const(constant); + type_ = DriveType::CONSTANT; + return *this; + } + + DriveChunk &operator=(Const &&constant) + { + set_none(); + new (&constant_) Const(std::move(constant)); + type_ = DriveType::CONSTANT; + return *this; + } + + DriveChunk &operator=(DriveChunkWire const &wire) + { + set_none(); + new (&wire_) DriveChunkWire(wire); + type_ = DriveType::WIRE; + return *this; + } + + DriveChunk &operator=(DriveChunkWire &&wire) + { + set_none(); + new (&wire_) DriveChunkWire(wire); + type_ = DriveType::WIRE; + return *this; + } + + DriveChunk &operator=(DriveChunkPort const &port) + { + set_none(); + new (&port_) DriveChunkPort(port); + type_ = DriveType::PORT; + return *this; + } + + DriveChunk &operator=(DriveChunkPort &&port) + { + set_none(); + new (&port_) DriveChunkPort(port); + type_ = DriveType::PORT; + return *this; + } + + DriveChunk &operator=(DriveChunkMarker const &marker) + { + set_none(); + new (&marker_) DriveChunkMarker(marker); + type_ = DriveType::MARKER; + return *this; + } + + DriveChunk &operator=(DriveChunkMarker &&marker) + { + set_none(); + new (&marker_) DriveChunkMarker(marker); + type_ = DriveType::MARKER; + return *this; + } + + DriveChunk &operator=(DriveChunkMultiple const &multiple) + { + set_none(multiple.size()); + if (multiple.multiple().empty()) + return *this; + new (&multiple_) DriveChunkMultiple(multiple); + type_ = DriveType::MULTIPLE; + return *this; + } + + DriveChunk &operator=(DriveChunkMultiple &&multiple) + { + set_none(multiple.size()); + if (multiple.multiple().empty()) + return *this; + new (&multiple_) DriveChunkMultiple(multiple); + type_ = DriveType::MULTIPLE; + return *this; + } + + DriveChunk &operator=(DriveBit const &other) + { + switch (other.type()) + { + case DriveType::NONE: + set_none(1); + break; + case DriveType::CONSTANT: + *this = Const(other.constant()); + break; + case DriveType::WIRE: + *this = DriveChunkWire(other.wire()); + break; + case DriveType::PORT: + *this = DriveChunkPort(other.port()); + break; + case DriveType::MARKER: + *this = DriveChunkMarker(other.marker()); + break; + case DriveType::MULTIPLE: + *this = DriveChunkMultiple(other.multiple()); + break; + } + return *this; + } + + bool can_append(DriveBit const &bit) const; + bool try_append(DriveBit const &bit); + bool try_append(DriveChunk const &chunk); + + unsigned int hash() const + { + unsigned int inner; + switch (type_) + { + case DriveType::NONE: + inner = 0; + break; + case DriveType::CONSTANT: + inner = constant_.hash(); + break; + case DriveType::WIRE: + inner = wire_.hash(); + break; + case DriveType::PORT: + inner = port_.hash(); + break; + case DriveType::MARKER: + inner = marker_.hash(); + break; + case DriveType::MULTIPLE: + inner = multiple_.hash(); + break; + } + return mkhash((unsigned int)type_, inner); + } + + bool operator==(const DriveChunk &other) const + { + if (type_ != other.type_) + return false; + + switch (type_) + { + case DriveType::NONE: + return true; + case DriveType::CONSTANT: + return constant_ == other.constant_; + case DriveType::WIRE: + return wire_ == other.wire_; + case DriveType::PORT: + return port_ == other.port_; + case DriveType::MARKER: + return marker_ == other.marker_; + case DriveType::MULTIPLE: + return multiple_ == other.multiple_; + } + log_abort(); + } + + bool operator!=(const DriveChunk &other) const + { + return !(*this == other); + } + + bool operator<(const DriveChunk &other) const + { + if (type_ != other.type_) + return type_ < other.type_; + switch (type_) + { + case DriveType::NONE: + return false; + case DriveType::CONSTANT: + return constant_ < other.constant_; + case DriveType::WIRE: + return wire_ < other.wire_; + case DriveType::PORT: + return port_ < other.port_; + case DriveType::MARKER: + return marker_ < other.marker_; + case DriveType::MULTIPLE: + return multiple_ < other.multiple_; + } + log_abort(); + } + + DriveType type() const { return type_; } + + bool is_none() const { return type_ == DriveType::NONE; } + bool is_constant() const { return type_ == DriveType::CONSTANT; } + bool is_wire() const { return type_ == DriveType::WIRE; } + bool is_port() const { return type_ == DriveType::PORT; } + bool is_marker() const { return type_ == DriveType::MARKER; } + bool is_multiple() const { return type_ == DriveType::MULTIPLE; } + + Const &constant() { log_assert(is_constant()); return constant_; } + Const const &constant() const { log_assert(is_constant()); return constant_; } + DriveChunkWire &wire() { log_assert(is_wire()); return wire_; } + DriveChunkWire const &wire() const { log_assert(is_wire()); return wire_; } + DriveChunkPort &port() { log_assert(is_port()); return port_; } + DriveChunkPort const &port() const { log_assert(is_port()); return port_; } + DriveChunkMarker &marker() { log_assert(is_marker()); return marker_; } + DriveChunkMarker const &marker() const { log_assert(is_marker()); return marker_; } + DriveChunkMultiple &multiple() { log_assert(is_multiple()); return multiple_; } + DriveChunkMultiple const &multiple() const { log_assert(is_multiple()); return multiple_; } + + + int size() const + { + switch (type_) + { + case DriveType::NONE: + return none_; + case DriveType::CONSTANT: + return constant_.size(); + case DriveType::WIRE: + return wire_.size(); + case DriveType::PORT: + return port_.size(); + case DriveType::MARKER: + return marker_.size(); + case DriveType::MULTIPLE: + return multiple_.size(); + } + log_abort(); + } +}; + +inline DriveChunkMultiple::DriveChunkMultiple(DriveBitMultiple const &bit) + : width_(1) +{ + for (auto const &bit : bit.multiple()) + multiple_.emplace(bit); +} + +struct DriveSpec +{ +private: + int width_ = 0; + mutable std::vector chunks_; + mutable std::vector bits_; + mutable unsigned int hash_ = 0; +public: + + inline bool packed() const { + return bits_.empty(); + } + + DriveSpec() {} + + DriveSpec(DriveChunk const &chunk) { *this = chunk; } + DriveSpec(DriveChunkWire const &chunk) { *this = chunk; } + DriveSpec(DriveChunkPort const &chunk) { *this = chunk; } + DriveSpec(DriveChunkMarker const &chunk) { *this = chunk; } + DriveSpec(DriveChunkMultiple const &chunk) { *this = chunk; } + + DriveSpec(DriveBit const &bit) { *this = bit; } + DriveSpec(DriveBitWire const &bit) { *this = bit; } + DriveSpec(DriveBitPort const &bit) { *this = bit; } + DriveSpec(DriveBitMarker const &bit) { *this = bit; } + DriveSpec(DriveBitMultiple const &bit) { *this = bit; } + + DriveSpec(std::vector const &chunks) : chunks_(chunks) { compute_width(); } + + DriveSpec(std::vector const &bits) + { + for (auto const &bit : bits) + append(bit); + } + + DriveSpec(SigSpec const &sig) + { + // TODO: converting one chunk at a time would be faster + for (auto const &bit : sig.bits()) + append(bit); + } + + std::vector const &chunks() const { pack(); return chunks_; } + std::vector const &bits() const { unpack(); return bits_; } + + int size() const { return width_; } + + void append(DriveBit const &bit); + + void append(DriveChunk const &chunk); + + void pack() const; + + void unpack() const; + + DriveBit &operator[](int index) + { + log_assert(index >= 0 && index < size()); + unpack(); + return bits_[index]; + } + + const DriveBit &operator[](int index) const + { + log_assert(index >= 0 && index < size()); + unpack(); + return bits_[index]; + } + + void clear() + { + chunks_.clear(); + bits_.clear(); + width_ = 0; + } + + DriveSpec &operator=(DriveChunk const &chunk) + { + chunks_.clear(); + bits_.clear(); + append(chunk); + return *this; + } + + DriveSpec &operator=(DriveChunkWire const &chunk) { return *this = DriveChunk(chunk); } + DriveSpec &operator=(DriveChunkPort const &chunk) { return *this = DriveChunk(chunk); } + DriveSpec &operator=(DriveChunkMarker const &chunk) { return *this = DriveChunk(chunk); } + DriveSpec &operator=(DriveChunkMultiple const &chunk) { return *this = DriveChunk(chunk); } + + DriveSpec &operator=(DriveBit const &bit) + { + chunks_.clear(); + bits_.clear(); + append(bit); + return *this; + } + + DriveSpec &operator=(DriveBitWire const &bit) { return *this = DriveBit(bit); } + DriveSpec &operator=(DriveBitPort const &bit) { return *this = DriveBit(bit); } + DriveSpec &operator=(DriveBitMarker const &bit) { return *this = DriveBit(bit); } + DriveSpec &operator=(DriveBitMultiple const &bit) { return *this = DriveBit(bit); } + + unsigned int hash() const { + if (hash_ != 0) return hash_; + + pack(); + hash_ = hash_ops>().hash(chunks_); + hash_ |= (hash_ == 0); + return hash_; + } + + bool operator==(DriveSpec const &other) const { + if (size() != other.size() || hash() != other.hash()) + return false; + return chunks() == other.chunks(); + } + +private: + void compute_width(); +}; + + + +struct DriverMap +{ + CellTypes celltypes; + + DriverMap() { celltypes.setup(); } + DriverMap(Design *design) { celltypes.setup(); celltypes.setup_design(design); } + +private: + + // Internally we represent all DriveBits by mapping them to DriveBitIds + // which use less memory and are cheaper to compare. + struct DriveBitId + { + int id = -1; + + DriveBitId() {}; + + DriveBitId(int id) : id(id) { } + + bool operator==(const DriveBitId &other) const { return id == other.id; } + bool operator!=(const DriveBitId &other) const { return id != other.id; } + bool operator<(const DriveBitId &other) const { return id < other.id; } + unsigned int hash() const { return id; } + }; + // Essentially a dict> but using less memory + // and fewer allocations + struct DriveBitGraph { + dict first_edges; + dict second_edges; + dict> more_edges; + + void add_edge(DriveBitId src, DriveBitId dst); + DriveBitId pop_edge(DriveBitId src); + void clear(DriveBitId src); + bool contains(DriveBitId src); + int count(DriveBitId src); + + DriveBitId at(DriveBitId src, int index); + }; + + // The following two maps maintain a sparse DriveBit to DriveBitId mapping. + // This saves a lot of memory compared to a `dict` or + // `idict`. + + // Maps wires to the first DriveBitId of the consecutive range used for + // that wire. + dict wire_offsets; + + // Maps cell ports to a the first DriveBitId of the consecutive range used + // for that cell port. + dict, DriveBitId> port_offsets; + + // For the inverse map that maps DriveBitIds back to DriveBits we use a + // sorted map containing only the first DriveBit for each wire and cell + // port. + std::map drive_bits; + + // As a memory optimization for gate level net lists we store single-bit + // wires and cell ports in a `dict` which requires less memory and fewer + // allocations than `std::map` but doesn't support the kind of lookups we + // need for a sparse coarse grained mapping. + dict isolated_drive_bits; + + // Used for allocating DriveBitIds, none and constant states use a fixewd + // mapping to the first few ids, which we need to skip. + int next_offset = 1 + (int)State::Sm; + + // Union-Find over directly connected bits that share the same single + // driver or are undriven. We never merge connections between drivers + // and/or kept wires. + mfp same_driver; + + // For each bit, store a set of connected driver bits for which the + // explicit connection should be preserved and the driving direction is + // locally unambiguous (one side only drives or requires a driven value). + DriveBitGraph connected_drivers; + + // For each bit, store a set of connected driver bits for which the + // explicit connection should be preserved and the driving direction is + // locally ambiguous. Every such ambiguous connection is also present in + // the reverse direction and has to be resolved when querying drivers. + DriveBitGraph connected_undirected; + + // Subset of `connected_undirected` for caching the resolved driving + // direction. In case multiple drivers are present this can still contain + // both orientations of a single connection, but for a single driver only + // one will be present. + DriveBitGraph connected_oriented; + + // Stores for which bits we already resolved the orientation (cached in + // `connected_oriented`). + pool oriented_present; + + + enum class BitMode { + NONE = 0, // Not driven, no need to keep wire + DRIVEN = 1, // Not driven, uses a value driven elsewhere + DRIVEN_UNIQUE = 2, // Uses a value driven elsewhere, has at most one direct connection + KEEP = 3, // Wire that should be kept + TRISTATE = 4, // Can drive a value but can also use a value driven elsewhere + DRIVER = 5, // Drives a value + }; + + BitMode bit_mode(DriveBit const &bit); + DriveBitId id_from_drive_bit(DriveBit const &bit); + DriveBit drive_bit_from_id(DriveBitId id); + + void connect_directed_merge(DriveBitId driven_id, DriveBitId driver_id); + void connect_directed_buffer(DriveBitId driven_id, DriveBitId driver_id); + void connect_undirected(DriveBitId a_id, DriveBitId b_id); + +public: + + void add(Module *module); + + // Add a single bit connection to the driver map. + void add(DriveBit const &a, DriveBit const &b); + + template + static constexpr bool is_sig_type() { + return + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value; + } + + // We use the enable_if to produce better compiler errors when unsupported + // types are used + template + typename std::enable_if() && is_sig_type()>::type + add(T const &a, U const &b) + { + log_assert(a.size() == b.size()); + for (int i = 0; i != GetSize(a); ++i) + add(DriveBit(a[i]), DriveBit(b[i])); + } + + + // Specialized version that avoids unpacking + void add(SigSpec const &a, SigSpec const &b); + +private: + void add_port(Cell *cell, IdString const &port, SigSpec const &b); + + // Only used a local variables in `orient_undirected`, always cleared, only + // stored to reduce allocations. + pool orient_undirected_seen; + pool orient_undirected_drivers; + dict orient_undirected_distance; + + void orient_undirected(DriveBitId id); + +public: + DriveBit operator()(DriveBit const &bit); + + DriveSpec operator()(DriveSpec spec); + +private: + bool keep_wire(Wire *wire) { + // TODO configurable + return wire->has_attribute(ID(keep)); + } +}; + +YOSYS_NAMESPACE_END + +#endif diff --git a/kernel/ff.h b/kernel/ff.h index e684d3c4314..d6cf99b9bf8 100644 --- a/kernel/ff.h +++ b/kernel/ff.h @@ -131,9 +131,11 @@ struct FfData { bool is_fine; // True if this FF is an $anyinit cell. Depends on has_gclk. bool is_anyinit; - // Polarities, corresponding to sig_*. True means active-high, false - // means active-low. + // Polarities, corresponding to sig_*. + // True means rising edge, false means falling edge. bool pol_clk; + // True means active-high, false + // means active-low. bool pol_ce; bool pol_aload; bool pol_arst; diff --git a/kernel/functional.cc b/kernel/functional.cc new file mode 100644 index 00000000000..c270a2bb631 --- /dev/null +++ b/kernel/functional.cc @@ -0,0 +1,853 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/functional.h" +#include "kernel/topo_scc.h" +#include "ff.h" +#include "ffinit.h" +#include + +YOSYS_NAMESPACE_BEGIN +namespace Functional { + +const char *fn_to_string(Fn fn) { + switch(fn) { + case Fn::invalid: return "invalid"; + case Fn::buf: return "buf"; + case Fn::slice: return "slice"; + case Fn::zero_extend: return "zero_extend"; + case Fn::sign_extend: return "sign_extend"; + case Fn::concat: return "concat"; + case Fn::add: return "add"; + case Fn::sub: return "sub"; + case Fn::mul: return "mul"; + case Fn::unsigned_div: return "unsigned_div"; + case Fn::unsigned_mod: return "unsigned_mod"; + case Fn::bitwise_and: return "bitwise_and"; + case Fn::bitwise_or: return "bitwise_or"; + case Fn::bitwise_xor: return "bitwise_xor"; + case Fn::bitwise_not: return "bitwise_not"; + case Fn::reduce_and: return "reduce_and"; + case Fn::reduce_or: return "reduce_or"; + case Fn::reduce_xor: return "reduce_xor"; + case Fn::unary_minus: return "unary_minus"; + case Fn::equal: return "equal"; + case Fn::not_equal: return "not_equal"; + case Fn::signed_greater_than: return "signed_greater_than"; + case Fn::signed_greater_equal: return "signed_greater_equal"; + case Fn::unsigned_greater_than: return "unsigned_greater_than"; + case Fn::unsigned_greater_equal: return "unsigned_greater_equal"; + case Fn::logical_shift_left: return "logical_shift_left"; + case Fn::logical_shift_right: return "logical_shift_right"; + case Fn::arithmetic_shift_right: return "arithmetic_shift_right"; + case Fn::mux: return "mux"; + case Fn::constant: return "constant"; + case Fn::input: return "input"; + case Fn::state: return "state"; + case Fn::memory_read: return "memory_read"; + case Fn::memory_write: return "memory_write"; + } + log_error("fn_to_string: unknown Functional::Fn value %d", (int)fn); +} + +vector IR::inputs(IdString kind) const { + vector ret; + for (const auto &[name, input] : _inputs) + if(input.kind == kind) + ret.push_back(&input); + return ret; +} + +vector IR::outputs(IdString kind) const { + vector ret; + for (const auto &[name, output] : _outputs) + if(output.kind == kind) + ret.push_back(&output); + return ret; +} + +vector IR::states(IdString kind) const { + vector ret; + for (const auto &[name, state] : _states) + if(state.kind == kind) + ret.push_back(&state); + return ret; +} + +vector IR::all_inputs() const { + vector ret; + for (const auto &[name, input] : _inputs) + ret.push_back(&input); + return ret; +} + +vector IR::all_outputs() const { + vector ret; + for (const auto &[name, output] : _outputs) + ret.push_back(&output); + return ret; +} + +vector IR::all_states() const { + vector ret; + for (const auto &[name, state] : _states) + ret.push_back(&state); + return ret; +} + +struct PrintVisitor : DefaultVisitor { + std::function np; + PrintVisitor(std::function np) : np(np) { } + // as a general rule the default handler is good enough iff the only arguments are of type Node + std::string slice(Node, Node a, int offset, int out_width) override { return "slice(" + np(a) + ", " + std::to_string(offset) + ", " + std::to_string(out_width) + ")"; } + std::string zero_extend(Node, Node a, int out_width) override { return "zero_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } + std::string sign_extend(Node, Node a, int out_width) override { return "sign_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } + std::string constant(Node, RTLIL::Const const& value) override { return "constant(" + value.as_string() + ")"; } + std::string input(Node, IdString name, IdString kind) override { return "input(" + name.str() + ", " + kind.str() + ")"; } + std::string state(Node, IdString name, IdString kind) override { return "state(" + name.str() + ", " + kind.str() + ")"; } + std::string default_handler(Node self) override { + std::string ret = fn_to_string(self.fn()); + ret += "("; + for(size_t i = 0; i < self.arg_count(); i++) { + if(i > 0) ret += ", "; + ret += np(self.arg(i)); + } + ret += ")"; + return ret; + } +}; + +std::string Node::to_string() +{ + return to_string([](Node n) { return RTLIL::unescape_id(n.name()); }); +} + +std::string Node::to_string(std::function np) +{ + return visit(PrintVisitor(np)); +} + +class CellSimplifier { + Factory &factory; + Node sign(Node a) { + return factory.slice(a, a.width() - 1, 1); + } + Node neg_if(Node a, Node s) { + return factory.mux(a, factory.unary_minus(a), s); + } + Node abs(Node a) { + return neg_if(a, sign(a)); + } + Node handle_shift(Node a, Node b, bool is_right, bool is_signed) { + // to prevent new_width == 0, we handle this case separately + if(a.width() == 1) { + if(!is_signed) + return factory.bitwise_and(a, factory.bitwise_not(factory.reduce_or(b))); + else + return a; + } + int new_width = ceil_log2(a.width()); + Node b_truncated = factory.extend(b, new_width, false); + Node y = + !is_right ? factory.logical_shift_left(a, b_truncated) : + !is_signed ? factory.logical_shift_right(a, b_truncated) : + factory.arithmetic_shift_right(a, b_truncated); + if(b.width() <= new_width) + return y; + Node overflow = factory.unsigned_greater_equal(b, factory.constant(RTLIL::Const(a.width(), b.width()))); + Node y_if_overflow = is_signed ? factory.extend(sign(a), a.width(), true) : factory.constant(RTLIL::Const(State::S0, a.width())); + return factory.mux(y, y_if_overflow, overflow); + } +public: + Node logical_shift_left(Node a, Node b) { return handle_shift(a, b, false, false); } + Node logical_shift_right(Node a, Node b) { return handle_shift(a, b, true, false); } + Node arithmetic_shift_right(Node a, Node b) { return handle_shift(a, b, true, true); } + Node bitwise_mux(Node a, Node b, Node s) { + Node aa = factory.bitwise_and(a, factory.bitwise_not(s)); + Node bb = factory.bitwise_and(b, s); + return factory.bitwise_or(aa, bb); + } + CellSimplifier(Factory &f) : factory(f) {} +private: + Node handle_pow(Node a0, Node b, int y_width, bool is_signed) { + Node a = factory.extend(a0, y_width, is_signed); + Node r = factory.constant(Const(1, y_width)); + for(int i = 0; i < b.width(); i++) { + Node b_bit = factory.slice(b, i, 1); + r = factory.mux(r, factory.mul(r, a), b_bit); + a = factory.mul(a, a); + } + if (is_signed) { + Node a_ge_1 = factory.unsigned_greater_than(abs(a0), factory.constant(Const(1, a0.width()))); + Node zero_result = factory.bitwise_and(a_ge_1, sign(b)); + r = factory.mux(r, factory.constant(Const(0, y_width)), zero_result); + } + return r; + } + Node handle_bmux(Node a, Node s, int a_offset, int width, int sn) { + if(sn < 1) + return factory.slice(a, a_offset, width); + else { + Node y0 = handle_bmux(a, s, a_offset, width, sn - 1); + Node y1 = handle_bmux(a, s, a_offset + (width << (sn - 1)), width, sn - 1); + return factory.mux(y0, y1, factory.slice(s, sn - 1, 1)); + } + } + Node handle_pmux(Node a, Node b, Node s) { + // TODO : what to do about multiple b bits set ? + log_assert(b.width() == a.width() * s.width()); + Node y = a; + for(int i = 0; i < s.width(); i++) + y = factory.mux(y, factory.slice(b, a.width() * i, a.width()), factory.slice(s, i, 1)); + return y; + } + dict handle_fa(Node a, Node b, Node c) { + Node t1 = factory.bitwise_xor(a, b); + Node t2 = factory.bitwise_and(a, b); + Node t3 = factory.bitwise_and(c, t1); + Node y = factory.bitwise_xor(c, t1); + Node x = factory.bitwise_or(t2, t3); + return {{ID(X), x}, {ID(Y), y}}; + } + dict handle_alu(Node a_in, Node b_in, int y_width, bool is_signed, Node ci, Node bi) { + Node a = factory.extend(a_in, y_width, is_signed); + Node b_uninverted = factory.extend(b_in, y_width, is_signed); + Node b = factory.mux(b_uninverted, factory.bitwise_not(b_uninverted), bi); + Node x = factory.bitwise_xor(a, b); + // we can compute the carry into each bit using (a+b+c)^a^b. since we want the carry out, + // i.e. the carry into the next bit, we have to add an extra bit to a and b, and + // then slice off the bottom bit of the result. + Node a_extra = factory.extend(a, y_width + 1, false); + Node b_extra = factory.extend(b, y_width + 1, false); + Node y_extra = factory.add(factory.add(a_extra, b_extra), factory.extend(ci, a.width() + 1, false)); + Node y = factory.slice(y_extra, 0, y_width); + Node carries = factory.bitwise_xor(y_extra, factory.bitwise_xor(a_extra, b_extra)); + Node co = factory.slice(carries, 1, y_width); + return {{ID(X), x}, {ID(Y), y}, {ID(CO), co}}; + } + Node handle_lcu(Node p, Node g, Node ci) { + return handle_alu(g, factory.bitwise_or(p, g), g.width(), false, ci, factory.constant(Const(State::S0, 1))).at(ID(CO)); + } +public: + std::variant, Node> handle(IdString cellName, IdString cellType, dict parameters, dict inputs) + { + int a_width = parameters.at(ID(A_WIDTH), Const(-1)).as_int(); + int b_width = parameters.at(ID(B_WIDTH), Const(-1)).as_int(); + int y_width = parameters.at(ID(Y_WIDTH), Const(-1)).as_int(); + bool a_signed = parameters.at(ID(A_SIGNED), Const(0)).as_bool(); + bool b_signed = parameters.at(ID(B_SIGNED), Const(0)).as_bool(); + if(cellType.in({ID($add), ID($sub), ID($and), ID($or), ID($xor), ID($xnor), ID($mul)})){ + bool is_signed = a_signed && b_signed; + Node a = factory.extend(inputs.at(ID(A)), y_width, is_signed); + Node b = factory.extend(inputs.at(ID(B)), y_width, is_signed); + if(cellType == ID($add)) + return factory.add(a, b); + else if(cellType == ID($sub)) + return factory.sub(a, b); + else if(cellType == ID($mul)) + return factory.mul(a, b); + else if(cellType == ID($and)) + return factory.bitwise_and(a, b); + else if(cellType == ID($or)) + return factory.bitwise_or(a, b); + else if(cellType == ID($xor)) + return factory.bitwise_xor(a, b); + else if(cellType == ID($xnor)) + return factory.bitwise_not(factory.bitwise_xor(a, b)); + else + log_abort(); + }else if(cellType.in({ID($eq), ID($ne), ID($eqx), ID($nex), ID($le), ID($lt), ID($ge), ID($gt)})){ + bool is_signed = a_signed && b_signed; + int width = max(a_width, b_width); + Node a = factory.extend(inputs.at(ID(A)), width, is_signed); + Node b = factory.extend(inputs.at(ID(B)), width, is_signed); + if(cellType.in({ID($eq), ID($eqx)})) + return factory.extend(factory.equal(a, b), y_width, false); + else if(cellType.in({ID($ne), ID($nex)})) + return factory.extend(factory.not_equal(a, b), y_width, false); + else if(cellType == ID($lt)) + return factory.extend(is_signed ? factory.signed_greater_than(b, a) : factory.unsigned_greater_than(b, a), y_width, false); + else if(cellType == ID($le)) + return factory.extend(is_signed ? factory.signed_greater_equal(b, a) : factory.unsigned_greater_equal(b, a), y_width, false); + else if(cellType == ID($gt)) + return factory.extend(is_signed ? factory.signed_greater_than(a, b) : factory.unsigned_greater_than(a, b), y_width, false); + else if(cellType == ID($ge)) + return factory.extend(is_signed ? factory.signed_greater_equal(a, b) : factory.unsigned_greater_equal(a, b), y_width, false); + else + log_abort(); + }else if(cellType.in({ID($logic_or), ID($logic_and)})){ + Node a = factory.reduce_or(inputs.at(ID(A))); + Node b = factory.reduce_or(inputs.at(ID(B))); + Node y = cellType == ID($logic_and) ? factory.bitwise_and(a, b) : factory.bitwise_or(a, b); + return factory.extend(y, y_width, false); + }else if(cellType == ID($not)){ + Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed); + return factory.bitwise_not(a); + }else if(cellType == ID($pos)){ + return factory.extend(inputs.at(ID(A)), y_width, a_signed); + }else if(cellType == ID($neg)){ + Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed); + return factory.unary_minus(a); + }else if(cellType == ID($logic_not)){ + Node a = factory.reduce_or(inputs.at(ID(A))); + Node y = factory.bitwise_not(a); + return factory.extend(y, y_width, false); + }else if(cellType.in({ID($reduce_or), ID($reduce_bool)})){ + Node a = factory.reduce_or(inputs.at(ID(A))); + return factory.extend(a, y_width, false); + }else if(cellType == ID($reduce_and)){ + Node a = factory.reduce_and(inputs.at(ID(A))); + return factory.extend(a, y_width, false); + }else if(cellType.in({ID($reduce_xor), ID($reduce_xnor)})){ + Node a = factory.reduce_xor(inputs.at(ID(A))); + Node y = cellType == ID($reduce_xnor) ? factory.bitwise_not(a) : a; + return factory.extend(y, y_width, false); + }else if(cellType == ID($shl) || cellType == ID($sshl)){ + Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed); + Node b = inputs.at(ID(B)); + return logical_shift_left(a, b); + }else if(cellType == ID($shr) || cellType == ID($sshr)){ + int width = max(a_width, y_width); + Node a = factory.extend(inputs.at(ID(A)), width, a_signed); + Node b = inputs.at(ID(B)); + Node y = a_signed && cellType == ID($sshr) ? + arithmetic_shift_right(a, b) : + logical_shift_right(a, b); + return factory.extend(y, y_width, a_signed); + }else if(cellType == ID($shiftx) || cellType == ID($shift)){ + int width = max(a_width, y_width); + Node a = factory.extend(inputs.at(ID(A)), width, cellType == ID($shift) && a_signed); + Node b = inputs.at(ID(B)); + Node shr = logical_shift_right(a, b); + if(b_signed) { + Node shl = logical_shift_left(a, factory.unary_minus(b)); + Node y = factory.mux(shr, shl, sign(b)); + return factory.extend(y, y_width, false); + } else { + return factory.extend(shr, y_width, false); + } + }else if(cellType == ID($mux)){ + return factory.mux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S))); + }else if(cellType == ID($pmux)){ + return handle_pmux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S))); + }else if(cellType == ID($concat)){ + Node a = inputs.at(ID(A)); + Node b = inputs.at(ID(B)); + return factory.concat(a, b); + }else if(cellType == ID($slice)){ + int offset = parameters.at(ID(OFFSET)).as_int(); + Node a = inputs.at(ID(A)); + return factory.slice(a, offset, y_width); + }else if(cellType.in({ID($div), ID($mod), ID($divfloor), ID($modfloor)})) { + int width = max(a_width, b_width); + bool is_signed = a_signed && b_signed; + Node a = factory.extend(inputs.at(ID(A)), width, is_signed); + Node b = factory.extend(inputs.at(ID(B)), width, is_signed); + if(is_signed) { + if(cellType == ID($div)) { + // divide absolute values, then flip the sign if input signs differ + // but extend the width first, to handle the case (most negative value) / (-1) + Node abs_y = factory.unsigned_div(abs(a), abs(b)); + Node out_sign = factory.not_equal(sign(a), sign(b)); + return neg_if(factory.extend(abs_y, y_width, false), out_sign); + } else if(cellType == ID($mod)) { + // similar to division but output sign == divisor sign + Node abs_y = factory.unsigned_mod(abs(a), abs(b)); + return neg_if(factory.extend(abs_y, y_width, false), sign(a)); + } else if(cellType == ID($divfloor)) { + // if b is negative, flip both signs so that b is positive + Node b_sign = sign(b); + Node a1 = neg_if(a, b_sign); + Node b1 = neg_if(b, b_sign); + // if a is now negative, calculate ~((~a) / b) = -((-a - 1) / b + 1) + // which equals the negative of (-a) / b with rounding up rather than down + // note that to handle the case where a = most negative value properly, + // we have to calculate a1_sign from the original values rather than using sign(a1) + Node a1_sign = factory.bitwise_and(factory.not_equal(sign(a), sign(b)), factory.reduce_or(a)); + Node a2 = factory.mux(a1, factory.bitwise_not(a1), a1_sign); + Node y1 = factory.unsigned_div(a2, b1); + Node y2 = factory.extend(y1, y_width, false); + return factory.mux(y2, factory.bitwise_not(y2), a1_sign); + } else if(cellType == ID($modfloor)) { + // calculate |a| % |b| and then subtract from |b| if input signs differ and the remainder is non-zero + Node abs_b = abs(b); + Node abs_y = factory.unsigned_mod(abs(a), abs_b); + Node flip_y = factory.bitwise_and(factory.bitwise_xor(sign(a), sign(b)), factory.reduce_or(abs_y)); + Node y_flipped = factory.mux(abs_y, factory.sub(abs_b, abs_y), flip_y); + // since y_flipped is strictly less than |b|, the top bit is always 0 and we can just sign extend the flipped result + Node y = neg_if(y_flipped, sign(b)); + return factory.extend(y, y_width, true); + } else + log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); + } else { + if(cellType.in({ID($mod), ID($modfloor)})) + return factory.extend(factory.unsigned_mod(a, b), y_width, false); + else + return factory.extend(factory.unsigned_div(a, b), y_width, false); + } + } else if(cellType == ID($pow)) { + return handle_pow(inputs.at(ID(A)), inputs.at(ID(B)), y_width, a_signed && b_signed); + } else if (cellType == ID($lut)) { + int width = parameters.at(ID(WIDTH)).as_int(); + Const lut_table = parameters.at(ID(LUT)); + lut_table.extu(1 << width); + return handle_bmux(factory.constant(lut_table), inputs.at(ID(A)), 0, 1, width); + } else if (cellType == ID($bwmux)) { + Node a = inputs.at(ID(A)); + Node b = inputs.at(ID(B)); + Node s = inputs.at(ID(S)); + return factory.bitwise_or( + factory.bitwise_and(a, factory.bitwise_not(s)), + factory.bitwise_and(b, s)); + } else if (cellType == ID($bweqx)) { + Node a = inputs.at(ID(A)); + Node b = inputs.at(ID(B)); + return factory.bitwise_not(factory.bitwise_xor(a, b)); + } else if(cellType == ID($bmux)) { + int width = parameters.at(ID(WIDTH)).as_int(); + int s_width = parameters.at(ID(S_WIDTH)).as_int(); + return handle_bmux(inputs.at(ID(A)), inputs.at(ID(S)), 0, width, s_width); + } else if(cellType == ID($demux)) { + int width = parameters.at(ID(WIDTH)).as_int(); + int s_width = parameters.at(ID(S_WIDTH)).as_int(); + int y_width = width << s_width; + int b_width = ceil_log2(y_width); + Node a = factory.extend(inputs.at(ID(A)), y_width, false); + Node s = factory.extend(inputs.at(ID(S)), b_width, false); + Node b = factory.mul(s, factory.constant(Const(width, b_width))); + return factory.logical_shift_left(a, b); + } else if(cellType == ID($fa)) { + return handle_fa(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(C))); + } else if(cellType == ID($lcu)) { + return handle_lcu(inputs.at(ID(P)), inputs.at(ID(G)), inputs.at(ID(CI))); + } else if(cellType == ID($alu)) { + return handle_alu(inputs.at(ID(A)), inputs.at(ID(B)), y_width, a_signed && b_signed, inputs.at(ID(CI)), inputs.at(ID(BI))); + } else if(cellType.in({ID($assert), ID($assume), ID($live), ID($fair), ID($cover)})) { + Node a = factory.mux(factory.constant(Const(State::S1, 1)), inputs.at(ID(A)), inputs.at(ID(EN))); + auto &output = factory.add_output(cellName, cellType, Sort(1)); + output.set_value(a); + return {}; + } else if(cellType.in({ID($anyconst), ID($allconst), ID($anyseq), ID($allseq)})) { + int width = parameters.at(ID(WIDTH)).as_int(); + auto &input = factory.add_input(cellName, cellType, Sort(width)); + return factory.value(input); + } else if(cellType == ID($initstate)) { + if(factory.ir().has_state(ID($initstate), ID($state))) + return factory.value(factory.ir().state(ID($initstate))); + else { + auto &state = factory.add_state(ID($initstate), ID($state), Sort(1)); + state.set_initial_value(RTLIL::Const(State::S1, 1)); + state.set_next_value(factory.constant(RTLIL::Const(State::S0, 1))); + return factory.value(state); + } + } else if(cellType == ID($check)) { + log_error("The design contains a $check cell `%s'. This is not supported by the functional backend. Call `chformal -lower' to avoid this error.\n", cellName.c_str()); + } else { + log_error("`%s' cells are not supported by the functional backend\n", cellType.c_str()); + } + } +}; + +class FunctionalIRConstruction { + std::deque> queue; + dict graph_nodes; + dict, Node> cell_outputs; + DriverMap driver_map; + Factory& factory; + CellSimplifier simplifier; + vector memories_vector; + dict memories; + SigMap sig_map; // TODO: this is only for FfInitVals, remove this once FfInitVals supports DriverMap + FfInitVals ff_initvals; + + Node enqueue(DriveSpec const &spec) + { + auto it = graph_nodes.find(spec); + if(it == graph_nodes.end()){ + auto node = factory.create_pending(spec.size()); + graph_nodes.insert({spec, node}); + queue.emplace_back(spec); + return node; + }else + return it->second; + } + Node enqueue_cell(Cell *cell, IdString port_name) + { + auto it = cell_outputs.find({cell, port_name}); + if(it == cell_outputs.end()) { + queue.emplace_back(cell); + std::optional rv; + for(auto const &[name, sigspec] : cell->connections()) + if(driver_map.celltypes.cell_output(cell->type, name)) { + auto node = factory.create_pending(sigspec.size()); + factory.suggest_name(node, cell->name.str() + "$" + name.str()); + cell_outputs.emplace({cell, name}, node); + if(name == port_name) + rv = node; + } + return *rv; + } else + return it->second; + } +public: + FunctionalIRConstruction(Module *module, Factory &f) + : factory(f) + , simplifier(f) + , sig_map(module) + , ff_initvals(&sig_map, module) + { + driver_map.add(module); + for (auto cell : module->cells()) { + if (cell->type.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover), ID($check))) + queue.emplace_back(cell); + } + for (auto wire : module->wires()) { + if (wire->port_input) + factory.add_input(wire->name, ID($input), Sort(wire->width)); + if (wire->port_output) { + auto &output = factory.add_output(wire->name, ID($output), Sort(wire->width)); + output.set_value(enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width)))); + } + } + memories_vector = Mem::get_all_memories(module); + for (auto &mem : memories_vector) { + if (mem.cell != nullptr) + memories[mem.cell] = &mem; + } + } +private: + Node concatenate_read_results(Mem *mem, vector results) + { + // sanity check: all read ports concatenated should equal to the RD_DATA port + const SigSpec &rd_data = mem->cell->connections().at(ID(RD_DATA)); + int current = 0; + for(size_t i = 0; i < mem->rd_ports.size(); i++) { + int width = mem->width << mem->rd_ports[i].wide_log2; + log_assert (results[i].width() == width); + log_assert (mem->rd_ports[i].data == rd_data.extract(current, width)); + current += width; + } + log_assert (current == rd_data.size()); + log_assert (!results.empty()); + Node node = results[0]; + for(size_t i = 1; i < results.size(); i++) + node = factory.concat(node, results[i]); + return node; + } + Node handle_memory(Mem *mem) + { + // To simplify memory handling, the functional backend makes the following assumptions: + // - Since async2sync or clk2fflogic must be run to use the functional backend, + // we can assume that all ports are asynchronous. + // - Async rd/wr are always transparent and so we must do reads after writes, + // but we can ignore transparency_mask. + // - We ignore collision_x_mask because x is a dont care value for us anyway. + // - Since wr port j can only have priority over wr port i if j > i, if we do writes in + // ascending index order the result will obey the priorty relation. + vector read_results; + auto &state = factory.add_state(mem->cell->name, ID($state), Sort(ceil_log2(mem->size), mem->width)); + state.set_initial_value(MemContents(mem)); + Node node = factory.value(state); + for (size_t i = 0; i < mem->wr_ports.size(); i++) { + const auto &wr = mem->wr_ports[i]; + if (wr.clk_enable) + log_error("Write port %zd of memory %s.%s is clocked. This is not supported by the functional backend. " + "Call async2sync or clk2fflogic to avoid this error.\n", i, log_id(mem->module), log_id(mem->memid)); + Node en = enqueue(driver_map(DriveSpec(wr.en))); + Node addr = enqueue(driver_map(DriveSpec(wr.addr))); + Node new_data = enqueue(driver_map(DriveSpec(wr.data))); + Node old_data = factory.memory_read(node, addr); + Node wr_data = simplifier.bitwise_mux(old_data, new_data, en); + node = factory.memory_write(node, addr, wr_data); + } + if (mem->rd_ports.empty()) + log_error("Memory %s.%s has no read ports. This is not supported by the functional backend. " + "Call opt_clean to remove it.", log_id(mem->module), log_id(mem->memid)); + for (size_t i = 0; i < mem->rd_ports.size(); i++) { + const auto &rd = mem->rd_ports[i]; + if (rd.clk_enable) + log_error("Read port %zd of memory %s.%s is clocked. This is not supported by the functional backend. " + "Call memory_nordff to avoid this error.\n", i, log_id(mem->module), log_id(mem->memid)); + Node addr = enqueue(driver_map(DriveSpec(rd.addr))); + read_results.push_back(factory.memory_read(node, addr)); + } + state.set_next_value(node); + return concatenate_read_results(mem, read_results); + } + void process_cell(Cell *cell) + { + if (cell->is_mem_cell()) { + Mem *mem = memories.at(cell, nullptr); + if (mem == nullptr) { + log_assert(cell->has_memid()); + log_error("The design contains an unpacked memory at %s. This is not supported by the functional backend. " + "Call memory_collect to avoid this error.\n", log_const(cell->parameters.at(ID(MEMID)))); + } + Node node = handle_memory(mem); + factory.update_pending(cell_outputs.at({cell, ID(RD_DATA)}), node); + } else if (RTLIL::builtin_ff_cell_types().count(cell->type)) { + FfData ff(&ff_initvals, cell); + if (!ff.has_gclk) + log_error("The design contains a %s flip-flop at %s. This is not supported by the functional backend. " + "Call async2sync or clk2fflogic to avoid this error.\n", log_id(cell->type), log_id(cell)); + auto &state = factory.add_state(ff.name, ID($state), Sort(ff.width)); + Node q_value = factory.value(state); + factory.suggest_name(q_value, ff.name); + factory.update_pending(cell_outputs.at({cell, ID(Q)}), q_value); + state.set_next_value(enqueue(ff.sig_d)); + state.set_initial_value(ff.val_init); + } else { + dict connections; + IdString output_name; // for the single output case + int n_outputs = 0; + for(auto const &[name, sigspec] : cell->connections()) { + if(driver_map.celltypes.cell_input(cell->type, name) && sigspec.size() > 0) + connections.insert({ name, enqueue(DriveChunkPort(cell, {name, sigspec})) }); + if(driver_map.celltypes.cell_output(cell->type, name)) { + output_name = name; + n_outputs++; + } + } + std::variant, Node> outputs = simplifier.handle(cell->name, cell->type, cell->parameters, connections); + if(auto *nodep = std::get_if(&outputs); nodep != nullptr) { + log_assert(n_outputs == 1); + factory.update_pending(cell_outputs.at({cell, output_name}), *nodep); + } else { + for(auto [name, node] : std::get>(outputs)) + factory.update_pending(cell_outputs.at({cell, name}), node); + } + } + } + void undriven(const char *name) { + log_error("The design contains an undriven signal %s. This is not supported by the functional backend. " + "Call setundef with appropriate options to avoid this error.\n", name); + } + // we perform this check separately to give better error messages that include the wire or port name + void check_undriven(DriveSpec const& spec, std::string const& name) { + for(auto const &chunk : spec.chunks()) + if(chunk.is_none()) + undriven(name.c_str()); + } +public: + void process_queue() + { + for (; !queue.empty(); queue.pop_front()) { + if(auto p = std::get_if(&queue.front()); p != nullptr) { + process_cell(*p); + continue; + } + + DriveSpec spec = std::get(queue.front()); + Node pending = graph_nodes.at(spec); + + if (spec.chunks().size() > 1) { + auto chunks = spec.chunks(); + Node node = enqueue(chunks[0]); + for(size_t i = 1; i < chunks.size(); i++) + node = factory.concat(node, enqueue(chunks[i])); + factory.update_pending(pending, node); + } else if (spec.chunks().size() == 1) { + DriveChunk chunk = spec.chunks()[0]; + if (chunk.is_wire()) { + DriveChunkWire wire_chunk = chunk.wire(); + if (wire_chunk.is_whole()) { + if (wire_chunk.wire->port_input) { + Node node = factory.value(factory.ir().input(wire_chunk.wire->name)); + factory.suggest_name(node, wire_chunk.wire->name); + factory.update_pending(pending, node); + } else { + DriveSpec driver = driver_map(DriveSpec(wire_chunk)); + check_undriven(driver, RTLIL::unescape_id(wire_chunk.wire->name)); + Node node = enqueue(driver); + factory.suggest_name(node, wire_chunk.wire->name); + factory.update_pending(pending, node); + } + } else { + DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.wire->width); + Node node = factory.slice(enqueue(whole_wire), wire_chunk.offset, wire_chunk.width); + factory.update_pending(pending, node); + } + } else if (chunk.is_port()) { + DriveChunkPort port_chunk = chunk.port(); + if (port_chunk.is_whole()) { + if (driver_map.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) { + Node node = enqueue_cell(port_chunk.cell, port_chunk.port); + factory.update_pending(pending, node); + } else { + DriveSpec driver = driver_map(DriveSpec(port_chunk)); + check_undriven(driver, RTLIL::unescape_id(port_chunk.cell->name) + " port " + RTLIL::unescape_id(port_chunk.port)); + factory.update_pending(pending, enqueue(driver)); + } + } else { + DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port))); + Node node = factory.slice(enqueue(whole_port), port_chunk.offset, port_chunk.width); + factory.update_pending(pending, node); + } + } else if (chunk.is_constant()) { + Node node = factory.constant(chunk.constant()); + factory.suggest_name(node, "$const" + std::to_string(chunk.size()) + "b" + chunk.constant().as_string()); + factory.update_pending(pending, node); + } else if (chunk.is_multiple()) { + log_error("Signal %s has multiple drivers. This is not supported by the functional backend. " + "If tristate drivers are used, call tristate -formal to avoid this error.\n", log_signal(chunk)); + } else if (chunk.is_none()) { + undriven(log_signal(chunk)); + } else { + log_error("unhandled drivespec: %s\n", log_signal(chunk)); + log_abort(); + } + } else { + log_abort(); + } + } + } +}; + +IR IR::from_module(Module *module) { + IR ir; + auto factory = ir.factory(); + FunctionalIRConstruction ctor(module, factory); + ctor.process_queue(); + ir.topological_sort(); + ir.forward_buf(); + return ir; +} + +void IR::topological_sort() { + Graph::SccAdaptor compute_graph_scc(_graph); + bool scc = false; + std::vector perm; + TopoSortedSccs toposort(compute_graph_scc, [&](int *begin, int *end) { + perm.insert(perm.end(), begin, end); + if (end > begin + 1) + { + log_warning("Combinational loop:\n"); + for (int *i = begin; i != end; ++i) { + Node node(_graph[*i]); + log("- %s = %s\n", RTLIL::unescape_id(node.name()).c_str(), node.to_string().c_str()); + } + log("\n"); + scc = true; + } + }); + for(const auto &[name, state]: _states) + if(state.has_next_value()) + toposort.process(state.next_value().id()); + for(const auto &[name, output]: _outputs) + if(output.has_value()) + toposort.process(output.value().id()); + // any nodes untouched by this point are dead code and will be removed by permute + _graph.permute(perm); + if(scc) log_error("The design contains combinational loops. This is not supported by the functional backend. " + "Try `scc -select; simplemap; select -clear` to avoid this error.\n"); +} + +static IdString merge_name(IdString a, IdString b) { + if(a[0] == '$' && b[0] == '\\') + return b; + else + return a; +} + +void IR::forward_buf() { + std::vector perm, alias; + perm.clear(); + + for (int i = 0; i < _graph.size(); ++i) + { + auto node = _graph[i]; + if (node.function().fn() == Fn::buf && node.arg(0).index() < i) + { + int target_index = alias[node.arg(0).index()]; + auto target_node = _graph[perm[target_index]]; + if(node.has_sparse_attr()) { + if(target_node.has_sparse_attr()) { + IdString id = merge_name(node.sparse_attr(), target_node.sparse_attr()); + target_node.sparse_attr() = id; + } else { + IdString id = node.sparse_attr(); + target_node.sparse_attr() = id; + } + } + alias.push_back(target_index); + } + else + { + alias.push_back(GetSize(perm)); + perm.push_back(i); + } + } + _graph.permute(perm, alias); +} + +// Quoting routine to make error messages nicer +static std::string quote_fmt(const char *fmt) +{ + std::string r; + for(const char *p = fmt; *p != 0; p++) { + switch(*p) { + case '\n': r += "\\n"; break; + case '\t': r += "\\t"; break; + case '"': r += "\\\""; break; + case '\\': r += "\\\\"; break; + default: r += *p; break; + } + } + return r; +} + +void Writer::print_impl(const char *fmt, vector> &fns) +{ + size_t next_index = 0; + for(const char *p = fmt; *p != 0; p++) + switch(*p) { + case '{': + if(*++p == '{') { + *os << '{'; + } else { + char *pe; + size_t index = strtoul(p, &pe, 10); + if(*pe != '}') + log_error("invalid format string: expected {}, {} or {{, got \"%s\": \"%s\"\n", + quote_fmt(std::string(p - 1, pe - p + 2).c_str()).c_str(), + quote_fmt(fmt).c_str()); + if(p == pe) + index = next_index; + else + p = pe; + if(index >= fns.size()) + log_error("invalid format string: index %zu out of bounds (%zu): \"%s\"\n", index, fns.size(), quote_fmt(fmt).c_str()); + fns[index](); + next_index = index + 1; + } + break; + case '}': + p++; + if(*p != '}') + log_error("invalid format string: unescaped }: \"%s\"\n", quote_fmt(fmt).c_str()); + *os << '}'; + break; + default: + *os << *p; + } +} + +} +YOSYS_NAMESPACE_END diff --git a/kernel/functional.h b/kernel/functional.h new file mode 100644 index 00000000000..f1fb2767f6f --- /dev/null +++ b/kernel/functional.h @@ -0,0 +1,642 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef FUNCTIONAL_H +#define FUNCTIONAL_H + +#include "kernel/yosys.h" +#include "kernel/compute_graph.h" +#include "kernel/drivertools.h" +#include "kernel/mem.h" +#include "kernel/utils.h" + +USING_YOSYS_NAMESPACE +YOSYS_NAMESPACE_BEGIN + +namespace Functional { + // each function is documented with a short pseudocode declaration or definition + // standard C/Verilog operators are used to describe the result + // + // the sorts used in this are: + // - bit[N]: a bitvector of N bits + // bit[N] can be indicated as signed or unsigned. this is not tracked by the functional backend + // but is meant to indicate how the value is interpreted + // if a bit[N] is marked as neither signed nor unsigned, this means the result should be valid with *either* interpretation + // - memory[N, M]: a memory with N address and M data bits + // - int: C++ int + // - Const[N]: yosys RTLIL::Const (with size() == N) + // - IdString: yosys IdString + // - any: used in documentation to indicate that the sort is unconstrained + // + // nodes in the functional backend are either of sort bit[N] or memory[N,M] (for some N, M: int) + // additionally, they can carry a constant of sort int, Const[N] or IdString + // each node has a 'sort' field that stores the sort of the node + // slice, zero_extend, sign_extend use the sort field to store out_width + enum class Fn { + // invalid() = known-invalid/shouldn't happen value + // TODO: maybe remove this and use e.g. std::optional instead? + invalid, + // buf(a: any): any = a + // no-op operation + // when constructing the compute graph we generate invalid buf() nodes as a placeholder + // and later insert the argument + buf, + // slice(a: bit[in_width], offset: int, out_width: int): bit[out_width] = a[offset +: out_width] + // required: offset + out_width <= in_width + slice, + // zero_extend(a: unsigned bit[in_width], out_width: int): unsigned bit[out_width] = a (zero extended) + // required: out_width > in_width + zero_extend, + // sign_extend(a: signed bit[in_width], out_width: int): signed bit[out_width] = a (sign extended) + // required: out_width > in_width + sign_extend, + // concat(a: bit[N], b: bit[M]): bit[N+M] = {b, a} (verilog syntax) + // concatenates two bitvectors, with a in the least significant position and b in the more significant position + concat, + // add(a: bit[N], b: bit[N]): bit[N] = a + b + add, + // sub(a: bit[N], b: bit[N]): bit[N] = a - b + sub, + // mul(a: bit[N], b: bit[N]): bit[N] = a * b + mul, + // unsigned_div(a: unsigned bit[N], b: unsigned bit[N]): bit[N] = a / b + unsigned_div, + // unsigned_mod(a: signed bit[N], b: signed bit[N]): bit[N] = a % b + unsigned_mod, + // bitwise_and(a: bit[N], b: bit[N]): bit[N] = a & b + bitwise_and, + // bitwise_or(a: bit[N], b: bit[N]): bit[N] = a | b + bitwise_or, + // bitwise_xor(a: bit[N], b: bit[N]): bit[N] = a ^ b + bitwise_xor, + // bitwise_not(a: bit[N]): bit[N] = ~a + bitwise_not, + // reduce_and(a: bit[N]): bit[1] = &a + reduce_and, + // reduce_or(a: bit[N]): bit[1] = |a + reduce_or, + // reduce_xor(a: bit[N]): bit[1] = ^a + reduce_xor, + // unary_minus(a: bit[N]): bit[N] = -a + unary_minus, + // equal(a: bit[N], b: bit[N]): bit[1] = (a == b) + equal, + // not_equal(a: bit[N], b: bit[N]): bit[1] = (a != b) + not_equal, + // signed_greater_than(a: signed bit[N], b: signed bit[N]): bit[1] = (a > b) + signed_greater_than, + // signed_greater_equal(a: signed bit[N], b: signed bit[N]): bit[1] = (a >= b) + signed_greater_equal, + // unsigned_greater_than(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a > b) + unsigned_greater_than, + // unsigned_greater_equal(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a >= b) + unsigned_greater_equal, + // logical_shift_left(a: bit[N], b: unsigned bit[M]): bit[N] = a << b + // required: M == clog2(N) + logical_shift_left, + // logical_shift_right(a: unsigned bit[N], b: unsigned bit[M]): unsigned bit[N] = a >> b + // required: M == clog2(N) + logical_shift_right, + // arithmetic_shift_right(a: signed bit[N], b: unsigned bit[M]): signed bit[N] = a >> b + // required: M == clog2(N) + arithmetic_shift_right, + // mux(a: bit[N], b: bit[N], s: bit[1]): bit[N] = s ? b : a + mux, + // constant(a: Const[N]): bit[N] = a + constant, + // input(a: IdString): any + // returns the current value of the input with the specified name + input, + // state(a: IdString): any + // returns the current value of the state variable with the specified name + state, + // memory_read(memory: memory[addr_width, data_width], addr: bit[addr_width]): bit[data_width] = memory[addr] + memory_read, + // memory_write(memory: memory[addr_width, data_width], addr: bit[addr_width], data: bit[data_width]): memory[addr_width, data_width] + // returns a copy of `memory` but with the value at `addr` changed to `data` + memory_write + }; + // returns the name of a Fn value, as a string literal + const char *fn_to_string(Fn); + // Sort represents the sort or type of a node + // currently the only two sorts are signal/bit and memory + class Sort { + std::variant> _v; + public: + explicit Sort(int width) : _v(width) { } + Sort(int addr_width, int data_width) : _v(std::make_pair(addr_width, data_width)) { } + bool is_signal() const { return _v.index() == 0; } + bool is_memory() const { return _v.index() == 1; } + // returns the width of a bitvector sort, errors out for other sorts + int width() const { return std::get<0>(_v); } + // returns the address width of a bitvector sort, errors out for other sorts + int addr_width() const { return std::get<1>(_v).first; } + // returns the data width of a bitvector sort, errors out for other sorts + int data_width() const { return std::get<1>(_v).second; } + bool operator==(Sort const& other) const { return _v == other._v; } + unsigned int hash() const { return mkhash(_v); } + }; + class IR; + class Factory; + class Node; + class IRInput { + friend class Factory; + public: + IdString name; + IdString kind; + Sort sort; + private: + IRInput(IR &, IdString name, IdString kind, Sort sort) + : name(name), kind(kind), sort(std::move(sort)) {} + }; + class IROutput { + friend class Factory; + IR &_ir; + public: + IdString name; + IdString kind; + Sort sort; + private: + IROutput(IR &ir, IdString name, IdString kind, Sort sort) + : _ir(ir), name(name), kind(kind), sort(std::move(sort)) {} + public: + Node value() const; + bool has_value() const; + void set_value(Node value); + }; + class IRState { + friend class Factory; + IR &_ir; + public: + IdString name; + IdString kind; + Sort sort; + private: + std::variant _initial; + IRState(IR &ir, IdString name, IdString kind, Sort sort) + : _ir(ir), name(name), kind(kind), sort(std::move(sort)) {} + public: + Node next_value() const; + bool has_next_value() const; + RTLIL::Const const& initial_value_signal() const { return std::get(_initial); } + MemContents const& initial_value_memory() const { return std::get(_initial); } + void set_next_value(Node value); + void set_initial_value(RTLIL::Const value) { value.extu(sort.width()); _initial = std::move(value); } + void set_initial_value(MemContents value) { log_assert(Sort(value.addr_width(), value.data_width()) == sort); _initial = std::move(value); } + }; + class IR { + friend class Factory; + friend class Node; + friend class IRInput; + friend class IROutput; + friend class IRState; + // one NodeData is stored per Node, containing the function and non-node arguments + // note that NodeData is deduplicated by ComputeGraph + class NodeData { + Fn _fn; + std::variant< + std::monostate, + RTLIL::Const, + std::pair, + int + > _extra; + public: + NodeData() : _fn(Fn::invalid) {} + NodeData(Fn fn) : _fn(fn) {} + template NodeData(Fn fn, T &&extra) : _fn(fn), _extra(std::forward(extra)) {} + Fn fn() const { return _fn; } + const RTLIL::Const &as_const() const { return std::get(_extra); } + std::pair as_idstring_pair() const { return std::get>(_extra); } + int as_int() const { return std::get(_extra); } + int hash() const { + return mkhash((unsigned int) _fn, mkhash(_extra)); + } + bool operator==(NodeData const &other) const { + return _fn == other._fn && _extra == other._extra; + } + }; + // Attr contains all the information about a note that should not be deduplicated + struct Attr { + Sort sort; + }; + // our specialised version of ComputeGraph + // the sparse_attr IdString stores a naming suggestion, retrieved with name() + // the key is currently used to identify the nodes that represent output and next state values + // the bool is true for next state values + using Graph = ComputeGraph>; + Graph _graph; + dict, IRInput> _inputs; + dict, IROutput> _outputs; + dict, IRState> _states; + IR::Graph::Ref mutate(Node n); + public: + static IR from_module(Module *module); + Factory factory(); + int size() const { return _graph.size(); } + Node operator[](int i); + void topological_sort(); + void forward_buf(); + IRInput const& input(IdString name, IdString kind) const { return _inputs.at({name, kind}); } + IRInput const& input(IdString name) const { return input(name, ID($input)); } + IROutput const& output(IdString name, IdString kind) const { return _outputs.at({name, kind}); } + IROutput const& output(IdString name) const { return output(name, ID($output)); } + IRState const& state(IdString name, IdString kind) const { return _states.at({name, kind}); } + IRState const& state(IdString name) const { return state(name, ID($state)); } + bool has_input(IdString name, IdString kind) const { return _inputs.count({name, kind}); } + bool has_output(IdString name, IdString kind) const { return _outputs.count({name, kind}); } + bool has_state(IdString name, IdString kind) const { return _states.count({name, kind}); } + vector inputs(IdString kind) const; + vector inputs() const { return inputs(ID($input)); } + vector outputs(IdString kind) const; + vector outputs() const { return outputs(ID($output)); } + vector states(IdString kind) const; + vector states() const { return states(ID($state)); } + vector all_inputs() const; + vector all_outputs() const; + vector all_states() const; + class iterator { + friend class IR; + IR *_ir; + int _index; + iterator(IR *ir, int index) : _ir(ir), _index(index) {} + public: + using iterator_category = std::input_iterator_tag; + using value_type = Node; + using pointer = arrow_proxy; + using reference = Node; + using difference_type = ptrdiff_t; + Node operator*(); + iterator &operator++() { _index++; return *this; } + bool operator!=(iterator const &other) const { return _ir != other._ir || _index != other._index; } + bool operator==(iterator const &other) const { return !(*this != other); } + pointer operator->(); + }; + iterator begin() { return iterator(this, 0); } + iterator end() { return iterator(this, _graph.size()); } + }; + // Node is an immutable reference to a FunctionalIR node + class Node { + friend class Factory; + friend class IR; + friend class IRInput; + friend class IROutput; + friend class IRState; + IR::Graph::ConstRef _ref; + explicit Node(IR::Graph::ConstRef ref) : _ref(ref) { } + explicit operator IR::Graph::ConstRef() { return _ref; } + public: + // the node's index. may change if nodes are added or removed + int id() const { return _ref.index(); } + // a name suggestion for the node, which need not be unique + IdString name() const { + if(_ref.has_sparse_attr()) + return _ref.sparse_attr(); + else + return std::string("\\n") + std::to_string(id()); + } + Fn fn() const { return _ref.function().fn(); } + Sort sort() const { return _ref.attr().sort; } + // returns the width of a bitvector node, errors out for other nodes + int width() const { return sort().width(); } + size_t arg_count() const { return _ref.size(); } + Node arg(int n) const { return Node(_ref.arg(n)); } + // visit calls the appropriate visitor method depending on the type of the node + template auto visit(Visitor v) const + { + // currently templated but could be switched to AbstractVisitor & + switch(_ref.function().fn()) { + case Fn::invalid: log_error("invalid node in visit"); break; + case Fn::buf: return v.buf(*this, arg(0)); break; + case Fn::slice: return v.slice(*this, arg(0), _ref.function().as_int(), sort().width()); break; + case Fn::zero_extend: return v.zero_extend(*this, arg(0), width()); break; + case Fn::sign_extend: return v.sign_extend(*this, arg(0), width()); break; + case Fn::concat: return v.concat(*this, arg(0), arg(1)); break; + case Fn::add: return v.add(*this, arg(0), arg(1)); break; + case Fn::sub: return v.sub(*this, arg(0), arg(1)); break; + case Fn::mul: return v.mul(*this, arg(0), arg(1)); break; + case Fn::unsigned_div: return v.unsigned_div(*this, arg(0), arg(1)); break; + case Fn::unsigned_mod: return v.unsigned_mod(*this, arg(0), arg(1)); break; + case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1)); break; + case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1)); break; + case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1)); break; + case Fn::bitwise_not: return v.bitwise_not(*this, arg(0)); break; + case Fn::unary_minus: return v.unary_minus(*this, arg(0)); break; + case Fn::reduce_and: return v.reduce_and(*this, arg(0)); break; + case Fn::reduce_or: return v.reduce_or(*this, arg(0)); break; + case Fn::reduce_xor: return v.reduce_xor(*this, arg(0)); break; + case Fn::equal: return v.equal(*this, arg(0), arg(1)); break; + case Fn::not_equal: return v.not_equal(*this, arg(0), arg(1)); break; + case Fn::signed_greater_than: return v.signed_greater_than(*this, arg(0), arg(1)); break; + case Fn::signed_greater_equal: return v.signed_greater_equal(*this, arg(0), arg(1)); break; + case Fn::unsigned_greater_than: return v.unsigned_greater_than(*this, arg(0), arg(1)); break; + case Fn::unsigned_greater_equal: return v.unsigned_greater_equal(*this, arg(0), arg(1)); break; + case Fn::logical_shift_left: return v.logical_shift_left(*this, arg(0), arg(1)); break; + case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1)); break; + case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1)); break; + case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2)); break; + case Fn::constant: return v.constant(*this, _ref.function().as_const()); break; + case Fn::input: return v.input(*this, _ref.function().as_idstring_pair().first, _ref.function().as_idstring_pair().second); break; + case Fn::state: return v.state(*this, _ref.function().as_idstring_pair().first, _ref.function().as_idstring_pair().second); break; + case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break; + case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break; + } + log_abort(); + } + std::string to_string(); + std::string to_string(std::function); + }; + inline IR::Graph::Ref IR::mutate(Node n) { return _graph[n._ref.index()]; } + inline Node IR::operator[](int i) { return Node(_graph[i]); } + inline Node IROutput::value() const { return Node(_ir._graph({name, kind, false})); } + inline bool IROutput::has_value() const { return _ir._graph.has_key({name, kind, false}); } + inline void IROutput::set_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, kind, false}); } + inline Node IRState::next_value() const { return Node(_ir._graph({name, kind, true})); } + inline bool IRState::has_next_value() const { return _ir._graph.has_key({name, kind, true}); } + inline void IRState::set_next_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, kind, true}); } + inline Node IR::iterator::operator*() { return Node(_ir->_graph[_index]); } + inline arrow_proxy IR::iterator::operator->() { return arrow_proxy(**this); } + // AbstractVisitor provides an abstract base class for visitors + template struct AbstractVisitor { + virtual T buf(Node self, Node n) = 0; + virtual T slice(Node self, Node a, int offset, int out_width) = 0; + virtual T zero_extend(Node self, Node a, int out_width) = 0; + virtual T sign_extend(Node self, Node a, int out_width) = 0; + virtual T concat(Node self, Node a, Node b) = 0; + virtual T add(Node self, Node a, Node b) = 0; + virtual T sub(Node self, Node a, Node b) = 0; + virtual T mul(Node self, Node a, Node b) = 0; + virtual T unsigned_div(Node self, Node a, Node b) = 0; + virtual T unsigned_mod(Node self, Node a, Node b) = 0; + virtual T bitwise_and(Node self, Node a, Node b) = 0; + virtual T bitwise_or(Node self, Node a, Node b) = 0; + virtual T bitwise_xor(Node self, Node a, Node b) = 0; + virtual T bitwise_not(Node self, Node a) = 0; + virtual T unary_minus(Node self, Node a) = 0; + virtual T reduce_and(Node self, Node a) = 0; + virtual T reduce_or(Node self, Node a) = 0; + virtual T reduce_xor(Node self, Node a) = 0; + virtual T equal(Node self, Node a, Node b) = 0; + virtual T not_equal(Node self, Node a, Node b) = 0; + virtual T signed_greater_than(Node self, Node a, Node b) = 0; + virtual T signed_greater_equal(Node self, Node a, Node b) = 0; + virtual T unsigned_greater_than(Node self, Node a, Node b) = 0; + virtual T unsigned_greater_equal(Node self, Node a, Node b) = 0; + virtual T logical_shift_left(Node self, Node a, Node b) = 0; + virtual T logical_shift_right(Node self, Node a, Node b) = 0; + virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; + virtual T mux(Node self, Node a, Node b, Node s) = 0; + virtual T constant(Node self, RTLIL::Const const & value) = 0; + virtual T input(Node self, IdString name, IdString kind) = 0; + virtual T state(Node self, IdString name, IdString kind) = 0; + virtual T memory_read(Node self, Node mem, Node addr) = 0; + virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0; + }; + // DefaultVisitor provides defaults for all visitor methods which just calls default_handler + template struct DefaultVisitor : public AbstractVisitor { + virtual T default_handler(Node self) = 0; + T buf(Node self, Node) override { return default_handler(self); } + T slice(Node self, Node, int, int) override { return default_handler(self); } + T zero_extend(Node self, Node, int) override { return default_handler(self); } + T sign_extend(Node self, Node, int) override { return default_handler(self); } + T concat(Node self, Node, Node) override { return default_handler(self); } + T add(Node self, Node, Node) override { return default_handler(self); } + T sub(Node self, Node, Node) override { return default_handler(self); } + T mul(Node self, Node, Node) override { return default_handler(self); } + T unsigned_div(Node self, Node, Node) override { return default_handler(self); } + T unsigned_mod(Node self, Node, Node) override { return default_handler(self); } + T bitwise_and(Node self, Node, Node) override { return default_handler(self); } + T bitwise_or(Node self, Node, Node) override { return default_handler(self); } + T bitwise_xor(Node self, Node, Node) override { return default_handler(self); } + T bitwise_not(Node self, Node) override { return default_handler(self); } + T unary_minus(Node self, Node) override { return default_handler(self); } + T reduce_and(Node self, Node) override { return default_handler(self); } + T reduce_or(Node self, Node) override { return default_handler(self); } + T reduce_xor(Node self, Node) override { return default_handler(self); } + T equal(Node self, Node, Node) override { return default_handler(self); } + T not_equal(Node self, Node, Node) override { return default_handler(self); } + T signed_greater_than(Node self, Node, Node) override { return default_handler(self); } + T signed_greater_equal(Node self, Node, Node) override { return default_handler(self); } + T unsigned_greater_than(Node self, Node, Node) override { return default_handler(self); } + T unsigned_greater_equal(Node self, Node, Node) override { return default_handler(self); } + T logical_shift_left(Node self, Node, Node) override { return default_handler(self); } + T logical_shift_right(Node self, Node, Node) override { return default_handler(self); } + T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); } + T mux(Node self, Node, Node, Node) override { return default_handler(self); } + T constant(Node self, RTLIL::Const const &) override { return default_handler(self); } + T input(Node self, IdString, IdString) override { return default_handler(self); } + T state(Node self, IdString, IdString) override { return default_handler(self); } + T memory_read(Node self, Node, Node) override { return default_handler(self); } + T memory_write(Node self, Node, Node, Node) override { return default_handler(self); } + }; + // a factory is used to modify a FunctionalIR. it creates new nodes and allows for some modification of existing nodes. + class Factory { + friend class IR; + IR &_ir; + explicit Factory(IR &ir) : _ir(ir) {} + Node add(IR::NodeData &&fn, Sort const &sort, std::initializer_list args) { + log_assert(!sort.is_signal() || sort.width() > 0); + log_assert(!sort.is_memory() || (sort.addr_width() > 0 && sort.data_width() > 0)); + IR::Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); + for (auto arg : args) + ref.append_arg(IR::Graph::ConstRef(arg)); + return Node(ref); + } + void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); } + void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal() && b.width() == ceil_log2(a.width())); } + void check_unary(Node const &a) { log_assert(a.sort().is_signal()); } + public: + IR &ir() { return _ir; } + Node slice(Node a, int offset, int out_width) { + log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width()); + if(offset == 0 && out_width == a.width()) + return a; + return add(IR::NodeData(Fn::slice, offset), Sort(out_width), {a}); + } + // extend will either extend or truncate the provided value to reach the desired width + Node extend(Node a, int out_width, bool is_signed) { + int in_width = a.sort().width(); + log_assert(a.sort().is_signal()); + if(in_width == out_width) + return a; + if(in_width > out_width) + return slice(a, 0, out_width); + if(is_signed) + return add(Fn::sign_extend, Sort(out_width), {a}); + else + return add(Fn::zero_extend, Sort(out_width), {a}); + } + Node concat(Node a, Node b) { + log_assert(a.sort().is_signal() && b.sort().is_signal()); + return add(Fn::concat, Sort(a.sort().width() + b.sort().width()), {a, b}); + } + Node add(Node a, Node b) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); } + Node sub(Node a, Node b) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); } + Node mul(Node a, Node b) { check_basic_binary(a, b); return add(Fn::mul, a.sort(), {a, b}); } + Node unsigned_div(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_div, a.sort(), {a, b}); } + Node unsigned_mod(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_mod, a.sort(), {a, b}); } + Node bitwise_and(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); } + Node bitwise_or(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); } + Node bitwise_xor(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); } + Node bitwise_not(Node a) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); } + Node unary_minus(Node a) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); } + Node reduce_and(Node a) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_and, Sort(1), {a}); + } + Node reduce_or(Node a) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_or, Sort(1), {a}); + } + Node reduce_xor(Node a) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_xor, Sort(1), {a}); + } + Node equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); } + Node not_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); } + Node signed_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); } + Node signed_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_equal, Sort(1), {a, b}); } + Node unsigned_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_than, Sort(1), {a, b}); } + Node unsigned_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_equal, Sort(1), {a, b}); } + Node logical_shift_left(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_left, a.sort(), {a, b}); } + Node logical_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_right, a.sort(), {a, b}); } + Node arithmetic_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::arithmetic_shift_right, a.sort(), {a, b}); } + Node mux(Node a, Node b, Node s) { + log_assert(a.sort().is_signal() && a.sort() == b.sort() && s.sort() == Sort(1)); + return add(Fn::mux, a.sort(), {a, b, s}); + } + Node memory_read(Node mem, Node addr) { + log_assert(mem.sort().is_memory() && addr.sort().is_signal() && mem.sort().addr_width() == addr.sort().width()); + return add(Fn::memory_read, Sort(mem.sort().data_width()), {mem, addr}); + } + Node memory_write(Node mem, Node addr, Node data) { + log_assert(mem.sort().is_memory() && addr.sort().is_signal() && data.sort().is_signal() && + mem.sort().addr_width() == addr.sort().width() && mem.sort().data_width() == data.sort().width()); + return add(Fn::memory_write, mem.sort(), {mem, addr, data}); + } + Node constant(RTLIL::Const value) { + return add(IR::NodeData(Fn::constant, std::move(value)), Sort(value.size()), {}); + } + Node create_pending(int width) { + return add(Fn::buf, Sort(width), {}); + } + void update_pending(Node node, Node value) { + log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0); + log_assert(node.sort() == value.sort()); + _ir.mutate(node).append_arg(value._ref); + } + IRInput &add_input(IdString name, IdString kind, Sort sort) { + auto [it, inserted] = _ir._inputs.emplace({name, kind}, IRInput(_ir, name, kind, std::move(sort))); + if (!inserted) log_error("input `%s` was re-defined", name.c_str()); + return it->second; + } + IROutput &add_output(IdString name, IdString kind, Sort sort) { + auto [it, inserted] = _ir._outputs.emplace({name, kind}, IROutput(_ir, name, kind, std::move(sort))); + if (!inserted) log_error("output `%s` was re-defined", name.c_str()); + return it->second; + } + IRState &add_state(IdString name, IdString kind, Sort sort) { + auto [it, inserted] = _ir._states.emplace({name, kind}, IRState(_ir, name, kind, std::move(sort))); + if (!inserted) log_error("state `%s` was re-defined", name.c_str()); + return it->second; + } + Node value(IRInput const& input) { + return add(IR::NodeData(Fn::input, std::pair(input.name, input.kind)), input.sort, {}); + } + Node value(IRState const& state) { + return add(IR::NodeData(Fn::state, std::pair(state.name, state.kind)), state.sort, {}); + } + void suggest_name(Node node, IdString name) { + _ir.mutate(node).sparse_attr() = name; + } + }; + inline Factory IR::factory() { return Factory(*this); } + template class Scope { + protected: + char substitution_character = '_'; + virtual bool is_character_legal(char, int) = 0; + private: + pool _used_names; + dict _by_id; + public: + void reserve(std::string name) { + _used_names.insert(std::move(name)); + } + std::string unique_name(IdString suggestion) { + std::string str = RTLIL::unescape_id(suggestion); + for(size_t i = 0; i < str.size(); i++) + if(!is_character_legal(str[i], i)) + str[i] = substitution_character; + if(_used_names.count(str) == 0) { + _used_names.insert(str); + return str; + } + for (int idx = 0 ; ; idx++){ + std::string suffixed = str + "_" + std::to_string(idx); + if(_used_names.count(suffixed) == 0) { + _used_names.insert(suffixed); + return suffixed; + } + } + } + std::string operator()(Id id, IdString suggestion) { + auto it = _by_id.find(id); + if(it != _by_id.end()) + return it->second; + std::string str = unique_name(suggestion); + _by_id.insert({id, str}); + return str; + } + }; + class Writer { + std::ostream *os; + void print_impl(const char *fmt, vector>& fns); + public: + Writer(std::ostream &os) : os(&os) {} + template Writer& operator <<(T&& arg) { *os << std::forward(arg); return *this; } + template + void print(const char *fmt, Args&&... args) + { + vector> fns { [&]() { *this << args; }... }; + print_impl(fmt, fns); + } + template + void print_with(Fn fn, const char *fmt, Args&&... args) + { + vector> fns { [&]() { + if constexpr (std::is_invocable_v) + *this << fn(args); + else + *this << args; }... + }; + print_impl(fmt, fns); + } + }; + +} + +YOSYS_NAMESPACE_END + +#endif diff --git a/kernel/hashlib.h b/kernel/hashlib.h index 6b880f3a666..859115829d2 100644 --- a/kernel/hashlib.h +++ b/kernel/hashlib.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -186,6 +187,37 @@ inline unsigned int mkhash(const T &v) { return hash_ops().hash(v); } +template<> struct hash_ops { + static inline bool cmp(std::monostate a, std::monostate b) { + return a == b; + } + static inline unsigned int hash(std::monostate) { + return mkhash_init; + } +}; + +template struct hash_ops> { + static inline bool cmp(std::variant a, std::variant b) { + return a == b; + } + static inline unsigned int hash(std::variant a) { + unsigned int h = std::visit([](const auto &v) { return mkhash(v); }, a); + return mkhash(a.index(), h); + } +}; + +template struct hash_ops> { + static inline bool cmp(std::optional a, std::optional b) { + return a == b; + } + static inline unsigned int hash(std::optional a) { + if(a.has_value()) + return mkhash(*a); + else + return 0; + } +}; + inline int hashtable_size(int min_size) { // Primes as generated by https://oeis.org/A175953 diff --git a/kernel/log.cc b/kernel/log.cc index 9a61e8f08b3..fabbe09fd2f 100644 --- a/kernel/log.cc +++ b/kernel/log.cc @@ -459,8 +459,21 @@ void log_cmd_error(const char *format, ...) if (log_cmd_error_throw) { log_last_error = vstringf(format, ap); + + // Make sure the error message gets through any selective silencing + // of log output + bool pop_errfile = false; + if (log_errfile != NULL) { + log_files.push_back(log_errfile); + pop_errfile = true; + } + log("ERROR: %s", log_last_error.c_str()); log_flush(); + + if (pop_errfile) + log_files.pop_back(); + throw log_cmd_error_exception(); } @@ -662,6 +675,16 @@ const char *log_id(const RTLIL::IdString &str) return p+1; } +const char *log_str(const char *str) +{ + log_id_cache.push_back(strdup(str)); + return log_id_cache.back(); +} + +const char *log_str(std::string const &str) { + return log_str(str.c_str()); +} + void log_module(RTLIL::Module *module, std::string indent) { std::stringstream buf; diff --git a/kernel/log.h b/kernel/log.h index 53aae58c6bc..4b90cf9dceb 100644 --- a/kernel/log.h +++ b/kernel/log.h @@ -206,6 +206,8 @@ void log_check_expected(); const char *log_signal(const RTLIL::SigSpec &sig, bool autoint = true); const char *log_const(const RTLIL::Const &value, bool autoint = true); const char *log_id(const RTLIL::IdString &id); +const char *log_str(const char *str); +const char *log_str(std::string const &str); template static inline const char *log_id(T *obj, const char *nullstr = nullptr) { if (nullstr && obj == nullptr) diff --git a/kernel/mem.cc b/kernel/mem.cc index 01c866770f5..80be52a0eee 100644 --- a/kernel/mem.cc +++ b/kernel/mem.cc @@ -1679,3 +1679,219 @@ SigSpec MemWr::decompress_en(const std::vector &swizzle, SigSpec sig) { res.append(sig[i]); return res; } + +using addr_t = MemContents::addr_t; + +MemContents::MemContents(Mem *mem) : + MemContents(ceil_log2(mem->size), mem->width) +{ + for(const auto &init : mem->inits) { + if(init.en.is_fully_zero()) continue; + log_assert(init.en.size() == _data_width); + if(init.en.is_fully_ones()) + insert_concatenated(init.addr.as_int(), init.data); + else { + // TODO: this case could be handled more efficiently by adding + // a flag to reserve_range that tells it to preserve + // previous contents + addr_t addr = init.addr.as_int(); + addr_t words = init.data.size() / _data_width; + RTLIL::Const data = init.data; + log_assert(data.size() % _data_width == 0); + for(addr_t i = 0; i < words; i++) { + RTLIL::Const previous = (*this)[addr + i]; + for(int j = 0; j < _data_width; j++) + if(init.en[j] != State::S1) + data[_data_width * i + j] = previous[j]; + } + insert_concatenated(init.addr.as_int(), data); + } + } +} + +MemContents::iterator & MemContents::iterator::operator++() { + auto it = _memory->_values.upper_bound(_addr); + if(it == _memory->_values.end()) { + _memory = nullptr; + _addr = ~(addr_t) 0; + } else + _addr = it->first; + return *this; +} + +void MemContents::check() { + log_assert(_addr_width > 0 && _addr_width < (int)sizeof(addr_t) * 8); + log_assert(_data_width > 0); + log_assert(_default_value.size() == _data_width); + if(_values.empty()) return; + auto it = _values.begin(); + for(;;) { + log_assert(!it->second.empty()); + log_assert(it->second.size() % _data_width == 0); + auto end1 = _range_end(it); + log_assert(_range_begin(it) < (addr_t)(1<<_addr_width)); + log_assert(end1 <= (addr_t)(1<<_addr_width)); + if(++it == _values.end()) + break; + // check that ranges neither overlap nor touch + log_assert(_range_begin(it) > end1); + } +} + +bool MemContents::_range_contains(std::map::iterator it, addr_t addr) const { + // if addr < begin, the subtraction will overflow, and the comparison will always fail + // (since we have an invariant that begin + size <= 2^(addr_t bits)) + return it != _values.end() && addr - _range_begin(it) < _range_size(it); +} + + +bool MemContents::_range_contains(std::map::iterator it, addr_t begin_addr, addr_t end_addr) const { + // note that we assume begin_addr <= end_addr + return it != _values.end() && _range_begin(it) <= begin_addr && end_addr - _range_begin(it) <= _range_size(it); +} + +bool MemContents::_range_overlaps(std::map::iterator it, addr_t begin_addr, addr_t end_addr) const { + if(it == _values.end() || begin_addr >= end_addr) + return false; + auto top1 = _range_end(it) - 1; + auto top2 = end_addr - 1; + return !(top1 < begin_addr || top2 < _range_begin(it)); +} + +std::map::iterator MemContents::_range_at(addr_t addr) const { + // allow addr == 1<<_addr_width (which will just return end()) + log_assert(addr <= (addr_t)(1<<_addr_width)); + // get the first range with base > addr + // (we use const_cast since map::iterators are only passed around internally and not exposed to the user + // and using map::iterator in both the const and non-const case simplifies the code a little, + // at the cost of having to be a little careful when implementing const methods) + auto it = const_cast &>(_values).upper_bound(addr); + // if we get the very first range, all ranges are past the addr, so return the first one + if(it == _values.begin()) + return it; + // otherwise, go back to the previous interval + // this must be the last interval with base <= addr + auto it_prev = std::next(it, -1); + if(_range_contains(it_prev, addr)) + return it_prev; + else + return it; +} + +RTLIL::Const MemContents::operator[](addr_t addr) const { + auto it = _range_at(addr); + if(_range_contains(it, addr)) + return it->second.extract(_range_offset(it, addr), _data_width); + else + return _default_value; +} + +addr_t MemContents::count_range(addr_t begin_addr, addr_t end_addr) const { + addr_t count = 0; + for(auto it = _range_at(begin_addr); _range_overlaps(it, begin_addr, end_addr); it++) { + auto first = std::max(_range_begin(it), begin_addr); + auto last = std::min(_range_end(it), end_addr); + count += last - first; + } + return count; +} + +void MemContents::clear_range(addr_t begin_addr, addr_t end_addr) { + if(begin_addr >= end_addr) return; + // identify which ranges are affected by this operation + // the first iterator affected is the first one containing any addr >= begin_addr + auto begin_it = _range_at(begin_addr); + // the first iterator *not* affected is the first one with base addr > end_addr - 1 + auto end_it = _values.upper_bound(end_addr - 1); + if(begin_it == end_it) + return; // nothing to do + // the last iterator affected is one before the first one not affected + auto last_it = std::next(end_it, -1); + // the first and last range may need to be truncated, the rest can just be deleted + // to handle the begin_it == last_it case correctly, do the end case first by inserting a new range past the end + if(_range_contains(last_it, end_addr - 1)) { + auto new_begin = end_addr; + auto end = _range_end(last_it); + // if there is data past the end address, preserve it by creating a new range + if(new_begin != end) + end_it = _values.emplace_hint(last_it, new_begin, last_it->second.extract(_range_offset(last_it, new_begin), (_range_end(last_it) - new_begin) * _data_width)); + // the original range will either be truncated in the next if() block or deleted in the erase, so we can leave it untruncated + } + if(_range_contains(begin_it, begin_addr)) { + auto new_end = begin_addr; + // if there is data before the start address, truncate but don't delete + if(new_end != begin_it->first) { + begin_it->second.extu(_range_offset(begin_it, new_end)); + ++begin_it; + } + // else: begin_it will be deleted + } + _values.erase(begin_it, end_it); +} + +std::map::iterator MemContents::_reserve_range(addr_t begin_addr, addr_t end_addr) { + if(begin_addr >= end_addr) + return _values.end(); // need a dummy value to return, end() is cheap + // find the first range containing any addr >= begin_addr - 1 + auto lower_it = begin_addr == 0 ? _values.begin() : _range_at(begin_addr - 1); + // check if our range is already covered by a single range + // note that since ranges are not allowed to touch, if any range contains begin_addr, lower_it equals that range + if (_range_contains(lower_it, begin_addr, end_addr)) + return lower_it; + // find the first range containing any addr >= end_addr + auto upper_it = _range_at(end_addr); + // check if either of the two ranges we just found touch our range + bool lower_touch = begin_addr > 0 && _range_contains(lower_it, begin_addr - 1); + bool upper_touch = _range_contains(upper_it, end_addr); + if (lower_touch && upper_touch) { + log_assert (lower_it != upper_it); // lower_it == upper_it should be excluded by the check above + // we have two different ranges touching at either end, we need to merge them + auto upper_end = _range_end(upper_it); + // make range bigger (maybe reserve here instead of resize?) + lower_it->second.bits.resize(_range_offset(lower_it, upper_end), State::Sx); + // copy only the data beyond our range + std::copy(_range_data(upper_it, end_addr), _range_data(upper_it, upper_end), _range_data(lower_it, end_addr)); + // keep lower_it, but delete upper_it + _values.erase(std::next(lower_it), std::next(upper_it)); + return lower_it; + } else if (lower_touch) { + // we have a range to the left, just make it bigger and delete any other that may exist. + lower_it->second.bits.resize(_range_offset(lower_it, end_addr), State::Sx); + // keep lower_it and upper_it + _values.erase(std::next(lower_it), upper_it); + return lower_it; + } else if (upper_touch) { + // we have a range to the right, we need to expand it + // since we need to erase and reinsert to a new address, steal the data + RTLIL::Const data = std::move(upper_it->second); + // note that begin_addr is not in upper_it, otherwise the whole range covered check would have tripped + data.bits.insert(data.bits.begin(), (_range_begin(upper_it) - begin_addr) * _data_width, State::Sx); + // delete lower_it and upper_it, then reinsert + _values.erase(lower_it, std::next(upper_it)); + return _values.emplace(begin_addr, std::move(data)).first; + } else { + // no ranges are touching, so just delete all ranges in our range and allocate a new one + // could try to resize an existing range but not sure if that actually helps + _values.erase(lower_it, upper_it); + return _values.emplace(begin_addr, RTLIL::Const(State::Sx, (end_addr - begin_addr) * _data_width)).first; + } +} + +void MemContents::insert_concatenated(addr_t addr, RTLIL::Const const &values) { + addr_t words = (values.size() + _data_width - 1) / _data_width; + log_assert(addr < (addr_t)(1<<_addr_width)); + log_assert(words <= (addr_t)(1<<_addr_width) - addr); + auto it = _reserve_range(addr, addr + words); + auto to_begin = _range_data(it, addr); + std::copy(values.bits.begin(), values.bits.end(), to_begin); + // if values is not word-aligned, fill any missing bits with 0 + std::fill(to_begin + values.size(), to_begin + words * _data_width, State::S0); +} + +std::vector::iterator MemContents::_range_write(std::vector::iterator it, RTLIL::Const const &word) { + auto from_end = word.size() <= _data_width ? word.bits.end() : word.bits.begin() + _data_width; + auto to_end = std::copy(word.bits.begin(), from_end, it); + auto it_next = std::next(it, _data_width); + std::fill(to_end, it_next, State::S0); + return it_next; +} \ No newline at end of file diff --git a/kernel/mem.h b/kernel/mem.h index 8c484274ccf..4a6a9a431f4 100644 --- a/kernel/mem.h +++ b/kernel/mem.h @@ -22,6 +22,7 @@ #include "kernel/yosys.h" #include "kernel/ffinit.h" +#include "kernel/utils.h" YOSYS_NAMESPACE_BEGIN @@ -224,6 +225,114 @@ struct Mem : RTLIL::AttrObject { Mem(Module *module, IdString memid, int width, int start_offset, int size) : module(module), memid(memid), packed(false), mem(nullptr), cell(nullptr), width(width), start_offset(start_offset), size(size) {} }; +// MemContents efficiently represents the contents of a potentially sparse memory by storing only those segments that are actually defined +class MemContents { +public: + class range; class iterator; + using addr_t = uint32_t; +private: + // we ban _addr_width == sizeof(addr_t) * 8 because it adds too many cornercases + int _addr_width; + int _data_width; + RTLIL::Const _default_value; + // for each range, store the concatenation of the words at the start address + // invariants: + // - no overlapping or adjacent ranges + // - no empty ranges + // - all Consts are a multiple of the word size + std::map _values; + // returns an iterator to the range containing addr, if it exists, or the first range past addr + std::map::iterator _range_at(addr_t addr) const; + addr_t _range_size(std::map::iterator it) const { return it->second.size() / _data_width; } + addr_t _range_begin(std::map::iterator it) const { return it->first; } + addr_t _range_end(std::map::iterator it) const { return _range_begin(it) + _range_size(it); } + // check if the iterator points to a range containing addr + bool _range_contains(std::map::iterator it, addr_t addr) const; + // check if the iterator points to a range containing [begin_addr, end_addr). assumes end_addr >= begin_addr. + bool _range_contains(std::map::iterator it, addr_t begin_addr, addr_t end_addr) const; + // check if the iterator points to a range overlapping with [begin_addr, end_addr) + bool _range_overlaps(std::map::iterator it, addr_t begin_addr, addr_t end_addr) const; + // return the offset the addr would have in the range at `it` + size_t _range_offset(std::map::iterator it, addr_t addr) const { return (addr - it->first) * _data_width; } + // assuming _range_contains(it, addr), return an iterator pointing to the data at addr + std::vector::iterator _range_data(std::map::iterator it, addr_t addr) { return it->second.bits.begin() + _range_offset(it, addr); } + // internal version of reserve_range that returns an iterator to the range + std::map::iterator _reserve_range(addr_t begin_addr, addr_t end_addr); + // write a single word at addr, return iterator to next word + std::vector::iterator _range_write(std::vector::iterator it, RTLIL::Const const &data); +public: + class range { + int _data_width; + addr_t _base; + RTLIL::Const const &_values; + friend class iterator; + range(int data_width, addr_t base, RTLIL::Const const &values) + : _data_width(data_width), _base(base), _values(values) {} + public: + addr_t base() const { return _base; } + addr_t size() const { return ((addr_t) _values.size()) / _data_width; } + addr_t limit() const { return _base + size(); } + RTLIL::Const const &concatenated() const { return _values; } + RTLIL::Const operator[](addr_t addr) const { + log_assert(addr - _base < size()); + return _values.extract((addr - _base) * _data_width, _data_width); + } + RTLIL::Const at_offset(addr_t offset) const { return (*this)[_base + offset]; } + }; + class iterator { + MemContents const *_memory; + // storing addr instead of an iterator gives more well-defined behaviour under insertions/deletions + // use ~0 for end so that all end iterators compare the same + addr_t _addr; + friend class MemContents; + iterator(MemContents const *memory, addr_t addr) : _memory(memory), _addr(addr) {} + public: + using iterator_category = std::input_iterator_tag; + using value_type = range; + using pointer = arrow_proxy; + using reference = range; + using difference_type = addr_t; + reference operator *() const { return range(_memory->_data_width, _addr, _memory->_values.at(_addr)); } + pointer operator->() const { return arrow_proxy(**this); } + bool operator !=(iterator const &other) const { return _memory != other._memory || _addr != other._addr; } + bool operator ==(iterator const &other) const { return !(*this != other); } + iterator &operator++(); + }; + MemContents(int addr_width, int data_width, RTLIL::Const default_value) + : _addr_width(addr_width), _data_width(data_width) + , _default_value((default_value.extu(data_width), std::move(default_value))) + { log_assert(_addr_width > 0 && _addr_width < (int)sizeof(addr_t) * 8); log_assert(_data_width > 0); } + MemContents(int addr_width, int data_width) : MemContents(addr_width, data_width, RTLIL::Const(State::Sx, data_width)) {} + explicit MemContents(Mem *mem); + int addr_width() const { return _addr_width; } + int data_width() const { return _data_width; } + RTLIL::Const const &default_value() const { return _default_value; } + // return the value at the address if it exists, the default_value of the memory otherwise. address must not exceed 2**addr_width. + RTLIL::Const operator [](addr_t addr) const; + // return the number of defined words in the range [begin_addr, end_addr) + addr_t count_range(addr_t begin_addr, addr_t end_addr) const; + // allocate memory for the range [begin_addr, end_addr), but leave the contents undefined. + void reserve_range(addr_t begin_addr, addr_t end_addr) { _reserve_range(begin_addr, end_addr); } + // insert multiple words (provided as a single concatenated RTLIL::Const) at the given address, overriding any previous assignment. + void insert_concatenated(addr_t addr, RTLIL::Const const &values); + // insert multiple words at the given address, overriding any previous assignment. + template void insert_range(addr_t addr, Iterator begin, Iterator end) { + auto words = end - begin; + log_assert(addr < (addr_t)(1<<_addr_width)); log_assert(words <= (addr_t)(1<<_addr_width) - addr); + auto range = _reserve_range(addr, addr + words); + auto it = _range_data(range, addr); + for(; begin != end; ++begin) + it = _range_write(it, *begin); + } + // undefine all words in the range [begin_addr, end_addr) + void clear_range(addr_t begin_addr, addr_t end_addr); + // check invariants, abort if invariants failed + void check(); + iterator end() const { return iterator(nullptr, ~(addr_t) 0); } + iterator begin() const { return _values.empty() ? end() : iterator(this, _values.begin()->first); } + bool empty() const { return _values.empty(); } +}; + YOSYS_NAMESPACE_END #endif diff --git a/kernel/qcsat.cc b/kernel/qcsat.cc index aaee984fbaa..c9d00efa1cd 100644 --- a/kernel/qcsat.cc +++ b/kernel/qcsat.cc @@ -77,7 +77,7 @@ void QuickConeSat::prepare() int QuickConeSat::cell_complexity(RTLIL::Cell *cell) { - if (cell->type.in(ID($concat), ID($slice), ID($pos), ID($_BUF_))) + if (cell->type.in(ID($concat), ID($slice), ID($pos), ID($buf), ID($_BUF_))) return 0; if (cell->type.in(ID($not), ID($and), ID($or), ID($xor), ID($xnor), ID($reduce_and), ID($reduce_or), ID($reduce_xor), diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index d3946a620de..2d313537895 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -21,6 +21,7 @@ #include "kernel/macc.h" #include "kernel/celltypes.h" #include "kernel/binding.h" +#include "kernel/sigtools.h" #include "frontends/verilog/verilog_frontend.h" #include "frontends/verilog/preproc.h" #include "backends/rtlil/rtlil_backend.h" @@ -213,7 +214,7 @@ RTLIL::Const::Const(const std::string &str) } } -RTLIL::Const::Const(int val, int width) +RTLIL::Const::Const(long long val, int width) { flags = RTLIL::CONST_FLAG_NONE; bits.reserve(width); @@ -1108,6 +1109,13 @@ namespace { cell->type.begins_with("$verific$") || cell->type.begins_with("$array:") || cell->type.begins_with("$extern:")) return; + if (cell->type == ID($buf)) { + port(ID::A, param(ID::WIDTH)); + port(ID::Y, param(ID::WIDTH)); + check_expected(); + return; + } + if (cell->type.in(ID($not), ID($pos), ID($neg))) { param_bool(ID::A_SIGNED); port(ID::A, param(ID::A_WIDTH)); @@ -2493,6 +2501,23 @@ DEF_METHOD(ReduceBool, 1, ID($reduce_bool)) DEF_METHOD(LogicNot, 1, ID($logic_not)) #undef DEF_METHOD +#define DEF_METHOD(_func, _y_size, _type) \ + RTLIL::Cell* RTLIL::Module::add ## _func(RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool /* is_signed */, const std::string &src) { \ + RTLIL::Cell *cell = addCell(name, _type); \ + cell->parameters[ID::WIDTH] = sig_a.size(); \ + cell->setPort(ID::A, sig_a); \ + cell->setPort(ID::Y, sig_y); \ + cell->set_src_attribute(src); \ + return cell; \ + } \ + RTLIL::SigSpec RTLIL::Module::_func(RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed, const std::string &src) { \ + RTLIL::SigSpec sig_y = addWire(NEW_ID, _y_size); \ + add ## _func(name, sig_a, sig_y, is_signed, src); \ + return sig_y; \ + } +DEF_METHOD(Buf, sig_a.size(), ID($buf)) +#undef DEF_METHOD + #define DEF_METHOD(_func, _y_size, _type) \ RTLIL::Cell* RTLIL::Module::add ## _func(RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed, const std::string &src) { \ RTLIL::Cell *cell = addCell(name, _type); \ @@ -3540,6 +3565,110 @@ void RTLIL::Cell::unsetPort(const RTLIL::IdString& portname) } } +void RTLIL::Design::bufNormalize(bool enable) +{ + if (!enable) + { + if (!flagBufferedNormalized) + return; + + for (auto module : modules()) { + module->bufNormQueue.clear(); + for (auto wire : module->wires()) { + wire->driverCell_ = nullptr; + wire->driverPort_ = IdString(); + } + } + + flagBufferedNormalized = false; + return; + } + + if (!flagBufferedNormalized) + { + for (auto module : modules()) + { + for (auto cell : module->cells()) + for (auto &conn : cell->connections()) { + if (!cell->output(conn.first) || GetSize(conn.second) == 0) + continue; + if (conn.second.is_wire()) { + Wire *wire = conn.second.as_wire(); + log_assert(wire->driverCell_ == nullptr); + wire->driverCell_ = cell; + wire->driverPort_ = conn.first; + } else { + pair key(cell, conn.first); + module->bufNormQueue.insert(key); + } + } + } + + flagBufferedNormalized = true; + } + + for (auto module : modules()) + module->bufNormalize(); +} + +void RTLIL::Module::bufNormalize() +{ + if (!design->flagBufferedNormalized) + return; + + while (GetSize(bufNormQueue) || !connections_.empty()) + { + pool> queue; + bufNormQueue.swap(queue); + + pool outWires; + for (auto &conn : connections()) + for (auto &chunk : conn.first.chunks()) + if (chunk.wire) outWires.insert(chunk.wire); + + SigMap sigmap(this); + new_connections({}); + + for (auto &key : queue) + { + Cell *cell = key.first; + const IdString &portname = key.second; + const SigSpec &sig = cell->getPort(portname); + if (GetSize(sig) == 0) continue; + + if (sig.is_wire()) { + Wire *wire = sig.as_wire(); + if (wire->driverCell_) { + log_error("Conflict between %s %s in module %s\n", + log_id(cell), log_id(wire->driverCell_), log_id(this)); + } + log_assert(wire->driverCell_ == nullptr); + wire->driverCell_ = cell; + wire->driverPort_ = portname; + continue; + } + + for (auto &chunk : sig.chunks()) + if (chunk.wire) outWires.insert(chunk.wire); + + Wire *wire = addWire(NEW_ID, GetSize(sig)); + sigmap.add(sig, wire); + cell->setPort(portname, wire); + + // FIXME: Move init attributes from old 'sig' to new 'wire' + } + + for (auto wire : outWires) + { + SigSpec outsig = wire, insig = sigmap(wire); + for (int i = 0; i < GetSize(wire); i++) + if (insig[i] == outsig[i]) + insig[i] = State::Sx; + addBuf(NEW_ID, insig, outsig); + } + } +} + void RTLIL::Cell::setPort(const RTLIL::IdString& portname, RTLIL::SigSpec signal) { auto r = connections_.insert(portname); @@ -3559,6 +3688,40 @@ void RTLIL::Cell::setPort(const RTLIL::IdString& portname, RTLIL::SigSpec signal log_backtrace("-X- ", yosys_xtrace-1); } + while (module->design && module->design->flagBufferedNormalized && output(portname)) + { + pair key(this, portname); + + if (conn_it->second.is_wire()) { + Wire *w = conn_it->second.as_wire(); + if (w->driverCell_ == this && w->driverPort_ == portname) { + w->driverCell_ = nullptr; + w->driverPort_ = IdString(); + } + } + + if (GetSize(signal) == 0) { + module->bufNormQueue.erase(key); + break; + } + + if (!signal.is_wire()) { + module->bufNormQueue.insert(key); + break; + } + + Wire *w = signal.as_wire(); + if (w->driverCell_ != nullptr) { + pair other_key(w->driverCell_, w->driverPort_); + module->bufNormQueue.insert(other_key); + } + w->driverCell_ = this; + w->driverPort_ = portname; + + module->bufNormQueue.erase(key); + break; + } + conn_it->second = std::move(signal); } @@ -3654,9 +3817,9 @@ void RTLIL::Cell::fixup_parameters(bool set_a_signed, bool set_b_signed) type.begins_with("$verific$") || type.begins_with("$array:") || type.begins_with("$extern:")) return; - if (type == ID($mux) || type == ID($pmux) || type == ID($bmux)) { + if (type == ID($buf) || type == ID($mux) || type == ID($pmux) || type == ID($bmux)) { parameters[ID::WIDTH] = GetSize(connections_[ID::Y]); - if (type != ID($mux)) + if (type != ID($buf) && type != ID($mux)) parameters[ID::S_WIDTH] = GetSize(connections_[ID::S]); check(); return; @@ -3754,6 +3917,20 @@ RTLIL::SigChunk RTLIL::SigChunk::extract(int offset, int length) const return ret; } +RTLIL::SigBit RTLIL::SigChunk::operator[](int offset) const +{ + log_assert(offset >= 0); + log_assert(offset <= width); + RTLIL::SigBit ret; + if (wire) { + ret.wire = wire; + ret.offset = this->offset + offset; + } else { + ret.data = data[offset]; + } + return ret; +} + bool RTLIL::SigChunk::operator <(const RTLIL::SigChunk &other) const { if (wire && other.wire) diff --git a/kernel/rtlil.h b/kernel/rtlil.h index f9da2949508..c49734cd08b 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -503,6 +503,7 @@ namespace RTLIL RTLIL::Const const_pow (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); RTLIL::Const const_pos (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); + RTLIL::Const const_buf (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); RTLIL::Const const_neg (const RTLIL::Const &arg1, const RTLIL::Const &arg2, bool signed1, bool signed2, int result_len); RTLIL::Const const_mux (const RTLIL::Const &arg1, const RTLIL::Const &arg2, const RTLIL::Const &arg3); @@ -662,7 +663,7 @@ struct RTLIL::Const Const() : flags(RTLIL::CONST_FLAG_NONE) {} Const(const std::string &str); - Const(int val, int width = 32); + Const(long long val, int width = 32); Const(RTLIL::State bit, int width = 1); Const(const std::vector &bits) : bits(bits) { flags = CONST_FLAG_NONE; } Const(const std::vector &bits); @@ -769,6 +770,7 @@ struct RTLIL::SigChunk SigChunk(const RTLIL::SigBit &bit); RTLIL::SigChunk extract(int offset, int length) const; + RTLIL::SigBit operator[](int offset) const; inline int size() const { return width; } inline bool is_wire() const { return wire != NULL; } @@ -1064,6 +1066,9 @@ struct RTLIL::Design pool monitors; dict scratchpad; + bool flagBufferedNormalized = false; + void bufNormalize(bool enable=true); + int refcount_modules_; dict modules_; std::vector bindings_; @@ -1208,6 +1213,9 @@ struct RTLIL::Module : public RTLIL::AttrObject std::vector ports; void fixup_ports(); + pool> bufNormQueue; + void bufNormalize(); + template void rewrite_sigspecs(T &functor); template void rewrite_sigspecs2(T &functor); void cloneInto(RTLIL::Module *new_mod) const; @@ -1279,6 +1287,7 @@ struct RTLIL::Module : public RTLIL::AttrObject RTLIL::Cell* addNot (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addPos (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); + RTLIL::Cell* addBuf (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addNeg (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); RTLIL::Cell* addAnd (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, const RTLIL::SigSpec &sig_y, bool is_signed = false, const std::string &src = ""); @@ -1413,6 +1422,7 @@ struct RTLIL::Module : public RTLIL::AttrObject RTLIL::SigSpec Not (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); RTLIL::SigSpec Pos (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); + RTLIL::SigSpec Buf (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); RTLIL::SigSpec Neg (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, bool is_signed = false, const std::string &src = ""); RTLIL::SigSpec And (RTLIL::IdString name, const RTLIL::SigSpec &sig_a, const RTLIL::SigSpec &sig_b, bool is_signed = false, const std::string &src = ""); @@ -1500,6 +1510,10 @@ struct RTLIL::Module : public RTLIL::AttrObject #endif }; +namespace RTLIL_BACKEND { +void dump_wire(std::ostream &f, std::string indent, const RTLIL::Wire *wire); +} + struct RTLIL::Wire : public RTLIL::AttrObject { unsigned int hashidx_; @@ -1511,6 +1525,12 @@ struct RTLIL::Wire : public RTLIL::AttrObject Wire(); ~Wire(); + friend struct RTLIL::Design; + friend struct RTLIL::Cell; + friend void RTLIL_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL::Wire *wire); + RTLIL::Cell *driverCell_ = nullptr; + RTLIL::IdString driverPort_; + public: // do not simply copy wires Wire(RTLIL::Wire &other) = delete; @@ -1521,6 +1541,9 @@ struct RTLIL::Wire : public RTLIL::AttrObject int width, start_offset, port_id; bool port_input, port_output, upto, is_signed; + RTLIL::Cell *driverCell() const { log_assert(driverCell_); return driverCell_; }; + RTLIL::IdString driverPort() const { log_assert(driverCell_); return driverPort_; }; + #ifdef WITH_PYTHON static std::map *get_all_wires(void); #endif diff --git a/kernel/satgen.cc b/kernel/satgen.cc index a6db78868a3..946343c6324 100644 --- a/kernel/satgen.cc +++ b/kernel/satgen.cc @@ -430,7 +430,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) return true; } - if (cell->type.in(ID($pos), ID($neg))) + if (cell->type.in(ID($pos), ID($buf), ID($neg))) { std::vector a = importDefSigSpec(cell->getPort(ID::A), timestep); std::vector y = importDefSigSpec(cell->getPort(ID::Y), timestep); @@ -438,7 +438,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) std::vector yy = model_undef ? ez->vec_var(y.size()) : y; - if (cell->type == ID($pos)) { + if (cell->type.in(ID($pos), ID($buf))) { ez->assume(ez->vec_eq(a, yy)); } else { std::vector zero(a.size(), ez->CONST_FALSE); @@ -451,7 +451,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep) std::vector undef_y = importUndefSigSpec(cell->getPort(ID::Y), timestep); extendSignalWidthUnary(undef_a, undef_y, cell); - if (cell->type == ID($pos)) { + if (cell->type.in(ID($pos), ID($buf))) { ez->assume(ez->vec_eq(undef_a, undef_y)); } else { int undef_any_a = ez->expression(ezSAT::OpOr, undef_a); diff --git a/kernel/sexpr.cc b/kernel/sexpr.cc new file mode 100644 index 00000000000..0b977d2a89c --- /dev/null +++ b/kernel/sexpr.cc @@ -0,0 +1,163 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "sexpr.h" + +YOSYS_NAMESPACE_BEGIN + +std::ostream &operator<<(std::ostream &os, SExpr const &sexpr) { + if(sexpr.is_atom()) + os << sexpr.atom(); + else if(sexpr.is_list()){ + os << "("; + auto l = sexpr.list(); + for(size_t i = 0; i < l.size(); i++) { + if(i > 0) os << " "; + os << l[i]; + } + os << ")"; + }else + os << ""; + return os; +} + + std::string SExpr::to_string() const { + std::stringstream ss; + ss << *this; + return ss.str(); +} + +void SExprWriter::nl_if_pending() { + if(_pending_nl) { + os << '\n'; + _pos = 0; + _pending_nl = false; + } +} + +void SExprWriter::puts(std::string_view s) { + if(s.empty()) return; + nl_if_pending(); + for(auto c : s) { + if(c == '\n') { + os << c; + _pos = 0; + } else { + if(_pos == 0) { + for(int i = 0; i < _indent; i++) + os << " "; + _pos = 2 * _indent; + } + os << c; + _pos++; + } + } +} + + +// Calculate how much space would be left if expression was written +// out in full horizontally. Returns any negative value if it doesn't fit. +// +// (Ideally we would avoid recalculating the widths of subexpression, +// but I can't figure out how to store the widths. As an alternative, +// we bail out of the calculation as soon as we can tell the expression +// doesn't fit in the available space.) +int SExprWriter::check_fit(SExpr const &sexpr, int space) { + if(sexpr.is_atom()) + return space - sexpr.atom().size(); + else if(sexpr.is_list()) { + space -= 2; + if(sexpr.list().size() > 1) + space -= sexpr.list().size() - 1; + for(auto arg : sexpr.list()) { + if(space < 0) break; + space = check_fit(arg, space); + } + return space; + } else + return -1; +} + +void SExprWriter::print(SExpr const &sexpr, bool close, bool indent_rest) { + if(sexpr.is_atom()) + puts(sexpr.atom()); + else if(sexpr.is_list()) { + auto args = sexpr.list(); + puts("("); + // Expressions are printed horizontally if they fit on the line. + // We do the check *after* puts("(") to make sure that _pos is accurate. + // (Otherwise there could be a pending newline + indentation) + bool vertical = args.size() > 1 && check_fit(sexpr, _max_line_width - _pos + 1) < 0; + if(vertical) _indent++; + for(size_t i = 0; i < args.size(); i++) { + if(i > 0) puts(vertical ? "\n" : " "); + print(args[i]); + } + // Any remaining arguments are currently always printed vertically, + // but are not indented if indent_rest = false. + _indent += (!close && indent_rest) - vertical; + if(close) + puts(")"); + else { + _unclosed.push_back(indent_rest); + _pending_nl = true; + } + }else + log_error("shouldn't happen: SExpr '%s' is neither an atom nor a list", sexpr.to_string().c_str()); +} + +void SExprWriter::close(size_t n) { + log_assert(_unclosed.size() - (_unclosed_stack.empty() ? 0 : _unclosed_stack.back()) >= n); + while(n-- > 0) { + bool indented = _unclosed[_unclosed.size() - 1]; + _unclosed.pop_back(); + // Only print ) on the same line if it fits. + _pending_nl = _pos >= _max_line_width; + if(indented) + _indent--; + puts(")"); + _pending_nl = true; + } +} + +void SExprWriter::comment(std::string const &str, bool hanging) { + if(hanging) { + if(_pending_nl) { + _pending_nl = false; + puts(" "); + } + } + size_t i = 0, e; + do{ + e = str.find('\n', i); + puts("; "); + puts(std::string_view(str).substr(i, e - i)); + puts("\n"); + i = e + 1; + }while(e != std::string::npos); +} + +SExprWriter::~SExprWriter() { + while(!_unclosed_stack.empty()) + pop(); + close(_unclosed.size()); + nl_if_pending(); +} + +YOSYS_NAMESPACE_END diff --git a/kernel/sexpr.h b/kernel/sexpr.h new file mode 100644 index 00000000000..41073ea2327 --- /dev/null +++ b/kernel/sexpr.h @@ -0,0 +1,122 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef SEXPR_H +#define SEXPR_H + +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +class SExpr { +public: + std::variant, std::string> _v; +public: + SExpr(std::string a) : _v(std::move(a)) {} + SExpr(const char *a) : _v(a) {} + // FIXME: should maybe be defined for all integral types + SExpr(int n) : _v(std::to_string(n)) {} + SExpr(std::vector const &l) : _v(l) {} + SExpr(std::vector &&l) : _v(std::move(l)) {} + // It would be nicer to have an std::initializer_list constructor, + // but that causes confusing issues with overload resolution sometimes. + template static SExpr list(Args&&... args) { + return SExpr(std::vector{std::forward(args)...}); + } + bool is_atom() const { return std::holds_alternative(_v); } + std::string const &atom() const { return std::get(_v); } + bool is_list() const { return std::holds_alternative>(_v); } + std::vector const &list() const { return std::get>(_v); } + std::string to_string() const; +}; + +std::ostream &operator<<(std::ostream &os, SExpr const &sexpr); + +namespace SExprUtil { + // A little hack so that `using SExprUtil::list` lets you import a shortcut to `SExpr::list` + template SExpr list(Args&&... args) { + return SExpr(std::vector{std::forward(args)...}); + } +} + +// SExprWriter is a pretty printer for s-expr. It does not try very hard to get a good layout. +class SExprWriter { + std::ostream &os; + int _max_line_width; + int _indent = 0; + int _pos = 0; + // If _pending_nl is set, print a newline before the next character. + // This lets us "undo" the last newline so we can put + // closing parentheses or a hanging comment on the same line. + bool _pending_nl = false; + // Unclosed parentheses (boolean stored is indent_rest) + vector _unclosed; + // Used only for push() and pop() (stores _unclosed.size()) + vector _unclosed_stack; + void nl_if_pending(); + void puts(std::string_view s); + int check_fit(SExpr const &sexpr, int space); + void print(SExpr const &sexpr, bool close = true, bool indent_rest = true); +public: + SExprWriter(std::ostream &os, int max_line_width = 80) + : os(os) + , _max_line_width(max_line_width) + {} + // Print an s-expr. + SExprWriter &operator <<(SExpr const &sexpr) { + print(sexpr); + _pending_nl = true; + return *this; + } + // Print an s-expr (which must be a list), but leave room for extra elements + // which may be printed using either << or further calls to open. + // If indent_rest = false, the remaining elements are not intended + // (for avoiding unreasonable indentation on deeply nested structures). + void open(SExpr const &sexpr, bool indent_rest = true) { + log_assert(sexpr.is_list()); + print(sexpr, false, indent_rest); + } + // Close the s-expr opened with the last call to open + // (if an argument is given, close that many s-exprs). + void close(size_t n = 1); + // push() remembers how many s-exprs are currently open + void push() { + _unclosed_stack.push_back(_unclosed.size()); + } + // pop() closes all s-expr opened since the corresponding call to push() + void pop() { + auto t = _unclosed_stack.back(); + log_assert(_unclosed.size() >= t); + close(_unclosed.size() - t); + _unclosed_stack.pop_back(); + } + // Print a comment. + // If hanging = true, append it to the end of the last printed s-expr. + void comment(std::string const &str, bool hanging = false); + // Flush any unprinted characters to the std::ostream, but does not close unclosed parentheses. + void flush() { + nl_if_pending(); + } + // Destructor closes any unclosed parentheses and flushes. + ~SExprWriter(); +}; + +YOSYS_NAMESPACE_END + +#endif diff --git a/kernel/topo_scc.h b/kernel/topo_scc.h new file mode 100644 index 00000000000..7e730bb27dc --- /dev/null +++ b/kernel/topo_scc.h @@ -0,0 +1,357 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Jannis Harder + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef TOPO_SCC_H +#define TOPO_SCC_H + +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +class SigCellGraph { +public: + typedef int node_type; + + struct successor_enumerator { + std::vector>::const_iterator current, end; + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current->second; + ++current; + return result; + } + }; + + struct node_enumerator { + int current, end; + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current; + ++current; + return result; + } + }; + +private: + idict cell_ids; + idict sig_ids; + std::vector> edges; + std::vector> edge_ranges; + std::vector indices_; + int offset; + bool computed = false; + + void compute() { + offset = GetSize(sig_ids); + edge_ranges.clear(); + indices_.clear(); + indices_.resize(GetSize(sig_ids) + GetSize(cell_ids), -1); + + std::sort(edges.begin(), edges.end()); + auto last = std::unique(edges.begin(), edges.end()); + edges.erase(last, edges.end()); + auto edge = edges.begin(); + auto edge_end = edges.end(); + int range_begin = 0; + for (int node = -offset, node_end = GetSize(cell_ids); node != node_end; ++node) { + while (edge != edge_end && edge->first <= node) + ++edge; + int range_end = edge - edges.begin(); + edge_ranges.emplace_back(std::make_pair(range_begin, range_end)); + range_begin = range_end; + } + } + +public: + node_type node(RTLIL::Cell *cell) { return cell_ids(cell); } + node_type node(SigBit const &bit) { return ~sig_ids(bit); } + + bool is_cell(node_type node) { return node >= 0; } + bool is_sig(node_type node) { return node < 0; } + + Cell *cell(node_type node) { return node >= 0 ? cell_ids[node] : nullptr; } + SigBit sig(node_type node) { return node < 0 ? sig_ids[~node] : SigBit(); } + + template + void add_edge(Src &&src, Dst &&dst) { + computed = false; + node_type src_node = node(std::forward(src)); + node_type dst_node = node(std::forward(dst)); + edges.emplace_back(std::make_pair(src_node, dst_node)); + } + + node_enumerator enumerate_nodes() { + if (!computed) compute(); + return {-GetSize(sig_ids), GetSize(cell_ids)}; + } + + successor_enumerator enumerate_successors(node_type const &node) const { + auto range = edge_ranges[node + offset]; + return {edges.begin() + range.first, edges.begin() + range.second}; + } + + int &dfs_index(node_type const &node) { + return indices_[node + offset]; + } +}; + + +class IntGraph { +public: + typedef int node_type; + + struct successor_enumerator { + std::vector>::const_iterator current, end; + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current->second; + ++current; + return result; + } + }; + + struct node_enumerator { + int current, end; + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current; + ++current; + return result; + } + }; + +private: + std::vector> edges; + std::vector> edge_ranges; + std::vector indices_; + bool computed = false; + + void compute() { + edge_ranges.clear(); + + int node_end = 0; + for (auto const &edge : edges) + node_end = std::max(node_end, std::max(edge.first, edge.second) + 1); + indices_.clear(); + indices_.resize(node_end, -1); + + std::sort(edges.begin(), edges.end()); + auto last = std::unique(edges.begin(), edges.end()); + edges.erase(last, edges.end()); + auto edge = edges.begin(); + auto edge_end = edges.end(); + int range_begin = 0; + for (int node = 0; node != node_end; ++node) { + while (edge != edge_end && edge->first <= node) + ++edge; + int range_end = edge - edges.begin(); + edge_ranges.emplace_back(std::make_pair(range_begin, range_end)); + range_begin = range_end; + } + } + +public: + void add_edge(int src, int dst) { + log_assert(src >= 0); + log_assert(dst >= 0); + computed = false; + edges.emplace_back(std::make_pair(src, dst)); + } + + node_enumerator enumerate_nodes() { + if (!computed) compute(); + return {0, GetSize(indices_)}; + } + + successor_enumerator enumerate_successors(int node) const { + auto range = edge_ranges[node]; + return {edges.begin() + range.first, edges.begin() + range.second}; + } + + int &dfs_index(node_type const &node) { + return indices_[node]; + } +}; + +template +class TopoSortedSccs +{ + typedef typename G::node_enumerator node_enumerator; + typedef typename G::successor_enumerator successor_enumerator; + typedef typename G::node_type node_type; + + struct dfs_entry { + node_type node; + successor_enumerator successors; + int lowlink; + + dfs_entry(node_type node, successor_enumerator successors, int lowlink) : + node(node), successors(successors), lowlink(lowlink) + {} + }; + + G &graph; + ComponentCallback component; + + std::vector dfs_stack; + std::vector component_stack; + int next_index = 0; + +public: + TopoSortedSccs(G &graph, ComponentCallback component) + : graph(graph), component(component) {} + + // process all sources (nodes without a successor) + TopoSortedSccs &process_sources() { + node_enumerator nodes = graph.enumerate_nodes(); + while (!nodes.finished()) { + node_type node = nodes.next(); + successor_enumerator successors = graph.enumerate_successors(node); + if (successors.finished()) + { + graph.dfs_index(node) = next_index; + next_index++; + component_stack.push_back(node); + component(component_stack.data(), component_stack.data() + 1); + component_stack.clear(); + graph.dfs_index(node) = INT_MAX; + } + } + return *this; + } + + // process all remaining nodes in the graph + TopoSortedSccs &process_all() { + node_enumerator nodes = graph.enumerate_nodes(); + // iterate over all nodes to ensure we process the whole graph + while (!nodes.finished()) + process(nodes.next()); + return *this; + } + + // process all nodes that are reachable from a given start node + TopoSortedSccs &process(node_type node) { + // only start a new search if the node wasn't visited yet + if (graph.dfs_index(node) >= 0) + return *this; + while (true) { + // at this point we're visiting the node for the first time during + // the DFS search + + // we record the timestamp of when we first visited the node as the + // dfs_index + int lowlink = next_index; + next_index++; + graph.dfs_index(node) = lowlink; + + // and we add the node to the component stack where it will remain + // until all nodes of the component containing this node are popped + component_stack.push_back(node); + + // then we start iterating over the successors of this node + successor_enumerator successors = graph.enumerate_successors(node); + while (true) { + if (successors.finished()) { + // when we processed all successors, i.e. when we visited + // the complete DFS subtree rooted at the current node, we + // first check whether the current node is a SCC root + // + // (why this check identifies SCC roots is out of scope for + // this comment, see other material on Tarjan's SCC + // algorithm) + if (lowlink == graph.dfs_index(node)) { + // the SCC containing the current node is at the top of + // the component stack, with the current node at the bottom + int current = GetSize(component_stack); + do { + --current; + } while (component_stack[current] != node); + + // we invoke the callback with a pointer range of the + // nodes in the SCC + + node_type *stack_ptr = component_stack.data(); + node_type *component_begin = stack_ptr + current; + node_type *component_end = stack_ptr + component_stack.size(); + + // note that we allow the callback to permute the nodes + // in this range as well as to modify dfs_index of the + // nodes in the SCC. + component(component_begin, component_end); + + // by setting the dfs_index of all already emitted + // nodes to INT_MAX, we don't need a separate check for + // whether successor nodes are still on the component + // stack before updating the lowlink value + for (; component_begin != component_end; ++component_begin) + graph.dfs_index(*component_begin) = INT_MAX; + component_stack.resize(current); + } + + // after checking for a completed SCC the DFS either + // continues the search at the parent node or returns to + // the outer loop if we already are at the root node. + if (dfs_stack.empty()) + return *this; + auto &dfs_top = dfs_stack.back(); + + node = dfs_top.node; + successors = std::move(dfs_top.successors); + + // the parent's lowlink is updated when returning + lowlink = min(lowlink, dfs_top.lowlink); + dfs_stack.pop_back(); + // continue checking the remaining successors of the parent node. + } else { + node_type succ = successors.next(); + if (graph.dfs_index(succ) < 0) { + // if the successor wasn't visted yet, the DFS recurses + // into the successor + + // we save the state for this node and make the + // successor the current node. + dfs_stack.emplace_back(node, std::move(successors), lowlink); + node = succ; + + // this break gets us to the section corresponding to + // the function entry in the recursive version + break; + } else { + // the textbook version guards this update with a check + // whether the successor is still on the component + // stack. If the successor node was already visisted + // but is not on the component stack, it must be part + // of an already emitted SCC. We can avoid this check + // by setting the DFS index of all nodes in a SCC to + // INT_MAX when the SCC is emitted. + lowlink = min(lowlink, graph.dfs_index(succ)); + } + } + } + } + } +}; + +YOSYS_NAMESPACE_END + +#endif diff --git a/kernel/utils.h b/kernel/utils.h index 3216c5eb5f0..99f327db4cd 100644 --- a/kernel/utils.h +++ b/kernel/utils.h @@ -253,6 +253,15 @@ template , typename OPS = hash_ops> cla } }; +// this class is used for implementing operator-> on iterators that return values rather than references +// it's necessary because in C++ operator-> is called recursively until a raw pointer is obtained +template +struct arrow_proxy { + T v; + explicit arrow_proxy(T const & v) : v(v) {} + T* operator->() { return &v; } +}; + YOSYS_NAMESPACE_END #endif diff --git a/kernel/yosys.cc b/kernel/yosys.cc index efdc54b796c..510151479b6 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -555,10 +555,15 @@ void yosys_setup() #undef X #ifdef WITH_PYTHON - PyImport_AppendInittab((char*)"libyosys", INIT_MODULE); - Py_Initialize(); - PyRun_SimpleString("import sys"); - signal(SIGINT, SIG_DFL); + // With Python 3.12, calling PyImport_AppendInittab on an already + // initialized platform fails (such as when libyosys is imported + // from a Python interpreter) + if (!Py_IsInitialized()) { + PyImport_AppendInittab((char*)"libyosys", INIT_MODULE); + Py_Initialize(); + PyRun_SimpleString("import sys"); + signal(SIGINT, SIG_DFL); + } #endif Pass::init_register(); diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index 56d2356b102..ec1b5aba2ba 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 33e3ae1bc5a..b0b0eb19f66 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -16,6 +16,7 @@ OBJS += passes/cmds/setundef.o OBJS += passes/cmds/splitnets.o OBJS += passes/cmds/splitcells.o OBJS += passes/cmds/stat.o +OBJS += passes/cmds/internal_stats.o OBJS += passes/cmds/setattr.o OBJS += passes/cmds/copy.o OBJS += passes/cmds/splice.o @@ -49,3 +50,4 @@ OBJS += passes/cmds/xprop.o OBJS += passes/cmds/dft_tag.o OBJS += passes/cmds/future.o OBJS += passes/cmds/box_derive.o +OBJS += passes/cmds/example_dt.o diff --git a/passes/cmds/check.cc b/passes/cmds/check.cc index 255c32fbad9..83fe781a0ea 100644 --- a/passes/cmds/check.cc +++ b/passes/cmds/check.cc @@ -59,6 +59,12 @@ struct CheckPass : public Pass { log(" -assert\n"); log(" produce a runtime error if any problems are found in the current design\n"); log("\n"); + log(" -force-detailed-loop-check\n"); + log(" for the detection of combinatorial loops, use a detailed connectivity\n"); + log(" model for all internal cells for which it is available. This disables\n"); + log(" falling back to a simpler overapproximating model for those cells for\n"); + log(" which the detailed model is expected costly.\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) override { @@ -68,6 +74,8 @@ struct CheckPass : public Pass { bool mapped = false; bool allow_tbuf = false; bool assert_mode = false; + bool force_detailed_loop_check = false; + bool suggest_detail = false; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { @@ -91,6 +99,10 @@ struct CheckPass : public Pass { assert_mode = true; continue; } + if (args[argidx] == "-force-detailed-loop-check") { + force_detailed_loop_check = true; + continue; + } break; } extra_args(args, argidx, design); @@ -154,9 +166,10 @@ struct CheckPass : public Pass { struct CircuitEdgesDatabase : AbstractCellEdgesDatabase { TopoSort> &topo; SigMap sigmap; + bool force_detail; - CircuitEdgesDatabase(TopoSort> &topo, SigMap &sigmap) - : topo(topo), sigmap(sigmap) {} + CircuitEdgesDatabase(TopoSort> &topo, SigMap &sigmap, bool force_detail) + : topo(topo), sigmap(sigmap), force_detail(force_detail) {} void add_edge(RTLIL::Cell *cell, RTLIL::IdString from_port, int from_bit, RTLIL::IdString to_port, int to_bit, int) override { @@ -171,10 +184,41 @@ struct CheckPass : public Pass { topo.edge(std::make_pair(from.wire->name, from.offset), std::make_pair(to.wire->name, to.offset)); } - bool add_edges_from_cell(Cell *cell) { - if (AbstractCellEdgesDatabase::add_edges_from_cell(cell)) + bool detail_costly(Cell *cell) { + // Only those cell types for which the edge data can expode quadratically + // in port widths are those for us to check. + if (!cell->type.in( + ID($add), ID($sub), + ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx))) + return false; + + int in_widths = 0, out_widths = 0; + + for (auto &conn : cell->connections()) { + if (cell->input(conn.first)) + in_widths += conn.second.size(); + if (cell->output(conn.first)) + out_widths += conn.second.size(); + } + + const int threshold = 1024; + + // if the multiplication may overflow we will catch it here + if (in_widths + out_widths >= threshold) return true; + if (in_widths * out_widths >= threshold) + return true; + + return false; + } + + bool add_edges_from_cell(Cell *cell) { + if (force_detail || !detail_costly(cell)) { + if (AbstractCellEdgesDatabase::add_edges_from_cell(cell)) + return true; + } + // We don't have accurate cell edges, do the fallback of all input-output pairs for (auto &conn : cell->connections()) { if (cell->input(conn.first)) @@ -189,12 +233,15 @@ struct CheckPass : public Pass { topo.edge(std::make_pair(cell->name, -1), std::make_pair(bit.wire->name, bit.offset)); } - return true; + + // Return false to signify the fallback + return false; } }; - CircuitEdgesDatabase edges_db(topo, sigmap); + CircuitEdgesDatabase edges_db(topo, sigmap, force_detailed_loop_check); + pool coarsened_cells; for (auto cell : module->cells()) { if (mapped && cell->type.begins_with("$") && design->module(cell->type) == nullptr) { @@ -225,8 +272,10 @@ struct CheckPass : public Pass { } if (yosys_celltypes.cell_evaluable(cell->type) || cell->type.in(ID($mem_v2), ID($memrd), ID($memrd_v2)) \ - || RTLIL::builtin_ff_cell_types().count(cell->type)) - edges_db.add_edges_from_cell(cell); + || RTLIL::builtin_ff_cell_types().count(cell->type)) { + if (!edges_db.add_edges_from_cell(cell)) + coarsened_cells.insert(cell); + } } pool init_bits; @@ -284,10 +333,10 @@ struct CheckPass : public Pass { for (auto &loop : topo.loops) { string message = stringf("found logic loop in module %s:\n", log_id(module)); - // `loop` only contains wire bits, or an occassional special helper node for cells for - // which we have done the edges fallback. The cell and its ports that led to an edge is - // an information we need to recover now. For that we need to have the previous wire bit - // of the loop at hand. + // `loop` only contains wire bits, or an occasional special helper node for cells for + // which we have done the edges fallback. The cell and its ports that led to an edge are + // a piece of information we need to recover now. For that we need to have the previous + // wire bit of the loop at hand. SigBit prev; for (auto it = loop.rbegin(); it != loop.rend(); it++) if (it->second != -1) { // skip the fallback helper nodes @@ -316,10 +365,10 @@ struct CheckPass : public Pass { SigBit edge_to = sigmap(cell->getPort(to_port))[to_bit]; if (edge_from == from && edge_to == to && nhits++ < HITS_LIMIT) - message += stringf(" %s[%d] --> %s[%d]\n", log_id(from_port), from_bit, + message += stringf(" %s[%d] --> %s[%d]\n", log_id(from_port), from_bit, log_id(to_port), to_bit); if (nhits == HITS_LIMIT) - message += " ...\n"; + message += " ...\n"; } }; @@ -334,9 +383,16 @@ struct CheckPass : public Pass { std::string src_attr = driver->get_src_attribute(); driver_src = stringf(" source: %s", src_attr.c_str()); } + message += stringf(" cell %s (%s)%s\n", log_id(driver), log_id(driver->type), driver_src.c_str()); - MatchingEdgePrinter printer(message, sigmap, prev, bit); - printer.add_edges_from_cell(driver); + + if (!coarsened_cells.count(driver)) { + MatchingEdgePrinter printer(message, sigmap, prev, bit); + printer.add_edges_from_cell(driver); + } else { + message += " (cell's internal connectivity overapproximated; loop may be a false positive)\n"; + suggest_detail = true; + } if (wire->name.isPublic()) { std::string wire_src; @@ -376,6 +432,9 @@ struct CheckPass : public Pass { log("Found and reported %d problems.\n", counter); + if (suggest_detail) + log("Consider re-running with '-force-detailed-loop-check' to rule out false positives.\n"); + if (assert_mode && counter > 0) log_error("Found %d problems in 'check -assert'.\n", counter); } diff --git a/passes/cmds/example_dt.cc b/passes/cmds/example_dt.cc new file mode 100644 index 00000000000..1912d7da172 --- /dev/null +++ b/passes/cmds/example_dt.cc @@ -0,0 +1,256 @@ +#include "kernel/yosys.h" +#include "kernel/drivertools.h" +#include "kernel/topo_scc.h" +#include "kernel/compute_graph.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + + + + +struct ExampleWorker +{ + DriverMap dm; + Module *module; + + ExampleWorker(Module *module) : module(module) { + dm.celltypes.setup(); + } +}; + +struct ExampleDtPass : public Pass +{ + ExampleDtPass() : Pass("example_dt", "drivertools example") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log("TODO: add help message\n"); + log("\n"); + } + + + void execute(std::vector args, RTLIL::Design *design) override + { + size_t argidx = 1; + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) { + ExampleWorker worker(module); + DriverMap dm; + + struct ExampleFn { + IdString name; + dict parameters; + + ExampleFn(IdString name) : name(name) {} + ExampleFn(IdString name, dict parameters) : name(name), parameters(parameters) {} + + bool operator==(ExampleFn const &other) const { + return name == other.name && parameters == other.parameters; + } + + unsigned int hash() const { + return mkhash(name.hash(), parameters.hash()); + } + }; + + typedef ComputeGraph ExampleGraph; + + ExampleGraph compute_graph; + + + dm.add(module); + + idict queue; + idict cells; + + IntGraph edges; + std::vector graph_nodes; + + auto enqueue = [&](DriveSpec const &spec) { + int index = queue(spec); + if (index == GetSize(graph_nodes)) + graph_nodes.emplace_back(compute_graph.add(ID($pending), index).index()); + //if (index >= GetSize(graph_nodes)) + return compute_graph[graph_nodes[index]]; + }; + + for (auto cell : module->cells()) { + if (cell->type.in(ID($assert), ID($assume), ID($cover), ID($check))) + enqueue(DriveBitMarker(cells(cell), 0)); + } + + for (auto wire : module->wires()) { + if (!wire->port_output) + continue; + enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))).assign_key(wire->name); + } + + for (int i = 0; i != GetSize(queue); ++i) + { + DriveSpec spec = queue[i]; + ExampleGraph::Ref node = compute_graph[i]; + + if (spec.chunks().size() > 1) { + node.set_function(ID($$concat)); + + for (auto const &chunk : spec.chunks()) { + node.append_arg(enqueue(chunk)); + } + } else if (spec.chunks().size() == 1) { + DriveChunk chunk = spec.chunks()[0]; + if (chunk.is_wire()) { + DriveChunkWire wire_chunk = chunk.wire(); + if (wire_chunk.is_whole()) { + node.sparse_attr() = wire_chunk.wire->name; + if (wire_chunk.wire->port_input) { + node.set_function(ExampleFn(ID($$input), {{wire_chunk.wire->name, {}}})); + } else { + DriveSpec driver = dm(DriveSpec(wire_chunk)); + node.set_function(ID($$buf)); + + node.append_arg(enqueue(driver)); + } + } else { + DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.wire->width); + node.set_function(ExampleFn(ID($$slice), {{ID(offset), wire_chunk.offset}, {ID(width), wire_chunk.width}})); + node.append_arg(enqueue(whole_wire)); + } + } else if (chunk.is_port()) { + DriveChunkPort port_chunk = chunk.port(); + if (port_chunk.is_whole()) { + if (dm.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) { + if (port_chunk.cell->type.in(ID($dff), ID($ff))) + { + Cell *cell = port_chunk.cell; + node.set_function(ExampleFn(ID($$state), {{cell->name, {}}})); + for (auto const &conn : cell->connections()) { + if (!dm.celltypes.cell_input(cell->type, conn.first)) + continue; + enqueue(DriveChunkPort(cell, conn)).assign_key(cell->name); + } + } + else + { + node.set_function(ExampleFn(ID($$cell_output), {{port_chunk.port, {}}})); + node.append_arg(enqueue(DriveBitMarker(cells(port_chunk.cell), 0))); + } + } else { + node.set_function(ID($$buf)); + + DriveSpec driver = dm(DriveSpec(port_chunk)); + node.append_arg(enqueue(driver)); + } + + } else { + DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port))); + node.set_function(ExampleFn(ID($$slice), {{ID(offset), port_chunk.offset}})); + node.append_arg(enqueue(whole_port)); + } + } else if (chunk.is_constant()) { + node.set_function(ExampleFn(ID($$const), {{ID(value), chunk.constant()}})); + + } else if (chunk.is_multiple()) { + node.set_function(ID($$multi)); + for (auto const &driver : chunk.multiple().multiple()) + node.append_arg(enqueue(driver)); + } else if (chunk.is_marker()) { + Cell *cell = cells[chunk.marker().marker]; + + node.set_function(ExampleFn(cell->type, cell->parameters)); + for (auto const &conn : cell->connections()) { + if (!dm.celltypes.cell_input(cell->type, conn.first)) + continue; + + node.append_arg(enqueue(DriveChunkPort(cell, conn))); + } + } else if (chunk.is_none()) { + node.set_function(ID($$undriven)); + + } else { + log_error("unhandled drivespec: %s\n", log_signal(chunk)); + log_abort(); + } + } else { + log_abort(); + } + } + + + // Perform topo sort and detect SCCs + ExampleGraph::SccAdaptor compute_graph_scc(compute_graph); + + + std::vector perm; + TopoSortedSccs(compute_graph_scc, [&](int *begin, int *end) { + perm.insert(perm.end(), begin, end); + if (end > begin + 1) + { + log_warning("SCC:"); + for (int *i = begin; i != end; ++i) + log(" %d", *i); + log("\n"); + } + }).process_sources().process_all(); + compute_graph.permute(perm); + + + // Forward $$buf unless we have a name in the sparse attribute + std::vector alias; + perm.clear(); + + for (int i = 0; i < compute_graph.size(); ++i) + { + if (compute_graph[i].function().name == ID($$buf) && !compute_graph[i].has_sparse_attr() && compute_graph[i].arg(0).index() < i) + { + + alias.push_back(alias[compute_graph[i].arg(0).index()]); + } + else + { + alias.push_back(GetSize(perm)); + perm.push_back(i); + } + } + compute_graph.permute(perm, alias); + + // Dump the compute graph + for (int i = 0; i < compute_graph.size(); ++i) + { + auto ref = compute_graph[i]; + log("n%d ", i); + log("%s", log_id(ref.function().name)); + for (auto const ¶m : ref.function().parameters) + { + if (param.second.empty()) + log("[%s]", log_id(param.first)); + else + log("[%s=%s]", log_id(param.first), log_const(param.second)); + } + log("("); + + for (int i = 0, end = ref.size(); i != end; ++i) + { + if (i > 0) + log(", "); + log("n%d", ref.arg(i).index()); + } + log(")\n"); + if (ref.has_sparse_attr()) + log("// wire %s\n", log_id(ref.sparse_attr())); + log("// was #%d %s\n", ref.attr(), log_signal(queue[ref.attr()])); + } + + for (auto const &key : compute_graph.keys()) + { + log("return %d as %s \n", key.second, log_id(key.first)); + } + } + log("Plugin test passed!\n"); + } +} ExampleDtPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/internal_stats.cc b/passes/cmds/internal_stats.cc new file mode 100644 index 00000000000..cc35dcc5fc0 --- /dev/null +++ b/passes/cmds/internal_stats.cc @@ -0,0 +1,123 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include +#include +#include + +#include "kernel/yosys.h" +#include "kernel/celltypes.h" +#include "passes/techmap/libparse.h" +#include "kernel/cost.h" +#include "frontends/ast/ast.h" +#include "libs/json11/json11.hpp" + +#if defined(__APPLE__) && defined(__MACH__) +#include +#include +#endif + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +std::optional current_mem_bytes() { + +#if defined(__APPLE__) + task_basic_info_64_data_t basicInfo; + mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT; + kern_return_t error = task_info(mach_task_self(), TASK_BASIC_INFO_64, (task_info_t)&basicInfo, &count); + if (error != KERN_SUCCESS) { + return {}; // Error getting task information + } + return basicInfo.resident_size; // Return RSS in KB + +#elif defined(__linux__) + // Not all linux distributions have to have this file + std::ifstream statusFile("/proc/self/status"); + std::string line; + while (std::getline(statusFile, line)) { + if (line.find("VmRSS:") == 0) { + std::istringstream iss(line); + std::string token; + // Skip prefix + iss >> token; + uint64_t rss; + iss >> rss; + return rss * 1024; + } + } + // Error reading /proc/self/status + return {}; + +#else + return {}; +#endif +} + +struct InternalStatsPass : public Pass { + InternalStatsPass() : Pass("internal_stats", "print internal statistics") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("Print internal statistics for developers (experimental)\n"); + } + void execute(std::vector args, RTLIL::Design *design) override + { + bool json_mode = false; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-json") { + json_mode = true; + continue; + } + break; + } + extra_args(args, argidx, design); + + if(!json_mode) + log_header(design, "Printing internal statistics.\n"); + + log_experimental("internal_stats"); + + if (json_mode) { + log("{\n"); + log(" \"creator\": %s,\n", json11::Json(yosys_version_str).dump().c_str()); + std::stringstream invocation; + std::copy(args.begin(), args.end(), std::ostream_iterator(invocation, " ")); + log(" \"invocation\": %s,\n", json11::Json(invocation.str()).dump().c_str()); + if (auto mem = current_mem_bytes()) { + log(" \"memory_now\": %s,\n", std::to_string(*mem).c_str()); + } + auto ast_bytes = AST::astnode_count() * (unsigned long long) sizeof(AST::AstNode); + log(" \"memory_ast\": %s,\n", std::to_string(ast_bytes).c_str()); + } + + // stats go here + + if (json_mode) { + log("\n"); + log("}\n"); + } + + } +} InternalStatsPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/cmds/printattrs.cc b/passes/cmds/printattrs.cc index 7973ac2625a..6f630479d56 100644 --- a/passes/cmds/printattrs.cc +++ b/passes/cmds/printattrs.cc @@ -42,7 +42,7 @@ struct PrintAttrsPass : public Pass { static void log_const(const RTLIL::IdString &s, const RTLIL::Const &x, const unsigned int indent) { if (x.flags == RTLIL::CONST_FLAG_STRING) log("%s(* %s=\"%s\" *)\n", get_indent_str(indent).c_str(), log_id(s), x.decode_string().c_str()); - else if (x.flags == RTLIL::CONST_FLAG_NONE) + else if (x.flags == RTLIL::CONST_FLAG_NONE || x.flags == RTLIL::CONST_FLAG_SIGNED) log("%s(* %s=%s *)\n", get_indent_str(indent).c_str(), log_id(s), x.as_string().c_str()); else log_assert(x.flags == RTLIL::CONST_FLAG_STRING || x.flags == RTLIL::CONST_FLAG_NONE); //intended to fail diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index 951a321e8a0..608505ebf5e 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -44,8 +44,8 @@ struct statdata_t #define X(_name) unsigned int _name; STAT_INT_MEMBERS #undef X - double area; - double sequential_area; + double area = 0; + double sequential_area = 0; string tech; std::map techinfo; diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index dd17a2eba9d..c83dfe254aa 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -123,6 +123,7 @@ struct SimShared std::vector triggered_assertions; std::vector display_output; bool serious_asserts = false; + bool fst_noinit = false; bool initstate = true; }; @@ -1553,7 +1554,7 @@ struct SimWorker : SimShared bool did_something = top->setInputs(); if (initial) { - did_something |= top->setInitState(); + if (!fst_noinit) did_something |= top->setInitState(); initialize_stable_past(); initial = false; } @@ -2688,6 +2689,10 @@ struct SimPass : public Pass { log(" fail the simulation command if, in the course of simulating,\n"); log(" any of the asserts in the design fail\n"); log("\n"); + log(" -fst-noinit\n"); + log(" do not initialize latches and memories from an input FST or VCD file\n"); + log(" (use the initial defined by the design instead)\n"); + log("\n"); log(" -q\n"); log(" disable per-cycle/sample log message\n"); log("\n"); @@ -2850,6 +2855,10 @@ struct SimPass : public Pass { worker.serious_asserts = true; continue; } + if (args[argidx] == "-fst-noinit") { + worker.fst_noinit = true; + continue; + } if (args[argidx] == "-x") { worker.ignore_x = true; continue; diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc index 74813bca93f..4e1d167447c 100644 --- a/passes/techmap/Makefile.inc +++ b/passes/techmap/Makefile.inc @@ -12,10 +12,12 @@ OBJS += passes/techmap/abc.o OBJS += passes/techmap/abc9.o OBJS += passes/techmap/abc9_exe.o OBJS += passes/techmap/abc9_ops.o +OBJS += passes/techmap/abc_new.o ifneq ($(ABCEXTERNAL),) passes/techmap/abc.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"' passes/techmap/abc9.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"' passes/techmap/abc9_exe.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"' +passes/techmap/abc_new.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"' endif endif @@ -41,6 +43,7 @@ OBJS += passes/techmap/nlutmap.o OBJS += passes/techmap/shregmap.o OBJS += passes/techmap/deminout.o OBJS += passes/techmap/insbuf.o +OBJS += passes/techmap/bufnorm.o OBJS += passes/techmap/attrmvcp.o OBJS += passes/techmap/attrmap.o OBJS += passes/techmap/zinit.o @@ -49,6 +52,7 @@ OBJS += passes/techmap/dffunmap.o OBJS += passes/techmap/flowmap.o OBJS += passes/techmap/extractinv.o OBJS += passes/techmap/cellmatch.o +OBJS += passes/techmap/clockgate.o endif ifeq ($(DISABLE_SPAWN),0) diff --git a/passes/techmap/abc9.cc b/passes/techmap/abc9.cc index 876917e565e..a96a82659ac 100644 --- a/passes/techmap/abc9.cc +++ b/passes/techmap/abc9.cc @@ -220,7 +220,8 @@ struct Abc9Pass : public ScriptPass std::string arg = args[argidx]; if ((arg == "-exe" || arg == "-script" || arg == "-D" || /*arg == "-S" ||*/ arg == "-lut" || arg == "-luts" || - /*arg == "-box" ||*/ arg == "-W") && + /*arg == "-box" ||*/ arg == "-W" || arg == "-genlib" || + arg == "-constr" || arg == "-dont_use" || arg == "-liberty") && argidx+1 < args.size()) { if (arg == "-lut" || arg == "-luts") lut_mode = true; diff --git a/passes/techmap/abc9_exe.cc b/passes/techmap/abc9_exe.cc index 8e02e25a4bf..651a8375cd1 100644 --- a/passes/techmap/abc9_exe.cc +++ b/passes/techmap/abc9_exe.cc @@ -167,8 +167,8 @@ struct abc9_output_filter void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe_file, vector lut_costs, bool dff_mode, std::string delay_target, std::string /*lutin_shared*/, bool fast_mode, bool show_tempdir, std::string box_file, std::string lut_file, - std::string wire_delay, std::string tempdir_name -) + std::vector liberty_files, std::string wire_delay, std::string tempdir_name, + std::string constr_file, std::vector dont_use_cells) { std::string abc9_script; @@ -176,8 +176,17 @@ void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe abc9_script += stringf("read_lut %s/lutdefs.txt; ", tempdir_name.c_str()); else if (!lut_file.empty()) abc9_script += stringf("read_lut \"%s\"; ", lut_file.c_str()); - else - log_abort(); + else if (!liberty_files.empty()) { + std::string dont_use_args; + for (std::string dont_use_cell : dont_use_cells) { + dont_use_args += stringf("-X \"%s\" ", dont_use_cell.c_str()); + } + for (std::string liberty_file : liberty_files) { + abc9_script += stringf("read_lib %s -w \"%s\" ; ", dont_use_args.c_str(), liberty_file.c_str()); + } + if (!constr_file.empty()) + abc9_script += stringf("read_constr -v \"%s\"; ", constr_file.c_str()); + } log_assert(!box_file.empty()); abc9_script += stringf("read_box \"%s\"; ", box_file.c_str()); @@ -359,6 +368,26 @@ struct Abc9ExePass : public Pass { log(" of output quality):\n"); log("%s\n", fold_abc9_cmd(RTLIL::constpad.at("abc9.script.default.fast").substr(1,std::string::npos)).c_str()); log("\n"); + log(" -constr \n"); + log(" pass this file with timing constraints to ABC.\n"); + log(" use with -liberty.\n"); + log("\n"); + log(" a constr file contains two lines:\n"); + log(" set_driving_cell \n"); + log(" set_load \n"); + log("\n"); + log(" the set_driving_cell statement defines which cell type is assumed to\n"); + log(" drive the primary inputs and the set_load statement sets the load in\n"); + log(" femtofarads for each primary output.\n"); + log("\n"); + log(" -liberty \n"); + log(" read the given Liberty file as a description of the target cell library.\n"); + log(" this option can be used multiple times.\n"); + log("\n"); + log(" -dont_use \n"); + log(" avoid usage of the technology cell when mapping the design.\n"); + log(" this option can be used multiple times.\n"); + log("\n"); log(" -D \n"); log(" set delay target. the string {D} in the default scripts above is\n"); log(" replaced by this option when used, and an empty string otherwise\n"); @@ -411,7 +440,8 @@ struct Abc9ExePass : public Pass { log_header(design, "Executing ABC9_EXE pass (technology mapping using ABC9).\n"); std::string exe_file = yosys_abc_executable; - std::string script_file, clk_str, box_file, lut_file; + std::string script_file, clk_str, box_file, lut_file, constr_file; + std::vector liberty_files, dont_use_cells; std::string delay_target, lutin_shared = "-S 1", wire_delay; std::string tempdir_name; bool fast_mode = false, dff_mode = false; @@ -499,6 +529,18 @@ struct Abc9ExePass : public Pass { tempdir_name = args[++argidx]; continue; } + if (arg == "-liberty" && argidx+1 < args.size()) { + liberty_files.push_back(args[++argidx]); + continue; + } + if (arg == "-dont_use" && argidx+1 < args.size()) { + dont_use_cells.push_back(args[++argidx]); + continue; + } + if (arg == "-constr" && argidx+1 < args.size()) { + constr_file = args[++argidx]; + continue; + } break; } extra_args(args, argidx, design); @@ -562,7 +604,8 @@ struct Abc9ExePass : public Pass { abc9_module(design, script_file, exe_file, lut_costs, dff_mode, delay_target, lutin_shared, fast_mode, show_tempdir, - box_file, lut_file, wire_delay, tempdir_name); + box_file, lut_file, liberty_files, wire_delay, tempdir_name, + constr_file, dont_use_cells); } } Abc9ExePass; diff --git a/passes/techmap/abc_new.cc b/passes/techmap/abc_new.cc new file mode 100644 index 00000000000..eefe34f84c7 --- /dev/null +++ b/passes/techmap/abc_new.cc @@ -0,0 +1,153 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Martin PoviĊĦer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/register.h" +#include "kernel/rtlil.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct AbcNewPass : public ScriptPass { + AbcNewPass() : ScriptPass("abc_new", "(experimental) use ABC for SC technology mapping (new)") + { + experimental(); + } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" abc_new [options] [selection]\n"); + log("\n"); + log("This command uses the ABC tool [1] to optimize the current design and map it to\n"); + log("the target standard cell library.\n"); + log("\n"); + log(" -run :\n"); + log(" only run the commands between the labels (see below). an empty\n"); + 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(" -exe \n"); + log(" -script \n"); + log(" -D \n"); + log(" -constr \n"); + log(" -dont_use \n"); + log(" -liberty \n"); + log(" these options are passed on to the 'abc9_exe' command which invokes\n"); + log(" the ABC tool on individual modules of the design. please see\n"); + log(" 'help abc9_exe' for more details\n"); + log("\n"); + log("[1] http://www.eecs.berkeley.edu/~alanmi/abc/\n"); + log("\n"); + help_script(); + log("\n"); + } + + bool cleanup; + std::string abc_exe_options; + + void execute(std::vector args, RTLIL::Design *d) override + { + std::string run_from, run_to; + cleanup = true; + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-exe" || args[argidx] == "-script" || + args[argidx] == "-D" || + args[argidx] == "-constr" || args[argidx] == "-dont_use" || + args[argidx] == "-liberty") { + abc_exe_options += " " + args[argidx] + " " + args[argidx + 1]; + argidx++; + } else if (args[argidx] == "-run" && argidx + 1 < args.size()) { + size_t pos = args[++argidx].find(':'); + if (pos == std::string::npos) + break; + run_from = args[argidx].substr(0, pos); + run_to = args[argidx].substr(pos + 1); + } else if (args[argidx] == "-nocleanup") { + cleanup = false; + } else { + break; + } + } + extra_args(args, argidx, d); + + log_header(d, "Executing ABC_NEW pass.\n"); + log_push(); + run_script(d, run_from, run_to); + log_pop(); + } + + void script() override + { + if (check_label("check")) { + run("abc9_ops -check"); + } + + if (check_label("prep_boxes")) { + run("box_derive"); + run("abc9_ops -prep_box"); + } + + if (check_label("map")) { + std::vector selected_modules; + + if (!help_mode) { + selected_modules = active_design->selected_whole_modules_warn(); + active_design->selection_stack.emplace_back(false); + } else { + selected_modules = {nullptr}; + run("foreach module in selection"); + } + + for (auto mod : selected_modules) { + std::string tmpdir = ""; + std::string modname = ""; + std::string exe_options = "[options]"; + if (!help_mode) { + tmpdir = cleanup ? (get_base_tmpdir() + "/") : "_tmp_"; + tmpdir += proc_program_prefix() + "yosys-abc-XXXXXX"; + tmpdir = make_temp_dir(tmpdir); + modname = mod->name.str(); + exe_options = abc_exe_options; + log_header(active_design, "Mapping module '%s'.\n", log_id(mod)); + log_push(); + active_design->selection().select(mod); + } + + run(stringf(" abc9_ops -write_box %s/input.box", tmpdir.c_str())); + run(stringf(" write_xaiger2 -mapping_prep -map2 %s/input.map2 %s/input.xaig", tmpdir.c_str(), tmpdir.c_str())); + run(stringf(" abc9_exe %s -cwd %s -box %s/input.box", exe_options.c_str(), tmpdir.c_str(), tmpdir.c_str())); + run(stringf(" read_xaiger2 -sc_mapping -module_name %s -map2 %s/input.map2 %s/output.aig", + modname.c_str(), tmpdir.c_str(), tmpdir.c_str())); + + if (!help_mode) { + active_design->selection().selected_modules.clear(); + log_pop(); + } + } + + if (!help_mode) { + active_design->selection_stack.pop_back(); + } + } + } +} AbcNewPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/bufnorm.cc b/passes/techmap/bufnorm.cc new file mode 100644 index 00000000000..3c2cd923027 --- /dev/null +++ b/passes/techmap/bufnorm.cc @@ -0,0 +1,512 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct BufnormPass : public Pass { + BufnormPass() : Pass("bufnorm", "(experimental) convert design into buffered-normalized form") { + experimental(); + } + void help() override + { + log("\n"); + log(" bufnorm [options] [selection]\n"); + log("\n"); + log("Insert buffer cells into the design as needed, to make sure that each wire\n"); + log("has exactly one driving cell port, and aliasing wires are buffered using\n"); + log("buffer cells, than can be chained in a canonical order.\n"); + log("\n"); + log("Running 'bufnorm' on the whole design enters 'buffered-normalized mode'.\n"); + log("\n"); + log(" -buf\n"); + log(" Create $buf cells for all buffers. The default is to use $_BUF_ cells\n"); + log(" for sigle-bit buffers and $buf cells only for multi-bit buffers.\n"); + log("\n"); + log(" -chain\n"); + log(" Chain all alias wires. By default only wires with positive-valued\n"); + log(" 'chain' or 'keep' attribute on them are chained.\n"); + log("\n"); + log(" -output\n"); + log(" Enable chaining of ouput ports wires.\n"); + log("\n"); + log(" -public\n"); + log(" Enable chaining of wires wth public names.\n"); + log("\n"); + log(" -nochain\n"); + log(" Disable chaining of wires with 'chain' attribute.\n"); + log("\n"); + log(" -nokeep\n"); + log(" Disable chaining of wires with 'keep' attribute.\n"); + log("\n"); + log(" -flat\n"); + log(" Alias for -nokeep and -nochain.\n"); + log("\n"); + log(" -nosticky\n"); + log(" Disable 'sticky' behavior of output ports already driving whole\n"); + log(" wires, and always enforce canonical sort order instead.\n"); + log("\n"); + log(" -alphasort\n"); + log(" Strictly use alphanumeric sort for chain-order. (Default is\n"); + log(" to chain 'keep' wires first, then ports in declaration order,\n"); + log(" and then the other wires in alphanumeric sort order.)\n"); + log("\n"); + // log(" -noinit\n"); + // log(" Do not move 'init' attributes to the wires on FF output ports.\n"); + // log("\n"); + log("Run 'bufnorm' with -pos, -bits, or -conn on the whole design to remove all\n"); + log("$buf buffer cells and exit 'buffered-normalized mode' again.\n"); + log("\n"); + log(" -pos\n"); + log(" Create (multi- and single-bit) $pos cells instead $buf and $_BUF_.\n"); + log("\n"); + log(" -bits\n"); + log(" Create arrays of $_BUF_ cells instead of multi-bit $buf cells.\n"); + log("\n"); + log(" -conn\n"); + log(" Create 'direct connections' instead of buffer cells.\n"); + log("\n"); + log(" -nomode\n"); + log(" Do not automatically enter or leave 'buffered-normalized mode'.\n"); + log("\n"); + log("The 'bufnorm' command can also be used to just switch in and out of\n"); + log("'buffered-normalized mode' and run the low-level re-normalizer.\n"); + log("\n"); + log(" -update\n"); + log(" Enter 'buffered-normalized mode' and (re-)normalize.\n"); + log("\n"); + log(" -reset\n"); + log(" Leave 'buffered-normalized mode' without changing the netlist.\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *design) override + { + bool buf_mode = false; + bool chain_mode = false; + bool output_mode = false; + bool public_mode = false; + bool nochain_mode = false; + bool nokeep_mode = false; + bool nosticky_mode = false; + bool alphasort_mode = false; + // bool noinit_mode = false; // FIXME: Actually move init attributes + bool nomode_mode = false; + + bool pos_mode = false; + bool bits_mode = false; + bool conn_mode = false; + + bool update_mode = false; + bool reset_mode = false; + bool got_non_update_reset_opt = false; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + std::string arg = args[argidx]; + if (arg == "-buf") { + buf_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-chain") { + chain_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-output") { + output_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-public") { + public_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-nochain") { + nochain_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-nokeep") { + nokeep_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-flat") { + nochain_mode = true; + nokeep_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-nosticky") { + nosticky_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-alphasort") { + alphasort_mode = true; + got_non_update_reset_opt = true; + continue; + } + // if (arg == "-noinit") { + // noinit_mode = true; + // got_non_update_reset_opt = true; + // continue; + // } + if (arg == "-pos") { + pos_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-bits") { + bits_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-conn") { + conn_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-nomode") { + nomode_mode = true; + got_non_update_reset_opt = true; + continue; + } + if (arg == "-update") { + update_mode = true; + continue; + } + if (arg == "-reset") { + reset_mode = true; + continue; + } + break; + } + extra_args(args, argidx, design); + + if (buf_mode && pos_mode) + log_cmd_error("Options -buf and -pos are exclusive.\n"); + + if (buf_mode && conn_mode) + log_cmd_error("Options -buf and -conn are exclusive.\n"); + + if (pos_mode && conn_mode) + log_cmd_error("Options -pos and -conn are exclusive.\n"); + + if (update_mode && reset_mode) + log_cmd_error("Options -update and -reset are exclusive.\n"); + + if (update_mode && got_non_update_reset_opt) + log_cmd_error("Option -update can't be mixed with other options.\n"); + + if (reset_mode && got_non_update_reset_opt) + log_cmd_error("Option -reset can't be mixed with other options.\n"); + + if (update_mode) { + design->bufNormalize(); + return; + } + + if (reset_mode) { + design->bufNormalize(false); + return; + } + + log_header(design, "Executing BUFNORM pass (convert to buffer-normalized form).\n"); + + int count_removed_buffers = 0; + int count_updated_buffers = 0; + int count_kept_buffers = 0; + int count_created_buffers = 0; + int count_updated_cellports = 0; + + if (!nomode_mode && (pos_mode || bits_mode || conn_mode)) { + if (design->selection().full_selection) + design->bufNormalize(false); + } + + for (auto module : design->selected_modules()) + { + log("Buffer-normalizing module %s.\n", log_id(module)); + + SigMap sigmap(module); + module->new_connections({}); + + dict, Cell*> old_buffers; + + { + vector old_dup_buffers; + for (auto cell : module->cells()) + { + if (!cell->type.in(ID($buf), ID($_BUF_))) + continue; + + SigSpec insig = cell->getPort(ID::A); + SigSpec outsig = cell->getPort(ID::Y); + for (int i = 0; i < GetSize(insig) && i < GetSize(outsig); i++) + sigmap.add(insig[i], outsig[i]); + + pair key(cell->type, outsig.as_wire()); + if (old_buffers.count(key)) + old_dup_buffers.push_back(cell); + else + old_buffers[key] = cell; + } + for (auto cell : old_dup_buffers) + module->remove(cell); + count_removed_buffers += GetSize(old_dup_buffers); + } + + dict> bit2wires; + dict> whole_wires; + dict mapped_bits; + pool unmapped_wires; + + for (auto wire : module->wires()) + { + SigSpec keysig = sigmap(wire); + whole_wires[keysig].insert(wire); + + for (auto keybit : sigmap(wire)) + bit2wires[keybit].insert(wire); + + if (wire->port_input) { + log(" primary input: %s\n", log_id(wire)); + for (auto bit : SigSpec(wire)) + mapped_bits[sigmap(bit)] = bit; + } else { + unmapped_wires.insert(wire); + } + } + + auto chain_this_wire_f = [&](Wire *wire) + { + if (chain_mode) + return true; + + if (output_mode && wire->port_output) + return true; + + if (public_mode && wire->name.isPublic()) + return true; + + if (!nokeep_mode && wire->get_bool_attribute(ID::keep)) + return true; + + if (!nochain_mode && wire->get_bool_attribute(ID::chain)) + return true; + + return false; + }; + + auto compare_wires_f = [&](Wire *a, Wire *b) + { + // Chaining wires first, then flat wires + bool chain_a = chain_this_wire_f(a); + bool chain_b = chain_this_wire_f(b); + if (chain_a != chain_b) return chain_a; + + if (!alphasort_mode) + { + // Wires with 'chain' attribute first, high values before low values + if (!nochain_mode) { + int chain_a_val = a->attributes.at(ID::chain, Const(0)).as_int(); + int chain_b_val = b->attributes.at(ID::chain, Const(0)).as_int(); + if (chain_a_val != chain_b_val) return chain_a_val > chain_b_val; + } + + // Then wires with 'keep' attribute + if (!nokeep_mode) { + bool keep_a = a->get_bool_attribute(ID::keep); + bool keep_b = b->get_bool_attribute(ID::keep); + if (keep_a != keep_b) return keep_a; + } + + // Ports before non-ports + if ((a->port_id != 0) != (b->port_id != 0)) + return a->port_id != 0; + + // Ports in declaration order + if (a->port_id != b->port_id) + return a->port_id < b->port_id; + } + + // Nets with public names first + if (a->name.isPublic() != b->name.isPublic()) + return a->name.isPublic(); + + // Otherwise just sort by name alphanumerically + return a->name.str() < b->name.str(); + }; + + for (auto cell : module->cells()) + { + if (cell->type.in(ID($buf), ID($_BUF_))) + continue; + + for (auto &conn : cell->connections()) + { + if (!cell->output(conn.first)) + continue; + + Wire *w = nullptr; + + if (!nosticky_mode && conn.second.is_wire()) + w = conn.second.as_wire(); + + if (w == nullptr) + { + SigSpec keysig = sigmap(conn.second); + auto it = whole_wires.find(keysig); + if (it != whole_wires.end()) { + it->second.sort(compare_wires_f); + w = *(it->second.begin()); + } else { + w = module->addWire(NEW_ID, GetSize(conn.second)); + for (int i = 0; i < GetSize(w); i++) + sigmap.add(SigBit(w, i), keysig[i]); + } + } + + if (w->name.isPublic()) + log(" directly driven by cell %s port %s: %s\n", + log_id(cell), log_id(conn.first), log_id(w)); + + for (auto bit : SigSpec(w)) + mapped_bits[sigmap(bit)] = bit; + unmapped_wires.erase(w); + + cell->setPort(conn.first, w); + } + } + + pool added_buffers; + + auto make_buffer_f = [&](const IdString &type, const SigSpec &src, const SigSpec &dst) + { + auto it = old_buffers.find(pair(type, dst)); + + if (it != old_buffers.end()) + { + Cell *cell = it->second; + old_buffers.erase(it); + added_buffers.insert(cell); + + if (cell->getPort(ID::A) == src) { + count_kept_buffers++; + } else { + cell->setPort(ID::A, src); + count_updated_buffers++; + } + return; + } + + Cell *cell = module->addCell(NEW_ID, type); + added_buffers.insert(cell); + + cell->setPort(ID::A, src); + cell->setPort(ID::Y, dst); + cell->fixup_parameters(); + count_created_buffers++; + }; + + unmapped_wires.sort(compare_wires_f); + for (auto wire : unmapped_wires) + { + bool chain_this_wire = chain_this_wire_f(wire); + + SigSpec keysig = sigmap(wire), insig = wire, outsig = wire; + for (int i = 0; i < GetSize(insig); i++) + insig[i] = mapped_bits.at(keysig[i], State::Sx); + if (chain_this_wire) { + for (int i = 0; i < GetSize(outsig); i++) + mapped_bits[keysig[i]] = outsig[i]; + } + + log(" %s %s for %s -> %s\n", + chain_this_wire ? "chaining" : "adding", + conn_mode ? "connection" : "buffer", + log_signal(insig), log_signal(outsig)); + + if (conn_mode) { + if (bits_mode) { + for (int i = 0; i < GetSize(insig) && i < GetSize(outsig); i++) + module->connect(outsig[i], insig[i]); + } else { + module->connect(outsig, insig); + } + } else { + if (bits_mode) { + IdString celltype = pos_mode ? ID($pos) : buf_mode ? ID($buf) : ID($_BUF_); + for (int i = 0; i < GetSize(insig) && i < GetSize(outsig); i++) + make_buffer_f(celltype, insig[i], outsig[i]); + } else { + IdString celltype = pos_mode ? ID($pos) : buf_mode ? ID($buf) : + GetSize(outsig) == 1 ? ID($_BUF_) : ID($buf); + make_buffer_f(celltype, insig, outsig); + } + } + } + + for (auto &it : old_buffers) + module->remove(it.second); + count_removed_buffers += GetSize(old_buffers); + + for (auto cell : module->cells()) + { + if (added_buffers.count(cell)) + continue; + + for (auto &conn : cell->connections()) + { + if (cell->output(conn.first)) + continue; + + SigSpec newsig = conn.second; + for (auto &bit : newsig) + bit = mapped_bits[sigmap(bit)]; + + if (conn.second != newsig) { + log(" fixing input signal on cell %s port %s: %s\n", + log_id(cell), log_id(conn.first), log_signal(newsig)); + cell->setPort(conn.first, newsig); + count_updated_cellports++; + } + } + } + } + + log("Summary: removed %d, updated %d, kept %d, and created %d buffers, and updated %d cell ports.\n", + count_removed_buffers, count_updated_buffers, count_kept_buffers, + count_created_buffers, count_updated_cellports); + + if (!nomode_mode && !(pos_mode || bits_mode || conn_mode)) { + if (design->selection().full_selection) + design->bufNormalize(true); + } + } +} BufnormPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/techmap/clockgate.cc b/passes/techmap/clockgate.cc new file mode 100644 index 00000000000..bf53b02bbc8 --- /dev/null +++ b/passes/techmap/clockgate.cc @@ -0,0 +1,227 @@ +#include "kernel/yosys.h" +#include "kernel/ff.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct ClockGateCell { + IdString name; + IdString ce_pin; + IdString clk_in_pin; + IdString clk_out_pin; +}; + +ClockGateCell icg_from_arg(std::string& name, std::string& str) { + ClockGateCell c; + c.name = RTLIL::escape_id(name); + char delimiter = ':'; + size_t pos1 = str.find(delimiter); + if (pos1 == std::string::npos) + log_cmd_error("Not enough ports in descriptor string"); + size_t pos2 = str.find(delimiter, pos1 + 1); + if (pos2 == std::string::npos) + log_cmd_error("Not enough ports in descriptor string"); + size_t pos3 = str.find(delimiter, pos2 + 1); + if (pos3 != std::string::npos) + log_cmd_error("Too many ports in descriptor string"); + + std::string ce = str.substr(0, pos1); + c.ce_pin = RTLIL::escape_id(ce); + + std::string clk_in = str.substr(pos1 + 1, pos2 - (pos1 + 1)); + c.clk_in_pin = RTLIL::escape_id(clk_in); + + std::string clk_out = str.substr(pos2 + 1, str.size() - (pos2 + 1)); + c.clk_out_pin = RTLIL::escape_id(clk_out); + return c; +} + +struct ClockgatePass : public Pass { + ClockgatePass() : Pass("clockgate", "extract clock gating out of flip flops") { } + void help() override { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" clockgate [options] [selection]\n"); + log("\n"); + log("This pass transforms each set of FFs sharing the same clock and\n"); + log("enable signal into a clock-gating cell and a set of enable-less FFs.\n"); + log("Primarily a power-saving transformation on ASIC designs.\n"); + log("\n"); + log(" -pos ::\n"); + log(" If specified, rising-edge FFs will have CE inputs\n"); + log(" removed and a gated clock will be created by the\n"); + log(" user-specified ICG (integrated clock gating)\n"); + log(" cell with ports named , , .\n"); + log(" The ICG's clock enable pin must be active high.\n"); + log(" -neg ::\n"); + log(" If specified, falling-edge FFs will have CE inputs\n"); + log(" removed and a gated clock will be created by the\n"); + log(" user-specified ICG (integrated clock gating)\n"); + log(" cell with ports named , , .\n"); + log(" The ICG's clock enable pin must be active high.\n"); + log(" -tie_lo \n"); + log(" Port of the ICG will be tied to zero.\n"); + log(" Intended for DFT scan-enable pins.\n"); + log(" -min_net_size \n"); + log(" Only transform sets of at least eligible FFs.\n"); + // log(" \n"); + } + + // One ICG will be generated per ClkNetInfo + // if the number of FFs associated with it is sufficent + struct ClkNetInfo { + // Original, ungated clock into enabled FF + SigBit clk_bit; + // Original clock enable into enabled FF + SigBit ce_bit; + bool pol_clk; + bool pol_ce; + unsigned int hash() const { + auto t = std::make_tuple(clk_bit, ce_bit, pol_clk, pol_ce); + unsigned int h = mkhash_init; + h = mkhash(h, hash_ops::hash(t)); + return h; + } + bool operator==(const ClkNetInfo& other) const { + return (clk_bit == other.clk_bit) && + (ce_bit == other.ce_bit) && + (pol_clk == other.pol_clk) && + (pol_ce == other.pol_ce); + } + }; + + struct GClkNetInfo { + // How many CE FFs on this CLK net have we seen? + int net_size; + // After ICG generation, we have new gated CLK signals + Wire* new_net; + }; + + ClkNetInfo clk_info_from_ff(FfData& ff) { + SigBit clk = ff.sig_clk[0]; + SigBit ce = ff.sig_ce[0]; + ClkNetInfo info{clk, ce, ff.pol_clk, ff.pol_ce}; + return info; + } + + void execute(std::vector args, RTLIL::Design *design) override { + log_header(design, "Executing CLOCK_GATE pass (extract clock gating out of flip flops).\n"); + + std::optional pos_icg_desc; + std::optional neg_icg_desc; + std::vector tie_lo_ports; + int min_net_size = 0; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-pos" && argidx+2 < args.size()) { + auto name = args[++argidx]; + auto rest = args[++argidx]; + pos_icg_desc = icg_from_arg(name, rest); + } + if (args[argidx] == "-neg" && argidx+2 < args.size()) { + auto name = args[++argidx]; + auto rest = args[++argidx]; + neg_icg_desc = icg_from_arg(name, rest); + } + if (args[argidx] == "-tie_lo" && argidx+1 < args.size()) { + tie_lo_ports.push_back(RTLIL::escape_id(args[++argidx])); + } + if (args[argidx] == "-min_net_size" && argidx+1 < args.size()) { + min_net_size = atoi(args[++argidx].c_str()); + } + } + + extra_args(args, argidx, design); + + pool ce_ffs; + dict clk_nets; + + int gated_flop_count = 0; + for (auto module : design->selected_whole_modules()) { + for (auto cell : module->cells()) { + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) + continue; + + FfData ff(nullptr, cell); + // It would be odd to get constants, but we better handle it + if (ff.has_ce) { + if (!ff.sig_clk.is_bit() || !ff.sig_ce.is_bit()) + continue; + if (!ff.sig_clk[0].is_wire() || !ff.sig_ce[0].is_wire()) + continue; + + ce_ffs.insert(cell); + + ClkNetInfo info = clk_info_from_ff(ff); + auto it = clk_nets.find(info); + if (it == clk_nets.end()) + clk_nets[info] = GClkNetInfo(); + clk_nets[info].net_size++; + } + } + + for (auto& clk_net : clk_nets) { + auto& clk = clk_net.first; + auto& gclk = clk_net.second; + + if (gclk.net_size < min_net_size) + continue; + + std::optional matching_icg_desc; + + if (pos_icg_desc && clk.pol_clk) + matching_icg_desc = pos_icg_desc; + else if (neg_icg_desc && !clk.pol_clk) + matching_icg_desc = neg_icg_desc; + + if (!matching_icg_desc) + continue; + + Cell* icg = module->addCell(NEW_ID, matching_icg_desc->name); + icg->setPort(matching_icg_desc->ce_pin, clk.ce_bit); + icg->setPort(matching_icg_desc->clk_in_pin, clk.clk_bit); + gclk.new_net = module->addWire(NEW_ID); + icg->setPort(matching_icg_desc->clk_out_pin, gclk.new_net); + // Tie low DFT ports like scan chain enable + for (auto port : tie_lo_ports) + icg->setPort(port, Const(0, 1)); + // Fix CE polarity if needed + if (!clk.pol_ce) { + SigBit ce_fixed_pol = module->NotGate(NEW_ID, clk.ce_bit); + icg->setPort(matching_icg_desc->ce_pin, ce_fixed_pol); + } + } + + for (auto cell : ce_ffs) { + FfData ff(nullptr, cell); + ClkNetInfo info = clk_info_from_ff(ff); + auto it = clk_nets.find(info); + log_assert(it != clk_nets.end() && "Bug: desync ce_ffs and clk_nets"); + + if (!it->second.new_net) + continue; + + log_debug("Fix up FF %s\n", cell->name.c_str()); + // Now we start messing with the design + ff.has_ce = false; + // Construct the clock gate + // ICG = integrated clock gate, industry shorthand + ff.sig_clk = (*it).second.new_net; + + // Rebuild the flop + (void)ff.emit(); + + gated_flop_count++; + } + ce_ffs.clear(); + clk_nets.clear(); + } + + log("Converted %d FFs.\n", gated_flop_count); + } +} ClockgatePass; + + +PRIVATE_NAMESPACE_END diff --git a/setup.py b/setup.py new file mode 100644 index 00000000000..22196597109 --- /dev/null +++ b/setup.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 Efabless Corporation +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import os +import re +import shlex +import shutil +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext + +__dir__ = os.path.dirname(os.path.abspath(__file__)) + +yosys_version_rx = re.compile(r"YOSYS_VER\s*:=\s*([\w\-\+\.]+)") + +version = yosys_version_rx.search( + open(os.path.join(__dir__, "Makefile"), encoding="utf8").read() +)[1].replace( + "+", "." +) # Convert to patch version + + +class libyosys_so_ext(Extension): + def __init__( + self, + ) -> None: + super().__init__( + "libyosys.so", + [], + ) + self.args = [ + "ENABLE_PYOSYS=1", + # Wheel meant to be imported from interpreter + "ENABLE_PYTHON_CONFIG_EMBED=0", + # Would need to be installed separately by the user + "ENABLE_TCL=0", + # Would need to be installed separately by the user + "ENABLE_READLINE=0", + # Show compile commands + "PRETTY=0", + ] + + def custom_build(self, bext: build_ext): + bext.spawn( + ["make", f"-j{os.cpu_count() or 1}", self.name] + + shlex.split(os.getenv("makeFlags", "")) + + self.args + ) + build_path = os.path.dirname(os.path.dirname(bext.get_ext_fullpath(self.name))) + pyosys_path = os.path.join(build_path, "pyosys") + target = os.path.join(pyosys_path, os.path.basename(self.name)) + os.makedirs(pyosys_path, exist_ok=True) + shutil.copyfile(self.name, target) + + # I don't know how debug info is getting here. + bext.spawn(["strip", "-S", target]) + + +class custom_build_ext(build_ext): + def build_extension(self, ext) -> None: + if not hasattr(ext, "custom_build"): + return super().build_extension(ext) + return ext.custom_build(self) + + +setup( + name="pyosys", + packages=["pyosys"], + version=version, + description="Python access to libyosys", + long_description=open(os.path.join(__dir__, "README.md")).read(), + long_description_content_type="text/markdown", + install_requires=["wheel", "setuptools"], + classifiers=[ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Intended Audience :: Developers", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS :: MacOS X", + ], + package_dir={"pyosys": "misc"}, + python_requires=">=3.8", + ext_modules=[libyosys_so_ext()], + cmdclass={ + "build_ext": custom_build_ext, + }, +) diff --git a/techlibs/common/simlib.v b/techlibs/common/simlib.v index 5bbad34f677..60f98af2838 100644 --- a/techlibs/common/simlib.v +++ b/techlibs/common/simlib.v @@ -58,7 +58,6 @@ endgenerate endmodule - // -------------------------------------------------------- // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| @@ -88,6 +87,27 @@ endmodule // -------------------------------------------------------- +// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| +//- +//- $buf (A, Y) +//- +//- A simple coarse-grain buffer cell type for the experimental buffered-normalized +//- mode. Note this cell does't get removed by 'opt_clean' and is not recommended +//- for general use. +//- +module \$buf (A, Y); + +parameter WIDTH = 0; + +input [WIDTH-1:0] A; +output [WIDTH-1:0] Y; + +assign Y = A; + +endmodule + +// -------------------------------------------------------- + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| //- //- $neg (A, Y) diff --git a/techlibs/common/techmap.v b/techlibs/common/techmap.v index 68b276588b5..11929614763 100644 --- a/techlibs/common/techmap.v +++ b/techlibs/common/techmap.v @@ -304,6 +304,7 @@ endmodule // Divide and Modulo // -------------------------------------------------------- +`ifndef NODIV module \$__div_mod_u (A, B, Y, R); parameter WIDTH = 1; @@ -531,7 +532,7 @@ module _90_modfloor (A, B, Y); .R(Y) ); endmodule - +`endif // -------------------------------------------------------- // Power diff --git a/techlibs/gowin/cells_sim.v b/techlibs/gowin/cells_sim.v index 96121a881e1..76635355d95 100644 --- a/techlibs/gowin/cells_sim.v +++ b/techlibs/gowin/cells_sim.v @@ -1966,5 +1966,103 @@ output CLKOUT; parameter DCS_MODE = "RISING"; endmodule +(* blackbox *) +module EMCU ( + input FCLK, + input PORESETN, + input SYSRESETN, + input RTCSRCCLK, + output [15:0] IOEXPOUTPUTO, + output [15:0] IOEXPOUTPUTENO, + input [15:0] IOEXPINPUTI, + output UART0TXDO, + output UART1TXDO, + output UART0BAUDTICK, + output UART1BAUDTICK, + input UART0RXDI, + input UART1RXDI, + output INTMONITOR, + output MTXHRESETN, + output [12:0] SRAM0ADDR, + output [3:0] SRAM0WREN, + output [31:0] SRAM0WDATA, + output SRAM0CS, + input [31:0] SRAM0RDATA, + output TARGFLASH0HSEL, + output [28:0] TARGFLASH0HADDR, + output [1:0] TARGFLASH0HTRANS, + output [2:0] TARGFLASH0HSIZE, + output [2:0] TARGFLASH0HBURST, + output TARGFLASH0HREADYMUX, + input [31:0] TARGFLASH0HRDATA, + input [2:0] TARGFLASH0HRUSER, + input TARGFLASH0HRESP, + input TARGFLASH0EXRESP, + input TARGFLASH0HREADYOUT, + output TARGEXP0HSEL, + output [31:0] TARGEXP0HADDR, + output [1:0] TARGEXP0HTRANS, + output TARGEXP0HWRITE, + output [2:0] TARGEXP0HSIZE, + output [2:0] TARGEXP0HBURST, + output [3:0] TARGEXP0HPROT, + output [1:0] TARGEXP0MEMATTR, + output TARGEXP0EXREQ, + output [3:0] TARGEXP0HMASTER, + output [31:0] TARGEXP0HWDATA, + output TARGEXP0HMASTLOCK, + output TARGEXP0HREADYMUX, + output TARGEXP0HAUSER, + output [3:0] TARGEXP0HWUSER, + input [31:0] TARGEXP0HRDATA, + input TARGEXP0HREADYOUT, + input TARGEXP0HRESP, + input TARGEXP0EXRESP, + input [2:0] TARGEXP0HRUSER, + output [31:0] INITEXP0HRDATA, + output INITEXP0HREADY, + output INITEXP0HRESP, + output INITEXP0EXRESP, + output [2:0] INITEXP0HRUSER, + input INITEXP0HSEL, + input [31:0] INITEXP0HADDR, + input [1:0] INITEXP0HTRANS, + input INITEXP0HWRITE, + input [2:0] INITEXP0HSIZE, + input [2:0] INITEXP0HBURST, + input [3:0] INITEXP0HPROT, + input [1:0] INITEXP0MEMATTR, + input INITEXP0EXREQ, + input [3:0] INITEXP0HMASTER, + input [31:0] INITEXP0HWDATA, + input INITEXP0HMASTLOCK, + input INITEXP0HAUSER, + input [3:0] INITEXP0HWUSER, + output [3:0] APBTARGEXP2PSTRB, + output [2:0] APBTARGEXP2PPROT, + output APBTARGEXP2PSEL, + output APBTARGEXP2PENABLE, + output [11:0] APBTARGEXP2PADDR, + output APBTARGEXP2PWRITE, + output [31:0] APBTARGEXP2PWDATA, + input [31:0] APBTARGEXP2PRDATA, + input APBTARGEXP2PREADY, + input APBTARGEXP2PSLVERR, + input [3:0] MTXREMAP, + output DAPTDO, + output DAPJTAGNSW, + output DAPNTDOEN, + input DAPSWDITMS, + input DAPTDI, + input DAPNTRST, + input DAPSWCLKTCK, + output [3:0] TPIUTRACEDATA, + output TPIUTRACECLK, + input [4:0] GPINT, + input FLASHERR, + input FLASHINT + + ); +endmodule diff --git a/techlibs/gowin/cells_xtra.py b/techlibs/gowin/cells_xtra.py index 224ece02f17..3f0047ecbff 100644 --- a/techlibs/gowin/cells_xtra.py +++ b/techlibs/gowin/cells_xtra.py @@ -23,7 +23,7 @@ class State(Enum): 'OSCO', 'OSCW', 'OSCZ', 'OSER10', 'OSER16', 'OSER10', 'OSER4', 'OSER8', 'OVIDEO', 'PLLVR', 'RAM16S1', 'RAM16S2', 'RAM16S4', 'RAM16SDP1', 'RAM16SDP2', 'RAM16SDP4', 'rPLL', 'SDP', - 'SDPX9', 'SP', 'SPX9', 'TBUF', 'TLVDS_OBUF', 'VCC' + 'SDPX9', 'SP', 'SPX9', 'TBUF', 'TLVDS_OBUF', 'VCC', 'DCS', 'EMCU' } def xtract_cells_decl(dir, fout): fname = os.path.join(dir, 'prim_sim.v') diff --git a/techlibs/gowin/cells_xtra.v b/techlibs/gowin/cells_xtra.v index 6ab50a097d1..4cb4bab2f8c 100644 --- a/techlibs/gowin/cells_xtra.v +++ b/techlibs/gowin/cells_xtra.v @@ -1699,9 +1699,6 @@ input CLKIN, CE; output CLKOUT, CLKOUTN; endmodule -module EMCU (...); -endmodule - module FLASH64K (...); input[4:0]XADR; input[5:0]YADR; diff --git a/techlibs/quicklogic/synth_quicklogic.cc b/techlibs/quicklogic/synth_quicklogic.cc index 0e7aaa75276..76ef4457069 100644 --- a/techlibs/quicklogic/synth_quicklogic.cc +++ b/techlibs/quicklogic/synth_quicklogic.cc @@ -266,7 +266,8 @@ struct SynthQuickLogicPass : public ScriptPass { if (check_label("map_gates")) { if (inferAdder && family == "qlf_k6n10f") { - run("techmap -map +/techmap.v -map " + lib_path + family + "/arith_map.v", "(unless -no_adder)"); + run("techmap -map +/techmap.v -map " + lib_path + family + "/arith_map.v -D NODIV", "(unless -no_adder)"); + run("techmap", "(unless -no_adder)"); } else { run("techmap"); } diff --git a/tests/arch/quicklogic/qlf_k6n10f/div.ys b/tests/arch/quicklogic/qlf_k6n10f/div.ys new file mode 100644 index 00000000000..dd5de9d3a78 --- /dev/null +++ b/tests/arch/quicklogic/qlf_k6n10f/div.ys @@ -0,0 +1,14 @@ +# division by constants should not infer carry chains. +read_verilog < sel(0u); assert(val.template bmux<4>(sel).get() == 0xfu); } + + { + // stream operator smoke test + cxxrtl::value<8> val(0x1fu); + std::ostringstream oss; + oss << val; + assert(oss.str() == "8'1f"); + } } diff --git a/tests/functional/.gitignore b/tests/functional/.gitignore new file mode 100644 index 00000000000..f0c4cf14e45 --- /dev/null +++ b/tests/functional/.gitignore @@ -0,0 +1,6 @@ +my_module_cxxrtl.cc +my_module_functional_cxx.cc +vcd_harness +*.vcd +*.smt2 +*.rkt diff --git a/tests/functional/README.md b/tests/functional/README.md new file mode 100644 index 00000000000..1459c3198b8 --- /dev/null +++ b/tests/functional/README.md @@ -0,0 +1,19 @@ +Tests for the functional backend use pytest as a testrunner. + +Run with `pytest -v` + +Pytest options you might want: + +- `-v`: More progress indication. + +- `--basetemp tmp`: Store test files (including vcd results) in tmp. + CAREFUL: contents of tmp will be deleted + +- `-k `: Run only tests that contain the pattern, e.g. + `-k cxx` or `-k smt` or `-k demux` or `-k 'cxx[demux` + +- `-s`: Don't hide stdout/stderr from the test code. + +Custom options for functional backend tests: + +- `--per-cell N`: Run only N tests for each cell. diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py new file mode 100644 index 00000000000..a9fbb3c5947 --- /dev/null +++ b/tests/functional/conftest.py @@ -0,0 +1,34 @@ +import pytest +from rtlil_cells import generate_test_cases +import random + +random_seed = random.getrandbits(32) + +def pytest_configure(config): + config.addinivalue_line("markers", "smt: test uses smtlib/z3") + config.addinivalue_line("markers", "rkt: test uses racket/rosette") + +def pytest_addoption(parser): + parser.addoption("--per-cell", type=int, default=None, help="run only N tests per cell") + parser.addoption("--steps", type=int, default=1000, help="run each test for N steps") + parser.addoption("--seed", type=int, default=random_seed, help="seed for random number generation, use random seed if unspecified") + +def pytest_collection_finish(session): + print('random seed: {}'.format(session.config.getoption("seed"))) + +@pytest.fixture +def num_steps(request): + return request.config.getoption("steps") + +@pytest.fixture +def rnd(request): + seed1 = request.config.getoption("seed") + return lambda seed2: random.Random('{}-{}'.format(seed1, seed2)) + +def pytest_generate_tests(metafunc): + if "cell" in metafunc.fixturenames: + per_cell = metafunc.config.getoption("per_cell", default=None) + seed1 = metafunc.config.getoption("seed") + rnd = lambda seed2: random.Random('{}-{}'.format(seed1, seed2)) + names, cases = generate_test_cases(per_cell, rnd) + metafunc.parametrize("cell,parameters", cases, ids=names) diff --git a/tests/functional/picorv32.v b/tests/functional/picorv32.v new file mode 100644 index 00000000000..cfc7ce0627a --- /dev/null +++ b/tests/functional/picorv32.v @@ -0,0 +1,3044 @@ +/* + * PicoRV32 -- A Small RISC-V (RV32I) Processor Core + * + * Copyright (C) 2015 Claire Xenia Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/* verilator lint_off WIDTH */ +/* verilator lint_off PINMISSING */ +/* verilator lint_off CASEOVERLAP */ +/* verilator lint_off CASEINCOMPLETE */ + +`timescale 1 ns / 1 ps +// `default_nettype none +// `define DEBUGNETS +// `define DEBUGREGS +// `define DEBUGASM +// `define DEBUG + +`ifdef DEBUG + `define debug(debug_command) debug_command +`else + `define debug(debug_command) +`endif + +`ifdef FORMAL + `define FORMAL_KEEP (* keep *) + `define assert(assert_expr) assert(assert_expr) +`else + `ifdef DEBUGNETS + `define FORMAL_KEEP (* keep *) + `else + `define FORMAL_KEEP + `endif + `define assert(assert_expr) empty_statement +`endif + +// uncomment this for register file in extra module +// `define PICORV32_REGS picorv32_regs + +// this macro can be used to check if the verilog files in your +// design are read in the correct order. +`define PICORV32_V + + +/*************************************************************** + * picorv32 + ***************************************************************/ + +module picorv32 #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] LATCHED_MEM_RDATA = 0, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + input clk, resetn, + output reg trap, + + output reg mem_valid, + output reg mem_instr, + input mem_ready, + + output reg [31:0] mem_addr, + output reg [31:0] mem_wdata, + output reg [ 3:0] mem_wstrb, + input [31:0] mem_rdata, + + // Look-Ahead Interface + output mem_la_read, + output mem_la_write, + output [31:0] mem_la_addr, + output reg [31:0] mem_la_wdata, + output reg [ 3:0] mem_la_wstrb, + + // Pico Co-Processor Interface (PCPI) + output reg pcpi_valid, + output reg [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ Interface + input [31:0] irq, + output reg [31:0] eoi, + +`ifdef RISCV_FORMAL + output reg rvfi_valid, + output reg [63:0] rvfi_order, + output reg [31:0] rvfi_insn, + output reg rvfi_trap, + output reg rvfi_halt, + output reg rvfi_intr, + output reg [ 1:0] rvfi_mode, + output reg [ 1:0] rvfi_ixl, + output reg [ 4:0] rvfi_rs1_addr, + output reg [ 4:0] rvfi_rs2_addr, + output reg [31:0] rvfi_rs1_rdata, + output reg [31:0] rvfi_rs2_rdata, + output reg [ 4:0] rvfi_rd_addr, + output reg [31:0] rvfi_rd_wdata, + output reg [31:0] rvfi_pc_rdata, + output reg [31:0] rvfi_pc_wdata, + output reg [31:0] rvfi_mem_addr, + output reg [ 3:0] rvfi_mem_rmask, + output reg [ 3:0] rvfi_mem_wmask, + output reg [31:0] rvfi_mem_rdata, + output reg [31:0] rvfi_mem_wdata, + + output reg [63:0] rvfi_csr_mcycle_rmask, + output reg [63:0] rvfi_csr_mcycle_wmask, + output reg [63:0] rvfi_csr_mcycle_rdata, + output reg [63:0] rvfi_csr_mcycle_wdata, + + output reg [63:0] rvfi_csr_minstret_rmask, + output reg [63:0] rvfi_csr_minstret_wmask, + output reg [63:0] rvfi_csr_minstret_rdata, + output reg [63:0] rvfi_csr_minstret_wdata, +`endif + + // Trace Interface + output reg trace_valid, + output reg [35:0] trace_data +); + localparam integer irq_timer = 0; + localparam integer irq_ebreak = 1; + localparam integer irq_buserror = 2; + + localparam integer irqregs_offset = ENABLE_REGS_16_31 ? 32 : 16; + localparam integer regfile_size = (ENABLE_REGS_16_31 ? 32 : 16) + 4*ENABLE_IRQ*ENABLE_IRQ_QREGS; + localparam integer regindex_bits = (ENABLE_REGS_16_31 ? 5 : 4) + ENABLE_IRQ*ENABLE_IRQ_QREGS; + + localparam WITH_PCPI = ENABLE_PCPI || ENABLE_MUL || ENABLE_FAST_MUL || ENABLE_DIV; + + localparam [35:0] TRACE_BRANCH = {4'b 0001, 32'b 0}; + localparam [35:0] TRACE_ADDR = {4'b 0010, 32'b 0}; + localparam [35:0] TRACE_IRQ = {4'b 1000, 32'b 0}; + + reg [63:0] count_cycle, count_instr; + reg [31:0] reg_pc, reg_next_pc, reg_op1, reg_op2, reg_out; + reg [4:0] reg_sh; + + reg [31:0] next_insn_opcode; + reg [31:0] dbg_insn_opcode; + reg [31:0] dbg_insn_addr; + + wire dbg_mem_valid = mem_valid; + wire dbg_mem_instr = mem_instr; + wire dbg_mem_ready = mem_ready; + wire [31:0] dbg_mem_addr = mem_addr; + wire [31:0] dbg_mem_wdata = mem_wdata; + wire [ 3:0] dbg_mem_wstrb = mem_wstrb; + wire [31:0] dbg_mem_rdata = mem_rdata; + + assign pcpi_rs1 = reg_op1; + assign pcpi_rs2 = reg_op2; + + wire [31:0] next_pc; + + reg irq_delay; + reg irq_active; + reg [31:0] irq_mask; + reg [31:0] irq_pending; + reg [31:0] timer; + +`ifndef PICORV32_REGS + reg [31:0] cpuregs [0:regfile_size-1]; + + integer i; + initial begin + if (REGS_INIT_ZERO) begin + for (i = 0; i < regfile_size; i = i+1) + cpuregs[i] = 0; + end + end +`endif + + task empty_statement; + // This task is used by the `assert directive in non-formal mode to + // avoid empty statement (which are unsupported by plain Verilog syntax). + begin end + endtask + +`ifdef DEBUGREGS + wire [31:0] dbg_reg_x0 = 0; + wire [31:0] dbg_reg_x1 = cpuregs[1]; + wire [31:0] dbg_reg_x2 = cpuregs[2]; + wire [31:0] dbg_reg_x3 = cpuregs[3]; + wire [31:0] dbg_reg_x4 = cpuregs[4]; + wire [31:0] dbg_reg_x5 = cpuregs[5]; + wire [31:0] dbg_reg_x6 = cpuregs[6]; + wire [31:0] dbg_reg_x7 = cpuregs[7]; + wire [31:0] dbg_reg_x8 = cpuregs[8]; + wire [31:0] dbg_reg_x9 = cpuregs[9]; + wire [31:0] dbg_reg_x10 = cpuregs[10]; + wire [31:0] dbg_reg_x11 = cpuregs[11]; + wire [31:0] dbg_reg_x12 = cpuregs[12]; + wire [31:0] dbg_reg_x13 = cpuregs[13]; + wire [31:0] dbg_reg_x14 = cpuregs[14]; + wire [31:0] dbg_reg_x15 = cpuregs[15]; + wire [31:0] dbg_reg_x16 = cpuregs[16]; + wire [31:0] dbg_reg_x17 = cpuregs[17]; + wire [31:0] dbg_reg_x18 = cpuregs[18]; + wire [31:0] dbg_reg_x19 = cpuregs[19]; + wire [31:0] dbg_reg_x20 = cpuregs[20]; + wire [31:0] dbg_reg_x21 = cpuregs[21]; + wire [31:0] dbg_reg_x22 = cpuregs[22]; + wire [31:0] dbg_reg_x23 = cpuregs[23]; + wire [31:0] dbg_reg_x24 = cpuregs[24]; + wire [31:0] dbg_reg_x25 = cpuregs[25]; + wire [31:0] dbg_reg_x26 = cpuregs[26]; + wire [31:0] dbg_reg_x27 = cpuregs[27]; + wire [31:0] dbg_reg_x28 = cpuregs[28]; + wire [31:0] dbg_reg_x29 = cpuregs[29]; + wire [31:0] dbg_reg_x30 = cpuregs[30]; + wire [31:0] dbg_reg_x31 = cpuregs[31]; +`endif + + // Internal PCPI Cores + + wire pcpi_mul_wr; + wire [31:0] pcpi_mul_rd; + wire pcpi_mul_wait; + wire pcpi_mul_ready; + + wire pcpi_div_wr; + wire [31:0] pcpi_div_rd; + wire pcpi_div_wait; + wire pcpi_div_ready; + + reg pcpi_int_wr; + reg [31:0] pcpi_int_rd; + reg pcpi_int_wait; + reg pcpi_int_ready; + + generate if (ENABLE_FAST_MUL) begin + picorv32_pcpi_fast_mul pcpi_mul ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_mul_wr ), + .pcpi_rd (pcpi_mul_rd ), + .pcpi_wait (pcpi_mul_wait ), + .pcpi_ready(pcpi_mul_ready ) + ); + end else if (ENABLE_MUL) begin + picorv32_pcpi_mul pcpi_mul ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_mul_wr ), + .pcpi_rd (pcpi_mul_rd ), + .pcpi_wait (pcpi_mul_wait ), + .pcpi_ready(pcpi_mul_ready ) + ); + end else begin + assign pcpi_mul_wr = 0; + assign pcpi_mul_rd = 32'bx; + assign pcpi_mul_wait = 0; + assign pcpi_mul_ready = 0; + end endgenerate + + generate if (ENABLE_DIV) begin + picorv32_pcpi_div pcpi_div ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_div_wr ), + .pcpi_rd (pcpi_div_rd ), + .pcpi_wait (pcpi_div_wait ), + .pcpi_ready(pcpi_div_ready ) + ); + end else begin + assign pcpi_div_wr = 0; + assign pcpi_div_rd = 32'bx; + assign pcpi_div_wait = 0; + assign pcpi_div_ready = 0; + end endgenerate + + always @* begin + pcpi_int_wr = 0; + pcpi_int_rd = 32'bx; + pcpi_int_wait = |{ENABLE_PCPI && pcpi_wait, (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_wait, ENABLE_DIV && pcpi_div_wait}; + pcpi_int_ready = |{ENABLE_PCPI && pcpi_ready, (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_ready, ENABLE_DIV && pcpi_div_ready}; + + (* parallel_case *) + case (1'b1) + ENABLE_PCPI && pcpi_ready: begin + pcpi_int_wr = ENABLE_PCPI ? pcpi_wr : 0; + pcpi_int_rd = ENABLE_PCPI ? pcpi_rd : 0; + end + (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_ready: begin + pcpi_int_wr = pcpi_mul_wr; + pcpi_int_rd = pcpi_mul_rd; + end + ENABLE_DIV && pcpi_div_ready: begin + pcpi_int_wr = pcpi_div_wr; + pcpi_int_rd = pcpi_div_rd; + end + endcase + end + + + // Memory Interface + + reg [1:0] mem_state; + reg [1:0] mem_wordsize; + reg [31:0] mem_rdata_word; + reg [31:0] mem_rdata_q; + reg mem_do_prefetch; + reg mem_do_rinst; + reg mem_do_rdata; + reg mem_do_wdata; + + wire mem_xfer; + reg mem_la_secondword, mem_la_firstword_reg, last_mem_valid; + wire mem_la_firstword = COMPRESSED_ISA && (mem_do_prefetch || mem_do_rinst) && next_pc[1] && !mem_la_secondword; + wire mem_la_firstword_xfer = COMPRESSED_ISA && mem_xfer && (!last_mem_valid ? mem_la_firstword : mem_la_firstword_reg); + + reg prefetched_high_word; + reg clear_prefetched_high_word; + reg [15:0] mem_16bit_buffer; + + wire [31:0] mem_rdata_latched_noshuffle; + wire [31:0] mem_rdata_latched; + + wire mem_la_use_prefetched_high_word = COMPRESSED_ISA && mem_la_firstword && prefetched_high_word && !clear_prefetched_high_word; + assign mem_xfer = (mem_valid && mem_ready) || (mem_la_use_prefetched_high_word && mem_do_rinst); + + wire mem_busy = |{mem_do_prefetch, mem_do_rinst, mem_do_rdata, mem_do_wdata}; + wire mem_done = resetn && ((mem_xfer && |mem_state && (mem_do_rinst || mem_do_rdata || mem_do_wdata)) || (&mem_state && mem_do_rinst)) && + (!mem_la_firstword || (~&mem_rdata_latched[1:0] && mem_xfer)); + + assign mem_la_write = resetn && !mem_state && mem_do_wdata; + assign mem_la_read = resetn && ((!mem_la_use_prefetched_high_word && !mem_state && (mem_do_rinst || mem_do_prefetch || mem_do_rdata)) || + (COMPRESSED_ISA && mem_xfer && (!last_mem_valid ? mem_la_firstword : mem_la_firstword_reg) && !mem_la_secondword && &mem_rdata_latched[1:0])); + assign mem_la_addr = (mem_do_prefetch || mem_do_rinst) ? {next_pc[31:2] + mem_la_firstword_xfer, 2'b00} : {reg_op1[31:2], 2'b00}; + + assign mem_rdata_latched_noshuffle = (mem_xfer || LATCHED_MEM_RDATA) ? mem_rdata : mem_rdata_q; + + assign mem_rdata_latched = COMPRESSED_ISA && mem_la_use_prefetched_high_word ? {16'bx, mem_16bit_buffer} : + COMPRESSED_ISA && mem_la_secondword ? {mem_rdata_latched_noshuffle[15:0], mem_16bit_buffer} : + COMPRESSED_ISA && mem_la_firstword ? {16'bx, mem_rdata_latched_noshuffle[31:16]} : mem_rdata_latched_noshuffle; + + always @(posedge clk) begin + if (!resetn) begin + mem_la_firstword_reg <= 0; + last_mem_valid <= 0; + end else begin + if (!last_mem_valid) + mem_la_firstword_reg <= mem_la_firstword; + last_mem_valid <= mem_valid && !mem_ready; + end + end + + always @* begin + (* full_case *) + case (mem_wordsize) + 0: begin + mem_la_wdata = reg_op2; + mem_la_wstrb = 4'b1111; + mem_rdata_word = mem_rdata; + end + 1: begin + mem_la_wdata = {2{reg_op2[15:0]}}; + mem_la_wstrb = reg_op1[1] ? 4'b1100 : 4'b0011; + case (reg_op1[1]) + 1'b0: mem_rdata_word = {16'b0, mem_rdata[15: 0]}; + 1'b1: mem_rdata_word = {16'b0, mem_rdata[31:16]}; + endcase + end + 2: begin + mem_la_wdata = {4{reg_op2[7:0]}}; + mem_la_wstrb = 4'b0001 << reg_op1[1:0]; + case (reg_op1[1:0]) + 2'b00: mem_rdata_word = {24'b0, mem_rdata[ 7: 0]}; + 2'b01: mem_rdata_word = {24'b0, mem_rdata[15: 8]}; + 2'b10: mem_rdata_word = {24'b0, mem_rdata[23:16]}; + 2'b11: mem_rdata_word = {24'b0, mem_rdata[31:24]}; + endcase + end + endcase + end + + always @(posedge clk) begin + if (mem_xfer) begin + mem_rdata_q <= COMPRESSED_ISA ? mem_rdata_latched : mem_rdata; + next_insn_opcode <= COMPRESSED_ISA ? mem_rdata_latched : mem_rdata; + end + + if (COMPRESSED_ISA && mem_done && (mem_do_prefetch || mem_do_rinst)) begin + case (mem_rdata_latched[1:0]) + 2'b00: begin // Quadrant 0 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.ADDI4SPN + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= {2'b0, mem_rdata_latched[10:7], mem_rdata_latched[12:11], mem_rdata_latched[5], mem_rdata_latched[6], 2'b00}; + end + 3'b010: begin // C.LW + mem_rdata_q[31:20] <= {5'b0, mem_rdata_latched[5], mem_rdata_latched[12:10], mem_rdata_latched[6], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + 3'b 110: begin // C.SW + {mem_rdata_q[31:25], mem_rdata_q[11:7]} <= {5'b0, mem_rdata_latched[5], mem_rdata_latched[12:10], mem_rdata_latched[6], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + endcase + end + 2'b01: begin // Quadrant 1 + case (mem_rdata_latched[15:13]) + 3'b 000: begin // C.ADDI + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + 3'b 010: begin // C.LI + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + 3'b 011: begin + if (mem_rdata_latched[11:7] == 2) begin // C.ADDI16SP + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[4:3], + mem_rdata_latched[5], mem_rdata_latched[2], mem_rdata_latched[6], 4'b 0000}); + end else begin // C.LUI + mem_rdata_q[31:12] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + end + 3'b100: begin + if (mem_rdata_latched[11:10] == 2'b00) begin // C.SRLI + mem_rdata_q[31:25] <= 7'b0000000; + mem_rdata_q[14:12] <= 3'b 101; + end + if (mem_rdata_latched[11:10] == 2'b01) begin // C.SRAI + mem_rdata_q[31:25] <= 7'b0100000; + mem_rdata_q[14:12] <= 3'b 101; + end + if (mem_rdata_latched[11:10] == 2'b10) begin // C.ANDI + mem_rdata_q[14:12] <= 3'b111; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + if (mem_rdata_latched[12:10] == 3'b011) begin // C.SUB, C.XOR, C.OR, C.AND + if (mem_rdata_latched[6:5] == 2'b00) mem_rdata_q[14:12] <= 3'b000; + if (mem_rdata_latched[6:5] == 2'b01) mem_rdata_q[14:12] <= 3'b100; + if (mem_rdata_latched[6:5] == 2'b10) mem_rdata_q[14:12] <= 3'b110; + if (mem_rdata_latched[6:5] == 2'b11) mem_rdata_q[14:12] <= 3'b111; + mem_rdata_q[31:25] <= mem_rdata_latched[6:5] == 2'b00 ? 7'b0100000 : 7'b0000000; + end + end + 3'b 110: begin // C.BEQZ + mem_rdata_q[14:12] <= 3'b000; + { mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8] } <= + $signed({mem_rdata_latched[12], mem_rdata_latched[6:5], mem_rdata_latched[2], + mem_rdata_latched[11:10], mem_rdata_latched[4:3]}); + end + 3'b 111: begin // C.BNEZ + mem_rdata_q[14:12] <= 3'b001; + { mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8] } <= + $signed({mem_rdata_latched[12], mem_rdata_latched[6:5], mem_rdata_latched[2], + mem_rdata_latched[11:10], mem_rdata_latched[4:3]}); + end + endcase + end + 2'b10: begin // Quadrant 2 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.SLLI + mem_rdata_q[31:25] <= 7'b0000000; + mem_rdata_q[14:12] <= 3'b 001; + end + 3'b010: begin // C.LWSP + mem_rdata_q[31:20] <= {4'b0, mem_rdata_latched[3:2], mem_rdata_latched[12], mem_rdata_latched[6:4], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + 3'b100: begin + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] == 0) begin // C.JR + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= 12'b0; + end + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] != 0) begin // C.MV + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:25] <= 7'b0000000; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JALR + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= 12'b0; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[6:2] != 0) begin // C.ADD + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:25] <= 7'b0000000; + end + end + 3'b110: begin // C.SWSP + {mem_rdata_q[31:25], mem_rdata_q[11:7]} <= {4'b0, mem_rdata_latched[8:7], mem_rdata_latched[12:9], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + endcase + end + endcase + end + end + + always @(posedge clk) begin + if (resetn && !trap) begin + if (mem_do_prefetch || mem_do_rinst || mem_do_rdata) + `assert(!mem_do_wdata); + + if (mem_do_prefetch || mem_do_rinst) + `assert(!mem_do_rdata); + + if (mem_do_rdata) + `assert(!mem_do_prefetch && !mem_do_rinst); + + if (mem_do_wdata) + `assert(!(mem_do_prefetch || mem_do_rinst || mem_do_rdata)); + + if (mem_state == 2 || mem_state == 3) + `assert(mem_valid || mem_do_prefetch); + end + end + + always @(posedge clk) begin + if (!resetn || trap) begin + if (!resetn) + mem_state <= 0; + if (!resetn || mem_ready) + mem_valid <= 0; + mem_la_secondword <= 0; + prefetched_high_word <= 0; + end else begin + if (mem_la_read || mem_la_write) begin + mem_addr <= mem_la_addr; + mem_wstrb <= mem_la_wstrb & {4{mem_la_write}}; + end + if (mem_la_write) begin + mem_wdata <= mem_la_wdata; + end + case (mem_state) + 0: begin + if (mem_do_prefetch || mem_do_rinst || mem_do_rdata) begin + mem_valid <= !mem_la_use_prefetched_high_word; + mem_instr <= mem_do_prefetch || mem_do_rinst; + mem_wstrb <= 0; + mem_state <= 1; + end + if (mem_do_wdata) begin + mem_valid <= 1; + mem_instr <= 0; + mem_state <= 2; + end + end + 1: begin + `assert(mem_wstrb == 0); + `assert(mem_do_prefetch || mem_do_rinst || mem_do_rdata); + `assert(mem_valid == !mem_la_use_prefetched_high_word); + `assert(mem_instr == (mem_do_prefetch || mem_do_rinst)); + if (mem_xfer) begin + if (COMPRESSED_ISA && mem_la_read) begin + mem_valid <= 1; + mem_la_secondword <= 1; + if (!mem_la_use_prefetched_high_word) + mem_16bit_buffer <= mem_rdata[31:16]; + end else begin + mem_valid <= 0; + mem_la_secondword <= 0; + if (COMPRESSED_ISA && !mem_do_rdata) begin + if (~&mem_rdata[1:0] || mem_la_secondword) begin + mem_16bit_buffer <= mem_rdata[31:16]; + prefetched_high_word <= 1; + end else begin + prefetched_high_word <= 0; + end + end + mem_state <= mem_do_rinst || mem_do_rdata ? 0 : 3; + end + end + end + 2: begin + `assert(mem_wstrb != 0); + `assert(mem_do_wdata); + if (mem_xfer) begin + mem_valid <= 0; + mem_state <= 0; + end + end + 3: begin + `assert(mem_wstrb == 0); + `assert(mem_do_prefetch); + if (mem_do_rinst) begin + mem_state <= 0; + end + end + endcase + end + + if (clear_prefetched_high_word) + prefetched_high_word <= 0; + end + + + // Instruction Decoder + + reg instr_lui, instr_auipc, instr_jal, instr_jalr; + reg instr_beq, instr_bne, instr_blt, instr_bge, instr_bltu, instr_bgeu; + reg instr_lb, instr_lh, instr_lw, instr_lbu, instr_lhu, instr_sb, instr_sh, instr_sw; + reg instr_addi, instr_slti, instr_sltiu, instr_xori, instr_ori, instr_andi, instr_slli, instr_srli, instr_srai; + reg instr_add, instr_sub, instr_sll, instr_slt, instr_sltu, instr_xor, instr_srl, instr_sra, instr_or, instr_and; + reg instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh, instr_ecall_ebreak; + reg instr_getq, instr_setq, instr_retirq, instr_maskirq, instr_waitirq, instr_timer; + wire instr_trap; + + reg [regindex_bits-1:0] decoded_rd, decoded_rs1, decoded_rs2; + reg [31:0] decoded_imm, decoded_imm_j; + reg decoder_trigger; + reg decoder_trigger_q; + reg decoder_pseudo_trigger; + reg decoder_pseudo_trigger_q; + reg compressed_instr; + + reg is_lui_auipc_jal; + reg is_lb_lh_lw_lbu_lhu; + reg is_slli_srli_srai; + reg is_jalr_addi_slti_sltiu_xori_ori_andi; + reg is_sb_sh_sw; + reg is_sll_srl_sra; + reg is_lui_auipc_jal_jalr_addi_add_sub; + reg is_slti_blt_slt; + reg is_sltiu_bltu_sltu; + reg is_beq_bne_blt_bge_bltu_bgeu; + reg is_lbu_lhu_lw; + reg is_alu_reg_imm; + reg is_alu_reg_reg; + reg is_compare; + + assign instr_trap = (CATCH_ILLINSN || WITH_PCPI) && !{instr_lui, instr_auipc, instr_jal, instr_jalr, + instr_beq, instr_bne, instr_blt, instr_bge, instr_bltu, instr_bgeu, + instr_lb, instr_lh, instr_lw, instr_lbu, instr_lhu, instr_sb, instr_sh, instr_sw, + instr_addi, instr_slti, instr_sltiu, instr_xori, instr_ori, instr_andi, instr_slli, instr_srli, instr_srai, + instr_add, instr_sub, instr_sll, instr_slt, instr_sltu, instr_xor, instr_srl, instr_sra, instr_or, instr_and, + instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh, + instr_getq, instr_setq, instr_retirq, instr_maskirq, instr_waitirq, instr_timer}; + + wire is_rdcycle_rdcycleh_rdinstr_rdinstrh; + assign is_rdcycle_rdcycleh_rdinstr_rdinstrh = |{instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh}; + + reg [63:0] new_ascii_instr; + `FORMAL_KEEP reg [63:0] dbg_ascii_instr; + `FORMAL_KEEP reg [31:0] dbg_insn_imm; + `FORMAL_KEEP reg [4:0] dbg_insn_rs1; + `FORMAL_KEEP reg [4:0] dbg_insn_rs2; + `FORMAL_KEEP reg [4:0] dbg_insn_rd; + `FORMAL_KEEP reg [31:0] dbg_rs1val; + `FORMAL_KEEP reg [31:0] dbg_rs2val; + `FORMAL_KEEP reg dbg_rs1val_valid; + `FORMAL_KEEP reg dbg_rs2val_valid; + + always @* begin + new_ascii_instr = ""; + + if (instr_lui) new_ascii_instr = "lui"; + if (instr_auipc) new_ascii_instr = "auipc"; + if (instr_jal) new_ascii_instr = "jal"; + if (instr_jalr) new_ascii_instr = "jalr"; + + if (instr_beq) new_ascii_instr = "beq"; + if (instr_bne) new_ascii_instr = "bne"; + if (instr_blt) new_ascii_instr = "blt"; + if (instr_bge) new_ascii_instr = "bge"; + if (instr_bltu) new_ascii_instr = "bltu"; + if (instr_bgeu) new_ascii_instr = "bgeu"; + + if (instr_lb) new_ascii_instr = "lb"; + if (instr_lh) new_ascii_instr = "lh"; + if (instr_lw) new_ascii_instr = "lw"; + if (instr_lbu) new_ascii_instr = "lbu"; + if (instr_lhu) new_ascii_instr = "lhu"; + if (instr_sb) new_ascii_instr = "sb"; + if (instr_sh) new_ascii_instr = "sh"; + if (instr_sw) new_ascii_instr = "sw"; + + if (instr_addi) new_ascii_instr = "addi"; + if (instr_slti) new_ascii_instr = "slti"; + if (instr_sltiu) new_ascii_instr = "sltiu"; + if (instr_xori) new_ascii_instr = "xori"; + if (instr_ori) new_ascii_instr = "ori"; + if (instr_andi) new_ascii_instr = "andi"; + if (instr_slli) new_ascii_instr = "slli"; + if (instr_srli) new_ascii_instr = "srli"; + if (instr_srai) new_ascii_instr = "srai"; + + if (instr_add) new_ascii_instr = "add"; + if (instr_sub) new_ascii_instr = "sub"; + if (instr_sll) new_ascii_instr = "sll"; + if (instr_slt) new_ascii_instr = "slt"; + if (instr_sltu) new_ascii_instr = "sltu"; + if (instr_xor) new_ascii_instr = "xor"; + if (instr_srl) new_ascii_instr = "srl"; + if (instr_sra) new_ascii_instr = "sra"; + if (instr_or) new_ascii_instr = "or"; + if (instr_and) new_ascii_instr = "and"; + + if (instr_rdcycle) new_ascii_instr = "rdcycle"; + if (instr_rdcycleh) new_ascii_instr = "rdcycleh"; + if (instr_rdinstr) new_ascii_instr = "rdinstr"; + if (instr_rdinstrh) new_ascii_instr = "rdinstrh"; + + if (instr_getq) new_ascii_instr = "getq"; + if (instr_setq) new_ascii_instr = "setq"; + if (instr_retirq) new_ascii_instr = "retirq"; + if (instr_maskirq) new_ascii_instr = "maskirq"; + if (instr_waitirq) new_ascii_instr = "waitirq"; + if (instr_timer) new_ascii_instr = "timer"; + end + + reg [63:0] q_ascii_instr; + reg [31:0] q_insn_imm; + reg [31:0] q_insn_opcode; + reg [4:0] q_insn_rs1; + reg [4:0] q_insn_rs2; + reg [4:0] q_insn_rd; + reg dbg_next; + + wire launch_next_insn; + reg dbg_valid_insn; + + reg [63:0] cached_ascii_instr; + reg [31:0] cached_insn_imm; + reg [31:0] cached_insn_opcode; + reg [4:0] cached_insn_rs1; + reg [4:0] cached_insn_rs2; + reg [4:0] cached_insn_rd; + + always @(posedge clk) begin + q_ascii_instr <= dbg_ascii_instr; + q_insn_imm <= dbg_insn_imm; + q_insn_opcode <= dbg_insn_opcode; + q_insn_rs1 <= dbg_insn_rs1; + q_insn_rs2 <= dbg_insn_rs2; + q_insn_rd <= dbg_insn_rd; + dbg_next <= launch_next_insn; + + if (!resetn || trap) + dbg_valid_insn <= 0; + else if (launch_next_insn) + dbg_valid_insn <= 1; + + if (decoder_trigger_q) begin + cached_ascii_instr <= new_ascii_instr; + cached_insn_imm <= decoded_imm; + if (&next_insn_opcode[1:0]) + cached_insn_opcode <= next_insn_opcode; + else + cached_insn_opcode <= {16'b0, next_insn_opcode[15:0]}; + cached_insn_rs1 <= decoded_rs1; + cached_insn_rs2 <= decoded_rs2; + cached_insn_rd <= decoded_rd; + end + + if (launch_next_insn) begin + dbg_insn_addr <= next_pc; + end + end + + always @* begin + dbg_ascii_instr = q_ascii_instr; + dbg_insn_imm = q_insn_imm; + dbg_insn_opcode = q_insn_opcode; + dbg_insn_rs1 = q_insn_rs1; + dbg_insn_rs2 = q_insn_rs2; + dbg_insn_rd = q_insn_rd; + + if (dbg_next) begin + if (decoder_pseudo_trigger_q) begin + dbg_ascii_instr = cached_ascii_instr; + dbg_insn_imm = cached_insn_imm; + dbg_insn_opcode = cached_insn_opcode; + dbg_insn_rs1 = cached_insn_rs1; + dbg_insn_rs2 = cached_insn_rs2; + dbg_insn_rd = cached_insn_rd; + end else begin + dbg_ascii_instr = new_ascii_instr; + if (&next_insn_opcode[1:0]) + dbg_insn_opcode = next_insn_opcode; + else + dbg_insn_opcode = {16'b0, next_insn_opcode[15:0]}; + dbg_insn_imm = decoded_imm; + dbg_insn_rs1 = decoded_rs1; + dbg_insn_rs2 = decoded_rs2; + dbg_insn_rd = decoded_rd; + end + end + end + +`ifdef DEBUGASM + always @(posedge clk) begin + if (dbg_next) begin + $display("debugasm %x %x %s", dbg_insn_addr, dbg_insn_opcode, dbg_ascii_instr ? dbg_ascii_instr : "*"); + end + end +`endif + +`ifdef DEBUG + always @(posedge clk) begin + if (dbg_next) begin + if (&dbg_insn_opcode[1:0]) + $display("DECODE: 0x%08x 0x%08x %-0s", dbg_insn_addr, dbg_insn_opcode, dbg_ascii_instr ? dbg_ascii_instr : "UNKNOWN"); + else + $display("DECODE: 0x%08x 0x%04x %-0s", dbg_insn_addr, dbg_insn_opcode[15:0], dbg_ascii_instr ? dbg_ascii_instr : "UNKNOWN"); + end + end +`endif + + always @(posedge clk) begin + is_lui_auipc_jal <= |{instr_lui, instr_auipc, instr_jal}; + is_lui_auipc_jal_jalr_addi_add_sub <= |{instr_lui, instr_auipc, instr_jal, instr_jalr, instr_addi, instr_add, instr_sub}; + is_slti_blt_slt <= |{instr_slti, instr_blt, instr_slt}; + is_sltiu_bltu_sltu <= |{instr_sltiu, instr_bltu, instr_sltu}; + is_lbu_lhu_lw <= |{instr_lbu, instr_lhu, instr_lw}; + is_compare <= |{is_beq_bne_blt_bge_bltu_bgeu, instr_slti, instr_slt, instr_sltiu, instr_sltu}; + + if (mem_do_rinst && mem_done) begin + instr_lui <= mem_rdata_latched[6:0] == 7'b0110111; + instr_auipc <= mem_rdata_latched[6:0] == 7'b0010111; + instr_jal <= mem_rdata_latched[6:0] == 7'b1101111; + instr_jalr <= mem_rdata_latched[6:0] == 7'b1100111 && mem_rdata_latched[14:12] == 3'b000; + instr_retirq <= mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000010 && ENABLE_IRQ; + instr_waitirq <= mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000100 && ENABLE_IRQ; + + is_beq_bne_blt_bge_bltu_bgeu <= mem_rdata_latched[6:0] == 7'b1100011; + is_lb_lh_lw_lbu_lhu <= mem_rdata_latched[6:0] == 7'b0000011; + is_sb_sh_sw <= mem_rdata_latched[6:0] == 7'b0100011; + is_alu_reg_imm <= mem_rdata_latched[6:0] == 7'b0010011; + is_alu_reg_reg <= mem_rdata_latched[6:0] == 7'b0110011; + + { decoded_imm_j[31:20], decoded_imm_j[10:1], decoded_imm_j[11], decoded_imm_j[19:12], decoded_imm_j[0] } <= $signed({mem_rdata_latched[31:12], 1'b0}); + + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[19:15]; + decoded_rs2 <= mem_rdata_latched[24:20]; + + if (mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000000 && ENABLE_IRQ && ENABLE_IRQ_QREGS) + decoded_rs1[regindex_bits-1] <= 1; // instr_getq + + if (mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000010 && ENABLE_IRQ) + decoded_rs1 <= ENABLE_IRQ_QREGS ? irqregs_offset : 3; // instr_retirq + + compressed_instr <= 0; + if (COMPRESSED_ISA && mem_rdata_latched[1:0] != 2'b11) begin + compressed_instr <= 1; + decoded_rd <= 0; + decoded_rs1 <= 0; + decoded_rs2 <= 0; + + { decoded_imm_j[31:11], decoded_imm_j[4], decoded_imm_j[9:8], decoded_imm_j[10], decoded_imm_j[6], + decoded_imm_j[7], decoded_imm_j[3:1], decoded_imm_j[5], decoded_imm_j[0] } <= $signed({mem_rdata_latched[12:2], 1'b0}); + + case (mem_rdata_latched[1:0]) + 2'b00: begin // Quadrant 0 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.ADDI4SPN + is_alu_reg_imm <= |mem_rdata_latched[12:5]; + decoded_rs1 <= 2; + decoded_rd <= 8 + mem_rdata_latched[4:2]; + end + 3'b010: begin // C.LW + is_lb_lh_lw_lbu_lhu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rd <= 8 + mem_rdata_latched[4:2]; + end + 3'b110: begin // C.SW + is_sb_sh_sw <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 8 + mem_rdata_latched[4:2]; + end + endcase + end + 2'b01: begin // Quadrant 1 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.NOP / C.ADDI + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + 3'b001: begin // C.JAL + instr_jal <= 1; + decoded_rd <= 1; + end + 3'b 010: begin // C.LI + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + end + 3'b 011: begin + if (mem_rdata_latched[12] || mem_rdata_latched[6:2]) begin + if (mem_rdata_latched[11:7] == 2) begin // C.ADDI16SP + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + end else begin // C.LUI + instr_lui <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + end + end + end + 3'b100: begin + if (!mem_rdata_latched[11] && !mem_rdata_latched[12]) begin // C.SRLI, C.SRAI + is_alu_reg_imm <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= {mem_rdata_latched[12], mem_rdata_latched[6:2]}; + end + if (mem_rdata_latched[11:10] == 2'b10) begin // C.ANDI + is_alu_reg_imm <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + end + if (mem_rdata_latched[12:10] == 3'b011) begin // C.SUB, C.XOR, C.OR, C.AND + is_alu_reg_reg <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 8 + mem_rdata_latched[4:2]; + end + end + 3'b101: begin // C.J + instr_jal <= 1; + end + 3'b110: begin // C.BEQZ + is_beq_bne_blt_bge_bltu_bgeu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 0; + end + 3'b111: begin // C.BNEZ + is_beq_bne_blt_bge_bltu_bgeu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 0; + end + endcase + end + 2'b10: begin // Quadrant 2 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.SLLI + if (!mem_rdata_latched[12]) begin + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + decoded_rs2 <= {mem_rdata_latched[12], mem_rdata_latched[6:2]}; + end + end + 3'b010: begin // C.LWSP + if (mem_rdata_latched[11:7]) begin + is_lb_lh_lw_lbu_lhu <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 2; + end + end + 3'b100: begin + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JR + instr_jalr <= 1; + decoded_rd <= 0; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] != 0) begin // C.MV + is_alu_reg_reg <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JALR + instr_jalr <= 1; + decoded_rd <= 1; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[6:2] != 0) begin // C.ADD + is_alu_reg_reg <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + end + 3'b110: begin // C.SWSP + is_sb_sh_sw <= 1; + decoded_rs1 <= 2; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + endcase + end + endcase + end + end + + if (decoder_trigger && !decoder_pseudo_trigger) begin + pcpi_insn <= WITH_PCPI ? mem_rdata_q : 'bx; + + instr_beq <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b000; + instr_bne <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b001; + instr_blt <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b100; + instr_bge <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b101; + instr_bltu <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b110; + instr_bgeu <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b111; + + instr_lb <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b000; + instr_lh <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b001; + instr_lw <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b010; + instr_lbu <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b100; + instr_lhu <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b101; + + instr_sb <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b000; + instr_sh <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b001; + instr_sw <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b010; + + instr_addi <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b000; + instr_slti <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b010; + instr_sltiu <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b011; + instr_xori <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b100; + instr_ori <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b110; + instr_andi <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b111; + + instr_slli <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000; + instr_srli <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000; + instr_srai <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000; + + instr_add <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b000 && mem_rdata_q[31:25] == 7'b0000000; + instr_sub <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b000 && mem_rdata_q[31:25] == 7'b0100000; + instr_sll <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000; + instr_slt <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b010 && mem_rdata_q[31:25] == 7'b0000000; + instr_sltu <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b011 && mem_rdata_q[31:25] == 7'b0000000; + instr_xor <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b100 && mem_rdata_q[31:25] == 7'b0000000; + instr_srl <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000; + instr_sra <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000; + instr_or <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b110 && mem_rdata_q[31:25] == 7'b0000000; + instr_and <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b111 && mem_rdata_q[31:25] == 7'b0000000; + + instr_rdcycle <= ((mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000000000000010) || + (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000000100000010)) && ENABLE_COUNTERS; + instr_rdcycleh <= ((mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000000000000010) || + (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000000100000010)) && ENABLE_COUNTERS && ENABLE_COUNTERS64; + instr_rdinstr <= (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000001000000010) && ENABLE_COUNTERS; + instr_rdinstrh <= (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000001000000010) && ENABLE_COUNTERS && ENABLE_COUNTERS64; + + instr_ecall_ebreak <= ((mem_rdata_q[6:0] == 7'b1110011 && !mem_rdata_q[31:21] && !mem_rdata_q[19:7]) || + (COMPRESSED_ISA && mem_rdata_q[15:0] == 16'h9002)); + + instr_getq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000000 && ENABLE_IRQ && ENABLE_IRQ_QREGS; + instr_setq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000001 && ENABLE_IRQ && ENABLE_IRQ_QREGS; + instr_maskirq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000011 && ENABLE_IRQ; + instr_timer <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000101 && ENABLE_IRQ && ENABLE_IRQ_TIMER; + + is_slli_srli_srai <= is_alu_reg_imm && |{ + mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000 + }; + + is_jalr_addi_slti_sltiu_xori_ori_andi <= instr_jalr || is_alu_reg_imm && |{ + mem_rdata_q[14:12] == 3'b000, + mem_rdata_q[14:12] == 3'b010, + mem_rdata_q[14:12] == 3'b011, + mem_rdata_q[14:12] == 3'b100, + mem_rdata_q[14:12] == 3'b110, + mem_rdata_q[14:12] == 3'b111 + }; + + is_sll_srl_sra <= is_alu_reg_reg && |{ + mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000 + }; + + is_lui_auipc_jal_jalr_addi_add_sub <= 0; + is_compare <= 0; + + (* parallel_case *) + case (1'b1) + instr_jal: + decoded_imm <= decoded_imm_j; + |{instr_lui, instr_auipc}: + decoded_imm <= mem_rdata_q[31:12] << 12; + |{instr_jalr, is_lb_lh_lw_lbu_lhu, is_alu_reg_imm}: + decoded_imm <= $signed(mem_rdata_q[31:20]); + is_beq_bne_blt_bge_bltu_bgeu: + decoded_imm <= $signed({mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8], 1'b0}); + is_sb_sh_sw: + decoded_imm <= $signed({mem_rdata_q[31:25], mem_rdata_q[11:7]}); + default: + decoded_imm <= 1'bx; + endcase + end + + if (!resetn) begin + is_beq_bne_blt_bge_bltu_bgeu <= 0; + is_compare <= 0; + + instr_beq <= 0; + instr_bne <= 0; + instr_blt <= 0; + instr_bge <= 0; + instr_bltu <= 0; + instr_bgeu <= 0; + + instr_addi <= 0; + instr_slti <= 0; + instr_sltiu <= 0; + instr_xori <= 0; + instr_ori <= 0; + instr_andi <= 0; + + instr_add <= 0; + instr_sub <= 0; + instr_sll <= 0; + instr_slt <= 0; + instr_sltu <= 0; + instr_xor <= 0; + instr_srl <= 0; + instr_sra <= 0; + instr_or <= 0; + instr_and <= 0; + end + end + + + // Main State Machine + + localparam cpu_state_trap = 8'b10000000; + localparam cpu_state_fetch = 8'b01000000; + localparam cpu_state_ld_rs1 = 8'b00100000; + localparam cpu_state_ld_rs2 = 8'b00010000; + localparam cpu_state_exec = 8'b00001000; + localparam cpu_state_shift = 8'b00000100; + localparam cpu_state_stmem = 8'b00000010; + localparam cpu_state_ldmem = 8'b00000001; + + reg [7:0] cpu_state; + reg [1:0] irq_state; + + `FORMAL_KEEP reg [127:0] dbg_ascii_state; + + always @* begin + dbg_ascii_state = ""; + if (cpu_state == cpu_state_trap) dbg_ascii_state = "trap"; + if (cpu_state == cpu_state_fetch) dbg_ascii_state = "fetch"; + if (cpu_state == cpu_state_ld_rs1) dbg_ascii_state = "ld_rs1"; + if (cpu_state == cpu_state_ld_rs2) dbg_ascii_state = "ld_rs2"; + if (cpu_state == cpu_state_exec) dbg_ascii_state = "exec"; + if (cpu_state == cpu_state_shift) dbg_ascii_state = "shift"; + if (cpu_state == cpu_state_stmem) dbg_ascii_state = "stmem"; + if (cpu_state == cpu_state_ldmem) dbg_ascii_state = "ldmem"; + end + + reg set_mem_do_rinst; + reg set_mem_do_rdata; + reg set_mem_do_wdata; + + reg latched_store; + reg latched_stalu; + reg latched_branch; + reg latched_compr; + reg latched_trace; + reg latched_is_lu; + reg latched_is_lh; + reg latched_is_lb; + reg [regindex_bits-1:0] latched_rd; + + reg [31:0] current_pc; + assign next_pc = latched_store && latched_branch ? reg_out & ~1 : reg_next_pc; + + reg [3:0] pcpi_timeout_counter; + reg pcpi_timeout; + + reg [31:0] next_irq_pending; + reg do_waitirq; + + reg [31:0] alu_out, alu_out_q; + reg alu_out_0, alu_out_0_q; + reg alu_wait, alu_wait_2; + + reg [31:0] alu_add_sub; + reg [31:0] alu_shl, alu_shr; + reg alu_eq, alu_ltu, alu_lts; + + generate if (TWO_CYCLE_ALU) begin + always @(posedge clk) begin + alu_add_sub <= instr_sub ? reg_op1 - reg_op2 : reg_op1 + reg_op2; + alu_eq <= reg_op1 == reg_op2; + alu_lts <= $signed(reg_op1) < $signed(reg_op2); + alu_ltu <= reg_op1 < reg_op2; + alu_shl <= reg_op1 << reg_op2[4:0]; + alu_shr <= $signed({instr_sra || instr_srai ? reg_op1[31] : 1'b0, reg_op1}) >>> reg_op2[4:0]; + end + end else begin + always @* begin + alu_add_sub = instr_sub ? reg_op1 - reg_op2 : reg_op1 + reg_op2; + alu_eq = reg_op1 == reg_op2; + alu_lts = $signed(reg_op1) < $signed(reg_op2); + alu_ltu = reg_op1 < reg_op2; + alu_shl = reg_op1 << reg_op2[4:0]; + alu_shr = $signed({instr_sra || instr_srai ? reg_op1[31] : 1'b0, reg_op1}) >>> reg_op2[4:0]; + end + end endgenerate + + always @* begin + alu_out_0 = 'bx; + (* parallel_case, full_case *) + case (1'b1) + instr_beq: + alu_out_0 = alu_eq; + instr_bne: + alu_out_0 = !alu_eq; + instr_bge: + alu_out_0 = !alu_lts; + instr_bgeu: + alu_out_0 = !alu_ltu; + is_slti_blt_slt && (!TWO_CYCLE_COMPARE || !{instr_beq,instr_bne,instr_bge,instr_bgeu}): + alu_out_0 = alu_lts; + is_sltiu_bltu_sltu && (!TWO_CYCLE_COMPARE || !{instr_beq,instr_bne,instr_bge,instr_bgeu}): + alu_out_0 = alu_ltu; + endcase + + alu_out = 'bx; + (* parallel_case, full_case *) + case (1'b1) + is_lui_auipc_jal_jalr_addi_add_sub: + alu_out = alu_add_sub; + is_compare: + alu_out = alu_out_0; + instr_xori || instr_xor: + alu_out = reg_op1 ^ reg_op2; + instr_ori || instr_or: + alu_out = reg_op1 | reg_op2; + instr_andi || instr_and: + alu_out = reg_op1 & reg_op2; + BARREL_SHIFTER && (instr_sll || instr_slli): + alu_out = alu_shl; + BARREL_SHIFTER && (instr_srl || instr_srli || instr_sra || instr_srai): + alu_out = alu_shr; + endcase + +`ifdef RISCV_FORMAL_BLACKBOX_ALU + alu_out_0 = $anyseq; + alu_out = $anyseq; +`endif + end + + reg clear_prefetched_high_word_q; + always @(posedge clk) clear_prefetched_high_word_q <= clear_prefetched_high_word; + + always @* begin + clear_prefetched_high_word = clear_prefetched_high_word_q; + if (!prefetched_high_word) + clear_prefetched_high_word = 0; + if (latched_branch || irq_state || !resetn) + clear_prefetched_high_word = COMPRESSED_ISA; + end + + reg cpuregs_write; + reg [31:0] cpuregs_wrdata; + reg [31:0] cpuregs_rs1; + reg [31:0] cpuregs_rs2; + reg [regindex_bits-1:0] decoded_rs; + + always @* begin + cpuregs_write = 0; + cpuregs_wrdata = 'bx; + + if (cpu_state == cpu_state_fetch) begin + (* parallel_case *) + case (1'b1) + latched_branch: begin + cpuregs_wrdata = reg_pc + (latched_compr ? 2 : 4); + cpuregs_write = 1; + end + latched_store && !latched_branch: begin + cpuregs_wrdata = latched_stalu ? alu_out_q : reg_out; + cpuregs_write = 1; + end + ENABLE_IRQ && irq_state[0]: begin + cpuregs_wrdata = reg_next_pc | latched_compr; + cpuregs_write = 1; + end + ENABLE_IRQ && irq_state[1]: begin + cpuregs_wrdata = irq_pending & ~irq_mask; + cpuregs_write = 1; + end + endcase + end + end + +`ifndef PICORV32_REGS + always @(posedge clk) begin + if (resetn && cpuregs_write && latched_rd) +`ifdef PICORV32_TESTBUG_001 + cpuregs[latched_rd ^ 1] <= cpuregs_wrdata; +`elsif PICORV32_TESTBUG_002 + cpuregs[latched_rd] <= cpuregs_wrdata ^ 1; +`else + cpuregs[latched_rd] <= cpuregs_wrdata; +`endif + end + + always @* begin + decoded_rs = 'bx; + if (ENABLE_REGS_DUALPORT) begin +`ifndef RISCV_FORMAL_BLACKBOX_REGS + cpuregs_rs1 = decoded_rs1 ? cpuregs[decoded_rs1] : 0; + cpuregs_rs2 = decoded_rs2 ? cpuregs[decoded_rs2] : 0; +`else + cpuregs_rs1 = decoded_rs1 ? $anyseq : 0; + cpuregs_rs2 = decoded_rs2 ? $anyseq : 0; +`endif + end else begin + decoded_rs = (cpu_state == cpu_state_ld_rs2) ? decoded_rs2 : decoded_rs1; +`ifndef RISCV_FORMAL_BLACKBOX_REGS + cpuregs_rs1 = decoded_rs ? cpuregs[decoded_rs] : 0; +`else + cpuregs_rs1 = decoded_rs ? $anyseq : 0; +`endif + cpuregs_rs2 = cpuregs_rs1; + end + end +`else + wire[31:0] cpuregs_rdata1; + wire[31:0] cpuregs_rdata2; + + wire [5:0] cpuregs_waddr = latched_rd; + wire [5:0] cpuregs_raddr1 = ENABLE_REGS_DUALPORT ? decoded_rs1 : decoded_rs; + wire [5:0] cpuregs_raddr2 = ENABLE_REGS_DUALPORT ? decoded_rs2 : 0; + + `PICORV32_REGS cpuregs ( + .clk(clk), + .wen(resetn && cpuregs_write && latched_rd), + .waddr(cpuregs_waddr), + .raddr1(cpuregs_raddr1), + .raddr2(cpuregs_raddr2), + .wdata(cpuregs_wrdata), + .rdata1(cpuregs_rdata1), + .rdata2(cpuregs_rdata2) + ); + + always @* begin + decoded_rs = 'bx; + if (ENABLE_REGS_DUALPORT) begin + cpuregs_rs1 = decoded_rs1 ? cpuregs_rdata1 : 0; + cpuregs_rs2 = decoded_rs2 ? cpuregs_rdata2 : 0; + end else begin + decoded_rs = (cpu_state == cpu_state_ld_rs2) ? decoded_rs2 : decoded_rs1; + cpuregs_rs1 = decoded_rs ? cpuregs_rdata1 : 0; + cpuregs_rs2 = cpuregs_rs1; + end + end +`endif + + assign launch_next_insn = cpu_state == cpu_state_fetch && decoder_trigger && (!ENABLE_IRQ || irq_delay || irq_active || !(irq_pending & ~irq_mask)); + + always @(posedge clk) begin + trap <= 0; + reg_sh <= 'bx; + reg_out <= 'bx; + set_mem_do_rinst = 0; + set_mem_do_rdata = 0; + set_mem_do_wdata = 0; + + alu_out_0_q <= alu_out_0; + alu_out_q <= alu_out; + + alu_wait <= 0; + alu_wait_2 <= 0; + + if (launch_next_insn) begin + dbg_rs1val <= 'bx; + dbg_rs2val <= 'bx; + dbg_rs1val_valid <= 0; + dbg_rs2val_valid <= 0; + end + + if (WITH_PCPI && CATCH_ILLINSN) begin + if (resetn && pcpi_valid && !pcpi_int_wait) begin + if (pcpi_timeout_counter) + pcpi_timeout_counter <= pcpi_timeout_counter - 1; + end else + pcpi_timeout_counter <= ~0; + pcpi_timeout <= !pcpi_timeout_counter; + end + + if (ENABLE_COUNTERS) begin + count_cycle <= resetn ? count_cycle + 1 : 0; + if (!ENABLE_COUNTERS64) count_cycle[63:32] <= 0; + end else begin + count_cycle <= 'bx; + count_instr <= 'bx; + end + + next_irq_pending = ENABLE_IRQ ? irq_pending & LATCHED_IRQ : 'bx; + + if (ENABLE_IRQ && ENABLE_IRQ_TIMER && timer) begin + timer <= timer - 1; + end + + decoder_trigger <= mem_do_rinst && mem_done; + decoder_trigger_q <= decoder_trigger; + decoder_pseudo_trigger <= 0; + decoder_pseudo_trigger_q <= decoder_pseudo_trigger; + do_waitirq <= 0; + + trace_valid <= 0; + + if (!ENABLE_TRACE) + trace_data <= 'bx; + + if (!resetn) begin + reg_pc <= PROGADDR_RESET; + reg_next_pc <= PROGADDR_RESET; + if (ENABLE_COUNTERS) + count_instr <= 0; + latched_store <= 0; + latched_stalu <= 0; + latched_branch <= 0; + latched_trace <= 0; + latched_is_lu <= 0; + latched_is_lh <= 0; + latched_is_lb <= 0; + pcpi_valid <= 0; + pcpi_timeout <= 0; + irq_active <= 0; + irq_delay <= 0; + irq_mask <= ~0; + next_irq_pending = 0; + irq_state <= 0; + eoi <= 0; + timer <= 0; + if (~STACKADDR) begin + latched_store <= 1; + latched_rd <= 2; + reg_out <= STACKADDR; + end + cpu_state <= cpu_state_fetch; + end else + (* parallel_case, full_case *) + case (cpu_state) + cpu_state_trap: begin + trap <= 1; + end + + cpu_state_fetch: begin + mem_do_rinst <= !decoder_trigger && !do_waitirq; + mem_wordsize <= 0; + + current_pc = reg_next_pc; + + (* parallel_case *) + case (1'b1) + latched_branch: begin + current_pc = latched_store ? (latched_stalu ? alu_out_q : reg_out) & ~1 : reg_next_pc; + `debug($display("ST_RD: %2d 0x%08x, BRANCH 0x%08x", latched_rd, reg_pc + (latched_compr ? 2 : 4), current_pc);) + end + latched_store && !latched_branch: begin + `debug($display("ST_RD: %2d 0x%08x", latched_rd, latched_stalu ? alu_out_q : reg_out);) + end + ENABLE_IRQ && irq_state[0]: begin + current_pc = PROGADDR_IRQ; + irq_active <= 1; + mem_do_rinst <= 1; + end + ENABLE_IRQ && irq_state[1]: begin + eoi <= irq_pending & ~irq_mask; + next_irq_pending = next_irq_pending & irq_mask; + end + endcase + + if (ENABLE_TRACE && latched_trace) begin + latched_trace <= 0; + trace_valid <= 1; + if (latched_branch) + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_BRANCH | (current_pc & 32'hfffffffe); + else + trace_data <= (irq_active ? TRACE_IRQ : 0) | (latched_stalu ? alu_out_q : reg_out); + end + + reg_pc <= current_pc; + reg_next_pc <= current_pc; + + latched_store <= 0; + latched_stalu <= 0; + latched_branch <= 0; + latched_is_lu <= 0; + latched_is_lh <= 0; + latched_is_lb <= 0; + latched_rd <= decoded_rd; + latched_compr <= compressed_instr; + + if (ENABLE_IRQ && ((decoder_trigger && !irq_active && !irq_delay && |(irq_pending & ~irq_mask)) || irq_state)) begin + irq_state <= + irq_state == 2'b00 ? 2'b01 : + irq_state == 2'b01 ? 2'b10 : 2'b00; + latched_compr <= latched_compr; + if (ENABLE_IRQ_QREGS) + latched_rd <= irqregs_offset | irq_state[0]; + else + latched_rd <= irq_state[0] ? 4 : 3; + end else + if (ENABLE_IRQ && (decoder_trigger || do_waitirq) && instr_waitirq) begin + if (irq_pending) begin + latched_store <= 1; + reg_out <= irq_pending; + reg_next_pc <= current_pc + (compressed_instr ? 2 : 4); + mem_do_rinst <= 1; + end else + do_waitirq <= 1; + end else + if (decoder_trigger) begin + `debug($display("-- %-0t", $time);) + irq_delay <= irq_active; + reg_next_pc <= current_pc + (compressed_instr ? 2 : 4); + if (ENABLE_TRACE) + latched_trace <= 1; + if (ENABLE_COUNTERS) begin + count_instr <= count_instr + 1; + if (!ENABLE_COUNTERS64) count_instr[63:32] <= 0; + end + if (instr_jal) begin + mem_do_rinst <= 1; + reg_next_pc <= current_pc + decoded_imm_j; + latched_branch <= 1; + end else begin + mem_do_rinst <= 0; + mem_do_prefetch <= !instr_jalr && !instr_retirq; + cpu_state <= cpu_state_ld_rs1; + end + end + end + + cpu_state_ld_rs1: begin + reg_op1 <= 'bx; + reg_op2 <= 'bx; + + (* parallel_case *) + case (1'b1) + (CATCH_ILLINSN || WITH_PCPI) && instr_trap: begin + if (WITH_PCPI) begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + if (ENABLE_REGS_DUALPORT) begin + pcpi_valid <= 1; + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + if (pcpi_int_ready) begin + mem_do_rinst <= 1; + pcpi_valid <= 0; + reg_out <= pcpi_int_rd; + latched_store <= pcpi_int_wr; + cpu_state <= cpu_state_fetch; + end else + if (CATCH_ILLINSN && (pcpi_timeout || instr_ecall_ebreak)) begin + pcpi_valid <= 0; + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end else begin + cpu_state <= cpu_state_ld_rs2; + end + end else begin + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end + ENABLE_COUNTERS && is_rdcycle_rdcycleh_rdinstr_rdinstrh: begin + (* parallel_case, full_case *) + case (1'b1) + instr_rdcycle: + reg_out <= count_cycle[31:0]; + instr_rdcycleh && ENABLE_COUNTERS64: + reg_out <= count_cycle[63:32]; + instr_rdinstr: + reg_out <= count_instr[31:0]; + instr_rdinstrh && ENABLE_COUNTERS64: + reg_out <= count_instr[63:32]; + endcase + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + is_lui_auipc_jal: begin + reg_op1 <= instr_lui ? 0 : reg_pc; + reg_op2 <= decoded_imm; + if (TWO_CYCLE_ALU) + alu_wait <= 1; + else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + ENABLE_IRQ && ENABLE_IRQ_QREGS && instr_getq: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && ENABLE_IRQ_QREGS && instr_setq: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + latched_rd <= latched_rd | irqregs_offset; + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && instr_retirq: begin + eoi <= 0; + irq_active <= 0; + latched_branch <= 1; + latched_store <= 1; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= CATCH_MISALIGN ? (cpuregs_rs1 & 32'h fffffffe) : cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && instr_maskirq: begin + latched_store <= 1; + reg_out <= irq_mask; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + irq_mask <= cpuregs_rs1 | MASKED_IRQ; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && ENABLE_IRQ_TIMER && instr_timer: begin + latched_store <= 1; + reg_out <= timer; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + timer <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + is_lb_lh_lw_lbu_lhu && !instr_trap: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_ldmem; + mem_do_rinst <= 1; + end + is_slli_srli_srai && !BARREL_SHIFTER: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + reg_sh <= decoded_rs2; + cpu_state <= cpu_state_shift; + end + is_jalr_addi_slti_sltiu_xori_ori_andi, is_slli_srli_srai && BARREL_SHIFTER: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + reg_op2 <= is_slli_srli_srai && BARREL_SHIFTER ? decoded_rs2 : decoded_imm; + if (TWO_CYCLE_ALU) + alu_wait <= 1; + else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + default: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + if (ENABLE_REGS_DUALPORT) begin + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + (* parallel_case *) + case (1'b1) + is_sb_sh_sw: begin + cpu_state <= cpu_state_stmem; + mem_do_rinst <= 1; + end + is_sll_srl_sra && !BARREL_SHIFTER: begin + cpu_state <= cpu_state_shift; + end + default: begin + if (TWO_CYCLE_ALU || (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu)) begin + alu_wait_2 <= TWO_CYCLE_ALU && (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu); + alu_wait <= 1; + end else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + endcase + end else + cpu_state <= cpu_state_ld_rs2; + end + endcase + end + + cpu_state_ld_rs2: begin + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + + (* parallel_case *) + case (1'b1) + WITH_PCPI && instr_trap: begin + pcpi_valid <= 1; + if (pcpi_int_ready) begin + mem_do_rinst <= 1; + pcpi_valid <= 0; + reg_out <= pcpi_int_rd; + latched_store <= pcpi_int_wr; + cpu_state <= cpu_state_fetch; + end else + if (CATCH_ILLINSN && (pcpi_timeout || instr_ecall_ebreak)) begin + pcpi_valid <= 0; + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end + is_sb_sh_sw: begin + cpu_state <= cpu_state_stmem; + mem_do_rinst <= 1; + end + is_sll_srl_sra && !BARREL_SHIFTER: begin + cpu_state <= cpu_state_shift; + end + default: begin + if (TWO_CYCLE_ALU || (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu)) begin + alu_wait_2 <= TWO_CYCLE_ALU && (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu); + alu_wait <= 1; + end else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + endcase + end + + cpu_state_exec: begin + reg_out <= reg_pc + decoded_imm; + if ((TWO_CYCLE_ALU || TWO_CYCLE_COMPARE) && (alu_wait || alu_wait_2)) begin + mem_do_rinst <= mem_do_prefetch && !alu_wait_2; + alu_wait <= alu_wait_2; + end else + if (is_beq_bne_blt_bge_bltu_bgeu) begin + latched_rd <= 0; + latched_store <= TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0; + latched_branch <= TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0; + if (mem_done) + cpu_state <= cpu_state_fetch; + if (TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0) begin + decoder_trigger <= 0; + set_mem_do_rinst = 1; + end + end else begin + latched_branch <= instr_jalr; + latched_store <= 1; + latched_stalu <= 1; + cpu_state <= cpu_state_fetch; + end + end + + cpu_state_shift: begin + latched_store <= 1; + if (reg_sh == 0) begin + reg_out <= reg_op1; + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_fetch; + end else if (TWO_STAGE_SHIFT && reg_sh >= 4) begin + (* parallel_case, full_case *) + case (1'b1) + instr_slli || instr_sll: reg_op1 <= reg_op1 << 4; + instr_srli || instr_srl: reg_op1 <= reg_op1 >> 4; + instr_srai || instr_sra: reg_op1 <= $signed(reg_op1) >>> 4; + endcase + reg_sh <= reg_sh - 4; + end else begin + (* parallel_case, full_case *) + case (1'b1) + instr_slli || instr_sll: reg_op1 <= reg_op1 << 1; + instr_srli || instr_srl: reg_op1 <= reg_op1 >> 1; + instr_srai || instr_sra: reg_op1 <= $signed(reg_op1) >>> 1; + endcase + reg_sh <= reg_sh - 1; + end + end + + cpu_state_stmem: begin + if (ENABLE_TRACE) + reg_out <= reg_op2; + if (!mem_do_prefetch || mem_done) begin + if (!mem_do_wdata) begin + (* parallel_case, full_case *) + case (1'b1) + instr_sb: mem_wordsize <= 2; + instr_sh: mem_wordsize <= 1; + instr_sw: mem_wordsize <= 0; + endcase + if (ENABLE_TRACE) begin + trace_valid <= 1; + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_ADDR | ((reg_op1 + decoded_imm) & 32'hffffffff); + end + reg_op1 <= reg_op1 + decoded_imm; + set_mem_do_wdata = 1; + end + if (!mem_do_prefetch && mem_done) begin + cpu_state <= cpu_state_fetch; + decoder_trigger <= 1; + decoder_pseudo_trigger <= 1; + end + end + end + + cpu_state_ldmem: begin + latched_store <= 1; + if (!mem_do_prefetch || mem_done) begin + if (!mem_do_rdata) begin + (* parallel_case, full_case *) + case (1'b1) + instr_lb || instr_lbu: mem_wordsize <= 2; + instr_lh || instr_lhu: mem_wordsize <= 1; + instr_lw: mem_wordsize <= 0; + endcase + latched_is_lu <= is_lbu_lhu_lw; + latched_is_lh <= instr_lh; + latched_is_lb <= instr_lb; + if (ENABLE_TRACE) begin + trace_valid <= 1; + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_ADDR | ((reg_op1 + decoded_imm) & 32'hffffffff); + end + reg_op1 <= reg_op1 + decoded_imm; + set_mem_do_rdata = 1; + end + if (!mem_do_prefetch && mem_done) begin + (* parallel_case, full_case *) + case (1'b1) + latched_is_lu: reg_out <= mem_rdata_word; + latched_is_lh: reg_out <= $signed(mem_rdata_word[15:0]); + latched_is_lb: reg_out <= $signed(mem_rdata_word[7:0]); + endcase + decoder_trigger <= 1; + decoder_pseudo_trigger <= 1; + cpu_state <= cpu_state_fetch; + end + end + end + endcase + + if (ENABLE_IRQ) begin + next_irq_pending = next_irq_pending | irq; + if(ENABLE_IRQ_TIMER && timer) + if (timer - 1 == 0) + next_irq_pending[irq_timer] = 1; + end + + if (CATCH_MISALIGN && resetn && (mem_do_rdata || mem_do_wdata)) begin + if (mem_wordsize == 0 && reg_op1[1:0] != 0) begin + `debug($display("MISALIGNED WORD: 0x%08x", reg_op1);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + if (mem_wordsize == 1 && reg_op1[0] != 0) begin + `debug($display("MISALIGNED HALFWORD: 0x%08x", reg_op1);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + end + if (CATCH_MISALIGN && resetn && mem_do_rinst && (COMPRESSED_ISA ? reg_pc[0] : |reg_pc[1:0])) begin + `debug($display("MISALIGNED INSTRUCTION: 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + if (!CATCH_ILLINSN && decoder_trigger_q && !decoder_pseudo_trigger_q && instr_ecall_ebreak) begin + cpu_state <= cpu_state_trap; + end + + if (!resetn || mem_done) begin + mem_do_prefetch <= 0; + mem_do_rinst <= 0; + mem_do_rdata <= 0; + mem_do_wdata <= 0; + end + + if (set_mem_do_rinst) + mem_do_rinst <= 1; + if (set_mem_do_rdata) + mem_do_rdata <= 1; + if (set_mem_do_wdata) + mem_do_wdata <= 1; + + irq_pending <= next_irq_pending & ~MASKED_IRQ; + + if (!CATCH_MISALIGN) begin + if (COMPRESSED_ISA) begin + reg_pc[0] <= 0; + reg_next_pc[0] <= 0; + end else begin + reg_pc[1:0] <= 0; + reg_next_pc[1:0] <= 0; + end + end + current_pc = 'bx; + end + +`ifdef RISCV_FORMAL + reg dbg_irq_call; + reg dbg_irq_enter; + reg [31:0] dbg_irq_ret; + always @(posedge clk) begin + rvfi_valid <= resetn && (launch_next_insn || trap) && dbg_valid_insn; + rvfi_order <= resetn ? rvfi_order + rvfi_valid : 0; + + rvfi_insn <= dbg_insn_opcode; + rvfi_rs1_addr <= dbg_rs1val_valid ? dbg_insn_rs1 : 0; + rvfi_rs2_addr <= dbg_rs2val_valid ? dbg_insn_rs2 : 0; + rvfi_pc_rdata <= dbg_insn_addr; + rvfi_rs1_rdata <= dbg_rs1val_valid ? dbg_rs1val : 0; + rvfi_rs2_rdata <= dbg_rs2val_valid ? dbg_rs2val : 0; + rvfi_trap <= trap; + rvfi_halt <= trap; + rvfi_intr <= dbg_irq_enter; + rvfi_mode <= 3; + rvfi_ixl <= 1; + + if (!resetn) begin + dbg_irq_call <= 0; + dbg_irq_enter <= 0; + end else + if (rvfi_valid) begin + dbg_irq_call <= 0; + dbg_irq_enter <= dbg_irq_call; + end else + if (irq_state == 1) begin + dbg_irq_call <= 1; + dbg_irq_ret <= next_pc; + end + + if (!resetn) begin + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end else + if (cpuregs_write && !irq_state) begin +`ifdef PICORV32_TESTBUG_003 + rvfi_rd_addr <= latched_rd ^ 1; +`else + rvfi_rd_addr <= latched_rd; +`endif +`ifdef PICORV32_TESTBUG_004 + rvfi_rd_wdata <= latched_rd ? cpuregs_wrdata ^ 1 : 0; +`else + rvfi_rd_wdata <= latched_rd ? cpuregs_wrdata : 0; +`endif + end else + if (rvfi_valid) begin + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end + + casez (dbg_insn_opcode) + 32'b 0000000_?????_000??_???_?????_0001011: begin // getq + rvfi_rs1_addr <= 0; + rvfi_rs1_rdata <= 0; + end + 32'b 0000001_?????_?????_???_000??_0001011: begin // setq + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end + 32'b 0000010_?????_00000_???_00000_0001011: begin // retirq + rvfi_rs1_addr <= 0; + rvfi_rs1_rdata <= 0; + end + endcase + + if (!dbg_irq_call) begin + if (dbg_mem_instr) begin + rvfi_mem_addr <= 0; + rvfi_mem_rmask <= 0; + rvfi_mem_wmask <= 0; + rvfi_mem_rdata <= 0; + rvfi_mem_wdata <= 0; + end else + if (dbg_mem_valid && dbg_mem_ready) begin + rvfi_mem_addr <= dbg_mem_addr; + rvfi_mem_rmask <= dbg_mem_wstrb ? 0 : ~0; + rvfi_mem_wmask <= dbg_mem_wstrb; + rvfi_mem_rdata <= dbg_mem_rdata; + rvfi_mem_wdata <= dbg_mem_wdata; + end + end + end + + always @* begin +`ifdef PICORV32_TESTBUG_005 + rvfi_pc_wdata = (dbg_irq_call ? dbg_irq_ret : dbg_insn_addr) ^ 4; +`else + rvfi_pc_wdata = dbg_irq_call ? dbg_irq_ret : dbg_insn_addr; +`endif + + rvfi_csr_mcycle_rmask = 0; + rvfi_csr_mcycle_wmask = 0; + rvfi_csr_mcycle_rdata = 0; + rvfi_csr_mcycle_wdata = 0; + + rvfi_csr_minstret_rmask = 0; + rvfi_csr_minstret_wmask = 0; + rvfi_csr_minstret_rdata = 0; + rvfi_csr_minstret_wdata = 0; + + if (rvfi_valid && rvfi_insn[6:0] == 7'b 1110011 && rvfi_insn[13:12] == 3'b010) begin + if (rvfi_insn[31:20] == 12'h C00) begin + rvfi_csr_mcycle_rmask = 64'h 0000_0000_FFFF_FFFF; + rvfi_csr_mcycle_rdata = {32'h 0000_0000, rvfi_rd_wdata}; + end + if (rvfi_insn[31:20] == 12'h C80) begin + rvfi_csr_mcycle_rmask = 64'h FFFF_FFFF_0000_0000; + rvfi_csr_mcycle_rdata = {rvfi_rd_wdata, 32'h 0000_0000}; + end + if (rvfi_insn[31:20] == 12'h C02) begin + rvfi_csr_minstret_rmask = 64'h 0000_0000_FFFF_FFFF; + rvfi_csr_minstret_rdata = {32'h 0000_0000, rvfi_rd_wdata}; + end + if (rvfi_insn[31:20] == 12'h C82) begin + rvfi_csr_minstret_rmask = 64'h FFFF_FFFF_0000_0000; + rvfi_csr_minstret_rdata = {rvfi_rd_wdata, 32'h 0000_0000}; + end + end + end +`endif + + // Formal Verification +`ifdef FORMAL + reg [3:0] last_mem_nowait; + always @(posedge clk) + last_mem_nowait <= {last_mem_nowait, mem_ready || !mem_valid}; + + // stall the memory interface for max 4 cycles + restrict property (|last_mem_nowait || mem_ready || !mem_valid); + + // resetn low in first cycle, after that resetn high + restrict property (resetn != $initstate); + + // this just makes it much easier to read traces. uncomment as needed. + // assume property (mem_valid || !mem_ready); + + reg ok; + always @* begin + if (resetn) begin + // instruction fetches are read-only + if (mem_valid && mem_instr) + assert (mem_wstrb == 0); + + // cpu_state must be valid + ok = 0; + if (cpu_state == cpu_state_trap) ok = 1; + if (cpu_state == cpu_state_fetch) ok = 1; + if (cpu_state == cpu_state_ld_rs1) ok = 1; + if (cpu_state == cpu_state_ld_rs2) ok = !ENABLE_REGS_DUALPORT; + if (cpu_state == cpu_state_exec) ok = 1; + if (cpu_state == cpu_state_shift) ok = 1; + if (cpu_state == cpu_state_stmem) ok = 1; + if (cpu_state == cpu_state_ldmem) ok = 1; + assert (ok); + end + end + + reg last_mem_la_read = 0; + reg last_mem_la_write = 0; + reg [31:0] last_mem_la_addr; + reg [31:0] last_mem_la_wdata; + reg [3:0] last_mem_la_wstrb = 0; + + always @(posedge clk) begin + last_mem_la_read <= mem_la_read; + last_mem_la_write <= mem_la_write; + last_mem_la_addr <= mem_la_addr; + last_mem_la_wdata <= mem_la_wdata; + last_mem_la_wstrb <= mem_la_wstrb; + + if (last_mem_la_read) begin + assert(mem_valid); + assert(mem_addr == last_mem_la_addr); + assert(mem_wstrb == 0); + end + if (last_mem_la_write) begin + assert(mem_valid); + assert(mem_addr == last_mem_la_addr); + assert(mem_wdata == last_mem_la_wdata); + assert(mem_wstrb == last_mem_la_wstrb); + end + if (mem_la_read || mem_la_write) begin + assert(!mem_valid || mem_ready); + end + end +`endif +endmodule + +// This is a simple example implementation of PICORV32_REGS. +// Use the PICORV32_REGS mechanism if you want to use custom +// memory resources to implement the processor register file. +// Note that your implementation must match the requirements of +// the PicoRV32 configuration. (e.g. QREGS, etc) +module picorv32_regs ( + input clk, wen, + input [5:0] waddr, + input [5:0] raddr1, + input [5:0] raddr2, + input [31:0] wdata, + output [31:0] rdata1, + output [31:0] rdata2 +); + reg [31:0] regs [0:30]; + + always @(posedge clk) + if (wen) regs[~waddr[4:0]] <= wdata; + + assign rdata1 = regs[~raddr1[4:0]]; + assign rdata2 = regs[~raddr2[4:0]]; +endmodule + + +/*************************************************************** + * picorv32_pcpi_mul + ***************************************************************/ + +module picorv32_pcpi_mul #( + parameter STEPS_AT_ONCE = 1, + parameter CARRY_CHAIN = 4 +) ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output reg pcpi_wr, + output reg [31:0] pcpi_rd, + output reg pcpi_wait, + output reg pcpi_ready +); + reg instr_mul, instr_mulh, instr_mulhsu, instr_mulhu; + wire instr_any_mul = |{instr_mul, instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_any_mulh = |{instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_rs1_signed = |{instr_mulh, instr_mulhsu}; + wire instr_rs2_signed = |{instr_mulh}; + + reg pcpi_wait_q; + wire mul_start = pcpi_wait && !pcpi_wait_q; + + always @(posedge clk) begin + instr_mul <= 0; + instr_mulh <= 0; + instr_mulhsu <= 0; + instr_mulhu <= 0; + + if (resetn && pcpi_valid && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001) begin + case (pcpi_insn[14:12]) + 3'b000: instr_mul <= 1; + 3'b001: instr_mulh <= 1; + 3'b010: instr_mulhsu <= 1; + 3'b011: instr_mulhu <= 1; + endcase + end + + pcpi_wait <= instr_any_mul; + pcpi_wait_q <= pcpi_wait; + end + + reg [63:0] rs1, rs2, rd, rdx; + reg [63:0] next_rs1, next_rs2, this_rs2; + reg [63:0] next_rd, next_rdx, next_rdt; + reg [6:0] mul_counter; + reg mul_waiting; + reg mul_finish; + integer i, j; + + // carry save accumulator + always @* begin + next_rd = rd; + next_rdx = rdx; + next_rs1 = rs1; + next_rs2 = rs2; + + for (i = 0; i < STEPS_AT_ONCE; i=i+1) begin + this_rs2 = next_rs1[0] ? next_rs2 : 0; + if (CARRY_CHAIN == 0) begin + next_rdt = next_rd ^ next_rdx ^ this_rs2; + next_rdx = ((next_rd & next_rdx) | (next_rd & this_rs2) | (next_rdx & this_rs2)) << 1; + next_rd = next_rdt; + end else begin + next_rdt = 0; + for (j = 0; j < 64; j = j + CARRY_CHAIN) + {next_rdt[j+CARRY_CHAIN-1], next_rd[j +: CARRY_CHAIN]} = + next_rd[j +: CARRY_CHAIN] + next_rdx[j +: CARRY_CHAIN] + this_rs2[j +: CARRY_CHAIN]; + next_rdx = next_rdt << 1; + end + next_rs1 = next_rs1 >> 1; + next_rs2 = next_rs2 << 1; + end + end + + always @(posedge clk) begin + mul_finish <= 0; + if (!resetn) begin + mul_waiting <= 1; + end else + if (mul_waiting) begin + if (instr_rs1_signed) + rs1 <= $signed(pcpi_rs1); + else + rs1 <= $unsigned(pcpi_rs1); + + if (instr_rs2_signed) + rs2 <= $signed(pcpi_rs2); + else + rs2 <= $unsigned(pcpi_rs2); + + rd <= 0; + rdx <= 0; + mul_counter <= (instr_any_mulh ? 63 - STEPS_AT_ONCE : 31 - STEPS_AT_ONCE); + mul_waiting <= !mul_start; + end else begin + rd <= next_rd; + rdx <= next_rdx; + rs1 <= next_rs1; + rs2 <= next_rs2; + + mul_counter <= mul_counter - STEPS_AT_ONCE; + if (mul_counter[6]) begin + mul_finish <= 1; + mul_waiting <= 1; + end + end + end + + always @(posedge clk) begin + pcpi_wr <= 0; + pcpi_ready <= 0; + if (mul_finish && resetn) begin + pcpi_wr <= 1; + pcpi_ready <= 1; + pcpi_rd <= instr_any_mulh ? rd >> 32 : rd; + end + end +endmodule + +module picorv32_pcpi_fast_mul #( + parameter EXTRA_MUL_FFS = 0, + parameter EXTRA_INSN_FFS = 0, + parameter MUL_CLKGATE = 0 +) ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output pcpi_wr, + output [31:0] pcpi_rd, + output pcpi_wait, + output pcpi_ready +); + reg instr_mul, instr_mulh, instr_mulhsu, instr_mulhu; + wire instr_any_mul = |{instr_mul, instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_any_mulh = |{instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_rs1_signed = |{instr_mulh, instr_mulhsu}; + wire instr_rs2_signed = |{instr_mulh}; + + reg shift_out; + reg [3:0] active; + reg [32:0] rs1, rs2, rs1_q, rs2_q; + reg [63:0] rd, rd_q; + + wire pcpi_insn_valid = pcpi_valid && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001; + reg pcpi_insn_valid_q; + + always @* begin + instr_mul = 0; + instr_mulh = 0; + instr_mulhsu = 0; + instr_mulhu = 0; + + if (resetn && (EXTRA_INSN_FFS ? pcpi_insn_valid_q : pcpi_insn_valid)) begin + case (pcpi_insn[14:12]) + 3'b000: instr_mul = 1; + 3'b001: instr_mulh = 1; + 3'b010: instr_mulhsu = 1; + 3'b011: instr_mulhu = 1; + endcase + end + end + + always @(posedge clk) begin + pcpi_insn_valid_q <= pcpi_insn_valid; + if (!MUL_CLKGATE || active[0]) begin + rs1_q <= rs1; + rs2_q <= rs2; + end + if (!MUL_CLKGATE || active[1]) begin + rd <= $signed(EXTRA_MUL_FFS ? rs1_q : rs1) * $signed(EXTRA_MUL_FFS ? rs2_q : rs2); + end + if (!MUL_CLKGATE || active[2]) begin + rd_q <= rd; + end + end + + always @(posedge clk) begin + if (instr_any_mul && !(EXTRA_MUL_FFS ? active[3:0] : active[1:0])) begin + if (instr_rs1_signed) + rs1 <= $signed(pcpi_rs1); + else + rs1 <= $unsigned(pcpi_rs1); + + if (instr_rs2_signed) + rs2 <= $signed(pcpi_rs2); + else + rs2 <= $unsigned(pcpi_rs2); + active[0] <= 1; + end else begin + active[0] <= 0; + end + + active[3:1] <= active; + shift_out <= instr_any_mulh; + + if (!resetn) + active <= 0; + end + + assign pcpi_wr = active[EXTRA_MUL_FFS ? 3 : 1]; + assign pcpi_wait = 0; + assign pcpi_ready = active[EXTRA_MUL_FFS ? 3 : 1]; +`ifdef RISCV_FORMAL_ALTOPS + assign pcpi_rd = + instr_mul ? (pcpi_rs1 + pcpi_rs2) ^ 32'h5876063e : + instr_mulh ? (pcpi_rs1 + pcpi_rs2) ^ 32'hf6583fb7 : + instr_mulhsu ? (pcpi_rs1 - pcpi_rs2) ^ 32'hecfbe137 : + instr_mulhu ? (pcpi_rs1 + pcpi_rs2) ^ 32'h949ce5e8 : 1'bx; +`else + assign pcpi_rd = shift_out ? (EXTRA_MUL_FFS ? rd_q : rd) >> 32 : (EXTRA_MUL_FFS ? rd_q : rd); +`endif +endmodule + + +/*************************************************************** + * picorv32_pcpi_div + ***************************************************************/ + +module picorv32_pcpi_div ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output reg pcpi_wr, + output reg [31:0] pcpi_rd, + output reg pcpi_wait, + output reg pcpi_ready +); + reg instr_div, instr_divu, instr_rem, instr_remu; + wire instr_any_div_rem = |{instr_div, instr_divu, instr_rem, instr_remu}; + + reg pcpi_wait_q; + wire start = pcpi_wait && !pcpi_wait_q; + + always @(posedge clk) begin + instr_div <= 0; + instr_divu <= 0; + instr_rem <= 0; + instr_remu <= 0; + + if (resetn && pcpi_valid && !pcpi_ready && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001) begin + case (pcpi_insn[14:12]) + 3'b100: instr_div <= 1; + 3'b101: instr_divu <= 1; + 3'b110: instr_rem <= 1; + 3'b111: instr_remu <= 1; + endcase + end + + pcpi_wait <= instr_any_div_rem && resetn; + pcpi_wait_q <= pcpi_wait && resetn; + end + + reg [31:0] dividend; + reg [62:0] divisor; + reg [31:0] quotient; + reg [31:0] quotient_msk; + reg running; + reg outsign; + + always @(posedge clk) begin + pcpi_ready <= 0; + pcpi_wr <= 0; + pcpi_rd <= 'bx; + + if (!resetn) begin + running <= 0; + end else + if (start) begin + running <= 1; + dividend <= (instr_div || instr_rem) && pcpi_rs1[31] ? -pcpi_rs1 : pcpi_rs1; + divisor <= ((instr_div || instr_rem) && pcpi_rs2[31] ? -pcpi_rs2 : pcpi_rs2) << 31; + outsign <= (instr_div && (pcpi_rs1[31] != pcpi_rs2[31]) && |pcpi_rs2) || (instr_rem && pcpi_rs1[31]); + quotient <= 0; + quotient_msk <= 1 << 31; + end else + if (!quotient_msk && running) begin + running <= 0; + pcpi_ready <= 1; + pcpi_wr <= 1; +`ifdef RISCV_FORMAL_ALTOPS + case (1) + instr_div: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h7f8529ec; + instr_divu: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h10e8fd70; + instr_rem: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h8da68fa5; + instr_remu: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h3138d0e1; + endcase +`else + if (instr_div || instr_divu) + pcpi_rd <= outsign ? -quotient : quotient; + else + pcpi_rd <= outsign ? -dividend : dividend; +`endif + end else begin + if (divisor <= dividend) begin + dividend <= dividend - divisor; + quotient <= quotient | quotient_msk; + end + divisor <= divisor >> 1; +`ifdef RISCV_FORMAL_ALTOPS + quotient_msk <= quotient_msk >> 5; +`else + quotient_msk <= quotient_msk >> 1; +`endif + end + end +endmodule + + +/*************************************************************** + * picorv32_axi + ***************************************************************/ + +module picorv32_axi #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + input clk, resetn, + output trap, + + // AXI4-lite master memory interface + + output mem_axi_awvalid, + input mem_axi_awready, + output [31:0] mem_axi_awaddr, + output [ 2:0] mem_axi_awprot, + + output mem_axi_wvalid, + input mem_axi_wready, + output [31:0] mem_axi_wdata, + output [ 3:0] mem_axi_wstrb, + + input mem_axi_bvalid, + output mem_axi_bready, + + output mem_axi_arvalid, + input mem_axi_arready, + output [31:0] mem_axi_araddr, + output [ 2:0] mem_axi_arprot, + + input mem_axi_rvalid, + output mem_axi_rready, + input [31:0] mem_axi_rdata, + + // Pico Co-Processor Interface (PCPI) + output pcpi_valid, + output [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ interface + input [31:0] irq, + output [31:0] eoi, + +`ifdef RISCV_FORMAL + output rvfi_valid, + output [63:0] rvfi_order, + output [31:0] rvfi_insn, + output rvfi_trap, + output rvfi_halt, + output rvfi_intr, + output [ 4:0] rvfi_rs1_addr, + output [ 4:0] rvfi_rs2_addr, + output [31:0] rvfi_rs1_rdata, + output [31:0] rvfi_rs2_rdata, + output [ 4:0] rvfi_rd_addr, + output [31:0] rvfi_rd_wdata, + output [31:0] rvfi_pc_rdata, + output [31:0] rvfi_pc_wdata, + output [31:0] rvfi_mem_addr, + output [ 3:0] rvfi_mem_rmask, + output [ 3:0] rvfi_mem_wmask, + output [31:0] rvfi_mem_rdata, + output [31:0] rvfi_mem_wdata, +`endif + + // Trace Interface + output trace_valid, + output [35:0] trace_data +); + wire mem_valid; + wire [31:0] mem_addr; + wire [31:0] mem_wdata; + wire [ 3:0] mem_wstrb; + wire mem_instr; + wire mem_ready; + wire [31:0] mem_rdata; + + picorv32_axi_adapter axi_adapter ( + .clk (clk ), + .resetn (resetn ), + .mem_axi_awvalid(mem_axi_awvalid), + .mem_axi_awready(mem_axi_awready), + .mem_axi_awaddr (mem_axi_awaddr ), + .mem_axi_awprot (mem_axi_awprot ), + .mem_axi_wvalid (mem_axi_wvalid ), + .mem_axi_wready (mem_axi_wready ), + .mem_axi_wdata (mem_axi_wdata ), + .mem_axi_wstrb (mem_axi_wstrb ), + .mem_axi_bvalid (mem_axi_bvalid ), + .mem_axi_bready (mem_axi_bready ), + .mem_axi_arvalid(mem_axi_arvalid), + .mem_axi_arready(mem_axi_arready), + .mem_axi_araddr (mem_axi_araddr ), + .mem_axi_arprot (mem_axi_arprot ), + .mem_axi_rvalid (mem_axi_rvalid ), + .mem_axi_rready (mem_axi_rready ), + .mem_axi_rdata (mem_axi_rdata ), + .mem_valid (mem_valid ), + .mem_instr (mem_instr ), + .mem_ready (mem_ready ), + .mem_addr (mem_addr ), + .mem_wdata (mem_wdata ), + .mem_wstrb (mem_wstrb ), + .mem_rdata (mem_rdata ) + ); + + picorv32 #( + .ENABLE_COUNTERS (ENABLE_COUNTERS ), + .ENABLE_COUNTERS64 (ENABLE_COUNTERS64 ), + .ENABLE_REGS_16_31 (ENABLE_REGS_16_31 ), + .ENABLE_REGS_DUALPORT(ENABLE_REGS_DUALPORT), + .TWO_STAGE_SHIFT (TWO_STAGE_SHIFT ), + .BARREL_SHIFTER (BARREL_SHIFTER ), + .TWO_CYCLE_COMPARE (TWO_CYCLE_COMPARE ), + .TWO_CYCLE_ALU (TWO_CYCLE_ALU ), + .COMPRESSED_ISA (COMPRESSED_ISA ), + .CATCH_MISALIGN (CATCH_MISALIGN ), + .CATCH_ILLINSN (CATCH_ILLINSN ), + .ENABLE_PCPI (ENABLE_PCPI ), + .ENABLE_MUL (ENABLE_MUL ), + .ENABLE_FAST_MUL (ENABLE_FAST_MUL ), + .ENABLE_DIV (ENABLE_DIV ), + .ENABLE_IRQ (ENABLE_IRQ ), + .ENABLE_IRQ_QREGS (ENABLE_IRQ_QREGS ), + .ENABLE_IRQ_TIMER (ENABLE_IRQ_TIMER ), + .ENABLE_TRACE (ENABLE_TRACE ), + .REGS_INIT_ZERO (REGS_INIT_ZERO ), + .MASKED_IRQ (MASKED_IRQ ), + .LATCHED_IRQ (LATCHED_IRQ ), + .PROGADDR_RESET (PROGADDR_RESET ), + .PROGADDR_IRQ (PROGADDR_IRQ ), + .STACKADDR (STACKADDR ) + ) picorv32_core ( + .clk (clk ), + .resetn (resetn), + .trap (trap ), + + .mem_valid(mem_valid), + .mem_addr (mem_addr ), + .mem_wdata(mem_wdata), + .mem_wstrb(mem_wstrb), + .mem_instr(mem_instr), + .mem_ready(mem_ready), + .mem_rdata(mem_rdata), + + .pcpi_valid(pcpi_valid), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_wr ), + .pcpi_rd (pcpi_rd ), + .pcpi_wait (pcpi_wait ), + .pcpi_ready(pcpi_ready), + + .irq(irq), + .eoi(eoi), + +`ifdef RISCV_FORMAL + .rvfi_valid (rvfi_valid ), + .rvfi_order (rvfi_order ), + .rvfi_insn (rvfi_insn ), + .rvfi_trap (rvfi_trap ), + .rvfi_halt (rvfi_halt ), + .rvfi_intr (rvfi_intr ), + .rvfi_rs1_addr (rvfi_rs1_addr ), + .rvfi_rs2_addr (rvfi_rs2_addr ), + .rvfi_rs1_rdata(rvfi_rs1_rdata), + .rvfi_rs2_rdata(rvfi_rs2_rdata), + .rvfi_rd_addr (rvfi_rd_addr ), + .rvfi_rd_wdata (rvfi_rd_wdata ), + .rvfi_pc_rdata (rvfi_pc_rdata ), + .rvfi_pc_wdata (rvfi_pc_wdata ), + .rvfi_mem_addr (rvfi_mem_addr ), + .rvfi_mem_rmask(rvfi_mem_rmask), + .rvfi_mem_wmask(rvfi_mem_wmask), + .rvfi_mem_rdata(rvfi_mem_rdata), + .rvfi_mem_wdata(rvfi_mem_wdata), +`endif + + .trace_valid(trace_valid), + .trace_data (trace_data) + ); +endmodule + + +/*************************************************************** + * picorv32_axi_adapter + ***************************************************************/ + +module picorv32_axi_adapter ( + input clk, resetn, + + // AXI4-lite master memory interface + + output mem_axi_awvalid, + input mem_axi_awready, + output [31:0] mem_axi_awaddr, + output [ 2:0] mem_axi_awprot, + + output mem_axi_wvalid, + input mem_axi_wready, + output [31:0] mem_axi_wdata, + output [ 3:0] mem_axi_wstrb, + + input mem_axi_bvalid, + output mem_axi_bready, + + output mem_axi_arvalid, + input mem_axi_arready, + output [31:0] mem_axi_araddr, + output [ 2:0] mem_axi_arprot, + + input mem_axi_rvalid, + output mem_axi_rready, + input [31:0] mem_axi_rdata, + + // Native PicoRV32 memory interface + + input mem_valid, + input mem_instr, + output mem_ready, + input [31:0] mem_addr, + input [31:0] mem_wdata, + input [ 3:0] mem_wstrb, + output [31:0] mem_rdata +); + reg ack_awvalid; + reg ack_arvalid; + reg ack_wvalid; + reg xfer_done; + + assign mem_axi_awvalid = mem_valid && |mem_wstrb && !ack_awvalid; + assign mem_axi_awaddr = mem_addr; + assign mem_axi_awprot = 0; + + assign mem_axi_arvalid = mem_valid && !mem_wstrb && !ack_arvalid; + assign mem_axi_araddr = mem_addr; + assign mem_axi_arprot = mem_instr ? 3'b100 : 3'b000; + + assign mem_axi_wvalid = mem_valid && |mem_wstrb && !ack_wvalid; + assign mem_axi_wdata = mem_wdata; + assign mem_axi_wstrb = mem_wstrb; + + assign mem_ready = mem_axi_bvalid || mem_axi_rvalid; + assign mem_axi_bready = mem_valid && |mem_wstrb; + assign mem_axi_rready = mem_valid && !mem_wstrb; + assign mem_rdata = mem_axi_rdata; + + always @(posedge clk) begin + if (!resetn) begin + ack_awvalid <= 0; + end else begin + xfer_done <= mem_valid && mem_ready; + if (mem_axi_awready && mem_axi_awvalid) + ack_awvalid <= 1; + if (mem_axi_arready && mem_axi_arvalid) + ack_arvalid <= 1; + if (mem_axi_wready && mem_axi_wvalid) + ack_wvalid <= 1; + if (xfer_done || !mem_valid) begin + ack_awvalid <= 0; + ack_arvalid <= 0; + ack_wvalid <= 0; + end + end + end +endmodule + + +/*************************************************************** + * picorv32_wb + ***************************************************************/ + +module picorv32_wb #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + output trap, + + // Wishbone interfaces + input wb_rst_i, + input wb_clk_i, + + output reg [31:0] wbm_adr_o, + output reg [31:0] wbm_dat_o, + input [31:0] wbm_dat_i, + output reg wbm_we_o, + output reg [3:0] wbm_sel_o, + output reg wbm_stb_o, + input wbm_ack_i, + output reg wbm_cyc_o, + + // Pico Co-Processor Interface (PCPI) + output pcpi_valid, + output [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ interface + input [31:0] irq, + output [31:0] eoi, + +`ifdef RISCV_FORMAL + output rvfi_valid, + output [63:0] rvfi_order, + output [31:0] rvfi_insn, + output rvfi_trap, + output rvfi_halt, + output rvfi_intr, + output [ 4:0] rvfi_rs1_addr, + output [ 4:0] rvfi_rs2_addr, + output [31:0] rvfi_rs1_rdata, + output [31:0] rvfi_rs2_rdata, + output [ 4:0] rvfi_rd_addr, + output [31:0] rvfi_rd_wdata, + output [31:0] rvfi_pc_rdata, + output [31:0] rvfi_pc_wdata, + output [31:0] rvfi_mem_addr, + output [ 3:0] rvfi_mem_rmask, + output [ 3:0] rvfi_mem_wmask, + output [31:0] rvfi_mem_rdata, + output [31:0] rvfi_mem_wdata, +`endif + + // Trace Interface + output trace_valid, + output [35:0] trace_data, + + output mem_instr +); + wire mem_valid; + wire [31:0] mem_addr; + wire [31:0] mem_wdata; + wire [ 3:0] mem_wstrb; + reg mem_ready; + reg [31:0] mem_rdata; + + wire clk; + wire resetn; + + assign clk = wb_clk_i; + assign resetn = ~wb_rst_i; + + picorv32 #( + .ENABLE_COUNTERS (ENABLE_COUNTERS ), + .ENABLE_COUNTERS64 (ENABLE_COUNTERS64 ), + .ENABLE_REGS_16_31 (ENABLE_REGS_16_31 ), + .ENABLE_REGS_DUALPORT(ENABLE_REGS_DUALPORT), + .TWO_STAGE_SHIFT (TWO_STAGE_SHIFT ), + .BARREL_SHIFTER (BARREL_SHIFTER ), + .TWO_CYCLE_COMPARE (TWO_CYCLE_COMPARE ), + .TWO_CYCLE_ALU (TWO_CYCLE_ALU ), + .COMPRESSED_ISA (COMPRESSED_ISA ), + .CATCH_MISALIGN (CATCH_MISALIGN ), + .CATCH_ILLINSN (CATCH_ILLINSN ), + .ENABLE_PCPI (ENABLE_PCPI ), + .ENABLE_MUL (ENABLE_MUL ), + .ENABLE_FAST_MUL (ENABLE_FAST_MUL ), + .ENABLE_DIV (ENABLE_DIV ), + .ENABLE_IRQ (ENABLE_IRQ ), + .ENABLE_IRQ_QREGS (ENABLE_IRQ_QREGS ), + .ENABLE_IRQ_TIMER (ENABLE_IRQ_TIMER ), + .ENABLE_TRACE (ENABLE_TRACE ), + .REGS_INIT_ZERO (REGS_INIT_ZERO ), + .MASKED_IRQ (MASKED_IRQ ), + .LATCHED_IRQ (LATCHED_IRQ ), + .PROGADDR_RESET (PROGADDR_RESET ), + .PROGADDR_IRQ (PROGADDR_IRQ ), + .STACKADDR (STACKADDR ) + ) picorv32_core ( + .clk (clk ), + .resetn (resetn), + .trap (trap ), + + .mem_valid(mem_valid), + .mem_addr (mem_addr ), + .mem_wdata(mem_wdata), + .mem_wstrb(mem_wstrb), + .mem_instr(mem_instr), + .mem_ready(mem_ready), + .mem_rdata(mem_rdata), + + .pcpi_valid(pcpi_valid), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_wr ), + .pcpi_rd (pcpi_rd ), + .pcpi_wait (pcpi_wait ), + .pcpi_ready(pcpi_ready), + + .irq(irq), + .eoi(eoi), + +`ifdef RISCV_FORMAL + .rvfi_valid (rvfi_valid ), + .rvfi_order (rvfi_order ), + .rvfi_insn (rvfi_insn ), + .rvfi_trap (rvfi_trap ), + .rvfi_halt (rvfi_halt ), + .rvfi_intr (rvfi_intr ), + .rvfi_rs1_addr (rvfi_rs1_addr ), + .rvfi_rs2_addr (rvfi_rs2_addr ), + .rvfi_rs1_rdata(rvfi_rs1_rdata), + .rvfi_rs2_rdata(rvfi_rs2_rdata), + .rvfi_rd_addr (rvfi_rd_addr ), + .rvfi_rd_wdata (rvfi_rd_wdata ), + .rvfi_pc_rdata (rvfi_pc_rdata ), + .rvfi_pc_wdata (rvfi_pc_wdata ), + .rvfi_mem_addr (rvfi_mem_addr ), + .rvfi_mem_rmask(rvfi_mem_rmask), + .rvfi_mem_wmask(rvfi_mem_wmask), + .rvfi_mem_rdata(rvfi_mem_rdata), + .rvfi_mem_wdata(rvfi_mem_wdata), +`endif + + .trace_valid(trace_valid), + .trace_data (trace_data) + ); + + localparam IDLE = 2'b00; + localparam WBSTART = 2'b01; + localparam WBEND = 2'b10; + + reg [1:0] state; + + wire we; + assign we = (mem_wstrb[0] | mem_wstrb[1] | mem_wstrb[2] | mem_wstrb[3]); + + always @(posedge wb_clk_i) begin + if (wb_rst_i) begin + wbm_adr_o <= 0; + wbm_dat_o <= 0; + wbm_we_o <= 0; + wbm_sel_o <= 0; + wbm_stb_o <= 0; + wbm_cyc_o <= 0; + state <= IDLE; + end else begin + case (state) + IDLE: begin + if (mem_valid) begin + wbm_adr_o <= mem_addr; + wbm_dat_o <= mem_wdata; + wbm_we_o <= we; + wbm_sel_o <= mem_wstrb; + + wbm_stb_o <= 1'b1; + wbm_cyc_o <= 1'b1; + state <= WBSTART; + end else begin + mem_ready <= 1'b0; + + wbm_stb_o <= 1'b0; + wbm_cyc_o <= 1'b0; + wbm_we_o <= 1'b0; + end + end + WBSTART:begin + if (wbm_ack_i) begin + mem_rdata <= wbm_dat_i; + mem_ready <= 1'b1; + + state <= WBEND; + + wbm_stb_o <= 1'b0; + wbm_cyc_o <= 1'b0; + wbm_we_o <= 1'b0; + end + end + WBEND: begin + mem_ready <= 1'b0; + + state <= IDLE; + end + default: + state <= IDLE; + endcase + end + end +endmodule diff --git a/tests/functional/picorv32_tb.v b/tests/functional/picorv32_tb.v new file mode 100644 index 00000000000..1404c2d8c27 --- /dev/null +++ b/tests/functional/picorv32_tb.v @@ -0,0 +1,62 @@ +module gold( + input wire clk, + output reg resetn, + output wire trap, + output wire mem_valid, + output wire mem_instr, + output wire mem_ready, + output wire [31:0] mem_addr, + output wire [31:0] mem_wdata, + output wire [31:0] mem_wstrb, + output wire [31:0] mem_rdata, +); + + initial resetn = 1'b0; + always @(posedge clk) resetn <= 1'b1; + + reg [31:0] rom[0:15]; + + initial begin + rom[0] = 32'h00200093; + rom[1] = 32'h00200113; + rom[2] = 32'h00111863; + rom[3] = 32'h00102023; + rom[4] = 32'h00108093; + rom[5] = 32'hfe0008e3; + rom[6] = 32'h00008193; + rom[7] = 32'h402181b3; + rom[8] = 32'hfe304ee3; + rom[9] = 32'hfe0186e3; + rom[10] = 32'h00110113; + rom[11] = 32'hfc000ee3; + end + + assign mem_ready = 1'b1; + assign mem_rdata = rom[mem_addr[5:2]]; + + wire pcpi_wr = 1'b0; + wire [31:0] pcpi_rd = 32'b0; + wire pcpi_wait = 1'b0; + wire pcpi_ready = 1'b0; + + wire [31:0] irq = 32'b0; + + picorv32 picorv32_i( + .clk(clk), + .resetn(resetn), + .trap(trap), + .mem_valid(mem_valid), + .mem_instr(mem_instr), + .mem_ready(mem_ready), + .mem_addr(mem_addr), + .mem_wdata(mem_wdata), + .mem_wstrb(mem_wstrb), + .mem_rdata(mem_rdata), + .pcpi_wr(pcpi_wr), + .pcpi_rd(pcpi_rd), + .pcpi_wait(pcpi_wait), + .pcpi_ready(pcpi_ready), + .irq(irq) + ); + +endmodule diff --git a/tests/functional/rkt_vcd.py b/tests/functional/rkt_vcd.py new file mode 100644 index 00000000000..1b2cf31e384 --- /dev/null +++ b/tests/functional/rkt_vcd.py @@ -0,0 +1,115 @@ +from __future__ import annotations +from pathlib import Path +import re +import subprocess +from typing import Dict, List, Tuple +from random import Random + +vcd_version = "Yosys/tests/functional/rkt_vcd.py" +StepList = List[Tuple[int, str]] +SignalStepMap = Dict[str, StepList] +SignalWidthMap = Dict[str, int] + +def write_vcd(filename: Path, signals: SignalStepMap, timescale='1 ns', date='today'): + with open(filename, 'w') as f: + # Write the header + f.write(f"$date\n {date}\n$end\n") + f.write(f"$timescale {timescale} $end\n") + + # Declare signals + f.write("$scope module gold $end\n") + for signal_name, changes in signals.items(): + signal_size = len(changes[0][1]) + f.write(f"$var wire {signal_size - 1} {signal_name} {signal_name} $end\n") + f.write("$upscope $end\n") + f.write("$enddefinitions $end\n") + + # Collect all unique timestamps + timestamps = sorted(set(time for changes in signals.values() for time, _ in changes)) + + # Write initial values + f.write("#0\n") + for signal_name, changes in signals.items(): + for time, value in changes: + if time == 0: + f.write(f"{value} {signal_name}\n") + + # Write value changes + for time in timestamps: + if time != 0: + f.write(f"#{time}\n") + for signal_name, changes in signals.items(): + for change_time, value in changes: + if change_time == time: + f.write(f"{value} {signal_name}\n") + +def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: Random): + signals: dict[str, list[str]] = {} + inputs: SignalWidthMap = {} + outputs: SignalWidthMap = {} + + current_struct_name: str = "" + with open(rkt_file_path, 'r') as rkt_file: + for line in rkt_file: + m = re.search(r'gold_(Inputs|Outputs|State)', line) + if m: + current_struct_name = m.group(1) + if current_struct_name == "State": break + elif not current_struct_name: continue # skip lines before structs + m = re.search(r'; (.+?)\b \(bitvector (\d+)\)', line) + if not m: continue # skip non matching lines (probably closing the struct) + signal = m.group(1) + width = int(m.group(2)) + if current_struct_name == "Inputs": + inputs[signal] = width + elif current_struct_name == "Outputs": + outputs[signal] = width + + for signal, width in inputs.items(): + step_list: list[int] = [] + for step in range(num_steps): + value = rnd.getrandbits(width) + binary_string = format(value, '0{}b'.format(width)) + step_list.append(binary_string) + signals[signal] = step_list + + test_rkt_file_path = rkt_file_path.with_suffix('.tst.rkt') + with open(test_rkt_file_path, 'w') as test_rkt_file: + test_rkt_file.writelines([ + '#lang rosette\n', + f'(require "{rkt_file_path.name}")\n', + ]) + + for step in range(num_steps): + this_step = f"step_{step}" + value_list: list[str] = [] + for signal, width in inputs.items(): + value = signals[signal][step] + value_list.append(f"(bv #b{value} {width})") + gold_Inputs = f"(gold_Inputs {' '.join(value_list)})" + gold_State = f"(cdr step_{step-1})" if step else "gold_initial" + test_rkt_file.write(f"(define {this_step} (gold {gold_Inputs} {gold_State})) (car {this_step})\n") + + cmd = ["racket", test_rkt_file_path] + status = subprocess.run(cmd, capture_output=True) + assert status.returncode == 0, f"{cmd[0]} failed" + + for signal in outputs.keys(): + signals[signal] = [] + + for line in status.stdout.decode().splitlines(): + m = re.match(r'\(gold_Outputs( \(bv \S+ \d+\))+\)', line) + assert m, f"Incomplete output definition {line!r}" + for output, (value, width) in zip(outputs.keys(), re.findall(r'\(bv (\S+) (\d+)\)', line)): + assert isinstance(value, str), f"Bad value {value!r}" + assert value.startswith(('#b', '#x')), f"Non-binary value {value!r}" + assert int(width) == outputs[output], f"Width mismatch for output {output!r} (got {width}, expected {outputs[output]})" + int_value = int(value[2:], 16 if value.startswith('#x') else 2) + binary_string = format(int_value, '0{}b'.format(width)) + signals[output].append(binary_string) + + vcd_signals: SignalStepMap = {} + for signal, steps in signals.items(): + vcd_signals[signal] = [(time, f"b{value}") for time, value in enumerate(steps)] + + write_vcd(vcd_path, vcd_signals) diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py new file mode 100644 index 00000000000..964d81ddf50 --- /dev/null +++ b/tests/functional/rtlil_cells.py @@ -0,0 +1,381 @@ +from itertools import chain +import random + +def write_rtlil_cell(f, cell_type, inputs, outputs, parameters): + f.write('autoidx 1\n') + f.write('module \\gold\n') + idx = 1 + for name, width in inputs.items(): + f.write(f'\twire width {width} input {idx} \\{name}\n') + idx += 1 + for name, width in outputs.items(): + f.write(f'\twire width {width} output {idx} \\{name}\n') + idx += 1 + f.write(f'\tcell ${cell_type} \\UUT\n') + for (name, value) in parameters.items(): + if value >= 2**32: + f.write(f'\t\tparameter \\{name} {value.bit_length()}\'{value:b}\n') + else: + f.write(f'\t\tparameter \\{name} {value}\n') + for name in chain(inputs.keys(), outputs.keys()): + f.write(f'\t\tconnect \\{name} \\{name}\n') + f.write(f'\tend\nend\n') + +class BaseCell: + def __init__(self, name, parameters, inputs, outputs, test_values): + self.name = name + self.parameters = parameters + self.inputs = inputs + self.outputs = outputs + self.test_values = test_values + def get_port_width(self, port, parameters): + def parse_specifier(spec): + if isinstance(spec, int): + return spec + if isinstance(spec, str): + return parameters[spec] + if callable(spec): + return spec(parameters) + assert False, "expected int, str or lambda" + if port in self.inputs: + return parse_specifier(self.inputs[port]) + elif port in self.outputs: + return parse_specifier(self.outputs[port]) + else: + assert False, "expected input or output" + def generate_tests(self, rnd): + def print_parameter(v): + if isinstance(v, bool): + return "S" if v else "U" + else: + return str(v) + for values in self.test_values: + if isinstance(values, int): + values = [values] + name = '-'.join([print_parameter(v) for v in values]) + parameters = {parameter: int(values[i]) for i, parameter in enumerate(self.parameters)} + if self.is_test_valid(values): + yield (name, parameters) + def write_rtlil_file(self, path, parameters): + inputs = {port: self.get_port_width(port, parameters) for port in self.inputs} + outputs = {port: self.get_port_width(port, parameters) for port in self.outputs} + with open(path, 'w') as f: + write_rtlil_cell(f, self.name, inputs, outputs, parameters) + def is_test_valid(self, values): + return True + +class UnaryCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['A_WIDTH', 'Y_WIDTH', 'A_SIGNED'], {'A': 'A_WIDTH'}, {'Y': 'Y_WIDTH'}, values) + +class BinaryCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': 'Y_WIDTH'}, values) + +class ShiftCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': 'Y_WIDTH'}, values) + def is_test_valid(self, values): + (a_width, b_width, y_width, a_signed, b_signed) = values + if not self.name in ('shift', 'shiftx') and b_signed: return False + if self.name == 'shiftx' and a_signed: return False + return True + +class MuxCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['WIDTH'], {'A': 'WIDTH', 'B': 'WIDTH', 'S': 1}, {'Y': 'WIDTH'}, values) + +class BWCell(BaseCell): + def __init__(self, name, values): + inputs = {'A': 'WIDTH', 'B': 'WIDTH'} + if name == "bwmux": inputs['S'] = 'WIDTH' + super().__init__(name, ['WIDTH'], inputs, {'Y': 'WIDTH'}, values) + +class PMuxCell(BaseCell): + def __init__(self, name, values): + b_width = lambda par: par['WIDTH'] * par['S_WIDTH'] + super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': 'WIDTH', 'B': b_width, 'S': 'S_WIDTH'}, {'Y': 'WIDTH'}, values) + +class BMuxCell(BaseCell): + def __init__(self, name, values): + a_width = lambda par: par['WIDTH'] << par['S_WIDTH'] + super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': a_width, 'S': 'S_WIDTH'}, {'Y': 'WIDTH'}, values) + +class DemuxCell(BaseCell): + def __init__(self, name, values): + y_width = lambda par: par['WIDTH'] << par['S_WIDTH'] + super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': 'WIDTH', 'S': 'S_WIDTH'}, {'Y': y_width}, values) + +class LUTCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['WIDTH', 'LUT'], {'A': 'WIDTH'}, {'Y': 1}, values) + def generate_tests(self, rnd): + for width in self.test_values: + lut = rnd(f'lut-{width}').getrandbits(2**width) + yield (f'{width}', {'WIDTH' : width, 'LUT' : lut}) + +class ConcatCell(BaseCell): + def __init__(self, name, values): + y_width = lambda par: par['A_WIDTH'] + par['B_WIDTH'] + super().__init__(name, ['A_WIDTH', 'B_WIDTH'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': y_width}, values) + +class SliceCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['A_WIDTH', 'OFFSET', 'Y_WIDTH'], {'A': 'A_WIDTH'}, {'Y': 'Y_WIDTH'}, values) + +class FACell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['WIDTH'], {'A': 'WIDTH', 'B': 'WIDTH', 'C': 'WIDTH'}, {'X': 'WIDTH', 'Y': 'WIDTH'}, values) + self.sim_preprocessing = "techmap" # because FA is not implemented in yosys sim + +class LCUCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['WIDTH'], {'P': 'WIDTH', 'G': 'WIDTH', 'CI': 1}, {'CO': 'WIDTH'}, values) + self.sim_preprocessing = "techmap" # because LCU is not implemented in yosys sim + +class ALUCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH', 'CI': 1, 'BI': 1}, {'X': 'Y_WIDTH', 'Y': 'Y_WIDTH', 'CO': 'Y_WIDTH'}, values) + self.sim_preprocessing = "techmap" # because ALU is not implemented in yosys sim + +class FailCell(BaseCell): + def __init__(self, name): + super().__init__(name, [], {}, {}) + def generate_tests(self, rnd): + yield ('', {}) + def write_rtlil_file(self, path, parameters): + raise Exception(f'\'{self.name}\' cell unimplemented in test generator') + +class FFCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['WIDTH'], ['D'], ['Q'], values) + def write_rtlil_file(self, path, parameters): + from test_functional import yosys_synth + verilog_file = path.parent / 'verilog.v' + with open(verilog_file, 'w') as f: + width = parameters['WIDTH'] + f.write(f""" +module gold( + input wire clk, + input wire [{width-1}:0] D, + output reg [{width-1}:0] Q +); + initial Q = {width}'b{("101" * width)[:width]}; + always @(posedge clk) + Q <= D; +endmodule""") + yosys_synth(verilog_file, path) + +class MemCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['DATA_WIDTH', 'ADDR_WIDTH'], {'WA': 'ADDR_WIDTH', 'RA': 'ADDR_WIDTH', 'WD': 'DATA_WIDTH'}, {'RD': 'DATA_WIDTH'}, values) + def write_rtlil_file(self, path, parameters): + from test_functional import yosys_synth + verilog_file = path.parent / 'verilog.v' + with open(verilog_file, 'w') as f: + f.write(""" +module gold( + input wire clk, + input wire [{1}:0] WA, + input wire [{0}:0] WD, + input wire [{1}:0] RA, + output reg [{0}:0] RD +); + reg [{0}:0] mem[0:{2}]; + integer i; + initial + for(i = 0; i <= {2}; i = i + 1) + mem[i] = 9192 * (i + 1); + always @(*) + RD = mem[RA]; + always @(posedge clk) + mem[WA] <= WD; +endmodule""".format(parameters['DATA_WIDTH'] - 1, parameters['ADDR_WIDTH'] - 1, 2**parameters['ADDR_WIDTH'] - 1)) + yosys_synth(verilog_file, path) + +class MemDualCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['DATA_WIDTH', 'ADDR_WIDTH'], + {'WA1': 'ADDR_WIDTH', 'WA2': 'ADDR_WIDTH', + 'RA1': 'ADDR_WIDTH', 'RA2': 'ADDR_WIDTH', + 'WD1': 'DATA_WIDTH', 'WD2': 'DATA_WIDTH'}, + {'RD1': 'DATA_WIDTH', 'RD2': 'DATA_WIDTH'}, values) + self.sim_preprocessing = "memory_map" # issue #4496 in yosys -sim prevents this example from working without memory_map + def write_rtlil_file(self, path, parameters): + from test_functional import yosys_synth + verilog_file = path.parent / 'verilog.v' + with open(verilog_file, 'w') as f: + f.write(""" +module gold( + input wire clk, + input wire [{1}:0] WA1, + input wire [{0}:0] WD1, + input wire [{1}:0] WA2, + input wire [{0}:0] WD2, + input wire [{1}:0] RA1, + input wire [{1}:0] RA2, + output reg [{0}:0] RD1, + output reg [{0}:0] RD2 +); + reg [{0}:0] mem[0:{2}]; + integer i; + initial + for(i = 0; i <= {2}; i = i + 1) + mem[i] = 9192 * (i + 1); + always @(*) + RD1 = mem[RA1]; + always @(*) + RD2 = mem[RA2]; + always @(posedge clk) begin + mem[WA1] <= WD1; + mem[WA2] <= WD2; + end +endmodule""".format(parameters['DATA_WIDTH'] - 1, parameters['ADDR_WIDTH'] - 1, 2**parameters['ADDR_WIDTH'] - 1)) + yosys_synth(verilog_file, path) + +class PicorvCell(BaseCell): + def __init__(self): + super().__init__("picorv", [], {}, {}, [()]) + self.smt_max_steps = 50 # z3 is too slow for more steps + def write_rtlil_file(self, path, parameters): + from test_functional import yosys, base_path, quote + tb_file = base_path / 'tests/functional/picorv32_tb.v' + cpu_file = base_path / 'tests/functional/picorv32.v' + yosys(f"read_verilog {quote(tb_file)} {quote(cpu_file)}; prep -top gold; flatten; write_rtlil {quote(path)}") + +binary_widths = [ + # try to cover extending A operand, extending B operand, extending/truncating result + (16, 32, 48, True, True), + (16, 32, 48, False, False), + (32, 16, 48, True, True), + (32, 16, 48, False, False), + (32, 32, 16, True, True), + (32, 32, 16, False, False), + # have at least one test that checks small inputs, which will exercise the cornercases more + (4, 4, 8, True, True), + (4, 4, 8, False, False) +] + +unary_widths = [ + (6, 12, True), + (6, 12, False), + (32, 16, True), + (32, 16, False) +] + +# note that meaningless combinations of signednesses are eliminated, +# like e.g. most shift operations don't take signed shift amounts +shift_widths = [ + # one set of tests that definitely checks all possible shift amounts + # with a bigger result width to make sure it's not truncated + (32, 6, 64, True, False), + (32, 6, 64, False, False), + (32, 6, 64, True, True), + (32, 6, 64, False, True), + # one set that checks very oversized shifts + (32, 32, 64, True, False), + (32, 32, 64, False, False), + (32, 32, 64, True, True), + (32, 32, 64, False, True), + # at least one test where the result is going to be truncated + (32, 6, 16, False, False), + # since 1-bit shifts are special cased + (1, 4, 1, False, False), + (1, 4, 1, True, False), +] + +rtlil_cells = [ + UnaryCell("not", unary_widths), + UnaryCell("pos", unary_widths), + UnaryCell("neg", unary_widths), + BinaryCell("and", binary_widths), + BinaryCell("or", binary_widths), + BinaryCell("xor", binary_widths), + BinaryCell("xnor", binary_widths), + UnaryCell("reduce_and", unary_widths), + UnaryCell("reduce_or", unary_widths), + UnaryCell("reduce_xor", unary_widths), + UnaryCell("reduce_xnor", unary_widths), + UnaryCell("reduce_bool", unary_widths), + ShiftCell("shl", shift_widths), + ShiftCell("shr", shift_widths), + ShiftCell("sshl", shift_widths), + ShiftCell("sshr", shift_widths), + ShiftCell("shift", shift_widths), + ShiftCell("shiftx", shift_widths), + FACell("fa", [8, 20]), + LCUCell("lcu", [1, 10]), + ALUCell("alu", binary_widths), + BinaryCell("lt", binary_widths), + BinaryCell("le", binary_widths), + BinaryCell("eq", binary_widths), + BinaryCell("ne", binary_widths), + BinaryCell("eqx", binary_widths), + BinaryCell("nex", binary_widths), + BinaryCell("ge", binary_widths), + BinaryCell("gt", binary_widths), + BinaryCell("add", binary_widths), + BinaryCell("sub", binary_widths), + BinaryCell("mul", binary_widths), +# BinaryCell("macc"), + BinaryCell("div", binary_widths), + BinaryCell("mod", binary_widths), + BinaryCell("divfloor", binary_widths), + BinaryCell("modfloor", binary_widths), + BinaryCell("pow", binary_widths), + UnaryCell("logic_not", unary_widths), + BinaryCell("logic_and", binary_widths), + BinaryCell("logic_or", binary_widths), + SliceCell("slice", [(32, 10, 15), (8, 0, 4), (10, 0, 10)]), + ConcatCell("concat", [(16, 16), (8, 14), (20, 10)]), + MuxCell("mux", [10, 16, 40]), + BMuxCell("bmux", [(10, 1), (10, 2), (10, 4)]), + PMuxCell("pmux", [(10, 1), (10, 4), (20, 4)]), + DemuxCell("demux", [(10, 1), (32, 2), (16, 4)]), + LUTCell("lut", [4, 6, 8]), +# ("sop", ["A", "Y"]), +# ("tribuf", ["A", "EN", "Y"]), +# ("specify2", ["EN", "SRC", "DST"]), +# ("specify3", ["EN", "SRC", "DST", "DAT"]), +# ("specrule", ["EN_SRC", "EN_DST", "SRC", "DST"]), + BWCell("bweqx", [10, 16, 40]), + BWCell("bwmux", [10, 16, 40]), + FFCell("ff", [10, 20, 40]), + MemCell("mem", [(16, 4)]), + MemDualCell("mem-dual", [(16, 4)]), +# ("assert", ["A", "EN"]), +# ("assume", ["A", "EN"]), +# ("live", ["A", "EN"]), +# ("fair", ["A", "EN"]), +# ("cover", ["A", "EN"]), +# ("initstate", ["Y"]), +# ("anyconst", ["Y"]), +# ("anyseq", ["Y"]), +# ("anyinit", ["D", "Q"]), +# ("allconst", ["Y"]), +# ("allseq", ["Y"]), +# ("equiv", ["A", "B", "Y"]), +# ("print", ["EN", "TRG", "ARGS"]), +# ("check", ["A", "EN", "TRG", "ARGS"]), +# ("set_tag", ["A", "SET", "CLR", "Y"]), +# ("get_tag", ["A", "Y"]), +# ("overwrite_tag", ["A", "SET", "CLR"]), +# ("original_tag", ["A", "Y"]), +# ("future_ff", ["A", "Y"]), +# ("scopeinfo", []), + PicorvCell() +] + +def generate_test_cases(per_cell, rnd): + tests = [] + names = [] + for cell in rtlil_cells: + seen_names = set() + for (name, parameters) in cell.generate_tests(rnd): + if not name in seen_names: + seen_names.add(name) + tests.append((cell, parameters)) + names.append(f'{cell.name}-{name}' if name != '' else cell.name) + if per_cell is not None and len(seen_names) >= per_cell: + break + return (names, tests) \ No newline at end of file diff --git a/tests/functional/run-test.sh b/tests/functional/run-test.sh new file mode 100755 index 00000000000..9f70462eed9 --- /dev/null +++ b/tests/functional/run-test.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +pytest -v -m "not smt and not rkt" "$@" diff --git a/tests/functional/smt_vcd.py b/tests/functional/smt_vcd.py new file mode 100644 index 00000000000..c23be440e3b --- /dev/null +++ b/tests/functional/smt_vcd.py @@ -0,0 +1,188 @@ +import sys +import argparse +import os +import smtio +import re + +class SExprParserError(Exception): + pass + +class SExprParser: + def __init__(self): + self.peekbuf = None + self.stack = [[]] + self.atom_pattern = re.compile(r'[a-zA-Z0-9~!@$%^&*_\-+=<>.?/#]+') + def parse_line(self, line): + ptr = 0 + while ptr < len(line): + if line[ptr].isspace(): + ptr += 1 + elif line[ptr] == ';': + break + elif line[ptr] == '(': + ptr += 1 + self.stack.append([]) + elif line[ptr] == ')': + ptr += 1 + assert len(self.stack) > 1, "too many closed parentheses" + v = self.stack.pop() + self.stack[-1].append(v) + else: + match = self.atom_pattern.match(line, ptr) + if match is None: + raise SExprParserError(f"invalid character '{line[ptr]}' in line '{line}'") + start, ptr = match.span() + self.stack[-1].append(line[start:ptr]) + def finish(self): + assert len(self.stack) == 1, "too many open parentheses" + def retrieve(self): + rv, self.stack[0] = self.stack[0], [] + return rv + +def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io, num_steps, rnd): + inputs = {} + outputs = {} + states = {} + + def handle_datatype(lst): + print(lst) + datatype_name = lst[1] + declarations = lst[2][0][1:] # Skip the first item (e.g., 'mk_inputs') + if datatype_name.endswith("_Inputs"): + for declaration in declarations: + input_name = declaration[0] + bitvec_size = declaration[1][2] + assert input_name.startswith("gold_Inputs_") + inputs[input_name[len("gold_Inputs_"):]] = int(bitvec_size) + elif datatype_name.endswith("_Outputs"): + for declaration in declarations: + output_name = declaration[0] + bitvec_size = declaration[1][2] + assert output_name.startswith("gold_Outputs_") + outputs[output_name[len("gold_Outputs_"):]] = int(bitvec_size) + elif datatype_name.endswith("_State"): + for declaration in declarations: + state_name = declaration[0] + assert state_name.startswith("gold_State_") + if declaration[1][0] == "_": + states[state_name[len("gold_State_"):]] = int(declaration[1][2]) + else: + states[state_name[len("gold_State_"):]] = (declaration[1][1][2], declaration[1][2][2]) + + parser = SExprParser() + with open(smt_file_path, 'r') as smt_file: + for line in smt_file: + parser.parse_line(line) + for expr in parser.retrieve(): + smt_io.write(smt_io.unparse(expr)) + if expr[0] == "declare-datatype": + handle_datatype(expr) + + parser.finish() + assert smt_io.check_sat() == 'sat' + + def set_step(inputs, step): + # This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4} + # and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input. + + mk_inputs_parts = [] + for input_name, width in inputs.items(): + value = rnd.getrandbits(width) # Generate a random number up to the maximum value for the bit size + binary_string = format(value, '0{}b'.format(width)) # Convert value to binary with leading zeros + mk_inputs_parts.append(f"#b{binary_string}") + + mk_inputs_call = "gold_Inputs " + " ".join(mk_inputs_parts) + return [ + f"(define-const test_inputs_step_n{step} gold_Inputs ({mk_inputs_call}))\n", + f"(define-const test_results_step_n{step} (Pair gold_Outputs gold_State) (gold test_inputs_step_n{step} test_state_step_n{step}))\n", + f"(define-const test_outputs_step_n{step} gold_Outputs (first test_results_step_n{step}))\n", + f"(define-const test_state_step_n{step+1} gold_State (second test_results_step_n{step}))\n", + ] + + smt_commands = [f"(define-const test_state_step_n0 gold_State gold-initial)\n"] + for step in range(num_steps): + for step_command in set_step(inputs, step): + smt_commands.append(step_command) + + for command in smt_commands: + smt_io.write(command) + + assert smt_io.check_sat() == 'sat' + + # Store signal values + signals = {name: [] for name in list(inputs.keys()) + list(outputs.keys())} + # Retrieve and print values for each state + def hex_to_bin(value): + if value.startswith('x'): + hex_value = value[1:] # Remove the 'x' prefix + bin_value = bin(int(hex_value, 16))[2:] # Convert to binary and remove the '0b' prefix + return f'b{bin_value.zfill(len(hex_value) * 4)}' # Add 'b' prefix and pad with zeros + return value + + combined_assertions = [] + for step in range(num_steps): + print(f"Values for step {step + 1}:") + for input_name, width in inputs.items(): + value = smt_io.get(f'(gold_Inputs_{input_name} test_inputs_step_n{step})') + value = hex_to_bin(value[1:]) + print(f" {input_name}: {value}") + signals[input_name].append((step, value)) + for output_name, width in outputs.items(): + value = smt_io.get(f'(gold_Outputs_{output_name} test_outputs_step_n{step})') + value = hex_to_bin(value[1:]) + print(f" {output_name}: {value}") + signals[output_name].append((step, value)) + combined_assertions.append(f'(= (gold_Outputs_{output_name} test_outputs_step_n{step}) #{value})') + # Create a single assertion covering all timesteps + combined_condition = " ".join(combined_assertions) + smt_io.write(f'(assert (not (and {combined_condition})))') + + # Check the combined assertion + assert smt_io.check_sat(["unsat"]) == "unsat" + + def write_vcd(filename, signals, timescale='1 ns', date='today'): + with open(filename, 'w') as f: + # Write the header + f.write(f"$date\n {date}\n$end\n") + f.write(f"$timescale {timescale} $end\n") + + # Declare signals + f.write("$scope module gold $end\n") + for signal_name, changes in signals.items(): + signal_size = len(changes[0][1]) + f.write(f"$var wire {signal_size - 1} {signal_name} {signal_name} $end\n") + f.write("$upscope $end\n") + f.write("$enddefinitions $end\n") + + # Collect all unique timestamps + timestamps = sorted(set(time for changes in signals.values() for time, _ in changes)) + + # Write initial values + f.write("#0\n") + for signal_name, changes in signals.items(): + for time, value in changes: + if time == 0: + f.write(f"{value} {signal_name}\n") + + # Write value changes + for time in timestamps: + if time != 0: + f.write(f"#{time}\n") + for signal_name, changes in signals.items(): + for change_time, value in changes: + if change_time == time: + f.write(f"{value} {signal_name}\n") + + + write_vcd(vcd_path, signals) + +def simulate_smt(smt_file_path, vcd_path, num_steps, rnd): + so = smtio.SmtOpts() + so.solver = "z3" + so.logic = "ABV" + so.debug_print = True + smt_io = smtio.SmtIo(opts=so) + try: + simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io, num_steps, rnd) + finally: + smt_io.p_close() \ No newline at end of file diff --git a/tests/functional/smtio.py b/tests/functional/smtio.py new file mode 100644 index 00000000000..e32f43c60a0 --- /dev/null +++ b/tests/functional/smtio.py @@ -0,0 +1,1331 @@ +# +# yosys -- Yosys Open SYnthesis Suite +# +# Copyright (C) 2012 Claire Xenia Wolf +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +import sys, re, os, signal, json +import subprocess +if os.name == "posix": + import resource +from copy import copy +from select import select +from time import time +from queue import Queue, Empty +from threading import Thread + + +# This is needed so that the recursive SMT2 S-expression parser +# does not run out of stack frames when parsing large expressions +if os.name == "posix": + smtio_reclimit = 64 * 1024 + if sys.getrecursionlimit() < smtio_reclimit: + sys.setrecursionlimit(smtio_reclimit) + + current_rlimit_stack = resource.getrlimit(resource.RLIMIT_STACK) + if current_rlimit_stack[0] != resource.RLIM_INFINITY: + smtio_stacksize = 128 * 1024 * 1024 + if os.uname().sysname == "Darwin": + # MacOS has rather conservative stack limits + smtio_stacksize = 8 * 1024 * 1024 + if current_rlimit_stack[1] != resource.RLIM_INFINITY: + smtio_stacksize = min(smtio_stacksize, current_rlimit_stack[1]) + if current_rlimit_stack[0] < smtio_stacksize: + try: + resource.setrlimit(resource.RLIMIT_STACK, (smtio_stacksize, current_rlimit_stack[1])) + except ValueError: + # couldn't get more stack, just run with what we have + pass + + +# currently running solvers (so we can kill them) +running_solvers = dict() +forced_shutdown = False +solvers_index = 0 + +def force_shutdown(signum, frame): + global forced_shutdown + if not forced_shutdown: + forced_shutdown = True + if signum is not None: + print("<%s>" % signal.Signals(signum).name) + for p in running_solvers.values(): + # os.killpg(os.getpgid(p.pid), signal.SIGTERM) + os.kill(p.pid, signal.SIGTERM) + sys.exit(1) + +if os.name == "posix": + signal.signal(signal.SIGHUP, force_shutdown) +signal.signal(signal.SIGINT, force_shutdown) +signal.signal(signal.SIGTERM, force_shutdown) + +def except_hook(exctype, value, traceback): + if not forced_shutdown: + sys.__excepthook__(exctype, value, traceback) + force_shutdown(None, None) + +sys.excepthook = except_hook + + +def recursion_helper(iteration, *request): + stack = [iteration(*request)] + + while stack: + top = stack.pop() + try: + request = next(top) + except StopIteration: + continue + + stack.append(top) + stack.append(iteration(*request)) + + +hex_dict = { + "0": "0000", "1": "0001", "2": "0010", "3": "0011", + "4": "0100", "5": "0101", "6": "0110", "7": "0111", + "8": "1000", "9": "1001", "A": "1010", "B": "1011", + "C": "1100", "D": "1101", "E": "1110", "F": "1111", + "a": "1010", "b": "1011", "c": "1100", "d": "1101", + "e": "1110", "f": "1111" +} + + +class SmtModInfo: + def __init__(self): + self.inputs = set() + self.outputs = set() + self.registers = set() + self.memories = dict() + self.wires = set() + self.wsize = dict() + self.clocks = dict() + self.cells = dict() + self.asserts = dict() + self.assumes = dict() + self.covers = dict() + self.maximize = set() + self.minimize = set() + self.anyconsts = dict() + self.anyseqs = dict() + self.allconsts = dict() + self.allseqs = dict() + self.asize = dict() + self.witness = [] + + +class SmtIo: + def __init__(self, opts=None): + global solvers_index + + self.logic = None + self.logic_qf = True + self.logic_ax = True + self.logic_uf = True + self.logic_bv = True + self.logic_dt = False + self.forall = False + self.timeout = 0 + self.produce_models = True + self.recheck = False + self.smt2cache = [list()] + self.smt2_options = dict() + self.smt2_assumptions = dict() + self.p = None + self.p_index = solvers_index + solvers_index += 1 + + if opts is not None: + self.logic = opts.logic + self.solver = opts.solver + self.solver_opts = opts.solver_opts + self.debug_print = opts.debug_print + self.debug_file = opts.debug_file + self.dummy_file = opts.dummy_file + self.timeinfo = opts.timeinfo + self.timeout = opts.timeout + self.unroll = opts.unroll + self.noincr = opts.noincr + self.info_stmts = opts.info_stmts + self.nocomments = opts.nocomments + + else: + self.solver = "yices" + self.solver_opts = list() + self.debug_print = False + self.debug_file = None + self.dummy_file = None + self.timeinfo = os.name != "nt" + self.timeout = 0 + self.unroll = False + self.noincr = False + self.info_stmts = list() + self.nocomments = False + + self.start_time = time() + + self.modinfo = dict() + self.curmod = None + self.topmod = None + self.setup_done = False + + def __del__(self): + if self.p is not None and not forced_shutdown: + os.killpg(os.getpgid(self.p.pid), signal.SIGTERM) + if running_solvers is not None: + del running_solvers[self.p_index] + + def setup(self): + assert not self.setup_done + + if self.forall: + self.unroll = False + + if self.solver == "yices": + if self.forall: + self.noincr = True + + if self.noincr: + self.popen_vargs = ['yices-smt2'] + self.solver_opts + else: + self.popen_vargs = ['yices-smt2', '--incremental'] + self.solver_opts + if self.timeout != 0: + self.popen_vargs.append('-t') + self.popen_vargs.append('%d' % self.timeout); + + if self.solver == "z3": + self.popen_vargs = ['z3', '-smt2', '-in'] + self.solver_opts + if self.timeout != 0: + self.popen_vargs.append('-T:%d' % self.timeout); + + if self.solver in ["cvc4", "cvc5"]: + self.recheck = True + if self.noincr: + self.popen_vargs = [self.solver, '--lang', 'smt2.6' if self.logic_dt else 'smt2'] + self.solver_opts + else: + self.popen_vargs = [self.solver, '--incremental', '--lang', 'smt2.6' if self.logic_dt else 'smt2'] + self.solver_opts + if self.timeout != 0: + self.popen_vargs.append('--tlimit=%d000' % self.timeout); + + if self.solver == "mathsat": + self.popen_vargs = ['mathsat'] + self.solver_opts + if self.timeout != 0: + print('timeout option is not supported for mathsat.') + sys.exit(1) + + if self.solver in ["boolector", "bitwuzla"]: + if self.noincr: + self.popen_vargs = [self.solver, '--smt2'] + self.solver_opts + else: + self.popen_vargs = [self.solver, '--smt2', '-i'] + self.solver_opts + self.unroll = True + if self.timeout != 0: + print('timeout option is not supported for %s.' % self.solver) + sys.exit(1) + + if self.solver == "abc": + if len(self.solver_opts) > 0: + self.popen_vargs = ['yosys-abc', '-S', '; '.join(self.solver_opts)] + else: + self.popen_vargs = ['yosys-abc', '-S', '%blast; &sweep -C 5000; &syn4; &cec -s -m -C 2000'] + self.logic_ax = False + self.unroll = True + self.noincr = True + if self.timeout != 0: + print('timeout option is not supported for abc.') + sys.exit(1) + + if self.solver == "dummy": + assert self.dummy_file is not None + self.dummy_fd = open(self.dummy_file, "r") + else: + if self.dummy_file is not None: + self.dummy_fd = open(self.dummy_file, "w") + if not self.noincr: + self.p_open() + + if self.unroll: + assert not self.forall + self.logic_uf = False + self.unroll_idcnt = 0 + self.unroll_buffer = "" + self.unroll_level = 0 + self.unroll_sorts = set() + self.unroll_objs = set() + self.unroll_decls = dict() + self.unroll_cache = dict() + self.unroll_stack = list() + + if self.logic is None: + self.logic = "" + if self.logic_qf: self.logic += "QF_" + if self.logic_ax: self.logic += "A" + if self.logic_uf: self.logic += "UF" + if self.logic_bv: self.logic += "BV" + if self.logic_dt: self.logic = "ALL" + if self.solver == "yices" and self.forall: self.logic = "BV" + + self.setup_done = True + + for stmt in self.info_stmts: + self.write(stmt) + + if self.produce_models: + self.write("(set-option :produce-models true)") + + #See the SMT-LIB Standard, Section 4.1.7 + modestart_options = [":global-declarations", ":interactive-mode", ":produce-assertions", ":produce-assignments", ":produce-models", ":produce-proofs", ":produce-unsat-assumptions", ":produce-unsat-cores", ":random-seed"] + for key, val in self.smt2_options.items(): + if key in modestart_options: + self.write("(set-option {} {})".format(key, val)) + + self.write("(set-logic %s)" % self.logic) + + if self.forall and self.solver == "yices": + self.write("(set-option :yices-ef-max-iters 1000000000)") + + for key, val in self.smt2_options.items(): + if key not in modestart_options: + self.write("(set-option {} {})".format(key, val)) + + def timestamp(self): + secs = int(time() - self.start_time) + return "## %3d:%02d:%02d " % (secs // (60*60), (secs // 60) % 60, secs % 60) + + def replace_in_stmt(self, stmt, pat, repl): + if stmt == pat: + return repl + + if isinstance(stmt, list): + return [self.replace_in_stmt(s, pat, repl) for s in stmt] + + return stmt + + def unroll_stmt(self, stmt): + result = [] + recursion_helper(self._unroll_stmt_into, stmt, result) + return result.pop() + + def _unroll_stmt_into(self, stmt, output, depth=128): + if not isinstance(stmt, list): + output.append(stmt) + return + + new_stmt = [] + for s in stmt: + if depth: + yield from self._unroll_stmt_into(s, new_stmt, depth - 1) + else: + yield s, new_stmt + stmt = new_stmt + + if len(stmt) >= 2 and not isinstance(stmt[0], list) and stmt[0] in self.unroll_decls: + assert stmt[1] in self.unroll_objs + + key = tuple(stmt) + if key not in self.unroll_cache: + decl = copy(self.unroll_decls[key[0]]) + + self.unroll_cache[key] = "|UNROLL#%d|" % self.unroll_idcnt + decl[1] = self.unroll_cache[key] + self.unroll_idcnt += 1 + + if decl[0] == "declare-fun": + if isinstance(decl[3], list) or decl[3] not in self.unroll_sorts: + self.unroll_objs.add(decl[1]) + decl[2] = list() + else: + self.unroll_objs.add(decl[1]) + decl = list() + + elif decl[0] == "define-fun": + arg_index = 1 + for arg_name, arg_sort in decl[2]: + decl[4] = self.replace_in_stmt(decl[4], arg_name, key[arg_index]) + arg_index += 1 + decl[2] = list() + + if len(decl) > 0: + tmp = [] + if depth: + yield from self._unroll_stmt_into(decl, tmp, depth - 1) + else: + yield decl, tmp + + decl = tmp.pop() + self.write(self.unparse(decl), unroll=False) + + output.append(self.unroll_cache[key]) + return + + output.append(stmt) + + def p_thread_main(self): + while True: + data = self.p.stdout.readline().decode("utf-8") + if data == "": break + self.p_queue.put(data) + self.p_queue.put("") + self.p_running = False + + def p_open(self): + assert self.p is None + try: + self.p = subprocess.Popen(self.popen_vargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except FileNotFoundError: + print("%s SMT Solver '%s' not found in path." % (self.timestamp(), self.popen_vargs[0]), flush=True) + sys.exit(1) + running_solvers[self.p_index] = self.p + self.p_running = True + self.p_next = None + self.p_queue = Queue() + self.p_thread = Thread(target=self.p_thread_main) + self.p_thread.start() + + def p_write(self, data, flush): + assert self.p is not None + self.p.stdin.write(bytes(data, "utf-8")) + if flush: self.p.stdin.flush() + + def p_read(self): + assert self.p is not None + if self.p_next is not None: + data = self.p_next + self.p_next = None + return data + if not self.p_running: + return "" + return self.p_queue.get() + + def p_poll(self, timeout=0.1): + assert self.p is not None + assert self.p_running + if self.p_next is not None: + return False + try: + self.p_next = self.p_queue.get(True, timeout) + return False + except Empty: + return True + + def p_close(self): + assert self.p is not None + self.p.stdin.close() + self.p_thread.join() + assert not self.p_running + del running_solvers[self.p_index] + self.p = None + self.p_next = None + self.p_queue = None + self.p_thread = None + + def write(self, stmt, unroll=True): + if stmt.startswith(";"): + self.info(stmt) + if not self.setup_done: + self.info_stmts.append(stmt) + return + elif not self.setup_done: + self.setup() + + stmt = stmt.strip() + + if self.nocomments or self.unroll: + stmt = re.sub(r" *;.*", "", stmt) + if stmt == "": return + + recheck = None + + if self.solver != "dummy": + if self.noincr: + # Don't close the solver yet, if we're just unrolling definitions + # required for a (get-...) statement + if self.p is not None and not stmt.startswith("(get-") and unroll: + self.p_close() + + if unroll and self.unroll: + s = re.sub(r"\|[^|]*\|", "", stmt) + self.unroll_level += s.count("(") - s.count(")") + if self.unroll_level > 0: + self.unroll_buffer += stmt + self.unroll_buffer += " " + return + else: + stmt = self.unroll_buffer + stmt + self.unroll_buffer = "" + + s = self.parse(stmt) + + if self.recheck and s and s[0].startswith("get-"): + recheck = self.unroll_idcnt + + if self.debug_print: + print("-> %s" % s) + + if len(s) == 3 and s[0] == "declare-sort" and s[2] == "0": + self.unroll_sorts.add(s[1]) + return + + elif len(s) == 4 and s[0] == "declare-fun" and s[2] == [] and s[3] in self.unroll_sorts: + self.unroll_objs.add(s[1]) + return + + elif len(s) >= 4 and s[0] == "declare-fun": + for arg_sort in s[2]: + if arg_sort in self.unroll_sorts: + self.unroll_decls[s[1]] = s + return + + elif len(s) >= 4 and s[0] == "define-fun": + for arg_name, arg_sort in s[2]: + if arg_sort in self.unroll_sorts: + self.unroll_decls[s[1]] = s + return + + stmt = self.unparse(self.unroll_stmt(s)) + + if recheck is not None and recheck != self.unroll_idcnt: + self.check_sat(["sat"]) + + if stmt == "(push 1)": + self.unroll_stack.append(( + copy(self.unroll_sorts), + copy(self.unroll_objs), + copy(self.unroll_decls), + copy(self.unroll_cache), + )) + + if stmt == "(pop 1)": + self.unroll_sorts, self.unroll_objs, self.unroll_decls, self.unroll_cache = self.unroll_stack.pop() + + if self.debug_print: + print("> %s" % stmt) + + if self.debug_file: + print(stmt, file=self.debug_file) + self.debug_file.flush() + + if self.solver != "dummy": + if self.noincr: + if stmt == "(push 1)": + self.smt2cache.append(list()) + elif stmt == "(pop 1)": + self.smt2cache.pop() + else: + if self.p is not None: + self.p_write(stmt + "\n", True) + self.smt2cache[-1].append(stmt) + else: + self.p_write(stmt + "\n", True) + + def info(self, stmt): + if not stmt.startswith("; yosys-smt2-"): + return + + fields = stmt.split() + + if fields[1] == "yosys-smt2-solver-option": + self.smt2_options[fields[2]] = fields[3] + + if fields[1] == "yosys-smt2-nomem": + if self.logic is None: + self.logic_ax = False + + if fields[1] == "yosys-smt2-nobv": + if self.logic is None: + self.logic_bv = False + + if fields[1] == "yosys-smt2-stdt": + if self.logic is None: + self.logic_dt = True + + if fields[1] == "yosys-smt2-forall": + if self.logic is None: + self.logic_qf = False + self.forall = True + + if fields[1] == "yosys-smt2-module": + self.curmod = fields[2] + self.modinfo[self.curmod] = SmtModInfo() + + if fields[1] == "yosys-smt2-cell": + self.modinfo[self.curmod].cells[fields[3]] = fields[2] + + if fields[1] == "yosys-smt2-topmod": + self.topmod = fields[2] + + if fields[1] == "yosys-smt2-input": + self.modinfo[self.curmod].inputs.add(fields[2]) + self.modinfo[self.curmod].wsize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-output": + self.modinfo[self.curmod].outputs.add(fields[2]) + self.modinfo[self.curmod].wsize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-register": + self.modinfo[self.curmod].registers.add(fields[2]) + self.modinfo[self.curmod].wsize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-memory": + self.modinfo[self.curmod].memories[fields[2]] = (int(fields[3]), int(fields[4]), int(fields[5]), int(fields[6]), fields[7] == "async") + + if fields[1] == "yosys-smt2-wire": + self.modinfo[self.curmod].wires.add(fields[2]) + self.modinfo[self.curmod].wsize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-clock": + for edge in fields[3:]: + if fields[2] not in self.modinfo[self.curmod].clocks: + self.modinfo[self.curmod].clocks[fields[2]] = edge + elif self.modinfo[self.curmod].clocks[fields[2]] != edge: + self.modinfo[self.curmod].clocks[fields[2]] = "event" + + if fields[1] == "yosys-smt2-assert": + if len(fields) > 4: + self.modinfo[self.curmod].asserts["%s_a %s" % (self.curmod, fields[2])] = f'{fields[4]} ({fields[3]})' + else: + self.modinfo[self.curmod].asserts["%s_a %s" % (self.curmod, fields[2])] = fields[3] + + if fields[1] == "yosys-smt2-cover": + if len(fields) > 4: + self.modinfo[self.curmod].covers["%s_c %s" % (self.curmod, fields[2])] = f'{fields[4]} ({fields[3]})' + else: + self.modinfo[self.curmod].covers["%s_c %s" % (self.curmod, fields[2])] = fields[3] + + if fields[1] == "yosys-smt2-assume": + if len(fields) > 4: + self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = f'{fields[4]} ({fields[3]})' + else: + self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = fields[3] + + if fields[1] == "yosys-smt2-maximize": + self.modinfo[self.curmod].maximize.add(fields[2]) + + if fields[1] == "yosys-smt2-minimize": + self.modinfo[self.curmod].minimize.add(fields[2]) + + if fields[1] == "yosys-smt2-anyconst": + self.modinfo[self.curmod].anyconsts[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5]) + self.modinfo[self.curmod].asize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-anyseq": + self.modinfo[self.curmod].anyseqs[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5]) + self.modinfo[self.curmod].asize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-allconst": + self.modinfo[self.curmod].allconsts[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5]) + self.modinfo[self.curmod].asize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-allseq": + self.modinfo[self.curmod].allseqs[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5]) + self.modinfo[self.curmod].asize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-witness": + data = json.loads(stmt.split(None, 2)[2]) + if data.get("type") in ["cell", "mem", "posedge", "negedge", "input", "reg", "init", "seq", "blackbox"]: + self.modinfo[self.curmod].witness.append(data) + + def hiernets(self, top, regs_only=False): + def hiernets_worker(nets, mod, cursor): + for netname in sorted(self.modinfo[mod].wsize.keys()): + if not regs_only or netname in self.modinfo[mod].registers: + nets.append(cursor + [netname]) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + hiernets_worker(nets, celltype, cursor + [cellname]) + + nets = list() + hiernets_worker(nets, top, []) + return nets + + def hieranyconsts(self, top): + def worker(results, mod, cursor): + for name, value in sorted(self.modinfo[mod].anyconsts.items()): + width = self.modinfo[mod].asize[name] + results.append((cursor, name, value[0], value[1], width)) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + worker(results, celltype, cursor + [cellname]) + + results = list() + worker(results, top, []) + return results + + def hieranyseqs(self, top): + def worker(results, mod, cursor): + for name, value in sorted(self.modinfo[mod].anyseqs.items()): + width = self.modinfo[mod].asize[name] + results.append((cursor, name, value[0], value[1], width)) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + worker(results, celltype, cursor + [cellname]) + + results = list() + worker(results, top, []) + return results + + def hierallconsts(self, top): + def worker(results, mod, cursor): + for name, value in sorted(self.modinfo[mod].allconsts.items()): + width = self.modinfo[mod].asize[name] + results.append((cursor, name, value[0], value[1], width)) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + worker(results, celltype, cursor + [cellname]) + + results = list() + worker(results, top, []) + return results + + def hierallseqs(self, top): + def worker(results, mod, cursor): + for name, value in sorted(self.modinfo[mod].allseqs.items()): + width = self.modinfo[mod].asize[name] + results.append((cursor, name, value[0], value[1], width)) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + worker(results, celltype, cursor + [cellname]) + + results = list() + worker(results, top, []) + return results + + def hiermems(self, top): + def hiermems_worker(mems, mod, cursor): + for memname in sorted(self.modinfo[mod].memories.keys()): + mems.append(cursor + [memname]) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + hiermems_worker(mems, celltype, cursor + [cellname]) + + mems = list() + hiermems_worker(mems, top, []) + return mems + + def hierwitness(self, top, allregs=False, blackbox=True): + init_witnesses = [] + seq_witnesses = [] + clk_witnesses = [] + mem_witnesses = [] + + def absolute(path, cursor, witness): + return { + **witness, + "path": path + tuple(witness["path"]), + "smtpath": cursor + [witness["smtname"]], + } + + for witness in self.modinfo[top].witness: + if witness["type"] == "input": + seq_witnesses.append(absolute((), [], witness)) + if witness["type"] in ("posedge", "negedge"): + clk_witnesses.append(absolute((), [], witness)) + + init_types = ["init"] + if allregs: + init_types.append("reg") + + seq_types = ["seq"] + if blackbox: + seq_types.append("blackbox") + + def worker(mod, path, cursor): + cell_paths = {} + for witness in self.modinfo[mod].witness: + if witness["type"] in init_types: + init_witnesses.append(absolute(path, cursor, witness)) + if witness["type"] in seq_types: + seq_witnesses.append(absolute(path, cursor, witness)) + if witness["type"] == "mem": + if allregs and not witness["rom"]: + width, size = witness["width"], witness["size"] + witness = {**witness, "uninitialized": [{"width": width * size, "offset": 0}]} + if not witness["uninitialized"]: + continue + + mem_witnesses.append(absolute(path, cursor, witness)) + if witness["type"] == "cell": + cell_paths[witness["smtname"]] = tuple(witness["path"]) + + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + worker(celltype, path + cell_paths.get(cellname, ("?" + cellname,)), cursor + [cellname]) + + worker(top, (), []) + return init_witnesses, seq_witnesses, clk_witnesses, mem_witnesses + + def read(self): + stmt = [] + count_brackets = 0 + + while True: + if self.solver == "dummy": + line = self.dummy_fd.readline().strip() + else: + line = self.p_read().strip() + if self.dummy_file is not None: + self.dummy_fd.write(line + "\n") + + count_brackets += line.count("(") + count_brackets -= line.count(")") + stmt.append(line) + + if self.debug_print: + print("< %s" % line) + if count_brackets == 0: + break + if self.solver != "dummy" and self.p.poll(): + print("%s Solver terminated unexpectedly: %s" % (self.timestamp(), "".join(stmt)), flush=True) + sys.exit(1) + + stmt = "".join(stmt) + if stmt.startswith("(error"): + print("%s Solver Error: %s" % (self.timestamp(), stmt), flush=True) + if self.solver != "dummy": + self.p_close() + sys.exit(1) + + return stmt + + def check_sat(self, expected=["sat", "unsat", "unknown", "timeout", "interrupted"]): + if self.smt2_assumptions: + assume_exprs = " ".join(self.smt2_assumptions.values()) + check_stmt = f"(check-sat-assuming ({assume_exprs}))" + else: + check_stmt = "(check-sat)" + if self.debug_print: + print(f"> {check_stmt}") + if self.debug_file and not self.nocomments: + print("; running check-sat..", file=self.debug_file) + self.debug_file.flush() + + if self.solver != "dummy": + if self.noincr: + if self.p is not None: + self.p_close() + self.p_open() + for cache_ctx in self.smt2cache: + for cache_stmt in cache_ctx: + self.p_write(cache_stmt + "\n", False) + + self.p_write(f"{check_stmt}\n", True) + + if self.timeinfo: + i = 0 + s = r"/-\|" + + count = 0 + num_bs = 0 + while self.p_poll(): + count += 1 + + if count < 25: + continue + + if count % 10 == 0 or count == 25: + secs = count // 10 + + if secs < 60: + m = "(%d seconds)" % secs + elif secs < 60*60: + m = "(%d seconds -- %d:%02d)" % (secs, secs // 60, secs % 60) + else: + m = "(%d seconds -- %d:%02d:%02d)" % (secs, secs // (60*60), (secs // 60) % 60, secs % 60) + + print("%s %s %c" % ("\b \b" * num_bs, m, s[i]), end="", file=sys.stderr) + num_bs = len(m) + 3 + + else: + print("\b" + s[i], end="", file=sys.stderr) + + sys.stderr.flush() + i = (i + 1) % len(s) + + if num_bs != 0: + print("\b \b" * num_bs, end="", file=sys.stderr) + sys.stderr.flush() + + else: + count = 0 + while self.p_poll(60): + count += 1 + msg = None + + if count == 1: + msg = "1 minute" + + elif count in [5, 10, 15, 30]: + msg = "%d minutes" % count + + elif count == 60: + msg = "1 hour" + + elif count % 60 == 0: + msg = "%d hours" % (count // 60) + + if msg is not None: + print("%s waiting for solver (%s)" % (self.timestamp(), msg), flush=True) + + if self.forall: + result = self.read() + while result not in ["sat", "unsat", "unknown", "timeout", "interrupted", ""]: + print("%s %s: %s" % (self.timestamp(), self.solver, result)) + result = self.read() + else: + result = self.read() + + if self.debug_file: + print("(set-info :status %s)" % result, file=self.debug_file) + print(check_stmt, file=self.debug_file) + self.debug_file.flush() + + if result not in expected: + if result == "": + print("%s Unexpected EOF response from solver." % (self.timestamp()), flush=True) + else: + print("%s Unexpected response from solver: %s" % (self.timestamp(), result), flush=True) + if self.solver != "dummy": + self.p_close() + sys.exit(1) + + return result + + def parse(self, stmt): + def worker(stmt, cursor=0): + while stmt[cursor] in [" ", "\t", "\r", "\n"]: + cursor += 1 + + if stmt[cursor] == '(': + expr = [] + cursor += 1 + while stmt[cursor] != ')': + el, cursor = worker(stmt, cursor) + expr.append(el) + return expr, cursor+1 + + if stmt[cursor] == '|': + expr = "|" + cursor += 1 + while stmt[cursor] != '|': + expr += stmt[cursor] + cursor += 1 + expr += "|" + return expr, cursor+1 + + expr = "" + while stmt[cursor] not in ["(", ")", "|", " ", "\t", "\r", "\n"]: + expr += stmt[cursor] + cursor += 1 + return expr, cursor + return worker(stmt)[0] + + def unparse(self, stmt): + if isinstance(stmt, list): + return "(" + " ".join([self.unparse(s) for s in stmt]) + ")" + return stmt + + def bv2hex(self, v): + h = "" + v = self.bv2bin(v) + while len(v) > 0: + d = 0 + if len(v) > 0 and v[-1] == "1": d += 1 + if len(v) > 1 and v[-2] == "1": d += 2 + if len(v) > 2 and v[-3] == "1": d += 4 + if len(v) > 3 and v[-4] == "1": d += 8 + h = hex(d)[2:] + h + if len(v) < 4: break + v = v[:-4] + return h + + def bv2bin(self, v): + if type(v) is list and len(v) == 3 and v[0] == "_" and v[1].startswith("bv"): + x, n = int(v[1][2:]), int(v[2]) + return "".join("1" if (x & (1 << i)) else "0" for i in range(n-1, -1, -1)) + if v == "true": return "1" + if v == "false": return "0" + if v.startswith("#b"): + return v[2:] + if v.startswith("#x"): + return "".join(hex_dict.get(x) for x in v[2:]) + assert False + + def bv2int(self, v): + return int(self.bv2bin(v), 2) + + def get_raw_unsat_assumptions(self): + self.write("(get-unsat-assumptions)") + exprs = set(self.unparse(part) for part in self.parse(self.read())) + unsat_assumptions = [] + for key, value in self.smt2_assumptions.items(): + # normalize expression + value = self.unparse(self.parse(value)) + if value in exprs: + exprs.remove(value) + unsat_assumptions.append(key) + return unsat_assumptions + + def get_unsat_assumptions(self, minimize=False): + if not minimize: + return self.get_raw_unsat_assumptions() + required_assumptions = {} + + while True: + candidate_assumptions = {} + for key in self.get_raw_unsat_assumptions(): + if key not in required_assumptions: + candidate_assumptions[key] = self.smt2_assumptions[key] + + while candidate_assumptions: + + candidate_key, candidate_assume = candidate_assumptions.popitem() + + self.smt2_assumptions = {} + for key, assume in candidate_assumptions.items(): + self.smt2_assumptions[key] = assume + for key, assume in required_assumptions.items(): + self.smt2_assumptions[key] = assume + result = self.check_sat() + + if result == 'unsat': + candidate_assumptions = None + else: + required_assumptions[candidate_key] = candidate_assume + + if candidate_assumptions is not None: + return list(required_assumptions) + + def get(self, expr): + self.write("(get-value (%s))" % (expr)) + return self.parse(self.read())[0][1] + + def get_list(self, expr_list): + if len(expr_list) == 0: + return [] + self.write("(get-value (%s))" % " ".join(expr_list)) + return [n[1] for n in self.parse(self.read()) if n] + + def get_path(self, mod, path): + assert mod in self.modinfo + path = path.replace("\\", "/").split(".") + + for i in range(len(path)-1): + first = ".".join(path[0:i+1]) + second = ".".join(path[i+1:]) + + if first in self.modinfo[mod].cells: + nextmod = self.modinfo[mod].cells[first] + return [first] + self.get_path(nextmod, second) + + return [".".join(path)] + + def net_expr(self, mod, base, path): + if len(path) == 0: + return base + + if len(path) == 1: + assert mod in self.modinfo + if path[0] == "": + return base + if isinstance(path[0], int): + return "(|%s#%d| %s)" % (mod, path[0], base) + if path[0] in self.modinfo[mod].cells: + return "(|%s_h %s| %s)" % (mod, path[0], base) + if path[0] in self.modinfo[mod].wsize: + return "(|%s_n %s| %s)" % (mod, path[0], base) + if path[0] in self.modinfo[mod].memories: + return "(|%s_m %s| %s)" % (mod, path[0], base) + assert 0 + + assert mod in self.modinfo + assert path[0] in self.modinfo[mod].cells + + nextmod = self.modinfo[mod].cells[path[0]] + nextbase = "(|%s_h %s| %s)" % (mod, path[0], base) + return self.net_expr(nextmod, nextbase, path[1:]) + + def witness_net_expr(self, mod, base, witness): + net = self.net_expr(mod, base, witness["smtpath"]) + is_bool = self.net_width(mod, witness["smtpath"]) == 1 + if is_bool: + assert witness["width"] == 1 + assert witness["smtoffset"] == 0 + return net + return "((_ extract %d %d) %s)" % (witness["smtoffset"] + witness["width"] - 1, witness["smtoffset"], net) + + def net_width(self, mod, net_path): + for i in range(len(net_path)-1): + assert mod in self.modinfo + assert net_path[i] in self.modinfo[mod].cells + mod = self.modinfo[mod].cells[net_path[i]] + + assert mod in self.modinfo + if isinstance(net_path[-1], int): + return None + assert net_path[-1] in self.modinfo[mod].wsize + return self.modinfo[mod].wsize[net_path[-1]] + + def net_clock(self, mod, net_path): + for i in range(len(net_path)-1): + assert mod in self.modinfo + assert net_path[i] in self.modinfo[mod].cells + mod = self.modinfo[mod].cells[net_path[i]] + + assert mod in self.modinfo + if net_path[-1] not in self.modinfo[mod].clocks: + return None + return self.modinfo[mod].clocks[net_path[-1]] + + def net_exists(self, mod, net_path): + for i in range(len(net_path)-1): + if mod not in self.modinfo: return False + if net_path[i] not in self.modinfo[mod].cells: return False + mod = self.modinfo[mod].cells[net_path[i]] + + if mod not in self.modinfo: return False + if net_path[-1] not in self.modinfo[mod].wsize: return False + return True + + def mem_exists(self, mod, mem_path): + for i in range(len(mem_path)-1): + if mod not in self.modinfo: return False + if mem_path[i] not in self.modinfo[mod].cells: return False + mod = self.modinfo[mod].cells[mem_path[i]] + + if mod not in self.modinfo: return False + if mem_path[-1] not in self.modinfo[mod].memories: return False + return True + + def mem_expr(self, mod, base, path, port=None, infomode=False): + if len(path) == 1: + assert mod in self.modinfo + assert path[0] in self.modinfo[mod].memories + if infomode: + return self.modinfo[mod].memories[path[0]] + return "(|%s_m%s %s| %s)" % (mod, "" if port is None else ":%s" % port, path[0], base) + + assert mod in self.modinfo + assert path[0] in self.modinfo[mod].cells + + nextmod = self.modinfo[mod].cells[path[0]] + nextbase = "(|%s_h %s| %s)" % (mod, path[0], base) + return self.mem_expr(nextmod, nextbase, path[1:], port=port, infomode=infomode) + + def mem_info(self, mod, path): + return self.mem_expr(mod, "", path, infomode=True) + + def get_net(self, mod_name, net_path, state_name): + return self.get(self.net_expr(mod_name, state_name, net_path)) + + def get_net_list(self, mod_name, net_path_list, state_name): + return self.get_list([self.net_expr(mod_name, state_name, n) for n in net_path_list]) + + def get_net_hex(self, mod_name, net_path, state_name): + return self.bv2hex(self.get_net(mod_name, net_path, state_name)) + + def get_net_hex_list(self, mod_name, net_path_list, state_name): + return [self.bv2hex(v) for v in self.get_net_list(mod_name, net_path_list, state_name)] + + def get_net_bin(self, mod_name, net_path, state_name): + return self.bv2bin(self.get_net(mod_name, net_path, state_name)) + + def get_net_bin_list(self, mod_name, net_path_list, state_name): + return [self.bv2bin(v) for v in self.get_net_list(mod_name, net_path_list, state_name)] + + def wait(self): + if self.p is not None: + self.p.wait() + self.p_close() + + +class SmtOpts: + def __init__(self): + self.shortopts = "s:S:v" + self.longopts = ["unroll", "noincr", "noprogress", "timeout=", "dump-smt2=", "logic=", "dummy=", "info=", "nocomments"] + self.solver = "yices" + self.solver_opts = list() + self.debug_print = False + self.debug_file = None + self.dummy_file = None + self.unroll = False + self.noincr = False + self.timeinfo = os.name != "nt" + self.timeout = 0 + self.logic = None + self.info_stmts = list() + self.nocomments = False + + def handle(self, o, a): + if o == "-s": + self.solver = a + elif o == "-S": + self.solver_opts.append(a) + elif o == "--timeout": + self.timeout = int(a) + elif o == "-v": + self.debug_print = True + elif o == "--unroll": + self.unroll = True + elif o == "--noincr": + self.noincr = True + elif o == "--noprogress": + self.timeinfo = False + elif o == "--dump-smt2": + self.debug_file = open(a, "w") + elif o == "--logic": + self.logic = a + elif o == "--dummy": + self.dummy_file = a + elif o == "--info": + self.info_stmts.append(a) + elif o == "--nocomments": + self.nocomments = True + else: + return False + return True + + def helpmsg(self): + return """ + -s + set SMT solver: z3, yices, boolector, bitwuzla, cvc4, mathsat, dummy + default: yices + + -S + pass as command line argument to the solver + + --timeout + set the solver timeout to the specified value (in seconds). + + --logic + use the specified SMT2 logic (e.g. QF_AUFBV) + + --dummy + if solver is "dummy", read solver output from that file + otherwise: write solver output to that file + + -v + enable debug output + + --unroll + unroll uninterpreted functions + + --noincr + don't use incremental solving, instead restart solver for + each (check-sat). This also avoids (push) and (pop). + + --noprogress + disable timer display during solving + (this option is set implicitly on Windows) + + --dump-smt2 + write smt2 statements to file + + --info + include the specified smt2 info statement in the smt2 output + + --nocomments + strip all comments from the generated smt2 code +""" + + +class MkVcd: + def __init__(self, f): + self.f = f + self.t = -1 + self.nets = dict() + self.clocks = dict() + + def add_net(self, path, width): + path = tuple(path) + assert self.t == -1 + key = "n%d" % len(self.nets) + self.nets[path] = (key, width) + + def add_clock(self, path, edge): + path = tuple(path) + assert self.t == -1 + key = "n%d" % len(self.nets) + self.nets[path] = (key, 1) + self.clocks[path] = (key, edge) + + def set_net(self, path, bits): + path = tuple(path) + assert self.t >= 0 + assert path in self.nets + if path not in self.clocks: + print("b%s %s" % (bits, self.nets[path][0]), file=self.f) + + def escape_name(self, name): + name = re.sub(r"\[([0-9a-zA-Z_]*[a-zA-Z_][0-9a-zA-Z_]*)\]", r"<\1>", name) + if re.match(r"[\[\]]", name) and name[0] != "\\": + name = "\\" + name + return name + + def set_time(self, t): + assert t >= self.t + if t != self.t: + if self.t == -1: + print("$version Generated by Yosys-SMTBMC $end", file=self.f) + print("$timescale 1ns $end", file=self.f) + print("$var integer 32 t smt_step $end", file=self.f) + print("$var event 1 ! smt_clock $end", file=self.f) + + def vcdescape(n): + if n.startswith("$") or ":" in n: + return "\\" + n + return n + + scope = [] + for path in sorted(self.nets): + key, width = self.nets[path] + + uipath = list(path) + if "." in uipath[-1] and not uipath[-1].startswith("$"): + uipath = uipath[0:-1] + uipath[-1].split(".") + for i in range(len(uipath)): + uipath[i] = re.sub(r"\[([^\]]*)\]", r"<\1>", uipath[i]) + + while uipath[:len(scope)] != scope: + print("$upscope $end", file=self.f) + scope = scope[:-1] + + while uipath[:-1] != scope: + scopename = uipath[len(scope)] + print("$scope module %s $end" % vcdescape(scopename), file=self.f) + scope.append(uipath[len(scope)]) + + if path in self.clocks and self.clocks[path][1] == "event": + print("$var event 1 %s %s $end" % (key, vcdescape(uipath[-1])), file=self.f) + else: + print("$var wire %d %s %s $end" % (width, key, vcdescape(uipath[-1])), file=self.f) + + for i in range(len(scope)): + print("$upscope $end", file=self.f) + + print("$enddefinitions $end", file=self.f) + + self.t = t + assert self.t >= 0 + + if self.t > 0: + print("#%d" % (10 * self.t - 5), file=self.f) + for path in sorted(self.clocks.keys()): + if self.clocks[path][1] == "posedge": + print("b0 %s" % self.nets[path][0], file=self.f) + elif self.clocks[path][1] == "negedge": + print("b1 %s" % self.nets[path][0], file=self.f) + + print("#%d" % (10 * self.t), file=self.f) + print("1!", file=self.f) + print("b%s t" % format(self.t, "032b"), file=self.f) + + for path in sorted(self.clocks.keys()): + if self.clocks[path][1] == "negedge": + print("b0 %s" % self.nets[path][0], file=self.f) + else: + print("b1 %s" % self.nets[path][0], file=self.f) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py new file mode 100644 index 00000000000..7a09966d897 --- /dev/null +++ b/tests/functional/test_functional.py @@ -0,0 +1,94 @@ +import subprocess +import pytest +import sys +import shlex +from pathlib import Path + +base_path = Path(__file__).resolve().parent.parent.parent + +# quote a string or pathlib path so that it can be used by bash or yosys +# TODO: is this really appropriate for yosys? +def quote(path): + return shlex.quote(str(path)) + +# run a shell command and require the return code to be 0 +def run(cmd, **kwargs): + print(' '.join([quote(x) for x in cmd])) + status = subprocess.run(cmd, **kwargs) + assert status.returncode == 0, f"{cmd[0]} failed" + +def yosys(script): + run([base_path / 'yosys', '-Q', '-p', script]) + +def compile_cpp(in_path, out_path, args): + run(['g++', '-g', '-std=c++17'] + args + [str(in_path), '-o', str(out_path)]) + +def yosys_synth(verilog_file, rtlil_file): + yosys(f"read_verilog {quote(verilog_file)} ; prep ; write_rtlil {quote(rtlil_file)}") + +# simulate an rtlil file with yosys, comparing with a given vcd file, and writing out the yosys simulation results into a second vcd file +def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file, preprocessing = ""): + try: + yosys(f"read_rtlil {quote(rtlil_file)}; {preprocessing}; sim -r {quote(vcd_reference_file)} -scope gold -vcd {quote(vcd_out_file)} -timescale 1us -sim-gold -fst-noinit") + except: + # if yosys sim fails it's probably because of a simulation mismatch + # since yosys sim aborts on simulation mismatch to generate vcd output + # we have to re-run with a different set of flags + # on this run we ignore output and return code, we just want a best-effort attempt to get a vcd + subprocess.run([base_path / 'yosys', '-Q', '-p', + f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_out_file)} -a -r {quote(vcd_reference_file)} -scope gold -timescale 1us -fst-noinit'], + capture_output=True, check=False) + raise + +def test_cxx(cell, parameters, tmp_path, num_steps, rnd): + rtlil_file = tmp_path / 'rtlil.il' + vcdharness_cc_file = base_path / 'tests/functional/vcd_harness.cc' + cc_file = tmp_path / 'my_module_functional_cxx.cc' + vcdharness_exe_file = tmp_path / 'a.out' + vcd_functional_file = tmp_path / 'functional.vcd' + vcd_yosys_sim_file = tmp_path / 'yosys.vcd' + + cell.write_rtlil_file(rtlil_file, parameters) + yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_cxx {quote(cc_file)}") + compile_cpp(vcdharness_cc_file, vcdharness_exe_file, ['-I', tmp_path, '-I', str(base_path / 'backends/functional/cxx_runtime')]) + seed = str(rnd(cell.name + "-cxx").getrandbits(32)) + run([str(vcdharness_exe_file.resolve()), str(vcd_functional_file), str(num_steps), str(seed)]) + yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) + +@pytest.mark.smt +def test_smt(cell, parameters, tmp_path, num_steps, rnd): + import smt_vcd + + rtlil_file = tmp_path / 'rtlil.il' + smt_file = tmp_path / 'smtlib.smt' + vcd_functional_file = tmp_path / 'functional.vcd' + vcd_yosys_sim_file = tmp_path / 'yosys.vcd' + + if hasattr(cell, 'smt_max_steps'): + num_steps = min(num_steps, cell.smt_max_steps) + + cell.write_rtlil_file(rtlil_file, parameters) + yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_smt2 {quote(smt_file)}") + run(['z3', smt_file]) # check if output is valid smtlib before continuing + smt_vcd.simulate_smt(smt_file, vcd_functional_file, num_steps, rnd(cell.name + "-smt")) + yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) + +@pytest.mark.rkt +def test_rkt(cell, parameters, tmp_path, num_steps, rnd): + import rkt_vcd + + rtlil_file = tmp_path / 'rtlil.il' + rkt_file = tmp_path / 'smtlib.rkt' + vcd_functional_file = tmp_path / 'functional.vcd' + vcd_yosys_sim_file = tmp_path / 'yosys.vcd' + + cell.write_rtlil_file(rtlil_file, parameters) + yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_rosette -provides {quote(rkt_file)}") + rkt_vcd.simulate_rosette(rkt_file, vcd_functional_file, num_steps, rnd(cell.name + "-rkt")) + yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) + +def test_print_graph(tmp_path): + tb_file = base_path / 'tests/functional/picorv32_tb.v' + cpu_file = base_path / 'tests/functional/picorv32.v' + # currently we only check that we can print the graph without getting an error, not that it prints anything sensibl + yosys(f"read_verilog {quote(tb_file)} {quote(cpu_file)}; prep -top gold; flatten; clk2fflogic; test_generic") diff --git a/tests/functional/vcd_harness.cc b/tests/functional/vcd_harness.cc new file mode 100644 index 00000000000..30238429b6e --- /dev/null +++ b/tests/functional/vcd_harness.cc @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "my_module_functional_cxx.cc" + +class VcdFile { + std::ofstream &ofs; + std::string code_alloc = "!"; + std::unordered_map codes; + std::string name_mangle(std::string name) { + std::string ret = name; + bool escape = ret.empty() || !isalpha(ret[0]) && ret[0] != '_'; + for(size_t i = 0; i < ret.size(); i++) { + if(isspace(ret[i])) ret[i] = '_'; + if(!isalnum(ret[i]) && ret[i] != '_' && ret[i] != '$') + escape = true; + } + if(escape) + return "\\" + ret; + else + return ret; + } + std::string allocate_code() { + std::string ret = code_alloc; + for (size_t i = 0; i < code_alloc.size(); i++) + if (code_alloc[i]++ == '~') + code_alloc[i] = '!'; + else + return ret; + code_alloc.push_back('!'); + return ret; + } +public: + VcdFile(std::ofstream &ofs) : ofs(ofs) {} + struct DumpHeader { + VcdFile *file; + explicit DumpHeader(VcdFile *file) : file(file) {} + template void operator()(const char *name, Signal value) + { + auto it = file->codes.find(name); + if(it == file->codes.end()) + it = file->codes.emplace(name, file->allocate_code()).first; + file->ofs << "$var wire " << n << " " << it->second << " " << file->name_mangle(name) << " $end\n"; + } + template void operator()(const char *name, Memory value) {} + }; + struct Dump { + VcdFile *file; + explicit Dump(VcdFile *file) : file(file) {} + template void operator()(const char *name, Signal value) + { + if (n == 1) { + file->ofs << (value[0] ? '1' : '0'); + file->ofs << file->codes.at(name) << "\n"; + } else { + file->ofs << "b"; + for (size_t i = n; i-- > 0;) + file->ofs << (value[i] ? '1' : '0'); + file->ofs << " " << file->codes.at(name) << "\n"; + } + } + template void operator()(const char *name, Memory value) {} + }; + void begin_header() { + constexpr int number_timescale = 1; + const std::string units_timescale = "us"; + ofs << "$timescale " << number_timescale << " " << units_timescale << " $end\n"; + ofs << "$scope module gold $end\n"; + } + void end_header() { + ofs << "$enddefinitions $end\n$dumpvars\n"; + } + template void header(Args ...args) { + begin_header(); + DumpHeader d(this); + (args.visit(d), ...); + end_header(); + } + void begin_data(int step) { + ofs << "#" << step << "\n"; + } + template void data(int step, Args ...args) { + begin_data(step); + Dump d(this); + (args.visit(d), ...); + } + DumpHeader dump_header() { return DumpHeader(this); } + Dump dump() { return Dump(this); } +}; + +template Signal random_signal(std::mt19937 &gen) +{ + std::uniform_int_distribution dist; + std::array words; + for (auto &w : words) + w = dist(gen); + return Signal::from_array(words); +} + +struct Randomize { + std::mt19937 &gen; + Randomize(std::mt19937 &gen) : gen(gen) {} + + template void operator()(const char *, Signal &signal) { signal = random_signal(gen); } +}; + +int main(int argc, char **argv) +{ + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } + + const std::string functional_vcd_filename = argv[1]; + const int steps = atoi(argv[2]); + const uint32_t seed = atoi(argv[3]); + + gold::Inputs inputs; + gold::Outputs outputs; + gold::State state; + gold::State next_state; + + std::ofstream vcd_file(functional_vcd_filename); + VcdFile vcd(vcd_file); + vcd.header(inputs, outputs, state); + + std::mt19937 gen(seed); + + gold::initialize(state); + + for (int step = 0; step < steps; ++step) { + inputs.visit(Randomize(gen)); + + gold::eval(inputs, outputs, state, next_state); + vcd.data(step, inputs, outputs, state); + + state = next_state; + } + + return 0; +} diff --git a/tests/liberty/.gitignore b/tests/liberty/.gitignore index bf5ba57dce0..8763aaac515 100644 --- a/tests/liberty/.gitignore +++ b/tests/liberty/.gitignore @@ -1,4 +1,3 @@ *.log -test.ys *.filtered *.verilogsim diff --git a/tests/liberty/run-test.sh b/tests/liberty/run-test.sh index 07d2cce1cd5..ff5b20d74e5 100755 --- a/tests/liberty/run-test.sh +++ b/tests/liberty/run-test.sh @@ -3,11 +3,13 @@ set -e for x in *.lib; do echo "Testing on $x.." - echo "read_verilog small.v" > test.ys - echo "synth -top small" >> test.ys - echo "dfflibmap -info -liberty ${x}" >> test.ys - ../../yosys -ql ${x%.lib}.log -s test.ys + ../../yosys -p "read_verilog small.v; synth -top small; dfflibmap -info -liberty ${x}" -ql ${x%.lib}.log ../../yosys-filterlib - $x 2>/dev/null > $x.filtered ../../yosys-filterlib -verilogsim $x > $x.verilogsim diff $x.filtered $x.filtered.ok && diff $x.verilogsim $x.verilogsim.ok done + +for x in *.ys; do + echo "Running $x.." + ../../yosys -q -s $x -l ${x%.ys}.log +done diff --git a/tests/liberty/unit_delay.ys b/tests/liberty/unit_delay.ys new file mode 100644 index 00000000000..8dd4091835a --- /dev/null +++ b/tests/liberty/unit_delay.ys @@ -0,0 +1,3 @@ +# Nothing gets imported: the file lacks timing data +read_liberty -wb -unit_delay normal.lib +select -assert-none =*/t:$specify* diff --git a/tests/techmap/aigmap.ys b/tests/techmap/aigmap.ys index 6f6cdd1f21a..deec440e26a 100644 --- a/tests/techmap/aigmap.ys +++ b/tests/techmap/aigmap.ys @@ -84,14 +84,14 @@ assign name``_y2 = op name``_a2; `BIOP(xnor, ~^, 3, 3, 3) `BIOP(logic_and, &&, 3, 3, 1) `BIOP(logic_or, ||, 3, 3, 1) -`BIOP(logic_eq, ==, 3, 3, 1) -`BIOP(logic_ne, !=, 3, 3, 1) -`BIOP(logic_lt, <, 3, 3, 1) -`BIOP(logic_le, <=, 3, 3, 1) -`BIOP(logic_gt, >, 3, 3, 1) -`BIOP(logic_ge, >=, 3, 3, 1) +`BIOP(eq, ==, 3, 3, 1) +`BIOP(ne, !=, 3, 3, 1) +`BIOP(lt, <, 3, 3, 1) +`BIOP(le, <=, 3, 3, 1) +`BIOP(gt, >, 3, 3, 1) +`BIOP(ge, >=, 3, 3, 1) `UNOP(pos, +, 3) -`UNOP(neg, ~, 3) +`UNOP(not, ~, 3) `UNOP_REDUCE(logic_not, !, 3) `UNOP_REDUCE(reduce_and, &, 3) `UNOP_REDUCE(reduce_or, |, 3) diff --git a/tests/techmap/clockgate.ys b/tests/techmap/clockgate.ys new file mode 100644 index 00000000000..dac4de0ecc5 --- /dev/null +++ b/tests/techmap/clockgate.ys @@ -0,0 +1,196 @@ +read_verilog << EOT + +module dffe_00( input clk, en, + input d1, output reg q1, + ); + always @( negedge clk ) begin + if ( ~en ) + q1 <= d1; + end +endmodule + +module dffe_01( input clk, en, + input d1, output reg q1, + ); + always @( negedge clk ) begin + if ( en ) + q1 <= d1; + end +endmodule + +module dffe_10( input clk, en, + input d1, output reg q1, + ); + always @( posedge clk ) begin + if ( ~en ) + q1 <= d1; + end +endmodule + +module dffe_11( input clk, en, + input d1, output reg q1, + ); + always @( posedge clk ) begin + if ( en ) + q1 <= d1; + end +endmodule + +module dffe_wide_11( input clk, en, + input [3:0] d1, output reg [3:0] q1, + ); + always @( posedge clk ) begin + if ( en ) + q1 <= d1; + end +endmodule + +EOT + +proc +opt + +design -save before + +#------------------------------------------------------------------------------ + +# Test -pos + +clockgate -pos pdk_icg ce:clkin:clkout -tie_lo scanen + +# falling edge clock flops don't get matched on -pos +select -module dffe_00 -assert-count 0 t:\\pdk_icg +select -module dffe_01 -assert-count 0 t:\\pdk_icg +# falling edge clock flops do get matched on -pos +select -module dffe_10 -assert-count 1 t:\\pdk_icg +select -module dffe_11 -assert-count 1 t:\\pdk_icg +# if necessary, EN is inverted, since the given ICG +# is assumed to have an active-high EN +select -module dffe_10 -assert-count 1 t:\$_NOT_ +select -module dffe_11 -assert-count 0 t:\$_NOT_ + +# Extra credit: multi-bit FFs work fine as well +select -module dffe_wide_11 -assert-count 1 t:\\pdk_icg + +#------------------------------------------------------------------------------ + +# Test -neg + +design -load before +clockgate -min_net_size 1 -neg pdk_icg ce:clkin:clkout -tie_lo scanen + +# rising edge clock flops don't get matched on -neg +select -module dffe_00 -assert-count 1 t:\\pdk_icg +select -module dffe_01 -assert-count 1 t:\\pdk_icg +# rising edge clock flops do get matched on -neg +select -module dffe_10 -assert-count 0 t:\\pdk_icg +select -module dffe_11 -assert-count 0 t:\\pdk_icg +# if necessary, EN is inverted, since the given ICG +# is assumed to have an active-high EN +select -module dffe_00 -assert-count 1 t:\$_NOT_ +select -module dffe_01 -assert-count 0 t:\$_NOT_ + +#------------------------------------------------------------------------------ + +# Same as first case, but on fine-grained cells + +design -load before + +techmap + +clockgate -pos pdk_icg ce:clkin:clkout -tie_lo scanen + +# falling edge clock flops don't get matched on -pos +select -module dffe_00 -assert-count 0 t:\\pdk_icg +select -module dffe_01 -assert-count 0 t:\\pdk_icg +# falling edge clock flops do get matched on -pos +select -module dffe_10 -assert-count 1 t:\\pdk_icg +select -module dffe_11 -assert-count 1 t:\\pdk_icg +# if necessary, EN is inverted, since the given ICG +# is assumed to have an active-high EN +select -module dffe_10 -assert-count 1 t:\$_NOT_ +select -module dffe_11 -assert-count 0 t:\$_NOT_ + +# Extra credit: multi-bit FFs work fine as well +select -module dffe_wide_11 -assert-count 1 t:\\pdk_icg + +#------------------------------------------------------------------------------ + +design -load before +clockgate -min_net_size 2 -neg pdk_icg ce:clkin:clkout -tie_lo scanen + +# No FF set sharing a (clock, clock enable) pair is large enough +select -module dffe_00 -assert-count 0 t:\\pdk_icg +select -module dffe_01 -assert-count 0 t:\\pdk_icg +select -module dffe_10 -assert-count 0 t:\\pdk_icg +select -module dffe_11 -assert-count 0 t:\\pdk_icg + +#------------------------------------------------------------------------------ + +design -reset +read_rtlil << EOT + +module \bad1 + wire input 1 \clk + wire input 3 \d1 + wire input 2 \en + wire output 4 \q1 + cell $dffe $auto$ff.cc:266:slice$27 + parameter \CLK_POLARITY 1 + parameter \EN_POLARITY 1 + parameter \WIDTH 1 + connect \CLK \clk + connect \D \d1 + connect \EN 1'1 + connect \Q \q1 + end +end + +module \bad2 + wire input 1 \clk + wire input 3 \d1 + wire input 2 \en + wire output 4 \q1 + cell $dffe $auto$ff.cc:266:slice$27 + parameter \CLK_POLARITY 1 + parameter \EN_POLARITY 1 + parameter \WIDTH 1 + connect \CLK 1'1 + connect \D \d1 + connect \EN \en + connect \Q \q1 + end +end + +EOT + +# Check we don't choke on constants +clockgate -pos pdk_icg ce:clkin:clkout -tie_lo scanen +select -module bad1 -assert-count 0 t:\\pdk_icg +select -module bad2 -assert-count 0 t:\\pdk_icg + +#------------------------------------------------------------------------------ + +# Regression test: EN is a bit from a multi-bit wire +design -reset +read_verilog << EOT +module dffe_wide_11( input clk, input [1:0] en, + input [3:0] d1, output reg [3:0] q1, + ); + always @( posedge clk ) begin + if ( en[0] ) + q1 <= d1; + end +endmodule + +EOT + +proc +opt + +clockgate -pos pdk_icg ce:clkin:clkout -tie_lo scanen +select -assert-count 1 t:\\pdk_icg + +#------------------------------------------------------------------------------ + +# TODO test -tie_lo diff --git a/tests/various/aiger2.ys b/tests/various/aiger2.ys new file mode 100644 index 00000000000..d2b024d2f5f --- /dev/null +++ b/tests/various/aiger2.ys @@ -0,0 +1,224 @@ +read_verilog -icells <, 3, 3, 1) +`BIOP(ge, >=, 3, 3, 1) +`UNOP(not, ~, 3) +`UNOP_REDUCE(logic_not, !, 3) +`UNOP_REDUCE(reduce_and, &, 3) +`UNOP_REDUCE(reduce_or, |, 3) +`UNOP_REDUCE(reduce_xor, ^, 3) +`UNOP_REDUCE(reduce_xnor, ~^, 3) + +wire [3:0] mux_a, mux_b, mux_s, mux_y; +assign mux_y = mux_s ? mux_b : mux_a; + +wire [1:0] fa_a, fa_b, fa_c, fa_x, fa_y; +\$fa #( + .WIDTH(2) +) fa(.A(fa_a), .B(fa_b), .C(fa_c), .X(fa_x), .Y(fa_y)); + +wire [1:0] bwmux_a, bwmux_b, bwmux_s, bwmux_y; +\$bwmux #( + .WIDTH(2) +) bwmux(.A(bwmux_a), .B(bwmux_b), .S(bwmux_s), .Y(bwmux_y)); +endmodule +EOF + +expose -input c:* %ci* w:* %i +expose c:* %co* w:* %i +splitnets -ports +copy test gold +select test +write_aiger2 aiger2_ops.aig +select -clear +delete test +read_aiger -module_name test aiger2_ops.aig +select -assert-none test/t:$_AND_ test/t:$_NOT_ %% test/c:* %D +miter -equiv -make_outcmp -flatten gold test miter +sat -verify -show-ports -prove trigger 0 miter + +design -reset +read_verilog -icells <&2; exit 1' ERR + +(../../yosys -v 3 -C <&1 | grep -F "ERROR: Module \`nonexistent' not found!" > /dev/null diff --git a/tests/verific/bounds.vhd b/tests/verific/bounds.vhd new file mode 100644 index 00000000000..14c6c34a76a --- /dev/null +++ b/tests/verific/bounds.vhd @@ -0,0 +1,18 @@ +library IEEE; +use IEEE.STD_LOGIC_1164.ALL; +use IEEE.NUMERIC_STD.ALL; + +entity work is + Port ( + a : in INTEGER range -5 to 10; + b : out INTEGER range -6 to 11 + ); +end entity work; + +architecture Behavioral of work is +begin + process(a) + begin + b <= a; + end process; +end architecture Behavioral; diff --git a/tests/verific/bounds.ys b/tests/verific/bounds.ys new file mode 100644 index 00000000000..425af717cf4 --- /dev/null +++ b/tests/verific/bounds.ys @@ -0,0 +1,6 @@ +read -vhdl bounds.vhd +verific -import work +select -assert-count 1 a:bottom_bound=5'bs11011 +select -assert-count 1 a:top_bound=5'bs01010 +select -assert-count 1 a:bottom_bound=5'bs11010 +select -assert-count 1 a:top_bound=5'bs01011 \ No newline at end of file diff --git a/tests/verilog/size_cast.sv b/tests/verilog/size_cast.sv index 1636f8d701e..a1d6bfae554 100644 --- a/tests/verilog/size_cast.sv +++ b/tests/verilog/size_cast.sv @@ -15,6 +15,18 @@ module top; logic signed [1:0] L2sb10 = 2; logic signed [1:0] L2sb11 = 3; + typedef logic u1bit_t; + typedef logic signed s1bit_t; + typedef logic [1:0] u2bit_t; + typedef logic signed [1:0] s2bit_t; + typedef logic [2:0] u3bit_t; + + typedef struct packed { + u1bit_t sign; + u3bit_t msbs; + byte lsbs; + } s12bit_packed_struct_t; + logic y = 1; always @* begin @@ -32,6 +44,19 @@ module top; assert (1'(L2sb10) == 1'b0); assert (1'(L2sb11) == 1'b1); + assert (u1bit_t'(L1b0 ) == 1'b0); + assert (u1bit_t'(L1b1 ) == 1'b1); + assert (s1bit_t'(L1sb0 ) == 1'b0); + assert (s1bit_t'(L1sb1 ) == 1'b1); + assert (u1bit_t'(L2b00 ) == 1'b0); + assert (u1bit_t'(L2b01 ) == 1'b1); + assert (u1bit_t'(L2b10 ) == 1'b0); + assert (u1bit_t'(L2b11 ) == 1'b1); + assert (s1bit_t'(L2sb00) == 1'b0); + assert (s1bit_t'(L2sb01) == 1'b1); + assert (s1bit_t'(L2sb10) == 1'b0); + assert (s1bit_t'(L2sb11) == 1'b1); + assert (2'(L1b0 ) == 2'b00); assert (2'(L1b1 ) == 2'b01); assert (2'(L1sb0 ) == 2'b00); @@ -45,6 +70,19 @@ module top; assert (2'(L2sb10) == 2'b10); assert (2'(L2sb11) == 2'b11); + assert (u2bit_t'(L1b0 ) == 2'b00); + assert (u2bit_t'(L1b1 ) == 2'b01); + assert (s2bit_t'(L1sb0 ) == 2'b00); + assert (s2bit_t'(L1sb1 ) == 2'b11); + assert (u2bit_t'(L2b00 ) == 2'b00); + assert (u2bit_t'(L2b01 ) == 2'b01); + assert (u2bit_t'(L2b10 ) == 2'b10); + assert (u2bit_t'(L2b11 ) == 2'b11); + assert (s2bit_t'(L2sb00) == 2'b00); + assert (s2bit_t'(L2sb01) == 2'b01); + assert (s2bit_t'(L2sb10) == 2'b10); + assert (s2bit_t'(L2sb11) == 2'b11); + assert (3'(L1b0 ) == 3'b000); assert (3'(L1b1 ) == 3'b001); assert (3'(L1sb0 ) == 3'b000); @@ -58,6 +96,19 @@ module top; assert (3'(L2sb10) == 3'b110); assert (3'(L2sb11) == 3'b111); + assert (u3bit_t'(L1b0 ) == 3'b000); + assert (u3bit_t'(L1b1 ) == 3'b001); + assert (u3bit_t'(L1sb0 ) == 3'b000); + assert (u3bit_t'(L1sb1 ) == 3'b111); + assert (u3bit_t'(L2b00 ) == 3'b000); + assert (u3bit_t'(L2b01 ) == 3'b001); + assert (u3bit_t'(L2b10 ) == 3'b010); + assert (u3bit_t'(L2b11 ) == 3'b011); + assert (u3bit_t'(L2sb00) == 3'b000); + assert (u3bit_t'(L2sb01) == 3'b001); + assert (u3bit_t'(L2sb10) == 3'b110); + assert (u3bit_t'(L2sb11) == 3'b111); + assert (3'(L1b0 | '1) == 3'b111); assert (3'(L1b1 | '1) == 3'b111); assert (3'(L1sb0 | '1) == 3'b111); @@ -71,6 +122,58 @@ module top; assert (3'(L2sb10 | '1) == 3'b111); assert (3'(L2sb11 | '1) == 3'b111); + assert (u3bit_t'(L1b0 | '1) == 3'b111); + assert (u3bit_t'(L1b1 | '1) == 3'b111); + assert (u3bit_t'(L1sb0 | '1) == 3'b111); + assert (u3bit_t'(L1sb1 | '1) == 3'b111); + assert (u3bit_t'(L2b00 | '1) == 3'b111); + assert (u3bit_t'(L2b01 | '1) == 3'b111); + assert (u3bit_t'(L2b10 | '1) == 3'b111); + assert (u3bit_t'(L2b11 | '1) == 3'b111); + assert (u3bit_t'(L2sb00 | '1) == 3'b111); + assert (u3bit_t'(L2sb01 | '1) == 3'b111); + assert (u3bit_t'(L2sb10 | '1) == 3'b111); + assert (u3bit_t'(L2sb11 | '1) == 3'b111); + + assert (byte'(L1b0 | '1) == 8'hff); + assert (byte'(L1b1 | '1) == 8'hff); + assert (byte'(L1sb0 | '1) == 8'hff); + assert (byte'(L1sb1 | '1) == 8'hff); + assert (byte'(L2b00 | '1) == 8'hff); + assert (byte'(L2b01 | '1) == 8'hff); + assert (byte'(L2b10 | '1) == 8'hff); + assert (byte'(L2b11 | '1) == 8'hff); + assert (byte'(L2sb00 | '1) == 8'hff); + assert (byte'(L2sb01 | '1) == 8'hff); + assert (byte'(L2sb10 | '1) == 8'hff); + assert (byte'(L2sb11 | '1) == 8'hff); + + assert (int'(L1b0 | '1) == 32'hffff_ffff); + assert (int'(L1b1 | '1) == 32'hffff_ffff); + assert (int'(L1sb0 | '1) == 32'hffff_ffff); + assert (int'(L1sb1 | '1) == 32'hffff_ffff); + assert (int'(L2b00 | '1) == 32'hffff_ffff); + assert (int'(L2b01 | '1) == 32'hffff_ffff); + assert (int'(L2b10 | '1) == 32'hffff_ffff); + assert (int'(L2b11 | '1) == 32'hffff_ffff); + assert (int'(L2sb00 | '1) == 32'hffff_ffff); + assert (int'(L2sb01 | '1) == 32'hffff_ffff); + assert (int'(L2sb10 | '1) == 32'hffff_ffff); + assert (int'(L2sb11 | '1) == 32'hffff_ffff); + + assert (s12bit_packed_struct_t'(L1b0 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L1b1 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L1sb0 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L1sb1 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L2b00 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L2b01 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L2b10 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L2b11 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L2sb00 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L2sb01 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L2sb10 | '1) == 12'hfff); + assert (s12bit_packed_struct_t'(L2sb11 | '1) == 12'hfff); + assert (3'(L1b0 | '0) == 3'b000); assert (3'(L1b1 | '0) == 3'b001); assert (3'(L1sb0 | '0) == 3'b000); @@ -84,6 +187,58 @@ module top; assert (3'(L2sb10 | '0) == 3'b010); assert (3'(L2sb11 | '0) == 3'b011); + assert (u3bit_t'(L1b0 | '0) == 3'b000); + assert (u3bit_t'(L1b1 | '0) == 3'b001); + assert (u3bit_t'(L1sb0 | '0) == 3'b000); + assert (u3bit_t'(L1sb1 | '0) == 3'b001); + assert (u3bit_t'(L2b00 | '0) == 3'b000); + assert (u3bit_t'(L2b01 | '0) == 3'b001); + assert (u3bit_t'(L2b10 | '0) == 3'b010); + assert (u3bit_t'(L2b11 | '0) == 3'b011); + assert (u3bit_t'(L2sb00 | '0) == 3'b000); + assert (u3bit_t'(L2sb01 | '0) == 3'b001); + assert (u3bit_t'(L2sb10 | '0) == 3'b010); + assert (u3bit_t'(L2sb11 | '0) == 3'b011); + + assert (byte'(L1b0 | '0) == 8'h00); + assert (byte'(L1b1 | '0) == 8'h01); + assert (byte'(L1sb0 | '0) == 8'h00); + assert (byte'(L1sb1 | '0) == 8'h01); + assert (byte'(L2b00 | '0) == 8'h00); + assert (byte'(L2b01 | '0) == 8'h01); + assert (byte'(L2b10 | '0) == 8'h02); + assert (byte'(L2b11 | '0) == 8'h03); + assert (byte'(L2sb00 | '0) == 8'h00); + assert (byte'(L2sb01 | '0) == 8'h01); + assert (byte'(L2sb10 | '0) == 8'h02); + assert (byte'(L2sb11 | '0) == 8'h03); + + assert (int'(L1b0 | '0) == 32'h0000_0000); + assert (int'(L1b1 | '0) == 32'h0000_0001); + assert (int'(L1sb0 | '0) == 32'h0000_0000); + assert (int'(L1sb1 | '0) == 32'h0000_0001); + assert (int'(L2b00 | '0) == 32'h0000_0000); + assert (int'(L2b01 | '0) == 32'h0000_0001); + assert (int'(L2b10 | '0) == 32'h0000_0002); + assert (int'(L2b11 | '0) == 32'h0000_0003); + assert (int'(L2sb00 | '0) == 32'h0000_0000); + assert (int'(L2sb01 | '0) == 32'h0000_0001); + assert (int'(L2sb10 | '0) == 32'h0000_0002); + assert (int'(L2sb11 | '0) == 32'h0000_0003); + + assert (s12bit_packed_struct_t'(L1b0 | '0) == 12'h000); + assert (s12bit_packed_struct_t'(L1b1 | '0) == 12'h001); + assert (s12bit_packed_struct_t'(L1sb0 | '0) == 12'h000); + assert (s12bit_packed_struct_t'(L1sb1 | '0) == 12'h001); + assert (s12bit_packed_struct_t'(L2b00 | '0) == 12'h000); + assert (s12bit_packed_struct_t'(L2b01 | '0) == 12'h001); + assert (s12bit_packed_struct_t'(L2b10 | '0) == 12'h002); + assert (s12bit_packed_struct_t'(L2b11 | '0) == 12'h003); + assert (s12bit_packed_struct_t'(L2sb00 | '0) == 12'h000); + assert (s12bit_packed_struct_t'(L2sb01 | '0) == 12'h001); + assert (s12bit_packed_struct_t'(L2sb10 | '0) == 12'h002); + assert (s12bit_packed_struct_t'(L2sb11 | '0) == 12'h003); + assert (3'(y ? L1b0 : '1) == 3'b000); assert (3'(y ? L1b1 : '1) == 3'b001); assert (3'(y ? L1sb0 : '1) == 3'b000); @@ -97,6 +252,58 @@ module top; assert (3'(y ? L2sb10 : '1) == 3'b010); assert (3'(y ? L2sb11 : '1) == 3'b011); + assert (u3bit_t'(y ? L1b0 : '1) == 3'b000); + assert (u3bit_t'(y ? L1b1 : '1) == 3'b001); + assert (u3bit_t'(y ? L1sb0 : '1) == 3'b000); + assert (u3bit_t'(y ? L1sb1 : '1) == 3'b001); + assert (u3bit_t'(y ? L2b00 : '1) == 3'b000); + assert (u3bit_t'(y ? L2b01 : '1) == 3'b001); + assert (u3bit_t'(y ? L2b10 : '1) == 3'b010); + assert (u3bit_t'(y ? L2b11 : '1) == 3'b011); + assert (u3bit_t'(y ? L2sb00 : '1) == 3'b000); + assert (u3bit_t'(y ? L2sb01 : '1) == 3'b001); + assert (u3bit_t'(y ? L2sb10 : '1) == 3'b010); + assert (u3bit_t'(y ? L2sb11 : '1) == 3'b011); + + assert (byte'(y ? L1b0 : '1) == 8'h00); + assert (byte'(y ? L1b1 : '1) == 8'h01); + assert (byte'(y ? L1sb0 : '1) == 8'h00); + assert (byte'(y ? L1sb1 : '1) == 8'h01); + assert (byte'(y ? L2b00 : '1) == 8'h00); + assert (byte'(y ? L2b01 : '1) == 8'h01); + assert (byte'(y ? L2b10 : '1) == 8'h02); + assert (byte'(y ? L2b11 : '1) == 8'h03); + assert (byte'(y ? L2sb00 : '1) == 8'h00); + assert (byte'(y ? L2sb01 : '1) == 8'h01); + assert (byte'(y ? L2sb10 : '1) == 8'h02); + assert (byte'(y ? L2sb11 : '1) == 8'h03); + + assert (int'(y ? L1b0 : '1) == 32'h0000_0000); + assert (int'(y ? L1b1 : '1) == 32'h0000_0001); + assert (int'(y ? L1sb0 : '1) == 32'h0000_0000); + assert (int'(y ? L1sb1 : '1) == 32'h0000_0001); + assert (int'(y ? L2b00 : '1) == 32'h0000_0000); + assert (int'(y ? L2b01 : '1) == 32'h0000_0001); + assert (int'(y ? L2b10 : '1) == 32'h0000_0002); + assert (int'(y ? L2b11 : '1) == 32'h0000_0003); + assert (int'(y ? L2sb00 : '1) == 32'h0000_0000); + assert (int'(y ? L2sb01 : '1) == 32'h0000_0001); + assert (int'(y ? L2sb10 : '1) == 32'h0000_0002); + assert (int'(y ? L2sb11 : '1) == 32'h0000_0003); + + assert (s12bit_packed_struct_t'(y ? L1b0 : '1) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1b1 : '1) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L1sb0 : '1) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1sb1 : '1) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2b00 : '1) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2b01 : '1) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2b10 : '1) == 12'h002); + assert (s12bit_packed_struct_t'(y ? L2b11 : '1) == 12'h003); + assert (s12bit_packed_struct_t'(y ? L2sb00 : '1) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2sb01 : '1) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2sb10 : '1) == 12'h002); + assert (s12bit_packed_struct_t'(y ? L2sb11 : '1) == 12'h003); + assert (3'(y ? L1b0 : '0) == 3'b000); assert (3'(y ? L1b1 : '0) == 3'b001); assert (3'(y ? L1sb0 : '0) == 3'b000); @@ -110,6 +317,58 @@ module top; assert (3'(y ? L2sb10 : '0) == 3'b010); assert (3'(y ? L2sb11 : '0) == 3'b011); + assert (u3bit_t'(y ? L1b0 : '0) == 3'b000); + assert (u3bit_t'(y ? L1b1 : '0) == 3'b001); + assert (u3bit_t'(y ? L1sb0 : '0) == 3'b000); + assert (u3bit_t'(y ? L1sb1 : '0) == 3'b001); + assert (u3bit_t'(y ? L2b00 : '0) == 3'b000); + assert (u3bit_t'(y ? L2b01 : '0) == 3'b001); + assert (u3bit_t'(y ? L2b10 : '0) == 3'b010); + assert (u3bit_t'(y ? L2b11 : '0) == 3'b011); + assert (u3bit_t'(y ? L2sb00 : '0) == 3'b000); + assert (u3bit_t'(y ? L2sb01 : '0) == 3'b001); + assert (u3bit_t'(y ? L2sb10 : '0) == 3'b010); + assert (u3bit_t'(y ? L2sb11 : '0) == 3'b011); + + assert (byte'(y ? L1b0 : '0) == 8'h00); + assert (byte'(y ? L1b1 : '0) == 8'h01); + assert (byte'(y ? L1sb0 : '0) == 8'h00); + assert (byte'(y ? L1sb1 : '0) == 8'h01); + assert (byte'(y ? L2b00 : '0) == 8'h00); + assert (byte'(y ? L2b01 : '0) == 8'h01); + assert (byte'(y ? L2b10 : '0) == 8'h02); + assert (byte'(y ? L2b11 : '0) == 8'h03); + assert (byte'(y ? L2sb00 : '0) == 8'h00); + assert (byte'(y ? L2sb01 : '0) == 8'h01); + assert (byte'(y ? L2sb10 : '0) == 8'h02); + assert (byte'(y ? L2sb11 : '0) == 8'h03); + + assert (int'(y ? L1b0 : '0) == 32'h0000_0000); + assert (int'(y ? L1b1 : '0) == 32'h0000_0001); + assert (int'(y ? L1sb0 : '0) == 32'h0000_0000); + assert (int'(y ? L1sb1 : '0) == 32'h0000_0001); + assert (int'(y ? L2b00 : '0) == 32'h0000_0000); + assert (int'(y ? L2b01 : '0) == 32'h0000_0001); + assert (int'(y ? L2b10 : '0) == 32'h0000_0002); + assert (int'(y ? L2b11 : '0) == 32'h0000_0003); + assert (int'(y ? L2sb00 : '0) == 32'h0000_0000); + assert (int'(y ? L2sb01 : '0) == 32'h0000_0001); + assert (int'(y ? L2sb10 : '0) == 32'h0000_0002); + assert (int'(y ? L2sb11 : '0) == 32'h0000_0003); + + assert (s12bit_packed_struct_t'(y ? L1b0 : '0) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1b1 : '0) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L1sb0 : '0) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1sb1 : '0) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2b00 : '0) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2b01 : '0) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2b10 : '0) == 12'h002); + assert (s12bit_packed_struct_t'(y ? L2b11 : '0) == 12'h003); + assert (s12bit_packed_struct_t'(y ? L2sb00 : '0) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2sb01 : '0) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2sb10 : '0) == 12'h002); + assert (s12bit_packed_struct_t'(y ? L2sb11 : '0) == 12'h003); + assert (3'(y ? L1b0 : 1'sb0) == 3'b000); assert (3'(y ? L1b1 : 1'sb0) == 3'b001); assert (3'(y ? L1sb0 : 1'sb0) == 3'b000); @@ -123,6 +382,123 @@ module top; assert (3'(y ? L2sb10 : 1'sb0) == 3'b110); assert (3'(y ? L2sb11 : 1'sb0) == 3'b111); + assert (u3bit_t'(y ? L1b0 : 1'sb0) == 3'b000); + assert (u3bit_t'(y ? L1b1 : 1'sb0) == 3'b001); + assert (u3bit_t'(y ? L1sb0 : 1'sb0) == 3'b000); + assert (u3bit_t'(y ? L1sb1 : 1'sb0) == 3'b111); + assert (u3bit_t'(y ? L2b00 : 1'sb0) == 3'b000); + assert (u3bit_t'(y ? L2b01 : 1'sb0) == 3'b001); + assert (u3bit_t'(y ? L2b10 : 1'sb0) == 3'b010); + assert (u3bit_t'(y ? L2b11 : 1'sb0) == 3'b011); + assert (u3bit_t'(y ? L2sb00 : 1'sb0) == 3'b000); + assert (u3bit_t'(y ? L2sb01 : 1'sb0) == 3'b001); + assert (u3bit_t'(y ? L2sb10 : 1'sb0) == 3'b110); + assert (u3bit_t'(y ? L2sb11 : 1'sb0) == 3'b111); + + assert (byte'(y ? L1b0 : 1'sb0) == 8'h00); + assert (byte'(y ? L1b1 : 1'sb0) == 8'h01); + assert (byte'(y ? L1sb0 : 1'sb0) == 8'h00); + assert (byte'(y ? L1sb1 : 1'sb0) == 8'hff); + assert (byte'(y ? L2b00 : 1'sb0) == 8'h00); + assert (byte'(y ? L2b01 : 1'sb0) == 8'h01); + assert (byte'(y ? L2b10 : 1'sb0) == 8'h02); + assert (byte'(y ? L2b11 : 1'sb0) == 8'h03); + assert (byte'(y ? L2sb00 : 1'sb0) == 8'h00); + assert (byte'(y ? L2sb01 : 1'sb0) == 8'h01); + assert (byte'(y ? L2sb10 : 1'sb0) == 8'hfe); + assert (byte'(y ? L2sb11 : 1'sb0) == 8'hff); + + assert (int'(y ? L1b0 : 1'sb0) == 32'h0000_0000); + assert (int'(y ? L1b1 : 1'sb0) == 32'h0000_0001); + assert (int'(y ? L1sb0 : 1'sb0) == 32'h0000_0000); + assert (int'(y ? L1sb1 : 1'sb0) == 32'hffff_ffff); + assert (int'(y ? L2b00 : 1'sb0) == 32'h0000_0000); + assert (int'(y ? L2b01 : 1'sb0) == 32'h0000_0001); + assert (int'(y ? L2b10 : 1'sb0) == 32'h0000_0002); + assert (int'(y ? L2b11 : 1'sb0) == 32'h0000_0003); + assert (int'(y ? L2sb00 : 1'sb0) == 32'h0000_0000); + assert (int'(y ? L2sb01 : 1'sb0) == 32'h0000_0001); + assert (int'(y ? L2sb10 : 1'sb0) == 32'hffff_fffe); + assert (int'(y ? L2sb11 : 1'sb0) == 32'hffff_ffff); + + assert (s12bit_packed_struct_t'(y ? L1b0 : 1'sb0) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1b1 : 1'sb0) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L1sb0 : 1'sb0) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1sb1 : 1'sb0) == 12'hfff); + assert (s12bit_packed_struct_t'(y ? L2b00 : 1'sb0) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2b01 : 1'sb0) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2b10 : 1'sb0) == 12'h002); + assert (s12bit_packed_struct_t'(y ? L2b11 : 1'sb0) == 12'h003); + assert (s12bit_packed_struct_t'(y ? L2sb00 : 1'sb0) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2sb01 : 1'sb0) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2sb10 : 1'sb0) == 12'hffe); + assert (s12bit_packed_struct_t'(y ? L2sb11 : 1'sb0) == 12'hfff); + + assert (3'(y ? L1b0 : s1bit_t'(0)) == 3'b000); + assert (3'(y ? L1b1 : s1bit_t'(0)) == 3'b001); + assert (3'(y ? L1sb0 : s1bit_t'(0)) == 3'b000); + assert (3'(y ? L1sb1 : s1bit_t'(0)) == 3'b111); + assert (3'(y ? L2b00 : s1bit_t'(0)) == 3'b000); + assert (3'(y ? L2b01 : s1bit_t'(0)) == 3'b001); + assert (3'(y ? L2b10 : s1bit_t'(0)) == 3'b010); + assert (3'(y ? L2b11 : s1bit_t'(0)) == 3'b011); + assert (3'(y ? L2sb00 : s1bit_t'(0)) == 3'b000); + assert (3'(y ? L2sb01 : s1bit_t'(0)) == 3'b001); + assert (3'(y ? L2sb10 : s1bit_t'(0)) == 3'b110); + assert (3'(y ? L2sb11 : s1bit_t'(0)) == 3'b111); + + assert (u3bit_t'(y ? L1b0 : s1bit_t'(0)) == 3'b000); + assert (u3bit_t'(y ? L1b1 : s1bit_t'(0)) == 3'b001); + assert (u3bit_t'(y ? L1sb0 : s1bit_t'(0)) == 3'b000); + assert (u3bit_t'(y ? L1sb1 : s1bit_t'(0)) == 3'b111); + assert (u3bit_t'(y ? L2b00 : s1bit_t'(0)) == 3'b000); + assert (u3bit_t'(y ? L2b01 : s1bit_t'(0)) == 3'b001); + assert (u3bit_t'(y ? L2b10 : s1bit_t'(0)) == 3'b010); + assert (u3bit_t'(y ? L2b11 : s1bit_t'(0)) == 3'b011); + assert (u3bit_t'(y ? L2sb00 : s1bit_t'(0)) == 3'b000); + assert (u3bit_t'(y ? L2sb01 : s1bit_t'(0)) == 3'b001); + assert (u3bit_t'(y ? L2sb10 : s1bit_t'(0)) == 3'b110); + assert (u3bit_t'(y ? L2sb11 : s1bit_t'(0)) == 3'b111); + + assert (byte'(y ? L1b0 : s1bit_t'(0)) == 8'h00); + assert (byte'(y ? L1b1 : s1bit_t'(0)) == 8'h01); + assert (byte'(y ? L1sb0 : s1bit_t'(0)) == 8'h00); + assert (byte'(y ? L1sb1 : s1bit_t'(0)) == 8'hff); + assert (byte'(y ? L2b00 : s1bit_t'(0)) == 8'h00); + assert (byte'(y ? L2b01 : s1bit_t'(0)) == 8'h01); + assert (byte'(y ? L2b10 : s1bit_t'(0)) == 8'h02); + assert (byte'(y ? L2b11 : s1bit_t'(0)) == 8'h03); + assert (byte'(y ? L2sb00 : s1bit_t'(0)) == 8'h00); + assert (byte'(y ? L2sb01 : s1bit_t'(0)) == 8'h01); + assert (byte'(y ? L2sb10 : s1bit_t'(0)) == 8'hfe); + assert (byte'(y ? L2sb11 : s1bit_t'(0)) == 8'hff); + + assert (int'(y ? L1b0 : s1bit_t'(0)) == 32'h0000_0000); + assert (int'(y ? L1b1 : s1bit_t'(0)) == 32'h0000_0001); + assert (int'(y ? L1sb0 : s1bit_t'(0)) == 32'h0000_0000); + assert (int'(y ? L1sb1 : s1bit_t'(0)) == 32'hffff_ffff); + assert (int'(y ? L2b00 : s1bit_t'(0)) == 32'h0000_0000); + assert (int'(y ? L2b01 : s1bit_t'(0)) == 32'h0000_0001); + assert (int'(y ? L2b10 : s1bit_t'(0)) == 32'h0000_0002); + assert (int'(y ? L2b11 : s1bit_t'(0)) == 32'h0000_0003); + assert (int'(y ? L2sb00 : s1bit_t'(0)) == 32'h0000_0000); + assert (int'(y ? L2sb01 : s1bit_t'(0)) == 32'h0000_0001); + assert (int'(y ? L2sb10 : s1bit_t'(0)) == 32'hffff_fffe); + assert (int'(y ? L2sb11 : s1bit_t'(0)) == 32'hffff_ffff); + + assert (s12bit_packed_struct_t'(y ? L1b0 : s1bit_t'(0)) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1b1 : s1bit_t'(0)) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L1sb0 : s1bit_t'(0)) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1sb1 : s1bit_t'(0)) == 12'hfff); + assert (s12bit_packed_struct_t'(y ? L2b00 : s1bit_t'(0)) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2b01 : s1bit_t'(0)) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2b10 : s1bit_t'(0)) == 12'h002); + assert (s12bit_packed_struct_t'(y ? L2b11 : s1bit_t'(0)) == 12'h003); + assert (s12bit_packed_struct_t'(y ? L2sb00 : s1bit_t'(0)) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2sb01 : s1bit_t'(0)) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2sb10 : s1bit_t'(0)) == 12'hffe); + assert (s12bit_packed_struct_t'(y ? L2sb11 : s1bit_t'(0)) == 12'hfff); + assert (3'(y ? L1b0 : 1'sb1) == 3'b000); assert (3'(y ? L1b1 : 1'sb1) == 3'b001); assert (3'(y ? L1sb0 : 1'sb1) == 3'b000); @@ -136,5 +512,122 @@ module top; assert (3'(y ? L2sb10 : 1'sb1) == 3'b110); assert (3'(y ? L2sb11 : 1'sb1) == 3'b111); + assert (u3bit_t'(y ? L1b0 : 1'sb1) == 3'b000); + assert (u3bit_t'(y ? L1b1 : 1'sb1) == 3'b001); + assert (u3bit_t'(y ? L1sb0 : 1'sb1) == 3'b000); + assert (u3bit_t'(y ? L1sb1 : 1'sb1) == 3'b111); + assert (u3bit_t'(y ? L2b00 : 1'sb1) == 3'b000); + assert (u3bit_t'(y ? L2b01 : 1'sb1) == 3'b001); + assert (u3bit_t'(y ? L2b10 : 1'sb1) == 3'b010); + assert (u3bit_t'(y ? L2b11 : 1'sb1) == 3'b011); + assert (u3bit_t'(y ? L2sb00 : 1'sb1) == 3'b000); + assert (u3bit_t'(y ? L2sb01 : 1'sb1) == 3'b001); + assert (u3bit_t'(y ? L2sb10 : 1'sb1) == 3'b110); + assert (u3bit_t'(y ? L2sb11 : 1'sb1) == 3'b111); + + assert (byte'(y ? L1b0 : 1'sb1) == 8'h00); + assert (byte'(y ? L1b1 : 1'sb1) == 8'h01); + assert (byte'(y ? L1sb0 : 1'sb1) == 8'h00); + assert (byte'(y ? L1sb1 : 1'sb1) == 8'hff); + assert (byte'(y ? L2b00 : 1'sb1) == 8'h00); + assert (byte'(y ? L2b01 : 1'sb1) == 8'h01); + assert (byte'(y ? L2b10 : 1'sb1) == 8'h02); + assert (byte'(y ? L2b11 : 1'sb1) == 8'h03); + assert (byte'(y ? L2sb00 : 1'sb1) == 8'h00); + assert (byte'(y ? L2sb01 : 1'sb1) == 8'h01); + assert (byte'(y ? L2sb10 : 1'sb1) == 8'hfe); + assert (byte'(y ? L2sb11 : 1'sb1) == 8'hff); + + assert (int'(y ? L1b0 : 1'sb1) == 32'h0000_0000); + assert (int'(y ? L1b1 : 1'sb1) == 32'h0000_0001); + assert (int'(y ? L1sb0 : 1'sb1) == 32'h0000_0000); + assert (int'(y ? L1sb1 : 1'sb1) == 32'hffff_ffff); + assert (int'(y ? L2b00 : 1'sb1) == 32'h0000_0000); + assert (int'(y ? L2b01 : 1'sb1) == 32'h0000_0001); + assert (int'(y ? L2b10 : 1'sb1) == 32'h0000_0002); + assert (int'(y ? L2b11 : 1'sb1) == 32'h0000_0003); + assert (int'(y ? L2sb00 : 1'sb1) == 32'h0000_0000); + assert (int'(y ? L2sb01 : 1'sb1) == 32'h0000_0001); + assert (int'(y ? L2sb10 : 1'sb1) == 32'hffff_fffe); + assert (int'(y ? L2sb11 : 1'sb1) == 32'hffff_ffff); + + assert (s12bit_packed_struct_t'(y ? L1b0 : 1'sb1) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1b1 : 1'sb1) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L1sb0 : 1'sb1) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1sb1 : 1'sb1) == 12'hfff); + assert (s12bit_packed_struct_t'(y ? L2b00 : 1'sb1) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2b01 : 1'sb1) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2b10 : 1'sb1) == 12'h002); + assert (s12bit_packed_struct_t'(y ? L2b11 : 1'sb1) == 12'h003); + assert (s12bit_packed_struct_t'(y ? L2sb00 : 1'sb1) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2sb01 : 1'sb1) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2sb10 : 1'sb1) == 12'hffe); + assert (s12bit_packed_struct_t'(y ? L2sb11 : 1'sb1) == 12'hfff); + + assert (3'(y ? L1b0 : s1bit_t'(1)) == 3'b000); + assert (3'(y ? L1b1 : s1bit_t'(1)) == 3'b001); + assert (3'(y ? L1sb0 : s1bit_t'(1)) == 3'b000); + assert (3'(y ? L1sb1 : s1bit_t'(1)) == 3'b111); + assert (3'(y ? L2b00 : s1bit_t'(1)) == 3'b000); + assert (3'(y ? L2b01 : s1bit_t'(1)) == 3'b001); + assert (3'(y ? L2b10 : s1bit_t'(1)) == 3'b010); + assert (3'(y ? L2b11 : s1bit_t'(1)) == 3'b011); + assert (3'(y ? L2sb00 : s1bit_t'(1)) == 3'b000); + assert (3'(y ? L2sb01 : s1bit_t'(1)) == 3'b001); + assert (3'(y ? L2sb10 : s1bit_t'(1)) == 3'b110); + assert (3'(y ? L2sb11 : s1bit_t'(1)) == 3'b111); + + assert (u3bit_t'(y ? L1b0 : s1bit_t'(1)) == 3'b000); + assert (u3bit_t'(y ? L1b1 : s1bit_t'(1)) == 3'b001); + assert (u3bit_t'(y ? L1sb0 : s1bit_t'(1)) == 3'b000); + assert (u3bit_t'(y ? L1sb1 : s1bit_t'(1)) == 3'b111); + assert (u3bit_t'(y ? L2b00 : s1bit_t'(1)) == 3'b000); + assert (u3bit_t'(y ? L2b01 : s1bit_t'(1)) == 3'b001); + assert (u3bit_t'(y ? L2b10 : s1bit_t'(1)) == 3'b010); + assert (u3bit_t'(y ? L2b11 : s1bit_t'(1)) == 3'b011); + assert (u3bit_t'(y ? L2sb00 : s1bit_t'(1)) == 3'b000); + assert (u3bit_t'(y ? L2sb01 : s1bit_t'(1)) == 3'b001); + assert (u3bit_t'(y ? L2sb10 : s1bit_t'(1)) == 3'b110); + assert (u3bit_t'(y ? L2sb11 : s1bit_t'(1)) == 3'b111); + + assert (byte'(y ? L1b0 : s1bit_t'(1)) == 8'h00); + assert (byte'(y ? L1b1 : s1bit_t'(1)) == 8'h01); + assert (byte'(y ? L1sb0 : s1bit_t'(1)) == 8'h00); + assert (byte'(y ? L1sb1 : s1bit_t'(1)) == 8'hff); + assert (byte'(y ? L2b00 : s1bit_t'(1)) == 8'h00); + assert (byte'(y ? L2b01 : s1bit_t'(1)) == 8'h01); + assert (byte'(y ? L2b10 : s1bit_t'(1)) == 8'h02); + assert (byte'(y ? L2b11 : s1bit_t'(1)) == 8'h03); + assert (byte'(y ? L2sb00 : s1bit_t'(1)) == 8'h00); + assert (byte'(y ? L2sb01 : s1bit_t'(1)) == 8'h01); + assert (byte'(y ? L2sb10 : s1bit_t'(1)) == 8'hfe); + assert (byte'(y ? L2sb11 : s1bit_t'(1)) == 8'hff); + + assert (int'(y ? L1b0 : s1bit_t'(1)) == 32'h0000_0000); + assert (int'(y ? L1b1 : s1bit_t'(1)) == 32'h0000_0001); + assert (int'(y ? L1sb0 : s1bit_t'(1)) == 32'h0000_0000); + assert (int'(y ? L1sb1 : s1bit_t'(1)) == 32'hffff_ffff); + assert (int'(y ? L2b00 : s1bit_t'(1)) == 32'h0000_0000); + assert (int'(y ? L2b01 : s1bit_t'(1)) == 32'h0000_0001); + assert (int'(y ? L2b10 : s1bit_t'(1)) == 32'h0000_0002); + assert (int'(y ? L2b11 : s1bit_t'(1)) == 32'h0000_0003); + assert (int'(y ? L2sb00 : s1bit_t'(1)) == 32'h0000_0000); + assert (int'(y ? L2sb01 : s1bit_t'(1)) == 32'h0000_0001); + assert (int'(y ? L2sb10 : s1bit_t'(1)) == 32'hffff_fffe); + assert (int'(y ? L2sb11 : s1bit_t'(1)) == 32'hffff_ffff); + + assert (s12bit_packed_struct_t'(y ? L1b0 : s1bit_t'(1)) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1b1 : s1bit_t'(1)) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L1sb0 : s1bit_t'(1)) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L1sb1 : s1bit_t'(1)) == 12'hfff); + assert (s12bit_packed_struct_t'(y ? L2b00 : s1bit_t'(1)) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2b01 : s1bit_t'(1)) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2b10 : s1bit_t'(1)) == 12'h002); + assert (s12bit_packed_struct_t'(y ? L2b11 : s1bit_t'(1)) == 12'h003); + assert (s12bit_packed_struct_t'(y ? L2sb00 : s1bit_t'(1)) == 12'h000); + assert (s12bit_packed_struct_t'(y ? L2sb01 : s1bit_t'(1)) == 12'h001); + assert (s12bit_packed_struct_t'(y ? L2sb10 : s1bit_t'(1)) == 12'hffe); + assert (s12bit_packed_struct_t'(y ? L2sb11 : s1bit_t'(1)) == 12'hfff); + end endmodule