Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/host.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ include (${CMAKE_DIR}/no_hardware.cmake)
pico_add_subdirectory(${HOST_DIR}/pico_divider)
pico_add_subdirectory(${HOST_DIR}/pico_multicore)
pico_add_subdirectory(${HOST_DIR}/pico_platform)
pico_add_subdirectory(${HOST_DIR}/pico_rand)
pico_add_subdirectory(${HOST_DIR}/pico_runtime)
pico_add_subdirectory(${HOST_DIR}/pico_printf)
pico_add_subdirectory(${HOST_DIR}/pico_stdio)
Expand Down
13 changes: 13 additions & 0 deletions src/host/pico_rand/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package(default_visibility = ["//visibility:public"])

cc_library(
name = "pico_rand",
srcs = ["rand.c"],
hdrs = ["include/pico/rand.h"],
includes = ["include"],
target_compatible_with = ["//bazel/constraint:host"],
deps = [
"//src/host/hardware_sync",
"//src/host/hardware_timer",
],
)
12 changes: 12 additions & 0 deletions src/host/pico_rand/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pico_add_library(pico_rand)

target_sources(pico_rand INTERFACE
${CMAKE_CURRENT_LIST_DIR}/rand.c
)

target_include_directories(pico_rand_headers SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)

pico_mirrored_target_link_libraries(pico_rand INTERFACE
hardware_timer
hardware_sync
)
89 changes: 89 additions & 0 deletions src/host/pico_rand/include/pico/rand.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#ifndef _PICO_RAND_H
#define _PICO_RAND_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/** \file pico/rand.h
* \defgroup pico_rand pico_rand
*
* \brief Random Number Generator API
*
* This module generates random numbers at runtime utilizing a number of possible entropy
* sources and uses those sources to modify the state of a 128-bit 'Pseudo
* Random Number Generator' implemented in software.
*
* The random numbers (32 to 128 bit) to be supplied are read from the PRNG which is used
* to help provide a large number space.
*
* The following (multiple) sources of entropy are available (of varying quality), each enabled by a \#define:
*
* - Time (\ref PICO_RAND_ENTROPY_SRC_TIME == 1): The 64-bit microsecond timer is mixed in each time.
*
* \note All entropy sources are hashed before application to the PRNG state machine.
*
* The \em first time a random number is requested, the 128-bit PRNG state
* must be seeded. Multiple entropy sources are also available for the seeding operation:
*
* - Time (\ref PICO_RAND_SEED_ENTROPY_SRC_TIME == 1): The 64-bit microsecond timer is mixed into the seed.
*
*/

// ---------------
// ENTROPY SOURCES
// ---------------

// PICO_CONFIG: PICO_RAND_ENTROPY_SRC_TIME, Enable/disable use of hardware timestamp as an entropy source, type=bool, default=1, group=pico_rand
#ifndef PICO_RAND_ENTROPY_SRC_TIME
#define PICO_RAND_ENTROPY_SRC_TIME 1
#endif

// --------------------
// SEED ENTROPY SOURCES
// --------------------

// PICO_CONFIG: PICO_RAND_SEED_ENTROPY_SRC_TIME, Enable/disable use of hardware timestamp as an entropy source for the random seed, type=bool, default=PICO_RAND_ENTROPY_SRC_TIME, group=pico_rand
#ifndef PICO_RAND_SEED_ENTROPY_SRC_TIME
#define PICO_RAND_SEED_ENTROPY_SRC_TIME PICO_RAND_ENTROPY_SRC_TIME
#endif

// We provide a maximum of 128 bits entropy in one go
typedef struct rng_128 {
uint64_t r[2];
} rng_128_t;

/*! \brief Get 128-bit random number
* \ingroup pico_rand
*
* \param rand128 Pointer to storage to accept a 128-bit random number
*/
void get_rand_128(rng_128_t *rand128);

/*! \brief Get 64-bit random number
* \ingroup pico_rand
*
* \return 64-bit random number
*/
uint64_t get_rand_64(void);

/*! \brief Get 32-bit random number
* \ingroup pico_rand
*
* \return 32-bit random number
*/
uint32_t get_rand_32(void);

#ifdef __cplusplus
}
#endif

#endif
161 changes: 161 additions & 0 deletions src/host/pico_rand/rand.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/

/* xoroshiro128ss(), rotl():

Written in 2018 by David Blackman and Sebastiano Vigna ([email protected])

To the extent possible under law, the author has dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.

See <http://creativecommons.org/publicdomain/zero/1.0/>

splitmix64() implementation:

Written in 2015 by Sebastiano Vigna ([email protected])
To the extent possible under law, the author has dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.

See <http://creativecommons.org/publicdomain/zero/1.0/>
*/

#include "pico/rand.h"
#if PICO_RAND_ENTROPY_SRC_TIME
#include "hardware/timer.h"
#endif
#include "hardware/sync.h"

static bool rng_initialised = false;

// Note: By design, do not initialise any of the variables that hold entropy,
// they may have useful junk in them, either from power-up or a previous boot.
static rng_128_t rng_state;

/* From the original source:

This is a fixed-increment version of Java 8's SplittableRandom generator
See http://dx.doi.org/10.1145/2714064.2660195 and
http://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html

It is a very fast generator passing BigCrush, and it can be useful if
for some reason you absolutely want 64 bits of state; otherwise, we
rather suggest to use a xoroshiro128+ (for moderately parallel
computations) or xorshift1024* (for massively parallel computations)
generator.

Note: This can be called with any value (i.e. including 0)
*/
static __noinline uint64_t splitmix64(uint64_t x) {
uint64_t z = x + 0x9E3779B97F4A7C15ull;
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ull;
z = (z ^ (z >> 27)) * 0x94D049BB133111EBull;
return z ^ (z >> 31);
}

/* From the original source:

This is xoroshiro128** 1.0, one of our all-purpose, rock-solid,
small-state generators. It is extremely (sub-ns) fast and it passes all
tests we are aware of, but its state space is large enough only for
mild parallelism.

For generating just floating-point numbers, xoroshiro128+ is even
faster (but it has a very mild bias, see notes in the comments).

The state must be seeded so that it is not everywhere zero. If you have
a 64-bit seed, we suggest to seed a splitmix64 generator and use its
output to fill s.
*/
static inline uint64_t rotl(const uint64_t x, int k) {
return (x << k) | (x >> (64 - k));
}

static __noinline uint64_t xoroshiro128ss(rng_128_t *local_rng_state) {
const uint64_t s0 = local_rng_state->r[0];
uint64_t s1 = local_rng_state->r[1];

// Because the state is *modified* outside of this function, there is a
// 1 in 2^128 chance that it could be all zeroes (which is not allowed).
while (s0 == 0 && s1 == 0) {
s1 = time_us_64(); // should not be 0, but loop anyway
}

const uint64_t result = rotl(s0 * 5, 7) * 9;

s1 ^= s0;
local_rng_state->r[0] = rotl(s0, 24) ^ s1 ^ (s1 << 16); // a, b
local_rng_state->r[1] = rotl(s1, 37); // c

return result;
}

static void initialise_rand(void) {
rng_128_t local_rng_state = local_rng_state;
uint which = 0;

#if PICO_RAND_SEED_ENTROPY_SRC_TIME
// Mix in hashed time. This is [possibly] predictable boot-to-boot
// but will vary application-to-application.
local_rng_state.r[which] ^= splitmix64(time_us_64());
which ^= 1;
#endif

spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_RAND);
uint32_t save = spin_lock_blocking(lock);
if (!rng_initialised) {
(void) xoroshiro128ss(&local_rng_state);
rng_state = local_rng_state;
rng_initialised = true;
}
spin_unlock(lock, save);
}

uint64_t get_rand_64(void) {
if (!rng_initialised) {
// Do not provide 'RNs' until the system has been initialised. Note:
// The first initialisation can be quite time-consuming depending on
// the amount of RAM hashed, see RAM_HASH_START and RAM_HASH_END
initialise_rand();
}

static volatile uint8_t check_byte;
rng_128_t local_rng_state = rng_state;
uint8_t local_check_byte = check_byte;
// Modify PRNG state with the run-time entropy sources,
// hashed to reduce correlation with previous modifications.
uint which = 0;
#if PICO_RAND_ENTROPY_SRC_TIME
local_rng_state.r[which] ^= splitmix64(time_us_64());
which ^= 1;
#endif

spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_RAND);
uint32_t save = spin_lock_blocking(lock);
if (local_check_byte != check_byte) {
// someone got a random number in the interim, so mix it in
local_rng_state.r[0] ^= rng_state.r[0];
local_rng_state.r[1] ^= rng_state.r[1];
}
// Generate a 64-bit RN from the modified PRNG state.
// Note: This also "churns" the 128-bit state for next time.
uint64_t rand64 = xoroshiro128ss(&local_rng_state);
rng_state = local_rng_state;
check_byte++;
spin_unlock(lock, save);

return rand64;
}

void get_rand_128(rng_128_t *ptr128) {
ptr128->r[0] = get_rand_64();
ptr128->r[1] = get_rand_64();
}

uint32_t get_rand_32(void) {
return (uint32_t) get_rand_64();
}
Loading