diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f874d99e..8a66efee 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -43,6 +43,7 @@ on: - '.github/workflows/publish-reports-github-pages.yml' - '**.md' - '*.sh' + workflow_call: workflow_dispatch: release: types: [published] @@ -69,7 +70,7 @@ jobs: # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.13'] + python-version: ['3.12'] build_type: [RelWithDebInfo] c_compiler: [clang, cl] include: @@ -98,7 +99,7 @@ jobs: env: ALLURE_RESULTS_PATH: build/allure-results - COVERAGE_REPORT_PATH: build/python-coverage + COVERAGE_DATA_PATH: build/python-coverage.sqlite steps: - uses: actions/checkout@v4 @@ -237,51 +238,54 @@ jobs: - name: "[🐍] Run Unit tests" run: > - poetry run coverage run -m pytest -m "not slow" + poetry run coverage run --data-file=${{ env.COVERAGE_DATA_PATH }} -m pytest -m "not slow" --alluredir ${{ env.ALLURE_RESULTS_PATH }} --color no shell: bash # NOTE: required for windows poetry calls - - name: "[🐍] Create coverage report" - if: success() || failure() - run: poetry run coverage html --directory ${{ env.COVERAGE_REPORT_PATH }} - shell: bash # NOTE: required for windows poetry calls + - run: pwd - # - name: "Print listing of build dir" - # if: success() || failure() - # run: find ${{github.workspace}}/build -path ${{github.workspace}}/build/_deps -prune -o -print - # shell: bash # NOTE: required for windows + - run: ls -aR - name: "[🐍] Store coverage results" if: success() || failure() uses: actions/upload-artifact@v4 with: - name: upload-python-coverage-report-${{ matrix.os }}_py${{ matrix.python-version }} - path: ${{ env.COVERAGE_REPORT_PATH }} + name: python-unit-tests-coverage-report-${{ matrix.os }}_py${{ matrix.python-version }} + path: ${{ env.COVERAGE_DATA_PATH }} # NOTE: pytest-cov coverage binary file, can be combined later! retention-days: 1 if-no-files-found: error - + - name: "[🐍] Store allure test results" if: success() || failure() uses: actions/upload-artifact@v4 with: - name: upload-python-allure-report-${{ matrix.os }}_py${{ matrix.python-version }} + name: python-unit-tests-allure-report-${{ matrix.os }}_py${{ matrix.python-version }} path: ${{ env.ALLURE_RESULTS_PATH }} retention-days: 1 if-no-files-found: error call-publish-pypi: - name: "Trigger workflow to publish distribution 📦s to PyPI/TestPyPI" + name: "Publish distribution 📦s to PyPI/TestPyPI" needs: build_and_test concurrency: group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }}-publish-pypi cancel-in-progress: true uses: ./.github/workflows/publish-pypi.yml - call-publish-reports-github-pages: - name: "Trigger workflow to publish reports to GitHub Pages" + call-integration-tests: + name: "Integration Tests" needs: build_and_test concurrency: - group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }}-publish-reports-github-pages + group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }}-integration-tests cancel-in-progress: true - uses: ./.github/workflows/publish-reports-github-pages.yml + uses: ./.github/workflows/integration-tests.yml + + # NOTE: this is called from integration tests + # call-publish-reports-github-pages: + # name: "Publish reports to GitHub Pages" + # needs: build_and_test + # concurrency: + # group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }}-publish-reports-github-pages + # cancel-in-progress: true + # uses: ./.github/workflows/publish-reports-github-pages.yml diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 00000000..70216510 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,202 @@ +## +## -------------------------------------------------------------------------------- +## SPDX-FileCopyrightText: 2024 Martin Jan Köhler and Harald Pretl +## Johannes Kepler University, Institute for Integrated Circuits. +## +## This file is part of KPEX +## (see https://github.com/martinjankoehler/klayout-pex). +## +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## SPDX-License-Identifier: GPL-3.0-or-later +## -------------------------------------------------------------------------------- +## + +name: "Integration tests" + +on: + workflow_call: + workflow_dispatch: + # pull_request: + # branches: [ "main" ] + # paths: + # - '.github/workflows/integration-tests.yml' + +env: + KLAYOUT_DEB: "klayout_0.29.10-1_amd64.deb" + KLAYOUT_URL: "https://www.klayout.org/downloads/Ubuntu-22/klayout_0.29.10-1_amd64.deb" + + +jobs: + run-tests: + name: Integration Test ${{ matrix.os }}-${{ matrix.build_type }}-py${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. + # Consider changing this to true when your workflow is stable. + fail-fast: false + + matrix: + os: [ubuntu-22.04] + python-version: ['3.12'] + + continue-on-error: true + + env: + ALLURE_RESULTS_PATH: build/allure-results + COVERAGE_DATA_PATH: build/python-coverage.sqlite + + steps: + - uses: actions/checkout@v4 + + - name: "[🐍] Setup python ${{ matrix.python-version }}" + id: setup-python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: "[🐍] Install Poetry" + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + virtualenvs-path: .venv + installer-parallel: true + + - name: "[🐍] Load cached venv" + id: cached-poetry-dependencies + uses: actions/cache@v4 + with: + path: .venv + key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} + + - name: "[🐍] Install dependencies" + run: poetry install --no-interaction --no-root + shell: bash # NOTE: required for windows poetry calls + + - name: "Restore cached klayout deb 📦 download" + id: cache-klayout-deb + uses: actions/cache/restore@v4 + with: + path: klayout-deb + key: ${{ runner.os }}-klayout-deb + + - name: "Download klayout deb 📦" + if: steps.cache-klayout-deb.outputs.cache-hit != 'true' + run: | + mkdir -p klayout-deb + pushd klayout-deb + wget "$KLAYOUT_URL" + popd + + - name: "Save cached klayout deb 📦 download" + if: success() || failure() + uses: actions/cache/save@v4 + with: + path: klayout-deb + key: ${{ runner.os }}-klayout-deb + + - name: "Install klayout deb 📦" + run: | + pushd klayout-deb + sudo apt-get update + sudo apt-get install ./$KLAYOUT_DEB + popd + + - name: "Restore cached LVSDB results" + uses: actions/cache/restore@v4 + with: + path: output_sky130A/.kpex_cache + key: ${{ matrix.os }}-${{ matrix.python-version }}-kpex_cache + + - name: "Download and merge Python dist artifacts" + uses: actions/download-artifact@v4 + with: + pattern: python-dist-ubuntu-latest* + merge-multiple: true + path: dist # destination + + - name: "Unpack Source Distribution" + run: | + tar xvfz dist/klayout_pex*.tar.gz --strip-components=1 + + - name: "[🐍] Run Unit tests" + run: > + poetry run coverage run --data-file=${{ env.COVERAGE_DATA_PATH }} -m pytest -m "slow" + --alluredir ${{ env.ALLURE_RESULTS_PATH }} + --color no + shell: bash # NOTE: required for windows poetry calls + + - name: "[🐍] Store coverage results" + if: success() || failure() + uses: actions/upload-artifact@v4 + with: + name: python-integration-tests-coverage-report-${{ matrix.os }}_py${{ matrix.python-version }} + path: ${{ env.COVERAGE_DATA_PATH }} # NOTE: pytest-cov coverage binary file, can be combined later! + retention-days: 1 + if-no-files-found: error + + - name: "[🐍] Store allure test results" + if: success() || failure() + uses: actions/upload-artifact@v4 + with: + name: python-integration-tests-allure-report-${{ matrix.os }}_py${{ matrix.python-version }} + path: ${{ env.ALLURE_RESULTS_PATH }} + retention-days: 1 + if-no-files-found: error + + - name: "Save cached LVSDB results" + if: success() || failure() + uses: actions/cache/save@v4 + with: + path: output_sky130A/.kpex_cache + key: ${{ matrix.os }}-${{ matrix.python-version }}-kpex_cache + + call-publish-reports-github-pages: + name: "Publish reports to GitHub Pages" + needs: run-tests + concurrency: + group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }}-publish-reports-github-pages + cancel-in-progress: true + uses: ./.github/workflows/publish-reports-github-pages.yml + + # run-inside-docker: + # name: "Run tests" + # runs-on: ubuntu-latest + # continue-on-error: true + # + # steps: + # - uses: actions/checkout@v4 + # + # - name: Generate Cache Key from Dockerfiles + # id: generate_cache_key + # run: | + # files="./tests/fixtures/docker-compose.yml" + # file_contents=$(cat $files) + # key=$(echo "${file_contents}" | sha1sum | awk '{print $1}') + # echo "key=${key}" >> "$GITHUB_OUTPUT" + # + # - name: "Cache Docker images" + # uses: ScribeMD/docker-cache@0.5.0 + # with: + # key: docker-${{ runner.os }}-integration-tests-${{ steps.generate_cache_key.outputs.key }} + # + # - name: "Install Docker and run tests" + # uses: adambirds/docker-compose-action@v1.5.0 + # with: + # compose-file: "./tests/fixtures/docker-compose.yml" + # test-container: iic-osic-tools + # test-command: --skip ./run_integration_tests.sh + # continue-on-error: true # ensure docker cache is written nevertheless + diff --git a/.github/workflows/publish-reports-github-pages.yml b/.github/workflows/publish-reports-github-pages.yml index bbc158fc..0cde01ec 100644 --- a/.github/workflows/publish-reports-github-pages.yml +++ b/.github/workflows/publish-reports-github-pages.yml @@ -43,17 +43,17 @@ jobs: # uses: actions/upload-artifact/merge@v4 # with: # name: merged-allure-reports - # pattern: upload-python-allure-report-* + # pattern: python-*-tests-allure-report-ubuntu* # # delete-merged: true # retention-days: 1 - name: "Download and merge Allure coverage artifacts" uses: actions/download-artifact@v4 with: - pattern: upload-python-allure-report-* + pattern: python-*-tests-allure-report-* merge-multiple: true path: build/allure-results # destination - + - name: "Set up JDK (for Allure)" uses: actions/setup-java@v4 with: @@ -92,18 +92,49 @@ jobs: python-coverage-report: name: "Generate Coverage Report (Aggregated Suites)" runs-on: ubuntu-latest - + steps: - uses: actions/checkout@v4 - - - name: "Download and merge Python coverage artifacts" + + - name: "[🐍] Download and merge 📦 artifacts" uses: actions/download-artifact@v4 with: - pattern: upload-python-coverage-report-* + pattern: python-dist-ubuntu-latest* merge-multiple: true - path: pycov # destination + path: dist # destination + + - name: "[🐍] Unpack Source 📦" + run: | + tar xvfz dist/klayout_pex*.tar.gz --strip-components=1 + + - name: "[🐍] Download and merge coverage artifacts" + uses: actions/download-artifact@v4 + with: + pattern: python-*-tests-coverage-report-ubuntu-* + merge-multiple: false + path: pycov-databases # destination + + - name: "Display structure of downloaded files" + run: ls -R pycov-databases + + - name: "[🐍] Setup python" + id: setup-python + uses: actions/setup-python@v5 + + - name: "[🐍] Install dependencies" + run: | + python -m pip install --upgrade pip + pip install coverage - - name: Display structure of downloaded files + - name: "[🐍] Combine coverage reports" + run: | + coverage combine pycov-databases/*/python-coverage.sqlite + + - name: "[🐍] Create coverage report" + run: | + coverage html --directory pycov --data-file=.coverage + + - name: "Display structure of coverage report" run: ls -R pycov - name: "Publish test report" diff --git a/_run_tests.sh b/_run_tests.sh new file mode 100644 index 00000000..aff11aab --- /dev/null +++ b/_run_tests.sh @@ -0,0 +1,62 @@ +#! /bin/bash +## +## -------------------------------------------------------------------------------- +## SPDX-FileCopyrightText: 2024 Martin Jan Köhler and Harald Pretl +## Johannes Kepler University, Institute for Integrated Circuits. +## +## This file is part of KPEX +## (see https://github.com/martinjankoehler/klayout-pex). +## +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## SPDX-License-Identifier: GPL-3.0-or-later +## -------------------------------------------------------------------------------- +## + +# Usage: run_tests +# Example: run_tests "not slow" +function run_tests() { + PATTERN="$1" + + DIR=$(dirname -- $(realpath ${BASH_SOURCE})) + + mkdir -p "$DIR"/build + + ALLURE_RESULTS_PATH="$DIR/build/allure-results" + ALLURE_REPORT_PATH="$DIR/build/allure-report" + COVERAGE_PATH="$DIR/build/coverage-results" + + rm -rf "$ALLURE_RESULTS_PATH" + rm -rf "$ALLURE_REPORT_PATH" + rm -rf "$COVERAGE_PATH" + + set -x + set -e + + poetry run coverage run -m pytest -m "$PATTERN" \ + --alluredir "$ALLURE_RESULTS_PATH" \ + --color no + + poetry run coverage html --directory "$COVERAGE_PATH" + + allure generate \ + --single-file "$ALLURE_RESULTS_PATH" \ + --output "$ALLURE_REPORT_PATH" \ + --clean + + if [[ -z "$RUNNER_OS" ]] && [[ -d "/Applications/Safari.app" ]] + then + open -a Safari "$ALLURE_REPORT_PATH"/index.html + open -a Safari "$COVERAGE_PATH"/index.html + fi +} diff --git a/klayout_pex/kpex_cli.py b/klayout_pex/kpex_cli.py index b2f8bc81..f5b45dcd 100755 --- a/klayout_pex/kpex_cli.py +++ b/klayout_pex/kpex_cli.py @@ -169,6 +169,8 @@ def parse_args(arg_list: List[str] = None) -> argparse.Namespace: group_pex_input.add_argument("--cache-lvs", dest="cache_lvs", type=true_or_false, default=True, help="Used cached LVSDB (for given input GDS) (default is %(default)s)") + group_pex_input.add_argument("--cache-dir", dest="cache_dir_path", default=None, + help="Path for cached LVSDB (default is .kpex_cache within --out_dir)") group_pex_options = main_parser.add_argument_group("Parasitic Extraction Options") group_pex_options.add_argument("--blackbox", dest="blackbox_devices", @@ -402,6 +404,9 @@ def input_file_stem(path: str): subproc(f"\nPlease activate one or more engines using the arguments:\n{engine_help}") found_errors = True + if args.cache_dir_path is None: + args.cache_dir_path = os.path.join(args.output_dir_base_path, '.kpex_cache') + if found_errors: raise ArgumentValidationError("Argument validation failed") @@ -683,13 +688,22 @@ def create_lvsdb(self, args: argparse.Namespace) -> kdb.LayoutVsSchematic: case InputMode.GDS: lvs_log_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_lvs.log") lvsdb_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}.lvsdb.gz") + lvsdb_cache_path = os.path.join(args.cache_dir_path, args.pdk, + os.path.splitroot(os.path.abspath(args.gds_path))[-1], + f"{args.effective_cell_name}.lvsdb.gz") lvs_needed = True - if os.path.exists(lvsdb_path) and args.cache_lvs: - if self.modification_date(lvsdb_path) > self.modification_date(args.gds_path): - warning(f"Reusing cached LVSDB") - subproc(lvsdb_path) + if args.cache_lvs: + if not os.path.exists(lvsdb_cache_path): + info(f"Cache miss: extracted LVSDB does not exist") + subproc(lvsdb_cache_path) + elif self.modification_date(lvsdb_cache_path) <= self.modification_date(args.gds_path): + info(f"Cache miss: extracted LVSDB is older than the input GDS") + subproc(lvsdb_cache_path) + else: + warning(f"Cache hit: Reusing cached LVSDB") + subproc(lvsdb_cache_path) lvs_needed = False if lvs_needed: @@ -700,6 +714,12 @@ def create_lvsdb(self, args: argparse.Namespace) -> kdb.LayoutVsSchematic: schematic_path=args.effective_schematic_path, log_path=lvs_log_path, lvsdb_path=lvsdb_path) + if args.cache_lvs: + cache_dir_path = os.path.dirname(lvsdb_cache_path) + if not os.path.exists(cache_dir_path): + os.makedirs(cache_dir_path, exist_ok=True) + shutil.copy(lvsdb_path, lvsdb_cache_path) + lvsdb.read(lvsdb_path) return lvsdb diff --git a/klayout_pex/magic/magic_runner.py b/klayout_pex/magic/magic_runner.py index 3bc4cf7b..d2927eea 100644 --- a/klayout_pex/magic/magic_runner.py +++ b/klayout_pex/magic/magic_runner.py @@ -57,6 +57,7 @@ def prepare_magic_script(gds_path: str, output_netlist_path = os.path.abspath(output_netlist_path) halo_scale = 200.0 + halo_decl = '' if halo is None else f"\nextract halo {round(halo * halo_scale)}" script: str = "" match pex_mode: @@ -72,7 +73,7 @@ def prepare_magic_script(gds_path: str, cellname delete {cell_name} -noprompt cellname rename {cell_name}_flat {cell_name} select top cell -extract path {run_dir_path}{'' if halo is None else f"\nextract halo {round(halo * halo_scale)}"} +extract path {run_dir_path}{halo_decl} extract all ext2spice cthresh {c_threshold} ext2spice format ngspice @@ -90,7 +91,7 @@ def prepare_magic_script(gds_path: str, cellname delete {cell_name} -noprompt cellname rename {cell_name}_flat {cell_name} select top cell -extract path {run_dir_path}{'' if halo is None else f"\nextract halo {round(halo * halo_scale)}"} +extract path {run_dir_path}{halo_decl} extract do resistance extract all ext2sim labels on diff --git a/run_integration_tests.sh b/run_integration_tests.sh new file mode 100755 index 00000000..4a7489f9 --- /dev/null +++ b/run_integration_tests.sh @@ -0,0 +1,28 @@ +#! /bin/bash +## +## -------------------------------------------------------------------------------- +## SPDX-FileCopyrightText: 2024 Martin Jan Köhler and Harald Pretl +## Johannes Kepler University, Institute for Integrated Circuits. +## +## This file is part of KPEX +## (see https://github.com/martinjankoehler/klayout-pex). +## +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## SPDX-License-Identifier: GPL-3.0-or-later +## -------------------------------------------------------------------------------- +## + +source _run_tests.sh + +run_tests "slow" diff --git a/run_tests.sh b/run_tests.sh old mode 100755 new mode 100644 index 63fdbc5c..82494a2e --- a/run_tests.sh +++ b/run_tests.sh @@ -4,7 +4,7 @@ ## SPDX-FileCopyrightText: 2024 Martin Jan Köhler and Harald Pretl ## Johannes Kepler University, Institute for Integrated Circuits. ## -## This file is part of KPEX +## This file is part of KPEX ## (see https://github.com/martinjankoehler/klayout-pex). ## ## This program is free software: you can redistribute it and/or modify @@ -23,35 +23,6 @@ ## -------------------------------------------------------------------------------- ## -DIR=$(dirname -- $(realpath ${BASH_SOURCE})) - -mkdir -p "$DIR"/build - -ALLURE_RESULTS_PATH="$DIR/build/allure-results" -ALLURE_REPORT_PATH="$DIR/build/allure-report" -COVERAGE_PATH="$DIR/build/coverage-results" - -set -x -set -e - -rm -rf "$ALLURE_RESULTS_PATH" -rm -rf "$ALLURE_REPORT_PATH" -rm -rf "$COVERAGE_PATH" - -poetry run coverage run -m pytest -m "not slow" \ - --alluredir "$ALLURE_RESULTS_PATH" \ - --color no - -poetry run coverage html --directory "$COVERAGE_PATH" - -allure generate \ - --single-file "$ALLURE_RESULTS_PATH" \ - --output "$ALLURE_REPORT_PATH" \ - --clean - -if [[ -z "$RUNNER_OS" ]] && [[ -d "/Applications/Safari.app" ]] -then - open -a Safari "$ALLURE_REPORT_PATH"/index.html - open -a Safari "$COVERAGE_PATH"/index.html -fi +source _run_tests.sh +run_tests "slow or not slow" diff --git a/run_unit_tests.sh b/run_unit_tests.sh new file mode 100755 index 00000000..032aea38 --- /dev/null +++ b/run_unit_tests.sh @@ -0,0 +1,28 @@ +#! /bin/bash +## +## -------------------------------------------------------------------------------- +## SPDX-FileCopyrightText: 2024 Martin Jan Köhler and Harald Pretl +## Johannes Kepler University, Institute for Integrated Circuits. +## +## This file is part of KPEX +## (see https://github.com/martinjankoehler/klayout-pex). +## +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## SPDX-License-Identifier: GPL-3.0-or-later +## -------------------------------------------------------------------------------- +## + +source _run_tests.sh + +run_tests "not slow" diff --git a/tests/rcx25/rcx25_test.py b/tests/rcx25/rcx25_test.py index b27c8ec8..d301ceca 100644 --- a/tests/rcx25/rcx25_test.py +++ b/tests/rcx25/rcx25_test.py @@ -87,12 +87,10 @@ def _run_rcx25d_single_cell(*path_components) -> Tuple[CellExtractionResults, CS preview_png_path = tempfile.mktemp(prefix=f"layout_preview_", suffix=".png") _save_layout_preview(gds_path, preview_png_path) - tech_json_path = os.path.realpath(os.path.join(__file__, '..', '..', '..', - 'build', 'sky130A_tech.pb.json')) output_dir_path = os.path.realpath(os.path.join(__file__, '..', '..', '..', 'output_sky130A')) cli = KpexCLI() cli.main(['main', - '--tech', tech_json_path, + '--pdk', 'sky130A', '--gds', gds_path, '--out_dir', output_dir_path, '--2.5D', 'y'])