Coverage for hdl_registers/generator/vhdl/record_package.py: 98%
205 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 11:11 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 11:11 +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 """
49 __version__ = "1.0.0"
51 SHORT_DESCRIPTION = "VHDL record package"
53 @property
54 def output_file(self) -> Path:
55 """
56 Result will be placed in this file.
57 """
58 return self.output_folder / f"{self.name}_register_record_pkg.vhd"
60 def create(
61 self,
62 **kwargs: Any, # noqa: ANN401
63 ) -> Path:
64 """
65 See super class for API details.
67 Overloaded here because this package file shall only be created if the register list
68 actually has any registers.
69 """
70 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs)
72 def get_code(
73 self,
74 **kwargs: Any, # noqa: ANN401, ARG002
75 ) -> str:
76 """
77 Get a complete VHDL package with register record types.
78 """
79 package_name = self.output_file.stem
81 vhdl = f"""\
82library ieee;
83use ieee.fixed_pkg.all;
84use ieee.std_logic_1164.all;
85use ieee.numeric_std.all;
87library register_file;
88use register_file.register_file_pkg.register_t;
90use work.{self.name}_regs_pkg.all;
93package {package_name} is
95"""
97 if self.register_list.register_objects:
98 vhdl += f"""\
99{self._register_field_records()}\
100{self._register_records()}\
101{self._register_was_accessed()}\
102"""
104 vhdl += "end package;\n"
106 if self.register_list.register_objects:
107 vhdl += f"""
108package body {package_name} is
110{self._register_field_record_conversion_implementations()}\
111{self._register_record_conversion_implementations()}\
112{self._register_was_accessed_conversion_implementations()}\
113end package body;
114"""
116 return vhdl
118 def _register_field_records(self) -> str:
119 """
120 For every register (plain or in array) that has at least one field:
122 * Record with members for each field that are of the correct native VHDL type.
123 * Default value constant for the above record.
124 * Function to convert the above record to SLV.
125 * Function to convert a register SLV to the above record.
126 """
127 vhdl = """\
128 -- -----------------------------------------------------------------------------
129 -- Record with correctly-typed members for each field in each register.
130"""
132 for register, register_array in self.iterate_registers():
133 if not register.fields:
134 continue
136 register_name = self.qualified_register_name(
137 register=register, register_array=register_array
138 )
139 register_description = self.register_description(
140 register=register, register_array=register_array
141 )
143 record = ""
144 init = []
146 for field in register.fields:
147 field_name = self.qualified_field_name(
148 register=register, register_array=register_array, field=field
149 )
150 init.append(f"{field.name} => {field_name}_init")
152 field_type_name = self.field_type_name(
153 register=register, register_array=register_array, field=field
154 )
155 record += f" {field.name} : {field_type_name};\n"
157 init_str = " " + ",\n ".join(init)
159 vhdl += f"""\
160 -- Fields in the {register_description} as a record.
161 type {register_name}_t is record
162{record}\
163 end record;
164 -- Default value for the {register_description} as a record.
165 constant {register_name}_init : {register_name}_t := (
166{init_str}
167 );
168 -- Convert a record of the {register_description} to SLV.
169 function to_slv(data : {register_name}_t) return register_t;
170 -- Convert an SLV register value to the record for the {register_description}.
171 function to_{register_name}(data : register_t) return {register_name}_t;
173"""
175 return vhdl
177 def _register_records(self) -> str:
178 """
179 Get two records,
180 * One with all the registers that are in the 'up' direction.
181 * One with all the registers that are in the 'down' direction.
183 Along with conversion function declarations to/from SLV.
185 In order to create the above records, we have to create partial-array records for each
186 register array.
187 One record for 'up' and one for 'down', with all registers in the array that are in
188 that direction.
189 """
190 vhdl = ""
192 direction = HardwareAccessDirection.UP
194 if self.has_any_hardware_accessible_register(direction=direction):
195 vhdl += self._array_field_records(direction=direction)
196 vhdl += self._get_register_record(direction=direction)
197 vhdl += f"""\
198 -- Convert record with everything in the '{direction.name.lower()}' direction to SLV \
199register list.
200 function to_slv(data : {self.name}_regs_{direction.name.lower()}_t) return {self.name}_regs_t;
202"""
204 direction = HardwareAccessDirection.DOWN
206 if self.has_any_hardware_accessible_register(direction=direction):
207 vhdl += self._array_field_records(direction=direction)
208 vhdl += self._get_register_record(direction=direction)
209 vhdl += f"""\
210 -- Convert SLV register list to record with everything in the \
211'{direction.name.lower()}' direction.
212 function to_{self.name}_regs_{direction.name.lower()}(data : {self.name}_regs_t) \
213return {self.name}_regs_{direction.name.lower()}_t;
215"""
217 return vhdl
219 def _array_field_records(self, direction: HardwareAccessDirection) -> str:
220 """
221 For every register array that has at least one register in the specified direction:
223 * Record with members for each register in the array that is in the specified direction.
224 * Default value constant for the above record.
225 * VHDL vector type of the above record, ranged per the range of the register array.
227 This function assumes that the register last has registers in the given direction.
228 """
229 vhdl = ""
231 for array in self.iterate_hardware_accessible_register_arrays(direction=direction):
232 array_name = self.qualified_register_array_name(register_array=array)
233 vhdl += f"""\
234 -- Registers of the '{array.name}' array that are in the '{direction.name.lower()}' direction.
235 type {array_name}_{direction.name.lower()}_t is record
236"""
238 vhdl_array_init = []
239 for register in self.iterate_hardware_accessible_array_registers(
240 register_array=array, direction=direction
241 ):
242 vhdl += self._record_member_declaration_for_register(
243 register=register, register_array=array
244 )
246 register_name = self.qualified_register_name(
247 register=register, register_array=array
248 )
249 init = f"{register_name}_init" if register.fields else "(others => '0')"
250 vhdl_array_init.append(f"{register.name} => {init}")
252 init = " " + ",\n ".join(vhdl_array_init)
253 vhdl += f"""\
254 end record;
255 -- Default value of the above record.
256 constant {array_name}_{direction.name.lower()}_init : {array_name}_{direction.name.lower()}_t := (
257{init}
258 );
259 -- VHDL array of the above record, ranged per the length of the '{array.name}' \
260register array.
261 type {array_name}_{direction.name.lower()}_vec_t is array (0 to {array.length - 1}) of \
262{array_name}_{direction.name.lower()}_t;
264"""
266 heading = f"""\
267 -- -----------------------------------------------------------------------------
268 -- Below is a record with correctly typed and ranged members for all registers, register arrays
269 -- and fields that are in the '{direction.name.lower()}' direction.
270"""
271 if vhdl:
272 heading += f"""\
273 -- But first, records for the registers of each register array the are in \
274the '{direction.name.lower()}' direction.
275"""
277 return f"{heading}{vhdl}"
279 def _get_register_record(self, direction: HardwareAccessDirection) -> str:
280 """
281 Get the record that contains all registers and arrays in the specified direction.
282 Also default value constant for this record.
284 This function assumes that the register list has registers in the given direction.
285 """
286 record_init = []
287 vhdl = f"""\
288 -- Record with everything in the '{direction.name.lower()}' direction.
289 type {self.name}_regs_{direction.name.lower()}_t is record
290"""
292 for array in self.iterate_hardware_accessible_register_arrays(direction=direction):
293 array_name = self.qualified_register_array_name(register_array=array)
295 vhdl += f" {array.name} : {array_name}_{direction.name.lower()}_vec_t;\n"
296 record_init.append(
297 f"{array.name} => (others => {array_name}_{direction.name.lower()}_init)"
298 )
300 for register in self.iterate_hardware_accessible_plain_registers(direction=direction):
301 vhdl += self._record_member_declaration_for_register(register=register)
303 if register.fields:
304 register_name = self.qualified_register_name(register=register)
305 record_init.append(f"{register.name} => {register_name}_init")
306 else:
307 record_init.append(f"{register.name} => (others => '0')")
309 init = " " + ",\n ".join(record_init)
311 return f"""\
312{vhdl}\
313 end record;
314 -- Default value of the above record.
315 constant {self.name}_regs_{direction.name.lower()}_init : \
316{self.name}_regs_{direction.name.lower()}_t := (
317{init}
318 );
319"""
321 def _record_member_declaration_for_register(
322 self, register: Register, register_array: RegisterArray | None = None
323 ) -> str:
324 """
325 Get the record member declaration line for a register that shall be part of the record.
326 """
327 register_name = self.qualified_register_name(
328 register=register, register_array=register_array
329 )
331 if register.fields:
332 return f" {register.name} : {register_name}_t;\n"
334 return f" {register.name} : register_t;\n"
336 def _register_was_accessed(self) -> str:
337 """
338 Get record for 'reg_was_read' and 'reg_was_written' ports.
339 Should include only the registers that are actually readable/writeable.
340 """
341 vhdl = ""
343 for direction in SoftwareAccessDirection:
344 if self.has_any_software_accessible_register(direction=direction):
345 vhdl += self._register_was_accessed_record(direction=direction)
347 return vhdl
349 def _register_was_accessed_record(self, direction: SoftwareAccessDirection) -> str:
350 """
351 Get the record for 'reg_was_read' or 'reg_was_written'.
352 """
353 vhdl = f"""\
354 -- ---------------------------------------------------------------------------
355 -- Below is a record with a status bit for each {direction.value.name_adjective} register in the \
356register list.
357 -- It can be used for the 'reg_was_{direction.value.name_past}' port of a register file wrapper.
358"""
360 for array in self.iterate_software_accessible_register_arrays(direction=direction):
361 array_name = self.qualified_register_array_name(register_array=array)
362 vhdl += f"""\
363 -- One status bit for each {direction.value.name_adjective} register in the '{array.name}' \
364register array.
365 type {array_name}_was_{direction.value.name_past}_t is record
366"""
368 for register in self.iterate_software_accessible_array_registers(
369 register_array=array, direction=direction
370 ):
371 vhdl += f" {register.name} : std_ulogic;\n"
373 vhdl += f"""\
374 end record;
375 -- Default value of the above record.
376 constant {array_name}_was_{direction.value.name_past}_init : \
377{array_name}_was_{direction.value.name_past}_t := (others => '0');
378 -- Vector of the above record, ranged per the length of the '{array.name}' \
379register array.
380 type {array_name}_was_{direction.value.name_past}_vec_t is array (0 to {array.length - 1}) \
381of {array_name}_was_{direction.value.name_past}_t;
383"""
385 vhdl += f"""\
386 -- Combined status mask record for all {direction.value.name_adjective} register.
387 type {self.name}_reg_was_{direction.value.name_past}_t is record
388"""
390 array_init = []
391 for array in self.iterate_software_accessible_register_arrays(direction=direction):
392 array_name = self.qualified_register_array_name(register_array=array)
394 vhdl += f" {array.name} : {array_name}_was_{direction.value.name_past}_vec_t;\n"
395 array_init.append(
396 f"{array.name} => (others => {array_name}_was_{direction.value.name_past}_init)"
397 )
399 has_at_least_one_register = False
400 for register in self.iterate_software_accessible_plain_registers(direction=direction):
401 vhdl += f" {register.name} : std_ulogic;\n"
402 has_at_least_one_register = True
404 init_arrays = (" " + ",\n ".join(array_init)) if array_init else ""
405 init_registers = " others => '0'" if has_at_least_one_register else ""
406 separator = ",\n" if (init_arrays and init_registers) else ""
408 vhdl += f"""\
409 end record;
410 -- Default value for the above record.
411 constant {self.name}_reg_was_{direction.value.name_past}_init : \
412{self.name}_reg_was_{direction.value.name_past}_t := (
413{init_arrays}{separator}{init_registers}
414 );
415 -- Convert an SLV 'reg_was_{direction.value.name_past}' from generic register file \
416to the record above.
417 function to_{self.name}_reg_was_{direction.value.name_past}(
418 data : {self.name}_reg_was_accessed_t
419 ) return {self.name}_reg_was_{direction.value.name_past}_t;
421"""
423 return vhdl
425 def _register_field_record_conversion_implementations(self) -> str:
426 """
427 Implementation of functions that convert a register record with native field types
428 to/from SLV.
429 """
430 vhdl = ""
432 def _get_functions(register: Register, register_array: RegisterArray | None) -> str:
433 register_name = self.qualified_register_name(
434 register=register, register_array=register_array
435 )
437 to_slv = ""
438 to_record = ""
440 for field in register.fields:
441 field_name = self.qualified_field_name(
442 register=register, register_array=register_array, field=field
443 )
444 field_to_slv = self.field_to_slv(
445 field=field, field_name=field_name, value=f"data.{field.name}"
446 )
447 to_slv += f" result({field_name}) := {field_to_slv};\n"
449 if isinstance(field, Bit):
450 to_record += f" result.{field.name} := data({field_name});\n"
452 elif isinstance(field, BitVector):
453 to_record += f" result.{field.name} := {field_name}_t(data({field_name}));\n"
455 elif isinstance(field, (Enumeration, Integer)):
456 to_record += f" result.{field.name} := to_{field_name}(data);\n"
458 else:
459 raise TypeError(f'Got unexpected field type: "{field}".')
461 # Set "don't care" on the bits that have no field, so that a register value comparison
462 # can be true even if there is junk in the unused bits.
463 return f"""\
464 function to_slv(data : {register_name}_t) return register_t is
465 variable result : register_t := (others => '-');
466 begin
467{to_slv}
468 return result;
469 end function;
471 function to_{register_name}(data : register_t) return {register_name}_t is
472 variable result : {register_name}_t := {register_name}_init;
473 begin
474{to_record}
475 return result;
476 end function;
478"""
480 for register, register_array in self.iterate_registers():
481 if register.fields:
482 vhdl += _get_functions(register=register, register_array=register_array)
484 return vhdl
486 def _register_record_conversion_implementations(self) -> str:
487 """
488 Conversion function implementations to/from SLV for the records containing all
489 registers and arrays in 'up'/'down' direction.
490 """
491 vhdl = ""
493 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.UP):
494 vhdl += self._register_record_up_to_slv()
496 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.DOWN):
497 vhdl += self._get_registers_down_to_record_function()
499 return vhdl
501 def _register_record_up_to_slv(self) -> str:
502 """
503 Conversion function implementation for converting a record of all the 'up' registers
504 to a register SLV list.
506 This function assumes that the register list has registers in the given direction.
507 """
508 to_slv = ""
510 for register, register_array in self.iterate_hardware_accessible_registers(
511 direction=HardwareAccessDirection.UP
512 ):
513 register_name = self.qualified_register_name(
514 register=register, register_array=register_array
515 )
517 if register_array is None:
518 result = f" result({register_name})"
519 record = f"data.{register.name}"
521 if register.fields:
522 to_slv += f"{result} := to_slv({record});\n"
523 else:
524 to_slv += f"{result} := {record};\n"
526 else:
527 for array_idx in range(register_array.length):
528 result = f" result({register_name}({array_idx}))"
529 record = f"data.{register_array.name}({array_idx}).{register.name}"
531 if register.fields:
532 to_slv += f"{result} := to_slv({record});\n"
533 else:
534 to_slv += f"{result} := {record};\n"
536 return f"""\
537 function to_slv(data : {self.name}_regs_up_t) return {self.name}_regs_t is
538 variable result : {self.name}_regs_t := {self.name}_regs_init;
539 begin
540{to_slv}
541 return result;
542 end function;
544"""
546 def _get_registers_down_to_record_function(self) -> str:
547 """
548 Conversion function implementation for converting all the 'down' registers
549 in a register SLV list to record.
551 This function assumes that the register list has registers in the given direction.
552 """
553 to_record = ""
555 for register, register_array in self.iterate_hardware_accessible_registers(
556 direction=HardwareAccessDirection.DOWN
557 ):
558 register_name = self.qualified_register_name(
559 register=register, register_array=register_array
560 )
562 if register_array is None:
563 result = f" result.{register.name}"
564 data = f"data({register_name})"
566 if register.fields:
567 to_record += f"{result} := to_{register_name}({data});\n"
568 else:
569 to_record += f"{result} := {data};\n"
571 else:
572 for array_idx in range(register_array.length):
573 result = f" result.{register_array.name}({array_idx}).{register.name}"
574 data = f"data({register_name}({array_idx}))"
576 if register.fields:
577 to_record += f"{result} := to_{register_name}({data});\n"
578 else:
579 to_record += f"{result} := {data};\n"
581 return f"""\
582 function to_{self.name}_regs_down(data : {self.name}_regs_t) return \
583{self.name}_regs_down_t is
584 variable result : {self.name}_regs_down_t := {self.name}_regs_down_init;
585 begin
586{to_record}
587 return result;
588 end function;
590"""
592 def _register_was_accessed_conversion_implementations(self) -> str:
593 """
594 Get conversion functions from SLV 'reg_was_read'/'reg_was_written' to record types.
595 """
596 vhdl = ""
598 for direction in SoftwareAccessDirection:
599 if self.has_any_software_accessible_register(direction=direction):
600 vhdl += self._register_was_accessed_conversion_implementation(direction=direction)
602 return vhdl
604 def _register_was_accessed_conversion_implementation(
605 self, direction: SoftwareAccessDirection
606 ) -> str:
607 """
608 Get a conversion function from SLV 'reg_was_read'/'reg_was_written' to record type.
609 """
610 vhdl = f"""\
611 function to_{self.name}_reg_was_{direction.value.name_past}(
612 data : {self.name}_reg_was_accessed_t
613 ) return {self.name}_reg_was_{direction.value.name_past}_t is
614 variable result : {self.name}_reg_was_{direction.value.name_past}_t := \
615{self.name}_reg_was_{direction.value.name_past}_init;
616 begin
617"""
619 for register in self.iterate_software_accessible_plain_registers(direction=direction):
620 register_name = self.qualified_register_name(register=register)
621 vhdl += f" result.{register.name} := data({register_name});\n"
623 for array in self.iterate_register_arrays():
624 for register in self.iterate_software_accessible_array_registers(
625 register_array=array, direction=direction
626 ):
627 register_name = self.qualified_register_name(
628 register=register, register_array=array
629 )
631 for array_index in range(array.length):
632 vhdl += (
633 f" result.{array.name}({array_index}).{register.name} := "
634 f"data({register_name}(array_index=>{array_index}));\n"
635 )
637 return f"""\
638{vhdl}
639 return result;
640 end function;
642"""