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
« 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# --------------------------------------------------------------------------------------------------
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.
17from __future__ import annotations
19from typing import TYPE_CHECKING
21import pytest
22from tsfpga.system_utils import read_file
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
36if TYPE_CHECKING:
37 from hdl_registers.field.register_field import RegisterField
40@pytest.fixture
41def cpp_test_toml_code(tmp_path):
42 registers = from_toml("test", HDL_REGISTERS_TESTS / "regs_test.toml")
44 return read_file(
45 CppInterfaceGenerator(register_list=registers, output_folder=tmp_path).create()
46 )
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
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
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 )
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 )
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 )
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
95 if max_check is None:
96 assert "field_value <=" not in cpp
97 else:
98 assert f"field_value <= {max_check}" in cpp
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])
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])
106 return Checker()
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))
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))