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

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 

9import sys 

10import unittest 

11 

12import pytest 

13 

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 

21 

22 

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() 

39 

40 

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

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

43class TestBasicProject(unittest.TestCase): 

44 

45 tmp_path = None 

46 

47 top_template = """ 

48library ieee; 

49use ieee.std_logic_1164.all; 

50use ieee.numeric_std.all; 

51 

52library common; 

53use common.attribute_pkg.all; 

54 

55library resync; 

56 

57 

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; 

66 

67architecture a of test_proj_top is 

68 signal input_p1 : std_logic; 

69begin 

70 

71 pipe_input : process 

72 begin 

73 wait until rising_edge(clk_in); 

74 input_p1 <= input; 

75 end process; 

76 

77{code_block} 

78 

79end architecture; 

80""" 

81 

82 def setUp(self): 

83 module_folder = self.tmp_path / "modules" / "apa" 

84 

85 self.project_folder = self.tmp_path / "vivado" 

86 

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, 

95 

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) 

101 

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)] 

118 

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" 

130 

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"] 

137 

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"] 

144 

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 ] 

155 

156 def _create_vivado_project(self): 

157 assert self.proj.create(self.project_folder) 

158 

159 for path in self.create_files: 

160 assert path.exists(), path 

161 

162 def test_create_project(self): 

163 self._create_vivado_project() 

164 

165 def test_synth_project(self): 

166 self._create_vivado_project() 

167 

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 

172 

173 def test_synth_should_fail_if_source_code_does_not_compile(self): 

174 create_file(self.top_file, "garbage\napa\nhest") 

175 

176 self._create_vivado_project() 

177 

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.") 

181 

182 def test_synth_with_assert_false_should_fail(self): 

183 assert_false = """ 

184 assert false severity failure;""" 

185 

186 top = self.top_template.format(code_block=assert_false) 

187 create_file(self.top_file, top) 

188 

189 self._create_vivado_project() 

190 

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."') 

194 

195 def test_build_project(self): 

196 self._create_vivado_project() 

197 

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 

202 

203 # Sanity check some of the build result 

204 assert build_result.synthesis_size["Total LUTs"] == 0, build_result.synthesis_size 

205 

206 assert build_result.implementation_size["FFs"] > 0, build_result.implementation_size 

207 assert build_result.implementation_size["FFs"] < 2000, build_result.implementation_size 

208 

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 

213 

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 

220 

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 

225 

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, 

238 

239 clk_out => clk_out, 

240 data_out => resynced_input 

241 ); 

242 

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; 

250 

251 term2 := unsigned(not bit_pattern); 

252 term2(22) := resynced_input; 

253 

254 output <= xor (term1 * term2); 

255 end process; 

256 end block;""" 

257 

258 top = self.top_template.format(code_block=bad_timing) 

259 create_file(self.top_file, top) 

260 

261 self._create_vivado_project() 

262 

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 ) 

268 

269 assert (self.runs_folder / "impl_2" / "timing_summary.rpt").exists() 

270 

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;""" 

278 

279 top = self.top_template.format(code_block=bad_resync) 

280 create_file(self.top_file, top) 

281 

282 self._create_vivado_project() 

283 

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 ) 

289 

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() 

293 

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) 

302 

303 self._create_vivado_project() 

304 

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 ) 

310 

311 assert (self.runs_folder / "impl_2" / "pulse_width.rpt").exists() 

312 

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'); 

317 

318 attribute dont_touch of result_word : signal is "true"; 

319 begin 

320 

321 input_process : process 

322 begin 

323 wait until rising_edge(clk_in); 

324 input_word <= (others => input_p1); 

325 end process; 

326 

327 result_process : process 

328 begin 

329 wait until rising_edge(clk_out); 

330 result_word <= input_word; 

331 end process; 

332 

333 end block;""" 

334 top = self.top_template.format(code_block=resync_wide_word) 

335 create_file(self.top_file, top) 

336 

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*] 

341 

342# The constraints below are the same as e.g. resync_counter.tcl. 

343 

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 

346 

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 ) 

354 

355 self._create_vivado_project() 

356 

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 ) 

362 

363 assert (self.runs_folder / "impl_2" / "bus_skew.rpt").exists() 

364 

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) 

375 

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