I have one code rather long example (350 lines!-] that I thought would cover more than the simplest. Here is the code as of now. I decided to make a rather untypical xC program with 4 workers and one client so show interface and what combine does to allocation. I also added a task that uses a channel.
If anybody has time on a Saturday to look this over I'd be happy. Also if you find some flaws or some good ideas to add even more flavour to it.
I have three things I cannot explain. Three TODOs:
- Why does why round_cnt_task take a timer?
- Why round_cnt_task could not be [[[distributable]] as it is (wrong structure, like the error says, even if the text there is slightly wrong?). But this has the structure ok:
- Why round_cnt_task_2 (not used) could not be [[[distributable]] as it is (because I use a channel?)
Code: Select all
/*
* main.xc
*
* Created on: 12. feb. 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 http://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, 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 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 int 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)
// -----------------------------------------------------------------------------
in port inP1_button = on tile[0]: XS1_PORT_1M; // External HW GPIO J1 P63 (Board's buttons 4E.0 and 4E.1 could have been used, bit want to show 1-bit port)
#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; // 4-bit port. xCORE-200 explorerKIT GPIO J1 7
#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
// -----------------------------------------------------------------------------
// do_swipe_leds
// Set 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
// -----------------------------------------------------------------------------
// TODO if [[distributable]] error: combinable function must end in a `while(1){select{..}}' or combined `par' statement
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;
}
}
// TODO if [[distributable]] error: select case in a [[distributable]] function which is not on an interface
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;
while (true) {
select {
case tmr when timerafter (time_ticks) :> time_ticks : {
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
// par [[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 (1) {
select {
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 port 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;
unsigned random_seed = random_create_generator_from_seed(1); // xmos. Pseudorandom, so will look the same on and after each start-up
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.cnt = 0;
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 (1) {
select {
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 are 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;
}
}
}
// -----------------------------------------------------------------------------
// main
// Starts 1+NUM_WORKERS tasks, running on 2 or 1+NUM_WORKERS logical cores
// -----------------------------------------------------------------------------
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 (int 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;
}
Code: Select all
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
Code: Select all
10:38:37 **** Incremental Build of configuration Default for project _xmos_issues_xcore200 ****
xmake CONFIG=Default all
Checking build modules
Using build modules: module_random
Analyzing main.xc
Creating dependencies for main.xc
Compiling main.xc
Rebuild .build/_obj.rsp
Creating xmos_issues_xcore200.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: 12208 . OKAY
(Stack: 1724, Code: 9482, Data: 1002)
Constraints checks PASSED.
Build Complete