Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ddrctrl_controller.vhd 13.82 KiB
-------------------------------------------------------------------------------
--
-- 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;
    g_out_data_w              : NATURAL;
    g_wr_data_w               : NATURAL;
    g_rd_fifo_depth           : NATURAL;
    g_rd_data_w               : NATURAL;
    g_block_size              : NATURAL;
    g_rd_fifo_uw_w            : NATURAL;
    g_max_adr                 : NATURAL;
    g_burstsize               : NATURAL;
    g_last_burstsize          : NATURAL
  );
  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_ds                    : 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;
    rd_fifo_usedw             : IN  STD_LOGIC_VECTOR(g_rd_fifo_uw_w-1 DOWNTO 0);

    -- ddrctrl_output
    outp_ds                   : OUT NATURAL;
    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_adr    : 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;
  CONSTANT  c_zeros           : STD_LOGIC_VECTOR(c_bitshift_adr-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_max_read_cnt    : NATURAL                                     := (g_max_adr+1)/g_burstsize;                     -- 256

  -- type for statemachine
  TYPE t_state IS (RESET, WRITING, SET_STOP, STOP_WRITING, 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
  stop_adr                    : STD_LOGIC_VECTOR(c_adr_w-1 DOWNTO 0);
  stopped                     : STD_LOGIC;
  rst_ddrctrl_input           : STD_LOGIC;

  -- writing signals
  need_burst                  : STD_LOGIC;

  -- reading signals
  outp_ds                     : NATURAL;
  outp_bsn                    : STD_LOGIC_VECTOR(c_dp_stream_bsn_w-1 DOWNTO 0);
  read_cnt                    : 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', TO_UVEC(g_max_adr, c_adr_w), '1', '1', '0', 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_ds, 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;


    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';



    WHEN WRITING =>
      -- 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_adr-1 DOWNTO 0) = c_zeros AND q_reg.dvr_mosi.burstbegin = '0'THEN
        v.need_burst            := '1';
      END IF;
      IF dvr_miso.done = '1' AND q_reg.need_burst = '1' THEN
        v.dvr_mosi.burstbegin   := '1';
        v.need_burst            := '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_adr-1 DOWNTO 0) := c_zeros(c_bitshift_adr-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';
      v.wr_sosi                 := inp_sosi;


    WHEN SET_STOP =>
      --setting 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 c_bitshift_adr) := TO_UVEC(inp_adr-c_pof_ma, c_adr_w)(c_adr_w-1 DOWNTO c_bitshift_adr);
      ELSE
        v.stop_adr(c_adr_w-1 DOWNTO c_bitshift_adr) := TO_UVEC(inp_adr+c_pof_ma, c_adr_w)(c_adr_w-1 DOWNTO c_bitshift_adr);
      END IF;
      v.stop_adr(c_bitshift_adr-1 DOWNTO 0)         := c_zeros;

      -- 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_adr-1 DOWNTO 0) = c_zeros AND q_reg.dvr_mosi.burstbegin = '0'THEN
        v.need_burst            := '1';
      END IF;
      IF dvr_miso.done = '1' AND q_reg.need_burst = '1' THEN
        v.dvr_mosi.burstbegin   := '1';
        v.need_burst            := '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_adr-1 DOWNTO 0) := c_zeros(c_bitshift_adr-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';
      v.wr_sosi                 := inp_sosi;

    WHEN STOP_WRITING =>
      v.dvr_mosi.burstbegin := '0';
      v.stopped                               := '1';
      -- 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.need_burst = '0' THEN
        v.wr_sosi.valid                         := '0';
        v.state                                 := START_READING;
      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_adr-1 DOWNTO 0) = c_zeros AND q_reg.dvr_mosi.burstbegin = '0'THEN
        v.need_burst            := '1';
      END IF;
      IF dvr_miso.done = '1' AND q_reg.need_burst = '1' THEN
        v.dvr_mosi.burstbegin   := '1';
        v.need_burst            := '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_adr-1 DOWNTO 0) := c_zeros(c_bitshift_adr-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';
      v.wr_sosi                 := inp_sosi;


    WHEN START_READING =>
      v.rd_burst_en                             := '1';
      v.dvr_mosi.wr                             := '0';
      v.dvr_mosi.rd                             := '1';
      v.outp_ds                                 :=  inp_ds;

      FOR I IN 0 TO inp_bsn_adr+(g_max_adr-TO_UINT(q_reg.stop_adr)) LOOP     -- takes a while  WRONG, wil be fixed after L2SDP-705, 706 and 70
        IF v.outp_ds-c_rest <= 0 THEN
          v.outp_ds := v.outp_ds+c_rd_data_w-c_rest;
        ELSE
          v.outp_ds := v.outp_ds-c_rest;
        END IF;
      END LOOP;
      v.outp_bsn := TO_UVEC(TO_UINT(inp_sosi.bsn), c_dp_stream_bsn_w); -- WRONG, wil be fixed after L2SDP-705, 706 and 707
      v.state := READING;


    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) <= 10 AND dvr_miso.done = '1' AND q_reg.rd_burst_en = '1' AND dvr_miso.done = '1' THEN
        IF TO_UINT(q_reg.stop_adr(c_adr_w-1 DOWNTO 0))+g_burstsize*q_reg.read_cnt >= g_max_adr THEN
          v.dvr_mosi.address(c_adr_w-1 DOWNTO 0)  := TO_UVEC((TO_UINT(q_reg.stop_adr(c_adr_w-1 DOWNTO 0))+g_burstsize*q_reg.read_cnt)-g_max_adr-1, c_adr_w);
        ELSE
          v.dvr_mosi.address(c_adr_w-1 DOWNTO 0)  := TO_UVEC(TO_UINT(q_reg.stop_adr(c_adr_w-1 DOWNTO 0))+g_burstsize*q_reg.read_cnt, c_adr_w);
        END IF;
        v.dvr_mosi.burstbegin := '1';
        v.read_cnt := v.read_cnt+1;
        v.rd_burst_en := '0';
      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 TO_UINT(rd_fifo_usedw) = 11 THEN
        v.rd_burst_en := '1';
      END IF;

      IF q_reg.read_cnt >= c_max_read_cnt THEN
        v.state := STOP_READING;
      ELSE
        v.state := READING;
      END IF;



    WHEN STOP_READING =>
    IF dvr_miso.done = '1' THEN
      v.rst_ddrctrl_input := '0';
      v.stopped := '0';
      v.state := IDLE;
    ELSE
      v.state := STOP_READING;
    END IF;




    WHEN IDLE =>
      v.wr_sosi.valid := '0';
      -- 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 = WRITING OR q_reg.state = SET_STOP OR q_reg.state = IDLE THEN
      IF stop_in = '1' THEN
        v.state := SET_STOP;
      ELSIF v.stop_adr = TO_UVEC(inp_adr, c_adr_w) AND v.stop_adr(c_bitshift_adr-1 DOWNTO 0) = c_zeros(c_bitshift_adr-1 DOWNTO 0) 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;
        v.wr_sosi := inp_sosi;
      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';
      v.wr_sosi.valid := '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;
  outp_ds           <= q_reg.outp_ds;
  rst_ddrctrl_input <= q_reg.rst_ddrctrl_input OR rst;

END rtl;