Coverage for tsfpga/module.py: 99%
186 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-21 23:39 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-21 23:39 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project, a project platform for modern FPGA development.
5# https://tsfpga.com
6# https://github.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9from __future__ import annotations
11import random
12from typing import TYPE_CHECKING, Any
14from hdl_registers.generator.vhdl.axi_lite.wrapper import VhdlAxiLiteWrapperGenerator
15from hdl_registers.generator.vhdl.record_package import VhdlRecordPackageGenerator
16from hdl_registers.generator.vhdl.register_package import VhdlRegisterPackageGenerator
17from hdl_registers.generator.vhdl.simulation.check_package import (
18 VhdlSimulationCheckPackageGenerator,
19)
20from hdl_registers.generator.vhdl.simulation.read_write_package import (
21 VhdlSimulationReadWritePackageGenerator,
22)
23from hdl_registers.generator.vhdl.simulation.wait_until_package import (
24 VhdlSimulationWaitUntilPackageGenerator,
25)
26from hdl_registers.parser.toml import from_toml
28from tsfpga.constraint import Constraint
29from tsfpga.hdl_file import HdlFile, get_hdl_file_endings
30from tsfpga.ip_core_file import IpCoreFile
31from tsfpga.module_list import ModuleList
32from tsfpga.system_utils import load_python_module
34if TYPE_CHECKING:
35 from collections.abc import Callable, Iterable
36 from pathlib import Path
38 from hdl_registers.register import Register
39 from hdl_registers.register_list import RegisterList
40 from vunit.ui import VUnit
41 from vunit.ui.test import Test
42 from vunit.ui.testbench import TestBench
44 from .build_project_list import BuildProject
47class BaseModule:
48 """
49 Base class for handling a HDL module with RTL code, constraints, etc.
51 Files are gathered from a lot of different sub-folders, to accommodate for projects having
52 different catalog structure.
53 """
55 # Set to False if you do not want to create these register artifacts for this module.
56 # Can be done in a child class or on an object instance.
57 # Note that artifacts will only be created if the module actually has any registers.
58 create_register_package = True
59 create_record_package = True
60 create_axi_lite_wrapper = True
61 create_simulation_read_write_package = True
62 create_simulation_check_package = True
63 create_simulation_wait_until_package = True
65 def __init__(
66 self, path: Path, library_name: str, default_registers: list[Register] | None = None
67 ) -> None:
68 """
69 Arguments:
70 path: Path to the module folder.
71 library_name: VHDL library name.
72 default_registers: Default registers.
73 """
74 self.path = path.resolve()
75 self.name = path.name
76 self.library_name = library_name
78 self._default_registers = default_registers
79 self._registers: RegisterList | None = None
81 @property
82 def test_folders(self) -> list[Path]:
83 """
84 Testbench files will be gathered from these folders.
85 """
86 return [
87 self.path / "test",
88 self.path / "rtl" / "tb",
89 ]
91 @property
92 def sim_folders(self) -> list[Path]:
93 """
94 Source code files with simulation models will be gathered from these folders.
96 Theses are always included in the simulation project.
97 When testbenches from this module are excluded, these simulation model files will still be
98 included and can be used by other modules.
99 """
100 return [
101 self.path / "sim",
102 self.register_simulation_folder,
103 ]
105 @property
106 def synthesis_folders(self) -> list[Path]:
107 """
108 Synthesis/implementation source code files will be gathered from these folders.
109 """
110 return [
111 self.path,
112 self.path / "src",
113 self.path / "rtl",
114 self.path / "hdl" / "rtl",
115 self.path / "hdl" / "package",
116 self.register_synthesis_folder,
117 ]
119 @property
120 def register_data_file(self) -> Path:
121 """
122 The path to this module's register data file (which may or may not exist).
123 """
124 return self.path / f"regs_{self.name}.toml"
126 @property
127 def register_simulation_folder(self) -> Path:
128 """
129 Generated register artifacts that are needed for simulation will be placed in this folder.
130 """
131 return self.path / "regs_sim"
133 @property
134 def register_synthesis_folder(self) -> Path:
135 """
136 Generated register artifacts that are needed for synthesis/implementation will be
137 placed in this folder.
138 """
139 return self.path / "regs_src"
141 @property
142 def registers(self) -> RegisterList | None:
143 """
144 Get the registers for this module.
145 Will be ``None`` if the module doesn't have any registers.
146 I.e. if no TOML file exists and no hook creates registers.
147 """
148 if self._registers:
149 # Only create object from TOML once.
150 return self._registers
152 toml_file = self.register_data_file
153 if toml_file.exists():
154 self._registers = from_toml(
155 name=self.name, toml_file=toml_file, default_registers=self._default_registers
156 )
158 self.registers_hook()
159 return self._registers
161 def setup_vunit(
162 self,
163 vunit_proj: VUnit,
164 **kwargs: Any, # noqa: ANN401
165 ) -> None:
166 """
167 Setup local configuration of this module's test benches.
169 .. Note::
170 This default method does nothing.
171 Should be overridden by modules that have any test benches that operate via generics.
173 Arguments:
174 vunit_proj: The VUnit project that is used to run simulation.
175 kwargs: Use this to pass an arbitrary list of arguments from your ``simulate.py``
176 to the module where you set up your tests.
177 This could be, e.g., data dimensions, location of test files, etc.
178 """
180 def get_build_projects(self) -> list[BuildProject]:
181 """
182 Get FPGA build projects defined by this module.
184 .. Note::
185 This default method does nothing.
186 Should be overridden by modules that set up build projects.
188 Return:
189 FPGA build projects.
190 """
191 return []
193 def get_simulation_files(
194 self,
195 include_tests: bool = True,
196 files_include: set[Path] | None = None,
197 files_avoid: set[Path] | None = None,
198 include_vhdl_files: bool = True,
199 include_verilog_files: bool = True,
200 include_systemverilog_files: bool = True,
201 **kwargs: Any, # noqa: ANN401
202 ) -> list[HdlFile]:
203 """
204 Get a list of files that shall be included in a simulation project.
206 The ``files_include`` and ``files_avoid`` arguments can be used to filter what files are
207 included.
208 This can be useful in many situations, e.g. when encrypted files of files that include an
209 IP core shall be avoided.
210 It is recommended to overload this function in a subclass in your ``module_*.py``,
211 and call this super method with the arguments supplied.
213 Arguments:
214 include_tests: When ``False``, testbenches (``test`` files) are not included.
215 The ``sim`` files are always included.
216 files_include: Optionally filter to only include these files.
217 files_avoid: Optionally filter to discard these files.
218 include_vhdl_files: Optionally disable inclusion of files with VHDL
219 file endings.
220 include_verilog_files: Optionally disable inclusion of files with Verilog
221 file endings.
222 include_systemverilog_files: Optionally disable inclusion of files with SystemVerilog
223 file endings.
224 kwargs: Further parameters that can be sent by simulation flow to control what
225 files are included.
227 Return:
228 Files that should be included in a simulation project.
229 """
230 # Shallow copy the list since we might append to it.
231 sim_and_test_folders = self.sim_folders.copy()
233 if include_tests:
234 sim_and_test_folders += self.test_folders
236 self.create_register_simulation_files()
238 test_files = self._get_hdl_file_list(
239 folders=sim_and_test_folders,
240 files_include=files_include,
241 files_avoid=files_avoid,
242 include_vhdl_files=include_vhdl_files,
243 include_verilog_files=include_verilog_files,
244 include_systemverilog_files=include_systemverilog_files,
245 )
247 synthesis_files = self.get_synthesis_files(
248 files_include=files_include,
249 files_avoid=files_avoid,
250 include_vhdl_files=include_vhdl_files,
251 include_verilog_files=include_verilog_files,
252 include_systemverilog_files=include_systemverilog_files,
253 **kwargs,
254 )
256 return synthesis_files + test_files
258 def get_synthesis_files(
259 self,
260 files_include: set[Path] | None = None,
261 files_avoid: set[Path] | None = None,
262 include_vhdl_files: bool = True,
263 include_verilog_files: bool = True,
264 include_systemverilog_files: bool = True,
265 **kwargs: Any, # noqa: ANN401, ARG002
266 ) -> list[HdlFile]:
267 """
268 Get a list of files that shall be included in a synthesis project.
270 See :meth:`.get_simulation_files` for instructions on how to use ``files_include``
271 and ``files_avoid``.
273 Arguments:
274 files_include: Optionally filter to only include these files.
275 files_avoid: Optionally filter to discard these files.
276 include_vhdl_files: Optionally disable inclusion of files with VHDL
277 file endings.
278 include_verilog_files: Optionally disable inclusion of files with Verilog
279 file endings.
280 include_systemverilog_files: Optionally disable inclusion of files with SystemVerilog
281 file endings.
282 kwargs: Further parameters that can be sent by build flow to control what
283 files are included.
285 Return:
286 Files that should be included in a synthesis project.
287 """
288 self.create_register_synthesis_files()
290 return self._get_hdl_file_list(
291 folders=self.synthesis_folders,
292 files_include=files_include,
293 files_avoid=files_avoid,
294 include_vhdl_files=include_vhdl_files,
295 include_verilog_files=include_verilog_files,
296 include_systemverilog_files=include_systemverilog_files,
297 )
299 def get_ip_core_files(
300 self,
301 files_include: set[Path] | None = None,
302 files_avoid: set[Path] | None = None,
303 **kwargs: Any, # noqa: ANN401, ARG002
304 ) -> list[IpCoreFile]:
305 """
306 Get the IP cores of this module.
308 Note that the :class:`.ip_core_file.IpCoreFile` class accepts a ``variables`` argument that
309 can be used to parameterize IP core creation.
310 By overloading this method in a subclass, you can pass on ``kwargs`` arguments from the
311 build/simulation flow to :class:`.ip_core_file.IpCoreFile` creation to achieve
312 this parameterization.
314 Arguments:
315 files_include: Optionally filter to only include these files.
316 files_avoid: Optionally filter to discard these files.
317 kwargs: Further parameters that can be sent by build/simulation flow to control what
318 IP cores are included and what their variables are.
320 Return:
321 The IP cores for this module.
322 """
323 # Could be made into a class property, like e.g. 'synthesis_folders', if any user needs it.
324 folders = [self.path / "ip_cores"]
326 return [
327 IpCoreFile(path=path)
328 for path in self._get_file_list(
329 folders=folders,
330 file_endings=".tcl",
331 files_include=files_include,
332 files_avoid=files_avoid,
333 )
334 ]
336 def get_scoped_constraints(
337 self,
338 files_include: set[Path] | None = None,
339 files_avoid: set[Path] | None = None,
340 **kwargs: Any, # noqa: ANN401, ARG002
341 ) -> list[Constraint]:
342 """
343 Get the constraints that shall be applied to a certain entity within this module.
345 Arguments:
346 files_include: Optionally filter to only include these files.
347 files_avoid: Optionally filter to discard these files.
348 kwargs: Further parameters that can be sent by build/simulation flow to control what
349 constraints are included.
351 Return:
352 The constraints.
353 """
354 # Could be made into a class property, like e.g. 'synthesis_folders', if any user needs it.
355 folders = [
356 self.path / "scoped_constraints",
357 self.path / "entity_constraints",
358 self.path / "hdl" / "constraints",
359 ]
361 constraint_files = self._get_file_list(
362 folders=folders,
363 file_endings=(".tcl", ".xdc"),
364 files_include=files_include,
365 files_avoid=files_avoid,
366 )
368 constraints = []
369 if constraint_files:
370 synthesis_files = self.get_synthesis_files()
371 for constraint_file in constraint_files:
372 # Scoped constraints often depend on clocks having been created by another
373 # constraint file before they can work. Set processing order to "late" to make
374 # this more probable.
375 constraint = Constraint(
376 constraint_file, scoped_constraint=True, processing_order="late"
377 )
378 constraint.validate_scoped_entity(synthesis_files)
379 constraints.append(constraint)
381 return constraints
383 def get_documentation_files(
384 self,
385 files_include: set[Path] | None = None,
386 files_avoid: set[Path] | None = None,
387 include_vhdl_files: bool = True,
388 include_verilog_files: bool = True,
389 include_systemverilog_files: bool = True,
390 **kwargs: Any, # noqa: ANN401, ARG002
391 ) -> list[HdlFile]:
392 """
393 Get a list of files that shall be included in a documentation build.
395 It will return all files from the module except testbenches and any generated
396 register package.
397 Overwrite in a subclass if you want to change this behavior.
399 See :meth:`.get_simulation_files` for instructions on how to use ``files_include``
400 and ``files_avoid``.
402 Arguments:
403 files_include: Optionally filter to only include these files.
404 files_avoid: Optionally filter to discard these files.
405 include_vhdl_files: Optionally disable inclusion of files with VHDL
406 file endings.
407 include_verilog_files: Optionally disable inclusion of files with Verilog
408 file endings.
409 include_systemverilog_files: Optionally disable inclusion of files with SystemVerilog
410 file endings.
411 kwargs: Further parameters that can be sent by documentation build flow to control what
412 files are included.
414 Return:
415 Files that should be included in documentation.
416 """
417 # Do not include generated register code in the documentation.
418 files_to_avoid = set(
419 self._get_file_list(
420 folders=[self.register_synthesis_folder, self.register_simulation_folder],
421 file_endings=HdlFile.file_endings,
422 )
423 )
424 if files_avoid:
425 files_to_avoid |= files_avoid
427 return self._get_hdl_file_list(
428 folders=self.synthesis_folders + self.sim_folders,
429 files_include=files_include,
430 files_avoid=files_to_avoid,
431 include_vhdl_files=include_vhdl_files,
432 include_verilog_files=include_verilog_files,
433 include_systemverilog_files=include_systemverilog_files,
434 )
436 def registers_hook(self) -> None:
437 """
438 Will be called directly after creating this module's registers from
439 the TOML definition file in :meth:`.registers`.
440 If the TOML file does not exist this hook will still be called, but the module's registers
441 will be ``None``.
443 This is a good place if you want to add or modify some registers from Python.
444 Override this method and implement the desired behavior in a subclass.
446 .. Note::
447 This default method does nothing.
448 Shall be overridden by modules that utilize this mechanism.
449 """
451 def create_register_simulation_files(self) -> None:
452 """
453 Create the register artifacts that are needed for simulation.
454 Does not create the synthesis files, which are also technically needed for simulation.
455 So a call to :meth:`.create_register_synthesis_files` must also be done.
457 If this module does not have registers, this method does nothing.
458 """
459 if self.registers is not None:
460 if self.create_simulation_read_write_package:
461 VhdlSimulationReadWritePackageGenerator(
462 register_list=self.registers, output_folder=self.register_simulation_folder
463 ).create_if_needed()
465 if self.create_simulation_check_package:
466 VhdlSimulationCheckPackageGenerator(
467 register_list=self.registers, output_folder=self.register_simulation_folder
468 ).create_if_needed()
470 if self.create_simulation_wait_until_package:
471 VhdlSimulationWaitUntilPackageGenerator(
472 register_list=self.registers, output_folder=self.register_simulation_folder
473 ).create_if_needed()
475 def create_register_synthesis_files(self) -> None:
476 """
477 Create the register artifacts that are needed for synthesis/implementation.
479 If this module does not have registers, this method does nothing.
480 """
481 if self.registers is not None:
482 if self.create_register_package:
483 VhdlRegisterPackageGenerator(
484 register_list=self.registers, output_folder=self.register_synthesis_folder
485 ).create_if_needed()
487 if self.create_record_package:
488 VhdlRecordPackageGenerator(
489 register_list=self.registers, output_folder=self.register_synthesis_folder
490 ).create_if_needed()
492 if self.create_axi_lite_wrapper:
493 VhdlAxiLiteWrapperGenerator(
494 register_list=self.registers, output_folder=self.register_synthesis_folder
495 ).create_if_needed()
497 def pre_build(
498 self,
499 project: BuildProject, # noqa: ARG002
500 **kwargs: Any, # noqa: ANN401, ARG002
501 ) -> bool:
502 """
503 Will be called by the build flow before the EDA build tool is called.
504 A typical use case for this mechanism is to set a register constant or default value
505 based on the generics that are passed to the project.
506 Could also be used to, e.g., generate BRAM init files based on project information, etc.
508 .. Note::
509 This default method does nothing.
510 Should be overridden by modules that utilize this mechanism.
512 Arguments:
513 project: The project that is being built. Either a :class:`.VivadoProject` or a
514 :class:`.YosysNetlistBuild`.
515 kwargs: All other parameters to the build flow. Includes arguments to
516 :meth:`.VivadoProject.build`/:meth:`.YosysNetlistBuild.build` method as well as
517 other arguments set in :meth:`.VivadoProject.__init__`/
518 :meth:`.YosysNetlistBuild.__init__`.
520 Return:
521 True if everything went well.
522 """
523 return True
525 def add_vunit_config( # noqa: PLR0913, PLR0917
526 self,
527 test: Test | TestBench,
528 name: str | None = None,
529 generics: dict[str, Any] | None = None,
530 count: int = 1,
531 set_random_seed: bool | int | None = False,
532 pre_config: Callable[..., bool] | None = None,
533 post_check: Callable[..., bool] | None = None,
534 ) -> None:
535 """
536 Convenient wrapper to add VUnit test configuration(s).
537 Call from :meth:`.setup_vunit` in a child class.
539 Arguments:
540 test: VUnit test or testbench object.
541 name: Optional designated name for this config.
542 Will be used to form the name of the config together with the ``generics`` value.
543 generics: Generic values that will be applied to the testbench entity.
544 The values will also be used to form the name of the config.
545 count: Number of times to setup the same test configuration.
546 Is useful for testbenches that randomize their behavior based on the VUnit seed.
547 set_random_seed: Controls setting of the ``seed`` generic:
549 * When this argument is not assigned, or assigned ``False``, the generic will not
550 be set.
551 * When set to boolean ``True``, a random natural (non-negative integer)
552 generic value will be set.
553 * When set to an integer value, that specific value will be set for the generic.
554 This is useful to get a static test case name for waveform inspection.
556 If the generic is to be set it must exist in the testbench entity, and should have
557 VHDL type ``natural``.
559 .. deprecated:: 13.2.2
560 Use VUnit mechanism for random seed instead rather than this clunky variant.
561 pre_config: Function to be run before the test.
562 See `VUnit documentation <https://vunit.github.io/py/ui.html>`_ for details.
563 post_check: Function to be run after the test.
564 See `VUnit documentation <https://vunit.github.io/py/ui.html>`_ for details.
565 """
566 generics = {} if generics is None else generics
568 if set_random_seed != False: # noqa: E712
569 print(
570 f"DEPRECATED: {self.__class__.__name__}.add_vunit_config() argument "
571 "'set_random_seed' is deprecated and will be removed."
572 )
574 # Note that "bool" is a sub-class of "int" in python, so isinstance(set_random_seed, int)
575 # returns True if it is an integer or a bool.
576 if isinstance(set_random_seed, bool):
577 if set_random_seed:
578 # Use the maximum range for a natural in VHDL-2008
579 # Does not need to be cryptographically secure.
580 generics["seed"] = random.randint(0, 2**31 - 1) # noqa: S311
582 elif isinstance(set_random_seed, int):
583 generics["seed"] = set_random_seed
585 base_name = self.test_case_name(name=name, generics=generics)
587 for count_index in range(count):
588 if base_name == "":
589 # VUnit does not allow an empty name, which can happen if both 'name' and 'generics'
590 # to this method are 'None', but the user sets for example a 'pre_config'.
591 # Avoid this error mode by setting the count index, even if count is 1.
592 name = str(count_index)
593 elif count > 1:
594 name = f"{base_name}.{count_index}"
595 else:
596 name = base_name
598 test.add_config(
599 name=name, generics=generics, pre_config=pre_config, post_check=post_check
600 )
602 @staticmethod
603 def test_case_name(name: str | None = None, generics: dict[str, Any] | None = None) -> str:
604 """
605 Construct a string suitable for naming test cases.
606 Is typically used only internally by :meth:`.add_vunit_config`, could be a private method,
607 but there might be use cases where this is called in a child class.
609 Note that this method can return an empty string if both arguments are ``None``.
611 Arguments:
612 name: Optional base name.
613 generics: Dictionary of values that will be included in the name.
615 Return:
616 For example ``MyBaseName.GenericA_ValueA.GenericB_ValueB``.
617 """
618 test_case_name = name or ""
620 if generics:
621 generics_string = ".".join([f"{key}_{value}" for key, value in generics.items()])
623 test_case_name = f"{name}.{generics_string}" if test_case_name else generics_string
625 return test_case_name
627 def netlist_build_name(self, name: str, generics: dict[str, Any] | None = None) -> str:
628 """
629 Construct a string suitable for naming a netlist build project.
630 Call from :meth:`.get_build_projects` in a child class.
632 Arguments:
633 name: Base name.
634 generics: Dictionary of values that will be included in the name.
636 Return:
637 For example ``LibraryName.MyBaseName.GenericA_ValueA.GenericB_ValueB``.
638 """
639 return self.test_case_name(name=f"{self.library_name}.{name}", generics=generics)
641 @staticmethod
642 def _get_file_list(
643 folders: list[Path],
644 file_endings: str | tuple[str, ...],
645 files_include: set[Path] | None = None,
646 files_avoid: set[Path] | None = None,
647 ) -> list[Path]:
648 """
649 Return a list of files given a list of folders.
651 Arguments:
652 folders: The folders to search.
653 file_endings: File endings to include.
654 files_include: Optionally filter to only include these files.
655 files_avoid: Optionally filter to discard these files.
656 """
657 files = []
658 for folder in folders:
659 for path in folder.glob("*"):
660 if not path.is_file():
661 continue
663 if not path.name.lower().endswith(file_endings):
664 continue
666 if files_include is not None and path not in files_include:
667 continue
669 if files_avoid is not None and path in files_avoid:
670 continue
672 files.append(path)
674 return files
676 def _get_hdl_file_list(
677 self,
678 folders: list[Path],
679 files_include: set[Path] | None = None,
680 files_avoid: set[Path] | None = None,
681 include_vhdl_files: bool = True,
682 include_verilog_files: bool = True,
683 include_systemverilog_files: bool = True,
684 ) -> list[HdlFile]:
685 """
686 Return a list of HDL file objects.
687 """
688 return [
689 HdlFile(path=file_path)
690 for file_path in self._get_file_list(
691 folders=folders,
692 file_endings=get_hdl_file_endings(
693 include_vhdl=include_vhdl_files,
694 include_verilog=include_verilog_files,
695 include_systemverilog=include_systemverilog_files,
696 ),
697 files_include=files_include,
698 files_avoid=files_avoid,
699 )
700 ]
702 def __str__(self) -> str:
703 return f"{self.name}:{self.path}"
706def get_modules(
707 modules_folder: Path | None = None,
708 modules_folders: list[Path] | None = None,
709 names_include: set[str] | None = None,
710 names_avoid: set[str] | None = None,
711 library_name_has_lib_suffix: bool = False,
712 default_registers: list[Register] | None = None,
713) -> ModuleList:
714 """
715 Get a list of module objects (:class:`BaseModule` or subclasses thereof) based on the source
716 code folders.
718 Arguments:
719 modules_folder: The path to the folder containing modules.
720 modules_folders: Optionally, you can specify many folders with modules that will all
721 be searched.
722 names_include: If specified, only modules with these names will be included.
723 names_avoid: If specified, modules with these names will be discarded.
724 library_name_has_lib_suffix: If set, the VHDL library name will be ``<module name>_lib``,
725 otherwise it is just ``<module name>``.
726 default_registers: Default registers.
728 Return:
729 The modules created from the specified folders.
730 """
731 modules = ModuleList()
733 folders = []
734 if modules_folder is not None:
735 folders.append(modules_folder)
736 if modules_folders is not None:
737 folders += modules_folders
739 for module_folder in _iterate_module_folders(folders):
740 module_name = module_folder.name
742 if names_include is not None and module_name not in names_include:
743 continue
745 if names_avoid is not None and module_name in names_avoid:
746 continue
748 modules.append(
749 _get_module_object(
750 path=module_folder,
751 name=module_name,
752 library_name_has_lib_suffix=library_name_has_lib_suffix,
753 default_registers=default_registers,
754 )
755 )
757 return modules
760def get_module(
761 name: str,
762 modules_folder: Path | None = None,
763 modules_folders: list[Path] | None = None,
764 library_name_has_lib_suffix: bool = False,
765 default_registers: list[Register] | None = None,
766) -> BaseModule:
767 """
768 Get a single module object, for a module found in one of the specified source code folders.
769 Is a wrapper around :func:`.get_modules`.
771 Arguments:
772 name: The name of the module.
773 modules_folder: The path to the folder containing modules.
774 modules_folders: Optionally, you can specify many folders with modules that will all
775 be searched.
776 library_name_has_lib_suffix: If set, the VHDL library name will be ``<module name>_lib``,
777 otherwise it is just ``<module name>``.
778 default_registers: Default registers.
780 Return:
781 The requested module.
782 """
783 modules = get_modules(
784 modules_folder=modules_folder,
785 modules_folders=modules_folders,
786 names_include={name},
787 library_name_has_lib_suffix=library_name_has_lib_suffix,
788 default_registers=default_registers,
789 )
791 if not modules:
792 raise RuntimeError(f'Could not find module "{name}".')
794 if len(modules) > 1:
795 raise RuntimeError(f'Found multiple modules named "{name}".')
797 return modules[0]
800def _iterate_module_folders(modules_folders: list[Path]) -> Iterable[Path]:
801 for modules_folder in modules_folders:
802 for module_folder in modules_folder.glob("*"):
803 if module_folder.is_dir():
804 yield module_folder
807def _get_module_object(
808 path: Path,
809 name: str,
810 library_name_has_lib_suffix: bool,
811 default_registers: list[Register] | None,
812) -> BaseModule:
813 module_file = path / f"module_{name}.py"
814 library_name = f"{name}_lib" if library_name_has_lib_suffix else name
816 if module_file.exists():
817 # We assume that the user lets their 'Module' class inherit from 'BaseModule'.
818 module: BaseModule = load_python_module(module_file).Module(
819 path=path,
820 library_name=library_name,
821 default_registers=default_registers,
822 )
823 return module
825 return BaseModule(path=path, library_name=library_name, default_registers=default_registers)