【问题标题】:Linear feedback shift register?线性反馈移位寄存器?
【发布时间】:2022-05-20 03:46:59
【问题描述】:

最近我反复接触到 LFSR 的概念,我觉得这很有趣,因为它与不同的领域有联系,而且本身也很吸引人。我花了一些力气才明白,最后的帮助是这真的很好page,比(起初)神秘的wikipedia entry好多了。所以我想为一个像 LFSR 一样工作的程序编写一些小代码。更准确地说,这以某种方式显示了 LFSR 的工作原理。这是经过一些更长时间的尝试(Python)后我能想到的最干净的东西:

def lfsr(seed, taps):
    sr, xor = seed, 0
    while 1:
        for t in taps:
            xor += int(sr[t-1])
        if xor%2 == 0.0:
            xor = 0
        else:
            xor = 1
        print(xor)
        sr, xor = str(xor) + sr[:-1], 0
        print(sr)
        if sr == seed:
            break

lfsr('11001001', (8,7,6,1))      #example

我将XOR函数的输出命名为“xor”,不是很正确。 然而,这只是为了展示它如何在可能的状态中循环,实际上你注意到寄存器是由一个字符串表示的。没有太多的逻辑连贯性。

这可以很容易地变成一个不错的玩具,你可以看几个小时(至少我可以:-)

def lfsr(seed, taps):
    import time
    sr, xor = seed, 0
    while 1:
        for t in taps:
            xor += int(sr[t-1])
        if xor%2 == 0.0:
            xor = 0
        else:
            xor = 1
        print(xor)
        print('')
        time.sleep(0.75)
        sr, xor = str(xor) + sr[:-1], 0
        print(sr)
        print('')
        time.sleep(0.75)

然后我就想到了,这在写软件有什么用?听说可以生成随机数;是真的吗?如何? 所以,如果有人可以,那就太好了:

  • 解释如何在软件开发中使用这种设备
  • 想出一些代码来支持上面的观点,或者就像我的一样以任何语言展示不同的方法

此外,由于没有太多关于这块逻辑和数字电路的教学内容,如果这可以成为新手(如我)更好地理解这件事的地方,那就太好了 em>,或者更好的是,了解它是什么以及在编写软件时它如何有用。应该让它成为一个社区 wiki 吗?

也就是说,如果有人喜欢打高尔夫球……不客气。

【问题讨论】:

  • 如果你在 SO 中搜索 lfsr 你会发现很多...
  • 我做到了,不是我想要的
  • 为什么这被标记为代码高尔夫?
  • @Landei:因为我(也)希望看到它打高尔夫球。如果这对标签来说还不够,对不起。
  • 就在 2 天前,我使用 LFSR 将整数序列转换为半随机序列,然后将其转换回来:stackoverflow.com/questions/9804100/…

标签: python language-agnostic digital-logic


【解决方案1】:

因为我一直在寻找 Python 中的 LFSR 实现,所以我偶然发现了这个主题。但是,我发现根据我的需要,以下内容更准确:

def lfsr(seed, mask):
    result = seed
    nbits = mask.bit_length()-1
    while True:
        result = (result << 1)
        xor = result >> nbits
        if xor != 0:
            result ^= mask

        yield xor, result

上述 LFSR 生成器基于 GF(2k) 模演算 (GF = Galois Field)。刚刚完成了代数课程,我将用数学的方式来解释这一点。

让我们以 GF(24) 为例,它等于 {a4x4 + a3x3 + a2x2 + a1x1 + a0x0 | a0, a1, ..., a4 ∈ Z2}(澄清一下,Zn = {0,1,...,n-1},因此 Z2 = {0,1},即一位)。这意味着这是所有四次多项式的集合,所有因子都存在或不存在,但没有这些因子的倍数(例如,没有 2xk)。 x3、x4 + x3、1 和 x4 + x3 + x2 + x + 1 都是该组成员的示例。

我们将此集合模数取为四次多项式(即 P(x) ∈ GF(24)),例如P(x) = x4+x1+x0。这种对组的模运算也表示为 GF(24) / P(x)。供您参考,P(x) 描述了 LFSR 中的“抽头”。

我们还采用了一个 3 次或更低阶的随机多项式(这样它就不受我们的模数影响,否则我们也可以直接对其执行模数运算),例如A0(x) = x0。现在每个后续的 Ai(x) 都是通过将其乘以 x 来计算的: Ai(x) = Ai-1(x) * x 模 P(x)。

由于我们处于有限的领域,模运算可能会产生影响,但只有当得到的 Ai(x) 至少具有因子 x4 (我们在 P(x) 中的最高因子)。请注意,由于我们正在处理 Z2 中的数字,因此执行取模运算本身只不过是通过将两个 ai 相加来确定每个 ai 是 0 还是 1来自 P(x) 和 Ai(x) 的值一起(即 0+0=0、0+1=1、1+1=0 或“异或”这两个)。

每个多项式都可以写成一组位,例如 x4+x1+x0 ~ 10011。A 0(x) 可以看作是种子。 'times x' 操作可以看作是左移操作。取模运算可以看成是位掩码运算,掩码就是我们的P(x)。

因此,上面描述的算法会生成(无限流)有效的四位 LFSR 模式。例如,对于我们定义的 A0(x) (x0) 和 P(x) (x4 +x1+x0),我们可以在GF(24中定义如下第一个产生的结果)(注意 A0 直到第一轮结束时才会产生——数学家通常从 '1' 开始计数):

 i   Ai(x)                   'x⁴'  bit pattern
 0   0x³ + 0x² + 0x¹ + 1x⁰   0     0001        (not yielded)
 1   0x³ + 0x² + 1x¹ + 0x⁰   0     0010
 2   0x³ + 1x² + 0x¹ + 0x⁰   0     0100
 3   1x³ + 0x² + 0x¹ + 0x⁰   0     1000
 4   0x³ + 0x² + 1x¹ + 1x⁰   1     0011        (first time we 'overflow')
 5   0x³ + 1x² + 1x¹ + 0x⁰   0     0110
 6   1x³ + 1x² + 0x¹ + 0x⁰   0     1100
 7   1x³ + 0x² + 1x¹ + 1x⁰   1     1011
 8   0x³ + 1x² + 0x¹ + 1x⁰   1     0101
 9   1x³ + 0x² + 1x¹ + 0x⁰   0     1010
10   0x³ + 1x² + 1x¹ + 1x⁰   1     0111
11   1x³ + 1x² + 1x¹ + 0x⁰   0     1110
12   1x³ + 1x² + 1x¹ + 1x⁰   1     1111
13   1x³ + 1x² + 0x¹ + 1x⁰   1     1101
14   1x³ + 0x² + 0x¹ + 1x⁰   1     1001
15   0x³ + 0x² + 0x¹ + 1x⁰   1     0001        (same as i=0)

请注意,您的掩码必须在第四个位置包含“1”,以确保您的 LFSR 生成四位结果。另请注意,“1”必须出现在第零位,以确保您的比特流不会以 0000 位模式结束,或者最后一位将变为未使用(如果所有位都向左移动,您将一个班次后第 0 个位置也以 0 结束)。

并非所有 P(x) 都必然是 GF(2k) 的生成器(即,并非所有 k 位掩码都生成所有 2k-1- 1 个数字)。例如,x4 + x3 + x2 + x1 + x0 sup> 生成 3 组,每组 5 个不同的多项式,或“周期 5 的 3 个周期”:0001,0010,0100,1000,1111; 0011,0110,1100,0111,1110;和 0101,1010,1011,1001,1101。请注意,0000 永远无法生成,也无法生成任何其他数字。

通常,LFSR 的输出是“移出”的位,如果执行模运算,则为“1”,否则为“0”。周期为 2k-1-1 的 LFSR,也称为伪噪声或 PN-LFSR,遵循 Golomb 的随机性假设,即该输出位“足够”随机。

因此,这些位的序列可用于加密,例如 A5/1 和 A5/2 移动加密标准或 E0 蓝牙标准。但是,它们并不像人们想的那样安全:Berlekamp-Massey algorithm 可用于对 LFSR 的特征多项式(P(x))进行逆向工程。因此,强大的加密标准使用Non-linear FSR 或类似的非线性函数。与此相关的主题是 AES 中使用的S-Boxes


请注意,我使用了int.bit_length() operation。这直到 Python 2.7 才实现。
如果您只需要有限位模式,则可以检查种子是否等于结果,然后中断循环。
您可以在 for 循环中使用我的 LFSR 方法(例如 for xor, pattern in lfsr(0b001,0b10011)),或者您可以对方法的结果重复调用 .next() 操作,每次都返回一个新的 (xor, result)-pair。

【讨论】:

    【解决方案2】:

    实际上,基于 LFSR 的算法非常普遍。 CRC实际上是直接基于LFSR的。当然,在计算机科学课上,人们在谈论输入值应该如何与累积值进行异或运算时会谈论多项式,而在电子工程中,我们谈论的是抽头。它们是相同的,只是术语不同。

    CRC32 是一个很常见的。它用于检测以太网帧中的错误。这意味着当我发布这个答案时,我的电脑使用了基于 LFSR 的算法来生成 IP 数据包的哈希值,以便我的路由器可以验证它正在传输的内容没有损坏。

    Zip 和 Gzip 文件是另一个示例。两者都使用 CRC 进行错误检测。 Zip 使用 CRC32,Gzip 使用 CRC16 和 CRC32。

    CRC 基本上是散列函数。它足以让互联网正常工作。这意味着 LFSR 是相当好的散列函数。我不确定你是否知道这一点,但一般来说,好的散列函数被认为是好的随机数生成器。但 LFSR 的问题在于,选择正确的抽头(多项式)对于散列/随机数的质量非常重要。

    您的代码通常是玩具代码,因为它对一串 1 和 0 进行操作。在现实世界中,LFSR 对字节中的位起作用。您通过 LFSR 推送的每个字节都会更改寄存器的累积值。该值实际上是您通过寄存器推送的所有字节的校验和。使用该值作为随机数的两种常见方法是使用计数器并通过寄存器推送一个数字序列,从而将线性序列 1、2、3、4 转换为一些散列序列,如 15306、22、5587, 994,或者将当前值反馈到寄存器中,以看似随机的顺序生成一个新的数字。

    应该注意的是,天真地使用位摆弄 LFSR 执行此操作非常慢,因为您必须一次处理位。因此,人们想出了使用预先计算好的表格来一次处理 8 位甚至 32 位的方法。这就是为什么您几乎从未在野外看到 LFSR 代码的原因。在大多数生产代码中,它伪装成其他东西。

    但有时一个简单的 LFSR 可以派上用场。我曾经为 PIC micro 编写了一个Modbus 驱动程序,该协议使用 CRC16。预先计算的表格需要 256 字节的内存,而我的 CPU 只有 68 字节 (I'm not kidding)。所以我不得不使用 LFSR。

    【讨论】:

    • TCP/IP 使用简单的校验和,而不是 CRC32。其他协议可能有额外的检测错误的方法(例如:以太网有一个 FCS,而 IIRC 是一个 CRC)。
    • @ninjalj:已更正。感谢您强调这一点。
    【解决方案3】:

    LFSR 有很多应用。其中之一是产生噪音,例如 SN76489 和变体(用于 Master System、Game Gear、MegaDrive、NeoGeo Pocket 等)使用 LFSR 来产生白/周期性噪音。 SN76489的LFSRin this page有很好的描述。

    【讨论】:

    • 我最近不得不编写一些校准地震探测器的软件。事实证明,至少有一家公司(纳米计量学)使用 LFSR 来生成用于测量频率响应的“噪声”信号(您将此噪声馈送到检测器,测量响应,然后将响应的 FFT 与输入;它为您提供系统对一系列频率的响应)。
    【解决方案4】:

    这是我的 python 库之一 - pylfsr 用于实现 LFSR。我试图让它高效地处理任何长度的 LFSR 来生成二进制序列。

    import numpy as np
    from pylfsr import LFSR
    
    #for 5-bit LFSR with polynomial  x^5 + x^4 +  x^3 + x^2 +1
    seed = [0,0,0,1,0]
    fpoly = [5,4,3,2]
    L = LFSR(fpoly=fpoly,initstate =seed)
    seq = L.runKCycle(10)
    

    您也可以在步骤中显示所有信息,

    state = [1,1,1]
    fpoly = [3,2]
    L = LFSR(initstate=state,fpoly=fpoly,counter_start_zero=False)
    print('count \t state \t\toutbit \t seq')
    print('-'*50)
    for _ in range(15):
        print(L.count,L.state,'',L.outbit,L.seq,sep='\t')
        L.next()
    print('-'*50)
    print('Output: ',L.seq)
    

    输出

    count    state      outbit   seq
    --------------------------------------------------
    1   [1 1 1]     1   [1]
    2   [0 1 1]     1   [1 1]
    3   [0 0 1]     1   [1 1 1]
    4   [1 0 0]     0   [1 1 1 0]
    5   [0 1 0]     0   [1 1 1 0 0]
    6   [1 0 1]     1   [1 1 1 0 0 1]
    7   [1 1 0]     0   [1 1 1 0 0 1 0]
    8   [1 1 1]     1   [1 1 1 0 0 1 0 1]
    9   [0 1 1]     1   [1 1 1 0 0 1 0 1 1]
    10  [0 0 1]     1   [1 1 1 0 0 1 0 1 1 1]
    11  [1 0 0]     0   [1 1 1 0 0 1 0 1 1 1 0]
    12  [0 1 0]     0   [1 1 1 0 0 1 0 1 1 1 0 0]
    13  [1 0 1]     1   [1 1 1 0 0 1 0 1 1 1 0 0 1]
    14  [1 1 0]     0   [1 1 1 0 0 1 0 1 1 1 0 0 1 0]
    --------------------------------------------------
    Output:  [1 1 1 0 0 1 0 1 1 1 0 0 1 0 1]
    

    也可以这样可视化

    查看Documentation here

    【讨论】:

      【解决方案5】:

      要使其真正优雅和 Pythonic,请尝试创建一个生成器,yield-ing 来自 LFSR 的连续值。此外,与浮点 0.0 进行比较是不必要且令人困惑的。

      LFSR 只是在计算机中创建伪随机数的众多方法之一。伪随机,因为数字不是真正随机的 - 您可以通过从种子(初始值)开始并继续进行相同的数学运算来轻松地重复它们。

      【讨论】:

      • 我会试试的,不过现在我不确定如何去做。我认为 0.0 可以让我避免另一种表达方式......事实上你是对的,它没用。所以它为每个状态生成一个数字,我的意思是,状态是它生成的数字?它还有其他用途吗?
      • @Mattia:你看到维基百科上的“应用程序”部分了吗?看起来信息很丰富
      • 它很好,但它的重点仍然是硬件应用程序,我也希望看到一些代码。
      【解决方案6】:

      以下是使用整数和二元运算符而不是字符串的代码变体。它还按照某人的建议使用 yield。

      def lfsr2(seed, taps):
          sr = seed
          nbits = 8
          while 1:
              xor = 1
              for t in taps:
                  if (sr & (1<<(t-1))) != 0:
                      xor ^= 1
              sr = (xor << nbits-1) + (sr >> 1)
              yield xor, sr
              if sr == seed:
                  break
      
      nbits = 8
      for xor, sr in lfsr2(0b11001001, (8,7,6,1)):
          print xor, bin(2**nbits+sr)[3:]
      

      【讨论】:

        【解决方案7】:

        如果我们假设种子是一个整数列表而不是一个字符串(或者如果不是,则将其转换),那么以下应该更优雅地完成您想要的操作:

        def lfsr(seed, taps) :
          while True:
            nxt = sum([ seed[x] for x in taps]) % 2
            yield nxt
            seed = ([nxt] + seed)[:max(taps)+1]
        

        例子:

        for x in lfsr([1,0,1,1,1,0,1,0,0],[1,5,6]) :
          print x
        

        【讨论】:

        • 这对我来说真的很有趣,但由于某种原因我仍然无法运行它。你能举个例子来说明它的用法吗?谢谢
        【解决方案8】:
        list_init=[1,0,1,1]
        list_coeff=[1,1,0,0]
        out=[]
        for i in range(15):
            list_init.append(sum([list_init[i]*list_coeff[i] for i in range(len(list_init))])%2)
            out.append(list_init.pop(0))
        print(out)
        
        #https://www.rocq.inria.fr/secret/Anne.Canteaut/encyclopedia.pdf
        

        【讨论】:

        • 请解释你的代码做了什么以及为什么。简单地发布代码通常不是一个好的答案
        • 我的 list_init 是 anne 的 pdf 所称的阶段,我的 list_coeff 是 pdf 中所谓的 lfsr 的反馈系数,结果存储在一个新的列表中,特别是这个有系数pdf 中给出的示例的各个阶段欢呼 ciao :)
        【解决方案9】:

        这里有一段代码,您可以在其中选择种子、位数和您想要的抽头:

        from functools import reduce 
        
        def lfsr(seed=1, bits=8, taps=[8, 6, 5, 4]):
            """
                    1 2 3 4 5 6 7 8 (bits == 8)
                   ┌─┬─┬─┬─┬─┬─┬─┬─┐
                ┌─→│0│1│0│1│0│0│1│1├─→
                │  └─┴─┴─┴┬┴┬┴─┴┬┴─┘
                └──────XOR┘ │   │
                        └──XOR──┘ (taps == 7, 5, 4)
            """
            taps = [bits - tap for tap in taps]
            r = seed & (1 << bits) - 1
            while(1):        
                tap_bits = [(r >> tap) & 1 for tap in taps]        
                bit = reduce(lambda x, y : x ^ y, tap_bits)
                yield r & 1
                r &= (1 << bits) - 1
                r = (r >> 1) | (bit << (bits - 1))
        

        【讨论】:

          【解决方案10】:

          这个类提供了一个易于使用的 LFSR 生成器对象

          import numpy as np
          
          class lfsr:
              
              def __init__(self, seed=1, nbits=8, taps=(0,1, 5, 6)): # different taps may not work well. I suggest looking for a standard configuration
                  self.seed0 =seed
                  self.seed = seed
                  self.nbits = nbits
                  self.bmask = (2**nbits)-1
                  self.taps = taps
          
                  
              def next_rnd(self):
                  b_in = 0
                  for t in self.taps:
                      o = 2**t                                                
                      b_in ^= (o&self.seed)>>t                                         
                  
                  self.seed =(self.seed >> 1) | (b_in << (self.nbits-1))
                  self.seed = self.seed & self.bmask          
                  return self.seed
                  
                  
              def print_s(self):
                  print(self.seed)        
                        
                  
              def get_rnd_array(self, seed=None):
                  self.seed = seed if seed is not None else self.seed        
                  arr = np.zeros((2**self.nbits))      
                  for i in range(2**self.nbits):
                      arr[i] = self.next_rnd()
                      
                  return arr
          
              def get_double_rnd_array_circular(self, seed=None): # ref.: Compact and Accurate Stochastic Circuits with Shared Random Number Sources
                  k = int(self.nbits/2)
                  self.seed = seed if seed is not None else self.seed        
                  arr0 = np.zeros((2**self.nbits)) 
                  arr1 = np.zeros((2**self.nbits)) 
                  
                  for i in range(2**self.nbits):
                      rnd = self.next_rnd()            
                      arr0[i] = rnd                         
                      rnd_p0 = rnd >> k            
                      rnd_p1 = (rnd & (2**k-1)) << k                      
                      rnd_p2 = rnd_p1 | rnd_p0                    
                      arr1[i] = rnd_p2            
                  return arr0, arr1
              
              
          l = lfsr(1, 4, (0,1))
          
          
          print(l.get_rnd_array(11))
          print(l.get_double_rnd_array_circular(11))
          
          

          【讨论】:

            猜你喜欢
            • 2017-11-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-04-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多