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
« 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
9"""Apio scons plugin for the xilinx architecture."""
11# pylint: disable=duplicate-code
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)
31class PluginXilinx(PluginBase):
32 """Apio scons plugin for the Xilinx architecture."""
34 def __init__(self, apio_env: ApioEnv):
35 # -- Call parent constructor.
36 super().__init__(apio_env)
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 ]
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 )
59 @override
60 def make_synth_builder(self) -> BuilderBase | CompositeBuilder:
61 """Creates and returns the synth builder."""
63 # -- Keep short references.
64 apio_env = self.apio_env
65 params = apio_env.params
66 xilinx_params = params.fpga_definition.xilinx_params
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 )
90 @override
91 def make_pnr_builder(self) -> BuilderBase | CompositeBuilder:
92 """Creates and returns the pnr builder."""
94 # -- Keep short references.
95 apio_env = self.apio_env
96 params = apio_env.params
97 xilinx_params = params.fpga_definition.xilinx_params
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
105 quiet = "" if params.verbosity.all or params.verbosity.pnr else "-q"
106 extra_options = " ".join(params.apio_env_params.nextpnr_extra_options)
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 )
119 # -- Create the builder
120 return Builder(
121 action=action,
122 src_suffix=".json",
123 suffix=".fasm",
124 emitter=emitter,
125 )
127 @override
128 def make_bitstream_builder(self) -> BuilderBase | CompositeBuilder:
129 """Creates and returns the bitstream builder."""
131 # -- Keep short references.
132 apio_env = self.apio_env
133 params = apio_env.params
134 xilinx_params = params.fpga_definition.xilinx_params
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"
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"
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 )
161 @override
162 def make_testbench_compile_builder(self) -> BuilderBase | CompositeBuilder:
163 """Creates and returns the testbench compile builder."""
165 # -- Keep short references.
166 apio_env = self.apio_env
167 params = apio_env.params
169 # -- Sanity checks
170 assert apio_env.targeting_one_of("sim", "test")
171 assert params.target.HasField("sim") or params.target.HasField("test")
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
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
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 )
207 @override
208 def make_lint_config_builder(self) -> BuilderBase:
209 """Creates and returns the lint config builder."""
211 # -- Sanity checks
212 assert self.apio_env.targeting_one_of("lint")
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 )
223 @override
224 def make_lint_builder(self) -> BuilderBase | CompositeBuilder:
225 """Creates and returns the lint builder."""
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 )