mirror of
https://github.com/YosysHQ/yosys
synced 2026-06-26 10:38:47 +00:00
commit
e5713bc7db
125 changed files with 17609 additions and 746 deletions
|
|
@ -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
|
||||
|
|
|
|||
3
.github/actions/setup-build-env/action.yml
vendored
3
.github/actions/setup-build-env/action.yml
vendored
|
|
@ -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'
|
||||
|
|
|
|||
32
.github/workflows/prepare-docs.yml
vendored
32
.github/workflows/prepare-docs.yml
vendored
|
|
@ -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 }}
|
||||
|
|
|
|||
10
.github/workflows/test-build.yml
vendored
10
.github/workflows/test-build.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
1
.github/workflows/test-verific.yml
vendored
1
.github/workflows/test-verific.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
136
.github/workflows/wheels.yml
vendored
Normal file
136
.github/workflows/wheels.yml
vendored
Normal file
|
|
@ -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/' }}
|
||||
44
.github/workflows/wheels/_run_cibw_linux.py
vendored
Normal file
44
.github/workflows/wheels/_run_cibw_linux.py
vendored
Normal file
|
|
@ -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)
|
||||
23
.github/workflows/wheels/cibw_before_all.sh
vendored
Normal file
23
.github/workflows/wheels/cibw_before_all.sh
vendored
Normal file
|
|
@ -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
|
||||
34
.github/workflows/wheels/cibw_before_build.sh
vendored
Normal file
34
.github/workflows/wheels/cibw_before_build.sh
vendored
Normal file
|
|
@ -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
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -45,4 +45,10 @@ __pycache__
|
|||
/tests/unit/bintest/
|
||||
/tests/unit/objtest/
|
||||
/tests/ystests
|
||||
/result
|
||||
/result
|
||||
/dist
|
||||
/*.egg-info
|
||||
/build
|
||||
/venv
|
||||
/boost
|
||||
/ffi
|
||||
|
|
|
|||
25
CHANGELOG
25
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
|
||||
|
|
|
|||
34
Makefile
34
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 \
|
||||
|
|
|
|||
2
abc
2
abc
|
|
@ -1 +1 @@
|
|||
Subproject commit 2188bc71228b0788569d83ad2b7e7b91ca5dcc09
|
||||
Subproject commit cac8f99eaa220a5e3db5caeb87cef0a975c953a2
|
||||
|
|
@ -18,32 +18,6 @@
|
|||
*
|
||||
*/
|
||||
|
||||
// https://stackoverflow.com/a/46137633
|
||||
#ifdef _MSC_VER
|
||||
#include <stdlib.h>
|
||||
#define bswap32 _byteswap_ulong
|
||||
#elif defined(__APPLE__)
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#define bswap32 OSSwapInt32
|
||||
#elif defined(__GNUC__)
|
||||
#define bswap32 __builtin_bswap32
|
||||
#else
|
||||
#include <cstdint>
|
||||
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<const char*>(&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<const char*>(&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<const char*>(&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<const char*>(&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<const char*>(&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<const char*>(&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();
|
||||
|
|
|
|||
1
backends/aiger2/Makefile.inc
Normal file
1
backends/aiger2/Makefile.inc
Normal file
|
|
@ -0,0 +1 @@
|
|||
OBJS += backends/aiger2/aiger.o
|
||||
1471
backends/aiger2/aiger.cc
Normal file
1471
backends/aiger2/aiger.cc
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1127,7 +1127,7 @@ struct fmt_part {
|
|||
}
|
||||
|
||||
case UNICHAR: {
|
||||
uint32_t codepoint = val.template get<uint32_t>();
|
||||
uint32_t codepoint = val.template zcast<32>().template get<uint32_t>();
|
||||
if (codepoint >= 0x10000)
|
||||
buf += (char)(0xf0 | (codepoint >> 18));
|
||||
else if (codepoint >= 0x800)
|
||||
|
|
|
|||
4
backends/functional/Makefile.inc
Normal file
4
backends/functional/Makefile.inc
Normal file
|
|
@ -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
|
||||
277
backends/functional/cxx.cc
Normal file
277
backends/functional/cxx.cc
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* 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 <ctype.h>
|
||||
|
||||
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<typename Id> struct CxxScope : public Functional::Scope<Id> {
|
||||
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<IdString, CxxType> types;
|
||||
CxxScope<IdString> 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 <typename T> 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<class NodePrinter> struct CxxPrintVisitor : public Functional::AbstractVisitor<void> {
|
||||
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<typename... Args> void print(const char *fmt, Args&&... args) {
|
||||
f.print_with(np, fmt, std::forward<Args>(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<int>().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<Signal<{}>, {}> mem;\n", state->sort.data_width(), 1<<state->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<int> 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<std::string> 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
|
||||
418
backends/functional/cxx_runtime/sim.h
Normal file
418
backends/functional/cxx_runtime/sim.h
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* 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 <array>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
template<size_t n>
|
||||
class Signal {
|
||||
template<size_t m> friend class Signal;
|
||||
std::array<bool, n> _bits;
|
||||
public:
|
||||
Signal() { }
|
||||
Signal(uint32_t val)
|
||||
{
|
||||
for(size_t i = 0; i < n; i++)
|
||||
if(i < 32)
|
||||
_bits[i] = val & (1<<i);
|
||||
else
|
||||
_bits[i] = false;
|
||||
}
|
||||
|
||||
Signal(std::initializer_list<uint32_t> 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<<i);
|
||||
k += 32;
|
||||
}
|
||||
for(; k < n; k++)
|
||||
_bits[k] = false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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<<i);
|
||||
k += 32;
|
||||
}
|
||||
for(; k < n; k++)
|
||||
ret._bits[k] = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static Signal from_signed(int32_t val)
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
if(i < 32)
|
||||
ret._bits[i] = val & (1<<i);
|
||||
else
|
||||
ret._bits[i] = val < 0;
|
||||
return ret;
|
||||
}
|
||||
static Signal repeat(bool b)
|
||||
{
|
||||
Signal<n> 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<size_t m>
|
||||
Signal<m> slice(size_t offset) const
|
||||
{
|
||||
Signal<m> 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<typename T>
|
||||
T as_numeric() const
|
||||
{
|
||||
T ret = 0;
|
||||
for(size_t i = 0; i < std::min<size_t>(sizeof(T) * 8, n); i++)
|
||||
if(_bits[i])
|
||||
ret |= ((T)1)<<i;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T as_numeric_clamped() const
|
||||
{
|
||||
for(size_t i = sizeof(T) * 8; i < n; i++)
|
||||
if(_bits[i])
|
||||
return ~((T)0);
|
||||
return as_numeric<T>();
|
||||
}
|
||||
|
||||
uint32_t as_int() const { return as_numeric<uint32_t>(); }
|
||||
|
||||
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<<b)-1)];
|
||||
return ret;
|
||||
}
|
||||
std::string as_string_b10() const {
|
||||
std::string ret;
|
||||
if(n < 4) return std::string() + (char)('0' + as_int());
|
||||
Signal<n> t = *this;
|
||||
Signal<n> 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<n> const &s) { return os << s.as_string(); }
|
||||
|
||||
Signal<n> operator ~() const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = !_bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator -() const
|
||||
{
|
||||
Signal<n> 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<n> operator +(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> 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<n> operator -(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> 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<n> operator *(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> 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<n> divmod(Signal<n> const &b, bool modulo) const
|
||||
{
|
||||
if(!b.any()) return 0;
|
||||
Signal<n> q = 0;
|
||||
Signal<n> 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<n> operator /(Signal<n> const &b) const { return divmod(b, false); }
|
||||
Signal<n> operator %(Signal<n> const &b) const { return divmod(b, true); }
|
||||
|
||||
bool operator ==(Signal<n> const &b) const
|
||||
{
|
||||
for(size_t i = 0; i < n; i++)
|
||||
if(_bits[i] != b._bits[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator >=(Signal<n> const &b) const
|
||||
{
|
||||
for(size_t i = n; i-- != 0; )
|
||||
if(_bits[i] != b._bits[i])
|
||||
return _bits[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator >(Signal<n> const &b) const
|
||||
{
|
||||
for(size_t i = n; i-- != 0; )
|
||||
if(_bits[i] != b._bits[i])
|
||||
return _bits[i];
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator !=(Signal<n> const &b) const { return !(*this == b); }
|
||||
bool operator <=(Signal<n> const &b) const { return b <= *this; }
|
||||
bool operator <(Signal<n> const &b) const { return b < *this; }
|
||||
|
||||
bool signed_greater_than(Signal<n> const &b) const
|
||||
{
|
||||
if(_bits[n-1] != b._bits[n-1])
|
||||
return b._bits[n-1];
|
||||
return *this > b;
|
||||
}
|
||||
|
||||
bool signed_greater_equal(Signal<n> const &b) const
|
||||
{
|
||||
if(_bits[n-1] != b._bits[n-1])
|
||||
return b._bits[n-1];
|
||||
return *this >= b;
|
||||
}
|
||||
|
||||
Signal<n> operator &(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = _bits[i] && b._bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator |(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = _bits[i] || b._bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator ^(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = _bits[i] != b._bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t nb>
|
||||
Signal<n> operator <<(Signal<nb> const &b) const
|
||||
{
|
||||
Signal<n> ret = 0;
|
||||
size_t amount = b.template as_numeric_clamped<size_t>();
|
||||
if(amount < n)
|
||||
std::copy(_bits.begin(), _bits.begin() + (n - amount), ret._bits.begin() + amount);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t nb>
|
||||
Signal<n> operator >>(Signal<nb> const &b) const
|
||||
{
|
||||
Signal<n> ret = 0;
|
||||
size_t amount = b.template as_numeric_clamped<size_t>();
|
||||
if(amount < n)
|
||||
std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t nb>
|
||||
Signal<n> arithmetic_shift_right(Signal<nb> const &b) const
|
||||
{
|
||||
Signal<n> ret = Signal::repeat(sign());
|
||||
size_t amount = b.template as_numeric_clamped<size_t>();
|
||||
if(amount < n)
|
||||
std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t m>
|
||||
Signal<n+m> concat(Signal<m> const& b) const
|
||||
{
|
||||
Signal<n + m> 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<size_t m>
|
||||
Signal<m> zero_extend() const
|
||||
{
|
||||
assert(m >= n);
|
||||
Signal<m> ret = 0;
|
||||
std::copy(_bits.begin(), _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t m>
|
||||
Signal<m> sign_extend() const
|
||||
{
|
||||
assert(m >= n);
|
||||
Signal<m> ret = Signal<m>::repeat(sign());
|
||||
std::copy(_bits.begin(), _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template<size_t a, size_t d>
|
||||
class Memory {
|
||||
std::array<Signal<d>, 1<<a> _contents;
|
||||
public:
|
||||
Memory() {}
|
||||
Memory(std::array<Signal<d>, 1<<a> const &contents) : _contents(contents) {}
|
||||
Signal<d> read(Signal<a> addr) const
|
||||
{
|
||||
return _contents[addr.template as_numeric<size_t>()];
|
||||
}
|
||||
Memory write(Signal<a> addr, Signal<d> data) const
|
||||
{
|
||||
Memory ret = *this;
|
||||
ret._contents[addr.template as_numeric<size_t>()] = data;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
295
backends/functional/smtlib.cc
Normal file
295
backends/functional/smtlib.cc
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* 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 <ctype.h>
|
||||
|
||||
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<int> {
|
||||
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<IdString> field_names;
|
||||
vector<Field> 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<typename Fn> 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<SExpr> {
|
||||
using Node = Functional::Node;
|
||||
std::function<SExpr(Node)> 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<SExpr> 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<<state->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<std::string> 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
|
||||
308
backends/functional/smtlib_rosette.cc
Normal file
308
backends/functional/smtlib_rosette.cc
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* 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 <ctype.h>
|
||||
|
||||
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<int> {
|
||||
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<IdString> field_names;
|
||||
vector<Field> 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<SExpr> 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<typename Fn> 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<SExpr> {
|
||||
using Node = Functional::Node;
|
||||
std::function<SExpr(Node)> 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<<state->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<std::string> 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
|
||||
156
backends/functional/test_generic.cc
Normal file
156
backends/functional/test_generic.cc
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* 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 <random>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
struct MemContentsTest {
|
||||
int addr_width, data_width;
|
||||
MemContents state;
|
||||
using addr_t = MemContents::addr_t;
|
||||
std::map<addr_t, RTLIL::Const> 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<<addr_width); addr++) {
|
||||
auto it = reference.find(addr);
|
||||
if(it != reference.end()) {
|
||||
if(state.count_range(addr, addr + 1) != 1) goto error;
|
||||
if(it->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<<addr_width); addr++) {
|
||||
if(addr % 8 == 0) printf("%.8x ", addr);
|
||||
auto it = reference.find(addr);
|
||||
bool ref_def = it != reference.end();
|
||||
RTLIL::Const ref_value = ref_def ? it->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<typename Rnd> void run(Rnd &rnd, int n) {
|
||||
std::uniform_int_distribution<addr_t> addr_dist(0, (1<<addr_width) - 1);
|
||||
std::poisson_distribution<addr_t> length_dist(10);
|
||||
std::uniform_int_distribution<uint64_t> data_dist(0, ((uint64_t)1<<data_width) - 1);
|
||||
while(n-- > 0) {
|
||||
addr_t low = addr_dist(rnd);
|
||||
//addr_t length = std::min((1<<addr_width) - low, length_dist(rnd));
|
||||
//addr_t high = low + length - 1;
|
||||
addr_t high = addr_dist(rnd);
|
||||
if(low > 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<std::string> 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
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 @@ elif covermode:
|
|||
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 @@ elif covermode:
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
3
docs/source/code_examples/.gitignore
vendored
3
docs/source/code_examples/.gitignore
vendored
|
|
@ -1,2 +1,5 @@
|
|||
*.dot
|
||||
*.pdf
|
||||
*.out
|
||||
*.log
|
||||
*.stat
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
<suppressed ~2 debug messages>
|
||||
|
||||
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.
|
||||
<suppressed ~5 debug messages>
|
||||
|
||||
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.
|
||||
<suppressed ~2 debug messages>
|
||||
|
||||
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.
|
||||
<suppressed ~1 debug messages>
|
||||
|
||||
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.
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
.PHONY: all dots
|
||||
all: dots
|
||||
.PHONY: all dots examples
|
||||
all: dots examples
|
||||
dots:
|
||||
examples:
|
||||
|
||||
.PHONY: test
|
||||
test: stubnets.so
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import os
|
|||
project = 'YosysHQ Yosys'
|
||||
author = 'YosysHQ GmbH'
|
||||
copyright ='2024 YosysHQ GmbH'
|
||||
yosys_ver = "0.45"
|
||||
yosys_ver = "0.46"
|
||||
|
||||
# select HTML theme
|
||||
html_theme = 'furo'
|
||||
|
|
|
|||
|
|
@ -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<ReturnType>`` or ``Functional::DefaultVisitor<ReturnType>``.
|
||||
The former will produce a compiler error if a case is unhandled, the latter will call ``default_handler(node)`` instead.
|
||||
Visitor methods should be marked as ``override`` to provide compiler errors if the arguments are wrong.
|
||||
|
||||
Utility classes
|
||||
-----------------
|
||||
|
||||
``functional.h`` also provides utility classes that are independent of the main FunctionalIR representation but are likely to be useful for backends.
|
||||
|
||||
``Functional::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<Id>`` takes a template argument that specifies a type that's used to uniquely distinguish variables.
|
||||
Typically this would be ``int`` (if variables are used for ``Functional::IR`` nodes) or ``IdString``.
|
||||
- The derived class should provide a constructor that calls ``reserve`` for every reserved word in the target language.
|
||||
- A method ``bool is_legal_character(char c, int index)`` has to be provided that returns ``true`` iff ``c`` is legal in an identifier at position ``index``.
|
||||
|
||||
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.
|
||||
|
|
@ -10,5 +10,6 @@ of interest for developers looking to customise Yosys builds.
|
|||
|
||||
extensions
|
||||
build_verific
|
||||
functional_ir
|
||||
test_suites
|
||||
|
||||
|
|
|
|||
2
frontends/aiger2/Makefile.inc
Normal file
2
frontends/aiger2/Makefile.inc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
OBJS += frontends/aiger2/xaiger.o
|
||||
473
frontends/aiger2/xaiger.cc
Normal file
473
frontends/aiger2/xaiger.cc
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) Martin Povišer <povik@cutebit.org>
|
||||
*
|
||||
* 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] <filename>\n");
|
||||
log("\n");
|
||||
log("Read a standard cell mapping from a XAIGER file into an existing module.\n");
|
||||
log("\n");
|
||||
log(" -module_name <name>\n");
|
||||
log(" name of the target module\n");
|
||||
log("\n");
|
||||
log(" -map2 <filename>\n");
|
||||
log(" read file with symbol information\n");
|
||||
log("\n");
|
||||
}
|
||||
|
||||
void read_sc_mapping(std::istream *&f, std::string filename, std::vector<std::string> 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<int> outputs;
|
||||
for (int i = 0; i < (int) O; i++) {
|
||||
int po;
|
||||
*f >> po;
|
||||
log_assert(f->get() == '\n');
|
||||
outputs.push_back(po);
|
||||
}
|
||||
|
||||
std::vector<std::pair<Cell *, Module *>> boxes;
|
||||
std::vector<bool> retained_boxes;
|
||||
std::vector<SigBit> 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<RTLIL::IdString> ins;
|
||||
};
|
||||
std::vector<MappingCell> 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<std::string> 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
|
||||
|
|
@ -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, ")");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -465,6 +465,9 @@ struct LibertyFrontend : public Frontend {
|
|||
log(" -setattr <attribute_name>\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<std::string> 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<std::string> 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<Wire *> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -450,6 +450,19 @@ void VerificImporter::import_attributes(dict<RTLIL::IdString, RTLIL::Const> &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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ struct CellTypes
|
|||
void setup_internals_eval()
|
||||
{
|
||||
std::vector<RTLIL::IdString> 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);
|
||||
|
|
|
|||
403
kernel/compute_graph.h
Normal file
403
kernel/compute_graph.h
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Jannis Harder <jix@yosyshq.com> <me@jix.one>
|
||||
*
|
||||
* 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 <tuple>
|
||||
#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<Fn> 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<Node> nodes;
|
||||
std::vector<int> args;
|
||||
dict<Key, int> keys_;
|
||||
dict<int, SparseAttr> sparse_attrs;
|
||||
|
||||
public:
|
||||
template<typename Graph>
|
||||
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<int>::const_iterator arg_indices_cbegin() const
|
||||
{
|
||||
Node const &node = deref();
|
||||
return graph_->args.cbegin() + node.arg_offset;
|
||||
}
|
||||
|
||||
std::vector<int>::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<ComputeGraph const>;
|
||||
|
||||
struct Ref : public BaseRef<ComputeGraph>
|
||||
{
|
||||
private:
|
||||
friend struct ComputeGraph;
|
||||
Ref(ComputeGraph *graph, int index) : BaseRef<ComputeGraph>(graph, index) {}
|
||||
Node &deref() const { this->check(); return this->graph_->nodes[this->index_]; }
|
||||
|
||||
public:
|
||||
Ref(BaseRef<ComputeGraph> 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<Key, int> 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<typename T>
|
||||
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<typename T>
|
||||
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<Ref> 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<Ref> args)
|
||||
{
|
||||
Ref added = add(function, std::move(attr));
|
||||
for (auto arg : args)
|
||||
added.append_arg(arg);
|
||||
return added;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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<int> 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<int> const &perm)
|
||||
{
|
||||
log_assert(perm.size() <= nodes.size());
|
||||
std::vector<int> 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<int> const &perm, std::vector<int> const &inv_perm)
|
||||
{
|
||||
log_assert(inv_perm.size() == nodes.size());
|
||||
std::vector<Node> new_nodes;
|
||||
new_nodes.reserve(perm.size());
|
||||
dict<int, SparseAttr> 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<int> 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<int>::const_iterator current, end;
|
||||
successor_enumerator(std::vector<int>::const_iterator current, std::vector<int>::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<int> const &dfs_indices() { return indices_; }
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
|
||||
#endif
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
949
kernel/drivertools.cc
Normal file
949
kernel/drivertools.cc
Normal file
|
|
@ -0,0 +1,949 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Jannis Harder <jix@yosyshq.com> <me@jix.one>
|
||||
*
|
||||
* 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<DriveBit> 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<DriveBitId> &seen = orient_undirected_seen;
|
||||
pool<DriveBitId> &drivers = orient_undirected_drivers;
|
||||
dict<DriveBitId, int> &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("<marker %d> [%d]", chunk.marker, chunk.offset));
|
||||
return log_str(stringf("<marker %d> [%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("<none x%d>", 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 = "<multiple";
|
||||
const char *sep = " ";
|
||||
for (auto const &single : chunk.multiple().multiple()) {
|
||||
str += sep;
|
||||
sep = ", ";
|
||||
str += log_signal(single);
|
||||
}
|
||||
str += ">";
|
||||
return log_str(str);
|
||||
}
|
||||
default:
|
||||
log_abort();
|
||||
}
|
||||
}
|
||||
|
||||
const char *log_signal(DriveSpec const &spec)
|
||||
{
|
||||
auto &chunks = spec.chunks();
|
||||
if (chunks.empty())
|
||||
return "{}";
|
||||
if (chunks.size() == 1)
|
||||
return log_signal(chunks[0]);
|
||||
|
||||
std::string str;
|
||||
const char *sep = "{ ";
|
||||
|
||||
for (auto i = chunks.rbegin(), end = chunks.rend(); i != end; ++i)
|
||||
{
|
||||
str += sep;
|
||||
sep = " ";
|
||||
str += log_signal(*i);
|
||||
}
|
||||
str += " }";
|
||||
|
||||
return log_str(str);
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
1332
kernel/drivertools.h
Normal file
1332
kernel/drivertools.h
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
|
|
|
|||
853
kernel/functional.cc
Normal file
853
kernel/functional.cc
Normal file
|
|
@ -0,0 +1,853 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* 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 <deque>
|
||||
|
||||
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<IRInput const*> IR::inputs(IdString kind) const {
|
||||
vector<IRInput const*> ret;
|
||||
for (const auto &[name, input] : _inputs)
|
||||
if(input.kind == kind)
|
||||
ret.push_back(&input);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<IROutput const*> IR::outputs(IdString kind) const {
|
||||
vector<IROutput const*> ret;
|
||||
for (const auto &[name, output] : _outputs)
|
||||
if(output.kind == kind)
|
||||
ret.push_back(&output);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<IRState const*> IR::states(IdString kind) const {
|
||||
vector<IRState const*> ret;
|
||||
for (const auto &[name, state] : _states)
|
||||
if(state.kind == kind)
|
||||
ret.push_back(&state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<IRInput const*> IR::all_inputs() const {
|
||||
vector<IRInput const*> ret;
|
||||
for (const auto &[name, input] : _inputs)
|
||||
ret.push_back(&input);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<IROutput const*> IR::all_outputs() const {
|
||||
vector<IROutput const*> ret;
|
||||
for (const auto &[name, output] : _outputs)
|
||||
ret.push_back(&output);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<IRState const*> IR::all_states() const {
|
||||
vector<IRState const*> ret;
|
||||
for (const auto &[name, state] : _states)
|
||||
ret.push_back(&state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct PrintVisitor : DefaultVisitor<std::string> {
|
||||
std::function<std::string(Node)> np;
|
||||
PrintVisitor(std::function<std::string(Node)> 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<std::string(Node)> 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<IdString, Node> 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<IdString, Node> 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<dict<IdString, Node>, Node> handle(IdString cellName, IdString cellType, dict<IdString, Const> parameters, dict<IdString, Node> 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<std::variant<DriveSpec, Cell *>> queue;
|
||||
dict<DriveSpec, Node> graph_nodes;
|
||||
dict<std::pair<Cell *, IdString>, Node> cell_outputs;
|
||||
DriverMap driver_map;
|
||||
Factory& factory;
|
||||
CellSimplifier simplifier;
|
||||
vector<Mem> memories_vector;
|
||||
dict<Cell*, Mem*> 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<Node> 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<Node> 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<Node> 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<IdString, Node> 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<dict<IdString, Node>, Node> outputs = simplifier.handle(cell->name, cell->type, cell->parameters, connections);
|
||||
if(auto *nodep = std::get_if<Node>(&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<dict<IdString, Node>>(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<Cell *>(&queue.front()); p != nullptr) {
|
||||
process_cell(*p);
|
||||
continue;
|
||||
}
|
||||
|
||||
DriveSpec spec = std::get<DriveSpec>(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<int> 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<int> 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<std::function<void()>> &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 {<number>}, {} 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
|
||||
642
kernel/functional.h
Normal file
642
kernel/functional.h
Normal file
|
|
@ -0,0 +1,642 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* 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<int, std::pair<int, int>> _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<RTLIL::Const, MemContents> _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<RTLIL::Const>(_initial); }
|
||||
MemContents const& initial_value_memory() const { return std::get<MemContents>(_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<IdString, IdString>,
|
||||
int
|
||||
> _extra;
|
||||
public:
|
||||
NodeData() : _fn(Fn::invalid) {}
|
||||
NodeData(Fn fn) : _fn(fn) {}
|
||||
template<class T> NodeData(Fn fn, T &&extra) : _fn(fn), _extra(std::forward<T>(extra)) {}
|
||||
Fn fn() const { return _fn; }
|
||||
const RTLIL::Const &as_const() const { return std::get<RTLIL::Const>(_extra); }
|
||||
std::pair<IdString, IdString> as_idstring_pair() const { return std::get<std::pair<IdString, IdString>>(_extra); }
|
||||
int as_int() const { return std::get<int>(_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<NodeData, Attr, IdString, std::tuple<IdString, IdString, bool>>;
|
||||
Graph _graph;
|
||||
dict<std::pair<IdString, IdString>, IRInput> _inputs;
|
||||
dict<std::pair<IdString, IdString>, IROutput> _outputs;
|
||||
dict<std::pair<IdString, IdString>, 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<IRInput const*> inputs(IdString kind) const;
|
||||
vector<IRInput const*> inputs() const { return inputs(ID($input)); }
|
||||
vector<IROutput const*> outputs(IdString kind) const;
|
||||
vector<IROutput const*> outputs() const { return outputs(ID($output)); }
|
||||
vector<IRState const*> states(IdString kind) const;
|
||||
vector<IRState const*> states() const { return states(ID($state)); }
|
||||
vector<IRInput const*> all_inputs() const;
|
||||
vector<IROutput const*> all_outputs() const;
|
||||
vector<IRState const*> 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<Node>;
|
||||
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<class Visitor> 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<std::string(Node)>);
|
||||
};
|
||||
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<Node> IR::iterator::operator->() { return arrow_proxy<Node>(**this); }
|
||||
// AbstractVisitor provides an abstract base class for visitors
|
||||
template<class T> 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<class T> struct DefaultVisitor : public AbstractVisitor<T> {
|
||||
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<Node> 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 Id> class Scope {
|
||||
protected:
|
||||
char substitution_character = '_';
|
||||
virtual bool is_character_legal(char, int) = 0;
|
||||
private:
|
||||
pool<std::string> _used_names;
|
||||
dict<Id, std::string> _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<std::function<void()>>& fns);
|
||||
public:
|
||||
Writer(std::ostream &os) : os(&os) {}
|
||||
template<class T> Writer& operator <<(T&& arg) { *os << std::forward<T>(arg); return *this; }
|
||||
template<typename... Args>
|
||||
void print(const char *fmt, Args&&... args)
|
||||
{
|
||||
vector<std::function<void()>> fns { [&]() { *this << args; }... };
|
||||
print_impl(fmt, fns);
|
||||
}
|
||||
template<typename Fn, typename... Args>
|
||||
void print_with(Fn fn, const char *fmt, Args&&... args)
|
||||
{
|
||||
vector<std::function<void()>> fns { [&]() {
|
||||
if constexpr (std::is_invocable_v<Fn, Args>)
|
||||
*this << fn(args);
|
||||
else
|
||||
*this << args; }...
|
||||
};
|
||||
print_impl(fmt, fns);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
|
|
@ -186,6 +187,37 @@ inline unsigned int mkhash(const T &v) {
|
|||
return hash_ops<T>().hash(v);
|
||||
}
|
||||
|
||||
template<> struct hash_ops<std::monostate> {
|
||||
static inline bool cmp(std::monostate a, std::monostate b) {
|
||||
return a == b;
|
||||
}
|
||||
static inline unsigned int hash(std::monostate) {
|
||||
return mkhash_init;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... T> struct hash_ops<std::variant<T...>> {
|
||||
static inline bool cmp(std::variant<T...> a, std::variant<T...> b) {
|
||||
return a == b;
|
||||
}
|
||||
static inline unsigned int hash(std::variant<T...> a) {
|
||||
unsigned int h = std::visit([](const auto &v) { return mkhash(v); }, a);
|
||||
return mkhash(a.index(), h);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> struct hash_ops<std::optional<T>> {
|
||||
static inline bool cmp(std::optional<T> a, std::optional<T> b) {
|
||||
return a == b;
|
||||
}
|
||||
static inline unsigned int hash(std::optional<T> 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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<typename T> static inline const char *log_id(T *obj, const char *nullstr = nullptr) {
|
||||
if (nullstr && obj == nullptr)
|
||||
|
|
|
|||
216
kernel/mem.cc
216
kernel/mem.cc
|
|
@ -1679,3 +1679,219 @@ SigSpec MemWr::decompress_en(const std::vector<int> &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<addr_t, RTLIL::Const>::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<addr_t, RTLIL::Const>::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<addr_t, RTLIL::Const>::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<addr_t, RTLIL::Const>::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<std::map<addr_t, RTLIL::Const> &>(_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<addr_t, RTLIL::Const>::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<State>::iterator MemContents::_range_write(std::vector<State>::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;
|
||||
}
|
||||
109
kernel/mem.h
109
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<addr_t, RTLIL::Const> _values;
|
||||
// returns an iterator to the range containing addr, if it exists, or the first range past addr
|
||||
std::map<addr_t, RTLIL::Const>::iterator _range_at(addr_t addr) const;
|
||||
addr_t _range_size(std::map<addr_t, RTLIL::Const>::iterator it) const { return it->second.size() / _data_width; }
|
||||
addr_t _range_begin(std::map<addr_t, RTLIL::Const>::iterator it) const { return it->first; }
|
||||
addr_t _range_end(std::map<addr_t, RTLIL::Const>::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<addr_t, RTLIL::Const>::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<addr_t, RTLIL::Const>::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<addr_t, RTLIL::Const>::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<addr_t, RTLIL::Const>::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<State>::iterator _range_data(std::map<addr_t, RTLIL::Const>::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<addr_t, RTLIL::Const>::iterator _reserve_range(addr_t begin_addr, addr_t end_addr);
|
||||
// write a single word at addr, return iterator to next word
|
||||
std::vector<State>::iterator _range_write(std::vector<State>::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<range>;
|
||||
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<range>(**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<typename Iterator> 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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
183
kernel/rtlil.cc
183
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<RTLIL::Cell*, RTLIL::IdString> 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<pair<RTLIL::Cell*, RTLIL::IdString>> queue;
|
||||
bufNormQueue.swap(queue);
|
||||
|
||||
pool<Wire*> 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<RTLIL::Cell*, RTLIL::IdString> 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<RTLIL::Cell*, RTLIL::IdString> 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)
|
||||
|
|
|
|||
|
|
@ -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<RTLIL::State> &bits) : bits(bits) { flags = CONST_FLAG_NONE; }
|
||||
Const(const std::vector<bool> &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<RTLIL::Monitor*> monitors;
|
||||
dict<std::string, std::string> scratchpad;
|
||||
|
||||
bool flagBufferedNormalized = false;
|
||||
void bufNormalize(bool enable=true);
|
||||
|
||||
int refcount_modules_;
|
||||
dict<RTLIL::IdString, RTLIL::Module*> modules_;
|
||||
std::vector<RTLIL::Binding*> bindings_;
|
||||
|
|
@ -1208,6 +1213,9 @@ public:
|
|||
std::vector<RTLIL::IdString> ports;
|
||||
void fixup_ports();
|
||||
|
||||
pool<pair<RTLIL::Cell*, RTLIL::IdString>> bufNormQueue;
|
||||
void bufNormalize();
|
||||
|
||||
template<typename T> void rewrite_sigspecs(T &functor);
|
||||
template<typename T> void rewrite_sigspecs2(T &functor);
|
||||
void cloneInto(RTLIL::Module *new_mod) const;
|
||||
|
|
@ -1279,6 +1287,7 @@ public:
|
|||
|
||||
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 @@ public:
|
|||
|
||||
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 @@ public:
|
|||
#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 @@ protected:
|
|||
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 @@ public:
|
|||
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<unsigned int, RTLIL::Wire*> *get_all_wires(void);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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<int> a = importDefSigSpec(cell->getPort(ID::A), timestep);
|
||||
std::vector<int> y = importDefSigSpec(cell->getPort(ID::Y), timestep);
|
||||
|
|
@ -438,7 +438,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep)
|
|||
|
||||
std::vector<int> 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<int> zero(a.size(), ez->CONST_FALSE);
|
||||
|
|
@ -451,7 +451,7 @@ bool SatGen::importCell(RTLIL::Cell *cell, int timestep)
|
|||
std::vector<int> 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);
|
||||
|
|
|
|||
163
kernel/sexpr.cc
Normal file
163
kernel/sexpr.cc
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* 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 << "<invalid>";
|
||||
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
|
||||
122
kernel/sexpr.h
Normal file
122
kernel/sexpr.h
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* 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::vector<SExpr>, 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<SExpr> const &l) : _v(l) {}
|
||||
SExpr(std::vector<SExpr> &&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<typename... Args> static SExpr list(Args&&... args) {
|
||||
return SExpr(std::vector<SExpr>{std::forward<Args>(args)...});
|
||||
}
|
||||
bool is_atom() const { return std::holds_alternative<std::string>(_v); }
|
||||
std::string const &atom() const { return std::get<std::string>(_v); }
|
||||
bool is_list() const { return std::holds_alternative<std::vector<SExpr>>(_v); }
|
||||
std::vector<SExpr> const &list() const { return std::get<std::vector<SExpr>>(_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<typename... Args> SExpr list(Args&&... args) {
|
||||
return SExpr(std::vector<SExpr>{std::forward<Args>(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<bool> _unclosed;
|
||||
// Used only for push() and pop() (stores _unclosed.size())
|
||||
vector<size_t> _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
|
||||
357
kernel/topo_scc.h
Normal file
357
kernel/topo_scc.h
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Jannis Harder <jix@yosyshq.com> <me@jix.one>
|
||||
*
|
||||
* 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<std::pair<int, int>>::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<RTLIL::Cell *> cell_ids;
|
||||
idict<RTLIL::SigBit> sig_ids;
|
||||
std::vector<std::pair<int, int>> edges;
|
||||
std::vector<std::pair<int, int>> edge_ranges;
|
||||
std::vector<int> 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<typename Src, typename Dst>
|
||||
void add_edge(Src &&src, Dst &&dst) {
|
||||
computed = false;
|
||||
node_type src_node = node(std::forward<Src>(src));
|
||||
node_type dst_node = node(std::forward<Dst>(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<std::pair<int, int>>::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<std::pair<int, int>> edges;
|
||||
std::vector<std::pair<int, int>> edge_ranges;
|
||||
std::vector<int> 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<typename G, typename ComponentCallback>
|
||||
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_entry> dfs_stack;
|
||||
std::vector<node_type> 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
|
||||
|
|
@ -253,6 +253,15 @@ template <typename T, typename C = std::less<T>, typename OPS = hash_ops<T>> 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<class T>
|
||||
struct arrow_proxy {
|
||||
T v;
|
||||
explicit arrow_proxy(T const & v) : v(v) {}
|
||||
T* operator->() { return &v; }
|
||||
};
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@
|
|||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <initializer_list>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<std::string> 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<std::pair<RTLIL::IdString, int>> &topo;
|
||||
SigMap sigmap;
|
||||
bool force_detail;
|
||||
|
||||
CircuitEdgesDatabase(TopoSort<std::pair<RTLIL::IdString, int>> &topo, SigMap &sigmap)
|
||||
: topo(topo), sigmap(sigmap) {}
|
||||
CircuitEdgesDatabase(TopoSort<std::pair<RTLIL::IdString, int>> &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<Cell *> 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<SigBit> 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);
|
||||
}
|
||||
|
|
|
|||
256
passes/cmds/example_dt.cc
Normal file
256
passes/cmds/example_dt.cc
Normal file
|
|
@ -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<std::string> 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<IdString, Const> parameters;
|
||||
|
||||
ExampleFn(IdString name) : name(name) {}
|
||||
ExampleFn(IdString name, dict<IdString, Const> 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<ExampleFn, int, IdString, IdString> ExampleGraph;
|
||||
|
||||
ExampleGraph compute_graph;
|
||||
|
||||
|
||||
dm.add(module);
|
||||
|
||||
idict<DriveSpec> queue;
|
||||
idict<Cell *> cells;
|
||||
|
||||
IntGraph edges;
|
||||
std::vector<int> 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<int> 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<int> 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
|
||||
123
passes/cmds/internal_stats.cc
Normal file
123
passes/cmds/internal_stats.cc
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* 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 <iterator>
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 <mach/task.h>
|
||||
#include <mach/mach_init.h>
|
||||
#endif
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
std::optional<uint64_t> 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<std::string> 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<std::string>(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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<RTLIL::IdString, int> techinfo;
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ struct SimShared
|
|||
std::vector<TriggeredAssertion> triggered_assertions;
|
||||
std::vector<DisplayOutput> 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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -167,8 +167,8 @@ struct abc9_output_filter
|
|||
void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe_file,
|
||||
vector<int> 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<std::string> liberty_files, std::string wire_delay, std::string tempdir_name,
|
||||
std::string constr_file, std::vector<std::string> 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 <file>\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 <cell_name>\n");
|
||||
log(" set_load <floating_point_number>\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 <file>\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 <cell_name>\n");
|
||||
log(" avoid usage of the technology cell <cell_name> when mapping the design.\n");
|
||||
log(" this option can be used multiple times.\n");
|
||||
log("\n");
|
||||
log(" -D <picoseconds>\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<std::string> 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;
|
||||
|
||||
|
|
|
|||
153
passes/techmap/abc_new.cc
Normal file
153
passes/techmap/abc_new.cc
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Martin Povišer <povik@cutebit.org>
|
||||
*
|
||||
* 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 <from_label>:<to_label>\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 <command>\n");
|
||||
log(" -script <file>\n");
|
||||
log(" -D <picoseconds>\n");
|
||||
log(" -constr <file>\n");
|
||||
log(" -dont_use <cell_name>\n");
|
||||
log(" -liberty <file>\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<std::string> 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<Module *> 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 = "<abc-temp-dir>";
|
||||
std::string modname = "<module>";
|
||||
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
|
||||
512
passes/techmap/bufnorm.cc
Normal file
512
passes/techmap/bufnorm.cc
Normal file
|
|
@ -0,0 +1,512 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* 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<std::string> 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<pair<IdString, SigSpec>, Cell*> old_buffers;
|
||||
|
||||
{
|
||||
vector<Cell*> 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<IdString,Wire*> 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<SigBit, pool<Wire*>> bit2wires;
|
||||
dict<SigSpec, pool<Wire*>> whole_wires;
|
||||
dict<SigBit, SigBit> mapped_bits;
|
||||
pool<Wire*> 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<Cell*> added_buffers;
|
||||
|
||||
auto make_buffer_f = [&](const IdString &type, const SigSpec &src, const SigSpec &dst)
|
||||
{
|
||||
auto it = old_buffers.find(pair<IdString, SigSpec>(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
|
||||
227
passes/techmap/clockgate.cc
Normal file
227
passes/techmap/clockgate.cc
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
#include "kernel/yosys.h"
|
||||
#include "kernel/ff.h"
|
||||
#include <optional>
|
||||
|
||||
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 <celltype> <ce>:<clk>:<gclk>\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 <celltype> ICG (integrated clock gating)\n");
|
||||
log(" cell with ports named <ce>, <clk>, <gclk>.\n");
|
||||
log(" The ICG's clock enable pin must be active high.\n");
|
||||
log(" -neg <celltype> <ce>:<clk>:<gclk>\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 <celltype> ICG (integrated clock gating)\n");
|
||||
log(" cell with ports named <ce>, <clk>, <gclk>.\n");
|
||||
log(" The ICG's clock enable pin must be active high.\n");
|
||||
log(" -tie_lo <port_name>\n");
|
||||
log(" Port <port_name> of the ICG will be tied to zero.\n");
|
||||
log(" Intended for DFT scan-enable pins.\n");
|
||||
log(" -min_net_size <n>\n");
|
||||
log(" Only transform sets of at least <n> 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<decltype(t)>::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<std::string> args, RTLIL::Design *design) override {
|
||||
log_header(design, "Executing CLOCK_GATE pass (extract clock gating out of flip flops).\n");
|
||||
|
||||
std::optional<ClockGateCell> pos_icg_desc;
|
||||
std::optional<ClockGateCell> neg_icg_desc;
|
||||
std::vector<std::string> 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<Cell*> ce_ffs;
|
||||
dict<ClkNetInfo, GClkNetInfo> 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<ClockGateCell> 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
|
||||
97
setup.py
Normal file
97
setup.py
Normal file
|
|
@ -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,
|
||||
},
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ _skip = { 'ALU', 'BANDGAP', 'DFF', 'DFFC', 'DFFCE', 'DFFE', 'DFFN', 'DFFNC', 'DF
|
|||
'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')
|
||||
|
|
|
|||
|
|
@ -1699,9 +1699,6 @@ input CLKIN, CE;
|
|||
output CLKOUT, CLKOUTN;
|
||||
endmodule
|
||||
|
||||
module EMCU (...);
|
||||
endmodule
|
||||
|
||||
module FLASH64K (...);
|
||||
input[4:0]XADR;
|
||||
input[5:0]YADR;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue