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

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

28 make_verilator_config_builder, 

29 get_define_flags, 

30) 

31 

32 

33class PluginGowin(PluginBase): 

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

35 

36 def __init__(self, apio_env: ApioEnv): 

37 # -- Call parent constructor. 

38 super().__init__(apio_env) 

39 

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 

62 

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 ) 

70 

71 # @overrides 

72 def synth_builder(self) -> BuilderBase | CompositeBuilder: 

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

74 

75 # -- Keep short references. 

76 apio_env = self.apio_env 

77 params = apio_env.params 

78 gowin_params = params.fpga_info.gowin_params 

79 

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 ) 

100 

101 # @overrides 

102 def pnr_builder(self) -> BuilderBase | CompositeBuilder: 

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

104 

105 # -- Keep short references. 

106 apio_env = self.apio_env 

107 params = apio_env.params 

108 gowin_params = params.fpga_info.gowin_params 

109 

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 

115 

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 ) 

139 

140 # @overrides 

141 def bitstream_builder(self) -> BuilderBase | CompositeBuilder: 

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

143 

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 ) 

151 

152 # @overrides 

153 def testbench_compile_builder(self) -> BuilderBase | CompositeBuilder: 

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

155 

156 # -- Keep short references. 

157 apio_env = self.apio_env 

158 params = apio_env.params 

159 

160 # -- Sanity checks 

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

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

163 

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) 

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

190 

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 ) 

199 

200 # @overrides 

201 def lint_config_builder(self) -> BuilderBase | CompositeBuilder: 

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

203 

204 # -- Sanity checks 

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

206 

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 ) 

218 

219 # @overrides 

220 def 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 src_suffix=SRC_SUFFIXES, 

230 source_scanner=self.verilog_src_scanner, 

231 )