how to trigger an event/select on buffered port output ?

Technical questions regarding the XTC tools and programming with XMOS.
User avatar
fabriceo
XCore Addict
Posts: 186
Joined: Mon Jan 08, 2018 4:14 pm

how to trigger an event/select on buffered port output ?

Post by fabriceo »

Hello,
I m struggling to find any information or source code about this:
buffered output port are useful to serialize data, like in I2S or SPI.
after issuing a first "p <: x", the port is immediately filled with x and starts serialization.
after issuing a second "p <: y" the task is not stopped because the port has 1 word buffer.
but after issuing imediately a third "p <: z" , the task will wait the end of the first output;

so my question is how to detect that a port is ready to accept another value in its buffer to avoid systematically blocking a task,
or saying in other words, how to write a select case statement to execute the case only when the output port is ready to accept a new value ?

for input port it is as easy as that:

Code: Select all

while (1) select {
   case p :> x : { code dealing with input word x }
   <... other cases or default ...>
}
what is the syntax for output ?

thanks


User avatar
fabriceo
XCore Addict
Posts: 186
Joined: Mon Jan 08, 2018 4:14 pm

Post by fabriceo »

Hello,
it seems there is just no any mechanism raising an event for output ports... (please xmos team confirm or not).
Then I propose a way to mitigate this, by controlling variation of the "ts" (timestamp) register attached to the ports.
The ts register is set to the value of the port counter each time there is a transfer between the serdes (serialization) register and the transfer register (also called fifo).

in the following code example, the port pp is declared buffered 32 and is by default clocked by CLKBLK_REF so 100mhz.
this requires 32 cpu clock to shift out all the serialization register.

Code: Select all

#define gettime(_x)   asm volatile("gettime %0":"=r"(_x))
#define getts(_x,_p)  asm volatile("getts %0, res[%1]":"=r"(_x):"r"(_p));
buffered out port:32 pp = { your_1bit_port };

void mytask(){
    unsigned ts0, ts1, t0, t1;
    pp <: 0;            // fill serdes register and start outputing 32 bits one by one (non blocking)
    getts(ts0,pp);      // get the latest value ot timestamp counter
    gettime(t0);        // capture cpu time
    pp <: 0;            // fill transfer register (fifo), non blocking for first time
    pp <: 0;            //wait for filling transfer register as fifo is now full
    pp <: 0;            //same
    //sync(pp);         //wait total end of serialization
    gettime(t1);        // capture cpu time
    delay_ticks(100); //to ensure that serialization is completely finished
    getts(ts1,pp);
    printf("time= %d, ts1-ts0= %d, n= %d, \n",t1-t0-1, ts1-ts0,n);
}
running this program will print:
time= 64, ts1-ts0= 96, n= 0

the 2 first port-outputs (pp <: 0) are non blocking. but the 2 others will be waiting for the readiness of the transfer-register, so 32 cpu cycle each.
Therefor the cpu time taken by these 4 port-outputs will be "time= 64" only.
then we have a delay_ticks(100) before reading the ts value and we get "ts1-ts0= 96", because the transfer register has been transferred into the serdes register only 3 times, not four. whatever you wait , it will never show 128 but stays at 96.

removing the // before the sync(pp) change the game and will print:
time= 128, ts1-ts0= 96, n= 0

we can see that sync(pp) is waiting the serialization to be completely finished, including the latest value held in the transfer register.
so we get "time= 128" cpu cycles which perfectly correspond to the 4 x 32 bits serialization.

Still the timestamp register doesn't change and is stuck with "ts1-ts0= 96"

So, it is possible to use the cpu time between 2 port-outputs access by checking the variation of the timestamp register, as per the example below:

Code: Select all

void mytask(){
    unsigned ts0, ts1, ts2, t0, t1, n= 0;
    pp <: 0;            // fill serdes register and start outputting 32 bits one by one
    getts(ts0,pp);      // provide the last and unknown value ot timestamp counter
    gettime(t0);        // timestamp starting point
    pp <: 0;            // fill transfer register (fifo), non blocking for first time
    do { n++; getts(ts1,pp); } while (ts0 == ts1);  //this will wait a transfer between fifo and serdes register
    pp <: 0;            //fill fifo which is now empty (non blocking)
    do { n++; getts(ts2,pp); } while (ts2 == ts1);  //wait next transfer
    pp <: 0;            //fill buffer which is now empty  (non blocking)
    do { n++; getts(ts1,pp); } while (ts2 == ts1);  //wait next transfer
    //sync(pp);         //wait total end of serialization
    gettime(t1);        // timestamp end of code
    printf("time= %d, ts1-ts0= %d, n= %d\n",t1-t0-1, ts1-ts0,n);
}
running this program will print:
time= 100, ts1-ts0= 96, n= 24

so we were able to compute "n=24" using cpu time available between each port access.
time = 100 instead of time = 96 is just because the last do/while loop brings 4 extra instructions to execute its last cycle (a while/do) might be better here).
we also see that "ts1-ts0= 96" prooves that we have used all the possibilities given by this solution.

remark, it is not possible to detect any changes after the very first port-output as there is no fifo-serdes transfer, but a direct access to the serdes register.
after a very first port-output, it seems that the fifo mechanism with transfer and timestamp capture always works , even if the fifo becomes empty at some point in time.
this is demonstrated with the code example:

Code: Select all

void mytask(){ 
    unsigned ts0, ts1, ts2;
    pp <: 0;            // fill serdes register and start outputting 32 bits one by one
    getts(ts0,pp);      // get the last value ot timestamp
    delay_ticks(100); //creates a fifo empty situation as this is longer than serialization time
    getts(ts1,pp);      // get the last value of timestamp (unchanged here)
    pp <: 0;            // fill the empty fifo which seems to trigger an immediate transfer to serdes
    getts(ts2,pp);      // get timestamp
    printf("1st  ts = %d\n",ts1-ts0);
    printf("2nd ts = %d\n",ts2-ts1);
}
which is printing:
1st ts = 0
2nd ts = 103


hope this helps someone !

remark2: as an example with this solution, it would be possible to modernize the xua_audio.xc of the usb software audio application by using a while(1) select with cases for managing client-server xc interface with decouple task, and the default statement could update lrclk and the adc/dac registers once the ts variation shows that the fifo is empty ready to accept a new sample...
User avatar
CousinItt
Respected Member
Posts: 365
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

Could you use xC to handle the timestamp directly on the output? I don't know if it would be a bit more efficient, but probably more readable.

See section 6.8 in the XMOS programming guide.
User avatar
fabriceo
XCore Addict
Posts: 186
Joined: Mon Jan 08, 2018 4:14 pm

Post by fabriceo »

Hello CousinItt
no unfortunately you have to use the asm getts to read the timestamp counter in this case, because if you use a statement like
pp <: 0 @ ts;
then the assembly generated includes a "syncr" instruction before the getts, so it destroy the benefit of the fifo...

Code: Select all

	out res[r5], r4
	syncr res[r5]
	getts r0, res[r5]