diff --git a/libraries/base/common/hdllib.cfg b/libraries/base/common/hdllib.cfg index f62ff6389abc265b1fc5b55173359a581f483e52..d5624d00cb139209e894f47c91dbaab9f3d4f275 100644 --- a/libraries/base/common/hdllib.cfg +++ b/libraries/base/common/hdllib.cfg @@ -164,6 +164,7 @@ 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 @@ -208,6 +209,7 @@ 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 diff --git a/libraries/base/common/src/vhdl/common_mem_master_mux.vhd b/libraries/base/common/src/vhdl/common_mem_master_mux.vhd index a896efd8a1b3d430377d320045bee001f6848f08..82e5fff65c07f3f870fe92a2869ff3ae607e4517 100644 --- a/libraries/base/common/src/vhdl/common_mem_master_mux.vhd +++ b/libraries/base/common/src/vhdl/common_mem_master_mux.vhd @@ -24,16 +24,20 @@ -- Purpose: Multiplex an array of MM master interfaces to a single MM master -- interface -- Description: --- This common_mem_master_mux is a simple multiplexer and does not provide --- arbitration between the masters in the array. Therefore the precondition --- is that the application takes care that the MM accesses of the multiple --- masters in the array do not overlap. --- For read accesses the g_rd_latency defines how much idle time there needs --- to be after the last read access by one master and a next MM access by --- another master. +-- 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 time the g_rd_latency of the --- external mux_miso, to ensure that the read data.is passed on to the +-- 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. @@ -49,7 +53,7 @@ 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 : NATURAL -- Maximum read latency + g_rd_latency_min : NATURAL -- Minimum read latency ); PORT ( mm_clk : IN STD_LOGIC; @@ -62,8 +66,8 @@ END common_mem_master_mux; ARCHITECTURE rtl OF common_mem_master_mux IS - SIGNAL index_reg : NATURAL := 0; - SIGNAL index_pipeline : t_natural_arr(0 TO g_rd_latency) := (OTHERS=>0); + SIGNAL index : NATURAL := 0; + SIGNAL index_hold : NATURAL := 0; BEGIN @@ -75,41 +79,50 @@ BEGIN gen_multiple : IF g_nof_masters>1 GENERATE -- Detect which master in the array is active - p_index : PROCESS(master_mosi_arr, index_reg) + -- 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 - -- default hold last active index instead of reset to 0 to ease observing index in wave window - index_pipeline(0) <= index_reg; + 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_pipeline(0) <= I; -- index of active master + index <= I; -- index of active master EXIT; END IF; END LOOP; END PROCESS; - index_reg <= index_pipeline(0) WHEN rising_edge(mm_clk); -- hold index - index_pipeline(1 TO g_rd_latency) <= index_pipeline(0 TO g_rd_latency-1) WHEN rising_edge(mm_clk); + index_hold <= index WHEN rising_edge(mm_clk); -- hold index of last active master - -- Master access, can be write or read - p_mosi : PROCESS(master_mosi_arr) - BEGIN - mux_mosi <= c_mem_mosi_rst; -- default clear, to avoid latches - FOR I IN 0 TO g_nof_masters-1 LOOP - IF I = index_pipeline(0) THEN -- pass on selected master - mux_mosi <= master_mosi_arr(I); - END IF; - END LOOP; - END PROCESS; + -- Multiplex master access, can be write or read + mux_mosi <= master_mosi_arr(index); - -- Slave response to read access after g_rd_latency mm_clk cycles - p_miso : PROCESS(mux_miso, index_pipeline) + -- 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 I = index_pipeline(g_rd_latency) THEN -- check index for read response - master_miso_arr(I).rdval <= mux_miso.rdval; + -- 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; 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 new file mode 100644 index 0000000000000000000000000000000000000000..01ae0fb1529c6fcee7ffc3fae6882183b5143b60 --- /dev/null +++ b/libraries/base/common/tb/vhdl/tb_common_mem_master_mux.vhd @@ -0,0 +1,190 @@ +------------------------------------------------------------------------------- +-- +-- 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 + ); +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_rd_latency : NATURAL := 1; + CONSTANT c_rd_latency_arr : t_nat_natural_arr := array_init(c_rd_latency, g_nof_masters); + CONSTANT c_rd_latency_min : NATURAL := smallest(c_rd_latency_arr); + 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_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_rd_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_rd_latency_arr, + g_pipeline_mosi => FALSE, + g_pipeline_miso => FALSE + ) + 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_rd_latency_min + ) + 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;