Coverage for hdl_registers/generator/vhdl/record_package.py: 99%
204 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-28 20:51 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-28 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# --------------------------------------------------------------------------------------------------
10# Standard libraries
11from pathlib import Path
12from typing import TYPE_CHECKING, Any, Optional
14# First party libraries
15from hdl_registers.field.bit import Bit
16from hdl_registers.field.bit_vector import BitVector
17from hdl_registers.field.enumeration import Enumeration
18from hdl_registers.field.integer import Integer
19from hdl_registers.register_mode import HardwareAccessDirection, SoftwareAccessDirection
21# Local folder libraries
22from .vhdl_generator_common import VhdlGeneratorCommon
24if TYPE_CHECKING:
25 # First party libraries
26 from hdl_registers.register import Register
27 from hdl_registers.register_array import RegisterArray
30class VhdlRecordPackageGenerator(VhdlGeneratorCommon):
31 """
32 Generate a VHDL package with register record types containing natively-typed members for
33 each register field.
34 See the :ref:`generator_vhdl` article for usage details.
36 * For each register, plain or in array, a record with natively-typed members for each
37 register field.
38 * For each register array, a correctly-ranged array of records for the registers in
39 that array.
40 * Combined record with all the registers and register arrays.
41 One each for registers in the up direction and in the down direction.
42 * Constants with default values for all of the above types.
43 * Conversion functions to/from ``std_logic_vector`` representation for all of the
44 above types.
46 The generated VHDL file needs also the generated package
47 from :class:`.VhdlRegisterPackageGenerator`.
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(self, **kwargs: Any) -> Path:
62 """
63 See super class for API details.
65 Overloaded here because this package file shall only be created if the register list
66 actually has any registers.
67 """
68 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs)
70 def get_code(self, **kwargs: Any) -> str:
71 """
72 Get a complete VHDL package with register record types.
73 """
74 package_name = self.output_file.stem
76 vhdl = f"""\
77library ieee;
78use ieee.fixed_pkg.all;
79use ieee.std_logic_1164.all;
80use ieee.numeric_std.all;
82library register_file;
83use register_file.register_file_pkg.register_t;
85use work.{self.name}_regs_pkg.all;
88package {package_name} is
90"""
92 if self.register_list.register_objects:
93 vhdl += f"""\
94{self._register_field_records()}\
95{self._register_records()}\
96{self._register_was_accessed()}\
97"""
99 vhdl += "end package;\n"
101 if self.register_list.register_objects:
102 vhdl += f"""
103package body {package_name} is
105{self._register_field_record_conversion_implementations()}\
106{self._register_record_conversion_implementations()}\
107{self._register_was_accessed_conversion_implementations()}\
108end package body;
109"""
111 return vhdl
113 def _register_field_records(self) -> str:
114 """
115 For every register (plain or in array) that has at least one field:
117 * Record with members for each field that are of the correct native VHDL type.
118 * Default value constant for the above record.
119 * Function to convert the above record to SLV.
120 * Function to convert a register SLV to the above record.
121 """
122 vhdl = """\
123 -- -----------------------------------------------------------------------------
124 -- Record with correctly-typed members for each field in each register.
125"""
127 for register, register_array in self.iterate_registers():
128 if not register.fields:
129 continue
131 register_name = self.qualified_register_name(
132 register=register, register_array=register_array
133 )
134 register_description = self.register_description(
135 register=register, register_array=register_array
136 )
138 record = ""
139 init = []
141 for field in register.fields:
142 field_name = self.qualified_field_name(
143 register=register, register_array=register_array, field=field
144 )
145 init.append(f"{field.name} => {field_name}_init")
147 field_type_name = self.field_type_name(
148 register=register, register_array=register_array, field=field
149 )
150 record += f" {field.name} : {field_type_name};\n"
152 init_str = " " + ",\n ".join(init)
154 vhdl += f"""\
155 -- Fields in the {register_description} as a record.
156 type {register_name}_t is record
157{record}\
158 end record;
159 -- Default value for the {register_description} as a record.
160 constant {register_name}_init : {register_name}_t := (
161{init_str}
162 );
163 -- Convert a record of the {register_description} to SLV.
164 function to_slv(data : {register_name}_t) return register_t;
165 -- Convert an SLV register value to the record for the {register_description}.
166 function to_{register_name}(data : register_t) return {register_name}_t;
168"""
170 return vhdl
172 def _register_records(self) -> str:
173 """
174 Get two records,
175 * One with all the registers that are in the 'up' direction.
176 * One with all the registers that are in the 'down' direction.
178 Along with conversion function declarations to/from SLV.
180 In order to create the above records, we have to create partial-array records for each
181 register array.
182 One record for 'up' and one for 'down', with all registers in the array that are in
183 that direction.
184 """
185 vhdl = ""
187 direction = HardwareAccessDirection.UP
189 if self.has_any_hardware_accessible_register(direction=direction):
190 vhdl += self._array_field_records(direction=direction)
191 vhdl += self._get_register_record(direction=direction)
192 vhdl += f"""\
193 -- Convert record with everything in the '{direction.name.lower()}' direction to SLV \
194register list.
195 function to_slv(data : {self.name}_regs_{direction.name.lower()}_t) return {self.name}_regs_t;
197"""
199 direction = HardwareAccessDirection.DOWN
201 if self.has_any_hardware_accessible_register(direction=direction):
202 vhdl += self._array_field_records(direction=direction)
203 vhdl += self._get_register_record(direction=direction)
204 vhdl += f"""\
205 -- Convert SLV register list to record with everything in the \
206'{direction.name.lower()}' direction.
207 function to_{self.name}_regs_{direction.name.lower()}(data : {self.name}_regs_t) \
208return {self.name}_regs_{direction.name.lower()}_t;
210"""
212 return vhdl
214 def _array_field_records(self, direction: "HardwareAccessDirection") -> str:
215 """
216 For every register array that has at least one register in the specified direction:
218 * Record with members for each register in the array that is in the specified direction.
219 * Default value constant for the above record.
220 * VHDL vector type of the above record, ranged per the range of the register array.
222 This function assumes that the register last has registers in the given direction.
223 """
224 vhdl = ""
226 for array in self.iterate_hardware_accessible_register_arrays(direction=direction):
227 array_name = self.qualified_register_array_name(register_array=array)
228 vhdl += f"""\
229 -- Registers of the '{array.name}' array that are in the '{direction.name.lower()}' direction.
230 type {array_name}_{direction.name.lower()}_t is record
231"""
233 vhdl_array_init = []
234 for register in self.iterate_hardware_accessible_array_registers(
235 register_array=array, direction=direction
236 ):
237 vhdl += self._record_member_declaration_for_register(
238 register=register, register_array=array
239 )
241 register_name = self.qualified_register_name(
242 register=register, register_array=array
243 )
244 init = f"{register_name}_init" if register.fields else "(others => '0')"
245 vhdl_array_init.append(f"{register.name} => {init}")
247 init = " " + ",\n ".join(vhdl_array_init)
248 vhdl += f"""\
249 end record;
250 -- Default value of the above record.
251 constant {array_name}_{direction.name.lower()}_init : {array_name}_{direction.name.lower()}_t := (
252{init}
253 );
254 -- VHDL array of the above record, ranged per the length of the '{array.name}' \
255register array.
256 type {array_name}_{direction.name.lower()}_vec_t is array (0 to {array.length - 1}) of \
257{array_name}_{direction.name.lower()}_t;
259"""
261 heading = f"""\
262 -- -----------------------------------------------------------------------------
263 -- Below is a record with correctly typed and ranged members for all registers, register arrays
264 -- and fields that are in the '{direction.name.lower()}' direction.
265"""
266 if vhdl:
267 heading += f"""\
268 -- But first, records for the registers of each register array the are in \
269the '{direction.name.lower()}' direction.
270"""
272 return f"{heading}{vhdl}"
274 def _get_register_record(self, direction: "HardwareAccessDirection") -> str:
275 """
276 Get the record that contains all registers and arrays in the specified direction.
277 Also default value constant for this record.
279 This function assumes that the register list has registers in the given direction.
280 """
281 record_init = []
282 vhdl = f"""\
283 -- Record with everything in the '{direction.name.lower()}' direction.
284 type {self.name}_regs_{direction.name.lower()}_t is record
285"""
287 for array in self.iterate_hardware_accessible_register_arrays(direction=direction):
288 array_name = self.qualified_register_array_name(register_array=array)
290 vhdl += f" {array.name} : {array_name}_{direction.name.lower()}_vec_t;\n"
291 record_init.append(
292 f"{array.name} => (others => {array_name}_{direction.name.lower()}_init)"
293 )
295 for register in self.iterate_hardware_accessible_plain_registers(direction=direction):
296 vhdl += self._record_member_declaration_for_register(register=register)
298 if register.fields:
299 register_name = self.qualified_register_name(register=register)
300 record_init.append(f"{register.name} => {register_name}_init")
301 else:
302 record_init.append(f"{register.name} => (others => '0')")
304 init = " " + ",\n ".join(record_init)
306 return f"""\
307{vhdl}\
308 end record;
309 -- Default value of the above record.
310 constant {self.name}_regs_{direction.name.lower()}_init : \
311{self.name}_regs_{direction.name.lower()}_t := (
312{init}
313 );
314"""
316 def _record_member_declaration_for_register(
317 self, register: "Register", register_array: Optional["RegisterArray"] = None
318 ) -> str:
319 """
320 Get the record member declaration line for a register that shall be part of the record.
321 """
322 register_name = self.qualified_register_name(
323 register=register, register_array=register_array
324 )
326 if register.fields:
327 return f" {register.name} : {register_name}_t;\n"
329 return f" {register.name} : register_t;\n"
331 def _register_was_accessed(self) -> str:
332 """
333 Get record for 'reg_was_read' and 'reg_was_written' ports.
334 Should include only the registers that are actually readable/writeable.
335 """
336 vhdl = ""
338 for direction in SoftwareAccessDirection:
339 if self.has_any_software_accessible_register(direction=direction):
340 vhdl += self._register_was_accessed_record(direction=direction)
342 return vhdl
344 def _register_was_accessed_record(self, direction: "SoftwareAccessDirection") -> str:
345 """
346 Get the record for 'reg_was_read' or 'reg_was_written'.
347 """
348 vhdl = f"""\
349 -- ---------------------------------------------------------------------------
350 -- Below is a record with a status bit for each {direction.value.name_adjective} register in the \
351register list.
352 -- It can be used for the 'reg_was_{direction.value.name_past}' port of a register file wrapper.
353"""
355 for array in self.iterate_software_accessible_register_arrays(direction=direction):
356 array_name = self.qualified_register_array_name(register_array=array)
357 vhdl += f"""\
358 -- One status bit for each {direction.value.name_adjective} register in the '{array.name}' \
359register array.
360 type {array_name}_was_{direction.value.name_past}_t is record
361"""
363 for register in self.iterate_software_accessible_array_registers(
364 register_array=array, direction=direction
365 ):
366 vhdl += f" {register.name} : std_ulogic;\n"
368 vhdl += f"""\
369 end record;
370 -- Default value of the above record.
371 constant {array_name}_was_{direction.value.name_past}_init : \
372{array_name}_was_{direction.value.name_past}_t := (others => '0');
373 -- Vector of the above record, ranged per the length of the '{array.name}' \
374register array.
375 type {array_name}_was_{direction.value.name_past}_vec_t is array (0 to {array.length - 1}) \
376of {array_name}_was_{direction.value.name_past}_t;
378"""
380 vhdl += f"""\
381 -- Combined status mask record for all {direction.value.name_adjective} register.
382 type {self.name}_reg_was_{direction.value.name_past}_t is record
383"""
385 array_init = []
386 for array in self.iterate_software_accessible_register_arrays(direction=direction):
387 array_name = self.qualified_register_array_name(register_array=array)
389 vhdl += f" {array.name} : {array_name}_was_{direction.value.name_past}_vec_t;\n"
390 array_init.append(
391 f"{array.name} => (others => {array_name}_was_{direction.value.name_past}_init)"
392 )
394 has_at_least_one_register = False
395 for register in self.iterate_software_accessible_plain_registers(direction=direction):
396 vhdl += f" {register.name} : std_ulogic;\n"
397 has_at_least_one_register = True
399 init_arrays = (" " + ",\n ".join(array_init)) if array_init else ""
400 init_registers = " others => '0'" if has_at_least_one_register else ""
401 separator = ",\n" if (init_arrays and init_registers) else ""
403 vhdl += f"""\
404 end record;
405 -- Default value for the above record.
406 constant {self.name}_reg_was_{direction.value.name_past}_init : \
407{self.name}_reg_was_{direction.value.name_past}_t := (
408{init_arrays}{separator}{init_registers}
409 );
410 -- Convert an SLV 'reg_was_{direction.value.name_past}' from generic register file \
411to the record above.
412 function to_{self.name}_reg_was_{direction.value.name_past}(
413 data : {self.name}_reg_was_accessed_t
414 ) return {self.name}_reg_was_{direction.value.name_past}_t;
416"""
418 return vhdl
420 def _register_field_record_conversion_implementations(self) -> str:
421 """
422 Implementation of functions that convert a register record with native field types
423 to/from SLV.
424 """
425 vhdl = ""
427 def _get_functions(register: "Register", register_array: Optional["RegisterArray"]) -> str:
428 register_name = self.qualified_register_name(
429 register=register, register_array=register_array
430 )
432 to_slv = ""
433 to_record = ""
435 for field in register.fields:
436 field_name = self.qualified_field_name(
437 register=register, register_array=register_array, field=field
438 )
439 field_to_slv = self.field_to_slv(
440 field=field, field_name=field_name, value=f"data.{field.name}"
441 )
442 to_slv += f" result({field_name}) := {field_to_slv};\n"
444 if isinstance(field, Bit):
445 to_record += f" result.{field.name} := data({field_name});\n"
447 elif isinstance(field, BitVector):
448 to_record += f" result.{field.name} := {field_name}_t(data({field_name}));\n"
450 elif isinstance(field, (Enumeration, Integer)):
451 to_record += f" result.{field.name} := to_{field_name}(data);\n"
453 else:
454 raise TypeError(f'Got unexpected field type: "{field}".')
456 # Set "don't care" on the bits that have no field, so that a register value comparison
457 # can be true even if there is junk in the unused bits.
458 return f"""\
459 function to_slv(data : {register_name}_t) return register_t is
460 variable result : register_t := (others => '-');
461 begin
462{to_slv}
463 return result;
464 end function;
466 function to_{register_name}(data : register_t) return {register_name}_t is
467 variable result : {register_name}_t := {register_name}_init;
468 begin
469{to_record}
470 return result;
471 end function;
473"""
475 for register, register_array in self.iterate_registers():
476 if register.fields:
477 vhdl += _get_functions(register=register, register_array=register_array)
479 return vhdl
481 def _register_record_conversion_implementations(self) -> str:
482 """
483 Conversion function implementations to/from SLV for the records containing all
484 registers and arrays in 'up'/'down' direction.
485 """
486 vhdl = ""
488 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.UP):
489 vhdl += self._register_record_up_to_slv()
491 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.DOWN):
492 vhdl += self._get_registers_down_to_record_function()
494 return vhdl
496 def _register_record_up_to_slv(self) -> str:
497 """
498 Conversion function implementation for converting a record of all the 'up' registers
499 to a register SLV list.
501 This function assumes that the register list has registers in the given direction.
502 """
503 to_slv = ""
505 for register, register_array in self.iterate_hardware_accessible_registers(
506 direction=HardwareAccessDirection.UP
507 ):
508 register_name = self.qualified_register_name(
509 register=register, register_array=register_array
510 )
512 if register_array is None:
513 result = f" result({register_name})"
514 record = f"data.{register.name}"
516 if register.fields:
517 to_slv += f"{result} := to_slv({record});\n"
518 else:
519 to_slv += f"{result} := {record};\n"
521 else:
522 for array_idx in range(register_array.length):
523 result = f" result({register_name}({array_idx}))"
524 record = f"data.{register_array.name}({array_idx}).{register.name}"
526 if register.fields:
527 to_slv += f"{result} := to_slv({record});\n"
528 else:
529 to_slv += f"{result} := {record};\n"
531 return f"""\
532 function to_slv(data : {self.name}_regs_up_t) return {self.name}_regs_t is
533 variable result : {self.name}_regs_t := {self.name}_regs_init;
534 begin
535{to_slv}
536 return result;
537 end function;
539"""
541 def _get_registers_down_to_record_function(self) -> str:
542 """
543 Conversion function implementation for converting all the 'down' registers
544 in a register SLV list to record.
546 This function assumes that the register list has registers in the given direction.
547 """
548 to_record = ""
550 for register, register_array in self.iterate_hardware_accessible_registers(
551 direction=HardwareAccessDirection.DOWN
552 ):
553 register_name = self.qualified_register_name(
554 register=register, register_array=register_array
555 )
557 if register_array is None:
558 result = f" result.{register.name}"
559 data = f"data({register_name})"
561 if register.fields:
562 to_record += f"{result} := to_{register_name}({data});\n"
563 else:
564 to_record += f"{result} := {data};\n"
566 else:
567 for array_idx in range(register_array.length):
568 result = f" result.{register_array.name}({array_idx}).{register.name}"
569 data = f"data({register_name}({array_idx}))"
571 if register.fields:
572 to_record += f"{result} := to_{register_name}({data});\n"
573 else:
574 to_record += f"{result} := {data};\n"
576 return f"""\
577 function to_{self.name}_regs_down(data : {self.name}_regs_t) return \
578{self.name}_regs_down_t is
579 variable result : {self.name}_regs_down_t := {self.name}_regs_down_init;
580 begin
581{to_record}
582 return result;
583 end function;
585"""
587 def _register_was_accessed_conversion_implementations(self) -> str:
588 """
589 Get conversion functions from SLV 'reg_was_read'/'reg_was_written' to record types.
590 """
591 vhdl = ""
593 for direction in SoftwareAccessDirection:
594 if self.has_any_software_accessible_register(direction=direction):
595 vhdl += self._register_was_accessed_conversion_implementation(direction=direction)
597 return vhdl
599 def _register_was_accessed_conversion_implementation(
600 self, direction: "SoftwareAccessDirection"
601 ) -> str:
602 """
603 Get a conversion function from SLV 'reg_was_read'/'reg_was_written' to record type.
604 """
605 vhdl = f"""\
606 function to_{self.name}_reg_was_{direction.value.name_past}(
607 data : {self.name}_reg_was_accessed_t
608 ) return {self.name}_reg_was_{direction.value.name_past}_t is
609 variable result : {self.name}_reg_was_{direction.value.name_past}_t := \
610{self.name}_reg_was_{direction.value.name_past}_init;
611 begin
612"""
614 for register in self.iterate_software_accessible_plain_registers(direction=direction):
615 register_name = self.qualified_register_name(register=register)
616 vhdl += f" result.{register.name} := data({register_name});\n"
618 for array in self.iterate_register_arrays():
619 for register in self.iterate_software_accessible_array_registers(
620 register_array=array, direction=direction
621 ):
622 register_name = self.qualified_register_name(
623 register=register, register_array=array
624 )
626 for array_index in range(array.length):
627 vhdl += (
628 f" result.{array.name}({array_index}).{register.name} := "
629 f"data({register_name}(array_index=>{array_index}));\n"
630 )
632 return f"""\
633{vhdl}
634 return result;
635 end function;
637"""