parallel tasks

New to XMOS and XCore? Get started here.
stdefeber
Active Member
Posts: 35
Joined: Wed Dec 18, 2013 9:20 pm

parallel tasks

Post by stdefeber »

I am new to the XMOS way of parallel processing.

I received my startKit last wednesday an fired up xTimecomposer friday evening.
The compiling/running the examples went without trouble.

Next thing I liked to do is start my own project. Parallel tasks included.
This is; let a LED blink on core 0 and another LED on core 1.
Unfortunately the xTIMEcomposer complaisn about

../src/test_app3.xc: In function `main':
../src/test_app3.xc:20: error: statement placed on a core must be call to combinable function
../src/test_app3.xc:21: error: statement placed on a core must be call to combinable function
xmake[1]: *** [.build_Debug/src/test_app3.xc.o] Error 1
xmake: *** [bin/Debug/test_app3_Debug.xe] Error 2


here is the code:

Code: Select all

#include <stdio.h>
#include <xs1.h>
#include <platform.h>
#include <print.h>
#include "task1.h"
#include "task2.h"


int main()
{

  par {
      on tile[0].core[0]: task1();
      on tile[0].core[1]: task2();
  }

  return 0;
}
and for task1

Code: Select all

#include <xs1.h>
#include <timer.h>

port p = XS1_PORT_1A;

[[combinable]]
void task1()
{

  while (1)
  {
     select 
     {
           
       p <: 0;
       delay_milliseconds(200);
       p <: 1;
       delay_milliseconds(200);
     }
  }
}
and for task2

Code: Select all

#include <xs1.h>
#include <timer.h>

port p2 = XS1_PORT_4A;

[[combinable]]
void task2()
{

  while (1)
  {
     p2 <: 0;
     delay_milliseconds(200);
     p2 <: 1;
     delay_milliseconds(200);
  }
}

Please any suggestions ?

best regards

Simon


User avatar
Bianco
XCore Expert
Posts: 754
Joined: Thu Dec 10, 2009 6:56 pm

Post by Bianco »

I'm not sure what you want.
Combinable fuctions are an advanced (and very new) feature of XC.
With combinable functions we can run multiple tasks on one logical core using cooperative scheduling. (One thing that is certainly wrong in your code is that your select statements are invalid, they should contain event statements, you cannot just put code in a select block)

To get started I would just use regular parallel threads, of which each runs on its own logical core.

An example for the startKIT that demonstrates paralellism, waiting for multiple events, intertask communication using channels, using timers explicitly and code reuse follows.
Task 1 generates the timing of the LED driven by Task 2 and vice versa.

Code: Select all

#include <platform.h>
#include <xs1.h>

#define HALF_PERIOD1 50000000
#define HALF_PERIOD2 25000000

on tile[0] : out port p1 = XS1_PORT_1A;
on tile[0] : out port p2 = XS1_PORT_1D;

void led_task(chanend c_in, chanend c_out, out port p, int delay)
{
  unsigned ctime;
  int state = 0;
  int recv;
  timer t;
  
  t :> ctime;
  
  while(1) {
    select {
      case t when timerafter(ctime + delay) :> ctime:
        if (state == 0) {
          state = 1;
          p <: 1;
        } else {
          state = 0;
          p <: 0;
        }
        break;
      case c_in :> recv:
        p <: recv;
        break;
    }
  }
}

int main(void)
{
  chan c1, c2;

  par {
      on tile[0] : led_task(c1, c2, p1, HALF_PERIOD1);
      on tile[0] : led_task(c2, c1, p2, HALF_PERIOD2);
  } 
  
  return 0;
}
User avatar
Folknology
XCore Legend
Posts: 1274
Joined: Thu Dec 10, 2009 10:20 pm

Post by Folknology »

Hi Simon and welcome.

For combinable tasks they must end in :

Code: Select all

void task () {
....
  while(1) {
    select {
      ...
    }
  }
}
I notice your second task is missing it's select hence the complaint from the compiler. Also as Bianco states there are rules on how to use selects. Selects need to contain 'cases' which are selected on events or interfaces. Take a look at the Xmos online programming references for XC/C

regards
Al
stdefeber
Active Member
Posts: 35
Joined: Wed Dec 18, 2013 9:20 pm

Post by stdefeber »

OK. Got it.
Intention is to blink 2 LEDs in separate tasks (and/or on separate cores).
I want to use the button to set blink speed for both tasks.
This is now implemented just on one.

I can not use the result of a button change in both tasks ! How come ?

This is the end result;

Code: Select all

/*
 * test_app3.xc
 *
 *  Created on: Dec 20, 2013
 *      Author: stdefeber
 */

#include <xs1.h>
#include <platform.h>
//#include <print.h>
#include "startkit_gpio.h"

port p1 = XS1_PORT_1A;
port p2 = XS1_PORT_1D;

//[[distributable]]
[[combinable]]
static void task1(client startkit_button_if button)
{
  //int delayTime;
  timer tmr;
  int timeStamp;
  int led = 0;

  int delayTime = 50 * 1000 * 1000; //Delay Time in ms

  tmr :> timeStamp;
  while (1)
  {
     select {
       case tmr when timerafter(timeStamp + delayTime) :> void:
         if (led == 0)
         {
             led = 1;
             p1 <: 1;
         }
         else
         {
             p1 <: 0;
             led = 0;
         }
         timeStamp += delayTime;
         break;

       case button.changed():
         if (button.get_value() == BUTTON_DOWN)
         {
           // If the button has been pressed down then
           // invert the pattern
           if (delayTime == 100*1000*1000)
           {
             delayTime = 50 * 1000 * 1000;
           }
           else
           {
             delayTime = 100 * 1000 * 1000;
           }
         }
         break;
     }
  }
}

[[combinable]]
static void task2()
{
  //int delayTime;
  timer tmr;
  int timeStamp;
  int led = 0;

  int delayTime = 50 * 1000 * 1000; //Delay Time in ms

  tmr :> timeStamp;
  while (1)
  {
     select
     {
       case tmr when timerafter(timeStamp + delayTime) :> void:
         if (led == 0)
         {
             led = 1;
             p2 <: 1;
         }
         else
         {
             p2 <: 0;
             led = 0;
         }
         timeStamp += delayTime;
         break;
     }
  }
}

startkit_gpio_ports gpio_ports =
  {XS1_PORT_32A, XS1_PORT_4A, XS1_PORT_4B, XS1_CLKBLK_1};

int main()
{
  startkit_button_if i_button;
  //startkit_led_if i_led;
  par {
      on tile[0].core[0]: startkit_gpio_driver(null, i_button,
                                                   null, null,
                                                   gpio_ports);

      on tile[0].core[0]: task1(i_button);
      on tile[0].core[1]: task2();
  }

  return 0;
}

User avatar
Folknology
XCore Legend
Posts: 1274
Joined: Thu Dec 10, 2009 10:20 pm

Post by Folknology »

There are a number of different approaches to your problem you could use tasks and interfaces or cores and channels, it would depend on your other requirements. If you imagine each has a deployment cost on your BOM then the highest costs is tiles followed by cores and then tasks. Thus an efficient concurrent approach for something as trivial as centrally pulsing LEDs for indication would be task based, I have knocked up a quick example which should work on StartKit, the button toggles the delay period between full and half.

Code: Select all

#include <xs1.h>

#define DELAY 10000000

interface indicator {
	void set();
	void clear();
	void toggle();
};

[[distributable]]
void led_driver(interface indicator server ind, out port led) {
	int state = 0;
	while(1) {
		select {
			case ind.set():
				led <: state = 1;
				break;
			case ind.clear():
				led <: state = 0;
				break;
			case ind.toggle():
				state = state ? 0 : 1;
				led <: state;
				break;
		}
	}
}

void indicator_controller(interface indicator client ind[n],unsigned n, in port button) {
	timer t;
	int time;
	int bstate;
	int delay = DELAY;
	int debounce = 0;

	button :> bstate;
	t :> time;

	while(1) {
		select {
			case t when timerafter(time+delay) :> time :
				for(unsigned i=0; i < n; i++) ind[i].toggle();
				debounce = 1;
				break;
			case button when pinsneq(bstate) :> bstate :
				if(debounce) {
					button when pinsneq(bstate) :> bstate;
					debounce = 0;
					delay = (delay == DELAY) ? DELAY/2 : DELAY;
				}
				break;
		}
	}
}

out port ld1 = XS1_PORT_1A;
out port ld2 = XS1_PORT_1D;
in port p32 = XS1_PORT_32A;

int main(void) {
	interface indicator ldint[2];
	par {
		indicator_controller(ldint,2,p32);
		[[distribute]] led_driver(ldint[0],ld1);
		[[distribute]] led_driver(ldint[1],ld2);
	}

  return 0;
}
Clearly there are lots of different ways of approaching such a thing..

regards
Al
User avatar
Folknology
XCore Legend
Posts: 1274
Joined: Thu Dec 10, 2009 10:20 pm

Post by Folknology »

Better still the more nuanced version is:

Code: Select all

#include <xs1.h>

#define DELAY 10000000

interface indicator {
	void set();
	void clear();
	void toggle();
};

[[distributable]]
void led_driver(interface indicator server ind[n], out port led[n], static const unsigned n) {
	int state[] = {0,0};
	while(1) {
		select {
			case ind[unsigned i].set():
				led[i] <: state[i] = 1;
				break;
			case ind[unsigned i].clear():
				led[i] <: state[i] = 0;
				break;
			case ind[unsigned i].toggle():
				state[i] = state[i] ? 0 : 1;
				led[i] <: state[i];
				break;
		}
	}
}

void indicator_controller(interface indicator client ind[n],unsigned n, in port button) {
	timer t;
	int time;
	int bstate;
	int delay = DELAY;
	int debounce = 0;

	button :> bstate;
	t :> time;

	while(1) {
		select {
			case t when timerafter(time+delay) :> time :
				for(unsigned i=0; i < n; i++) ind[i].toggle();
				debounce = 1;
				break;
			case button when pinsneq(bstate) :> bstate :
				if(debounce) {
					button when pinsneq(bstate) :> bstate;
					debounce = 0;
					delay = (delay == DELAY) ? DELAY/2 : DELAY;
				}
				break;
		}
	}
}

out port ld[] = {XS1_PORT_1A,XS1_PORT_1D};
in port p32 = XS1_PORT_32A;

int main(void) {
	interface indicator ldint[2];

	par {
		indicator_controller(ldint,2,p32);
		[[distribute]] led_driver(ldint,ld,2);
	}

  return 0;
}
For fun bonus points try to guess what happens when you change the initial status line to:

Code: Select all

int state[] = {0,1};
Or make it work with any number of 1 bit led ports:

Code: Select all

int state[n];
for(unsigned x=0; x<n; x++) state[x] = 0;
regards
Al