# LFSR.py                                                        DMQ 8-Feb-2010
'''
Modeling a Linear Feedback Shift Register (LFSR)

As in Stinson p.24, except bits shift to right and the stages are numbered
starting at 0.

         --> SR[0] -> SR[1] -> SR[2] --> SR[3] ---> Output
        |                         |         |
         ---- < ----- FB --- < --{+}-- < --{+}

Tap placement is critical. There are only a few "magic" arrangements that give
a maximum length output sequence before the pattern must repeat.  Any other tap
locations will result in the state of the LFSR repeating in less than 2**L - 1
clock cycles.

Problem 1: For the four-stage LFSR shown above, but with taps at stages 1 and 3,
show how the 15 possible states (not including '0000') group into three short
cycles.

Problem 2: For LFSRs with length = {4, 7, 8, 11, 20}, find tap positions that will
give maximum-length sequences.  Hint: All but one of these can be done with only
two taps, and one of the taps must always be on the last stage.

There are three known solutions to this problem.
    1) Do some tricky math.
    2) Look it up in Wikipedia.
    3) Run a simulation.
'''

def spinLFSR(init='1101'taps=[3,2]):
    '''Given the initial state of an LFSR and its feedback taps, return the
    number of cycles until the initial state is repeated.

    >>> spinLFSR()
    15
    '''
    L = len(init)  # length of the shift register
    
    # convert string to [list of ints]
    SR0 = [int(init[i]) for i in range(L)]   # initial state [1, 1, 0, 1]
    SR = SR0[:]    # copy initial state
        
    # print SR, taps

    for clk in range(12**L):      # {1, 2, ... 16}
        
        FB = 0
        for i in tapsFB ^= SR[i]  # compute the feedback bit
        
        SR = ([FB] + SR)[0:L]       # shift right
        print SR

        if SR == SR0:
            return clk

def findtaps(L=4):
    '''Find tap positions that generate a maximum period in an LFSR of length L.

    >>> findtaps()
    [3, 0] 15 *
    [3, 1] 6 
    [3, 2] 15 *
    '''
    # Look for solutions with only two taps. First tap must always be at L-1.
    
    for taps in [[L-1,nfor n in range(L-1)]:  # [[3,0], [3,1], [3,2]]
        init = L * '1'                          # '1111'
        period = spinLFSR(inittaps)
        if (period == 2**L-1): flag = "*"
        elseflag = ""
        print tapsperiodflag                # [3,2] 15 *


### Define an LFSR object, more versatile that the simple functions above. Size
#   and taps of the LFSR should be instance variables.
        
class LFSR(object):
    '''
    Linear Feedback Shift Register
    '''
    def __init__(selfstate='1101'size=0taps=None):
        "Save initial state and design of the LFSR"

        if size == 0size = len(state)           # default size 4
        assert size >= len(state)   # only first few bits are needed
        
        if taps == Nonetaps = [size-1size-2]  # default taps [3,2]
        assert taps[0] == size - 1  # first tap is always on the last stage
    
        init = size * [0]           # set up initial state
        for i in range(len(state)):
            if   state[i] == '0'init[i] = 0
            elif state[i] == '1'init[i] = 1
            elsereturn "Bad initial state"           

        self.init  = init          # [1, 1, 0, 1]
        self.state = init
        self.taps  = taps          # [3, 2]
        self.size  = size          # 4
        self.clock = 0             # cummulative clock count

    def spin(selfN):
        "Advance the state by N clock cycles.  Stop if init is repeated."
        
        for n in range(N):
            self.clock += 1
            
            FB = 0
            for i in self.taps:         # compute the feedback bit
                FB ^= self.state[i]

            self.state = ([FB] + self.state)[0:self.size]  # shift right
            
            if (self.state == self.init):
                return "Initial state repeats at %s" % self.clock
                
        return self.state

'''
>>> sr = LFSR(size=7)
>>> sr.state
[1, 1, 0, 1, 0, 0, 0]
>>> sr.spin(1)
[0, 1, 1, 0, 1, 0, 0]
>>> sr.spin(1000)
'Initial state repeats at 127'

'''