Coverage for apio/scons/plugin_gowin.py: 97%

54 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-23 03:53 +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 

10 

11"""Apio scons plugin for the gowin architecture.""" 

12 

13# pylint: disable=duplicate-code 

14 

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 make_verilator_config_builder, 

28 get_define_flags, 

29) 

30 

31 

32class PluginGowin(PluginBase): 

33 """Apio scons plugin for the ice40 architecture.""" 

34 

35 def __init__(self, apio_env: ApioEnv): 

36 # -- Call parent constructor. 

37 super().__init__(apio_env) 

38 

39 # -- Cache values. 

40 yosys_path = Path(apio_env.params.environment.yosys_path) 

41 self.yosys_lib_dir = yosys_path / "gowin" 

42 self.sim_lib_files = [yosys_path / "gowin" / "cells_sim.v"] 

43 # -- For lint, also pass the black-box declarations of the primitives 

44 # -- that have no simulation model in cells_sim.v. The file is 

45 # -- per-family (derived from the part number); a few modules are 

46 # -- declared in both files — cells_sim.v goes first so its models 

47 # -- win, and the duplicates are waived with the MODDUP rule below 

48 # -- (scoped to the yosys lib dir). 

49 self.lint_lib_files = self.sim_lib_files 

50 part_num = apio_env.params.fpga_info.part_num.upper() 

51 for prefix, family in ( 51 ↛ exitline 51 didn't return from function '__init__' because the loop on line 51 didn't complete

52 ("GW1N", "gw1n"), 

53 ("GW2A", "gw2a"), 

54 ("GW5A", "gw5a"), 

55 ): 

56 if part_num.startswith(prefix): 56 ↛ 51line 56 didn't jump to line 51 because the condition on line 56 was always true

57 self.lint_lib_files = self.sim_lib_files + [ 

58 yosys_path / "gowin" / f"cells_xtra_{family}.v" 

59 ] 

60 break 

61 

62 def plugin_info(self) -> ArchPluginInfo: 

63 """Return plugin specific parameters.""" 

64 return ArchPluginInfo( 

65 constrains_file_suffix=".cst", 

66 pnr_file_suffix=".pnr.json", 

67 bitstream_file_suffix=".fs", 

68 ) 

69 

70 # @overrides 

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

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

73 

74 # -- Keep short references. 

75 apio_env = self.apio_env 

76 params = apio_env.params 

77 gowin_params = params.fpga_info.gowin_params 

78 

79 # -- The yosys synth builder. 

80 return Builder( 

81 action=( 

82 'yosys -p "synth_gowin -top {0} {1} -json $TARGET {2}" ' 

83 "{3} -DSYNTHESIZE {4} $SOURCES" 

84 ).format( 

85 params.apio_env_params.top_module, 

86 ( 

87 f"-family {gowin_params.yosys_family}" 

88 if gowin_params.yosys_family 

89 else "" 

90 ), 

91 " ".join(params.apio_env_params.yosys_extra_options), 

92 "" if params.verbosity.all or params.verbosity.synth else "-q", 

93 get_define_flags(apio_env), 

94 ), 

95 source_scanner=self.verilog_src_scanner, 

96 src_suffix=SRC_SUFFIXES, 

97 suffix=".json", 

98 ) 

99 

100 # @overrides 

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

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

103 

104 # -- Keep short references. 

105 apio_env = self.apio_env 

106 params = apio_env.params 

107 gowin_params = params.fpga_info.gowin_params 

108 

109 # -- We use an emmiter to add to the builder a second output file. 

110 def emitter(target, source, env): 

111 _ = env # Unused 

112 target.append(apio_env.target + ".pnr") 

113 return target, source 

114 

115 # -- Create the builder. 

116 return Builder( 

117 action=( 

118 "nextpnr-himbaechel --device {0} --json $SOURCE " 

119 "--write $TARGET --report {1} {2} " 

120 "--vopt cst={3} {4} {5}" 

121 ).format( 

122 params.fpga_info.part_num, 

123 apio_env.target + ".pnr", 

124 ( 

125 f"--vopt family={gowin_params.nextpnr_family}" 

126 if gowin_params.nextpnr_family 

127 else "" 

128 ), 

129 self.constrain_file(), 

130 "" if params.verbosity.all or params.verbosity.pnr else "-q", 

131 " ".join(params.apio_env_params.nextpnr_extra_options), 

132 ), 

133 src_suffix=".json", 

134 suffix=".pnr.json", 

135 emitter=emitter, 

136 ) 

137 

138 # @overrides 

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

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

141 

142 return Builder( 

143 action="gowin_pack -d {0} -o $TARGET $SOURCE".format( 

144 self.apio_env.params.fpga_info.gowin_params.packer_device 

145 ), 

146 src_suffix=".pnr.json", 

147 suffix=".fs", 

148 ) 

149 

150 # @overrides 

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

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

153 

154 # -- Keep short references. 

155 apio_env = self.apio_env 

156 params = apio_env.params 

157 

158 # -- Sanity checks 

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

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

161 

162 # -- We use a generator because we need a different action 

163 # -- string for sim and test. 

164 def action_generator(target, source, env, for_signature): 

165 _ = (source, env, for_signature) # Unused 

166 # Extract testbench file name from the target. 

167 testbench_file = str(target[0]) 

168 assert has_testbench_name(testbench_file), testbench_file 

169 

170 # Construct the actions list. 

171 action = [ 

172 # -- Print a testbench title. 

173 announce_testbench_action(), 

174 # -- Scan source files for issues. 

175 source_files_issue_scanner_action(), 

176 # -- Perform the actual test or sim compilation. 

177 iverilog_action( 

178 apio_env, 

179 verbose=params.verbosity.all, 

180 is_interactive=apio_env.targeting_one_of("sim"), 

181 lib_dirs=[self.yosys_lib_dir], 

182 lib_files=self.sim_lib_files, 

183 ), 

184 ] 

185 return action 

186 

187 # -- The testbench compiler builder. 

188 return Builder( 

189 # -- Dynamic action string generator. 

190 generator=action_generator, 

191 source_scanner=self.verilog_src_scanner, 

192 src_suffix=SRC_SUFFIXES, 

193 suffix=".out", 

194 ) 

195 

196 # @overrides 

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

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

199 

200 # -- Sanity checks 

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

202 

203 # -- Make the builder. 

204 # -- See https://verilator.org/guide/latest/warnings.html 

205 return make_verilator_config_builder( 

206 self.yosys_lib_dir, 

207 rules_to_suppress=[ 

208 "SPECIFYIGN", 

209 # -- cells_xtra_gw2a/gw5a re-declare a few modules that also 

210 # -- have simulation models in cells_sim.v (DQS, IDES4_MEM, 

211 # -- OSER4_MEM); verilator keeps the first definition. 

212 "MODDUP", 

213 ], 

214 ) 

215 

216 # @overrides 

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

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

219 

220 return Builder( 

221 action=verilator_lint_action( 

222 self.apio_env, 

223 lib_dirs=[self.yosys_lib_dir], 

224 lib_files=self.lint_lib_files, 

225 ), 

226 source_scanner=self.verilog_src_scanner, 

227 src_suffix=SRC_SUFFIXES, 

228 )