//
// Cache module for CS/ECE 552 Project
// Written by Andy Phelps
// 4 May 2006
//
//  Modified by Derek Hower
//  30 Oct 2006
//   Changed to 4-word lines, byte addressable
//
// See the documentation on the class web page.
// Although this module has been tested, it is not guaranteed to work.  
// Please report errors to the TA.
//
module cache (
         input enable,
         input clk,
         input rst,
         input createdump,
         input [4:0] tag_in,
         input [7:0] index,
         input [2:0] offset,
         input [15:0] data_in,
         input comp,
         input write,
         input valid_in,

         output [4:0] tag_out,
         output [15:0] data_out,
         output hit,
         output dirty,
         output valid,
         output err
         );

parameter cache_id = 0;  // overridden for each cache instance
wire [4:0] ram0_id = (cache_id<<3) + 0; // These allow each memory to create a unique dump file
wire [4:0] ram1_id = (cache_id<<3) + 1;
wire [4:0] ram2_id = (cache_id<<3) + 2;
wire [4:0] ram3_id = (cache_id<<3) + 3;
wire [4:0] ram4_id = (cache_id<<3) + 4;
wire [4:0] ram5_id = (cache_id<<3) + 5;

wire [15:0] w0, w1, w2, w3;
assign go = enable & ~rst;
assign match = (tag_in == tag_out);

assign err = offset[0]; //word aligned; odd address is invalid

assign wr_word0 = go & write & ~offset[2] & ~offset[1] & (match | ~comp);
assign wr_word1 = go & write & ~offset[2] &  offset[1] & (match | ~comp);
assign wr_word2 = go & write &  offset[2] & ~offset[1] & (match | ~comp);
assign wr_word3 = go & write &  offset[2] &  offset[1] & (match | ~comp);

assign wr_dirty = go & write & (match | ~comp);
assign wr_tag   = go & write & ~comp;
assign wr_valid = go & write & ~comp;
assign dirty_in = comp;  // a compare-and-write sets dirty; a cache-fill clears it

memc #(16) mem_w0 (w0,      index, data_in,  wr_word0, clk, rst, createdump, ram0_id);
memc #(16) mem_w1 (w1,      index, data_in,  wr_word1, clk, rst, createdump, ram1_id);
memc #(16) mem_w2 (w2,      index, data_in,  wr_word2, clk, rst, createdump, ram2_id);
memc #(16) mem_w3 (w3,      index, data_in,  wr_word3, clk, rst, createdump, ram3_id);

memc #( 5) mem_tg (tag_out, index, tag_in,   wr_tag,   clk, rst, createdump, ram4_id);
memc #( 1) mem_dr (dirtybit,index, dirty_in, wr_dirty, clk, rst, createdump, ram5_id);
memv       mem_vl (validbit,index, valid_in, wr_valid, clk, rst, createdump, ram0_id);

assign hit = go & match;
assign dirty = go & (~write | (comp & ~match)) & dirtybit;
assign data_out = (write | ~go)? 16'h0000 :
                          offset[2] ? (offset[1] ? w3 : w2) : (offset[1] ? w1 : w0) ;
assign valid = go & validbit & (~write | comp);

endmodule


// Memories used by the cache:

module memc (data_out, addr, data_in, write, clk, rst, createdump, file_id);
parameter Size = 1;
output [Size-1:0] data_out;
input [7:0] addr;
input [Size-1:0] data_in;
input write;
input clk;
input rst;
input createdump;
input [4:0] file_id;

reg [Size-1:0] mem [0:255];

integer mcd;
integer i;

assign data_out = (write | rst)? 0 : mem[addr];

always @(posedge clk) begin

  if (rst) begin
    for (i=0; i<256; i=i+1) begin
      mem[i] = 0;
    end
  end

  if (!rst && write) mem[addr] = data_in;
  if (!rst && createdump) begin
    case (file_id)
       0: mcd = $fopen("Icache_0_data_0");
       1: mcd = $fopen("Icache_0_data_1");
       2: mcd = $fopen("Icache_0_data_2");
       3: mcd = $fopen("Icache_0_data_3");
       4: mcd = $fopen("Icache_0_tags");
       5: mcd = $fopen("Icache_0_dirty");
       
       8: mcd = $fopen("Dcache_0_data_0");
       9: mcd = $fopen("Dcache_0_data_1");
      10: mcd = $fopen("Dcache_0_data_2");
      11: mcd = $fopen("Dcache_0_data_3");
      12: mcd = $fopen("Dcache_0_tags");
      13: mcd = $fopen("Dcache_0_dirty");
      
      16: mcd = $fopen("Icache_1_data_0");
      17: mcd = $fopen("Icache_1_data_1");
      18: mcd = $fopen("Icache_1_data_2");
      19: mcd = $fopen("Icache_1_data_3");
      20: mcd = $fopen("Icache_1_tags");
      21: mcd = $fopen("Icache_1_dirty");
      
      24: mcd = $fopen("Dcache_1_data_0");
      25: mcd = $fopen("Dcache_1_data_1");
      26: mcd = $fopen("Dcache_1_data_2");
      27: mcd = $fopen("Dcache_1_data_3");
      28: mcd = $fopen("Dcache_1_tags");
      29: mcd = $fopen("Dcache_1_dirty");
      default: $display("Unknown file_id %d", file_id);
    endcase
    for (i=0; i<256; i=i+1) begin
      $fdisplay(mcd,"%2h %4h", i, mem[i]);
    end
    $fclose(mcd);
  end
end
endmodule

// Separate version for the valid bit because of flash clear:

module memv (data_out, addr, data_in, write, clk, rst, createdump, file_id);
output data_out;
input [7:0] addr;
input data_in;
input write;
input clk;
input rst;
input createdump;
input [4:0] file_id;

reg mem [0:255];

integer mcd;
integer i;

assign data_out = (write | rst)? 0 : mem[addr];

always @(posedge clk) begin
  if (rst) begin
    for (i=0; i<256; i=i+1) begin
      mem[i] = 0; // in hardware this would be a special flash-clear wire!
    end
  end
  if (!rst && write) mem[addr] = data_in;
  if (!rst && createdump) begin
    case (file_id)
       0: mcd = $fopen("Icache_0_valid");
       8: mcd = $fopen("Dcache_0_valid");
      16: mcd = $fopen("Icache_1_valid");
      24: mcd = $fopen("Dcache_1_valid");
      default: $display("Unknown (v) file_id %d", file_id);
    endcase
    for (i=0; i<256; i=i+1) begin
      $fdisplay(mcd,"%2h %4h", i, mem[i]);
    end
    $fclose(mcd);
  end
end
endmodule
