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

1"""A manager class for to dispatch the Apio SCONS targets.""" 

2 

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 

8 

9 

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 

19 

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. 

23 

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" 

27 

28 

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 

44 

45 

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 

49 

50 

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.""" 

54 

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) 

58 

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 

69 

70 

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.""" 

77 

78 # pylint: disable=too-many-locals 

79 

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"]) 

87 

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() 

93 

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) 

97 

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 ) 

107 

108 part_info = parts[yosys_part] 

109 

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)) 

114 

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 ) 

122 

123 # -- Part found and it was generated by the openxc7 package builder. 

124 # -- Extract local chipdb file and release asset information. 

125 

126 chipdb_file_name = part_info["chipdb"] 

127 release_asset_name = part_info["asset"] 

128 

129 expected_chipdb_sha256 = part_info["chipdb-sha256"] 

130 expected_release_asset_sha256 = part_info["asset-sha256"] 

131 

132 chipdb_file_path = chipdb_dir / chipdb_file_name 

133 

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 

141 

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() 

151 

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"] 

160 

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 ) 

166 

167 # -- Fetch the chipdb release asset. 

168 cout(f"Fetching {release_asset_name}") 

169 downloader = FileDownloader(asset_url, chipdb_dir) 

170 downloader.download() 

171 

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 ) 

182 

183 # -- Asset file was verified OK. Unpack it to extract the chipdb file. 

184 util.unpack_tgz(asset_local_path, dest_dir=chipdb_dir) 

185 

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 ) 

192 

193 # -- Here when the extracted chipdb file is ok. 

194 cout("Chipdb checksum verified.") 

195 

196 # -- Delete the downloaded asset, we don't need it anymore. 

197 asset_local_path.unlink() 

198 

199 # -- All done, return with the chipdb file path. 

200 return chipdb_file_path