Coverage for hdl_registers/test/unit/test_parser.py: 100%

178 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-29 22:03 +0000

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the hdl_registers project, a HDL register generator fast enough to be run 

5# in real time. 

6# https://hdl-registers.com 

7# https://gitlab.com/hdl_registers/hdl_registers 

8# -------------------------------------------------------------------------------------------------- 

9 

10# Standard libraries 

11import unittest 

12 

13# Third party libraries 

14import pytest 

15from tsfpga.system_utils import create_file 

16 

17# First party libraries 

18from hdl_registers.parser import from_toml, load_toml_file 

19from hdl_registers.register import Register 

20 

21 

22def get_test_default_registers(): 

23 registers = [ 

24 Register("config", 0, "r_w", "Configuration register."), 

25 ] 

26 return registers 

27 

28 

29def test_bit_vector_without_width_should_raise_exception(tmp_path): 

30 toml_file = create_file( 

31 tmp_path / "regs.toml", 

32 """ 

33[register.test_reg] 

34 

35mode = "w" 

36 

37[register.test_reg.bit_vector.test_bit_vector] 

38 

39default_value = "0" 

40""", 

41 ) 

42 

43 with pytest.raises(ValueError) as exception_info: 

44 from_toml(module_name="", toml_file=toml_file) 

45 assert str(exception_info.value) == ( 

46 f'Bit vector "test_bit_vector" in register "test_reg" in file {toml_file} does not have ' 

47 'a "width" property' 

48 ) 

49 

50 

51# pylint: disable=too-many-public-methods 

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

53class TestRegisterParser(unittest.TestCase): 

54 

55 tmp_path = None 

56 

57 module_name = "sensor" 

58 toml_data = """\ 

59 

60################################################################################ 

61[register.data] 

62 

63mode = "w" 

64 

65 

66################################################################################ 

67[register.irq] 

68 

69mode = "r_w" 

70description = "Interrupt register" 

71 

72[register.irq.bit.bad] 

73 

74description = "Bad things happen" 

75 

76[register.irq.bit.not_good] 

77 

78description = "" 

79default_value = "1" 

80 

81[register.irq.bit_vector.interrupts] 

82 

83width = 4 

84description = "Many interrupts" 

85default_value = "0110" 

86 

87 

88 

89################################################################################ 

90[register_array.configuration] 

91 

92array_length = 3 

93description = "A register array" 

94 

95# ------------------------------------------------------------------------------ 

96[register_array.configuration.register.input_settings] 

97 

98description = "Input configuration" 

99mode = "r_w" 

100 

101[register_array.configuration.register.input_settings.bit.enable] 

102 

103description = "Enable things" 

104default_value = "1" 

105 

106[register_array.configuration.register.input_settings.bit.disable] 

107 

108description = "" 

109default_value = "0" 

110 

111 

112# ------------------------------------------------------------------------------ 

113[register_array.configuration.register.output_settings] 

114 

115mode = "w" 

116 

117[register_array.configuration.register.output_settings.bit_vector.data] 

118 

119width=16 

120description = "Some data" 

121default_value="0000000000000011" 

122 

123 

124################################################################################ 

125""" 

126 

127 def setUp(self): 

128 self.toml_file = create_file(self.tmp_path / "sensor_regs.toml", self.toml_data) 

129 

130 def create_toml_file_with_extras(self, toml_extras): 

131 data = self.toml_data + toml_extras 

132 create_file(self.toml_file, data) 

133 

134 def test_order_of_registers_and_bits(self): 

135 registers = from_toml(self.module_name, self.toml_file).register_objects 

136 

137 assert registers[0].name == "data" 

138 assert registers[0].mode == "w" 

139 assert registers[0].index == 0 

140 assert registers[0].description == "" 

141 assert registers[0].default_value == 0 

142 assert registers[0].fields == [] 

143 

144 assert registers[1].name == "irq" 

145 assert registers[1].mode == "r_w" 

146 assert registers[1].index == 1 

147 assert registers[1].description == "Interrupt register" 

148 assert registers[1].default_value == 6 * 2**2 + 1 * 2**1 

149 assert registers[1].fields[0].name == "bad" 

150 assert registers[1].fields[0].description == "Bad things happen" 

151 assert registers[1].fields[0].default_value == "0" 

152 assert registers[1].fields[1].name == "not_good" 

153 assert registers[1].fields[1].description == "" 

154 assert registers[1].fields[1].default_value == "1" 

155 assert registers[1].fields[2].name == "interrupts" 

156 assert registers[1].fields[2].description == "Many interrupts" 

157 assert registers[1].fields[2].width == 4 

158 assert registers[1].fields[2].default_value == "0110" 

159 

160 assert registers[2].name == "configuration" 

161 assert registers[2].length == 3 

162 assert registers[2].description == "A register array" 

163 assert registers[2].index == 2 + 2 * 3 - 1 

164 assert len(registers[2].registers) == 2 

165 assert registers[2].registers[0].name == "input_settings" 

166 assert registers[2].registers[0].mode == "r_w" 

167 assert registers[2].registers[0].index == 0 

168 assert registers[2].registers[0].description == "Input configuration" 

169 assert registers[2].registers[0].default_value == 1 

170 assert registers[2].registers[0].fields[0].name == "enable" 

171 assert registers[2].registers[0].fields[0].description == "Enable things" 

172 assert registers[2].registers[0].fields[0].default_value == "1" 

173 assert registers[2].registers[0].fields[1].name == "disable" 

174 assert registers[2].registers[0].fields[1].description == "" 

175 assert registers[2].registers[0].fields[1].default_value == "0" 

176 assert registers[2].registers[1].name == "output_settings" 

177 assert registers[2].registers[1].mode == "w" 

178 assert registers[2].registers[1].index == 1 

179 assert registers[2].registers[1].description == "" 

180 assert registers[2].registers[1].default_value == 3 

181 assert registers[2].registers[1].fields[0].name == "data" 

182 assert registers[2].registers[1].fields[0].description == "Some data" 

183 assert registers[2].registers[1].fields[0].width == 16 

184 assert registers[2].registers[1].fields[0].default_value == "0000000000000011" 

185 

186 def test_default_registers(self): 

187 default_registers = get_test_default_registers() 

188 num_default_registers = len(default_registers) 

189 toml_registers = from_toml(self.module_name, self.toml_file, default_registers) 

190 

191 # The registers from this test are appended at the end 

192 assert toml_registers.get_register("data").index == num_default_registers 

193 assert toml_registers.get_register("irq").index == num_default_registers + 1 

194 

195 def test_load_nonexistent_toml_file_should_raise_exception(self): 

196 file = self.toml_file.with_name("apa.toml") 

197 with pytest.raises(FileNotFoundError) as exception_info: 

198 load_toml_file(file) 

199 assert str(exception_info.value) == f"Requested TOML file does not exist: {file}" 

200 

201 def test_load_dirty_toml_file_should_raise_exception(self): 

202 self.create_toml_file_with_extras("apa") 

203 

204 with pytest.raises(ValueError) as exception_info: 

205 load_toml_file(self.toml_file) 

206 assert str(exception_info.value).startswith( 

207 f"Error while parsing TOML file {self.toml_file}:\nExpected '=' after a key" 

208 ) 

209 

210 def test_plain_register_with_array_length_attribute_should_raise_exception(self): 

211 self.create_toml_file_with_extras( 

212 """ 

213[register.apa] 

214 

215mode = "r_w" 

216array_length = 4 

217""" 

218 ) 

219 

220 with pytest.raises(ValueError) as exception_info: 

221 from_toml(self.module_name, self.toml_file) 

222 assert ( 

223 str(exception_info.value) 

224 == f'Error while parsing register "apa" in {self.toml_file}: Unknown key "array_length"' 

225 ) 

226 

227 def test_register_array_but_no_array_length_attribute_should_raise_exception(self): 

228 self.create_toml_file_with_extras( 

229 """ 

230[register_array.apa] 

231 

232[register_array.apa.register.hest] 

233 

234mode = "r_w" 

235""" 

236 ) 

237 

238 with pytest.raises(ValueError) as exception_info: 

239 from_toml(self.module_name, self.toml_file) 

240 assert ( 

241 str(exception_info.value) 

242 == f'Register array "apa" in {self.toml_file} does not have "array_length" attribute' 

243 ) 

244 

245 def test_register_in_array_with_no_mode_attribute_should_raise_exception(self): 

246 self.create_toml_file_with_extras( 

247 """ 

248[register_array.apa] 

249 

250array_length = 2 

251 

252[register_array.apa.register.hest] 

253 

254description = "nothing" 

255""" 

256 ) 

257 

258 with pytest.raises(ValueError) as exception_info: 

259 from_toml(self.module_name, self.toml_file) 

260 assert ( 

261 str(exception_info.value) 

262 == f'Register "hest" within array "apa" in {self.toml_file} does not have "mode" field' 

263 ) 

264 

265 def test_register_with_no_mode_field_should_raise_exception(self): 

266 self.create_toml_file_with_extras( 

267 """ 

268[register.apa] 

269 

270description = "w" 

271""" 

272 ) 

273 

274 with pytest.raises(ValueError) as exception_info: 

275 from_toml(self.module_name, self.toml_file) 

276 assert ( 

277 str(exception_info.value) 

278 == f'Register "apa" in {self.toml_file} does not have "mode" field' 

279 ) 

280 

281 def test_two_registers_with_same_name_should_raise_exception(self): 

282 self.create_toml_file_with_extras( 

283 """ 

284[register.irq] 

285 

286mode = "w" 

287""" 

288 ) 

289 

290 with pytest.raises(ValueError) as exception_info: 

291 from_toml(self.module_name, self.toml_file) 

292 expected = ( 

293 f"Error while parsing TOML file {self.toml_file}:\n" 

294 "Cannot declare ('register', 'irq') twice" 

295 ) 

296 assert str(exception_info.value).startswith(expected) 

297 

298 def test_register_with_same_name_as_register_array_should_raise_exception(self): 

299 self.create_toml_file_with_extras( 

300 """ 

301[register.configuration] 

302 

303mode = "w" 

304""" 

305 ) 

306 

307 with pytest.raises(ValueError) as exception_info: 

308 from_toml(self.module_name, self.toml_file) 

309 assert str(exception_info.value) == f'Duplicate name "configuration" in {self.toml_file}' 

310 

311 def test_two_bits_with_same_name_should_raise_exception(self): 

312 self.create_toml_file_with_extras( 

313 """ 

314[register.test_reg] 

315 

316mode = "w" 

317 

318[register.test_reg.bit.test_bit] 

319 

320description = "Declaration 1" 

321 

322[register.test_reg.bit.test_bit] 

323 

324description = "Declaration 2" 

325""" 

326 ) 

327 

328 with pytest.raises(ValueError) as exception_info: 

329 from_toml(self.module_name, self.toml_file) 

330 

331 expected = ( 

332 f"Error while parsing TOML file {self.toml_file}:\n" 

333 "Cannot declare ('register', 'test_reg', 'bit', 'test_bit') twice" 

334 ) 

335 assert str(exception_info.value).startswith(expected) 

336 

337 def test_overriding_default_register(self): 

338 self.create_toml_file_with_extras( 

339 """ 

340[register.config] 

341 

342description = "apa" 

343""" 

344 ) 

345 toml_registers = from_toml(self.module_name, self.toml_file, get_test_default_registers()) 

346 

347 assert toml_registers.get_register("config").description == "apa" 

348 

349 def test_changing_mode_of_default_register_should_raise_exception(self): 

350 self.create_toml_file_with_extras( 

351 """ 

352[register.config] 

353 

354mode = "w" 

355""" 

356 ) 

357 

358 with pytest.raises(ValueError) as exception_info: 

359 from_toml(self.module_name, self.toml_file, get_test_default_registers()) 

360 assert ( 

361 str(exception_info.value) 

362 == f'Overloading register "config" in {self.toml_file}, one can not change "mode" ' 

363 "from default" 

364 ) 

365 

366 def test_unknown_register_field_should_raise_exception(self): 

367 self.create_toml_file_with_extras( 

368 """ 

369[register.test_reg] 

370 

371mode = "w" 

372dummy = 3 

373""" 

374 ) 

375 

376 with pytest.raises(ValueError) as exception_info: 

377 from_toml(self.module_name, self.toml_file) 

378 assert ( 

379 str(exception_info.value) 

380 == f'Error while parsing register "test_reg" in {self.toml_file}: Unknown key "dummy"' 

381 ) 

382 

383 def test_unknown_register_array_field_should_raise_exception(self): 

384 self.create_toml_file_with_extras( 

385 """ 

386[register_array.test_array] 

387 

388array_length = 2 

389dummy = 3 

390 

391[register_array.test_array.hest] 

392 

393mode = "r" 

394""" 

395 ) 

396 

397 with pytest.raises(ValueError) as exception_info: 

398 from_toml(self.module_name, self.toml_file) 

399 assert ( 

400 str(exception_info.value) 

401 == f'Error while parsing register array "test_array" in {self.toml_file}: ' 

402 'Unknown key "dummy"' 

403 ) 

404 

405 def test_unknown_register_field_in_register_array_should_raise_exception(self): 

406 self.create_toml_file_with_extras( 

407 """ 

408[register_array.test_array] 

409 

410array_length = 2 

411 

412[register_array.test_array.register.hest] 

413 

414mode = "r" 

415dummy = 3 

416""" 

417 ) 

418 

419 with pytest.raises(ValueError) as exception_info: 

420 from_toml(self.module_name, self.toml_file) 

421 assert ( 

422 str(exception_info.value) 

423 == f'Error while parsing register "hest" in array "test_array" in {self.toml_file}: ' 

424 'Unknown key "dummy"' 

425 ) 

426 

427 def test_unknown_bit_field_should_raise_exception(self): 

428 self.create_toml_file_with_extras( 

429 """ 

430[register.dummy_reg] 

431 

432mode = "w" 

433 

434[register.dummy_reg.bit.dummy_bit] 

435 

436description = "Stuff" 

437height = 3 

438""" 

439 ) 

440 

441 with pytest.raises(ValueError) as exception_info: 

442 from_toml(self.module_name, self.toml_file) 

443 assert ( 

444 str(exception_info.value) 

445 == f'Error while parsing bit "dummy_bit" in register "dummy_reg" in {self.toml_file}: ' 

446 'Unknown key "height"' 

447 ) 

448 

449 def test_unknown_bit_vector_field_should_raise_exception(self): 

450 self.create_toml_file_with_extras( 

451 """ 

452[register.dummy_reg] 

453 

454mode = "w" 

455 

456[register.dummy_reg.bit_vector.dummy_bit_vector] 

457 

458description = "Stuff" 

459width = 3 

460height = 4 

461 

462""" 

463 ) 

464 

465 with pytest.raises(ValueError) as exception_info: 

466 from_toml(self.module_name, self.toml_file) 

467 assert ( 

468 str(exception_info.value) 

469 == f'Error while parsing bit vector "dummy_bit_vector" in register "dummy_reg" in ' 

470 f'{self.toml_file}: Unknown key "height"' 

471 ) 

472 

473 def test_constants_in_toml(self): 

474 self.create_toml_file_with_extras( 

475 """ 

476[constant.data_width] 

477 

478value = 0xf 

479description = "the width" 

480 

481[constant.apa] 

482 

483value = 3.14 

484""" 

485 ) 

486 

487 register_list = from_toml(self.module_name, self.toml_file) 

488 assert len(register_list.constants) == 2 

489 

490 assert register_list.constants[0].name == "data_width" 

491 assert register_list.constants[0].value == 15 

492 assert register_list.constants[0].description == "the width" 

493 

494 assert register_list.constants[1].name == "apa" 

495 assert register_list.constants[1].value == 3.14 

496 assert register_list.constants[1].description == "" 

497 

498 def test_constant_without_value_should_raise_exception(self): 

499 self.create_toml_file_with_extras( 

500 """ 

501[constant.data_width] 

502 

503description = "the width" 

504""" 

505 ) 

506 with pytest.raises(ValueError) as exception_info: 

507 from_toml(self.module_name, self.toml_file) 

508 assert ( 

509 str(exception_info.value) 

510 == f'Constant "data_width" in {self.toml_file} does not have "value" field' 

511 ) 

512 

513 def test_unknown_constant_field_should_raise_exception(self): 

514 self.create_toml_file_with_extras( 

515 """ 

516[constant.data_width] 

517 

518value = 0xf 

519default_value = 0xf 

520""" 

521 ) 

522 

523 with pytest.raises(ValueError) as exception_info: 

524 from_toml(self.module_name, self.toml_file) 

525 assert ( 

526 str(exception_info.value) 

527 == f'Error while parsing constant "data_width" in {self.toml_file}: ' 

528 'Unknown key "default_value"' 

529 )