-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--
-- Copyright 2025 VLSI Solution Oy
-- SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
--
-- Licensed under the Solderpad Hardware License v 2.1 (the "License");
-- you may not use this file except in compliance with the License, or,
-- at your option, the Apache License version 2.0.
-- You may obtain a copy of the License at
--
--    https://solderpad.org/licenses/SHL-2.1/
--
-- Unless required by applicable law or agreed to in writing, any work
-- 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.
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
--                               RISCV core
--                                 V1.1
--
--
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-- - - - - - - -
--  libraries
-- - - - - - - -
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use work.rv_pkg.all;


entity dcachew is
  generic (AXIDATA_W   : natural range 32 to 64 := 32; -- Data width
           WAYS        : natural range 1 to 8  := 2;   -- Number of ways 1,2,4,8
           LINE_ADDR_W : natural range 6 to 12 := 8;   -- Line address
           LINE_SIZE_W : natural range 2 to 8  := 5 ); -- Line size, byte addr
  port ( clk_i              : in  std_logic;
         xrst_i             : in  std_logic;           -- syncr reset
         --  mem bist if (NOTE: mem count is 2x WAYS as there are
         --  tag/data rams)
         --  rd/wr select is 1-hot style in order like ...t1,d1,t0,d0
         mbist_ena_i        : in  std_logic;
         mbist_rd_i         : in  slv_t(2*WAYS-1 downto 0); -- 1-hot like
         mbist_wr_i         : in  slv_t(2*WAYS-1 downto 0); --  t1,d1,t0,d0
         mbist_addr_i       : in  slv_t(LINE_ADDR_W+LINE_SIZE_W-1-2 downto 0);
         mbist_data_o       : out slv_t(AXIDATA_W-1 downto 0);
         --  cpu if
         cpu_rd_i           : in  std_logic;
         cpu_wr_i           : in  slv_03_00;
         cpu_addr_i         : in  slv_31_00;        -- ! ! WORD address, 1:0 NC
         cpu_wdata_i        : in  slv_31_00;
         cpu_req_tag_i      : in  slv_10_00;
         cpu_invalidate_i   : in  std_logic;
         cpu_wb_i           : in  std_logic;        -- writeback single line
         cpu_flush_i        : in  std_logic;
         cpu_rdata_o        : out slv_31_00;
         cpu_accept_o       : out std_logic;
         cpu_ack_o          : out std_logic;
         cpu_error_o        : out std_logic;
         cpu_resp_tag_o     : out slv_10_00;
         --  outport to axi bus
         xmem_accept_i      : in  std_logic;
         xmem_ack_i         : in  std_logic;
         xmem_error_i       : in  std_logic;
         xmem_rdata_i       : in  slv_t(AXIDATA_W-1 downto 0);
         xmem_rd_o          : out std_logic;
         xmem_wr_o          : out slv_t(AXIDATA_W/8-1 downto 0);
         xmem_len_o         : out slv_07_00;  -- burst len
         xmem_addr_o        : out slv_31_00;
         xmem_wdata_o       : out slv_t(AXIDATA_W-1 downto 0)
         );
end entity dcachew;


architecture rtl of dcachew is

  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  -- Default cache instance is 2 way set associative.
  -- The total size is 16KB.
  -- The replacement policy is a limited pseudo random scheme
  -- (between lines, toggling on line thrashing).
  -- The cache is a write back cache, with allocate on read and write.
  --
  --   _________    _________________________________________________________
  --  |31|30|29|...|18|17|16|15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0|
  --   ---------    ---------------------------------------------------------
  --  |                              |                       | LINE   |     |
  --  |           TAG ADDR           |    LINE ADDR          | OFFSET | byte|
  --  |------------------------------|-----------------------|--------|-----|
  --        31:13, 2^19 = 512k         12:5, 2^8 = 256         2^3=8   ignore
  --
  --   -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  --  | WAY-0 default implementation                                    |
  --
  --  |  addr[12:5] (line)      addr[12:5] & addr[4:2] (line+offset)    |
  --      |                      |
  --  |   |         TAG RAM      |         DATA RAM                     |
  --      |         256 x 21bit  |         256*8 x 32bit
  --  |   | 0x00   ___________   | 0x00   ____________                  |
  --      |------>|_ line 0 __|\ |------>|_ data 0 ___|
  --  |   |         \            |       |_________+1_|                 |
  --      |          \           |       |_________+2_|
  --  |   |           \          |       |_________+3_|                 |
  --      |            \         |       |_________+4_|
  --  |   |             \        |       |_________+5_|                 |
  --      |              \       |       |_________+6_|
  --  |   |               \      |       |_________+6_|                 |
  --      | 0x01   ________\__   |       |_________+7_|
  --  |   |------>|_ line 1 __|\ |------>|_ data 7 ___|                 |
  --      |         \            |       |_________+1_|
  --  |   |          \           |       |_________+2_|                 |
  --      |           \          |       |_________+3_|
  --  |   |            \         |       |_________+4_|                 |
  --      |             \        |       |_________+5_|
  --  |   |              \       |       |_________+6_|                 |
  --      | 0x02   _______\___   |       |_________+7_|
  --  |   |------>|_ line 2 __|  |------>|_ data 16___|                 |
  --      |         \            |       |_________+1_|
  --  |   |          \           |       |_________+2_|                 |
  --      |           \          |       |_________+3_|
  --  |   |            \         |       |_________+4_|                 |
  --      |             \        |       |_________+5_|
  --  |   |              \       |       |_________+6_|                 |
  --      |               \_     |       |_________+7_|
  --  |   |       . . . . . . . .| . . . . . . . . . . .                |
  --      | 0xFF   ___________   |       |____________|
  --  |   |------>|_ line 255_|  |------>|_ data 2040_|                 |
  --                \                    |_________+1_|
  --  |              \                   |_________+2_|                 |
  --                  \                  |_________+3_|
  --  |                \                 |_________+4_|                 |
  --                    \                |_________+5_|
  --  |                  \               |_________+6_|                 |
  --                      \_             |_________+7_|
  --  |__ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __|
  --
  --
  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



  -- Address mapping example:                                  NOTE: OBSOLETE
  --                                                           =====
  --  31          16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
  -- |--------------|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
  --  +--------------------+  +--------------------+   +------------+
  --  |  Tag address.      |  |   Line address     |      Address
  --  |                    |  |                    |      within line
  --  |                    |  |                    |
  --  |                    |  |                    |-LINE_ADDR_L
  --  |                    |  |- LINE_ADDR_H
  --  |                    |
  --  |                    |- TAG_ADDR_L
  --  |- TAG_ADDR_H
  --
  --         19 bits                  8 bits            3 bits (+2 for byte)
  --
  --        TAG  31:13             LINE  12:5           OFFSET  4:2
  --  NOTE: IN WORD ADDRESS                             I.E. CACHE LINE SIZE
  --        BITS 1:0 ARE
  --        ALWAYS "00"
  --

  function f_is_axi64b (dwidth : natural ) return natural is
  begin
    if (dwidth = 64) then
      return 1;
    else
      return 0;
    end if;
  end function;

  -- TAG address                                  default 19
  constant TAG_ADDR_W    : natural range 15 to 22 := 32-LINE_ADDR_W-LINE_SIZE_W;
  constant TAG_ADDR_H    : natural := 31;                         -- default 31
  constant TAG_ADDR_L    : natural := LINE_ADDR_W + LINE_SIZE_W;  -- default 13
  constant TAG_DATA_W    : natural := TAG_ADDR_W + 2; -- +2 for valid/dirty bits

  constant DATA_ADDR_W   : natural := LINE_ADDR_W + LINE_SIZE_W-2-
                                      f_is_axi64b(AXIDATA_W);
  constant DATA_ADDR_H   : natural := LINE_ADDR_W + LINE_SIZE_W -1;
  constant DATA_ADDR_L   : natural := 2+f_is_axi64b(AXIDATA_W);

  subtype DATA_ADDR_R is slv_t(LINE_ADDR_W + LINE_SIZE_W-1 downto
                               2+f_is_axi64b(AXIDATA_W));

  constant LINE_ADDR_H   : natural := LINE_ADDR_W + LINE_SIZE_W -1;
  constant LINE_ADDR_L   : natural := LINE_SIZE_W ;

  constant TAG_VALID_BIT : natural := TAG_ADDR_W + 1;
  constant TAG_DIRTY_BIT : natural := TAG_ADDR_W + 0;

  constant BURST_LEN_W   : slv_t(xmem_len_o'range) := slv_t(to_unsigned(
    (2**(LINE_SIZE_W-2-f_is_axi64b(AXIDATA_W))) -1, xmem_len_o'length));


  -- - - - - - - - - - - - - - - -
  --  states
  -- - - - - - - - - - - - - - - -
  --
  -- NOTE: to_unsigned causes Modelsim Warnings about local params
  --
  --
  -- One hot decoding
  --
  constant STATE_W            : natural := 11 -1;
  subtype  state_t is slv_t(STATE_W-1 downto 0);

  constant STATE_WRITEBACK    : state_t := "1000000000";
  constant STATE_INVALIDATE   : state_t := "0100000000";
  constant STATE_EVICT_WAIT   : state_t := "0010000000";
  constant STATE_EVICT        : state_t := "0001000000";
  constant STATE_REFILL       : state_t := "0000100000";
  constant STATE_READ         : state_t := "0000010000";
  constant STATE_LOOKUP       : state_t := "0000001000";
  constant STATE_FLUSH        : state_t := "0000000100";
  constant STATE_FLUSH_ADDR   : state_t := "0000000010";
  constant STATE_RESET        : state_t := "0000000001";

  constant STATE_WRITEBACK_B  : natural := 9;
  constant STATE_INVALIDATE_B : natural := 8;
  constant STATE_EVICT_WAIT_B : natural := 7;
  constant STATE_EVICT_B      : natural := 6;
  constant STATE_REFILL_B     : natural := 5;
  constant STATE_READ_B       : natural := 4;
  constant STATE_LOOKUP_B     : natural := 3;
  constant STATE_FLUSH_B      : natural := 2;
  constant STATE_FLUSH_ADDR_B : natural := 1;
  constant STATE_RESET_B      : natural := 0;

  -- states
  signal  next_state_w        : slv_t(STATE_W-1 downto 0);
  signal  state_q             : slv_t(STATE_W-1 downto 0);


  component tag_ram
    -- ambit translate off
    generic ( DATA_W             : natural range 8 to 32 := 16;
              ADDR_W             : natural range 4 to 32 := 8;
              ODELAY             : time := 3.5 ns;  -- clk to dout delay
              INITIALIZE_TO_ZERO : std_logic := '0' );
    -- ambit translate on
    port ( clk      : in  std_logic;
           xreset   : in  std_logic;  -- NC now in FPGA synthesis
           --  inputs
           rd       : in  std_logic;
           wr       : in  std_logic;
           addr     : in  slv_t(LINE_ADDR_W-1 downto 0);
           din      : in  slv_t(23 downto 0);   -- TAG_ADDR_W+2-1 downto 0);
           --  outputs
           dout     : out slv_t(23 downto 0) ); -- TAG_ADDR_W+2-1 downto 0);
  end component;


  component data_ram
    -- ambit translate off
    generic ( DATA_W             : natural range 8 to 64 := 32;
              ADDR_W             : natural range 4 to 32 := 8;
              ODELAY             : time := 4.5 ns;  -- clk to dout delay
              INITIALIZE_TO_ZERO : std_logic := '0' );
    -- ambit translate on
    port ( clk      : in  std_logic;
           xreset   : in  std_logic;  -- NC now in synthesis
           --  inputs
           rd       : in  std_logic;
           wr       : in  slv_t(AXIDATA_W/8-1 downto 0);
           addr     : in  slv_t(DATA_ADDR_W-1 downto 0);
           din      : in  slv_t(AXIDATA_W-1 downto 0);
           --  outputs
           dout     : out slv_t(AXIDATA_W-1 downto 0) );
  end component;


  --  tag mem is fixed 24-bit
--type tag_data_t is array (0 to WAYS-1) of slv_t(TAG_DATA_W-1 downto 0);
  type tag_data_t is array (0 to WAYS-1) of slv_t(23 downto 0);
  type tag_addr_t is array (0 to WAYS-1) of slv_t(TAG_ADDR_W-1 downto 0);

  type data_data_t  is  array (0 to WAYS-1) of slv_t(AXIDATA_W-1 downto 0);
  type data_write_t is  array (0 to WAYS-1) of slv_t(AXIDATA_W/8-1 downto 0);

  -- - - - - - - - -
  --  request buffer
  -- - - - - - - - -
  signal cpu_rd_q             : std_logic;
  signal cpu_wr_q             : slv_03_00;
  signal cpu_wr_ack_q         : std_logic;
  signal cpu_addr_q           : slv_31_00;
  signal cpu_wdata_q          : slv_31_00;
  signal cpu_tag_q            : slv_10_00;
  signal cpu_inval_q          : std_logic;
  signal cpu_wb_q             : std_logic;
  signal cpu_flush_q          : std_logic;

  signal cpu_accept_w         : std_logic;
  signal cpu_ack_w            : std_logic;
  signal tag_valid_m_w        : slv_t(WAYS-1 downto 0);
  signal tag_addr_bits_m_w    : tag_addr_t;

  signal tag_hit_m_w          : slv_t(WAYS-1 downto 0);  --  tag hit?
  signal tag_hit_any_m_w      : std_logic;

  -- - - - - - - - - - -
  --  registers / wires
  -- - - - - - - - - - -
  signal replace_way_q         : slv_t(WAYS-1 downto 0);

  signal xmem_wr_w             : slv_t(AXIDATA_W/8-1 downto 0);
  signal xmem_rd_w             : std_logic;
  signal xmem_len_w            : slv_07_00;
  signal xmem_1st_w            : std_logic;
  signal xmem_last_w           : std_logic;
  signal xmem_addr_w           : slv_31_00;
  signal xmem_wdata_w          : slv_t(AXIDATA_W-1 downto 0);
  signal xmem_accept_w         : std_logic;
  signal xmem_ack_w            : std_logic;
  signal xmem_error_w          : std_logic;

  signal tag_dirty_any_m_w     : std_logic;
  signal tag_hit_and_dirty_m_w : std_logic;

  signal flushing_q            : std_logic;

  -- - - - - -
  -- TAG RAMS
  -- - - - - -
  signal tag_mrd_w         : slv_t(WAYS-1 downto 0);
  --  tag RAM write enable (way 0,1, . . .)
  signal tag_mwr_w         : slv_t(WAYS-1 downto 0);
  signal tag_maddr_w       : slv_t(LINE_ADDR_W-1 downto 0);
  -- Tag RAM write data
  signal tag_mdin_w        : slv_t(23 downto 0);
  signal tag_mdout_w       : tag_data_t;

  signal data_mrd_w        : slv_t(WAYS-1 downto 0);
  signal data_mwr_w        : data_write_t;               -- ! array
  -- data addressing
  -- data read / write address
  signal data_maddr_w      : slv_t(DATA_ADDR_W-1 downto 0);
  signal data_mdin_w       : slv_t(AXIDATA_W-1 downto 0);

  signal flush_addr_q      : slv_t(LINE_ADDR_W-1 downto 0);

  signal tag_dirty_m_w     : slv_t(WAYS-1 downto 0);
  signal tag_read_w        : std_logic;
  signal tag_write_w       : slv_t(WAYS-1 downto 0);

  signal data_mdout_w      : data_data_t;

  signal evict_way_w       : std_logic;
  signal evict_vdway_w     : std_logic;
  signal evict_addr_w      : slv_t(32-LINE_SIZE_W-1 downto 0);
  signal evict_data_w      : slv_t(AXIDATA_W-1 downto 0);

  signal data_waddr_q      : slv_t(DATA_ADDR_W-1 downto 0);
  --  data RAM write enable (way 0, 1...)
  signal data_write_m_w    : data_write_t;
  signal no_data_write_w   : std_logic;
  signal data_read_w       : std_logic;

  signal flush_last_q      : std_logic;

  signal rdata_w           : slv_t(AXIDATA_W-1 downto 0);

  signal xmem_rd_q         : std_logic;
  signal xmem_wr0_q        : std_logic;
  signal xmem_len_q        : slv_07_00;
  signal xmem_wr_q         : slv_t(AXIDATA_W/8-1 downto 0);
  signal xmem_wdata_q      : slv_t(AXIDATA_W-1 downto 0);
  signal error_q           : std_logic;
  signal refill_request_w  : std_logic;
  signal evict_request_w   : std_logic;

  signal line_change_w     : std_logic;


  signal si_tag_w          : slv_t(WAYS-1 downto 0);
  signal so_tag_w          : slv_t(WAYS-1 downto 0);
  signal si_w              : slv_t(WAYS-1 downto 0);
  signal so_w              : slv_t(WAYS-1 downto 0);

  -- mbist signals
  signal mbist_rd_i_q      : slv_t(mbist_rd_i'range);    -- 1-hot  t1,d1,t0,d0

  -- pragma translate_off
  type state_dbg_t is (RESET, FLUSH_ADDR, FLUSH , LOOKUP, READ, REFILL,
                       EVICT, EVICT_WAIT, INVALIDATE, WRITEBACK, xx);
  signal state_dbg         : state_dbg_t;
  -- pragma translate_on


begin

  -- pragma translate_off
  --
  state_dbg <= RESET      when (state_q = STATE_RESET     ) else
               FLUSH_ADDR when (state_q = STATE_FLUSH_ADDR) else
               FLUSH      when (state_q = STATE_FLUSH     ) else
               LOOKUP     when (state_q = STATE_LOOKUP    ) else
               READ       when (state_q = STATE_READ      ) else
               REFILL     when (state_q = STATE_REFILL    ) else
               EVICT      when (state_q = STATE_EVICT     ) else
               EVICT_WAIT when (state_q = STATE_EVICT_WAIT) else
               INVALIDATE when (state_q = STATE_INVALIDATE) else
               WRITEBACK  when (state_q = STATE_WRITEBACK ) else
               xx;
  -- pragma translate_on



  p_mem : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        cpu_rd_q        <= '0';
        cpu_wr_q        <= X"0";
        cpu_wr_ack_q    <= '0';
        cpu_addr_q      <= X"00000000";
        cpu_wdata_q     <= X"00000000";
        cpu_tag_q       <= "00000000000";
        cpu_inval_q     <= '0';
        cpu_wb_q        <= '0';
        cpu_flush_q     <= '0';
      else
        if (cpu_accept_w = '1') then
          cpu_rd_q      <= cpu_rd_i;
          cpu_wr_q      <= cpu_wr_i;
          cpu_wr_ack_q  <= not_zero(cpu_wr_i);
          cpu_addr_q    <= cpu_addr_i;
          cpu_wdata_q   <= cpu_wdata_i;
          cpu_tag_q     <= cpu_req_tag_i;
          cpu_inval_q   <= cpu_invalidate_i;
          cpu_wb_q      <= cpu_wb_i;
          cpu_flush_q   <= cpu_flush_i;
        elsif ( (state_q(STATE_REFILL_B) = '1' and
                 next_state_w(STATE_REFILL_B) = '0' and not_zero(cpu_wr_q)) or
                (state_q(STATE_LOOKUP_B) = '1' and cpu_ack_w = '1') )then
          cpu_rd_q      <= '0';
          cpu_wr_q      <= X"0";
          cpu_addr_q    <= X"00000000";
          cpu_wdata_q   <= X"00000000";
          cpu_tag_q     <= "00000000000";
          cpu_inval_q   <= '0';
          cpu_wb_q      <= '0';
          cpu_flush_q   <= '0';
        end if;
        --
        if (cpu_wr_ack_q = '1' and (state_q(STATE_REFILL_B) = '1' or
                                    state_q(STATE_EVICT_B) = '1')) then
          cpu_wr_ack_q <= '0';
        end if;
      end if;
    end if;
  end process p_mem;



  line_change_w <= ne(cpu_addr_i(31 downto LINE_ADDR_L),     -- 29:3
                      cpu_addr_q(31 downto LINE_ADDR_L) );



  p_c_mem_accept : process (state_q, cpu_rd_q, cpu_wr_q, tag_hit_any_m_w,
                            cpu_rd_i, line_change_w)
  begin
    --
    cpu_accept_w  <= '0';
    --
    if (state_q(STATE_LOOKUP_B) = '1') then
      -- previous access missed - do not accept new requests
      if ( (cpu_rd_q = '1' or not_zero(cpu_wr_q)) and
           tag_hit_any_m_w = '0' ) then
        cpu_accept_w <= '0';
      -- write followed by read OR write  => WAIT FOR TAG OUTPUT TO UPDATE
      elsif ( not_zero(cpu_wr_q) and (cpu_rd_i = '1' ) and
              line_change_w = '0') then
        cpu_accept_w <= '0';
      elsif ( not_zero(cpu_wr_q) and line_change_w = '1') then
        cpu_accept_w <= '0';
      else
        cpu_accept_w <= '1';
      end if;
    end if;
  end process p_c_mem_accept;


  cpu_accept_o   <= cpu_accept_w;

  cpu_resp_tag_o <= cpu_tag_q;



  --
  -- tag RAM address
  -- NOTE: Tag rams are read only when : STATE_LOOKUP when NO write enable
  --                                     STATE_REFILL when pmem last
  --
  p_c_tagaddr : process ( cpu_addr_q,  state_q, cpu_wr_q, cpu_addr_i,
                          flush_addr_q, flushing_q, next_state_w,
                          mbist_ena_i, mbist_addr_i)
  begin

    tag_maddr_w <= cpu_addr_q(LINE_ADDR_H downto LINE_ADDR_L);     -- WRITE/READ
    if (state_q(STATE_LOOKUP_B) = '1' and
        (next_state_w(STATE_LOOKUP_B) = '1' or
         next_state_w(STATE_WRITEBACK_B) = '1')) then
      if ( cpu_wr_q = "0000" ) then   -- mem rd
        tag_maddr_w <= cpu_addr_i(LINE_ADDR_H downto LINE_ADDR_L); -- READ
      end if;
    end if;
    if (flushing_q = '1' or state_q(STATE_RESET_B) = '1') then     -- flush
      tag_maddr_w <= flush_addr_q;                                 -- WRITE
    end if;
    --
    if (mbist_ena_i = '1') then
      tag_maddr_w  <= mbist_addr_i(tag_maddr_w'range);
    end if;
  end process p_c_tagaddr;


  -- tag RAM write data, NOTE: FIXED 24 BIT RAM
  p_c_tagdat : process (state_q, flushing_q, cpu_addr_q, cpu_wr_q,
                        mbist_ena_i, xmem_rdata_i)
  begin
    --  in cache mode MSB bits are always "00000"
    tag_mdin_w <= (others => '0');
    --
    if (state_q(STATE_FLUSH_B) = '1' or state_q(STATE_RESET_B) = '1' or
        -- cache flush
        flushing_q = '1') then
      tag_mdin_w <= (others => '0');
    elsif (state_q(STATE_REFILL_B) = '1') then
      -- line refill
      -- read  - mark entry as valid
      -- write - mark entry as valid and dirty
      -- (write enabled when last word of refill)
      tag_mdin_w(TAG_VALID_BIT)      <= '1';
      tag_mdin_w(TAG_DIRTY_BIT)      <= ne(cpu_wr_q, "0000");
      tag_mdin_w(TAG_ADDR_W-1 downto 0) <=
        cpu_addr_q(TAG_ADDR_H downto TAG_ADDR_L);
    elsif (state_q(STATE_INVALIDATE_B) = '1') then
      -- invalidate - mark entry (if matching line) not valid (even if dirty...)
      tag_mdin_w(TAG_VALID_BIT)      <= '0';
      tag_mdin_w(TAG_DIRTY_BIT)      <= '0';
      tag_mdin_w(TAG_ADDR_W-1 downto 0) <=
        cpu_addr_q(TAG_ADDR_H downto TAG_ADDR_L);
    elsif (state_q(STATE_EVICT_WAIT_B) = '1') then
      -- evict completion
      tag_mdin_w(TAG_VALID_BIT)      <= '1';
      tag_mdin_w(TAG_DIRTY_BIT)      <= '0';
      tag_mdin_w(TAG_ADDR_W-1 downto 0) <=
        cpu_addr_q(TAG_ADDR_H downto TAG_ADDR_L);
    elsif (state_q(STATE_LOOKUP_B) = '1') then
      -- write - mark entry as dirty
      tag_mdin_w(TAG_VALID_BIT)       <= '1';
      tag_mdin_w(TAG_DIRTY_BIT)       <= ne(cpu_wr_q, "0000");
      tag_mdin_w(TAG_ADDR_W-1 downto 0)  <=
        cpu_addr_q(TAG_ADDR_H downto TAG_ADDR_L);
    end if;
    --
    --
    if (mbist_ena_i = '1') then
      tag_mdin_w <= xmem_rdata_i(23 downto 0);  -- fixed 24 bit ram
    end if;
  end process p_c_tagdat;



  --
  -- tag RAM write enable (way 0,1,. . .)
  -- NOTE: tag rams are read only in STATE_LOOKUP and STATE_REFILL
  --
  p_c_tag_wr : process (state_q, tag_dirty_any_m_w, cpu_wr_q, tag_hit_m_w,
                        replace_way_q, xmem_ack_w, xmem_1st_w)
  begin
    --
    tag_write_w <= (others => '0');
    --
    -- cache flush (reset)
    if (state_q(STATE_RESET_B) = '1') then
      tag_write_w <= (others => '1');
    -- cache flush
    elsif (state_q(STATE_FLUSH_B) = '1') then
      tag_write_w <= (others => (not tag_dirty_any_m_w));
    -- write - hit, mark as dirty
    elsif (state_q(STATE_LOOKUP_B) = '1' and not_zero(cpu_wr_q)) then
      tag_write_w <= tag_hit_m_w;
    elsif (state_q(STATE_EVICT_WAIT_B) = '1' and xmem_ack_w = '1') then
      tag_write_w <= replace_way_q;
    -- line refill
    elsif (state_q(STATE_REFILL_B) = '1') then
      if (xmem_1st_w = '1') then                        -- 1st word of refill
        tag_write_w <= replace_way_q;
      end if;
    -- invalidate - line matches address - invalidate
    elsif (state_q(STATE_INVALIDATE_B) = '1') then
      tag_write_w <= tag_hit_m_w;
    end if;
  end process p_c_tag_wr;



  tag_read_w <=  (state_q(STATE_LOOKUP_B)
                  and eq(tag_write_w, (tag_write_w'range => '0')) )
                 or (state_q(STATE_REFILL_B) and xmem_last_w)
                 or state_q(STATE_FLUSH_ADDR_B);

  data_read_w <=   (state_q(STATE_LOOKUP_B) and no_data_write_w)
                   or state_q(STATE_READ_B)
                   or state_q(STATE_EVICT_B)
                   or state_q(STATE_FLUSH_B);



  g_rams : for ii in 0 to WAYS-1 generate
    --
    -- tag RAMs
    --
    u_tag : tag_ram        -- semi synchronous ram for tag addr and status
      -- ambit translate off
      generic map ( DATA_W             => 24, -- TAG_DATA_W,   ! FIXED 24 BIT
                    ADDR_W             => LINE_ADDR_W,
                    ODELAY             => 3.5 ns,
                    INITIALIZE_TO_ZERO => '0' )
      -- ambit translate on
      port map ( clk      => clk_i,
                 xreset   => xrst_i,             -- NC now in FPGA synthesis
                 --  inputs
                 rd       => tag_mrd_w(ii),      -- common for all tag rams
                 wr       => tag_mwr_w(ii),      -- unique for each WAY
                 addr     => tag_maddr_w,        -- common for all tag rams
                 din      => tag_mdin_w,         -- common for all tag rams
                 --  outputs
                 dout     => tag_mdout_w(ii) );  -- unique for each WAY, 18:0


    tag_dirty_m_w(ii)     <= tag_mdout_w(ii)(TAG_DIRTY_BIT);
    tag_valid_m_w(ii)     <= tag_mdout_w(ii)(TAG_VALID_BIT);
    tag_addr_bits_m_w(ii) <= tag_mdout_w(ii)(TAG_ADDR_W-1 downto 0);

    tag_hit_m_w(ii)       <= tag_valid_m_w(ii) and
                             eq( tag_addr_bits_m_w(ii),
                                 cpu_addr_q(TAG_ADDR_H downto TAG_ADDR_L) );


    -- u_data : ram_ddata
    u_data : data_ram
      -- ambit translate off
      generic map ( DATA_W             => AXIDATA_W,  -- 32,
                    ADDR_W             => DATA_ADDR_W,
                    ODELAY             => 4.5 ns,
                    INITIALIZE_TO_ZERO => '0' )
      -- ambit translate on
      port map ( clk      => clk_i,
                 xreset   => xrst_i,             -- NC now in FPGA synthesis
                 --  inputs
                 rd       => data_mrd_w(ii),
                 wr       => data_mwr_w(ii),
                 addr     => data_maddr_w,
                 din      => data_mdin_w,
                 --  outputs
                 dout     => data_mdout_w(ii) );

    --
    -- mbist signal order :  tn, dn, . . . t1,d1, t0,d0
    --
    -- in cache mode the data in and rd enable is common for all WAYs
    tag_mrd_w(ii)  <= mbist_rd_i(2*ii+1) when (mbist_ena_i = '1')
                      else tag_read_w;
    tag_mwr_w(ii)  <= mbist_wr_i(2*ii+1) when (mbist_ena_i = '1')
                      else tag_write_w(ii);
    data_mrd_w(ii) <= mbist_rd_i(2*ii)   when (mbist_ena_i = '1')
                      else data_read_w;
    data_mwr_w(ii) <= (AXIDATA_W/8-1 downto 0 => mbist_wr_i(2*ii))
                      when (mbist_ena_i = '1')
                      else data_write_m_w(ii);


  end generate g_rams;

  -- tag comparison address matches
  tag_hit_any_m_w      <= not_zero(tag_hit_m_w);



  -- ram read is pipelined
  p_mbist : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        mbist_rd_i_q <= (others => '0');
      else
        mbist_rd_i_q <= mbist_rd_i;
        if (mbist_ena_i = '0') then
          mbist_rd_i_q <= (others => '0');
        end if;
      end if;
    end if;
  end process p_mbist;



  --
  -- MBIST out mux
  --
  p_c_mbist : process (mbist_ena_i, mbist_rd_i_q, tag_mdout_w, data_mdout_w)
  begin
    mbist_data_o <= (others => '0');
    if (mbist_ena_i = '1') then
      -- i.e. when 2-way cache => 4 mems (3, 2, 1, 0) => t1, d1, t0, d0
      for ii in WAYS-1 downto 0 loop   -- i.e. 2*WAYS-1 downto 0,  1-hot
        --
        -- ! ! NOTE: THIS RETURNS THE MEMORY DATA OF THE LAST BIT THAT IS '1'
        --
        if (mbist_rd_i_q(2*ii+1) = '1') then
          mbist_data_o(31 downto 24) <= (others => '0');
          mbist_data_o(23 downto 0)  <= tag_mdout_w(ii);  -- fixed 24 bit ram
        end if;
        if (mbist_rd_i_q(2*ii) = '1') then
          mbist_data_o <= data_mdout_w(ii);
        end if;
      end loop;
    end if;
  end process p_c_mbist;



  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  g_axi32i : if ( AXIDATA_W /= 64 ) generate
  begin
    p_c_data_din : process  (cpu_wdata_q, state_q, xmem_rdata_i, data_waddr_q,
                             cpu_addr_q, cpu_wr_q, mbist_ena_i)
    begin
      data_mdin_w <= cpu_wdata_q;             -- write data from reg
      if (state_q(STATE_REFILL_B) = '1') then
        data_mdin_w <= xmem_rdata_i;
        --
        if (data_waddr_q = cpu_addr_q(DATA_ADDR_R'range)) then
          if (cpu_wr_q(3) = '1') then
            data_mdin_w(31 downto 24) <= cpu_wdata_q(31 downto 24);
          end if;
          if (cpu_wr_q(2) = '1') then
            data_mdin_w(23 downto 16) <= cpu_wdata_q(23 downto 16);
          end if;
          if (cpu_wr_q(1) = '1') then
            data_mdin_w(15 downto 8)  <= cpu_wdata_q(15 downto 8);
          end if;
          if (cpu_wr_q(0) = '1') then
            data_mdin_w(7 downto 0)   <= cpu_wdata_q(7 downto 0);
          end if;
        end if;
      end if;
      if (mbist_ena_i = '1') then
        data_mdin_w <= xmem_rdata_i;
      end if;
    end process p_c_data_din;
  end generate g_axi32i;
  --
  --
  g_axi64i : if ( AXIDATA_W = 64 ) generate
  begin
    p_c_data_din : process  (cpu_wdata_q, state_q, xmem_rdata_i, data_waddr_q,
                             cpu_addr_q, cpu_wr_q, mbist_ena_i)
    begin
      data_mdin_w                  <= (others => '0');
      --
      data_mdin_w(31 downto 0)     <= cpu_wdata_q;   -- write data from reg
      if (cpu_addr_q(2) = '1') then
        data_mdin_w(63 downto 32)  <= cpu_wdata_q;   -- write data from reg
      end if;
      --
      if (state_q(STATE_REFILL_B) = '1') then
        data_mdin_w <= xmem_rdata_i;
        --
        if (data_waddr_q = cpu_addr_q(DATA_ADDR_R'range)) then
          for ii in 0 to ((32/8)-1) loop             -- cpu data bus is 32 bits
            if ( cpu_wr_q(ii) = '1' ) then
              if (cpu_addr_q(2) = '1') then
                data_mdin_w((ii+4)*8+7 downto (ii+4)*8) <=
                  cpu_wdata_q(ii*8+7 downto ii*8);
              else
                data_mdin_w(ii*8+7 downto ii*8) <=
                  cpu_wdata_q(ii*8+7 downto ii*8);
              end if;
            end if;
          end loop;
        end if;
      end if;
      if (mbist_ena_i = '1') then
        data_mdin_w <= xmem_rdata_i;
      end if;
    end process p_c_data_din;
  end generate g_axi64i;
  -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


  p_c_evic : process (flushing_q, tag_addr_bits_m_w, flush_addr_q,
                      cpu_addr_q, data_mdout_w, replace_way_q,
                      tag_valid_m_w, tag_dirty_m_w)
  begin
    --
    -- default: way 0
    --
    evict_vdway_w  <= tag_valid_m_w(0) and tag_dirty_m_w(0);
    evict_data_w <= data_mdout_w(0);
    if (flushing_q = '1') then
      evict_addr_w <= (tag_addr_bits_m_w(0) & flush_addr_q);
    else
      evict_addr_w <= (tag_addr_bits_m_w(0) &
                       cpu_addr_q(LINE_ADDR_H downto LINE_ADDR_L));
    end if;
    --
    for ii in 1 to WAYS-1 loop
        if (replace_way_q(ii) = '1') then
          evict_vdway_w  <= tag_valid_m_w(ii) and tag_dirty_m_w(ii);
          evict_data_w   <= data_mdout_w(ii);
          if (flushing_q = '1') then
            evict_addr_w <= (tag_addr_bits_m_w(ii) & flush_addr_q);
          else
            evict_addr_w <= (tag_addr_bits_m_w(ii) &
                             cpu_addr_q(LINE_ADDR_H downto LINE_ADDR_L));
          end if;
        end if;
    end loop;
  end process p_c_evic;


  -- if all ways are full => evict 1st before refill
  evict_way_w  <= (flushing_q or (not tag_hit_any_m_w)) and evict_vdway_w;


  -- - - - - - -
  -- DATA RAMS
  -- - - - - - -
  --  data RAM refill write address
  p_wr_addr : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        data_waddr_q    <= (others => '0');
      else
       -- if (state_q(STATE_REFILL_B) /= '1' and
       --     next_state_w(STATE_REFILL_B) = '1') then
        if (refill_request_w = '1') then
          data_waddr_q  <= xmem_addr_w(DATA_ADDR_R'range);
        elsif (state_q(STATE_EVICT_B) /= '1' and
               next_state_w(STATE_EVICT_B) = '1') then
          data_waddr_q  <= inc1u(data_maddr_w);                          -- ++1
        elsif (state_q(STATE_REFILL_B) = '1' and xmem_ack_w = '1') then
          data_waddr_q  <= inc1u(data_waddr_q);                          -- ++1
        elsif (state_q(STATE_EVICT_B) = '1' and xmem_accept_w = '1') then
          data_waddr_q  <= inc1u(data_waddr_q);                          -- ++1
        end if;
      end if;
    end if;
  end process p_wr_addr;


  --
  -- data RAM read
  --
  --   data read in states :   STATE_LOOKUP_B
  --                           STATE_READ_B
  --                           STATE_EVICT_B
  --
  --   data write in states :  STATE_REFILL_B
  --                           STATE_WRITE_B
  --                           STATE_LOOKUP_B
  --
  p_c_data_ad : process (cpu_addr_i, state_q, data_waddr_q, next_state_w,
                         cpu_addr_q, data_read_w,flush_addr_q,
                         mbist_ena_i, mbist_addr_i)
  begin
    --
    data_maddr_w <=  cpu_addr_i(DATA_ADDR_R'range);      -- default READ
    --
    -- line refill / evict
    if (state_q(STATE_REFILL_B) = '1' or state_q(STATE_EVICT_B) = '1') then
      data_maddr_w <= data_waddr_q;
    -- dirty lines must be stored
    elsif (state_q(STATE_FLUSH_B) = '1' or state_q(STATE_RESET_B) = '1') then
      data_maddr_w <= (flush_addr_q & (LINE_SIZE_W-1 downto
                                       2+f_is_axi64b(AXIDATA_W) => '0'));
    elsif (state_q(STATE_EVICT_B) /= '1' and
           next_state_w(STATE_EVICT_B) = '1') then
      -- START READ FROM LINE BOUNDARY
      data_maddr_w <= (cpu_addr_q(LINE_ADDR_H downto LINE_ADDR_L) &
                       (LINE_SIZE_W-1 downto 2+f_is_axi64b(AXIDATA_W) => '0'));
    -- lookup post refill
    elsif (state_q(STATE_READ_B) = '1') then
      data_maddr_w <= cpu_addr_q(DATA_ADDR_R'range);
    -- possible line update on write
    -- NOTE: done on-the-fly in STATE_REFILL
    else  -- in LOOKUP
      data_maddr_w <= cpu_addr_i(DATA_ADDR_R'range);     -- default read
      if (data_read_w = '0') then
        data_maddr_w <= cpu_addr_q(DATA_ADDR_R'range);   -- write
      end if;
    end if;
    --
    if (mbist_ena_i = '1') then
      data_maddr_w <= mbist_addr_i(data_maddr_w'range);
    end if;
  end process p_c_data_ad;


  --
  -- data RAM write enable (way 0 - n)
  --
  g_axi32wr : if ( AXIDATA_W /= 64 ) generate
  begin
    p_c_data_wr : process (state_q, xmem_ack_w, replace_way_q, cpu_wr_q,
                           tag_hit_m_w)
    begin
      --
      data_write_m_w <= (others => (others => '0'));
      --
      if (state_q(STATE_REFILL_B) = '1') then  -- WRITE
        for ii in 0 to WAYS-1 loop
          if (xmem_ack_w = '1' and replace_way_q(ii) = '1') then
            data_write_m_w(ii) <= (others => '1');
          else
            data_write_m_w(ii) <= (others => '0');
          end if;
        end loop;
      elsif (state_q(STATE_LOOKUP_B) = '1') then
        -- NOTE:  "or state_q(STATE_WRITE_B)= '1'" is obsolete as new write
        --        data is updated on the fly during refill
        for ii in 0 to WAYS-1 loop
          data_write_m_w(ii) <= cpu_wr_q and
                                (cpu_wr_q'range => tag_hit_m_w(ii));
        end loop;
      end if;
    end process p_c_data_wr;
  end generate g_axi32wr;
  --
  --
  g_axi64wr : if ( AXIDATA_W = 64 ) generate
  begin
    p_c_data_wr : process (state_q, xmem_ack_w, replace_way_q, cpu_addr_q,
                           cpu_wr_q, tag_hit_m_w)
    begin
      --
      data_write_m_w <= (others => (others => '0'));
      --
      if (state_q(STATE_REFILL_B) = '1') then  -- WRITE
        for ii in 0 to WAYS-1 loop
          if (xmem_ack_w = '1' and replace_way_q(ii) = '1') then
            data_write_m_w(ii) <= (others => '1');
          else
            data_write_m_w(ii) <= (others => '0');
          end if;
        end loop;
      elsif (state_q(STATE_LOOKUP_B) = '1') then
        -- NOTE:  "or state_q(STATE_WRITE_B)= '1'" is obsolete as new write
        --        data is updated on the fly during refill
        for ii in 0 to WAYS-1 loop
          if (cpu_addr_q(2) = '1') then
            data_write_m_w(ii)(7 downto 4) <= cpu_wr_q and
                                              (cpu_wr_q'range =>
                                               tag_hit_m_w(ii));
          else
            data_write_m_w(ii)(3 downto 0) <= cpu_wr_q and
                                              (cpu_wr_q'range =>
                                               tag_hit_m_w(ii));
          end if;
        end loop;
      end if;
    end process p_c_data_wr;
  end generate g_axi64wr;



  p_c_no_data_wr : process (data_write_m_w)
    begin
      no_data_write_w <= '1';
      for ii in 0 to WAYS-1 loop
        if ( not_zero(data_write_m_w(ii)) ) then
          no_data_write_w <= '0';
        end if;
      end loop;
    end process p_c_no_data_wr;


  -- - - - - - - - -
  -- flush counter
  -- - - - - - - - -
  p_fls_adr : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0' or mbist_ena_i = '1') then
        flush_addr_q <= (LINE_ADDR_W-1 downto 0 => '0');
      else
        if ( (state_q(STATE_RESET_B) = '1') or
             (state_q(STATE_FLUSH_B) = '1' and
              next_state_w(STATE_FLUSH_ADDR_B) = '1')) then
          flush_addr_q <= inc1u(flush_addr_q);                         -- +1
        elsif (state_q(STATE_LOOKUP_B) = '1') then
          flush_addr_q <= (LINE_ADDR_W-1 downto 0 => '0');
        end if;
      end if;
    end if;
  end process p_fls_adr;


  p_flsing : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        flushing_q <= '0';
      else
        if (state_q(STATE_LOOKUP_B) = '1' and
            next_state_w(STATE_FLUSH_ADDR_B) = '1') then
          flushing_q <= '1';
        elsif (state_q(STATE_FLUSH_B) = '1' and
               next_state_w(STATE_LOOKUP_B) = '1') then
          flushing_q <= '0';
        end if;
      end if;
    end if;
  end process p_flsing;


  p_fls_lst : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        flush_last_q <= '0';
      else
        if (state_q(STATE_LOOKUP_B) = '1') then
          flush_last_q <= '0';
        elsif ( flush_addr_q = (LINE_ADDR_W-1 downto 0 => '1') ) then
          flush_last_q <= '1';
        end if;
      end if;
    end if;
  end process p_fls_lst;


  -- - - - - - - - - - -
  -- replacement policy
  -- - - - - - - - - - -
  -- using random replacement policy - this way we cycle through the ways
  -- when needing to replace a line.

  -- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  -- function to select the way to use for fills when WAYS = 1
  -- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  g_pick_1w : if ( WAYS = 1 ) generate
    replace_way_q(0) <= '1';              --  only way to go
  end generate g_pick_1w;



  -- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  -- function to select the way to use for fills when WAYS = 2
  -- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  g_pick_2w : if ( WAYS = 2 ) generate
    p_rep_way : process (clk_i)
      variable toggle_cond1_v : std_logic;
      variable toggle_cond2_v : std_logic;
    begin
      if (clk_i'event and clk_i = '1') then
        if (xrst_i = '0') then
          replace_way_q    <= (others => '0');
          replace_way_q(0) <= '1';                      --  "...01"
        else
          toggle_cond1_v := state_q(STATE_REFILL_B) and xmem_last_w;
          toggle_cond2_v := flushing_q and tag_dirty_any_m_w  and
                            (not evict_way_w) and
                            (not state_q(STATE_FLUSH_ADDR_B));
          if (toggle_cond1_v = '1') then
            if ( tag_valid_m_w(0) = '0' ) then
              -- way 0 not occupied so use it
              replace_way_q <= "01";                    -- ONE HOT encoding
            elsif ( tag_valid_m_w(1) = '0' ) then
              -- way 1 not occupied so use it
              replace_way_q <= "10";                    -- ONE HOT encoding
            else
              replace_way_q <= not replace_way_q;
            end if;
          elsif (toggle_cond2_v = '1') then
            replace_way_q    <= (others => '0');
            for ii in 0 to WAYS-1 loop
              if (tag_valid_m_w(ii) = '1' and tag_dirty_m_w(ii) = '1') then
                -- evict lines in order 0...n
                replace_way_q(ii) <= '1';
                exit;
              end if;
            end loop;
          elsif ( (state_q(STATE_EVICT_WAIT_B) = '1' and
                   next_state_w(STATE_FLUSH_ADDR_B) = '1') or
                  (state_q(STATE_FLUSH_B) = '1' and
                   next_state_w(STATE_LOOKUP_B) = '1') or
                  (state_q(STATE_LOOKUP_B) = '1' and
                   next_state_w(STATE_FLUSH_ADDR_B) = '1') ) then
            replace_way_q    <= (others => '0');
            replace_way_q(0) <= '1';                    --  "...01"
          elsif (state_q(STATE_WRITEBACK_B) = '1') then
            replace_way_q <= tag_hit_m_w;
          end if;
        end if;
      end if;
    end process p_rep_way;
  end generate g_pick_2w;



  -- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  -- function to select the way to use for fills when WAYS = 4
  -- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  g_pick_4w : if ( WAYS = 4 ) generate
    -- pick_way_4ways
    b_pick_4w : block
      --
      signal random_num_q : slv_03_00;
      --
      function pick_way ( valid_bits : slv_t(WAYS-1 downto 0);
                          random_num : slv_03_00 )
        return slv_t is  -- [WAYS-1:0)
        variable pick_way_v :  slv_t(WAYS-1 downto 0);
      begin
        if ( valid_bits(0) = '0' ) then
          -- way 0 not occupied so use it
          pick_way_v     := "0001";
        elsif ( valid_bits(1) = '0' ) then
          -- way 1 not occupied so use it
          pick_way_v     := "0010";
        elsif ( valid_bits(2) = '0' )  then
          -- way 2 not occupied so use it
          pick_way_v     := "0100";
        elsif ( valid_bits(3) = '0' ) then
          -- way 3 not occupied so use it
          pick_way_v     := "1000";
        else
          -- all ways occupied so pick one randomly
          case random_num(3 downto 1) is
            when "000" | "001" =>
              pick_way_v := "0100";
            when "010" | "011" =>
              pick_way_v := "1000";
            when "100" | "101" =>
              pick_way_v := "0001";
            when others =>
              pick_way_v := "0010";
          end case;
        end if;
        return pick_way_v;
      end function pick_way;
      --
   begin
      --
      p_rand : process (clk_i)
      begin
        if (clk_i'event and clk_i = '1') then
          if (xrst_i = '0') then
            random_num_q <= X"F";
          else
            random_num_q <= random_num_q(2) & random_num_q(1) &
                            random_num_q(0) &
                            ( random_num_q(3) xor random_num_q(2) );
          end if;
        end if;
      end process p_rand;
      --
       p_rep_way : process (clk_i)
        variable toggle_cond1_v : std_logic;
        variable toggle_cond2_v : std_logic;
      begin
        if (clk_i'event and clk_i = '1') then
          if (xrst_i = '0') then
            replace_way_q    <= (others => '0');
            replace_way_q(0) <=  '1';                 --  "...01"
          else
            toggle_cond1_v := state_q(STATE_REFILL_B) and xmem_last_w;
            toggle_cond2_v := flushing_q and tag_dirty_any_m_w  and
                              (not evict_way_w) and
                              (not state_q(STATE_FLUSH_ADDR_B));
            --
            if (toggle_cond1_v = '1' or toggle_cond2_v = '1') then
              --
              replace_way_q <= pick_way( tag_valid_m_w, random_num_q );
              --
            elsif ( (state_q(STATE_EVICT_WAIT_B) = '1' and
                     next_state_w(STATE_FLUSH_ADDR_B) = '1') or
                    (state_q(STATE_FLUSH_B) = '1' and
                     next_state_w(STATE_LOOKUP_B) = '1') or
                    (state_q(STATE_LOOKUP_B) = '1' and
                     next_state_w(STATE_FLUSH_ADDR_B) = '1') ) then
              replace_way_q    <= (others => '0');
              replace_way_q(0) <=  '1';               --  "...01"
            elsif (state_q(STATE_WRITEBACK_B) = '1') then
              replace_way_q <= tag_hit_m_w;
            end if;
          end if;
        end if;
      end process p_rep_way;
    end block b_pick_4w;
  end generate g_pick_4w;



  -- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  -- function to select the way to use for fills when WAYS = 8
  -- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  g_pick_8w : if (  WAYS /= 1 and  WAYS /= 2 and  WAYS /= 3 and
                    WAYS /= 4 and  WAYS /= 5 and  WAYS /= 6 and
                    WAYS /= 7) generate
    --  pick_way_8ways
    b_pick_8w : block
      --
      signal random_num_q : slv_03_00;
      --
      function pick_way ( valid_bits : slv_t(WAYS-1 downto 0);
                          random_num : slv_03_00 )
        return slv_t is  -- [WAYS-1:0]
        variable pick_way_v :  slv_t(WAYS-1 downto 0);
      begin
        if ( valid_bits(0) = '0' ) then
          -- way 0 not occupied so use it
          pick_way_v     := "00000001";
        elsif ( valid_bits(1) = '0' ) then
          -- way 1 not occupied so use it
          pick_way_v     := "00000010";
        elsif ( valid_bits(2) = '0' ) then
          -- way 2 not occupied so use it
          pick_way_v     := "00000100";
        elsif ( valid_bits(3) = '0' ) then
          -- way 3 not occupied so use it
          pick_way_v     := "00001000";
        elsif ( valid_bits(4) = '0' ) then
          -- way 4 not occupied so use it
          pick_way_v     := "00010000";
        elsif ( valid_bits(5) = '0' ) then
          -- way 5 not occupied so use it
          pick_way_v     := "00100000";
        elsif ( valid_bits(6) = '0' ) then
          -- way 6 not occupied so use it
          pick_way_v     := "01000000";
        elsif ( valid_bits(7) = '0' ) then
          -- way 7 not occupied so use it
          pick_way_v     := "10000000";
        else
          -- All ways occupied so pick one randomly
          case random_num(3 downto 1) is
            when "000"  =>  pick_way_v := "00010000";
            when "001"  =>  pick_way_v := "00100000";
            when "010"  =>  pick_way_v := "01000000";
            when "011"  =>  pick_way_v := "10000000";
            when "100"  =>  pick_way_v := "00000001";
            when "101"  =>  pick_way_v := "00000010";
            when "110"  =>  pick_way_v := "00000100";
            when others =>  pick_way_v := "00001000";
          end case;
        end if;
        return pick_way_v;
      end function pick_way;
      --
    begin
      --
      p_rand : process (clk_i)
      begin
        if (clk_i'event and clk_i = '1') then
          if (xrst_i = '0') then
            random_num_q <= X"F";
          else
            random_num_q <= random_num_q(2) & random_num_q(1) &
                            random_num_q(0) &
                            ( random_num_q(3) xor random_num_q(2) );
          end if;
        end if;
      end process p_rand;
      --
      --
       p_rep_way : process (clk_i)
        variable toggle_cond1_v : std_logic;
        variable toggle_cond2_v : std_logic;
      begin
        if (clk_i'event and clk_i = '1') then
          if (xrst_i = '0') then
            replace_way_q    <= (others => '0');
            replace_way_q(0) <=  '1';                 --  "...01"
          else
            toggle_cond1_v := state_q(STATE_REFILL_B) and xmem_last_w;
            toggle_cond2_v := flushing_q and tag_dirty_any_m_w  and
                              (not evict_way_w) and
                              (not state_q(STATE_FLUSH_ADDR_B));
            --
            if (toggle_cond1_v = '1' or toggle_cond2_v = '1') then
              --
              replace_way_q <= pick_way( tag_valid_m_w, random_num_q );
              --
            elsif ( (state_q(STATE_EVICT_WAIT_B) = '1' and
                     next_state_w(STATE_FLUSH_ADDR_B) = '1') or
                    (state_q(STATE_FLUSH_B) = '1' and
                     next_state_w(STATE_LOOKUP_B) = '1') or
                    (state_q(STATE_LOOKUP_B) = '1' and
                     next_state_w(STATE_FLUSH_ADDR_B) = '1') ) then
              replace_way_q    <= (others => '0');
              replace_way_q(0) <=  '1';               --  "...01"
            elsif (state_q(STATE_WRITEBACK_B) = '1') then
              replace_way_q <= tag_hit_m_w;
            end if;
          end if;
        end if;
      end process p_rep_way;
    end block b_pick_8w;
   end generate g_pick_8w;



  -- - - - - - - - -
  -- output result
  -- - - - - - - - -
  -- data output mux
  p_c_dout : process (data_mdout_w, tag_hit_m_w)
  begin
    rdata_w <= data_mdout_w(0);
    for ii in 0 to WAYS-1 loop
      if (tag_hit_m_w(ii) = '1') then
        rdata_w <= data_mdout_w(ii);
      end if;
    end loop;
  end process p_c_dout;



  g_axi32o : if ( AXIDATA_W /= 64 ) generate
  begin
    cpu_rdata_o <= rdata_w;
  end generate g_axi32o;
  --
  --
  g_axi64o : if ( AXIDATA_W = 64 ) generate
    cpu_rdata_o <= rdata_w(63 downto 32) when (cpu_addr_q(2) = '1') else
                   rdata_w(31 downto 0);
  end generate g_axi64o;




  tag_hit_and_dirty_m_w <= not_zero(tag_hit_m_w and tag_dirty_m_w);

  tag_dirty_any_m_w     <= not_zero(tag_valid_m_w and tag_dirty_m_w);


  -- - - - - - - - - -
  -- next state logic
  -- - - - - - - - - -
  p_c_nxt_st : process (state_q, flush_last_q, tag_dirty_any_m_w, evict_way_w,
                        cpu_rd_q, cpu_wr_q, tag_hit_any_m_w,
                        cpu_wb_i, cpu_accept_w, cpu_flush_i,
                        cpu_invalidate_i, xmem_ack_w, cpu_wb_q,
                        flushing_q, xmem_last_w, xmem_accept_w,
                        tag_hit_and_dirty_m_w)
  begin
    --
    next_state_w <= state_q;
    --
    case state_q is
      -- - - - - - - -
      -- STATE_RESET
      -- - - - - - - -
      when STATE_RESET =>
        -- Final line checked
        if (flush_last_q = '1') then
          next_state_w <= STATE_LOOKUP;
        end if;
        -- - - - - - - - - -
        -- STATE_FLUSH_ADDR
        -- - - - - - - - - -
      when STATE_FLUSH_ADDR =>
        next_state_w <= STATE_FLUSH;
        -- - - - - - - -
        -- STATE_FLUSH
        -- - - - - - - -
      when STATE_FLUSH =>
        -- dirty line detected - evict unless initial cache reset cycle
        if (tag_dirty_any_m_w = '1') then
          -- evict dirty line - else wait for dirty way to be selected
          if (evict_way_w = '1') then
            next_state_w <= STATE_EVICT;
          end if;
          -- final line checked, nothing dirty
        elsif (flush_last_q = '1') then
          next_state_w <= STATE_LOOKUP;
        else
          next_state_w <= STATE_FLUSH_ADDR;
        end if;
      -- - - - - - - -
      -- STATE_LOOKUP
      -- - - - - - - -
      when STATE_LOOKUP =>
        -- previous access missed in the cache
        if ((cpu_rd_q = '1' or (not_zero(cpu_wr_q))) and
             tag_hit_any_m_w = '0') then
          -- evict dirty line first
          if (evict_way_w = '1') then
            next_state_w <= STATE_EVICT;
            -- allocate line and fill
          else
            next_state_w <= STATE_REFILL;
          end if;
        -- writeback a single line
        elsif (cpu_wb_i = '1' and cpu_accept_w = '1') then
            next_state_w <= STATE_WRITEBACK;
          -- flush whole cache
        elsif (cpu_flush_i  = '1' and cpu_accept_w = '1') then
            next_state_w <= STATE_FLUSH_ADDR;
          -- invalidate line (even if dirty)
        elsif (cpu_invalidate_i  = '1' and cpu_accept_w = '1') then
            next_state_w <= STATE_INVALIDATE;
        end if;
      -- - - - - - - -
      -- STATE_REFILL
      -- - - - - - - -
      when STATE_REFILL =>
        -- end of refill
        if (xmem_ack_w = '1' and xmem_last_w = '1') then
          -- refill reason was write
          if (cpu_wr_q /= "0000") then
--          next_state_w <= STATE_WRITE;
            next_state_w <= STATE_LOOKUP;
            -- refill reason was read
          else
            next_state_w <= STATE_READ;
          end if;
        end if;
      -- - - - - - - - - -
      -- STATE_WRITE/READ
      -- - - - - - - - - -
      when  STATE_READ =>  -- | STATE_WRITE =>
        next_state_w <= STATE_LOOKUP;
      -- - - - - - - -
      -- STATE_EVICT
      -- - - - - - - -
      when STATE_EVICT =>
        -- end of evict, wait for write completion
        if (xmem_accept_w = '1' and xmem_last_w = '1') then
          next_state_w <= STATE_EVICT_WAIT;
        end if;
      -- - - - - - - - - -
      -- STATE_EVICT_WAIT
      -- - - - - - - - - -
      when STATE_EVICT_WAIT =>
        -- single line writeback
        if (xmem_ack_w = '1' and cpu_wb_q = '1') then
          next_state_w <= STATE_LOOKUP;
        -- evict due to flush
        elsif (xmem_ack_w = '1' and flushing_q = '1') then
          next_state_w <= STATE_FLUSH_ADDR;
        -- write ack, start re-fill now
        elsif (xmem_ack_w = '1') then
          next_state_w <= STATE_REFILL;
        end if;
      -- - - - - - - - - - - - - - - - - - - - - -
      -- STATE_WRITEBACK: Writeback a cache line
      -- - - - - - - - - - - - - - - - - - - - - -
      when STATE_WRITEBACK =>
        -- line is dirty - write back to memory
        if (tag_hit_and_dirty_m_w = '1') then
          next_state_w <= STATE_EVICT;
        -- Line not dirty, carry on
        else
            next_state_w <= STATE_LOOKUP;
        end if;
      -- - - - - - - - - - - - - - - - - - - - - - -
      -- STATE_INVALIDATE: Invalidate a cache line
      -- - - - - - - - - - - - - - - - - - - - - - -
      when STATE_INVALIDATE =>
        next_state_w <= STATE_LOOKUP;
      when others =>
        null;
    end case;
  end process p_c_nxt_st;



  -- update state
  p_st : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if ( xrst_i = '0' ) then
        state_q <= STATE_RESET;
      else
        state_q <= next_state_w;
      end if;
    end if;
  end process p_st;


  p_c_mack : process (state_q, cpu_rd_q, cpu_wr_q, tag_hit_any_m_w,
                      cpu_flush_q, cpu_inval_q, cpu_wb_q,
                      cpu_wr_ack_q)
  begin
    --
    cpu_ack_w <= '0';
    --
    if (state_q(STATE_LOOKUP_B) = '1') then
      --  normal hit - read or write (lookup)
      if ((cpu_rd_q = '1' or (cpu_wr_q /= "0000")) and
          tag_hit_any_m_w = '1' ) then
        cpu_ack_w <= '1';
      --  flush, invalidate or writeback
      elsif (cpu_flush_q = '1' or cpu_inval_q = '1' or cpu_wb_q = '1') then
        cpu_ack_w <= '1';
      end if;
    elsif ( (state_q(STATE_REFILL_B) = '1' or state_q(STATE_EVICT_B) = '1') and
            cpu_wr_ack_q = '1') then
      --  ack imediately and leave write request in queue
      cpu_ack_w <= '1';               -- write-refill
    end if;
  end process p_c_mack;


  cpu_ack_o   <= cpu_ack_w;


  -- - - - - - - - - - - - - - - -
  -- AXI request
  -- - - - - - - - - - - - - - - -
  p_xmem_rd : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        xmem_rd_q   <= '0';
      else
        if (xmem_rd_w = '1') then
          xmem_rd_q <= not xmem_accept_w;
        end if;
      end if;
    end if;
  end process p_xmem_rd;


  p_xmem_wr0 : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        xmem_wr0_q   <= '0';
      else
        if (state_q(STATE_EVICT_B) /= '1' and
            next_state_w(STATE_EVICT_B) = '1') then
          xmem_wr0_q   <= '1';
        elsif (xmem_accept_w = '1') then
          xmem_wr0_q   <= '0';
        end if;
      end if;
    end if;
  end process p_xmem_wr0;



  p_xmem_len : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        xmem_len_q   <= X"00";
      else
        if ( state_q(STATE_EVICT_B) = '0' and                   -- xmem write
             next_state_w(STATE_EVICT_B) = '1' ) then
          xmem_len_q   <= BURST_LEN_W;                          -- default X"07"
        elsif (state_q(STATE_REFILL_B) = '0' and                -- xmem read
            next_state_w(STATE_REFILL_B) = '1') then
          xmem_len_q   <= BURST_LEN_W;
        elsif (state_q(STATE_REFILL_B) = '1' and xmem_ack_w = '1') then
          --   xmem read
          xmem_len_q   <= dec1u(xmem_len_q);                    --  - 1
        elsif (state_q(STATE_EVICT_B) = '1' and xmem_accept_w = '1') then
          --   xmem write
          xmem_len_q   <= dec1u(xmem_len_q);                    --  - 1
        end if;
      end if;
    end if;
  end process p_xmem_len;


  xmem_1st_w  <= eq(xmem_len_q, BURST_LEN_W) and xmem_ack_w;
  xmem_last_w <= eq(xmem_len_q, X"00");


  -- - - - - - - - - - - - - - - -
  -- skid buffer for write data
  -- - - - - - - - - - - - - - - -
  p_xmem_w : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        xmem_wr_q <= (others => '0');
      else
        if ( not_zero(xmem_wr_w) and xmem_accept_w = '0' ) then
          xmem_wr_q <= xmem_wr_w;
        elsif (xmem_accept_w = '1') then
          xmem_wr_q <= (others => '0');
        end if;
      end if;
    end if;
  end process p_xmem_w;


  p_xmem_wrdat : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        xmem_wdata_q    <= (others => '0');
      else
        if (xmem_accept_w = '0') then
          xmem_wdata_q  <= xmem_wdata_w;
        end if;
      end if;
    end if;
  end process p_xmem_wrdat;


  -- - - - - - - - - - - - - - - -
  -- AXI error handling
  -- - - - - - - - - - - - - - - -
  p_err : process (clk_i)
  begin
    if (clk_i'event and clk_i = '1') then
      if (xrst_i = '0') then
        error_q     <= '0';
      else
        if (xmem_ack_w = '1' and xmem_error_w = '1') then
          error_q   <= '1';
        elsif (cpu_ack_w = '1') then
          error_q   <= '0';
        end if;
      end if;
    end if;
  end process p_err;

  cpu_error_o <= error_q;


  -- - - - - - - - - - - - - - - -
  -- outport
  -- - - - - - - - - - - - - - - -
  refill_request_w  <= '1' when (state_q(STATE_REFILL_B) = '0' and
                                 next_state_w(STATE_REFILL_B) = '1')
                       else '0';

  evict_request_w   <= '1' when (state_q(STATE_EVICT_B) = '1') and
                       (evict_way_w = '1' or cpu_wb_q = '1')
                       else '0';

  -- AXI Read channel
  xmem_rd_w         <= (refill_request_w or xmem_rd_q);

  xmem_wr_w         <= (others => '1') when ( evict_request_w = '1' or
                                              not_zero(xmem_wr_q) )
                       else (others => '0'); -- X"0" / X"00"

  xmem_addr_w       <= (cpu_addr_q(31 downto LINE_SIZE_W) &
                        (LINE_SIZE_W-1 downto 0 => '0'))
                       when (not_zero(xmem_len_w) and xmem_rd_w = '1')
                       else
                       ( evict_addr_w & (LINE_SIZE_W-1 downto 0 => '0') )
                       when (not_zero(xmem_len_w) and xmem_rd_w = '0')
                       else X"00000000";

  --                   default X"07"
  xmem_len_w        <= BURST_LEN_W when (refill_request_w = '1' or
                                         xmem_rd_q = '1' or
                                         (state_q(STATE_EVICT_B) = '1' and
                                          xmem_wr0_q = '1'))
                       else X"00";

  xmem_wdata_w      <= xmem_wdata_q when ( not_zero(xmem_wr_q) )
                       else evict_data_w;

  xmem_wr_o         <= xmem_wr_w;
  xmem_rd_o         <= xmem_rd_w;
  xmem_len_o        <= xmem_len_w;
  xmem_addr_o       <= xmem_addr_w;     -- WORD ADDR, [1:0] "00"
  xmem_wdata_o      <= xmem_wdata_w;

  xmem_accept_w     <= xmem_accept_i;
  xmem_ack_w        <= xmem_ack_i;
  xmem_error_w      <= xmem_error_i;


end rtl;
