Coverage for hdl_registers/generator/cpp/test/test_register_cpp_generator.py: 99%

101 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-02 20:52 +0000

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

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

3# 

4# This file is part of the hdl-registers project, an HDL register generator fast enough to run 

5# in real time. 

6# https://hdl-registers.com 

7# https://github.com/hdl-registers/hdl-registers 

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

9 

10# Some limited unit tests that check the generated code. 

11# Note that the generated C++ code is also functionally tested in the 

12# file 'test_compiled_cpp_code.py'. 

13# That test generates C++ code from an example register set, compiles it and performs some 

14# run-time assertions in a C program. 

15# That test is considered more meaningful and exhaustive than a unit test would be. 

16 

17from __future__ import annotations 

18 

19from typing import TYPE_CHECKING 

20 

21import pytest 

22from tsfpga.system_utils import read_file 

23 

24from hdl_registers import HDL_REGISTERS_TESTS 

25from hdl_registers.field.numerical_interpretation import ( 

26 Signed, 

27 SignedFixedPoint, 

28 UnsignedFixedPoint, 

29) 

30from hdl_registers.generator.cpp.implementation import CppImplementationGenerator 

31from hdl_registers.generator.cpp.interface import CppInterfaceGenerator 

32from hdl_registers.parser.toml import from_toml 

33from hdl_registers.register_list import RegisterList 

34from hdl_registers.register_modes import REGISTER_MODES 

35 

36if TYPE_CHECKING: 

37 from hdl_registers.field.register_field import RegisterField 

38 

39 

40@pytest.fixture 

41def cpp_test_toml_code(tmp_path): 

42 registers = from_toml("test", HDL_REGISTERS_TESTS / "regs_test.toml") 

43 

44 return read_file( 

45 CppInterfaceGenerator(register_list=registers, output_folder=tmp_path).create() 

46 ) 

47 

48 

49def test_read_only_register_has_no_setters(cpp_test_toml_code): 

50 assert "get_status" in cpp_test_toml_code 

51 assert "set_status" not in cpp_test_toml_code 

52 

53 

54def test_write_only_register_has_no_setters(cpp_test_toml_code): 

55 assert "set_command" in cpp_test_toml_code 

56 assert "get_command" not in cpp_test_toml_code 

57 

58 

59@pytest.fixture 

60def cpp_range_test(): 

61 class Checker: 

62 def __init__(self): 

63 self.register_list = RegisterList(name="test") 

64 self.register = self.register_list.append_register( 

65 name="register", mode=REGISTER_MODES["r_w"], description="" 

66 ) 

67 

68 # Output folder does not matter. 

69 self.generator = CppImplementationGenerator( 

70 register_list=self.register_list, 

71 output_folder=HDL_REGISTERS_TESTS / "cpp_test", 

72 ) 

73 

74 def get_cpp(self, field: RegisterField, setter_or_getter: str): 

75 # It's hard to test this using the public interface. 

76 # Would be sketchy to find the checker code of the specific field within all the 

77 # generated code. 

78 # So we use the private method, bad practice as it may be. 

79 return self.generator._get_field_checker( # noqa: SLF001 

80 field=field, setter_or_getter=setter_or_getter 

81 ) 

82 

83 def check( 

84 self, 

85 field: RegisterField, 

86 getter: tuple[int | None, int | None], 

87 setter: tuple[int | None, int | None], 

88 ): 

89 def _check(cpp: str, min_check: int | None, max_check: int | None): 

90 if min_check is None: 

91 assert "field_value >=" not in cpp 

92 else: 

93 assert f"field_value >= {min_check}" in cpp 

94 

95 if max_check is None: 

96 assert "field_value <=" not in cpp 

97 else: 

98 assert f"field_value <= {max_check}" in cpp 

99 

100 getter_code = self.get_cpp(field=field, setter_or_getter="getter") 

101 _check(cpp=getter_code, min_check=getter[0], max_check=getter[1]) 

102 

103 setter_code = self.get_cpp(field=field, setter_or_getter="setter") 

104 _check(cpp=setter_code, min_check=setter[0], max_check=setter[1]) 

105 

106 return Checker() 

107 

108 

109def test_field_range_check_bit(cpp_range_test): 

110 field = cpp_range_test.register.append_bit(name="a", description="", default_value="0") 

111 cpp_range_test.check(field=field, getter=(None, None), setter=(None, None)) 

112 

113 

114def test_field_range_check_bit_vector_unsigned(cpp_range_test): 

115 field = cpp_range_test.register.append_bit_vector( 

116 name="a", description="", width=4, default_value="0000" 

117 ) 

118 cpp_range_test.check(field=field, getter=(None, None), setter=(None, 15)) 

119 

120 register = cpp_range_test.register_list.append_register( 

121 name="register", mode=REGISTER_MODES["r_w"], description="" 

122 ) 

123 field = register.append_bit_vector(name="a", description="", width=32, default_value=32 * "0") 

124 # Width now matches 'uint32_t'. 

125 cpp_range_test.check(field=field, getter=(None, None), setter=(None, None)) 

126 

127 

128def test_field_range_check_bit_vector_signed(cpp_range_test): 

129 field = cpp_range_test.register.append_bit_vector( 

130 name="a", 

131 description="", 

132 width=4, 

133 default_value="0000", 

134 numerical_interpretation=Signed(bit_width=4), 

135 ) 

136 cpp_range_test.check(field=field, getter=(None, None), setter=(-8, 7)) 

137 

138 register = cpp_range_test.register_list.append_register( 

139 name="register", mode=REGISTER_MODES["r_w"], description="" 

140 ) 

141 field = register.append_bit_vector( 

142 name="a", 

143 description="", 

144 width=32, 

145 default_value=32 * "0", 

146 numerical_interpretation=Signed(bit_width=32), 

147 ) 

148 # Width now matches 'int32_t'. 

149 cpp_range_test.check(field=field, getter=(None, None), setter=(None, None)) 

150 

151 

152def test_field_range_check_bit_vector_ufixed(cpp_range_test): 

153 field = cpp_range_test.register.append_bit_vector( 

154 name="a", 

155 description="", 

156 width=4, 

157 default_value="0000", 

158 numerical_interpretation=UnsignedFixedPoint(max_bit_index=1, min_bit_index=-2), 

159 ) 

160 cpp_range_test.check(field=field, getter=(None, None), setter=(0.0, 3.75)) 

161 

162 

163def test_field_range_check_bit_vector_sfixed(cpp_range_test): 

164 field = cpp_range_test.register.append_bit_vector( 

165 name="a", 

166 description="", 

167 width=4, 

168 default_value="0000", 

169 numerical_interpretation=SignedFixedPoint(max_bit_index=1, min_bit_index=-2), 

170 ) 

171 cpp_range_test.check(field=field, getter=(None, None), setter=(-2, 1.75)) 

172 

173 

174def test_field_range_check_enumeration(cpp_range_test): 

175 field = cpp_range_test.register.append_enumeration( 

176 name="a", description="", elements={"a": "", "b": "", "c": ""}, default_value="a" 

177 ) 

178 cpp_range_test.check(field=field, getter=(None, 2), setter=(None, 2)) 

179 

180 field = cpp_range_test.register.append_enumeration( 

181 name="b", description="", elements={"a": "", "b": "", "c": "", "d": ""}, default_value="a" 

182 ) 

183 # Upper limit is now the native width of the field. 

184 cpp_range_test.check(field=field, getter=(None, None), setter=(None, 3)) 

185 

186 

187def test_field_range_check_integer_unsigned(cpp_range_test): 

188 field = cpp_range_test.register.append_integer( 

189 name="a", description="", min_value=0, max_value=13, default_value=0 

190 ) 

191 cpp_range_test.check(field=field, getter=(None, 13), setter=(None, 13)) 

192 

193 field = cpp_range_test.register.append_integer( 

194 name="a", description="", min_value=0, max_value=15, default_value=0 

195 ) 

196 # Upper limit is now the native width of the field. 

197 cpp_range_test.check(field=field, getter=(None, None), setter=(None, 15)) 

198 

199 register = cpp_range_test.register_list.append_register( 

200 name="register", mode=REGISTER_MODES["r_w"], description="" 

201 ) 

202 field = register.append_integer( 

203 name="a", description="", min_value=0, max_value=4294967294, default_value=0 

204 ) 

205 # Width matches 'uint32_t', but upper limit is not native. 

206 cpp_range_test.check(field=field, getter=(None, 4294967294), setter=(None, 4294967294)) 

207 

208 register = cpp_range_test.register_list.append_register( 

209 name="register", mode=REGISTER_MODES["r_w"], description="" 

210 ) 

211 field = register.append_integer( 

212 name="a", description="", min_value=0, max_value=4294967295, default_value=0 

213 ) 

214 # Upper limit is the native width of the field and matches 'uint32_t'. 

215 cpp_range_test.check(field=field, getter=(None, None), setter=(None, None)) 

216 

217 

218def test_field_range_check_integer_signed(cpp_range_test): 

219 field = cpp_range_test.register.append_integer( 

220 name="a", description="", min_value=-1, max_value=13, default_value=0 

221 ) 

222 cpp_range_test.check(field=field, getter=(-1, 13), setter=(-1, 13)) 

223 

224 field = cpp_range_test.register.append_integer( 

225 name="a", description="", min_value=-16, max_value=13, default_value=0 

226 ) 

227 # Lower limit is now the native width of the field. 

228 cpp_range_test.check(field=field, getter=(None, 13), setter=(-16, 13)) 

229 

230 field = cpp_range_test.register.append_integer( 

231 name="a", description="", min_value=-16, max_value=15, default_value=0 

232 ) 

233 # Upper limit is now the native width of the field. 

234 cpp_range_test.check(field=field, getter=(None, None), setter=(-16, 15)) 

235 

236 register = cpp_range_test.register_list.append_register( 

237 name="register", mode=REGISTER_MODES["r_w"], description="" 

238 ) 

239 field = register.append_integer( 

240 name="a", description="", min_value=-2147483647, max_value=13, default_value=0 

241 ) 

242 # Lower limit matches 'int32_t' but is not native. 

243 cpp_range_test.check(field=field, getter=(-2147483647, 13), setter=(-2147483647, 13)) 

244 

245 register = cpp_range_test.register_list.append_register( 

246 name="register", mode=REGISTER_MODES["r_w"], description="" 

247 ) 

248 field = register.append_integer( 

249 name="a", description="", min_value=-2147483648, max_value=13, default_value=0 

250 ) 

251 # Lower limit is the native width of the field and matches 'int32_t'. 

252 cpp_range_test.check(field=field, getter=(None, 13), setter=(None, 13)) 

253 

254 register = cpp_range_test.register_list.append_register( 

255 name="register", mode=REGISTER_MODES["r_w"], description="" 

256 ) 

257 field = register.append_integer( 

258 name="a", description="", min_value=-1, max_value=2147483644, default_value=0 

259 ) 

260 # Upper limit matches 'int32_t' but is not native. 

261 cpp_range_test.check(field=field, getter=(-1, 2147483644), setter=(-1, 2147483644)) 

262 

263 register = cpp_range_test.register_list.append_register( 

264 name="register", mode=REGISTER_MODES["r_w"], description="" 

265 ) 

266 field = register.append_integer( 

267 name="a", description="", min_value=-1, max_value=2147483647, default_value=0 

268 ) 

269 # Upper limit is the native width of the field and matches 'int32_t'. 

270 cpp_range_test.check(field=field, getter=(-1, None), setter=(-1, None))