blob: 23248ea777b4fa6f80a4c0ace85e92e30524a7d0 [file] [log] [blame] [edit]
# 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
```