Skip to content

Commit 3cdb66e

Browse files
author
Fernando Hueso González
committed
implement PRBS generator.
Fixes #8199
1 parent 79cfab5 commit 3cdb66e

File tree

5 files changed

+236
-0
lines changed

5 files changed

+236
-0
lines changed

math/mathcore/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ set(HEADERS
9494
TRandom1.h
9595
TRandom2.h
9696
TRandom3.h
97+
TRandomBinary.h
9798
TRandomGen.h
9899
TStatistic.h
99100
VectorizedTMath.h
@@ -182,6 +183,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(MathCore
182183
src/TRandom1.cxx
183184
src/TRandom2.cxx
184185
src/TRandom3.cxx
186+
src/TRandomBinary.cxx
185187
src/TRandomGen.cxx
186188
src/TStatistic.cxx
187189
src/UnBinData.cxx

math/mathcore/inc/LinkDef2.h

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#pragma link C++ class TRandom1+;
3333
#pragma link C++ class TRandom2+;
3434
#pragma link C++ class TRandom3-;
35+
#pragma link C++ class TRandomBinary+;
3536

3637
#pragma link C++ class ROOT::Math::TRandomEngine+;
3738
#pragma link C++ class ROOT::Math::LCGEngine+;

math/mathcore/inc/TRandomBinary.h

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// @(#)root/mathcore:$Id$
2+
// Author: Fernando Hueso-González 04/08/2021
3+
4+
#ifndef ROOT_TRandomBinary
5+
#define ROOT_TRandomBinary
6+
7+
//////////////////////////////////////////////////////////////////////////
8+
// //
9+
// TRandomBinary //
10+
// //
11+
// Pseudo Random Binary Sequence generator class (periodicity = 2**n-1) //
12+
// //
13+
//////////////////////////////////////////////////////////////////////////
14+
15+
#include <array>
16+
#include <bitset>
17+
#include <vector>
18+
#include <set>
19+
#include <cmath>
20+
#include "Rtypes.h"
21+
#include "TError.h"
22+
23+
class TRandomBinary {
24+
public:
25+
/**
26+
* @brief Generate the next pseudo-random bit using the current state of a linear feedback shift register (LFSR) and update it
27+
* @tparam k the length of the LFSR, usually also the order of the monic polynomial PRBS-k (last exponent)
28+
* @tparam nTaps the number of taps
29+
* @param lfsr the current value of the LFSR. Passed by reference, it will be updated with the next value
30+
* @param taps the taps that will be XOR-ed to calculate the new bit. They are the exponents of the monic polynomial. Ordering is unimportant. Note that an exponent E in the polynom maps to bit index E-1 in the LFSR.
31+
* @param left if true, the direction of the register shift is to the left <<, the newBit is set on lfsr at bit position 0 (right). If false, shift is to the right and the newBit is stored at bit position (k-1)
32+
* @return the new random bit
33+
* @throw an exception is thrown if taps are out of the range [1, k]
34+
* @see https://en.wikipedia.org/wiki/Monic_polynomial
35+
* @see https://en.wikipedia.org/wiki/Linear-feedback_shift_register
36+
* @see https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence
37+
*/
38+
template <size_t k, size_t nTaps>
39+
static bool
40+
NextLFSR(std::bitset<k>& lfsr, const std::array<uint16_t, nTaps> taps, const bool left = true)
41+
{
42+
static_assert(k <= 32, "For the moment, only supported until k == 32.");
43+
static_assert(k > 0, "Non-zero degree is needed for the LFSR.");
44+
static_assert(nTaps > 0, "At least one tap is needed for the LFSR.");
45+
static_assert(nTaps <= k, "Cannot use more taps than polynomial order");
46+
47+
// First, calculate the XOR (^) of all selected bits (marked by the taps)
48+
bool newBit = lfsr[taps.at(0) - 1]; // the exponent E of the polynomial correspond to index E - 1 in the bitset
49+
for(uint16_t j = 1; j < nTaps ; ++j)
50+
{
51+
newBit ^= lfsr[taps.at(j) - 1];
52+
}
53+
54+
//Apply the shift to the register in the right direction, and overwrite the empty one with newBit
55+
if(left)
56+
{
57+
lfsr <<= 1;
58+
lfsr[0] = newBit;
59+
}
60+
else
61+
{
62+
lfsr >>= 1;
63+
lfsr[k-1] = newBit;
64+
}
65+
66+
return newBit;
67+
}
68+
69+
/**
70+
* @brief Generation of a sequence of pseudo-random bits using a linear feedback shift register (LFSR), until a register value is repeated (or maxPeriod is reached)
71+
* @tparam k the length of the LFSR, usually also the order of the monic polynomial PRBS-k (last exponent)
72+
* @tparam nTaps the number of taps
73+
* @param start the start value (seed) of the LFSR
74+
* @param taps the taps that will be XOR-ed to calculate the new bit. They are the exponents of the monic polynomial. Ordering is unimportant. Note that an exponent E in the polynom maps to bit index E-1 in the LFSR.
75+
* @param left if true, the direction of the register shift is to the left <<, the newBit is set on lfsr at bit position 0 (right). If false, shift is to the right and the newBit is stored at bit position (k-1)
76+
* @param wrapping if true, allow repetition of values in the LFSRhistory, until maxPeriod is reached or the repeated value == start. Enabling this option saves memory as no history is kept
77+
* @param oppositeBit if true, use the high/low bit of the LFSR to store output (for left=true/false, respectively) instead of the newBit returned by ::NextLFSR
78+
* @return the array of pseudo random bits, or an empty array if input was incorrect
79+
* @see https://en.wikipedia.org/wiki/Monic_polynomial
80+
* @see https://en.wikipedia.org/wiki/Linear-feedback_shift_register
81+
* @see https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence
82+
*/
83+
template <size_t k, size_t nTaps>
84+
static std::vector<bool>
85+
GenerateSequence(const std::bitset<k> start, const std::array<uint16_t, nTaps> taps, const bool left = true, const bool wrapping = false, const bool oppositeBit = false)
86+
{
87+
std::vector<bool> result; // Store result here
88+
89+
//Sanity-checks
90+
static_assert(k <= 32, "For the moment, only supported until k == 32.");
91+
static_assert(k > 0, "Non-zero degree is needed for the LFSR.");
92+
static_assert(nTaps >= 2, "At least two taps are needed for a proper sequence");
93+
static_assert(nTaps <= k, "Cannot use more taps than polynomial order");
94+
for(auto tap : taps) {
95+
if(tap > k || tap == 0) {
96+
Error("TRandomBinary", "Tap %u is out of range [1,%lu]", tap, k);
97+
return result;
98+
}
99+
}
100+
if(start.none()) {
101+
Error("TRandomBinary", "A non-zero start value is needed");
102+
return result;
103+
}
104+
105+
// Calculate maximum period and pre-allocate space in result
106+
const uint32_t maxPeriod = pow(2,k) - 1;
107+
result.reserve(maxPeriod);
108+
109+
std::set<uint32_t> lfsrHistory; // a placeholder to store the history of all different values of the LFSR
110+
std::bitset<k> lfsr(start); // a variable storing the current value of the LFSR
111+
uint32_t i = 0; // a loop counter
112+
if(oppositeBit) // if oppositeBit enabled, first value is already started with the seed
113+
result.emplace_back(left ? lfsr[k-1] : lfsr[0]);
114+
115+
//Loop now until maxPeriod or a lfsr value is repeated. If wrapping enabled, allow repeated values if not equal to seed
116+
do {
117+
bool newBit = NextLFSR(lfsr, taps, left);
118+
119+
if(!oppositeBit)
120+
result.emplace_back(newBit);
121+
else
122+
result.emplace_back(left ? lfsr[k-1] : lfsr[0]);
123+
124+
++i;
125+
126+
if(!wrapping) // If wrapping not allowed, break the loop once a repeated value is encountered
127+
{
128+
if(lfsrHistory.count(lfsr.to_ulong()))
129+
break;
130+
131+
lfsrHistory.insert(lfsr.to_ulong()); // Add to the history
132+
}
133+
}
134+
while(lfsr != start && i < maxPeriod);
135+
136+
if(oppositeBit)
137+
result.pop_back();// remove last element, as we already pushed the one from the seed above the while loop
138+
139+
result.shrink_to_fit();//only some special taps will lead to the maxPeriod, others will stop earlier
140+
141+
return result;
142+
}
143+
144+
TRandomBinary() = default;
145+
virtual ~TRandomBinary() = default;
146+
147+
ClassDef(TRandomBinary,0) //Pseudo Random Binary Sequence (periodicity = 2**n - 1)
148+
};
149+
150+
#endif

math/mathcore/src/TRandomBinary.cxx

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// @(#)root/mathcore:$Id$
2+
// Author: Fernando Hueso-González 04/08/2021
3+
4+
/**
5+
6+
\class TRandomBinary
7+
8+
@ingroup Random
9+
10+
This class contains a generator of Pseudo Random Binary Sequences (<a
11+
href="https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence">PRBS</a>).
12+
It should NOT be used for general-purpose random number generation or any
13+
statistical study, see ::TRandom2 class instead.
14+
15+
The goal is to generate binary bit sequences with the same algorithm as the ones usually implemented
16+
in electronic chips, so that the theoretically expected ones can be compared with the acquired sequences.
17+
18+
The main ingredients of a PRBS generator are a monic polynomial of maximum degree \f$n\f$, with coefficients
19+
either 0 or 1, and a <a href="https://www.nayuki.io/page/galois-linear-feedback-shift-register">Galois</a>
20+
linear-feedback shift register with a non-zero seed. When the monic polynomial exponents are chosen appropriately,
21+
the period of the resulting bit sequence (0s and 1s) yields \f$2^n - 1\f$.
22+
23+
Other implementations can be found here:
24+
25+
- https://gist.github.com/mattbierner/d6d989bf26a7e54e7135
26+
- https://root.cern/doc/master/civetweb_8c_source.html#l06030
27+
- https://cryptography.fandom.com/wiki/Linear_feedback_shift_register
28+
- https://www3.advantest.com/documents/11348/33b24c8a-c8cb-40b8-a2a7-37515ba4abc8
29+
- https://www.reddit.com/r/askscience/comments/63a10q/for_prbs3_with_clock_input_on_each_gate_how_can/
30+
- https://es.mathworks.com/help/serdes/ref/prbs.html
31+
- https://metacpan.org/pod/Math::PRBS
32+
33+
*/
34+
35+
#include "TRandomBinary.h"
36+
37+
ClassImp(TRandomBinary);

tutorials/math/PRBS.C

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/// \file
2+
/// \ingroup tutorial_math
3+
/// \notebook -nodraw
4+
/// Tutorial illustrating the use of TRandomBinary::GenerateSequence
5+
/// can be run with:
6+
///
7+
/// ~~~{.cpp}
8+
/// root > .x PRBS.C
9+
/// root > .x PRBS.C+ with ACLIC
10+
/// ~~~
11+
///
12+
/// \macro_output
13+
/// \macro_code
14+
///
15+
/// \author Fernando Hueso-González
16+
17+
#include <TRandomBinary.h>
18+
#include <iostream>
19+
20+
void PRBS ()
21+
{
22+
printf("\nTRandomBinary::GenerateSequence PRBS3, PRBS4 and PRBS5 tests\n");
23+
printf("==========================\n");
24+
25+
//PRBS3
26+
std::array<uint16_t, 2> taps3 = {2, 3}; // Exponents of the monic polynomial
27+
auto prbs3 = TRandomBinary::GenerateSequence(std::bitset<3>().flip(), taps3);// Start value all high
28+
29+
//PRBS4
30+
std::array<uint16_t, 2> taps4 = {3, 4}; // Exponents of the monic polynomial
31+
auto prbs4 = TRandomBinary::GenerateSequence(std::bitset<4>().flip(), taps4);// Start value all high
32+
33+
//PRBS7
34+
std::array<uint16_t, 2> taps5 = {5, 3}; // Exponents of the monic polynomial
35+
auto prbs5 = TRandomBinary::GenerateSequence(std::bitset<5>().flip(), taps5);// Start value all high
36+
37+
for(auto prbs : {prbs3, prbs4, prbs5})
38+
{
39+
std::cout << "PRBS period " << prbs.size() << ":\t";
40+
for(auto p : prbs)
41+
{
42+
std::cout << p << " ";
43+
}
44+
std::cout << std::endl;
45+
}
46+
}

0 commit comments

Comments
 (0)