3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2026-06-26 10:38:47 +00:00

Merge pull request #25 from YosysHQ/main

bump fork for version 0.46
This commit is contained in:
Eder Monteiro 2024-10-09 11:28:55 -03:00 committed by GitHub
commit e5713bc7db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
125 changed files with 17609 additions and 746 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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
View 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/' }}

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

View 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

View 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
View file

@ -45,4 +45,10 @@ __pycache__
/tests/unit/bintest/
/tests/unit/objtest/
/tests/ystests
/result
/result
/dist
/*.egg-info
/build
/venv
/boost
/ffi

View file

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

View file

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

@ -1 +1 @@
Subproject commit 2188bc71228b0788569d83ad2b7e7b91ca5dcc09
Subproject commit cac8f99eaa220a5e3db5caeb87cef0a975c953a2

View file

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

View file

@ -0,0 +1 @@
OBJS += backends/aiger2/aiger.o

1471
backends/aiger2/aiger.cc Normal file

File diff suppressed because it is too large Load diff

View file

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

View 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
View 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 &current_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

View 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

View 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

View 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

View 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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,2 +1,5 @@
*.dot
*.pdf
*.out
*.log
*.stat

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
.PHONY: all dots
all: dots
.PHONY: all dots examples
all: dots examples
dots:
examples:
.PHONY: test
test: stubnets.so

View file

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

View file

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

View file

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

View file

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

View file

@ -10,5 +10,6 @@ of interest for developers looking to customise Yosys builds.
extensions
build_verific
functional_ir
test_suites

View file

@ -0,0 +1,2 @@
OBJS += frontends/aiger2/xaiger.o

473
frontends/aiger2/xaiger.cc Normal file
View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -30,6 +30,8 @@
#include <unordered_map>
#include <unordered_set>
#include <initializer_list>
#include <variant>
#include <optional>
#include <stdexcept>
#include <memory>
#include <cmath>

View file

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

View file

@ -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
View 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 &param : 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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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