Coverage for hdl_registers/generator/vhdl/record_package.py: 98%
205 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-04 20:51 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-04 20:51 +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 direction = HardwareAccessDirection.UP
195 if self.has_any_hardware_accessible_register(direction=direction):
196 vhdl += self._array_field_records(direction=direction)
197 vhdl += self._get_register_record(direction=direction)
198 vhdl += f"""\
199 -- Convert record with everything in the '{direction.name.lower()}' direction to SLV \
200register list.
201 function to_slv(data : {self.name}_regs_{direction.name.lower()}_t) return {self.name}_regs_t;
203"""
205 direction = HardwareAccessDirection.DOWN
207 if self.has_any_hardware_accessible_register(direction=direction):
208 vhdl += self._array_field_records(direction=direction)
209 vhdl += self._get_register_record(direction=direction)
210 vhdl += f"""\
211 -- Convert SLV register list to record with everything in the \
212'{direction.name.lower()}' direction.
213 function to_{self.name}_regs_{direction.name.lower()}(data : {self.name}_regs_t) \
214return {self.name}_regs_{direction.name.lower()}_t;
216"""
218 return vhdl
220 def _array_field_records(self, direction: HardwareAccessDirection) -> str:
221 """
222 For every register array that has at least one register in the specified direction:
224 * Record with members for each register in the array that is in the specified direction.
225 * Default value constant for the above record.
226 * VHDL vector type of the above record, ranged per the range of the register array.
228 This function assumes that the register last has registers in the given direction.
229 """
230 vhdl = ""
232 for array in self.iterate_hardware_accessible_register_arrays(direction=direction):
233 array_name = self.qualified_register_array_name(register_array=array)
234 vhdl += f"""\
235 -- Registers of the '{array.name}' array that are in the '{direction.name.lower()}' direction.
236 type {array_name}_{direction.name.lower()}_t is record
237"""
239 vhdl_array_init = []
240 for register in self.iterate_hardware_accessible_array_registers(
241 register_array=array, direction=direction
242 ):
243 vhdl += self._record_member_declaration_for_register(
244 register=register, register_array=array
245 )
247 register_name = self.qualified_register_name(
248 register=register, register_array=array
249 )
250 init = f"{register_name}_init" if register.fields else "(others => '0')"
251 vhdl_array_init.append(f"{register.name} => {init}")
253 init = " " + ",\n ".join(vhdl_array_init)
254 vhdl += f"""\
255 end record;
256 -- Default value of the above record.
257 constant {array_name}_{direction.name.lower()}_init : {array_name}_{direction.name.lower()}_t := (
258{init}
259 );
260 -- VHDL array of the above record, ranged per the length of the '{array.name}' \
261register array.
262 type {array_name}_{direction.name.lower()}_vec_t is array (0 to {array.length - 1}) of \
263{array_name}_{direction.name.lower()}_t;
265"""
267 heading = f"""\
268 -- -----------------------------------------------------------------------------
269 -- Below is a record with correctly typed and ranged members for all registers, register arrays
270 -- and fields that are in the '{direction.name.lower()}' direction.
271"""
272 if vhdl:
273 heading += f"""\
274 -- But first, records for the registers of each register array the are in \
275the '{direction.name.lower()}' direction.
276"""
278 return f"{heading}{vhdl}"
280 def _get_register_record(self, direction: HardwareAccessDirection) -> str:
281 """
282 Get the record that contains all registers and arrays in the specified direction.
283 Also default value constant for this record.
285 This function assumes that the register list has registers in the given direction.
286 """
287 record_init = []
288 vhdl = f"""\
289 -- Record with everything in the '{direction.name.lower()}' direction.
290 type {self.name}_regs_{direction.name.lower()}_t is record
291"""
293 for array in self.iterate_hardware_accessible_register_arrays(direction=direction):
294 array_name = self.qualified_register_array_name(register_array=array)
296 vhdl += f" {array.name} : {array_name}_{direction.name.lower()}_vec_t;\n"
297 record_init.append(
298 f"{array.name} => (others => {array_name}_{direction.name.lower()}_init)"
299 )
301 for register in self.iterate_hardware_accessible_plain_registers(direction=direction):
302 vhdl += self._record_member_declaration_for_register(register=register)
304 if register.fields:
305 register_name = self.qualified_register_name(register=register)
306 record_init.append(f"{register.name} => {register_name}_init")
307 else:
308 record_init.append(f"{register.name} => (others => '0')")
310 init = " " + ",\n ".join(record_init)
312 return f"""\
313{vhdl}\
314 end record;
315 -- Default value of the above record.
316 constant {self.name}_regs_{direction.name.lower()}_init : \
317{self.name}_regs_{direction.name.lower()}_t := (
318{init}
319 );
320"""
322 def _record_member_declaration_for_register(
323 self, register: Register, register_array: RegisterArray | None = None
324 ) -> str:
325 """
326 Get the record member declaration line for a register that shall be part of the record.
327 """
328 register_name = self.qualified_register_name(
329 register=register, register_array=register_array
330 )
332 if register.fields:
333 return f" {register.name} : {register_name}_t;\n"
335 return f" {register.name} : register_t;\n"
337 def _register_was_accessed(self) -> str:
338 """
339 Get record for 'reg_was_read' and 'reg_was_written' ports.
340 Should include only the registers that are actually readable/writeable.
341 """
342 vhdl = ""
344 for direction in SoftwareAccessDirection:
345 if self.has_any_software_accessible_register(direction=direction):
346 vhdl += self._register_was_accessed_record(direction=direction)
348 return vhdl
350 def _register_was_accessed_record(self, direction: SoftwareAccessDirection) -> str:
351 """
352 Get the record for 'reg_was_read' or 'reg_was_written'.
353 """
354 vhdl = f"""\
355 -- ---------------------------------------------------------------------------
356 -- Below is a record with a status bit for each {direction.value.name_adjective} register in the \
357register list.
358 -- It can be used for the 'reg_was_{direction.value.name_past}' port of a register file wrapper.
359"""
361 for array in self.iterate_software_accessible_register_arrays(direction=direction):
362 array_name = self.qualified_register_array_name(register_array=array)
363 vhdl += f"""\
364 -- One status bit for each {direction.value.name_adjective} register in the '{array.name}' \
365register array.
366 type {array_name}_was_{direction.value.name_past}_t is record
367"""
369 for register in self.iterate_software_accessible_array_registers(
370 register_array=array, direction=direction
371 ):
372 vhdl += f" {register.name} : std_ulogic;\n"
374 vhdl += f"""\
375 end record;
376 -- Default value of the above record.
377 constant {array_name}_was_{direction.value.name_past}_init : \
378{array_name}_was_{direction.value.name_past}_t := (others => '0');
379 -- Vector of the above record, ranged per the length of the '{array.name}' \
380register array.
381 type {array_name}_was_{direction.value.name_past}_vec_t is array (0 to {array.length - 1}) \
382of {array_name}_was_{direction.value.name_past}_t;
384"""
386 vhdl += f"""\
387 -- Combined status mask record for all {direction.value.name_adjective} register.
388 type {self.name}_reg_was_{direction.value.name_past}_t is record
389"""
391 array_init = []
392 for array in self.iterate_software_accessible_register_arrays(direction=direction):
393 array_name = self.qualified_register_array_name(register_array=array)
395 vhdl += f" {array.name} : {array_name}_was_{direction.value.name_past}_vec_t;\n"
396 array_init.append(
397 f"{array.name} => (others => {array_name}_was_{direction.value.name_past}_init)"
398 )
400 has_at_least_one_register = False
401 for register in self.iterate_software_accessible_plain_registers(direction=direction):
402 vhdl += f" {register.name} : std_ulogic;\n"
403 has_at_least_one_register = True
405 init_arrays = (" " + ",\n ".join(array_init)) if array_init else ""
406 init_registers = " others => '0'" if has_at_least_one_register else ""
407 separator = ",\n" if (init_arrays and init_registers) else ""
409 vhdl += f"""\
410 end record;
411 -- Default value for the above record.
412 constant {self.name}_reg_was_{direction.value.name_past}_init : \
413{self.name}_reg_was_{direction.value.name_past}_t := (
414{init_arrays}{separator}{init_registers}
415 );
416 -- Convert an SLV 'reg_was_{direction.value.name_past}' from generic register file \
417to the record above.
418 function to_{self.name}_reg_was_{direction.value.name_past}(
419 data : {self.name}_reg_was_accessed_t
420 ) return {self.name}_reg_was_{direction.value.name_past}_t;
422"""
424 return vhdl
426 def _register_field_record_conversion_implementations(self) -> str:
427 """
428 Implementation of functions that convert a register record with native field types
429 to/from SLV.
430 """
431 vhdl = ""
433 def _get_functions(register: Register, register_array: RegisterArray | None) -> str:
434 register_name = self.qualified_register_name(
435 register=register, register_array=register_array
436 )
438 to_slv = ""
439 to_record = ""
441 for field in register.fields:
442 field_name = self.qualified_field_name(
443 register=register, register_array=register_array, field=field
444 )
445 field_to_slv = self.field_to_slv(
446 field=field, field_name=field_name, value=f"data.{field.name}"
447 )
448 to_slv += f" result({field_name}) := {field_to_slv};\n"
450 if isinstance(field, Bit):
451 to_record += f" result.{field.name} := data({field_name});\n"
453 elif isinstance(field, BitVector):
454 to_record += f" result.{field.name} := {field_name}_t(data({field_name}));\n"
456 elif isinstance(field, (Enumeration, Integer)):
457 to_record += f" result.{field.name} := to_{field_name}(data);\n"
459 else:
460 raise TypeError(f'Got unexpected field type: "{field}".')
462 # Set "don't care" on the bits that have no field, so that a register value comparison
463 # can be true even if there is junk in the unused bits.
464 return f"""\
465 function to_slv(data : {register_name}_t) return register_t is
466 variable result : register_t := (others => '-');
467 begin
468{to_slv}
469 return result;
470 end function;
472 function to_{register_name}(data : register_t) return {register_name}_t is
473 variable result : {register_name}_t := {register_name}_init;
474 begin
475{to_record}
476 return result;
477 end function;
479"""
481 for register, register_array in self.iterate_registers():
482 if register.fields:
483 vhdl += _get_functions(register=register, register_array=register_array)
485 return vhdl
487 def _register_record_conversion_implementations(self) -> str:
488 """
489 Conversion function implementations to/from SLV for the records containing all
490 registers and arrays in 'up'/'down' direction.
491 """
492 vhdl = ""
494 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.UP):
495 vhdl += self._register_record_up_to_slv()
497 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.DOWN):
498 vhdl += self._get_registers_down_to_record_function()
500 return vhdl
502 def _register_record_up_to_slv(self) -> str:
503 """
504 Conversion function implementation for converting a record of all the 'up' registers
505 to a register SLV list.
507 This function assumes that the register list has registers in the given direction.
508 """
509 to_slv = ""
511 for register, register_array in self.iterate_hardware_accessible_registers(
512 direction=HardwareAccessDirection.UP
513 ):
514 register_name = self.qualified_register_name(
515 register=register, register_array=register_array
516 )
518 if register_array is None:
519 result = f" result({register_name})"
520 record = f"data.{register.name}"
522 if register.fields:
523 to_slv += f"{result} := to_slv({record});\n"
524 else:
525 to_slv += f"{result} := {record};\n"
527 else:
528 for array_idx in range(register_array.length):
529 result = f" result({register_name}({array_idx}))"
530 record = f"data.{register_array.name}({array_idx}).{register.name}"
532 if register.fields:
533 to_slv += f"{result} := to_slv({record});\n"
534 else:
535 to_slv += f"{result} := {record};\n"
537 return f"""\
538 function to_slv(data : {self.name}_regs_up_t) return {self.name}_regs_t is
539 variable result : {self.name}_regs_t := {self.name}_regs_init;
540 begin
541{to_slv}
542 return result;
543 end function;
545"""
547 def _get_registers_down_to_record_function(self) -> str:
548 """
549 Conversion function implementation for converting all the 'down' registers
550 in a register SLV list to record.
552 This function assumes that the register list has registers in the given direction.
553 """
554 to_record = ""
556 for register, register_array in self.iterate_hardware_accessible_registers(
557 direction=HardwareAccessDirection.DOWN
558 ):
559 register_name = self.qualified_register_name(
560 register=register, register_array=register_array
561 )
563 if register_array is None:
564 result = f" result.{register.name}"
565 data = f"data({register_name})"
567 if register.fields:
568 to_record += f"{result} := to_{register_name}({data});\n"
569 else:
570 to_record += f"{result} := {data};\n"
572 else:
573 for array_idx in range(register_array.length):
574 result = f" result.{register_array.name}({array_idx}).{register.name}"
575 data = f"data({register_name}({array_idx}))"
577 if register.fields:
578 to_record += f"{result} := to_{register_name}({data});\n"
579 else:
580 to_record += f"{result} := {data};\n"
582 return f"""\
583 function to_{self.name}_regs_down(data : {self.name}_regs_t) return \
584{self.name}_regs_down_t is
585 variable result : {self.name}_regs_down_t := {self.name}_regs_down_init;
586 begin
587{to_record}
588 return result;
589 end function;
591"""
593 def _register_was_accessed_conversion_implementations(self) -> str:
594 """
595 Get conversion functions from SLV 'reg_was_read'/'reg_was_written' to record types.
596 """
597 vhdl = ""
599 for direction in SoftwareAccessDirection:
600 if self.has_any_software_accessible_register(direction=direction):
601 vhdl += self._register_was_accessed_conversion_implementation(direction=direction)
603 return vhdl
605 def _register_was_accessed_conversion_implementation(
606 self, direction: SoftwareAccessDirection
607 ) -> str:
608 """
609 Get a conversion function from SLV 'reg_was_read'/'reg_was_written' to record type.
610 """
611 vhdl = f"""\
612 function to_{self.name}_reg_was_{direction.value.name_past}(
613 data : {self.name}_reg_was_accessed_t
614 ) return {self.name}_reg_was_{direction.value.name_past}_t is
615 variable result : {self.name}_reg_was_{direction.value.name_past}_t := \
616{self.name}_reg_was_{direction.value.name_past}_init;
617 begin
618"""
620 for register in self.iterate_software_accessible_plain_registers(direction=direction):
621 register_name = self.qualified_register_name(register=register)
622 vhdl += f" result.{register.name} := data({register_name});\n"
624 for array in self.iterate_register_arrays():
625 for register in self.iterate_software_accessible_array_registers(
626 register_array=array, direction=direction
627 ):
628 register_name = self.qualified_register_name(
629 register=register, register_array=array
630 )
632 for array_index in range(array.length):
633 vhdl += (
634 f" result.{array.name}({array_index}).{register.name} := "
635 f"data({register_name}(array_index=>{array_index}));\n"
636 )
638 return f"""\
639{vhdl}
640 return result;
641 end function;
643"""