Ways to share memory

Technical questions regarding the XTC tools and programming with XMOS.
yzoer
XCore Addict
Posts: 133
Joined: Tue Dec 15, 2009 10:23 pm

Post by yzoer »

Hi All,

Is there an easier way to just have READ access only while another task writes to that memory? I used assembler to get around it last time but I'm curious if there's a way where you won't get complaints from the compiler..

Thanks!

Yvo


User avatar
infiniteimprobability
XCore Legend
Posts: 1126
Joined: Thu May 27, 2010 10:08 am

Post by infiniteimprobability »

Is there an easier way to just have READ access only while another task writes to that memory?
I don't think so - the compiler just sees parallel usage violation regardless of direction.

However in the architecture memory accesses are interleaved so it's not an illegal thing to do at the ISA level, and can be quite handy (eg. a software lock)

This example http://www.xcore.com/forum/viewtopic.php?p=18541#p18541 inlines ASM to allow you to read (and write) from a different task. It's probably the closest to what you want..
yzoer
XCore Addict
Posts: 133
Joined: Tue Dec 15, 2009 10:23 pm

Post by yzoer »

Thanks for the info.. So perhaps somewhat off topic but I vaguely remember there being a project that was entirely written in assembler. Anyone remember which one that was?

Yvo
User avatar
Thomas
Experienced Member
Posts: 66
Joined: Fri Feb 05, 2010 12:34 pm

Post by Thomas »

I wanted to add a shared memory example to enable block processing.
It is useful for applications like voice recognition where blocks of sample data are processed.
A double buffer is used. It is filled sample by sample with data. When the input buffer is full, it is swapped with the output buffer and processed by a block based DSP task.
The block size is configurable with BUF_SIZE

The double buffer is implemented in a distributable task.
Samples are stored into the double buffer via the dbuf_store_if by a client (Note: In a real application the client dbuf_filler would be integrated with an existing task like i2s_handler)
When the input buffer is full, it is swapped with the output buffer.
At that point the double_buffer task sends the pointer to the output buffer to the dsp_task via the interface dbuf_ptr_if.
This causes an event in the dsp_task after which the buffer is processed by calling the function process_buffer(buf) which is implemented in C.
That means the dbuf_ptr_if serves both as synchronisation mechanism and to communicate the pointer to the output buffer.

Because double_buffer is a distributable task, it does not consume a whole core because the select case will be inlined with the task that implements the client side of the interface dbuf_store_if.

double_buffer.h

Code: Select all

// Copyright (c) 2016, XMOS Ltd, All rights reserved
#ifndef __double_buffer_h__
#define __double_buffer_h__

#define BUF_SIZE           240

#endif
process_buffer.c

Code: Select all

// Copyright (c) 2016, XMOS Ltd, All rights reserved
#include <xs1.h>
#include <stdio.h>
#include <stdint.h>
#include "double_buffer.h"

int32_t counter;

int process_buffer(int32_t *buffer) {
  for(int i=0; i<BUF_SIZE; i++) {
      // check the data. Must always increment
      if(buffer[i] != counter) {
          printf("ERROR: Unexpected value in double buffer at index %d. Expected %ld, Got %ld\n", i, counter, buffer[i]);
      };
      counter++;
  }
  return 0;
}
app_dbuf_distributable_task.xc

Code: Select all

// Copyright (c) 2016, XMOS Ltd, All rights reserved
#include <platform.h>
#include <stdio.h>
#include <stdint.h>
#include "double_buffer.h"

// interface to store into the double buffer
interface dbuf_store_if {
    void store(int32_t val); 
};

// interface to swap the double buffer with another task and syncronise
interface dbuf_ptr_if {
  void sync(int32_t * unsafe &buf_ptr);
};

// store data into the double buffer in regular intervals
// In a real application this would be integrated with an existing task like i2s_handler
void dbuf_filler(client interface dbuf_store_if i_dbuf) {
  int32_t sample=0;
  timer tmr;
  int t;
  tmr :> t;
  while (1) {
    select {
       case tmr when timerafter(t+2000) :> t:
          i_dbuf.store(sample); // put sample into double buffer
          sample++;
          break;
    }
  }
}

unsafe {
[[distributable]] //distribute server task across client logical cores
void double_buffer(server interface dbuf_store_if i_dbuf, client interface dbuf_ptr_if i_dbuf_swap){
    int32_t dbuf[2][BUF_SIZE];   // the double buffer
    int input_buf=0;
    int input_idx=0;

    int32_t * unsafe output_buf_ptr = 0; // invalid
    while(1){
        select{
            case i_dbuf.store(int32_t new_val):
              dbuf[input_buf][input_idx] = new_val;
              input_idx++;
              if(input_idx>=BUF_SIZE) {
                // input buffer is full. -> Swap the buffers

                // input buffer will be output buffer after swap
                output_buf_ptr = &dbuf[input_buf][0]; // update pointer 
                i_dbuf_swap.sync(output_buf_ptr); // send pointer to other task and synchronise
                
                // flip input buffer
                input_buf = 1 - input_buf;    
                // reset index            
                input_idx=0;

                printf("Double Buffer was swapped\n");
              }
              break;

        }
    }
}

extern int process_buffer(int32_t * unsafe buffer);

void dsp_task(server interface dbuf_ptr_if i_dbuf_swap) {

   int32_t * unsafe buf=0; // invalid

   while(1) {
       select {
           case i_dbuf_swap.sync(int32_t * unsafe &buf_ptr):
               buf = buf_ptr;
               // new buffer is valid. Now process it
               process_buffer(buf);
               //if(buf[0] > 4*BUF_SIZE) return;
               break;
       }
   }
}
}


int main() {

    interface dbuf_store_if i_dbuf;
    interface dbuf_ptr_if i_dbuf_swap;

    par {
        on tile[1]: dbuf_filler(i_dbuf);
        on tile[1]: [[distribute]]double_buffer(i_dbuf, i_dbuf_swap);
        on tile[1]: dsp_task(i_dbuf_swap);
    }

    return 0;
}