///////////////////////////////////////////
// cacheway
//
// Written: ross1728@gmail.com July 07, 2021
//          Implements the data, tag, valid, dirty, and replacement bits.
//
// Purpose: Storage and read/write access to data cache data, tag valid, dirty, and replacement.
// 
// A component of the CORE-V-WALLY configurable RISC-V project.
// 
// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University
//
// 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.
////////////////////////////////////////////////////////////////////////////////////////////////

`include "wally-config.vh"

module cacheway #(parameter NUMLINES=512, LINELEN = 256, TAGLEN = 26,
				          OFFSETLEN = 5, INDEXLEN = 9, DIRTY_BITS = 1) (
  input  logic                        clk,
  input  logic                        CacheEn,
  input  logic                        reset,
  input  logic [$clog2(NUMLINES)-1:0] CAdr,
  input  logic [`PA_BITS-1:0]         PAdr,
  input  logic [LINELEN-1:0]          LineWriteData,
  input  logic                        SetValid,
  input  logic                        ClearValid,
  input  logic                        SetDirty,
  input  logic                        ClearDirty,
  input  logic                        SelWriteback,
  input  logic                        SelFlush,
  input  logic                        VictimWay,
  input  logic                        FlushWay,
  input  logic                        InvalidateCache,
  input  logic                        FlushStage,
  input  logic [LINELEN/8-1:0]        LineByteMask,

  output logic [LINELEN-1:0]          ReadDataLineWay,
  output logic                        HitWay,
  output logic                        ValidWay,
  output logic                        DirtyWay,
  output logic [TAGLEN-1:0]           TagWay);

  localparam integer                  WORDSPERLINE = LINELEN/`XLEN;
  localparam integer                  BYTESPERLINE = LINELEN/8;
  localparam                          LOGWPL = $clog2(WORDSPERLINE);
  localparam                          LOGXLENBYTES = $clog2(`XLEN/8);
  localparam integer                  BYTESPERWORD = `XLEN/8;

  logic [NUMLINES-1:0]                ValidBits;
  logic [NUMLINES-1:0]                DirtyBits;
  logic [LINELEN-1:0]                 ReadDataLine;
  logic [TAGLEN-1:0]                  ReadTag;
  logic                               Dirty;
  logic                               SelTag;
  logic                               SelectedWriteWordEn;
  logic [LINELEN/8-1:0]               FinalByteMask;
  logic                               SetValidEN;
  logic                               SetValidWay;
  logic                               ClearValidWay;
  logic                               SetDirtyWay;
  logic                               ClearDirtyWay;
  logic                               SelNonHit;
  logic                               SelData;
  logic                               FlushWayEn, VictimWayEn;

  // FlushWay and VictimWay are part of a one hot way selection.  Must clear them if FlushWay not selected
  // or VictimWay not selected.
  assign FlushWayEn = FlushWay & SelFlush;
  assign VictimWayEn = VictimWay & SelWriteback;
  
  assign SelNonHit = FlushWayEn | SetValid | SelWriteback;
  
  mux2 #(1) seltagmux(VictimWay, FlushWay, SelFlush, SelTag);
  //assign SelTag = VictimWay | FlushWay;
  //assign SelData = HitWay | FlushWayEn | VictimWayEn;
  
  mux2 #(1) selectedwaymux(HitWay, SelTag, SelNonHit , SelData);

  /////////////////////////////////////////////////////////////////////////////////////////////
  // Write Enable demux
  /////////////////////////////////////////////////////////////////////////////////////////////

  // RT: Can we merge these two muxes?  This is also shared in cacheLRU.
  //mux3 #(1) selectwaymux(HitWay, VictimWay, FlushWay,     {SelFlush, SetValid}, SelData);
  //mux3 #(1) selecteddatamux(HitWay, VictimWay, FlushWay, {SelFlush, SelNonHit}, SelData);

  assign SetValidWay = SetValid & SelData;
  assign ClearValidWay = ClearValid & SelData;
  assign SetDirtyWay = SetDirty & SelData;
  assign ClearDirtyWay = ClearDirty & SelData;
  
  // If writing the whole line set all write enables to 1, else only set the correct word.
  assign SelectedWriteWordEn = (SetValidWay | SetDirtyWay) & ~FlushStage;
  assign FinalByteMask = SetValidWay ? '1 : LineByteMask; // OR
  assign SetValidEN = SetValidWay & ~FlushStage;

  /////////////////////////////////////////////////////////////////////////////////////////////
  // Tag Array
  /////////////////////////////////////////////////////////////////////////////////////////////

  ram1p1rwbe #(.DEPTH(NUMLINES), .WIDTH(TAGLEN)) CacheTagMem(.clk, .ce(CacheEn),
    .addr(CAdr), .dout(ReadTag), .bwe('1),
    .din(PAdr[`PA_BITS-1:OFFSETLEN+INDEXLEN]), .we(SetValidEN));

  

  // AND portion of distributed tag multiplexer
  assign TagWay = SelTag ? ReadTag : '0; // AND part of AOMux
  assign DirtyWay = SelTag & Dirty & ValidWay;
  assign HitWay = ValidWay & (ReadTag == PAdr[`PA_BITS-1:OFFSETLEN+INDEXLEN]);

  /////////////////////////////////////////////////////////////////////////////////////////////
  // Data Array
  /////////////////////////////////////////////////////////////////////////////////////////////

  genvar                             words;

  localparam integer           SRAMLEN = 128;
  localparam integer           NUMSRAM = LINELEN/SRAMLEN;
  localparam integer           SRAMLENINBYTES = SRAMLEN/8;
  localparam integer           LOGNUMSRAM = $clog2(NUMSRAM);
  
  for(words = 0; words < NUMSRAM; words++) begin: word
    ram1p1rwbe #(.DEPTH(NUMLINES), .WIDTH(SRAMLEN)) CacheDataMem(.clk, .ce(CacheEn), .addr(CAdr),
      .dout(ReadDataLine[SRAMLEN*(words+1)-1:SRAMLEN*words]),
      .din(LineWriteData[SRAMLEN*(words+1)-1:SRAMLEN*words]),
      .we(SelectedWriteWordEn), .bwe(FinalByteMask[SRAMLENINBYTES*(words+1)-1:SRAMLENINBYTES*words]));
  end

  // AND portion of distributed read multiplexers
  assign ReadDataLineWay = SelData ? ReadDataLine : '0;  // AND part of AO mux.

  /////////////////////////////////////////////////////////////////////////////////////////////
  // Valid Bits
  /////////////////////////////////////////////////////////////////////////////////////////////
  
  always_ff @(posedge clk) begin // Valid bit array, 
    if (reset) ValidBits        <= #1 '0;
    if(CacheEn) begin 
	  ValidWay <= #1 ValidBits[CAdr];
	  if(InvalidateCache & ~FlushStage)                    ValidBits <= #1 '0;
      else if (SetValidEN | (ClearValidWay & ~FlushStage)) ValidBits[CAdr] <= #1 SetValidWay;
    end
  end

  /////////////////////////////////////////////////////////////////////////////////////////////
  // Dirty Bits
  /////////////////////////////////////////////////////////////////////////////////////////////

  // Dirty bits
  if (DIRTY_BITS) begin:dirty
    always_ff @(posedge clk) begin
      // reset is optional.  Consider merging with TAG array in the future.
      //if (reset) DirtyBits <= #1 {NUMLINES{1'b0}}; 
      if(CacheEn) begin
        Dirty <= #1 DirtyBits[CAdr];
        if((SetDirtyWay | ClearDirtyWay) & ~FlushStage) DirtyBits[CAdr] <= #1 SetDirtyWay;
      end
    end
  end else assign Dirty = 1'b0;


endmodule


