diff --git a/libraries/io/ddr/hdllib.cfg b/libraries/io/ddr/hdllib.cfg new file mode 100644 index 0000000000000000000000000000000000000000..8f942740c32574e22f64227eebf66c6d62af2b14 --- /dev/null +++ b/libraries/io/ddr/hdllib.cfg @@ -0,0 +1,18 @@ +hdl_lib_name = io_ddr +hdl_library_clause_name = io_ddr_lib +hdl_lib_uses = common technology tech_ddr tech_ddr3 dp diag +hdl_lib_technology = + +build_dir_sim = $HDL_BUILD_DIR +build_dir_synth = $HDL_BUILD_DIR + +synth_files = + src/vhdl/io_ddr_driver.vhd + src/vhdl/io_ddr.vhd + +test_bench_files = + src/vhdl/tb_io_ddr.vhd + +quartus_qip_files = + + diff --git a/libraries/io/ddr/src/vhdl/io_ddr.vhd b/libraries/io/ddr/src/vhdl/io_ddr.vhd new file mode 100644 index 0000000000000000000000000000000000000000..5605fad0fa041a7b1d77164f878951edd8cb71ff --- /dev/null +++ b/libraries/io/ddr/src/vhdl/io_ddr.vhd @@ -0,0 +1,317 @@ +-------------------------------------------------------------------------------- +-- +-- 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/>. +-- +-------------------------------------------------------------------------------- + +LIBRARY IEEE, technology_lib, tech_ddr_lib, common_lib, dp_lib; +USE IEEE.STD_LOGIC_1164.ALL; +USE common_lib.common_pkg.ALL; +USE technology_lib.technology_select_pkg.ALL; +USE technology_lib.technology_pkg.ALL; +USE tech_ddr_lib.tech_ddr_pkg.ALL; +USE dp_lib.dp_stream_pkg.ALL; + +ENTITY io_ddr IS + GENERIC( + g_technology : NATURAL := c_tech_select_default; + g_tech_ddr : t_c_tech_ddr; + g_wr_data_w : NATURAL := c_tech_ddr_ctlr.data_w; + g_wr_use_ctrl : BOOLEAN := FALSE; -- TRUE to allow filling the WR FIFO (by disabling flush) after an EOP + g_wr_fifo_depth : NATURAL := 128; -- >=16 AND >c_tech_ddr_ctlr.maxburstsize , defined at read side of write FIFO. + g_rd_fifo_depth : NATURAL := 256; -- >=16 AND >c_tech_ddr_ctlr.maxburstsize > c_ddr_ctrl_nof_latent_reads, defined at write side of read FIFO. + g_rd_data_w : NATURAL := c_tech_ddr_ctlr.data_w; + g_flush_wr_fifo : BOOLEAN := FALSE; -- TRUE instantiates a dp_flush + controller to flush the write fifo when the driver is not ready to write + g_flush_sop : BOOLEAN := FALSE; + g_flush_sop_channel : BOOLEAN := FALSE; + g_flush_sop_start_channel : NATURAL := 0; + g_flush_nof_channels : NATURAL := 0 + ); + PORT ( + ctlr_ref_clk : IN STD_LOGIC; + ctlr_ref_rst : IN STD_LOGIC; + + ctlr_gen_clk : OUT STD_LOGIC; -- Controller generated clock + ctlr_gen_rst : OUT STD_LOGIC; + ctlr_gen_clk_2x : OUT STD_LOGIC; -- Controller generated double frequency clock + ctlr_gen_rst_2x : OUT STD_LOGIC; -- ctlr_gen_rst synchronized to ctlr_gen_clk_2x + + ctlr_init_done : OUT STD_LOGIC; + ctlr_rdy : OUT STD_LOGIC; + + dvr_start_addr : IN t_tech_ddr_addr; + dvr_end_addr : IN t_tech_ddr_addr; + + dvr_en : IN STD_LOGIC; + dvr_wr_not_rd : IN STD_LOGIC; + dvr_done : OUT STD_LOGIC; + + wr_clk : IN STD_LOGIC; + wr_rst : IN STD_LOGIC; + + wr_sosi : IN t_dp_sosi; + wr_siso : OUT t_dp_siso; + + rd_sosi : OUT t_dp_sosi; + rd_siso : IN t_dp_siso; + + rd_clk : IN STD_LOGIC; + rd_rst : IN STD_LOGIC; + + rd_fifo_usedw : OUT STD_LOGIC_VECTOR(ceil_log2(g_rd_fifo_depth * (g_tech_ddr.data_w/g_rd_data_w) )-1 DOWNTO 0); + + phy_in : IN t_tech_ddr_phy_in; + phy_io : INOUT t_tech_ddr_phy_io; + phy_ou : OUT t_tech_ddr_phy_ou + ); +END io_ddr; + + +ARCHITECTURE str OF io_ddr IS + + CONSTANT c_wr_fifo_depth : NATURAL := g_wr_fifo_depth * (g_tech_ddr.data_w/g_wr_data_w); -- Multiply fifo depth by the fifo's rd/wr width ratio to get write side depth + + CONSTANT c_latency : NATURAL := 1; + + SIGNAL ctlr_burst : STD_LOGIC; + SIGNAL ctlr_burst_size : STD_LOGIC_VECTOR(g_tech_ddr.maxburstsize_w-1 DOWNTO 0); + SIGNAL ctlr_address : STD_LOGIC_VECTOR(ceil_log2(g_tech_ddr.cs_w-1) + g_tech_ddr.ba_w + g_tech_ddr.a_w + g_tech_ddr.a_col_w - g_tech_ddr.rsl_w-1 DOWNTO 0); -- ceil_log2(..-1) because the chip select lines are converted to a logical address + SIGNAL ctlr_rd_req : STD_LOGIC; + SIGNAL ctlr_wr_req : STD_LOGIC; + + SIGNAL ctlr_ref_rst_n : STD_LOGIC; + SIGNAL ctlr_gen_rst_n : STD_LOGIC; + + SIGNAL i_ctlr_gen_clk : STD_LOGIC; + SIGNAL i_ctlr_gen_rst : STD_LOGIC; + SIGNAL i_ctlr_gen_clk_2x : STD_LOGIC; + SIGNAL i_ctlr_init_done : STD_LOGIC; + SIGNAL i_ctlr_rdy : STD_LOGIC; + SIGNAL i_dvr_done : STD_LOGIC; + + SIGNAL dvr_cur_addr : t_tech_ddr_addr; + SIGNAL dvr_flush : STD_LOGIC := '0'; + + SIGNAL ctlr_wr_siso : t_dp_siso := c_dp_siso_rdy; -- default xon='1' + SIGNAL ctlr_wr_sosi : t_dp_sosi; + + SIGNAL flush_wr_siso : t_dp_siso; + SIGNAL flush_wr_sosi : t_dp_sosi; + + SIGNAL ctlr_rd_siso : t_dp_siso; + SIGNAL ctlr_rd_sosi : t_dp_sosi; + + SIGNAL wr_fifo_usedw : STD_LOGIC_VECTOR(ceil_log2(g_wr_fifo_depth)-1 DOWNTO 0); -- read side depth of the write FIFO + +BEGIN + + dvr_done <= i_dvr_done; + + ctlr_ref_rst_n <= NOT(ctlr_ref_rst); + i_ctlr_gen_rst <= NOT(ctlr_gen_rst_n); + + ctlr_gen_clk <= i_ctlr_gen_clk; + ctlr_gen_rst <= i_ctlr_gen_rst; + ctlr_gen_clk_2x <= i_ctlr_gen_clk_2x; + ctlr_rdy <= i_ctlr_rdy; + ctlr_init_done <= i_ctlr_init_done; + + u_wr_fifo : ENTITY dp_lib.dp_fifo_dc_mixed_widths + GENERIC MAP ( + g_wr_data_w => g_wr_data_w, + g_rd_data_w => g_tech_ddr.data_w, + g_use_ctrl => g_wr_use_ctrl, + g_wr_fifo_size => c_wr_fifo_depth, + g_wr_fifo_af_margin => 4 + c_latency, --default (4) + c_latency to compensate for latency introduced by registering wr_siso.ready + g_rd_fifo_rl => 0 + ) + PORT MAP ( + wr_rst => wr_rst, + wr_clk => wr_clk, + rd_rst => i_ctlr_gen_rst, + rd_clk => i_ctlr_gen_clk, + + snk_out => wr_siso, + snk_in => wr_sosi, + + wr_usedw => OPEN, + rd_usedw => wr_fifo_usedw, + rd_emp => OPEN, + + src_in => flush_wr_siso, + src_out => flush_wr_sosi + ); + + u_dp_flush : ENTITY dp_lib.dp_flush -- Always instantiate the flusher as it also contains a RL adapter + GENERIC MAP ( + g_ready_latency => 0, + g_framed_xon => g_wr_use_ctrl, -- stop flushing when dvr_flush is low and a sop has arrived + g_framed_xoff => FALSE -- immediately start flushing when dvr_flush goes high + ) + PORT MAP ( + rst => i_ctlr_gen_rst, + clk => i_ctlr_gen_clk, + + snk_in => flush_wr_sosi, + snk_out => flush_wr_siso, + + src_out => ctlr_wr_sosi, + src_in => ctlr_wr_siso, -- fixed streaming xon='1' + + flush_en => dvr_flush -- memory mapped xon/xoff control + ); + + gen_flush : IF g_flush_wr_fifo = TRUE GENERATE + u_flush_ctrl : ENTITY work.ddr_flush_ctrl + GENERIC MAP ( + g_sop => g_flush_sop, + g_sop_channel => g_flush_sop_channel, + g_sop_start_channel => g_flush_sop_start_channel, + g_nof_channels => g_flush_nof_channels + ) + PORT MAP ( + rst => wr_rst, + clk => wr_clk, + + dvr_en => dvr_en, + dvr_wr_not_rd => dvr_wr_not_rd, + dvr_done => i_dvr_done, + + wr_sosi => wr_sosi, + + dvr_flush => dvr_flush + ); + END GENERATE; + + u_rd_fifo : ENTITY dp_lib.dp_fifo_dc_mixed_widths + GENERIC MAP ( + g_wr_data_w => g_tech_ddr.data_w, + g_rd_data_w => g_rd_data_w, + g_use_ctrl => FALSE, + g_wr_fifo_size => g_rd_fifo_depth, + g_wr_fifo_af_margin => c_ddr_ctrl_nof_latent_reads, -- >=4 (required by dp_fifo) + g_rd_fifo_rl => 1 + ) + PORT MAP ( + wr_rst => i_ctlr_gen_rst, + wr_clk => i_ctlr_gen_clk, + rd_rst => rd_rst, + rd_clk => rd_clk, + + snk_out => ctlr_rd_siso, + snk_in => ctlr_rd_sosi, + + wr_usedw => OPEN, + rd_usedw => rd_fifo_usedw, + rd_emp => OPEN, + + src_in => rd_siso, + src_out => rd_sosi + ); + + u_io_ddr_driver : ENTITY work.io_ddr_driver + GENERIC MAP ( + g_tech_ddr => g_tech_ddr, + g_wr_fifo_depth => g_wr_fifo_depth + ) + PORT MAP ( + rst => i_ctlr_gen_rst, + clk => i_ctlr_gen_clk, + + ctlr_rdy => i_ctlr_rdy, + ctlr_init_done => i_ctlr_init_done, + ctlr_wr_req => ctlr_wr_req, + ctlr_rd_req => ctlr_rd_req, + ctlr_burst => ctlr_burst, + ctlr_burst_size => ctlr_burst_size, + + wr_val => ctlr_wr_sosi.valid, + wr_rdy => ctlr_wr_siso.ready, + rd_rdy => ctlr_rd_siso.ready, + + cur_addr => dvr_cur_addr, + start_addr => dvr_start_addr, + end_addr => dvr_end_addr, + + dvr_en => dvr_en, + dvr_wr_not_rd => dvr_wr_not_rd, + dvr_done => i_dvr_done, + + wr_fifo_usedw => wr_fifo_usedw + ); + + ctlr_address <= dvr_cur_addr.chip & + dvr_cur_addr.bank & + dvr_cur_addr.row(g_tech_ddr.a_w-1 DOWNTO 0) & + dvr_cur_addr.column(g_tech_ddr.a_col_w-1 DOWNTO g_tech_ddr.rsl_w); + + gen_uphy_4g_800_master : IF func_tech_ddr_module_size(c_tech_ddr)=4 AND g_tech_ddr.mts=800 GENERATE + u_uphy_4g_800_master : COMPONENT uphy_4g_800_master + PORT MAP ( + pll_ref_clk => ctlr_ref_clk, + global_reset_n => ctlr_ref_rst_n, + soft_reset_n => '1', + afi_clk => i_ctlr_gen_clk, + afi_half_clk => OPEN, + afi_reset_n => ctlr_gen_rst_n, + mem_a => phy_ou.a(g_tech_ddr.a_w-1 DOWNTO 0), + mem_ba => phy_ou.ba(g_tech_ddr.ba_w-1 DOWNTO 0), + mem_ck => phy_io.clk(g_tech_ddr.clk_w-1 DOWNTO 0), + mem_ck_n => phy_io.clk_n(g_tech_ddr.clk_w-1 DOWNTO 0), + mem_cke => phy_ou.cke(g_tech_ddr.clk_w-1 DOWNTO 0), + mem_cs_n => phy_ou.cs_n(g_tech_ddr.cs_w-1 DOWNTO 0), + mem_dm => phy_ou.dm(g_tech_ddr.dm_w-1 DOWNTO 0), + mem_ras_n => phy_ou.ras_n, + mem_cas_n => phy_ou.cas_n, + mem_we_n => phy_ou.we_n, + mem_reset_n => phy_ou.reset_n, + mem_dq => phy_io.dq(g_tech_ddr.dq_w-1 DOWNTO 0), + mem_dqs => phy_io.dqs(g_tech_ddr.dqs_w-1 DOWNTO 0), + mem_dqs_n => phy_io.dqs_n(g_tech_ddr.dqs_w-1 DOWNTO 0), + mem_odt => phy_ou.odt(g_tech_ddr.cs_w-1 DOWNTO 0), + avl_ready => i_ctlr_rdy, + avl_burstbegin => ctlr_burst, + avl_addr => ctlr_address, + avl_rdata_valid => ctlr_rd_sosi.valid, + avl_rdata => ctlr_rd_sosi.data(g_tech_ddr.data_w-1 DOWNTO 0), + avl_wdata => ctlr_wr_sosi.data(g_tech_ddr.data_w-1 DOWNTO 0), + avl_be => (OTHERS => '1'), + avl_read_req => ctlr_rd_req, + avl_write_req => ctlr_wr_req, + avl_size => ctlr_burst_size, + local_init_done => i_ctlr_init_done, + local_cal_success => OPEN, + local_cal_fail => OPEN, + oct_rdn => phy_in.oct_rdn, + oct_rup => phy_in.oct_rup, + seriesterminationcontrol => OPEN, + parallelterminationcontrol => OPEN, + pll_mem_clk => i_ctlr_gen_clk_2x, + pll_write_clk => OPEN, + pll_write_clk_pre_phy_clk => OPEN, + pll_addr_cmd_clk => OPEN, + pll_locked => OPEN, + pll_avl_clk => OPEN, + pll_config_clk => OPEN, + dll_delayctrl => OPEN + ); + END GENERATE; + +END str; + diff --git a/libraries/io/ddr/src/vhdl/io_ddr_driver.vhd b/libraries/io/ddr/src/vhdl/io_ddr_driver.vhd new file mode 100644 index 0000000000000000000000000000000000000000..549c1ee29449c7255cd7c0b55851b7f8fdd63541 --- /dev/null +++ b/libraries/io/ddr/src/vhdl/io_ddr_driver.vhd @@ -0,0 +1,275 @@ +-------------------------------------------------------------------------------- +-- +-- 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/>. +-- +-------------------------------------------------------------------------------- + +LIBRARY IEEE, tech_ddr_lib, common_lib; +USE IEEE.STD_LOGIC_1164.ALL; +USE common_lib.common_pkg.ALL; +USE tech_ddr_lib.tech_ddr_pkg.ALL; + +ENTITY io_ddr_driver IS + GENERIC ( + g_tech_ddr : t_c_tech_ddr; + g_wr_fifo_depth : NATURAL := 128 + ); + PORT ( + clk : IN STD_LOGIC; + rst : IN STD_LOGIC; + + ctlr_rdy : IN STD_LOGIC; + ctlr_init_done : IN STD_LOGIC; + ctlr_burst : OUT STD_LOGIC; + ctlr_burst_size : OUT STD_LOGIC_VECTOR(g_tech_ddr.maxburstsize_w-1 DOWNTO 0); + ctlr_wr_req : OUT STD_LOGIC; + ctlr_rd_req : OUT STD_LOGIC; + + dvr_en : IN STD_LOGIC := '1'; + dvr_wr_not_rd : IN STD_LOGIC; + dvr_done : OUT STD_LOGIC; -- Requested wr or rd sequence is done. + + wr_val : IN STD_LOGIC; + wr_rdy : OUT STD_LOGIC; + rd_rdy : IN STD_LOGIC; + + cur_addr : OUT t_tech_ddr_addr; + start_addr : IN t_tech_ddr_addr; + end_addr : IN t_tech_ddr_addr; + + wr_fifo_usedw : IN STD_LOGIC_VECTOR + ); +END io_ddr_driver; + + +ARCHITECTURE str OF io_ddr_driver IS + + CONSTANT c_chip_addr_w : NATURAL := ceil_log2(g_tech_ddr.cs_w); --Chip sel lines converted to logical address + CONSTANT c_address_w : NATURAL := c_chip_addr_w + g_tech_ddr.ba_w + g_tech_ddr.a_w + g_tech_ddr.a_col_w +1; -- 1 bit added to detect overflow + + CONSTANT c_margin : NATURAL := 2; -- wr_burst_size is updated one cycle after reading actual nof available words. + -- Subtract two (wr_fifo_usedw and wr_burst_size are both registered) so we cannot + -- post a request for a too large burst size, which could cause the wr_burst state + -- to be two valid words short. + + SIGNAL req_burst_cycles : STD_LOGIC_VECTOR(g_tech_ddr.maxburstsize_w-1 DOWNTO 0); + SIGNAL nxt_req_burst_cycles : STD_LOGIC_VECTOR(g_tech_ddr.maxburstsize_w-1 DOWNTO 0); + + TYPE t_state_enum IS (s_init, s_idle, s_wait1, s_wait2, s_wait3, s_rd_request, s_wr_request, s_wr_burst); + + SIGNAL state : t_state_enum; + SIGNAL nxt_state : t_state_enum; + SIGNAL prev_state : t_state_enum; + + SIGNAL wr_burst_size : NATURAL; + SIGNAL rd_burst_size : NATURAL; + + SIGNAL nxt_wr_burst_size : NATURAL; + SIGNAL nxt_rd_burst_size : NATURAL; + + SIGNAL i_ctlr_burst_size : STD_LOGIC_VECTOR(g_tech_ddr.maxburstsize_w-1 DOWNTO 0); + + SIGNAL i_dvr_done : STD_LOGIC; + SIGNAL nxt_dvr_done : STD_LOGIC; + + SIGNAL start_address : STD_LOGIC_VECTOR(c_address_w-1 DOWNTO 0); + SIGNAL end_address : STD_LOGIC_VECTOR(c_address_w-1 DOWNTO 0); + + SIGNAL cur_address : STD_LOGIC_VECTOR(c_address_w-1 DOWNTO 0); + SIGNAL nxt_cur_address : STD_LOGIC_VECTOR(c_address_w-1 DOWNTO 0); + SIGNAL diff_address : STD_LOGIC_VECTOR(c_address_w-1 DOWNTO 0); + + SIGNAL addresses_rem : STD_LOGIC_VECTOR(31 DOWNTO 0); -- nof words (on the user side interface) to rd/wr until end addr is reached + SIGNAL reg_addresses_rem : STD_LOGIC_VECTOR(31 DOWNTO 0); -- nof words (on the user side interface) to rd/wr until end addr is reached + + SIGNAL reg_wr_fifo_usedw : STD_LOGIC_VECTOR(ceil_log2(g_wr_fifo_depth)-1 DOWNTO 0); -- read side depth of the write FIFO + +BEGIN + + ctlr_burst_size <= i_ctlr_burst_size; + dvr_done <= i_dvr_done; + + p_clk : PROCESS(rst, clk) + BEGIN + IF rst='1' THEN + state <= s_init; + req_burst_cycles <= (OTHERS => '0'); + i_dvr_done <= '0'; + cur_address <= (OTHERS=>'0'); + wr_burst_size <= 0; + rd_burst_size <= 0; + reg_addresses_rem <= (OTHERS=>'0'); + reg_wr_fifo_usedw <= (OTHERS=>'0'); + prev_state <= s_idle; + ELSIF rising_edge(clk) THEN + state <= nxt_state; + req_burst_cycles <= nxt_req_burst_cycles; + i_dvr_done <= nxt_dvr_done; + cur_address <= nxt_cur_address; + wr_burst_size <= nxt_wr_burst_size; + rd_burst_size <= nxt_rd_burst_size; + reg_addresses_rem <= addresses_rem; + reg_wr_fifo_usedw <= wr_fifo_usedw; + prev_state <= state; + END IF; + END PROCESS; + + -- Add 1 address (accounting for address resulotion) to diff_address: we also want to write the last address. Shift the result right to provide the correct resolution. + addresses_rem <= RESIZE_UVEC( SHIFT_UVEC( INCR_UVEC(diff_address, g_tech_ddr.rsl), g_tech_ddr.rsl_w), addresses_rem'LENGTH); + + -- End address - current address + diff_address <= SUB_UVEC(end_address, cur_address, c_address_w); + + p_burst_size : PROCESS (reg_addresses_rem, reg_wr_fifo_usedw) + VARIABLE v_burst_size : NATURAL; + BEGIN + -- Write burst size is smallest of g_tech_ddr.maxburstsize, addresses_rem and wr_fifo_usedw + v_burst_size := g_tech_ddr.maxburstsize+c_margin; + IF UNSIGNED(reg_wr_fifo_usedw)>=c_margin AND UNSIGNED(reg_addresses_rem)>=c_margin THEN + IF v_burst_size > SIGNED('0' & reg_addresses_rem) THEN v_burst_size := TO_UINT(reg_addresses_rem); END IF; + IF v_burst_size > SIGNED('0' & reg_wr_fifo_usedw) THEN v_burst_size := TO_UINT(reg_wr_fifo_usedw); END IF; + v_burst_size := v_burst_size - c_margin; + ELSE + v_burst_size := 0; + END IF; + nxt_wr_burst_size <= v_burst_size; + + -- Read burst size is smallest of g_tech_ddr.maxburstsize and addresses_rem + v_burst_size := g_tech_ddr.maxburstsize; + IF UNSIGNED(reg_addresses_rem)>=1 THEN -- prevent assigning <0 value to natural + IF v_burst_size > SIGNED('0' & reg_addresses_rem) THEN v_burst_size := TO_UINT(reg_addresses_rem); END IF; + ELSE + v_burst_size := 0; + END IF; + nxt_rd_burst_size <= v_burst_size; + END PROCESS; + + p_state : PROCESS(prev_state, state, i_dvr_done, ctlr_rdy, req_burst_cycles, dvr_wr_not_rd, wr_val, wr_fifo_usedw, wr_burst_size, rd_burst_size, dvr_en, ctlr_init_done, reg_addresses_rem, rd_rdy, i_ctlr_burst_size, start_address, cur_address) + BEGIN + nxt_state <= state; + ctlr_wr_req <= '0'; + ctlr_rd_req <= '0'; + ctlr_burst <= '0'; + i_ctlr_burst_size <= (OTHERS => '0'); + nxt_req_burst_cycles <= req_burst_cycles; + wr_rdy <= '0'; + nxt_dvr_done <= i_dvr_done; + nxt_cur_address <= cur_address; + + CASE state IS + + WHEN s_wr_burst => -- Performs the burst portion (word 2+) + ctlr_wr_req <= '1'; + IF ctlr_rdy = '1' THEN -- when local_ready goes low, that cycle does not count as a burst cycle + nxt_req_burst_cycles <= INCR_UVEC(req_burst_cycles, -1); + wr_rdy <= '1'; -- wr side uses latency of 0, so wr_rdy<='1' acknowledges a successful write request. + IF UNSIGNED(req_burst_cycles) = 1 THEN -- Then we're in the last cycle of this burst sequence + nxt_state <= s_wr_request; -- We can only initiate a burst through the wr_request state + END IF; + END IF; + + WHEN s_wr_request => -- Performs 1 write or read and goes into s_wr_burst when requested write words >1 + nxt_state <= s_wait3; + IF UNSIGNED(reg_addresses_rem) = 0 THEN -- end address reached + nxt_dvr_done <= '1'; + nxt_state <= s_idle; + ELSIF ctlr_rdy = '1' THEN + IF wr_val = '1' THEN + -- Always perform 1st write here + ctlr_burst <= '1'; -- assert burst begin: strictly this is a burst of 1. + ctlr_wr_req <= '1'; + wr_rdy <= '1'; + i_ctlr_burst_size <= TO_UVEC(1, g_tech_ddr.maxburstsize_w); -- Set ctlr_burst_size to 1 by default + IF wr_burst_size > 1 THEN + -- Perform any remaining writes in a burst + nxt_state <= s_wr_burst; + nxt_req_burst_cycles <= TO_UVEC(wr_burst_size-1, g_tech_ddr.maxburstsize_w); -- Forward the required nof burst cycles (-1 as we've done the 1st in this state already) to burst state + i_ctlr_burst_size <= TO_UVEC(wr_burst_size , g_tech_ddr.maxburstsize_w); + END IF; -- ELSE: there is only 1 word, so no need for remaining burst + nxt_cur_address <= INCR_UVEC(cur_address, UNSIGNED(i_ctlr_burst_size)*g_tech_ddr.rsl); +-- IF UNSIGNED(i_ctlr_burst_size) = 1 THEN -- Prevents FSM from going into this state again too soon (reg_addresses_rem invalid) +-- nxt_state <= s_wait3; +-- END IF; + END IF; + END IF; + + WHEN s_rd_request => -- Posts a read request for a burst (0...g_tech_ddr.maxburstsize) + nxt_state <= s_wait3; + IF UNSIGNED(reg_addresses_rem) = 0 THEN -- end address reached + nxt_dvr_done <= '1'; + nxt_state <= s_idle; + ELSE + IF rd_rdy = '1' THEN -- Fifo uses its internal almost_full signal to toggle its snk_out.rdy + IF ctlr_rdy = '1' THEN + ctlr_rd_req <= '1'; + ctlr_burst <= '1'; -- assert burst begin: strictly this is a burst of 1. + i_ctlr_burst_size <= TO_UVEC(rd_burst_size, g_tech_ddr.maxburstsize_w); + IF rd_burst_size = 0 THEN i_ctlr_burst_size <= TO_UVEC(1, g_tech_ddr.maxburstsize_w); END IF; + nxt_cur_address <= INCR_UVEC(cur_address, UNSIGNED(i_ctlr_burst_size)*g_tech_ddr.rsl); +-- IF UNSIGNED(i_ctlr_burst_size) = 1 THEN -- Prevents FSM from going into this state again too soon (reg_addresses_rem invalid) +-- nxt_state <= s_wait3; +-- END IF; + END IF; + END IF; + END IF; + + -- This wait state is inserted between two requests when necessary, e.g. when FSM enters wr_request + -- from the state wr_request, an extra cycle is needed for reg_addresses_rem to be valid. + WHEN s_wait3 => + IF prev_state = s_wr_request THEN nxt_state <= s_wr_request; END IF; + IF prev_state = s_rd_request THEN nxt_state <= s_rd_request; END IF; + + -- In this cycle reg_addresses_rem is valid. This cycle is added so wr_burst_size and rd_burst_size + -- (derived from reg_addresses_rem) are valid the next cycle. + WHEN s_wait2 => + IF dvr_wr_not_rd = '1' THEN + nxt_state <= s_wr_request; + ELSE + nxt_state <= s_rd_request; + END IF; + + -- Wait a cycle so reg_addresses_rem is valid the next cyle. + WHEN s_wait1 => + nxt_state <= s_wait2; + + WHEN s_idle => + IF dvr_en = '1' THEN + nxt_cur_address <= start_address; + nxt_dvr_done <= '0'; + nxt_state <= s_wait1; + END IF; + + WHEN OTHERS => -- s_init + IF ctlr_init_done = '1' THEN + nxt_state <= s_idle; + END IF; + + END CASE; + END PROCESS; + + end_address <= RESIZE_UVEC( end_addr.chip & end_addr.bank & end_addr.row(g_tech_ddr.a_w-1 DOWNTO 0) & end_addr.column(g_tech_ddr.a_col_w-1 DOWNTO 0), c_address_w); + start_address <= RESIZE_UVEC(start_addr.chip & start_addr.bank & start_addr.row(g_tech_ddr.a_w-1 DOWNTO 0) & start_addr.column(g_tech_ddr.a_col_w-1 DOWNTO 0), c_address_w); + + cur_addr.chip( c_chip_addr_w -1 DOWNTO 0) <= cur_address(c_chip_addr_w+g_tech_ddr.ba_w+g_tech_ddr.a_w+g_tech_ddr.a_col_w-1 DOWNTO g_tech_ddr.ba_w+g_tech_ddr.a_w+g_tech_ddr.a_col_w); + cur_addr.bank( g_tech_ddr.ba_w -1 DOWNTO 0) <= cur_address( g_tech_ddr.ba_w+g_tech_ddr.a_w+g_tech_ddr.a_col_w-1 DOWNTO g_tech_ddr.a_w+g_tech_ddr.a_col_w); + cur_addr.row( g_tech_ddr.a_w -1 DOWNTO 0) <= cur_address( g_tech_ddr.a_w+g_tech_ddr.a_col_w-1 DOWNTO g_tech_ddr.a_col_w); + cur_addr.column(g_tech_ddr.a_col_w-1 DOWNTO 0) <= cur_address( g_tech_ddr.a_col_w-1 DOWNTO 0); + +END str; + diff --git a/libraries/io/ddr/tb/vhdl/tb_io_ddr.vhd b/libraries/io/ddr/tb/vhdl/tb_io_ddr.vhd new file mode 100644 index 0000000000000000000000000000000000000000..56183cd5672acacb6b8ad824f850305a3c6fb080 --- /dev/null +++ b/libraries/io/ddr/tb/vhdl/tb_io_ddr.vhd @@ -0,0 +1,272 @@ +-------------------------------------------------------------------------------- +-- +-- 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: +-- +-- - uphy_4g_800_master +-- - uphy_4g_800_slave +-- +-- The DUT can be selected, using the c_tech_ddr constants. +-- +-- Testbench is selftesting: +-- +-- > 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_mem_pkg.ALL; +USE dp_lib.dp_stream_pkg.ALL; +USE technology_lib.technology_pkg.ALL; +USE tech_ddr_lib.tech_ddr_pkg.ALL; + +ENTITY tb_io_ddr IS +END ENTITY tb_io_ddr; + +ARCHITECTURE str of tb_io_ddr IS + + CONSTANT c_ctlr_ref_clk_period : TIME := 5 ns; -- 200 MHz + + CONSTANT c_tech_ddr : t_c_tech_ddr := c_tech_ddr_4g_800m; + + CONSTANT c_data_w : NATURAL := 256; --32; + + SIGNAL ctlr_ref_clk : STD_LOGIC := '0'; + SIGNAL ctlr_ref_rst : STD_LOGIC := '1'; + SIGNAL tb_end : STD_LOGIC := '0'; + SIGNAL ctlr_gen_clk : STD_LOGIC; + SIGNAL ctlr_gen_rst : STD_LOGIC; + + SIGNAL ctlr_rdy : STD_LOGIC; + SIGNAL ctlr_init_done : STD_LOGIC; + + SIGNAL dvr_start_addr : t_tech_ddr_addr; + SIGNAL dvr_end_addr : t_tech_ddr_addr; + + SIGNAL dvr_en : STD_LOGIC; + SIGNAL dvr_wr_not_rd : STD_LOGIC; + SIGNAL dvr_done : STD_LOGIC; + + SIGNAL wr_siso : t_dp_siso; + SIGNAL wr_sosi : t_dp_sosi; + + SIGNAL wr_siso_pre_fifo : t_dp_siso; + SIGNAL wr_sosi_pre_fifo : t_dp_sosi; + + SIGNAL rd_siso : t_dp_siso; + SIGNAL rd_sosi : t_dp_sosi; + + 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 phy_in : t_tech_ddr_phy_in; + SIGNAL phy_io : t_tech_ddr_phy_io; + SIGNAL phy_ou : t_tech_ddr_phy_ou; + + SIGNAL ras_n : STD_LOGIC_VECTOR(0 DOWNTO 0); + SIGNAL cas_n : STD_LOGIC_VECTOR(0 DOWNTO 0); + SIGNAL we_n : STD_LOGIC_VECTOR(0 DOWNTO 0); + +BEGIN + + ctlr_ref_clk <= NOT(ctlr_ref_clk) OR tb_end AFTER c_ctlr_ref_clk_period/2; + ctlr_ref_rst <= '0' AFTER 100 ns; + + dvr_start_addr <= c_tech_ddr_addr_lo; + dvr_end_addr <= c_tech_ddr_addr_hi_sim; + + p_stimuli : PROCESS + BEGIN + tb_end <= '0'; + dvr_en <= '0'; + src_diag_en <= '0'; + dvr_wr_not_rd <= '0'; + snk_diag_en <= '0'; + + WAIT UNTIL ctlr_init_done = '1'; + FOR i IN 0 TO 1 LOOP + WAIT UNTIL rising_edge(ctlr_gen_clk); -- Give the driver FSM a cycle to go into idle mode + END LOOP; + + -- START WRITE + src_diag_en <= '1'; + dvr_wr_not_rd <= '1'; + dvr_en <= '1'; + + WAIT UNTIL rising_edge(ctlr_gen_clk); + + dvr_en <= '0'; + + -- WRITE DONE + WAIT UNTIL dvr_done = '1'; + + src_diag_en <= '0'; + + -- START READ + snk_diag_en <= '1'; + dvr_wr_not_rd <= '0'; + dvr_en <= '1'; + + WAIT UNTIL rising_edge(ctlr_gen_clk); + + dvr_en <= '0'; + + -- READ DONE + WAIT UNTIL dvr_done = '1'; + + WAIT FOR 2 us; -- 'Done' means all requests are posted. Wait for the last read data to arrive. + + 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; + ASSERT FALSE REPORT "[OK] Test passed." SEVERITY NOTE; + tb_end <= '1'; + + WAIT; + END PROCESS; + + u_diagnostics: ENTITY diagnostics_lib.diagnostics + GENERIC MAP ( + g_dat_w => c_data_w, + g_nof_streams => 1 + ) + PORT MAP ( + rst => ctlr_gen_rst, + clk => ctlr_gen_clk, + + snk_out_arr(0) => rd_siso, + snk_in_arr(0) => rd_sosi, + 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) => wr_sosi, + src_in_arr(0) => wr_siso, + src_diag_en(0) => src_diag_en, + src_diag_md(0) => '1', + src_val_cnt(0) => src_val_cnt + ); + + gen_ddr_4g_memory_model : IF func_tech_ddr_module_size(c_tech_ddr) = 4 GENERATE + u_4gb_ddr3_memory_model : COMPONENT alt_mem_if_ddr3_mem_model_top_ddr3_mem_if_dm_pins_en_mem_if_dqsn_en + GENERIC MAP ( + MEM_IF_ADDR_WIDTH => 15, + MEM_IF_ROW_ADDR_WIDTH => 15, + MEM_IF_COL_ADDR_WIDTH => 10, + MEM_IF_CS_PER_RANK => 1, + MEM_IF_CONTROL_WIDTH => 1, + MEM_IF_DQS_WIDTH => 8, + MEM_IF_CS_WIDTH => 2, + MEM_IF_BANKADDR_WIDTH => 3, + MEM_IF_DQ_WIDTH => 64, + MEM_IF_CK_WIDTH => 2, + MEM_IF_CLK_EN_WIDTH => 2, + DEVICE_WIDTH => 1, + MEM_TRCD => 6, + MEM_TRTP => 3, + MEM_DQS_TO_CLK_CAPTURE_DELAY => 100, + MEM_CLK_TO_DQS_CAPTURE_DELAY => 100000, + MEM_IF_ODT_WIDTH => 2, + MEM_MIRROR_ADDRESSING_DEC => 0, + MEM_REGDIMM_ENABLED => false, + DEVICE_DEPTH => 1, + MEM_GUARANTEED_WRITE_INIT => false, + MEM_VERBOSE => true, + MEM_INIT_EN => false, + MEM_INIT_FILE => "", + DAT_DATA_WIDTH => 32 + ) + PORT MAP ( + mem_a => phy_ou.a(c_tech_ddr.a_w-1 DOWNTO 0), -- memory.mem_a + mem_ba => phy_ou.ba, -- .mem_ba + mem_ck => phy_io.clk, -- .mem_ck + mem_ck_n => phy_io.clk_n, -- .mem_ck_n + mem_cke => phy_ou.cke(c_tech_ddr.cs_w-1 DOWNTO 0), -- .mem_cke + mem_cs_n => phy_ou.cs_n(c_tech_ddr.cs_w-1 DOWNTO 0), -- .mem_cs_n + mem_dm => phy_ou.dm, -- .mem_dm + mem_ras_n => ras_n, -- .mem_ras_n + mem_cas_n => cas_n, -- .mem_cas_n + mem_we_n => we_n, -- .mem_we_n + mem_reset_n => phy_ou.reset_n, -- .mem_reset_n + mem_dq => phy_io.dq, -- .mem_dq + mem_dqs => phy_io.dqs, -- .mem_dqs + mem_dqs_n => phy_io.dqs_n, -- .mem_dqs_n + mem_odt => phy_ou.odt -- .mem_odt + ); + + ras_n(0) <= phy_ou.ras_n; + cas_n(0) <= phy_ou.cas_n; + we_n(0) <= phy_ou.we_n; + END GENERATE; + + u_io_ddr: ENTITY work.io_ddr + GENERIC MAP( + g_technology => c_tech_select_default, + g_ddr => c_tech_ddr, + g_wr_data_w => c_data_w, + g_rd_data_w => c_data_w + ) + PORT MAP ( + ctlr_ref_clk => ctlr_ref_clk, + ctlr_ref_rst => ctlr_ref_rst, + + ctlr_gen_clk => ctlr_gen_clk, + ctlr_gen_rst => ctlr_gen_rst, + + ctlr_init_done => ctlr_init_done, + ctlr_rdy => ctlr_rdy, + + dvr_start_addr => dvr_start_addr, + dvr_end_addr => dvr_end_addr, + dvr_en => dvr_en, + dvr_wr_not_rd => dvr_wr_not_rd, + dvr_done => dvr_done, + + wr_clk => ctlr_gen_clk, + wr_rst => ctlr_gen_rst, + + wr_sosi => wr_sosi, + wr_siso => wr_siso, + + rd_sosi => rd_sosi, + rd_siso => rd_siso, + + rd_clk => ctlr_gen_clk, + rd_rst => ctlr_gen_rst, + + phy_ou => phy_ou, + phy_io => phy_io, + phy_in => phy_in + ); + +END ARCHITECTURE str; + +