:>, <: vs inuchar, outuchar and friends

Technical questions regarding the XTC tools and programming with XMOS.
User avatar
data
Active Member
Posts: 43
Joined: Wed Apr 06, 2011 8:02 pm

:>, <: vs inuchar, outuchar and friends

Post by data »

I've been assuming that inuchar, outuchar and so forth are deprecated in favor of the nifty polymorphic :> and <: operators (although that polymorphism can trigger compiler bugs!), but in sc_usb I find them appearing in use with xud_interrupt_driven. Are there situations where the function calls are preferable? Perhaps they're a more efficient protocol?

For that matter, what is the protocol used by :> and <:?


MaxFlashrom
Experienced Member
Posts: 82
Joined: Fri Nov 05, 2010 2:59 pm

Post by MaxFlashrom »

The <: and :> should not make your compiler choke if the lexical analyzer performs greedy tokenization, after all, <= and >= += etc. work fine on all C compilers.

I'm always hearing "you don't need to worry about assembler these days: just use C/Python/Java" and then someone asks yet another question like this and I find myself reading some disassembly to get at the truth. (I enjoy it, really :) Yes, I know, often knowing the exact implementation of something is not a good idea if you come to rely on it and it's not guaranteed not to change.

I compiled some code:

Code: Select all

#include <xs1.h>

void sender(chanend outChan);
void receiver(chanend inChan);

int main(void)
{
	chan myChan;

	par {
		sender(myChan);
		receiver(myChan);
	}
}

void sender(chanend outChan)
{
	unsigned int val = 0x12345678;

	while(1) {
		outChan <: val;
		outuint(outChan, val);
	}
}

void receiver(chanend inChan)
{
	unsigned int val;

	while(1) {
		inChan :> val;
		val = inuint(inChan);
	}
}

outuint/outuchar and inuint/inuchar just write/read a value(of the relevant width) to/from a channel. The <: and :> operators accomplish a synchronized thread rendezvous. A value is sent and a confirmation is received before continuing for outputs. For inputs, a handshake is made back to the sender. The outuchar/inuchar can be used to implement lower level protocols where direct control is required, without resorting to assembler.

Code: Select all

		outChan <: val;
// send CT_END to outChan
0x00010100 <sender+16>: ldw   (ru6)       r0, sp[0x0]
0x00010102 <sender+18>: outct (rus)     res[r0], 0x1 *

// wait for an acknowledgement from recipient of CT_END token
0x00010104 <sender+20>: ldw   (ru6)       r0, sp[0x0]
0x00010106 <sender+22>: chkct (rus)     res[r0], 0x1 *

// send val to outChan
0x00010108 <sender+24>: ldw   (ru6)       r1, sp[0x0]
0x0001010a <sender+26>: ldw   (ru6)       r0, sp[0x1]
0x0001010c <sender+28>: out   (r2r)       res[r1], r0 *

// send CT_END to outchan
0x0001010e <sender+30>: ldw   (ru6)       r0, sp[0x0]
0x00010110 <sender+32>: outct (rus)     res[r0], 0x1 *

// wait for an acknowledgement of CT_END token
0x00010112 <sender+34>: ldw   (ru6)       r0, sp[0x0]
0x00010114 <sender+36>: chkct (rus)     res[r0], 0x1 *



// outuint just sends val to outChan; there is no handshaking.
		outuint(outChan, val);
0x00010116 <sender+38>: ldw   (ru6)       r1, sp[0x1]
0x00010118 <sender+40>: ldw   (ru6)       r0, sp[0x0]
0x0001011a <sender+42>: out   (r2r)       res[r0], r1 *



		inChan :> val;
// wait to receive a CT_END token from the sender
0x0001012a <receiver+10>: ldw   (ru6)       r0, sp[0x0]
0x0001012c <receiver+12>: chkct (rus)     res[r0], 0x1 *

// send back a CT_END token as an acknowledgement
0x0001012e <receiver+14>: ldw   (ru6)       r0, sp[0x0]
0x00010130 <receiver+16>: outct (rus)     res[r0], 0x1 *

// read from outChan and store in val
0x00010132 <receiver+18>: ldw   (ru6)       r0, sp[0x0]
0x00010134 <receiver+20>: in    (2r)         r0, res[r0] *
0x00010136 <receiver+22>: stw   (ru6)       r0, sp[0x1]

// do nothing useful at all. The compiler could be improved.
// val = val
0x00010138 <receiver+24>: ldw   (ru6)       r0, sp[0x1]
0x0001013a <receiver+26>: stw   (ru6)       r0, sp[0x1]

// wait to receive a CT_END from the sender
0x0001013c <receiver+28>: ldw   (ru6)       r0, sp[0x0]
0x0001013e <receiver+30>: chkct (rus)     res[r0], 0x1 *

// send back an acknowledgement CT_END token to the sender
0x00010140 <receiver+32>: ldw   (ru6)       r0, sp[0x0]
0x00010142 <receiver+34>: outct (rus)     res[r0], 0x1 *



// inuint just reads from the channel and stores the value; there is no handshaking.
		val = inuint(inChan);
0x00010144 <receiver+36>: ldw   (ru6)       r0, sp[0x0]
0x00010146 <receiver+38>: in    (2r)         r0, res[r0] *
0x00010148 <receiver+40>: stw   (ru6)       r0, sp[0x1]

I hope that helps.
Max.
richard
Respected Member
Posts: 318
Joined: Tue Dec 15, 2009 12:46 am

Post by richard »

It's worth mentioning that the behaviour of :> and <: depends on the type of channel they are used on. On normal channels the communication sequence Max described is used. On streaming channels :> and <: use a single in / out instruction. I'd summarise your choices as follows:
  • Streaming channels provide the best performance. The number of streaming channels between threads on different cores is limited by the number of links between cores (each streaming channel uses one link at each hop along the path between the channel ends). Communication is not synchronised which may make your program harder to reason about (the behaviour of the program could depend on the amount of buffering in the channel).
  • Standard channel communication is a sensible default choice. Communication is packetized so you can as many channels between cores as you like. You can get a many of the performance benefits of streaming channels by making good use of transactions. Communication is synchronised so output statements are guaranteed to wait until the matching input statement is reached.
  • Intrinsics such as inuchar(), outuchar() provide access to the hardware's raw I/O instructions. This can be used to implement custom protocols. I'd would avoid these intrinsics unless you have a good reason to use them. There are a number of things you must keep in mind when designing a protocol, and it is very easy to write something that works most of the time but actually has subtle bugs that cause deadlock if the timing of your program changes.
User avatar
data
Active Member
Posts: 43
Joined: Wed Apr 06, 2011 8:02 pm

Post by data »

MaxFlashrom wrote:The <: and :> should not make your compiler choke if the lexical analyzer performs greedy tokenization, after all, <= and >= += etc. work fine on all C compilers.
True, but try passing a struct, and maybe one with a union in it. I haven't yet had time to work out precisely what makes the error go, but the error is one that invites you to report it to XMOS. (I have done so.)
MaxFlashrom wrote: I hope that helps.
Max.
Very much -- quite interesting, thank you!
User avatar
data
Active Member
Posts: 43
Joined: Wed Apr 06, 2011 8:02 pm

Post by data »

richard wrote:It's worth mentioning that the behaviour of :> and <: depends on the type of channel they are used on. On normal channels the communication sequence Max described is used. On streaming channels :> and <: use a single in / out instruction. I'd summarise your choices as follows:
This is also very helpful -- thank you for writing this. I wasn't clear on these categories before.

Clearly there are principles in this system which I haven't yet completely grasped -- need to study a little more.
richard wrote:There are a number of things you must keep in mind when designing a protocol, and it is very easy to write something that works most of the time but actually has subtle bugs that cause deadlock if the timing of your program changes.
This is absolutely true, and I've had to learn it by painful experience!