# Modules ## Interface This document contains all the information needed to configure modules for your _**f4pga**_ project as well as some info about the API used to write modules. ### Configuration interface: Modules are configured through an internal API by _**f4pga**_. The basic requirement for a module script is to expose a class with `Module` interface. _**f4pga**_ reads its configuration from two different sources: **platform's flow definition**, which is a file that usually comes bundled with f4pga and **project's flow configuration**, which is a set of configuration options provided by the user through a JSON file or CLI interface. Those sources contain snippets of _module configurations_. A _module configuration_ is a structure with the following fields: * `takes` - a dictionary that contains keys which are names of the dependencies used by the module. The values are paths to those dependencies. They can be either singular strings or lists of strings. * `produces` - a dictionary that contains keys which are names of the dependencies produced by the module. The values are requested filenames for the files generated by the module. They can be either singular strings or lists of strings. * `values` - a dictionary that contains other values used to configure the module. The keys are value's names and the values can have any type. ### Platform-level configuration In case of **platform's flow definition**, a `values` dictionary can be defined globally and the values defined there will be passed to every module's config. Those values can be overridden per-module through `module_options` dictionary. Parameters used during module's construction can also be defined in `module_options` as `params` (those are not a part of _module configuration_, instead they are used during the actual construction of a module instance, before it declares any of its input/outputs etc.. This is typically used to achieve some parametrization over module's I/O). Defining dictionaries for `takes` and `produces` is currently disallowed within **platform's flow definition**. For examples of **platform's flow definition** described here, please have a look at `f4pga/platforms/` directory. It contains **platform flow definitions** that come bundled with f4pga. ### Project-level configuration This section describes **project's flow configuration**. Similarly to **platform's flow definition**, `values` dict can be provided. The values provided there will overwrite the values from **platform's flow definition** in case of a collision. Unlike **platform's flow definition**, **project's flow configuration** may contain `dependencies` dict. This dictionary would be used to map symbolic dependency names to actual paths. Most dependencies can have their paths resolved implicitly without the need to provide explicit paths, which is a mechanism that is described in a later section of this document. However some dependencies must be provided explicitly, eg. paths to project's Verilog source files. It should be noted that depending on the flow definition and the dependency in question, the path does not necessarily have to point to an already existing file. If the dependency is a product of a module within the flow, the path assigned to it will be used by the module to build that dependency. This is also used to in case of _on-demand_ dependencies, which won't be produced unless the user explicitly provides a path for them. **project's flow configuration** cannot specify `params` for modules and does not use `module_options` dictionary. Neither it can instantiate any extra stages. Any entry with a couple _exceptions*_ is treated as a platform name. Enabling support for a given platform within a **project's flow configuration** file requires having an entry for that platform. Each of those entries may contain `dependencies`, `values` fields which will overload the `dependecies` and `values` defined in a global scope of **project's flow configuration**. Any other field under those platform entries is treated as a _stage-specific-configuration_. The key is a name of a stage within a flow for the specified platform and the values are dicts which may contain `dependencies` and `values` fields that overload `dependencies` and `values` respectively, locally for the stage. Additionally a `default_target` field can be provided to specify a default target to built when the user does not specify it through a CLI interface. The aforementioned _*exceptions_ are: * `dependencies` - dependencies shared by all platforms. * `values` - values shared by all platforms * `default_platform` - default platform to chose in case it doesn't get specified by the user Those apply only to flow configuration file. ### Internal environmental variables It's very useful to be able to refer to some data within **platform's flow definition** and **project's flow configuration** to either avoid redundant definitions or to store and access results of certain operations. _**f4pga**_ allows doing that by using a special syntax for accessing internal environmental variables. The syntax is `${variable_name}`. Any string value within **platform's flow definition** and **project's flow configuration** that contains such patterns will have them replaced with the values of the variables referenced if those values are strings. Eg.: With the following values defined: ```json { "a_value": "1234", "another_value": "a_value: ${a_value}" } ``` `another_value` will resolve to: ```json "a_value: 1234" ``` If the value is a list however, the result would be a list with all entries being the original string with the reference to a variable replaced by following items of the original list. Eg.: With the following values defined ```json { "list_of_values": ["a", "b", "c"], "some_string": "item: ${list_of_values}" } ``` `some_string` will resolve to ```json ["item: a", "item: b", "item: c"] ``` Be careful when using this kind of resolution, as it's computational and memory complexity grows exponentially in regards to the number of list variables being referenced, which is a rather obvious fact, but it's still worth mentioning. The variables that can be referenced within a definition/configuration fall into 3 categories: * **value references** - anything declared as a `value` can be accessed by it's name * **dependency references** - any dependency path can be referenced using the name of the dependency prefaced with a ':' prefix. Eg.: `${:eblif}` will resolve to the path of `eblif` dependency. Make sure that the dependency can be actually resolved when you are using this kind of reference. For example you can't use the a reference to `eblif` dependency in a module which does not rely on it. An exception is the producer module which can in fact reference it's own outputs but these references cannot be used during the _mapping_ stage (more on that later). * **built-in references** - there are a couple of built-in variables which are very handy: * `shareDir` - path to f4pga's _share_ directory. * `binDir` - path to f4pga's _bin_ directory. * `prjxray_db` - Project X-Ray database path. * `python3` - path to Python 3 interpreter. * `noisyWarnings` - (this one should probably get removed) ### `Module` class Each module is represented as a class derived from `Module` class. The class should implement the following methods: * `execute(self, ctx: ModuleContext)` - executes the module in _exec_ mode * `map_io(self, ctx: ModuleContext) -> 'dict[str, ]'` - executes the module in _mapping_ mode * `__init__(self, params: 'dict[str, ]')` - initializer. The `params` is a dict with optional parameter for the module. Each module script should expose the class by defining it's name/type alias as `ModuleClass`. f4pga tries to access a `ModuleClass` attribute within a package when instantiating a module. ### Module's execution modes A module has essentially two execution modes: * _mapping_ mode * _exec_ mode #### _mapping_ mode In _mapping_ mode the module is provided with an incomplete configuration which includes: * `takes` namespace: this maps names of input dependencies to the paths of these dependencies * `values` namespace: this maps names of variables to the values of those variables. The module has to provide a dictionary that will provide every output dependency that's not _on-demand_ a default path. This is basically a promise that when executed in _exec_ mode, the module will produce files for this paths. Typically such paths would be derived from a path of one of it's input dependencies. This mechanism allows the user to avoid specifying an explicit path for each intermediate target. It should be noted that variables referring to the output dependencies can't be accessed at this stage for the obvious reason as their values are yet to be evaluated. #### _exec_ mode In _exec_ mode the module does the actual work. The configuration passed into this mode is full and it includes: * `takes` namespace: this maps names of input dependencies to the paths of these dependencies * `values` namespace: this maps names of variables to the values of those variables. * `produces` namespace: this maps names of output dependencies to explicit paths. This should not be used directly really, but it's useful for `ModuleContext.is_output_explicit` method. * `outputs` namespace: this maps names of output dependencies to their paths. When the module finishes executing in _exec_ mode, all of the dependencies described in `outputs` should be present. ### Module initialization/instantiation In the `__init__` method of module's class, the following fields should be set: * `takes` - a list of symbolic dependency names for dependencies used by the module * `produces` - a list of symbolic dependencies names for dependencies produced by the module. * `values` - a list of names given to the variables used withing the module * `prod_meta` - A dictionary which maps product names to descriptions of these products. Those entries are optional and can be skipped. #### Qualifiers/decorators By default the presence of all the dependencies and values is mandatory (In case of `produces` that means that the module always has to produce the listed dependencies). This can be changed by "decorating" a name in one of the following ways: * '`?`' _suffix_ * In `takes` - the dependency is not necessary for the module to execute * In `produces` - the dependency may be produced, but it is not guaranteed. * In `values` the value is not required for the module to execute. Referring to it through `ModuleContext.values.value_name` won't raise an exception if the value is not present, instead `None` will be returned. * '`!`' _suffix_ * In `produces` - the dependency is going to be produced only if the user provides an explicit path for it. Currently it's impossible to combine both '`!`' and '`?`' together. This limitation does not have any reason behind it other than the way the qualifier system is implemented at the moment. It might be removed in the future. ## Common modules ```{toctree} fasm generic_script_wrapper io_rename mkdirs pack place place_constraints route synth ```