Coverage for apio/managers/xilinx_chipdb.py: 82%
69 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 03:53 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 03:53 +0000
1"""A manager class for to dispatch the Apio SCONS targets."""
3# -*- coding: utf-8 -*-
4# -- This file is part of the Apio project
5# -- (C) 2016-2019 FPGAwars
6# -- Author Jesús Arroyo
7# -- License GPLv2
10import json
11from pathlib import Path
12from typing import Any
13from apio.common.debug_util import is_debug
14from apio.common.apio_console import cout, fatal_error
15from apio.common.apio_styles import INFO, EMPH1
16from apio.apio_context import ApioContext
17from apio.managers.downloader import FileDownloader
18from apio.utils import util
20# -- NOTE: We perform verification using sha256 only, without verifying also
21# -- its size. According to source on the internet, if using sha256, there
22# -- is no need to check also the size.
24# -- The name of the Xilinx parts index file at the root of the openxc7
25# -- package.
26PARTS_INDEX_FILE_NAME = "XILINX-PARTS-INDEX.json"
29# -- The expected version of the parts index schema, stored in the
30# -- top-level "schema" field. Apio does not choose an engine at run
31# -- time: this number is the contract with the installed package.
32# --
33# -- Schema 6 is the current (legacy) nextpnr-xilinx. The command line stays
34# -- `nextpnr-xilinx --chipdb <file> --xdc ...`, one chipdb file per
35# -- base part, and the entry fields are those of schema 5. There is
36# -- no "pnr" field.
37# --
38# -- Schema 7 will be the new nextpnr-xilinx, installed under the same
39# -- name, one chipdb file per die, and the command line
40# -- `--device <part> --chipdb <file> -o xdc=... -o fasm=... --report`.
41# -- That lands in a later commit, which raises this constant, changes
42# -- the command line, and moves the apio-1.7.x.jsonc tag with it.
43EXPECTED_SCHEMA_VERSION = 6
46def _parts_index_path(apio_ctx: ApioContext) -> Path:
47 """Path of the parts index inside the installed openxc7 package."""
48 return apio_ctx.get_package_dir("openxc7") / PARTS_INDEX_FILE_NAME
51def read_xilinx_parts_index(apio_ctx: ApioContext) -> dict[str, Any]:
52 """Open the openxc7 parts index and check that its schema is the one
53 this apio reads."""
55 parts_index_path = _parts_index_path(apio_ctx)
56 with open(parts_index_path, encoding="utf-8") as f:
57 json_data = json.load(f)
59 # -- Verify that the index has a schema version we understand.
60 actual_schema_version = (
61 json_data["schema"] if "schema" in json_data else "Unknown"
62 )
63 if actual_schema_version != EXPECTED_SCHEMA_VERSION:
64 fatal_error(
65 f"Unexpected schema version {actual_schema_version}, "
66 f"expected {EXPECTED_SCHEMA_VERSION}"
67 )
68 return json_data
71def chipdb_file_on_demand(
72 apio_ctx: ApioContext,
73 yosys_part: str,
74) -> Path:
75 """Given a xilinx yosys-part, it fetches the chipdb file on-demand and
76 returns its path."""
78 # pylint: disable=too-many-locals
80 # -- Get the local chipdb dir in the installed openxc7 package.
81 # -- The path of this dir is defined in packages.jsonc.
82 openxc7_define_consts = apio_ctx.all_packages["openxc7"]["env"][
83 "define-consts"
84 ]
85 assert "CHIPDB_DIR" in openxc7_define_consts, openxc7_define_consts
86 chipdb_dir = Path(openxc7_define_consts["CHIPDB_DIR"])
88 # -- Delete all *.tgz files in the chipdb dir, just in case they were
89 # -- not deleted after extracting the chipdb file.
90 for path in chipdb_dir.glob("*.tgz"): 90 ↛ 91line 90 didn't jump to line 91 because the loop on line 90 never started
91 cout(f"Deleting a leftover chipdb archive {path.name}", style=INFO)
92 path.unlink()
94 # -- Read the xilinx parts index and check its schema.
95 json_data = read_xilinx_parts_index(apio_ctx)
96 parts_index_path = _parts_index_path(apio_ctx)
98 # -- Lookup part information using yosys_part as a key. A missing
99 # -- part is a fatal error that names it. There is no default.
100 parts = json_data["parts"]
101 if yosys_part not in parts:
102 fatal_error(
103 f"No such xilinx yosys part {yosys_part}",
104 info=f"See {str(parts_index_path)} for the list of "
105 "supported xilinx parts.",
106 )
108 part_info = parts[yosys_part]
110 # -- Dump for debugging.
111 if is_debug(1): 111 ↛ 112line 111 didn't jump to line 112 because the condition on line 111 was never true
112 cout("Part info:", style=EMPH1)
113 cout(json.dumps(part_info, indent=2))
115 # -- If a chipdb was not generated by the opensc7 package builder
116 # -- there is nothing we can do.
117 if not part_info["generated"]: 117 ↛ 118line 117 didn't jump to line 118 because the condition on line 117 was never true
118 fatal_error(
119 f"Yosys xilinx part {yosys_part} exists but not generated",
120 info="Ask the Apio team to generate it.",
121 )
123 # -- Part found and it was generated by the openxc7 package builder.
124 # -- Extract local chipdb file and release asset information.
126 chipdb_file_name = part_info["chipdb"]
127 release_asset_name = part_info["asset"]
129 expected_chipdb_sha256 = part_info["chipdb-sha256"]
130 expected_release_asset_sha256 = part_info["asset-sha256"]
132 chipdb_file_path = chipdb_dir / chipdb_file_name
134 # -- If the chipdb file already exists in the chipdb dir and it
135 # -- the expected sha256 checksum then we are good.
136 if chipdb_file_path.exists():
137 cout(f"Chipdb file found: {chipdb_file_name}")
138 actual_asset_sha256 = util.compute_file_sha256(chipdb_file_path)
139 if actual_asset_sha256 == expected_chipdb_sha256: 139 ↛ 144line 139 didn't jump to line 144 because the condition on line 139 was always true
140 return chipdb_file_path
142 # -- Here when the chipdb sha256 doesn't match. We delete it and
143 # -- continue as if it did not exist.
144 cout(
145 "Existing chipdb file has an unexpected checksum: "
146 f"{actual_asset_sha256}",
147 style=INFO,
148 )
149 cout(f"Deleting old chipdb file {chipdb_file_path.name}")
150 chipdb_file_path.unlink()
152 # -- Here the chipdb file doesn't exist so we need to fetch it from the
153 # -- same github release from which the openxc7 package was downloaded.
154 # -- This information is stored in install-packages.json and is cached
155 # -- in-memory.
156 package_install_info = apio_ctx.package_manager.installed_packages[
157 "openxc7"
158 ]
159 openxc7_package_url = package_install_info["loaded-from"]
161 # -- Construct the chipdb release asset url by replacing the file name
162 # -- in the package url.
163 asset_url = (
164 openxc7_package_url.rsplit("/", 1)[0] + "/" + release_asset_name
165 )
167 # -- Fetch the chipdb release asset.
168 cout(f"Fetching {release_asset_name}")
169 downloader = FileDownloader(asset_url, chipdb_dir)
170 downloader.download()
172 # -- Check that the sha256 of the downloaded asset matches the expected
173 # -- shat256 we found in the part index.
174 asset_local_path = chipdb_dir / release_asset_name
175 actual_asset_sha256 = util.compute_file_sha256(asset_local_path)
176 if actual_asset_sha256 != expected_release_asset_sha256: 176 ↛ 177line 176 didn't jump to line 177 because the condition on line 176 was never true
177 fatal_error(
178 f"Downloaded chipdb asset has an unexpected checksum: "
179 f"{actual_asset_sha256}",
180 f"Expected {expected_release_asset_sha256}",
181 )
183 # -- Asset file was verified OK. Unpack it to extract the chipdb file.
184 util.unpack_tgz(asset_local_path, dest_dir=chipdb_dir)
186 actual_asset_sha256 = util.compute_file_sha256(chipdb_file_path)
187 if actual_asset_sha256 != expected_chipdb_sha256: 187 ↛ 188line 187 didn't jump to line 188 because the condition on line 187 was never true
188 fatal_error(
189 f"Chipdb file has an expected checksum {actual_asset_sha256}",
190 f"Expected {expected_chipdb_sha256}",
191 )
193 # -- Here when the extracted chipdb file is ok.
194 cout("Chipdb checksum verified.")
196 # -- Delete the downloaded asset, we don't need it anymore.
197 asset_local_path.unlink()
199 # -- All done, return with the chipdb file path.
200 return chipdb_file_path