├── fpga_interchange ├── constraints │ ├── __init__.py │ ├── placement_oracle.py │ ├── tool.py │ └── sat.py ├── fasm_generators │ ├── __init__.py │ ├── xc7 │ │ ├── __init__.py │ │ └── xc7_iobs.py │ ├── nexus │ │ ├── __init__.py │ │ └── nexus.py │ ├── utils.py │ └── luts.py ├── testarch_generators │ └── __init__.py ├── __init__.py ├── chip_info_utils.py ├── capnp_utils.py ├── constraint_generator.py ├── fasm_generator.py ├── patch.py ├── nextpnr.py ├── nextpnr_emit.py ├── compare_timings.py ├── yaml_support.py ├── json_support.py ├── add_prim_lib.py ├── prjxray_db_reader.py ├── annotations.py ├── compare.py ├── field_cache.py ├── convert.py ├── parameter_definitions.py └── device_timing_patching.py ├── .github ├── workflows │ ├── format.sh │ ├── testarch_device.sh │ ├── test.sh │ └── ci.yml ├── check_python_scripts.sh └── check_license.sh ├── .gitignore ├── test_data ├── gen_device_config.yaml ├── nexus_device_config.yaml ├── ecp5_constraints.yaml ├── series7_luts.yaml ├── xcup_luts.yaml ├── xcup_device_config.yaml └── series7_constraints.yaml ├── requirements.txt ├── LICENSE ├── Makefile ├── setup.py ├── README.md ├── Makefile.rapidwright └── tests ├── test_exclusive_set.py ├── test_constraints.py ├── test_basics.py ├── bram_constraint_test.py └── test_converters.py /fpga_interchange/constraints/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fpga_interchange/fasm_generators/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fpga_interchange/testarch_generators/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | make format 4 | test $(git status --porcelain | wc -l) -eq 0 || { git diff; false; } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__ 3 | __main__.py 4 | src 5 | *egg-info* 6 | dist 7 | env 8 | *.swp 9 | *.swo 10 | *.yaml 11 | !test_data/* 12 | -------------------------------------------------------------------------------- /test_data/gen_device_config.yaml: -------------------------------------------------------------------------------- 1 | global_buffer_bels: 2 | - VCC 3 | - GND 4 | buckets: 5 | - bucket: LUTS 6 | cells: 7 | - LUT1 8 | - LUT2 9 | - LUT3 10 | - LUT4 11 | - bucket: FFS 12 | cells: 13 | - DFFS 14 | - DFFR 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | psutil 3 | pycapnp==1.1.0 4 | ninja 5 | pyyaml 6 | yapf==0.24.0 7 | # TODO: https://github.com/SymbiFlow/python-fpga-interchange/issues/11 8 | git+https://github.com/litghost/rapidyaml.git@fixup_python_packaging#egg=rapidyaml 9 | -e . 10 | -------------------------------------------------------------------------------- /fpga_interchange/fasm_generators/xc7/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | from .xc7 import XC7FasmGenerator 13 | -------------------------------------------------------------------------------- /fpga_interchange/fasm_generators/nexus/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | from .nexus import NexusFasmGenerator 13 | -------------------------------------------------------------------------------- /fpga_interchange/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2021 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | 13 | def get_version(): 14 | import pkg_resources 15 | 16 | return pkg_resources.get_distribution("python-fpga-interchange").version 17 | -------------------------------------------------------------------------------- /.github/workflows/testarch_device.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export INTERCHANGE_SCHEMA_PATH="$GITHUB_WORKSPACE/env/fpga-interchange-schema/interchange" 4 | export CAPNP_PATH="$GITHUB_WORKSPACE/env/capnproto-java/compiler/src/main/schema/" 5 | 6 | python3 fpga_interchange/testarch_generators/generate_testarch.py --schema_dir $INTERCHANGE_SCHEMA_PATH 7 | mv device_resources.device.gz $GITHUB_WORKSPACE/env 8 | python3 fpga_interchange/nextpnr_emit.py --schema_dir $INTERCHANGE_SCHEMA_PATH --output_dir $GITHUB_WORKSPACE/env --device_config test_data/gen_device_config.yaml --device $GITHUB_WORKSPACE/env/device_resources.device.gz 9 | 10 | test $(git status --porcelain | wc -l) -eq 0 || { git diff; false; } 11 | -------------------------------------------------------------------------------- /.github/workflows/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export RAPIDWRIGHT_PATH="$GITHUB_WORKSPACE/env/RapidWright" 4 | 5 | # Create the device resource for the test part. 6 | pushd "$GITHUB_WORKSPACE/env" 7 | "$RAPIDWRIGHT_PATH/scripts/invoke_rapidwright.sh" \ 8 | com.xilinx.rapidwright.interchange.DeviceResourcesExample \ 9 | xc7a50tfgg484-1 10 | popd 11 | 12 | export CAPNP_PATH="$GITHUB_WORKSPACE/env/capnproto-java/compiler/src/main/schema/" 13 | export INTERCHANGE_SCHEMA_PATH="$GITHUB_WORKSPACE/env/fpga-interchange-schema/interchange" 14 | export DEVICE_RESOURCE_PATH="$GITHUB_WORKSPACE/env" 15 | 16 | make test-py 17 | RESULT=$? 18 | if [ $RESULT -ne 0 ]; then 19 | exit $RESULT 20 | fi 21 | test $(git status --porcelain | wc -l) -eq 0 || { git diff; false; } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 The F4PGA Authors 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /fpga_interchange/chip_info_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2021 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | 13 | class LutCell(): 14 | def __init__(self): 15 | self.name = '' 16 | self.pins = [] 17 | 18 | 19 | class LutBel(): 20 | def __init__(self): 21 | self.name = '' 22 | self.pins = [] 23 | self.low_bit = 0 24 | self.high_bit = 0 25 | self.out_pin = '' 26 | 27 | 28 | class LutElement(): 29 | def __init__(self): 30 | self.width = 0 31 | self.lut_bels = [] 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 The F4PGA Authors. 2 | # 3 | # Use of this source code is governed by a ISC-style 4 | # license that can be found in the LICENSE file or at 5 | # https://opensource.org/licenses/ISC 6 | # 7 | # SPDX-License-Identifier: ISC 8 | SHELL=bash 9 | PYTHON?=python3 10 | 11 | ALL_EXCLUDE = third_party .git env build 12 | FORMAT_EXCLUDE = $(foreach x,$(ALL_EXCLUDE),-and -not -path './$(x)/*') 13 | 14 | PYTHON_SRCS=$(shell find . -name "*py" $(FORMAT_EXCLUDE)) 15 | 16 | IN_ENV = if [ -e env/bin/activate ]; then . env/bin/activate; fi; 17 | env: 18 | $(PYTHON) -mvenv env 19 | $(IN_ENV) pip install --upgrade -r requirements.txt 20 | 21 | format: ${PYTHON_SRCS} 22 | $(IN_ENV) yapf -i ${PYTHON_SRCS} 23 | 24 | test-py: 25 | $(IN_ENV) pytest -s --doctest-modules 26 | 27 | clean: 28 | rm -rf env 29 | 30 | .PHONY: clean env build test-py 31 | -------------------------------------------------------------------------------- /fpga_interchange/capnp_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | import capnp.lib.capnp 12 | 13 | 14 | def get_module_from_id(capnp_id, parser=None): 15 | """ Return the capnp module based on the capnp node id. 16 | 17 | This is useful to determine the schema of a node within a capnp tree. 18 | 19 | The parser argument is optional, because in most circumstances the pycapnp 20 | _global_schema_parser is used. In the event that this parser was not used, 21 | the parser must be provided. This case should be rare. 22 | 23 | """ 24 | if parser is None: 25 | parser = capnp.lib.capnp._global_schema_parser 26 | 27 | return parser.modules_by_id[capnp_id] 28 | -------------------------------------------------------------------------------- /test_data/nexus_device_config.yaml: -------------------------------------------------------------------------------- 1 | # Which BEL names are global buffers for nextpnr? 2 | global_buffer_bels: 3 | - DCC 4 | # Which cell names are global buffers, and which pins should use dedicated routing resources 5 | global_buffer_cells: 6 | - cell: DCC 7 | pins: # list of pins that use global resources 8 | - name: CLKI # pin name 9 | guide_placement: true # attempt to place so that this pin can use dedicated resources 10 | max_hops: 10 # max hops of interconnect to search (10 is for test purposes and may need to be refined) 11 | - name: CLKO 12 | force_dedicated_routing: true # the net connected to this pin _must_ use dedicated routing only 13 | - cell: OSC_CORE 14 | pins: # list of pins that use global resources 15 | - name: HFCLKOUT 16 | force_dedicated_routing: true # the net connected to this pin _must_ use dedicated routing only 17 | # How should nextpnr lump BELs during analytic placement? 18 | buckets: 19 | - bucket: LUTS 20 | cells: 21 | - LUT4 22 | - bucket: FFS 23 | cells: 24 | - FD1P3BX 25 | - FD1P3DX 26 | - FD1P3IX 27 | - FD1P3JX 28 | - bucket: IOBs 29 | cells: 30 | - IB 31 | - OB 32 | - bucket: BRAMs 33 | cells: 34 | - DP16K_MODE 35 | - PDP16K_MODE 36 | - PDPSC16K_MODE 37 | 38 | -------------------------------------------------------------------------------- /fpga_interchange/constraints/placement_oracle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | from fpga_interchange.constraints.model import BelMatcher 13 | 14 | 15 | class PlacementOracle(): 16 | """ Placement oracle returns a set of matchers for a cell type. """ 17 | 18 | def __init__(self): 19 | self.cell_types = {} 20 | 21 | def add_cell_matcher(self, cell_type, site_type, bel): 22 | if cell_type not in self.cell_types: 23 | self.cell_types[cell_type] = [] 24 | 25 | self.cell_types[cell_type].append(BelMatcher(site_type, bel)) 26 | 27 | def matchers_for_cell(self, cell_type): 28 | """ Returns list of matchers for a cell type. """ 29 | if cell_type not in self.cell_types: 30 | assert False, 'Unsupported cell {}'.format(cell_type) 31 | 32 | return self.cell_types[cell_type] 33 | 34 | def add_sites_from_device(self, device): 35 | self.cell_types = {} 36 | 37 | for cell_bel in device.yield_cell_bel_mappings(): 38 | for site_type, bel in cell_bel.site_types_and_bels: 39 | self.add_cell_matcher( 40 | str(cell_bel.cell), str(site_type), str(bel)) 41 | -------------------------------------------------------------------------------- /.github/check_python_scripts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | # 4 | # Use of this source code is governed by a ISC-style 5 | # license that can be found in the LICENSE file or at 6 | # https://opensource.org/licenses/ISC 7 | # 8 | # SPDX-License-Identifier: ISC 9 | 10 | echo 11 | echo "===================================" 12 | echo "Check Python UTF coding and shebang" 13 | echo "===================================" 14 | echo 15 | 16 | ERROR_FILES_SHEBANG="" 17 | ERROR_FILES_UTF_CODING="" 18 | FILES_TO_CHECK=`find . \ 19 | -size +0 -type f \( -name '*.py' \) \ 20 | \( -not -path "*/.*/*" -not -path "*/third_party/*" -not -path "*/env/*" \)` 21 | 22 | for file in $FILES_TO_CHECK; do 23 | echo "Checking $file" 24 | if [[ -x $file ]]; then 25 | grep -q "\#\!/usr/bin/env python3" $file || ERROR_FILES_SHEBANG="$ERROR_FILES_SHEBANG $file" 26 | fi 27 | grep -q "\#.*coding: utf-8" $file || ERROR_FILES_UTF_CODING="$ERROR_FILES_UTF_CODING $file" 28 | done 29 | 30 | if [ ! -z "$ERROR_FILES_SHEBANG" ]; then 31 | for file in $ERROR_FILES_SHEBANG; do 32 | echo "ERROR: $file does not have the python3 shebang." 33 | done 34 | exit 1 35 | fi 36 | 37 | if [ ! -z "$ERROR_FILES_UTF_CODING" ]; then 38 | for file in $ERROR_FILES_UTF_CODING; do 39 | echo "ERROR: $file does not have the UTF encoding set." 40 | echo "Add # coding: utf-8" 41 | done 42 | exit 1 43 | fi 44 | 45 | echo 46 | -------------------------------------------------------------------------------- /fpga_interchange/constraint_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | 13 | class ConstraintPrototype(): 14 | def __init__(self): 15 | self.tags = {} 16 | self.bel_cell_constraints = {} 17 | 18 | def add_tag(self, tag_prefix, tag): 19 | """ Add a uniquely named tag (and its prefix) to this prototype. """ 20 | assert tag_prefix not in self.tags 21 | self.tags[tag_prefix] = tag 22 | 23 | def add_cell_placement_constraint(self, cell_type, site_index, site_type, 24 | bel, tag, constraint): 25 | """ Add a constraint that is applied when a cell is placed. 26 | 27 | The way to read this function: 28 | - When a cell of type is placed at , apply 29 | to . 30 | - When a cell of type is removed from , remove 31 | to . 32 | 33 | """ 34 | assert tag in self.tags, tag 35 | 36 | key = cell_type, site_index, site_type, bel 37 | 38 | if key not in self.bel_cell_constraints: 39 | self.bel_cell_constraints[key] = [] 40 | 41 | self.bel_cell_constraints[key].append((tag, constraint)) 42 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | Run-tests: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | python-version: ['3.7', '3.8', '3.9'] 12 | steps: 13 | 14 | - uses: actions/checkout@v1 15 | with: 16 | submodules: recursive 17 | 18 | - name: Setup Python 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | 23 | - name: Install 24 | run: | 25 | sudo apt-get update 26 | sudo apt-get install libyaml-dev libyaml-cpp-dev swig cmake default-jdk -y 27 | pip3 install --upgrade -r requirements.txt 28 | 29 | - name: Check formatting 30 | run: ./.github/workflows/format.sh 31 | 32 | - name: Get and build tools 33 | run: | 34 | make env 35 | git clone https://github.com/Xilinx/RapidWright.git $GITHUB_WORKSPACE/env/RapidWright 36 | make -C "$GITHUB_WORKSPACE/env/RapidWright" update_jars 37 | make -C "$GITHUB_WORKSPACE/env/RapidWright" compile 38 | git clone https://github.com/capnproto/capnproto-java.git $GITHUB_WORKSPACE/env/capnproto-java 39 | git clone https://github.com/chipsalliance/fpga-interchange-schema.git $GITHUB_WORKSPACE/env/fpga-interchange-schema 40 | 41 | - name: Test testarch 42 | run: ./.github/workflows/testarch_device.sh 43 | 44 | - name: Test 45 | run: ./.github/workflows/test.sh 46 | 47 | - name: "License Checks" 48 | run: | 49 | ./.github/check_license.sh 50 | ./.github/check_python_scripts.sh 51 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | import setuptools 13 | 14 | with open("README.md", "r") as fh: 15 | long_description = fh.read() 16 | 17 | setuptools.setup( 18 | name="python-fpga-interchange", 19 | version="0.0.18", 20 | author="F4PGA Authors", 21 | author_email="f4pga-wg@lists.chipsalliance.org", 22 | description="Python library for reading and writing FPGA interchange files", 23 | long_description=long_description, 24 | long_description_content_type="text/markdown", 25 | url="https://github.com/chipsalliance/python-fpga-interchange", 26 | python_requires=">=3.7", 27 | packages=setuptools.find_packages(), 28 | include_package_data=True, 29 | install_requires=["pycapnp==1.1.0", "python-sat"], 30 | classifiers=[ 31 | "Programming Language :: Python :: 3", 32 | "License :: OSI Approved :: ISC License", 33 | "Operating System :: OS Independent", 34 | ], 35 | entry_points={ 36 | 'console_scripts': [ 37 | 'fpga_inter_add_prim_lib=fpga_interchange.add_prim_lib:main', 38 | 'fpga_inter_convert=fpga_interchange.convert:main', 39 | 'fpga_inter_nextpnr_emit=fpga_interchange.nextpnr_emit:main', 40 | 'fpga_inter_patch=fpga_interchange.patch:main', 41 | 'fpga_inter_yosys_json=fpga_interchange.yosys_json:main', 42 | 'fpga_inter_fasm_generator=fpga_interchange.fasm_generator:main', 43 | ], 44 | }) 45 | -------------------------------------------------------------------------------- /fpga_interchange/fasm_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2021 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | import argparse 12 | 13 | from fpga_interchange.interchange_capnp import Interchange 14 | from fpga_interchange.fasm_generators.xc7 import XC7FasmGenerator 15 | from fpga_interchange.fasm_generators.nexus import NexusFasmGenerator 16 | 17 | 18 | def main(): 19 | parser = argparse.ArgumentParser(description=__doc__) 20 | 21 | parser.add_argument( 22 | '--schema_dir', 23 | required=True, 24 | help="Path to the FPGA interchange schemas directory.") 25 | parser.add_argument( 26 | '--family', required=True, help="Device family. E.g. xc7, xcu, etc.") 27 | parser.add_argument('device_resources', help="Device resources file.") 28 | parser.add_argument('logical_netlist', help="Logical netlist file.") 29 | parser.add_argument('physical_netlist', help="Physical netlist file.") 30 | parser.add_argument('fasm_file', help="FASM output file.") 31 | 32 | args = parser.parse_args() 33 | 34 | interchange = Interchange(args.schema_dir) 35 | 36 | family_map = { 37 | "xc7": XC7FasmGenerator, 38 | "nexus": NexusFasmGenerator, 39 | } 40 | 41 | device_resources = args.device_resources 42 | logical_net = args.logical_netlist 43 | physical_net = args.physical_netlist 44 | fasm_file = args.fasm_file 45 | 46 | fasm_generator = family_map[args.family](interchange, device_resources, 47 | logical_net, physical_net) 48 | fasm_generator.fill_features() 49 | fasm_generator.output_fasm(fasm_file) 50 | 51 | 52 | if __name__ == "__main__": 53 | main() 54 | -------------------------------------------------------------------------------- /.github/check_license.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | # 4 | # Use of this source code is governed by a ISC-style 5 | # license that can be found in the LICENSE file or at 6 | # https://opensource.org/licenses/ISC 7 | # 8 | # SPDX-License-Identifier: ISC 9 | 10 | echo 11 | echo "===========================" 12 | echo "Check SPDX identifier" 13 | echo "===========================" 14 | echo 15 | 16 | ERROR_FILES="" 17 | FILES_TO_CHECK=`find . \ 18 | -size +0 -type f \( -name '*.sh' -o -name '*.py' -o -name 'Makefile' -o -name '*.tcl' \) \ 19 | \( -not -path "*/.*/*" -not -path "*/third_party/*" -not -path "*/database/*" -not -path "*/env/*" \)` 20 | 21 | for file in $FILES_TO_CHECK; do 22 | echo "Checking $file" 23 | grep -q "SPDX-License-Identifier" $file || ERROR_FILES="$ERROR_FILES $file" 24 | done 25 | 26 | if [ ! -z "$ERROR_FILES" ]; then 27 | for file in $ERROR_FILES; do 28 | echo "ERROR: $file does not have license information." 29 | done 30 | exit 1 31 | fi 32 | 33 | echo 34 | echo "===========================" 35 | echo "Check third party LICENSE" 36 | echo "===========================" 37 | echo 38 | 39 | function check_if_submodule { 40 | for submodule in `git submodule --quiet foreach 'echo $sm_path'`; do 41 | if [ "$1" == "$submodule" ]; then 42 | return 1 43 | fi 44 | done 45 | } 46 | 47 | THIRD_PARTY_DIRS="" 48 | if [[ -e third_party ]]; then 49 | THIRD_PARTY_DIRS=`ls third_party --ignore=reformat.tcl --ignore cctz --ignore gflags --ignore yosys ` 50 | fi 51 | ERROR_NO_LICENSE="" 52 | 53 | for dir in $THIRD_PARTY_DIRS; do 54 | # Checks if we are not in a submodule 55 | if check_if_submodule $dir; then 56 | echo "Checking third_party/$dir" 57 | [ -f third_party/$dir/LICENSE ] || ERROR_NO_LICENSE="$ERROR_NO_LICENSE $dir" 58 | fi 59 | done 60 | 61 | if [ ! -z "$ERROR_NO_LICENSE" ]; then 62 | for dir in $ERROR_NO_LICENSE; do 63 | echo "ERROR: $dir does not have the LICENSE file." 64 | done 65 | exit 1 66 | fi 67 | -------------------------------------------------------------------------------- /fpga_interchange/fasm_generators/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2021 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | from fpga_interchange.parameter_definitions import ParameterDefinition 13 | 14 | 15 | def format_feature_value(bits, start_bit=0): 16 | """ 17 | Formats a FASM feature value assignment according to the given bits 18 | as a string or any iterable yeilding "0" and "1". The iterable must return 19 | bits starting from LSB and ending on MSB. The yieled bit count determines 20 | the FASM feature assignment width - there is no padding. Optionally the 21 | start_bit parameter can be used for offset. 22 | """ 23 | bits = list(bits) 24 | 25 | count = len(bits) 26 | value = "".join(bits[::-1]) 27 | 28 | if count == 1: 29 | bitrange = "[{}]".format(start_bit) 30 | elif count > 1: 31 | bitrange = "[{}:{}]".format(count - 1 + start_bit, start_bit) 32 | else: 33 | assert False, count 34 | 35 | return "{}={}'b{}".format(bitrange, count, value) 36 | 37 | 38 | def get_cell_integer_param(device_resources, 39 | cell_data, 40 | name, 41 | force_format=None): 42 | """ 43 | Retrieves definition and decodes value of an integer cell parameter. The 44 | function can optionally force a specific encoding format if needed. 45 | """ 46 | 47 | # Get the parameter definition to determine its type 48 | param = device_resources.get_parameter_definition(cell_data.cell_type, 49 | name) 50 | 51 | # Force the format if requested by substituting the paraameter 52 | # definition object. 53 | if not param.is_integer_like() and force_format is not None: 54 | if force_format != param.string_format: 55 | param = ParameterDefinition( 56 | name=name, 57 | string_format=force_format, 58 | default_value=cell_data.attributes[name]) 59 | 60 | # Decode 61 | return param.decode_integer(cell_data.attributes[name]) 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FPGA Interchange 2 | 3 | This python module is designed to read and write FPGA interchange files, and 4 | provide some interoperability with other common formats. 5 | 6 | ## Capabilities 7 | 8 | This library supports the following capabilities: 9 | - Generate FPGA interchange files using Pythonic object model 10 | - Read FPGA interchange files into Pythonic object model 11 | - Sanity check logical netlist for completeness and correctness. 12 | - Sanity check a logical and physical netlist for completeness and 13 | correctness, given a device database. 14 | - Read some common logical netlist formats into the Pythonic object model: 15 | - (Planned) eblif 16 | - Yosys Netlist JSON 17 | - Basic (incomplete) placer constraint solver 18 | 19 | ## Basic placer constraint solver 20 | 21 | The placer constraint solver enforces the constraints per 22 | 23 | ### Running basic placer constraint solver 24 | 25 | First generate xc7a35tcpg236-1 database from RapidWright: 26 | ``` 27 | "$RAPIDWRIGHT_PATH/scripts/invoke_rapidwright.sh" \ 28 | com.xilinx.rapidwright.interchange.DeviceResourcesExample \ 29 | xc7a50tfgg484-1 30 | ``` 31 | 32 | Annotated the xc7a35tcpg236-1 database with constraints: 33 | ``` 34 | python3 -mfpga_interchange.patch \ 35 | --schema_dir $RAPIDWRIGHT_PATH/interchange \ 36 | --schema device \ 37 | --patch_path constraints \ 38 | --patch_format yaml \ 39 | xc7a35tcpg236-1.device \ 40 | test_data/series7_constraints.yaml \ 41 | xc7a35tcpg236-1_constraints.device 42 | ``` 43 | 44 | Write out example physical netlist: 45 | ``` 46 | python3 tests/example_netlist.py \ 47 | --schema_dir "$RAPIDWRIGHT_PATH/interchange" \ 48 | --logical_netlist simple.netlist \ 49 | --physical_netlist simple.phys \ 50 | --xdc simple.xdc 51 | ``` 52 | 53 | ``` 54 | python3 -mfpga_interchange.constraints.tool \ 55 | --schema_dir "$RAPIDWRIGHT_PATH/interchange" \ 56 | --allowed_sites IOB_X0Y0,IOB_X0Y1,IOB_X0Y2,SLICE_X0Y0,BUFGCTRL_X0Y0 \ 57 | --filtered_cells VCC,GND \ 58 | --verbose \ 59 | xc7a35tcpg236-1_constraints.device \ 60 | simple.netlist 61 | ``` 62 | 63 | This repository was moved to CHIPS Alliance as of commit `b13ee55ebce` As part of the CHIPS due diligence process for non-Apache 2.0 projects, all contributors for over 1% of the codebase signed the CHIPS CLA: Antmicro (corporate CLA), Google (corporate CLA), gatecat (individual CLA). Any further contributions require a CLA to comply with the CHIPS Alliance IP policy. 64 | -------------------------------------------------------------------------------- /fpga_interchange/patch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ Small utility for patching FPGA interchange capnp files. 12 | 13 | This utility completely replaces portion of the message at the specified 14 | location. 15 | 16 | """ 17 | import argparse 18 | 19 | from fpga_interchange.interchange_capnp import read_capnp_file, \ 20 | write_capnp_file 21 | from fpga_interchange.convert import SCHEMAS, FORMATS, get_schema, read_format_to_message 22 | from fpga_interchange.json_support import to_json, from_json 23 | 24 | 25 | def patch_capnp(message, patch_path, patch_format, in_f): 26 | message_to_populate = message 27 | for path in patch_path[:-1]: 28 | message_to_populate = getattr(message_to_populate, path) 29 | 30 | message_to_populate = message_to_populate.init(patch_path[-1]) 31 | 32 | if patch_format == 'capnp': 33 | message = read_capnp_file(message_to_populate.schema, in_f) 34 | json_data = to_json(message) 35 | from_json(message_to_populate, json_data) 36 | else: 37 | read_format_to_message(message_to_populate, patch_format, in_f) 38 | 39 | 40 | def main(): 41 | parser = argparse.ArgumentParser(description=__doc__) 42 | 43 | parser.add_argument('--schema_dir', required=True) 44 | parser.add_argument('--schema', required=True, choices=SCHEMAS) 45 | parser.add_argument('--root_schema_path', default=None) 46 | parser.add_argument('--patch_path', required=True) 47 | parser.add_argument('--patch_format', required=True, choices=FORMATS) 48 | parser.add_argument('root') 49 | parser.add_argument('patch') 50 | parser.add_argument('output') 51 | 52 | args = parser.parse_args() 53 | 54 | patch_path = args.patch_path.split('.') 55 | 56 | root_schema = get_schema(args.schema_dir, args.schema, 57 | args.root_schema_path) 58 | 59 | with open(args.root, 'rb') as f: 60 | message = read_capnp_file(root_schema, f) 61 | 62 | message = message.as_builder() 63 | 64 | with open(args.patch, 'rb') as f: 65 | patch_capnp(message, patch_path, args.patch_format, f) 66 | 67 | with open(args.output, 'wb') as f: 68 | write_capnp_file(message, f) 69 | 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /Makefile.rapidwright: -------------------------------------------------------------------------------- 1 | 2 | NEXTPNR_PATH := $(shell echo ~/cat_x/nextpnr/) 3 | RAPIDWRIGHT_PATH := $(shell echo ~/cat_x/RapidWright) 4 | INTERCHANGE_PATH := $(shell echo ~/cat_x/fpga_interchange_schema)/interchange 5 | 6 | PART := xc7a35tcpg236-1 7 | CONSTRAINTS := test_data/series7_constraints.yaml 8 | LUTS := test_data/series7_luts.yaml 9 | BEL_BUCKET_SEEDS := test_data/series7_bel_buckets.yaml 10 | 11 | .DELETE_ON_ERROR: 12 | .PHONY: all yaml 13 | 14 | all: $(NEXTPNR_PATH)/fpga_interchange/chipdb.bba 15 | 16 | rapidwright.force: 17 | touch rapidwright.force 18 | 19 | rapidwright.update: rapidwright.force 20 | # FIXME: Remove patch of api-lib once RapidWright 2020.2.1 is released. 21 | cd $(RAPIDWRIGHT_PATH) && \ 22 | make update_jars && \ 23 | wget https://github.com/Xilinx/RapidWright/releases/download/v2020.2.1-beta/rapidwright-api-lib-2020.2.1_update1.jar && \ 24 | mv rapidwright-api-lib-2020.2.1_update1.jar jars/rapidwright-api-lib-2020.2.0.jar && \ 25 | make 26 | touch rapidwright.update 27 | 28 | $(PART).device: rapidwright.update 29 | RAPIDWRIGHT_PATH=$(RAPIDWRIGHT_PATH) /usr/bin/time -v \ 30 | $(RAPIDWRIGHT_PATH)/scripts/invoke_rapidwright.sh \ 31 | com.xilinx.rapidwright.interchange.DeviceResourcesExample \ 32 | $(PART) 33 | 34 | $(PART)_constraints.device: $(PART).device 35 | /usr/bin/time -v python3 -mfpga_interchange.patch \ 36 | --schema_dir $(INTERCHANGE_PATH) \ 37 | --schema device \ 38 | --patch_path constraints \ 39 | --patch_format yaml \ 40 | $(PART).device \ 41 | $(CONSTRAINTS) \ 42 | $(PART)_constraints.device 43 | 44 | $(PART)_constraints_luts.device: $(PART)_constraints.device 45 | /usr/bin/time -v python3 -mfpga_interchange.patch \ 46 | --schema_dir $(INTERCHANGE_PATH) \ 47 | --schema device \ 48 | --patch_path lutDefinitions \ 49 | --patch_format yaml \ 50 | $(PART)_constraints.device \ 51 | $(LUTS) \ 52 | $(PART)_constraints_luts.device 53 | 54 | yaml: $(PART)_constraints_luts.device 55 | /usr/bin/time -v python3 -mfpga_interchange.convert \ 56 | --schema_dir $(INTERCHANGE_PATH) \ 57 | --schema device \ 58 | --input_format capnp \ 59 | --output_format yaml \ 60 | $(PART)_constraints_luts.device \ 61 | $(PART)_constraints_luts.yaml 62 | 63 | $(NEXTPNR_PATH)/fpga_interchange/chipdb.bba: $(PART)_constraints_luts.device 64 | /usr/bin/time -v python3 -mfpga_interchange.nextpnr_emit \ 65 | --schema_dir $(INTERCHANGE_PATH) \ 66 | --output_dir $(NEXTPNR_PATH)/fpga_interchange/ \ 67 | --bel_bucket_seeds $(BEL_BUCKET_SEEDS) \ 68 | --device $(PART)_constraints_luts.device 69 | 70 | clean: 71 | rm -f rapidwright.force 72 | rm -f $(PART)*.device 73 | rm -f $(NEXTPNR_PATH)/fpga_interchange/chipdb.bba 74 | -------------------------------------------------------------------------------- /fpga_interchange/nextpnr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | from enum import Enum 12 | 13 | 14 | class PortType(Enum): 15 | PORT_IN = 0 16 | PORT_OUT = 1 17 | PORT_INOUT = 2 18 | 19 | 20 | class BbaWriter(): 21 | def __init__(self, f, const_ids): 22 | self.f = f 23 | self.const_ids = const_ids 24 | 25 | self.labels = set() 26 | self.refs = set() 27 | 28 | def println(self, s): 29 | print(s, file=self.f) 30 | 31 | def u8(self, value): 32 | print('u8 {}'.format(value), file=self.f) 33 | 34 | def u16(self, value): 35 | print('u16 {}'.format(value), file=self.f) 36 | 37 | def u32(self, value): 38 | print('u32 {}'.format(value), file=self.f) 39 | 40 | def label(self, label, label_type): 41 | assert label not in self.labels, label 42 | self.labels.add(label) 43 | print('label {} {}'.format(label, label_type), file=self.f) 44 | 45 | def ref(self, ref, comment=None): 46 | self.refs.add(ref) 47 | if comment is None: 48 | print('ref {}'.format(ref), file=self.f) 49 | else: 50 | print('ref {} {}'.format(ref, comment), file=self.f) 51 | 52 | def str(self, s, comment=None): 53 | if comment is None: 54 | print('str |{}|'.format(s), file=self.f) 55 | else: 56 | print('str |{}| {}'.format(s, comment), file=self.f) 57 | 58 | def str_id(self, s): 59 | if s == ('', ): 60 | index = self.const_ids.get_index('') 61 | else: 62 | index = self.const_ids.get_index(s) 63 | 64 | # Pretty weird to see an empty string here, fail and make sure that 65 | # this was the intention. 66 | assert index > 0, s 67 | 68 | self.u32(index) 69 | 70 | def pre(self, s): 71 | print("pre {}".format(s), file=self.f) 72 | 73 | def post(self, s): 74 | print("post {}".format(s), file=self.f) 75 | 76 | def push(self, name): 77 | print("push {}".format(name), file=self.f) 78 | 79 | def pop(self): 80 | print("pop", file=self.f) 81 | 82 | def check_labels(self): 83 | refs_and_labels = self.refs & self.labels 84 | assert len(refs_and_labels) == len(self.refs), "{} vs. {}".format( 85 | len(refs_and_labels), len(self.refs)) 86 | assert len(refs_and_labels) == len(self.labels), "{} vs. {}".format( 87 | len(refs_and_labels), len(self.labels)) 88 | -------------------------------------------------------------------------------- /fpga_interchange/nextpnr_emit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | import argparse 12 | import os 13 | 14 | from fpga_interchange.interchange_capnp import Interchange 15 | from fpga_interchange.converters import Enumerator 16 | from fpga_interchange.nextpnr import BbaWriter 17 | from fpga_interchange.populate_chip_info import populate_chip_info 18 | import yaml 19 | 20 | 21 | def main(): 22 | parser = argparse.ArgumentParser() 23 | parser.add_argument('--schema_dir', required=True) 24 | parser.add_argument('--output_dir', required=True) 25 | parser.add_argument('--device', required=True) 26 | parser.add_argument('--device_config', required=True) 27 | parser.add_argument( 28 | '--suffix', 29 | type=str, 30 | default=None, 31 | help="An optional suffix to append to output file names") 32 | 33 | args = parser.parse_args() 34 | interchange = Interchange(args.schema_dir) 35 | 36 | with open(args.device, 'rb') as f: 37 | device = interchange.read_device_resources(f) 38 | 39 | with open(args.device_config, 'r') as f: 40 | device_config = yaml.safe_load(f.read()) 41 | 42 | const_ids = Enumerator() 43 | 44 | # ID = 0 is always the empty string! 45 | assert const_ids.get_index('') == 0 46 | 47 | chip_info = populate_chip_info(device, const_ids, device_config) 48 | 49 | if args.suffix: 50 | fname = 'chipdb-{}.bba'.format(args.suffix) 51 | else: 52 | fname = 'chipdb.bba' 53 | 54 | with open(os.path.join(args.output_dir, fname), 'w') as f: 55 | bba = BbaWriter(f, const_ids) 56 | bba.pre("#include \"nextpnr.h\"") 57 | bba.pre("NEXTPNR_NAMESPACE_BEGIN") 58 | bba.post("NEXTPNR_NAMESPACE_END") 59 | bba.push("chipdb_blob") 60 | 61 | root_prefix = 'chip_info' 62 | bba.ref(root_prefix, root_prefix) 63 | chip_info.append_bba(bba, root_prefix) 64 | 65 | bba.label(chip_info.strings_label(root_prefix), 'strings_slice') 66 | bba.ref('strings_data') 67 | bba.u32(len(const_ids.values) - 1) 68 | 69 | bba.label('strings_data', 'strings') 70 | for s in const_ids.values[1:]: 71 | bba.str(s) 72 | 73 | bba.pop() 74 | 75 | bba.check_labels() 76 | 77 | if args.suffix: 78 | fname = 'constids-{}.txt'.format(args.suffix) 79 | else: 80 | fname = 'constids.txt' 81 | 82 | with open(os.path.join(args.output_dir, fname), 'w') as f: 83 | for s in const_ids.values[1:]: 84 | print('X({})'.format(s), file=f) 85 | 86 | 87 | if __name__ == "__main__": 88 | main() 89 | -------------------------------------------------------------------------------- /tests/test_exclusive_set.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | import unittest 13 | 14 | from fpga_interchange.constraints.sat import ExclusiveStateGroup, Solver 15 | from pysat.solvers import Solver as SatSolver 16 | 17 | DEBUG = False 18 | 19 | 20 | class TestExclusiveSet(unittest.TestCase): 21 | def test_two_state_set(self): 22 | state_group = ExclusiveStateGroup('TEST', 'default') 23 | 24 | state_group.add_state('default') 25 | state_group.add_state('state1') 26 | 27 | solver = Solver() 28 | solver.add_state_group(state_group) 29 | 30 | clauses = solver.prepare_for_sat() 31 | 32 | for state in state_group.states: 33 | with SatSolver() as sat: 34 | for clause in clauses: 35 | sat.add_clause(clause) 36 | 37 | variable_name = state_group.assert_state(state).variable_name() 38 | assumptions = [solver.get_variable(variable_name)] 39 | self.assertTrue(sat.solve(assumptions=assumptions)) 40 | 41 | model = sat.get_model() 42 | state_groups_vars, other_vars = solver.decode_solution_model( 43 | model) 44 | self.assertEqual(other_vars, set()) 45 | self.assertEqual(state_groups_vars, {'TEST': state}) 46 | 47 | def test_four_state_set(self): 48 | state_group = ExclusiveStateGroup('TEST', 'default') 49 | 50 | state_group.add_state('default') 51 | state_group.add_state('state1') 52 | state_group.add_state('state2') 53 | state_group.add_state('state3') 54 | 55 | solver = Solver() 56 | solver.add_state_group(state_group) 57 | 58 | clauses = solver.prepare_for_sat() 59 | 60 | if DEBUG: 61 | for variable_name, variable_idx in sorted( 62 | solver.variable_name_to_index.items(), key=lambda x: x[1]): 63 | print('{: 10d} - {}'.format(variable_idx, variable_name)) 64 | 65 | print('Clauses:') 66 | for clause in clauses: 67 | print(clause) 68 | 69 | for state in state_group.states: 70 | with SatSolver() as sat: 71 | for clause in clauses: 72 | sat.add_clause(clause) 73 | 74 | variable_name = state_group.assert_state(state).variable_name() 75 | assumptions = [solver.get_variable(variable_name)] 76 | 77 | if DEBUG: 78 | print('Assumptions:') 79 | print(assumptions) 80 | 81 | result = sat.solve(assumptions=assumptions) 82 | if not result: 83 | if DEBUG: 84 | print('Core:') 85 | print(sat.get_core()) 86 | self.assertTrue(result) 87 | 88 | model = sat.get_model() 89 | state_groups_vars, other_vars = solver.decode_solution_model( 90 | model) 91 | self.assertEqual(other_vars, set()) 92 | self.assertEqual(state_groups_vars, {'TEST': state}) 93 | 94 | 95 | if __name__ == '__main__': 96 | unittest.main() 97 | -------------------------------------------------------------------------------- /fpga_interchange/compare_timings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2021 The F4PGA Authors. 5 | # 6 | # Use this source code is governed by a ISC-style 7 | # license that can be found in LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ 12 | This file contains script to compare 2 timing analysis result files. 13 | First file is used as baseline, second one is compared against base. 14 | Results are as follows: 15 | (base_line_net_name, compared_net_name) (relative_value, baseline_value, compared_value) 16 | """ 17 | 18 | import argparse 19 | import sys 20 | import re 21 | 22 | # ============================================================================ 23 | 24 | 25 | def main(): 26 | 27 | parser = argparse.ArgumentParser( 28 | description="Performs static timing analysis") 29 | parser.add_argument( 30 | "--base_timing", required=True, help="Path to file with base timings") 31 | parser.add_argument( 32 | "--compare_timing", 33 | required=True, 34 | help="Path to file with timings to compare") 35 | parser.add_argument( 36 | "--name_mapping", 37 | help= 38 | "Path to file with mappings from compare_timing net name to base_timings net name" 39 | ) 40 | parser.add_argument( 41 | "--output_file", help="Path to file with results", default=None) 42 | 43 | args = parser.parse_args() 44 | 45 | if args.output_file is not None: 46 | output_file = open(args.output_file, 'w') 47 | else: 48 | output_file = sys.stdout 49 | 50 | baseline = {} 51 | with open(args.base_timing, 'r') as f: 52 | for line in f.readlines(): 53 | line = line.split() 54 | line[0] = re.sub("\\\\+", '\\\\', line[0]) 55 | baseline[line[0]] = int(float(line[1])) 56 | 57 | comp = {} 58 | with open(args.compare_timing, 'r') as f: 59 | for line in f.readlines(): 60 | line = line.split() 61 | line[0] = re.sub("\\\\+", '\\\\', line[0]) 62 | comp[line[0]] = int(float(line[1])) 63 | 64 | map_net = {} 65 | if args.name_mapping is not None: 66 | with open(args.name_mapping, 'r') as f: 67 | for line in f.readlines(): 68 | line = line.split() 69 | map_net[line[0]] = line[1] 70 | 71 | not_found = {} 72 | net_compare = {} 73 | 74 | for key, value in comp.items(): 75 | n_key = key 76 | if key in map_net.keys(): 77 | n_key = map_net[key] 78 | if n_key not in baseline.keys(): 79 | not_found[key] = value 80 | continue 81 | base_v = baseline[n_key] 82 | if base_v != 0: 83 | net_compare[(n_key, key)] = (value / base_v, base_v, value) 84 | elif value == 0: 85 | net_compare[(n_key, key)] = (1, base_v, value) 86 | else: 87 | net_compare[(n_key, key)] = (value, base_v, value) 88 | baseline.pop(n_key) 89 | 90 | for key, value in net_compare.items(): 91 | print(key, value, file=output_file) 92 | 93 | print("Nets not mapped:", file=output_file) 94 | for key, value in not_found.items(): 95 | print("\t", key, value, file=output_file) 96 | 97 | print("Nets not used:", file=output_file) 98 | for key, value in baseline.items(): 99 | print("\t", key, value, file=output_file) 100 | 101 | 102 | # ============================================================================= 103 | 104 | if __name__ == "__main__": 105 | main() 106 | -------------------------------------------------------------------------------- /fpga_interchange/yaml_support.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ Implements YAML text format support using pyyaml library. """ 12 | from fpga_interchange.converters import AbstractWriter, AbstractReader, \ 13 | to_writer, from_reader 14 | 15 | 16 | class YamlWriter(AbstractWriter): 17 | def __init__(self, struct_reader, parent): 18 | super().__init__(struct_reader, parent) 19 | self.out = {} 20 | self.struct_reader = struct_reader 21 | self.parent = parent 22 | 23 | def dereference_value(self, annotation_type, value, root_writer, 24 | parent_writer): 25 | if annotation_type.type == 'root': 26 | return root_writer.out[annotation_type.field][value] 27 | elif annotation_type.type == 'rootValue': 28 | return root_writer.get_field_value(annotation_type.field, value) 29 | else: 30 | assert annotation_type.type == 'parent' 31 | return self.get_parent( 32 | annotation_type.depth).out[annotation_type.field][value] 33 | 34 | def set_value(self, key, value_which, value): 35 | self.out[key] = value 36 | 37 | def set_value_inner_key(self, key, inner_key, value_which, value): 38 | self.out.update({key: {inner_key: value}}) 39 | 40 | def make_list(self): 41 | return [] 42 | 43 | def append_to_list(self, l, value_which, value): 44 | l.append(value) 45 | 46 | def output(self): 47 | return self.out 48 | 49 | 50 | class YamlIndexCache(): 51 | def __init__(self, data): 52 | self.data = data 53 | self.caches = {} 54 | 55 | def get_index(self, field, value): 56 | if field not in self.caches: 57 | self.caches[field] = {} 58 | for idx, obj in enumerate(self.data[field]): 59 | self.caches[field][id(obj)] = idx 60 | 61 | return self.caches[field][id(value)] 62 | 63 | 64 | class YamlReader(AbstractReader): 65 | def __init__(self, data, parent): 66 | super().__init__(data, parent) 67 | self.data = data 68 | self.index_cache = YamlIndexCache(self.data) 69 | self.parent = parent 70 | 71 | def get_index(self, field, value): 72 | return self.index_cache.get_index(field, value) 73 | 74 | def read_scalar(self, field_which, field_data): 75 | return field_data 76 | 77 | def reference_value(self, annotation_type, value, root_reader, 78 | parent_reader): 79 | if annotation_type.type == 'root': 80 | return root_reader.get_index(annotation_type.field, value) 81 | elif annotation_type.type == 'rootValue': 82 | return root_reader.get_object( 83 | annotation_type.field).get_index(value) 84 | else: 85 | assert annotation_type.type == 'parent' 86 | return self.get_parent(annotation_type.depth).get_index( 87 | annotation_type.field, value) 88 | 89 | def keys(self): 90 | return self.data.keys() 91 | 92 | def get_field_keys(self, key): 93 | return self.data[key].keys() 94 | 95 | def get_inner_field(self, key, inner_key): 96 | return self.data[key][inner_key] 97 | 98 | def get_field(self, key): 99 | return self.data[key] 100 | 101 | 102 | def to_yaml(struct_reader): 103 | """ Converts struct_reader to dict tree suitable for use with pyyaml,dump """ 104 | return to_writer(struct_reader, YamlWriter) 105 | 106 | 107 | def from_yaml(message, data): 108 | """ Converts data from pyyaml.load to FPGA interchange message. """ 109 | from_reader(message, data, YamlReader) 110 | -------------------------------------------------------------------------------- /tests/test_constraints.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | import os 13 | import unittest 14 | from pysat.solvers import Solver 15 | 16 | from fpga_interchange.convert import read_format, get_schema 17 | from fpga_interchange.interchange_capnp import Interchange 18 | from fpga_interchange.device_resources import DeviceResources 19 | from fpga_interchange.patch import patch_capnp 20 | from fpga_interchange.compare import compare_capnp 21 | from fpga_interchange.constraints.tool import make_problem_from_device, \ 22 | create_constraint_cells_from_netlist 23 | from example_netlist import example_physical_netlist, example_logical_netlist 24 | 25 | 26 | class TestConstraintsRoundTrip(unittest.TestCase): 27 | def test_parse_series7_constraints(self): 28 | schema = get_schema(os.environ['INTERCHANGE_SCHEMA_PATH'], 'device', 29 | 'Device.Constraints') 30 | path = os.path.join('test_data', 'series7_constraints.yaml') 31 | with open(path, 'rb') as f: 32 | _ = read_format(schema, 'yaml', f) 33 | 34 | def test_parse_ecp5_constraints(self): 35 | schema = get_schema(os.environ['INTERCHANGE_SCHEMA_PATH'], 'device', 36 | 'Device.Constraints') 37 | path = os.path.join('test_data', 'ecp5_constraints.yaml') 38 | with open(path, 'rb') as f: 39 | _ = read_format(schema, 'yaml', f) 40 | 41 | def test_patch_series7_constraints(self): 42 | phys_netlist = example_physical_netlist() 43 | 44 | interchange = Interchange( 45 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 46 | 47 | with open( 48 | os.path.join(os.environ['DEVICE_RESOURCE_PATH'], 49 | phys_netlist.part + '.device'), 'rb') as f: 50 | dev_message = interchange.read_device_resources_raw(f) 51 | 52 | dev_message = dev_message.as_builder() 53 | 54 | path = os.path.join('test_data', 'series7_constraints.yaml') 55 | with open(path, 'rb') as f: 56 | patch_capnp(dev_message, ['constraints'], 'yaml', f) 57 | 58 | schema = get_schema(os.environ['INTERCHANGE_SCHEMA_PATH'], 'device', 59 | 'Device.Constraints') 60 | with open(path, 'rb') as f: 61 | series7 = read_format(schema, 'yaml', f) 62 | 63 | compare_capnp(self, series7, dev_message.constraints) 64 | 65 | def test_simple_placement(self): 66 | netlist = example_logical_netlist() 67 | cells = create_constraint_cells_from_netlist( 68 | netlist, filtered_out={'GND', 'VCC'}) 69 | 70 | phys_netlist = example_physical_netlist() 71 | 72 | interchange = Interchange( 73 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 74 | 75 | with open( 76 | os.path.join(os.environ['DEVICE_RESOURCE_PATH'], 77 | phys_netlist.part + '.device'), 'rb') as f: 78 | dev_message = interchange.read_device_resources_raw(f) 79 | 80 | dev_message = dev_message.as_builder() 81 | 82 | path = os.path.join('test_data', 'series7_constraints.yaml') 83 | with open(path, 'rb') as f: 84 | patch_capnp(dev_message, ['constraints'], 'yaml', f) 85 | 86 | device = DeviceResources(dev_message) 87 | 88 | allowed_sites = { 89 | 'IOB_X0Y0', 90 | 'IOB_X0Y1', 91 | 'IOB_X0Y2', 92 | 'SLICE_X0Y0', 93 | 'BUFGCTRL_X0Y0', 94 | } 95 | 96 | model, placement_oracle, placements = make_problem_from_device( 97 | device, allowed_sites) 98 | 99 | solver = model.build_sat(placements, cells, placement_oracle) 100 | clauses = solver.prepare_for_sat() 101 | 102 | with Solver() as sat: 103 | for clause in clauses: 104 | sat.add_clause(clause) 105 | 106 | solved = sat.solve() 107 | self.assertTrue(solved) 108 | -------------------------------------------------------------------------------- /fpga_interchange/json_support.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ Implements JSON text format support. """ 12 | from fpga_interchange.converters import AbstractWriter, AbstractReader, \ 13 | to_writer, from_reader 14 | 15 | 16 | class JsonWriter(AbstractWriter): 17 | def __init__(self, struct_reader, parent): 18 | super().__init__(struct_reader, parent) 19 | self.out = {} 20 | self.struct_reader = struct_reader 21 | self.next_id = 0 22 | self.obj_id_cache = {} 23 | 24 | def get_object_with_id(self, field, value): 25 | if field not in self.out.keys(): 26 | return {'_id': None} 27 | item = self.out[field][value] 28 | 29 | if id(item) not in self.obj_id_cache: 30 | self.obj_id_cache[id(item)] = self.next_id 31 | self.next_id += 1 32 | 33 | item_id = self.obj_id_cache[id(item)] 34 | 35 | if '_id' not in item: 36 | item['_id'] = item_id 37 | else: 38 | assert item['_id'] == item_id, ( 39 | item['_id'], 40 | item_id, 41 | ) 42 | 43 | return {'_id': item_id} 44 | 45 | def dereference_value(self, annotation_type, value, root_writer, 46 | parent_writer): 47 | if annotation_type.type == 'root': 48 | return root_writer.get_object_with_id(annotation_type.field, value) 49 | elif annotation_type.type == 'rootValue': 50 | return root_writer.get_field_value(annotation_type.field, value) 51 | else: 52 | assert annotation_type.type == 'parent' 53 | return self.get_parent(annotation_type.depth).get_object_with_id( 54 | annotation_type.field, value) 55 | 56 | def set_value(self, key, value_which, value): 57 | self.out[key] = value 58 | 59 | def set_value_inner_key(self, key, inner_key, value_which, value): 60 | self.out.update({key: {inner_key: value}}) 61 | 62 | def make_list(self): 63 | return [] 64 | 65 | def append_to_list(self, l, value_which, value): 66 | l.append(value) 67 | 68 | def output(self): 69 | return self.out 70 | 71 | 72 | class JsonIndexCache(): 73 | def __init__(self, data): 74 | self.data = data 75 | self.caches = {} 76 | 77 | def get_index(self, field, value): 78 | if field not in self.caches: 79 | self.caches[field] = {} 80 | for idx, obj in enumerate(self.data[field]): 81 | if '_id' in obj: 82 | self.caches[field][obj['_id']] = idx 83 | 84 | return self.caches[field][value['_id']] 85 | 86 | 87 | class JsonReader(AbstractReader): 88 | def __init__(self, data, parent): 89 | super().__init__(data, parent) 90 | self.data = data 91 | self.index_cache = JsonIndexCache(self.data) 92 | self.parent = parent 93 | 94 | def get_index(self, field, value): 95 | return self.index_cache.get_index(field, value) 96 | 97 | def read_scalar(self, field_which, field_data): 98 | return field_data 99 | 100 | def reference_value(self, annotation_type, value, root_reader, 101 | parent_reader): 102 | if annotation_type.type == 'root': 103 | return root_reader.get_index(annotation_type.field, value) 104 | elif annotation_type.type == 'rootValue': 105 | return root_reader.get_object( 106 | annotation_type.field).get_index(value) 107 | else: 108 | assert annotation_type.type == 'parent' 109 | return self.get_parent(annotation_type.depth).get_index( 110 | annotation_type.field, value) 111 | 112 | def keys(self): 113 | return self.data.keys() 114 | 115 | def get_field_keys(self, key): 116 | return self.data[key].keys() 117 | 118 | def get_inner_field(self, key, inner_key): 119 | return self.data[key][inner_key] 120 | 121 | def get_field(self, key): 122 | return self.data[key] 123 | 124 | 125 | def to_json(struct_reader): 126 | """ Converts struct_reader to dict tree suitable for use with json,dump """ 127 | return to_writer(struct_reader, JsonWriter) 128 | 129 | 130 | def from_json(message, data): 131 | """ Converts data from json.load to FPGA interchange message. """ 132 | from_reader(message, data, JsonReader) 133 | -------------------------------------------------------------------------------- /test_data/ecp5_constraints.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | - tag: LSR0.SRMODE 3 | siteTypes: [PLC2] 4 | description: "Is LSR0 a synchronous or asynchronous signal?" 5 | default: SYNC 6 | states: 7 | - state: SYNC 8 | description: "Resets are synchronous" 9 | - state: ASYNC 10 | description: "Resets are asynchronous" 11 | - tag: LSR1.SRMODE 12 | siteTypes: [PLC2] 13 | description: "Is LSR1 a synchronous or asynchronous signal?" 14 | default: SYNC 15 | states: 16 | - state: SYNC 17 | description: "Resets are synchronous" 18 | - state: ASYNC 19 | description: "Resets are asynchronous" 20 | 21 | routedTags: 22 | - routedTag: MUXLSR0.SRMODE 23 | routingBel: MUXLSR0 24 | belPins: 25 | - pin: LSR0 26 | tag: LSR0.SRMODE 27 | - pin: LSR1 28 | tag: LSR1.SRMODE 29 | - routedTag: MUXLSR1.SRMODE 30 | routingBel: MUXLSR1 31 | belPins: 32 | - pin: LSR0 33 | tag: LSR0.SRMODE 34 | - pin: LSR1 35 | tag: LSR1.SRMODE 36 | - routedTag: MUXLSR2.SRMODE 37 | routingBel: MUXLSR2 38 | belPins: 39 | - pin: LSR0 40 | tag: LSR0.SRMODE 41 | - pin: LSR1 42 | tag: LSR1.SRMODE 43 | - routedTag: MUXLSR3.SRMODE 44 | routingBel: MUXLSR3 45 | belPins: 46 | - pin: LSR0 47 | tag: LSR0.SRMODE 48 | - pin: LSR1 49 | tag: LSR1.SRMODE 50 | cellConstraints: 51 | # FF0 52 | - cell: FDRE 53 | locations: 54 | - siteTypes: [PLC2] 55 | bel: { name: FF0 } 56 | requires: 57 | - routedTag: 58 | tag: MUXLSR0.SRMODE 59 | port: R 60 | states: [SYNC] 61 | - cell: FDSE 62 | locations: 63 | - siteTypes: [PLC2] 64 | bel: { name: FF0 } 65 | requires: 66 | - routedTag: 67 | tag: MUXLSR0.SRMODE 68 | port: S 69 | states: [SYNC] 70 | - cell: FDPE 71 | locations: 72 | - siteTypes: [PLC2] 73 | bel: { name: FF0 } 74 | requires: 75 | - routedTag: 76 | tag: MUXLSR0.SRMODE 77 | port: PRE 78 | states: [ASYNC] 79 | - cell: FDCE 80 | locations: 81 | - siteTypes: [PLC2] 82 | bel: { name: FF0 } 83 | requires: 84 | - routedTag: 85 | tag: MUXLSR0.SRMODE 86 | port: CLR 87 | states: [ASYNC] 88 | # FF1 89 | - cell: FDRE 90 | locations: 91 | - siteTypes: [PLC2] 92 | bel: { name: FF1 } 93 | requires: 94 | - routedTag: 95 | tag: MUXLSR1.SRMODE 96 | port: R 97 | states: [SYNC] 98 | - cell: FDSE 99 | locations: 100 | - siteTypes: [PLC2] 101 | bel: { name: FF1 } 102 | requires: 103 | - routedTag: 104 | tag: MUXLSR1.SRMODE 105 | port: S 106 | states: [SYNC] 107 | - cell: FDPE 108 | locations: 109 | - siteTypes: [PLC2] 110 | bel: { name: FF1 } 111 | requires: 112 | - routedTag: 113 | tag: MUXLSR1.SRMODE 114 | port: PRE 115 | states: [ASYNC] 116 | - cell: FDCE 117 | locations: 118 | - siteTypes: [PLC2] 119 | bel: { name: FF1 } 120 | requires: 121 | - routedTag: 122 | tag: MUXLSR1.SRMODE 123 | port: CLR 124 | states: [ASYNC] 125 | # FF2 126 | - cell: FDRE 127 | locations: 128 | - siteTypes: [PLC2] 129 | bel: { name: FF2 } 130 | requires: 131 | - routedTag: 132 | tag: MUXLSR2.SRMODE 133 | port: R 134 | states: [SYNC] 135 | - cell: FDSE 136 | locations: 137 | - siteTypes: [PLC2] 138 | bel: { name: FF2 } 139 | requires: 140 | - routedTag: 141 | tag: MUXLSR2.SRMODE 142 | port: S 143 | states: [SYNC] 144 | - cell: FDPE 145 | locations: 146 | - siteTypes: [PLC2] 147 | bel: { name: FF2 } 148 | requires: 149 | - routedTag: 150 | tag: MUXLSR2.SRMODE 151 | port: PRE 152 | states: [ASYNC] 153 | - cell: FDCE 154 | locations: 155 | - siteTypes: [PLC2] 156 | bel: { name: FF2 } 157 | requires: 158 | - routedTag: 159 | tag: MUXLSR2.SRMODE 160 | port: CLR 161 | states: [ASYNC] 162 | # FF3 163 | - cell: FDRE 164 | locations: 165 | - siteTypes: [PLC2] 166 | bel: { name: FF3 } 167 | requires: 168 | - routedTag: 169 | tag: MUXLSR3.SRMODE 170 | port: R 171 | states: [SYNC] 172 | - cell: FDSE 173 | locations: 174 | - siteTypes: [PLC2] 175 | bel: { name: FF3 } 176 | requires: 177 | - routedTag: 178 | tag: MUXLSR3.SRMODE 179 | port: S 180 | states: [SYNC] 181 | - cell: FDPE 182 | locations: 183 | - siteTypes: [PLC2] 184 | bel: { name: FF3 } 185 | requires: 186 | - routedTag: 187 | tag: MUXLSR3.SRMODE 188 | port: PRE 189 | states: [ASYNC] 190 | - cell: FDCE 191 | locations: 192 | - siteTypes: [PLC2] 193 | bel: { name: FF3 } 194 | requires: 195 | - routedTag: 196 | tag: MUXLSR3.SRMODE 197 | port: CLR 198 | states: [ASYNC] 199 | -------------------------------------------------------------------------------- /test_data/series7_luts.yaml: -------------------------------------------------------------------------------- 1 | lutCells: 2 | - cell: LUT1 3 | inputPins: 4 | - I0 5 | equation: 6 | initParam: INIT 7 | - cell: LUT2 8 | inputPins: 9 | - I0 10 | - I1 11 | equation: 12 | initParam: INIT 13 | - cell: LUT3 14 | inputPins: 15 | - I0 16 | - I1 17 | - I2 18 | equation: 19 | initParam: INIT 20 | - cell: LUT4 21 | inputPins: 22 | - I0 23 | - I1 24 | - I2 25 | - I3 26 | equation: 27 | initParam: INIT 28 | - cell: LUT5 29 | inputPins: 30 | - I0 31 | - I1 32 | - I2 33 | - I3 34 | - I4 35 | equation: 36 | initParam: INIT 37 | - cell: LUT6 38 | inputPins: 39 | - I0 40 | - I1 41 | - I2 42 | - I3 43 | - I4 44 | - I5 45 | equation: 46 | initParam: INIT 47 | lutElements: 48 | - site: SLICEL 49 | luts: 50 | - width: 64 51 | bels: 52 | - name: A6LUT 53 | inputPins: 54 | - A1 55 | - A2 56 | - A3 57 | - A4 58 | - A5 59 | - A6 60 | outputPin: O6 61 | lowBit: 0 62 | highBit: 63 63 | - name: A5LUT 64 | inputPins: 65 | - A1 66 | - A2 67 | - A3 68 | - A4 69 | - A5 70 | outputPin: O5 71 | lowBit: 0 72 | highBit: 31 73 | - width: 64 74 | bels: 75 | - name: B6LUT 76 | inputPins: 77 | - A1 78 | - A2 79 | - A3 80 | - A4 81 | - A5 82 | - A6 83 | outputPin: O6 84 | lowBit: 0 85 | highBit: 63 86 | - name: B5LUT 87 | inputPins: 88 | - A1 89 | - A2 90 | - A3 91 | - A4 92 | - A5 93 | outputPin: O5 94 | lowBit: 0 95 | highBit: 31 96 | - width: 64 97 | bels: 98 | - name: C6LUT 99 | inputPins: 100 | - A1 101 | - A2 102 | - A3 103 | - A4 104 | - A5 105 | - A6 106 | outputPin: O6 107 | lowBit: 0 108 | highBit: 63 109 | - name: C5LUT 110 | inputPins: 111 | - A1 112 | - A2 113 | - A3 114 | - A4 115 | - A5 116 | outputPin: O5 117 | lowBit: 0 118 | highBit: 31 119 | - width: 64 120 | bels: 121 | - name: D6LUT 122 | inputPins: 123 | - A1 124 | - A2 125 | - A3 126 | - A4 127 | - A5 128 | - A6 129 | outputPin: O6 130 | lowBit: 0 131 | highBit: 63 132 | - name: D5LUT 133 | inputPins: 134 | - A1 135 | - A2 136 | - A3 137 | - A4 138 | - A5 139 | outputPin: O5 140 | lowBit: 0 141 | highBit: 31 142 | - site: SLICEM 143 | luts: 144 | - width: 64 145 | bels: 146 | - name: A6LUT 147 | inputPins: 148 | - A1 149 | - A2 150 | - A3 151 | - A4 152 | - A5 153 | - A6 154 | outputPin: O6 155 | lowBit: 0 156 | highBit: 63 157 | - name: A5LUT 158 | inputPins: 159 | - A1 160 | - A2 161 | - A3 162 | - A4 163 | - A5 164 | outputPin: O5 165 | lowBit: 0 166 | highBit: 31 167 | - width: 64 168 | bels: 169 | - name: B6LUT 170 | inputPins: 171 | - A1 172 | - A2 173 | - A3 174 | - A4 175 | - A5 176 | - A6 177 | outputPin: O6 178 | lowBit: 0 179 | highBit: 63 180 | - name: B5LUT 181 | inputPins: 182 | - A1 183 | - A2 184 | - A3 185 | - A4 186 | - A5 187 | outputPin: O5 188 | lowBit: 0 189 | highBit: 31 190 | - width: 64 191 | bels: 192 | - name: C6LUT 193 | inputPins: 194 | - A1 195 | - A2 196 | - A3 197 | - A4 198 | - A5 199 | - A6 200 | outputPin: O6 201 | lowBit: 0 202 | highBit: 63 203 | - name: C5LUT 204 | inputPins: 205 | - A1 206 | - A2 207 | - A3 208 | - A4 209 | - A5 210 | outputPin: O5 211 | lowBit: 0 212 | highBit: 31 213 | - width: 64 214 | bels: 215 | - name: D6LUT 216 | inputPins: 217 | - A1 218 | - A2 219 | - A3 220 | - A4 221 | - A5 222 | - A6 223 | outputPin: O6 224 | lowBit: 0 225 | highBit: 63 226 | - name: D5LUT 227 | inputPins: 228 | - A1 229 | - A2 230 | - A3 231 | - A4 232 | - A5 233 | outputPin: O5 234 | lowBit: 0 235 | highBit: 31 236 | -------------------------------------------------------------------------------- /fpga_interchange/add_prim_lib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2021 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ Utility for adding a primitive library to a device from Yosys JSON 12 | 13 | This takes a Yosys JSON of blackboxes (e.g. that produced by "synth_arch; write_json" 14 | without any design) and patches a device capnp to include those boxes as primLibs. 15 | 16 | Example usage: 17 | 18 | yosys -p "synth_nexus; write_json nexus_boxes.json" 19 | python -mfpga_interchange.add_prim_lib --schema_dir ${SCHEMA_DIR} \ 20 | nexus_unpatched.device nexus_boxes.json nexus_patched.device 21 | 22 | """ 23 | import argparse 24 | import json 25 | 26 | from fpga_interchange.interchange_capnp import Interchange, read_capnp_file, write_capnp_file 27 | from fpga_interchange.logical_netlist import LogicalNetlist, Cell, \ 28 | Direction, Library 29 | 30 | from fpga_interchange.yosys_json import is_bus 31 | 32 | 33 | def main(): 34 | parser = argparse.ArgumentParser(description=__doc__) 35 | parser.add_argument('--schema_dir', required=True) 36 | parser.add_argument('--library', default="primitives") 37 | parser.add_argument('device_in') 38 | parser.add_argument('yosys_json') 39 | parser.add_argument('device_out') 40 | 41 | args = parser.parse_args() 42 | interchange = Interchange(args.schema_dir) 43 | with open(args.device_in, 'rb') as f: 44 | device = read_capnp_file(interchange.device_resources_schema.Device, f) 45 | 46 | device = device.as_builder() 47 | 48 | with open(args.yosys_json) as f: 49 | yosys_json = json.load(f) 50 | 51 | prim_lib = Library(args.library) 52 | 53 | assert 'modules' in yosys_json, yosys_json.keys() 54 | for module_name, module_data in sorted( 55 | yosys_json['modules'].items(), key=lambda x: x[0]): 56 | # Library should only contain blackboxes 57 | assert module_data['attributes'].get('blackbox', 0) or \ 58 | module_data['attributes'].get('whitebox', 0), module_name 59 | property_map = {} 60 | if 'attributes' in module_data: 61 | property_map.update(module_data['attributes']) 62 | if 'parameters' in module_data: 63 | property_map.update(module_data['parameters']) 64 | cell = Cell(module_name, property_map) 65 | 66 | for port_name, port_data in module_data['ports'].items(): 67 | if port_data['direction'] == 'input': 68 | direction = Direction.Input 69 | elif port_data['direction'] == 'output': 70 | direction = Direction.Output 71 | else: 72 | assert port_data['direction'] == 'inout' 73 | direction = Direction.Inout 74 | 75 | property_map = {} 76 | if 'attributes' in port_data: 77 | property_map = port_data['attributes'] 78 | 79 | offset = port_data.get('offset', 0) 80 | upto = port_data.get('upto', False) 81 | 82 | if is_bus(port_name, port_data['bits'], offset, upto): 83 | end = offset 84 | start = offset + len(port_data['bits']) - 1 85 | 86 | if upto: 87 | start, end = end, start 88 | 89 | cell.add_bus_port( 90 | name=port_name, 91 | direction=direction, 92 | start=start, 93 | end=end, 94 | property_map=property_map) 95 | else: 96 | cell.add_port( 97 | name=port_name, 98 | direction=direction, 99 | property_map=property_map) 100 | prim_lib.add_cell(cell) 101 | 102 | libraries = {} 103 | libraries[args.library] = prim_lib 104 | # Create the netlist 105 | netlist = LogicalNetlist( 106 | name=args.library, 107 | property_map={}, 108 | top_instance_name=None, 109 | top_instance=None, 110 | libraries=libraries) 111 | 112 | str_list = [s for s in device.strList] 113 | 114 | netlist_capnp = netlist.convert_to_capnp( 115 | interchange, indexed_strings=str_list) 116 | 117 | # Patch device 118 | device.primLibs = netlist_capnp 119 | 120 | if len(device.strList) != len(str_list): 121 | # At least 1 string was added to the list, update the strList. 122 | device.init('strList', len(str_list)) 123 | 124 | for idx, s in enumerate(str_list): 125 | device.strList[idx] = s 126 | 127 | # Save patched device 128 | with open(args.device_out, 'wb') as f: 129 | write_capnp_file(device, f) 130 | 131 | 132 | if __name__ == "__main__": 133 | main() 134 | -------------------------------------------------------------------------------- /fpga_interchange/prjxray_db_reader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2021 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ 12 | This file defines PRJXRAY database timing reader. 13 | 14 | The prjxray_db_reader is abstraction layer for device_timing_patchin.py 15 | so that later script doesn't have to know every odds and ends 16 | of the database timing models are taken from. 17 | 18 | Extract_data method is used to read and extract data from PRJXRAY 19 | timing database. It returns dictionary compatible with device_timing_patching.py 20 | 21 | So far it only extracts data regarding interconnect, 22 | pip and site port delay/RC models. The ultimate goal is to support 23 | delays inside sites. 24 | """ 25 | 26 | import os 27 | import json 28 | 29 | CAPACITANCE = 1e-9 # convert from prjxray internal values to farads 30 | RESISTANCE = 1e-6 # convert from prjxray internal values to ohms 31 | DELAY = 1e-9 # convert from ns to s 32 | 33 | 34 | class prjxray_db_reader: 35 | def __init__(self, timing_dir): 36 | self.timing_dir = timing_dir 37 | 38 | def extract_data(self): 39 | return_dict = {} 40 | for i, _file in enumerate(os.listdir(self.timing_dir)): 41 | if not os.path.isfile(os.path.join( 42 | self.timing_dir, _file)) or 'tile_type_' not in _file: 43 | continue 44 | with open(os.path.join(self.timing_dir, _file), 'r') as f: 45 | tile_data = json.load(f) 46 | tile_name = tile_data['tile_type'] 47 | tile_dict = {} 48 | tile_dict['wires'] = {} 49 | for name, data in tile_data['wires'].items(): 50 | if data is None: 51 | continue 52 | wire_name = name 53 | tile_dict['wires'][wire_name] = ( 54 | tuple([float(data['res']) * RESISTANCE] * 6), 55 | tuple([float(data['cap']) * CAPACITANCE] * 6)) 56 | 57 | tile_dict['pips'] = {} 58 | for data in tile_data['pips'].values(): 59 | wire0 = data['src_wire'] 60 | wire1 = data['dst_wire'] 61 | key = (wire0, wire1) 62 | model_values = data['src_to_dst'] 63 | input_cap = tuple([None] * 6) 64 | internal_cap = tuple([None] * 6) 65 | if model_values['in_cap'] is not None: 66 | internal_cap = tuple( 67 | [float(model_values['in_cap']) * CAPACITANCE] * 6) 68 | delays = tuple([None] * 6) 69 | if model_values['delay'] is not None: 70 | delays = (float(model_values['delay'][0]) * DELAY, None, 71 | float(model_values['delay'][1]) * DELAY, 72 | float(model_values['delay'][2]) * DELAY, None, 73 | float(model_values['delay'][3]) * DELAY) 74 | output_cap = tuple([None] * 6) 75 | 76 | output_res = tuple([None] * 6) 77 | if model_values['res'] is not None: 78 | output_res = tuple( 79 | [float(model_values['res']) * RESISTANCE] * 6) 80 | 81 | tile_dict['pips'][key] = (input_cap, internal_cap, delays, 82 | output_res, output_cap) 83 | 84 | tile_dict['sites'] = {} 85 | for site in tile_data['sites']: 86 | siteType = site['type'] 87 | tile_dict['sites'][siteType] = {} 88 | for sitePin, dic in site['site_pins'].items(): 89 | values = None 90 | delays = None 91 | if dic is None: 92 | delays = tuple([None] * 6) 93 | values = (None, tuple([None] * 6)) 94 | elif 'res' in dic.keys(): 95 | values = ('r', 96 | tuple([float(dic['res']) * RESISTANCE] * 6)) 97 | delays = (float(dic['delay'][0]) * DELAY, None, 98 | float(dic['delay'][1]) * DELAY, 99 | float(dic['delay'][2]) * DELAY, None, 100 | float(dic['delay'][3]) * DELAY) 101 | else: 102 | values = ('c', 103 | tuple([float(dic['cap']) * CAPACITANCE] * 6)) 104 | delays = (float(dic['delay'][0]) * DELAY, None, 105 | float(dic['delay'][1]) * DELAY, 106 | float(dic['delay'][2]) * DELAY, None, 107 | float(dic['delay'][3]) * DELAY) 108 | tile_dict['sites'][siteType][sitePin] = (values, delays) 109 | return_dict[tile_name] = tile_dict 110 | return return_dict 111 | 112 | 113 | if __name__ == "__main__": 114 | print("This file conatins reader class for prjxray-db-timings") 115 | -------------------------------------------------------------------------------- /fpga_interchange/constraints/tool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | import argparse 13 | import pprint 14 | from pysat.solvers import Solver 15 | import sys 16 | 17 | from fpga_interchange.interchange_capnp import Interchange 18 | from fpga_interchange.constraints.placement_oracle import PlacementOracle 19 | from fpga_interchange.constraints.model import CellInstance, Placement 20 | 21 | 22 | def make_problem_from_device(device, allowed_sites): 23 | """ Generate constraint problem from device database. """ 24 | model = device.get_constraints() 25 | 26 | placement_oracle = PlacementOracle() 27 | placement_oracle.add_sites_from_device(device) 28 | 29 | placements = [] 30 | for tile, site, tile_type, site_type, bel, bel_type in device.yield_bels(): 31 | if site not in allowed_sites: 32 | continue 33 | 34 | placements.append( 35 | Placement( 36 | tile=tile, 37 | site=site, 38 | tile_type=tile_type, 39 | site_type=site_type, 40 | bel=bel)) 41 | 42 | return model, placement_oracle, placements 43 | 44 | 45 | def create_constraint_cells_from_netlist(netlist, filtered_out=set()): 46 | """ Generate cells from logical netlist. """ 47 | cells = [] 48 | for leaf_cell_name, cell_inst in netlist.yield_leaf_cells(): 49 | if cell_inst.cell_name in filtered_out: 50 | continue 51 | 52 | cells.append( 53 | CellInstance( 54 | cell=cell_inst.cell_name, name=leaf_cell_name, ports={})) 55 | 56 | return cells 57 | 58 | 59 | def main(): 60 | parser = argparse.ArgumentParser( 61 | description="Run FPGA constraints placement engine.") 62 | parser.add_argument('--schema_dir', required=True) 63 | parser.add_argument( 64 | '--assumptions', help='Comma seperated list of assumptions to hold') 65 | parser.add_argument('--verbose', action='store_true') 66 | parser.add_argument('--allowed_sites', required=True) 67 | parser.add_argument('--filtered_cells') 68 | parser.add_argument('device') 69 | parser.add_argument('netlist') 70 | 71 | args = parser.parse_args() 72 | 73 | interchange = Interchange(args.schema_dir) 74 | 75 | with open(args.device, 'rb') as f: 76 | device = interchange.read_device_resources(f) 77 | 78 | with open(args.netlist, 'rb') as f: 79 | netlist = interchange.read_logical_netlist(f) 80 | 81 | allowed_sites = set(args.allowed_sites.split(',')) 82 | filtered_cells = set() 83 | if args.filtered_cells is not None: 84 | filtered_cells = set(cell for cell in args.filtered_cells.split(',')) 85 | 86 | model, placement_oracle, placements = make_problem_from_device( 87 | device, allowed_sites) 88 | cells = create_constraint_cells_from_netlist(netlist, filtered_cells) 89 | 90 | solver = model.build_sat(placements, cells, placement_oracle) 91 | 92 | if args.verbose: 93 | print() 94 | print("Preparing solver") 95 | print() 96 | clauses = solver.prepare_for_sat() 97 | 98 | if args.verbose: 99 | print() 100 | print("Variable names ({} total):".format(len(solver.variable_names))) 101 | print() 102 | for variable in solver.variable_names: 103 | print(variable) 104 | 105 | print() 106 | print("Clauses:") 107 | print() 108 | for clause in solver.abstract_clauses: 109 | print(clause) 110 | 111 | assumptions = [] 112 | 113 | if args.assumptions: 114 | for assumption in args.assumptions.split(','): 115 | assumptions.append(solver.get_variable(assumption)) 116 | 117 | with Solver() as sat: 118 | for clause in clauses: 119 | if args.verbose: 120 | print(clause) 121 | sat.add_clause(clause) 122 | 123 | if args.verbose: 124 | print() 125 | print("Running SAT:") 126 | print() 127 | print("Assumptions:") 128 | print(assumptions) 129 | 130 | solved = sat.solve(assumptions=assumptions) 131 | if args.verbose: 132 | print(sat.time()) 133 | 134 | if solved: 135 | model = sat.get_model() 136 | else: 137 | core = sat.get_core() 138 | 139 | if solved: 140 | if args.verbose: 141 | print() 142 | print("Raw Solution:") 143 | print() 144 | print(model) 145 | 146 | print("Solution:") 147 | state_groups_vars, other_vars = solver.decode_solution_model(model) 148 | assert len(other_vars) == 0 149 | 150 | pprint.pprint(state_groups_vars) 151 | else: 152 | print("Unsatifiable!") 153 | if core is not None: 154 | print("Core:") 155 | print(core) 156 | print("Core variables:") 157 | for core_index in core: 158 | print(solver.variable_names[core_index]) 159 | sys.exit(1) 160 | 161 | 162 | if __name__ == "__main__": 163 | main() 164 | -------------------------------------------------------------------------------- /fpga_interchange/annotations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ Defines some utility functions for inspecting capnp annotations. 12 | 13 | get_annotation_value is generic to any capnp file. 14 | 15 | get_first_enum_field_display_name and AnnotationCache are both specific 16 | to the annotations used in the FPGA interchange capnp format. 17 | 18 | """ 19 | from fpga_interchange.capnp_utils import get_module_from_id 20 | 21 | 22 | def get_first_enum_field_display_name(annotation_value, parser=None): 23 | """ Returns the displayName of annotations of a specific structure. 24 | 25 | All annotations used for FPGA interchange capnp file annotations have 26 | an enum as the first field. This functions returns the displayName of the 27 | supplied annotation_value, if and only if the first field of the 28 | annotation_value is an enum. 29 | 30 | The parser argument is optional, as the default parser is the global 31 | parser defined by pycapnp. In the event that the annotation in question 32 | was not parsed by this parser, that parser should be supplied. 33 | This case should be rare. 34 | 35 | """ 36 | 37 | field0_proto = annotation_value.schema.fields_list[0].proto 38 | if field0_proto.which() != 'slot': 39 | return None 40 | 41 | field0_type = field0_proto.slot.type 42 | if field0_type.which() != 'enum': 43 | return None 44 | 45 | e = get_module_from_id(field0_type.enum.typeId, parser) 46 | return e.schema.node.displayName 47 | 48 | 49 | def get_annotation_value(annotation, parser=None): 50 | """ Get annotation value from an annotation. Schema that the annotation belongs too is required. """ 51 | 52 | annotation_module = get_module_from_id(annotation.id, parser) 53 | name = annotation_module.__name__ 54 | 55 | which = annotation.value.which() 56 | if which == 'struct': 57 | annotation_type = annotation_module._nodeSchema.node.annotation.type 58 | 59 | assert annotation_type.which() == 'struct' 60 | struct_type_id = annotation_type.struct.typeId 61 | annotation_struct_type = get_module_from_id(struct_type_id, parser) 62 | return name, annotation.value.struct.as_struct(annotation_struct_type) 63 | elif which == 'void': 64 | return name, None 65 | elif which in [ 66 | 'bool', 'float32', 'float64', 'int16', 'int32', 'int64', 'int8', 67 | 'text', 'uint16', 'uint32', 'uint64', 'uint8' 68 | ]: 69 | return name, getattr(annotation.value, which) 70 | else: 71 | raise NotImplementedError( 72 | 'Annotation of type {} not supported yet.'.format(which)) 73 | 74 | 75 | class AnnotationCache(): 76 | """ Cache for annotation values and display names. 77 | 78 | Because parsing annotation values requires non-trival operations to 79 | inspect, it is useful to provide a simple cache of annotations based on a 80 | structural key. 81 | 82 | In this case the structural key is: 83 | - node_id - The unique id supplied to the capnp struct the annotation 84 | comes from. 85 | - field_idx - The field number that the annotation applies too. 86 | - annotation_idx - The index into the annotations list. 87 | 88 | It is believed that the above 3-tuple uniquely specifies an annotation in 89 | a schema. 90 | 91 | """ 92 | 93 | def __init__(self, parser=None): 94 | self.parser = parser 95 | 96 | self.annotation_values = {} 97 | self.annotation_display_names = {} 98 | 99 | def get_annotation_value(self, node_id, field_idx, annotation_idx, 100 | annotation): 101 | """ Get the annotation value for the specified annotation at the specified key. """ 102 | key = (node_id, field_idx, annotation_idx) 103 | if key not in self.annotation_values: 104 | self.annotation_values[key] = get_annotation_value( 105 | annotation, self.parser) 106 | 107 | return self.annotation_values[key] 108 | 109 | def is_first_field_display_name(self, annotation, expected_display_name): 110 | """ See if the annotation displayName supplied matches the expected_display_name. """ 111 | if annotation.id not in self.annotation_display_names: 112 | _, annotation_value = get_annotation_value(annotation, self.parser) 113 | self.annotation_display_names[ 114 | annotation.id] = get_first_enum_field_display_name( 115 | annotation_value, self.parser) 116 | 117 | display_name = self.annotation_display_names[annotation.id] 118 | if display_name is None: 119 | return False 120 | return display_name == expected_display_name 121 | 122 | def is_reference_annotation(self, annotation): 123 | """ Is the annotation a FPGA interchange text reference annotation? """ 124 | return self.is_first_field_display_name( 125 | annotation, 'References.capnp:ReferenceType') 126 | 127 | def is_implementation_annotation(self, annotation): 128 | """ Is the annotation a FPGA interchange text impl annotation? """ 129 | return self.is_first_field_display_name( 130 | annotation, 'References.capnp:ImplementationType') 131 | -------------------------------------------------------------------------------- /tests/test_basics.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | import os 13 | import unittest 14 | import pprint 15 | import tempfile 16 | 17 | from fpga_interchange.interchange_capnp import Interchange, write_capnp_file, \ 18 | CompressionFormat 19 | from fpga_interchange.logical_netlist import LogicalNetlist 20 | from fpga_interchange.physical_netlist import PhysicalNetlist 21 | from example_netlist import example_logical_netlist, example_physical_netlist 22 | 23 | 24 | class TestRoundTrip(unittest.TestCase): 25 | def test_logical_netlist(self): 26 | logical_netlist = example_logical_netlist() 27 | 28 | interchange = Interchange( 29 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 30 | 31 | with tempfile.NamedTemporaryFile('w+b') as f: 32 | netlist_capnp = logical_netlist.convert_to_capnp(interchange) 33 | write_capnp_file(netlist_capnp, f) 34 | f.seek(0) 35 | 36 | read_logical_netlist = LogicalNetlist.read_from_capnp( 37 | f, interchange) 38 | 39 | self.assertEqual(read_logical_netlist.name, logical_netlist.name) 40 | self.assertEqual(read_logical_netlist.top_instance, 41 | logical_netlist.top_instance) 42 | 43 | self.assertEqual(read_logical_netlist.libraries.keys(), 44 | logical_netlist.libraries.keys()) 45 | for library_name, library in logical_netlist.libraries.items(): 46 | read_library = read_logical_netlist.libraries[library_name] 47 | self.assertEqual(library.cells.keys(), read_library.cells.keys()) 48 | for cell_name, cell in library.cells.items(): 49 | read_cell = read_library.cells[cell_name] 50 | 51 | self.assertEqual(cell.name, read_cell.name) 52 | self.assertEqual(cell.property_map, read_cell.property_map) 53 | self.assertEqual(cell.view, read_cell.view) 54 | self.assertEqual(cell.nets.keys(), read_cell.nets.keys()) 55 | self.assertEqual(cell.ports.keys(), read_cell.ports.keys()) 56 | self.assertEqual(cell.cell_instances.keys(), 57 | read_cell.cell_instances.keys()) 58 | 59 | def test_physical_netlist(self): 60 | phys_netlist = example_physical_netlist() 61 | 62 | interchange = Interchange( 63 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 64 | 65 | with tempfile.NamedTemporaryFile('w+b') as f: 66 | netlist_capnp = phys_netlist.convert_to_capnp(interchange) 67 | write_capnp_file(netlist_capnp, f) 68 | f.seek(0) 69 | read_phys_netlist = PhysicalNetlist.read_from_capnp(f, interchange) 70 | 71 | self.assertEqual( 72 | len(phys_netlist.placements), len(read_phys_netlist.placements)) 73 | 74 | def test_check_routing_tree_and_stitch_segments(self): 75 | phys_netlist = example_physical_netlist() 76 | 77 | interchange = Interchange( 78 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 79 | 80 | with open( 81 | os.path.join(os.environ['DEVICE_RESOURCE_PATH'], 82 | phys_netlist.part + '.device'), 'rb') as f: 83 | device_resources = interchange.read_device_resources(f) 84 | 85 | phys_netlist.check_physical_nets(device_resources) 86 | before_stitch = phys_netlist.get_normalized_tuple_tree( 87 | device_resources) 88 | phys_netlist.stitch_physical_nets(device_resources) 89 | after_stitch = phys_netlist.get_normalized_tuple_tree(device_resources) 90 | phys_netlist.stitch_physical_nets(device_resources, flatten=True) 91 | after_stitch_from_flat = phys_netlist.get_normalized_tuple_tree( 92 | device_resources) 93 | 94 | self.assertEqual(len(before_stitch), len(after_stitch)) 95 | self.assertEqual(len(before_stitch), len(after_stitch_from_flat)) 96 | 97 | bad_nets = set() 98 | for net in before_stitch: 99 | if before_stitch[net] != after_stitch[net]: 100 | bad_nets.add(net) 101 | print(net) 102 | pprint.pprint(before_stitch[net]) 103 | pprint.pprint(after_stitch[net]) 104 | 105 | if before_stitch[net] != after_stitch_from_flat[net]: 106 | bad_nets.add(net) 107 | print(net) 108 | pprint.pprint(before_stitch[net]) 109 | pprint.pprint(after_stitch_from_flat[net]) 110 | 111 | self.assertEqual(set(), bad_nets) 112 | 113 | def test_capnp_modes(self): 114 | logical_netlist = example_logical_netlist() 115 | interchange = Interchange( 116 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 117 | 118 | for compression_format in [ 119 | CompressionFormat.UNCOMPRESSED, CompressionFormat.GZIP 120 | ]: 121 | for packed in [True, False]: 122 | with tempfile.NamedTemporaryFile('w+b') as f: 123 | netlist_capnp = logical_netlist.convert_to_capnp( 124 | interchange) 125 | write_capnp_file( 126 | netlist_capnp, 127 | f, 128 | compression_format=compression_format, 129 | is_packed=packed) 130 | f.seek(0) 131 | _ = LogicalNetlist.read_from_capnp( 132 | f, 133 | interchange, 134 | compression_format=compression_format, 135 | is_packed=packed) 136 | -------------------------------------------------------------------------------- /fpga_interchange/fasm_generators/xc7/xc7_iobs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2021 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ 12 | This file contains all the currently supported IOSTANDARD, slewand DRIVE combinations that 13 | translate into specific features. 14 | 15 | This is a temporary solution before adding all the information necessary to generate a 16 | correct FASM out of a physical netlist directly without using additional data files. 17 | """ 18 | 19 | iob_settings = { 20 | "LVCMOS12_LVCMOS15_LVCMOS18.IN": { 21 | "iostandards": { 22 | 'LVCMOS12': [], 23 | 'LVCMOS15': [], 24 | 'LVCMOS18': [] 25 | }, 26 | "slews": [], 27 | }, 28 | "LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVDS_25_LVTTL_SSTL135_SSTL15_TMDS_33.IN_ONLY": 29 | { 30 | "iostandards": { 31 | 'LVCMOS12': [], 32 | 'LVCMOS15': [], 33 | 'LVCMOS18': [], 34 | 'LVCMOS25': [], 35 | 'LVCMOS33': [], 36 | 'LVTTL': [], 37 | 'SSTL135': [], 38 | 'SSTL15': [], 39 | 'DIFF_SSTL135': [], 40 | 'DIFF_SSTL15': [] 41 | }, 42 | "slews": [], 43 | }, 44 | "LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVTTL.SLEW.FAST": { 45 | "iostandards": { 46 | 'LVCMOS12': [], 47 | 'LVCMOS15': [], 48 | 'LVCMOS18': [], 49 | 'LVCMOS25': [], 50 | 'LVCMOS33': [], 51 | 'LVTTL': [] 52 | }, 53 | "slews": ['FAST'], 54 | }, 55 | "LVCMOS12_LVCMOS15_LVCMOS18_SSTL135_SSTL15.STEPDOWN": { 56 | "iostandards": { 57 | 'LVCMOS12': [], 58 | 'LVCMOS15': [], 59 | 'LVCMOS18': [], 60 | 'SSTL135': [], 61 | 'SSTL15': [], 62 | 'DIFF_SSTL135': [], 63 | 'DIFF_SSTL15': [] 64 | }, 65 | "slews": [], 66 | }, 67 | "LVCMOS25_LVCMOS33_LVTTL.IN": { 68 | "iostandards": { 69 | 'LVCMOS25': [], 70 | 'LVCMOS33': [], 71 | 'LVTTL': [] 72 | }, 73 | "slews": [], 74 | }, 75 | "SSTL135_SSTL15.IN": { 76 | "iostandards": { 77 | 'SSTL135': [], 78 | 'SSTL15': [] 79 | }, 80 | "slews": [], 81 | }, 82 | "LVCMOS12.DRIVE.I12": { 83 | "iostandards": { 84 | 'LVCMOS12': [12] 85 | }, 86 | "slews": [], 87 | }, 88 | "LVCMOS12.DRIVE.I4": { 89 | "iostandards": { 90 | 'LVCMOS12': [4] 91 | }, 92 | "slews": [], 93 | }, 94 | "LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVTTL_SSTL135_SSTL15.SLEW.SLOW": 95 | { 96 | "iostandards": { 97 | 'LVCMOS12': [], 98 | 'LVCMOS15': [], 99 | 'LVCMOS18': [], 100 | 'LVCMOS25': [], 101 | 'LVCMOS33': [], 102 | 'LVTTL': [], 103 | 'SSTL135': [], 104 | 'SSTL15': [], 105 | 'DIFF_SSTL135': [], 106 | 'DIFF_SSTL15': [] 107 | }, 108 | "slews": ['SLOW'], 109 | }, 110 | "LVCMOS12_LVCMOS25.DRIVE.I8": { 111 | "iostandards": { 112 | 'LVCMOS12': [8], 113 | 'LVCMOS25': [8] 114 | }, 115 | "slews": [], 116 | }, 117 | "LVCMOS15.DRIVE.I12": { 118 | "iostandards": { 119 | 'LVCMOS15': [12] 120 | }, 121 | "slews": [], 122 | }, 123 | "LVCMOS15.DRIVE.I8": { 124 | "iostandards": { 125 | 'LVCMOS15': [8] 126 | }, 127 | "slews": [], 128 | }, 129 | "LVCMOS15_LVCMOS18_LVCMOS25.DRIVE.I4": { 130 | "iostandards": { 131 | 'LVCMOS15': [4], 132 | 'LVCMOS18': [4], 133 | 'LVCMOS25': [4] 134 | }, 135 | "slews": [], 136 | }, 137 | "LVCMOS15_SSTL15.DRIVE.I16_I_FIXED": { 138 | "iostandards": { 139 | 'LVCMOS15': [16], 140 | 'SSTL15': [], 141 | 'DIFF_SSTL15': [] 142 | }, 143 | "slews": [], 144 | }, 145 | "LVCMOS18.DRIVE.I12_I8": { 146 | "iostandards": { 147 | 'LVCMOS18': [8, 12] 148 | }, 149 | "slews": [], 150 | }, 151 | "LVCMOS18.DRIVE.I16": { 152 | "iostandards": { 153 | 'LVCMOS18': [16] 154 | }, 155 | "slews": [], 156 | }, 157 | "LVCMOS18.DRIVE.I24": { 158 | "iostandards": { 159 | 'LVCMOS18': [24] 160 | }, 161 | "slews": [], 162 | }, 163 | "LVCMOS25.DRIVE.I12": { 164 | "iostandards": { 165 | 'LVCMOS25': [12] 166 | }, 167 | "slews": [], 168 | }, 169 | "LVCMOS25.DRIVE.I16": { 170 | "iostandards": { 171 | 'LVCMOS25': [16] 172 | }, 173 | "slews": [], 174 | }, 175 | "LVCMOS33.DRIVE.I16": { 176 | "iostandards": { 177 | 'LVCMOS33': [16] 178 | }, 179 | "slews": [], 180 | }, 181 | "LVCMOS33_LVTTL.DRIVE.I12_I16": { 182 | "iostandards": { 183 | 'LVCMOS33': [12], 184 | 'LVTTL': [16] 185 | }, 186 | "slews": [], 187 | }, 188 | "LVCMOS33_LVTTL.DRIVE.I12_I8": { 189 | "iostandards": { 190 | 'LVCMOS33': [8], 191 | 'LVTTL': [8, 12] 192 | }, 193 | "slews": [], 194 | }, 195 | "LVCMOS33_LVTTL.DRIVE.I4": { 196 | "iostandards": { 197 | 'LVCMOS33': [4], 198 | 'LVTTL': [4] 199 | }, 200 | "slews": [], 201 | }, 202 | "LVTTL.DRIVE.I24": { 203 | "iostandards": { 204 | 'LVTTL': [24] 205 | }, 206 | "slews": [], 207 | }, 208 | "SSTL135.DRIVE.I_FIXED": { 209 | "iostandards": { 210 | 'SSTL135': [], 211 | 'DIFF_SSTL135': [] 212 | }, 213 | "slews": [], 214 | }, 215 | "SSTL135_SSTL15.SLEW.FAST": { 216 | "iostandards": { 217 | 'SSTL135': [], 218 | 'SSTL15': [], 219 | 'DIFF_SSTL135': [], 220 | 'DIFF_SSTL15': [] 221 | }, 222 | "slews": ['FAST'], 223 | }, 224 | "LVDS_25_SSTL135_SSTL15.IN_DIFF": { 225 | "iostandards": { 226 | 'DIFF_SSTL135': [], 227 | 'DIFF_SSTL15': [] 228 | }, 229 | "slews": [], 230 | }, 231 | } 232 | -------------------------------------------------------------------------------- /fpga_interchange/compare.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ Implements a way to compare two FPGA interchange capnp messages. """ 12 | from fpga_interchange.annotations import AnnotationCache 13 | from fpga_interchange.field_cache import FieldCache, SCALAR_TYPES 14 | 15 | 16 | class CompareCapnp(): 17 | def __init__(self, unittest, root1, root2): 18 | self.annotation_cache = AnnotationCache() 19 | self.unittest = unittest 20 | self.root1 = root1 21 | self.root2 = root2 22 | self.field_cache = {} 23 | self.value_cache1 = {} 24 | self.value_cache2 = {} 25 | 26 | def get_field_cache(self, schema, schema_node_id): 27 | """ Return the field cache for the specified schema. """ 28 | if schema_node_id not in self.field_cache: 29 | self.field_cache[schema_node_id] = FieldCache( 30 | self.annotation_cache, schema) 31 | return self.field_cache[schema_node_id] 32 | 33 | def dereference_value(self, annotation, value1, value2): 34 | """ Dereference values as needed. 35 | 36 | Only values annotated with a 'rootValue' reference are affected. 37 | Otherwise returns original inputs. 38 | """ 39 | if annotation is None: 40 | return value1, value2 41 | 42 | if annotation.type != 'rootValue': 43 | return value1, value2 44 | 45 | field = annotation.field 46 | if field not in self.value_cache1: 47 | self.value_cache1[field] = [v for v in getattr(self.root1, field)] 48 | self.value_cache2[field] = [v for v in getattr(self.root2, field)] 49 | 50 | value1 = self.value_cache1[field][value1] 51 | value2 = self.value_cache2[field][value2] 52 | 53 | return value1, value2 54 | 55 | def compare_capnp(self, schema_node_id, message1, message2, 56 | field_lists=[]): 57 | """ Compare two capnp structs. 58 | 59 | schema_node_id (int) - Schema node id for both message1 and message2. 60 | message1, message2 - Messages to be compared. 61 | field_lists - List of fields already dereferenced. Useful for 62 | determine where compare failures occurs. 63 | 64 | """ 65 | field_cache = self.get_field_cache(message1.schema, schema_node_id) 66 | 67 | fields1 = field_cache.fields(message1) 68 | fields2 = field_cache.fields(message2) 69 | 70 | self.unittest.assertEqual(fields1, fields2, msg=str(field_lists)) 71 | 72 | orig_field_lists = field_lists 73 | 74 | for field_idx, field in enumerate(field_cache.fields_list): 75 | key = field.key 76 | if key not in fields1: 77 | continue 78 | 79 | field_lists = list(orig_field_lists) 80 | field_lists.append(key) 81 | 82 | which = field.which 83 | if which == 'group': 84 | group1 = getattr(message1, key) 85 | group2 = getattr(message2, key) 86 | 87 | inner_key1 = group1.which() 88 | inner_key2 = group1.which() 89 | self.unittest.assertEqual( 90 | inner_key1, inner_key2, msg=str(field_lists)) 91 | 92 | field_lists.append(inner_key1) 93 | value1 = getattr(group1, inner_key1) 94 | value2 = getattr(group2, inner_key2) 95 | 96 | field_proto_data = field.get_group_proto(inner_key1) 97 | else: 98 | assert which == 'slot', which 99 | value1 = getattr(message1, key) 100 | value2 = getattr(message2, key) 101 | 102 | field_proto_data = field.get_field_proto() 103 | 104 | if field_proto_data.hide_field: 105 | continue 106 | 107 | field_which = field_proto_data.field_which 108 | if field_which == 'struct': 109 | self.compare_capnp( 110 | schema_node_id=field_proto_data.schema_node_id, 111 | message1=value1, 112 | message2=value2, 113 | field_lists=field_lists) 114 | elif field_which == 'list': 115 | list_which = field_proto_data.list_which 116 | 117 | field_lists.append(0) 118 | 119 | if list_which == 'struct': 120 | for idx, (elem1, elem2) in enumerate(zip(value1, value2)): 121 | field_lists[-1] = idx 122 | self.compare_capnp( 123 | schema_node_id=field_proto_data.schema_node_id, 124 | message1=elem1, 125 | message2=elem2, 126 | field_lists=field_lists) 127 | else: 128 | for idx, (elem1, elem2) in enumerate(zip(value1, value2)): 129 | field_lists[-1] = idx 130 | elem1, elem2 = self.dereference_value( 131 | field_proto_data.ref_annotation, elem1, elem2) 132 | self.unittest.assertEqual( 133 | elem1, elem2, msg=str(field_lists)) 134 | elif field_which == 'void': 135 | pass 136 | elif field_which == 'enum': 137 | self.unittest.assertEqual(value1, value2, msg=str(field_lists)) 138 | else: 139 | assert field_which in SCALAR_TYPES, field_which 140 | 141 | value1, value2 = self.dereference_value( 142 | field_proto_data.ref_annotation, value1, value2) 143 | self.unittest.assertEqual(value1, value2, msg=str(field_lists)) 144 | 145 | 146 | def compare_capnp(unittest, message1, message2): 147 | """ Compares two FPGA interchange capnp message. 148 | 149 | unittest (unittest object): unittest self argument. 150 | Used to invoke self.assertEqual, etc. 151 | 152 | message1, message2 (capnp messages): Messages to compare. 153 | 154 | Note that 'rootValue' elements will be compared by value, rather than by 155 | index. This is because 'rootValue' elements are backed by non-stable 156 | value arrays. 157 | """ 158 | schema_node_id = message1.schema.node.id 159 | 160 | unittest.assertEqual(schema_node_id, message2.schema.node.id) 161 | 162 | comp = CompareCapnp(unittest, message1, message2) 163 | comp.compare_capnp(schema_node_id, message1, message2) 164 | -------------------------------------------------------------------------------- /fpga_interchange/fasm_generators/luts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2021 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | from math import log2 12 | 13 | from fpga_interchange.chip_info_utils import LutCell, LutBel, LutElement 14 | 15 | 16 | class LutMapper(): 17 | def __init__(self, device_resources): 18 | """ 19 | Fills luts definition from the device resources database 20 | """ 21 | 22 | self.site_lut_elements = dict() 23 | self.lut_cells = dict() 24 | 25 | for site_lut_element in device_resources.device_resource_capnp.lutDefinitions.lutElements: 26 | site = site_lut_element.site 27 | self.site_lut_elements[site] = list() 28 | for lut in site_lut_element.luts: 29 | lut_element = LutElement() 30 | self.site_lut_elements[site].append(lut_element) 31 | 32 | lut_element.width = lut.width 33 | 34 | for bel in lut.bels: 35 | lut_bel = LutBel() 36 | lut_element.lut_bels.append(lut_bel) 37 | 38 | lut_bel.name = bel.name 39 | for pin in bel.inputPins: 40 | lut_bel.pins.append(pin) 41 | 42 | lut_bel.out_pin = bel.outputPin 43 | 44 | assert bel.lowBit < lut.width 45 | assert bel.highBit < lut.width 46 | 47 | lut_bel.low_bit = bel.lowBit 48 | lut_bel.high_bit = bel.highBit 49 | 50 | for lut_cell in device_resources.device_resource_capnp.lutDefinitions.lutCells: 51 | lut = LutCell() 52 | self.lut_cells[lut_cell.cell] = lut 53 | 54 | lut.name = lut_cell.cell 55 | for pin in lut_cell.inputPins: 56 | lut.pins.append(pin) 57 | 58 | def find_lut_bel(self, site_type, bel): 59 | """ 60 | Returns the LUT Bel definition and the corresponding LUT element given the 61 | corresponding site_type and bel name 62 | """ 63 | assert site_type in self.site_lut_elements, site_type 64 | lut_elements = self.site_lut_elements[site_type] 65 | 66 | for lut_element in lut_elements: 67 | for lut_bel in lut_element.lut_bels: 68 | if lut_bel.name == bel: 69 | return lut_element, lut_bel 70 | 71 | assert False 72 | 73 | def get_phys_lut_init(self, log_init, lut_element, lut_bel, lut_cell, 74 | phys_to_log): 75 | bitstring_init = "{value:0{digits}b}".format( 76 | value=log_init, digits=lut_bel.high_bit + 1) 77 | 78 | # Invert the string to have the LSB at the beginning 79 | logical_lut_init = bitstring_init[::-1] 80 | 81 | physical_lut_init = str() 82 | for phys_init_index in range(0, lut_element.width): 83 | log_init_index = 0 84 | 85 | for phys_port_idx in range(0, int(log2(lut_element.width))): 86 | if not phys_init_index & (1 << phys_port_idx): 87 | continue 88 | 89 | log_port = None 90 | if phys_port_idx < len(lut_bel.pins): 91 | log_port = phys_to_log.get(lut_bel.pins[phys_port_idx]) 92 | 93 | if log_port is None: 94 | continue 95 | 96 | log_port_idx = lut_cell.pins.index(log_port) 97 | log_init_index |= (1 << log_port_idx) 98 | 99 | physical_lut_init += logical_lut_init[log_init_index] 100 | 101 | # Invert the string to have the MSB at the beginning 102 | return physical_lut_init[::-1] 103 | 104 | def get_phys_cell_lut_init(self, logical_init_value, cell_data): 105 | """ 106 | Returns the LUTs physical INIT parameter mapping given the initial logical INIT 107 | value and the cells' data containing the physical mapping of the input pins. 108 | 109 | It is left to the caller to handle cases of fractured LUTs. 110 | """ 111 | 112 | def physical_to_logical_map(lut_bel, bel_pins): 113 | """ 114 | Returns the physical pin to logical pin LUTs mapping. 115 | Unused physical pins are set to None. 116 | """ 117 | phys_to_log = dict() 118 | 119 | for pin in lut_bel.pins: 120 | phys_to_log[pin] = None 121 | 122 | for bel_pin in bel_pins: 123 | if bel_pin.bel_pin == pin: 124 | phys_to_log[pin] = bel_pin.cell_pin 125 | break 126 | 127 | return phys_to_log 128 | 129 | cell_type = cell_data.cell_type 130 | bel = cell_data.bel 131 | bel_pins = cell_data.bel_pins 132 | site_type = cell_data.site_type 133 | 134 | lut_element, lut_bel = self.find_lut_bel(site_type, bel) 135 | phys_to_log = physical_to_logical_map(lut_bel, bel_pins) 136 | lut_cell = self.lut_cells[cell_type] 137 | 138 | return self.get_phys_lut_init(logical_init_value, lut_element, lut_bel, 139 | lut_cell, phys_to_log) 140 | 141 | def get_phys_wire_lut_init(self, 142 | logical_init_value, 143 | site_type, 144 | cell_type, 145 | bel, 146 | bel_pin, 147 | lut_pin=None): 148 | """ 149 | Returns the LUTs physical INIT parameter mapping of a LUT-thru wire 150 | 151 | It is left to the caller to handle cases of fructured LUTs. 152 | """ 153 | 154 | lut_element, lut_bel = self.find_lut_bel(site_type, bel) 155 | lut_cell = self.lut_cells[cell_type] 156 | phys_to_log = dict((pin, None) for pin in lut_bel.pins) 157 | 158 | if lut_pin == None: 159 | assert len(lut_cell.pins) == 1, (lut_cell.name, lut_cell.pins) 160 | phys_to_log[bel_pin] = lut_cell.pins[0] 161 | else: 162 | phys_to_log[bel_pin] = lut_pin 163 | 164 | return self.get_phys_lut_init(logical_init_value, lut_element, lut_bel, 165 | lut_cell, phys_to_log) 166 | 167 | def get_const_lut_init(self, const_init_value, site_type, bel): 168 | """ 169 | Returns the LUTs physical INIT parameter mapping of a wire tied to 170 | the constant net (GND or VCC). 171 | """ 172 | 173 | lut_element, _ = self.find_lut_bel(site_type, bel) 174 | width = lut_element.width 175 | 176 | return "".rjust(width, str(const_init_value)) 177 | -------------------------------------------------------------------------------- /fpga_interchange/field_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ Provides a FieldCache which caches data about a capnp struct field. 12 | 13 | Because capnp structs are constant, and parsing the dynamic capnp data is 14 | a non-trival operation, the FieldCache converts commonly accessed fields into 15 | namedtuple's to avoid overhead. 16 | 17 | """ 18 | from collections import namedtuple 19 | 20 | # List of scalar capnp types. 21 | SCALAR_TYPES = [ 22 | 'bool', 23 | 'int8', 24 | 'int16', 25 | 'int32', 26 | 'int64', 27 | 'uint8', 28 | 'uint16', 29 | 'uint32', 30 | 'uint64', 31 | 'float32', 32 | 'float64', 33 | 'text', 34 | ] 35 | 36 | FieldProtoData = namedtuple( 37 | 'FieldProtoData', 38 | 'field_proto ref_annotation imp_annotation hide_field field_type field_which list_which schema_node_id' 39 | ) 40 | ReferenceAnnotation = namedtuple('ReferenceAnnotation', 'type field depth') 41 | 42 | 43 | def make_reference_annotation(annotation_value): 44 | """ Convert a reference annotation capnp message to a ReferenceAnnotation. 45 | """ 46 | type = annotation_value.type 47 | field = annotation_value.field 48 | depth = None 49 | 50 | if type == 'parent': 51 | depth = annotation_value.depth 52 | 53 | return ReferenceAnnotation(type=type, field=field, depth=depth) 54 | 55 | 56 | def make_field_proto(annotation_cache, schema_node_id, field_idx, field_proto): 57 | """ Convert a field proto message into a FieldProtoData object. """ 58 | field_type = field_proto.slot.type 59 | field_which = field_type.which() 60 | 61 | ref_annotation = None 62 | imp_annotation = None 63 | hide_field = False 64 | for annotation_idx, annotation in enumerate(field_proto.annotations): 65 | _, annotation_value = annotation_cache.get_annotation_value( 66 | schema_node_id, field_idx, annotation_idx, annotation) 67 | if annotation_cache.is_reference_annotation(annotation): 68 | assert ref_annotation is None 69 | ref_annotation = make_reference_annotation(annotation_value) 70 | 71 | if annotation_cache.is_implementation_annotation(annotation): 72 | assert imp_annotation is None 73 | imp_annotation = annotation_value 74 | hide_field = imp_annotation.hide 75 | 76 | list_which = None 77 | schema_node_id = None 78 | if field_which == 'list': 79 | list_which = field_type.list.elementType.which() 80 | if list_which == 'struct': 81 | schema_node_id = field_type.list.elementType.struct.typeId 82 | elif field_which == 'struct': 83 | schema_node_id = field_type.struct.typeId 84 | 85 | return FieldProtoData( 86 | field_proto=field_proto, 87 | ref_annotation=ref_annotation, 88 | imp_annotation=imp_annotation, 89 | hide_field=hide_field, 90 | field_type=field_type, 91 | field_which=field_which, 92 | list_which=list_which, 93 | schema_node_id=schema_node_id) 94 | 95 | 96 | class FieldData(): 97 | """ Object to cache data about a field. 98 | 99 | Note: This cannot be a simple flat object in the event where the field 100 | is a union group. 101 | 102 | """ 103 | 104 | def __init__(self, field_cache, field_index, field): 105 | self.field_cache = field_cache 106 | self.field_index = field_index 107 | self.field = field 108 | field_proto = field.proto 109 | self.key = field_proto.name 110 | self.which = field_proto.which() 111 | 112 | if self.which == 'group': 113 | self.field_proto = None 114 | self.group_protos = {} 115 | else: 116 | assert self.which == 'slot', self.which 117 | self.field_proto = make_field_proto( 118 | annotation_cache=self.field_cache.annotation_cache, 119 | schema_node_id=self.field_cache.schema_node_id, 120 | field_idx=field_index, 121 | field_proto=field_proto, 122 | ) 123 | self.group_protos = None 124 | 125 | def get_field_proto(self): 126 | """ Return field proto data when which == 'slot'. """ 127 | return self.field_proto 128 | 129 | def get_group_proto(self, inner_key): 130 | """ Return group field proto data when which == 'group'. """ 131 | group_proto = self.group_protos.get(inner_key, None) 132 | if group_proto is None: 133 | group_proto = self.field.schema.fields[inner_key].proto 134 | self.group_protos[inner_key] = make_field_proto( 135 | annotation_cache=self.field_cache.annotation_cache, 136 | schema_node_id=self.field_cache.schema_node_id, 137 | field_idx=self.field_index, 138 | field_proto=group_proto, 139 | ) 140 | 141 | return self.group_protos[inner_key] 142 | 143 | 144 | class FieldCache(): 145 | """ Provides field data caching for a specific message schema. """ 146 | 147 | def __init__(self, annotation_cache, schema): 148 | self.annotation_cache = annotation_cache 149 | self.schema = schema 150 | self.schema_node_id = schema.node.id 151 | self.has_union_fields = bool(schema.union_fields) 152 | self.field_data = {} 153 | self.base_fields = set(schema.non_union_fields) 154 | self.union_fields = set(schema.union_fields) 155 | self.fields_list = [] 156 | 157 | for idx, field in enumerate(schema.fields_list): 158 | self.fields_list.append(FieldData(self, idx, field)) 159 | 160 | def fields(self, struct_reader): 161 | """ Return list of fields in specified message reader. """ 162 | if self.has_union_fields: 163 | fields = set(self.base_fields) 164 | fields.add(struct_reader.which()) 165 | return fields 166 | else: 167 | return self.base_fields 168 | 169 | def get_reader_fields(self, input_fields): 170 | """ Return information to build message from list of input_fields. 171 | 172 | 173 | Returns: 174 | fields - List of all fields in output. 175 | defered_fields - Map of fields to defer and their implemenation 176 | annotation. 177 | union_field - Which field (if any) is a union field that needs 178 | special handling. 179 | 180 | """ 181 | fields = set(self.base_fields) 182 | defered_fields = {} 183 | 184 | union_field = None 185 | for field in self.union_fields: 186 | if field in input_fields: 187 | assert union_field is None, (field, union_field) 188 | union_field = field 189 | fields.add(field) 190 | 191 | for field_data in self.fields_list: 192 | key = field_data.key 193 | if key not in fields: 194 | continue 195 | 196 | which = field_data.which 197 | if which != 'slot': 198 | continue 199 | 200 | if field_data.field_proto.imp_annotation is not None: 201 | defered_fields[key] = field_data.field_proto.imp_annotation 202 | 203 | return fields, defered_fields, union_field 204 | -------------------------------------------------------------------------------- /fpga_interchange/convert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ Utilities for converting between file formats using string options. 12 | 13 | This file provides a main function that allows conversions between supported 14 | formats, and selecting subsets of the schemas where possible. 15 | 16 | """ 17 | import argparse 18 | import json 19 | 20 | # Treat Rapidyaml and PyYAML as optional dependencies. 21 | try: 22 | import ryml as _ryml 23 | from fpga_interchange.rapidyaml_support import to_rapidyaml, from_rapidyaml 24 | RAPIDYAML_INSTALLED = True 25 | except ImportError as e: 26 | RAPIDYAML_IMPORT_ERROR = e 27 | RAPIDYAML_INSTALLED = False 28 | 29 | try: 30 | import yaml as _yaml 31 | from yaml import CSafeLoader as _SafeLoader, CDumper as _Dumper 32 | PYYAML_INSTALLED = True 33 | except ImportError as e: 34 | PYYAML_IMPORT_ERROR = e 35 | PYYAML_INSTALLED = False 36 | 37 | from fpga_interchange.interchange_capnp import Interchange, read_capnp_file, write_capnp_file 38 | from fpga_interchange.json_support import to_json, from_json 39 | from fpga_interchange.yaml_support import to_yaml, from_yaml 40 | 41 | SCHEMAS = ('device', 'logical', 'physical') 42 | FORMATS = ('json', 'yaml', 'capnp', 'pyyaml') 43 | 44 | 45 | def get_ryml(): 46 | if RAPIDYAML_INSTALLED: 47 | return _ryml 48 | else: 49 | # TODO: https://github.com/SymbiFlow/python-fpga-interchange/issues/11 50 | raise RuntimeError( 51 | 'Rapidyaml failed import, "yaml" support not available.\n\nImport error:\n{}\n\nInstall with "pip install git+https://github.com/litghost/rapidyaml.git@fixup_python_packaging#egg=rapidyaml&subdirectory=api/python"' 52 | .format(RAPIDYAML_IMPORT_ERROR)) 53 | 54 | 55 | def get_pyyaml(): 56 | if PYYAML_INSTALLED: 57 | return _yaml, _SafeLoader, _Dumper 58 | else: 59 | raise RuntimeError( 60 | 'PyYAML failed import, "pyyaml" support not available.\n\nImport error:\n{}\n\nInstall with "pip install pyyaml"' 61 | .format(PYYAML_IMPORT_ERROR)) 62 | 63 | 64 | def follow_path(schema_root, path): 65 | """ Follow path from schema_root to get a specific schema. """ 66 | schema = schema_root 67 | 68 | for leaf in path: 69 | schema = getattr(schema, leaf) 70 | 71 | return schema 72 | 73 | 74 | def read_format_to_message(message, input_format, in_f): 75 | if input_format == 'json': 76 | json_string = in_f.read().decode('utf-8') 77 | json_data = json.loads(json_string) 78 | from_json(message, json_data) 79 | elif input_format == 'yaml': 80 | ryml = get_ryml() 81 | yaml_string = in_f.read().decode('utf-8') 82 | yaml_tree = ryml.parse(yaml_string) 83 | from_rapidyaml(message, yaml_tree) 84 | elif input_format == 'pyyaml': 85 | yaml, SafeLoader, _ = get_pyyaml() 86 | yaml_string = in_f.read().decode('utf-8') 87 | yaml_data = yaml.load(yaml_string, Loader=SafeLoader) 88 | from_yaml(message, yaml_data) 89 | else: 90 | assert False, 'Invalid input format {}'.format(input_format) 91 | 92 | 93 | def read_format(schema, input_format, in_f): 94 | """ Read serialized format into capnp message of specific schema. 95 | 96 | schema: Capnp schema for input format. 97 | input_format (str): Input format type, either capnp, json, yaml. 98 | in_f (file-like): Binary file that contains serialized data. 99 | 100 | Returns capnp message Builder of specified input format. 101 | 102 | """ 103 | if input_format == 'capnp': 104 | message = read_capnp_file(schema, in_f) 105 | message = message.as_builder() 106 | elif input_format in ['json', 'yaml', 'pyyaml']: 107 | message = schema.new_message() 108 | read_format_to_message(message, input_format, in_f) 109 | else: 110 | assert False, 'Invalid input format {}'.format(input_format) 111 | 112 | return message 113 | 114 | 115 | def write_format(message, output_format, out_f): 116 | """ Write capnp file to a serialized output format. 117 | 118 | message: Capnp Builder object to be serialized into output file. 119 | output_format (str): Input format type, either capnp, json, yaml. 120 | in_f (file-like): Binary file to writer to serialized format. 121 | 122 | """ 123 | if output_format == 'capnp': 124 | write_capnp_file(message, out_f) 125 | elif output_format == 'json': 126 | message = message.as_reader() 127 | json_data = to_json(message) 128 | json_string = json.dumps(json_data, indent=2) 129 | out_f.write(json_string.encode('utf-8')) 130 | elif output_format == 'yaml': 131 | ryml = get_ryml() 132 | 133 | message = message.as_reader() 134 | strings, yaml_tree = to_rapidyaml(message) 135 | yaml_string = ryml.emit(yaml_tree) 136 | out_f.write(yaml_string.encode('utf-8')) 137 | elif output_format == 'pyyaml': 138 | yaml, _, Dumper = get_pyyaml() 139 | 140 | message = message.as_reader() 141 | yaml_data = to_yaml(message) 142 | yaml_string = yaml.dump(yaml_data, sort_keys=False, Dumper=Dumper) 143 | out_f.write(yaml_string.encode('utf-8')) 144 | else: 145 | assert False, 'Invalid output format {}'.format(output_format) 146 | 147 | 148 | def get_schema(schema_dir, schema, schema_path=None): 149 | """ Returns capnp schema based on directory of schemas, schema type. 150 | 151 | schema_dir (str): Path to directory containing schemas. 152 | schema (str): Schema type to return, either device, logical, physical. 153 | schema_path (str): Optional '.' seperated path to locate a schema. 154 | 155 | Returns capnp schema. 156 | 157 | """ 158 | schemas = Interchange(schema_dir) 159 | 160 | schema_map = { 161 | 'device': schemas.device_resources_schema, 162 | 'logical': schemas.logical_netlist_schema, 163 | 'physical': schemas.physical_netlist_schema, 164 | } 165 | 166 | # Make sure schema_map is complete. 167 | for schema_str in SCHEMAS: 168 | assert schema_str in schema_map 169 | 170 | if schema_path is None: 171 | default_path = { 172 | 'device': ['Device'], 173 | 'logical': ['Netlist'], 174 | 'physical': ['PhysNetlist'], 175 | } 176 | path = default_path[schema] 177 | else: 178 | path = schema_path.split('.') 179 | 180 | schema = follow_path(schema_map[schema], path) 181 | 182 | return schema 183 | 184 | 185 | def main(): 186 | parser = argparse.ArgumentParser() 187 | 188 | parser.add_argument('--schema_dir', required=True) 189 | parser.add_argument('--schema', required=True, choices=SCHEMAS) 190 | parser.add_argument('--input_format', required=True, choices=FORMATS) 191 | parser.add_argument('--output_format', required=True, choices=FORMATS) 192 | parser.add_argument('--schema_path') 193 | parser.add_argument('input') 194 | parser.add_argument('output') 195 | 196 | args = parser.parse_args() 197 | 198 | schema = get_schema(args.schema_dir, args.schema, args.schema_path) 199 | 200 | with open(args.input, 'rb') as in_f: 201 | message = read_format(schema, args.input_format, in_f) 202 | 203 | with open(args.output, 'wb') as out_f: 204 | write_format(message, args.output_format, out_f) 205 | 206 | 207 | if __name__ == "__main__": 208 | main() 209 | -------------------------------------------------------------------------------- /test_data/xcup_luts.yaml: -------------------------------------------------------------------------------- 1 | lutCells: 2 | - cell: LUT1 3 | inputPins: 4 | - I0 5 | equation: 6 | initParam: INIT 7 | - cell: LUT2 8 | inputPins: 9 | - I0 10 | - I1 11 | equation: 12 | initParam: INIT 13 | - cell: LUT3 14 | inputPins: 15 | - I0 16 | - I1 17 | - I2 18 | equation: 19 | initParam: INIT 20 | - cell: LUT4 21 | inputPins: 22 | - I0 23 | - I1 24 | - I2 25 | - I3 26 | equation: 27 | initParam: INIT 28 | - cell: LUT5 29 | inputPins: 30 | - I0 31 | - I1 32 | - I2 33 | - I3 34 | - I4 35 | equation: 36 | initParam: INIT 37 | - cell: LUT6 38 | inputPins: 39 | - I0 40 | - I1 41 | - I2 42 | - I3 43 | - I4 44 | - I5 45 | equation: 46 | initParam: INIT 47 | lutElements: 48 | - site: SLICEL 49 | luts: 50 | - width: 64 51 | bels: 52 | - name: A6LUT 53 | inputPins: 54 | - A1 55 | - A2 56 | - A3 57 | - A4 58 | - A5 59 | - A6 60 | outputPin: O6 61 | lowBit: 0 62 | highBit: 63 63 | - name: A5LUT 64 | inputPins: 65 | - A1 66 | - A2 67 | - A3 68 | - A4 69 | - A5 70 | outputPin: O5 71 | lowBit: 0 72 | highBit: 31 73 | - width: 64 74 | bels: 75 | - name: B6LUT 76 | inputPins: 77 | - A1 78 | - A2 79 | - A3 80 | - A4 81 | - A5 82 | - A6 83 | outputPin: O6 84 | lowBit: 0 85 | highBit: 63 86 | - name: B5LUT 87 | inputPins: 88 | - A1 89 | - A2 90 | - A3 91 | - A4 92 | - A5 93 | outputPin: O5 94 | lowBit: 0 95 | highBit: 31 96 | - width: 64 97 | bels: 98 | - name: C6LUT 99 | inputPins: 100 | - A1 101 | - A2 102 | - A3 103 | - A4 104 | - A5 105 | - A6 106 | outputPin: O6 107 | lowBit: 0 108 | highBit: 63 109 | - name: C5LUT 110 | inputPins: 111 | - A1 112 | - A2 113 | - A3 114 | - A4 115 | - A5 116 | outputPin: O5 117 | lowBit: 0 118 | highBit: 31 119 | - width: 64 120 | bels: 121 | - name: D6LUT 122 | inputPins: 123 | - A1 124 | - A2 125 | - A3 126 | - A4 127 | - A5 128 | - A6 129 | outputPin: O6 130 | lowBit: 0 131 | highBit: 63 132 | - name: D5LUT 133 | inputPins: 134 | - A1 135 | - A2 136 | - A3 137 | - A4 138 | - A5 139 | outputPin: O5 140 | lowBit: 0 141 | highBit: 31 142 | - width: 64 143 | bels: 144 | - name: E6LUT 145 | inputPins: 146 | - A1 147 | - A2 148 | - A3 149 | - A4 150 | - A5 151 | - A6 152 | outputPin: O6 153 | lowBit: 0 154 | highBit: 63 155 | - name: E5LUT 156 | inputPins: 157 | - A1 158 | - A2 159 | - A3 160 | - A4 161 | - A5 162 | outputPin: O5 163 | lowBit: 0 164 | highBit: 31 165 | - width: 64 166 | bels: 167 | - name: F6LUT 168 | inputPins: 169 | - A1 170 | - A2 171 | - A3 172 | - A4 173 | - A5 174 | - A6 175 | outputPin: O6 176 | lowBit: 0 177 | highBit: 63 178 | - name: F5LUT 179 | inputPins: 180 | - A1 181 | - A2 182 | - A3 183 | - A4 184 | - A5 185 | outputPin: O5 186 | lowBit: 0 187 | highBit: 31 188 | - width: 64 189 | bels: 190 | - name: G6LUT 191 | inputPins: 192 | - A1 193 | - A2 194 | - A3 195 | - A4 196 | - A5 197 | - A6 198 | outputPin: O6 199 | lowBit: 0 200 | highBit: 63 201 | - name: G5LUT 202 | inputPins: 203 | - A1 204 | - A2 205 | - A3 206 | - A4 207 | - A5 208 | outputPin: O5 209 | lowBit: 0 210 | highBit: 31 211 | - width: 64 212 | bels: 213 | - name: H6LUT 214 | inputPins: 215 | - A1 216 | - A2 217 | - A3 218 | - A4 219 | - A5 220 | - A6 221 | outputPin: O6 222 | lowBit: 0 223 | highBit: 63 224 | - name: H5LUT 225 | inputPins: 226 | - A1 227 | - A2 228 | - A3 229 | - A4 230 | - A5 231 | outputPin: O5 232 | lowBit: 0 233 | highBit: 31 234 | - site: SLICEM 235 | luts: 236 | - width: 64 237 | bels: 238 | - name: A6LUT 239 | inputPins: 240 | - A1 241 | - A2 242 | - A3 243 | - A4 244 | - A5 245 | - A6 246 | outputPin: O6 247 | lowBit: 0 248 | highBit: 63 249 | - name: A5LUT 250 | inputPins: 251 | - A1 252 | - A2 253 | - A3 254 | - A4 255 | - A5 256 | outputPin: O5 257 | lowBit: 0 258 | highBit: 31 259 | - width: 64 260 | bels: 261 | - name: B6LUT 262 | inputPins: 263 | - A1 264 | - A2 265 | - A3 266 | - A4 267 | - A5 268 | - A6 269 | outputPin: O6 270 | lowBit: 0 271 | highBit: 63 272 | - name: B5LUT 273 | inputPins: 274 | - A1 275 | - A2 276 | - A3 277 | - A4 278 | - A5 279 | outputPin: O5 280 | lowBit: 0 281 | highBit: 31 282 | - width: 64 283 | bels: 284 | - name: C6LUT 285 | inputPins: 286 | - A1 287 | - A2 288 | - A3 289 | - A4 290 | - A5 291 | - A6 292 | outputPin: O6 293 | lowBit: 0 294 | highBit: 63 295 | - name: C5LUT 296 | inputPins: 297 | - A1 298 | - A2 299 | - A3 300 | - A4 301 | - A5 302 | outputPin: O5 303 | lowBit: 0 304 | highBit: 31 305 | - width: 64 306 | bels: 307 | - name: D6LUT 308 | inputPins: 309 | - A1 310 | - A2 311 | - A3 312 | - A4 313 | - A5 314 | - A6 315 | outputPin: O6 316 | lowBit: 0 317 | highBit: 63 318 | - name: D5LUT 319 | inputPins: 320 | - A1 321 | - A2 322 | - A3 323 | - A4 324 | - A5 325 | outputPin: O5 326 | lowBit: 0 327 | highBit: 31 328 | - width: 64 329 | bels: 330 | - name: E6LUT 331 | inputPins: 332 | - A1 333 | - A2 334 | - A3 335 | - A4 336 | - A5 337 | - A6 338 | outputPin: O6 339 | lowBit: 0 340 | highBit: 63 341 | - name: E5LUT 342 | inputPins: 343 | - A1 344 | - A2 345 | - A3 346 | - A4 347 | - A5 348 | outputPin: O5 349 | lowBit: 0 350 | highBit: 31 351 | - width: 64 352 | bels: 353 | - name: F6LUT 354 | inputPins: 355 | - A1 356 | - A2 357 | - A3 358 | - A4 359 | - A5 360 | - A6 361 | outputPin: O6 362 | lowBit: 0 363 | highBit: 63 364 | - name: F5LUT 365 | inputPins: 366 | - A1 367 | - A2 368 | - A3 369 | - A4 370 | - A5 371 | outputPin: O5 372 | lowBit: 0 373 | highBit: 31 374 | - width: 64 375 | bels: 376 | - name: G6LUT 377 | inputPins: 378 | - A1 379 | - A2 380 | - A3 381 | - A4 382 | - A5 383 | - A6 384 | outputPin: O6 385 | lowBit: 0 386 | highBit: 63 387 | - name: G5LUT 388 | inputPins: 389 | - A1 390 | - A2 391 | - A3 392 | - A4 393 | - A5 394 | outputPin: O5 395 | lowBit: 0 396 | highBit: 31 397 | - width: 64 398 | bels: 399 | - name: H6LUT 400 | inputPins: 401 | - A1 402 | - A2 403 | - A3 404 | - A4 405 | - A5 406 | - A6 407 | outputPin: O6 408 | lowBit: 0 409 | highBit: 63 410 | - name: H5LUT 411 | inputPins: 412 | - A1 413 | - A2 414 | - A3 415 | - A4 416 | - A5 417 | outputPin: O5 418 | lowBit: 0 419 | highBit: 31 420 | -------------------------------------------------------------------------------- /fpga_interchange/parameter_definitions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | from enum import Enum 12 | import re 13 | 14 | 15 | class ParameterFormat(Enum): 16 | STRING = 0 17 | BOOLEAN = 1 18 | INTEGER = 2 19 | FLOATING_POINT = 3 20 | VERILOG_BINARY = 4 21 | VERILOG_HEX = 5 22 | C_BINARY = 6 23 | C_HEX = 7 24 | 25 | 26 | # 0/1/256 27 | INTEGER_RE = re.compile(r'[0-9]+$') 28 | 29 | # 0.0 30 | FLOATING_POINT_RE = re.compile(r'([0-9]*)\.([0-9]*)$') 31 | 32 | # 1'b0 33 | VERILOG_BINARY_RE = re.compile(r"([1-9][0-9]*)'b([01]+)$") 34 | 35 | # 1'h0 36 | VERILOG_HEX_RE = re.compile(r"([1-9][0-9]*)'h([0-9a-fA-F]+)$") 37 | 38 | # 0b10 39 | C_BINARY_RE = re.compile(r"0b([01]+)$") 40 | 41 | # 0xF 42 | C_HEX_RE = re.compile(r"0x([0-9a-fA-F]+)$") 43 | 44 | 45 | def is_parameter_formatted(format_type, str_value): 46 | """ Is parameter formatted per the format_type specified? 47 | 48 | >>> is_parameter_formatted(ParameterFormat.STRING, "") 49 | True 50 | >>> is_parameter_formatted(ParameterFormat.BOOLEAN, "TRUE") 51 | True 52 | >>> is_parameter_formatted(ParameterFormat.BOOLEAN, "FALSE") 53 | True 54 | >>> is_parameter_formatted(ParameterFormat.BOOLEAN, "1") 55 | True 56 | >>> is_parameter_formatted(ParameterFormat.BOOLEAN, "0") 57 | True 58 | >>> is_parameter_formatted(ParameterFormat.BOOLEAN, "2") 59 | False 60 | >>> is_parameter_formatted(ParameterFormat.INTEGER, "0") 61 | True 62 | >>> is_parameter_formatted(ParameterFormat.INTEGER, "1") 63 | True 64 | >>> is_parameter_formatted(ParameterFormat.INTEGER, "a") 65 | False 66 | >>> is_parameter_formatted(ParameterFormat.INTEGER, "01") 67 | False 68 | >>> is_parameter_formatted(ParameterFormat.FLOATING_POINT, "1.") 69 | True 70 | >>> is_parameter_formatted(ParameterFormat.FLOATING_POINT, "0.") 71 | True 72 | >>> is_parameter_formatted(ParameterFormat.FLOATING_POINT, "1.0") 73 | True 74 | >>> is_parameter_formatted(ParameterFormat.FLOATING_POINT, "0.0") 75 | True 76 | >>> is_parameter_formatted(ParameterFormat.FLOATING_POINT, ".1") 77 | True 78 | >>> is_parameter_formatted(ParameterFormat.FLOATING_POINT, ".0") 79 | True 80 | >>> is_parameter_formatted(ParameterFormat.FLOATING_POINT, ".") 81 | False 82 | >>> is_parameter_formatted(ParameterFormat.VERILOG_BINARY, "1'b1") 83 | True 84 | >>> is_parameter_formatted(ParameterFormat.VERILOG_BINARY, "1'b11") 85 | False 86 | >>> is_parameter_formatted(ParameterFormat.VERILOG_HEX, "4'hF") 87 | True 88 | >>> is_parameter_formatted(ParameterFormat.VERILOG_HEX, "5'h1F") 89 | True 90 | >>> is_parameter_formatted(ParameterFormat.VERILOG_HEX, "4'h1F") 91 | False 92 | >>> is_parameter_formatted(ParameterFormat.C_BINARY, "0b011") 93 | True 94 | >>> is_parameter_formatted(ParameterFormat.C_BINARY, "0b012") 95 | False 96 | >>> is_parameter_formatted(ParameterFormat.C_HEX, "0xF") 97 | True 98 | >>> is_parameter_formatted(ParameterFormat.C_HEX, "F") 99 | False 100 | 101 | """ 102 | if format_type == ParameterFormat.STRING: 103 | # All strings are accepted! 104 | return True 105 | elif format_type == ParameterFormat.BOOLEAN: 106 | return str_value == "TRUE" or str_value == "FALSE" or str_value == "0" or str_value == "1" 107 | elif format_type == ParameterFormat.INTEGER: 108 | m = INTEGER_RE.match(str_value) 109 | if m is None: 110 | return False 111 | 112 | if len(str_value) > 1 and str_value[0] == '0': 113 | # No leading zeros. 114 | return False 115 | 116 | return True 117 | elif format_type == ParameterFormat.FLOATING_POINT: 118 | m = FLOATING_POINT_RE.match(str_value) 119 | if m is None: 120 | return False 121 | 122 | if m.group(1) == '' and m.group(2) == '': 123 | # '.' is not valid, but 1. and .0 are valid. 124 | return False 125 | else: 126 | return True 127 | elif format_type == ParameterFormat.VERILOG_BINARY: 128 | m = VERILOG_BINARY_RE.match(str_value) 129 | if m is None: 130 | return False 131 | 132 | width = int(m.group(1)) 133 | return width >= len(m.group(2)) 134 | elif format_type == ParameterFormat.VERILOG_HEX: 135 | m = VERILOG_HEX_RE.match(str_value) 136 | if m is None: 137 | return False 138 | 139 | width = int(m.group(1)) 140 | 141 | rem = width % 4 142 | if rem != 0: 143 | # Round up to nearest multiple of 4 144 | width += (4 - rem) 145 | 146 | return width >= len(m.group(2)) * 4 147 | elif format_type == ParameterFormat.C_BINARY: 148 | m = C_BINARY_RE.match(str_value) 149 | return m is not None 150 | elif format_type == ParameterFormat.C_HEX: 151 | m = C_HEX_RE.match(str_value) 152 | return m is not None 153 | else: 154 | raise RuntimeError("Unknown format_type {}".format(format_type)) 155 | 156 | 157 | class ParameterDefinition(): 158 | """ Definition for a parameter in the logical netlist. 159 | 160 | name (str) - Name of the parameter 161 | string_format (ParameterFormat) - When expressed as a string, how should 162 | this parameter be formatted? 163 | default_value (str) - What is the default value of this parameter? 164 | 165 | """ 166 | 167 | def __init__(self, name, string_format, default_value): 168 | self.name = name 169 | self.string_format = string_format 170 | self.default_value = default_value 171 | self.width = None 172 | 173 | assert is_parameter_formatted(self.string_format, self.default_value) 174 | 175 | if self.string_format == ParameterFormat.VERILOG_BINARY: 176 | m = VERILOG_BINARY_RE.match(self.default_value) 177 | assert m is not None 178 | self.width = int(m.group(1)) 179 | 180 | if self.string_format == ParameterFormat.VERILOG_HEX: 181 | m = VERILOG_HEX_RE.match(self.default_value) 182 | assert m is not None 183 | self.width = int(m.group(1)) 184 | 185 | def is_integer_like(self): 186 | return self.string_format in [ 187 | ParameterFormat.BOOLEAN, 188 | ParameterFormat.INTEGER, 189 | ParameterFormat.VERILOG_BINARY, 190 | ParameterFormat.VERILOG_HEX, 191 | ParameterFormat.C_BINARY, 192 | ParameterFormat.C_HEX, 193 | ] 194 | 195 | def encode_integer(self, int_value): 196 | if self.string_format == ParameterFormat.BOOLEAN: 197 | if int_value == 0: 198 | return 'FALSE' 199 | elif int_value == 1: 200 | return 'TRUE' 201 | else: 202 | raise ValueError( 203 | 'Invalid boolean int value {}'.format(int_value)) 204 | elif self.string_format == ParameterFormat.INTEGER: 205 | return '{}'.format(int_value) 206 | elif self.string_format == ParameterFormat.VERILOG_BINARY: 207 | assert self.width is not None 208 | assert int_value >= 0, int_value 209 | assert int_value <= (2**self.width), (self.width, int_value) 210 | return "{width}'b{value:0{width}b}".format( 211 | width=self.width, value=int_value) 212 | elif self.string_format == ParameterFormat.VERILOG_HEX: 213 | assert self.width is not None 214 | assert int_value >= 0, int_value 215 | assert int_value <= (2**self.width), (self.width, int_value) 216 | digits = (self.width + 3) // 4 217 | return "{width}'h{value:0{digits}X}".format( 218 | width=self.width, value=int_value, digits=digits) 219 | elif self.string_format == ParameterFormat.C_BINARY: 220 | return '0b{value:0{digits}b}'.format(value=int_value, digits=width) 221 | elif self.string_format == ParameterFormat.C_HEX: 222 | digits = (self.width + 3) // 4 223 | return '0x{value:0{digits}X}'.format( 224 | value=int_value, digits=digits) 225 | 226 | def decode_integer(self, str_value): 227 | assert self.is_integer_like(), (self.name, self.string_format) 228 | 229 | if self.string_format == ParameterFormat.BOOLEAN: 230 | if str_value in ["FALSE", "0", "1'b0"]: 231 | return 0 232 | elif str_value in ["TRUE", "1", "1'b1"]: 233 | return 1 234 | else: 235 | raise ValueError( 236 | 'Invalid boolean string value {}'.format(str_value)) 237 | elif self.string_format == ParameterFormat.INTEGER: 238 | return int(str_value) 239 | elif self.string_format == ParameterFormat.VERILOG_BINARY: 240 | bin_string = str_value.replace("{}'b".format(self.width), "") 241 | return int(bin_string, 2) 242 | elif self.string_format == ParameterFormat.VERILOG_HEX: 243 | hex_string = str_value.replace("{}'h".format(self.width), "") 244 | return int(hex_string, 16) 245 | elif self.string_format == ParameterFormat.C_BINARY: 246 | bin_string = str_value.replace("0b", "") 247 | return int(bin_string, 2) 248 | elif self.string_format == ParameterFormat.C_HEX: 249 | hex_string = str_value.replace("0x", "") 250 | return int(hex_string, 16) 251 | -------------------------------------------------------------------------------- /fpga_interchange/fasm_generators/nexus/nexus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ 12 | This file defines the Nexus devices FASM generator class. 13 | """ 14 | import re 15 | from collections import namedtuple 16 | from enum import Enum 17 | from itertools import product 18 | 19 | from fpga_interchange.fasm_generators.generic import FasmGenerator 20 | 21 | VCC_NET = "GLOBAL_LOGIC1" 22 | GND_NET = "GLOBAL_LOGIC0" 23 | 24 | 25 | class NexusFasmGenerator(FasmGenerator): 26 | def handle_pips(self): 27 | pip_feature_format = "{tile}.PIP.{wire1}.{wire0}" 28 | avail_lut_thrus = list() 29 | for _, _, _, _, bel, bel_type in self.device_resources.yield_bels(): 30 | if bel_type == "OXIDE_COMB": 31 | avail_lut_thrus.append(bel) 32 | 33 | site_thru_pips, lut_thru_pips = self.fill_pip_features( 34 | pip_feature_format, {}, 35 | avail_lut_thrus, 36 | wire_rename=lambda x: x.replace(":", "__")) 37 | self.handle_lut_thru(lut_thru_pips) 38 | 39 | def write_lut(self, tile, bel, init): 40 | bel_tile = "{}__PLC".format(tile) 41 | bel_prefix = bel.replace("_LUT", ".K") 42 | self.add_cell_feature((bel_tile, bel_prefix, 43 | "INIT[15:0] = 16'b{}".format(init))) 44 | 45 | def handle_lut_thru(self, lut_thru_pips): 46 | for (net_name, site, bel), pin in lut_thru_pips.items(): 47 | pin_name = pin["pin_name"] 48 | is_valid = pin["is_valid"] 49 | if not is_valid: 50 | continue 51 | tile_name, _ = self.get_tile_info_at_site(site) 52 | site_type = list( 53 | self.device_resources.site_name_to_site[site].keys())[0] 54 | 55 | if net_name == VCC_NET: 56 | lut_init = self.lut_mapper.get_const_lut_init( 57 | 1, site_type, bel) 58 | elif net_name == GND_NET: 59 | lut_init = self.lut_mapper.get_const_lut_init( 60 | 0, site_type, bel) 61 | else: 62 | lut_init = self.lut_mapper.get_phys_wire_lut_init( 63 | 2, site_type, "LUT4", bel, pin_name, "A") 64 | self.write_lut(tile_name, bel, lut_init) 65 | 66 | def get_logic_tiletypes(self): 67 | """ 68 | This function gets a list of tiletypes that can contain logic 69 | """ 70 | logic_tiletypes = set() 71 | for _, _, tile_type, _, _, bel_type in self.device_resources.yield_bels( 72 | ): 73 | if bel_type == "OXIDE_COMB": 74 | logic_tiletypes.add(tile_type) 75 | return logic_tiletypes 76 | 77 | def handle_slice_routing_bels(self): 78 | tile_types = self.get_logic_tiletypes() 79 | routing_bels = self.get_routing_bels(tile_types) 80 | 81 | for site, bel, pin, _ in routing_bels: 82 | tile = site.replace("_PLC", "__PLC") 83 | dst_wire = bel.replace("RBEL_", "") 84 | feature = "{}.{}".format(dst_wire, pin) 85 | self.add_cell_feature((tile, "PIP", feature)) 86 | 87 | def handle_slice_ff(self): 88 | for cell_instance, cell_data in self.physical_cells_instances.items(): 89 | if not cell_data.cell_type.startswith("FD1P3"): 90 | continue 91 | bel_tile = "{}__PLC".format(cell_data.tile_name) 92 | bel_prefix = cell_data.bel.replace("_FF", ".REG") 93 | 94 | self.add_cell_feature((bel_tile, bel_prefix, "USED.YES")) 95 | regset = "SET" if cell_data.cell_type in ("FD1P3BX", 96 | "FD1P3JX") else "RESET" 97 | self.add_cell_feature((bel_tile, bel_prefix, 98 | "REGSET.{}".format(regset))) 99 | self.add_cell_feature((bel_tile, bel_prefix, "LSRMODE.LSR")) 100 | self.add_cell_feature((bel_tile, bel_prefix, 101 | "SEL.DF")) # TODO: LUT->FF path 102 | 103 | slice_prefix = cell_data.bel.split("_")[0] 104 | srmode = "ASYNC" if cell_data.cell_type in ( 105 | "FD1P3BX", "FD1P3DX") else "LSR_OVER_CE" 106 | self.add_cell_feature((bel_tile, slice_prefix, 107 | "SRMODE.{}".format(srmode))) 108 | # TODO: control set inversion/constants 109 | self.add_cell_feature((bel_tile, slice_prefix, "CLKMUX.CLK")) 110 | self.add_cell_feature((bel_tile, slice_prefix, "LSRMUX.LSR")) 111 | self.add_cell_feature((bel_tile, slice_prefix, "CEMUX.CE")) 112 | 113 | def handle_luts(self): 114 | """ 115 | This function handles LUTs FASM features generation 116 | """ 117 | for cell_instance, cell_data in self.physical_cells_instances.items(): 118 | if cell_data.cell_type != "LUT4": 119 | continue 120 | 121 | init_param = self.device_resources.get_parameter_definition( 122 | cell_data.cell_type, "INIT") 123 | init_value = init_param.decode_integer( 124 | cell_data.attributes["INIT"]) 125 | 126 | phys_lut_init = self.lut_mapper.get_phys_cell_lut_init( 127 | init_value, cell_data) 128 | self.write_lut(cell_data.tile_name, cell_data.bel, phys_lut_init) 129 | 130 | def handle_brams(self): 131 | """ 132 | This function handles BRAMs FASM features generation 133 | """ 134 | 135 | # WID (write IDs) are used to match init data to BRAM instances 136 | curr_wid = 2 137 | 138 | for cell_instance, cell_data in self.physical_cells_instances.items(): 139 | if cell_data.cell_type not in ("PDPSC16K_MODE", "PDP16K_MODE", 140 | "DP16K_MODE"): 141 | continue 142 | # Not yet supported 143 | assert cell_data.cell_type != "DP16K_MODE", cell_instance 144 | site = cell_data.site_name 145 | bel = cell_data.bel 146 | mode = cell_data.cell_type 147 | self.add_cell_feature((site, bel, "MODE.{}".format(mode))) 148 | for param in ("ASYNC_RST_RELEASE", "DATA_WIDTH_W", "DATA_WIDTH_R", 149 | "OUTREG", "RESETMODE"): 150 | if param not in cell_data.attributes: 151 | continue 152 | self.add_cell_feature((site, bel, "{}.{}.{}".format( 153 | mode, param, cell_data.attributes[param]))) 154 | self.add_cell_feature((site, bel, 155 | "{}.CSDECODE_R[2:0] = 3'b000".format(mode))) 156 | self.add_cell_feature((site, bel, 157 | "{}.CSDECODE_W[2:0] = 3'b000".format(mode))) 158 | self.add_cell_feature((site, bel, "DP16K_MODE.CLKAMUX.CLKA")) 159 | self.add_cell_feature((site, bel, "DP16K_MODE.CLKBMUX.CLKB")) 160 | self.add_cell_feature((site, bel, "INIT_DATA.STATIC")) 161 | self.add_cell_feature((site, bel, 162 | "WID[10:0] = 11'b{:011b}".format(curr_wid))) 163 | for i in range(0x40): 164 | param = "INITVAL_{:02X}".format(i) 165 | if param not in cell_data.attributes: 166 | continue 167 | self.add_cell_feature( 168 | ("IP_EBR_WID{}".format(curr_wid), "{}[319:0] = {}".format( 169 | param, cell_data.attributes[param].replace( 170 | "0x", "320'h")))) 171 | curr_wid += 1 172 | 173 | def handle_io(self): 174 | allowed_io_types = { 175 | "OB": [ 176 | "BASE_TYPE.OUTPUT_LVCMOS33", 177 | "TMUX.INV", 178 | ], 179 | "IB": [ 180 | "BASE_TYPE.INPUT_LVCMOS33", 181 | ] 182 | } 183 | for cell_instance, cell_data in self.physical_cells_instances.items(): 184 | if cell_data.cell_type not in allowed_io_types: 185 | continue 186 | for feature in allowed_io_types[cell_data.cell_type]: 187 | self.add_cell_feature((cell_data.site_name, cell_data.bel, 188 | feature)) 189 | 190 | def handle_osc(self): 191 | for cell_instance, cell_data in self.physical_cells_instances.items(): 192 | if cell_data.cell_type != "OSC_CORE": 193 | continue 194 | site = cell_data.site_name 195 | bel = cell_data.bel 196 | self.add_cell_feature((site, bel, "HF_OSC_EN.ENABLED")) 197 | self.add_cell_feature((site, bel, "HFDIV_FABRIC_EN.ENABLED")) 198 | self.add_cell_feature((site, bel, "DEBUG_N.DISABLED")) 199 | self.add_cell_feature( 200 | (site, bel, "HF_CLK_DIV[7:0] = 8'b{:08b}".format( 201 | int(cell_data.attributes.get("HF_CLK_DIV", "1"))))) 202 | 203 | def fill_features(self): 204 | dev_name = self.device_resources.device_resource_capnp.name 205 | self.add_annotation("oxide.device", dev_name) 206 | self.add_annotation("oxide.device_variant", "ES") 207 | self.handle_luts() 208 | self.handle_brams() 209 | self.handle_io() 210 | self.handle_osc() 211 | # Handling PIPs and Route-throughs 212 | self.handle_pips() 213 | self.handle_slice_ff() 214 | self.handle_slice_routing_bels() 215 | -------------------------------------------------------------------------------- /test_data/xcup_device_config.yaml: -------------------------------------------------------------------------------- 1 | # Which BEL names are global buffers for nextpnr? 2 | global_buffer_bels: 3 | - BUFG 4 | - BUFGCTRL 5 | - BUFGCE 6 | - VCC 7 | - GND 8 | # Which cell names are global buffers, and which pins should use dedicated routing resources 9 | global_buffer_cells: 10 | - cell: BUFG 11 | pins: # list of pins that use global resources 12 | - name: I # pin name 13 | guide_placement: true # attempt to place so that this pin can use dedicated resources 14 | max_hops: 10 # max hops of interconnect to search (10 is for test purposes and may need to be refined) 15 | - name: O 16 | force_dedicated_routing: true # the net connected to this pin _must_ use dedicated routing only 17 | - cell: BUFGCTRL 18 | pins: 19 | - name: I0 20 | guide_placement: true 21 | max_hops: 32 22 | - name: I1 23 | guide_placement: true 24 | max_hops: 32 25 | - name: O 26 | force_dedicated_routing: true # dedicated routing reaches only up to the interconnect before BUFCE_LEAF 27 | max_hops: 32 28 | - cell: BUFGCE 29 | pins: 30 | - name: I 31 | guide_placement: true 32 | max_hops: 32 33 | - name: O 34 | force_dedicated_routing: true # dedicated routing reaches only up to the interconnect before BUFCE_LEAF 35 | max_hops: 32 36 | 37 | # How should nextpnr lump BELs during analytic placement? 38 | buckets: 39 | - bucket: FLIP_FLOPS 40 | cells: 41 | - FDRE 42 | - bucket: LUTS 43 | cells: 44 | - LUT1 45 | - bucket: BRAMS 46 | cells: 47 | - RAMB18E2 48 | - RAMB36E2 49 | - FIFO18E2 50 | - FIFO36E2 51 | - bucket: URAMS 52 | cells: 53 | - URAM288_BASE 54 | - bucket: BUFG 55 | cells: 56 | - BUFGCE 57 | - BUFGCTRL 58 | - bucket: IBUFs 59 | cells: 60 | - INBUF 61 | - DIFFINBUF 62 | - bucket: IBUFCTRLs 63 | cells: 64 | - IBUFCTRL 65 | - bucket: OBUFs 66 | cells: 67 | - OBUF 68 | - OBUFTDS 69 | - bucket: OBUF_GTs 70 | cells: 71 | - OBUFDS_GTE4_ADV 72 | - bucket: MMCM 73 | cells: 74 | - MMCME4_ADV 75 | - bucket: PLL 76 | cells: 77 | - PLLE4_ADV 78 | - bucket: PULLs 79 | cells: 80 | - PULLDOWN 81 | - bucket: CARRY 82 | cells: 83 | - CARRY8 84 | - bucket: IDELAYCTRL 85 | cells: 86 | - IDELAYCTRL 87 | - bucket: ISERDES 88 | cells: 89 | - ISERDESE3 90 | # don't route through the following cells 91 | # FIXME: It seems that antenna nets can be produced when 92 | # using pseudo PIPs through LUTs. For now disable them 93 | disabled_routethroughs: 94 | - BUFGCTRL 95 | - OUTINV 96 | - A6LUT 97 | - B6LUT 98 | - C6LUT 99 | - D6LUT 100 | - E6LUT 101 | - F6LUT 102 | - G6LUT 103 | - H6LUT 104 | - A5LUT 105 | - B5LUT 106 | - C5LUT 107 | - D5LUT 108 | - E5LUT 109 | - F5LUT 110 | - G5LUT 111 | - H5LUT 112 | # Do not allow cells to be placed at BELs 113 | disabled_cell_bel_map: 114 | - cell: FDRE 115 | bels: 116 | - TFF 117 | - IPFF 118 | - OPFF 119 | - OUT_FF 120 | - IN_FF 121 | - cell: FDCE 122 | bels: 123 | - TFF 124 | - IPFF 125 | - OPFF 126 | - OUT_FF 127 | - IN_FF 128 | - cell: FDPE 129 | bels: 130 | - TFF 131 | - IPFF 132 | - OPFF 133 | - OUT_FF 134 | - IN_FF 135 | disabled_site_pips: 136 | - bels: 137 | - A6LUT 138 | - B6LUT 139 | - C6LUT 140 | - D6LUT 141 | - E6LUT 142 | - F6LUT 143 | - G6LUT 144 | - H6LUT 145 | ipin: A6 146 | opin: O6 147 | 148 | clusters: 149 | - name: LUTFF 150 | root_cell_types: 151 | - FDCE 152 | - FDPE 153 | - FDRE 154 | - FDSE 155 | cluster_cells: 156 | - cells: 157 | - LUT1 158 | - LUT2 159 | - LUT3 160 | - LUT4 161 | - LUT5 162 | - LUT6 163 | ports: 164 | - D 165 | 166 | macro_clusters: 167 | - name: RAM32X1S 168 | sites: 169 | - site: SLICEM 170 | cells: 171 | - { cell: SP, bels: [H6LUT] } 172 | - name: RAM32X1D 173 | sites: 174 | - site: SLICEM 175 | cells: 176 | - { cell: SP, bels: [H6LUT] } 177 | - { cell: DP, bels: [G6LUT] } 178 | - name: RAM32X16DR8 179 | sites: 180 | - site: SLICEM 181 | cells: 182 | - { cell: RAMH_D1, bels: [H6LUT] } 183 | - { cell: RAMG_D1, bels: [G6LUT] } 184 | - { cell: RAMF_D1, bels: [F6LUT] } 185 | - { cell: RAME_D1, bels: [E6LUT] } 186 | - { cell: RAMD_D1, bels: [D6LUT] } 187 | - { cell: RAMC_D1, bels: [C6LUT] } 188 | - { cell: RAMB_D1, bels: [B6LUT] } 189 | - { cell: RAMA_D1, bels: [A6LUT] } 190 | - { cell: RAMH, bels: [H5LUT] } 191 | - { cell: RAMG, bels: [G5LUT] } 192 | - { cell: RAMF, bels: [F5LUT] } 193 | - { cell: RAME, bels: [E5LUT] } 194 | - { cell: RAMD, bels: [D5LUT] } 195 | - { cell: RAMC, bels: [C5LUT] } 196 | - { cell: RAMB, bels: [B5LUT] } 197 | - { cell: RAMA, bels: [A5LUT] } 198 | - name: RAM32M 199 | sites: 200 | - site: SLICEM 201 | cells: 202 | - { cell: RAMD_D1, bels: [H6LUT] } 203 | - { cell: RAMC_D1, bels: [G6LUT] } 204 | - { cell: RAMB_D1, bels: [F6LUT] } 205 | - { cell: RAMA_D1, bels: [E6LUT] } 206 | - { cell: RAMD, bels: [H5LUT] } 207 | - { cell: RAMC, bels: [G5LUT] } 208 | - { cell: RAMB, bels: [F5LUT] } 209 | - { cell: RAMA, bels: [E5LUT] } 210 | - name: RAM32M16 211 | sites: 212 | - site: SLICEM 213 | cells: 214 | - { cell: RAMH_D1, bels: [H6LUT] } 215 | - { cell: RAMG_D1, bels: [G6LUT] } 216 | - { cell: RAMF_D1, bels: [F6LUT] } 217 | - { cell: RAME_D1, bels: [E6LUT] } 218 | - { cell: RAMD_D1, bels: [D6LUT] } 219 | - { cell: RAMC_D1, bels: [C6LUT] } 220 | - { cell: RAMB_D1, bels: [B6LUT] } 221 | - { cell: RAMA_D1, bels: [A6LUT] } 222 | - { cell: RAMH, bels: [H5LUT] } 223 | - { cell: RAMG, bels: [G5LUT] } 224 | - { cell: RAMF, bels: [F5LUT] } 225 | - { cell: RAME, bels: [E5LUT] } 226 | - { cell: RAMD, bels: [D5LUT] } 227 | - { cell: RAMC, bels: [C5LUT] } 228 | - { cell: RAMB, bels: [B5LUT] } 229 | - { cell: RAMA, bels: [A5LUT] } 230 | - name: RAM64X1S 231 | sites: 232 | - site: SLICEM 233 | cells: 234 | - { cell: SP, bels: [H6LUT] } 235 | - name: RAM64X2S 236 | sites: 237 | - site: SLICEM 238 | cells: 239 | - { cell: RAM64X1S0/SP, bels: [H6LUT] } 240 | - { cell: RAM64X1S1/SP, bels: [G6LUT] } 241 | - name: RAM64X1D 242 | sites: 243 | - site: SLICEM 244 | cells: 245 | - { cell: SP, bels: [H6LUT] } 246 | - { cell: DP, bels: [G6LUT] } 247 | - name: RAM64M 248 | sites: 249 | - site: SLICEM 250 | cells: 251 | - { cell: RAMD, bels: [H6LUT] } 252 | - { cell: RAMC, bels: [G6LUT] } 253 | - { cell: RAMB, bels: [F6LUT] } 254 | - { cell: RAMA, bels: [E6LUT] } 255 | - name: RAM64M8 256 | sites: 257 | - site: SLICEM 258 | cells: 259 | - { cell: RAMH, bels: [H6LUT] } 260 | - { cell: RAMG, bels: [G6LUT] } 261 | - { cell: RAMF, bels: [F6LUT] } 262 | - { cell: RAME, bels: [E6LUT] } 263 | - { cell: RAMD, bels: [D6LUT] } 264 | - { cell: RAMC, bels: [C6LUT] } 265 | - { cell: RAMB, bels: [B6LUT] } 266 | - { cell: RAMA, bels: [A6LUT] } 267 | - name: RAM64X8SW 268 | sites: 269 | - site: SLICEM 270 | cells: 271 | - { cell: RAMS64E1_H, bels: [H6LUT] } 272 | - { cell: RAMS64E1_G, bels: [G6LUT] } 273 | - { cell: RAMS64E1_F, bels: [F6LUT] } 274 | - { cell: RAMS64E1_E, bels: [E6LUT] } 275 | - { cell: RAMS64E1_D, bels: [D6LUT] } 276 | - { cell: RAMS64E1_C, bels: [C6LUT] } 277 | - { cell: RAMS64E1_B, bels: [B6LUT] } 278 | - { cell: RAMS64E1_A, bels: [A6LUT] } 279 | - name: RAM128X1S 280 | sites: 281 | - site: SLICEM 282 | cells: 283 | - { cell: LOW, bels: [H6LUT] } 284 | - { cell: HIGH, bels: [G6LUT] } 285 | - { cell: F7, bels: [F7MUX_GH] } 286 | - name: RAM128X1D 287 | sites: 288 | - site: SLICEM 289 | cells: 290 | - { cell: SP.LOW, bels: [H6LUT] } 291 | - { cell: SP.HIGH, bels: [G6LUT] } 292 | - { cell: DP.LOW, bels: [F6LUT] } 293 | - { cell: DP.HIGH, bels: [E6LUT] } 294 | - { cell: F7.SP, bels: [F7MUX_GH] } 295 | - { cell: F7.DP, bels: [F7MUX_EF] } 296 | - name: RAM256X1S 297 | sites: 298 | - site: SLICEM 299 | cells: 300 | - { cell: RAMS64E_D, bels: [H6LUT] } 301 | - { cell: RAMS64E_C, bels: [G6LUT] } 302 | - { cell: RAMS64E_B, bels: [F6LUT] } 303 | - { cell: RAMS64E_A, bels: [E6LUT] } 304 | - { cell: F7.A, bels: [F7MUX_EF] } 305 | - { cell: F7.B, bels: [F7MUX_GH] } 306 | - { cell: F8, bels: [F8MUX_TOP] } 307 | - name: RAM256X1D 308 | sites: 309 | - site: SLICEM 310 | cells: 311 | - { cell: SP.A, bels: [H6LUT] } 312 | - { cell: SP.B, bels: [G6LUT] } 313 | - { cell: SP.C, bels: [F6LUT] } 314 | - { cell: SP.D, bels: [E6LUT] } 315 | - { cell: DP.A, bels: [D6LUT] } 316 | - { cell: DP.B, bels: [C6LUT] } 317 | - { cell: DP.C, bels: [B6LUT] } 318 | - { cell: DP.D, bels: [A6LUT] } 319 | - { cell: F7.SPA, bels: [F7MUX_GH] } 320 | - { cell: F7.SPB, bels: [F7MUX_EF] } 321 | - { cell: F7.DPA, bels: [F7MUX_CD] } 322 | - { cell: F7.DPB, bels: [F7MUX_AB] } 323 | - { cell: F8.SP, bels: [F8MUX_TOP] } 324 | - { cell: F8.DP, bels: [F8MUX_BOT] } 325 | - name: RAM512X1S 326 | sites: 327 | - site: SLICEM 328 | cells: 329 | - { cell: RAMS64E1_H, bels: [H6LUT] } 330 | - { cell: RAMS64E1_G, bels: [G6LUT] } 331 | - { cell: RAMS64E1_F, bels: [F6LUT] } 332 | - { cell: RAMS64E1_E, bels: [E6LUT] } 333 | - { cell: RAMS64E1_D, bels: [D6LUT] } 334 | - { cell: RAMS64E1_C, bels: [C6LUT] } 335 | - { cell: RAMS64E1_B, bels: [B6LUT] } 336 | - { cell: RAMS64E1_A, bels: [A6LUT] } 337 | - { cell: F7.A, bels: [F7MUX_AB] } 338 | - { cell: F7.B, bels: [F7MUX_CD] } 339 | - { cell: F7.C, bels: [F7MUX_EF] } 340 | - { cell: F7.D, bels: [F7MUX_GH] } 341 | - { cell: F8.A, bels: [F8MUX_BOT] } 342 | - { cell: F8.B, bels: [F8MUX_TOP] } 343 | - { cell: F9, bels: [F9MUX] } 344 | -------------------------------------------------------------------------------- /test_data/series7_constraints.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | - tag: BRAM_MODE 3 | tileTypes: 4 | - BRAM_L 5 | - BRAM_R 6 | description: "Is RAMB36 in use?" 7 | default: 2xRAMB18 8 | states: 9 | - state: 2xRAMB18 10 | description: "BRAM tile is fractured into two RAMB18 sites." 11 | - state: RAMB36 12 | description: "BRAM tile is fused into one RAMB36 site." 13 | - tag: RESET_TYPE 14 | description: "Are the resets synchronous or asynchronous?" 15 | default: ASYNC 16 | siteTypes: 17 | - SLICEL 18 | - SLICEM 19 | states: 20 | - state: SYNC 21 | description: "Resets are synchronous" 22 | - state: ASYNC 23 | description: "Resets are asynchronous" 24 | - tag: FF_MODE 25 | description: "Are the FF BELs latches or FFs?" 26 | default: FF 27 | siteTypes: 28 | - SLICEL 29 | - SLICEM 30 | states: 31 | - state: FF 32 | description: "FF BELs are FF" 33 | - state: LATCH 34 | description: "FF BELs are latches" 35 | - tag: DLUT_STATE 36 | siteTypes: [SLICEM] 37 | description: "State of the D[56]LUT BEL" 38 | default: LUT 39 | states: 40 | - state: LUT 41 | description: "The D5LUT/D6LUT BEL is operating as a LUT" 42 | - state: RAM32 43 | description: "RAM32 type BELs can be placed at D5LUT and D6LUT" 44 | - state: RAM64 45 | description: "RAM64 type BELs can be placed at D6LUT" 46 | - state: SRL16 47 | description: "SRL16 type BELs can be placed at D5LUT and D6LUT" 48 | - state: SRL32 49 | description: "SRL32 type BELs can be placed at D6LUT" 50 | - tag: CLUT_STATE 51 | siteTypes: [SLICEM] 52 | description: "State of the C[56]LUT BEL" 53 | default: LUT 54 | states: 55 | - state: LUT 56 | description: "The C5LUT/C6LUT BEL is operating as a LUT" 57 | - state: RAM32 58 | description: "RAM32 type BELs can be placed at C5LUT and C6LUT" 59 | - state: RAM64 60 | description: "RAM64 type BELs can be placed at C6LUT" 61 | - state: SRL16 62 | description: "SRL16 type BELs can be placed at C5LUT and C6LUT" 63 | - state: SRL32 64 | description: "SRL32 type BELs can be placed at C6LUT" 65 | - tag: BLUT_STATE 66 | siteTypes: [SLICEM] 67 | description: "State of the B[56]LUT BEL" 68 | default: LUT 69 | states: 70 | - state: LUT 71 | description: "The B5LUT/B6LUT BEL is operating as a LUT" 72 | - state: RAM32 73 | description: "RAM32 type BELs can be placed at B5LUT and B6LUT" 74 | - state: RAM64 75 | description: "RAM64 type BELs can be placed at B6LUT" 76 | - state: SRL16 77 | description: "SRL16 type BELs can be placed at B5LUT and B6LUT" 78 | - state: SRL32 79 | description: "SRL32 type BELs can be placed at B6LUT" 80 | - tag: ALUT_STATE 81 | siteTypes: [SLICEM] 82 | description: "State of the A[56]LUT BEL" 83 | default: LUT 84 | states: 85 | - state: LUT 86 | description: "The A5LUT/A6LUT BEL is operating as a LUT" 87 | - state: RAM32 88 | description: "RAM32 type BELs can be placed at A5LUT and A6LUT" 89 | - state: RAM64 90 | description: "RAM64 type BELs can be placed at A6LUT" 91 | - state: SRL16 92 | description: "SRL16 type BELs can be placed at A5LUT and A6LUT" 93 | - state: SRL32 94 | description: "SRL32 type BELs can be placed at A6LUT" 95 | routedTags: [] 96 | cellConstraints: 97 | - cell: RAMB18E1 98 | locations: 99 | - siteTypes: 100 | - RAMB18E1 101 | bel: 102 | name: RAMB18E1 103 | implies: 104 | - tag: BRAM_MODE 105 | state: 2xRAMB18 106 | - cell: FIFO18E1 107 | locations: 108 | - siteTypes: 109 | - FIFO18E1 110 | bel: 111 | name: FIFO18E1 112 | implies: 113 | - tag: BRAM_MODE 114 | state: 2xRAMB18 115 | - cell: RAMB36E1 116 | locations: 117 | - siteTypes: 118 | - RAMB36E1 119 | bel: 120 | name: RAMB36E1 121 | implies: 122 | - tag: BRAM_MODE 123 | state: RAMB36 124 | - cell: FIFO36E1 125 | locations: 126 | - siteTypes: 127 | - FIFO36E1 128 | bel: 129 | name: FIFO36E1 130 | implies: 131 | - tag: BRAM_MODE 132 | state: RAMB36 133 | - cells: 134 | - FDRE 135 | - FDSE 136 | locations: 137 | - siteTypes: 138 | - SLICEL 139 | - SLICEM 140 | bel: { anyBel: null } 141 | implies: 142 | - { tag: RESET_TYPE, state: SYNC } 143 | - { tag: FF_MODE, state: FF } 144 | - cells: 145 | - FDPE 146 | - FDCE 147 | locations: 148 | - siteTypes: 149 | - SLICEL 150 | - SLICEM 151 | bel: { anyBel: null } 152 | implies: 153 | - { tag: RESET_TYPE, state: ASYNC } 154 | - { tag: FF_MODE, state: FF } 155 | - cells: 156 | - LDPE 157 | - LDCE 158 | locations: 159 | - siteTypes: 160 | - SLICEL 161 | - SLICEM 162 | bel: { anyBel: null } 163 | implies: 164 | - { tag: FF_MODE, state: LATCH } 165 | - cells: 166 | - RAMD32 167 | - RAMD64E 168 | - RAMS32 169 | - RAMS64E 170 | locations: 171 | - siteTypes: [SLICEM] 172 | bel: 173 | bels: 174 | - A5LUT 175 | - A6LUT 176 | - B5LUT 177 | - B6LUT 178 | - C5LUT 179 | - C6LUT 180 | requires: 181 | - tag: DLUT_STATE 182 | states: 183 | - RAM32 184 | - RAM64 185 | - cells: 186 | - RAMD32 187 | - RAMS32 188 | locations: 189 | - siteTypes: [SLICEM] 190 | bel: 191 | bels: 192 | - D5LUT 193 | - D6LUT 194 | implies: 195 | - { tag: DLUT_STATE, state: RAM32 } 196 | - cells: 197 | - RAMD64E 198 | - RAMS64E 199 | locations: 200 | - siteTypes: [SLICEM] 201 | bel: {name: D6LUT} 202 | implies: 203 | - { tag: DLUT_STATE, state: RAM64 } 204 | - cells: 205 | - SRL16E 206 | - SRLC16E 207 | locations: 208 | - siteTypes: [SLICEM] 209 | bel: 210 | bels: 211 | - D5LUT 212 | - D6LUT 213 | implies: 214 | - { tag: DLUT_STATE, state: SRL16 } 215 | - cells: 216 | - SRLC32E 217 | locations: 218 | - siteTypes: [SLICEM] 219 | bel: {name: D6LUT} 220 | implies: 221 | - { tag: DLUT_STATE, state: SRL32 } 222 | - cells: 223 | - LUT1 224 | - LUT2 225 | - LUT3 226 | - LUT4 227 | - LUT5 228 | - LUT6 229 | - INV 230 | - BUF 231 | locations: 232 | - siteTypes: [SLICEM] 233 | bel: 234 | bels: 235 | - D5LUT 236 | - D6LUT 237 | implies: 238 | - { tag: DLUT_STATE, state: LUT } 239 | - cells: 240 | - RAMD32 241 | - RAMS32 242 | locations: 243 | - siteTypes: [SLICEM] 244 | bel: 245 | bels: 246 | - C5LUT 247 | - C6LUT 248 | implies: 249 | - { tag: CLUT_STATE, state: RAM32 } 250 | - cells: 251 | - RAMD64E 252 | - RAMS64E 253 | locations: 254 | - siteTypes: [SLICEM] 255 | bel: {name: C6LUT} 256 | implies: 257 | - { tag: CLUT_STATE, state: RAM64 } 258 | - cells: 259 | - SRL16E 260 | - SRLC16E 261 | locations: 262 | - siteTypes: [SLICEM] 263 | bel: 264 | bels: 265 | - C5LUT 266 | - C6LUT 267 | implies: 268 | - { tag: CLUT_STATE, state: SRL16 } 269 | - cells: 270 | - SRLC32E 271 | locations: 272 | - siteTypes: [SLICEM] 273 | bel: {name: C6LUT} 274 | implies: 275 | - { tag: CLUT_STATE, state: SRL32 } 276 | - cells: 277 | - LUT1 278 | - LUT2 279 | - LUT3 280 | - LUT4 281 | - LUT5 282 | - LUT6 283 | - INV 284 | - BUF 285 | locations: 286 | - siteTypes: [SLICEM] 287 | bel: 288 | bels: 289 | - C5LUT 290 | - C6LUT 291 | implies: 292 | - { tag: CLUT_STATE, state: LUT } 293 | - cells: 294 | - RAMD32 295 | - RAMS32 296 | locations: 297 | - siteTypes: [SLICEM] 298 | bel: 299 | bels: 300 | - B5LUT 301 | - B6LUT 302 | implies: 303 | - { tag: BLUT_STATE, state: RAM32 } 304 | - cells: 305 | - RAMD64E 306 | - RAMS64E 307 | locations: 308 | - siteTypes: [SLICEM] 309 | bel: {name: B6LUT} 310 | implies: 311 | - { tag: BLUT_STATE, state: RAM64 } 312 | - cells: 313 | - SRL16E 314 | - SRLC16E 315 | locations: 316 | - siteTypes: [SLICEM] 317 | bel: 318 | bels: 319 | - B5LUT 320 | - B6LUT 321 | implies: 322 | - { tag: BLUT_STATE, state: SRL16 } 323 | - cells: 324 | - SRLC32E 325 | locations: 326 | - siteTypes: [SLICEM] 327 | bel: {name: B6LUT} 328 | implies: 329 | - { tag: BLUT_STATE, state: SRL32 } 330 | - cells: 331 | - LUT1 332 | - LUT2 333 | - LUT3 334 | - LUT4 335 | - LUT5 336 | - LUT6 337 | - INV 338 | - BUF 339 | locations: 340 | - siteTypes: [SLICEM] 341 | bel: 342 | bels: 343 | - B5LUT 344 | - B6LUT 345 | implies: 346 | - { tag: BLUT_STATE, state: LUT } 347 | - cells: 348 | - RAMD32 349 | - RAMS32 350 | locations: 351 | - siteTypes: [SLICEM] 352 | bel: 353 | bels: 354 | - A5LUT 355 | - A6LUT 356 | implies: 357 | - { tag: ALUT_STATE, state: RAM32 } 358 | - cells: 359 | - RAMD64E 360 | - RAMS64E 361 | locations: 362 | - siteTypes: [SLICEM] 363 | bel: {name: A6LUT} 364 | implies: 365 | - { tag: ALUT_STATE, state: RAM64 } 366 | - cells: 367 | - SRL16E 368 | - SRLC16E 369 | locations: 370 | - siteTypes: [SLICEM] 371 | bel: 372 | bels: 373 | - A5LUT 374 | - A6LUT 375 | implies: 376 | - { tag: ALUT_STATE, state: SRL16 } 377 | - cells: 378 | - SRLC32E 379 | locations: 380 | - siteTypes: [SLICEM] 381 | bel: {name: A6LUT} 382 | implies: 383 | - { tag: ALUT_STATE, state: SRL32 } 384 | - cells: 385 | - LUT1 386 | - LUT2 387 | - LUT3 388 | - LUT4 389 | - LUT5 390 | - LUT6 391 | - INV 392 | - BUF 393 | locations: 394 | - siteTypes: [SLICEM] 395 | bel: 396 | bels: 397 | - A5LUT 398 | - A6LUT 399 | implies: 400 | - { tag: ALUT_STATE, state: LUT } 401 | -------------------------------------------------------------------------------- /tests/bram_constraint_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | import os.path 13 | import unittest 14 | from pysat.solvers import Solver 15 | 16 | from example_netlist import example_physical_netlist 17 | from fpga_interchange.interchange_capnp import Interchange 18 | from fpga_interchange.convert import read_format, get_schema 19 | from fpga_interchange.constraints.model import Constraints, CellInstance, Placement 20 | from fpga_interchange.constraints.placement_oracle import PlacementOracle 21 | 22 | 23 | def create_bram_tile(grid_x, grid_y, bram18_x0, bram18_y0, bram36_x0, 24 | bram36_y0): 25 | """ Create a BRAM_L/R like tile, with 2 RAMB18E1 sites and 1 RAMB36E1 site. """ 26 | tile_name = 'BRAM_X{}Y{}'.format(grid_x, grid_y) 27 | yield Placement( 28 | tile=tile_name, 29 | tile_type='BRAM_L', 30 | site='RAMB36E1_X{}Y{}'.format(bram36_x0, bram36_y0), 31 | site_type='RAMB36E1', 32 | bel='RAMB36E1') 33 | 34 | for dy in range(2): 35 | yield Placement( 36 | tile=tile_name, 37 | tile_type='BRAM_L', 38 | site='RAMB18E1_X{}Y{}'.format(bram18_x0, bram18_y0 + dy), 39 | site_type='RAMB18E1', 40 | bel='RAMB18E1') 41 | 42 | 43 | def create_placements(number_rows, number_cols): 44 | """ Create a simple grid of BRAM_L/R like tiles. """ 45 | for grid_x in range(number_rows): 46 | for grid_y in range(number_cols): 47 | for placement in create_bram_tile( 48 | grid_x=grid_x, 49 | grid_y=grid_y, 50 | bram18_x0=grid_x, 51 | bram18_y0=grid_y * 2, 52 | bram36_x0=grid_x, 53 | bram36_y0=grid_y, 54 | ): 55 | yield placement 56 | 57 | 58 | def create_cells(number_ramb18, number_ramb36): 59 | """ Creates some RAMB18E1 and RAMB36E1 cells for placement. """ 60 | for ramb18_idx in range(number_ramb18): 61 | yield CellInstance( 62 | cell='RAMB18E1', name='RAMB18_{}'.format(ramb18_idx), ports={}) 63 | 64 | for ramb36_idx in range(number_ramb36): 65 | yield CellInstance( 66 | cell='RAMB36E1', name='RAMB36_{}'.format(ramb36_idx), ports={}) 67 | 68 | 69 | class TestBramConstraints(unittest.TestCase): 70 | def setUp(self): 71 | schema = get_schema(os.environ['INTERCHANGE_SCHEMA_PATH'], 'device', 72 | 'Device.Constraints') 73 | path = os.path.join('test_data', 'series7_constraints.yaml') 74 | with open(path, 'rb') as f: 75 | constraints = read_format(schema, 'yaml', f) 76 | 77 | self.model = Constraints() 78 | self.model.read_constraints(constraints) 79 | 80 | interchange = Interchange( 81 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 82 | phys_netlist = example_physical_netlist() 83 | with open( 84 | os.path.join(os.environ['DEVICE_RESOURCE_PATH'], 85 | phys_netlist.part + '.device'), 'rb') as f: 86 | device = interchange.read_device_resources(f) 87 | 88 | self.placement_oracle = PlacementOracle() 89 | self.placement_oracle.add_sites_from_device(device) 90 | 91 | def assertFits(self, placements, cells): 92 | """ Assert that the cells fits in the placement locations provided. 93 | 94 | Also checks that the BRAM_MODE tile constraint is obeyed. 95 | 96 | """ 97 | placements = list(placements) 98 | cells = list(cells) 99 | 100 | # Create some lookups for cells, tiles and grids. 101 | cell_names = set(cell.name for cell in cells) 102 | cell_name_to_cell = {} 103 | for cell in cells: 104 | assert cell.name not in cell_name_to_cell 105 | cell_name_to_cell[cell.name] = cell 106 | 107 | sites = {} 108 | tiles = {} 109 | sites_to_tile = {} 110 | for placement in placements: 111 | tiles[placement.tile] = None 112 | sites[placement.site] = None 113 | sites_to_tile[placement.site] = placement.tile 114 | 115 | # Setup the SAT solver 116 | solver = self.model.build_sat(placements, cells, self.placement_oracle) 117 | clauses = solver.prepare_for_sat() 118 | 119 | cell_placements = {} 120 | 121 | with Solver() as sat: 122 | for clause in clauses: 123 | sat.add_clause(clause) 124 | 125 | # sat.solve returns True if the system of equations has a solution. 126 | self.assertTrue(sat.solve()) 127 | # model is one example solution 128 | model = sat.get_model() 129 | 130 | # Convert example solution into state group variables. 131 | state_groups_vars, other_vars = solver.decode_solution_model(model) 132 | self.assertEqual(len(other_vars), 0) 133 | 134 | # Convert tile BRAM_MODE state and cell placements into tiles and 135 | # placements dict. 136 | for variable, state in state_groups_vars.items(): 137 | if variable.endswith('.BRAM_MODE'): 138 | tile = variable[:-(len('BRAM_MODE') + 1)] 139 | 140 | if tiles[tile] is None: 141 | tiles[tile] = state 142 | else: 143 | assert tiles[tile] == state 144 | 145 | if variable in cell_names: 146 | cell_name = variable 147 | site, _ = state.split('.') 148 | self.assertTrue(site in sites) 149 | self.assertTrue(cell_name not in cell_placements) 150 | cell_placements[cell_name] = site 151 | 152 | # For each cell placement, ensure that the BRAM mode constraint was 153 | # followed. 154 | for cell_name, site in cell_placements.items(): 155 | self.assertTrue(sites[site] is None) 156 | sites[site] = cell_name 157 | 158 | cell = cell_name_to_cell[cell_name] 159 | 160 | tile = sites_to_tile[site] 161 | 162 | if cell.cell == 'RAMB18E1': 163 | self.assertEqual(tiles[tile], '2xRAMB18') 164 | elif cell.cell == 'RAMB36E1': 165 | self.assertEqual(tiles[tile], 'RAMB36') 166 | else: 167 | assert False, cell.cell 168 | 169 | def assertDoesNotFit(self, placements, cells): 170 | """ Asserts that the cells does not fit in the placements. """ 171 | placements = list(placements) 172 | cells = list(cells) 173 | 174 | solver = self.model.build_sat(placements, cells, self.placement_oracle) 175 | clauses = solver.prepare_for_sat() 176 | 177 | with Solver() as sat: 178 | for clause in clauses: 179 | sat.add_clause(clause) 180 | 181 | # sat.solve returns False, which means that there is no solution. 182 | self.assertFalse(sat.solve()) 183 | 184 | def test_1_of_each_cell(self): 185 | self.assertFits( 186 | placements=create_placements(4, 4), 187 | cells=create_cells(number_ramb18=1, number_ramb36=0)) 188 | self.assertFits( 189 | placements=create_placements(4, 4), 190 | cells=create_cells(number_ramb18=0, number_ramb36=1)) 191 | self.assertFits( 192 | placements=create_placements(4, 4), 193 | cells=create_cells(number_ramb18=1, number_ramb36=1)) 194 | 195 | def test_perfect_fit(self): 196 | # In a 4x4 placement, you can fit: 197 | # - 16 RAMB36 198 | # - 32 RAMB18 199 | # - Or trade 1 36 for 2 18's 200 | self.assertFits( 201 | placements=create_placements(4, 4), 202 | cells=create_cells(number_ramb18=32, number_ramb36=0)) 203 | self.assertFits( 204 | placements=create_placements(4, 4), 205 | cells=create_cells(number_ramb18=0, number_ramb36=16)) 206 | self.assertFits( 207 | placements=create_placements(4, 4), 208 | cells=create_cells(number_ramb18=2, number_ramb36=15)) 209 | self.assertFits( 210 | placements=create_placements(4, 4), 211 | cells=create_cells(number_ramb18=4, number_ramb36=14)) 212 | self.assertFits( 213 | placements=create_placements(4, 4), 214 | cells=create_cells(number_ramb18=30, number_ramb36=1)) 215 | 216 | def test_too_small(self): 217 | # A 2x2 grid has 8 maximum RAMB18 or 4 maximum RAMB36. 218 | self.assertFits( 219 | placements=create_placements(2, 2), 220 | cells=create_cells(number_ramb18=8, number_ramb36=0)) 221 | self.assertDoesNotFit( 222 | placements=create_placements(2, 2), 223 | cells=create_cells(number_ramb18=9, number_ramb36=0)) 224 | 225 | self.assertFits( 226 | placements=create_placements(2, 2), 227 | cells=create_cells(number_ramb18=0, number_ramb36=4)) 228 | self.assertDoesNotFit( 229 | placements=create_placements(2, 2), 230 | cells=create_cells(number_ramb18=0, number_ramb36=5)) 231 | 232 | self.assertFits( 233 | placements=create_placements(2, 2), 234 | cells=create_cells(number_ramb18=6, number_ramb36=1)) 235 | self.assertDoesNotFit( 236 | placements=create_placements(2, 2), 237 | cells=create_cells(number_ramb18=8, number_ramb36=1)) 238 | 239 | self.assertFits( 240 | placements=create_placements(2, 2), 241 | cells=create_cells(number_ramb18=1, number_ramb36=3)) 242 | self.assertDoesNotFit( 243 | placements=create_placements(2, 2), 244 | cells=create_cells(number_ramb18=1, number_ramb36=4)) 245 | 246 | self.assertDoesNotFit( 247 | placements=create_placements(5, 1), 248 | cells=create_cells(number_ramb18=10, number_ramb36=1)) 249 | 250 | 251 | if __name__ == '__main__': 252 | unittest.main() 253 | -------------------------------------------------------------------------------- /tests/test_converters.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2020 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | 12 | import os 13 | import io 14 | import unittest 15 | import json 16 | import yaml 17 | from yaml import CSafeLoader as SafeLoader, CDumper as Dumper 18 | 19 | from fpga_interchange.json_support import to_json, from_json 20 | from fpga_interchange.yaml_support import to_yaml, from_yaml 21 | from fpga_interchange.rapidyaml_support import to_rapidyaml, from_rapidyaml 22 | from fpga_interchange.compare import compare_capnp 23 | import ryml 24 | from fpga_interchange.capnp_utils import get_module_from_id 25 | from fpga_interchange.interchange_capnp import Interchange 26 | from fpga_interchange.convert import read_format, write_format, get_schema, \ 27 | FORMATS 28 | from example_netlist import example_logical_netlist, example_physical_netlist 29 | import pytest 30 | import psutil 31 | 32 | # Set to true to have round_trip_X write files to disk for inspection. 33 | OUTPUT_FILES = False 34 | 35 | 36 | def check_mem(): 37 | vmem = psutil.virtual_memory() 38 | return vmem.total < (8 * 1024 * 1024 * 1024) 39 | 40 | 41 | class TestConverterRoundTrip(unittest.TestCase): 42 | def round_trip_json(self, prefix, in_message): 43 | value = to_json(in_message) 44 | json_string = json.dumps(value, indent=2) 45 | 46 | if OUTPUT_FILES: 47 | with open(prefix + '_test_json.json', 'w') as f: 48 | f.write(json_string) 49 | 50 | value_out = json.loads(json_string) 51 | message = get_module_from_id(in_message.schema.node.id).new_message() 52 | from_json(message, value_out) 53 | compare_capnp(self, in_message, message) 54 | 55 | value2 = to_json(message) 56 | json_string2 = json.dumps(value2, indent=2) 57 | 58 | if OUTPUT_FILES: 59 | with open(prefix + '_test_json2.json', 'w') as f: 60 | f.write(json_string2) 61 | 62 | self.assertTrue(json_string == json_string2) 63 | 64 | def round_trip_yaml(self, prefix, in_message): 65 | value = to_yaml(in_message) 66 | yaml_string = yaml.dump(value, sort_keys=False, Dumper=Dumper) 67 | 68 | if OUTPUT_FILES: 69 | with open(prefix + '_test_yaml.yaml', 'w') as f: 70 | f.write(yaml_string) 71 | 72 | value_out = yaml.load(yaml_string, Loader=SafeLoader) 73 | message = get_module_from_id(in_message.schema.node.id).new_message() 74 | from_yaml(message, value_out) 75 | compare_capnp(self, in_message, message) 76 | 77 | value2 = to_yaml(message) 78 | yaml_string2 = yaml.dump(value2, sort_keys=False, Dumper=Dumper) 79 | 80 | if OUTPUT_FILES: 81 | with open(prefix + '_test_yaml2.yaml', 'w') as f: 82 | f.write(yaml_string2) 83 | 84 | self.assertTrue(yaml_string == yaml_string2) 85 | 86 | def round_trip_rapidyaml(self, prefix, in_message): 87 | strings, value = to_rapidyaml(in_message) 88 | yaml_string = ryml.emit(value) 89 | 90 | if OUTPUT_FILES: 91 | with open(prefix + '_test_rapidyaml.yaml', 'w') as f: 92 | f.write(yaml_string) 93 | 94 | value_out = ryml.parse(yaml_string) 95 | message = get_module_from_id(in_message.schema.node.id).new_message() 96 | from_rapidyaml(message, value_out) 97 | compare_capnp(self, in_message, message) 98 | 99 | strings, value2 = to_rapidyaml(message) 100 | yaml_string2 = ryml.emit(value2) 101 | 102 | if OUTPUT_FILES: 103 | with open(prefix + '_test_rapidyaml2.yaml', 'w') as f: 104 | f.write(yaml_string2) 105 | 106 | self.assertTrue(yaml_string == yaml_string2) 107 | 108 | def test_logical_netlist_json(self): 109 | logical_netlist = example_logical_netlist() 110 | 111 | interchange = Interchange( 112 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 113 | netlist_capnp = logical_netlist.convert_to_capnp(interchange) 114 | 115 | self.round_trip_json('log', netlist_capnp) 116 | 117 | def test_physical_netlist_json(self): 118 | phys_netlist = example_physical_netlist() 119 | 120 | interchange = Interchange( 121 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 122 | netlist_capnp = phys_netlist.convert_to_capnp(interchange) 123 | 124 | self.round_trip_json('phys', netlist_capnp) 125 | 126 | @pytest.mark.skipif(check_mem(), reason='This test needs ~7-8 GB of RAM') 127 | def test_device_json(self): 128 | return 129 | phys_netlist = example_physical_netlist() 130 | 131 | interchange = Interchange( 132 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 133 | 134 | with open( 135 | os.path.join(os.environ['DEVICE_RESOURCE_PATH'], 136 | phys_netlist.part + '.device'), 'rb') as f: 137 | dev_message = interchange.read_device_resources_raw(f) 138 | 139 | self.round_trip_json('device', dev_message) 140 | 141 | def test_logical_netlist_yaml(self): 142 | logical_netlist = example_logical_netlist() 143 | 144 | interchange = Interchange( 145 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 146 | netlist_capnp = logical_netlist.convert_to_capnp(interchange) 147 | 148 | self.round_trip_yaml('log', netlist_capnp) 149 | 150 | def test_physical_netlist_yaml(self): 151 | phys_netlist = example_physical_netlist() 152 | 153 | interchange = Interchange( 154 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 155 | netlist_capnp = phys_netlist.convert_to_capnp(interchange) 156 | 157 | self.round_trip_yaml('phys', netlist_capnp) 158 | 159 | def test_logical_netlist_rapidyaml(self): 160 | logical_netlist = example_logical_netlist() 161 | 162 | interchange = Interchange( 163 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 164 | netlist_capnp = logical_netlist.convert_to_capnp(interchange) 165 | 166 | self.round_trip_rapidyaml('log', netlist_capnp) 167 | 168 | def test_physical_netlist_rapidyaml(self): 169 | phys_netlist = example_physical_netlist() 170 | 171 | interchange = Interchange( 172 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 173 | netlist_capnp = phys_netlist.convert_to_capnp(interchange) 174 | 175 | self.round_trip_rapidyaml('phys', netlist_capnp) 176 | 177 | @pytest.mark.skipif(check_mem(), reason='This test needs ~7-8 GB of RAM') 178 | def test_device_rapidyaml(self): 179 | return 180 | phys_netlist = example_physical_netlist() 181 | 182 | interchange = Interchange( 183 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 184 | 185 | with open( 186 | os.path.join(os.environ['DEVICE_RESOURCE_PATH'], 187 | phys_netlist.part + '.device'), 'rb') as f: 188 | dev_message = interchange.read_device_resources_raw(f) 189 | 190 | self.round_trip_rapidyaml('device', dev_message) 191 | 192 | def assertSameSchema(self, schema_a, schema_b): 193 | self.assertEqual(type(schema_a), type(schema_b)) 194 | self.assertEqual(schema_a.schema.node.id, schema_b.schema.node.id) 195 | 196 | def test_get_schemas(self): 197 | schema_dir = os.environ['INTERCHANGE_SCHEMA_PATH'] 198 | 199 | interchange = Interchange( 200 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 201 | 202 | self.assertSameSchema(interchange.device_resources_schema.Device, 203 | get_schema(schema_dir, 'device')) 204 | 205 | self.assertSameSchema(interchange.device_resources_schema.Device, 206 | get_schema(schema_dir, 'device', 'Device')) 207 | 208 | self.assertSameSchema( 209 | interchange.device_resources_schema.Device.SiteType, 210 | get_schema(schema_dir, 'device', 'Device.SiteType')) 211 | 212 | self.assertSameSchema(interchange.logical_netlist_schema.Netlist, 213 | get_schema(schema_dir, 'logical')) 214 | 215 | self.assertSameSchema(interchange.logical_netlist_schema.Netlist, 216 | get_schema(schema_dir, 'logical', 'Netlist')) 217 | 218 | self.assertSameSchema(interchange.physical_netlist_schema.PhysNetlist, 219 | get_schema(schema_dir, 'physical')) 220 | 221 | self.assertSameSchema( 222 | interchange.physical_netlist_schema.PhysNetlist, 223 | get_schema(schema_dir, 'physical', 'PhysNetlist')) 224 | 225 | def round_read_write_message(self, schema, message): 226 | schema_dir = os.environ['INTERCHANGE_SCHEMA_PATH'] 227 | 228 | schema = get_schema(schema_dir, schema) 229 | 230 | for format1 in FORMATS: 231 | f = io.BytesIO() 232 | write_format(message, format1, f) 233 | f.seek(0) 234 | message_out = read_format(schema, format1, f) 235 | compare_capnp(self, message, message_out) 236 | 237 | for format2 in FORMATS: 238 | f2 = io.BytesIO() 239 | write_format(message_out, format2, f2) 240 | f2.seek(0) 241 | message_out2 = read_format(schema, format2, f2) 242 | compare_capnp(self, message, message_out2) 243 | compare_capnp(self, message_out, message_out2) 244 | 245 | def test_logical_netlist_convert(self): 246 | logical_netlist = example_logical_netlist() 247 | 248 | interchange = Interchange( 249 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 250 | netlist_capnp = logical_netlist.convert_to_capnp(interchange) 251 | 252 | self.round_read_write_message('logical', netlist_capnp) 253 | 254 | def test_physical_netlist_convert(self): 255 | phys_netlist = example_physical_netlist() 256 | 257 | interchange = Interchange( 258 | schema_directory=os.environ['INTERCHANGE_SCHEMA_PATH']) 259 | netlist_capnp = phys_netlist.convert_to_capnp(interchange) 260 | 261 | self.round_read_write_message('physical', netlist_capnp) 262 | -------------------------------------------------------------------------------- /fpga_interchange/device_timing_patching.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (C) 2021 The F4PGA Authors. 5 | # 6 | # Use of this source code is governed by a ISC-style 7 | # license that can be found in the LICENSE file or at 8 | # https://opensource.org/licenses/ISC 9 | # 10 | # SPDX-License-Identifier: ISC 11 | """ 12 | This file contains simple script for patching device with timing data. 13 | 14 | It uses simple abstraction layer. It constructs specialized class, 15 | for given architecture, passing database path, then it calls extract_data method. 16 | That method must return dictionary with keys being tile_type names. 17 | Data stored at each key must be dictionary with keys: "wires", "pips", "sites". 18 | "wires" entry must hold dictionary with keys being wire name, and data being RC model 19 | "pips" entry must hold dictionary with keys being pair (wire0 name, wire1 name) and data being pip model 20 | "sites" entry must hold dictionary with keys being site name and data being 21 | dictionary with keys being sitePort name and value being model of this port 22 | 23 | So far this script only adds delay models to site ports, PIPs and nodes. 24 | But adding support for cells shouldn't be hard thanks to abstraction layer. 25 | """ 26 | import argparse 27 | 28 | from fpga_interchange.interchange_capnp import read_capnp_file,\ 29 | write_capnp_file 30 | 31 | from fpga_interchange.convert import get_schema 32 | from fpga_interchange.prjxray_db_reader import prjxray_db_reader 33 | import os 34 | import json 35 | 36 | 37 | def create_wire_to_node_map(device): 38 | wire_map = {} 39 | node_to_model = {} 40 | for node in device.nodes: 41 | node_to_model[node] = (tuple([0] * 6), tuple([0] * 6)) 42 | for wire in node.wires: 43 | wire_map[wire] = node 44 | return node_to_model, wire_map 45 | 46 | 47 | def create_tile_type_wire_name_to_wire_list(device): 48 | tile_tileType_map = {} 49 | for tile in device.tileList: 50 | tile_tileType_map[tile.name] = tile.type 51 | tileType_wireName_wireList_map = {} 52 | for i, wire in enumerate(device.wires): 53 | tileType = tile_tileType_map[wire.tile] 54 | key = (tileType, wire.wire) 55 | if key not in tileType_wireName_wireList_map.keys(): 56 | tileType_wireName_wireList_map[key] = [] 57 | tileType_wireName_wireList_map[key].append(i) 58 | return tileType_wireName_wireList_map 59 | 60 | 61 | def create_string_to_dev_string_map(device): 62 | string_map = {} 63 | for i, string in enumerate(device.strList): 64 | string_map[string] = i 65 | return string_map 66 | 67 | 68 | def create_tile_type_name_to_tile_type(device): 69 | tileType_name_tileType_map = {} 70 | for i, tile in enumerate(device.tileTypeList): 71 | tileType_name_tileType_map[tile.name] = i 72 | return tileType_name_tileType_map 73 | 74 | 75 | def create_tile_type_wire0_wire1_pip_map(device): 76 | tileType_wires_pip_map = {} 77 | for i, tile in enumerate(device.tileTypeList): 78 | for pip in tile.pips: 79 | wire0 = tile.wires[pip.wire0] 80 | wire1 = tile.wires[pip.wire1] 81 | key = (i, wire0, wire1) 82 | tileType_wires_pip_map[key] = pip 83 | return tileType_wires_pip_map 84 | 85 | 86 | def create_site_name_to_site_type_map(device): 87 | siteName_siteType_map = {} 88 | for i, site in enumerate(device.siteTypeList): 89 | siteName_siteType_map[site.name] = i 90 | return siteName_siteType_map 91 | 92 | 93 | def create_site_type_name_to_site_pin_map(device): 94 | siteType_name_sitePin = {} 95 | for i, site in enumerate(device.siteTypeList): 96 | for sitePin in site.pins: 97 | siteType_name_sitePin[(i, sitePin.name)] = sitePin 98 | return siteType_name_sitePin 99 | 100 | 101 | def populate_corner_model(corner_model, fast_min, fast_typ, fast_max, slow_min, 102 | slow_typ, slow_max): 103 | fields = ['min', 'typ', 'max'] 104 | fast = [fast_min, fast_typ, fast_max] 105 | slow = [slow_min, slow_typ, slow_max] 106 | if any(x is not None for x in fast): 107 | corner_model.fast.init('fast') 108 | if any(x is not None for x in slow): 109 | corner_model.slow.init('slow') 110 | for i, field in enumerate(fields): 111 | if fast[i] is not None: 112 | x = getattr(corner_model.fast.fast, field) 113 | setattr(x, field, fast[i]) 114 | for i, field in enumerate(fields): 115 | if slow[i] is not None: 116 | x = getattr(corner_model.slow.slow, field) 117 | setattr(x, field, slow[i]) 118 | 119 | 120 | def main(): 121 | parser = argparse.ArgumentParser( 122 | description="Add timing information to Device") 123 | 124 | parser.add_argument("--schema_dir", required=True) 125 | parser.add_argument("--timing_dir", required=True) 126 | parser.add_argument("--family", required=True) 127 | parser.add_argument("device") 128 | parser.add_argument("patched_device") 129 | 130 | args = parser.parse_args() 131 | 132 | device_schema = get_schema(args.schema_dir, "device") 133 | with open(args.device, 'rb') as f: 134 | dev = read_capnp_file(device_schema, f) 135 | 136 | dev = dev.as_builder() 137 | 138 | node_model_map, wire_node_map = create_wire_to_node_map(dev) 139 | tileType_wire_name_wire_list_map = create_tile_type_wire_name_to_wire_list( 140 | dev) 141 | string_map = create_string_to_dev_string_map(dev) 142 | tile_name_tileType_map = create_tile_type_name_to_tile_type(dev) 143 | tileType_wires_pip_map = create_tile_type_wire0_wire1_pip_map(dev) 144 | siteName_siteType_map = create_site_name_to_site_type_map(dev) 145 | siteType_name_sitePin_map = create_site_type_name_to_site_pin_map(dev) 146 | 147 | tile_type_name_to_number = {} 148 | for i, tileType in enumerate(dev.tileTypeList): 149 | name = dev.strList[tileType.name] 150 | tile_type_name_to_number[name] = i 151 | 152 | pip_models = {} 153 | 154 | family_map = {"xc7": prjxray_db_reader} 155 | 156 | timing_dir = args.timing_dir 157 | timing_reader = family_map[args.family](timing_dir) 158 | timing_data = timing_reader.extract_data() 159 | for tile, _data in timing_data.items(): 160 | if tile not in string_map: 161 | continue 162 | tile_name = string_map[tile] 163 | tileType = tile_name_tileType_map[tile_name] 164 | for name, data in _data['wires'].items(): 165 | wire_name = string_map[name] 166 | for wire in tileType_wire_name_wire_list_map[(tileType, 167 | wire_name)]: 168 | if wire not in wire_node_map: 169 | continue 170 | node = wire_node_map[wire] 171 | model = node_model_map[node] 172 | res = list(model[0]) 173 | cap = list(model[1]) 174 | for i in range(len(res)): 175 | res[i] += data[0][i] 176 | for i in range(len(cap)): 177 | cap[i] += data[1][i] 178 | model = (tuple(res), tuple(cap)) 179 | node_model_map[node] = model 180 | 181 | for old_key, data in _data['pips'].items(): 182 | wire0 = string_map[old_key[0]] 183 | wire1 = string_map[old_key[1]] 184 | key = (tileType, wire0, wire1) 185 | if key not in tileType_wires_pip_map: 186 | continue 187 | pip = tileType_wires_pip_map[key] 188 | pip_models[pip] = data 189 | 190 | for site, data in _data['sites'].items(): 191 | siteType = siteName_siteType_map[string_map[site]] 192 | for sitePin, model in data.items(): 193 | sitePin_obj = siteType_name_sitePin_map[(siteType, 194 | string_map[sitePin])] 195 | if model[0][0] is not None and model[0][0] == 'r': 196 | sitePin_obj.model.init('resistance') 197 | corner_model = sitePin_obj.model.resistance 198 | populate_corner_model(corner_model, *model[0][1]) 199 | elif model[0][0] is not None and model[0][0] == 'c': 200 | sitePin_obj.model.init('capacitance') 201 | corner_model = sitePin_obj.model.capacitance 202 | populate_corner_model(corner_model, *model[0][1]) 203 | 204 | sitePin_obj.init('delay') 205 | corner_model = sitePin_obj.delay 206 | populate_corner_model(corner_model, *model[1]) 207 | 208 | timing_set = set() 209 | for timing in node_model_map.values(): 210 | timing_set.add(timing) 211 | timing_dict = {timing: i for i, timing in enumerate(timing_set)} 212 | dev.init("nodeTimings", len(timing_dict)) 213 | for model, i in timing_dict.items(): 214 | corner_model = dev.nodeTimings[i].resistance 215 | populate_corner_model(corner_model, *model[0]) 216 | corner_model = dev.nodeTimings[i].capacitance 217 | populate_corner_model(corner_model, *model[1]) 218 | 219 | for node, timing in node_model_map.items(): 220 | node.nodeTiming = timing_dict[timing] 221 | 222 | timing_set = set() 223 | for model in pip_models.values(): 224 | timing_set.add(model) 225 | timing_dict = {timing: i for i, timing in enumerate(timing_set)} 226 | dev.init("pipTimings", len(timing_dict)) 227 | for model, i in timing_dict.items(): 228 | pipTiming = dev.pipTimings[i] 229 | corner_model = pipTiming.inputCapacitance 230 | populate_corner_model(corner_model, *model[0]) 231 | 232 | corner_model = pipTiming.internalCapacitance 233 | populate_corner_model(corner_model, *model[1]) 234 | 235 | corner_model = pipTiming.internalDelay 236 | populate_corner_model(corner_model, *model[2]) 237 | 238 | corner_model = pipTiming.outputResistance 239 | populate_corner_model(corner_model, *model[3]) 240 | 241 | corner_model = pipTiming.outputCapacitance 242 | populate_corner_model(corner_model, *model[4]) 243 | 244 | for pip, timing in pip_models.items(): 245 | pip.timing = timing_dict[timing] 246 | 247 | with open(args.patched_device, "wb") as fp: 248 | write_capnp_file(dev, fp) 249 | 250 | 251 | if __name__ == "__main__": 252 | main() 253 | -------------------------------------------------------------------------------- /fpga_interchange/constraints/sat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2020 The F4PGA Authors. 4 | # 5 | # Use of this source code is governed by a ISC-style 6 | # license that can be found in the LICENSE file or at 7 | # https://opensource.org/licenses/ISC 8 | # 9 | # SPDX-License-Identifier: ISC 10 | 11 | 12 | class AssertStateVariable(): 13 | """ Abstract asserted state variable. """ 14 | 15 | def __init__(self, parent, state): 16 | self.parent = parent 17 | self.state = state 18 | 19 | def variable_name(self): 20 | return '{}.{}'.format(self.parent.prefix, self.state) 21 | 22 | def variable(self, solver): 23 | return solver.get_variable(self.variable_name()) 24 | 25 | def __str__(self): 26 | return self.variable_name() 27 | 28 | 29 | class DeassertStateVariable(): 30 | """ Abstract deasserted state variable. """ 31 | 32 | def __init__(self, parent, state): 33 | self.parent = parent 34 | self.state = state 35 | 36 | def variable_name(self): 37 | return '{}.NOT.{}'.format(self.parent.prefix, self.state) 38 | 39 | def variable(self, solver): 40 | return solver.get_variable(self.variable_name()) 41 | 42 | def __str__(self): 43 | return self.variable_name() 44 | 45 | 46 | class Not(): 47 | """ Abstract inverted variable. """ 48 | 49 | def __init__(self, variable): 50 | self.a_variable = variable 51 | 52 | def variable_name(self): 53 | return self.a_variable.variable_name() 54 | 55 | def variable(self, solver): 56 | return -solver.get_variable(self.variable_name()) 57 | 58 | def __str__(self): 59 | return '!' + self.variable_name() 60 | 61 | 62 | class Xor(): 63 | """ Abstract XOR SAT clause. """ 64 | 65 | def __init__(self, variable_a, variable_b): 66 | self.variable_a = variable_a 67 | self.variable_b = variable_b 68 | 69 | def clauses(self): 70 | yield [self.variable_a, self.variable_b] 71 | yield [Not(self.variable_a), Not(self.variable_b)] 72 | 73 | def __str__(self): 74 | return '{} xor {}'.format(self.variable_a.variable_name(), 75 | self.variable_b.variable_name()) 76 | 77 | 78 | class Implies(): 79 | """ Abstract implies (->) SAT clause. """ 80 | 81 | def __init__(self, source_variable, target_variable): 82 | self.source_variable = source_variable 83 | self.target_variable = target_variable 84 | 85 | def clauses(self): 86 | yield [Not(self.source_variable), self.target_variable] 87 | 88 | def __str__(self): 89 | return '{} -> {}'.format(self.source_variable.variable_name(), 90 | self.target_variable.variable_name()) 91 | 92 | 93 | class Or(): 94 | """ Abstract OR SAT clause. """ 95 | 96 | def __init__(self, variables): 97 | self.variables = variables 98 | 99 | def clauses(self): 100 | yield self.variables 101 | 102 | def __str__(self): 103 | return 'sum({})'.format(', '.join(str(var) for var in self.variables)) 104 | 105 | 106 | class ExclusiveStateGroup(): 107 | """ A group of states that have at most 1 state selected. """ 108 | 109 | def __init__(self, prefix, default): 110 | self.prefix = prefix 111 | self.states = set() 112 | self.default = default 113 | 114 | def name(self): 115 | """ Return name of state group. """ 116 | return self.prefix 117 | 118 | def add_state(self, state): 119 | """ Add a state to this group. """ 120 | self.states.add(state) 121 | 122 | def assert_state(self, state): 123 | """ Return a SAT variable that asserts that a state must be asserted. """ 124 | return AssertStateVariable(self, state) 125 | 126 | def deassert_state(self, state): 127 | """ Return a SAT variable that asserts that a state must be deasserted. """ 128 | return DeassertStateVariable(self, state) 129 | 130 | def select_one(self): 131 | """ Yields SAT clauses that ensure that one variable from this state group is selected. """ 132 | yield Or([self.assert_state(state) for state in self.states]) 133 | 134 | def implies_clause(self, source_variable, state): 135 | """ Yields SAT clauses that ensure if source_variable is true, then state is asserted from this group. """ 136 | assert state in self.states, state 137 | yield Implies(source_variable, self.assert_state(state)) 138 | 139 | def implies_not_clause(self, source_variable, state): 140 | """ Yields SAT clauses that ensure if source_variable is true, then state is deassert from this group. """ 141 | assert state in self.states 142 | yield Implies(source_variable, self.deassert_state(state)) 143 | 144 | def requires_clause(self, source_variable, states): 145 | """ Yields SAT clauses that ensure if source_variable is true, then one of the supplied states must be asserted from this group. """ 146 | for other_state in self.states - states: 147 | yield self.implies_not_clause(source_variable, other_state) 148 | 149 | def variables(self): 150 | """ Yields SAT variables generated from this state group. """ 151 | for state in self.states: 152 | yield self.assert_state(state) 153 | yield self.deassert_state(state) 154 | 155 | def clauses(self): 156 | """ Yield SAT clauses that ensure this state group selects at most one state. """ 157 | for state in self.states: 158 | yield Xor( 159 | AssertStateVariable(self, state), 160 | DeassertStateVariable(self, state)) 161 | for other_state in (self.states - set([state])): 162 | yield Implies( 163 | AssertStateVariable(self, state), 164 | DeassertStateVariable(self, other_state)) 165 | 166 | def get_state(self, variables_for_state_group): 167 | """ Return state for this group based on true SAT variables relevant to this group. """ 168 | state = None 169 | for variable in variables_for_state_group: 170 | assert variable.startswith(self.prefix + '.') 171 | data_portion = variable[len(self.prefix) + 1:] 172 | 173 | not_set = False 174 | if data_portion.startswith('NOT.'): 175 | data_portion = data_portion[len('NOT.'):] 176 | not_set = True 177 | 178 | assert data_portion in self.states 179 | 180 | if not_set: 181 | continue 182 | 183 | if state is None: 184 | state = data_portion 185 | else: 186 | assert False, (state, data_portion) 187 | 188 | if state is None: 189 | state = self.default 190 | 191 | return state 192 | 193 | 194 | class Solver(): 195 | """ Abstract SAT solver, where each SAT variable is a string. 196 | 197 | Clauses used in this class are "abstract" clauses, that can yield more than 198 | one clause. 199 | 200 | """ 201 | 202 | def __init__(self): 203 | self.variable_names = set() 204 | self.variable_name_to_index = None 205 | self.abstract_clauses = [] 206 | self.state_group_names = set() 207 | self.state_groups = [] 208 | self.variable_to_state_group = {} 209 | 210 | def add_state_group(self, state_group): 211 | """ Adds a state group to the solver. 212 | 213 | state_group (ExclusiveStateGroup) - State group. 214 | 215 | """ 216 | assert state_group.name() not in self.state_group_names 217 | self.state_group_names.add(state_group.name()) 218 | self.state_groups.append(state_group) 219 | 220 | def add_variable_names(self, variables): 221 | """ Adds a variable names to this Solver. 222 | 223 | These variable names cannot already be apart of the Solver. 224 | 225 | """ 226 | new_variable_names = set() 227 | for variable in variables: 228 | new_variable_names.add(variable) 229 | 230 | assert len(self.variable_names & variables) == 0 231 | 232 | self.variable_names |= new_variable_names 233 | 234 | def add_clause(self, clause): 235 | """ Add an abstract clause to the Solver. 236 | 237 | Interface for abstract clause should have one method that yields a 238 | list of abstract variable objects. 239 | 240 | Abstract variable objects should have a method called variable, that 241 | takes a Solver object. 242 | 243 | """ 244 | self.abstract_clauses.append(clause) 245 | 246 | def get_variable(self, variable_name): 247 | """ Return SAT variable index for a variable name. """ 248 | assert self.variable_name_to_index is not None 249 | return self.variable_name_to_index[variable_name] 250 | 251 | def get_variable_name(self, variable_index): 252 | """ Return a SAT variable name for a given variable index. """ 253 | return self.variable_names[variable_index - 1] 254 | 255 | def prepare_for_sat(self): 256 | """ Convert SAT clauses using variable name strings to SAT indicies """ 257 | 258 | for state_group in self.state_groups: 259 | new_variables = set() 260 | for variable in state_group.variables(): 261 | new_variables.add(variable.variable_name()) 262 | 263 | self.add_variable_names(new_variables) 264 | 265 | for variable in new_variables: 266 | assert variable not in self.variable_to_state_group 267 | self.variable_to_state_group[variable] = state_group 268 | 269 | for clause in state_group.clauses(): 270 | self.add_clause(clause) 271 | 272 | self.variable_names = sorted(self.variable_names) 273 | self.variable_name_to_index = {} 274 | 275 | # Assign SAT variables indicies to variable names 276 | for idx, variable_name in enumerate(self.variable_names): 277 | assert variable_name not in self.variable_name_to_index 278 | self.variable_name_to_index[variable_name] = idx + 1 279 | 280 | # Convert abstract clauses using variable names to SAT clauses 281 | concrete_clauses = set() 282 | for abstract_clause in self.abstract_clauses: 283 | for clause in abstract_clause.clauses(): 284 | concrete_clause = [] 285 | for part in clause: 286 | concrete_clause.append(part.variable(self)) 287 | 288 | assert len(set(concrete_clause)) == len(concrete_clause) 289 | concrete_clauses.add(tuple(sorted(concrete_clause))) 290 | 291 | return sorted(concrete_clauses) 292 | 293 | def decode_solution_model(self, sat_model): 294 | """ Decode a solution from a SAT solver. 295 | 296 | Returns a dict of state group states and a set of SAT variables that 297 | don't belong to state group states. 298 | 299 | """ 300 | state_group_variables = {} 301 | other_variables = set() 302 | 303 | for idx in sat_model: 304 | if idx < 0: 305 | continue 306 | 307 | variable = self.get_variable_name(idx) 308 | 309 | if variable in self.variable_to_state_group: 310 | state_group = self.variable_to_state_group[variable] 311 | 312 | state_group_name = state_group.name() 313 | if state_group_name not in state_group_variables: 314 | state_group_variables[state_group_name] = set() 315 | 316 | state_group_variables[state_group_name].add(variable) 317 | else: 318 | other_variables.add(variable) 319 | 320 | state_group_results = {} 321 | for state_group_name, variables in state_group_variables.items(): 322 | state_group = self.variable_to_state_group[list(variables)[0]] 323 | state_group_results[state_group_name] = state_group.get_state( 324 | variables) 325 | 326 | return state_group_results, other_variables 327 | 328 | def print_debug(self): 329 | """ Print debugging information for the abstract SAT solver. """ 330 | print() 331 | print("Variable names ({} total):".format(len(self.variable_names))) 332 | print() 333 | for variable in self.variable_names: 334 | print(variable) 335 | print() 336 | 337 | print("Clauses:") 338 | print() 339 | for clause in self.abstract_clauses: 340 | print(clause) 341 | --------------------------------------------------------------------------------