`timescale 1ns / 1ps
module fifo #(
	parameter WIDTH = 8,
	parameter DEPTH = 8,
	
	// AUTOCALCULATED
	parameter STORAGE_MSB = WIDTH - 1,
	parameter INDEX_MSB = $clog2(DEPTH) - 1
)(
	input wire clk,
	input wire rst,
	input wire clear,
	
	input wire [STORAGE_MSB:0] in,
	input wire push,
	
	output reg [STORAGE_MSB:0] out,
	input wire pop,
	
	output wire empty,
	output reg out_valid,
	output wire full
);

// combined clear and reset
wire rstclr = rst | clear; 

// inner state
reg [STORAGE_MSB:0] mem [DEPTH-1:0]; // buffer
reg [INDEX_MSB + 1:0] level; // fill level
reg [INDEX_MSB:0] push_idx; // this is where the next data will be pushed
reg [INDEX_MSB:0] pop_idx; // this is the index that is going to be popped next

wire will_pop = pop && !empty; // the FIFO will pop in this cycle
wire will_push = push && !full; // the FIFO will store a new item in this cycle

// push
integer i;
always @(posedge clk)
begin
	if (rstclr)
	begin
		for (i = 0; i < DEPTH; i = i + 1)
		begin
			mem[i] <= 0;
		end
		push_idx <= 0;
	end
	else
	begin
		if (will_push)
		begin
			mem[push_idx] <= in;
			push_idx <= push_idx + 1'b1;
		end
	end
end

// pop
always @(posedge clk)
begin
	if (rst)
	begin
		pop_idx <= 0;
		out <= 0;
		out_valid <= 0;
	end
	else
	begin
		if (will_pop)
		begin
			out <= mem[pop_idx];
			pop_idx <= pop_idx + 1'b1;
			out_valid <= 1;
		end
		else
		begin
			out_valid <= 0;
		end
	end
end

// level maintenance
always @(posedge clk)
begin
	if (rst)
	begin
		level <= 0;
	end
	else
	begin
		if (will_push && !will_pop)
		begin
			level <= level + 1'b1;
		end
		
		if (will_pop && !will_push)
		begin
			level <= level - 1'b1;
		end
	end
end

// state flags
assign empty = (level == 0);
assign full = (level == DEPTH);

endmodule
