Coverage for tsfpga/module.py: 99%
199 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-07 20:51 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-07 20:51 +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# --------------------------------------------------------------------------------------------------
9# Standard libraries
10import random
11from pathlib import Path
12from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, Union
14# Third party libraries
15from hdl_registers.generator.vhdl.axi_lite.wrapper import VhdlAxiLiteWrapperGenerator
16from hdl_registers.generator.vhdl.record_package import VhdlRecordPackageGenerator
17from hdl_registers.generator.vhdl.register_package import VhdlRegisterPackageGenerator
18from hdl_registers.generator.vhdl.simulation.check_package import (
19 VhdlSimulationCheckPackageGenerator,
20)
21from hdl_registers.generator.vhdl.simulation.read_write_package import (
22 VhdlSimulationReadWritePackageGenerator,
23)
24from hdl_registers.generator.vhdl.simulation.wait_until_package import (
25 VhdlSimulationWaitUntilPackageGenerator,
26)
27from hdl_registers.parser.toml import from_toml
28from hdl_registers.register import Register
29from hdl_registers.register_list import RegisterList
31# First party libraries
32from tsfpga.constraint import Constraint
33from tsfpga.hdl_file import HdlFile
34from tsfpga.ip_core_file import IpCoreFile
35from tsfpga.module_list import ModuleList
36from tsfpga.system_utils import load_python_module
38if TYPE_CHECKING:
39 # Local folder libraries
40 from .vivado.project import VivadoProject
43class BaseModule:
44 """
45 Base class for handling a HDL module with RTL code, constraints, etc.
47 Files are gathered from a lot of different sub-folders, to accommodate for projects having
48 different catalog structure.
49 """
51 # Set to False if you do not want to create these register artifacts for this module.
52 # Can be done in a child class or on an object instance.
53 # Note that artifacts will only be created if the module actually has any registers.
54 create_register_package = True
55 create_record_package = True
56 create_axi_lite_wrapper = True
57 create_simulation_read_write_package = True
58 create_simulation_check_package = True
59 create_simulation_wait_until_package = True
61 def __init__(
62 self, path: Path, library_name: str, default_registers: Optional[list[Register]] = None
63 ):
64 """
65 Arguments:
66 path: Path to the module folder.
67 library_name: VHDL library name.
68 default_registers: Default registers.
69 """
70 self.path = path.resolve()
71 self.name = path.name
72 self.library_name = library_name
74 self._default_registers = default_registers
75 self._registers: Optional[RegisterList] = None
77 @staticmethod
78 def _get_file_list(
79 folders: list[Path],
80 file_endings: Union[str, tuple[str, ...]],
81 files_include: Optional[set[Path]] = None,
82 files_avoid: Optional[set[Path]] = None,
83 ) -> list[Path]:
84 """
85 Returns a list of files given a list of folders.
87 Arguments:
88 folders: The folders to search.
89 file_endings: File endings to include.
90 files_include: Optionally filter to only include these files.
91 files_avoid: Optionally filter to discard these files.
92 """
93 files = []
94 for folder in folders:
95 for file in folder.glob("*"):
96 if not file.is_file():
97 continue
99 if not file.name.lower().endswith(file_endings):
100 continue
102 if files_include is not None and file not in files_include:
103 continue
105 if files_avoid is not None and file in files_avoid:
106 continue
108 files.append(file)
110 return files
112 def _get_hdl_file_list( # pylint: disable=too-many-arguments
113 self,
114 folders: list[Path],
115 files_include: Optional[set[Path]] = None,
116 files_avoid: Optional[set[Path]] = None,
117 include_vhdl_files: bool = True,
118 include_verilog_files: bool = True,
119 include_systemverilog_files: bool = True,
120 ) -> list[HdlFile]:
121 """
122 Return a list of HDL file objects.
123 """
124 file_endings: tuple[str, ...] = tuple()
125 if include_vhdl_files:
126 file_endings += HdlFile.file_endings_mapping[HdlFile.Type.VHDL]
127 if include_verilog_files:
128 file_endings += HdlFile.file_endings_mapping[HdlFile.Type.VERILOG_SOURCE]
129 file_endings += HdlFile.file_endings_mapping[HdlFile.Type.VERILOG_HEADER]
130 if include_systemverilog_files:
131 file_endings += HdlFile.file_endings_mapping[HdlFile.Type.SYSTEMVERILOG_SOURCE]
132 file_endings += HdlFile.file_endings_mapping[HdlFile.Type.SYSTEMVERILOG_HEADER]
134 return [
135 HdlFile(path=file_path)
136 for file_path in self._get_file_list(
137 folders=folders,
138 file_endings=file_endings,
139 files_include=files_include,
140 files_avoid=files_avoid,
141 )
142 ]
144 @property
145 def register_data_file(self) -> Path:
146 """
147 The path to this module's register data file (which may or may not exist).
148 """
149 return self.path / f"regs_{self.name}.toml"
151 @property
152 def registers(self) -> Optional[RegisterList]:
153 """
154 Get the registers for this module.
155 Will be ``None`` if the module doesn't have any registers.
156 I.e. if no TOML file exists and no hook creates registers.
157 """
158 if self._registers:
159 # Only create object from TOML once.
160 return self._registers
162 toml_file = self.register_data_file
163 if toml_file.exists():
164 self._registers = from_toml(
165 name=self.name, toml_file=toml_file, default_registers=self._default_registers
166 )
168 self.registers_hook()
169 return self._registers
171 def registers_hook(self) -> None:
172 """
173 This function will be called directly after creating this module's registers from
174 the TOML definition file.
175 If the TOML file does not exist this hook will still be called, but the module's registers
176 will be ``None``.
178 This is a good place if you want to add or modify some registers from Python.
179 Override this method and implement the desired behavior in a subclass.
181 .. Note::
182 This default method does nothing.
183 Shall be overridden by modules that utilize this mechanism.
184 """
186 def create_register_synthesis_files(self) -> None:
187 """
188 Create the register artifacts that are needed for synthesis.
189 If this module does not have registers, this method does nothing.
190 """
191 if self.registers is not None:
192 # Delete any old file that might exist so we don't have multiple and
193 # outdated definitions.
194 # This package location was used before the separate register folders were introduced,
195 # back when we only created one register artifact.
196 old_regs_pkg = self.path / f"{self.name}_regs_pkg.vhd"
197 if old_regs_pkg.exists():
198 old_regs_pkg.unlink()
200 if self.create_register_package:
201 VhdlRegisterPackageGenerator(
202 register_list=self.registers, output_folder=self.register_synthesis_folder
203 ).create_if_needed()
205 if self.create_record_package:
206 VhdlRecordPackageGenerator(
207 register_list=self.registers, output_folder=self.register_synthesis_folder
208 ).create_if_needed()
210 if self.create_axi_lite_wrapper:
211 VhdlAxiLiteWrapperGenerator(
212 register_list=self.registers, output_folder=self.register_synthesis_folder
213 ).create_if_needed()
215 def create_register_simulation_files(self) -> None:
216 """
217 Create the register artifacts that are needed for simulation.
218 Does not create the implementation files, which are also technically needed for simulation.
219 So a call to :meth:`.create_register_synthesis_files` must also be done.
221 If this module does not have registers, this method does nothing.
222 """
223 if self.registers is not None:
224 if self.create_simulation_read_write_package:
225 VhdlSimulationReadWritePackageGenerator(
226 register_list=self.registers, output_folder=self.register_simulation_folder
227 ).create_if_needed()
229 if self.create_simulation_check_package:
230 VhdlSimulationCheckPackageGenerator(
231 register_list=self.registers, output_folder=self.register_simulation_folder
232 ).create_if_needed()
234 if self.create_simulation_wait_until_package:
235 VhdlSimulationWaitUntilPackageGenerator(
236 register_list=self.registers, output_folder=self.register_simulation_folder
237 ).create_if_needed()
239 @property
240 def synthesis_folders(self) -> list[Path]:
241 """
242 Synthesis/implementation source code files will be gathered from these folders.
243 """
244 return [
245 self.path,
246 self.path / "src",
247 self.path / "rtl",
248 self.path / "hdl" / "rtl",
249 self.path / "hdl" / "package",
250 self.register_synthesis_folder,
251 ]
253 @property
254 def register_synthesis_folder(self) -> Path:
255 """
256 Generated register artifacts that are needed for synthesis/implementation will be
257 placed in this folder.
258 """
259 return self.path / "regs_src"
261 @property
262 def sim_folders(self) -> list[Path]:
263 """
264 Files with simulation models (the ``sim`` folder) will be gathered from these folders.
265 """
266 return [
267 self.path / "sim",
268 self.register_simulation_folder,
269 ]
271 @property
272 def register_simulation_folder(self) -> Path:
273 """
274 Generated register artifacts that are needed for simulation will be placed in this folder.
275 """
276 return self.path / "regs_sim"
278 @property
279 def test_folders(self) -> list[Path]:
280 """
281 Testbench files will be gathered from these folders.
282 """
283 return [
284 self.path / "test",
285 self.path / "rtl" / "tb",
286 ]
288 def get_synthesis_files( # pylint: disable=unused-argument
289 self,
290 files_include: Optional[set[Path]] = None,
291 files_avoid: Optional[set[Path]] = None,
292 include_vhdl_files: bool = True,
293 include_verilog_files: bool = True,
294 include_systemverilog_files: bool = True,
295 **kwargs: Any,
296 ) -> list[HdlFile]:
297 """
298 Get a list of files that shall be included in a synthesis project.
300 The ``files_include`` and ``files_avoid`` arguments can be used to filter what files are
301 included.
302 This can be useful in many situations, e.g. when encrypted files of files that include an
303 IP core shall be avoided.
304 It is recommended to overload this function in a subclass in your ``module_*.py``,
305 and call this super method with the arguments supplied.
307 Arguments:
308 files_include: Optionally filter to only include these files.
309 files_avoid: Optionally filter to discard these files.
310 include_vhdl_files: Optionally disable inclusion of files with VHDL
311 file endings.
312 include_verilog_files: Optionally disable inclusion of files with Verilog
313 file endings.
314 include_systemverilog_files: Optionally disable inclusion of files with SystemVerilog
315 file endings.
316 kwargs: Further parameters that can be sent by build flow to control what
317 files are included.
319 Return:
320 Files that should be included in a synthesis project.
321 """
322 self.create_register_synthesis_files()
324 return self._get_hdl_file_list(
325 folders=self.synthesis_folders,
326 files_include=files_include,
327 files_avoid=files_avoid,
328 include_vhdl_files=include_vhdl_files,
329 include_verilog_files=include_verilog_files,
330 include_systemverilog_files=include_systemverilog_files,
331 )
333 def get_simulation_files( # pylint: disable=too-many-arguments
334 self,
335 include_tests: bool = True,
336 files_include: Optional[set[Path]] = None,
337 files_avoid: Optional[set[Path]] = None,
338 include_vhdl_files: bool = True,
339 include_verilog_files: bool = True,
340 include_systemverilog_files: bool = True,
341 **kwargs: Any,
342 ) -> list[HdlFile]:
343 """
344 See :meth:`.get_synthesis_files` for instructions on how to use ``files_include``
345 and ``files_avoid``.
347 Arguments:
348 include_tests: When ``False``, the ``test`` files are not included
349 (the ``sim`` files are always included).
350 files_include: Optionally filter to only include these files.
351 files_avoid: Optionally filter to discard these files.
352 include_vhdl_files: Optionally disable inclusion of files with VHDL
353 file endings.
354 include_verilog_files: Optionally disable inclusion of files with Verilog
355 file endings.
356 include_systemverilog_files: Optionally disable inclusion of files with SystemVerilog
357 file endings.
358 kwargs: Further parameters that can be sent by simulation flow to control what
359 files are included.
361 Return:
362 Files that should be included in a simulation project.
363 """
364 # Shallow copy the list since we might append to it.
365 sim_and_test_folders = self.sim_folders.copy()
367 if include_tests:
368 sim_and_test_folders += self.test_folders
370 self.create_register_simulation_files()
372 test_files = self._get_hdl_file_list(
373 folders=sim_and_test_folders,
374 files_include=files_include,
375 files_avoid=files_avoid,
376 include_vhdl_files=include_vhdl_files,
377 include_verilog_files=include_verilog_files,
378 include_systemverilog_files=include_systemverilog_files,
379 )
381 synthesis_files = self.get_synthesis_files(
382 files_include=files_include,
383 files_avoid=files_avoid,
384 include_vhdl_files=include_vhdl_files,
385 include_verilog_files=include_verilog_files,
386 include_systemverilog_files=include_systemverilog_files,
387 **kwargs,
388 )
390 return synthesis_files + test_files
392 def get_documentation_files( # pylint: disable=unused-argument
393 self,
394 files_include: Optional[set[Path]] = None,
395 files_avoid: Optional[set[Path]] = None,
396 include_vhdl_files: bool = True,
397 include_verilog_files: bool = True,
398 include_systemverilog_files: bool = True,
399 **kwargs: Any,
400 ) -> list[HdlFile]:
401 """
402 Get a list of files that shall be included in a documentation build.
404 It will return all files from the module except testbenches and any generated
405 register package.
406 Overwrite in a subclass if you want to change this behavior.
408 Arguments:
409 files_include: Optionally filter to only include these files.
410 files_avoid: Optionally filter to discard these files.
411 include_vhdl_files: Optionally disable inclusion of files with VHDL
412 file endings.
413 include_verilog_files: Optionally disable inclusion of files with Verilog
414 file endings.
415 include_systemverilog_files: Optionally disable inclusion of files with SystemVerilog
416 file endings.
418 Return:
419 Files that should be included in documentation.
420 """
421 # Do not include generated register code in the documentation.
422 files_to_avoid = set(
423 self._get_file_list(
424 folders=[self.register_synthesis_folder, self.register_simulation_folder],
425 file_endings="vhd",
426 )
427 )
428 if files_avoid:
429 files_to_avoid |= files_avoid
431 return self._get_hdl_file_list(
432 folders=self.synthesis_folders + self.sim_folders,
433 files_include=files_include,
434 files_avoid=files_to_avoid,
435 include_vhdl_files=include_vhdl_files,
436 include_verilog_files=include_verilog_files,
437 include_systemverilog_files=include_systemverilog_files,
438 )
440 # pylint: disable=unused-argument
441 def get_ip_core_files(
442 self,
443 files_include: Optional[set[Path]] = None,
444 files_avoid: Optional[set[Path]] = None,
445 **kwargs: Any,
446 ) -> list[IpCoreFile]:
447 """
448 Get IP cores for this module.
450 Note that the :class:`.ip_core_file.IpCoreFile` class accepts a ``variables`` argument that
451 can be used to parameterize IP core creation. By overloading this method in a subclass
452 you can pass on ``kwargs`` arguments from the build/simulation flow to
453 :class:`.ip_core_file.IpCoreFile` creation to achieve this parameterization.
455 Arguments:
456 files_include: Optionally filter to only include these files.
457 files_avoid: Optionally filter to discard these files.
458 kwargs: Further parameters that can be sent by build/simulation flow to control what
459 IP cores are included and what their variables are.
461 Return:
462 The IP cores for this module.
463 """
464 folders = [
465 self.path / "ip_cores",
466 ]
467 file_endings = "tcl"
468 return [
469 IpCoreFile(ip_core_file)
470 for ip_core_file in self._get_file_list(
471 folders=folders,
472 file_endings=file_endings,
473 files_include=files_include,
474 files_avoid=files_avoid,
475 )
476 ]
478 # pylint: disable=unused-argument
479 def get_scoped_constraints(
480 self,
481 files_include: Optional[set[Path]] = None,
482 files_avoid: Optional[set[Path]] = None,
483 **kwargs: Any,
484 ) -> list[Constraint]:
485 """
486 Constraints that shall be applied to a certain entity within this module.
488 Arguments:
489 files_include: Optionally filter to only include these files.
490 files_avoid: Optionally filter to discard these files.
491 kwargs: Further parameters that can be sent by build/simulation flow to control what
492 constraints are included.
494 Return:
495 The constraints.
496 """
497 folders = [
498 self.path / "scoped_constraints",
499 self.path / "entity_constraints",
500 self.path / "hdl" / "constraints",
501 ]
502 file_endings = ("tcl", "xdc")
503 constraint_files = self._get_file_list(
504 folders=folders,
505 file_endings=file_endings,
506 files_include=files_include,
507 files_avoid=files_avoid,
508 )
510 constraints = []
511 if constraint_files:
512 synthesis_files = self.get_synthesis_files()
513 for constraint_file in constraint_files:
514 # Scoped constraints often depend on clocks having been created by another
515 # constraint file before they can work. Set processing order to "late" to make
516 # this more probable.
517 constraint = Constraint(
518 constraint_file, scoped_constraint=True, processing_order="late"
519 )
520 constraint.validate_scoped_entity(synthesis_files)
521 constraints.append(constraint)
523 return constraints
525 def setup_vunit(self, vunit_proj: Any, **kwargs: Any) -> None:
526 """
527 Setup local configuration of this module's test benches.
529 .. Note::
530 This default method does nothing. Should be overridden by modules that have
531 any test benches that operate via generics.
533 Arguments:
534 vunit_proj: The VUnit project that is used to run simulation.
535 kwargs: Use this to pass an arbitrary list of arguments from your ``simulate.py``
536 to the module where you set up your tests. This could be, e.g., data dimensions,
537 location of test files, etc.
538 """
540 def pre_build(
541 self, project: "VivadoProject", **kwargs: Any
542 ) -> bool: # pylint: disable=unused-argument
543 """
544 This method hook will be called before an FPGA build is run. A typical use case for this
545 mechanism is to set a register constant or default value based on the generics that
546 are passed to the project. Could also be used to, e.g., generate BRAM init files
547 based on project information, etc.
549 .. Note::
550 This default method does nothing. Should be overridden by modules that
551 utilize this mechanism.
553 Arguments:
554 project: The project that is being built.
555 kwargs: All other parameters to the build flow. Includes arguments to
556 :meth:`.VivadoProject.build` method as well as other arguments set in
557 :meth:`.VivadoProject.__init__`.
559 Return:
560 True if everything went well.
561 """
562 return True
564 def get_build_projects(self) -> list["VivadoProject"]:
565 """
566 Get FPGA build projects defined by this module.
568 .. Note::
569 This default method does nothing. Should be overridden by modules that set up
570 build projects.
572 Return:
573 FPGA build projects.
574 """
575 return []
577 @staticmethod
578 def test_case_name(
579 name: Optional[str] = None, generics: Optional[dict[str, Any]] = None
580 ) -> str:
581 """
582 Construct a string suitable for naming test cases.
584 Arguments:
585 name: Optional base name.
586 generics: Dictionary of values that will be included in the name.
588 Return:
589 For example ``MyBaseName.GenericA_ValueA.GenericB_ValueB``.
590 """
591 if name:
592 test_case_name = name
593 else:
594 test_case_name = ""
596 if generics:
597 generics_string = ".".join([f"{key}_{value}" for key, value in generics.items()])
599 if test_case_name:
600 test_case_name = f"{name}.{generics_string}"
601 else:
602 test_case_name = generics_string
604 return test_case_name
606 def add_vunit_config( # pylint: disable=too-many-arguments
607 self,
608 test: Any,
609 name: Optional[str] = None,
610 generics: Optional[dict[str, Any]] = None,
611 set_random_seed: Optional[Union[bool, int]] = False,
612 pre_config: Optional[Callable[..., bool]] = None,
613 post_check: Optional[Callable[..., bool]] = None,
614 ) -> None:
615 """
616 Add config for VUnit test case.
617 Wrapper that sets a suitable name and can set a random seed generic.
619 Arguments:
620 test: VUnit test object. Can be testbench or test case.
621 name: Optional designated name for this config. Will be used to form the name of
622 the config together with the ``generics`` value.
623 generics: Generic values that will be applied to the testbench entity. The values
624 will also be used to form the name of the config.
625 set_random_seed: Controls setting of the ``seed`` generic:
627 * When this argument is not assigned, or assigned ``False``, the generic will not
628 be set.
629 * When set to boolean ``True``, a random natural (non-negative integer)
630 generic value will be set.
631 * When set to an integer value, that value will be set for the generic.
632 This is useful to get a static test case name for waveform inspection.
634 If the generic is to be set it must exist in the testbench entity, and should have
635 VHDL type ``natural``.
636 pre_config: Function to be run before the test.
637 See `VUnit documentation <https://vunit.github.io/py/ui.html>`_ for details.
638 post_check: Function to be run after the test.
639 See `VUnit documentation <https://vunit.github.io/py/ui.html>`_ for details.
640 """
641 generics = {} if generics is None else generics
643 # Note that "bool" is a sub-class of "int" in python, so isinstance(set_random_seed, int)
644 # returns True if it is an integer or a bool.
645 if isinstance(set_random_seed, bool):
646 if set_random_seed:
647 # Use the maximum range for a natural in VHDL-2008
648 generics["seed"] = random.randint(0, 2**31 - 1)
650 elif isinstance(set_random_seed, int):
651 generics["seed"] = set_random_seed
653 name = self.test_case_name(name=name, generics=generics)
654 # VUnit does not allow an empty name, which can happen if both 'name' and 'generics' to
655 # this method are None, but the user sets for example a 'pre_config'.
656 # Avoid this error mode by setting a default name when it happens.
657 name = "test" if name == "" else name
659 test.add_config(name=name, generics=generics, pre_config=pre_config, post_check=post_check)
661 def __str__(self) -> str:
662 return f"{self.name}:{self.path}"
665def get_modules(
666 modules_folder: Optional[Path] = None,
667 modules_folders: Optional[list[Path]] = None,
668 names_include: Optional[set[str]] = None,
669 names_avoid: Optional[set[str]] = None,
670 library_name_has_lib_suffix: bool = False,
671 default_registers: Optional[list[Register]] = None,
672) -> ModuleList:
673 """
674 Get a list of module objects (:class:`BaseModule` or subclasses thereof) based on the source
675 code folders.
677 Arguments:
678 modules_folder: The path to the folder containing modules.
679 modules_folders: Optionally, you can specify many folders with modules that will all
680 be searched.
681 names_include: If specified, only modules with these names will be included.
682 names_avoid: If specified, modules with these names will be discarded.
683 library_name_has_lib_suffix: If set, the library name will be ``<module name>_lib``,
684 otherwise it is just ``<module name>``.
685 default_registers: Default registers.
687 Return:
688 The modules created from the specified folders.
689 """
690 modules = ModuleList()
692 folders = []
693 if modules_folder is not None:
694 folders.append(modules_folder)
695 if modules_folders is not None:
696 folders += modules_folders
698 for module_folder in _iterate_module_folders(folders):
699 module_name = module_folder.name
701 if names_include is not None and module_name not in names_include:
702 continue
704 if names_avoid is not None and module_name in names_avoid:
705 continue
707 modules.append(
708 _get_module_object(
709 path=module_folder,
710 name=module_name,
711 library_name_has_lib_suffix=library_name_has_lib_suffix,
712 default_registers=default_registers,
713 )
714 )
716 return modules
719def get_module(
720 name: str,
721 modules_folder: Optional[Path] = None,
722 modules_folders: Optional[list[Path]] = None,
723 library_name_has_lib_suffix: bool = False,
724 default_registers: Optional[list[Register]] = None,
725) -> BaseModule:
726 """
727 Get a single module object, for a module found in one of the specified source code folders.
728 Is a wrapper around :func:`.get_modules`.
730 Arguments:
731 name: The name of the module.
732 modules_folder: The path to the folder containing modules.
733 modules_folders: Optionally, you can specify many folders with modules that will all
734 be searched.
735 library_name_has_lib_suffix: If set, the library name will be ``<module name>_lib``,
736 otherwise it is just ``<module name>``.
737 default_registers: Default registers.
739 Return:
740 The requested module.
741 """
742 modules = get_modules(
743 modules_folder=modules_folder,
744 modules_folders=modules_folders,
745 names_include={name},
746 library_name_has_lib_suffix=library_name_has_lib_suffix,
747 default_registers=default_registers,
748 )
750 if not modules:
751 raise RuntimeError(f'Could not find module "{name}".')
753 if len(modules) > 1:
754 raise RuntimeError(f'Found multiple modules named "{name}".')
756 return modules[0]
759def _iterate_module_folders(modules_folders: list[Path]) -> Iterable[Path]:
760 for modules_folder in modules_folders:
761 for module_folder in modules_folder.glob("*"):
762 if module_folder.is_dir():
763 yield module_folder
766def _get_module_object(
767 path: Path,
768 name: str,
769 library_name_has_lib_suffix: bool,
770 default_registers: Optional[list["Register"]],
771) -> BaseModule:
772 module_file = path / f"module_{name}.py"
773 library_name = f"{name}_lib" if library_name_has_lib_suffix else name
775 if module_file.exists():
776 # We assume that the user lets their 'Module' class inherit from 'BaseModule'.
777 module: BaseModule = load_python_module(module_file).Module(
778 path=path,
779 library_name=library_name,
780 default_registers=default_registers,
781 )
782 return module
784 return BaseModule(path=path, library_name=library_name, default_registers=default_registers)