Coverage for apio/scons/plugin_gowin.py: 97%
62 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 gowin 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 PluginGowin(PluginBase):
32 """Apio scons plugin for the ice40 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 / "gowin"
41 self.sim_lib_files = [yosys_path / "gowin" / "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. The file is
44 # -- per-family (derived from the part number); a few modules are
45 # -- declared in both files — cells_sim.v goes first so its models
46 # -- win, and the duplicates are waived with the MODDUP rule below
47 # -- (scoped to the yosys lib dir).
48 self.lint_lib_files = self.sim_lib_files
49 device_lc = (
50 apio_env.params.fpga_definition.gowin_params.nextpnr_device.upper()
51 )
52 for prefix, family in ( 52 ↛ exitline 52 didn't return from function '__init__' because the loop on line 52 didn't complete
53 ("GW1N", "gw1n"),
54 ("GW2A", "gw2a"),
55 ("GW5A", "gw5a"),
56 ):
57 if device_lc.startswith(prefix): 57 ↛ 52line 57 didn't jump to line 52 because the condition on line 57 was always true
58 self.lint_lib_files = self.sim_lib_files + [
59 yosys_path / "gowin" / f"cells_xtra_{family}.v"
60 ]
61 break
63 @override
64 def plugin_info(self) -> ArchPluginInfo:
65 """Return plugin specific parameters."""
66 return ArchPluginInfo(
67 constrains_file_suffix=".cst",
68 pnr_file_suffix=".pnr.json",
69 bitstream_file_suffix=".fs",
70 )
72 @override
73 def make_synth_builder(self) -> BuilderBase | CompositeBuilder:
74 """Creates and returns the synth builder."""
76 # -- Keep short references.
77 apio_env = self.apio_env
78 params = apio_env.params
79 gowin_params = params.fpga_definition.gowin_params
81 # -- The yosys synth builder.
82 return Builder(
83 action=(
84 'yosys -p "synth_gowin -top {0} {1} {2}; '
85 'write_json $TARGET" '
86 "{3} -DSYNTHESIZE {4} $SOURCES"
87 ).format(
88 params.apio_env_params.top_module,
89 (
90 f"-family {gowin_params.yosys_family}"
91 if gowin_params.yosys_family
92 else ""
93 ),
94 " ".join(params.apio_env_params.yosys_extra_options),
95 "" if params.verbosity.all or params.verbosity.synth else "-q",
96 get_define_flags(apio_env),
97 ),
98 source_scanner=self.verilog_src_scanner,
99 src_suffix=SRC_SUFFIXES,
100 suffix=".json",
101 )
103 @override
104 def make_pnr_builder(self) -> BuilderBase | CompositeBuilder:
105 """Creates and returns the pnr builder."""
107 # -- Keep short references.
108 apio_env = self.apio_env
109 params = apio_env.params
110 gowin_params = params.fpga_definition.gowin_params
112 # -- We use an emmiter to add to the builder a second output file.
113 def emitter(target, source, env):
114 _ = env # Unused
115 target.append(apio_env.target + ".pnr")
116 return target, source
118 # -- Create the builder.
119 return Builder(
120 action=(
121 "nextpnr-himbaechel --device {0} --json $SOURCE "
122 "--write $TARGET --report {1} {2} "
123 "--vopt cst={3} {4} {5}"
124 ).format(
125 params.fpga_definition.gowin_params.nextpnr_device,
126 apio_env.target + ".pnr",
127 (
128 f"--vopt family={gowin_params.nextpnr_family}"
129 if gowin_params.nextpnr_family
130 else ""
131 ),
132 self.constrain_file(),
133 "" if params.verbosity.all or params.verbosity.pnr else "-q",
134 " ".join(params.apio_env_params.nextpnr_extra_options),
135 ),
136 src_suffix=".json",
137 suffix=".pnr.json",
138 emitter=emitter,
139 )
141 @override
142 def make_bitstream_builder(self) -> BuilderBase | CompositeBuilder:
143 """Creates and returns the bitstream builder."""
145 return Builder(
146 action="gowin_pack -d {0} -o $TARGET $SOURCE".format(
147 self.apio_env.params.fpga_definition.gowin_params.packer_device
148 ),
149 src_suffix=".pnr.json",
150 suffix=".fs",
151 )
153 @override
154 def make_testbench_compile_builder(self) -> BuilderBase | CompositeBuilder:
155 """Creates and returns the testbench compile builder."""
157 # -- Keep short references.
158 apio_env = self.apio_env
159 params = apio_env.params
161 # -- Sanity checks
162 assert apio_env.targeting_one_of("sim", "test")
163 assert params.target.HasField("sim") or params.target.HasField("test")
165 # -- We use a generator because we need a different action
166 # -- string for sim and test.
167 def action_generator(target, source, env, for_signature):
168 _ = (source, env, for_signature) # Unused
169 # Extract testbench file name from the target.
170 testbench_file = str(target[0])
171 assert has_testbench_name(testbench_file), testbench_file
173 # Construct the actions list.
174 action = [
175 # -- Print a testbench title.
176 announce_testbench_action(),
177 # -- Scan source files for issues.
178 source_files_issue_scanner_action(),
179 # -- Perform the actual test or sim compilation.
180 iverilog_action(
181 apio_env,
182 verbose=params.verbosity.all,
183 is_interactive=apio_env.targeting_one_of("sim"),
184 lib_dirs=[self.yosys_lib_dir],
185 lib_files=self.sim_lib_files,
186 ),
187 ]
188 return action
190 # -- The testbench compiler builder.
191 return Builder(
192 # -- Dynamic action string generator.
193 generator=action_generator,
194 source_scanner=self.verilog_src_scanner,
195 src_suffix=SRC_SUFFIXES,
196 suffix=".out",
197 )
199 @override
200 def make_lint_config_builder(self) -> BuilderBase | CompositeBuilder:
201 """Creates and returns the lint config builder."""
203 # -- Sanity checks
204 assert self.apio_env.targeting_one_of("lint")
206 # -- Make the builder.
207 # -- See https://verilator.org/guide/latest/warnings.html
208 return make_verilator_config_builder(
209 self.yosys_lib_dir,
210 rules_to_suppress=[
211 "SPECIFYIGN",
212 # -- cells_xtra_gw2a/gw5a re-declare a few modules that also
213 # -- have simulation models in cells_sim.v (DQS, IDES4_MEM,
214 # -- OSER4_MEM); verilator keeps the first definition.
215 "MODDUP",
216 ],
217 )
219 @override
220 def make_lint_builder(self) -> BuilderBase | CompositeBuilder:
221 """Creates and returns the lint builder."""
223 return Builder(
224 action=verilator_lint_action(
225 self.apio_env,
226 lib_dirs=[self.yosys_lib_dir],
227 lib_files=self.lint_lib_files,
228 ),
229 source_scanner=self.verilog_src_scanner,
230 src_suffix=SRC_SUFFIXES,
231 )