Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the tsfpga project. 

5# https://tsfpga.com 

6# https://gitlab.com/tsfpga/tsfpga 

7# -------------------------------------------------------------------------------------------------- 

8 

9from collections import OrderedDict 

10from pathlib import Path 

11import unittest 

12from unittest.mock import MagicMock 

13 

14import pytest 

15 

16from tsfpga.build_step_tcl_hook import BuildStepTclHook 

17from tsfpga.ip_core_file import IpCoreFile 

18from tsfpga.module import BaseModule, get_modules 

19from tsfpga.system_utils import create_file 

20from tsfpga.vivado.common import to_tcl_path 

21from tsfpga.vivado.tcl import VivadoTcl 

22from tsfpga.test import file_contains_string 

23 

24# pylint: disable=unused-import 

25from tsfpga.test.conftest import fixture_tmp_path # noqa: F401 

26 

27 

28def test_set_create_run_index(): 

29 tcl = VivadoTcl(name="").create(project_folder=Path(), modules=[], part="", top="", run_index=2) 

30 assert "\ncurrent_run [get_runs synth_2]\n" in tcl 

31 

32 

33def test_static_generics(): 

34 # Use OrderedDict here in test so that order will be preserved and we can test for equality. 

35 # In real world case a normal dict can be used. 

36 generics = OrderedDict(enable=True, disable=False, integer=123, slv="4'b0101") 

37 

38 tcl = VivadoTcl(name="").create( 

39 project_folder=Path(), modules=[], part="", top="", run_index=1, generics=generics 

40 ) 

41 expected = ( 

42 "\nset_property generic {enable=1'b1 disable=1'b0 integer=123 slv=4'b0101} " 

43 "[current_fileset]\n" 

44 ) 

45 assert expected in tcl 

46 

47 

48def test_build_step_hooks(): 

49 dummy = BuildStepTclHook(Path("dummy.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE") 

50 files = BuildStepTclHook(Path("files.tcl"), "STEPS.ROUTE_DESIGN.TCL.PRE") 

51 tcl = VivadoTcl(name="").create( 

52 project_folder=Path(), 

53 modules=[], 

54 part="part", 

55 top="", 

56 run_index=1, 

57 build_step_hooks=[dummy, files], 

58 ) 

59 

60 assert ( 

61 f"\nset_property STEPS.SYNTH_DESIGN.TCL.PRE {{{to_tcl_path(dummy.tcl_file)}}} ${{run}}\n" 

62 in tcl 

63 ) 

64 assert ( 

65 f"\nset_property STEPS.ROUTE_DESIGN.TCL.PRE {{{to_tcl_path(files.tcl_file)}}} ${{run}}\n" 

66 in tcl 

67 ) 

68 

69 

70def test_build_step_hooks_with_same_hook_step(tmp_path): 

71 dummy = BuildStepTclHook(Path("dummy.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE") 

72 files = BuildStepTclHook(Path("files.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE") 

73 tcl = VivadoTcl(name="").create( 

74 project_folder=tmp_path / "dummy_project_folder", 

75 modules=[], 

76 part="part", 

77 top="", 

78 run_index=1, 

79 build_step_hooks=[dummy, files], 

80 ) 

81 

82 hook_file = tmp_path / "dummy_project_folder" / "hook_STEPS_SYNTH_DESIGN_TCL_PRE.tcl" 

83 

84 assert file_contains_string(hook_file, f"source {{{to_tcl_path(dummy.tcl_file)}}}") 

85 assert file_contains_string(hook_file, f"source {{{to_tcl_path(files.tcl_file)}}}") 

86 

87 assert ( 

88 f"\nset_property STEPS.SYNTH_DESIGN.TCL.PRE {{{to_tcl_path(hook_file)}}} ${{run}}\n" in tcl 

89 ) 

90 

91 

92def test_ip_cache_location(tmp_path): 

93 tcl = VivadoTcl(name="").create( 

94 project_folder=Path(), modules=[], part="part", top="", run_index=1 

95 ) 

96 assert "config_ip_cache" not in tcl 

97 

98 tcl = VivadoTcl(name="").create( 

99 project_folder=Path(), modules=[], part="part", top="", run_index=1, ip_cache_path=tmp_path 

100 ) 

101 assert f"\nconfig_ip_cache -use_cache_location {{{to_tcl_path(tmp_path)}}}\n" in tcl 

102 

103 

104def test_multiple_threads_is_capped_by_vivado_limits(): 

105 num_threads = 128 

106 tcl = VivadoTcl(name="").build( 

107 project_file=Path(), output_path=Path(), num_threads=num_threads, run_index=1 

108 ) 

109 print(tcl) 

110 assert "set_param general.maxThreads 32" in tcl 

111 assert "set_param synth.maxThreads 8" in tcl 

112 assert "launch_runs ${run} -jobs 128" in tcl 

113 assert "launch_runs ${run} -jobs 128" in tcl 

114 

115 

116def test_set_build_run_index(): 

117 tcl = VivadoTcl(name="").build( 

118 project_file=Path(), output_path=Path(), num_threads=0, run_index=1 

119 ) 

120 assert "impl_1" in tcl 

121 assert "synth_1" in tcl 

122 assert "impl_2" not in tcl 

123 assert "synth_2" not in tcl 

124 

125 tcl = VivadoTcl(name="").build( 

126 project_file=Path(), output_path=Path(), num_threads=0, run_index=2 

127 ) 

128 assert "impl_2" in tcl 

129 assert "synth_2" in tcl 

130 assert "impl_1" not in tcl 

131 assert "synth_1" not in tcl 

132 

133 

134def test_runtime_generics(): 

135 tcl = VivadoTcl(name="").build( 

136 project_file=Path(), 

137 output_path=Path(), 

138 num_threads=0, 

139 run_index=0, 

140 generics=dict(dummy=True), 

141 ) 

142 expected = "\nset_property generic {dummy=1'b1} [current_fileset]\n" 

143 assert expected in tcl 

144 

145 

146def test_module_getters_are_called_with_correct_arguments(): 

147 modules = [MagicMock(spec=BaseModule)] 

148 VivadoTcl(name="").create( 

149 project_folder=Path(), 

150 modules=modules, 

151 part="", 

152 top="", 

153 run_index=1, 

154 other_arguments=dict(apa=123, hest=456), 

155 ) 

156 

157 modules[0].get_synthesis_files.assert_called_once_with(apa=123, hest=456) 

158 modules[0].get_scoped_constraints.assert_called_once_with(apa=123, hest=456) 

159 modules[0].get_ip_core_files.assert_called_once_with(apa=123, hest=456) 

160 

161 

162# pylint: disable=too-many-instance-attributes 

163@pytest.mark.usefixtures("fixture_tmp_path") 

164class TestVivadoTcl(unittest.TestCase): 

165 

166 tmp_path = None 

167 

168 def setUp(self): 

169 self.modules_folder = self.tmp_path / "modules" 

170 

171 # A library with some synth files and some test files 

172 self.a_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "a.vhd")) 

173 self.b_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "b.vhd")) 

174 self.tb_a_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "test" / "tb_a.vhd")) 

175 self.a_xdc = to_tcl_path( 

176 create_file(self.modules_folder / "apa" / "scoped_constraints" / "a.xdc") 

177 ) 

178 

179 self.c_v = to_tcl_path(create_file(self.modules_folder / "apa" / "c.v")) 

180 self.b_tcl = to_tcl_path( 

181 create_file(self.modules_folder / "apa" / "scoped_constraints" / "b.tcl") 

182 ) 

183 

184 self.c_tcl = to_tcl_path(create_file(self.modules_folder / "apa" / "ip_cores" / "c.tcl")) 

185 

186 # A library with only test files 

187 self.d_vhd = to_tcl_path(create_file(self.modules_folder / "zebra" / "test" / "d.vhd")) 

188 

189 self.modules = get_modules([self.modules_folder]) 

190 

191 self.tcl = VivadoTcl(name="name") 

192 

193 def test_source_file_list_is_correctly_formatted(self): 

194 tcl = self.tcl.create( 

195 project_folder=Path(), modules=self.modules, part="", top="", run_index=1 

196 ) 

197 

198 # Order of files is not really deterministic 

199 expected_1 = f"\nread_vhdl -library apa -vhdl2008 {{{{{self.b_vhd}}} {{{self.a_vhd}}}}}\n" 

200 expected_2 = f"\nread_vhdl -library apa -vhdl2008 {{{{{self.a_vhd}}} {{{self.b_vhd}}}}}\n" 

201 assert expected_1 in tcl or expected_2 in tcl 

202 

203 expected = f"\nread_verilog {{{self.c_v}}}\n" 

204 assert expected in tcl 

205 

206 def test_only_synthesis_files_added_to_create_project_tcl(self): 

207 tcl = self.tcl.create( 

208 project_folder=Path(), modules=self.modules, part="", top="", run_index=1 

209 ) 

210 assert self.a_vhd in tcl and self.c_v in tcl 

211 assert self.tb_a_vhd not in tcl and "tb_a.vhd" not in tcl 

212 

213 def test_constraints(self): 

214 tcl = self.tcl.create( 

215 project_folder=Path(), modules=self.modules, part="part", top="", run_index=1 

216 ) 

217 

218 expected = f"\nread_xdc -ref a {{{self.a_xdc}}}\n" 

219 assert expected in tcl 

220 expected = f"\nread_xdc -ref b -unmanaged {{{self.b_tcl}}}\n" 

221 assert expected in tcl 

222 

223 def test_ip_core_files(self): 

224 ip_core_file_path = self.tmp_path / "my_name.tcl" 

225 module = MagicMock(spec=BaseModule) 

226 module.get_ip_core_files.return_value = [ 

227 IpCoreFile(path=ip_core_file_path, apa="hest", zebra=123) 

228 ] 

229 

230 self.modules.append(module) 

231 

232 tcl = self.tcl.create( 

233 project_folder=Path(), modules=self.modules, part="part", top="", run_index=1 

234 ) 

235 

236 assert ( 

237 f""" 

238proc create_ip_core_c {{}} {{ 

239 source -notrace {{{self.c_tcl}}} 

240}} 

241create_ip_core_c 

242""" 

243 in tcl 

244 ) 

245 

246 assert ( 

247 f""" 

248proc create_ip_core_my_name {{}} {{ 

249 set apa "hest" 

250 set zebra "123" 

251 source -notrace {{{to_tcl_path(ip_core_file_path)}}} 

252}} 

253create_ip_core_my_name 

254""" 

255 in tcl 

256 ) 

257 

258 def test_create_with_ip_cores_only(self): 

259 tcl = self.tcl.create( 

260 project_folder=Path(), 

261 modules=self.modules, 

262 part="part", 

263 top="", 

264 run_index=1, 

265 ip_cores_only=True, 

266 ) 

267 assert self.c_tcl in tcl 

268 assert self.a_vhd not in tcl 

269 

270 def test_empty_library_not_in_create_project_tcl(self): 

271 tcl = self.tcl.create( 

272 project_folder=Path(), modules=self.modules, part="part", top="", run_index=1 

273 ) 

274 assert "zebra" not in tcl 

275 

276 def test_multiple_tcl_sources(self): 

277 extra_tcl_sources = [Path("dummy.tcl"), Path("files.tcl")] 

278 tcl = self.tcl.create( 

279 project_folder=Path(), 

280 modules=self.modules, 

281 part="part", 

282 top="", 

283 run_index=1, 

284 tcl_sources=extra_tcl_sources, 

285 ) 

286 

287 for filename in extra_tcl_sources: 

288 assert f"\nsource -notrace {{{to_tcl_path(filename)}}}\n" in tcl 

289 

290 def test_io_buffer_setting(self): 

291 tcl = self.tcl.create( 

292 project_folder=Path(), 

293 modules=self.modules, 

294 part="part", 

295 top="", 

296 run_index=1, 

297 disable_io_buffers=True, 

298 ) 

299 

300 no_io_buffers_tcl = ( 

301 "\nset_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} " 

302 "-value -no_iobuf -objects [get_runs synth_1]\n" 

303 ) 

304 assert no_io_buffers_tcl in tcl 

305 

306 tcl = self.tcl.create( 

307 project_folder=Path(), 

308 modules=self.modules, 

309 part="part", 

310 top="", 

311 run_index=1, 

312 disable_io_buffers=False, 

313 ) 

314 

315 assert no_io_buffers_tcl not in tcl 

316 

317 def test_analyze_synthesis_settings_on_and_off(self): 

318 tcl = self.tcl.build( 

319 project_file=Path(), 

320 output_path=Path(), 

321 num_threads=1, 

322 run_index=1, 

323 analyze_synthesis_timing=True, 

324 ) 

325 assert "open_run" in tcl 

326 assert "report_clock_interaction" in tcl 

327 

328 tcl = self.tcl.build( 

329 project_file=Path(), 

330 output_path=Path(), 

331 num_threads=1, 

332 run_index=1, 

333 analyze_synthesis_timing=False, 

334 ) 

335 # When disabled, the run should not even be opened, which saves time 

336 assert "open_run" not in tcl 

337 assert "report_clock_interaction" not in tcl