//////////////////////////////////////////////////////////////////////////////////
//
// This file is part of the Next186 Soc PC project
// http://opencores.org/project,next186
//
// Filename: cache_controller.v
// Description: Part of the Next186 SoC PC project, cache controller
// Version 1.0
// Creation date: Jan2012
//
// Author: Nicolae Dumitrache 
// e-mail: ndumitrache@opencores.org
//
/////////////////////////////////////////////////////////////////////////////////
// 
// Copyright (C) 2012 Nicolae Dumitrache
// 
// This source file may be used and distributed without 
// restriction provided that this copyright statement is not 
// removed from the file and that any derivative work contains 
// the original copyright notice and the associated disclaimer.
// 
// This source file is free software; you can redistribute it 
// and/or modify it under the terms of the GNU Lesser General 
// Public License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any 
// later version. 
// 
// This source 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 Lesser General Public License for more 
// details. 
// 
// You should have received a copy of the GNU Lesser General 
// Public License along with this source; if not, download it
// from http://www.opencores.org/lgpl.shtml 
// 
///////////////////////////////////////////////////////////////////////////////////
// Additional Comments: 
//
// preloaded with bootstrap code
//////////////////////////////////////////////////////////////////////////////////

`timescale 1ns / 1ps
`define WAYS	2	// 2^ways
`define SETS	6	// 2^sets
`define LINE	6	// 2^LINE bytes / cache line
`define ADDR	21

module cache_controller(
	 input [`ADDR-1:0]addr,
     output [31:0]dout,
	 input [31:0]din,
	 input clk,	
	 input mreq,
	 input [3:0]wmask,
	 output reg ce = 1'b1,	// clock enable for CPU
	 input [15:0]ddr_din,
	 output reg[15:0]ddr_dout,
	 input ddr_clk,
	 input cache_write_data, // 1 when data must be written to cache, on posedge ddr_clk
	 input cache_read_data, // 1 when data must be read from cache, on posedge ddr_clk 
	 output reg ddr_rd = 0,
	 output reg ddr_wr = 0,
	 output reg [`ADDR-`LINE-1:0]hiaddr,
	 input flush
    );
	
	initial ce = 1'b1;
	
	reg [`ADDR-1:0]raddr;
	reg [31:0]rdin;
	reg [3:0]rwmask;
	reg rmreq;
	wire [`ADDR-1:0]maddr = ce ? addr : raddr;
	wire [31:0]mdin = ce ? din : rdin;
	wire [3:0]mwmask = ce ? wmask : rwmask;
	wire mmreq = ce ? mreq : rmreq;
	
	reg flushreq = 1'b0;
	reg [`WAYS+`SETS:0]flushcount = 0;
	wire r_flush = flushcount[`WAYS+`SETS];
	wire [`SETS-1:0]index = r_flush ? flushcount[`SETS-1:0] : maddr[`LINE+`SETS-1:`LINE];
	wire [(1<<`WAYS)-1:0]fit;
	wire [(1<<`WAYS)-1:0]free;
	
	reg [(1<<`WAYS)-1:0]cache_dirty[0:(1<<`SETS)-1];/* = 
		{4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0, 4'h0,
		 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1, 4'h1};*/
	initial $readmemh("cache_init_dirty.mem", cache_dirty);

/*	reg [`WAYS-1:0]cache_lru[0:(1<<`WAYS)-1][0:(1<<`SETS)-1] =
		{{2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0, 2'h0},
		 {2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1, 2'h1},
		 {2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2, 2'h2},
		 {2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3, 2'h3}};*/

	reg [`WAYS-1:0]cache_lru0[0:(1<<`SETS)-1];
	reg [`WAYS-1:0]cache_lru1[0:(1<<`SETS)-1];
	reg [`WAYS-1:0]cache_lru2[0:(1<<`SETS)-1];
	reg [`WAYS-1:0]cache_lru3[0:(1<<`SETS)-1];
	initial $readmemh("cache_init0_lru.mem", cache_lru0);
	initial $readmemh("cache_init1_lru.mem", cache_lru1);
	initial $readmemh("cache_init2_lru.mem", cache_lru2);
	initial $readmemh("cache_init3_lru.mem", cache_lru3);
		 
/*	reg [`ADDR-`SETS-`LINE-1:0]cache_addr[0:(1<<`WAYS)-1][0:(1<<`SETS)-1]=
		{{10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff, 10'h1ff},
		 {10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000, 10'h000},
		 {10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001, 10'h001},
		 {10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h003, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002, 10'h002}};*/
	reg [`ADDR-`SETS-`LINE-1:0]cache_addr0[0:(1<<`SETS)-1];
	reg [`ADDR-`SETS-`LINE-1:0]cache_addr1[0:(1<<`SETS)-1];
	reg [`ADDR-`SETS-`LINE-1:0]cache_addr2[0:(1<<`SETS)-1];
	reg [`ADDR-`SETS-`LINE-1:0]cache_addr3[0:(1<<`SETS)-1];
	initial $readmemh("cache_init0_addr.mem", cache_addr0);
	initial $readmemh("cache_init1_addr.mem", cache_addr1);
	initial $readmemh("cache_init2_addr.mem", cache_addr2);
	initial $readmemh("cache_init3_addr.mem", cache_addr3);

	reg [2:0]STATE = 0;
	reg [`LINE-2:0]lowaddr = 0; //cache mem address
	reg s_lowaddr5 = 0;
	wire [31:0]cache_QA;
	wire [`WAYS-1:0]lru[(1<<`WAYS)-1:0];

//	genvar i;
//	for(i=0; i<(1<<`WAYS); i=i+1) begin
//		assign fit[i] = ~r_flush && (cache_addr[i][index] == maddr[`ADDR-1:`LINE+`SETS]);
//	end
	assign fit[0] = ~r_flush && (cache_addr0[index] == maddr[`ADDR-1:`LINE+`SETS]);
	assign fit[1] = ~r_flush && (cache_addr1[index] == maddr[`ADDR-1:`LINE+`SETS]);
	assign fit[2] = ~r_flush && (cache_addr2[index] == maddr[`ADDR-1:`LINE+`SETS]);
	assign fit[3] = ~r_flush && (cache_addr3[index] == maddr[`ADDR-1:`LINE+`SETS]);
	assign free[0] = r_flush ? (flushcount[`WAYS+`SETS-1:`SETS] == 0) : ~|cache_lru0[index];
	assign free[1] = r_flush ? (flushcount[`WAYS+`SETS-1:`SETS] == 1) : ~|cache_lru1[index];
	assign free[2] = r_flush ? (flushcount[`WAYS+`SETS-1:`SETS] == 2) : ~|cache_lru2[index];
	assign free[3] = r_flush ? (flushcount[`WAYS+`SETS-1:`SETS] == 3) : ~|cache_lru3[index];
	assign lru[0] = {`WAYS{fit[0]}} & cache_lru0[index];
	assign lru[1] = {`WAYS{fit[1]}} & cache_lru1[index];
	assign lru[2] = {`WAYS{fit[2]}} & cache_lru2[index];
	assign lru[3] = {`WAYS{fit[3]}} & cache_lru3[index];

	wire hit = |fit;
	wire st0 = STATE == 3'b000;
//	assign ce = st0 && (~mreq || hit);
	wire dirty = |(free & cache_dirty[index]);	

	wire [`WAYS-1:0]blk = flushcount[`WAYS+`SETS-1:`SETS] | {|fit[3:2], fit[3] | fit[1]};
	wire [`WAYS-1:0]fblk = {|free[3:2], free[3] | free[1]};
	wire [`WAYS-1:0]csblk = lru[0] | lru[1] | lru[2] | lru[3];

	always @(posedge ddr_clk) begin
		if(cache_write_data || cache_read_data) lowaddr <= lowaddr + 1'b1;
		ddr_dout <= lowaddr[0] ? cache_QA[15:0] : cache_QA[31:16];
	end
		
	cache cache_mem (
	  .ClockA(ddr_clk), // input clka
	  .ClockEnA(cache_write_data | cache_read_data),
	  .ByteEnA({lowaddr[0], lowaddr[0], ~lowaddr[0], ~lowaddr[0]}),
	  .WrA(cache_write_data),
	  .AddressA({blk, ~index[`SETS-1:10-`LINE], index[10-`LINE-1:0], lowaddr[`LINE-2:1]}), // input [11 : 0] addra
	  .DataInA({ddr_din, ddr_din}), // input [31 : 0] dina
	  .QA(cache_QA), // output [31 : 0] douta
	  .ClockB(clk), // input clkb
	  .ByteEnB(mwmask),
	  .ClockEnB(mmreq && hit && st0),
	  .WrB(mmreq && hit && st0), 
	  .AddressB({blk, ~index[`SETS-1:10-`LINE], index[10-`LINE-1:0], maddr[`LINE-1:2]}), // input [11 : 0] addrb
	  .DataInB(mdin), // input [31 : 0] dinb
	  .QB(dout), // output [31 : 0] doutb
	  .ResetA(1'b0),
	  .ResetB(1'b0)
	);


//	for(i=0; i<(1<<`WAYS); i=i+1)
		always @(posedge clk)
			if(st0 && mmreq)
				if(hit) begin
					cache_lru0[index] <= fit[0] ? {`WAYS{1'b1}} : cache_lru0[index] - (cache_lru0[index] > csblk); 
					cache_lru1[index] <= fit[1] ? {`WAYS{1'b1}} : cache_lru1[index] - (cache_lru1[index] > csblk); 
					cache_lru2[index] <= fit[2] ? {`WAYS{1'b1}} : cache_lru2[index] - (cache_lru2[index] > csblk); 
					cache_lru3[index] <= fit[3] ? {`WAYS{1'b1}} : cache_lru3[index] - (cache_lru3[index] > csblk); 
					if(fit[0]) cache_dirty[index][0] <= cache_dirty[index][0] | (|mwmask);
					if(fit[1]) cache_dirty[index][1] <= cache_dirty[index][1] | (|mwmask);
					if(fit[2]) cache_dirty[index][2] <= cache_dirty[index][2] | (|mwmask);
					if(fit[3]) cache_dirty[index][3] <= cache_dirty[index][3] | (|mwmask);
				end else begin
					if(free[0]) cache_dirty[index][0] <= 1'b0;
					if(free[1]) cache_dirty[index][1] <= 1'b0;
					if(free[2]) cache_dirty[index][2] <= 1'b0;
					if(free[3]) cache_dirty[index][3] <= 1'b0;
				end

		
	always @(posedge clk) begin
		s_lowaddr5 <= lowaddr[`LINE-2];
		flushreq <= ~flushcount[`WAYS+`SETS] & (flushreq | flush);
		if(ce) begin
			raddr <= addr;
			rdin <= din;
			rwmask <= wmask;
			rmreq <= mreq;
		end
		
		case(STATE)
			3'b000: begin
				case(fblk)
					0: hiaddr <= dirty ? {cache_addr0[index], index} : maddr[`ADDR-1:`LINE];
					1: hiaddr <= dirty ? {cache_addr1[index], index} : maddr[`ADDR-1:`LINE];
					2: hiaddr <= dirty ? {cache_addr2[index], index} : maddr[`ADDR-1:`LINE];
					3: hiaddr <= dirty ? {cache_addr3[index], index} : maddr[`ADDR-1:`LINE];
				endcase
				if(mmreq && !hit) begin	// cache miss
					if(!r_flush) case(fblk)
						0: cache_addr0[index] <= maddr[`ADDR-1:`LINE+`SETS];
						1: cache_addr1[index] <= maddr[`ADDR-1:`LINE+`SETS];
						2: cache_addr2[index] <= maddr[`ADDR-1:`LINE+`SETS];
						3: cache_addr3[index] <= maddr[`ADDR-1:`LINE+`SETS];
					endcase
//					waddr <= {cache_addr[fblk][index], index}; 
//					if(!r_flush) cache_addr[fblk][index] <= maddr[20:8+`SETS];
					ddr_rd <= ~dirty & ~r_flush;
					ddr_wr <= dirty;
					STATE <= dirty ? 3'b011 : 3'b100;
					ce <= 1'b0;
				end else begin
					flushcount[`WAYS+`SETS] <= flushcount[`WAYS+`SETS] | flushreq;
					ce <= 1'b1;
				end
			end
			3'b011: begin	// write cache to ddr
				ddr_rd <= ~r_flush; //1'b1;
				if(s_lowaddr5) begin
					ddr_wr <= 1'b0;
					STATE <= 3'b111;
				end
			end
			3'b111: begin // read cache from ddr
				hiaddr <= maddr[`ADDR-1:`LINE];
				if(~s_lowaddr5) STATE <= 3'b100;
			end
			3'b100: begin	
				if(r_flush) begin
					flushcount <= flushcount + 1'b1;
					STATE <= 3'b000;
				end else if(s_lowaddr5) STATE <= 3'b101;
			end
			3'b101: begin
				ddr_rd <= 1'b0;
				if(~s_lowaddr5) STATE <= 3'b000;
			end
		endcase
	end
	
endmodule


module seg_map(
	 input CLK,
	 input [3:0]cpuaddr,
	 output [8:0]cpurdata,
	 input [8:0]cpuwdata,
	 input [4:0]memaddr,
	 output [8:0]memdata,
	 input WE,
	 input [3:0]seg_addr,
	 output vga_planar_seg
    );

	reg [8:0]map[0:31];/* = {9'h000, 9'h001, 9'h002, 9'h003, 9'h004, 9'h005, 9'h006, 9'h007, 9'h008, 9'h009,
								 9'h00a, 9'h00b,	// VGA seg 1 and 2
								 9'h012, 9'h013, 9'h014, 9'h015,
								 9'h016,	// HMA
								 9'h001, 9'h002, 9'h003, 9'h004, 9'h005, 9'h006, 9'h007, 9'h008, 9'h009, 
								 9'h00a, 9'h00b, 9'h00c, 9'h00d, 9'h00e, 9'h00f}; // VGA seg 1..6								 */
	reg [15:0]vga_seg = 16'h0000;
	assign memdata = map[memaddr];
	assign cpurdata = map[{1'b0, cpuaddr}];
	assign vga_planar_seg = vga_seg[seg_addr];
	initial $readmemh("segmap.mem", map);
	
	always @(posedge CLK) 
		if(WE) begin
			map[{1'b0, cpuaddr}] <= cpuwdata;
			vga_seg[cpuaddr] <= cpuwdata == 9'ha;
		end

endmodule
