Coverage for hdl_registers/generator/vhdl/record_package.py: 99%
211 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-20 22:43 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-20 22:43 +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# --------------------------------------------------------------------------------------------------
10from __future__ import annotations
12from typing import TYPE_CHECKING, Any
14from hdl_registers.field.bit import Bit
15from hdl_registers.field.bit_vector import BitVector
16from hdl_registers.field.enumeration import Enumeration
17from hdl_registers.field.integer import Integer
18from hdl_registers.register_mode import HardwareAccessDirection, SoftwareAccessDirection
20from .vhdl_generator_common import VhdlGeneratorCommon
22if TYPE_CHECKING:
23 from pathlib import Path
25 from hdl_registers.register import Register
26 from hdl_registers.register_array import RegisterArray
29class VhdlRecordPackageGenerator(VhdlGeneratorCommon):
30 """
31 Generate a VHDL package with register record types containing natively-typed members for
32 each register field.
33 See the :ref:`generator_vhdl` article for usage details.
35 * For each register, plain or in array, a record with natively-typed members for each
36 register field.
37 * For each register array, a correctly-ranged array of records for the registers in
38 that array.
39 * Combined record with all the registers and register arrays.
40 One each for registers in the up direction and in the down direction.
41 * Constants with default values for all of the above types.
42 * Conversion functions to/from ``std_logic_vector`` representation for all of the
43 above types.
45 The generated VHDL file needs also the generated package
46 from :class:`.VhdlRegisterPackageGenerator`.
47 See also :ref:`vhdl_dependencies` for further dependencies.
48 """
50 __version__ = "1.0.0"
52 SHORT_DESCRIPTION = "VHDL record package"
54 @property
55 def output_file(self) -> Path:
56 """
57 Result will be placed in this file.
58 """
59 return self.output_folder / f"{self.name}_register_record_pkg.vhd"
61 def create(
62 self,
63 **kwargs: Any, # noqa: ANN401
64 ) -> Path:
65 """
66 See super class for API details.
68 Overloaded here because this package file shall only be created if the register list
69 actually has any registers.
70 """
71 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs)
73 def get_code(
74 self,
75 **kwargs: Any, # noqa: ANN401, ARG002
76 ) -> str:
77 """
78 Get a complete VHDL package with register record types.
79 """
80 package_name = self.output_file.stem
82 vhdl = f"""\
83library ieee;
84use ieee.fixed_pkg.all;
85use ieee.std_logic_1164.all;
86use ieee.numeric_std.all;
88library register_file;
89use register_file.register_file_pkg.register_t;
91use work.{self.name}_regs_pkg.all;
94package {package_name} is
96"""
98 if self.register_list.register_objects:
99 vhdl += f"""\
100{self._register_field_records()}\
101{self._register_records()}\
102{self._register_was_accessed()}\
103"""
105 vhdl += "end package;\n"
107 if self.register_list.register_objects:
108 vhdl += f"""
109package body {package_name} is
111{self._register_field_record_conversion_implementations()}\
112{self._register_record_conversion_implementations()}\
113{self._register_was_accessed_conversion_implementations()}\
114end package body;
115"""
117 return vhdl
119 def _register_field_records(self) -> str:
120 """
121 For every register (plain or in array) that has at least one field:
123 * Record with members for each field that are of the correct native VHDL type.
124 * Default value constant for the above record.
125 * Function to convert the above record to SLV.
126 * Function to convert a register SLV to the above record.
127 """
128 vhdl = """\
129 -- -----------------------------------------------------------------------------
130 -- Record with correctly-typed members for each field in each register.
131"""
133 for register, register_array in self.iterate_registers():
134 if not register.fields:
135 continue
137 register_name = self.qualified_register_name(
138 register=register, register_array=register_array
139 )
140 register_description = self.register_description(
141 register=register, register_array=register_array
142 )
144 record = ""
145 init = []
147 for field in register.fields:
148 field_name = self.qualified_field_name(
149 register=register, register_array=register_array, field=field
150 )
151 init.append(f"{field.name} => {field_name}_init")
153 field_type_name = self.field_type_name(
154 register=register, register_array=register_array, field=field
155 )
156 record += f" {field.name} : {field_type_name};\n"
158 init_str = " " + ",\n ".join(init)
160 vhdl += f"""\
161 -- Fields in the {register_description} as a record.
162 type {register_name}_t is record
163{record}\
164 end record;
165 -- Default value for the {register_description} as a record.
166 constant {register_name}_init : {register_name}_t := (
167{init_str}
168 );
169 -- Convert a record of the {register_description} to SLV.
170 function to_slv(data : {register_name}_t) return register_t;
171 -- Convert an SLV register value to the record for the {register_description}.
172 function to_{register_name}(data : register_t) return {register_name}_t;
174"""
176 return vhdl
178 def _register_records(self) -> str:
179 """
180 Get two records,
181 * One with all the registers that are in the 'up' direction.
182 * One with all the registers that are in the 'down' direction.
184 Along with conversion function declarations to/from SLV.
186 In order to create the above records, we have to create partial-array records for each
187 register array.
188 One record for 'up' and one for 'down', with all registers in the array that are in
189 that direction.
190 """
191 vhdl = ""
193 vhdl += self._array_records(direction=None)
194 vhdl += self._register_record(direction=None)
195 vhdl += f"""\
196 -- Convert record to SLV register list.
197 function to_slv(data : {self.name}_registers_t) return {self.name}_regs_t;
199"""
201 direction = HardwareAccessDirection.UP
203 if self.has_any_hardware_accessible_register(direction=direction):
204 vhdl += self._array_records(direction=direction)
205 vhdl += self._register_record(direction=direction)
206 vhdl += f"""\
207 -- Convert record with everything in the '{direction.name.lower()}' direction to SLV \
208register list.
209 function to_slv(data : {self.name}_regs_{direction.name.lower()}_t) return {self.name}_regs_t;
211"""
213 direction = HardwareAccessDirection.DOWN
215 if self.has_any_hardware_accessible_register(direction=direction):
216 vhdl += self._array_records(direction=direction)
217 vhdl += self._register_record(direction=direction)
218 vhdl += f"""\
219 -- Convert SLV register list to record with everything in the \
220'{direction.name.lower()}' direction.
221 function to_{self.name}_regs_{direction.name.lower()}(data : {self.name}_regs_t) \
222return {self.name}_regs_{direction.name.lower()}_t;
224"""
226 return vhdl
228 def _array_records(self, direction: HardwareAccessDirection | None) -> str:
229 """
230 For every register array that has at least one register in the specified direction:
232 * Record with members for each register in the array that is in the specified direction.
233 * Default value constant for the above record.
234 * VHDL vector type of the above record, ranged per the range of the register array.
236 This function assumes that the register last has registers in the given direction.
237 """
238 comment_suffix = (
239 f" that are in the '{direction.name.lower()}' direction" if direction else ""
240 )
241 type_suffix = f"_{direction.name.lower()}" if direction else ""
242 vhdl = ""
244 for array in self.iterate_hardware_accessible_register_arrays(direction=direction):
245 array_name = self.qualified_register_array_name(register_array=array)
246 vhdl += f"""\
247 -- Registers of the '{array.name}' array{comment_suffix}.
248 type {array_name}{type_suffix}_t is record
249"""
251 vhdl_array_init = []
252 for register in self.iterate_hardware_accessible_array_registers(
253 register_array=array, direction=direction
254 ):
255 vhdl += self._record_member_declaration_for_register(
256 register=register, register_array=array
257 )
259 register_name = self.qualified_register_name(
260 register=register, register_array=array
261 )
262 init = f"{register_name}_init" if register.fields else "(others => '0')"
263 vhdl_array_init.append(f"{register.name} => {init}")
265 init = " " + ",\n ".join(vhdl_array_init)
266 vhdl += f"""\
267 end record;
268 -- Default value of the above record.
269 constant {array_name}{type_suffix}_init : {array_name}{type_suffix}_t := (
270{init}
271 );
272 -- VHDL array of the above record.
273 type {array_name}{type_suffix}_vec_t is array ({array_name}_range) of {array_name}{type_suffix}_t;
275"""
277 heading = f"""\
278 -- -----------------------------------------------------------------------------
279 -- Below is a record with correctly typed and ranged members for all registers, register arrays
280 -- and fields{comment_suffix}.
281"""
282 if vhdl:
283 heading += f"""\
284 -- But first, records for the registers of each register array{comment_suffix}.
285"""
287 return f"{heading}{vhdl}"
289 def _register_record(self, direction: HardwareAccessDirection | None) -> str:
290 """
291 Get the record that contains all registers and arrays in the specified direction.
292 Also default value constant for this record.
294 This function assumes that the register list has registers in the given direction.
295 """
296 comment_suffix = f" in the '{direction.name.lower()}' direction" if direction else ""
297 type_suffix = f"_{direction.name.lower()}" if direction else ""
298 regs_type_suffix = f"regs_{direction.name.lower()}" if direction else "registers"
300 record_init = []
301 vhdl = f"""\
302 -- Record with everything{comment_suffix}.
303 type {self.name}_{regs_type_suffix}_t is record
304"""
306 for array in self.iterate_hardware_accessible_register_arrays(direction=direction):
307 array_name = self.qualified_register_array_name(register_array=array)
309 vhdl += f" {array.name} : {array_name}{type_suffix}_vec_t;\n"
310 record_init.append(f"{array.name} => (others => {array_name}{type_suffix}_init)")
312 for register in self.iterate_hardware_accessible_plain_registers(direction=direction):
313 vhdl += self._record_member_declaration_for_register(register=register)
315 if register.fields:
316 register_name = self.qualified_register_name(register=register)
317 record_init.append(f"{register.name} => {register_name}_init")
318 else:
319 record_init.append(f"{register.name} => (others => '0')")
321 init = " " + ",\n ".join(record_init)
323 return f"""\
324{vhdl}\
325 end record;
326 -- Default value of the above record.
327 constant {self.name}_{regs_type_suffix}_init : {self.name}_{regs_type_suffix}_t := (
328{init}
329 );
330"""
332 def _record_member_declaration_for_register(
333 self, register: Register, register_array: RegisterArray | None = None
334 ) -> str:
335 """
336 Get the record member declaration line for a register that shall be part of the record.
337 """
338 register_name = self.qualified_register_name(
339 register=register, register_array=register_array
340 )
342 if register.fields:
343 return f" {register.name} : {register_name}_t;\n"
345 return f" {register.name} : register_t;\n"
347 def _register_was_accessed(self) -> str:
348 """
349 Get record for 'reg_was_read' and 'reg_was_written' ports.
350 Should include only the registers that are actually readable/writeable.
351 """
352 vhdl = ""
354 for direction in SoftwareAccessDirection:
355 if self.has_any_software_accessible_register(direction=direction):
356 vhdl += self._register_was_accessed_record(direction=direction)
358 return vhdl
360 def _register_was_accessed_record(self, direction: SoftwareAccessDirection) -> str:
361 """
362 Get the record for 'reg_was_read' or 'reg_was_written'.
363 """
364 vhdl = f"""\
365 -- ---------------------------------------------------------------------------
366 -- Below is a record with a status bit for each {direction.value.name_adjective} register in the \
367register list.
368 -- It can be used for the 'reg_was_{direction.value.name_past}' port of a register file wrapper.
369"""
371 for array in self.iterate_software_accessible_register_arrays(direction=direction):
372 array_name = self.qualified_register_array_name(register_array=array)
373 vhdl += f"""\
374 -- One status bit for each {direction.value.name_adjective} register in the '{array.name}' \
375register array.
376 type {array_name}_was_{direction.value.name_past}_t is record
377"""
379 for register in self.iterate_software_accessible_array_registers(
380 register_array=array, direction=direction
381 ):
382 vhdl += f" {register.name} : std_ulogic;\n"
384 vhdl += f"""\
385 end record;
386 -- Default value of the above record.
387 constant {array_name}_was_{direction.value.name_past}_init : \
388{array_name}_was_{direction.value.name_past}_t := (others => '0');
389 -- Vector of the above record, ranged per the length of the '{array.name}' \
390register array.
391 type {array_name}_was_{direction.value.name_past}_vec_t is array (0 to {array.length - 1}) \
392of {array_name}_was_{direction.value.name_past}_t;
394"""
396 vhdl += f"""\
397 -- Combined status mask record for all {direction.value.name_adjective} register.
398 type {self.name}_reg_was_{direction.value.name_past}_t is record
399"""
401 array_init = []
402 for array in self.iterate_software_accessible_register_arrays(direction=direction):
403 array_name = self.qualified_register_array_name(register_array=array)
405 vhdl += f" {array.name} : {array_name}_was_{direction.value.name_past}_vec_t;\n"
406 array_init.append(
407 f"{array.name} => (others => {array_name}_was_{direction.value.name_past}_init)"
408 )
410 has_at_least_one_register = False
411 for register in self.iterate_software_accessible_plain_registers(direction=direction):
412 vhdl += f" {register.name} : std_ulogic;\n"
413 has_at_least_one_register = True
415 init_arrays = (" " + ",\n ".join(array_init)) if array_init else ""
416 init_registers = " others => '0'" if has_at_least_one_register else ""
417 separator = ",\n" if (init_arrays and init_registers) else ""
419 vhdl += f"""\
420 end record;
421 -- Default value for the above record.
422 constant {self.name}_reg_was_{direction.value.name_past}_init : \
423{self.name}_reg_was_{direction.value.name_past}_t := (
424{init_arrays}{separator}{init_registers}
425 );
426 -- Convert an SLV 'reg_was_{direction.value.name_past}' from generic register file \
427to the record above.
428 function to_{self.name}_reg_was_{direction.value.name_past}(
429 data : {self.name}_reg_was_accessed_t
430 ) return {self.name}_reg_was_{direction.value.name_past}_t;
432"""
434 return vhdl
436 def _register_field_record_conversion_implementations(self) -> str:
437 """
438 Implementation of functions that convert a register record with native field types
439 to/from SLV.
440 """
441 vhdl = ""
443 def _get_functions(register: Register, register_array: RegisterArray | None) -> str:
444 register_name = self.qualified_register_name(
445 register=register, register_array=register_array
446 )
448 to_slv = ""
449 to_record = ""
451 for field in register.fields:
452 field_name = self.qualified_field_name(
453 register=register, register_array=register_array, field=field
454 )
455 field_to_slv = self.field_to_slv(
456 field=field, field_name=field_name, value=f"data.{field.name}"
457 )
458 to_slv += f" result({field_name}) := {field_to_slv};\n"
460 if isinstance(field, Bit):
461 to_record += f" result.{field.name} := data({field_name});\n"
463 elif isinstance(field, BitVector):
464 to_record += f" result.{field.name} := {field_name}_t(data({field_name}));\n"
466 elif isinstance(field, (Enumeration, Integer)):
467 to_record += f" result.{field.name} := to_{field_name}(data);\n"
469 else:
470 raise TypeError(f'Got unexpected field type: "{field}".')
472 # Set "don't care" on the bits that have no field, so that a register value comparison
473 # can be true even if there is junk in the unused bits.
474 return f"""\
475 function to_slv(data : {register_name}_t) return register_t is
476 variable result : register_t := (others => '-');
477 begin
478{to_slv}
479 return result;
480 end function;
482 function to_{register_name}(data : register_t) return {register_name}_t is
483 variable result : {register_name}_t := {register_name}_init;
484 begin
485{to_record}
486 return result;
487 end function;
489"""
491 for register, register_array in self.iterate_registers():
492 if register.fields:
493 vhdl += _get_functions(register=register, register_array=register_array)
495 return vhdl
497 def _register_record_conversion_implementations(self) -> str:
498 """
499 Conversion function implementations to/from SLV for the records containing all
500 registers and arrays in 'up'/'down' direction.
501 """
502 vhdl = ""
504 for direction in [None, HardwareAccessDirection.UP]:
505 if self.has_any_hardware_accessible_register(direction=direction):
506 vhdl += self._register_record_to_slv(direction=direction)
508 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.DOWN):
509 vhdl += self._get_registers_down_to_record_function()
511 return vhdl
513 def _register_record_to_slv(self, direction: HardwareAccessDirection | None) -> str:
514 """
515 Conversion function implementation for converting a record of all the 'up' registers
516 to a register SLV list.
518 This function assumes that the register list has registers in the given direction.
519 """
520 to_slv = ""
522 for register, register_array in self.iterate_hardware_accessible_registers(
523 direction=direction
524 ):
525 register_name = self.qualified_register_name(
526 register=register, register_array=register_array
527 )
529 if register_array is None:
530 result = f" result({register_name})"
531 record = f"data.{register.name}"
533 if register.fields:
534 to_slv += f"{result} := to_slv({record});\n"
535 else:
536 to_slv += f"{result} := {record};\n"
538 else:
539 for array_idx in range(register_array.length):
540 result = f" result({register_name}({array_idx}))"
541 record = f"data.{register_array.name}({array_idx}).{register.name}"
543 if register.fields:
544 to_slv += f"{result} := to_slv({record});\n"
545 else:
546 to_slv += f"{result} := {record};\n"
548 type_suffix = f"regs_{direction.name}" if direction else "registers"
549 return f"""\
550 function to_slv(data : {self.name}_{type_suffix}_t) return {self.name}_regs_t is
551 variable result : {self.name}_regs_t := {self.name}_regs_init;
552 begin
553{to_slv}
554 return result;
555 end function;
557"""
559 def _get_registers_down_to_record_function(self) -> str:
560 """
561 Conversion function implementation for converting all the 'down' registers
562 in a register SLV list to record.
564 This function assumes that the register list has registers in the given direction.
565 """
566 to_record = ""
568 for register, register_array in self.iterate_hardware_accessible_registers(
569 direction=HardwareAccessDirection.DOWN
570 ):
571 register_name = self.qualified_register_name(
572 register=register, register_array=register_array
573 )
575 if register_array is None:
576 result = f" result.{register.name}"
577 data = f"data({register_name})"
579 if register.fields:
580 to_record += f"{result} := to_{register_name}({data});\n"
581 else:
582 to_record += f"{result} := {data};\n"
584 else:
585 for array_idx in range(register_array.length):
586 result = f" result.{register_array.name}({array_idx}).{register.name}"
587 data = f"data({register_name}({array_idx}))"
589 if register.fields:
590 to_record += f"{result} := to_{register_name}({data});\n"
591 else:
592 to_record += f"{result} := {data};\n"
594 return f"""\
595 function to_{self.name}_regs_down(data : {self.name}_regs_t) return \
596{self.name}_regs_down_t is
597 variable result : {self.name}_regs_down_t := {self.name}_regs_down_init;
598 begin
599{to_record}
600 return result;
601 end function;
603"""
605 def _register_was_accessed_conversion_implementations(self) -> str:
606 """
607 Get conversion functions from SLV 'reg_was_read'/'reg_was_written' to record types.
608 """
609 vhdl = ""
611 for direction in SoftwareAccessDirection:
612 if self.has_any_software_accessible_register(direction=direction):
613 vhdl += self._register_was_accessed_conversion_implementation(direction=direction)
615 return vhdl
617 def _register_was_accessed_conversion_implementation(
618 self, direction: SoftwareAccessDirection
619 ) -> str:
620 """
621 Get a conversion function from SLV 'reg_was_read'/'reg_was_written' to record type.
622 """
623 vhdl = f"""\
624 function to_{self.name}_reg_was_{direction.value.name_past}(
625 data : {self.name}_reg_was_accessed_t
626 ) return {self.name}_reg_was_{direction.value.name_past}_t is
627 variable result : {self.name}_reg_was_{direction.value.name_past}_t := \
628{self.name}_reg_was_{direction.value.name_past}_init;
629 begin
630"""
632 for register in self.iterate_software_accessible_plain_registers(direction=direction):
633 register_name = self.qualified_register_name(register=register)
634 vhdl += f" result.{register.name} := data({register_name});\n"
636 for array in self.iterate_register_arrays():
637 for register in self.iterate_software_accessible_array_registers(
638 register_array=array, direction=direction
639 ):
640 register_name = self.qualified_register_name(
641 register=register, register_array=array
642 )
644 for array_index in range(array.length):
645 vhdl += (
646 f" result.{array.name}({array_index}).{register.name} := "
647 f"data({register_name}(array_index=>{array_index}));\n"
648 )
650 return f"""\
651{vhdl}
652 return result;
653 end function;
655"""