------------------------------------------------------------------------------
--  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 - 2019, 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:      mmu_acache
-- File:        mmu_acache.vhd
-- Author:      Jiri Gaisler - Gaisler Research
-- Description: Interface module between (MMU,I/D cache controllers) and Amba AHB
------------------------------------------------------------------------------  

library ieee;
use ieee.std_logic_1164.all;
library grlib;
use grlib.config_types.all;
use grlib.config.all;
use grlib.amba.all;
use grlib.stdlib.all;
use grlib.devices.all;
library gaisler;
use gaisler.libiu.all;
use gaisler.libcache.all;
use gaisler.leon3.all;
use gaisler.mmuconfig.all;
use gaisler.mmuiface.all;

entity mmu_acache is
  generic (
    hindex    : integer range 0 to NAHBMST-1  := 0;
    ilinesize : integer range 4 to 8 := 4;
    cached    : integer := 0;
    clk2x     : integer := 0;
    scantest  : integer := 0
    );
  port (
    rst    : in  std_logic;
    clk    : in  std_logic;
    mcii   : in  memory_ic_in_type;
    mcio   : out memory_ic_out_type;
    mcdi   : in  memory_dc_in_type;
    mcdo   : out memory_dc_out_type;
    mcmmi  : in  memory_mm_in_type;
    mcmmo  : out memory_mm_out_type;
    ahbi   : in  ahb_mst_in_type;
    ahbo   : out ahb_mst_out_type;
    ahbso  : in  ahb_slv_out_vector;
    hclken : in  std_ulogic
  );


end;

architecture rtl of mmu_acache is

  constant REGAHBOUT: boolean := (clk2x /= 0);

  type reg_type is record -- cache control register type
     bg     :  std_logic;                     -- bus grant
     bo      : std_logic_vector(1 downto 0);  -- bus owner
     ba      : std_logic;                     -- bus active
     lb      : std_ulogic;                    -- last burst cycle
     retry   : std_logic;                     -- retry/split pending
     retry2  : std_ulogic;                    -- retry/split pending
     werr    : std_logic;                     -- write error
     hlocken : std_ulogic;                    -- ready to perform locked transaction
     hcache  : std_logic;                     -- cacheable access
     nba     : std_ulogic;
     nbo     : std_logic_vector(1 downto 0);  -- bus owner
  end record;

  type reg2_type is record
     reqmsk  : std_logic_vector(2 downto 0);
     hclken2 : std_ulogic;
  end record;

  type regout_type is record
    haddr: std_logic_vector(31 downto 0);
    htrans: std_logic_vector(1 downto 0);
    hwrite: std_ulogic;
    hlock: std_ulogic;
    hsize: std_logic_vector(2 downto 0);
    hburst: std_logic_vector(2 downto 0);
    hwdata: std_logic_vector(31 downto 0);
    hbusreq: std_ulogic;
    hprot: std_logic_vector(3 downto 0);
  end record;

  constant RESET_ALL : boolean := GRLIB_CONFIG_ARRAY(grlib_sync_reset_enable_all) = 1;
  constant RRES : reg_type := (
    bg      => '0',
    bo      => (others => '0'),
    ba      => '0',
    lb      => '0',
    retry   => '0',
    retry2  => '0',
    werr    => '0',
    hlocken => '0',
    hcache  => '0',
    nba     => '0',
    nbo     => (others => '0')
    );
  constant R2RES : reg2_type := (
    reqmsk => (others => '0'), hclken2 => '0'
    );
  constant RORES : regout_type := (
    haddr => (others => '0'),
    htrans => (others => '0'),
    hwrite => '0',
    hlock => '0',
    hsize => "000",
    hburst => "000",
    hwdata => x"00000000",
    hbusreq => '0',
    hprot => "1100"
    );

  function L3DI return integer is
  begin
    return GAISLER_LEON3;
  end function L3DI;

  constant hconfig : ahb_config_type := (
    0 => ahb_device_reg ( VENDOR_GAISLER, L3DI, 0, LEON3_VERSION, 0),
    others => zero32);

  constant ctbl : std_logic_vector(15 downto 0) := conv_std_logic_vector(cached, 16);
  function dec_fixed(haddr : std_logic_vector(3 downto 0);
                     cached : integer)
    return std_ulogic is
  begin
    if (cached /= 0) then return ctbl(conv_integer(haddr(3 downto 0)));
    else return('1'); end if;
  end;

  signal r, rin : reg_type;
  signal r2, r2in : reg2_type;
  signal ro, roin : regout_type;

begin

  comb : process(ahbi, r, rst, mcii, mcdi, mcmmi, ahbso, hclken, r2, ro)
    variable v, delr : reg_type;
    variable v2, delr2 : reg2_type;
    variable vo : regout_type;
    variable haddr   : std_logic_vector(31 downto 0);   -- address bus
    variable htrans  : std_logic_vector(1 downto 0);    -- transfer type 
    variable hwrite  : std_logic;                       -- read/write
    variable hlock   : std_logic;                       -- bus lock
    variable hsize   : std_logic_vector(2 downto 0);    -- transfer size
    variable hburst  : std_logic_vector(2 downto 0);    -- burst type
    variable hwdata  : std_logic_vector(31 downto 0);   -- write data
    variable hbusreq : std_logic;   -- bus request
    variable hlock_masked : std_logic;
    variable iready, dready, mmready : std_logic;   
    variable igrant, dgrant, mmgrant : std_logic;   
    variable iretry, dretry, mmretry : std_logic;   
    variable ihcache, dhcache, mmhcache, dec_hcache : std_logic;   
    variable imexc, dmexc, mmmexc : std_logic;
    variable dreq : std_logic;
    variable nbo : std_logic_vector(1 downto 0);
    variable su, nb, bo_icache : std_ulogic;
    variable scanen : std_ulogic;
    variable vreqmsk: std_ulogic;
    variable burst : std_ulogic;
    variable tmp1,tmp2: std_ulogic;
  begin

    -- initialisation

    htrans := HTRANS_IDLE;
    v := r;  v.werr := '0'; v2 := r2;
    iready := '0'; dready := '0'; mmready := '0';
    igrant := '0'; dgrant := '0'; mmgrant := '0'; 
    imexc := '0'; dmexc := '0'; mmmexc := '0'; hlock := '0';
    iretry := '0'; dretry := '0'; mmretry := '0';
    ihcache := '0'; dhcache := '0'; mmhcache := '0'; su := '0';
    hlock_masked := '0';
    if (r.bo = "00") then bo_icache := '1'; else bo_icache := '0'; end if;
    
    haddr := (others => '0');
    hwrite := '0';
    hsize := (others => '0');
    hlock := '0'; 
    hburst := (others => '0'); 
    if ahbi.hready = '1' then v.lb := '0'; end if;
    v.retry2 := (r.retry or r.retry2) and not (r.ba and not r.retry);
    vreqmsk := orv(r2.reqmsk);

    -- generate AHB signals

    dreq := mcdi.req;
    hwdata := mcdi.data;
    hbusreq := '0';

    if (mcii.req = '1') and ((clk2x = 0) or (r2.reqmsk(2) = '1')) and (r.hlocken = '0') and
      not (( ((r.ba and dreq) = '1') and (r.bo = "01")) or
           ( ((r.ba and mcmmi.req) = '1') and (r.bo = "10"))) then
      nbo := "00";
      hbusreq := '1'; burst := mcii.burst;
      htrans := HTRANS_NONSEQ;
    elsif (dreq = '1') and ((clk2x = 0) or (r2.reqmsk(1) = '1')) and 
      not (( ((r.ba and mcii.req) = '1') and (r.bo = "00")) or
           ( ((r.ba and mcmmi.req) = '1') and (r.bo = "10"))) then
      nbo := "01";
      hbusreq := '1'; burst := mcdi.burst;
      if (not mcdi.lock or r.hlocken) = '1' then htrans := HTRANS_NONSEQ; end if;
    elsif (mcmmi.req = '1') and ((clk2x = 0) or (r2.reqmsk(0) = '1')) and (r.hlocken = '0') and
      not (( ((r.ba and mcii.req) = '1') and (r.bo = "00")) or
           ( ((r.ba and dreq) = '1') and (r.bo = "01"))) then
      nbo := "10";
      hbusreq := '1'; burst := '0';
      htrans := HTRANS_NONSEQ;
    else
      nbo := "11"; burst := '0';
    end if;

    -- dont change bus master if we have started driving htrans
    if r.nba = '1' then
      nbo := r.nbo; hbusreq := '1'; htrans := HTRANS_NONSEQ;
    end if;

    -- dont change bus master on retry
    if (r.retry2 and not r.ba) = '1' then 
      nbo := r.bo; hbusreq := '1'; htrans := HTRANS_NONSEQ;
    end if;

    dec_hcache := ahb_slv_dec_cache(mcdi.address, ahbso, cached);
    
    if nbo = "10" then
      haddr := mcmmi.address; hwrite := not mcmmi.read; hsize := '0' & mcmmi.size;
      hlock := mcmmi.lock;
      htrans := HTRANS_NONSEQ; hburst := HBURST_SINGLE; 
      if (mcmmi.req and r.bg and ahbi.hready and not r.retry) = '1' 
      then mmgrant := '1'; v.hcache := dec_fixed(haddr(31 downto 28), cached); end if; 
    elsif nbo = "00" then
      haddr := mcii.address; hwrite := '0'; hsize := HSIZE_WORD; hlock := '0';
      su := mcii.su;
      if ((mcii.req and r.ba) = '1')  and (r.bo = "00") and ((not r.retry) = '1') then
        htrans := HTRANS_SEQ; haddr(4 downto 2) := haddr(4 downto 2) +1;
        if (((ilinesize = 4) and haddr(3 downto 2) = "10")
          or ((ilinesize = 8) and haddr(4 downto 2) = "110")) and (ahbi.hready = '1')
        then v.lb := '1'; end if;
      end if;
      if mcii.burst = '1' then hburst := HBURST_INCR; 
      else hburst := HBURST_SINGLE; end if;
      if (mcii.req and r.bg and ahbi.hready and not r.retry) = '1' 
      then igrant := '1'; v.hcache := dec_fixed(haddr(31 downto 28), cached); end if; 
    elsif nbo = "01" then
      haddr := mcdi.address; hwrite := not mcdi.read; hsize := '0' & mcdi.size;
      hlock := mcdi.lock; 
      if mcdi.asi /= "1010" then su := '1'; else su := '0'; end if;  --ASI_UDATA
      if mcdi.burst = '1' then hburst := HBURST_INCR; 
      else hburst := HBURST_SINGLE; end if;
      if ((dreq and r.ba) = '1') and (r.bo = "01") and ((not r.retry) = '1') then 
        htrans := HTRANS_SEQ; haddr(4 downto 2) := haddr(4 downto 2) +1;
        hburst := HBURST_INCR; 
      end if;
      if (dreq and r.bg and ahbi.hready and not r.retry) = '1' then
        dgrant := (not mcdi.lock or r.hlocken) or (r.retry2 and (not r.bo(1) and r.bo(0)));
        v.hcache := dec_hcache;
      end if;
    end if;

    if (hclken = '1') or (clk2x = 0) then
      if (r.ba = '1') and ((ahbi.hresp = HRESP_RETRY) or (ahbi.hresp = HRESP_SPLIT))
      then v.retry := not ahbi.hready; else v.retry := '0'; end if;
    end if;
      
    if r.retry = '1' then htrans := HTRANS_IDLE; end if;

    if r.bo = "10" then
      hwdata := mcmmi.data;
      if r.ba = '1' then
        mmhcache := r.hcache;
        if ahbi.hready = '1' then
          case ahbi.hresp is
          when HRESP_OKAY => mmready := '1'; 
          when HRESP_RETRY | HRESP_SPLIT=> mmretry := '1'; 
          when others => mmready := '1'; mmmexc := '1'; v.werr := not mcmmi.read;
          end case;
        end if;
      end if;
    elsif r.bo = "00" then
      if r.ba = '1' then
        ihcache := r.hcache;
        if ahbi.hready = '1' then
          case ahbi.hresp is
          when HRESP_OKAY => iready := '1'; 
          when HRESP_RETRY | HRESP_SPLIT=> iretry := '1'; 
          when others => iready := '1'; imexc := '1';
          end case;
        end if;
      end if;
    elsif r.bo = "01" then
      if r.ba = '1' then
        dhcache := r.hcache;
        if ahbi.hready = '1' then
          case ahbi.hresp is
          when HRESP_OKAY => dready := '1';
          when HRESP_RETRY | HRESP_SPLIT=> dretry := '1'; 
          when others => dready := '1'; dmexc := '1'; v.werr := not mcdi.read;
          end case;
        end if;
      end if;
      hlock := mcdi.lock or ((r.retry or (r.retry2 and not r.ba)) and r.hlocken);
    end if;

    if nbo = "01" and ((hsize = "011") or ((mcdi.read and mcdi.cache) = '1')) then 
      hsize := "010";
    end if;

    if (r.bo = "01") and (hlock = '1') then nbo := "01"; end if;
    if ahbi.hready = '1' then
      if r.retry = '0' then v.bo := nbo; end if;
      v.bg := ahbi.hgrant(hindex);
      if (htrans = HTRANS_NONSEQ) or (htrans = HTRANS_SEQ) then
        v.ba := r.bg;
      else v.ba := '0'; end if;
      v.hlocken := hlock and ahbi.hgrant(hindex);
      if (clk2x /= 0) then 
        igrant := igrant and vreqmsk;
        dgrant := dgrant and vreqmsk;
        mmgrant := mmgrant and vreqmsk;
        if (r.bo = nbo) then v.ba := v.ba and vreqmsk; end if;
      end if;
    end if;

    hlock_masked := hlock or mcdi.lock_hold;

    if hburst = HBURST_SINGLE then nb := '1'; else nb := '0'; end if;

    v.nbo := nbo; v.nba := orv(htrans) and not v.ba;

    if (clk2x /= 0) then
      v2.hclken2 := hclken;
      if hclken = '1' then
        v2.reqmsk := mcii.req & mcdi.req & mcmmi.req;
        if (clk2x > 8) and (r2.hclken2 = '1') then v2.reqmsk := "111"; end if;
      end if;      
    end if;
                                                
    delr := v;
    
    -- reset operation

    if (not RESET_ALL) and (rst = '0') then
      v.bg := '0'; v.bo := "00"; v.ba := '0'; v.retry := '0'; v.werr := '0'; v.lb := '0';
      v.hcache := '0'; v.hlocken := '0'; v.nba := '0'; v.nbo := "00";
      v.retry2 := '0';
    end if;

    -- drive ports
    
    ahbo.haddr   <= haddr ;
    ahbo.htrans  <= htrans;
--    ahbo.hbusreq <= hbusreq and not r.lb and not ((((not bo_icache) and r.ba) or nb) and r.bg);
--    ahbo.hbusreq <= hbusreq and not r.lb and not((not burst) and r.bg);
    ahbo.hbusreq <= hbusreq and (not r.lb or orv(nbo)) and (burst or not r.bg);
    ahbo.hwdata  <= ahbdrivedata(hwdata);
    ahbo.hlock   <= hlock_masked;
    ahbo.hwrite  <= hwrite;
    ahbo.hsize   <= hsize;
    ahbo.hburst  <= hburst;
    ahbo.hindex  <= hindex;
    if nbo = "00" then ahbo.hprot <= "11" & su & '0';
    else ahbo.hprot <= "11" & su & '1'; end if;

    mcio.grant   <= igrant;
    mcio.ready   <= iready;
    mcio.mexc    <= imexc;
    mcio.retry   <= iretry;
    mcio.cache   <= ihcache;
    mcdo.grant   <= dgrant;
    mcdo.ready   <= dready;
    mcdo.mexc    <= dmexc;
    mcdo.retry   <= dretry;
    mcdo.werr    <= r.werr;
    mcdo.cache   <= dhcache;
    mcdo.ba      <= r.ba;
    mcdo.bg      <= r.bg and not v.bo(1);

    mcmmo.grant   <= mmgrant;
    mcmmo.ready   <= mmready;
    mcmmo.mexc    <= mmmexc;
    mcmmo.retry   <= mmretry;
    mcmmo.werr    <= r.werr;
    mcmmo.cache   <= mmhcache;

    rin <= v; r2in <= v2;

    --------------------------------------------------------------------------
    -- "retiming" of AHB outputs to make them registered
    -- delr assigned to v before reset logic
    delr2 := v2;
    vo := RORES;
    if REGAHBOUT then

      ahbo.haddr <= ro.haddr;
      ahbo.htrans <= ro.htrans;
      ahbo.hbusreq <= ro.hbusreq;
      ahbo.hwdata <= ahbdrivedata(ro.hwdata);
      ahbo.hlock <= ro.hlock;
      ahbo.hwrite <= ro.hwrite;
      ahbo.hsize <= ro.hsize;
      ahbo.hburst <= ro.hburst;
      ahbo.hprot <= ro.hprot;

      -- initialisation
      htrans := HTRANS_IDLE;
      hlock := '0';
      su := '0';

      haddr := (others => '0');
      hwrite := '0';
      hsize := (others => '0');
      hlock := '0';
      hburst := (others => '0');

      -- generate AHB signals

      dreq := mcdi.next_req;
      hwdata := mcdi.next_data;
      hbusreq := '0';

      if (mcii.next_req = '1') and ((clk2x = 0) or (delr2.reqmsk(2) = '1')) and (delr.hlocken = '0') and
        not (( ((delr.ba and dreq) = '1') and (delr.bo = "01")) or
             ( ((delr.ba and mcmmi.next_req) = '1') and (delr.bo = "10"))) then
        nbo := "00";
        hbusreq := '1'; burst := mcii.next_burst;
        htrans := HTRANS_NONSEQ;
      elsif (dreq = '1') and ((clk2x = 0) or (delr2.reqmsk(1) = '1')) and
        not (( ((delr.ba and mcii.next_req) = '1') and (delr.bo = "00")) or
             ( ((delr.ba and mcmmi.next_req) = '1') and (delr.bo = "10"))) then
        nbo := "01";
        hbusreq := '1'; burst := mcdi.next_burst;
        if (not mcdi.next_lock or delr.hlocken) = '1' then htrans := HTRANS_NONSEQ; end if;
      elsif (mcmmi.next_req = '1') and ((clk2x = 0) or (delr2.reqmsk(0) = '1')) and (delr.hlocken = '0') and
        not (( ((delr.ba and mcii.next_req) = '1') and (delr.bo = "00")) or
             ( ((delr.ba and dreq) = '1') and (delr.bo = "01"))) then
        nbo := "10";
        hbusreq := '1'; burst := '0';
        htrans := HTRANS_NONSEQ;
      else
        nbo := "11"; burst := '0';
      end if;

      -- dont change bus master if we have started driving htrans
      if delr.nba = '1' then
        nbo := delr.nbo; hbusreq := '1'; htrans := HTRANS_NONSEQ;
      end if;

      -- dont change bus master on retry
      if (delr.retry2 and not delr.ba) = '1' then
        nbo := delr.bo; hbusreq := '1'; htrans := HTRANS_NONSEQ;
      end if;

      if nbo = "10" then
        haddr := mcmmi.next_address; hwrite := not mcmmi.next_read; hsize := '0' & mcmmi.next_size;
        hlock := mcmmi.next_lock;
        htrans := HTRANS_NONSEQ; hburst := HBURST_SINGLE;
      elsif nbo = "00" then
        haddr := mcii.next_address; hwrite := '0'; hsize := HSIZE_WORD; hlock := '0';
        su := mcii.next_su;
        if ((mcii.next_req and delr.ba) = '1')  and (delr.bo = "00") and ((not delr.retry) = '1') then
          htrans := HTRANS_SEQ; haddr(4 downto 2) := haddr(4 downto 2) +1;
        end if;
        if mcii.next_burst = '1' then hburst := HBURST_INCR;
        else hburst := HBURST_SINGLE; end if;
      elsif nbo = "01" then
        haddr := mcdi.next_address; hwrite := not mcdi.next_read; hsize := '0' & mcdi.next_size;
        hlock := mcdi.next_lock;
        if mcdi.next_asi /= "1010" then su := '1'; else su := '0'; end if;  --ASI_UDATA
        if mcdi.next_burst = '1' then hburst := HBURST_INCR;
        else hburst := HBURST_SINGLE; end if;
        if ((dreq and delr.ba) = '1') and (delr.bo = "01") and ((not delr.retry) = '1') then
          htrans := HTRANS_SEQ; haddr(4 downto 2) := haddr(4 downto 2) +1;
          hburst := HBURST_INCR;
        end if;
      end if;

      if delr.retry = '1' then htrans := HTRANS_IDLE; end if;

      if delr.bo = "10" then
        hwdata := mcmmi.next_data;
      elsif delr.bo = "00" then
        null;
      elsif delr.bo = "01" then
        hlock := mcdi.next_lock or ((delr.retry or (delr.retry2 and not delr.ba)) and delr.hlocken);
      end if;

      if nbo = "01" and ((hsize = "011") or ((mcdi.next_read and mcdi.next_cache) = '1')) then
        hsize := "010";
      end if;

      if (delr.bo = "01") and (hlock = '1') then nbo := "01"; end if;

      hlock_masked := hlock or mcdi.next_lock_hold;

      vo.haddr := haddr;
      vo.htrans := htrans;

      -- Workaround for simulator bug (Riviera 2017.2), split below expression into temporaries
      -- and then logically AND together.
      -- vo.hbusreq := hbusreq and (not delr.lb or orv(nbo)) and (burst and not delr.bg);
      tmp1 := (not delr.lb or orv(nbo));
      tmp2 := (burst or not delr.bg);
      vo.hbusreq := hbusreq and tmp1 and tmp2;

      vo.hwdata := hwdata;
      vo.hlock := hlock_masked;
      vo.hwrite := hwrite;
      vo.hsize := hsize;
      vo.hburst := hburst;
      if nbo="00" then vo.hprot := "11" & su & '0';
      else vo.hprot := "11" & su & '1'; end if;

    end if;

    roin <= vo;

  end process;

  mcio.data  <= ahbreadword(ahbi.hrdata);
  mcdo.data  <= ahbreadword(ahbi.hrdata);
  mcmmo.data <= ahbreadword(ahbi.hrdata);
  ahbo.hirq    <= (others => '0');
  ahbo.hconfig <= hconfig;

  reg : process(clk)
  begin
    if rising_edge(clk) then
      r <= rin;
      if RESET_ALL and (rst = '0') then r <= RRES; end if;
    end if;
  end process;

  reg2gen : if (clk2x /= 0) generate
    reg2 : process(clk)
    begin
      if rising_edge(clk) then
        r2 <= r2in;
        if RESET_ALL and (rst = '0') then r2 <= R2RES; end if;
      end if;
    end process;  
  end generate;

  noreg2gen : if (clk2x = 0) generate
    r2.reqmsk <= "000";
  end generate;    

  regogen : if REGAHBOUT generate
    rego : process(clk)
    begin
      if rising_edge(clk) then
        ro <= roin;
        if RESET_ALL and (rst = '0') then ro <= RORES; end if;
      end if;
    end process;
  end generate;

  noregogen : if not REGAHBOUT generate
    ro <= RORES;
  end generate;

end;
