diff --git a/libraries/base/mm/src/vhdl/mm_bus_comb.vhd b/libraries/base/mm/src/vhdl/mm_bus_comb.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..8befe54e9cfb695294b7db6851dc1c6d4e1aa2d3
--- /dev/null
+++ b/libraries/base/mm/src/vhdl/mm_bus_comb.vhd
@@ -0,0 +1,209 @@
+-------------------------------------------------------------------------------
+--
+-- 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 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 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.
+--     
+-- 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.
+-- . 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 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;
+        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..8648f4b5f68b32dca86223006dcfea4fd732f847
--- /dev/null
+++ b/libraries/base/mm/src/vhdl/mm_bus_pipe.vhd
@@ -0,0 +1,182 @@
+-------------------------------------------------------------------------------
+--
+-- 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 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.
+--     When g_pipeline_miso_wait = TRUE then it is not necessary to use 
+--     g_pipeline_mosi = TRUE, because the MM latency adapter that recovers
+--     the timing of the waitrequest also pipelines the mosi.
+--
+--   * 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 total write latency from master to slave is c_pipeline_mosi.
+--   The total read latency from master via slave back to master is
+--   c_pipeline_mosi + g_rd_latency_arr of the selected slave + 
+--   c_pipeline_miso_rdval. 
+--     
+-- Usage:
+--   See 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_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
+
+  -- 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,
+      -- MM input RL = 1
+      in_mosi       => bus_mosi_arr(I),
+      in_miso       => bus_miso_arr(I),
+      -- MM output RL = 0
+      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_pipeline.vhd b/libraries/base/mm/src/vhdl/mm_pipeline.vhd
new file mode 100644
index 0000000000000000000000000000000000000000..09f3cda1a1c471c9d535d561bf7338b9c2a25111
--- /dev/null
+++ b/libraries/base/mm/src/vhdl/mm_pipeline.vhd
@@ -0,0 +1,82 @@
+-------------------------------------------------------------------------------
+--
+-- 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.
+-- 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. 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.
+
+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;
+    -- 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_pipeline;
+
+
+ARCHITECTURE rtl OF mm_pipeline IS
+
+  SIGNAL in_mosi_reg  : t_mem_mosi := c_mem_mosi_rst;
+
+BEGIN
+
+  -- Pass on miso
+  in_miso <= out_miso;
+
+  -- Optionally pipeline the mosi
+  --in_mosi_reg <= in_mosi WHEN rising_edge(mm_clk);  -- without mm_rst
+  p_reg : PROCESS(mm_rst, mm_clk)  -- with mm_rst
+  BEGIN
+    IF mm_rst = '1' THEN
+      in_mosi_reg <= c_mem_mosi_rst;
+    ELSIF rising_edge(mm_clk) THEN
+      in_mosi_reg <= in_mosi;
+    END IF;
+  END PROCESS;
+
+  out_mosi <= in_mosi_reg WHEN g_pipeline = TRUE ELSE in_mosi;
+
+END rtl;