Coverage for apio/scons/plugin_xilinx.py: 100%
77 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:55 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:55 +0000
1# -*- coding: utf-8 -*-
2# -- This file is part of the Apio project
3# -- (C) 2016-2018 FPGAwars
4# -- Author Jesús Arroyo
5# -- License GPLv2
6# -- Derived from:
7# ---- Platformio project
8# ---- (C) 2014-2016 Ivan Kravets <me@ikravets.com>
9# ---- License Apache v2
11"""Apio scons plugin for the xilinx architecture."""
13# pylint: disable=duplicate-code
15import os
16from pathlib import Path
17from SCons.Script import Builder, Action
18from SCons.Builder import BuilderBase, CompositeBuilder
19from apio.common.common_util import SRC_SUFFIXES
20from apio.scons.apio_env import ApioEnv
21from apio.scons.plugin_base import PluginBase, ArchPluginInfo
22from apio.scons.plugin_util import (
23 verilator_lint_action,
24 has_testbench_name,
25 announce_testbench_action,
26 source_files_issue_scanner_action,
27 iverilog_action,
28 basename,
29 make_verilator_config_builder,
30 get_define_flags,
31)
34class PluginXilinx(PluginBase):
35 """Apio scons plugin for the Xilinx architecture."""
37 def __init__(self, apio_env: ApioEnv):
38 # -- Call parent constructor.
39 super().__init__(apio_env)
41 # -- Cache values.
42 yosys_path = Path(apio_env.params.environment.yosys_path)
43 self.yosys_lib_dir = yosys_path / "xilinx"
44 self.sim_lib_files = [yosys_path / "xilinx" / "cells_sim.v"]
45 # -- For lint, also pass the black-box declarations of the primitives
46 # -- that have no simulation model in cells_sim.v (PLLE2_*, MMCME2_*,
47 # -- etc.); without them verilator fails with MODMISSING on any design
48 # -- that instantiates one. The two files declare disjoint modules.
49 self.lint_lib_files = self.sim_lib_files + [
50 yosys_path / "xilinx" / "cells_xtra.v"
51 ]
53 def plugin_info(self) -> ArchPluginInfo:
54 """Return plugin specific parameters."""
55 return ArchPluginInfo(
56 constrains_file_suffix=".xdc",
57 pnr_file_suffix=".frames",
58 bitstream_file_suffix=".bit",
59 )
61 # @overrides
62 def synth_builder(self) -> BuilderBase | CompositeBuilder:
63 """Creates and returns the synth builder."""
65 # -- Keep short references.
66 apio_env = self.apio_env
67 params = apio_env.params
68 xilinx_params = params.fpga_info.xilinx_params
70 # -- The yosys synth builder.
71 return Builder(
72 action=(
73 # -- yosys-extra-options goes INSIDE the synth_xilinx command
74 # -- (like the other architectures do with synth_ice40/ecp5/
75 # -- gowin), so synth flags such as -nodsp work; it used to
76 # -- land after write_json, where it did nothing useful.
77 'yosys -p "synth_xilinx -arch {0} -top {1} {2}; '
78 'write_json $TARGET " '
79 "{3} -DSYNTHESIZE {4} $SOURCES"
80 ).format(
81 xilinx_params.yosys_arch,
82 params.apio_env_params.top_module,
83 " ".join(params.apio_env_params.yosys_extra_options),
84 "" if params.verbosity.all or params.verbosity.synth else "-q",
85 get_define_flags(apio_env),
86 ),
87 suffix=".json",
88 src_suffix=SRC_SUFFIXES,
89 source_scanner=self.verilog_src_scanner,
90 )
92 # @overrides
93 def pnr_builder(self) -> BuilderBase | CompositeBuilder:
94 """Creates and returns the pnr builder."""
96 # -- Keep short references.
97 apio_env = self.apio_env
98 params = apio_env.params
99 xilinx_params = params.fpga_info.xilinx_params
101 # -- We use an emmiter to add to the builder a second output file.
102 def emitter(target, source, env):
103 _ = env # Unused
104 target.append(apio_env.target + ".pnr")
105 return target, source
107 # -- Get params.
108 chipdb_dir = Path(apio_env.params.environment.xilinx_chipdb_path)
109 package_name = xilinx_params.package
111 # -- The chipdb is one to one with the package name.
112 chipdb_file_path = chipdb_dir / f"{package_name}.bin"
114 # -- Find the path of the report_xilinx utility that is used to
115 # -- generate the report file hardware.pnr. It's embedded in the Apio
116 # -- source tree.
118 # -- Get the full path of this file (plugin_xilinx.py)
119 current_python_file = Path(__file__)
121 # -- The parent folder is the apio root folder
122 apio_root = current_python_file.parent.parent
124 # -- Add the report_xilinx.py folder to the path. As of Aug 2026,
125 # -- nextpnr-xilinx doesn't support the --report flag so we workaround
126 # -- by running a report generation script on a --post-route trigger.
127 # --
128 # -- TODO: Clean it up once nextpnr-xilinx gets the --report flag.
129 report_py = apio_root / "scons/report_xilinx.py"
131 report_file = apio_env.target + ".pnr"
133 # -- Print action (platform independent)
134 def print_report_var(target, source, env):
135 _ = (target, source, env) # Unused
136 print(f"[Setting APIO_XILINX_REPORT_FILE={report_file}]")
137 return 0 # success
139 # -- Create the builder
140 return Builder(
141 action=[
142 # -- Action 1: Inform user about the env setting.
143 Action(
144 print_report_var,
145 strfunction=lambda target, source, env: "", # hide
146 ),
147 # -- Action 2: The actual nextpnr.
148 (
149 "nextpnr-xilinx --chipdb {0} --xdc {1} --json $SOURCE "
150 "--fasm $TARGET "
151 "--post-route {2} "
152 "{3} "
153 "{4}"
154 ).format(
155 chipdb_file_path,
156 self.constrain_file(),
157 report_py,
158 # -- Honor --verbose-pnr like the other archs (ice40/ecp5/
159 # -- gowin), which show the full nextpnr log (fmax,
160 # -- critical path). The xilinx plugin used to hard-code
161 # -- -q.
162 (
163 ""
164 if params.verbosity.all or params.verbosity.pnr
165 else "-q"
166 ),
167 " ".join(params.apio_env_params.nextpnr_extra_options),
168 ),
169 ],
170 suffix=".fasm",
171 src_suffix=".json",
172 emitter=emitter,
173 # -- Pass to the report_xiling.py script the path of its output
174 # -- file.
175 ENV={
176 **os.environ,
177 "APIO_XILINX_REPORT_FILE": report_file,
178 },
179 )
181 # @overrides
182 def bitstream_pre_builder(self) -> BuilderBase | CompositeBuilder:
183 """Creates and returns the pre-bitstream builder."""
185 # -- Keep short references.
186 apio_env = self.apio_env
187 params = apio_env.params
188 xilinx_params = params.fpga_info.xilinx_params
190 part1 = f"{xilinx_params.package}-{xilinx_params.speed}"
191 prjxray_db = Path(apio_env.params.environment.xilinx_prjxray_db_path)
192 prjxray_db = prjxray_db / xilinx_params.family
194 return Builder(
195 action="fasm2frames --part {0} --db-root {1} "
196 " $SOURCE > $TARGET ".format(
197 part1,
198 prjxray_db,
199 ),
200 suffix=".frames",
201 src_suffix=".fasm",
202 )
204 # @overrides
205 def bitstream_builder(self) -> BuilderBase | CompositeBuilder:
206 """Creates and returns the bitstream builder."""
208 # -- Keep short references.
209 apio_env = self.apio_env
210 params = apio_env.params
211 xilinx_params = params.fpga_info.xilinx_params
212 part1 = f"{xilinx_params.package}-{xilinx_params.speed}"
214 prjxray_db = Path(apio_env.params.environment.xilinx_prjxray_db_path)
215 prjxray_db = prjxray_db / xilinx_params.family
216 part_file = prjxray_db / part1 / "part.yaml"
218 return Builder(
219 action="xc7frames2bit --part_file {0} --part_name {1} "
220 "--frm_file "
221 "$SOURCE --output_file $TARGET".format(
222 part_file,
223 part1,
224 ),
225 suffix=".bit",
226 src_suffix=".frames",
227 )
229 # @overrides
230 def testbench_compile_builder(self) -> BuilderBase | CompositeBuilder:
231 """Creates and returns the testbench compile builder."""
233 # -- Keep short references.
234 apio_env = self.apio_env
235 params = apio_env.params
237 # -- Sanity checks
238 assert apio_env.targeting_one_of("sim", "test")
239 assert params.target.HasField("sim") or params.target.HasField("test")
241 # -- We use a generator because we need a different action
242 # -- string for sim and test.
243 def action_generator(target, source, env, for_signature):
244 _ = (source, env, for_signature) # Unused
245 # Extract testbench name from target file name.
246 testbench_file = str(target[0])
247 assert has_testbench_name(testbench_file), testbench_file
248 testbench_name = basename(testbench_file)
250 # Construct the actions list.
251 action = [
252 # -- Print a testbench title.
253 announce_testbench_action(),
254 # -- Scan source files for issues.
255 source_files_issue_scanner_action(),
256 # -- Perform the actual test or sim compilation.
257 iverilog_action(
258 apio_env,
259 verbose=params.verbosity.all,
260 vcd_output_name=testbench_name,
261 is_interactive=apio_env.targeting_one_of("sim"),
262 lib_dirs=[self.yosys_lib_dir],
263 lib_files=self.sim_lib_files,
264 ),
265 ]
266 return action
268 # -- The testbench compiler builder.
269 return Builder(
270 # -- Dynamic action string generator.
271 generator=action_generator,
272 suffix=".out",
273 src_suffix=SRC_SUFFIXES,
274 source_scanner=self.verilog_src_scanner,
275 )
277 # @overrides
278 def lint_config_builder(self) -> BuilderBase:
279 """Creates and returns the lint config builder."""
281 # -- Sanity checks
282 assert self.apio_env.targeting_one_of("lint")
284 # -- Make the builder.
285 return make_verilator_config_builder(
286 self.yosys_lib_dir,
287 rules_to_supress=[
288 "SPECIFYIGN",
289 ],
290 )
292 # @overrides
293 def lint_builder(self) -> BuilderBase | CompositeBuilder:
294 """Creates and returns the lint builder."""
296 return Builder(
297 action=verilator_lint_action(
298 self.apio_env,
299 lib_dirs=[self.yosys_lib_dir],
300 lib_files=self.lint_lib_files,
301 ),
302 src_suffix=SRC_SUFFIXES,
303 source_scanner=self.verilog_src_scanner,
304 )