The generic and function nodes are the basic nodes that you use to create other kind of nodes in the graph.
The generic and function nodes are the basic nodes that you use to create other kind of nodes in the graph.
There are 3 generic classes provided by the framework to be used to create new nodes :
There are 3 generic classes provided by the framework to be used to create new nodes.
To create a new kind of node, you inherit from one of those classes:
* `GenericSource`
* `GenericSource`
* `GenericNode`
* `GenericNode`
* `GenericSink`
* `GenericSink`
They are defined in `cmsisdsp.cg.scheduler`
They are defined in `cmsisdsp.cg.scheduler`.
There are 3 other classes that can be used to create new nodes from functions:
There are 3 other classes that can be used to create new nodes from functions. A function has no state and a C++ wrapper is not required. In this case, the tool is generating code for calling the function directly rather than using a C++ wrapper.
* `Unary`
* `Unary` (unary operators like `negate`, `inverse` ...)
* `Binary`
* `Binary` (binary operators like `add`, `mul` ...)
* `Dsp`
* `Dsp` (Some CMSIS-DSP function either binary or unary)
# Generic Nodes
# Generic Nodes
Any new kind of node must inherit from one of those classes. Those classes are providing the methods `addInput` and/or `addOutput` to define new IOs.
When you define a new kind of node, it must inherit from one of those classes. Those classes are providing the methods `addInput` and/or `addOutput` to define new inputs / outputs.
The method `typeName` from the parent class must be overridden.
A new kind of node is generally defined as:
A new kind of node is generally defined as:
@ -36,6 +36,20 @@ class ProcessingNode(GenericNode):
return "ProcessingNode"
return "ProcessingNode"
```
```
The method `typeName` from the parent class must be overridden and provide the name of the `C++` wrapper to be used for this node.
The object constructor is defining the inputs / outputs : number of samples and datatype.
The object constructor is also defining the name used to identity this node in the generated code (so it must be a valid C variable name).
`GenericSink` is only providing the `addInput` function.
`GenericSource` is only providing the `addOutput` function
`GenericNode` is providing both.
You can use each function as much as you want to create several inputs and / or several outputs for a node.
See the [simple](../examples/simple/README.md) example for more explanation about how to define a new node.
See the [simple](../examples/simple/README.md) example for more explanation about how to define a new node.
## Methods
## Methods
@ -46,7 +60,7 @@ The constructor of the node is using the `addInput` and/or `addOutput` to define
def addInput(self,name,theType,theLength):
def addInput(self,name,theType,theLength):
```
```
* `name` is the name of the input. It will becomes a property of the Python object so it must not conflict with existing properties. If `name` is, for instance, "i" then it can be accessed with `node.i` in the code
* `name` is the name of the input. It will becomes a property of the Python object so it must not conflict with existing properties. If `name` is, for instance, `"i"` then it can be accessed with `node.i` in the code
* `theType` is the datatype of the IO. It must inherit from `CGStaticType` (see below for more details about defining the types)
* `theType` is the datatype of the IO. It must inherit from `CGStaticType` (see below for more details about defining the types)
* `theLength` is the amount of **samples** consumed by this IO at each execution of the node
* `theLength` is the amount of **samples** consumed by this IO at each execution of the node
* `name` is the name of the input. It will becomes a property of the Python object so it must not conflict with existing properties. If `name` is, for instance, "o" then it can be accessed with `node.o` in the code
* `name` is the name of the output. It will becomes a property of the Python object so it must not conflict with existing properties. If `name` is, for instance, `"o"` then it can be accessed with `node.o` in the code
* `theType` is the datatype of the IO. It must inherit from `CGStaticType` (see below for more details about defining the types)
* `theType` is the datatype of the IO. It must inherit from `CGStaticType` (see below for more details about defining the types)
* `theLength` is the amount of **samples** produced by this IO at each execution of the node
* `theLength` is the amount of **samples** produced by this IO at each execution of the node
@ -70,9 +84,9 @@ This method defines the name of the C++ class implementing the wrapper for this
Datatypes for the IOs are inheriting from `CGStaticType`.
Datatypes for the IOs are inheriting from `CGStaticType`.
Currently there are 3 classes defined:
Currently there are 3 classes defined in the project:
* `CType` for the standard CMSIS-DSP types
* `CType` for the standard CMSIS-DSP types like `q15_t`, `float32_t` ...
* `CStructType` for a C struct
* `CStructType` for a C struct
* `PythonClassType` to create structured datatype for the Python scheduler
* `PythonClassType` to create structured datatype for the Python scheduler
* `size_in_bytes` is the size of the struct. It should take into account padding. It is used in case of buffer sharing since the datatype of the shared buffer is `int8_t`. The Python script must be able to compute the size of those buffers and needs to know the size of the structure including padding.
* `size_in_bytes` is the size of the C struct. It should take into account padding. It is used when the compute graph memory optimization is used since size of the datatype is needed.
## PythonClassType
## PythonClassType
@ -125,9 +139,17 @@ Most CMSIS-DSP functions have no state. The compute graph framework is providing
This feature is relying on the nodes:
This feature is relying on the nodes:
* `Unary`
* `Unary`
* To use an unary operator like `negate`, `inverse` ...
* `Binary`
* `Binary`
* To use a binary operator like `add`, `mul` ...
* `Dsp`
* `Dsp`
* Should detect if the CMSIS-DSP operator is unary or binary and use the datatype to compute the name of the function. In practice, only a subset of CMSIS-DSP function is supported so you should use `Unary` or `Binary` nodes
* `Constant`
* `Constant`
* Special node to be used **only** with function nodes when some arguments cannot be connected to a FIFO. For instance, with `arm_scale_f32` the scaling factor is a scalar value and a FIFO cannot be connected to this argument. The function is a binary operator but between a stream and a scalar.
All of this is explained in detail in the [simple example with CMSIS-DSP](../examples/simpledsp/README.md).
All of this is explained in detail in the [simple example with CMSIS-DSP](../examples/simpledsp/README.md).
@ -13,13 +13,14 @@ Typically this method is used as:
```python
```python
the_graph = Graph()
the_graph = Graph()
# Connect the source output to the processing node input
# Connect the source output to the processing node input and add this directed
# edge to the object the_graph
the_graph.connect(src.o,processing.i)
the_graph.connect(src.o,processing.i)
```
```
There are two optional arguments:
There are two optional arguments for the `connect` function:
* `fifoClass` : To use a different C++ class for implementing the connection between the two IOs. (it is also possible to change the FIFO class globally by setting an option on the graph. See below). Only the `FIFO` class is provided by default. Any new implementation must inherit from `FIFObase<T>`
* `fifoClass` : To use a different C++ class for implementing the connection between the two IOs. (it is also possible to change the FIFO class globally by setting an option on the graph. See below). The `FIFO` class is provided by default. Any new implementation must inherit from `FIFObase<T>`
* `fifoScale` : In asynchronous mode, it is a scaling factor to increase the length of the FIFO compared to what has been computed by the synchronous approximation. This setting can also be set globally using the scheduler options. `fifoScale` is overriding the global setting. It must be a `float` (not an `int`).
* `fifoScale` : In asynchronous mode, it is a scaling factor to increase the length of the FIFO compared to what has been computed by the synchronous approximation. This setting can also be set globally using the scheduler options. `fifoScale` is overriding the global setting. It must be a `float` (not an `int`).
```python
```python
@ -55,3 +56,7 @@ Class used for FIFO by default. Can also be customized for each connection (`con
Prefix used to generate the duplicate node classes like `Duplicate2`, `Duplicate3` ...
Prefix used to generate the duplicate node classes like `Duplicate2`, `Duplicate3` ...
Those nodes are inserted automatically to implement one-to-many connections.
If you need to connect an output to more than 3 nodes, you'll have to create the `Duplicate` nodes.