mirror of
https://github.com/raspberrypi/pico-sdk.git
synced 2025-12-10 07:14:36 +01:00
pico_rand (#2598)
This commit is contained in:
parent
4635b37e68
commit
3f6f0fc0ef
5 changed files with 276 additions and 0 deletions
|
|
@ -31,6 +31,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)
|
||||
|
|
|
|||
13
src/host/pico_rand/BUILD.bazel
Normal file
13
src/host/pico_rand/BUILD.bazel
Normal file
|
|
@ -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
src/host/pico_rand/CMakeLists.txt
Normal file
12
src/host/pico_rand/CMakeLists.txt
Normal file
|
|
@ -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
src/host/pico_rand/include/pico/rand.h
Normal file
89
src/host/pico_rand/include/pico/rand.h
Normal file
|
|
@ -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
src/host/pico_rand/rand.c
Normal file
161
src/host/pico_rand/rand.c
Normal file
|
|
@ -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 (vigna@acm.org)
|
||||
|
||||
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 (vigna@acm.org)
|
||||
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…
Add table
Reference in a new issue