//******************************************************************************
//                                                                             *
// Copyright (C) 2010 Regents of the University of California.                 *
//                                                                             *
// The information contained herein is the exclusive property of the VCL       *
// group but may be used and/or modified for non-comercial purposes if the     *
// author is acknowledged.  For all other uses, permission must be attained    *
// by the VLSI Computation Lab.                                                *
//                                                                             *
// This work has been developed by members of the VLSI Computation Lab         *
// (VCL) in the Department of Electrical and Computer Engineering at           *
// the University of California at Davis.  Contact: bbaas@ece.ucdavis.edu      *
//******************************************************************************
// FIFO.v
//
// 16-bit by 32,  dual-clock circular FIFO for interfacing at clock boundaries
//
// $Id: FIFO.v,v 1.0 7/19/2010 02:15:36 astill Exp $
// Written by: Aaron Stillmaker
//
// Origional AsAP FIFO Written by: Ryan Apperson 
// First In First Out circuitry:
// Main goal in rewriting was to have the whole FIFO in one file and not be
// AsAP specific.  I started fresh writing most code from scratch using 
// Ryan's thesis as a guide, some of code was used from his origional  
// code, and some of the new code was modeled after the origional code.
//

// Define FIFO Address width minus 1 and Data word width minus 1

`define ADDR_WIDTH_M1 6
`define DATA_WIDTH_M1 15

`timescale 10ps/1ps
`celldefine
module FIFO (
   reserve,         // reserve space constant
   wr_sync_cntrl,   // Config input for wr side synchronizer
   clk_wr,          // clock coming from write side of FIFO -- write signals
   data_in,         // data to be written  
   wr_valid,        // write side data is valid for writing to FIFO
   delay_sel,       // choose one/two delay cell for input data
   wr_request,      // low= Full or utilizing reserve space, else NOT FULL
   async_empty,     // true if empty, but referenced to write side
   reset,           // synchronous to read clock  --------------------------
   clk_rd,          // clock coming from read side of FIFO  -- read signals 
   data_out,        // data to be read  
   empty,           // FIFO is EMPTY (combinational in from 1st stage of FIFO)
   rd_request,      // asks the FIFO for data
   async_full,      // true if FIFO is in reserve, but referenced to read side
   rd_sync_cntrl,   // Config input for rd side synchronizer
   nap,              // no increment read pointer signal
   fifo_util        // FIFO utilization, used for DVFS
   );


   // I/O %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

   input [1:0]               delay_sel;
   input [`DATA_WIDTH_M1:0]  data_in;
   
   input [`ADDR_WIDTH_M1:0]  reserve;
   input [2:0]               wr_sync_cntrl;   
   input [2:0]               rd_sync_cntrl;    

   input                     clk_wr,
                             clk_rd, 
                             reset,
                             wr_valid, 
                             rd_request,
                             nap;

   output [`DATA_WIDTH_M1:0] data_out;  
   
   output [1:0]              fifo_util;

   output                    empty, 
                             wr_request,
                             async_full,
                             async_empty;


   // Internal Wires %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   wire [`ADDR_WIDTH_M1:0]   temp_adder_out, // temporary address out
                             rd_ptr_on_wr,
                             wr_ptr_gray;    // write pointer in gray code
							
   wire [`DATA_WIDTH_M1:0]   data_out_c;     // data out from memory
				
	
   wire                      wr_conv_temp1,  // temporary wires used in gray
                             wr_conv_temp2,  //to binary conversions
                             rd_conv_temp1,
                             rd_conv_temp2,
							 rd_en,          // read inable flag
                             rd_inc;         // read increment flag
	
   // Internal Registers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   reg  [`ADDR_WIDTH_M1:0]   wr_ptr,         // write pointer
                             rd_ptr,         // read pointer
                             rd_ptr_gray,    // read pointer in gray code
                             wr_ptr_gray_d1, // delayed write ptr in gray code
                             wr_ptr_gray_d2,
                             rd_ptr_gray_d1, // delayed read ptr in gray code
                             rd_ptr_gray_d2,
                             rd_ptr_gray_on_wr,
                             wr_ptr_gray_on_rd,
                             wr_ptr_on_rd,
                             wr_Reg1,        // registered pointers
                             wr_Reg2,
                             wr_Reg3,
                             wr_Reg4,
                             wr_RegS,
                             rd_Reg1,
                             rd_Reg2,
                             rd_Reg3,
                             rd_Reg4,
                             rd_RegS;
							
   reg  [`DATA_WIDTH_M1:0]   data_out,       // data output
                             data_in_d,
                             data_inREG1,    // registered data in values
                             data_inREG2,
                             data_inREG3;


	
   reg                       wr_hold_r,      // delayed write hold value
                             wr_valid_d,
                             validREG1,      // registered valid values
                             validREG2,
                             validREG3;
	
   // Main %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

	
	
   // Write Logic %%%
   
	
   // Temporary wires used in the Gray Code to Binary Converter
		
   assign wr_conv_temp1 = (rd_ptr_gray_on_wr[1]^rd_ptr_gray_on_wr[2]);
   assign wr_conv_temp2 = (rd_ptr_gray_on_wr[1]^(~rd_ptr_gray_on_wr[2]));
	
	
   // Reserve Logic Calculation, if the MSB is 1, hold.
   //Accordingly assign the wr request output and async full

   assign temp_adder_out = wr_ptr_on_rd - rd_ptr + {1'b0, reserve};
   assign async_full = temp_adder_out[`ADDR_WIDTH_M1];
   assign wr_request = ~wr_hold_r;
	
			
   // Asynchronous Communication of RD address pointer from RD side to WR side
		
   always @(wr_sync_cntrl or rd_ptr_gray_d2 or wr_Reg1 or wr_Reg2 or wr_Reg3 or
            wr_Reg4) begin
      case(wr_sync_cntrl)        
         3'b000:   wr_RegS = rd_ptr_gray_d2;
         3'b100:   wr_RegS = wr_Reg1;
         3'b101:   wr_RegS = wr_Reg2;
         3'b110:   wr_RegS = wr_Reg3;
         3'b111:   wr_RegS = wr_Reg4;
         default:  wr_RegS = 7'bxxxxxxx;
      endcase
   end

   
   always @(posedge clk_wr or posedge reset) begin


      // Binary Incrementer %%
	  
      // Asynchronous Communication of RD address pointer from RD side to
      //WR side %%
			
      if (reset) begin                //reset address FFs
         wr_ptr <= 7'b0000000;
         wr_ptr_gray_d1 <= #1 7'b0000000;
         wr_ptr_gray_d2 <= #1 7'b0000000;
         wr_hold_r	<= #1 1'b0;
		 
         wr_Reg1  <= 7'b0000000;
         wr_Reg2  <= 7'b0000000;
         wr_Reg3  <= 7'b0000000;
         wr_Reg4  <= 7'b0000000;
         rd_ptr_gray_on_wr <= 7'b0000000;
		 
         // Insert delay to avoid the holdtime violation

         case	(delay_sel)
            0: begin
               wr_valid_d <= wr_valid;
               data_in_d <= data_in;
            end
            1: begin
               wr_valid_d <= validREG1;
               data_in_d <= data_inREG1;
            end
            2: begin
               wr_valid_d <= validREG2;
               data_in_d <= data_inREG2;
            end
            3: begin
               wr_valid_d <= validREG3;
               data_in_d <= data_inREG3;
            end
            default: begin
               wr_valid_d <= wr_valid;
               data_in_d <=	data_in;
            end
	
         endcase
		 
      end 
	  else begin
         wr_ptr <= #1 wr_ptr + wr_valid_d;
         wr_ptr_gray_d1 <= #1 wr_ptr_gray;
         wr_ptr_gray_d2 <= #1 wr_ptr_gray_d1;
         wr_hold_r <= #1 async_full;
		 
         wr_Reg1  <= #1 rd_ptr_gray_d2;
         wr_Reg2  <= #1 wr_Reg1;
         wr_Reg3  <= #1 wr_Reg2;
         wr_Reg4  <= #1 wr_Reg3;

         validREG1  <= #1 wr_valid;
         validREG2  <= #1 validREG1;
         validREG3  <= #1 validREG2;

         data_inREG1  <= #1 data_in;
         data_inREG2  <= #1 data_inREG1;
         data_inREG3  <= #1 data_inREG2;

         rd_ptr_gray_on_wr <= wr_RegS;
		 
		 
         // Insert delay to avoid the holdtime violation

         case	(delay_sel)
            0: begin
               wr_valid_d <= wr_valid;
               data_in_d <= data_in;
            end
            1: begin
               wr_valid_d <= validREG1;
               data_in_d <= data_inREG1;
            end
            2: begin
               wr_valid_d <= validREG2;
               data_in_d <= data_inREG2;
            end
            3: begin
               wr_valid_d <= validREG3;
               data_in_d <= data_inREG3;
            end
            default: begin
               wr_valid_d <= wr_valid;
               data_in_d <=	data_in;
            end
	
         endcase
		 
      end
	  
   end
   
   
   // Binary to Gray Code Converter %%
   
   assign wr_ptr_gray[0] = wr_ptr[0]^wr_ptr[1];
   assign wr_ptr_gray[1] = wr_ptr[1]^wr_ptr[2];
   assign wr_ptr_gray[2] = wr_ptr[2]^wr_ptr[3];
   assign wr_ptr_gray[3] = wr_ptr[3]^wr_ptr[4];
   assign wr_ptr_gray[4] = wr_ptr[4]^wr_ptr[5];
   assign wr_ptr_gray[5] = wr_ptr[5]^wr_ptr[6];
   assign wr_ptr_gray[6] = wr_ptr[6];
   

   // Gray Code to Binary Converter %%

   assign rd_ptr_on_wr[6] = rd_ptr_gray_on_wr[6];
   assign rd_ptr_on_wr[5] = rd_ptr_gray_on_wr[5]^rd_ptr_gray_on_wr[6];
   assign rd_ptr_on_wr[4] = rd_ptr_gray_on_wr[4]^rd_ptr_on_wr[5];
   assign rd_ptr_on_wr[3] = rd_ptr_gray_on_wr[3]^rd_ptr_on_wr[4];
		
   assign rd_ptr_on_wr[2] = rd_ptr_on_wr[3] ? ~rd_ptr_gray_on_wr[2] :
                        rd_ptr_gray_on_wr[2];
   assign rd_ptr_on_wr[1] = rd_ptr_on_wr[3] ? wr_conv_temp2 : wr_conv_temp1;
   assign rd_ptr_on_wr[0] = rd_ptr_on_wr[3] ? wr_conv_temp2^rd_ptr_gray_on_wr[0]
                            : wr_conv_temp1^rd_ptr_gray_on_wr[0];



   // Read Logic %%%
	
	
   // Temporary wires used in the Gray Code to Binary Converter
		
   assign rd_conv_temp1 = (wr_ptr_gray_on_rd[1]^wr_ptr_gray_on_rd[2]);
   assign rd_conv_temp2 = (wr_ptr_gray_on_rd[1]^(~wr_ptr_gray_on_rd[2]));
	
	
   // Read Enable Logic
	
   assign rd_en = ~empty & rd_request;
	
	
   // Increment Enable Logic
	
   assign rd_inc	= rd_en & ~nap;
	
	
   // Empty Logic, see if the next value for the read pointer would be empty
	
   assign empty = (rd_ptr + 1 == wr_ptr_on_rd) & ~nap;
	
			
   // Asynchronous Communication of WR address pointer from WR side to RD side
		
   always @(rd_sync_cntrl or wr_ptr_gray_d2 or rd_Reg1 or rd_Reg2 or rd_Reg3 or
            rd_Reg4) begin
      case(rd_sync_cntrl)
         3'b000: rd_RegS = wr_ptr_gray_d2;
         3'b100: rd_RegS = rd_Reg1;
         3'b101: rd_RegS = rd_Reg2;
         3'b110: rd_RegS = rd_Reg3;
         3'b111: rd_RegS = rd_Reg4;
         default: rd_RegS = 7'bxxxxxxx;
      endcase
   end
	
	
   always @(posedge clk_rd) begin

      // Binary Incrementers %%

      if (reset) begin
         rd_ptr <= #1 7'b1111111;
         wr_ptr_on_rd = 7'b0000000;
         rd_ptr_gray <= #1 7'b0000000;
         rd_ptr_gray_d1 <= #1 7'b0000000;
         rd_ptr_gray_d2 <= #1 7'b0000000;
      end else begin
         rd_ptr <= #1 rd_ptr + rd_inc;
         rd_ptr_gray_d1 <= #1 rd_ptr_gray;
         rd_ptr_gray_d2 <= #1 rd_ptr_gray_d1;
      end
		
      // Binary to Gray Code Converter %%
		
      rd_ptr_gray[0] <= rd_ptr[0]^rd_ptr[1];
      rd_ptr_gray[1] <= rd_ptr[1]^rd_ptr[2];
      rd_ptr_gray[2] <= rd_ptr[2]^rd_ptr[3];
      rd_ptr_gray[3] <= rd_ptr[3]^rd_ptr[4];
      rd_ptr_gray[4] <= rd_ptr[4]^rd_ptr[5];
      rd_ptr_gray[5] <= rd_ptr[5]^rd_ptr[6];
      rd_ptr_gray[6] <= rd_ptr[6];
		
		
      // Asynchronous Communication of WR address ptr from WR side to RD side %%
		
      if (reset) begin
         rd_Reg1  <= 7'b0000000;
         rd_Reg2  <= 7'b0000000;
         rd_Reg3  <= 7'b0000000;
         rd_Reg4  <= 7'b0000000;
         wr_ptr_gray_on_rd <= 7'b0000000;
      end else begin
         rd_Reg1  <= #1 wr_ptr_gray_d2;
         rd_Reg2  <= #1 rd_Reg1;
         rd_Reg3  <= #1 rd_Reg2;
         rd_Reg4  <= #1 rd_Reg3;
         wr_ptr_gray_on_rd <= rd_RegS;
      end
		
		
      // Gray Code to Binary Converter %%

      wr_ptr_on_rd[6] = wr_ptr_gray_on_rd[6];
      wr_ptr_on_rd[5] = wr_ptr_gray_on_rd[5]^wr_ptr_gray_on_rd[6];
      wr_ptr_on_rd[4] = wr_ptr_gray_on_rd[4]^wr_ptr_on_rd[5];
      wr_ptr_on_rd[3] = wr_ptr_gray_on_rd[3]^wr_ptr_on_rd[4];
		
      wr_ptr_on_rd[2] = wr_ptr_on_rd[3] ? ~wr_ptr_gray_on_rd[2] :
                        wr_ptr_gray_on_rd[2];
      wr_ptr_on_rd[1] = wr_ptr_on_rd[3] ? rd_conv_temp2 : rd_conv_temp1;
      wr_ptr_on_rd[0] = wr_ptr_on_rd[3] ? rd_conv_temp2^wr_ptr_gray_on_rd[0] :
                        rd_conv_temp1^wr_ptr_gray_on_rd[0];

		
      // Register the SRAM output
		
      data_out	<= #1 data_out_c;
		
		
   end
   
	
   // Asychronous Empty Logic, used for asynchrnous wake
	
   assign async_empty	= (wr_ptr == rd_ptr_on_wr);
	
   
   // FIFO utilization used by Dynamic Voltage and Frequency Scaling logic %%%
   wire [6:0] fifo_util_temp;
   assign fifo_util_temp = wr_ptr - rd_ptr - 1;
   
   reg [1:0] fifo_util;
   always @ ( fifo_util_temp )
   begin
       if( fifo_util_temp[6] == 1'b1 ) begin	// util = 64
          fifo_util = 2'b11;
       end
       else begin
          fifo_util = fifo_util_temp[5:4];	// util = 0 to 63
       end
   end


   //SRAM Memory Definition
   SRAM SRAM (
              .wr_en(wr_valid_d),   // write enable
              .clk_wr(clk_wr),      // clock coming from write side of FIFO
              .wr_ptr(wr_ptr),      // write pointer
              .data_in(data_in_d),  // data to be written into the SRAM 
              .rd_en(rd_en),        // read enable
              .clk_rd(clk_rd),      // clock coming from read side of FIFO
              .rd_ptr(rd_ptr),      // read pointer
              .data_out(data_out_c) // data to be read from the SRAM
             );


endmodule
`endcelldefine


`celldefine
