Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /include/root/Math/LFSR.h was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 // @(#)root/mathcore:$Id$
0002 // Author: Fernando Hueso-González   04/08/2021
0003 
0004 #ifndef ROOT_Math_LFSR
0005 #define ROOT_Math_LFSR
0006 
0007 #include <array>
0008 #include <bitset>
0009 #include <cassert>
0010 #include <cstdint>
0011 #include <vector>
0012 #include <set>
0013 #include <cmath>
0014 #include <cstdint> // for std::uint16_t
0015 #include "TError.h"
0016 
0017 /// Pseudo Random Binary Sequence (PRBS) generator namespace with functions based
0018 /// on linear feedback shift registers (LFSR) with a periodicity of 2^n-1
0019 ///
0020 /// @note It should NOT be used for general-purpose random number generation or any
0021 /// statistical study, for those cases see e.g. std::mt19937 instead.
0022 ///
0023 /// The goal is to generate binary bit sequences with the same algorithm as the ones usually implemented
0024 /// in electronic chips, so that the theoretically expected ones can be compared with the acquired sequences.
0025 ///
0026 /// The main ingredients of a PRBS generator are a monic polynomial of maximum degree \f$n\f$, with coefficients
0027 /// either 0 or 1, and a <a href="https://www.nayuki.io/page/galois-linear-feedback-shift-register">Galois</a>
0028 /// linear-feedback shift register with a non-zero seed. When the monic polynomial exponents are chosen appropriately,
0029 /// the period of the resulting bit sequence (0s and 1s) yields \f$2^n - 1\f$.
0030 ///
0031 /// @sa https://gist.github.com/mattbierner/d6d989bf26a7e54e7135,
0032 /// https://root.cern/doc/master/civetweb_8c_source.html#l06030,
0033 /// https://cryptography.fandom.com/wiki/Linear_feedback_shift_register,
0034 /// https://www3.advantest.com/documents/11348/33b24c8a-c8cb-40b8-a2a7-37515ba4abc8,
0035 /// https://www.reddit.com/r/askscience/comments/63a10q/for_prbs3_with_clock_input_on_each_gate_how_can/,
0036 /// https://es.mathworks.com/help/serdes/ref/prbs.html, https://metacpan.org/pod/Math::PRBS,
0037 /// https://ez.analog.com/data_converters/high-speed_adcs/f/q-a/545335/ad9689-pn9-and-pn23
0038 
0039 namespace ROOT::Math::LFSR {
0040 
0041 /**
0042  * @brief Generate the next pseudo-random bit using the current state of a linear feedback shift register (LFSR) and
0043  * update it
0044  * @tparam k the length of the LFSR, usually also the order of the monic polynomial PRBS-k (last exponent)
0045  * @tparam nTaps the number of taps
0046  * @param lfsr the current value of the LFSR. Passed by reference, it will be updated with the next value
0047  * @param taps the taps that will be XOR-ed to calculate the new bit. They are the exponents of the monic polynomial.
0048  * Ordering is unimportant. Note that an exponent E in the polynom maps to bit index E-1 in the LFSR.
0049  * @param left if true, the direction of the register shift is to the left <<, the newBit is set on lfsr at bit position
0050  * 0 (right). If false, shift is to the right and the newBit is stored at bit position (k-1)
0051  * @return the new random bit
0052  * @throw an exception is thrown if taps are out of the range [1, k]
0053  * @see https://en.wikipedia.org/wiki/Monic_polynomial, https://en.wikipedia.org/wiki/Linear-feedback_shift_register,
0054  * https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence
0055  */
0056 template <size_t k, size_t nTaps>
0057 bool NextLFSR(std::bitset<k> &lfsr, std::array<std::uint16_t, nTaps> taps, bool left = true)
0058 {
0059    static_assert(k <= 32, "For the moment, only supported until k == 32.");
0060    static_assert(k > 0, "Non-zero degree is needed for the LFSR.");
0061    static_assert(nTaps > 0, "At least one tap is needed for the LFSR.");
0062    static_assert(nTaps <= k, "Cannot use more taps than polynomial order");
0063    for (std::uint16_t j = 0; j < nTaps; ++j) {
0064       assert(static_cast<size_t>(taps[j] - 1) <= k && static_cast<size_t>(taps[j] - 1) > 0 &&
0065              "Tap value is out of range [1,k]");
0066    }
0067 
0068    // First, calculate the XOR (^) of all selected bits (marked by the taps)
0069    bool newBit = lfsr[taps[0] - 1]; // the exponent E of the polynomial correspond to index E - 1 in the bitset
0070    for (std::uint16_t j = 1; j < nTaps; ++j) {
0071       newBit ^= lfsr[taps[j] - 1];
0072    }
0073 
0074    // Apply the shift to the register in the right direction, and overwrite the empty one with newBit
0075    if (left) {
0076       lfsr <<= 1;
0077       lfsr[0] = newBit;
0078    } else {
0079       lfsr >>= 1;
0080       lfsr[k - 1] = newBit;
0081    }
0082 
0083    return newBit;
0084 }
0085 
0086 /**
0087  * @brief Generation of a sequence of pseudo-random bits using a linear feedback shift register (LFSR), until a
0088  * register value is repeated (or maxPeriod is reached)
0089  * @tparam k the length of the LFSR, usually also the order of the monic polynomial PRBS-k (last exponent)
0090  * @tparam nTaps the number of taps
0091  * @tparam Output the type of the container where the bit result (0 or 1) is stored (e.g. char, bool). It's unsigned
0092  * char by default, use bool instead if you want to save memory
0093  * @param start the start value (seed) of the LFSR
0094  * @param taps the taps that will be XOR-ed to calculate the new bit. They are the exponents of the monic polynomial.
0095  * Ordering is unimportant. Note that an exponent E in the polynom maps to bit index E-1 in the LFSR.
0096  * @param left if true, the direction of the register shift is to the left <<, the newBit is set on lfsr at bit
0097  * position 0 (right). If false, shift is to the right and the newBit is stored at bit position (k-1)
0098  * @param wrapping if true, allow repetition of values in the LFSRhistory, until maxPeriod is reached or the repeated
0099  * value == start. Enabling this option saves memory as no history is kept
0100  * @param oppositeBit if true, use the high/low bit of the LFSR to store output (for left=true/false, respectively)
0101  * instead of the newBit returned by ::NextLFSR
0102  * @return the array of pseudo random bits, or an empty array if input was incorrect
0103  * @see https://en.wikipedia.org/wiki/Monic_polynomial, https://en.wikipedia.org/wiki/Linear-feedback_shift_register,
0104  * https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence
0105  */
0106 template <size_t k, size_t nTaps, typename Output = unsigned char>
0107 std::vector<Output> GenerateSequence(std::bitset<k> start, std::array<std::uint16_t, nTaps> taps, bool left = true,
0108                                      bool wrapping = false, bool oppositeBit = false)
0109 {
0110    std::vector<Output> result; // Store result here
0111 
0112    // Sanity-checks
0113    static_assert(k <= 32, "For the moment, only supported until k == 32.");
0114    static_assert(k > 0, "Non-zero degree is needed for the LFSR.");
0115    static_assert(nTaps >= 2, "At least two taps are needed for a proper sequence");
0116    static_assert(nTaps <= k, "Cannot use more taps than polynomial order");
0117    for (auto tap : taps) {
0118       if (tap > k || tap == 0) {
0119          Error("ROOT::Math::LFSR", "Tap %u is out of range [1,%lu]", tap, k);
0120          return result;
0121       }
0122    }
0123    if (start.none()) {
0124       Error("ROOT::Math::LFSR", "A non-zero start value is needed");
0125       return result;
0126    }
0127 
0128    // Calculate maximum period and pre-allocate space in result
0129    const std::uint32_t maxPeriod = pow(2, k) - 1;
0130    result.reserve(maxPeriod);
0131 
0132    std::set<uint32_t> lfsrHistory; // a placeholder to store the history of all different values of the LFSR
0133    std::bitset<k> lfsr(start);     // a variable storing the current value of the LFSR
0134    std::uint32_t i = 0;            // a loop counter
0135    if (oppositeBit)                // if oppositeBit enabled, first value is already started with the seed
0136       result.emplace_back(left ? lfsr[k - 1] : lfsr[0]);
0137 
0138    // Loop now until maxPeriod or a lfsr value is repeated. If wrapping enabled, allow repeated values if not equal
0139    // to seed
0140    do {
0141       bool newBit = NextLFSR(lfsr, taps, left);
0142 
0143       if (!oppositeBit)
0144          result.emplace_back(newBit);
0145       else
0146          result.emplace_back(left ? lfsr[k - 1] : lfsr[0]);
0147 
0148       ++i;
0149 
0150       if (!wrapping) // If wrapping not allowed, break the loop once a repeated value is encountered
0151       {
0152          if (lfsrHistory.count(lfsr.to_ulong()))
0153             break;
0154 
0155          lfsrHistory.insert(lfsr.to_ulong()); // Add to the history
0156       }
0157    } while (lfsr != start && i < maxPeriod);
0158 
0159    if (oppositeBit)
0160       result.pop_back(); // remove last element, as we already pushed the one from the seed above the while loop
0161 
0162    result.shrink_to_fit(); // only some special taps will lead to the maxPeriod, others will stop earlier
0163 
0164    return result;
0165 }
0166 } // namespace ROOT::Math::LFSR
0167 
0168 #endif