3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2026-01-19 00:38:59 +00:00

Merge remote-tracking branch 'origin/main' into support-parameter-default-values-in-json-frontend-and-verilog-backend

This commit is contained in:
Gus Smith 2026-01-06 09:39:59 -08:00
commit 5b7d436851
1758 changed files with 182731 additions and 54865 deletions

View file

@ -10,3 +10,11 @@ insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = false
[*.rst]
indent_style = space
indent_size = 3
[*.yml]
indent_style = space
indent_size = 2

View file

@ -1 +1 @@
$Format:%h$
$Format:%H$

75
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View file

@ -0,0 +1,75 @@
name: Bug Report
description: Report an issue or regression with Yosys
labels: ["pending-verification"]
body:
- type: markdown
attributes:
value: >
If you have a general question, please ask it on the [Discourse forum](https://yosyshq.discourse.group/).
If you have a feature request, please fill out the appropriate issue form, this form is for bugs and/or regressions.
Please contact [YosysHQ GmbH](https://www.yosyshq.com/) if you need
commercial support or work done for Yosys.
- type: input
id: yosys_version
attributes:
label: Version
description: "The version of yosys this bug was encountered on."
placeholder: "The output of `yosys --version`"
validations:
required: true
- type: dropdown
id: os
attributes:
label: On which OS did this happen?
options:
- Linux
- macOS
- Windows
- BSD
- WebAssembly
multiple: true
validations:
required: true
- type: markdown
attributes:
value: >
When providing steps to reproduce the issue, please ensure that the issue
is reproducible in the current git main of Yosys. Also ensure to
provide all necessary source files needed.
Please see [https://stackoverflow.com/help/mcve](https://stackoverflow.com/help/mcve)
for information on how to create a Minimal, Complete, and Verifiable Example
(MCVE).
- type: textarea
id: reproduction_steps
attributes:
label: Reproduction Steps
description: "Please provide clear and concise steps to reproduce the issue."
validations:
required: true
- type: textarea
id: expected_behavior
attributes:
label: Expected Behavior
description: "Please describe the behavior you would have expected from the tool."
validations:
required: true
- type: textarea
id: actual_behavior
attributes:
label: Actual Behavior
description: "Please describe how the behavior you see differs from the expected behavior."
validations:
required: true

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,8 @@
contact_links:
- name: Discourse
url: https://yosyshq.discourse.group
about: "Have a question? Ask it on our Discourse group!"
- name: IRC Channel
url: https://web.libera.chat/#yosys
about: "#yosys on irc.libera.chat"

54
.github/ISSUE_TEMPLATE/docs_report.yml vendored Normal file
View file

@ -0,0 +1,54 @@
name: Documentation Report
description: Report a problem with the Yosys documentation
labels: ["pending-verification"]
body:
- type: markdown
attributes:
value: >
If you have a general question, please ask it on the [Discourse forum](https://yosyshq.discourse.group/).
If you have found a bug in Yosys, or in building the documentation,
please fill out the Bug Report issue form, this form is for problems
with the live documentation on [Read the
Docs](https://yosyshq.readthedocs.io/projects/yosys/). Please only
report problems that appear on the latest version of the documentation.
Please contact [YosysHQ GmbH](https://www.yosyshq.com/) if you need
commercial support for Yosys.
- type: input
id: docs_url
attributes:
label: Link to page
description: "Please provide a link to the page where the problem was found."
placeholder: "e.g. https://yosyshq.readthedocs.io/projects/yosys/"
validations:
required: true
- type: input
id: build_number
attributes:
label: Build number
description: "If possible, please provide the latest build number from https://readthedocs.org/projects/yosys/builds/."
placeholder: "e.g. Build #24078236"
validations:
required: false
- type: textarea
id: problem
attributes:
label: Issue
description: "Please describe what is incorrect, invalid, or missing."
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected
description: "If applicable, please describe what should appear instead."
validations:
required: false

View file

@ -0,0 +1,25 @@
name: Feature Request
description: "Submit a feature request for Yosys"
labels: ["feature-request"]
body:
- type: markdown
attributes:
value: >
If you have a general question, please ask it on the [Discourse forum](https://yosyshq.discourse.group/).
If you have a bug report, please fill out the appropriate issue form, this form is for feature requests.
Please contact [YosysHQ GmbH](https://www.yosyshq.com/) if you need
commercial support or work done for Yosys.
- type: textarea
id: feature_description
attributes:
label: Feature Description
description: "A clear and detailed description of the feature."
validations:
required: true

9
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,9 @@
_If your work is part of a larger effort, please discuss your general plans on [Discourse](https://yosyshq.discourse.group/) first to align your vision with maintainers._
_What are the reasons/motivation for this change?_
_Explain how this is achieved._
_Make sure your change comes with tests. If not possible, share how a reviewer might evaluate it._
_These template prompts can be deleted when you're done responding to them._

View file

@ -0,0 +1,91 @@
name: Build environment setup
description: Configure build env for Yosys builds
inputs:
runs-on:
required: true
type: string
get-build-deps:
description: 'Install Yosys build dependencies'
default: false
required: false
type: boolean
get-docs-deps:
description: 'Install Yosys docs dependencies'
default: false
required: false
type: boolean
get-test-deps:
description: 'Install Yosys test dependencies'
default: false
required: false
type: boolean
get-iverilog:
description: 'Install iverilog'
default: false
required: false
type: boolean
runs:
using: composite
steps:
# if updating common/build/docs dependencies, make sure to update README.md
# and docs/source/getting_started/installation.rst to match.
- name: Linux common dependencies
if: runner.os == 'Linux'
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: gawk git make python3
version: ${{ inputs.runs-on }}-commonys
- name: Linux build dependencies
if: runner.os == 'Linux' && inputs.get-build-deps == 'true'
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: bison clang flex libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev
version: ${{ inputs.runs-on }}-buildys
- name: Linux docs dependencies
if: runner.os == 'Linux' && inputs.get-docs-deps == 'true'
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: graphviz xdot
version: ${{ inputs.runs-on }}-docsys
# if updating test dependencies, make sure to update
# docs/source/yosys_internals/extending_yosys/test_suites.rst to match.
- name: Linux test dependencies
if: runner.os == 'Linux' && inputs.get-test-deps == 'true'
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: libgtest-dev
version: ${{ inputs.runs-on }}-testys
- name: Install macOS Dependencies
if: runner.os == 'macOS'
shell: bash
run: |
brew bundle
- name: Linux runtime environment
if: runner.os == 'Linux'
shell: bash
run: |
echo "${{ github.workspace }}/.local/bin" >> $GITHUB_PATH
echo "procs=$(nproc)" >> $GITHUB_ENV
- name: macOS runtime environment
if: runner.os == 'macOS'
shell: bash
run: |
echo "${{ github.workspace }}/.local/bin" >> $GITHUB_PATH
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
echo "$(brew --prefix bison)/bin" >> $GITHUB_PATH
echo "$(brew --prefix flex)/bin" >> $GITHUB_PATH
echo "procs=$(sysctl -n hw.ncpu)" >> $GITHUB_ENV
- name: Setup iverilog
if: inputs.get-iverilog == 'true'
uses: ./.github/actions/setup-iverilog
with:
runs-on: ${{ inputs.runs-on }}

View file

@ -0,0 +1,70 @@
name: iverilog setup
description: Cached build and install of iverilog
inputs:
runs-on:
required: true
type: string
runs:
using: composite
steps:
- name: iverilog Linux deps
if: steps.restore-iverilog.outputs.cache-hit != 'true' && runner.os == 'Linux'
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: autoconf gperf make gcc g++ bison flex libbz2-dev
version: ${{ inputs.runs-on }}-iverilog
- name: iverilog macOS deps
if: steps.restore-iverilog.outputs.cache-hit != 'true' && runner.os == 'macOS'
shell: bash
run: |
brew install autoconf
- name: Get iverilog
id: get-iverilog
shell: bash
run: |
git clone https://github.com/steveicarus/iverilog.git
cd iverilog
echo "IVERILOG_GIT=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- 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
- uses: actions/cache/restore@v4
id: restore-iverilog
with:
path: .local/
key: ${{ inputs.runs-on }}-${{ steps.get-iverilog.outputs.IVERILOG_GIT }}
- name: Build iverilog
if: steps.restore-iverilog.outputs.cache-hit != 'true'
shell: bash
run: |
mkdir -p ${{ github.workspace }}/.local/
cd iverilog
autoconf
CC=gcc CXX=g++ ./configure --prefix=${{ github.workspace }}/.local
make -j$procs
make install
- name: Check iverilog
shell: bash
run: |
iverilog -V
- uses: actions/cache/save@v4
id: save-iverilog
if: steps.restore-iverilog.outputs.cache-hit != 'true'
with:
path: .local/
key: ${{ steps.restore-iverilog.outputs.cache-primary-key }}

View file

@ -1,24 +0,0 @@
## Steps to reproduce the issue
*Provide instructions for reproducing the issue. Make sure to include
all necessary source files. (You can simply drag&drop a .zip file into
the issue editor.)*
Also, make sure that the issue is actually reproducable in current git
master of Yosys.
See https://stackoverflow.com/help/mcve for some information on how to
create a Minimal, Complete, and Verifiable example (MCVE).
Please do not waste our time with issues that lack sufficient information
to reproduce the issue easily. We will simply close those issues.
Contact https://www.yosyshq.com/ if you need commercial support for Yosys.
## Expected behavior
*Please describe the behavior you would have expected from the tool.*
## Actual behavior
*Please describe how the behavior you see differs from the expected behavior.*

35
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,35 @@
name: "CodeQL"
on:
workflow_dispatch:
schedule:
- cron: '0 3 * * *'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true
persist-credentials: false
- name: Setup environment
uses: ./.github/actions/setup-build-env
with:
runs-on: ubuntu-latest
get-build-deps: true
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: cpp
queries: security-extended,security-and-quality
- name: Build
run: make yosys -j6
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

View file

@ -1,24 +0,0 @@
name: Emscripten Build
on: [push, pull_request]
jobs:
emcc:
runs-on: ubuntu-latest
steps:
- uses: mymindstorm/setup-emsdk@v11
- uses: actions/checkout@v2
- name: Cache sources
id: cache-sources
uses: actions/cache@v2
with:
path: .
key: cache-yosys
- name: Build
run: |
make config-emcc
make YOSYS_VER=latest
- uses: actions/upload-artifact@v2
with:
name: yosysjs
path: yosysjs-latest.zip

126
.github/workflows/extra-builds.yml vendored Normal file
View file

@ -0,0 +1,126 @@
name: Test extra build flows
on:
# always test main
push:
branches:
- main
# test PRs
pull_request:
# allow triggering tests, ignores skip check
workflow_dispatch:
jobs:
pre_job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# don't run on documentation changes
paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]'
# cancel previous builds if a new commit is pushed
# but never cancel main
cancel_others: ${{ github.ref != 'refs/heads/main' }}
vs-prep:
name: Prepare Visual Studio build
runs-on: ubuntu-latest
needs: [pre_job]
if: needs.pre_job.outputs.should_skip != 'true'
steps:
- uses: actions/checkout@v4
with:
submodules: true
persist-credentials: false
- run: sudo apt-get install libfl-dev
- name: Build
run: make vcxsrc YOSYS_COMPILER="Visual Studio" VCX_DIR_NAME=yosys-win32-vcxsrc-latest
- uses: actions/upload-artifact@v4
with:
name: vcxsrc
path: yosys-win32-vcxsrc-latest.zip
vs-build:
name: Visual Studio build
runs-on: windows-latest
needs: [vs-prep, pre_job]
if: needs.pre_job.outputs.should_skip != 'true'
steps:
- uses: actions/download-artifact@v4
with:
name: vcxsrc
path: .
- name: unzip
run: unzip yosys-win32-vcxsrc-latest.zip
- name: setup-msbuild
uses: microsoft/setup-msbuild@v2
- name: MSBuild
working-directory: yosys-win32-vcxsrc-latest
run: msbuild YosysVS.sln /p:PlatformToolset=v142 /p:Configuration=Release /p:WindowsTargetPlatformVersion=10.0.26100.0
wasi-build:
name: WASI build
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
persist-credentials: false
- name: Build
run: |
WASI_SDK=wasi-sdk-27.0-x86_64-linux
WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-27/wasi-sdk-27.0-x86_64-linux.tar.gz
if ! [ -d ${WASI_SDK} ]; then curl -L ${WASI_SDK_URL} | tar xzf -; fi
FLEX_VER=2.6.4
FLEX=flex-${FLEX_VER}
FLEX_URL=https://github.com/westes/flex/releases/download/v${FLEX_VER}/${FLEX}.tar.gz
if ! [ -d ${FLEX} ]; then curl -L ${FLEX_URL} | tar xzf -; fi
mkdir -p flex-build
(cd flex-build &&
../${FLEX}/configure --prefix=$(pwd)/../flex-prefix &&
make &&
make install)
mkdir -p build
cat > build/Makefile.conf <<END
export PATH := $(pwd)/${WASI_SDK}/bin:$(pwd)/flex-prefix/bin:${PATH}
WASI_SYSROOT := $(pwd)/${WASI_SDK}/share/wasi-sysroot
CONFIG := wasi
PREFIX := /
ENABLE_TCL := 0
ENABLE_READLINE := 0
ENABLE_PLUGINS := 0
ENABLE_ZLIB := 0
CXXFLAGS += -I$(pwd)/flex-prefix/include
END
make -C build -f ../Makefile CXX=clang -j$(nproc)
nix-build:
name: "Build nix flake"
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
fail-fast: false
steps:
- uses: actions/checkout@v4
with:
submodules: true
persist-credentials: false
- uses: cachix/install-nix-action@v31
with:
install_url: https://releases.nixos.org/nix/nix-2.30.0/install
- run: nix build .?submodules=1 -L

83
.github/workflows/prepare-docs.yml vendored Normal file
View file

@ -0,0 +1,83 @@
name: Build docs artifact with Verific
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=${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
needs: check_docs_rebuild
if: ${{ needs.check_docs_rebuild.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }}
runs-on: [self-hosted, linux, x64, fast]
steps:
- name: Checkout Yosys
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: true
- name: Runtime environment
run: |
echo "procs=$(nproc)" >> $GITHUB_ENV
- name: Build Yosys
run: |
make config-clang
echo "ENABLE_VERIFIC := 1" >> Makefile.conf
echo "ENABLE_VERIFIC_EDIF := 1" >> Makefile.conf
echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf
echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 1" >> Makefile.conf
echo "ENABLE_CCACHE := 1" >> Makefile.conf
echo "ENABLE_HELP_SOURCE := 1" >> Makefile.conf
make -j$procs
- name: Prepare docs
shell: bash
run:
make docs/prep -j$procs TARGETS= EXTRA_TARGETS=
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: cmd-ref-${{ github.sha }}
path: |
docs/source/generated
docs/source/_images
docs/source/code_examples
- name: Install doc prereqs
shell: bash
run: |
make docs/reqs
- name: Test build docs
shell: bash
run: |
make -C docs html -j$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 }}
webhook_token: ${{ secrets.RTDS_WEBHOOK_TOKEN }}
commit_ref: ${{ github.ref }}

34
.github/workflows/source-vendor.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: Create source archive with vendored dependencies
on: [push, workflow_dispatch]
jobs:
vendor-sources:
runs-on: ubuntu-latest
steps:
- name: Checkout repository with submodules
uses: actions/checkout@v4
with:
submodules: 'recursive'
persist-credentials: false
- name: Create clean tarball
run: |
git archive --format=tar HEAD -o yosys-src-vendored.tar
git submodule foreach '
git archive --format=tar --prefix="${sm_path}/" HEAD --output=${toplevel}/vendor-${name}.tar
'
# 2008 bug https://lists.gnu.org/archive/html/bug-tar/2008-08/msg00002.html
for file in vendor-*.tar; do
tar --concatenate --file=yosys-src-vendored.tar "$file"
done
gzip yosys-src-vendored.tar
- name: Store tarball artifact
uses: actions/upload-artifact@v4
with:
name: vendored-sources
path: yosys-src-vendored.tar.gz
retention-days: 1

265
.github/workflows/test-build.yml vendored Normal file
View file

@ -0,0 +1,265 @@
name: Build and run tests
on:
# always test main
push:
branches:
- main
# test PRs
pull_request:
# allow triggering tests, ignores skip check
workflow_dispatch:
jobs:
pre_job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# don't run on documentation changes
paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]'
# cancel previous builds if a new commit is pushed
# but never cancel main
cancel_others: ${{ github.ref != 'refs/heads/main' }}
pre_docs_job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# don't run on readme changes
paths_ignore: '["**/README.md"]'
# cancel previous builds if a new commit is pushed
# but never cancel main
cancel_others: ${{ github.ref != 'refs/heads/main' }}
build-yosys:
name: Reusable build
runs-on: ${{ matrix.os }}
# pre_job is a subset of pre_docs_job, so we can always build for pre_docs_job
needs: pre_docs_job
if: needs.pre_docs_job.outputs.should_skip != 'true'
env:
CC: clang
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
fail-fast: false
steps:
- name: Checkout Yosys
uses: actions/checkout@v4
with:
submodules: true
persist-credentials: false
- name: Setup environment
uses: ./.github/actions/setup-build-env
with:
runs-on: ${{ matrix.os }}
get-build-deps: true
- name: Build
shell: bash
run: |
mkdir build
cd build
make -f ../Makefile config-$CC
make -f ../Makefile -j$procs
- name: Log yosys-config output
run: |
./yosys-config || true
- name: Compress build
shell: bash
run: |
cd build
tar -cvf ../build.tar share/ yosys yosys-*
- name: Store build artifact
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.os }}
path: build.tar
retention-days: 1
test-yosys:
name: Run tests
runs-on: ${{ matrix.os }}
needs: [build-yosys, pre_job]
if: needs.pre_job.outputs.should_skip != 'true'
env:
CC: clang
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
fail-fast: false
steps:
- name: Checkout Yosys
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup environment
uses: ./.github/actions/setup-build-env
with:
runs-on: ${{ matrix.os }}
get-test-deps: true
get-iverilog: true
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: build-${{ matrix.os }}
- name: Uncompress build
shell: bash
run:
tar -xvf build.tar
- name: Log yosys-config output
run: |
./yosys-config || true
- name: Run tests
shell: bash
run: |
make -j$procs test TARGETS= EXTRA_TARGETS= CONFIG=$CC
- name: Report errors
if: ${{ failure() }}
shell: bash
run: |
find tests/**/*.err -print -exec cat {} \;
test-cells:
name: Run test_cell
runs-on: ${{ matrix.os }}
needs: [build-yosys, pre_job]
if: needs.pre_job.outputs.should_skip != 'true'
env:
CC: clang
strategy:
matrix:
os: [ubuntu-latest]
steps:
- name: Checkout Yosys
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup environment
uses: ./.github/actions/setup-build-env
with:
runs-on: ${{ matrix.os }}
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: build-${{ matrix.os }}
- name: Uncompress build
shell: bash
run:
tar -xvf build.tar
- name: test_cell
shell: bash
run: |
./yosys -p 'test_cell -n 20 -s 1 all'
./yosys -p 'test_cell -n 20 -s 1 -nosat -aigmap $pow $pmux'
./yosys -p 'test_cell -n 20 -s 1 -nosat -aigmap $eqx $nex $bweqx'
./yosys -p 'test_cell -n 20 -s 1 -aigmap $buf'
test-docs:
name: Run docs tests
runs-on: ${{ matrix.os }}
needs: [build-yosys, pre_docs_job]
if: needs.pre_docs_job.outputs.should_skip != 'true'
env:
CC: clang
strategy:
matrix:
os: [ubuntu-latest]
fail-fast: false
steps:
- name: Checkout Yosys
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup environment
uses: ./.github/actions/setup-build-env
with:
runs-on: ${{ matrix.os }}
get-build-deps: true
get-docs-deps: true
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: build-${{ matrix.os }}
- name: Uncompress build
shell: bash
run:
tar -xvf build.tar
- name: Log yosys-config output
run: |
./yosys-config || true
- name: Run tests
shell: bash
run: |
make -C docs test -j$procs
test-docs-build:
name: Try build docs
runs-on: [self-hosted, linux, x64, fast]
needs: [pre_docs_job]
if: ${{ needs.pre_docs_job.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }}
strategy:
matrix:
docs-target: [html, latexpdf]
fail-fast: false
steps:
- name: Checkout Yosys
uses: actions/checkout@v4
with:
submodules: true
persist-credentials: false
- name: Runtime environment
run: |
echo "procs=$(nproc)" >> $GITHUB_ENV
- name: Build Yosys
run: |
make config-clang
echo "ENABLE_CCACHE := 1" >> Makefile.conf
echo "ENABLE_HELP_SOURCE := 1" >> Makefile.conf
make -j$procs
- name: Install doc prereqs
shell: bash
run: |
make docs/reqs
- name: Build docs
shell: bash
run: |
make docs DOC_TARGET=${{ matrix.docs-target }} -j$procs
- name: Store docs build artifact
uses: actions/upload-artifact@v4
with:
name: docs-build-${{ matrix.docs-target }}
path: docs/build/
retention-days: 7

91
.github/workflows/test-compile.yml vendored Normal file
View file

@ -0,0 +1,91 @@
name: Compiler testing
on:
# always test main
push:
branches:
- main
# test PRs
pull_request:
# allow triggering tests, ignores skip check
workflow_dispatch:
jobs:
pre_job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# don't run on documentation changes
paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]'
# cancel previous builds if a new commit is pushed
# but never cancel main
cancel_others: ${{ github.ref != 'refs/heads/main' }}
test-compile:
runs-on: ${{ matrix.os }}
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
env:
CXXFLAGS: ${{ startsWith(matrix.compiler, 'gcc') && '-Wp,-D_GLIBCXX_ASSERTIONS' || ''}}
CC_SHORT: ${{ startsWith(matrix.compiler, 'gcc') && 'gcc' || 'clang' }}
strategy:
matrix:
os:
- ubuntu-latest
compiler:
# oldest supported
- 'clang-10'
- 'gcc-10'
# newest, make sure to update maximum standard step to match
- 'clang-19'
- 'gcc-14'
include:
# macOS x86
- os: macos-15-intel
compiler: 'clang-19'
# macOS arm
- os: macos-latest
compiler: 'clang-19'
fail-fast: false
steps:
- name: Checkout Yosys
uses: actions/checkout@v4
with:
submodules: true
persist-credentials: false
- name: Setup environment
uses: ./.github/actions/setup-build-env
with:
runs-on: ${{ matrix.os }}
get-build-deps: true
- name: Setup Cpp
uses: aminya/setup-cpp@v1
with:
compiler: ${{ matrix.compiler }}
- name: Tool versions
shell: bash
run: |
$CC --version
$CXX --version
# minimum standard
- name: Build C++17
shell: bash
run: |
make config-$CC_SHORT
make -j$procs CXXSTD=c++17 compile-only
# maximum standard, only on newest compilers
- name: Build C++20
if: ${{ matrix.compiler == 'clang-19' || matrix.compiler == 'gcc-14' }}
shell: bash
run: |
make config-$CC_SHORT
make -j$procs CXXSTD=c++20 compile-only

View file

@ -1,130 +0,0 @@
name: Build and run tests (Linux)
on: [push, pull_request]
jobs:
test-linux:
runs-on: ${{ matrix.os.id }}
strategy:
matrix:
os:
- { id: ubuntu-20.04, name: focal }
compiler:
- 'clang-12'
- 'gcc-11'
cpp_std:
- 'c++11'
- 'c++14'
- 'c++17'
- 'c++20'
include:
# Limit the older compilers to C++11 mode
- os: { id: ubuntu-20.04, name: focal }
compiler: 'clang-11'
cpp_std: 'c++11'
- os: { id: ubuntu-20.04, name: focal }
compiler: 'gcc-10'
cpp_std: 'c++11'
- os: { id: ubuntu-18.04, name: bionic }
compiler: 'clang-3.9'
cpp_std: 'c++11'
- os: { id: ubuntu-18.04, name: bionic }
compiler: 'gcc-4.8'
cpp_std: 'c++11'
fail-fast: false
steps:
- name: Install Dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install gperf build-essential bison flex libreadline-dev gawk tcl-dev libffi-dev git graphviz xdot pkg-config python python3 libboost-system-dev libboost-python-dev libboost-filesystem-dev zlib1g-dev
- name: Setup GCC
if: startsWith(matrix.compiler, 'gcc')
shell: bash
run: |
CXX=${CC/#gcc/g++}
sudo apt-add-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install $CC $CXX
echo "CC=$CC" >> $GITHUB_ENV
echo "CXX=$CXX" >> $GITHUB_ENV
env:
CC: ${{ matrix.compiler }}
- name: Setup Clang
if: startsWith(matrix.compiler, 'clang')
shell: bash
run: |
wget https://apt.llvm.org/llvm-snapshot.gpg.key
sudo apt-key add llvm-snapshot.gpg.key
rm llvm-snapshot.gpg.key
sudo apt-add-repository "deb https://apt.llvm.org/${{ matrix.os.name }}/ llvm-toolchain-${{ matrix.os.name }} main"
sudo apt-get update
CXX=${CC/#clang/clang++}
sudo apt-get install $CC $CXX
echo "CC=$CC" >> $GITHUB_ENV
echo "CXX=$CXX" >> $GITHUB_ENV
env:
CC: ${{ matrix.compiler }}
- name: Runtime environment
shell: bash
env:
WORKSPACE: ${{ github.workspace }}
run: |
echo "GITHUB_WORKSPACE=`pwd`" >> $GITHUB_ENV
echo "$GITHUB_WORKSPACE/.local/bin" >> $GITHUB_PATH
echo "procs=$(nproc)" >> $GITHUB_ENV
- name: Tool versions
shell: bash
run: |
$CC --version
$CXX --version
- name: Checkout Yosys
uses: actions/checkout@v2
- name: Get iverilog
shell: bash
run: |
git clone https://github.com/steveicarus/iverilog.git
- name: Cache iverilog
id: cache-iverilog
uses: actions/cache@v2
with:
path: .local/
key: ${{ matrix.os.id }}-${{ hashFiles('iverilog/.git/refs/heads/master') }}
- name: Build iverilog
if: steps.cache-iverilog.outputs.cache-hit != 'true'
shell: bash
run: |
mkdir -p $GITHUB_WORKSPACE/.local/
cd iverilog
autoconf
CC=gcc CXX=g++ ./configure --prefix=$GITHUB_WORKSPACE/.local
make -j${{ env.procs }}
make install
- name: Build yosys (gcc-4.8)
if: matrix.compiler == 'gcc-4.8'
shell: bash
run: |
make config-${{ matrix.compiler }}
make -j${{ env.procs }} CCXXSTD=${{ matrix.cpp_std }} CC=$CC CXX=$CC LD=$CC
- name: Build yosys
if: matrix.compiler != 'gcc-4.8'
shell: bash
run: |
make config-${CC%%-*}
make -j${{ env.procs }} CCXXSTD=${{ matrix.cpp_std }} CC=$CC CXX=$CC LD=$CC
- name: Run tests
if: (matrix.cpp_std == 'c++11') && (matrix.compiler == 'gcc-11')
shell: bash
run: |
make -j${{ env.procs }} test CXXSTD=${{ matrix.cpp_std }} CC=$CC CXX=$CC LD=$CC

View file

@ -1,127 +0,0 @@
name: Build and run tests (macOS)
on: [push, pull_request]
jobs:
test-macos:
runs-on: ${{ matrix.os.id }}
strategy:
matrix:
os:
- { id: macos-11, name: 'Big Sur' }
cpp_std:
- 'c++11'
- 'c++17'
fail-fast: false
steps:
- name: Install Dependencies
run: |
brew install bison flex gawk libffi pkg-config bash
- name: Runtime environment
shell: bash
env:
WORKSPACE: ${{ github.workspace }}
run: |
echo "GITHUB_WORKSPACE=`pwd`" >> $GITHUB_ENV
echo "$GITHUB_WORKSPACE/.local/bin" >> $GITHUB_PATH
echo "$(brew --prefix bison)/bin" >> $GITHUB_PATH
echo "$(brew --prefix flex)/bin" >> $GITHUB_PATH
echo "procs=$(sysctl -n hw.ncpu)" >> $GITHUB_ENV
- name: Tool versions
shell: bash
run: |
cc --version
- name: Checkout Yosys
uses: actions/checkout@v2
- name: Get iverilog
shell: bash
run: |
git clone https://github.com/steveicarus/iverilog.git
- name: Cache iverilog
id: cache-iverilog
uses: actions/cache@v2
with:
path: .local/
key: ${{ matrix.os.id }}-${{ hashFiles('iverilog/.git/refs/heads/master') }}
- name: Build iverilog
if: steps.cache-iverilog.outputs.cache-hit != 'true'
shell: bash
run: |
mkdir -p $GITHUB_WORKSPACE/.local/
cd iverilog
autoconf
CC=gcc CXX=g++ ./configure --prefix=$GITHUB_WORKSPACE/.local/
make -j${{ env.procs }}
make install
- name: Build yosys
shell: bash
run: |
make config-clang
make -j${{ env.procs }} CXXSTD=${{ matrix.cpp_std }} CC=cc CXX=cc LD=cc
- name: Run tests
if: matrix.cpp_std == 'c++11'
shell: bash
run: |
make -j${{ env.procs }} test CXXSTD=${{ matrix.cpp_std }} CC=cc CXX=cc LD=cc
test-macos-homebrew:
runs-on: ${{ matrix.os.id }}
strategy:
matrix:
os:
- { id: macos-10.15, name: Catalina }
cpp_std:
- 'c++17'
compiler:
- gcc
fail-fast: false
steps:
- name: Install Dependencies
run: |
brew install bison flex gawk libffi pkg-config bash
- name: Runtime environment
shell: bash
env:
WORKSPACE: ${{ github.workspace }}
run: |
echo "GITHUB_WORKSPACE=`pwd`" >> $GITHUB_ENV
echo "$GITHUB_WORKSPACE/.local/bin" >> $GITHUB_PATH
echo "$(brew --prefix bison)/bin" >> $GITHUB_PATH
echo "$(brew --prefix flex)/bin" >> $GITHUB_PATH
echo "procs=$(sysctl -n hw.ncpu)" >> $GITHUB_ENV
- name: Setup compiler
shell: bash
run: |
brew install ${{ matrix.compiler }}
CC=${COMPILER/@/-}
CXX=${CC/#gcc/g++}
echo "CC=$CC" >> $GITHUB_ENV
echo "CXX=$CXX" >> $GITHUB_ENV
env:
COMPILER: ${{ matrix.compiler }}
- name: Tool versions
shell: bash
run: |
$CC --version
$CXX --version
- name: Checkout Yosys
uses: actions/checkout@v2
- name: Build yosys
shell: bash
run: |
make config-gcc
make -j${{ env.procs }} CXXSTD=${{ matrix.cpp_std }} CC=$CC CXX=$CC LD=$CC

78
.github/workflows/test-sanitizers.yml vendored Normal file
View file

@ -0,0 +1,78 @@
name: Check clang sanitizers
on:
# always test main
push:
branches:
- main
# ignore PRs due to time needed
# allow triggering tests, ignores skip check
workflow_dispatch:
jobs:
pre_job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# don't run on documentation changes
paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]'
run_san:
name: Build and run tests
runs-on: ${{ matrix.os }}
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
env:
CC: clang
ASAN_OPTIONS: halt_on_error=1
UBSAN_OPTIONS: halt_on_error=1
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
sanitizer: ['undefined,address']
fail-fast: false
steps:
- name: Checkout Yosys
uses: actions/checkout@v4
with:
submodules: true
persist-credentials: false
- name: Setup environment
uses: ./.github/actions/setup-build-env
with:
runs-on: ${{ matrix.os }}
get-build-deps: true
get-test-deps: true
get-iverilog: true
- name: Build
shell: bash
run: |
make config-$CC
echo 'SANITIZER = ${{ matrix.sanitizer }}' >> Makefile.conf
make -j$procs
- name: Log yosys-config output
run: |
./yosys-config || true
- name: Run tests
shell: bash
run: |
make -j$procs test TARGETS= EXTRA_TARGETS=
- name: Report errors
if: ${{ failure() }}
shell: bash
run: |
find tests/**/*.err -print -exec cat {} \;
- name: Run unit tests
shell: bash
run: |
make -j$procs unit-test ENABLE_LIBYOSYS=1

124
.github/workflows/test-verific.yml vendored Normal file
View file

@ -0,0 +1,124 @@
name: Build and run tests with Verific (Linux)
on:
# always test main
push:
branches:
- main
# test PRs
pull_request:
# allow triggering tests, ignores skip check
workflow_dispatch:
jobs:
pre-job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# don't run on documentation changes
paths_ignore: '["**/README.md", "docs/**", "guidelines/**"]'
# cancel previous builds if a new commit is pushed
# but never cancel main
cancel_others: ${{ github.ref != 'refs/heads/main' }}
test-verific:
needs: pre-job
if: ${{ needs.pre-job.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }}
runs-on: [self-hosted, linux, x64, fast]
steps:
- name: Checkout Yosys
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: true
- name: Runtime environment
run: |
echo "procs=$(nproc)" >> $GITHUB_ENV
- name: Build Yosys
run: |
make config-clang
echo "ENABLE_VERIFIC := 1" >> Makefile.conf
echo "ENABLE_VERIFIC_EDIF := 1" >> Makefile.conf
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$procs ENABLE_LTO=1
- name: Install Yosys
run: |
make install DESTDIR=${GITHUB_WORKSPACE}/.local PREFIX=
- name: Checkout SBY
uses: actions/checkout@v4
with:
repository: 'YosysHQ/sby'
path: 'sby'
persist-credentials: false
- name: Build SBY
run: |
make -C sby install DESTDIR=${GITHUB_WORKSPACE}/.local PREFIX=
- name: Run Yosys tests
run: |
make -j$procs test
- name: Run Verific specific Yosys tests
run: |
make -C tests/sva
cd tests/svtypes && bash run-test.sh
- name: Run SBY tests
if: ${{ github.ref == 'refs/heads/main' }}
run: |
make -C sby run_ci
- name: Run unit tests
shell: bash
run: |
make -j$procs unit-test ENABLE_LTO=1 ENABLE_LIBYOSYS=1
test-pyosys:
needs: pre-job
if: ${{ needs.pre-job.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }}
runs-on: [self-hosted, linux, x64, fast]
steps:
- name: Checkout Yosys
uses: actions/checkout@v4
with:
persist-credentials: false
submodules: true
- name: Install UV
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Runtime environment
run: |
echo "procs=$(nproc)" >> $GITHUB_ENV
echo "${{ github.workspace }}/.local/bin" >> $GITHUB_PATH
- name: Build pyosys
run: |
make config-clang
echo "ENABLE_VERIFIC := 1" >> Makefile.conf
echo "ENABLE_VERIFIC_EDIF := 1" >> Makefile.conf
echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf
echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 1" >> Makefile.conf
echo "ENABLE_CCACHE := 1" >> Makefile.conf
echo "ENABLE_PYOSYS := 1" >> Makefile.conf
echo "PYTHON_DESTDIR := /usr/lib/python3/site-packages" >> Makefile.conf
make -j$procs
- name: Install pyosys
run: |
make install DESTDIR=${GITHUB_WORKSPACE}/.local PREFIX=
- name: Run pyosys tests
run: |
export PYTHONPATH=${GITHUB_WORKSPACE}/.local/usr/lib/python3/site-packages:$PYTHONPATH
python3 tests/pyosys/run_tests.py

25
.github/workflows/update-flake-lock.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: update-flake-lock
on:
workflow_dispatch: # allows manual triggering
schedule:
- cron: '0 0 * * 0' # runs weekly on Sunday at 00:00
jobs:
lockfile:
if: github.repository == 'YosysHQ/Yosys'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: Update flake.lock
uses: DeterminateSystems/update-flake-lock@main
with:
token: ${{CI_CREATE_PR_TOKEN}}
pr-title: "Update flake.lock" # Title of PR to be created
pr-labels: | # Labels to be set on the PR
dependencies
automated

View file

@ -7,20 +7,20 @@ on:
jobs:
bump-version:
if: github.repository == 'YosysHQ/Yosys'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
persist-credentials: false
- name: Take last commit
id: log
run: echo "::set-output name=message::$(git log --no-merges -1 --oneline)"
- name: Take repository
id: repo
run: echo "::set-output name=message::$GITHUB_REPOSITORY"
run: echo "message=$(git log --no-merges -1 --oneline)" >> $GITHUB_OUTPUT
- name: Bump version
if: "!contains(steps.log.outputs.message, 'Bump version') && contains(steps.repo.outputs.message, 'YosysHQ/yosys')"
if: ${{ !contains(steps.log.outputs.message, 'Bump version') }}
run: |
make bumpversion
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
@ -28,7 +28,7 @@ jobs:
git add Makefile
git commit -m "Bump version"
- name: Push changes # push the output folder to your repo
if: "!contains(steps.log.outputs.message, 'Bump version') && contains(steps.repo.outputs.message, 'YosysHQ/yosys')"
if: ${{ !contains(steps.log.outputs.message, 'Bump version') }}
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,37 +0,0 @@
name: Visual Studio Build
on: [push, pull_request]
jobs:
yosys-vcxsrc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Cache sources
id: cache-sources
uses: actions/cache@v2
with:
path: .
key: cache-yosys
- name: Build
run: make vcxsrc YOSYS_VER=latest
- uses: actions/upload-artifact@v2
with:
name: vcxsrc
path: yosys-win32-vcxsrc-latest.zip
build:
runs-on: windows-2019
needs: yosys-vcxsrc
steps:
- uses: actions/download-artifact@v2
with:
name: vcxsrc
path: .
- name: unzip
run: unzip yosys-win32-vcxsrc-latest.zip
- name: setup-msbuild
uses: microsoft/setup-msbuild@v1
- name: MSBuild
working-directory: yosys-win32-vcxsrc-latest
run: msbuild YosysVS.sln /p:PlatformToolset=v142 /p:Configuration=Release /p:WindowsTargetPlatformVersion=10.0.17763.0

136
.github/workflows/wheels.yml vendored Normal file
View file

@ -0,0 +1,136 @@
name: Build Wheels for PyPI
# run every Sunday at 10 AM
on:
workflow_dispatch:
schedule:
- cron: "0 10 * * 0"
jobs:
build_wheels:
strategy:
fail-fast: false
matrix:
os: [
{
name: "Ubuntu 22.04",
family: "linux",
runner: "ubuntu-22.04",
archs: "x86_64",
},
{
name: "Ubuntu 22.04",
family: "linux",
runner: "ubuntu-22.04-arm",
archs: "aarch64",
},
{
name: "macOS 15 x64",
family: "macos",
runner: "macos-15-intel",
archs: "x86_64",
},
{
name: "macOS 15 arm64",
family: "macos",
runner: "macos-15",
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
persist-credentials: false
- uses: actions/setup-python@v5
- name: Get FFI
shell: bash
run: |
mkdir -p ffi
curl -L https://github.com/libffi/libffi/releases/download/v3.4.8/libffi-3.4.8.tar.gz | tar --strip-components=1 -xzC ffi
- if: ${{ matrix.os.family == 'linux' }}
name: "[Linux] Bison 3.8.2"
shell: bash
run: |
mkdir -p bison
curl -L https://ftpmirror.gnu.org/gnu/bison/bison-3.8.2.tar.gz | tar --strip-components=1 -xzC bison
## 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:$(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: >
OPTFLAGS=-O3
PKG_CONFIG_PATH=./ffi/pfx/lib/pkgconfig
PATH="$PWD/bison/src:$PATH"
CIBW_ENVIRONMENT_MACOS: >
OPTFLAGS=-O3
PKG_CONFIG_PATH=./ffi/pfx/lib/pkgconfig
MACOSX_DEPLOYMENT_TARGET=11
makeFlags='CONFIG=clang'
PATH="$PWD/bison/src:$PATH"
CIBW_BEFORE_BUILD: bash ./.github/workflows/wheels/cibw_before_build.sh
CIBW_TEST_COMMAND: python3 {project}/tests/pyosys/run_tests.py
- uses: actions/upload-artifact@v4
with:
name: python-wheels-${{ matrix.os.runner }}
path: ./wheelhouse/*.whl
upload_wheels:
name: Upload Wheels
if: (github.repository == 'YosysHQ/Yosys') && (github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
# Specifying a GitHub environment is optional, but strongly encouraged
environment: pypi
permissions:
# IMPORTANT: this permission is mandatory for Trusted Publishing
id-token: write
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

View file

@ -0,0 +1,52 @@
#!/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
from pathlib import Path
__yosys_root__ = Path(__file__).absolute().parents[3]
for source in ["ffi", "bison"]:
if not (__yosys_root__ / source).is_dir():
print(
"You need to download ffi and bison in a similar manner to wheels.yml first."
)
exit(-1)
with open(__yosys_root__ / ".github" / "workflows" / "wheels.yml") as f:
workflow = yaml.safe_load(f)
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", platform.machine())
subprocess.check_call(["cibuildwheel"], env=env)

View file

@ -0,0 +1,37 @@
#!/bin/bash
set -e -x
# Build-time dependencies
## Linux Docker Images
if command -v yum &> /dev/null; then
yum install -y flex # manylinux's bison versions are hopelessly out of date
fi
if command -v apk &> /dev/null; then
apk add flex bison
fi
if ! printf '%s\n' '%require "3.8"' '%%' 'start: ;' | bison -o /dev/null /dev/stdin ; then
(
set -e -x
cd bison
./configure
make clean
make install -j$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu)
)
fi
## macOS/Windows -- installed in GitHub Action itself, not container
# Runtime Dependencies
## Build Static FFI (platform-dependent but not Python version dependent)
(
set -e -x
cd ffi
## Ultimate libyosys.so will be shared, so we need fPIC for the static libraries
CFLAGS=-fPIC CXXFLAGS=-fPIC ./configure --prefix=$PWD/pfx
make clean
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,13 @@
set -e
set -x
# Don't use Python objects from previous compiles
make clean-py
# 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

83
.gitignore vendored
View file

@ -1,23 +1,29 @@
## user config
/Makefile.conf
## homebrew
/Brewfile.lock.json
## build artifacts
/.git-abc-submodule-hash
# compiler intermediate files
*.o
*.d
.*.swp
*.dwo
*.gch
*.gcda
*.gcno
__pycache__
/.cproject
/.project
/.settings
/qtcreator.files
/qtcreator.includes
/qtcreator.config
/qtcreator.creator
/qtcreator.creator.user
/coverage.info
/coverage_html
/Makefile.conf
/abc
/viz.js
*.so.dSYM/
## test artifacts
**/run-test.mk
*.err
*.log
*.tmp
# compiler output files
/kernel/version_*.cc
/share
/yosys
/yosys.exe
/yosys.js
@ -28,16 +34,55 @@ __pycache__
/yosys-smtbmc
/yosys-smtbmc.exe
/yosys-smtbmc-script.py
/yosys-witness
/yosys-witness.exe
/yosys-witness-script.py
/yosys-filterlib
/yosys-filterlib.exe
/kernel/*.pyh
/kernel/python_wrappers.cc
/kernel/version_*.cc
/share
/yosys-win32-mxebin-*
/yosys-win32-vcxsrc-*
/yosysjs-*
/libyosys.so
# build directories
/tests/unit/bintest/
/tests/unit/objtest/
/tests/ystests
/build
/result
/dist
# pyosys
/kernel/*.pyh
/kernel/python_wrappers.cc
/ffi
/bison
/venv
/*.whl
/*.egg-info
# yosysjs dependency
/viz.js
# other
/coverage.info
/coverage_html
# these really belong in global gitignore since they're not specific to this project but rather to user tool choice
# but too many people don't have a global gitignore configured:
# https://docs.github.com/en/get-started/git-basics/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer
__pycache__
*~
.*.swp
/.cache
/.vscode
/.cproject
/.project
/.settings
/qtcreator.files
/qtcreator.includes
/qtcreator.config
/qtcreator.creator
/qtcreator.creator.user
/compile_commands.json

7
.gitmodules vendored Normal file
View file

@ -0,0 +1,7 @@
[submodule "abc"]
path = abc
url = https://github.com/YosysHQ/abc
# Don't use paths as names to avoid git archive problems
[submodule "cxxopts"]
path = libs/cxxopts
url = https://github.com/jarro2783/cxxopts

View file

@ -1,6 +1,6 @@
Marcelina Kościelnicka <mwk@0x04.net>
Marcelina Kościelnicka <mwk@0x04.net> <koriakin@0x04.net>
Marcelina Kościelnicka <mwk@0x04.net> <marcin@symbioticeda.com>
Wanda Phinode <wanda@phinode.net> <mwk@0x04.net>
Wanda Phinode <wanda@phinode.net> <koriakin@0x04.net>
Wanda Phinode <wanda@phinode.net> <marcin@symbioticeda.com>
Claire Xenia Wolf <claire@yosyshq.com> <claire@clairexen.net>
Claire Xenia Wolf <claire@yosyshq.com> <claire@symbioticeda.com>
Claire Xenia Wolf <claire@yosyshq.com> <clifford@symbioticeda.com>

20
.readthedocs.yaml Normal file
View file

@ -0,0 +1,20 @@
# .readthedocs.yaml
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
build:
os: ubuntu-22.04
tools:
python: '3.12'
formats:
- pdf
sphinx:
configuration: docs/source/conf.py
fail_on_warning: true
python:
install:
- requirements: docs/source/requirements.txt

View file

@ -6,7 +6,9 @@ brew "git"
brew "graphviz"
brew "pkg-config"
brew "python3"
brew "tcl-tk"
brew "uv"
brew "xdot"
brew "bash"
brew 'boost-python3'
brew "llvm@20"
brew "lld"
brew "googletest"

668
CHANGELOG
View file

@ -2,9 +2,675 @@
List of major changes and improvements between releases
=======================================================
Yosys 0.18 .. Yosys 0.18-dev
Yosys 0.60 .. Yosys 0.61-dev
--------------------------
Yosys 0.59 .. Yosys 0.60
--------------------------
* Various
- read_verilog: suport unsized parameters.
- Added static library compile option.
* New commands and options
- Added "sdc" pass for reading SDC files.
- Added experimental "sdc_expand" and "opensta" for OpenSTA integration.
- Added "icell_liberty" pass for used internal cells.
Yosys 0.58 .. Yosys 0.59
--------------------------
* Various
- Pyosys is rewritten using pybind11.
- alumacc: merge independent of sign.
- write_btor: Include $assert and $assume cells in -ywmap output.
- RTLIL parser rewritten for efficiency.
- Wildcards enabled for Liberty file consuming.
- timeest: Add top ports launching/sampling.
* New commands and options
- Added "-apply_derived_type" option to "box_derive" pass.
- Added "-publish_icells" option to "chtype" pass.
- Added "-width" option to "sim" pass.
- Added "sort" pass for sorting the design objects.
- Merged "synth_ecp5" and "synth_nexus" into "synth_lattice" pass.
- Added "-strict-gw5a-dffs" and "-setundef" options to "synth_gowin" pass.
Yosys 0.57 .. Yosys 0.58
--------------------------
* Various
- Run ABC passes in parallel.
- Extending support for buffer normalization.
- Overhaul of logging APIs.
- read_blif: Represent sequential elements with gate cells.
- Support multiple lib files in abc9_exe.
* New commands and options
- Added "-wireshape" option to "show" command to allow
control the shape of wire nodes.
- Added "-relativeshare" option to "read_verilog", "synth"
and "techmap" pass for synthesis reproducibility testing.
- "write_rtlil" pass no longer sorts design, added "-sort"
option to match old behavior
- Added "-sva-continue-on-err" to "verific" pass to allow
processing designs that includes unsupported SVA.
Yosys 0.56 .. Yosys 0.57
--------------------------
* New commands and options
- Added "-initstates" option to "abstract" pass.
- Added "-set-assumes" option to "equiv_induct"
and "equiv_simple" passes.
- Added "-always" option to "raise_error" pass.
- Added "-hierarchy" option to "stat" pass.
- Added "-noflatten" option to "synth_quicklogic" pass.
* Various
- smtbmc: Support skipping steps in cover mode.
- write_btor: support $buf.
- read_verilog: support package import.
Yosys 0.55 .. Yosys 0.56
--------------------------
* New commands and options
- Added "-unescape" option to "rename" pass.
- Added "-assert2cover" option to "chformal" pass.
- Added "linecoverage" pass to generate lcov report from selection.
- Added "opt_hier" pass to enable hierarchical optimization.
- Added "-hieropt" option to "synth" pass.
- Added "-expect-return", "-err-grep" and "-suffix" options
to "bugpoint" pass.
- Added "raise_error" dev pass.
* Various
- Added groups to command reference documentation.
- Added bugpoint guide to documentation.
- verific: correctly reset Verific flags after import.
Yosys 0.54 .. Yosys 0.55
--------------------------
* Various
- read_verilog: Implemented SystemVerilog unique/priority if.
- "attrmap" pass is able to alter memory attributes.
- verific: Support SVA followed-by operator in cover mode.
Yosys 0.53 .. Yosys 0.54
--------------------------
* New commands and options
- Added "-genlib" option to "abc_new" and "abc9_exe" passes.
- Added "-verbose" and "-quiet" options to "libcache" pass.
- Added "-no-sort" option to "write_aiger" pass.
* Various
- Added "muldiv_c" peepopt.
- Accept (and ignore) SystemVerilog unique/priority if.
- "read_verilog" copy inout ports in and out of functions/tasks.
- Enable single-bit vector wires in RTLIL.
* Xilinx support
- Single-port URAM mapping to support memories 2048 x 144b
Yosys 0.52 .. Yosys 0.53
--------------------------
* New commands and options
- Added "constmap" pass for technology mapping of coarse constant value.
- Added "timeest" pass to estimate the critical path in clock domain.
- Added "-blackbox" option to "cutpoint" pass to cut all instances of
blackboxes.
- Added "-noscopeinfo" option to "cutpoint" pass.
- Added "-nocleanup" option to "flatten" pass to prevent removal of
unused submodules.
- Added "-declockgate" option to "formalff" pass that turns clock
gating into clock enables.
* Various
- Added "$scopeinfo" cells to preserve information during "cutpoint" pass.
- Added dataflow tracking documentation.
- share: Restrict activation patterns to potentially relevant signal.
- liberty: More robust parsing.
- verific: bit blast RAM if using mem2reg attribute.
Yosys 0.51 .. Yosys 0.52
--------------------------
* New commands and options
- Added "-pattern-limit" option to "share" pass to limit analysis effort.
- Added "libcache" pass to control caching of technology library
data parsed from liberty files.
- Added "read_verilog_file_list" to parse verilog file list.
* Various
- Added $macc_v2 cell.
- Improve lexer performance and zlib support for "read_liberty".
- opt_expr: optimize pow of 2 cells.
Yosys 0.50 .. Yosys 0.51
--------------------------
* New commands and options
- Added "abstract" pass to allow reducing and never increasing
the constraints on a circuit's behavior in a formal verification setting.
* Various
- "splitcells" pass now splits "aldff" cells.
- FunctionalIR documentation
* QuickLogic support
- Added IOFF inference for qlf_k6n10f
* Intel support
- Fixed RAM and DSP support.
- Overall performance improvement for "synth_intel".
Yosys 0.49 .. Yosys 0.50
--------------------------
* Various
- "write_verilog" emits "$check" cell names as labels.
Yosys 0.48 .. Yosys 0.49
--------------------------
* Various
- "$scopeinfo" cells are now part of JSON export by default.
- Added option to specify hierarchical separator for "flatten".
- Improved "wreduce" to handle more cases of operator size reduction.
- Updated hashing interface, see docs/source/yosys_internals/hashing.rst
for breaking API changes.
* New commands and options
- Added "-noscopeinfo" option to "json" and "write_json" pass.
Yosys 0.47 .. Yosys 0.48
--------------------------
* Various
- Removed "read_ilang" deprecated pass.
- Enhanced boxing features in the experimental "abc_new" command.
- Added new Tcl methods for design inspection.
- Added clock enable inference to "dfflibmap".
- Added a Han-Carlson and Sklansky option for $lcu mapping.
* New commands and options
- Added "-nopeepopt" option to "clk2fflogic" pass.
- Added "-liberty" and "-dont_use" options to "clockgate" pass.
- Added "-ignore_buses" option to "read_liberty" pass.
- Added "-dont_map" option to "techmap" pass.
- Added "-selected" option to "write_json" pass.
- Added "wrapcell" command for creating wrapper modules
around selected cells.
- Added "portarcs" command for deriving propagation timing arcs.
- Added "setenv" command for setting environment variables.
* Gowin support
- Added "-family" option to "synth_gowin" pass.
- Cell definitions split by family.
* Verific support
- Improved blackbox support.
Yosys 0.46 .. Yosys 0.47
--------------------------
* Various
- Added cxxopts library for handling command line arguments.
- Added docs generation from cells help output.
* New commands and options
- Added "-json" option to "synth_xilinx" pass.
- Added "-derive_luts" option to "cellmatch" pass.
- Added "t:@<name>" syntax to "select" pass.
- Added "-list-mod" option to "select" pass.
- Removed deprecated "qwp" pass.
* Verific support
- Initial state handling for VHDL assertions.
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
- Added cell types help messages.
* New back-ends
- Added initial NG-Ultra support. ( synth_nanoxplore )
Yosys 0.43 .. Yosys 0.44
--------------------------
* Various
- Added ENABLE_LTO compile option to enable link time
optimizations.
- Build support for Haiku OS.
* New commands and options
- Added "keep_hierarchy" pass to add attribute with
same name to modules based on cost.
- Added options "-noopt","-bloat" and "-check_cost" to
"test_cell" pass.
* New back-ends
- Added initial PolarFire support. ( synth_microchip )
Yosys 0.42 .. Yosys 0.43
--------------------------
* Various
- C++ compiler with C++17 support is required.
- Support for IO liberty files for verification.
- Limit padding from shiftadd for "peepopt" pass.
* Verific support
- Support building Yosys with various Verific library
configurations. Can be built now without YosysHQ
specific patch and extension library.
Yosys 0.41 .. Yosys 0.42
--------------------------
* New commands and options
- Added "box_derive" pass to derive box modules.
- Added option "assert-mod-count" to "select" pass.
- Added option "-header","-push" and "-pop" to "log" pass.
* Intel support
- Dropped Quartus support in "synth_intel_alm" pass.
Yosys 0.40 .. Yosys 0.41
--------------------------
* New commands and options
- Added "cellmatch" pass for picking out standard cells automatically.
* Various
- Extended the experimental incremental JSON API to allow arbitrary
smtlib subexpressions.
- Added support for using ABCs library merging when providing multiple
liberty files.
* Verific support
- Expose library name as module attribute.
Yosys 0.39 .. Yosys 0.40
--------------------------
* New commands and options
- Added option "-vhdl2019" to "read" and "verific" pass.
* Various
- Major documentation overhaul.
- Added port statistics to "stat" command.
- Added new formatting features to cxxrtl backend.
* Verific support
- Added better support for VHDL constants import.
- Added support for VHDL 2009.
Yosys 0.38 .. Yosys 0.39
--------------------------
* New commands and options
- Added option "-extra-map" to "synth" pass.
- Added option "-dont_use" to "dfflibmap" pass.
- Added option "-href" to "show" command.
- Added option "-noscopeinfo" to "flatten" pass.
- Added option "-scopename" to "flatten" pass.
* SystemVerilog
- Added support for packed multidimensional arrays.
* Various
- Added "$scopeinfo" cells to preserve information about
the hierarchy during flattening.
- Added sequential area output to "stat -liberty".
- Added ability to record/replay diagnostics in cxxrtl backend.
* Verific support
- Added attributes to module instantiation.
Yosys 0.37 .. Yosys 0.38
--------------------------
* New commands and options
- Added option "-tech" to "opt_lut" pass.
- Added option "-nokeep_prints" to "hierarchy" pass.
- Added option "-nolower" to "async2sync" and "clk2fflogic" pass.
- Added option "-lower" to "chformal" pass.
* Various
- Added $check cell to represent assertions with messages.
- Allow capturing $print cell output in CXXRTL.
- Added API to overwrite existing pass from plugin.
- Follow the XDG Base Directory Specification for storing history files.
- Without a known top module, derive all deferred modules (hierarchy pass).
- Detect and error out on combinational loops in write_aiger.
* Verific support
- Added option "-no-split-complex-ports" to "verific -import".
Yosys 0.36 .. Yosys 0.37
--------------------------
* New commands and options
- Added option "-nodisplay" to read_verilog.
* SystemVerilog
- Correct hierarchical path names for structs and unions.
* Various
- Print hierarchy for failed assertions in "sim" pass.
- Add "--present-only" option to "yosys-witness" to omit unused signals.
- Implement a generic record/replay interface for CXXRTL.
- Improved readability of emitted code with "write_verilog".
Yosys 0.35 .. Yosys 0.36
--------------------------
* New commands and options
- Added option "--" to pass arguments down to tcl when using -c option.
- Added ability on MacOS and Windows to pass options after arguments on cli.
- Added option "-cmp2softlogic" to synth_lattice.
- Added option "-lowpower" to "booth" pass.
* QuickLogic support
- Added "K6N10f" support.
- Added "-nodsp", "-nocarry", "-nobram" and "-bramtypes" options to
"synth_quicklogic" pass.
- Added "ql_bram_merge" pass to merge 18K BRAM cells into TDP36K.
- Added "ql_bram_types" pass to change TDP36K depending on configuration.
- Added "ql_dsp_io_regs" pass to update QL_DSP2 depending on configuration.
- Added "ql_dsp_macc" pass to infer multiplier-accumulator DSP cells.
- Added "ql_dsp_simd" pass to merge DSP pairs to operate in SIMD mode.
* ECP5,iCE40 and Gowin support
- Enabled abc9 by default, added "-noabc9" option to disable.
* MachXO3 support
- Quality of results improvements.
- Enabled "booth" pass by default for it in "synth_lattice".
* Various
- Improved "peepopt" by adding shiftadd pattern support.
- Added "--incremental" mode to smtbmc.
Yosys 0.34 .. Yosys 0.35
--------------------------
* Various
- Improvements on "peepopt" shiftmul matcher.
- Improvements on "ram_style" attributes handling.
* Verific support
- Improved static elaboration for VHDL and mixed HDL designs.
- Expose "hdlname" attribute with original module name.
- Expose "architecture" attribute with VHDL architecture name.
Yosys 0.33 .. Yosys 0.34
--------------------------
* New commands and options
- Added option "-assert" to "sim" pass.
- Added option "-noinitstate" to "sim" pass.
- Added option "-dont_use" to "abc" pass.
- Added "dft_tag" pass to create tagging logic for data flow tracking.
- Added "future" pass to resolve future sampled value functions.
- Added "booth" pass to map $mul cells to Booth multipliers.
- Added option "-booth" to "synth" pass.
* SystemVerilog
- Added support for assignments within expressions, e.g., `x[y++] = z;` or
`x = (y *= 2) - 1;`.
* Verific support
- "src" attribute contain full location info.
- module parameters are kept after import.
- accurate access order semantics in memory inference.
- better "bind" support for mixed language projects.
* Various
- "show" command displays dot instead of box for wire aliases.
Yosys 0.32 .. Yosys 0.33
--------------------------
* Various
- Added "$print" cell, produced by "$display" and "$write"
Verilog tasks.
- Added "$print" cell handling in CXXRTL.
* Lattice FPGA support
- Added generic "synth_lattice" pass (for now MachXO2/XO3/XO3D)
- Removed "synth_machxo2" pass
- Pass "ecp5_gsr" renamed to "lattice_gsr"
- "synth_machxo2" equivalent is "synth_lattice -family xo2"
Yosys 0.31 .. Yosys 0.32
--------------------------
* Verific support
- Added sub option "-lib" to reading commands for VHDL and
SystemVerilog, that will later import all units/modules from
marked files as blackboxes.
* Various
- Added support for $lt, $le, $gt, $ge to the code generating AIGs.
Yosys 0.30 .. Yosys 0.31
--------------------------
* New commands and options
- Added option "-lsbidx" to "write_edif" pass.
* Various
- Added support for $divfloor operator to cxxrtl backend.
- dfflegalize: allow setting mince and minsrst args from scratchpad.
Yosys 0.29 .. Yosys 0.30
--------------------------
* New commands and options
- Added "recover_names" pass to recover names post-mapping.
* Gowin support
- Added remaining primitives blackboxes.
* Various
- "show -colorattr" will now color the cells, wires, and
connection arrows.
- "show -viewer none" will not execute viewer.
Yosys 0.28 .. Yosys 0.29
--------------------------
* New commands and options
- Added "synthprop" pass for synthesizable properties.
* Verific support
- Handle conditions on clocked concurrent assertions in unclocked
procedural contexts.
* Verilog
- Fix const eval of unbased unsized constants.
- Handling of attributes for struct / union variables.
Yosys 0.27 .. Yosys 0.28
--------------------------
* Verilog
- Out of bounds checking for struct/union members.
* Verific support
- Fix enum_values support and signed attribute values.
* ECP5 support
- Added "synth_ecp5 -iopad"
* MachXO2 support
- Added "synth_machxo2 -ccu2"
Yosys 0.26 .. Yosys 0.27
--------------------------
* New commands and options
- Added option "-make_assert" to "equiv_make" pass.
- Added option "-coverenable" to "chformal" pass.
* Verilog
- Resolve package types in interfaces.
- Handle range offsets in packed arrays within packed structs.
- Support for data and array queries on struct/union item expressions.
* GateMate support
- Enable register initialization.
Yosys 0.25 .. Yosys 0.26
--------------------------
* New commands and options
- Added "bwmuxmap" pass to replace $bwmux cells with equivalent logic.
- Added "xprop" experimental pass for formal x propagation.
- Added "splitcells" pass to split up multi-bit cells.
- Added "viz" pass to visualize data flow graph.
- Added option "-make_cover" to "miter" pass.
- Added option "-noparallelcase" to "write_verilog" pass.
- Added option "-chain" to "insbuf" pass.
- Added options "-hierarchy" and "-assume" to "formalff" pass.
- Added options "-append" and "-summary" to "sim" pass.
- Added option "-ywmap" to "write_btor" pass.
- Added option "-ignore-self-reset" to "fsm_detect" pass.
* Verilog
- Support for struct members of union type.
- Support for struct member package types.
* Various
- Added Yosys witness (.yw) cosimulation.
- GCC 4.8 is deprecated, compiler with full C++11 support is required.
Yosys 0.24 .. Yosys 0.25
--------------------------
* Verific support
- Respect "noblackbox" attribute for modules.
* Various
- Documentation is hosted at https://yosyshq.readthedocs.io/projects/yosys/en/latest/
Yosys 0.23 .. Yosys 0.24
--------------------------
* New commands and options
- Added option "-set-def-formal" to "sat" pass.
- Added option "-s" to "tee" command.
* Verilog
- Support for module-scoped identifiers referring to tasks and functions.
- Support for arrays with swapped ranges within structs.
* Verific support
- Support for importing verilog configurations per name.
- "verific -set-XXXXX" commands are now able to set severity to all messages
of certain type (errors, warnings, infos and comments)
* Various
- TCL shell support (use "yosys -C")
- Added FABulous eFPGA frontend
Yosys 0.22 .. Yosys 0.23
--------------------------
* New commands and options
- Added option "-cross" to "miter" pass.
- Added option "-nocheck" to "equiv_opt" pass.
* Formal Verification
- yosys-smtbmc: Added "--detect-loops" option for checking if states are
unique in temporal induction counter examples.
* Verific support
- Added support for reading Liberty files using Verific library.
(Optinally enabled with ENABLE_VERIFIC_LIBERTY)
- Added option "-cells" to "verific -import" enabling import of
all cells from verific design.
* Various
- MinGW build (Windows) plugin support.
- Added YOSYS_ABORT_ON_LOG_ERROR environment variable for debugging.
Setting it to 1 causes abort() to be called when Yosys terminates with an
error message.
Yosys 0.21 .. Yosys 0.22
--------------------------
* Verific support
- Added support for here-document for "verific" command (for reading
source files).
- Added support for reading EDIF files using Verific library.
(Optinally enabled with ENABLE_VERIFIC_EDIF)
* Various
- Added tech specific utilization to "stat" json.
Yosys 0.20 .. Yosys 0.21
--------------------------
* New commands and options
- Added "formalff" pass - transforms FFs for formal verification
- Added option "-formal" to "memory_map" pass
- Added option "-witness" to "rename" - give public names to all signals
present in yosys witness traces
- Added option "-hdlname" to "sim" pass - preserves hiearachy when writing
simulation output for a flattened design
- Addded option "-scramble-name" to "rename" pass
* Formal Verification
- Added $anyinit cell to directly represent FFs with an unconstrained
initialization value. These can be generated by the new formalff pass.
- New JSON based yosys witness format for formal verification traces.
- yosys-smtbmc: Reading and writing of yosys witness traces.
- write_smt2: Emit inline metadata to support yosys witness trace.
- yosys-witness is a new tool to inspect and convert yosys witness traces.
- write_aiger: Option to write a map file for yosys witness trace
conversion.
- yosys-witness: Conversion from and to AIGER witness traces.
* Verific support
- Filename re-writing support for "verific" pass.
* Various
- ABC performance improvements
- Filename re-writing added for "show -lib".
* SmartFusion2 support
- Added $alu support
- Added SYSRESET and XTLOSC cells
- Compatible now with LiberoSoc flow
Yosys 0.19 .. Yosys 0.20
--------------------------
* New commands and options
- Added option "-wb" to "read_liberty" pass
* Various
- Added support for $modfloor operator to cxxrtl backend
- Support build on OpenBSD
- Fixed smt2 backend use of $shift/$shiftx with negative shift amounts,
which affects bit/part-select assignments with a dynamic index. Shift
operators were not affected.
* Verific support
- Proper import of port ranges into Yosys, may result in reversed
bit-order of top-level ports for some synthesis flows.
Yosys 0.18 .. Yosys 0.19
--------------------------
* New commands and options
- Added option "-rom-only" to "memory_libmap" pass
- Added option "-smtcheck" to "hierarchy" pass
- Added option "-keepdc" to "memory_libmap" pass
- Added option "-suffix" to "rename" pass
- Added "gatemate_foldinv" pass
* Formal Verification
- Added support for $pos cell in btor backend
- Added the "smtlib2_module" and "smtlib2_comb_expr" attributes
* GateMate support
- Added LUT tree mapping
* Verific support
- Added option "-pp" to "verific -import"
Yosys 0.17 .. Yosys 0.18
--------------------------
* Various

View file

@ -10,6 +10,7 @@
# PATH (can use glob) USERNAME(S)
CODEOWNERS @nakengelhardt
passes/cmds/scratchpad.cc @nakengelhardt
frontends/rpc/ @whitequark
backends/cxxrtl/ @whitequark
@ -18,7 +19,9 @@ passes/techmap/flowmap.cc @whitequark
passes/opt/opt_lut.cc @whitequark
passes/techmap/abc9*.cc @eddiehung @Ravenslofty
backends/aiger/xaiger.cc @eddiehung
docs/ @KrystalDelusion
docs/source/using_yosys/synthesis/abc.rst @KrystalDelusion @Ravenslofty
.github/workflows/*.yml @mmicko
## External Contributors
# Only users with write permission to the repository get review
@ -27,15 +30,16 @@ backends/aiger/xaiger.cc @eddiehung
# These still override previous lines, so be careful not to
# accidentally disable any of the above rules.
frontends/verilog/ @zachjs
frontends/ast/ @zachjs
frontends/verilog/ @widlarizer
frontends/ast/ @widlarizer
techlibs/intel_alm/ @Ravenslofty
techlibs/gowin/ @pepijndevos
techlibs/gatemate/ @pu-cc
# pyosys
misc/*.py @btut
pyosys/* @donn
setup.py @donn
backends/firrtl @ucbjrl @azidar

128
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at contact@yosyshq.com and/or
claire@clairexen.net.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

70
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,70 @@
# Introduction
Thanks for thinking about contributing to the Yosys project. If this is your
first time contributing to an open source project, please take a look at the
following guide:
https://opensource.guide/how-to-contribute/#orienting-yourself-to-a-new-project.
Information about the Yosys coding style is available on our Read the Docs:
https://yosys.readthedocs.io/en/latest/yosys_internals/extending_yosys/contributing.html.
# Using the issue tracker
The [issue tracker](https://github.com/YosysHQ/yosys/issues) is used for
tracking bugs or other problems with Yosys or its documentation. It is also the
place to go for requesting new features.
When [creating a new issue](https://github.com/YosysHQ/yosys/issues/new/choose),
we have a few templates available. Please make use of these! It will make it
much easier for someone to respond and help.
### Bug reports
Before you submit an issue, please check out the [how-to guide for
`bugpoint`](https://yosys.readthedocs.io/en/latest/using_yosys/bugpoint.html).
This guide will take you through the process of using the [`bugpoint`
command](https://yosys.readthedocs.io/en/latest/cmd/bugpoint.html) in Yosys to
produce a [minimal, complete and verifiable
example](https://stackoverflow.com/help/minimal-reproducible-example) (MVCE).
Providing an MVCE with your bug report drastically increases the likelihood that
someone will be able to help resolve your issue.
# Using pull requests
If you are working on something to add to Yosys, or fix something that isn't
working quite right, make a [PR](https://github.com/YosysHQ/yosys/pulls)! An
open PR, even as a draft, tells everyone that you're working on it and they
don't have to. It can also be a useful way to solicit feedback on in-progress
changes. See below to find the best way to [ask us
questions](#asking-questions).
In general, all changes to the code are done as a PR, with [Continuous
Integration (CI)](https://github.com/YosysHQ/yosys/actions) tools that
automatically run the full suite of tests compiling and running Yosys. Please
make use of this! If you're adding a feature: add a test! Not only does it
verify that your feature is working as expected, but it can also be a handy way
for people to see how the feature is used. If you're fixing a bug: add a test!
If you can, do this first; it's okay if the test starts off failing - you
already know there is a bug. CI also helps to make sure that your changes still
work under a range of compilers, settings, and targets.
### Labels
We use [labels](https://github.com/YosysHQ/yosys/labels) to help categorise
issues and PRs. If a label seems relevant to your work, please do add it; this
also includes the labels beggining with 'status-'. The 'merge-' labels are used
by maintainers for tracking and communicating which PRs are ready and pending
merge; please do not use these labels if you are not a maintainer.
# Asking questions
If you have a question about how to use Yosys, please ask on our [Discourse forum](https://yosyshq.discourse.group/) or in our [discussions
page](https://github.com/YosysHQ/yosys/discussions).
The Discourse is also a great place to ask questions about developing or
contributing to Yosys.
We have open [dev 'jour fixe' (JF) meetings](https://docs.google.com/document/d/1SapA6QAsJcsgwsdKJDgnGR2mr97pJjV4eeXg_TVJhRU/edit?usp=sharing) where developers from YosysHQ and the
community come together to discuss open issues and PRs. This is also a good
place to talk to us about how to implement larger PRs.

View file

@ -1,6 +1,6 @@
ISC License
Copyright (C) 2012 - 2020 Claire Xenia Wolf <claire@yosyshq.com>
Copyright (C) 2012 - 2025 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

View file

@ -8,6 +8,7 @@ RUN apt-get update -qq \
&& DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
ca-certificates \
clang \
lld \
curl \
libffi-dev \
libreadline-dev \

1029
Makefile

File diff suppressed because it is too large Load diff

520
README.md
View file

@ -1,22 +1,3 @@
```
yosys -- Yosys Open SYnthesis Suite
Copyright (C) 2012 - 2020 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.
```
yosys Yosys Open SYnthesis Suite
===================================
@ -33,6 +14,10 @@ Yosys is free software licensed under the ISC license (a GPL
compatible license that is similar in terms to the MIT license
or the 2-clause BSD license).
Third-party software distributed alongside this software
is licensed under compatible licenses.
Please refer to `abc` and `libs` subdirectories for their license terms.
Web Site and Other Resources
============================
@ -40,17 +25,20 @@ Web Site and Other Resources
More information and documentation can be found on the Yosys web site:
- https://yosyshq.net/yosys/
The "Documentation" page on the web site contains links to more resources,
including a manual that even describes some of the Yosys internals:
- https://yosyshq.net/yosys/documentation.html
If you have any Yosys-related questions, please post them on the Discourse group:
- https://yosyshq.discourse.group
The directory `guidelines` contains additional information
for people interested in using the Yosys C++ APIs.
Documentation from this repository is automatically built and available on Read
the Docs:
- https://yosyshq.readthedocs.io/projects/yosys
Users interested in formal verification might want to use the formal verification
front-end for Yosys, SymbiYosys:
- https://symbiyosys.readthedocs.io/en/latest/
- https://github.com/YosysHQ/SymbiYosys
Users interested in formal verification might want to use the formal
verification front-end for Yosys, SBY:
- https://yosyshq.readthedocs.io/projects/sby/
- https://github.com/YosysHQ/sby
The Yosys blog has news and articles from users:
- https://blog.yosyshq.com
Installation
@ -68,51 +56,54 @@ For more information about the difference between Tabby CAD Suite and the OSS CA
Many Linux distributions also provide Yosys binaries, some more up to date than others. Check with your package manager!
Building from Source
====================
You need a C++ compiler with C++11 support (up-to-date CLANG or GCC is
For more details, and instructions for other platforms, check [building from
source](https://yosyshq.readthedocs.io/projects/yosys/en/latest/getting_started/installation.html#building-from-source)
on Read the Docs.
When cloning Yosys, some required libraries are included as git submodules. Make
sure to call e.g.
$ git clone --recurse-submodules https://github.com/YosysHQ/yosys.git
or
$ git clone https://github.com/YosysHQ/yosys.git
$ cd yosys
$ git submodule update --init --recursive
You need a C++ compiler with C++17 support (up-to-date CLANG or GCC is
recommended) and some standard tools such as GNU Flex, GNU Bison, and GNU Make.
TCL, readline and libffi are optional (see ``ENABLE_*`` settings in Makefile).
Xdot (graphviz) is used by the ``show`` command in yosys to display schematics.
For example on Ubuntu Linux 16.04 LTS the following commands will install all
For example on Ubuntu Linux 22.04 LTS the following commands will install all
prerequisites for building yosys:
$ sudo apt-get install build-essential clang bison flex \
libreadline-dev gawk tcl-dev libffi-dev git \
graphviz xdot pkg-config python3 libboost-system-dev \
libboost-python-dev libboost-filesystem-dev zlib1g-dev
$ sudo apt-get install gawk git make python3 lld bison clang flex \
libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev \
graphviz xdot
$ curl -LsSf https://astral.sh/uv/install.sh | sh
Similarily, on Mac OS X Homebrew can be used to install dependencies (from within cloned yosys repository):
$ brew tap Homebrew/bundle && brew bundle
or MacPorts:
$ sudo port install bison flex readline gawk libffi \
git graphviz pkgconfig python36 boost zlib tcl
On FreeBSD use the following command to install all prerequisites:
# pkg install bison flex readline gawk libffi\
git graphviz pkgconf python3 python36 tcl-wrapper boost-libs
On FreeBSD system use gmake instead of make. To run tests use:
% MAKE=gmake CC=cc gmake test
For Cygwin use the following command to install all prerequisites, or select these additional packages:
setup-x86_64.exe -q --packages=bison,flex,gcc-core,gcc-g++,git,libffi-devel,libreadline-devel,make,pkg-config,python3,tcl-devel,boost-build,zlib-devel
To configure the build system to use a specific compiler, use one of
The environment variable `CXX` can be used to control the C++ compiler used, or
run one of the following to override it:
$ make config-clang
$ make config-gcc
For other compilers and build configurations it might be
necessary to make some changes to the config section of the
Makefile.
The Makefile has many variables influencing the build process. These can be
adjusted by modifying the Makefile.conf file which is created at the `make
config-...` step (see above), or they can be set by passing an option to the
make command directly:
$ make CXX=$CXX
For other compilers and build configurations it might be necessary to make some
changes to the config section of the Makefile. It's also an alternative way to
set the make variables mentioned above.
$ vi Makefile # ..or..
$ vi Makefile.conf
@ -122,10 +113,9 @@ To build Yosys simply type 'make' in this directory.
$ make
$ sudo make install
Note that this also downloads, builds and installs ABC (using yosys-abc
as executable name).
Tests are located in the tests subdirectory and can be executed using the test target. Note that you need gawk as well as a recent version of iverilog (i.e. build from git). Then, execute tests via:
Tests are located in the tests subdirectory and can be executed using the test
target. Note that you need gawk as well as a recent version of iverilog (i.e.
build from git). Then, execute tests via:
$ make test
@ -136,6 +126,7 @@ To use a separate (out-of-tree) build directory, provide a path to the Makefile.
Out-of-tree builds require a clean source tree.
Getting Started
===============
@ -156,9 +147,10 @@ reading and elaborating the design using the Verilog frontend:
yosys> read -sv tests/simple/fiedler-cooley.v
yosys> hierarchy -top up3down5
writing the design to the console in Yosys's internal format:
writing the design to the console in the RTLIL format used by Yosys
internally:
yosys> write_ilang
yosys> write_rtlil
convert processes (``always`` blocks) to netlist elements and perform
some simple optimizations:
@ -239,376 +231,56 @@ The command ``prep`` provides a good default word-level synthesis script, as
used in SMT-based formal verification.
Unsupported Verilog-2005 Features
=================================
The following Verilog-2005 features are not supported by
Yosys and there are currently no plans to add support
for them:
- Non-synthesizable language features as defined in
IEC 62142(E):2005 / IEEE Std. 1364.1(E):2002
- The ``tri``, ``triand`` and ``trior`` net types
- The ``config`` and ``disable`` keywords and library map files
Verilog Attributes and non-standard features
============================================
- The ``full_case`` attribute on case statements is supported
(also the non-standard ``// synopsys full_case`` directive)
- The ``parallel_case`` attribute on case statements is supported
(also the non-standard ``// synopsys parallel_case`` directive)
- The ``// synopsys translate_off`` and ``// synopsys translate_on``
directives are also supported (but the use of ``` `ifdef .. `endif ```
is strongly recommended instead).
- The ``nomem2reg`` attribute on modules or arrays prohibits the
automatic early conversion of arrays to separate registers. This
is potentially dangerous. Usually the front-end has good reasons
for converting an array to a list of registers. Prohibiting this
step will likely result in incorrect synthesis results.
- The ``mem2reg`` attribute on modules or arrays forces the early
conversion of arrays to separate registers.
- The ``nomeminit`` attribute on modules or arrays prohibits the
creation of initialized memories. This effectively puts ``mem2reg``
on all memories that are written to in an ``initial`` block and
are not ROMs.
- The ``nolatches`` attribute on modules or always-blocks
prohibits the generation of logic-loops for latches. Instead
all not explicitly assigned values default to x-bits. This does
not affect clocked storage elements such as flip-flops.
- The ``nosync`` attribute on registers prohibits the generation of a
storage element. The register itself will always have all bits set
to 'x' (undefined). The variable may only be used as blocking assigned
temporary variable within an always block. This is mostly used internally
by Yosys to synthesize Verilog functions and access arrays.
- The ``nowrshmsk`` attribute on a register prohibits the generation of
shift-and-mask type circuits for writing to bit slices of that register.
- The ``onehot`` attribute on wires mark them as one-hot state register. This
is used for example for memory port sharing and set by the fsm_map pass.
- The ``blackbox`` attribute on modules is used to mark empty stub modules
that have the same ports as the real thing but do not contain information
on the internal configuration. This modules are only used by the synthesis
passes to identify input and output ports of cells. The Verilog backend
also does not output blackbox modules on default. ``read_verilog``, unless
called with ``-noblackbox`` will automatically set the blackbox attribute
on any empty module it reads.
- The ``noblackbox`` attribute set on an empty module prevents ``read_verilog``
from automatically setting the blackbox attribute on the module.
- The ``whitebox`` attribute on modules triggers the same behavior as
``blackbox``, but is for whitebox modules, i.e. library modules that
contain a behavioral model of the cell type.
- The ``lib_whitebox`` attribute overwrites ``whitebox`` when ``read_verilog``
is run in `-lib` mode. Otherwise it's automatically removed.
- The ``dynports`` attribute is used by the Verilog front-end to mark modules
that have ports with a width that depends on a parameter.
- The ``hdlname`` attribute is used by some passes to document the original
(HDL) name of a module when renaming a module. It should contain a single
name, or, when describing a hierarchical name in a flattened design, multiple
names separated by a single space character.
- The ``keep`` attribute on cells and wires is used to mark objects that should
never be removed by the optimizer. This is used for example for cells that
have hidden connections that are not part of the netlist, such as IO pads.
Setting the ``keep`` attribute on a module has the same effect as setting it
on all instances of the module.
- The ``keep_hierarchy`` attribute on cells and modules keeps the ``flatten``
command from flattening the indicated cells and modules.
- The ``init`` attribute on wires is set by the frontend when a register is
initialized "FPGA-style" with ``reg foo = val``. It can be used during
synthesis to add the necessary reset logic.
- The ``top`` attribute on a module marks this module as the top of the
design hierarchy. The ``hierarchy`` command sets this attribute when called
with ``-top``. Other commands, such as ``flatten`` and various backends
use this attribute to determine the top module.
- The ``src`` attribute is set on cells and wires created by to the string
``<hdl-file-name>:<line-number>`` by the HDL front-end and is then carried
through the synthesis. When entities are combined, a new |-separated
string is created that contains all the string from the original entities.
- The ``defaultvalue`` attribute is used to store default values for
module inputs. The attribute is attached to the input wire by the HDL
front-end when the input is declared with a default value.
- The ``parameter`` and ``localparam`` attributes are used to mark wires
that represent module parameters or localparams (when the HDL front-end
is run in ``-pwires`` mode).
- Wires marked with the ``hierconn`` attribute are connected to wires with the
same name (format ``cell_name.identifier``) when they are imported from
sub-modules by ``flatten``.
- The ``clkbuf_driver`` attribute can be set on an output port of a blackbox
module to mark it as a clock buffer output, and thus prevent ``clkbufmap``
from inserting another clock buffer on a net driven by such output.
- The ``clkbuf_sink`` attribute can be set on an input port of a module to
request clock buffer insertion by the ``clkbufmap`` pass.
- The ``clkbuf_inv`` attribute can be set on an output port of a module
with the value set to the name of an input port of that module. When
the ``clkbufmap`` would otherwise insert a clock buffer on this output,
it will instead try inserting the clock buffer on the input port (this
is used to implement clock inverter cells that clock buffer insertion
will "see through").
- The ``clkbuf_inhibit`` is the default attribute to set on a wire to prevent
automatic clock buffer insertion by ``clkbufmap``. This behaviour can be
overridden by providing a custom selection to ``clkbufmap``.
- The ``invertible_pin`` attribute can be set on a port to mark it as
invertible via a cell parameter. The name of the inversion parameter
is specified as the value of this attribute. The value of the inversion
parameter must be of the same width as the port, with 1 indicating
an inverted bit and 0 indicating a non-inverted bit.
- The ``iopad_external_pin`` attribute on a blackbox module's port marks
it as the external-facing pin of an I/O pad, and prevents ``iopadmap``
from inserting another pad cell on it.
- The module attribute ``abc9_lut`` is an integer attribute indicating to
`abc9` that this module describes a LUT with an area cost of this value, and
propagation delays described using `specify` statements.
- The module attribute ``abc9_box`` is a boolean specifying a black/white-box
definition, with propagation delays described using `specify` statements, for
use by `abc9`.
- The port attribute ``abc9_carry`` marks the carry-in (if an input port) and
carry-out (if output port) ports of a box. This information is necessary for
`abc9` to preserve the integrity of carry-chains. Specifying this attribute
onto a bus port will affect only its most significant bit.
- The module attribute ``abc9_flop`` is a boolean marking the module as a
flip-flop. This allows `abc9` to analyse its contents in order to perform
sequential synthesis.
- The frontend sets attributes ``always_comb``, ``always_latch`` and
``always_ff`` on processes derived from SystemVerilog style always blocks
according to the type of the always. These are checked for correctness in
``proc_dlatch``.
- The cell attribute ``wildcard_port_conns`` represents wildcard port
connections (SystemVerilog ``.*``). These are resolved to concrete
connections to matching wires in ``hierarchy``.
- In addition to the ``(* ... *)`` attribute syntax, Yosys supports
the non-standard ``{* ... *}`` attribute syntax to set default attributes
for everything that comes after the ``{* ... *}`` statement. (Reset
by adding an empty ``{* *}`` statement.)
- In module parameter and port declarations, and cell port and parameter
lists, a trailing comma is ignored. This simplifies writing Verilog code
generators a bit in some cases.
- Modules can be declared with ``module mod_name(...);`` (with three dots
instead of a list of module ports). With this syntax it is sufficient
to simply declare a module port as 'input' or 'output' in the module
body.
- When defining a macro with `define, all text between triple double quotes
is interpreted as macro body, even if it contains unescaped newlines. The
triple double quotes are removed from the macro body. For example:
`define MY_MACRO(a, b) """
assign a = 23;
assign b = 42;
"""
- The attribute ``via_celltype`` can be used to implement a Verilog task or
function by instantiating the specified cell type. The value is the name
of the cell type to use. For functions the name of the output port can
be specified by appending it to the cell type separated by a whitespace.
The body of the task or function is unused in this case and can be used
to specify a behavioral model of the cell type for simulation. For example:
module my_add3(A, B, C, Y);
parameter WIDTH = 8;
input [WIDTH-1:0] A, B, C;
output [WIDTH-1:0] Y;
...
endmodule
module top;
...
(* via_celltype = "my_add3 Y" *)
(* via_celltype_defparam_WIDTH = 32 *)
function [31:0] add3;
input [31:0] A, B, C;
begin
add3 = A + B + C;
end
endfunction
...
endmodule
- The ``wiretype`` attribute is added by the verilog parser for wires of a
typedef'd type to indicate the type identifier.
- Various ``enum_value_{value}`` attributes are added to wires of an enumerated type
to give a map of possible enum items to their values.
- The ``enum_base_type`` attribute is added to enum items to indicate which
enum they belong to (enums -- anonymous and otherwise -- are
automatically named with an auto-incrementing counter). Note that enums
are currently not strongly typed.
- A limited subset of DPI-C functions is supported. The plugin mechanism
(see ``help plugin``) can be used to load .so files with implementations
of DPI-C routines. As a non-standard extension it is possible to specify
a plugin alias using the ``<alias>:`` syntax. For example:
module dpitest;
import "DPI-C" function foo:round = real my_round (real);
parameter real r = my_round(12.345);
endmodule
$ yosys -p 'plugin -a foo -i /lib/libm.so; read_verilog dpitest.v'
- Sized constants (the syntax ``<size>'s?[bodh]<value>``) support constant
expressions as ``<size>``. If the expression is not a simple identifier, it
must be put in parentheses. Examples: ``WIDTH'd42``, ``(4+2)'b101010``
- The system tasks ``$finish``, ``$stop`` and ``$display`` are supported in
initial blocks in an unconditional context (only if/case statements on
expressions over parameters and constant values are allowed). The intended
use for this is synthesis-time DRC.
- There is limited support for converting ``specify`` .. ``endspecify``
statements to special ``$specify2``, ``$specify3``, and ``$specrule`` cells,
for use in blackboxes and whiteboxes. Use ``read_verilog -specify`` to
enable this functionality. (By default these blocks are ignored.)
- The ``reprocess_after`` internal attribute is used by the Verilog frontend to
mark cells with bindings which might depend on the specified instantiated
module. Modules with such cells will be reprocessed during the ``hierarchy``
pass once the referenced module definition(s) become available.
Non-standard or SystemVerilog features for formal verification
==============================================================
- Support for ``assert``, ``assume``, ``restrict``, and ``cover`` is enabled
when ``read_verilog`` is called with ``-formal``.
- The system task ``$initstate`` evaluates to 1 in the initial state and
to 0 otherwise.
- The system function ``$anyconst`` evaluates to any constant value. This is
equivalent to declaring a reg as ``rand const``, but also works outside
of checkers. (Yosys also supports ``rand const`` outside checkers.)
- The system function ``$anyseq`` evaluates to any value, possibly a different
value in each cycle. This is equivalent to declaring a reg as ``rand``,
but also works outside of checkers. (Yosys also supports ``rand``
variables outside checkers.)
- The system functions ``$allconst`` and ``$allseq`` can be used to construct
formal exist-forall problems. Assumptions only hold if the trace satisfies
the assumption for all ``$allconst/$allseq`` values. For assertions and cover
statements it is sufficient if just one ``$allconst/$allseq`` value triggers
the property (similar to ``$anyconst/$anyseq``).
- Wires/registers declared using the ``anyconst/anyseq/allconst/allseq`` attribute
(for example ``(* anyconst *) reg [7:0] foobar;``) will behave as if driven
by a ``$anyconst/$anyseq/$allconst/$allseq`` function.
- The SystemVerilog tasks ``$past``, ``$stable``, ``$rose`` and ``$fell`` are
supported in any clocked block.
- The syntax ``@($global_clock)`` can be used to create FFs that have no
explicit clock input (``$ff`` cells). The same can be achieved by using
``@(posedge <netname>)`` or ``@(negedge <netname>)`` when ``<netname>``
is marked with the ``(* gclk *)`` Verilog attribute.
Supported features from SystemVerilog
=====================================
When ``read_verilog`` is called with ``-sv``, it accepts some language features
from SystemVerilog:
- The ``assert`` statement from SystemVerilog is supported in its most basic
form. In module context: ``assert property (<expression>);`` and within an
always block: ``assert(<expression>);``. It is transformed to an ``$assert`` cell.
- The ``assume``, ``restrict``, and ``cover`` statements from SystemVerilog are
also supported. The same limitations as with the ``assert`` statement apply.
- The keywords ``always_comb``, ``always_ff`` and ``always_latch``, ``logic``
and ``bit`` are supported.
- Declaring free variables with ``rand`` and ``rand const`` is supported.
- Checkers without a port list that do not need to be instantiated (but instead
behave like a named block) are supported.
- SystemVerilog packages are supported. Once a SystemVerilog file is read
into a design with ``read_verilog``, all its packages are available to
SystemVerilog files being read into the same design afterwards.
- typedefs are supported (including inside packages)
- type casts are currently not supported
- enums are supported (including inside packages)
- but are currently not strongly typed
- packed structs and unions are supported.
- SystemVerilog interfaces (SVIs) are supported. Modports for specifying whether
ports are inputs or outputs are supported.
Additional information
======================
The ``read_verilog`` command, used by default when calling ``read`` with Verilog
source input, does not perform syntax checking. You should instead lint your
source with another tool such as
[Verilator](https://www.veripool.org/verilator/) first, e.g. by calling
``verilator --lint-only``.
Building the documentation
==========================
Note that there is no need to build the manual if you just want to read it.
Simply download the PDF from https://yosyshq.net/yosys/documentation.html
instead.
Simply visit https://yosys.readthedocs.io/en/latest/ instead.
On Ubuntu, texlive needs these packages to be able to build the manual:
In addition to those packages listed above for building Yosys from source, the
following are used for building the website:
sudo apt-get install texlive-binaries
sudo apt-get install texlive-science # install algorithm2e.sty
sudo apt-get install texlive-bibtex-extra # gets multibib.sty
sudo apt-get install texlive-fonts-extra # gets skull.sty and dsfont.sty
sudo apt-get install texlive-publishers # IEEEtran.cls
$ sudo apt install pdf2svg faketime
Also the non-free font luximono should be installed, there is unfortunately
no Ubuntu package for this so it should be installed separately using
`getnonfreefonts`:
Or for MacOS, using homebrew:
wget https://tug.org/fonts/getnonfreefonts/install-getnonfreefonts
sudo texlua install-getnonfreefonts # will install to /usr/local by default, can be changed by editing BINDIR at MANDIR at the top of the script
getnonfreefonts luximono # installs to /home/user/texmf
$ brew install pdf2svg libfaketime
Then execute, from the root of the repository:
PDFLaTeX, included with most LaTeX distributions, is also needed during the
build process for the website. Or, run the following:
make manual
$ sudo apt install texlive-latex-base texlive-latex-extra latexmk
Notes:
Or for MacOS, using homebrew:
- To run `make manual` you need to have installed Yosys with `make install`,
otherwise it will fail on finding `kernel/yosys.h` while building
`PRESENTATION_Prog`.
$ brew install basictex
$ sudo tlmgr update --self
$ sudo tlmgr install collection-latexextra latexmk tex-gyre
The Python package, Sphinx, is needed along with those listed in
`docs/source/requirements.txt`:
$ pip install -U sphinx -r docs/source/requirements.txt
From the root of the repository, run `make docs`. This will build/rebuild yosys
as necessary before generating the website documentation from the yosys help
commands. To build for pdf instead of html, call
`make docs DOC_TARGET=latexpdf`.
It is recommended to use the `ENABLE_HELP_SOURCE` make option for Yosys builds
that will be used to build the documentation. This option enables source
location tracking for passes and improves the command reference through grouping
related commands and allowing for the documentation to link to the corresponding
source files. Without this, a warning will be raised during the Sphinx build
about `Found commands assigned to group unknown` and `make docs` is configured
to fail on warnings by default.

1
abc Submodule

@ -0,0 +1 @@
Subproject commit 799ba632239b2a4db2bacda81de4e6efdc486b0c

View file

@ -19,6 +19,9 @@
#include "kernel/yosys.h"
#include "kernel/sigtools.h"
#include "kernel/json.h"
#include "kernel/yw.h"
#include "libs/json11/json11.hpp"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
@ -51,6 +54,8 @@ struct AigerWriter
vector<pair<int, int>> aig_gates;
vector<int> aig_latchin, aig_latchinit, aig_outputs;
vector<SigBit> bit2aig_stack;
size_t next_loop_check = 1024;
int aig_m = 0, aig_i = 0, aig_l = 0, aig_o = 0, aig_a = 0;
int aig_b = 0, aig_c = 0, aig_j = 0, aig_f = 0;
@ -61,6 +66,10 @@ struct AigerWriter
dict<SigBit, int> init_inputs;
int initstate_ff = 0;
dict<SigBit, int> ywmap_clocks;
vector<Cell *> ywmap_asserts;
vector<Cell *> ywmap_assumes;
int mkgate(int a0, int a1)
{
aig_m++, aig_a++;
@ -76,6 +85,23 @@ struct AigerWriter
return it->second;
}
if (bit2aig_stack.size() == next_loop_check) {
for (size_t i = 0; i < next_loop_check; ++i)
{
SigBit report_bit = bit2aig_stack[i];
if (report_bit != bit)
continue;
for (size_t j = i; j < next_loop_check; ++j) {
report_bit = bit2aig_stack[j];
if (report_bit.is_wire() && report_bit.wire->name.isPublic())
break;
}
log_error("Found combinational logic loop while processing signal %s.\n", log_signal(report_bit));
}
next_loop_check *= 2;
}
bit2aig_stack.push_back(bit);
// NB: Cannot use iterator returned from aig_map.insert()
// since this function is called recursively
@ -96,6 +122,8 @@ struct AigerWriter
a = initstate_ff;
}
bit2aig_stack.pop_back();
if (bit == State::Sx || bit == State::Sz)
log_error("Design contains 'x' or 'z' bits. Use 'setundef' to replace those constants.\n");
@ -104,7 +132,7 @@ struct AigerWriter
return a;
}
AigerWriter(Module *module, bool zinit_mode, bool imode, bool omode, bool bmode, bool lmode) : module(module), zinit_mode(zinit_mode), sigmap(module)
AigerWriter(Module *module, bool no_sort, bool zinit_mode, bool imode, bool omode, bool bmode, bool lmode) : module(module), zinit_mode(zinit_mode), sigmap(module)
{
pool<SigBit> undriven_bits;
pool<SigBit> unused_bits;
@ -114,16 +142,47 @@ struct AigerWriter
if (wire->name.isPublic())
sigmap.add(wire);
// promote input wires
for (auto wire : module->wires())
if (wire->port_input)
sigmap.add(wire);
// promote output wires
for (auto wire : module->wires())
if (wire->port_output)
sigmap.add(wire);
// promote input wires
for (auto wire : module->wires())
if (wire->port_input)
sigmap.add(wire);
// handle ports
// provided the input_bits and output_bits don't get sorted they
// will be returned in reverse order, so add them in reverse to
// match
for (auto riter = module->ports.rbegin(); riter != module->ports.rend(); ++riter) {
auto *wire = module->wire(*riter);
for (int i = 0; i < GetSize(wire); i++)
{
SigBit wirebit(wire, i);
SigBit bit = sigmap(wirebit);
if (bit.wire == nullptr) {
if (wire->port_output) {
aig_map[wirebit] = (bit == State::S1) ? 1 : 0;
output_bits.insert(wirebit);
}
continue;
}
if (wire->port_input)
input_bits.insert(bit);
if (wire->port_output) {
if (bit != wirebit)
alias_map[wirebit] = bit;
output_bits.insert(wirebit);
}
}
}
// handle wires
for (auto wire : module->wires())
{
if (wire->attributes.count(ID::init)) {
@ -139,34 +198,27 @@ struct AigerWriter
SigBit wirebit(wire, i);
SigBit bit = sigmap(wirebit);
if (bit.wire == nullptr) {
if (wire->port_output) {
aig_map[wirebit] = (bit == State::S1) ? 1 : 0;
output_bits.insert(wirebit);
}
if (bit.wire == nullptr)
continue;
if (wire->port_input || wire->port_output)
continue;
}
undriven_bits.insert(bit);
unused_bits.insert(bit);
}
if (wire->port_input)
input_bits.insert(bit);
if (wire->port_output) {
if (bit != wirebit)
alias_map[wirebit] = bit;
output_bits.insert(wirebit);
if (wire->width == 1) {
auto gclk_attr = wire->attributes.find(ID::replaced_by_gclk);
if (gclk_attr != wire->attributes.end()) {
SigBit bit = sigmap(wire);
if (gclk_attr->second == State::S1)
ywmap_clocks[bit] |= 1;
else if (gclk_attr->second == State::S0)
ywmap_clocks[bit] |= 2;
}
}
}
for (auto bit : input_bits)
undriven_bits.erase(bit);
for (auto bit : output_bits)
unused_bits.erase(bit);
for (auto cell : module->cells())
{
if (cell->type == ID($_NOT_))
@ -186,6 +238,22 @@ struct AigerWriter
unused_bits.erase(D);
undriven_bits.erase(Q);
ff_map[Q] = D;
if (cell->type != ID($_FF_)) {
auto sig_clk = sigmap(cell->getPort(ID::C).as_bit());
ywmap_clocks[sig_clk] |= cell->type == ID($_DFF_N_) ? 2 : 1;
}
continue;
}
if (cell->type == ID($anyinit))
{
auto sig_d = sigmap(cell->getPort(ID::D));
auto sig_q = sigmap(cell->getPort(ID::Q));
for (int i = 0; i < sig_d.size(); i++) {
undriven_bits.erase(sig_q[i]);
ff_map[sig_q[i]] = sig_d[i];
}
continue;
}
@ -216,6 +284,7 @@ struct AigerWriter
unused_bits.erase(A);
unused_bits.erase(EN);
asserts.push_back(make_pair(A, EN));
ywmap_asserts.push_back(cell);
continue;
}
@ -226,6 +295,7 @@ struct AigerWriter
unused_bits.erase(A);
unused_bits.erase(EN);
assumes.push_back(make_pair(A, EN));
ywmap_assumes.push_back(cell);
continue;
}
@ -267,6 +337,9 @@ struct AigerWriter
continue;
}
if (cell->type == ID($scopeinfo))
continue;
log_error("Unsupported cell type: %s (%s)\n", log_id(cell->type), log_id(cell));
}
@ -283,8 +356,11 @@ struct AigerWriter
}
init_map.sort();
input_bits.sort();
output_bits.sort();
// we are relying here on unsorted pools iterating last-in-first-out
if (!no_sort) {
input_bits.sort();
output_bits.sort();
}
not_map.sort();
ff_map.sort();
and_map.sort();
@ -602,8 +678,7 @@ struct AigerWriter
f << std::endl;
}
}
f << stringf("c\nGenerated by %s\n", yosys_version_str);
f << stringf("c\nGenerated by %s\n", yosys_maybe_version());
}
void write_map(std::ostream &f, bool verbose_map, bool no_startoffset)
@ -638,7 +713,7 @@ struct AigerWriter
}
if (wire->port_output) {
int o = ordered_outputs.at(sig[i]);
int o = ordered_outputs.at(SigSpec(wire, i));
output_lines[o] += stringf("output %d %d %s\n", o, index, log_id(wire));
}
@ -674,10 +749,144 @@ struct AigerWriter
for (auto &it : latch_lines)
f << it.second;
if (initstate_ff)
f << stringf("ninitff %d\n", ((initstate_ff >> 1)-1-aig_i));
wire_lines.sort();
for (auto &it : wire_lines)
f << it.second;
}
void write_ywmap(PrettyJson &json)
{
json.begin_object();
json.entry("version", "Yosys Witness Aiger map");
json.entry("gennerator", yosys_maybe_version());
json.entry("latch_count", aig_l);
json.entry("input_count", aig_i);
dict<int, Json> clock_lines;
dict<int, Json> input_lines;
dict<int, Json> init_lines;
dict<int, Json> seq_lines;
for (auto cell : module->cells())
{
if (cell->type.in(ID($_FF_), ID($_DFF_N_), ID($_DFF_P_), ID($anyinit), ID($anyconst), ID($anyseq)))
{
// Use sig_q to get the FF output name, but sig to lookup aiger bits
auto sig_qy = cell->getPort(cell->type.in(ID($anyconst), ID($anyseq)) ? ID::Y : ID::Q);
SigSpec sig = sigmap(sig_qy);
if (cell->get_bool_attribute(ID(clk2fflogic)))
sig_qy = cell->getPort(ID::D); // For a clk2fflogic $_FF_ the named signal is the D input not the Q output
for (int i = 0; i < GetSize(sig_qy); i++) {
if (sig_qy[i].wire == nullptr || sig[i].wire == nullptr)
continue;
auto wire = sig_qy[i].wire;
if (init_inputs.count(sig[i])) {
int a = init_inputs.at(sig[i]);
log_assert((a & 1) == 0);
init_lines[a] = json11::Json(json11::Json::object {
{ "path", witness_path(wire) },
{ "input", (a >> 1) - 1 },
{ "offset", sig_qy[i].offset },
});
}
if (input_bits.count(sig[i])) {
int a = aig_map.at(sig[i]);
log_assert((a & 1) == 0);
seq_lines[a] = json11::Json(json11::Json::object {
{ "path", witness_path(wire) },
{ "input", (a >> 1) - 1 },
{ "offset", sig_qy[i].offset },
});
}
}
}
}
for (auto wire : module->wires())
{
SigSpec sig = sigmap(wire);
if (wire->port_input)
{
auto path = witness_path(wire);
for (int i = 0; i < GetSize(wire); i++) {
if (aig_map.count(sig[i]) == 0 || sig[i].wire == nullptr)
continue;
int a = aig_map.at(sig[i]);
log_assert((a & 1) == 0);
input_lines[a] = json11::Json(json11::Json::object {
{ "path", path },
{ "input", (a >> 1) - 1 },
{ "offset", i },
});
if (ywmap_clocks.count(sig[i])) {
int clock_mode = ywmap_clocks[sig[i]];
if (clock_mode != 3) {
clock_lines[a] = json11::Json(json11::Json::object {
{ "path", path },
{ "input", (a >> 1) - 1 },
{ "offset", i },
{ "edge", clock_mode == 1 ? "posedge" : "negedge" },
});
}
}
}
}
}
json.name("clocks");
json.begin_array();
clock_lines.sort();
for (auto &it : clock_lines)
json.value(it.second);
json.end_array();
json.name("inputs");
json.begin_array();
input_lines.sort();
for (auto &it : input_lines)
json.value(it.second);
json.end_array();
json.name("seqs");
json.begin_array();
input_lines.sort();
for (auto &it : seq_lines)
json.value(it.second);
json.end_array();
json.name("inits");
json.begin_array();
input_lines.sort();
for (auto &it : init_lines)
json.value(it.second);
json.end_array();
json.name("asserts");
json.begin_array();
for (Cell *cell : ywmap_asserts)
json.value(witness_path(cell));
json.end_array();
json.name("assumes");
json.begin_array();
for (Cell *cell : ywmap_assumes)
json.value(witness_path(cell));
json.end_array();
json.end_object();
}
};
struct AigerBackend : public Backend {
@ -708,6 +917,9 @@ struct AigerBackend : public Backend {
log(" -symbols\n");
log(" include a symbol table in the generated AIGER file\n");
log("\n");
log(" -no-sort\n");
log(" don't sort input/output ports\n");
log("\n");
log(" -map <filename>\n");
log(" write an extra file with port and latch symbols\n");
log("\n");
@ -717,6 +929,9 @@ struct AigerBackend : public Backend {
log(" -no-startoffset\n");
log(" make indexes zero based, enable using map files with smt solvers.\n");
log("\n");
log(" -ywmap <filename>\n");
log(" write a map file for conversion to and from yosys witness traces.\n");
log("\n");
log(" -I, -O, -B, -L\n");
log(" If the design contains no input/output/assert/flip-flop then create one\n");
log(" dummy input/output/bad_state-pin or latch to make the tools reading the\n");
@ -729,6 +944,7 @@ struct AigerBackend : public Backend {
bool zinit_mode = false;
bool miter_mode = false;
bool symbols_mode = false;
bool no_sort = false;
bool verbose_map = false;
bool imode = false;
bool omode = false;
@ -736,6 +952,7 @@ struct AigerBackend : public Backend {
bool lmode = false;
bool no_startoffset = false;
std::string map_filename;
std::string yw_map_filename;
log_header(design, "Executing AIGER backend.\n");
@ -758,6 +975,10 @@ struct AigerBackend : public Backend {
symbols_mode = true;
continue;
}
if (args[argidx] == "-no-sort") {
no_sort = true;
continue;
}
if (map_filename.empty() && args[argidx] == "-map" && argidx+1 < args.size()) {
map_filename = args[++argidx];
continue;
@ -767,6 +988,10 @@ struct AigerBackend : public Backend {
verbose_map = true;
continue;
}
if (yw_map_filename.empty() && args[argidx] == "-ywmap" && argidx+1 < args.size()) {
yw_map_filename = args[++argidx];
continue;
}
if (args[argidx] == "-no-startoffset") {
no_startoffset = true;
continue;
@ -791,6 +1016,9 @@ struct AigerBackend : public Backend {
}
extra_args(f, filename, args, argidx, !ascii_mode);
if (!yw_map_filename.empty() && !zinit_mode)
log_error("Currently -ywmap requires -zinit.\n");
Module *top_module = design->top_module();
if (top_module == nullptr)
@ -804,7 +1032,7 @@ struct AigerBackend : public Backend {
if (!top_module->memories.empty())
log_error("Found unmapped memories in module %s: unmapped memories are not supported in AIGER backend!\n", log_id(top_module));
AigerWriter writer(top_module, zinit_mode, imode, omode, bmode, lmode);
AigerWriter writer(top_module, no_sort, zinit_mode, imode, omode, bmode, lmode);
writer.write_aiger(*f, ascii_mode, miter_mode, symbols_mode);
if (!map_filename.empty()) {
@ -812,9 +1040,20 @@ struct AigerBackend : public Backend {
std::ofstream mapf;
mapf.open(map_filename.c_str(), std::ofstream::trunc);
if (mapf.fail())
log_error("Can't open file `%s' for writing: %s\n", map_filename.c_str(), strerror(errno));
log_error("Can't open file `%s' for writing: %s\n", map_filename, strerror(errno));
writer.write_map(mapf, verbose_map, no_startoffset);
}
if (!yw_map_filename.empty()) {
std::ofstream mapf;
mapf.open(yw_map_filename.c_str(), std::ofstream::trunc);
PrettyJson json;
if (!json.write_to_file(yw_map_filename))
log_error("Can't open file `%s' for writing: %s\n", yw_map_filename, strerror(errno));
writer.write_ywmap(json);
}
}
} AigerBackend;

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);
@ -89,6 +53,8 @@ struct XAigerWriter
dict<SigBit, float> arrival_times;
vector<pair<int, int>> aig_gates;
vector<SigBit> bit2aig_stack;
int next_loop_check = 1024;
vector<int> aig_outputs;
int aig_m = 0, aig_i = 0, aig_l = 0, aig_o = 0, aig_a = 0;
@ -112,6 +78,24 @@ struct XAigerWriter
return it->second;
}
if (GetSize(bit2aig_stack)== next_loop_check) {
for (int i = 0; i < next_loop_check; ++i)
{
SigBit report_bit = bit2aig_stack[i];
if (report_bit != bit)
continue;
for (int j = i; j < next_loop_check; ++j) {
report_bit = bit2aig_stack[j];
if (report_bit.is_wire() && report_bit.wire->name.isPublic())
break;
}
log_error("Found combinatorial logic loop while processing signal %s.\n", log_signal(report_bit));
}
next_loop_check *= 2;
}
bit2aig_stack.push_back(bit);
// NB: Cannot use iterator returned from aig_map.insert()
// since this function is called recursively
@ -129,6 +113,8 @@ struct XAigerWriter
a = bit2aig(alias_map.at(bit));
}
bit2aig_stack.pop_back();
if (bit == State::Sx || bit == State::Sz) {
log_debug("Design contains 'x' or 'z' bits. Treating as 1'b0.\n");
a = aig_map.at(State::S0);
@ -274,6 +260,10 @@ struct XAigerWriter
continue;
auto offset = i.first.offset;
auto rhs = cell->getPort(i.first.name);
if (offset >= rhs.size())
continue;
#ifndef NDEBUG
if (ys_debug(1)) {
static pool<std::pair<IdString,TimingInfo::NameBit>> seen;
@ -281,7 +271,7 @@ struct XAigerWriter
log_id(cell->type), log_id(i.first.name), offset, d);
}
#endif
arrival_times[cell->getPort(i.first.name)[offset]] = d;
arrival_times[rhs[offset]] = d;
}
if (abc9_flop)
@ -533,9 +523,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);
@ -636,14 +629,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;
@ -660,22 +651,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();
@ -683,7 +671,7 @@ struct XAigerWriter
//f.write(reinterpret_cast<const char*>(&buffer_size_be), sizeof(buffer_size_be));
//f.write(buffer_str.data(), buffer_str.size());
f << stringf("Generated by %s\n", yosys_version_str);
f << stringf("Generated by %s\n", yosys_maybe_version());
design->scratchpad_set_int("write_xaiger.num_ands", and_map.size());
design->scratchpad_set_int("write_xaiger.num_wires", aig_map.size());
@ -800,7 +788,7 @@ struct XAigerBackend : public Backend {
std::ofstream mapf;
mapf.open(map_filename.c_str(), std::ofstream::trunc);
if (mapf.fail())
log_error("Can't open file `%s' for writing: %s\n", map_filename.c_str(), strerror(errno));
log_error("Can't open file `%s' for writing: %s\n", map_filename, strerror(errno));
writer.write_map(mapf);
}
}

View file

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

1488
backends/aiger2/aiger.cc Normal file

File diff suppressed because it is too large Load diff

View file

@ -157,14 +157,14 @@ struct BlifDumper
f << stringf("%c", ch);
f << stringf("\"\n");
} else
f << stringf("%s\n", param.second.as_string().c_str());
f << stringf("%s\n", param.second.as_string());
}
}
void dump()
{
f << stringf("\n");
f << stringf(".model %s\n", str(module->name).c_str());
f << stringf(".model %s\n", str(module->name));
std::map<int, RTLIL::Wire*> inputs, outputs;
@ -179,7 +179,7 @@ struct BlifDumper
for (auto &it : inputs) {
RTLIL::Wire *wire = it.second;
for (int i = 0; i < wire->width; i++)
f << stringf(" %s", str(RTLIL::SigSpec(wire, i)).c_str());
f << stringf(" %s", str(RTLIL::SigSpec(wire, i)));
}
f << stringf("\n");
@ -187,7 +187,7 @@ struct BlifDumper
for (auto &it : outputs) {
RTLIL::Wire *wire = it.second;
for (int i = 0; i < wire->width; i++)
f << stringf(" %s", str(RTLIL::SigSpec(wire, i)).c_str());
f << stringf(" %s", str(RTLIL::SigSpec(wire, i)));
}
f << stringf("\n");
@ -200,7 +200,7 @@ struct BlifDumper
if (!config->impltf_mode) {
if (!config->false_type.empty()) {
if (config->false_type == "+")
f << stringf(".names %s\n", config->false_out.c_str());
f << stringf(".names %s\n", config->false_out);
else if (config->false_type != "-")
f << stringf(".%s %s %s=$false\n", subckt_or_gate(config->false_type),
config->false_type.c_str(), config->false_out.c_str());
@ -208,7 +208,7 @@ struct BlifDumper
f << stringf(".names $false\n");
if (!config->true_type.empty()) {
if (config->true_type == "+")
f << stringf(".names %s\n1\n", config->true_out.c_str());
f << stringf(".names %s\n1\n", config->true_out);
else if (config->true_type != "-")
f << stringf(".%s %s %s=$true\n", subckt_or_gate(config->true_type),
config->true_type.c_str(), config->true_out.c_str());
@ -216,7 +216,7 @@ struct BlifDumper
f << stringf(".names $true\n1\n");
if (!config->undef_type.empty()) {
if (config->undef_type == "+")
f << stringf(".names %s\n", config->undef_out.c_str());
f << stringf(".names %s\n", config->undef_out);
else if (config->undef_type != "-")
f << stringf(".%s %s %s=$undef\n", subckt_or_gate(config->undef_type),
config->undef_type.c_str(), config->undef_out.c_str());
@ -226,6 +226,9 @@ struct BlifDumper
for (auto cell : module->cells())
{
if (cell->type == ID($scopeinfo))
continue;
if (config->unbuf_types.count(cell->type)) {
auto portnames = config->unbuf_types.at(cell->type);
f << stringf(".names %s %s\n1 1\n",
@ -328,31 +331,31 @@ struct BlifDumper
}
if (!config->icells_mode && cell->type == ID($_FF_)) {
f << stringf(".latch %s %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(),
f << stringf(".latch %s %s%s\n", str(cell->getPort(ID::D)), str(cell->getPort(ID::Q)),
str_init(cell->getPort(ID::Q)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_DFF_N_)) {
f << stringf(".latch %s %s fe %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(),
f << stringf(".latch %s %s fe %s%s\n", str(cell->getPort(ID::D)), str(cell->getPort(ID::Q)),
str(cell->getPort(ID::C)).c_str(), str_init(cell->getPort(ID::Q)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_DFF_P_)) {
f << stringf(".latch %s %s re %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(),
f << stringf(".latch %s %s re %s%s\n", str(cell->getPort(ID::D)), str(cell->getPort(ID::Q)),
str(cell->getPort(ID::C)).c_str(), str_init(cell->getPort(ID::Q)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_DLATCH_N_)) {
f << stringf(".latch %s %s al %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(),
f << stringf(".latch %s %s al %s%s\n", str(cell->getPort(ID::D)), str(cell->getPort(ID::Q)),
str(cell->getPort(ID::E)).c_str(), str_init(cell->getPort(ID::Q)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_DLATCH_P_)) {
f << stringf(".latch %s %s ah %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(),
f << stringf(".latch %s %s ah %s%s\n", str(cell->getPort(ID::D)), str(cell->getPort(ID::Q)),
str(cell->getPort(ID::E)).c_str(), str_init(cell->getPort(ID::Q)).c_str());
goto internal_cell;
}
@ -363,10 +366,10 @@ struct BlifDumper
auto width = cell->parameters.at(ID::WIDTH).as_int();
log_assert(inputs.size() == width);
for (int i = width-1; i >= 0; i--)
f << stringf(" %s", str(inputs.extract(i, 1)).c_str());
f << stringf(" %s", str(inputs.extract(i, 1)));
auto &output = cell->getPort(ID::Y);
log_assert(output.size() == 1);
f << stringf(" %s", str(output).c_str());
f << stringf(" %s", str(output));
f << stringf("\n");
RTLIL::SigSpec mask = cell->parameters.at(ID::LUT);
for (int i = 0; i < (1 << width); i++)
@ -384,15 +387,15 @@ struct BlifDumper
auto &inputs = cell->getPort(ID::A);
auto width = cell->parameters.at(ID::WIDTH).as_int();
auto depth = cell->parameters.at(ID::DEPTH).as_int();
vector<State> table = cell->parameters.at(ID::TABLE).bits;
vector<State> table = cell->parameters.at(ID::TABLE).to_bits();
while (GetSize(table) < 2*width*depth)
table.push_back(State::S0);
log_assert(inputs.size() == width);
for (int i = 0; i < width; i++)
f << stringf(" %s", str(inputs.extract(i, 1)).c_str());
f << stringf(" %s", str(inputs.extract(i, 1)));
auto &output = cell->getPort(ID::Y);
log_assert(output.size() == 1);
f << stringf(" %s", str(output).c_str());
f << stringf(" %s", str(output));
f << stringf("\n");
for (int i = 0; i < depth; i++) {
for (int j = 0; j < width; j++) {
@ -407,11 +410,11 @@ struct BlifDumper
goto internal_cell;
}
f << stringf(".%s %s", subckt_or_gate(cell->type.str()), str(cell->type).c_str());
f << stringf(".%s %s", subckt_or_gate(cell->type.str()), str(cell->type));
for (auto &conn : cell->connections())
{
if (conn.second.size() == 1) {
f << stringf(" %s=%s", str(conn.first).c_str(), str(conn.second[0]).c_str());
f << stringf(" %s=%s", str(conn.first), str(conn.second[0]));
continue;
}
@ -420,11 +423,11 @@ struct BlifDumper
if (w == nullptr) {
for (int i = 0; i < GetSize(conn.second); i++)
f << stringf(" %s[%d]=%s", str(conn.first).c_str(), i, str(conn.second[i]).c_str());
f << stringf(" %s[%d]=%s", str(conn.first), i, str(conn.second[i]));
} else {
for (int i = 0; i < std::min(GetSize(conn.second), GetSize(w)); i++) {
SigBit sig(w, i);
f << stringf(" %s[%d]=%s", str(conn.first).c_str(), sig.wire->upto ?
f << stringf(" %s[%d]=%s", str(conn.first), sig.wire->upto ?
sig.wire->start_offset+sig.wire->width-sig.offset-1 :
sig.wire->start_offset+sig.offset, str(conn.second[i]).c_str());
}
@ -433,7 +436,7 @@ struct BlifDumper
f << stringf("\n");
if (config->cname_mode)
f << stringf(".cname %s\n", str(cell->name).c_str());
f << stringf(".cname %s\n", str(cell->name));
if (config->attr_mode)
dump_params(".attr", cell->attributes);
if (config->param_mode)
@ -442,7 +445,7 @@ struct BlifDumper
if (0) {
internal_cell:
if (config->iname_mode)
f << stringf(".cname %s\n", str(cell->name).c_str());
f << stringf(".cname %s\n", str(cell->name));
if (config->iattr_mode)
dump_params(".attr", cell->attributes);
}
@ -458,12 +461,12 @@ struct BlifDumper
continue;
if (config->conn_mode)
f << stringf(".conn %s %s\n", str(rhs_bit).c_str(), str(lhs_bit).c_str());
f << stringf(".conn %s %s\n", str(rhs_bit), str(lhs_bit));
else if (!config->buf_type.empty())
f << stringf(".%s %s %s=%s %s=%s\n", subckt_or_gate(config->buf_type), config->buf_type.c_str(),
f << stringf(".%s %s %s=%s %s=%s\n", subckt_or_gate(config->buf_type), config->buf_type,
config->buf_in.c_str(), str(rhs_bit).c_str(), config->buf_out.c_str(), str(lhs_bit).c_str());
else
f << stringf(".names %s %s\n1 1\n", str(rhs_bit).c_str(), str(lhs_bit).c_str());
f << stringf(".names %s %s\n1 1\n", str(rhs_bit), str(lhs_bit));
}
f << stringf(".end\n");
@ -512,8 +515,8 @@ struct BlifBackend : public Backend {
log(" suppresses the generation of this nets without fanout.\n");
log("\n");
log("The following options can be useful when the generated file is not going to be\n");
log("read by a BLIF parser but a custom tool. It is recommended to not name the output\n");
log("file *.blif when any of this options is used.\n");
log("read by a BLIF parser but a custom tool. It is recommended not to name the\n");
log("output file *.blif when any of these options are used.\n");
log("\n");
log(" -icells\n");
log(" do not translate Yosys's internal gates to generic BLIF logic\n");
@ -646,7 +649,7 @@ struct BlifBackend : public Backend {
if (module->get_bool_attribute(ID::top))
top_module_name = module->name.str();
*f << stringf("# Generated by %s\n", yosys_version_str);
*f << stringf("# Generated by %s\n", yosys_maybe_version());
std::vector<RTLIL::Module*> mod_list;
@ -671,7 +674,7 @@ struct BlifBackend : public Backend {
}
if (!top_module_name.empty())
log_error("Can't find top module `%s'!\n", top_module_name.c_str());
log_error("Can't find top module `%s'!\n", top_module_name);
for (auto module : mod_list)
BlifDumper::dump(*f, module, design, config);

View file

@ -28,6 +28,9 @@
#include "kernel/celltypes.h"
#include "kernel/log.h"
#include "kernel/mem.h"
#include "kernel/json.h"
#include "kernel/yw.h"
#include "kernel/utils.h"
#include <string>
USING_YOSYS_NAMESPACE
@ -83,20 +86,34 @@ struct BtorWorker
vector<string> info_lines;
dict<int, int> info_clocks;
void btorf(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 2, 3))
struct ywmap_btor_sig {
SigSpec sig;
Cell *cell = nullptr;
ywmap_btor_sig(const SigSpec &sig) : sig(sig) {}
ywmap_btor_sig(Cell *cell) : cell(cell) {}
};
vector<ywmap_btor_sig> ywmap_inputs;
vector<ywmap_btor_sig> ywmap_states;
dict<SigBit, int> ywmap_clock_bits;
dict<SigBit, int> ywmap_clock_inputs;
vector<Cell *> ywmap_asserts;
vector<Cell *> ywmap_assumes;
PrettyJson ywmap_json;
template <typename... Args>
void btorf(FmtString<TypeIdentity<Args>...> fmt, const Args &... args)
{
va_list ap;
va_start(ap, fmt);
f << indent << vstringf(fmt, ap);
va_end(ap);
f << indent << fmt.format(args...);
}
void infof(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 2, 3))
template <typename... Args>
void infof(FmtString<TypeIdentity<Args>...> fmt, const Args &... args)
{
va_list ap;
va_start(ap, fmt);
info_lines.push_back(vstringf(fmt, ap));
va_end(ap);
info_lines.push_back(fmt.format(args...));
}
template<typename T>
@ -110,7 +127,7 @@ struct BtorWorker
std::replace(src.begin(), src.end(), ' ', '_');
if (srcsymbols.count(src) || module->count_id("\\" + src)) {
for (int i = 1;; i++) {
string s = stringf("%s-%d", src.c_str(), i);
string s = stringf("%s-%d", src, i);
if (!srcsymbols.count(s) && !module->count_id("\\" + s)) {
src = s;
break;
@ -126,10 +143,54 @@ struct BtorWorker
return " " + infostr;
}
void ywmap_state(const SigSpec &sig) {
if (ywmap_json.active())
ywmap_states.emplace_back(sig);
}
void ywmap_state(Cell *cell) {
if (ywmap_json.active())
ywmap_states.emplace_back(cell);
}
void ywmap_input(const SigSpec &sig) {
if (ywmap_json.active())
ywmap_inputs.emplace_back(sig);
}
void emit_ywmap_btor_sig(const ywmap_btor_sig &btor_sig) {
if (btor_sig.cell == nullptr) {
if (btor_sig.sig.empty()) {
ywmap_json.value(nullptr);
return;
}
ywmap_json.begin_array();
ywmap_json.compact();
for (auto &chunk : btor_sig.sig.chunks()) {
log_assert(chunk.is_wire());
ywmap_json.begin_object();
ywmap_json.entry("path", witness_path(chunk.wire));
ywmap_json.entry("width", chunk.width);
ywmap_json.entry("offset", chunk.offset);
ywmap_json.end_object();
}
ywmap_json.end_array();
} else {
ywmap_json.begin_object();
ywmap_json.compact();
ywmap_json.entry("path", witness_path(btor_sig.cell));
Mem *mem = mem_cells[btor_sig.cell];
ywmap_json.entry("width", mem->width);
ywmap_json.entry("size", mem->size);
ywmap_json.end_object();
}
}
void btorf_push(const string &id)
{
if (verbose) {
f << indent << stringf(" ; begin %s\n", id.c_str());
f << indent << stringf(" ; begin %s\n", id);
indent += " ";
}
}
@ -138,7 +199,7 @@ struct BtorWorker
{
if (verbose) {
indent = indent.substr(4);
f << indent << stringf(" ; end %s\n", id.c_str());
f << indent << stringf(" ; end %s\n", id);
}
}
@ -183,7 +244,7 @@ struct BtorWorker
string cell_list;
for (auto c : cell_recursion_guard)
cell_list += stringf("\n %s", log_id(c));
log_error("Found topological loop while processing cell %s. Active cells:%s\n", log_id(cell), cell_list.c_str());
log_error("Found topological loop while processing cell %s. Active cells:%s\n", log_id(cell), cell_list);
}
cell_recursion_guard.insert(cell);
@ -259,12 +320,12 @@ struct BtorWorker
btorf("%d slt %d %d %d\n", nid_b_ltz, sid_bit, nid_b, nid_zero);
nid = next_nid++;
btorf("%d ite %d %d %d %d%s\n", nid, sid, nid_b_ltz, nid_l, nid_r, getinfo(cell).c_str());
btorf("%d ite %d %d %d %d%s\n", nid, sid, nid_b_ltz, nid_l, nid_r, getinfo(cell));
}
else
{
nid = next_nid++;
btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str());
btorf("%d %s %d %d %d%s\n", nid, btor_op, sid, nid_a, nid_b, getinfo(cell));
}
SigSpec sig = sigmap(cell->getPort(ID::Y));
@ -305,7 +366,7 @@ struct BtorWorker
int sid = get_bv_sid(width);
int nid = next_nid++;
btorf("%d %c%s %d %d %d%s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str());
btorf("%d %c%s %d %d %d%s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op, sid, nid_a, nid_b, getinfo(cell));
SigSpec sig = sigmap(cell->getPort(ID::Y));
@ -331,12 +392,12 @@ struct BtorWorker
if (cell->type == ID($_ANDNOT_)) {
btorf("%d not %d %d\n", nid1, sid, nid_b);
btorf("%d and %d %d %d%s\n", nid2, sid, nid_a, nid1, getinfo(cell).c_str());
btorf("%d and %d %d %d%s\n", nid2, sid, nid_a, nid1, getinfo(cell));
}
if (cell->type == ID($_ORNOT_)) {
btorf("%d not %d %d\n", nid1, sid, nid_b);
btorf("%d or %d %d %d%s\n", nid2, sid, nid_a, nid1, getinfo(cell).c_str());
btorf("%d or %d %d %d%s\n", nid2, sid, nid_a, nid1, getinfo(cell));
}
SigSpec sig = sigmap(cell->getPort(ID::Y));
@ -358,13 +419,13 @@ struct BtorWorker
if (cell->type == ID($_OAI3_)) {
btorf("%d or %d %d %d\n", nid1, sid, nid_a, nid_b);
btorf("%d and %d %d %d\n", nid2, sid, nid1, nid_c);
btorf("%d not %d %d%s\n", nid3, sid, nid2, getinfo(cell).c_str());
btorf("%d not %d %d%s\n", nid3, sid, nid2, getinfo(cell));
}
if (cell->type == ID($_AOI3_)) {
btorf("%d and %d %d %d\n", nid1, sid, nid_a, nid_b);
btorf("%d or %d %d %d\n", nid2, sid, nid1, nid_c);
btorf("%d not %d %d%s\n", nid3, sid, nid2, getinfo(cell).c_str());
btorf("%d not %d %d%s\n", nid3, sid, nid2, getinfo(cell));
}
SigSpec sig = sigmap(cell->getPort(ID::Y));
@ -389,14 +450,14 @@ struct BtorWorker
btorf("%d or %d %d %d\n", nid1, sid, nid_a, nid_b);
btorf("%d or %d %d %d\n", nid2, sid, nid_c, nid_d);
btorf("%d and %d %d %d\n", nid3, sid, nid1, nid2);
btorf("%d not %d %d%s\n", nid4, sid, nid3, getinfo(cell).c_str());
btorf("%d not %d %d%s\n", nid4, sid, nid3, getinfo(cell));
}
if (cell->type == ID($_AOI4_)) {
btorf("%d and %d %d %d\n", nid1, sid, nid_a, nid_b);
btorf("%d and %d %d %d\n", nid2, sid, nid_c, nid_d);
btorf("%d or %d %d %d\n", nid3, sid, nid1, nid2);
btorf("%d not %d %d%s\n", nid4, sid, nid3, getinfo(cell).c_str());
btorf("%d not %d %d%s\n", nid4, sid, nid3, getinfo(cell));
}
SigSpec sig = sigmap(cell->getPort(ID::Y));
@ -428,9 +489,9 @@ struct BtorWorker
int nid = next_nid++;
if (cell->type.in(ID($lt), ID($le), ID($ge), ID($gt))) {
btorf("%d %c%s %d %d %d%s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str());
btorf("%d %c%s %d %d %d%s\n", nid, a_signed || b_signed ? 's' : 'u', btor_op, sid, nid_a, nid_b, getinfo(cell));
} else {
btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str());
btorf("%d %s %d %d %d%s\n", nid, btor_op, sid, nid_a, nid_b, getinfo(cell));
}
SigSpec sig = sigmap(cell->getPort(ID::Y));
@ -446,7 +507,7 @@ struct BtorWorker
goto okay;
}
if (cell->type.in(ID($not), ID($neg), ID($_NOT_), ID($pos)))
if (cell->type.in(ID($not), ID($neg), ID($_NOT_), ID($pos), ID($buf), ID($_BUF_)))
{
string btor_op;
if (cell->type.in(ID($not), ID($_NOT_))) btor_op = "not";
@ -458,14 +519,14 @@ struct BtorWorker
int nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed);
SigSpec sig = sigmap(cell->getPort(ID::Y));
// the $pos cell just passes through, all other cells need an actual operation applied
// the $pos/$buf cells just pass through, all other cells need an actual operation applied
int nid = nid_a;
if (cell->type != ID($pos))
if (!cell->type.in(ID($pos), ID($buf), ID($_BUF_)))
{
log_assert(!btor_op.empty());
int sid = get_bv_sid(width);
nid = next_nid++;
btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str());
btorf("%d %s %d %d%s\n", nid, btor_op, sid, nid_a, getinfo(cell));
}
if (GetSize(sig) < width) {
@ -505,9 +566,9 @@ struct BtorWorker
int nid = next_nid++;
if (btor_op != "not")
btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str());
btorf("%d %s %d %d %d%s\n", nid, btor_op, sid, nid_a, nid_b, getinfo(cell));
else
btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str());
btorf("%d %s %d %d%s\n", nid, btor_op, sid, nid_a, getinfo(cell));
SigSpec sig = sigmap(cell->getPort(ID::Y));
@ -538,11 +599,11 @@ struct BtorWorker
if (cell->type == ID($reduce_xnor)) {
int nid2 = next_nid++;
btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str());
btorf("%d %s %d %d%s\n", nid, btor_op, sid, nid_a, getinfo(cell));
btorf("%d not %d %d\n", nid2, sid, nid);
nid = nid2;
} else {
btorf("%d %s %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, getinfo(cell).c_str());
btorf("%d %s %d %d%s\n", nid, btor_op, sid, nid_a, getinfo(cell));
}
SigSpec sig = sigmap(cell->getPort(ID::Y));
@ -577,9 +638,9 @@ struct BtorWorker
int tmp = nid;
nid = next_nid++;
btorf("%d ite %d %d %d %d\n", tmp, sid, nid_s, nid_b, nid_a);
btorf("%d not %d %d%s\n", nid, sid, tmp, getinfo(cell).c_str());
btorf("%d not %d %d%s\n", nid, sid, tmp, getinfo(cell));
} else {
btorf("%d ite %d %d %d %d%s\n", nid, sid, nid_s, nid_b, nid_a, getinfo(cell).c_str());
btorf("%d ite %d %d %d %d%s\n", nid, sid, nid_s, nid_b, nid_a, getinfo(cell));
}
add_nid_sig(nid, sig_y);
@ -602,7 +663,7 @@ struct BtorWorker
int nid_s = get_sig_nid(sig_s.extract(i));
int nid2 = next_nid++;
if (i == GetSize(sig_s)-1)
btorf("%d ite %d %d %d %d%s\n", nid2, sid, nid_s, nid_b, nid, getinfo(cell).c_str());
btorf("%d ite %d %d %d %d%s\n", nid2, sid, nid_s, nid_b, nid, getinfo(cell));
else
btorf("%d ite %d %d %d %d\n", nid2, sid, nid_s, nid_b, nid);
nid = nid2;
@ -612,12 +673,12 @@ struct BtorWorker
goto okay;
}
if (cell->type.in(ID($dff), ID($ff), ID($_DFF_P_), ID($_DFF_N), ID($_FF_)))
if (cell->type.in(ID($dff), ID($ff), ID($anyinit), ID($_DFF_P_), ID($_DFF_N), ID($_FF_)))
{
SigSpec sig_d = sigmap(cell->getPort(ID::D));
SigSpec sig_q = sigmap(cell->getPort(ID::Q));
if (!info_filename.empty() && cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_)))
if ((!info_filename.empty() || ywmap_json.active()) && cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_)))
{
SigSpec sig_c = sigmap(cell->getPort(cell->type == ID($dff) ? ID::CLK : ID::C));
int nid = get_sig_nid(sig_c);
@ -629,7 +690,11 @@ struct BtorWorker
if (cell->type == ID($dff) && !cell->getParam(ID::CLK_POLARITY).as_bool())
negedge = true;
info_clocks[nid] |= negedge ? 2 : 1;
if (!info_filename.empty())
info_clocks[nid] |= negedge ? 2 : 1;
if (ywmap_json.active())
ywmap_clock_bits[sig_c] |= negedge ? 2 : 1;
}
IdString symbol;
@ -642,12 +707,13 @@ struct BtorWorker
}
}
Const initval;
Const::Builder initval_bits(GetSize(sig_q));
for (int i = 0; i < GetSize(sig_q); i++)
if (initbits.count(sig_q[i]))
initval.bits.push_back(initbits.at(sig_q[i]) ? State::S1 : State::S0);
initval_bits.push_back(initbits.at(sig_q[i]) ? State::S1 : State::S0);
else
initval.bits.push_back(State::Sx);
initval_bits.push_back(State::Sx);
Const initval = initval_bits.build();
int nid_init_val = -1;
@ -662,6 +728,11 @@ struct BtorWorker
else
btorf("%d state %d %s\n", nid, sid, log_id(symbol));
if (cell->get_bool_attribute(ID(clk2fflogic)))
ywmap_state(cell->getPort(ID::D)); // For a clk2fflogic FF the named signal is the D input not the Q output
else
ywmap_state(sig_q);
if (nid_init_val >= 0) {
int nid_init = next_nid++;
if (verbose)
@ -681,7 +752,9 @@ struct BtorWorker
int sid = get_bv_sid(GetSize(sig_y));
int nid = next_nid++;
btorf("%d state %d%s\n", nid, sid, getinfo(cell).c_str());
btorf("%d state %d%s\n", nid, sid, getinfo(cell));
ywmap_state(sig_y);
if (cell->type == ID($anyconst)) {
int nid2 = next_nid++;
@ -702,9 +775,11 @@ struct BtorWorker
int one_nid = get_sig_nid(State::S1);
int zero_nid = get_sig_nid(State::S0);
initstate_nid = next_nid++;
btorf("%d state %d%s\n", initstate_nid, sid, getinfo(cell).c_str());
btorf("%d state %d%s\n", initstate_nid, sid, getinfo(cell));
btorf("%d init %d %d %d\n", next_nid++, sid, initstate_nid, one_nid);
btorf("%d next %d %d %d\n", next_nid++, sid, initstate_nid, zero_nid);
ywmap_state(sig_y);
}
add_nid_sig(initstate_nid, sig_y);
@ -757,7 +832,10 @@ struct BtorWorker
}
}
if (constword)
// If not fully defined, undef bits should be able to take a
// different value for each address so we can't initialise from
// one value (and btor2parser doesn't like it)
if (constword && firstword.is_fully_def())
{
if (verbose)
btorf("; initval = %s\n", log_signal(firstword));
@ -768,6 +846,8 @@ struct BtorWorker
nid_init_val = next_nid++;
btorf("%d state %d\n", nid_init_val, sid);
ywmap_state(nullptr);
for (int i = 0; i < mem->size; i++) {
Const thisword = initdata.extract(i*mem->width, mem->width);
if (thisword.is_fully_undef())
@ -793,6 +873,8 @@ struct BtorWorker
else
btorf("%d state %d %s\n", nid, sid, log_id(mem->memid));
ywmap_state(cell);
if (nid_init_val >= 0)
{
int nid_init = next_nid++;
@ -915,10 +997,13 @@ struct BtorWorker
int sid = get_bv_sid(GetSize(sig));
int nid_input = next_nid++;
if (is_init)
if (is_init) {
btorf("%d state %d\n", nid_input, sid);
else
ywmap_state(sig);
} else {
btorf("%d input %d\n", nid_input, sid);
ywmap_input(sig);
}
int nid_masked_input;
if (sig_mask_undef.is_fully_ones()) {
@ -957,15 +1042,16 @@ struct BtorWorker
{
if (bit.wire == nullptr)
{
Const c(bit.data);
while (i+GetSize(c) < GetSize(sig) && sig[i+GetSize(c)].wire == nullptr)
c.bits.push_back(sig[i+GetSize(c)].data);
Const::Builder c_bits;
c_bits.push_back(bit.data);
while (i + GetSize(c_bits) < GetSize(sig) && sig[i + GetSize(c_bits)].wire == nullptr)
c_bits.push_back(sig[i + GetSize(c_bits)].data);
Const c = c_bits.build();
if (consts.count(c) == 0) {
int sid = get_bv_sid(GetSize(c));
int nid = next_nid++;
btorf("%d const %d %s\n", nid, sid, c.as_string().c_str());
btorf("%d const %d %s\n", nid, sid, c.as_string());
consts[c] = nid;
nid_width[nid] = GetSize(c);
}
@ -993,7 +1079,9 @@ struct BtorWorker
int sid = get_bv_sid(GetSize(s));
int nid = next_nid++;
btorf("%d input %d\n", nid, sid);
ywmap_input(s);
nid_width[nid] = GetSize(s);
add_nid_sig(nid, s);
for (int j = 0; j < GetSize(s); j++)
nidbits.push_back(make_pair(nid, j));
@ -1075,12 +1163,15 @@ struct BtorWorker
return nid;
}
BtorWorker(std::ostream &f, RTLIL::Module *module, bool verbose, bool single_bad, bool cover_mode, bool print_internal_names, string info_filename) :
BtorWorker(std::ostream &f, RTLIL::Module *module, bool verbose, bool single_bad, bool cover_mode, bool print_internal_names, string info_filename, string ywmap_filename) :
f(f), sigmap(module), module(module), verbose(verbose), single_bad(single_bad), cover_mode(cover_mode), print_internal_names(print_internal_names), info_filename(info_filename)
{
if (!info_filename.empty())
infof("name %s\n", log_id(module));
if (!ywmap_filename.empty())
ywmap_json.write_to_file(ywmap_filename);
memories = Mem::get_all_memories(module);
dict<IdString, Mem*> mem_dict;
@ -1094,6 +1185,20 @@ struct BtorWorker
btorf_push("inputs");
if (ywmap_json.active()) {
for (auto wire : module->wires())
{
auto gclk_attr = wire->attributes.find(ID::replaced_by_gclk);
if (gclk_attr == wire->attributes.end())
continue;
SigSpec sig = sigmap(wire);
if (gclk_attr->second == State::S1)
ywmap_clock_bits[sig] |= 1;
else if (gclk_attr->second == State::S0)
ywmap_clock_bits[sig] |= 2;
}
}
for (auto wire : module->wires())
{
if (wire->attributes.count(ID::init)) {
@ -1110,8 +1215,29 @@ struct BtorWorker
int sid = get_bv_sid(GetSize(sig));
int nid = next_nid++;
btorf("%d input %d%s\n", nid, sid, getinfo(wire).c_str());
btorf("%d input %d%s\n", nid, sid, getinfo(wire));
ywmap_input(wire);
add_nid_sig(nid, sig);
if (!info_filename.empty()) {
auto gclk_attr = wire->attributes.find(ID::replaced_by_gclk);
if (gclk_attr != wire->attributes.end()) {
if (gclk_attr->second == State::S1)
info_clocks[nid] |= 1;
else if (gclk_attr->second == State::S0)
info_clocks[nid] |= 2;
}
}
if (ywmap_json.active()) {
for (int i = 0; i < GetSize(sig); i++) {
auto input_bit = SigBit(wire, i);
auto bit = sigmap(input_bit);
if (!ywmap_clock_bits.count(bit))
continue;
ywmap_clock_inputs[input_bit] = ywmap_clock_bits[bit];
}
}
}
btorf_pop("inputs");
@ -1134,7 +1260,7 @@ struct BtorWorker
btorf_push(stringf("output %s", log_id(wire)));
int nid = get_sig_nid(wire);
btorf("%d output %d%s\n", next_nid++, nid, getinfo(wire).c_str());
btorf("%d output %d%s\n", next_nid++, nid, getinfo(wire));
btorf_pop(stringf("output %s", log_id(wire)));
}
@ -1156,6 +1282,8 @@ struct BtorWorker
btorf("%d or %d %d %d\n", nid_a_or_not_en, sid, nid_a, nid_not_en);
btorf("%d constraint %d\n", nid, nid_a_or_not_en);
if (ywmap_json.active()) ywmap_assumes.emplace_back(cell);
btorf_pop(log_id(cell));
}
@ -1176,10 +1304,12 @@ struct BtorWorker
bad_properties.push_back(nid_en_and_not_a);
} else {
if (cover_mode) {
infof("bad %d%s\n", nid_en_and_not_a, getinfo(cell, true).c_str());
infof("bad %d%s\n", nid_en_and_not_a, getinfo(cell, true));
} else {
int nid = next_nid++;
btorf("%d bad %d%s\n", nid, nid_en_and_not_a, getinfo(cell, true).c_str());
btorf("%d bad %d%s\n", nid, nid_en_and_not_a, getinfo(cell, true));
if (ywmap_json.active()) ywmap_asserts.emplace_back(cell);
}
}
@ -1201,7 +1331,7 @@ struct BtorWorker
bad_properties.push_back(nid_en_and_a);
} else {
int nid = next_nid++;
btorf("%d bad %d%s\n", nid, nid_en_and_a, getinfo(cell, true).c_str());
btorf("%d bad %d%s\n", nid, nid_en_and_a, getinfo(cell, true));
}
btorf_pop(log_id(cell));
@ -1222,7 +1352,7 @@ struct BtorWorker
continue;
int this_nid = next_nid++;
btorf("%d uext %d %d %d%s\n", this_nid, sid, nid, 0, getinfo(wire).c_str());
btorf("%d uext %d %d %d%s\n", this_nid, sid, nid, 0, getinfo(wire));
if (info_clocks.count(nid))
info_clocks[this_nid] |= info_clocks[nid];
@ -1245,7 +1375,7 @@ struct BtorWorker
SigSpec sig = sigmap(cell->getPort(ID::D));
int nid_q = get_sig_nid(sig);
int sid = get_bv_sid(GetSize(sig));
btorf("%d next %d %d %d%s\n", next_nid++, sid, nid, nid_q, getinfo(cell).c_str());
btorf("%d next %d %d %d%s\n", next_nid++, sid, nid, nid_q, getinfo(cell));
btorf_pop(stringf("next %s", log_id(cell)));
}
@ -1304,7 +1434,7 @@ struct BtorWorker
}
int nid2 = next_nid++;
btorf("%d next %d %d %d%s\n", nid2, sid, nid, nid_head, (mem->cell ? getinfo(mem->cell) : getinfo(mem->mem)).c_str());
btorf("%d next %d %d %d%s\n", nid2, sid, nid, nid_head, (mem->cell ? getinfo(mem->cell) : getinfo(mem->mem)));
btorf_pop(stringf("next %s", log_id(mem->memid)));
}
@ -1337,6 +1467,7 @@ struct BtorWorker
log_assert(cursor == 0);
log_assert(GetSize(todo) == 1);
btorf("%d bad %d\n", nid, todo[cursor]);
// What do we do with ywmap_asserts when using single_bad?
}
}
@ -1363,11 +1494,59 @@ struct BtorWorker
std::ofstream f;
f.open(info_filename.c_str(), std::ofstream::trunc);
if (f.fail())
log_error("Can't open file `%s' for writing: %s\n", info_filename.c_str(), strerror(errno));
log_error("Can't open file `%s' for writing: %s\n", info_filename, strerror(errno));
for (auto &it : info_lines)
f << it;
f.close();
}
if (ywmap_json.active())
{
ywmap_json.begin_object();
ywmap_json.entry("version", "Yosys Witness BTOR map");
ywmap_json.entry("generator", yosys_maybe_version());
ywmap_json.name("clocks");
ywmap_json.begin_array();
for (auto &entry : ywmap_clock_inputs) {
if (entry.second != 1 && entry.second != 2)
continue;
log_assert(entry.first.is_wire());
ywmap_json.begin_object();
ywmap_json.compact();
ywmap_json.entry("path", witness_path(entry.first.wire));
ywmap_json.entry("offset", entry.first.offset);
ywmap_json.entry("edge", entry.second == 1 ? "posedge" : "negedge");
ywmap_json.end_object();
}
ywmap_json.end_array();
ywmap_json.name("inputs");
ywmap_json.begin_array();
for (auto &entry : ywmap_inputs)
emit_ywmap_btor_sig(entry);
ywmap_json.end_array();
ywmap_json.name("states");
ywmap_json.begin_array();
for (auto &entry : ywmap_states)
emit_ywmap_btor_sig(entry);
ywmap_json.end_array();
ywmap_json.name("asserts");
ywmap_json.begin_array();
for (Cell *cell : ywmap_asserts)
ywmap_json.value(witness_path(cell));
ywmap_json.end_array();
ywmap_json.name("assumes");
ywmap_json.begin_array();
for (Cell *cell : ywmap_assumes)
ywmap_json.value(witness_path(cell));
ywmap_json.end_array();
ywmap_json.end_object();
}
}
};
@ -1396,18 +1575,22 @@ struct BtorBackend : public Backend {
log(" -x\n");
log(" Output symbols for internal netnames (starting with '$')\n");
log("\n");
log(" -ywmap <filename>\n");
log(" Create a map file for conversion to and from Yosys witness traces\n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
{
bool verbose = false, single_bad = false, cover_mode = false, print_internal_names = false;
string info_filename;
string ywmap_filename;
log_header(design, "Executing BTOR backend.\n");
log_push();
Pass::call(design, "memory_map -rom-only");
Pass::call(design, "bmuxmap");
Pass::call(design, "demuxmap");
Pass::call(design, "bwmuxmap");
log_pop();
size_t argidx;
@ -1433,6 +1616,10 @@ struct BtorBackend : public Backend {
print_internal_names = true;
continue;
}
if (args[argidx] == "-ywmap" && argidx+1 < args.size()) {
ywmap_filename = args[++argidx];
continue;
}
break;
}
extra_args(f, filename, args, argidx);
@ -1443,9 +1630,9 @@ struct BtorBackend : public Backend {
log_cmd_error("No top module found.\n");
*f << stringf("; BTOR description generated by %s for module %s.\n",
yosys_version_str, log_id(topmod));
yosys_maybe_version(), log_id(topmod));
BtorWorker(*f, topmod, verbose, single_bad, cover_mode, print_internal_names, info_filename);
BtorWorker(*f, topmod, verbose, single_bad, cover_mode, print_internal_names, info_filename, ywmap_filename);
*f << stringf("; end of yosys output\n");
}

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -ex
@ -10,11 +10,11 @@ cd test_cells.tmp
for fn in test_*.il; do
../../../yosys -p "
read_ilang $fn
read_rtlil $fn
rename gold gate
synth
read_ilang $fn
read_rtlil $fn
miter -equiv -make_assert -flatten gold gate main
hierarchy -top main
write_btor ${fn%.il}.btor

View file

@ -1,2 +1,11 @@
OBJS += backends/cxxrtl/cxxrtl_backend.o
$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl.h))
$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl_vcd.h))
$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h))
$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h))
$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.cc))
$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi.h))
$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_vcd.cc))
$(eval $(call add_include_file,backends/cxxrtl/runtime/cxxrtl/capi/cxxrtl_capi_vcd.h))

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
This directory contains the runtime components of CXXRTL and should be placed on the include path
when building the simulation using the `-I${YOSYS}/backends/cxxrtl/runtime` option. These components
are not used in the Yosys binary; they are only built as a part of the simulation binary.
The interfaces declared in `cxxrtl_capi*.h` contain the stable C API. These interfaces will not be
changed in backward-incompatible ways unless no other option is available, and any breaking changes
will be made in a way that causes the downstream code to fail in a visible way. The ABI of these
interfaces is considered stable as well, and it will not use features complicating its use via
libraries such as libffi or ctypes.
The implementations in `cxxrtl_capi*.cc` are considered private; they are still placed in the include
path to enable build-system-less builds (where the CXXRTL runtime component is included in the C++
file of the simulation toplevel).
The interfaces declared in `cxxrtl*.h` (without `capi`) are unstable and may change without notice.
For clarity, all of the files in this directory and its subdirectories have unique names regardless
of the directory where they are placed.

View file

@ -16,10 +16,10 @@
*
*/
// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_capi.h`.
// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl/capi/cxxrtl_capi.h`.
#include <backends/cxxrtl/cxxrtl.h>
#include <backends/cxxrtl/cxxrtl_capi.h>
#include <cxxrtl/capi/cxxrtl_capi.h>
#include <cxxrtl/cxxrtl.h>
struct _cxxrtl_handle {
std::unique_ptr<cxxrtl::module> module;
@ -35,19 +35,19 @@ cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design) {
return cxxrtl_create_at(design, "");
}
cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *root) {
std::string path = root;
if (!path.empty()) {
cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *top_path_) {
std::string top_path = top_path_;
if (!top_path.empty()) {
// module::debug_info() accepts either an empty path, or a path ending in space to simplify
// the logic in generated code. While this is sketchy at best to expose in the C++ API, this
// would be a lot worse in the C API, so don't expose it here.
assert(path.back() != ' ');
path += ' ';
assert(top_path.back() != ' ');
top_path += ' ';
}
cxxrtl_handle handle = new _cxxrtl_handle;
handle->module = std::move(design->module);
handle->module->debug_info(handle->objects, path);
handle->module->debug_info(&handle->objects, nullptr, top_path);
delete design;
return handle;
}
@ -90,3 +90,46 @@ void cxxrtl_enum(cxxrtl_handle handle, void *data,
void cxxrtl_outline_eval(cxxrtl_outline outline) {
outline->eval();
}
int cxxrtl_attr_type(cxxrtl_attr_set attrs_, const char *name) {
auto attrs = (cxxrtl::metadata_map*)attrs_;
if (!attrs->count(name))
return CXXRTL_ATTR_NONE;
switch (attrs->at(name).value_type) {
case cxxrtl::metadata::UINT:
return CXXRTL_ATTR_UNSIGNED_INT;
case cxxrtl::metadata::SINT:
return CXXRTL_ATTR_SIGNED_INT;
case cxxrtl::metadata::STRING:
return CXXRTL_ATTR_STRING;
case cxxrtl::metadata::DOUBLE:
return CXXRTL_ATTR_DOUBLE;
default:
// Present unsupported attribute type the same way as no attribute at all.
return CXXRTL_ATTR_NONE;
}
}
uint64_t cxxrtl_attr_get_unsigned_int(cxxrtl_attr_set attrs_, const char *name) {
auto &attrs = *(cxxrtl::metadata_map*)attrs_;
assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::UINT);
return attrs[name].as_uint();
}
int64_t cxxrtl_attr_get_signed_int(cxxrtl_attr_set attrs_, const char *name) {
auto &attrs = *(cxxrtl::metadata_map*)attrs_;
assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::SINT);
return attrs[name].as_sint();
}
const char *cxxrtl_attr_get_string(cxxrtl_attr_set attrs_, const char *name) {
auto &attrs = *(cxxrtl::metadata_map*)attrs_;
assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::STRING);
return attrs[name].as_string().c_str();
}
double cxxrtl_attr_get_double(cxxrtl_attr_set attrs_, const char *name) {
auto &attrs = *(cxxrtl::metadata_map*)attrs_;
assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::DOUBLE);
return attrs[name].as_double();
}

View file

@ -55,8 +55,8 @@ cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design);
// Create a design handle at a given hierarchy position from a design toplevel.
//
// This operation is similar to `cxxrtl_create`, except the full hierarchical name of every object
// is prepended with `root`.
cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *root);
// is prepended with `top_path`.
cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *top_path);
// Release all resources used by a design and its handle.
void cxxrtl_destroy(cxxrtl_handle handle);
@ -200,6 +200,10 @@ enum cxxrtl_flag {
// node, such as inputs and dangling wires.
CXXRTL_UNDRIVEN = 1 << 4,
// Generated correspond to netlist nodes that correspond to state with an internal name, that
// need to be saved, but wouldn't otherwise have a debug item generated.
CXXRTL_GENERATED = 1 << 5,
// More object flags may be added in the future, but the existing ones will never change.
};
@ -240,6 +244,11 @@ struct cxxrtl_object {
// through wires, the bits are double buffered. To avoid race conditions, user code should
// always read from `curr` and write to `next`. The `curr` pointer is always valid; for objects
// that cannot be modified, or cannot be modified in a race-free way, `next` is NULL.
//
// In case where `width == 0`, `curr` is a non-NULL pointer unique for the wire. That is,
// there is a 1-to-1 correspondence between simulation objects and `curr` pointers, regardless
// of whether they have storage or not. (Aliases' `curr` pointer equals that of some other
// simulated object.)
uint32_t *curr;
uint32_t *next;
@ -249,6 +258,15 @@ struct cxxrtl_object {
// this field to NULL.
struct _cxxrtl_outline *outline;
// Opaque reference to an attribute set.
//
// See the documentation of `cxxrtl_attr_set` for details. When creating a `cxxrtl_object`, set
// this field to NULL.
//
// The lifetime of the pointers returned by `cxxrtl_attr_*` family of functions is the same as
// the lifetime of this structure.
struct _cxxrtl_attr_set *attrs;
// More description fields may be added in the future, but the existing ones will never change.
};
@ -304,6 +322,62 @@ typedef struct _cxxrtl_outline *cxxrtl_outline;
// re-evaluated, otherwise the bits read from that object are meaningless.
void cxxrtl_outline_eval(cxxrtl_outline outline);
// Opaque reference to an attribute set.
//
// An attribute set is a map between attribute names (always strings) and values (which may have
// several different types). To find out the type of an attribute, use `cxxrtl_attr_type`, and
// to retrieve the value of an attribute, use `cxxrtl_attr_as_string`.
typedef struct _cxxrtl_attr_set *cxxrtl_attr_set;
// Type of an attribute.
enum cxxrtl_attr_type {
// Attribute is not present.
CXXRTL_ATTR_NONE = 0,
// Attribute has an unsigned integer value.
CXXRTL_ATTR_UNSIGNED_INT = 1,
// Attribute has an unsigned integer value.
CXXRTL_ATTR_SIGNED_INT = 2,
// Attribute has a string value.
CXXRTL_ATTR_STRING = 3,
// Attribute has a double precision floating point value.
CXXRTL_ATTR_DOUBLE = 4,
// More attribute types may be defined in the future, but the existing values will never change.
};
// Determine the presence and type of an attribute in an attribute set.
//
// This function returns one of the possible `cxxrtl_attr_type` values.
int cxxrtl_attr_type(cxxrtl_attr_set attrs, const char *name);
// Retrieve an unsigned integer valued attribute from an attribute set.
//
// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_UNSIGNED_INT`.
// If assertions are disabled, returns 0 if the attribute is missing or has an incorrect type.
uint64_t cxxrtl_attr_get_unsigned_int(cxxrtl_attr_set attrs, const char *name);
// Retrieve a signed integer valued attribute from an attribute set.
//
// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_SIGNED_INT`.
// If assertions are disabled, returns 0 if the attribute is missing or has an incorrect type.
int64_t cxxrtl_attr_get_signed_int(cxxrtl_attr_set attrs, const char *name);
// Retrieve a string valued attribute from an attribute set. The returned string is zero-terminated.
//
// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_STRING`. If assertions
// are disabled, returns NULL if the attribute is missing or has an incorrect type.
const char *cxxrtl_attr_get_string(cxxrtl_attr_set attrs, const char *name);
// Retrieve a double precision floating point valued attribute from an attribute set.
//
// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_DOUBLE`. If assertions
// are disabled, returns NULL if the attribute is missing or has an incorrect type.
double cxxrtl_attr_get_double(cxxrtl_attr_set attrs, const char *name);
#ifdef __cplusplus
}
#endif

View file

@ -16,10 +16,10 @@
*
*/
// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_vcd_capi.h`.
// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl/capi/cxxrtl_capi_vcd.h`.
#include <backends/cxxrtl/cxxrtl_vcd.h>
#include <backends/cxxrtl/cxxrtl_vcd_capi.h>
#include <cxxrtl/capi/cxxrtl_capi_vcd.h>
#include <cxxrtl/cxxrtl_vcd.h>
extern const cxxrtl::debug_items &cxxrtl_debug_items_from_handle(cxxrtl_handle handle);

View file

@ -16,8 +16,8 @@
*
*/
#ifndef CXXRTL_VCD_CAPI_H
#define CXXRTL_VCD_CAPI_H
#ifndef CXXRTL_CAPI_VCD_H
#define CXXRTL_CAPI_VCD_H
// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_vcd_capi.cc`.
//
@ -27,7 +27,7 @@
#include <stddef.h>
#include <stdint.h>
#include <backends/cxxrtl/cxxrtl_capi.h>
#include <cxxrtl/capi/cxxrtl_capi.h>
#ifdef __cplusplus
extern "C" {

View file

@ -28,6 +28,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <cassert>
#include <limits>
#include <type_traits>
@ -38,8 +39,10 @@
#include <memory>
#include <functional>
#include <sstream>
#include <iostream>
#include <backends/cxxrtl/cxxrtl_capi.h>
// `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements.
#include <cxxrtl/capi/cxxrtl_capi.h>
#ifndef __has_attribute
# define __has_attribute(x) 0
@ -144,7 +147,7 @@ struct value : public expr_base<value<Bits>> {
// These functions ensure that a conversion is never out of range, and should be always used, if at all
// possible, instead of direct manipulation of the `data` member. For very large types, .slice() and
// .concat() can be used to split them into more manageable parts.
template<class IntegerT>
template<class IntegerT, typename std::enable_if<!std::is_signed<IntegerT>::value, int>::type = 0>
CXXRTL_ALWAYS_INLINE
IntegerT get() const {
static_assert(std::numeric_limits<IntegerT>::is_integer && !std::numeric_limits<IntegerT>::is_signed,
@ -157,15 +160,32 @@ struct value : public expr_base<value<Bits>> {
return result;
}
template<class IntegerT>
template<class IntegerT, typename std::enable_if<std::is_signed<IntegerT>::value, int>::type = 0>
CXXRTL_ALWAYS_INLINE
void set(IntegerT other) {
IntegerT get() const {
auto unsigned_result = get<typename std::make_unsigned<IntegerT>::type>();
IntegerT result;
memcpy(&result, &unsigned_result, sizeof(IntegerT));
return result;
}
template<class IntegerT, typename std::enable_if<!std::is_signed<IntegerT>::value, int>::type = 0>
CXXRTL_ALWAYS_INLINE
void set(IntegerT value) {
static_assert(std::numeric_limits<IntegerT>::is_integer && !std::numeric_limits<IntegerT>::is_signed,
"set<T>() requires T to be an unsigned integral type");
static_assert(std::numeric_limits<IntegerT>::digits >= Bits,
"set<T>() requires the value to be at least as wide as T is");
for (size_t n = 0; n < chunks; n++)
data[n] = (other >> (n * chunk::bits)) & chunk::mask;
data[n] = (value >> (n * chunk::bits)) & chunk::mask;
}
template<class IntegerT, typename std::enable_if<std::is_signed<IntegerT>::value, int>::type = 0>
CXXRTL_ALWAYS_INLINE
void set(IntegerT value) {
typename std::make_unsigned<IntegerT>::type unsigned_value;
memcpy(&unsigned_value, &value, sizeof(IntegerT));
set(unsigned_value);
}
// Operations with compile-time parameters.
@ -418,6 +438,7 @@ struct value : public expr_base<value<Bits>> {
carry = (shift_bits == 0) ? 0
: data[n] >> (chunk::bits - shift_bits);
}
result.data[result.chunks - 1] &= result.msb_mask;
return result;
}
@ -428,12 +449,12 @@ struct value : public expr_base<value<Bits>> {
// Detect shifts definitely large than Bits early.
for (size_t n = 1; n < amount.chunks; n++)
if (amount.data[n] != 0)
return {};
return (Signed && is_neg()) ? value<Bits>().bit_not() : value<Bits>();
// Past this point we can use the least significant chunk as the shift size.
size_t shift_chunks = amount.data[0] / chunk::bits;
size_t shift_bits = amount.data[0] % chunk::bits;
if (shift_chunks >= chunks)
return {};
return (Signed && is_neg()) ? value<Bits>().bit_not() : value<Bits>();
value<Bits> result;
chunk::type carry = 0;
for (size_t n = 0; n < chunks - shift_chunks; n++) {
@ -442,12 +463,13 @@ struct value : public expr_base<value<Bits>> {
: data[chunks - 1 - n] << (chunk::bits - shift_bits);
}
if (Signed && is_neg()) {
size_t top_chunk_idx = (Bits - shift_bits) / chunk::bits;
size_t top_chunk_bits = (Bits - shift_bits) % chunk::bits;
size_t top_chunk_idx = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) / chunk::bits;
size_t top_chunk_bits = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) % chunk::bits;
for (size_t n = top_chunk_idx + 1; n < chunks; n++)
result.data[n] = chunk::mask;
if (shift_bits != 0)
if (amount.data[0] != 0)
result.data[top_chunk_idx] |= chunk::mask << top_chunk_bits;
result.data[result.chunks - 1] &= result.msb_mask;
}
return result;
}
@ -472,9 +494,15 @@ struct value : public expr_base<value<Bits>> {
carry = (shift_bits == 0) ? 0
: data[result.chunks + shift_chunks - 1 - n] << (chunk::bits - shift_bits);
}
result.data[result.chunks - 1] &= result.msb_mask;
return result;
}
CXXRTL_ALWAYS_INLINE
value<Bits> bwmux(const value<Bits> &b, const value<Bits> &s) const {
return (bit_and(s.bit_not())).bit_or(b.bit_and(s));
}
template<size_t ResultBits, size_t SelBits>
value<ResultBits> demux(const value<SelBits> &sel) const {
static_assert(Bits << SelBits == ResultBits, "invalid sizes used in demux()");
@ -507,12 +535,14 @@ struct value : public expr_base<value<Bits>> {
size_t count = 0;
for (size_t n = 0; n < chunks; n++) {
chunk::type x = data[chunks - 1 - n];
if (x == 0) {
count += (n == 0 ? Bits % chunk::bits : chunk::bits);
} else {
// This loop implements the find first set idiom as recognized by LLVM.
for (; x != 0; count++)
// First add to `count` as if the chunk is zero
constexpr size_t msb_chunk_bits = Bits % chunk::bits != 0 ? Bits % chunk::bits : chunk::bits;
count += (n == 0 ? msb_chunk_bits : chunk::bits);
// If the chunk isn't zero, correct the `count` value and return
if (x != 0) {
for (; x != 0; count--)
x >>= 1;
break;
}
}
return count;
@ -541,7 +571,7 @@ struct value : public expr_base<value<Bits>> {
}
value<Bits> neg() const {
return value<Bits> { 0u }.sub(*this);
return value<Bits>().sub(*this);
}
bool ucmp(const value<Bits> &other) const {
@ -575,6 +605,38 @@ struct value : public expr_base<value<Bits>> {
result.data[result.chunks - 1] &= result.msb_mask;
return result;
}
std::pair<value<Bits>, value<Bits>> udivmod(value<Bits> divisor) const {
value<Bits> quotient;
value<Bits> dividend = *this;
if (dividend.ucmp(divisor))
return {/*quotient=*/value<Bits>{0u}, /*remainder=*/dividend};
int64_t divisor_shift = divisor.ctlz() - dividend.ctlz();
assert(divisor_shift >= 0);
divisor = divisor.shl(value<Bits>{(chunk::type) divisor_shift});
for (size_t step = 0; step <= divisor_shift; step++) {
quotient = quotient.shl(value<Bits>{1u});
if (!dividend.ucmp(divisor)) {
dividend = dividend.sub(divisor);
quotient.set_bit(0, true);
}
divisor = divisor.shr(value<Bits>{1u});
}
return {quotient, /*remainder=*/dividend};
}
std::pair<value<Bits>, value<Bits>> sdivmod(const value<Bits> &other) const {
value<Bits + 1> quotient;
value<Bits + 1> remainder;
value<Bits + 1> dividend = sext<Bits + 1>();
value<Bits + 1> divisor = other.template sext<Bits + 1>();
if (is_neg()) dividend = dividend.neg();
if (other.is_neg()) divisor = divisor.neg();
std::tie(quotient, remainder) = dividend.udivmod(divisor);
if (is_neg() != other.is_neg()) quotient = quotient.neg();
if (is_neg()) remainder = remainder.neg();
return {quotient.template trunc<Bits>(), remainder.template trunc<Bits>()};
}
};
// Expression template for a slice, usable as lvalue or rvalue, and composable with other expression templates here.
@ -741,8 +803,13 @@ struct wire {
next.template set<IntegerT>(other);
}
bool commit() {
// This method intentionally takes a mandatory argument (to make it more difficult to misuse in
// black box implementations, leading to missed observer events). It is generic over its argument
// to allow the `on_update` method to be non-virtual.
template<class ObserverT>
bool commit(ObserverT &observer) {
if (curr != next) {
observer.on_update(curr.chunks, curr.data, next.data);
curr = next;
return true;
}
@ -816,12 +883,17 @@ struct memory {
write { index, val, mask, priority });
}
bool commit() {
// See the note for `wire::commit()`.
template<class ObserverT>
bool commit(ObserverT &observer) {
bool changed = false;
for (const write &entry : write_queue) {
value<Width> elem = data[entry.index];
elem = elem.update(entry.val, entry.mask);
changed |= (data[entry.index] != elem);
if (data[entry.index] != elem) {
observer.on_update(value<Width>::chunks, data[0].data, elem.data, entry.index);
changed |= true;
}
data[entry.index] = elem;
}
write_queue.clear();
@ -840,14 +912,14 @@ struct metadata {
// In debug mode, using the wrong .as_*() function will assert.
// In release mode, using the wrong .as_*() function will safely return a default value.
const unsigned uint_value = 0;
const signed sint_value = 0;
const uint64_t uint_value = 0;
const int64_t sint_value = 0;
const std::string string_value = "";
const double double_value = 0.0;
metadata() : value_type(MISSING) {}
metadata(unsigned value) : value_type(UINT), uint_value(value) {}
metadata(signed value) : value_type(SINT), sint_value(value) {}
metadata(uint64_t value) : value_type(UINT), uint_value(value) {}
metadata(int64_t value) : value_type(SINT), sint_value(value) {}
metadata(const std::string &value) : value_type(STRING), string_value(value) {}
metadata(const char *value) : value_type(STRING), string_value(value) {}
metadata(double value) : value_type(DOUBLE), double_value(value) {}
@ -855,12 +927,12 @@ struct metadata {
metadata(const metadata &) = default;
metadata &operator=(const metadata &) = delete;
unsigned as_uint() const {
uint64_t as_uint() const {
assert(value_type == UINT);
return uint_value;
}
signed as_sint() const {
int64_t as_sint() const {
assert(value_type == SINT);
return sint_value;
}
@ -874,10 +946,322 @@ struct metadata {
assert(value_type == DOUBLE);
return double_value;
}
// Internal CXXRTL use only.
static std::map<std::string, metadata> deserialize(const char *ptr) {
std::map<std::string, metadata> result;
std::string name;
// Grammar:
// string ::= [^\0]+ \0
// metadata ::= [uid] .{8} | s <string>
// map ::= ( <string> <metadata> )* \0
for (;;) {
if (*ptr) {
name += *ptr++;
} else if (!name.empty()) {
ptr++;
auto get_u64 = [&]() {
uint64_t result = 0;
for (size_t count = 0; count < 8; count++)
result = (result << 8) | *ptr++;
return result;
};
char type = *ptr++;
if (type == 'u') {
uint64_t value = get_u64();
result.emplace(name, value);
} else if (type == 'i') {
int64_t value = (int64_t)get_u64();
result.emplace(name, value);
} else if (type == 'd') {
double dvalue;
uint64_t uvalue = get_u64();
static_assert(sizeof(dvalue) == sizeof(uvalue), "double must be 64 bits in size");
memcpy(&dvalue, &uvalue, sizeof(dvalue));
result.emplace(name, dvalue);
} else if (type == 's') {
std::string value;
while (*ptr)
value += *ptr++;
ptr++;
result.emplace(name, value);
} else {
assert(false && "Unknown type specifier");
return result;
}
name.clear();
} else {
return result;
}
}
}
};
typedef std::map<std::string, metadata> metadata_map;
struct performer;
// An object that allows formatting a string lazily.
struct lazy_fmt {
virtual std::string operator() () const = 0;
};
// Flavor of a `$check` cell.
enum class flavor {
// Corresponds to a `$assert` cell in other flows, and a Verilog `assert ()` statement.
ASSERT,
// Corresponds to a `$assume` cell in other flows, and a Verilog `assume ()` statement.
ASSUME,
// Corresponds to a `$live` cell in other flows, and a Verilog `assert (eventually)` statement.
ASSERT_EVENTUALLY,
// Corresponds to a `$fair` cell in other flows, and a Verilog `assume (eventually)` statement.
ASSUME_EVENTUALLY,
// Corresponds to a `$cover` cell in other flows, and a Verilog `cover ()` statement.
COVER,
};
// An object that can be passed to a `eval()` method in order to act on side effects. The default behavior implemented
// below is the same as the behavior of `eval(nullptr)`, except that `-print-output` option of `write_cxxrtl` is not
// taken into account.
struct performer {
// Called by generated formatting code to evaluate a Verilog `$time` expression.
virtual int64_t vlog_time() const { return 0; }
// Called by generated formatting code to evaluate a Verilog `$realtime` expression.
virtual double vlog_realtime() const { return vlog_time(); }
// Called when a `$print` cell is triggered.
virtual void on_print(const lazy_fmt &formatter, const metadata_map &attributes) {
std::cout << formatter();
}
// Called when a `$check` cell is triggered.
virtual void on_check(flavor type, bool condition, const lazy_fmt &formatter, const metadata_map &attributes) {
if (type == flavor::ASSERT || type == flavor::ASSUME) {
if (!condition)
std::cerr << formatter();
CXXRTL_ASSERT(condition && "Check failed");
}
}
};
// An object that can be passed to a `commit()` method in order to produce a replay log of every state change in
// the simulation. Unlike `performer`, `observer` does not use virtual calls as their overhead is unacceptable, and
// a comparatively heavyweight template-based solution is justified.
struct observer {
// Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks
// at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and
// `base` points to the first chunk.
void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {}
// Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]`
// with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to
// the memory element chunk count and `base` points to the first chunk of the first element of the memory.
void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {}
};
// Must be kept in sync with `struct FmtPart` in kernel/fmt.h!
// Default member initializers would make this a non-aggregate-type in C++11, so they are commented out.
struct fmt_part {
enum {
LITERAL = 0,
INTEGER = 1,
STRING = 2,
UNICHAR = 3,
VLOG_TIME = 4,
} type;
// LITERAL type
std::string str;
// INTEGER/STRING/UNICHAR types
// + value<Bits> val;
// INTEGER/STRING/VLOG_TIME types
enum {
RIGHT = 0,
LEFT = 1,
NUMERIC = 2,
} justify; // = RIGHT;
char padding; // = '\0';
size_t width; // = 0;
// INTEGER type
unsigned base; // = 10;
bool signed_; // = false;
enum {
MINUS = 0,
PLUS_MINUS = 1,
SPACE_MINUS = 2,
} sign; // = MINUS;
bool hex_upper; // = false;
bool show_base; // = false;
bool group; // = false;
// VLOG_TIME type
bool realtime; // = false;
// + int64_t itime;
// + double ftime;
// Format the part as a string.
//
// The values of `vlog_time` and `vlog_realtime` are used for Verilog `$time` and `$realtime`, correspondingly.
template<size_t Bits>
std::string render(value<Bits> val, performer *performer = nullptr)
{
// We might want to replace some of these bit() calls with direct
// chunk access if it turns out to be slow enough to matter.
std::string buf;
std::string prefix;
switch (type) {
case LITERAL:
return str;
case STRING: {
buf.reserve(Bits/8);
for (int i = 0; i < Bits; i += 8) {
char ch = 0;
for (int j = 0; j < 8 && i + j < int(Bits); j++)
if (val.bit(i + j))
ch |= 1 << j;
if (ch != 0)
buf.append({ch});
}
std::reverse(buf.begin(), buf.end());
break;
}
case UNICHAR: {
uint32_t codepoint = val.template zcast<32>().template get<uint32_t>();
if (codepoint >= 0x10000)
buf += (char)(0xf0 | (codepoint >> 18));
else if (codepoint >= 0x800)
buf += (char)(0xe0 | (codepoint >> 12));
else if (codepoint >= 0x80)
buf += (char)(0xc0 | (codepoint >> 6));
else
buf += (char)codepoint;
if (codepoint >= 0x10000)
buf += (char)(0x80 | ((codepoint >> 12) & 0x3f));
if (codepoint >= 0x800)
buf += (char)(0x80 | ((codepoint >> 6) & 0x3f));
if (codepoint >= 0x80)
buf += (char)(0x80 | ((codepoint >> 0) & 0x3f));
break;
}
case INTEGER: {
bool negative = signed_ && val.is_neg();
if (negative) {
prefix = "-";
val = val.neg();
} else {
switch (sign) {
case MINUS: break;
case PLUS_MINUS: prefix = "+"; break;
case SPACE_MINUS: prefix = " "; break;
}
}
size_t val_width = Bits;
if (base != 10) {
val_width = 1;
for (size_t index = 0; index < Bits; index++)
if (val.bit(index))
val_width = index + 1;
}
if (base == 2) {
if (show_base)
prefix += "0b";
for (size_t index = 0; index < val_width; index++) {
if (group && index > 0 && index % 4 == 0)
buf += '_';
buf += (val.bit(index) ? '1' : '0');
}
} else if (base == 8 || base == 16) {
if (show_base)
prefix += (base == 16) ? (hex_upper ? "0X" : "0x") : "0o";
size_t step = (base == 16) ? 4 : 3;
for (size_t index = 0; index < val_width; index += step) {
if (group && index > 0 && index % (4 * step) == 0)
buf += '_';
uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2);
if (step == 4)
value |= val.bit(index + 3) << 3;
buf += (hex_upper ? "0123456789ABCDEF" : "0123456789abcdef")[value];
}
} else if (base == 10) {
if (show_base)
prefix += "0d";
if (val.is_zero())
buf += '0';
value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>();
size_t index = 0;
while (!xval.is_zero()) {
if (group && index > 0 && index % 3 == 0)
buf += '_';
value<(Bits > 4 ? Bits : 4)> quotient, remainder;
if (Bits >= 4)
std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u});
else
std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval);
buf += '0' + remainder.template trunc<4>().template get<uint8_t>();
xval = quotient;
index++;
}
} else assert(false && "Unsupported base for fmt_part");
if (justify == NUMERIC && group && padding == '0') {
int group_size = base == 10 ? 3 : 4;
while (prefix.size() + buf.size() < width) {
if (buf.size() % (group_size + 1) == group_size)
buf += '_';
buf += '0';
}
}
std::reverse(buf.begin(), buf.end());
break;
}
case VLOG_TIME: {
if (performer) {
buf = realtime ? std::to_string(performer->vlog_realtime()) : std::to_string(performer->vlog_time());
} else {
buf = realtime ? std::to_string(0.0) : std::to_string(0);
}
break;
}
}
std::string str;
assert(width == 0 || padding != '\0');
if (prefix.size() + buf.size() < width) {
size_t pad_width = width - prefix.size() - buf.size();
switch (justify) {
case LEFT:
str += prefix;
str += buf;
str += std::string(pad_width, padding);
break;
case RIGHT:
str += std::string(pad_width, padding);
str += prefix;
str += buf;
break;
case NUMERIC:
str += prefix;
str += std::string(pad_width, padding);
str += buf;
break;
}
} else {
str += prefix;
str += buf;
}
return str;
}
};
// Tag class to disambiguate values/wires and their aliases.
struct debug_alias {};
@ -889,6 +1273,9 @@ using debug_outline = ::_cxxrtl_outline;
//
// To avoid violating strict aliasing rules, this structure has to be a subclass of the one used
// in the C API, or it would not be possible to cast between the pointers to these.
//
// The `attrs` member cannot be owned by this structure because a `cxxrtl_object` can be created
// from external C code.
struct debug_item : ::cxxrtl_object {
// Object types.
enum : uint32_t {
@ -907,13 +1294,14 @@ struct debug_item : ::cxxrtl_object {
DRIVEN_SYNC = CXXRTL_DRIVEN_SYNC,
DRIVEN_COMB = CXXRTL_DRIVEN_COMB,
UNDRIVEN = CXXRTL_UNDRIVEN,
GENERATED = CXXRTL_GENERATED,
};
debug_item(const ::cxxrtl_object &object) : cxxrtl_object(object) {}
template<size_t Bits>
debug_item(value<Bits> &item, size_t lsb_offset = 0, uint32_t flags_ = 0) {
static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
static_assert(Bits == 0 || sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
"value<Bits> is not compatible with C layout");
type = VALUE;
flags = flags_;
@ -924,11 +1312,12 @@ struct debug_item : ::cxxrtl_object {
curr = item.data;
next = item.data;
outline = nullptr;
attrs = nullptr;
}
template<size_t Bits>
debug_item(const value<Bits> &item, size_t lsb_offset = 0) {
static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
static_assert(Bits == 0 || sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
"value<Bits> is not compatible with C layout");
type = VALUE;
flags = DRIVEN_COMB;
@ -939,12 +1328,14 @@ struct debug_item : ::cxxrtl_object {
curr = const_cast<chunk_t*>(item.data);
next = nullptr;
outline = nullptr;
attrs = nullptr;
}
template<size_t Bits>
debug_item(wire<Bits> &item, size_t lsb_offset = 0, uint32_t flags_ = 0) {
static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) &&
sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t),
static_assert(Bits == 0 ||
(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) &&
sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t)),
"wire<Bits> is not compatible with C layout");
type = WIRE;
flags = flags_;
@ -955,11 +1346,12 @@ struct debug_item : ::cxxrtl_object {
curr = item.curr.data;
next = item.next.data;
outline = nullptr;
attrs = nullptr;
}
template<size_t Width>
debug_item(memory<Width> &item, size_t zero_offset = 0) {
static_assert(sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t),
static_assert(Width == 0 || sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t),
"memory<Width> is not compatible with C layout");
type = MEMORY;
flags = 0;
@ -970,11 +1362,12 @@ struct debug_item : ::cxxrtl_object {
curr = item.data ? item.data[0].data : nullptr;
next = nullptr;
outline = nullptr;
attrs = nullptr;
}
template<size_t Bits>
debug_item(debug_alias, const value<Bits> &item, size_t lsb_offset = 0) {
static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
static_assert(Bits == 0 || sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
"value<Bits> is not compatible with C layout");
type = ALIAS;
flags = DRIVEN_COMB;
@ -985,12 +1378,14 @@ struct debug_item : ::cxxrtl_object {
curr = const_cast<chunk_t*>(item.data);
next = nullptr;
outline = nullptr;
attrs = nullptr;
}
template<size_t Bits>
debug_item(debug_alias, const wire<Bits> &item, size_t lsb_offset = 0) {
static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) &&
sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t),
static_assert(Bits == 0 ||
(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) &&
sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t)),
"wire<Bits> is not compatible with C layout");
type = ALIAS;
flags = DRIVEN_COMB;
@ -1001,11 +1396,12 @@ struct debug_item : ::cxxrtl_object {
curr = const_cast<chunk_t*>(item.curr.data);
next = nullptr;
outline = nullptr;
attrs = nullptr;
}
template<size_t Bits>
debug_item(debug_outline &group, const value<Bits> &item, size_t lsb_offset = 0) {
static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
static_assert(Bits == 0 || sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
"value<Bits> is not compatible with C layout");
type = OUTLINE;
flags = DRIVEN_COMB;
@ -1016,6 +1412,7 @@ struct debug_item : ::cxxrtl_object {
curr = const_cast<chunk_t*>(item.data);
next = nullptr;
outline = &group;
attrs = nullptr;
}
template<size_t Bits, class IntegerT>
@ -1036,11 +1433,38 @@ struct debug_item : ::cxxrtl_object {
};
static_assert(std::is_standard_layout<debug_item>::value, "debug_item is not compatible with C layout");
struct debug_items {
std::map<std::string, std::vector<debug_item>> table;
} // namespace cxxrtl
void add(const std::string &name, debug_item &&item) {
std::vector<debug_item> &parts = table[name];
typedef struct _cxxrtl_attr_set {
cxxrtl::metadata_map map;
} *cxxrtl_attr_set;
namespace cxxrtl {
// Representation of an attribute set in the C++ interface.
using debug_attrs = ::_cxxrtl_attr_set;
struct debug_items {
// Debug items may be composed of multiple parts, but the attributes are shared between all of them.
// There are additional invariants, not all of which are not checked by this code:
// - Memories and non-memories cannot be mixed together.
// - Bit indices (considering `lsb_at` and `width`) must not overlap.
// - Row indices (considering `depth` and `zero_at`) must be the same.
// - The `INPUT` and `OUTPUT` flags must be the same for all parts.
// Other than that, the parts can be quite different, e.g. it is OK to mix a value, a wire, an alias,
// and an outline, in the debug information for a single name in four parts.
std::map<std::string, std::vector<debug_item>> table;
std::map<std::string, std::unique_ptr<debug_attrs>> attrs_table;
void add(const std::string &path, debug_item &&item, metadata_map &&item_attrs = {}) {
assert((path.empty() || path[path.size() - 1] != ' ') && path.find(" ") == std::string::npos);
std::unique_ptr<debug_attrs> &attrs = attrs_table[path];
if (attrs.get() == nullptr)
attrs = std::unique_ptr<debug_attrs>(new debug_attrs);
for (auto attr : item_attrs)
attrs->map.insert(attr);
item.attrs = attrs.get();
std::vector<debug_item> &parts = table[path];
parts.emplace_back(item);
std::sort(parts.begin(), parts.end(),
[](const debug_item &a, const debug_item &b) {
@ -1048,31 +1472,82 @@ struct debug_items {
});
}
size_t count(const std::string &name) const {
if (table.count(name) == 0)
// This overload exists to reduce excessive stack slot allocation in `CXXRTL_EXTREMELY_COLD void debug_info()`.
template<class... T>
void add(const std::string &base_path, const char *path, const char *serialized_item_attrs, T&&... args) {
add(base_path + path, debug_item(std::forward<T>(args)...), metadata::deserialize(serialized_item_attrs));
}
size_t count(const std::string &path) const {
if (table.count(path) == 0)
return 0;
return table.at(name).size();
return table.at(path).size();
}
const std::vector<debug_item> &parts_at(const std::string &name) const {
return table.at(name);
const std::vector<debug_item> &at(const std::string &path) const {
return table.at(path);
}
const debug_item &at(const std::string &name) const {
const std::vector<debug_item> &parts = table.at(name);
// Like `at()`, but operates only on single-part debug items.
const debug_item &operator [](const std::string &path) const {
const std::vector<debug_item> &parts = table.at(path);
assert(parts.size() == 1);
return parts.at(0);
}
const debug_item &operator [](const std::string &name) const {
return at(name);
bool is_memory(const std::string &path) const {
return at(path).at(0).type == debug_item::MEMORY;
}
const metadata_map &attrs(const std::string &path) const {
return attrs_table.at(path)->map;
}
};
// Tag class to disambiguate the default constructor used by the toplevel module that calls reset(),
// Only `module` scopes are defined. The type is implicit, since Yosys does not currently support
// any other scope types.
struct debug_scope {
std::string module_name;
std::unique_ptr<debug_attrs> module_attrs;
std::unique_ptr<debug_attrs> cell_attrs;
};
struct debug_scopes {
std::map<std::string, debug_scope> table;
void add(const std::string &path, const std::string &module_name, metadata_map &&module_attrs, metadata_map &&cell_attrs) {
assert((path.empty() || path[path.size() - 1] != ' ') && path.find(" ") == std::string::npos);
assert(table.count(path) == 0);
debug_scope &scope = table[path];
scope.module_name = module_name;
scope.module_attrs = std::unique_ptr<debug_attrs>(new debug_attrs { module_attrs });
scope.cell_attrs = std::unique_ptr<debug_attrs>(new debug_attrs { cell_attrs });
}
// This overload exists to reduce excessive stack slot allocation in `CXXRTL_EXTREMELY_COLD void debug_info()`.
void add(const std::string &base_path, const char *path, const char *module_name, const char *serialized_module_attrs, const char *serialized_cell_attrs) {
add(base_path + path, module_name, metadata::deserialize(serialized_module_attrs), metadata::deserialize(serialized_cell_attrs));
}
size_t contains(const std::string &path) const {
return table.count(path);
}
const debug_scope &operator [](const std::string &path) const {
return table.at(path);
}
};
// Tag class to disambiguate the default constructor used by the toplevel module that calls `reset()`,
// and the constructor of interior modules that should not call it.
struct interior {};
// The core API of the `module` class consists of only four virtual methods: `reset()`, `eval()`,
// `commit`, and `debug_info()`. (The virtual destructor is made necessary by C++.) Every other method
// is a convenience method, and exists solely to simplify some common pattern for C++ API consumers.
// No behavior may be added to such convenience methods that other parts of CXXRTL can rely on, since
// there is no guarantee they will be called (and, for example, other CXXRTL libraries will often call
// the `eval()` and `commit()` directly instead, as well as being exposed in the C API).
struct module {
module() {}
virtual ~module() {}
@ -1088,21 +1563,35 @@ struct module {
virtual void reset() = 0;
virtual bool eval() = 0;
// The `eval()` callback object, `performer`, is included in the virtual call signature since
// the generated code has broadly identical performance properties.
virtual bool eval(performer *performer = nullptr) = 0;
// The `commit()` callback object, `observer`, is not included in the virtual call signature since
// the generated code is severely pessimized by it. To observe commit events, the non-virtual
// `commit(observer *)` overload must be called directly on a `module` subclass.
virtual bool commit() = 0;
size_t step() {
size_t step(performer *performer = nullptr) {
size_t deltas = 0;
bool converged = false;
do {
converged = eval();
converged = eval(performer);
deltas++;
} while (commit() && !converged);
return deltas;
}
virtual void debug_info(debug_items &items, std::string path = "") {
(void)items, (void)path;
virtual void debug_info(debug_items *items, debug_scopes *scopes, std::string path, metadata_map &&cell_attrs = {}) {
(void)items, (void)scopes, (void)path, (void)cell_attrs;
}
// Compatibility method.
#if __has_attribute(deprecated)
__attribute__((deprecated("Use `debug_info(&items, /*scopes=*/nullptr, path);` instead.")))
#endif
void debug_info(debug_items &items, std::string path) {
debug_info(&items, /*scopes=*/nullptr, path);
}
};
@ -1281,7 +1770,7 @@ value<BitsY> shr_uu(const value<BitsA> &a, const value<BitsB> &b) {
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> shr_su(const value<BitsA> &a, const value<BitsB> &b) {
return a.shr(b).template scast<BitsY>();
return a.template scast<BitsY>().shr(b);
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
@ -1520,35 +2009,23 @@ CXXRTL_ALWAYS_INLINE
std::pair<value<BitsY>, value<BitsY>> divmod_uu(const value<BitsA> &a, const value<BitsB> &b) {
constexpr size_t Bits = max(BitsY, max(BitsA, BitsB));
value<Bits> quotient;
value<Bits> remainder;
value<Bits> dividend = a.template zext<Bits>();
value<Bits> divisor = b.template zext<Bits>();
if (dividend.ucmp(divisor))
return {/*quotient=*/value<BitsY> { 0u }, /*remainder=*/dividend.template trunc<BitsY>()};
uint32_t divisor_shift = dividend.ctlz() - divisor.ctlz();
divisor = divisor.shl(value<32> { divisor_shift });
for (size_t step = 0; step <= divisor_shift; step++) {
quotient = quotient.shl(value<1> { 1u });
if (!dividend.ucmp(divisor)) {
dividend = dividend.sub(divisor);
quotient.set_bit(0, true);
}
divisor = divisor.shr(value<1> { 1u });
}
return {quotient.template trunc<BitsY>(), /*remainder=*/dividend.template trunc<BitsY>()};
value<Bits> divisor = b.template trunc<BitsB>().template zext<Bits>();
std::tie(quotient, remainder) = dividend.udivmod(divisor);
return {quotient.template trunc<BitsY>(), remainder.template trunc<BitsY>()};
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
std::pair<value<BitsY>, value<BitsY>> divmod_ss(const value<BitsA> &a, const value<BitsB> &b) {
value<BitsA + 1> ua = a.template sext<BitsA + 1>();
value<BitsB + 1> ub = b.template sext<BitsB + 1>();
if (ua.is_neg()) ua = ua.neg();
if (ub.is_neg()) ub = ub.neg();
value<BitsY> y, r;
std::tie(y, r) = divmod_uu<BitsY>(ua, ub);
if (a.is_neg() != b.is_neg()) y = y.neg();
if (a.is_neg()) r = r.neg();
return {y, r};
constexpr size_t Bits = max(BitsY, max(BitsA, BitsB));
value<Bits> quotient;
value<Bits> remainder;
value<Bits> dividend = a.template sext<Bits>();
value<Bits> divisor = b.template sext<Bits>();
std::tie(quotient, remainder) = dividend.sdivmod(divisor);
return {quotient.template trunc<BitsY>(), remainder.template trunc<BitsY>()};
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
@ -1575,6 +2052,46 @@ value<BitsY> mod_ss(const value<BitsA> &a, const value<BitsB> &b) {
return divmod_ss<BitsY>(a, b).second;
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> modfloor_uu(const value<BitsA> &a, const value<BitsB> &b) {
return divmod_uu<BitsY>(a, b).second;
}
// GHDL Modfloor operator. Returns r=a mod b, such that r has the same sign as b and
// a=b*N+r where N is some integer
// In practical terms, when a and b have different signs and the remainder returned by divmod_ss is not 0
// then return the remainder + b
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> modfloor_ss(const value<BitsA> &a, const value<BitsB> &b) {
value<BitsY> r;
r = divmod_ss<BitsY>(a, b).second;
if((b.is_neg() != a.is_neg()) && !r.is_zero())
return add_ss<BitsY>(b, r);
return r;
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> divfloor_uu(const value<BitsA> &a, const value<BitsB> &b) {
return divmod_uu<BitsY>(a, b).first;
}
// Divfloor. Similar to above: returns q=a//b, where q has the sign of a*b and a=b*q+N.
// In other words, returns (truncating) a/b, except if a and b have different signs
// and there's non-zero remainder, subtract one more towards floor.
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> divfloor_ss(const value<BitsA> &a, const value<BitsB> &b) {
value<BitsY> q, r;
std::tie(q, r) = divmod_ss<BitsY>(a, b);
if ((b.is_neg() != a.is_neg()) && !r.is_zero())
return sub_uu<BitsY>(q, value<1> { 1u });
return q;
}
// Memory helper
struct memory_index {
bool valid;

View file

@ -0,0 +1,873 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2023 Catherine <whitequark@whitequark.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* 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 CXXRTL_REPLAY_H
#define CXXRTL_REPLAY_H
#if !defined(WIN32)
#include <unistd.h>
#define O_BINARY 0
#else
#include <io.h>
#endif
#include <fcntl.h>
#include <cstring>
#include <cstdio>
#include <atomic>
#include <unordered_map>
#include <cxxrtl/cxxrtl.h>
#include <cxxrtl/cxxrtl_time.h>
// Theory of operation
// ===================
//
// Log format
// ----------
//
// The replay log is a simple data format based on a sequence of 32-bit words. The following BNF-like grammar describes
// enough detail to understand the overall structure of the log data and be able to read hex dumps. For a greater
// degree of detail see the source code. The format is considered fully internal to CXXRTL and is subject to change
// without notice.
//
// <file> ::= <file-header> <definitions> <sample>+
// <file-header> ::= 0x52585843 0x00004c54
// <definitions> ::= <packet-define>* <packet-end>
// <sample> ::= <packet-sample> (<packet-change> | <packet-diag>)* <packet-end>
// <packet-define> ::= 0xc0000000 ...
// <packet-sample> ::= 0xc0000001 ...
// <packet-change> ::= 0x0??????? <chunk>+ | 0x1??????? <index> <chunk>+ | 0x2??????? | 0x3???????
// <chunk>, <index> ::= 0x????????
// <packet-diag> ::= <packet-break> | <packet-print> | <packet-assert> | <packet-assume>
// <packet-break> ::= 0xc0000010 <message> <source-location>
// <packet-print> ::= 0xc0000011 <message> <source-location>
// <packet-assert> ::= 0xc0000012 <message> <source-location>
// <packet-assume> ::= 0xc0000013 <message> <source-location>
// <packet-end> ::= 0xFFFFFFFF
//
// The replay log contains sample data, however, it does not cover the entire design. Rather, it only contains sample
// data for the subset of debug items containing _design state_: inputs and registers/latches. This keeps its size to
// a minimum, and recording speed to a maximum. The player samples any missing data by setting the design state items
// to the same values they had during recording, and re-evaluating the design.
//
// Packets for diagnostics (prints, breakpoints, assertions, and assumptions) are used solely for diagnostics emitted
// by the C++ testbench driving the simulation, and are not recorded while evaluating the design. (Diagnostics emitted
// by the RTL can be reconstructed at replay time, so recording them would be a waste of space.)
//
// Limits
// ------
//
// The log may contain:
//
// * Up to 2**28-1 debug items containing design state.
// * Up to 2**32 chunks per debug item.
// * Up to 2**32 rows per memory.
// * Up to 2**32 samples.
//
// Of these limits, the last two are most likely to be eventually exceeded by practical recordings. However, other
// performance considerations will likely limit the size of such practical recordings first, so the log data format
// will undergo a breaking change at that point.
//
// Operations
// ----------
//
// As suggested by the name "replay log", this format is designed for recording (writing) once and playing (reading)
// many times afterwards, such that reading the format can be done linearly and quickly. The log format is designed to
// support three primary read operations:
//
// 1. Initialization
// 2. Rewinding (to time T)
// 3. Replaying (for N samples)
//
// During initialization, the player establishes the mapping between debug item names and their 28-bit identifiers in
// the log. It is done once.
//
// During rewinding, the player begins reading at the latest non-incremental sample that still lies before the requested
// sample time. It continues reading incremental samples after that point until it reaches the requested sample time.
// This process is very cheap as the design is not evaluated; it is essentially a (convoluted) memory copy operation.
//
// During replaying, the player evaluates the design at the current time, which causes all debug items to assume
// the values they had before recording. This process is expensive. Once done, the player advances to the next state
// by reading the next (complete or incremental) sample, as above. Since a range of samples is replayed, this process
// is repeated several times in a row.
//
// In principle, when replaying, the player could only read the state of the inputs and the time delta and use a normal
// eval/commit loop to progress the simulation, which is fully deterministic so its calculated design state should be
// exactly the same as the recorded design state. In practice, it is both faster and more reliable (in presence of e.g.
// user-defined black boxes) to read the recorded values instead of calculating them.
//
// Note: The operations described above are conceptual and do not correspond exactly to methods on `cxxrtl::player`.
// The `cxxrtl::player::replay()` method does not evaluate the design. This is so that delta cycles could be ignored
// if they are not of interest while replaying.
namespace cxxrtl {
// A single diagnostic that can be manipulated as an object (including being written to and read from a file).
// This differs from the base CXXRTL interface, where diagnostics can only be emitted via a procedure call, and are
// not materialized as objects.
struct diagnostic {
// The `BREAK` flavor corresponds to a breakpoint, which is a diagnostic type that can currently only be emitted
// by the C++ testbench code.
enum flavor {
BREAK = 0,
PRINT = 1,
ASSERT = 2,
ASSUME = 3,
};
flavor type;
std::string message;
std::string location; // same format as the `src` attribute of `$print` or `$check` cell
diagnostic()
: type(BREAK) {}
diagnostic(flavor type, const std::string &message, const std::string &location)
: type(type), message(message), location(location) {}
diagnostic(flavor type, const std::string &message, const char *file, unsigned line)
: type(type), message(message), location(std::string(file) + ':' + std::to_string(line)) {}
};
// A spool stores CXXRTL design state changes in a file.
class spool {
public:
// Unique pointer to a specific sample within a replay log. (Timestamps are not unique.)
typedef uint32_t pointer_t;
// Numeric identifier assigned to a debug item within a replay log. Range limited to [1, MAXIMUM_IDENT].
typedef uint32_t ident_t;
static constexpr uint16_t VERSION = 0x0400;
static constexpr uint64_t HEADER_MAGIC = 0x00004c5452585843;
static constexpr uint64_t VERSION_MASK = 0xffff000000000000;
static constexpr uint32_t PACKET_DEFINE = 0xc0000000;
static constexpr uint32_t PACKET_SAMPLE = 0xc0000001;
enum sample_flag : uint32_t {
EMPTY = 0,
INCREMENTAL = 1,
};
static constexpr uint32_t MAXIMUM_IDENT = 0x0fffffff;
static constexpr uint32_t CHANGE_MASK = 0x30000000;
static constexpr uint32_t PACKET_CHANGE = 0x00000000/* | ident */;
static constexpr uint32_t PACKET_CHANGEI = 0x10000000/* | ident */;
static constexpr uint32_t PACKET_CHANGEL = 0x20000000/* | ident */;
static constexpr uint32_t PACKET_CHANGEH = 0x30000000/* | ident */;
static constexpr uint32_t PACKET_DIAGNOSTIC = 0xc0000010/* | diagnostic::flavor */;
static constexpr uint32_t DIAGNOSTIC_MASK = 0x0000000f;
static constexpr uint32_t PACKET_END = 0xffffffff;
// Writing spools.
class writer {
int fd;
size_t position;
std::vector<uint32_t> buffer;
// These functions aren't overloaded because of implicit numeric conversions.
void emit_word(uint32_t word) {
if (position + 1 == buffer.size())
flush();
buffer[position++] = word;
}
void emit_dword(uint64_t dword) {
emit_word(dword >> 0);
emit_word(dword >> 32);
}
void emit_ident(ident_t ident) {
assert(ident <= MAXIMUM_IDENT);
emit_word(ident);
}
void emit_size(size_t size) {
assert(size <= std::numeric_limits<uint32_t>::max());
emit_word(size);
}
// Same implementation as `emit_size()`, different declared intent.
void emit_index(size_t index) {
assert(index <= std::numeric_limits<uint32_t>::max());
emit_word(index);
}
void emit_string(std::string str) {
// Align to a word boundary, and add at least one terminating \0.
str.resize(str.size() + (sizeof(uint32_t) - (str.size() + sizeof(uint32_t)) % sizeof(uint32_t)));
for (size_t index = 0; index < str.size(); index += sizeof(uint32_t)) {
uint32_t word;
memcpy(&word, &str[index], sizeof(uint32_t));
emit_word(word);
}
}
void emit_time(const time &timestamp) {
const value<time::bits> &raw_timestamp(timestamp);
emit_word(raw_timestamp.data[0]);
emit_word(raw_timestamp.data[1]);
emit_word(raw_timestamp.data[2]);
}
public:
// Creates a writer, and transfers ownership of `fd`, which must be open for appending.
//
// The buffer size is currently fixed to a "reasonably large" size, determined empirically by measuring writer
// performance on a representative design; large but not so large it would e.g. cause address space exhaustion
// on 32-bit platforms.
writer(spool &spool) : fd(spool.take_write()), position(0), buffer(32 * 1024 * 1024) {
assert(fd != -1);
#if !defined(WIN32)
int result = ftruncate(fd, 0);
#else
int result = _chsize_s(fd, 0);
#endif
assert(result == 0);
}
writer(writer &&moved) : fd(moved.fd), position(moved.position), buffer(moved.buffer) {
moved.fd = -1;
moved.position = 0;
}
writer(const writer &) = delete;
writer &operator=(const writer &) = delete;
// Both write() calls and fwrite() calls are too expensive to perform implicitly. The API consumer must determine
// the optimal time to flush the writer and do that explicitly for best performance.
void flush() {
assert(fd != -1);
size_t data_size = position * sizeof(uint32_t);
size_t data_written = write(fd, buffer.data(), data_size);
assert(data_size == data_written);
position = 0;
}
~writer() {
if (fd != -1) {
flush();
close(fd);
}
}
void write_magic() {
// `CXXRTL` followed by version in binary. This header will read backwards on big-endian machines, which allows
// detection of this case, both visually and programmatically.
emit_dword(((uint64_t)VERSION << 48) | HEADER_MAGIC);
}
void write_define(ident_t ident, const std::string &name, size_t part_index, size_t chunks, size_t depth) {
emit_word(PACKET_DEFINE);
emit_ident(ident);
emit_string(name);
emit_index(part_index);
emit_size(chunks);
emit_size(depth);
}
void write_sample(bool incremental, pointer_t pointer, const time &timestamp) {
uint32_t flags = (incremental ? sample_flag::INCREMENTAL : 0);
emit_word(PACKET_SAMPLE);
emit_word(flags);
emit_word(pointer);
emit_time(timestamp);
}
void write_change(ident_t ident, size_t chunks, const chunk_t *data) {
assert(ident <= MAXIMUM_IDENT);
if (chunks == 1 && *data == 0) {
emit_word(PACKET_CHANGEL | ident);
} else if (chunks == 1 && *data == 1) {
emit_word(PACKET_CHANGEH | ident);
} else {
emit_word(PACKET_CHANGE | ident);
for (size_t offset = 0; offset < chunks; offset++)
emit_word(data[offset]);
}
}
void write_change(ident_t ident, size_t chunks, const chunk_t *data, size_t index) {
assert(ident <= MAXIMUM_IDENT);
emit_word(PACKET_CHANGEI | ident);
emit_index(index);
for (size_t offset = 0; offset < chunks; offset++)
emit_word(data[offset]);
}
void write_diagnostic(const diagnostic &diagnostic) {
emit_word(PACKET_DIAGNOSTIC | diagnostic.type);
emit_string(diagnostic.message);
emit_string(diagnostic.location);
}
void write_end() {
emit_word(PACKET_END);
}
};
// Reading spools.
class reader {
FILE *f;
uint32_t absorb_word() {
// If we're at end of file, `fread` will not write to `word`, and `PACKET_END` will be returned.
uint32_t word = PACKET_END;
fread(&word, sizeof(word), 1, f);
return word;
}
uint64_t absorb_dword() {
uint32_t lo = absorb_word();
uint32_t hi = absorb_word();
return ((uint64_t)hi << 32) | lo;
}
ident_t absorb_ident() {
ident_t ident = absorb_word();
assert(ident <= MAXIMUM_IDENT);
return ident;
}
size_t absorb_size() {
return absorb_word();
}
size_t absorb_index() {
return absorb_word();
}
std::string absorb_string() {
std::string str;
do {
size_t end = str.size();
str.resize(end + 4);
uint32_t word = absorb_word();
memcpy(&str[end], &word, sizeof(uint32_t));
} while (str.back() != '\0');
// Strings have no embedded zeroes besides the terminating one(s).
return str.substr(0, str.find('\0'));
}
time absorb_time() {
value<time::bits> raw_timestamp;
raw_timestamp.data[0] = absorb_word();
raw_timestamp.data[1] = absorb_word();
raw_timestamp.data[2] = absorb_word();
return time(raw_timestamp);
}
public:
typedef uint64_t pos_t;
// Creates a reader, and transfers ownership of `fd`, which must be open for reading.
reader(spool &spool) : f(fdopen(spool.take_read(), "r")) {
assert(f != nullptr);
}
reader(reader &&moved) : f(moved.f) {
moved.f = nullptr;
}
reader(const reader &) = delete;
reader &operator=(const reader &) = delete;
~reader() {
if (f != nullptr)
fclose(f);
}
pos_t position() {
return ftell(f);
}
void rewind(pos_t position) {
fseek(f, position, SEEK_SET);
}
void read_magic() {
uint64_t magic = absorb_dword();
assert((magic & ~VERSION_MASK) == HEADER_MAGIC);
assert((magic >> 48) == VERSION);
}
bool read_define(ident_t &ident, std::string &name, size_t &part_index, size_t &chunks, size_t &depth) {
uint32_t header = absorb_word();
if (header == PACKET_END)
return false;
assert(header == PACKET_DEFINE);
ident = absorb_ident();
name = absorb_string();
part_index = absorb_index();
chunks = absorb_size();
depth = absorb_size();
return true;
}
bool read_sample(bool &incremental, pointer_t &pointer, time &timestamp) {
uint32_t header = absorb_word();
if (header == PACKET_END)
return false;
assert(header == PACKET_SAMPLE);
uint32_t flags = absorb_word();
incremental = (flags & sample_flag::INCREMENTAL);
pointer = absorb_word();
timestamp = absorb_time();
return true;
}
bool read_header(uint32_t &header) {
header = absorb_word();
return header != PACKET_END;
}
// This method must be separate from `read_change_data` because `chunks` and `depth` can only be looked up
// if `ident` is known.
bool read_change_ident(uint32_t header, ident_t &ident) {
if ((header & ~(CHANGE_MASK | MAXIMUM_IDENT)) != 0)
return false; // some other packet
ident = header & MAXIMUM_IDENT;
return true;
}
void read_change_data(uint32_t header, size_t chunks, size_t depth, chunk_t *data) {
uint32_t index = 0;
switch (header & CHANGE_MASK) {
case PACKET_CHANGEL:
*data = 0;
return;
case PACKET_CHANGEH:
*data = 1;
return;
case PACKET_CHANGE:
break;
case PACKET_CHANGEI:
index = absorb_word();
assert(index < depth);
break;
default:
assert(false && "Unrecognized change packet");
}
for (size_t offset = 0; offset < chunks; offset++)
data[chunks * index + offset] = absorb_word();
}
bool read_diagnostic(uint32_t header, diagnostic &diagnostic) {
if ((header & ~DIAGNOSTIC_MASK) != PACKET_DIAGNOSTIC)
return false; // some other packet
uint32_t type = header & DIAGNOSTIC_MASK;
assert(type == diagnostic::BREAK || type == diagnostic::PRINT ||
type == diagnostic::ASSERT || type == diagnostic::ASSUME);
diagnostic.type = (diagnostic::flavor)type;
diagnostic.message = absorb_string();
diagnostic.location = absorb_string();
return true;
}
};
// Opening spools. For certain uses of the record/replay mechanism, two distinct open files (two open files, i.e.
// two distinct file pointers, and not just file descriptors, which share the file pointer if duplicated) are used,
// for a reader and writer thread. This class manages the lifetime of the descriptors for these files. When only
// one of them is used, the other is closed harmlessly when the spool is destroyed.
private:
std::atomic<int> writefd;
std::atomic<int> readfd;
public:
spool(const std::string &filename)
: writefd(open(filename.c_str(), O_CREAT|O_BINARY|O_WRONLY|O_APPEND, 0644)),
readfd(open(filename.c_str(), O_BINARY|O_RDONLY)) {
assert(writefd.load() != -1 && readfd.load() != -1);
}
spool(spool &&moved) : writefd(moved.writefd.exchange(-1)), readfd(moved.readfd.exchange(-1)) {}
spool(const spool &) = delete;
spool &operator=(const spool &) = delete;
~spool() {
int fd;
if ((fd = writefd.exchange(-1)) != -1)
close(fd);
if ((fd = readfd.exchange(-1)) != -1)
close(fd);
}
// Atomically acquire a write file descriptor for the spool. Can be called once, and will return -1 the next time
// it is called. Thread-safe.
int take_write() {
return writefd.exchange(-1);
}
// Atomically acquire a read file descriptor for the spool. Can be called once, and will return -1 the next time
// it is called. Thread-safe.
int take_read() {
return readfd.exchange(-1);
}
};
// A CXXRTL recorder samples design state, producing complete or incremental updates, and writes them to a spool.
class recorder {
struct variable {
spool::ident_t ident; /* <= spool::MAXIMUM_IDENT */
size_t chunks;
size_t depth; /* == 1 for wires */
chunk_t *curr;
bool memory;
};
spool::writer writer;
std::vector<variable> variables;
std::vector<size_t> inputs; // values of inputs must be recorded explicitly, as their changes are not observed
std::unordered_map<const chunk_t*, spool::ident_t> ident_lookup;
bool streaming = false; // whether variable definitions have been written
spool::pointer_t pointer = 0;
time timestamp;
public:
template<typename ...Args>
recorder(Args &&...args) : writer(std::forward<Args>(args)...) {}
void start(module &module, std::string top_path = "") {
debug_items items;
module.debug_info(&items, /*scopes=*/nullptr, top_path);
start(items);
}
void start(const debug_items &items) {
assert(!streaming);
writer.write_magic();
for (auto item : items.table)
for (size_t part_index = 0; part_index < item.second.size(); part_index++) {
auto &part = item.second[part_index];
if ((part.flags & debug_item::INPUT) || (part.flags & debug_item::DRIVEN_SYNC) ||
(part.type == debug_item::MEMORY)) {
variable var;
var.ident = variables.size() + 1;
var.chunks = (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8);
var.depth = part.depth;
var.curr = part.curr;
var.memory = (part.type == debug_item::MEMORY);
ident_lookup[var.curr] = var.ident;
assert(variables.size() < spool::MAXIMUM_IDENT);
if (part.flags & debug_item::INPUT)
inputs.push_back(variables.size());
variables.push_back(var);
writer.write_define(var.ident, item.first, part_index, var.chunks, var.depth);
}
}
writer.write_end();
streaming = true;
}
const time &latest_time() {
return timestamp;
}
const time &advance_time(const time &delta) {
assert(!delta.is_negative());
timestamp += delta;
return timestamp;
}
void record_complete() {
assert(streaming);
writer.write_sample(/*incremental=*/false, pointer++, timestamp);
for (auto var : variables) {
assert(var.ident != 0);
if (!var.memory)
writer.write_change(var.ident, var.chunks, var.curr);
else
for (size_t index = 0; index < var.depth; index++)
writer.write_change(var.ident, var.chunks, &var.curr[var.chunks * index], index);
}
writer.write_end();
}
// This function is generic over ModuleT to encourage observer callbacks to be inlined into the commit function.
template<class ModuleT>
bool record_incremental(ModuleT &module) {
assert(streaming);
struct : observer {
std::unordered_map<const chunk_t*, spool::ident_t> *ident_lookup;
spool::writer *writer;
CXXRTL_ALWAYS_INLINE
void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {
writer->write_change(ident_lookup->at(base), chunks, value);
}
CXXRTL_ALWAYS_INLINE
void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {
writer->write_change(ident_lookup->at(base), chunks, value, index);
}
} record_observer;
record_observer.ident_lookup = &ident_lookup;
record_observer.writer = &writer;
writer.write_sample(/*incremental=*/true, pointer++, timestamp);
for (auto input_index : inputs) {
variable &var = variables.at(input_index);
assert(!var.memory);
writer.write_change(var.ident, var.chunks, var.curr);
}
bool changed = module.commit(record_observer);
writer.write_end();
return changed;
}
void record_diagnostic(const diagnostic &diagnostic) {
assert(streaming);
// Emit an incremental delta cycle per diagnostic to simplify the logic of the recorder. This is inefficient, but
// diagnostics should be rare enough that this inefficiency does not matter. If it turns out to be an issue, this
// code should be changed to accumulate diagnostics to a buffer that is flushed in `record_{complete,incremental}`
// and also in `advance_time` before the timestamp is changed. (Right now `advance_time` never writes to the spool.)
writer.write_sample(/*incremental=*/true, pointer++, timestamp);
writer.write_diagnostic(diagnostic);
writer.write_end();
}
void flush() {
writer.flush();
}
};
// A CXXRTL player reads samples from a spool, and changes the design state accordingly. To start reading samples,
// a spool must have been initialized: the recorder must have been started and an initial complete sample must have
// been written.
class player {
struct variable {
size_t chunks;
size_t depth; /* == 1 for wires */
chunk_t *curr;
};
spool::reader reader;
std::unordered_map<spool::ident_t, variable> variables;
bool streaming = false; // whether variable definitions have been read
bool initialized = false; // whether a sample has ever been read
spool::pointer_t pointer = 0;
time timestamp;
std::map<spool::pointer_t, spool::reader::pos_t, std::greater<spool::pointer_t>> index_by_pointer;
std::map<time, spool::reader::pos_t, std::greater<time>> index_by_timestamp;
bool peek_sample(spool::pointer_t &pointer, time &timestamp) {
bool incremental;
auto position = reader.position();
bool success = reader.read_sample(incremental, pointer, timestamp);
reader.rewind(position);
return success;
}
public:
template<typename ...Args>
player(Args &&...args) : reader(std::forward<Args>(args)...) {}
// The `top_path` must match the one given to the recorder.
void start(module &module, std::string top_path = "") {
debug_items items;
module.debug_info(&items, /*scopes=*/nullptr, top_path);
start(items);
}
void start(const debug_items &items) {
assert(!streaming);
reader.read_magic();
while (true) {
spool::ident_t ident;
std::string name;
size_t part_index;
size_t chunks;
size_t depth;
if (!reader.read_define(ident, name, part_index, chunks, depth))
break;
assert(variables.count(ident) == 0);
assert(items.count(name) != 0);
assert(part_index < items.count(name));
const debug_item &part = items.at(name).at(part_index);
assert(chunks == (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8));
assert(depth == part.depth);
variable &var = variables[ident];
var.chunks = chunks;
var.depth = depth;
var.curr = part.curr;
}
assert(variables.size() > 0);
streaming = true;
// Establish the initial state of the design.
std::vector<diagnostic> diagnostics;
initialized = replay(&diagnostics);
assert(initialized && diagnostics.empty());
}
// Returns the pointer of the current sample.
spool::pointer_t current_pointer() {
assert(initialized);
return pointer;
}
// Returns the time of the current sample.
const time &current_time() {
assert(initialized);
return timestamp;
}
// Returns `true` if there is a next sample to read, and sets `pointer` to its pointer if there is.
bool get_next_pointer(spool::pointer_t &pointer) {
assert(streaming);
time timestamp;
return peek_sample(pointer, timestamp);
}
// Returns `true` if there is a next sample to read, and sets `timestamp` to its time if there is.
bool get_next_time(time &timestamp) {
assert(streaming);
uint32_t pointer;
return peek_sample(pointer, timestamp);
}
// If this function returns `true`, then `current_pointer() == at_pointer`, and the module contains values that
// correspond to this pointer in the replay log. To obtain a valid pointer, call `current_pointer()`; while pointers
// are monotonically increasing for each consecutive sample, using arithmetic operations to create a new pointer is
// not allowed. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in this sample.
bool rewind_to(spool::pointer_t at_pointer, std::vector<diagnostic> *diagnostics) {
assert(initialized);
// The pointers in the replay log start from one that is greater than `at_pointer`. In this case the pointer will
// never be reached.
assert(index_by_pointer.size() > 0);
if (at_pointer < index_by_pointer.rbegin()->first)
return false;
// Find the last complete sample whose pointer is less than or equal to `at_pointer`. Note that the comparison
// function used here is `std::greater`, inverting the direction of `lower_bound`.
auto position_it = index_by_pointer.lower_bound(at_pointer);
assert(position_it != index_by_pointer.end());
reader.rewind(position_it->second);
// Replay samples until eventually arriving to `at_pointer` or encountering end of file.
while(replay(diagnostics)) {
if (pointer == at_pointer)
return true;
if (diagnostics)
diagnostics->clear();
}
return false;
}
// If this function returns `true`, then `current_time() <= at_or_before_timestamp`, and the module contains values
// that correspond to `current_time()` in the replay log. If `current_time() == at_or_before_timestamp` and there
// are several consecutive samples with the same time, the module contains values that correspond to the first of
// these samples. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in this sample.
bool rewind_to_or_before(const time &at_or_before_timestamp, std::vector<diagnostic> *diagnostics) {
assert(initialized);
// The timestamps in the replay log start from one that is greater than `at_or_before_timestamp`. In this case
// the timestamp will never be reached. Otherwise, this function will always succeed.
assert(index_by_timestamp.size() > 0);
if (at_or_before_timestamp < index_by_timestamp.rbegin()->first)
return false;
// Find the last complete sample whose timestamp is less than or equal to `at_or_before_timestamp`. Note that
// the comparison function used here is `std::greater`, inverting the direction of `lower_bound`.
auto position_it = index_by_timestamp.lower_bound(at_or_before_timestamp);
assert(position_it != index_by_timestamp.end());
reader.rewind(position_it->second);
// Replay samples until eventually arriving to or past `at_or_before_timestamp` or encountering end of file.
while (replay(diagnostics)) {
if (timestamp == at_or_before_timestamp)
break;
time next_timestamp;
if (!get_next_time(next_timestamp))
break;
if (next_timestamp > at_or_before_timestamp)
break;
if (diagnostics)
diagnostics->clear();
}
return true;
}
// If this function returns `true`, then `current_pointer()` and `current_time()` are updated for the next sample
// and the module now contains values that correspond to that sample. If it returns `false`, there was no next sample
// to read. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in the next sample.
bool replay(std::vector<diagnostic> *diagnostics) {
assert(streaming);
bool incremental;
auto position = reader.position();
if (!reader.read_sample(incremental, pointer, timestamp))
return false;
// The very first sample that is read must be a complete sample. This is required for the rewind functions to work.
assert(initialized || !incremental);
// It is possible (though not very useful) to have several complete samples with the same timestamp in a row.
// Ensure that we associate the timestamp with the position of the first such complete sample. (This condition
// works because the player never jumps over a sample.)
if (!incremental && !index_by_pointer.count(pointer)) {
assert(!index_by_timestamp.count(timestamp));
index_by_pointer[pointer] = position;
index_by_timestamp[timestamp] = position;
}
uint32_t header;
while (reader.read_header(header)) {
spool::ident_t ident;
diagnostic diag;
if (reader.read_change_ident(header, ident)) {
variable &var = variables.at(ident);
reader.read_change_data(header, var.chunks, var.depth, var.curr);
} else if (reader.read_diagnostic(header, diag)) {
if (diagnostics)
diagnostics->push_back(diag);
} else assert(false && "Unrecognized packet header");
}
return true;
}
};
}
#endif

View file

@ -0,0 +1,231 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2023 Catherine <whitequark@whitequark.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* 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 CXXRTL_TIME_H
#define CXXRTL_TIME_H
#include <cinttypes>
#include <string>
#include <cxxrtl/cxxrtl.h>
namespace cxxrtl {
// A timestamp or a difference in time, stored as a 96-bit number of femtoseconds (10e-15 s). The range and resolution
// of this format can represent any VCD timestamp within approx. ±1255321.2 years, without the need for a timescale.
class time {
public:
static constexpr size_t bits = 96; // 3 chunks
private:
static constexpr size_t resolution_digits = 15;
static_assert(sizeof(chunk_t) == 4, "a chunk is expected to be 32-bit");
static constexpr value<bits> resolution = value<bits> {
chunk_t(1000000000000000ull & 0xffffffffull), chunk_t(1000000000000000ull >> 32), 0u
};
// Signed number of femtoseconds from the beginning of time.
value<bits> raw;
public:
constexpr time() {}
explicit constexpr time(const value<bits> &raw) : raw(raw) {}
explicit operator const value<bits> &() const { return raw; }
static constexpr time maximum() {
return time(value<bits> { 0xffffffffu, 0xffffffffu, 0x7fffffffu });
}
time(int64_t secs, int64_t femtos) {
value<64> secs_val;
secs_val.set(secs);
value<64> femtos_val;
femtos_val.set(femtos);
raw = secs_val.sext<bits>().mul<bits>(resolution).add(femtos_val.sext<bits>());
}
bool is_zero() const {
return raw.is_zero();
}
// Extracts the sign of the value.
bool is_negative() const {
return raw.is_neg();
}
// Extracts the number of whole seconds. Negative if the value is negative.
int64_t secs() const {
return raw.sdivmod(resolution).first.trunc<64>().get<int64_t>();
}
// Extracts the number of femtoseconds in the fractional second. Negative if the value is negative.
int64_t femtos() const {
return raw.sdivmod(resolution).second.trunc<64>().get<int64_t>();
}
bool operator==(const time &other) const {
return raw == other.raw;
}
bool operator!=(const time &other) const {
return raw != other.raw;
}
bool operator>(const time &other) const {
return other.raw.scmp(raw);
}
bool operator>=(const time &other) const {
return !raw.scmp(other.raw);
}
bool operator<(const time &other) const {
return raw.scmp(other.raw);
}
bool operator<=(const time &other) const {
return !other.raw.scmp(raw);
}
time operator+(const time &other) const {
return time(raw.add(other.raw));
}
time &operator+=(const time &other) {
*this = *this + other;
return *this;
}
time operator-() const {
return time(raw.neg());
}
time operator-(const time &other) const {
return *this + (-other);
}
time &operator-=(const time &other) {
*this = *this - other;
return *this;
}
operator std::string() const {
char buf[48]; // x=2**95; len(f"-{x/1_000_000_000_000_000}.{x^1_000_000_000_000_000}") == 48
int64_t secs = this->secs();
int64_t femtos = this->femtos();
snprintf(buf, sizeof(buf), "%s%" PRIi64 ".%015" PRIi64,
is_negative() ? "-" : "", secs >= 0 ? secs : -secs, femtos >= 0 ? femtos : -femtos);
return buf;
}
#if __cplusplus >= 201603L
[[nodiscard("ignoring parse errors")]]
#endif
bool parse(const std::string &str) {
enum {
parse_sign_opt,
parse_integral,
parse_fractional,
} state = parse_sign_opt;
bool negative = false;
int64_t integral = 0;
int64_t fractional = 0;
size_t frac_digits = 0;
for (auto chr : str) {
switch (state) {
case parse_sign_opt:
state = parse_integral;
if (chr == '+' || chr == '-') {
negative = (chr == '-');
break;
}
/* fallthrough */
case parse_integral:
if (chr >= '0' && chr <= '9') {
integral *= 10;
integral += chr - '0';
} else if (chr == '.') {
state = parse_fractional;
} else {
return false;
}
break;
case parse_fractional:
if (chr >= '0' && chr <= '9' && frac_digits < resolution_digits) {
fractional *= 10;
fractional += chr - '0';
frac_digits++;
} else {
return false;
}
break;
}
}
if (frac_digits == 0)
return false;
while (frac_digits++ < resolution_digits)
fractional *= 10;
*this = negative ? -time { integral, fractional} : time { integral, fractional };
return true;
}
};
// Out-of-line definition required until C++17.
constexpr value<time::bits> time::resolution;
std::ostream &operator<<(std::ostream &os, const time &val) {
os << (std::string)val;
return os;
}
// These literals are (confusingly) compatible with the ones from `std::chrono`: the `std::chrono` literals do not
// have an underscore (e.g. 1ms) and the `cxxrtl::time` literals do (e.g. 1_ms). This syntactic difference is
// a requirement of the C++ standard. Despite being compatible the literals should not be mixed in the same namespace.
namespace time_literals {
time operator""_s(unsigned long long seconds) {
return time { (int64_t)seconds, 0 };
}
time operator""_ms(unsigned long long milliseconds) {
return time { 0, (int64_t)milliseconds * 1000000000000 };
}
time operator""_us(unsigned long long microseconds) {
return time { 0, (int64_t)microseconds * 1000000000 };
}
time operator""_ns(unsigned long long nanoseconds) {
return time { 0, (int64_t)nanoseconds * 1000000 };
}
time operator""_ps(unsigned long long picoseconds) {
return time { 0, (int64_t)picoseconds * 1000 };
}
time operator""_fs(unsigned long long femtoseconds) {
return time { 0, (int64_t)femtoseconds };
}
};
};
#endif

View file

@ -19,7 +19,7 @@
#ifndef CXXRTL_VCD_H
#define CXXRTL_VCD_H
#include <backends/cxxrtl/cxxrtl.h>
#include <cxxrtl/cxxrtl.h>
namespace cxxrtl {
@ -50,9 +50,13 @@ class vcd_writer {
void emit_scope(const std::vector<std::string> &scope) {
assert(!streaming);
while (current_scope.size() > scope.size() ||
(current_scope.size() > 0 &&
current_scope[current_scope.size() - 1] != scope[current_scope.size() - 1])) {
size_t same_scope_count = 0;
while ((same_scope_count < current_scope.size()) &&
(same_scope_count < scope.size()) &&
(current_scope[same_scope_count] == scope[same_scope_count])) {
same_scope_count++;
}
while (current_scope.size() > same_scope_count) {
buffer += "$upscope $end\n";
current_scope.pop_back();
}
@ -123,6 +127,8 @@ class vcd_writer {
bool bit_curr = var.curr[bit / (8 * sizeof(chunk_t))] & (1 << (bit % (8 * sizeof(chunk_t))));
buffer += (bit_curr ? '1' : '0');
}
if (var.width == 0)
buffer += '0';
buffer += ' ';
emit_ident(var.ident);
buffer += '\n';

View file

@ -30,9 +30,9 @@
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
#define EDIF_DEF(_id) edif_names(RTLIL::unescape_id(_id), true).c_str()
#define EDIF_DEFR(_id, _ren, _bl, _br) edif_names(RTLIL::unescape_id(_id), true, _ren, _bl, _br).c_str()
#define EDIF_REF(_id) edif_names(RTLIL::unescape_id(_id), false).c_str()
#define EDIF_DEF(_id) edif_names(RTLIL::unescape_id(_id), true)
#define EDIF_DEFR(_id, _ren, _bl, _br) edif_names(RTLIL::unescape_id(_id), true, _ren, _bl, _br)
#define EDIF_REF(_id) edif_names(RTLIL::unescape_id(_id), false)
struct EdifNames
{
@ -48,8 +48,8 @@ struct EdifNames
if (define) {
std::string new_id = operator()(id, false);
if (port_rename)
return stringf("(rename %s \"%s%c%d:%d%c\")", new_id.c_str(), id.c_str(), delim_left, range_left, range_right, delim_right);
return new_id != id ? stringf("(rename %s \"%s\")", new_id.c_str(), id.c_str()) : id;
return stringf("(rename %s \"%s%c%d:%d%c\")", new_id, id, delim_left, range_left, range_right, delim_right);
return new_id != id ? stringf("(rename %s \"%s\")", new_id, id) : id;
}
if (name_map.count(id) > 0)
@ -107,8 +107,8 @@ struct EdifBackend : public Backend {
log(" constant drivers first)\n");
log("\n");
log(" -gndvccy\n");
log(" create \"GND\" and \"VCC\" cells with \"Y\" outputs. (the default is \"G\"\n");
log(" for \"GND\" and \"P\" for \"VCC\".)\n");
log(" create \"GND\" and \"VCC\" cells with \"Y\" outputs. (the default is\n");
log(" \"G\" for \"GND\" and \"P\" for \"VCC\".)\n");
log("\n");
log(" -attrprop\n");
log(" create EDIF properties for cell attributes\n");
@ -120,6 +120,9 @@ struct EdifBackend : public Backend {
log(" sets the delimiting character for module port rename clauses to\n");
log(" parentheses, square brackets, or angle brackets.\n");
log("\n");
log(" -lsbidx\n");
log(" use index 0 for the LSB bit of a net or port instead of MSB.\n");
log("\n");
log("Unfortunately there are different \"flavors\" of the EDIF file format. This\n");
log("command generates EDIF files for the Xilinx place&route tools. It might be\n");
log("necessary to make small modifications to this command when a different tool\n");
@ -132,6 +135,7 @@ struct EdifBackend : public Backend {
std::string top_module_name;
bool port_rename = false;
bool attr_properties = false;
bool lsbidx = false;
std::map<RTLIL::IdString, std::map<RTLIL::IdString, int>> lib_cell_ports;
bool nogndvcc = false, gndvccy = false, keepmode = false;
CellTypes ct(design);
@ -173,6 +177,10 @@ struct EdifBackend : public Backend {
}
continue;
}
if (args[argidx] == "-lsbidx") {
lsbidx = true;
continue;
}
break;
}
extra_args(f, filename, args, argidx);
@ -184,6 +192,14 @@ struct EdifBackend : public Backend {
for (auto module : design->modules())
{
lib_cell_ports[module->name];
for (auto port : module->ports)
{
Wire *wire = module->wire(port);
lib_cell_ports[module->name][port] = std::max(lib_cell_ports[module->name][port], GetSize(wire));
}
if (module->get_blackbox_attribute())
continue;
@ -197,10 +213,13 @@ struct EdifBackend : public Backend {
for (auto cell : module->cells())
{
if (cell->type == ID($scopeinfo))
continue;
if (design->module(cell->type) == nullptr || design->module(cell->type)->get_blackbox_attribute()) {
lib_cell_ports[cell->type];
for (auto p : cell->connections())
lib_cell_ports[cell->type][p.first] = GetSize(p.second);
lib_cell_ports[cell->type][p.first] = std::max(lib_cell_ports[cell->type][p.first], GetSize(p.second));
}
}
}
@ -212,7 +231,8 @@ struct EdifBackend : public Backend {
*f << stringf(" (edifVersion 2 0 0)\n");
*f << stringf(" (edifLevel 0)\n");
*f << stringf(" (keywordMap (keywordLevel 0))\n");
*f << stringf(" (comment \"Generated by %s\")\n", yosys_version_str);
*f << stringf(" (comment \"Generated by %s\")\n", yosys_maybe_version());
*f << stringf(" (external LIB\n");
*f << stringf(" (edifLevel 0)\n");
@ -314,21 +334,21 @@ struct EdifBackend : public Backend {
auto add_prop = [&](IdString name, Const val) {
if ((val.flags & RTLIL::CONST_FLAG_STRING) != 0)
*f << stringf("\n (property %s (string \"%s\"))", EDIF_DEF(name), val.decode_string().c_str());
else if (val.bits.size() <= 32 && RTLIL::SigSpec(val).is_fully_def())
*f << stringf("\n (property %s (string \"%s\"))", EDIF_DEF(name), val.decode_string());
else if (val.size() <= 32 && RTLIL::SigSpec(val).is_fully_def())
*f << stringf("\n (property %s (integer %u))", EDIF_DEF(name), val.as_int());
else {
std::string hex_string = "";
for (size_t i = 0; i < val.bits.size(); i += 4) {
for (auto i = 0; i < val.size(); i += 4) {
int digit_value = 0;
if (i+0 < val.bits.size() && val.bits.at(i+0) == RTLIL::State::S1) digit_value |= 1;
if (i+1 < val.bits.size() && val.bits.at(i+1) == RTLIL::State::S1) digit_value |= 2;
if (i+2 < val.bits.size() && val.bits.at(i+2) == RTLIL::State::S1) digit_value |= 4;
if (i+3 < val.bits.size() && val.bits.at(i+3) == RTLIL::State::S1) digit_value |= 8;
if (i+0 < val.size() && val.at(i+0) == RTLIL::State::S1) digit_value |= 1;
if (i+1 < val.size() && val.at(i+1) == RTLIL::State::S1) digit_value |= 2;
if (i+2 < val.size() && val.at(i+2) == RTLIL::State::S1) digit_value |= 4;
if (i+3 < val.size() && val.at(i+3) == RTLIL::State::S1) digit_value |= 8;
char digit_str[2] = { "0123456789abcdef"[digit_value], 0 };
hex_string = std::string(digit_str) + hex_string;
}
*f << stringf("\n (property %s (string \"%d'h%s\"))", EDIF_DEF(name), GetSize(val.bits), hex_string.c_str());
*f << stringf("\n (property %s (string \"%d'h%s\"))", EDIF_DEF(name), GetSize(val), hex_string);
}
};
for (auto module : sorted_modules)
@ -437,7 +457,7 @@ struct EdifBackend : public Backend {
*f << ")\n";
for (int i = 0; i < wire->width; i++) {
RTLIL::SigSpec sig = sigmap(RTLIL::SigSpec(wire, i));
net_join_db[sig].insert(make_pair(stringf("(portRef (member %s %d))", EDIF_REF(wire->name), GetSize(wire)-i-1), wire->port_input));
net_join_db[sig].insert(make_pair(stringf("(portRef (member %s %d))", EDIF_REF(wire->name), lsbidx ? i : GetSize(wire)-i-1), wire->port_input));
}
}
}
@ -468,13 +488,13 @@ struct EdifBackend : public Backend {
log_warning("Bit %d of cell port %s.%s.%s driven by %s will be left unconnected in EDIF output.\n",
i, log_id(module), log_id(cell), log_id(p.first), log_signal(sig[i]));
else {
int member_idx = GetSize(sig)-i-1;
int member_idx = lsbidx ? i : GetSize(sig)-i-1;
auto m = design->module(cell->type);
int width = sig.size();
if (m) {
auto w = m->wire(p.first);
if (w) {
member_idx = GetSize(w)-i-1;
member_idx = lsbidx ? i : GetSize(w)-i-1;
width = GetSize(w);
}
}
@ -493,13 +513,13 @@ struct EdifBackend : public Backend {
if (sig.wire == NULL && sig != RTLIL::State::S0 && sig != RTLIL::State::S1) {
if (sig == RTLIL::State::Sx) {
for (auto &ref : it.second)
log_warning("Exporting x-bit on %s as zero bit.\n", ref.first.c_str());
log_warning("Exporting x-bit on %s as zero bit.\n", ref.first);
sig = RTLIL::State::S0;
} else if (sig == RTLIL::State::Sz) {
continue;
} else {
for (auto &ref : it.second)
log_error("Don't know how to handle %s on %s.\n", log_signal(sig), ref.first.c_str());
log_error("Don't know how to handle %s on %s.\n", log_signal(sig), ref.first);
log_abort();
}
}
@ -516,7 +536,7 @@ struct EdifBackend : public Backend {
}
*f << stringf(" (net %s (joined\n", EDIF_DEF(netname));
for (auto &ref : it.second)
*f << stringf(" %s\n", ref.first.c_str());
*f << stringf(" %s\n", ref.first);
if (sig.wire == NULL) {
if (nogndvcc)
log_error("Design contains constant nodes (map with \"hilomap\" first).\n");
@ -557,7 +577,7 @@ struct EdifBackend : public Backend {
auto &refs = net_join_db.at(mapped_sig);
for (auto &ref : refs)
if (ref.second)
*f << stringf(" %s\n", ref.first.c_str());
*f << stringf(" %s\n", ref.first);
*f << stringf(" )");
if (attr_properties && raw_sig.wire != NULL)

View file

@ -21,7 +21,6 @@
#include "kernel/register.h"
#include "kernel/sigtools.h"
#include "kernel/celltypes.h"
#include "kernel/cellaigs.h"
#include "kernel/log.h"
#include "kernel/mem.h"
#include <algorithm>
@ -150,7 +149,7 @@ std::string dump_const(const RTLIL::Const &data)
// Numeric (non-real) parameter.
else
{
int width = data.bits.size();
int width = data.size();
// If a standard 32-bit int, then emit standard int value like "56" or
// "-56". Firrtl supports negative-valued int literals.
@ -164,7 +163,7 @@ std::string dump_const(const RTLIL::Const &data)
for (int i = 0; i < width; i++)
{
switch (data.bits[i])
switch (data[i])
{
case State::S0: break;
case State::S1: int_val |= (1 << i); break;
@ -206,7 +205,7 @@ std::string dump_const(const RTLIL::Const &data)
for (int i = width - 1; i >= 0; i--)
{
log_assert(i < width);
switch (data.bits[i])
switch (data[i])
{
case State::S0: res_str += "0"; break;
case State::S1: res_str += "1"; break;
@ -254,7 +253,7 @@ void emit_extmodule(RTLIL::Cell *cell, RTLIL::Module *mod_instance, std::ostream
const std::string extmoduleFileinfo = getFileinfo(cell);
// Emit extmodule header.
f << stringf(" extmodule %s: %s\n", exported_name.c_str(), extmoduleFileinfo.c_str());
f << stringf(" extmodule %s: %s\n", exported_name, extmoduleFileinfo);
// Emit extmodule ports.
for (auto wire : mod_instance->wires())
@ -281,7 +280,7 @@ void emit_extmodule(RTLIL::Cell *cell, RTLIL::Module *mod_instance, std::ostream
// Emit extmodule "defname" field. This is the name of the verilog blackbox
// that is used when verilog is emitted, so we use the name of mod_instance
// here.
f << stringf("%sdefname = %s\n", indent.c_str(), blackbox_name.c_str());
f << stringf("%sdefname = %s\n", indent, blackbox_name);
// Emit extmodule generic parameters.
for (const auto &p : cell->parameters)
@ -302,7 +301,7 @@ void emit_extmodule(RTLIL::Cell *cell, RTLIL::Module *mod_instance, std::ostream
param_name.end()
);
f << stringf("%sparameter %s = %s\n", indent.c_str(), param_name.c_str(), param_value.c_str());
f << stringf("%sparameter %s = %s\n", indent, param_name, param_value);
}
f << "\n";
@ -346,6 +345,12 @@ void emit_elaborated_extmodules(RTLIL::Design *design, std::ostream &f)
{
// Find the module corresponding to this instance.
auto modInstance = design->module(cell->type);
// Ensure that we actually have a module instance
if (modInstance == nullptr) {
log_error("Unknown cell type %s\n", cell->type);
return;
}
bool modIsBlackbox = modInstance->get_blackbox_attribute();
if (modIsBlackbox)
@ -412,7 +417,7 @@ struct FirrtlWorker
else
{
string wire_id = make_id(chunk.wire->name);
new_expr = stringf("bits(%s, %d, %d)", wire_id.c_str(), chunk.offset + chunk.width - 1, chunk.offset);
new_expr = stringf("bits(%s, %d, %d)", wire_id, chunk.offset + chunk.width - 1, chunk.offset);
}
if (expr.empty())
@ -460,7 +465,7 @@ struct FirrtlWorker
// If there is no instance for this, just return.
if (instModule == NULL)
{
log_warning("No instance for %s.%s\n", cell_type.c_str(), cell_name.c_str());
log_warning("No instance for %s.%s\n", cell_type, cell_name);
return;
}
@ -472,7 +477,7 @@ struct FirrtlWorker
instanceOf;
std::string cellFileinfo = getFileinfo(cell);
wire_exprs.push_back(stringf("%s" "inst %s%s of %s %s", indent.c_str(), cell_name.c_str(), cell_name_comment.c_str(), instanceName.c_str(), cellFileinfo.c_str()));
wire_exprs.push_back(stringf("%s" "inst %s%s of %s %s", indent, cell_name, cell_name_comment, instanceName, cellFileinfo));
for (auto it = cell->connections().begin(); it != cell->connections().end(); ++it) {
if (it->second.size() > 0) {
@ -485,7 +490,7 @@ struct FirrtlWorker
const SigSpec *sinkSig = nullptr;
switch (dir) {
case FD_INOUT:
log_warning("Instance port connection %s.%s is INOUT; treating as OUT\n", cell_type.c_str(), log_signal(it->second));
log_warning("Instance port connection %s.%s is INOUT; treating as OUT\n", cell_type, log_signal(it->second));
YS_FALLTHROUGH
case FD_OUT:
sourceExpr = firstName;
@ -493,27 +498,27 @@ struct FirrtlWorker
sinkSig = &secondSig;
break;
case FD_NODIRECTION:
log_warning("Instance port connection %s.%s is NODIRECTION; treating as IN\n", cell_type.c_str(), log_signal(it->second));
log_warning("Instance port connection %s.%s is NODIRECTION; treating as IN\n", cell_type, log_signal(it->second));
YS_FALLTHROUGH
case FD_IN:
sourceExpr = secondExpr;
sinkExpr = firstName;
break;
default:
log_error("Instance port %s.%s unrecognized connection direction 0x%x !\n", cell_type.c_str(), log_signal(it->second), dir);
log_error("Instance port %s.%s unrecognized connection direction 0x%x !\n", cell_type, log_signal(it->second), dir);
break;
}
// Check for subfield assignment.
std::string bitsString = "bits(";
if (sinkExpr.compare(0, bitsString.length(), bitsString) == 0) {
if (sinkSig == nullptr)
log_error("Unknown subfield %s.%s\n", cell_type.c_str(), sinkExpr.c_str());
log_error("Unknown subfield %s.%s\n", cell_type, sinkExpr);
// Don't generate the assignment here.
// Add the source and sink to the "reverse_wire_map" and we'll output the assignment
// as part of the coalesced subfield assignments for this wire.
register_reverse_wire_map(sourceExpr, *sinkSig);
} else {
wire_exprs.push_back(stringf("\n%s%s <= %s %s", indent.c_str(), sinkExpr.c_str(), sourceExpr.c_str(), cellFileinfo.c_str()));
wire_exprs.push_back(stringf("\n%s%s <= %s %s", indent, sinkExpr, sourceExpr, cellFileinfo));
}
}
}
@ -530,7 +535,7 @@ struct FirrtlWorker
int max_shift_width_bits = FIRRTL_MAX_DSH_WIDTH_ERROR - 1;
string max_shift_string = stringf("UInt<%d>(%d)", max_shift_width_bits, (1<<max_shift_width_bits) - 1);
// Deal with the difference in semantics between FIRRTL and verilog
result = stringf("mux(gt(%s, %s), %s, bits(%s, %d, 0))", b_expr.c_str(), max_shift_string.c_str(), max_shift_string.c_str(), b_expr.c_str(), max_shift_width_bits - 1);
result = stringf("mux(gt(%s, %s), %s, bits(%s, %d, 0))", b_expr, max_shift_string, max_shift_string, b_expr, max_shift_width_bits - 1);
}
return result;
}
@ -538,7 +543,7 @@ struct FirrtlWorker
void emit_module()
{
std::string moduleFileinfo = getFileinfo(module);
f << stringf(" module %s: %s\n", make_id(module->name), moduleFileinfo.c_str());
f << stringf(" module %s: %s\n", make_id(module->name), moduleFileinfo);
vector<string> port_decls, wire_decls, mem_exprs, cell_exprs, wire_exprs;
std::vector<Mem> memories = Mem::get_all_memories(module);
@ -560,12 +565,12 @@ struct FirrtlWorker
{
if (wire->port_input && wire->port_output)
log_error("Module port %s.%s is inout!\n", log_id(module), log_id(wire));
port_decls.push_back(stringf("%s%s %s: UInt<%d> %s\n", indent.c_str(), wire->port_input ? "input" : "output",
port_decls.push_back(stringf("%s%s %s: UInt<%d> %s\n", indent, wire->port_input ? "input" : "output",
wireName, wire->width, wireFileinfo.c_str()));
}
else
{
wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), wireName, wire->width, wireFileinfo.c_str()));
wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent, wireName, wire->width, wireFileinfo));
}
}
@ -597,7 +602,7 @@ struct FirrtlWorker
if (cell->type.in(ID($not), ID($logic_not), ID($_NOT_), ID($neg), ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_bool), ID($reduce_xnor)))
{
string a_expr = make_expr(cell->getPort(ID::A));
wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), y_width, cellFileinfo.c_str()));
wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent, y_id, y_width, cellFileinfo));
if (a_signed) {
a_expr = "asSInt(" + a_expr + ")";
@ -605,7 +610,7 @@ struct FirrtlWorker
// Don't use the results of logical operations (a single bit) to control padding
if (!(cell->type.in(ID($eq), ID($eqx), ID($gt), ID($ge), ID($lt), ID($le), ID($ne), ID($nex), ID($reduce_bool), ID($logic_not)) && y_width == 1) ) {
a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width);
a_expr = stringf("pad(%s, %d)", a_expr, y_width);
}
// Assume the FIRRTL width is a single bit.
@ -617,27 +622,27 @@ struct FirrtlWorker
firrtl_width = a_width;
} else if (cell->type == ID($logic_not)) {
primop = "eq";
a_expr = stringf("%s, UInt(0)", a_expr.c_str());
a_expr = stringf("%s, UInt(0)", a_expr);
}
else if (cell->type == ID($reduce_and)) primop = "andr";
else if (cell->type == ID($reduce_or)) primop = "orr";
else if (cell->type == ID($reduce_xor)) primop = "xorr";
else if (cell->type == ID($reduce_xnor)) {
primop = "not";
a_expr = stringf("xorr(%s)", a_expr.c_str());
a_expr = stringf("xorr(%s)", a_expr);
}
else if (cell->type == ID($reduce_bool)) {
primop = "neq";
// Use the sign of the a_expr and its width as the type (UInt/SInt) and width of the comparand.
a_expr = stringf("%s, %cInt<%d>(0)", a_expr.c_str(), a_signed ? 'S' : 'U', a_width);
a_expr = stringf("%s, %cInt<%d>(0)", a_expr, a_signed ? 'S' : 'U', a_width);
}
string expr = stringf("%s(%s)", primop.c_str(), a_expr.c_str());
string expr = stringf("%s(%s)", primop, a_expr);
if ((firrtl_is_signed && !always_uint))
expr = stringf("asUInt(%s)", expr.c_str());
expr = stringf("asUInt(%s)", expr);
cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent, y_id, expr, cellFileinfo));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
@ -649,13 +654,13 @@ struct FirrtlWorker
string a_expr = make_expr(cell->getPort(ID::A));
string b_expr = make_expr(cell->getPort(ID::B));
std::string cellFileinfo = getFileinfo(cell);
wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), y_width, cellFileinfo.c_str()));
wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent, y_id, y_width, cellFileinfo));
if (a_signed) {
a_expr = "asSInt(" + a_expr + ")";
// Expand the "A" operand to the result width
if (a_width < y_width) {
a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width);
a_expr = stringf("pad(%s, %d)", a_expr, y_width);
a_width = y_width;
}
}
@ -665,7 +670,7 @@ struct FirrtlWorker
b_expr = "asSInt(" + b_expr + ")";
// Expand the "B" operand to the result width
if (b_width < y_width) {
b_expr = stringf("pad(%s, %d)", b_expr.c_str(), y_width);
b_expr = stringf("pad(%s, %d)", b_expr, y_width);
b_width = y_width;
}
}
@ -675,11 +680,11 @@ struct FirrtlWorker
if (cell->type.in(ID($add), ID($sub), ID($mul), ID($div), ID($mod), ID($xor), ID($_XOR_), ID($xnor), ID($and), ID($_AND_), ID($or), ID($_OR_)))
{
if (a_width < y_width) {
a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width);
a_expr = stringf("pad(%s, %d)", a_expr, y_width);
a_width = y_width;
}
if (b_width < y_width) {
b_expr = stringf("pad(%s, %d)", b_expr.c_str(), y_width);
b_expr = stringf("pad(%s, %d)", b_expr, y_width);
b_width = y_width;
}
}
@ -851,23 +856,23 @@ struct FirrtlWorker
string expr;
// Deal with $xnor == ~^ (not xor)
if (primop == "xnor") {
expr = stringf("not(xor(%s, %s))", a_expr.c_str(), b_expr.c_str());
expr = stringf("not(xor(%s, %s))", a_expr, b_expr);
} else {
expr = stringf("%s(%s, %s)", primop.c_str(), a_expr.c_str(), b_expr.c_str());
expr = stringf("%s(%s, %s)", primop, a_expr, b_expr);
}
// Deal with FIRRTL's "shift widens" semantics, or the need to widen the FIRRTL result.
// If the operation is signed, the FIRRTL width will be 1 one bit larger.
if (extract_y_bits) {
expr = stringf("bits(%s, %d, 0)", expr.c_str(), y_width - 1);
expr = stringf("bits(%s, %d, 0)", expr, y_width - 1);
} else if (firrtl_is_signed && (firrtl_width + 1) < y_width) {
expr = stringf("pad(%s, %d)", expr.c_str(), y_width);
expr = stringf("pad(%s, %d)", expr, y_width);
}
if ((firrtl_is_signed && !always_uint))
expr = stringf("asUInt(%s)", expr.c_str());
expr = stringf("asUInt(%s)", expr);
cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent, y_id, expr, cellFileinfo));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
@ -880,11 +885,11 @@ struct FirrtlWorker
string a_expr = make_expr(cell->getPort(ID::A));
string b_expr = make_expr(cell->getPort(ID::B));
string s_expr = make_expr(cell->getPort(ID::S));
wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), width, cellFileinfo.c_str()));
wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent, y_id, width, cellFileinfo));
string expr = stringf("mux(%s, %s, %s)", s_expr.c_str(), b_expr.c_str(), a_expr.c_str());
string expr = stringf("mux(%s, %s, %s)", s_expr, b_expr, a_expr);
cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent, y_id, expr, cellFileinfo));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
@ -906,9 +911,9 @@ struct FirrtlWorker
string expr = make_expr(cell->getPort(ID::D));
string clk_expr = "asClock(" + make_expr(cell->getPort(ID::CLK)) + ")";
wire_decls.push_back(stringf("%sreg %s: UInt<%d>, %s %s\n", indent.c_str(), y_id.c_str(), width, clk_expr.c_str(), cellFileinfo.c_str()));
wire_decls.push_back(stringf("%sreg %s: UInt<%d>, %s %s\n", indent, y_id, width, clk_expr, cellFileinfo));
cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent, y_id, expr, cellFileinfo));
register_reverse_wire_map(y_id, cell->getPort(ID::Q));
continue;
@ -921,7 +926,7 @@ struct FirrtlWorker
string a_expr = make_expr(cell->getPort(ID::A));
// Get the initial bit selector
string b_expr = make_expr(cell->getPort(ID::B));
wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width));
wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent, y_id, y_width));
if (cell->getParam(ID::B_SIGNED).as_bool()) {
// Use validif to constrain the selection (test the sign bit)
@ -929,9 +934,9 @@ struct FirrtlWorker
int b_sign = cell->parameters.at(ID::B_WIDTH).as_int() - 1;
b_expr = stringf("validif(not(bits(%s, %d, %d)), %s)", b_string, b_sign, b_sign, b_string);
}
string expr = stringf("dshr(%s, %s)", a_expr.c_str(), b_expr.c_str());
string expr = stringf("dshr(%s, %s)", a_expr, b_expr);
cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str()));
cell_exprs.push_back(stringf("%s%s <= %s\n", indent, y_id, expr));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
}
@ -943,21 +948,21 @@ struct FirrtlWorker
string b_expr = make_expr(cell->getPort(ID::B));
auto b_string = b_expr.c_str();
string expr;
wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width));
wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent, y_id, y_width));
if (cell->getParam(ID::B_SIGNED).as_bool()) {
// We generate a left or right shift based on the sign of b.
std::string dshl = stringf("bits(dshl(%s, %s), 0, %d)", a_expr.c_str(), gen_dshl(b_expr, b_width).c_str(), y_width);
std::string dshr = stringf("dshr(%s, %s)", a_expr.c_str(), b_string);
std::string dshl = stringf("bits(dshl(%s, %s), 0, %d)", a_expr, gen_dshl(b_expr, b_width), y_width);
std::string dshr = stringf("dshr(%s, %s)", a_expr, b_string);
expr = stringf("mux(%s < 0, %s, %s)",
b_string,
dshl.c_str(),
dshr.c_str()
);
} else {
expr = stringf("dshr(%s, %s)", a_expr.c_str(), b_string);
expr = stringf("dshr(%s, %s)", a_expr, b_string);
}
cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str()));
cell_exprs.push_back(stringf("%s%s <= %s\n", indent, y_id, expr));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
}
@ -968,13 +973,16 @@ struct FirrtlWorker
// Verilog appears to treat the result as signed, so if the result is wider than "A",
// we need to pad.
if (a_width < y_width) {
a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width);
a_expr = stringf("pad(%s, %d)", a_expr, y_width);
}
wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width));
cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), a_expr.c_str()));
wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent, y_id, y_width));
cell_exprs.push_back(stringf("%s%s <= %s\n", indent, y_id, a_expr));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
}
if (cell->type == ID($scopeinfo))
continue;
log_error("Cell type not supported: %s (%s.%s)\n", log_id(cell->type), log_id(module), log_id(cell));
}
@ -991,7 +999,7 @@ struct FirrtlWorker
for (int i = 0; i < GetSize(mem.rd_ports); i++)
{
auto &port = mem.rd_ports[i];
string port_name(stringf("%s.r%d", mem_id.c_str(), i));
string port_name(stringf("%s.r%d", mem_id, i));
if (port.clk_enable)
log_error("Clocked read port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid));
@ -1002,17 +1010,17 @@ struct FirrtlWorker
string ena_expr = make_expr(State::S1);
string clk_expr = make_expr(State::S0);
rpe << stringf("%s%s.addr <= %s\n", indent.c_str(), port_name.c_str(), addr_expr.c_str());
rpe << stringf("%s%s.en <= %s\n", indent.c_str(), port_name.c_str(), ena_expr.c_str());
rpe << stringf("%s%s.clk <= asClock(%s)\n", indent.c_str(), port_name.c_str(), clk_expr.c_str());
rpe << stringf("%s%s.addr <= %s\n", indent, port_name, addr_expr);
rpe << stringf("%s%s.en <= %s\n", indent, port_name, ena_expr);
rpe << stringf("%s%s.clk <= asClock(%s)\n", indent, port_name, clk_expr);
cell_exprs.push_back(rpe.str());
register_reverse_wire_map(stringf("%s.data", port_name.c_str()), port.data);
register_reverse_wire_map(stringf("%s.data", port_name), port.data);
}
for (int i = 0; i < GetSize(mem.wr_ports); i++)
{
auto &port = mem.wr_ports[i];
string port_name(stringf("%s.w%d", mem_id.c_str(), i));
string port_name(stringf("%s.w%d", mem_id, i));
if (!port.clk_enable)
log_error("Unclocked write port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid));
@ -1029,18 +1037,18 @@ struct FirrtlWorker
string ena_expr = make_expr(port.en[0]);
string clk_expr = make_expr(port.clk);
string mask_expr = make_expr(State::S1);
wpe << stringf("%s%s.data <= %s\n", indent.c_str(), port_name.c_str(), data_expr.c_str());
wpe << stringf("%s%s.addr <= %s\n", indent.c_str(), port_name.c_str(), addr_expr.c_str());
wpe << stringf("%s%s.en <= %s\n", indent.c_str(), port_name.c_str(), ena_expr.c_str());
wpe << stringf("%s%s.clk <= asClock(%s)\n", indent.c_str(), port_name.c_str(), clk_expr.c_str());
wpe << stringf("%s%s.mask <= %s\n", indent.c_str(), port_name.c_str(), mask_expr.c_str());
wpe << stringf("%s%s.data <= %s\n", indent, port_name, data_expr);
wpe << stringf("%s%s.addr <= %s\n", indent, port_name, addr_expr);
wpe << stringf("%s%s.en <= %s\n", indent, port_name, ena_expr);
wpe << stringf("%s%s.clk <= asClock(%s)\n", indent, port_name, clk_expr);
wpe << stringf("%s%s.mask <= %s\n", indent, port_name, mask_expr);
cell_exprs.push_back(wpe.str());
}
std::ostringstream me;
me << stringf(" mem %s:\n", mem_id.c_str());
me << stringf(" mem %s:\n", mem_id);
me << stringf(" data-type => UInt<%d>\n", mem.width);
me << stringf(" depth => %d\n", mem.size);
for (int i = 0; i < GetSize(mem.rd_ports); i++)
@ -1060,8 +1068,8 @@ struct FirrtlWorker
int y_width = GetSize(conn.first);
string expr = make_expr(conn.second);
wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width));
cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str()));
wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent, y_id, y_width));
cell_exprs.push_back(stringf("%s%s <= %s\n", indent, y_id, expr));
register_reverse_wire_map(y_id, conn.first);
}
@ -1104,7 +1112,7 @@ struct FirrtlWorker
chunk_width++;
}
new_expr = stringf("bits(%s, %d, %d)", start_map.first.c_str(),
new_expr = stringf("bits(%s, %d, %d)", start_map.first,
start_map.second + chunk_width - 1, start_map.second);
is_valid = true;
}
@ -1127,13 +1135,13 @@ struct FirrtlWorker
if (is_valid) {
if (make_unconn_id) {
wire_decls.push_back(stringf("%swire %s: UInt<1> %s\n", indent.c_str(), unconn_id.c_str(), wireFileinfo.c_str()));
wire_decls.push_back(stringf("%swire %s: UInt<1> %s\n", indent, unconn_id, wireFileinfo));
// `invalid` is a firrtl construction for simulation so we will not
// tag it with a @[fileinfo] tag as it doesn't directly correspond to
// a specific line of verilog code.
wire_decls.push_back(stringf("%s%s is invalid\n", indent.c_str(), unconn_id.c_str()));
wire_decls.push_back(stringf("%s%s is invalid\n", indent, unconn_id));
}
wire_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), make_id(wire->name), expr.c_str(), wireFileinfo.c_str()));
wire_exprs.push_back(stringf("%s%s <= %s %s\n", indent, make_id(wire->name), expr, wireFileinfo));
} else {
if (make_unconn_id) {
unconn_id.clear();
@ -1141,7 +1149,7 @@ struct FirrtlWorker
// `invalid` is a firrtl construction for simulation so we will not
// tag it with a @[fileinfo] tag as it doesn't directly correspond to
// a specific line of verilog code.
wire_decls.push_back(stringf("%s%s is invalid\n", indent.c_str(), make_id(wire->name)));
wire_decls.push_back(stringf("%s%s is invalid\n", indent, make_id(wire->name)));
}
}
@ -1190,6 +1198,7 @@ struct FirrtlBackend : public Backend {
log(" pmuxtree\n");
log(" bmuxmap\n");
log(" demuxmap\n");
log(" bwmuxmap\n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
@ -1206,21 +1215,20 @@ struct FirrtlBackend : public Backend {
}
extra_args(f, filename, args, argidx);
if (!design->full_selection())
log_cmd_error("This command only operates on fully selected designs!\n");
log_header(design, "Executing FIRRTL backend.\n");
log_push();
Pass::call(design, "pmuxtree");
Pass::call(design, "bmuxmap");
Pass::call(design, "demuxmap");
Pass::call(design, "bwmuxmap");
used_names.clear();
namecache.clear();
autoid_counter = 0;
// Get the top module, or a reasonable facsimile - we need something for the circuit name.
Module *top = design->top_module();
Module *top = nullptr;
Module *last = nullptr;
// Generate module and wire names.
for (auto module : design->modules()) {
@ -1237,8 +1245,11 @@ struct FirrtlBackend : public Backend {
if (top == nullptr)
top = last;
if (!top)
log_cmd_error("There is no top module in this design!\n");
std::string circuitFileinfo = getFileinfo(top);
*f << stringf("circuit %s: %s\n", make_id(top->name), circuitFileinfo.c_str());
*f << stringf("circuit %s: %s\n", make_id(top->name), circuitFileinfo);
emit_elaborated_extmodules(design, *f);
@ -1252,6 +1263,7 @@ struct FirrtlBackend : public Backend {
}
}
used_names.clear();
namecache.clear();
autoid_counter = 0;
}

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -ex
cd ../../

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);
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);
SmtModule smt(module);
smt.write(*f);
}
}
} FunctionalSmtBackend;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,373 @@
/*
* 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;
bool use_assoc_list_helpers;
std::optional<std::string> input_helper_name;
std::optional<std::string> output_helper_name;
SmtrStruct input_struct;
SmtrStruct output_struct;
SmtrStruct state_struct;
SmtrModule(Module *module, bool assoc_list_helpers)
: ir(Functional::IR::from_module(module)), scope(), name(scope.unique_name(module->name)), use_assoc_list_helpers(assoc_list_helpers),
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");
if (assoc_list_helpers) {
input_helper_name = scope.unique_name(module->name.str() + "_inputs_helper");
scope.reserve(*input_helper_name);
output_helper_name = scope.unique_name(module->name.str() + "_outputs_helper");
scope.reserve(*output_helper_name);
}
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", 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();
}
void write_initial(SExprWriter &w)
{
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();
}
void write_assoc_list_helpers(SExprWriter &w)
{
log_assert(output_helper_name && input_helper_name);
// Input struct keyword-based constructor.
w.push();
w.open(list("define"));
const auto inputs_name = "inputs";
w.open(list(*input_helper_name, inputs_name));
w.close();
w.open(list(input_struct.name));
for (auto input : ir.inputs()) {
w.push();
w.open(list("let"));
w.push();
w.open(list());
w.open(list("assoc-result"));
w << list("assoc", "\"" + RTLIL::unescape_id(input->name) + "\"", inputs_name);
w.pop();
w.open(list("if", "assoc-result"));
w << list("cdr", "assoc-result");
w.open(list("begin"));
w << list("fprintf", list("current-error-port"), "\"%s not found in inputs\"");
w << "'not-found";
w.pop();
}
w.pop();
// Output struct keyword-based destructuring
w.push();
w.open(list("define"));
const auto outputs_name = "outputs";
w << list(*output_helper_name, outputs_name);
w.open(list("list"));
for (auto output : ir.outputs()) {
w << list("cons", "\"" + RTLIL::unescape_id(output->name) + "\"", output_struct.access("outputs", output->name));
}
w.pop();
}
void write(std::ostream &out)
{
SExprWriter w(out);
input_struct.write_definition(w);
output_struct.write_definition(w);
state_struct.write_definition(w);
if (use_assoc_list_helpers) {
write_assoc_list_helpers(w);
}
write_eval(w);
write_initial(w);
}
};
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] [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(" -assoc-list-helpers\n");
log(" provide helper functions which convert inputs/outputs from/to association lists\n");
log(" \n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
{
auto provides = false;
auto assoc_list_helpers = 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 if (args[argidx] == "-assoc-list-helpers")
assoc_list_helpers = 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, assoc_list_helpers);
smtr.write(*f);
}
}
} FunctionalSmtrBackend;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,158 @@
/*
* 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.append(word);
}
insert_concatenated(low, values);
}
}
}
};
struct FunctionalTestGeneric : public Pass
{
FunctionalTestGeneric() : Pass("test_generic", "test the generic compute graph") {
internal();
}
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);
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

@ -100,13 +100,13 @@ struct IntersynthBackend : public Backend {
}
extra_args(f, filename, args, argidx);
log("Output filename: %s\n", filename.c_str());
log("Output filename: %s\n", filename);
for (auto filename : libfiles) {
std::ifstream f;
f.open(filename.c_str());
if (f.fail())
log_error("Can't open lib file `%s'.\n", filename.c_str());
log_error("Can't open lib file `%s'.\n", filename);
RTLIL::Design *lib = new RTLIL::Design;
Frontend::frontend_call(lib, &f, filename, (filename.size() > 3 && filename.compare(filename.size()-3, std::string::npos, ".il") == 0 ? "rtlil" : "verilog"));
libs.push_back(lib);
@ -172,15 +172,15 @@ struct IntersynthBackend : public Backend {
if (sig.size() != 0) {
conntypes_code.insert(stringf("conntype b%d %d 2 %d\n", sig.size(), sig.size(), sig.size()));
celltype_code += stringf(" b%d %s%s", sig.size(), ct.cell_output(cell->type, port.first) ? "*" : "", log_id(port.first));
node_code += stringf(" %s %s", log_id(port.first), netname(conntypes_code, celltypes_code, constcells_code, sig).c_str());
node_code += stringf(" %s %s", log_id(port.first), netname(conntypes_code, celltypes_code, constcells_code, sig));
}
}
for (auto &param : cell->parameters) {
celltype_code += stringf(" cfg:%d %s", int(param.second.bits.size()), log_id(param.first));
if (param.second.bits.size() != 32) {
celltype_code += stringf(" cfg:%d %s", int(param.second.size()), log_id(param.first));
if (param.second.size() != 32) {
node_code += stringf(" %s '", log_id(param.first));
for (int i = param.second.bits.size()-1; i >= 0; i--)
node_code += param.second.bits[i] == State::S1 ? "1" : "0";
for (int i = param.second.size()-1; i >= 0; i--)
node_code += param.second[i] == State::S1 ? "1" : "0";
} else
node_code += stringf(" %s 0x%x", log_id(param.first), param.second.as_int());
}
@ -199,13 +199,13 @@ struct IntersynthBackend : public Backend {
if (!flag_notypes) {
*f << stringf("### Connection Types\n");
for (auto code : conntypes_code)
*f << stringf("%s", code.c_str());
*f << stringf("%s", code);
*f << stringf("\n### Cell Types\n");
for (auto code : celltypes_code)
*f << stringf("%s", code.c_str());
*f << stringf("%s", code);
}
*f << stringf("\n### Netlists\n");
*f << stringf("%s", netlists_code.c_str());
*f << stringf("%s", netlists_code);
for (auto lib : libs)
delete lib;

View file

@ -21,12 +21,13 @@
#include "kernel/register.h"
#include "kernel/sigtools.h"
#include "kernel/celltypes.h"
#include "kernel/cellaigs.h"
#include "kernel/log.h"
#include <string>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <sstream>
#include <iterator>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
@ -116,17 +117,17 @@ struct JnyWriter
_include_connections(connections), _include_attributes(attributes), _include_properties(properties)
{ }
void write_metadata(Design *design, uint16_t indent_level = 0)
void write_metadata(Design *design, uint16_t indent_level = 0, std::string invk = "")
{
log_assert(design != nullptr);
design->sort();
f << "{\n";
f << stringf(" \"generator\": \"%s\",\n", escape_string(yosys_version_str).c_str());
// XXX(aki): Replace this with a proper version info eventually:tm:
f << " \"version\": \"0.0.0\",\n";
f << " \"$schema\": \"https://raw.githubusercontent.com/YosysHQ/yosys/main/misc/jny.schema.json\",\n";
f << stringf(" \"generator\": \"%s\",\n", escape_string(yosys_maybe_version()));
f << " \"version\": \"0.0.1\",\n";
f << " \"invocation\": \"" << escape_string(invk) << "\",\n";
f << " \"features\": [";
size_t fnum{0};
@ -231,7 +232,7 @@ struct JnyWriter
const auto _indent = gen_indent(indent_level);
f << _indent << "{\n";
f << stringf(" %s\"name\": \"%s\",\n", _indent.c_str(), escape_string(RTLIL::unescape_id(mod->name)).c_str());
f << stringf(" %s\"name\": \"%s\",\n", _indent, escape_string(RTLIL::unescape_id(mod->name)));
f << _indent << " \"cell_sorts\": [\n";
bool first_sort{true};
@ -279,7 +280,7 @@ struct JnyWriter
f << ",\n";
f << _indent << " {\n";
f << stringf(" %s\"name\": \"%s\",\n", _indent.c_str(), escape_string(RTLIL::unescape_id(con.first)).c_str());
f << stringf(" %s\"name\": \"%s\",\n", _indent, escape_string(RTLIL::unescape_id(con.first)));
f << _indent << " \"direction\": \"";
if (port_cell->input(con.first))
f << "i";
@ -289,7 +290,7 @@ struct JnyWriter
if (con.second.size() == 1)
f << _indent << " \"range\": [0, 0]\n";
else
f << stringf(" %s\"range\": [%d, %d]\n", _indent.c_str(), con.second.size(), 0);
f << stringf(" %s\"range\": [%d, %d]\n", _indent, con.second.size(), 0);
f << _indent << " }";
first_port = false;
@ -303,7 +304,7 @@ struct JnyWriter
const auto _indent = gen_indent(indent_level);
f << _indent << "{\n";
f << stringf(" %s\"type\": \"%s\",\n", _indent.c_str(), sort.first.c_str());
f << stringf(" %s\"type\": \"%s\",\n", _indent, sort.first);
f << _indent << " \"ports\": [\n";
write_cell_ports(port_cell, indent_level + 2);
@ -350,10 +351,10 @@ struct JnyWriter
f << stringf(",\n");
const auto param_val = param.second;
if (!param_val.empty()) {
f << stringf(" %s\"%s\": ", _indent.c_str(), escape_string(RTLIL::unescape_id(param.first)).c_str());
f << stringf(" %s\"%s\": ", _indent, escape_string(RTLIL::unescape_id(param.first)));
write_param_val(param_val);
} else {
f << stringf(" %s\"%s\": true", _indent.c_str(), escape_string(RTLIL::unescape_id(param.first)).c_str());
f << stringf(" %s\"%s\": true", _indent, escape_string(RTLIL::unescape_id(param.first)));
}
first_param = false;
@ -365,7 +366,7 @@ struct JnyWriter
log_assert(cell != nullptr);
f << _indent << " {\n";
f << stringf(" %s\"name\": \"%s\"", _indent.c_str(), escape_string(RTLIL::unescape_id(cell->name)).c_str());
f << stringf(" %s\"name\": \"%s\"", _indent, escape_string(RTLIL::unescape_id(cell->name)));
if (_include_connections) {
f << ",\n" << _indent << " \"connections\": [\n";
@ -409,11 +410,12 @@ struct JnyWriter
struct JnyBackend : public Backend {
JnyBackend() : Backend("jny", "generate design metadata") { }
void help() override {
// XXX(aki): TODO: explicitly document the JSON schema
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" jny [options] [selection]\n");
log("\n");
log("Write JSON netlist metadata for the current design\n");
log("\n");
log(" -no-connections\n");
log(" Don't include connection information in the netlist output.\n");
log("\n");
@ -423,8 +425,8 @@ struct JnyBackend : public Backend {
log(" -no-properties\n");
log(" Don't include property information in the netlist output.\n");
log("\n");
log("Write a JSON metadata for the current design\n");
log("\n");
log("The JSON schema for JNY output files is located in the \"jny.schema.json\" file\n");
log("which is located at \"https://raw.githubusercontent.com/YosysHQ/yosys/main/misc/jny.schema.json\"\n");
log("\n");
}
@ -453,12 +455,22 @@ struct JnyBackend : public Backend {
break;
}
// Compose invocation line
std::ostringstream invk;
if (!args.empty()) {
std::copy(args.begin(), args.end(),
std::ostream_iterator<std::string>(invk, " ")
);
}
invk << filename;
extra_args(f, filename, args, argidx);
log_header(design, "Executing jny backend.\n");
JnyWriter jny_writer(*f, false, connections, attributes, properties);
jny_writer.write_metadata(design);
jny_writer.write_metadata(design, 0, invk.str());
}
} JnyBackend;
@ -472,7 +484,7 @@ struct JnyPass : public Pass {
log("\n");
log(" jny [options] [selection]\n");
log("\n");
log("Write a JSON netlist metadata for the current design\n");
log("Write JSON netlist metadata for the current design\n");
log("\n");
log(" -o <filename>\n");
log(" write to the specified file.\n");
@ -520,32 +532,43 @@ struct JnyPass : public Pass {
break;
}
// Compose invocation line
std::ostringstream invk;
if (!args.empty()) {
std::copy(args.begin(), args.end(),
std::ostream_iterator<std::string>(invk, " ")
);
}
extra_args(args, argidx, design);
std::ostream *f;
std::stringstream buf;
bool empty = filename.empty();
if (!filename.empty()) {
if (!empty) {
rewrite_filename(filename);
std::ofstream *ff = new std::ofstream;
ff->open(filename.c_str(), std::ofstream::trunc);
if (ff->fail()) {
delete ff;
log_error("Can't open file `%s' for writing: %s\n", filename.c_str(), strerror(errno));
log_error("Can't open file `%s' for writing: %s\n", filename, strerror(errno));
}
f = ff;
invk << filename;
} else {
f = &buf;
}
JnyWriter jny_writer(*f, false, connections, attributes, properties);
jny_writer.write_metadata(design);
jny_writer.write_metadata(design, 0, invk.str());
if (!filename.empty()) {
if (!empty) {
delete f;
} else {
log("%s", buf.str().c_str());
log("%s", buf.str());
}
}

View file

@ -34,6 +34,7 @@ struct JsonWriter
bool use_selection;
bool aig_mode;
bool compat_int_mode;
bool scopeinfo_mode;
Design *design;
Module *module;
@ -43,9 +44,9 @@ struct JsonWriter
dict<SigBit, string> sigids;
pool<Aig> aig_models;
JsonWriter(std::ostream &f, bool use_selection, bool aig_mode, bool compat_int_mode) :
JsonWriter(std::ostream &f, bool use_selection, bool aig_mode, bool compat_int_mode, bool scopeinfo_mode) :
f(f), use_selection(use_selection), aig_mode(aig_mode),
compat_int_mode(compat_int_mode) { }
compat_int_mode(compat_int_mode), scopeinfo_mode(scopeinfo_mode) { }
string get_string(string str)
{
@ -134,7 +135,7 @@ struct JsonWriter
bool first = true;
for (auto &param : parameters) {
f << stringf("%s\n", first ? "" : ",");
f << stringf(" %s%s: ", for_module ? "" : " ", get_name(param.first).c_str());
f << stringf(" %s%s: ", for_module ? "" : " ", get_name(param.first));
write_parameter_value(param.second);
first = false;
}
@ -154,7 +155,7 @@ struct JsonWriter
log_error("Module %s contains processes, which are not supported by JSON backend (run `proc` first).\n", log_id(module));
}
f << stringf(" %s: {\n", get_name(module->name).c_str());
f << stringf(" %s: {\n", get_name(module->name));
f << stringf(" \"attributes\": {");
write_parameters(module->attributes, /*for_module=*/true);
@ -173,7 +174,7 @@ struct JsonWriter
if (use_selection && !module->selected(w))
continue;
f << stringf("%s\n", first ? "" : ",");
f << stringf(" %s: {\n", get_name(n).c_str());
f << stringf(" %s: {\n", get_name(n));
f << stringf(" \"direction\": \"%s\",\n", w->port_input ? w->port_output ? "inout" : "input" : "output");
if (w->start_offset)
f << stringf(" \"offset\": %d,\n", w->start_offset);
@ -181,7 +182,7 @@ struct JsonWriter
f << stringf(" \"upto\": 1,\n");
if (w->is_signed)
f << stringf(" \"signed\": %d,\n", w->is_signed);
f << stringf(" \"bits\": %s\n", get_bits(w).c_str());
f << stringf(" \"bits\": %s\n", get_bits(w));
f << stringf(" }");
first = false;
}
@ -192,14 +193,16 @@ struct JsonWriter
for (auto c : module->cells()) {
if (use_selection && !module->selected(c))
continue;
if (!scopeinfo_mode && c->type == ID($scopeinfo))
continue;
f << stringf("%s\n", first ? "" : ",");
f << stringf(" %s: {\n", get_name(c->name).c_str());
f << stringf(" %s: {\n", get_name(c->name));
f << stringf(" \"hide_name\": %s,\n", c->name[0] == '$' ? "1" : "0");
f << stringf(" \"type\": %s,\n", get_name(c->type).c_str());
f << stringf(" \"type\": %s,\n", get_name(c->type));
if (aig_mode) {
Aig aig(c);
if (!aig.name.empty()) {
f << stringf(" \"model\": \"%s\",\n", aig.name.c_str());
f << stringf(" \"model\": \"%s\",\n", aig.name);
aig_models.insert(aig);
}
}
@ -217,7 +220,7 @@ struct JsonWriter
if (c->input(conn.first))
direction = c->output(conn.first) ? "inout" : "input";
f << stringf("%s\n", first2 ? "" : ",");
f << stringf(" %s: \"%s\"", get_name(conn.first).c_str(), direction.c_str());
f << stringf(" %s: \"%s\"", get_name(conn.first), direction);
first2 = false;
}
f << stringf("\n },\n");
@ -226,7 +229,7 @@ struct JsonWriter
bool first2 = true;
for (auto &conn : c->connections()) {
f << stringf("%s\n", first2 ? "" : ",");
f << stringf(" %s: %s", get_name(conn.first).c_str(), get_bits(conn.second).c_str());
f << stringf(" %s: %s", get_name(conn.first), get_bits(conn.second));
first2 = false;
}
f << stringf("\n }\n");
@ -242,7 +245,7 @@ struct JsonWriter
if (use_selection && !module->selected(it.second))
continue;
f << stringf("%s\n", first ? "" : ",");
f << stringf(" %s: {\n", get_name(it.second->name).c_str());
f << stringf(" %s: {\n", get_name(it.second->name));
f << stringf(" \"hide_name\": %s,\n", it.second->name[0] == '$' ? "1" : "0");
f << stringf(" \"attributes\": {");
write_parameters(it.second->attributes);
@ -262,9 +265,9 @@ struct JsonWriter
if (use_selection && !module->selected(w))
continue;
f << stringf("%s\n", first ? "" : ",");
f << stringf(" %s: {\n", get_name(w->name).c_str());
f << stringf(" %s: {\n", get_name(w->name));
f << stringf(" \"hide_name\": %s,\n", w->name[0] == '$' ? "1" : "0");
f << stringf(" \"bits\": %s,\n", get_bits(w).c_str());
f << stringf(" \"bits\": %s,\n", get_bits(w));
if (w->start_offset)
f << stringf(" \"offset\": %d,\n", w->start_offset);
if (w->upto)
@ -288,7 +291,7 @@ struct JsonWriter
design->sort();
f << stringf("{\n");
f << stringf(" \"creator\": %s,\n", get_string(yosys_version_str).c_str());
f << stringf(" \"creator\": %s,\n", get_string(yosys_maybe_version()));
f << stringf(" \"modules\": {\n");
vector<Module*> modules = use_selection ? design->selected_modules() : design->modules();
bool first_module = true;
@ -305,7 +308,7 @@ struct JsonWriter
for (auto &aig : aig_models) {
if (!first_model)
f << stringf(",\n");
f << stringf(" \"%s\": [\n", aig.name.c_str());
f << stringf(" \"%s\": [\n", aig.name);
int node_idx = 0;
for (auto &node : aig.nodes) {
if (node_idx != 0)
@ -349,6 +352,12 @@ struct JsonBackend : public Backend {
log(" emit 32-bit or smaller fully-defined parameter values directly\n");
log(" as JSON numbers (for compatibility with old parsers)\n");
log("\n");
log(" -selected\n");
log(" output only select module\n");
log("\n");
log(" -noscopeinfo\n");
log(" don't include $scopeinfo cells in the output\n");
log("\n");
log("\n");
log("The general syntax of the JSON output created by this command is as follows:\n");
log("\n");
@ -397,9 +406,9 @@ struct JsonBackend : public Backend {
log(" \"signed\": <1 if the port is signed>\n");
log(" }\n");
log("\n");
log("The \"offset\" and \"upto\" fields are skipped if their value would be 0.");
log("They don't affect connection semantics, and are only used to preserve original");
log("HDL bit indexing.");
log("The \"offset\" and \"upto\" fields are skipped if their value would be 0.\n");
log("They don't affect connection semantics, and are only used to preserve original\n");
log("HDL bit indexing.\n");
log("And <cell_details> is:\n");
log("\n");
log(" {\n");
@ -459,8 +468,8 @@ struct JsonBackend : public Backend {
log("connected to a constant driver are denoted as string \"0\", \"1\", \"x\", or\n");
log("\"z\" instead of a number.\n");
log("\n");
log("Bit vectors (including integers) are written as string holding the binary");
log("representation of the value. Strings are written as strings, with an appended");
log("Bit vectors (including integers) are written as string holding the binary\n");
log("representation of the value. Strings are written as strings, with an appended\n");
log("blank in cases of strings of the form /[01xz]* */.\n");
log("\n");
log("For example the following Verilog code:\n");
@ -593,6 +602,8 @@ struct JsonBackend : public Backend {
{
bool aig_mode = false;
bool compat_int_mode = false;
bool use_selection = false;
bool scopeinfo_mode = true;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
@ -605,13 +616,21 @@ struct JsonBackend : public Backend {
compat_int_mode = true;
continue;
}
if (args[argidx] == "-selected") {
use_selection = true;
continue;
}
if (args[argidx] == "-noscopeinfo") {
scopeinfo_mode = false;
continue;
}
break;
}
extra_args(f, filename, args, argidx);
log_header(design, "Executing JSON backend.\n");
JsonWriter json_writer(*f, false, aig_mode, compat_int_mode);
JsonWriter json_writer(*f, use_selection, aig_mode, compat_int_mode, scopeinfo_mode);
json_writer.write_design(design);
}
} JsonBackend;
@ -636,6 +655,9 @@ struct JsonPass : public Pass {
log(" emit 32-bit or smaller fully-defined parameter values directly\n");
log(" as JSON numbers (for compatibility with old parsers)\n");
log("\n");
log(" -noscopeinfo\n");
log(" don't include $scopeinfo cells in the output\n");
log("\n");
log("See 'help write_json' for a description of the JSON format used.\n");
log("\n");
}
@ -644,6 +666,7 @@ struct JsonPass : public Pass {
std::string filename;
bool aig_mode = false;
bool compat_int_mode = false;
bool scopeinfo_mode = true;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
@ -660,33 +683,38 @@ struct JsonPass : public Pass {
compat_int_mode = true;
continue;
}
if (args[argidx] == "-noscopeinfo") {
scopeinfo_mode = false;
continue;
}
break;
}
extra_args(args, argidx, design);
std::ostream *f;
std::stringstream buf;
bool empty = filename.empty();
if (!filename.empty()) {
if (!empty) {
rewrite_filename(filename);
std::ofstream *ff = new std::ofstream;
ff->open(filename.c_str(), std::ofstream::trunc);
if (ff->fail()) {
delete ff;
log_error("Can't open file `%s' for writing: %s\n", filename.c_str(), strerror(errno));
log_error("Can't open file `%s' for writing: %s\n", filename, strerror(errno));
}
f = ff;
} else {
f = &buf;
}
JsonWriter json_writer(*f, true, aig_mode, compat_int_mode);
JsonWriter json_writer(*f, true, aig_mode, compat_int_mode, scopeinfo_mode);
json_writer.write_design(design);
if (!filename.empty()) {
if (!empty) {
delete f;
} else {
log("%s", buf.str().c_str());
log("%s", buf.str());
}
}
} JsonPass;

View file

@ -1,2 +0,0 @@
yosys.pb.cc
yosys.pb.h

View file

@ -1,10 +0,0 @@
ifeq ($(ENABLE_PROTOBUF),1)
backends/protobuf/yosys.pb.cc backends/protobuf/yosys.pb.h: misc/yosys.proto
$(Q) cd misc && protoc --cpp_out "../backends/protobuf" yosys.proto
backends/protobuf/protobuf.cc: backends/protobuf/yosys.pb.h
OBJS += backends/protobuf/protobuf.o backends/protobuf/yosys.pb.o
endif

View file

@ -1,371 +0,0 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
* Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.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 <google/protobuf/text_format.h>
#include "kernel/rtlil.h"
#include "kernel/register.h"
#include "kernel/sigtools.h"
#include "kernel/celltypes.h"
#include "kernel/cellaigs.h"
#include "kernel/log.h"
#include "yosys.pb.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct ProtobufDesignSerializer
{
bool aig_mode_;
bool use_selection_;
yosys::pb::Design *pb_;
Design *design_;
Module *module_;
SigMap sigmap_;
int sigidcounter_;
dict<SigBit, uint64_t> sigids_;
pool<Aig> aig_models_;
ProtobufDesignSerializer(bool use_selection, bool aig_mode) :
aig_mode_(aig_mode), use_selection_(use_selection) { }
string get_name(IdString name)
{
return RTLIL::unescape_id(name);
}
void serialize_parameters(google::protobuf::Map<std::string, yosys::pb::Parameter> *out,
const dict<IdString, Const> &parameters)
{
for (auto &param : parameters) {
std::string key = get_name(param.first);
yosys::pb::Parameter pb_param;
if ((param.second.flags & RTLIL::ConstFlags::CONST_FLAG_STRING) != 0) {
pb_param.set_str(param.second.decode_string());
} else if (GetSize(param.second.bits) > 64) {
pb_param.set_str(param.second.as_string());
} else {
pb_param.set_int_(param.second.as_int());
}
(*out)[key] = pb_param;
}
}
void get_bits(yosys::pb::BitVector *out, SigSpec sig)
{
for (auto bit : sigmap_(sig)) {
auto sig = out->add_signal();
// Constant driver.
if (bit.wire == nullptr) {
if (bit == State::S0) sig->set_constant(sig->CONSTANT_DRIVER_LOW);
else if (bit == State::S1) sig->set_constant(sig->CONSTANT_DRIVER_HIGH);
else if (bit == State::Sz) sig->set_constant(sig->CONSTANT_DRIVER_Z);
else sig->set_constant(sig->CONSTANT_DRIVER_X);
continue;
}
// Signal - give it a unique identifier.
if (sigids_.count(bit) == 0) {
sigids_[bit] = sigidcounter_++;
}
sig->set_id(sigids_[bit]);
}
}
void serialize_module(yosys::pb::Module* out, Module *module)
{
module_ = module;
log_assert(module_->design == design_);
sigmap_.set(module_);
sigids_.clear();
sigidcounter_ = 0;
serialize_parameters(out->mutable_attribute(), module_->attributes);
for (auto n : module_->ports) {
Wire *w = module->wire(n);
if (use_selection_ && !module_->selected(w))
continue;
yosys::pb::Module::Port pb_port;
pb_port.set_direction(w->port_input ? w->port_output ?
yosys::pb::DIRECTION_INOUT : yosys::pb::DIRECTION_INPUT : yosys::pb::DIRECTION_OUTPUT);
get_bits(pb_port.mutable_bits(), w);
(*out->mutable_port())[get_name(n)] = pb_port;
}
for (auto c : module_->cells()) {
if (use_selection_ && !module_->selected(c))
continue;
yosys::pb::Module::Cell pb_cell;
pb_cell.set_hide_name(c->name[0] == '$');
pb_cell.set_type(get_name(c->type));
if (aig_mode_) {
Aig aig(c);
if (aig.name.empty())
continue;
pb_cell.set_model(aig.name);
aig_models_.insert(aig);
}
serialize_parameters(pb_cell.mutable_parameter(), c->parameters);
serialize_parameters(pb_cell.mutable_attribute(), c->attributes);
if (c->known()) {
for (auto &conn : c->connections()) {
yosys::pb::Direction direction = yosys::pb::DIRECTION_OUTPUT;
if (c->input(conn.first))
direction = c->output(conn.first) ? yosys::pb::DIRECTION_INOUT : yosys::pb::DIRECTION_INPUT;
(*pb_cell.mutable_port_direction())[get_name(conn.first)] = direction;
}
}
for (auto &conn : c->connections()) {
yosys::pb::BitVector vec;
get_bits(&vec, conn.second);
(*pb_cell.mutable_connection())[get_name(conn.first)] = vec;
}
(*out->mutable_cell())[get_name(c->name)] = pb_cell;
}
for (auto w : module_->wires()) {
if (use_selection_ && !module_->selected(w))
continue;
auto netname = out->add_netname();
netname->set_hide_name(w->name[0] == '$');
get_bits(netname->mutable_bits(), w);
serialize_parameters(netname->mutable_attributes(), w->attributes);
}
}
void serialize_models(google::protobuf::Map<string, yosys::pb::Model> *models)
{
for (auto &aig : aig_models_) {
yosys::pb::Model pb_model;
for (auto &node : aig.nodes) {
auto pb_node = pb_model.add_node();
if (node.portbit >= 0) {
if (node.inverter) {
pb_node->set_type(pb_node->TYPE_NPORT);
} else {
pb_node->set_type(pb_node->TYPE_PORT);
}
auto port = pb_node->mutable_port();
port->set_portname(log_id(node.portname));
port->set_bitindex(node.portbit);
} else if (node.left_parent < 0 && node.right_parent < 0) {
if (node.inverter) {
pb_node->set_type(pb_node->TYPE_TRUE);
} else {
pb_node->set_type(pb_node->TYPE_FALSE);
}
} else {
if (node.inverter) {
pb_node->set_type(pb_node->TYPE_NAND);
} else {
pb_node->set_type(pb_node->TYPE_AND);
}
auto gate = pb_node->mutable_gate();
gate->set_left(node.left_parent);
gate->set_right(node.right_parent);
}
for (auto &op : node.outports) {
auto pb_op = pb_node->add_out_port();
pb_op->set_name(log_id(op.first));
pb_op->set_bit_index(op.second);
}
}
(*models)[aig.name] = pb_model;
}
}
void serialize_design(yosys::pb::Design *pb, Design *design)
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
pb_ = pb;
pb_->Clear();
pb_->set_creator(yosys_version_str);
design_ = design;
design_->sort();
auto modules = use_selection_ ? design_->selected_modules() : design_->modules();
for (auto mod : modules) {
yosys::pb::Module pb_mod;
serialize_module(&pb_mod, mod);
(*pb->mutable_modules())[mod->name.str()] = pb_mod;
}
serialize_models(pb_->mutable_models());
}
};
struct ProtobufBackend : public Backend {
ProtobufBackend(): Backend("protobuf", "write design to a Protocol Buffer file") { }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" write_protobuf [options] [filename]\n");
log("\n");
log("Write a JSON netlist of the current design.\n");
log("\n");
log(" -aig\n");
log(" include AIG models for the different gate types\n");
log("\n");
log(" -text\n");
log(" output protobuf in Text/ASCII representation\n");
log("\n");
log("The schema of the output Protocol Buffer is defined in misc/yosys.pb in the\n");
log("Yosys source code distribution.\n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
{
bool aig_mode = false;
bool text_mode = false;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
if (args[argidx] == "-aig") {
aig_mode = true;
continue;
}
if (args[argidx] == "-text") {
text_mode = true;
continue;
}
break;
}
extra_args(f, filename, args, argidx, !text_mode);
log_header(design, "Executing Protobuf backend.\n");
yosys::pb::Design pb;
ProtobufDesignSerializer serializer(false, aig_mode);
serializer.serialize_design(&pb, design);
if (text_mode) {
string out;
google::protobuf::TextFormat::PrintToString(pb, &out);
*f << out;
} else {
pb.SerializeToOstream(f);
}
}
} ProtobufBackend;
struct ProtobufPass : public Pass {
ProtobufPass() : Pass("protobuf", "write design in Protobuf format") { }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" protobuf [options] [selection]\n");
log("\n");
log("Write a JSON netlist of all selected objects.\n");
log("\n");
log(" -o <filename>\n");
log(" write to the specified file.\n");
log("\n");
log(" -aig\n");
log(" include AIG models for the different gate types\n");
log("\n");
log(" -text\n");
log(" output protobuf in Text/ASCII representation\n");
log("\n");
log("The schema of the output Protocol Buffer is defined in misc/yosys.pb in the\n");
log("Yosys source code distribution.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
std::string filename;
bool aig_mode = false;
bool text_mode = false;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
if (args[argidx] == "-o" && argidx+1 < args.size()) {
filename = args[++argidx];
continue;
}
if (args[argidx] == "-aig") {
aig_mode = true;
continue;
}
if (args[argidx] == "-text") {
text_mode = true;
continue;
}
break;
}
extra_args(args, argidx, design);
std::ostream *f;
std::stringstream buf;
if (!filename.empty()) {
rewrite_filename(filename);
std::ofstream *ff = new std::ofstream;
ff->open(filename.c_str(), text_mode ? std::ofstream::trunc : (std::ofstream::trunc | std::ofstream::binary));
if (ff->fail()) {
delete ff;
log_error("Can't open file `%s' for writing: %s\n", filename.c_str(), strerror(errno));
}
f = ff;
} else {
f = &buf;
}
yosys::pb::Design pb;
ProtobufDesignSerializer serializer(true, aig_mode);
serializer.serialize_design(&pb, design);
if (text_mode) {
string out;
google::protobuf::TextFormat::PrintToString(pb, &out);
*f << out;
} else {
pb.SerializeToOstream(f);
}
if (!filename.empty()) {
delete f;
} else {
log("%s", buf.str().c_str());
}
}
} ProtobufPass;
PRIVATE_NAMESPACE_END;

View file

@ -24,22 +24,33 @@
#include "rtlil_backend.h"
#include "kernel/yosys.h"
#include "kernel/utils.h"
#include <errno.h>
#include <iterator>
USING_YOSYS_NAMESPACE
using namespace RTLIL_BACKEND;
YOSYS_NAMESPACE_BEGIN
void RTLIL_BACKEND::dump_attributes(std::ostream &f, std::string indent, const RTLIL::AttrObject *obj)
{
for (const auto& [name, value] : reversed(obj->attributes)) {
f << stringf("%s" "attribute %s ", indent, name);
dump_const(f, value);
f << stringf("\n");
}
}
void RTLIL_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int width, int offset, bool autoint)
{
if (width < 0)
width = data.bits.size() - offset;
if ((data.flags & RTLIL::CONST_FLAG_STRING) == 0 || width != (int)data.bits.size()) {
width = data.size() - offset;
if ((data.flags & RTLIL::CONST_FLAG_STRING) == 0 || width != (int)data.size()) {
if (width == 32 && autoint) {
int32_t val = 0;
for (int i = 0; i < width; i++) {
log_assert(offset+i < (int)data.bits.size());
switch (data.bits[offset+i]) {
log_assert(offset+i < (int)data.size());
switch (data[offset+i]) {
case State::S0: break;
case State::S1: val |= 1 << i; break;
default: val = -1; break;
@ -50,13 +61,18 @@ void RTLIL_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int wi
return;
}
}
f << stringf("%d'", width);
if (data.is_fully_undef()) {
if ((data.flags & RTLIL::CONST_FLAG_UNSIZED) == 0) {
f << stringf("%d'", width);
}
if (data.flags & RTLIL::CONST_FLAG_SIGNED) {
f << stringf("s");
}
if (data.is_fully_undef_x_only()) {
f << "x";
} else {
for (int i = offset+width-1; i >= offset; i--) {
log_assert(i < (int)data.bits.size());
switch (data.bits[i]) {
log_assert(i < (int)data.size());
switch (data[i]) {
case State::S0: f << stringf("0"); break;
case State::S1: f << stringf("1"); break;
case RTLIL::Sx: f << stringf("x"); break;
@ -75,7 +91,7 @@ void RTLIL_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int wi
else if (str[i] == '\t')
f << stringf("\\t");
else if (str[i] < 32)
f << stringf("\\%03o", str[i]);
f << stringf("\\%03o", (unsigned char)str[i]);
else if (str[i] == '"')
f << stringf("\\\"");
else if (str[i] == '\\')
@ -93,11 +109,11 @@ void RTLIL_BACKEND::dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk,
dump_const(f, chunk.data, chunk.width, chunk.offset, autoint);
} else {
if (chunk.width == chunk.wire->width && chunk.offset == 0)
f << stringf("%s", chunk.wire->name.c_str());
f << stringf("%s", chunk.wire->name);
else if (chunk.width == 1)
f << stringf("%s [%d]", chunk.wire->name.c_str(), chunk.offset);
f << stringf("%s [%d]", chunk.wire->name, chunk.offset);
else
f << stringf("%s [%d:%d]", chunk.wire->name.c_str(), chunk.offset+chunk.width-1, chunk.offset);
f << stringf("%s [%d:%d]", chunk.wire->name, chunk.offset+chunk.width-1, chunk.offset);
}
}
@ -107,8 +123,9 @@ void RTLIL_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, boo
dump_sigchunk(f, sig.as_chunk(), autoint);
} else {
f << stringf("{ ");
for (auto it = sig.chunks().rbegin(); it != sig.chunks().rend(); ++it) {
dump_sigchunk(f, *it, false);
auto chunks = sig.chunks();
for (const auto& chunk : reversed(chunks)) {
dump_sigchunk(f, chunk, false);
f << stringf(" ");
}
f << stringf("}");
@ -117,12 +134,12 @@ void RTLIL_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, boo
void RTLIL_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL::Wire *wire)
{
for (auto &it : wire->attributes) {
f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str());
dump_const(f, it.second);
f << stringf("\n");
dump_attributes(f, indent, wire);
if (wire->driverCell_) {
f << stringf("%s" "# driver %s %s\n", indent,
wire->driverCell()->name, wire->driverPort());
}
f << stringf("%s" "wire ", indent.c_str());
f << stringf("%s" "wire ", indent);
if (wire->width != 1)
f << stringf("width %d ", wire->width);
if (wire->upto)
@ -137,101 +154,85 @@ void RTLIL_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL::
f << stringf("inout %d ", wire->port_id);
if (wire->is_signed)
f << stringf("signed ");
f << stringf("%s\n", wire->name.c_str());
f << stringf("%s\n", wire->name);
}
void RTLIL_BACKEND::dump_memory(std::ostream &f, std::string indent, const RTLIL::Memory *memory)
{
for (auto &it : memory->attributes) {
f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str());
dump_const(f, it.second);
f << stringf("\n");
}
f << stringf("%s" "memory ", indent.c_str());
dump_attributes(f, indent, memory);
f << stringf("%s" "memory ", indent);
if (memory->width != 1)
f << stringf("width %d ", memory->width);
if (memory->size != 0)
f << stringf("size %d ", memory->size);
if (memory->start_offset != 0)
f << stringf("offset %d ", memory->start_offset);
f << stringf("%s\n", memory->name.c_str());
f << stringf("%s\n", memory->name);
}
void RTLIL_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL::Cell *cell)
{
for (auto &it : cell->attributes) {
f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str());
dump_const(f, it.second);
dump_attributes(f, indent, cell);
f << stringf("%s" "cell %s %s\n", indent, cell->type, cell->name);
for (const auto& [name, param] : reversed(cell->parameters)) {
f << stringf("%s parameter%s%s%s %s ", indent,
(param.flags & RTLIL::CONST_FLAG_SIGNED) != 0 ? " signed" : "",
(param.flags & RTLIL::CONST_FLAG_REAL) != 0 ? " real" : "",
(param.flags & RTLIL::CONST_FLAG_UNSIZED) != 0 ? " unsized" : "",
name);
dump_const(f, param);
f << stringf("\n");
}
f << stringf("%s" "cell %s %s\n", indent.c_str(), cell->type.c_str(), cell->name.c_str());
for (auto &it : cell->parameters) {
f << stringf("%s parameter%s%s %s ", indent.c_str(),
(it.second.flags & RTLIL::CONST_FLAG_SIGNED) != 0 ? " signed" : "",
(it.second.flags & RTLIL::CONST_FLAG_REAL) != 0 ? " real" : "",
it.first.c_str());
dump_const(f, it.second);
for (const auto& [port, sig] : reversed(cell->connections_)) {
f << stringf("%s connect %s ", indent, port);
dump_sigspec(f, sig);
f << stringf("\n");
}
for (auto &it : cell->connections()) {
f << stringf("%s connect %s ", indent.c_str(), it.first.c_str());
dump_sigspec(f, it.second);
f << stringf("\n");
}
f << stringf("%s" "end\n", indent.c_str());
f << stringf("%s" "end\n", indent);
}
void RTLIL_BACKEND::dump_proc_case_body(std::ostream &f, std::string indent, const RTLIL::CaseRule *cs)
{
for (auto it = cs->actions.begin(); it != cs->actions.end(); ++it)
{
f << stringf("%s" "assign ", indent.c_str());
dump_sigspec(f, it->first);
for (const auto& [lhs, rhs] : cs->actions) {
f << stringf("%s" "assign ", indent);
dump_sigspec(f, lhs);
f << stringf(" ");
dump_sigspec(f, it->second);
dump_sigspec(f, rhs);
f << stringf("\n");
}
for (auto it = cs->switches.begin(); it != cs->switches.end(); ++it)
dump_proc_switch(f, indent, *it);
for (const auto& sw : cs->switches)
dump_proc_switch(f, indent, sw);
}
void RTLIL_BACKEND::dump_proc_switch(std::ostream &f, std::string indent, const RTLIL::SwitchRule *sw)
{
for (auto it = sw->attributes.begin(); it != sw->attributes.end(); ++it) {
f << stringf("%s" "attribute %s ", indent.c_str(), it->first.c_str());
dump_const(f, it->second);
f << stringf("\n");
}
dump_attributes(f, indent, sw);
f << stringf("%s" "switch ", indent.c_str());
f << stringf("%s" "switch ", indent);
dump_sigspec(f, sw->signal);
f << stringf("\n");
for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it)
for (const auto case_ : sw->cases)
{
for (auto ait = (*it)->attributes.begin(); ait != (*it)->attributes.end(); ++ait) {
f << stringf("%s attribute %s ", indent.c_str(), ait->first.c_str());
dump_const(f, ait->second);
f << stringf("\n");
}
f << stringf("%s case ", indent.c_str());
for (size_t i = 0; i < (*it)->compare.size(); i++) {
dump_attributes(f, indent, case_);
f << stringf("%s case ", indent);
for (size_t i = 0; i < case_->compare.size(); i++) {
if (i > 0)
f << stringf(" , ");
dump_sigspec(f, (*it)->compare[i]);
dump_sigspec(f, case_->compare[i]);
}
f << stringf("\n");
dump_proc_case_body(f, indent + " ", *it);
dump_proc_case_body(f, indent + " ", case_);
}
f << stringf("%s" "end\n", indent.c_str());
f << stringf("%s" "end\n", indent);
}
void RTLIL_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RTLIL::SyncRule *sy)
{
f << stringf("%s" "sync ", indent.c_str());
f << stringf("%s" "sync ", indent);
switch (sy->type) {
case RTLIL::ST0: f << stringf("low ");
if (0) case RTLIL::ST1: f << stringf("high ");
@ -246,21 +247,17 @@ void RTLIL_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RT
case RTLIL::STi: f << stringf("init\n"); break;
}
for (auto &it: sy->actions) {
f << stringf("%s update ", indent.c_str());
dump_sigspec(f, it.first);
for (const auto& [lhs, rhs] : sy->actions) {
f << stringf("%s update ", indent);
dump_sigspec(f, lhs);
f << stringf(" ");
dump_sigspec(f, it.second);
dump_sigspec(f, rhs);
f << stringf("\n");
}
for (auto &it: sy->mem_write_actions) {
for (auto it2 = it.attributes.begin(); it2 != it.attributes.end(); ++it2) {
f << stringf("%s attribute %s ", indent.c_str(), it2->first.c_str());
dump_const(f, it2->second);
f << stringf("\n");
}
f << stringf("%s memwr %s ", indent.c_str(), it.memid.c_str());
dump_attributes(f, indent, &it);
f << stringf("%s memwr %s ", indent, it.memid);
dump_sigspec(f, it.address);
f << stringf(" ");
dump_sigspec(f, it.data);
@ -274,21 +271,17 @@ void RTLIL_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RT
void RTLIL_BACKEND::dump_proc(std::ostream &f, std::string indent, const RTLIL::Process *proc)
{
for (auto it = proc->attributes.begin(); it != proc->attributes.end(); ++it) {
f << stringf("%s" "attribute %s ", indent.c_str(), it->first.c_str());
dump_const(f, it->second);
f << stringf("\n");
}
f << stringf("%s" "process %s\n", indent.c_str(), proc->name.c_str());
dump_attributes(f, indent, proc);
f << stringf("%s" "process %s\n", indent, proc->name);
dump_proc_case_body(f, indent + " ", &proc->root_case);
for (auto it = proc->syncs.begin(); it != proc->syncs.end(); ++it)
dump_proc_sync(f, indent + " ", *it);
f << stringf("%s" "end\n", indent.c_str());
for (auto* sync : proc->syncs)
dump_proc_sync(f, indent + " ", sync);
f << stringf("%s" "end\n", indent);
}
void RTLIL_BACKEND::dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right)
{
f << stringf("%s" "connect ", indent.c_str());
f << stringf("%s" "connect ", indent);
dump_sigspec(f, left);
f << stringf(" ");
dump_sigspec(f, right);
@ -297,18 +290,14 @@ void RTLIL_BACKEND::dump_conn(std::ostream &f, std::string indent, const RTLIL::
void RTLIL_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Module *module, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n)
{
bool print_header = flag_m || design->selected_whole_module(module->name);
bool print_body = !flag_n || !design->selected_whole_module(module->name);
bool print_header = flag_m || module->is_selected_whole();
bool print_body = !flag_n || !module->is_selected_whole();
if (print_header)
{
for (auto it = module->attributes.begin(); it != module->attributes.end(); ++it) {
f << stringf("%s" "attribute %s ", indent.c_str(), it->first.c_str());
dump_const(f, it->second);
f << stringf("\n");
}
dump_attributes(f, indent, module);
f << stringf("%s" "module %s\n", indent.c_str(), module->name.c_str());
f << stringf("%s" "module %s\n", indent, module->name);
if (!module->avail_parameters.empty()) {
if (only_selected)
@ -316,9 +305,9 @@ void RTLIL_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Modu
for (const auto &p : module->avail_parameters) {
const auto &it = module->parameter_default_values.find(p);
if (it == module->parameter_default_values.end()) {
f << stringf("%s" " parameter %s\n", indent.c_str(), p.c_str());
f << stringf("%s" " parameter %s\n", indent, p);
} else {
f << stringf("%s" " parameter %s ", indent.c_str(), p.c_str());
f << stringf("%s" " parameter %s ", indent, p);
dump_const(f, it->second);
f << stringf("\n");
}
@ -328,40 +317,40 @@ void RTLIL_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Modu
if (print_body)
{
for (auto it : module->wires())
if (!only_selected || design->selected(module, it)) {
for (const auto& [_, wire] : reversed(module->wires_))
if (!only_selected || design->selected(module, wire)) {
if (only_selected)
f << stringf("\n");
dump_wire(f, indent + " ", it);
dump_wire(f, indent + " ", wire);
}
for (auto it : module->memories)
if (!only_selected || design->selected(module, it.second)) {
for (const auto& [_, mem] : reversed(module->memories))
if (!only_selected || design->selected(module, mem)) {
if (only_selected)
f << stringf("\n");
dump_memory(f, indent + " ", it.second);
dump_memory(f, indent + " ", mem);
}
for (auto it : module->cells())
if (!only_selected || design->selected(module, it)) {
for (const auto& [_, cell] : reversed(module->cells_))
if (!only_selected || design->selected(module, cell)) {
if (only_selected)
f << stringf("\n");
dump_cell(f, indent + " ", it);
dump_cell(f, indent + " ", cell);
}
for (auto it : module->processes)
if (!only_selected || design->selected(module, it.second)) {
for (const auto& [_, process] : reversed(module->processes))
if (!only_selected || design->selected(module, process)) {
if (only_selected)
f << stringf("\n");
dump_proc(f, indent + " ", it.second);
dump_proc(f, indent + " ", process);
}
bool first_conn_line = true;
for (auto it = module->connections().begin(); it != module->connections().end(); ++it) {
for (const auto& [lhs, rhs] : module->connections()) {
bool show_conn = !only_selected || design->selected_whole_module(module->name);
if (!show_conn) {
RTLIL::SigSpec sigs = it->first;
sigs.append(it->second);
RTLIL::SigSpec sigs = lhs;
sigs.append(rhs);
for (auto &c : sigs.chunks()) {
if (c.wire == NULL || !design->selected(module, c.wire))
continue;
@ -371,14 +360,14 @@ void RTLIL_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Modu
if (show_conn) {
if (only_selected && first_conn_line)
f << stringf("\n");
dump_conn(f, indent + " ", it->first, it->second);
dump_conn(f, indent + " ", lhs, rhs);
first_conn_line = false;
}
}
}
if (print_header)
f << stringf("%s" "end\n", indent.c_str());
f << stringf("%s" "end\n", indent);
}
void RTLIL_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n)
@ -387,7 +376,7 @@ void RTLIL_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool onl
if (!flag_m) {
int count_selected_mods = 0;
for (auto module : design->modules()) {
for (auto* module : design->modules()) {
if (design->selected_whole_module(module->name))
flag_m = true;
if (design->selected(module))
@ -403,7 +392,7 @@ void RTLIL_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool onl
f << stringf("autoidx %d\n", autoidx);
}
for (auto module : design->modules()) {
for (const auto& [_, module] : reversed(design->modules_)) {
if (!only_selected || design->selected(module)) {
if (only_selected)
f << stringf("\n");
@ -431,10 +420,14 @@ struct RTLILBackend : public Backend {
log(" -selected\n");
log(" only write selected parts of the design.\n");
log("\n");
log(" -sort\n");
log(" sort design in-place (used to be default).\n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
{
bool selected = false;
bool do_sort = false;
log_header(design, "Executing RTLIL backend.\n");
@ -445,33 +438,24 @@ struct RTLILBackend : public Backend {
selected = true;
continue;
}
if (arg == "-sort") {
do_sort = true;
continue;
}
break;
}
extra_args(f, filename, args, argidx);
design->sort();
log("Output filename: %s\n", filename);
log("Output filename: %s\n", filename.c_str());
*f << stringf("# Generated by %s\n", yosys_version_str);
if (do_sort)
design->sort();
*f << stringf("# Generated by %s\n", yosys_maybe_version());
RTLIL_BACKEND::dump_design(*f, design, selected, true, false);
}
} RTLILBackend;
struct IlangBackend : public Backend {
IlangBackend() : Backend("ilang", "(deprecated) alias of write_rtlil") { }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log("See `help write_rtlil`.\n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
{
RTLILBackend.execute(f, filename, args, design);
}
} IlangBackend;
struct DumpPass : public Pass {
DumpPass() : Pass("dump", "print parts of the design in RTLIL format") { }
void help() override
@ -530,14 +514,15 @@ struct DumpPass : public Pass {
std::ostream *f;
std::stringstream buf;
bool empty = filename.empty();
if (!filename.empty()) {
if (!empty) {
rewrite_filename(filename);
std::ofstream *ff = new std::ofstream;
ff->open(filename.c_str(), append ? std::ofstream::app : std::ofstream::trunc);
ff->open(filename, append ? std::ofstream::app : std::ofstream::trunc);
if (ff->fail()) {
delete ff;
log_error("Can't open file `%s' for writing: %s\n", filename.c_str(), strerror(errno));
log_error("Can't open file `%s' for writing: %s\n", filename, strerror(errno));
}
f = ff;
} else {
@ -546,10 +531,10 @@ struct DumpPass : public Pass {
RTLIL_BACKEND::dump_design(*f, design, true, flag_m, flag_n);
if (!filename.empty()) {
if (!empty) {
delete f;
} else {
log("%s", buf.str().c_str());
log("%s", buf.str());
}
}
} DumpPass;

View file

@ -31,6 +31,7 @@
YOSYS_NAMESPACE_BEGIN
namespace RTLIL_BACKEND {
void dump_attributes(std::ostream &f, std::string indent, const RTLIL::AttrObject *obj);
void dump_const(std::ostream &f, const RTLIL::Const &data, int width = -1, int offset = 0, bool autoint = true);
void dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, bool autoint = true);
void dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, bool autoint = true);

View file

@ -218,8 +218,8 @@ struct SimplecWorker
s[i] -= 'a' - 'A';
util_declarations.push_back("");
util_declarations.push_back(stringf("#ifndef %s", s.c_str()));
util_declarations.push_back(stringf("#define %s", s.c_str()));
util_declarations.push_back(stringf("#ifndef %s", s));
util_declarations.push_back(stringf("#define %s", s));
}
string util_get_bit(const string &signame, int n, int idx)
@ -232,33 +232,33 @@ struct SimplecWorker
if (generated_utils.count(util_name) == 0)
{
util_ifdef_guard(util_name);
util_declarations.push_back(stringf("static inline bool %s(const %s *sig)", util_name.c_str(), sigtype(n).c_str()));
util_declarations.push_back(stringf("static inline bool %s(const %s *sig)", util_name, sigtype(n)));
util_declarations.push_back(stringf("{"));
int word_idx = idx / max_uintsize, word_offset = idx % max_uintsize;
string value_name = stringf("value_%d_%d", std::min(n-1, (word_idx+1)*max_uintsize-1), word_idx*max_uintsize);
util_declarations.push_back(stringf(" return (sig->%s >> %d) & 1;", value_name.c_str(), word_offset));
util_declarations.push_back(stringf(" return (sig->%s >> %d) & 1;", value_name, word_offset));
util_declarations.push_back(stringf("}"));
util_declarations.push_back(stringf("#endif"));
generated_utils.insert(util_name);
}
return stringf("%s(&%s)", util_name.c_str(), signame.c_str());
return stringf("%s(&%s)", util_name, signame);
}
string util_set_bit(const string &signame, int n, int idx, const string &expr)
{
if (n == 1 && idx == 0)
return stringf(" %s.value_0_0 = %s;", signame.c_str(), expr.c_str());
return stringf(" %s.value_0_0 = %s;", signame, expr);
string util_name = stringf("yosys_simplec_set_bit_%d_of_%d", idx, n);
if (generated_utils.count(util_name) == 0)
{
util_ifdef_guard(util_name);
util_declarations.push_back(stringf("static inline void %s(%s *sig, bool value)", util_name.c_str(), sigtype(n).c_str()));
util_declarations.push_back(stringf("static inline void %s(%s *sig, bool value)", util_name, sigtype(n)));
util_declarations.push_back(stringf("{"));
int word_idx = idx / max_uintsize, word_offset = idx % max_uintsize;
@ -266,9 +266,9 @@ struct SimplecWorker
#if 0
util_declarations.push_back(stringf(" if (value)"));
util_declarations.push_back(stringf(" sig->%s |= 1UL << %d;", value_name.c_str(), word_offset));
util_declarations.push_back(stringf(" sig->%s |= 1UL << %d;", value_name, word_offset));
util_declarations.push_back(stringf(" else"));
util_declarations.push_back(stringf(" sig->%s &= ~(1UL << %d);", value_name.c_str(), word_offset));
util_declarations.push_back(stringf(" sig->%s &= ~(1UL << %d);", value_name, word_offset));
#else
util_declarations.push_back(stringf(" sig->%s = (sig->%s & ~((uint%d_t)1 << %d)) | ((uint%d_t)value << %d);",
value_name.c_str(), value_name.c_str(), max_uintsize, word_offset, max_uintsize, word_offset));
@ -279,7 +279,7 @@ struct SimplecWorker
generated_utils.insert(util_name);
}
return stringf(" %s(&%s, %s);", util_name.c_str(), signame.c_str(), expr.c_str());
return stringf(" %s(&%s, %s);", util_name, signame, expr);
}
void create_module_struct(Module *mod)
@ -339,38 +339,38 @@ struct SimplecWorker
for (int i = 0; i < GetSize(topo.sorted); i++)
topoidx[mod->cell(topo.sorted[i])] = i;
string ifdef_name = stringf("yosys_simplec_%s_state_t", cid(mod->name).c_str());
string ifdef_name = stringf("yosys_simplec_%s_state_t", cid(mod->name));
for (int i = 0; i < GetSize(ifdef_name); i++)
if ('a' <= ifdef_name[i] && ifdef_name[i] <= 'z')
ifdef_name[i] -= 'a' - 'A';
struct_declarations.push_back("");
struct_declarations.push_back(stringf("#ifndef %s", ifdef_name.c_str()));
struct_declarations.push_back(stringf("#define %s", ifdef_name.c_str()));
struct_declarations.push_back(stringf("struct %s_state_t", cid(mod->name).c_str()));
struct_declarations.push_back(stringf("#ifndef %s", ifdef_name));
struct_declarations.push_back(stringf("#define %s", ifdef_name));
struct_declarations.push_back(stringf("struct %s_state_t", cid(mod->name)));
struct_declarations.push_back("{");
struct_declarations.push_back(" // Input Ports");
for (Wire *w : mod->wires())
if (w->port_input)
struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width).c_str(), cid(w->name).c_str(), log_id(w)));
struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width), cid(w->name), log_id(w)));
struct_declarations.push_back("");
struct_declarations.push_back(" // Output Ports");
for (Wire *w : mod->wires())
if (!w->port_input && w->port_output)
struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width).c_str(), cid(w->name).c_str(), log_id(w)));
struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width), cid(w->name), log_id(w)));
struct_declarations.push_back("");
struct_declarations.push_back(" // Internal Wires");
for (Wire *w : mod->wires())
if (!w->port_input && !w->port_output)
struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width).c_str(), cid(w->name).c_str(), log_id(w)));
struct_declarations.push_back(stringf(" %s %s; // %s", sigtype(w->width), cid(w->name), log_id(w)));
for (Cell *c : mod->cells())
if (design->module(c->type))
struct_declarations.push_back(stringf(" struct %s_state_t %s; // %s", cid(c->type).c_str(), cid(c->name).c_str(), log_id(c)));
struct_declarations.push_back(stringf(" struct %s_state_t %s; // %s", cid(c->type), cid(c->name), log_id(c)));
struct_declarations.push_back(stringf("};"));
struct_declarations.push_back("#endif");
@ -407,14 +407,14 @@ struct SimplecWorker
string b_expr = b.wire ? util_get_bit(work->prefix + cid(b.wire->name), b.wire->width, b.offset) : b.data ? "1" : "0";
string expr;
if (cell->type == ID($_AND_)) expr = stringf("%s & %s", a_expr.c_str(), b_expr.c_str());
if (cell->type == ID($_NAND_)) expr = stringf("!(%s & %s)", a_expr.c_str(), b_expr.c_str());
if (cell->type == ID($_OR_)) expr = stringf("%s | %s", a_expr.c_str(), b_expr.c_str());
if (cell->type == ID($_NOR_)) expr = stringf("!(%s | %s)", a_expr.c_str(), b_expr.c_str());
if (cell->type == ID($_XOR_)) expr = stringf("%s ^ %s", a_expr.c_str(), b_expr.c_str());
if (cell->type == ID($_XNOR_)) expr = stringf("!(%s ^ %s)", a_expr.c_str(), b_expr.c_str());
if (cell->type == ID($_ANDNOT_)) expr = stringf("%s & (!%s)", a_expr.c_str(), b_expr.c_str());
if (cell->type == ID($_ORNOT_)) expr = stringf("%s | (!%s)", a_expr.c_str(), b_expr.c_str());
if (cell->type == ID($_AND_)) expr = stringf("%s & %s", a_expr, b_expr);
if (cell->type == ID($_NAND_)) expr = stringf("!(%s & %s)", a_expr, b_expr);
if (cell->type == ID($_OR_)) expr = stringf("%s | %s", a_expr, b_expr);
if (cell->type == ID($_NOR_)) expr = stringf("!(%s | %s)", a_expr, b_expr);
if (cell->type == ID($_XOR_)) expr = stringf("%s ^ %s", a_expr, b_expr);
if (cell->type == ID($_XNOR_)) expr = stringf("!(%s ^ %s)", a_expr, b_expr);
if (cell->type == ID($_ANDNOT_)) expr = stringf("%s & (!%s)", a_expr, b_expr);
if (cell->type == ID($_ORNOT_)) expr = stringf("%s | (!%s)", a_expr, b_expr);
log_assert(y.wire);
funct_declarations.push_back(util_set_bit(work->prefix + cid(y.wire->name), y.wire->width, y.offset, expr) +
@ -436,8 +436,8 @@ struct SimplecWorker
string c_expr = c.wire ? util_get_bit(work->prefix + cid(c.wire->name), c.wire->width, c.offset) : c.data ? "1" : "0";
string expr;
if (cell->type == ID($_AOI3_)) expr = stringf("!((%s & %s) | %s)", a_expr.c_str(), b_expr.c_str(), c_expr.c_str());
if (cell->type == ID($_OAI3_)) expr = stringf("!((%s | %s) & %s)", a_expr.c_str(), b_expr.c_str(), c_expr.c_str());
if (cell->type == ID($_AOI3_)) expr = stringf("!((%s & %s) | %s)", a_expr, b_expr, c_expr);
if (cell->type == ID($_OAI3_)) expr = stringf("!((%s | %s) & %s)", a_expr, b_expr, c_expr);
log_assert(y.wire);
funct_declarations.push_back(util_set_bit(work->prefix + cid(y.wire->name), y.wire->width, y.offset, expr) +
@ -461,8 +461,8 @@ struct SimplecWorker
string d_expr = d.wire ? util_get_bit(work->prefix + cid(d.wire->name), d.wire->width, d.offset) : d.data ? "1" : "0";
string expr;
if (cell->type == ID($_AOI4_)) expr = stringf("!((%s & %s) | (%s & %s))", a_expr.c_str(), b_expr.c_str(), c_expr.c_str(), d_expr.c_str());
if (cell->type == ID($_OAI4_)) expr = stringf("!((%s | %s) & (%s | %s))", a_expr.c_str(), b_expr.c_str(), c_expr.c_str(), d_expr.c_str());
if (cell->type == ID($_AOI4_)) expr = stringf("!((%s & %s) | (%s & %s))", a_expr, b_expr, c_expr, d_expr);
if (cell->type == ID($_OAI4_)) expr = stringf("!((%s | %s) & (%s | %s))", a_expr, b_expr, c_expr, d_expr);
log_assert(y.wire);
funct_declarations.push_back(util_set_bit(work->prefix + cid(y.wire->name), y.wire->width, y.offset, expr) +
@ -484,9 +484,9 @@ struct SimplecWorker
string s_expr = s.wire ? util_get_bit(work->prefix + cid(s.wire->name), s.wire->width, s.offset) : s.data ? "1" : "0";
// casts to bool are a workaround for CBMC bug (https://github.com/diffblue/cbmc/issues/933)
string expr = stringf("%s ? %s(bool)%s : %s(bool)%s", s_expr.c_str(),
cell->type == ID($_NMUX_) ? "!" : "", b_expr.c_str(),
cell->type == ID($_NMUX_) ? "!" : "", a_expr.c_str());
string expr = stringf("%s ? %s(bool)%s : %s(bool)%s", s_expr,
cell->type == ID($_NMUX_) ? "!" : "", b_expr,
cell->type == ID($_NMUX_) ? "!" : "", a_expr);
log_assert(y.wire);
funct_declarations.push_back(util_set_bit(work->prefix + cid(y.wire->name), y.wire->width, y.offset, expr) +
@ -504,7 +504,7 @@ struct SimplecWorker
while (work->dirty)
{
if (verbose && (!work->dirty_bits.empty() || !work->dirty_cells.empty()))
log(" In %s:\n", work->log_prefix.c_str());
log(" In %s:\n", work->log_prefix);
while (!work->dirty_bits.empty() || !work->dirty_cells.empty())
{
@ -517,8 +517,8 @@ struct SimplecWorker
if (chunk.wire == nullptr)
continue;
if (verbose)
log(" Propagating %s.%s[%d:%d].\n", work->log_prefix.c_str(), log_id(chunk.wire), chunk.offset+chunk.width-1, chunk.offset);
funct_declarations.push_back(stringf(" // Updated signal in %s: %s", work->log_prefix.c_str(), log_signal(chunk)));
log(" Propagating %s.%s[%d:%d].\n", work->log_prefix, log_id(chunk.wire), chunk.offset+chunk.width-1, chunk.offset);
funct_declarations.push_back(stringf(" // Updated signal in %s: %s", work->log_prefix, log_signal(chunk)));
}
for (SigBit bit : dirtysig)
@ -539,7 +539,7 @@ struct SimplecWorker
work->parent->set_dirty(parent_bit);
if (verbose)
log(" Propagating %s.%s[%d] -> %s.%s[%d].\n", work->log_prefix.c_str(), log_id(bit.wire), bit.offset,
log(" Propagating %s.%s[%d] -> %s.%s[%d].\n", work->log_prefix, log_id(bit.wire), bit.offset,
work->parent->log_prefix.c_str(), log_id(parent_bit.wire), parent_bit.offset);
}
@ -556,11 +556,11 @@ struct SimplecWorker
child->set_dirty(child_bit);
if (verbose)
log(" Propagating %s.%s[%d] -> %s.%s.%s[%d].\n", work->log_prefix.c_str(), log_id(bit.wire), bit.offset,
log(" Propagating %s.%s[%d] -> %s.%s.%s[%d].\n", work->log_prefix, log_id(bit.wire), bit.offset,
work->log_prefix.c_str(), log_id(std::get<0>(port)), log_id(child_bit.wire), child_bit.offset);
} else {
if (verbose)
log(" Marking cell %s.%s (via %s.%s[%d]).\n", work->log_prefix.c_str(), log_id(std::get<0>(port)),
log(" Marking cell %s.%s (via %s.%s[%d]).\n", work->log_prefix, log_id(std::get<0>(port)),
work->log_prefix.c_str(), log_id(bit.wire), bit.offset);
work->set_dirty(std::get<0>(port));
}
@ -579,7 +579,7 @@ struct SimplecWorker
string hiername = work->log_prefix + "." + log_id(cell);
if (verbose)
log(" Evaluating %s (%s, best of %d).\n", hiername.c_str(), log_id(cell->type), GetSize(work->dirty_cells));
log(" Evaluating %s (%s, best of %d).\n", hiername, log_id(cell->type), GetSize(work->dirty_cells));
if (activated_cells.count(hiername))
reactivated_cells.insert(hiername);
@ -630,13 +630,13 @@ struct SimplecWorker
void make_func(HierDirtyFlags *work, const string &func_name, const vector<string> &preamble)
{
log("Generating function %s():\n", func_name.c_str());
log("Generating function %s():\n", func_name);
activated_cells.clear();
reactivated_cells.clear();
funct_declarations.push_back("");
funct_declarations.push_back(stringf("static void %s(struct %s_state_t *state)", func_name.c_str(), cid(work->module->name).c_str()));
funct_declarations.push_back(stringf("static void %s(struct %s_state_t *state)", func_name, cid(work->module->name)));
funct_declarations.push_back("{");
for (auto &line : preamble)
funct_declarations.push_back(line);
@ -657,7 +657,7 @@ struct SimplecWorker
{
SigSpec sig = sigmaps.at(module)(w);
Const val = w->attributes.at(ID::init);
val.bits.resize(GetSize(sig), State::Sx);
val.resize(GetSize(sig), State::Sx);
for (int i = 0; i < GetSize(sig); i++)
if (val[i] == State::S0 || val[i] == State::S1) {

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -ex
../../yosys -p 'synth -top test; write_simplec -verbose -i8 test00_uut.c' test00_uut.v
clang -o test00_tb test00_tb.c

View file

@ -7,6 +7,7 @@ ifneq ($(CONFIG),emcc)
# MSYS targets support yosys-smtbmc, but require a launcher script
ifeq ($(CONFIG),$(filter $(CONFIG),msys2 msys2-64))
TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc.exe $(PROGRAM_PREFIX)yosys-smtbmc-script.py
TARGETS += $(PROGRAM_PREFIX)yosys-witness.exe $(PROGRAM_PREFIX)yosys-witness-script.py
# Needed to find the Python interpreter for yosys-smtbmc scripts.
# Override if necessary, it is only used for msys2 targets.
PYTHON := $(shell cygpath -w -m $(PREFIX)/bin/python3)
@ -15,18 +16,31 @@ $(PROGRAM_PREFIX)yosys-smtbmc-script.py: backends/smt2/smtbmc.py
$(P) sed -e 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' \
-e "s|#!/usr/bin/env python3|#!$(PYTHON)|" < $< > $@
$(PROGRAM_PREFIX)yosys-witness-script.py: backends/smt2/witness.py
$(P) sed -e 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' \
-e "s|#!/usr/bin/env python3|#!$(PYTHON)|" < $< > $@
$(PROGRAM_PREFIX)yosys-smtbmc.exe: misc/launcher.c $(PROGRAM_PREFIX)yosys-smtbmc-script.py
$(P) $(CXX) -DGUI=0 -O -s -o $@ $<
$(PROGRAM_PREFIX)yosys-witness.exe: misc/launcher.c $(PROGRAM_PREFIX)yosys-witness-script.py
$(P) $(CXX) -DGUI=0 -O -s -o $@ $<
# Other targets
else
TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc
TARGETS += $(PROGRAM_PREFIX)yosys-smtbmc $(PROGRAM_PREFIX)yosys-witness
$(PROGRAM_PREFIX)yosys-smtbmc: backends/smt2/smtbmc.py
$(P) sed 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' < $< > $@.new
$(Q) chmod +x $@.new
$(Q) mv $@.new $@
$(PROGRAM_PREFIX)yosys-witness: backends/smt2/witness.py
$(P) sed 's|##yosys-sys-path##|sys.path += [os.path.dirname(os.path.realpath(__file__)) + p for p in ["/share/python3", "/../share/$(PROGRAM_PREFIX)yosys/python3"]]|;' < $< > $@.new
$(Q) chmod +x $@.new
$(Q) mv $@.new $@
endif
$(eval $(call add_share_file,share/python3,backends/smt2/smtio.py))
$(eval $(call add_share_file,share/python3,backends/smt2/ywio.py))
endif
endif

View file

@ -23,6 +23,8 @@
#include "kernel/celltypes.h"
#include "kernel/log.h"
#include "kernel/mem.h"
#include "libs/json11/json11.hpp"
#include "kernel/utils.h"
#include <string>
USING_YOSYS_NAMESPACE
@ -80,27 +82,27 @@ struct Smt2Worker
if (statebv)
{
if (width == 0) {
decl_str = stringf("(define-fun |%s| ((state |%s_s|)) Bool (= ((_ extract %d %d) state) #b1))", name.c_str(), get_id(module), statebv_width, statebv_width);
decl_str = stringf("(define-fun |%s| ((state |%s_s|)) Bool (= ((_ extract %d %d) state) #b1))", name, get_id(module), statebv_width, statebv_width);
statebv_width += 1;
} else {
decl_str = stringf("(define-fun |%s| ((state |%s_s|)) (_ BitVec %d) ((_ extract %d %d) state))", name.c_str(), get_id(module), width, statebv_width+width-1, statebv_width);
decl_str = stringf("(define-fun |%s| ((state |%s_s|)) (_ BitVec %d) ((_ extract %d %d) state))", name, get_id(module), width, statebv_width+width-1, statebv_width);
statebv_width += width;
}
}
else if (statedt)
{
if (width == 0) {
decl_str = stringf(" (|%s| Bool)", name.c_str());
decl_str = stringf(" (|%s| Bool)", name);
} else {
decl_str = stringf(" (|%s| (_ BitVec %d))", name.c_str(), width);
decl_str = stringf(" (|%s| (_ BitVec %d))", name, width);
}
}
else
{
if (width == 0) {
decl_str = stringf("(declare-fun |%s| (|%s_s|) Bool)", name.c_str(), get_id(module));
decl_str = stringf("(declare-fun |%s| (|%s_s|) Bool)", name, get_id(module));
} else {
decl_str = stringf("(declare-fun |%s| (|%s_s|) (_ BitVec %d))", name.c_str(), get_id(module), width);
decl_str = stringf("(declare-fun |%s| (|%s_s|) (_ BitVec %d))", name, get_id(module), width);
}
}
@ -128,7 +130,7 @@ struct Smt2Worker
for (auto &mem : memories)
{
if (is_smtlib2_module)
log_error("Memory %s.%s not allowed in module with smtlib2_module attribute", get_id(module), mem.memid.c_str());
log_error("Memory %s.%s not allowed in module with smtlib2_module attribute", get_id(module), mem.memid);
mem.narrow();
mem_dict[mem.memid] = &mem;
@ -239,6 +241,17 @@ struct Smt2Worker
clock_negedge.erase(bit);
}
for (auto wire : module->wires())
{
auto gclk_attr = wire->attributes.find(ID::replaced_by_gclk);
if (gclk_attr != wire->attributes.end()) {
if (gclk_attr->second == State::S1)
clock_posedge.insert(sigmap(wire));
else if (gclk_attr->second == State::S0)
clock_negedge.insert(sigmap(wire));
}
}
for (auto wire : module->wires())
{
if (!wire->port_input || GetSize(wire) != 1)
@ -317,13 +330,14 @@ struct Smt2Worker
{
sigmap.apply(bit);
if (bit_driver.count(bit)) {
export_cell(bit_driver.at(bit));
sigmap.apply(bit);
}
if (bit.wire == nullptr)
return bit == RTLIL::State::S1 ? "true" : "false";
if (bit_driver.count(bit))
export_cell(bit_driver.at(bit));
sigmap.apply(bit);
if (fcache.count(bit) == 0) {
if (verbose) log("%*s-> external bool: %s\n", 2+2*GetSize(recursive_cells), "",
log_signal(bit));
@ -369,7 +383,7 @@ struct Smt2Worker
}
if (fcache.count(sig[i]) && fcache.at(sig[i]).second == -1) {
subexpr.push_back(stringf("(ite %s #b1 #b0)", get_bool(sig[i], state_name).c_str()));
subexpr.push_back(stringf("(ite %s #b1 #b0)", get_bool(sig[i], state_name)));
continue;
}
@ -446,11 +460,14 @@ struct Smt2Worker
{
RTLIL::SigSpec sig_a, sig_b;
RTLIL::SigSpec sig_y = sigmap(cell->getPort(ID::Y));
bool is_signed = cell->getParam(ID::A_SIGNED).as_bool();
bool is_signed = type == 'U' ? false : cell->getParam(ID::A_SIGNED).as_bool();
int width = GetSize(sig_y);
if (type == 's' || type == 'd' || type == 'b') {
width = max(width, GetSize(cell->getPort(ID::A)));
if (type == 's' || type == 'S' || type == 'd' || type == 'b') {
if (type == 'b')
width = GetSize(cell->getPort(ID::A));
else
width = max(width, GetSize(cell->getPort(ID::A)));
if (cell->hasPort(ID::B))
width = max(width, GetSize(cell->getPort(ID::B)));
}
@ -462,7 +479,7 @@ struct Smt2Worker
if (cell->hasPort(ID::B)) {
sig_b = cell->getPort(ID::B);
sig_b.extend_u0(width, is_signed && !(type == 's'));
sig_b.extend_u0(width, (type == 'S') || (is_signed && !(type == 's')));
}
std::string processed_expr;
@ -471,13 +488,14 @@ struct Smt2Worker
if (ch == 'A') processed_expr += get_bv(sig_a);
else if (ch == 'B') processed_expr += get_bv(sig_b);
else if (ch == 'P') processed_expr += get_bv(cell->getPort(ID::B));
else if (ch == 'S') processed_expr += get_bv(cell->getPort(ID::S));
else if (ch == 'L') processed_expr += is_signed ? "a" : "l";
else if (ch == 'U') processed_expr += is_signed ? "s" : "u";
else processed_expr += ch;
}
if (width != GetSize(sig_y) && type != 'b')
processed_expr = stringf("((_ extract %d 0) %s)", GetSize(sig_y)-1, processed_expr.c_str());
processed_expr = stringf("((_ extract %d 0) %s)", GetSize(sig_y)-1, processed_expr);
if (verbose)
log("%*s-> import cell: %s\n", 2+2*GetSize(recursive_cells), "", log_id(cell));
@ -547,6 +565,9 @@ struct Smt2Worker
if (cell->type.in(ID($_FF_), ID($_DFF_P_), ID($_DFF_N_)))
{
registers.insert(cell);
SigBit q_bit = cell->getPort(ID::Q);
if (q_bit.is_wire())
decls.push_back(witness_signal("reg", 1, 0, "", idcounter, q_bit.wire));
makebits(stringf("%s#%d", get_id(module), idcounter), 0, log_signal(cell->getPort(ID::Q)));
register_bool(cell->getPort(ID::Q), idcounter++);
recursive_cells.erase(cell);
@ -577,31 +598,48 @@ struct Smt2Worker
if (cell->type.in(ID($ff), ID($dff)))
{
registers.insert(cell);
int smtoffset = 0;
for (auto chunk : cell->getPort(ID::Q).chunks()) {
if (chunk.is_wire())
decls.push_back(witness_signal("reg", chunk.width, chunk.offset, "", idcounter, chunk.wire, smtoffset));
smtoffset += chunk.width;
}
makebits(stringf("%s#%d", get_id(module), idcounter), GetSize(cell->getPort(ID::Q)), log_signal(cell->getPort(ID::Q)));
register_bv(cell->getPort(ID::Q), idcounter++);
recursive_cells.erase(cell);
return;
}
if (cell->type.in(ID($anyconst), ID($anyseq), ID($allconst), ID($allseq)))
if (cell->type.in(ID($anyconst), ID($anyseq), ID($anyinit), ID($allconst), ID($allseq)))
{
auto QY = cell->type == ID($anyinit) ? ID::Q : ID::Y;
registers.insert(cell);
string infostr = cell->attributes.count(ID::src) ? cell->attributes.at(ID::src).decode_string().c_str() : get_id(cell);
if (cell->attributes.count(ID::reg))
infostr += " " + cell->attributes.at(ID::reg).decode_string();
decls.push_back(stringf("; yosys-smt2-%s %s#%d %d %s\n", cell->type.c_str() + 1, get_id(module), idcounter, GetSize(cell->getPort(ID::Y)), infostr.c_str()));
if (cell->getPort(ID::Y).is_wire() && cell->getPort(ID::Y).as_wire()->get_bool_attribute(ID::maximize)){
decls.push_back(stringf("; yosys-smt2-%s %s#%d %d %s\n", cell->type.c_str() + 1, get_id(module), idcounter, GetSize(cell->getPort(QY)), infostr));
if (cell->getPort(QY).is_wire() && cell->getPort(QY).as_wire()->get_bool_attribute(ID::maximize)){
decls.push_back(stringf("; yosys-smt2-maximize %s#%d\n", get_id(module), idcounter));
log("Wire %s is maximized\n", cell->getPort(ID::Y).as_wire()->name.str().c_str());
log("Wire %s is maximized\n", cell->getPort(QY).as_wire()->name.str());
}
else if (cell->getPort(ID::Y).is_wire() && cell->getPort(ID::Y).as_wire()->get_bool_attribute(ID::minimize)){
else if (cell->getPort(QY).is_wire() && cell->getPort(QY).as_wire()->get_bool_attribute(ID::minimize)){
decls.push_back(stringf("; yosys-smt2-minimize %s#%d\n", get_id(module), idcounter));
log("Wire %s is minimized\n", cell->getPort(ID::Y).as_wire()->name.str().c_str());
log("Wire %s is minimized\n", cell->getPort(QY).as_wire()->name.str());
}
makebits(stringf("%s#%d", get_id(module), idcounter), GetSize(cell->getPort(ID::Y)), log_signal(cell->getPort(ID::Y)));
bool init_only = cell->type.in(ID($anyconst), ID($anyinit), ID($allconst));
bool clk2fflogic = cell->type == ID($anyinit) && cell->get_bool_attribute(ID(clk2fflogic));
int smtoffset = 0;
for (auto chunk : cell->getPort(clk2fflogic ? ID::D : QY).chunks()) {
if (chunk.is_wire())
decls.push_back(witness_signal(init_only ? "init" : "seq", chunk.width, chunk.offset, "", idcounter, chunk.wire, smtoffset));
smtoffset += chunk.width;
}
makebits(stringf("%s#%d", get_id(module), idcounter), GetSize(cell->getPort(QY)), log_signal(cell->getPort(QY)));
if (cell->type == ID($anyseq))
ex_input_eq.push_back(stringf(" (= (|%s#%d| state) (|%s#%d| other_state))", get_id(module), idcounter, get_id(module), idcounter));
register_bv(cell->getPort(ID::Y), idcounter++);
register_bv(cell->getPort(QY), idcounter++);
recursive_cells.erase(cell);
return;
}
@ -611,6 +649,9 @@ struct Smt2Worker
if (cell->type == ID($xor)) return export_bvop(cell, "(bvxor A B)");
if (cell->type == ID($xnor)) return export_bvop(cell, "(bvxnor A B)");
if (cell->type == ID($bweqx)) return export_bvop(cell, "(bvxnor A B)", 'U');
if (cell->type == ID($bwmux)) return export_bvop(cell, "(bvor (bvand A (bvnot S)) (bvand B S))", 'U');
if (cell->type == ID($shl)) return export_bvop(cell, "(bvshl A B)", 's');
if (cell->type == ID($shr)) return export_bvop(cell, "(bvlshr A B)", 's');
if (cell->type == ID($sshl)) return export_bvop(cell, "(bvshl A B)", 's');
@ -619,8 +660,8 @@ struct Smt2Worker
if (cell->type.in(ID($shift), ID($shiftx))) {
if (cell->getParam(ID::B_SIGNED).as_bool()) {
return export_bvop(cell, stringf("(ite (bvsge P #b%0*d) "
"(bvlshr A B) (bvlshr A (bvneg B)))",
GetSize(cell->getPort(ID::B)), 0), 's');
"(bvlshr A B) (bvshl A (bvneg B)))",
GetSize(cell->getPort(ID::B)), 0), 'S'); // type 'S' sign extends B
} else {
return export_bvop(cell, "(bvlshr A B)", 's');
}
@ -681,7 +722,7 @@ struct Smt2Worker
2*GetSize(cell->getPort(ID::A).chunks()) < GetSize(cell->getPort(ID::A))) {
bool is_and = cell->type == ID($reduce_and);
string bits(GetSize(cell->getPort(ID::A)), is_and ? '1' : '0');
return export_bvop(cell, stringf("(%s A #b%s)", is_and ? "=" : "distinct", bits.c_str()), 'b');
return export_bvop(cell, stringf("(%s A #b%s)", is_and ? "=" : "distinct", bits), 'b');
}
if (cell->type == ID($reduce_and)) return export_reduce(cell, "(and A)", true);
@ -705,7 +746,7 @@ struct Smt2Worker
get_bv(sig_s);
for (int i = 0; i < GetSize(sig_s); i++)
processed_expr = stringf("(ite %s %s %s)", get_bool(sig_s[i]).c_str(),
processed_expr = stringf("(ite %s %s %s)", get_bool(sig_s[i]),
get_bv(sig_b.extract(i*width, width)).c_str(), processed_expr.c_str());
if (verbose)
@ -734,7 +775,7 @@ struct Smt2Worker
int arrayid = idcounter++;
memarrays[mem] = arrayid;
int abits = ceil_log2(mem->size);
int abits = max(1, ceil_log2(mem->size));
bool has_sync_wr = false;
bool has_async_wr = false;
@ -748,6 +789,7 @@ struct Smt2Worker
log_error("Memory %s.%s has mixed clocked/nonclocked write ports. This is not supported by \"write_smt2\".\n", log_id(cell), log_id(module));
decls.push_back(stringf("; yosys-smt2-memory %s %d %d %d %d %s\n", get_id(mem->memid), abits, mem->width, GetSize(mem->rd_ports), GetSize(mem->wr_ports), has_async_wr ? "async" : "sync"));
decls.push_back(witness_memory(get_id(mem->memid), cell, mem));
string memstate;
if (has_async_wr) {
@ -840,6 +882,7 @@ struct Smt2Worker
if (m != nullptr)
{
decls.push_back(stringf("; yosys-smt2-cell %s %s\n", get_id(cell->type), get_id(cell->name)));
decls.push_back(witness_cell(get_id(cell->name), cell));
string cell_state = stringf("(|%s_h %s| state)", get_id(module), get_id(cell->name));
for (auto &conn : cell->connections())
@ -920,7 +963,7 @@ struct Smt2Worker
pool<SigBit> reg_bits;
for (auto cell : module->cells())
if (cell->type.in(ID($ff), ID($dff), ID($_FF_), ID($_DFF_P_), ID($_DFF_N_))) {
if (cell->type.in(ID($ff), ID($dff), ID($_FF_), ID($_DFF_P_), ID($_DFF_N_), ID($anyinit))) {
// not using sigmap -- we want the net directly at the dff output
for (auto bit : cell->getPort(ID::Q))
reg_bits.insert(bit);
@ -938,14 +981,19 @@ struct Smt2Worker
for (auto wire : module->wires()) {
bool is_register = false;
for (auto bit : SigSpec(wire))
bool contains_clock = false;
for (auto bit : SigSpec(wire)) {
if (reg_bits.count(bit))
is_register = true;
auto sig_bit = sigmap(bit);
if (clock_posedge.count(sig_bit) || clock_negedge.count(sig_bit))
contains_clock = true;
}
bool is_smtlib2_comb_expr = wire->has_attribute(ID::smtlib2_comb_expr);
if (is_smtlib2_comb_expr && !is_smtlib2_module)
log_error("smtlib2_comb_expr is only valid in a module with the smtlib2_module attribute: wire %s.%s", log_id(module),
log_id(wire));
if (wire->port_id || is_register || wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic())) {
if (wire->port_id || is_register || contains_clock || wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic())) {
RTLIL::SigSpec sig = sigmap(wire);
std::vector<std::string> comments;
if (wire->port_input)
@ -956,9 +1004,20 @@ struct Smt2Worker
comments.push_back(stringf("; yosys-smt2-register %s %d\n", get_id(wire), wire->width));
if (wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic()))
comments.push_back(stringf("; yosys-smt2-wire %s %d\n", get_id(wire), wire->width));
if (GetSize(wire) == 1 && (clock_posedge.count(sig) || clock_negedge.count(sig)))
if (contains_clock && GetSize(wire) == 1 && (clock_posedge.count(sig) || clock_negedge.count(sig)))
comments.push_back(stringf("; yosys-smt2-clock %s%s%s\n", get_id(wire),
clock_posedge.count(sig) ? " posedge" : "", clock_negedge.count(sig) ? " negedge" : ""));
if (wire->port_input && contains_clock) {
for (int i = 0; i < GetSize(sig); i++) {
bool is_posedge = clock_posedge.count(sig[i]);
bool is_negedge = clock_negedge.count(sig[i]);
if (is_posedge != is_negedge)
comments.push_back(witness_signal(
is_posedge ? "posedge" : "negedge", 1, i, get_id(wire), -1, wire));
}
}
if (wire->port_input)
comments.push_back(witness_signal("input", wire->width, 0, get_id(wire), -1, wire));
std::string smtlib2_comb_expr;
if (is_smtlib2_comb_expr) {
smtlib2_comb_expr =
@ -968,6 +1027,8 @@ struct Smt2Worker
if (!bvmode && GetSize(sig) > 1)
log_error("smtlib2_comb_expr is unsupported on multi-bit wires when -nobv is specified: wire %s.%s",
log_id(module), log_id(wire));
comments.push_back(witness_signal("blackbox", wire->width, 0, get_id(wire), -1, wire));
}
auto &out_decls = is_smtlib2_comb_expr ? smtlib2_decls : decls;
if (bvmode && GetSize(sig) > 1) {
@ -1018,24 +1079,24 @@ struct Smt2Worker
RTLIL::SigSpec sig = sigmap(wire);
Const val = wire->attributes.at(ID::init);
val.bits.resize(GetSize(sig), State::Sx);
val.resize(GetSize(sig), State::Sx);
if (bvmode && GetSize(sig) > 1) {
Const mask(State::S1, GetSize(sig));
bool use_mask = false;
for (int i = 0; i < GetSize(sig); i++)
if (val[i] != State::S0 && val[i] != State::S1) {
val[i] = State::S0;
mask[i] = State::S0;
val.set(i, State::S0);
mask.set(i, State::S0);
use_mask = true;
}
if (use_mask)
init_list.push_back(stringf("(= (bvand %s #b%s) #b%s) ; %s", get_bv(sig).c_str(), mask.as_string().c_str(), val.as_string().c_str(), get_id(wire)));
init_list.push_back(stringf("(= (bvand %s #b%s) #b%s) ; %s", get_bv(sig), mask.as_string(), val.as_string(), get_id(wire)));
else
init_list.push_back(stringf("(= %s #b%s) ; %s", get_bv(sig).c_str(), val.as_string().c_str(), get_id(wire)));
init_list.push_back(stringf("(= %s #b%s) ; %s", get_bv(sig), val.as_string(), get_id(wire)));
} else {
for (int i = 0; i < GetSize(sig); i++)
if (val[i] == State::S0 || val[i] == State::S1)
init_list.push_back(stringf("(= %s %s) ; %s", get_bool(sig[i]).c_str(), val[i] == State::S1 ? "true" : "false", get_id(wire)));
init_list.push_back(stringf("(= %s %s) ; %s", get_bool(sig[i]), val[i] == State::S1 ? "true" : "false", get_id(wire)));
}
}
@ -1058,8 +1119,19 @@ struct Smt2Worker
string name_a = get_bool(cell->getPort(ID::A));
string name_en = get_bool(cell->getPort(ID::EN));
if (cell->name[0] == '$' && cell->attributes.count(ID::src))
decls.push_back(stringf("; yosys-smt2-%s %d %s %s\n", cell->type.c_str() + 1, id, get_id(cell), cell->attributes.at(ID::src).decode_string().c_str()));
bool private_name = cell->name[0] == '$';
if (!private_name && cell->has_attribute(ID::hdlname)) {
for (auto const &part : cell->get_hdlname_attribute()) {
if (part == "_witness_") {
private_name = true;
break;
}
}
}
if (private_name && cell->attributes.count(ID::src))
decls.push_back(stringf("; yosys-smt2-%s %d %s %s\n", cell->type.c_str() + 1, id, get_id(cell), cell->attributes.at(ID::src).decode_string()));
else
decls.push_back(stringf("; yosys-smt2-%s %d %s\n", cell->type.c_str() + 1, id, get_id(cell)));
@ -1108,11 +1180,11 @@ struct Smt2Worker
SigSpec sig = sigmap(conn.second);
if (bvmode || GetSize(w) == 1) {
hier.push_back(stringf(" (= %s (|%s_n %s| %s)) ; %s.%s\n", (GetSize(w) > 1 ? get_bv(sig) : get_bool(sig)).c_str(),
hier.push_back(stringf(" (= %s (|%s_n %s| %s)) ; %s.%s\n", (GetSize(w) > 1 ? get_bv(sig) : get_bool(sig)),
get_id(cell->type), get_id(w), cell_state.c_str(), get_id(cell->type), get_id(w)));
} else {
for (int i = 0; i < GetSize(w); i++)
hier.push_back(stringf(" (= %s (|%s_n %s %d| %s)) ; %s.%s[%d]\n", get_bool(sig[i]).c_str(),
hier.push_back(stringf(" (= %s (|%s_n %s %d| %s)) ; %s.%s[%d]\n", get_bool(sig[i]),
get_id(cell->type), get_id(w), i, cell_state.c_str(), get_id(cell->type), get_id(w), i));
}
}
@ -1132,25 +1204,25 @@ struct Smt2Worker
{
std::string expr_d = get_bool(cell->getPort(ID::D));
std::string expr_q = get_bool(cell->getPort(ID::Q), "next_state");
trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d.c_str(), expr_q.c_str(), get_id(cell), log_signal(cell->getPort(ID::Q))));
ex_state_eq.push_back(stringf("(= %s %s)", get_bool(cell->getPort(ID::Q)).c_str(), get_bool(cell->getPort(ID::Q), "other_state").c_str()));
trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d, expr_q, get_id(cell), log_signal(cell->getPort(ID::Q))));
ex_state_eq.push_back(stringf("(= %s %s)", get_bool(cell->getPort(ID::Q)), get_bool(cell->getPort(ID::Q), "other_state")));
}
if (cell->type.in(ID($ff), ID($dff)))
if (cell->type.in(ID($ff), ID($dff), ID($anyinit)))
{
std::string expr_d = get_bv(cell->getPort(ID::D));
std::string expr_q = get_bv(cell->getPort(ID::Q), "next_state");
trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d.c_str(), expr_q.c_str(), get_id(cell), log_signal(cell->getPort(ID::Q))));
ex_state_eq.push_back(stringf("(= %s %s)", get_bv(cell->getPort(ID::Q)).c_str(), get_bv(cell->getPort(ID::Q), "other_state").c_str()));
trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d, expr_q, get_id(cell), log_signal(cell->getPort(ID::Q))));
ex_state_eq.push_back(stringf("(= %s %s)", get_bv(cell->getPort(ID::Q)), get_bv(cell->getPort(ID::Q), "other_state")));
}
if (cell->type.in(ID($anyconst), ID($allconst)))
{
std::string expr_d = get_bv(cell->getPort(ID::Y));
std::string expr_q = get_bv(cell->getPort(ID::Y), "next_state");
trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d.c_str(), expr_q.c_str(), get_id(cell), log_signal(cell->getPort(ID::Y))));
trans.push_back(stringf(" (= %s %s) ; %s %s\n", expr_d, expr_q, get_id(cell), log_signal(cell->getPort(ID::Y))));
if (cell->type == ID($anyconst))
ex_state_eq.push_back(stringf("(= %s %s)", get_bv(cell->getPort(ID::Y)).c_str(), get_bv(cell->getPort(ID::Y), "other_state").c_str()));
ex_state_eq.push_back(stringf("(= %s %s)", get_bv(cell->getPort(ID::Y)), get_bv(cell->getPort(ID::Y), "other_state")));
}
}
@ -1161,7 +1233,7 @@ struct Smt2Worker
{
int arrayid = memarrays.at(mem);
int abits = ceil_log2(mem->size);;
int abits = max(1, ceil_log2(mem->size));
bool has_sync_wr = false;
bool has_async_wr = false;
@ -1269,11 +1341,11 @@ struct Smt2Worker
std::string expr_d = stringf("(|%s#%d#%d| state)", get_id(module), arrayid, GetSize(mem->wr_ports));
std::string expr_q = stringf("(|%s#%d#0| next_state)", get_id(module), arrayid);
trans.push_back(stringf(" (= %s %s) ; %s\n", expr_d.c_str(), expr_q.c_str(), get_id(mem->memid)));
trans.push_back(stringf(" (= %s %s) ; %s\n", expr_d, expr_q, get_id(mem->memid)));
ex_state_eq.push_back(stringf("(= (|%s#%d#0| state) (|%s#%d#0| other_state))", get_id(module), arrayid, get_id(module), arrayid));
if (has_async_wr)
hier.push_back(stringf(" (= %s (|%s| state)) ; %s\n", expr_d.c_str(), final_memstate.c_str(), get_id(mem->memid)));
hier.push_back(stringf(" (= %s (|%s| state)) ; %s\n", expr_d, final_memstate, get_id(mem->memid)));
Const init_data = mem->get_init_data();
@ -1289,10 +1361,10 @@ struct Smt2Worker
for (int k = 0; k < GetSize(initword); k++) {
if (initword[k] == State::S0 || initword[k] == State::S1) {
gen_init_constr = true;
initmask[k] = State::S1;
initmask.set(k, State::S1);
} else {
initmask[k] = State::S0;
initword[k] = State::S0;
initmask.set(k, State::S0);
initword.set(k, State::S0);
}
}
@ -1330,7 +1402,7 @@ struct Smt2Worker
expr = "\n " + ex_state_eq.front() + "\n";
} else {
for (auto &str : ex_state_eq)
expr += stringf("\n %s", str.c_str());
expr += stringf("\n %s", str);
expr += "\n)";
}
}
@ -1343,7 +1415,7 @@ struct Smt2Worker
expr = "\n " + ex_input_eq.front() + "\n";
} else {
for (auto &str : ex_input_eq)
expr += stringf("\n %s", str.c_str());
expr += stringf("\n %s", str);
expr += "\n)";
}
}
@ -1357,7 +1429,7 @@ struct Smt2Worker
assert_expr = "\n " + assert_list.front() + "\n";
} else {
for (auto &str : assert_list)
assert_expr += stringf("\n %s", str.c_str());
assert_expr += stringf("\n %s", str);
assert_expr += "\n)";
}
}
@ -1370,7 +1442,7 @@ struct Smt2Worker
assume_expr = "\n " + assume_list.front() + "\n";
} else {
for (auto &str : assume_list)
assume_expr += stringf("\n %s", str.c_str());
assume_expr += stringf("\n %s", str);
assume_expr += "\n)";
}
}
@ -1383,7 +1455,7 @@ struct Smt2Worker
init_expr = "\n " + init_list.front() + "\n";
} else {
for (auto &str : init_list)
init_expr += stringf("\n %s", str.c_str());
init_expr += stringf("\n %s", str);
init_expr += "\n)";
}
}
@ -1435,6 +1507,91 @@ struct Smt2Worker
f << "true)";
f << stringf(" ; end of module %s\n", get_id(module));
}
template<class T> static std::vector<std::string> witness_path(T *obj) {
std::vector<std::string> path;
if (obj->name.isPublic()) {
auto hdlname = obj->get_string_attribute(ID::hdlname);
for (auto token : split_tokens(hdlname))
path.push_back("\\" + token);
}
if (path.empty())
path.push_back(obj->name.str());
return path;
}
std::string witness_signal(const char *type, int width, int offset, const std::string &smtname, int smtid, RTLIL::Wire *wire, int smtoffset = 0)
{
std::vector<std::string> hiername;
const char *wire_name = wire->name.c_str();
if (wire_name[0] == '\\') {
auto hdlname = wire->get_string_attribute(ID::hdlname);
for (auto token : split_tokens(hdlname))
hiername.push_back("\\" + token);
}
if (hiername.empty())
hiername.push_back(wire->name.str());
std::string line = "; yosys-smt2-witness ";
(json11::Json { json11::Json::object {
{ "type", type },
{ "offset", offset },
{ "width", width },
{ "smtname", smtname.empty() ? json11::Json(smtid) : json11::Json(smtname) },
{ "smtoffset", smtoffset },
{ "path", witness_path(wire) },
}}).dump(line);
line += "\n";
return line;
}
std::string witness_cell(const char *smtname, RTLIL::Cell *cell)
{
std::string line = "; yosys-smt2-witness ";
(json11::Json {json11::Json::object {
{ "type", "cell" },
{ "smtname", smtname },
{ "path", witness_path(cell) },
}}).dump(line);
line += "\n";
return line;
}
std::string witness_memory(const char *smtname, RTLIL::Cell *cell, Mem *mem)
{
json11::Json::array uninitialized;
auto init_data = mem->get_init_data();
int cursor = 0;
while (cursor < init_data.size()) {
while (cursor < init_data.size() && init_data[cursor] != State::Sx)
cursor++;
int offset = cursor;
while (cursor < init_data.size() && init_data[cursor] == State::Sx)
cursor++;
int width = cursor - offset;
if (width)
uninitialized.push_back(json11::Json::object {
{"width", width},
{"offset", offset},
});
}
std::string line = "; yosys-smt2-witness ";
(json11::Json { json11::Json::object {
{ "type", "mem" },
{ "width", mem->width },
{ "size", mem->size },
{ "rom", mem->wr_ports.empty() },
{ "statebv", statebv },
{ "smtname", smtname },
{ "uninitialized", uninitialized },
{ "path", witness_path(cell) },
}}).dump(line);
line += "\n";
return line;
}
};
struct Smt2Backend : public Backend {
@ -1609,7 +1766,6 @@ struct Smt2Backend : public Backend {
log_header(design, "Executing SMT2 backend.\n");
log_push();
Pass::call(design, "memory_map -rom-only");
Pass::call(design, "bmuxmap");
Pass::call(design, "demuxmap");
log_pop();
@ -1620,7 +1776,7 @@ struct Smt2Backend : public Backend {
if (args[argidx] == "-tpl" && argidx+1 < args.size()) {
template_f.open(args[++argidx]);
if (template_f.fail())
log_error("Can't open template file `%s'.\n", args[argidx].c_str());
log_error("Can't open template file `%s'.\n", args[argidx]);
continue;
}
if (args[argidx] == "-bv" || args[argidx] == "-mem") {
@ -1675,7 +1831,7 @@ struct Smt2Backend : public Backend {
}
}
*f << stringf("; SMT-LIBv2 description generated by %s\n", yosys_version_str);
*f << stringf("; SMT-LIBv2 description generated by %s\n", yosys_maybe_version());
if (!bvmode)
*f << stringf("; yosys-smt2-nobv\n");
@ -1690,7 +1846,7 @@ struct Smt2Backend : public Backend {
*f << stringf("; yosys-smt2-stdt\n");
for (auto &it : solver_options)
*f << stringf("; yosys-smt2-solver-option %s %s\n", it.first.c_str(), it.second.c_str());
*f << stringf("; yosys-smt2-solver-option %s %s\n", it.first, it.second);
std::vector<RTLIL::Module*> sorted_modules;
@ -1757,7 +1913,7 @@ struct Smt2Backend : public Backend {
}
if (topmod)
*f << stringf("; yosys-smt2-topmod %s\n", topmod_id.c_str());
*f << stringf("; yosys-smt2-topmod %s\n", topmod_id);
*f << stringf("; end of yosys output\n");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,616 @@
from collections import defaultdict
import json
import typing
import ywio
if typing.TYPE_CHECKING:
import smtbmc
else:
import sys
smtbmc = sys.modules["__main__"]
class InteractiveError(Exception):
pass
def mkkey(data):
if isinstance(data, list):
return tuple(map(mkkey, data))
elif isinstance(data, dict):
raise InteractiveError(f"JSON objects found in assumption key: {data!r}")
return data
class Incremental:
def __init__(self):
self.traceidx = 0
self.state_set = set()
self.map_cache = {}
self._cached_hierwitness = {}
self._witness_index = None
self._yw_constraints = {}
self._define_sorts = {}
def setup(self):
generic_assert_map = smtbmc.get_assert_map(
smtbmc.topmod, "state", smtbmc.topmod
)
self.inv_generic_assert_map = {
tuple(data[1:]): key for key, data in generic_assert_map.items()
}
assert len(self.inv_generic_assert_map) == len(generic_assert_map)
def print_json(self, **kwargs):
print(json.dumps(kwargs), flush=True)
def print_msg(self, msg):
self.print_json(msg=msg)
def get_cached_assert(self, step, name):
try:
assert_map = self.map_cache[step]
except KeyError:
assert_map = self.map_cache[step] = smtbmc.get_assert_map(
smtbmc.topmod, f"s{step}", smtbmc.topmod
)
return assert_map[self.inv_generic_assert_map[name]][0]
def arg_step(self, cmd, declare=False, name="step", optional=False):
step = cmd.get(name, None)
if step is None and optional:
return None
if not isinstance(step, int):
if optional:
raise InteractiveError(f"{name} must be an integer")
else:
raise InteractiveError(f"integer {name} argument required")
if declare and step in self.state_set:
raise InteractiveError(f"step {step} already declared")
if not declare and step not in self.state_set:
raise InteractiveError(f"step {step} not declared")
return step
def expr_arg_len(self, expr, min_len, max_len=-1):
if max_len == -1:
max_len = min_len
arg_len = len(expr) - 1
if min_len is not None and arg_len < min_len:
if min_len == max_len:
raise InteractiveError(
f"{json.dumps(expr[0])} expression must have "
f"{min_len} argument{'s' if min_len != 1 else ''}"
)
else:
raise InteractiveError(
f"{json.dumps(expr[0])} expression must have at least "
f"{min_len} argument{'s' if min_len != 1 else ''}"
)
if max_len is not None and arg_len > max_len:
raise InteractiveError(
f"{json.dumps(expr[0])} expression can have at most "
f"{min_len} argument{'s' if max_len != 1 else ''}"
)
def expr_step(self, expr, smt_out):
self.expr_arg_len(expr, 1)
step = expr[1]
if step not in self.state_set:
raise InteractiveError(f"step {step} not declared")
smt_out.append(f"s{step}")
return "module", smtbmc.topmod
def expr_cell(self, expr, smt_out):
self.expr_arg_len(expr, 2)
position = len(smt_out)
smt_out.append(None)
arg_sort = self.expr(expr[2], smt_out, required_sort=["module", None])
smt_out.append(")")
module = arg_sort[1]
cell = expr[1]
submod = smtbmc.smt.modinfo[module].cells.get(cell)
if submod is None:
raise InteractiveError(f"module {module!r} has no cell {cell!r}")
smt_out[position] = f"(|{module}_h {cell}| "
return ("module", submod)
def expr_mod_constraint(self, expr, smt_out):
suffix = expr[0][3:]
self.expr_arg_len(expr, 1, 2 if suffix in ["_a", "_u", "_c"] else 1)
position = len(smt_out)
smt_out.append(None)
arg_sort = self.expr(expr[-1], smt_out, required_sort=["module", None])
module = arg_sort[1]
if len(expr) == 3:
smt_out[position] = f"(|{module}{suffix} {expr[1]}| "
else:
smt_out[position] = f"(|{module}{suffix}| "
smt_out.append(")")
return "Bool"
def expr_mod_constraint2(self, expr, smt_out):
self.expr_arg_len(expr, 2)
position = len(smt_out)
smt_out.append(None)
arg_sort = self.expr(expr[1], smt_out, required_sort=["module", None])
smt_out.append(" ")
self.expr(expr[2], smt_out, required_sort=arg_sort)
module = arg_sort[1]
suffix = expr[0][3:]
smt_out[position] = f"(|{module}{suffix}| "
smt_out.append(")")
return "Bool"
def expr_not(self, expr, smt_out):
self.expr_arg_len(expr, 1)
smt_out.append("(not ")
self.expr(expr[1], smt_out, required_sort="Bool")
smt_out.append(")")
return "Bool"
def expr_eq(self, expr, smt_out):
self.expr_arg_len(expr, 2)
smt_out.append("(= ")
arg_sort = self.expr(expr[1], smt_out)
if (
smtbmc.smt.unroll
and isinstance(arg_sort, (list, tuple))
and arg_sort[0] == "module"
):
raise InteractiveError("state equality not supported in unroll mode")
smt_out.append(" ")
self.expr(expr[2], smt_out, required_sort=arg_sort)
smt_out.append(")")
return "Bool"
def expr_andor(self, expr, smt_out):
if len(expr) == 1:
smt_out.push({"and": "true", "or": "false"}[expr[0]])
elif len(expr) == 2:
self.expr(expr[1], smt_out, required_sort="Bool")
else:
sep = f"({expr[0]} "
for arg in expr[1:]:
smt_out.append(sep)
sep = " "
self.expr(arg, smt_out, required_sort="Bool")
smt_out.append(")")
return "Bool"
def expr_bv_binop(self, expr, smt_out):
self.expr_arg_len(expr, 2)
smt_out.append(f"({expr[0]} ")
arg_sort = self.expr(expr[1], smt_out, required_sort=("BitVec", None))
smt_out.append(" ")
self.expr(expr[2], smt_out, required_sort=arg_sort)
smt_out.append(")")
return arg_sort
def expr_extract(self, expr, smt_out):
self.expr_arg_len(expr, 3)
hi = expr[1]
lo = expr[2]
smt_out.append(f"((_ extract {hi} {lo}) ")
arg_sort = self.expr(expr[3], smt_out, required_sort=("BitVec", None))
smt_out.append(")")
if not (isinstance(hi, int) and 0 <= hi < arg_sort[1]):
raise InteractiveError(
f"high bit index must be 0 <= index < {arg_sort[1]}, is {hi!r}"
)
if not (isinstance(lo, int) and 0 <= lo <= hi):
raise InteractiveError(
f"low bit index must be 0 <= index < {hi}, is {lo!r}"
)
return "BitVec", hi - lo + 1
def expr_bv(self, expr, smt_out):
self.expr_arg_len(expr, 1)
arg = expr[1]
if not isinstance(arg, str) or arg.count("0") + arg.count("1") != len(arg):
raise InteractiveError("bv argument must contain only 0 or 1 bits")
smt_out.append("#b" + arg)
return "BitVec", len(arg)
def expr_yw(self, expr, smt_out):
self.expr_arg_len(expr, 1, 2)
if len(expr) == 2:
name = None
step = expr[1]
elif len(expr) == 3:
name = expr[1]
step = expr[2]
if step not in self.state_set:
raise InteractiveError(f"step {step} not declared")
if name not in self._yw_constraints:
raise InteractiveError(f"no yw file loaded as name {name!r}")
constraints = self._yw_constraints[name].get(step, [])
if len(constraints) == 0:
smt_out.append("true")
elif len(constraints) == 1:
smt_out.append(constraints[0])
else:
sep = "(and "
for constraint in constraints:
smt_out.append(sep)
sep = " "
smt_out.append(constraint)
smt_out.append(")")
return "Bool"
def expr_yw_sig(self, expr, smt_out):
self.expr_arg_len(expr, 3, 4)
step = expr[1]
path = expr[2]
offset = expr[3]
width = expr[4] if len(expr) == 5 else 1
if not isinstance(offset, int) or offset < 0:
raise InteractiveError(
f"offset must be a non-negative integer, got {json.dumps(offset)}"
)
if not isinstance(width, int) or width <= 0:
raise InteractiveError(
f"width must be a positive integer, got {json.dumps(width)}"
)
if not isinstance(path, list) or not all(isinstance(s, str) for s in path):
raise InteractiveError(
f"path must be a string list, got {json.dumps(path)}"
)
if step not in self.state_set:
raise InteractiveError(f"step {step} not declared")
smt_expr = smtbmc.ywfile_signal(
ywio.WitnessSig(path=path, offset=offset, width=width), step
)
smt_out.append(smt_expr)
return "BitVec", width
def expr_smtlib(self, expr, smt_out):
self.expr_arg_len(expr, 2)
smtlib_expr = expr[1]
sort = expr[2]
if not isinstance(smtlib_expr, str):
raise InteractiveError(
"raw SMT-LIB expression has to be a string, "
f"got {json.dumps(smtlib_expr)}"
)
if (
isinstance(sort, list)
and len(sort) == 2
and sort[0] == "BitVec"
and (sort[1] is None or isinstance(sort[1], int))
):
sort = tuple(sort)
elif not isinstance(sort, str):
raise InteractiveError(f"unsupported raw SMT-LIB sort {json.dumps(sort)}")
smt_out.append(smtlib_expr)
return sort
def expr_label(self, expr, smt_out):
if len(expr) != 3:
raise InteractiveError(
f'expected ["!", label, sub_expr], got {json.dumps(expr)}'
)
label = expr[1]
subexpr = expr[2]
if not isinstance(label, str):
raise InteractiveError("expression label has to be a string")
smt_out.append("(! ")
sort = self.expr(subexpr, smt_out)
smt_out.append(" :named ")
smt_out.append(label)
smt_out.append(")")
return sort
def expr_def(self, expr, smt_out):
self.expr_arg_len(expr, 1)
sort = self._define_sorts.get(expr[1])
if sort is None:
raise InteractiveError(f"unknown definition {json.dumps(expr)}")
smt_out.append(expr[1])
return sort
expr_handlers = {
"step": expr_step,
"cell": expr_cell,
"mod_h": expr_mod_constraint,
"mod_is": expr_mod_constraint,
"mod_i": expr_mod_constraint,
"mod_a": expr_mod_constraint,
"mod_u": expr_mod_constraint,
"mod_t": expr_mod_constraint2,
"not": expr_not,
"and": expr_andor,
"or": expr_andor,
"bv": expr_bv,
"bvand": expr_bv_binop,
"bvor": expr_bv_binop,
"bvxor": expr_bv_binop,
"extract": expr_extract,
"def": expr_def,
"=": expr_eq,
"yw": expr_yw,
"yw_sig": expr_yw_sig,
"smtlib": expr_smtlib,
"!": expr_label,
}
def expr(self, expr, smt_out, required_sort=None):
if not isinstance(expr, (list, tuple)) or not expr:
raise InteractiveError(
f"expression must be a non-empty JSON array, found: {json.dumps(expr)}"
)
name = expr[0]
handler = self.expr_handlers.get(name)
if handler:
sort = handler(self, expr, smt_out)
if required_sort is not None:
if isinstance(required_sort, (list, tuple)):
if (
not isinstance(sort, (list, tuple))
or len(sort) != len(required_sort)
or any(
r is not None and r != s
for r, s in zip(required_sort, sort)
)
):
raise InteractiveError(
f"required sort {json.dumps(required_sort)} "
f"found sort {json.dumps(sort)}"
)
return sort
raise InteractiveError(f"unknown expression {json.dumps(expr[0])}")
def expr_smt(self, expr, required_sort):
return self.expr_smt_and_sort(expr, required_sort)[0]
def expr_smt_and_sort(self, expr, required_sort=None):
smt_out = []
output_sort = self.expr(expr, smt_out, required_sort=required_sort)
out = "".join(smt_out)
return out, output_sort
def cmd_new_step(self, cmd):
step = self.arg_step(cmd, declare=True)
self.state_set.add(step)
smtbmc.smt_state(step)
def cmd_assert(self, cmd):
name = cmd.get("cmd")
assert_fn = {
"assert_antecedent": smtbmc.smt_assert_antecedent,
"assert_consequent": smtbmc.smt_assert_consequent,
"assert": smtbmc.smt_assert,
}[name]
assert_fn(self.expr_smt(cmd.get("expr"), "Bool"))
def cmd_assert_design_assumes(self, cmd):
step = self.arg_step(cmd)
smtbmc.smt_assert_design_assumes(step)
def cmd_get_design_assume(self, cmd):
key = mkkey(cmd.get("key"))
return smtbmc.assume_enables.get(key)
def cmd_update_assumptions(self, cmd):
expr = cmd.get("expr")
key = cmd.get("key")
key = mkkey(key)
result = smtbmc.smt.smt2_assumptions.pop(key, None)
if expr is not None:
expr = self.expr_smt(expr, "Bool")
smtbmc.smt.smt2_assumptions[key] = expr
return result
def cmd_get_unsat_assumptions(self, cmd):
return smtbmc.smt.get_unsat_assumptions(minimize=bool(cmd.get("minimize")))
def cmd_push(self, cmd):
smtbmc.smt_push()
def cmd_pop(self, cmd):
smtbmc.smt_pop()
def cmd_check(self, cmd):
return smtbmc.smt_check_sat()
def cmd_smtlib(self, cmd):
command = cmd.get("command")
response = cmd.get("response", False)
if not isinstance(command, str):
raise InteractiveError(
f"raw SMT-LIB command must be a string, found {json.dumps(command)}"
)
smtbmc.smt.write(command)
if response:
return smtbmc.smt.read()
def cmd_define(self, cmd):
expr = cmd.get("expr")
if expr is None:
raise InteractiveError("'define' copmmand requires 'expr' parameter")
expr, sort = self.expr_smt_and_sort(expr)
if isinstance(sort, tuple) and sort[0] == "module":
raise InteractiveError("'define' does not support module sorts")
define_name = f"|inc def {len(self._define_sorts)}|"
self._define_sorts[define_name] = sort
if isinstance(sort, tuple):
sort = f"(_ {' '.join(map(str, sort))})"
smtbmc.smt.write(f"(define-const {define_name} {sort} {expr})")
return {"name": define_name}
def cmd_design_hierwitness(self, cmd=None):
allregs = (cmd is None) or bool(cmd.get("allreges", False))
if self._cached_hierwitness[allregs] is not None:
return self._cached_hierwitness[allregs]
inits, seqs, clocks, mems = smtbmc.smt.hierwitness(smtbmc.topmod, allregs)
self._cached_hierwitness[allregs] = result = dict(
inits=inits, seqs=seqs, clocks=clocks, mems=mems
)
return result
def cmd_write_yw_trace(self, cmd):
steps = cmd.get("steps")
allregs = bool(cmd.get("allregs", False))
if steps is None:
steps = sorted(self.state_set)
path = cmd.get("path")
smtbmc.write_yw_trace(steps, self.traceidx, allregs=allregs, filename=path)
if path is None:
self.traceidx += 1
def cmd_read_yw_trace(self, cmd):
steps = cmd.get("steps")
path = cmd.get("path")
name = cmd.get("name")
skip_x = cmd.get("skip_x", False)
if path is None:
raise InteractiveError("path required")
constraints = defaultdict(list)
if steps is None:
steps = sorted(self.state_set)
map_steps = {i: int(j) for i, j in enumerate(steps)}
last_step = smtbmc.ywfile_constraints(
path, constraints, map_steps=map_steps, skip_x=skip_x
)
self._yw_constraints[name] = {
map_steps.get(i, i): [smtexpr for cexfile, smtexpr in constraint_list]
for i, constraint_list in constraints.items()
}
return dict(last_step=last_step)
def cmd_modinfo(self, cmd):
fields = cmd.get("fields", [])
mod = cmd.get("mod")
if mod is None:
mod = smtbmc.topmod
modinfo = smtbmc.smt.modinfo.get(mod)
if modinfo is None:
return None
result = dict(name=mod)
for field in fields:
result[field] = getattr(modinfo, field, None)
return result
def cmd_ping(self, cmd):
return cmd
cmd_handlers = {
"new_step": cmd_new_step,
"assert": cmd_assert,
"assert_antecedent": cmd_assert,
"assert_consequent": cmd_assert,
"assert_design_assumes": cmd_assert_design_assumes,
"get_design_assume": cmd_get_design_assume,
"update_assumptions": cmd_update_assumptions,
"get_unsat_assumptions": cmd_get_unsat_assumptions,
"push": cmd_push,
"pop": cmd_pop,
"check": cmd_check,
"smtlib": cmd_smtlib,
"define": cmd_define,
"design_hierwitness": cmd_design_hierwitness,
"write_yw_trace": cmd_write_yw_trace,
"read_yw_trace": cmd_read_yw_trace,
"modinfo": cmd_modinfo,
"ping": cmd_ping,
}
def handle_command(self, cmd):
if not isinstance(cmd, dict) or "cmd" not in cmd:
raise InteractiveError('object with "cmd" key required')
name = cmd.get("cmd", None)
handler = self.cmd_handlers.get(name)
if handler:
return handler(self, cmd)
else:
raise InteractiveError(f"unknown command: {name}")
def mainloop(self):
self.setup()
while True:
try:
cmd = input().strip()
if not cmd or cmd.startswith("#") or cmd.startswith("//"):
continue
try:
cmd = json.loads(cmd)
except json.decoder.JSONDecodeError as e:
self.print_json(err=f"invalid JSON: {e}")
continue
except EOFError:
break
try:
result = self.handle_command(cmd)
except InteractiveError as e:
self.print_json(err=str(e))
continue
except Exception as e:
self.print_json(err=f"internal error: {e}")
raise
else:
self.print_json(ok=result)

View file

@ -16,7 +16,7 @@
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
import sys, re, os, signal
import sys, re, os, signal, json
import subprocess
if os.name == "posix":
import resource
@ -79,6 +79,20 @@ def except_hook(exctype, value, traceback):
sys.excepthook = except_hook
def recursion_helper(iteration, *request):
stack = [iteration(*request)]
while stack:
top = stack.pop()
try:
request = next(top)
except StopIteration:
continue
stack.append(top)
stack.append(iteration(*request))
hex_dict = {
"0": "0000", "1": "0001", "2": "0010", "3": "0011",
"4": "0100", "5": "0101", "6": "0110", "7": "0111",
@ -100,6 +114,7 @@ class SmtModInfo:
self.clocks = dict()
self.cells = dict()
self.asserts = dict()
self.assumes = dict()
self.covers = dict()
self.maximize = set()
self.minimize = set()
@ -108,6 +123,7 @@ class SmtModInfo:
self.allconsts = dict()
self.allseqs = dict()
self.asize = dict()
self.witness = []
class SmtIo:
@ -126,6 +142,7 @@ class SmtIo:
self.recheck = False
self.smt2cache = [list()]
self.smt2_options = dict()
self.smt2_assumptions = dict()
self.p = None
self.p_index = solvers_index
solvers_index += 1
@ -143,6 +160,7 @@ class SmtIo:
self.noincr = opts.noincr
self.info_stmts = opts.info_stmts
self.nocomments = opts.nocomments
self.smt2_options.update(opts.smt2_options)
else:
self.solver = "yices"
@ -244,6 +262,7 @@ class SmtIo:
self.logic_uf = False
self.unroll_idcnt = 0
self.unroll_buffer = ""
self.unroll_level = 0
self.unroll_sorts = set()
self.unroll_objs = set()
self.unroll_decls = dict()
@ -296,10 +315,22 @@ class SmtIo:
return stmt
def unroll_stmt(self, stmt):
if not isinstance(stmt, list):
return stmt
result = []
recursion_helper(self._unroll_stmt_into, stmt, result)
return result.pop()
stmt = [self.unroll_stmt(s) for s in stmt]
def _unroll_stmt_into(self, stmt, output, depth=128):
if not isinstance(stmt, list):
output.append(stmt)
return
new_stmt = []
for s in stmt:
if depth:
yield from self._unroll_stmt_into(s, new_stmt, depth - 1)
else:
yield s, new_stmt
stmt = new_stmt
if len(stmt) >= 2 and not isinstance(stmt[0], list) and stmt[0] in self.unroll_decls:
assert stmt[1] in self.unroll_objs
@ -328,16 +359,23 @@ class SmtIo:
decl[2] = list()
if len(decl) > 0:
decl = self.unroll_stmt(decl)
tmp = []
if depth:
yield from self._unroll_stmt_into(decl, tmp, depth - 1)
else:
yield decl, tmp
decl = tmp.pop()
self.write(self.unparse(decl), unroll=False)
return self.unroll_cache[key]
output.append(self.unroll_cache[key])
return
return stmt
output.append(stmt)
def p_thread_main(self):
while True:
data = self.p.stdout.readline().decode("ascii")
data = self.p.stdout.readline().decode("utf-8")
if data == "": break
self.p_queue.put(data)
self.p_queue.put("")
@ -359,7 +397,7 @@ class SmtIo:
def p_write(self, data, flush):
assert self.p is not None
self.p.stdin.write(bytes(data, "ascii"))
self.p.stdin.write(bytes(data, "utf-8"))
if flush: self.p.stdin.flush()
def p_read(self):
@ -419,13 +457,15 @@ class SmtIo:
self.p_close()
if unroll and self.unroll:
stmt = self.unroll_buffer + stmt
self.unroll_buffer = ""
s = re.sub(r"\|[^|]*\|", "", stmt)
if s.count("(") != s.count(")"):
self.unroll_buffer = stmt + " "
self.unroll_level += s.count("(") - s.count(")")
if self.unroll_level > 0:
self.unroll_buffer += stmt
self.unroll_buffer += " "
return
else:
stmt = self.unroll_buffer + stmt
self.unroll_buffer = ""
s = self.parse(stmt)
@ -565,6 +605,12 @@ class SmtIo:
else:
self.modinfo[self.curmod].covers["%s_c %s" % (self.curmod, fields[2])] = fields[3]
if fields[1] == "yosys-smt2-assume":
if len(fields) > 4:
self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = f'{fields[4]} ({fields[3]})'
else:
self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = fields[3]
if fields[1] == "yosys-smt2-maximize":
self.modinfo[self.curmod].maximize.add(fields[2])
@ -587,6 +633,11 @@ class SmtIo:
self.modinfo[self.curmod].allseqs[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5])
self.modinfo[self.curmod].asize[fields[2]] = int(fields[3])
if fields[1] == "yosys-smt2-witness":
data = json.loads(stmt.split(None, 2)[2])
if data.get("type") in ["cell", "mem", "posedge", "negedge", "input", "reg", "init", "seq", "blackbox"]:
self.modinfo[self.curmod].witness.append(data)
def hiernets(self, top, regs_only=False):
def hiernets_worker(nets, mod, cursor):
for netname in sorted(self.modinfo[mod].wsize.keys()):
@ -658,6 +709,57 @@ class SmtIo:
hiermems_worker(mems, top, [])
return mems
def hierwitness(self, top, allregs=False, blackbox=True):
init_witnesses = []
seq_witnesses = []
clk_witnesses = []
mem_witnesses = []
def absolute(path, cursor, witness):
return {
**witness,
"path": path + tuple(witness["path"]),
"smtpath": cursor + [witness["smtname"]],
}
for witness in self.modinfo[top].witness:
if witness["type"] == "input":
seq_witnesses.append(absolute((), [], witness))
if witness["type"] in ("posedge", "negedge"):
clk_witnesses.append(absolute((), [], witness))
init_types = ["init"]
if allregs:
init_types.append("reg")
seq_types = ["seq"]
if blackbox:
seq_types.append("blackbox")
def worker(mod, path, cursor):
cell_paths = {}
for witness in self.modinfo[mod].witness:
if witness["type"] in init_types:
init_witnesses.append(absolute(path, cursor, witness))
if witness["type"] in seq_types:
seq_witnesses.append(absolute(path, cursor, witness))
if witness["type"] == "mem":
if allregs and not witness["rom"]:
width, size = witness["width"], witness["size"]
witness = {**witness, "uninitialized": [{"width": width * size, "offset": 0}]}
if not witness["uninitialized"]:
continue
mem_witnesses.append(absolute(path, cursor, witness))
if witness["type"] == "cell":
cell_paths[witness["smtname"]] = tuple(witness["path"])
for cellname, celltype in sorted(self.modinfo[mod].cells.items()):
worker(celltype, path + cell_paths.get(cellname, ("?" + cellname,)), cursor + [cellname])
worker(top, (), [])
return init_witnesses, seq_witnesses, clk_witnesses, mem_witnesses
def read(self):
stmt = []
count_brackets = 0
@ -692,8 +794,13 @@ class SmtIo:
return stmt
def check_sat(self, expected=["sat", "unsat", "unknown", "timeout", "interrupted"]):
if self.smt2_assumptions:
assume_exprs = " ".join(self.smt2_assumptions.values())
check_stmt = f"(check-sat-assuming ({assume_exprs}))"
else:
check_stmt = "(check-sat)"
if self.debug_print:
print("> (check-sat)")
print(f"> {check_stmt}")
if self.debug_file and not self.nocomments:
print("; running check-sat..", file=self.debug_file)
self.debug_file.flush()
@ -707,11 +814,11 @@ class SmtIo:
for cache_stmt in cache_ctx:
self.p_write(cache_stmt + "\n", False)
self.p_write("(check-sat)\n", True)
self.p_write(f"{check_stmt}\n", True)
if self.timeinfo:
i = 0
s = "/-\|"
s = r"/-\|"
count = 0
num_bs = 0
@ -775,7 +882,7 @@ class SmtIo:
if self.debug_file:
print("(set-info :status %s)" % result, file=self.debug_file)
print("(check-sat)", file=self.debug_file)
print(check_stmt, file=self.debug_file)
self.debug_file.flush()
if result not in expected:
@ -790,31 +897,28 @@ class SmtIo:
return result
def parse(self, stmt):
def worker(stmt):
if stmt[0] == '(':
def worker(stmt, cursor=0):
while stmt[cursor] in [" ", "\t", "\r", "\n"]:
cursor += 1
if stmt[cursor] == '(':
expr = []
cursor = 1
cursor += 1
while stmt[cursor] != ')':
el, le = worker(stmt[cursor:])
el, cursor = worker(stmt, cursor)
expr.append(el)
cursor += le
return expr, cursor+1
if stmt[0] == '|':
if stmt[cursor] == '|':
expr = "|"
cursor = 1
cursor += 1
while stmt[cursor] != '|':
expr += stmt[cursor]
cursor += 1
expr += "|"
return expr, cursor+1
if stmt[0] in [" ", "\t", "\r", "\n"]:
el, le = worker(stmt[1:])
return el, le+1
expr = ""
cursor = 0
while stmt[cursor] not in ["(", ")", "|", " ", "\t", "\r", "\n"]:
expr += stmt[cursor]
cursor += 1
@ -855,6 +959,55 @@ class SmtIo:
def bv2int(self, v):
return int(self.bv2bin(v), 2)
def get_raw_unsat_assumptions(self):
if not self.smt2_assumptions:
return []
self.write("(get-unsat-assumptions)")
exprs = set(self.unparse(part) for part in self.parse(self.read()))
unsat_assumptions = []
for key, value in self.smt2_assumptions.items():
# normalize expression
value = self.unparse(self.parse(value))
if value in exprs:
exprs.remove(value)
unsat_assumptions.append(key)
return unsat_assumptions
def get_unsat_assumptions(self, minimize=False):
if not minimize:
return self.get_raw_unsat_assumptions()
orig_assumptions = self.smt2_assumptions
self.smt2_assumptions = dict(orig_assumptions)
required_assumptions = {}
while True:
candidate_assumptions = {}
for key in self.get_raw_unsat_assumptions():
if key not in required_assumptions:
candidate_assumptions[key] = self.smt2_assumptions[key]
while candidate_assumptions:
candidate_key, candidate_assume = candidate_assumptions.popitem()
self.smt2_assumptions = {}
for key, assume in candidate_assumptions.items():
self.smt2_assumptions[key] = assume
for key, assume in required_assumptions.items():
self.smt2_assumptions[key] = assume
result = self.check_sat()
if result == 'unsat':
candidate_assumptions = None
else:
required_assumptions[candidate_key] = candidate_assume
if candidate_assumptions is not None:
self.smt2_assumptions = orig_assumptions
return list(required_assumptions)
def get(self, expr):
self.write("(get-value (%s))" % (expr))
return self.parse(self.read())[0][1]
@ -863,7 +1016,7 @@ class SmtIo:
if len(expr_list) == 0:
return []
self.write("(get-value (%s))" % " ".join(expr_list))
return [n[1] for n in self.parse(self.read())]
return [n[1] for n in self.parse(self.read()) if n]
def get_path(self, mod, path):
assert mod in self.modinfo
@ -887,6 +1040,8 @@ class SmtIo:
assert mod in self.modinfo
if path[0] == "":
return base
if isinstance(path[0], int):
return "(|%s#%d| %s)" % (mod, path[0], base)
if path[0] in self.modinfo[mod].cells:
return "(|%s_h %s| %s)" % (mod, path[0], base)
if path[0] in self.modinfo[mod].wsize:
@ -902,6 +1057,15 @@ class SmtIo:
nextbase = "(|%s_h %s| %s)" % (mod, path[0], base)
return self.net_expr(nextmod, nextbase, path[1:])
def witness_net_expr(self, mod, base, witness):
net = self.net_expr(mod, base, witness["smtpath"])
is_bool = self.net_width(mod, witness["smtpath"]) == 1
if is_bool:
assert witness["width"] == 1
assert witness["smtoffset"] == 0
return net
return "((_ extract %d %d) %s)" % (witness["smtoffset"] + witness["width"] - 1, witness["smtoffset"], net)
def net_width(self, mod, net_path):
for i in range(len(net_path)-1):
assert mod in self.modinfo
@ -909,6 +1073,8 @@ class SmtIo:
mod = self.modinfo[mod].cells[net_path[i]]
assert mod in self.modinfo
if isinstance(net_path[-1], int):
return None
assert net_path[-1] in self.modinfo[mod].wsize
return self.modinfo[mod].wsize[net_path[-1]]
@ -988,7 +1154,7 @@ class SmtIo:
class SmtOpts:
def __init__(self):
self.shortopts = "s:S:v"
self.longopts = ["unroll", "noincr", "noprogress", "timeout=", "dump-smt2=", "logic=", "dummy=", "info=", "nocomments"]
self.longopts = ["unroll", "noincr", "noprogress", "timeout=", "dump-smt2=", "logic=", "dummy=", "info=", "nocomments", "smt2-option="]
self.solver = "yices"
self.solver_opts = list()
self.debug_print = False
@ -1001,6 +1167,7 @@ class SmtOpts:
self.logic = None
self.info_stmts = list()
self.nocomments = False
self.smt2_options = {}
def handle(self, o, a):
if o == "-s":
@ -1027,6 +1194,13 @@ class SmtOpts:
self.info_stmts.append(a)
elif o == "--nocomments":
self.nocomments = True
elif o == "--smt2-option":
args = a.split('=', 1)
if len(args) != 2:
print("--smt2-option expects an <option>=<value> argument")
sys.exit(1)
option, value = args
self.smt2_options[option] = value
else:
return False
return True
@ -1034,7 +1208,7 @@ class SmtOpts:
def helpmsg(self):
return """
-s <solver>
set SMT solver: z3, yices, boolector, bitwuzla, cvc4, mathsat, dummy
set SMT solver: z3, yices, boolector, bitwuzla, cvc4, cvc5, mathsat, dummy
default: yices
-S <opt>
@ -1050,6 +1224,9 @@ class SmtOpts:
if solver is "dummy", read solver output from that file
otherwise: write solver output to that file
--smt2-option <option>=<value>
enable an SMT-LIBv2 option.
-v
enable debug output
@ -1104,7 +1281,7 @@ class MkVcd:
def escape_name(self, name):
name = re.sub(r"\[([0-9a-zA-Z_]*[a-zA-Z_][0-9a-zA-Z_]*)\]", r"<\1>", name)
if re.match("[\[\]]", name) and name[0] != "\\":
if re.match(r"[\[\]]", name) and name[0] != "\\":
name = "\\" + name
return name

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -ex
@ -21,7 +21,7 @@ EOT
for x in $(set +x; ls test_*.il | sort -R); do
x=${x%.il}
cat > $x.ys <<- EOT
read_ilang $x.il
read_rtlil $x.il
copy gold gate
cd gate

487
backends/smt2/witness.py Normal file
View file

@ -0,0 +1,487 @@
#!/usr/bin/env python3
#
# yosys -- Yosys Open SYnthesis Suite
#
# Copyright (C) 2022 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.
#
import os, sys, itertools, re
##yosys-sys-path##
import json
import click
from ywio import ReadWitness, WriteWitness, WitnessSig, WitnessSigMap, WitnessValues, coalesce_signals
@click.group()
def cli():
pass
@cli.command(help="""
Display a Yosys witness trace in a human readable format.
""")
@click.argument("input", type=click.File("r"))
@click.option("--skip-x", help="Treat x bits as unassigned.", is_flag=True)
def display(input, skip_x):
click.echo(f"Reading Yosys witness trace {input.name!r}...")
inyw = ReadWitness(input)
if skip_x:
inyw.skip_x()
def output():
yield click.style("*** RTLIL bit-order below may differ from source level declarations ***", fg="red")
if inyw.clocks:
yield click.style("=== Clock Signals ===", fg="blue")
for clock in inyw.clocks:
yield f" {clock['edge']} {WitnessSig(clock['path'], clock['offset']).pretty()}"
for t, values in inyw.steps():
if t:
yield click.style(f"=== Step {t} ===", fg="blue")
else:
yield click.style("=== Initial State ===", fg="blue")
step_prefix = click.style(f"#{t}", fg="bright_black")
signals, missing = values.present_signals(inyw.sigmap)
assert not missing
for sig in signals:
display_bits = values[sig].replace("?", click.style("?", fg="bright_black"))
yield f" {step_prefix} {sig.pretty()} = {display_bits}"
click.echo_via_pager([line + "\n" for line in output()])
@cli.command(help="""
Display statistics of a Yosys witness trace.
""")
@click.argument("input", type=click.File("r"))
def stats(input):
click.echo(f"Reading Yosys witness trace {input.name!r}...")
inyw = ReadWitness(input)
total = 0
for t, values in inyw.steps():
click.echo(f"{t:5}: {len(values.values):8} bits")
total += len(values.values)
click.echo(f"total: {total:8} bits")
@cli.command(help="""
Transform a Yosys witness trace.
Currently no transformations are implemented, so it is only useful for testing.
If two or more inputs are provided they will be concatenated together into the output.
""")
@click.argument("inputs", type=click.File("r"), nargs=-1)
@click.argument("output", type=click.File("w"))
@click.option("--append", "-p", type=int, multiple=True,
help="Number of steps (+ve or -ve) to append to end of input trace. "
+"Can be defined multiple times, following the same order as input traces. ")
@click.option("--skip-x", help="Leave input x bits unassigned.", is_flag=True)
def yw2yw(inputs, output, append, skip_x):
if len(inputs) == 0:
raise click.ClickException(f"no inputs specified")
outyw = WriteWitness(output, "yosys-witness yw2yw")
join_inputs = len(inputs) > 1
inyws = {}
if not append:
# default to 0
append = [0] * len(inputs)
if len(append) != len(inputs):
print(f"Mismatch in number of --append values ({len(append)}) and input traces ({len(inputs)}).")
sys.exit(1)
for (input, p) in zip(inputs, append):
if (join_inputs):
click.echo(f"Loading signals from yosys witness trace {input.name!r}...")
inyw = ReadWitness(input)
if p:
click.echo(f" appending {p} steps")
if (p + len(inyw) <= 0):
click.echo(f" skipping {input.name!r} (only {len(inyw)} steps to skip)")
continue
inyw.append_steps(p)
inyws[input] = inyw
for clock in inyw.clocks:
if clock not in outyw.clocks:
outyw.add_clock(clock["path"], clock["offset"], clock["edge"])
for sig in inyw.signals:
if sig not in outyw.signals:
outyw.add_sig(sig.path, sig.offset, sig.width, sig.init_only)
init_values = sum([inyw.init_step() for inyw in inyws.values()], start=WitnessValues())
first_witness = True
for (input, inyw) in inyws.items():
click.echo(f"Copying yosys witness trace from {input.name!r} to {output.name!r}...")
if first_witness:
outyw.step(init_values, skip_x=skip_x)
else:
outyw.step(inyw.first_step(), skip_x=skip_x)
for t, values in inyw.steps(1):
outyw.step(values, skip_x=skip_x)
click.echo(f" copied {t + 1} time steps.")
first_witness = False
outyw.end_trace()
if join_inputs:
click.echo(f"Copied {outyw.t} total time steps.")
class AigerMap:
def __init__(self, mapfile):
data = json.load(mapfile)
version = data.get("version") if isinstance(data, dict) else {}
if version != "Yosys Witness Aiger map":
raise click.ClickException(f"{mapfile.name}: unexpected file format version {version!r}")
self.latch_count = data["latch_count"]
self.input_count = data["input_count"]
self.clocks = data["clocks"]
self.sigmap = WitnessSigMap()
self.init_inputs = set(init["input"] for init in data["inits"])
for bit in data["inputs"] + data["seqs"] + data["inits"]:
self.sigmap.add_bit((tuple(bit["path"]), bit["offset"]), bit["input"])
@cli.command(help="""
Convert an AIGER witness trace into a Yosys witness trace.
This requires a Yosys witness AIGER map file as generated by 'write_aiger -ywmap'.
""")
@click.argument("input", type=click.File("r"))
@click.argument("mapfile", type=click.File("r"))
@click.argument("output", type=click.File("w"))
@click.option("--skip-x", help="Leave input x bits unassigned.", is_flag=True)
@click.option("--present-only", help="Only include bits present in at least one time step.", is_flag=True)
def aiw2yw(input, mapfile, output, skip_x, present_only):
input_name = input.name
click.echo(f"Converting AIGER witness trace {input_name!r} to Yosys witness trace {output.name!r}...")
click.echo(f"Using Yosys witness AIGER map file {mapfile.name!r}")
aiger_map = AigerMap(mapfile)
header_lines = list(itertools.islice(input, 0, 2))
if len(header_lines) == 2 and header_lines[1][0] in ".bcjf":
status = header_lines[0].strip()
if status == "0":
raise click.ClickException(f"{input_name}: file contains no trace, the AIGER status is unsat")
elif status == "2":
raise click.ClickException(f"{input_name}: file contains no trace, the AIGER status is sat")
elif status != "1":
raise click.ClickException(f"{input_name}: unexpected data in AIGER witness file")
else:
input = itertools.chain(header_lines, input)
ffline = next(input, None)
if ffline is None:
raise click.ClickException(f"{input_name}: unexpected end of file")
ffline = ffline.strip()
if not re.match(r'[01x]*$', ffline):
raise click.ClickException(f"{input_name}: unexpected data in AIGER witness file")
if not re.match(r'[0]*$', ffline):
raise click.ClickException(f"{input_name}: non-default initial state not supported")
if not present_only:
outyw = WriteWitness(output, "yosys-witness aiw2yw")
for clock in aiger_map.clocks:
outyw.add_clock(clock["path"], clock["offset"], clock["edge"])
for (path, offset), id in aiger_map.sigmap.bit_to_id.items():
outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs)
missing = set()
seen = set()
buffered_steps = []
skip = "x?" if skip_x else "?"
t = -1
while True:
inline = next(input, None)
if inline is None:
click.echo(f"Warning: {input_name}: file may be incomplete")
break
inline = inline.strip()
if inline in [".", "# DONE"]:
break
if inline.startswith("#"):
continue
t += 1
if not re.match(r"[01x]*$", inline):
raise click.ClickException(f"{input_name}: unexpected data in AIGER witness file")
if len(inline) != aiger_map.input_count:
raise click.ClickException(
f"{input_name}: {mapfile.name}: number of inputs does not match, "
f"{len(inline)} in witness, {aiger_map.input_count} in map file"
)
values = WitnessValues()
for i, v in enumerate(inline):
if v in skip or (t > 0 and i in aiger_map.init_inputs):
continue
try:
bit = aiger_map.sigmap.id_to_bit[i]
except IndexError:
bit = None
if bit is None:
missing.add(i)
elif present_only:
seen.add(i)
values[bit] = v
if present_only:
buffered_steps.append(values)
else:
outyw.step(values)
if present_only:
outyw = WriteWitness(output, "yosys-witness aiw2yw")
for clock in aiger_map.clocks:
outyw.add_clock(clock["path"], clock["offset"], clock["edge"])
for (path, offset), id in aiger_map.sigmap.bit_to_id.items():
if id in seen:
outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs)
for values in buffered_steps:
outyw.step(values)
outyw.end_trace()
if missing:
click.echo("The following AIGER inputs belong to unknown signals:")
click.echo(" " + " ".join(str(id) for id in sorted(missing)))
click.echo(f"Converted {outyw.t} time steps.")
@cli.command(help="""
Convert a Yosys witness trace into an AIGER witness trace.
This requires a Yosys witness AIGER map file as generated by 'write_aiger -ywmap'.
""")
@click.argument("input", type=click.File("r"))
@click.argument("mapfile", type=click.File("r"))
@click.argument("output", type=click.File("w"))
def yw2aiw(input, mapfile, output):
click.echo(f"Converting Yosys witness trace {input.name!r} to AIGER witness trace {output.name!r}...")
click.echo(f"Using Yosys witness AIGER map file {mapfile.name!r}")
aiger_map = AigerMap(mapfile)
inyw = ReadWitness(input)
print("1", file=output)
print("b0", file=output)
# TODO the b0 status isn't really accurate, but we don't have any better info here
print("0" * aiger_map.latch_count, file=output)
all_missing = set()
for t, step in inyw.steps():
bits, missing = step.pack_present(aiger_map.sigmap)
bits = bits[::-1].replace('?', 'x')
all_missing.update(missing)
bits += 'x' * (aiger_map.input_count - len(bits))
print(bits, file=output)
print(".", file=output)
if all_missing:
click.echo("The following signals are missing in the AIGER map file and will be dropped:")
for sig in coalesce_signals(WitnessSig(*bit) for bit in all_missing):
click.echo(" " + sig.pretty())
click.echo(f"Converted {len(inyw)} time steps.")
class BtorMap:
def __init__(self, mapfile):
self.data = data = json.load(mapfile)
version = data.get("version") if isinstance(data, dict) else {}
if version != "Yosys Witness BTOR map":
raise click.ClickException(f"{mapfile.name}: unexpected file format version {version!r}")
self.sigmap = WitnessSigMap()
for mode in ("states", "inputs"):
for btor_signal_def in data[mode]:
if btor_signal_def is None:
continue
if isinstance(btor_signal_def, dict):
btor_signal_def["path"] = tuple(btor_signal_def["path"])
else:
for chunk in btor_signal_def:
chunk["path"] = tuple(chunk["path"])
@cli.command(help="""
Convert a BTOR witness trace into a Yosys witness trace.
This requires a Yosys witness BTOR map file as generated by 'write_btor -ywmap'.
""")
@click.argument("input", type=click.File("r"))
@click.argument("mapfile", type=click.File("r"))
@click.argument("output", type=click.File("w"))
def wit2yw(input, mapfile, output):
input_name = input.name
click.echo(f"Converting BTOR witness trace {input_name!r} to Yosys witness trace {output.name!r}...")
click.echo(f"Using Yosys witness BTOR map file {mapfile.name!r}")
btor_map = BtorMap(mapfile)
input = filter(None, (line.split(';', 1)[0].strip() for line in input))
sat = next(input, None)
if sat != "sat":
raise click.ClickException(f"{input_name}: not a BTOR witness file")
props = next(input, None)
t = -1
line = next(input, None)
frames = []
bits = {}
while line and not line.startswith('.'):
current_t = int(line[1:].strip())
if line[0] == '#':
mode = "states"
elif line[0] == '@':
mode = "inputs"
else:
raise click.ClickException(f"{input_name}: unexpected data in BTOR witness file")
if current_t > t:
t = current_t
values = WitnessValues()
array_inits = set()
frames.append(values)
line = next(input, None)
while line and line[0] not in "#@.":
if current_t > 0 and mode == "states":
line = next(input, None)
continue
tokens = line.split()
line = next(input, None)
btor_sig = btor_map.data[mode][int(tokens[0])]
btor_sigs = [btor_sig]
if btor_sig is None:
continue
if isinstance(btor_sig, dict):
addr = tokens[1]
if not addr.startswith('['):
addr = '[*]'
value = tokens[1]
else:
value = tokens[2]
if not addr.endswith(']'):
raise click.ClickException(f"{input_name}: expected address in BTOR witness file")
path = btor_sig["path"]
width = btor_sig["width"]
size = btor_sig["size"]
if addr == '[*]':
btor_sigs = [
[{
"path": (*path, f"\\[{addr}]"),
"width": width,
"offset": 0,
}]
for addr in range(size)
if (path, addr) not in array_inits
]
array_inits.update((path, addr) for addr in range(size))
else:
addr = int(addr[1:-1], 2)
if addr < 0 or addr >= size:
raise click.ClickException(f"{input_name}: out of bounds address in BTOR witness file")
array_inits.add((path, addr))
btor_sig = [{
"path": (*path, f"\\[{addr}]"),
"width": width,
"offset": 0,
}]
btor_sigs = [btor_sig]
else:
value = tokens[1]
for btor_sig in btor_sigs:
value_bits = iter(reversed(value))
for chunk in btor_sig:
offset = chunk["offset"]
path = chunk["path"]
for i in range(offset, offset + chunk["width"]):
key = (path, i)
bits[key] = mode == "inputs"
values[key] = next(value_bits)
if next(value_bits, None) is not None:
raise click.ClickException(f"{input_name}: excess bits in BTOR witness file")
if line is None:
raise click.ClickException(f"{input_name}: unexpected end of BTOR witness file")
if line.strip() != '.':
raise click.ClickException(f"{input_name}: unexpected data in BTOR witness file")
if next(input, None) is not None:
raise click.ClickException(f"{input_name}: unexpected trailing data in BTOR witness file")
outyw = WriteWitness(output, 'yosys-witness wit2yw')
outyw.signals = coalesce_signals((), bits)
for clock in btor_map.data["clocks"]:
outyw.add_clock(clock["path"], clock["offset"], clock["edge"])
for values in frames:
outyw.step(values)
outyw.end_trace()
click.echo(f"Converted {outyw.t} time steps.")
if __name__ == "__main__":
cli()

432
backends/smt2/ywio.py Normal file
View file

@ -0,0 +1,432 @@
#
# yosys -- Yosys Open SYnthesis Suite
#
# Copyright (C) 2022 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.
#
import json, re
from functools import total_ordering
class PrettyJson:
def __init__(self, f):
self.f = f
self.indent = 0
self.state = ["value"]
def line(self):
indent = len(self.state) - bool(self.state and self.state[-1] == "value")
print("\n", end=" " * (2 * indent), file=self.f)
def raw(self, str):
print(end=str, file=self.f)
def begin_object(self):
self.begin_value()
self.raw("{")
self.state.append("object_first")
def begin_array(self):
self.begin_value()
self.raw("[")
self.state.append("array_first")
def end_object(self):
state = self.state.pop()
if state == "object":
self.line()
else:
assert state == "object_first"
self.raw("}")
self.end_value()
def end_array(self):
state = self.state.pop()
if state == "array":
self.line()
else:
assert state == "array_first"
self.raw("]")
self.end_value()
def name(self, name):
if self.state[-1] == "object_first":
self.state[-1] = "object"
else:
self.raw(",")
self.line()
json.dump(str(name), self.f)
self.raw(": ")
self.state.append("value")
def begin_value(self):
if self.state[-1] == "array_first":
self.line()
self.state[-1] = "array"
elif self.state[-1] == "array":
self.raw(",")
self.line()
else:
assert self.state.pop() == "value"
def end_value(self):
if not self.state:
print(file=self.f, flush=True)
def value(self, value):
self.begin_value()
json.dump(value, self.f)
self.end_value()
def entry(self, name, value):
self.name(name)
self.value(value)
def object(self, entries=None):
if isinstance(entries, dict):
entries = dict.items()
self.begin_object()
for name, value in entries:
self.entry(name, value)
self.end_object()
def array(self, values=None):
self.begin_array()
for value in values:
self.value(value)
self.end_array()
addr_re = re.compile(r'\\\[[0-9]+\]$')
public_name_re = re.compile(r"\\([a-zA-Z_][a-zA-Z0-9_]*(\[[0-9]+\])?|\[[0-9]+\])$")
def pretty_name(id):
if public_name_re.match(id):
return id.lstrip("\\")
else:
return id
def pretty_path(path):
out = ""
for name in path:
name = pretty_name(name)
if name.startswith("["):
out += name
continue
if out:
out += "."
if name.startswith("\\") or name.startswith("$"):
out += name + " "
else:
out += name
return out
@total_ordering
class WitnessSig:
def __init__(self, path, offset, width=1, init_only=False):
path = tuple(path)
self.path, self.width, self.offset, self.init_only = path, width, offset, init_only
self.memory_path = None
self.memory_addr = None
sort_path = path
sort_id = -1
if path and addr_re.match(path[-1]):
self.memory_path = sort_path = path[:-1]
self.memory_addr = sort_id = int(path[-1][2:-1])
self.sort_key = (init_only, sort_path, sort_id, offset, width)
def bits(self):
return ((self.path, i) for i in range(self.offset, self.offset + self.width))
def rev_bits(self):
return ((self.path, i) for i in reversed(range(self.offset, self.offset + self.width)))
def pretty(self):
if self.width > 1:
last_offset = self.offset + self.width - 1
return f"{pretty_path(self.path)}[{last_offset}:{self.offset}]"
else:
return f"{pretty_path(self.path)}[{self.offset}]"
def __eq__(self, other):
return self.sort_key == other.sort_key
def __hash__(self):
return hash(self.sort_key)
def __lt__(self, other):
return self.sort_key < other.sort_key
def coalesce_signals(signals, bits=None):
if bits is None:
bits = {}
for sig in signals:
for bit in sig.bits():
if sig.init_only:
bits.setdefault(bit, False)
else:
bits[bit] = True
active = None
out = []
for bit, not_init in sorted(bits.items()):
if active:
if active[0] == bit[0] and active[2] == bit[1] and active[3] == not_init:
active[2] += 1
else:
out.append(WitnessSig(active[0], active[1], active[2] - active[1], not active[3]))
active = None
if active is None:
active = [bit[0], bit[1], bit[1] + 1, not_init]
if active:
out.append(WitnessSig(active[0], active[1], active[2] - active[1], not active[3]))
return sorted(out)
class WitnessSigMap:
def __init__(self, signals=[]):
self.signals = []
self.id_to_bit = []
self.bit_to_id = {}
self.bit_to_sig = {}
for sig in signals:
self.add_signal(sig)
def add_signal(self, sig):
self.signals.append(sig)
for bit in sig.bits():
self.add_bit(bit)
self.bit_to_sig[bit] = sig
def add_bit(self, bit, id=None):
if id is None:
id = len(self.id_to_bit)
self.id_to_bit.append(bit)
else:
if len(self.id_to_bit) <= id:
self.id_to_bit += [None] * (id - len(self.id_to_bit) + 1)
self.id_to_bit[id] = bit
self.bit_to_id[bit] = id
class WitnessValues:
def __init__(self):
self.values = {}
def __setitem__(self, key, value):
if isinstance(key, tuple) and len(key) == 2:
if value != "?":
assert isinstance(value, str)
assert len(value) == 1
self.values[key] = value
else:
assert isinstance(key, WitnessSig)
assert key.width == len(value)
if isinstance(value, str):
value = reversed(value)
for bit, bit_value in zip(key.bits(), value):
if bit_value != "?":
self.values[bit] = bit_value
def __getitem__(self, key):
if isinstance(key, tuple) and len(key) == 2:
return self.values.get(key, "?")
else:
assert isinstance(key, WitnessSig)
return "".join([self.values.get(bit, "?") for bit in key.rev_bits()])
def pack_present(self, sigmap):
missing = []
max_id = max((sigmap.bit_to_id.get(bit, -1) for bit in self.values), default=-1)
vector = ["?"] * (max_id + 1)
for bit, bit_value in self.values.items():
id = sigmap.bit_to_id.get(bit, - 1)
if id < 0:
missing.append(bit)
else:
vector[max_id - sigmap.bit_to_id[bit]] = bit_value
return "".join(vector), missing
def pack(self, sigmap):
packed, missing = self.pack_present(sigmap)
if missing:
raise RuntimeError(f"Cannot pack bits {missing!r}")
return packed
def unpack(self, sigmap, bits):
for i, bit_value in enumerate(reversed(bits)):
if bit_value != "?":
self.values[sigmap.id_to_bit[i]] = bit_value
def present_signals(self, sigmap):
signals = set(sigmap.bit_to_sig.get(bit) for bit in self.values)
missing_signals = None in signals
if missing_signals:
signals.discard(None)
return sorted(signals), missing_signals
def __add__(self, other: "WitnessValues"):
new = WitnessValues()
new += self
new += other
return new
def __iadd__(self, other: "WitnessValues"):
for key, value in other.values.items():
self.values.setdefault(key, value)
return self
class WriteWitness:
def __init__(self, f, generator):
self.out = PrettyJson(f)
self.t = 0
self.header_written = False
self.clocks = []
self.signals = []
self.out.begin_object()
self.out.entry("format", "Yosys Witness Trace")
self.out.entry("generator", generator)
def add_clock(self, path, offset, edge):
assert not self.header_written
self.clocks.append({
"path": path,
"edge": edge,
"offset": offset,
})
def add_sig(self, path, offset, width=1, init_only=False):
assert not self.header_written
sig = WitnessSig(path, offset, width, init_only)
self.signals.append(sig)
return sig
def write_header(self):
assert not self.header_written
self.header_written = True
self.out.name("clocks")
self.out.array(self.clocks)
self.signals = coalesce_signals(self.signals)
self.sigmap = WitnessSigMap(self.signals)
self.out.name("signals")
self.out.array({
"path": sig.path,
"width": sig.width,
"offset": sig.offset,
"init_only": sig.init_only,
} for sig in self.signals)
self.out.name("steps")
self.out.begin_array()
def step(self, values, skip_x=False):
if not self.header_written:
self.write_header()
packed = values.pack(self.sigmap)
if skip_x:
packed = packed.replace('x', '?')
self.out.value({"bits": packed})
self.t += 1
def end_trace(self):
if not self.header_written:
self.write_header()
self.out.end_array()
self.out.end_object()
class ReadWitness:
def __init__(self, f):
data = json.load(f)
if not isinstance(data, dict):
data = {}
data_format = data.get("format", "Unknown Format")
if data_format != "Yosys Witness Trace":
raise ValueError(f"unsupported format {data_format!r}")
self.clocks = data["clocks"]
for clock in self.clocks:
clock["path"] = tuple(clock["path"])
self.signals = [
WitnessSig(sig["path"], sig["offset"], sig["width"], sig["init_only"])
for sig in data["signals"]
]
self.sigmap = WitnessSigMap(self.signals)
self.bits = [step["bits"] for step in data["steps"]]
def skip_x(self):
self.bits = [step.replace('x', '?') for step in self.bits]
def init_step(self):
return self.step(0)
def non_init_bits(self):
if len(self) > 1:
return len(self.bits[1])
else:
return sum([sig.width for sig in self.signals if not sig.init_only])
def first_step(self):
values = WitnessValues()
# may have issues when non_init_bits is 0
values.unpack(WitnessSigMap([sig for sig in self.signals if not sig.init_only]), self.bits[0][-self.non_init_bits():])
return values
def step(self, t):
values = WitnessValues()
values.unpack(self.sigmap, self.bits[t])
return values
def steps(self, start=0):
for i in range(start, len(self.bits)):
yield i, self.step(i)
def append_steps(self, t):
if not t:
pass
elif t < 0:
self.bits = self.bits[:t]
else:
self.bits.extend(["0"*self.non_init_bits()]*t)
def __len__(self):
return len(self.bits)

View file

@ -59,7 +59,7 @@ struct SmvWorker
{
if (!idcache.count(id))
{
string name = stringf("_%s", id.c_str());
string name = stringf("_%s", id);
if (name.compare(0, 2, "_\\") == 0)
name = "_" + name.substr(2);
@ -163,15 +163,15 @@ struct SmvWorker
if (width >= 0) {
if (is_signed) {
if (GetSize(sig) > width)
s = stringf("signed(resize(%s, %d))", s.c_str(), width);
s = stringf("signed(resize(%s, %d))", s, width);
else
s = stringf("resize(signed(%s), %d)", s.c_str(), width);
s = stringf("resize(signed(%s), %d)", s, width);
} else
s = stringf("resize(%s, %d)", s.c_str(), width);
s = stringf("resize(%s, %d)", s, width);
} else if (is_signed)
s = stringf("signed(%s)", s.c_str());
s = stringf("signed(%s)", s);
else if (count_chunks > 1)
s = stringf("(%s)", s.c_str());
s = stringf("(%s)", s);
strbuf.push_back(s);
return strbuf.back().c_str();
@ -262,7 +262,7 @@ struct SmvWorker
if (cell->type == ID($sshr) && signed_a)
{
expr_a = rvalue_s(sig_a, width);
expr = stringf("resize(unsigned(%s %s %s), %d)", expr_a.c_str(), op.c_str(), rvalue(sig_b.extract(0, shift_b_width)), width_y);
expr = stringf("resize(unsigned(%s %s %s), %d)", expr_a, op, rvalue(sig_b.extract(0, shift_b_width)), width_y);
if (shift_b_width < GetSize(sig_b))
expr = stringf("%s != 0ud%d_0 ? (bool(%s) ? !0ud%d_0 : 0ud%d_0) : %s",
rvalue(sig_b.extract(shift_b_width, GetSize(sig_b) - shift_b_width)), GetSize(sig_b) - shift_b_width,
@ -278,8 +278,8 @@ struct SmvWorker
// f << stringf(" %s : unsigned word[%d]; -- neg(%s)\n", b_shl, GetSize(sig_b), log_signal(sig_b));
definitions.push_back(stringf("%s := unsigned(-%s);", b_shl, rvalue_s(sig_b)));
string expr_shl = stringf("resize(%s << %s[%d:0], %d)", expr_a.c_str(), b_shl, shift_b_width-1, width_y);
string expr_shr = stringf("resize(%s >> %s[%d:0], %d)", expr_a.c_str(), b_shr, shift_b_width-1, width_y);
string expr_shl = stringf("resize(%s << %s[%d:0], %d)", expr_a, b_shl, shift_b_width-1, width_y);
string expr_shr = stringf("resize(%s >> %s[%d:0], %d)", expr_a, b_shr, shift_b_width-1, width_y);
if (shift_b_width < GetSize(sig_b)) {
expr_shl = stringf("%s[%d:%d] != 0ud%d_0 ? 0ud%d_0 : %s", b_shl, GetSize(sig_b)-1, shift_b_width,
@ -288,7 +288,7 @@ struct SmvWorker
GetSize(sig_b)-shift_b_width, width_y, expr_shr.c_str());
}
expr = stringf("bool(%s) ? %s : %s", rvalue(sig_b[GetSize(sig_b)-1]), expr_shl.c_str(), expr_shr.c_str());
expr = stringf("bool(%s) ? %s : %s", rvalue(sig_b[GetSize(sig_b)-1]), expr_shl, expr_shr);
}
else
{
@ -297,13 +297,13 @@ struct SmvWorker
else
expr_a = stringf("resize(unsigned(%s), %d)", rvalue_s(sig_a, width_ay), width);
expr = stringf("resize(%s %s %s[%d:0], %d)", expr_a.c_str(), op.c_str(), rvalue_u(sig_b), shift_b_width-1, width_y);
expr = stringf("resize(%s %s %s[%d:0], %d)", expr_a, op, rvalue_u(sig_b), shift_b_width-1, width_y);
if (shift_b_width < GetSize(sig_b))
expr = stringf("%s[%d:%d] != 0ud%d_0 ? 0ud%d_0 : %s", rvalue_u(sig_b), GetSize(sig_b)-1, shift_b_width,
GetSize(sig_b)-shift_b_width, width_y, expr.c_str());
}
definitions.push_back(stringf("%s := %s;", lvalue(cell->getPort(ID::Y)), expr.c_str()));
definitions.push_back(stringf("%s := %s;", lvalue(cell->getPort(ID::Y)), expr));
continue;
}
@ -426,7 +426,7 @@ struct SmvWorker
if (cell->type == ID($reduce_or)) expr = stringf("%s != 0ub%d_0", expr_a, width_a);
if (cell->type == ID($reduce_bool)) expr = stringf("%s != 0ub%d_0", expr_a, width_a);
definitions.push_back(stringf("%s := resize(word1(%s), %d);", expr_y, expr.c_str(), width_y));
definitions.push_back(stringf("%s := resize(word1(%s), %d);", expr_y, expr, width_y));
continue;
}
@ -445,7 +445,7 @@ struct SmvWorker
if (cell->type == ID($reduce_xnor))
expr = "!(" + expr + ")";
definitions.push_back(stringf("%s := resize(%s, %d);", expr_y, expr.c_str(), width_y));
definitions.push_back(stringf("%s := resize(%s, %d);", expr_y, expr, width_y));
continue;
}
@ -463,7 +463,7 @@ struct SmvWorker
if (cell->type == ID($logic_and)) expr = expr_a + " & " + expr_b;
if (cell->type == ID($logic_or)) expr = expr_a + " | " + expr_b;
definitions.push_back(stringf("%s := resize(word1(%s), %d);", expr_y, expr.c_str(), width_y));
definitions.push_back(stringf("%s := resize(word1(%s), %d);", expr_y, expr, width_y));
continue;
}
@ -475,7 +475,7 @@ struct SmvWorker
string expr_a = stringf("(%s = 0ub%d_0)", rvalue(cell->getPort(ID::A)), width_a);
const char *expr_y = lvalue(cell->getPort(ID::Y));
definitions.push_back(stringf("%s := resize(word1(%s), %d);", expr_y, expr_a.c_str(), width_y));
definitions.push_back(stringf("%s := resize(word1(%s), %d);", expr_y, expr_a, width_y));
continue;
}
@ -491,7 +491,7 @@ struct SmvWorker
expr += stringf("bool(%s) ? %s : ", rvalue(sig_s[i]), rvalue(sig_b.extract(i*width, width)));
expr += rvalue(sig_a);
definitions.push_back(stringf("%s := %s;", lvalue(cell->getPort(ID::Y)), expr.c_str()));
definitions.push_back(stringf("%s := %s;", lvalue(cell->getPort(ID::Y)), expr));
continue;
}
@ -505,7 +505,7 @@ struct SmvWorker
if (cell->type.in(ID($_BUF_), ID($_NOT_)))
{
string op = cell->type == ID($_NOT_) ? "!" : "";
definitions.push_back(stringf("%s := %s%s;", lvalue(cell->getPort(ID::Y)), op.c_str(), rvalue(cell->getPort(ID::A))));
definitions.push_back(stringf("%s := %s%s;", lvalue(cell->getPort(ID::Y)), op, rvalue(cell->getPort(ID::A))));
continue;
}
@ -573,6 +573,9 @@ struct SmvWorker
continue;
}
if (cell->type == ID($scopeinfo))
continue;
if (cell->type[0] == '$') {
if (cell->type.in(ID($dffe), ID($sdff), ID($sdffe), ID($sdffce)) || cell->type.str().substr(0, 6) == "$_SDFF" || (cell->type.str().substr(0, 6) == "$_DFFE" && cell->type.str().size() == 10)) {
log_error("Unsupported cell type %s for cell %s.%s -- please run `dffunmap` before `write_smv`.\n",
@ -647,7 +650,7 @@ struct SmvWorker
for (int k = GetSize(sig)-1; k >= 0; k--)
bits += sig[k] == State::S1 ? '1' : '0';
expr = stringf("0ub%d_%s", GetSize(bits), bits.c_str()) + expr;
expr = stringf("0ub%d_%s", GetSize(bits), bits) + expr;
}
else if (sigmap(SigBit(wire, i)) == SigBit(wire, i))
{
@ -680,36 +683,36 @@ struct SmvWorker
}
}
definitions.push_back(stringf("%s := %s;", cid(wire->name), expr.c_str()));
definitions.push_back(stringf("%s := %s;", cid(wire->name), expr));
}
if (!inputvars.empty()) {
f << stringf(" IVAR\n");
for (const string &line : inputvars)
f << stringf(" %s\n", line.c_str());
f << stringf(" %s\n", line);
}
if (!vars.empty()) {
f << stringf(" VAR\n");
for (const string &line : vars)
f << stringf(" %s\n", line.c_str());
f << stringf(" %s\n", line);
}
if (!definitions.empty()) {
f << stringf(" DEFINE\n");
for (const string &line : definitions)
f << stringf(" %s\n", line.c_str());
f << stringf(" %s\n", line);
}
if (!assignments.empty()) {
f << stringf(" ASSIGN\n");
for (const string &line : assignments)
f << stringf(" %s\n", line.c_str());
f << stringf(" %s\n", line);
}
if (!invarspecs.empty()) {
for (const string &line : invarspecs)
f << stringf(" INVARSPEC %s\n", line.c_str());
f << stringf(" INVARSPEC %s\n", line);
}
}
};
@ -744,6 +747,7 @@ struct SmvBackend : public Backend {
log_push();
Pass::call(design, "bmuxmap");
Pass::call(design, "demuxmap");
Pass::call(design, "bwmuxmap");
log_pop();
size_t argidx;
@ -752,7 +756,7 @@ struct SmvBackend : public Backend {
if (args[argidx] == "-tpl" && argidx+1 < args.size()) {
template_f.open(args[++argidx]);
if (template_f.fail())
log_error("Can't open template file `%s'.\n", args[argidx].c_str());
log_error("Can't open template file `%s'.\n", args[argidx]);
continue;
}
if (args[argidx] == "-verbose") {
@ -791,9 +795,9 @@ struct SmvBackend : public Backend {
modules.erase(module);
if (module == nullptr)
log_error("Module '%s' not found.\n", stmt[1].c_str());
log_error("Module '%s' not found.\n", stmt[1]);
*f << stringf("-- SMV description generated by %s\n", yosys_version_str);
*f << stringf("-- SMV description generated by %s\n", yosys_maybe_version());
log("Creating SMV representation of module %s.\n", log_id(module));
SmvWorker worker(module, verbose, *f);
@ -812,7 +816,7 @@ struct SmvBackend : public Backend {
if (!modules.empty())
{
*f << stringf("-- SMV description generated by %s\n", yosys_version_str);
*f << stringf("-- SMV description generated by %s\n", yosys_maybe_version());
for (auto module : modules) {
log("Creating SMV representation of module %s.\n", log_id(module));

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -ex
@ -17,11 +17,11 @@ EOT
for fn in test_*.il; do
../../../yosys -p "
read_ilang $fn
read_rtlil $fn
rename gold gate
synth
read_ilang $fn
read_rtlil $fn
miter -equiv -flatten gold gate main
hierarchy -top main
write_smv -tpl template.txt ${fn#.il}.smv

View file

@ -51,16 +51,16 @@ static void print_spice_net(std::ostream &f, RTLIL::SigBit s, std::string &neg,
if (s.wire->port_id)
use_inames = true;
if (s.wire->width > 1)
f << stringf(" %s.%d", spice_id2str(s.wire->name, use_inames, inums).c_str(), s.offset);
f << stringf(" %s.%d", spice_id2str(s.wire->name, use_inames, inums), s.offset);
else
f << stringf(" %s", spice_id2str(s.wire->name, use_inames, inums).c_str());
f << stringf(" %s", spice_id2str(s.wire->name, use_inames, inums));
} else {
if (s == RTLIL::State::S0)
f << stringf(" %s", neg.c_str());
f << stringf(" %s", neg);
else if (s == RTLIL::State::S1)
f << stringf(" %s", pos.c_str());
f << stringf(" %s", pos);
else
f << stringf(" %s%d", ncpf.c_str(), nc_counter++);
f << stringf(" %s%d", ncpf, nc_counter++);
}
}
@ -72,6 +72,9 @@ static void print_spice_module(std::ostream &f, RTLIL::Module *module, RTLIL::De
for (auto cell : module->cells())
{
if (cell->type == ID($scopeinfo))
continue;
f << stringf("X%d", cell_counter++);
std::vector<RTLIL::SigSpec> port_sigs;
@ -116,7 +119,7 @@ static void print_spice_module(std::ostream &f, RTLIL::Module *module, RTLIL::De
}
}
f << stringf(" %s\n", spice_id2str(cell->type).c_str());
f << stringf(" %s\n", spice_id2str(cell->type));
}
for (auto &conn : module->connections())
@ -124,7 +127,7 @@ static void print_spice_module(std::ostream &f, RTLIL::Module *module, RTLIL::De
f << (buf == "DC" ? stringf("V%d", conn_counter++) : stringf("X%d", cell_counter++));
print_spice_net(f, conn.second.extract(i, 1), neg, pos, ncpf, nc_counter, use_inames, inums);
print_spice_net(f, conn.first.extract(i, 1), neg, pos, ncpf, nc_counter, use_inames, inums);
f << (buf == "DC" ? " DC 0\n" : stringf(" %s\n", buf.c_str()));
f << (buf == "DC" ? " DC 0\n" : stringf(" %s\n", buf));
}
}
@ -212,7 +215,7 @@ struct SpiceBackend : public Backend {
if (module->get_bool_attribute(ID::top))
top_module_name = module->name.str();
*f << stringf("* SPICE netlist generated by %s\n", yosys_version_str);
*f << stringf("* SPICE netlist generated by %s\n", yosys_maybe_version());
*f << stringf("\n");
for (auto module : design->modules())
@ -239,23 +242,23 @@ struct SpiceBackend : public Backend {
ports.at(wire->port_id-1) = wire;
}
*f << stringf(".SUBCKT %s", spice_id2str(module->name).c_str());
*f << stringf(".SUBCKT %s", spice_id2str(module->name));
for (RTLIL::Wire *wire : ports) {
log_assert(wire != NULL);
if (wire->width > 1) {
for (int i = 0; i < wire->width; i++)
*f << stringf(" %s.%d", spice_id2str(wire->name).c_str(), big_endian ? wire->width - 1 - i : i);
*f << stringf(" %s.%d", spice_id2str(wire->name), big_endian ? wire->width - 1 - i : i);
} else
*f << stringf(" %s", spice_id2str(wire->name).c_str());
*f << stringf(" %s", spice_id2str(wire->name));
}
*f << stringf("\n");
print_spice_module(*f, module, design, neg, pos, buf, ncpf, big_endian, use_inames);
*f << stringf(".ENDS %s\n\n", spice_id2str(module->name).c_str());
*f << stringf(".ENDS %s\n\n", spice_id2str(module->name));
}
if (!top_module_name.empty()) {
if (top_module == NULL)
log_error("Can't find top module `%s'!\n", top_module_name.c_str());
log_error("Can't find top module `%s'!\n", top_module_name);
print_spice_module(*f, top_module, design, neg, pos, buf, ncpf, big_endian, use_inames);
*f << stringf("\n");
}

File diff suppressed because it is too large Load diff

View file

@ -17,35 +17,23 @@
*
* ---
*
* A very simple and straightforward frontend for the RTLIL text
* representation.
* A simple and straightforward Verilog backend.
*
*/
#ifndef RTLIL_FRONTEND_H
#define RTLIL_FRONTEND_H
#ifndef VERILOG_BACKEND_H
#define VERILOG_BACKEND_H
#include "kernel/yosys.h"
#include <string>
YOSYS_NAMESPACE_BEGIN
namespace VERILOG_BACKEND {
namespace RTLIL_FRONTEND {
extern std::istream *lexin;
extern RTLIL::Design *current_design;
extern bool flag_nooverwrite;
extern bool flag_overwrite;
extern bool flag_lib;
}
const pool<string> verilog_keywords();
bool char_is_verilog_escaped(char c);
bool id_is_verilog_escaped(const std::string &str);
}; /* namespace VERILOG_BACKEND */
YOSYS_NAMESPACE_END
extern int rtlil_frontend_yydebug;
int rtlil_frontend_yylex(void);
void rtlil_frontend_yyerror(char const *s);
void rtlil_frontend_yyrestart(FILE *f);
int rtlil_frontend_yyparse(void);
int rtlil_frontend_yylex_destroy(void);
int rtlil_frontend_yyget_lineno(void);
#endif
#endif /* VERILOG_BACKEND_H */

9
docs/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
/build/
/source/generated
/source/_images/**/*.log
/source/_images/**/*.aux
/source/_images/**/*.pdf
/source/_images/**/*.svg
/source/_images/**/*.dot
/source/_images/code_examples
/venv

262
docs/Makefile Normal file
View file

@ -0,0 +1,262 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS = -W --keep-going
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " epub3 to make an epub3"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
@echo " dummy to check syntax errors of document sources"
.PHONY: clean
clean: clean-examples
rm -rf $(BUILDDIR)/*
rm -rf util/__pycache__
rm -rf source/generated
$(MAKE) -C source/_images clean
.PHONY: html
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
# singlehtml section links are broken
.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SymbiYosys.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SymbiYosys.qhc"
.PHONY: applehelp
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/SymbiYosys"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SymbiYosys"
@echo "# devhelp"
.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: epub3
epub3:
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
@echo
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
.PHONY: latex
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: latexpdfja
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
.PHONY: dummy
dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."
PYTHON ?= python3
.PHONY: test test-examples test-macros examples
test: test-examples test-macros
FORCE:
Makefile-%: FORCE
$(MAKE) -C $(@D) $(*F)
CODE_EXAMPLES := $(wildcard source/code_examples/*/Makefile)
TEST_EXAMPLES := $(addsuffix -examples,$(CODE_EXAMPLES))
CLEAN_EXAMPLES := $(addsuffix -clean,$(CODE_EXAMPLES))
test-examples: $(TEST_EXAMPLES)
clean-examples: $(CLEAN_EXAMPLES)
examples: $(TEST_EXAMPLES)
test-macros:
$(PYTHON) tests/macro_commands.py
.PHONY: images
images:
$(MAKE) -C source/_images
$(MAKE) -C source/_images convert
.PHONY: gen
gen:
$(MAKE) examples
$(MAKE) images
.PHONY: reqs
reqs:
$(PYTHON) -m pip install -r source/requirements.txt

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,50 @@
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'
ifneq ($(shell :; command -v rsync),)
RSYNC_CP ?= rsync -t
else
RSYNC_CP ?= cp -a
endif
# find all code example makefiles
.PHONY: examples
CODE_EXAMPLES := ../code_examples/*/Makefile
examples: $(CODE_EXAMPLES)
# target to convert all dot files
# needs to be run *after* examples, otherwise no dot files will be found
.PHONY: convert
DOT_FILES := $(shell find . -name *.dot)
convert: $(DOT_FILES:.dot=.pdf) $(DOT_FILES:.dot=.svg)
# use empty FORCE target because .PHONY ignores % expansion
FORCE:
../%/Makefile: FORCE
@make -C $(@D) dots
@mkdir -p $*
@find $(@D) -name *.dot -exec $(RSYNC_CP) {} $* \;
# find and build all tex files
.PHONY: all_tex
TEX_FILES := $(shell find . -name *.tex)
all_tex: $(TEX_FILES:.tex=.pdf) $(TEX_FILES:.tex=.svg)
%.pdf: %.dot
$(FAKETIME) dot -Tpdf -o $@ $<
%.pdf: %.tex
cd $(@D) && $(FAKETIME) pdflatex $(<F) --interaction=nonstopmode
%.svg: %.pdf
pdf2svg $< $@
.PHONY: clean tidy
tidy:
rm -f **/*.log **/*.aux
clean: tidy
rm -rf code_examples
rm -f **/*.pdf **/*.svg

View file

@ -0,0 +1,38 @@
\documentclass[12pt,tikz]{standalone}
\pdfinfoomitdate 1
\pdfsuppressptexinfo 1
\pdftrailerid{}
\usepackage[utf8]{inputenc}
\usepackage{amsmath}
\usepackage{pgfplots}
\usepackage{tikz}
\usetikzlibrary{calc}
\pagestyle{empty}
\begin{document}
\begin{tikzpicture}
\path (-1.5,3) coordinate (cursor);
\draw[-latex] ($ (cursor) + (0,-1.5) $) -- ++(1,0);
\draw[fill=orange!10] ($ (cursor) + (1,-3) $) rectangle node[rotate=90] {Frontend} ++(1,3) coordinate (cursor);
\draw[-latex] ($ (cursor) + (0,-1.5) $) -- ++(1,0);
\draw[fill=green!10] ($ (cursor) + (1,-3) $) rectangle node[rotate=90] {Pass} ++(1,3) coordinate (cursor);
\draw[-latex] ($ (cursor) + (0,-1.5) $) -- ++(1,0);
\draw[fill=green!10] ($ (cursor) + (1,-3) $) rectangle node[rotate=90] {Pass} ++(1,3) coordinate (cursor);
\draw[-latex] ($ (cursor) + (0,-1.5) $) -- ++(1,0);
\draw[fill=green!10] ($ (cursor) + (1,-3) $) rectangle node[rotate=90] {Pass} ++(1,3) coordinate (cursor);
\draw[-latex] ($ (cursor) + (0,-1.5) $) -- ++(1,0);
\draw[fill=orange!10] ($ (cursor) + (1,-3) $) rectangle node[rotate=90] {Backend} ++(1,3) coordinate (cursor);
\draw[-latex] ($ (cursor) + (0,-1.5) $) -- ++(1,0);
\path (-3,-0.5) coordinate (cursor);
\draw (cursor) -- node[below] {HDL} ++(3,0) coordinate (cursor);
\draw[|-|] (cursor) -- node[below] {Internal Format(s)} ++(8,0) coordinate (cursor);
\draw (cursor) -- node[below] {Netlist} ++(3,0);
\path (-3,3.5) coordinate (cursor);
\draw[-] (cursor) -- node[above] {High-Level} ++(3,0) coordinate (cursor);
\draw[-] (cursor) -- ++(8,0) coordinate (cursor);
\draw[->] (cursor) -- node[above] {Low-Level} ++(3,0);
\end{tikzpicture}
\end{document}

View file

@ -0,0 +1,37 @@
\documentclass[12pt,tikz]{standalone}
\pdfinfoomitdate 1
\pdfsuppressptexinfo 1
\pdftrailerid{}
\usepackage[utf8]{inputenc}
\usepackage{amsmath}
\usepackage{pgfplots}
\usepackage{tikz}
\usetikzlibrary{shapes.geometric}
\pagestyle{empty}
\begin{document}
\begin{tikzpicture}
\tikzstyle{process} = [draw, fill=green!10, rectangle, minimum height=3em, minimum width=10em, node distance=15em]
\tikzstyle{data} = [draw, fill=blue!10, ellipse, minimum height=3em, minimum width=7em, node distance=15em]
\node[process] (vlog) {Verilog Frontend};
\node[process, dashed, fill=green!5] (vhdl) [right of=vlog] {VHDL Frontend};
\node[process] (rtlilfe) [right of=vhdl] {RTLIL Frontend};
\node[data] (ast) [below of=vlog, node distance=5em, xshift=7.5em] {AST};
\node[process] (astfe) [below of=ast, node distance=5em] {AST Frontend};
\node[data] (rtlil) [below of=astfe, node distance=5em, xshift=7.5em] {RTLIL};
\node[process] (pass) [right of=rtlil, node distance=5em, xshift=7.5em] {Passes};
\node[process] (vlbe) [below of=rtlil, node distance=7em, xshift=-13em] {Verilog Backend};
\node[process] (rtlilbe) [below of=rtlil, node distance=7em, xshift=0em] {RTLIL Backend};
\node[process, dashed, fill=green!5] (otherbe) [below of=rtlil, node distance=7em, xshift=+13em] {Other Backends};
\draw[-latex] (vlog) -- (ast);
\draw[-latex] (vhdl) -- (ast);
\draw[-latex] (ast) -- (astfe);
\draw[-latex] (astfe) -- (rtlil);
\draw[-latex] (rtlilfe) -- (rtlil);
\draw[latex-latex] (rtlil) -- (pass);
\draw[-latex] (rtlil) -- (vlbe);
\draw[-latex] (rtlil) -- (rtlilbe);
\draw[-latex] (rtlil) -- (otherbe);
\end{tikzpicture}
\end{document}

View file

@ -0,0 +1,27 @@
\documentclass[12pt,tikz]{standalone}
\pdfinfoomitdate 1
\pdfsuppressptexinfo 1
\pdftrailerid{}
\usepackage[utf8]{inputenc}
\usepackage{amsmath}
\usepackage{pgfplots}
\usepackage{tikz}
\pagestyle{empty}
\begin{document}
\begin{tikzpicture}
\tikzstyle{entity} = [draw, fill=gray!10, rectangle, minimum height=3em, minimum width=7em, node distance=5em, font={\ttfamily}]
\node[entity] (design) {RTLIL::Design};
\node[entity] (module) [right of=design, node distance=11em] {RTLIL::Module} edge [-latex] node[above] {\tiny 1 \hskip3em N} (design);
\node[entity] (process) [fill=green!10, right of=module, node distance=10em] {RTLIL::Process} (process.west) edge [-latex] (module);
\node[entity] (memory) [fill=red!10, below of=process] {RTLIL::Memory} edge [-latex] (module);
\node[entity] (wire) [fill=blue!10, above of=process] {RTLIL::Wire} (wire.west) edge [-latex] (module);
\node[entity] (cell) [fill=blue!10, above of=wire] {RTLIL::Cell} (cell.west) edge [-latex] (module);
\node[entity] (case) [fill=green!10, right of=process, node distance=10em] {RTLIL::CaseRule} edge [latex-latex] (process);
\node[entity] (sync) [fill=green!10, above of=case] {RTLIL::SyncRule} edge [-latex] (process);
\node[entity] (switch) [fill=green!10, below of=case] {RTLIL::SwitchRule} edge [-latex] (case);
\draw[latex-] (switch.east) -- ++(1em,0) |- (case.east);
\end{tikzpicture}
\end{document}

Some files were not shown because too many files have changed in this diff Show more