Coverage for hdl_registers/generator/vhdl/simulation/read_write_package.py: 96%
141 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 06:41 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 06:41 +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 See :ref:`vhdl_dependencies` for further dependencies.
59 """
61 __version__ = "1.1.1"
63 SHORT_DESCRIPTION = "VHDL simulation read/write package"
65 @property
66 def output_file(self) -> Path:
67 """
68 Result will be placed in this file.
69 """
70 return self.output_folder / f"{self.name}_register_read_write_pkg.vhd"
72 def create(
73 self,
74 **kwargs: Any, # noqa: ANN401
75 ) -> Path:
76 """
77 See super class for API details.
79 Overloaded here because this package file shall only be created if the register list
80 actually has any registers.
81 """
82 return self._create_if_there_are_registers_otherwise_delete_file(**kwargs)
84 def get_code(
85 self,
86 **kwargs: Any, # noqa: ANN401, ARG002
87 ) -> str:
88 """
89 Get a package with methods for reading/writing registers.
90 """
91 package_name = self.output_file.stem
93 return f"""\
94library ieee;
95use ieee.numeric_std.all;
96use ieee.std_logic_1164.all;
98library vunit_lib;
99use vunit_lib.bus_master_pkg.bus_master_t;
100use vunit_lib.bus_master_pkg.read_bus;
101use vunit_lib.bus_master_pkg.write_bus;
102use vunit_lib.com_types_pkg.network_t;
104library register_file;
105use register_file.register_file_pkg.register_t;
106use register_file.register_file_pkg.register_width;
107use register_file.register_operations_pkg.register_bus_master;
109use work.{self.name}_regs_pkg.all;
110use work.{self.name}_register_record_pkg.all;
113package {package_name} is
115{self._declarations()}\
116end package;
118package body {package_name} is
120{self._implementations()}\
121end package body;
122"""
124 def _declarations(self) -> str:
125 """
126 Get procedure declarations for all procedures.
127 """
128 separator = self.get_separator_line(indent=2)
129 vhdl = ""
131 for register, register_array in self.iterate_registers():
132 register_name = self.qualified_register_name(
133 register=register, register_array=register_array
134 )
135 declarations = []
137 if register.mode.software_can_read:
138 # Read the register as a plain SLV.
139 signature = self._register_read_write_signature(
140 is_read_not_write=True,
141 register=register,
142 register_array=register_array,
143 value_type="register_t",
144 )
145 declarations.append(f"{signature};\n")
147 # Read the register as a plain SLV casted to integer.
148 signature = self._register_read_write_signature(
149 is_read_not_write=True,
150 register=register,
151 register_array=register_array,
152 value_type="integer",
153 )
154 declarations.append(f"{signature};\n")
156 if register.fields:
157 # Read the register as a record.
158 signature = self._register_read_write_signature(
159 is_read_not_write=True,
160 register=register,
161 register_array=register_array,
162 value_type=f"{register_name}_t",
163 )
164 declarations.append(f"{signature};\n")
166 for field in register.fields:
167 # Read the field as its native type.
168 value_type = self.field_type_name(
169 register=register, register_array=register_array, field=field
170 )
171 signature = self._field_read_write_signature(
172 is_read_not_write=True,
173 register=register,
174 register_array=register_array,
175 field=field,
176 value_type=value_type,
177 )
178 declarations.append(f"{signature};\n")
180 if self._should_be_able_to_access_field_as_integer(field=field):
181 # Read the field casted to an integer.
182 signature = self._field_read_write_signature(
183 is_read_not_write=True,
184 register=register,
185 register_array=register_array,
186 field=field,
187 value_type="integer",
188 )
189 declarations.append(f"{signature};\n")
191 if register.mode.software_can_write:
192 # Write the register as an integer.
193 signature = self._register_read_write_signature(
194 is_read_not_write=False,
195 register=register,
196 register_array=register_array,
197 value_type="integer",
198 )
199 declarations.append(f"{signature};\n")
201 if register.fields:
202 # Write the register as a record.
203 signature = self._register_read_write_signature(
204 is_read_not_write=False,
205 register=register,
206 register_array=register_array,
207 value_type=f"{register_name}_t",
208 )
209 declarations.append(f"{signature};\n")
210 else:
211 # Write the register as a plain SLV.
212 # This one is made available only if there are no fields.
213 # This is because there can be a signature ambiguity if both are available
214 # that some compilers can not resolve.
215 # Namely e.g. value=>(field_name => '1').
216 # Where the field is a std_logic.
217 # GHDL gets confused in this case between using the signature with the record
218 # or the one with SLV.
219 signature = self._register_read_write_signature(
220 is_read_not_write=False,
221 register=register,
222 register_array=register_array,
223 value_type="register_t",
224 )
225 declarations.append(f"{signature};\n")
227 for field in register.fields:
228 # Write the field as its native type.
229 value_type = self.field_type_name(
230 register=register, register_array=register_array, field=field
231 )
232 signature = self._field_read_write_signature(
233 is_read_not_write=False,
234 register=register,
235 register_array=register_array,
236 field=field,
237 value_type=value_type,
238 )
239 declarations.append(f"{signature};\n")
241 if self._should_be_able_to_access_field_as_integer(field=field):
242 # Write the field casted to an integer.
243 signature = self._field_read_write_signature(
244 is_read_not_write=False,
245 register=register,
246 register_array=register_array,
247 field=field,
248 value_type="integer",
249 )
250 declarations.append(f"{signature};\n")
252 vhdl += separator
253 vhdl += "\n".join(declarations)
254 vhdl += separator
255 vhdl += "\n"
257 return vhdl
259 def _register_read_write_signature(
260 self,
261 is_read_not_write: bool,
262 register: Register,
263 register_array: RegisterArray | None,
264 value_type: str,
265 ) -> str:
266 """
267 Get signature for a 'read_reg'/'write_reg' procedure.
268 """
269 direction = "read" if is_read_not_write else "write"
270 value_direction = "out" if is_read_not_write else "in"
272 register_name = self.qualified_register_name(
273 register=register, register_array=register_array
274 )
275 register_description = self.register_description(
276 register=register, register_array=register_array
277 )
278 # If it is not either of these, then it is the native type which shall not have a comment
279 # since it is the default.
280 type_comment = (
281 " as a plain 'register_t'"
282 if value_type == "register_t"
283 else " as an 'integer'"
284 if value_type == "integer"
285 else ""
286 )
288 return f"""\
289 -- {direction.capitalize()} the {register_description}{type_comment}.
290 procedure {direction}_{register_name}(
291 signal net : inout network_t;
292{self.get_array_index_port(register_array=register_array)}\
293 value : {value_direction} {value_type};
294 base_address : in unsigned(32 - 1 downto 0) := (others => '0');
295 bus_handle : in bus_master_t := register_bus_master
296 )\
297"""
299 def _field_read_write_signature(
300 self,
301 is_read_not_write: bool,
302 register: Register,
303 register_array: RegisterArray | None,
304 field: RegisterField,
305 value_type: str,
306 ) -> str:
307 """
308 Get signature for a 'read_field'/'write_field' procedure.
309 """
310 direction = "read" if is_read_not_write else "write"
312 field_name = self.qualified_field_name(
313 register=register, register_array=register_array, field=field
314 )
315 field_description = self.field_description(
316 register=register, field=field, register_array=register_array
317 )
318 # If its not integer, then it is the native type which shall shall not have a comment
319 # since it is the default.
320 type_comment = " as an 'integer'" if value_type == "integer" else ""
322 comment = ""
323 if not is_read_not_write:
324 if self.field_setter_should_read_modify_write(register=register):
325 comment = (
326 " -- Will read-modify-write the register to set the field to the "
327 "supplied 'value'.\n"
328 )
329 else:
330 comment = (
331 " -- Will write the whole register, with the field set to the \n"
332 " -- supplied 'value' and everything else set to default.\n"
333 )
335 value_direction = "out" if is_read_not_write else "in"
337 return f"""\
338 -- {direction.capitalize()} the {field_description}{type_comment}.
339{comment}\
340 procedure {direction}_{field_name}(
341 signal net : inout network_t;
342{self.get_array_index_port(register_array=register_array)}\
343 value : {value_direction} {value_type};
344 base_address : in unsigned(32 - 1 downto 0) := (others => '0');
345 bus_handle : in bus_master_t := register_bus_master
346 )\
347"""
349 @staticmethod
350 def _should_be_able_to_access_field_as_integer(field: RegisterField) -> bool:
351 """
352 Return True if the field is of a type where there should be procedures to read/write it
353 casted as an integer.
354 """
355 return isinstance(field, BitVector) and isinstance(
356 field.numerical_interpretation, (Signed, Unsigned)
357 )
359 def _implementations(self) -> str:
360 """
361 Get implementations of all procedures.
362 """
363 separator = self.get_separator_line(indent=2)
364 vhdl = ""
366 for register, register_array in self.iterate_registers():
367 register_name = self.qualified_register_name(
368 register=register, register_array=register_array
369 )
370 implementations = []
372 if register.mode.software_can_read:
373 # Read the register as a plain SLV.
374 implementations.append(
375 self._register_read_implementation(
376 register=register,
377 register_array=register_array,
378 value_type="register_t",
379 value_conversion="reg_value",
380 )
381 )
383 # Read the register as a plain SLV casted to integer.
384 implementations.append(
385 self._register_read_implementation(
386 register=register,
387 register_array=register_array,
388 value_type="integer",
389 value_conversion="to_integer(unsigned(reg_value))",
390 )
391 )
393 if register.fields:
394 # Read the register as a record.
395 implementations.append(
396 self._register_read_implementation(
397 register=register,
398 register_array=register_array,
399 value_type=f"{register_name}_t",
400 value_conversion=f"to_{register_name}(reg_value)",
401 )
402 )
404 for field in register.fields:
405 # Read the field as its native type.
406 value_type = self.field_type_name(
407 register=register, register_array=register_array, field=field
408 )
409 implementations.append(
410 self._field_read_implementation(
411 register=register,
412 register_array=register_array,
413 field=field,
414 value_type=value_type,
415 value_conversion=f"reg_value.{field.name}",
416 )
417 )
419 if self._should_be_able_to_access_field_as_integer(field=field):
420 # Read the field casted to an integer.
421 implementations.append(
422 self._field_read_implementation(
423 register=register,
424 register_array=register_array,
425 field=field,
426 value_type="integer",
427 value_conversion=f"to_integer(reg_value.{field.name})",
428 )
429 )
431 if register.mode.software_can_write:
432 # Write the register as an integer.
433 implementations.append(
434 self._register_write_implementation(
435 register=register,
436 register_array=register_array,
437 value_type="integer",
438 value_conversion="std_ulogic_vector(to_unsigned(value, register_width))",
439 )
440 )
442 if register.fields:
443 # Write the register as a record.
444 implementations.append(
445 self._register_write_implementation(
446 register=register,
447 register_array=register_array,
448 value_type=f"{register_name}_t",
449 value_conversion="to_slv(value)",
450 )
451 )
452 else:
453 # Write the register as a plain SLV.
454 # Only if there are no fields.
455 # See the signatures method for more info.
456 implementations.append(
457 self._register_write_implementation(
458 register=register,
459 register_array=register_array,
460 value_type="register_t",
461 value_conversion="value",
462 )
463 )
465 for field in register.fields:
466 # Write the field as its native type.
467 value_type = self.field_type_name(
468 register=register, register_array=register_array, field=field
469 )
470 implementations.append(
471 self._field_write_implementation(
472 register=register,
473 register_array=register_array,
474 field=field,
475 value_type=value_type,
476 )
477 )
479 if self._should_be_able_to_access_field_as_integer(field=field):
480 # Read the field casted to an integer.
481 implementations.append(
482 self._field_write_implementation(
483 register=register,
484 register_array=register_array,
485 field=field,
486 value_type="integer",
487 )
488 )
490 vhdl += separator
491 vhdl += "\n".join(implementations)
492 vhdl += separator
493 vhdl += "\n"
495 return vhdl
497 def _register_read_implementation(
498 self,
499 register: Register,
500 register_array: RegisterArray | None,
501 value_type: str,
502 value_conversion: str,
503 ) -> str:
504 """
505 Get implementation for a 'read_reg' procedure.
506 """
507 signature = self._register_read_write_signature(
508 is_read_not_write=True,
509 register=register,
510 register_array=register_array,
511 value_type=value_type,
512 )
514 return f"""\
515{signature} is
516{self.reg_index_constant(register=register, register_array=register_array)}\
517{self.reg_address_constant()}\
518 variable reg_value : register_t := (others => '0');
519 begin
520 read_bus(
521 net => net,
522 bus_handle => bus_handle,
523 address => std_logic_vector(reg_address),
524 data => reg_value
525 );
526 value := {value_conversion};
527 end procedure;
528"""
530 def _register_write_implementation(
531 self,
532 register: Register,
533 register_array: RegisterArray | None,
534 value_type: str,
535 value_conversion: str,
536 ) -> str:
537 """
538 Get implementation for a 'write_reg' procedure.
539 """
540 signature = self._register_read_write_signature(
541 is_read_not_write=False,
542 register=register,
543 register_array=register_array,
544 value_type=value_type,
545 )
547 return f"""\
548{signature} is
549{self.reg_index_constant(register=register, register_array=register_array)}\
550{self.reg_address_constant()}\
551 constant reg_value : register_t := {value_conversion};
552 begin
553 write_bus(
554 net => net,
555 bus_handle => bus_handle,
556 address => std_logic_vector(reg_address),
557 data => reg_value
558 );
559 end procedure;
560"""
562 def _field_read_implementation(
563 self,
564 register: Register,
565 register_array: RegisterArray | None,
566 field: RegisterField,
567 value_type: str,
568 value_conversion: str,
569 ) -> str:
570 """
571 Get implementation for a 'read_field' procedure.
572 """
573 signature = self._field_read_write_signature(
574 is_read_not_write=True,
575 register=register,
576 register_array=register_array,
577 field=field,
578 value_type=value_type,
579 )
581 register_name = self.qualified_register_name(
582 register=register, register_array=register_array
583 )
585 return f"""\
586{signature} is
587 variable reg_value : {register_name}_t := {register_name}_init;
588 begin
589 read_{register_name}(
590 net => net,
591{self.get_array_index_association(register_array=register_array)}\
592 value => reg_value,
593 base_address => base_address,
594 bus_handle => bus_handle
595 );
596 value := {value_conversion};
597 end procedure;
598"""
600 def _field_write_implementation(
601 self,
602 register: Register,
603 register_array: RegisterArray | None,
604 field: RegisterField,
605 value_type: str,
606 ) -> str:
607 """
608 Get implementation for a 'write_field' procedure.
609 """
610 signature = self._field_read_write_signature(
611 is_read_not_write=False,
612 register=register,
613 register_array=register_array,
614 field=field,
615 value_type=value_type,
616 )
617 register_name = self.qualified_register_name(
618 register=register, register_array=register_array
619 )
621 if self.field_setter_should_read_modify_write(register=register):
622 set_base_value = f"""\
623 read_{register_name}(
624 net => net,
625{self.get_array_index_association(register_array=register_array)}\
626 value => reg_value,
627 base_address => base_address,
628 bus_handle => bus_handle
629 );
630"""
631 else:
632 set_base_value = ""
634 if value_type == "integer":
635 field_name = self.qualified_field_name(
636 register=register, register_array=register_array, field=field
637 )
638 field_width = f"{field_name}_width"
640 if isinstance(field, BitVector) and isinstance(
641 field.numerical_interpretation, Unsigned
642 ):
643 field_conversion = f"to_unsigned(value, {field_width})"
644 elif isinstance(field, BitVector) and isinstance(
645 field.numerical_interpretation, Signed
646 ):
647 field_conversion = f"to_signed(value, {field_width})"
648 else:
649 raise ValueError(f"Should not end up here for field: {field}")
650 else:
651 field_conversion = "value"
653 return f"""\
654{signature} is
655 variable reg_value : {register_name}_t := {register_name}_init;
656 begin
657{set_base_value}\
658 reg_value.{field.name} := {field_conversion};
660 write_{register_name}(
661 net => net,
662{self.get_array_index_association(register_array=register_array)}\
663 value => reg_value,
664 base_address => base_address,
665 bus_handle => bus_handle
666 );
667 end procedure;
668"""