------------------------------------------------------------------------------- -- -- Copyright 2022 -- 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: Job van Wee -- Purpose: controller for ddrctrl, decides when to write when to read or when to stop writing or reading. -- -- Description: -- -- Remark: -- Use VHDL coding template from: -- https://support.astron.nl/confluence/display/SBe/VHDL+design+patterns+for+RTL+coding -- LIBRARY IEEE, dp_lib, common_lib, tech_ddr_lib; USE IEEE.std_logic_1164.ALL; USE IEEE.numeric_std.ALL; USE dp_lib.dp_stream_pkg.ALL; USE common_lib.common_mem_pkg.ALL; USE common_lib.common_pkg.ALL; USE tech_ddr_lib.tech_ddr_pkg.ALL; ENTITY ddrctrl_controller IS GENERIC ( g_tech_ddr : t_c_tech_ddr; g_stop_percentage : NATURAL := 50; g_nof_streams : NATURAL; -- 12 g_out_data_w : NATURAL; -- 14 g_wr_data_w : NATURAL; -- 168 g_rd_fifo_depth : NATURAL; -- 256 g_rd_data_w : NATURAL; -- 256 g_block_size : NATURAL; -- 1024 g_wr_fifo_uw_w : NATURAL; -- 8 g_rd_fifo_uw_w : NATURAL; -- 8 g_max_adr : NATURAL; -- 16128 g_burstsize : NATURAL; -- 64 g_last_burstsize : NATURAL; -- 18 g_adr_per_b : NATURAL; -- 299 g_bim : NATURAL -- 54 ); PORT ( clk : IN STD_LOGIC; rst : IN STD_LOGIC; -- ddrctrl_input inp_of : IN NATURAL; inp_sosi : IN t_dp_sosi; inp_adr : IN NATURAL; inp_bsn_adr : IN NATURAL; inp_data_stopped : IN STD_LOGIC; rst_ddrctrl_input : OUT STD_LOGIC; -- io_ddr dvr_mosi : OUT t_mem_ctlr_mosi; dvr_miso : IN t_mem_ctlr_miso; wr_sosi : OUT t_dp_sosi; wr_fifo_usedw : IN STD_LOGIC_VECTOR(g_wr_fifo_uw_w-1 DOWNTO 0); rd_fifo_usedw : IN STD_LOGIC_VECTOR(g_rd_fifo_uw_w-1 DOWNTO 0); -- ddrctrl_output outp_bsn : OUT STD_LOGIC_VECTOR(c_dp_stream_bsn_w-1 DOWNTO 0) := (OTHERS => '0'); -- ddrctrl_controller stop_in : IN STD_LOGIC; stop_out : OUT STD_LOGIC ); END ddrctrl_controller; ARCHITECTURE rtl OF ddrctrl_controller IS CONSTANT c_bitshift_w : NATURAL := ceil_log2(g_burstsize); -- bitshift to make sure there is only a burst start at a interval of c_burstsize. CONSTANT c_adr_w : NATURAL := func_tech_ddr_ctlr_address_w( g_tech_ddr ); -- the lengt of the address vector, for simulation this is smaller, otherwise the simulation would take to long, 27 CONSTANT c_pof_ma : NATURAL := (((g_max_adr*(100-g_stop_percentage))/100)/g_adr_per_b)*g_adr_per_b; CONSTANT c_zeros : STD_LOGIC_VECTOR(c_bitshift_w-1 DOWNTO 0) := (OTHERS => '0'); -- constant for reading CONSTANT c_rd_data_w : NATURAL := g_nof_streams*g_out_data_w; -- 168 CONSTANT c_rest : NATURAL := c_rd_data_w-(g_wr_data_w mod c_rd_data_w); -- 96 CONSTANT c_io_ddr_data_w : NATURAL := func_tech_ddr_ctlr_data_w(g_tech_ddr); -- 576 -- type for statemachine TYPE t_state IS (RESET, WAIT_FOR_SOP, START_WRITING, WRITING, SET_STOP, STOP_WRITING, LAST_WRITE_BURST, START_READING, READING, STOP_READING, IDLE); -- record for readability TYPE t_reg IS RECORD -- state of program state : t_state; started : STD_LOGIC; -- stopping signals ready_for_set_stop : STD_LOGIC; stop_adr : STD_LOGIC_VECTOR(c_adr_w-1 DOWNTO 0); last_adr_to_write_to : STD_LOGIC_VECTOR(c_adr_w-1 DOWNTO 0); stop_burstsize : NATURAL; stopped : STD_LOGIC; rst_ddrctrl_input : STD_LOGIC; -- writing signals wr_burst_en : STD_LOGIC; -- reading signals outp_bsn : STD_LOGIC_VECTOR(c_dp_stream_bsn_w-1 DOWNTO 0); read_adr : NATURAL; rd_burst_en : STD_LOGIC; -- output dvr_mosi : t_mem_ctlr_mosi; wr_sosi : t_dp_sosi; END RECORD; CONSTANT c_t_reg_init : t_reg := (RESET, '0', '0', TO_UVEC(g_max_adr, c_adr_w), (OTHERS => '0'), 0, '1', '1', '0', (OTHERS => '0'), 0, '0', c_mem_ctlr_mosi_rst, c_dp_sosi_init); -- signals for readability SIGNAL d_reg : t_reg := c_t_reg_init; SIGNAL q_reg : t_reg := c_t_reg_init; BEGIN q_reg <= d_reg WHEN rising_edge(clk); -- put the input data into c_v and fill the output vector from c_v p_state : PROCESS(q_reg, rst, inp_of, inp_sosi, inp_adr, inp_bsn_adr, inp_data_stopped, dvr_miso, rd_fifo_usedw, stop_in) VARIABLE v : t_reg := c_t_reg_init; BEGIN v := q_reg; v.wr_sosi := inp_sosi; CASE q_reg.state IS WHEN RESET => v := c_t_reg_init; v.dvr_mosi.burstbegin := '1'; v.dvr_mosi.burstsize(dvr_mosi.burstsize'length-1 DOWNTO 0) := (OTHERS => '0'); v.dvr_mosi.wr := '1'; v.wr_sosi.valid := '1'; WHEN WAIT_FOR_SOP => v.dvr_mosi.burstbegin := '0'; v.rst_ddrctrl_input := '0'; IF q_reg.started = '0' AND inp_sosi.eop = '1' THEN v.wr_sosi.valid := '1'; ELSIF inp_sosi.sop = '1' THEN v.state := WRITING; --START_WRITING; ELSE v.wr_sosi.valid := '0'; v.state := WAIT_FOR_SOP; END IF; WHEN START_WRITING => -- this state generates the first write burst. IF TO_UINT(wr_fifo_usedw) > g_burstsize AND dvr_miso.done = '1' AND v.wr_burst_en = '1' AND q_reg.dvr_mosi.burstbegin = '0' THEN v.dvr_mosi.address(c_adr_w-1 DOWNTO 0) := TO_UVEC(TO_UINT(q_reg.last_adr_to_write_to(c_adr_w-1 DOWNTO 0))+q_reg.stop_burstsize, c_adr_w); v.dvr_mosi.wr := '1'; v.dvr_mosi.rd := '0'; v.dvr_mosi.burstbegin := '1'; v.dvr_mosi.burstsize(dvr_mosi.burstsize'length-1 DOWNTO 0) := TO_UVEC(g_burstsize-q_reg.stop_burstsize, dvr_mosi.burstsize'length); v.wr_burst_en := '0'; v.wr_burst_en := '1'; v.state := WRITING; ELSE v.dvr_mosi.burstbegin := '0'; v.state := START_WRITING; END IF; WHEN WRITING => -- this state generates the rest of the write bursts, it also checks if there is a stop signal or if it needs to stop writing. IF TO_UINT(wr_fifo_usedw) > g_burstsize AND dvr_miso.done = '1' AND q_reg.wr_burst_en = '1' AND q_reg.dvr_mosi.burstbegin = '0' THEN v.dvr_mosi.burstbegin := '1'; v.wr_burst_en := '0'; IF inp_adr < g_burstsize-1 THEN v.dvr_mosi.address := TO_UVEC(g_max_adr-g_last_burstsize, dvr_mosi.address'length); v.dvr_mosi.burstsize := TO_UVEC(g_last_burstsize, dvr_mosi.burstsize'length); ELSE v.dvr_mosi.address := TO_UVEC(inp_adr-g_burstsize, dvr_mosi.address'length); v.dvr_mosi.address(c_bitshift_w-1 DOWNTO 0) := c_zeros(c_bitshift_w-1 DOWNTO 0); -- makes sure that a burst is only started on a multiple of g_burstsize v.dvr_mosi.burstsize := TO_UVEC(g_burstsize, dvr_mosi.burstsize'length); END IF; ELSE v.dvr_mosi.burstbegin := '0'; END IF; v.dvr_mosi.wr := '1'; v.dvr_mosi.rd := '0'; IF TO_UVEC(inp_adr, c_adr_w)(c_bitshift_w-1 DOWNTO 0) = c_zeros AND q_reg.dvr_mosi.burstbegin = '0'THEN v.wr_burst_en := '1'; END IF; IF stop_in = '1' THEN v.ready_for_set_stop := '1'; END IF; IF q_reg.ready_for_set_stop = '1' AND inp_sosi.eop = '1' THEN v.state := SET_STOP; ELSIF q_reg.stop_adr = TO_UVEC(inp_adr, c_adr_w) AND q_reg.stopped = '0' THEN v.state := STOP_WRITING; ELSE v.state := WRITING; END IF; WHEN SET_STOP => -- this state sets a stop address dependend on the g_stop_percentage. IF inp_adr+c_pof_ma >= g_max_adr THEN v.stop_adr(c_adr_w-1 DOWNTO 0) := TO_UVEC(inp_adr-c_pof_ma, c_adr_w)(c_adr_w-1 DOWNTO 0); ELSE v.stop_adr(c_adr_w-1 DOWNTO 0) := TO_UVEC(inp_adr+c_pof_ma, c_adr_w)(c_adr_w-1 DOWNTO 0); END IF; v.ready_for_set_stop := '0'; v.last_adr_to_write_to(c_adr_w-1 DOWNTO c_bitshift_w) := v.stop_adr(c_adr_w-1 DOWNTO c_bitshift_w); v.last_adr_to_write_to(c_bitshift_w-1 DOWNTO 0) := (OTHERS => '0'); v.stop_burstsize := TO_UINT(v.stop_adr(c_adr_w-1 DOWNTO 0))-TO_UINT(v.last_adr_to_write_to)+1; -- still a write cyle -- if adr mod g_burstsize = 0 -- this makes sure that only ones every 64 writes a writeburst is started. IF TO_UVEC(inp_adr, c_adr_w)(c_bitshift_w-1 DOWNTO 0) = c_zeros AND q_reg.dvr_mosi.burstbegin = '0'THEN v.wr_burst_en := '1'; END IF; IF dvr_miso.done = '1' AND q_reg.wr_burst_en = '1' THEN v.dvr_mosi.burstbegin := '1'; v.wr_burst_en := '0'; IF inp_adr < g_burstsize-1 THEN v.dvr_mosi.address := TO_UVEC(g_max_adr-g_last_burstsize, dvr_mosi.address'length); v.dvr_mosi.burstsize := TO_UVEC(g_last_burstsize, dvr_mosi.burstsize'length); ELSE v.dvr_mosi.address := TO_UVEC(inp_adr-g_burstsize, dvr_mosi.address'length); v.dvr_mosi.address(c_bitshift_w-1 DOWNTO 0) := c_zeros(c_bitshift_w-1 DOWNTO 0); -- makes sure that a burst is only started on a multiple of g_burstsize v.dvr_mosi.burstsize := TO_UVEC(g_burstsize, dvr_mosi.burstsize'length); END IF; ELSE v.dvr_mosi.burstbegin := '0'; END IF; v.dvr_mosi.wr := '1'; v.dvr_mosi.rd := '0'; IF q_reg.stop_adr = TO_UVEC(inp_adr, c_adr_w) AND q_reg.stopped = '0' THEN v.state := STOP_WRITING; ELSE v.state := WRITING; END IF; WHEN STOP_WRITING => -- this state stops the writing by generatign one last whole write burst which almost empties wr_fifo. v.wr_sosi.valid := '0'; v.dvr_mosi.burstbegin := '0'; v.stopped := '1'; v.stop_adr := TO_UVEC(g_max_adr, c_adr_w); -- wait until the write burst is finished IF inp_data_stopped = '0' THEN v.state := STOP_WRITING; ELSIF dvr_miso.done = '1' AND q_reg.dvr_mosi.burstbegin = '0' AND q_reg.wr_burst_en = '0' THEN v.state := LAST_WRITE_BURST; ELSE v.state := STOP_WRITING; END IF; -- still receiving write data. -- if adr mod g_burstsize = 0 -- this makes sure that only ones every 64 writes a writeburst is started. IF TO_UVEC(inp_adr, c_adr_w)(c_bitshift_w-1 DOWNTO 0) = c_zeros AND q_reg.dvr_mosi.burstbegin = '0'THEN v.wr_burst_en := '1'; END IF; IF dvr_miso.done = '1' AND q_reg.wr_burst_en = '1' THEN v.dvr_mosi.burstbegin := '1'; v.wr_burst_en := '0'; IF inp_adr < g_burstsize-1 THEN v.dvr_mosi.address := TO_UVEC(g_max_adr-g_last_burstsize, dvr_mosi.address'length); v.dvr_mosi.burstsize := TO_UVEC(g_last_burstsize, dvr_mosi.burstsize'length); ELSE v.dvr_mosi.address := TO_UVEC(inp_adr-g_burstsize, dvr_mosi.address'length); v.dvr_mosi.address(c_bitshift_w-1 DOWNTO 0) := c_zeros(c_bitshift_w-1 DOWNTO 0); -- makes sure that a burst is only started on a multiple of g_burstsize v.dvr_mosi.burstsize := TO_UVEC(g_burstsize, dvr_mosi.burstsize'length); END IF; ELSE v.dvr_mosi.burstbegin := '0'; END IF; v.dvr_mosi.wr := '1'; v.dvr_mosi.rd := '0'; WHEN LAST_WRITE_BURST => -- this state stops the writing by generatign one last write burst which empties wr_fifo. IF dvr_miso.done = '1' THEN v.dvr_mosi.burstbegin := '1'; v.dvr_mosi.address(c_adr_w-1 DOWNTO 0) := q_reg.last_adr_to_write_to(c_adr_w-1 DOWNTO 0); v.dvr_mosi.burstsize := TO_UVEC(q_reg.stop_burstsize, dvr_mosi.burstsize'length); v.state := START_READING; v.rd_burst_en := '1'; ELSE v.dvr_mosi.burstbegin := '0'; v.state := LAST_WRITE_BURST; END IF; v.dvr_mosi.wr := '1'; v.dvr_mosi.rd := '0'; WHEN START_READING => -- this state generates the first read burst, the size of this burst is dependend on the size of the last write burst. v.dvr_mosi.burstbegin := '0'; v.outp_bsn := TO_UVEC(TO_UINT(inp_sosi.bsn)-g_bim, c_dp_stream_bsn_w); IF dvr_miso.done = '1' AND v.rd_burst_en = '1' AND q_reg.dvr_mosi.burstbegin = '0' THEN v.dvr_mosi.burstbegin := '1'; v.dvr_mosi.burstsize(dvr_mosi.burstsize'length-1 DOWNTO 0) := TO_UVEC(g_burstsize-q_reg.stop_burstsize, dvr_mosi.burstsize'length); v.dvr_mosi.wr := '0'; v.dvr_mosi.rd := '1'; v.dvr_mosi.address(c_adr_w-1 DOWNTO 0) := TO_UVEC(TO_UINT(q_reg.last_adr_to_write_to(c_adr_w-1 DOWNTO 0))+q_reg.stop_burstsize, c_adr_w); v.rd_burst_en := '0'; v.read_adr := TO_UINT(q_reg.last_adr_to_write_to(c_adr_w-1 DOWNTO 0))+g_burstsize; END IF; -- makes sure the fifo is filled before asking for another rd request. to prevent 4 rd burst to happend directly after one another. IF dvr_miso.done = '0' AND q_reg.rd_burst_en = '0' THEN v.rd_burst_en := '1'; v.state := READING; ELSE v.state := START_READING; END IF; WHEN READING => -- rd_fifo needs a refil after rd_fifo_usedw <= 10 because of delays, if you wait until rd_fifo_usedw = 0 then you get an empty fifo which results in your outputs sosi.valid not being constatly valid. IF TO_UINT(rd_fifo_usedw) <= g_burstsize AND dvr_miso.done = '1' AND q_reg.rd_burst_en = '1' THEN v.dvr_mosi.wr := '0'; v.dvr_mosi.rd := '1'; v.dvr_mosi.burstbegin := '1'; v.rd_burst_en := '0'; IF q_reg.read_adr > g_max_adr-g_burstsize THEN v.dvr_mosi.address := TO_UVEC(q_reg.read_adr, dvr_mosi.address'length); v.dvr_mosi.burstsize := TO_UVEC(g_last_burstsize, dvr_mosi.burstsize'length); v.read_adr := 0; ELSE v.dvr_mosi.address := TO_UVEC(q_reg.read_adr, dvr_mosi.address'length); v.dvr_mosi.burstsize := TO_UVEC(g_burstsize, dvr_mosi.burstsize'length); v.read_adr := q_reg.read_adr+g_burstsize; END IF; ELSE v.dvr_mosi.burstbegin := '0'; END IF; -- makes sure the fifo is filled before asking for another rd request. to prevent 4 rd burst to happend directly after one another. IF dvr_miso.done = '0' THEN v.rd_burst_en := '1'; END IF; -- goes to STOP_reading when this read burst was from the burstblock before q_reg.stop_adr IF q_reg.last_adr_to_write_to(c_adr_w-1 DOWNTO 0) = TO_UVEC(q_reg.read_adr, c_adr_w) THEN v.state := STOP_READING; ELSE v.state := READING; END IF; WHEN STOP_READING => -- this is the last read burst, this make sure every data containing word in the memory has been read. IF TO_UINT(rd_fifo_usedw) <= g_burstsize AND dvr_miso.done = '1' AND q_reg.rd_burst_en = '1' THEN v.dvr_mosi.burstbegin := '1'; v.dvr_mosi.address(c_adr_w-1 DOWNTO 0) := q_reg.last_adr_to_write_to(c_adr_w-1 DOWNTO 0); v.dvr_mosi.burstsize := TO_UVEC(q_reg.stop_burstsize, dvr_mosi.burstsize'length); v.stopped := '0'; v.wr_sosi.valid := '0'; v.state := WAIT_FOR_SOP; v.wr_burst_en := '1'; v.rst_ddrctrl_input := '1'; ELSE v.dvr_mosi.burstbegin := '0'; v.state := STOP_READING; END IF; v.dvr_mosi.wr := '0'; v.dvr_mosi.rd := '1'; IF dvr_miso.done = '0' THEN v.rd_burst_en := '1'; END IF; WHEN IDLE => IF q_reg.started = '0' THEN v.wr_sosi.valid := '0'; END IF; -- the statemachine goes to Idle when its finished or when its waiting on other components. WHEN OTHERS => v := c_t_reg_init; END CASE; IF q_reg.state = RESET OR q_reg.state = IDLE THEN IF stop_in = '1' THEN v.ready_for_set_stop := '1'; ELSIF q_reg.ready_for_set_stop = '1' AND inp_sosi.eop = '1' THEN v.state := SET_STOP; ELSIF q_reg.stop_adr = TO_UVEC(inp_adr, c_adr_w) AND q_reg.stopped = '0' THEN v.state := STOP_WRITING; ELSIF v.stopped = '0' AND inp_sosi.valid = '1' AND q_reg.started = '1' THEN v.state := WRITING; ELSIF q_reg.stopped = '1' THEN v.state := STOP_READING; ELSE v.state := IDLE; END IF; END IF; IF rst = '1' THEN v.state := RESET; END IF; IF inp_sosi.eop = '1' THEN v.started := '1'; END IF; d_reg <= v; END PROCESS; -- fill outputs dvr_mosi <= q_reg.dvr_mosi; wr_sosi <= q_reg.wr_sosi; stop_out <= q_reg.stopped; outp_bsn <= q_reg.outp_bsn; rst_ddrctrl_input <= q_reg.rst_ddrctrl_input OR rst; END rtl;