Coverage for hdl_registers/generator/vhdl/simulation/read_write_package.py: 96%
141 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_vector import BitVector
15from hdl_registers.field.numerical_interpretation import Signed, Unsigned
17from .vhdl_simulation_generator_common import VhdlSimulationGeneratorCommon
19if TYPE_CHECKING:
20 from pathlib import Path
22 from hdl_registers.field.register_field import RegisterField
23 from hdl_registers.register import Register
24 from hdl_registers.register_array import RegisterArray
27class VhdlSimulationReadWritePackageGenerator(VhdlSimulationGeneratorCommon):
28 """
29 Generate VHDL code with register read/write procedures that simplify simulation.
30 See the :ref:`generator_vhdl` article for usage details.
32 * For each readable register, procedures that read the register value.
33 Value can be read as:
35 1. bit vector,
37 2. integer, or
39 3. native VHDL record type as given by :class:`.VhdlRecordPackageGenerator`.
41 * For each field in each readable register, a procedure that reads the natively-typed value of
42 the field.
44 * For each writeable register, a procedure that writes the register value.
45 Value can be written as:
47 1. bit vector, or
49 2. native VHDL record type as given by :class:`.VhdlRecordPackageGenerator`.
51 * For each field in each writeable register, a procedure that writes a given
52 natively-typed field value.
54 Uses VUnit Verification Component calls to create bus read/write operations.
56 The generated VHDL file needs also the generated packages from
57 :class:`.VhdlRegisterPackageGenerator` and :class:`.VhdlRecordPackageGenerator`.
58 """
60 __version__ = "1.1.0"
62 SHORT_DESCRIPTION = "VHDL simulation read/write package"
64 @property
65 def output_file(self) -> Path:
66 """
67 Result will be placed in this file.
68 """
69 return self.output_folder / f"{self.name}_register_read_write_pkg.vhd"
71 def create(
72 self,
73 **kwargs: Any, # noqa: ANN401
74 ) -> Path:
75 """
76 See super class for API details.
78 Overloaded here because this package file shall only be created if the register list
79 actually has any registers.
80 """
81 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs)
83 def get_code(
84 self,
85 **kwargs: Any, # noqa: ANN401, ARG002
86 ) -> str:
87 """
88 Get a package with methods for reading/writing registers.
89 """
90 package_name = self.output_file.stem
92 return f"""\
93library ieee;
94use ieee.numeric_std.all;
95use ieee.std_logic_1164.all;
97library vunit_lib;
98use vunit_lib.bus_master_pkg.bus_master_t;
99use vunit_lib.bus_master_pkg.read_bus;
100use vunit_lib.bus_master_pkg.write_bus;
101use vunit_lib.com_types_pkg.network_t;
103library common;
104use common.addr_pkg.addr_t;
105use common.addr_pkg.addr_width;
107library register_file;
108use register_file.register_file_pkg.register_t;
109use register_file.register_file_pkg.register_width;
110use register_file.register_operations_pkg.register_bus_master;
112use work.{self.name}_regs_pkg.all;
113use work.{self.name}_register_record_pkg.all;
116package {package_name} is
118{self._declarations()}\
119end package;
121package body {package_name} is
123{self._implementations()}\
124end package body;
125"""
127 def _declarations(self) -> str:
128 """
129 Get procedure declarations for all procedures.
130 """
131 separator = self.get_separator_line(indent=2)
132 vhdl = ""
134 for register, register_array in self.iterate_registers():
135 register_name = self.qualified_register_name(
136 register=register, register_array=register_array
137 )
138 declarations = []
140 if register.mode.software_can_read:
141 # Read the register as a plain SLV.
142 signature = self._register_read_write_signature(
143 is_read_not_write=True,
144 register=register,
145 register_array=register_array,
146 value_type="register_t",
147 )
148 declarations.append(f"{signature};\n")
150 # Read the register as a plain SLV casted to integer.
151 signature = self._register_read_write_signature(
152 is_read_not_write=True,
153 register=register,
154 register_array=register_array,
155 value_type="integer",
156 )
157 declarations.append(f"{signature};\n")
159 if register.fields:
160 # Read the register as a record.
161 signature = self._register_read_write_signature(
162 is_read_not_write=True,
163 register=register,
164 register_array=register_array,
165 value_type=f"{register_name}_t",
166 )
167 declarations.append(f"{signature};\n")
169 for field in register.fields:
170 # Read the field as its native type.
171 value_type = self.field_type_name(
172 register=register, register_array=register_array, field=field
173 )
174 signature = self._field_read_write_signature(
175 is_read_not_write=True,
176 register=register,
177 register_array=register_array,
178 field=field,
179 value_type=value_type,
180 )
181 declarations.append(f"{signature};\n")
183 if self._should_be_able_to_access_field_as_integer(field=field):
184 # Read the field casted to an integer.
185 signature = self._field_read_write_signature(
186 is_read_not_write=True,
187 register=register,
188 register_array=register_array,
189 field=field,
190 value_type="integer",
191 )
192 declarations.append(f"{signature};\n")
194 if register.mode.software_can_write:
195 # Write the register as an integer.
196 signature = self._register_read_write_signature(
197 is_read_not_write=False,
198 register=register,
199 register_array=register_array,
200 value_type="integer",
201 )
202 declarations.append(f"{signature};\n")
204 if register.fields:
205 # Write the register as a record.
206 signature = self._register_read_write_signature(
207 is_read_not_write=False,
208 register=register,
209 register_array=register_array,
210 value_type=f"{register_name}_t",
211 )
212 declarations.append(f"{signature};\n")
213 else:
214 # Write the register as a plain SLV.
215 # This one is made available only if there are no fields.
216 # This is because there can be a signature ambiguity if both are available
217 # that some compilers can not resolve.
218 # Namely e.g. value=>(field_name => '1').
219 # Where the field is a std_logic.
220 # GHDL gets confused in this case between using the signature with the record
221 # or the one with SLV.
222 signature = self._register_read_write_signature(
223 is_read_not_write=False,
224 register=register,
225 register_array=register_array,
226 value_type="register_t",
227 )
228 declarations.append(f"{signature};\n")
230 for field in register.fields:
231 # Write the field as its native type.
232 value_type = self.field_type_name(
233 register=register, register_array=register_array, field=field
234 )
235 signature = self._field_read_write_signature(
236 is_read_not_write=False,
237 register=register,
238 register_array=register_array,
239 field=field,
240 value_type=value_type,
241 )
242 declarations.append(f"{signature};\n")
244 if self._should_be_able_to_access_field_as_integer(field=field):
245 # Write the field casted to an integer.
246 signature = self._field_read_write_signature(
247 is_read_not_write=False,
248 register=register,
249 register_array=register_array,
250 field=field,
251 value_type="integer",
252 )
253 declarations.append(f"{signature};\n")
255 vhdl += separator
256 vhdl += "\n".join(declarations)
257 vhdl += separator
258 vhdl += "\n"
260 return vhdl
262 def _register_read_write_signature(
263 self,
264 is_read_not_write: bool,
265 register: Register,
266 register_array: RegisterArray | None,
267 value_type: str,
268 ) -> str:
269 """
270 Get signature for a 'read_reg'/'write_reg' procedure.
271 """
272 direction = "read" if is_read_not_write else "write"
273 value_direction = "out" if is_read_not_write else "in"
275 register_name = self.qualified_register_name(
276 register=register, register_array=register_array
277 )
278 register_description = self.register_description(
279 register=register, register_array=register_array
280 )
281 # If it is not either of these, then it is the native type which shall not have a comment
282 # since it is the default.
283 type_comment = (
284 " as a plain 'register_t'"
285 if value_type == "register_t"
286 else " as an 'integer'"
287 if value_type == "integer"
288 else ""
289 )
291 return f"""\
292 -- {direction.capitalize()} the {register_description}{type_comment}.
293 procedure {direction}_{register_name}(
294 signal net : inout network_t;
295{self.get_array_index_port(register_array=register_array)}\
296 value : {value_direction} {value_type};
297 base_address : in addr_t := (others => '0');
298 bus_handle : in bus_master_t := register_bus_master
299 )\
300"""
302 def _field_read_write_signature(
303 self,
304 is_read_not_write: bool,
305 register: Register,
306 register_array: RegisterArray | None,
307 field: RegisterField,
308 value_type: str,
309 ) -> str:
310 """
311 Get signature for a 'read_field'/'write_field' procedure.
312 """
313 direction = "read" if is_read_not_write else "write"
315 field_name = self.qualified_field_name(
316 register=register, register_array=register_array, field=field
317 )
318 field_description = self.field_description(
319 register=register, field=field, register_array=register_array
320 )
321 # If its not integer, then it is the native type which shall shall not have a comment
322 # since it is the default.
323 type_comment = " as an 'integer'" if value_type == "integer" else ""
325 comment = ""
326 if not is_read_not_write:
327 if self.field_setter_should_read_modify_write(register=register):
328 comment = (
329 " -- Will read-modify-write the register to set the field to the "
330 "supplied 'value'.\n"
331 )
332 else:
333 comment = (
334 " -- Will write the whole register, with the field set to the \n"
335 " -- supplied 'value' and everything else set to default.\n"
336 )
338 value_direction = "out" if is_read_not_write else "in"
340 return f"""\
341 -- {direction.capitalize()} the {field_description}{type_comment}.
342{comment}\
343 procedure {direction}_{field_name}(
344 signal net : inout network_t;
345{self.get_array_index_port(register_array=register_array)}\
346 value : {value_direction} {value_type};
347 base_address : in addr_t := (others => '0');
348 bus_handle : in bus_master_t := register_bus_master
349 )\
350"""
352 @staticmethod
353 def _should_be_able_to_access_field_as_integer(field: RegisterField) -> bool:
354 """
355 Return True if the field is of a type where there should be procedures to read/write it
356 casted as an integer.
357 """
358 return isinstance(field, BitVector) and isinstance(
359 field.numerical_interpretation, (Signed, Unsigned)
360 )
362 def _implementations(self) -> str:
363 """
364 Get implementations of all procedures.
365 """
366 separator = self.get_separator_line(indent=2)
367 vhdl = ""
369 for register, register_array in self.iterate_registers():
370 register_name = self.qualified_register_name(
371 register=register, register_array=register_array
372 )
373 implementations = []
375 if register.mode.software_can_read:
376 # Read the register as a plain SLV.
377 implementations.append(
378 self._register_read_implementation(
379 register=register,
380 register_array=register_array,
381 value_type="register_t",
382 value_conversion="reg_value",
383 )
384 )
386 # Read the register as a plain SLV casted to integer.
387 implementations.append(
388 self._register_read_implementation(
389 register=register,
390 register_array=register_array,
391 value_type="integer",
392 value_conversion="to_integer(unsigned(reg_value))",
393 )
394 )
396 if register.fields:
397 # Read the register as a record.
398 implementations.append(
399 self._register_read_implementation(
400 register=register,
401 register_array=register_array,
402 value_type=f"{register_name}_t",
403 value_conversion=f"to_{register_name}(reg_value)",
404 )
405 )
407 for field in register.fields:
408 # Read the field as its native type.
409 value_type = self.field_type_name(
410 register=register, register_array=register_array, field=field
411 )
412 implementations.append(
413 self._field_read_implementation(
414 register=register,
415 register_array=register_array,
416 field=field,
417 value_type=value_type,
418 value_conversion=f"reg_value.{field.name}",
419 )
420 )
422 if self._should_be_able_to_access_field_as_integer(field=field):
423 # Read the field casted to an integer.
424 implementations.append(
425 self._field_read_implementation(
426 register=register,
427 register_array=register_array,
428 field=field,
429 value_type="integer",
430 value_conversion=f"to_integer(reg_value.{field.name})",
431 )
432 )
434 if register.mode.software_can_write:
435 # Write the register as an integer.
436 implementations.append(
437 self._register_write_implementation(
438 register=register,
439 register_array=register_array,
440 value_type="integer",
441 value_conversion="std_ulogic_vector(to_unsigned(value, register_width))",
442 )
443 )
445 if register.fields:
446 # Write the register as a record.
447 implementations.append(
448 self._register_write_implementation(
449 register=register,
450 register_array=register_array,
451 value_type=f"{register_name}_t",
452 value_conversion="to_slv(value)",
453 )
454 )
455 else:
456 # Write the register as a plain SLV.
457 # Only if there are no fields.
458 # See the signatures method for more info.
459 implementations.append(
460 self._register_write_implementation(
461 register=register,
462 register_array=register_array,
463 value_type="register_t",
464 value_conversion="value",
465 )
466 )
468 for field in register.fields:
469 # Write the field as its native type.
470 value_type = self.field_type_name(
471 register=register, register_array=register_array, field=field
472 )
473 implementations.append(
474 self._field_write_implementation(
475 register=register,
476 register_array=register_array,
477 field=field,
478 value_type=value_type,
479 )
480 )
482 if self._should_be_able_to_access_field_as_integer(field=field):
483 # Read the field casted to an integer.
484 implementations.append(
485 self._field_write_implementation(
486 register=register,
487 register_array=register_array,
488 field=field,
489 value_type="integer",
490 )
491 )
493 vhdl += separator
494 vhdl += "\n".join(implementations)
495 vhdl += separator
496 vhdl += "\n"
498 return vhdl
500 def _register_read_implementation(
501 self,
502 register: Register,
503 register_array: RegisterArray | None,
504 value_type: str,
505 value_conversion: str,
506 ) -> str:
507 """
508 Get implementation for a 'read_reg' procedure.
509 """
510 signature = self._register_read_write_signature(
511 is_read_not_write=True,
512 register=register,
513 register_array=register_array,
514 value_type=value_type,
515 )
517 return f"""\
518{signature} is
519{self.reg_index_constant(register=register, register_array=register_array)}\
520{self.reg_address_constant()}\
521 variable reg_value : register_t := (others => '0');
522 begin
523 read_bus(
524 net => net,
525 bus_handle => bus_handle,
526 address => std_logic_vector(reg_address),
527 data => reg_value
528 );
529 value := {value_conversion};
530 end procedure;
531"""
533 def _register_write_implementation(
534 self,
535 register: Register,
536 register_array: RegisterArray | None,
537 value_type: str,
538 value_conversion: str,
539 ) -> str:
540 """
541 Get implementation for a 'write_reg' procedure.
542 """
543 signature = self._register_read_write_signature(
544 is_read_not_write=False,
545 register=register,
546 register_array=register_array,
547 value_type=value_type,
548 )
550 return f"""\
551{signature} is
552{self.reg_index_constant(register=register, register_array=register_array)}\
553{self.reg_address_constant()}\
554 constant reg_value : register_t := {value_conversion};
555 begin
556 write_bus(
557 net => net,
558 bus_handle => bus_handle,
559 address => std_logic_vector(reg_address),
560 data => reg_value
561 );
562 end procedure;
563"""
565 def _field_read_implementation(
566 self,
567 register: Register,
568 register_array: RegisterArray | None,
569 field: RegisterField,
570 value_type: str,
571 value_conversion: str,
572 ) -> str:
573 """
574 Get implementation for a 'read_field' procedure.
575 """
576 signature = self._field_read_write_signature(
577 is_read_not_write=True,
578 register=register,
579 register_array=register_array,
580 field=field,
581 value_type=value_type,
582 )
584 register_name = self.qualified_register_name(
585 register=register, register_array=register_array
586 )
588 return f"""\
589{signature} is
590 variable reg_value : {register_name}_t := {register_name}_init;
591 begin
592 read_{register_name}(
593 net => net,
594{self.get_array_index_association(register_array=register_array)}\
595 value => reg_value,
596 base_address => base_address,
597 bus_handle => bus_handle
598 );
599 value := {value_conversion};
600 end procedure;
601"""
603 def _field_write_implementation(
604 self,
605 register: Register,
606 register_array: RegisterArray | None,
607 field: RegisterField,
608 value_type: str,
609 ) -> str:
610 """
611 Get implementation for a 'write_field' procedure.
612 """
613 signature = self._field_read_write_signature(
614 is_read_not_write=False,
615 register=register,
616 register_array=register_array,
617 field=field,
618 value_type=value_type,
619 )
620 register_name = self.qualified_register_name(
621 register=register, register_array=register_array
622 )
624 if self.field_setter_should_read_modify_write(register=register):
625 set_base_value = f"""\
626 read_{register_name}(
627 net => net,
628{self.get_array_index_association(register_array=register_array)}\
629 value => reg_value,
630 base_address => base_address,
631 bus_handle => bus_handle
632 );
633"""
634 else:
635 set_base_value = ""
637 if value_type == "integer":
638 field_name = self.qualified_field_name(
639 register=register, register_array=register_array, field=field
640 )
641 field_width = f"{field_name}_width"
643 if isinstance(field, BitVector) and isinstance(
644 field.numerical_interpretation, Unsigned
645 ):
646 field_conversion = f"to_unsigned(value, {field_width})"
647 elif isinstance(field, BitVector) and isinstance(
648 field.numerical_interpretation, Signed
649 ):
650 field_conversion = f"to_signed(value, {field_width})"
651 else:
652 raise ValueError(f"Should not end up here for field: {field}")
653 else:
654 field_conversion = "value"
656 return f"""\
657{signature} is
658 variable reg_value : {register_name}_t := {register_name}_init;
659 begin
660{set_base_value}\
661 reg_value.{field.name} := {field_conversion};
663 write_{register_name}(
664 net => net,
665{self.get_array_index_association(register_array=register_array)}\
666 value => reg_value,
667 base_address => base_address,
668 bus_handle => bus_handle
669 );
670 end procedure;
671"""