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:
commit
5b7d436851
1758 changed files with 182731 additions and 54865 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
$Format:%h$
|
||||
$Format:%H$
|
||||
|
|
|
|||
75
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
75
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal 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
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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
54
.github/ISSUE_TEMPLATE/docs_report.yml
vendored
Normal 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
|
||||
25
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
25
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal 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
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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._
|
||||
91
.github/actions/setup-build-env/action.yml
vendored
Normal file
91
.github/actions/setup-build-env/action.yml
vendored
Normal 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 }}
|
||||
70
.github/actions/setup-iverilog/action.yml
vendored
Normal file
70
.github/actions/setup-iverilog/action.yml
vendored
Normal 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 }}
|
||||
24
.github/issue_template.md
vendored
24
.github/issue_template.md
vendored
|
|
@ -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
35
.github/workflows/codeql.yml
vendored
Normal 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
|
||||
24
.github/workflows/emcc.yml
vendored
24
.github/workflows/emcc.yml
vendored
|
|
@ -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
126
.github/workflows/extra-builds.yml
vendored
Normal 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
83
.github/workflows/prepare-docs.yml
vendored
Normal 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
34
.github/workflows/source-vendor.yml
vendored
Normal 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
265
.github/workflows/test-build.yml
vendored
Normal 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
91
.github/workflows/test-compile.yml
vendored
Normal 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
|
||||
130
.github/workflows/test-linux.yml
vendored
130
.github/workflows/test-linux.yml
vendored
|
|
@ -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
|
||||
127
.github/workflows/test-macos.yml
vendored
127
.github/workflows/test-macos.yml
vendored
|
|
@ -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
78
.github/workflows/test-sanitizers.yml
vendored
Normal 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
124
.github/workflows/test-verific.yml
vendored
Normal 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
25
.github/workflows/update-flake-lock.yml
vendored
Normal 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
|
||||
14
.github/workflows/version.yml
vendored
14
.github/workflows/version.yml
vendored
|
|
@ -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 }}
|
||||
|
|
|
|||
37
.github/workflows/vs.yml
vendored
37
.github/workflows/vs.yml
vendored
|
|
@ -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
136
.github/workflows/wheels.yml
vendored
Normal 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
|
||||
52
.github/workflows/wheels/_run_cibw_linux.py
vendored
Normal file
52
.github/workflows/wheels/_run_cibw_linux.py
vendored
Normal 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)
|
||||
37
.github/workflows/wheels/cibw_before_all.sh
vendored
Normal file
37
.github/workflows/wheels/cibw_before_all.sh
vendored
Normal 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
|
||||
)
|
||||
13
.github/workflows/wheels/cibw_before_build.sh
vendored
Normal file
13
.github/workflows/wheels/cibw_before_build.sh
vendored
Normal 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
83
.gitignore
vendored
|
|
@ -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
7
.gitmodules
vendored
Normal 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
|
||||
6
.mailmap
6
.mailmap
|
|
@ -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
20
.readthedocs.yaml
Normal 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
|
||||
6
Brewfile
6
Brewfile
|
|
@ -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
668
CHANGELOG
|
|
@ -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
|
||||
|
|
|
|||
12
CODEOWNERS
12
CODEOWNERS
|
|
@ -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
128
CODE_OF_CONDUCT.md
Normal 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
70
CONTRIBUTING.md
Normal 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.
|
||||
2
COPYING
2
COPYING
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
520
README.md
520
README.md
|
|
@ -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
1
abc
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 799ba632239b2a4db2bacda81de4e6efdc486b0c
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
backends/aiger2/Makefile.inc
Normal file
1
backends/aiger2/Makefile.inc
Normal file
|
|
@ -0,0 +1 @@
|
|||
OBJS += backends/aiger2/aiger.o
|
||||
1488
backends/aiger2/aiger.cc
Normal file
1488
backends/aiger2/aiger.cc
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
18
backends/cxxrtl/runtime/README.txt
Normal file
18
backends/cxxrtl/runtime/README.txt
Normal 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.
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
@ -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" {
|
||||
|
|
@ -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;
|
||||
873
backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h
Normal file
873
backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h
Normal 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 ×tamp) {
|
||||
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 ×tamp) {
|
||||
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 ×tamp) {
|
||||
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 ×tamp) {
|
||||
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 ¤t_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 ×tamp) {
|
||||
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
|
||||
231
backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h
Normal file
231
backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h
Normal 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
|
||||
|
|
@ -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';
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
|
||||
cd ../../
|
||||
|
|
|
|||
4
backends/functional/Makefile.inc
Normal file
4
backends/functional/Makefile.inc
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
OBJS += backends/functional/cxx.o
|
||||
OBJS += backends/functional/smtlib.o
|
||||
OBJS += backends/functional/smtlib_rosette.o
|
||||
OBJS += backends/functional/test_generic.o
|
||||
277
backends/functional/cxx.cc
Normal file
277
backends/functional/cxx.cc
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/functional.h"
|
||||
#include <ctype.h>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
const char *reserved_keywords[] = {
|
||||
"alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit",
|
||||
"atomic_noexcept","auto","bitand","bitor","bool","break","case",
|
||||
"catch","char","char16_t","char32_t","char8_t","class","co_await",
|
||||
"co_return","co_yield","compl","concept","const","const_cast","consteval",
|
||||
"constexpr","constinit","continue","decltype","default","delete",
|
||||
"do","double","dynamic_cast","else","enum","explicit","export",
|
||||
"extern","false","float","for","friend","goto","if","inline",
|
||||
"int","long","mutable","namespace","new","noexcept","not","not_eq",
|
||||
"nullptr","operator","or","or_eq","private","protected","public",
|
||||
"reflexpr","register","reinterpret_cast","requires","return","short",
|
||||
"signed","sizeof","static","static_log_assert","static_cast","struct",
|
||||
"switch","synchronized","template","this","thread_local","throw",
|
||||
"true","try","typedef","typeid","typename","union","unsigned",
|
||||
"using","virtual","void","volatile","wchar_t","while","xor","xor_eq",
|
||||
nullptr
|
||||
};
|
||||
|
||||
template<typename Id> struct CxxScope : public Functional::Scope<Id> {
|
||||
CxxScope() {
|
||||
for(const char **p = reserved_keywords; *p != nullptr; p++)
|
||||
this->reserve(*p);
|
||||
}
|
||||
bool is_character_legal(char c, int index) override {
|
||||
return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || c == '_' || c == '$');
|
||||
}
|
||||
};
|
||||
|
||||
struct CxxType {
|
||||
Functional::Sort sort;
|
||||
CxxType(Functional::Sort sort) : sort(sort) {}
|
||||
std::string to_string() const {
|
||||
if(sort.is_memory()) {
|
||||
return stringf("Memory<%d, %d>", sort.addr_width(), sort.data_width());
|
||||
} else if(sort.is_signal()) {
|
||||
return stringf("Signal<%d>", sort.width());
|
||||
} else {
|
||||
log_error("unknown sort");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using CxxWriter = Functional::Writer;
|
||||
|
||||
struct CxxStruct {
|
||||
std::string name;
|
||||
dict<IdString, CxxType> types;
|
||||
CxxScope<IdString> scope;
|
||||
CxxStruct(std::string name) : name(name)
|
||||
{
|
||||
scope.reserve("fn");
|
||||
scope.reserve("visit");
|
||||
}
|
||||
void insert(IdString name, CxxType type) {
|
||||
scope(name, name);
|
||||
types.insert({name, type});
|
||||
}
|
||||
void print(CxxWriter &f) {
|
||||
f.print("\tstruct {} {{\n", name);
|
||||
for (auto p : types) {
|
||||
f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first, p.first));
|
||||
}
|
||||
f.print("\n\t\ttemplate <typename T> void visit(T &&fn) {{\n");
|
||||
for (auto p : types) {
|
||||
f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first, p.first));
|
||||
}
|
||||
f.print("\t\t}}\n");
|
||||
f.print("\t}};\n\n");
|
||||
};
|
||||
std::string operator[](IdString field) {
|
||||
return scope(field, field);
|
||||
}
|
||||
};
|
||||
|
||||
std::string cxx_const(RTLIL::Const const &value) {
|
||||
std::stringstream ss;
|
||||
ss << "Signal<" << value.size() << ">(" << std::hex << std::showbase;
|
||||
if(value.size() > 32) ss << "{";
|
||||
for(int i = 0; i < value.size(); i += 32) {
|
||||
if(i > 0) ss << ", ";
|
||||
ss << value.extract(i, 32).as_int();
|
||||
}
|
||||
if(value.size() > 32) ss << "}";
|
||||
ss << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template<class NodePrinter> struct CxxPrintVisitor : public Functional::AbstractVisitor<void> {
|
||||
using Node = Functional::Node;
|
||||
CxxWriter &f;
|
||||
NodePrinter np;
|
||||
CxxStruct &input_struct;
|
||||
CxxStruct &state_struct;
|
||||
CxxPrintVisitor(CxxWriter &f, NodePrinter np, CxxStruct &input_struct, CxxStruct &state_struct) : f(f), np(np), input_struct(input_struct), state_struct(state_struct) { }
|
||||
template<typename... Args> void print(const char *fmt, Args&&... args) {
|
||||
f.print_with(np, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
void buf(Node, Node n) override { print("{}", n); }
|
||||
void slice(Node, Node a, int offset, int out_width) override { print("{0}.slice<{2}>({1})", a, offset, out_width); }
|
||||
void zero_extend(Node, Node a, int out_width) override { print("{}.zero_extend<{}>()", a, out_width); }
|
||||
void sign_extend(Node, Node a, int out_width) override { print("{}.sign_extend<{}>()", a, out_width); }
|
||||
void concat(Node, Node a, Node b) override { print("{}.concat({})", a, b); }
|
||||
void add(Node, Node a, Node b) override { print("{} + {}", a, b); }
|
||||
void sub(Node, Node a, Node b) override { print("{} - {}", a, b); }
|
||||
void mul(Node, Node a, Node b) override { print("{} * {}", a, b); }
|
||||
void unsigned_div(Node, Node a, Node b) override { print("{} / {}", a, b); }
|
||||
void unsigned_mod(Node, Node a, Node b) override { print("{} % {}", a, b); }
|
||||
void bitwise_and(Node, Node a, Node b) override { print("{} & {}", a, b); }
|
||||
void bitwise_or(Node, Node a, Node b) override { print("{} | {}", a, b); }
|
||||
void bitwise_xor(Node, Node a, Node b) override { print("{} ^ {}", a, b); }
|
||||
void bitwise_not(Node, Node a) override { print("~{}", a); }
|
||||
void unary_minus(Node, Node a) override { print("-{}", a); }
|
||||
void reduce_and(Node, Node a) override { print("{}.all()", a); }
|
||||
void reduce_or(Node, Node a) override { print("{}.any()", a); }
|
||||
void reduce_xor(Node, Node a) override { print("{}.parity()", a); }
|
||||
void equal(Node, Node a, Node b) override { print("{} == {}", a, b); }
|
||||
void not_equal(Node, Node a, Node b) override { print("{} != {}", a, b); }
|
||||
void signed_greater_than(Node, Node a, Node b) override { print("{}.signed_greater_than({})", a, b); }
|
||||
void signed_greater_equal(Node, Node a, Node b) override { print("{}.signed_greater_equal({})", a, b); }
|
||||
void unsigned_greater_than(Node, Node a, Node b) override { print("{} > {}", a, b); }
|
||||
void unsigned_greater_equal(Node, Node a, Node b) override { print("{} >= {}", a, b); }
|
||||
void logical_shift_left(Node, Node a, Node b) override { print("{} << {}", a, b); }
|
||||
void logical_shift_right(Node, Node a, Node b) override { print("{} >> {}", a, b); }
|
||||
void arithmetic_shift_right(Node, Node a, Node b) override { print("{}.arithmetic_shift_right({})", a, b); }
|
||||
void mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); }
|
||||
void constant(Node, RTLIL::Const const & value) override { print("{}", cxx_const(value)); }
|
||||
void input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); print("input.{}", input_struct[name]); }
|
||||
void state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); print("current_state.{}", state_struct[name]); }
|
||||
void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); }
|
||||
void memory_write(Node, Node mem, Node addr, Node data) override { print("{}.write({}, {})", mem, addr, data); }
|
||||
};
|
||||
|
||||
bool equal_def(RTLIL::Const const &a, RTLIL::Const const &b) {
|
||||
if(a.size() != b.size()) return false;
|
||||
for(int i = 0; i < a.size(); i++)
|
||||
if((a[i] == State::S1) != (b[i] == State::S1))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct CxxModule {
|
||||
Functional::IR ir;
|
||||
CxxStruct input_struct, output_struct, state_struct;
|
||||
std::string module_name;
|
||||
|
||||
explicit CxxModule(Module *module) :
|
||||
ir(Functional::IR::from_module(module)),
|
||||
input_struct("Inputs"),
|
||||
output_struct("Outputs"),
|
||||
state_struct("State")
|
||||
{
|
||||
for (auto input : ir.inputs())
|
||||
input_struct.insert(input->name, input->sort);
|
||||
for (auto output : ir.outputs())
|
||||
output_struct.insert(output->name, output->sort);
|
||||
for (auto state : ir.states())
|
||||
state_struct.insert(state->name, state->sort);
|
||||
module_name = CxxScope<int>().unique_name(module->name);
|
||||
}
|
||||
void write_header(CxxWriter &f) {
|
||||
f.print("#include \"sim.h\"\n\n");
|
||||
}
|
||||
void write_struct_def(CxxWriter &f) {
|
||||
f.print("struct {} {{\n", module_name);
|
||||
input_struct.print(f);
|
||||
output_struct.print(f);
|
||||
state_struct.print(f);
|
||||
f.print("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n");
|
||||
f.print("\tstatic void initialize(State &);\n");
|
||||
f.print("}};\n\n");
|
||||
}
|
||||
void write_initial_def(CxxWriter &f) {
|
||||
f.print("void {0}::initialize({0}::State &state)\n{{\n", module_name);
|
||||
for (auto state : ir.states()) {
|
||||
if (state->sort.is_signal())
|
||||
f.print("\tstate.{} = {};\n", state_struct[state->name], cxx_const(state->initial_value_signal()));
|
||||
else if (state->sort.is_memory()) {
|
||||
f.print("\t{{\n");
|
||||
f.print("\t\tstd::array<Signal<{}>, {}> mem;\n", state->sort.data_width(), 1<<state->sort.addr_width());
|
||||
const auto &contents = state->initial_value_memory();
|
||||
f.print("\t\tmem.fill({});\n", cxx_const(contents.default_value()));
|
||||
for(auto range : contents)
|
||||
for(auto addr = range.base(); addr < range.limit(); addr++)
|
||||
if(!equal_def(range[addr], contents.default_value()))
|
||||
f.print("\t\tmem[{}] = {};\n", addr, cxx_const(range[addr]));
|
||||
f.print("\t\tstate.{} = mem;\n", state_struct[state->name]);
|
||||
f.print("\t}}\n");
|
||||
}
|
||||
}
|
||||
f.print("}}\n\n");
|
||||
}
|
||||
void write_eval_def(CxxWriter &f) {
|
||||
f.print("void {0}::eval({0}::Inputs const &input, {0}::Outputs &output, {0}::State const ¤t_state, {0}::State &next_state)\n{{\n", module_name);
|
||||
CxxScope<int> locals;
|
||||
locals.reserve("input");
|
||||
locals.reserve("output");
|
||||
locals.reserve("current_state");
|
||||
locals.reserve("next_state");
|
||||
auto node_name = [&](Functional::Node n) { return locals(n.id(), n.name()); };
|
||||
CxxPrintVisitor printVisitor(f, node_name, input_struct, state_struct);
|
||||
for (auto node : ir) {
|
||||
f.print("\t{} {} = ", CxxType(node.sort()).to_string(), node_name(node));
|
||||
node.visit(printVisitor);
|
||||
f.print(";\n");
|
||||
}
|
||||
for (auto state : ir.states())
|
||||
f.print("\tnext_state.{} = {};\n", state_struct[state->name], node_name(state->next_value()));
|
||||
for (auto output : ir.outputs())
|
||||
f.print("\toutput.{} = {};\n", output_struct[output->name], node_name(output->value()));
|
||||
f.print("}}\n\n");
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionalCxxBackend : public Backend
|
||||
{
|
||||
FunctionalCxxBackend() : Backend("functional_cxx", "convert design to C++ using the functional backend") {}
|
||||
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log("TODO: add help message\n");
|
||||
log("\n");
|
||||
}
|
||||
|
||||
void printCxx(std::ostream &stream, std::string, Module *module)
|
||||
{
|
||||
CxxWriter f(stream);
|
||||
CxxModule mod(module);
|
||||
mod.write_header(f);
|
||||
mod.write_struct_def(f);
|
||||
mod.write_eval_def(f);
|
||||
mod.write_initial_def(f);
|
||||
}
|
||||
|
||||
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
log_header(design, "Executing Functional C++ backend.\n");
|
||||
|
||||
size_t argidx = 1;
|
||||
extra_args(f, filename, args, argidx, design);
|
||||
|
||||
for (auto module : design->selected_modules()) {
|
||||
log("Dumping module `%s'.\n", module->name);
|
||||
printCxx(*f, filename, module);
|
||||
}
|
||||
}
|
||||
} FunctionalCxxBackend;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
418
backends/functional/cxx_runtime/sim.h
Normal file
418
backends/functional/cxx_runtime/sim.h
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SIM_H
|
||||
#define SIM_H
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
template<size_t n>
|
||||
class Signal {
|
||||
template<size_t m> friend class Signal;
|
||||
std::array<bool, n> _bits;
|
||||
public:
|
||||
Signal() { }
|
||||
Signal(uint32_t val)
|
||||
{
|
||||
for(size_t i = 0; i < n; i++)
|
||||
if(i < 32)
|
||||
_bits[i] = val & (1<<i);
|
||||
else
|
||||
_bits[i] = false;
|
||||
}
|
||||
|
||||
Signal(std::initializer_list<uint32_t> vals)
|
||||
{
|
||||
size_t k, i;
|
||||
|
||||
k = 0;
|
||||
for (auto val : vals) {
|
||||
for(i = 0; i < 32; i++)
|
||||
if(i + k < n)
|
||||
_bits[i + k] = val & (1<<i);
|
||||
k += 32;
|
||||
}
|
||||
for(; k < n; k++)
|
||||
_bits[k] = false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static Signal from_array(T vals)
|
||||
{
|
||||
size_t k, i;
|
||||
Signal ret;
|
||||
|
||||
k = 0;
|
||||
for (auto val : vals) {
|
||||
for(i = 0; i < 32; i++)
|
||||
if(i + k < n)
|
||||
ret._bits[i + k] = val & (1<<i);
|
||||
k += 32;
|
||||
}
|
||||
for(; k < n; k++)
|
||||
ret._bits[k] = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static Signal from_signed(int32_t val)
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
if(i < 32)
|
||||
ret._bits[i] = val & (1<<i);
|
||||
else
|
||||
ret._bits[i] = val < 0;
|
||||
return ret;
|
||||
}
|
||||
static Signal repeat(bool b)
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = b;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int size() const { return n; }
|
||||
bool operator[](int i) const { assert(n >= 0 && i < n); return _bits[i]; }
|
||||
|
||||
template<size_t m>
|
||||
Signal<m> slice(size_t offset) const
|
||||
{
|
||||
Signal<m> ret;
|
||||
|
||||
assert(offset + m <= n);
|
||||
std::copy(_bits.begin() + offset, _bits.begin() + offset + m, ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool any() const
|
||||
{
|
||||
for(int i = 0; i < n; i++)
|
||||
if(_bits[i])
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool all() const
|
||||
{
|
||||
for(int i = 0; i < n; i++)
|
||||
if(!_bits[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parity() const
|
||||
{
|
||||
bool result = false;
|
||||
for(int i = 0; i < n; i++)
|
||||
result ^= _bits[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
bool sign() const { return _bits[n-1]; }
|
||||
|
||||
template<typename T>
|
||||
T as_numeric() const
|
||||
{
|
||||
T ret = 0;
|
||||
for(size_t i = 0; i < std::min<size_t>(sizeof(T) * 8, n); i++)
|
||||
if(_bits[i])
|
||||
ret |= ((T)1)<<i;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T as_numeric_clamped() const
|
||||
{
|
||||
for(size_t i = sizeof(T) * 8; i < n; i++)
|
||||
if(_bits[i])
|
||||
return ~((T)0);
|
||||
return as_numeric<T>();
|
||||
}
|
||||
|
||||
uint32_t as_int() const { return as_numeric<uint32_t>(); }
|
||||
|
||||
private:
|
||||
std::string as_string_p2(int b) const {
|
||||
std::string ret;
|
||||
for(int i = (n - 1) - (n - 1) % b; i >= 0; i -= b)
|
||||
ret += "0123456789abcdef"[(*this >> Signal<32>(i)).as_int() & ((1<<b)-1)];
|
||||
return ret;
|
||||
}
|
||||
std::string as_string_b10() const {
|
||||
std::string ret;
|
||||
if(n < 4) return std::string() + (char)('0' + as_int());
|
||||
Signal<n> t = *this;
|
||||
Signal<n> b = 10;
|
||||
do{
|
||||
ret += (char)('0' + (t % b).as_int());
|
||||
t = t / b;
|
||||
}while(t.any());
|
||||
std::reverse(ret.begin(), ret.end());
|
||||
return ret;
|
||||
}
|
||||
public:
|
||||
std::string as_string(int base = 16, bool showbase = true) const {
|
||||
std::string ret;
|
||||
if(showbase) {
|
||||
ret += std::to_string(n);
|
||||
switch(base) {
|
||||
case 2: ret += "'b"; break;
|
||||
case 8: ret += "'o"; break;
|
||||
case 10: ret += "'d"; break;
|
||||
case 16: ret += "'h"; break;
|
||||
default: assert(0);
|
||||
}
|
||||
}
|
||||
switch(base) {
|
||||
case 2: return ret + as_string_p2(1);
|
||||
case 8: return ret + as_string_p2(3);
|
||||
case 10: return ret + as_string_b10();
|
||||
case 16: return ret + as_string_p2(4);
|
||||
default: assert(0);
|
||||
}
|
||||
}
|
||||
friend std::ostream &operator << (std::ostream &os, Signal<n> const &s) { return os << s.as_string(); }
|
||||
|
||||
Signal<n> operator ~() const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = !_bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator -() const
|
||||
{
|
||||
Signal<n> ret;
|
||||
int x = 1;
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
x += (int)!_bits[i];
|
||||
ret._bits[i] = (x & 1) != 0;
|
||||
x >>= 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator +(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
int x = 0;
|
||||
for(size_t i = 0; i < n; i++){
|
||||
x += (int)_bits[i] + (int)b._bits[i];
|
||||
ret._bits[i] = x & 1;
|
||||
x >>= 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator -(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
int x = 1;
|
||||
for(size_t i = 0; i < n; i++){
|
||||
x += (int)_bits[i] + (int)!b._bits[i];
|
||||
ret._bits[i] = x & 1;
|
||||
x >>= 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator *(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
int x = 0;
|
||||
for(size_t i = 0; i < n; i++){
|
||||
for(size_t j = 0; j <= i; j++)
|
||||
x += (int)_bits[j] & (int)b._bits[i-j];
|
||||
ret._bits[i] = x & 1;
|
||||
x >>= 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
Signal<n> divmod(Signal<n> const &b, bool modulo) const
|
||||
{
|
||||
if(!b.any()) return 0;
|
||||
Signal<n> q = 0;
|
||||
Signal<n> r = 0;
|
||||
for(size_t i = n; i-- != 0; ){
|
||||
r = r << Signal<1>(1);
|
||||
r._bits[0] = _bits[i];
|
||||
if(r >= b){
|
||||
r = r - b;
|
||||
q._bits[i] = true;
|
||||
}
|
||||
}
|
||||
return modulo ? r : q;
|
||||
}
|
||||
public:
|
||||
|
||||
Signal<n> operator /(Signal<n> const &b) const { return divmod(b, false); }
|
||||
Signal<n> operator %(Signal<n> const &b) const { return divmod(b, true); }
|
||||
|
||||
bool operator ==(Signal<n> const &b) const
|
||||
{
|
||||
for(size_t i = 0; i < n; i++)
|
||||
if(_bits[i] != b._bits[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator >=(Signal<n> const &b) const
|
||||
{
|
||||
for(size_t i = n; i-- != 0; )
|
||||
if(_bits[i] != b._bits[i])
|
||||
return _bits[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator >(Signal<n> const &b) const
|
||||
{
|
||||
for(size_t i = n; i-- != 0; )
|
||||
if(_bits[i] != b._bits[i])
|
||||
return _bits[i];
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator !=(Signal<n> const &b) const { return !(*this == b); }
|
||||
bool operator <=(Signal<n> const &b) const { return b <= *this; }
|
||||
bool operator <(Signal<n> const &b) const { return b < *this; }
|
||||
|
||||
bool signed_greater_than(Signal<n> const &b) const
|
||||
{
|
||||
if(_bits[n-1] != b._bits[n-1])
|
||||
return b._bits[n-1];
|
||||
return *this > b;
|
||||
}
|
||||
|
||||
bool signed_greater_equal(Signal<n> const &b) const
|
||||
{
|
||||
if(_bits[n-1] != b._bits[n-1])
|
||||
return b._bits[n-1];
|
||||
return *this >= b;
|
||||
}
|
||||
|
||||
Signal<n> operator &(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = _bits[i] && b._bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator |(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = _bits[i] || b._bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator ^(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = _bits[i] != b._bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t nb>
|
||||
Signal<n> operator <<(Signal<nb> const &b) const
|
||||
{
|
||||
Signal<n> ret = 0;
|
||||
size_t amount = b.template as_numeric_clamped<size_t>();
|
||||
if(amount < n)
|
||||
std::copy(_bits.begin(), _bits.begin() + (n - amount), ret._bits.begin() + amount);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t nb>
|
||||
Signal<n> operator >>(Signal<nb> const &b) const
|
||||
{
|
||||
Signal<n> ret = 0;
|
||||
size_t amount = b.template as_numeric_clamped<size_t>();
|
||||
if(amount < n)
|
||||
std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t nb>
|
||||
Signal<n> arithmetic_shift_right(Signal<nb> const &b) const
|
||||
{
|
||||
Signal<n> ret = Signal::repeat(sign());
|
||||
size_t amount = b.template as_numeric_clamped<size_t>();
|
||||
if(amount < n)
|
||||
std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t m>
|
||||
Signal<n+m> concat(Signal<m> const& b) const
|
||||
{
|
||||
Signal<n + m> ret;
|
||||
std::copy(_bits.begin(), _bits.end(), ret._bits.begin());
|
||||
std::copy(b._bits.begin(), b._bits.end(), ret._bits.begin() + n);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t m>
|
||||
Signal<m> zero_extend() const
|
||||
{
|
||||
assert(m >= n);
|
||||
Signal<m> ret = 0;
|
||||
std::copy(_bits.begin(), _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t m>
|
||||
Signal<m> sign_extend() const
|
||||
{
|
||||
assert(m >= n);
|
||||
Signal<m> ret = Signal<m>::repeat(sign());
|
||||
std::copy(_bits.begin(), _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template<size_t a, size_t d>
|
||||
class Memory {
|
||||
std::array<Signal<d>, 1<<a> _contents;
|
||||
public:
|
||||
Memory() {}
|
||||
Memory(std::array<Signal<d>, 1<<a> const &contents) : _contents(contents) {}
|
||||
Signal<d> read(Signal<a> addr) const
|
||||
{
|
||||
return _contents[addr.template as_numeric<size_t>()];
|
||||
}
|
||||
Memory write(Signal<a> addr, Signal<d> data) const
|
||||
{
|
||||
Memory ret = *this;
|
||||
ret._contents[addr.template as_numeric<size_t>()] = data;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
295
backends/functional/smtlib.cc
Normal file
295
backends/functional/smtlib.cc
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/functional.h"
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/sexpr.h"
|
||||
#include <ctype.h>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
using SExprUtil::list;
|
||||
|
||||
const char *reserved_keywords[] = {
|
||||
// reserved keywords from the smtlib spec
|
||||
"BINARY", "DECIMAL", "HEXADECIMAL", "NUMERAL", "STRING", "_", "!", "as", "let", "exists", "forall", "match", "par",
|
||||
"assert", "check-sat", "check-sat-assuming", "declare-const", "declare-datatype", "declare-datatypes",
|
||||
"declare-fun", "declare-sort", "define-fun", "define-fun-rec", "define-funs-rec", "define-sort",
|
||||
"exit", "get-assertions", "symbol", "sort", "get-assignment", "get-info", "get-model",
|
||||
"get-option", "get-proof", "get-unsat-assumptions", "get-unsat-core", "get-value",
|
||||
"pop", "push", "reset", "reset-assertions", "set-info", "set-logic", "set-option",
|
||||
|
||||
// reserved for our own purposes
|
||||
"pair", "Pair", "first", "second",
|
||||
"inputs", "state",
|
||||
nullptr
|
||||
};
|
||||
|
||||
struct SmtScope : public Functional::Scope<int> {
|
||||
SmtScope() {
|
||||
for(const char **p = reserved_keywords; *p != nullptr; p++)
|
||||
reserve(*p);
|
||||
}
|
||||
bool is_character_legal(char c, int index) override {
|
||||
return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("~!@$%^&*_-+=<>.?/", c));
|
||||
}
|
||||
};
|
||||
|
||||
struct SmtSort {
|
||||
Functional::Sort sort;
|
||||
SmtSort(Functional::Sort sort) : sort(sort) {}
|
||||
SExpr to_sexpr() const {
|
||||
if(sort.is_memory()) {
|
||||
return list("Array", list("_", "BitVec", sort.addr_width()), list("_", "BitVec", sort.data_width()));
|
||||
} else if(sort.is_signal()) {
|
||||
return list("_", "BitVec", sort.width());
|
||||
} else {
|
||||
log_error("unknown sort");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SmtStruct {
|
||||
struct Field {
|
||||
SmtSort sort;
|
||||
std::string accessor;
|
||||
};
|
||||
idict<IdString> field_names;
|
||||
vector<Field> fields;
|
||||
SmtScope &scope;
|
||||
public:
|
||||
std::string name;
|
||||
SmtStruct(std::string name, SmtScope &scope) : scope(scope), name(name) {}
|
||||
void insert(IdString field_name, SmtSort sort) {
|
||||
field_names(field_name);
|
||||
auto accessor = scope.unique_name("\\" + name + "_" + RTLIL::unescape_id(field_name));
|
||||
fields.emplace_back(Field{sort, accessor});
|
||||
}
|
||||
void write_definition(SExprWriter &w) {
|
||||
w.open(list("declare-datatype", name));
|
||||
w.open(list());
|
||||
w.open(list(name));
|
||||
for(const auto &field : fields)
|
||||
w << list(field.accessor, field.sort.to_sexpr());
|
||||
w.close(3);
|
||||
}
|
||||
template<typename Fn> void write_value(SExprWriter &w, Fn fn) {
|
||||
if(field_names.empty()) {
|
||||
// Zero-argument constructors in SMTLIB must not be called as functions.
|
||||
w << name;
|
||||
} else {
|
||||
w.open(list(name));
|
||||
for(auto field_name : field_names) {
|
||||
w << fn(field_name);
|
||||
w.comment(RTLIL::unescape_id(field_name), true);
|
||||
}
|
||||
w.close();
|
||||
}
|
||||
}
|
||||
SExpr access(SExpr record, IdString name) {
|
||||
size_t i = field_names.at(name);
|
||||
return list(fields[i].accessor, std::move(record));
|
||||
}
|
||||
};
|
||||
|
||||
std::string smt_const(RTLIL::Const const &c) {
|
||||
std::string s = "#b";
|
||||
for(int i = c.size(); i-- > 0; )
|
||||
s += c[i] == State::S1 ? '1' : '0';
|
||||
return s;
|
||||
}
|
||||
|
||||
struct SmtPrintVisitor : public Functional::AbstractVisitor<SExpr> {
|
||||
using Node = Functional::Node;
|
||||
std::function<SExpr(Node)> n;
|
||||
SmtStruct &input_struct;
|
||||
SmtStruct &state_struct;
|
||||
|
||||
SmtPrintVisitor(SmtStruct &input_struct, SmtStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {}
|
||||
|
||||
SExpr from_bool(SExpr &&arg) {
|
||||
return list("ite", std::move(arg), "#b1", "#b0");
|
||||
}
|
||||
SExpr to_bool(SExpr &&arg) {
|
||||
return list("=", std::move(arg), "#b1");
|
||||
}
|
||||
SExpr extract(SExpr &&arg, int offset, int out_width = 1) {
|
||||
return list(list("_", "extract", offset + out_width - 1, offset), std::move(arg));
|
||||
}
|
||||
|
||||
SExpr buf(Node, Node a) override { return n(a); }
|
||||
SExpr slice(Node, Node a, int offset, int out_width) override { return extract(n(a), offset, out_width); }
|
||||
SExpr zero_extend(Node, Node a, int out_width) override { return list(list("_", "zero_extend", out_width - a.width()), n(a)); }
|
||||
SExpr sign_extend(Node, Node a, int out_width) override { return list(list("_", "sign_extend", out_width - a.width()), n(a)); }
|
||||
SExpr concat(Node, Node a, Node b) override { return list("concat", n(b), n(a)); }
|
||||
SExpr add(Node, Node a, Node b) override { return list("bvadd", n(a), n(b)); }
|
||||
SExpr sub(Node, Node a, Node b) override { return list("bvsub", n(a), n(b)); }
|
||||
SExpr mul(Node, Node a, Node b) override { return list("bvmul", n(a), n(b)); }
|
||||
SExpr unsigned_div(Node, Node a, Node b) override { return list("bvudiv", n(a), n(b)); }
|
||||
SExpr unsigned_mod(Node, Node a, Node b) override { return list("bvurem", n(a), n(b)); }
|
||||
SExpr bitwise_and(Node, Node a, Node b) override { return list("bvand", n(a), n(b)); }
|
||||
SExpr bitwise_or(Node, Node a, Node b) override { return list("bvor", n(a), n(b)); }
|
||||
SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); }
|
||||
SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); }
|
||||
SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); }
|
||||
SExpr reduce_and(Node, Node a) override { return from_bool(list("=", n(a), smt_const(RTLIL::Const(State::S1, a.width())))); }
|
||||
SExpr reduce_or(Node, Node a) override { return from_bool(list("distinct", n(a), smt_const(RTLIL::Const(State::S0, a.width())))); }
|
||||
SExpr reduce_xor(Node, Node a) override {
|
||||
vector<SExpr> s { "bvxor" };
|
||||
for(int i = 0; i < a.width(); i++)
|
||||
s.push_back(extract(n(a), i));
|
||||
return s;
|
||||
}
|
||||
SExpr equal(Node, Node a, Node b) override { return from_bool(list("=", n(a), n(b))); }
|
||||
SExpr not_equal(Node, Node a, Node b) override { return from_bool(list("distinct", n(a), n(b))); }
|
||||
SExpr signed_greater_than(Node, Node a, Node b) override { return from_bool(list("bvsgt", n(a), n(b))); }
|
||||
SExpr signed_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvsge", n(a), n(b))); }
|
||||
SExpr unsigned_greater_than(Node, Node a, Node b) override { return from_bool(list("bvugt", n(a), n(b))); }
|
||||
SExpr unsigned_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvuge", n(a), n(b))); }
|
||||
|
||||
SExpr extend(SExpr &&a, int in_width, int out_width) {
|
||||
if(in_width < out_width)
|
||||
return list(list("_", "zero_extend", out_width - in_width), std::move(a));
|
||||
else
|
||||
return std::move(a);
|
||||
}
|
||||
SExpr logical_shift_left(Node, Node a, Node b) override { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); }
|
||||
SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); }
|
||||
SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); }
|
||||
SExpr mux(Node, Node a, Node b, Node s) override { return list("ite", to_bool(n(s)), n(b), n(a)); }
|
||||
SExpr constant(Node, RTLIL::Const const &value) override { return smt_const(value); }
|
||||
SExpr memory_read(Node, Node mem, Node addr) override { return list("select", n(mem), n(addr)); }
|
||||
SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("store", n(mem), n(addr), n(data)); }
|
||||
|
||||
SExpr input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); return input_struct.access("inputs", name); }
|
||||
SExpr state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); return state_struct.access("state", name); }
|
||||
};
|
||||
|
||||
struct SmtModule {
|
||||
Functional::IR ir;
|
||||
SmtScope scope;
|
||||
std::string name;
|
||||
|
||||
SmtStruct input_struct;
|
||||
SmtStruct output_struct;
|
||||
SmtStruct state_struct;
|
||||
|
||||
SmtModule(Module *module)
|
||||
: ir(Functional::IR::from_module(module))
|
||||
, scope()
|
||||
, name(scope.unique_name(module->name))
|
||||
, input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope)
|
||||
, output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope)
|
||||
, state_struct(scope.unique_name(module->name.str() + "_State"), scope)
|
||||
{
|
||||
scope.reserve(name + "-initial");
|
||||
for (auto input : ir.inputs())
|
||||
input_struct.insert(input->name, input->sort);
|
||||
for (auto output : ir.outputs())
|
||||
output_struct.insert(output->name, output->sort);
|
||||
for (auto state : ir.states())
|
||||
state_struct.insert(state->name, state->sort);
|
||||
}
|
||||
|
||||
void write_eval(SExprWriter &w)
|
||||
{
|
||||
w.push();
|
||||
w.open(list("define-fun", name,
|
||||
list(list("inputs", input_struct.name),
|
||||
list("state", state_struct.name)),
|
||||
list("Pair", output_struct.name, state_struct.name)));
|
||||
auto inlined = [&](Functional::Node n) {
|
||||
return n.fn() == Functional::Fn::constant;
|
||||
};
|
||||
SmtPrintVisitor visitor(input_struct, state_struct);
|
||||
auto node_to_sexpr = [&](Functional::Node n) -> SExpr {
|
||||
if(inlined(n))
|
||||
return n.visit(visitor);
|
||||
else
|
||||
return scope(n.id(), n.name());
|
||||
};
|
||||
visitor.n = node_to_sexpr;
|
||||
for(auto n : ir)
|
||||
if(!inlined(n)) {
|
||||
w.open(list("let", list(list(node_to_sexpr(n), n.visit(visitor)))), false);
|
||||
w.comment(SmtSort(n.sort()).to_sexpr().to_string(), true);
|
||||
}
|
||||
w.open(list("pair"));
|
||||
output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.output(name).value()); });
|
||||
state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.state(name).next_value()); });
|
||||
w.pop();
|
||||
}
|
||||
|
||||
void write_initial(SExprWriter &w)
|
||||
{
|
||||
std::string initial = name + "-initial";
|
||||
w << list("declare-const", initial, state_struct.name);
|
||||
for (auto state : ir.states()) {
|
||||
if(state->sort.is_signal())
|
||||
w << list("assert", list("=", state_struct.access(initial, state->name), smt_const(state->initial_value_signal())));
|
||||
else if(state->sort.is_memory()) {
|
||||
const auto &contents = state->initial_value_memory();
|
||||
for(int i = 0; i < 1<<state->sort.addr_width(); i++) {
|
||||
auto addr = smt_const(RTLIL::Const(i, state->sort.addr_width()));
|
||||
w << list("assert", list("=", list("select", state_struct.access(initial, state->name), addr), smt_const(contents[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void write(std::ostream &out)
|
||||
{
|
||||
SExprWriter w(out);
|
||||
|
||||
input_struct.write_definition(w);
|
||||
output_struct.write_definition(w);
|
||||
state_struct.write_definition(w);
|
||||
|
||||
w << list("declare-datatypes",
|
||||
list(list("Pair", 2)),
|
||||
list(list("par", list("X", "Y"), list(list("pair", list("first", "X"), list("second", "Y"))))));
|
||||
|
||||
write_eval(w);
|
||||
write_initial(w);
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionalSmtBackend : public Backend {
|
||||
FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {}
|
||||
|
||||
void help() override { log("\nFunctional SMT Backend.\n\n"); }
|
||||
|
||||
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
log_header(design, "Executing Functional SMT Backend.\n");
|
||||
|
||||
size_t argidx = 1;
|
||||
extra_args(f, filename, args, argidx, design);
|
||||
|
||||
for (auto module : design->selected_modules()) {
|
||||
log("Processing module `%s`.\n", module->name);
|
||||
SmtModule smt(module);
|
||||
smt.write(*f);
|
||||
}
|
||||
}
|
||||
} FunctionalSmtBackend;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
373
backends/functional/smtlib_rosette.cc
Normal file
373
backends/functional/smtlib_rosette.cc
Normal 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
|
||||
158
backends/functional/test_generic.cc
Normal file
158
backends/functional/test_generic.cc
Normal 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
|
||||
|
|
@ -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 ¶m : 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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ¶m : 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;
|
||||
|
|
|
|||
2
backends/protobuf/.gitignore
vendored
2
backends/protobuf/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
yosys.pb.cc
|
||||
yosys.pb.h
|
||||
|
|
@ -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
|
||||
|
|
@ -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> ¶meters)
|
||||
{
|
||||
for (auto ¶m : 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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
616
backends/smt2/smtbmc_incremental.py
Normal file
616
backends/smt2/smtbmc_incremental.py
Normal 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)
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
487
backends/smt2/witness.py
Normal 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
432
backends/smt2/ywio.py
Normal 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)
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
9
docs/.gitignore
vendored
Normal 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
262
docs/Makefile
Normal 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
|
||||
BIN
docs/source/_downloads/APPNOTE_010_Verilog_to_BLIF.pdf
Normal file
BIN
docs/source/_downloads/APPNOTE_010_Verilog_to_BLIF.pdf
Normal file
Binary file not shown.
BIN
docs/source/_downloads/APPNOTE_012_Verilog_to_BTOR.pdf
Normal file
BIN
docs/source/_downloads/APPNOTE_012_Verilog_to_BTOR.pdf
Normal file
Binary file not shown.
50
docs/source/_images/Makefile
Normal file
50
docs/source/_images/Makefile
Normal 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
|
||||
38
docs/source/_images/internals/approach_flow.tex
Normal file
38
docs/source/_images/internals/approach_flow.tex
Normal 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}
|
||||
37
docs/source/_images/internals/overview_flow.tex
Normal file
37
docs/source/_images/internals/overview_flow.tex
Normal 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}
|
||||
27
docs/source/_images/internals/overview_rtlil.tex
Normal file
27
docs/source/_images/internals/overview_rtlil.tex
Normal 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
Loading…
Add table
Add a link
Reference in a new issue