Coverage for apio/scons/plugin_xilinx.py: 100%

67 statements  

« prev     ^ index     » next       coverage.py v7.17.2, created at 2026-11-07 05:08 +0000

1# This file is part of the Apio project 

2# (C) 2016-present FPGAwars 

3# Project authors: 

4# * Jesús Arroyo (Jesus89) (2016-2019) 

5# * Juan Gonzalez (obijuan) (2019-2024) 

6# * The Apio Team (2024-present) 

7# License: GPLv2 

8 

9"""Apio scons plugin for the xilinx architecture.""" 

10 

11# pylint: disable=duplicate-code 

12 

13from pathlib import Path 

14from typing_extensions import override 

15from SCons.Script import Builder 

16from SCons.Builder import BuilderBase, CompositeBuilder 

17from apio.common.common_util import SRC_SUFFIXES 

18from apio.scons.apio_env import ApioEnv 

19from apio.scons.plugin_base import PluginBase, ArchPluginInfo 

20from apio.scons.plugin_util import ( 

21 verilator_lint_action, 

22 has_testbench_name, 

23 announce_testbench_action, 

24 source_files_issue_scanner_action, 

25 iverilog_action, 

26 make_verilator_config_builder, 

27 get_define_flags, 

28) 

29 

30 

31class PluginXilinx(PluginBase): 

32 """Apio scons plugin for the Xilinx architecture.""" 

33 

34 def __init__(self, apio_env: ApioEnv): 

35 # -- Call parent constructor. 

36 super().__init__(apio_env) 

37 

38 # -- Cache values. 

39 yosys_path = Path(apio_env.params.environment.yosys_path) 

40 self.yosys_lib_dir = yosys_path / "xilinx" 

41 self.sim_lib_files = [yosys_path / "xilinx" / "cells_sim.v"] 

42 # -- For lint, also pass the black-box declarations of the primitives 

43 # -- that have no simulation model in cells_sim.v (PLLE2_*, MMCME2_*, 

44 # -- etc.); without them verilator fails with MODMISSING on any design 

45 # -- that instantiates one. The two files declare disjoint modules. 

46 self.lint_lib_files = self.sim_lib_files + [ 

47 yosys_path / "xilinx" / "cells_xtra.v" 

48 ] 

49 

50 @override 

51 def plugin_info(self) -> ArchPluginInfo: 

52 """Return plugin specific parameters.""" 

53 return ArchPluginInfo( 

54 constrains_file_suffix=".xdc", 

55 pnr_file_suffix=".frames", 

56 bitstream_file_suffix=".bit", 

57 ) 

58 

59 @override 

60 def make_synth_builder(self) -> BuilderBase | CompositeBuilder: 

61 """Creates and returns the synth builder.""" 

62 

63 # -- Keep short references. 

64 apio_env = self.apio_env 

65 params = apio_env.params 

66 xilinx_params = params.fpga_definition.xilinx_params 

67 

68 # -- The yosys synth builder. 

69 return Builder( 

70 action=( 

71 # -- yosys-extra-options goes INSIDE the synth_xilinx command 

72 # -- (like the other architectures do with synth_ice40/ecp5/ 

73 # -- gowin), so synth flags such as -nodsp work; it used to 

74 # -- land after write_json, where it did nothing useful. 

75 'yosys -p "synth_xilinx -arch {0} -top {1} {2}; ' 

76 'write_json $TARGET " ' 

77 "{3} -DSYNTHESIZE {4} $SOURCES" 

78 ).format( 

79 xilinx_params.yosys_arch, 

80 params.apio_env_params.top_module, 

81 " ".join(params.apio_env_params.yosys_extra_options), 

82 "" if params.verbosity.all or params.verbosity.synth else "-q", 

83 get_define_flags(apio_env), 

84 ), 

85 suffix=".json", 

86 source_scanner=self.verilog_src_scanner, 

87 src_suffix=SRC_SUFFIXES, 

88 ) 

89 

90 @override 

91 def make_pnr_builder(self) -> BuilderBase | CompositeBuilder: 

92 """Creates and returns the pnr builder.""" 

93 

94 # -- Keep short references. 

95 apio_env = self.apio_env 

96 params = apio_env.params 

97 xilinx_params = params.fpga_definition.xilinx_params 

98 

99 # -- We use an emitter to add to the builder a second output file. 

100 def emitter(target, source, env): 

101 _ = env # Unused 

102 target.append(apio_env.target + ".pnr") 

103 return target, source 

104 

105 quiet = "" if params.verbosity.all or params.verbosity.pnr else "-q" 

106 extra_options = " ".join(params.apio_env_params.nextpnr_extra_options) 

107 

108 action = ( 

109 "nextpnr-xilinx --device {0} -o xdc={1} " 

110 "-o fasm=$TARGET --json $SOURCE --report {2} {3} {4}" 

111 ).format( 

112 xilinx_params.nextpnr_device, 

113 self.constrain_file(), 

114 apio_env.target + ".pnr", 

115 quiet, 

116 extra_options, 

117 ) 

118 

119 # -- Create the builder 

120 return Builder( 

121 action=action, 

122 src_suffix=".json", 

123 suffix=".fasm", 

124 emitter=emitter, 

125 ) 

126 

127 @override 

128 def make_bitstream_builder(self) -> BuilderBase | CompositeBuilder: 

129 """Creates and returns the bitstream builder.""" 

130 

131 # -- Keep short references. 

132 apio_env = self.apio_env 

133 params = apio_env.params 

134 xilinx_params = params.fpga_definition.xilinx_params 

135 

136 prjxray_db = Path(apio_env.params.environment.xilinx_prjxray_db_path) 

137 prjxray_db = prjxray_db / xilinx_params.yosys_family 

138 part_file = prjxray_db / xilinx_params.nextpnr_device / "part.yaml" 

139 

140 # -- Intermediate .frames file path expression. When resolved, it's 

141 # -- the same as target file but with the ".frames" extension. 

142 frames_file_macro = "${TARGET.base}.frames" 

143 

144 return Builder( 

145 action=[ 

146 # -- STEP1: Converts .fasm to .frames 

147 "fasm2frames --part {0} --db-root {1} " 

148 " $SOURCE > {2} ".format( 

149 xilinx_params.nextpnr_device, prjxray_db, frames_file_macro 

150 ), 

151 # -- STEP2 Converts .frames to .bit 

152 "xc7frames2bit --part_file {0} --part_name {1} " 

153 "--frm_file {2} --output_file $TARGET".format( 

154 part_file, xilinx_params.nextpnr_device, frames_file_macro 

155 ), 

156 ], 

157 src_suffix=".fasm", 

158 suffix=".bit", 

159 ) 

160 

161 @override 

162 def make_testbench_compile_builder(self) -> BuilderBase | CompositeBuilder: 

163 """Creates and returns the testbench compile builder.""" 

164 

165 # -- Keep short references. 

166 apio_env = self.apio_env 

167 params = apio_env.params 

168 

169 # -- Sanity checks 

170 assert apio_env.targeting_one_of("sim", "test") 

171 assert params.target.HasField("sim") or params.target.HasField("test") 

172 

173 # -- We use a generator because we need a different action 

174 # -- string for sim and test. 

175 def action_generator(target, source, env, for_signature): 

176 _ = (source, env, for_signature) # Unused 

177 # Extract testbench file name from the target. 

178 testbench_file = str(target[0]) 

179 assert has_testbench_name(testbench_file), testbench_file 

180 

181 # Construct the actions list. 

182 action = [ 

183 # -- Print a testbench title. 

184 announce_testbench_action(), 

185 # -- Scan source files for issues. 

186 source_files_issue_scanner_action(), 

187 # -- Perform the actual test or sim compilation. 

188 iverilog_action( 

189 apio_env, 

190 verbose=params.verbosity.all, 

191 is_interactive=apio_env.targeting_one_of("sim"), 

192 lib_dirs=[self.yosys_lib_dir], 

193 lib_files=self.sim_lib_files, 

194 ), 

195 ] 

196 return action 

197 

198 # -- The testbench compiler builder. 

199 return Builder( 

200 # -- Dynamic action string generator. 

201 generator=action_generator, 

202 source_scanner=self.verilog_src_scanner, 

203 src_suffix=SRC_SUFFIXES, 

204 suffix=".out", 

205 ) 

206 

207 @override 

208 def make_lint_config_builder(self) -> BuilderBase: 

209 """Creates and returns the lint config builder.""" 

210 

211 # -- Sanity checks 

212 assert self.apio_env.targeting_one_of("lint") 

213 

214 # -- Make the builder. 

215 # -- See https://verilator.org/guide/latest/warnings.html 

216 return make_verilator_config_builder( 

217 self.yosys_lib_dir, 

218 rules_to_suppress=[ 

219 "SPECIFYIGN", 

220 ], 

221 ) 

222 

223 @override 

224 def make_lint_builder(self) -> BuilderBase | CompositeBuilder: 

225 """Creates and returns the lint builder.""" 

226 

227 return Builder( 

228 action=verilator_lint_action( 

229 self.apio_env, 

230 lib_dirs=[self.yosys_lib_dir], 

231 lib_files=self.lint_lib_files, 

232 ), 

233 source_scanner=self.verilog_src_scanner, 

234 src_suffix=SRC_SUFFIXES, 

235 )