Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

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

53 

54 

55entity test_proj_top is 

56 port ( 

57 clk_in : in std_logic; 

58 input : in std_logic; 

59 clk_out : in std_logic; 

60 output : out std_logic 

61 ); 

62end entity; 

63 

64architecture a of test_proj_top is 

65 signal input_p1 : std_logic; 

66begin 

67 

68 pipe_input : process 

69 begin 

70 wait until rising_edge(clk_in); 

71 input_p1 <= input; 

72 end process; 

73 

74{code_block} 

75 

76end architecture; 

77""" 

78 

79 def setUp(self): 

80 modules_folder = self.tmp_path / "modules" 

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

82 

83 # Default top level 

84 resync = """ 

85 code_block : entity resync.resync_level 

86 generic map ( 

87 enable_input_register => false 

88 ) 

89 port map ( 

90 data_in => input_p1, 

91 

92 clk_out => clk_out, 

93 data_out => output 

94 );""" 

95 top = self.top_template.format(code_block=resync) 

96 self.top_file = create_file(modules_folder / "apa" / "test_proj_top.vhd", top) 

97 

98 self.constraint_io = """ 

99set_property -dict {package_pin H16 iostandard lvcmos33} [get_ports clk_in] 

100set_property -dict {package_pin P14 iostandard lvcmos33} [get_ports input] 

101set_property -dict {package_pin K17 iostandard lvcmos33} [get_ports clk_out] 

102set_property -dict {package_pin T16 iostandard lvcmos33} [get_ports output] 

103""" 

104 constraint_clocks = """ 

105# 250 MHz 

106create_clock -period 4 -name clk_in [get_ports clk_in] 

107create_clock -period 4 -name clk_out [get_ports clk_out] 

108""" 

109 self.constraint_file = create_file( 

110 modules_folder / "apa" / "test_proj_pinning.tcl", self.constraint_io + constraint_clocks 

111 ) 

112 constraints = [Constraint(self.constraint_file)] 

113 

114 self.modules = get_modules([modules_folder, tsfpga.TSFPGA_MODULES]) 

115 self.proj = VivadoProject( 

116 name="test_proj", 

117 modules=self.modules, 

118 part="xc7z020clg400-1", 

119 constraints=constraints, 

120 # Faster 

121 default_run_index=2, 

122 ) 

123 self.log_file = self.project_folder / "vivado.log" 

124 self.runs_folder = self.project_folder / "test_proj.runs" 

125 

126 def _create_vivado_project(self): 

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

128 

129 def test_create_project(self): 

130 self._create_vivado_project() 

131 

132 def test_synth_project(self): 

133 self._create_vivado_project() 

134 build_result = self.proj.build(self.project_folder, synth_only=True) 

135 assert build_result.success 

136 assert (self.runs_folder / "synth_2" / "hierarchical_utilization.rpt").exists() 

137 

138 def test_synth_should_fail_if_source_code_does_not_compile(self): 

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

140 

141 self._create_vivado_project() 

142 

143 build_result = self.proj.build(self.project_folder, synth_only=True) 

144 assert not build_result.success 

145 assert file_contains_string(self.log_file, "\nERROR: Run synth_2 failed.") 

146 

147 def test_synth_with_assert_false_should_fail(self): 

148 assert_false = """ 

149 assert false severity failure;""" 

150 

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

152 create_file(self.top_file, top) 

153 

154 self._create_vivado_project() 

155 

156 build_result = self.proj.build(self.project_folder, synth_only=True, run_index=2) 

157 assert not build_result.success 

158 assert file_contains_string(self.log_file, 'RTL assertion: "Assertion violation."') 

159 

160 def test_build_project(self): 

161 self._create_vivado_project() 

162 build_result = self.proj.build(self.project_folder, self.project_folder) 

163 

164 assert (self.project_folder / (self.proj.name + ".bit")).exists() 

165 assert (self.project_folder / (self.proj.name + ".bin")).exists() 

166 assert (self.runs_folder / "impl_2" / "hierarchical_utilization.rpt").exists() 

167 

168 # Sanity check some of the build result 

169 assert build_result.success 

170 

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

172 

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

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

175 

176 def test_build_with_bad_setup_timing_should_fail(self): 

177 # Do a ridiculously wide multiplication, which Vivado can't optimize away 

178 bad_timing = """ 

179 mult_block : block 

180 signal resynced_input : std_logic; 

181 begin 

182 resync_level_inst : entity resync.resync_level 

183 generic map ( 

184 enable_input_register => false 

185 ) 

186 port map ( 

187 data_in => input_p1, 

188 

189 clk_out => clk_out, 

190 data_out => resynced_input 

191 ); 

192 

193 mult : process 

194 constant bit_pattern : std_logic_vector(32 -1 downto 0) := x"deadbeef"; 

195 variable term1, term2, term3 : unsigned(bit_pattern'range); 

196 begin 

197 wait until rising_edge(clk_out); 

198 term1 := unsigned(bit_pattern); 

199 term1(28) := resynced_input; 

200 

201 term2 := unsigned(not bit_pattern); 

202 term2(22) := resynced_input; 

203 

204 output <= xor (term1 * term2); 

205 end process; 

206 end block;""" 

207 

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

209 create_file(self.top_file, top) 

210 

211 self._create_vivado_project() 

212 

213 build_result = self.proj.build(self.project_folder, self.project_folder) 

214 assert not build_result.success 

215 assert file_contains_string( 

216 self.log_file, "\nERROR: Setup/hold timing not OK after implementation run." 

217 ) 

218 

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

220 

221 def test_build_with_unhandled_clock_crossing_should_fail(self): 

222 bad_resync = """ 

223 pipe_output : process 

224 begin 

225 wait until rising_edge(clk_out); 

226 output <= input_p1; 

227 end process;""" 

228 

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

230 create_file(self.top_file, top) 

231 

232 self._create_vivado_project() 

233 

234 build_result = self.proj.build(self.project_folder, self.project_folder) 

235 assert not build_result.success 

236 assert file_contains_string( 

237 self.log_file, "\nERROR: Unhandled clock crossing in synth_2 run." 

238 ) 

239 

240 assert (self.runs_folder / "synth_2" / "hierarchical_utilization.rpt").exists() 

241 assert (self.runs_folder / "synth_2" / "timing_summary.rpt").exists() 

242 assert (self.runs_folder / "synth_2" / "clock_interaction.rpt").exists() 

243 

244 def test_build_with_bad_pulse_width_timing_should_fail(self): 

245 # Overwrite the default constraint file 

246 constraint_clocks = """ 

247# 250 MHz, but duty cycle is only 0.2 ns 

248create_clock -period 4 -waveform {1.0 1.2} -name clk_in [get_ports clk_in] 

249create_clock -period 4 -waveform {1.0 1.2} -name clk_out [get_ports clk_out] 

250""" 

251 create_file(self.constraint_file, self.constraint_io + constraint_clocks) 

252 

253 self._create_vivado_project() 

254 

255 build_result = self.proj.build(self.project_folder, self.project_folder) 

256 assert not build_result.success 

257 assert file_contains_string( 

258 self.log_file, "\nERROR: Pulse width timing violation after implementation run." 

259 ) 

260 

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

262 

263 def test_building_vivado_netlist_project(self): 

264 project = VivadoNetlistProject( 

265 name="test_proj", 

266 modules=self.modules, 

267 part="xc7z020clg400-1", 

268 # Faster 

269 default_run_index=2, 

270 build_result_checkers=[TotalLuts(LessThan(1000))], 

271 ) 

272 assert project.create(self.project_folder) 

273 

274 build_result = project.build( 

275 project_path=self.project_folder, output_path=self.project_folder 

276 ) 

277 assert build_result.success 

278 assert (self.runs_folder / "synth_2" / "hierarchical_utilization.rpt").exists()