[prim_lfsr] Add support for Fibonacci LFSRs and lockup protection
diff --git a/hw/ip/prim/rtl/prim_lfsr.sv b/hw/ip/prim/rtl/prim_lfsr.sv
index 412be10..afa9813 100644
--- a/hw/ip/prim/rtl/prim_lfsr.sv
+++ b/hw/ip/prim/rtl/prim_lfsr.sv
@@ -2,24 +2,36 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 //
-// Galois type LFSR ([1], internal XOR gates, very fast).
-// Parameterizable width from 4 to 64 bits.
-// Coefficients obtained from [2].
+// This module implements different LFSR types:
+//
+// 0) Galois XOR type LFSR ([1], internal XOR gates, very fast).
+//    Parameterizable width from 4 to 64 bits.
+//    Coefficients obtained from [2].
+//
+// 1) Fibonacci XNOR type LFSR, parameterizable from 3 to 168 bits.
+//    Coefficients obtained from [3].
+//
+// All flavors have an additional entropy input and lockup protection, which
+// reseeds the state once it has accidentally fallen into the all-zero (XOR) or
+// all-one (XNOR) state.
 //
 // Refs: [1] https://en.wikipedia.org/wiki/Linear-feedback_shift_register
 //       [2] https://users.ece.cmu.edu/~koopman/lfsr/
+//       [3] https://www.xilinx.com/support/documentation/application_notes/xapp052.pdf
 
 module prim_lfsr #(
+  // Lfsr Type: 0) Galois XOR, 1) Fibonacci XNOR
+  parameter int unsigned       LfsrType = 0,
   // Lfsr width
-  parameter int unsigned       LfsrDw = 32,
+  parameter int unsigned       LfsrDw   = 32,
   // Width of input to be XOR'd into state (lfsr_q[InDw-1:0])
-  parameter int unsigned       InDw   =  8,
+  parameter int unsigned       InDw     =  8,
   // Width of output tap (from lfsr_q[OutDw-1:0])
-  parameter int unsigned       OutDw  =  8,
+  parameter int unsigned       OutDw    =  8,
   // Lfsr reset state, must be nonzero!
-  parameter logic [LfsrDw-1:0] Seed   = LfsrDw'(1),
+  parameter logic [LfsrDw-1:0] Seed     = LfsrDw'(1),
   // Custom polynomial coeffs
-  parameter logic [LfsrDw-1:0] Custom = '0
+  parameter logic [LfsrDw-1:0] Custom   = '0
 ) (
   input                    clk_i,
   input                    rst_ni,
@@ -29,85 +41,304 @@
 );
 
   // automatically generated with get-lfsr-coeffs.py script
-  localparam int unsigned LUT_OFF = 4;
-  localparam logic [63:0] COEFFS [0:60]  = '{ 64'h9,
-                                              64'h12,
-                                              64'h21,
-                                              64'h41,
-                                              64'h8E,
-                                              64'h108,
-                                              64'h204,
-                                              64'h402,
-                                              64'h829,
-                                              64'h100D,
-                                              64'h2015,
-                                              64'h4001,
-                                              64'h8016,
-                                              64'h10004,
-                                              64'h20013,
-                                              64'h40013,
-                                              64'h80004,
-                                              64'h100002,
-                                              64'h200001,
-                                              64'h400010,
-                                              64'h80000D,
-                                              64'h1000004,
-                                              64'h2000023,
-                                              64'h4000013,
-                                              64'h8000004,
-                                              64'h10000002,
-                                              64'h20000029,
-                                              64'h40000004,
-                                              64'h80000057,
-                                              64'h100000029,
-                                              64'h200000073,
-                                              64'h400000002,
-                                              64'h80000003B,
-                                              64'h100000001F,
-                                              64'h2000000031,
-                                              64'h4000000008,
-                                              64'h800000001C,
-                                              64'h10000000004,
-                                              64'h2000000001F,
-                                              64'h4000000002C,
-                                              64'h80000000032,
-                                              64'h10000000000D,
-                                              64'h200000000097,
-                                              64'h400000000010,
-                                              64'h80000000005B,
-                                              64'h1000000000038,
-                                              64'h200000000000E,
-                                              64'h4000000000025,
-                                              64'h8000000000004,
-                                              64'h10000000000023,
-                                              64'h2000000000003E,
-                                              64'h40000000000023,
-                                              64'h8000000000004A,
-                                              64'h100000000000016,
-                                              64'h200000000000031,
-                                              64'h40000000000003D,
-                                              64'h800000000000001,
-                                              64'h1000000000000013,
-                                              64'h2000000000000034,
-                                              64'h4000000000000001,
-                                              64'h800000000000000D };
+  localparam int unsigned GAL_XOR_LUT_OFF = 4;
+  localparam logic [63:0] GAL_XOR_COEFFS [0:60] =
+    '{ 64'h9,
+       64'h12,
+       64'h21,
+       64'h41,
+       64'h8E,
+       64'h108,
+       64'h204,
+       64'h402,
+       64'h829,
+       64'h100D,
+       64'h2015,
+       64'h4001,
+       64'h8016,
+       64'h10004,
+       64'h20013,
+       64'h40013,
+       64'h80004,
+       64'h100002,
+       64'h200001,
+       64'h400010,
+       64'h80000D,
+       64'h1000004,
+       64'h2000023,
+       64'h4000013,
+       64'h8000004,
+       64'h10000002,
+       64'h20000029,
+       64'h40000004,
+       64'h80000057,
+       64'h100000029,
+       64'h200000073,
+       64'h400000002,
+       64'h80000003B,
+       64'h100000001F,
+       64'h2000000031,
+       64'h4000000008,
+       64'h800000001C,
+       64'h10000000004,
+       64'h2000000001F,
+       64'h4000000002C,
+       64'h80000000032,
+       64'h10000000000D,
+       64'h200000000097,
+       64'h400000000010,
+       64'h80000000005B,
+       64'h1000000000038,
+       64'h200000000000E,
+       64'h4000000000025,
+       64'h8000000000004,
+       64'h10000000000023,
+       64'h2000000000003E,
+       64'h40000000000023,
+       64'h8000000000004A,
+       64'h100000000000016,
+       64'h200000000000031,
+       64'h40000000000003D,
+       64'h800000000000001,
+       64'h1000000000000013,
+       64'h2000000000000034,
+       64'h4000000000000001,
+       64'h800000000000000D };
 
+  // automatically generated with get-lfsr-coeffs.py script
+localparam int unsigned FIB_XNOR_LUT_OFF = 3;
+localparam logic [167:0] FIB_XNOR_COEFFS [0:165] =
+  '{ 168'h6,
+     168'hC,
+     168'h14,
+     168'h30,
+     168'h60,
+     168'hB8,
+     168'h110,
+     168'h240,
+     168'h500,
+     168'h829,
+     168'h100D,
+     168'h2015,
+     168'h6000,
+     168'hD008,
+     168'h12000,
+     168'h20400,
+     168'h40023,
+     168'h90000,
+     168'h140000,
+     168'h300000,
+     168'h420000,
+     168'hE10000,
+     168'h1200000,
+     168'h2000023,
+     168'h4000013,
+     168'h9000000,
+     168'h14000000,
+     168'h20000029,
+     168'h48000000,
+     168'h80200003,
+     168'h100080000,
+     168'h204000003,
+     168'h500000000,
+     168'h801000000,
+     168'h100000001F,
+     168'h2000000031,
+     168'h4400000000,
+     168'hA000140000,
+     168'h12000000000,
+     168'h300000C0000,
+     168'h63000000000,
+     168'hC0000030000,
+     168'h1B0000000000,
+     168'h300003000000,
+     168'h420000000000,
+     168'hC00000180000,
+     168'h1008000000000,
+     168'h3000000C00000,
+     168'h6000C00000000,
+     168'h9000000000000,
+     168'h18003000000000,
+     168'h30000000030000,
+     168'h40000040000000,
+     168'hC0000600000000,
+     168'h102000000000000,
+     168'h200004000000000,
+     168'h600003000000000,
+     168'hC00000000000000,
+     168'h1800300000000000,
+     168'h3000000000000030,
+     168'h6000000000000000,
+     168'hD800000000000000,
+     168'h10000400000000000,
+     168'h30180000000000000,
+     168'h60300000000000000,
+     168'h80400000000000000,
+     168'h140000028000000000,
+     168'h300060000000000000,
+     168'h410000000000000000,
+     168'h820000000001040000,
+     168'h1000000800000000000,
+     168'h3000600000000000000,
+     168'h6018000000000000000,
+     168'hC000000018000000000,
+     168'h18000000600000000000,
+     168'h30000600000000000000,
+     168'h40200000000000000000,
+     168'hC0000000060000000000,
+     168'h110000000000000000000,
+     168'h240000000480000000000,
+     168'h600000000003000000000,
+     168'h800400000000000000000,
+     168'h1800000300000000000000,
+     168'h3003000000000000000000,
+     168'h4002000000000000000000,
+     168'hC000000000000000018000,
+     168'h10000000004000000000000,
+     168'h30000C00000000000000000,
+     168'h600000000000000000000C0,
+     168'hC00C0000000000000000000,
+     168'h140000000000000000000000,
+     168'h200001000000000000000000,
+     168'h400800000000000000000000,
+     168'hA00000000001400000000000,
+     168'h1040000000000000000000000,
+     168'h2004000000000000000000000,
+     168'h5000000000028000000000000,
+     168'h8000000004000000000000000,
+     168'h18600000000000000000000000,
+     168'h30000000000000000C00000000,
+     168'h40200000000000000000000000,
+     168'hC0300000000000000000000000,
+     168'h100010000000000000000000000,
+     168'h200040000000000000000000000,
+     168'h5000000000000000A0000000000,
+     168'h800000010000000000000000000,
+     168'h1860000000000000000000000000,
+     168'h3003000000000000000000000000,
+     168'h4010000000000000000000000000,
+     168'hA000000000140000000000000000,
+     168'h10080000000000000000000000000,
+     168'h30000000000000000000180000000,
+     168'h60018000000000000000000000000,
+     168'hC0000000000000000300000000000,
+     168'h140005000000000000000000000000,
+     168'h200000001000000000000000000000,
+     168'h404000000000000000000000000000,
+     168'h810000000000000000000000000102,
+     168'h1000040000000000000000000000000,
+     168'h3000000000000006000000000000000,
+     168'h5000000000000000000000000000000,
+     168'h8000000004000000000000000000000,
+     168'h18000000000000000000000000030000,
+     168'h30000000030000000000000000000000,
+     168'h60000000000000000000000000000000,
+     168'hA0000014000000000000000000000000,
+     168'h108000000000000000000000000000000,
+     168'h240000000000000000000000000000000,
+     168'h600000000000C00000000000000000000,
+     168'h800000040000000000000000000000000,
+     168'h1800000000000300000000000000000000,
+     168'h2000000000000010000000000000000000,
+     168'h4008000000000000000000000000000000,
+     168'hC000000000000000000000000000000600,
+     168'h10000080000000000000000000000000000,
+     168'h30600000000000000000000000000000000,
+     168'h4A400000000000000000000000000000000,
+     168'h80000004000000000000000000000000000,
+     168'h180000003000000000000000000000000000,
+     168'h200001000000000000000000000000000000,
+     168'h600006000000000000000000000000000000,
+     168'hC00000000000000006000000000000000000,
+     168'h1000000000000100000000000000000000000,
+     168'h3000000000000006000000000000000000000,
+     168'h6000000003000000000000000000000000000,
+     168'h8000001000000000000000000000000000000,
+     168'h1800000000000000000000000000C000000000,
+     168'h20000000000001000000000000000000000000,
+     168'h48000000000000000000000000000000000000,
+     168'hC0000000000000006000000000000000000000,
+     168'h180000000000000000000000000000000000000,
+     168'h280000000000000000000000000000005000000,
+     168'h60000000C000000000000000000000000000000,
+     168'hC00000000000000000000000000018000000000,
+     168'h1800000600000000000000000000000000000000,
+     168'h3000000C00000000000000000000000000000000,
+     168'h4000000080000000000000000000000000000000,
+     168'hC000300000000000000000000000000000000000,
+     168'h10000400000000000000000000000000000000000,
+     168'h30000000000000000000006000000000000000000,
+     168'h600000000000000C0000000000000000000000000,
+     168'hC0060000000000000000000000000000000000000,
+     168'h180000006000000000000000000000000000000000,
+     168'h3000000000C0000000000000000000000000000000,
+     168'h410000000000000000000000000000000000000000,
+     168'hA00140000000000000000000000000000000000000 };
+
+  logic lockup;
   logic [LfsrDw-1:0] lfsr_d, lfsr_q;
+  logic [LfsrDw-1:0] next_lfsr_state, coeffs;
 
-  // Custom Galois polynomial
-  // re-seed the LFSR in case it enters the all-zero state
-  if (Custom) begin : gen_custom
-    assign lfsr_d = (en_i && !lfsr_q) ? Seed                                      :
-                    (en_i)            ? LfsrDw'(data_i) ^ ({LfsrDw{lfsr_q[0]}} &
-                                        Custom[LfsrDw-1:0]) ^ (lfsr_q >> 1)       :
-                                        lfsr_q;
-  end else begin : gen_lut
-    assign lfsr_d = (en_i && !lfsr_q) ? Seed                                                :
-                    (en_i)            ? LfsrDw'(data_i) ^ ({LfsrDw{lfsr_q[0]}} &
-                                        COEFFS[LfsrDw-LUT_OFF][LfsrDw-1:0]) ^ (lfsr_q >> 1) :
-                                        lfsr_q;
+  //////////////////////////////////////////////////////
+  // Galois XOR
+  if (LfsrType == 0) begin : gen_fib_xor
+
+    // if custom polynomial is provided
+    if (Custom) begin : gen_custom
+      assign coeffs = Custom[LfsrDw-1:0];
+    end else begin : gen_lut
+      assign coeffs = GAL_XOR_COEFFS[LfsrDw-GAL_XOR_LUT_OFF][LfsrDw-1:0];
+      // check that the most significant bit of polynomial is 1
+      `ASSERT_INIT(MinLfsrWidth_A, LfsrDw >= $low(GAL_XOR_COEFFS)+GAL_XOR_LUT_OFF)
+      `ASSERT_INIT(MaxLfsrWidth_A, LfsrDw <= $high(GAL_XOR_COEFFS)+GAL_XOR_LUT_OFF)
+    end
+
+    // calculate next state using internal XOR feedback and entropy input
+    assign next_lfsr_state = LfsrDw'(data_i) ^ ({LfsrDw{lfsr_q[0]}} & coeffs) ^ (lfsr_q >> 1);
+
+    // lockup condition is all-zero
+    assign lockup = ~(|lfsr_q);
+
+    // check that seed is not all-zero
+    `ASSERT_INIT(SeedNzCheck_A, |Seed)
+
+  //////////////////////////////////////////////////////
+  // Fibonacci XNOR
+  end else if (LfsrType == 1) begin : gen_fib_xnor
+
+    // if custom polynomial is provided
+    if (Custom) begin : gen_custom
+      assign coeffs = Custom[LfsrDw-1:0];
+    end else begin : gen_lut
+      assign coeffs = FIB_XNOR_COEFFS[LfsrDw-FIB_XNOR_LUT_OFF][LfsrDw-1:0];
+      // check that the most significant bit of polynomial is 1
+      `ASSERT_INIT(MinLfsrWidth_A, LfsrDw >= $low(FIB_XNOR_COEFFS)+FIB_XNOR_LUT_OFF)
+      `ASSERT_INIT(MaxLfsrWidth_A, LfsrDw <= $high(FIB_XNOR_COEFFS)+FIB_XNOR_LUT_OFF)
+    end
+
+    // calculate next state using external XNOR feedback and entropy input
+    assign next_lfsr_state = LfsrDw'(data_i) ^ {lfsr_q[LfsrDw-2:0], ~(^(lfsr_q & coeffs))};
+
+    // lockup condition is all-ones
+    assign lockup = &lfsr_q;
+
+    // check that seed is not all-ones
+    `ASSERT_INIT(SeedNzCheck_A, !(&Seed))
+
+  //////////////////////////////////////////////////////
+  // Unknown
+  end else begin : gen_unknown_type
+    `ASSERT_INIT(UnknownLfsrType_A, 0)
   end
 
+  //////////////////////////////////////////////////////
+  // Shared logic
+  //////////////////////////////////////////////////////
+
+  assign lfsr_d = (en_i && lockup) ? Seed            :
+                  (en_i)           ? next_lfsr_state :
+                                     lfsr_q;
+
   assign data_o  = lfsr_q[OutDw-1:0];
 
   always_ff @(posedge clk_i or negedge rst_ni) begin : p_reg
@@ -118,16 +349,15 @@
     end
   end
 
-  // assertions
-  `ASSERT_INIT(InputWidth, LfsrDw >= InDw)
-  `ASSERT_INIT(OutputWidth, LfsrDw >= OutDw)
-  `ASSERT_INIT(MinLfsrWidth, LfsrDw >= $low(COEFFS)+LUT_OFF)
-  `ASSERT_INIT(MaxLfsrWidth, LfsrDw <= $high(COEFFS)+LUT_OFF)
-  // check that the most significant bit of polynomial is 1
-  `ASSERT_INIT(CoeffNzCheck, (Custom && Custom[LfsrDw-1]) ||
-                             (!Custom) && COEFFS[LfsrDw-LUT_OFF][LfsrDw-1])
-  `ASSERT_INIT(SeedNzCheck, Seed)
-  // check that an all zero LFSR is correctly reseeded
-  `ASSERT(LfsrNzCheck, en_i && !lfsr_q |=> lfsr_q, clk_i, !rst_ni)
+  //////////////////////////////////////////////////////
+  // shared assertions
+  //////////////////////////////////////////////////////
+
+  `ASSERT_INIT(InputWidth_A, LfsrDw >= InDw)
+  `ASSERT_INIT(OutputWidth_A, LfsrDw >= OutDw)
+  // check that a stuck LFSR is correctly reseeded
+  `ASSERT(LfsrLockupCheck_A, en_i && lockup |=> !lockup, clk_i, !rst_ni)
+  // MSB must be one in any case
+  `ASSERT(CoeffCheck_A, coeffs[LfsrDw-1], clk_i, !rst_ni)
 
 endmodule