From b16924381c90e07d5d8cf5bc236c7dc48a0fd677 Mon Sep 17 00:00:00 2001
From: Elof Wecksell <elof@wecksell.se>
Date: Tue, 12 Jan 2021 10:35:31 +0100
Subject: gr-digital: improved performance and capability of scramblers to
 64-bit registers. Use __builtin_parity or volk popcnt. qa was also enhanced
 to detect errors.

Signed-off-by: Elof Wecksell <elof@wecksell.se>
Co-authored-by: Martin Braun <martin@gnuradio.org>
---
 gr-digital/python/digital/CMakeLists.txt           |  1 +
 .../bindings/additive_scrambler_bb_python.cc       |  4 +-
 .../digital/bindings/descrambler_bb_python.cc      |  4 +-
 gr-digital/python/digital/bindings/glfsr_python.cc |  6 +-
 .../digital/bindings/glfsr_source_b_python.cc      |  4 +-
 .../digital/bindings/glfsr_source_f_python.cc      |  4 +-
 gr-digital/python/digital/bindings/lfsr_python.cc  |  6 +-
 .../python/digital/bindings/scrambler_bb_python.cc |  4 +-
 gr-digital/python/digital/qa_glfsr_source.py       |  6 +-
 gr-digital/python/digital/qa_lfsr.py               | 19 ++++-
 gr-digital/python/digital/qa_scrambler.py          | 81 +++++++++++++++++++++-
 gr-digital/python/digital/utils/__init__.py        |  2 +
 gr-digital/python/digital/utils/lfsr.py            | 21 ++++++
 13 files changed, 138 insertions(+), 24 deletions(-)
 create mode 100644 gr-digital/python/digital/utils/lfsr.py

(limited to 'gr-digital/python/digital')

diff --git a/gr-digital/python/digital/CMakeLists.txt b/gr-digital/python/digital/CMakeLists.txt
index b425e4dc72..5dc40f8e3a 100644
--- a/gr-digital/python/digital/CMakeLists.txt
+++ b/gr-digital/python/digital/CMakeLists.txt
@@ -37,6 +37,7 @@ GR_PYTHON_INSTALL(
     FILES
     utils/__init__.py
     utils/gray_code.py
+    utils/lfsr.py
     utils/mod_codes.py
     utils/alignment.py
     utils/tagged_streams.py
diff --git a/gr-digital/python/digital/bindings/additive_scrambler_bb_python.cc b/gr-digital/python/digital/bindings/additive_scrambler_bb_python.cc
index 8e25963e31..76bbe87eaf 100644
--- a/gr-digital/python/digital/bindings/additive_scrambler_bb_python.cc
+++ b/gr-digital/python/digital/bindings/additive_scrambler_bb_python.cc
@@ -13,8 +13,8 @@
 /* If manual edits are made, the following tags should be modified accordingly.    */
 /* BINDTOOL_GEN_AUTOMATIC(0)                                                       */
 /* BINDTOOL_USE_PYGCCXML(0)                                                        */
-/* BINDTOOL_HEADER_FILE(additive_scrambler_bb.h)                                        */
-/* BINDTOOL_HEADER_FILE_HASH(a35bcdbbc4e320dd37c13a0adb156c57)                     */
+/* BINDTOOL_HEADER_FILE(additive_scrambler_bb.h)                                   */
+/* BINDTOOL_HEADER_FILE_HASH(526b44ff08fc8ae301bc1e7a3508c540)                     */
 /***********************************************************************************/
 
 #include <pybind11/complex.h>
diff --git a/gr-digital/python/digital/bindings/descrambler_bb_python.cc b/gr-digital/python/digital/bindings/descrambler_bb_python.cc
index 2f1aa8dccc..6d8d4344a6 100644
--- a/gr-digital/python/digital/bindings/descrambler_bb_python.cc
+++ b/gr-digital/python/digital/bindings/descrambler_bb_python.cc
@@ -13,8 +13,8 @@
 /* If manual edits are made, the following tags should be modified accordingly.    */
 /* BINDTOOL_GEN_AUTOMATIC(0)                                                       */
 /* BINDTOOL_USE_PYGCCXML(0)                                                        */
-/* BINDTOOL_HEADER_FILE(descrambler_bb.h)                                        */
-/* BINDTOOL_HEADER_FILE_HASH(d630c5081c027a198e21e0622963b40b)                     */
+/* BINDTOOL_HEADER_FILE(descrambler_bb.h)                                          */
+/* BINDTOOL_HEADER_FILE_HASH(af9fc52c6846ef88340101d3c6bbacc7)                     */
 /***********************************************************************************/
 
 #include <pybind11/complex.h>
diff --git a/gr-digital/python/digital/bindings/glfsr_python.cc b/gr-digital/python/digital/bindings/glfsr_python.cc
index e716305931..4f120b0e1e 100644
--- a/gr-digital/python/digital/bindings/glfsr_python.cc
+++ b/gr-digital/python/digital/bindings/glfsr_python.cc
@@ -13,8 +13,8 @@
 /* If manual edits are made, the following tags should be modified accordingly.    */
 /* BINDTOOL_GEN_AUTOMATIC(0)                                                       */
 /* BINDTOOL_USE_PYGCCXML(0)                                                        */
-/* BINDTOOL_HEADER_FILE(glfsr.h)                                        */
-/* BINDTOOL_HEADER_FILE_HASH(4299ef7303d0f404e97b16abb6de32ee)                     */
+/* BINDTOOL_HEADER_FILE(glfsr.h)                                                   */
+/* BINDTOOL_HEADER_FILE_HASH(0a9a03db1fc3d6a7b81b009e620c41c2)                     */
 /***********************************************************************************/
 
 #include <pybind11/complex.h>
@@ -35,7 +35,7 @@ void bind_glfsr(py::module& m)
 
     py::class_<glfsr, std::shared_ptr<glfsr>>(m, "glfsr", D(glfsr))
 
-        .def(py::init<uint32_t, uint32_t>(),
+        .def(py::init<uint64_t, uint64_t>(),
              py::arg("mask"),
              py::arg("seed"),
              D(glfsr, glfsr, 0))
diff --git a/gr-digital/python/digital/bindings/glfsr_source_b_python.cc b/gr-digital/python/digital/bindings/glfsr_source_b_python.cc
index 5c8a850f41..17b3e7636f 100644
--- a/gr-digital/python/digital/bindings/glfsr_source_b_python.cc
+++ b/gr-digital/python/digital/bindings/glfsr_source_b_python.cc
@@ -13,8 +13,8 @@
 /* If manual edits are made, the following tags should be modified accordingly.    */
 /* BINDTOOL_GEN_AUTOMATIC(0)                                                       */
 /* BINDTOOL_USE_PYGCCXML(0)                                                        */
-/* BINDTOOL_HEADER_FILE(glfsr_source_b.h)                                        */
-/* BINDTOOL_HEADER_FILE_HASH(7b6d09b85b54ce73f4e3c80b1809bed7)                     */
+/* BINDTOOL_HEADER_FILE(glfsr_source_b.h)                                          */
+/* BINDTOOL_HEADER_FILE_HASH(d5e395b769198beb82a644f02b11f443)                     */
 /***********************************************************************************/
 
 #include <pybind11/complex.h>
diff --git a/gr-digital/python/digital/bindings/glfsr_source_f_python.cc b/gr-digital/python/digital/bindings/glfsr_source_f_python.cc
index 4e8a9883a0..7926ae0e09 100644
--- a/gr-digital/python/digital/bindings/glfsr_source_f_python.cc
+++ b/gr-digital/python/digital/bindings/glfsr_source_f_python.cc
@@ -13,8 +13,8 @@
 /* If manual edits are made, the following tags should be modified accordingly.    */
 /* BINDTOOL_GEN_AUTOMATIC(0)                                                       */
 /* BINDTOOL_USE_PYGCCXML(0)                                                        */
-/* BINDTOOL_HEADER_FILE(glfsr_source_f.h)                                        */
-/* BINDTOOL_HEADER_FILE_HASH(fefa5e006f5c5c49a91ff379b1c571d4)                     */
+/* BINDTOOL_HEADER_FILE(glfsr_source_f.h)                                          */
+/* BINDTOOL_HEADER_FILE_HASH(2e85e8ef0b1f9d6585364f2342f14ad1)                     */
 /***********************************************************************************/
 
 #include <pybind11/complex.h>
diff --git a/gr-digital/python/digital/bindings/lfsr_python.cc b/gr-digital/python/digital/bindings/lfsr_python.cc
index 8efe61e005..c971d77f46 100644
--- a/gr-digital/python/digital/bindings/lfsr_python.cc
+++ b/gr-digital/python/digital/bindings/lfsr_python.cc
@@ -13,8 +13,8 @@
 /* If manual edits are made, the following tags should be modified accordingly.    */
 /* BINDTOOL_GEN_AUTOMATIC(0)                                                       */
 /* BINDTOOL_USE_PYGCCXML(0)                                                        */
-/* BINDTOOL_HEADER_FILE(lfsr.h)                                        */
-/* BINDTOOL_HEADER_FILE_HASH(6ad566b46874d5852d96814797e67bc4)                     */
+/* BINDTOOL_HEADER_FILE(lfsr.h)                                                    */
+/* BINDTOOL_HEADER_FILE_HASH(ae0c3aeb06a21a60a4415fe89135120e)                     */
 /***********************************************************************************/
 
 #include <pybind11/complex.h>
@@ -35,7 +35,7 @@ void bind_lfsr(py::module& m)
 
     py::class_<lfsr, std::shared_ptr<lfsr>>(m, "lfsr", D(lfsr))
 
-        .def(py::init<uint32_t, uint32_t, uint32_t>(),
+        .def(py::init<uint64_t, uint64_t, uint32_t>(),
              py::arg("mask"),
              py::arg("seed"),
              py::arg("reg_len"),
diff --git a/gr-digital/python/digital/bindings/scrambler_bb_python.cc b/gr-digital/python/digital/bindings/scrambler_bb_python.cc
index 6632e97261..1afaa9e2e1 100644
--- a/gr-digital/python/digital/bindings/scrambler_bb_python.cc
+++ b/gr-digital/python/digital/bindings/scrambler_bb_python.cc
@@ -13,8 +13,8 @@
 /* If manual edits are made, the following tags should be modified accordingly.    */
 /* BINDTOOL_GEN_AUTOMATIC(0)                                                       */
 /* BINDTOOL_USE_PYGCCXML(0)                                                        */
-/* BINDTOOL_HEADER_FILE(scrambler_bb.h)                                        */
-/* BINDTOOL_HEADER_FILE_HASH(857ba8ebaf0d0a1080493edc410b3aea)                     */
+/* BINDTOOL_HEADER_FILE(scrambler_bb.h)                                            */
+/* BINDTOOL_HEADER_FILE_HASH(83d0f3673216d2c2d68b1833512e01e2)                     */
 /***********************************************************************************/
 
 #include <pybind11/complex.h>
diff --git a/gr-digital/python/digital/qa_glfsr_source.py b/gr-digital/python/digital/qa_glfsr_source.py
index dbe488399b..7fde483ed3 100644
--- a/gr-digital/python/digital/qa_glfsr_source.py
+++ b/gr-digital/python/digital/qa_glfsr_source.py
@@ -29,7 +29,7 @@ class test_glfsr_source(gr_unittest.TestCase):
         self.assertRaises(RuntimeError,
                           lambda: digital.glfsr_source_b(0))
         self.assertRaises(RuntimeError,
-                          lambda: digital.glfsr_source_b(33))
+                          lambda: digital.glfsr_source_b(65))
 
     def test_002_correlation_b(self):
         for degree in range(
@@ -59,8 +59,8 @@ class test_glfsr_source(gr_unittest.TestCase):
         self.assertRaises(RuntimeError,
                           lambda: digital.glfsr_source_f(0))
         self.assertRaises(RuntimeError,
-                          lambda: digital.glfsr_source_f(33))
-
+                          lambda: digital.glfsr_source_f(65))
+        
     def test_005_correlation_f(self):
         for degree in range(
                 1, 11):                # Higher degrees take too long to correlate
diff --git a/gr-digital/python/digital/qa_lfsr.py b/gr-digital/python/digital/qa_lfsr.py
index 0d7a2ccb94..e0db6a382b 100644
--- a/gr-digital/python/digital/qa_lfsr.py
+++ b/gr-digital/python/digital/qa_lfsr.py
@@ -10,9 +10,9 @@
 
 
 import math
-
+import numpy as np
 from gnuradio import gr, gr_unittest, digital
-
+from gnuradio.digital.utils import lfsr_args
 
 class test_lfsr(gr_unittest.TestCase):
 
@@ -25,7 +25,6 @@ class test_lfsr(gr_unittest.TestCase):
     def test_lfsr_001(self):
         reglen = 8
         l = digital.lfsr(1, 1, reglen)
-
         result_data = []
         for i in range(4 * (reglen + 1)):
             result_data.append(l.next_bit())
@@ -33,6 +32,20 @@ class test_lfsr(gr_unittest.TestCase):
         expected_result = 4 * ([1, ] + reglen * [0, ])
         self.assertFloatTuplesAlmostEqual(expected_result, result_data, 5)
 
+    def test_lfsr_002(self):
+        l = digital.lfsr(*lfsr_args(0b1,5,3,0))
+        result_data = [l.next_bit() for _ in range(2*(2**5-1))]
+        
+        expected_result = [1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0,
+                           0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0]*2
+        self.assertEqual(expected_result, result_data)
+
+        seq1 = [l.next_bit() for _ in range(2**5-1)]
+        seq2 = [l.next_bit() for _ in range(2**5-1)]
+        self.assertEqual(seq1,seq2)
+
+        res = (np.convolve(seq1,[1,0,1,0,0,1])%2)
+        self.assertTrue(sum(res[5:-5])==0,"LRS not generated properly")
 
 if __name__ == '__main__':
     gr_unittest.run(test_lfsr)
diff --git a/gr-digital/python/digital/qa_scrambler.py b/gr-digital/python/digital/qa_scrambler.py
index 6f6c35134a..7d87f87b6c 100644
--- a/gr-digital/python/digital/qa_scrambler.py
+++ b/gr-digital/python/digital/qa_scrambler.py
@@ -8,8 +8,9 @@
 #
 #
 
-
 from gnuradio import gr, gr_unittest, digital, blocks
+from gnuradio.digital.utils import lfsr_args
+import numpy as np
 import pmt
 
 # See gr-digital/lib/additive_scrambler_bb_impl.cc for reference.
@@ -25,7 +26,6 @@ def additive_scramble_lfsr(mask, seed, reglen, bpb, data):
         out.append(d ^ scramble_word)
     return out
 
-
 class test_scrambler(gr_unittest.TestCase):
 
     def setUp(self):
@@ -34,6 +34,83 @@ class test_scrambler(gr_unittest.TestCase):
     def tearDown(self):
         self.tb = None
 
+
+    def test_lfsr_002(self):
+        _a = lfsr_args(1,51,3,0)
+        l = digital.lfsr(*_a)
+        seq = [l.next_bit() for _ in range(2**10)]
+        reg = np.zeros(52,np.int8)
+        reg[::-1][(51,3,0),] = 1
+        res = (np.convolve(seq,reg)%2)
+        self.assertTrue(sum(res[52:-52])==0,"LRS not generated properly")
+
+    def test_scrambler_descrambler_001(self):
+        src_data = np.random.randint(0,2,500,dtype=np.int8)
+        src = blocks.vector_source_b(src_data, False)
+        scrambler = digital.scrambler_bb(*lfsr_args(0b1,7,2,0))     # p(x) = x^7 + x^2 + 1
+        descrambler = digital.descrambler_bb(*lfsr_args(0b111,7,2,0)) # we can use any seed here, it is descrambling.
+        m_tap = blocks.vector_sink_b()
+        dst = blocks.vector_sink_b()
+        self.tb.connect(src, scrambler, descrambler, dst)
+        self.tb.connect(scrambler, m_tap)
+        self.tb.run()
+        self.assertEqual(src_data[:-7].tolist(), dst.data()[7:]) # skip garbage during synchronization
+        self.assertEqual(tuple(np.convolve(m_tap.data(),[1,0,0,0,0,1,0,1])%2)[7:-10],
+                tuple(src_data[:-10])) # manual descrambling test
+
+    def test_scrambler_descrambler_002(self):
+        _a = lfsr_args(0b1,51,6,0) #p(x) = x^51+x^6+1
+        src_data = np.random.randint(0,2,1000,dtype=np.int8)
+        src = blocks.vector_source_b(src_data, False)
+        scrambler = digital.scrambler_bb(*_a)
+        m_tap = blocks.vector_sink_b()
+        descrambler = digital.descrambler_bb(*_a)
+        dst = blocks.vector_sink_b()
+        self.tb.connect(src, scrambler, descrambler, dst)
+        self.tb.connect(scrambler, m_tap)
+        self.tb.run()
+        self.assertTrue(np.all(src_data[:-51]==dst.data()[51:])) # skip garbage during synchronization
+        reg = np.zeros(52,np.int8)
+        reg[::-1][(51,6,0),] = 1
+        self.assertTrue(np.all( np.convolve(m_tap.data(),reg)[51:-60]%2 
+            == src_data[:-60])) # manual descrambling test
+
+    def test_scrambler_descrambler_003(self):
+        src_data = np.random.randint(0,2,1000,dtype=np.int8)
+        src = blocks.vector_source_b(src_data, False)
+        scrambler = digital.scrambler_bb(*lfsr_args(1,12,10,3,2,0)) # this is the product of the other two
+        descrambler1 = digital.descrambler_bb(*lfsr_args(1,5,3,0))
+        descrambler2 = digital.descrambler_bb(*lfsr_args(1,7,2,0))
+        dst = blocks.vector_sink_b()
+        self.tb.connect(src, scrambler, descrambler1, descrambler2, dst)
+        self.tb.run()
+        self.assertTrue(np.all(src_data[:-12]==dst.data()[12:])) # skip garbage during synchronization
+
+    def test_additive_scrambler_001(self):
+        _a = lfsr_args(1,51,3,0) #i p(x) = x^51+x^3+1, seed 0x1
+        src_data = np.random.randint(0,2,1000,dtype=np.int8).tolist()
+        src = blocks.vector_source_b(src_data, False)
+        scrambler = digital.additive_scrambler_bb(*_a)
+        descrambler = digital.additive_scrambler_bb(*_a)
+        dst = blocks.vector_sink_b()
+        self.tb.connect(src, scrambler, descrambler, dst)
+        self.tb.run()
+        self.assertEqual(tuple(src_data), tuple(dst.data()))
+
+    def test_additive_scrambler_002(self):
+        _a = lfsr_args(1,51,3,0) #i p(x) = x^51+x^3+1, seed 0x1
+        src_data = [1,]*1000
+        src = blocks.vector_source_b(src_data, False)
+        scrambler = digital.additive_scrambler_bb(*_a)
+        dst = blocks.vector_sink_b()
+        self.tb.connect(src, scrambler, dst)
+        self.tb.run()
+        reg = np.zeros(52,np.int8)
+        reg[::-1][(51,3,0),] = 1
+        res = (np.convolve(dst.data(),reg)%2)[52:-52]
+        self.assertEqual(len(res), sum(res)) # when convolved with mask, 
+                                             # after sync, only 1's would be returned.
+
     def test_scrambler_descrambler(self):
         src_data = [1, ] * 1000
         src = blocks.vector_source_b(src_data, False)
diff --git a/gr-digital/python/digital/utils/__init__.py b/gr-digital/python/digital/utils/__init__.py
index 47378f54c5..0d5aae0b79 100644
--- a/gr-digital/python/digital/utils/__init__.py
+++ b/gr-digital/python/digital/utils/__init__.py
@@ -7,3 +7,5 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 #
 # 
+
+from .lfsr import lfsr_args
diff --git a/gr-digital/python/digital/utils/lfsr.py b/gr-digital/python/digital/utils/lfsr.py
new file mode 100644
index 0000000000..2b8a47cb76
--- /dev/null
+++ b/gr-digital/python/digital/utils/lfsr.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+#
+# Copyright 2020 Free Software Foundation, Inc.
+# 
+# This file is part of GNU Radio
+# 
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# 
+
+def lfsr_args(seed, *exp):
+    """
+    Produce arguments to create scrambler objects from exponent polynomial expressions.
+     seed: start-value of register
+    *exp: exponents of desired polynomial.
+     Example:
+    >>> l = digital.lfsr(*lfrs_args(0b11001,7,1,0))
+    Creates an lfsr object with seed 0b11001, mask 0b1000011, K=6
+    """
+    from functools import reduce
+    return reduce(int.__xor__, map(lambda x:2**x, exp)), seed, max(exp)-1
-- 
cgit v1.2.3