Coverage for hdl_registers/generator/vhdl/record_package.py: 99%
204 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-07 20:51 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-07 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"""\
77{self.header}
78library ieee;
79use ieee.fixed_pkg.all;
80use ieee.std_logic_1164.all;
81use ieee.numeric_std.all;
83library reg_file;
84use reg_file.reg_file_pkg.reg_t;
86use work.{self.name}_regs_pkg.all;
89package {package_name} is
91"""
93 if self.register_list.register_objects:
94 vhdl += f"""\
95{self._register_field_records()}\
96{self._register_records()}\
97{self._register_was_accessed()}\
98"""
100 vhdl += "end package;\n"
102 if self.register_list.register_objects:
103 vhdl += f"""
104package body {package_name} is
106{self._register_field_record_conversion_implementations()}\
107{self._register_record_conversion_implementations()}\
108{self._register_was_accessed_conversion_implementations()}\
109end package body;
110"""
112 return vhdl
114 def _register_field_records(self) -> str:
115 """
116 For every register (plain or in array) that has at least one field:
118 * Record with members for each field that are of the correct native VHDL type.
119 * Default value constant for the above record.
120 * Function to convert the above record to SLV.
121 * Function to convert a register SLV to the above record.
122 """
123 vhdl = """\
124 -- -----------------------------------------------------------------------------
125 -- Record with correctly-typed members for each field in each register.
126"""
128 for register, register_array in self.iterate_registers():
129 if not register.fields:
130 continue
132 register_name = self.qualified_register_name(
133 register=register, register_array=register_array
134 )
135 register_description = self.register_description(
136 register=register, register_array=register_array
137 )
139 record = ""
140 init = []
142 for field in register.fields:
143 field_name = self.qualified_field_name(
144 register=register, register_array=register_array, field=field
145 )
146 init.append(f"{field.name} => {field_name}_init")
148 field_type_name = self.field_type_name(
149 register=register, register_array=register_array, field=field
150 )
151 record += f" {field.name} : {field_type_name};\n"
153 init_str = " " + ",\n ".join(init)
155 vhdl += f"""\
156 -- Fields in the {register_description} as a record.
157 type {register_name}_t is record
158{record}\
159 end record;
160 -- Default value for the {register_description} as a record.
161 constant {register_name}_init : {register_name}_t := (
162{init_str}
163 );
164 -- Convert a record of the {register_description} to SLV.
165 function to_slv(data : {register_name}_t) return reg_t;
166 -- Convert an SLV register value to the record for the {register_description}.
167 function to_{register_name}(data : reg_t) return {register_name}_t;
169"""
171 return vhdl
173 def _register_records(self) -> str:
174 """
175 Get two records,
176 * One with all the registers that are in the 'up' direction.
177 * One with all the registers that are in the 'down' direction.
179 Along with conversion function declarations to/from SLV.
181 In order to create the above records, we have to create partial-array records for each
182 register array.
183 One record for 'up' and one for 'down', with all registers in the array that are in
184 that direction.
185 """
186 vhdl = ""
188 direction = HardwareAccessDirection.UP
190 if self.has_any_hardware_accessible_register(direction=direction):
191 vhdl += self._array_field_records(direction=direction)
192 vhdl += self._get_register_record(direction=direction)
193 vhdl += f"""\
194 -- Convert record with everything in the '{direction.name.lower()}' direction to SLV \
195register list.
196 function to_slv(data : {self.name}_regs_{direction.name.lower()}_t) return {self.name}_regs_t;
198"""
200 direction = HardwareAccessDirection.DOWN
202 if self.has_any_hardware_accessible_register(direction=direction):
203 vhdl += self._array_field_records(direction=direction)
204 vhdl += self._get_register_record(direction=direction)
205 vhdl += f"""\
206 -- Convert SLV register list to record with everything in the \
207'{direction.name.lower()}' direction.
208 function to_{self.name}_regs_{direction.name.lower()}(data : {self.name}_regs_t) \
209return {self.name}_regs_{direction.name.lower()}_t;
211"""
213 return vhdl
215 def _array_field_records(self, direction: "HardwareAccessDirection") -> str:
216 """
217 For every register array that has at least one register in the specified direction:
219 * Record with members for each register in the array that is in the specified direction.
220 * Default value constant for the above record.
221 * VHDL vector type of the above record, ranged per the range of the register array.
223 This function assumes that the register map has registers in the given direction.
224 """
225 vhdl = ""
227 for array in self.iterate_hardware_accessible_register_arrays(direction=direction):
228 array_name = self.qualified_register_array_name(register_array=array)
229 vhdl += f"""\
230 -- Registers of the '{array.name}' array that are in the '{direction.name.lower()}' direction.
231 type {array_name}_{direction.name.lower()}_t is record
232"""
234 vhdl_array_init = []
235 for register in self.iterate_hardware_accessible_array_registers(
236 register_array=array, direction=direction
237 ):
238 vhdl += self._record_member_declaration_for_register(
239 register=register, register_array=array
240 )
242 register_name = self.qualified_register_name(
243 register=register, register_array=array
244 )
245 init = f"{register_name}_init" if register.fields else "(others => '0')"
246 vhdl_array_init.append(f"{register.name} => {init}")
248 init = " " + ",\n ".join(vhdl_array_init)
249 vhdl += f"""\
250 end record;
251 -- Default value of the above record.
252 constant {array_name}_{direction.name.lower()}_init : {array_name}_{direction.name.lower()}_t := (
253{init}
254 );
255 -- VHDL array of the above record, ranged per the length of the '{array.name}' \
256register array.
257 type {array_name}_{direction.name.lower()}_vec_t is array (0 to {array.length - 1}) of \
258{array_name}_{direction.name.lower()}_t;
260"""
262 heading = f"""\
263 -- -----------------------------------------------------------------------------
264 -- Below is a record with correctly typed and ranged members for all registers, register arrays
265 -- and fields that are in the '{direction.name.lower()}' direction.
266"""
267 if vhdl:
268 heading += f"""\
269 -- But first, records for the registers of each register array the are in \
270the '{direction.name.lower()}' direction.
271"""
273 return f"{heading}{vhdl}"
275 def _get_register_record(self, direction: "HardwareAccessDirection") -> str:
276 """
277 Get the record that contains all registers and arrays in the specified direction.
278 Also default value constant for this record.
280 This function assumes that the register map has registers in the given direction.
281 """
282 record_init = []
283 vhdl = f"""\
284 -- Record with everything in the '{direction.name.lower()}' direction.
285 type {self.name}_regs_{direction.name.lower()}_t is record
286"""
288 for array in self.iterate_hardware_accessible_register_arrays(direction=direction):
289 array_name = self.qualified_register_array_name(register_array=array)
291 vhdl += f" {array.name} : {array_name}_{direction.name.lower()}_vec_t;\n"
292 record_init.append(
293 f"{array.name} => (others => {array_name}_{direction.name.lower()}_init)"
294 )
296 for register in self.iterate_hardware_accessible_plain_registers(direction=direction):
297 vhdl += self._record_member_declaration_for_register(register=register)
299 if register.fields:
300 register_name = self.qualified_register_name(register=register)
301 record_init.append(f"{register.name} => {register_name}_init")
302 else:
303 record_init.append(f"{register.name} => (others => '0')")
305 init = " " + ",\n ".join(record_init)
307 return f"""\
308{vhdl}\
309 end record;
310 -- Default value of the above record.
311 constant {self.name}_regs_{direction.name.lower()}_init : \
312{self.name}_regs_{direction.name.lower()}_t := (
313{init}
314 );
315"""
317 def _record_member_declaration_for_register(
318 self, register: "Register", register_array: Optional["RegisterArray"] = None
319 ) -> str:
320 """
321 Get the record member declaration line for a register that shall be part of the record.
322 """
323 register_name = self.qualified_register_name(
324 register=register, register_array=register_array
325 )
327 if register.fields:
328 return f" {register.name} : {register_name}_t;\n"
330 return f" {register.name} : reg_t;\n"
332 def _register_was_accessed(self) -> str:
333 """
334 Get record for 'reg_was_read' and 'reg_was_written' ports.
335 Should include only the registers that are actually readable/writeable.
336 """
337 vhdl = ""
339 for direction in SoftwareAccessDirection:
340 if self.has_any_software_accessible_register(direction=direction):
341 vhdl += self._register_was_accessed_record(direction=direction)
343 return vhdl
345 def _register_was_accessed_record(self, direction: "SoftwareAccessDirection") -> str:
346 """
347 Get the record for 'reg_was_read' or 'reg_was_written'.
348 """
349 vhdl = f"""\
350 -- ---------------------------------------------------------------------------
351 -- Below is a record with a status bit for each {direction.value.name_adjective} register in the \
352register map.
353 -- It can be used for the 'reg_was_{direction.value.name_past}' port of a register file wrapper.
354"""
356 for array in self.iterate_software_accessible_register_arrays(direction=direction):
357 array_name = self.qualified_register_array_name(register_array=array)
358 vhdl += f"""\
359 -- One status bit for each {direction.value.name_adjective} register in the '{array.name}' \
360register array.
361 type {array_name}_was_{direction.value.name_past}_t is record
362"""
364 for register in self.iterate_software_accessible_array_registers(
365 register_array=array, direction=direction
366 ):
367 vhdl += f" {register.name} : std_ulogic;\n"
369 vhdl += f"""\
370 end record;
371 -- Default value of the above record.
372 constant {array_name}_was_{direction.value.name_past}_init : \
373{array_name}_was_{direction.value.name_past}_t := (others => '0');
374 -- Vector of the above record, ranged per the length of the '{array.name}' \
375register array.
376 type {array_name}_was_{direction.value.name_past}_vec_t is array (0 to {array.length - 1}) \
377of {array_name}_was_{direction.value.name_past}_t;
379"""
381 vhdl += f"""\
382 -- Combined status mask record for all {direction.value.name_adjective} register.
383 type {self.name}_reg_was_{direction.value.name_past}_t is record
384"""
386 array_init = []
387 for array in self.iterate_software_accessible_register_arrays(direction=direction):
388 array_name = self.qualified_register_array_name(register_array=array)
390 vhdl += f" {array.name} : {array_name}_was_{direction.value.name_past}_vec_t;\n"
391 array_init.append(
392 f"{array.name} => (others => {array_name}_was_{direction.value.name_past}_init)"
393 )
395 has_at_least_one_register = False
396 for register in self.iterate_software_accessible_plain_registers(direction=direction):
397 vhdl += f" {register.name} : std_ulogic;\n"
398 has_at_least_one_register = True
400 init_arrays = (" " + ",\n ".join(array_init)) if array_init else ""
401 init_registers = " others => '0'" if has_at_least_one_register else ""
402 separator = ",\n" if (init_arrays and init_registers) else ""
404 vhdl += f"""\
405 end record;
406 -- Default value for the above record.
407 constant {self.name}_reg_was_{direction.value.name_past}_init : \
408{self.name}_reg_was_{direction.value.name_past}_t := (
409{init_arrays}{separator}{init_registers}
410 );
411 -- Convert an SLV 'reg_was_{direction.value.name_past}' from generic register file \
412to the record above.
413 function to_{self.name}_reg_was_{direction.value.name_past}(
414 data : {self.name}_reg_was_accessed_t
415 ) return {self.name}_reg_was_{direction.value.name_past}_t;
417"""
419 return vhdl
421 def _register_field_record_conversion_implementations(self) -> str:
422 """
423 Implementation of functions that convert a register record with native field types
424 to/from SLV.
425 """
426 vhdl = ""
428 def _get_functions(register: "Register", register_array: Optional["RegisterArray"]) -> str:
429 register_name = self.qualified_register_name(
430 register=register, register_array=register_array
431 )
433 to_slv = ""
434 to_record = ""
436 for field in register.fields:
437 field_name = self.qualified_field_name(
438 register=register, register_array=register_array, field=field
439 )
440 field_to_slv = self.field_to_slv(
441 field=field, field_name=field_name, value=f"data.{field.name}"
442 )
443 to_slv += f" result({field_name}) := {field_to_slv};\n"
445 if isinstance(field, Bit):
446 to_record += f" result.{field.name} := data({field_name});\n"
448 elif isinstance(field, BitVector):
449 to_record += f" result.{field.name} := {field_name}_t(data({field_name}));\n"
451 elif isinstance(field, (Enumeration, Integer)):
452 to_record += f" result.{field.name} := to_{field_name}(data);\n"
454 else:
455 raise TypeError(f'Got unexpected field type: "{field}".')
457 # Set "don't care" on the bits that have no field, so that a register value comparison
458 # can be true even if there is junk in the unused bits.
459 return f"""\
460 function to_slv(data : {register_name}_t) return reg_t is
461 variable result : reg_t := (others => '-');
462 begin
463{to_slv}
464 return result;
465 end function;
467 function to_{register_name}(data : reg_t) return {register_name}_t is
468 variable result : {register_name}_t := {register_name}_init;
469 begin
470{to_record}
471 return result;
472 end function;
474"""
476 for register, register_array in self.iterate_registers():
477 if register.fields:
478 vhdl += _get_functions(register=register, register_array=register_array)
480 return vhdl
482 def _register_record_conversion_implementations(self) -> str:
483 """
484 Conversion function implementations to/from SLV for the records containing all
485 registers and arrays in 'up'/'down' direction.
486 """
487 vhdl = ""
489 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.UP):
490 vhdl += self._register_record_up_to_slv()
492 if self.has_any_hardware_accessible_register(direction=HardwareAccessDirection.DOWN):
493 vhdl += self._get_registers_down_to_record_function()
495 return vhdl
497 def _register_record_up_to_slv(self) -> str:
498 """
499 Conversion function implementation for converting a record of all the 'up' registers
500 to a register SLV list.
502 This function assumes that the register map has registers in the given direction.
503 """
504 to_slv = ""
506 for register, register_array in self.iterate_hardware_accessible_registers(
507 direction=HardwareAccessDirection.UP
508 ):
509 register_name = self.qualified_register_name(
510 register=register, register_array=register_array
511 )
513 if register_array is None:
514 result = f" result({register_name})"
515 record = f"data.{register.name}"
517 if register.fields:
518 to_slv += f"{result} := to_slv({record});\n"
519 else:
520 to_slv += f"{result} := {record};\n"
522 else:
523 for array_idx in range(register_array.length):
524 result = f" result({register_name}({array_idx}))"
525 record = f"data.{register_array.name}({array_idx}).{register.name}"
527 if register.fields:
528 to_slv += f"{result} := to_slv({record});\n"
529 else:
530 to_slv += f"{result} := {record};\n"
532 return f"""\
533 function to_slv(data : {self.name}_regs_up_t) return {self.name}_regs_t is
534 variable result : {self.name}_regs_t := {self.name}_regs_init;
535 begin
536{to_slv}
537 return result;
538 end function;
540"""
542 def _get_registers_down_to_record_function(self) -> str:
543 """
544 Conversion function implementation for converting all the 'down' registers
545 in a register SLV list to record.
547 This function assumes that the register map has registers in the given direction.
548 """
549 to_record = ""
551 for register, register_array in self.iterate_hardware_accessible_registers(
552 direction=HardwareAccessDirection.DOWN
553 ):
554 register_name = self.qualified_register_name(
555 register=register, register_array=register_array
556 )
558 if register_array is None:
559 result = f" result.{register.name}"
560 data = f"data({register_name})"
562 if register.fields:
563 to_record += f"{result} := to_{register_name}({data});\n"
564 else:
565 to_record += f"{result} := {data};\n"
567 else:
568 for array_idx in range(register_array.length):
569 result = f" result.{register_array.name}({array_idx}).{register.name}"
570 data = f"data({register_name}({array_idx}))"
572 if register.fields:
573 to_record += f"{result} := to_{register_name}({data});\n"
574 else:
575 to_record += f"{result} := {data};\n"
577 return f"""\
578 function to_{self.name}_regs_down(data : {self.name}_regs_t) return \
579{self.name}_regs_down_t is
580 variable result : {self.name}_regs_down_t := {self.name}_regs_down_init;
581 begin
582{to_record}
583 return result;
584 end function;
586"""
588 def _register_was_accessed_conversion_implementations(self) -> str:
589 """
590 Get conversion functions from SLV 'reg_was_read'/'reg_was_written' to record types.
591 """
592 vhdl = ""
594 for direction in SoftwareAccessDirection:
595 if self.has_any_software_accessible_register(direction=direction):
596 vhdl += self._register_was_accessed_conversion_implementation(direction=direction)
598 return vhdl
600 def _register_was_accessed_conversion_implementation(
601 self, direction: "SoftwareAccessDirection"
602 ) -> str:
603 """
604 Get a conversion function from SLV 'reg_was_read'/'reg_was_written' to record type.
605 """
606 vhdl = f"""\
607 function to_{self.name}_reg_was_{direction.value.name_past}(
608 data : {self.name}_reg_was_accessed_t
609 ) return {self.name}_reg_was_{direction.value.name_past}_t is
610 variable result : {self.name}_reg_was_{direction.value.name_past}_t := \
611{self.name}_reg_was_{direction.value.name_past}_init;
612 begin
613"""
615 for register in self.iterate_software_accessible_plain_registers(direction=direction):
616 register_name = self.qualified_register_name(register=register)
617 vhdl += f" result.{register.name} := data({register_name});\n"
619 for array in self.iterate_register_arrays():
620 for register in self.iterate_software_accessible_array_registers(
621 register_array=array, direction=direction
622 ):
623 register_name = self.qualified_register_name(
624 register=register, register_array=array
625 )
627 for array_index in range(array.length):
628 vhdl += (
629 f" result.{array.name}({array_index}).{register.name} := "
630 f"data({register_name}(array_index=>{array_index}));\n"
631 )
633 return f"""\
634{vhdl}
635 return result;
636 end function;
638"""