From 6cedf793d20a2a85e2fbe4b11a02aaa99624d97c Mon Sep 17 00:00:00 2001 From: Eric Kooistra <kooistra@astron.nl> Date: Fri, 10 Jan 2020 11:27:29 +0100 Subject: [PATCH] Added common_mem_bus.vhd with test bench. This implements a MM bus that can be used to replace the MM bus that we make with Qsys. --- libraries/base/common/hdllib.cfg | 4 + .../base/common/src/vhdl/common_mem_bus.vhd | 228 ++++++++++++++++++ .../base/common/tb/vhdl/tb_common_mem_bus.vhd | 175 ++++++++++++++ .../common/tb/vhdl/tb_tb_common_mem_bus.vhd | 54 +++++ 4 files changed, 461 insertions(+) create mode 100644 libraries/base/common/src/vhdl/common_mem_bus.vhd create mode 100644 libraries/base/common/tb/vhdl/tb_common_mem_bus.vhd create mode 100644 libraries/base/common/tb/vhdl/tb_tb_common_mem_bus.vhd diff --git a/libraries/base/common/hdllib.cfg b/libraries/base/common/hdllib.cfg index 209919bdf6..7b6228f14d 100644 --- a/libraries/base/common/hdllib.cfg +++ b/libraries/base/common/hdllib.cfg @@ -109,6 +109,7 @@ 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_bus.vhd src/vhdl/common_mem_mux.vhd src/vhdl/common_mem_demux.vhd src/vhdl/common_reg_cross_domain.vhd @@ -162,6 +163,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_bus.vhd tb/vhdl/tb_common_mem_mux.vhd tb/vhdl/tb_common_multiplexer.vhd tb/vhdl/tb_common_operation_tree.vhd @@ -192,6 +194,7 @@ 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 @@ -218,6 +221,7 @@ 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_mem_bus.vhd b/libraries/base/common/src/vhdl/common_mem_bus.vhd new file mode 100644 index 0000000000..f0c78aaf7b --- /dev/null +++ b/libraries/base/common/src/vhdl/common_mem_bus.vhd @@ -0,0 +1,228 @@ +------------------------------------------------------------------------------- +-- +-- 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, 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. +-- +-- * 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. +-- +-- ______________ +-- strip index: | | +-- master_mosi.address[h:w] ---+-->| delay line |--\ +-- | |____________| | +-- | | +-- selected v | +-- master_mosi --> slave_mosi_arr.wr[ ]----------------------> slave_mosi_arr +-- rd | +-- selected v +-- master_miso <--------------------------slave_miso_arr[ ]<-- slave_miso_arr +-- +-- . not selected slave_mosi_arr get master_mosi but with wr='0', rd='0' +-- . not selected slave_miso_arr are ignored +-- +-- 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_natural_arr; -- Address base per slave + g_width_arr : t_natural_arr; -- Address width per slave + g_rd_latency_arr : t_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_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_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; + + SIGNAL x : NATURAL; + SIGNAL y : NATURAL; + SIGNAL z : NATURAL; +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; + x <= c_mm_bus_addr_w; + y <= v_base; + 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 + 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) <= master_mosi; + 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; + 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; diff --git a/libraries/base/common/tb/vhdl/tb_common_mem_bus.vhd b/libraries/base/common/tb/vhdl/tb_common_mem_bus.vhd new file mode 100644 index 0000000000..eb3531a375 --- /dev/null +++ b/libraries/base/common/tb/vhdl/tb_common_mem_bus.vhd @@ -0,0 +1,175 @@ +------------------------------------------------------------------------------- +-- +-- 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. This +-- test bench is derived from tb_common_mem_mux.vhd and therefore it uses an +-- array of fixed width slaves. For now 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. +------------------------------------------------------------------------------- + +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_natural_arr := array_init(g_base_offset, g_nof_slaves, c_slave_span); -- Address base per slave + CONSTANT c_width_arr : t_natural_arr := array_init( g_width_w, g_nof_slaves); -- Address width per slave + CONSTANT c_rd_latency_arr : t_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_natural_arr(0 TO g_nof_slaves-1) := c_base_arr; + SIGNAL dbg_c_width_arr : t_natural_arr(0 TO g_nof_slaves-1) := c_width_arr; + SIGNAL dbg_c_rd_latency_arr : t_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 temp : 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); + temp := TO_UINT(miso.rddata(31 DOWNTO 0)); + IF temp /= 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_tb_common_mem_bus.vhd b/libraries/base/common/tb/vhdl/tb_tb_common_mem_bus.vhd new file mode 100644 index 0000000000..2a98bc2190 --- /dev/null +++ b/libraries/base/common/tb/vhdl/tb_tb_common_mem_bus.vhd @@ -0,0 +1,54 @@ +------------------------------------------------------------------------------- +-- +-- 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; -- GitLab