Coverage for tsfpga/vivado/test/test_tcl.py: 100%
155 statements
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-20 20:51 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-20 20:51 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project, a project platform for modern FPGA development.
5# https://tsfpga.com
6# https://github.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9# Standard libraries
10from collections import OrderedDict
11from pathlib import Path
12from unittest.mock import MagicMock
14# Third party libraries
15import pytest
17# First party libraries
18from tsfpga.build_step_tcl_hook import BuildStepTclHook
19from tsfpga.ip_core_file import IpCoreFile
20from tsfpga.module import BaseModule, get_modules
21from tsfpga.system_utils import create_file
23# pylint: disable=unused-import
24from tsfpga.test.test_utils import file_contains_string
25from tsfpga.vivado.common import to_tcl_path
26from tsfpga.vivado.generics import BitVectorGenericValue, StringGenericValue
27from tsfpga.vivado.tcl import VivadoTcl
30def test_set_create_run_index():
31 tcl = VivadoTcl(name="").create(project_folder=Path(), modules=[], part="", top="", run_index=2)
32 assert '\ncurrent_run [get_runs "synth_2"]\n' in tcl
35def test_static_generics():
36 # Use OrderedDict here in test so that order will be preserved and we can test for equality.
37 # In real world case a normal dict can be used.
38 generics = OrderedDict(
39 enable=True,
40 disable=False,
41 integer=123,
42 slv=BitVectorGenericValue("0101"),
43 string=StringGenericValue("apa"),
44 )
46 tcl = VivadoTcl(name="").create(
47 project_folder=Path(), modules=[], part="", top="", run_index=1, generics=generics
48 )
49 expected = (
50 '\nset_property "generic" '
51 "{enable=1'b1 disable=1'b0 integer=123 slv=4'b0101 string=\"apa\"} "
52 "[current_fileset]\n"
53 )
54 assert expected in tcl
57def test_build_step_hooks():
58 dummy = BuildStepTclHook(Path("dummy.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE")
59 files = BuildStepTclHook(Path("files.tcl"), "STEPS.ROUTE_DESIGN.TCL.PRE")
60 tcl = VivadoTcl(name="").create(
61 project_folder=Path(),
62 modules=[],
63 part="part",
64 top="",
65 run_index=1,
66 build_step_hooks=[dummy, files],
67 )
69 assert (
70 f"\n "
71 f'set_property "STEPS.SYNTH_DESIGN.TCL.PRE" {{{to_tcl_path(dummy.tcl_file)}}} ${{run}}\n'
72 ) in tcl
73 assert (
74 f"\n "
75 f'set_property "STEPS.ROUTE_DESIGN.TCL.PRE" {{{to_tcl_path(files.tcl_file)}}} ${{run}}\n'
76 ) in tcl
79def test_build_step_hooks_with_same_hook_step(tmp_path):
80 dummy = BuildStepTclHook(Path("dummy.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE")
81 files = BuildStepTclHook(Path("files.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE")
82 tcl = VivadoTcl(name="").create(
83 project_folder=tmp_path / "dummy_project_folder",
84 modules=[],
85 part="part",
86 top="",
87 run_index=1,
88 build_step_hooks=[dummy, files],
89 )
91 hook_file = tmp_path / "dummy_project_folder" / "hook_STEPS_SYNTH_DESIGN_TCL_PRE.tcl"
93 assert file_contains_string(hook_file, f"source {{{to_tcl_path(dummy.tcl_file)}}}")
94 assert file_contains_string(hook_file, f"source {{{to_tcl_path(files.tcl_file)}}}")
96 assert (
97 f'\n set_property "STEPS.SYNTH_DESIGN.TCL.PRE" {{{to_tcl_path(hook_file)}}} ${{run}}\n'
98 in tcl
99 )
102def test_ip_cache_location(tmp_path):
103 tcl = VivadoTcl(name="").create(
104 project_folder=Path(), modules=[], part="part", top="", run_index=1
105 )
106 assert "config_ip_cache" not in tcl
108 tcl = VivadoTcl(name="").create(
109 project_folder=Path(), modules=[], part="part", top="", run_index=1, ip_cache_path=tmp_path
110 )
111 assert f"\nconfig_ip_cache -use_cache_location {{{to_tcl_path(tmp_path)}}}\n" in tcl
114def test_multiple_threads_is_capped_by_vivado_limits():
115 num_threads = 128
116 tcl = VivadoTcl(name="").build(
117 project_file=Path(), output_path=Path(), num_threads=num_threads, run_index=1
118 )
119 assert 'set_param "general.maxThreads" 32' in tcl
120 assert 'set_param "synth.maxThreads" 8' in tcl
121 assert "launch_runs ${run} -jobs 128" in tcl
122 assert "launch_runs ${run} -jobs 128" in tcl
125def test_set_build_run_index():
126 tcl = VivadoTcl(name="").build(
127 project_file=Path(), output_path=Path(), num_threads=0, run_index=1
128 )
129 assert "impl_1" in tcl
130 assert "synth_1" in tcl
131 assert "impl_2" not in tcl
132 assert "synth_2" not in tcl
134 tcl = VivadoTcl(name="").build(
135 project_file=Path(), output_path=Path(), num_threads=0, run_index=2
136 )
137 assert "impl_2" in tcl
138 assert "synth_2" in tcl
139 assert "impl_1" not in tcl
140 assert "synth_1" not in tcl
143def test_runtime_generics():
144 tcl = VivadoTcl(name="").build(
145 project_file=Path(),
146 output_path=Path(),
147 num_threads=0,
148 run_index=0,
149 generics=dict(dummy=True),
150 )
151 expected = '\nset_property "generic" {dummy=1\'b1} [current_fileset]\n'
152 assert expected in tcl
155def test_build_with_synth_only():
156 tcl = VivadoTcl(name="").build(
157 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, synth_only=False
158 )
159 assert "synth_" in tcl
160 assert "impl_" in tcl
162 tcl = VivadoTcl(name="").build(
163 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, synth_only=True
164 )
165 assert "synth_" in tcl
166 assert "impl_" not in tcl
169def test_build_with_from_impl():
170 tcl = VivadoTcl(name="").build(
171 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, from_impl=False
172 )
173 assert "synth_" in tcl
174 assert "impl_" in tcl
176 tcl = VivadoTcl(name="").build(
177 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, from_impl=True
178 )
179 assert "synth_" not in tcl
180 assert "impl_" in tcl
183def test_module_getters_are_called_with_correct_arguments():
184 modules = [MagicMock(spec=BaseModule)]
185 VivadoTcl(name="").create(
186 project_folder=Path(),
187 modules=modules,
188 part="",
189 top="",
190 run_index=1,
191 other_arguments=dict(apa=123, hest=456),
192 )
194 modules[0].get_synthesis_files.assert_called_once_with(apa=123, hest=456)
195 modules[0].get_scoped_constraints.assert_called_once_with(apa=123, hest=456)
196 modules[0].get_ip_core_files.assert_called_once_with(apa=123, hest=456)
199@pytest.fixture
200def vivado_tcl_test(tmp_path):
201 class VivadoTclTest: # pylint: disable=too-many-instance-attributes
202 def __init__(self):
203 self.modules_folder = tmp_path / "modules"
205 # A library with some synth files and some test files
206 self.a_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "a.vhd"))
207 self.b_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "b.vhd"))
208 self.tb_a_vhd = to_tcl_path(
209 create_file(self.modules_folder / "apa" / "test" / "tb_a.vhd")
210 )
211 self.a_xdc = to_tcl_path(
212 create_file(self.modules_folder / "apa" / "scoped_constraints" / "a.xdc")
213 )
215 self.c_v = to_tcl_path(create_file(self.modules_folder / "apa" / "c.v"))
216 self.b_tcl = to_tcl_path(
217 create_file(self.modules_folder / "apa" / "scoped_constraints" / "b.tcl")
218 )
220 self.c_tcl = to_tcl_path(
221 create_file(self.modules_folder / "apa" / "ip_cores" / "c.tcl")
222 )
224 # A library with only test files
225 self.d_vhd = to_tcl_path(create_file(self.modules_folder / "zebra" / "test" / "d.vhd"))
227 self.modules = get_modules(self.modules_folder)
229 self.tcl = VivadoTcl(name="name")
231 return VivadoTclTest()
234# False positive for pytest fixtures
235# pylint: disable=redefined-outer-name
238def test_source_file_list_is_correctly_formatted(vivado_tcl_test):
239 tcl = vivado_tcl_test.tcl.create(
240 project_folder=Path(), modules=vivado_tcl_test.modules, part="", top="", run_index=1
241 )
243 # Order of files is not really deterministic
244 expected_1 = (
245 '\nread_vhdl -library "apa" -vhdl2008 '
246 f"{{{{{vivado_tcl_test.b_vhd}}} {{{vivado_tcl_test.a_vhd}}}}}\n"
247 )
248 expected_2 = (
249 '\nread_vhdl -library "apa" -vhdl2008 '
250 f"{{{{{vivado_tcl_test.a_vhd}}} {{{vivado_tcl_test.b_vhd}}}}}\n"
251 )
252 assert expected_1 in tcl or expected_2 in tcl
254 expected = f"\nread_verilog {{{vivado_tcl_test.c_v}}}\n"
255 assert expected in tcl
258def test_only_synthesis_files_added_to_create_project_tcl(vivado_tcl_test):
259 tcl = vivado_tcl_test.tcl.create(
260 project_folder=Path(), modules=vivado_tcl_test.modules, part="", top="", run_index=1
261 )
262 assert vivado_tcl_test.a_vhd in tcl and vivado_tcl_test.c_v in tcl
263 assert vivado_tcl_test.tb_a_vhd not in tcl and "tb_a.vhd" not in tcl
266def test_constraints(vivado_tcl_test):
267 tcl = vivado_tcl_test.tcl.create(
268 project_folder=Path(), modules=vivado_tcl_test.modules, part="part", top="", run_index=1
269 )
271 expected = f'\nread_xdc -ref "a" {{{vivado_tcl_test.a_xdc}}}\n'
272 assert expected in tcl
273 expected = f'\nread_xdc -ref "b" -unmanaged {{{vivado_tcl_test.b_tcl}}}\n'
274 assert expected in tcl
277def test_ip_core_files(vivado_tcl_test):
278 ip_core_file_path = vivado_tcl_test.modules_folder.parent / "my_name.tcl"
279 module = MagicMock(spec=BaseModule)
280 module.get_ip_core_files.return_value = [
281 IpCoreFile(path=ip_core_file_path, apa="hest", zebra=123)
282 ]
284 vivado_tcl_test.modules.append(module)
286 tcl = vivado_tcl_test.tcl.create(
287 project_folder=Path(), modules=vivado_tcl_test.modules, part="part", top="", run_index=1
288 )
290 assert (
291 f"""
292proc create_ip_core_c {{}} {{
293 source -notrace {{{vivado_tcl_test.c_tcl}}}
294}}
295create_ip_core_c
296"""
297 in tcl
298 )
300 assert (
301 f"""
302proc create_ip_core_my_name {{}} {{
303 set apa "hest"
304 set zebra "123"
305 source -notrace {{{to_tcl_path(ip_core_file_path)}}}
306}}
307create_ip_core_my_name
308"""
309 in tcl
310 )
313def test_create_with_ip_cores_only(vivado_tcl_test):
314 tcl = vivado_tcl_test.tcl.create(
315 project_folder=Path(),
316 modules=vivado_tcl_test.modules,
317 part="part",
318 top="",
319 run_index=1,
320 ip_cores_only=True,
321 )
322 assert vivado_tcl_test.c_tcl in tcl
323 assert vivado_tcl_test.a_vhd not in tcl
326def test_empty_library_not_in_create_project_tcl(vivado_tcl_test):
327 tcl = vivado_tcl_test.tcl.create(
328 project_folder=Path(), modules=vivado_tcl_test.modules, part="part", top="", run_index=1
329 )
330 assert "zebra" not in tcl
333def test_multiple_tcl_sources(vivado_tcl_test):
334 extra_tcl_sources = [Path("dummy.tcl"), Path("files.tcl")]
335 tcl = vivado_tcl_test.tcl.create(
336 project_folder=Path(),
337 modules=vivado_tcl_test.modules,
338 part="part",
339 top="",
340 run_index=1,
341 tcl_sources=extra_tcl_sources,
342 )
344 for filename in extra_tcl_sources:
345 assert f"\nsource -notrace {{{to_tcl_path(filename)}}}\n" in tcl
348def test_io_buffer_setting(vivado_tcl_test):
349 tcl = vivado_tcl_test.tcl.create(
350 project_folder=Path(),
351 modules=vivado_tcl_test.modules,
352 part="part",
353 top="",
354 run_index=1,
355 disable_io_buffers=True,
356 )
358 no_io_buffers_tcl = (
359 '\nset_property -name "STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS" '
360 '-value "-no_iobuf" -objects [get_runs "synth_1"]\n'
361 )
362 assert no_io_buffers_tcl in tcl
364 tcl = vivado_tcl_test.tcl.create(
365 project_folder=Path(),
366 modules=vivado_tcl_test.modules,
367 part="part",
368 top="",
369 run_index=1,
370 disable_io_buffers=False,
371 )
373 assert no_io_buffers_tcl not in tcl
376def test_analyze_synthesis_settings_on_and_off(vivado_tcl_test):
377 tcl = vivado_tcl_test.tcl.build(
378 project_file=Path(),
379 output_path=Path(),
380 num_threads=1,
381 run_index=1,
382 analyze_synthesis_timing=True,
383 )
384 assert "open_run" in tcl
385 assert "report_clock_interaction" in tcl
387 tcl = vivado_tcl_test.tcl.build(
388 project_file=Path(),
389 output_path=Path(),
390 num_threads=1,
391 run_index=1,
392 analyze_synthesis_timing=False,
393 )
394 # When disabled, the run should not even be opened, which saves time
395 assert "open_run" not in tcl
396 assert "report_clock_interaction" not in tcl
399def test_impl_explore(vivado_tcl_test):
400 num_runs = 4
402 tcl = vivado_tcl_test.tcl.build(
403 project_file=Path(),
404 output_path=Path(),
405 num_threads=num_runs,
406 run_index=1,
407 impl_explore=True,
408 )
410 assert (
411 f'launch_runs -jobs {num_runs} [get_runs "impl_explore_*"] -to_step "write_bitstream"'
412 in tcl
413 )
414 assert (
415 'wait_on_runs -quiet -exit_condition ANY_ONE_MET_TIMING [get_runs "impl_explore_*"]' in tcl
416 )
417 assert 'reset_runs [get_runs -filter {STATUS == "Queued..."}]' in tcl
418 assert (
419 'wait_on_runs -quiet [get_runs -filter {STATUS != "Not started"} "impl_explore_*"]' in tcl
420 )
421 assert 'foreach run [get_runs -filter {PROGRESS == "100%"} "impl_explore_*"]' in tcl