kode24

Started 17Feb2020, updated 2Dec2020. This note is in group Technology (plus My XMOS pages  and Aquarium pages).

For the TIOBE index 016:[10]: “xC programming”.

Resource page for kode24.no article

Kode24.no er en del av Scandinavia Online / Aller Media (Standard disclaimer)

Slik styrer han akvariet sitt med xC (This is how he controls his aquarium with xC)

Svar på en kommentar: “Den ble litt lang, ja. Men jeg ønsket at de som først trigget på innholdet skulle få igjen for “strevet”. Og om du kjøper et kort og kommer deg opp på xC, kan du jo lese den om igjen etter noen uker. Pluss, tilbudet gjelder! Lykke til!”

Tilbakemelding fra kode24 14.april 2020. Artikkelen var lest av 1100! Om 5% har lest hele så blir det 55 personer! Det er bra i min verden!

References

Article in kode24.no 24. March 2020
www.kode24.no – Slik styrer han akvariet sitt med xC. Øyvind Teig gir deg full gjennomgang av hvordan han bygde XMOS-systemet. In Norwegian. (This is how he controls his aquarium with xC. Øyvind Teig gives you a full overview of how he built the XMOS system). Kode24 is a Norwegian web magazine for coders.

Try the Google translation of it (here) → observe that task may become bag!

Thanks to Sverre Hendseth, NTNU for a thorough reading and commenting before the final version for kode24.

The source to the article is here.

Democode

The tool used is XMOS xTIMEcomposer 14.4.1. It is free for download, but you have to register

It also contains a simulator and a scope. You can use them on this democode. However, timerafter(?) and ports will then not be simulated, so you need to rewrite or find other code examples for the simulator. Something to do if you are alone at home in these corona times?

The code (below) is in in the xC language. Papers about it from XMOS are referenced in the above link
The code runs on an xCORE-200 explorer kit  (XMOS and mine My xCORE-200 eXplorerKIT notes (WiFi))

This code contains three TODOs. See Code example for article on the XMOS xCore Exchange. Update 10Aug2020: Some of them are actually resolved there.

More on xC

For more about xC, also see My XMOS pages

Disclaimer

Standard disclaimer

Aquarium notes

My aquarium notes
My aquarium holiday automatic fish feeder (for granules)
My USB watchdog (and relay output) box

Code

Mail me (here) if you want the full code of the aquarium, and I will upload it to somewhere. I will publish it here.

Task viewer

Screen clips from xTIMEcomposer. Red text and build outputs are insets.

[[combine]] in line 339. 3 cores looks like ok from the figure

No [[combine]] in line 339. With the 6 cores it does not look logical from the figure

Task / data flow diagram

The code

/*
 * main.xc
 *
 *  Created on: 12. feb. 2020
 *      Author: teig
 *      Ver 0.75 2020.02.21 in buffered port:1 inP1_button, instead of in port inP1_button,
 *      Ver 0.74 2020.02.15 https://xcore.com/viewtopic.php?f=26&t=7839
 *      Ver 0.73 2020.02.15 round_cnt_task added, but it cannot be [[distributable]]
 *      Ver 0.72 2020.02.15 Works, before [[distributable]]
 *      Ver 0.70 2020.02.14 outP4_leds is new
 *      Ver 0.60 2020.02.13 inP1_button button added
 *      Ver 0.50 2020.02.13 Starts workers in 4 sequences and workers simulate random work
 */

#include <platform.h> // core
#include <stdio.h>    // printf
#include <timer.h>    // delay_milliseconds(..), XS1_TIMER_HZ etc
#include <random.h>   // xmos. Also uses "random_conf.h"
#include <iso646.h>   // readability

// ---
// Control printing
// See https://stackoverflow.com/questions/1644868/define-macro-for-debug-printing-in-c
// ---

#define DEBUG_PRINT_TEST 0 // [0->1] code about [5,12] kB
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

// ---
// Define bool
// BOOLEAN #include <stdbool.h> if C99
// See https://www.teigfam.net/oyvind/home/technology/165-xc-code-examples/#bool
// ---

typedef enum {false,true} bool; // 0,1 This typedef matches any integer-type type like long, int,
                                // signed, unsigned, char, bool

// ---
// Define type equal to the width of xC "timer". This processor has 10 HW timers,
// but the numbers needed in this code will (with NUM_WORKERS 4) be 2 timers if
// all worker_task run on the same logical core (par [[combine]]) or 5 timers
// if worker_task each have a logical core for themselves.
// Both signed and unsigned (always int) will do, since both will wrap around on
// "overflow" and the hex code will look the same. This way AFTER is well defined
// since adding a value will trigger "timerafter" ticks into the future
// ---

typedef signed time32_t; // Ticks to 100 in 1 us

// ---
// Define number of workers. Is needed here because variable length arrays
// are not permitted to tasks when they are [[combinable]]
// ---

#define NUM_WORKERS 4

// ---
// Define data typedefs
// ---

typedef unsigned worked_ms_t;

typedef struct log_t {
    unsigned    cnt;
    unsigned    log_started  [NUM_WORKERS];
    unsigned    log_finished [NUM_WORKERS];
    worked_ms_t log_worked_ms[NUM_WORKERS];
    bool        button_pressed;
} log_t;

// ---
// do_print_log
// Prints log if DEBUG_PRINT_TEST is 1. If DEBUG_PRINT_TEST is 0, this function
// is not generated by the compiler
// ---

void do_print_log (
        log_t log,
        unsigned const num_workers) {

    debug_print ("\ncnt %u %s\n", log.cnt, log.button_pressed ? "BUTTON" : "");
    debug_print ("%s", "log.log_started   ");
    for (unsigned ix=0; ix < num_workers; ix++) {
        debug_print ("%2u ", log.log_started[ix]);
    }
    debug_print ("%s", "\nlog.log_worked_ms ");
    for (unsigned ix=0; ix < num_workers; ix++) {
        debug_print ("%2u ", log.log_worked_ms[ix]);
    }
    debug_print ("%s", "\nlog.log_finished  ");
    for (unsigned ix=0; ix < num_workers; ix++) {
        debug_print ("%2u ", log.log_finished[ix]);
    }
    debug_print ("%s", "\n");
}

// ---
// 1 BIT PORT
// External button defined (button press pulls a pullup resistor down)
// Board's buttons 4E.0 and 4E.1 could have been used, bit want to show 1-bit port
// ---

in buffered port:1 inP1_button = on tile[0]: XS1_PORT_1M; // External HW GPIO J1 P63

#define BUTTON_PRESSED  0
#define BUTTON_RELEASED 1

// ---
// 4 BIT PORT
// Internal LEDs defined. High is "on"
// ---

out buffered port:4 outP4_leds = on tile[0]: XS1_PORT_4F; // xCORE-200 explorerKIT GPIO J1 P5, P3, P1

#define BOARD_LEDS_INIT           0x00
#define BOARD_LED_MASK_GREEN_ONLY 0x01 // BIT0
#define BOARD_LED_MASK_RGB_BLUE   0x02 // BIT1
#define BOARD_LED_MASK_RGB_GREEN  0x04 // BIT2
#define BOARD_LED_MASK_RGB_RED    0x08 // BIT3

#define BOARD_LED_MASK_MAX_1 (BOARD_LED_MASK_GREEN_ONLY)
#define BOARD_LED_MASK_MAX_2 (BOARD_LED_MASK_RGB_BLUE  bitor BOARD_LED_MASK_MAX_1)
#define BOARD_LED_MASK_MAX_3 (BOARD_LED_MASK_RGB_GREEN bitor BOARD_LED_MASK_MAX_2)
#define BOARD_LED_MASK_MAX_4 (BOARD_LED_MASK_RGB_RED   bitor BOARD_LED_MASK_MAX_3)

#define BOARD_LED_MASK_MAX BOARD_LED_MASK_MAX_1 // _1, _2, _3 or _4

// ---
// do_swipe_leds
// Sets LEDs on the xCORE-200 explorerKIT board. There are two, one green only
// and one RGB (with three lines). High is LED on
// ---

void do_swipe_leds (
        out buffered port:4 outP4_leds,
        unsigned &?led_bits, // '&' is reference. Aside: pointer types:
                             // no decoration (safe), "movable", "alias" and  "unsafe"
        unsigned const board_led_mask_max) {

    if (isnull(led_bits)) { // Just to show a nullable type, shown with '?':
        outP4_leds <: BOARD_LED_MASK_GREEN_ONLY;
    } else {
        outP4_leds <: led_bits; // Output LED bits

        led_bits++;
        led_bits and_eq board_led_mask_max; // GREEN on and off and 3-coloured RGB LED
    }
}

// ---
// round_cnt_task
// Task that just outputs an incremented value, showing use of a chan
// This takes two chanends and one logical core.
// Plus one timer, for some reason TODO
// ---

// if [[distributable]] error message from compiler here
void round_cnt_task (chanend c_cnt) { // chans are untyped in xC (but interface is typed++)
    unsigned cnt = 0;
    while (true) {
        cnt++;
        // Synchronous, blocking, no buffer overflow ever possible since there is no buffer:
        c_cnt <: cnt;
    }
}

// if [[distributable]] error message from compiler here
// Not used task, but does the same as the task above
void round_cnt_task_2 (chanend c_cnt) { // chans are untyped in xC (but interface is typed++)
    unsigned cnt = 0;
    timer    tmr;
    time32_t time_ticks; // Ticks to 100 in 1 us

    tmr :> time_ticks; // Now
    while (true) {
        select { // The case passively waits on an event:
            case tmr when timerafter (time_ticks) :> time_ticks : {
                // time_tics always updated, so timerafter is always ready
                cnt++;
                // Synchronous, blocking, no buffer overflow ever possible since there is no buffer:
                c_cnt <: cnt;
            } break;
        }
    }
}

// ---
// An interface is implemented by chanends, locks, calls or safe patterns set
// up by the code generation. The particular _transaction_ pattern below enables
// the compiler to set up that particular asynchronous pattern, based on
// synchronous, blocking primitives.
// ---

typedef interface worker_if_t {
                            void        async_work_request (void);
    [[notification]] slave  void        finished_work (void);
    [[clears_notification]] worked_ms_t get_work_result (void);
} worker_if_t;

// ---
// worker_task
// NUM_WORKERS of these are started. They may share a logical core when
// [[combine]] par or run on NUM_WORKERS logical cores if no [[combine]].
// The pattern starts with async_work_request and then simulates work for
// some time, then sends a [[notification]] of finished_work and then the
// clients responds with get_work_result which [[clears_notification]].
// The compiler will insert the correct code to allow only that pattern.
// ---

[[combinable]]
void worker_task (
        server worker_if_t i_worker,
        const unsigned index_of_server) {

    timer       tmr;
    time32_t    time_ticks; // Ticks to 100 in 1 us
    bool        doCollectData = false;
    worked_ms_t sim_work_ms = 0;
    unsigned    random_seed = random_create_generator_from_seed(index_of_server); // xmos
    unsigned    random_work_delay_ms;

    debug_print ("worker_task %u\n", index_of_server);

    while (true) {
        select { // Each case passively waits on an event:
            case i_worker.async_work_request () : {
                doCollectData = true;
                random_work_delay_ms = random_get_random_number (random_seed) % 100; // [0..99]
                sim_work_ms = random_work_delay_ms;
                tmr :> time_ticks; // Immediately
                time_ticks += (sim_work_ms * XS1_TIMER_KHZ); // Simulate work
            } break;
            case (doCollectData == true) => tmr when timerafter (time_ticks) :> void : {
                // Now we have simulated that picking up log.log_worked_ms took random_work_delay_ms
                doCollectData = false;
                i_worker.finished_work();
            } break;
            case i_worker.get_work_result (void) -> worked_ms_t worked_ms : {
                worked_ms = sim_work_ms;
            } break;
        }
    }
}

// ---
// client_task
// Asks for work from NUM_WORKERS worker_task (service requested
// in different sequences) and results from workers, when they arrive, handled.
// Each interface call is blocking and synchronous, but the net result of the
// pattern is asynchronous worker_task assignments.
// Log, a button and LEDs handled.
// ---

[[combinable]]
void client_task (
        client worker_if_t i_worker[NUM_WORKERS],
        in buffered port:1 inP1_button,
        out buffered port:4 outP4_leds,
        chanend c_cnt) {

    timer    tmr;
    time32_t time_ticks; // Ticks to 100 in 1 us
    bool     expect_notification_nums = 0;

    // xmos. Pseudorandom, so will look the same on and after each start-up
    unsigned random_seed = random_create_generator_from_seed(1);

    unsigned random_number;
    log_t    log;
    bool     allow_button = false;
    bool     button_current_val = BUTTON_RELEASED;
    unsigned led_bits; // Init below..

    led_bits = BOARD_LEDS_INIT; // ..here to avoid "not used" if "null" used instead
    log.button_pressed = false;

    debug_print ("%s", "client_task\n");

    tmr :> time_ticks;
    time_ticks += (1 * XS1_TIMER_HZ); // 1 second before first timerafter

    while (true) {
        select { // Each case passively waits on an event:
            case (expect_notification_nums == 0) => tmr when timerafter (time_ticks) :> void : {
                random_number = random_get_random_number (random_seed); // Just trying to start randomly

                // Start as [0,1,2,3], [3,0,1,2], [2,3,0,1], [1,2,3,0]:
                for (unsigned ix=0; ix < NUM_WORKERS; ix++) {
                    unsigned random_worker = random_number % NUM_WORKERS; // Inside [0..(NUM_WORKERS-1)]
                    i_worker[random_worker].async_work_request();
                    // Now log.log_started in random sequence
                    random_number++; // Next (but modulo NUM_WORKERS above)

                    log.log_started[ix] = random_worker;
                }
                expect_notification_nums = NUM_WORKERS;
                // === Do something else while all worker_task work ===
            } break;
            case (expect_notification_nums > 0) => i_worker[unsigned index_of_server].finished_work() : {

                // Server async_work_request entries protected by code and scheduler until this is run:
                log.log_worked_ms[index_of_server] = i_worker[index_of_server].get_work_result();
                // async_work_request is not allowed again before the above line is run,
                // by compiler and code

                expect_notification_nums--;

                log.log_finished[expect_notification_nums] = index_of_server;
                if (expect_notification_nums == 0) {
                    select { // Nested select
                        case c_cnt :> log.cnt: {} break;
                    }
                    do_print_log (log, NUM_WORKERS); // Only if DEBUG_PRINT_TEST is 1
                    do_swipe_leds (outP4_leds, led_bits, BOARD_LED_MASK_MAX); // led_bits may be "null"
                    // === Process received log.log_worked_ms, or just.. ===
                    tmr :> time_ticks; // ..repeat immediately
                    allow_button = (log.cnt >= 10);
                } else {}
            } break;
            case allow_button => inP1_button when pinsneq(button_current_val) :> button_current_val: {
                // I/O pin changed value
                // Debouncing not done (best done in separate task, with its own timerafter)
                log.button_pressed = (button_current_val == BUTTON_PRESSED); // May not reach do_print_log
            } break;
            // default: {} break; // Only for busy poll! Not allowed in [[combinable]] function
        }
    }
}

// ---
// main
// Starts tasks
// ---

int main() {
    worker_if_t i_worker[NUM_WORKERS];
    chan c_cnt;
    par {
        [[combine]] // NUM_WORKERS(4) =
                    // [cores,timers,chanends]->[3,3,11], if no [[combine]] then ->[6,6,11]
        par (unsigned ix = 0; ix < NUM_WORKERS; ix++) {
            worker_task (i_worker[ix], ix);
        }
        client_task (i_worker, inP1_button, outP4_leds, c_cnt);
        round_cnt_task (c_cnt);
    }
    return 0;
}

The code (download)

The code may be donwloaded from My xC code downloads page, part #202.

Log

client_task
worker_task 0
worker_task 1
worker_task 2
worker_task 3

cnt 1 
log.log_started    2  3  0  1 
log.log_worked_ms 92 78 69 59 
log.log_finished   0  1  2  3 

cnt 2 
log.log_started    1  2  3  0 
log.log_worked_ms 18 25 77 58 
log.log_finished   2  3  1  0 

cnt 3 
log.log_started    3  0  1  2 
log.log_worked_ms 66 15 61 96 
log.log_finished   3  0  2  1 

cnt 4 
log.log_started    2  3  0  1 
log.log_worked_ms 56 86 16 30 
log.log_finished   1  0  3  2 

cnt 5 
log.log_started    1  2  3  0 
log.log_worked_ms  2 85 73 62 
log.log_finished   1  2  3  0 

cnt 6 
log.log_started    0  1  2  3 
log.log_worked_ms  9 92 83 50 
log.log_finished   1  2  3  0 

cnt 7 
log.log_started    1  2  3  0 
log.log_worked_ms 55 81 94 12 
log.log_finished   2  1  0  3 

cnt 8 
log.log_started    0  1  2  3 
log.log_worked_ms 53 60 30 55 
log.log_finished   1  3  0  2 

cnt 9 
log.log_started    0  1  2  3 
log.log_worked_ms 99 72 32  7 
log.log_finished   0  1  2  3 

cnt 10 
log.log_started    3  0  1  2 
log.log_worked_ms 97 63 61 23 
log.log_finished   0  1  2  3 

cnt 11 
log.log_started    2  3  0  1 
log.log_worked_ms 54 46 78 22 
log.log_finished   2  0  1  3 

cnt 12 
log.log_started    1  2  3  0 
log.log_worked_ms 32 85 54 67 
log.log_finished   1  3  2  0 

cnt 13 
log.log_started    0  1  2  3 
log.log_worked_ms 15 56 77 42 
log.log_finished   2  1  3  0 

cnt 14 
log.log_started    2  3  0  1 
log.log_worked_ms 17 18 38 81 
log.log_finished   3  2  1  0 

cnt 15 
log.log_started    3  0  1  2 
log.log_worked_ms 78 39 41 48 
log.log_finished   0  3  2  1 

cnt 16 
log.log_started    1  2  3  0 
log.log_worked_ms 39 57 71 77 
log.log_finished   3  2  1  0 

cnt 17 
log.log_started    1  2  3  0 
log.log_worked_ms 81 97 84 44 
log.log_finished   1  2  0  3 

cnt 18 
log.log_started    2  3  0  1 
log.log_worked_ms 56 58 97 67 
log.log_finished   2  3  1  0 

cnt 19 
log.log_started    3  0  1  2 
log.log_worked_ms 74 31 88  9 
log.log_finished   2  0  1  3 

cnt 20 
log.log_started    0  1  2  3 
log.log_worked_ms 55 64 93 78 
log.log_finished   2  3  1  0 

cnt 21 
log.log_started    3  0  1  2 
log.log_worked_ms 98 51 56 49 
log.log_finished   0  2  1  3 

cnt 22 
log.log_started    1  2  3  0 
log.log_worked_ms 92 73 74 19 
log.log_finished   0  2  1  3 

cnt 23 
log.log_started    2  3  0  1 
log.log_worked_ms  8 14 77 55 
log.log_finished   2  3  1  0 

cnt 24 
log.log_started    0  1  2  3 
log.log_worked_ms 41 72 47 34 
log.log_finished   1  2  0  3 

cnt 25 
log.log_started    1  2  3  0 
log.log_worked_ms 32 53 42 19 
log.log_finished   1  2  0  3 

cnt 26 
log.log_started    0  1  2  3 
log.log_worked_ms 12 64 29 97 
log.log_finished   3  1  2  0 

cnt 27 
log.log_started    3  0  1  2 
log.log_worked_ms 40 31 10 77 
log.log_finished   3  0  1  2 

cnt 28 
log.log_started    2  3  0  1 

Build log

Last list only when xCC_MAP_FLAGS includes -report

10:11:23 **** Incremental Build of configuration Default for project _kode24_xcore200_1q2020 ****
xmake CONFIG=Default all 
Checking build modules
Using build modules: module_random
Analyzing random_init.c
Analyzing main.xc
Analyzing random.xc
Creating dependencies for random.xc
Creating dependencies for main.xc
Creating dependencies for random_init.c
Compiling random_init.c
Compiling main.xc
Compiling random.xc
Rebuild .build/_obj.rsp
Creating _kode24_xcore200_1q2020.xe
Constraint check for tile[0]:
  Cores available:            8,   used:          3 .  OKAY
  Timers available:          10,   used:          3 .  OKAY
  Chanends available:        32,   used:         11 .  OKAY
  Memory available:       262144,   used:      12224 .  OKAY
    (Stack: 1724, Code: 9494, Data: 1006)
Constraints checks PASSED.
Build Complete

10:11:26 Build Finished (took 2s.240ms)

makefile

# The TARGET variable determines what target system the application is
# compiled for. It either refers to an XN file in the source directories
# or a valid argument for the --target option when compiling
TARGET = xCORE-200-EXPLORER

# The APP_NAME variable determines the name of the final .xe file. It should
# not include the .xe postfix. If left blank the name will default to
# the project name
APP_NAME = _kode24_xcore200_1q2020

# The USED_MODULES variable lists other module used by the application.
USED_MODULES = module_random

# The flags passed to xcc when building the application
# You can also set the following to override flags for a particular language:
# xCC_xC_FLAGS, xCC_C_FLAGS, xCC_ASM_FLAGS, xCC_CPP_FLAGS
# If the variable xCC_MAP_FLAGS is set it overrides the flags passed to
# xcc for the final link (mapping) stage.
xCC_FLAGS = -O2 -g

# The xCORE_ARM_PROJECT variable, if set to 1, configures this
# project to create both xCORE and ARM binaries.
xCORE_ARM_PROJECT = 0

# _kode24_xcore200_1q2020.txt appears in .build
xCC_MAP_FLAGS = -Xmapper --map -Xmapper _kode24_xcore200_1q2020.txt -report

# The VERBOSE variable, if set to 1, enables verbose output from the make system.
VERBOSE = 0

XMOS_MAKE_PATH ?= ../..
-include $(XMOS_MAKE_PATH)/xcommon/module_xcommon/build/Makefile.common

Code: terminating and restarting worker_task

Search for worker_task and see what I have done for xC testing. I added a variable workeing in worker_task, that terminates it when one session is over. It is then restarted again in worker_task_scheduler. This shows that a par can run from other par. This version compiles to cores:6, timers:6, chanends:11. Now worker_task cannot be [[combinable]], same for worker_task_scheduler and the four par components there. It has a high cost compared to cores:3, timers:3, chanends:11above. However, the compiler/mapper still does a great job in just adding 3 cores. I assume that the par components in worker_task_scheduler are mapped as the same cores that work_task.

/*
* _xc_test_4_2020.xc
*
*  Created on: 9. apr. 2020
*      Author: teig
*/

#include <platform.h> // core
#include <stdio.h>    // printf
#include <timer.h>    // delay_milliseconds(..), XS1_TIMER_HZ etc
#include <random.h>   // xmos. Also uses "random_conf.h"
#include <iso646.h>   // readability

// ---
// Control printing
// See https://stackoverflow.com/questions/1644868/define-macro-for-debug-printing-in-c
// ---

#define DEBUG_PRINT_TEST 1 // [0->1] code about [5,12] kB
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

// ---
// Define bool
// BOOLEAN #include <stdbool.h> if C99
// See https://www.teigfam.net/oyvind/home/technology/165-xc-code-examples/#bool
// ---

typedef enum {false,true} bool; // 0,1 This typedef matches any integer-type type like long, int,
                                // signed, unsigned, char, bool

// ---
// Define type equal to the width of xC "timer". This processor has 10 HW timers,
// but the numbers needed in this code will (with NUM_WORKERS 4) be 2 timers if
// all worker_task run on the same logical core (par [[combine]]) or 5 timers
// if worker_task each have a logical core for themselves.
// Both signed and unsigned (always int) will do, since both will wrap around on
// "overflow" and the hex code will look the same. This way AFTER is well defined
// since adding a value will trigger "timerafter" ticks into the future
// ---

typedef signed time32_t; // Ticks to 100 in 1 us

// ---
// Define number of workers. Is needed here because variable length arrays
// are not permitted to tasks when they are [[combinable]]
// ---

#define NUM_WORKERS 4

// ---
// Define data typedefs
// ---

typedef unsigned worked_ms_t;

typedef struct log_t {
    unsigned    cnt;
    unsigned    log_started  [NUM_WORKERS];
    unsigned    log_finished [NUM_WORKERS];
    worked_ms_t log_worked_ms[NUM_WORKERS];
    bool        button_pressed;
} log_t;

// ---
// do_print_log
// Prints log if DEBUG_PRINT_TEST is 1. If DEBUG_PRINT_TEST is 0, this function
// is not generated by the compiler
// ---

void do_print_log (
        log_t log,
        unsigned const num_workers) {

    debug_print ("\ncnt %u %s\n", log.cnt, log.button_pressed ? "BUTTON" : "");
    debug_print ("%s", "log.log_started   ");
    for (unsigned ix=0; ix < num_workers; ix++) {
        debug_print ("%2u ", log.log_started[ix]);
    }
    debug_print ("%s", "\nlog.log_worked_ms ");
    for (unsigned ix=0; ix < num_workers; ix++) {
        debug_print ("%2u ", log.log_worked_ms[ix]);
    }
    debug_print ("%s", "\nlog.log_finished  ");
    for (unsigned ix=0; ix < num_workers; ix++) {
        debug_print ("%2u ", log.log_finished[ix]);
    }
    debug_print ("%s", "\n");
}

// ---
// 1 BIT PORT
// External button defined (button press pulls a pullup resistor down)
// Board's buttons 4E.0 and 4E.1 could have been used, bit want to show 1-bit port
// ---

in buffered port:1 inP1_button = on tile[0]: XS1_PORT_1M; // External HW GPIO J1 P63

#define BUTTON_PRESSED  0
#define BUTTON_RELEASED 1

// ---
// 4 BIT PORT
// Internal LEDs defined. High is "on"
// ---

out buffered port:4 outP4_leds = on tile[0]: XS1_PORT_4F; // xCORE-200 explorerKIT GPIO J1 P5, P3, P1

#define BOARD_LEDS_INIT           0x00
#define BOARD_LED_MASK_GREEN_ONLY 0x01 // BIT0
#define BOARD_LED_MASK_RGB_BLUE   0x02 // BIT1
#define BOARD_LED_MASK_RGB_GREEN  0x04 // BIT2
#define BOARD_LED_MASK_RGB_RED    0x08 // BIT3

#define BOARD_LED_MASK_MAX_1 (BOARD_LED_MASK_GREEN_ONLY)
#define BOARD_LED_MASK_MAX_2 (BOARD_LED_MASK_RGB_BLUE  bitor BOARD_LED_MASK_MAX_1)
#define BOARD_LED_MASK_MAX_3 (BOARD_LED_MASK_RGB_GREEN bitor BOARD_LED_MASK_MAX_2)
#define BOARD_LED_MASK_MAX_4 (BOARD_LED_MASK_RGB_RED   bitor BOARD_LED_MASK_MAX_3)

#define BOARD_LED_MASK_MAX BOARD_LED_MASK_MAX_4 // _1, _2, _3 or _4

// ---
// do_swipe_leds
// Sets LEDs on the xCORE-200 explorerKIT board. There are two, one green only
// and one RGB (with three lines). High is LED on
// ---

void do_swipe_leds (
        out buffered port:4 outP4_leds,
        unsigned &?led_bits, // '&' is reference. Aside: pointer types:
                             // no decoration (safe), "movable", "alias" and  "unsafe"
        unsigned const board_led_mask_max) {

    if (isnull(led_bits)) { // Just to show a nullable type, shown with '?':
        outP4_leds <: BOARD_LED_MASK_GREEN_ONLY;
    } else {
        outP4_leds <: led_bits; // Output LED bits

        led_bits++;
        led_bits and_eq board_led_mask_max; // GREEN on and off and 3-coloured RGB LED
    }
}

// ---
// round_cnt_task
// Task that just outputs an incremented value, showing use of a chan
// This takes two chanends and one logical core.
// Plus one timer, for some reason TODO
// ---

// if [[distributable]] error message from compiler here
void round_cnt_task (chanend c_cnt) { // chans are untyped in xC (but interface is typed++)
    unsigned cnt = 0;
    while (true) {
        cnt++;
        // Synchronous, blocking, no buffer overflow ever possible since there is no buffer:
        c_cnt <: cnt;
    }
}

// if [[distributable]] error message from compiler here
// Not used task, but does the same as the task above
void round_cnt_task_2 (chanend c_cnt) { // chans are untyped in xC (but interface is typed++)
    unsigned cnt = 0;
    timer    tmr;
    time32_t time_ticks; // Ticks to 100 in 1 us

    tmr :> time_ticks; // Now
    while (true) {
        select { // The case passively waits on an event:
            case tmr when timerafter (time_ticks) :> time_ticks : {
                // time_tics always updated, so timerafter is always ready
                cnt++;
                // Synchronous, blocking, no buffer overflow ever possible since there is no buffer:
                c_cnt <: cnt;
            } break;
        }
    }
}

// ---
// An interface is implemented by chanends, locks, calls or safe patterns set
// up by the code generation. The particular _transaction_ pattern below enables
// the compiler to set up that particular asynchronous pattern, based on
// synchronous, blocking primitives.
// ---

typedef interface worker_if_t {
                            void        async_work_request (void);
    [[notification]] slave  void        finished_work (void);
    [[clears_notification]] worked_ms_t get_work_result (void);
} worker_if_t;

// ---
// worker_task
// NUM_WORKERS of these are started. They may share a logical core when
// [[combine]] par or run on NUM_WORKERS logical cores if no [[combine]].
// The pattern starts with async_work_request and then simulates work for
// some time, then sends a [[notification]] of finished_work and then the
// clients responds with get_work_result which [[clears_notification]].
// The compiler will insert the correct code to allow only that pattern.
// ---

//[[combinable]]
void worker_task (
        server worker_if_t i_worker,
        const unsigned index_of_server) {

    timer       tmr;
    time32_t    time_ticks; // Ticks to 100 in 1 us
    bool        doCollectData = false;
    worked_ms_t sim_work_ms = 0;
    unsigned    random_seed = random_create_generator_from_seed(index_of_server); // xmos
    unsigned    random_work_delay_ms;
    bool        working = true;

    debug_print ("worker_task %u\n", index_of_server);

    while (working) {
        select { // Each case passively waits on an event:
            case i_worker.async_work_request () : {
                doCollectData = true;
                random_work_delay_ms = random_get_random_number (random_seed) % 100; // [0..99]
                sim_work_ms = random_work_delay_ms;
                tmr :> time_ticks; // Immediately
                time_ticks += (sim_work_ms * XS1_TIMER_KHZ); // Simulate work
            } break;
            case (doCollectData == true) => tmr when timerafter (time_ticks) :> void : {
                // Now we have simulated that picking up log.log_worked_ms took random_work_delay_ms
                doCollectData = false;
                i_worker.finished_work();
            } break;
            case i_worker.get_work_result (void) -> worked_ms_t worked_ms : {
                worked_ms = sim_work_ms;
                working = false;
            } break;
        }
    }
}

// ---
// client_task
// Asks for work from NUM_WORKERS worker_task (service requested
// in different sequences) and results from workers, when they arrive, handled.
// Each interface call is blocking and synchronous, but the net result of the
// pattern is asynchronous worker_task assignments.
// Log, a button and LEDs handled.
// ---

[[combinable]]
void client_task (
        client worker_if_t i_worker[NUM_WORKERS],
        in buffered port:1 inP1_button,
        out buffered port:4 outP4_leds,
        chanend c_cnt) {

    timer    tmr;
    time32_t time_ticks; // Ticks to 100 in 1 us
    bool     expect_notification_nums = 0;

    // xmos. Pseudorandom, so will look the same on and after each start-up
    unsigned random_seed = random_create_generator_from_seed(1);

    unsigned random_number;
    log_t    log;
    bool     allow_button = false;
    bool     button_current_val = BUTTON_RELEASED;
    unsigned led_bits; // Init below..

    led_bits = BOARD_LEDS_INIT; // ..here to avoid "not used" if "null" used instead
    log.button_pressed = false;

    debug_print ("%s", "client_task\n");

    tmr :> time_ticks;
    time_ticks += (1 * XS1_TIMER_HZ); // 1 second before first timerafter

    while (true) {
        select { // Each case passively waits on an event:
            case (expect_notification_nums == 0) => tmr when timerafter (time_ticks) :> void : {
                random_number = random_get_random_number (random_seed); // Just trying to start randomly

                // Start as [0,1,2,3], [3,0,1,2], [2,3,0,1], [1,2,3,0]:
                for (unsigned ix=0; ix < NUM_WORKERS; ix++) {
                    unsigned random_worker = random_number % NUM_WORKERS; // Inside [0..(NUM_WORKERS-1)]
                    i_worker[random_worker].async_work_request();
                    // Now log.log_started in random sequence
                    random_number++; // Next (but modulo NUM_WORKERS above)

                    log.log_started[ix] = random_worker;
                }
                expect_notification_nums = NUM_WORKERS;
                // === Do something else while all worker_task work ===
            } break;
            case (expect_notification_nums > 0) => i_worker[unsigned index_of_server].finished_work() : {

                // Server async_work_request entries protected by code and scheduler until this is run:
                log.log_worked_ms[index_of_server] = i_worker[index_of_server].get_work_result();
                // async_work_request is not allowed again before the above line is run,
                // by compiler and code

                expect_notification_nums--;

                log.log_finished[expect_notification_nums] = index_of_server;
                if (expect_notification_nums == 0) {
                    select { // Nested select
                        case c_cnt :> log.cnt: {} break;
                    }
                    do_print_log (log, NUM_WORKERS); // Only if DEBUG_PRINT_TEST is 1
                    do_swipe_leds (outP4_leds, led_bits, BOARD_LED_MASK_MAX); // led_bits may be "null"
                    // === Process received log.log_worked_ms, or just.. ===
                    tmr :> time_ticks; // ..repeat immediately
                    allow_button = (log.cnt >= 10);
                } else {}
            } break;
            case allow_button => inP1_button when pinsneq(button_current_val) :> button_current_val: {
                // I/O pin changed value
                // Debouncing not done (best done in separate task, with its own timerafter)
                log.button_pressed = (button_current_val == BUTTON_PRESSED); // May not reach do_print_log
            } break;
        }
    }
}

void worker_task_scheduler (server worker_if_t i_worker[NUM_WORKERS]) { // cores,timers,chanends 6,6,11

    par {
        while (true) { // Probably mapped on the same core as this:
            worker_task (i_worker[0], 0);
        }
        while (true) {
            worker_task (i_worker[1], 1);
        }
        while (true) {
            worker_task (i_worker[2], 2);
        }
        while (true) {
            worker_task (i_worker[3], 3);
        }
    }
}

void worker_task_scheduler_ (server worker_if_t i_worker[NUM_WORKERS]) { // cores,timers,chanends 6,6,11

    while (true) {
        par (unsigned ix = 0; ix < NUM_WORKERS; ix++) {
            worker_task (i_worker[ix], ix);
        }
    }
}

// ---
// main
// Starts tasks
// ---

int main() {
    worker_if_t i_worker[NUM_WORKERS];
    chan c_cnt;

    par {
        worker_task_scheduler (i_worker);
        client_task (i_worker, inP1_button, outP4_leds, c_cnt);
        round_cnt_task (c_cnt);

    }
    return 0;
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.