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

50 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-25 02:31 +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 

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 black-box cells such as EHXPLLL PLL. 

48 self.lint_lib_files = [yosys_path / "ecp5" / "cells_bb.v"] 

49 

50 def plugin_info(self) -> ArchPluginInfo: 

51 """Return plugin specific parameters.""" 

52 return ArchPluginInfo( 

53 constrains_file_suffix=".lpf", 

54 pnr_file_suffix=".config", 

55 bitstream_file_suffix=".bit", 

56 clk_name_index=2, 

57 ) 

58 

59 # @overrides 

60 def synth_builder(self) -> BuilderBase: 

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

62 

63 # -- Keep short references. 

64 apio_env = self.apio_env 

65 params = apio_env.params 

66 

67 # -- The yosys synth builder. 

68 return Builder( 

69 action=( 

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

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

72 ).format( 

73 params.apio_env_params.top_module, 

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

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

76 get_define_flags(apio_env), 

77 ), 

78 suffix=".json", 

79 src_suffix=SRC_SUFFIXES, 

80 source_scanner=self.verilog_src_scanner, 

81 ) 

82 

83 # @overrides 

84 def pnr_builder(self) -> BuilderBase: 

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

86 

87 # -- Keep short references. 

88 apio_env = self.apio_env 

89 params = apio_env.params 

90 

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

92 def emitter(target, source, env): 

93 _ = env # Unused 

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

95 return target, source 

96 

97 # -- Create the builder. 

98 return Builder( 

99 action=( 

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

101 "--json $SOURCE --textcfg $TARGET " 

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

103 "{5} {6} {7}" 

104 ).format( 

105 params.fpga_info.ecp5_params.type, 

106 params.fpga_info.ecp5_params.package, 

107 params.fpga_info.ecp5_params.speed, 

108 apio_env.target + ".pnr", 

109 self.constrain_file(), 

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

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

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

113 ), 

114 suffix=".config", 

115 src_suffix=".json", 

116 emitter=emitter, 

117 ) 

118 

119 # @overrides 

120 def bitstream_builder(self) -> BuilderBase: 

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

122 

123 return Builder( 

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

125 self.database_path, 

126 ), 

127 suffix=".bit", 

128 src_suffix=".config", 

129 ) 

130 

131 # @overrides 

132 def testbench_compile_builder(self) -> BuilderBase: 

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

134 # -- Keep short references. 

135 apio_env = self.apio_env 

136 params = apio_env.params 

137 

138 # -- Sanity checks 

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

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

141 

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

143 # -- string for sim and test. 

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

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

146 # Extract testbench name from target file name. 

147 testbench_file = str(target[0]) 

148 assert has_testbench_name(testbench_file), testbench_file 

149 testbench_name = basename(testbench_file) 

150 

151 # Construct the actions list. 

152 action = [ 

153 # -- Print a testbench title. 

154 announce_testbench_action(), 

155 # -- Scan source files for issues. 

156 source_files_issue_scanner_action(), 

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

158 iverilog_action( 

159 apio_env, 

160 verbose=params.verbosity.all, 

161 vcd_output_name=testbench_name, 

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

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

164 extra_params=["-DNO_INCLUDES"], 

165 lib_dirs=[self.yosys_lib_dir], 

166 lib_files=self.sim_lib_files, 

167 ), 

168 ] 

169 return action 

170 

171 # -- The testbench compiler builder. 

172 return Builder( 

173 # -- Dynamic action string generator. 

174 generator=action_generator, 

175 suffix=".out", 

176 src_suffix=SRC_SUFFIXES, 

177 source_scanner=self.verilog_src_scanner, 

178 ) 

179 

180 # @overrides 

181 def lint_config_builder(self) -> BuilderBase: 

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

183 

184 # -- Sanity checks 

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

186 

187 # -- Make the builder. 

188 return make_verilator_config_builder( 

189 self.yosys_lib_dir, 

190 rules_to_supress=[], 

191 ) 

192 

193 # @overrides 

194 def lint_builder(self) -> BuilderBase: 

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

196 

197 return Builder( 

198 action=verilator_lint_action( 

199 self.apio_env, 

200 lib_dirs=[self.yosys_lib_dir], 

201 lib_files=self.lint_lib_files, 

202 ), 

203 src_suffix=SRC_SUFFIXES, 

204 source_scanner=self.verilog_src_scanner, 

205 )