The combined code: 6 to zero channels!

Technical questions regarding the XTC tools and programming with XMOS.
Post Reply
User avatar
aclassifier
Respected Member
Posts: 483
Joined: Wed Apr 25, 2012 8:52 pm
Contact:

The combined code: 6 to zero channels!

Post by aclassifier »

I have made a blog chapter on two cases where I compare use of channels and interface, and related that to the number of chanends. Three button clients send a message to a handling mux server every second.

I always had 6 chanends when I used chan, and 6, 4 or zero chanends when I used interface. Since chanends are a limited resource (32 per core) then this is interesting.

There are 8 different par placements. 4 are legal for the chan case, 6 for the interface case. Two more! I wonder if this is by design or two errors too much?

I have once asked for any rules about how #chanends are calculated, but got no response. I can see why! [2]

The blog note is at [1]. A side-by-side note with the two code fragments I have also attached here. Plus the code (below).

[1] The combined code: 6 to zero channels (Disclaimer: no money, no gifts, no ads. Just fun and expenses)

[2] Calculating number of chanends

Code: Select all

#if defined TEST_CHAN_AND_COMBINE_TEST

    #include <platform.h>
    #include <stdio.h>
    #include <timer.h> // XS1_TIMER_HZ etc

    #define DEBUG_PRINT_TEST 0
    #if (DEBUG_PRINT_TEST == 1)
        // Uses 1 timer and one chanend (not counted below)
        #define debug_print(fmt, ...) do \
            { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)
    #else
        #define debug_print(fmt, ...)
    #endif

     [[combinable]]
     void button (chanend c_out) {
        timer t;
        int s;
        t :> s;
        while (1) {
            select {
                case t when timerafter(s) :> void: {
                    c_out <: (s/XS1_TIMER_KHZ); // ms
                    s += XS1_TIMER_HZ;
                    break;
                }
            }
        }
    }

    [[combinable]]
    void handle (chanend c_but[3]) {
        int val;
        while (1) {
            select {
                case c_but[int i] :> val: {
                    debug_print ("handle: from %d val %u\n", i, val);
                    break;
                }
            }
        }
    }

    #define DO_PLACED 1 // 1-4 works

    int main (void) {
        chan c_but[3]; // Using 6 chanends always
        par {
            #if (DO_PLACED == 1) // Works, also with interface. Uses 4 cores, 4 timers, 6 chanends
                on tile[0].core[0]: handle (c_but);
                par {
                    on tile[0].core[2]: button (c_but[0]);
                    on tile[0].core[3]: button (c_but[1]);
                    on tile[0].core[4]: button (c_but[2]);
                }
            #elif (DO_PLACED == 2) // Works, also with interface. Uses 2 cores, 2 timers, 6 chanends
                on tile[0].core[0]: handle (c_but);
                par {
                    on tile[0].core[1]: button (c_but[0]);
                    on tile[0].core[1]: button (c_but[1]);
                    on tile[0].core[1]: button (c_but[2]);
                }
            #elif (DO_PLACED == 3) // Works, also with interface. Uses 4 cores, 4 timers, 6 chanends
                handle (c_but);
                par {
                    button (c_but[0]);
                    button (c_but[1]);
                    button (c_but[2]);
                }
            #elif (DO_PLACED == 4) // Works, also with interface. Uses 2 cores, 2 timers, 6 chanends
                handle (c_but);
                [[combine]]
                par {
                    button (c_but[0]);
                    button (c_but[1]);
                    button (c_but[2]);
                }
            #elif (DO_PLACED == 5) // Errs, WORKS with interface
                on tile[0].core[0]: handle (c_but);
                                         // ^~~~~ note: other end is used here
                par {
                    on tile[0].core[0]: button (c_but[0]);
                    // ^~~~~ error: `c_but' used between two combined tasks
                    on tile[0].core[1]: button (c_but[1]);
                    on tile[0].core[1]: button (c_but[2]);
                }
            #elif (DO_PLACED == 6) // Errs, WORKS with interface
                 [[combine]]
                 par {
                     handle (c_but);
                     // ^~~~~ note: other end is used here
                     button (c_but[0]);
                          // ^~~~~ error: `c_but' used between two combined tasks
                     button (c_but[1]);
                          // ^~~~~ error: `c_but' used between two combined tasks
                     button (c_but[2]);
                          // ^~~~~ error: `c_but' used between two combined tasks
                 }
            #elif (DO_PLACED == 7) // Errs as with interface
                on tile[0].core[0]: handle (c_but);
                [[combine]]
                par {
                // ^~~~~ error: cannot apply [[combine]] to multi-tile par
                    on tile[0].core[2]: button (c_but[0]);
                    on tile[0].core[3]: button (c_but[1]);
                    on tile[0].core[4]: button (c_but[2]);
                }
            #elif (DO_PLACED == 8)  // Errs as with interface
                on tile[0].core[0]: handle (c_but);
                [[combine]]
                par {
                // ^~~~~ error: cannot apply [[combine]] to multi-tile par
                    button (c_but[0]);
                    // ^~~~~ error: components of multi-tile par must have `on' specifier or call a service
                    button (c_but[1]);
                    // ^~~~~ error: components of multi-tile par must have `on' specifier or call a service
                    button (c_but[2]);
                    // ^~~~~ error: components of multi-tile par must have `on' specifier or call a service
                }
            #else
                // warning: unused variable `c_but' [-Wunused-variable]
            #endif
        }
        return 0;
    }

#elif defined TEST_INTERFACE_AND_COMBINE_TEST

    #include <platform.h>
    #include <stdio.h>
    #include <timer.h> // XS1_TIMER_HZ etc

    #define DEBUG_PRINT_TEST 1
    #if (DEBUG_PRINT_TEST == 1)
        // Uses 1 timer and one chanend (not counted below)
        #define debug_print(fmt, ...) do \
            { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)
    #else
        #define debug_print(fmt, ...)
    #endif

    interface ifa {
        void but (int x);
    };

    [[combinable]]
    void button (client interface ifa i_but) {
       timer t;
       int s;
       t :> s;
       while (1) {
           select {
               case t when timerafter(s) :> void: {
                   i_but.but(s/XS1_TIMER_KHZ); // ms
                   s += XS1_TIMER_HZ;
                   break;
               }
           }
       }
    }

    [[combinable]]
    void handle (server interface ifa i_but[3]) {
        while (1) {
            select {
                case i_but[int i].but (int val) : {
                    debug_print ("handle: from %d val %u\n", i, val);
                    break;
                }
            }
        }
    }

    #define DO_PLACED 6 // 1-6 works

    int main (void) {
        interface ifa i_but[3]; // 6 to zero chanends
        par {
            #if (DO_PLACED == 1) // Works, also with chan. Uses 4 cores, 4 timers, 6 chanends
                on tile[0].core[0]: handle (i_but);
                par {
                    on tile[0].core[2]: button (i_but[0]);
                    on tile[0].core[3]: button (i_but[1]);
                    on tile[0].core[4]: button (i_but[2]);
                }
            #elif (DO_PLACED == 2) // Works, also with chan. Uses 2 cores, 2 timers, 4 chanends
                on tile[0].core[0]: handle (i_but);
                par {
                    on tile[0].core[1]: button (i_but[0]);
                    on tile[0].core[1]: button (i_but[1]);
                    on tile[0].core[1]: button (i_but[2]);
                }
            #elif (DO_PLACED == 3) // Works, also with chan. Uses 4 cores, 4 timers, 6 chanends
                handle (i_but);
                par {
                    button (i_but[0]);
                    button (i_but[1]);
                    button (i_but[2]);
                }
            #elif (DO_PLACED == 4) // Works, also with chan. Uses 2 cores, 2 timers, 4 chanends
                handle (i_but);
                [[combine]]
                par {
                    button (i_but[0]);
                    button (i_but[1]);
                    button (i_but[2]);
                }
            #elif (DO_PLACED == 5) // Works, NOT with chan. Uses 2 cores, 2 timers, 4 chanends
                on tile[0].core[0]: handle (i_but);
                par {
                    on tile[0].core[0]: button (i_but[0]);
                    on tile[0].core[1]: button (i_but[1]);
                    on tile[0].core[1]: button (i_but[2]);
                }
            #elif (DO_PLACED == 6) // Works, NOT with chan. Uses 1 core, 1 timer, 0 chanends
                 [[combine]]
                 par {
                     handle (i_but);
                     button (i_but[0]);
                     button (i_but[1]);
                     button (i_but[2]);
                 }
            #elif (DO_PLACED == 7) // Errs as with chan
                on tile[0].core[0]: handle (i_but);
                [[combine]]
                par {
                // ^~~~~ error: cannot apply [[combine]] to multi-tile par
                    on tile[0].core[2]: button (i_but[0]);
                    on tile[0].core[3]: button (i_but[1]);
                    on tile[0].core[4]: button (i_but[2]);
                }
            #elif (DO_PLACED == 8)  // Errs as with chan
                on tile[0].core[0]: handle (i_but);
                [[combine]]
                par {
                // ^~~~~ error: cannot apply [[combine]] to multi-tile par
                    button (i_but[0]);
                    // ^~~~~ error: components of multi-tile par must have `on' specifier or call a service
                    button (i_but[1]);
                    // ^~~~~ error: components of multi-tile par must have `on' specifier or call a service
                    button (i_but[2]);
                    // ^~~~~ error: components of multi-tile par must have `on' specifier or call a service
                }
            #else
                    // warning: unused variable `i_but' [-Wunused-variable]
            #endif
        }
        return 0;
    }
#endif
Attachments
141_fig1_the_combined_code__6_to_zero_channel_oyvind_teig.pdf
(55.66 KiB) Downloaded 183 times


--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
MyKeys
Active Member
Posts: 33
Joined: Mon Jul 03, 2017 9:41 am

Post by MyKeys »

An Interface resolves to shared memory or channels depending on where the tasks are running.
Combinable tasks all run on the same single core and hence an interface here will use shared memory.
User avatar
aclassifier
Respected Member
Posts: 483
Joined: Wed Apr 25, 2012 8:52 pm
Contact:

Post by aclassifier »

Thanks for that crisp summary!

How about adding something like this: "The less concrete placement that's done with Interface then lesser channels need to be used, since the code may then be placed on the same core. In the utmost case no chanends are needed."

Could synchronisation also be included in the rules, when that is and is not necessary, even if all basic mechanisms are(?) synchronous? Like using the chan instruction or using locks, or just not needing them. I guess this also has to do with whether a select case is guarded.

Update: But the XCORE is a shared memory architecture, "Each xCORE Tile integrates a single 64KB SRAM bank for both instructions and data." (From XS1-U16A-128-FB217 Datasheet). I guess then that any communication and synchronisation (single or multi-core) may use a combination of channel communication and shared memory variables?
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
User avatar
akp
XCore Expert
Posts: 578
Joined: Thu Nov 26, 2015 11:47 pm

Post by akp »

dealing with the paucity of chanends has been a nightmare for me. you will need to familiarize yourself with the other methods of sharing memory between tasks. there is a very good forum thread on this https://www.xcore.com/viewtopic.php?f=26&t=3061
User avatar
aclassifier
Respected Member
Posts: 483
Joined: Wed Apr 25, 2012 8:52 pm
Contact:

Post by aclassifier »

Thanks a lot for that reference! I unabashedly made a chapter in my blog [1].

Would have been interesting to see whether the compiler in the cases in your reference would actually use chaneneds for some it it. I saw that for distributed tasks it may use locks. I must learn about distributed tasks one of these days. I have never used them. I must. (Update: reading myself up, I do have several that only communicate with other tasks over interfaces.) XC is so small, but so extensive! Thanks akp!

[1] Sharing memory (Disclaimer: no money, no ads, no gifts, only fun and expenses)
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
Post Reply