Coverage for apio/scons/plugin_gowin.py: 97%
55 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 gowin architecture."""
13# pylint: disable=duplicate-code
15from pathlib import Path
16from SCons.Script import Builder
17from SCons.Builder import BuilderBase, CompositeBuilder
18from apio.common.common_util import SRC_SUFFIXES
19from apio.scons.apio_env import ApioEnv
20from apio.scons.plugin_base import PluginBase, ArchPluginInfo
21from apio.scons.plugin_util import (
22 verilator_lint_action,
23 has_testbench_name,
24 announce_testbench_action,
25 source_files_issue_scanner_action,
26 iverilog_action,
27 basename,
28 make_verilator_config_builder,
29 get_define_flags,
30)
33class PluginGowin(PluginBase):
34 """Apio scons plugin for the ice40 architecture."""
36 def __init__(self, apio_env: ApioEnv):
37 # -- Call parent constructor.
38 super().__init__(apio_env)
40 # -- Cache values.
41 yosys_path = Path(apio_env.params.environment.yosys_path)
42 self.yosys_lib_dir = yosys_path / "gowin"
43 self.sim_lib_files = [yosys_path / "gowin" / "cells_sim.v"]
44 # -- For lint, also pass the black-box declarations of the primitives
45 # -- that have no simulation model in cells_sim.v. The file is
46 # -- per-family (derived from the part number); a few modules are
47 # -- declared in both files — cells_sim.v goes first so its models
48 # -- win, and the duplicates are waived with the MODDUP rule below
49 # -- (scoped to the yosys lib dir).
50 self.lint_lib_files = self.sim_lib_files
51 part_num = apio_env.params.fpga_info.part_num.upper()
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 part_num.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 def plugin_info(self) -> ArchPluginInfo:
64 """Return plugin specific parameters."""
65 return ArchPluginInfo(
66 constrains_file_suffix=".cst",
67 pnr_file_suffix=".pnr.json",
68 bitstream_file_suffix=".fs",
69 )
71 # @overrides
72 def synth_builder(self) -> BuilderBase | CompositeBuilder:
73 """Creates and returns the synth builder."""
75 # -- Keep short references.
76 apio_env = self.apio_env
77 params = apio_env.params
78 gowin_params = params.fpga_info.gowin_params
80 # -- The yosys synth builder.
81 return Builder(
82 action=(
83 'yosys -p "synth_gowin -top {0} {1} -json $TARGET {2}" '
84 "{3} -DSYNTHESIZE {4} $SOURCES"
85 ).format(
86 params.apio_env_params.top_module,
87 (
88 f"-family {gowin_params.yosys_family}"
89 if gowin_params.yosys_family
90 else ""
91 ),
92 " ".join(params.apio_env_params.yosys_extra_options),
93 "" if params.verbosity.all or params.verbosity.synth else "-q",
94 get_define_flags(apio_env),
95 ),
96 suffix=".json",
97 src_suffix=SRC_SUFFIXES,
98 source_scanner=self.verilog_src_scanner,
99 )
101 # @overrides
102 def pnr_builder(self) -> BuilderBase | CompositeBuilder:
103 """Creates and returns the pnr builder."""
105 # -- Keep short references.
106 apio_env = self.apio_env
107 params = apio_env.params
108 gowin_params = params.fpga_info.gowin_params
110 # -- We use an emmiter to add to the builder a second output file.
111 def emitter(target, source, env):
112 _ = env # Unused
113 target.append(apio_env.target + ".pnr")
114 return target, source
116 # -- Create the builder.
117 return Builder(
118 action=(
119 "nextpnr-himbaechel --device {0} --json $SOURCE "
120 "--write $TARGET --report {1} {2} "
121 "--vopt cst={3} {4} {6} {5}"
122 ).format(
123 params.fpga_info.part_num,
124 apio_env.target + ".pnr",
125 (
126 f"--vopt family={gowin_params.nextpnr_family}"
127 if gowin_params.nextpnr_family
128 else ""
129 ),
130 self.constrain_file(),
131 "" if params.verbosity.all or params.verbosity.pnr else "-q",
132 "--gui" if params.nextpnr_gui else "",
133 " ".join(params.apio_env_params.nextpnr_extra_options),
134 ),
135 suffix=".pnr.json",
136 src_suffix=".json",
137 emitter=emitter,
138 )
140 # @overrides
141 def bitstream_builder(self) -> BuilderBase | CompositeBuilder:
142 """Creates and returns the bitstream builder."""
144 return Builder(
145 action="gowin_pack -d {0} -o $TARGET $SOURCE".format(
146 self.apio_env.params.fpga_info.gowin_params.packer_device
147 ),
148 suffix=".fs",
149 src_suffix=".pnr.json",
150 )
152 # @overrides
153 def testbench_compile_builder(self) -> BuilderBase | CompositeBuilder:
154 """Creates and returns the testbench compile builder."""
156 # -- Keep short references.
157 apio_env = self.apio_env
158 params = apio_env.params
160 # -- Sanity checks
161 assert apio_env.targeting_one_of("sim", "test")
162 assert params.target.HasField("sim") or params.target.HasField("test")
164 # -- We use a generator because we need a different action
165 # -- string for sim and test.
166 def action_generator(target, source, env, for_signature):
167 _ = (source, env, for_signature) # Unused
168 # Extract testbench name from target file name.
169 testbench_file = str(target[0])
170 assert has_testbench_name(testbench_file), testbench_file
171 testbench_name = basename(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 vcd_output_name=testbench_name,
184 is_interactive=apio_env.targeting_one_of("sim"),
185 lib_dirs=[self.yosys_lib_dir],
186 lib_files=self.sim_lib_files,
187 ),
188 ]
189 return action
191 # -- The testbench compiler builder.
192 return Builder(
193 # -- Dynamic action string generator.
194 generator=action_generator,
195 suffix=".out",
196 src_suffix=SRC_SUFFIXES,
197 source_scanner=self.verilog_src_scanner,
198 )
200 # @overrides
201 def lint_config_builder(self) -> BuilderBase | CompositeBuilder:
202 """Creates and returns the lint config builder."""
204 # -- Sanity checks
205 assert self.apio_env.targeting_one_of("lint")
207 # -- Make the builder.
208 return make_verilator_config_builder(
209 self.yosys_lib_dir,
210 rules_to_supress=[
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 # @overrides
220 def 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 src_suffix=SRC_SUFFIXES,
230 source_scanner=self.verilog_src_scanner,
231 )