|
| 1 | +## LFSR module which implements a Linear Feedback Shift Register that can be |
| 2 | +## used to generate pseudo-random boolean sequences. It supports both Fibonacci |
| 3 | +## and Galois LFSRs. |
| 4 | +## |
| 5 | +## LFSRs used in many digital communication systems (including, for example LTE |
| 6 | +## and 5GNR). For more information see: |
| 7 | +## https://simple.wikipedia.org/wiki/Linear-feedback_shift_register |
| 8 | +## |
| 9 | +## Notes: |
| 10 | +## - This code is heavily based on Nikesh Bajaj's pylfsr, which can be |
| 11 | +## found in https://pylfsr.github.io |
| 12 | +## - This implementation is not optimized for performance, but for simplicity. |
| 13 | +## It would be relatively trivial to implement a much faster version by |
| 14 | +## operating on the bits directly, rather than using tensors. |
| 15 | + |
| 16 | +import arraymancer |
| 17 | +import std / [algorithm, strformat] |
| 18 | + |
| 19 | +type LFSR_TYPE* = enum |
| 20 | + ## LFSR types (see https://simple.wikipedia.org/wiki/Linear-feedback_shift_register) |
| 21 | + fibonacci, galois |
| 22 | + |
| 23 | +type LFSR_INIT_STATE_TYPE* = enum |
| 24 | + ## Common LFSR Initial States |
| 25 | + ## While it is possible to set any LFSR initial `state` by passing a |
| 26 | + ## Tensor[bool] to `initLFSR`, for convenience it is also possible |
| 27 | + ## to pass one of these enum values as the initial `state`: |
| 28 | + ## - `single_true`: all bits set to `false` except the LSB (i.e. the last one) |
| 29 | + ## - `all_true`: all bits set to `true` |
| 30 | + single_true, all_true |
| 31 | + |
| 32 | +type LFSR* = object |
| 33 | + ## LFSR object used to generate fibonacci or galois pseudo random sequences |
| 34 | + ## To use it first create the object using `initLFSR` and then call either |
| 35 | + ## the `next` procedure to get the sequence values one by one or `generate` |
| 36 | + ## to generate multiple values in one go. |
| 37 | + taps*: Tensor[int] |
| 38 | + conf*: LFSR_TYPE |
| 39 | + state*: Tensor[bool] |
| 40 | + init_state: Tensor[bool] |
| 41 | + verbose*: bool |
| 42 | + counter_starts_at_zero*: bool |
| 43 | + outbit*: bool |
| 44 | + count*: int |
| 45 | + seq_bit_index: int |
| 46 | + sequence: Tensor[bool] |
| 47 | + feedbackbit: bool |
| 48 | + |
| 49 | +proc initLFSR*(taps: Tensor[int] | seq[int], |
| 50 | + conf = fibonacci, |
| 51 | + state: Tensor[bool], |
| 52 | + verbose = false, |
| 53 | + counter_starts_at_zero = true): LFSR = |
| 54 | + ## Initialize LFSR with given feedback polynomial and initial state |
| 55 | + ## |
| 56 | + ## Inputs: |
| 57 | + ## - taps: Feedback polynomial taps as a Tensor[int] or seq[int] of exponents |
| 58 | + ## in descending order. Exponent 0 can be omitted, since it is always |
| 59 | + ## implicitly added. For example, to use the `x^5 + x^3 + 1` |
| 60 | + ## polynomial you can set taps to `[5, 3]` or to `[5, 3, 0]` (or to |
| 61 | + ## `[5, 3].toTensor` or `[5, 3, 0].toTensor`). |
| 62 | + ## - state: Initial state of the LFSR as a Tensor[bool] of size equal to the |
| 63 | + ## highest exponent in taps (which must be its first element) |
| 64 | + ## - conf: LFSR type as an enum (`fibonacci` or `galois`) |
| 65 | + ## - verbose: Enable it to print additional logs |
| 66 | + ## - counter_starts_at_zero: Start the count from 0 or 1 (defaults to `true`) |
| 67 | + ## |
| 68 | + ## Return: |
| 69 | + ## - Ready to use LFSR object |
| 70 | + |
| 71 | + # Remove the last value from taps if it is a zero |
| 72 | + when typeof(taps) is Tensor: |
| 73 | + let taps = if taps[taps.size - 1] == 0: taps[_..^2] else: taps |
| 74 | + else: |
| 75 | + let taps = if taps[taps.size - 1] == 0: taps.toTensor[_..^2] else: taps.toTensor |
| 76 | + if taps.size > 1 and not taps.toSeq1D.isSorted(order = SortOrder.Descending): |
| 77 | + raise newException(ValueError, |
| 78 | + &"The LFSR polynomial must be ordered in descending exponent order, but it is not:\n{taps=}") |
| 79 | + if state.size != taps.max(): |
| 80 | + raise newException(ValueError, |
| 81 | + &"The LFSR state size is {state.size} but must be {taps.max()} because that is the highest taps exponent ({taps=})") |
| 82 | + result = LFSR( |
| 83 | + taps: taps, |
| 84 | + conf: conf, |
| 85 | + state: state, |
| 86 | + init_state: state, |
| 87 | + verbose: verbose, |
| 88 | + counter_starts_at_zero: counter_starts_at_zero, |
| 89 | + outbit: false, |
| 90 | + count: 0, |
| 91 | + seq_bit_index: state.size - 1, |
| 92 | + sequence: newTensor[bool](0), |
| 93 | + feedbackbit: false |
| 94 | + ) |
| 95 | + |
| 96 | +proc initLFSR*(taps: Tensor[int] | seq[int], |
| 97 | + conf = fibonacci, |
| 98 | + state = single_true, |
| 99 | + verbose = false, counter_starts_at_zero = true): LFSR = |
| 100 | + ## Overload of initLFSR that takes an enum for the initial state |
| 101 | + ## |
| 102 | + ## Inputs: |
| 103 | + ## - taps: Feedback polynomial taps as a Tensor[int] or seq[int] of exponents |
| 104 | + ## in descending order. Exponent 0 can be omitted, since it is always |
| 105 | + ## implicitly added. For example, to use the `x^5 + x^3 + 1` |
| 106 | + ## polynomial you can set taps to `[5, 3]` or to `[5, 3, 0]` (or to |
| 107 | + ## `[5, 3].toTensor` or `[5, 3, 0].toTensor`). |
| 108 | + ## - state: Initial state of the LFSR as an enum value (`single_true` or |
| 109 | + ## `all_true`). Defaults to `single_true`. |
| 110 | + ## - verbose: Enable it to print additional logs |
| 111 | + ## - counter_starts_at_zero: Start the count from 0 or 1 (defaults to `true`) |
| 112 | + ## |
| 113 | + ## Return: |
| 114 | + ## - Ready to use LFSR object |
| 115 | + when typeof(taps) is not Tensor: |
| 116 | + let taps = taps.toTensor |
| 117 | + let init_state = if state == all_true: |
| 118 | + arraymancer.ones[bool](taps.max()) |
| 119 | + else: |
| 120 | + zeros[bool](taps.max() - 1).append(true) |
| 121 | + initLFSR(taps, conf = conf, state = init_state, |
| 122 | + verbose = verbose, counter_starts_at_zero = counter_starts_at_zero) |
| 123 | + |
| 124 | +proc reset*(self: var LFSR) = |
| 125 | + ## Reset the LFSR `state` and `count` to their initial values |
| 126 | + self.state = self.init_state |
| 127 | + self.count = 0 |
| 128 | + |
| 129 | +proc next*(self: var LFSR, verbose = false, |
| 130 | + store_sequence: static bool = false) : bool = |
| 131 | + ## Run one cycle on LFSR with given feedback polynomial and |
| 132 | + ## update the count, state, feedback bit, output bit and sequence |
| 133 | + ## |
| 134 | + ## Inputs: |
| 135 | + ## - Preconfigured LFSR object |
| 136 | + ## - verbose: Print additional logs even if LSRF.verbose is disabled |
| 137 | + ## - store_sequence: static bool that enables saving the generated sequence |
| 138 | + ## |
| 139 | + ## Return: |
| 140 | + ## - bool output bit |
| 141 | + if self.verbose or verbose: |
| 142 | + echo "State: ", self.state |
| 143 | + |
| 144 | + if self.counter_starts_at_zero: |
| 145 | + result = self.state[self.seq_bit_index] |
| 146 | + when store_sequence: |
| 147 | + self.sequence = self.sequence.append(result) |
| 148 | + |
| 149 | + if self.conf == fibonacci: |
| 150 | + var b = self.state[self.taps[0] - 1] xor self.state[self.taps[1] - 1] |
| 151 | + if self.taps.size > 2: |
| 152 | + for coeff in self.taps[2.._]: |
| 153 | + b = self.state[coeff - 1] xor b |
| 154 | + |
| 155 | + self.state = self.state.roll(1) |
| 156 | + self.feedbackbit = b |
| 157 | + self.state[0] = self.feedbackbit |
| 158 | + else: # galois |
| 159 | + self.feedbackbit = self.state[0] |
| 160 | + self.state = self.state.roll(-1) |
| 161 | + for k in self.taps[1.._]: |
| 162 | + self.state[k-1] = self.state[k-1] xor self.feedbackbit |
| 163 | + |
| 164 | + if not self.counter_starts_at_zero: |
| 165 | + result = self.state[self.seq_bit_index] |
| 166 | + when store_sequence: |
| 167 | + self.sequence = self.sequence.append(result) |
| 168 | + |
| 169 | + self.count += 1 |
| 170 | + self.outbit = result |
| 171 | + |
| 172 | + return result |
| 173 | + |
| 174 | +iterator generator*(lfsr: var LFSR,n: int, |
| 175 | + store_sequence: static bool = false): bool = |
| 176 | + ## Generator that will generate a random sequence of length `n` |
| 177 | + ## |
| 178 | + ## Inputs: |
| 179 | + ## - Preconfigured LFSR object |
| 180 | + ## - n: Number of random values to generate |
| 181 | + ## - store_sequence: static bool that enables saving the generated sequence |
| 182 | + ## |
| 183 | + ## Return: |
| 184 | + ## - Generated boolean values |
| 185 | + yield lfsr.next(store_sequence = store_sequence) |
| 186 | + |
| 187 | +proc generate*(lfsr: var LFSR, n: int, |
| 188 | + store_sequence: static bool = false): Tensor[bool] {.noinit.} = |
| 189 | + ## Generate a random sequence of length `n` |
| 190 | + ## |
| 191 | + ## Inputs: |
| 192 | + ## - Preconfigured LFSR object |
| 193 | + ## - n: Number of random values to generate |
| 194 | + ## - store_sequence: static bool that enables saving the generated sequence |
| 195 | + ## |
| 196 | + ## Return: |
| 197 | + ## - `Tensor[bool]` of size `n` containing the generated values |
| 198 | + result = newTensor[bool](n) |
| 199 | + for i in 0 ..< n: |
| 200 | + result[i] = lfsr.next(store_sequence = store_sequence) |
| 201 | + |
| 202 | +func lfsr_tap_example*(size: int): seq[int] = |
| 203 | + ## Get an example "maximal" LSFR tap sequence for a given size (up to 24) |
| 204 | + ## |
| 205 | + ## This is a convenience function which can be used to select a set of taps |
| 206 | + ## that generates a "maximal" sequence of the given size. |
| 207 | + ## Note that there are many more tap sequences that generate maximal |
| 208 | + ## sequences and that these examples are have been taken from wikipedia. |
| 209 | + doAssert size >= 2 and size <= 24, |
| 210 | + "LSFR tap examples are only available for sizes between 2 and 24" |
| 211 | + let examples = @[ |
| 212 | + @[2, 1], |
| 213 | + @[3, 2], |
| 214 | + @[4, 3], |
| 215 | + @[5, 3], |
| 216 | + @[6, 5], |
| 217 | + @[7, 6], |
| 218 | + @[8, 6, 5, 4], |
| 219 | + @[9, 5], |
| 220 | + @[10, 7], |
| 221 | + @[11, 9], |
| 222 | + @[12, 11, 10, 4], |
| 223 | + @[13, 12, 11, 8], |
| 224 | + @[14, 13, 12, 2], |
| 225 | + @[15, 14], |
| 226 | + @[16, 15, 13, 4], |
| 227 | + @[17, 14], |
| 228 | + @[18, 11], |
| 229 | + @[19, 18, 17, 14], |
| 230 | + @[20, 17], |
| 231 | + @[21, 19], |
| 232 | + @[22, 21], |
| 233 | + @[23, 18], |
| 234 | + @[24, 23, 22, 17] |
| 235 | + ] |
| 236 | + examples[size-2] |
0 commit comments