Coverage for tsfpga/test/functional/vivado/test_building_vivado_project.py: 0%
140 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# --------------------------------------------------------------------------------------------------
9import sys
10import unittest
12import pytest
14import tsfpga
15from tsfpga.constraint import Constraint
16from tsfpga.module import get_modules, get_hdl_modules
17from tsfpga.system_utils import create_file, run_command
18from tsfpga.vivado.build_result_checker import LessThan, TotalLuts
19from tsfpga.vivado.project import VivadoNetlistProject, VivadoProject
20from tsfpga.test import file_contains_string
23def test_building_artyz7_project(tmp_path):
24 build_py = tsfpga.TSFPGA_EXAMPLES / "build.py"
25 cmd = [
26 sys.executable,
27 build_py,
28 "artyz7",
29 "--projects-path",
30 tmp_path / "projects",
31 "--output-path",
32 tmp_path / "artifacts",
33 ]
34 run_command(cmd, cwd=tsfpga.REPO_ROOT)
35 assert (tmp_path / "artifacts" / "artyz7" / "artyz7.bit").exists()
36 assert (tmp_path / "artifacts" / "artyz7" / "artyz7.bin").exists()
37 assert (tmp_path / "artifacts" / "artyz7" / "artyz7.xsa").exists()
38 assert (tmp_path / "artifacts" / "artyz7" / "artyz7-0.0.0.0.zip").exists()
41# pylint: disable=too-many-instance-attributes
42@pytest.mark.usefixtures("fixture_tmp_path")
43class TestBasicProject(unittest.TestCase):
45 tmp_path = None
47 top_template = """
48library ieee;
49use ieee.std_logic_1164.all;
50use ieee.numeric_std.all;
52library common;
53use common.attribute_pkg.all;
55library resync;
58entity test_proj_top is
59 port (
60 clk_in : in std_logic;
61 input : in std_logic;
62 clk_out : in std_logic;
63 output : out std_logic
64 );
65end entity;
67architecture a of test_proj_top is
68 signal input_p1 : std_logic;
69begin
71 pipe_input : process
72 begin
73 wait until rising_edge(clk_in);
74 input_p1 <= input;
75 end process;
77{code_block}
79end architecture;
80"""
82 def setUp(self):
83 module_folder = self.tmp_path / "modules" / "apa"
85 self.project_folder = self.tmp_path / "vivado"
87 # Default top level
88 resync = """
89 code_block : entity resync.resync_level
90 generic map (
91 enable_input_register => false
92 )
93 port map (
94 data_in => input_p1,
96 clk_out => clk_out,
97 data_out => output
98 );"""
99 top = self.top_template.format(code_block=resync)
100 self.top_file = create_file(module_folder / "test_proj_top.vhd", top)
102 self.constraint_io = """
103set_property -dict {package_pin H16 iostandard lvcmos33} [get_ports clk_in]
104set_property -dict {package_pin P14 iostandard lvcmos33} [get_ports input]
105set_property -dict {package_pin K17 iostandard lvcmos33} [get_ports clk_out]
106set_property -dict {package_pin T16 iostandard lvcmos33} [get_ports output]
107"""
108 self.constraint_clocks = """
109# 250 MHz
110create_clock -period 4 -name clk_in [get_ports clk_in]
111create_clock -period 4 -name clk_out [get_ports clk_out]
112"""
113 self.constraint_file = create_file(
114 file=module_folder / "test_proj_pinning.tcl",
115 contents=self.constraint_io + self.constraint_clocks,
116 )
117 constraints = [Constraint(self.constraint_file)]
119 self.modules = get_hdl_modules() + get_modules(modules_folders=[module_folder.parent])
120 self.proj = VivadoProject(
121 name="test_proj",
122 modules=self.modules,
123 part="xc7z020clg400-1",
124 constraints=constraints,
125 # Faster
126 default_run_index=2,
127 )
128 self.log_file = self.project_folder / "vivado.log"
129 self.runs_folder = self.project_folder / "test_proj.runs"
131 @property
132 def create_files(self):
133 """
134 Files that should exist when the project has been created.
135 """
136 return [self.project_folder / "test_proj.xpr"]
138 @property
139 def synthesis_files(self):
140 """
141 Files that should exist when the project has been synthesized.
142 """
143 return [self.runs_folder / "synth_2" / "hierarchical_utilization.rpt"]
145 @property
146 def build_files(self):
147 """
148 Files that should exist when the project has been fully built.
149 """
150 return [
151 self.project_folder / f"{self.proj.name}.bit",
152 self.project_folder / f"{self.proj.name}.bin",
153 self.runs_folder / "impl_2" / "hierarchical_utilization.rpt",
154 ]
156 def _create_vivado_project(self):
157 assert self.proj.create(self.project_folder)
159 for path in self.create_files:
160 assert path.exists(), path
162 def test_create_project(self):
163 self._create_vivado_project()
165 def test_synth_project(self):
166 self._create_vivado_project()
168 build_result = self.proj.build(self.project_folder, synth_only=True)
169 assert build_result.success
170 for path in self.synthesis_files:
171 assert path.exists(), path
173 def test_synth_should_fail_if_source_code_does_not_compile(self):
174 create_file(self.top_file, "garbage\napa\nhest")
176 self._create_vivado_project()
178 build_result = self.proj.build(self.project_folder, synth_only=True)
179 assert not build_result.success
180 assert file_contains_string(self.log_file, "\nERROR: Run synth_2 failed.")
182 def test_synth_with_assert_false_should_fail(self):
183 assert_false = """
184 assert false severity failure;"""
186 top = self.top_template.format(code_block=assert_false)
187 create_file(self.top_file, top)
189 self._create_vivado_project()
191 build_result = self.proj.build(self.project_folder, synth_only=True, run_index=2)
192 assert not build_result.success
193 assert file_contains_string(self.log_file, 'RTL assertion: "Assertion violation."')
195 def test_build_project(self):
196 self._create_vivado_project()
198 build_result = self.proj.build(self.project_folder, self.project_folder)
199 assert build_result.success
200 for path in self.synthesis_files + self.build_files:
201 assert path.exists(), path
203 # Sanity check some of the build result
204 assert build_result.synthesis_size["Total LUTs"] == 0, build_result.synthesis_size
206 assert build_result.implementation_size["FFs"] > 0, build_result.implementation_size
207 assert build_result.implementation_size["FFs"] < 2000, build_result.implementation_size
209 def test_build_project_in_steps(self):
210 self._create_vivado_project()
211 for path in self.synthesis_files + self.build_files:
212 assert not path.exists(), path
214 build_result = self.proj.build(self.project_folder, self.project_folder, synth_only=True)
215 assert build_result.success
216 for path in self.synthesis_files:
217 assert path.exists(), path
218 for path in self.build_files:
219 assert not path.exists(), path
221 build_result = self.proj.build(self.project_folder, self.project_folder, from_impl=True)
222 assert build_result.success
223 for path in self.synthesis_files + self.build_files:
224 assert path.exists(), path
226 def test_build_with_bad_setup_timing_should_fail(self):
227 # Do a ridiculously wide multiplication, which Vivado can't optimize away
228 bad_timing = """
229 mult_block : block
230 signal resynced_input : std_logic;
231 begin
232 resync_level_inst : entity resync.resync_level
233 generic map (
234 enable_input_register => false
235 )
236 port map (
237 data_in => input_p1,
239 clk_out => clk_out,
240 data_out => resynced_input
241 );
243 mult : process
244 constant bit_pattern : std_logic_vector(32 -1 downto 0) := x"deadbeef";
245 variable term1, term2, term3 : unsigned(bit_pattern'range);
246 begin
247 wait until rising_edge(clk_out);
248 term1 := unsigned(bit_pattern);
249 term1(28) := resynced_input;
251 term2 := unsigned(not bit_pattern);
252 term2(22) := resynced_input;
254 output <= xor (term1 * term2);
255 end process;
256 end block;"""
258 top = self.top_template.format(code_block=bad_timing)
259 create_file(self.top_file, top)
261 self._create_vivado_project()
263 build_result = self.proj.build(self.project_folder, self.project_folder)
264 assert not build_result.success
265 assert file_contains_string(
266 self.log_file, "\nERROR: Setup/hold timing not OK after implementation run."
267 )
269 assert (self.runs_folder / "impl_2" / "timing_summary.rpt").exists()
271 def test_build_with_unhandled_clock_crossing_should_fail(self):
272 bad_resync = """
273 pipe_output : process
274 begin
275 wait until rising_edge(clk_out);
276 output <= input_p1;
277 end process;"""
279 top = self.top_template.format(code_block=bad_resync)
280 create_file(self.top_file, top)
282 self._create_vivado_project()
284 build_result = self.proj.build(self.project_folder, self.project_folder)
285 assert not build_result.success
286 assert file_contains_string(
287 self.log_file, "\nERROR: Unhandled clock crossing in synth_2 run."
288 )
290 assert (self.runs_folder / "synth_2" / "hierarchical_utilization.rpt").exists()
291 assert (self.runs_folder / "synth_2" / "timing_summary.rpt").exists()
292 assert (self.runs_folder / "synth_2" / "clock_interaction.rpt").exists()
294 def test_build_with_bad_pulse_width_timing_should_fail(self):
295 # Overwrite the default constraint file
296 constraint_clocks = """
297# 250 MHz, but duty cycle is only 0.2 ns
298create_clock -period 4 -waveform {1.0 1.2} -name clk_in [get_ports clk_in]
299create_clock -period 4 -waveform {1.0 1.2} -name clk_out [get_ports clk_out]
300"""
301 create_file(self.constraint_file, self.constraint_io + constraint_clocks)
303 self._create_vivado_project()
305 build_result = self.proj.build(self.project_folder, self.project_folder)
306 assert not build_result.success
307 assert file_contains_string(
308 self.log_file, "\nERROR: Pulse width timing violation after implementation run."
309 )
311 assert (self.runs_folder / "impl_2" / "pulse_width.rpt").exists()
313 def test_build_with_bad_bus_skew_should_fail(self):
314 resync_wide_word = """
315 resync_block : block
316 signal input_word, result_word : std_logic_vector(0 to 32 - 1) := (others => '0');
318 attribute dont_touch of result_word : signal is "true";
319 begin
321 input_process : process
322 begin
323 wait until rising_edge(clk_in);
324 input_word <= (others => input_p1);
325 end process;
327 result_process : process
328 begin
329 wait until rising_edge(clk_out);
330 result_word <= input_word;
331 end process;
333 end block;"""
334 top = self.top_template.format(code_block=resync_wide_word)
335 create_file(self.top_file, top)
337 # Overwrite the default constraint file
338 constraint_bus_skew = """
339set input_word [get_cells resync_block.input_word_reg*]
340set result_word [get_cells resync_block.result_word_reg*]
342# The constraints below are the same as e.g. resync_counter.tcl.
344# Vivado manages to solve ~1.2 ns skew, so the value below will not succeed
345set_bus_skew -from ${input_word} -to ${result_word} 0.8
347# Set max delay to exclude the clock crossing from regular timing analysis
348set_max_delay -datapath_only -from ${input_word} -to ${result_word} 3
349"""
350 create_file(
351 file=self.constraint_file,
352 contents=self.constraint_io + self.constraint_clocks + constraint_bus_skew,
353 )
355 self._create_vivado_project()
357 build_result = self.proj.build(self.project_folder, self.project_folder)
358 assert not build_result.success
359 assert file_contains_string(
360 self.log_file, "\nERROR: Bus skew constraints not met after implementation run."
361 )
363 assert (self.runs_folder / "impl_2" / "bus_skew.rpt").exists()
365 def test_building_vivado_netlist_project(self):
366 project = VivadoNetlistProject(
367 name="test_proj",
368 modules=self.modules,
369 part="xc7z020clg400-1",
370 # Faster
371 default_run_index=2,
372 build_result_checkers=[TotalLuts(LessThan(1000))],
373 )
374 assert project.create(self.project_folder)
376 build_result = project.build(
377 project_path=self.project_folder, output_path=self.project_folder
378 )
379 assert build_result.success
380 for path in self.synthesis_files:
381 assert path.exists(), path