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

entity fip_urv is
  generic(
    g_IRAM_LOG_SIZE : natural := 12;
    g_DRAM_LOG_SIZE : natural := 12);
  port(
    clk_sys_i : in  std_logic;
    rst_n_i   : in  std_logic;
    restart_i : in  std_logic;
    dwb_o     : out t_wishbone_master_out;
    dwb_i     : in  t_wishbone_master_in;

    fic0_ready_i   : in  std_logic;
    fic0_clk_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_en_o     : out std_logic;
    leds_o       : out std_logic_vector(4 downto 1)
  );
end fip_urv;

architecture arch of fip_urv is

  impure function f_x_to_zero (x : std_logic_vector) return std_logic_vector
  is
    variable tmp : std_logic_vector(x'length-1 downto 0);
    variable found_undef : boolean := false;
  begin
-- synthesis translate_off
    for i in 0 to x'length-1 loop
      if( x(i) = 'U' or x(i) = 'Z' or x(i) = 'X' ) then
        found_undef := true;
      end if;

      if x(i) = '1' or x(i) = 'H' then
        tmp(i) := '1';
      else
        tmp(i) := '0';
      end if;
    end loop;
    return tmp;

    if found_undef then
      report "Undefined data value read from memory" severity warning;
    end if;

-- synthesis translate_on
    return x;
  end function f_x_to_zero;

  signal cpu_rst        : std_logic;
  signal cpu_rst_d      : std_logic;

  signal im_addr,  im_waddr : std_logic_vector(31 downto 0);
  signal im_data,  im_wdata : std_logic_vector(31 downto 0);
  signal im_valid, im_write : std_logic;

  signal dm_addr, dm_data_s, dm_data_l                            : std_logic_vector(31 downto 0);
  signal dm_data_select                                           : std_logic_vector(3 downto 0);
  signal dm_load, dm_store, dm_load_done, dm_store_done : std_logic;

  signal reg_dm_addr, reg_dm_data_s : std_logic_vector(31 downto 0);
  signal reg_dm_data_select         : std_logic_vector(3 downto 0);
  signal reg_dm_load, reg_dm_store  : std_logic;

  signal dm_cycle_in_progress, reg_dm_is_wishbone : std_logic;

  signal dm_mem_rdata, dm_wb_rdata : std_logic_vector(31 downto 0);
  signal dm_wb_write, dm_select_wb : std_logic;
  signal reg_dm_data_write             : std_logic;

  signal dwb_out         : t_wishbone_master_out;
begin

  dwb_o <= dwb_out;

  U_cpu_core : urv_cpu
    generic map (
      g_timer_frequency => 0,
      g_with_hw_debug => 0,
      g_with_hw_mul => 0,
      g_with_hw_div => 0
    )
    port map (
      clk_i            => clk_sys_i,
      rst_i            => cpu_rst,
      irq_i            => '0',
      im_addr_o        => im_addr,
      im_data_i        => im_data,
      im_valid_i       => im_valid,
      dm_addr_o        => dm_addr,
      dm_data_s_o      => dm_data_s,
      dm_data_l_i      => dm_data_l,
      dm_data_select_o => dm_data_select,
      dm_store_o       => dm_store,
      dm_load_o        => dm_load,
      dm_load_done_i   => dm_load_done,
      dm_store_done_i  => dm_store_done,
      dbg_force_i      => '0',
      dbg_enabled_o    => open,
      dbg_insn_i       => x"0000_0000",
      dbg_insn_set_i   => '0',
      dbg_insn_ready_o => open,
      dbg_mbx_data_i   => x"0000_0000",
      dbg_mbx_write_i  => '0',
      dbg_mbx_data_o   => open);

  process (clk_sys_i)
  begin
    if rising_edge(clk_sys_i) then
      if rst_n_i = '0' then
        reg_dm_load <= '0';
        reg_dm_store <= '0';
      else
        reg_dm_load <= dm_load;
        reg_dm_store <= dm_store;
        reg_dm_addr <= dm_addr;
        reg_dm_data_s <= dm_data_s;
        reg_dm_data_select <= dm_data_select;
      end if;
    end if;
  end process;

  p_rom: process (clk_sys_i)
  is
    constant IRAM_WSIZE : natural := 2 ** (g_IRAM_LOG_SIZE - 2);
    variable iram : t_ram32_type(0 to IRAM_WSIZE - 1);
    -- := f_load_mem32_from_file ("../../../sw/fip_urv/fip_dbg.ram", IRAM_WSIZE, True);
  begin
    if rising_edge(clk_sys_i) then
      if cpu_rst = '1' then
        if im_write = '1' then
          iram (to_integer(unsigned(im_waddr(g_IRAM_LOG_SIZE - 1 downto 2)))) := im_wdata;
        end if;
      else
        im_data <= iram (to_integer(unsigned(im_addr(g_IRAM_LOG_SIZE - 1 downto 2))));
      end if;
    end if;
  end process;

  -- 1st MByte of the mem is the RAM
  reg_dm_is_wishbone <= '1' when reg_dm_addr(31 downto 20) /= x"000" else '0';

  reg_dm_data_write <= not reg_dm_is_wishbone and reg_dm_store;
  dm_data_l     <= dm_wb_rdata when dm_select_wb = '1' else dm_mem_rdata;

  p_ram: process (clk_sys_i)
  is
    variable dram : t_ram32_type (2**(g_DRAM_LOG_SIZE - 2) - 1 downto 0);
    variable addr : natural range dram'range;
  begin
    if rising_edge(clk_sys_i) then
      addr := to_integer(unsigned(reg_dm_addr(g_DRAM_LOG_SIZE - 1 downto 2)));
      dm_mem_rdata <= dram(addr);
      if reg_dm_data_write = '1' then
        for i in 0 to 3 loop
          if reg_dm_data_select (i) = '1' then
            dram(addr)(8*i + 7 downto 8*i) := reg_dm_data_s(8*i + 7 downto 8*i);
          end if;
        end loop;
      end if;
    end if;
  end process;

  -- Wishbone bus arbitration / internal RAM access
  p_wishbone_master : process(clk_sys_i)
  begin
    if rising_edge(clk_sys_i) then
      if rst_n_i = '0' then
        dwb_out.cyc          <= '0';
        dwb_out.stb          <= '0';
        dwb_out.adr          <= (others => '0');
        dwb_out.sel          <= x"0";
        dwb_out.we           <= '0';
        dwb_out.dat          <= (others => '0');
        dm_cycle_in_progress <= '0';
        dm_load_done         <= '0';
        dm_store_done        <= '0';
        dm_select_wb         <= '0';
      else

        if dm_cycle_in_progress = '0' then
          if reg_dm_is_wishbone = '0' then
            --  Internal access
            if reg_dm_store = '1' then
              dm_load_done  <= '0';
              dm_store_done <= '1';
              dm_select_wb  <= '0';
            elsif reg_dm_load = '1' then
              dm_load_done  <= '1';
              dm_store_done <= '0';
              dm_select_wb  <= '0';
            else
              dm_store_done <= '0';
              dm_load_done  <= '0';
              dm_select_wb  <= '0';
            end if;
          else
            --  Wishbone access
            if reg_dm_load = '1' or reg_dm_store = '1' then
              dwb_out.cyc          <= '1';
              dwb_out.stb          <= '1';
              dwb_out.we           <= reg_dm_store;
              dm_wb_write          <= reg_dm_store;
              dwb_out.adr          <= reg_dm_addr;
              dwb_out.dat          <= reg_dm_data_s;
              dwb_out.sel          <= reg_dm_data_select;
              dm_load_done         <= '0';
              dm_store_done        <= '0';
              dm_cycle_in_progress <= '1';
            else
              dm_store_done        <= '0';
              dm_load_done         <= '0';
              dm_cycle_in_progress <= '0';
            end if;
          end if;
        else
          --  Wishbone transfer in progress.
          if dwb_i.stall = '0' then
            dwb_out.stb <= '0';
          end if;

          if dwb_i.ack = '1' then
            if dm_wb_write = '0' then
              dm_wb_rdata  <= f_x_to_zero(dwb_i.dat);
              dm_select_wb <= '1';
              dm_load_done <= '1';
            else
              dm_store_done <= '1';
              dm_select_wb  <= '0';
            end if;

            dm_cycle_in_progress <= '0';
            dwb_out.cyc          <= '0';
          end if;
        end if;
      end if;
    end if;
  end process p_wishbone_master;

  p_im_valid : process(clk_sys_i)
  begin
    if rising_edge(clk_sys_i) then
      if cpu_rst = '1' then
        im_valid  <= '0';
        cpu_rst_d <= '1';
      else
        cpu_rst_d <= cpu_rst;
        im_valid  <= (not cpu_rst_d);
      end if;
    end if;
  end process p_im_valid;

  b_init: block
    type t_main_state is (init, envm_ready, envm_ready2, read_word, wait_word, done);
    signal main_state : t_main_state;

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

    signal hpms_start, hpms_done : std_logic;
    signal fic_start, fic_done : std_logic;
    signal hpms_data : std_logic_vector (31 downto 0);

    signal envm_addr : std_logic_vector (31 downto 0);
    signal envm_len : unsigned (31 downto 0);

    signal dest_addr : std_logic_vector (31 downto 0);

    signal leds : std_logic_vector(1 downto 0);
  begin
    fic0_hburst_o <= "000";
    fic0_hmastlock_o <= '0';
    fic0_hprot_o <= "0011";
    fic0_hsize_o <= "010";
    fic0_htrans_o <= htrans;
    fic0_haddr_o <= haddr;

    leds_o(2 downto 1) <= "11";
    leds_o(4 downto 3) <= leds;

    -- Main FSM: copy eNVM to IRAM
    process (clk_sys_i)
    begin
      if rising_edge (clk_sys_i) then
        hpms_start <= '0';
        im_write <= '0';
        cpu_rst <= '1';
        led_en_o <= '1';
        fic0_hwrite_o <= '0';

        if rst_n_i = '0' then
          main_state <= init;
          leds <= "11";
        else
          case main_state is
            when init =>
              leds <= "11";
              if fic0_ready_i = '1' then
                -- poll eNVM ready bit from status register
                haddr <= x"6008_0120";
                fic0_hwrite_o <= '0';
                hpms_start <= '1';

                main_state <= envm_ready;
              end if;

--             main_state <= done;

            when envm_ready =>
              leds <= "10";
              hpms_start <= '1';
              if hpms_done = '1' then
                hpms_start <= '0';
                main_state <= envm_ready2;
              end if;

            when envm_ready2 =>
              if hpms_done = '0' then
                if hpms_data (0) = '1' then
                  --  eNVM is ready.
                  main_state <= read_word;

                  --  Copy the whole IRAM
                  envm_addr <= x"6000_0000";
                  envm_len <= to_unsigned(2**g_IRAM_LOG_SIZE, 32);
                  dest_addr <= x"0000_0000";
                else
                  --  eNVM not ready
                  --  poll again.
                  hpms_start <= '1';
                  main_state <= envm_ready;
                end if;
              end if;

            when read_word =>
              leds <= "00";
              if hpms_done = '0' then
                haddr <= envm_addr;
                hpms_start <= '1';

                envm_addr <= std_logic_vector(unsigned(envm_addr) + 4);
                envm_len <= envm_len - 4;

                main_state <= wait_word;
              end if;

            when wait_word =>
              leds <= "00";
              hpms_start <= '1';
              if hpms_done = '1' then
                --  Write the word.
                im_wdata <= hpms_data;
                im_write <= '1';
                im_waddr <= dest_addr;

                dest_addr <= std_logic_vector(unsigned(dest_addr) + 4);

                if envm_len = 0 then
                  main_state <= done;
                else
                  main_state <= read_word;
                end if;
              end if;
          
            when done =>
              led_en_o <= '0';
              leds <= "00";
              cpu_rst <= '0';

              if restart_i = '1' then
                cpu_rst <= '1';
                main_state <= init;
              end if;
          end case;
        end if;
      end if;
    end process;

    inst_sync_start: entity work.gc_sync
      port map (
        clk_i => fic0_clk_i,
        rst_n_a_i => rst_n_i,
        d_i => hpms_start,
        q_o => fic_start
      );
    inst_sync_done: entity work.gc_sync
      port map (
        clk_i => clk_sys_i,
        rst_n_a_i => rst_n_i,
        d_i => fic_done,
        q_o => hpms_done
      );

    --  AHB FSM (in FIC clock domain)
    process (fic0_clk_i)
    begin
      if rising_edge (fic0_clk_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 <= wait_idle;
                fic_done <= '1';
                hpms_data <= fic0_hrdata_i;
              end if;
            when wait_idle =>
              if fic_start = '0' then
                fic_done <= '0';
                ahb_state <= idle;
              else
                fic_done <= '1';
              end if;
          end case;
        end if;
      end if;
    end process;
  end block;
end arch;
