diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..986ea58 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,43 @@ +pipeline { + agent { + label 'x86&&macOS&&Apps' + } + environment { + VIEW = 'random' + REPO = 'lib_random' + } + options { + skipDefaultCheckout() + } + stages { + stage('Get view') { + steps { + prepareAppsSandbox("${VIEW}", "${REPO}") + } + } + stage('Library checks') { + steps { + xcoreLibraryChecks("${REPO}") + } + } + stage('Build') { + steps { + dir("${REPO}") { + xcoreAllAppsBuild('examples') + xcoreAllAppNotesBuild('examples') + } + } + } + stage('Test') { + steps { + runXmostest("${REPO}", "tests") + } + } + } + post { + always { + archiveArtifacts artifacts: "${REPO}/**/*.*", fingerprint: true + cleanWs() + } + } +} diff --git a/examples/app_dieharder/Makefile b/examples/app_dieharder/Makefile new file mode 100755 index 0000000..8f7774b --- /dev/null +++ b/examples/app_dieharder/Makefile @@ -0,0 +1,10 @@ +TARGET = STARTKIT +APP_NAME = +USED_MODULES = lib_random + +XCC_FLAGS = -Os -g -fcmdline-buffer-bytes=100 +#XCC_FLAGS += -save-temps -v + + +XMOS_MAKE_PATH ?= ../.. +-include $(XMOS_MAKE_PATH)/xcommon/module_xcommon/build/Makefile.common diff --git a/examples/app_dieharder/README b/examples/app_dieharder/README new file mode 100644 index 0000000..41214db --- /dev/null +++ b/examples/app_dieharder/README @@ -0,0 +1,11 @@ +This is a wrapper for testing the quality of the PRNG using dieharder. +http://webhome.phy.duke.edu/~rgb/General/dieharder.php + +It is slow as it runs the random_prng.h library code under simulation. + +If this is a problem, copy the lfsr57(), lfsr88(), & lfsr113() functions +into a host application and run nativly, viz test just the algorithm! + +The expected usage is: + axe --args bin/dieharder.xe -g prng57 -n -1 -o - -B | dieharder -g 200 -a +(see dieharder.xe and dieharder help files for details) diff --git a/examples/app_dieharder/src/gen.xc b/examples/app_dieharder/src/gen.xc new file mode 100755 index 0000000..de67dfe --- /dev/null +++ b/examples/app_dieharder/src/gen.xc @@ -0,0 +1,135 @@ +// Copyright (c) 2017, XMOS Ltd, All rights reserved + +// The expected usage is for validating against 'dieharder' +// axe --args bin/dieharder.xe -g prng57 -n -1 -o - -B | dieharder -g 200 -a + +// We want raw stdio.h, not the safe version. +#define UNSAFE_LIBC +#include +#include +#include +#include +#include +#include "random_prng.h" + +static const char*unsafe prngString(PrngSize prng) { + unsafe { + switch (prng) { + case prng57: return "prng57"; + case prng88: return "prng88"; + case prng113: return "prng113"; + default: return "prngInvalid"; + } + } +} + +unsigned readArgs(unsigned argC, char*unsafe argV[], + PrngSize& prng, unsigned& numValues, const char*unsafe& filename, unsigned& binary) { + unsigned found_g = 0; + unsigned found_n = 0; + unsigned found_o = 0; + binary = 0; + + if (argC == 7 || argC == 8) { + for(unsigned i=1; i` set\n"); + printf (" and make sure you pass commandline arg using `xsim --args ...` or `axe --args ...`\n"); + } + else { + printf ("Usage: `axe --args %s -g -n -o [-B]`\n", argV[0]); + printf (" -g see random_prng.h and PrngSize for details;\n"); + printf (" -n the number of 32bit values to generate, or -1 (with -B) for continuous;\n"); + printf (" -o output file name, or '-' for stdout;\n"); + printf (" -B to turn on binary output, otherwise output will be in ASCII dieharder format.\n"); + } + return 0; +} + +FILE*unsafe openOutFile(const char*unsafe filename, unsigned numValues, unsigned prng, unsigned binary) { + FILE*unsafe fp = (strcmp(filename, "-") == 0) ? stdout : + fopen(filename, binary? "wb":"w"); + if (!fp) + printf ("Unable to open output file %s\n", filename); + else if (!binary) { + fprintf(fp, "#==================================================================\n"); + fprintf(fp, "# generator random_prng.h %s seed=null\n", prngString(prng)); + fprintf(fp, "#==================================================================\n"); + fprintf(fp, "type: d\n"); + fprintf(fp, "count: %u\n", numValues); + fprintf(fp, "numbit: 32\n"); + } + return fp; +} + +int main(unsigned argC, char*unsafe argV[argC]) { + PrngSize prng = 0; + unsigned numValues = 0; + const char*unsafe filename; + unsigned binary; + unsafe { + if (!readArgs(argC, argV, prng, numValues, filename, binary)) + return 1; + } + FILE*unsafe fp = openOutFile(filename, numValues, prng, binary); + + //interface random_pool rpi[1]; + par { + //random_pool_server(rpi, 1, bitsToPoolSize(1)); // We wont be using it. + { + interface random_prng prngi; + par { + [[distribute]] + random_prng_server(prngi, null, prng57, null); + // random_prng_server(prngi, rpi[0], prng57, null); + { + // Client task. + while (numValues) { + enum{blkLen=1000}; + uint32_t values[blkLen]; + unsigned len = (blkLen < numValues)? blkLen : numValues; + prngi.value(values, len); + if (binary) + fwrite(values, len, sizeof(uint32_t), fp); + else + for (unsigned i=0; i < len; ++i) + fprintf(fp, "%10u\n", values[i]); + if (!binary || numValues != (unsigned)(-1)) + numValues -= len; + } + fclose(fp); + prngi.release(); + } + } + //rpi[0].release(); + } + } + return 0; +} diff --git a/lib_random/api/random.h b/lib_random/api/random.h deleted file mode 100644 index 72efee2..0000000 --- a/lib_random/api/random.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2016-2017, XMOS Ltd, All rights reserved -#ifndef __RANDOM_H__ -#define __RANDOM_H__ - -#include -#include - -#ifndef REFERENCE_PARAM -#ifdef __XC__ -#define REFERENCE_PARAM(type, name) type &name -#else -#define REFERENCE_PARAM(type, name) type *name -#endif -#endif - -/** Type representing a random number generator. - */ -typedef unsigned random_generator_t; - -/** Function that creates a random number generator from a seed. - * - * \param seed seed for the generator. - * - * \returns a random number generator. - */ -random_generator_t random_create_generator_from_seed(unsigned seed); - - -/** Function that attempts to create a random number generator from - * a true random value into the seed, using - * an asynchronous timer. To use this function you must enable the - * ``RANDOM_ENABLE_HW_SEED`` define in your application's ``random_conf.h``. - * - * \returns a random number generator. - */ -random_generator_t random_create_generator_from_hw_seed(void); - -/** Function that produces a random number. The number has a cycle of 2^32 - * and is produced using a LFSR. - * - * \param g the used generator to produce the seed. - * - * \returns a random 32 bit number. - */ -unsigned -random_get_random_number(REFERENCE_PARAM(random_generator_t, g)); - -void random_get_random_bytes(REFERENCE_PARAM(random_generator_t, g), uint8_t in_buffer[], size_t byte_count); - -#endif // __RANDOM_H__ diff --git a/lib_random/api/random_bit.h b/lib_random/api/random_bit.h new file mode 100755 index 0000000..0aaf87b --- /dev/null +++ b/lib_random/api/random_bit.h @@ -0,0 +1,60 @@ +// Copyright (c) 2017, XMOS Ltd, All rights reserved +#ifndef __RANDOM_BIT_H__ +#define __RANDOM_BIT_H__ + +#include +#include + +#ifndef REFERENCE_PARAM +#ifdef __XC__ +#define REFERENCE_PARAM(type, name) type &name +#else +#define REFERENCE_PARAM(type, name) type *name +#endif +#endif + + +/** The random_bit.h library is NOT thread safe. + * + * There is one oscillator per tile, hence there may be one user of the library per tile. + * However, on a multi-tile device, each tile may have one thread calling the library. + */ + + +/** Functions used to claim and release the random_bit library. + * + * As is only one oscillator per tile, there may only be one user of the + * library per tile. + * + * random_bit_claim() returns 0 if the library has already been claimed on + * this tile. + * + * N.B. Ownership of the library does not prevent others from calling it! + */ +int random_bit_claim(); +void random_bit_release(); + +/** Functions that start & stop the random number generator. + * + * Calling this function will start/stop the free running oscillator. + * A running oscillator will increase power consumption by a few mW. + * + * N.B. There is no checking that the caller has previously claimed the library. + */ +void random_bit_start(); +void random_bit_stop(); + +/** Function that produces a random bit. + * + * If random bits are available, then it returns '1' and the random bit. + * + * If no random bits are available, then it returns 0 and the absolute + * time in ticks at which a bit will be available. + * The calling code may then wait until this time (for example in a select statement), + * recalling the function when the bit will be available. + * + * N.B. There is no checking that the caller has previously claimed the library. + */ +uint32_t random_bit(REFERENCE_PARAM(uint32_t, bit_time)); + +#endif // __RANDOM_BIT_H__ diff --git a/lib_random/api/random_pool.h b/lib_random/api/random_pool.h new file mode 100755 index 0000000..6ed6f2f --- /dev/null +++ b/lib_random/api/random_pool.h @@ -0,0 +1,74 @@ +// Copyright (c) 2017, XMOS Ltd, All rights reserved +#ifndef __RANDOM_POOL_H__ +#define __RANDOM_POOL_H__ + +#include +#include +#include "random_impl.h" + +/** Interface for clients to request bits from a random pool. + * + * Multiple clients, situated on any tile, may receive bits from a server. + * There may be only one server per tile. + * + * The server task calls random_bit_claim() for the tile. + * If the claim fails, the task will exit immediately. + */ +interface random_pool { +/** Method that returns the maximum bits a pool can hold. */ + size_t capacity(); + +/** Method that returns the number of bits available in the pool. */ + size_t available(); + +/** Method that returns the approximate period until `bits` will be available. + * + * Clients may then callback or sleep whilst waiting for the pool to fill. + * The client will need to do multiple `fetch()` followed by waits if + * there are multiple clients calling `fetch() or if `bits` > `capacity()`. + */ + uint32_t timeUntil(size_t bits); + +/** Method that overwrites `value[ bitPos : bitPos+numBits ]` with random bits. + * + * The bit range will be truncated to fit within value[]. + * + * \returns The number of bits overwritten, starting at 'bitPos'. + * This will be the same as numBits if the bits are available. + */ + size_t insert(uint32_t& value, size_t bitPos, size_t numBits); + +/** Method exits the random_pool server task when all clients have released. + * + * This allows a combined-par to exit if/when the client owning tasks exits. + * random_bit_release() will be called, allowing another task to claim it. + */ + void release(); +}; + +/** Type represents an opaque bit pool size. */ +typedef size_t PoolSize_t; + +/** Helper function for turning the minimum number of bits into a `PoolSize_t` opaque value. + * + * N.B. As `PoolSize_t` values are opaque, they must be generated via this function! + * PoolSize_t bitsToPoolSize(size_t bits); + */ +#define bitsToPoolSize(bits) _bitsToPoolSize(bits) + +/** Server task that collects random bits into a pool. + * + * The server task may be combined with other tasks in a combined-par statement. + * There may be one server task per tile (as there is only one random_bit generator per tile). + * Clients may then request random bits from the sever task's pool via the `interface random_pool`. + * There may be multiple clients, situated on a mixture of tile. + * + * \param rpi Server end of an `interface random_pool`. + * \param n Number of `client interface random_pool`s being served. + * \param poolSize Size of random bit pool to create - N.B. use bitsToPoolSize() to generate the value. + */ +[[combinable]] +void random_pool_server(server interface random_pool rpi[n], static const size_t n, + static const PoolSize_t poolSize); + +#endif // __RANDOM_POOL_H__ diff --git a/lib_random/api/random_prng.h b/lib_random/api/random_prng.h new file mode 100755 index 0000000..f45b375 --- /dev/null +++ b/lib_random/api/random_prng.h @@ -0,0 +1,70 @@ +// Copyright (c) 2017, XMOS Ltd, All rights reserved +#ifndef __RANDOM_PRNG_H__ +#define __RANDOM_PRNG_H__ + +#include +#include +#include "random_impl.h" +#include "random_pool.h" + + +/** Interface for a client to request pseudo-random numbers. */ +interface random_prng { + +/** Method that returns a pseudo-random number. + * + * ====== WARNING ==================================================== + * The PRNG is a simple CRC and hence is not cryptographically secure. + * Exposing a value outside of the chip exposes the entire sequence. + * See random_csprng for how to hash the PRNG to make it secure. + * + * If n > 1, the PRNG will be called mulitple times to retrived 32bit values. + */ + void value(uint32_t values[n], size_t n); + +/** The following methods perturbe the PRNG with bits from a random_pool of true random bits. + * Each returns the numbe of bits actually taken from the random_pool. + */ + size_t perturbe_nonBlocking(size_t numBits); // Returns the numBits actually used (available). + size_t perturbe_blocking(size_t numBits); // Blocks until 'numBits' are available to use. + size_t perturbe_halfAvailableBits(); // Uses half the available bits in the pool. + +/** Method exits the random_prng server task. + * + * This allows a combined-par to exit if/when the client owning task exits. + * N.B. The user owns the random_pool client (passed to the server) and + * they must call `rpi.release()` when they have finished with it. + */ + void release(); +}; + +/** A type that specifies the repeat range for the PRNG + * + * There are 3 choices offering 32bit sequences of ~2^57, ~2^88 and ~2^113 in length. + * The processing time will increase with the PrngSize. + * The enumerated value is the number of uint32_t required to 'seed' the generator. + */ +typedef enum {prng57=2, prng88=3, prng113=4} PrngSize; + +/** Server task that generates the pseudo-random number. + * + * This is a 1:1 relationship. + * The call to perturbe_blocking() will 'sleep' both the client and server. + * Being distributable, the server-code will be inlined into the client task. + * + * N.B. Using the same client (PRNG stream) for muliple consumers will result in + * the consumers recieving correlated numbers (shared state). + * If correlation is an issue, consumers must instantiate their own + * server-client pair with a unique seed. + * + * \param prngi Server end of an `interface random_PRNG`. + * \param rpi Client end of an `interface random_pool`, null if perturbe_*() is never called. + * \param prngSize Either 'prng57', 'prng88' or 'prng113'. + * \param seed Optional seed for the PRNG (or null) + * N.B. each seed[] entry must be > 127, the default value is 128. + */ +[[distributable]] +void random_prng_server(server interface random_prng prngi, client interface random_pool ?rpi, + static const PrngSize prngSize, uint32_t (&?seed)[prngSize]); + +#endif // __RANDOM_PRNG_H__ diff --git a/lib_random/doc/rst/random.rst b/lib_random/doc/rst/random.rst index 57919d2..ce74172 100644 --- a/lib_random/doc/rst/random.rst +++ b/lib_random/doc/rst/random.rst @@ -4,18 +4,19 @@ API --- To use the module you need to use ``lib_random`` in your application and -include the ``random.h`` header. - - - +include only one of the ``api`` header files. +The three APIs are nested, hence you should choose the one that gives you +the required level of functionality. +In most cases, you will want to be using the top level, ``random_prng.h``. +The top level is built upon the lower level, hence access to entropy. +For alternative PRNG see stdlib.h which includes: + rand, srand, rand_r, drand48, erand48, jrand48, lcong48, lrand48, + mrand48, nrand48, seed48, srand48. |appendix| Known Issues ------------ -There are no known issues with this library. - - .. include:: ../../../CHANGELOG.rst diff --git a/lib_random/module_build_info b/lib_random/module_build_info old mode 100644 new mode 100755 index 2c8f3cc..d2d3c17 --- a/lib_random/module_build_info +++ b/lib_random/module_build_info @@ -1,14 +1,5 @@ -# You can set flags specifically for your module by using the MODULE_XCC_FLAGS -# variable. So the following -# -# MODULE_XCC_FLAGS = $(XCC_FLAGS) -O3 -# -# specifies that everything in the modules should have the application -# build flags with -O3 appended (so the files will build at -# optimization level -O3). -# -# You can also set MODULE_XCC_C_FLAGS, MODULE_XCC_XC_FLAGS etc.. - MODULE_XCC_FLAGS = -g -Os +#MODULE_XCC_FLAGS += -save-temps -v -VERSION = 1.0.0 +DEPENDENT_MODULES = lib_locks lib_xassert +VERSION = 1.1.0 diff --git a/lib_random/src/random.xc b/lib_random/src/random.xc deleted file mode 100644 index 2bbc8ff..0000000 --- a/lib_random/src/random.xc +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2016-2017, XMOS Ltd, All rights reserved -#include "random.h" -#include - -static const unsigned random_poly = 0xEDB88320; - -unsigned random_get_random_number(random_generator_t &g) -{ - crc32(g, -1, random_poly); - return (unsigned) g; -} - -void random_get_random_bytes(random_generator_t &g, uint8_t in_buffer[], size_t byte_count) -{ - for (int i=0; i < byte_count; i++) - { - in_buffer[i] = (uint8_t)random_get_random_number(g); - } -} - -random_generator_t random_create_generator_from_seed(unsigned seed) -{ - random_generator_t gen = (random_generator_t) seed; - (void) random_get_random_number(gen); - return gen; -} - -static const unsigned XS1_L_RING_OSCILLATOR_VALUE_REG = 0x070B; - -random_generator_t random_create_generator_from_hw_seed(void) -{ - unsigned init_seed = getps(XS1_L_RING_OSCILLATOR_VALUE_REG); - return random_create_generator_from_seed(init_seed); -} - diff --git a/lib_random/src/random_bit.xc b/lib_random/src/random_bit.xc new file mode 100755 index 0000000..cd7f748 --- /dev/null +++ b/lib_random/src/random_bit.xc @@ -0,0 +1,79 @@ +// Copyright (c) 2017, XMOS Ltd, All rights reserved +#include "random_bit.h" +#include "hwlock.h" +#include "random_impl.h" + +#if __XS2__ +# include "xs2a_registers.h" +#else +# include "xs1b_registers.h" +# define XS1_PS_RING_OSC_CTRL XS1_L_PS_RING_OSC_CTRL +# define XS1_PS_RING_OSC_DATA0 XS1_L_PS_RING_OSC_DATA0 +#endif + +// Optional thread protection... + +#define LOAD32(dst, ptr) asm("ldw %0, %1[0]" : "=r"(dst) : "r"(ptr)); +#define STORE32(src, ptr) asm("stw %0, %1[0]" : : "r"(src), "r"(ptr)); + +extern unsigned __libc_hwlock; +static int per_tile_available = 1; + +int random_bit_claim() { + int available; + hwlock_acquire(__libc_hwlock); + LOAD32(available, &per_tile_available); + if (available) { + int newValue = 0; + STORE32(newValue, &per_tile_available); + } + hwlock_release(__libc_hwlock); + return available; +} + +void random_bit_release() { + int newValue = 1; + STORE32(newValue, &per_tile_available); +} + + +// None thread safe code... + +static int per_tile_last_time = 0; + +void random_bit_start() { + int last_time; + timer tmr; + tmr :> last_time; + STORE32(last_time, &per_tile_last_time); + setps(XS1_PS_RING_OSC_CTRL, 2); +} + +void random_bit_stop() { + setps(XS1_PS_RING_OSC_CTRL, 0); +} + +uint32_t random_bit(uint32_t &bit_time) { + int last_time; + LOAD32(last_time, &per_tile_last_time); + timer tmr; + uint32_t time; + tmr :> time; + // If the timer wraps, we will miss the opportunity - tough! + // N.B. unsigned wrapping has defined behaviour. + if (time - last_time > TIME_FOR_ONE_BIT) { + random_bit_stop(); + asm("nop"); + asm("nop"); + asm("nop"); + asm("nop"); // Allow the data to stabilise. + bit_time = getps(XS1_PS_RING_OSC_DATA0); + random_bit_start(); + bit_time &=1; + return 1; + } + else { + bit_time = last_time + TIME_FOR_ONE_BIT; + return 0; + } +} diff --git a/lib_random/src/random_impl.h b/lib_random/src/random_impl.h new file mode 100755 index 0000000..1311e7a --- /dev/null +++ b/lib_random/src/random_impl.h @@ -0,0 +1,20 @@ +// Copyright (c) 2017, XMOS Ltd, All rights reserved +#ifndef __RANDOM_IMPL_H__ +#define __RANDOM_IMPL_H__ + +#include + +// Used by random_bit() & random_pool_server() +#define TIME_FOR_ONE_BIT 20000 + +// N.B. _rp_impl_type is NOT part of the interface. +// as part of the implementation it may change at any time. +typedef uint8_t _rp_impl_type; +// random_pool_server() : assert(sizeof(uint32_t) >= sizeof(_rp_impl_type) && "memcpy needs fixing"); + + +// N.B. The implementation requires an extra bit for indexing efficiency, +// hence we do NOT use `(b+(N-1))/N` rounding. +#define _bitsToPoolSize(bits) (((bits) / (sizeof(_rp_impl_type)*8)) + 1) + +#endif // __RANDOM_IMPL_H__ diff --git a/lib_random/src/random_init.c b/lib_random/src/random_init.c deleted file mode 100644 index a9a9a4b..0000000 --- a/lib_random/src/random_init.c +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2016-2017, XMOS Ltd, All rights reserved -#include "random.h" -#include - -static const unsigned XS1_L_RING_OSCILLATOR_CONTROL_REG = 0x060B; -static const unsigned XS1_L_RING_OSCILLATOR_CONTROL_START = 0x3; - -__attribute__((constructor)) -void random_simple_init_seed() -{ -/* This constructor starts of the ring oscillator when the program loads. - This will run on an asynchronous time base to the main xCORE. By starting it - off now the later call to random_create_generator_from_hw_seed will pick up - a value later which has drifted to a random state */ - setps(XS1_L_RING_OSCILLATOR_CONTROL_REG, - XS1_L_RING_OSCILLATOR_CONTROL_START); -} diff --git a/lib_random/src/random_pool.xc b/lib_random/src/random_pool.xc new file mode 100755 index 0000000..14ca39b --- /dev/null +++ b/lib_random/src/random_pool.xc @@ -0,0 +1,172 @@ +// Copyright (c) 2017, XMOS Ltd, All rights reserved + +#include "random_pool.h" +#include +#include +#include "xassert.h" +#include "random_bit.h" + +// We always want xassert(e) to trap. +#if !(XASSERT_ENABLE_ASSERTIONS0) +# undef xassert +# define xassert(e) do { if (!(e)) __builtin_trap();} while(0) +#endif + +// Internally, we use bit indexes into the pool (head, tail etc). +// To make head/tail handling easier and more efficient: +// head==tail means empty; +// we will always have at least one bit 'empty'. +// See _bitsToPoolSize() macro for extra bit calculation. + +static inline size_t poolSizeInBits(static const PoolSize_t poolSize) { + return poolSize * (sizeof(_rp_impl_type)*8); +} + +static inline size_t poolSizeCapacity(static const PoolSize_t poolSize) { + return poolSizeInBits(poolSize) - 1; // see comment above. +} + +static inline size_t available(size_t head, size_t tail, static const PoolSize_t poolSize) { + if (tail > head) + head += poolSizeInBits(poolSize); + return head - tail; +} + +// incrementor for bit position +static inline void incrementPos(size_t& pos, static const PoolSize_t poolSize) { + ++pos; + if (pos == poolSizeInBits(poolSize)) + pos = 0; +} + +// Convert the `head` and `tail` bit positions into `_rp_impl_type[]` accessors. +static inline size_t indexOffset(size_t pos) { + return pos / (sizeof(_rp_impl_type)*8); +} +static inline size_t bitOffset(size_t pos) { + return pos % (sizeof(_rp_impl_type)*8); +} + +static inline _rp_impl_type bitMask(size_t numBits) { + return (1ULL << numBits) - 1; +} + +static inline uint32_t poolFill(size_t& head, size_t& tail, + _rp_impl_type pool[poolSize], static const PoolSize_t poolSize) { + uint32_t bit_time; + while (random_bit(bit_time)) { + // xor the bit at 'head'. + pool[indexOffset(head)] ^= (bit_time << bitOffset(head)); + incrementPos(head, poolSize); + if (head == tail) { // We have a full pool. + // Throw away old bits and keep the newest (always one bit empty). + incrementPos(tail, poolSize); + } + } + return bit_time; // Time when next bit available. +} + +static inline void xorInsert(uint32_t& value, size_t bitPos, size_t numBits, + size_t& tail, _rp_impl_type pool[poolSize], static const PoolSize_t poolSize ) { + // asserted (bitPos+numBits <= 32), bits will map onto a single uint32_t 'value'. + + // Initialise the pool access variables, we will update them in the loop. + size_t tailIndex = indexOffset(tail); + size_t tailBit = bitOffset(tail); // zero on subsequent passes. + + // Update 'tail' assuming we will remove 'numBits'. + // asserted (numBits<=available() && numBits < poolSizeInBits), so we wont need to worry about head. + tail += numBits; + if (tail >= poolSizeInBits(poolSize)) + tail -= poolSizeInBits(poolSize); + + while (numBits) { + size_t allotted = (sizeof(_rp_impl_type)*8) - tailBit; // Initially grab all top bits... + size_t invalidTop = 0; // so we don't discard any top bits. + if (allotted > numBits) { + // We don't need them all (or they are not available). + allotted = numBits; + invalidTop = (sizeof(_rp_impl_type)*8) - (tailBit + allotted); + } + _rp_impl_type v = pool[tailIndex]; + v <<= invalidTop; // Mask top bits. + v >>= (tailBit + invalidTop); // Mask bottom bits. + v <<= bitPos; // Position bits ready for insertion. + value ^= v; // xor-ing does not affect the randomness quality. + + numBits -= allotted; + if (numBits) { + // Update loop variables. + bitPos += allotted; + ++tailIndex; + if (tailIndex == poolSize) + tailIndex = 0; + tailBit = 0; + } + } +} + +[[combinable]] +void random_pool_server(server interface random_pool rpi[numClients], static const size_t numClients, + static const PoolSize_t poolSize) { + if (!random_bit_claim()) { + xassert(0); // "You can have only one random_pool_server task per tile." + } + + // We will release when the last client has released us. + // All clients have implicitly claimed us, set the 'numClients' lowest bits. + xassert(numClients <= 32); // "The maximum number of clients is 32". + uint32_t activeClients = (1ULL< size_t numBits: + numBits = poolSizeCapacity(poolSize); + break; + + case rpi[unsigned id].available() -> size_t numBits: + numBits = available(head, tail, poolSize); + break; + + case rpi[unsigned id].timeUntil(size_t numBits) -> uint32_t period: + if (numBits > poolSizeCapacity(poolSize)) + numBits = poolSizeCapacity(poolSize); + size_t bits = available(head, tail, poolSize); + period = (numBits < bits)? 0 : (numBits - bits) * TIME_FOR_ONE_BIT; + break; + + case rpi[unsigned id].insert(uint32_t& value, size_t bitPos, size_t numBits) -> size_t bits: + if (bitPos > 31) + numBits = 0; + if (numBits > sizeof(uint32_t)*8 - bitPos) + numBits = sizeof(uint32_t)*8 - bitPos; + size_t avail = available(head, tail, poolSize); + bits = (numBits > avail)? avail : numBits; + if (bits) { + uint32_t local = value; // Can't use remote-references as argument :-( + xorInsert(local, bitPos, bits, tail, pool, poolSize); + value = local; + } + break; + + case rpi[unsigned id].release(): + activeClients &= ~(1UL << id); + if (!activeClients) { + random_bit_release(); + return; + } + break; + + case tmr when timerafter(time) :> void: + time = poolFill(head, tail, pool, poolSize); + break; + } + } +} diff --git a/lib_random/src/random_prng.xc b/lib_random/src/random_prng.xc new file mode 100755 index 0000000..dbc5166 --- /dev/null +++ b/lib_random/src/random_prng.xc @@ -0,0 +1,166 @@ +// Copyright (c) 2017, XMOS Ltd, All rights reserved + +#include "random_prng.h" +#include +#include +#include "xassert.h" +#include "random_pool.h" + +// We always want xassert(e) to trap. +#if !(XASSERT_ENABLE_ASSERTIONS0) +# undef xassert +# define xassert(e) do { if (!(e)) __builtin_trap();} while(0) +#endif + +// From: P. L'Ecuyer, "Maximally Equidistributed Combined Tausworthe Generators" +// Mathematics of Computation, 65, 213 (1996), 203--213: +// www.iro.umontreal.ca/~lecuyer/myftp/papers/tausme.ps +static uint32_t quickTaus(uint32_t state, uint32_t c, + uint32_t q, uint32_t s, uint32_t k_s) { + return (((state << q) ^ state) >> k_s) ^ ((state & c) << s); +} +#define QT_PARAMS(k,q,s) (uint32_t)(-(1UL<<(32-k))),q,s,(k-s) +// state[] seeds must be >= 2^(32-k) +#define QT_DEFAULT_SEED 128 + +// period ~ 2^57 +// N.B. seed with: state[0] > 7, state[1] > 15 +static uint32_t lfsr57(uint32_t state[2]) { + state[0] = quickTaus(state[0], QT_PARAMS(29, 2,18) ); + state[1] = quickTaus(state[1], QT_PARAMS(28, 9,14) ); + return state[0] ^ state[1]; +} + +// period ~ 2^88 +// N.B. seed with: state[0] > 1, state[1] > 7, state[2] > 15 +static uint32_t lfsr88(uint32_t state[3]) { + state[0] = quickTaus(state[0], QT_PARAMS(31,13,12) ); + state[1] = quickTaus(state[1], QT_PARAMS(29, 2, 4) ); + state[2] = quickTaus(state[2], QT_PARAMS(28, 3,17) ); + return state[0] ^ state[1] ^ state[2]; +} + +// period ~2^113 +// N.B. seed with: state[0] > 1, state[1] > 7, state[2] > 15, state[3] > 127 +static uint32_t lfsr113(uint32_t state[4]) { + // Alternative values of the QT_PARAMS 's' are listed in table 1 of: + // Tables Of Maximally Equidistributed Combined LFSR Generators: Pierre L'ecuyer + // www.ams.org/mcom/1999-68-225/S0025-5718-99-01039-X/S0025-5718-99-01039-X.pdf + state[0] = quickTaus(state[0], QT_PARAMS(31, 6,18) ); + state[1] = quickTaus(state[1], QT_PARAMS(29, 2, 2) ); + state[2] = quickTaus(state[2], QT_PARAMS(28,13, 7) ); + state[3] = quickTaus(state[3], QT_PARAMS(25, 3,13) ); + return state[0] ^ state[1] ^ state[2] ^ state[3]; +} + +static inline void sleepUntil(size_t numBits, client interface random_pool rpi) { + timer tmr; + uint32_t time; + tmr :> time; + time += rpi.timeUntil(numBits); + tmr when timerafter(time) :> void; +} + +static size_t perturbe(uint32_t state[prngSize], static const size_t prngSize, + size_t numBits, size_t& perturbeIndex, client interface random_pool rpi) { + + // Truncate large numbers. + const size_t maxBits = prngSize * (sizeof(uint32_t)*8); + if (numBits > maxBits) + numBits = maxBits; + + // Initialise state access variables, we will update them in the loop. + size_t bitsInserted = 0; // What the caller wants to know. + // These three variables track where 'perturbeIndex' needs to be set to. + size_t index = perturbeIndex / (sizeof(uint32_t)*8); + size_t bitPos = perturbeIndex % (sizeof(uint32_t)*8); + size_t inserted = 0; + + while (numBits) { + size_t tailBits = (sizeof(uint32_t)*8) - bitPos; + if (tailBits > numBits) + tailBits = numBits; + + inserted = rpi.insert(state[index], bitPos, tailBits); + if (state[index] < QT_DEFAULT_SEED) { + // N.B. The value of the state must be >= 2^(32-k) viz 'QT_DEFAULT_SEED' + // But the rpi pool may be empty. + state[index] ^= 0xffffffff; // Will this break our PRNG? + } + bitsInserted += inserted; + numBits -= inserted; + + if (numBits && // More to do. + inserted == tailBits) { // The pool is not empty. + // We need to move onto the next state[] value. + ++index; + if (index == prngSize) + index = 0; + bitPos = 0; + } + // else index, bitPos, inserted give where we got up to. + } + + perturbeIndex = (index * (sizeof(uint32_t)*8)) + bitPos + inserted; + return bitsInserted; +} + +[[distributable]] +void random_prng_server(server interface random_prng prngi, client interface random_pool ?rpi, + static const PrngSize prngSize, uint32_t (&?seed)[prngSize]) { + + xassert(prngSize >= prng57 && prngSize <= prng113); // "Invalid prngSize". + + size_t perturbeIndex = 0; // Track the next bit to be peturbed. + uint32_t state[prngSize]; // Can't initialise distributable variables. Why not? + if (!isnull(seed)) { + for (int i=0; i= QT_DEFAULT_SEED); // "Invalid seed". + memcpy(state, seed, prngSize * sizeof(uint32_t)); + } + else { + memset(state, QT_DEFAULT_SEED, prngSize * sizeof(uint32_t)); + } + + while(1) { + select { + case prngi.value(uint32_t values[n], size_t n) : + switch (prngSize) { + case prng57: + for (size_t i = 0; i < n; ++i) + values[i] = lfsr57(state); + break; + case prng88: + for (size_t i = 0; i < n; ++i) + values[i] = lfsr88(state); + break; + case prng113: + for (size_t i = 0; i < n; ++i) + values[i] = lfsr113(state); + break; + } + break; + + case prngi.perturbe_nonBlocking(size_t numBits) -> size_t bitsUsed: + bitsUsed = perturbe(state, prngSize, numBits, perturbeIndex, rpi); + break; + + case prngi.perturbe_blocking(size_t numBits) -> size_t bitsUsed: + bitsUsed = numBits; + while (numBits) { + numBits -= perturbe(state, prngSize, numBits, perturbeIndex, rpi); + if (numBits) + sleepUntil(numBits, rpi); // N.B. Only one clients wanting to block. + } + break; + + case prngi.perturbe_halfAvailableBits() -> size_t bitsUsed: + bitsUsed = rpi.available() / 2; // We round down, so we can never use all bits. + (void) perturbe(state, prngSize, bitsUsed, perturbeIndex, rpi); + break; + + case prngi.release(): + return; + } + } +} diff --git a/test/random_bit/Makefile b/test/random_bit/Makefile new file mode 100755 index 0000000..85cb9fd --- /dev/null +++ b/test/random_bit/Makefile @@ -0,0 +1,10 @@ +TARGET = SLICEKIT-L16 +APP_NAME = +USED_MODULES = lib_random + +XCC_FLAGS = -Os -g + +VERBOSE = 0 + +XMOS_MAKE_PATH ?= ../.. +-include $(XMOS_MAKE_PATH)/xcommon/module_xcommon/build/Makefile.common diff --git a/test/random_bit/expected.output b/test/random_bit/expected.output new file mode 100644 index 0000000..1364fd4 --- /dev/null +++ b/test/random_bit/expected.output @@ -0,0 +1,6 @@ +testClaimSimple-1 done. +testClaimSimple-0 done. +testClaimingParallel-1 done. +testClaimingParallel-0 done. +testBit-1 done. +testBit-0 done. diff --git a/test/random_bit/src/test.xc b/test/random_bit/src/test.xc new file mode 100755 index 0000000..1776e6b --- /dev/null +++ b/test/random_bit/src/test.xc @@ -0,0 +1,199 @@ +// Copyright (c) 2017, XMOS Ltd, All rights reserved + +#include +#include +#include +#include +#include "random_bit.h" + +///////////////////////////////////////////////////////// +// First some unit testing boiler plate +interface checker { + void str(const char str[100]); + void release(); +}; +static void checkerServer(server interface checker si[n], static const size_t n, uint32_t timeout) { + // We will release when the last client has released us. + // All clients have implicitly claimed us, set the 'n' lowest bits. + if (n > 32) { + fprintf(stderr, "Error checkerServer: The maximum number of clients is 32\n"); + return; + } + uint32_t activeClients = (1ULL< start; + + while(1){ + select{ + case si[unsigned id].str(const char str[100]) : + char local[100]; + memcpy(local,str,100); + fprintf(stderr, "%s\n", local); + break; + case si[unsigned id].release(): + activeClients &= ~(1UL << id); + if (!activeClients) + return; + break; + case tmr when timerafter(start + timeout) :> void: + fprintf(stderr, "Error checkerServer: timout after %d tick, activeClients mask=0x%X\n", timeout, activeClients); + exit(1); + break; + } + } +} + +#define INFO(ci, ...) \ + do { \ + char _checker_buff[100]; \ + snprintf(_checker_buff, 100, __VA_ARGS__); \ + ci.str(_checker_buff); \ + } while(0) + +#define CHECK(c, ci, ...) \ + if (!(c)) INFO(ci, __VA_ARGS__) + +///////////////////////////////////////////////////////// + +// Use 'TIME_FOR_ONE_BIT' from random_impl.h +#include "../../lib_random/src/random_impl.h" +#define UNCERTAINTY 200 + +static void sleep(uint32_t n) { + timer tmr; + uint32_t now; + tmr :> now; + tmr when timerafter(now+n) :> void; +} + +void testClaimSimple(client interface checker ci, int id) { + CHECK( random_bit_claim() == 1, ci, "testClaimSimple-%d step 1", id ); + random_bit_release(); + CHECK( random_bit_claim() == 1, ci, "testClaimSimple-%d step 2", id ); + CHECK( random_bit_claim() == 0, ci, "testClaimSimple-%d step 3", id ); + random_bit_release(); + INFO(ci, "testClaimSimple-%d done.", id); + ci.release(); +} + +static int canClaim(uint32_t n) { + sleep(n); + if (random_bit_claim()) { + sleep(100); // keep hold for a while. + random_bit_release(); + return 1; + } + return 0; +} + +void testClaimingParallel(client interface checker ci, int id) { + int ret[6]; + par { + // Only one logical core on a tile can claim. + ret[0] = canClaim(0); + ret[1] = canClaim(0); + ret[2] = canClaim(0); + // Once released (after 100), another logical core can claim (after 200) + ret[3] = canClaim(200); + ret[4] = canClaim(200); + ret[5] = canClaim(200); + } + CHECK( ret[0]+ret[1]+ret[2] == 1, // One of them succeeds. + ci, "testClaimingParallel-%d now", id ); + CHECK( ret[3]+ret[4]+ret[5] == 1, // One of them succeeds. + ci, "testClaimingParallel-%d delayed", id ); + INFO(ci, "testClaimingParallel-%d done.", id); + ci.release(); +} + +void testBit_notReady(client interface checker ci, int id, int step, uint32_t expectedTime) { + uint32_t time; + CHECK( random_bit(time) == 0, + ci, "testBit-%d step 1 should not be ready", id ); + CHECK( expectedTime-UNCERTAINTY < time && + expectedTime+UNCERTAINTY > time, + ci,"testBit-%d step %d expected time %d +- %d, acutal time %d\n", id, step, expectedTime, UNCERTAINTY, time); +} + +void testBit_ready(client interface checker ci, int id, int step) { + uint32_t bit; + CHECK( random_bit(bit) == 1, + ci, "testBit-%d step %d should be ready", id ,step ); + // We don't currently CHECK the bit - need to feed this into the simulator's "getps(0x70B)". +} + +void testBit(client interface checker ci, int id) { + // We don't need to call random_bit_claim() ... even if it is bad style :0 + timer tmr; + uint32_t t; + + random_bit_start(); + tmr :> t; + // Check at start. + t += TIME_FOR_ONE_BIT; // We expect it to be ready in 'TIME_FOR_ONE_BIT' ticks. + testBit_notReady(ci, id, 1, t); + + sleep(TIME_FOR_ONE_BIT/2); + + // Check at half way point. + testBit_notReady(ci, id, 2, t); + + sleep(TIME_FOR_ONE_BIT/2); + + // Check at ready time. + testBit_ready(ci, id, 3); + tmr :> t; // Time till next bit from when we read previous bit. + t += TIME_FOR_ONE_BIT; // We expect next bit ready in 'TIME_FOR_ONE_BIT' ticks from now. + + // Check next bit is not ready. + testBit_notReady(ci, id, 4, t); + + // Sleep extra long. + sleep(TIME_FOR_ONE_BIT*2); + + // We expect only one bit ready. + testBit_ready(ci, id, 5); + tmr :> t; // Time till next bit from when we read previous bit. + t += TIME_FOR_ONE_BIT; // We expect next bit ready in 'TIME_FOR_ONE_BIT' ticks from now. + + // Check next bit is not ready. + testBit_notReady(ci, id, 6, t); + + // stopping and starting will reset the time until ready. + sleep(TIME_FOR_ONE_BIT*2); + random_bit_stop(); + random_bit_start(); + tmr :> t; + t += TIME_FOR_ONE_BIT; // We expect it to be ready in 'TIME_FOR_ONE_BIT' ticks. + testBit_notReady(ci, id, 7, t); + + random_bit_stop(); + INFO(ci, "testBit-%d done.", id); + ci.release(); +} + +int main() { + interface checker i[6]; + par { + on tile[0] : checkerServer(i,6, TIME_FOR_ONE_BIT*6); + + on tile[0] : { + testClaimSimple(i[0], 0); + // Followed by. + testClaimingParallel(i[1] ,0); + } + on tile[0] : testBit(i[2], 0); + + // The tests on each tile are independant. + on tile[1] : { + testClaimSimple(i[3], 1); + // Followed by. + testClaimingParallel(i[4], 1); + } + on tile[1] : testBit(i[5], 1); + } + return 0; +} \ No newline at end of file diff --git a/test/random_pool/Makefile b/test/random_pool/Makefile new file mode 100644 index 0000000..fbb3bc9 --- /dev/null +++ b/test/random_pool/Makefile @@ -0,0 +1,11 @@ +TARGET = SLICEKIT-L16 +APP_NAME = +USED_MODULES = lib_random + +XCC_FLAGS = -Os -g +#XCC_FLAGS += -save-temps -v + +VERBOSE = 0 + +XMOS_MAKE_PATH ?= ../.. +-include $(XMOS_MAKE_PATH)/xcommon/module_xcommon/build/Makefile.common diff --git a/test/random_pool/expected.output b/test/random_pool/expected.output new file mode 100644 index 0000000..8717b1c --- /dev/null +++ b/test/random_pool/expected.output @@ -0,0 +1,6 @@ +testPool-3 done. +testPool-4 done. +testPool-5 done. +testPool-0 done. +testPool-1 done. +testPool-2 done. diff --git a/test/random_pool/src/test.xc b/test/random_pool/src/test.xc new file mode 100755 index 0000000..4c0be42 --- /dev/null +++ b/test/random_pool/src/test.xc @@ -0,0 +1,201 @@ +// Copyright (c) 2017, XMOS Ltd, All rights reserved + +#include +#include +#include +#include +#include "random_pool.h" + +///////////////////////////////////////////////////////// +// First some unit testing boiler plate +interface checker { + void str(const char str[100]); + void release(); +}; +static void checkerServer(server interface checker si[n], static const size_t n, uint32_t timeout) { + // We will release when the last client has released us. + // All clients have implicitly claimed us, set the 'n' lowest bits. + if (n > 32) { + fprintf(stderr, "Error checkerServer: The maximum number of clients is 32\n"); + return; + } + uint32_t activeClients = (1ULL< start; + + while(1){ + select{ + case si[unsigned id].str(const char str[100]) : + char local[100]; + memcpy(local,str,100); + fprintf(stderr, "%s\n", local); + break; + case si[unsigned id].release(): + activeClients &= ~(1UL << id); + if (!activeClients) + return; + break; + case tmr when timerafter(start + timeout) :> void: + fprintf(stderr, "Error checkerServer: timout after %d tick, activeClients mask=0x%X\n", timeout, activeClients); + exit(1); + break; + } + } +} + +#define INFO(ci, ...) \ + do { \ + char _checker_buff[100]; \ + snprintf(_checker_buff, 100, __VA_ARGS__); \ + ci.str(_checker_buff); \ + } while(0) + +#define CHECK(c, ci, ...) \ + if (!(c)) INFO(ci, __VA_ARGS__) + +///////////////////////////////////////////////////////// + +// Use 'TIME_FOR_ONE_BIT' from random_impl.h +#include "../../lib_random/src/random_impl.h" +#define DELAY 500 + +static void delay() { + timer tmr; + uint32_t now; + tmr :> now; + tmr when timerafter(now+DELAY) :> void; +} + +void testPool_timeuntil(client interface checker ci, int id, + client interface random_pool rpi, size_t numBits, size_t poolBits) { + uint32_t time = rpi.timeUntil(numBits); + + // How many bit are we waiting for. + size_t reportedBits = (numBits > rpi.capacity())? rpi.capacity() : numBits; + reportedBits = (reportedBits now; + testPool_timeuntil(ci, id, rpi, 0, 0); + testPool_timeuntil(ci, id, rpi, 1, 0); + testPool_timeuntil(ci, id, rpi, 2, 0); + testPool_timeuntil(ci, id, rpi, 99, 0); + + tmr when timerafter(now + TIME_FOR_ONE_BIT) :> void; + CHECK( rpi.available() == 1, + ci, "testPool-%d expected 1, actual %d bits", id, rpi.available() ); + + // Retest, but the periods will be reduced as we have a bit in the pool + testPool_timeuntil(ci, id, rpi, 0, 1); + testPool_timeuntil(ci, id, rpi, 1, 1); + testPool_timeuntil(ci, id, rpi, 2, 1); + testPool_timeuntil(ci, id, rpi, 99, 1); + + tmr when timerafter(now + TIME_FOR_ONE_BIT*2) :> void; + CHECK( rpi.available() == 2, + ci, "testPool-%d expected 2, actual %d bits", id, rpi.available() ); + + delay(); // Allow all cores to check the pool before continuing. + + // Then allow one core to plunder the pool. + if (id==0 || id==3) { + // Retest, but the periods will be reduced as we have 2 bits in the pool + testPool_timeuntil(ci, id, rpi, 0, 2); + testPool_timeuntil(ci, id, rpi, 1, 2); + testPool_timeuntil(ci, id, rpi, 2, 2); + testPool_timeuntil(ci, id, rpi, 99, 2); + + uint32_t bits; + CHECK( rpi.insert(bits, 32, 1) == 0, // Outside of range. + ci, "testPool-%d incorrectly inserted bit32", id); + CHECK( rpi.available() == 2, + ci, "testPool-%d 2-0=2, actual %d bits", id, rpi.available() ); + + CHECK( rpi.insert(bits, 31, 5) == 1, // Truncate to last bit. + ci, "testPool-%d expected bit31 to be filled", id); + // We don't currently CHECK the bit - need to feed this into the simulator's "getps(0x70B)". + CHECK( rpi.available() == 1, + ci, "testPool-%d 2-1=1, actual %d bits", id, rpi.available() ); + + CHECK( rpi.insert(bits, 0, 999) == 1, // Request far too many bits! + ci, "testPool-%d expected only bit0 to be filled", id); + // We don't currently CHECK the bit - need to feed this into the simulator's "getps(0x70B)". + CHECK( rpi.available() == 0, + ci, "testPool-%d 1-1=0, actual %d bits", id, rpi.available() ); + + CHECK( rpi.insert(bits, 0, 1) == 0, // None available. + ci, "testPool-%d expected no bits to be filled", id); + CHECK( rpi.available() == 0, + ci, "testPool-%d 0-0=0, actual %d bits", id, rpi.available() ); + + // Retest now the pool is empty. + testPool_timeuntil(ci, id, rpi, 0, 0); + testPool_timeuntil(ci, id, rpi, 1, 0); + testPool_timeuntil(ci, id, rpi, 2, 0); + testPool_timeuntil(ci, id, rpi, 99, 0); + } + else { + // Wait for the pool to be plundered. + delay(); + + // The bit will have been taken by id 0. + CHECK( rpi.available() == 0, + ci, "testPool-%d pool contains %d bits", id, rpi.available() ); + uint32_t bits; + CHECK( rpi.insert(bits, 32, 1) == 0, // Outside of range. + ci, "testPool-%d bit32 expected no bits to be filled", id); + + CHECK( rpi.insert(bits, 31, 5) == 0, // Truncate to last bit. + ci, "testPool-%d bit31 expected no bits to be filled", id); + + CHECK( rpi.insert(bits, 0, 999) == 0, // Request far too many bits! + ci, "testPool-%d bit0 expected no bits to be filled", id); + + CHECK( rpi.insert(bits, 0, 1) == 0, // None available. + ci, "testPool-%d expected no bits to be filled", id); + } + rpi.release(); + + INFO(ci, "testPool-%d done.", id); + ci.release(); +} + +int main() { + interface checker i[6]; + interface random_pool rpi_0[3]; + interface random_pool rpi_1[3]; + + par { + on tile[0] : checkerServer(i, 6, TIME_FOR_ONE_BIT*3); + + on tile[0] : random_pool_server(rpi_0, 3, bitsToPoolSize(3)); // Rounded to 8. + on tile[0] : testPool(i[0], 0, rpi_0[0]); + on tile[0] : testPool(i[1], 1, rpi_0[1]); + on tile[0] : testPool(i[2], 2, rpi_0[2]); + + on tile[1] : random_pool_server(rpi_1, 3, bitsToPoolSize(11)); // Rounded to 16. + on tile[1] : testPool(i[3], 3, rpi_1[0]); + on tile[1] : testPool(i[4], 4, rpi_1[1]); + on tile[1] : testPool(i[5], 5, rpi_1[2]); + } + return 0; // failure; +} diff --git a/test/random_prng/Makefile b/test/random_prng/Makefile new file mode 100644 index 0000000..526dc6f --- /dev/null +++ b/test/random_prng/Makefile @@ -0,0 +1,11 @@ +TARGET = STARTKIT +APP_NAME = +USED_MODULES = lib_random + +XCC_FLAGS = -Os -g +#XCC_FLAGS += -save-temps -v + +VERBOSE = 0 + +XMOS_MAKE_PATH ?= ../.. +-include $(XMOS_MAKE_PATH)/xcommon/module_xcommon/build/Makefile.common diff --git a/test/random_prng/expected.output b/test/random_prng/expected.output new file mode 100644 index 0000000..c693646 --- /dev/null +++ b/test/random_prng/expected.output @@ -0,0 +1,16 @@ +Task-pool-client0 available=0 capacity=15 +Task-pool-client1 available=0 capacity=15 +subtask-A raw - 16C800B3 279E25 +subtask-A half 0 3589669D 4AF41D37 +Task-pool-client1 available=0 capacity=15 +Exit Task-pool-client1 +subtask-A nonblocking 2 7461669 8ABA72F6 +subtask-A blocking 1 8EDBF15A 2C5DF060 +Exit subtask-A +subtask-B raw - 505F0BD9 35375D85 +subtask-B half 0 783D9EC4 B0C1B1A0 +subtask-B nonblocking 2 47AE1A01 95AB4431 +subtask-B blocking 1 20AB0D85 B0E484A6 +Exit subtask-B +Exit Task-pool-client0 +Exit Task-pool-server diff --git a/test/random_prng/src/test.xc b/test/random_prng/src/test.xc new file mode 100755 index 0000000..65b9eee --- /dev/null +++ b/test/random_prng/src/test.xc @@ -0,0 +1,77 @@ + +#include "random_prng.h" +#include +#include + +#define PRINT(...) printf(__VA_ARGS__) +//#define PRINT(...) + +void test(const char*unsafe name, client interface random_prng prngi) { + uint32_t values[2]; + prngi.value(values, 2); + PRINT("%s raw - %X %X\n",name, values[0], values[1]); + + size_t done = prngi.perturbe_halfAvailableBits(); + prngi.value(values, 2); + PRINT("%s half %d %X %X\n", name, done, values[0], values[1]); + + done = prngi.perturbe_nonBlocking(2); + prngi.value(values, 2); + PRINT("%s nonblocking %d %X %X\n", name, done, values[0], values[1]); + + done = prngi.perturbe_blocking(1); + prngi.value(values, 2); + PRINT("%s blocking %d %X %X\n", name, done, values[0], values[1]); + + // Client prngi is no longer wanted. + prngi.release(); // This will allow the random_prng_server() to exit too. + PRINT("Exit %s\n", name); +} + +int main() { + interface random_pool rpi[2]; + par { + // Task-pool-server + random_pool_server(rpi, 2, bitsToPoolSize(11)); + + // Task-pool-client0 with subtasks. + { + PRINT("Task-pool-client0 available=%d capacity=%d\n", rpi[0].available(), rpi[0].capacity()); + uint32_t seed[prng57] = {1234,5678}; + interface random_prng prngi; + // subtask-A. + par { + [[distribute]] + random_prng_server(prngi, rpi[0], prng57, seed); + test("subtask-A", prngi); + } + // Follow with a sequential subtask-B. + // We can reuse the prngi interface object to start a new server & client. + // N.B. BUT ONLY IF THE INTERFACE IS THE SAME TYPE - both 'distribute' or not!! + par { + [[distribute]] + random_prng_server(prngi, rpi[0], prng113, null); // Use the default seed + test("subtask-B", prngi); + } + // Client rpi[0] is no longer wanted. + rpi[0].release(); // This will allow the random_pool_server() to exit too. + PRINT("Exit Task-pool-client0\n"); + } + + // Task-pool-client1 + { + PRINT("Task-pool-client1 available=%d capacity=%d\n", rpi[1].available(), rpi[1].capacity()); + timer tmr; + uint32_t time; + tmr :> time; + tmr when timerafter(time+20000) :> void; + PRINT("Task-pool-client1 available=%d capacity=%d\n", rpi[1].available(), rpi[1].capacity()); + // Client rpi[1] is no longer wanted. + rpi[1].release(); // This will allow the random_pool_server() to exit too. + PRINT("Exit Task-pool-client1\n"); + } + } + PRINT("Exit Task-pool-server\n"); + return 0; +} + diff --git a/test/runtests.py b/test/runtests.py new file mode 100755 index 0000000..07130d4 --- /dev/null +++ b/test/runtests.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +import xmostest, sys, subprocess + +if __name__ == "__main__": + xmostest.init() + xmostest.register_group("lib_random", "lib_random_tests", "lib_random library tests", "Tests random_bit.h, random_pool.h and random_prng.h") + xmostest.runtests() + xmostest.finish() + diff --git a/test/test_random_bit.py b/test/test_random_bit.py new file mode 100755 index 0000000..d593f10 --- /dev/null +++ b/test/test_random_bit.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import xmostest + +def runtest(): + resources = xmostest.request_resource("xsim") + + tester = xmostest.ComparisonTester(open('random_bit/expected.output'), + 'lib_random', + 'lib_random_tests', + 'random_bit', {}) + + xmostest.run_on_simulator(resources['xsim'], + 'random_bit/bin/random_bit.xe', + tester=tester) + diff --git a/test/test_random_pool.py b/test/test_random_pool.py new file mode 100755 index 0000000..3781d9d --- /dev/null +++ b/test/test_random_pool.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import xmostest + +def runtest(): + resources = xmostest.request_resource("xsim") + + tester = xmostest.ComparisonTester(open('random_pool/expected.output'), + 'lib_random', + 'lib_random_tests', + 'random_pool', {}) + + xmostest.run_on_simulator(resources['xsim'], + 'random_pool/bin/random_pool.xe', + tester=tester) + diff --git a/test/test_random_prng.py b/test/test_random_prng.py new file mode 100755 index 0000000..8fa443c --- /dev/null +++ b/test/test_random_prng.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import xmostest + +def runtest(): + resources = xmostest.request_resource("xsim") + + tester = xmostest.ComparisonTester(open('random_prng/expected.output'), + 'lib_random', + 'lib_random_tests', + 'random_prng', {}) + + xmostest.run_on_simulator(resources['xsim'], + 'random_prng/bin/random_prng.xe', + tester=tester) +