--------------------------------------------------------------------------------
-- CERN BE-CO-HT
-- Mock Turtle
-- https://gitlab.cern.ch/coht/mockturtle
--------------------------------------------------------------------------------
--
-- unit name:   mt_urv_wrapper
--
-- description: A small wrapper for the URV encompassing the internal RAM and
-- access to the RAM through CPU CSR register block.
--
--------------------------------------------------------------------------------
-- Copyright CERN 2014-2018
--------------------------------------------------------------------------------
-- Copyright and related rights are licensed under the Solderpad Hardware
-- 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://solderpad.org/licenses/SHL-2.0.
-- Unless required by applicable law or agreed to in writing, software,
-- hardware and materials distributed under this 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.
--------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.genram_pkg.all;
--use work.memory_loader_pkg.all;
use work.wishbone_pkg.all;
use work.urv_pkg.all;
use work.secded_32b_pkg.all;

entity diot_hydra2_core is
  generic(
    g_IRAM_LOG_SIZE : natural := 12;
    g_DRAM_LOG_SIZE : natural := 12);
  port(
    clk_sys_i : in  std_logic;
    clk_cpu_i : in  std_logic_vector(3 downto 1);
    rst_n_i   : in  std_logic;

    --  CPU fault (double ecc error, double divergence)
    cpu_flt_o : out std_logic;

    --  CPU self-reset (recovery)
    cpu_rst_o : out std_logic;
    iram_copy_i : in  std_logic;
    dram_clear_i : in  std_logic;
    iram_err_i : in  std_logic;
    iram_src_i   : in  std_logic;
    iram_page_i  : in  std_logic_vector(7 downto 0);
    src_page_i  : in  std_logic_vector(7 downto 0);

    dwb_o     : out t_wishbone_master_out;
    dwb_i     : in  t_wishbone_master_in;

    envm_i    : in  t_wishbone_slave_in;
    envm_o    : out t_wishbone_slave_out;

    spi_en_o  : out std_logic;
    spi_sck_o : out std_logic;
    spi_csn_o : out std_logic;
    spi_mosi_o  : out std_logic;
    spi_miso_i  : in  std_logic;

    fic0_ready_i   : in  std_logic;

    fic0_haddr_o     : out std_logic_vector(31 downto 0);
    fic0_hburst_o    : out std_logic_vector(2 downto 0);
    fic0_hmastlock_o : out std_logic;
    fic0_hprot_o     : out std_logic_vector(3 downto 0);
    fic0_hsize_o     : out std_logic_vector(2 downto 0);
    fic0_htrans_o    : out std_logic_vector(1 downto 0);
    fic0_hwdata_o    : out std_logic_vector(31 downto 0);
    fic0_hwrite_o    : out std_logic;
    fic0_hrdata_i    : in  std_logic_vector(31 downto 0);
    fic0_hready_i    : in  std_logic;
    fic0_hresp_i     : in  std_logic_vector(1 downto 0);

    led_o     : out std_logic
  );
end diot_hydra2_core;

architecture arch of diot_hydra2_core is

  signal cpu_rst_n : std_logic;

  signal im_waddr : std_logic_vector(g_IRAM_LOG_SIZE - 1 downto 2);
  signal im_wdata : std_logic_vector(31 downto 0);
  signal im_wecc, im_force_ecc : std_logic_vector(6 downto 0);
  signal im_write : std_logic;
  signal dram_clr : std_logic;
  signal cpu_flt : std_logic;

  --  Last reset was due to a fault
  signal rst_flt : std_logic;
begin

  inst_hydra: entity work.hydra_core
    generic map (
      g_IRAM_LOG_SIZE => g_IRAM_LOG_SIZE,
      g_DRAM_LOG_SIZE => g_DRAM_LOG_SIZE
    )
    port map (
      clk_sys_i        => clk_sys_i,
      clk_cpu_i        => clk_cpu_i,
      rst_n_i          => rst_n_i,
      cpu_rst_n_i      => cpu_rst_n,
      dwb_o            => dwb_o,
      dwb_i            => dwb_i,
      iram_addr_i      => im_waddr,
      iram_we_i        => im_write,
      iram_data_i      => im_wdata,
      iram_ecc_i       => im_wecc,
      dram_clr_i       => dram_clr,
      cpu_flt_o        => cpu_flt,

      cpu_rst_o => open,
      reset_cause_o => open,
      nbr_iram_ecc_corr_o => open,
      nbr_iram_scrub_corr_o => open,
      nbr_iram_scrub_uncorr_last_o => open,
      nbr_iram_scrub_cycle_o => open,
      nbr_cpu_fault_o => open,
      iram_scrub_en_o => open,
      nbr_dram_ecc_corr_o => open,
      nbr_dram_scrub_corr_o => open,
      nbr_dram_scrub_uncorr_last_o => open,
      nbr_dram_scrub_cycle_o => open,
      dram_scrub_en_o => open
    );

  im_force_ecc <= b"000_0000" when (iram_err_i = '0'
                                    or im_waddr (g_IRAM_LOG_SIZE - 1 downto 4) /= (g_IRAM_LOG_SIZE - 1 downto 4 => '1'))
    else b"000_0001" when im_waddr(2) = '0'
    else b"000_0011";
--  im_force_ecc <= b"000_0001" when im_waddr(8 downto 2) = b"0_1000_10" else b"000_0000";  -- BUG!

  b_init: block
    type t_main_state is (init_delay, init, envm_ready, read_word, wait_word, done,
                          spi_send_cmd_l, spi_send_cmd_h,
                          spi_recv_word_l, spi_recv_word_h,
                          spi_write_word);

    signal main_state : t_main_state;

    --  About 1ms delay
    constant delay : std_logic_vector(27 downto 0) := x"003_0000"
    -- pragma translate_off
    xor x"003_0002"
    -- pragma translate_on
    ;

    type t_ahb_state is (idle, addr_phase, wait_rsp);
    signal ahb_state : t_ahb_state;
    signal htrans : std_logic_vector(1 downto 0);

    signal fic_start, fic_done, fic_wr : std_logic;
    signal fic_rdata, fic_wdata : std_logic_vector (31 downto 0);

    signal fic_addr : std_logic_vector (31 downto 0);

    signal spi_dat : std_logic_vector(39 downto 0);
    signal spi_cnt : natural range 0 to 63;

    signal envm_ip : std_logic;
  begin
    -- Main FSM: copy eNVM to IRAM
    p_init: process (clk_sys_i)
    begin
      if rising_edge (clk_sys_i) then
        fic_start <= '0';
        im_write <= '0';
        cpu_rst_n <= '0';
        cpu_rst_o <= '0';
        envm_o.ack <= '0';
        envm_o.stall <= '0';

        if rst_n_i = '0' then
          main_state <= init_delay;
          fic_addr <= x"0000_0000";
          led_o <= '1';
          dram_clr <= '0';
          rst_flt <= '0';
          spi_en_o <= '0';
          spi_sck_o <= '0';
          envm_ip <= '0';
          fic_wr <= '0';
        else
          case main_state is
            when init_delay =>
              spi_sck_o <= '0';
              spi_mosi_o <= '0';
              --  fic_addr is used as a counter
              if fic_addr (31 downto 4) = delay then
                main_state <= init;
              else
                fic_addr <= std_logic_vector(unsigned(fic_addr) + 4);
              end if;
            when init =>
              led_o <= '0';
              dram_clr <= dram_clear_i or rst_flt;
              im_waddr <= iram_page_i(g_IRAM_LOG_SIZE - 1 - 8 downto 0) & b"0000_00";

              --  Select source: eflash or spi.
              if iram_src_i = '0' then
                -- poll eNVM ready bit from status register
                fic_addr <= x"6008_0120";
                fic_start <= '1';
                fic_wr <= '0';
                main_state <= envm_ready;
              else
                spi_en_o <= '1';
                spi_csn_o <= '0';
                spi_cnt <= 32;
                spi_dat <= x"03_00" & src_page_i & x"00_00";
                main_state <= spi_send_cmd_l;
              end if;

            when envm_ready =>
              if fic_done = '1' then
                if fic_rdata (0) = '1' then
                  --  eNVM is ready.
                  if iram_copy_i = '1' or rst_flt = '1' then
                    main_state <= read_word;
                  else
                    main_state <= done;
                  end if;

                  --  Copy the whole IRAM
                  fic_addr <= x"6000" & src_page_i & x"00";
                else
                  --  eNVM not ready
                  --  poll again.
                  fic_start <= '1';
                  fic_wr <= '0';
                  main_state <= envm_ready;
                end if;
              end if;

            when read_word =>
              if im_write = '1' then
                --  If we have just written a word, increment it.
                im_waddr <= std_logic_vector(unsigned(im_waddr) + 1);
              end if;

              if fic_done = '0' then
                --  Read the word
                fic_start <= '1';
                fic_wr <= '0';
                main_state <= wait_word;
              end if;

            when wait_word =>
              if fic_done = '1' then
                --  Write the word.
                im_wdata <= fic_rdata;
                im_wecc <= f_calc_ecc (fic_rdata) xor im_force_ecc;
                im_write <= '1';

                --  We have just read the word from envm, increment the address.
                fic_addr <= std_logic_vector(unsigned(fic_addr) + 4);

                if im_waddr = (im_waddr'range => '1') then
                  --  Finish
                  fic_addr <= x"0000_0000";
                  main_state <= done;
                else
                  main_state <= read_word;
                end if;
              end if;

            when spi_send_cmd_l =>
              spi_mosi_o <= spi_dat(spi_dat'left);
              spi_dat <= spi_dat(spi_dat'left - 1 downto 0) & '0';
              spi_sck_o <= '0';
              spi_cnt <= spi_cnt - 1;
              main_state <= spi_send_cmd_h;
            when spi_send_cmd_h =>
              spi_sck_o <= '1';
              if spi_cnt = 0 then
                main_state <= spi_recv_word_l;
                spi_cnt <= 40;
              else
                main_state <= spi_send_cmd_l;
              end if;
            when spi_recv_word_l =>
              if im_write = '1' then
                im_waddr <= std_logic_vector(unsigned(im_waddr) + 1);
              end if;
              spi_sck_o <= '0';
              spi_mosi_o <= '0';
              spi_cnt <= spi_cnt - 1;
              main_state <= spi_recv_word_h;
            when spi_recv_word_h =>
              spi_dat <= spi_dat(spi_dat'left - 1 downto 0) & spi_miso_i;
              spi_sck_o <= '1';
              if spi_cnt = 0 then
                --  Write the word.
                main_state <= spi_write_word;
              else
                main_state <= spi_recv_word_l;
              end if;
            when spi_write_word =>
              spi_cnt <= 40;
              im_wdata <= spi_dat(15 downto 8) & spi_dat(23 downto 16) & spi_dat(31 downto 24) & spi_dat(39 downto 32);
              im_wecc <= spi_dat(6 downto 0) xor im_force_ecc;
              im_write <= '1';

              if im_waddr = (im_waddr'range => '1') then
                --  Finished.
                main_state <= done;
                fic_addr <= x"0000_0000";
                spi_en_o <= '0';
                spi_sck_o <= '0';
                spi_mosi_o <= '0';
              else
                main_state <= spi_recv_word_l;
              end if;

            when done =>
              if envm_ip = '0' and (envm_i.cyc and envm_i.stb) = '1' then
                --  User access to eNVM
                envm_ip <= '1';
                fic_addr <= x"600" & envm_i.adr(19 downto 0);
                envm_o.stall <= '1';
                fic_wr <= envm_i.we;
                fic_wdata <= envm_i.dat;
                fic_start <= '1'; --  pulse
              elsif envm_ip = '1' then
                --  eNVM access in progress.
                envm_o.stall <= '1';
                envm_o.ack <= fic_done;
                if (envm_i.cyc and envm_i.stb) = '0' then
                  --  End of access.
                  envm_ip <= '0';
                end if;
              else
                --  Let the led blinks.
                fic_addr <= std_logic_vector(unsigned(fic_addr) + 4);
                led_o <= fic_addr(27);
              end if;

              cpu_rst_n <= '1';
              dram_clr <= '0';

              if cpu_flt = '1' then
                cpu_rst_n <= '0';
                cpu_rst_o <= '1';
                main_state <= init;
                rst_flt <= cpu_flt;
              end if;
          end case;
        end if;
      end if;
    end process;

    envm_o.err <= '0';
    envm_o.rty <= '0';
    envm_o.dat <= fic_rdata;

    fic0_hburst_o <= "000";
    fic0_hmastlock_o <= '0';
    fic0_hprot_o <= "0011";
    fic0_hsize_o <= "010"; --  Always 32b
    fic0_htrans_o <= htrans;
    fic0_haddr_o <= fic_addr;
    fic0_hwrite_o <= fic_wr;
    fic0_hwdata_o <= fic_wdata;

    --  FIC FSM
    --  See UG0448-V8 12.3 Timing Diagrams
    process (clk_sys_i)
    begin
      if rising_edge (clk_sys_i) then
        htrans <= "00";
        fic_done <= '0';

        if rst_n_i = '0' then
          ahb_state <= idle;
        else
          case ahb_state is
            when idle =>
              if fic_start = '1' then
                htrans <= "10";  --  Non seq
                ahb_state <= addr_phase;
              end if;
            when addr_phase =>
              if fic0_hready_i = '1' then
                ahb_state <= wait_rsp;
              else
                --  Was not ready.
                htrans <= "10";  --  Non seq
              end if;
            when wait_rsp =>
              if fic0_hready_i = '1' then
                ahb_state <= idle;
                fic_done <= '1';
                fic_rdata <= fic0_hrdata_i;
              end if;
          end case;
        end if;
      end if;
    end process;
  end block;
end arch;
