Skip to content

OpenCLWorkerFFTTutorial

mitchell-conrad edited this page Jan 19, 2021 · 1 revision

Table of Contents

FFT Tutorial (OpenCL_Worker)

The purpose of this tutorial is to provide an overview of the current capabilities of the OpenCLWorker, as well as provide examples for how users should use it within their models. This guide assumes basic proficiency MEDEA modelling, including the ability to generate interfaces containing Components, Aggregates and Vectors, along with the ability to specify ControlFlow and DataFlow links within component behaviours. It is also assumed that the OpenCL runtime is available on any nodes that the user wishes to assign OpenCLWorker-dependent components to, which for most modern GPUs should be as simple as ensuring that the machine has up-to-date drivers (Installing OpenCL GPU Drivers).

The contents of this guide are as follows:

  1. Create a simple FFT_Processor component that will run the FFT operation of the OpenCL Worker when stimulated by an incoming message containing data, then print a message containing the results of the FFT.
  2. Demonstrate how to generate data that can be passed into the FFT by creating a new Sensor component to stimulate the FFT_Processor.
  3. Extend the FFT_Processor component to perform a brief analysis of the data produced by the FFT operation and display the results.

Creating a OpenCL Worker Function

Create SensorSample Aggregate

  1. Inside the Interfaces aspect, create an Aggregate called SensorSample
  2. Inside SensorSample, create a Member called SensorID of type Integer with key set to true
  3. Inside SensorSample, create a Vector called SampleVector
  4. Inside SampleVector, create a Member called SampleValue of type Float

Create FFT_Processor Definition

  1. Inside the Interfaces aspect, create a Component called FFT_Processor
  2. Inside FFT_Processor, create a SubscriberPort using Aggregate SensorSample, then name it FFT_Sample_Port
  3. Inside FFT_Processor, create an Attribute called FFT_Length of type Integer

Setup FFT_Processor Impl

  1. Inside the Behaviour aspect, select the ComponentImpl FFT_Processor
  2. Inside FFT_Processor, create a ClassInstance of OpenCL_Worker
  3. Inside FFT_Sample_Port, create a FunctionCall of FFT
  4. Connect the SampleVector inside SensorSample inside FFT_Sample_Port to Data inside FFT 's Input Parameters

Feeding in Data

Before we can make use of our new FFT_Processor, we will need to create a Component that will provide it with data to process. In this example we will generate a Vector of samples that represent a signal with a frequency of 13 Hz sampled over a period of one second. To do this we need to set the value of the ith sample accordingly: samples[i] = cos(freq * i * 2*pi / fftLength), where freq would be 13 and fftLength would be 32.

In order to set the value of each sample we will have to iterate through each element of the vector, so we will now create the body of the while loop.

Create Sensor Definition

  1. Inside the Interfaces aspect, create a Component called Sensor
  2. Inside Sensor, create an PublisherPort using Aggregate SensorSample, then name it Sensor_Sample_Port
  3. Inside Sensor, create an Attribute called FFT_Length of type Integer
  4. Inside Sensor, create an Attribute called Sensor_ID of type Integer

Create Sensor Implementation

  1. Inside the Behaviour Aspect, select the Sensor ComponentImpl and create a PeriodicPort
  2. Inside the PeriodicPort, create a Variable called NewSample (Which will hold the samples we are generating before they are transmitting)
  3. Inside NewSample, create a Vector called NewSampleVector
  4. Inside NewSampleVector, create a Member called NewSampleValue and set the type to Float
  5. Inside Sensor, create a ClassInstance of Vector_Operations
  6. Inside the PeriodicPort, create a FunctionCall of type Resize
  7. Connect NewSampleVector to the Vector InputParameter within the Resize FunctionCall
  8. Connect the FFT_Length Attribute to the Size InputParameter within the Resize FunctionCall
  9. Set the DefaultValue InputParameter within the Resize FunctionCall to 0
  10. Inside the PeriodicPort, create a ForLoop
  11. Inside the ForLoop, connect VariableParameter i to the lhs inside the BooleanExpression
  12. Connect the matching rhs inside the BooleanExpression to the Sensor 's FFT_Length AttributeImpl
  13. Inside the PeriodicPort, create a PublisherPortImpl of Sensor_Sample_Port
  14. Connect the Sensor_ID AttributeImpl to the SensorID Member of the SensorSample Aggregate within the Sensor_Sample_Port
  15. Connect the NewSample Variable within the PeriodicPort to the SampleVector Member of the SensorSample Aggregate within the Sensor_Sample_Port
  16. Inside the Sensor, create a ClassInstance of Utility_Worker
  17. Inside the ForLoop, create a FunctionCall from Utility_Worker of type Evaluate Complexity
  18. Inside EvaluateComplexity 's Input Parameters, create two Optional Parameter
  19. Set the value of Complexity Function Input Parameter inside Evaluate Complexity to "cos(13 * i * (2*pi) / L)" (according to the equation mentioned at the start of the section)
  20. Connect the i VariableParameter inside the ForLoop 's BooleanExpression to the first Optional Parameter inside EvaluateComplexity
  21. Connect the FFT_Length AttributeImpl to the second Optional Parameter inside EvaluateComplexity
  22. Inside the ForLoop, create a FunctionCall from Vector_Operations of type Set
  23. Connect the NewSample Variable to the Input Parameter Vector inside Set
  24. Connect the i Optional Parameter inside the ForLoop 's BooleanExpression to the Input Parameter Index within Set
  25. Connect the Return Parameter Operations inside Evaluate Complexity to Input Parameter Value inside Set
    • The Sensor will now produce a message containing our 13 Hz samples vector each second!

Processing the Result

Now that we have valid data to process, we can turn our attention back to the FFT_Processor and begin to analyse the result of the FFT operation. While the data passed in was a simple array of float values, the result treats each pair of floats as the real and imaginary components of the complex number associated with each frequency, with our array of length 32 illustrated below:

                     Array Index
             |------|
 freq = 0Hz  | real |      0
             |  im  |      1
             |------|
 freq = 1Hz  | real |      2
             |  im  |      3
             |------|
 freq = 2Hz  | real |      4
             |  im  |      5
             |------|
    ...        ...         ...
             |------|
 freq = 13Hz | real |      26
             |  im  |      27
             |------|
 freq = 14Hz | real |      28
             |  im  |      29
             |------|
 freq = 15Hz | real |      30
             |  im  |      31
             |------|

For the purposes of this analysis we will simply calculate the magnitude for each frequency and print it, which should display a spike at frequency 13 that matches with the frequency generated by our Sensor Component.

  1. Inside FFT_Processor 's FFT_Sample_Port, create a ForLoop
  2. Inside the ForLoop, connect VariableParameter i to the lhs inside the BooleanExpression
  3. Connect the matching rhs inside the BooleanExpression to the Sensor 's FFT_Length AttributeImpl
  4. There is no need to change the values of the iteration Setter within the ForLoop, leave it at i += 1
  5. Inside Sensor, create a ClassInstance of Vector_Operations
  6. Inside the ForLoop, create a FunctionCall from Vector_Operations of type Get
  7. Connect SampleVector inside FFT_Sample_Port 's SensorSample to Input Parameter Vector inside Get
  8. Connect the i VariableParameter within the ForLoop to Input Parameter Index inside Get
  9. Inside the ForLoop, create a Setter and change the InputParameter operator to +=
  10. Connect the i VariableParameter within the ForLoop to the lhs within the Setter
  11. Set the value of the rhs within the Setter to 1
  12. Inside the ForLoop, create another FunctionCall from Vector_Operations of type Get
  13. Connect SampleVector inside FFT_Sample_Port 's SensorSample to Input Parameter Vector inside the new Get
  14. Connect the i VariableParameter within the ForLoop to Input Parameter Index inside the second Get
  15. Inside FFT_Processor, create a ClassInstance of Utility_Worker
  16. Inside the ForLoop, create a FunctionCall from Utility_Worker of type Evaluate Complexity
  17. Inside Evaluate Complexity 's Input Parameters, create two Optional Parameters
  18. Set the value of the Complexity Function Input Parameter inside Evaluate Complexity to "sqrt(r^2 + i^2)"
  19. Connect the Return Parameters' Value within the first Get to the first Optional Parameter within Evaluate Complexity 's Input Parameters
  20. Connect the Return Parameters' Value within the second Get to the second Optional Parameter within Evaluate Complexity 's Input Parameters
  21. Inside the ForLoop, create another FunctionCall from Utility_Worker of type Evaluate Complexity
  22. Inside this new Evaluate Complexity 's Input Parameters, create an Optional Parameter
  23. Set the value of the Complexity Function Input Parameter inside the second Evaluate Complexity to "(n-1)/2"
  24. Connect the i VariableParameter within the ForLoop to the Optional Parameter within the second Evaluate Complexity 's Input Parameters
  25. Inside the ForLoop, create a FunctionCall from Utility_Worker of type Log
  26. Set the value of the Message Format Input Parameter inside Log to "%f Hz frequency: %f \r\n"
  27. Inside Log 's Input Parameters, create two Optional Parameters
  28. Connect the Return Parameter Operations within the second Evaluate Complexity to the first Optional Parameter within Log 's Input Parameters
  29. Connect the Return Parameter Operations within the first Evaluate Complexity to the second Optional Parameter within Log 's Input Parameters
    • The FFT_Processor will now print out the magnitudes of the FFT result when run

Executing the Tutorial Model

  1. Inside Assemblies aspect, create a ComponentAssembly called FFT_Assembly
  2. Inside FFT_Assembly, create a ComponentInstance of Sensor called sensor1
  3. Inside Sensor set the value of Attribute Sensor_ID to 1
  4. Inside Sensor set the value of Attribute FFT_Length to 32
  5. Inside FFT_Assembly, create a ComponentInstance of FFT_Processor called processor
  6. Inside processor set the value of Attribute FFT_Length to 32
  7. Connect PublisherPortInstance Sensor_Sample_Port to the SubscriberPortInstance FFT_Sample_Port
  8. Deploy FFT_Assembly to a HardwareNode that has an OpenCL device
    • If the HardwareNode has no contents (AMD, NVIDIA, Intel), then it does not support OpenCL

Expected Output

Download for completed tutorial: Tutorial.graphml

The resulting output after initialisation should take the form:

0.000000 Hz frequency: 0.000000 1.000000 Hz frequency: 0.000000 2.000000 Hz frequency: 0.000000 3.000000 Hz frequency: 0.000000 4.000000 Hz frequency: 0.000000 5.000000 Hz frequency: 0.000000 6.000000 Hz frequency: 0.000000 7.000000 Hz frequency: 0.000000 8.000000 Hz frequency: 0.000000 9.000000 Hz frequency: 0.000000 10.000000 Hz frequency: 0.000000 11.000000 Hz frequency: 0.000000 12.000000 Hz frequency: 0.000000 13.000000 Hz frequency: 16.000000 14.000000 Hz frequency: 0.000000 15.000000 Hz frequency: 0.000000

Clone this wiki locally