Coverage for tsfpga/vivado/test/test_tcl.py: 100%
149 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-28 04:01 +0000
« prev ^ index » next coverage.py v6.4, created at 2022-05-28 04:01 +0000
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# --------------------------------------------------------------------------------------------------
9from collections import OrderedDict
10from pathlib import Path
11import unittest
12from unittest.mock import MagicMock
14import pytest
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.generics import BitVectorGenericValue, StringGenericValue
22from tsfpga.vivado.tcl import VivadoTcl
23from tsfpga.test import file_contains_string
25# pylint: disable=unused-import
26from tsfpga.test.conftest import fixture_tmp_path # noqa: F401
29def test_set_create_run_index():
30 tcl = VivadoTcl(name="").create(project_folder=Path(), modules=[], part="", top="", run_index=2)
31 assert "\ncurrent_run [get_runs synth_2]\n" in tcl
34def test_static_generics():
35 # Use OrderedDict here in test so that order will be preserved and we can test for equality.
36 # In real world case a normal dict can be used.
37 generics = OrderedDict(
38 enable=True,
39 disable=False,
40 integer=123,
41 slv=BitVectorGenericValue("0101"),
42 string=StringGenericValue("apa"),
43 )
45 tcl = VivadoTcl(name="").create(
46 project_folder=Path(), modules=[], part="", top="", run_index=1, generics=generics
47 )
48 expected = (
49 "\nset_property generic {enable=1'b1 disable=1'b0 integer=123 slv=4'b0101 string=apa} "
50 "[current_fileset]\n"
51 )
52 assert expected in tcl
55def test_build_step_hooks():
56 dummy = BuildStepTclHook(Path("dummy.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE")
57 files = BuildStepTclHook(Path("files.tcl"), "STEPS.ROUTE_DESIGN.TCL.PRE")
58 tcl = VivadoTcl(name="").create(
59 project_folder=Path(),
60 modules=[],
61 part="part",
62 top="",
63 run_index=1,
64 build_step_hooks=[dummy, files],
65 )
67 assert (
68 f"\nset_property STEPS.SYNTH_DESIGN.TCL.PRE {{{to_tcl_path(dummy.tcl_file)}}} ${{run}}\n"
69 in tcl
70 )
71 assert (
72 f"\nset_property STEPS.ROUTE_DESIGN.TCL.PRE {{{to_tcl_path(files.tcl_file)}}} ${{run}}\n"
73 in tcl
74 )
77def test_build_step_hooks_with_same_hook_step(tmp_path):
78 dummy = BuildStepTclHook(Path("dummy.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE")
79 files = BuildStepTclHook(Path("files.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE")
80 tcl = VivadoTcl(name="").create(
81 project_folder=tmp_path / "dummy_project_folder",
82 modules=[],
83 part="part",
84 top="",
85 run_index=1,
86 build_step_hooks=[dummy, files],
87 )
89 hook_file = tmp_path / "dummy_project_folder" / "hook_STEPS_SYNTH_DESIGN_TCL_PRE.tcl"
91 assert file_contains_string(hook_file, f"source {{{to_tcl_path(dummy.tcl_file)}}}")
92 assert file_contains_string(hook_file, f"source {{{to_tcl_path(files.tcl_file)}}}")
94 assert (
95 f"\nset_property STEPS.SYNTH_DESIGN.TCL.PRE {{{to_tcl_path(hook_file)}}} ${{run}}\n" in tcl
96 )
99def test_ip_cache_location(tmp_path):
100 tcl = VivadoTcl(name="").create(
101 project_folder=Path(), modules=[], part="part", top="", run_index=1
102 )
103 assert "config_ip_cache" not in tcl
105 tcl = VivadoTcl(name="").create(
106 project_folder=Path(), modules=[], part="part", top="", run_index=1, ip_cache_path=tmp_path
107 )
108 assert f"\nconfig_ip_cache -use_cache_location {{{to_tcl_path(tmp_path)}}}\n" in tcl
111def test_multiple_threads_is_capped_by_vivado_limits():
112 num_threads = 128
113 tcl = VivadoTcl(name="").build(
114 project_file=Path(), output_path=Path(), num_threads=num_threads, run_index=1
115 )
116 print(tcl)
117 assert "set_param general.maxThreads 32" in tcl
118 assert "set_param synth.maxThreads 8" in tcl
119 assert "launch_runs ${run} -jobs 128" in tcl
120 assert "launch_runs ${run} -jobs 128" in tcl
123def test_set_build_run_index():
124 tcl = VivadoTcl(name="").build(
125 project_file=Path(), output_path=Path(), num_threads=0, run_index=1
126 )
127 assert "impl_1" in tcl
128 assert "synth_1" in tcl
129 assert "impl_2" not in tcl
130 assert "synth_2" not in tcl
132 tcl = VivadoTcl(name="").build(
133 project_file=Path(), output_path=Path(), num_threads=0, run_index=2
134 )
135 assert "impl_2" in tcl
136 assert "synth_2" in tcl
137 assert "impl_1" not in tcl
138 assert "synth_1" not in tcl
141def test_runtime_generics():
142 tcl = VivadoTcl(name="").build(
143 project_file=Path(),
144 output_path=Path(),
145 num_threads=0,
146 run_index=0,
147 generics=dict(dummy=True),
148 )
149 expected = "\nset_property generic {dummy=1'b1} [current_fileset]\n"
150 assert expected in tcl
153def test_build_with_synth_only():
154 tcl = VivadoTcl(name="").build(
155 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, synth_only=False
156 )
157 assert "synth_" in tcl
158 assert "impl_" in tcl
160 tcl = VivadoTcl(name="").build(
161 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, synth_only=True
162 )
163 assert "synth_" in tcl
164 assert "impl_" not in tcl
167def test_build_with_from_impl():
168 tcl = VivadoTcl(name="").build(
169 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, from_impl=False
170 )
171 assert "synth_" in tcl
172 assert "impl_" in tcl
174 tcl = VivadoTcl(name="").build(
175 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, from_impl=True
176 )
177 assert "synth_" not in tcl
178 assert "impl_" in tcl
181def test_module_getters_are_called_with_correct_arguments():
182 modules = [MagicMock(spec=BaseModule)]
183 VivadoTcl(name="").create(
184 project_folder=Path(),
185 modules=modules,
186 part="",
187 top="",
188 run_index=1,
189 other_arguments=dict(apa=123, hest=456),
190 )
192 modules[0].get_synthesis_files.assert_called_once_with(apa=123, hest=456)
193 modules[0].get_scoped_constraints.assert_called_once_with(apa=123, hest=456)
194 modules[0].get_ip_core_files.assert_called_once_with(apa=123, hest=456)
197# pylint: disable=too-many-instance-attributes
198@pytest.mark.usefixtures("fixture_tmp_path")
199class TestVivadoTcl(unittest.TestCase):
201 tmp_path = None
203 def setUp(self):
204 self.modules_folder = self.tmp_path / "modules"
206 # A library with some synth files and some test files
207 self.a_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "a.vhd"))
208 self.b_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "b.vhd"))
209 self.tb_a_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "test" / "tb_a.vhd"))
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(create_file(self.modules_folder / "apa" / "ip_cores" / "c.tcl"))
221 # A library with only test files
222 self.d_vhd = to_tcl_path(create_file(self.modules_folder / "zebra" / "test" / "d.vhd"))
224 self.modules = get_modules([self.modules_folder])
226 self.tcl = VivadoTcl(name="name")
228 def test_source_file_list_is_correctly_formatted(self):
229 tcl = self.tcl.create(
230 project_folder=Path(), modules=self.modules, part="", top="", run_index=1
231 )
233 # Order of files is not really deterministic
234 expected_1 = f"\nread_vhdl -library apa -vhdl2008 {{{{{self.b_vhd}}} {{{self.a_vhd}}}}}\n"
235 expected_2 = f"\nread_vhdl -library apa -vhdl2008 {{{{{self.a_vhd}}} {{{self.b_vhd}}}}}\n"
236 assert expected_1 in tcl or expected_2 in tcl
238 expected = f"\nread_verilog {{{self.c_v}}}\n"
239 assert expected in tcl
241 def test_only_synthesis_files_added_to_create_project_tcl(self):
242 tcl = self.tcl.create(
243 project_folder=Path(), modules=self.modules, part="", top="", run_index=1
244 )
245 assert self.a_vhd in tcl and self.c_v in tcl
246 assert self.tb_a_vhd not in tcl and "tb_a.vhd" not in tcl
248 def test_constraints(self):
249 tcl = self.tcl.create(
250 project_folder=Path(), modules=self.modules, part="part", top="", run_index=1
251 )
253 expected = f"\nread_xdc -ref a {{{self.a_xdc}}}\n"
254 assert expected in tcl
255 expected = f"\nread_xdc -ref b -unmanaged {{{self.b_tcl}}}\n"
256 assert expected in tcl
258 def test_ip_core_files(self):
259 ip_core_file_path = self.tmp_path / "my_name.tcl"
260 module = MagicMock(spec=BaseModule)
261 module.get_ip_core_files.return_value = [
262 IpCoreFile(path=ip_core_file_path, apa="hest", zebra=123)
263 ]
265 self.modules.append(module)
267 tcl = self.tcl.create(
268 project_folder=Path(), modules=self.modules, part="part", top="", run_index=1
269 )
271 assert (
272 f"""
273proc create_ip_core_c {{}} {{
274 source -notrace {{{self.c_tcl}}}
275}}
276create_ip_core_c
277"""
278 in tcl
279 )
281 assert (
282 f"""
283proc create_ip_core_my_name {{}} {{
284 set apa "hest"
285 set zebra "123"
286 source -notrace {{{to_tcl_path(ip_core_file_path)}}}
287}}
288create_ip_core_my_name
289"""
290 in tcl
291 )
293 def test_create_with_ip_cores_only(self):
294 tcl = self.tcl.create(
295 project_folder=Path(),
296 modules=self.modules,
297 part="part",
298 top="",
299 run_index=1,
300 ip_cores_only=True,
301 )
302 assert self.c_tcl in tcl
303 assert self.a_vhd not in tcl
305 def test_empty_library_not_in_create_project_tcl(self):
306 tcl = self.tcl.create(
307 project_folder=Path(), modules=self.modules, part="part", top="", run_index=1
308 )
309 assert "zebra" not in tcl
311 def test_multiple_tcl_sources(self):
312 extra_tcl_sources = [Path("dummy.tcl"), Path("files.tcl")]
313 tcl = self.tcl.create(
314 project_folder=Path(),
315 modules=self.modules,
316 part="part",
317 top="",
318 run_index=1,
319 tcl_sources=extra_tcl_sources,
320 )
322 for filename in extra_tcl_sources:
323 assert f"\nsource -notrace {{{to_tcl_path(filename)}}}\n" in tcl
325 def test_io_buffer_setting(self):
326 tcl = self.tcl.create(
327 project_folder=Path(),
328 modules=self.modules,
329 part="part",
330 top="",
331 run_index=1,
332 disable_io_buffers=True,
333 )
335 no_io_buffers_tcl = (
336 "\nset_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} "
337 "-value -no_iobuf -objects [get_runs synth_1]\n"
338 )
339 assert no_io_buffers_tcl in tcl
341 tcl = self.tcl.create(
342 project_folder=Path(),
343 modules=self.modules,
344 part="part",
345 top="",
346 run_index=1,
347 disable_io_buffers=False,
348 )
350 assert no_io_buffers_tcl not in tcl
352 def test_analyze_synthesis_settings_on_and_off(self):
353 tcl = self.tcl.build(
354 project_file=Path(),
355 output_path=Path(),
356 num_threads=1,
357 run_index=1,
358 analyze_synthesis_timing=True,
359 )
360 assert "open_run" in tcl
361 assert "report_clock_interaction" in tcl
363 tcl = self.tcl.build(
364 project_file=Path(),
365 output_path=Path(),
366 num_threads=1,
367 run_index=1,
368 analyze_synthesis_timing=False,
369 )
370 # When disabled, the run should not even be opened, which saves time
371 assert "open_run" not in tcl
372 assert "report_clock_interaction" not in tcl