-------------------------------------------------------------------------------- -- -- Copyright (C) 2014 -- ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/> -- JIVE (Joint Institute for VLBI in Europe) <http://www.jive.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/>. -- -------------------------------------------------------------------------------- -- This testbench tests the different type of DDR controllers. -- -- The DUT can be selected, using the g_technology and g_tech_ddr constants. -- -- Testbench is selftesting: -- -- > as 10 -- > run -all -- LIBRARY IEEE, technology_lib, tech_ddr_lib, common_lib, dp_lib, diagnostics_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; USE dp_lib.dp_stream_pkg.ALL; USE technology_lib.technology_pkg.ALL; USE technology_lib.technology_select_pkg.ALL; USE tech_ddr_lib.tech_ddr_pkg.ALL; ENTITY tb_io_ddr IS GENERIC ( g_technology : NATURAL := c_tech_select_default; g_tech_ddr3 : t_c_tech_ddr := c_tech_ddr3_4g_800m_master; g_tech_ddr4 : t_c_tech_ddr := c_tech_ddr4_4g_1600m; g_tb_end : BOOLEAN := TRUE; -- when TRUE then tb_end ends this simulation, else a higher multi-testbench will end the simulation g_use_ddr_memory_model : BOOLEAN := FALSE; -- when TRUE use the internal DDR memory model, else use the DDR model in this tb. g_cross_domain_dvr_ctlr : BOOLEAN := FALSE; -- when TRUE insert clock cross domain logic g_ctlr_ref_clk_period : TIME := 5000 ps; -- 200 MHz g_dvr_clk_period : TIME := 5000 ps; -- 50 MHz g_dp_clk_period : TIME := 5000 ps; -- 200 MHz g_mm_clk_period : TIME := 8000 ps; -- 125 MHz g_dp_factor : NATURAL := 4; -- 1 or power of 2, c_dp_data_w = c_ctlr_data_w / g_dp_factor g_rd_fifo_depth : NATURAL := 512; -- default 256 because 32b*256 fits in 1 M9K, use larger to fit more read bursts eg. in case g_dp_factor>1 g_block_len : NATURAL := 2500; -- block length for a DDR write access and read back access in number of c_ctlr_data_w words g_nof_block : NATURAL := 2; -- number of blocks that will be written to DDR and readback from DDR g_nof_wr_per_block : NATURAL := 1; -- number of write accesses per block g_nof_rd_per_block : NATURAL := 1; -- number of read accesses per block g_nof_repeat : NATURAL := 1; -- number of stimuli repeats with write flush after each repeat g_wr_flush_mode : STRING := "VAL" -- "VAL", "SOP", "SYN" ); PORT ( tb_end : OUT STD_LOGIC ); END ENTITY tb_io_ddr; ARCHITECTURE str of tb_io_ddr IS -- Select DDR3 or DDR4 dependent on the technology CONSTANT c_tech_ddr : t_c_tech_ddr := func_tech_sel_ddr(g_technology, g_tech_ddr3, g_tech_ddr4); CONSTANT c_cross_domain_dvr_ctlr : BOOLEAN := g_cross_domain_dvr_ctlr OR g_ctlr_ref_clk_period/=g_dvr_clk_period; CONSTANT c_ctlr_address_w : NATURAL := func_tech_ddr_ctlr_address_w(c_tech_ddr); CONSTANT c_ctlr_data_w : NATURAL := func_tech_ddr_ctlr_data_w(c_tech_ddr); CONSTANT c_dp_data_w : NATURAL := c_ctlr_data_w/g_dp_factor; CONSTANT c_queue_nof_rd : NATURAL := sel_a_b(c_tech_ddr.name="DDR3", 1, 3); -- derived empirically from simulation, seems to match (c_tech_ddr.command_queue_depth-1)/2 CONSTANT c_wr_fifo_depth : NATURAL := 256; CONSTANT c_rd_fifo_depth : NATURAL := g_rd_fifo_depth; CONSTANT c_rd_fifo_af_margin : NATURAL := 4 + c_queue_nof_rd*c_tech_ddr.maxburstsize; -- sufficient to fit one or more rd burst accesses of g_tech_ddr.maxburstsize each -- Frame size for sop/eop CONSTANT c_wr_frame_size : NATURAL := 32; -- Sync period CONSTANT c_wr_sync_period : NATURAL := 512; -- Typical DDR access stimuli -- . write block of words in 1 write access and then readback in 4 block read accesses -- . use appropriate c_len to access across a DDR address column (a_col_w=10) CONSTANT c_nof_access_per_block : NATURAL := g_nof_wr_per_block + g_nof_rd_per_block; CONSTANT c_nof_access : NATURAL := g_nof_block * c_nof_access_per_block; FUNCTION func_ctlr_address_lo_arr RETURN t_nat_natural_arr IS CONSTANT c_wr : NATURAL := g_block_len/g_nof_wr_per_block; CONSTANT c_rd : NATURAL := g_block_len/g_nof_rd_per_block; VARIABLE v_arr : t_nat_natural_arr(0 TO c_nof_access-1); BEGIN FOR R IN 0 TO g_nof_block-1 LOOP -- Write block in g_nof_wr_per_block accesses FOR I IN 0 TO g_nof_wr_per_block-1 LOOP v_arr(R*c_nof_access_per_block+I) := R*g_block_len+I*c_wr; END LOOP; -- Read back block in g_nof_rd_per_block accesses FOR I IN 0 TO g_nof_rd_per_block-1 LOOP v_arr(R*c_nof_access_per_block+g_nof_wr_per_block+I) := R*g_block_len+I*c_rd; END LOOP; END LOOP; RETURN v_arr; END; FUNCTION func_ctlr_nof_address_arr RETURN t_nat_natural_arr IS CONSTANT c_wr : NATURAL := g_block_len/g_nof_wr_per_block; CONSTANT c_rd : NATURAL := g_block_len/g_nof_rd_per_block; CONSTANT c_wr_last : NATURAL := g_block_len - c_wr*(g_nof_wr_per_block-1); CONSTANT c_rd_last : NATURAL := g_block_len - c_rd*(g_nof_rd_per_block-1); VARIABLE v_arr : t_nat_natural_arr(0 TO c_nof_access-1); BEGIN FOR R IN 0 TO g_nof_block-1 LOOP -- Write block in g_nof_wr_per_block accesses FOR I IN 0 TO g_nof_wr_per_block-1 LOOP v_arr(R*c_nof_access_per_block+I) := c_wr; END LOOP; v_arr(R*c_nof_access_per_block+g_nof_wr_per_block-1) := c_wr_last; -- Read back block in g_nof_rd_per_block accesses FOR I IN 0 TO g_nof_rd_per_block-1 LOOP v_arr(R*c_nof_access_per_block+g_nof_wr_per_block+I) := c_rd; END LOOP; v_arr(R*c_nof_access_per_block+g_nof_wr_per_block+g_nof_rd_per_block-1) := c_rd_last; END LOOP; RETURN v_arr; END; FUNCTION func_ctlr_wr_not_rd_arr RETURN STD_LOGIC_VECTOR IS VARIABLE v_arr : STD_LOGIC_VECTOR(0 TO c_nof_access-1); BEGIN FOR R IN 0 TO g_nof_block-1 LOOP -- Write block in g_nof_wr_per_block accesses FOR I IN 0 TO g_nof_wr_per_block-1 LOOP v_arr(R*c_nof_access_per_block+I) := '1'; END LOOP; -- Read back block in g_nof_rd_per_block accesses FOR I IN 0 TO g_nof_rd_per_block-1 LOOP v_arr(R*c_nof_access_per_block+g_nof_wr_per_block+I) := '0'; END LOOP; END LOOP; RETURN v_arr; END; CONSTANT c_ctlr_address_lo_arr : t_nat_natural_arr(0 TO c_nof_access-1) := func_ctlr_address_lo_arr; CONSTANT c_ctlr_nof_address_arr : t_nat_natural_arr(0 TO c_nof_access-1) := func_ctlr_nof_address_arr; CONSTANT c_ctlr_wr_not_rd_arr : STD_LOGIC_VECTOR(0 TO c_nof_access-1) := func_ctlr_wr_not_rd_arr; SIGNAL dbg_c_ctlr_address_lo_arr : t_nat_natural_arr(0 TO c_nof_access-1) := c_ctlr_address_lo_arr; SIGNAL dbg_c_ctlr_nof_address_arr : t_nat_natural_arr(0 TO c_nof_access-1) := c_ctlr_nof_address_arr; SIGNAL dbg_c_ctlr_wr_not_rd_arr : STD_LOGIC_VECTOR(0 TO c_nof_access-1) := c_ctlr_wr_not_rd_arr; SIGNAL dbg_c_tech_ddr : t_c_tech_ddr := c_tech_ddr; SIGNAL dbg_c_dp_data_w : NATURAL := c_dp_data_w; SIGNAL dbg_c_wr_fifo_depth : NATURAL := c_wr_fifo_depth; SIGNAL dbg_c_rd_fifo_depth : NATURAL := c_rd_fifo_depth; SIGNAL dbg_c_rd_fifo_af_margin : NATURAL := c_rd_fifo_af_margin; SIGNAL i_tb_end : STD_LOGIC := '0'; SIGNAL ctlr_ref_clk : STD_LOGIC := '0'; SIGNAL ctlr_ref_rst : STD_LOGIC; SIGNAL ctlr_clk : STD_LOGIC; SIGNAL ctlr_rst : STD_LOGIC; SIGNAL dvr_clk : STD_LOGIC := '0'; SIGNAL dvr_rst : STD_LOGIC; SIGNAL dp_clk : STD_LOGIC := '0'; SIGNAL dp_rst : STD_LOGIC; SIGNAL mm_clk : STD_LOGIC := '0'; SIGNAL mm_rst : STD_LOGIC; SIGNAL dvr_miso : t_mem_ctlr_miso; SIGNAL dvr_mosi : t_mem_ctlr_mosi; SIGNAL reg_io_ddr_mosi : t_mem_mosi := c_mem_mosi_rst; SIGNAL reg_io_ddr_miso : t_mem_miso := c_mem_miso_rst; SIGNAL dvr_done : STD_LOGIC; SIGNAL dvr_en : STD_LOGIC; SIGNAL dvr_wr_not_rd : STD_LOGIC; SIGNAL dvr_start_address : STD_LOGIC_VECTOR(c_ctlr_address_w-1 DOWNTO 0); SIGNAL dvr_nof_data : STD_LOGIC_VECTOR(c_ctlr_address_w-1 DOWNTO 0); SIGNAL dvr_wr_flush_en : STD_LOGIC; SIGNAL diag_wr_src_in : t_dp_siso; SIGNAL diag_wr_src_out : t_dp_sosi; SIGNAL wr_fifo_usedw : STD_LOGIC_VECTOR(ceil_log2(c_wr_fifo_depth * g_dp_factor)-1 DOWNTO 0); SIGNAL wr_src_out : t_dp_sosi; SIGNAL wr_val_cnt : NATURAL := 0; SIGNAL diag_rd_snk_out : t_dp_siso; SIGNAL diag_rd_snk_in : t_dp_sosi; SIGNAL rd_fifo_usedw : STD_LOGIC_VECTOR(ceil_log2(c_rd_fifo_depth * g_dp_factor)-1 DOWNTO 0); SIGNAL dbg_wr_data : STD_LOGIC_VECTOR(c_dp_data_w-1 DOWNTO 0); SIGNAL dbg_wr_val : STD_LOGIC; SIGNAL dbg_rd_data : STD_LOGIC_VECTOR(c_dp_data_w-1 DOWNTO 0); SIGNAL dbg_rd_val : STD_LOGIC; SIGNAL src_diag_en : STD_LOGIC; SIGNAL src_val_cnt : STD_LOGIC_VECTOR(31 DOWNTO 0); SIGNAL snk_diag_en : STD_LOGIC; SIGNAL snk_diag_res : STD_LOGIC; SIGNAL snk_diag_res_val : STD_LOGIC; SIGNAL snk_val_cnt : STD_LOGIC_VECTOR(31 DOWNTO 0); SIGNAL expected_cnt : NATURAL; -- PHY interface SIGNAL phy_in : t_tech_ddr_phy_in; SIGNAL phy_io : t_tech_ddr_phy_io; SIGNAL phy_ou : t_tech_ddr_phy_ou; BEGIN ctlr_ref_clk <= NOT ctlr_ref_clk OR i_tb_end AFTER g_ctlr_ref_clk_period/2; dvr_clk <= NOT dvr_clk OR i_tb_end AFTER g_dvr_clk_period/2; dvr_rst <= '1', '0' AFTER 100 ns; dp_clk <= NOT dp_clk OR i_tb_end AFTER g_dp_clk_period/2; dp_rst <= '1', '0' AFTER 100 ns; mm_clk <= NOT mm_clk OR i_tb_end AFTER g_mm_clk_period/2; mm_rst <= '1', '0' AFTER 100 ns; tb_end <= i_tb_end; p_stimuli : PROCESS BEGIN i_tb_end <= '0'; dvr_en <= '0'; dvr_wr_flush_en <= '0'; dvr_wr_not_rd <= '0'; dvr_start_address <= (OTHERS=>'0'); dvr_nof_data <= (OTHERS=>'0'); src_diag_en <= '0'; snk_diag_en <= '0'; expected_cnt <= 0; ctlr_ref_rst <= '1'; WAIT FOR 100 ns; ctlr_ref_rst <= '0'; proc_common_wait_until_high(dvr_clk, dvr_done); -- Start diagnostics source for write and sink for verify read proc_common_wait_some_cycles(dp_clk, 1); src_diag_en <= '1'; snk_diag_en <= '1'; -- After reset the write FIFO is flushed until the first write access is started, even when dvr_wr_flush_en='0' proc_common_wait_some_cycles(ctlr_clk, 1000); FOR R IN 0 TO g_nof_repeat-1 LOOP proc_common_wait_some_cycles(dvr_clk, 1); FOR I IN c_ctlr_address_lo_arr'RANGE LOOP dvr_start_address <= TO_UVEC(c_ctlr_address_lo_arr(I) , c_ctlr_address_w); dvr_nof_data <= TO_UVEC(c_ctlr_nof_address_arr(I), c_ctlr_address_w); -- START ACCESS dvr_wr_not_rd <= c_ctlr_wr_not_rd_arr(I); dvr_en <= '1'; proc_common_wait_some_cycles(dvr_clk, 1); dvr_en <= '0'; -- ACCESS DONE proc_common_wait_until_lo_hi(dvr_clk, dvr_done); IF c_ctlr_wr_not_rd_arr(I)='0' THEN expected_cnt <= expected_cnt + c_ctlr_nof_address_arr(I)*g_dp_factor; END IF; END LOOP; -- Stop diagnostics source proc_common_wait_some_cycles(dp_clk, 1); src_diag_en <= '0'; -- Flush the wr fifo proc_common_wait_some_cycles(dvr_clk, 1); dvr_wr_flush_en <= '1'; proc_common_wait_some_cycles(dvr_clk, 1); dvr_wr_flush_en <= '0'; -- Wait until the wr fifo has been flushed and the rd fifo has been read empty proc_common_wait_some_cycles(ctlr_clk, c_tech_ddr.command_queue_depth*c_tech_ddr.maxburstsize); -- rd FIFO may still get filled some more proc_common_wait_some_cycles(ctlr_clk, largest(TO_UINT(wr_fifo_usedw)/g_dp_factor, TO_UINT(rd_fifo_usedw))); proc_common_wait_some_cycles(ctlr_clk, 10); -- some extra margin ASSERT UNSIGNED(wr_fifo_usedw) < g_dp_factor REPORT "[ERROR] Write FIFO is flushed but not empty!" SEVERITY FAILURE; ASSERT UNSIGNED(rd_fifo_usedw) = 0 REPORT "[ERROR] Read FIFO is not empty!" SEVERITY FAILURE; ASSERT UNSIGNED(snk_val_cnt) = expected_cnt REPORT "[ERROR] Unexpected number of read data!" SEVERITY FAILURE; -- Check diagnostics sink after the rd fifo has been read empty proc_common_wait_some_cycles(dp_clk, 1); ASSERT snk_diag_res_val = '1' REPORT "[ERROR] DIAG_RES INVALID!" SEVERITY FAILURE; ASSERT snk_diag_res = '0' REPORT "[ERROR] NON-ZERO DIAG_RES!" SEVERITY FAILURE; -- Stop diagnostics sink snk_diag_en <= '0'; -- Restart diagnostics source and sink proc_common_wait_some_cycles(dp_clk, 1); src_diag_en <= '1'; snk_diag_en <= '1'; END LOOP; -- If the test failed then it would have stopped already, so it the test has passed REPORT "[OK] Test passed." SEVERITY NOTE; -- Stop the simulation -- . Stopping the clocks via tb_end does end the tb for the DDR3 IP, but is not sufficient to stop the tb for the DDR4 IP. -- . Making ctlr_ref_rst <= '1'; also does not stop the tb with the DDR4 IP (apparently some loop remains running in the DDR4 model), so therefore force simulation stop i_tb_end <= '1'; ctlr_ref_rst <= '1'; IF g_tb_end=FALSE THEN REPORT "Tb Simulation finished." SEVERITY NOTE; ELSE REPORT "Tb Simulation finished." SEVERITY FAILURE; END IF; WAIT; END PROCESS; u_diagnostics: ENTITY diagnostics_lib.diagnostics GENERIC MAP ( g_dat_w => c_dp_data_w, g_nof_streams => 1 ) PORT MAP ( rst => dp_rst, clk => dp_clk, snk_out_arr(0) => diag_rd_snk_out, snk_in_arr(0) => diag_rd_snk_in, snk_diag_en(0) => snk_diag_en, snk_diag_md(0) => '1', snk_diag_res(0) => snk_diag_res, snk_diag_res_val(0) => snk_diag_res_val, snk_val_cnt(0) => snk_val_cnt, src_out_arr(0) => diag_wr_src_out, src_in_arr(0) => diag_wr_src_in, src_diag_en(0) => src_diag_en, src_diag_md(0) => '1', src_val_cnt(0) => src_val_cnt ); dbg_wr_data <= diag_wr_src_out.data(c_dp_data_w-1 DOWNTO 0); dbg_wr_val <= diag_wr_src_out.valid; dbg_rd_data <= diag_rd_snk_in.data(c_dp_data_w-1 DOWNTO 0); dbg_rd_val <= diag_rd_snk_in.valid; wr_val_cnt <= wr_val_cnt + 1 WHEN rising_edge(dp_clk) AND diag_wr_src_out.valid='1'; p_sop_eop : PROCESS (diag_wr_src_out, wr_val_cnt) BEGIN -- Default, fits g_wr_flush_mode="VAL" wr_src_out <= diag_wr_src_out; IF g_wr_flush_mode="SOP" THEN wr_src_out.sop <= '0'; wr_src_out.eop <= '0'; IF wr_val_cnt MOD c_wr_frame_size = 0 THEN wr_src_out.sop <= diag_wr_src_out.valid; ELSIF wr_val_cnt MOD c_wr_frame_size = c_wr_frame_size-1 THEN wr_src_out.eop <= diag_wr_src_out.valid; END IF; END IF; IF g_wr_flush_mode="SYN" THEN wr_src_out.sync <= '0'; IF wr_val_cnt MOD c_wr_sync_period = 0 THEN wr_src_out.sync <= diag_wr_src_out.valid; END IF; END IF; END PROCESS; -- Map original dvr interface signals to t_mem_ctlr_mosi/miso dvr_done <= dvr_miso.done; -- Requested wr or rd sequence is done dvr_mosi.burstbegin <= dvr_en; dvr_mosi.wr <= dvr_wr_not_rd; -- No need to use dvr_mosi.rd dvr_mosi.address <= RESIZE_MEM_CTLR_ADDRESS(dvr_start_address); dvr_mosi.burstsize <= RESIZE_MEM_CTLR_BURSTSIZE(dvr_nof_data); dvr_mosi.flush <= dvr_wr_flush_en; u_io_ddr: ENTITY work.io_ddr GENERIC MAP( g_technology => g_technology, g_tech_ddr => c_tech_ddr, g_use_ddr_memory_model => g_use_ddr_memory_model, -- when TRUE use internal DDR memory model g_cross_domain_dvr_ctlr => c_cross_domain_dvr_ctlr, g_cross_domain_delay_len => c_meta_delay_len, g_wr_data_w => c_dp_data_w, g_wr_fifo_depth => c_wr_fifo_depth, -- >=16 AND >g_tech_ddr.maxburstsize, defined at DDR side of the FIFO. g_rd_fifo_depth => c_rd_fifo_depth, -- >=16 AND >g_tech_ddr.maxburstsize, defined at DDR side of the FIFO. g_rd_fifo_af_margin => c_rd_fifo_af_margin, g_rd_data_w => c_dp_data_w, g_wr_flush_mode => g_wr_flush_mode, g_wr_flush_use_channel => FALSE, g_wr_flush_start_channel => 0, g_wr_flush_nof_channels => 1 ) PORT MAP ( -- DDR reference clock ctlr_ref_clk => ctlr_ref_clk, ctlr_ref_rst => ctlr_ref_rst, -- DDR controller clock domain ctlr_clk_out => ctlr_clk, ctlr_rst_out => ctlr_rst, ctlr_clk_in => ctlr_clk, -- connect ctlr_clk_out to ctlr_clk_in at top level to avoid potential delta-cycle differences between the same clock ctlr_rst_in => ctlr_rst, -- MM clock domain mm_clk => mm_clk, mm_rst => mm_rst, -- MM register map for DDR controller status info reg_io_ddr_mosi => reg_io_ddr_mosi, reg_io_ddr_miso => reg_io_ddr_miso, -- Driver clock domain dvr_clk => dvr_clk, dvr_rst => dvr_rst, dvr_miso => dvr_miso, dvr_mosi => dvr_mosi, -- Write FIFO clock domain wr_clk => dp_clk, wr_rst => dp_rst, wr_fifo_usedw => wr_fifo_usedw, wr_sosi => wr_src_out, wr_siso => diag_wr_src_in, -- Read FIFO clock domain rd_clk => dp_clk, rd_rst => dp_rst, rd_fifo_usedw => rd_fifo_usedw, rd_sosi => diag_rd_snk_in, rd_siso => diag_rd_snk_out, -- DDR PHY external interface phy_ou => phy_ou, phy_io => phy_io, phy_in => phy_in ); external_ddr_memory_model : IF g_use_ddr_memory_model=FALSE GENERATE u_tech_ddr_memory_model : ENTITY tech_ddr_lib.tech_ddr_memory_model GENERIC MAP ( g_sim => TRUE, g_tech_ddr => c_tech_ddr ) PORT MAP ( mem_in => phy_ou, mem_io => phy_io ); END GENERATE; END ARCHITECTURE str;