-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This module, which is heavily based on Nikesh Bajaj's pylsfr (https://pylfsr.github.io), implements a Linear Feedback Shift Register that can be used to generate pseudo-random boolean sequences. It supports both Fibonacci and Galois LFSRs. LFSRs are used in many digital communication systems (including, for example LTE and 5GNR). For more information see https://simple.wikipedia.org/wiki/Linear-feedback_shift_register
- Loading branch information
1 parent
db4bec2
commit 3079052
Showing
2 changed files
with
251 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
## LFSR module which implements a Linear Feedback Shift Register that can be | ||
## used to generate pseudo-random boolean sequences. It supports both Fibonacci | ||
## and Galois LFSRs. | ||
## | ||
## LFSRs used in many digital communication systems (including, for example LTE | ||
## and 5GNR). For more information see: | ||
## https://simple.wikipedia.org/wiki/Linear-feedback_shift_register | ||
## | ||
## Note that this code is heavily based on Nikesh Bajaj's pylfsr, which can be | ||
## found in https://pylfsr.github.io | ||
|
||
import arraymancer | ||
import std/strformat | ||
|
||
type LFSR_TYPE* = enum | ||
## LFSR types (see https://simple.wikipedia.org/wiki/Linear-feedback_shift_register) | ||
fibonacci, galois | ||
|
||
type LFSR_INIT_STATE_TYPE* = enum | ||
## Common LFSR Initial States | ||
## While it is possible to set any LFSR initial `state` by passing a | ||
## Tensor[bool] to `initLFSR`, for convenience it is also possible | ||
## to pass one of these enum values as the initial `state`: | ||
## - `all_true`: all bits set to `true` | ||
## - `single_true`: all bits set to `false` except the last one | ||
all_true, single_true | ||
|
||
type LFSR* = object | ||
## LFSR object used to generate fibonacci or galois pseudo random sequences | ||
## To use it first create the object using `initLFSR` and then call either | ||
## the `next` procedure to get the sequence values one by one or `generate` | ||
## to generate multiple values in one go. | ||
fpoly*: Tensor[int] | ||
conf*: LFSR_TYPE | ||
state*: Tensor[bool] | ||
init_state: Tensor[bool] | ||
verbose*: bool | ||
counter_starts_at_zero*: bool | ||
outbit*: bool | ||
count*: int | ||
seq_bit_index: int | ||
sequence: Tensor[bool] | ||
feedbackbit: bool | ||
|
||
proc initLFSR*(fpoly: typeof(LFSR.fpoly), | ||
conf = fibonacci, | ||
state: Tensor[bool], | ||
verbose = false, | ||
counter_starts_at_zero = true): LFSR = | ||
## Initialize LFSR with given feedback polynomial and initial state | ||
## | ||
## Inputs: | ||
## - fpoly: Feedback polynomial as a Tensor[int] of exponents in descending | ||
## order. Note that exponent 0 can be omitted, since it is always | ||
## implicitly added. For example, to use the `x^5 + x^2 + 1` | ||
## polinomial you can set fpoly to `[5, 2].toTensor` or to | ||
## `[5, 2, 0].toTensor`. | ||
## - state: Initial state of the LFSR as a Tensor[bool] of size equal to the | ||
## highest exponent in fpoly (which must be its first element) | ||
## - conf: LFSR type as an enum (`fibonacci` or `galois`) | ||
## - verbose: Enable it to print additonal logs | ||
## - counter_starts_at_zero: Start the count from 0 or 1 | ||
## | ||
## Return: | ||
## - Ready to use LFSR object | ||
# Remove the last value from fpoly if it is a zero | ||
let fpoly = if fpoly[fpoly.size - 1] == 0: fpoly[_..^2] else: fpoly | ||
if fpoly.size > 1 and fpoly[0] < fpoly[fpoly.size - 1]: | ||
raise newException(ValueError, | ||
&"The LFSR polinomial must be ordered in descending exponent order, but it is not:\n{fpoly=}") | ||
if state.size != fpoly.max(): | ||
raise newException(ValueError, | ||
&"The LFSR state size is {state.size} but must be {fpoly.max()} because that is the highest fpoly exponent ({fpoly=})") | ||
result = LFSR( | ||
fpoly: fpoly, | ||
conf: conf, | ||
state: state, | ||
init_state: state, | ||
verbose: verbose, | ||
counter_starts_at_zero: counter_starts_at_zero, | ||
outbit: false, | ||
count: 0, | ||
seq_bit_index: state.size - 1, | ||
sequence: newTensor[bool](0), | ||
feedbackbit: false | ||
) | ||
|
||
proc initLFSR*(fpoly: typeof(LFSR.fpoly), | ||
conf = fibonacci, | ||
state = all_true, | ||
verbose = false, counter_starts_at_zero = true): LFSR = | ||
## Overload of initLFSR that takes an enum for the initial state | ||
## | ||
## Inputs: | ||
## - fpoly: Feedback polynomial as a Tensor[int] of exponents in descending | ||
## order. Note that exponent 0 can be omitted, since it is always | ||
## implicitly added. For example, to use the `x^5 + x^2 + 1` | ||
## polinomial you can set fpoly to `[5, 2].toTensor` or to | ||
## `[5, 2, 0].toTensor`. | ||
## - state: Initial state of the LFSR as a Tensor[bool] of size equal to the | ||
## highest exponent in fpoly (which must be its first element) | ||
## - verbose: Enable it to print additonal logs | ||
## - counter_starts_at_zero: Start the count from 0 or 1 | ||
## | ||
## Return: | ||
## - Ready to use LFSR object | ||
let init_state = if state == all_true: | ||
arraymancer.ones[bool](fpoly.max()) | ||
else: | ||
zeros[bool](fpoly.max() - 1).append(true) | ||
initLFSR(fpoly, conf = conf, state = init_state, | ||
verbose = verbose, counter_starts_at_zero = counter_starts_at_zero) | ||
|
||
proc reset*(self: var LFSR) = | ||
## Reset the LFSR `state` and `count` to their initial values | ||
self.state = self.init_state | ||
self.count = 0 | ||
|
||
proc next*(self: var LFSR, verbose = false, | ||
store_sequence: static bool = false) : bool = | ||
## Run one cycle on LFSR with given feedback polynomial and | ||
## update the count, state, feedback bit, output bit and sequence | ||
## | ||
## Inputs: | ||
## - Preconfigured LFSR object | ||
## - verbose: Print additonal logs even if LSRF.verbose is disabled | ||
## - store_sequence: static bool that enables saving the generated sequence | ||
## | ||
## Return: | ||
## - bool output bit | ||
if self.verbose or verbose: | ||
echo "State: ", self.state | ||
|
||
if self.counter_starts_at_zero: | ||
result = self.state[self.seq_bit_index] | ||
when store_sequence: | ||
self.sequence = self.sequence.append(result) | ||
|
||
if self.conf == fibonacci: | ||
var b = self.state[self.fpoly[0] - 1] xor self.state[self.fpoly[1] - 1] | ||
if self.fpoly.size > 2: | ||
for coeff in self.fpoly[2.._]: | ||
b = self.state[coeff - 1] xor b | ||
|
||
self.state = self.state.roll(1) | ||
self.feedbackbit = b | ||
self.state[0] = self.feedbackbit | ||
else: # galois | ||
self.feedbackbit = self.state[0] | ||
self.state = self.state.roll(-1) | ||
for k in self.fpoly[1.._]: | ||
self.state[k-1] = self.state[k-1] xor self.feedbackbit | ||
|
||
if not self.counter_starts_at_zero: | ||
result = self.state[self.seq_bit_index] | ||
when store_sequence: | ||
self.sequence = self.sequence.append(result) | ||
|
||
self.count += 1 | ||
self.outbit = result | ||
|
||
return result | ||
|
||
iterator generator*(lfsr: var LFSR,n: int, | ||
store_sequence: static bool = false): bool = | ||
## Generator that will generate a random sequence of length `n` | ||
## | ||
## Inputs: | ||
## - Preconfigured LFSR object | ||
## - n: Number of random values to generate | ||
## - store_sequence: static bool that enables saving the generated sequence | ||
## | ||
## Return: | ||
## - Generated boolean values | ||
yield lfsr.next(store_sequence = store_sequence) | ||
|
||
proc generate*(lfsr: var LFSR, n: int, | ||
store_sequence: static bool = false): Tensor[bool] {.noinit.} = | ||
## Generate a random sequence of length `n` | ||
## | ||
## Inputs: | ||
## - Preconfigured LFSR object | ||
## - n: Number of random values to generate | ||
## - store_sequence: static bool that enables saving the generated sequence | ||
## | ||
## Return: | ||
## - `Tensor[bool]` of size `n` containing the generated values | ||
result = newTensor[bool](n) | ||
for i in 0 ..< n: | ||
result[i] = lfsr.next(store_sequence = store_sequence) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import ../impulse/lfsr/lfsr | ||
import arraymancer / tensor | ||
|
||
proc test_fibonacci(): bool = | ||
var lfsr = initLFSR( | ||
fpoly = [5, 2].toTensor, | ||
state = all_true, | ||
conf = fibonacci | ||
) | ||
|
||
let sequence = lfsr.generate(31).asType(int) | ||
let expected_sequence = [1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, | ||
0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0].toTensor | ||
return sequence == expected_sequence | ||
|
||
proc test_galois(): bool = | ||
var lfsr = initLFSR( | ||
fpoly = [5, 2].toTensor, | ||
state = all_true, | ||
conf = galois | ||
) | ||
|
||
let sequence = lfsr.generate(31).asType(int) | ||
let expected_sequence = [1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, | ||
0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1].toTensor | ||
return sequence == expected_sequence | ||
|
||
proc test_init(): bool = | ||
var lfsr1 = initLFSR( | ||
fpoly = [5, 2].toTensor, | ||
state = all_true | ||
) | ||
var lfsr2 = initLFSR( | ||
fpoly = [5, 2].toTensor, | ||
state = all_true, | ||
conf = fibonacci | ||
) | ||
var lfsr3 = initLFSR( | ||
fpoly = [5, 2].toTensor, | ||
state = ones[bool](5), | ||
conf = fibonacci | ||
) | ||
let s1 = lfsr1.generate(31) | ||
let s2 = lfsr2.generate(31) | ||
let s3 = lfsr3.generate(31) | ||
return s1 == s2 and s2 == s3 | ||
|
||
proc test_reset(): bool = | ||
var lfsr = initLFSR( | ||
fpoly = [5, 2].toTensor, | ||
state = all_true | ||
) | ||
let s1 = lfsr.generate(31) | ||
lfsr.reset() | ||
let s2 = lfsr.generate(31) | ||
return s1 == s2 | ||
|
||
doAssert test_fibonacci() | ||
doAssert test_galois() | ||
doAssert test_init() | ||
doAssert test_reset() |