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

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 gowin 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 PluginGowin(PluginBase): 

32 """Apio scons plugin for the ice40 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 / "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 

62 

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 ) 

71 

72 @override 

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

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

75 

76 # -- Keep short references. 

77 apio_env = self.apio_env 

78 params = apio_env.params 

79 gowin_params = params.fpga_definition.gowin_params 

80 

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 ) 

102 

103 @override 

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

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

106 

107 # -- Keep short references. 

108 apio_env = self.apio_env 

109 params = apio_env.params 

110 gowin_params = params.fpga_definition.gowin_params 

111 

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 

117 

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 ) 

140 

141 @override 

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

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

144 

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 ) 

152 

153 @override 

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

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

156 

157 # -- Keep short references. 

158 apio_env = self.apio_env 

159 params = apio_env.params 

160 

161 # -- Sanity checks 

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

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

164 

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 

172 

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 

189 

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 ) 

198 

199 @override 

200 def make_lint_config_builder(self) -> BuilderBase | CompositeBuilder: 

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

202 

203 # -- Sanity checks 

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

205 

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 ) 

218 

219 @override 

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

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

222 

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 )