`timescale 1ns / 1ps

module DiffWidthSyncFIFO #(
    parameter reg [7:0] DATA_WIDTH  = 8,
    parameter reg [7:0] DATA_DEPTH  = 12,
    parameter reg [7:0] READ_DEPTH  = 3,
    parameter reg [7:0] WRITE_DEPTH = 4
) (
    input wire clk,
    input wire reset,

    input wire read_ready,
    output reg read_en,
    output reg [DATA_WIDTH - 1 : 0] read_data[READ_DEPTH],

    output wire write_ready,
    input wire write_en,
    input wire [DATA_WIDTH - 1 : 0] write_data[WRITE_DEPTH]
);
  reg [DATA_WIDTH - 1 : 0] data[DATA_DEPTH];
  reg [7:0] occupancy;
  reg [7:0] cnt_read, cnt_write;
  reg [7:0] wi, ri;
  reg read_finish, write_finish;

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      occupancy <= 0;
    end
    else begin
      if (read_finish && write_finish) begin
        occupancy <= occupancy + (WRITE_DEPTH - READ_DEPTH);
      end
      else if (read_finish) begin
        occupancy <= occupancy - READ_DEPTH;
      end
      else if (write_finish) begin
        occupancy <= occupancy + WRITE_DEPTH;
      end
      else begin
        occupancy <= occupancy;
      end
    end
  end

  // write data to fifo
  assign write_ready = ((DATA_DEPTH - occupancy) >= WRITE_DEPTH && !write_en) ? 1 : 0;
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      cnt_write   <= 0;
      wi <= 0;
      write_finish <= 0;
    end else begin
      if (write_en && (DATA_DEPTH - occupancy) >= WRITE_DEPTH) begin
        for (wi = 0; wi < WRITE_DEPTH; wi = wi + 1) begin
          data[cnt_write] <= write_data[wi];

          if (cnt_write < DATA_DEPTH - 1) cnt_write <= cnt_write + 1;
          else cnt_write <= 0;
        end
        write_finish <= 1;
      end else begin
        write_finish <= 0;
      end
    end
  end

  integer i;
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      for (i = 0; i < READ_DEPTH; i = i + 1) begin
        read_data[i] <= 0;
      end
      ri <= 0;
      read_en <= 0;
      cnt_read  <= 0;
      read_finish <= 0;
    end else begin
      if (read_ready && occupancy >= READ_DEPTH) begin
        read_en <= 1;
        for (ri = 0; ri < READ_DEPTH; ri = ri + 1) begin
          read_data[ri] <= data[cnt_read];

          if (cnt_read < DATA_DEPTH - 1) cnt_read <= cnt_read + 1;
          else cnt_read <= 0;
        end
        read_finish <= 1;
      end else begin
        read_en <= 0;
        read_finish <= 0;
      end
    end
  end

endmodule
