------------------------------------------------------------------------------
--  This file is a part of the GRLIB VHDL IP LIBRARY
--  Copyright (C) 2003 - 2008, Gaisler Research
--  Copyright (C) 2008 - 2014, Aeroflex Gaisler
--  Copyright (C) 2015 - 2017, Cobham Gaisler
--
--  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 2 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, write to the Free Software
--  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
-----------------------------------------------------------------------------
-- Entity:      srctrl
-- File:        srctrl.vhd
-- Author:      Jiri Gaisler - Gaisler Research
-- Modified:    Marko Isomaki - Gaisler Research
-- Description: 32-bit SRAM memory controller with read-modify-write
--              Supports also 64-bit AHB read/write accesses
------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
library grlib;
use grlib.amba.all;
use grlib.stdlib.all;
library gaisler;
use grlib.devices.all;
use gaisler.memctrl.all;

entity srctrl is
  generic (
    hindex  : integer := 0;
    romaddr : integer := 0;
    rommask : integer := 16#ff0#;
    ramaddr : integer := 16#400#;
    rammask : integer := 16#ff0#;
    ioaddr  : integer := 16#200#;
    iomask  : integer := 16#ff0#;
    ramws   : integer := 0;
    romws   : integer := 2;
    iows    : integer := 2;
    rmw     : integer := 0;             -- read-modify-write enable
    prom8en : integer := 0;
    oepol   : integer := 0;
    srbanks : integer range 1 to 5  := 1;
    banksz  : integer range 0 to 13 := 13;
    romasel : integer range 0 to 28 := 19
  );
  port (
    rst     : in  std_ulogic;
    clk     : in  std_ulogic;
    ahbsi   : in  ahb_slv_in_type;
    ahbso   : out ahb_slv_out_type;
    sri     : in  memory_in_type;
    sro     : out memory_out_type;
    sdo     : out sdctrl_out_type
  );
end;

architecture rtl of srctrl is

constant VERSION : amba_version_type := 0;
constant hconfig : ahb_config_type := (
  0 => ahb_device_reg(VENDOR_GAISLER, GAISLER_SRCTRL, 0, VERSION, 0),
  4 => ahb_membar(romaddr, '1', '1', rommask),
  5 => ahb_membar(ramaddr, '1', '1', rammask),
  6 => ahb_membar(ioaddr, '0', '0', iomask),
  others => zero32);

type srcycletype is (idle, read1, read2, write1, write2, write3, rmw1, rmw2, rmw3);
type prom8cycletype is (idle, read1, read2);

function byteswap (rdata, wdata : std_logic_vector(31 downto 0); addr, size : std_logic_vector) return std_logic_vector is
variable tmp : std_logic_vector(31 downto 0);
variable a : std_logic_vector(1 downto 0);
begin
  tmp := rdata; a := addr(1 downto 0);
  if size(0) = '0' then
    case a is
    when "00" => tmp(31 downto 24) := wdata(31 downto 24);
    when "01" => tmp(23 downto 16) := wdata(23 downto 16);
    when "10" => tmp(15 downto 8) := wdata(15 downto 8);
    when others => tmp(7 downto 0) := wdata(7 downto 0);
    end case;
  else
    if addr(1) = '0' then tmp(31 downto 16) := wdata(31 downto 16);
    else tmp(15 downto 0) := wdata(15 downto 0); end if;
  end if;
  return(tmp);
end;

-- local registers

type reg_type is record
  hready        : std_ulogic;
  hsel          : std_ulogic;
  hmbsel        : std_logic_vector(0 to 2);
  bdrive        : std_ulogic;
  nbdrive       : std_ulogic;
  srstate       : srcycletype;
  haddr         : std_logic_vector(31 downto 0);
  hrdata        : std_logic_vector(63 downto 0);
  hwdata        : std_logic_vector(63 downto 0);
  hwrite        : std_ulogic;
  htrans        : std_logic_vector(1 downto 0);
  hburst        : std_logic_vector(2 downto 0);
  hresp         : std_logic_vector(1 downto 0);
  size          : std_logic_vector(1 downto 0);
  read          : std_ulogic;
  oen           : std_ulogic;
  ramsn         : std_ulogic;
  romsn         : std_ulogic;
  vramsn        : std_logic_vector(4 downto 0);
  ramoen        : std_logic_vector(4 downto 0);
  vromsn        : std_logic_vector(1 downto 0);
  writen        : std_ulogic;
  wen           : std_logic_vector(3 downto 0);
  mben          : std_logic_vector(3 downto 0);
  ws            : std_logic_vector(3 downto 0);
  iosn          : std_ulogic;
-- 8-bit prom access
  pr8state      : prom8cycletype;
  data8         : std_logic_vector(23 downto 0);
  ready8        : std_ulogic;
  bwidth        : std_logic_vector(1 downto 0);
end record;

signal r, ri : reg_type;

-- vectored output enable to data pads
signal rbdrive, ribdrive : std_logic_vector(31 downto 0);
attribute syn_preserve : boolean;
attribute syn_preserve of rbdrive : signal is true;

begin

  ctrl : process(rst, ahbsi, r, sri, rbdrive)
  variable v       : reg_type;          -- local variables for registers
  variable dqm     : std_logic_vector(3 downto 0);
  variable adec    : std_logic_vector(1 downto 0);
  variable rams    : std_logic_vector(4 downto 0);
  variable roms    : std_logic_vector(1 downto 0);
  variable haddr   : std_logic_vector(31 downto 0);
  variable hrdata  : std_logic_vector(63 downto 0);
  variable hsize   : std_logic_vector(1 downto 0);
  variable hwrite  : std_ulogic;
  variable htrans  : std_logic_vector(1 downto 0);
  variable vramws, vromws, viows : std_logic_vector(3 downto 0);
-- 8-bit prom access
  variable romsn   : std_ulogic;
  variable bdrive  : std_ulogic;
  variable oen     : std_ulogic;
  variable writen  : std_ulogic;
  variable hready  : std_ulogic;
  variable ws      : std_logic_vector(3 downto 0);
  variable prom8sel : std_ulogic;
  variable vbdrive : std_logic_vector(31 downto 0);
  variable sbdrive  : std_ulogic;
  begin

-- Variable default settings to avoid latches

    v := r; v.hresp := HRESP_OKAY; v.hrdata(31 downto 0) := sri.data; 
    hrdata := r.hrdata;
    vramws := conv_std_logic_vector(ramws, 4); vbdrive := rbdrive;
    vromws := conv_std_logic_vector(romws, 4);
    viows := conv_std_logic_vector(iows, 4);
    v.bwidth := sri.bwidth;
    if (prom8en = 1) and (r.bwidth = "00") then prom8sel := '1';
    else prom8sel := '0'; end if;

    if (ahbsi.hready = '1') then
      if (ahbsi.hsel(hindex) and ahbsi.htrans(1)) = '1' then
        v.size := ahbsi.hsize(1 downto 0); v.hwrite := ahbsi.hwrite;
        v.htrans := ahbsi.htrans; v.hburst := ahbsi.hburst;
        v.hsel := '1'; v.hmbsel := ahbsi.hmbsel(0 to 2);
        v.haddr := ahbsi.haddr; v.hready := '0';
      else
        v.hsel := '0';
      end if;
    end if;

    if (r.hsel = '1') and (ahbsi.hready = '0') then
      haddr := r.haddr;  hsize := r.size;
      htrans := r.htrans; hwrite := r.hwrite;
    else
      haddr := ahbsi.haddr;  hsize := ahbsi.hsize(1 downto 0);
      htrans := ahbsi.htrans; hwrite := ahbsi.hwrite;
    end if;

-- chip-select decoding
   adec := haddr(banksz+14 downto banksz+13);

   rams := '0' & decode(adec);

   case srbanks is
   when 1 => rams := "00001";
   when 2 => rams := "000" & (rams(3 downto 2) or rams(1 downto 0));
   when others => null;
   end case;
   roms := haddr(romasel) & not haddr(romasel);

-- generate write strobes

    if rmw = 1 then dqm := "0000"; else
      case r.size is
      when "00" =>
        case r.haddr(1 downto 0) is
        when "00" => dqm := "1110";
        when "01" => dqm := "1101";
        when "10" => dqm := "1011";
        when others => dqm := "0111";
        end case;
      when "01" =>
        if r.haddr(1) = '0' then dqm := "1100"; else  dqm := "0011"; end if;
      when others => dqm := "0000";
      end case;
    end if;

-- main FSM

    case r.srstate is
    when idle =>
      if (v.hsel = '1') and not
        (((v.ramsn or r.romsn) = '0') or ((v.romsn or r.ramsn) = '0')) and not
        ((v.hmbsel(0) and not hwrite and prom8sel) = '1' and prom8en = 1)
      then
        v.hready := '0';
        v.ramsn := not v.hmbsel(1); v.romsn := not v.hmbsel(0);
        v.iosn := not v.hmbsel(2);
        v.read := not hwrite;
        if hwrite = '1' then
          if (rmw = 1) and (hsize(1) = '0') and (v.hmbsel(1) = '1') then
            v.srstate := rmw1; v.read := '1';
          else v.srstate := write1; end if;
        elsif ahbsi.htrans = "10" then v.srstate := read1;
        else v.srstate := read2; end if;
        v.oen := not v.read;
      else
        v.ramsn := '1'; v.romsn := '1'; v.bdrive := '1'; v.oen := '1';
         v.iosn := '1';
      end if;
      if v.romsn = '0' then v.ws := vromws;
      elsif v.iosn = '0' then v.ws := viows;
      else v.ws := vramws; end if;
    when read1 =>
      v.srstate := read2;
      v.hrdata(63 downto 32) := r.hrdata(31 downto 0);
    when read2 =>
      v.ws := r.ws -1; v.oen := '0';
      if r.ws = "0000" then
        if (r.size /= "11") or (r.haddr(2) = '1') or (AHBDW = 32) then 
          v.srstate := idle; v.hready := '1'; v.haddr := ahbsi.haddr;
          v.ramsn := not (ahbsi.hmbsel(1) and ahbsi.htrans(1));
          v.romsn := not (ahbsi.hmbsel(0) and ahbsi.htrans(1));
          v.oen := not (ahbsi.hsel(hindex) and ahbsi.htrans(1) and not ahbsi.hwrite);
        else
          v.srstate := read1; v.haddr(2) := '1';
          if v.romsn = '0' then v.ws := vromws;
          elsif v.iosn = '0' then v.ws := viows;
          else v.ws := vramws; end if;
        end if;
      end if;
    when write1 =>
      if r.romsn = '0' then v.ws := vromws;
      elsif v.iosn = '0' then v.ws := viows;
      else v.ws := vramws; end if;
      v.srstate := write2; v.bdrive := '0'; v.wen := dqm; v.writen := '0';
      v.hwdata(31 downto 0) := ahbsi.hwdata(31 downto 0);
      if not ((r.size = "11") and (r.haddr(2) = '1')) then
        v.hwdata(63 downto 32) := ahbsi.hwdata(63 mod AHBDW downto 32 mod AHBDW);
      end if;
    when write2 =>
      if r.ws = "0000" then
        if (r.size /= "11") or (r.haddr(2) = '1') or (AHBDW = 32) then 
          v.srstate := idle; v.bdrive := '1'; v.hready := '1';
        else
          v.srstate := write3; 
        end if;
        v.wen := "1111"; v.writen := '1';
      end if;
      v.ws := r.ws -1;
    when write3 =>
        v.haddr(2) := '1'; v.hwdata(63 downto 32) := r.hwdata(31 downto 0);
        v.srstate := write1; 
    when rmw1 =>
      if (rmw = 1) then v.oen := '0';
        v.srstate := rmw2;
        v.hwdata(31 downto 0) := ahbsi.hwdata(31 downto 0);
        v.hwdata(63 downto 32) := ahbsi.hwdata(63 mod AHBDW downto 32 mod AHBDW);
      end if;
    when rmw2 =>
      if (rmw = 1) then
        v.ws := r.ws -1;
        if r.ws = "0000" then v.oen := '1'; v.srstate := rmw3; end if;
      end if;
    when rmw3 =>
      if (rmw = 1) then
        v.hwdata(63 downto 32) := byteswap(r.hrdata(31 downto 0), r.hwdata(63 downto 32), r.haddr, r.size);
        v.srstate := write2; v.bdrive := '0'; v.wen := dqm; v.writen := '0';
      end if;
      if r.romsn = '0' then v.ws := vromws; else v.ws := vramws; end if;
    end case;

    if (ahbsi.hready and ahbsi.hsel(hindex) ) = '1' then
      if ahbsi.htrans(1) = '0' then v.hready := '1'; end if;
    end if;

-- 8-bit PROM access FSM
    if prom8en = 1 then
      hready := '0'; ws := v.ws; v.ready8 := '0';
      bdrive := '1'; oen := '1'; writen := '1'; romsn := '1';

      if r.ready8 = '1' then
        v.data8 := r.data8(15 downto 0) & r.hrdata(31 downto 24);
        case r.size is
        when "00" => hrdata(31 downto 0) :=  r.hrdata(31 downto 24) &
          r.hrdata(31 downto 24) &  r.hrdata(31 downto 24) &  r.hrdata(31 downto 24);
        when "01" => hrdata(31 downto 0) := r.data8(7 downto 0) &  r.hrdata(31 downto 24) &
                r.data8(7 downto 0) & r.hrdata(31 downto 24);
        when others => hrdata(31 downto 0) := r.data8 & r.hrdata(31 downto 24);
        end case;
      end if;

      case r.pr8state is
        when idle =>
          if ( (v.hsel and v.hmbsel(0) and not hwrite and prom8sel) = '1')
          then
            romsn := '0'; v.pr8state := read1; oen := '0';
          end if;
        when read1 =>
          oen := '0'; romsn := '0'; v.pr8state := read2; ws := vromws;
        when read2 =>
          oen := '0'; ws := r.ws - 1; romsn := '0';
          if r.ws = "0000" then
            v.haddr(1 downto 0) := r.haddr(1 downto 0) + 1;
            if (r.size = "00") or ((r.size = "01") and  (r.haddr(0) = '1'))
              or r.haddr(1 downto 0) = "11"
            then
              hready := '1'; v.pr8state := idle; oen := '1';
            else
              v.pr8state := read1;
            end if;
            v.ready8 := '1';
          end if;
        when others =>
          v.pr8state := idle;
      end case;

      v.romsn := v.romsn and romsn; v.bdrive := v.bdrive and bdrive;
      v.oen := v.oen and oen; v.writen := v.writen and writen;
      v.hready := v.hready or hready; v.ws := ws;
    end if;

    if (v.oen or v.ramsn) = '0' then v.ramoen := not rams;
    else v.ramoen := (others => '1'); end if;

    if v.romsn = '0' then v.vromsn := not roms;
    else v.vromsn := (others => '1'); end if;

    if v.ramsn = '0' then v.vramsn := not rams;
    else v.vramsn := (others => '1'); end if;

    if v.read = '1' then v.mben := "0000"; else v.mben := v.wen; end if;

    v.nbdrive := not v.bdrive;

    if oepol = 1 then sbdrive := r.nbdrive; vbdrive := (others => v.nbdrive);
    else sbdrive := r.bdrive; vbdrive := (others => v.bdrive);  end if;

    if (r.size /= "11") or (AHBDW = 32) then hrdata(63 downto 32) := hrdata(31 downto 0); end if;
-- reset

    if rst = '0' then
      v.srstate := idle; v.hsel := '0'; v.writen := '1';
      v.wen := (others => '1'); v.hready := '1'; v.read := '1';
      v.ws := (others => '0');
      if prom8en = 1 then v.pr8state := idle; end if;
    end if;

    ribdrive <= vbdrive;
    ri <= v;

    sro.address  <= r.haddr;
    sro.bdrive   <= (others => sbdrive);
    sro.vbdrive  <= rbdrive;
    sro.ramsn    <= "111" & r.vramsn;
    sro.ramoen   <= "111" & r.ramoen;
    sro.romsn    <= "111111" & r.vromsn;
    sro.iosn     <= r.iosn;
    sro.wrn      <= r.wen;
    sro.oen      <= r.oen;
    sro.read     <= r.read;
    sro.data <= r.hwdata(63 downto 32);
    sro.writen   <= r.writen;
    sro.ramn     <= r.ramsn;
    sro.romn     <= r.romsn;
    ahbso.hready  <= r.hready;
    ahbso.hresp   <= r.hresp;
    ahbso.hrdata  <= ahbdrivedata(hrdata);
    ahbso.hconfig <= hconfig;
    ahbso.hirq    <= (others => '0');
    ahbso.hindex  <= hindex;
    ahbso.hsplit  <= (others => '0');

  end process;

  sdo <= sdctrl_out_none;
  sro.mben  <= r.mben;
  sro.sdram_en    <= '0';
  sro.rs_edac_en  <= '0';
  sro.ce          <= '0';
  sro.sddata   <= (others => '0');
  sro.svbdrive <= (others => '0');
  sro.sa       <= (others => '0');
  sro.cb       <= (others => '0');
  sro.scb      <= (others => '0');
  sro.vcdrive  <= (others => '0');
  sro.svcdrive <= (others => '0');
  sro.scb      <= (others => '0');

  regs : process(clk,rst)
  begin

    if rising_edge(clk) then r <= ri; rbdrive <= ribdrive; end if;

    if rst = '0' then
      r.ramsn  <= '1';
      r.romsn  <= '1'; r.oen    <= '1';
      r.bdrive <= '1'; r.nbdrive <= '0';
      r.vramsn <= (others => '1'); r.vromsn <= (others => '1');
      if oepol  = 0 then rbdrive <= (others => '1');
      else rbdrive <= (others => '0'); end if;
    end if;
  end process;

-- pragma translate_off
  bootmsg : report_version
  generic map ("srctrl" & tost(hindex) &
        ": 32-bit PROM/SRAM controller rev " & tost(VERSION));
-- pragma translate_on

end;

