Started to rework the documentation for the ComputeGraph examples.
@ -1 +1,220 @@
|
||||
Options
|
||||
## Options
|
||||
|
||||
Several options can be used in the Python to control the schedule generation. Some options are used by the scheduling algorithm and other options are used by the code generators or graphviz generator:
|
||||
|
||||
### Options for the graph
|
||||
|
||||
Those options needs to be used on the graph object created with `Graph()`.
|
||||
|
||||
For instance :
|
||||
|
||||
```python
|
||||
g = Graph()
|
||||
g.defaultFIFOClass = "FIFO"
|
||||
```
|
||||
|
||||
#### defaultFIFOClass (default = "FIFO")
|
||||
|
||||
Class used for FIFO by default. Can also be customized for each connection (`connect` of `connectWithDelay` call) with something like:
|
||||
|
||||
`g.connect(src.o,b.i,fifoClass="FIFOClassNameForThisConnection")`
|
||||
|
||||
#### duplicateNodeClassName(default="Duplicate")
|
||||
|
||||
Prefix used to generate the duplicate node classes like `Duplicate2`, `Duplicate3` ...
|
||||
|
||||
### Options for the scheduling
|
||||
|
||||
Those options needs to be used on a configuration objects passed as argument of the scheduling function. For instance:
|
||||
|
||||
```python
|
||||
conf = Configuration()
|
||||
conf.debugLimit = 10
|
||||
sched = g.computeSchedule(config = conf)
|
||||
```
|
||||
|
||||
Note that the configuration object also contain options for the code generators.
|
||||
|
||||
#### memoryOptimization (default = False)
|
||||
|
||||
When the amount of data written to a FIFO and read from the FIFO is the same, the FIFO is just an array. In this case, depending on the scheduling, the memory used by different arrays may be reused if those arrays are not needed at the same time.
|
||||
|
||||
This option is enabling an analysis to optimize the memory usage by merging some buffers when it is possible.
|
||||
|
||||
#### sinkPriority (default = True)
|
||||
|
||||
Try to prioritize the scheduling of the sinks to minimize the latency between sources and sinks.
|
||||
|
||||
When this option is enabled, the tool may not be able to find a schedule in all cases. If it can't find a schedule, it will raise a `DeadLock` exception.
|
||||
|
||||
#### displayFIFOSizes (default = False)
|
||||
|
||||
During computation of the schedule, the evolution of the FIFO sizes is generated on `stdout`.
|
||||
|
||||
#### dumpSchedule (default = False)
|
||||
|
||||
During computation of the schedule, the human readable schedule is generated on `stdout`.
|
||||
|
||||
### Options for the code generator
|
||||
|
||||
#### debugLimit (default = 0)
|
||||
|
||||
When `debugLimit` is > 0, the number of iterations of the scheduling is limited to `debugLimit`. Otherwise, the scheduling is running forever or until an error has occured.
|
||||
|
||||
#### dumpFIFO (default = False)
|
||||
|
||||
When true, generate some code to dump the FIFO content at runtime. Only useful for debug.
|
||||
|
||||
In C++ code generation, it is only available when using the mode `codeArray == False`.
|
||||
|
||||
When this mode is enabled, the first line of the scheduler file is :
|
||||
|
||||
`#define DEBUGSCHED 1`
|
||||
|
||||
and it also enable some debug code in `GenericNodes.h`
|
||||
|
||||
#### schedName (default = "scheduler")
|
||||
|
||||
Name of the scheduler function used in the generated code.
|
||||
|
||||
#### prefix (default = "")
|
||||
|
||||
Prefix to add before the FIFO buffer definitions. Those buffers are not static and are global. If you want to use several schedulers in your code, the buffer names used by each should be different.
|
||||
|
||||
Another possibility would be to make the buffer static by redefining the macro `CG_BEFORE_BUFFER`
|
||||
|
||||
#### Options for C Code Generation only
|
||||
|
||||
##### cOptionalArgs (default = "")
|
||||
|
||||
Optional arguments to pass to the C API of the scheduler function
|
||||
|
||||
It can either use a `string` or a list of `string` where an element is an argument of the function (and should be valid `C`).
|
||||
|
||||
##### codeArray (default = True)
|
||||
|
||||
When true, the scheduling is defined as an array. Otherwise, a list of function calls is generated.
|
||||
|
||||
A list of function call may be easier to read but if the schedule is long, it is not good for code size. In that case, it is better to encode the schedule as an array rather than a list of functions.
|
||||
|
||||
When `codeArray` is True, the option `switchCase`can also be used.
|
||||
|
||||
##### switchCase (default = True)
|
||||
|
||||
`codeArray` must be true or this option is ignored.
|
||||
|
||||
When the schedule is encoded as an array, it can either be an array of function pointers (`switchCase` false) or an array of indexes for a state machine (`switchCase` true)
|
||||
|
||||
##### eventRecorder (default = False)
|
||||
|
||||
Enable the generation of `CMSIS EventRecorder` intrumentation in the code. The CMSIS-DSP Pack is providing definition of 3 events:
|
||||
|
||||
* Schedule iteration
|
||||
* Node execution
|
||||
* Error
|
||||
|
||||
##### customCName (default = "custom.h")
|
||||
|
||||
Name of custom header in generated C code. If you use several scheduler, you may want to use different headers for each one.
|
||||
|
||||
##### postCustomCName (default = "")
|
||||
|
||||
Name of custom header in generated C code coming after all of the other includes.
|
||||
|
||||
##### genericNodeCName (default = "GenericNodes.h")
|
||||
|
||||
Name of GenericNodes header in generated C code. If you use several scheduler, you may want to use different headers for each one.
|
||||
|
||||
##### appNodesCName (default = "AppNodes.h")
|
||||
|
||||
Name of AppNodes header in generated C code. If you use several scheduler, you may want to use different headers for each one.
|
||||
|
||||
##### schedulerCFileName (default = "scheduler")
|
||||
|
||||
Name of scheduler cpp and header in generated C code. If you use several scheduler, you may want to use different headers for each one.
|
||||
|
||||
If the option is set to `xxx`, the names generated will be `xxx.cpp` and `xxx.h`
|
||||
|
||||
##### CAPI (default = True)
|
||||
|
||||
By default, the scheduler function is callable from C. When false, it is a standard C++ API.
|
||||
|
||||
##### CMSISDSP (default = True)
|
||||
|
||||
If you don't use any of the datatypes or functions of the CMSIS-DSP, you don't need to include the `arm_math.h` in the scheduler file. This option can thus be set to `False`.
|
||||
|
||||
##### asynchronous (default = False)
|
||||
|
||||
When true, the scheduling is for a dynamic / asynchronous flow. A node may not always produce or consume the same amount of data. As consequence, a scheduling can fail. Each node needs to implement a `prepareForRunning` function to identify and recover from FIFO underflows and overflows.
|
||||
|
||||
A synchronous schedule is used as start and should describe the average case.
|
||||
|
||||
This implies `codeArray` and `switchCase`. This disables `memoryOptimizations`.
|
||||
|
||||
Synchronous FIFOs that are just buffers will be considered as FIFOs in asynchronous mode.
|
||||
|
||||
More info are available in the documentation for [this mode](Dynamic.md).
|
||||
|
||||
##### FIFOIncrease (default 0)
|
||||
|
||||
In case of dynamic / asynchronous scheduling, the FIFOs may need to be bigger than what is computed assuming a static / synchronous scheduling. This option is used to increase the FIFO size. It represents a percent increase.
|
||||
|
||||
For instance, a value of 10 means the FIFO will have their size updated from `oldSize` to `1.1 * oldSize` which is ` (1 + 10%)* oldSize`
|
||||
|
||||
If the value is a `float` instead of an `int` it will be used as is. For instance, `1.1` would increase the size by `1.1` and be equivalent to the setting `10` (for 10 percent).
|
||||
|
||||
##### asyncDefaultSkip (default True)
|
||||
|
||||
Behavior of a pure function (like CMSIS-DSP) in asynchronous mode. When `True`, the execution is skipped if the function can't be executed. If `False`, an error is raised.
|
||||
|
||||
If another error recovery is needed, the function must be packaged into a C++ class to implement a `prepareForRun` function.
|
||||
|
||||
#### Options for Python code generation only
|
||||
|
||||
##### pyOptionalArgs (default = "")
|
||||
|
||||
Optional arguments to pass to the Python version of the scheduler function
|
||||
|
||||
##### customPythonName (default = "custom")
|
||||
|
||||
Name of custom header in generated Python code. If you use several scheduler, you may want to use different headers for each one.
|
||||
|
||||
##### appNodesPythonName (default = "appnodes")
|
||||
|
||||
Name of AppNodes header in generated Python code. If you use several scheduler, you may want to use different headers for each one.
|
||||
|
||||
##### schedulerPythonFileName (default = "sched")
|
||||
|
||||
Name of scheduler file in generated Python code. If you use several scheduler, you may want to use different headers for each one.
|
||||
|
||||
If the option is set to `xxx`, the name generated will be `xxx.py`
|
||||
|
||||
### Options for the graphviz generator
|
||||
|
||||
#### horizontal (default = True)
|
||||
|
||||
Horizontal or vertical layout for the graph.
|
||||
|
||||
#### displayFIFOBuf (default = False)
|
||||
|
||||
By default, the graph is displaying the FIFO sizes. If you want to know with FIFO variable is used in the code, you can set this option to true and the graph will display the FIFO variable names.
|
||||
|
||||
### Options for connections
|
||||
|
||||
It is now possible to write something like:
|
||||
|
||||
```python
|
||||
g.connect(src.o,b.i,fifoClass="FIFOSource")
|
||||
```
|
||||
|
||||
The `fifoClass` argument allows to choose a specific FIFO class in the generated C++ or Python.
|
||||
|
||||
Only the `FIFO` class is provided by default. Any new implementation must inherit from `FIFObase<T>`
|
||||
|
||||
There is also an option to set the scaling factor when used in asynchronous mode:
|
||||
|
||||
```python
|
||||
g.connect(odd.o,debug.i,fifoScale=3.0)
|
||||
```
|
||||
|
||||
When this option is set, it will be used (instead of the global setting). This must be a float.
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
## How to build the examples
|
||||
|
||||
In folder `ComputeGraph/example/build`, type the `cmake` command:
|
||||
|
||||
```bash
|
||||
cmake -DHOST=YES \
|
||||
-DDOT="path to dot.EXE" \
|
||||
-DCMSISCORE="path to cmsis core include directory" \
|
||||
-G "Unix Makefiles" ..
|
||||
```
|
||||
|
||||
The Graphviz dot tool is requiring a recent version supporting the HTML-like labels.
|
||||
|
||||
If cmake is successful, you can type `make` to build the examples. It will also build CMSIS-DSP for the host.
|
||||
|
||||
If you don't have graphviz, the option -DDOT can be removed.
|
||||
|
||||
If for some reason it does not work, you can go into an example folder (for instance example1), and type the commands:
|
||||
|
||||
```bash
|
||||
python graph.py
|
||||
dot -Tpdf -o test.pdf test.dot
|
||||
```
|
||||
|
||||
It will generate the C++ files for the schedule and a pdf representation of the graph.
|
||||
|
||||
Note that the Python code is relying on the CMSIS-DSP PythonWrapper which is now also containing the Python scripts for the Synchronous Data Flow.
|
||||
|
||||
For `example3` which is using an input file, `cmake` should have copied the input test pattern `input_example3.txt` inside the build folder. The output file will also be generated in the build folder.
|
||||
|
||||
`example4` is like `example3` but in pure Python and using the CMSIS-DSP Python wrapper (which must already be installed before trying the example). To run a Python example, you need to go into an example folder and type:
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
`example7` is communicating with `OpenModelica`. You need to install the VHTModelica blocks from the [VHT-SystemModeling](https://github.com/ARM-software/VHT-SystemModeling) project on our GitHub
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 5.1 KiB |
@ -0,0 +1,20 @@
|
||||
import numpy as np
|
||||
from pylab import figure, clf, plot, xlabel, ylabel, xlim, ylim, title, grid, axes, show,semilogx, semilogy
|
||||
from numpy import genfromtxt
|
||||
ref_data = genfromtxt('input_example3.txt', delimiter=',')
|
||||
|
||||
figure()
|
||||
plot(ref_data)
|
||||
|
||||
output_data = genfromtxt('../build/output_example3.txt', delimiter=',')
|
||||
|
||||
plot(output_data)
|
||||
show()
|
||||
|
||||
print(ref_data.shape)
|
||||
print(output_data.shape)
|
||||
nb = output_data.shape[0] - 128
|
||||
|
||||
print("Comparison of input and output : max absolute error")
|
||||
diff = output_data[128:] - ref_data[:nb]
|
||||
print(np.max(np.abs(diff)))
|
||||
|
After Width: | Height: | Size: 74 KiB |
@ -1,20 +0,0 @@
|
||||
import numpy as np
|
||||
from cmsisdsp.cg.static.nodes.simu import *
|
||||
|
||||
a=np.zeros(10)
|
||||
f=FIFO(10,a)
|
||||
|
||||
f.dump()
|
||||
|
||||
nb = 1
|
||||
for i in range(4):
|
||||
w=f.getWriteBuffer(2)
|
||||
w[0:2]=nb*np.ones(2)
|
||||
nb = nb + 1
|
||||
f.dump()
|
||||
|
||||
print(a)
|
||||
|
||||
for i in range(4):
|
||||
w=f.getReadBuffer(2)
|
||||
print(w)
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 50 KiB |
@ -0,0 +1,25 @@
|
||||
# Example 5
|
||||
|
||||
This is a pure python example. It is computing a sequence of MFCC with an overlap of 0.5 s and it is creating an animation.
|
||||
|
||||
It can be run with:
|
||||
|
||||
`python main.py`
|
||||
|
||||
The `NumPy` sink at the end is just recording all the MFCC outputs as a list of buffers. This list is used to create an animation.
|
||||
|
||||
<img src="docassets/graph5.png" alt="graph5" style="zoom:100%;" />
|
||||
|
||||
## Expected output
|
||||
|
||||
```
|
||||
Generate graphviz and code
|
||||
Schedule length = 292
|
||||
Memory usage 6614 bytes
|
||||
```
|
||||
|
||||
And when executed you should get an animation looking like this:
|
||||
|
||||

|
||||
|
||||
The Python `main.py` contains a line which can be uncommented to record the animation as a `.mp4` video.
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 112 KiB |
@ -0,0 +1,15 @@
|
||||
# Example 6
|
||||
|
||||
This example is similar to example 5 but with C code generation instead of Python.
|
||||
|
||||

|
||||
|
||||
## Expected output
|
||||
|
||||
```
|
||||
nbMFCCOutputs = 126
|
||||
Generate graphviz and code
|
||||
Schedule length = 17
|
||||
Memory usage 2204 bytes
|
||||
```
|
||||
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
@ -0,0 +1,62 @@
|
||||
# Example 7
|
||||
|
||||
This is an example showing how a graph in in Python (not C) can interact with an [OpenModelica](https://openmodelica.org/) model.
|
||||
|
||||

|
||||
|
||||
First you need to get the project [AVH-SystemModeling](https://github.com/ARM-software/AVH-SystemModeling) from our ARM-Software repository.
|
||||
|
||||
Then, you need launch `OpenModelica` and choose `Open Model`.
|
||||
|
||||
Select `AVH-SystemModeling/VHTModelicaBlock/ARM/package.mo`
|
||||
|
||||
Then choose `Open Model` again and select `PythonTest.mo`.
|
||||
|
||||
You should see something like that in `Open Modelica`:
|
||||
|
||||

|
||||
|
||||
Customize the output path in the `Wave` node.
|
||||
|
||||
Refer to the `Open Modelica` documentation to know who to build and run this simulation. Once it is started in Modelica, launch the Python script in `example7`:
|
||||
|
||||
`python main.py`
|
||||
|
||||
You should see :
|
||||
|
||||
```
|
||||
Connecting as INPUT
|
||||
Connecting as OUTPUT
|
||||
```
|
||||
|
||||
In Modelica window, the simulation should continue to `100%`.
|
||||
|
||||
In the simulation window, you should be able to plot the output wav and get something like:
|
||||
|
||||

|
||||
|
||||
A `.wav` should have been generated so that you can listen to the result : A Larsen effect !
|
||||
|
||||
The `Processing` node in the compute graph is implemented in `custom.py` and is a gain computed with `CMSIS-DSP` Python wrapper
|
||||
|
||||
```python
|
||||
class Processing(GenericNode):
|
||||
def __init__(self,inputSize,outputSize,fifoin,fifoout):
|
||||
GenericNode.__init__(self,inputSize,outputSize,fifoin,fifoout)
|
||||
|
||||
def run(self):
|
||||
|
||||
i=self.getReadBuffer()
|
||||
o=self.getWriteBuffer()
|
||||
|
||||
b=dsp.arm_scale_q15(i,0x6000,1)
|
||||
|
||||
o[:]=b[:]
|
||||
|
||||
return(0)
|
||||
```
|
||||
|
||||
|
||||
|
||||
The gain has been chosen to create an instability.
|
||||
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 58 KiB |
@ -0,0 +1,54 @@
|
||||
# Example 8
|
||||
|
||||
This example is illustrating :
|
||||
|
||||
* The `Duplicate` node to have a one-to-many connection at an output
|
||||
* A structured datatype for the samples in the connections
|
||||
|
||||

|
||||
|
||||
## Structured datatype
|
||||
|
||||
It is possible to use a custom datatype:
|
||||
|
||||
```python
|
||||
complexType=CStructType("complex","MyComplex",8)
|
||||
```
|
||||
|
||||
This is defining a new datatype that is mapped to the type `complex` in C/C++ and the class `MyComplex` in Python. The last argument is the size in bytes of the struct in C.
|
||||
|
||||
The type complex may be defined with:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
float re;
|
||||
float im;
|
||||
} complex;
|
||||
```
|
||||
|
||||
**Note that:**
|
||||
|
||||
- The value **must have** value semantic in C/C++. So avoid classes
|
||||
- In Python, the classes have reference semantic which implies some constraints:
|
||||
- You should never modify an object from the read buffer
|
||||
- You should change the field of an object in the write buffer but not the object itself
|
||||
- If you need a new object : copy or create a new object. Never use an object from the read buffer as it is if you intend to customize it
|
||||
|
||||
The size of the C structure should take into account the padding that may be added to the struct.
|
||||
|
||||
When no buffer sharing is used, the size of buffers is always expressed in number of samples.
|
||||
|
||||
But in case of buffer sharing, the datatype of the buffer is `int8_t` and the size of the buffer must be computed by the Compute Graph taking into account ay padding that may exist.
|
||||
|
||||
## Duplicate node
|
||||
|
||||
In case of a one-to-many connections, the Python code will automatically add `Duplicate` nodes in the graph. Those `Duplicate` nodes do not appear directly in the graphviz but only as a stylized way : a dot.
|
||||
|
||||
Currently it is limited to 3. If you need more that 3 outputs on an IO you'll have to insert the `Duplicate` nodes explicitly in the graph.
|
||||
|
||||
In the generated code, you'll see the `Duplicate` nodes. For instance, in this example:
|
||||
|
||||
```C++
|
||||
Duplicate3<complex,5,complex,5,complex,5,complex,5> dup0(fifo2,fifo3,fifo4,fifo5);
|
||||
```
|
||||
|
||||
|
After Width: | Height: | Size: 24 KiB |
@ -0,0 +1,7 @@
|
||||
# Example 9
|
||||
|
||||
Thsi example is just checking that duplicate node insertion and delay on a connection are working well together.
|
||||
|
||||
The Python script is able to schedule the graph.
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 20 KiB |