Coverage for apio/scons/plugin_ecp5.py: 100%

50 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 ecp5 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 PluginEcp5(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 trellis_path = Path(apio_env.params.environment.trellis_path) 

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

43 

44 self.database_path = trellis_path / "database" 

45 self.yosys_lib_dir = yosys_path / "ecp5" 

46 self.sim_lib_files = [yosys_path / "ecp5" / "cells_sim.v"] 

47 # -- For lint, the simulation models PLUS the black-box cells that 

48 # -- have no model in cells_sim.v (EHXPLLL, DCUA, ...). Passing only 

49 # -- cells_bb.v (as before) made verilator fail with MODMISSING on 

50 # -- designs that instantiate simulation-modeled cells (IO cells, 

51 # -- DP16KD, ...). The two files declare disjoint modules. 

52 self.lint_lib_files = self.sim_lib_files + [ 

53 yosys_path / "ecp5" / "cells_bb.v" 

54 ] 

55 

56 def plugin_info(self) -> ArchPluginInfo: 

57 """Return plugin specific parameters.""" 

58 return ArchPluginInfo( 

59 constrains_file_suffix=".lpf", 

60 pnr_file_suffix=".config", 

61 bitstream_file_suffix=".bit", 

62 ) 

63 

64 # @overrides 

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

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

67 

68 # -- Keep short references. 

69 apio_env = self.apio_env 

70 params = apio_env.params 

71 

72 # -- The yosys synth builder. 

73 return Builder( 

74 action=( 

75 'yosys -p "synth_ecp5 -top {0} -json $TARGET {1}" ' 

76 "{2} -DSYNTHESIZE {3} $SOURCES" 

77 ).format( 

78 params.apio_env_params.top_module, 

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

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

81 get_define_flags(apio_env), 

82 ), 

83 suffix=".json", 

84 src_suffix=SRC_SUFFIXES, 

85 source_scanner=self.verilog_src_scanner, 

86 ) 

87 

88 # @overrides 

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

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

91 

92 # -- Keep short references. 

93 apio_env = self.apio_env 

94 params = apio_env.params 

95 

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

97 def emitter(target, source, env): 

98 _ = env # Unused 

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

100 return target, source 

101 

102 # -- Create the builder. 

103 return Builder( 

104 action=( 

105 "nextpnr-ecp5 --{0} --package {1} --speed {2} " 

106 "--json $SOURCE --textcfg $TARGET " 

107 "--report {3} --lpf {4} --timing-allow-fail --force " 

108 "{5} {6} {7}" 

109 ).format( 

110 params.fpga_info.ecp5_params.type, 

111 params.fpga_info.ecp5_params.package, 

112 params.fpga_info.ecp5_params.speed, 

113 apio_env.target + ".pnr", 

114 self.constrain_file(), 

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

116 "--gui" if params.nextpnr_gui else "", 

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

118 ), 

119 suffix=".config", 

120 src_suffix=".json", 

121 emitter=emitter, 

122 ) 

123 

124 # @overrides 

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

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

127 

128 return Builder( 

129 action="ecppack --compress --db {0} $SOURCE $TARGET".format( 

130 self.database_path, 

131 ), 

132 suffix=".bit", 

133 src_suffix=".config", 

134 ) 

135 

136 # @overrides 

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

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

139 # -- Keep short references. 

140 apio_env = self.apio_env 

141 params = apio_env.params 

142 

143 # -- Sanity checks 

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

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

146 

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

148 # -- string for sim and test. 

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

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

151 # Extract testbench name from target file name. 

152 testbench_file = str(target[0]) 

153 assert has_testbench_name(testbench_file), testbench_file 

154 testbench_name = basename(testbench_file) 

155 

156 # Construct the actions list. 

157 action = [ 

158 # -- Print a testbench title. 

159 announce_testbench_action(), 

160 # -- Scan source files for issues. 

161 source_files_issue_scanner_action(), 

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

163 iverilog_action( 

164 apio_env, 

165 verbose=params.verbosity.all, 

166 vcd_output_name=testbench_name, 

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

168 # -- Per https://github.com/YosysHQ/yosys/issues/5668 

169 extra_params=["-DNO_INCLUDES"], 

170 lib_dirs=[self.yosys_lib_dir], 

171 lib_files=self.sim_lib_files, 

172 ), 

173 ] 

174 return action 

175 

176 # -- The testbench compiler builder. 

177 return Builder( 

178 # -- Dynamic action string generator. 

179 generator=action_generator, 

180 suffix=".out", 

181 src_suffix=SRC_SUFFIXES, 

182 source_scanner=self.verilog_src_scanner, 

183 ) 

184 

185 # @overrides 

186 def lint_config_builder(self) -> BuilderBase: 

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

188 

189 # -- Sanity checks 

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

191 

192 # -- Make the builder. 

193 return make_verilator_config_builder( 

194 self.yosys_lib_dir, 

195 rules_to_supress=[ 

196 # -- cells_sim.v double-includes cells_ff.vh/cells_io.vh 

197 # -- (directly and via common_sim.vh); the duplicates are 

198 # -- identical and verilator keeps the first definition. 

199 "MODDUP", 

200 # -- Benign width mismatch in common_sim.vh (BB model). 

201 "WIDTHEXPAND", 

202 # -- The techmap wrappers in cells_io.vh/cells_ff.vh 

203 # -- instantiate TRELLIS_* cells with partial pin lists on 

204 # -- purpose; user instances keep full pin checking (the 

205 # -- waiver is scoped to the yosys lib dir). 

206 "PINMISSING", 

207 ], 

208 ) 

209 

210 # @overrides 

211 def lint_builder(self) -> BuilderBase | CompositeBuilder: 

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

213 

214 return Builder( 

215 action=verilator_lint_action( 

216 self.apio_env, 

217 lib_dirs=[self.yosys_lib_dir], 

218 lib_files=self.lint_lib_files, 

219 ), 

220 src_suffix=SRC_SUFFIXES, 

221 source_scanner=self.verilog_src_scanner, 

222 )