Add copy of waveform-generator
diff --git a/.gitmodules b/.gitmodules
index a6a3ac0..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +0,0 @@
-[submodule "verilog/rtl/waveform-generator"]
-	path = verilog/rtl/waveform-generator
-	url = https://codeberg.org/mole99/waveform-generator
diff --git a/verilog/rtl/waveform-generator b/verilog/rtl/waveform-generator
deleted file mode 160000
index 3afa6c9..0000000
--- a/verilog/rtl/waveform-generator
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3afa6c9c8eb7268c48bfb7fba8a825d9a69643f2
diff --git a/verilog/rtl/waveform-generator/LICENSE b/verilog/rtl/waveform-generator/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/verilog/rtl/waveform-generator/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/verilog/rtl/waveform-generator/design/wfg_core/README.md b/verilog/rtl/waveform-generator/design/wfg_core/README.md
new file mode 100644
index 0000000..7c09f69
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_core/README.md
@@ -0,0 +1,13 @@
+<img align="right" src="https://github.com/semify-eda/wfg/blob/main/doc/semify.png" width="100" height="100" >
+
+*Copyright © 2021* [Semify EDA](
+https://github.com/semify-eda)
+
+## wfg_core documentation
+#### Overview
+This module is responsible to synchronize all implemented modules, ensuring data transfers with a set frequency.
+According to the configuration parameter wfg_pat_subcycle, clock cycles are counted and a subcycle pulse is generated. This subcycle enables finer division of the synchronization pulse and is used for synchronizations with phase shifts. The configuration parameter wfg_pat_sync specifies the number of subcycle pulses during a sync period. The resulting sync frequency 
+
+![Formula](formula.png)
+
+can be determined using these two parameters. The outputs of the module are subcycle and sync pulses, as well as three optional status signals (active, start pulse, subcyle counter).
\ No newline at end of file
diff --git a/verilog/rtl/waveform-generator/design/wfg_core/data/wfg_core_reg.csv b/verilog/rtl/waveform-generator/design/wfg_core/data/wfg_core_reg.csv
new file mode 100644
index 0000000..237ec1d
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_core/data/wfg_core_reg.csv
@@ -0,0 +1,8 @@
+Address,RegName,BitName,Access,HW,MSB,LSB,Reset,Description
+4'h0,CTRL,,,,,,,Core control register
+,,EN,rw,cfg,0,0,1'b0,Core enable
+4'h4,CFG,,,,,,,Core configuration register
+,,SYNC,rw,cfg,7,0,0,"Sync pulse clock divider.
+Count clk until threshold is reached"
+,,SUBCYCLE,rw,cfg,23,8,0,"Subcycle pulse clock divider.
+Count clk until threshold is reached"
diff --git a/verilog/rtl/waveform-generator/design/wfg_core/data/wfg_core_reg.json b/verilog/rtl/waveform-generator/design/wfg_core/data/wfg_core_reg.json
new file mode 100644
index 0000000..d84ee99
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_core/data/wfg_core_reg.json
@@ -0,0 +1,40 @@
+{
+    "registers": {
+        "CFG": {
+            "address": "4'h4",
+            "description": "Core configuration register",
+            "entries": {
+                "SUBCYCLE": {
+                    "LSB": "8",
+                    "MSB": "23",
+                    "access": "rw",
+                    "description": "Subcycle pulse clock divider.\nCount clk until threshold is reached",
+                    "hardware": "cfg",
+                    "reset": "0"
+                },
+                "SYNC": {
+                    "LSB": "0",
+                    "MSB": "7",
+                    "access": "rw",
+                    "description": "Sync pulse clock divider.\nCount clk until threshold is reached",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        },
+        "CTRL": {
+            "address": "4'h0",
+            "description": "Core control register",
+            "entries": {
+                "EN": {
+                    "LSB": "0",
+                    "MSB": "0",
+                    "access": "rw",
+                    "description": "Core enable",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/verilog/rtl/waveform-generator/design/wfg_core/formula.png b/verilog/rtl/waveform-generator/design/wfg_core/formula.png
new file mode 100644
index 0000000..0e6fda0
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_core/formula.png
Binary files differ
diff --git a/verilog/rtl/waveform-generator/design/wfg_core/rtl/wfg_core.sv b/verilog/rtl/waveform-generator/design/wfg_core/rtl/wfg_core.sv
new file mode 100644
index 0000000..099ea3c
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_core/rtl/wfg_core.sv
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_core (
+    input wire clk,    // I; System clock
+    input wire rst_n,  // I; Active low reset
+    input wire en_i,   // I; Enable signal
+
+    // Config
+    input wire [ 7:0] wfg_sync_count_i,     // I; Sync counter threshold
+    input wire [15:0] wfg_subcycle_count_i, // I; Subcycle counter threshold
+
+    // Signals
+    output wire       wfg_core_sync_o,          // O; Sync signal
+    output wire       wfg_core_subcycle_o,      // O; Subcycle signal
+    output wire       wfg_core_start_o,         // O; Indicate start
+    output wire [7:0] wfg_core_subcycle_cnt_o,  // O; Subcycle pulse counter
+    output wire       active_o                  // O; Active indication signal
+);
+
+    // -------------------------------------------------------------------------
+    // Definition
+    // -------------------------------------------------------------------------
+
+    logic [15:0]   subcycle_count;   // L; counting variable
+    logic [ 7:0]   sync_count;       // L; counting variable
+    logic        temp_subcycle;    // L; Internal subcycle
+    logic        temp_sync;        // L; Internal subcycle
+    logic        subcycle_dly;        // L; Rising edge det subcycle
+    logic        sync_dly;            // L; Rising edge det sync
+    logic        en_i_dly;            // L; Rising edge det enable
+    logic [ 7:0]   subcycle_pls_cnt;    // L; Counts Subcycles
+
+    // -------------------------------------------------------------------------
+    // Implementation
+    // -------------------------------------------------------------------------
+
+    always @(posedge clk, negedge rst_n) begin
+
+        if (~rst_n) begin
+            subcycle_count <= 16'd0;
+            sync_count    <=  8'd0;
+            temp_subcycle <=  1'b0;
+            temp_sync    <=  1'b0;
+            subcycle_pls_cnt  <=  8'd0;
+        end else begin
+
+            if (en_i) begin
+
+                if (subcycle_count > 0) begin
+                    temp_subcycle  <= temp_subcycle;
+                    subcycle_count <= subcycle_count - 1;
+                end else begin
+                    temp_subcycle  <= ~temp_subcycle;
+                    subcycle_count <= (wfg_subcycle_count_i);
+
+                    if (sync_count > 0) begin
+                        temp_sync  <= temp_sync;
+                        sync_count <= sync_count - 1;
+                    end else begin
+                        temp_sync  <= ~temp_sync;
+                        sync_count <= (wfg_sync_count_i);
+                    end
+
+                end
+
+                if (wfg_core_subcycle_o) begin
+                    subcycle_pls_cnt <= subcycle_pls_cnt + 1;
+                end
+
+            end else begin
+                subcycle_count   <= 8'd0;
+                sync_count       <= 8'd0;
+                temp_subcycle    <= 1'b0;
+                temp_sync        <= 1'b0;
+                subcycle_pls_cnt <= 8'd0;
+            end
+
+            subcycle_dly <= temp_subcycle;
+            sync_dly     <= temp_sync;
+            en_i_dly     <= en_i;
+
+            if (wfg_core_sync_o) begin
+                subcycle_pls_cnt <= 8'd0;
+            end
+
+        end
+
+    end
+
+    assign wfg_core_subcycle_o     = temp_subcycle & ~subcycle_dly;
+    assign wfg_core_sync_o         = temp_sync & ~sync_dly;
+    assign active_o                = en_i;
+    assign wfg_core_start_o        = en_i & ~en_i_dly;
+    assign wfg_core_subcycle_cnt_o = subcycle_pls_cnt;
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_core/rtl/wfg_core_top.sv b/verilog/rtl/waveform-generator/design/wfg_core/rtl/wfg_core_top.sv
new file mode 100644
index 0000000..0a198e4
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_core/rtl/wfg_core_top.sv
@@ -0,0 +1,83 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_core_top #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                 wb_clk_i,
+    input                 wb_rst_i,
+    input                 wbs_stb_i,
+    input                 wbs_cyc_i,
+    input                 wbs_we_i,
+    input  [(BUSW/8-1):0] wbs_sel_i,
+    input  [  (BUSW-1):0] wbs_dat_i,
+    input  [  (BUSW-1):0] wbs_adr_i,
+    output                wbs_ack_o,
+    output [  (BUSW-1):0] wbs_dat_o,
+
+    // Core synchronisation interface
+    output wire       wfg_core_sync_o,          // O; Sync signal
+    output wire       wfg_core_subcycle_o,      // O; Subcycle signal
+    output wire       wfg_core_start_o,         // O; Indicate start
+    output wire [7:0] wfg_core_subcycle_cnt_o,  // O; Subcycle pulse counter
+    output wire       active_o                  // O; Active indication signal
+);
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_core_reg.json
+    //template: wishbone/instantiate_top.template
+    //marker_template_code
+
+    logic [23: 8] cfg_subcycle_q;          // CFG.SUBCYCLE register output
+    logic [ 7: 0] cfg_sync_q;              // CFG.SYNC register output
+    logic         ctrl_en_q;               // CTRL.EN register output
+
+    //marker_template_end
+
+    wfg_core_wishbone_reg wfg_core_wishbone_reg (
+        .wb_clk_i (wb_clk_i),
+        .wb_rst_i (wb_rst_i),
+        .wbs_stb_i(wbs_stb_i),
+        .wbs_cyc_i(wbs_cyc_i),
+        .wbs_we_i (wbs_we_i),
+        .wbs_sel_i(wbs_sel_i),
+        .wbs_dat_i(wbs_dat_i),
+        .wbs_adr_i(wbs_adr_i),
+        .wbs_ack_o(wbs_ack_o),
+        .wbs_dat_o(wbs_dat_o),
+
+        //marker_template_start
+        //data: ../data/wfg_core_reg.json
+        //template: wishbone/assign_to_module.template
+        //marker_template_code
+
+        .cfg_subcycle_q_o(cfg_subcycle_q),  // CFG.SUBCYCLE register output
+        .cfg_sync_q_o    (cfg_sync_q),      // CFG.SYNC register output
+        .ctrl_en_q_o     (ctrl_en_q)        // CTRL.EN register output
+
+        //marker_template_end
+    );
+
+    wfg_core wfg_core (
+        .clk  (wb_clk_i),  // clock signal
+        .rst_n(!wb_rst_i), // reset signal
+
+        // Control
+        .en_i(ctrl_en_q),  // I; Enable signal
+
+        // Configuration
+        .wfg_sync_count_i    (cfg_sync_q),     // I; Sync counter threshold
+        .wfg_subcycle_count_i(cfg_subcycle_q), // I: Subcycle counter threshold
+
+        // Output
+        .wfg_core_sync_o        (wfg_core_sync_o),          // O; Sync signal
+        .wfg_core_subcycle_o    (wfg_core_subcycle_o),      // O; Subcycle signal
+        .wfg_core_start_o       (wfg_core_start_o),         // O; Indicate start
+        .wfg_core_subcycle_cnt_o(wfg_core_subcycle_cnt_o),  // O; Subcycle pulse counter
+        .active_o               (active_o)                  // O; Active indication signal
+    );
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_core/rtl/wfg_core_wishbone_reg.sv b/verilog/rtl/waveform-generator/design/wfg_core/rtl/wfg_core_wishbone_reg.sv
new file mode 100644
index 0000000..268ff26
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_core/rtl/wfg_core_wishbone_reg.sv
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_core_wishbone_reg #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                       wb_clk_i,
+    input                       wb_rst_i,
+    input                       wbs_stb_i,
+    input                       wbs_cyc_i,
+    input                       wbs_we_i,
+    input        [(BUSW/8-1):0] wbs_sel_i,
+    input        [  (BUSW-1):0] wbs_dat_i,
+    input        [  (BUSW-1):0] wbs_adr_i,
+    output logic                wbs_ack_o,
+    output logic [  (BUSW-1):0] wbs_dat_o,
+
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_core_reg.json
+    //template: wishbone/register_interface.template
+    //marker_template_code
+
+    output logic [23:8] cfg_subcycle_q_o,  // CFG.SUBCYCLE register output
+    output logic [ 7:0] cfg_sync_q_o,      // CFG.SYNC register output
+    output logic        ctrl_en_q_o        // CTRL.EN register output
+
+    //marker_template_end
+);
+
+    //marker_template_start
+    //data: ../data/wfg_core_reg.json
+    //template: wishbone/instantiate_registers.template
+    //marker_template_code
+
+    logic [23: 8] cfg_subcycle_ff;         // CFG.SUBCYCLE FF
+    logic [ 7: 0] cfg_sync_ff;             // CFG.SYNC FF
+    logic         ctrl_en_ff;              // CTRL.EN FF
+
+    //marker_template_end
+
+    // Wishbone write to slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            //marker_template_start
+            //data: ../data/wfg_core_reg.json
+            //template: wishbone/reset_registers.template
+            //marker_template_code
+
+            cfg_subcycle_ff <= 0;
+            cfg_sync_ff     <= 0;
+            ctrl_en_ff      <= 1'b0;
+
+            //marker_template_end
+        end else if (wbs_stb_i && wbs_we_i && wbs_cyc_i) begin
+            case (wbs_adr_i)
+                //marker_template_start
+                //data: ../data/wfg_core_reg.json
+                //template: wishbone/assign_to_registers.template
+                //marker_template_code
+
+                4'h4: begin
+                    cfg_subcycle_ff <= wbs_dat_i[23:8];
+                    cfg_sync_ff     <= wbs_dat_i[7:0];
+                end
+                4'h0:       ctrl_en_ff               <= wbs_dat_i[ 0: 0];
+
+                //marker_template_end
+                default: begin
+                end
+            endcase
+        end
+    end
+
+    // Wishbone read from slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            wbs_dat_o <= '0;
+        end else begin
+            if (wbs_stb_i && !wbs_we_i && wbs_cyc_i) begin
+                wbs_dat_o <= '0;  // default value
+                case (wbs_adr_i)
+                    //marker_template_start
+                    //data: ../data/wfg_core_reg.json
+                    //template: wishbone/assign_from_registers.template
+                    //marker_template_code
+
+                    4'h4: begin
+                        wbs_dat_o[23:8] <= cfg_subcycle_ff;
+                        wbs_dat_o[7:0]  <= cfg_sync_ff;
+                    end
+                    4'h0:       wbs_dat_o[ 0: 0] <= ctrl_en_ff;
+
+                    //marker_template_end
+                    default:    wbs_dat_o <= 'X;
+                endcase
+            end
+        end
+    end
+
+    // Acknowledgement
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) wbs_ack_o <= 1'b0;
+        else wbs_ack_o <= wbs_stb_i && wbs_cyc_i & !wbs_ack_o;
+    end
+
+    //marker_template_start
+    //data: ../data/wfg_core_reg.json
+    //template: wishbone/assign_outputs.template
+    //marker_template_code
+
+    assign cfg_subcycle_q_o = cfg_subcycle_ff;
+    assign cfg_sync_q_o     = cfg_sync_ff;
+    assign ctrl_en_q_o      = ctrl_en_ff;
+
+    //marker_template_end
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_core/sim/Makefile b/verilog/rtl/waveform-generator/design/wfg_core/sim/Makefile
new file mode 100644
index 0000000..b577876
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_core/sim/Makefile
@@ -0,0 +1,19 @@
+# source files
+SRC := $(wildcard ../rtl/*.sv)
+SRC += $(wildcard ../testbench/*.sv)
+
+# defaults
+SIM ?= icarus
+TOPLEVEL_LANG ?= verilog
+
+VERILOG_SOURCES += $(SRC)
+
+# TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file
+TOPLEVEL = wfg_core_tb
+
+# MODULE is the basename of the Python test file
+export PYTHONPATH := $(PYTHONPATH):../testbench/
+MODULE = test_wfg_core
+
+# include cocotb's make rules to take care of the simulator setup
+include $(shell cocotb-config --makefiles)/Makefile.sim
diff --git a/verilog/rtl/waveform-generator/design/wfg_core/testbench/test_wfg_core.py b/verilog/rtl/waveform-generator/design/wfg_core/testbench/test_wfg_core.py
new file mode 100644
index 0000000..0fd7952
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_core/testbench/test_wfg_core.py
@@ -0,0 +1,94 @@
+# SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+# SPDX-License-Identifier: Apache-2.0
+
+import random
+import cocotb
+from cocotb.clock import Clock
+from cocotb.regression import TestFactory
+from cocotb.triggers import Timer, RisingEdge, FallingEdge, ClockCycles
+from cocotbext.wishbone.driver import WishboneMaster
+from cocotbext.wishbone.driver import WBOp
+
+CLK_PER_SYNC = 300
+SYSCLK = 100000000
+DATA_CNT = 10
+
+short_per = Timer(100, units="ns")
+long_time = Timer(100, units="us")
+
+async def set_register(dut, wbs, address, data):
+    dut._log.info(f"Set register {address} : {data}")
+
+    wbRes = await wbs.send_cycle([WBOp(address, data)])
+    
+    rvalues = [wb.datrd for wb in wbRes]
+    dut._log.info(f"Returned values : {rvalues}")
+
+async def configure(dut, wbs, en, sync_count, subcycle_count):
+    await set_register(dut, wbs, 0x4, (sync_count << 0) | (subcycle_count << 8))
+    await set_register(dut, wbs, 0x0, en) # Enable core
+
+@cocotb.coroutine
+async def core_test(dut, en, sync_count, subcycle_count):
+    dut._log.info(f"Configuration: sync_count={sync_count}, subcycle_count={subcycle_count}")
+
+    cocotb.start_soon(Clock(dut.io_wbs_clk, 1/SYSCLK*1e9, units="ns").start())
+
+    dut._log.info("Initialize and reset model")
+
+    # Start reset
+    dut.io_wbs_rst.value = 1    
+    await Timer(100, units='ns')
+
+    # Stop reset
+    dut.io_wbs_rst.value = 0
+
+    # Wishbone Master
+    wbs = WishboneMaster(dut, "io_wbs", dut.io_wbs_clk,
+                              width=32,   # size of data bus
+                              timeout=10) # in clock cycle number
+
+    # Setup core
+    await configure(dut, wbs, en, sync_count, subcycle_count)
+
+    #await short_per
+    #await short_per
+    
+    sync_pulse_count = 0
+    clk_count = 0
+    
+    await FallingEdge(dut.wfg_core_sync_o)
+
+    for i in range ((sync_count + 1) * (subcycle_count + 1) * 3):
+        await ClockCycles(dut.io_wbs_clk, 1)
+        clk_count += 1
+   
+        if dut.wfg_core_sync_o == 1:
+            assert ((sync_pulse_count+1) * clk_count) == ((sync_count + 1) * (subcycle_count + 1) * 2)
+            break
+            
+        if (dut.wfg_core_subcycle_o == 1):
+            sync_pulse_count += 1
+            clk_count = 0
+
+length = 2
+
+sync_array = []
+
+for i in range(length):
+    n = random.randint(1,2**7)
+    sync_array.append(n)
+
+subcycle_array = []
+
+for i in range(length):
+    n = random.randint(1,2**7)
+    subcycle_array.append(n)
+
+
+factory = TestFactory(core_test)
+factory.add_option("en", [1])
+factory.add_option("sync_count", sync_array)
+factory.add_option("subcycle_count", subcycle_array)
+
+factory.generate_tests()
diff --git a/verilog/rtl/waveform-generator/design/wfg_core/testbench/wfg_core_tb.sv b/verilog/rtl/waveform-generator/design/wfg_core/testbench/wfg_core_tb.sv
new file mode 100644
index 0000000..24f1308
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_core/testbench/wfg_core_tb.sv
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`timescale 1ns / 1ps
+
+`ifdef VERILATOR  // make parameter readable from VPI
+`define VL_RD /*verilator public_flat_rd*/
+`else
+`define VL_RD
+`endif
+
+module wfg_core_tb #(
+    parameter int BUSW = 32
+) (
+    // Wishbone interface signals
+    input               io_wbs_clk,
+    input               io_wbs_rst,
+    input  [(BUSW-1):0] io_wbs_adr,
+    input  [(BUSW-1):0] io_wbs_datwr,
+    output [(BUSW-1):0] io_wbs_datrd,
+    input               io_wbs_we,
+    input               io_wbs_stb,
+    output              io_wbs_ack,
+    input               io_wbs_cyc,
+
+    // Core synchronisation interface
+    output wire       wfg_core_sync_o,          // O; Sync signal
+    output wire       wfg_core_subcycle_o,      // O; Subcycle signal
+    output wire       wfg_core_start_o,         // O; Indicate start
+    output wire [7:0] wfg_core_subcycle_cnt_o,  // O; Subcycle pulse counter
+    output wire       active_o                  // O; Active indication signal
+);
+
+    wfg_core_top wfg_core_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(io_wbs_adr),
+        .wbs_ack_o(io_wbs_ack),
+        .wbs_dat_o(io_wbs_datrd),
+
+        // Core synchronisation interface
+        .wfg_core_sync_o(wfg_core_sync_o),
+        .wfg_core_subcycle_o(wfg_core_subcycle_o),
+        .wfg_core_start_o(wfg_core_start_o),
+        .wfg_core_subcycle_cnt_o(wfg_core_subcycle_cnt_o),
+        .active_o(active_o)
+    );
+
+    // Dump waves
+`ifndef VERILATOR
+    initial begin
+        $dumpfile("wfg_core_tb.vcd");
+        $dumpvars(0, wfg_core_tb);
+    end
+`endif
+
+endmodule
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_pat/README.md b/verilog/rtl/waveform-generator/design/wfg_drive_pat/README.md
new file mode 100644
index 0000000..f000339
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_pat/README.md
@@ -0,0 +1,45 @@
+# wfg_drive_pat
+
+The module wfg_drive_pat drives a pattern onto the number of channels specified by the parameter CHANNELS. CHANNELS can range from 1 to 32.
+
+For correct operation it needs to be connected to a wfg_core.
+The signals used are *pat_sync* and *pat_subcycle_cnt*.
+
+The input is received via an axi stream (32 bit) interface. One transaction is performed after each *pat_sync*. The lowest CHANNELS bits are used for transmission, higher bits are discarded.
+
+The fields *begin* and *end* must be set for the channel to work correctly;
+begin can be set to a value of `1` to `subcycles - 1`, while end must be set from `begin+1` to `subcycles`, where `subcycles` is to the number of subcycles at which the core puts out *pat_sync* and in the next cycle resets the count to 0.
+
+Patselect can take values 0-3.
+  - 0 corresponds to *Return to zero (RZ)*
+  - 1 corresponds to *Return to one (RO)*
+  - 2 corresponds to *Non return to one (NRZ)*
+  - 3 corresponds to *Return to complement (RC)*
+
+## Testbench
+
+*WIP*
+
+The module wfg_drive_pat is verified using a testbench written in cocotb and python.
+
+It uses and extends standard components of cocotb.
+
+
+### Testcase generation
+
+Testcases are generated with a `TestFactory`.
+It takes a list of values for each input port and creates a testcase for each of the combinations.
+These values can be fixed to test certain cases, or be set to `None`, in this case they will either be randomized or use a default value.
+
+Input data for the AXI stream is randomly generated for each testcase run.
+
+
+### Output checking
+
+A `OutputMonitor` captures all output values at each clockcycle.
+A `Scoreboard` checks the equivalence of the received values to previously generated expected output.
+
+Expected output is generated at the time the input is randomized. For each clockcycle it is calculated what the corresponding output should be based on the input and the *pat_subcycle_cnt*.
+
+
+
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_pat/data/wfg_drive_pat_reg.csv b/verilog/rtl/waveform-generator/design/wfg_drive_pat/data/wfg_drive_pat_reg.csv
new file mode 100644
index 0000000..82b4c89
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_pat/data/wfg_drive_pat_reg.csv
@@ -0,0 +1,24 @@
+Address,RegName,BitName,Access,HW,MSB,LSB,Reset,Description
+4'h0,CTRL,,,,,,,Control register for pattern unit
+,,EN,rw,cfg,31,0,0,Parallel Interface Output enable
+4'h4,CFG,,,,,,,Pattern configuration register
+,,BEGIN,rw,cfg,7,0,0,"Select at which subcycle the output occurs, binary encoded"
+,,END,rw,cfg,15,8,0,"Select at which subcycle the transition according to PATSEL occurs, binary encoded"
+,,CORE_SEL,rw,cfg,16,16,1'b0,"0 : core
+1 : subcore"
+4'h8,PATSEL0,,,,,,,Low bits of PATSEL
+,,LOW,rw,cfg,31,0,0,"Select Output Format
+  
+   b00: Return to zero (RZ)
+   b01: Return to one (RO)
+   b10: Non return to one (NRZ)
+   B11: Return to complement (RC)
+"
+4'hC,PATSEL1,,,,,,,High bits of PATSEL
+,,HIGH,rw,cfg,31,0,0,"Select Output Format
+  
+   b00: Return to zero (RZ)
+   b01: Return to one (RO)
+   b10: Non return to one (NRZ)
+   B11: Return to complement (RC)
+"
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_pat/data/wfg_drive_pat_reg.json b/verilog/rtl/waveform-generator/design/wfg_drive_pat/data/wfg_drive_pat_reg.json
new file mode 100644
index 0000000..3e3d6a3
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_pat/data/wfg_drive_pat_reg.json
@@ -0,0 +1,76 @@
+{
+    "registers": {
+        "CFG": {
+            "address": "4'h4",
+            "description": "Pattern configuration register",
+            "entries": {
+                "BEGIN": {
+                    "LSB": "0",
+                    "MSB": "7",
+                    "access": "rw",
+                    "description": "Select at which subcycle the output occurs, binary encoded",
+                    "hardware": "cfg",
+                    "reset": "0"
+                },
+                "CORE_SEL": {
+                    "LSB": "16",
+                    "MSB": "16",
+                    "access": "rw",
+                    "description": "0 : core\n1 : subcore",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                },
+                "END": {
+                    "LSB": "8",
+                    "MSB": "15",
+                    "access": "rw",
+                    "description": "Select at which subcycle the transition according to PATSEL occurs, binary encoded",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        },
+        "CTRL": {
+            "address": "4'h0",
+            "description": "Control register for pattern unit",
+            "entries": {
+                "EN": {
+                    "LSB": "0",
+                    "MSB": "31",
+                    "access": "rw",
+                    "description": "Parallel Interface Output enable",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        },
+        "PATSEL0": {
+            "address": "4'h8",
+            "description": "Low bits of PATSEL",
+            "entries": {
+                "LOW": {
+                    "LSB": "0",
+                    "MSB": "31",
+                    "access": "rw",
+                    "description": "Select Output Format\n  \n   b00: Return to zero (RZ)\n   b01: Return to one (RO)\n   b10: Non return to one (NRZ)\n   B11: Return to complement (RC)\n",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        },
+        "PATSEL1": {
+            "address": "4'hC",
+            "description": "High bits of PATSEL",
+            "entries": {
+                "HIGH": {
+                    "LSB": "0",
+                    "MSB": "31",
+                    "access": "rw",
+                    "description": "Select Output Format\n  \n   b00: Return to zero (RZ)\n   b01: Return to one (RO)\n   b10: Non return to one (NRZ)\n   B11: Return to complement (RC)\n",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat.sv b/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat.sv
new file mode 100644
index 0000000..20e9f99
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat.sv
@@ -0,0 +1,124 @@
+//                    Copyright Message
+//  --------------------------------------------------------------------------
+//
+//  CONFIDENTIAL and PROPRIETARY
+//  COPYRIGHT (c) semify 2021
+//
+//  All rights are reserved. Reproduction in whole or in part is
+//  prohibited without the written consent of the copyright owner.
+//
+//  ----------------------------------------------------------------------------
+//                    Design Information
+//  ----------------------------------------------------------------------------
+//
+//  Author: Erwin Peterlin
+//
+//  Description : wfg_drive_pat
+//
+
+module wfg_drive_pat #(
+    parameter int CHANNELS   = 8,
+    parameter int AXIS_WIDTH = 32
+) (
+    input logic clk,   // I; System clock
+    input logic rst_n, // I; Active low reset
+
+    // Core synchronisation interface
+    input logic       wfg_core_sync_i,         // I; Start Pulse for one cycle
+    input logic [7:0] wfg_core_subcycle_cnt_i, // I; Current Subcycle
+
+    // Subcore synchronisation interface
+    input logic       wfg_subcore_sync_i,         // I; Start Pulse for one cycle
+    input logic [7:0] wfg_subcore_subcycle_cnt_i, // I; Current Subcycle
+
+
+    input logic [CHANNELS-1:0] ctrl_en_q_i,  // I; Enable signal
+    input logic [(CHANNELS*2)-1:0] patsel_q_i,  // I; Output pattern selector
+    input logic [7:0] cfg_begin_q_i,  // I; Selects at which subcycle the output begins
+    input logic [7:0] cfg_end_q_i,  // I; Selects at which subcycle the output ends
+    input logic cfg_core_sel_q_i,  // I; Core select
+
+    // AXI streaming interface
+    output wire        wfg_axis_tready_o,  // O; ready
+    input  wire        wfg_axis_tvalid_i,  // I; valid
+    input  wire        wfg_axis_tlast_i,   // I; last
+    input  wire [31:0] wfg_axis_tdata_i,   // I; data
+
+    output logic [CHANNELS-1:0] pat_dout_o,    // O; output pins
+    output logic [CHANNELS-1:0] pat_dout_en_o  // O; output enabled
+);
+
+    // -------------------------------------------------------------------------
+    // Definition
+    // -------------------------------------------------------------------------
+
+    assign pat_dout_en_o = ctrl_en_q_i;
+
+    logic [AXIS_WIDTH-1:0] axis_data_ff;
+    logic axis_ready_ff;
+    assign wfg_axis_tready_o = axis_ready_ff;
+
+    logic wfg_sync_i;
+    logic [7:0] wfg_subcycle_cnt_i;
+
+    // -------------------------------------------------------------------------
+    // Implementation
+    // -------------------------------------------------------------------------
+
+    always_comb begin
+        wfg_sync_i = 'x;
+        wfg_subcycle_cnt_i = 'x;
+
+        case (cfg_core_sel_q_i)
+            1'b0: begin
+                wfg_sync_i         = wfg_core_sync_i;
+                wfg_subcycle_cnt_i = wfg_core_subcycle_cnt_i;
+            end
+            1'b1: begin
+                wfg_sync_i         = wfg_subcore_sync_i;
+                wfg_subcycle_cnt_i = wfg_subcore_subcycle_cnt_i;
+            end
+        endcase
+    end
+
+    genvar k;
+    generate
+        for (k = 0; k < CHANNELS; k++) begin : gen_channels
+            wfg_drive_pat_channel drv (
+                .clk(clk),
+                .rst_n(rst_n),
+                .wfg_core_subcycle_cnt_i(wfg_subcycle_cnt_i),
+                .patsel_q_i({patsel_q_i[k+32], patsel_q_i[k]}),
+                .cfg_begin_q_i(cfg_begin_q_i),
+                .cfg_end_q_i(cfg_end_q_i),
+                .ctrl_en_q_i(ctrl_en_q_i[k]),
+                .axis_data_ff(axis_data_ff[k]),
+                .data_o(pat_dout_o[k])
+            );
+        end
+    endgenerate
+
+    // -------------------------------------------------------------------------
+    // ff
+    // -------------------------------------------------------------------------
+
+    always_ff @(posedge clk or negedge rst_n) begin
+        if (!rst_n) begin
+            axis_data_ff  <= '0;
+            axis_ready_ff <= '0;
+        end else begin
+
+            if (wfg_sync_i) begin
+                axis_ready_ff <= '1;
+
+                if (wfg_axis_tvalid_i) begin
+                    axis_data_ff <= wfg_axis_tdata_i;
+                end
+            end else begin
+                axis_ready_ff <= '0;
+            end
+
+        end
+    end  //always_ff
+endmodule
+
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat_channel.sv b/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat_channel.sv
new file mode 100644
index 0000000..1c4cdb6
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat_channel.sv
@@ -0,0 +1,73 @@
+//  CONFIDENTIAL and PROPRIETARY
+//  COPYRIGHT (c) semify 2021
+//
+//  All rights are reserved. Reproduction in whole or in part is
+//  prohibited without the written consent of the copyright owner.
+//
+//  ----------------------------------------------------------------------------
+//                    Design Information
+//  ----------------------------------------------------------------------------
+//
+//  Author: Erwin Peterlin
+//
+//  Description : wfg_drive_pat_channel
+//
+module wfg_drive_pat_channel (
+    input logic clk,   // I; System clock
+    input logic rst_n, // I; Active low reset
+
+    input logic [7:0] wfg_core_subcycle_cnt_i,
+    input logic [1:0] patsel_q_i,
+    input logic [7:0] cfg_begin_q_i,
+    input logic [7:0] cfg_end_q_i,
+    input logic       ctrl_en_q_i,
+    input logic       axis_data_ff,
+
+    output logic data_o
+);
+
+    logic data_next, data_ff;
+    assign data_o = data_ff;
+
+    always_comb begin
+        data_next = data_ff;
+
+        if (wfg_core_subcycle_cnt_i == cfg_begin_q_i) begin
+            data_next = axis_data_ff;
+        end  //if
+
+        if (wfg_core_subcycle_cnt_i == cfg_end_q_i) begin
+            case (patsel_q_i)
+                2'b00: begin
+                    data_next = '0;
+                end  //RZ
+                2'b01: begin
+                    data_next = '1;
+                end  //RO
+                2'b10: begin
+                    data_next = data_ff;
+                end  //NRZ
+                2'b11: begin
+                    if (data_ff == '0 || data_ff == '1) begin
+                        data_next = !axis_data_ff;
+                    end  //if
+                end  //RC
+                default: data_next = 'x;
+                //default: $error("invalid pat_select");
+            endcase
+        end  //if
+
+        if (!ctrl_en_q_i) begin
+            data_next = '0;
+        end  //if
+    end  //always_comb
+
+
+    always_ff @(posedge clk or negedge rst_n) begin
+        if (!rst_n) begin
+            data_ff <= '0;
+        end else begin
+            data_ff <= data_next;
+        end
+    end  //always_ff
+endmodule
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat_top.sv b/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat_top.sv
new file mode 100644
index 0000000..8000666
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat_top.sv
@@ -0,0 +1,117 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_drive_pat_top #(
+    parameter int BUSW = 32,
+    parameter int AXIS_DATA_WIDTH = 32,
+    parameter int CHANNELS = 32
+) (
+    // Wishbone Slave ports
+    input                 wb_clk_i,
+    input                 wb_rst_i,
+    input                 wbs_stb_i,
+    input                 wbs_cyc_i,
+    input                 wbs_we_i,
+    input  [(BUSW/8-1):0] wbs_sel_i,
+    input  [  (BUSW-1):0] wbs_dat_i,
+    input  [  (BUSW-1):0] wbs_adr_i,
+    output                wbs_ack_o,
+    output [  (BUSW-1):0] wbs_dat_o,
+
+    // Core synchronisation interface
+    input wire        wfg_core_sync_i,         // I; sync pulse
+    input logic [7:0] wfg_core_subcycle_cnt_i, // I; subcycle_cnt
+
+    // Subcore synchronisation interface
+    input logic       wfg_subcore_sync_i,         // I; sync pulse
+    input logic [7:0] wfg_subcore_subcycle_cnt_i, // I; subcycle_cnt
+
+    // AXI-Stream interface
+    output wire wfg_axis_tready_o,
+    input wire [AXIS_DATA_WIDTH-1:0] wfg_axis_tdata_i,
+    input wire wfg_axis_tlast_i,
+    input wire wfg_axis_tvalid_i,
+
+    output logic [CHANNELS-1:0] pat_dout_o,    // O; output pins
+    output logic [CHANNELS-1:0] pat_dout_en_o  // O; output enabled
+);
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_drive_pat_reg.json
+    //template: wishbone/instantiate_top.template
+    //marker_template_code
+
+    logic [ 7: 0] cfg_begin_q;             // CFG.BEGIN register output
+    logic         cfg_core_sel_q;          // CFG.CORE_SEL register output
+    logic [15: 8] cfg_end_q;               // CFG.END register output
+    logic [31: 0] ctrl_en_q;               // CTRL.EN register output
+    logic [31: 0] patsel0_low_q;           // PATSEL0.LOW register output
+    logic [31: 0] patsel1_high_q;          // PATSEL1.HIGH register output
+
+    //marker_template_end
+
+    wfg_drive_pat_wishbone_reg wfg_drive_pat_wishbone_reg (
+        .wb_clk_i (wb_clk_i),
+        .wb_rst_i (wb_rst_i),
+        .wbs_stb_i(wbs_stb_i),
+        .wbs_cyc_i(wbs_cyc_i),
+        .wbs_we_i (wbs_we_i),
+        .wbs_sel_i(wbs_sel_i),
+        .wbs_dat_i(wbs_dat_i),
+        .wbs_adr_i(wbs_adr_i),
+        .wbs_ack_o(wbs_ack_o),
+        .wbs_dat_o(wbs_dat_o),
+
+        //marker_template_start
+        //data: ../data/wfg_drive_pat_reg.json
+        //template: wishbone/assign_to_module.template
+        //marker_template_code
+
+        .cfg_begin_q_o   (cfg_begin_q),     // CFG.BEGIN register output
+        .cfg_core_sel_q_o(cfg_core_sel_q),  // CFG.CORE_SEL register output
+        .cfg_end_q_o     (cfg_end_q),       // CFG.END register output
+        .ctrl_en_q_o     (ctrl_en_q),       // CTRL.EN register output
+        .patsel0_low_q_o (patsel0_low_q),   // PATSEL0.LOW register output
+        .patsel1_high_q_o(patsel1_high_q)   // PATSEL1.HIGH register output
+
+        //marker_template_end
+    );
+
+    wfg_drive_pat #(
+        .CHANNELS  (CHANNELS),
+        .AXIS_WIDTH(AXIS_DATA_WIDTH)
+    ) wfg_drive_pat (
+        .clk  (wb_clk_i),  // clock signal
+        .rst_n(!wb_rst_i), // reset signal
+
+        // Core synchronisation interface
+        .wfg_core_sync_i        (wfg_core_sync_i),
+        .wfg_core_subcycle_cnt_i(wfg_core_subcycle_cnt_i),
+
+        // Subcore synchronisation interface
+        .wfg_subcore_sync_i        (wfg_subcore_sync_i),
+        .wfg_subcore_subcycle_cnt_i(wfg_subcore_subcycle_cnt_i),
+
+        // AXI streaming interface
+        .wfg_axis_tready_o(wfg_axis_tready_o),  // O; ready
+        .wfg_axis_tvalid_i(wfg_axis_tvalid_i),  // I; valid
+        .wfg_axis_tlast_i (wfg_axis_tlast_i),   // I; last
+        .wfg_axis_tdata_i (wfg_axis_tdata_i),   // I; data
+
+        // Control
+        .ctrl_en_q_i(ctrl_en_q),  // I; pat enable
+
+        // Configuration
+        .cfg_begin_q_i   (cfg_begin_q),
+        .cfg_end_q_i     (cfg_end_q),
+        .patsel_q_i      ({patsel1_high_q, patsel0_low_q}),
+        .cfg_core_sel_q_i(cfg_core_sel_q),
+
+        // Output
+        .pat_dout_o   (pat_dout_o),
+        .pat_dout_en_o(pat_dout_en_o)
+    );
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat_wishbone_reg.sv b/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat_wishbone_reg.sv
new file mode 100644
index 0000000..e677fd8
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_pat/rtl/wfg_drive_pat_wishbone_reg.sv
@@ -0,0 +1,138 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_drive_pat_wishbone_reg #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                       wb_clk_i,
+    input                       wb_rst_i,
+    input                       wbs_stb_i,
+    input                       wbs_cyc_i,
+    input                       wbs_we_i,
+    input        [(BUSW/8-1):0] wbs_sel_i,
+    input        [  (BUSW-1):0] wbs_dat_i,
+    input        [  (BUSW-1):0] wbs_adr_i,
+    output logic                wbs_ack_o,
+    output logic [  (BUSW-1):0] wbs_dat_o,
+
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_drive_pat_reg.json
+    //template: wishbone/register_interface.template
+    //marker_template_code
+
+    output logic [ 7:0] cfg_begin_q_o,     // CFG.BEGIN register output
+    output logic        cfg_core_sel_q_o,  // CFG.CORE_SEL register output
+    output logic [15:8] cfg_end_q_o,       // CFG.END register output
+    output logic [31:0] ctrl_en_q_o,       // CTRL.EN register output
+    output logic [31:0] patsel0_low_q_o,   // PATSEL0.LOW register output
+    output logic [31:0] patsel1_high_q_o   // PATSEL1.HIGH register output
+
+    //marker_template_end
+);
+
+    //marker_template_start
+    //data: ../data/wfg_drive_pat_reg.json
+    //template: wishbone/instantiate_registers.template
+    //marker_template_code
+
+    logic [ 7: 0] cfg_begin_ff;            // CFG.BEGIN FF
+    logic         cfg_core_sel_ff;         // CFG.CORE_SEL FF
+    logic [15: 8] cfg_end_ff;              // CFG.END FF
+    logic [31: 0] ctrl_en_ff;              // CTRL.EN FF
+    logic [31: 0] patsel0_low_ff;          // PATSEL0.LOW FF
+    logic [31: 0] patsel1_high_ff;         // PATSEL1.HIGH FF
+
+    //marker_template_end
+
+    // Wishbone write to slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            //marker_template_start
+            //data: ../data/wfg_drive_pat_reg.json
+            //template: wishbone/reset_registers.template
+            //marker_template_code
+
+            cfg_begin_ff    <= 0;
+            cfg_core_sel_ff <= 1'b0;
+            cfg_end_ff      <= 0;
+            ctrl_en_ff      <= 0;
+            patsel0_low_ff  <= 0;
+            patsel1_high_ff <= 0;
+
+            //marker_template_end
+        end else if (wbs_stb_i && wbs_we_i && wbs_cyc_i) begin
+            case (wbs_adr_i)
+                //marker_template_start
+                //data: ../data/wfg_drive_pat_reg.json
+                //template: wishbone/assign_to_registers.template
+                //marker_template_code
+
+                4'h4: begin
+                    cfg_begin_ff    <= wbs_dat_i[7:0];
+                    cfg_core_sel_ff <= wbs_dat_i[16:16];
+                    cfg_end_ff      <= wbs_dat_i[15:8];
+                end
+                4'h0:       ctrl_en_ff               <= wbs_dat_i[31: 0];
+                4'h8:       patsel0_low_ff           <= wbs_dat_i[31: 0];
+                4'hC:       patsel1_high_ff          <= wbs_dat_i[31: 0];
+
+                //marker_template_end
+                default: begin
+                end
+            endcase
+        end
+    end
+
+    // Wishbone read from slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            wbs_dat_o <= '0;
+        end else begin
+            if (wbs_stb_i && !wbs_we_i && wbs_cyc_i) begin
+                wbs_dat_o <= '0;  // default value
+                case (wbs_adr_i)
+                    //marker_template_start
+                    //data: ../data/wfg_drive_pat_reg.json
+                    //template: wishbone/assign_from_registers.template
+                    //marker_template_code
+
+                    4'h4: begin
+                        wbs_dat_o[7:0]   <= cfg_begin_ff;
+                        wbs_dat_o[16:16] <= cfg_core_sel_ff;
+                        wbs_dat_o[15:8]  <= cfg_end_ff;
+                    end
+                    4'h0:       wbs_dat_o[31: 0] <= ctrl_en_ff;
+                    4'h8:       wbs_dat_o[31: 0] <= patsel0_low_ff;
+                    4'hC:       wbs_dat_o[31: 0] <= patsel1_high_ff;
+
+                    //marker_template_end
+                    default:    wbs_dat_o <= 'X;
+                endcase
+            end
+        end
+    end
+
+    // Acknowledgement
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) wbs_ack_o <= 1'b0;
+        else wbs_ack_o <= wbs_stb_i && wbs_cyc_i & !wbs_ack_o;
+    end
+
+    //marker_template_start
+    //data: ../data/wfg_drive_pat_reg.json
+    //template: wishbone/assign_outputs.template
+    //marker_template_code
+
+    assign cfg_begin_q_o    = cfg_begin_ff;
+    assign cfg_core_sel_q_o = cfg_core_sel_ff;
+    assign cfg_end_q_o      = cfg_end_ff;
+    assign ctrl_en_q_o      = ctrl_en_ff;
+    assign patsel0_low_q_o  = patsel0_low_ff;
+    assign patsel1_high_q_o = patsel1_high_ff;
+
+    //marker_template_end
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_pat/sim/Makefile b/verilog/rtl/waveform-generator/design/wfg_drive_pat/sim/Makefile
new file mode 100644
index 0000000..38a0dcf
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_pat/sim/Makefile
@@ -0,0 +1,19 @@
+# source files
+SRC := $(wildcard ../rtl/*.sv)
+SRC += $(wildcard ../testbench/*.sv)
+
+# defaults
+SIM ?= icarus
+TOPLEVEL_LANG ?= verilog
+
+VERILOG_SOURCES += $(SRC)
+
+# TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file
+TOPLEVEL = wfg_drive_pat_tb
+
+# MODULE is the basename of the Python test file
+export PYTHONPATH := $(PYTHONPATH):../testbench/
+MODULE = test_wfg_drive_pat
+
+# include cocotb's make rules to take care of the simulator setup
+include $(shell cocotb-config --makefiles)/Makefile.sim
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_pat/testbench/test_wfg_drive_pat.py b/verilog/rtl/waveform-generator/design/wfg_drive_pat/testbench/test_wfg_drive_pat.py
new file mode 100644
index 0000000..3d4768b
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_pat/testbench/test_wfg_drive_pat.py
@@ -0,0 +1,273 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import random
+import cocotb
+import pdb
+from cocotb.clock import Clock
+from cocotb.triggers import ClockCycles
+from cocotbext.wishbone.driver import WishboneMaster, WBOp
+from cocotbext.axi import AxiStreamBus, AxiStreamSource
+from cocotb.triggers import RisingEdge, FallingEdge
+from cocotb_bus.monitors import Monitor
+from cocotb_bus.scoreboard import Scoreboard
+from cocotb.regression import TestFactory
+
+ADDITIONAL_OUTPUT = False
+
+CHANNELS = 32
+SUBCYCLES_PER_SYNC = 24
+
+class OutputMonitor(Monitor):
+    def __init__(self, dut, name="",callback=None, event=None):
+        self.dut = dut
+        self.name = name
+        Monitor.__init__(self, callback, event)
+
+    async def _monitor_recv(self):
+        clkedge = RisingEdge(self.dut.io_wbs_clk)
+        while True:
+            await clkedge
+            output = self.dut.wfg_drive_pat_dout_o.value
+            self._recv(str(output))
+#========
+
+"""
+async def drive_sync(dut):
+    dut.wfg_wfg_pat_sync_i.value = 0
+    sync_cnt = 0
+    while True:
+        await RisingEdge(dut.io_wbs_clk)
+        if sync_cnt == CLK_PER_SYNC-1:
+            sync_cnt = 0
+            dut.wfg_wfg_pat_sync_i.value = 1
+        else:
+            sync_cnt = sync_cnt + 1
+            dut.wfg_wfg_pat_sync_i.value = 0
+"""
+
+async def reset(dut):
+    await ClockCycles(dut.io_wbs_clk, 1)
+    dut.io_wbs_rst.value = 1
+    await ClockCycles(dut.io_wbs_clk, 1)
+    dut.io_wbs_rst.value = 0
+    await ClockCycles(dut.io_wbs_clk, 1)
+
+async def set_register(dut, wbs, address, data):
+    dut._log.info(f"Set register {address} : {data}")
+
+    wbRes = await wbs.send_cycle([WBOp(address, data)])
+    
+    rvalues = [wb.datrd for wb in wbRes]
+    dut._log.info(f"Returned values : {rvalues}")
+
+async def configure(dut, wbs, en, pat, begin, end): # TODO
+    #begin = 9
+    #end = 16
+
+    #en = 7
+    #pat = [1,2]
+    #pat[0] = 3
+    #pat[1] = 1
+
+    await set_register(dut, wbs, 0x4, (begin & 0xFF) | ((end & 0xFF)<<8))
+    await set_register(dut, wbs, 0x8, pat[0])
+    await set_register(dut, wbs, 0xC, pat[1])
+    await set_register(dut, wbs, 0x0, en) # Enable PAT
+
+class MyScoreboard(Scoreboard):
+    def compare(self, got, exp, log, **_):
+        if got != exp and exp != 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx':
+            self.errors += 1
+            #log.error("Received transaction differed from expected output.")
+            log.warning("Received transaction differed from expected output.")
+            log.warning("Expected: {0!s}.\nReceived: {1!s}.".format(exp, got))
+            #if self._imm:
+            #  raise TestFailure("Received transaction differed from expected transaction.")
+        elif ADDITIONAL_OUTPUT:
+            log.info("Received transaction matches expected output.")
+            log.info("Expected: {0!s}.\nReceived: {1!s}.".format(exp, got))
+#========
+
+class Testbench(object):
+    def __init__(self, dut):
+        self.dut = dut
+        self.dut._log.info("Init TB...")
+
+        clock = Clock(dut.io_wbs_clk, 10, units="ns")  # Create a 10ns period clock on port clk
+        cocotb.fork(clock.start())  # Start the clock
+        cocotb.fork(self.drive_subcycles())  # count subcycles
+
+    async def drive_subcycles(self):
+        self.dut.wfg_core_subcycle_cnt_i.value = 0
+        self.dut.wfg_core_sync_i.value = 1
+        while True:
+            await RisingEdge(self.dut.io_wbs_clk)
+            subcycles = self.dut.wfg_core_subcycle_cnt_i.value
+            if subcycles == SUBCYCLES_PER_SYNC-2:
+                self.dut.wfg_core_subcycle_cnt_i.value = subcycles + 1
+                self.dut.wfg_core_sync_i.value = 1
+            elif subcycles == SUBCYCLES_PER_SYNC-1:
+                self.dut.wfg_core_subcycle_cnt_i.value = 0
+                self.dut.wfg_core_sync_i.value = 0
+            else:
+                self.dut.wfg_core_subcycle_cnt_i.value = subcycles + 1
+                self.dut.wfg_core_sync_i.value = 0
+#========
+
+def make_expected_output(dut, input, out_begin, out_end, pat_select_i, en_i):
+    expected_output = []
+
+    print(input)
+
+    byte = bin(input)[2:].rjust(CHANNELS, '0')[::-1]
+
+    print("byte")
+    print(byte)
+    print("en")
+    print(bin(en_i))    
+    
+    for i in range(SUBCYCLES_PER_SYNC):
+
+        next_output = ''
+        for j in range(CHANNELS):
+            pat_select = (pat_select_i[0]>>j & 1) | ((pat_select_i[1]>>j & 1)<<1)
+            en         = (en_i>>j) & 1
+            bit        = str((input>>j) & 1)
+            
+            print("i={}, j={}, en={}, pat={}, bit={}".format(i, j, en, pat_select, bit))
+
+            if en == 0:
+                next_output = 'z' + next_output
+            elif pat_select == 0: # RZ
+                if i >= out_end:
+                    next_output = '0' + next_output
+                elif i >= out_begin:
+                    next_output = bit + next_output
+                else:
+                    next_output = '0' + next_output
+            elif pat_select == 1: # RO
+                if i >= out_end:
+                    next_output = '1' + next_output
+                elif i >= out_begin:
+                    next_output = bit + next_output
+                else:
+                    next_output = '0' + next_output
+            elif pat_select == 2: # NRZ
+                if i >= out_end:
+                    next_output = bit + next_output
+                elif i >= out_begin:
+                    next_output = bit + next_output
+                else:
+                    next_output = '0' + next_output
+            elif pat_select == 3: # RC
+                if i >= out_end:
+                    if bit == '0':
+                        next_output = '1' + next_output
+                    else:
+                        next_output = '0' + next_output
+                elif i >= out_begin:
+                    next_output = bit + next_output
+                else:
+                    next_output = '0' + next_output
+            else:
+                dut._log.error('should not happen!')
+                
+            print(next_output)
+
+        expected_output.append(''.join(next_output))
+    
+    print()
+    
+    for j in reversed(range(CHANNELS)):
+        pat_select = (pat_select_i[0]>>j & 1) | ((pat_select_i[1]>>j & 1)<<1)
+        print(pat_select, end="")
+    print("")
+    
+    print(byte)
+    
+    for exp in expected_output:
+        print(exp)
+    
+    return expected_output
+    
+@cocotb.coroutine
+async def run_test(dut, en=None, pat=None, begin=None, end=None, inputlen=None):
+
+    if en == None:
+        en = random.randint(0, 2**(CHANNELS-1))
+    if pat == None:
+        pat = [random.randint(0, 2**(CHANNELS-1)), random.randint(0, 2**(CHANNELS-1))]
+    if begin == None:
+        begin = 0x08 # int.from_bytes(b'\x08\x08\x08\x08\x08\x08\x08\x09', byteorder='big')
+    if end == None:
+        end = 0x10 # int.from_bytes(b'\x10\x10\x10\x10\x10\x10\x10\x10', byteorder='big')
+    if inputlen == None:
+        inputlen = random.randint(1, 20)
+
+    tb = Testbench(dut)
+
+    input = [random.randint(0, 2**(CHANNELS-1)) for _ in range(inputlen)]
+    print(input)
+    
+    for byte in input:
+        expected_output = make_expected_output(dut, input=byte, out_begin=begin, out_end=end, pat_select_i=pat, en_i=en)
+
+        await reset(dut)
+        
+        # Wishbone Master
+        wbs = WishboneMaster(dut, "io_wbs", dut.io_wbs_clk,
+                                      width=32,   # size of data bus
+                                      timeout=10) # in clock cycle number
+
+        await configure(dut, wbs, en=en, pat=pat, begin=begin, end=end)
+
+        await FallingEdge(dut.wfg_core_sync_i) # TODO
+        
+        output_mon = OutputMonitor(dut)
+        
+        scoreboard = MyScoreboard(dut)
+        scoreboard.add_interface(output_mon, expected_output)
+
+        print("Sending data")
+    
+        axis_source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "wfg_axis"), dut.io_wbs_clk, dut.io_wbs_rst)
+        for byte in input:
+            await axis_source.send([byte])
+            
+        await axis_source.send([0xFF])
+        await axis_source.send([0xFF])
+
+        print("Waiting for expected_output != []")
+
+        while expected_output != []:
+            print(expected_output)
+            await ClockCycles(dut.io_wbs_clk, 1)
+
+        output_mon.kill()
+
+        print("Result")
+
+        raise scoreboard.result
+
+
+factory = TestFactory(run_test)
+factory.add_option("en", [None])
+
+#pat = [None] + [sum(j * 2**i for i in range(0, 15, 2)) for j in range (0, 4)]
+pat = [None]
+factory.add_option("pat", pat)
+
+#begin = [None, int.from_bytes(b'\x01\x01\x01\x01\x01\x01\x01\x01', byteorder='big')]
+begin = [None]
+
+factory.add_option("begin", begin)
+
+end = [None]
+factory.add_option("end", end)
+
+inputlen = [None, 1]
+factory.add_option("inputlen", inputlen)
+
+factory.generate_tests()
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_pat/testbench/wfg_drive_pat_tb.sv b/verilog/rtl/waveform-generator/design/wfg_drive_pat/testbench/wfg_drive_pat_tb.sv
new file mode 100644
index 0000000..fc49fb9
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_pat/testbench/wfg_drive_pat_tb.sv
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`timescale 1ns / 1ps
+
+`ifdef VERILATOR  // make parameter readable from VPI
+`define VL_RD /*verilator public_flat_rd*/
+`else
+`define VL_RD
+`endif
+
+module wfg_drive_pat_tb #(
+    parameter int BUSW = 32,
+    parameter int AXIS_DATA_WIDTH = 32,
+    parameter int CHANNELS = 32
+) (
+    // Wishbone interface signals
+    input               io_wbs_clk,
+    input               io_wbs_rst,
+    input  [(BUSW-1):0] io_wbs_adr,
+    input  [(BUSW-1):0] io_wbs_datwr,
+    output [(BUSW-1):0] io_wbs_datrd,
+    input               io_wbs_we,
+    input               io_wbs_stb,
+    output              io_wbs_ack,
+    input               io_wbs_cyc,
+
+    // Core synchronisation interface
+    input logic wfg_core_sync_i,
+    input logic [7:0] wfg_core_subcycle_cnt_i,
+
+    // AXI-Stream interface
+    output wire                        wfg_axis_tready,  // O; ready
+    input  logic                       wfg_axis_tvalid,  // I; valid
+    input  logic                       wfg_axis_tlast,   // I; last
+    input  logic [AXIS_DATA_WIDTH-1:0] wfg_axis_tdata,   // I; data
+
+    // pat IO interface
+    output wire [CHANNELS-1:0] wfg_drive_pat_dout_o,    // O; output pins
+    output wire [CHANNELS-1:0] wfg_drive_pat_dout_en_o  // O; output enabled
+);
+
+    wfg_drive_pat_top #(
+        .BUSW(BUSW),
+        .CHANNELS(CHANNELS),
+        .AXIS_DATA_WIDTH(AXIS_DATA_WIDTH)
+    ) wfg_drive_pat_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(io_wbs_adr),
+        .wbs_ack_o(io_wbs_ack),
+        .wbs_dat_o(io_wbs_datrd),
+
+        .wfg_core_sync_i(wfg_core_sync_i),
+        .wfg_core_subcycle_cnt_i(wfg_core_subcycle_cnt_i),
+
+        .wfg_axis_tready_o(wfg_axis_tready),
+        .wfg_axis_tdata_i (wfg_axis_tdata),
+        .wfg_axis_tlast_i (wfg_axis_tlast),
+        .wfg_axis_tvalid_i(wfg_axis_tvalid),
+
+        .pat_dout_o(wfg_drive_pat_dout_o),
+        .pat_dout_en_o(wfg_drive_pat_dout_en_o)
+    );
+
+    // Dump waves
+`ifndef VERILATOR
+    initial begin
+        $dumpfile("wfg_drive_pat_tb.vcd");
+        $dumpvars(0, wfg_drive_pat_tb);
+    end
+`endif
+
+endmodule
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_spi/README.md b/verilog/rtl/waveform-generator/design/wfg_drive_spi/README.md
new file mode 100644
index 0000000..c4e591d
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_spi/README.md
@@ -0,0 +1,20 @@
+<img align="right" src="https://github.com/semify-eda/wfg/blob/main/doc/semify.png" width="100" height="100" >
+
+*Copyright © 2021* [Semify EDA](
+https://github.com/semify-eda)
+
+## wfg_drive_spi documentation
+
+#### Overview
+
+The wfg_drive_spi module is a SPI master module used in the output stage of the waveform generator and is only operating in MOSI mode. The module is
+triggered by the wfg_core, each sync puls starts a new transmission.
+The input is received via an axi stream (32 bit) interface.
+The transmission is handled entirely within the module and all SPI signals are generated automatically and are configurable.
+
+#### Verification
+
+A system clock with frequency f = 100 MHz and an asynchronous wfg_sync signal are defined in the cocotb testbench. 
+The Python function random.randint is used to generate a list of random input values. Via a cocotb AXI-Stream master the SPI module receives a new input
+at each sync pulse. The functionality is tested with a test factory that runs through all possible configuration parameters. 
+The SPI module is connected to a cocotb SPI interface which decodes each successful transmission.
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_spi/data/wfg_drive_spi_reg.csv b/verilog/rtl/waveform-generator/design/wfg_drive_spi/data/wfg_drive_spi_reg.csv
new file mode 100644
index 0000000..b235f78
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_spi/data/wfg_drive_spi_reg.csv
@@ -0,0 +1,23 @@
+Address,RegName,BitName,Access,HW,MSB,LSB,Reset,Description
+4'h0,CTRL,,,,,,,Control register for SPI unit
+,,EN,rw,cfg,0,0,1'b0,SPI enable
+4'h4,CFG,,,,,,,SPI configuration register
+,,CPOL,rw,cfg,0,0,1'b0,"Clock polarity
+  0: CK to 0 when idle
+  1: CK to 1 when idle"
+,,LSBFIRST,rw,cfg,1,1,1'b0,"Frame format
+  0: MSB transmitted first
+  1: LSB transmitted first"
+,,DFF,rw,cfg,3,2,2'b00,"Data frame format
+  00: 8bit
+  01: 16bit
+  10: 24bit
+  11: 32bit"
+,,SSPOL,rw,cfg,4,4,1'b0,"Slave select polarity
+  0: Active low
+  1: Active high"
+,,CORE_SEL,rw,cfg,5,5,1'b0,"0 : core
+1 : subcore"
+4'h8,CLKCFG,,,,,,,SPI clock configuration register
+,,DIV,rw,cfg,7,0,0,"SPI Clock divider.
+Divider ratio is 2*DIV+1"
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_spi/data/wfg_drive_spi_reg.json b/verilog/rtl/waveform-generator/design/wfg_drive_spi/data/wfg_drive_spi_reg.json
new file mode 100644
index 0000000..dcade9e
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_spi/data/wfg_drive_spi_reg.json
@@ -0,0 +1,78 @@
+{
+    "registers": {
+        "CFG": {
+            "address": "4'h4",
+            "description": "SPI configuration register",
+            "entries": {
+                "CORE_SEL": {
+                    "LSB": "5",
+                    "MSB": "5",
+                    "access": "rw",
+                    "description": "0 : core\n1 : subcore",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                },
+                "CPOL": {
+                    "LSB": "0",
+                    "MSB": "0",
+                    "access": "rw",
+                    "description": "Clock polarity\n  0: CK to 0 when idle\n  1: CK to 1 when idle",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                },
+                "DFF": {
+                    "LSB": "2",
+                    "MSB": "3",
+                    "access": "rw",
+                    "description": "Data frame format\n  00: 8bit\n  01: 16bit\n  10: 24bit\n  11: 32bit",
+                    "hardware": "cfg",
+                    "reset": "2'b00"
+                },
+                "LSBFIRST": {
+                    "LSB": "1",
+                    "MSB": "1",
+                    "access": "rw",
+                    "description": "Frame format\n  0: MSB transmitted first\n  1: LSB transmitted first",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                },
+                "SSPOL": {
+                    "LSB": "4",
+                    "MSB": "4",
+                    "access": "rw",
+                    "description": "Slave select polarity\n  0: Active low\n  1: Active high",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                }
+            }
+        },
+        "CLKCFG": {
+            "address": "4'h8",
+            "description": "SPI clock configuration register",
+            "entries": {
+                "DIV": {
+                    "LSB": "0",
+                    "MSB": "7",
+                    "access": "rw",
+                    "description": "SPI Clock divider.\nDivider ratio is 2*DIV+1",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        },
+        "CTRL": {
+            "address": "4'h0",
+            "description": "Control register for SPI unit",
+            "entries": {
+                "EN": {
+                    "LSB": "0",
+                    "MSB": "0",
+                    "access": "rw",
+                    "description": "SPI enable",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_spi/rtl/wfg_drive_spi.sv b/verilog/rtl/waveform-generator/design/wfg_drive_spi/rtl/wfg_drive_spi.sv
new file mode 100644
index 0000000..f4a5645
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_spi/rtl/wfg_drive_spi.sv
@@ -0,0 +1,201 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_drive_spi #(
+    parameter int AXIS_DATA_WIDTH = 32
+) (
+    input logic clk,   // I; System clock
+    input logic rst_n, // I; active low reset
+
+    // Core synchronisation interface
+    input logic wfg_core_sync_i,     // I; Sync pulse
+    input logic wfg_core_subcycle_i, // I; Subcycle pulse
+
+    // Subcore synchronisation interface
+    input logic wfg_subcore_sync_i,     // I; Sync pulse
+    input logic wfg_subcore_subcycle_i, // I; Subcycle pulse
+
+    // AXI streaming interface
+    output logic                       wfg_axis_tready_o,  // O; ready
+    input  logic                       wfg_axis_tvalid_i,  // I; valid
+    input  logic                       wfg_axis_tlast_i,   // I; last
+    input  logic [AXIS_DATA_WIDTH-1:0] wfg_axis_tdata_i,   // I; data
+
+    // Control
+    input logic ctrl_en_q_i,  // I; SPI enable
+
+    // Configuration
+    input logic [7:0] clkcfg_div_q_i,    // I; SPI speed
+    input logic       cfg_cpol_q_i,      // I; Clock polarity
+    input logic       cfg_lsbfirst_q_i,  // I; Frame format
+    input logic [1:0] cfg_dff_q_i,       // I; Data frame format
+    input logic       cfg_sspol_q_i,     // I; Slave select polarity
+    input logic       cfg_core_sel_q_i,  // I; Core select
+
+    // SPI IO interface
+    output logic wfg_drive_spi_sclk_o,  // O; clock
+    output logic wfg_drive_spi_cs_no,   // O; chip select
+    output logic wfg_drive_spi_sdo_o    // O; data out
+);
+
+    typedef enum logic [1:0] {
+        ST_IDLE,
+        ST_SEND_DATA,
+        ST_LAST_BIT
+    } my_uart_states_t;
+
+    my_uart_states_t cur_state, next_state;
+
+    logic transitioning;
+    assign transitioning = cur_state != next_state;
+
+    logic [7:0] counter;
+    logic [7:0] clk_div;
+    logic [4:0] current_bit;
+    logic [31:0] spi_data;
+    logic spi_cs;       // chip slect
+    logic spi_clk;      // clock
+    logic ready;
+    logic lsbfirst;     // lsb first
+    logic cpol;         // clock polarity
+    logic cspol;        // chip select control
+    logic [1:0] byte_cnt;
+    logic [4:0] bytes_to_bits [0:3];
+
+    logic wfg_sync_i;
+    logic wfg_subcycle_i;
+
+    always_comb begin
+        wfg_sync_i = 'x;
+        wfg_subcycle_i = 'x;
+
+        case (cfg_core_sel_q_i)
+            1'b0: begin
+                wfg_sync_i     = wfg_core_sync_i;
+                wfg_subcycle_i = wfg_core_subcycle_i;
+            end
+            1'b1: begin
+                wfg_sync_i     = wfg_subcore_sync_i;
+                wfg_subcycle_i = wfg_subcore_subcycle_i;
+            end
+        endcase
+    end
+
+    // Present state logic
+    always_ff @(posedge clk, negedge rst_n)
+        if (!rst_n) cur_state <= ST_IDLE;
+        else cur_state <= next_state;
+
+    // State transitions
+    always_comb begin
+        next_state = cur_state;
+        case (cur_state)
+            ST_IDLE: begin
+                if (wfg_sync_i && wfg_axis_tvalid_i && ctrl_en_q_i) next_state = ST_SEND_DATA;
+            end
+            ST_SEND_DATA: begin
+                if (counter == 0 && current_bit == 0 && spi_clk) begin
+                    next_state = ST_LAST_BIT;
+                end
+            end
+            ST_LAST_BIT: begin
+                if (counter == 0) next_state = ST_IDLE;
+            end
+            default: begin
+                next_state = ST_IDLE;
+            end
+        endcase
+    end
+
+    // Value assignments
+    always_ff @(posedge clk, negedge rst_n)
+        if (!rst_n) begin
+            wfg_drive_spi_sclk_o <= '0;
+            wfg_drive_spi_cs_no  <= '0;
+            wfg_drive_spi_sdo_o  <= '0;
+
+            counter              <= '0;
+            current_bit          <= '0;
+            spi_cs               <= '0;
+            spi_data             <= '0;
+            spi_clk              <= '0;
+            ready                <= '0;
+            clk_div              <= '0;
+            lsbfirst             <= '0;
+            cpol                 <= '0;
+            cspol                <= '0;
+            byte_cnt             <= '0;
+            bytes_to_bits[0]     <= 5'd7;
+            bytes_to_bits[1]     <= 5'd15;
+            bytes_to_bits[2]     <= 5'd23;
+            bytes_to_bits[3]     <= 5'd31;
+
+        end else begin
+            wfg_drive_spi_sclk_o <= cpol ? !spi_clk : spi_clk;
+            wfg_drive_spi_cs_no  <= cspol ? spi_cs : !spi_cs;
+            wfg_drive_spi_sdo_o  <= lsbfirst ? spi_data[0] : spi_data[bytes_to_bits[byte_cnt]];
+
+            case (next_state)
+                ST_IDLE: begin
+                    counter  <= '0;
+                    spi_clk  <= '0;
+                    spi_cs   <= '0;
+                    spi_data <= '0;
+
+                    clk_div  <= clkcfg_div_q_i;
+                    lsbfirst <= cfg_lsbfirst_q_i;
+                    byte_cnt <= cfg_dff_q_i;
+                    cpol     <= cfg_cpol_q_i;
+                    cspol    <= cfg_sspol_q_i;
+                end
+                ST_SEND_DATA: begin
+                    spi_cs <= 1'b1;
+
+                    if (transitioning) begin
+                        spi_data <= wfg_axis_tdata_i;
+                        ready <= 1'b1;
+                        counter <= clk_div;
+                        current_bit <= bytes_to_bits[byte_cnt];
+                    end else begin
+                        counter <= counter - 1;
+                        ready   <= 1'b0;
+
+                        if (counter == 0) begin
+                            spi_clk <= !spi_clk;
+                            counter <= clk_div;
+
+                            if (spi_clk == 1'b1) begin
+                                current_bit <= current_bit - 1;
+                                if (lsbfirst) begin
+                                    spi_data <= {1'b0, spi_data[31:1]};
+                                end else begin
+                                    spi_data <= {spi_data[30:0], 1'b0};
+                                end
+                            end
+                        end
+                    end
+                end
+                ST_LAST_BIT: begin
+                    if (transitioning) begin
+                        counter <= clk_div;
+                    end else begin
+                        counter <= counter - 1;
+                    end
+                    spi_cs <= 1'b1;
+                    spi_clk <= 1'b0;
+                    ready <= 1'b0;
+                    spi_data <= 1'b0;
+                end
+                default: begin
+                    spi_cs   <= 'x;
+                    spi_clk  <= 'x;
+                    spi_data <= 'x;
+                end
+            endcase
+        end
+
+    assign wfg_axis_tready_o = ready;
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_spi/rtl/wfg_drive_spi_top.sv b/verilog/rtl/waveform-generator/design/wfg_drive_spi/rtl/wfg_drive_spi_top.sv
new file mode 100644
index 0000000..9adc837
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_spi/rtl/wfg_drive_spi_top.sv
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_drive_spi_top #(
+    parameter int BUSW = 32,
+    parameter int AXIS_DATA_WIDTH = 32
+) (
+    // Wishbone Slave ports
+    input                 wb_clk_i,
+    input                 wb_rst_i,
+    input                 wbs_stb_i,
+    input                 wbs_cyc_i,
+    input                 wbs_we_i,
+    input  [(BUSW/8-1):0] wbs_sel_i,
+    input  [  (BUSW-1):0] wbs_dat_i,
+    input  [  (BUSW-1):0] wbs_adr_i,
+    output                wbs_ack_o,
+    output [  (BUSW-1):0] wbs_dat_o,
+
+    // Core synchronisation interface
+    input wire wfg_core_sync_i,     // I; core_sync pulse
+    input wire wfg_core_subcycle_i, // I; subcycle_cnt
+
+    // Subcore synchronisation interface
+    input logic wfg_subcore_sync_i,     // I; Sync pulse
+    input logic wfg_subcore_subcycle_i, // I; Subcycle pulse
+
+    // AXI-Stream interface
+    output wire wfg_axis_tready_o,
+    input wire [AXIS_DATA_WIDTH-1:0] wfg_axis_tdata_i,
+    input wire wfg_axis_tlast_i,
+    input wire wfg_axis_tvalid_i,
+
+    // SPI IO interface
+    output wire wfg_drive_spi_sclk_o,  // O; clock
+    output wire wfg_drive_spi_cs_no,   // O; chip select
+    output wire wfg_drive_spi_sdo_o    // O; data out
+);
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_drive_spi_reg.json
+    //template: wishbone/instantiate_top.template
+    //marker_template_code
+
+    logic         cfg_core_sel_q;          // CFG.CORE_SEL register output
+    logic         cfg_cpol_q;              // CFG.CPOL register output
+    logic [ 3: 2] cfg_dff_q;               // CFG.DFF register output
+    logic         cfg_lsbfirst_q;          // CFG.LSBFIRST register output
+    logic         cfg_sspol_q;             // CFG.SSPOL register output
+    logic [ 7: 0] clkcfg_div_q;            // CLKCFG.DIV register output
+    logic         ctrl_en_q;               // CTRL.EN register output
+
+    //marker_template_end
+
+    wfg_drive_spi_wishbone_reg wfg_drive_spi_wishbone_reg (
+        .wb_clk_i (wb_clk_i),
+        .wb_rst_i (wb_rst_i),
+        .wbs_stb_i(wbs_stb_i),
+        .wbs_cyc_i(wbs_cyc_i),
+        .wbs_we_i (wbs_we_i),
+        .wbs_sel_i(wbs_sel_i),
+        .wbs_dat_i(wbs_dat_i),
+        .wbs_adr_i(wbs_adr_i),
+        .wbs_ack_o(wbs_ack_o),
+        .wbs_dat_o(wbs_dat_o),
+
+        //marker_template_start
+        //data: ../data/wfg_drive_spi_reg.json
+        //template: wishbone/assign_to_module.template
+        //marker_template_code
+
+        .cfg_core_sel_q_o(cfg_core_sel_q),  // CFG.CORE_SEL register output
+        .cfg_cpol_q_o    (cfg_cpol_q),      // CFG.CPOL register output
+        .cfg_dff_q_o     (cfg_dff_q),       // CFG.DFF register output
+        .cfg_lsbfirst_q_o(cfg_lsbfirst_q),  // CFG.LSBFIRST register output
+        .cfg_sspol_q_o   (cfg_sspol_q),     // CFG.SSPOL register output
+        .clkcfg_div_q_o  (clkcfg_div_q),    // CLKCFG.DIV register output
+        .ctrl_en_q_o     (ctrl_en_q)        // CTRL.EN register output
+
+        //marker_template_end
+    );
+
+    wfg_drive_spi wfg_drive_spi (
+        .clk  (wb_clk_i),  // clock signal
+        .rst_n(!wb_rst_i), // reset signal
+
+        // Core synchronisation interface
+        .wfg_core_sync_i    (wfg_core_sync_i),
+        .wfg_core_subcycle_i(wfg_core_subcycle_i),
+
+        // Subcore synchronisation interface
+        .wfg_subcore_sync_i    (wfg_subcore_sync_i),
+        .wfg_subcore_subcycle_i(wfg_subcore_subcycle_i),
+
+        // AXI streaming interface
+        .wfg_axis_tready_o(wfg_axis_tready_o),  // O; ready
+        .wfg_axis_tvalid_i(wfg_axis_tvalid_i),  // I; valid
+        .wfg_axis_tlast_i (wfg_axis_tlast_i),   // I; last
+        .wfg_axis_tdata_i (wfg_axis_tdata_i),   // I; data
+
+        // Control
+        .ctrl_en_q_i(ctrl_en_q),  // I; SPI enable
+
+        // Configuration
+        .clkcfg_div_q_i  (clkcfg_div_q),    // I: clock divider
+        .cfg_cpol_q_i    (cfg_cpol_q),      // I; Clock polarity
+        .cfg_lsbfirst_q_i(cfg_lsbfirst_q),  // I; Frame format
+        .cfg_dff_q_i     (cfg_dff_q),       // I; Data frame format
+        .cfg_sspol_q_i   (cfg_sspol_q),     // I; Slave select polarity
+        .cfg_core_sel_q_i(cfg_core_sel_q),  // I; Core select
+
+        // SPI IO interface
+        .wfg_drive_spi_sclk_o(wfg_drive_spi_sclk_o),  // O; clock
+        .wfg_drive_spi_cs_no (wfg_drive_spi_cs_no),   // O; chip select
+        .wfg_drive_spi_sdo_o (wfg_drive_spi_sdo_o)    // O; data out
+    );
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_spi/rtl/wfg_drive_spi_wishbone_reg.sv b/verilog/rtl/waveform-generator/design/wfg_drive_spi/rtl/wfg_drive_spi_wishbone_reg.sv
new file mode 100644
index 0000000..f30d26f
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_spi/rtl/wfg_drive_spi_wishbone_reg.sv
@@ -0,0 +1,144 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_drive_spi_wishbone_reg #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                       wb_clk_i,
+    input                       wb_rst_i,
+    input                       wbs_stb_i,
+    input                       wbs_cyc_i,
+    input                       wbs_we_i,
+    input        [(BUSW/8-1):0] wbs_sel_i,
+    input        [  (BUSW-1):0] wbs_dat_i,
+    input        [  (BUSW-1):0] wbs_adr_i,
+    output logic                wbs_ack_o,
+    output logic [  (BUSW-1):0] wbs_dat_o,
+
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_drive_spi_reg.json
+    //template: wishbone/register_interface.template
+    //marker_template_code
+
+    output logic       cfg_core_sel_q_o,  // CFG.CORE_SEL register output
+    output logic       cfg_cpol_q_o,      // CFG.CPOL register output
+    output logic [3:2] cfg_dff_q_o,       // CFG.DFF register output
+    output logic       cfg_lsbfirst_q_o,  // CFG.LSBFIRST register output
+    output logic       cfg_sspol_q_o,     // CFG.SSPOL register output
+    output logic [7:0] clkcfg_div_q_o,    // CLKCFG.DIV register output
+    output logic       ctrl_en_q_o        // CTRL.EN register output
+
+    //marker_template_end
+);
+
+    //marker_template_start
+    //data: ../data/wfg_drive_spi_reg.json
+    //template: wishbone/instantiate_registers.template
+    //marker_template_code
+
+    logic         cfg_core_sel_ff;         // CFG.CORE_SEL FF
+    logic         cfg_cpol_ff;             // CFG.CPOL FF
+    logic [ 3: 2] cfg_dff_ff;              // CFG.DFF FF
+    logic         cfg_lsbfirst_ff;         // CFG.LSBFIRST FF
+    logic         cfg_sspol_ff;            // CFG.SSPOL FF
+    logic [ 7: 0] clkcfg_div_ff;           // CLKCFG.DIV FF
+    logic         ctrl_en_ff;              // CTRL.EN FF
+
+    //marker_template_end
+
+    // Wishbone write to slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            //marker_template_start
+            //data: ../data/wfg_drive_spi_reg.json
+            //template: wishbone/reset_registers.template
+            //marker_template_code
+
+            cfg_core_sel_ff <= 1'b0;
+            cfg_cpol_ff     <= 1'b0;
+            cfg_dff_ff      <= 2'b00;
+            cfg_lsbfirst_ff <= 1'b0;
+            cfg_sspol_ff    <= 1'b0;
+            clkcfg_div_ff   <= 0;
+            ctrl_en_ff      <= 1'b0;
+
+            //marker_template_end
+        end else if (wbs_stb_i && wbs_we_i && wbs_cyc_i) begin
+            case (wbs_adr_i)
+                //marker_template_start
+                //data: ../data/wfg_drive_spi_reg.json
+                //template: wishbone/assign_to_registers.template
+                //marker_template_code
+
+                4'h4: begin
+                    cfg_core_sel_ff <= wbs_dat_i[5:5];
+                    cfg_cpol_ff     <= wbs_dat_i[0:0];
+                    cfg_dff_ff      <= wbs_dat_i[3:2];
+                    cfg_lsbfirst_ff <= wbs_dat_i[1:1];
+                    cfg_sspol_ff    <= wbs_dat_i[4:4];
+                end
+                4'h8:       clkcfg_div_ff            <= wbs_dat_i[ 7: 0];
+                4'h0:       ctrl_en_ff               <= wbs_dat_i[ 0: 0];
+
+                //marker_template_end
+                default: begin
+                end
+            endcase
+        end
+    end
+
+    // Wishbone read from slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            wbs_dat_o <= '0;
+        end else begin
+            if (wbs_stb_i && !wbs_we_i && wbs_cyc_i) begin
+                wbs_dat_o <= '0;  // default value
+                case (wbs_adr_i)
+                    //marker_template_start
+                    //data: ../data/wfg_drive_spi_reg.json
+                    //template: wishbone/assign_from_registers.template
+                    //marker_template_code
+
+                    4'h4: begin
+                        wbs_dat_o[5:5] <= cfg_core_sel_ff;
+                        wbs_dat_o[0:0] <= cfg_cpol_ff;
+                        wbs_dat_o[3:2] <= cfg_dff_ff;
+                        wbs_dat_o[1:1] <= cfg_lsbfirst_ff;
+                        wbs_dat_o[4:4] <= cfg_sspol_ff;
+                    end
+                    4'h8:       wbs_dat_o[ 7: 0] <= clkcfg_div_ff;
+                    4'h0:       wbs_dat_o[ 0: 0] <= ctrl_en_ff;
+
+                    //marker_template_end
+                    default:    wbs_dat_o <= 'X;
+                endcase
+            end
+        end
+    end
+
+    // Acknowledgement
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) wbs_ack_o <= 1'b0;
+        else wbs_ack_o <= wbs_stb_i && wbs_cyc_i & !wbs_ack_o;
+    end
+
+    //marker_template_start
+    //data: ../data/wfg_drive_spi_reg.json
+    //template: wishbone/assign_outputs.template
+    //marker_template_code
+
+    assign cfg_core_sel_q_o = cfg_core_sel_ff;
+    assign cfg_cpol_q_o     = cfg_cpol_ff;
+    assign cfg_dff_q_o      = cfg_dff_ff;
+    assign cfg_lsbfirst_q_o = cfg_lsbfirst_ff;
+    assign cfg_sspol_q_o    = cfg_sspol_ff;
+    assign clkcfg_div_q_o   = clkcfg_div_ff;
+    assign ctrl_en_q_o      = ctrl_en_ff;
+
+    //marker_template_end
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_spi/sim/Makefile b/verilog/rtl/waveform-generator/design/wfg_drive_spi/sim/Makefile
new file mode 100644
index 0000000..6402328
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_spi/sim/Makefile
@@ -0,0 +1,19 @@
+# source files
+SRC := $(wildcard ../rtl/*.sv)
+SRC += $(wildcard ../testbench/*.sv)
+
+# defaults
+SIM ?= icarus
+TOPLEVEL_LANG ?= verilog
+
+VERILOG_SOURCES += $(SRC)
+
+# TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file
+TOPLEVEL = wfg_drive_spi_tb
+
+# MODULE is the basename of the Python test file
+export PYTHONPATH := $(PYTHONPATH):../testbench/
+MODULE = test_wfg_drive_spi
+
+# include cocotb's make rules to take care of the simulator setup
+include $(shell cocotb-config --makefiles)/Makefile.sim
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_spi/testbench/test_wfg_drive_spi.py b/verilog/rtl/waveform-generator/design/wfg_drive_spi/testbench/test_wfg_drive_spi.py
new file mode 100644
index 0000000..5e2ff94
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_spi/testbench/test_wfg_drive_spi.py
@@ -0,0 +1,159 @@
+# SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import cocotb
+from cocotb.clock import Clock
+from cocotb.regression import TestFactory
+from cocotb.triggers import Timer, RisingEdge, FallingEdge
+from cocotbext.wishbone.driver import WishboneMaster, WBOp
+from cocotbext.axi import AxiStreamBus, AxiStreamSource
+from cocotbext.spi import SpiMaster, SpiSignals, SpiConfig, SpiSlaveBase
+#from random import randbytes # Possible in Python 3.9+
+
+CLK_PER_SYNC = 300
+SYSCLK = 100000000
+DATA_CNT = 10
+
+short_per = Timer(100, units="ns")
+long_time = Timer(100, units="us")
+
+async def drive_sync(dut):
+    dut.wfg_core_sync_i.value = 0
+    sync_cnt = 0
+    while True:
+        await RisingEdge(dut.io_wbs_clk)
+        if sync_cnt == CLK_PER_SYNC-1:
+            sync_cnt = 0
+            dut.wfg_core_sync_i.value = 1
+        else:
+            sync_cnt = sync_cnt + 1
+            dut.wfg_core_sync_i.value = 0
+
+async def set_register(dut, wbs, address, data):
+    dut._log.info(f"Set register {address} : {data}")
+
+    wbRes = await wbs.send_cycle([WBOp(address, data)])
+    
+    rvalues = [wb.datrd for wb in wbRes]
+    dut._log.info(f"Returned values : {rvalues}")
+
+async def configure(dut, wbs, en=1, cnt=3, cpol=0, lsbfirst=0, dff=0, sspol=0):
+    await set_register(dut, wbs, 0x8, cnt) # Clock divider
+    await set_register(dut, wbs, 0x4, (cpol<<0) | (lsbfirst<<1) | (dff<<2) | (sspol<<4))
+    await set_register(dut, wbs, 0x0, en) # Enable SPI
+
+class SimpleSpiSlave(SpiSlaveBase):
+  def __init__(self, dut, signals, config):
+    self._config = config
+    self.content = 0
+    super().__init__(signals)
+    self.latest_value = None
+
+  async def get_content(self):
+    await self.idle.wait()
+    return self.content
+
+  async def _transaction(self, frame_start, frame_end):
+    await frame_start
+    self.content = int(await self._shift(self._config.word_width, tx_word=None))
+    await frame_end
+    
+    # For now we have to mirror the bits ourselves
+    if not self._config.msb_first:
+        mirrored = 0
+        bit_length = self._config.word_width
+        for bit_pos in range(bit_length):
+            mirrored |= ((self.content & 1<<(bit_pos)) > 0) << (bit_length-1-bit_pos)
+        self.content = mirrored
+    
+    self.latest_value = self.content.to_bytes(self._config.word_width // 8, 'big')
+
+@cocotb.coroutine
+async def spi_test(dut, en, cnt, cpol, lsbfirst, dff, sspol):
+    dut._log.info(f"Configuration: en={en}, cnt={cnt}, cpol={cpol}, lsbfirst={lsbfirst}, dff={dff}, sspol={sspol}")
+
+    cocotb.start_soon(Clock(dut.io_wbs_clk, 1/SYSCLK*1e9, units="ns").start())
+    cocotb.fork(drive_sync(dut))
+
+    dut._log.info("Initialize and reset model")
+
+    # Start reset
+    dut.io_wbs_rst.value = 1
+    dut.wfg_axis_tdata.value = 0x00000000
+    dut.wfg_axis_tlast.value = 0
+    dut.wfg_axis_tvalid.value = 1
+    
+    await Timer(100, units='ns')
+
+    # Stop reset
+    dut.io_wbs_rst.value = 0
+
+    # Wishbone Master
+    wbs = WishboneMaster(dut, "io_wbs", dut.io_wbs_clk,
+                              width=32,   # size of data bus
+                              timeout=10) # in clock cycle number
+
+    # Setup as SPI Master
+    await configure(dut, wbs,  en=en, cnt=cnt, cpol=cpol, lsbfirst=lsbfirst, dff=dff, sspol=sspol)
+
+    # Create SPI Slave
+    spi_signals = SpiSignals(
+        sclk = dut.wfg_drive_spi_sclk_o,
+        mosi = dut.wfg_drive_spi_sdo_o,
+        miso = dut.wfg_drive_spi_sdi_i,
+        cs   = dut.wfg_drive_spi_cs_no,
+        cs_active_low = not sspol
+    )
+
+    spi_config = SpiConfig(
+        word_width          = (8 * (dff + 1)),          # number of bits in a SPI transaction
+        sclk_freq           = SYSCLK/((cnt + 1) * 2),   # clock rate in Hz
+        cpol                = cpol,                     # clock idle polarity
+        cpha                = False,                     # clock phase (CPHA=True means sample on FallingEdge)
+        msb_first           = not lsbfirst,             # the order that bits are clocked onto the wire
+        data_output_idle    = 1,                        # the idle value of the MOSI or MISO line 
+        frame_spacing_ns    = 1                         # the spacing between frames that the master waits for or the slave obeys
+                                                        #       the slave should raise SpiFrameError if this is not obeyed.
+    )
+
+    spi_slave = SimpleSpiSlave(dut, spi_signals, spi_config)
+
+    await short_per
+    await short_per
+    
+    #await RisingEdge(dut.wfg_core_sync_i)
+
+    axis_source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "wfg_axis"), dut.io_wbs_clk, dut.io_wbs_rst)
+
+    for i in range(DATA_CNT):
+    
+        random_bytes = os.getrandom(dff + 1, os.GRND_NONBLOCK) 
+        #random_bytes = randbytes(dff + 1) # Possible in Python 3.9+
+        
+        dut._log.info("Sending data: 0x{}".format(random_bytes.hex()))
+        
+        await axis_source.send([int.from_bytes(random_bytes, "big")])
+        # wait for operation to complete
+        await axis_source.wait()
+
+        await RisingEdge(dut.wfg_core_sync_i)
+
+        await short_per
+        
+        dut._log.info("SPI received: 0x{}".format(spi_slave.latest_value.hex()))
+        
+        assert random_bytes == spi_slave.latest_value
+       
+    
+    await short_per
+
+factory = TestFactory(spi_test)
+factory.add_option("en", [1])
+factory.add_option("cnt", [3])
+factory.add_option("cpol", [0, 1])
+factory.add_option("lsbfirst", [0, 1])
+factory.add_option("dff", [0,1,2,3])
+factory.add_option("sspol", [0, 1])
+
+factory.generate_tests()
diff --git a/verilog/rtl/waveform-generator/design/wfg_drive_spi/testbench/wfg_drive_spi_tb.sv b/verilog/rtl/waveform-generator/design/wfg_drive_spi/testbench/wfg_drive_spi_tb.sv
new file mode 100644
index 0000000..b943b17
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_drive_spi/testbench/wfg_drive_spi_tb.sv
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`timescale 1ns / 1ps
+
+`ifdef VERILATOR  // make parameter readable from VPI
+`define VL_RD /*verilator public_flat_rd*/
+`else
+`define VL_RD
+`endif
+
+module wfg_drive_spi_tb #(
+    parameter int BUSW = 32,
+    parameter int AXIS_DATA_WIDTH = 32
+) (
+    // Wishbone interface signals
+    input               io_wbs_clk,
+    input               io_wbs_rst,
+    input  [(BUSW-1):0] io_wbs_adr,
+    input  [(BUSW-1):0] io_wbs_datwr,
+    output [(BUSW-1):0] io_wbs_datrd,
+    input               io_wbs_we,
+    input               io_wbs_stb,
+    output              io_wbs_ack,
+    input               io_wbs_cyc,
+
+    // Bus master A interface
+    input logic wfg_core_sync_i,     // I; Single cycle pulse for write access
+    input logic wgf_core_subcycle_i, // I; Single cycle pulse for read access
+
+    // AXI-Stream interface
+    output wire                        wfg_axis_tready,  // O; ready
+    input  logic                       wfg_axis_tvalid,  // I; valid
+    input  logic                       wfg_axis_tlast,   // I; last
+    input  logic [AXIS_DATA_WIDTH-1:0] wfg_axis_tdata,   // I; data
+
+    // SPI IO interface
+    output wire wfg_drive_spi_sclk_o,  // O; clock
+    output wire wfg_drive_spi_cs_no,   // O; chip select
+    output wire wfg_drive_spi_sdo_o,   // O; data out
+    input  wire wfg_drive_spi_sdi_i    // I; data in, dummy signal for cocotb
+);
+
+    wfg_drive_spi_top wfg_drive_spi_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(io_wbs_adr),
+        .wbs_ack_o(io_wbs_ack),
+        .wbs_dat_o(io_wbs_datrd),
+
+        .wfg_core_sync_i(wfg_core_sync_i),
+        .wfg_core_subcycle_i(wgf_core_subcycle_i),
+
+        .wfg_axis_tready_o(wfg_axis_tready),
+        .wfg_axis_tdata_i (wfg_axis_tdata),
+        .wfg_axis_tlast_i (wfg_axis_tlast),
+        .wfg_axis_tvalid_i(wfg_axis_tvalid),
+
+        .wfg_drive_spi_sclk_o(wfg_drive_spi_sclk_o),
+        .wfg_drive_spi_cs_no (wfg_drive_spi_cs_no),
+        .wfg_drive_spi_sdo_o (wfg_drive_spi_sdo_o)
+    );
+
+    // Dump waves
+`ifndef VERILATOR
+    initial begin
+        $dumpfile("wfg_drive_spi_tb.vcd");
+        $dumpvars(0, wfg_drive_spi_tb);
+    end
+`endif
+
+endmodule
diff --git a/verilog/rtl/waveform-generator/design/wfg_interconnect/data/wfg_interconnect_reg.csv b/verilog/rtl/waveform-generator/design/wfg_interconnect/data/wfg_interconnect_reg.csv
new file mode 100644
index 0000000..ac0ed40
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_interconnect/data/wfg_interconnect_reg.csv
@@ -0,0 +1,11 @@
+Address,RegName,BitName,Access,HW,MSB,LSB,Reset,Description
+4'h0,CTRL,,,,,,,Control register for interconnect
+,,EN,rw,cfg,0,0,1'b0,Interconnect enable
+4'h4,DRIVER0,,,,,,,Driver configuration register
+,,SELECT,rw,cfg,1,0,0,"Select
+0 – wfg_stim_sine
+1 – wfg_stim_mem"
+4'h8,DRIVER1,,,,,,,Driver configuration register
+,,SELECT,rw,cfg,1,0,0,"Select
+0 – wfg_stim_sine
+1 – wfg_stim_mem"
diff --git a/verilog/rtl/waveform-generator/design/wfg_interconnect/data/wfg_interconnect_reg.json b/verilog/rtl/waveform-generator/design/wfg_interconnect/data/wfg_interconnect_reg.json
new file mode 100644
index 0000000..33317f5
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_interconnect/data/wfg_interconnect_reg.json
@@ -0,0 +1,46 @@
+{
+    "registers": {
+        "CTRL": {
+            "address": "4'h0",
+            "description": "Control register for interconnect",
+            "entries": {
+                "EN": {
+                    "LSB": "0",
+                    "MSB": "0",
+                    "access": "rw",
+                    "description": "Interconnect enable",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                }
+            }
+        },
+        "DRIVER0": {
+            "address": "4'h4",
+            "description": "Driver configuration register",
+            "entries": {
+                "SELECT": {
+                    "LSB": "0",
+                    "MSB": "1",
+                    "access": "rw",
+                    "description": "Select\n0 \u2013 wfg_stim_sine\n1 \u2013 wfg_stim_mem",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        },
+        "DRIVER1": {
+            "address": "4'h8",
+            "description": "Driver configuration register",
+            "entries": {
+                "SELECT": {
+                    "LSB": "0",
+                    "MSB": "1",
+                    "access": "rw",
+                    "description": "Select\n0 \u2013 wfg_stim_sine\n1 \u2013 wfg_stim_mem",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect.sv b/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect.sv
new file mode 100644
index 0000000..0931e22
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect.sv
@@ -0,0 +1,91 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+
+`ifndef WFG_INTERCONNECT_PKG
+`define WFG_INTERCONNECT_PKG
+typedef struct packed {
+    logic wfg_axis_tvalid;
+    logic [31:0] wfg_axis_tdata;
+} axis_t;
+`endif
+
+module wfg_interconnect #(
+    parameter int AXIS_DATA_WIDTH = 32
+) (
+    input logic clk,   // I; System clock
+    input logic rst_n, // I; active low reset
+
+    // Control
+    input logic ctrl_en_q_i,
+
+    // Configuration
+    input logic [1:0] driver0_select_q_i,
+    input logic [1:0] driver1_select_q_i,
+
+    // Stimuli
+    input axis_t stimulus_0,
+    input axis_t stimulus_1,
+
+    output logic wfg_axis_tready_stimulus_0,
+    output logic wfg_axis_tready_stimulus_1,
+
+    // Driver
+    output axis_t driver_0,
+    output axis_t driver_1,
+
+    input wfg_axis_tready_driver_0,
+    input wfg_axis_tready_driver_1
+);
+
+    // Driver 0
+    always_comb begin
+        case (driver0_select_q_i)
+            2'b00: driver_0 = stimulus_0;
+            2'b01: driver_0 = stimulus_1;
+            2'b10: driver_0 = '0;
+            2'b11: driver_0 = '0;
+            default: driver_0 = 'x;
+        endcase
+    end
+
+    // Driver 1
+    always_comb begin
+        case (driver1_select_q_i)
+            2'b00: driver_1 = stimulus_0;
+            2'b01: driver_1 = stimulus_1;
+            2'b10: driver_1 = '0;
+            2'b11: driver_1 = '0;
+            default: driver_1 = 'x;
+        endcase
+    end
+
+    // Stimulus 0
+    always_comb begin
+        if (driver0_select_q_i == 2'b00 && driver1_select_q_i == 2'b00) begin
+            wfg_axis_tready_stimulus_0 = wfg_axis_tready_driver_0 && wfg_axis_tready_driver_1;
+        end else if (driver0_select_q_i == 2'b00) begin
+            wfg_axis_tready_stimulus_0 = wfg_axis_tready_driver_0;
+        end else if (driver1_select_q_i == 2'b00) begin
+            wfg_axis_tready_stimulus_0 = wfg_axis_tready_driver_1;
+        end else begin
+            wfg_axis_tready_stimulus_0 = '0;
+        end
+    end
+
+    // Stimulus 1
+    always_comb begin
+        if (driver0_select_q_i == 2'b01 && driver1_select_q_i == 2'b01) begin
+            wfg_axis_tready_stimulus_1 = wfg_axis_tready_driver_0 && wfg_axis_tready_driver_1;
+        end else if (driver0_select_q_i == 2'b01) begin
+            wfg_axis_tready_stimulus_1 = wfg_axis_tready_driver_0;
+        end else if (driver1_select_q_i == 2'b01) begin
+            wfg_axis_tready_stimulus_1 = wfg_axis_tready_driver_1;
+        end else begin
+            wfg_axis_tready_stimulus_1 = '0;
+        end
+    end
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect_pkg.svh b/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect_pkg.svh
new file mode 100644
index 0000000..fafc1bb
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect_pkg.svh
@@ -0,0 +1,36 @@
+`ifndef WFG_INTERCONNECT_PKG
+`define WFG_INTERCONNECT_PKG
+
+//package wfg_interconnect_pkg;
+
+    /*interface stimulus_if (input test);
+        logic wfg_axis_tready;
+        logic wfg_axis_tvalid;
+        logic [31:0] wfg_axis_tdata;
+    endinterface
+
+    interface driver_if (input test);
+        logic wfg_axis_tready;
+        logic wfg_axis_tvalid;
+        logic [31:0] wfg_axis_tdata;
+    endinterface*/
+    
+    typedef struct packed
+    {
+        logic wfg_axis_tvalid;
+        logic [31:0] wfg_axis_tdata;
+    } stimulus_t;
+    
+    typedef struct packed
+    {
+        logic wfg_pat_sync;
+        logic wfg_pat_subcycle;
+        logic [7:0] wfg_pat_subcycle_cnt;
+
+        logic [31:0] wfg_axis_tdata;
+        logic wfg_axis_tvalid;
+    } driver_t;
+
+//endpackage
+
+`endif
diff --git a/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect_top.sv b/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect_top.sv
new file mode 100644
index 0000000..855dbd8
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect_top.sv
@@ -0,0 +1,109 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+
+`ifndef WFG_INTERCONNECT_PKG
+`define WFG_INTERCONNECT_PKG
+typedef struct packed {
+    logic wfg_axis_tvalid;
+    logic [31:0] wfg_axis_tdata;
+} axis_t;
+`endif
+
+module wfg_interconnect_top #(
+    parameter int BUSW = 32,
+    parameter int AXIS_DATA_WIDTH = 32
+) (
+    // Wishbone Slave ports
+    input                 wb_clk_i,
+    input                 wb_rst_i,
+    input                 wbs_stb_i,
+    input                 wbs_cyc_i,
+    input                 wbs_we_i,
+    input  [(BUSW/8-1):0] wbs_sel_i,
+    input  [  (BUSW-1):0] wbs_dat_i,
+    input  [  (BUSW-1):0] wbs_adr_i,
+    output                wbs_ack_o,
+    output [  (BUSW-1):0] wbs_dat_o,
+
+    // Core synchronisation interface
+    input wire wfg_pat_sync_i,     // I; pat_sync pulse
+    input wire wfg_pat_subcycle_i, // I; subcycle_cnt
+
+    // Stimuli
+    input axis_t stimulus_0,
+    input axis_t stimulus_1,
+
+    output wfg_axis_tready_stimulus_0,
+    output wfg_axis_tready_stimulus_1,
+
+    // Driver
+    output axis_t driver_0,
+    output axis_t driver_1,
+
+    input wfg_axis_tready_driver_0,
+    input wfg_axis_tready_driver_1
+);
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_interconnect_reg.json
+    //template: wishbone/instantiate_top.template
+    //marker_template_code
+
+    logic         ctrl_en_q;               // CTRL.EN register output
+    logic [ 1: 0] driver0_select_q;        // DRIVER0.SELECT register output
+    logic [ 1: 0] driver1_select_q;        // DRIVER1.SELECT register output
+
+    //marker_template_end
+
+    wfg_interconnect_wishbone_reg wfg_interconnect_wishbone_reg (
+        .wb_clk_i (wb_clk_i),
+        .wb_rst_i (wb_rst_i),
+        .wbs_stb_i(wbs_stb_i),
+        .wbs_cyc_i(wbs_cyc_i),
+        .wbs_we_i (wbs_we_i),
+        .wbs_sel_i(wbs_sel_i),
+        .wbs_dat_i(wbs_dat_i),
+        .wbs_adr_i(wbs_adr_i),
+        .wbs_ack_o(wbs_ack_o),
+        .wbs_dat_o(wbs_dat_o),
+
+        //marker_template_start
+        //data: ../data/wfg_interconnect_reg.json
+        //template: wishbone/assign_to_module.template
+        //marker_template_code
+
+        .ctrl_en_q_o       (ctrl_en_q),         // CTRL.EN register output
+        .driver0_select_q_o(driver0_select_q),  // DRIVER0.SELECT register output
+        .driver1_select_q_o(driver1_select_q)   // DRIVER1.SELECT register output
+
+        //marker_template_end
+    );
+
+    wfg_interconnect wfg_interconnect (
+        .clk  (wb_clk_i),  // clock signal
+        .rst_n(!wb_rst_i), // reset signal
+
+        // Control
+        .ctrl_en_q_i(ctrl_en_q),
+
+        // Configuration
+        .driver0_select_q_i(driver0_select_q),
+        .driver1_select_q_i(driver1_select_q),
+
+        .stimulus_0,
+        .stimulus_1,
+
+        .wfg_axis_tready_stimulus_0,
+        .wfg_axis_tready_stimulus_1,
+
+        .driver_0,
+        .driver_1,
+
+        .wfg_axis_tready_driver_0,
+        .wfg_axis_tready_driver_1
+    );
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect_wishbone_reg.sv b/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect_wishbone_reg.sv
new file mode 100644
index 0000000..57fd417
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_interconnect/rtl/wfg_interconnect_wishbone_reg.sv
@@ -0,0 +1,116 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_interconnect_wishbone_reg #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                       wb_clk_i,
+    input                       wb_rst_i,
+    input                       wbs_stb_i,
+    input                       wbs_cyc_i,
+    input                       wbs_we_i,
+    input        [(BUSW/8-1):0] wbs_sel_i,
+    input        [  (BUSW-1):0] wbs_dat_i,
+    input        [  (BUSW-1):0] wbs_adr_i,
+    output logic                wbs_ack_o,
+    output logic [  (BUSW-1):0] wbs_dat_o,
+
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_interconnect_reg.json
+    //template: wishbone/register_interface.template
+    //marker_template_code
+
+    output logic       ctrl_en_q_o,         // CTRL.EN register output
+    output logic [1:0] driver0_select_q_o,  // DRIVER0.SELECT register output
+    output logic [1:0] driver1_select_q_o   // DRIVER1.SELECT register output
+
+    //marker_template_end
+);
+
+    //marker_template_start
+    //data: ../data/wfg_interconnect_reg.json
+    //template: wishbone/instantiate_registers.template
+    //marker_template_code
+
+    logic         ctrl_en_ff;              // CTRL.EN FF
+    logic [ 1: 0] driver0_select_ff;       // DRIVER0.SELECT FF
+    logic [ 1: 0] driver1_select_ff;       // DRIVER1.SELECT FF
+
+    //marker_template_end
+
+    // Wishbone write to slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            //marker_template_start
+            //data: ../data/wfg_interconnect_reg.json
+            //template: wishbone/reset_registers.template
+            //marker_template_code
+
+            ctrl_en_ff        <= 1'b0;
+            driver0_select_ff <= 0;
+            driver1_select_ff <= 0;
+
+            //marker_template_end
+        end else if (wbs_stb_i && wbs_we_i && wbs_cyc_i) begin
+            case (wbs_adr_i)
+                //marker_template_start
+                //data: ../data/wfg_interconnect_reg.json
+                //template: wishbone/assign_to_registers.template
+                //marker_template_code
+
+                4'h0:       ctrl_en_ff               <= wbs_dat_i[ 0: 0];
+                4'h4:       driver0_select_ff        <= wbs_dat_i[ 1: 0];
+                4'h8:       driver1_select_ff        <= wbs_dat_i[ 1: 0];
+
+                //marker_template_end
+                default: begin
+                end
+            endcase
+        end
+    end
+
+    // Wishbone read from slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            wbs_dat_o <= '0;
+        end else begin
+            if (wbs_stb_i && !wbs_we_i && wbs_cyc_i) begin
+                wbs_dat_o <= '0;  // default value
+                case (wbs_adr_i)
+                    //marker_template_start
+                    //data: ../data/wfg_interconnect_reg.json
+                    //template: wishbone/assign_from_registers.template
+                    //marker_template_code
+
+                    4'h0:       wbs_dat_o[ 0: 0] <= ctrl_en_ff;
+                    4'h4:       wbs_dat_o[ 1: 0] <= driver0_select_ff;
+                    4'h8:       wbs_dat_o[ 1: 0] <= driver1_select_ff;
+
+                    //marker_template_end
+                    default:    wbs_dat_o <= 'X;
+                endcase
+            end
+        end
+    end
+
+    // Acknowledgement
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) wbs_ack_o <= 1'b0;
+        else wbs_ack_o <= wbs_stb_i && wbs_cyc_i & !wbs_ack_o;
+    end
+
+    //marker_template_start
+    //data: ../data/wfg_interconnect_reg.json
+    //template: wishbone/assign_outputs.template
+    //marker_template_code
+
+    assign ctrl_en_q_o        = ctrl_en_ff;
+    assign driver0_select_q_o = driver0_select_ff;
+    assign driver1_select_q_o = driver1_select_ff;
+
+    //marker_template_end
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_interconnect/sim/Makefile b/verilog/rtl/waveform-generator/design/wfg_interconnect/sim/Makefile
new file mode 100644
index 0000000..3a7c3ec
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_interconnect/sim/Makefile
@@ -0,0 +1,19 @@
+# source files
+SRC := $(wildcard ../rtl/*.sv)
+SRC += $(wildcard ../testbench/*.sv)
+
+# defaults
+SIM ?= icarus
+TOPLEVEL_LANG ?= verilog
+
+VERILOG_SOURCES += $(SRC)
+
+# TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file
+TOPLEVEL = wfg_interconnect_tb
+
+# MODULE is the basename of the Python test file
+export PYTHONPATH := $(PYTHONPATH):../testbench/
+MODULE = test_wfg_interconnect
+
+# include cocotb's make rules to take care of the simulator setup
+include $(shell cocotb-config --makefiles)/Makefile.sim
diff --git a/verilog/rtl/waveform-generator/design/wfg_interconnect/testbench/test_wfg_interconnect.py b/verilog/rtl/waveform-generator/design/wfg_interconnect/testbench/test_wfg_interconnect.py
new file mode 100644
index 0000000..8ac78cd
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_interconnect/testbench/test_wfg_interconnect.py
@@ -0,0 +1,109 @@
+# SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import cocotb
+from cocotb.clock import Clock
+from cocotb.regression import TestFactory
+from cocotb.triggers import Timer, RisingEdge, FallingEdge
+from cocotbext.wishbone.driver import WishboneMaster
+from cocotbext.wishbone.driver import WBOp
+from cocotbext.axi import AxiStreamBus, AxiStreamSource, AxiStreamSink
+from cocotbext.spi import SpiMaster, SpiSignals, SpiConfig, SpiSlaveBase
+#from random import randbytes # Possible in Python 3.9+
+
+SYSCLK = 100000000
+DATA_CNT = 10
+NUM_CHANNELS = 2
+
+short_per = Timer(100, units="ns")
+long_time = Timer(100, units="us")
+
+async def set_register(dut, wbs, address, data):
+    dut._log.info(f"Set register {address} : {data}")
+    wbRes = await wbs.send_cycle([WBOp(address, data)])
+    rvalues = [wb.datrd for wb in wbRes]
+    dut._log.info(f"Returned values : {rvalues}")
+
+async def configure(dut, wbs, en=1, select0=0, select1=0):
+    await set_register(dut, wbs, 0x8, select1)
+    await set_register(dut, wbs, 0x4, select0)
+    await set_register(dut, wbs, 0x0, en) # Enable
+
+async def receive_data(axis_sink, received_data, cnt):
+    while 1:
+        data = await axis_sink.recv()
+        received_data[cnt].append(data.tdata[0])
+
+@cocotb.coroutine
+async def test_interconnect(dut, en, select0, select1):
+    dut._log.info(f"Configuration: en={en}, select0={select0}, select1={select1}")
+
+    cocotb.start_soon(Clock(dut.io_wbs_clk, 1/SYSCLK*1e9, units="ns").start())
+
+    dut._log.info("Initialize and reset model")
+    
+    received_data = [[] for y in range(NUM_CHANNELS)]
+    
+    # Start reset
+    dut.io_wbs_rst.value = 1
+    dut.stimulus_0.value = 1
+    await Timer(100, units='ns')
+
+    # Stop reset
+    dut.io_wbs_rst.value = 0
+
+    # Wishbone Master
+    wbs = WishboneMaster(dut, "io_wbs", dut.io_wbs_clk,
+                              width=32,   # size of data bus
+                              timeout=10) # in clock cycle number
+
+    # Setup
+    await configure(dut, wbs, en=en, select0=select0, select1=select1)
+
+    await short_per
+
+    axis_stimulus_0 = AxiStreamSource(AxiStreamBus.from_prefix(dut, "stimulus_0_wfg_axis"), dut.io_wbs_clk, dut.io_wbs_rst)
+    
+    axis_stimulus_1 = AxiStreamSource(AxiStreamBus.from_prefix(dut, "stimulus_1_wfg_axis"), dut.io_wbs_clk, dut.io_wbs_rst)
+
+    axis_driver_0 = AxiStreamSink(AxiStreamBus.from_prefix(dut, "driver_0_wfg_axis"), dut.io_wbs_clk, dut.io_wbs_rst)
+
+    axis_driver_1 = AxiStreamSink(AxiStreamBus.from_prefix(dut, "driver_1_wfg_axis"), dut.io_wbs_clk, dut.io_wbs_rst)
+    
+    cocotb.start_soon(receive_data(axis_driver_0, received_data, 0))
+    cocotb.start_soon(receive_data(axis_driver_1, received_data, 1))
+
+    send_data = [[int.from_bytes(os.getrandom(4, os.GRND_NONBLOCK), "big") for _ in range(DATA_CNT)] for y in range(NUM_CHANNELS)]
+
+    for cnt in range(len(send_data)):
+        for data in send_data[cnt]:
+
+            if cnt == 0:
+                await axis_stimulus_0.send([data])
+            if cnt == 1:
+                await axis_stimulus_1.send([data])
+
+            await short_per
+    
+    await short_per
+
+    if select0 == 0 and select1 == 0:
+        assert(received_data[0] == send_data[0])
+        assert(received_data[1] == send_data[0])
+    if select0 == 1 and select1 == 0:
+        assert(received_data[0] == send_data[1])
+        assert(received_data[1] == send_data[0])
+    if select0 == 0 and select1 == 1:
+        assert(received_data[0] == send_data[0])
+        assert(received_data[1] == send_data[1])
+    if select0 == 1 and select1 == 1:
+        assert(received_data[0] == send_data[1])
+        assert(received_data[1] == send_data[1])
+
+factory = TestFactory(test_interconnect)
+factory.add_option("en", [1])
+factory.add_option("select0", [0, 1])
+factory.add_option("select1", [0, 1])
+
+factory.generate_tests()
diff --git a/verilog/rtl/waveform-generator/design/wfg_interconnect/testbench/wfg_interconnect_tb.sv b/verilog/rtl/waveform-generator/design/wfg_interconnect/testbench/wfg_interconnect_tb.sv
new file mode 100644
index 0000000..5112546
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_interconnect/testbench/wfg_interconnect_tb.sv
@@ -0,0 +1,105 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`timescale 1ns / 1ps
+
+`ifdef VERILATOR  // make parameter readable from VPI
+`define VL_RD /*verilator public_flat_rd*/
+`else
+`define VL_RD
+`endif
+
+`ifndef WFG_INTERCONNECT_PKG
+`define WFG_INTERCONNECT_PKG
+typedef struct packed {
+    logic wfg_axis_tvalid;
+    logic [31:0] wfg_axis_tdata;
+} axis_t;
+`endif
+
+module wfg_interconnect_tb #(
+    parameter int BUSW = 32,
+    parameter int AXIS_DATA_WIDTH = 32
+) (
+    // Wishbone interface signals
+    input               io_wbs_clk,
+    input               io_wbs_rst,
+    input  [(BUSW-1):0] io_wbs_adr,
+    input  [(BUSW-1):0] io_wbs_datwr,
+    output [(BUSW-1):0] io_wbs_datrd,
+    input               io_wbs_we,
+    input               io_wbs_stb,
+    output              io_wbs_ack,
+    input               io_wbs_cyc,
+
+    // Stimuli
+    input stimulus_0_wfg_axis_tvalid,
+    input logic [31:0] stimulus_0_wfg_axis_tdata,
+
+    input stimulus_1_wfg_axis_tvalid,
+    input logic [31:0] stimulus_1_wfg_axis_tdata,
+
+    output logic stimulus_0_wfg_axis_tready,
+    output logic stimulus_1_wfg_axis_tready,
+
+    // Driver
+    output driver_0_wfg_axis_tvalid,
+    output logic [31:0] driver_0_wfg_axis_tdata,
+
+    output driver_1_wfg_axis_tvalid,
+    output logic [31:0] driver_1_wfg_axis_tdata,
+
+    input driver_0_wfg_axis_tready,
+    input driver_1_wfg_axis_tready
+);
+    axis_t stimulus_0;
+    assign stimulus_0.wfg_axis_tdata  = stimulus_0_wfg_axis_tdata;
+    assign stimulus_0.wfg_axis_tvalid = stimulus_0_wfg_axis_tvalid;
+
+    axis_t stimulus_1;
+    assign stimulus_1.wfg_axis_tdata  = stimulus_1_wfg_axis_tdata;
+    assign stimulus_1.wfg_axis_tvalid = stimulus_1_wfg_axis_tvalid;
+
+    axis_t driver_0;
+    assign driver_0_wfg_axis_tdata  = driver_0.wfg_axis_tdata;
+    assign driver_0_wfg_axis_tvalid = driver_0.wfg_axis_tvalid;
+
+    axis_t driver_1;
+    assign driver_1_wfg_axis_tdata  = driver_1.wfg_axis_tdata;
+    assign driver_1_wfg_axis_tvalid = driver_1.wfg_axis_tvalid;
+
+
+    wfg_interconnect_top wfg_interconnect_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(io_wbs_adr),
+        .wbs_ack_o(io_wbs_ack),
+        .wbs_dat_o(io_wbs_datrd),
+
+        .stimulus_0,
+        .stimulus_1,
+
+        .wfg_axis_tready_stimulus_0(stimulus_0_wfg_axis_tready),
+        .wfg_axis_tready_stimulus_1(stimulus_1_wfg_axis_tready),
+
+        .driver_0,
+        .driver_1,
+
+        .wfg_axis_tready_driver_0(driver_0_wfg_axis_tready),
+        .wfg_axis_tready_driver_1(driver_1_wfg_axis_tready)
+    );
+
+    // Dump waves
+`ifndef VERILATOR
+    initial begin
+        $dumpfile("wfg_interconnect_tb.vcd");
+        $dumpvars(0, wfg_interconnect_tb);
+    end
+`endif
+
+endmodule
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_mem/data/wfg_stim_mem_reg.csv b/verilog/rtl/waveform-generator/design/wfg_stim_mem/data/wfg_stim_mem_reg.csv
new file mode 100644
index 0000000..f851df9
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_mem/data/wfg_stim_mem_reg.csv
@@ -0,0 +1,10 @@
+Address,RegName,BitName,Access,HW,MSB,LSB,Reset,Description
+4'h0,CTRL,,,,,,,Control register for memory unit
+,,EN,rw,cfg,0,0,1'b0,Enables stimuli generation
+4'h4,START,,,,,,,Start register
+,,VAL,rw,cfg,15,0,0,Address start value
+4'h8,END,,,,,,,End register
+,,VAL,rw,cfg,15,0,0,Address end value
+4'hC,CFG,,,,,,,Configuration register
+,,INC,rw,cfg,7,0,8'h01,Increment the address with value
+,,GAIN,rw,cfg,23,8,8'h01,Gain value
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_mem/data/wfg_stim_mem_reg.json b/verilog/rtl/waveform-generator/design/wfg_stim_mem/data/wfg_stim_mem_reg.json
new file mode 100644
index 0000000..c188c24
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_mem/data/wfg_stim_mem_reg.json
@@ -0,0 +1,68 @@
+{
+    "registers": {
+        "CFG": {
+            "address": "4'hC",
+            "description": "Configuration register",
+            "entries": {
+                "GAIN": {
+                    "LSB": "8",
+                    "MSB": "23",
+                    "access": "rw",
+                    "description": "Gain value",
+                    "hardware": "cfg",
+                    "reset": "8'h01"
+                },
+                "INC": {
+                    "LSB": "0",
+                    "MSB": "7",
+                    "access": "rw",
+                    "description": "Increment the address with value",
+                    "hardware": "cfg",
+                    "reset": "8'h01"
+                }
+            }
+        },
+        "CTRL": {
+            "address": "4'h0",
+            "description": "Control register for memory unit",
+            "entries": {
+                "EN": {
+                    "LSB": "0",
+                    "MSB": "0",
+                    "access": "rw",
+                    "description": "Enables stimuli generation",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                }
+            }
+        },
+        "END": {
+            "address": "4'h8",
+            "description": "End register",
+            "entries": {
+                "VAL": {
+                    "LSB": "0",
+                    "MSB": "15",
+                    "access": "rw",
+                    "description": "Address end value",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        },
+        "START": {
+            "address": "4'h4",
+            "description": "Start register",
+            "entries": {
+                "VAL": {
+                    "LSB": "0",
+                    "MSB": "15",
+                    "access": "rw",
+                    "description": "Address start value",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/dsp_scale_sn_us.sv b/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/dsp_scale_sn_us.sv
new file mode 100644
index 0000000..617970d
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/dsp_scale_sn_us.sv
@@ -0,0 +1,255 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+
+module dsp_scale_sn_us #(
+    parameter integer DATA_W     = 8,
+    parameter integer SCALE_W    = 16,
+    parameter integer SCALE_SHFT = 1
+) (
+    input wire clk,      // I; System clock
+    input wire reset_ni, // I; active loaw reset
+
+    // Data interface
+    input wire scale_gif_data_in_update_i,  // I; GIF update pulse
+    input wire signed [DATA_W-1 : 0] scale_gif_data_in_i,  // I; GIF data to be scaled (signed)
+    input wire [SCALE_W-1:0] scale_factor_i,  // I; Scaling factor (unsigned)
+    output logic scale_gif_result_update_o,  // O; GIF update pulse
+    output logic signed [DATA_W-1 : 0] scale_gif_result_o  // O; GIF scaled data (signed)
+);
+
+
+
+    // -------------------------------------------------------------------------
+    // Definition
+    // -------------------------------------------------------------------------
+    localparam integer MUL_SIZE = DATA_W + SCALE_W + 1;
+    localparam integer OVL_SIZE = SCALE_W - SCALE_SHFT + 1;
+    localparam logic [OVL_SIZE:0] NO_OVL_POS = '0;
+    localparam logic [OVL_SIZE:0] NO_OVL_NEG = '1;
+
+    // multiplication result
+    logic signed [MUL_SIZE-1:0] mul_result;
+    logic signed [DATA_W-1  :0] mul_result_sat;
+
+    // FF for storing the result
+    logic                      scale_gif_result_update_ff;     // update pulse
+    logic signed [DATA_W-1 :0] scale_gif_result_ff;            // GIF scaled data (signed)
+
+
+
+    // -------------------------------------------------------------------------
+    // Implementation
+    // -------------------------------------------------------------------------
+
+
+    // Saturation and scaling
+    always_comb begin
+        mul_result = $signed({1'b0, scale_factor_i}) * $signed(scale_gif_data_in_i);
+        if ((mul_result[MUL_SIZE-1 -: OVL_SIZE+1] == NO_OVL_POS) | (mul_result[MUL_SIZE-1 -: OVL_SIZE+1] == NO_OVL_NEG)) begin
+            // no overflow or underflow
+            //mul_result_sat = mul_result[ (MUL_SIZE-1) - (SCALE_W-SCALE_SHFT+1) -: DATA_W];
+            mul_result_sat = mul_result[DATA_W-1 : 0];
+        end else if (mul_result[MUL_SIZE-1]) begin
+            // underflow
+            mul_result_sat = {1'b1, {(DATA_W - 1) {1'b0}}};
+        end else begin
+            // overflow
+            mul_result_sat = {1'b0, {(DATA_W - 1) {1'b1}}};
+        end
+    end
+
+    // Result storage
+    always_ff @(posedge clk, negedge reset_ni) begin
+        if (~reset_ni) begin
+`ifndef FPGA_ASYNC_RESET_DISABLE
+            scale_gif_result_ff <= '0;
+`endif
+            scale_gif_result_update_ff <= 1'b0;
+        end else begin
+            if (scale_gif_data_in_update_i) begin
+                scale_gif_result_ff        <= mul_result_sat;
+                scale_gif_result_update_ff <= 1'b1;
+            end else begin
+                scale_gif_result_update_ff <= 1'b0;
+            end
+        end
+    end
+
+
+    // Outputs ------------------------------------------------------------------
+    assign scale_gif_result_update_o = scale_gif_result_update_ff;
+    assign scale_gif_result_o        = scale_gif_result_ff;
+
+
+endmodule
+
+
+module dsp_scale_sn_us_2 #(
+    parameter integer DATA_W     = 16,
+    parameter integer SCALE_W    = 16,
+    parameter integer SCALE_SHFT = 1
+) (
+    input wire clk,      // I; System clock
+    input wire reset_ni, // I; active loaw reset
+
+    // Data interface
+    input wire scale_gif_data_in_update_i,  // I; GIF update pulse
+    input wire signed [DATA_W-1 : 0] scale_gif_data_in_i,  // I; GIF data to be scaled (signed)
+    input wire [SCALE_W-1:0] scale_factor_i,  // I; Scaling factor (unsigned)
+    output logic scale_gif_result_update_o,  // O; GIF update pulse
+    output logic signed [DATA_W-1 : 0] scale_gif_result_o  // O; GIF scaled data (signed)
+);
+
+    // -------------------------------------------------------------------------
+    // Definition
+    // -------------------------------------------------------------------------
+    localparam integer MUL_SIZE = DATA_W + SCALE_W + SCALE_SHFT;  // width of full muliplcation
+    localparam integer MUL_SAT_SIZE    = MUL_SIZE - (SCALE_W - SCALE_SHFT); // width of saturated multiplaction result
+
+    // multiplication result
+    logic signed [MUL_SIZE-1:0] mul_result;
+    logic signed [MUL_SAT_SIZE-1:0] mul_result_sat;       // saturated multiplication
+    logic signed [DATA_W-1      :0] mul_result_sat_round; // saturated multiplication rounded to output bit width
+
+    // FF for storing the result
+    logic                      scale_gif_result_update_ff;     // update pulse
+    logic signed [DATA_W-1 :0] scale_gif_result_ff;            // GIF scaled data (signed)
+
+
+
+    // -------------------------------------------------------------------------
+    // Implementation
+    // -------------------------------------------------------------------------
+
+    // Multiplication
+    // assign mul_result = $signed({1'b0, scale_factor_i}) * $signed(scale_gif_data_in_i);
+    assign mul_result = {1'b0, scale_factor_i} * $signed(
+        {scale_gif_data_in_i, {(SCALE_SHFT) {1'b0}}}
+    );
+
+    // Saturation
+    dsp_saturate_sn #(
+        .DATA_IN_W (MUL_SIZE),
+        .DATA_OUT_W(MUL_SAT_SIZE)
+    ) u_dsp_saturate_sn (
+        .data_in_i (mul_result),     // I; data to be saturated
+        .data_of_o (),               // O; indicates that a data overflow occurs
+        .data_out_o(mul_result_sat)  // O; saturated data
+    );
+
+    // Rounding
+    dsp_round_sn #(
+        .DATA_IN_W (MUL_SAT_SIZE),
+        .DATA_OUT_W(DATA_W)
+    ) u_dsp_round_us (
+        .data_in_i (mul_result_sat),       // I; data to be rounded
+        .data_out_o(mul_result_sat_round)  // O; rounded data
+    );
+
+    // Result storage
+    always_ff @(posedge clk, negedge reset_ni) begin
+        if (~reset_ni) begin
+`ifndef FPGA_ASYNC_RESET_DISABLE
+            scale_gif_result_ff <= '0;
+`endif
+            scale_gif_result_update_ff <= 1'b0;
+        end else begin
+            if (scale_gif_data_in_update_i) begin
+                scale_gif_result_ff        <= mul_result_sat;
+                scale_gif_result_update_ff <= 1'b1;
+            end else begin
+                scale_gif_result_update_ff <= 1'b0;
+            end
+        end
+    end
+
+
+    // Outputs ------------------------------------------------------------------
+    assign scale_gif_result_update_o = scale_gif_result_update_ff;
+    assign scale_gif_result_o        = scale_gif_result_ff;
+
+
+endmodule
+
+`default_nettype wire
+
+
+`ifdef DSP_SCALE_SN_US_TB
+// Mini TB
+module dsp_scale_sn_us_tb ();
+
+    logic signed [7:0] data_in;
+    logic              data_in_update;
+    logic signed [7:0] data_out;
+    logic        [3:0] scale_factor;
+
+    bit clk;                       // System clock
+    bit reset_ni;                  // active loaw reset
+
+    dsp_scale_sn_us_2 #(
+        .DATA_W    (8),
+        .SCALE_W   (4),
+        .SCALE_SHFT(3)
+    ) u_dsp_scale_sn_us_2 (
+        .clk     (clk),      // I; System clock
+        .reset_ni(reset_ni), // I; active loaw reset
+
+        .scale_gif_data_in_update_i(data_in_update),  // I; GIF update pulse
+        .scale_gif_data_in_i       (data_in),         // I; GIF data to be scaled (signed)
+        .scale_factor_i            (scale_factor),    // I; Scaling factor (unsigned)
+        .scale_gif_result_update_o (),                // O; GIF update pulse
+        .scale_gif_result_o        ()                 // O; GIF scaled data (signed)
+    );
+
+    dsp_scale_sn_us #(
+        .DATA_W    (8),
+        .SCALE_W   (4),
+        .SCALE_SHFT(3)
+    ) u_dsp_scale_sn_us (
+        .clk     (clk),      // I; System clock
+        .reset_ni(reset_ni), // I; active loaw reset
+
+        .scale_gif_data_in_update_i(data_in_update),  // I; GIF update pulse
+        .scale_gif_data_in_i       (data_in),         // I; GIF data to be scaled (signed)
+        .scale_factor_i            (scale_factor),    // I; Scaling factor (unsigned)
+        .scale_gif_result_update_o (),                // O; GIF update pulse
+        .scale_gif_result_o        ()                 // O; GIF scaled data (signed)
+    );
+
+    assign #5 clk = ~clk;
+    initial begin
+        reset_ni = 1'b0;
+        data_in_update = 1'b0;
+        data_in = 8'h00;
+        @(negedge clk);
+        reset_ni = 1'b1;
+        @(negedge clk);
+        @(negedge clk);
+
+        for (int i = 0; i < 16; i++) begin
+            @(negedge clk);
+            data_in        = 23;
+            scale_factor   = i;
+            data_in_update = 1'b1;
+            @(negedge clk);
+            data_in_update = 1'b0;
+            @(negedge clk);
+            @(negedge clk);
+            @(negedge clk);
+        end
+
+        repeat (10) begin
+            @(negedge clk);
+        end
+
+        $finish();
+    end
+
+    assign data_rounded = data_out * $signed({1'b0, 3'b100});
+
+endmodule
+`endif
+
+
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/wfg_stim_mem.sv b/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/wfg_stim_mem.sv
new file mode 100644
index 0000000..6ddc575
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/wfg_stim_mem.sv
@@ -0,0 +1,117 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_stim_mem (
+    input wire clk,   // clock signal
+    input wire rst_n, // reset signal
+
+    input  wire        wfg_axis_tready_i,  // ready signal - AXI
+    output wire        wfg_axis_tvalid_o,  // valid signal - AXI
+    output wire [31:0] wfg_axis_tdata_o,   // mem output   - AXI
+
+    input wire         ctrl_en_q_i,    // enable/disable
+    input logic [15:0] end_val_q_i,    // END.VAL register output
+    input logic [15:0] start_val_q_i,  // START.VAL register output
+    input logic [ 7:0] cfg_inc_q_i,    // CFG.INC register output
+    input logic [15:0] cfg_gain_q_i,   // CFG.GAIN register output
+
+    // Memory interface
+    output              csb1,
+    output       [ 9:0] addr1,
+    input  logic [31:0] dout1
+);
+    logic [15:0] cur_address;
+    logic [31:0] data;
+    logic [31:0] data_calc;
+    logic valid;
+
+    assign csb1              = !ctrl_en_q_i;  // Enable the memory
+    assign addr1             = cur_address[9:0];  // Assign address
+
+    assign wfg_axis_tvalid_o = valid;
+    assign wfg_axis_tdata_o  = data;
+
+    typedef enum {
+        ST_IDLE,
+        ST_READ,
+        ST_CALC,
+        ST_DONE
+    } wfg_stim_mem_states_t;
+
+    wfg_stim_mem_states_t cur_state;
+    wfg_stim_mem_states_t next_state;
+
+    always_ff @(posedge clk, negedge rst_n) begin
+        if (!rst_n) cur_state <= ST_IDLE;
+        else cur_state <= next_state;
+    end
+
+    always_comb begin
+        next_state = cur_state;
+        case (cur_state)
+            ST_IDLE: begin
+                if (ctrl_en_q_i) next_state = ST_READ;
+            end
+            ST_READ: begin
+                next_state = ST_CALC;
+            end
+            ST_CALC: begin
+                next_state = ST_DONE;
+            end
+            ST_DONE: begin
+                if (wfg_axis_tready_i == 1'b1) next_state = ST_IDLE;
+            end
+        endcase
+    end
+
+    always_ff @(posedge clk, negedge rst_n) begin
+        if (!rst_n) begin
+            cur_address <= '0;
+            valid <= '0;
+            data <= '0;
+        end else begin
+            valid <= '0;
+
+            case (cur_state)
+                ST_IDLE: begin
+                    if (!ctrl_en_q_i) begin
+                        cur_address <= start_val_q_i;
+                    end
+                end
+                ST_READ: begin
+                    if (cur_address + cfg_inc_q_i > end_val_q_i) begin
+                        cur_address <= start_val_q_i;
+                    end else begin
+                        cur_address <= cur_address + cfg_inc_q_i;
+                    end
+                end
+                ST_CALC: begin
+                    data <= data_calc;
+                end
+                ST_DONE: begin
+                    valid <= '1;
+                end
+                default: valid <= 'x;
+            endcase
+        end
+    end
+
+    dsp_scale_sn_us #(
+        .DATA_W(32),
+        .SCALE_W(16),
+        .SCALE_SHFT(0)
+    ) dsp_scale_sn_us_inst (
+        .clk     (clk),   // I; System clock
+        .reset_ni(rst_n), // I; active loaw reset
+
+        // Data interface
+        .scale_gif_data_in_update_i(next_state == ST_CALC),  // I; GIF update pulse
+        .scale_gif_data_in_i       (dout1),                  // I; GIF data to be scaled (signed)
+        .scale_factor_i            (cfg_gain_q_i),           // I; Scaling factor (unsigned)
+        .scale_gif_result_update_o (),                       // O; GIF update pulse
+        .scale_gif_result_o        (data_calc)
+    );
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/wfg_stim_mem_top.sv b/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/wfg_stim_mem_top.sv
new file mode 100644
index 0000000..f4ee5f2
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/wfg_stim_mem_top.sv
@@ -0,0 +1,87 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_stim_mem_top #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                 wb_clk_i,
+    input                 wb_rst_i,
+    input                 wbs_stb_i,
+    input                 wbs_cyc_i,
+    input                 wbs_we_i,
+    input  [(BUSW/8-1):0] wbs_sel_i,
+    input  [  (BUSW-1):0] wbs_dat_i,
+    input  [  (BUSW-1):0] wbs_adr_i,
+    output                wbs_ack_o,
+    output [  (BUSW-1):0] wbs_dat_o,
+
+    // AXI-Stream interface
+    input         wfg_axis_tready_i,
+    output        wfg_axis_tvalid_o,
+    output [31:0] wfg_axis_tdata_o,
+
+    // Memory interface
+    output        csb1,
+    output [ 9:0] addr1,
+    input  [31:0] dout1
+);
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_stim_mem_reg.json
+    //template: wishbone/instantiate_top.template
+    //marker_template_code
+
+    logic [23: 8] cfg_gain_q;              // CFG.GAIN register output
+    logic [ 7: 0] cfg_inc_q;               // CFG.INC register output
+    logic         ctrl_en_q;               // CTRL.EN register output
+    logic [15: 0] end_val_q;               // END.VAL register output
+    logic [15: 0] start_val_q;             // START.VAL register output
+
+    //marker_template_end
+
+    wfg_stim_mem_wishbone_reg wfg_stim_mem_wishbone_reg (
+        .wb_clk_i (wb_clk_i),
+        .wb_rst_i (wb_rst_i),
+        .wbs_stb_i(wbs_stb_i),
+        .wbs_cyc_i(wbs_cyc_i),
+        .wbs_we_i (wbs_we_i),
+        .wbs_sel_i(wbs_sel_i),
+        .wbs_dat_i(wbs_dat_i),
+        .wbs_adr_i(wbs_adr_i),
+        .wbs_ack_o(wbs_ack_o),
+        .wbs_dat_o(wbs_dat_o),
+
+        //marker_template_start
+        //data: ../data/wfg_stim_mem_reg.json
+        //template: wishbone/assign_to_module.template
+        //marker_template_code
+
+        .cfg_gain_q_o (cfg_gain_q),  // CFG.GAIN register output
+        .cfg_inc_q_o  (cfg_inc_q),   // CFG.INC register output
+        .ctrl_en_q_o  (ctrl_en_q),   // CTRL.EN register output
+        .end_val_q_o  (end_val_q),   // END.VAL register output
+        .start_val_q_o(start_val_q)  // START.VAL register output
+
+        //marker_template_end
+    );
+
+    wfg_stim_mem wfg_stim_mem (
+        .clk              (wb_clk_i),           // clock signal
+        .rst_n            (!wb_rst_i),          // reset signal
+        .wfg_axis_tready_i(wfg_axis_tready_i),  // ready signal - AXI
+        .wfg_axis_tvalid_o(wfg_axis_tvalid_o),  // valid signal - AXI
+        .wfg_axis_tdata_o (wfg_axis_tdata_o),   // mem output   - AXI
+        .ctrl_en_q_i      (ctrl_en_q),          // enable/disable
+        .end_val_q_i      (end_val_q),
+        .start_val_q_i    (start_val_q),
+        .cfg_inc_q_i      (cfg_inc_q),
+        .cfg_gain_q_i     (cfg_gain_q),
+        .csb1             (csb1),
+        .addr1            (addr1),
+        .dout1            (dout1)
+    );
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/wfg_stim_mem_wishbone_reg.sv b/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/wfg_stim_mem_wishbone_reg.sv
new file mode 100644
index 0000000..e8fc64e
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_mem/rtl/wfg_stim_mem_wishbone_reg.sv
@@ -0,0 +1,132 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_stim_mem_wishbone_reg #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                       wb_clk_i,
+    input                       wb_rst_i,
+    input                       wbs_stb_i,
+    input                       wbs_cyc_i,
+    input                       wbs_we_i,
+    input        [(BUSW/8-1):0] wbs_sel_i,
+    input        [  (BUSW-1):0] wbs_dat_i,
+    input        [  (BUSW-1):0] wbs_adr_i,
+    output logic                wbs_ack_o,
+    output logic [  (BUSW-1):0] wbs_dat_o,
+
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_stim_mem_reg.json
+    //template: wishbone/register_interface.template
+    //marker_template_code
+
+    output logic [23:8] cfg_gain_q_o,  // CFG.GAIN register output
+    output logic [ 7:0] cfg_inc_q_o,   // CFG.INC register output
+    output logic        ctrl_en_q_o,   // CTRL.EN register output
+    output logic [15:0] end_val_q_o,   // END.VAL register output
+    output logic [15:0] start_val_q_o  // START.VAL register output
+
+    //marker_template_end
+);
+
+    //marker_template_start
+    //data: ../data/wfg_stim_mem_reg.json
+    //template: wishbone/instantiate_registers.template
+    //marker_template_code
+
+    logic [23: 8] cfg_gain_ff;             // CFG.GAIN FF
+    logic [ 7: 0] cfg_inc_ff;              // CFG.INC FF
+    logic         ctrl_en_ff;              // CTRL.EN FF
+    logic [15: 0] end_val_ff;              // END.VAL FF
+    logic [15: 0] start_val_ff;            // START.VAL FF
+
+    //marker_template_end
+
+    // Wishbone write to slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            //marker_template_start
+            //data: ../data/wfg_stim_mem_reg.json
+            //template: wishbone/reset_registers.template
+            //marker_template_code
+
+            cfg_gain_ff  <= 8'h01;
+            cfg_inc_ff   <= 8'h01;
+            ctrl_en_ff   <= 1'b0;
+            end_val_ff   <= 0;
+            start_val_ff <= 0;
+
+            //marker_template_end
+        end else if (wbs_stb_i && wbs_we_i && wbs_cyc_i) begin
+            case (wbs_adr_i)
+                //marker_template_start
+                //data: ../data/wfg_stim_mem_reg.json
+                //template: wishbone/assign_to_registers.template
+                //marker_template_code
+
+                4'hC: begin
+                    cfg_gain_ff <= wbs_dat_i[23:8];
+                    cfg_inc_ff  <= wbs_dat_i[7:0];
+                end
+                4'h0:       ctrl_en_ff               <= wbs_dat_i[ 0: 0];
+                4'h8:       end_val_ff               <= wbs_dat_i[15: 0];
+                4'h4:       start_val_ff             <= wbs_dat_i[15: 0];
+
+                //marker_template_end
+                default: begin
+                end
+            endcase
+        end
+    end
+
+    // Wishbone read from slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            wbs_dat_o <= '0;
+        end else begin
+            if (wbs_stb_i && !wbs_we_i && wbs_cyc_i) begin
+                wbs_dat_o <= '0;  // default value
+                case (wbs_adr_i)
+                    //marker_template_start
+                    //data: ../data/wfg_stim_mem_reg.json
+                    //template: wishbone/assign_from_registers.template
+                    //marker_template_code
+
+                    4'hC: begin
+                        wbs_dat_o[23:8] <= cfg_gain_ff;
+                        wbs_dat_o[7:0]  <= cfg_inc_ff;
+                    end
+                    4'h0:       wbs_dat_o[ 0: 0] <= ctrl_en_ff;
+                    4'h8:       wbs_dat_o[15: 0] <= end_val_ff;
+                    4'h4:       wbs_dat_o[15: 0] <= start_val_ff;
+
+                    //marker_template_end
+                    default:    wbs_dat_o <= 'X;
+                endcase
+            end
+        end
+    end
+
+    // Acknowledgement
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) wbs_ack_o <= 1'b0;
+        else wbs_ack_o <= wbs_stb_i && wbs_cyc_i & !wbs_ack_o;
+    end
+
+    //marker_template_start
+    //data: ../data/wfg_stim_mem_reg.json
+    //template: wishbone/assign_outputs.template
+    //marker_template_code
+
+    assign cfg_gain_q_o  = cfg_gain_ff;
+    assign cfg_inc_q_o   = cfg_inc_ff;
+    assign ctrl_en_q_o   = ctrl_en_ff;
+    assign end_val_q_o   = end_val_ff;
+    assign start_val_q_o = start_val_ff;
+
+    //marker_template_end
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_mem/sim/Makefile b/verilog/rtl/waveform-generator/design/wfg_stim_mem/sim/Makefile
new file mode 100644
index 0000000..e946d20
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_mem/sim/Makefile
@@ -0,0 +1,19 @@
+# source files
+SRC := $(wildcard ../rtl/*.sv)
+SRC += $(wildcard ../testbench/*.sv)
+
+# defaults
+SIM ?= icarus
+TOPLEVEL_LANG ?= verilog
+
+VERILOG_SOURCES += $(SRC)
+
+# TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file
+TOPLEVEL = wfg_stim_mem_tb
+
+# MODULE is the basename of the Python test file
+export PYTHONPATH := $(PYTHONPATH):../testbench/
+MODULE = test_wfg_stim_mem
+
+# include cocotb's make rules to take care of the simulator setup
+include $(shell cocotb-config --makefiles)/Makefile.sim
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_mem/testbench/test_wfg_stim_mem.py b/verilog/rtl/waveform-generator/design/wfg_stim_mem/testbench/test_wfg_stim_mem.py
new file mode 100644
index 0000000..a9a5d8c
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_mem/testbench/test_wfg_stim_mem.py
@@ -0,0 +1,83 @@
+# SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import statistics
+import cocotb
+from cocotb.clock import Clock
+from cocotb.triggers import Timer, RisingEdge, FallingEdge
+from cocotb.regression import TestFactory
+from cocotbext.wishbone.driver import WishboneMaster
+from cocotbext.wishbone.driver import WBOp
+
+DATA_CNT = 10
+
+short_per = Timer(100, units="ns")
+long_time = Timer(100, units="us")
+
+async def set_register(dut, wbs, peripheral_address, address, data):
+    if address > 0xF:
+        dut._log.error("Can not access peripheral registers outside 0xF")
+
+    real_address = (peripheral_address<<4) | (address & 0xF)
+    dut._log.info(f"Set register {real_address} : {data}")
+
+    wbRes = await wbs.send_cycle([WBOp(real_address, data)])
+    rvalues = [wb.datrd for wb in wbRes]
+    dut._log.info(f"Returned values : {rvalues}")
+
+async def configure_stim_mem(dut, wbs, en, start=0x0000, end=0x0000, inc=0x01, gain=0x0001):
+    await set_register(dut, wbs, 0x0, 0x4, start)
+    await set_register(dut, wbs, 0x0, 0x8, end)
+    await set_register(dut, wbs, 0x0, 0xC, gain<<8 | inc)
+    await set_register(dut, wbs, 0x0, 0x0, en) # Enable
+
+@cocotb.coroutine
+async def mem_test(dut, start=0x0000, end=0x0000, inc=0x01, gain=0x0001):
+    cocotb.start_soon(Clock(dut.io_wbs_clk, 10, units="ns").start())
+
+    dut._log.info("Initialize and reset model")
+
+    # Start reset
+    dut.io_wbs_rst.value = 1
+    dut.wfg_axis_tready.value = 1
+    
+    await Timer(100, units='ns')
+
+    # Stop reset
+    dut.io_wbs_rst.value = 0
+
+    # Wishbone Master
+    wbs = WishboneMaster(dut, "io_wbs", dut.io_wbs_clk,
+                              width=32,   # size of data bus
+                              timeout=10) # in clock cycle number
+
+    await short_per
+    
+    dut._log.info("Configure stim_mem")
+    await configure_stim_mem(dut, wbs, en=1, start=start, end=end, inc=inc, gain=gain)
+
+    cur_address = start
+
+    # Gather data
+    for i in range(DATA_CNT):
+        await FallingEdge(dut.wfg_axis_tvalid)
+        value = dut.wfg_axis_tdata.value
+        
+        dut._log.info(f"Test: {cur_address} == {value}")
+        
+        assert(cur_address*gain == value)
+        
+        cur_address += inc
+        
+        if (cur_address > end):
+            cur_address = start
+
+factory = TestFactory(mem_test)
+
+factory.add_option("start", [0x0000, 0x0010])
+factory.add_option("end", [0x0005, 0x001F])
+factory.add_option("inc", [0x01, 0x02])
+factory.add_option("gain", [0x01, 0x04])
+
+factory.generate_tests()
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_mem/testbench/wfg_stim_mem_tb.sv b/verilog/rtl/waveform-generator/design/wfg_stim_mem/testbench/wfg_stim_mem_tb.sv
new file mode 100644
index 0000000..31deb4a
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_mem/testbench/wfg_stim_mem_tb.sv
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`timescale 1ns / 1ps
+
+`ifdef VERILATOR  // make parameter readable from VPI
+`define VL_RD /*verilator public_flat_rd*/
+`else
+`define VL_RD
+`endif
+
+module wfg_stim_mem_tb #(
+    parameter int BUSW = 32
+) (
+    // Wishbone interface signals
+    input               io_wbs_clk,
+    input               io_wbs_rst,
+    input  [(BUSW-1):0] io_wbs_adr,
+    input  [(BUSW-1):0] io_wbs_datwr,
+    output [(BUSW-1):0] io_wbs_datrd,
+    input               io_wbs_we,
+    input               io_wbs_stb,
+    output              io_wbs_ack,
+    input               io_wbs_cyc,
+
+    // AXI-Stream interface
+    input         wfg_axis_tready,
+    output        wfg_axis_tvalid,
+    output [31:0] wfg_axis_tdata
+);
+
+    logic        csb1;
+    logic [9:0 ] addr1;
+    logic [31:0] dout1;
+
+    localparam MEM_SIZE = 2 ** 10;
+
+    logic [31:0] mem[MEM_SIZE];
+
+    wfg_stim_mem_top wfg_stim_mem_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(io_wbs_adr),
+        .wbs_ack_o(io_wbs_ack),
+        .wbs_dat_o(io_wbs_datrd),
+
+        .wfg_axis_tready_i(wfg_axis_tready),
+        .wfg_axis_tvalid_o(wfg_axis_tvalid),
+        .wfg_axis_tdata_o (wfg_axis_tdata),
+
+        .csb1 (csb1),
+        .addr1(addr1),
+        .dout1(dout1)
+    );
+
+    initial begin
+        $readmemh("memory.hex", mem);
+    end
+
+    always_ff @(negedge io_wbs_clk) begin
+        if (!csb1) dout1 <= mem[addr1];
+    end
+
+    // Dump waves
+`ifndef VERILATOR
+    initial begin
+        $dumpfile("wfg_stim_mem_tb.vcd");
+        $dumpvars(0, wfg_stim_mem_tb);
+    end
+`endif
+
+endmodule
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_sine/README.md b/verilog/rtl/waveform-generator/design/wfg_stim_sine/README.md
new file mode 100644
index 0000000..5ad0e00
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_sine/README.md
@@ -0,0 +1,26 @@
+# Documentation
+
+## Overview
+
+The Sine Wave Pattern Generator serves as a data generator that provides data for the AXI streaming interface. The sine wave generator, as the name implies, generates values in the form of sine waves. The values are generated from the angular phase, which is controlled by the `inc` (increment) register. Other parameters that influence the sine wave are gain, multiplier with a range of 0 - 1.999..., and offset with a range of -2 to 1.999....
+
+## RTL Implementation - CORDIC Algorithm
+
+CORDIC (COordinate Rotation DIgital Computer) is a hardware-efficient iterative method that uses rotations to compute a wide range of elementary functions. The RTL uses the CORDIC algorithm as a primitive method for generating sinusoidal values from phase. The behavior of the CORDIC algorithm is controlled by the following input registers:
+  - inc_val_q_i (16bit) - phase increment value
+  - gain_val_q_i (16bit) - gain value (0 - 1.999..)
+  - offset_val_q_i (18bit) - offset value (-2 - 1.999..)
+
+The output is represented by the following registers:
+  - sine_out (18bit) - sine value (-2 - 1.999...) calculated from the input parameters. 
+
+Some other inputs are:
+  - clk - system clock
+  - rst - reset
+  - ctrl_en_q_i - enable/disable data generation
+
+The output is a 2f16 signed value with a frequency of 100*MHz*.
+
+## Cocotb Testbench
+
+Cocotb is used for the testbench environment. It allows the user to access all RTL signals, control them and influence their behavior. In this case, the testbench accesses and modifies the phase increment value (`inc_val_q_i`), the gain factor (`gain_val_q_i`) and the offset (`offset_val_q_i`), as well as the system clock, reset and data generation enable. On the other hand, the testbench receives sine values from RTL via the `sine_out` output line. Just as RTL performs the calculation of the sine value using the CORDIC algorithm, the Testbench can import the *sin()* function from the *math* library. With the corresponding calculation, the sine value is output in hexadecimal form. The verification of the values calculated in the testbench and the values from the CORDIC algorithm is explained in detail in the following section.
\ No newline at end of file
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_sine/data/wfg_stim_sine_reg.csv b/verilog/rtl/waveform-generator/design/wfg_stim_sine/data/wfg_stim_sine_reg.csv
new file mode 100644
index 0000000..eb11531
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_sine/data/wfg_stim_sine_reg.csv
@@ -0,0 +1,9 @@
+Address,RegName,BitName,Access,HW,MSB,LSB,Reset,Description
+4'h0,CTRL,,,,,,,Control register for sine wave generation unit
+,,EN,rw,cfg,0,0,1'b0,Enables stimuli generation
+4'h4,INC,,,,,,,Increment register
+,,VAL,rw,cfg,15,0,16'h1000,Increment for angle per sample (f=fs/2**16/INC). Counter is implemented as wrap-around counter.
+4'h8,GAIN,,,,,,,Gain register
+,,VAL,rw,cfg,15,0,16'h4000,Sine wave gain in 2f14 unsigned representation. 
+4'hC,OFFSET,,,,,,,Offset register
+,,VAL,rw,cfg,17,0,18'h0000,Signed offset added after applying gain.
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_sine/data/wfg_stim_sine_reg.json b/verilog/rtl/waveform-generator/design/wfg_stim_sine/data/wfg_stim_sine_reg.json
new file mode 100644
index 0000000..586a2ac
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_sine/data/wfg_stim_sine_reg.json
@@ -0,0 +1,60 @@
+{
+    "registers": {
+        "CTRL": {
+            "address": "4'h0",
+            "description": "Control register for sine wave generation unit",
+            "entries": {
+                "EN": {
+                    "LSB": "0",
+                    "MSB": "0",
+                    "access": "rw",
+                    "description": "Enables stimuli generation",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                }
+            }
+        },
+        "GAIN": {
+            "address": "4'h8",
+            "description": "Gain register",
+            "entries": {
+                "VAL": {
+                    "LSB": "0",
+                    "MSB": "15",
+                    "access": "rw",
+                    "description": "Sine wave gain in 2f14 unsigned representation. ",
+                    "hardware": "cfg",
+                    "reset": "16'h4000"
+                }
+            }
+        },
+        "INC": {
+            "address": "4'h4",
+            "description": "Increment register",
+            "entries": {
+                "VAL": {
+                    "LSB": "0",
+                    "MSB": "15",
+                    "access": "rw",
+                    "description": "Increment for angle per sample (f=fs/2**16/INC). Counter is implemented as wrap-around counter.",
+                    "hardware": "cfg",
+                    "reset": "16'h1000"
+                }
+            }
+        },
+        "OFFSET": {
+            "address": "4'hC",
+            "description": "Offset register",
+            "entries": {
+                "VAL": {
+                    "LSB": "0",
+                    "MSB": "17",
+                    "access": "rw",
+                    "description": "Signed offset added after applying gain.",
+                    "hardware": "cfg",
+                    "reset": "18'h0000"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_sine/rtl/wfg_stim_sine.sv b/verilog/rtl/waveform-generator/design/wfg_stim_sine/rtl/wfg_stim_sine.sv
new file mode 100644
index 0000000..9458111
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_sine/rtl/wfg_stim_sine.sv
@@ -0,0 +1,198 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_stim_sine (
+    input  wire               clk,                // clock signal
+    input  wire               rst_n,              // reset signal
+    input  wire               wfg_axis_tready_i,  // ready signal - AXI
+    output wire               wfg_axis_tvalid_o,  // valid signal - AXI
+    output wire signed [31:0] wfg_axis_tdata_o,   // sine output  - AXI
+    input  wire               ctrl_en_q_i,        // enable/disable simulation
+    input  wire        [15:0] inc_val_q_i,        // angular increment
+    input  wire        [15:0] gain_val_q_i,       // sine gain/multiplier
+    input  wire signed [17:0] offset_val_q_i      // sine offset
+);
+    // 0.60725*2^16, expand the decimal by 2^16 to reduce the error
+    parameter bit [15:0] K = 16'h9b74;
+
+    // Because arithmetic shift is used, a signed type needs to be defined
+    logic signed [16:0] sin_17;
+    logic signed [17:0] sin_18;
+
+    // Used as a temporary variable
+    logic signed [16:0] x;
+    logic signed [16:0] y;
+    logic signed [16:0] z;
+
+    // quadrant
+    logic [1:0]  quadrant;
+
+    // Store the angle of each rotation
+    logic [15:0] rot[0:15];
+    logic [3:0] iteration;
+    logic signed [34:0] temp;
+
+    logic valid;
+    logic [15:0] phase_in;
+    logic [15:0] increment;
+    logic signed [17:0] overflow_chk;
+
+    typedef enum {
+        ST_IDLE,
+        ST_CALC,
+        ST_QUADRANT,
+        ST_GAIN,
+        ST_OFFSET,
+        ST_DONE
+    } wfg_stim_sine_states_t;
+
+    wfg_stim_sine_states_t cur_state;
+    wfg_stim_sine_states_t next_state;
+
+    always_ff @(posedge clk, negedge rst_n) begin
+        if (!rst_n) cur_state <= ST_IDLE;
+        else cur_state <= next_state;
+    end
+
+    always_comb begin
+        next_state = cur_state;
+        case (cur_state)
+            ST_IDLE: begin
+                if (ctrl_en_q_i == 1'b1) next_state = ST_CALC;
+            end
+            ST_CALC: begin
+                if (iteration == 15) next_state = ST_QUADRANT;
+            end
+            ST_QUADRANT:    next_state = ST_GAIN;
+            ST_GAIN:        next_state = ST_OFFSET;
+            ST_OFFSET:      next_state = ST_DONE;
+            ST_DONE: begin
+                if (wfg_axis_tready_i == 1'b1) next_state = ST_IDLE;
+            end
+            default: next_state = ST_IDLE; // TODO wfg_stim_sine_states_t'('x);
+        endcase
+    end
+
+    assign increment = inc_val_q_i + phase_in;
+
+    always_ff @(posedge clk, negedge rst_n) begin
+        if (!rst_n) begin
+            rot[0]       <= 16'h2000;  //45
+            rot[1]       <= 16'h12e4;  //26.5651
+            rot[2]       <= 16'h09fb;  //14.0362
+            rot[3]       <= 16'h0511;  //7.1250
+            rot[4]       <= 16'h028b;  //3.5763
+            rot[5]       <= 16'h0145;  //1.7899
+            rot[6]       <= 16'h00a3;  //0.8952
+            rot[7]       <= 16'h0051;  //0.4476
+            rot[8]       <= 16'h0028;  //0.2238
+            rot[9]       <= 16'h0014;  //0.1119
+            rot[10]      <= 16'h000a;  //0.0560
+            rot[11]      <= 16'h0005;  //0.0280
+            rot[12]      <= 16'h0003;  //0.0140
+            rot[13]      <= 16'h0001;  //0.0070
+            rot[14]      <= 16'h0001;  //0.0035
+            rot[15]      <= 16'h0000;  //0.0018
+
+            x            <= K;
+            y            <= '0;
+            z            <= '0;
+
+            iteration    <= '0;
+            phase_in     <= '0;
+
+            valid        <= '0;
+            sin_17       <= '0;
+
+            quadrant     <= '0;
+            sin_18       <= '0;
+            temp         <= '0;
+            overflow_chk <= '0;
+
+        end else begin
+            valid <= 0;
+
+            case (cur_state)
+                ST_IDLE: begin
+                    iteration <= 0;
+                    x <= K;
+                    y <= '0;
+
+                    // The first two digits of the input indicate the quadrant
+                    quadrant <= phase_in[15:14];
+                    // z is used to store the angle after transforming to the first quadrant
+                    z <= {3'b0, phase_in[13:0]};
+                end
+                ST_CALC: begin
+                    iteration <= iteration + 1;  // TODO check
+
+                    if (z[16] == 1'b1) begin
+                        x <= x + (y >>> (iteration));
+                        y <= y - (x >>> (iteration));
+                        z <= z + rot[iteration];
+                    end else begin
+                        x <= x - (y >>> (iteration));
+                        y <= y + (x >>> (iteration));
+                        z <= z - rot[iteration];
+                    end
+
+
+                end
+                ST_QUADRANT: begin
+                    case (quadrant)
+                        2'b00: begin  // The first quadrant
+                            sin_17 <= y;  // sin
+                        end
+                        2'b01: begin  // The second quadrant
+                            sin_17 <= x;  // cos
+                        end
+                        2'b10: begin  // The third quadrant
+                            sin_17 <= ~(y) + 1'b1;  // -sin
+                        end
+                        2'b11: begin  // The fourth quadrant
+                            sin_17 <= ~(x) + 1'b1;  // -cos
+                        end
+                        default: begin
+                            sin_17 <= 'x;
+                        end
+                    endcase
+                end
+                ST_GAIN: begin
+                    // Multiplying by gain value - signed multiplication
+                    if (gain_val_q_i[15:0] > 16'h7FFF) begin
+                        temp[34:0] <= {{16{sin_17[16]}}, sin_17[15:0]} * {{16{1'b0}}, 16'h7FFF};
+                    end else begin
+                        temp[34:0] <=   {{16{sin_17[16]}}, sin_17[15:0]} *
+                                        {{16{1'b0}}, gain_val_q_i[15:0]};
+                    end
+                end
+                ST_OFFSET: begin
+                    // Adding the offset value
+                    overflow_chk[17:0] <= temp[31:14] + offset_val_q_i[17:0];
+                end
+                ST_DONE: begin
+                    valid <= 1;
+                    // Underflow check
+                    if (temp[31] && offset_val_q_i[17] && !overflow_chk[17]) begin
+                        sin_18 <= 18'b100000000000000000;
+                        // Overflow check
+                    end else if (!temp[31] && !offset_val_q_i[17] && overflow_chk[17]) begin
+                        sin_18 <= 18'b011111111111111111;
+                    end else begin
+                        sin_18 <= overflow_chk;
+                    end
+
+                    if (wfg_axis_tready_i == 1'b1) phase_in <= increment;
+                end
+                default: valid <= 'x;
+            endcase
+        end
+    end
+
+    // I/O assignment
+    assign wfg_axis_tdata_o  = sin_18;
+    assign wfg_axis_tvalid_o = valid;
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_sine/rtl/wfg_stim_sine_top.sv b/verilog/rtl/waveform-generator/design/wfg_stim_sine/rtl/wfg_stim_sine_top.sv
new file mode 100644
index 0000000..e072998
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_sine/rtl/wfg_stim_sine_top.sv
@@ -0,0 +1,76 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_stim_sine_top #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                 wb_clk_i,
+    input                 wb_rst_i,
+    input                 wbs_stb_i,
+    input                 wbs_cyc_i,
+    input                 wbs_we_i,
+    input  [(BUSW/8-1):0] wbs_sel_i,
+    input  [  (BUSW-1):0] wbs_dat_i,
+    input  [  (BUSW-1):0] wbs_adr_i,
+    output                wbs_ack_o,
+    output [  (BUSW-1):0] wbs_dat_o,
+
+    // AXI-Stream interface
+    input                wfg_axis_tready_i,
+    output               wfg_axis_tvalid_o,
+    output signed [31:0] wfg_axis_tdata_o
+);
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_stim_sine_reg.json
+    //template: wishbone/instantiate_top.template
+    //marker_template_code
+
+    logic         ctrl_en_q;               // CTRL.EN register output
+    logic [15: 0] gain_val_q;              // GAIN.VAL register output
+    logic [15: 0] inc_val_q;               // INC.VAL register output
+    logic [17: 0] offset_val_q;            // OFFSET.VAL register output
+
+    //marker_template_end
+
+    wfg_stim_sine_wishbone_reg wfg_stim_sine_wishbone_reg (
+        .wb_clk_i (wb_clk_i),
+        .wb_rst_i (wb_rst_i),
+        .wbs_stb_i(wbs_stb_i),
+        .wbs_cyc_i(wbs_cyc_i),
+        .wbs_we_i (wbs_we_i),
+        .wbs_sel_i(wbs_sel_i),
+        .wbs_dat_i(wbs_dat_i),
+        .wbs_adr_i(wbs_adr_i),
+        .wbs_ack_o(wbs_ack_o),
+        .wbs_dat_o(wbs_dat_o),
+
+        //marker_template_start
+        //data: ../data/wfg_stim_sine_reg.json
+        //template: wishbone/assign_to_module.template
+        //marker_template_code
+
+        .ctrl_en_q_o   (ctrl_en_q),    // CTRL.EN register output
+        .gain_val_q_o  (gain_val_q),   // GAIN.VAL register output
+        .inc_val_q_o   (inc_val_q),    // INC.VAL register output
+        .offset_val_q_o(offset_val_q)  // OFFSET.VAL register output
+
+        //marker_template_end
+    );
+
+    wfg_stim_sine wfg_stim_sine (
+        .clk              (wb_clk_i),           // clock signal
+        .rst_n            (!wb_rst_i),          // reset signal
+        .wfg_axis_tready_i(wfg_axis_tready_i),  // ready signal - AXI
+        .wfg_axis_tvalid_o(wfg_axis_tvalid_o),  // valid signal - AXI
+        .wfg_axis_tdata_o (wfg_axis_tdata_o),   // sine output  - AXI
+        .ctrl_en_q_i      (ctrl_en_q),          // enable/disable simulation
+        .inc_val_q_i      (inc_val_q),          // angular increment
+        .gain_val_q_i     (gain_val_q),         // sine gain/multiplier
+        .offset_val_q_i   (offset_val_q)        // sine offset
+    );
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_sine/rtl/wfg_stim_sine_wishbone_reg.sv b/verilog/rtl/waveform-generator/design/wfg_stim_sine/rtl/wfg_stim_sine_wishbone_reg.sv
new file mode 100644
index 0000000..6666a33
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_sine/rtl/wfg_stim_sine_wishbone_reg.sv
@@ -0,0 +1,122 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_stim_sine_wishbone_reg #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                       wb_clk_i,
+    input                       wb_rst_i,
+    input                       wbs_stb_i,
+    input                       wbs_cyc_i,
+    input                       wbs_we_i,
+    input        [(BUSW/8-1):0] wbs_sel_i,
+    input        [  (BUSW-1):0] wbs_dat_i,
+    input        [  (BUSW-1):0] wbs_adr_i,
+    output logic                wbs_ack_o,
+    output logic [  (BUSW-1):0] wbs_dat_o,
+
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_stim_sine_reg.json
+    //template: wishbone/register_interface.template
+    //marker_template_code
+
+    output logic        ctrl_en_q_o,    // CTRL.EN register output
+    output logic [15:0] gain_val_q_o,   // GAIN.VAL register output
+    output logic [15:0] inc_val_q_o,    // INC.VAL register output
+    output logic [17:0] offset_val_q_o  // OFFSET.VAL register output
+
+    //marker_template_end
+);
+
+    //marker_template_start
+    //data: ../data/wfg_stim_sine_reg.json
+    //template: wishbone/instantiate_registers.template
+    //marker_template_code
+
+    logic         ctrl_en_ff;              // CTRL.EN FF
+    logic [15: 0] gain_val_ff;             // GAIN.VAL FF
+    logic [15: 0] inc_val_ff;              // INC.VAL FF
+    logic [17: 0] offset_val_ff;           // OFFSET.VAL FF
+
+    //marker_template_end
+
+    // Wishbone write to slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            //marker_template_start
+            //data: ../data/wfg_stim_sine_reg.json
+            //template: wishbone/reset_registers.template
+            //marker_template_code
+
+            ctrl_en_ff    <= 1'b0;
+            gain_val_ff   <= 16'h4000;
+            inc_val_ff    <= 16'h1000;
+            offset_val_ff <= 18'h0000;
+
+            //marker_template_end
+        end else if (wbs_stb_i && wbs_we_i && wbs_cyc_i) begin
+            case (wbs_adr_i)
+                //marker_template_start
+                //data: ../data/wfg_stim_sine_reg.json
+                //template: wishbone/assign_to_registers.template
+                //marker_template_code
+
+                4'h0:       ctrl_en_ff               <= wbs_dat_i[ 0: 0];
+                4'h8:       gain_val_ff              <= wbs_dat_i[15: 0];
+                4'h4:       inc_val_ff               <= wbs_dat_i[15: 0];
+                4'hC:       offset_val_ff            <= wbs_dat_i[17: 0];
+
+                //marker_template_end
+                default: begin
+                end
+            endcase
+        end
+    end
+
+    // Wishbone read from slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            wbs_dat_o <= '0;
+        end else begin
+            if (wbs_stb_i && !wbs_we_i && wbs_cyc_i) begin
+                wbs_dat_o <= '0;  // default value
+                case (wbs_adr_i)
+                    //marker_template_start
+                    //data: ../data/wfg_stim_sine_reg.json
+                    //template: wishbone/assign_from_registers.template
+                    //marker_template_code
+
+                    4'h0:       wbs_dat_o[ 0: 0] <= ctrl_en_ff;
+                    4'h8:       wbs_dat_o[15: 0] <= gain_val_ff;
+                    4'h4:       wbs_dat_o[15: 0] <= inc_val_ff;
+                    4'hC:       wbs_dat_o[17: 0] <= offset_val_ff;
+
+                    //marker_template_end
+                    default:    wbs_dat_o <= 'X;
+                endcase
+            end
+        end
+    end
+
+    // Acknowledgement
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) wbs_ack_o <= 1'b0;
+        else wbs_ack_o <= wbs_stb_i && wbs_cyc_i & !wbs_ack_o;
+    end
+
+    //marker_template_start
+    //data: ../data/wfg_stim_sine_reg.json
+    //template: wishbone/assign_outputs.template
+    //marker_template_code
+
+    assign ctrl_en_q_o    = ctrl_en_ff;
+    assign gain_val_q_o   = gain_val_ff;
+    assign inc_val_q_o    = inc_val_ff;
+    assign offset_val_q_o = offset_val_ff;
+
+    //marker_template_end
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_sine/sim/Makefile b/verilog/rtl/waveform-generator/design/wfg_stim_sine/sim/Makefile
new file mode 100644
index 0000000..8ad0eba
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_sine/sim/Makefile
@@ -0,0 +1,19 @@
+# source files
+SRC := $(wildcard ../rtl/*.sv)
+SRC += $(wildcard ../testbench/*.sv)
+
+# defaults
+SIM ?= icarus
+TOPLEVEL_LANG ?= verilog
+
+VERILOG_SOURCES += $(SRC)
+
+# TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file
+TOPLEVEL = wfg_stim_sine_tb
+
+# MODULE is the basename of the Python test file
+export PYTHONPATH := $(PYTHONPATH):../testbench/
+MODULE = test_wfg_stim_sine
+
+# include cocotb's make rules to take care of the simulator setup
+include $(shell cocotb-config --makefiles)/Makefile.sim
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_sine/testbench/test_wfg_stim_sine.py b/verilog/rtl/waveform-generator/design/wfg_stim_sine/testbench/test_wfg_stim_sine.py
new file mode 100644
index 0000000..91615bc
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_sine/testbench/test_wfg_stim_sine.py
@@ -0,0 +1,110 @@
+# SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import statistics
+import cocotb
+from cocotb.clock import Clock
+from cocotb.triggers import Timer, RisingEdge, FallingEdge
+from cocotb.regression import TestFactory
+from cocotbext.wishbone.driver import WishboneMaster
+from cocotbext.wishbone.driver import WBOp
+
+DATA_CNT = 10
+
+short_per = Timer(100, units="ns")
+long_time = Timer(100, units="us")
+
+async def set_register(dut, wbs, peripheral_address, address, data):
+    if address > 0xF:
+        dut._log.error("Can not access peripheral registers outside 0xF")
+
+    real_address = (peripheral_address<<4) | (address & 0xF)
+    dut._log.info(f"Set register {real_address} : {data}")
+
+    wbRes = await wbs.send_cycle([WBOp(real_address, data)])
+    rvalues = [wb.datrd for wb in wbRes]
+    dut._log.info(f"Returned values : {rvalues}")
+
+async def configure_stim_sine(dut, wbs, en, inc=0x1000, gain=0x4000, offset=0):
+    await set_register(dut, wbs, 0x0, 0x4, inc)
+    await set_register(dut, wbs, 0x0, 0x8, gain)
+    await set_register(dut, wbs, 0x0, 0xC, offset)
+    await set_register(dut, wbs, 0x0, 0x0, en) # Enable
+
+@cocotb.coroutine
+async def sine_test(dut, sine_inc=0x1000, sine_gain=0x4000, sine_offset=0):
+    cocotb.start_soon(Clock(dut.io_wbs_clk, 10, units="ns").start())
+
+    dut._log.info("Initialize and reset model")
+
+    # Start reset
+    dut.io_wbs_rst.value = 1
+    dut.wfg_axis_tready.value = 1
+    
+    await Timer(100, units='ns')
+
+    # Stop reset
+    dut.io_wbs_rst.value = 0
+
+    # Wishbone Master
+    wbs = WishboneMaster(dut, "io_wbs", dut.io_wbs_clk,
+                              width=32,   # size of data bus
+                              timeout=10) # in clock cycle number
+
+    await short_per
+    
+    dut._log.info("Configure stim_sine")
+    await configure_stim_sine(dut, wbs, en=1, inc=sine_inc, gain=sine_gain, offset=sine_offset)
+
+    num_values = int((2**16) / sine_inc + 1)
+    y_data = []
+
+    # Gather data
+    for i in range(num_values):
+        await FallingEdge(dut.wfg_axis_tvalid)
+        value = int(dut.wfg_axis_tdata.value)
+        
+        # Sign extend
+        if value & (1<<17):
+            value |= ((1<<14)-1)<<18
+            
+        value = value.to_bytes(4, 'big')
+        value = int.from_bytes(value, 'big', signed=True)
+        y_data.append(value)
+
+    y_data_float = []
+    
+    y_error = []
+    y_squared_error = []
+    y_absolute_error = []
+
+    # Compare results
+    for (cnt, value) in enumerate(y_data):
+        input_val = cnt * sine_inc
+        while input_val >= (2**16):
+            input_val -= (2**16)
+    
+        angle_rad = float(input_val) / (2**16) * 2 * math.pi
+        calculated_value = math.sin(angle_rad) * (sine_gain / 2**14) + (sine_offset/2**16)
+        output_as_float = float(value) / (2**16)
+        y_data_float.append(output_as_float)
+        y_error.append(output_as_float - calculated_value)
+        y_squared_error.append((output_as_float - calculated_value)**2)
+        y_absolute_error.append(abs(output_as_float - calculated_value))
+    
+    y_mean_squared_error = statistics.mean(y_squared_error)
+    dut._log.info("y_mean_squared_error: {}".format(y_mean_squared_error))
+    
+    y_mean_absolute_error = statistics.mean(y_absolute_error)
+    dut._log.info("y_mean_absolute_error: {}".format(y_mean_absolute_error))
+
+    assert(y_mean_absolute_error < 0.001)
+
+factory = TestFactory(sine_test)
+
+factory.add_option("sine_inc", [0x500, 0x1000])
+factory.add_option("sine_gain", [0x4000, 0x2000, 0x6000])
+factory.add_option("sine_offset", [0x0000, 0x8000])
+
+factory.generate_tests()
diff --git a/verilog/rtl/waveform-generator/design/wfg_stim_sine/testbench/wfg_stim_sine_tb.sv b/verilog/rtl/waveform-generator/design/wfg_stim_sine/testbench/wfg_stim_sine_tb.sv
new file mode 100644
index 0000000..89c5ef3
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_stim_sine/testbench/wfg_stim_sine_tb.sv
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`timescale 1ns / 1ps
+
+`ifdef VERILATOR  // make parameter readable from VPI
+`define VL_RD /*verilator public_flat_rd*/
+`else
+`define VL_RD
+`endif
+
+module wfg_stim_sine_tb #(
+    parameter int BUSW = 32
+) (
+    // Wishbone interface signals
+    input               io_wbs_clk,
+    input               io_wbs_rst,
+    input  [(BUSW-1):0] io_wbs_adr,
+    input  [(BUSW-1):0] io_wbs_datwr,
+    output [(BUSW-1):0] io_wbs_datrd,
+    input               io_wbs_we,
+    input               io_wbs_stb,
+    output              io_wbs_ack,
+    input               io_wbs_cyc,
+
+    // AXI-Stream interface
+    input                wfg_axis_tready,
+    output               wfg_axis_tvalid,
+    output signed [17:0] wfg_axis_tdata
+);
+
+    wfg_stim_sine_top wfg_stim_sine_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(io_wbs_adr),
+        .wbs_ack_o(io_wbs_ack),
+        .wbs_dat_o(io_wbs_datrd),
+
+        .wfg_axis_tready_i(wfg_axis_tready),
+        .wfg_axis_tvalid_o(wfg_axis_tvalid),
+        .wfg_axis_tdata_o (wfg_axis_tdata)
+    );
+
+    // Dump waves
+`ifndef VERILATOR
+    initial begin
+        $dumpfile("wfg_stim_sine_tb.vcd");
+        $dumpvars(0, wfg_stim_sine_tb);
+    end
+`endif
+
+endmodule
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/README.md b/verilog/rtl/waveform-generator/design/wfg_subcore/README.md
new file mode 100644
index 0000000..b3c1af1
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/README.md
@@ -0,0 +1,13 @@
+<img align="right" src="https://github.com/semify-eda/wfg/blob/main/doc/semify.png" width="100" height="100" >
+
+*Copyright © 2021* [Semify EDA](
+https://github.com/semify-eda)
+
+## wfg_subcore documentation
+#### Overview
+This module is responsible to synchronize all implemented modules, ensuring data transfers with a set frequency.
+According to the configuration parameter wfg_pat_subcycle, clock cycles are counted and a subcycle pulse is generated. This subcycle enables finer division of the synchronization pulse and is used for synchronizations with phase shifts. The configuration parameter wfg_pat_sync specifies the number of subcycle pulses during a sync period. The resulting sync frequency 
+
+![Formula](formula.png)
+
+can be determined using these two parameters. The outputs of the module are subcycle and sync pulses, as well as three optional status signals (active, start pulse, subcyle counter).
\ No newline at end of file
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/data/wfg_subcore_reg.csv b/verilog/rtl/waveform-generator/design/wfg_subcore/data/wfg_subcore_reg.csv
new file mode 100644
index 0000000..237ec1d
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/data/wfg_subcore_reg.csv
@@ -0,0 +1,8 @@
+Address,RegName,BitName,Access,HW,MSB,LSB,Reset,Description
+4'h0,CTRL,,,,,,,Core control register
+,,EN,rw,cfg,0,0,1'b0,Core enable
+4'h4,CFG,,,,,,,Core configuration register
+,,SYNC,rw,cfg,7,0,0,"Sync pulse clock divider.
+Count clk until threshold is reached"
+,,SUBCYCLE,rw,cfg,23,8,0,"Subcycle pulse clock divider.
+Count clk until threshold is reached"
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/data/wfg_subcore_reg.json b/verilog/rtl/waveform-generator/design/wfg_subcore/data/wfg_subcore_reg.json
new file mode 100644
index 0000000..d84ee99
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/data/wfg_subcore_reg.json
@@ -0,0 +1,40 @@
+{
+    "registers": {
+        "CFG": {
+            "address": "4'h4",
+            "description": "Core configuration register",
+            "entries": {
+                "SUBCYCLE": {
+                    "LSB": "8",
+                    "MSB": "23",
+                    "access": "rw",
+                    "description": "Subcycle pulse clock divider.\nCount clk until threshold is reached",
+                    "hardware": "cfg",
+                    "reset": "0"
+                },
+                "SYNC": {
+                    "LSB": "0",
+                    "MSB": "7",
+                    "access": "rw",
+                    "description": "Sync pulse clock divider.\nCount clk until threshold is reached",
+                    "hardware": "cfg",
+                    "reset": "0"
+                }
+            }
+        },
+        "CTRL": {
+            "address": "4'h0",
+            "description": "Core control register",
+            "entries": {
+                "EN": {
+                    "LSB": "0",
+                    "MSB": "0",
+                    "access": "rw",
+                    "description": "Core enable",
+                    "hardware": "cfg",
+                    "reset": "1'b0"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/formula.png b/verilog/rtl/waveform-generator/design/wfg_subcore/formula.png
new file mode 100644
index 0000000..0e6fda0
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/formula.png
Binary files differ
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/rtl/wfg_subcore.sv b/verilog/rtl/waveform-generator/design/wfg_subcore/rtl/wfg_subcore.sv
new file mode 100644
index 0000000..845a8fa
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/rtl/wfg_subcore.sv
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_subcore (
+    input wire clk,    // I; System clock
+    input wire rst_n,  // I; Active low reset
+    input wire en_i,   // I; Enable signal
+
+    // Config
+    input wire [ 7:0] wfg_sync_count_i,     // I; Sync counter threshold
+    input wire [15:0] wfg_subcycle_count_i, // I; Subcycle counter threshold
+
+    // Signals
+    output wire       wfg_subcore_sync_o,          // O; Sync signal
+    output wire       wfg_subcore_subcycle_o,      // O; Subcycle signal
+    output wire       wfg_subcore_start_o,         // O; Indicate start
+    output wire [7:0] wfg_subcore_subcycle_cnt_o,  // O; Subcycle pulse counter
+    output wire       active_o                     // O; Active indication signal
+);
+
+    // -------------------------------------------------------------------------
+    // Definition
+    // -------------------------------------------------------------------------
+
+    logic [15:0]   subcycle_count;   // L; counting variable
+    logic [ 7:0]   sync_count;       // L; counting variable
+    logic        temp_subcycle;    // L; Internal subcycle
+    logic        temp_sync;        // L; Internal subcycle
+    logic        subcycle_dly;        // L; Rising edge det subcycle
+    logic        sync_dly;            // L; Rising edge det sync
+    logic        en_i_dly;            // L; Rising edge det enable
+    logic [ 7:0]   subcycle_pls_cnt;    // L; Counts Subcycles
+
+    // -------------------------------------------------------------------------
+    // Implementation
+    // -------------------------------------------------------------------------
+
+    always @(posedge clk, negedge rst_n) begin
+
+        if (~rst_n) begin
+            subcycle_count <= 16'd0;
+            sync_count    <=  8'd0;
+            temp_subcycle <=  1'b0;
+            temp_sync    <=  1'b0;
+            subcycle_pls_cnt  <=  8'd0;
+        end else begin
+
+            if (en_i) begin
+
+                if (subcycle_count > 0) begin
+                    temp_subcycle  <= temp_subcycle;
+                    subcycle_count <= subcycle_count - 1;
+                end else begin
+                    temp_subcycle  <= ~temp_subcycle;
+                    subcycle_count <= (wfg_subcycle_count_i);
+
+                    if (sync_count > 0) begin
+                        temp_sync  <= temp_sync;
+                        sync_count <= sync_count - 1;
+                    end else begin
+                        temp_sync  <= ~temp_sync;
+                        sync_count <= (wfg_sync_count_i);
+                    end
+
+                end
+
+                if (wfg_subcore_subcycle_o) begin
+                    subcycle_pls_cnt <= subcycle_pls_cnt + 1;
+                end
+
+            end else begin
+                subcycle_count   <= 8'd0;
+                sync_count       <= 8'd0;
+                temp_subcycle    <= 1'b0;
+                temp_sync        <= 1'b0;
+                subcycle_pls_cnt <= 8'd0;
+            end
+
+            subcycle_dly <= temp_subcycle;
+            sync_dly     <= temp_sync;
+            en_i_dly     <= en_i;
+
+            if (wfg_subcore_sync_o) begin
+                subcycle_pls_cnt <= 8'd0;
+            end
+
+        end
+
+    end
+
+    assign wfg_subcore_subcycle_o     = temp_subcycle & ~subcycle_dly;
+    assign wfg_subcore_sync_o         = temp_sync & ~sync_dly;
+    assign active_o                   = en_i;
+    assign wfg_subcore_start_o        = en_i & ~en_i_dly;
+    assign wfg_subcore_subcycle_cnt_o = subcycle_pls_cnt;
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/rtl/wfg_subcore_top.sv b/verilog/rtl/waveform-generator/design/wfg_subcore/rtl/wfg_subcore_top.sv
new file mode 100644
index 0000000..e591667
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/rtl/wfg_subcore_top.sv
@@ -0,0 +1,83 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_subcore_top #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                 wb_clk_i,
+    input                 wb_rst_i,
+    input                 wbs_stb_i,
+    input                 wbs_cyc_i,
+    input                 wbs_we_i,
+    input  [(BUSW/8-1):0] wbs_sel_i,
+    input  [  (BUSW-1):0] wbs_dat_i,
+    input  [  (BUSW-1):0] wbs_adr_i,
+    output                wbs_ack_o,
+    output [  (BUSW-1):0] wbs_dat_o,
+
+    // subcore synchronisation interface
+    output wire       wfg_subcore_sync_o,          // O; Sync signal
+    output wire       wfg_subcore_subcycle_o,      // O; Subcycle signal
+    output wire       wfg_subcore_start_o,         // O; Indicate start
+    output wire [7:0] wfg_subcore_subcycle_cnt_o,  // O; Subcycle pulse counter
+    output wire       active_o                     // O; Active indication signal
+);
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_subcore_reg.json
+    //template: wishbone/instantiate_top.template
+    //marker_template_code
+
+    logic [23: 8] cfg_subcycle_q;          // CFG.SUBCYCLE register output
+    logic [ 7: 0] cfg_sync_q;              // CFG.SYNC register output
+    logic         ctrl_en_q;               // CTRL.EN register output
+
+    //marker_template_end
+
+    wfg_subcore_wishbone_reg wfg_subcore_wishbone_reg (
+        .wb_clk_i (wb_clk_i),
+        .wb_rst_i (wb_rst_i),
+        .wbs_stb_i(wbs_stb_i),
+        .wbs_cyc_i(wbs_cyc_i),
+        .wbs_we_i (wbs_we_i),
+        .wbs_sel_i(wbs_sel_i),
+        .wbs_dat_i(wbs_dat_i),
+        .wbs_adr_i(wbs_adr_i),
+        .wbs_ack_o(wbs_ack_o),
+        .wbs_dat_o(wbs_dat_o),
+
+        //marker_template_start
+        //data: ../data/wfg_subcore_reg.json
+        //template: wishbone/assign_to_module.template
+        //marker_template_code
+
+        .cfg_subcycle_q_o(cfg_subcycle_q),  // CFG.SUBCYCLE register output
+        .cfg_sync_q_o    (cfg_sync_q),      // CFG.SYNC register output
+        .ctrl_en_q_o     (ctrl_en_q)        // CTRL.EN register output
+
+        //marker_template_end
+    );
+
+    wfg_subcore wfg_subcore (
+        .clk  (wb_clk_i),  // clock signal
+        .rst_n(!wb_rst_i), // reset signal
+
+        // Control
+        .en_i(ctrl_en_q),  // I; Enable signal
+
+        // Configuration
+        .wfg_sync_count_i    (cfg_sync_q),     // I; Sync counter threshold
+        .wfg_subcycle_count_i(cfg_subcycle_q), // I: Subcycle counter threshold
+
+        // Output
+        .wfg_subcore_sync_o        (wfg_subcore_sync_o),          // O; Sync signal
+        .wfg_subcore_subcycle_o    (wfg_subcore_subcycle_o),      // O; Subcycle signal
+        .wfg_subcore_start_o       (wfg_subcore_start_o),         // O; Indicate start
+        .wfg_subcore_subcycle_cnt_o(wfg_subcore_subcycle_cnt_o),  // O; Subcycle pulse counter
+        .active_o                  (active_o)                     // O; Active indication signal
+    );
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/rtl/wfg_subcore_wishbone_reg.sv b/verilog/rtl/waveform-generator/design/wfg_subcore/rtl/wfg_subcore_wishbone_reg.sv
new file mode 100644
index 0000000..d20697a
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/rtl/wfg_subcore_wishbone_reg.sv
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+module wfg_subcore_wishbone_reg #(
+    parameter int BUSW = 32
+) (
+    // Wishbone Slave ports
+    input                       wb_clk_i,
+    input                       wb_rst_i,
+    input                       wbs_stb_i,
+    input                       wbs_cyc_i,
+    input                       wbs_we_i,
+    input        [(BUSW/8-1):0] wbs_sel_i,
+    input        [  (BUSW-1):0] wbs_dat_i,
+    input        [  (BUSW-1):0] wbs_adr_i,
+    output logic                wbs_ack_o,
+    output logic [  (BUSW-1):0] wbs_dat_o,
+
+    // Registers
+    //marker_template_start
+    //data: ../data/wfg_subcore_reg.json
+    //template: wishbone/register_interface.template
+    //marker_template_code
+
+    output logic [23:8] cfg_subcycle_q_o,  // CFG.SUBCYCLE register output
+    output logic [ 7:0] cfg_sync_q_o,      // CFG.SYNC register output
+    output logic        ctrl_en_q_o        // CTRL.EN register output
+
+    //marker_template_end
+);
+
+    //marker_template_start
+    //data: ../data/wfg_subcore_reg.json
+    //template: wishbone/instantiate_registers.template
+    //marker_template_code
+
+    logic [23: 8] cfg_subcycle_ff;         // CFG.SUBCYCLE FF
+    logic [ 7: 0] cfg_sync_ff;             // CFG.SYNC FF
+    logic         ctrl_en_ff;              // CTRL.EN FF
+
+    //marker_template_end
+
+    // Wishbone write to slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            //marker_template_start
+            //data: ../data/wfg_subcore_reg.json
+            //template: wishbone/reset_registers.template
+            //marker_template_code
+
+            cfg_subcycle_ff <= 0;
+            cfg_sync_ff     <= 0;
+            ctrl_en_ff      <= 1'b0;
+
+            //marker_template_end
+        end else if (wbs_stb_i && wbs_we_i && wbs_cyc_i) begin
+            case (wbs_adr_i)
+                //marker_template_start
+                //data: ../data/wfg_subcore_reg.json
+                //template: wishbone/assign_to_registers.template
+                //marker_template_code
+
+                4'h4: begin
+                    cfg_subcycle_ff <= wbs_dat_i[23:8];
+                    cfg_sync_ff     <= wbs_dat_i[7:0];
+                end
+                4'h0:       ctrl_en_ff               <= wbs_dat_i[ 0: 0];
+
+                //marker_template_end
+                default: begin
+                end
+            endcase
+        end
+    end
+
+    // Wishbone read from slave
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) begin
+            wbs_dat_o <= '0;
+        end else begin
+            if (wbs_stb_i && !wbs_we_i && wbs_cyc_i) begin
+                wbs_dat_o <= '0;  // default value
+                case (wbs_adr_i)
+                    //marker_template_start
+                    //data: ../data/wfg_subcore_reg.json
+                    //template: wishbone/assign_from_registers.template
+                    //marker_template_code
+
+                    4'h4: begin
+                        wbs_dat_o[23:8] <= cfg_subcycle_ff;
+                        wbs_dat_o[7:0]  <= cfg_sync_ff;
+                    end
+                    4'h0:       wbs_dat_o[ 0: 0] <= ctrl_en_ff;
+
+                    //marker_template_end
+                    default:    wbs_dat_o <= 'X;
+                endcase
+            end
+        end
+    end
+
+    // Acknowledgement
+    always_ff @(posedge wb_clk_i) begin
+        if (wb_rst_i) wbs_ack_o <= 1'b0;
+        else wbs_ack_o <= wbs_stb_i && wbs_cyc_i & !wbs_ack_o;
+    end
+
+    //marker_template_start
+    //data: ../data/wfg_subcore_reg.json
+    //template: wishbone/assign_outputs.template
+    //marker_template_code
+
+    assign cfg_subcycle_q_o = cfg_subcycle_ff;
+    assign cfg_sync_q_o     = cfg_sync_ff;
+    assign ctrl_en_q_o      = ctrl_en_ff;
+
+    //marker_template_end
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/sim/Makefile b/verilog/rtl/waveform-generator/design/wfg_subcore/sim/Makefile
new file mode 100644
index 0000000..2ef8e95
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/sim/Makefile
@@ -0,0 +1,19 @@
+# source files
+SRC := $(wildcard ../rtl/*.sv)
+SRC += $(wildcard ../testbench/*.sv)
+
+# defaults
+SIM ?= icarus
+TOPLEVEL_LANG ?= verilog
+
+VERILOG_SOURCES += $(SRC)
+
+# TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file
+TOPLEVEL = wfg_subcore_tb
+
+# MODULE is the basename of the Python test file
+export PYTHONPATH := $(PYTHONPATH):../testbench/
+MODULE = test_wfg_subcore
+
+# include cocotb's make rules to take care of the simulator setup
+include $(shell cocotb-config --makefiles)/Makefile.sim
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/testbench/test_wfg_core.py b/verilog/rtl/waveform-generator/design/wfg_subcore/testbench/test_wfg_core.py
new file mode 100644
index 0000000..0fd7952
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/testbench/test_wfg_core.py
@@ -0,0 +1,94 @@
+# SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+# SPDX-License-Identifier: Apache-2.0
+
+import random
+import cocotb
+from cocotb.clock import Clock
+from cocotb.regression import TestFactory
+from cocotb.triggers import Timer, RisingEdge, FallingEdge, ClockCycles
+from cocotbext.wishbone.driver import WishboneMaster
+from cocotbext.wishbone.driver import WBOp
+
+CLK_PER_SYNC = 300
+SYSCLK = 100000000
+DATA_CNT = 10
+
+short_per = Timer(100, units="ns")
+long_time = Timer(100, units="us")
+
+async def set_register(dut, wbs, address, data):
+    dut._log.info(f"Set register {address} : {data}")
+
+    wbRes = await wbs.send_cycle([WBOp(address, data)])
+    
+    rvalues = [wb.datrd for wb in wbRes]
+    dut._log.info(f"Returned values : {rvalues}")
+
+async def configure(dut, wbs, en, sync_count, subcycle_count):
+    await set_register(dut, wbs, 0x4, (sync_count << 0) | (subcycle_count << 8))
+    await set_register(dut, wbs, 0x0, en) # Enable core
+
+@cocotb.coroutine
+async def core_test(dut, en, sync_count, subcycle_count):
+    dut._log.info(f"Configuration: sync_count={sync_count}, subcycle_count={subcycle_count}")
+
+    cocotb.start_soon(Clock(dut.io_wbs_clk, 1/SYSCLK*1e9, units="ns").start())
+
+    dut._log.info("Initialize and reset model")
+
+    # Start reset
+    dut.io_wbs_rst.value = 1    
+    await Timer(100, units='ns')
+
+    # Stop reset
+    dut.io_wbs_rst.value = 0
+
+    # Wishbone Master
+    wbs = WishboneMaster(dut, "io_wbs", dut.io_wbs_clk,
+                              width=32,   # size of data bus
+                              timeout=10) # in clock cycle number
+
+    # Setup core
+    await configure(dut, wbs, en, sync_count, subcycle_count)
+
+    #await short_per
+    #await short_per
+    
+    sync_pulse_count = 0
+    clk_count = 0
+    
+    await FallingEdge(dut.wfg_core_sync_o)
+
+    for i in range ((sync_count + 1) * (subcycle_count + 1) * 3):
+        await ClockCycles(dut.io_wbs_clk, 1)
+        clk_count += 1
+   
+        if dut.wfg_core_sync_o == 1:
+            assert ((sync_pulse_count+1) * clk_count) == ((sync_count + 1) * (subcycle_count + 1) * 2)
+            break
+            
+        if (dut.wfg_core_subcycle_o == 1):
+            sync_pulse_count += 1
+            clk_count = 0
+
+length = 2
+
+sync_array = []
+
+for i in range(length):
+    n = random.randint(1,2**7)
+    sync_array.append(n)
+
+subcycle_array = []
+
+for i in range(length):
+    n = random.randint(1,2**7)
+    subcycle_array.append(n)
+
+
+factory = TestFactory(core_test)
+factory.add_option("en", [1])
+factory.add_option("sync_count", sync_array)
+factory.add_option("subcycle_count", subcycle_array)
+
+factory.generate_tests()
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/testbench/test_wfg_subcore.py b/verilog/rtl/waveform-generator/design/wfg_subcore/testbench/test_wfg_subcore.py
new file mode 100644
index 0000000..20e8b33
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/testbench/test_wfg_subcore.py
@@ -0,0 +1,94 @@
+# SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+# SPDX-License-Identifier: Apache-2.0
+
+import random
+import cocotb
+from cocotb.clock import Clock
+from cocotb.regression import TestFactory
+from cocotb.triggers import Timer, RisingEdge, FallingEdge, ClockCycles
+from cocotbext.wishbone.driver import WishboneMaster
+from cocotbext.wishbone.driver import WBOp
+
+CLK_PER_SYNC = 300
+SYSCLK = 100000000
+DATA_CNT = 10
+
+short_per = Timer(100, units="ns")
+long_time = Timer(100, units="us")
+
+async def set_register(dut, wbs, address, data):
+    dut._log.info(f"Set register {address} : {data}")
+
+    wbRes = await wbs.send_cycle([WBOp(address, data)])
+    
+    rvalues = [wb.datrd for wb in wbRes]
+    dut._log.info(f"Returned values : {rvalues}")
+
+async def configure(dut, wbs, en, sync_count, subcycle_count):
+    await set_register(dut, wbs, 0x4, (sync_count << 0) | (subcycle_count << 8))
+    await set_register(dut, wbs, 0x0, en) # Enable subcore
+
+@cocotb.coroutine
+async def subcore_test(dut, en, sync_count, subcycle_count):
+    dut._log.info(f"Configuration: sync_count={sync_count}, subcycle_count={subcycle_count}")
+
+    cocotb.start_soon(Clock(dut.io_wbs_clk, 1/SYSCLK*1e9, units="ns").start())
+
+    dut._log.info("Initialize and reset model")
+
+    # Start reset
+    dut.io_wbs_rst.value = 1    
+    await Timer(100, units='ns')
+
+    # Stop reset
+    dut.io_wbs_rst.value = 0
+
+    # Wishbone Master
+    wbs = WishboneMaster(dut, "io_wbs", dut.io_wbs_clk,
+                              width=32,   # size of data bus
+                              timeout=10) # in clock cycle number
+
+    # Setup subcore
+    await configure(dut, wbs, en, sync_count, subcycle_count)
+
+    #await short_per
+    #await short_per
+    
+    sync_pulse_count = 0
+    clk_count = 0
+    
+    await FallingEdge(dut.wfg_subcore_sync_o)
+
+    for i in range ((sync_count + 1) * (subcycle_count + 1) * 3):
+        await ClockCycles(dut.io_wbs_clk, 1)
+        clk_count += 1
+   
+        if dut.wfg_subcore_sync_o == 1:
+            assert ((sync_pulse_count+1) * clk_count) == ((sync_count + 1) * (subcycle_count + 1) * 2)
+            break
+            
+        if (dut.wfg_subcore_subcycle_o == 1):
+            sync_pulse_count += 1
+            clk_count = 0
+
+length = 2
+
+sync_array = []
+
+for i in range(length):
+    n = random.randint(1,2**7)
+    sync_array.append(n)
+
+subcycle_array = []
+
+for i in range(length):
+    n = random.randint(1,2**7)
+    subcycle_array.append(n)
+
+
+factory = TestFactory(subcore_test)
+factory.add_option("en", [1])
+factory.add_option("sync_count", sync_array)
+factory.add_option("subcycle_count", subcycle_array)
+
+factory.generate_tests()
diff --git a/verilog/rtl/waveform-generator/design/wfg_subcore/testbench/wfg_subcore_tb.sv b/verilog/rtl/waveform-generator/design/wfg_subcore/testbench/wfg_subcore_tb.sv
new file mode 100644
index 0000000..071f902
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_subcore/testbench/wfg_subcore_tb.sv
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`timescale 1ns / 1ps
+
+`ifdef VERILATOR  // make parameter readable from VPI
+`define VL_RD /*verilator public_flat_rd*/
+`else
+`define VL_RD
+`endif
+
+module wfg_subcore_tb #(
+    parameter int BUSW = 32
+) (
+    // Wishbone interface signals
+    input               io_wbs_clk,
+    input               io_wbs_rst,
+    input  [(BUSW-1):0] io_wbs_adr,
+    input  [(BUSW-1):0] io_wbs_datwr,
+    output [(BUSW-1):0] io_wbs_datrd,
+    input               io_wbs_we,
+    input               io_wbs_stb,
+    output              io_wbs_ack,
+    input               io_wbs_cyc,
+
+    // subcore synchronisation interface
+    output wire       wfg_subcore_sync_o,          // O; Sync signal
+    output wire       wfg_subcore_subcycle_o,      // O; Subcycle signal
+    output wire       wfg_subcore_start_o,         // O; Indicate start
+    output wire [7:0] wfg_subcore_subcycle_cnt_o,  // O; Subcycle pulse counter
+    output wire       active_o                     // O; Active indication signal
+);
+
+    wfg_subcore_top wfg_subcore_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(io_wbs_adr),
+        .wbs_ack_o(io_wbs_ack),
+        .wbs_dat_o(io_wbs_datrd),
+
+        // subcore synchronisation interface
+        .wfg_subcore_sync_o(wfg_subcore_sync_o),
+        .wfg_subcore_subcycle_o(wfg_subcore_subcycle_o),
+        .wfg_subcore_start_o(wfg_subcore_start_o),
+        .wfg_subcore_subcycle_cnt_o(wfg_subcore_subcycle_cnt_o),
+        .active_o(active_o)
+    );
+
+    // Dump waves
+`ifndef VERILATOR
+    initial begin
+        $dumpfile("wfg_subcore_tb.vcd");
+        $dumpvars(0, wfg_subcore_tb);
+    end
+`endif
+
+endmodule
diff --git a/verilog/rtl/waveform-generator/design/wfg_top/rtl/wfg_top.sv b/verilog/rtl/waveform-generator/design/wfg_top/rtl/wfg_top.sv
new file mode 100644
index 0000000..13e7b37
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_top/rtl/wfg_top.sv
@@ -0,0 +1,303 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`default_nettype none
+
+`ifndef WFG_INTERCONNECT_PKG
+`define WFG_INTERCONNECT_PKG
+typedef struct packed {
+    logic wfg_axis_tvalid;
+    logic [31:0] wfg_axis_tdata;
+} axis_t;
+`endif
+
+module wfg_top #(
+    parameter int BUSW = 32
+) (
+`ifdef USE_POWER_PINS
+    inout vccd1,       // User area 1 1.8V supply
+    inout vssd1,       // User area 1 digital ground
+`endif
+    // Wishbone interface signals
+    input               io_wbs_clk,
+    input               io_wbs_rst,
+    input  [(BUSW-1):0] io_wbs_adr,
+    input  [(BUSW-1):0] io_wbs_datwr,
+    output [(BUSW-1):0] io_wbs_datrd,
+    input               io_wbs_we,
+    input               io_wbs_stb,
+    output              io_wbs_ack,
+    input               io_wbs_cyc,
+
+    output wfg_drive_spi_sclk_o,
+    output wfg_drive_spi_cs_no,
+    output wfg_drive_spi_sdo_o,
+
+    output [31:0] wfg_drive_pat_dout_o,
+
+    // Memory interface
+    output        csb1,
+    output [ 9:0] addr1,
+    input  [31:0] dout1,
+    
+    output [10:0] io_oeb
+);
+    assign io_oeb = '0;
+
+    // Wishbone interconnect
+
+    // Adress select lines
+    logic wfg_sel;
+    logic wfg_core_sel;
+    logic wfg_subcore_sel;
+    logic wfg_intercnct_sel;
+    logic wfg_stim_sine_sel;
+    logic wfg_stim_mem_sel;
+    logic wfg_drive_spi_sel;
+    logic wfg_drive_pat_sel;
+
+    assign wfg_sel           = (io_wbs_adr[BUSW-1:BUSW-4] == 4'h3);  // Base address: 0x30000000
+
+    // Nothing should be assigned to the null page
+    assign wfg_core_sel      = (io_wbs_adr[BUSW-5:4] == 28'h01);  // 0x10
+    assign wfg_subcore_sel   = (io_wbs_adr[BUSW-5:4] == 28'h02);  // 0x20
+    assign wfg_intercnct_sel = (io_wbs_adr[BUSW-5:4] == 28'h03);  // 0x30
+    assign wfg_stim_sine_sel = (io_wbs_adr[BUSW-5:4] == 28'h04);  // 0x40
+    assign wfg_stim_mem_sel  = (io_wbs_adr[BUSW-5:4] == 28'h05);  // 0x50
+    assign wfg_drive_spi_sel = (io_wbs_adr[BUSW-5:4] == 28'h06);  // 0x60
+    assign wfg_drive_pat_sel = (io_wbs_adr[BUSW-5:4] == 28'h07);  // 0x70
+
+    // Acknowledgement
+    logic wfg_core_ack;
+    logic wfg_subcore_ack;
+    logic wfg_intercnct_ack;
+    logic wfg_stim_sine_ack;
+    logic wfg_stim_mem_ack;
+    logic wfg_drive_spi_ack;
+    logic wfg_drive_pat_ack;
+
+    assign io_wbs_ack = (wfg_core_ack) || (wfg_subcore_ack) || (wfg_intercnct_ack) || (wfg_stim_sine_ack) || (wfg_stim_mem_ack) || (wfg_drive_spi_ack) || (wfg_drive_pat_ack);
+
+    // Return data
+    logic [(BUSW-1):0] wfg_core_data;
+    logic [(BUSW-1):0] wfg_subcore_data;
+    logic [(BUSW-1):0] wfg_intercnct_data;
+    logic [(BUSW-1):0] wfg_stim_sine_data;
+    logic [(BUSW-1):0] wfg_stim_mem_data;
+    logic [(BUSW-1):0] wfg_drive_spi_data;
+    logic [(BUSW-1):0] wfg_drive_pat_data;
+
+    logic [(BUSW-1):0] my_io_wbs_datrd;
+
+    always_comb begin
+        unique case (1'b1)
+            wfg_core_sel:
+                my_io_wbs_datrd = wfg_core_data;
+            wfg_subcore_sel:
+                my_io_wbs_datrd = wfg_subcore_data;
+            wfg_intercnct_sel:
+                my_io_wbs_datrd = wfg_intercnct_data;
+            wfg_stim_sine_sel:
+                my_io_wbs_datrd = wfg_stim_sine_data;
+            wfg_stim_mem_sel:
+                my_io_wbs_datrd = wfg_stim_mem_data;
+            wfg_drive_spi_sel:
+                my_io_wbs_datrd = wfg_drive_spi_data;
+            wfg_drive_pat_sel:
+                my_io_wbs_datrd = wfg_drive_pat_data;
+            default:
+                my_io_wbs_datrd = 'x;
+        endcase
+    end
+
+    assign io_wbs_datrd = my_io_wbs_datrd;
+
+    // Core synchronisation interface
+    logic wfg_core_sync;
+    logic wfg_core_subcycle;
+    logic wfg_core_start;
+    logic [7:0] wfg_core_subcycle_cnt;
+    logic wfg_core_active;
+
+    wfg_core_top wfg_core_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb && wfg_core_sel && wfg_sel),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(wfg_core_sel ? io_wbs_adr & 4'hF : 4'h0),
+        .wbs_ack_o(wfg_core_ack),
+        .wbs_dat_o(wfg_core_data),
+
+        .wfg_core_sync_o        (wfg_core_sync),
+        .wfg_core_subcycle_o    (wfg_core_subcycle),
+        .wfg_core_start_o       (wfg_core_start),
+        .wfg_core_subcycle_cnt_o(wfg_core_subcycle_cnt),
+        .active_o               (wfg_core_active)
+    );
+
+    // Subcore synchronisation interface
+    logic wfg_subcore_sync;
+    logic wfg_subcore_subcycle;
+    logic wfg_subcore_start;
+    logic [7:0] wfg_subcore_subcycle_cnt;
+    logic wfg_subcore_active;
+
+    wfg_subcore_top wfg_subcore_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb && wfg_subcore_sel && wfg_sel),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(wfg_subcore_sel ? io_wbs_adr & 4'hF : 4'h0),
+        .wbs_ack_o(wfg_subcore_ack),
+        .wbs_dat_o(wfg_subcore_data),
+
+        .wfg_subcore_sync_o        (wfg_subcore_sync),
+        .wfg_subcore_subcycle_o    (wfg_subcore_subcycle),
+        .wfg_subcore_start_o       (wfg_subcore_start),
+        .wfg_subcore_subcycle_cnt_o(wfg_subcore_subcycle_cnt),
+        .active_o                  (wfg_subcore_active)
+    );
+
+    axis_t driver_0;
+    axis_t driver_1;
+
+    wfg_interconnect_top wfg_interconnect_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb && wfg_intercnct_sel && wfg_sel),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(wfg_intercnct_sel ? io_wbs_adr & 4'hF : 4'h0),
+        .wbs_ack_o(wfg_intercnct_ack),
+        .wbs_dat_o(wfg_intercnct_data),
+
+        .stimulus_0,
+        .stimulus_1,
+
+        .wfg_axis_tready_stimulus_0(stimulus_0_wfg_axis_tready),
+        .wfg_axis_tready_stimulus_1(stimulus_1_wfg_axis_tready),
+
+        .driver_0,
+        .driver_1,
+
+        .wfg_axis_tready_driver_0(driver_0_wfg_axis_tready),
+        .wfg_axis_tready_driver_1(driver_1_wfg_axis_tready)
+    );
+    axis_t stimulus_0;
+    axis_t stimulus_1;
+
+    logic stimulus_0_wfg_axis_tready;
+
+    wfg_stim_sine_top wfg_stim_sine_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb && wfg_stim_sine_sel && wfg_sel),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(wfg_stim_sine_sel ? io_wbs_adr & 4'hF : 4'h0),
+        .wbs_ack_o(wfg_stim_sine_ack),
+        .wbs_dat_o(wfg_stim_sine_data),
+
+        .wfg_axis_tready_i(stimulus_0_wfg_axis_tready),
+        .wfg_axis_tvalid_o(stimulus_0.wfg_axis_tvalid),
+        .wfg_axis_tdata_o (stimulus_0.wfg_axis_tdata)
+    );
+
+    logic stimulus_1_wfg_axis_tready;
+
+    wfg_stim_mem_top wfg_stim_mem_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb && wfg_stim_mem_sel && wfg_sel),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(wfg_stim_mem_sel ? io_wbs_adr & 4'hF : 4'h0),
+        .wbs_ack_o(wfg_stim_mem_ack),
+        .wbs_dat_o(wfg_stim_mem_data),
+
+        .wfg_axis_tready_i(stimulus_1_wfg_axis_tready),
+        .wfg_axis_tvalid_o(stimulus_1.wfg_axis_tvalid),
+        .wfg_axis_tdata_o (stimulus_1.wfg_axis_tdata),
+
+        .csb1 (csb1),
+        .addr1(addr1),
+        .dout1(dout1)
+    );
+
+    logic driver_0_wfg_axis_tready;
+
+    wfg_drive_spi_top wfg_drive_spi_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb && wfg_drive_spi_sel && wfg_sel),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(wfg_drive_spi_sel ? io_wbs_adr & 4'hF : 4'h0),
+        .wbs_ack_o(wfg_drive_spi_ack),
+        .wbs_dat_o(wfg_drive_spi_data),
+
+        .wfg_core_sync_i    (wfg_core_sync),
+        .wfg_core_subcycle_i(wfg_core_subcycle),
+
+        .wfg_subcore_sync_i    (wfg_subcore_sync),
+        .wfg_subcore_subcycle_i(wfg_subcore_subcycle),
+
+        .wfg_axis_tready_o(driver_0_wfg_axis_tready),
+        .wfg_axis_tdata_i (driver_0.wfg_axis_tdata),
+        .wfg_axis_tlast_i (1'b0),
+        .wfg_axis_tvalid_i(driver_0.wfg_axis_tvalid),
+
+        .wfg_drive_spi_sclk_o(wfg_drive_spi_sclk_o),
+        .wfg_drive_spi_cs_no (wfg_drive_spi_cs_no),
+        .wfg_drive_spi_sdo_o (wfg_drive_spi_sdo_o)
+    );
+
+    logic driver_1_wfg_axis_tready;
+
+    wfg_drive_pat_top #(
+        .CHANNELS(32)
+    ) wfg_drive_pat_top (
+        .wb_clk_i (io_wbs_clk),
+        .wb_rst_i (io_wbs_rst),
+        .wbs_stb_i(io_wbs_stb && wfg_drive_pat_sel && wfg_sel),
+        .wbs_cyc_i(io_wbs_cyc),
+        .wbs_we_i (io_wbs_we),
+        .wbs_sel_i(4'b1111),
+        .wbs_dat_i(io_wbs_datwr),
+        .wbs_adr_i(wfg_drive_pat_sel ? io_wbs_adr & 4'hF : 4'h0),
+        .wbs_ack_o(wfg_drive_pat_ack),
+        .wbs_dat_o(wfg_drive_pat_data),
+
+        .wfg_core_sync_i        (wfg_core_sync),
+        .wfg_core_subcycle_cnt_i(wfg_core_subcycle_cnt),
+
+        .wfg_subcore_sync_i        (wfg_subcore_sync),
+        .wfg_subcore_subcycle_cnt_i(wfg_subcore_subcycle_cnt),
+
+        .wfg_axis_tready_o(driver_1_wfg_axis_tready),
+        .wfg_axis_tdata_i (driver_1.wfg_axis_tdata),
+        .wfg_axis_tlast_i (1'b0),
+        .wfg_axis_tvalid_i(driver_1.wfg_axis_tvalid),
+
+        .pat_dout_o(wfg_drive_pat_dout_o),
+        .pat_dout_en_o(wfg_drive_pat_dout_en_o)
+    );
+
+    logic [31:0] wfg_drive_pat_dout_en_o;
+
+endmodule
+`default_nettype wire
diff --git a/verilog/rtl/waveform-generator/design/wfg_top/sim/Makefile b/verilog/rtl/waveform-generator/design/wfg_top/sim/Makefile
new file mode 100644
index 0000000..1ac810d
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_top/sim/Makefile
@@ -0,0 +1,26 @@
+# source files
+SRC := $(wildcard ../rtl/*.sv)
+SRC += $(wildcard ../testbench/*.sv)
+SRC += $(wildcard ../../wfg_core/rtl/*.sv)
+SRC += $(wildcard ../../wfg_subcore/rtl/*.sv)
+SRC += $(wildcard ../../wfg_interconnect/rtl/*.sv)
+SRC += $(wildcard ../../wfg_stim_sine/rtl/*.sv)
+SRC += $(wildcard ../../wfg_stim_mem/rtl/*.sv)
+SRC += $(wildcard ../../wfg_drive_spi/rtl/*.sv)
+SRC += $(wildcard ../../wfg_drive_pat/rtl/*.sv)
+
+# defaults
+SIM ?= icarus
+TOPLEVEL_LANG ?= verilog
+
+VERILOG_SOURCES += $(SRC)
+
+# TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file
+TOPLEVEL = wfg_top_tb
+
+# MODULE is the basename of the Python test file
+export PYTHONPATH := $(PYTHONPATH):../testbench/
+MODULE = test_wfg_top
+
+# include cocotb's make rules to take care of the simulator setup
+include $(shell cocotb-config --makefiles)/Makefile.sim
diff --git a/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=1280_gain=16384_off=0.png b/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=1280_gain=16384_off=0.png
new file mode 100644
index 0000000..920bd90
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=1280_gain=16384_off=0.png
Binary files differ
diff --git a/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=1280_gain=16384_off=0.svg b/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=1280_gain=16384_off=0.svg
new file mode 100644
index 0000000..8da78d6
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=1280_gain=16384_off=0.svg
@@ -0,0 +1,1671 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="720pt" height="432pt" viewBox="0 0 720 432" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <metadata>
+  <rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+   <cc:Work>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:date>2022-06-27T10:30:06.963381</dc:date>
+    <dc:format>image/svg+xml</dc:format>
+    <dc:creator>
+     <cc:Agent>
+      <dc:title>Matplotlib v3.5.2, https://matplotlib.org/</dc:title>
+     </cc:Agent>
+    </dc:creator>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <defs>
+  <style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
+ </defs>
+ <g id="figure_1">
+  <g id="patch_1">
+   <path d="M 0 432 
+L 720 432 
+L 720 0 
+L 0 0 
+z
+" style="fill: #ffffff"/>
+  </g>
+  <g id="axes_1">
+   <g id="patch_2">
+    <path d="M 74.99 196.72 
+L 709.2 196.72 
+L 709.2 45.36 
+L 74.99 45.36 
+z
+" style="fill: #ffffff"/>
+   </g>
+   <g id="PathCollection_1">
+    <defs>
+     <path id="m2bce359496" d="M 0 3 
+C 0.795609 3 1.55874 2.683901 2.12132 2.12132 
+C 2.683901 1.55874 3 0.795609 3 0 
+C 3 -0.795609 2.683901 -1.55874 2.12132 -2.12132 
+C 1.55874 -2.683901 0.795609 -3 0 -3 
+C -0.795609 -3 -1.55874 -2.683901 -2.12132 -2.12132 
+C -2.683901 -1.55874 -3 -0.795609 -3 0 
+C -3 0.795609 -2.683901 1.55874 -2.12132 2.12132 
+C -1.55874 2.683901 -0.795609 3 0 3 
+z
+" style="stroke: #1f77b4"/>
+    </defs>
+    <g clip-path="url(#p20292001c7)">
+     <use xlink:href="#m2bce359496" x="103.817727" y="121.080975" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="115.122718" y="112.644349" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="126.427709" y="104.342205" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="137.732701" y="96.297467" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="149.037692" y="88.622555" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="160.342683" y="81.430935" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="171.647674" y="74.828724" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="182.952665" y="68.92939" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="194.257656" y="63.820136" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="205.562647" y="59.571354" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="216.867638" y="56.241881" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="228.172629" y="53.890553" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="239.47762" y="52.547837" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="250.782611" y="52.24" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="262.087602" y="52.96494" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="273.392594" y="54.711101" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="284.697585" y="57.465875" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="296.002576" y="61.169375" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="307.307567" y="65.772222" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="318.612558" y="71.205072" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="329.917549" y="77.382826" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="341.22254" y="84.222481" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="352.527531" y="91.623178" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="363.832522" y="99.472496" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="375.137513" y="107.629652" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="386.442504" y="115.993784" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="397.747496" y="124.451423" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="409.052487" y="132.836567" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="420.357478" y="141.066217" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="431.662469" y="148.980676" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="442.96746" y="156.472777" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="454.272451" y="163.429054" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="465.577442" y="169.762302" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="476.882433" y="175.358001" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="488.187424" y="180.134203" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="499.492415" y="184.015261" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="510.797406" y="186.958099" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="522.102398" y="188.912286" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="533.407389" y="189.84" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="544.71238" y="189.737037" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="556.017371" y="188.598146" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="567.322362" y="186.444337" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="578.627353" y="183.308182" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="589.932344" y="179.233806" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="601.237335" y="174.280046" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="612.542326" y="168.548814" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="623.847317" y="162.083186" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="635.152308" y="155.007137" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="646.457299" y="147.42468" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="657.762291" y="139.431424" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="669.067282" y="131.168153" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#m2bce359496" x="680.372273" y="122.756743" style="fill: #1f77b4; stroke: #1f77b4"/>
+    </g>
+   </g>
+   <g id="matplotlib.axis_1">
+    <g id="xtick_1">
+     <g id="line2d_1">
+      <path d="M 87.075388 196.72 
+L 87.075388 45.36 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_2">
+      <defs>
+       <path id="ma65ff080c7" d="M 0 0 
+L 0 3.5 
+" style="stroke: #000000; stroke-width: 0.8"/>
+      </defs>
+      <g>
+       <use xlink:href="#ma65ff080c7" x="87.075388" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_1">
+      <!-- 0 -->
+      <g transform="translate(83.894138 211.318438)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-30" d="M 2034 4250 
+Q 1547 4250 1301 3770 
+Q 1056 3291 1056 2328 
+Q 1056 1369 1301 889 
+Q 1547 409 2034 409 
+Q 2525 409 2770 889 
+Q 3016 1369 3016 2328 
+Q 3016 3291 2770 3770 
+Q 2525 4250 2034 4250 
+z
+M 2034 4750 
+Q 2819 4750 3233 4129 
+Q 3647 3509 3647 2328 
+Q 3647 1150 3233 529 
+Q 2819 -91 2034 -91 
+Q 1250 -91 836 529 
+Q 422 1150 422 2328 
+Q 422 3509 836 4129 
+Q 1250 4750 2034 4750 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-30"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_2">
+     <g id="line2d_3">
+      <path d="M 184.869428 196.72 
+L 184.869428 45.36 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_4">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="184.869428" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_2">
+      <!-- 50000 -->
+      <g transform="translate(168.963178 211.318438)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-35" d="M 691 4666 
+L 3169 4666 
+L 3169 4134 
+L 1269 4134 
+L 1269 2991 
+Q 1406 3038 1543 3061 
+Q 1681 3084 1819 3084 
+Q 2600 3084 3056 2656 
+Q 3513 2228 3513 1497 
+Q 3513 744 3044 326 
+Q 2575 -91 1722 -91 
+Q 1428 -91 1123 -41 
+Q 819 9 494 109 
+L 494 744 
+Q 775 591 1075 516 
+Q 1375 441 1709 441 
+Q 2250 441 2565 725 
+Q 2881 1009 2881 1497 
+Q 2881 1984 2565 2268 
+Q 2250 2553 1709 2553 
+Q 1456 2553 1204 2497 
+Q 953 2441 691 2322 
+L 691 4666 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-35"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_3">
+     <g id="line2d_5">
+      <path d="M 282.663469 196.72 
+L 282.663469 45.36 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_6">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="282.663469" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_3">
+      <!-- 100000 -->
+      <g transform="translate(263.575969 211.318438)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-31" d="M 794 531 
+L 1825 531 
+L 1825 4091 
+L 703 3866 
+L 703 4441 
+L 1819 4666 
+L 2450 4666 
+L 2450 531 
+L 3481 531 
+L 3481 0 
+L 794 0 
+L 794 531 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-31"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_4">
+     <g id="line2d_7">
+      <path d="M 380.457509 196.72 
+L 380.457509 45.36 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_8">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="380.457509" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_4">
+      <!-- 150000 -->
+      <g transform="translate(361.370009 211.318438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-31"/>
+       <use xlink:href="#DejaVuSans-35" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_5">
+     <g id="line2d_9">
+      <path d="M 478.25155 196.72 
+L 478.25155 45.36 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_10">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="478.25155" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_5">
+      <!-- 200000 -->
+      <g transform="translate(459.16405 211.318438)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-32" d="M 1228 531 
+L 3431 531 
+L 3431 0 
+L 469 0 
+L 469 531 
+Q 828 903 1448 1529 
+Q 2069 2156 2228 2338 
+Q 2531 2678 2651 2914 
+Q 2772 3150 2772 3378 
+Q 2772 3750 2511 3984 
+Q 2250 4219 1831 4219 
+Q 1534 4219 1204 4116 
+Q 875 4013 500 3803 
+L 500 4441 
+Q 881 4594 1212 4672 
+Q 1544 4750 1819 4750 
+Q 2544 4750 2975 4387 
+Q 3406 4025 3406 3419 
+Q 3406 3131 3298 2873 
+Q 3191 2616 2906 2266 
+Q 2828 2175 2409 1742 
+Q 1991 1309 1228 531 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-32"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_6">
+     <g id="line2d_11">
+      <path d="M 576.04559 196.72 
+L 576.04559 45.36 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_12">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="576.04559" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_6">
+      <!-- 250000 -->
+      <g transform="translate(556.95809 211.318438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-32"/>
+       <use xlink:href="#DejaVuSans-35" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_7">
+     <g id="line2d_13">
+      <path d="M 673.839631 196.72 
+L 673.839631 45.36 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_14">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="673.839631" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_7">
+      <!-- 300000 -->
+      <g transform="translate(654.752131 211.318438)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-33" d="M 2597 2516 
+Q 3050 2419 3304 2112 
+Q 3559 1806 3559 1356 
+Q 3559 666 3084 287 
+Q 2609 -91 1734 -91 
+Q 1441 -91 1130 -33 
+Q 819 25 488 141 
+L 488 750 
+Q 750 597 1062 519 
+Q 1375 441 1716 441 
+Q 2309 441 2620 675 
+Q 2931 909 2931 1356 
+Q 2931 1769 2642 2001 
+Q 2353 2234 1838 2234 
+L 1294 2234 
+L 1294 2753 
+L 1863 2753 
+Q 2328 2753 2575 2939 
+Q 2822 3125 2822 3475 
+Q 2822 3834 2567 4026 
+Q 2313 4219 1838 4219 
+Q 1578 4219 1281 4162 
+Q 984 4106 628 3988 
+L 628 4550 
+Q 988 4650 1302 4700 
+Q 1616 4750 1894 4750 
+Q 2613 4750 3031 4423 
+Q 3450 4097 3450 3541 
+Q 3450 3153 3228 2886 
+Q 3006 2619 2597 2516 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-33"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="text_8">
+     <!-- time in ns -->
+     <g transform="translate(367.289531 224.996563)scale(0.1 -0.1)">
+      <defs>
+       <path id="DejaVuSans-74" d="M 1172 4494 
+L 1172 3500 
+L 2356 3500 
+L 2356 3053 
+L 1172 3053 
+L 1172 1153 
+Q 1172 725 1289 603 
+Q 1406 481 1766 481 
+L 2356 481 
+L 2356 0 
+L 1766 0 
+Q 1100 0 847 248 
+Q 594 497 594 1153 
+L 594 3053 
+L 172 3053 
+L 172 3500 
+L 594 3500 
+L 594 4494 
+L 1172 4494 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-69" d="M 603 3500 
+L 1178 3500 
+L 1178 0 
+L 603 0 
+L 603 3500 
+z
+M 603 4863 
+L 1178 4863 
+L 1178 4134 
+L 603 4134 
+L 603 4863 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-6d" d="M 3328 2828 
+Q 3544 3216 3844 3400 
+Q 4144 3584 4550 3584 
+Q 5097 3584 5394 3201 
+Q 5691 2819 5691 2113 
+L 5691 0 
+L 5113 0 
+L 5113 2094 
+Q 5113 2597 4934 2840 
+Q 4756 3084 4391 3084 
+Q 3944 3084 3684 2787 
+Q 3425 2491 3425 1978 
+L 3425 0 
+L 2847 0 
+L 2847 2094 
+Q 2847 2600 2669 2842 
+Q 2491 3084 2119 3084 
+Q 1678 3084 1418 2786 
+Q 1159 2488 1159 1978 
+L 1159 0 
+L 581 0 
+L 581 3500 
+L 1159 3500 
+L 1159 2956 
+Q 1356 3278 1631 3431 
+Q 1906 3584 2284 3584 
+Q 2666 3584 2933 3390 
+Q 3200 3197 3328 2828 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-65" d="M 3597 1894 
+L 3597 1613 
+L 953 1613 
+Q 991 1019 1311 708 
+Q 1631 397 2203 397 
+Q 2534 397 2845 478 
+Q 3156 559 3463 722 
+L 3463 178 
+Q 3153 47 2828 -22 
+Q 2503 -91 2169 -91 
+Q 1331 -91 842 396 
+Q 353 884 353 1716 
+Q 353 2575 817 3079 
+Q 1281 3584 2069 3584 
+Q 2775 3584 3186 3129 
+Q 3597 2675 3597 1894 
+z
+M 3022 2063 
+Q 3016 2534 2758 2815 
+Q 2500 3097 2075 3097 
+Q 1594 3097 1305 2825 
+Q 1016 2553 972 2059 
+L 3022 2063 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-20" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-6e" d="M 3513 2113 
+L 3513 0 
+L 2938 0 
+L 2938 2094 
+Q 2938 2591 2744 2837 
+Q 2550 3084 2163 3084 
+Q 1697 3084 1428 2787 
+Q 1159 2491 1159 1978 
+L 1159 0 
+L 581 0 
+L 581 3500 
+L 1159 3500 
+L 1159 2956 
+Q 1366 3272 1645 3428 
+Q 1925 3584 2291 3584 
+Q 2894 3584 3203 3211 
+Q 3513 2838 3513 2113 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-73" d="M 2834 3397 
+L 2834 2853 
+Q 2591 2978 2328 3040 
+Q 2066 3103 1784 3103 
+Q 1356 3103 1142 2972 
+Q 928 2841 928 2578 
+Q 928 2378 1081 2264 
+Q 1234 2150 1697 2047 
+L 1894 2003 
+Q 2506 1872 2764 1633 
+Q 3022 1394 3022 966 
+Q 3022 478 2636 193 
+Q 2250 -91 1575 -91 
+Q 1294 -91 989 -36 
+Q 684 19 347 128 
+L 347 722 
+Q 666 556 975 473 
+Q 1284 391 1588 391 
+Q 1994 391 2212 530 
+Q 2431 669 2431 922 
+Q 2431 1156 2273 1281 
+Q 2116 1406 1581 1522 
+L 1381 1569 
+Q 847 1681 609 1914 
+Q 372 2147 372 2553 
+Q 372 3047 722 3315 
+Q 1072 3584 1716 3584 
+Q 2034 3584 2315 3537 
+Q 2597 3491 2834 3397 
+z
+" transform="scale(0.015625)"/>
+      </defs>
+      <use xlink:href="#DejaVuSans-74"/>
+      <use xlink:href="#DejaVuSans-69" x="39.208984"/>
+      <use xlink:href="#DejaVuSans-6d" x="66.992188"/>
+      <use xlink:href="#DejaVuSans-65" x="164.404297"/>
+      <use xlink:href="#DejaVuSans-20" x="225.927734"/>
+      <use xlink:href="#DejaVuSans-69" x="257.714844"/>
+      <use xlink:href="#DejaVuSans-6e" x="285.498047"/>
+      <use xlink:href="#DejaVuSans-20" x="348.876953"/>
+      <use xlink:href="#DejaVuSans-6e" x="380.664062"/>
+      <use xlink:href="#DejaVuSans-73" x="444.042969"/>
+     </g>
+    </g>
+   </g>
+   <g id="matplotlib.axis_2">
+    <g id="ytick_1">
+     <g id="line2d_15">
+      <path d="M 74.99 189.925102 
+L 709.2 189.925102 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_16">
+      <defs>
+       <path id="m035fd2fdf7" d="M 0 0 
+L -3.5 0 
+" style="stroke: #000000; stroke-width: 0.8"/>
+      </defs>
+      <g>
+       <use xlink:href="#m035fd2fdf7" x="74.99" y="189.925102" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_9">
+      <!-- −1.0 -->
+      <g transform="translate(43.707187 193.72432)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-2212" d="M 678 2272 
+L 4684 2272 
+L 4684 1741 
+L 678 1741 
+L 678 2272 
+z
+" transform="scale(0.015625)"/>
+        <path id="DejaVuSans-2e" d="M 684 794 
+L 1344 794 
+L 1344 0 
+L 684 0 
+L 684 794 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-2212"/>
+       <use xlink:href="#DejaVuSans-31" x="83.789062"/>
+       <use xlink:href="#DejaVuSans-2e" x="147.412109"/>
+       <use xlink:href="#DejaVuSans-30" x="179.199219"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_2">
+     <g id="line2d_17">
+      <path d="M 74.99 155.497785 
+L 709.2 155.497785 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_18">
+      <g>
+       <use xlink:href="#m035fd2fdf7" x="74.99" y="155.497785" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_10">
+      <!-- −0.5 -->
+      <g transform="translate(43.707187 159.297004)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-2212"/>
+       <use xlink:href="#DejaVuSans-30" x="83.789062"/>
+       <use xlink:href="#DejaVuSans-2e" x="147.412109"/>
+       <use xlink:href="#DejaVuSans-35" x="179.199219"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_3">
+     <g id="line2d_19">
+      <path d="M 74.99 121.070469 
+L 709.2 121.070469 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_20">
+      <g>
+       <use xlink:href="#m035fd2fdf7" x="74.99" y="121.070469" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_11">
+      <!-- 0.0 -->
+      <g transform="translate(52.086875 124.869687)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="95.410156"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_4">
+     <g id="line2d_21">
+      <path d="M 74.99 86.643152 
+L 709.2 86.643152 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_22">
+      <g>
+       <use xlink:href="#m035fd2fdf7" x="74.99" y="86.643152" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_12">
+      <!-- 0.5 -->
+      <g transform="translate(52.086875 90.442371)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-35" x="95.410156"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_5">
+     <g id="line2d_23">
+      <path d="M 74.99 52.215835 
+L 709.2 52.215835 
+" clip-path="url(#p20292001c7)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_24">
+      <g>
+       <use xlink:href="#m035fd2fdf7" x="74.99" y="52.215835" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_13">
+      <!-- 1.0 -->
+      <g transform="translate(52.086875 56.015054)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-31"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="95.410156"/>
+      </g>
+     </g>
+    </g>
+    <g id="text_14">
+     <!-- Value -->
+     <g transform="translate(37.6275 134.77125)rotate(-90)scale(0.1 -0.1)">
+      <defs>
+       <path id="DejaVuSans-56" d="M 1831 0 
+L 50 4666 
+L 709 4666 
+L 2188 738 
+L 3669 4666 
+L 4325 4666 
+L 2547 0 
+L 1831 0 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-61" d="M 2194 1759 
+Q 1497 1759 1228 1600 
+Q 959 1441 959 1056 
+Q 959 750 1161 570 
+Q 1363 391 1709 391 
+Q 2188 391 2477 730 
+Q 2766 1069 2766 1631 
+L 2766 1759 
+L 2194 1759 
+z
+M 3341 1997 
+L 3341 0 
+L 2766 0 
+L 2766 531 
+Q 2569 213 2275 61 
+Q 1981 -91 1556 -91 
+Q 1019 -91 701 211 
+Q 384 513 384 1019 
+Q 384 1609 779 1909 
+Q 1175 2209 1959 2209 
+L 2766 2209 
+L 2766 2266 
+Q 2766 2663 2505 2880 
+Q 2244 3097 1772 3097 
+Q 1472 3097 1187 3025 
+Q 903 2953 641 2809 
+L 641 3341 
+Q 956 3463 1253 3523 
+Q 1550 3584 1831 3584 
+Q 2591 3584 2966 3190 
+Q 3341 2797 3341 1997 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-6c" d="M 603 4863 
+L 1178 4863 
+L 1178 0 
+L 603 0 
+L 603 4863 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-75" d="M 544 1381 
+L 544 3500 
+L 1119 3500 
+L 1119 1403 
+Q 1119 906 1312 657 
+Q 1506 409 1894 409 
+Q 2359 409 2629 706 
+Q 2900 1003 2900 1516 
+L 2900 3500 
+L 3475 3500 
+L 3475 0 
+L 2900 0 
+L 2900 538 
+Q 2691 219 2414 64 
+Q 2138 -91 1772 -91 
+Q 1169 -91 856 284 
+Q 544 659 544 1381 
+z
+M 1991 3584 
+L 1991 3584 
+z
+" transform="scale(0.015625)"/>
+      </defs>
+      <use xlink:href="#DejaVuSans-56"/>
+      <use xlink:href="#DejaVuSans-61" x="60.658203"/>
+      <use xlink:href="#DejaVuSans-6c" x="121.9375"/>
+      <use xlink:href="#DejaVuSans-75" x="149.720703"/>
+      <use xlink:href="#DejaVuSans-65" x="213.099609"/>
+     </g>
+    </g>
+   </g>
+   <g id="patch_3">
+    <path d="M 74.99 196.72 
+L 74.99 45.36 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_4">
+    <path d="M 709.2 196.72 
+L 709.2 45.36 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_5">
+    <path d="M 74.99 196.72 
+L 709.2 196.72 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_6">
+    <path d="M 74.99 45.36 
+L 709.2 45.36 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="legend_1">
+    <g id="patch_7">
+     <path d="M 525.282812 68.038125 
+L 702.2 68.038125 
+Q 704.2 68.038125 704.2 66.038125 
+L 704.2 52.36 
+Q 704.2 50.36 702.2 50.36 
+L 525.282812 50.36 
+Q 523.282812 50.36 523.282812 52.36 
+L 523.282812 66.038125 
+Q 523.282812 68.038125 525.282812 68.038125 
+z
+" style="fill: #ffffff; opacity: 0.8; stroke: #cccccc; stroke-linejoin: miter"/>
+    </g>
+    <g id="PathCollection_2">
+     <g>
+      <use xlink:href="#m2bce359496" x="537.282812" y="59.333438" style="fill: #1f77b4; stroke: #1f77b4"/>
+     </g>
+    </g>
+    <g id="text_15">
+     <!-- SPI data represented as float -->
+     <g transform="translate(555.282812 61.958438)scale(0.1 -0.1)">
+      <defs>
+       <path id="DejaVuSans-53" d="M 3425 4513 
+L 3425 3897 
+Q 3066 4069 2747 4153 
+Q 2428 4238 2131 4238 
+Q 1616 4238 1336 4038 
+Q 1056 3838 1056 3469 
+Q 1056 3159 1242 3001 
+Q 1428 2844 1947 2747 
+L 2328 2669 
+Q 3034 2534 3370 2195 
+Q 3706 1856 3706 1288 
+Q 3706 609 3251 259 
+Q 2797 -91 1919 -91 
+Q 1588 -91 1214 -16 
+Q 841 59 441 206 
+L 441 856 
+Q 825 641 1194 531 
+Q 1563 422 1919 422 
+Q 2459 422 2753 634 
+Q 3047 847 3047 1241 
+Q 3047 1584 2836 1778 
+Q 2625 1972 2144 2069 
+L 1759 2144 
+Q 1053 2284 737 2584 
+Q 422 2884 422 3419 
+Q 422 4038 858 4394 
+Q 1294 4750 2059 4750 
+Q 2388 4750 2728 4690 
+Q 3069 4631 3425 4513 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-50" d="M 1259 4147 
+L 1259 2394 
+L 2053 2394 
+Q 2494 2394 2734 2622 
+Q 2975 2850 2975 3272 
+Q 2975 3691 2734 3919 
+Q 2494 4147 2053 4147 
+L 1259 4147 
+z
+M 628 4666 
+L 2053 4666 
+Q 2838 4666 3239 4311 
+Q 3641 3956 3641 3272 
+Q 3641 2581 3239 2228 
+Q 2838 1875 2053 1875 
+L 1259 1875 
+L 1259 0 
+L 628 0 
+L 628 4666 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-49" d="M 628 4666 
+L 1259 4666 
+L 1259 0 
+L 628 0 
+L 628 4666 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-64" d="M 2906 2969 
+L 2906 4863 
+L 3481 4863 
+L 3481 0 
+L 2906 0 
+L 2906 525 
+Q 2725 213 2448 61 
+Q 2172 -91 1784 -91 
+Q 1150 -91 751 415 
+Q 353 922 353 1747 
+Q 353 2572 751 3078 
+Q 1150 3584 1784 3584 
+Q 2172 3584 2448 3432 
+Q 2725 3281 2906 2969 
+z
+M 947 1747 
+Q 947 1113 1208 752 
+Q 1469 391 1925 391 
+Q 2381 391 2643 752 
+Q 2906 1113 2906 1747 
+Q 2906 2381 2643 2742 
+Q 2381 3103 1925 3103 
+Q 1469 3103 1208 2742 
+Q 947 2381 947 1747 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-72" d="M 2631 2963 
+Q 2534 3019 2420 3045 
+Q 2306 3072 2169 3072 
+Q 1681 3072 1420 2755 
+Q 1159 2438 1159 1844 
+L 1159 0 
+L 581 0 
+L 581 3500 
+L 1159 3500 
+L 1159 2956 
+Q 1341 3275 1631 3429 
+Q 1922 3584 2338 3584 
+Q 2397 3584 2469 3576 
+Q 2541 3569 2628 3553 
+L 2631 2963 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-70" d="M 1159 525 
+L 1159 -1331 
+L 581 -1331 
+L 581 3500 
+L 1159 3500 
+L 1159 2969 
+Q 1341 3281 1617 3432 
+Q 1894 3584 2278 3584 
+Q 2916 3584 3314 3078 
+Q 3713 2572 3713 1747 
+Q 3713 922 3314 415 
+Q 2916 -91 2278 -91 
+Q 1894 -91 1617 61 
+Q 1341 213 1159 525 
+z
+M 3116 1747 
+Q 3116 2381 2855 2742 
+Q 2594 3103 2138 3103 
+Q 1681 3103 1420 2742 
+Q 1159 2381 1159 1747 
+Q 1159 1113 1420 752 
+Q 1681 391 2138 391 
+Q 2594 391 2855 752 
+Q 3116 1113 3116 1747 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-66" d="M 2375 4863 
+L 2375 4384 
+L 1825 4384 
+Q 1516 4384 1395 4259 
+Q 1275 4134 1275 3809 
+L 1275 3500 
+L 2222 3500 
+L 2222 3053 
+L 1275 3053 
+L 1275 0 
+L 697 0 
+L 697 3053 
+L 147 3053 
+L 147 3500 
+L 697 3500 
+L 697 3744 
+Q 697 4328 969 4595 
+Q 1241 4863 1831 4863 
+L 2375 4863 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-6f" d="M 1959 3097 
+Q 1497 3097 1228 2736 
+Q 959 2375 959 1747 
+Q 959 1119 1226 758 
+Q 1494 397 1959 397 
+Q 2419 397 2687 759 
+Q 2956 1122 2956 1747 
+Q 2956 2369 2687 2733 
+Q 2419 3097 1959 3097 
+z
+M 1959 3584 
+Q 2709 3584 3137 3096 
+Q 3566 2609 3566 1747 
+Q 3566 888 3137 398 
+Q 2709 -91 1959 -91 
+Q 1206 -91 779 398 
+Q 353 888 353 1747 
+Q 353 2609 779 3096 
+Q 1206 3584 1959 3584 
+z
+" transform="scale(0.015625)"/>
+      </defs>
+      <use xlink:href="#DejaVuSans-53"/>
+      <use xlink:href="#DejaVuSans-50" x="63.476562"/>
+      <use xlink:href="#DejaVuSans-49" x="123.779297"/>
+      <use xlink:href="#DejaVuSans-20" x="153.271484"/>
+      <use xlink:href="#DejaVuSans-64" x="185.058594"/>
+      <use xlink:href="#DejaVuSans-61" x="248.535156"/>
+      <use xlink:href="#DejaVuSans-74" x="309.814453"/>
+      <use xlink:href="#DejaVuSans-61" x="349.023438"/>
+      <use xlink:href="#DejaVuSans-20" x="410.302734"/>
+      <use xlink:href="#DejaVuSans-72" x="442.089844"/>
+      <use xlink:href="#DejaVuSans-65" x="480.953125"/>
+      <use xlink:href="#DejaVuSans-70" x="542.476562"/>
+      <use xlink:href="#DejaVuSans-72" x="605.953125"/>
+      <use xlink:href="#DejaVuSans-65" x="644.816406"/>
+      <use xlink:href="#DejaVuSans-73" x="706.339844"/>
+      <use xlink:href="#DejaVuSans-65" x="758.439453"/>
+      <use xlink:href="#DejaVuSans-6e" x="819.962891"/>
+      <use xlink:href="#DejaVuSans-74" x="883.341797"/>
+      <use xlink:href="#DejaVuSans-65" x="922.550781"/>
+      <use xlink:href="#DejaVuSans-64" x="984.074219"/>
+      <use xlink:href="#DejaVuSans-20" x="1047.550781"/>
+      <use xlink:href="#DejaVuSans-61" x="1079.337891"/>
+      <use xlink:href="#DejaVuSans-73" x="1140.617188"/>
+      <use xlink:href="#DejaVuSans-20" x="1192.716797"/>
+      <use xlink:href="#DejaVuSans-66" x="1224.503906"/>
+      <use xlink:href="#DejaVuSans-6c" x="1259.708984"/>
+      <use xlink:href="#DejaVuSans-6f" x="1287.492188"/>
+      <use xlink:href="#DejaVuSans-61" x="1348.673828"/>
+      <use xlink:href="#DejaVuSans-74" x="1409.953125"/>
+     </g>
+    </g>
+   </g>
+  </g>
+  <g id="axes_2">
+   <g id="patch_8">
+    <path d="M 74.99 390.04 
+L 709.2 390.04 
+L 709.2 238.68 
+L 74.99 238.68 
+z
+" style="fill: #ffffff"/>
+   </g>
+   <g id="matplotlib.axis_3">
+    <g id="xtick_8">
+     <g id="line2d_25">
+      <path d="M 87.075388 390.04 
+L 87.075388 238.68 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_26">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="87.075388" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_16">
+      <!-- 0 -->
+      <g transform="translate(83.894138 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_9">
+     <g id="line2d_27">
+      <path d="M 184.869428 390.04 
+L 184.869428 238.68 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_28">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="184.869428" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_17">
+      <!-- 50000 -->
+      <g transform="translate(168.963178 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-35"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_10">
+     <g id="line2d_29">
+      <path d="M 282.663469 390.04 
+L 282.663469 238.68 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_30">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="282.663469" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_18">
+      <!-- 100000 -->
+      <g transform="translate(263.575969 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-31"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_11">
+     <g id="line2d_31">
+      <path d="M 380.457509 390.04 
+L 380.457509 238.68 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_32">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="380.457509" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_19">
+      <!-- 150000 -->
+      <g transform="translate(361.370009 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-31"/>
+       <use xlink:href="#DejaVuSans-35" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_12">
+     <g id="line2d_33">
+      <path d="M 478.25155 390.04 
+L 478.25155 238.68 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_34">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="478.25155" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_20">
+      <!-- 200000 -->
+      <g transform="translate(459.16405 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-32"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_13">
+     <g id="line2d_35">
+      <path d="M 576.04559 390.04 
+L 576.04559 238.68 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_36">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="576.04559" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_21">
+      <!-- 250000 -->
+      <g transform="translate(556.95809 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-32"/>
+       <use xlink:href="#DejaVuSans-35" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_14">
+     <g id="line2d_37">
+      <path d="M 673.839631 390.04 
+L 673.839631 238.68 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_38">
+      <g>
+       <use xlink:href="#ma65ff080c7" x="673.839631" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_22">
+      <!-- 300000 -->
+      <g transform="translate(654.752131 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-33"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="text_23">
+     <!-- time in ns -->
+     <g transform="translate(367.289531 418.316562)scale(0.1 -0.1)">
+      <use xlink:href="#DejaVuSans-74"/>
+      <use xlink:href="#DejaVuSans-69" x="39.208984"/>
+      <use xlink:href="#DejaVuSans-6d" x="66.992188"/>
+      <use xlink:href="#DejaVuSans-65" x="164.404297"/>
+      <use xlink:href="#DejaVuSans-20" x="225.927734"/>
+      <use xlink:href="#DejaVuSans-69" x="257.714844"/>
+      <use xlink:href="#DejaVuSans-6e" x="285.498047"/>
+      <use xlink:href="#DejaVuSans-20" x="348.876953"/>
+      <use xlink:href="#DejaVuSans-6e" x="380.664062"/>
+      <use xlink:href="#DejaVuSans-73" x="444.042969"/>
+     </g>
+    </g>
+   </g>
+   <g id="matplotlib.axis_4">
+    <g id="ytick_6">
+     <g id="line2d_39">
+      <path d="M 74.99 364.200927 
+L 709.2 364.200927 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_40">
+      <g>
+       <use xlink:href="#m035fd2fdf7" x="74.99" y="364.200927" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_24">
+      <!-- −0.0001 -->
+      <g transform="translate(24.619687 368.000146)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-2212"/>
+       <use xlink:href="#DejaVuSans-30" x="83.789062"/>
+       <use xlink:href="#DejaVuSans-2e" x="147.412109"/>
+       <use xlink:href="#DejaVuSans-30" x="179.199219"/>
+       <use xlink:href="#DejaVuSans-30" x="242.822266"/>
+       <use xlink:href="#DejaVuSans-30" x="306.445312"/>
+       <use xlink:href="#DejaVuSans-31" x="370.068359"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_7">
+     <g id="line2d_41">
+      <path d="M 74.99 328.148763 
+L 709.2 328.148763 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_42">
+      <g>
+       <use xlink:href="#m035fd2fdf7" x="74.99" y="328.148763" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_25">
+      <!-- 0.0000 -->
+      <g transform="translate(32.999375 331.947982)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="95.410156"/>
+       <use xlink:href="#DejaVuSans-30" x="159.033203"/>
+       <use xlink:href="#DejaVuSans-30" x="222.65625"/>
+       <use xlink:href="#DejaVuSans-30" x="286.279297"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_8">
+     <g id="line2d_43">
+      <path d="M 74.99 292.096599 
+L 709.2 292.096599 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_44">
+      <g>
+       <use xlink:href="#m035fd2fdf7" x="74.99" y="292.096599" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_26">
+      <!-- 0.0001 -->
+      <g transform="translate(32.999375 295.895818)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="95.410156"/>
+       <use xlink:href="#DejaVuSans-30" x="159.033203"/>
+       <use xlink:href="#DejaVuSans-30" x="222.65625"/>
+       <use xlink:href="#DejaVuSans-31" x="286.279297"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_9">
+     <g id="line2d_45">
+      <path d="M 74.99 256.044435 
+L 709.2 256.044435 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_46">
+      <g>
+       <use xlink:href="#m035fd2fdf7" x="74.99" y="256.044435" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_27">
+      <!-- 0.0002 -->
+      <g transform="translate(32.999375 259.843654)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="95.410156"/>
+       <use xlink:href="#DejaVuSans-30" x="159.033203"/>
+       <use xlink:href="#DejaVuSans-30" x="222.65625"/>
+       <use xlink:href="#DejaVuSans-32" x="286.279297"/>
+      </g>
+     </g>
+    </g>
+    <g id="text_28">
+     <!-- Error -->
+     <g transform="translate(18.54 326.545156)rotate(-90)scale(0.1 -0.1)">
+      <defs>
+       <path id="DejaVuSans-45" d="M 628 4666 
+L 3578 4666 
+L 3578 4134 
+L 1259 4134 
+L 1259 2753 
+L 3481 2753 
+L 3481 2222 
+L 1259 2222 
+L 1259 531 
+L 3634 531 
+L 3634 0 
+L 628 0 
+L 628 4666 
+z
+" transform="scale(0.015625)"/>
+      </defs>
+      <use xlink:href="#DejaVuSans-45"/>
+      <use xlink:href="#DejaVuSans-72" x="63.183594"/>
+      <use xlink:href="#DejaVuSans-72" x="102.546875"/>
+      <use xlink:href="#DejaVuSans-6f" x="141.410156"/>
+      <use xlink:href="#DejaVuSans-72" x="202.591797"/>
+     </g>
+    </g>
+   </g>
+   <g id="line2d_47">
+    <path d="M 103.817727 383.16 
+L 115.122718 340.834409 
+L 126.427709 338.870839 
+L 137.732701 367.102849 
+L 149.037692 380.170385 
+L 160.342683 367.567795 
+L 171.647674 318.728996 
+L 182.952665 308.559068 
+L 194.257656 329.207795 
+L 205.562647 346.565442 
+L 216.867638 333.82513 
+L 228.172629 337.031946 
+L 239.47762 330.493602 
+L 250.782611 346.09223 
+L 262.087602 348.347553 
+L 273.392594 333.805283 
+L 284.697585 374.187902 
+L 296.002576 372.273547 
+L 307.307567 361.203822 
+L 318.612558 340.563855 
+L 329.917549 292.93206 
+L 341.22254 270.959175 
+L 352.527531 285.518372 
+L 363.832522 330.605228 
+L 375.137513 286.556658 
+L 386.442504 268.336366 
+L 397.747496 340.806273 
+L 409.052487 299.772087 
+L 420.357478 371.626297 
+L 431.662469 367.235597 
+L 442.96746 348.847052 
+L 454.272451 312.649236 
+L 465.577442 350.428249 
+L 476.882433 360.76228 
+L 488.187424 355.092249 
+L 499.492415 313.567761 
+L 510.797406 316.916335 
+L 522.102398 332.794068 
+L 533.407389 316.821851 
+L 544.71238 320.296631 
+L 556.017371 307.551805 
+L 567.322362 299.109193 
+L 578.627353 296.009471 
+L 589.932344 282.361524 
+L 601.237335 245.56 
+L 612.542326 329.638798 
+L 623.847317 307.521644 
+L 635.152308 319.4794 
+L 646.457299 352.675944 
+L 657.762291 310.065063 
+L 669.067282 299.936944 
+L 680.372273 309.808278 
+" clip-path="url(#pec72612a62)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
+   </g>
+   <g id="patch_9">
+    <path d="M 74.99 390.04 
+L 74.99 238.68 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_10">
+    <path d="M 709.2 390.04 
+L 709.2 238.68 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_11">
+    <path d="M 74.99 390.04 
+L 709.2 390.04 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_12">
+    <path d="M 74.99 238.68 
+L 709.2 238.68 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="legend_2">
+    <g id="patch_13">
+     <path d="M 81.99 261.63625 
+L 227.160313 261.63625 
+Q 229.160313 261.63625 229.160313 259.63625 
+L 229.160313 245.68 
+Q 229.160313 243.68 227.160313 243.68 
+L 81.99 243.68 
+Q 79.99 243.68 79.99 245.68 
+L 79.99 259.63625 
+Q 79.99 261.63625 81.99 261.63625 
+z
+" style="fill: #ffffff; opacity: 0.8; stroke: #cccccc; stroke-linejoin: miter"/>
+    </g>
+    <g id="line2d_48">
+     <path d="M 83.99 251.778438 
+L 93.99 251.778438 
+L 103.99 251.778438 
+" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
+    </g>
+    <g id="text_29">
+     <!-- error = y_data - y_calc -->
+     <g transform="translate(111.99 255.278438)scale(0.1 -0.1)">
+      <defs>
+       <path id="DejaVuSans-3d" d="M 678 2906 
+L 4684 2906 
+L 4684 2381 
+L 678 2381 
+L 678 2906 
+z
+M 678 1631 
+L 4684 1631 
+L 4684 1100 
+L 678 1100 
+L 678 1631 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-79" d="M 2059 -325 
+Q 1816 -950 1584 -1140 
+Q 1353 -1331 966 -1331 
+L 506 -1331 
+L 506 -850 
+L 844 -850 
+Q 1081 -850 1212 -737 
+Q 1344 -625 1503 -206 
+L 1606 56 
+L 191 3500 
+L 800 3500 
+L 1894 763 
+L 2988 3500 
+L 3597 3500 
+L 2059 -325 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-5f" d="M 3263 -1063 
+L 3263 -1509 
+L -63 -1509 
+L -63 -1063 
+L 3263 -1063 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-2d" d="M 313 2009 
+L 1997 2009 
+L 1997 1497 
+L 313 1497 
+L 313 2009 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-63" d="M 3122 3366 
+L 3122 2828 
+Q 2878 2963 2633 3030 
+Q 2388 3097 2138 3097 
+Q 1578 3097 1268 2742 
+Q 959 2388 959 1747 
+Q 959 1106 1268 751 
+Q 1578 397 2138 397 
+Q 2388 397 2633 464 
+Q 2878 531 3122 666 
+L 3122 134 
+Q 2881 22 2623 -34 
+Q 2366 -91 2075 -91 
+Q 1284 -91 818 406 
+Q 353 903 353 1747 
+Q 353 2603 823 3093 
+Q 1294 3584 2113 3584 
+Q 2378 3584 2631 3529 
+Q 2884 3475 3122 3366 
+z
+" transform="scale(0.015625)"/>
+      </defs>
+      <use xlink:href="#DejaVuSans-65"/>
+      <use xlink:href="#DejaVuSans-72" x="61.523438"/>
+      <use xlink:href="#DejaVuSans-72" x="100.886719"/>
+      <use xlink:href="#DejaVuSans-6f" x="139.75"/>
+      <use xlink:href="#DejaVuSans-72" x="200.931641"/>
+      <use xlink:href="#DejaVuSans-20" x="242.044922"/>
+      <use xlink:href="#DejaVuSans-3d" x="273.832031"/>
+      <use xlink:href="#DejaVuSans-20" x="357.621094"/>
+      <use xlink:href="#DejaVuSans-79" x="389.408203"/>
+      <use xlink:href="#DejaVuSans-5f" x="448.587891"/>
+      <use xlink:href="#DejaVuSans-64" x="498.587891"/>
+      <use xlink:href="#DejaVuSans-61" x="562.064453"/>
+      <use xlink:href="#DejaVuSans-74" x="623.34375"/>
+      <use xlink:href="#DejaVuSans-61" x="662.552734"/>
+      <use xlink:href="#DejaVuSans-20" x="723.832031"/>
+      <use xlink:href="#DejaVuSans-2d" x="755.619141"/>
+      <use xlink:href="#DejaVuSans-20" x="791.703125"/>
+      <use xlink:href="#DejaVuSans-79" x="823.490234"/>
+      <use xlink:href="#DejaVuSans-5f" x="882.669922"/>
+      <use xlink:href="#DejaVuSans-63" x="932.669922"/>
+      <use xlink:href="#DejaVuSans-61" x="987.650391"/>
+      <use xlink:href="#DejaVuSans-6c" x="1048.929688"/>
+      <use xlink:href="#DejaVuSans-63" x="1076.712891"/>
+     </g>
+    </g>
+   </g>
+  </g>
+  <g id="text_30">
+   <!-- Stimulus: Sine wave generator, Driver: SPI module -->
+   <g transform="translate(56.145 26.87625)scale(0.24 -0.24)">
+    <defs>
+     <path id="DejaVuSans-3a" d="M 750 794 
+L 1409 794 
+L 1409 0 
+L 750 0 
+L 750 794 
+z
+M 750 3309 
+L 1409 3309 
+L 1409 2516 
+L 750 2516 
+L 750 3309 
+z
+" transform="scale(0.015625)"/>
+     <path id="DejaVuSans-77" d="M 269 3500 
+L 844 3500 
+L 1563 769 
+L 2278 3500 
+L 2956 3500 
+L 3675 769 
+L 4391 3500 
+L 4966 3500 
+L 4050 0 
+L 3372 0 
+L 2619 2869 
+L 1863 0 
+L 1184 0 
+L 269 3500 
+z
+" transform="scale(0.015625)"/>
+     <path id="DejaVuSans-76" d="M 191 3500 
+L 800 3500 
+L 1894 563 
+L 2988 3500 
+L 3597 3500 
+L 2284 0 
+L 1503 0 
+L 191 3500 
+z
+" transform="scale(0.015625)"/>
+     <path id="DejaVuSans-67" d="M 2906 1791 
+Q 2906 2416 2648 2759 
+Q 2391 3103 1925 3103 
+Q 1463 3103 1205 2759 
+Q 947 2416 947 1791 
+Q 947 1169 1205 825 
+Q 1463 481 1925 481 
+Q 2391 481 2648 825 
+Q 2906 1169 2906 1791 
+z
+M 3481 434 
+Q 3481 -459 3084 -895 
+Q 2688 -1331 1869 -1331 
+Q 1566 -1331 1297 -1286 
+Q 1028 -1241 775 -1147 
+L 775 -588 
+Q 1028 -725 1275 -790 
+Q 1522 -856 1778 -856 
+Q 2344 -856 2625 -561 
+Q 2906 -266 2906 331 
+L 2906 616 
+Q 2728 306 2450 153 
+Q 2172 0 1784 0 
+Q 1141 0 747 490 
+Q 353 981 353 1791 
+Q 353 2603 747 3093 
+Q 1141 3584 1784 3584 
+Q 2172 3584 2450 3431 
+Q 2728 3278 2906 2969 
+L 2906 3500 
+L 3481 3500 
+L 3481 434 
+z
+" transform="scale(0.015625)"/>
+     <path id="DejaVuSans-2c" d="M 750 794 
+L 1409 794 
+L 1409 256 
+L 897 -744 
+L 494 -744 
+L 750 256 
+L 750 794 
+z
+" transform="scale(0.015625)"/>
+     <path id="DejaVuSans-44" d="M 1259 4147 
+L 1259 519 
+L 2022 519 
+Q 2988 519 3436 956 
+Q 3884 1394 3884 2338 
+Q 3884 3275 3436 3711 
+Q 2988 4147 2022 4147 
+L 1259 4147 
+z
+M 628 4666 
+L 1925 4666 
+Q 3281 4666 3915 4102 
+Q 4550 3538 4550 2338 
+Q 4550 1131 3912 565 
+Q 3275 0 1925 0 
+L 628 0 
+L 628 4666 
+z
+" transform="scale(0.015625)"/>
+    </defs>
+    <use xlink:href="#DejaVuSans-53"/>
+    <use xlink:href="#DejaVuSans-74" x="63.476562"/>
+    <use xlink:href="#DejaVuSans-69" x="102.685547"/>
+    <use xlink:href="#DejaVuSans-6d" x="130.46875"/>
+    <use xlink:href="#DejaVuSans-75" x="227.880859"/>
+    <use xlink:href="#DejaVuSans-6c" x="291.259766"/>
+    <use xlink:href="#DejaVuSans-75" x="319.042969"/>
+    <use xlink:href="#DejaVuSans-73" x="382.421875"/>
+    <use xlink:href="#DejaVuSans-3a" x="434.521484"/>
+    <use xlink:href="#DejaVuSans-20" x="468.212891"/>
+    <use xlink:href="#DejaVuSans-53" x="500"/>
+    <use xlink:href="#DejaVuSans-69" x="563.476562"/>
+    <use xlink:href="#DejaVuSans-6e" x="591.259766"/>
+    <use xlink:href="#DejaVuSans-65" x="654.638672"/>
+    <use xlink:href="#DejaVuSans-20" x="716.162109"/>
+    <use xlink:href="#DejaVuSans-77" x="747.949219"/>
+    <use xlink:href="#DejaVuSans-61" x="829.736328"/>
+    <use xlink:href="#DejaVuSans-76" x="891.015625"/>
+    <use xlink:href="#DejaVuSans-65" x="950.195312"/>
+    <use xlink:href="#DejaVuSans-20" x="1011.71875"/>
+    <use xlink:href="#DejaVuSans-67" x="1043.505859"/>
+    <use xlink:href="#DejaVuSans-65" x="1106.982422"/>
+    <use xlink:href="#DejaVuSans-6e" x="1168.505859"/>
+    <use xlink:href="#DejaVuSans-65" x="1231.884766"/>
+    <use xlink:href="#DejaVuSans-72" x="1293.408203"/>
+    <use xlink:href="#DejaVuSans-61" x="1334.521484"/>
+    <use xlink:href="#DejaVuSans-74" x="1395.800781"/>
+    <use xlink:href="#DejaVuSans-6f" x="1435.009766"/>
+    <use xlink:href="#DejaVuSans-72" x="1496.191406"/>
+    <use xlink:href="#DejaVuSans-2c" x="1537.304688"/>
+    <use xlink:href="#DejaVuSans-20" x="1569.091797"/>
+    <use xlink:href="#DejaVuSans-44" x="1600.878906"/>
+    <use xlink:href="#DejaVuSans-72" x="1677.880859"/>
+    <use xlink:href="#DejaVuSans-69" x="1718.994141"/>
+    <use xlink:href="#DejaVuSans-76" x="1746.777344"/>
+    <use xlink:href="#DejaVuSans-65" x="1805.957031"/>
+    <use xlink:href="#DejaVuSans-72" x="1867.480469"/>
+    <use xlink:href="#DejaVuSans-3a" x="1906.84375"/>
+    <use xlink:href="#DejaVuSans-20" x="1940.535156"/>
+    <use xlink:href="#DejaVuSans-53" x="1972.322266"/>
+    <use xlink:href="#DejaVuSans-50" x="2035.798828"/>
+    <use xlink:href="#DejaVuSans-49" x="2096.101562"/>
+    <use xlink:href="#DejaVuSans-20" x="2125.59375"/>
+    <use xlink:href="#DejaVuSans-6d" x="2157.380859"/>
+    <use xlink:href="#DejaVuSans-6f" x="2254.792969"/>
+    <use xlink:href="#DejaVuSans-64" x="2315.974609"/>
+    <use xlink:href="#DejaVuSans-75" x="2379.451172"/>
+    <use xlink:href="#DejaVuSans-6c" x="2442.830078"/>
+    <use xlink:href="#DejaVuSans-65" x="2470.613281"/>
+   </g>
+  </g>
+ </g>
+ <defs>
+  <clipPath id="p20292001c7">
+   <rect x="74.99" y="45.36" width="634.21" height="151.36"/>
+  </clipPath>
+  <clipPath id="pec72612a62">
+   <rect x="74.99" y="238.68" width="634.21" height="151.36"/>
+  </clipPath>
+ </defs>
+</svg>
diff --git a/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=4096_gain=16384_off=0.png b/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=4096_gain=16384_off=0.png
new file mode 100644
index 0000000..f02a980
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=4096_gain=16384_off=0.png
Binary files differ
diff --git a/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=4096_gain=16384_off=0.svg b/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=4096_gain=16384_off=0.svg
new file mode 100644
index 0000000..8ccbf59
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_top/sim/output_inc=4096_gain=16384_off=0.svg
@@ -0,0 +1,1595 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="720pt" height="432pt" viewBox="0 0 720 432" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <metadata>
+  <rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+   <cc:Work>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:date>2022-07-13T12:28:25.653301</dc:date>
+    <dc:format>image/svg+xml</dc:format>
+    <dc:creator>
+     <cc:Agent>
+      <dc:title>Matplotlib v3.5.2, https://matplotlib.org/</dc:title>
+     </cc:Agent>
+    </dc:creator>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <defs>
+  <style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
+ </defs>
+ <g id="figure_1">
+  <g id="patch_1">
+   <path d="M 0 432 
+L 720 432 
+L 720 0 
+L 0 0 
+z
+" style="fill: #ffffff"/>
+  </g>
+  <g id="axes_1">
+   <g id="patch_2">
+    <path d="M 74.99 196.72 
+L 709.2 196.72 
+L 709.2 45.36 
+L 74.99 45.36 
+z
+" style="fill: #ffffff"/>
+   </g>
+   <g id="PathCollection_1">
+    <defs>
+     <path id="mae39fc909d" d="M 0 3 
+C 0.795609 3 1.55874 2.683901 2.12132 2.12132 
+C 2.683901 1.55874 3 0.795609 3 0 
+C 3 -0.795609 2.683901 -1.55874 2.12132 -2.12132 
+C 1.55874 -2.683901 0.795609 -3 0 -3 
+C -0.795609 -3 -1.55874 -2.683901 -2.12132 -2.12132 
+C -2.683901 -1.55874 -3 -0.795609 -3 0 
+C -3 0.795609 -2.683901 1.55874 -2.12132 2.12132 
+C -1.55874 2.683901 -0.795609 3 0 3 
+z
+" style="stroke: #1f77b4"/>
+    </defs>
+    <g clip-path="url(#p9829768d15)">
+     <use xlink:href="#mae39fc909d" x="103.817727" y="121.050499" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="139.852386" y="94.694995" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="175.887045" y="72.384574" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="211.921705" y="57.478765" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="247.956364" y="52.24" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="283.991023" y="57.482965" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="320.025682" y="72.394023" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="356.060341" y="94.705494" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="392.095" y="121.029501" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="428.129659" y="147.385005" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="464.164318" y="169.695426" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="500.198977" y="184.601235" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="536.233636" y="189.84" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="572.268295" y="184.597035" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="608.302955" y="169.685977" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="644.337614" y="147.374506" style="fill: #1f77b4; stroke: #1f77b4"/>
+     <use xlink:href="#mae39fc909d" x="680.372273" y="121.050499" style="fill: #1f77b4; stroke: #1f77b4"/>
+    </g>
+   </g>
+   <g id="matplotlib.axis_1">
+    <g id="xtick_1">
+     <g id="line2d_1">
+      <path d="M 175.138921 196.72 
+L 175.138921 45.36 
+" clip-path="url(#p9829768d15)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_2">
+      <defs>
+       <path id="m4b97302bc4" d="M 0 0 
+L 0 3.5 
+" style="stroke: #000000; stroke-width: 0.8"/>
+      </defs>
+      <g>
+       <use xlink:href="#m4b97302bc4" x="175.138921" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_1">
+      <!-- 20000 -->
+      <g transform="translate(159.232671 211.318438)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-32" d="M 1228 531 
+L 3431 531 
+L 3431 0 
+L 469 0 
+L 469 531 
+Q 828 903 1448 1529 
+Q 2069 2156 2228 2338 
+Q 2531 2678 2651 2914 
+Q 2772 3150 2772 3378 
+Q 2772 3750 2511 3984 
+Q 2250 4219 1831 4219 
+Q 1534 4219 1204 4116 
+Q 875 4013 500 3803 
+L 500 4441 
+Q 881 4594 1212 4672 
+Q 1544 4750 1819 4750 
+Q 2544 4750 2975 4387 
+Q 3406 4025 3406 3419 
+Q 3406 3131 3298 2873 
+Q 3191 2616 2906 2266 
+Q 2828 2175 2409 1742 
+Q 1991 1309 1228 531 
+z
+" transform="scale(0.015625)"/>
+        <path id="DejaVuSans-30" d="M 2034 4250 
+Q 1547 4250 1301 3770 
+Q 1056 3291 1056 2328 
+Q 1056 1369 1301 889 
+Q 1547 409 2034 409 
+Q 2525 409 2770 889 
+Q 3016 1369 3016 2328 
+Q 3016 3291 2770 3770 
+Q 2525 4250 2034 4250 
+z
+M 2034 4750 
+Q 2819 4750 3233 4129 
+Q 3647 3509 3647 2328 
+Q 3647 1150 3233 529 
+Q 2819 -91 2034 -91 
+Q 1250 -91 836 529 
+Q 422 1150 422 2328 
+Q 422 3509 836 4129 
+Q 1250 4750 2034 4750 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-32"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_2">
+     <g id="line2d_3">
+      <path d="M 299.826323 196.72 
+L 299.826323 45.36 
+" clip-path="url(#p9829768d15)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_4">
+      <g>
+       <use xlink:href="#m4b97302bc4" x="299.826323" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_2">
+      <!-- 40000 -->
+      <g transform="translate(283.920073 211.318438)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-34" d="M 2419 4116 
+L 825 1625 
+L 2419 1625 
+L 2419 4116 
+z
+M 2253 4666 
+L 3047 4666 
+L 3047 1625 
+L 3713 1625 
+L 3713 1100 
+L 3047 1100 
+L 3047 0 
+L 2419 0 
+L 2419 1100 
+L 313 1100 
+L 313 1709 
+L 2253 4666 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-34"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_3">
+     <g id="line2d_5">
+      <path d="M 424.513724 196.72 
+L 424.513724 45.36 
+" clip-path="url(#p9829768d15)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_6">
+      <g>
+       <use xlink:href="#m4b97302bc4" x="424.513724" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_3">
+      <!-- 60000 -->
+      <g transform="translate(408.607474 211.318438)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-36" d="M 2113 2584 
+Q 1688 2584 1439 2293 
+Q 1191 2003 1191 1497 
+Q 1191 994 1439 701 
+Q 1688 409 2113 409 
+Q 2538 409 2786 701 
+Q 3034 994 3034 1497 
+Q 3034 2003 2786 2293 
+Q 2538 2584 2113 2584 
+z
+M 3366 4563 
+L 3366 3988 
+Q 3128 4100 2886 4159 
+Q 2644 4219 2406 4219 
+Q 1781 4219 1451 3797 
+Q 1122 3375 1075 2522 
+Q 1259 2794 1537 2939 
+Q 1816 3084 2150 3084 
+Q 2853 3084 3261 2657 
+Q 3669 2231 3669 1497 
+Q 3669 778 3244 343 
+Q 2819 -91 2113 -91 
+Q 1303 -91 875 529 
+Q 447 1150 447 2328 
+Q 447 3434 972 4092 
+Q 1497 4750 2381 4750 
+Q 2619 4750 2861 4703 
+Q 3103 4656 3366 4563 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-36"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_4">
+     <g id="line2d_7">
+      <path d="M 549.201126 196.72 
+L 549.201126 45.36 
+" clip-path="url(#p9829768d15)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_8">
+      <g>
+       <use xlink:href="#m4b97302bc4" x="549.201126" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_4">
+      <!-- 80000 -->
+      <g transform="translate(533.294876 211.318438)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-38" d="M 2034 2216 
+Q 1584 2216 1326 1975 
+Q 1069 1734 1069 1313 
+Q 1069 891 1326 650 
+Q 1584 409 2034 409 
+Q 2484 409 2743 651 
+Q 3003 894 3003 1313 
+Q 3003 1734 2745 1975 
+Q 2488 2216 2034 2216 
+z
+M 1403 2484 
+Q 997 2584 770 2862 
+Q 544 3141 544 3541 
+Q 544 4100 942 4425 
+Q 1341 4750 2034 4750 
+Q 2731 4750 3128 4425 
+Q 3525 4100 3525 3541 
+Q 3525 3141 3298 2862 
+Q 3072 2584 2669 2484 
+Q 3125 2378 3379 2068 
+Q 3634 1759 3634 1313 
+Q 3634 634 3220 271 
+Q 2806 -91 2034 -91 
+Q 1263 -91 848 271 
+Q 434 634 434 1313 
+Q 434 1759 690 2068 
+Q 947 2378 1403 2484 
+z
+M 1172 3481 
+Q 1172 3119 1398 2916 
+Q 1625 2713 2034 2713 
+Q 2441 2713 2670 2916 
+Q 2900 3119 2900 3481 
+Q 2900 3844 2670 4047 
+Q 2441 4250 2034 4250 
+Q 1625 4250 1398 4047 
+Q 1172 3844 1172 3481 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-38"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_5">
+     <g id="line2d_9">
+      <path d="M 673.888528 196.72 
+L 673.888528 45.36 
+" clip-path="url(#p9829768d15)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_10">
+      <g>
+       <use xlink:href="#m4b97302bc4" x="673.888528" y="196.72" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_5">
+      <!-- 100000 -->
+      <g transform="translate(654.801028 211.318438)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-31" d="M 794 531 
+L 1825 531 
+L 1825 4091 
+L 703 3866 
+L 703 4441 
+L 1819 4666 
+L 2450 4666 
+L 2450 531 
+L 3481 531 
+L 3481 0 
+L 794 0 
+L 794 531 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-31"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="text_6">
+     <!-- time in ns -->
+     <g transform="translate(367.289531 224.996563)scale(0.1 -0.1)">
+      <defs>
+       <path id="DejaVuSans-74" d="M 1172 4494 
+L 1172 3500 
+L 2356 3500 
+L 2356 3053 
+L 1172 3053 
+L 1172 1153 
+Q 1172 725 1289 603 
+Q 1406 481 1766 481 
+L 2356 481 
+L 2356 0 
+L 1766 0 
+Q 1100 0 847 248 
+Q 594 497 594 1153 
+L 594 3053 
+L 172 3053 
+L 172 3500 
+L 594 3500 
+L 594 4494 
+L 1172 4494 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-69" d="M 603 3500 
+L 1178 3500 
+L 1178 0 
+L 603 0 
+L 603 3500 
+z
+M 603 4863 
+L 1178 4863 
+L 1178 4134 
+L 603 4134 
+L 603 4863 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-6d" d="M 3328 2828 
+Q 3544 3216 3844 3400 
+Q 4144 3584 4550 3584 
+Q 5097 3584 5394 3201 
+Q 5691 2819 5691 2113 
+L 5691 0 
+L 5113 0 
+L 5113 2094 
+Q 5113 2597 4934 2840 
+Q 4756 3084 4391 3084 
+Q 3944 3084 3684 2787 
+Q 3425 2491 3425 1978 
+L 3425 0 
+L 2847 0 
+L 2847 2094 
+Q 2847 2600 2669 2842 
+Q 2491 3084 2119 3084 
+Q 1678 3084 1418 2786 
+Q 1159 2488 1159 1978 
+L 1159 0 
+L 581 0 
+L 581 3500 
+L 1159 3500 
+L 1159 2956 
+Q 1356 3278 1631 3431 
+Q 1906 3584 2284 3584 
+Q 2666 3584 2933 3390 
+Q 3200 3197 3328 2828 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-65" d="M 3597 1894 
+L 3597 1613 
+L 953 1613 
+Q 991 1019 1311 708 
+Q 1631 397 2203 397 
+Q 2534 397 2845 478 
+Q 3156 559 3463 722 
+L 3463 178 
+Q 3153 47 2828 -22 
+Q 2503 -91 2169 -91 
+Q 1331 -91 842 396 
+Q 353 884 353 1716 
+Q 353 2575 817 3079 
+Q 1281 3584 2069 3584 
+Q 2775 3584 3186 3129 
+Q 3597 2675 3597 1894 
+z
+M 3022 2063 
+Q 3016 2534 2758 2815 
+Q 2500 3097 2075 3097 
+Q 1594 3097 1305 2825 
+Q 1016 2553 972 2059 
+L 3022 2063 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-20" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-6e" d="M 3513 2113 
+L 3513 0 
+L 2938 0 
+L 2938 2094 
+Q 2938 2591 2744 2837 
+Q 2550 3084 2163 3084 
+Q 1697 3084 1428 2787 
+Q 1159 2491 1159 1978 
+L 1159 0 
+L 581 0 
+L 581 3500 
+L 1159 3500 
+L 1159 2956 
+Q 1366 3272 1645 3428 
+Q 1925 3584 2291 3584 
+Q 2894 3584 3203 3211 
+Q 3513 2838 3513 2113 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-73" d="M 2834 3397 
+L 2834 2853 
+Q 2591 2978 2328 3040 
+Q 2066 3103 1784 3103 
+Q 1356 3103 1142 2972 
+Q 928 2841 928 2578 
+Q 928 2378 1081 2264 
+Q 1234 2150 1697 2047 
+L 1894 2003 
+Q 2506 1872 2764 1633 
+Q 3022 1394 3022 966 
+Q 3022 478 2636 193 
+Q 2250 -91 1575 -91 
+Q 1294 -91 989 -36 
+Q 684 19 347 128 
+L 347 722 
+Q 666 556 975 473 
+Q 1284 391 1588 391 
+Q 1994 391 2212 530 
+Q 2431 669 2431 922 
+Q 2431 1156 2273 1281 
+Q 2116 1406 1581 1522 
+L 1381 1569 
+Q 847 1681 609 1914 
+Q 372 2147 372 2553 
+Q 372 3047 722 3315 
+Q 1072 3584 1716 3584 
+Q 2034 3584 2315 3537 
+Q 2597 3491 2834 3397 
+z
+" transform="scale(0.015625)"/>
+      </defs>
+      <use xlink:href="#DejaVuSans-74"/>
+      <use xlink:href="#DejaVuSans-69" x="39.208984"/>
+      <use xlink:href="#DejaVuSans-6d" x="66.992188"/>
+      <use xlink:href="#DejaVuSans-65" x="164.404297"/>
+      <use xlink:href="#DejaVuSans-20" x="225.927734"/>
+      <use xlink:href="#DejaVuSans-69" x="257.714844"/>
+      <use xlink:href="#DejaVuSans-6e" x="285.498047"/>
+      <use xlink:href="#DejaVuSans-20" x="348.876953"/>
+      <use xlink:href="#DejaVuSans-6e" x="380.664062"/>
+      <use xlink:href="#DejaVuSans-73" x="444.042969"/>
+     </g>
+    </g>
+   </g>
+   <g id="matplotlib.axis_2">
+    <g id="ytick_1">
+     <g id="line2d_11">
+      <path d="M 74.99 189.84315 
+L 709.2 189.84315 
+" clip-path="url(#p9829768d15)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_12">
+      <defs>
+       <path id="m95f006266e" d="M 0 0 
+L -3.5 0 
+" style="stroke: #000000; stroke-width: 0.8"/>
+      </defs>
+      <g>
+       <use xlink:href="#m95f006266e" x="74.99" y="189.84315" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_7">
+      <!-- −1.0 -->
+      <g transform="translate(43.707187 193.642368)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-2212" d="M 678 2272 
+L 4684 2272 
+L 4684 1741 
+L 678 1741 
+L 678 2272 
+z
+" transform="scale(0.015625)"/>
+        <path id="DejaVuSans-2e" d="M 684 794 
+L 1344 794 
+L 1344 0 
+L 684 0 
+L 684 794 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-2212"/>
+       <use xlink:href="#DejaVuSans-31" x="83.789062"/>
+       <use xlink:href="#DejaVuSans-2e" x="147.412109"/>
+       <use xlink:href="#DejaVuSans-30" x="179.199219"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_2">
+     <g id="line2d_13">
+      <path d="M 74.99 155.441575 
+L 709.2 155.441575 
+" clip-path="url(#p9829768d15)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_14">
+      <g>
+       <use xlink:href="#m95f006266e" x="74.99" y="155.441575" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_8">
+      <!-- −0.5 -->
+      <g transform="translate(43.707187 159.240794)scale(0.1 -0.1)">
+       <defs>
+        <path id="DejaVuSans-35" d="M 691 4666 
+L 3169 4666 
+L 3169 4134 
+L 1269 4134 
+L 1269 2991 
+Q 1406 3038 1543 3061 
+Q 1681 3084 1819 3084 
+Q 2600 3084 3056 2656 
+Q 3513 2228 3513 1497 
+Q 3513 744 3044 326 
+Q 2575 -91 1722 -91 
+Q 1428 -91 1123 -41 
+Q 819 9 494 109 
+L 494 744 
+Q 775 591 1075 516 
+Q 1375 441 1709 441 
+Q 2250 441 2565 725 
+Q 2881 1009 2881 1497 
+Q 2881 1984 2565 2268 
+Q 2250 2553 1709 2553 
+Q 1456 2553 1204 2497 
+Q 953 2441 691 2322 
+L 691 4666 
+z
+" transform="scale(0.015625)"/>
+       </defs>
+       <use xlink:href="#DejaVuSans-2212"/>
+       <use xlink:href="#DejaVuSans-30" x="83.789062"/>
+       <use xlink:href="#DejaVuSans-2e" x="147.412109"/>
+       <use xlink:href="#DejaVuSans-35" x="179.199219"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_3">
+     <g id="line2d_15">
+      <path d="M 74.99 121.04 
+L 709.2 121.04 
+" clip-path="url(#p9829768d15)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_16">
+      <g>
+       <use xlink:href="#m95f006266e" x="74.99" y="121.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_9">
+      <!-- 0.0 -->
+      <g transform="translate(52.086875 124.839219)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="95.410156"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_4">
+     <g id="line2d_17">
+      <path d="M 74.99 86.638425 
+L 709.2 86.638425 
+" clip-path="url(#p9829768d15)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_18">
+      <g>
+       <use xlink:href="#m95f006266e" x="74.99" y="86.638425" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_10">
+      <!-- 0.5 -->
+      <g transform="translate(52.086875 90.437644)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-35" x="95.410156"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_5">
+     <g id="line2d_19">
+      <path d="M 74.99 52.23685 
+L 709.2 52.23685 
+" clip-path="url(#p9829768d15)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_20">
+      <g>
+       <use xlink:href="#m95f006266e" x="74.99" y="52.23685" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_11">
+      <!-- 1.0 -->
+      <g transform="translate(52.086875 56.036069)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-31"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="95.410156"/>
+      </g>
+     </g>
+    </g>
+    <g id="text_12">
+     <!-- Value -->
+     <g transform="translate(37.6275 134.77125)rotate(-90)scale(0.1 -0.1)">
+      <defs>
+       <path id="DejaVuSans-56" d="M 1831 0 
+L 50 4666 
+L 709 4666 
+L 2188 738 
+L 3669 4666 
+L 4325 4666 
+L 2547 0 
+L 1831 0 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-61" d="M 2194 1759 
+Q 1497 1759 1228 1600 
+Q 959 1441 959 1056 
+Q 959 750 1161 570 
+Q 1363 391 1709 391 
+Q 2188 391 2477 730 
+Q 2766 1069 2766 1631 
+L 2766 1759 
+L 2194 1759 
+z
+M 3341 1997 
+L 3341 0 
+L 2766 0 
+L 2766 531 
+Q 2569 213 2275 61 
+Q 1981 -91 1556 -91 
+Q 1019 -91 701 211 
+Q 384 513 384 1019 
+Q 384 1609 779 1909 
+Q 1175 2209 1959 2209 
+L 2766 2209 
+L 2766 2266 
+Q 2766 2663 2505 2880 
+Q 2244 3097 1772 3097 
+Q 1472 3097 1187 3025 
+Q 903 2953 641 2809 
+L 641 3341 
+Q 956 3463 1253 3523 
+Q 1550 3584 1831 3584 
+Q 2591 3584 2966 3190 
+Q 3341 2797 3341 1997 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-6c" d="M 603 4863 
+L 1178 4863 
+L 1178 0 
+L 603 0 
+L 603 4863 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-75" d="M 544 1381 
+L 544 3500 
+L 1119 3500 
+L 1119 1403 
+Q 1119 906 1312 657 
+Q 1506 409 1894 409 
+Q 2359 409 2629 706 
+Q 2900 1003 2900 1516 
+L 2900 3500 
+L 3475 3500 
+L 3475 0 
+L 2900 0 
+L 2900 538 
+Q 2691 219 2414 64 
+Q 2138 -91 1772 -91 
+Q 1169 -91 856 284 
+Q 544 659 544 1381 
+z
+M 1991 3584 
+L 1991 3584 
+z
+" transform="scale(0.015625)"/>
+      </defs>
+      <use xlink:href="#DejaVuSans-56"/>
+      <use xlink:href="#DejaVuSans-61" x="60.658203"/>
+      <use xlink:href="#DejaVuSans-6c" x="121.9375"/>
+      <use xlink:href="#DejaVuSans-75" x="149.720703"/>
+      <use xlink:href="#DejaVuSans-65" x="213.099609"/>
+     </g>
+    </g>
+   </g>
+   <g id="patch_3">
+    <path d="M 74.99 196.72 
+L 74.99 45.36 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_4">
+    <path d="M 709.2 196.72 
+L 709.2 45.36 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_5">
+    <path d="M 74.99 196.72 
+L 709.2 196.72 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_6">
+    <path d="M 74.99 45.36 
+L 709.2 45.36 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="legend_1">
+    <g id="patch_7">
+     <path d="M 525.282812 68.038125 
+L 702.2 68.038125 
+Q 704.2 68.038125 704.2 66.038125 
+L 704.2 52.36 
+Q 704.2 50.36 702.2 50.36 
+L 525.282812 50.36 
+Q 523.282812 50.36 523.282812 52.36 
+L 523.282812 66.038125 
+Q 523.282812 68.038125 525.282812 68.038125 
+z
+" style="fill: #ffffff; opacity: 0.8; stroke: #cccccc; stroke-linejoin: miter"/>
+    </g>
+    <g id="PathCollection_2">
+     <g>
+      <use xlink:href="#mae39fc909d" x="537.282812" y="59.333438" style="fill: #1f77b4; stroke: #1f77b4"/>
+     </g>
+    </g>
+    <g id="text_13">
+     <!-- SPI data represented as float -->
+     <g transform="translate(555.282812 61.958438)scale(0.1 -0.1)">
+      <defs>
+       <path id="DejaVuSans-53" d="M 3425 4513 
+L 3425 3897 
+Q 3066 4069 2747 4153 
+Q 2428 4238 2131 4238 
+Q 1616 4238 1336 4038 
+Q 1056 3838 1056 3469 
+Q 1056 3159 1242 3001 
+Q 1428 2844 1947 2747 
+L 2328 2669 
+Q 3034 2534 3370 2195 
+Q 3706 1856 3706 1288 
+Q 3706 609 3251 259 
+Q 2797 -91 1919 -91 
+Q 1588 -91 1214 -16 
+Q 841 59 441 206 
+L 441 856 
+Q 825 641 1194 531 
+Q 1563 422 1919 422 
+Q 2459 422 2753 634 
+Q 3047 847 3047 1241 
+Q 3047 1584 2836 1778 
+Q 2625 1972 2144 2069 
+L 1759 2144 
+Q 1053 2284 737 2584 
+Q 422 2884 422 3419 
+Q 422 4038 858 4394 
+Q 1294 4750 2059 4750 
+Q 2388 4750 2728 4690 
+Q 3069 4631 3425 4513 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-50" d="M 1259 4147 
+L 1259 2394 
+L 2053 2394 
+Q 2494 2394 2734 2622 
+Q 2975 2850 2975 3272 
+Q 2975 3691 2734 3919 
+Q 2494 4147 2053 4147 
+L 1259 4147 
+z
+M 628 4666 
+L 2053 4666 
+Q 2838 4666 3239 4311 
+Q 3641 3956 3641 3272 
+Q 3641 2581 3239 2228 
+Q 2838 1875 2053 1875 
+L 1259 1875 
+L 1259 0 
+L 628 0 
+L 628 4666 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-49" d="M 628 4666 
+L 1259 4666 
+L 1259 0 
+L 628 0 
+L 628 4666 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-64" d="M 2906 2969 
+L 2906 4863 
+L 3481 4863 
+L 3481 0 
+L 2906 0 
+L 2906 525 
+Q 2725 213 2448 61 
+Q 2172 -91 1784 -91 
+Q 1150 -91 751 415 
+Q 353 922 353 1747 
+Q 353 2572 751 3078 
+Q 1150 3584 1784 3584 
+Q 2172 3584 2448 3432 
+Q 2725 3281 2906 2969 
+z
+M 947 1747 
+Q 947 1113 1208 752 
+Q 1469 391 1925 391 
+Q 2381 391 2643 752 
+Q 2906 1113 2906 1747 
+Q 2906 2381 2643 2742 
+Q 2381 3103 1925 3103 
+Q 1469 3103 1208 2742 
+Q 947 2381 947 1747 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-72" d="M 2631 2963 
+Q 2534 3019 2420 3045 
+Q 2306 3072 2169 3072 
+Q 1681 3072 1420 2755 
+Q 1159 2438 1159 1844 
+L 1159 0 
+L 581 0 
+L 581 3500 
+L 1159 3500 
+L 1159 2956 
+Q 1341 3275 1631 3429 
+Q 1922 3584 2338 3584 
+Q 2397 3584 2469 3576 
+Q 2541 3569 2628 3553 
+L 2631 2963 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-70" d="M 1159 525 
+L 1159 -1331 
+L 581 -1331 
+L 581 3500 
+L 1159 3500 
+L 1159 2969 
+Q 1341 3281 1617 3432 
+Q 1894 3584 2278 3584 
+Q 2916 3584 3314 3078 
+Q 3713 2572 3713 1747 
+Q 3713 922 3314 415 
+Q 2916 -91 2278 -91 
+Q 1894 -91 1617 61 
+Q 1341 213 1159 525 
+z
+M 3116 1747 
+Q 3116 2381 2855 2742 
+Q 2594 3103 2138 3103 
+Q 1681 3103 1420 2742 
+Q 1159 2381 1159 1747 
+Q 1159 1113 1420 752 
+Q 1681 391 2138 391 
+Q 2594 391 2855 752 
+Q 3116 1113 3116 1747 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-66" d="M 2375 4863 
+L 2375 4384 
+L 1825 4384 
+Q 1516 4384 1395 4259 
+Q 1275 4134 1275 3809 
+L 1275 3500 
+L 2222 3500 
+L 2222 3053 
+L 1275 3053 
+L 1275 0 
+L 697 0 
+L 697 3053 
+L 147 3053 
+L 147 3500 
+L 697 3500 
+L 697 3744 
+Q 697 4328 969 4595 
+Q 1241 4863 1831 4863 
+L 2375 4863 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-6f" d="M 1959 3097 
+Q 1497 3097 1228 2736 
+Q 959 2375 959 1747 
+Q 959 1119 1226 758 
+Q 1494 397 1959 397 
+Q 2419 397 2687 759 
+Q 2956 1122 2956 1747 
+Q 2956 2369 2687 2733 
+Q 2419 3097 1959 3097 
+z
+M 1959 3584 
+Q 2709 3584 3137 3096 
+Q 3566 2609 3566 1747 
+Q 3566 888 3137 398 
+Q 2709 -91 1959 -91 
+Q 1206 -91 779 398 
+Q 353 888 353 1747 
+Q 353 2609 779 3096 
+Q 1206 3584 1959 3584 
+z
+" transform="scale(0.015625)"/>
+      </defs>
+      <use xlink:href="#DejaVuSans-53"/>
+      <use xlink:href="#DejaVuSans-50" x="63.476562"/>
+      <use xlink:href="#DejaVuSans-49" x="123.779297"/>
+      <use xlink:href="#DejaVuSans-20" x="153.271484"/>
+      <use xlink:href="#DejaVuSans-64" x="185.058594"/>
+      <use xlink:href="#DejaVuSans-61" x="248.535156"/>
+      <use xlink:href="#DejaVuSans-74" x="309.814453"/>
+      <use xlink:href="#DejaVuSans-61" x="349.023438"/>
+      <use xlink:href="#DejaVuSans-20" x="410.302734"/>
+      <use xlink:href="#DejaVuSans-72" x="442.089844"/>
+      <use xlink:href="#DejaVuSans-65" x="480.953125"/>
+      <use xlink:href="#DejaVuSans-70" x="542.476562"/>
+      <use xlink:href="#DejaVuSans-72" x="605.953125"/>
+      <use xlink:href="#DejaVuSans-65" x="644.816406"/>
+      <use xlink:href="#DejaVuSans-73" x="706.339844"/>
+      <use xlink:href="#DejaVuSans-65" x="758.439453"/>
+      <use xlink:href="#DejaVuSans-6e" x="819.962891"/>
+      <use xlink:href="#DejaVuSans-74" x="883.341797"/>
+      <use xlink:href="#DejaVuSans-65" x="922.550781"/>
+      <use xlink:href="#DejaVuSans-64" x="984.074219"/>
+      <use xlink:href="#DejaVuSans-20" x="1047.550781"/>
+      <use xlink:href="#DejaVuSans-61" x="1079.337891"/>
+      <use xlink:href="#DejaVuSans-73" x="1140.617188"/>
+      <use xlink:href="#DejaVuSans-20" x="1192.716797"/>
+      <use xlink:href="#DejaVuSans-66" x="1224.503906"/>
+      <use xlink:href="#DejaVuSans-6c" x="1259.708984"/>
+      <use xlink:href="#DejaVuSans-6f" x="1287.492188"/>
+      <use xlink:href="#DejaVuSans-61" x="1348.673828"/>
+      <use xlink:href="#DejaVuSans-74" x="1409.953125"/>
+     </g>
+    </g>
+   </g>
+  </g>
+  <g id="axes_2">
+   <g id="patch_8">
+    <path d="M 74.99 390.04 
+L 709.2 390.04 
+L 709.2 238.68 
+L 74.99 238.68 
+z
+" style="fill: #ffffff"/>
+   </g>
+   <g id="matplotlib.axis_3">
+    <g id="xtick_6">
+     <g id="line2d_21">
+      <path d="M 175.138921 390.04 
+L 175.138921 238.68 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_22">
+      <g>
+       <use xlink:href="#m4b97302bc4" x="175.138921" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_14">
+      <!-- 20000 -->
+      <g transform="translate(159.232671 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-32"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_7">
+     <g id="line2d_23">
+      <path d="M 299.826323 390.04 
+L 299.826323 238.68 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_24">
+      <g>
+       <use xlink:href="#m4b97302bc4" x="299.826323" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_15">
+      <!-- 40000 -->
+      <g transform="translate(283.920073 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-34"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_8">
+     <g id="line2d_25">
+      <path d="M 424.513724 390.04 
+L 424.513724 238.68 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_26">
+      <g>
+       <use xlink:href="#m4b97302bc4" x="424.513724" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_16">
+      <!-- 60000 -->
+      <g transform="translate(408.607474 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-36"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_9">
+     <g id="line2d_27">
+      <path d="M 549.201126 390.04 
+L 549.201126 238.68 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_28">
+      <g>
+       <use xlink:href="#m4b97302bc4" x="549.201126" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_17">
+      <!-- 80000 -->
+      <g transform="translate(533.294876 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-38"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+      </g>
+     </g>
+    </g>
+    <g id="xtick_10">
+     <g id="line2d_29">
+      <path d="M 673.888528 390.04 
+L 673.888528 238.68 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_30">
+      <g>
+       <use xlink:href="#m4b97302bc4" x="673.888528" y="390.04" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_18">
+      <!-- 100000 -->
+      <g transform="translate(654.801028 404.638438)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-31"/>
+       <use xlink:href="#DejaVuSans-30" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="127.246094"/>
+       <use xlink:href="#DejaVuSans-30" x="190.869141"/>
+       <use xlink:href="#DejaVuSans-30" x="254.492188"/>
+       <use xlink:href="#DejaVuSans-30" x="318.115234"/>
+      </g>
+     </g>
+    </g>
+    <g id="text_19">
+     <!-- time in ns -->
+     <g transform="translate(367.289531 418.316562)scale(0.1 -0.1)">
+      <use xlink:href="#DejaVuSans-74"/>
+      <use xlink:href="#DejaVuSans-69" x="39.208984"/>
+      <use xlink:href="#DejaVuSans-6d" x="66.992188"/>
+      <use xlink:href="#DejaVuSans-65" x="164.404297"/>
+      <use xlink:href="#DejaVuSans-20" x="225.927734"/>
+      <use xlink:href="#DejaVuSans-69" x="257.714844"/>
+      <use xlink:href="#DejaVuSans-6e" x="285.498047"/>
+      <use xlink:href="#DejaVuSans-20" x="348.876953"/>
+      <use xlink:href="#DejaVuSans-6e" x="380.664062"/>
+      <use xlink:href="#DejaVuSans-73" x="444.042969"/>
+     </g>
+    </g>
+   </g>
+   <g id="matplotlib.axis_4">
+    <g id="ytick_6">
+     <g id="line2d_31">
+      <path d="M 74.99 376.729581 
+L 709.2 376.729581 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_32">
+      <g>
+       <use xlink:href="#m95f006266e" x="74.99" y="376.729581" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_20">
+      <!-- −0.0002 -->
+      <g transform="translate(24.619687 380.5288)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-2212"/>
+       <use xlink:href="#DejaVuSans-30" x="83.789062"/>
+       <use xlink:href="#DejaVuSans-2e" x="147.412109"/>
+       <use xlink:href="#DejaVuSans-30" x="179.199219"/>
+       <use xlink:href="#DejaVuSans-30" x="242.822266"/>
+       <use xlink:href="#DejaVuSans-30" x="306.445312"/>
+       <use xlink:href="#DejaVuSans-32" x="370.068359"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_7">
+     <g id="line2d_33">
+      <path d="M 74.99 345.544791 
+L 709.2 345.544791 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_34">
+      <g>
+       <use xlink:href="#m95f006266e" x="74.99" y="345.544791" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_21">
+      <!-- −0.0001 -->
+      <g transform="translate(24.619687 349.344009)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-2212"/>
+       <use xlink:href="#DejaVuSans-30" x="83.789062"/>
+       <use xlink:href="#DejaVuSans-2e" x="147.412109"/>
+       <use xlink:href="#DejaVuSans-30" x="179.199219"/>
+       <use xlink:href="#DejaVuSans-30" x="242.822266"/>
+       <use xlink:href="#DejaVuSans-30" x="306.445312"/>
+       <use xlink:href="#DejaVuSans-31" x="370.068359"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_8">
+     <g id="line2d_35">
+      <path d="M 74.99 314.36 
+L 709.2 314.36 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_36">
+      <g>
+       <use xlink:href="#m95f006266e" x="74.99" y="314.36" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_22">
+      <!-- 0.0000 -->
+      <g transform="translate(32.999375 318.159219)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="95.410156"/>
+       <use xlink:href="#DejaVuSans-30" x="159.033203"/>
+       <use xlink:href="#DejaVuSans-30" x="222.65625"/>
+       <use xlink:href="#DejaVuSans-30" x="286.279297"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_9">
+     <g id="line2d_37">
+      <path d="M 74.99 283.175209 
+L 709.2 283.175209 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_38">
+      <g>
+       <use xlink:href="#m95f006266e" x="74.99" y="283.175209" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_23">
+      <!-- 0.0001 -->
+      <g transform="translate(32.999375 286.974428)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="95.410156"/>
+       <use xlink:href="#DejaVuSans-30" x="159.033203"/>
+       <use xlink:href="#DejaVuSans-30" x="222.65625"/>
+       <use xlink:href="#DejaVuSans-31" x="286.279297"/>
+      </g>
+     </g>
+    </g>
+    <g id="ytick_10">
+     <g id="line2d_39">
+      <path d="M 74.99 251.990419 
+L 709.2 251.990419 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #b0b0b0; stroke-width: 0.8; stroke-linecap: square"/>
+     </g>
+     <g id="line2d_40">
+      <g>
+       <use xlink:href="#m95f006266e" x="74.99" y="251.990419" style="stroke: #000000; stroke-width: 0.8"/>
+      </g>
+     </g>
+     <g id="text_24">
+      <!-- 0.0002 -->
+      <g transform="translate(32.999375 255.789638)scale(0.1 -0.1)">
+       <use xlink:href="#DejaVuSans-30"/>
+       <use xlink:href="#DejaVuSans-2e" x="63.623047"/>
+       <use xlink:href="#DejaVuSans-30" x="95.410156"/>
+       <use xlink:href="#DejaVuSans-30" x="159.033203"/>
+       <use xlink:href="#DejaVuSans-30" x="222.65625"/>
+       <use xlink:href="#DejaVuSans-32" x="286.279297"/>
+      </g>
+     </g>
+    </g>
+    <g id="text_25">
+     <!-- Error -->
+     <g transform="translate(18.54 326.545156)rotate(-90)scale(0.1 -0.1)">
+      <defs>
+       <path id="DejaVuSans-45" d="M 628 4666 
+L 3578 4666 
+L 3578 4134 
+L 1259 4134 
+L 1259 2753 
+L 3481 2753 
+L 3481 2222 
+L 1259 2222 
+L 1259 531 
+L 3634 531 
+L 3634 0 
+L 628 0 
+L 628 4666 
+z
+" transform="scale(0.015625)"/>
+      </defs>
+      <use xlink:href="#DejaVuSans-45"/>
+      <use xlink:href="#DejaVuSans-72" x="63.183594"/>
+      <use xlink:href="#DejaVuSans-72" x="102.546875"/>
+      <use xlink:href="#DejaVuSans-6f" x="141.410156"/>
+      <use xlink:href="#DejaVuSans-72" x="202.591797"/>
+     </g>
+    </g>
+   </g>
+   <g id="line2d_41">
+    <path d="M 103.817727 361.944214 
+L 139.852386 245.56 
+L 175.887045 295.08845 
+L 211.921705 335.149746 
+L 247.956364 328.635264 
+L 283.991023 354.183432 
+L 320.025682 337.914242 
+L 356.060341 293.144214 
+L 392.095 266.775786 
+L 428.129659 383.16 
+L 464.164318 333.63155 
+L 500.198977 293.570254 
+L 536.233636 300.084736 
+L 572.268295 274.536568 
+L 608.302955 290.805758 
+L 644.337614 335.575786 
+L 680.372273 361.944214 
+" clip-path="url(#p0cf935b477)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
+   </g>
+   <g id="patch_9">
+    <path d="M 74.99 390.04 
+L 74.99 238.68 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_10">
+    <path d="M 709.2 390.04 
+L 709.2 238.68 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_11">
+    <path d="M 74.99 390.04 
+L 709.2 390.04 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="patch_12">
+    <path d="M 74.99 238.68 
+L 709.2 238.68 
+" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
+   </g>
+   <g id="legend_2">
+    <g id="patch_13">
+     <path d="M 557.029688 261.63625 
+L 702.2 261.63625 
+Q 704.2 261.63625 704.2 259.63625 
+L 704.2 245.68 
+Q 704.2 243.68 702.2 243.68 
+L 557.029688 243.68 
+Q 555.029688 243.68 555.029688 245.68 
+L 555.029688 259.63625 
+Q 555.029688 261.63625 557.029688 261.63625 
+z
+" style="fill: #ffffff; opacity: 0.8; stroke: #cccccc; stroke-linejoin: miter"/>
+    </g>
+    <g id="line2d_42">
+     <path d="M 559.029688 251.778438 
+L 569.029688 251.778438 
+L 579.029688 251.778438 
+" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
+    </g>
+    <g id="text_26">
+     <!-- error = y_data - y_calc -->
+     <g transform="translate(587.029688 255.278438)scale(0.1 -0.1)">
+      <defs>
+       <path id="DejaVuSans-3d" d="M 678 2906 
+L 4684 2906 
+L 4684 2381 
+L 678 2381 
+L 678 2906 
+z
+M 678 1631 
+L 4684 1631 
+L 4684 1100 
+L 678 1100 
+L 678 1631 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-79" d="M 2059 -325 
+Q 1816 -950 1584 -1140 
+Q 1353 -1331 966 -1331 
+L 506 -1331 
+L 506 -850 
+L 844 -850 
+Q 1081 -850 1212 -737 
+Q 1344 -625 1503 -206 
+L 1606 56 
+L 191 3500 
+L 800 3500 
+L 1894 763 
+L 2988 3500 
+L 3597 3500 
+L 2059 -325 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-5f" d="M 3263 -1063 
+L 3263 -1509 
+L -63 -1509 
+L -63 -1063 
+L 3263 -1063 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-2d" d="M 313 2009 
+L 1997 2009 
+L 1997 1497 
+L 313 1497 
+L 313 2009 
+z
+" transform="scale(0.015625)"/>
+       <path id="DejaVuSans-63" d="M 3122 3366 
+L 3122 2828 
+Q 2878 2963 2633 3030 
+Q 2388 3097 2138 3097 
+Q 1578 3097 1268 2742 
+Q 959 2388 959 1747 
+Q 959 1106 1268 751 
+Q 1578 397 2138 397 
+Q 2388 397 2633 464 
+Q 2878 531 3122 666 
+L 3122 134 
+Q 2881 22 2623 -34 
+Q 2366 -91 2075 -91 
+Q 1284 -91 818 406 
+Q 353 903 353 1747 
+Q 353 2603 823 3093 
+Q 1294 3584 2113 3584 
+Q 2378 3584 2631 3529 
+Q 2884 3475 3122 3366 
+z
+" transform="scale(0.015625)"/>
+      </defs>
+      <use xlink:href="#DejaVuSans-65"/>
+      <use xlink:href="#DejaVuSans-72" x="61.523438"/>
+      <use xlink:href="#DejaVuSans-72" x="100.886719"/>
+      <use xlink:href="#DejaVuSans-6f" x="139.75"/>
+      <use xlink:href="#DejaVuSans-72" x="200.931641"/>
+      <use xlink:href="#DejaVuSans-20" x="242.044922"/>
+      <use xlink:href="#DejaVuSans-3d" x="273.832031"/>
+      <use xlink:href="#DejaVuSans-20" x="357.621094"/>
+      <use xlink:href="#DejaVuSans-79" x="389.408203"/>
+      <use xlink:href="#DejaVuSans-5f" x="448.587891"/>
+      <use xlink:href="#DejaVuSans-64" x="498.587891"/>
+      <use xlink:href="#DejaVuSans-61" x="562.064453"/>
+      <use xlink:href="#DejaVuSans-74" x="623.34375"/>
+      <use xlink:href="#DejaVuSans-61" x="662.552734"/>
+      <use xlink:href="#DejaVuSans-20" x="723.832031"/>
+      <use xlink:href="#DejaVuSans-2d" x="755.619141"/>
+      <use xlink:href="#DejaVuSans-20" x="791.703125"/>
+      <use xlink:href="#DejaVuSans-79" x="823.490234"/>
+      <use xlink:href="#DejaVuSans-5f" x="882.669922"/>
+      <use xlink:href="#DejaVuSans-63" x="932.669922"/>
+      <use xlink:href="#DejaVuSans-61" x="987.650391"/>
+      <use xlink:href="#DejaVuSans-6c" x="1048.929688"/>
+      <use xlink:href="#DejaVuSans-63" x="1076.712891"/>
+     </g>
+    </g>
+   </g>
+  </g>
+  <g id="text_27">
+   <!-- Stimulus: Sine wave generator, Driver: SPI module -->
+   <g transform="translate(56.145 26.87625)scale(0.24 -0.24)">
+    <defs>
+     <path id="DejaVuSans-3a" d="M 750 794 
+L 1409 794 
+L 1409 0 
+L 750 0 
+L 750 794 
+z
+M 750 3309 
+L 1409 3309 
+L 1409 2516 
+L 750 2516 
+L 750 3309 
+z
+" transform="scale(0.015625)"/>
+     <path id="DejaVuSans-77" d="M 269 3500 
+L 844 3500 
+L 1563 769 
+L 2278 3500 
+L 2956 3500 
+L 3675 769 
+L 4391 3500 
+L 4966 3500 
+L 4050 0 
+L 3372 0 
+L 2619 2869 
+L 1863 0 
+L 1184 0 
+L 269 3500 
+z
+" transform="scale(0.015625)"/>
+     <path id="DejaVuSans-76" d="M 191 3500 
+L 800 3500 
+L 1894 563 
+L 2988 3500 
+L 3597 3500 
+L 2284 0 
+L 1503 0 
+L 191 3500 
+z
+" transform="scale(0.015625)"/>
+     <path id="DejaVuSans-67" d="M 2906 1791 
+Q 2906 2416 2648 2759 
+Q 2391 3103 1925 3103 
+Q 1463 3103 1205 2759 
+Q 947 2416 947 1791 
+Q 947 1169 1205 825 
+Q 1463 481 1925 481 
+Q 2391 481 2648 825 
+Q 2906 1169 2906 1791 
+z
+M 3481 434 
+Q 3481 -459 3084 -895 
+Q 2688 -1331 1869 -1331 
+Q 1566 -1331 1297 -1286 
+Q 1028 -1241 775 -1147 
+L 775 -588 
+Q 1028 -725 1275 -790 
+Q 1522 -856 1778 -856 
+Q 2344 -856 2625 -561 
+Q 2906 -266 2906 331 
+L 2906 616 
+Q 2728 306 2450 153 
+Q 2172 0 1784 0 
+Q 1141 0 747 490 
+Q 353 981 353 1791 
+Q 353 2603 747 3093 
+Q 1141 3584 1784 3584 
+Q 2172 3584 2450 3431 
+Q 2728 3278 2906 2969 
+L 2906 3500 
+L 3481 3500 
+L 3481 434 
+z
+" transform="scale(0.015625)"/>
+     <path id="DejaVuSans-2c" d="M 750 794 
+L 1409 794 
+L 1409 256 
+L 897 -744 
+L 494 -744 
+L 750 256 
+L 750 794 
+z
+" transform="scale(0.015625)"/>
+     <path id="DejaVuSans-44" d="M 1259 4147 
+L 1259 519 
+L 2022 519 
+Q 2988 519 3436 956 
+Q 3884 1394 3884 2338 
+Q 3884 3275 3436 3711 
+Q 2988 4147 2022 4147 
+L 1259 4147 
+z
+M 628 4666 
+L 1925 4666 
+Q 3281 4666 3915 4102 
+Q 4550 3538 4550 2338 
+Q 4550 1131 3912 565 
+Q 3275 0 1925 0 
+L 628 0 
+L 628 4666 
+z
+" transform="scale(0.015625)"/>
+    </defs>
+    <use xlink:href="#DejaVuSans-53"/>
+    <use xlink:href="#DejaVuSans-74" x="63.476562"/>
+    <use xlink:href="#DejaVuSans-69" x="102.685547"/>
+    <use xlink:href="#DejaVuSans-6d" x="130.46875"/>
+    <use xlink:href="#DejaVuSans-75" x="227.880859"/>
+    <use xlink:href="#DejaVuSans-6c" x="291.259766"/>
+    <use xlink:href="#DejaVuSans-75" x="319.042969"/>
+    <use xlink:href="#DejaVuSans-73" x="382.421875"/>
+    <use xlink:href="#DejaVuSans-3a" x="434.521484"/>
+    <use xlink:href="#DejaVuSans-20" x="468.212891"/>
+    <use xlink:href="#DejaVuSans-53" x="500"/>
+    <use xlink:href="#DejaVuSans-69" x="563.476562"/>
+    <use xlink:href="#DejaVuSans-6e" x="591.259766"/>
+    <use xlink:href="#DejaVuSans-65" x="654.638672"/>
+    <use xlink:href="#DejaVuSans-20" x="716.162109"/>
+    <use xlink:href="#DejaVuSans-77" x="747.949219"/>
+    <use xlink:href="#DejaVuSans-61" x="829.736328"/>
+    <use xlink:href="#DejaVuSans-76" x="891.015625"/>
+    <use xlink:href="#DejaVuSans-65" x="950.195312"/>
+    <use xlink:href="#DejaVuSans-20" x="1011.71875"/>
+    <use xlink:href="#DejaVuSans-67" x="1043.505859"/>
+    <use xlink:href="#DejaVuSans-65" x="1106.982422"/>
+    <use xlink:href="#DejaVuSans-6e" x="1168.505859"/>
+    <use xlink:href="#DejaVuSans-65" x="1231.884766"/>
+    <use xlink:href="#DejaVuSans-72" x="1293.408203"/>
+    <use xlink:href="#DejaVuSans-61" x="1334.521484"/>
+    <use xlink:href="#DejaVuSans-74" x="1395.800781"/>
+    <use xlink:href="#DejaVuSans-6f" x="1435.009766"/>
+    <use xlink:href="#DejaVuSans-72" x="1496.191406"/>
+    <use xlink:href="#DejaVuSans-2c" x="1537.304688"/>
+    <use xlink:href="#DejaVuSans-20" x="1569.091797"/>
+    <use xlink:href="#DejaVuSans-44" x="1600.878906"/>
+    <use xlink:href="#DejaVuSans-72" x="1677.880859"/>
+    <use xlink:href="#DejaVuSans-69" x="1718.994141"/>
+    <use xlink:href="#DejaVuSans-76" x="1746.777344"/>
+    <use xlink:href="#DejaVuSans-65" x="1805.957031"/>
+    <use xlink:href="#DejaVuSans-72" x="1867.480469"/>
+    <use xlink:href="#DejaVuSans-3a" x="1906.84375"/>
+    <use xlink:href="#DejaVuSans-20" x="1940.535156"/>
+    <use xlink:href="#DejaVuSans-53" x="1972.322266"/>
+    <use xlink:href="#DejaVuSans-50" x="2035.798828"/>
+    <use xlink:href="#DejaVuSans-49" x="2096.101562"/>
+    <use xlink:href="#DejaVuSans-20" x="2125.59375"/>
+    <use xlink:href="#DejaVuSans-6d" x="2157.380859"/>
+    <use xlink:href="#DejaVuSans-6f" x="2254.792969"/>
+    <use xlink:href="#DejaVuSans-64" x="2315.974609"/>
+    <use xlink:href="#DejaVuSans-75" x="2379.451172"/>
+    <use xlink:href="#DejaVuSans-6c" x="2442.830078"/>
+    <use xlink:href="#DejaVuSans-65" x="2470.613281"/>
+   </g>
+  </g>
+ </g>
+ <defs>
+  <clipPath id="p9829768d15">
+   <rect x="74.99" y="45.36" width="634.21" height="151.36"/>
+  </clipPath>
+  <clipPath id="p0cf935b477">
+   <rect x="74.99" y="238.68" width="634.21" height="151.36"/>
+  </clipPath>
+ </defs>
+</svg>
diff --git a/verilog/rtl/waveform-generator/design/wfg_top/testbench/test_wfg_top.py b/verilog/rtl/waveform-generator/design/wfg_top/testbench/test_wfg_top.py
new file mode 100644
index 0000000..ef444ee
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_top/testbench/test_wfg_top.py
@@ -0,0 +1,266 @@
+# SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+# SPDX-License-Identifier: Apache-2.0
+
+import random, math, statistics
+import matplotlib.pyplot as plt
+from scipy import optimize
+import numpy as np
+import cocotb
+from cocotb.utils import get_sim_time
+from cocotb.clock import Clock
+from cocotb.regression import TestFactory
+from cocotb.triggers import Timer, RisingEdge, FallingEdge, ClockCycles
+from cocotbext.wishbone.driver import WishboneMaster
+from cocotbext.wishbone.driver import WBOp
+from cocotbext.spi import SpiMaster, SpiSignals, SpiConfig, SpiSlaveBase
+
+CLK_PER_SYNC = 300
+SYSCLK = 100000000
+
+short_per = Timer(100, units="ns")
+long_time = Timer(100, units="us")
+
+class SimpleSpiSlave(SpiSlaveBase):
+    def __init__(self, dut, signals, config):
+        self._config = config
+        self.content = 0
+        super().__init__(signals)
+        self.latest_value = None
+        self.time = []
+        self.values = []
+
+    async def get_content(self):
+        await self.idle.wait()
+        return self.content
+
+    async def _transaction(self, frame_start, frame_end):
+        await frame_start
+        self.content = int(await self._shift(self._config.word_width, tx_word=None))
+        await frame_end
+    
+        # For now we have to mirror the bits ourselves
+        if not self._config.msb_first:
+            mirrored = 0
+            bit_length = self._config.word_width
+            for bit_pos in range(bit_length):
+                mirrored |= ((self.content & 1<<(bit_pos)) > 0) << (bit_length-1-bit_pos)
+            self.content = mirrored
+        
+        # Sign extend
+        if self.content & (1<<17):
+            self.content |= ((1<<14)-1)<<18
+        
+        self.latest_value = self.content.to_bytes(self._config.word_width // 8, 'big')
+        
+        self.latest_value = int.from_bytes(self.latest_value, 'big', signed=True)
+        
+        self.values.append(self.latest_value)
+        self.time.append(get_sim_time('ns'))
+
+async def set_register(dut, wbs, peripheral_address, address, data):
+    if address > 0xF:
+        dut._log.error("Can not access peripheral registers outside 0xF")
+
+    real_address = (peripheral_address<<4) | (address & 0xF)
+    dut._log.info(f"Set register {real_address} : {data}")
+
+    wbRes = await wbs.send_cycle([WBOp(real_address, data)])
+    rvalues = [wb.datrd for wb in wbRes]
+    dut._log.info(f"Returned values : {rvalues}")
+
+async def configure_core(dut, wbs, en, sync_count, subcycle_count):
+    await set_register(dut, wbs, 0x1, 0x4, (sync_count << 0) | (subcycle_count << 8))
+    await set_register(dut, wbs, 0x1, 0x0, en) # Enable
+
+async def configure_subcore(dut, wbs, en, sync_count, subcycle_count):
+    await set_register(dut, wbs, 0x2, 0x4, (sync_count << 0) | (subcycle_count << 8))
+    await set_register(dut, wbs, 0x2, 0x0, en) # Enable
+
+async def configure_interconnect(dut, wbs, en=1, driver0=0, driver1=1):
+    await set_register(dut, wbs, 0x3, 0x4, driver0)
+    await set_register(dut, wbs, 0x3, 0x8, driver1)
+    await set_register(dut, wbs, 0x3, 0x0, en) # Enable
+
+async def configure_stim_sine(dut, wbs, en, inc=0x1000, gain=0x4000, offset=0):
+    await set_register(dut, wbs, 0x4, 0x4, inc)
+    await set_register(dut, wbs, 0x4, 0x8, gain)
+    await set_register(dut, wbs, 0x4, 0xC, offset)
+    await set_register(dut, wbs, 0x4, 0x0, en) # Enable
+
+async def configure_stim_mem(dut, wbs, en, start=0x0000, end=0x00FF, inc=0x01, gain=0x0001):
+    await set_register(dut, wbs, 0x5, 0x4, start)
+    await set_register(dut, wbs, 0x5, 0x8, end)
+    await set_register(dut, wbs, 0x5, 0xC, gain<<8 | inc)
+    await set_register(dut, wbs, 0x5, 0x0, en) # Enable
+
+async def configure_drive_spi(dut, wbs, en=1, core_sel=0, cnt=3, cpol=0, lsbfirst=0, dff=0, sspol=0):
+    await set_register(dut, wbs, 0x6, 0x8, cnt) # Clock divider
+    await set_register(dut, wbs, 0x6, 0x4, (cpol<<0) | (lsbfirst<<1) | (dff<<2) | (sspol<<4) | (core_sel<<5))
+    await set_register(dut, wbs, 0x6, 0x0, en) # Enable SPI
+
+async def configure_drive_pat(dut, wbs, en=0xFFFFFFFF, core_sel=0, pat=0, begin=0, end=8):
+    await set_register(dut, wbs, 0x7, 0x4, (begin & 0xFF) | ((end & 0xFF)<<8) | (core_sel<<16))
+    await set_register(dut, wbs, 0x7, 0x8, pat[0])
+    await set_register(dut, wbs, 0x7, 0xC, pat[1])
+    await set_register(dut, wbs, 0x7, 0x0, en) # Enable PAT
+
+async def checkPattern(dut, start, end, inc, gain):
+
+    cur_address = start
+
+    while 1:
+        await FallingEdge(dut.wfg_drive_spi_cs_no)
+        await FallingEdge(dut.io_wbs_clk)
+        value = dut.wfg_drive_pat_dout_o.value
+        
+        dut._log.info(f"Test: {cur_address} == {value}")
+        
+        assert(cur_address*gain == value)
+        
+        cur_address += inc
+        
+        if (cur_address > end):
+            cur_address = start
+
+@cocotb.test()
+async def top_test(dut):
+    cocotb.start_soon(Clock(dut.io_wbs_clk, 1/SYSCLK*1e9, units="ns").start())
+
+    dut._log.info("Initialize and reset model")
+
+    # Start reset
+    dut.io_wbs_rst.value = 1    
+    await Timer(100, units='ns')
+
+    # Stop reset
+    dut.io_wbs_rst.value = 0
+
+    # Wishbone Master
+    wbs = WishboneMaster(dut, "io_wbs", dut.io_wbs_clk,
+                              width=32,   # size of data bus
+                              timeout=10) # in clock cycle number
+    # SPI settings
+    dff = 3
+    cnt = 3
+    cpol = 0
+    cpha = 0
+    lsbfirst = 0
+    sspol = 0
+
+    # Create SPI Slave
+    spi_signals = SpiSignals(
+        sclk = dut.wfg_drive_spi_sclk_o,
+        mosi = dut.wfg_drive_spi_sdo_o,
+        miso = dut.wfg_drive_spi_sdi_i,
+        cs   = dut.wfg_drive_spi_cs_no,
+        cs_active_low = not sspol
+    )
+    
+    spi_config = SpiConfig(
+        word_width          = (8 * (dff + 1)),          # number of bits in a SPI transaction
+        sclk_freq           = SYSCLK/((cnt + 1) * 2),   # clock rate in Hz
+        cpol                = cpol,                     # clock idle polarity
+        cpha                = False,                     # clock phase (CPHA=True means sample on FallingEdge)
+        msb_first           = not lsbfirst,             # the order that bits are clocked onto the wire
+        data_output_idle    = 1,                        # the idle value of the MOSI or MISO line 
+        frame_spacing_ns    = 1                         # the spacing between frames that the master waits for or the slave obeys
+                                                        #       the slave should raise SpiFrameError if this is not obeyed.
+    )
+    
+    spi_slave = SimpleSpiSlave(dut, spi_signals, spi_config)
+    
+    # Sine settings
+    sine_inc = 0x1000
+    sine_gain = 0x4000
+    sine_offset = 0
+    
+    num_spi_values = int((2**16) / sine_inc + 1)
+
+    # Setup core
+    dut._log.info("Configure core")
+    await configure_core(dut, wbs, en=1, sync_count=16, subcycle_count=16)
+    dut._log.info("Configure subcore")
+    await configure_subcore(dut, wbs, en=1, sync_count=32, subcycle_count=16)
+    dut._log.info("Configure interconnect")
+    await configure_interconnect(dut, wbs, en=1, driver0=0, driver1=1)
+    dut._log.info("Configure stim_sine")
+    await configure_stim_sine(dut, wbs, en=1, inc=sine_inc, gain=sine_gain, offset=sine_offset)
+    dut._log.info("Configure stim_mem")
+    await configure_stim_mem(dut, wbs, en=1, start=0x0000, end=0x000F, inc=0x01, gain=0x0001)
+    dut._log.info("Configure drive_spi")
+    await configure_drive_spi(dut, wbs, en=1, core_sel=0, cnt=cnt, cpol=cpol, lsbfirst=lsbfirst, dff=dff, sspol=sspol)
+    dut._log.info("Configure drive_pat")
+    pat = [0xFFFFFFFF, 0xFFFFFFFF]
+    await configure_drive_pat(dut, wbs, en=0xFFFFFFFF, core_sel=0, pat=pat, begin=0, end=8)
+
+    # Check pattern
+    cocotb.start_soon(checkPattern(dut, start=0x0000, end=0x000F, inc=0x01, gain=0x0001))
+
+    while len(spi_slave.values) < num_spi_values:
+        await short_per
+
+    def test_func(x, a, b, c):
+        return a * np.sin(b * x + c)
+
+    params, params_covariance = optimize.curve_fit(test_func, spi_slave.time, spi_slave.values, p0=[70000, 0.00007, -0.5])
+    
+    dut._log.info(params)
+    dut._log.info(spi_slave.time)
+    dut._log.info(spi_slave.values)
+
+    x_data = np.asarray(spi_slave.time)
+    y_data = np.asarray(spi_slave.values)
+    
+    #y_calc = test_func(x_data, params[0], params[1], params[2])
+    
+    y_error = []
+    y_squared_error = []
+    y_absolute_error = []
+    y_data_float = []
+    
+    for (cnt, value) in enumerate(y_data):
+        input_val = cnt * sine_inc
+        while input_val >= (2**16):
+            input_val -= (2**16)
+    
+        angle_rad = float(input_val) / (2**16) * 2 * math.pi
+        calculated_value = math.sin(angle_rad) * (sine_gain / 2**14) + (sine_offset/2**16)
+        output_as_float = float(value) / (2**16)
+        y_data_float.append(output_as_float)
+        y_error.append(output_as_float - calculated_value)
+        y_squared_error.append((output_as_float - calculated_value)**2)
+        y_absolute_error.append(abs(output_as_float - calculated_value))
+    
+    y_mean_squared_error = statistics.mean(y_squared_error)
+    dut._log.info("y_mean_squared_error: {}".format(y_mean_squared_error))
+    
+    y_mean_absolute_error = statistics.mean(y_absolute_error)
+    dut._log.info("y_mean_absolute_error: {}".format(y_mean_absolute_error))
+
+    x_data_highres = np.linspace(x_data[0], x_data[-1], num=100)
+    y_calc_highres = test_func(x_data_highres, params[0], params[1], params[2])
+    
+
+    
+    fig, ax = plt.subplots(2, 1)
+    fig.suptitle('Stimulus: Sine wave generator, Driver: SPI module', fontsize=24)
+    
+    ax[0].scatter(x_data, y_data_float, label='SPI data represented as float')
+    #ax[0].plot(x_data_highres, y_calc_highres, label='Fitted function')
+    ax[0].set(xlabel='time in ns', ylabel='Value')
+    ax[0].legend(loc='best')
+    ax[0].grid()
+    
+    ax[1].set(xlabel='time in ns', ylabel='Error')
+    ax[1].plot(x_data, y_error, label='error = y_data - y_calc')
+    ax[1].legend(loc='best')
+    ax[1].grid()
+
+    figure = plt.gcf()
+    figure.set_size_inches(10, 6)
+    plt.tight_layout()
+    fig.savefig("output_inc={}_gain={}_off={}.svg".format(sine_inc, sine_gain, sine_offset))
+    fig.savefig("output_inc={}_gain={}_off={}.png".format(sine_inc, sine_gain, sine_offset), dpi=199)
+    #plt.show()
+    
+    assert(y_mean_absolute_error < 0.001)
diff --git a/verilog/rtl/waveform-generator/design/wfg_top/testbench/wfg_top_tb.sv b/verilog/rtl/waveform-generator/design/wfg_top/testbench/wfg_top_tb.sv
new file mode 100644
index 0000000..e9c4e93
--- /dev/null
+++ b/verilog/rtl/waveform-generator/design/wfg_top/testbench/wfg_top_tb.sv
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: © 2022 semify <office@semify-eda.com>
+// SPDX-License-Identifier: Apache-2.0
+
+`timescale 1ns / 1ps
+
+`ifdef VERILATOR  // make parameter readable from VPI
+`define VL_RD /*verilator public_flat_rd*/
+`else
+`define VL_RD
+`endif
+
+module wfg_top_tb #(
+    parameter int BUSW = 32
+) (
+    // Wishbone interface signals
+    input               io_wbs_clk,
+    input               io_wbs_rst,
+    input  [(BUSW-1):0] io_wbs_adr,
+    input  [(BUSW-1):0] io_wbs_datwr,
+    output [(BUSW-1):0] io_wbs_datrd,
+    input               io_wbs_we,
+    input               io_wbs_stb,
+    output              io_wbs_ack,
+    input               io_wbs_cyc,
+
+    input  logic wfg_drive_spi_sdi_i,   // for cocotb
+    output logic wfg_drive_spi_sclk_o,
+    output logic wfg_drive_spi_cs_no,
+    output logic wfg_drive_spi_sdo_o,
+
+    output logic [31:0] wfg_drive_pat_dout_o
+);
+
+    logic        csb1;
+    logic [9:0 ] addr1;
+    logic [31:0] dout1;
+
+    localparam MEM_SIZE = 2 ** 10;
+
+    logic [31:0] mem[MEM_SIZE];
+
+    wfg_top wfg_top (
+        .io_wbs_clk(io_wbs_clk),
+        .io_wbs_rst(io_wbs_rst),
+        .io_wbs_adr(io_wbs_adr),
+        .io_wbs_datwr(io_wbs_datwr),
+        .io_wbs_datrd(io_wbs_datrd),
+        .io_wbs_we(io_wbs_we),
+        .io_wbs_stb(io_wbs_stb),
+        .io_wbs_ack(io_wbs_ack),
+        .io_wbs_cyc(io_wbs_cyc),
+
+        .wfg_drive_spi_sclk_o(wfg_drive_spi_sclk_o),
+        .wfg_drive_spi_cs_no (wfg_drive_spi_cs_no),
+        .wfg_drive_spi_sdo_o (wfg_drive_spi_sdo_o),
+
+        .wfg_drive_pat_dout_o(wfg_drive_pat_dout_o),
+
+        .csb1 (csb1),
+        .addr1(addr1),
+        .dout1(dout1)
+    );
+
+    initial begin
+        $readmemh("memory.hex", mem);
+    end
+
+    always_ff @(negedge io_wbs_clk) begin
+        if (!csb1) dout1 <= mem[addr1];
+    end
+
+    // Dump waves
+`ifndef VERILATOR
+    initial begin
+        $dumpfile("wfg_top_tb.vcd");
+        $dumpvars(0, wfg_top_tb);
+    end
+`endif
+
+endmodule