Skip to content

Commit

Permalink
Add an LFSR module
Browse files Browse the repository at this point in the history
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
AngelEzquerra committed Mar 24, 2024
1 parent db4bec2 commit 3079052
Show file tree
Hide file tree
Showing 2 changed files with 251 additions and 0 deletions.
190 changes: 190 additions & 0 deletions impulse/lfsr/lfsr.nim
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)
61 changes: 61 additions & 0 deletions tests/test_lfsr.nim
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()

0 comments on commit 3079052

Please sign in to comment.