C++ random utilities and embedded rng

Creating random numbers is very difficult for computers. Computers are deterministic by definition, and randomness should never be deterministic. Games of chance would become really boring, slot machines would disappear overnight, and random algorithms would not have existed. To create this randomness in computers there are 2 major solutions:

Pseudo random number generators (PRNG)

These generate, as the name suggests, pseudo random numbers. They are not truly random and will exhibit patterns, repeat them self, and with the same starting state they will always generate the same next state. This can be desirable for a game which keeps the same randomness between saves/play-troughs. But for things like encryption this is terrible.

True random number generators

True random number generators are not deterministic, they retrieve their source of randomness from hardware that is often tied to real world events, like the decay of radioactive material, background noise of the universe, or lava lamps. These make them a great source of randomness for cryptography.

C++

Now onto my/our favorite language. C++ has 3 pseudo random number generators build in: The mersenne twister, a Linear congruential generator or lcg and a Subtract with carry algorithm. These all have their separate uses, if you need better randomness, quicker generation of random numbers etc. The mersenne is one of the most widely used PRNG's, it has a very long period (the time before it starts repeating itself) which makes them feel more random. LCG's and Subtract with carry are faster than the mersenne twister, and require less state, but are also considered less secure. Besides this, c++ also has adapters, which alter the behavior of the rngs. But the most useful additions are distributions. These allow the uniform rngs to be distributed differently. They can map the output of a random number generator to a specific range of numbers, generate true or false values with a specific chance (as we'll see later), and even generate numbers in different distributions like normal distributions. Lastly c++ provides a random_device, that is supposed to be a non deterministic rng. Notice the word supposed. On x86/x86_64 this uses the RDRAND instruction but there is no guarantee. If the compiler doesn't know if there is a hardware rng, it is allowed to use a PRNG. For example when compiling with arm-none-eabi-g++ --specs=nosys.specs it uses the mersenne twister algorithm .

In the last paragraph I briefly mentioned state. Almost all PRNG's use state, which they manipulate to generate a new random number, this then also updates the state to a new state. For example consider the following crude LCG implementation, which uses the same constants as the c++ implementation, and also generates the same "random" numbers.

uint_fast32_t LCG (){ static uint_fast32_t seed = 1 ; return ( seed = ( 48271 * seed ) % 2147483647 ); }

You need to save the result of the calculation to make sure you do not keep generating the same number. For LCG this is just a 32 bit integer, but for the better mersenne twister, this is 5000 bytes for the 32bit c++ version and 2504 bytes for the 64 bit generator. Which is a lot of space on embedded devices. Luckily there is a solution

Hardware RNG

More and more embedded devices have build in hardware rngs and even when they don't, there are enough other sources of hardware . But as stated before even if your device has an hardware rng as long as the compiler doesn't know about it, it will use a mersenne twister. With it's overhead of ~2KB this is often undesirable.

Let's fix this and implement our own random device class. It should be compatible with all the other c++ random tools like the distributions and adapters, this would save us writing these parts our self. This also eliminates the potential for errors in those parts.

The development platform that is used at my university is the Arduino Due. According to the data sheet this has a True random number generator.

First we need to create a class that implements the std::uniform_random_bit_generator concept. We need to define the return type, a minimum, a maximum and the actual rng function. This is pretty easy to do, so let's show an example with the xkcd random number generator

struct xkcd_rng { using result_type = uint8_t ; uint8_t operator () (){ return 4 ; // chosen by fair dice roll. // guaranteed to be random. } constexpr static uint8_t min (){ return 1 ; } constexpr static uint8_t max (){ return 6 ; } };

This doesn't use our device's TRNG and isn't useful in the real world. So by implementing the aforementioned functions with code that interacts with our build in TRNG we can get good randomness without the overhead of the mersenne states etc. The data-sheet mentions one Control register that enables the RNG, so let's enable that in our constructor.

class AtmelSamRNG { Trng * const regs = TRNG ; inline static constexpr uint32_t KEY = ' R ' << 16 | ' N ' << 8 | ' G ' ; public : AtmelSamRNG () { REG_PMC_PCER1 = PMC_PCER1_PID41 ; regs -> TRNG_CR = TRNG_CR_ENABLE | TRNG_CR_KEY ( KEY ); } };

Now as you can see we actually write to more than one register. This is because to the TRNG module needs power to function. This Atmel chip, like most micro-controllers has a power controller. The power controller enables and disables the clock signal to modules within the micro-controller. By disabling the clock the module won't run and you save some power.

To take advantage of this power saving we want to disable the clock once we are done with the random numbers. We can implement a destructor to do so. But here we run into a problem. Consider you have two of these rng objects. If one gets destructed before the other, it will disable the TRNG module and the other rng object won't work correctly anymore. So we need a mechanism to counter this. Enter reference counting. Adding a ref_count, allows us to keep track when we need to disable the clock, but this gives us the next problem. As dictated by the rule of five , we need to implement copy and move constructors/assignment operators. Otherwise our reference counting wouldn't correctly count, which would defeat it's purpose. The full implementation of this can be found at the bottom. But the most important part is this destructor:

~ AtmelSamRNG () { ref_count --; if ( ref_count == 0 ) { regs -> TRNG_CR &= ~ TRNG_CR_ENABLE ; } }

Now after setting up our class we can get the actual random generated number from the hardware. Unfortunately the hardware only generates a number every 84 clock cycles. So there is a chance you have to wait 84 cycles before getting your random number.

uint32_t operator () () { while (!( regs -> TRNG_ISR & TRNG_ISR_DATRDY )); return regs -> TRNG_ODATA ; }

As you can see we block and wait until there is a new random number. You could overload the class and implement this so that free the processor for some other task, either with an cooperative RTOS or some other construction. Now that we have the important parts, to show how versatile this is we implement a simple program that generates a random blinking light. Using the standard Arduino platform functions for the enabling/disabling of the LED's, we get the following code.

#include < random > #include " TRNG.h " const auto chance = .2 ; AtmelSamRNG rng ; std :: bernoulli_distribution should_change_dist ( chance ); void setup () { pinMode ( LED_BUILTIN , OUTPUT ); } void loop () { if ( should_change_dist ( rng )) { digitalWrite ( LED_BUILTIN , ! digitalRead ( LED_BUILTIN )); } delay ( 100 ); }

Every 100ms there is a 20 percent chance that the led changes it's status. With the use of the random utilities of c++ this was really easy to implement, and looks clean and beautiful. I still don't like that the Arduino loves to work with so much globals, but for small programs like this it's easy and fast to put together.

Edit

As pointed out by reddit user /u/louiswins, even though I tried to warn you about implementing the rule of five, I failed to do so correctly myself. This is fixed in the full implementation below, so you can copy paste that again without errors.