module async_dual_clock_fifo (
    input wire wr_clk,
    input wire wr_rst,
    input wire wr_en,
    input wire [15:0] wr_data,
    output reg wr_full,
    input wire rd_clk,
    input wire rd_rst,
    input wire rd_en,
    output reg [15:0] rd_data,
    output reg rd_empty
);
    localparam integer DEPTH = 8;
    localparam integer ADDR_W = 3;
    localparam integer PTR_W = ADDR_W + 1;

    reg [15:0] mem [0:DEPTH-1];

    reg [PTR_W-1:0] wr_bin_ptr;
    reg [PTR_W-1:0] wr_gray_ptr;
    reg [PTR_W-1:0] rd_bin_ptr;
    reg [PTR_W-1:0] rd_gray_ptr;

    reg [PTR_W-1:0] rd_gray_sync1;
    reg [PTR_W-1:0] rd_gray_sync2;
    reg [PTR_W-1:0] wr_gray_sync1;
    reg [PTR_W-1:0] wr_gray_sync2;

    wire wr_fire;
    wire rd_fire;

    wire [PTR_W-1:0] wr_bin_ptr_next;
    wire [PTR_W-1:0] wr_gray_ptr_next;
    wire [PTR_W-1:0] rd_bin_ptr_next;
    wire [PTR_W-1:0] rd_gray_ptr_next;
    wire [PTR_W-1:0] rd_gray_sync2_next;
    wire [PTR_W-1:0] wr_gray_sync2_next;
    wire wr_full_next;
    wire rd_empty_next;

    function [PTR_W-1:0] bin_to_gray;
        input [PTR_W-1:0] value;
        begin
            bin_to_gray = (value >> 1) ^ value;
        end
    endfunction

    assign wr_fire = wr_en && !wr_full;
    assign rd_fire = rd_en && !rd_empty;

    assign wr_bin_ptr_next = wr_bin_ptr + (wr_fire ? {{PTR_W-1{1'b0}}, 1'b1} : {PTR_W{1'b0}});
    assign rd_bin_ptr_next = rd_bin_ptr + (rd_fire ? {{PTR_W-1{1'b0}}, 1'b1} : {PTR_W{1'b0}});

    assign wr_gray_ptr_next = bin_to_gray(wr_bin_ptr_next);
    assign rd_gray_ptr_next = bin_to_gray(rd_bin_ptr_next);

    assign rd_gray_sync2_next = rd_gray_sync1;
    assign wr_gray_sync2_next = wr_gray_sync1;

    assign wr_full_next =
        (wr_gray_ptr_next == {~rd_gray_sync2_next[PTR_W-1:PTR_W-2], rd_gray_sync2_next[PTR_W-3:0]});
    assign rd_empty_next = (rd_gray_ptr_next == wr_gray_sync2_next);

    always @(posedge wr_clk) begin
        if (wr_rst) begin
            wr_bin_ptr <= {PTR_W{1'b0}};
            wr_gray_ptr <= {PTR_W{1'b0}};
            rd_gray_sync1 <= {PTR_W{1'b0}};
            rd_gray_sync2 <= {PTR_W{1'b0}};
            wr_full <= 1'b0;
        end else begin
            rd_gray_sync1 <= rd_gray_ptr;
            rd_gray_sync2 <= rd_gray_sync1;

            if (wr_fire)
                mem[wr_bin_ptr[ADDR_W-1:0]] <= wr_data;

            wr_bin_ptr <= wr_bin_ptr_next;
            wr_gray_ptr <= wr_gray_ptr_next;
            wr_full <= wr_full_next;
        end
    end

    always @(posedge rd_clk) begin
        if (rd_rst) begin
            rd_bin_ptr <= {PTR_W{1'b0}};
            rd_gray_ptr <= {PTR_W{1'b0}};
            wr_gray_sync1 <= {PTR_W{1'b0}};
            wr_gray_sync2 <= {PTR_W{1'b0}};
            rd_data <= 16'h0000;
            rd_empty <= 1'b1;
        end else begin
            wr_gray_sync1 <= wr_gray_ptr;
            wr_gray_sync2 <= wr_gray_sync1;

            if (rd_fire)
                rd_data <= mem[rd_bin_ptr[ADDR_W-1:0]];

            rd_bin_ptr <= rd_bin_ptr_next;
            rd_gray_ptr <= rd_gray_ptr_next;
            rd_empty <= rd_empty_next;
        end
    end
endmodule
