module axi4_cdc_fifo39
(
    // Inputs
     input           rd_clk_i
    ,input           rd_rst_i
    ,input           rd_pop_i
    ,input           wr_clk_i
    ,input           wr_rst_i
    ,input  [ 38:0]  wr_data_i
    ,input           wr_push_i

    // Outputs
    ,output [ 38:0]  rd_data_o
    ,output          rd_empty_o
    ,output          wr_full_o
);





//-----------------------------------------------------------------
// Registers
//-----------------------------------------------------------------
reg [4:0]       rd_ptr_q;
reg [4:0]       wr_ptr_q;

//-----------------------------------------------------------------
// Write
//-----------------------------------------------------------------
wire [4:0]      wr_ptr_next_w = wr_ptr_q + 5'd1;

always @ (posedge wr_clk_i or posedge wr_rst_i)
if (wr_rst_i)
    wr_ptr_q <= 5'b0;
else if (wr_push_i & ~wr_full_o)
    wr_ptr_q <= wr_ptr_next_w;

wire [4:0] wr_rd_ptr_w;

axi4_cdc_fifo39_resync_bus
#( .WIDTH(5))
u_resync_rd_ptr_q
(
    .wr_clk_i(rd_clk_i),
    .wr_rst_i(rd_rst_i),
    .wr_i(1'b1),
    .wr_data_i(rd_ptr_q),
    .wr_busy_o(),
    .rd_clk_i(wr_clk_i),
    .rd_rst_i(wr_rst_i),
    .rd_data_o(wr_rd_ptr_w) // Delayed version of rd_ptr_q
);

assign wr_full_o = (wr_ptr_next_w == wr_rd_ptr_w);

//-------------------------------------------------------------------
// Dual port RAM
//-------------------------------------------------------------------
wire [38:0] rd_data_w;

axi4_cdc_fifo39_ram_dp_32_5
u_ram
(
    // Inputs
    .clk0_i(wr_clk_i),
    .rst0_i(wr_rst_i),
    .clk1_i(rd_clk_i),
    .rst1_i(rd_rst_i),

    // Write side
    .addr0_i(wr_ptr_q),
    .wr0_i(wr_push_i & ~wr_full_o),
    .data0_i(wr_data_i),
    .data0_o(),

    // Read side
    .addr1_i(rd_ptr_q),
    .data1_i(39'b0),
    .wr1_i(1'b0),
    .data1_o(rd_data_w)
);

//-----------------------------------------------------------------
// Read
//-----------------------------------------------------------------
wire [4:0] rd_wr_ptr_w;

axi4_cdc_fifo39_resync_bus
#( .WIDTH(5))
u_resync_wr_ptr_q
(
    .wr_clk_i(wr_clk_i),
    .wr_rst_i(wr_rst_i),
    .wr_i(1'b1),
    .wr_data_i(wr_ptr_q),
    .wr_busy_o(),
    .rd_clk_i(rd_clk_i),
    .rd_rst_i(rd_rst_i),
    .rd_data_o(rd_wr_ptr_w) // Delayed version of wr_ptr_q
);

//-------------------------------------------------------------------
// Read Skid Buffer
//-------------------------------------------------------------------
reg                rd_skid_q;
reg [38:0] rd_skid_data_q;
reg                rd_q;

wire read_ok_w = (rd_wr_ptr_w != rd_ptr_q);
wire valid_w   = (rd_skid_q | rd_q);

always @ (posedge rd_clk_i or posedge rd_rst_i)
if (rd_rst_i)
begin
    rd_skid_q <= 1'b0;
    rd_skid_data_q <= 39'b0;
end
else if (valid_w && !rd_pop_i)
begin
    rd_skid_q      <= 1'b1;
    rd_skid_data_q <= rd_data_o;
end
else
begin
    rd_skid_q      <= 1'b0;
    rd_skid_data_q <= 39'b0;
end

assign rd_data_o = rd_skid_q ? rd_skid_data_q : rd_data_w;

//-----------------------------------------------------------------
// Read Pointer
//-----------------------------------------------------------------
always @ (posedge rd_clk_i or posedge rd_rst_i)
if (rd_rst_i)
    rd_q <= 1'b0;
else
    rd_q <= read_ok_w;

wire [4:0] rd_ptr_next_w = rd_ptr_q + 5'd1;

always @ (posedge rd_clk_i or posedge rd_rst_i)
if (rd_rst_i)
    rd_ptr_q <= 5'b0;
// Read address increment
else if (read_ok_w && ((!valid_w) || (valid_w && rd_pop_i)))
    rd_ptr_q <= rd_ptr_next_w;

assign rd_empty_o = !valid_w;

endmodule