From ef722275000d8a045e5551410a1f4149344c10e4 Mon Sep 17 00:00:00 2001
From: Yannik Stradmann <yannik.stradmann@kip.uni-heidelberg.de>
Date: Wed, 19 Feb 2020 14:47:23 +0100
Subject: [PATCH] Add access pattern log test for reference generator

Change-Id: I374c45fff60d38b8b5b76ee813d706f19160f2ab
---
 libnux/dls_vx.h                               |   3 +
 test/with_hostcode/log_access_pattern_host.py | 139 ++++++++++++++++++
 .../refgen_access_pattern-ppu.cpp             |  35 +++++
 test/with_hostcode/wscript                    |  65 ++++++++
 wscript                                       |   1 +
 5 files changed, 243 insertions(+)
 create mode 100644 test/with_hostcode/log_access_pattern_host.py
 create mode 100644 test/with_hostcode/refgen_access_pattern-ppu.cpp
 create mode 100644 test/with_hostcode/wscript

diff --git a/libnux/dls_vx.h b/libnux/dls_vx.h
index f6fb225..5cd76b3 100644
--- a/libnux/dls_vx.h
+++ b/libnux/dls_vx.h
@@ -49,3 +49,6 @@ static constexpr omnibus_address_t dls_syndrv_base = 0x1c000000ul;
 
 /* Address for spike injection */
 static constexpr omnibus_address_t dls_spike_base = 0x1c000040ul;
+
+/* MADC and reference current generator configuration base address */
+static constexpr omnibus_address_t madc_base_address = (1ul << 19 | 1ul << 18);
diff --git a/test/with_hostcode/log_access_pattern_host.py b/test/with_hostcode/log_access_pattern_host.py
new file mode 100644
index 0000000..8edfb0b
--- /dev/null
+++ b/test/with_hostcode/log_access_pattern_host.py
@@ -0,0 +1,139 @@
+import json
+import os
+from os.path import join
+import unittest
+
+from dlens_vx.halco import iter_all
+from dlens_vx.halco import PPUOnDLS
+from dlens_vx.sta import PlaybackProgramExecutor, generate, DigitalInit
+from dlens_vx import logger
+from dlens_vx.tools.run_ppu_program import load_and_start_program, \
+    wait_until_ppu_finished, stop_program
+
+_THIS_DIR = os.path.dirname(os.path.realpath(__file__))
+TEST_BINARY_PATH = os.environ.get("TEST_BINARY_PATH",
+                                  join(_THIS_DIR,
+                                       os.pardir,
+                                       os.pardir,
+                                       os.pardir,
+                                       "build",
+                                       "libnux",
+                                       "test",
+                                       "with_hostcode")
+                                  )
+
+FLANGE_LOG_PATH = os.environ.get("FLANGE_LOG_PATH",
+                                 join(_THIS_DIR,
+                                      os.pardir,
+                                      os.pardir,
+                                      os.pardir,
+                                      "hxfpga",
+                                      "units",
+                                      "synplify_wrapper",
+                                      "sim",
+                                      "xrun.log")
+                                 )
+
+
+class LibnuxAccessPatternTestVx(unittest.TestCase):
+    EXECUTOR = PlaybackProgramExecutor()
+
+    class LogParser:
+        LOG_PREFIX = "HX_SIMULATION_LOG: "
+
+        def __init__(self, pattern: str, offset_bytes: int):
+            self.pattern = pattern
+            self.offset_bytes = offset_bytes
+            self.logfile = open(FLANGE_LOG_PATH, "rt")
+
+        def __iter__(self):
+            self.logfile.seek(self.offset_bytes)
+            return self
+
+        def __next__(self):
+            for line in self.logfile:
+                if line.startswith(self.LOG_PREFIX):
+                    event = json.loads(line[len(self.LOG_PREFIX):])
+                    if self.pattern in event:
+                        return event[self.pattern]
+
+            raise StopIteration
+
+        def __del__(self):
+            self.logfile.close()
+
+    @classmethod
+    def setUpClass(cls) -> None:
+        # Connect to some executor (sim or hardware)
+        cls.EXECUTOR.connect()
+
+        # Initialize the chip
+        init_builder, _ = generate(DigitalInit())
+        cls.EXECUTOR.run(init_builder.done())
+
+    @classmethod
+    def tearDownClass(cls) -> None:
+        # Disconnect the executor
+        cls.EXECUTOR.disconnect()
+
+    def run_ppu_program(self, ppu: PPUOnDLS, program_path: str, timeout: int):
+        load_and_start_program(self.EXECUTOR, program_path, ppu)
+        wait_until_ppu_finished(self.EXECUTOR, timeout=timeout, ppu=ppu)
+        ret_code = stop_program(self.EXECUTOR, ppu=ppu,
+                                print_mailbox=False)
+
+        self.assertEqual(0, ret_code,
+                         f"PPU exit code was {ret_code}, expected 0.")
+
+    def test_refgen_access(self):
+        log = logger.get("LibnuxAccessPatternTestVx.test_refgen_access")
+        program = join(TEST_BINARY_PATH, "refgen_access_pattern-ppu.bin")
+        initial_logsize = os.path.getsize(FLANGE_LOG_PATH)
+        log.debug("Initial size of %s: %dbytes." % (FLANGE_LOG_PATH,
+                                                    initial_logsize))
+
+        for ppu in iter_all(PPUOnDLS):
+            log.info("Running test on %s." % ppu)
+            self.run_ppu_program(ppu, program, int(5e5))
+
+        # Evaluate flange log
+        log_parser = self.LogParser("refgen_config_write", initial_logsize)
+        log_events = iter(log_parser)
+
+        initial_run = True
+        for _ in iter_all(PPUOnDLS):
+            # Reset by the first PPU has no effect, all values are already zero
+            if not initial_run:
+                self.assertDictEqual(next(log_events)["rg_i_amp_c"],
+                                     {'0': 0, '1': 0, '2': 15, '3': 20})
+                self.assertDictEqual(next(log_events)["rg_i_amp_c"],
+                                     {'0': 0, '1': 0, '2': 0, '3': 0})
+                self.assertDictEqual(next(log_events)["rg_i_offset_c"],
+                                     {'0': 0, '1': 0, '2': 35, '3': 40})
+                self.assertDictEqual(next(log_events)["rg_i_offset_c"],
+                                     {'0': 0, '1': 0, '2': 0, '3': 0})
+                self.assertDictEqual(next(log_events)["rg_i_slope_c"],
+                                     {'0': 0, '1': 0, '2': 55, '3': 60})
+                self.assertDictEqual(next(log_events)["rg_i_slope_c"],
+                                     {'0': 0, '1': 0, '2': 0, '3': 0})
+
+            initial_run = False
+            self.assertDictEqual(next(log_events)["rg_i_amp_c"],
+                                 {'0': 5, '1': 10, '2': 0, '3': 0})
+            self.assertDictEqual(next(log_events)["rg_i_amp_c"],
+                                 {'0': 5, '1': 10, '2': 15, '3': 20})
+            self.assertDictEqual(next(log_events)["rg_i_offset_c"],
+                                 {'0': 25, '1': 30, '2': 0, '3': 0})
+            self.assertDictEqual(next(log_events)["rg_i_offset_c"],
+                                 {'0': 25, '1': 30, '2': 35, '3': 40})
+            self.assertDictEqual(next(log_events)["rg_i_slope_c"],
+                                 {'0': 45, '1': 50, '2': 0, '3': 0})
+            self.assertDictEqual(next(log_events)["rg_i_slope_c"],
+                                 {'0': 45, '1': 50, '2': 55, '3': 60})
+
+        with self.assertRaises(StopIteration):
+            next(log_events)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/with_hostcode/refgen_access_pattern-ppu.cpp b/test/with_hostcode/refgen_access_pattern-ppu.cpp
new file mode 100644
index 0000000..bdedfb6
--- /dev/null
+++ b/test/with_hostcode/refgen_access_pattern-ppu.cpp
@@ -0,0 +1,35 @@
+#include <array>
+
+#include "libnux/omnibus.h"
+#include "libnux/dls_vx.h"
+
+using namespace libnux;
+
+
+// Reference generator configuration values used within this test
+static constexpr std::array<unsigned int, 4> amp_values{5, 10, 15, 20};
+static constexpr std::array<unsigned int, 4> slope_values{25, 30, 35, 40};
+static constexpr std::array<unsigned int, 4> offset_values{45, 50, 55, 60};
+
+
+int start()
+{
+	// Zero-initialize all reference generator config registers
+	for (unsigned int i = 21; i < 27; ++i) {
+		omnibus_write(madc_base_address + i, 0);
+	}
+
+	// refgen_amp configuration
+	omnibus_write(madc_base_address + 21, (amp_values.at(1) << 6 | amp_values.at(0)));
+	omnibus_write(madc_base_address + 22, (amp_values.at(3) << 6 | amp_values.at(2)));
+
+	// refgen_slope configuration
+	omnibus_write(madc_base_address + 23, (slope_values.at(1) << 6 | slope_values.at(0)));
+	omnibus_write(madc_base_address + 24, (slope_values.at(3) << 6 | slope_values.at(2)));
+
+	// refgen_offset configuration
+	omnibus_write(madc_base_address + 25, (offset_values.at(1) << 6 | offset_values.at(0)));
+	omnibus_write(madc_base_address + 26, (offset_values.at(3) << 6 | offset_values.at(2)));
+
+	return 0;
+}
diff --git a/test/with_hostcode/wscript b/test/with_hostcode/wscript
new file mode 100644
index 0000000..b3bdabe
--- /dev/null
+++ b/test/with_hostcode/wscript
@@ -0,0 +1,65 @@
+import os
+from os.path import join
+from waflib.extras.test_base import summary
+from waflib.extras.symwaf2ic import get_toplevel_path
+
+
+def depends(ctx):
+    ctx("haldls")
+    ctx("libnux")
+
+
+def options(opt):
+    opt.load("pytest")
+
+
+def configure(conf):
+    conf.load("compiler_cxx")
+    conf.load("python")
+    conf.check_python_version()
+    conf.check_python_headers()
+    conf.load("pytest")
+
+
+def build(bld):
+    bld.env.DLSvx_HARDWARE_AVAILABLE = "cube" == os.environ.get("SLURM_JOB_PARTITION")
+    bld.env.DLSvx_SIM_AVAILABLE = "FLANGE_SIMULATION_RCF_PORT" in os.environ
+
+    build_host_python(bld)
+    build_ppu_cpp(bld)
+
+    bld.add_post_fun(summary)
+
+
+def build_host_python(bld):
+    bld(name="libnux-simtest-log_access_pattern",
+        tests="log_access_pattern_host.py",
+        features="use pytest pylint pycodestyle",
+        use="dlens_vx",
+        install_path="${PREFIX}/bin/tests/sim",
+        pylint_config=join(get_toplevel_path(), "code-format", "pylintrc"),
+        pycodestyle_config=join(get_toplevel_path(), "code-format", "pycodestyle"),
+        skip_run=not bld.env.DLSvx_SIM_AVAILABLE,
+        test_environ=dict(
+            TEST_BINARY_PATH=os.path.join(get_toplevel_path(),
+                                          "build",
+                                          "libnux",
+                                          "test",
+                                          "with_hostcode"),
+            FLANGE_LOG_PATH=os.path.join(get_toplevel_path(),
+                                         "hxfpga",
+                                         "units",
+                                         "synplify_wrapper",
+                                         "sim",
+                                         "xrun.log")),
+        test_timeout=3600
+        )
+
+
+def build_ppu_cpp(bld):
+    bld.program(name="libnux-test-refgen_access_pattern-ppu",
+                features="cxx",
+                target="refgen_access_pattern-ppu.bin",
+                source="refgen_access_pattern-ppu.cpp",
+                use=["nux_runtime_vx"],
+                env=bld.all_envs["nux_vx"])
diff --git a/wscript b/wscript
index b0c05e1..ad200ba 100644
--- a/wscript
+++ b/wscript
@@ -9,6 +9,7 @@ from waflib.extras.symwaf2ic import get_toplevel_path
 
 def depends(dep):
     dep("haldls")
+    dep("libnux", "test/with_hostcode")
 
 
 def options(opt):
-- 
GitLab