diff --git a/libraries/base/common/hdllib.cfg b/libraries/base/common/hdllib.cfg
index df646d9c66c8a5c163941d091858556656929887..c7c3d8323ed975947cecfff9b124f78a0391aa0a 100644
--- a/libraries/base/common/hdllib.cfg
+++ b/libraries/base/common/hdllib.cfg
@@ -109,8 +109,6 @@ synth_files =
     src/vhdl/common_fifo_rd.vhd
     src/vhdl/common_blockreg.vhd
     src/vhdl/common_fifo_dc_lock_control.vhd
-    src/vhdl/common_mem_master_mux.vhd
-    src/vhdl/common_mem_bus.vhd
     src/vhdl/common_mem_mux.vhd
     src/vhdl/common_mem_demux.vhd
     src/vhdl/common_reg_cross_domain.vhd
@@ -164,8 +162,6 @@ test_bench_files =
     tb/vhdl/tb_common_init.vhd
     tb/vhdl/tb_common_int2float.vhd
     tb/vhdl/tb_common_led_controller.vhd
-    tb/vhdl/tb_common_mem_master_mux.vhd
-    tb/vhdl/tb_common_mem_bus.vhd
     tb/vhdl/tb_common_mem_mux.vhd
     tb/vhdl/tb_common_multiplexer.vhd
     tb/vhdl/tb_common_operation_tree.vhd
@@ -197,7 +193,6 @@ test_bench_files =
     
     tb/vhdl/tb_tb_common_add_sub.vhd
     tb/vhdl/tb_tb_common_adder_tree.vhd
-    tb/vhdl/tb_tb_common_mem_bus.vhd
     tb/vhdl/tb_tb_common_fanout_tree.vhd
     tb/vhdl/tb_tb_common_multiplexer.vhd
     tb/vhdl/tb_tb_common_operation_tree.vhd
@@ -210,7 +205,6 @@ test_bench_files =
 
 regression_test_vhdl = 
     tb/vhdl/tb_common_fifo_rd.vhd
-    tb/vhdl/tb_common_mem_master_mux.vhd
     tb/vhdl/tb_common_mem_mux.vhd
     tb/vhdl/tb_common_paged_ram_crw_crw.vhd
     tb/vhdl/tb_common_pulser_us_ms_s.vhd
@@ -225,7 +219,6 @@ regression_test_vhdl =
 
     tb/vhdl/tb_tb_common_adder_tree.vhd
     tb/vhdl/tb_tb_common_add_sub.vhd
-    tb/vhdl/tb_tb_common_mem_bus.vhd
     tb/vhdl/tb_tb_common_fanout_tree.vhd
     tb/vhdl/tb_tb_common_multiplexer.vhd
     tb/vhdl/tb_tb_common_operation_tree.vhd
diff --git a/libraries/base/common/src/vhdl/common_pkg.vhd b/libraries/base/common/src/vhdl/common_pkg.vhd
index 4023ea913639562ed8c86cbafeadf4bbde0ac14c..fe7d12376a4fb6a861405c4c1f49cf099ec65fb6 100644
--- a/libraries/base/common/src/vhdl/common_pkg.vhd
+++ b/libraries/base/common/src/vhdl/common_pkg.vhd
@@ -123,8 +123,8 @@ PACKAGE common_pkg IS
   TYPE t_slv_512_arr     IS ARRAY (INTEGER RANGE <>) OF STD_LOGIC_VECTOR(511 DOWNTO 0);
   TYPE t_slv_1024_arr    IS ARRAY (INTEGER RANGE <>) OF STD_LOGIC_VECTOR(1023 DOWNTO 0);
  
-  CONSTANT c_boolean_arr     : t_boolean_arr     := (TRUE, FALSE);  -- array all possible values that can be iterated over
-  CONSTANT c_nat_boolean_arr : t_nat_boolean_arr := (TRUE, FALSE);  -- array all possible values that can be iterated over
+  CONSTANT c_boolean_arr     : t_boolean_arr     := (TRUE, FALSE);  -- array the two possible boolean values that can be iterated over
+  CONSTANT c_nat_boolean_arr : t_nat_boolean_arr := (TRUE, FALSE);  -- array the two possible boolean values that can be iterated over
  
   TYPE t_integer_matrix IS ARRAY (INTEGER RANGE <>, INTEGER RANGE <>) OF INTEGER;
   TYPE t_boolean_matrix IS ARRAY (INTEGER RANGE <>, INTEGER RANGE <>) OF BOOLEAN;
@@ -206,6 +206,9 @@ PACKAGE common_pkg IS
   FUNCTION orv( slv : STD_LOGIC_VECTOR) RETURN STD_LOGIC;  -- alias of vector_or
   FUNCTION xorv(slv : STD_LOGIC_VECTOR) RETURN STD_LOGIC;  -- alias of vector_xor
   
+  FUNCTION array_and(arr : t_nat_boolean_arr) RETURN BOOLEAN;
+  FUNCTION array_or( arr : t_nat_boolean_arr) RETURN BOOLEAN;
+  
   FUNCTION matrix_and(mat : t_sl_matrix; wi, wj : NATURAL) RETURN STD_LOGIC;  -- '1' when all matrix bits are '1' else '0'
   FUNCTION matrix_or( mat : t_sl_matrix; wi, wj : NATURAL) RETURN STD_LOGIC;  -- '0' when all matrix bits are '0' else '1'
   
@@ -296,6 +299,7 @@ PACKAGE common_pkg IS
   FUNCTION sel_n(sel : NATURAL; a, b, c, d, e, f, g, h, i, j : STRING) RETURN STRING;    -- 10
   
   FUNCTION array_init(init : STD_LOGIC; nof              : NATURAL) RETURN STD_LOGIC_VECTOR;  -- useful to init a unconstrained array of size 1
+  FUNCTION array_init(init :   BOOLEAN; nof              : NATURAL) RETURN t_nat_boolean_arr; -- useful to init a unconstrained array of size 1
   FUNCTION array_init(init,             nof              : NATURAL) RETURN t_natural_arr;     -- useful to init a unconstrained array of size 1
   FUNCTION array_init(init,             nof              : NATURAL) RETURN t_nat_natural_arr; -- useful to init a unconstrained array of size 1
   FUNCTION array_init(init,             nof, incr        : NATURAL) RETURN t_natural_arr;     -- useful to init an array with incrementing numbers
@@ -309,12 +313,21 @@ PACKAGE common_pkg IS
   FUNCTION init_slv_64_matrix(nof_a, nof_b, k : INTEGER) RETURN t_slv_64_matrix;  -- initialize all elements in t_slv_64_matrix to value k
   
   -- Concatenate two or more STD_LOGIC_VECTORs into a single STD_LOGIC_VECTOR or extract one of them from a concatenated STD_LOGIC_VECTOR
+  -- . Note that using func_slv_concat() without the BOOLEAN use_* is equivalent to using the 
+  --   slv concatenation operator & directly. However this overloaded func_slv_concat() is
+  --   still nice to have, because it shows the relation with the inverse func_slv_extract().
   FUNCTION func_slv_concat(  use_a, use_b, use_c, use_d, use_e, use_f, use_g : BOOLEAN; a, b, c, d, e, f, g : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
   FUNCTION func_slv_concat(  use_a, use_b, use_c, use_d, use_e, use_f        : BOOLEAN; a, b, c, d, e, f    : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
   FUNCTION func_slv_concat(  use_a, use_b, use_c, use_d, use_e               : BOOLEAN; a, b, c, d, e       : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
   FUNCTION func_slv_concat(  use_a, use_b, use_c, use_d                      : BOOLEAN; a, b, c, d          : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
   FUNCTION func_slv_concat(  use_a, use_b, use_c                             : BOOLEAN; a, b, c             : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
   FUNCTION func_slv_concat(  use_a, use_b                                    : BOOLEAN; a, b                : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_concat(                                                             a, b, c, d, e, f, g : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_concat(                                                             a, b, c, d, e, f    : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_concat(                                                             a, b, c, d, e       : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_concat(                                                             a, b, c, d          : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_concat(                                                             a, b, c             : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_concat(                                                             a, b                : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR;
   FUNCTION func_slv_concat_w(use_a, use_b, use_c, use_d, use_e, use_f, use_g : BOOLEAN; a_w, b_w, c_w, d_w, e_w, f_w, g_w : NATURAL) RETURN NATURAL;
   FUNCTION func_slv_concat_w(use_a, use_b, use_c, use_d, use_e, use_f        : BOOLEAN; a_w, b_w, c_w, d_w, e_w, f_w      : NATURAL) RETURN NATURAL;
   FUNCTION func_slv_concat_w(use_a, use_b, use_c, use_d, use_e               : BOOLEAN; a_w, b_w, c_w, d_w, e_w           : NATURAL) RETURN NATURAL;
@@ -327,6 +340,12 @@ PACKAGE common_pkg IS
   FUNCTION func_slv_extract( use_a, use_b, use_c, use_d                      : BOOLEAN; a_w, b_w, c_w, d_w                : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR;
   FUNCTION func_slv_extract( use_a, use_b, use_c                             : BOOLEAN; a_w, b_w, c_w                     : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR;
   FUNCTION func_slv_extract( use_a, use_b                                    : BOOLEAN; a_w, b_w                          : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_extract(                                                            a_w, b_w, c_w, d_w, e_w, f_w, g_w : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_extract(                                                            a_w, b_w, c_w, d_w, e_w, f_w      : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_extract(                                                            a_w, b_w, c_w, d_w, e_w           : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_extract(                                                            a_w, b_w, c_w, d_w                : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_extract(                                                            a_w, b_w, c_w                     : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR;
+  FUNCTION func_slv_extract(                                                            a_w, b_w                          : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR;
   
   FUNCTION TO_UINT(vec : STD_LOGIC_VECTOR) RETURN NATURAL;  -- beware: NATURAL'HIGH = 2**31-1, not 2*32-1, use TO_SINT to avoid warning
   FUNCTION TO_SINT(vec : STD_LOGIC_VECTOR) RETURN INTEGER;
@@ -742,6 +761,20 @@ PACKAGE BODY common_pkg IS
     RETURN vector_tree(slv, "XOR");
   END;
   
+  FUNCTION array_and(arr : t_nat_boolean_arr) RETURN BOOLEAN IS
+    VARIABLE v_slv : STD_LOGIC_VECTOR(arr'RANGE);
+  BEGIN
+    FOR I IN arr'RANGE LOOP v_slv(I) := sel_a_b(arr(I), '1', '0'); END LOOP;  -- wire map boolean arr to slv
+    RETURN sel_a_b(vector_and(v_slv) = '1', TRUE, FALSE);                     -- use vector_tree to determine result
+  END;
+  
+  FUNCTION array_or(arr : t_nat_boolean_arr) RETURN BOOLEAN IS
+    VARIABLE v_slv : STD_LOGIC_VECTOR(arr'RANGE);
+  BEGIN
+    FOR I IN arr'RANGE LOOP v_slv(I) := sel_a_b(arr(I), '1', '0'); END LOOP;  -- wire map boolean arr to slv
+    RETURN sel_a_b(vector_or(v_slv) = '1', TRUE, FALSE);                      -- use vector_tree to determine result
+  END;
+  
   FUNCTION matrix_and(mat : t_sl_matrix; wi, wj : NATURAL) RETURN STD_LOGIC IS
     VARIABLE v_mat    : t_sl_matrix(0 TO wi-1, 0 TO wj-1) := mat;  -- map to fixed range
     VARIABLE v_result : STD_LOGIC := '1';
@@ -1310,6 +1343,15 @@ PACKAGE BODY common_pkg IS
     RETURN v_arr;
   END;
   
+  FUNCTION array_init(init : BOOLEAN; nof : NATURAL) RETURN t_nat_boolean_arr IS
+    VARIABLE v_arr : t_nat_boolean_arr(0 TO nof-1);
+  BEGIN
+    FOR I IN v_arr'RANGE LOOP
+      v_arr(I) := init;
+    END LOOP;
+    RETURN v_arr;
+  END;
+  
   FUNCTION array_init(init, nof : NATURAL) RETURN t_natural_arr IS
     VARIABLE v_arr : t_natural_arr(0 TO nof-1);
   BEGIN
@@ -1459,6 +1501,36 @@ PACKAGE BODY common_pkg IS
     RETURN func_slv_concat(use_a, use_b, FALSE, FALSE, FALSE, FALSE, FALSE, a, b, "0", "0", "0", "0", "0");
   END func_slv_concat;
   
+  FUNCTION func_slv_concat(a, b, c, d, e, f, g : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_concat(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, a, b, c, d, e, f, g);
+  END func_slv_concat;
+  
+  FUNCTION func_slv_concat(a, b, c, d, e, f : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_concat(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, a, b, c, d, e, f);
+  END func_slv_concat;
+  
+  FUNCTION func_slv_concat(a, b, c, d, e: STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_concat(TRUE, TRUE, TRUE, TRUE, TRUE, a, b, c, d, e);
+  END func_slv_concat;
+  
+  FUNCTION func_slv_concat(a, b, c, d : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_concat(TRUE, TRUE, TRUE, TRUE, a, b, c, d);
+  END func_slv_concat;
+  
+  FUNCTION func_slv_concat(a, b, c : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_concat(TRUE, TRUE, TRUE, a, b, c);
+  END func_slv_concat;
+  
+  FUNCTION func_slv_concat(a, b : STD_LOGIC_VECTOR) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_concat(TRUE, TRUE, a, b);
+  END func_slv_concat;
+  
   FUNCTION func_slv_concat_w(use_a, use_b, use_c, use_d, use_e, use_f, use_g : BOOLEAN; a_w, b_w, c_w, d_w, e_w, f_w, g_w : NATURAL) RETURN NATURAL IS
     VARIABLE v_len : NATURAL := 0;
   BEGIN
@@ -1569,6 +1641,36 @@ PACKAGE BODY common_pkg IS
     RETURN func_slv_extract(use_a, use_b, FALSE, FALSE, FALSE, FALSE, FALSE, a_w, b_w, 0, 0, 0, 0, 0, vec, sel);
   END func_slv_extract;
   
+  FUNCTION func_slv_extract(a_w, b_w, c_w, d_w, e_w, f_w, g_w : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_extract(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, a_w, b_w, c_w, d_w, e_w, f_w, g_w, vec, sel);
+  END func_slv_extract;
+
+  FUNCTION func_slv_extract(a_w, b_w, c_w, d_w, e_w, f_w : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_extract(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, a_w, b_w, c_w, d_w, e_w, f_w, vec, sel);
+  END func_slv_extract;
+
+  FUNCTION func_slv_extract(a_w, b_w, c_w, d_w, e_w : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_extract(TRUE, TRUE, TRUE, TRUE, TRUE, a_w, b_w, c_w, d_w, e_w, vec, sel);
+  END func_slv_extract;
+
+  FUNCTION func_slv_extract(a_w, b_w, c_w, d_w : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_extract(TRUE, TRUE, TRUE, TRUE, a_w, b_w, c_w, d_w, vec, sel);
+  END func_slv_extract;
+
+  FUNCTION func_slv_extract(a_w, b_w, c_w : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_extract(TRUE, TRUE, TRUE, a_w, b_w, c_w, vec, sel);
+  END func_slv_extract;
+
+  FUNCTION func_slv_extract(a_w, b_w : NATURAL; vec : STD_LOGIC_VECTOR; sel : NATURAL) RETURN STD_LOGIC_VECTOR IS
+  BEGIN
+    RETURN func_slv_extract(TRUE, TRUE, a_w, b_w, vec, sel);
+  END func_slv_extract;
+
   
   FUNCTION TO_UINT(vec : STD_LOGIC_VECTOR) RETURN NATURAL IS
   BEGIN
diff --git a/libraries/base/common/tb/vhdl/tb_common_mem_bus.vhd b/libraries/base/common/tb/vhdl/tb_common_mem_bus.vhd
deleted file mode 100644
index ed4bb56f4d0a4556f216165683a8dd45b02a8c9d..0000000000000000000000000000000000000000
--- a/libraries/base/common/tb/vhdl/tb_common_mem_bus.vhd
+++ /dev/null
@@ -1,177 +0,0 @@
--------------------------------------------------------------------------------
---
--- Copyright 2020
--- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
--- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
--- 
--- 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.
---
--------------------------------------------------------------------------------
-
--------------------------------------------------------------------------------
--- 
--- Author: E. Kooistra
--- Purpose: Test bench for common_mem_bus.vhd
--- Remark:
--- . This test bench covers:
---   . g_nof_slaves >= 1
---   . g_pipeline_mosi, g_pipeline_miso
---   . g_rd_latency >= 1 (using 0 is supported by common_mem_bus, but not by
---     the common_ram_r_w in u_slaves)
---   . same g_rd_latency for all slaves
---   . same g_width for all slaves
---   . regular base address spacing of slaves in c_base_arr
--- . The common_mem_bus.vhd can support a list of arbitrary width slaves, but
---   this tb_common_mem_bus test bench uses an array of fixed width slaves.
---   It is considered sufficient coverage for this tb and the corresponding
---   multi tb_tb to also only support regular c_base_arr, same g_rd_latency,
---   and same g_width for all slaves. The tb_common_mem_master_mux also uses a
---   common_mem_bus.vhd and the tb_common_mem_master_mux does uses an array of
---   arbitrary width slaves.
---   
--------------------------------------------------------------------------------
-
-LIBRARY IEEE;
-USE IEEE.STD_LOGIC_1164.ALL;
-USE IEEE.NUMERIC_STD.ALL;
-USE work.common_pkg.ALL;
-USE work.common_mem_pkg.ALL;
-USE work.tb_common_pkg.ALL;
-USE work.tb_common_mem_pkg.ALL;
-
-ENTITY tb_common_mem_bus IS
- GENERIC (
-    g_nof_slaves    : POSITIVE := 2;       -- Number of slave memory interfaces on the MM bus array.
-    g_base_offset   : NATURAL := 0;        -- Address of first slave on the MM bus
-    g_width_w       : POSITIVE := 4;       -- Address width of each slave memory in the MM bus array.
-    g_rd_latency    : NATURAL := 1;        -- Read latency of the slaves slave
-    g_pipeline_mosi : BOOLEAN := FALSE;
-    g_pipeline_miso : BOOLEAN := TRUE
-  );
-END tb_common_mem_bus;
-
--- Usage:
---   > as 10
---   > run -all
-
-
-ARCHITECTURE tb OF tb_common_mem_bus IS
-
-  CONSTANT mm_clk_period   : TIME    := 10 ns;
-
-  CONSTANT c_slave_span      : NATURAL := 2**g_width_w;
-  CONSTANT c_base_arr        : t_nat_natural_arr := array_init(g_base_offset, g_nof_slaves, c_slave_span);  -- Address base per slave
-  CONSTANT c_width_arr       : t_nat_natural_arr := array_init(    g_width_w, g_nof_slaves);                -- Address width per slave
-  CONSTANT c_rd_latency_arr  : t_nat_natural_arr := array_init( g_rd_latency, g_nof_slaves);                -- Read latency per slave
-  
-  CONSTANT c_mosi_latency    : NATURAL := sel_a_b(g_pipeline_mosi, 1, 0);
-  CONSTANT c_miso_latency    : NATURAL := sel_a_b(g_pipeline_miso, 1, 0);
-  CONSTANT c_read_latency    : NATURAL := c_mosi_latency + g_rd_latency + c_miso_latency;
-
-  CONSTANT c_data_w     : NATURAL := 32;
-  CONSTANT c_test_ram   : t_c_mem := (latency  => g_rd_latency,
-                                      adr_w    => g_width_w,
-                                      dat_w    => c_data_w,
-                                      nof_dat  => 2**g_width_w,
-                                      init_sl  => '0');
-  SIGNAL mm_rst   : STD_LOGIC;
-  SIGNAL mm_clk   : STD_LOGIC := '1';
-  SIGNAL tb_end   : STD_LOGIC;
-
-  SIGNAL mosi_arr : t_mem_mosi_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_mosi_rst);
-  SIGNAL miso_arr : t_mem_miso_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_miso_rst);
-  SIGNAL mosi     : t_mem_mosi := c_mem_mosi_rst;
-  SIGNAL miso     : t_mem_miso := c_mem_miso_rst;
-
-  -- Debug signals for monitoring in simulation Wave window
-  SIGNAL dbg_c_base_arr        : t_nat_natural_arr(0 TO g_nof_slaves-1) := c_base_arr;
-  SIGNAL dbg_c_width_arr       : t_nat_natural_arr(0 TO g_nof_slaves-1) := c_width_arr;
-  SIGNAL dbg_c_rd_latency_arr  : t_nat_natural_arr(0 TO g_nof_slaves-1) := c_rd_latency_arr;
-  
-BEGIN
-
-  mm_clk <= NOT mm_clk OR tb_end AFTER mm_clk_period/2;
-  mm_rst <= '1', '0' AFTER mm_clk_period*5;
-
-  p_stimuli : PROCESS
-    VARIABLE v_data : INTEGER;
-  BEGIN
-    tb_end <= '0';
-    mosi   <= c_mem_mosi_rst;
-    
-    -- Wait until reset is released
-    proc_common_wait_until_low(mm_clk, mm_rst);
-    proc_common_wait_some_cycles(mm_clk, 10);
-    
-    -- Write the whole memory range
-    FOR I IN 0 TO g_nof_slaves-1 LOOP
-      FOR J IN 0 TO 2**g_width_w-1 LOOP
-        proc_mem_mm_bus_wr(g_base_offset + I*2**g_width_w + J, I+J, mm_clk, mosi);
-      END LOOP;
-    END LOOP;
-
-    -- Read back the whole range and check if data is as expected
-    FOR I IN 0 TO g_nof_slaves-1 LOOP
-      FOR J IN 0 TO 2**g_width_w-1 LOOP
-        proc_mem_mm_bus_rd(g_base_offset + I*2**g_width_w + J, mm_clk, mosi);
-        proc_common_wait_some_cycles(mm_clk, c_read_latency);
-        v_data := TO_UINT(miso.rddata(31 DOWNTO 0));
-        IF v_data /= I+J THEN
-          REPORT "Error! Readvalue is not as expected" SEVERITY ERROR;
-        END IF;
-      END LOOP;
-    END LOOP;
-
-    proc_common_wait_some_cycles(mm_clk, 10);
-    tb_end <= '1';
-    WAIT;
-  END PROCESS;
-
-  u_slaves : FOR I IN 0 TO g_nof_slaves-1 GENERATE
-    u_ram : ENTITY work.common_ram_r_w
-    GENERIC MAP (
-      g_ram       => c_test_ram,
-      g_init_file => "UNUSED"
-    )
-    PORT MAP (
-      rst       => mm_rst,
-      clk       => mm_clk,
-      clken     => '1',
-      wr_en     => mosi_arr(I).wr,
-      wr_adr    => mosi_arr(I).address(g_width_w-1 DOWNTO 0),
-      wr_dat    => mosi_arr(I).wrdata(c_data_w-1 DOWNTO 0),
-      rd_en     => mosi_arr(I).rd,
-      rd_adr    => mosi_arr(I).address(g_width_w-1 DOWNTO 0),
-      rd_dat    => miso_arr(I).rddata(c_data_w-1 DOWNTO 0),
-      rd_val    => miso_arr(I).rdval
-    );
-  END GENERATE;
-
-  d_dut: ENTITY work.common_mem_bus
-  GENERIC MAP (
-    g_nof_slaves      => g_nof_slaves,
-    g_base_arr        => c_base_arr,
-    g_width_arr       => c_width_arr,
-    g_rd_latency_arr  => c_rd_latency_arr,
-    g_pipeline_mosi   => g_pipeline_mosi,
-    g_pipeline_miso   => g_pipeline_miso
-  )
-  PORT MAP (
-    mm_clk         => mm_clk,
-    master_mosi    => mosi,
-    master_miso    => miso,
-    slave_mosi_arr => mosi_arr,
-    slave_miso_arr => miso_arr
-  );
-
-END tb;
diff --git a/libraries/base/common/tb/vhdl/tb_common_mem_master_mux.vhd b/libraries/base/common/tb/vhdl/tb_common_mem_master_mux.vhd
deleted file mode 100644
index bab3efcee8670c745fb3cfedd34c336f3fc0fac2..0000000000000000000000000000000000000000
--- a/libraries/base/common/tb/vhdl/tb_common_mem_master_mux.vhd
+++ /dev/null
@@ -1,196 +0,0 @@
--------------------------------------------------------------------------------
---
--- Copyright 2020
--- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
--- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
--- 
--- 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.
---
--------------------------------------------------------------------------------
-
--------------------------------------------------------------------------------
--- 
--- Author: E. Kooistra
--- Purpose: Test bench for common_mem_master_mux.vhd and also common_mem_bus
--- Description:
---   The test bench uses common_mem_master_mux to access a RAM via an array of
---   masters. The array of masters is modelled using a stimuli from a single
---   master that get demultiplexed to the array of masters using
---   common_mem_bus. The address space of the RAM is defined by the g_base_arr
---   and g_width_arr that define the common_mem_bus. Therefore this test bench
---   implicitely also verifies common_mem_bus.vhd.
---   
---               stimuli            master              mux
---               mosi               mosi_arr            mosi
---   p_stimuli ----------> common -----------> common --------> RAM
---                         mem                 mem
---                         bus                 master
---                                             mux
---
--- Remark:
---   In an application it is typical to use common_mem_master_mux to connect
---   mulitple masters to multiple slabes via a common_mem_bus MM bus.
--------------------------------------------------------------------------------
-
-LIBRARY IEEE;
-USE IEEE.STD_LOGIC_1164.ALL;
-USE IEEE.NUMERIC_STD.ALL;
-USE work.common_pkg.ALL;
-USE work.common_mem_pkg.ALL;
-USE work.tb_common_pkg.ALL;
-USE work.tb_common_mem_pkg.ALL;
-
-ENTITY tb_common_mem_master_mux IS
- GENERIC (
-    g_nof_masters       : POSITIVE := 2;   -- Number of master memory interfaces on the MM bus array.
-    g_base_arr          : t_nat_natural_arr := (0, 256);  -- Address base per slave port of common_mem_bus
-    g_width_arr         : t_nat_natural_arr := (4,   8);  -- Address width per slave port of common_mem_bus
-    g_pipeline_bus_mosi : BOOLEAN := FALSE;
-    g_pipeline_bus_miso : BOOLEAN := FALSE
-  );
-END tb_common_mem_master_mux;
-
--- Usage:
---   > as 10
---   > run -all
-
-
-ARCHITECTURE tb OF tb_common_mem_master_mux IS
-
-  CONSTANT mm_clk_period   : TIME    := 10 ns;
-  
-  CONSTANT c_bus_mosi_latency   : NATURAL := sel_a_b(g_pipeline_bus_mosi, 1, 0);
-  CONSTANT c_bus_miso_latency   : NATURAL := sel_a_b(g_pipeline_bus_miso, 1, 0);
-  CONSTANT c_ram_rd_latency     : NATURAL := 1; 
-  CONSTANT c_ram_rd_latency_arr : t_nat_natural_arr := array_init(c_ram_rd_latency, g_nof_masters);
-  
-  CONSTANT c_read_latency    : NATURAL := c_bus_mosi_latency + c_ram_rd_latency + c_bus_miso_latency;
-
-  CONSTANT c_addr_w          : NATURAL := largest(ceil_log2(largest(g_base_arr)), largest(g_width_arr)) + 1;
-  CONSTANT c_data_w          : NATURAL := 32;
-  CONSTANT c_test_ram        : t_c_mem := (latency  => c_ram_rd_latency,
-                                           adr_w    => c_addr_w,
-                                           dat_w    => c_data_w,
-                                           nof_dat  => 2**c_addr_w,
-                                           init_sl  => '0');
-  SIGNAL mm_rst           : STD_LOGIC;
-  SIGNAL mm_clk           : STD_LOGIC := '1';
-  SIGNAL tb_end           : STD_LOGIC;
-
-  SIGNAL stimuli_mosi     : t_mem_mosi := c_mem_mosi_rst;
-  SIGNAL stimuli_miso     : t_mem_miso := c_mem_miso_rst;
-  SIGNAL master_mosi_arr  : t_mem_mosi_arr(0 TO g_nof_masters-1) := (OTHERS=>c_mem_mosi_rst);
-  SIGNAL master_miso_arr  : t_mem_miso_arr(0 TO g_nof_masters-1) := (OTHERS=>c_mem_miso_rst);
-  SIGNAL mux_mosi         : t_mem_mosi := c_mem_mosi_rst;
-  SIGNAL mux_miso         : t_mem_miso := c_mem_miso_rst;
-
-BEGIN
-
-  mm_clk <= NOT mm_clk OR tb_end AFTER mm_clk_period/2;
-  mm_rst <= '1', '0' AFTER mm_clk_period*5;
-
-  p_stimuli : PROCESS
-    VARIABLE v_base : NATURAL;
-    VARIABLE v_span : NATURAL;
-    VARIABLE v_data : INTEGER;
-  BEGIN
-    tb_end <= '0';
-    stimuli_mosi <= c_mem_mosi_rst;
-    
-    -- Wait until reset is released
-    proc_common_wait_until_low(mm_clk, mm_rst);
-    proc_common_wait_some_cycles(mm_clk, 10);
-    
-    -- Repeat twice to have wr all, rd all, wr all, rd all
-    FOR R IN 0 TO 1 LOOP
-      -- Write the whole memory range
-      FOR I IN 0 TO g_nof_masters-1 LOOP
-        v_base := g_base_arr(I);
-        v_span := 2**g_width_arr(I);
-        FOR J IN 0 TO v_span-1 LOOP
-          proc_mem_mm_bus_wr(v_base + J, R+J, mm_clk, stimuli_mosi);
-        END LOOP;
-      END LOOP;
-      
-      -- Read back the whole range in reverse order and check if data is as expected
-      FOR I IN g_nof_masters-1 DOWNTO 0 LOOP
-        v_base := g_base_arr(I);
-        v_span := 2**g_width_arr(I);
-        FOR J IN v_span-1 DOWNTO 0 LOOP
-          proc_mem_mm_bus_rd(v_base + J, mm_clk, stimuli_mosi);
-          proc_common_wait_some_cycles(mm_clk, c_read_latency);
-          v_data := TO_UINT(stimuli_miso.rddata(31 DOWNTO 0));
-          IF v_data /= R+J THEN
-            REPORT "Error! Readvalue is not as expected" SEVERITY ERROR;
-          END IF;
-        END LOOP;
-      END LOOP;
-    END LOOP;
-
-    proc_common_wait_some_cycles(mm_clk, 10);
-    tb_end <= '1';
-    WAIT;
-  END PROCESS;
-
-  -- Model multiple masters using stimuli from a single master
-  u_masters : ENTITY work.common_mem_bus
-  GENERIC MAP (
-    g_nof_slaves      => g_nof_masters,
-    g_base_arr        => g_base_arr,
-    g_width_arr       => g_width_arr,
-    g_rd_latency_arr  => c_ram_rd_latency_arr,
-    g_pipeline_mosi   => g_pipeline_bus_mosi,
-    g_pipeline_miso   => g_pipeline_bus_miso
-  )
-  PORT MAP (
-    mm_clk         => mm_clk,
-    master_mosi    => stimuli_mosi,
-    master_miso    => stimuli_miso,
-    slave_mosi_arr => master_mosi_arr,
-    slave_miso_arr => master_miso_arr
-  );
-  
-  -- DUT = device under test
-  u_dut: ENTITY work.common_mem_master_mux
-  GENERIC MAP (
-    g_nof_masters     => g_nof_masters,
-    g_rd_latency_min  => c_read_latency
-  )
-  PORT MAP (
-    mm_clk          => mm_clk,
-    master_mosi_arr => master_mosi_arr,
-    master_miso_arr => master_miso_arr,
-    mux_mosi        => mux_mosi,
-    mux_miso        => mux_miso
-  );
-
-  -- Model master access to MM bus with multiple slaves using a single RAM
-  u_ram : ENTITY work.common_ram_r_w
-  GENERIC MAP (
-    g_ram       => c_test_ram,
-    g_init_file => "UNUSED"
-  )
-  PORT MAP (
-    rst       => mm_rst,
-    clk       => mm_clk,
-    wr_en     => mux_mosi.wr,
-    wr_adr    => mux_mosi.address(c_addr_w-1 DOWNTO 0),
-    wr_dat    => mux_mosi.wrdata(c_data_w-1 DOWNTO 0),
-    rd_en     => mux_mosi.rd,
-    rd_adr    => mux_mosi.address(c_addr_w-1 DOWNTO 0),
-    rd_dat    => mux_miso.rddata(c_data_w-1 DOWNTO 0),
-    rd_val    => mux_miso.rdval
-  );
-
-
-END tb;
diff --git a/libraries/base/common/tb/vhdl/tb_tb_common_mem_bus.vhd b/libraries/base/common/tb/vhdl/tb_tb_common_mem_bus.vhd
deleted file mode 100644
index 2a98bc21908d137d16e330aee1f6889ab95bd884..0000000000000000000000000000000000000000
--- a/libraries/base/common/tb/vhdl/tb_tb_common_mem_bus.vhd
+++ /dev/null
@@ -1,54 +0,0 @@
--------------------------------------------------------------------------------
---
--- Copyright 2020
--- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
--- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
--- 
--- 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.
---
--------------------------------------------------------------------------------
-
--------------------------------------------------------------------------------
--- 
--- Author: E. Kooistra
--- Purpose: Multi test bench for common_mem_bus.vhd
---
--------------------------------------------------------------------------------
-
-LIBRARY IEEE;
-USE IEEE.std_logic_1164.ALL;
-USE work.common_pkg.ALL;
-
-ENTITY tb_tb_common_mem_bus IS
-END tb_tb_common_mem_bus;
-
-ARCHITECTURE tb OF tb_tb_common_mem_bus IS
-  SIGNAL tb_end : STD_LOGIC := '0';  -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end'
-BEGIN
-  -- Usage:
-  -- > as 4
-  -- > run -all
-
-  -- g_nof_slaves    : POSITIVE := 2;       -- Number of slave memory interfaces on the MM bus array.
-  -- g_base_offset   : NATURAL := 0;        -- Address of first slave on the MM bus
-  -- g_width_w       : POSITIVE := 4;       -- Address width of each slave memory in the MM bus array.
-  -- g_rd_latency    : NATURAL := 1;        -- Read latency of the slaves slave
-  -- g_pipeline_mosi : BOOLEAN := FALSE;
-  -- g_pipeline_miso : BOOLEAN := FALSE
-    
-  u_rd_latency_1       : ENTITY work.tb_common_mem_bus GENERIC MAP (16,      0, 3, 1, FALSE, FALSE);
-  u_base_offset        : ENTITY work.tb_common_mem_bus GENERIC MAP (16, 3*2**4, 4, 1, FALSE, FALSE);
-  u_pipeline_mosi      : ENTITY work.tb_common_mem_bus GENERIC MAP ( 3,      0, 4, 1, TRUE, FALSE);
-  u_pipeline_mosi_miso : ENTITY work.tb_common_mem_bus GENERIC MAP ( 3,      0, 4, 1, TRUE, TRUE);
-  
-END tb;
diff --git a/libraries/base/mm/hdllib.cfg b/libraries/base/mm/hdllib.cfg
index b3180241c7a1cc59125c22d754d1cd75fd96bd30..cd1b224af35e9aa0140662040d39fa5723b74aea 100644
--- a/libraries/base/mm/hdllib.cfg
+++ b/libraries/base/mm/hdllib.cfg
@@ -8,21 +8,39 @@ synth_files =
     src/vhdl/mm_fields.vhd
     tb/vhdl/mm_file_pkg.vhd
     tb/vhdl/mm_file_unb_pkg.vhd
+    
     src/verilog/timeout.v
     src/verilog/wbs_arbiter.v
     src/vhdl/mm_arbiter.vhd
+    
+    src/vhdl/mm_pipeline.vhd
+    src/vhdl/mm_latency_adapter.vhd
+    src/vhdl/mm_slave_enable.vhd
+    src/vhdl/mm_bus_comb.vhd
+    src/vhdl/mm_bus_pipe.vhd
+    src/vhdl/mm_bus.vhd
+    src/vhdl/mm_master_mux.vhd
+    src/vhdl/mm_slave_mux.vhd
 
 test_bench_files =
     tb/vhdl/mm_file.vhd
     tb/vhdl/tb_mm_file.vhd
+
+    tb/vhdl/mm_waitrequest_model.vhd
+    tb/vhdl/tb_mm_bus.vhd
+    tb/vhdl/tb_mm_master_mux.vhd
+
     tb/vhdl/tb_tb_mm_file.vhd
+    tb/vhdl/tb_tb_mm_bus.vhd
+    tb/vhdl/tb_tb_mm_master_mux.vhd
 
 regression_test_vhdl = 
     tb/vhdl/tb_tb_mm_file.vhd
+    tb/vhdl/tb_tb_mm_bus.vhd
+    tb/vhdl/tb_tb_mm_master_mux.vhd
 
 
 [modelsim_project_file]
 
 
 [quartus_project_file]
-
diff --git a/libraries/base/mm/src/vhdl/mm_bus.vhd b/libraries/base/mm/src/vhdl/mm_bus.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..0b447521408adcd9d45d60cdb8cd48983c5acfa1
--- /dev/null
+++ b/libraries/base/mm/src/vhdl/mm_bus.vhd
@@ -0,0 +1,183 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: Connect a single MM master interface to a list of MM slave
+--          interfaces using mm_bus_pipe.
+-- Description:
+--   In addition to mm_bus_pipe this mm_bus takes care of:
+--   - not connected slaves
+--   - slaves that do not need flow control
+--
+--                                     FOR g_nof_slaves:
+--                             g_slave_enable_arr
+--                             g_waitrequest_arr
+--                             g_rd_latency_arr
+--                              |          |
+--                              |          |
+--        g_base_arr            |          |
+--        g_width_arr           |          |
+--        g_pipeline_mosi       |          |
+--        g_pipeline_miso_rdval |          |
+--        g_pipeline_miso_wait  |          |
+--                          |   |          |
+--                        __v___v_     ____v___
+--   master_miso <-------| mm_bus |<--| mm     |<-- slave_miso_arr
+--   master_mosi ------->| pipe   |-->| slave  |--> slave_mosi_arr
+--                       |________|   | enable |
+--                                    |________|
+--   The mm_bus_comb takes care of:
+--   - MM bus multiplexer between master and slaves
+--   - MM slave address allocation on the MM bus
+--   The mm_bus_pipe takes care of:
+--   - Pipelining the MM bus, the mm_bus_comb and mm_slave_enable are
+--     combinatorial.
+--
+-- Usage:
+--   The ascii drawing shows how this mm_bus can be used in combination
+--   with other MM bus components to create an memory mapped bus:
+--
+--   . mm_bus        : connects a master to multiple independent slaves
+--   . mm_slave_mux  : connects an array of slave to a single slave port
+--   . mm_master_mux : connects mulitple masters to a single slave
+--
+--                mm_slave_mux
+--         mm_bus     |---S
+--            |-------|---S
+--            |---S   |---S
+--       M ---|
+--            |---S
+--            |---S
+--            |-------|
+--            |---S   |---S
+--                    |
+--       M -----------|
+--                mm_master_mux
+--
+--     The mm_slave_mux and mm_master_mux should typically be combinatorial,
+--     because all MM bus pipelining is concentrated in mm_bus_pipe.
+--
+--   * The mm_slave_mux is useful to present an array of equal slave MM
+--     ports via a single port on the MM bus. Otherwise the mm_bus could
+--     instead directly present each slave MM array port.
+--     The mm_slave_mux introduces hierarchy in the MM bus structure. This
+--     can help to influcence the timing closure. Using only mm_bus or
+--     the a combination of mm_bus and mm_slave_mux can help to steer
+--     where pipelining is inserted in the MM bus.
+--   * The MM bus based on mm_bus could be automatically generated by ARGS
+--     based on a set of MM slave ports described in YAML configuration
+--     files.
+--
+-- Limitations --> see mm_bus_comb
+--
+-- Todo (only if necessary):
+-- * The mm_bus assumes that the MM slave will eventually acknowledge an
+--   mosi.wr or rd access by pulling miso.waitrequest low. If the MM slave
+--   malfunctions then the MM bus access will stall. Therefore a MM slave
+--   port that uses mosi flow control should also support a waitrequest
+--   timeout mechanism. Such a waitrequest timeout mechanism could be made
+--   part of mm_slave_enable.
+-- * The master can then be informed about a failing mosi access using
+--   the miso.response field of the Avalon bus. A failing mosi access can
+--   be due to a not connected slave, an out-of-range address, a slave that
+--   times out on flow control.
+--
+-------------------------------------------------------------------------------
+
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+
+ENTITY mm_bus IS
+  GENERIC (
+    g_nof_slaves          : POSITIVE;           -- Number of MM slave interfaces on the bus
+    g_base_arr            : t_nat_natural_arr;  -- Address base per slave
+    g_width_arr           : t_nat_natural_arr;  -- Address width per slave
+    g_rd_latency_arr      : t_nat_natural_arr;  -- Read latency per slave
+    g_slave_enable_arr    : t_nat_boolean_arr;  -- Use FALSE for not connected slaves, else TRUE
+    g_waitrequest_arr     : t_nat_boolean_arr;  -- Enable waitrequest flow control per slave, else fixed '0'
+    g_pipeline_mosi       : BOOLEAN := FALSE;   -- Pipeline MM access (wr, rd)
+    g_pipeline_miso_rdval : BOOLEAN := FALSE;   -- Pipeline MM read (rdval)
+    g_pipeline_miso_wait  : BOOLEAN := FALSE    -- Pipeline MM access flow control (waitrequest)
+  );
+  PORT (
+    mm_rst         : IN  STD_LOGIC := '0';
+    mm_clk         : IN  STD_LOGIC := '0';
+    master_mosi    : IN  t_mem_mosi;
+    master_miso    : OUT t_mem_miso;
+    slave_mosi_arr : OUT t_mem_mosi_arr(0 TO g_nof_slaves-1);
+    slave_miso_arr : IN  t_mem_miso_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_miso_rst)
+  );
+END mm_bus;
+
+ARCHITECTURE str OF mm_bus IS
+
+  SIGNAL bus_mosi_arr   : t_mem_mosi_arr(0 TO g_nof_slaves-1);
+  SIGNAL bus_miso_arr   : t_mem_miso_arr(0 TO g_nof_slaves-1);
+
+BEGIN
+
+  -- MM bus
+  u_mm_bus_pipe : ENTITY work.mm_bus_pipe
+  GENERIC MAP (
+    g_nof_slaves          => g_nof_slaves,
+    g_base_arr            => g_base_arr,
+    g_width_arr           => g_width_arr,
+    g_rd_latency_arr      => g_rd_latency_arr,
+    g_waitrequest_arr     => g_waitrequest_arr,
+    g_pipeline_mosi       => g_pipeline_mosi,
+    g_pipeline_miso_rdval => g_pipeline_miso_rdval,
+    g_pipeline_miso_wait  => g_pipeline_miso_wait
+  )
+  PORT MAP (
+    mm_rst         => mm_rst,
+    mm_clk         => mm_clk,
+    master_mosi    => master_mosi,
+    master_miso    => master_miso,
+    slave_mosi_arr => bus_mosi_arr,
+    slave_miso_arr => bus_miso_arr
+  );
+
+  -- The MM bus interface with the MM slaves
+  gen_slave_ports : FOR I IN 0 TO g_nof_slaves-1 GENERATE
+    -- Rewire not connected slaves and slave that do not need mosi flow control via miso.waitrequest
+    u_slave_enable : ENTITY work.mm_slave_enable
+    GENERIC MAP (
+      g_enable       => g_slave_enable_arr(I),
+      g_waitrequest  => g_waitrequest_arr(I),
+      g_rd_latency   => g_rd_latency_arr(I)
+    )
+    PORT MAP (
+      mm_rst        => mm_rst,
+      mm_clk        => mm_clk,
+      -- MM input RL = 1
+      in_mosi       => bus_mosi_arr(I),
+      in_miso       => bus_miso_arr(I),
+      -- MM output RL = 0
+      out_mosi      => slave_mosi_arr(I),
+      out_miso      => slave_miso_arr(I)
+    );
+  END GENERATE;
+
+END str;
diff --git a/libraries/base/common/src/vhdl/common_mem_bus.vhd b/libraries/base/mm/src/vhdl/mm_bus_comb.vhd
similarity index 53%
rename from libraries/base/common/src/vhdl/common_mem_bus.vhd
rename to libraries/base/mm/src/vhdl/mm_bus_comb.vhd
index 32078476336ad8c684d3ec53fafc0d2231994b51..4fda2cd307a9c12189e85bbfbf5522b4fe04a364 100644
--- a/libraries/base/common/src/vhdl/common_mem_bus.vhd
+++ b/libraries/base/mm/src/vhdl/mm_bus_comb.vhd
@@ -1,233 +1,216 @@
--------------------------------------------------------------------------------
---
--- Copyright 2020
--- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
--- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
--- 
--- 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.
---
--------------------------------------------------------------------------------
-
--------------------------------------------------------------------------------
--- 
--- Author: E. Kooistra
--- Purpose: Connect a single MM master interface to a list of MM slave
---          interfaces
--- Description:
--- * MM bus
---   The common_mem_bus creates a memory mapped (MM) bus that connects read
---   and write accesses from the master interface to the addressed slave
---   interface. There is one master that controls the bus and there are
---   g_nof_slaves on the bus. Per slave the start address and address span
---   have to be specified via g_base_arr and g_width_arr.
--- 
--- * Slave allocation
---   The slaves have to be located on the bus such that the MSbits of the 
---   global address can be used to select the slave and the LSbits of the
---   global address can directly be used to select the address within the 
---   slave. Therefore:
---   . The width of a slave is the power of 2 that fits the address range of
---     the slave.
---   . The span of a slave is 2**width.
---   . The base address of a slave has to be a power of 2 multiple of the
---     slave span.
---
--- * The mm_clk is only used when there is a slave with read latency > 0 or
---   when the MM bus uses pipelining.
---
--- * Read latency
---   For read accesses a slave will typically have a read latency > 0, which
---   means that when the rd and address are active, then it takes read
---   latency number of clock cycles until the rddata becomes available. The
---   read latency can be specified per slave via g_rd_latency_arr.
---   The index_pipeline is used to support that a new wr access or rd access
---   can already start, while a current rd access still has to finish with
---   a rdval. Without the index_pipeline the master would have to wait with
---   a new rd or wr access to another slave until the read response from the
---   current slave has finished.
---                                          ________
---                                          | pipe |
---   master_mosi.address[h:w] = index --+-->| line |--\
---                                      |   |______|  |
---                                      |             |
---                                      v             |
---    master_mosi --> slave_mosi_arr.wr[ ]----------------> slave_mosi_arr
---                                   rd               |
---                                                    v
---    master_miso <--------------------slave_miso_arr[ ]<-- slave_miso_arr
---
---   A limitation is that if one slave has a read latency of 2 and another
---   slave has a read latency of 1 then it is not possible to access them
---   without a gap of 1 mm_clk cycle, because the rdval will then be active
---   simultaneously from both slaves. Therefore the master can only use
---   random read access between slaves if all slaves have the same read
---   latency. For slaves that have larger read latency the master must
---   insert an gap, before it can read a slave that has less read latency.
---   
--- * Pipelining
---   Default the common_mm_bus is combinatorial, so there is no pipelining
---   between the master interface and the slave interfaces. If possible do not
---   use pipelining of mosi and miso to avoid increasing the read latency and
---   achieve timing closure by lower clock rate for the MM bus. Pipelining the
---   MM bus can be necessary to achieve timing closure:
---   . g_pipeline_mosi
---     Pipelining mosi write accesses introduces an extra latency from master
---     to slave, which is typically not a problem. Pipelining mosi read
---     accesses increases the read latency between accessing the slave and
---     getting the rddata. Using a different pipelining for the wr and the rd
---     pulse would yield a different pipelining of the address for write and
---     for read, which is akward. Therefore assume that both mosi write and
---     mosi read have the same pipelining.
---   . g_pipeline_miso
---     Pipelining the miso read data increases the read latency.
---   The total write latency from master to slave is c_mosi_latency.
---   The total read latency from master via slave back to master is
---   c_mosi_latency + g_rd_latency_arr of the selected slave + c_miso_latency. 
---     
--- Remarks:
--- . The common_mem_bus resembles common_mem_mux, but the difference is that
---   with common_mem_mux all slaves have the same address range and are
---   spaced without address gaps. It is possible to use common_mem_mux in
---   series with common_mem_bus to provide hierarchy by reprensenting an array
---   of slave ports via a single slave port on the MM bus.
--- . In simulation selecting an unused element address will cause a simulation
---   failure. Therefore the element index is only accepted when it is in the
---   0 TO g_nof_slaves-1 range.
---
--------------------------------------------------------------------------------
-
-
-LIBRARY IEEE, common_lib;
-USE IEEE.STD_LOGIC_1164.ALL;
-USE common_lib.common_pkg.ALL;
-USE common_lib.common_mem_pkg.ALL;
-
-ENTITY common_mem_bus IS
-  GENERIC (
-    g_nof_slaves      : POSITIVE;           -- Number of MM slave interfaces on the bus
-    g_base_arr        : t_nat_natural_arr;  -- Address base per slave
-    g_width_arr       : t_nat_natural_arr;  -- Address width per slave
-    g_rd_latency_arr  : t_nat_natural_arr;  -- Read latency per slave
-    g_pipeline_mosi   : BOOLEAN := FALSE;
-    g_pipeline_miso   : BOOLEAN := FALSE
-  );
-  PORT (
-    mm_clk         : IN  STD_LOGIC := '0';
-    master_mosi    : IN  t_mem_mosi;
-    master_miso    : OUT t_mem_miso;
-    slave_mosi_arr : OUT t_mem_mosi_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_mosi_rst); 
-    slave_miso_arr : IN  t_mem_miso_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_miso_rst)
-  );
-END common_mem_bus;
-
-ARCHITECTURE rtl OF common_mem_bus IS
-  
-  -- Determine the address range of all slaves on the MM bus.
-  FUNCTION func_derive_mm_bus_addr_w(g_base_arr, g_width_arr : t_nat_natural_arr) RETURN NATURAL IS
-    VARIABLE v_base            : NATURAL := 0;
-    VARIABLE v_width           : NATURAL;
-    VARIABLE v_mm_bus_addr_max : NATURAL;
-  BEGIN
-    FOR I IN g_base_arr'RANGE LOOP
-      IF g_base_arr(I) > v_base THEN
-        v_base  := g_base_arr(I);
-        v_width := g_width_arr(I);
-      END IF;
-    END LOOP;
-    -- Largest base address + the width of the slave at this address - 1. The
-    -- -1 is because the addresses count from 0 to N-1.
-    v_mm_bus_addr_max := v_base + 2**v_width - 1;
-    -- Return number of bits to represent the largest address that will be used
-    -- on the MM bus
-    RETURN ceil_log2(v_mm_bus_addr_max);
-  END;
-  
-  CONSTANT c_mm_bus_addr_w      : NATURAL := func_derive_mm_bus_addr_w(g_base_arr, g_width_arr);
-  CONSTANT c_mosi_latency       : NATURAL := sel_a_b(g_pipeline_mosi, 1, 0);
-  CONSTANT c_miso_latency       : NATURAL := sel_a_b(g_pipeline_miso, 1, 0);
-  CONSTANT c_index_latency_max  : NATURAL := c_mosi_latency + largest(g_rd_latency_arr);
-
-  SIGNAL index_pipeline      : t_nat_natural_arr(0 TO c_index_latency_max) := (OTHERS=>0);
-  SIGNAL slave_mosi_arr_comb : t_mem_mosi_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_mosi_rst);
-  SIGNAL master_miso_comb    : t_mem_miso := c_mem_miso_rst;
-
-BEGIN
-
-  gen_single : IF g_nof_slaves=1 GENERATE 
-    slave_mosi_arr(0) <= master_mosi;
-    master_miso       <= slave_miso_arr(0);
-  END GENERATE;
-    
-  gen_multiple : IF g_nof_slaves>1 GENERATE 
-
-    -- Detect which slave in the array is addressed
-    p_index : PROCESS(master_mosi)
-      VARIABLE v_base : NATURAL;
-    BEGIN
-      index_pipeline(0) <= g_nof_slaves;   -- default index of none existing slave
-      FOR I IN 0 TO g_nof_slaves-1 LOOP
-        v_base := TO_UINT(master_mosi.address(c_mm_bus_addr_w-1 DOWNTO g_width_arr(I)));
-        ASSERT g_base_arr(I) MOD 2**g_width_arr(I) = 0 REPORT "Slave base address must be a multiple of the slave width." SEVERITY FAILURE;
-        IF v_base = g_base_arr(I) / 2**g_width_arr(I) THEN
-          index_pipeline(0) <= I;   -- return index of addressed slave
-          EXIT;
-        END IF;
-      END LOOP;
-    END PROCESS;
-    
-    index_pipeline(1 TO c_index_latency_max) <= index_pipeline(0 TO c_index_latency_max-1) WHEN rising_edge(mm_clk);
-        
-    -- Master access, can be write or read
-    p_mosi : PROCESS(master_mosi, index_pipeline)
-    BEGIN
-      slave_mosi_arr_comb <= (OTHERS=>master_mosi);  -- default assign to all, to avoid latches
-      FOR I IN 0 TO g_nof_slaves-1 LOOP
-        slave_mosi_arr_comb(I).rd <= '0';
-        slave_mosi_arr_comb(I).wr <= '0';
-        IF I = index_pipeline(0) THEN   -- check index for read or write access
-          slave_mosi_arr_comb(I).rd <= master_mosi.rd;
-          slave_mosi_arr_comb(I).wr <= master_mosi.wr;
-        END IF;
-      END LOOP;
-    END PROCESS;
-    
-    no_pipeline_mosi : IF g_pipeline_mosi = FALSE GENERATE
-      slave_mosi_arr <= slave_mosi_arr_comb;
-    END GENERATE;
-    gen_pipeline_mosi : IF g_pipeline_mosi = TRUE GENERATE
-      slave_mosi_arr <= slave_mosi_arr_comb WHEN rising_edge(mm_clk);
-    END GENERATE;
-    
-    -- Slave response to read access after read latency mm_clk cycles
-    p_miso : PROCESS(slave_miso_arr, index_pipeline)
-      VARIABLE v_rd_latency : NATURAL;
-    BEGIN
-      master_miso_comb <= c_mem_miso_rst;   -- default clear, to avoid latches
-      FOR I IN 0 TO g_nof_slaves-1 LOOP
-        v_rd_latency := c_mosi_latency + g_rd_latency_arr(I);
-        IF I = index_pipeline(v_rd_latency) THEN  -- check index for read response
-          master_miso_comb <= slave_miso_arr(I);
-        END IF;
-      END LOOP;
-    END PROCESS;
-
-    no_pipeline_miso : IF g_pipeline_miso = FALSE GENERATE
-      master_miso <= master_miso_comb;
-    END GENERATE;
-    gen_pipeline_miso : IF g_pipeline_miso = TRUE GENERATE
-      master_miso <= master_miso_comb WHEN rising_edge(mm_clk);
-    END GENERATE;
-
-  END GENERATE; 
-  
-END rtl;
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: Connect a single MM master interface to a list of MM slave
+--          interfaces using a combinatorial muliplexer as bus.
+-- Description:
+-- * MM bus
+--   The mm_bus_comb creates a memory mapped (MM) bus that connects read
+--   and write accesses from the master interface to the addressed slave
+--   interface. There is one master that controls the bus and there are
+--   g_nof_slaves on the bus. Per slave the start address and address span
+--   have to be specified via g_base_arr and g_width_arr.
+--
+-- * Slave allocation
+--   The slaves have to be located on the bus such that the MSbits of the
+--   global address can be used to select the slave and the LSbits of the
+--   global address can directly be used to select the address within the
+--   slave. Therefore:
+--   . The width of a slave is the power of 2 that fits the address range of
+--     the slave.
+--   . The span of a slave is 2**width.
+--   . The base address of a slave has to be a power of 2 multiple of the
+--     slave span.
+--
+-- * The mm_clk is only used when there is a slave with read latency > 0, to
+--   pipeline the slave_index_arr for the master_miso.rddata/rdval.
+--   Typically a master will wait for the last rdval, before accessing
+--   another slave port, so then it is not benecessary to pipeline the
+--   slave_index_arr. However registering the slave_index_arr eases timing
+--   closure on the miso part and will allow reading from different slave
+--   ports without waiting, provided that both slaves have the same read
+--   latency.
+--
+-- * Read latency
+--   For read accesses a slave will typically have a read latency > 0, which
+--   means that when the rd and address are active, then it takes read
+--   latency number of clock cycles until the rddata becomes available. The
+--   read latency can be specified per slave via g_rd_latency_arr.
+--   The slave_index_arr is used to support that a new wr access or rd access
+--   can already start, while a current rd access still has to finish with
+--   a rdval. Without the slave_index_arr the master would have to wait with
+--   a new rd or wr access to another slave until the read response from the
+--   current slave has finished.
+--                                          ________
+--                                          | delay|
+--   master_mosi.address[h:w] = index --+-->| line |--\
+--                                      |   |______|  |
+--                                      |             |
+--                                      v             |
+--    master_mosi --> slave_mosi_arr.wr[ ]----------------> slave_mosi_arr
+--                                   rd               |
+--                                                    v
+--    master_miso <--------------------slave_miso_arr[ ]<-- slave_miso_arr
+--
+--
+-- * No pipelining
+--   The mm_bus_comb is combinatorial, so there is no pipelining between
+--   the master interface and the slave interfaces. Use mm_bus_pipe to add
+--   pipelining.
+--
+-- Usage:
+--   See mm_bus.vhd.
+--
+-- Limitations:
+-- * A limitation is that if one slave has a read latency of 2 and another
+--   slave has a read latency of 1 then it is not possible to access them
+--   without a gap of 1 mm_clk cycle, because the rdval will then be active
+--   simultaneously from both slaves. Therefore the master can only use
+--   random read access between slaves if all slaves have the same read
+--   latency. For slaves that have larger read latency the master must
+--   insert an gap, before it can read a slave that has less read latency.
+--   An alternative workaround would be to use the same read latency for all
+--   slaves on the bus, by pipelining the miso.rd, rddata for MM slaves that
+--   have a smaller read latency.
+--
+-- Remarks:
+-- . The mm_bus_comb resembles common_mem_mux, but the difference is that
+--   with common_mem_mux all slaves have the same address range and are
+--   spaced without address gaps. It is possible to use common_mem_mux in
+--   series with mm_bus_comb to provide hierarchy by reprensenting an array
+--   of slave ports via a single slave port on the MM bus.
+--
+-------------------------------------------------------------------------------
+
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+
+ENTITY mm_bus_comb IS
+  GENERIC (
+    g_nof_slaves          : POSITIVE;           -- Number of MM slave interfaces on the bus
+    g_base_arr            : t_nat_natural_arr;  -- Address base per slave
+    g_width_arr           : t_nat_natural_arr;  -- Address width per slave
+    g_rd_latency_arr      : t_nat_natural_arr   -- Read latency per slave
+  );
+  PORT (
+    mm_clk         : IN  STD_LOGIC := '0';
+    master_mosi    : IN  t_mem_mosi;
+    master_miso    : OUT t_mem_miso;
+    slave_mosi_arr : OUT t_mem_mosi_arr(0 TO g_nof_slaves-1);
+    slave_miso_arr : IN  t_mem_miso_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_miso_rst)
+  );
+END mm_bus_comb;
+
+ARCHITECTURE rtl OF mm_bus_comb IS
+
+  -- Determine the address range of all slaves on the MM bus.
+  FUNCTION func_derive_mm_bus_addr_w(g_base_arr, g_width_arr : t_nat_natural_arr) RETURN NATURAL IS
+    VARIABLE v_base            : NATURAL := 0;
+    VARIABLE v_width           : NATURAL;
+    VARIABLE v_mm_bus_addr_max : NATURAL;
+  BEGIN
+    FOR I IN g_base_arr'RANGE LOOP
+      IF g_base_arr(I) > v_base THEN
+        v_base  := g_base_arr(I);
+        v_width := g_width_arr(I);
+      END IF;
+    END LOOP;
+    -- Largest base address + the width of the slave at this address - 1. The
+    -- -1 is because the addresses count from 0 to N-1.
+    v_mm_bus_addr_max := v_base + 2**v_width - 1;
+    -- Return number of bits to represent the largest address that will be used
+    -- on the MM bus
+    RETURN ceil_log2(v_mm_bus_addr_max);
+  END;
+
+  CONSTANT c_mm_bus_addr_w       : NATURAL := func_derive_mm_bus_addr_w(g_base_arr, g_width_arr);
+  CONSTANT c_rd_latency_max      : NATURAL := largest(g_rd_latency_arr);
+
+  SIGNAL slave_index_arr     : t_nat_natural_arr(0 TO c_rd_latency_max) := (OTHERS=>0);
+
+BEGIN
+
+  gen_single : IF g_nof_slaves=1 GENERATE
+    slave_mosi_arr(0) <= master_mosi;
+    master_miso       <= slave_miso_arr(0);
+  END GENERATE;
+
+  gen_multiple : IF g_nof_slaves>1 GENERATE
+    -- Detect which slave in the array is addressed
+    p_index : PROCESS(master_mosi)
+      VARIABLE v_base : NATURAL;
+    BEGIN
+      slave_index_arr(0) <= g_nof_slaves;   -- default index of none existing slave
+      FOR I IN 0 TO g_nof_slaves-1 LOOP
+        v_base := TO_UINT(master_mosi.address(c_mm_bus_addr_w-1 DOWNTO g_width_arr(I)));
+        ASSERT g_base_arr(I) MOD 2**g_width_arr(I) = 0 REPORT "Slave base address must be a multiple of the slave width." SEVERITY FAILURE;
+        IF v_base = g_base_arr(I) / 2**g_width_arr(I) THEN
+          slave_index_arr(0) <= I;   -- return index of addressed slave
+          EXIT;  -- Found addressed slave, no need to loop further. EXIT is
+                 -- not realy needed, because there can only be one 
+                 -- addressed slave so loop further will not change the index.
+        END IF;
+      END LOOP;
+    END PROCESS;
+
+    slave_index_arr(1 TO c_rd_latency_max) <= slave_index_arr(0 TO c_rd_latency_max-1) WHEN rising_edge(mm_clk);
+
+    -- Master access, can be write or read
+    p_slave_mosi_arr : PROCESS(master_mosi, slave_index_arr)
+    BEGIN
+      slave_mosi_arr <= (OTHERS=>master_mosi);  -- default assign to all, to avoid latches
+      FOR I IN 0 TO g_nof_slaves-1 LOOP
+        slave_mosi_arr(I).rd <= '0';
+        slave_mosi_arr(I).wr <= '0';
+        IF I = slave_index_arr(0) THEN   -- check index for read or write access
+          slave_mosi_arr(I).rd <= master_mosi.rd;
+          slave_mosi_arr(I).wr <= master_mosi.wr;
+        END IF;
+      END LOOP;
+    END PROCESS;
+
+
+    -- Slave response to read access after read latency mm_clk cycles
+    p_master_miso : PROCESS(slave_miso_arr, slave_index_arr)
+      VARIABLE v_rd_latency : NATURAL;
+    BEGIN
+      master_miso <= c_mem_miso_rst;   -- default clear, to avoid latches
+      FOR I IN 0 TO g_nof_slaves-1 LOOP
+        v_rd_latency := g_rd_latency_arr(I);
+        IF I = slave_index_arr(v_rd_latency) THEN  -- check index for read response
+          master_miso <= slave_miso_arr(I);
+        END IF;
+      END LOOP;
+      FOR I IN 0 TO g_nof_slaves-1 LOOP
+        IF I = slave_index_arr(0) THEN  -- check index for waitrequest
+          master_miso.waitrequest <= slave_miso_arr(I).waitrequest;
+        END IF;
+      END LOOP;
+    END PROCESS;
+
+  END GENERATE;
+
+END rtl;
diff --git a/libraries/base/mm/src/vhdl/mm_bus_pipe.vhd b/libraries/base/mm/src/vhdl/mm_bus_pipe.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..2b8f8ecba4dad4d87fdc9dbb6607df467bcd6075
--- /dev/null
+++ b/libraries/base/mm/src/vhdl/mm_bus_pipe.vhd
@@ -0,0 +1,223 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: Provide pipelining to the combinatorial mm_bus_comb
+-- Description:
+--   The mm_bus_comb is combinatorial, so there is no pipelining between
+--   the master interface and the slave interfaces. If possible do not
+--   use pipelining of mosi and miso to avoid extra logic and to avoid
+--   increasing the read latency. Instead first try achieve timing closure
+--   by lower clock rate for the MM bus. Pipelining the MM bus can be
+--   necessary to achieve timing closure. Thanks to mm_bus_comb the
+--   pipelining is clearly separated from the MM bus multiplexer. The
+--   pipelining is placed at the output of the bus, so at the slave side
+--   for mosi and at the master side for miso:
+--
+--                                   FOR g_nof_slaves:
+--      g_pipeline_miso_rdval        g_pipeline_mosi
+--      g_pipeline_miso_wait              |   g_pipeline_miso_wait
+--               |                        |           |
+--               v       ________     ____v___     ___v___
+--   <-- p_miso_pipe <--| mm_bus |<--|mm      |<--|mm     |<--------
+--   ------------------>|  comb  |-->|pipeline|-->|latency|-------->
+--     .              . |________| . |________|   |adapter| .    .
+--   master_miso      .            .            . |_______| . slave_miso_arr
+--   master_mosi      .            .            .           . slave_mosi_arr
+--                  m_miso   bus_miso_arr  pipe_miso_arr  adapt_miso_arr
+--                  m_mosi   bus_mosi_arr  pipe_mosi_arr  adapt_mosi_arr
+--
+--   The MM bus pipelining is defined by:
+--
+--   * g_pipeline_mosi
+--     Pipelining mosi write accesses introduces an extra latency from master
+--     to slave, which is typically not a problem. Pipelining mosi read
+--     accesses increases the read latency between accessing the slave and
+--     getting the rddata. Using a different pipelining for the wr and the rd
+--     pulse would yield a different pipelining of the address for write and
+--     for read, which is akward. Therefore both mosi write and mosi read
+--     use the same g_pipeline_mosi pipelining.
+--
+--   * g_pipeline_miso_rdval
+--     Pipelining the miso read data increases the read latency.
+--
+--   * g_pipeline_miso_wait
+--     Pipelining the miso waitrequest increases the write and read latency
+--     for slaves that need MM flow control. Only applies to slave that
+--     have g_waitrequest_arr is TRUE.
+--
+--   The pipelining generics are defined as BOOLEAN (rather than NATURAL),
+--   because the pipelining only needs to be 0 or 1.
+--
+--   The total write latency from master to slave is 1 when either
+--   g_pipeline_mosi or g_pipeline_miso_wait.
+--   The total read latency from master via slave back to master is
+--   write latency + g_rd_latency_arr of the selected slave + 1 or 0
+--   dependend on g_pipeline_miso_rdval.
+--
+-- Usage:
+--   See mm_bus.vhd
+--
+-- Remark:
+--   * It is not allowed to simultaneously use g_pipeline_miso_wait = TRUE
+--     and g_pipeline_mosi = TRUE, because this leads to a combinatorial loop
+--     of the miso.waitrequest that is used at the output of the mm_pipeline
+--     and at the input of the mm_latency adapter:
+--     - at the mm_pipeline output the waitrequest gates the mosi.wr and rd
+--     - at the mm_latency_adapter input in common_rl_decrease the wr or
+--       rd strobe is used to set the waitrequest.
+--     This combinatorial loop seems unavoidable when the interface between
+--     mm_pipeline and mm_latency_adpater is at RL = 0. A solution could be
+--     to increase the RL at the output of the mm_pipeline to RL = 1 by
+--     registering the waitrequest from the mm_latency_adapter. The total
+--     RL for the input of the MM latency adapter then becomes RL = 2, so
+--     then the mm_latency_adapter needs to adapt from RL = 2 to 0.
+--     Currently the mm_latency_adapter only supports RL 1 to 0. Is possible
+--     to extent this to RL = N to 0, similar as in dp_latency_adapter.
+--     However fortunately it is not necessary to support g_pipeline_mosi =
+--     TRUE when g_pipeline_miso_wait = TRUE, because g_pipeline_miso_wait =
+--     TRUE by itself already also pipeplines the mosi.
+--
+-------------------------------------------------------------------------------
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+
+ENTITY mm_bus_pipe IS
+  GENERIC (
+    g_nof_slaves          : POSITIVE;           -- Number of MM slave interfaces on the bus
+    g_base_arr            : t_nat_natural_arr;  -- Address base per slave
+    g_width_arr           : t_nat_natural_arr;  -- Address width per slave
+    g_rd_latency_arr      : t_nat_natural_arr;  -- Read latency per slave
+    g_waitrequest_arr     : t_nat_boolean_arr;  -- Enable waitrequest flow control per slave, else fixed '0'
+    g_pipeline_mosi       : BOOLEAN := FALSE;   -- Pipeline MM access (wr, rd)
+    g_pipeline_miso_rdval : BOOLEAN := FALSE;   -- Pipeline MM read (rdval)
+    g_pipeline_miso_wait  : BOOLEAN := FALSE    -- Pipeline MM access flow control (waitrequest)
+  );
+  PORT (
+    mm_rst         : IN  STD_LOGIC;
+    mm_clk         : IN  STD_LOGIC;
+    master_mosi    : IN  t_mem_mosi;
+    master_miso    : OUT t_mem_miso;
+    slave_mosi_arr : OUT t_mem_mosi_arr(0 TO g_nof_slaves-1);
+    slave_miso_arr : IN  t_mem_miso_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_miso_rst)
+  );
+END mm_bus_pipe;
+
+ARCHITECTURE str OF mm_bus_pipe IS
+
+  SIGNAL m_mosi         : t_mem_mosi := c_mem_mosi_rst;
+  SIGNAL m_miso         : t_mem_miso := c_mem_miso_rst;
+  SIGNAL m_miso_reg     : t_mem_miso := c_mem_miso_rst;
+
+  SIGNAL bus_mosi_arr   : t_mem_mosi_arr(0 TO g_nof_slaves-1);
+  SIGNAL bus_miso_arr   : t_mem_miso_arr(0 TO g_nof_slaves-1);
+  SIGNAL pipe_mosi_arr  : t_mem_mosi_arr(0 TO g_nof_slaves-1);
+  SIGNAL pipe_miso_arr  : t_mem_miso_arr(0 TO g_nof_slaves-1);
+  SIGNAL adapt_mosi_arr : t_mem_mosi_arr(0 TO g_nof_slaves-1);
+  SIGNAL adapt_miso_arr : t_mem_miso_arr(0 TO g_nof_slaves-1);
+
+BEGIN
+
+  ASSERT NOT(g_pipeline_miso_wait = TRUE AND g_pipeline_mosi = TRUE)
+  REPORT "Do not use g_pipeline_mosi = TRUE if g_pipeline_miso_wait = TRUE"
+  SEVERITY FAILURE;
+
+  -- Master side
+  m_mosi <= master_mosi;
+
+  m_miso_reg <= m_miso WHEN rising_edge(mm_clk);
+
+  p_miso_pipe : PROCESS(m_miso, m_miso_reg)
+  BEGIN
+    -- Default no miso pipelining
+    master_miso <= m_miso;
+    -- Use pipelining
+    IF g_pipeline_miso_rdval THEN
+      master_miso.rddata <= m_miso_reg.rddata;
+      master_miso.rdval  <= m_miso_reg.rdval;
+    END IF;
+    IF g_pipeline_miso_wait THEN
+      master_miso.waitrequest <= m_miso_reg.waitrequest;
+    END IF;
+  END PROCESS;
+
+  -- MM bus
+  u_mm_bus_comb : ENTITY work.mm_bus_comb
+  GENERIC MAP (
+    g_nof_slaves     => g_nof_slaves,
+    g_base_arr       => g_base_arr,
+    g_width_arr      => g_width_arr,
+    g_rd_latency_arr => g_rd_latency_arr
+  )
+  PORT MAP (
+    mm_clk         => mm_clk,
+    master_mosi    => m_mosi,
+    master_miso    => m_miso,
+    slave_mosi_arr => bus_mosi_arr,
+    slave_miso_arr => bus_miso_arr
+  );
+
+  -- Slaves side
+  gen_slave_pipes : FOR I IN 0 TO g_nof_slaves-1 GENERATE
+    u_slave_pipe_mosi : ENTITY work.mm_pipeline
+    GENERIC MAP (
+      g_pipeline => g_pipeline_mosi
+    )
+    PORT MAP (
+      mm_rst        => mm_rst,
+      mm_clk        => mm_clk,
+      in_mosi       => bus_mosi_arr(I),
+      in_miso       => bus_miso_arr(I),
+      out_mosi      => pipe_mosi_arr(I),
+      out_miso      => pipe_miso_arr(I)
+    );
+
+    gen_wires : IF g_waitrequest_arr(I) = FALSE GENERATE
+      adapt_mosi_arr(I) <= pipe_mosi_arr(I);
+      pipe_miso_arr(I)  <= adapt_miso_arr(I);
+    END GENERATE;
+
+    gen_slave_latency_adapter : IF g_waitrequest_arr(I) = TRUE GENERATE
+      u_slave_latency_adapter : ENTITY work.mm_latency_adapter
+      GENERIC MAP (
+        g_adapt => g_pipeline_miso_wait
+      )
+      PORT MAP (
+        mm_rst        => mm_rst,
+        mm_clk        => mm_clk,
+        -- MM input RL = 1
+        in_mosi       => pipe_mosi_arr(I),
+        in_miso       => pipe_miso_arr(I),
+        -- MM output RL = 0
+        out_mosi      => adapt_mosi_arr(I),
+        out_miso      => adapt_miso_arr(I)
+      );
+    END GENERATE;
+  END GENERATE;
+
+  slave_mosi_arr <= adapt_mosi_arr;
+  adapt_miso_arr <= slave_miso_arr;
+
+END str;
diff --git a/libraries/base/mm/src/vhdl/mm_latency_adapter.vhd b/libraries/base/mm/src/vhdl/mm_latency_adapter.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..137efa3891e545ffab1c485b056ab4fe79904e57
--- /dev/null
+++ b/libraries/base/mm/src/vhdl/mm_latency_adapter.vhd
@@ -0,0 +1,109 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: Adapt miso.waitrequest latency from 1 to 0, to support pipelining
+--          of the waitrequest flow control
+-- Description:
+--   Wraps common_rl_decrease.vhd.
+--   The common_rl_decrease.vhd latency adapter FIFO buffers the in_mosi, to
+--   create time to compensate for the pipeline of the in_mosi.waitrequest.
+--   When the in_mosi.waitrequest goes high, then this FIFO buffer can hold
+--   the in_mosi input that may still arrive, due to that the master at the
+--   input only notices the in_mosi.waitrequest from the output slave one
+--   cycle later due to the pipelining.
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+
+ENTITY mm_latency_adapter IS
+  GENERIC (
+    g_adapt       : BOOLEAN := TRUE    -- default when TRUE then decrease sink RL 1 to source RL 0, else then implement wires
+  );
+  PORT (
+    mm_rst        : IN  STD_LOGIC;
+    mm_clk        : IN  STD_LOGIC;
+    -- MM input RL = 1
+    in_mosi       : IN  t_mem_mosi;
+    in_miso       : OUT t_mem_miso;
+    -- MM output RL = 0
+    out_mosi      : OUT t_mem_mosi;
+    out_miso      : IN  t_mem_miso
+  );
+END mm_latency_adapter;
+
+
+ARCHITECTURE str OF mm_latency_adapter IS
+
+  -- Sum of all t_mem_mosi fields widths (synthesis will optimize away unused address and data bits)
+  CONSTANT c_data_w  : NATURAL := c_mem_address_w +  c_mem_data_w + 2;  -- 32 + 72 + 1 (wr) + 1 (rd) = 106
+
+  SIGNAL in_waitrequest  : STD_LOGIC;
+  SIGNAL in_data         : STD_LOGIC_VECTOR(c_data_w-1 DOWNTO 0);
+  SIGNAL in_val          : STD_LOGIC;
+  SIGNAL in_ready        : STD_LOGIC;
+  SIGNAL out_ready       : STD_LOGIC;
+  SIGNAL out_data        : STD_LOGIC_VECTOR(c_data_w-1 DOWNTO 0);
+  SIGNAL out_val         : STD_LOGIC;
+
+BEGIN
+
+  in_data <= func_slv_concat(in_mosi.address, in_mosi.wrdata, slv(in_mosi.wr), slv(in_mosi.rd));
+  in_val <= in_mosi.wr OR in_mosi.rd;
+
+  p_miso : PROCESS(out_miso, in_waitrequest)
+  BEGIN
+    in_miso <= out_miso;
+    --in_miso.rdval <= out_miso.rdval AND NOT in_waitrequest;
+    in_miso.waitrequest <= in_waitrequest;
+  END PROCESS;
+
+  -- Account for opposite meaning of waitrequest and ready
+  in_waitrequest <= NOT in_ready;
+  out_ready      <= NOT out_miso.waitrequest;
+
+  u_rl : ENTITY common_lib.common_rl_decrease
+  GENERIC MAP (
+    g_adapt   => g_adapt,
+    g_dat_w   => c_data_w
+  )
+  PORT MAP (
+    rst           => mm_rst,
+    clk           => mm_clk,
+    -- ST sink: RL = 1
+    snk_out_ready => in_ready,
+    snk_in_dat    => in_data,
+    snk_in_val    => in_val,
+    -- ST source: RL = 0
+    src_in_ready  => out_ready,
+    src_out_dat   => out_data,
+    src_out_val   => out_val
+  );
+
+  out_mosi.address <=    func_slv_extract(c_mem_address_w, c_mem_data_w, 1, 1, out_data, 0);
+  out_mosi.wrdata  <=    func_slv_extract(c_mem_address_w, c_mem_data_w, 1, 1, out_data, 1);
+  out_mosi.wr      <= sl(func_slv_extract(c_mem_address_w, c_mem_data_w, 1, 1, out_data, 2));
+  out_mosi.rd      <= sl(func_slv_extract(c_mem_address_w, c_mem_data_w, 1, 1, out_data, 3));
+
+END str;
diff --git a/libraries/base/common/src/vhdl/common_mem_master_mux.vhd b/libraries/base/mm/src/vhdl/mm_master_mux.vhd
similarity index 72%
rename from libraries/base/common/src/vhdl/common_mem_master_mux.vhd
rename to libraries/base/mm/src/vhdl/mm_master_mux.vhd
index 82e5fff65c07f3f870fe92a2869ff3ae607e4517..abac065ce676b536b61ea87e0d741c85253c3bd0 100644
--- a/libraries/base/common/src/vhdl/common_mem_master_mux.vhd
+++ b/libraries/base/mm/src/vhdl/mm_master_mux.vhd
@@ -1,132 +1,147 @@
--------------------------------------------------------------------------------
---
--- Copyright 2020
--- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
--- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
--- 
--- 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.
---
--------------------------------------------------------------------------------
-
--------------------------------------------------------------------------------
--- 
--- Author: E. Kooistra
--- Purpose: Multiplex an array of MM master interfaces to a single MM master
---          interface
--- Description:
---   This common_mem_master_mux is a simple multiplexer that allows multiple
---   masters to access the same MM port. The common_mem_master_mux does not
---   provide arbitration between the masters in the array. Therefore the
---   precondition is that the external application takes care that the MM
---   accesses of the multiple masters in the array do not overlap in time.
---
---   Write accesses from multiple masters occur may without gaps. After a read
---   access from one master the read latency must first be accounted for by
---   the application introducing a gap, before a read access by another master
---   can be multiplexed.
---
---   The common_mem_master_mux operates combinatorially, so it introduces no
---   extra latency. The mm_clk is needed to hold the index of the master that
---   is currently active, to ensure that the read data.is passed on to the
---   master that did the rd access.
--- Remarks:
--- . The mux_miso.waitrequest is not supported.
---
--------------------------------------------------------------------------------
-
-
-LIBRARY IEEE, common_lib;
-USE IEEE.STD_LOGIC_1164.ALL;
-USE common_lib.common_pkg.ALL;
-USE common_lib.common_mem_pkg.ALL;
-
-ENTITY common_mem_master_mux IS
-  GENERIC (
-    g_nof_masters     : POSITIVE;  -- Number of MM masters
-    g_rd_latency_min  : NATURAL    -- Minimum read latency
-  );
-  PORT (
-    mm_clk          : IN  STD_LOGIC;
-    master_mosi_arr : IN  t_mem_mosi_arr(0 TO g_nof_masters-1) := (OTHERS=>c_mem_mosi_rst);
-    master_miso_arr : OUT t_mem_miso_arr(0 TO g_nof_masters-1) := (OTHERS=>c_mem_miso_rst);
-    mux_mosi        : OUT t_mem_mosi; 
-    mux_miso        : IN  t_mem_miso
-  );
-END common_mem_master_mux;
-
-ARCHITECTURE rtl OF common_mem_master_mux IS
-  
-  SIGNAL index                : NATURAL := 0;
-  SIGNAL index_hold           : NATURAL := 0;
-
-BEGIN
-
-  gen_single : IF g_nof_masters=1 GENERATE 
-    mux_mosi           <= master_mosi_arr(0);
-    master_miso_arr(0) <= mux_miso;
-  END GENERATE;
-    
-  gen_multiple : IF g_nof_masters>1 GENERATE 
-
-    -- Detect which master in the array is active
-    -- The pre condition is that the input masters will only start an access
-    -- when the mux master is free. For a rd access this means that the
-    -- read latency of the rdval has passed. Therefor it is not necessary
-    -- that this common_mem_master_mux maintains an index pipeline
-    -- from rd until expected rdval. Instead it is sufficient to hold the
-    -- index of the active master, until the next master does an access. For
-    -- rd access hold the last active index to ensure that rdval will be
-    -- directed to the master that orginated the rd access. For wr access
-    -- hold last active index instead of reset to '0' to ease observation of
-    -- the index value in wave window.
-    p_index : PROCESS(master_mosi_arr, index_hold)
-    BEGIN
-      index <= index_hold;    -- default hold index of last active master
-      FOR I IN 0 TO g_nof_masters-1 LOOP
-        IF master_mosi_arr(I).wr='1' OR master_mosi_arr(I).rd='1' THEN
-          index <= I;    -- index of active master
-          EXIT;
-        END IF;
-      END LOOP;
-    END PROCESS;
-    
-    index_hold <= index WHEN rising_edge(mm_clk);    -- hold index of last active master
-    
-        
-    -- Multiplex master access, can be write or read
-    mux_mosi <= master_mosi_arr(index);
-    
-    -- Multiplex slave read response
-    p_miso : PROCESS(mux_miso, index)
-    BEGIN
-      master_miso_arr <= (OTHERS=>mux_miso);  -- default assign to all, to avoid latches
-      FOR I IN 0 TO g_nof_masters-1 LOOP
-        master_miso_arr(I).rdval <= '0';
-        -- If the minimal read latency is g_rd_latency_min = 0, then the mux
-        -- has to use the combinatorial index, else it use the registered
-        -- index, to ease achieving timing closure.
-        IF g_rd_latency_min=0 THEN
-          IF I = index THEN
-            master_miso_arr(I).rdval <= mux_miso.rdval;
-          END IF;
-        ELSE
-          IF I = index_hold THEN
-            master_miso_arr(I).rdval <= mux_miso.rdval;
-          END IF;
-        END IF;
-      END LOOP;
-    END PROCESS;
-    
-  END GENERATE; 
-  
-END rtl;
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: Multiplex an array of MM master interfaces to a single MM master
+--          interface
+-- Description:
+--   This mm_master_mux is a simple multiplexer that allows multiple
+--   masters to access the same MM port. The mm_master_mux does not
+--   provide arbitration between the masters in the array. Therefore the
+--   precondition is that the external application takes care that the MM
+--   accesses of the multiple masters in the array do not overlap in time.
+--
+--   Write accesses from multiple masters occur may without gaps. After a read
+--   access from one master the read latency must first be accounted for by
+--   the application introducing a gap, before a read access by another master
+--   can be multiplexed.
+--
+--   The mm_master_mux operates combinatorially, so it introduces no
+--   extra latency. The mm_clk is needed to hold the index of the master that
+--   is currently active, to ensure that the read data is passed on to the
+--   master that did the rd access.
+--
+-- Remarks:
+-- . This resembles common_mem_demux.vhd, but is not identical. The difference
+--   is that common_mem_demux is the inverse of common_mem_demux and therefore
+--   assumes that all the mux_mosi spans the entire array whereas for this
+--   mm_master_mux the mux_mosi spans one element.
+-- . There is no bus arbitrator. This is sufficient for use cases where e.g.
+--   one master only does some initialization accesses after reset and the
+--   other master is the main master that does all subsequent accesses.
+--   Therefore this mm_master_mux is typically suited per MM slave
+--   that needs dual master access, rather then to select between two main
+--   central MM masters.
+-- . There is no pipelining. The advantage is that the mux_miso.waitrequest is
+--   supported without extra effort.
+--
+-------------------------------------------------------------------------------
+
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+
+ENTITY mm_master_mux IS
+  GENERIC (
+    g_nof_masters     : POSITIVE;  -- Number of MM masters
+    g_rd_latency_min  : NATURAL    -- Minimum read latency
+  );
+  PORT (
+    mm_clk          : IN  STD_LOGIC;
+    master_mosi_arr : IN  t_mem_mosi_arr(0 TO g_nof_masters-1) := (OTHERS=>c_mem_mosi_rst);
+    master_miso_arr : OUT t_mem_miso_arr(0 TO g_nof_masters-1) := (OTHERS=>c_mem_miso_rst);
+    mux_mosi        : OUT t_mem_mosi;
+    mux_miso        : IN  t_mem_miso
+  );
+END mm_master_mux;
+
+ARCHITECTURE rtl OF mm_master_mux IS
+
+  SIGNAL index                : NATURAL := 0;
+  SIGNAL index_hold           : NATURAL := 0;
+
+BEGIN
+
+  gen_single : IF g_nof_masters=1 GENERATE
+    mux_mosi           <= master_mosi_arr(0);
+    master_miso_arr(0) <= mux_miso;
+  END GENERATE;
+
+  gen_multiple : IF g_nof_masters>1 GENERATE
+
+    -- Detect which master in the array is active
+    -- The pre condition is that the input masters will only start an access
+    -- when the mux master is free. For a rd access this means that the
+    -- read latency of the rdval has passed. Therefor it is not necessary
+    -- that this mm_master_mux maintains an index pipeline
+    -- from rd until expected rdval. Instead it is sufficient to hold the
+    -- index of the active master, until the next master does an access. For
+    -- rd access hold the last active index to ensure that rdval will be
+    -- directed to the master that orginated the rd access. For wr access
+    -- hold last active index instead of reset to '0' to ease observation of
+    -- the index value in wave window.
+    p_index : PROCESS(master_mosi_arr, index_hold)
+    BEGIN
+      index <= index_hold;    -- default hold index of last active master
+      FOR I IN 0 TO g_nof_masters-1 LOOP
+        IF master_mosi_arr(I).wr='1' OR master_mosi_arr(I).rd='1' THEN
+          index <= I;    -- index of active master
+          EXIT;  -- Found active master, no need to loop further. EXIT is not
+                 -- realy needed, because there should be only one active
+                 -- master, and if there are more active masters, then it
+                 -- does not matter whether the first or the last is selected.
+        END IF;
+      END LOOP;
+    END PROCESS;
+
+    index_hold <= index WHEN rising_edge(mm_clk);    -- hold index of last active master
+
+
+    -- Multiplex master access, can be write or read
+    mux_mosi <= master_mosi_arr(index);
+
+    -- Multiplex slave read response
+    p_miso : PROCESS(mux_miso, index)
+    BEGIN
+      master_miso_arr <= (OTHERS=>mux_miso);  -- default assign to all, to avoid latches
+      FOR I IN 0 TO g_nof_masters-1 LOOP
+        master_miso_arr(I).rdval <= '0';
+        -- If the minimal read latency is g_rd_latency_min = 0, then the mux
+        -- has to use the combinatorial index, else it use the registered
+        -- index, to ease achieving timing closure.
+        IF g_rd_latency_min=0 THEN
+          IF I = index THEN
+            master_miso_arr(I).rdval <= mux_miso.rdval;
+          END IF;
+        ELSE
+          IF I = index_hold THEN
+            master_miso_arr(I).rdval <= mux_miso.rdval;
+          END IF;
+        END IF;
+      END LOOP;
+    END PROCESS;
+
+  END GENERATE;
+
+END rtl;
diff --git a/libraries/base/mm/src/vhdl/mm_pipeline.vhd b/libraries/base/mm/src/vhdl/mm_pipeline.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..179a3d51adfa85d3b5ed77c5bb1a449a2c50ff24
--- /dev/null
+++ b/libraries/base/mm/src/vhdl/mm_pipeline.vhd
@@ -0,0 +1,153 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: Pipeline MM mosi
+-- Description:
+--   The mm_pipeline mosi registers the in_mosi if g_pipeline = TRUE, else it
+--   defaults to wires.
+--
+-- Background information
+--   The MM waitrequest resembles the behaviour of the streaming backpressure
+--   ready for ready latency RL = 0. For RL = 0 the ready acts as an
+--   acknowledge to pending data. For RL > 0 the ready acts as a request for
+--   new data. The miso.waitrequest is defined for RL = 0 but for analysis
+--   the timing diagrams below show an example of both RL = 0 and RL = 1. The
+--   miso.waitrequest is equivalent to NOT sosi.ready.
+--
+--   * RL=1
+--                 _   _   _   _   _   _   _   _   _   _   _   _
+--     clk       _| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_
+--
+--     in_dat     |a  |b          |c          |d
+--               _________         ___         ___
+--     in_val             |_______|   |_______|   |_______________
+--               _____         ___     _______         ___________
+--     ready          |_______|   |___|...    |_______|...........
+--               _________         ___     _______         _______
+--     reg_ready          |_______|   |___|...    |_______|.......
+--
+--     reg_dat        |a  |b          |c          |d
+--               _____________________________     ___________
+--     reg_val                                |___|           |___
+--               _________         ___     ___             ___
+--     out_val        |a  |_______|b  |___|c  |___________|d  |___
+--
+--
+--   * RL=0
+--                 _   _   _   _   _   _   _   _   _   _   _   _   _
+--     clk       _| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_
+--
+--     in_dat     |a  |b          |c      |d              |e
+--               _________         _______________         ___
+--     in_val             |_______|               |_______|   |_______
+--               _____________         ___     _______     ___________
+--     ack                    |_______|   |___|       |___|
+--
+--     reg_dat        |a  |b              |c      |d          |e
+--               _____________             ___________         ___
+--     reg_val                |___________|           |_______|   |___
+--               _____________                 _______         ___
+--     out_val        |a  |b  |_______________|c  |d  |_______|e  |___
+--
+--   In these timing diagrams the out_ready is wired to the in_ready, so
+--   therefore they are identical and called ready.
+--   The ready for RL = 0 or the reg_ready for RL = 1 is used to gate the
+--   out_val. The ready/reg_ready is used and not the in_val, because by
+--   using the ready/reg_ready the pipeline register is emptied as soon
+--   as the ready is active, rather than to wait for a next in_val to push
+--   it out. The ready/reg_ready have the same latency as the in_val,
+--   because they are both derived using the same RL.
+--
+-- Remark:
+-- * The mm_pipeline could be optimized regarding the miso.waitrequest flow
+--   control if it would be implemented similar as dp_pipeline.vhd. This
+--   involves using the pipeline register to accept an access when it is
+--   empty. In this way the waitrequest to the in_mosi only needs to apply
+--   when the out_miso is not ready and the pipeline is full. This would
+--   achieve the maximum throughput. The advantage of simply registering
+--   in_mosi and wiring in_miso is that it is simpler and does not put extra
+--   logic into the combinatorial miso.waitrequest path. It is better to
+--   keep it simpler and with less logic, then to try to win the last few
+--   percent of throughput.
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+
+ENTITY mm_pipeline IS
+  GENERIC (
+    g_pipeline    : BOOLEAN := TRUE
+  );
+  PORT (
+    mm_rst        : IN  STD_LOGIC;
+    mm_clk        : IN  STD_LOGIC;
+    in_mosi       : IN  t_mem_mosi;
+    in_miso       : OUT t_mem_miso;
+    out_mosi      : OUT t_mem_mosi;
+    out_miso      : IN  t_mem_miso
+  );
+END mm_pipeline;
+
+
+ARCHITECTURE rtl OF mm_pipeline IS
+
+  SIGNAL mosi_reg     : t_mem_mosi := c_mem_mosi_rst;
+  SIGNAL nxt_mosi_reg : t_mem_mosi;
+  SIGNAL ready        : STD_LOGIC;
+
+BEGIN
+
+  -- Pass on miso
+  in_miso <= out_miso;
+
+  -- Pipeline the mosi when g_pipeline = TRUE, else default to wires
+  gen_wires : IF g_pipeline = FALSE GENERATE
+    out_mosi <= in_mosi;
+  END GENERATE;
+
+  gen_pipeline : IF g_pipeline = TRUE GENERATE
+    p_reg : PROCESS(mm_rst, mm_clk)
+    BEGIN
+      IF mm_rst = '1' THEN
+        mosi_reg <= c_mem_mosi_rst;
+      ELSIF rising_edge(mm_clk) THEN
+        mosi_reg <= nxt_mosi_reg;
+      END IF;
+    END PROCESS;
+
+    ready <= NOT out_miso.waitrequest;
+
+    nxt_mosi_reg <= in_mosi WHEN ready = '1' ELSE mosi_reg;
+
+    p_out_mosi : PROCESS(mosi_reg, ready)
+    BEGIN
+      out_mosi <= mosi_reg;
+      IF ready /= '1' THEN
+        out_mosi.wr <= '0';  -- out_mosi.wr = mosi_reg.wr AND ready
+        out_mosi.rd <= '0';  -- out_mosi.rd = mosi_reg.rd AND ready
+      END IF;
+    END PROCESS;
+  END GENERATE;
+
+END rtl;
diff --git a/libraries/base/mm/src/vhdl/mm_slave_enable.vhd b/libraries/base/mm/src/vhdl/mm_slave_enable.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..7803a14da266adc98274703022c01b4c8945d3b3
--- /dev/null
+++ b/libraries/base/mm/src/vhdl/mm_slave_enable.vhd
@@ -0,0 +1,122 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: Connect an MM slave to the MM bus or represent an not connected
+--          slave. Force waitrequest = '0' if slave does not need mosi flow
+--          control
+-- Description:
+-- * g_enable
+--   When FALSE then the in_miso output tot the master is forced to
+--   c_mem_miso_rst to represent a not connected MM slave. When TRUE then
+--   the out_miso signal from the slave is passed on to the master.
+-- * g_waitrequest
+--   When FALSE then the in_miso.waitrequest is forced to '0' to indicate
+--   that the MM slave does not need mosi flow control. When FALSE then
+--   the miso.waitrequest from the connected slave is passed on to the
+--   master.
+-- * g_rd_latency
+--   Used to derive in_miso.rdval from in_mosi.rd and out_miso.waitrequest,
+--   to provide rdval for MM slaves that do not drive rdval. Typically any
+--   MM slave that needs miso.waitrequest flow control, also should support
+--   rdval themselves.
+--
+-- Todo:
+-- * Add miso.response field as defined in Avalon bus, to inform master about
+--   rd status (00 = okay, 01 = rsvd, 10 = slaveerror, 11 = decodeerror).
+--
+-------------------------------------------------------------------------------
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+
+ENTITY mm_slave_enable IS
+  GENERIC (
+    g_enable       : BOOLEAN;
+    g_waitrequest  : BOOLEAN;
+    g_rd_latency   : NATURAL
+  );
+  PORT (
+    mm_rst        : IN  STD_LOGIC;
+    mm_clk        : IN  STD_LOGIC;
+    -- MM input RL = 1
+    in_mosi       : IN  t_mem_mosi;
+    in_miso       : OUT t_mem_miso;
+    -- MM output RL = 0
+    out_mosi      : OUT t_mem_mosi;
+    out_miso      : IN  t_mem_miso
+  );
+END mm_slave_enable;
+
+
+ARCHITECTURE rtl OF mm_slave_enable IS
+
+  SIGNAL rd          : STD_LOGIC;
+  SIGNAL rdval       : STD_LOGIC;
+  SIGNAL waitrequest : STD_LOGIC;
+
+BEGIN
+
+  -- Use mosi.rd to create miso.rdval for unconnected slave or for slaves that do not support rdval
+  u_rdval : ENTITY common_lib.common_pipeline_sl
+  GENERIC MAP (
+    g_pipeline => g_rd_latency
+  )
+  PORT MAP (
+    rst     => mm_rst,
+    clk     => mm_clk,
+    in_dat  => rd,
+    out_dat => rdval
+  );
+
+
+  no_slave : IF g_enable = FALSE GENERATE
+    out_mosi <= c_mem_mosi_rst;
+
+    rd <= in_mosi.rd;
+
+    p_in_miso : PROCESS(rdval)
+    BEGIN
+      in_miso <= c_mem_miso_rst;  -- force all miso to 0, so rddata = 0 and no waitrequest
+      in_miso.rdval <= rdval;     -- support rdval to avoid hanging master that waits for rdval
+    END PROCESS;
+  END GENERATE;
+
+  gen_slave : IF g_enable = TRUE GENERATE
+    out_mosi <= in_mosi;
+
+    -- Use waitrequest from slave, or force waitrequest = '0' if slave does not need mosi flow control
+    waitrequest <= out_miso.waitrequest WHEN g_waitrequest = TRUE ELSE '0';
+
+    rd <= in_mosi.rd AND NOT waitrequest;
+
+    p_in_miso : PROCESS(out_miso, rdval, waitrequest)
+    BEGIN
+      in_miso <= out_miso;
+      in_miso.rdval <= rdval;
+      in_miso.waitrequest <= waitrequest;
+    END PROCESS;
+  END GENERATE;
+
+END rtl;
diff --git a/libraries/base/mm/src/vhdl/mm_slave_mux.vhd b/libraries/base/mm/src/vhdl/mm_slave_mux.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..c2a61ace47ece1df90a026338eceb366c79e7341
--- /dev/null
+++ b/libraries/base/mm/src/vhdl/mm_slave_mux.vhd
@@ -0,0 +1,70 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: Combines an array of MM interfaces into a single MM interface.
+-- Description:
+--   Wraps common_mem_mux.vhd.
+-- Remark:
+--   No need for g_rd_latency pipelining, so pure combinatorial and no need
+--   for clk. If necessary apply pipelining via mm_bus.vhd.
+-------------------------------------------------------------------------------
+
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+
+ENTITY mm_slave_mux IS
+  GENERIC (
+    g_broadcast   : BOOLEAN := FALSE;   -- TRUE use port[0] to access all, else use separate ports
+    g_nof_mosi    : POSITIVE := 256;    -- Number of slave memory interfaces in the array.
+    g_mosi_addr_w : POSITIVE := 8       -- Address width per slave
+  );
+  PORT (
+    mosi     : IN  t_mem_mosi;
+    miso     : OUT t_mem_miso;
+    mosi_arr : OUT t_mem_mosi_arr(g_nof_mosi - 1 DOWNTO 0);
+    miso_arr : IN  t_mem_miso_arr(g_nof_mosi - 1 DOWNTO 0) := (OTHERS=>c_mem_miso_rst)
+  );
+END mm_slave_mux;
+
+ARCHITECTURE str OF mm_slave_mux IS
+BEGIN
+
+  u_common_mem_mux : ENTITY common_lib.common_mem_mux
+  GENERIC MAP (
+    g_broadcast   => g_broadcast,
+    g_nof_mosi    => g_nof_mosi,
+    g_mult_addr_w => g_mosi_addr_w,
+    g_rd_latency  => 0
+  )
+  PORT MAP (
+    clk      => '0',   -- only used when g_rd_latency > 0
+    mosi     => mosi,
+    miso     => miso,
+    mosi_arr => mosi_arr,
+    miso_arr => miso_arr
+  );
+
+END str;
diff --git a/libraries/base/mm/tb/vhdl/mm_file_pkg.vhd b/libraries/base/mm/tb/vhdl/mm_file_pkg.vhd
index f253848f810b786efb2ead64f221e735c510d625..1b0148e99c85b83e36a8ed7e61c82d1ea343c83e 100644
--- a/libraries/base/mm/tb/vhdl/mm_file_pkg.vhd
+++ b/libraries/base/mm/tb/vhdl/mm_file_pkg.vhd
@@ -1,757 +1,757 @@
--------------------------------------------------------------------------------
---
--- Copyright (C) 2017
--- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
--- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
---
--- This program is free software: you can redistribute it and/or modify
--- it under the terms of the GNU General Public License as published by
--- the Free Software Foundation, either version 3 of the License, or
--- (at your option) any later version.
---
--- This program is distributed in the hope that it will be useful,
--- but WITHOUT ANY WARRANTY; without even the implied warranty of
--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--- GNU General Public License for more details.
---
--- You should have received a copy of the GNU General Public License
--- along with this program.  If not, see <http://www.gnu.org/licenses/>.
---
--------------------------------------------------------------------------------
-
--- Author :
---   D. van der Schuur  May 2012  Original for Python - file IO - VHDL 
---   E. Kooistra        feb 2017  Added purpose and description
---                                Added procedures for external control in a
---                                pure VHDL test bench.
---
--- Purpose: Provide DUT access via MM bus through file IO per MM slave
--- Description:
---   This package provides file IO access to MM slaves and to the status of
---   the simulation:
---
--- 1) MM slave access
---   Access to MM slaves is provided by component mm_file.vhd that first calls
---   mmf_file_create() and loop forever calling mmf_mm_from_file(). Each MM
---   slave has a dedicated pair of request (.ctrl) and response (.stat) IO
---   files.
---   The mmf_file_create() creates the .ctrl file and mmf_mm_from_file() reads
---   it to check whether there is a WR or RD access request. For a WR request
---   the wr_data and wr_addr are read from the .ctrl and output on the MM bus
---   via mm_mosi. For a RD access request the rd_addr is read from the .ctrl
---   and output on the MM bus via mm_mosi. The after the read latency the
---   rd_data is written to the .stat file that is then created and closed.
---
---                    wr             rd  _________               __________
---   mmf_mm_bus_wr() ---> ctrl file --->|         |---mm_mosi-->|          |
---                                      | mm_file |             | MM slave |
---   mmf_mm_bus_rd() <--- stat file <---|___\_____|<--mm_miso---|__________|
---                    rd             wr      \
---                                            \--> loop: mmf_mm_from_file()
---
---   The ctrl file is created by mm_file at initialization and recreated by
---   every call of mmf_mm_from_file().
---   The stat file is recreated by every call of mmf_mm_bus_rd().
---
--- 2) Simulator access
---   External access to the simulation is provided via a .ctrl file that
---   supports GET_SIM_TIME and then report the NOW time via the .stat file.
---   The simulation access is provided via a procedure mmf_poll_sim_ctrl_file()
---   that works similar component mm_file.vhd.
---
---                      wr             rd
---                    |---> ctrl file --->|
---   mmf_sim_get_now()|                   |mmf_poll_sim_ctrl_file()
---                    |<--- stat file <---|  \
---                      rd             wr     \
---                                             \--> loop: mmf_sim_ctrl_from_file()
---
---   The ctrl file is created by mmf_poll_sim_ctrl_file at initialization and
---   recreated by every call of mmf_sim_ctrl_from_file().
---   The stat file is recreated by every call of mmf_sim_get_now().
---
--- A) External control by a Python script
---   A Python script can issue requests via the .ctrl files to control the
---   simulation and read the .stat files. This models the MM access via a
---   Monitoring and Control protocol via 1GbE.
---
---   Internal procedures:
---   . mmf_file_create(filename: IN STRING);
---   . mmf_mm_from_file(SIGNAL mm_clk  : IN STD_LOGIC; 
---   . mmf_sim_ctrl_from_file(rd_filename: IN STRING;
---   
---   External procedures (used in a VHDL design to provide access to the MM
---   slaves and simulation via file IO):
---   . mm_file.vhd --> instead of a procedure MM slave file IO uses a component
---   . mmf_poll_sim_ctrl_file()
---   
--- B) External control by a VHDL process --> see tb_mm_file.vhd
---   Instead of a Python script the file IO access to the MM slaves can also
---   be used in a pure VHDL testbench. This is useful when the MM slave bus
---   signals (mm_mosi, mm_miso) are not available on the entity of the DUT
---   (device under test), which is typically the case when a complete FPGA
---   design needs to be simulated.
---
---   Internal procedures:
---   . mmf_wait_for_file_status()
---   . mmf_wait_for_file_empty()
---   . mmf_wait_for_file_not_empty()
---                                      
---   External procedures (used in a VHDL test bench to provide access to the 
---   MM slaves in a DUT VHDL design and simulation via file IO):
---   . mmf_mm_bus_wr()
---   . mmf_mm_bus_rd()
---   . mmf_sim_get_now()
---
---   External function to create unique sim.ctrl/sim.stat filename per test bench in a multi tb
---   . mmf_slave_prefix()
---
--- Remarks:
--- . The timing of the MM access in mmf_mm_bus_wr() and mmf_mm_bus_rd() and the
---   simulation access in mmf_sim_get_now() is not critical. The timing of the first
---   access depends on the tb. Due to falling_edge(mm_clk) in mmf_wait_for_file_*()
---   all subsequent accesses will start at falling_edge(mm_clk)
-  
-LIBRARY IEEE, common_lib;
-USE IEEE.STD_LOGIC_1164.ALL;
-USE IEEE.NUMERIC_STD.ALL;
-USE common_lib.common_pkg.ALL;
-USE common_lib.tb_common_pkg.ALL;
-USE common_lib.common_mem_pkg.ALL;
-USE common_lib.tb_common_mem_pkg.ALL;
-USE std.textio.ALL;
-USE IEEE.std_logic_textio.ALL;
-USE common_lib.common_str_pkg.ALL;
-
-PACKAGE mm_file_pkg IS
-
-  -- Constants used by mm_file.vhd
-  CONSTANT c_mmf_mm_clk_period : TIME :=  100 ps;  -- Default mm_clk period in simulation. Set much faster than DP clock to speed up
-                                                   -- simulation of MM access. Without file IO throttling 100 ps is a good balance
-                                                   -- between simulation speed and file IO rate.
-  CONSTANT c_mmf_mm_timeout    : TIME := 1000 ns;  -- Default MM file IO timeout period. Set large enough to account for MM-DP clock
-                                                   -- domain crossing delays. Use 0 ns to disable file IO throttling, to have file IO
-                                                   -- at the mm_clk rate.
-  CONSTANT c_mmf_mm_pause      : TIME :=  100 ns;  -- Default MM file IO pause period after timeout. Balance between file IO rate
-                                                   -- reduction and responsiveness to new MM access.
-  
-  -- Procedure to (re)create empty file
-  PROCEDURE mmf_file_create(filename: IN STRING);
-
-  -- Procedure to perform an MM access from file
-  PROCEDURE mmf_mm_from_file(SIGNAL mm_clk  : IN STD_LOGIC; 
-                             SIGNAL mm_rst  : IN STD_LOGIC; 
-                             SIGNAL mm_mosi : OUT t_mem_mosi;
-                             SIGNAL mm_miso : IN  t_mem_miso;
-                             rd_filename: IN STRING;
-                             wr_filename: IN STRING;
-                             rd_latency: IN NATURAL);
-
-  -- Procedure to process a simulation status request from the .ctrl file and provide response via the .stat file
-  PROCEDURE mmf_sim_ctrl_from_file(rd_filename: IN STRING;
-                                   wr_filename: IN STRING);
-
-  -- Procedure to poll the simulation status
-  PROCEDURE mmf_poll_sim_ctrl_file(rd_file_name: IN STRING; 
-                                   wr_file_name: IN STRING);
-
-  -- Procedure to poll the simulation status
-  PROCEDURE mmf_poll_sim_ctrl_file(SIGNAL mm_clk  : IN STD_LOGIC;
-                                   rd_file_name: IN STRING; 
-                                   wr_file_name: IN STRING);
-
-  -- Procedures that keep reading the file until it has been made empty or not empty by some other program,
-  -- to ensure the file is ready for a new write access
-  PROCEDURE mmf_wait_for_file_status(rd_filename   : IN STRING;  -- file name with extension
-                                     exit_on_empty : IN BOOLEAN;
-                                     SIGNAL mm_clk : IN STD_LOGIC);
-                                    
-  PROCEDURE mmf_wait_for_file_empty(rd_filename   : IN STRING;  -- file name with extension
-                                    SIGNAL mm_clk : IN STD_LOGIC);
-  PROCEDURE mmf_wait_for_file_not_empty(rd_filename   : IN STRING;  -- file name with extension
-                                        SIGNAL mm_clk : IN STD_LOGIC);
-                                        
-  -- Procedure to issue a write access via the MM request .ctrl file  
-  PROCEDURE mmf_mm_bus_wr(filename      : IN STRING;   -- file name without extension
-                          wr_addr       : IN INTEGER;  -- use integer to support full 32 bit range
-                          wr_data       : IN INTEGER;
-                          SIGNAL mm_clk : IN STD_LOGIC);
-                             
-  -- Procedure to issue a read access via the MM request .ctrl file and get the read data from the MM response file
-  PROCEDURE mmf_mm_bus_rd(filename       : IN STRING;   -- file name without extension
-                          rd_latency     : IN NATURAL;
-                          rd_addr        : IN INTEGER;  -- use integer to support full 32 bit range
-                          SIGNAL rd_data : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-                          SIGNAL mm_clk  : IN STD_LOGIC);
-  -- . rd_latency = 1
-  PROCEDURE mmf_mm_bus_rd(filename       : IN STRING;
-                          rd_addr        : IN INTEGER;
-                          SIGNAL rd_data : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-                          SIGNAL mm_clk  : IN STD_LOGIC);
-
-  -- Procedure that reads the rd_data every rd_interval until has the specified rd_value, the proc arguments can be understood as a sentence
-  PROCEDURE mmf_mm_wait_until_value(filename         : IN STRING;   -- file name without extension
-                                    rd_addr          : IN INTEGER;
-                                    c_representation : IN STRING;  -- treat rd_data as "SIGNED" or "UNSIGNED" 32 bit word
-                                    SIGNAL rd_data   : INOUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-                                    c_condition      : IN STRING;  -- ">", ">=", "=", "<=", "<", "/="
-                                    c_rd_value       : IN INTEGER;
-                                    c_rd_interval    : IN TIME;
-                                    SIGNAL mm_clk    : IN STD_LOGIC);
-                                       
-  -- Procedure to get NOW via simulator status
-  PROCEDURE mmf_sim_get_now(filename       : IN STRING;   -- file name without extension
-                            SIGNAL rd_now  : OUT STRING;
-                            SIGNAL mm_clk  : IN STD_LOGIC);
-
-  -- Functions to create prefixes for the mmf file filename
-  FUNCTION mmf_prefix(name : STRING; index : NATURAL) RETURN STRING;  -- generic prefix name with index to be used for a file IO filename
-  FUNCTION mmf_tb_prefix(tb : INTEGER) RETURN STRING;                 -- fixed test bench prefix with index tb to allow file IO with multi tb
-  FUNCTION mmf_subrack_prefix(subrack : INTEGER) RETURN STRING;       -- fixed subrack prefix with index subrack to allow file IO with multi subracks that use same unb numbers
-
-  -- Functions to create mmf file prefix that is unique per slave, for increasing number of hierarchy levels:
-  -- . return "filepath/s0_i0_"
-  -- . return "filepath/s0_i0_s1_i1_"
-  -- . return "filepath/s0_i0_s1_i1_s2_i2_"
-  -- . return "filepath/s0_i0_s1_i1_s2_i2_s3_i3_"
-  -- . return "filepath/s0_i0_s1_i1_s2_i2_s3_i3_s4_i4_"
-  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL) RETURN STRING;
-  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL) RETURN STRING;
-  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL) RETURN STRING;
-  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL) RETURN STRING;
-  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL; s4 : STRING; i4 : NATURAL) RETURN STRING;
-  
-  CONSTANT c_mmf_local_dir_path : STRING := "mmfiles/";   -- local directory in project file build directory
-  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL) RETURN STRING;
-  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL) RETURN STRING;
-  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL) RETURN STRING;
-  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL) RETURN STRING;
-  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL; s4 : STRING; i4 : NATURAL) RETURN STRING;
-  
-  ----------------------------------------------------------------------------
-  -- Declare mm_file component to support positional generic and port mapping of many instances in a TB
-  ----------------------------------------------------------------------------
-  COMPONENT mm_file
-  GENERIC(
-    g_file_prefix       : STRING;
-    g_file_enable       : STD_LOGIC := '1';
-    g_mm_rd_latency     : NATURAL := 2;
-    g_mm_timeout        : TIME := c_mmf_mm_timeout;
-    g_mm_pause          : TIME := c_mmf_mm_pause
-  );
-  PORT (
-    mm_rst        : IN  STD_LOGIC;
-    mm_clk        : IN  STD_LOGIC;
-    mm_master_out : OUT t_mem_mosi;
-    mm_master_in  : IN  t_mem_miso 
-  );
-  END COMPONENT;
-
-END mm_file_pkg;
-
-PACKAGE BODY mm_file_pkg IS
-
-  PROCEDURE mmf_file_create(filename: IN STRING) IS
-    FILE created_file : TEXT OPEN write_mode IS filename;
-  BEGIN
-    -- Write the file with nothing in it
-    write(created_file, "");
-  END;
-
-  PROCEDURE mmf_mm_from_file(SIGNAL mm_clk : IN STD_LOGIC; 
-                             SIGNAL mm_rst : IN STD_LOGIC; 
-                             SIGNAL mm_mosi : OUT t_mem_mosi;
-                             SIGNAL mm_miso : IN  t_mem_miso;
-                             rd_filename: IN STRING;
-                             wr_filename: IN STRING;
-                             rd_latency: IN NATURAL) IS
-    FILE rd_file : TEXT;
-    FILE wr_file : TEXT;
-
-    VARIABLE open_status_rd: file_open_status;
-    VARIABLE open_status_wr: file_open_status;
-
-    VARIABLE rd_line : LINE;
-    VARIABLE wr_line : LINE;
-
-    -- Note: Both the address and the data are interpreted as 32-bit data!
-    -- This means one has to use leading zeros in the file when either is
-    -- less than 8 hex characters, e.g.:
-    -- (address) 0000000A
-    -- (data)    DEADBEEF
-    -- ...as a hex address 'A' would fit in only 4 bits, causing an error in hread().
-    VARIABLE v_addr_slv : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-    VARIABLE v_data_slv : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-
-    VARIABLE v_rd_wr_str : STRING(1 TO 2); -- Contains 'RD' or 'WR'
-
-  BEGIN
-
-    proc_common_wait_until_low(mm_clk, mm_rst);
-
-    -- We have to open the file explicitely so we can check the status
-    file_open(open_status_rd, rd_file, rd_filename, read_mode);
-
-    -- open_status may throw an error if the file is being written to by some other program
-    IF open_status_rd=open_ok THEN
-
-      IF NOT endfile(rd_file) THEN
-        -- The file is not empty: process its contents
- 
-        -- Read a line from it, first line indicates RD or WR
-        readline(rd_file, rd_line);
-        read(rd_line, v_rd_wr_str); 
-        
-        -- The second line represents the address offset:
-        readline(rd_file, rd_line);
-        hread(rd_line, v_addr_slv);  -- read the string as HEX and assign to SLV.
-
-        -- Write only: The third line contains the data to write:
-        IF v_rd_wr_str="WR" THEN
-          readline(rd_file, rd_line);
-          hread(rd_line, v_data_slv);  -- read the string as HEX and assign to SLV.
-        END IF;
-        
-        -- We're done reading MM request from the .ctrl file.
-        -- Clear the .ctrl file by closing and recreating it, because we don't want to do the same
-        -- MM request again the next time this procedure is called.
-        file_close(rd_file); 
-        mmf_file_create(rd_filename); 
-        
-        -- Execute the MM request to the MM slave
-        IF v_rd_wr_str="WR" THEN
-          print_str("[" & time_to_str(now) & "] " & rd_filename & ": Writing 0x" & slv_to_hex(v_data_slv) & " to address 0x" & slv_to_hex(v_addr_slv));
-          -- Treat 32 bit hex data from file as 32 bit VHDL INTEGER, so need to use signed TO_SINT() to avoid out of NATURAL range
-          -- warning in simulation due to '1' sign bit, because unsigned VHDL NATURAL only fits 31 bits
-          proc_mem_mm_bus_wr(TO_UINT(v_addr_slv), TO_SINT(v_data_slv), mm_clk, mm_miso, mm_mosi);
-
-        ELSIF v_rd_wr_str="RD" THEN
-          proc_mem_mm_bus_rd(TO_UINT(v_addr_slv), mm_clk, mm_miso, mm_mosi);
-          IF rd_latency>0 THEN
-            proc_mem_mm_bus_rd_latency(rd_latency, mm_clk);
-          END IF;
-          v_data_slv := mm_miso.rddata(31 DOWNTO 0);
-          print_str("[" & time_to_str(now) & "] " & rd_filename & ": Reading from address 0x" & slv_to_hex(v_addr_slv) & ": 0x" & slv_to_hex(v_data_slv));
-      
-          -- Write the RD response read data to the .stat file
-          file_open(open_status_wr, wr_file, wr_filename, write_mode);
-          hwrite(wr_line, v_data_slv);
-          writeline(wr_file, wr_line);
-          file_close(wr_file); 
-        END IF;
- 
-      ELSE
-        -- Nothing to process; wait one MM clock cycle.
-        proc_common_wait_some_cycles(mm_clk, 1);
-      END IF;
-
-    ELSE
-      REPORT "mmf_mm_from_file() could not open " & rd_filename & " at " & time_to_str(now) SEVERITY NOTE;
-      -- Try again next time; wait one MM clock cycle.
-      proc_common_wait_some_cycles(mm_clk, 1);
-    END IF;
-
-    -- The END implicitely close the rd_file, if still necessary.
-  END;
-
-  
-  PROCEDURE mmf_sim_ctrl_from_file(rd_filename: IN STRING;
-                                   wr_filename: IN STRING) IS
-
-    FILE rd_file : TEXT;
-    FILE wr_file : TEXT;
-
-    VARIABLE open_status_rd: file_open_status;
-    VARIABLE open_status_wr: file_open_status;
-
-    VARIABLE rd_line : LINE;
-    VARIABLE wr_line : LINE;
-
-    VARIABLE v_rd_wr_str : STRING(1 TO 12); -- "GET_SIM_TIME"
-
-  BEGIN
-
-    -- We have to open the file explicitely so we can check the status
-    file_open(open_status_rd, rd_file, rd_filename, read_mode);
-
-    -- open_status may throw an error if the file is being written to by some other program
-    IF open_status_rd=open_ok THEN
-
-      IF NOT endfile(rd_file) THEN
-        -- The file is not empty: process its contents
- 
-        -- Read a line from it, interpret the simulation request
-        readline(rd_file, rd_line);
-        read(rd_line, v_rd_wr_str);
-
-        -- We're done reading this simulation request .ctrl file. Clear the file by closing and recreating it.
-        file_close(rd_file); 
-        mmf_file_create(rd_filename); 
-
-        -- Execute the simulation request
-        IF v_rd_wr_str="GET_SIM_TIME" THEN
-          -- Write the GET_SIM_TIME response time NOW to the .stat file
-          file_open(open_status_wr, wr_file, wr_filename, write_mode);
-          write(wr_line, time_to_str(now));
-          writeline(wr_file, wr_line);
-          file_close(wr_file); 
-        END IF;
- 
-      ELSE
-        -- Nothing to process; wait in procedure mmf_poll_sim_ctrl_file
-        NULL;
-      END IF;
-
-    ELSE
-      REPORT "mmf_mm_from_file() could not open " & rd_filename & " at " & time_to_str(now) SEVERITY NOTE;
-      -- Try again next time; wait in procedure mmf_poll_sim_ctrl_file
-    END IF;
-
-    -- The END implicitely close the rd_file, if still necessary.
-  END;
-
-
-  PROCEDURE mmf_poll_sim_ctrl_file(rd_file_name: IN STRING; wr_file_name : IN STRING) IS
-  BEGIN
-    -- Create the ctrl file that we're going to read from
-    print_str("[" & time_to_str(now) & "] " & rd_file_name & ": Created" );
-    mmf_file_create(rd_file_name);
-
-    WHILE TRUE LOOP
-      mmf_sim_ctrl_from_file(rd_file_name, wr_file_name);
-      WAIT FOR 1 ns;
-    END LOOP;
-
-  END;
-
-
-  PROCEDURE mmf_poll_sim_ctrl_file(SIGNAL mm_clk  : IN STD_LOGIC;
-                                   rd_file_name: IN STRING; wr_file_name : IN STRING) IS
-  BEGIN
-    -- Create the ctrl file that we're going to read from
-    print_str("[" & time_to_str(now) & "] " & rd_file_name & ": Created" );
-    mmf_file_create(rd_file_name);
-
-    WHILE TRUE LOOP
-      mmf_sim_ctrl_from_file(rd_file_name, wr_file_name);
-      proc_common_wait_some_cycles(mm_clk, 1);
-    END LOOP;
-
-  END;
-
-
-  PROCEDURE mmf_wait_for_file_status(rd_filename   : IN STRING;  -- file name with extension
-                                     exit_on_empty : IN BOOLEAN;
-                                     SIGNAL mm_clk : IN STD_LOGIC) IS
-    FILE     rd_file        : TEXT;
-    VARIABLE open_status_rd : file_open_status;
-    VARIABLE v_endfile      : BOOLEAN;
-  BEGIN
-    -- Check on falling_edge(mm_clk) because mmf_mm_from_file() operates on rising_edge(mm_clk)
-    -- Note: In fact the file IO also works fine when rising_edge() is used, but then
-    --       tb_tb_mm_file.vhd takes about 1% more mm_clk cycles
-    WAIT UNTIL falling_edge(mm_clk);
-    
-    -- Keep reading the file until it has become empty by some other program
-    WHILE TRUE LOOP
-      -- Open the file in read mode to check whether it is empty
-      file_open(open_status_rd, rd_file, rd_filename, read_mode);
-      -- open_status may throw an error if the file is being written to by some other program
-      IF open_status_rd=open_ok THEN
-        v_endfile := endfile(rd_file);
-        file_close(rd_file);
-        IF exit_on_empty THEN
-          IF v_endfile THEN
-            -- The file is empty; continue
-            EXIT;
-          ELSE
-            -- The file is not empty; wait one MM clock cycle.
-            WAIT UNTIL falling_edge(mm_clk);
-          END IF;
-        ELSE
-          IF v_endfile THEN
-            -- The file is empty; wait one MM clock cycle.
-            WAIT UNTIL falling_edge(mm_clk);
-          ELSE
-            -- The file is not empty; continue
-            EXIT;
-          END IF;
-        END IF;
-      ELSE
-        REPORT "mmf_wait_for_file_status() could not open " & rd_filename & " at " & time_to_str(now) SEVERITY NOTE;
-        WAIT UNTIL falling_edge(mm_clk);
-      END IF;
-    END LOOP;
-    -- The END implicitely close the file, if still necessary.
-  END;
-
-  PROCEDURE mmf_wait_for_file_empty(rd_filename   : IN STRING;  -- file name with extension
-                                    SIGNAL mm_clk : IN STD_LOGIC) IS
-  BEGIN
-    mmf_wait_for_file_status(rd_filename, TRUE, mm_clk);
-  END;
-
-  PROCEDURE mmf_wait_for_file_not_empty(rd_filename   : IN STRING;  -- file name with extension
-                                        SIGNAL mm_clk : IN STD_LOGIC) IS
-  BEGIN
-    mmf_wait_for_file_status(rd_filename, FALSE, mm_clk);
-  END;
-    
-  PROCEDURE mmf_mm_bus_wr(filename      : IN STRING;   -- file name without extension
-                          wr_addr       : IN INTEGER;  -- use integer to support full 32 bit range
-                          wr_data       : IN INTEGER;
-                          SIGNAL mm_clk : IN STD_LOGIC) IS
-    CONSTANT ctrl_filename  : STRING := filename & ".ctrl";
-    FILE     ctrl_file      : TEXT;
-    VARIABLE open_status_wr : file_open_status;
-    VARIABLE wr_line        : LINE;
-
-  BEGIN
-    -- Write MM WR access to the .ctrl file.
-    -- The MM device is ready for a new MM request, because any previous MM request has finished at
-    -- mmf_mm_bus_wr() or mmf_mm_bus_rd() procedure exit, therefore just overwrite the .ctrl file.
-    file_open(open_status_wr, ctrl_file, ctrl_filename, write_mode);
-    -- open_status may throw an error if the file is being written to by some other program
-    IF open_status_wr=open_ok THEN
-      write(wr_line, STRING'("WR"));
-      writeline(ctrl_file, wr_line);
-      hwrite(wr_line, TO_SVEC(wr_addr, c_word_w));
-      writeline(ctrl_file, wr_line);
-      hwrite(wr_line, TO_SVEC(wr_data, c_word_w));
-      writeline(ctrl_file, wr_line);
-      file_close(ctrl_file); 
-    ELSE
-      REPORT "mmf_mm_bus_wr() could not open " & ctrl_filename & " at " & time_to_str(now) SEVERITY NOTE;
-    END IF;
-
-    -- Prepare for next MM request
-    -- Keep reading the .ctrl file until it is empty, to ensure that the MM device is ready for a new MM request
-    mmf_wait_for_file_empty(ctrl_filename, mm_clk);
-
-    -- The END implicitely close the ctrl_file, if still necessary.
-  END;
-                          
-  PROCEDURE mmf_mm_bus_rd(filename       : IN STRING;   -- file name without extension
-                          rd_latency     : IN NATURAL;
-                          rd_addr        : IN INTEGER;  -- use integer to support full 32 bit range
-                          SIGNAL rd_data : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-                          SIGNAL mm_clk  : IN STD_LOGIC) IS
-    CONSTANT ctrl_filename  : STRING := filename & ".ctrl";
-    CONSTANT stat_filename  : STRING := filename & ".stat";
-    FILE     ctrl_file      : TEXT;
-    FILE     stat_file      : TEXT;
-    VARIABLE open_status_wr : file_open_status;
-    VARIABLE open_status_rd : file_open_status;
-    VARIABLE wr_line        : LINE;
-    VARIABLE rd_line        : LINE;
-    VARIABLE v_rd_data      : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-
-  BEGIN
-    -- Clear the .stat file by recreating it, because we don't want to do read old file data again
-    mmf_file_create(stat_filename); 
-
-    -- Write MM RD access to the .ctrl file.
-    -- The MM device is ready for a new MM request, because any previous MM request has finished at
-    -- mmf_mm_bus_wr() or mmf_mm_bus_rd() procedure exit, therefore just overwrite the .ctrl file.
-    file_open(open_status_wr, ctrl_file, ctrl_filename, write_mode);
-    -- open_status may throw an error if the file is being written to by some other program
-    IF open_status_wr=open_ok THEN
-      write(wr_line, STRING'("RD"));
-      writeline(ctrl_file, wr_line);
-      hwrite(wr_line, TO_SVEC(rd_addr, c_word_w));
-      writeline(ctrl_file, wr_line);
-      file_close(ctrl_file);
-    ELSE
-      REPORT "mmf_mm_bus_rd() could not open " & ctrl_filename & " at " & time_to_str(now) SEVERITY FAILURE;
-    END IF;
-    
-    -- Wait until the MM RD access has written the read data to the .stat file
-    mmf_wait_for_file_not_empty(stat_filename, mm_clk);
-
-    -- Read the MM RD access read data from the .stat file
-    file_open(open_status_rd, stat_file, stat_filename, read_mode);
-    -- open_status may throw an error if the file is being written to by some other program
-    IF open_status_rd=open_ok THEN
-      readline(stat_file, rd_line);
-      hread(rd_line, v_rd_data);
-      file_close(stat_file);
-      rd_data <= v_rd_data;
-      -- wait to ensure rd_data has got v_rd_data, otherwise rd_data still holds the old data on procedure exit
-      -- the wait should be < mm_clk period/2 to not affect the read rate
-      WAIT FOR 1 fs;
-    ELSE
-      REPORT "mmf_mm_bus_rd() could not open " & stat_filename & " at " & time_to_str(now) SEVERITY FAILURE;
-    END IF;
-    
-    -- No need to prepare for next MM request, because:
-    -- . the .ctrl file must already be empty because the .stat file was there
-    -- . the .stat file will be cleared on this procedure entry
-    
-    -- The END implicitely closes the files, if still necessary
-  END;
-
-  -- rd_latency = 1
-  PROCEDURE mmf_mm_bus_rd(filename       : IN STRING;
-                          rd_addr        : IN INTEGER;
-                          SIGNAL rd_data : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-                          SIGNAL mm_clk  : IN STD_LOGIC) IS
-  BEGIN
-    mmf_mm_bus_rd(filename, 1, rd_addr, rd_data, mm_clk);
-  END;
-  
-  PROCEDURE mmf_mm_wait_until_value(filename         : IN STRING;   -- file name without extension
-                                    rd_addr          : IN INTEGER;
-                                    c_representation : IN STRING;  -- treat rd_data as "SIGNED" or "UNSIGNED" 32 bit word
-                                    SIGNAL rd_data   : INOUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
-                                    c_condition      : IN STRING;  -- ">", ">=", "=", "<=", "<", "/="
-                                    c_rd_value       : IN INTEGER;
-                                    c_rd_interval    : IN TIME;
-                                    SIGNAL mm_clk    : IN STD_LOGIC) IS
-  BEGIN
-    WHILE TRUE LOOP
-      -- Read current 
-      mmf_mm_bus_rd(filename, rd_addr, rd_data, mm_clk);  -- only read low part
-      IF c_representation="SIGNED" THEN
-        IF    c_condition=">"  THEN IF TO_SINT(rd_data)> c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
-        ELSIF c_condition=">=" THEN IF TO_SINT(rd_data)>=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
-        ELSIF c_condition="/=" THEN IF TO_SINT(rd_data)/=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
-        ELSIF c_condition="<=" THEN IF TO_SINT(rd_data)<=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
-        ELSIF c_condition="<"  THEN IF TO_SINT(rd_data)< c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
-        ELSE                        IF TO_SINT(rd_data) =c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;  -- default: "="
-        END IF;
-      ELSE  -- default: UNSIGED
-        IF    c_condition=">"  THEN IF TO_UINT(rd_data)> c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
-        ELSIF c_condition=">=" THEN IF TO_UINT(rd_data)>=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
-        ELSIF c_condition="/=" THEN IF TO_UINT(rd_data)/=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
-        ELSIF c_condition="<=" THEN IF TO_UINT(rd_data)<=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
-        ELSIF c_condition="<"  THEN IF TO_UINT(rd_data)< c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
-        ELSE                        IF TO_UINT(rd_data) =c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;  -- default: "="
-        END IF;
-      END IF;
-    END LOOP;
-  END mmf_mm_wait_until_value;
-                          
-                            
-  PROCEDURE mmf_sim_get_now(filename       : IN STRING;   -- file name without extension
-                            SIGNAL rd_now  : OUT STRING;
-                            SIGNAL mm_clk  : IN STD_LOGIC) IS
-    CONSTANT ctrl_filename  : STRING := filename & ".ctrl";
-    CONSTANT stat_filename  : STRING := filename & ".stat";
-    FILE     ctrl_file      : TEXT;
-    FILE     stat_file      : TEXT;
-    VARIABLE open_status_wr : file_open_status;
-    VARIABLE open_status_rd : file_open_status;
-    VARIABLE wr_line        : LINE;
-    VARIABLE rd_line        : LINE;
-    VARIABLE v_rd_now       : STRING(rd_now'RANGE);
-
-  BEGIN
-    -- Clear the sim.stat file by recreating it, because we don't want to do read old simulator status again
-    mmf_file_create(stat_filename);
-        
-    -- Write GET_SIM_TIME to the sim.ctrl file
-    -- The simulation is ready for a new simulation status request, because any previous simulation status request has finished at
-    -- mmf_sim_get_now() procedure exit, therefore just overwrite the .ctrl file.
-    file_open(open_status_wr, ctrl_file, ctrl_filename, write_mode);
-    -- open_status may throw an error if the file is being written to by some other program
-    IF open_status_wr=open_ok THEN
-      write(wr_line, STRING'("GET_SIM_TIME"));
-      writeline(ctrl_file, wr_line);
-      file_close(ctrl_file);
-    ELSE
-      REPORT "mmf_sim_get_now() could not open " & ctrl_filename & " at " & time_to_str(now) SEVERITY FAILURE;
-    END IF;
-    
-    -- Wait until the simulation has written the simulation status to the sim.stat file
-    mmf_wait_for_file_not_empty(stat_filename, mm_clk);
-
-    -- Read the GET_SIM_TIME simulation status from the .stat file
-    file_open(open_status_rd, stat_file, stat_filename, read_mode);
-    -- open_status may throw an error if the file is being written to by some other program
-    IF open_status_rd=open_ok THEN
-      readline(stat_file, rd_line);
-      read(rd_line, v_rd_now);
-      file_close(stat_file);
-      rd_now <= v_rd_now;
-      print_str("GET_SIM_TIME = " & v_rd_now & " at " & time_to_str(now));
-    ELSE
-      REPORT "mmf_sim_get_now() could not open " & stat_filename & " at " & time_to_str(now) SEVERITY FAILURE;
-    END IF;
-    
-    -- No need to prepare for next simulation status request, because:
-    -- . the .ctrl file must already be empty because the .stat file was there
-    -- . the .stat file will be cleared on this procedure entry
-    
-    -- The END implicitely closes the files, if still necessary
-  END;
-  
-  -- Functions to create prefixes for the mmf file filename
-  FUNCTION mmf_prefix(name : STRING; index : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN name & "_" & int_to_str(index) & "_";
-  END;
-  
-  FUNCTION mmf_tb_prefix(tb : INTEGER) RETURN STRING IS
-  BEGIN
-    RETURN mmf_prefix("TB", tb);
-  END;
-  
-  FUNCTION mmf_subrack_prefix(subrack : INTEGER) RETURN STRING IS
-  BEGIN
-    RETURN mmf_prefix("SUBRACK", subrack);
-  END;
-  
-  -- Functions to create mmf file prefix that is unique per slave, for increasing number of hierarchy levels:
-  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN dir_path & mmf_prefix(s0, i0);
-  END;
-
-  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1);
-  END;
-
-  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2);
-  END;
-
-  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2) & mmf_prefix(s3, i3);
-  END;
-  
-  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL; s4 : STRING; i4 : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2) & mmf_prefix(s3, i3) & mmf_prefix(s4, i4);
-  END;
-
-  -- Use local dir_path  
-  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN c_mmf_local_dir_path & mmf_prefix(s0, i0);
-  END;
-
-  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN c_mmf_local_dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1);
-  END;
-
-  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN c_mmf_local_dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2);
-  END;
-
-  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN c_mmf_local_dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2) & mmf_prefix(s3, i3);
-  END;
-  
-  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL; s4 : STRING; i4 : NATURAL) RETURN STRING IS
-  BEGIN
-    RETURN c_mmf_local_dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2) & mmf_prefix(s3, i3) & mmf_prefix(s4, i4);
-  END;
-
-END mm_file_pkg;
-
+-------------------------------------------------------------------------------
+--
+-- Copyright (C) 2017
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-------------------------------------------------------------------------------
+
+-- Author :
+--   D. van der Schuur  May 2012  Original for Python - file IO - VHDL
+--   E. Kooistra        feb 2017  Added purpose and description
+--                                Added procedures for external control in a
+--                                pure VHDL test bench.
+--
+-- Purpose: Provide DUT access via MM bus through file IO per MM slave
+-- Description:
+--   This package provides file IO access to MM slaves and to the status of
+--   the simulation:
+--
+-- 1) MM slave access
+--   Access to MM slaves is provided by component mm_file.vhd that first calls
+--   mmf_file_create() and loop forever calling mmf_mm_from_file(). Each MM
+--   slave has a dedicated pair of request (.ctrl) and response (.stat) IO
+--   files.
+--   The mmf_file_create() creates the .ctrl file and mmf_mm_from_file() reads
+--   it to check whether there is a WR or RD access request. For a WR request
+--   the wr_data and wr_addr are read from the .ctrl and output on the MM bus
+--   via mm_mosi. For a RD access request the rd_addr is read from the .ctrl
+--   and output on the MM bus via mm_mosi. The after the read latency the
+--   rd_data is written to the .stat file that is then created and closed.
+--
+--                    wr             rd  _________               __________
+--   mmf_mm_bus_wr() ---> ctrl file --->|         |---mm_mosi-->|          |
+--                                      | mm_file |             | MM slave |
+--   mmf_mm_bus_rd() <--- stat file <---|___\_____|<--mm_miso---|__________|
+--                    rd             wr      \
+--                                            \--> loop: mmf_mm_from_file()
+--
+--   The ctrl file is created by mm_file at initialization and recreated by
+--   every call of mmf_mm_from_file().
+--   The stat file is recreated by every call of mmf_mm_bus_rd().
+--
+-- 2) Simulator access
+--   External access to the simulation is provided via a .ctrl file that
+--   supports GET_SIM_TIME and then report the NOW time via the .stat file.
+--   The simulation access is provided via a procedure mmf_poll_sim_ctrl_file()
+--   that works similar component mm_file.vhd.
+--
+--                      wr             rd
+--                    |---> ctrl file --->|
+--   mmf_sim_get_now()|                   |mmf_poll_sim_ctrl_file()
+--                    |<--- stat file <---|  \
+--                      rd             wr     \
+--                                             \--> loop: mmf_sim_ctrl_from_file()
+--
+--   The ctrl file is created by mmf_poll_sim_ctrl_file at initialization and
+--   recreated by every call of mmf_sim_ctrl_from_file().
+--   The stat file is recreated by every call of mmf_sim_get_now().
+--
+-- A) External control by a Python script
+--   A Python script can issue requests via the .ctrl files to control the
+--   simulation and read the .stat files. This models the MM access via a
+--   Monitoring and Control protocol via 1GbE.
+--
+--   Internal procedures:
+--   . mmf_file_create(filename: IN STRING);
+--   . mmf_mm_from_file(SIGNAL mm_clk  : IN STD_LOGIC;
+--   . mmf_sim_ctrl_from_file(rd_filename: IN STRING;
+--
+--   External procedures (used in a VHDL design to provide access to the MM
+--   slaves and simulation via file IO):
+--   . mm_file.vhd --> instead of a procedure MM slave file IO uses a component
+--   . mmf_poll_sim_ctrl_file()
+--
+-- B) External control by a VHDL process --> see tb_mm_file.vhd
+--   Instead of a Python script the file IO access to the MM slaves can also
+--   be used in a pure VHDL testbench. This is useful when the MM slave bus
+--   signals (mm_mosi, mm_miso) are not available on the entity of the DUT
+--   (device under test), which is typically the case when a complete FPGA
+--   design needs to be simulated.
+--
+--   Internal procedures:
+--   . mmf_wait_for_file_status()
+--   . mmf_wait_for_file_empty()
+--   . mmf_wait_for_file_not_empty()
+--
+--   External procedures (used in a VHDL test bench to provide access to the
+--   MM slaves in a DUT VHDL design and simulation via file IO):
+--   . mmf_mm_bus_wr()
+--   . mmf_mm_bus_rd()
+--   . mmf_sim_get_now()
+--
+--   External function to create unique sim.ctrl/sim.stat filename per test bench in a multi tb
+--   . mmf_slave_prefix()
+--
+-- Remarks:
+-- . The timing of the MM access in mmf_mm_bus_wr() and mmf_mm_bus_rd() and the
+--   simulation access in mmf_sim_get_now() is not critical. The timing of the first
+--   access depends on the tb. Due to falling_edge(mm_clk) in mmf_wait_for_file_*()
+--   all subsequent accesses will start at falling_edge(mm_clk)
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE IEEE.NUMERIC_STD.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.tb_common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+USE common_lib.tb_common_mem_pkg.ALL;
+USE std.textio.ALL;
+USE IEEE.std_logic_textio.ALL;
+USE common_lib.common_str_pkg.ALL;
+
+PACKAGE mm_file_pkg IS
+
+  -- Constants used by mm_file.vhd
+  CONSTANT c_mmf_mm_clk_period : TIME :=  100 ps;  -- Default mm_clk period in simulation. Set much faster than DP clock to speed up
+                                                   -- simulation of MM access. Without file IO throttling 100 ps is a good balance
+                                                   -- between simulation speed and file IO rate.
+  CONSTANT c_mmf_mm_timeout    : TIME := 1000 ns;  -- Default MM file IO timeout period. Set large enough to account for MM-DP clock
+                                                   -- domain crossing delays. Use 0 ns to disable file IO throttling, to have file IO
+                                                   -- at the mm_clk rate.
+  CONSTANT c_mmf_mm_pause      : TIME :=  100 ns;  -- Default MM file IO pause period after timeout. Balance between file IO rate
+                                                   -- reduction and responsiveness to new MM access.
+
+  -- Procedure to (re)create empty file
+  PROCEDURE mmf_file_create(filename: IN STRING);
+
+  -- Procedure to perform an MM access from file
+  PROCEDURE mmf_mm_from_file(SIGNAL mm_clk  : IN STD_LOGIC;
+                             SIGNAL mm_rst  : IN STD_LOGIC;
+                             SIGNAL mm_mosi : OUT t_mem_mosi;
+                             SIGNAL mm_miso : IN  t_mem_miso;
+                             rd_filename: IN STRING;
+                             wr_filename: IN STRING;
+                             rd_latency: IN NATURAL);
+
+  -- Procedure to process a simulation status request from the .ctrl file and provide response via the .stat file
+  PROCEDURE mmf_sim_ctrl_from_file(rd_filename: IN STRING;
+                                   wr_filename: IN STRING);
+
+  -- Procedure to poll the simulation status
+  PROCEDURE mmf_poll_sim_ctrl_file(rd_file_name: IN STRING;
+                                   wr_file_name: IN STRING);
+
+  -- Procedure to poll the simulation status
+  PROCEDURE mmf_poll_sim_ctrl_file(SIGNAL mm_clk  : IN STD_LOGIC;
+                                   rd_file_name: IN STRING;
+                                   wr_file_name: IN STRING);
+
+  -- Procedures that keep reading the file until it has been made empty or not empty by some other program,
+  -- to ensure the file is ready for a new write access
+  PROCEDURE mmf_wait_for_file_status(rd_filename   : IN STRING;  -- file name with extension
+                                     exit_on_empty : IN BOOLEAN;
+                                     SIGNAL mm_clk : IN STD_LOGIC);
+
+  PROCEDURE mmf_wait_for_file_empty(rd_filename   : IN STRING;  -- file name with extension
+                                    SIGNAL mm_clk : IN STD_LOGIC);
+  PROCEDURE mmf_wait_for_file_not_empty(rd_filename   : IN STRING;  -- file name with extension
+                                        SIGNAL mm_clk : IN STD_LOGIC);
+
+  -- Procedure to issue a write access via the MM request .ctrl file
+  PROCEDURE mmf_mm_bus_wr(filename      : IN STRING;   -- file name without extension
+                          wr_addr       : IN INTEGER;  -- use integer to support full 32 bit range
+                          wr_data       : IN INTEGER;
+                          SIGNAL mm_clk : IN STD_LOGIC);
+
+  -- Procedure to issue a read access via the MM request .ctrl file and get the read data from the MM response file
+  PROCEDURE mmf_mm_bus_rd(filename       : IN STRING;   -- file name without extension
+                          rd_latency     : IN NATURAL;
+                          rd_addr        : IN INTEGER;  -- use integer to support full 32 bit range
+                          SIGNAL rd_data : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+                          SIGNAL mm_clk  : IN STD_LOGIC);
+  -- . rd_latency = 1
+  PROCEDURE mmf_mm_bus_rd(filename       : IN STRING;
+                          rd_addr        : IN INTEGER;
+                          SIGNAL rd_data : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+                          SIGNAL mm_clk  : IN STD_LOGIC);
+
+  -- Procedure that reads the rd_data every rd_interval until has the specified rd_value, the proc arguments can be understood as a sentence
+  PROCEDURE mmf_mm_wait_until_value(filename         : IN STRING;   -- file name without extension
+                                    rd_addr          : IN INTEGER;
+                                    c_representation : IN STRING;  -- treat rd_data as "SIGNED" or "UNSIGNED" 32 bit word
+                                    SIGNAL rd_data   : INOUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+                                    c_condition      : IN STRING;  -- ">", ">=", "=", "<=", "<", "/="
+                                    c_rd_value       : IN INTEGER;
+                                    c_rd_interval    : IN TIME;
+                                    SIGNAL mm_clk    : IN STD_LOGIC);
+
+  -- Procedure to get NOW via simulator status
+  PROCEDURE mmf_sim_get_now(filename       : IN STRING;   -- file name without extension
+                            SIGNAL rd_now  : OUT STRING;
+                            SIGNAL mm_clk  : IN STD_LOGIC);
+
+  -- Functions to create prefixes for the mmf file filename
+  FUNCTION mmf_prefix(name : STRING; index : NATURAL) RETURN STRING;  -- generic prefix name with index to be used for a file IO filename
+  FUNCTION mmf_tb_prefix(tb : INTEGER) RETURN STRING;                 -- fixed test bench prefix with index tb to allow file IO with multi tb
+  FUNCTION mmf_subrack_prefix(subrack : INTEGER) RETURN STRING;       -- fixed subrack prefix with index subrack to allow file IO with multi subracks that use same unb numbers
+
+  -- Functions to create mmf file prefix that is unique per slave, for increasing number of hierarchy levels:
+  -- . return "filepath/s0_i0_"
+  -- . return "filepath/s0_i0_s1_i1_"
+  -- . return "filepath/s0_i0_s1_i1_s2_i2_"
+  -- . return "filepath/s0_i0_s1_i1_s2_i2_s3_i3_"
+  -- . return "filepath/s0_i0_s1_i1_s2_i2_s3_i3_s4_i4_"
+  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL) RETURN STRING;
+  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL) RETURN STRING;
+  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL) RETURN STRING;
+  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL) RETURN STRING;
+  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL; s4 : STRING; i4 : NATURAL) RETURN STRING;
+
+  CONSTANT c_mmf_local_dir_path : STRING := "mmfiles/";   -- local directory in project file build directory
+  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL) RETURN STRING;
+  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL) RETURN STRING;
+  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL) RETURN STRING;
+  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL) RETURN STRING;
+  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL; s4 : STRING; i4 : NATURAL) RETURN STRING;
+
+  ----------------------------------------------------------------------------
+  -- Declare mm_file component to support positional generic and port mapping of many instances in a TB
+  ----------------------------------------------------------------------------
+  COMPONENT mm_file
+  GENERIC(
+    g_file_prefix       : STRING;
+    g_file_enable       : STD_LOGIC := '1';
+    g_mm_rd_latency     : NATURAL := 2;
+    g_mm_timeout        : TIME := c_mmf_mm_timeout;
+    g_mm_pause          : TIME := c_mmf_mm_pause
+  );
+  PORT (
+    mm_rst        : IN  STD_LOGIC;
+    mm_clk        : IN  STD_LOGIC;
+    mm_master_out : OUT t_mem_mosi;
+    mm_master_in  : IN  t_mem_miso
+  );
+  END COMPONENT;
+
+END mm_file_pkg;
+
+PACKAGE BODY mm_file_pkg IS
+
+  PROCEDURE mmf_file_create(filename: IN STRING) IS
+    FILE created_file : TEXT OPEN write_mode IS filename;
+  BEGIN
+    -- Write the file with nothing in it
+    write(created_file, "");
+  END;
+
+  PROCEDURE mmf_mm_from_file(SIGNAL mm_clk : IN STD_LOGIC;
+                             SIGNAL mm_rst : IN STD_LOGIC;
+                             SIGNAL mm_mosi : OUT t_mem_mosi;
+                             SIGNAL mm_miso : IN  t_mem_miso;
+                             rd_filename: IN STRING;
+                             wr_filename: IN STRING;
+                             rd_latency: IN NATURAL) IS
+    FILE rd_file : TEXT;
+    FILE wr_file : TEXT;
+
+    VARIABLE open_status_rd: file_open_status;
+    VARIABLE open_status_wr: file_open_status;
+
+    VARIABLE rd_line : LINE;
+    VARIABLE wr_line : LINE;
+
+    -- Note: Both the address and the data are interpreted as 32-bit data!
+    -- This means one has to use leading zeros in the file when either is
+    -- less than 8 hex characters, e.g.:
+    -- (address) 0000000A
+    -- (data)    DEADBEEF
+    -- ...as a hex address 'A' would fit in only 4 bits, causing an error in hread().
+    VARIABLE v_addr_slv : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+    VARIABLE v_data_slv : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+
+    VARIABLE v_rd_wr_str : STRING(1 TO 2); -- Contains 'RD' or 'WR'
+
+  BEGIN
+
+    proc_common_wait_until_low(mm_clk, mm_rst);
+
+    -- We have to open the file explicitely so we can check the status
+    file_open(open_status_rd, rd_file, rd_filename, read_mode);
+
+    -- open_status may throw an error if the file is being written to by some other program
+    IF open_status_rd=open_ok THEN
+
+      IF NOT endfile(rd_file) THEN
+        -- The file is not empty: process its contents
+
+        -- Read a line from it, first line indicates RD or WR
+        readline(rd_file, rd_line);
+        read(rd_line, v_rd_wr_str);
+
+        -- The second line represents the address offset:
+        readline(rd_file, rd_line);
+        hread(rd_line, v_addr_slv);  -- read the string as HEX and assign to SLV.
+
+        -- Write only: The third line contains the data to write:
+        IF v_rd_wr_str="WR" THEN
+          readline(rd_file, rd_line);
+          hread(rd_line, v_data_slv);  -- read the string as HEX and assign to SLV.
+        END IF;
+
+        -- We're done reading MM request from the .ctrl file.
+        -- Clear the .ctrl file by closing and recreating it, because we don't want to do the same
+        -- MM request again the next time this procedure is called.
+        file_close(rd_file);
+        mmf_file_create(rd_filename);
+
+        -- Execute the MM request to the MM slave
+        IF v_rd_wr_str="WR" THEN
+          print_str("[" & time_to_str(now) & "] " & rd_filename & ": Writing 0x" & slv_to_hex(v_data_slv) & " to address 0x" & slv_to_hex(v_addr_slv));
+          -- Treat 32 bit hex data from file as 32 bit VHDL INTEGER, so need to use signed TO_SINT() to avoid out of NATURAL range
+          -- warning in simulation due to '1' sign bit, because unsigned VHDL NATURAL only fits 31 bits
+          proc_mem_mm_bus_wr(TO_UINT(v_addr_slv), TO_SINT(v_data_slv), mm_clk, mm_miso, mm_mosi);
+
+        ELSIF v_rd_wr_str="RD" THEN
+          proc_mem_mm_bus_rd(TO_UINT(v_addr_slv), mm_clk, mm_miso, mm_mosi);
+          IF rd_latency>0 THEN
+            proc_mem_mm_bus_rd_latency(rd_latency, mm_clk);
+          END IF;
+          v_data_slv := mm_miso.rddata(31 DOWNTO 0);
+          print_str("[" & time_to_str(now) & "] " & rd_filename & ": Reading from address 0x" & slv_to_hex(v_addr_slv) & ": 0x" & slv_to_hex(v_data_slv));
+
+          -- Write the RD response read data to the .stat file
+          file_open(open_status_wr, wr_file, wr_filename, write_mode);
+          hwrite(wr_line, v_data_slv);
+          writeline(wr_file, wr_line);
+          file_close(wr_file);
+        END IF;
+
+      ELSE
+        -- Nothing to process; wait one MM clock cycle.
+        proc_common_wait_some_cycles(mm_clk, 1);
+      END IF;
+
+    ELSE
+      REPORT "mmf_mm_from_file() could not open " & rd_filename & " at " & time_to_str(now) SEVERITY NOTE;
+      -- Try again next time; wait one MM clock cycle.
+      proc_common_wait_some_cycles(mm_clk, 1);
+    END IF;
+
+    -- The END implicitely close the rd_file, if still necessary.
+  END;
+
+
+  PROCEDURE mmf_sim_ctrl_from_file(rd_filename: IN STRING;
+                                   wr_filename: IN STRING) IS
+
+    FILE rd_file : TEXT;
+    FILE wr_file : TEXT;
+
+    VARIABLE open_status_rd: file_open_status;
+    VARIABLE open_status_wr: file_open_status;
+
+    VARIABLE rd_line : LINE;
+    VARIABLE wr_line : LINE;
+
+    VARIABLE v_rd_wr_str : STRING(1 TO 12); -- "GET_SIM_TIME"
+
+  BEGIN
+
+    -- We have to open the file explicitely so we can check the status
+    file_open(open_status_rd, rd_file, rd_filename, read_mode);
+
+    -- open_status may throw an error if the file is being written to by some other program
+    IF open_status_rd=open_ok THEN
+
+      IF NOT endfile(rd_file) THEN
+        -- The file is not empty: process its contents
+
+        -- Read a line from it, interpret the simulation request
+        readline(rd_file, rd_line);
+        read(rd_line, v_rd_wr_str);
+
+        -- We're done reading this simulation request .ctrl file. Clear the file by closing and recreating it.
+        file_close(rd_file);
+        mmf_file_create(rd_filename);
+
+        -- Execute the simulation request
+        IF v_rd_wr_str="GET_SIM_TIME" THEN
+          -- Write the GET_SIM_TIME response time NOW to the .stat file
+          file_open(open_status_wr, wr_file, wr_filename, write_mode);
+          write(wr_line, time_to_str(now));
+          writeline(wr_file, wr_line);
+          file_close(wr_file);
+        END IF;
+
+      ELSE
+        -- Nothing to process; wait in procedure mmf_poll_sim_ctrl_file
+        NULL;
+      END IF;
+
+    ELSE
+      REPORT "mmf_mm_from_file() could not open " & rd_filename & " at " & time_to_str(now) SEVERITY NOTE;
+      -- Try again next time; wait in procedure mmf_poll_sim_ctrl_file
+    END IF;
+
+    -- The END implicitely close the rd_file, if still necessary.
+  END;
+
+
+  PROCEDURE mmf_poll_sim_ctrl_file(rd_file_name: IN STRING; wr_file_name : IN STRING) IS
+  BEGIN
+    -- Create the ctrl file that we're going to read from
+    print_str("[" & time_to_str(now) & "] " & rd_file_name & ": Created" );
+    mmf_file_create(rd_file_name);
+
+    WHILE TRUE LOOP
+      mmf_sim_ctrl_from_file(rd_file_name, wr_file_name);
+      WAIT FOR 1 ns;
+    END LOOP;
+
+  END;
+
+
+  PROCEDURE mmf_poll_sim_ctrl_file(SIGNAL mm_clk  : IN STD_LOGIC;
+                                   rd_file_name: IN STRING; wr_file_name : IN STRING) IS
+  BEGIN
+    -- Create the ctrl file that we're going to read from
+    print_str("[" & time_to_str(now) & "] " & rd_file_name & ": Created" );
+    mmf_file_create(rd_file_name);
+
+    WHILE TRUE LOOP
+      mmf_sim_ctrl_from_file(rd_file_name, wr_file_name);
+      proc_common_wait_some_cycles(mm_clk, 1);
+    END LOOP;
+
+  END;
+
+
+  PROCEDURE mmf_wait_for_file_status(rd_filename   : IN STRING;  -- file name with extension
+                                     exit_on_empty : IN BOOLEAN;
+                                     SIGNAL mm_clk : IN STD_LOGIC) IS
+    FILE     rd_file        : TEXT;
+    VARIABLE open_status_rd : file_open_status;
+    VARIABLE v_endfile      : BOOLEAN;
+  BEGIN
+    -- Check on falling_edge(mm_clk) because mmf_mm_from_file() operates on rising_edge(mm_clk)
+    -- Note: In fact the file IO also works fine when rising_edge() is used, but then
+    --       tb_tb_mm_file.vhd takes about 1% more mm_clk cycles
+    WAIT UNTIL falling_edge(mm_clk);
+
+    -- Keep reading the file until it has become empty by some other program
+    WHILE TRUE LOOP
+      -- Open the file in read mode to check whether it is empty
+      file_open(open_status_rd, rd_file, rd_filename, read_mode);
+      -- open_status may throw an error if the file is being written to by some other program
+      IF open_status_rd=open_ok THEN
+        v_endfile := endfile(rd_file);
+        file_close(rd_file);
+        IF exit_on_empty THEN
+          IF v_endfile THEN
+            -- The file is empty; continue
+            EXIT;
+          ELSE
+            -- The file is not empty; wait one MM clock cycle.
+            WAIT UNTIL falling_edge(mm_clk);
+          END IF;
+        ELSE
+          IF v_endfile THEN
+            -- The file is empty; wait one MM clock cycle.
+            WAIT UNTIL falling_edge(mm_clk);
+          ELSE
+            -- The file is not empty; continue
+            EXIT;
+          END IF;
+        END IF;
+      ELSE
+        REPORT "mmf_wait_for_file_status() could not open " & rd_filename & " at " & time_to_str(now) SEVERITY NOTE;
+        WAIT UNTIL falling_edge(mm_clk);
+      END IF;
+    END LOOP;
+    -- The END implicitely close the file, if still necessary.
+  END;
+
+  PROCEDURE mmf_wait_for_file_empty(rd_filename   : IN STRING;  -- file name with extension
+                                    SIGNAL mm_clk : IN STD_LOGIC) IS
+  BEGIN
+    mmf_wait_for_file_status(rd_filename, TRUE, mm_clk);
+  END;
+
+  PROCEDURE mmf_wait_for_file_not_empty(rd_filename   : IN STRING;  -- file name with extension
+                                        SIGNAL mm_clk : IN STD_LOGIC) IS
+  BEGIN
+    mmf_wait_for_file_status(rd_filename, FALSE, mm_clk);
+  END;
+
+  PROCEDURE mmf_mm_bus_wr(filename      : IN STRING;   -- file name without extension
+                          wr_addr       : IN INTEGER;  -- use integer to support full 32 bit range
+                          wr_data       : IN INTEGER;
+                          SIGNAL mm_clk : IN STD_LOGIC) IS
+    CONSTANT ctrl_filename  : STRING := filename & ".ctrl";
+    FILE     ctrl_file      : TEXT;
+    VARIABLE open_status_wr : file_open_status;
+    VARIABLE wr_line        : LINE;
+
+  BEGIN
+    -- Write MM WR access to the .ctrl file.
+    -- The MM device is ready for a new MM request, because any previous MM request has finished at
+    -- mmf_mm_bus_wr() or mmf_mm_bus_rd() procedure exit, therefore just overwrite the .ctrl file.
+    file_open(open_status_wr, ctrl_file, ctrl_filename, write_mode);
+    -- open_status may throw an error if the file is being written to by some other program
+    IF open_status_wr=open_ok THEN
+      write(wr_line, STRING'("WR"));
+      writeline(ctrl_file, wr_line);
+      hwrite(wr_line, TO_SVEC(wr_addr, c_word_w));
+      writeline(ctrl_file, wr_line);
+      hwrite(wr_line, TO_SVEC(wr_data, c_word_w));
+      writeline(ctrl_file, wr_line);
+      file_close(ctrl_file);
+    ELSE
+      REPORT "mmf_mm_bus_wr() could not open " & ctrl_filename & " at " & time_to_str(now) SEVERITY NOTE;
+    END IF;
+
+    -- Prepare for next MM request
+    -- Keep reading the .ctrl file until it is empty, to ensure that the MM device is ready for a new MM request
+    mmf_wait_for_file_empty(ctrl_filename, mm_clk);
+
+    -- The END implicitely close the ctrl_file, if still necessary.
+  END;
+
+  PROCEDURE mmf_mm_bus_rd(filename       : IN STRING;   -- file name without extension
+                          rd_latency     : IN NATURAL;
+                          rd_addr        : IN INTEGER;  -- use integer to support full 32 bit range
+                          SIGNAL rd_data : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+                          SIGNAL mm_clk  : IN STD_LOGIC) IS
+    CONSTANT ctrl_filename  : STRING := filename & ".ctrl";
+    CONSTANT stat_filename  : STRING := filename & ".stat";
+    FILE     ctrl_file      : TEXT;
+    FILE     stat_file      : TEXT;
+    VARIABLE open_status_wr : file_open_status;
+    VARIABLE open_status_rd : file_open_status;
+    VARIABLE wr_line        : LINE;
+    VARIABLE rd_line        : LINE;
+    VARIABLE v_rd_data      : STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+
+  BEGIN
+    -- Clear the .stat file by recreating it, because we don't want to do read old file data again
+    mmf_file_create(stat_filename);
+
+    -- Write MM RD access to the .ctrl file.
+    -- The MM device is ready for a new MM request, because any previous MM request has finished at
+    -- mmf_mm_bus_wr() or mmf_mm_bus_rd() procedure exit, therefore just overwrite the .ctrl file.
+    file_open(open_status_wr, ctrl_file, ctrl_filename, write_mode);
+    -- open_status may throw an error if the file is being written to by some other program
+    IF open_status_wr=open_ok THEN
+      write(wr_line, STRING'("RD"));
+      writeline(ctrl_file, wr_line);
+      hwrite(wr_line, TO_SVEC(rd_addr, c_word_w));
+      writeline(ctrl_file, wr_line);
+      file_close(ctrl_file);
+    ELSE
+      REPORT "mmf_mm_bus_rd() could not open " & ctrl_filename & " at " & time_to_str(now) SEVERITY FAILURE;
+    END IF;
+
+    -- Wait until the MM RD access has written the read data to the .stat file
+    mmf_wait_for_file_not_empty(stat_filename, mm_clk);
+
+    -- Read the MM RD access read data from the .stat file
+    file_open(open_status_rd, stat_file, stat_filename, read_mode);
+    -- open_status may throw an error if the file is being written to by some other program
+    IF open_status_rd=open_ok THEN
+      readline(stat_file, rd_line);
+      hread(rd_line, v_rd_data);
+      file_close(stat_file);
+      rd_data <= v_rd_data;
+      -- wait to ensure rd_data has got v_rd_data, otherwise rd_data still holds the old data on procedure exit
+      -- the wait should be < mm_clk period/2 to not affect the read rate
+      WAIT FOR 1 fs;
+    ELSE
+      REPORT "mmf_mm_bus_rd() could not open " & stat_filename & " at " & time_to_str(now) SEVERITY FAILURE;
+    END IF;
+
+    -- No need to prepare for next MM request, because:
+    -- . the .ctrl file must already be empty because the .stat file was there
+    -- . the .stat file will be cleared on this procedure entry
+
+    -- The END implicitely closes the files, if still necessary
+  END;
+
+  -- rd_latency = 1
+  PROCEDURE mmf_mm_bus_rd(filename       : IN STRING;
+                          rd_addr        : IN INTEGER;
+                          SIGNAL rd_data : OUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+                          SIGNAL mm_clk  : IN STD_LOGIC) IS
+  BEGIN
+    mmf_mm_bus_rd(filename, 1, rd_addr, rd_data, mm_clk);
+  END;
+
+  PROCEDURE mmf_mm_wait_until_value(filename         : IN STRING;   -- file name without extension
+                                    rd_addr          : IN INTEGER;
+                                    c_representation : IN STRING;  -- treat rd_data as "SIGNED" or "UNSIGNED" 32 bit word
+                                    SIGNAL rd_data   : INOUT STD_LOGIC_VECTOR(c_word_w-1 DOWNTO 0);
+                                    c_condition      : IN STRING;  -- ">", ">=", "=", "<=", "<", "/="
+                                    c_rd_value       : IN INTEGER;
+                                    c_rd_interval    : IN TIME;
+                                    SIGNAL mm_clk    : IN STD_LOGIC) IS
+  BEGIN
+    WHILE TRUE LOOP
+      -- Read current
+      mmf_mm_bus_rd(filename, rd_addr, rd_data, mm_clk);  -- only read low part
+      IF c_representation="SIGNED" THEN
+        IF    c_condition=">"  THEN IF TO_SINT(rd_data)> c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
+        ELSIF c_condition=">=" THEN IF TO_SINT(rd_data)>=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
+        ELSIF c_condition="/=" THEN IF TO_SINT(rd_data)/=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
+        ELSIF c_condition="<=" THEN IF TO_SINT(rd_data)<=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
+        ELSIF c_condition="<"  THEN IF TO_SINT(rd_data)< c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
+        ELSE                        IF TO_SINT(rd_data) =c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;  -- default: "="
+        END IF;
+      ELSE  -- default: UNSIGED
+        IF    c_condition=">"  THEN IF TO_UINT(rd_data)> c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
+        ELSIF c_condition=">=" THEN IF TO_UINT(rd_data)>=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
+        ELSIF c_condition="/=" THEN IF TO_UINT(rd_data)/=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
+        ELSIF c_condition="<=" THEN IF TO_UINT(rd_data)<=c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
+        ELSIF c_condition="<"  THEN IF TO_UINT(rd_data)< c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;
+        ELSE                        IF TO_UINT(rd_data) =c_rd_value THEN EXIT; ELSE WAIT FOR c_rd_interval; END IF;  -- default: "="
+        END IF;
+      END IF;
+    END LOOP;
+  END mmf_mm_wait_until_value;
+
+
+  PROCEDURE mmf_sim_get_now(filename       : IN STRING;   -- file name without extension
+                            SIGNAL rd_now  : OUT STRING;
+                            SIGNAL mm_clk  : IN STD_LOGIC) IS
+    CONSTANT ctrl_filename  : STRING := filename & ".ctrl";
+    CONSTANT stat_filename  : STRING := filename & ".stat";
+    FILE     ctrl_file      : TEXT;
+    FILE     stat_file      : TEXT;
+    VARIABLE open_status_wr : file_open_status;
+    VARIABLE open_status_rd : file_open_status;
+    VARIABLE wr_line        : LINE;
+    VARIABLE rd_line        : LINE;
+    VARIABLE v_rd_now       : STRING(rd_now'RANGE);
+
+  BEGIN
+    -- Clear the sim.stat file by recreating it, because we don't want to do read old simulator status again
+    mmf_file_create(stat_filename);
+
+    -- Write GET_SIM_TIME to the sim.ctrl file
+    -- The simulation is ready for a new simulation status request, because any previous simulation status request has finished at
+    -- mmf_sim_get_now() procedure exit, therefore just overwrite the .ctrl file.
+    file_open(open_status_wr, ctrl_file, ctrl_filename, write_mode);
+    -- open_status may throw an error if the file is being written to by some other program
+    IF open_status_wr=open_ok THEN
+      write(wr_line, STRING'("GET_SIM_TIME"));
+      writeline(ctrl_file, wr_line);
+      file_close(ctrl_file);
+    ELSE
+      REPORT "mmf_sim_get_now() could not open " & ctrl_filename & " at " & time_to_str(now) SEVERITY FAILURE;
+    END IF;
+
+    -- Wait until the simulation has written the simulation status to the sim.stat file
+    mmf_wait_for_file_not_empty(stat_filename, mm_clk);
+
+    -- Read the GET_SIM_TIME simulation status from the .stat file
+    file_open(open_status_rd, stat_file, stat_filename, read_mode);
+    -- open_status may throw an error if the file is being written to by some other program
+    IF open_status_rd=open_ok THEN
+      readline(stat_file, rd_line);
+      read(rd_line, v_rd_now);
+      file_close(stat_file);
+      rd_now <= v_rd_now;
+      print_str("GET_SIM_TIME = " & v_rd_now & " at " & time_to_str(now));
+    ELSE
+      REPORT "mmf_sim_get_now() could not open " & stat_filename & " at " & time_to_str(now) SEVERITY FAILURE;
+    END IF;
+
+    -- No need to prepare for next simulation status request, because:
+    -- . the .ctrl file must already be empty because the .stat file was there
+    -- . the .stat file will be cleared on this procedure entry
+
+    -- The END implicitely closes the files, if still necessary
+  END;
+
+  -- Functions to create prefixes for the mmf file filename
+  FUNCTION mmf_prefix(name : STRING; index : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN name & "_" & int_to_str(index) & "_";
+  END;
+
+  FUNCTION mmf_tb_prefix(tb : INTEGER) RETURN STRING IS
+  BEGIN
+    RETURN mmf_prefix("TB", tb);
+  END;
+
+  FUNCTION mmf_subrack_prefix(subrack : INTEGER) RETURN STRING IS
+  BEGIN
+    RETURN mmf_prefix("SUBRACK", subrack);
+  END;
+
+  -- Functions to create mmf file prefix that is unique per slave, for increasing number of hierarchy levels:
+  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN dir_path & mmf_prefix(s0, i0);
+  END;
+
+  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1);
+  END;
+
+  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2);
+  END;
+
+  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2) & mmf_prefix(s3, i3);
+  END;
+
+  FUNCTION mmf_slave_prefix(dir_path, s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL; s4 : STRING; i4 : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2) & mmf_prefix(s3, i3) & mmf_prefix(s4, i4);
+  END;
+
+  -- Use local dir_path
+  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN c_mmf_local_dir_path & mmf_prefix(s0, i0);
+  END;
+
+  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN c_mmf_local_dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1);
+  END;
+
+  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN c_mmf_local_dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2);
+  END;
+
+  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN c_mmf_local_dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2) & mmf_prefix(s3, i3);
+  END;
+
+  FUNCTION mmf_slave_prefix(s0 : STRING; i0 : NATURAL; s1 : STRING; i1 : NATURAL; s2 : STRING; i2 : NATURAL; s3 : STRING; i3 : NATURAL; s4 : STRING; i4 : NATURAL) RETURN STRING IS
+  BEGIN
+    RETURN c_mmf_local_dir_path & mmf_prefix(s0, i0) & mmf_prefix(s1, i1) & mmf_prefix(s2, i2) & mmf_prefix(s3, i3) & mmf_prefix(s4, i4);
+  END;
+
+END mm_file_pkg;
+
diff --git a/libraries/base/mm/tb/vhdl/mm_waitrequest_model.vhd b/libraries/base/mm/tb/vhdl/mm_waitrequest_model.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..fc530c0b9df5c8c0868441ba17b9344e333c7c29
--- /dev/null
+++ b/libraries/base/mm/tb/vhdl/mm_waitrequest_model.vhd
@@ -0,0 +1,138 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+-- 
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+-- 
+-- Author: E. Kooistra
+-- Purpose: Provide waitrequest stimuli to model a slave with MM flow control
+-- Description:
+--   The model applies random waitrequest stimuli for a MM slave that does not
+--   need MM flow control. In this way the MM slave acts like a MM slave that
+--   does need MM flow control.
+--   * The model only controls the bus_miso.waitrequest. The other slave_miso 
+--     fields are wired to the bus_miso. The bus master will act upon the
+--     waitrequest, so model can rely on that regarding the bus_mosi. However
+--     towards the MM slave that has no flow control the model has to gate the
+--     bus_mosi wr and rd with the waitrequest, so that the MM slave only gets
+--     a ram_mosi rd or wr when it was acknowledged.
+--   * When g_waitrequest = TRUE then the waitrequest model is applied to the
+--     bus_miso. Use g_waitrequest = FALSE to bypass the waitrequest model,
+--     so then bus_miso.waitrequest is fixed '0'.
+--   * The g_seed is used to initalize the random PRSG, e.g use slave instance
+--     index as g_seed to have different stimuli per instance.
+--   * The maximum number of cycles that waitrequest depends on the period of
+--     the LFSR random sequence generator and can be:
+--     . '1' for g_prsg_w mm_clk cycles
+--     . '0' for g_prsg_w-1 mm_clk cycles
+-- Remarks:
+-- . To some extend the ASSERTs check the flow control. The testbench has to
+--   verify the rddata to ensure more test coverage.
+--
+-------------------------------------------------------------------------------
+
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+USE common_lib.common_lfsr_sequences_pkg.ALL;
+
+ENTITY mm_waitrequest_model IS
+  GENERIC (
+    g_waitrequest : BOOLEAN;
+    g_seed        : NATURAL := 0;
+    g_prsg_w      : NATURAL := 16
+  );
+  PORT (
+    mm_clk      : IN  STD_LOGIC;
+    bus_mosi    : IN  t_mem_mosi;
+    bus_miso    : OUT t_mem_miso;
+    slave_mosi  : OUT t_mem_mosi;
+    slave_miso  : IN  t_mem_miso
+  );
+END mm_waitrequest_model;
+
+ARCHITECTURE rtl OF mm_waitrequest_model IS
+  
+  CONSTANT c_prsg_init     : NATURAL := g_seed + 1;  -- PRSG init must be > 0
+  
+  SIGNAL prsg              : STD_LOGIC_VECTOR(g_prsg_w-1 DOWNTO 0) := TO_UVEC(c_prsg_init, g_prsg_w);
+  
+  SIGNAL waitrequest       : STD_LOGIC;
+
+  SIGNAL prev_bus_mosi     : t_mem_mosi;
+  SIGNAL prev_waitrequest  : STD_LOGIC;
+  
+BEGIN
+
+  no_waitrequest : IF g_waitrequest=FALSE GENERATE
+    slave_mosi <= bus_mosi;
+    
+    p_waitrequest : PROCESS(slave_miso)
+    BEGIN
+      bus_miso <= slave_miso;
+      bus_miso.waitrequest <= '0';
+    END PROCESS;
+  END GENERATE;
+  
+  gen_waitrequest : IF g_waitrequest=TRUE GENERATE
+    -- Model MM flow control using random waitrequest
+    p_reg : PROCESS(mm_clk)
+    BEGIN       
+      IF rising_edge(mm_clk) THEN
+        -- random waitrequest flow control
+        prsg             <= func_common_random(prsg);
+        -- check MM access
+        prev_bus_mosi    <= bus_mosi;
+        prev_waitrequest <= waitrequest;
+      END IF;
+    END PROCESS;
+    
+    waitrequest <= prsg(0);
+    
+    -- Apply MM flow control to bus master using waitrequest
+    p_bus_miso : PROCESS(waitrequest, slave_miso)
+    BEGIN
+      bus_miso <= slave_miso;
+      bus_miso.waitrequest <= waitrequest;
+    END PROCESS;
+
+    -- Gate MM rd and wr access to RAM slave that has no flow control
+    p_slave_mosi : PROCESS(waitrequest, bus_mosi)
+    BEGIN
+      slave_mosi <= bus_mosi;
+      slave_mosi.wr <= bus_mosi.wr AND NOT waitrequest;
+      slave_mosi.rd <= bus_mosi.rd AND NOT waitrequest;
+    END PROCESS;
+    
+    -- Verify that MM access is not removed before it is acknowledged by waitrequest
+    p_verify : PROCESS(bus_mosi, prev_bus_mosi, prev_waitrequest)
+    BEGIN
+      IF prev_waitrequest = '1' THEN
+        IF prev_bus_mosi.wr = '1' AND bus_mosi.wr = '0' THEN REPORT "Aborted slave write." SEVERITY ERROR; END IF;
+        IF prev_bus_mosi.rd = '1' AND bus_mosi.rd = '0' THEN REPORT "Aborted slave read." SEVERITY ERROR; END IF;
+        IF prev_bus_mosi.wr = '1' AND bus_mosi.address /= prev_bus_mosi.address THEN REPORT "Address change during pending slave write." SEVERITY ERROR; END IF;
+        IF prev_bus_mosi.rd = '1' AND bus_mosi.address /= prev_bus_mosi.address THEN REPORT "Address change during pending slave read." SEVERITY ERROR; END IF;
+      END IF;
+    END PROCESS;
+    
+  END GENERATE;
+  
+END rtl;
diff --git a/libraries/base/mm/tb/vhdl/tb_mm_bus.vhd b/libraries/base/mm/tb/vhdl/tb_mm_bus.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..44cb20799724a60185b5ad273e029245b8f3ee52
--- /dev/null
+++ b/libraries/base/mm/tb/vhdl/tb_mm_bus.vhd
@@ -0,0 +1,249 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: Test bench for mm_bus.vhd
+-- Remark:
+-- . This test bench covers:
+--   . g_nof_slaves >= 1
+--   . g_waitrequest for g_pipeline_miso_wait = FALSE
+--   . g_pipeline_mosi
+--   . g_pipeline_miso_rdval
+--   . g_pipeline_miso_wait = FALSE
+--   . g_rd_latency >= 1 (using 0 is supported by mm_bus, but not by
+--     the common_ram_r_w in u_slaves)
+--   . same g_rd_latency for all slaves
+--   . same g_width for all slaves
+--   . regular base address spacing of slaves in c_base_arr
+-- . The mm_bus.vhd can support a list of arbitrary width slaves, but
+--   this tb_mm_bus test bench uses an array of fixed width slaves.
+--   It is considered sufficient coverage for this tb and the corresponding
+--   multi tb_tb to also only support regular c_base_arr, same g_rd_latency,
+--   and same g_width for all slaves. The tb_mm_master_mux also uses a
+--   mm_bus.vhd and the tb_mm_master_mux does uses an array of
+--   arbitrary width slaves.
+--
+-------------------------------------------------------------------------------
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE IEEE.NUMERIC_STD.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+USE common_lib.tb_common_pkg.ALL;
+USE common_lib.tb_common_mem_pkg.ALL;
+
+ENTITY tb_mm_bus IS
+ GENERIC (
+    g_nof_slaves          : POSITIVE := 1;       -- Number of slave memory interfaces on the MM bus array.
+    g_base_offset         : NATURAL := 0;        -- Address of first slave on the MM bus
+    g_width_w             : POSITIVE := 4;       -- Address width of each slave memory in the MM bus array.
+    g_rd_latency          : NATURAL := 1;        -- Read latency of the slaves
+    g_waitrequest         : BOOLEAN := FALSE;     -- When TRUE model waitrequest by MM slaves, else fixed '0'
+    g_pipeline_mosi       : BOOLEAN := FALSE;
+    g_pipeline_miso_rdval : BOOLEAN := FALSE;
+    g_pipeline_miso_wait  : BOOLEAN := FALSE
+  );
+END tb_mm_bus;
+
+-- Usage:
+--   > as 10
+--   > run -all
+
+
+ARCHITECTURE tb OF tb_mm_bus IS
+
+  CONSTANT mm_clk_period   : TIME    := 10 ns;
+
+  CONSTANT c_repeat          : NATURAL := 10;--sel_a_b(g_waitrequest, 10, 2);  -- repeat 2 for deterministic, more often for random
+  CONSTANT c_slave_span      : NATURAL := 2**g_width_w;
+  CONSTANT c_base_arr        : t_nat_natural_arr := array_init(g_base_offset, g_nof_slaves, c_slave_span);  -- Address base per slave
+  CONSTANT c_width_arr       : t_nat_natural_arr := array_init(    g_width_w, g_nof_slaves);                -- Address width per slave
+  CONSTANT c_rd_latency_arr  : t_nat_natural_arr := array_init( g_rd_latency, g_nof_slaves);                -- Read latency per slave
+  CONSTANT c_slave_enable_arr: t_nat_boolean_arr := array_init(         TRUE, g_nof_slaves);                -- TRUE for connected slaves
+  CONSTANT c_waitrequest_arr : t_nat_boolean_arr := array_init(g_waitrequest, g_nof_slaves);                -- Flow control per slave
+
+  CONSTANT c_bus_pipelining      : BOOLEAN := g_pipeline_mosi OR g_pipeline_miso_rdval OR g_pipeline_miso_wait;
+  CONSTANT c_pipeline_mosi       : NATURAL := sel_a_b(g_pipeline_mosi, 1, 0);
+  CONSTANT c_pipeline_miso_rdval : NATURAL := sel_a_b(g_pipeline_miso_rdval, 1, 0);
+  CONSTANT c_pipeline_miso_wait  : NATURAL := sel_a_b(g_pipeline_miso_wait, 1, 0);
+  CONSTANT c_read_latency        : NATURAL := c_pipeline_mosi + g_rd_latency + c_pipeline_miso_rdval;
+
+  CONSTANT c_data_w     : NATURAL := 32;
+  CONSTANT c_test_ram   : t_c_mem := (latency  => g_rd_latency,
+                                      adr_w    => g_width_w,
+                                      dat_w    => c_data_w,
+                                      nof_dat  => c_slave_span,
+                                      init_sl  => '0');
+  SIGNAL mm_rst   : STD_LOGIC;
+  SIGNAL mm_clk   : STD_LOGIC := '1';
+  SIGNAL tb_end   : STD_LOGIC;
+
+  SIGNAL cnt_rd    : NATURAL := 0;
+  SIGNAL cnt_rdval : NATURAL := 0;
+
+  -- MM bus
+  SIGNAL master_mosi      : t_mem_mosi := c_mem_mosi_rst;
+  SIGNAL master_miso      : t_mem_miso := c_mem_miso_rst;
+  SIGNAL slave_mosi_arr   : t_mem_mosi_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_mosi_rst);
+  SIGNAL slave_miso_arr   : t_mem_miso_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_miso_rst);
+  SIGNAL ram_mosi_arr     : t_mem_mosi_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_mosi_rst);
+  SIGNAL ram_miso_arr     : t_mem_miso_arr(0 TO g_nof_slaves-1) := (OTHERS=>c_mem_miso_rst);
+
+  -- Debug signals for monitoring in simulation Wave window
+  SIGNAL dbg_c_base_arr        : t_nat_natural_arr(0 TO g_nof_slaves-1) := c_base_arr;
+  SIGNAL dbg_c_width_arr       : t_nat_natural_arr(0 TO g_nof_slaves-1) := c_width_arr;
+  SIGNAL dbg_c_rd_latency_arr  : t_nat_natural_arr(0 TO g_nof_slaves-1) := c_rd_latency_arr;
+
+BEGIN
+
+  mm_clk <= NOT mm_clk OR tb_end AFTER mm_clk_period/2;
+  mm_rst <= '1', '0' AFTER mm_clk_period*5;
+
+  -----------------------------------------------------------------------------
+  -- Write stimuli and readback to verify
+  -----------------------------------------------------------------------------
+  p_stimuli : PROCESS
+    VARIABLE v_wrdata  : INTEGER;  -- write data
+  BEGIN
+    tb_end <= '0';
+    master_mosi <= c_mem_mosi_rst;
+
+    -- Wait until reset is released
+    proc_common_wait_until_low(mm_clk, mm_rst);
+    proc_common_wait_some_cycles(mm_clk, 10);
+
+    -- Repeat twice to have wr all, rd all, wr all, rd all
+    v_wrdata := 0;
+    FOR vR IN 0 TO c_repeat-1 LOOP
+      -- Write the whole memory range
+      FOR vI IN 0 TO g_nof_slaves-1 LOOP
+        FOR vJ IN 0 TO c_slave_span-1 LOOP
+          proc_mem_mm_bus_wr(g_base_offset + vI*c_slave_span + vJ, v_wrdata, mm_clk, master_miso, master_mosi);
+          v_wrdata := v_wrdata + 1;
+        END LOOP;
+        proc_common_wait_some_cycles(mm_clk, 10);
+      END LOOP;
+
+      -- Read back the whole range and check if data is as expected
+      FOR vI IN 0 TO g_nof_slaves-1 LOOP
+        FOR vJ IN 0 TO c_slave_span-1 LOOP
+          proc_mem_mm_bus_rd(g_base_offset + vI*c_slave_span + vJ, mm_clk, master_miso, master_mosi);
+          --proc_common_wait_some_cycles(mm_clk, c_read_latency);  -- not needed, see p_verify
+          cnt_rd <= cnt_rd + 1;
+        END LOOP;
+        proc_common_wait_some_cycles(mm_clk, 10);
+      END LOOP;
+    END LOOP;
+
+    proc_common_wait_some_cycles(mm_clk, 10);
+
+    -- Verify that test has indeed ran
+    WAIT FOR 1 ns;  -- wait 1 ns to ensure that assert report appears at end of transcript log
+    ASSERT cnt_rdval = cnt_rd AND cnt_rdval > 0 REPORT "Wrong number of rdval" SEVERITY ERROR;
+
+    tb_end <= '1';
+    WAIT;
+  END PROCESS;
+
+  -- Use miso.rdval to know when to verify the rddata, rather than to wait for a fixed c_read_latency after
+  -- the mosi.rd. The advantage is that then rd accesses can be done on every mm_clk, without having to
+  -- wait for the c_read_latency. In case of g_pipeline_mosi = TRUE or g_pipeline_miso_wait = TRUE it is
+  -- even essential to use rdval, because then the latency between rd and rdval can become larger than
+  -- c_read_latency and even variable (in case of g_waitrequest = TRUE). The disadvantage is that the MM
+  -- slave must support rdval, but that is ensured by mm_slave_enable.
+  p_verify : PROCESS
+    VARIABLE v_expdata : INTEGER := 0;  -- expected data
+    VARIABLE v_rddata  : INTEGER;       -- read data
+  BEGIN
+    WAIT UNTIL rising_edge(mm_clk);
+    IF master_miso.rdval = '1' THEN
+      cnt_rdval <= cnt_rdval + 1;
+      v_rddata := TO_UINT(master_miso.rddata(c_data_w-1 DOWNTO 0));
+      IF v_rddata /= v_expdata THEN
+        REPORT "Error! Readvalue is not as expected" SEVERITY ERROR;
+      END IF;
+      v_expdata := v_expdata + 1;
+    END IF;
+  END PROCESS;
+
+  -----------------------------------------------------------------------------
+  -- The MM bus
+  -----------------------------------------------------------------------------
+  u_mm_bus: ENTITY work.mm_bus
+  GENERIC MAP (
+    g_nof_slaves          => g_nof_slaves,
+    g_base_arr            => c_base_arr,
+    g_width_arr           => c_width_arr,
+    g_rd_latency_arr      => c_rd_latency_arr,
+    g_slave_enable_arr    => c_slave_enable_arr,
+    g_waitrequest_arr     => c_waitrequest_arr,
+    g_pipeline_mosi       => g_pipeline_mosi,
+    g_pipeline_miso_rdval => g_pipeline_miso_rdval,
+    g_pipeline_miso_wait  => g_pipeline_miso_wait
+  )
+  PORT MAP (
+    mm_rst         => mm_rst,
+    mm_clk         => mm_clk,
+    master_mosi    => master_mosi,
+    master_miso    => master_miso,
+    slave_mosi_arr => slave_mosi_arr,
+    slave_miso_arr => slave_miso_arr
+  );
+
+  -----------------------------------------------------------------------------
+  -- Model the MM slaves
+  -----------------------------------------------------------------------------
+  gen_slaves : FOR I IN 0 TO g_nof_slaves-1 GENERATE
+    u_waitrequest_model : ENTITY work.mm_waitrequest_model
+    GENERIC MAP (
+      g_waitrequest => g_waitrequest,
+      g_seed        => I
+    )
+    PORT MAP (
+      mm_clk     => mm_clk,
+      bus_mosi   => slave_mosi_arr(I),
+      bus_miso   => slave_miso_arr(I),
+      slave_mosi => ram_mosi_arr(I),
+      slave_miso => ram_miso_arr(I)
+    );
+
+    u_ram : ENTITY common_lib.common_ram_r_w
+    GENERIC MAP (
+      g_ram       => c_test_ram,
+      g_init_file => "UNUSED"
+    )
+    PORT MAP (
+      rst       => mm_rst,
+      clk       => mm_clk,
+      clken     => '1',
+      wr_en     => ram_mosi_arr(I).wr,
+      wr_adr    => ram_mosi_arr(I).address(g_width_w-1 DOWNTO 0),
+      wr_dat    => ram_mosi_arr(I).wrdata(c_data_w-1 DOWNTO 0),
+      rd_en     => ram_mosi_arr(I).rd,
+      rd_adr    => ram_mosi_arr(I).address(g_width_w-1 DOWNTO 0),
+      rd_dat    => ram_miso_arr(I).rddata(c_data_w-1 DOWNTO 0),
+      rd_val    => ram_miso_arr(I).rdval
+    );
+  END GENERATE;
+
+END tb;
diff --git a/libraries/base/mm/tb/vhdl/tb_mm_master_mux.vhd b/libraries/base/mm/tb/vhdl/tb_mm_master_mux.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..d3550ff0001464c698daef02c55ab19cedc57373
--- /dev/null
+++ b/libraries/base/mm/tb/vhdl/tb_mm_master_mux.vhd
@@ -0,0 +1,225 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+--
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--
+-- Author: E. Kooistra
+-- Purpose: Test bench for mm_master_mux.vhd and also mm_bus
+-- Description:
+--   The test bench uses mm_master_mux to access a RAM via an array of
+--   masters. The array of masters is modelled using a stimuli from a single
+--   master that get demultiplexed to the array of masters using
+--   mm_bus. The address space of the RAM is defined by the g_base_arr
+--   and g_width_arr that define the mm_bus. Therefore this test bench
+--   implicitely also verifies mm_bus.vhd.
+--
+--               stimuli            master              mux
+--               mosi               mosi_arr            mosi
+--                         common -------/----> common
+--   p_stimuli ----------> mem    ------/-----> mem    --------> RAM
+--                         bus    -----/------> master
+--                                    /         mux
+--                                g_nof_masters
+-- Remark:
+--   In an application it is typical to use mm_master_mux to connect
+--   mulitple masters to multiple slabes via a mm_bus MM bus.
+-------------------------------------------------------------------------------
+
+LIBRARY IEEE, common_lib;
+USE IEEE.STD_LOGIC_1164.ALL;
+USE IEEE.NUMERIC_STD.ALL;
+USE common_lib.common_pkg.ALL;
+USE common_lib.common_mem_pkg.ALL;
+USE common_lib.tb_common_pkg.ALL;
+USE common_lib.tb_common_mem_pkg.ALL;
+
+ENTITY tb_mm_master_mux IS
+ GENERIC (
+    g_nof_masters             : POSITIVE := 2;   -- Number of master memory interfaces on the MM bus array.
+    g_base_arr                : t_nat_natural_arr := (0, 256);  -- Address base per slave port of mm_bus
+    g_width_arr               : t_nat_natural_arr := (4,   8);  -- Address width per slave port of mm_bus
+    g_waitrequest             : BOOLEAN := TRUE;    -- When TRUE model waitrequest by the MM RAM slave, else fixed '0'
+    g_pipeline_bus_mosi       : BOOLEAN := FALSE;
+    g_pipeline_bus_miso_rdval : BOOLEAN := FALSE;
+    g_pipeline_bus_miso_wait  : BOOLEAN := FALSE
+  );
+END tb_mm_master_mux;
+
+-- Usage:
+--   > as 10
+--   > run -all
+
+
+ARCHITECTURE tb OF tb_mm_master_mux IS
+
+  CONSTANT mm_clk_period   : TIME    := 10 ns;
+
+  CONSTANT c_repeat                  : NATURAL := sel_a_b(g_waitrequest, 10, 2);  -- repeat 2 for deterministic, more often for random
+  CONSTANT c_bus_pipeline_mosi       : NATURAL := sel_a_b(g_pipeline_bus_mosi, 1, 0);
+  CONSTANT c_bus_pipeline_miso_rdval : NATURAL := sel_a_b(g_pipeline_bus_miso_rdval, 1, 0);
+  CONSTANT c_bus_pipeline_miso_wait  : NATURAL := sel_a_b(g_pipeline_bus_miso_wait, 1, 0);
+  CONSTANT c_ram_rd_latency          : NATURAL := 1;
+  CONSTANT c_ram_rd_latency_arr      : t_nat_natural_arr := array_init(c_ram_rd_latency, g_nof_masters);
+  CONSTANT c_slave_enable_arr        : t_nat_boolean_arr := array_init(TRUE, g_nof_masters);
+  CONSTANT c_waitrequest_arr         : t_nat_boolean_arr := array_init(g_waitrequest, g_nof_masters);
+
+  CONSTANT c_read_latency    : NATURAL := c_bus_pipeline_mosi + c_ram_rd_latency + c_bus_pipeline_miso_rdval;
+
+  CONSTANT c_addr_w          : NATURAL := largest(ceil_log2(largest(g_base_arr)), largest(g_width_arr)) + 1;
+  CONSTANT c_data_w          : NATURAL := 32;
+  CONSTANT c_test_ram        : t_c_mem := (latency  => c_ram_rd_latency,
+                                           adr_w    => c_addr_w,
+                                           dat_w    => c_data_w,
+                                           nof_dat  => 2**c_addr_w,
+                                           init_sl  => '0');
+  SIGNAL mm_rst           : STD_LOGIC;
+  SIGNAL mm_clk           : STD_LOGIC := '1';
+  SIGNAL tb_end           : STD_LOGIC;
+
+  SIGNAL stimuli_mosi     : t_mem_mosi := c_mem_mosi_rst;
+  SIGNAL stimuli_miso     : t_mem_miso := c_mem_miso_rst;
+  SIGNAL master_mosi_arr  : t_mem_mosi_arr(0 TO g_nof_masters-1) := (OTHERS=>c_mem_mosi_rst);
+  SIGNAL master_miso_arr  : t_mem_miso_arr(0 TO g_nof_masters-1) := (OTHERS=>c_mem_miso_rst);
+  SIGNAL mux_mosi         : t_mem_mosi := c_mem_mosi_rst;
+  SIGNAL mux_miso         : t_mem_miso := c_mem_miso_rst;
+  SIGNAL ram_mosi         : t_mem_mosi := c_mem_mosi_rst;
+  SIGNAL ram_miso         : t_mem_miso := c_mem_miso_rst;
+
+BEGIN
+
+  mm_clk <= NOT mm_clk OR tb_end AFTER mm_clk_period/2;
+  mm_rst <= '1', '0' AFTER mm_clk_period*5;
+
+  p_stimuli : PROCESS
+    VARIABLE v_base    : NATURAL;
+    VARIABLE v_span    : NATURAL;
+    VARIABLE v_wrdata  : INTEGER;  -- write data
+    VARIABLE v_rddata  : INTEGER;  -- read data
+    VARIABLE v_expdata : INTEGER;  -- expected data
+  BEGIN
+    tb_end <= '0';
+    stimuli_mosi <= c_mem_mosi_rst;
+
+    -- Wait until reset is released
+    proc_common_wait_until_low(mm_clk, mm_rst);
+    proc_common_wait_some_cycles(mm_clk, 10);
+
+    -- Repeat twice to have wr all, rd all, wr all, rd all
+    v_wrdata := 0;
+    v_expdata := 0;
+    FOR vR IN 0 TO c_repeat-1 LOOP
+      -- Write the whole memory range
+      FOR vI IN 0 TO g_nof_masters-1 LOOP
+        v_base := g_base_arr(vI);
+        v_span := 2**g_width_arr(vI);
+        FOR vJ IN 0 TO v_span-1 LOOP
+          proc_mem_mm_bus_wr(v_base + vJ, v_wrdata, mm_clk, stimuli_miso, stimuli_mosi);
+          v_wrdata := v_wrdata + 1;
+        END LOOP;
+      END LOOP;
+
+      -- Read back the whole range and check if data is as expected
+      FOR vI IN 0 TO g_nof_masters-1 LOOP
+        v_base := g_base_arr(vI);
+        v_span := 2**g_width_arr(vI);
+        FOR vJ IN 0 TO v_span-1 LOOP
+          proc_mem_mm_bus_rd(v_base + vJ, mm_clk, stimuli_miso, stimuli_mosi);
+          proc_common_wait_some_cycles(mm_clk, c_read_latency);
+          v_rddata := TO_UINT(stimuli_miso.rddata(c_data_w-1 DOWNTO 0));
+          IF v_rddata /= v_expdata THEN
+            REPORT "Error! Readvalue is not as expected" SEVERITY ERROR;
+          END IF;
+          v_expdata := v_expdata + 1;
+        END LOOP;
+      END LOOP;
+    END LOOP;
+
+    proc_common_wait_some_cycles(mm_clk, 10);
+    tb_end <= '1';
+    WAIT;
+  END PROCESS;
+
+  -- Model multiple masters using stimuli from a single master
+  u_masters : ENTITY work.mm_bus
+  GENERIC MAP (
+    g_nof_slaves          => g_nof_masters,
+    g_base_arr            => g_base_arr,
+    g_width_arr           => g_width_arr,
+    g_rd_latency_arr      => c_ram_rd_latency_arr,
+    g_slave_enable_arr    => c_slave_enable_arr,
+    g_waitrequest_arr     => c_waitrequest_arr,
+    g_pipeline_mosi       => g_pipeline_bus_mosi,
+    g_pipeline_miso_rdval => g_pipeline_bus_miso_rdval,
+    g_pipeline_miso_wait  => g_pipeline_bus_miso_wait
+  )
+  PORT MAP (
+    mm_clk         => mm_clk,
+    master_mosi    => stimuli_mosi,
+    master_miso    => stimuli_miso,
+    slave_mosi_arr => master_mosi_arr,
+    slave_miso_arr => master_miso_arr
+  );
+
+  -- DUT = device under test
+  u_dut: ENTITY work.mm_master_mux
+  GENERIC MAP (
+    g_nof_masters     => g_nof_masters,
+    g_rd_latency_min  => c_read_latency
+  )
+  PORT MAP (
+    mm_clk          => mm_clk,
+    master_mosi_arr => master_mosi_arr,
+    master_miso_arr => master_miso_arr,
+    mux_mosi        => mux_mosi,
+    mux_miso        => mux_miso
+  );
+
+  -- Model master access to MM bus with multiple slaves using a single RAM
+  u_waitrequest_model : ENTITY work.mm_waitrequest_model
+  GENERIC MAP (
+    g_waitrequest => g_waitrequest
+  )
+  PORT MAP (
+    mm_clk     => mm_clk,
+    bus_mosi   => mux_mosi,
+    bus_miso   => mux_miso,
+    slave_mosi => ram_mosi,
+    slave_miso => ram_miso
+  );
+
+  u_ram : ENTITY common_lib.common_ram_r_w
+  GENERIC MAP (
+    g_ram       => c_test_ram,
+    g_init_file => "UNUSED"
+  )
+  PORT MAP (
+    rst       => mm_rst,
+    clk       => mm_clk,
+    wr_en     => ram_mosi.wr,
+    wr_adr    => ram_mosi.address(c_addr_w-1 DOWNTO 0),
+    wr_dat    => ram_mosi.wrdata(c_data_w-1 DOWNTO 0),
+    rd_en     => ram_mosi.rd,
+    rd_adr    => ram_mosi.address(c_addr_w-1 DOWNTO 0),
+    rd_dat    => ram_miso.rddata(c_data_w-1 DOWNTO 0),
+    rd_val    => ram_miso.rdval
+  );
+
+
+END tb;
diff --git a/libraries/base/mm/tb/vhdl/tb_tb_mm_bus.vhd b/libraries/base/mm/tb/vhdl/tb_tb_mm_bus.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..e399076bb1f973284b4ed38e5a7722a0dbca39e8
--- /dev/null
+++ b/libraries/base/mm/tb/vhdl/tb_tb_mm_bus.vhd
@@ -0,0 +1,67 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+-- 
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+-- 
+-- Author: E. Kooistra
+-- Purpose: Multi test bench for mm_bus.vhd
+--
+-------------------------------------------------------------------------------
+
+LIBRARY IEEE, common_lib;
+USE IEEE.std_logic_1164.ALL;
+USE common_lib.common_pkg.ALL;
+
+ENTITY tb_tb_mm_bus IS
+END tb_tb_mm_bus;
+
+ARCHITECTURE tb OF tb_tb_mm_bus IS
+  SIGNAL tb_end : STD_LOGIC := '0';  -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end'
+BEGIN
+  -- Usage:
+  -- > as 4
+  -- > run -all
+
+  -- g_nof_slaves          : POSITIVE := 2;       -- Number of slave memory interfaces on the MM bus array.
+  -- g_base_offset         : NATURAL := 0;        -- Address of first slave on the MM bus
+  -- g_width_w             : POSITIVE := 4;       -- Address width of each slave memory in the MM bus array.
+  -- g_rd_latency          : NATURAL := 1;        -- Read latency of the slaves slave
+  -- g_waitrequest         : BOOLEAN := FALSE;    -- When TRUE model waitrequest by MM slaves, else fixed '0'
+  -- g_pipeline_mosi       : BOOLEAN := FALSE;
+  -- g_pipeline_miso_rdval : BOOLEAN := TRUE;
+  -- g_pipeline_miso_wait  : BOOLEAN := FALSE
+    
+  u_no_pipe                          : ENTITY work.tb_mm_bus GENERIC MAP (16,      0, 3, 1, FALSE, FALSE, FALSE, FALSE);
+  u_no_pipe_base_offset              : ENTITY work.tb_mm_bus GENERIC MAP (16, 3*2**4, 4, 1, FALSE, FALSE, FALSE, FALSE);
+  u_pipe_mosi                        : ENTITY work.tb_mm_bus GENERIC MAP ( 3,      0, 4, 1, FALSE,  TRUE, FALSE, FALSE);
+  u_pipe_mosi_miso_rdval             : ENTITY work.tb_mm_bus GENERIC MAP ( 3,      0, 4, 1, FALSE,  TRUE,  TRUE, FALSE);
+  u_waitrequest_no_pipe              : ENTITY work.tb_mm_bus GENERIC MAP ( 3,      0, 4, 1,  TRUE, FALSE, FALSE, FALSE);
+  u_waitrequest_pipe_miso_rdval      : ENTITY work.tb_mm_bus GENERIC MAP ( 3,      0, 4, 1,  TRUE, FALSE,  TRUE, FALSE);
+  u_waitrequest_pipe_miso_rdval2     : ENTITY work.tb_mm_bus GENERIC MAP ( 3,      0, 4, 2,  TRUE, FALSE,  TRUE, FALSE);  
+  u_waitrequest_pipe_miso_wait       : ENTITY work.tb_mm_bus GENERIC MAP ( 2,      0, 4, 1,  TRUE, FALSE, FALSE,  TRUE);
+  u_waitrequest_pipe_mosi_one        : ENTITY work.tb_mm_bus GENERIC MAP ( 1,      0, 4, 1,  TRUE,  TRUE, FALSE, FALSE);
+  u_waitrequest_pipe_mosi            : ENTITY work.tb_mm_bus GENERIC MAP ( 2,      0, 4, 1,  TRUE,  TRUE, FALSE, FALSE);
+  u_waitrequest_pipe_mosi_miso_rdval : ENTITY work.tb_mm_bus GENERIC MAP ( 2,      0, 4, 1,  TRUE,  TRUE,  TRUE, FALSE);
+
+  -- Do not support simultaneous g_pipeline_mosi = TRUE and g_pipeline_miso_wait = TRUE, see mm_bus_pipe.vhd.
+  --u_waitrequest_pipe_mosi_miso_wait  : ENTITY work.tb_mm_bus GENERIC MAP ( 2,      0, 4, 1,  TRUE,  TRUE, FALSE,  TRUE);
+  --u_waitrequest_pipe_all             : ENTITY work.tb_mm_bus GENERIC MAP ( 2,      0, 4, 1,  TRUE,  TRUE,  TRUE,  TRUE);
+  
+END tb;
diff --git a/libraries/base/mm/tb/vhdl/tb_tb_mm_master_mux.vhd b/libraries/base/mm/tb/vhdl/tb_tb_mm_master_mux.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..978b06ac803a73275570f583d334638ed0878a88
--- /dev/null
+++ b/libraries/base/mm/tb/vhdl/tb_tb_mm_master_mux.vhd
@@ -0,0 +1,61 @@
+-------------------------------------------------------------------------------
+--
+-- Copyright 2020
+-- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
+-- P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+-- 
+-- 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.
+--
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+-- 
+-- Author: E. Kooistra
+-- Purpose: Multi test bench for mm_master_mux.vhd
+--
+-------------------------------------------------------------------------------
+
+LIBRARY IEEE, common_lib;
+USE IEEE.std_logic_1164.ALL;
+USE common_lib.common_pkg.ALL;
+
+ENTITY tb_tb_mm_master_mux IS
+END tb_tb_mm_master_mux;
+
+ARCHITECTURE tb OF tb_tb_mm_master_mux IS
+  SIGNAL tb_end : STD_LOGIC := '0';  -- declare tb_end to avoid 'No objects found' error on 'when -label tb_end'
+BEGIN
+  -- Usage:
+  -- > as 4
+  -- > run -all
+
+  -- g_nof_masters             : POSITIVE := 2;   -- Number of master memory interfaces on the MM bus array.
+  -- g_base_arr                : t_nat_natural_arr := (0, 256);  -- Address base per slave port of mm_bus
+  -- g_width_arr               : t_nat_natural_arr := (4,   8);  -- Address width per slave port of mm_bus
+  -- g_waitrequest             : BOOLEAN := FALSE;    -- When TRUE model waitrequest by the MM RAM slave, else fixed '0'
+  -- g_pipeline_bus_mosi       : BOOLEAN := FALSE;
+  -- g_pipeline_bus_miso_rdval : BOOLEAN := FALSE;
+  -- g_pipeline_bus_miso_wait  : BOOLEAN := FALSE    
+    
+  u_no_pipe                          : ENTITY work.tb_mm_master_mux GENERIC MAP (2, (0, 256), (4,   8), FALSE, FALSE, FALSE, FALSE);
+  u_pipe_mosi                        : ENTITY work.tb_mm_master_mux GENERIC MAP (2, (0, 256), (4,   8), FALSE,  TRUE, FALSE, FALSE);
+  u_pipe_miso_rdval                  : ENTITY work.tb_mm_master_mux GENERIC MAP (2, (0, 256), (4,   8), FALSE, FALSE,  TRUE, FALSE);
+  u_waitrequest_no_pipe              : ENTITY work.tb_mm_master_mux GENERIC MAP (2, (0, 256), (4,   8),  TRUE, FALSE, FALSE, FALSE);
+  u_waitrequest_pipe_miso_rdval      : ENTITY work.tb_mm_master_mux GENERIC MAP (2, (0, 256), (4,   8),  TRUE, FALSE,  TRUE, FALSE);
+  u_waitrequest_pipe_mosi            : ENTITY work.tb_mm_master_mux GENERIC MAP (2, (0, 256), (4,   8),  TRUE,  TRUE, FALSE, FALSE);
+  u_waitrequest_pipe_mosi_miso_rdval : ENTITY work.tb_mm_master_mux GENERIC MAP (2, (0, 256), (4,   8),  TRUE,  TRUE,  TRUE, FALSE);
+
+  -- Do not support simultaneous g_pipeline_mosi = TRUE and g_pipeline_miso_wait = TRUE, see mm_bus_pipe.vhd.
+  --u_waitrequest_pipe_all             : ENTITY work.tb_mm_master_mux GENERIC MAP (2, (0, 256), (4,   8),  TRUE,  TRUE,  TRUE, TRUE);
+  
+END tb;