Skip to content

Commit 3f6f0fc

Browse files
authored
pico_rand (#2598)
1 parent 4635b37 commit 3f6f0fc

File tree

5 files changed

+276
-0
lines changed

5 files changed

+276
-0
lines changed

src/host.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ include (${CMAKE_DIR}/no_hardware.cmake)
3131
pico_add_subdirectory(${HOST_DIR}/pico_divider)
3232
pico_add_subdirectory(${HOST_DIR}/pico_multicore)
3333
pico_add_subdirectory(${HOST_DIR}/pico_platform)
34+
pico_add_subdirectory(${HOST_DIR}/pico_rand)
3435
pico_add_subdirectory(${HOST_DIR}/pico_runtime)
3536
pico_add_subdirectory(${HOST_DIR}/pico_printf)
3637
pico_add_subdirectory(${HOST_DIR}/pico_stdio)

src/host/pico_rand/BUILD.bazel

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
cc_library(
4+
name = "pico_rand",
5+
srcs = ["rand.c"],
6+
hdrs = ["include/pico/rand.h"],
7+
includes = ["include"],
8+
target_compatible_with = ["//bazel/constraint:host"],
9+
deps = [
10+
"//src/host/hardware_sync",
11+
"//src/host/hardware_timer",
12+
],
13+
)

src/host/pico_rand/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
pico_add_library(pico_rand)
2+
3+
target_sources(pico_rand INTERFACE
4+
${CMAKE_CURRENT_LIST_DIR}/rand.c
5+
)
6+
7+
target_include_directories(pico_rand_headers SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
8+
9+
pico_mirrored_target_link_libraries(pico_rand INTERFACE
10+
hardware_timer
11+
hardware_sync
12+
)
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#ifndef _PICO_RAND_H
8+
#define _PICO_RAND_H
9+
10+
#include <stdint.h>
11+
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
/** \file pico/rand.h
17+
* \defgroup pico_rand pico_rand
18+
*
19+
* \brief Random Number Generator API
20+
*
21+
* This module generates random numbers at runtime utilizing a number of possible entropy
22+
* sources and uses those sources to modify the state of a 128-bit 'Pseudo
23+
* Random Number Generator' implemented in software.
24+
*
25+
* The random numbers (32 to 128 bit) to be supplied are read from the PRNG which is used
26+
* to help provide a large number space.
27+
*
28+
* The following (multiple) sources of entropy are available (of varying quality), each enabled by a \#define:
29+
*
30+
* - Time (\ref PICO_RAND_ENTROPY_SRC_TIME == 1): The 64-bit microsecond timer is mixed in each time.
31+
*
32+
* \note All entropy sources are hashed before application to the PRNG state machine.
33+
*
34+
* The \em first time a random number is requested, the 128-bit PRNG state
35+
* must be seeded. Multiple entropy sources are also available for the seeding operation:
36+
*
37+
* - Time (\ref PICO_RAND_SEED_ENTROPY_SRC_TIME == 1): The 64-bit microsecond timer is mixed into the seed.
38+
*
39+
*/
40+
41+
// ---------------
42+
// ENTROPY SOURCES
43+
// ---------------
44+
45+
// PICO_CONFIG: PICO_RAND_ENTROPY_SRC_TIME, Enable/disable use of hardware timestamp as an entropy source, type=bool, default=1, group=pico_rand
46+
#ifndef PICO_RAND_ENTROPY_SRC_TIME
47+
#define PICO_RAND_ENTROPY_SRC_TIME 1
48+
#endif
49+
50+
// --------------------
51+
// SEED ENTROPY SOURCES
52+
// --------------------
53+
54+
// 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
55+
#ifndef PICO_RAND_SEED_ENTROPY_SRC_TIME
56+
#define PICO_RAND_SEED_ENTROPY_SRC_TIME PICO_RAND_ENTROPY_SRC_TIME
57+
#endif
58+
59+
// We provide a maximum of 128 bits entropy in one go
60+
typedef struct rng_128 {
61+
uint64_t r[2];
62+
} rng_128_t;
63+
64+
/*! \brief Get 128-bit random number
65+
* \ingroup pico_rand
66+
*
67+
* \param rand128 Pointer to storage to accept a 128-bit random number
68+
*/
69+
void get_rand_128(rng_128_t *rand128);
70+
71+
/*! \brief Get 64-bit random number
72+
* \ingroup pico_rand
73+
*
74+
* \return 64-bit random number
75+
*/
76+
uint64_t get_rand_64(void);
77+
78+
/*! \brief Get 32-bit random number
79+
* \ingroup pico_rand
80+
*
81+
* \return 32-bit random number
82+
*/
83+
uint32_t get_rand_32(void);
84+
85+
#ifdef __cplusplus
86+
}
87+
#endif
88+
89+
#endif

src/host/pico_rand/rand.c

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
/* xoroshiro128ss(), rotl():
8+
9+
Written in 2018 by David Blackman and Sebastiano Vigna ([email protected])
10+
11+
To the extent possible under law, the author has dedicated all copyright
12+
and related and neighboring rights to this software to the public domain
13+
worldwide. This software is distributed without any warranty.
14+
15+
See <http://creativecommons.org/publicdomain/zero/1.0/>
16+
17+
splitmix64() implementation:
18+
19+
Written in 2015 by Sebastiano Vigna ([email protected])
20+
To the extent possible under law, the author has dedicated all copyright
21+
and related and neighboring rights to this software to the public domain
22+
worldwide. This software is distributed without any warranty.
23+
24+
See <http://creativecommons.org/publicdomain/zero/1.0/>
25+
*/
26+
27+
#include "pico/rand.h"
28+
#if PICO_RAND_ENTROPY_SRC_TIME
29+
#include "hardware/timer.h"
30+
#endif
31+
#include "hardware/sync.h"
32+
33+
static bool rng_initialised = false;
34+
35+
// Note: By design, do not initialise any of the variables that hold entropy,
36+
// they may have useful junk in them, either from power-up or a previous boot.
37+
static rng_128_t rng_state;
38+
39+
/* From the original source:
40+
41+
This is a fixed-increment version of Java 8's SplittableRandom generator
42+
See http://dx.doi.org/10.1145/2714064.2660195 and
43+
http://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html
44+
45+
It is a very fast generator passing BigCrush, and it can be useful if
46+
for some reason you absolutely want 64 bits of state; otherwise, we
47+
rather suggest to use a xoroshiro128+ (for moderately parallel
48+
computations) or xorshift1024* (for massively parallel computations)
49+
generator.
50+
51+
Note: This can be called with any value (i.e. including 0)
52+
*/
53+
static __noinline uint64_t splitmix64(uint64_t x) {
54+
uint64_t z = x + 0x9E3779B97F4A7C15ull;
55+
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ull;
56+
z = (z ^ (z >> 27)) * 0x94D049BB133111EBull;
57+
return z ^ (z >> 31);
58+
}
59+
60+
/* From the original source:
61+
62+
This is xoroshiro128** 1.0, one of our all-purpose, rock-solid,
63+
small-state generators. It is extremely (sub-ns) fast and it passes all
64+
tests we are aware of, but its state space is large enough only for
65+
mild parallelism.
66+
67+
For generating just floating-point numbers, xoroshiro128+ is even
68+
faster (but it has a very mild bias, see notes in the comments).
69+
70+
The state must be seeded so that it is not everywhere zero. If you have
71+
a 64-bit seed, we suggest to seed a splitmix64 generator and use its
72+
output to fill s.
73+
*/
74+
static inline uint64_t rotl(const uint64_t x, int k) {
75+
return (x << k) | (x >> (64 - k));
76+
}
77+
78+
static __noinline uint64_t xoroshiro128ss(rng_128_t *local_rng_state) {
79+
const uint64_t s0 = local_rng_state->r[0];
80+
uint64_t s1 = local_rng_state->r[1];
81+
82+
// Because the state is *modified* outside of this function, there is a
83+
// 1 in 2^128 chance that it could be all zeroes (which is not allowed).
84+
while (s0 == 0 && s1 == 0) {
85+
s1 = time_us_64(); // should not be 0, but loop anyway
86+
}
87+
88+
const uint64_t result = rotl(s0 * 5, 7) * 9;
89+
90+
s1 ^= s0;
91+
local_rng_state->r[0] = rotl(s0, 24) ^ s1 ^ (s1 << 16); // a, b
92+
local_rng_state->r[1] = rotl(s1, 37); // c
93+
94+
return result;
95+
}
96+
97+
static void initialise_rand(void) {
98+
rng_128_t local_rng_state = local_rng_state;
99+
uint which = 0;
100+
101+
#if PICO_RAND_SEED_ENTROPY_SRC_TIME
102+
// Mix in hashed time. This is [possibly] predictable boot-to-boot
103+
// but will vary application-to-application.
104+
local_rng_state.r[which] ^= splitmix64(time_us_64());
105+
which ^= 1;
106+
#endif
107+
108+
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_RAND);
109+
uint32_t save = spin_lock_blocking(lock);
110+
if (!rng_initialised) {
111+
(void) xoroshiro128ss(&local_rng_state);
112+
rng_state = local_rng_state;
113+
rng_initialised = true;
114+
}
115+
spin_unlock(lock, save);
116+
}
117+
118+
uint64_t get_rand_64(void) {
119+
if (!rng_initialised) {
120+
// Do not provide 'RNs' until the system has been initialised. Note:
121+
// The first initialisation can be quite time-consuming depending on
122+
// the amount of RAM hashed, see RAM_HASH_START and RAM_HASH_END
123+
initialise_rand();
124+
}
125+
126+
static volatile uint8_t check_byte;
127+
rng_128_t local_rng_state = rng_state;
128+
uint8_t local_check_byte = check_byte;
129+
// Modify PRNG state with the run-time entropy sources,
130+
// hashed to reduce correlation with previous modifications.
131+
uint which = 0;
132+
#if PICO_RAND_ENTROPY_SRC_TIME
133+
local_rng_state.r[which] ^= splitmix64(time_us_64());
134+
which ^= 1;
135+
#endif
136+
137+
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_RAND);
138+
uint32_t save = spin_lock_blocking(lock);
139+
if (local_check_byte != check_byte) {
140+
// someone got a random number in the interim, so mix it in
141+
local_rng_state.r[0] ^= rng_state.r[0];
142+
local_rng_state.r[1] ^= rng_state.r[1];
143+
}
144+
// Generate a 64-bit RN from the modified PRNG state.
145+
// Note: This also "churns" the 128-bit state for next time.
146+
uint64_t rand64 = xoroshiro128ss(&local_rng_state);
147+
rng_state = local_rng_state;
148+
check_byte++;
149+
spin_unlock(lock, save);
150+
151+
return rand64;
152+
}
153+
154+
void get_rand_128(rng_128_t *ptr128) {
155+
ptr128->r[0] = get_rand_64();
156+
ptr128->r[1] = get_rand_64();
157+
}
158+
159+
uint32_t get_rand_32(void) {
160+
return (uint32_t) get_rand_64();
161+
}

0 commit comments

Comments
 (0)