├── .clang-format
├── .github
├── check_license.sh
├── check_python_scripts.sh
└── workflows
│ ├── check-install.yml
│ ├── manylinux-install-cmake.sh
│ ├── presubmit.yml
│ ├── publish-to-pypi.yml
│ └── wheel.yml
├── .gitignore
├── .gitmodules
├── .readthedocs.yml
├── .style.yapf
├── AUTHORS
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── docs
├── Makefile
├── _static
│ ├── favicon.svg
│ ├── image
│ │ └── fasm-diagram.png
│ └── logo.svg
├── conf.py
├── environment.yml
├── index.rst
├── requirements.txt
└── specification
│ ├── introduction.rst
│ ├── line.rst
│ └── syntax.rst
├── environment.yml
├── examples
├── blank.fasm
├── comment.fasm
├── feature_only.fasm
└── many.fasm
├── fasm
├── __init__.py
├── model.py
├── output.py
├── parser
│ ├── __init__.py
│ ├── antlr.py
│ ├── antlr_to_tuple.pyx
│ ├── fasm.tx
│ └── textx.py
├── tool.py
└── version.py
├── pyproject.toml
├── requirements.txt
├── setup.cfg
├── setup.py
├── src
├── CMakeLists.txt
├── ParseFasm.cpp
├── ParseFasmRun.cpp
├── ParseFasmTests.cpp
└── antlr
│ ├── FasmLexer.g4
│ └── FasmParser.g4
├── tests
├── __init__.py
└── test_simple.py
├── third_party
└── antlr4_lib
│ ├── LICENSE.txt
│ ├── README.md
│ └── antlr-4.9.3-complete.jar
├── tox.ini
└── update_version.py
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | BasedOnStyle: Chromium
4 | IndentWidth: 8
5 | TabWidth: 4
6 | UseTab: Never
7 |
--------------------------------------------------------------------------------
/.github/check_license.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Copyright 2017-2022 F4PGA Authors
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | # SPDX-License-Identifier: Apache-2.0
17 |
18 | echo
19 | echo "==========================="
20 | echo "Check SPDX identifier"
21 | echo "==========================="
22 | echo
23 |
24 | ERROR_FILES=""
25 | FILES_TO_CHECK=`git ls-files | grep -ve '^third_party\|^\.' | grep -e '\.sh$\|\.py$\|\.cpp$\|\.h$\|CMakeLists.txt$\|Makefile$'`
26 |
27 | for file in $FILES_TO_CHECK; do
28 | echo "Checking $file"
29 | grep -q "SPDX-License-Identifier" $file || ERROR_FILES="$ERROR_FILES $file"
30 | done
31 |
32 | if [ ! -z "$ERROR_FILES" ]; then
33 | for file in $ERROR_FILES; do
34 | echo "ERROR: $file does not have license information."
35 | done
36 | exit 1
37 | fi
38 |
--------------------------------------------------------------------------------
/.github/check_python_scripts.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Copyright 2017-2022 F4PGA Authors
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | # SPDX-License-Identifier: Apache-2.0
17 |
18 | echo
19 | echo "==================================="
20 | echo "Check Python UTF coding and shebang"
21 | echo "==================================="
22 | echo
23 |
24 | ERROR_FILES_SHEBANG=""
25 | ERROR_FILES_UTF_CODING=""
26 | FILES_TO_CHECK=`git ls-files | grep -ve '^third_party\|^\.' | grep -e '.py$'`
27 |
28 | for file in $FILES_TO_CHECK; do
29 | echo "Checking $file"
30 | grep -q "\#\!/usr/bin/env python3" $file || ERROR_FILES_SHEBANG="$ERROR_FILES_SHEBANG $file"
31 | grep -q "\#.*coding: utf-8" $file || ERROR_FILES_UTF_CODING="$ERROR_FILES_UTF_CODING $file"
32 | done
33 |
34 | if [ ! -z "$ERROR_FILES_SHEBANG" ]; then
35 | for file in $ERROR_FILES_SHEBANG; do
36 | echo "ERROR: $file does not have the python3 shebang."
37 | done
38 | exit 1
39 | fi
40 |
41 | if [ ! -z "$ERROR_FILES_UTF_CODING" ]; then
42 | for file in $ERROR_FILES_UTF_CODING; do
43 | echo "ERROR: $file does not have the UTF encoding set."
44 | echo "Add # coding: utf-8"
45 | done
46 | exit 1
47 | fi
48 |
49 | echo
50 |
--------------------------------------------------------------------------------
/.github/workflows/check-install.yml:
--------------------------------------------------------------------------------
1 | name: Install from
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 |
8 | jobs:
9 |
10 | GitHub:
11 | strategy:
12 | matrix:
13 | os: [windows-latest, macos-latest, ubuntu-20.04]
14 | fail-fast: false
15 |
16 | name: GitHub
17 | runs-on: ${{ matrix.os }}
18 |
19 | steps:
20 | - name: Install dependencies (Ubuntu)
21 | if: startsWith(matrix.os, 'ubuntu')
22 | run: |
23 | sudo apt-get update
24 | sudo apt-get install -y cmake default-jre-headless uuid-dev libantlr4-runtime-dev
25 |
26 | - name: Install dependencies (Mac OS X)
27 | if: startsWith(matrix.os, 'macos')
28 | run: |
29 | true
30 |
31 | - name: Install dependencies (Windows)
32 | if: startsWith(matrix.os, 'windows')
33 | run: |
34 | true
35 |
36 | - name: 🐍 Set up Python
37 | uses: actions/setup-python@v2
38 | with:
39 | python-version: 3.x
40 |
41 | - name: Install pip+wheel
42 | run: |
43 | python -m pip install --user --upgrade wheel
44 | python -m pip install --user --upgrade pip
45 |
46 | - name: Test installation
47 | shell: bash
48 | run: |
49 | python -m pip install git+https://github.com/${GITHUB_REPOSITORY}.git@${GITHUB_SHA}#egg=fasm
50 |
51 | - name: Run Smoke Test
52 | run: |
53 | fasm --help
54 |
55 | # PyPi:
56 | # runs-on: ubuntu-20.04
57 | # name: PyPi
58 |
--------------------------------------------------------------------------------
/.github/workflows/manylinux-install-cmake.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -x
4 | set -e
5 |
6 | # Remove the ancient version of cmake
7 | yum remove cmake -y
8 |
9 | # Add in curl
10 | yum install wget -y
11 |
12 | yum install java-1.8.0-openjdk uuid uuid-devel libuuid libuuid-devel -y
13 |
14 | # Download new cmake
15 | wget https://github.com/Kitware/CMake/releases/download/v3.19.4/cmake-3.19.4-Linux-x86_64.sh -O /tmp/cmake.sh
16 | chmod a+x /tmp/cmake.sh
17 |
18 | # Install cmake into /usr
19 | /tmp/cmake.sh --prefix=/usr --skip-license
20 |
--------------------------------------------------------------------------------
/.github/workflows/presubmit.yml:
--------------------------------------------------------------------------------
1 | # Checks code that code meets requirements for a pull request.
2 | # Any automated checks for code quality and compliance belongs here.
3 | name: presubmit
4 | on: [push, pull_request]
5 | jobs:
6 | check:
7 | name: Source checks
8 | runs-on: ubuntu-20.04
9 | steps:
10 | - uses: actions/checkout@v2
11 | with:
12 | # Always clone the full depth so git-describe works.
13 | fetch-depth: 0
14 | submodules: true
15 | - name: Set up Python
16 | uses: actions/setup-python@v2
17 | - name: Install dependencies
18 | run: |
19 | sudo apt update
20 | sudo apt install clang-format
21 | python -m pip install --upgrade pip
22 | python -m pip install -r requirements.txt
23 | - name: Python style check
24 | run: |
25 | make format lint
26 | test $(git status --porcelain | wc -l) -eq 0 || { git diff; false; }
27 | - name: C++ style check
28 | run: |
29 | make format-cpp
30 | test $(git status --porcelain | wc -l) -eq 0 || { git diff; false; }
31 | - name: Check license
32 | run: make check-license
33 | - name: Python checks
34 | run: make check-python-scripts
35 | test:
36 | name: Test Python package
37 | runs-on: ubuntu-20.04
38 | strategy:
39 | matrix:
40 | antlr_runtime_type: [static, shared]
41 | python_version: [3.5, 3.6, 3.7, 3.8, 3.9]
42 | include:
43 | - python-version: 3.5
44 | TOXENV: py35
45 | - python-version: 3.6
46 | TOXENV: py36
47 | - python-version: 3.7
48 | TOXENV: py37
49 | - python-version: 3.8
50 | TOXENV: py38
51 | - python-version: 3.9
52 | TOXENV: py39
53 | fail-fast: false
54 | steps:
55 | - uses: actions/checkout@v2
56 | with:
57 | # Always clone the full depth so git-describe works.
58 | fetch-depth: 0
59 | submodules: true
60 | - name: Set up Python ${{ matrix.python-version }}
61 | uses: actions/setup-python@v2
62 | with:
63 | python-version: ${{ matrix.python-version }}
64 | - name: Install dependencies
65 | run: |
66 | sudo apt update
67 | sudo apt install cmake default-jre-headless uuid-dev libantlr4-runtime-dev
68 | python -m pip install --upgrade pip
69 | python -m pip install -r requirements.txt
70 | python update_version.py
71 | - name: Tox
72 | run: |
73 | export CMAKE_BUILD_PARALLEL_LEVEL=1
74 | export ANTLR4_RUNTIME_TYPE=${{ matrix.antlr_runtime_type }}
75 | tox -e ${{ matrix.TOXENV }}
76 |
--------------------------------------------------------------------------------
/.github/workflows/publish-to-pypi.yml:
--------------------------------------------------------------------------------
1 | name: PyPI
2 |
3 | on:
4 | push:
5 | pull_request:
6 | workflow_dispatch:
7 |
8 | jobs:
9 |
10 | Source:
11 | runs-on: ubuntu-20.04
12 | name: Source
13 |
14 | steps:
15 | - name: 🧰 Checkout
16 | uses: actions/checkout@v2
17 | with:
18 | # Always clone the full depth so git-describe works.
19 | fetch-depth: 0
20 | submodules: true
21 |
22 | - name: Install dependencies (Ubuntu)
23 | run: |
24 | sudo apt-get update
25 | sudo apt-get install -y cmake default-jre-headless uuid-dev libantlr4-runtime-dev
26 |
27 | - name: 🐍 Set up Python
28 | uses: actions/setup-python@v2
29 | with:
30 | python-version: 3.x
31 |
32 | - name: Install build dependencies
33 | run: |
34 | python -m pip install -U pip
35 | python -m pip install -r requirements.txt
36 | python update_version.py
37 |
38 | - name: Install package dependencies
39 | run: |
40 | python setup.py install
41 |
42 | - name: 🚧 Build distribution 📦
43 | run: |
44 | python setup.py sdist
45 |
46 | - name: Check distribution 📦
47 | run: |
48 | twine check dist/*
49 |
50 | - name: 📤 Publish source to Test PyPI
51 | env:
52 | TWINE_USERNAME: __token__
53 | TWINE_PASSWORD: ${{ secrets.PYPI_TEST_PASSWORD }}
54 | if: env.TWINE_PASSWORD != null
55 | run: |
56 | twine upload --skip-existing --verbose --repository testpypi dist/*.tar.gz
57 |
58 | - name: 📤 Publish source to PyPI
59 | if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'chipsalliance/fasm'
60 | env:
61 | TWINE_USERNAME: __token__
62 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
63 | run: |
64 | twine upload dist/*
65 |
66 | Linux:
67 | strategy:
68 | matrix:
69 | include:
70 | - { name: '3.6', python-version: 'cp36-cp36m' }
71 | - { name: '3.7', python-version: 'cp37-cp37m' }
72 | - { name: '3.8', python-version: 'cp38-cp38' }
73 | - { name: '3.9', python-version: 'cp39-cp39' }
74 | fail-fast: false
75 |
76 | name: '${{ matrix.name }} • manylinux'
77 | runs-on: ubuntu-latest
78 |
79 | steps:
80 | - name: 🧰 Checkout
81 | uses: actions/checkout@v2
82 | with:
83 | # Always clone the full depth so git-describe works.
84 | fetch-depth: 0
85 | submodules: true
86 |
87 | - name: 🐍 Set up Python
88 | uses: actions/setup-python@v2
89 |
90 | - name: Install build dependencies
91 | run: |
92 | python -m pip install -U pip
93 | python -m pip install twine auditwheel
94 | python update_version.py
95 |
96 | - name: 🚧 Build distribution 📦
97 | uses: RalfG/python-wheels-manylinux-build@v0.3.3-manylinux2010_x86_64
98 | with:
99 | build-requirements: 'cython'
100 | pre-build-command: 'bash .github/workflows/manylinux-install-cmake.sh'
101 | python-versions: ${{ matrix.python-version }}
102 | #pip-wheel-args: '-w ./dist --no-deps --verbose'
103 |
104 | - name: List distribution 📦
105 | run: |
106 | # Fix permissions
107 | echo "::group::Fixing permission"
108 | ls -l dist/*
109 | echo $USER
110 | whoami
111 | sudo chown -R $USER dist
112 | ls -l dist/*
113 | echo "::endgroup::"
114 | # Remove the non-manylinux versions
115 | rm -v dist/*linux_x86_64*.whl
116 | ls -l dist/*
117 |
118 | - name: Check distribution 📦
119 | run: |
120 | for WHEEL in dist/*.whl; do
121 | echo
122 | echo "::group::Checking $WHEEL"
123 | echo
124 | python -m zipfile --list $WHEEL
125 | echo
126 | auditwheel show $WHEEL
127 | echo
128 | twine check $WHEEL
129 | echo
130 | echo "::endgroup::"
131 | done
132 |
133 | - name: 📤 Publish wheels to Test PyPI
134 | env:
135 | TWINE_USERNAME: __token__
136 | TWINE_PASSWORD: ${{ secrets.PYPI_TEST_PASSWORD }}
137 | if: env.TWINE_PASSWORD != null
138 | run: |
139 | twine upload --skip-existing --verbose --repository testpypi dist/*.whl
140 |
141 | - name: 📤 Publish wheels to PyPI
142 | if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'chipsalliance/fasm'
143 | env:
144 | TWINE_USERNAME: __token__
145 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
146 | run: |
147 | twine upload dist/*.whl
148 |
149 | MacAndWindows:
150 | strategy:
151 | matrix:
152 | os: [windows-latest, macos-latest]
153 | python-version: [ '3.6', '3.7', '3.8', '3.9', 'pypy-3.6', 'pypy-3.7' ]
154 | fail-fast: false
155 |
156 | name: ${{ matrix.python-version }} • ${{ matrix.os }}
157 | runs-on: ${{ matrix.os }}
158 |
159 | steps:
160 | - name: 🧰 Checkout
161 | uses: actions/checkout@v2
162 | with:
163 | # Always clone the full depth so git-describe works.
164 | fetch-depth: 0
165 | submodules: true
166 |
167 | - name: Install dependencies (Mac OS X)
168 | if: startsWith(matrix.os, 'macos')
169 | run: |
170 | true
171 |
172 | - name: Install dependencies (Windows)
173 | if: startsWith(matrix.os, 'windows')
174 | run: |
175 | true
176 |
177 | - name: 🐍 Set up Python ${{ matrix.python-version }}
178 | uses: actions/setup-python@v2
179 | with:
180 | python-version: ${{ matrix.python-version }}
181 |
182 | - name: Install build dependencies
183 | run: |
184 | python -m pip install -U pip
185 | python -m pip install -r requirements.txt
186 | python update_version.py
187 |
188 | - name: Install package dependencies
189 | run: |
190 | python setup.py install
191 |
192 | - name: 🚧 Build distribution 📦
193 | run: |
194 | python setup.py bdist_wheel
195 |
196 | - name: Check distribution 📦
197 | shell: bash
198 | run: |
199 | python -m zipfile -l dist/*
200 | echo
201 | twine check dist/*
202 |
203 | - name: 📤 Publish wheels to Test PyPI
204 | env:
205 | TWINE_USERNAME: __token__
206 | TWINE_PASSWORD: ${{ secrets.PYPI_TEST_PASSWORD }}
207 | if: env.TWINE_PASSWORD != null
208 | run: |
209 | twine upload --skip-existing --verbose --repository testpypi dist/*.whl
210 |
211 | - name: 📤 Publish wheels to PyPI
212 | if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'chipsalliance/fasm'
213 | env:
214 | TWINE_USERNAME: __token__
215 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
216 | run: |
217 | twine upload dist/*.whl
218 |
--------------------------------------------------------------------------------
/.github/workflows/wheel.yml:
--------------------------------------------------------------------------------
1 | # Builds a binary distibutable package.
2 | # Minimal functionality checks may be run as part of the build process, but
3 | # source code checks and extensive functionality checks (e.g. tox) belong in presubmit.yml
4 | name: Python wheels
5 | on: [push, pull_request]
6 | jobs:
7 | wheels:
8 | runs-on: ubuntu-20.04
9 | strategy:
10 | matrix:
11 | python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
12 | fail-fast: false
13 | steps:
14 | - uses: actions/checkout@v2
15 | with:
16 | # Always clone the full depth so git-describe works.
17 | fetch-depth: 0
18 | submodules: true
19 | - name: Install dependencies
20 | run: |
21 | sudo apt update
22 | sudo apt install cmake default-jre-headless uuid-dev libantlr4-runtime-dev
23 | - name: Set up Python ${{ matrix.python-version }}
24 | uses: actions/setup-python@v2
25 | with:
26 | python-version: ${{ matrix.python-version }}
27 | - name: Install dependencies
28 | run: python -m pip install --upgrade -r requirements.txt
29 | - name: Generate version
30 | run: python update_version.py
31 | - name: Build wheels
32 | run: |
33 | export CMAKE_BUILD_PARALLEL_LEVEL=1
34 | python setup.py bdist_wheel
35 | - name: Test wheel installation
36 | run: |
37 | python -m pip install dist/*.whl
38 | (cd tests; python test_simple.py)
39 | - uses: actions/upload-artifact@v2
40 | with:
41 | name: fasm
42 | path: dist
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | .hypothesis/
51 | .pytest_cache/
52 |
53 | # Translations
54 | *.mo
55 | *.pot
56 |
57 | # Django stuff:
58 | *.log
59 | local_settings.py
60 | db.sqlite3
61 |
62 | # Flask stuff:
63 | instance/
64 | .webassets-cache
65 |
66 | # Scrapy stuff:
67 | .scrapy
68 |
69 | # Sphinx documentation
70 | docs/_build/
71 |
72 | # PyBuilder
73 | target/
74 |
75 | # Jupyter Notebook
76 | .ipynb_checkpoints
77 |
78 | # IPython
79 | profile_default/
80 | ipython_config.py
81 |
82 | # pyenv
83 | env/
84 | .python-version
85 |
86 | # celery beat schedule file
87 | celerybeat-schedule
88 |
89 | # SageMath parsed files
90 | *.sage.py
91 |
92 | # Environments
93 | .env
94 | .venv
95 | env/
96 | venv/
97 | ENV/
98 | env.bak/
99 | venv.bak/
100 |
101 | # Spyder project settings
102 | .spyderproject
103 | .spyproject
104 |
105 | # Rope project settings
106 | .ropeproject
107 |
108 | # mkdocs documentation
109 | /site
110 |
111 | # mypy
112 | .mypy_cache/
113 | .dmypy.json
114 | dmypy.json
115 |
116 | # Pyre type checker
117 | .pyre/
118 |
119 | # Emacs temporary files
120 | *~
121 |
122 | # Generated files
123 | /fasm/parser/tags.py
124 | /fasm/parser/antlr_to_tuple.c
125 | /fasm/version.py
126 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "third_party/googletest"]
2 | path = third_party/googletest
3 | url = https://github.com/google/googletest.git
4 | [submodule "third_party/antlr4"]
5 | path = third_party/antlr4
6 | url = https://github.com/antlr/antlr4.git
7 | [submodule "third_party/make-env"]
8 | path = third_party/make-env
9 | url = https://github.com/SymbiFlow/make-env.git
10 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Build documentation in the docs/ directory with Sphinx
9 | sphinx:
10 | configuration: docs/conf.py
11 |
12 | formats:
13 | - htmlzip
14 |
15 | conda:
16 | environment: docs/environment.yml
17 |
--------------------------------------------------------------------------------
/.style.yapf:
--------------------------------------------------------------------------------
1 | [style]
2 | based_on_style = pep8
3 | split_before_expression_after_opening_paren = True
4 | split_before_first_argument = True
5 | split_complex_comprehension = True
6 | split_penalty_comprehension = 2100
7 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | # This is the list of fasm's significant contributors.
2 | #
3 | # This does not necessarily list everyone who has contributed code,
4 | # especially since many employees of one corporation may be contributing.
5 | # To see the full list of contributors, see the revision history in
6 | # source control.
7 | Antmicro
8 | Google LLC
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | # Include the README
2 | include *.md
3 |
4 | # Include the license file
5 | include LICENSE
6 | include AUTHORS
7 |
8 | # Development support files
9 | include Makefile
10 | include requirements.txt
11 | include environment.yml
12 | include *.yapf
13 | include *.yml
14 | include pyproject.toml
15 | include .clang-format
16 |
17 | # Examples
18 | graft examples
19 |
20 | # Tests
21 | graft tests
22 |
23 | # Docs
24 | recursive-include docs *.py
25 | recursive-include docs *.rst
26 | recursive-include docs *.txt
27 | recursive-include docs *.yml
28 | recursive-include docs *.png
29 | recursive-include docs Makefile
30 | recursive-include tests *
31 | prune docs/_*
32 | prune docs/env
33 |
34 | # textx based parser
35 | recursive-include fasm/parser *.tx
36 | recursive-include fasm/parser *.pyx
37 | exclude fasm/parser/*.c
38 |
39 | # antlr based parser
40 | include src/*
41 | include src/antlr/*
42 |
43 | # Excludes
44 | global-exclude .git
45 | exclude .gitmodules
46 | exclude update_version.py
47 | prune third_party
48 | prune .github
49 | prune __pycache__
50 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2017-2022 F4PGA Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | # SPDX-License-Identifier: Apache-2.0
16 |
17 | # The top directory where environment will be created.
18 | TOP_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST))))
19 |
20 | # A pip `requirements.txt` file.
21 | # https://pip.pypa.io/en/stable/reference/pip_install/#requirements-file-format
22 | REQUIREMENTS_FILE := requirements.txt
23 |
24 | # A conda `environment.yml` file.
25 | # https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html
26 | ENVIRONMENT_FILE := environment.yml
27 |
28 | # Rule to checkout the git submodule if it wasn't cloned.
29 | $(TOP_DIR)/third_party/make-env/conda.mk: $(TOP_DIR)/.gitmodules
30 | cd $(TOP_DIR); git submodule update --init third_party/make-env
31 | touch $(TOP_DIR)/third_party/make-env/conda.mk
32 |
33 | -include $(TOP_DIR)/third_party/make-env/conda.mk
34 |
35 | # Update the version file
36 | # ------------------------------------------------------------------------
37 | fasm/version.py: update_version.py | $(CONDA_ENV_PYTHON)
38 | $(IN_CONDA_ENV) python ./update_version.py
39 |
40 | setup.py: fasm/version.py
41 | touch setup.py --reference fasm/version.py
42 |
43 | # Build/install into the conda environment.
44 | # ------------------------------------------------------------------------
45 | build-clean:
46 | rm -rf dist fasm.egg-info
47 |
48 | .PHONY: build-clean
49 |
50 | build: setup.py | $(CONDA_ENV_PYTHON)
51 | make build-clean
52 | $(IN_CONDA_ENV) python setup.py sdist bdist_wheel
53 |
54 | .PHONY: build
55 |
56 | # Install into environment
57 | install: setup.py | $(CONDA_ENV_PYTHON)
58 | $(IN_CONDA_ENV) python setup.py develop
59 |
60 | .PHONY: install
61 |
62 |
63 | # Build/install locally rather than inside the environment.
64 | # ------------------------------------------------------------------------
65 | local-build: setup.py
66 | python setup.py build
67 |
68 | .PHONY: local-build
69 |
70 | local-build-shared: setup.py
71 | python setup.py build --antlr-runtime=shared
72 |
73 | .PHONY: local-build-shared
74 |
75 | local-install: setup.py
76 | python setup.py install
77 |
78 | .PHONY: local-install
79 |
80 |
81 | # Test, lint, auto-format.
82 | # ------------------------------------------------------------------------
83 |
84 | # Run the tests
85 | test: fasm/version.py | $(CONDA_ENV_PYTHON)
86 | $(IN_CONDA_ENV) py.test -s tests
87 |
88 | .PHONY: test
89 |
90 | # Find files to apply tools to while ignoring files.
91 | define with_files
92 | $(IN_CONDA_ENV) git ls-files | grep -ve '^third_party\|^\.|^env' | grep -e $(1) | xargs -r -P $$(nproc) $(2)
93 | endef
94 |
95 | # Lint the python files
96 | lint: | $(CONDA_ENV_PYTHON)
97 | $(call with_py_files, flake8)
98 |
99 | .PHONY: lint
100 |
101 | # Format the python files
102 | define with_py_files
103 | $(call with_files, '.py$$', $(1))
104 | endef
105 |
106 | PYTHON_FORMAT ?= yapf
107 | format-py: | $(CONDA_ENV_PYTHON)
108 | $(call with_py_files, yapf -p -i)
109 |
110 | .PHONY: format-py
111 |
112 | # Format the C++ files
113 | define with_cpp_files
114 | $(call with_files, '\.cpp$$\|\.h$$', $(1))
115 | endef
116 |
117 | format-cpp:
118 | $(call with_cpp_files, clang-format -style=file -i)
119 |
120 | .PHONY: format-cpp
121 |
122 | # Format all the files!
123 | format: format-py format-cpp
124 | true
125 |
126 | # Check - ???
127 | check: setup.py | $(CONDA_ENV_PYTHON)
128 | $(IN_CONDA_ENV) python setup.py check -m -s
129 |
130 | .PHONY: check
131 |
132 | # Check files have license headers.
133 | check-license:
134 | @./.github/check_license.sh
135 |
136 | .PHONY: check-license
137 |
138 | # Check python scripts have the correct headers.
139 | check-python-scripts:
140 | @./.github/check_python_scripts.sh
141 |
142 | .PHONY: check-python-scripts
143 |
144 | # Upload to PyPI servers
145 | # ------------------------------------------------------------------------
146 |
147 | # PYPI_TEST = --repository-url https://test.pypi.org/legacy/
148 | PYPI_TEST = --repository testpypi
149 |
150 | # Check before uploading
151 | upload-check: build | $(CONDA_ENV_PYTHON)
152 | $(IN_CONDA_ENV) twine check dist/*
153 |
154 | .PHONY: upload-check
155 |
156 | # Upload to test.pypi.org
157 | upload-test: check | $(CONDA_ENV_PYTHON)
158 | $(IN_CONDA_ENV) twine upload ${PYPI_TEST} dist/*.tar.gz
159 | $(IN_CONDA_ENV) twine upload ${PYPI_TEST} dist/*.whl
160 |
161 | .PHONY: upload-test
162 |
163 | # Upload to the real pypi.org
164 | upload: check | $(CONDA_ENV_PYTHON)
165 | $(IN_CONDA_ENV) twine upload ${PYPI_TEST} dist/*.tar.gz
166 | $(IN_CONDA_ENV) twine upload ${PYPI_TEST} dist/*.whl
167 |
168 | .PHONY: upload
169 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## FPGA Assembly (FASM) Parser and Generation library
2 |
3 | This repository documents the FASM file format and provides parsing libraries and simple tooling for working with FASM files.
4 |
5 | It provides both a pure Python parser based on `textx` and a significantly faster C parser based on `ANTLR`. The library will try and use the ANTLR parser first and fall back to the `textx` parser if the compiled module is not found.
6 |
7 | Which parsers are supported by your currently install can be found via `python3 -c "import fasm.parser as p; print(p.available)`. The currently in use parser can be found via `fasm.parser.implementation`.
8 |
9 | It is highly recommended to use the ANTLR parser as it is about 15 times faster.
10 |
11 | functions for parsing and generating FASM files.
12 |
13 | ## Build Instructions
14 |
15 | CMake is required, and ANTLR has a few dependencies:
16 |
17 | sudo apt install cmake default-jre-headless uuid-dev libantlr4-runtime-dev
18 |
19 | Pull dependencies in `third_party`:
20 |
21 | git submodule update --init
22 |
23 | Build:
24 |
25 | make build
26 |
27 | Test with:
28 |
29 | python setup.py test
30 |
31 | The ANTLR runtime can either be linked statically or as a shared library. Use the
32 | `--antlr-runtime=[static|shared]` flag to select between the two modes e.g.:
33 |
34 | python setup.py install --antlr-runtime=shared
35 |
36 | Or, using `pip`:
37 |
38 | pip install . --install-option="--antlr-runtime=shared" --no-use-pep517
39 |
40 | The runtime will be built and statically linked by default. This flag is available in the build_ext, build, develop, and install commands.
41 |
42 | The --no-use-pep517 flag is needed because there is currently no way to pass flags with PEP517.
43 | Relevant issue: https://github.com/pypa/pip/issues/5771
44 |
45 | ## FPGA Assembly (FASM)
46 |
47 | FPGA Assembly is a file format designed by the
48 | [F4PGA Project](https://f4pga.org/) developers to provide a plain
49 | text file format for configuring the internals of an FPGA.
50 |
51 | It is designed to allow FPGA place and route to not care about the *actual*
52 | bitstream format used on an FPGA.
53 |
54 | 
55 |
56 | ### Properties
57 |
58 | * Removing a line from a FASM file leaves you with a valid FASM file.
59 | * Allow annotation with human readable comments.
60 | * Allow annotation with "computer readable" comments.
61 | * Has syntactic sugar for expressing memory / lut init bits / other large
62 | arrays of data.
63 | * Has a canonical form.
64 | * Does not require any specific bitstream format.
65 |
66 | ### Supported By
67 |
68 | FASM is currently supported by the
69 | [F4PGA Verilog to Routing fork](https://github.com/f4pga/vtr-verilog-to-routing),
70 | but we hope to get it merged upstream sometime soon.
71 |
72 | It is also used by [Project X-Ray](https://github.com/f4pga/prjxray).
73 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2017-2022 F4PGA Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | # SPDX-License-Identifier: Apache-2.0
16 | # Minimal makefile for Sphinx documentation
17 |
18 |
19 | SHELL = /bin/bash
20 | MAKEDIR := $(dir $(lastword $(MAKEFILE_LIST)))
21 |
22 | # You can set these variables from the command line.
23 | SPHINXOPTS =
24 | SPHINXBUILD = [ -e env/bin/activate ] && source env/bin/activate; sphinx-build
25 | SPHINXAUTOBUILD = [ -e env/bin/activate ] && source env/bin/activate; sphinx-autobuild
26 | SPHINXPROJ = F4PGAFASM
27 | SOURCEDIR = .
28 | BUILDDIR = _build
29 | OSFLAG =
30 | UNAME_S := $(shell uname -s)
31 | ifneq (, $(findstring Linux, $(UNAME_S)))
32 | OSFLAG := Linux
33 | endif
34 | ifeq ($(UNAME_S), Darwin)
35 | OSFLAG := MacOSX
36 | endif
37 | ifneq (, $(findstring Cygwin, $(UNAME_S)))
38 | OSFLAG := Linux
39 | endif
40 | ifneq (, $(findstring MINGW, $(UNAME_S)))
41 | OSFLAG := Linux
42 | endif
43 |
44 |
45 | # Put it first so that "make" without argument is like "make help".
46 | help:
47 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
48 |
49 | livehtml:
50 | @$(SPHINXAUTOBUILD) -b html --ignore \*.swp --ignore \*~ $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html"
51 |
52 | .PHONY: help livehtml Makefile
53 |
54 |
55 | env/Miniconda3-latest-$(OSFLAG)-x86_64.sh:
56 | mkdir env
57 | wget https://repo.anaconda.com/miniconda/Miniconda3-latest-$(OSFLAG)-x86_64.sh -O env/Miniconda3-latest-$(OSFLAG)-x86_64.sh
58 | chmod a+x env/Miniconda3-latest-$(OSFLAG)-x86_64.sh
59 |
60 | env:
61 | rm -rf env
62 | make env/Miniconda3-latest-$(OSFLAG)-x86_64.sh
63 | ./env/Miniconda3-latest-$(OSFLAG)-x86_64.sh -p $(PWD)/env -b -f
64 | source env/bin/activate; conda config --system --add envs_dirs $(PWD)/env/envs
65 | source env/bin/activate; conda config --system --add pkgs_dirs $(PWD)/env/pkgs
66 | source env/bin/activate; conda env update --name base --file ./environment.yml
67 |
68 | .PHONY: env
69 |
70 | # Catch-all target: route all unknown targets to Sphinx using the new
71 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
72 | %:
73 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
74 |
--------------------------------------------------------------------------------
/docs/_static/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/_static/image/fasm-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chipsalliance/fasm/ffafe821bae68637fe46e36bcfd2a01b97cdf6f2/docs/_static/image/fasm-diagram.png
--------------------------------------------------------------------------------
/docs/_static/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
67 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | # This file only contains a selection of the most common options. For a full
21 | # list see the documentation:
22 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
23 |
24 | from pathlib import Path
25 | from re import sub as re_sub
26 | from os import environ, path as os_path, popen
27 | # from sys import path as sys_path
28 |
29 | # sys_path.insert(0, os_path.abspath('.'))
30 |
31 | # -- General configuration ------------------------------------------------
32 |
33 | project = 'FPGA Assembly (FASM)'
34 | author = 'F4PGA Authors'
35 | copyright = f'{author}, 2018 - 2022'
36 |
37 | extensions = [
38 | 'sphinx.ext.autodoc',
39 | 'sphinx.ext.autosummary',
40 | 'sphinx.ext.doctest',
41 | 'sphinx.ext.imgmath',
42 | 'sphinx.ext.napoleon',
43 | 'sphinx.ext.todo',
44 | 'sphinx_markdown_tables',
45 | ]
46 |
47 | templates_path = ['_templates']
48 |
49 | source_suffix = ['.rst', '.md']
50 |
51 | master_doc = 'index'
52 |
53 | on_rtd = environ.get('READTHEDOCS', None) == 'True'
54 |
55 | if on_rtd:
56 | docs_dir = os_path.abspath(os_path.dirname(__file__))
57 | print("Docs dir is:", docs_dir)
58 | import subprocess
59 | subprocess.call('git fetch origin --unshallow', cwd=docs_dir, shell=True)
60 | subprocess.check_call('git fetch origin --tags', cwd=docs_dir, shell=True)
61 |
62 | release = re_sub('^v', '', popen('git describe').read().strip())
63 | # The short X.Y version.
64 | version = release
65 |
66 | exclude_patterns = ['_build', 'env', 'Thumbs.db', '.DS_Store']
67 |
68 | pygments_style = 'default'
69 |
70 | todo_include_todos = True
71 |
72 | # -- Options for HTML output ----------------------------------------------
73 |
74 | # Enable github links when not on readthedocs
75 | if not on_rtd:
76 | html_context = {
77 | "display_github": True, # Integrate GitHub
78 | "github_user": "chipsalliance", # Username
79 | "github_repo": "fasm", # Repo name
80 | "github_version": "master", # Version
81 | "conf_py_path": "/docs/",
82 | }
83 |
84 | html_show_sourcelink = True
85 |
86 | html_theme = 'sphinx_f4pga_theme'
87 |
88 | html_theme_options = {
89 | 'repo_name': 'chipsalliance/fasm',
90 | 'github_url': 'https://github.com/chipsalliance/fasm',
91 | 'globaltoc_collapse': True,
92 | 'color_primary': 'indigo',
93 | 'color_accent': 'blue',
94 | }
95 |
96 | html_static_path = ['_static']
97 |
98 | html_logo = str(Path(html_static_path[0]) / 'logo.svg')
99 | html_favicon = str(Path(html_static_path[0]) / 'favicon.svg')
100 |
101 | # -- Options for HTMLHelp output ------------------------------------------
102 |
103 | htmlhelp_basename = 'f4pga-fasm'
104 |
105 | # -- Options for LaTeX output ---------------------------------------------
106 |
107 | latex_elements = {}
108 |
109 | latex_documents = [
110 | (
111 | master_doc,
112 | 'fasm.tex',
113 | u'F4PGA FASM Documentation',
114 | u'F4PGA',
115 | 'manual',
116 | ),
117 | ]
118 |
119 | # -- Options for manual page output ---------------------------------------
120 |
121 | man_pages = [
122 | (
123 | master_doc,
124 | 'f4pga-fasm',
125 | u'F4PGA FASM Documentation',
126 | [author],
127 | 1,
128 | ),
129 | ]
130 |
131 | # -- Options for Texinfo output -------------------------------------------
132 |
133 | texinfo_documents = [
134 | (
135 | master_doc,
136 | 'FASM',
137 | u'F4PGA FASM Documentation',
138 | author,
139 | 'FASM',
140 | 'One line description of project.',
141 | 'Miscellaneous',
142 | ),
143 | ]
144 |
145 | # -- Sphinx.Ext.InterSphinx -----------------------------------------------
146 |
147 | intersphinx_mapping = {'https://docs.python.org/': None}
148 |
149 |
150 | def setup(app):
151 | pass
152 |
--------------------------------------------------------------------------------
/docs/environment.yml:
--------------------------------------------------------------------------------
1 | name: fasm-docs
2 | channels:
3 | - SymbiFlow
4 | - conda-forge
5 | - defaults
6 | dependencies:
7 | - python=3.7
8 | - pip
9 | # ReadTheDoc dependencies
10 | - mock
11 | - pillow
12 | - sphinx
13 | # Packages installed from PyPI
14 | - pip:
15 | - -r requirements.txt
16 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | FPGA Assembly (FASM)
2 | ====================
3 |
4 | .. toctree::
5 | :caption: FPGA Assembly (FASM) Specification
6 |
7 | specification/introduction
8 | specification/syntax
9 | specification/line
10 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | http://github.com/f4pga/sphinx_f4pga_theme/archive/f4pga.zip#sphinx-f4pga-theme
2 |
3 | docutils
4 | sphinx
5 | sphinx-autobuild
6 |
7 | breathe
8 | sphinx-markdown-tables
9 | sphinxcontrib-napoleon
10 |
--------------------------------------------------------------------------------
/docs/specification/introduction.rst:
--------------------------------------------------------------------------------
1 | .. _Specification:Introduction:
2 |
3 | Introduction
4 | ------------
5 |
6 | The FASM is a file format designed to specify the bits in an FPGA bitstream that need to be set (e.g. binary 1) or
7 | cleared (e.g. binary 0).
8 |
9 | A FASM file declares that specific "Features" within the bitstream should be enabled.
10 | Enabling a feature will cause bits within the bitstream to be set or cleared.
11 |
12 | A FASM file is illegal if a bit in the final bitstream must be set and cleared to respect the set of features specified
13 | in the FASM file.
14 |
15 | An empty FASM file will generate a platform specific "default" bitstream.
16 | The FASM file will specify zero or more features that mutate the "default" bitstream into the target bitstream.
17 |
18 | .. image:: ../_static/image/fasm-diagram.png
19 | :width: 100%
20 |
--------------------------------------------------------------------------------
/docs/specification/line.rst:
--------------------------------------------------------------------------------
1 | .. _Specification:Line:
2 |
3 | Meaning of a FASM line
4 | ----------------------
5 |
6 | .. csv-table:: Simplified ``SetFasmFeature``
7 | :delim: |
8 | :header-rows: 1
9 |
10 | YYYY.XXXXX | [A:B] | = C
11 | ``Feature`` | ``FeatureAddress`` | ``FeatureValue``
12 | **Required** | *Optional* | *Optional*
13 |
14 | Each line of a FASM file that enables a feature is defined by a ``SetFasmFeature``. Table 1 provides a simplified version of ``SetFasmFeature`` parse. A ``SetFasmFeature`` parse has three parts, the feature to be set (``Feature``), the address within the feature to be set (``FeatureAddress``) and the value of the feature (``FeatureValue``). Both the ``FeatureAddress`` and ``FeatureValue`` are optional.
15 |
16 | When a FASM file declares that a feature is to be enabled or disabled, then specific bits in the bitstream will be cleared or set.
17 |
18 | This section describes how the state of the bits are determined.
19 |
20 | Feature
21 | +++++++
22 |
23 | The ``Feature`` should uniquely specify a feature within the bitstream. If the feature is repeated across FPGA elements, a prefix identifier is required to uniquely identify where a feature is located.
24 |
25 | For example all SLICEL tiles have ALUT.INIT feature, however each tile CLBLL_L tile actually have two SLICEL, and there are many CLBLL_L tiles with a 7-series FPGA. So a unique path would required to both clarify which tile is being set, and which SLICEL within the tile is being set.
26 |
27 | FeatureAddress and FeatureValue
28 | +++++++++++++++++++++++++++++++
29 |
30 | If the ``FeatureAddress`` is not specified, then the address selected is 0.
31 |
32 | If the ``FeatureValue`` is not specified, then the value is 1.
33 |
34 | If the ``FeatureAddress`` is specified as a specific bit rather than a range (e.g. "[5]"), then the ``FeatureValue`` width must be 1-bit wide (e.g. 0 or 1). If the ``FeatureAddress`` is a range (e.g. "[15:0]"), then the ``FeatureValue`` width must be equal or less than the ``FeatureAddress`` width. It is invalid to specific a ``FeatureValue`` wider than the ``FeatureAddress``.
35 |
36 | For example, if the ``FeatureAddress`` was [15:0], then the address width is 16 bits, and the ``FeatureValue`` must be 16 bits or less. So a ``FeatureValue`` of 16'hFFFF is valid, but a ``FeatureValue`` of 17'h10000 is invalid.
37 |
38 | When the ``FeatureAddress`` is wider than 1 bit, the ``FeatureValue`` is shifted and masked for each specific address before enabling or disabling the feature. So for a ``FeatureAddress`` of [7:4], the feature at address 4 is set with a value of (``FeatureValue`` >> 0) & 1, and the feature at address 5 is set with a value of (``FeatureValue`` >> 1) & 1, etc.
39 |
40 | If the value of a feature is 1, then the output bitstream must clear and set all bits as specified.
41 | If the value of a feature is 0, then no change to the "default" bitstream is made.
42 |
43 | Note that the absence of a FASM feature line does not imply that the feature is set to 0. It only means that the relevant bits are used from the implementation specific "default" bitstream.
44 |
--------------------------------------------------------------------------------
/docs/specification/syntax.rst:
--------------------------------------------------------------------------------
1 | .. _Specification:Syntax:
2 |
3 | File Syntax description
4 | -----------------------
5 |
6 | FASM is a line oriented format.
7 |
8 | * A single FASM line will do nothing (blank or comments or annotations) or enable a set of features.
9 | * The FASM file format does not support line continuations. Enabling a feature will always be contained within one line.
10 | * Constants and arrays follow verilog syntax
11 |
12 | Due to the line orientated format, a FASM file has the following properties:
13 |
14 | * Removing a line from a FASM file, produces a FASM file.
15 | * Concatenating two FASM files together, produces a FASM file.
16 | * Sorting a FASM file does not change the resulting bitstream.
17 |
18 | If two FASM files are identical, then the resulting bitstream should have identical set of features are enabled. FASM does support various equivalent forms for enabling features, so there is a "canonical" form of a FASM file that removes this variation. If the canonical forms of two FASM files are the same, then they must generate the same bitstream.
19 |
20 | Lines
21 | +++++
22 |
23 | Examples FASM feature lines
24 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
25 |
26 | Canonical enabling of feature
27 | `````````````````````````````
28 |
29 | .. code-block:: text
30 |
31 | # Set a single feature bit to 1 (with an implicit 1)
32 | INT_L_X10Y146.SW6BEG0.WW2END0
33 | CLBLL_L_X12Y124.SLICEL_X0.BLUT.INIT[17]
34 |
35 | Recommended bitarray
36 | ````````````````````
37 |
38 | .. code-block:: text
39 |
40 | # Setting a bitarray
41 | CLBLL_R_X13Y132.SLICEL_X0.ALUT.INIT[63:32] = 32'b11110000111100001111000011110000
42 |
43 | Permitted advanced variations
44 | `````````````````````````````
45 |
46 | .. code-block:: text
47 |
48 | # The bitarray syntax also allows explicit 1 and explicit 0, if verbosity is desired.
49 | # An explicit 1 is the same as an implicit 1.
50 | INT_L_X10Y146.SW6BEG0.WW2END0 = 1
51 | CLBLL_L_X12Y124.SLICEL_X0.BLUT.INIT[17] = 1
52 | # Explicit bit range to 1
53 | INT_L_X10Y146.SW6BEG0.WW2END0[0:0] = 1'b1
54 | CLBLL_L_X12Y124.SLICEL_X0.BLUT.INIT[17:17] = 1'b1
55 |
56 | # An explicit 0 has no effect on the bitstream output.
57 | INT_L_X10Y146.SW6BEG0.WW2END0 = 0
58 | CLBLL_L_X12Y124.SLICEL_X0.BLUT.INIT[17] = 0
59 | # Explicit bit range to 0
60 | INT_L_X10Y146.SW6BEG0.WW2END0[0:0] = 1'b0
61 | CLBLL_L_X12Y124.SLICEL_X0.BLUT.INIT[17:17] = 1'b0
62 |
63 | Annotations
64 | +++++++++++
65 |
66 | To allow tools to output machine usable annotations (e.g. compiler metadata), annotations are supported. Annotations can be on the line with a FASM feature, or on a line by itself. An annotations must not change the output bitstream. If an annotation would have an affect on the output bitstream, it should be a FASM feature.
67 |
68 | Annotations that are on the same line as a FASM feature are associated with the feature. Annotations that are on a line with no FASM feature as associated with the FASM file itself.
69 |
70 | Example Annotations
71 | ~~~~~~~~~~~~~~~~~~~
72 |
73 | .. code-block:: text
74 |
75 | # Annotation on a FASM feature
76 | INT_L_X10Y146.SW6BEG0.WW2END0 { .attr = "" }
77 | INT_L_X10Y146.SW6BEG0.WW2END0 { .filename = "/a/b/c.txt" }
78 | INT_L_X10Y146.SW6BEG0.WW2END0 { module = "top", file = "/a/b/d.txt", line_number = "123" }
79 |
80 | # Annotation by itself
81 | { .top_module = "/a/b/c/d.txt" }
82 |
83 | # Annotation with FASM feature and comment
84 | INT_L_X10Y146.SW6BEG0.WW2END0 { .top_module = "/a/b/c/d.txt" } # This is a comment
85 |
86 | Formal syntax specification of a line of a FASM file
87 | ++++++++++++++++++++++++++++++++++++++++++++++++++++
88 |
89 | .. code-block:: text
90 |
91 | Identifier ::= [a-zA-Z] [0-9a-zA-Z]*
92 | Feature ::= Identifier ( '.' Identifier )*
93 | S ::= #x9 | #x20
94 |
95 | DecimalValue ::= [0-9_]*
96 | HexidecimalValue ::= [0-9a-fA-F_]+
97 | OctalValue ::= [0-7_]+
98 | BinaryValue ::= [01_]+
99 |
100 | VerilogValue ::= (( DecimalValue? S* "'" ( 'h' S* HexidecimalValue | 'b' S* BinaryValue | 'd' S* DecimalValue | 'o' S* OctalValue ) | DecimalValue )
101 |
102 | FeatureAddress ::= '[' DecimalValue (':' DecimalValue)? ']'
103 |
104 | Any ::= [^#xA#]
105 | Comment ::= '#' Any*
106 |
107 | AnnotationName ::= [.a-zA-Z] [_0-9a-zA-Z]*
108 | NonEscapeCharacters ::= [^\"]
109 | EscapeSequences ::= '\\' | '\"'
110 | Annotation ::= AnnotationName S* '=' S* '"' (NonEscapeCharacters | EscapeSequences)* '"'
111 | Annotations ::= '{' S* Annotation ( ',' S* Annotation )* S* '}'
112 |
113 | SetFasmFeature ::= Feature FeatureAddress? S* ('=' S* VerilogValue)?
114 | FasmLine ::= S* SetFasmFeature? S* Annotations? S* Comment?
115 |
116 | Canonicalization
117 | ++++++++++++++++
118 |
119 | If two FASM files have been canonicalized and are identical, then they enable an identical set of features. The canonical FASM file is also equivalent to the FASM file that would be generated by taking the output bitstream and converting it back into a FASM file.
120 |
121 | The canonicalization process is as follows:
122 |
123 | #. Flatten any ``FeatureAddress`` with width greater than 1.
124 |
125 | * For ``SetFasmFeature`` lines with a ``FeatureAddress`` width greater than 1 bit, 1 ``SetFasmFeature`` for the width the original ``FeatureAddress``.
126 | * When flattening, if the flattened address is 0, do not emit the address.
127 | #. Remove all comments and annotations.
128 | #. If the ``FeatureValue`` is 0, remove the FASM line.
129 | #. If the ``FeatureValue`` is 1, only output the ``Feature`` and the ``FeatureAddress`` if the ``Feature`` has addresses other than 0.
130 | #. Remove any lines that do not modify the default bitstream.
131 |
132 | * Example are psuedo-pips in Xilinx parts.
133 | #. Sort the lines in the FASM file.
134 |
135 | Example Canonicalization
136 | ~~~~~~~~~~~~~~~~~~~~~~~~
137 |
138 | ``ALUT.INIT[0] = 1``
139 |
140 | becomes
141 |
142 | ``ALUT.INIT``
143 |
144 | ----
145 |
146 | ``ALUT.SMALL = 1``
147 |
148 | becomes
149 |
150 | ``ALUT.SMALL``
151 |
152 | ----
153 |
154 | ``ALUT.INIT[3:0] = 4'b1101``
155 |
156 | becomes
157 |
158 | ``ALUT.INIT``
159 |
160 | ``ALUT.INIT[2]``
161 |
162 | ``ALUT.INIT[3]``
163 |
--------------------------------------------------------------------------------
/environment.yml:
--------------------------------------------------------------------------------
1 | name: make-env
2 | dependencies:
3 | - cmake
4 | - pip
5 | - python
6 | - swig
7 | - pip:
8 | - -r requirements.txt
9 |
--------------------------------------------------------------------------------
/examples/blank.fasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chipsalliance/fasm/ffafe821bae68637fe46e36bcfd2a01b97cdf6f2/examples/blank.fasm
--------------------------------------------------------------------------------
/examples/comment.fasm:
--------------------------------------------------------------------------------
1 | # Only a comment.
2 |
--------------------------------------------------------------------------------
/examples/feature_only.fasm:
--------------------------------------------------------------------------------
1 | EXAMPLE_FEATURE.X0.Y0.BLAH
2 |
--------------------------------------------------------------------------------
/examples/many.fasm:
--------------------------------------------------------------------------------
1 | # This file should have examples of all FASM lines that should parse.
2 | # If an example is missing, add it to the end with a comment when is being
3 | # demostrated.
4 | #
5 | # Blank line
6 |
7 | # Empty comment
8 | #
9 | #
10 |
11 | # Set a single feature bit to 1
12 | # Implicit 1
13 | INT_L_X10Y146.SW6BEG0.WW2END0
14 | CLBLL_L_X12Y124.SLICEL_X0.BLUT.INIT[17]
15 | # Explicit 1
16 | INT_L_X10Y146.SW6BEG0.WW2END0 = 1
17 | CLBLL_L_X12Y124.SLICEL_X0.BLUT.INIT[17] = 1
18 | # Explicit bit range
19 | INT_L_X10Y146.SW6BEG0.WW2END0[0:0] = 1'b1
20 | CLBLL_L_X12Y124.SLICEL_X0.BLUT.INIT[17:17] = 1'b1
21 |
22 | # Set a single feature bit to 0
23 | # Explicit 0
24 | INT_L_X10Y146.SW6BEG0.WW2END0 = 0
25 | CLBLL_L_X12Y124.SLICEL_X0.BLUT.INIT[17] = 0
26 | # Explicit bit range to 0
27 | INT_L_X10Y146.SW6BEG0.WW2END0[0:0] = 1'b0
28 | CLBLL_L_X12Y124.SLICEL_X0.BLUT.INIT[17:17] = 1'b0
29 |
30 | # Set a bitarray
31 | CLBLL_R_X13Y132.SLICEL_X0.ALUT.INIT[63:32] = 32'b11110000_11110000_11110000_11110000
32 | CLBLL_R_X13Y132.SLICEL_X0.ALUT.INIT[63:32] = 32'b11110000_11110000_11110000_11110000
33 | CLBLL_R_X13Y132.SLICEL_X0.ALUT.INIT[63:32] = 5'h1F
34 | CLBLL_R_X13Y132.SLICEL_X0.ALUT.INIT[63:32] = 32'o1234567
35 |
36 | # Annotation on a FASM feature
37 | INT_L_X10Y146.SW6BEG0.WW2END0 { .attr = "" }
38 | INT_L_X10Y146.SW6BEG0.WW2END0 { .filename = "/a/b/c.txt" }
39 | INT_L_X10Y146.SW6BEG0.WW2END0 { module = "top", file = "/a/b/d.txt", line_number = "123" }
40 | INT_L_X10Y146.SW6BEG0.WW2END0{module="top",file="/a/b/d.txt",line_number="123"}
41 |
42 | # Annotation by itself
43 | { .top_module = "/a/b/c/d.txt" }
44 |
45 | # Annotation with FASM feature and comment
46 | INT_L_X10Y146.SW6BEG0.WW2END0 { .top_module = "/a/b/c/d.txt" } # This is a comment
47 | # Comment on the last line!
48 |
--------------------------------------------------------------------------------
/fasm/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | from __future__ import print_function
21 |
22 | import os.path
23 |
24 | from fasm.model import ValueFormat, SetFasmFeature, Annotation, FasmLine
25 | from fasm.parser import parse_fasm_filename, parse_fasm_string
26 |
27 | try:
28 | from fasm.version import version_str
29 | except ImportError:
30 | version_str = "UNKNOWN"
31 | __dir__ = os.path.split(os.path.abspath(os.path.realpath(__file__)))[0]
32 | __version__ = version_str
33 |
34 |
35 | def fasm_value_to_str(value, width, value_format):
36 | """ Convert value from SetFasmFeature to a string. """
37 | if value_format == ValueFormat.PLAIN:
38 | return '{}'.format(value)
39 | elif value_format == ValueFormat.VERILOG_HEX:
40 | return "{}'h{:X}".format(width, value)
41 | elif value_format == ValueFormat.VERILOG_DECIMAL:
42 | return "{}'d{}".format(width, value)
43 | elif value_format == ValueFormat.VERILOG_OCTAL:
44 | return "{}'o{:o}".format(width, value)
45 | elif value_format == ValueFormat.VERILOG_BINARY:
46 | return "{}'b{:b}".format(width, value)
47 | else:
48 | assert False, value_format
49 |
50 |
51 | def set_feature_width(set_feature):
52 | if set_feature.end is None:
53 | return 1
54 | else:
55 | assert set_feature.start is not None
56 | assert set_feature.start >= 0
57 | assert set_feature.end >= set_feature.start
58 |
59 | return set_feature.end - set_feature.start + 1
60 |
61 |
62 | def set_feature_to_str(set_feature, check_if_canonical=False):
63 | """ Convert SetFasmFeature tuple to string. """
64 | feature_width = set_feature_width(set_feature)
65 | max_feature_value = 2**feature_width
66 | assert set_feature.value < max_feature_value
67 |
68 | if check_if_canonical:
69 | assert feature_width == 1
70 | assert set_feature.end is None
71 | if set_feature.start is not None:
72 | assert set_feature.start != 0
73 | assert set_feature.value_format is None
74 |
75 | feature = set_feature.feature
76 | address = ''
77 | feature_value = ''
78 |
79 | if set_feature.start is not None:
80 | if set_feature.end is not None:
81 | address = '[{}:{}]'.format(set_feature.end, set_feature.start)
82 | else:
83 | address = '[{}]'.format(set_feature.start)
84 |
85 | if set_feature.value_format is not None:
86 | feature_value = ' = {}'.format(
87 | fasm_value_to_str(
88 | value=set_feature.value,
89 | width=feature_width,
90 | value_format=set_feature.value_format))
91 |
92 | return '{}{}{}'.format(feature, address, feature_value)
93 |
94 |
95 | def canonical_features(set_feature):
96 | """ Yield SetFasmFeature tuples that are of canonical form.
97 |
98 | EG width 1, and value 1.
99 | """
100 | if set_feature.value == 0:
101 | return
102 |
103 | if set_feature.start is None:
104 | assert set_feature.value == 1
105 | assert set_feature.end is None
106 | yield SetFasmFeature(
107 | feature=set_feature.feature,
108 | start=None,
109 | end=None,
110 | value=1,
111 | value_format=None,
112 | )
113 |
114 | return
115 |
116 | if set_feature.start is not None and set_feature.end is None:
117 | assert set_feature.value == 1
118 |
119 | if set_feature.start == 0:
120 | yield SetFasmFeature(
121 | feature=set_feature.feature,
122 | start=None,
123 | end=None,
124 | value=1,
125 | value_format=None,
126 | )
127 | else:
128 | yield SetFasmFeature(
129 | feature=set_feature.feature,
130 | start=set_feature.start,
131 | end=None,
132 | value=1,
133 | value_format=None,
134 | )
135 |
136 | return
137 |
138 | assert set_feature.start is not None
139 | assert set_feature.start >= 0
140 | assert set_feature.end >= set_feature.start
141 |
142 | for address in range(set_feature.start, set_feature.end + 1):
143 | value = (set_feature.value >> (address - set_feature.start)) & 1
144 | if value:
145 | if address == 0:
146 | yield SetFasmFeature(
147 | feature=set_feature.feature,
148 | start=None,
149 | end=None,
150 | value=1,
151 | value_format=None,
152 | )
153 | else:
154 | yield SetFasmFeature(
155 | feature=set_feature.feature,
156 | start=address,
157 | end=None,
158 | value=1,
159 | value_format=None,
160 | )
161 |
162 |
163 | def fasm_line_to_string(fasm_line, canonical=False):
164 | if canonical:
165 | if fasm_line.set_feature:
166 | for feature in canonical_features(fasm_line.set_feature):
167 | yield set_feature_to_str(feature, check_if_canonical=True)
168 |
169 | return
170 |
171 | parts = []
172 |
173 | if fasm_line.set_feature:
174 | parts.append(set_feature_to_str(fasm_line.set_feature))
175 |
176 | if fasm_line.annotations and not canonical:
177 | annotations = '{{ {} }}'.format(
178 | ', '.join(
179 | '{} = "{}"'.format(annotation.name, annotation.value)
180 | for annotation in fasm_line.annotations))
181 |
182 | parts.append(annotations)
183 |
184 | if fasm_line.comment is not None and not canonical:
185 | comment = '#{}'.format(fasm_line.comment)
186 | parts.append(comment)
187 |
188 | if len(parts) == 0 and canonical:
189 | return
190 |
191 | yield ' '.join(parts)
192 |
193 |
194 | def fasm_tuple_to_string(model, canonical=False):
195 | """ Returns string of FASM file for the model given.
196 |
197 | Note that calling parse_fasm_filename and then calling fasm_tuple_to_string
198 | will result in all optional whitespace replaced with one space.
199 | """
200 |
201 | lines = []
202 | for fasm_line in model:
203 | for line in fasm_line_to_string(fasm_line, canonical=canonical):
204 | lines.append(line)
205 |
206 | if canonical:
207 | lines = list(sorted(set(lines)))
208 |
209 | return '\n'.join(lines) + '\n'
210 |
--------------------------------------------------------------------------------
/fasm/model.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | from collections import namedtuple
21 | import enum
22 |
23 |
24 | class ValueFormat(enum.Enum):
25 | """ Number format used for a FASM value. """
26 | PLAIN = 0
27 | VERILOG_DECIMAL = 1
28 | VERILOG_HEX = 2
29 | VERILOG_BINARY = 3
30 | VERILOG_OCTAL = 4
31 |
32 |
33 | ValueFormat.PLAIN.__doc__ = \
34 | "A decimal number without size or radix e.g. 42"
35 | ValueFormat.VERILOG_DECIMAL.__doc__ = \
36 | "A decimal number with optional size e.g. 8'd42"
37 | ValueFormat.VERILOG_HEX.__doc__ = \
38 | "A hexadecimal number with optional size e.g. 8'h2a"
39 | ValueFormat.VERILOG_BINARY.__doc__ = \
40 | "A binary number with optional size e.g. 8'b00101010"
41 | ValueFormat.VERILOG_OCTAL.__doc__ = \
42 | "An octal number with optional size e.g. 8'o52"
43 |
44 | SetFasmFeature = namedtuple(
45 | 'SetFasmFeature', 'feature start end value value_format')
46 | SetFasmFeature.__doc__ = """
47 | Python version of a SetFasmFeature line such as:
48 | feature[31:0] = 42
49 |
50 | feature is a string e.g. 'feature'
51 |
52 | start and end are ints e.g 31, 0
53 | When FeatureAddress is missing, start=None and
54 | end=None.
55 |
56 | value is an int e.g. 42
57 | When FeatureValue is missing, value=1.
58 |
59 | value_format determines how to output the value e.g. ValueFormat.PLAIN
60 | It should be a ValueFormat or None.
61 | If it is None, the value must be 1 and the value will
62 | be omitted from output.
63 | """
64 | SetFasmFeature.feature.__doc__ = "Feature name (string)"
65 | SetFasmFeature.start.__doc__ = \
66 | "Starting value of the feature range (int or None)"
67 | SetFasmFeature.end.__doc__ = \
68 | "Ending value of the feature range (int or None)"
69 | SetFasmFeature.value.__doc__ = \
70 | "FeatureValue describing the value, or None"
71 | SetFasmFeature.value_format.__doc__ = \
72 | "ValueFormat describing the format of the value, or None."
73 |
74 | Annotation = namedtuple('Annotation', 'name value')
75 | Annotation.__doc__ = """
76 | Python version of an Annotation, such as:
77 | { name = "value" }
78 |
79 | Both name and value are strings (not None),
80 | holding the name and value, respectively.
81 | """
82 | Annotation.name.__doc__ = "Annotation name (string)"
83 | Annotation.value.__doc__ = "Annotation value (string)"
84 |
85 | FasmLine = namedtuple('FasmLine', 'set_feature annotations comment')
86 | FasmLine.__doc__ = """
87 | Python version of a FasmLine such as:
88 | feature[31:0] = 42 { name = "value" } # comment
89 |
90 | set_feature should be a SetFasmFeature or None.
91 | annotations should be a list of Annotation or None.
92 | comment should a string or None, e.g. " comment"
93 | """
94 | FasmLine.set_feature.__doc__ = "SetFasmFeature or None"
95 | FasmLine.annotations.__doc__ = "List of Annotation or None"
96 | FasmLine.comment.__doc__ = \
97 | "String or none containing the line comment (after '#')"
98 |
--------------------------------------------------------------------------------
/fasm/output.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | """ Output utilities for FASM.
20 |
21 | merge_features - Combines multiple FASM SetFasmFeature into one.
22 | merge_and_sort - Groups and sorts FASM lines, useful for non-canonical output.
23 |
24 | """
25 | import enum
26 | from fasm import SetFasmFeature, FasmLine, ValueFormat
27 |
28 |
29 | def is_only_comment(line):
30 | """ Returns True if line is only a comment. """
31 | return not line.set_feature and not line.annotations and line.comment
32 |
33 |
34 | def is_only_annotation(line):
35 | """ Returns True if line is only an annotations. """
36 | return not line.set_feature and line.annotations and not line.comment
37 |
38 |
39 | def is_blank_line(line):
40 | """ Returns True if line is blank. """
41 | return not line.set_feature and not line.annotations and not line.comment
42 |
43 |
44 | def merge_features(features):
45 | """ Combines features with varying addresses but same feature.
46 |
47 | A[0] = 1
48 | A[1] = 1
49 |
50 | becomes
51 |
52 | A[1:0] = 2'b11
53 |
54 | and
55 |
56 | A[5] = 1
57 | A[7] = 1
58 |
59 | A[7:0] = 8'b10100000
60 |
61 | """
62 | # Ensure all features are for the same feature
63 | assert len(set(feature.feature for feature in features)) == 1
64 |
65 | set_bits = set()
66 | cleared_bits = set()
67 |
68 | for feature in features:
69 | start = 0
70 | end = 0
71 | value = 1
72 |
73 | if feature.start is None:
74 | assert feature.end is None
75 | else:
76 | start = feature.start
77 | if feature.end is not None:
78 | end = feature.end
79 | else:
80 | end = start
81 |
82 | if feature.value is not None:
83 | value = feature.value
84 |
85 | for bit in range(start, end + 1):
86 | bit_is_set = ((value >> (bit - start)) & 1) != 0
87 | if bit_is_set:
88 | assert bit not in cleared_bits
89 | set_bits.add(bit)
90 | else:
91 | assert bit not in set_bits
92 | cleared_bits.add(bit)
93 |
94 | max_bit = max(set_bits | cleared_bits)
95 |
96 | final_value = 0
97 | for bit in set_bits:
98 | final_value |= (1 << bit)
99 |
100 | return SetFasmFeature(
101 | feature=features[0].feature,
102 | start=0,
103 | end=max_bit,
104 | value=final_value,
105 | value_format=ValueFormat.VERILOG_BINARY)
106 |
107 |
108 | class MergeModel(object):
109 | """ Groups and merges features.
110 |
111 | Grouping logic:
112 | - Consecutive comments will be grouped.
113 | - Comments groups will attach to the next non-comment entry.
114 | - Consecutive annotations will be grouped.
115 | - Empty lines will be discarded
116 | - Features will be grouped by their first feature part.
117 | - Features within the same feature with different addresses will be
118 | merged.
119 |
120 | If a feature has a comment in its group, it is not eligable for address
121 | merging.
122 |
123 | """
124 |
125 | class State(enum.Enum):
126 | """ State of grouper. """
127 | NoGroup = 1
128 | InCommentGroup = 2
129 | InAnnotationGroup = 3
130 |
131 | def __init__(self):
132 | self.state = MergeModel.State.NoGroup
133 | self.groups = []
134 | self.current_group = None
135 |
136 | def start_comment_group(self, line):
137 | """ Start a new group of comments.
138 |
139 | Requires that input line is a comment and not already in a comment
140 | group.
141 |
142 | """
143 | assert self.state != MergeModel.State.InCommentGroup
144 | assert is_only_comment(line)
145 |
146 | if self.current_group is not None:
147 | self.groups.append(self.current_group)
148 |
149 | self.state = MergeModel.State.InCommentGroup
150 | self.current_group = [line]
151 |
152 | def add_to_comment_group(self, line):
153 | assert self.state == MergeModel.State.InCommentGroup
154 |
155 | if is_only_comment(line):
156 | self.current_group.append(line)
157 | elif is_only_annotation(line):
158 | self.current_group.append(line)
159 | self.state = MergeModel.State.InAnnotationGroup
160 | else:
161 | if not is_blank_line(line):
162 | self.current_group.append(line)
163 |
164 | self.groups.append(self.current_group)
165 | self.state = MergeModel.State.NoGroup
166 |
167 | def start_annotation_group(self, line):
168 | assert self.state != MergeModel.State.InAnnotationGroup
169 | assert is_only_annotation(line)
170 |
171 | if self.current_group is not None:
172 | self.groups.append(self.current_group)
173 |
174 | self.state = MergeModel.State.InAnnotationGroup
175 | self.current_group = [line]
176 |
177 | def add_to_annotation_group(self, line):
178 | assert self.state == MergeModel.State.InAnnotationGroup
179 |
180 | if is_only_comment(line):
181 | self.start_comment_group(line)
182 | elif is_only_annotation(line):
183 | self.current_group.append(line)
184 | self.state = MergeModel.State.InAnnotationGroup
185 | else:
186 | self.groups.append(self.current_group)
187 |
188 | self.current_group = None
189 | self.state = MergeModel.State.NoGroup
190 |
191 | self.add_to_model(line)
192 |
193 | def add_to_model(self, line):
194 | """ Add a line to the MergeModel.
195 |
196 | Will be grouped per MergeModel rules.
197 | This method is stateful, so order of insert matters, per grouping
198 | rules.
199 |
200 | """
201 | if self.state == MergeModel.State.NoGroup:
202 | if is_only_comment(line):
203 | self.start_comment_group(line)
204 | elif is_only_annotation(line):
205 | self.start_annotation_group(line)
206 | else:
207 | if not is_blank_line(line):
208 | self.groups.append([line])
209 |
210 | elif self.state == MergeModel.State.InCommentGroup:
211 | self.add_to_comment_group(line)
212 |
213 | elif self.state == MergeModel.State.InAnnotationGroup:
214 | self.add_to_annotation_group(line)
215 |
216 | else:
217 | assert False
218 |
219 | def merge_addresses(self):
220 | """ Merges address features when possible.
221 |
222 | Call after all lines have been added to the model.
223 | """
224 | for group in self.groups:
225 | for line in group:
226 | assert not is_blank_line(line)
227 |
228 | def find_eligable_feature(group):
229 | if len(group) > 1:
230 | return None
231 |
232 | if group[0].annotations:
233 | return None
234 |
235 | if group[0].comment:
236 | return None
237 |
238 | return group[0].set_feature
239 |
240 | eligable_address_features = {}
241 |
242 | non_eligable_groups = []
243 | non_eligable_features = set()
244 |
245 | for group in self.groups:
246 | feature = find_eligable_feature(group)
247 |
248 | if feature is None:
249 | non_eligable_groups.append(group)
250 | for line in group:
251 | if line.set_feature is not None:
252 | non_eligable_features.add(line.set_feature.feature)
253 | else:
254 | if feature.feature not in eligable_address_features:
255 | eligable_address_features[feature.feature] = []
256 |
257 | eligable_address_features[feature.feature].append(feature)
258 |
259 | self.groups = non_eligable_groups
260 |
261 | for feature_group in eligable_address_features.values():
262 | if feature_group[0].feature in non_eligable_features:
263 | for feature in feature_group:
264 | self.groups.append(
265 | [
266 | FasmLine(
267 | set_feature=feature,
268 | annotations=None,
269 | comment=None)
270 | ])
271 | else:
272 | if len(feature_group) > 1:
273 | self.groups.append(
274 | [
275 | FasmLine(
276 | set_feature=merge_features(feature_group),
277 | annotations=None,
278 | comment=None)
279 | ])
280 | else:
281 | for feature in feature_group:
282 | self.groups.append(
283 | [
284 | FasmLine(
285 | set_feature=feature,
286 | annotations=None,
287 | comment=None)
288 | ])
289 |
290 | def output_sorted_lines(self, zero_function=None, sort_key=None):
291 | """ Yields sorted FasmLine's.
292 |
293 | zero_function - Function that takes a feature string, and returns true
294 | that feature has no bits set. This allows tiles with
295 | only zero features to be dropped.
296 |
297 | sort_key - Function that takes a string argument and returns a key
298 | for the first feature part. Example:
299 |
300 | """
301 | feature_groups = {}
302 | non_feature_groups = []
303 |
304 | for group in self.groups:
305 | is_feature_group = False
306 | for line in group:
307 | if line.set_feature:
308 | group_id = line.set_feature.feature.split('.')[0]
309 |
310 | if group_id not in feature_groups:
311 | feature_groups[group_id] = []
312 |
313 | feature_groups[group_id].append(group)
314 | is_feature_group = True
315 | break
316 |
317 | if not is_feature_group:
318 | non_feature_groups.append(group)
319 |
320 | output_groups = []
321 |
322 | def feature_group_key(group):
323 | for line in group:
324 | if line.set_feature:
325 | assert line.set_feature.feature is not None
326 | return line.set_feature.feature
327 |
328 | assert False
329 |
330 | if sort_key is None:
331 | group_ids = sorted(feature_groups.keys())
332 | else:
333 | group_ids = sorted(feature_groups.keys(), key=sort_key)
334 |
335 | for group_id in group_ids:
336 | flattened_group = []
337 | for group in sorted(feature_groups[group_id],
338 | key=feature_group_key):
339 | flattened_group.extend(group)
340 |
341 | if zero_function is not None:
342 | if all(zero_function(line.set_feature.feature)
343 | for line in flattened_group
344 | if line.set_feature):
345 | continue
346 |
347 | output_groups.append(flattened_group)
348 |
349 | output_groups.extend(non_feature_groups)
350 |
351 | for idx in range(len(output_groups)):
352 | for line in output_groups[idx]:
353 | yield line
354 |
355 | if idx != len(output_groups) - 1:
356 | yield FasmLine(
357 | set_feature=None, annotations=None, comment=None)
358 |
359 |
360 | def merge_and_sort(model, zero_function=None, sort_key=None):
361 | """ Given a model, groups and sorts entries.
362 |
363 | zero_function - Function that takes a feature string, and returns true
364 | that feature has no bits set. This allows tiles with only
365 | zero features to be dropped.
366 |
367 | sort_key - Function that takes a string argument and returns a key
368 | for the first feature part. Example:
369 |
370 | A_X2Y1, A_X2Y100, A_X2Y2
371 |
372 | could be sorted as
373 |
374 | A_X2Y1, A_X2Y2, A_X2Y100
375 |
376 | with if the key function returns (A, 2, 1) for A_X2Y1.
377 |
378 | Yields FasmLine's.
379 |
380 | Grouping logic:
381 | - Consecutive comments will be grouped.
382 | - Comments groups will attach to the next non-comment entry.
383 | - Consecutive annotations will be grouped.
384 | - Empty lines will be discarded
385 | - Features will be grouped by their first feature part.
386 | - Features within the same feature with different addresses will be
387 | merged.
388 |
389 | Sorting logic:
390 | - Features will appear before raw annotations.
391 | """
392 | merged_model = MergeModel()
393 |
394 | for line in model:
395 | merged_model.add_to_model(line)
396 |
397 | # Add the last processed annotation or comment blocks to the model
398 | if merged_model.state != MergeModel.State.NoGroup:
399 | if merged_model.current_group is not None:
400 | merged_model.groups.append(merged_model.current_group)
401 |
402 | merged_model.merge_addresses()
403 | return merged_model.output_sorted_lines(
404 | zero_function=zero_function, sort_key=sort_key)
405 |
--------------------------------------------------------------------------------
/fasm/parser/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | from warnings import warn
21 |
22 | available = []
23 | """ List of parser submodules available. Strings should match module names. """
24 |
25 | try:
26 | from fasm.parser.antlr import \
27 | parse_fasm_filename, parse_fasm_string, implementation
28 | available.append('antlr')
29 | except ImportError as e:
30 | warn(
31 | """Unable to import fast Antlr4 parser implementation.
32 | ImportError: {}
33 |
34 | Falling back to the much slower pure Python textX based parser
35 | implementation.
36 |
37 | Getting the faster antlr parser can normally be done by installing the
38 | required dependencies and then reinstalling the fasm package with:
39 | pip uninstall
40 | pip install -v fasm
41 | """.format(e), RuntimeWarning)
42 | from fasm.parser.textx import \
43 | parse_fasm_filename, parse_fasm_string, implementation
44 |
45 | # The textx parser is available as a fallback.
46 | available.append('textx')
47 |
--------------------------------------------------------------------------------
/fasm/parser/antlr.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | from ctypes import CDLL, POINTER, CFUNCTYPE, c_char, c_size_t, c_char_p
21 | import os
22 | from fasm.parser import antlr_to_tuple
23 | import platform
24 | from pathlib import Path
25 |
26 | implementation = 'antlr'
27 | """
28 | Module name of the default parser implementation, accessible as fasm.parser
29 | """
30 |
31 | try:
32 | if platform.system() == 'Darwin':
33 | parse_fasm_lib = "libparse_fasm.dylib"
34 | else:
35 | parse_fasm_lib = "libparse_fasm.so"
36 |
37 | here = Path(os.path.dirname(os.path.realpath(__file__)))
38 | parse_fasm = CDLL(str(here / parse_fasm_lib))
39 | except OSError:
40 | raise ImportError('Could not find parse_fasm library.')
41 |
42 |
43 | def parse_fasm_string(s):
44 | """ Parse FASM string, returning list of FasmLine named tuples.
45 |
46 | >>> parse_fasm_string('a.b.c = 1')[0].set_feature.feature
47 | 'a.b.c'
48 |
49 | Args:
50 | s: The string containing FASM source to parse.
51 |
52 | Returns:
53 | A list of fasm.model.FasmLine.
54 | """
55 | result = [None]
56 | error = [None]
57 |
58 | # Use a closure to parse while allowing C++ to handle memory.
59 | @CFUNCTYPE(None, POINTER(c_char), c_size_t)
60 | def callback(s, n):
61 | data = s[:n]
62 | assert len(data) == n
63 | result[0] = antlr_to_tuple.parse_fasm_data(data)
64 | error[0] = None
65 |
66 | @CFUNCTYPE(None, c_size_t, c_size_t, c_char_p)
67 | def error_callback(line, position, message):
68 | result[0] = None
69 | error[0] = Exception(
70 | 'Parse error at {}:{} - {}'.format(
71 | line, position, message.decode('ascii')))
72 |
73 | parse_fasm.from_string(bytes(s, 'ascii'), 0, callback, error_callback)
74 |
75 | if error[0] is not None:
76 | raise error[0]
77 |
78 | return result[0]
79 |
80 |
81 | def parse_fasm_filename(filename):
82 | """ Parse FASM file, returning list of FasmLine named tuples.
83 |
84 | >>> parse_fasm_filename('examples/feature_only.fasm')[0]\
85 | .set_feature.feature
86 | 'EXAMPLE_FEATURE.X0.Y0.BLAH'
87 |
88 | Args:
89 | filename: The file containing FASM source to parse.
90 |
91 | Returns:
92 | A list of fasm.model.FasmLine.
93 | """
94 | result = [None]
95 | error = [None]
96 |
97 | # Use a closure to parse while allowing C++ to handle memory.
98 | @CFUNCTYPE(None, POINTER(c_char), c_size_t)
99 | def callback(s, n):
100 | data = s[:n]
101 | assert len(data) == n
102 | result[0] = antlr_to_tuple.parse_fasm_data(data)
103 | error[0] = None
104 |
105 | @CFUNCTYPE(None, c_size_t, c_size_t, c_char_p)
106 | def error_callback(line, position, message):
107 | result[0] = None
108 | error[0] = Exception(
109 | 'Parse error at {}:{} - {}'.format(
110 | line, position, message.decode('ascii')))
111 |
112 | parse_fasm.from_file(bytes(filename, 'ascii'), 0, callback, error_callback)
113 |
114 | if error[0] is not None:
115 | raise error[0]
116 |
117 | return result[0]
118 |
--------------------------------------------------------------------------------
/fasm/parser/antlr_to_tuple.pyx:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | from sys import byteorder
21 | from fasm.model import \
22 | SetFasmFeature, Annotation, FasmLine, ValueFormat
23 | from fasm.parser import tags
24 |
25 | TAG_TO_VALUE_FORMAT = {
26 | tags.plain: ValueFormat.PLAIN,
27 | tags.decimal: ValueFormat.VERILOG_DECIMAL,
28 | tags.hex: ValueFormat.VERILOG_HEX,
29 | tags.binary: ValueFormat.VERILOG_BINARY,
30 | tags.octal: ValueFormat.VERILOG_OCTAL
31 | }
32 | """ Maps tags from the parse_fasm library to ValueFormats """
33 |
34 | # The following functions decode the binary format generated by parse_fasm.
35 | # This is a lightweight binary format designed to be fast to decode.
36 |
37 | cdef (int, int) get_header(char* tags, char* data, int i):
38 | """
39 | Match a tag and retrieve length from the header.
40 | Returns data length and offset, or None and the same offset
41 | if the header doesn't match on of the given tags.
42 | """
43 | if not data[i] in tags:
44 | return -1, i
45 |
46 | return int.from_bytes(data[i + 1:i + 5], byteorder), i + 5
47 |
48 |
49 | def tagged_string_from_bytes(tag, data, i):
50 | """ Decode a tagged string. """
51 | length, i = get_header(tag, data, i)
52 | if length == -1:
53 | return None, i
54 |
55 | return data[i:i + length].decode('ascii'), i + length
56 |
57 |
58 | def fasm_address_from_bytes(data, i):
59 | """ Decode a FASM address: [x:y] """
60 | length, i = get_header(tags.address, data, i)
61 | if length == -1:
62 | return None, None, i
63 |
64 | assert length == 4 or length == 8, length
65 |
66 | end = int.from_bytes(data[i:i + 4], byteorder)
67 |
68 | if length == 8:
69 | start = int.from_bytes(data[i + 4:i + 8], byteorder)
70 | else: # If there is only one value, assign it to start
71 | start = end
72 | end = None
73 |
74 | return start, end, i + length
75 |
76 |
77 | def fasm_value_from_bytes(data, i):
78 | """ Decode a FASM value. """
79 | tag = data[i:i+1]
80 | if tag == tags.plain: # Matches a plain decimal integer.
81 | length = 4
82 | i = i + 1
83 | else: # Matches Verilog number formats: hex, octal, binary, or decimal.
84 | length, i = get_header(
85 | tags.hex + tags.octal + tags.binary + tags.decimal, data, i)
86 |
87 | if length == -1:
88 | return None, None, i
89 |
90 | assert length % 4 == 0, length
91 |
92 | value = 0
93 | for j in range(i, i + length, 4):
94 | value = value << 32 | int.from_bytes(data[j:j + 4], byteorder)
95 |
96 | return value, TAG_TO_VALUE_FORMAT[tag], i + length
97 |
98 |
99 | def fasm_width_from_bytes(data, i):
100 | """ Decode the width of a Verilog value. """
101 | if data[i:i+1] != tags.width:
102 | return None, i
103 | else:
104 | return int.from_bytes(data[i + 1:i + 5], byteorder), i + 5
105 |
106 |
107 | def address_width(start, end):
108 | """ Calculate bit width inferred from the address. """
109 | return 1 if (start is None or end is None) else end - start + 1
110 |
111 |
112 | def fasm_set_feature_from_bytes(data, i):
113 | """ Decode a set feature: feature = value """
114 | length, i = get_header(tags.set_feature, data, i)
115 | if length == -1:
116 | return None, i
117 |
118 | feature, p = tagged_string_from_bytes(tags.feature, data, i)
119 | start, end, p = fasm_address_from_bytes(data, p)
120 | width, p = fasm_width_from_bytes(data, p)
121 | value, value_format, p = fasm_value_from_bytes(data, p)
122 |
123 | assert p == i + length
124 |
125 | assert feature is not None
126 |
127 | if value is None:
128 | value = 1
129 |
130 | if width:
131 | assert value < 2 ** width, \
132 | "value {} larger than specified width of {}".format(value, width)
133 |
134 | assert value < 2**address_width(start, end), (value, start, end)
135 |
136 | return SetFasmFeature(
137 | feature=feature,
138 | start=start,
139 | end=end,
140 | value=value,
141 | value_format=value_format), i + length
142 |
143 |
144 | def fasm_annotation_from_bytes(data, i):
145 | """ Decode an annotation: x = "y" """
146 | length, i = get_header(tags.annotation, data, i)
147 | if length == -1:
148 | return None, i
149 |
150 | name, p = tagged_string_from_bytes(tags.annotation_name, data, i)
151 | value, p = tagged_string_from_bytes(tags.annotation_value, data, p)
152 |
153 | assert p == i + length
154 |
155 | assert name is not None
156 | assert value is not None
157 |
158 | return Annotation(name=name, value=value), i + length
159 |
160 |
161 | def fasm_annotations_from_bytes(data, i):
162 | """ Decode a set of annotations: { ... } """
163 | length, i = get_header(tags.annotations, data, i)
164 | if length == -1:
165 | return None, i
166 |
167 | annotations = []
168 | annotation, p = fasm_annotation_from_bytes(data, i)
169 | while annotation:
170 | annotations.append(annotation)
171 | annotation, p = fasm_annotation_from_bytes(data, p)
172 |
173 | assert p == i + length
174 |
175 | return annotations, i + length
176 |
177 |
178 | def fasm_line_from_bytes(data, i):
179 | """ Decode an entire FASM line. """
180 | length, i = get_header(tags.line, data, i)
181 | if length == -1:
182 | return None, i
183 |
184 | set_feature, p = fasm_set_feature_from_bytes(data, i)
185 | annotations, p = fasm_annotations_from_bytes(data, p)
186 | comment, p = tagged_string_from_bytes(tags.comment, data, p)
187 |
188 | assert p == i + length
189 |
190 | assert (
191 | set_feature is not None or annotations is not None
192 | or comment is not None)
193 |
194 | return FasmLine(
195 | set_feature=set_feature, annotations=annotations,
196 | comment=comment), i + length
197 |
198 |
199 | def parse_fasm_data(data):
200 | """ Parse FASM string, returning list of FasmLine named tuples."""
201 | lines = []
202 | line, p = fasm_line_from_bytes(data, 0)
203 | while line:
204 | lines.append(line)
205 | line, p = fasm_line_from_bytes(data, p)
206 |
207 | # Check that data read, plus the final null header,
208 | # is equal to the buffer size.
209 | assert p + 1 == len(data), p
210 |
211 | return lines
212 |
--------------------------------------------------------------------------------
/fasm/parser/fasm.tx:
--------------------------------------------------------------------------------
1 | // FasmFile must be first, it is the root of the parse tree.
2 | FasmFile: (lines*=FasmLine Newline*)* ;
3 |
4 | // None-newline whitespace
5 | S: /[ \t]/ ;
6 |
7 | Newline: /[\n\r]/ ;
8 |
9 | Identifier: /[a-zA-Z][0-9a-zA-Z_]*/ ;
10 | Feature: Identifier ( '.' Identifier )* ;
11 |
12 | DecimalValue: /[0-9_]+/ ;
13 | HexidecimalValue: /[0-9a-fA-F_]+/ ;
14 | OctalValue: /[0-7_]+/ ;
15 | BinaryValue: /[01_]+/ ;
16 |
17 | VerilogValue: ( width=INT? S* "'" ( 'h' S* hex_value=HexidecimalValue | 'b' S* binary_value=BinaryValue | 'd' S* decimal_value=DecimalValue | 'o' S* octal_value=OctalValue ) | plain_decimal=DecimalValue ) ;
18 |
19 | FeatureAddress: '[' address1=DecimalValue (':' address2=DecimalValue)? ']' ;
20 |
21 | CommentCap: '#' comment=/[^\n\r]*/ ;
22 |
23 | AnnotationName: /[.a-zA-Z][0-9a-zA-Z_]*/ ;
24 | NonEscapeCharacters: /[^\"]/ ;
25 | EscapeSequences: /[\\][\\"]/ ;
26 | AnnotationValue: (NonEscapeCharacters | EscapeSequences)* ;
27 | Annotation: name=AnnotationName S* '=' S* '"' value=AnnotationValue '"' ;
28 | Annotations: '{' S* annotations+=Annotation ( ',' S* annotations+=Annotation )* S* '}' ;
29 |
30 | SetFasmFeature: feature=Feature feature_address=FeatureAddress? S* ('=' S* feature_value=VerilogValue)? ;
31 | FasmLine: S* set_feature=SetFasmFeature? S* annotations=Annotations? S* comment=CommentCap?;
32 |
--------------------------------------------------------------------------------
/fasm/parser/textx.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | from __future__ import print_function
21 | import textx
22 | import os.path
23 | from fasm.model import \
24 | ValueFormat, SetFasmFeature, Annotation, FasmLine
25 |
26 | implementation = 'textx'
27 | """
28 | Module name of the default parser implementation, accessible as fasm.parser
29 | """
30 |
31 |
32 | def assert_max_width(width, value):
33 | """ asserts if the value is greater than the width. """
34 | assert value < (2**width), (width, value)
35 |
36 |
37 | def verilog_value_to_int(verilog_value):
38 | """ Convert VerilogValue model to width, value, value_format """
39 | width = None
40 |
41 | if verilog_value.plain_decimal:
42 | return width, int(verilog_value.plain_decimal), ValueFormat.PLAIN
43 |
44 | if verilog_value.width:
45 | width = int(verilog_value.width)
46 |
47 | if verilog_value.hex_value:
48 | value = int(verilog_value.hex_value.replace('_', ''), 16)
49 | value_format = ValueFormat.VERILOG_HEX
50 | elif verilog_value.binary_value:
51 | value = int(verilog_value.binary_value.replace('_', ''), 2)
52 | value_format = ValueFormat.VERILOG_BINARY
53 | elif verilog_value.decimal_value:
54 | value = int(verilog_value.decimal_value.replace('_', ''), 10)
55 | value_format = ValueFormat.VERILOG_DECIMAL
56 | elif verilog_value.octal_value:
57 | value = int(verilog_value.octal_value.replace('_', ''), 8)
58 | value_format = ValueFormat.VERILOG_OCTAL
59 | else:
60 | assert False, verilog_value
61 |
62 | if width is not None:
63 | assert_max_width(width, value)
64 |
65 | return width, value, value_format
66 |
67 |
68 | def set_feature_model_to_tuple(set_feature_model):
69 | start = None
70 | end = None
71 | value = 1
72 | address_width = 1
73 | value_format = None
74 |
75 | if set_feature_model.feature_address:
76 | if set_feature_model.feature_address.address2:
77 | end = int(set_feature_model.feature_address.address1, 10)
78 | start = int(set_feature_model.feature_address.address2, 10)
79 | address_width = end - start + 1
80 | else:
81 | start = int(set_feature_model.feature_address.address1, 10)
82 | end = None
83 | address_width = 1
84 |
85 | if set_feature_model.feature_value:
86 | width, value, value_format = verilog_value_to_int(
87 | set_feature_model.feature_value)
88 |
89 | if width is not None:
90 | assert width <= address_width
91 |
92 | assert value < (2**address_width), (value, address_width)
93 |
94 | return SetFasmFeature(
95 | feature=set_feature_model.feature,
96 | start=start,
97 | end=end,
98 | value=value,
99 | value_format=value_format,
100 | )
101 |
102 |
103 | def get_fasm_metamodel():
104 | return textx.metamodel_from_file(
105 | file_name=os.path.join(os.path.dirname(__file__), 'fasm.tx'),
106 | skipws=False)
107 |
108 |
109 | def fasm_model_to_tuple(fasm_model):
110 | """ Converts FasmFile model to list of FasmLine named tuples. """
111 | if not fasm_model:
112 | return
113 |
114 | for fasm_line in fasm_model.lines:
115 | set_feature = None
116 | annotations = None
117 | comment = None
118 |
119 | if fasm_line.set_feature:
120 | set_feature = set_feature_model_to_tuple(fasm_line.set_feature)
121 |
122 | if fasm_line.annotations:
123 | annotations = tuple(
124 | Annotation(
125 | name=annotation.name,
126 | value=annotation.value if annotation.value else '')
127 | for annotation in fasm_line.annotations.annotations)
128 |
129 | if fasm_line.comment:
130 | comment = fasm_line.comment.comment
131 |
132 | yield FasmLine(
133 | set_feature=set_feature,
134 | annotations=annotations,
135 | comment=comment,
136 | )
137 |
138 |
139 | def parse_fasm_string(s):
140 | """ Parse FASM string, returning list of FasmLine named tuples.
141 |
142 | >>> parse_fasm_string('a.b.c = 1')[0].set_feature.feature
143 | 'a.b.c'
144 |
145 | Args:
146 | s: The string containing FASM source to parse.
147 |
148 | Returns:
149 | A list of fasm.model.FasmLine.
150 | """
151 | return fasm_model_to_tuple(get_fasm_metamodel().model_from_str(s))
152 |
153 |
154 | def parse_fasm_filename(filename):
155 | """ Parse FASM file, returning list of FasmLine named tuples.
156 |
157 | >>> parse_fasm_filename('examples/feature_only.fasm')[0]\
158 | .set_feature.feature
159 | 'EXAMPLE_FEATURE.X0.Y0.BLAH'
160 |
161 | Args:
162 | filename: The file containing FASM source to parse.
163 |
164 | Returns:
165 | A list of fasm.model.FasmLine.
166 | """
167 | return fasm_model_to_tuple(get_fasm_metamodel().model_from_file(filename))
168 |
--------------------------------------------------------------------------------
/fasm/tool.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | import argparse
21 | import importlib
22 | import fasm.parser
23 | from fasm import fasm_tuple_to_string
24 |
25 |
26 | def nullable_string(val):
27 | if not val:
28 | return None
29 | return val
30 |
31 |
32 | def get_fasm_parser(name=None):
33 | module_name = None
34 | if name is None:
35 | module_name = 'fasm.parser'
36 | elif name in fasm.parser.available:
37 | module_name = 'fasm.parser.' + name
38 | else:
39 | raise Exception("Parser '{}' is not available.".format(name))
40 | return importlib.import_module(module_name)
41 |
42 |
43 | def main():
44 | parser = argparse.ArgumentParser('FASM tool')
45 | parser.add_argument('file', help='Filename to process')
46 | parser.add_argument(
47 | '--canonical',
48 | action='store_true',
49 | help='Return canonical form of FASM.')
50 | parser.add_argument(
51 | '--parser',
52 | type=nullable_string,
53 | help='Select FASM parser to use. '
54 | 'Default is to choose the best implementation available.')
55 |
56 | args = parser.parse_args()
57 |
58 | try:
59 | fasm_parser = get_fasm_parser(args.parser)
60 | fasm_tuples = fasm_parser.parse_fasm_filename(args.file)
61 | print(fasm_tuple_to_string(fasm_tuples, args.canonical))
62 | except Exception as e:
63 | print('Error: ' + str(e))
64 |
65 |
66 | if __name__ == '__main__':
67 | main()
68 |
--------------------------------------------------------------------------------
/fasm/version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | # ** WARNING **
21 | # This file is auto-generated by the update_version.py script.
22 | # ** WARNING **
23 |
24 | version_str = "0.0.2.post66"
25 | version_tuple = (0, 0, 2, 66)
26 | try:
27 | from packaging.version import Version as V
28 | pversion = V("0.0.2.post66")
29 | except ImportError:
30 | pass
31 |
32 | git_hash = "c0b734e6d373fcffd0522acdd102814bcefb626a"
33 | git_describe = "v0.0.2-66-gc0b734e"
34 | git_msg = """\
35 | commit c0b734e6d373fcffd0522acdd102814bcefb626a
36 | Author: Tim 'mithro' Ansell
37 | Date: Fri Feb 19 13:05:20 2021 -0800
38 |
39 | Improve the warning message when falling back to the textX.
40 |
41 | Signed-off-by: Tim 'mithro' Ansell
42 |
43 | """
44 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools", "wheel", "cython"]
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | check-manifest
2 | cython
3 | flake8
4 | pytest
5 | textx
6 | tox
7 | twine
8 | wheel
9 | yapf==0.24.0
10 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | license_files = LICENSE
3 |
4 | [bdist_wheel]
5 | universal=1
6 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | import os
21 | import re
22 | import setuptools
23 | import shutil
24 | import subprocess
25 | import sys
26 | import traceback
27 |
28 | from Cython.Build import cythonize
29 | from distutils.command.build import build
30 | from distutils.version import LooseVersion
31 | from setuptools import Extension
32 | from setuptools.command.build_ext import build_ext
33 | from setuptools.command.develop import develop
34 | from setuptools.command.install import install
35 |
36 | __dir__ = os.path.dirname(os.path.abspath(__file__))
37 |
38 | # Read in the description
39 | with open("README.md", "r") as fh:
40 | long_description = fh.read()
41 |
42 | # Read in the version information
43 | FASM_VERSION_FILE = os.path.join(__dir__, 'fasm', 'version.py')
44 | with open(FASM_VERSION_FILE) as f:
45 | if 'UNKNOWN' in f.read():
46 | print(
47 | "Running update_version.py to generate {}".format(
48 | FASM_VERSION_FILE))
49 | subprocess.check_call(['python', 'update_version.py'], cwd=__dir__)
50 | with open(FASM_VERSION_FILE) as f:
51 | lines = f.readlines()
52 | version_line = [v.strip() for v in lines if v.startswith('version_str')]
53 | assert len(version_line) == 1, version_line
54 | version_value = version_line[0].split(' = ', 1)[-1]
55 | assert version_value[0] == '"', version_value
56 | assert version_value[-1] == '"', version_value
57 | version = version_value[1:-1]
58 |
59 |
60 | # Based on: https://www.benjack.io/2018/02/02/python-cpp-revisited.html
61 | # GitHub: https://github.com/benjaminjack/python_cpp_example
62 | class CMakeExtension(Extension):
63 | def __init__(self, name, sourcedir='', prefix=''):
64 | Extension.__init__(self, name, sources=[])
65 | self.sourcedir = os.path.abspath(sourcedir)
66 | self.prefix = prefix
67 |
68 |
69 | # Used to share options between two classes.
70 | class SharedOptions():
71 | ANTLR_RUNTIMES = ['static', 'shared']
72 | options = [
73 | (
74 | 'antlr-runtime=', None,
75 | "Whether to use a 'static' or 'shared' ANTLR runtime.")
76 | ]
77 |
78 | def __init__(self):
79 | self.antlr_runtime = 'static'
80 |
81 | def initialize(self, other):
82 | other.antlr_runtime = None
83 |
84 | def load(self, other):
85 | if other.antlr_runtime is not None:
86 | self.antlr_runtime = other.antlr_runtime
87 | assert self.antlr_runtime in SharedOptions.ANTLR_RUNTIMES, \
88 | 'Invalid antlr_runtime {}, expected one of {}'.format(
89 | self.antlr_runtime, SharedOptions.ANTLR_RUNTIMES)
90 |
91 |
92 | # Global to allow sharing options.
93 | shared_options = SharedOptions()
94 |
95 |
96 | class AntlrCMakeBuild(build_ext):
97 | user_options = SharedOptions.options
98 |
99 | def copy_extensions_to_source(self):
100 | original_extensions = list(self.extensions)
101 | self.extensions = [
102 | ext for ext in self.extensions
103 | if not isinstance(ext, CMakeExtension)
104 | ]
105 | super().copy_extensions_to_source()
106 | self.extensions = original_extensions
107 |
108 | def run(self):
109 | shared_options.load(self)
110 | try:
111 | super().run()
112 |
113 | try:
114 | out = subprocess.check_output(['cmake', '--version'])
115 | except OSError:
116 | raise RuntimeError(
117 | "CMake must be installed to build "
118 | "the following extensions: " + ", ".join(
119 | e.name for e in self.extensions))
120 |
121 | cmake_version = LooseVersion(
122 | re.search(r'version\s*([\d.]+)', out.decode()).group(1))
123 | if cmake_version < '3.7.0':
124 | raise RuntimeError("CMake >= 3.7.0 is required.")
125 |
126 | for ext in self.extensions:
127 | self.build_extension(ext)
128 |
129 | except BaseException as e:
130 | print(
131 | "Failed to build ANTLR parser, "
132 | "falling back on slower textX parser. Error:\n", e)
133 | traceback.print_exc()
134 |
135 | # FIXME: Remove this function
136 | # see: https://github.com/chipsalliance/fasm/issues/50
137 | def add_flags(self):
138 | if sys.platform.startswith('win'):
139 | return
140 |
141 | for flag in ["CFLAGS", "CXXFLAGS"]:
142 | flags = [os.environ.get(flag, "")]
143 | if not flags[0]:
144 | flags.pop(0)
145 |
146 | if shared_options.antlr_runtime == 'static':
147 | # When linking the ANTLR runtime statically, -fPIC is
148 | # still necessary because libparse_fasm will be a
149 | # shared library.
150 | flags.append("-fPIC")
151 |
152 | # FIXME: These should be in the cmake config file?
153 | # Disable excessive warnings currently in ANTLR runtime.
154 | # warning: type attributes ignored after type is already defined
155 | # `class ANTLR4CPP_PUBLIC ATN;`
156 | flags.append('-Wno-attributes')
157 |
158 | # Lots of implicit fallthroughs.
159 | # flags.append('-Wimplicit-fallthrough=0')
160 |
161 | if flags:
162 | os.environ[flag] = " ".join(flags)
163 |
164 | def build_extension(self, ext):
165 | if isinstance(ext, CMakeExtension):
166 | extdir = os.path.join(
167 | os.path.abspath(
168 | os.path.dirname(self.get_ext_fullpath(ext.name))),
169 | ext.prefix)
170 | cmake_args = [
171 | '-DCMAKE_INSTALL_PREFIX=' + extdir,
172 | '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
173 | '-DPYTHON_EXECUTABLE=' + sys.executable,
174 | '-DANTLR_RUNTIME_TYPE=' + shared_options.antlr_runtime
175 | ]
176 |
177 | cfg = 'Debug' if self.debug else 'Release'
178 | build_args = ['--config', cfg]
179 | cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
180 | if not sys.platform.startswith('win') and (
181 | os.environ.get("CMAKE_BUILD_PARALLEL_LEVEL") is None):
182 | build_args += ['--', '-j']
183 |
184 | env = os.environ.copy()
185 | env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(
186 | env.get('CXXFLAGS', ''), self.distribution.get_version())
187 |
188 | # Remove the existing build_temp directory if it already exists.
189 | if os.path.exists(self.build_temp):
190 | shutil.rmtree(self.build_temp, ignore_errors=True)
191 | os.makedirs(self.build_temp, exist_ok=True)
192 |
193 | self.add_flags()
194 |
195 | subprocess.check_call(
196 | ['cmake', ext.sourcedir] + cmake_args,
197 | cwd=self.build_temp,
198 | env=env)
199 | subprocess.check_call(
200 | ['cmake', '--build', '.'] + build_args, cwd=self.build_temp)
201 | subprocess.check_call(
202 | ['cmake', '--install', '.'], cwd=self.build_temp)
203 | subprocess.check_call(['ctest'], cwd=self.build_temp)
204 | print() # Add an empty line for cleaner output
205 | else:
206 | super().build_extension(ext)
207 |
208 | def initialize_options(self):
209 | super().initialize_options()
210 | shared_options.initialize(self)
211 |
212 | def finalize_options(self):
213 | super().finalize_options()
214 | shared_options.load(self)
215 |
216 |
217 | class BuildCommand(build):
218 | user_options = build.user_options + SharedOptions.options
219 |
220 | def initialize_options(self):
221 | super().initialize_options()
222 | shared_options.initialize(self)
223 |
224 | def finalize_options(self):
225 | super().finalize_options()
226 | shared_options.load(self)
227 |
228 | def run(self):
229 | shared_options.load(self)
230 | super().run()
231 |
232 |
233 | class InstallCommand(install):
234 | user_options = install.user_options + SharedOptions.options
235 |
236 | def initialize_options(self):
237 | super().initialize_options()
238 | shared_options.initialize(self)
239 |
240 | def finalize_options(self):
241 | super().finalize_options()
242 | shared_options.load(self)
243 |
244 | def run(self):
245 | shared_options.load(self)
246 | super().run()
247 |
248 |
249 | class DevelopCommand(develop):
250 | user_options = develop.user_options + SharedOptions.options
251 |
252 | def initialize_options(self):
253 | super().initialize_options()
254 | shared_options.initialize(self)
255 |
256 | def finalize_options(self):
257 | super().finalize_options()
258 | shared_options.load(self)
259 |
260 | def run(self):
261 | shared_options.load(self)
262 | super().run()
263 |
264 |
265 | setuptools.setup(
266 | name="fasm",
267 | version=version,
268 | author="F4PGA Authors",
269 | author_email="f4pga-wg@lists.chipsalliance.org",
270 | description="FPGA Assembly (FASM) Parser and Generation library",
271 | long_description=long_description,
272 | long_description_content_type="text/markdown",
273 | url="https://github.com/chipsalliance/fasm",
274 | packages=setuptools.find_packages(exclude=('tests*', )),
275 | install_requires=['textx'],
276 | include_package_data=True,
277 | classifiers=[
278 | "Programming Language :: Python :: 3",
279 | "License :: OSI Approved :: Apache Software License",
280 | "Operating System :: OS Independent",
281 | ],
282 | entry_points={
283 | 'console_scripts': ['fasm=fasm.tool:main'],
284 | },
285 | ext_modules=[
286 | CMakeExtension('parse_fasm', sourcedir='src', prefix='fasm/parser')
287 | ] + cythonize("fasm/parser/antlr_to_tuple.pyx"),
288 | cmdclass={
289 | 'build_ext': AntlrCMakeBuild,
290 | 'build': BuildCommand,
291 | 'develop': DevelopCommand,
292 | 'install': InstallCommand,
293 | },
294 | )
295 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Copyright 2017-2022 F4PGA Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | # SPDX-License-Identifier: Apache-2.0
16 |
17 | # minimum required CMAKE version
18 | CMAKE_MINIMUM_REQUIRED(VERSION 3.7 FATAL_ERROR)
19 |
20 | project(parse_fasm)
21 |
22 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/antlr4/runtime/Cpp/cmake)
23 |
24 | set(CMAKE_CXX_STANDARD 17)
25 | set(ANTLR4_TAG e4c1a74)
26 | if (MSVC_VERSION GREATER_EQUAL "1900")
27 | include(CheckCXXCompilerFlag)
28 | CHECK_CXX_COMPILER_FLAG("/std:c++latest" _cpp_latest_flag_supported)
29 | if (_cpp_latest_flag_supported)
30 | add_compile_options("/std:c++latest")
31 | endif()
32 | endif()
33 |
34 | if(ANTLR_RUNTIME_TYPE STREQUAL "static")
35 | # Required if linking to static library
36 | add_definitions(-DANTLR4CPP_STATIC)
37 |
38 | # Build the static library.
39 | include(ExternalAntlr4Cpp)
40 | set(ANTLR4_RUNTIME antlr4_static)
41 | else()
42 | # Look to see if the shared library is already available.
43 | find_library(ANTLR4_RUNTIME NAMES antlr4-runtime REQUIRED)
44 | find_path(ANTLR4_INCLUDE_DIRS antlr4-runtime.h
45 | HINTS /usr/include/antlr4-runtime
46 | $ENV{ANTLR4_RUNTIME_INCLUDE})
47 |
48 | # If not, build it.
49 | if(NOT ANTLR4_RUNTIME OR NOT ANTLR4_INCLUDE_DIRS)
50 | include(ExternalAntlr4Cpp)
51 | set(ANTLR4_RUNTIME antlr4_shared)
52 | endif()
53 | endif()
54 |
55 | # add antrl4cpp artifacts to project environment
56 | include_directories(${ANTLR4_INCLUDE_DIRS})
57 |
58 | # set variable pointing to the antlr tool that supports C++
59 | # this is not required if the jar file can be found under PATH environment
60 | set(ANTLR_EXECUTABLE ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/antlr4_lib/antlr-4.9.3-complete.jar)
61 |
62 | # add macros to generate ANTLR Cpp code from grammar
63 | find_package(ANTLR REQUIRED)
64 |
65 | # Unit testing library
66 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest EXCLUDE_FROM_ALL googletest)
67 |
68 | # Lexer and parser targets
69 | antlr_target(FasmLexer antlr/FasmLexer.g4 LEXER
70 | OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
71 | COMPILE_FLAGS -Xexact-output-dir
72 | )
73 | antlr_target(FasmParser antlr/FasmParser.g4 PARSER VISITOR
74 | OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
75 | DEPENDS_ANTLR FasmLexer
76 | COMPILE_FLAGS -Xexact-output-dir -lib ${ANTLR_FasmLexer_OUTPUT_DIR}
77 | )
78 |
79 | # Explanation:
80 | # line 1: Skip lines starting in #define
81 | # 2: Extract TAGS(...) from dependencies
82 | # 3: Convert from TAGS('c', long_name) -> long_name = b'c, write to file
83 | add_custom_target(tags.py ALL
84 | COMMAND grep -ve ^\#define ${CMAKE_CURRENT_SOURCE_DIR}/ParseFasm.cpp |
85 | grep -hoe TAG\([^\)]*\) |
86 | sed -e s/TAG\(\\\(.*\\\),\ \\\(.*\\\)\)/\\2\ =\ b\\1/ > tags.py
87 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ParseFasm.cpp
88 | VERBATIM
89 | )
90 |
91 | # Include generated files in project environment
92 | include_directories(${ANTLR_FasmLexer_OUTPUT_DIR})
93 | include_directories(${ANTLR_FasmParser_OUTPUT_DIR})
94 |
95 | # Add generated grammar to binary target
96 | add_library(parse_fasm SHARED ParseFasm.cpp
97 | ${ANTLR_FasmLexer_CXX_OUTPUTS}
98 | ${ANTLR_FasmParser_CXX_OUTPUTS})
99 | target_link_libraries(parse_fasm ${ANTLR4_RUNTIME})
100 | #target_compile_options(parse_fasm PRIVATE -Wno-attributes) # Disable warning from antlr4-runtime
101 |
102 | add_executable(parse_fasm_tests
103 | ParseFasmTests.cpp
104 | ${ANTLR_FasmLexer_CXX_OUTPUTS}
105 | ${ANTLR_FasmParser_CXX_OUTPUTS})
106 | target_link_libraries(parse_fasm_tests ${ANTLR4_RUNTIME})
107 | target_link_libraries(parse_fasm_tests gtest_main)
108 | #target_compile_options(parse_fasm_tests PRIVATE -Wno-attributes) # Disable warning from antlr4-runtime
109 |
110 | # Standalone executable
111 | add_executable(parse_fasm_run EXCLUDE_FROM_ALL
112 | ParseFasmRun.cpp
113 | ${ANTLR_FasmLexer_CXX_OUTPUTS}
114 | ${ANTLR_FasmParser_CXX_OUTPUTS})
115 | target_link_libraries(parse_fasm_run ${ANTLR4_RUNTIME})
116 | set_target_properties(parse_fasm_run PROPERTIES OUTPUT_NAME parse_fasm)
117 | #target_compile_options(parse_fasm_run PRIVATE -Wno-attributes) # Disable warning from antlr4-runtime
118 |
119 | # Unit tests
120 | include(CTest)
121 |
122 | add_test(NAME parse_fasm_tests
123 | COMMAND parse_fasm_tests)
124 | enable_testing()
125 |
126 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/tags.py DESTINATION .)
127 |
--------------------------------------------------------------------------------
/src/ParseFasm.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2017-2022 F4PGA Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // SPDX-License-Identifier: Apache-2.0
16 |
17 | #include "FasmLexer.h"
18 | #include "FasmParser.h"
19 | #include "FasmParserVisitor.h"
20 | #include "antlr4-runtime.h"
21 |
22 | /// This code parses FASM and produces a lightweight binary format that
23 | /// is fast and simple to unpack based on the tag/length/value (TLV)
24 | /// format.
25 | ///
26 | /// For example, for a fixed width integer, this can just be:
27 | ///
28 | /// and for a variable length string:
29 | ///
30 | ///
31 | /// Note that the length itself must be a fixed width value, which does
32 | /// impose a size limit, but this format is more efficient and easier
33 | /// to decode than a UTF-8 style variable length encoding where a bit
34 | /// is reserved per per byte to indicate the end of a variable length
35 | /// value.
36 | ///
37 | /// Each 4-bytes of a numeric value is in native endian order. This
38 | /// format is designed to be produced and consumed on the same machine.
39 | ///
40 | /// TLVs can be nested, with each level adding 5 bytes of header
41 | /// overhead. There is a choice to aggregate values under another header
42 | /// (using withHeader) or not. Even though this encodes redundant size
43 | /// information, it can make the result easier to parse.
44 | ///
45 | /// Example of a nested TLV:
46 | ///
47 | ///
48 | ///
49 | ///
50 | /// Note that there is no need for a closing tag.
51 | ///
52 | /// For example, the consumer can allocate space for results in larger
53 | /// chunks, in this case it can preallocate a line at a time, after
54 | /// reading the first 5 bytes, although this is mostly useful for
55 | /// consumers that can use the data directly without further
56 | /// manipulation.
57 | ///
58 | /// The format used here does not rely on knowing the size of the
59 | /// entire output, which allows streaming line by line, although
60 | /// ANTLR does not yet implement an incremental parser.
61 | ///
62 | /// For a concrete example, see the test case in ParseFasmTests.cpp
63 |
64 | /// External C Interface
65 | ///
66 | /// These functions serialize the FASM parse tree to an easy to parse
67 | /// tag/length/value binary format, where the tag is one byte and
68 | /// the length is 4 bytes, in native endianness (typically little.)
69 | extern "C" {
70 | void from_string(const char* in,
71 | bool hex,
72 | void (*ret)(const char* str, size_t),
73 | void (*err)(size_t, size_t, const char*));
74 | void from_file(const char* path,
75 | bool hex,
76 | void (*ret)(const char* str, size_t),
77 | void (*err)(size_t, size_t, const char*));
78 | }
79 |
80 | using namespace antlr4;
81 | using namespace antlrcpp;
82 |
83 | /// Hex mode is useful for debugging.
84 | /// In this mode, binary values are printed as hex values surrounded by < >
85 | bool hex_mode = false;
86 |
87 | /// The Num class provides an << operator that either dumps the raw value
88 | /// or prints the hex value based on the mode. It also can print a tag.
89 | class Num {
90 | public:
91 | /// Constructors, with optional tag character
92 | Num(char tag, uint32_t num) : num(num), tag(tag) {}
93 | Num(uint32_t num) : num(num), tag(0) {}
94 |
95 | uint32_t num; /// The value
96 | char tag; /// Tag character
97 |
98 | /// The bit width
99 | static constexpr int kWidth = sizeof(num) * 8;
100 | };
101 |
102 | /// Output stream operator for Num.
103 | /// In hex mode, Nums are printed as , otherwise they are
104 | /// copied into the output stream in the underlying representation.
105 | /// As such, this will use the native endianness.
106 | std::ostream& operator<<(std::ostream& s, const Num& num) {
107 | if (num.tag)
108 | s << num.tag;
109 | if (hex_mode) {
110 | s << "<" << std::hex << num.num << ">";
111 | } else {
112 | s.write(reinterpret_cast(&num.num),
113 | sizeof(num.num));
114 | }
115 | return s;
116 | }
117 |
118 | /// The Str class wraps a std::string to provide an << operator
119 | /// that includes the tag and length.
120 | struct Str {
121 | /// Takes the tag and string data
122 | Str(char tag, std::string data) : tag(tag), data(data) {}
123 |
124 | char tag;
125 | std::string data;
126 | };
127 |
128 | /// This output stream operator adds the header needed to decode
129 | /// a string of unknown length and type.
130 | /// Note that some characters are escaped in hex mode to
131 | /// avoid confusion.
132 | std::ostream& operator<<(std::ostream& s, const Str& str) {
133 | s << str.tag << Num(str.data.size());
134 | if (hex_mode) { /// escape < \ >
135 | for (char c : str.data) {
136 | if (c == '<' || c == '>' || c == '\\') {
137 | s.put('\\');
138 | }
139 | s.put(c);
140 | }
141 | } else {
142 | s << str.data;
143 | }
144 | return s;
145 | }
146 |
147 | /// Wraps a string in another header, used to aggregate data.
148 | std::string withHeader(char tag, std::string data) {
149 | std::ostringstream header;
150 | header << tag << Num(data.size());
151 | return header.str() + data;
152 | }
153 |
154 | /// Counts characters that don't match the given character.
155 | /// Used to count digits skipping '_'.
156 | int count_without(std::string::iterator start,
157 | std::string::iterator end,
158 | char c) {
159 | int count = 0;
160 | auto it = start;
161 | while (it != end) {
162 | if (*it != c) {
163 | count++;
164 | }
165 | it++;
166 | }
167 | return count;
168 | }
169 |
170 | /// Calculates the number of leading pad bits are needed
171 | /// so that the rightmost bit will be the LSB of a Num.
172 | /// e.g. This would be 31 for 33'b0.
173 | int lead_bits(int bits) {
174 | return (Num::kWidth - (bits % Num::kWidth)) % Num::kWidth;
175 | }
176 |
177 | // clang-format off
178 | /// Decode a hex digit.
179 | int from_hex_digit(char c) {
180 | int result =
181 | c >= '0' && c <= '9' ? c - '0' :
182 | c >= 'a' && c <= 'f' ? c - 'a' + 10 :
183 | c >= 'A' && c <= 'F' ? c - 'A' + 10: -1;
184 | assert(result >= 0 && result < 16);
185 | return result;
186 | }
187 | // clang-format on
188 |
189 | /// Makes tags easy to extract for documentation and code generation.
190 | /// Use at most once per line to allow simple grepping.
191 | #define TAG(c, long_name) (c)
192 |
193 | /// Raised on parse errors
194 | struct ParseException {
195 | size_t line; ///< Line number of error.
196 | size_t position; ///< Position in that line.
197 | std::string message; ///< A descriptive message.
198 | };
199 |
200 | /// Helper macro to convert a rule context into a string
201 | /// For use inside FasmParserBaseVisitor
202 | #define GET(x) (context->x() ? visit(context->x()).as() : "")
203 |
204 | /// FasmParserBaseVisitor is a visitor for the parse tree
205 | /// generated by the ANTLR parser.
206 | /// It will encode the tree a line at a time and stream out of
207 | /// the given std::ostream
208 | class FasmParserBaseVisitor : public FasmParserVisitor {
209 | public:
210 | static constexpr size_t kHeaderSize = 5;
211 |
212 | /// The constructor requires a std::ostream to stream encoded lines.
213 | /// This is to avoid storing an entire copy of the parse tree in a
214 | /// different form.
215 | FasmParserBaseVisitor(std::ostream& out) : out(out) {}
216 |
217 | /// Stream out FASM lines.
218 | virtual Any visitFasmFile(
219 | FasmParser::FasmFileContext* context) override {
220 | for (auto& line : context->fasmLine()) {
221 | std::string str = visit(line).as();
222 | if (!str.empty()) {
223 | out << str;
224 | if (hex_mode)
225 | out << std::endl;
226 | }
227 | }
228 | return {};
229 | }
230 |
231 | /// This is called for each FASM line.
232 | /// Tag: comment (#)
233 | /// Tag: line (l)
234 | virtual Any visitFasmLine(
235 | FasmParser::FasmLineContext* context) override {
236 | std::ostringstream data;
237 | data << GET(setFasmFeature) << GET(annotations);
238 |
239 | if (context->COMMENT_CAP()) {
240 | std::string c = context->COMMENT_CAP()->getText();
241 | c.erase(0, 1); /// Remove the leading #
242 | data << Str(TAG('#', comment), c);
243 | }
244 |
245 | if (!data.str().empty()) {
246 | return withHeader(TAG('l', line), data.str());
247 | } else {
248 | return std::string(); /// Don't emit empty lines.
249 | }
250 | }
251 |
252 | /// The set feature portion of a line (before annotations and comment.)
253 | /// Tag: feature (f)
254 | /// Tag: set feature (s)
255 | virtual Any visitSetFasmFeature(
256 | FasmParser::SetFasmFeatureContext* context) override {
257 | std::ostringstream data;
258 | data << Str(TAG('f', feature), context->FEATURE()->getText())
259 | << GET(featureAddress) << GET(value);
260 | return withHeader(TAG('s', set_feature), data.str());
261 | }
262 |
263 | /// The bracketed address, where the second number is optional.
264 | /// Tag: address (:)
265 | virtual Any visitFeatureAddress(
266 | FasmParser::FeatureAddressContext* context) override {
267 | std::ostringstream data;
268 | data << Num(std::stoul(context->INT(0)->getText()));
269 |
270 | if (context->INT(1)) {
271 | data << Num(std::stoul(context->INT(1)->getText()));
272 | }
273 | return withHeader(TAG(':', address), data.str());
274 | }
275 |
276 | /// A Verilog style number. It can be "plain" (no leading size and
277 | /// base), or hex (h), binary (b), decimal (d), or octal (o). Tag: bit
278 | /// width (')
279 | virtual Any visitVerilogValue(
280 | FasmParser::VerilogValueContext* context) override {
281 | std::ostringstream data;
282 | if (context->verilogDigits()) {
283 | if (context->INT()) {
284 | data << Num(
285 | TAG('\'', width),
286 | std::stoi(context->INT()->getText()));
287 | }
288 | data << visit(context->verilogDigits())
289 | .as();
290 | }
291 | return data.str();
292 | }
293 |
294 | /// A "plain" decimal value.
295 | /// Tag: plain (p)
296 | virtual Any visitPlainDecimal(
297 | FasmParser::PlainDecimalContext* context) override {
298 | std::ostringstream data;
299 | try {
300 | data << Num(TAG('p', plain),
301 | std::stoi(context->INT()->getText()));
302 | } catch (...) {
303 | throw ParseException{
304 | .line = context->start->getLine(),
305 | .position = context->start->getCharPositionInLine(),
306 | .message = "Could not decode decimal number."};
307 | }
308 | return data.str();
309 | }
310 |
311 | /// A Verilog hex value.
312 | /// Tag: hex (h)
313 | virtual Any visitHexValue(
314 | FasmParser::HexValueContext* context) override {
315 | std::ostringstream data;
316 | std::string value = context->HEXADECIMAL_VALUE()->getText();
317 | auto it = value.begin();
318 | it += 2; /// skip 'h
319 |
320 | /// Build up Nums 4 bits at a time, skipping '_'.
321 | int bits = lead_bits(count_without(it, value.end(), '_') * 4);
322 | uint32_t word = 0;
323 | while (it != value.end()) {
324 | if (*it != '_') {
325 | word = (word << 4) | from_hex_digit(*it);
326 | bits += 4;
327 | if (bits == Num::kWidth) {
328 | data << Num(word);
329 | word = 0;
330 | bits = 0;
331 | }
332 | }
333 | it++;
334 | }
335 | assert(!word);
336 | return withHeader(TAG('h', hex), data.str());
337 | }
338 |
339 | /// A Verilog binary value.
340 | /// Tag: binary (b)
341 | virtual Any visitBinaryValue(
342 | FasmParser::BinaryValueContext* context) override {
343 | std::ostringstream data;
344 | std::string value = context->BINARY_VALUE()->getText();
345 | auto it = value.begin();
346 | it += 2; /// skip 'b
347 |
348 | /// Build up Nums a bit at a time, skipping '_'.
349 | int bits = lead_bits(count_without(it, value.end(), '_'));
350 | uint32_t word = 0;
351 | while (it != value.end()) {
352 | if (*it != '_') {
353 | word = (word << 1) | (*it == '1' ? 1 : 0);
354 | bits += 1;
355 | if (bits == Num::kWidth) {
356 | data << Num(word);
357 | word = 0;
358 | bits = 0;
359 | }
360 | }
361 | it++;
362 | }
363 | assert(!word);
364 | return withHeader(TAG('b', binary), data.str());
365 | }
366 |
367 | /// A Verilog decimal value.
368 | /// Tags: decimal (d)
369 | virtual Any visitDecimalValue(
370 | FasmParser::DecimalValueContext* context) override {
371 | long long unsigned integer = 0;
372 | std::string value = context->DECIMAL_VALUE()->getText();
373 | auto it = value.begin();
374 | it += 2; /// skip 'd
375 |
376 | /// Build up a Num, skipping '_'.
377 | while (it != value.end()) {
378 | if (*it != '_') {
379 | int digit_value = *it - '0';
380 | // Check for overflow
381 | if (integer > (std::numeric_limits<
382 | long long unsigned>::max() -
383 | digit_value) /
384 | 10) {
385 | throw ParseException{
386 | .line = context->start->getLine(),
387 | .position =
388 | context->start
389 | ->getCharPositionInLine(),
390 | .message =
391 | "Could not decode decimal "
392 | "number."};
393 | }
394 | integer = (integer * 10) + digit_value;
395 | }
396 | it++;
397 | }
398 |
399 | std::ostringstream data;
400 | data << Num(integer);
401 | return withHeader(TAG('d', decimal), data.str());
402 | }
403 |
404 | /// A Verilog octal value.
405 | /// Tags: octal (o)
406 | virtual Any visitOctalValue(
407 | FasmParser::OctalValueContext* context) override {
408 | std::ostringstream data;
409 | std::string value = context->OCTAL_VALUE()->getText();
410 | auto it = value.begin();
411 | it += 2; /// skip 'b
412 |
413 | /// Build up a Num 3 bits at a time.
414 | /// Note that since the word size is not evenly divisible by 3,
415 | /// intermediate values can be greater than the word size.
416 | /// This is why the 'word' below is 64 bits wide.
417 | int bits = lead_bits(count_without(it, value.end(), '_') * 3);
418 | uint64_t word = 0; /// could temporarily overflow uint32_t
419 | while (it != value.end()) {
420 | if (*it != '_') {
421 | word = (word << 3) | (*it - '0');
422 | bits += 3;
423 | if (bits >= Num::kWidth) {
424 | data << Num(word >>
425 | (bits - Num::kWidth));
426 | word >>= Num::kWidth;
427 | bits -= Num::kWidth;
428 | }
429 | }
430 | it++;
431 | }
432 | assert(!word);
433 | return withHeader(TAG('o', octal), data.str());
434 | }
435 |
436 | /// A collection of annotations. { ... }
437 | /// Tags: annotations ({)
438 | virtual Any visitAnnotations(
439 | FasmParser::AnnotationsContext* context) override {
440 | std::ostringstream data;
441 | for (auto& a : context->annotation()) {
442 | data << visit(a).as();
443 | }
444 | return withHeader(TAG('{', annotations), data.str());
445 | }
446 |
447 | /// An annotation: x = "y"
448 | /// Tags: annotation (a), annotation name (.), annotation value (=)
449 | virtual Any visitAnnotation(
450 | FasmParser::AnnotationContext* context) override {
451 | std::ostringstream data;
452 | data << Str(TAG('.', annotation_name),
453 | context->ANNOTATION_NAME()->getText());
454 | if (context->ANNOTATION_VALUE()) {
455 | std::string value =
456 | context->ANNOTATION_VALUE()->getText();
457 | value.erase(0, 1); /// Convert "value" -> value
458 | value.pop_back();
459 | data << Str(TAG('=', annotation_value), value);
460 | }
461 | return withHeader(TAG('a', annotation), data.str());
462 | }
463 |
464 | private:
465 | std::ostream& out;
466 | };
467 |
468 | // Prevent use of the GET macro outside FasmParseBaseVisitor
469 | #undef GET
470 |
471 | class FasmErrorListener : public BaseErrorListener {
472 | public:
473 | virtual void syntaxError(Recognizer* recognizer,
474 | Token* token,
475 | size_t line,
476 | size_t position,
477 | const std::string& msg,
478 | std::exception_ptr e) override {
479 | throw ParseException{.line = line,
480 | .position = position,
481 | .message = std::string(msg)};
482 | }
483 | };
484 |
485 | /// Common portion of 'from_string' and 'from_file'.
486 | /// Consumes an input stream and produces an output stream.
487 | static void parse_fasm(std::istream& in, std::ostream& out) {
488 | ANTLRInputStream stream(in);
489 | FasmLexer lexer(&stream);
490 | FasmErrorListener errorListener;
491 | lexer.removeErrorListeners();
492 | lexer.addErrorListener(&errorListener);
493 | CommonTokenStream tokens(&lexer);
494 | FasmParser parser(&tokens);
495 | parser.removeErrorListeners();
496 | parser.addErrorListener(&errorListener);
497 | auto* tree = parser.fasmFile();
498 | FasmParserBaseVisitor(out).visit(tree);
499 | }
500 |
501 | /// Parse the given input string, returning output.
502 | /// Use hex mode (see above) if hex is true.
503 | /// Use a callback to avoid copying the result.
504 | void from_string(const char* in,
505 | bool hex,
506 | void (*ret)(const char* str, size_t),
507 | void (*err)(size_t, size_t, const char*)) {
508 | hex_mode = hex;
509 | std::istringstream input(in);
510 | std::ostringstream output;
511 |
512 | try {
513 | parse_fasm(input, output);
514 | output.put(0);
515 | std::string result = output.str();
516 | ret(result.c_str(), result.size());
517 | } catch (ParseException e) {
518 | // Parse failure will throw this exception.
519 | err(e.line, e.position, e.message.c_str());
520 | }
521 | }
522 |
523 | /// Parse the given input file, returning output.
524 | /// Use hex mode (see above) if hex is true.
525 | /// Use a callback to avoid copying the result.
526 | void from_file(const char* path,
527 | bool hex,
528 | void (*ret)(const char* str, size_t),
529 | void (*err)(size_t, size_t, const char*)) {
530 | hex_mode = hex;
531 | std::fstream input(std::string(path), input.in);
532 | std::ostringstream output;
533 | if (input.is_open()) {
534 | try {
535 | parse_fasm(input, output);
536 | output.put(0);
537 | std::string result = output.str();
538 | ret(result.c_str(), result.size());
539 | } catch (ParseException e) {
540 | // Parse failure will throw this exception.
541 | err(e.line, e.position, e.message.c_str());
542 | }
543 | } else {
544 | err(0, 0, "Couldn't open file");
545 | }
546 | }
547 |
--------------------------------------------------------------------------------
/src/ParseFasmRun.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2017-2022 F4PGA Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // SPDX-License-Identifier: Apache-2.0
16 |
17 | #include "ParseFasm.cpp"
18 |
19 | /// This can be used as a standalone utility.
20 | /// parse_fasm [file] [-hex]
21 | /// file : The file to parse, otherwise use stdin.
22 | /// -hex : Enable hex mode.
23 | int main(int argc, char* argv[]) {
24 | /// If no args say otherwise,
25 | /// run as a filter (stdin -> parse_fasm -> stdout)
26 | bool filter = true;
27 |
28 | /// Parse flags first
29 | for (int i = 1; i < argc; i++) {
30 | std::string arg(argv[i]);
31 | if (arg == "-hex") {
32 | hex_mode = true;
33 | }
34 | }
35 |
36 | /// Run parse on file arguments
37 | for (int i = 1; i < argc; i++) {
38 | std::string arg(argv[i]);
39 | if (arg[0] == '-') {
40 | std::ifstream in;
41 | in.open(arg);
42 | parse_fasm(in, std::cout);
43 | }
44 | }
45 |
46 | /// Run as a filter
47 | if (filter) {
48 | std::istringstream in_line;
49 | for (std::string line; std::getline(std::cin, line);) {
50 | in_line.str(line);
51 | parse_fasm(in_line, std::cout);
52 | }
53 | }
54 | return 0;
55 | }
56 |
--------------------------------------------------------------------------------
/src/ParseFasmTests.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2017-2022 F4PGA Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // SPDX-License-Identifier: Apache-2.0
16 |
17 | /// Unit tests for ParseFasm
18 |
19 | #include
20 | #include "ParseFasm.cpp"
21 |
22 | // Test the Num class
23 | TEST(ParseFasmTests, Num) {
24 | std::ostringstream str;
25 | str << Num('a', 0x74736554);
26 | if (str.str() == "aTest") {
27 | std::cout << "little endian" << std::endl;
28 | } else if (str.str() == "atseT") {
29 | std::cout << "big endian" << std::endl;
30 | } else {
31 | ASSERT_TRUE(false);
32 | }
33 | }
34 |
35 | // Check that count_without() works
36 | TEST(ParseFasmTests, count_without) {
37 | std::string str = "_01_2_34_";
38 | EXPECT_EQ(count_without(str.begin(), str.end(), '_'), 5);
39 | }
40 |
41 | // clang-format off
42 | // A test of the full parser
43 | // Formatting is disabled to allow indenting the output string
44 | // to illustrate the structure.
45 | TEST(ParseFasmTests, parse_fasm) {
46 | std::istringstream input("a.b.c[31:0] = 7'o123 { d = \"e\", .f = \"\" } # hello\nthere");
47 | std::ostringstream output;
48 | bool stored_hex_mode = hex_mode;
49 | hex_mode = true;
50 | parse_fasm(input, output);
51 |
52 | /// The below string is the input above encoded in hex mode.
53 | EXPECT_EQ(output.str(),
54 | "l<50>" /// Line
55 | "s<20>" /// Set feature
56 | "f<5>a.b.c" /// Feature = "a.b.c"
57 | ":<7><1f><0>" /// Address = 31:0
58 | "'<7>" /// Width = 7
59 | "o<4><53>" /// Octal value = 123
60 | "{<1c>" /// Annotations (list)
61 | "a" /// Annotation
62 | ".<1>d" /// Name = "d"
63 | "=<1>e" /// Value = "e"
64 | "a" /// Annotation
65 | ".<2>.f" /// Name = ".f"
66 | "=<0>" /// Value = ""
67 | "#<6> hello" /// Comment
68 | "\n" /// Newline (only in hex mode for readability)
69 | "l" /// Line
70 | "s<9>" /// Set feature
71 | "f<5>there" /// Feature = "there"
72 | "\n" /// Newline (only in hex mode for readability)
73 | );
74 | hex_mode = stored_hex_mode;
75 | }
76 | // clang-format on
77 |
--------------------------------------------------------------------------------
/src/antlr/FasmLexer.g4:
--------------------------------------------------------------------------------
1 | // Copyright 2017-2022 F4PGA Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // SPDX-License-Identifier: Apache-2.0
16 |
17 | lexer grammar FasmLexer;
18 |
19 | // White space
20 | S : [ \t] -> skip ; // Skip all outer white space
21 | NEWLINE : [\n\r] ; // NEWLINE is not skipped, because it is needed to break lines.
22 |
23 | // Matches a feature identifier
24 | fragment IDENTIFIER : [a-zA-Z] [0-9a-zA-Z_]* ;
25 | FEATURE : IDENTIFIER ('.' IDENTIFIER)* ;
26 |
27 | // Number values
28 | // The leading '[hbdo] is kept in the same token to
29 | // disambiguate the grammar.
30 | HEXADECIMAL_VALUE : '\'h' [ \t]* [0-9a-fA-F_]+ ;
31 | BINARY_VALUE : '\'b' [ \t]* [01_]+ ;
32 | DECIMAL_VALUE : '\'d' [ \t]* [0-9_]+ ;
33 | OCTAL_VALUE : '\'o' [ \t]* [0-7_]+ ;
34 | INT : [0-9]+ ;
35 |
36 | // Everything after a # until a newline.
37 | COMMENT_CAP : '#' (~[\n\r])* ;
38 |
39 | // Simple tokens
40 | // These are not referenced in the parser by name,
41 | // but instead by the character they match.
42 | // They need to be here so the lexer will tokenize them.
43 | // This is needed because otherwise an IDENTIFIER with
44 | // a leading dot, or no dot, could also be an ANNOTATION_NAME.
45 | EQUAL : '=' ;
46 | OPEN_BRACKET : '[' ;
47 | COLON : ':' ;
48 | CLOSE_BRACKET : ']' ;
49 |
50 | // An opening curly bracket enters ANNOTATION_MODE
51 | BEGIN_ANNOTATION : '{' -> pushMode(ANNOTATION_MODE) ;
52 |
53 | // https://github.com/antlr/antlr4/issues/1226#issuecomment-230382658
54 | // Any character which does not match one of the above rules will appear in the token stream as
55 | // an ErrorCharacter token. This ensures the lexer itself will never encounter a syntax error,
56 | // so all error handling may be performed by the parser.
57 | ErrorCharacter
58 | : .
59 | ;
60 |
61 | // Inside an annotation, only the rules below apply.
62 | // That is why there is some duplication.
63 | mode ANNOTATION_MODE;
64 |
65 | // White space is skipped.
66 | ANNOTATION_S : [ \t] -> skip ;
67 |
68 | ANNOTATION_NAME : [.a-zA-Z] [0-9a-zA-Z_]* ;
69 | fragment NON_ESCAPE_CHARACTERS : ~[\\"] ;
70 | fragment ESCAPE_SEQUENCES : [\\] [\\"] ;
71 | ANNOTATION_VALUE : '"' (NON_ESCAPE_CHARACTERS | ESCAPE_SEQUENCES)* '"' ;
72 |
73 | // Simple tokens.
74 | ANNOTATION_EQUAL : '=' ;
75 | ANNOTATION_COMMA : ',' ;
76 |
77 | // A closing curly bracket pops out of ANNOTATION_MODE
78 | END_ANNOTATION : '}' -> popMode ;
79 |
--------------------------------------------------------------------------------
/src/antlr/FasmParser.g4:
--------------------------------------------------------------------------------
1 | // Copyright 2017-2022 F4PGA Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // SPDX-License-Identifier: Apache-2.0
16 |
17 | parser grammar FasmParser;
18 |
19 | // Reference tokens from the lexer.
20 | options { tokenVocab=FasmLexer; }
21 |
22 | // fasmFile is the root of the parser
23 | fasmFile : fasmLine (NEWLINE fasmLine)* EOF ;
24 |
25 | // Describes one line
26 | fasmLine : setFasmFeature?
27 | annotations?
28 | COMMENT_CAP?
29 | ;
30 |
31 | // Example: a.b.c[31:0] = 3'o7
32 | setFasmFeature : FEATURE featureAddress? (EQUAL value)? ;
33 | featureAddress : '[' INT (':' INT)? ']' ;
34 |
35 | // Matches an ordinary decimal integer, or a Verilog integer literal.
36 | value : INT? verilogDigits # VerilogValue
37 | | INT # PlainDecimal
38 | ;
39 |
40 | // Matches the portion of a Verilog integer literal starting with
41 | // the quote character (') e.g. 'b1101
42 | verilogDigits : HEXADECIMAL_VALUE # HexValue
43 | | BINARY_VALUE # BinaryValue
44 | | DECIMAL_VALUE # DecimalValue
45 | | OCTAL_VALUE # OctalValue
46 | ;
47 |
48 | // Example: { a = "b", c = "d" }
49 | annotations : BEGIN_ANNOTATION annotation (',' annotation)* END_ANNOTATION ;
50 | annotation : ANNOTATION_NAME ANNOTATION_EQUAL ANNOTATION_VALUE ;
51 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
--------------------------------------------------------------------------------
/tests/test_simple.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Copyright 2017-2022 F4PGA Authors
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 |
20 | import os
21 | import os.path
22 | import importlib
23 |
24 | import unittest
25 | import fasm
26 | import fasm.parser
27 |
28 | parsers = {}
29 | for name in fasm.parser.available:
30 | parsers[name] = importlib.import_module('fasm.parser.' + name)
31 |
32 |
33 | def example(fname):
34 | return os.path.join(os.path.dirname(__file__), '..', 'examples', fname)
35 |
36 |
37 | def check_round_trip(test, parser, result):
38 | s = fasm.fasm_tuple_to_string(result)
39 | test.assertEqual(list(parser.parse_fasm_string(s)), result)
40 |
41 |
42 | class TestFasm(unittest.TestCase):
43 | def test_blank_file(self):
44 | for name, parser in parsers.items():
45 | with self.subTest(name, parser=name):
46 | result = list(
47 | parser.parse_fasm_filename(example('blank.fasm')))
48 | self.assertEqual(result, [])
49 |
50 | check_round_trip(self, parser, result)
51 |
52 | def test_comment_file(self):
53 | for name, parser in parsers.items():
54 | with self.subTest(name, parser=name):
55 | result = list(
56 | parser.parse_fasm_filename(example('comment.fasm')))
57 | self.assertEqual(
58 | result, [
59 | fasm.FasmLine(
60 | set_feature=None,
61 | annotations=None,
62 | comment=' Only a comment.',
63 | )
64 | ])
65 |
66 | check_round_trip(self, parser, result)
67 |
68 | def test_one_line_feature(self):
69 | for name, parser in parsers.items():
70 | with self.subTest(name, parser=name):
71 | result = list(
72 | parser.parse_fasm_filename(example('feature_only.fasm')))
73 | self.assertEqual(
74 | result, [
75 | fasm.FasmLine(
76 | set_feature=fasm.SetFasmFeature(
77 | feature='EXAMPLE_FEATURE.X0.Y0.BLAH',
78 | start=None,
79 | end=None,
80 | value=1,
81 | value_format=None,
82 | ),
83 | annotations=None,
84 | comment=None,
85 | )
86 | ])
87 |
88 | self.assertEqual(
89 | fasm.fasm_tuple_to_string(result),
90 | 'EXAMPLE_FEATURE.X0.Y0.BLAH\n')
91 | check_round_trip(self, parser, result)
92 |
93 | def test_examples_file(self):
94 | for name, parser in parsers.items():
95 | with self.subTest(name, parser=name):
96 | result = list(parser.parse_fasm_filename(example('many.fasm')))
97 | check_round_trip(self, parser, result)
98 |
99 | def test_implementations(self):
100 | self.assertTrue('antlr' in fasm.parser.available)
101 | self.assertTrue('textx' in fasm.parser.available)
102 |
103 |
104 | if __name__ == '__main__':
105 | unittest.main()
106 |
--------------------------------------------------------------------------------
/third_party/antlr4_lib/LICENSE.txt:
--------------------------------------------------------------------------------
1 | [The "BSD 3-clause license"]
2 | Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of the copyright holder nor the names of its contributors
14 | may be used to endorse or promote products derived from this software
15 | without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
28 | =====
29 |
30 | MIT License for codepointat.js from https://git.io/codepointat
31 | MIT License for fromcodepoint.js from https://git.io/vDW1m
32 |
33 | Copyright Mathias Bynens
34 |
35 | Permission is hereby granted, free of charge, to any person obtaining
36 | a copy of this software and associated documentation files (the
37 | "Software"), to deal in the Software without restriction, including
38 | without limitation the rights to use, copy, modify, merge, publish,
39 | distribute, sublicense, and/or sell copies of the Software, and to
40 | permit persons to whom the Software is furnished to do so, subject to
41 | the following conditions:
42 |
43 | The above copyright notice and this permission notice shall be
44 | included in all copies or substantial portions of the Software.
45 |
46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
47 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
48 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
49 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
50 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
51 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
52 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
53 |
--------------------------------------------------------------------------------
/third_party/antlr4_lib/README.md:
--------------------------------------------------------------------------------
1 | # ANTLR v4
2 |
3 | [](https://travis-ci.org/antlr/antlr4) [](https://ci.appveyor.com/project/parrt/antlr4) [](http://java.oracle.com) [](https://raw.githubusercontent.com/antlr/antlr4/master/LICENSE.txt)
4 |
5 | **ANTLR** (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files. It's widely used to build languages, tools, and frameworks. From a grammar, ANTLR generates a parser that can build parse trees and also generates a listener interface (or visitor) that makes it easy to respond to the recognition of phrases of interest.
6 |
7 | *Given day-job constraints, my time working on this project is limited so I'll have to focus first on fixing bugs rather than changing/improving the feature set. Likely I'll do it in bursts every few months. Please do not be offended if your bug or pull request does not yield a response! --parrt*
8 |
9 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BF92STRXT8F8Q)
10 |
11 | ## Authors and major contributors
12 |
13 | * [Terence Parr](http://www.cs.usfca.edu/~parrt/), parrt@cs.usfca.edu
14 | ANTLR project lead and supreme dictator for life
15 | [University of San Francisco](http://www.usfca.edu/)
16 | * [Sam Harwell](http://tunnelvisionlabs.com/) (Tool co-author, Java and C# target)
17 | * Eric Vergnaud (Javascript, Python2, Python3 targets and significant work on C# target)
18 | * [Peter Boyer](https://github.com/pboyer) (Go target)
19 | * [Mike Lischke](http://www.soft-gems.net/) (C++ completed target)
20 | * Dan McLaughlin (C++ initial target)
21 | * David Sisson (C++ initial target and test)
22 | * [Janyou](https://github.com/janyou) (Swift target)
23 | * [Ewan Mellor](https://github.com/ewanmellor), [Hanzhou Shi](https://github.com/hanjoes) (Swift target merging)
24 | * [Ben Hamilton](https://github.com/bhamiltoncx) (Full Unicode support in serialized ATN and all languages' runtimes for code points > U+FFFF)
25 | * [Marcos Passos](https://github.com/marcospassos) (PHP target)
26 | * [Lingyu Li](https://github.com/lingyv-li) (Dart target)
27 |
28 | ## Useful information
29 |
30 | * [Release notes](https://github.com/antlr/antlr4/releases)
31 | * [Getting started with v4](https://github.com/antlr/antlr4/blob/master/doc/getting-started.md)
32 | * [Official site](http://www.antlr.org/)
33 | * [Documentation](https://github.com/antlr/antlr4/blob/master/doc/index.md)
34 | * [FAQ](https://github.com/antlr/antlr4/blob/master/doc/faq/index.md)
35 | * [ANTLR code generation targets](https://github.com/antlr/antlr4/blob/master/doc/targets.md)
(Currently: Java, C#, Python2|3, JavaScript, Go, C++, Swift)
36 | * [Java API](http://www.antlr.org/api/Java/index.html)
37 | * [ANTLR v3](http://www.antlr3.org/)
38 | * [v3 to v4 Migration, differences](https://github.com/antlr/antlr4/blob/master/doc/faq/general.md)
39 |
40 | You might also find the following pages useful, particularly if you want to mess around with the various target languages.
41 |
42 | * [How to build ANTLR itself](https://github.com/antlr/antlr4/blob/master/doc/building-antlr.md)
43 | * [How we create and deploy an ANTLR release](https://github.com/antlr/antlr4/blob/master/doc/releasing-antlr.md)
44 |
45 | ## The Definitive ANTLR 4 Reference
46 |
47 | Programmers run into parsing problems all the time. Whether it’s a data format like JSON, a network protocol like SMTP, a server configuration file for Apache, a PostScript/PDF file, or a simple spreadsheet macro language—ANTLR v4 and this book will demystify the process. ANTLR v4 has been rewritten from scratch to make it easier than ever to build parsers and the language applications built on top. This completely rewritten new edition of the bestselling Definitive ANTLR Reference shows you how to take advantage of these new features.
48 |
49 | You can buy the book [The Definitive ANTLR 4 Reference](http://amzn.com/1934356999) at amazon or an [electronic version at the publisher's site](https://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference).
50 |
51 | You will find the [Book source code](http://pragprog.com/titles/tpantlr2/source_code) useful.
52 |
53 | ## Additional grammars
54 | [This repository](https://github.com/antlr/grammars-v4) is a collection of grammars without actions where the
55 | root directory name is the all-lowercase name of the language parsed
56 | by the grammar. For example, java, cpp, csharp, c, etc...
57 |
--------------------------------------------------------------------------------
/third_party/antlr4_lib/antlr-4.9.3-complete.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chipsalliance/fasm/ffafe821bae68637fe46e36bcfd2a01b97cdf6f2/third_party/antlr4_lib/antlr-4.9.3-complete.jar
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py{27,34,35,36,37}
3 |
4 | [testenv]
5 | passenv =
6 | CMAKE_BUILD_PARALLEL_LEVEL
7 | basepython =
8 | py27: python2.7
9 | py34: python3.4
10 | py35: python3.5
11 | py36: python3.6
12 | py37: python3.7
13 | py38: python3.8
14 | py39: python3.9
15 | deps =
16 | -rrequirements.txt
17 | commands =
18 | check-manifest --ignore tox.ini,tests*,.github*
19 | # The --no-use-pep517 flag is needed because there is currently no way to pass flags with PEP517.
20 | # Relevant issue: https://github.com/pypa/pip/issues/5771
21 | pip install -e . --install-option="--antlr-runtime={env:ANTLR4_RUNTIME_TYPE:static}" --no-use-pep517
22 | flake8 --exclude .git,env,dist,build,third_party,.tox,fasm/version.py .
23 | py.test -s tests
24 |
25 | [flake8]
26 | exclude = .tox,*.egg,build,data,third_party
27 | select = E,W,F
28 | ignore = W503 # Deprecated warning contradicts W504
29 | per-file-ignores =
30 | __init__.py: F401
31 |
32 | [check-manifest]
33 | ignore =
34 | fasm/parser/tags.py
35 | fasm/parser/libparse_fasm.*
36 | # This is needed because this is an empty directory, which causes problems:
37 | # https://stackoverflow.com/questions/2640378/include-empty-directory-with-python-setup-py-sdist
38 | third_party/antlr4/runtime/PHP
39 |
--------------------------------------------------------------------------------
/update_version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import platform
4 | import subprocess
5 | import sys
6 |
7 | VERSION_FILE = 'fasm/version.py'
8 | VERSION_FILE_TEMPLATE = '''\
9 | #!/usr/bin/env python3
10 | # -*- coding: utf-8 -*-
11 | #
12 | # Copyright 2017-2022 F4PGA Authors
13 | #
14 | # Licensed under the Apache License, Version 2.0 (the "License");
15 | # you may not use this file except in compliance with the License.
16 | # You may obtain a copy of the License at
17 | #
18 | # http://www.apache.org/licenses/LICENSE-2.0
19 | #
20 | # Unless required by applicable law or agreed to in writing, software
21 | # distributed under the License is distributed on an "AS IS" BASIS,
22 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 | # See the License for the specific language governing permissions and
24 | # limitations under the License.
25 | #
26 | # SPDX-License-Identifier: Apache-2.0
27 |
28 | # ** WARNING **
29 | # This file is auto-generated by the update_version.py script.
30 | # ** WARNING **
31 |
32 | version_str = "{version}"
33 | version_tuple = {version_tuple}
34 | try:
35 | from packaging.version import Version as V
36 | pversion = V("{version}")
37 | except ImportError:
38 | pass
39 |
40 | git_hash = "{git_hash}"
41 | git_describe = "{git_describe}"
42 | git_msg = """\\
43 | {git_msg}
44 | """
45 | '''
46 |
47 | GIT = 'git'
48 | if platform.system() == 'Windows':
49 | GIT = 'git.exe'
50 |
51 |
52 | def get_hash():
53 | cmd = [GIT, 'rev-parse', 'HEAD']
54 | try:
55 | return subprocess.check_output(cmd).decode('utf-8').strip()
56 | except OSError:
57 | print(cmd)
58 | raise
59 |
60 |
61 | def get_describe():
62 | cmd = [
63 | GIT, 'describe', '--tags', 'HEAD', '--match', 'v*', '--exclude', '*-r*'
64 | ]
65 | try:
66 | return subprocess.check_output(cmd).decode('utf-8').strip()
67 | except OSError:
68 | print(cmd)
69 | raise
70 |
71 |
72 | def get_msg():
73 | cmd = [GIT, 'log', '-1', 'HEAD']
74 | try:
75 | data = subprocess.check_output(cmd).decode('utf-8')
76 | except OSError:
77 | print(cmd)
78 | raise
79 | return '\n'.join(line.rstrip() for line in data.split('\n'))
80 |
81 |
82 | def create_version_tuple(git_describe):
83 | """
84 | >>> t = '''\\
85 | ... v0.0
86 | ... v0.0.0
87 | ... v1.0.1-265-g5f0c7a7
88 | ... v0.0-7004-g1cf70ea2
89 | ... '''
90 | >>> for d in t.splitlines():
91 | ... v = create_version_tuple(d)
92 | ... print((create_version_str(v), v))
93 | ('0.0', (0, 0, None))
94 | ('0.0.0', (0, 0, 0, None))
95 | ('1.0.1.post265', (1, 0, 1, 265))
96 | ('0.0.post7004', (0, 0, 7004))
97 | """
98 | vtag = git_describe.strip()
99 | if vtag.startswith('v'):
100 | vtag = vtag[1:]
101 |
102 | vbits = vtag.split('.')
103 | vpost = [None]
104 | if '-' in vbits[-1]:
105 | vend = vbits.pop(-1).split('-')
106 | assert len(vend) == 3, (vtag, vbits, vend)
107 | assert len(vend[0]) > 0, (vtag, vbits, vend)
108 | vbits.append(vend.pop(0))
109 | vpost = [int(vend.pop(0))]
110 | assert vend[-1].startswith('g'), (vtag, vbits, vend, vpost)
111 | vbits = [int(i) for i in vbits]
112 | vbits.extend(vpost)
113 | return tuple(vbits)
114 |
115 |
116 | def create_version_str(version_tuple):
117 | vbits = [str(i) for i in version_tuple]
118 | if version_tuple[-1] is None:
119 | vbits.pop(-1)
120 | else:
121 | vbits[-1] = 'post' + vbits[-1]
122 | return '.'.join(vbits)
123 |
124 |
125 | def update_version_py(args):
126 | output = VERSION_FILE_TEMPLATE.format(**args)
127 |
128 | old = ''
129 | try:
130 | with open(VERSION_FILE) as f:
131 | old = f.read()
132 | except IOError as e:
133 | print(e)
134 |
135 | if old != output:
136 | with open(VERSION_FILE, 'w') as f:
137 | f.write(output)
138 | print('Updated {}'.format(VERSION_FILE))
139 |
140 |
141 | def main(args):
142 | git_hash = get_hash()
143 | git_describe = get_describe()
144 | git_msg = get_msg()
145 |
146 | version_tuple = create_version_tuple(git_describe)
147 | version = create_version_str(version_tuple)
148 |
149 | update_version_py(locals())
150 |
151 | return 0
152 |
153 |
154 | if __name__ == "__main__":
155 | import doctest
156 | failure_count, test_count = doctest.testmod()
157 | if failure_count > 0:
158 | sys.exit(-1)
159 | sys.exit(main(sys.argv))
160 |
--------------------------------------------------------------------------------