Coverage for tsfpga/vivado/test/test_tcl.py: 100%
156 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-10 20:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-10 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 {enable=1'b1 disable=1'b0 integer=123 slv=4'b0101 string=\"apa\"} "
51 "[current_fileset]\n"
52 )
53 assert expected in tcl
56def test_build_step_hooks():
57 dummy = BuildStepTclHook(Path("dummy.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE")
58 files = BuildStepTclHook(Path("files.tcl"), "STEPS.ROUTE_DESIGN.TCL.PRE")
59 tcl = VivadoTcl(name="").create(
60 project_folder=Path(),
61 modules=[],
62 part="part",
63 top="",
64 run_index=1,
65 build_step_hooks=[dummy, files],
66 )
68 assert (
69 f"\nset_property STEPS.SYNTH_DESIGN.TCL.PRE {{{to_tcl_path(dummy.tcl_file)}}} ${{run}}\n"
70 in tcl
71 )
72 assert (
73 f"\nset_property STEPS.ROUTE_DESIGN.TCL.PRE {{{to_tcl_path(files.tcl_file)}}} ${{run}}\n"
74 in tcl
75 )
78def test_build_step_hooks_with_same_hook_step(tmp_path):
79 dummy = BuildStepTclHook(Path("dummy.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE")
80 files = BuildStepTclHook(Path("files.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE")
81 tcl = VivadoTcl(name="").create(
82 project_folder=tmp_path / "dummy_project_folder",
83 modules=[],
84 part="part",
85 top="",
86 run_index=1,
87 build_step_hooks=[dummy, files],
88 )
90 hook_file = tmp_path / "dummy_project_folder" / "hook_STEPS_SYNTH_DESIGN_TCL_PRE.tcl"
92 assert file_contains_string(hook_file, f"source {{{to_tcl_path(dummy.tcl_file)}}}")
93 assert file_contains_string(hook_file, f"source {{{to_tcl_path(files.tcl_file)}}}")
95 assert (
96 f"\nset_property STEPS.SYNTH_DESIGN.TCL.PRE {{{to_tcl_path(hook_file)}}} ${{run}}\n" in tcl
97 )
100def test_ip_cache_location(tmp_path):
101 tcl = VivadoTcl(name="").create(
102 project_folder=Path(), modules=[], part="part", top="", run_index=1
103 )
104 assert "config_ip_cache" not in tcl
106 tcl = VivadoTcl(name="").create(
107 project_folder=Path(), modules=[], part="part", top="", run_index=1, ip_cache_path=tmp_path
108 )
109 assert f"\nconfig_ip_cache -use_cache_location {{{to_tcl_path(tmp_path)}}}\n" in tcl
112def test_multiple_threads_is_capped_by_vivado_limits():
113 num_threads = 128
114 tcl = VivadoTcl(name="").build(
115 project_file=Path(), output_path=Path(), num_threads=num_threads, run_index=1
116 )
117 print(tcl)
118 assert "set_param general.maxThreads 32" in tcl
119 assert "set_param synth.maxThreads 8" in tcl
120 assert "launch_runs ${run} -jobs 128" in tcl
121 assert "launch_runs ${run} -jobs 128" in tcl
124def test_set_build_run_index():
125 tcl = VivadoTcl(name="").build(
126 project_file=Path(), output_path=Path(), num_threads=0, run_index=1
127 )
128 assert "impl_1" in tcl
129 assert "synth_1" in tcl
130 assert "impl_2" not in tcl
131 assert "synth_2" not in tcl
133 tcl = VivadoTcl(name="").build(
134 project_file=Path(), output_path=Path(), num_threads=0, run_index=2
135 )
136 assert "impl_2" in tcl
137 assert "synth_2" in tcl
138 assert "impl_1" not in tcl
139 assert "synth_1" not in tcl
142def test_runtime_generics():
143 tcl = VivadoTcl(name="").build(
144 project_file=Path(),
145 output_path=Path(),
146 num_threads=0,
147 run_index=0,
148 generics=dict(dummy=True),
149 )
150 expected = "\nset_property generic {dummy=1'b1} [current_fileset]\n"
151 assert expected in tcl
154def test_build_with_synth_only():
155 tcl = VivadoTcl(name="").build(
156 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, synth_only=False
157 )
158 assert "synth_" in tcl
159 assert "impl_" in tcl
161 tcl = VivadoTcl(name="").build(
162 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, synth_only=True
163 )
164 assert "synth_" in tcl
165 assert "impl_" not in tcl
168def test_build_with_from_impl():
169 tcl = VivadoTcl(name="").build(
170 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, from_impl=False
171 )
172 assert "synth_" in tcl
173 assert "impl_" in tcl
175 tcl = VivadoTcl(name="").build(
176 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, from_impl=True
177 )
178 assert "synth_" not in tcl
179 assert "impl_" in tcl
182def test_module_getters_are_called_with_correct_arguments():
183 modules = [MagicMock(spec=BaseModule)]
184 VivadoTcl(name="").create(
185 project_folder=Path(),
186 modules=modules,
187 part="",
188 top="",
189 run_index=1,
190 other_arguments=dict(apa=123, hest=456),
191 )
193 modules[0].get_synthesis_files.assert_called_once_with(apa=123, hest=456)
194 modules[0].get_scoped_constraints.assert_called_once_with(apa=123, hest=456)
195 modules[0].get_ip_core_files.assert_called_once_with(apa=123, hest=456)
198@pytest.fixture
199def vivado_tcl_test(tmp_path):
200 class VivadoTclTest: # pylint: disable=too-many-instance-attributes
201 def __init__(self):
202 self.modules_folder = tmp_path / "modules"
204 # A library with some synth files and some test files
205 self.a_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "a.vhd"))
206 self.b_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "b.vhd"))
207 self.tb_a_vhd = to_tcl_path(
208 create_file(self.modules_folder / "apa" / "test" / "tb_a.vhd")
209 )
210 self.a_xdc = to_tcl_path(
211 create_file(self.modules_folder / "apa" / "scoped_constraints" / "a.xdc")
212 )
214 self.c_v = to_tcl_path(create_file(self.modules_folder / "apa" / "c.v"))
215 self.b_tcl = to_tcl_path(
216 create_file(self.modules_folder / "apa" / "scoped_constraints" / "b.tcl")
217 )
219 self.c_tcl = to_tcl_path(
220 create_file(self.modules_folder / "apa" / "ip_cores" / "c.tcl")
221 )
223 # A library with only test files
224 self.d_vhd = to_tcl_path(create_file(self.modules_folder / "zebra" / "test" / "d.vhd"))
226 self.modules = get_modules([self.modules_folder])
228 self.tcl = VivadoTcl(name="name")
230 return VivadoTclTest()
233# False positive for pytest fixtures
234# pylint: disable=redefined-outer-name
237def test_source_file_list_is_correctly_formatted(vivado_tcl_test):
238 tcl = vivado_tcl_test.tcl.create(
239 project_folder=Path(), modules=vivado_tcl_test.modules, part="", top="", run_index=1
240 )
242 # Order of files is not really deterministic
243 expected_1 = (
244 "\nread_vhdl -library apa -vhdl2008 "
245 f"{{{{{vivado_tcl_test.b_vhd}}} {{{vivado_tcl_test.a_vhd}}}}}\n"
246 )
247 expected_2 = (
248 "\nread_vhdl -library apa -vhdl2008 "
249 f"{{{{{vivado_tcl_test.a_vhd}}} {{{vivado_tcl_test.b_vhd}}}}}\n"
250 )
251 assert expected_1 in tcl or expected_2 in tcl
253 expected = f"\nread_verilog {{{vivado_tcl_test.c_v}}}\n"
254 assert expected in tcl
257def test_only_synthesis_files_added_to_create_project_tcl(vivado_tcl_test):
258 tcl = vivado_tcl_test.tcl.create(
259 project_folder=Path(), modules=vivado_tcl_test.modules, part="", top="", run_index=1
260 )
261 assert vivado_tcl_test.a_vhd in tcl and vivado_tcl_test.c_v in tcl
262 assert vivado_tcl_test.tb_a_vhd not in tcl and "tb_a.vhd" not in tcl
265def test_constraints(vivado_tcl_test):
266 tcl = vivado_tcl_test.tcl.create(
267 project_folder=Path(), modules=vivado_tcl_test.modules, part="part", top="", run_index=1
268 )
270 expected = f"\nread_xdc -ref a {{{vivado_tcl_test.a_xdc}}}\n"
271 assert expected in tcl
272 expected = f"\nread_xdc -ref b -unmanaged {{{vivado_tcl_test.b_tcl}}}\n"
273 assert expected in tcl
276def test_ip_core_files(vivado_tcl_test):
277 ip_core_file_path = vivado_tcl_test.modules_folder.parent / "my_name.tcl"
278 module = MagicMock(spec=BaseModule)
279 module.get_ip_core_files.return_value = [
280 IpCoreFile(path=ip_core_file_path, apa="hest", zebra=123)
281 ]
283 vivado_tcl_test.modules.append(module)
285 tcl = vivado_tcl_test.tcl.create(
286 project_folder=Path(), modules=vivado_tcl_test.modules, part="part", top="", run_index=1
287 )
289 assert (
290 f"""
291proc create_ip_core_c {{}} {{
292 source -notrace {{{vivado_tcl_test.c_tcl}}}
293}}
294create_ip_core_c
295"""
296 in tcl
297 )
299 assert (
300 f"""
301proc create_ip_core_my_name {{}} {{
302 set apa "hest"
303 set zebra "123"
304 source -notrace {{{to_tcl_path(ip_core_file_path)}}}
305}}
306create_ip_core_my_name
307"""
308 in tcl
309 )
312def test_create_with_ip_cores_only(vivado_tcl_test):
313 tcl = vivado_tcl_test.tcl.create(
314 project_folder=Path(),
315 modules=vivado_tcl_test.modules,
316 part="part",
317 top="",
318 run_index=1,
319 ip_cores_only=True,
320 )
321 assert vivado_tcl_test.c_tcl in tcl
322 assert vivado_tcl_test.a_vhd not in tcl
325def test_empty_library_not_in_create_project_tcl(vivado_tcl_test):
326 tcl = vivado_tcl_test.tcl.create(
327 project_folder=Path(), modules=vivado_tcl_test.modules, part="part", top="", run_index=1
328 )
329 assert "zebra" not in tcl
332def test_multiple_tcl_sources(vivado_tcl_test):
333 extra_tcl_sources = [Path("dummy.tcl"), Path("files.tcl")]
334 tcl = vivado_tcl_test.tcl.create(
335 project_folder=Path(),
336 modules=vivado_tcl_test.modules,
337 part="part",
338 top="",
339 run_index=1,
340 tcl_sources=extra_tcl_sources,
341 )
343 for filename in extra_tcl_sources:
344 assert f"\nsource -notrace {{{to_tcl_path(filename)}}}\n" in tcl
347def test_io_buffer_setting(vivado_tcl_test):
348 tcl = vivado_tcl_test.tcl.create(
349 project_folder=Path(),
350 modules=vivado_tcl_test.modules,
351 part="part",
352 top="",
353 run_index=1,
354 disable_io_buffers=True,
355 )
357 no_io_buffers_tcl = (
358 "\nset_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} "
359 "-value -no_iobuf -objects [get_runs synth_1]\n"
360 )
361 assert no_io_buffers_tcl in tcl
363 tcl = vivado_tcl_test.tcl.create(
364 project_folder=Path(),
365 modules=vivado_tcl_test.modules,
366 part="part",
367 top="",
368 run_index=1,
369 disable_io_buffers=False,
370 )
372 assert no_io_buffers_tcl not in tcl
375def test_analyze_synthesis_settings_on_and_off(vivado_tcl_test):
376 tcl = vivado_tcl_test.tcl.build(
377 project_file=Path(),
378 output_path=Path(),
379 num_threads=1,
380 run_index=1,
381 analyze_synthesis_timing=True,
382 )
383 assert "open_run" in tcl
384 assert "report_clock_interaction" in tcl
386 tcl = vivado_tcl_test.tcl.build(
387 project_file=Path(),
388 output_path=Path(),
389 num_threads=1,
390 run_index=1,
391 analyze_synthesis_timing=False,
392 )
393 # When disabled, the run should not even be opened, which saves time
394 assert "open_run" not in tcl
395 assert "report_clock_interaction" not in tcl
398def test_impl_explore(vivado_tcl_test):
399 num_runs = 4
401 tcl = vivado_tcl_test.tcl.build(
402 project_file=Path(),
403 output_path=Path(),
404 num_threads=num_runs,
405 run_index=1,
406 impl_explore=True,
407 )
409 assert f"launch_runs -jobs {num_runs} [get_runs impl_explore_*] -to_step write_bitstream" in tcl
410 assert "wait_on_runs -exit_condition ANY_ONE_MET_TIMING [get_runs impl_explore_*]" in tcl
411 assert 'reset_runs [get_runs -filter {STATUS == "Queued..."}]' in tcl
412 assert 'wait_on_runs [get_runs -filter {STATUS != "Not started"} impl_explore_*]' in tcl
413 assert 'foreach run [get_runs -filter {PROGRESS == "100%"} impl_explore_*]' in tcl