`timescale 1ns/1ps

module async_fifo_tb;
    parameter DEPTH = 16;
    parameter WIDTH = 8;

    // Clocks independentes
    logic clk_wr = 0;
    logic clk_rd = 0;

    always #4 clk_wr = ~clk_wr;  // 125 MHz
    always #7 clk_rd = ~clk_rd;  // ~71 MHz

    // Sinais
    logic rst_n;
    logic wr_en_i, rd_en_i;
    logic [WIDTH-1:0] write_data_i;
    logic [WIDTH-1:0] read_data_o;
    logic full_o, empty_o;

    integer write_val;
    integer read_expected;
    logic make_read;

    // DUT
    async_fifo #(
        .DEPTH (DEPTH),
        .WIDTH (WIDTH)
    ) uut (
        .clk_wr       (clk_wr),
        .clk_rd       (clk_rd),
        .rst_n        (rst_n),
        .wr_en_i      (wr_en_i),
        .rd_en_i      (rd_en_i),
        .write_data_i (write_data_i),
        .full_o       (full_o),
        .empty_o      (empty_o),
        .read_data_o  (read_data_o)
    );

    // Controle de reset e teste
    initial begin
        $dumpfile("build/async_fifo_tb.vcd");
        $dumpvars(0, async_fifo_tb);

        // Reset inicial
        rst_n        = 0;
        wr_en_i      = 0;
        rd_en_i      = 0;
        write_data_i = 0;
        make_read    = 0;
        #20 rst_n    = 1;

        // Espera um pouco após reset
        repeat (3) @(posedge clk_wr);
        repeat (3) @(posedge clk_rd);

        // Escreve na FIFO até encher
        for (int i = 0; i < DEPTH - 1; i++) begin
            @(posedge clk_wr);
            if (!full_o) begin
                write_data_i <= i + 1;
                wr_en_i      <= 1;
            end
            @(posedge clk_wr);
            wr_en_i <= 0;
        end

        // Verifica que FIFO está cheia
        @(posedge clk_wr);
        if (!full_o) $error("Erro: FIFO deveria estar cheia!");

        // Tenta escrever mais (não deve alterar)
        write_data_i <= 99;
        wr_en_i      <= 1;
        @(posedge clk_wr);
        wr_en_i <= 0;
        @(posedge clk_wr);
        if (!full_o) $error("Erro: FIFO deveria continuar cheia!");

        // Aguarda um pouco e começa leitura
        repeat (2) @(posedge clk_rd);

        for (int i = 0; i < DEPTH - 1; i++) begin
            @(posedge clk_rd);
            if (!empty_o) begin
                rd_en_i <= 1;
            end
            @(posedge clk_rd);
            rd_en_i <= 0;

            @(posedge clk_rd);
            if (read_data_o !== i + 1)
                $error("Erro: valor lido %0d diferente de esperado %0d", read_data_o, i + 1);
        end

        // Verifica que FIFO está vazia
        @(posedge clk_rd);
        if (!empty_o) $error("Erro: FIFO deveria estar vazia!");

        // Teste leitura com FIFO vazia
        rd_en_i <= 1;
        @(posedge clk_rd);
        rd_en_i <= 0;
        @(posedge clk_rd);
        if (!empty_o) $error("Erro: FIFO deveria continuar vazia!");

        // ---
        // Teste de operação simultânea
        // ---
        repeat (3) @(posedge clk_wr);
        repeat (3) @(posedge clk_rd);

        $display("Iniciando operação simultânea de escrita e leitura");

        write_val = 100;
        read_expected = write_val;

        fork
            // Processo de escrita
            begin
                for (int i = 0; i < DEPTH * 2; i++) begin
                    @(posedge clk_wr);
                    if (!full_o) begin
                        write_data_i <= write_val;
                        wr_en_i      <= 1;
                        write_val++;
                    end else begin
                        wr_en_i <= 0;
                    end
                    @(posedge clk_wr);
                    wr_en_i <= 0;
                end
            end

            // Processo de leitura
            begin
                for (int i = 0; i < DEPTH * 2; i++) begin
                    @(posedge clk_rd);
                    if (!empty_o) begin
                        rd_en_i <= 1;
                    end else begin
                        rd_en_i <= 0;
                    end

                    @(posedge clk_rd);
                    rd_en_i <= 0;

                    if (!empty_o && make_read) begin
                        make_read <= 0;
                        if (read_data_o !== read_expected)
                            $error("Erro: leitura=%0d, esperado=%0d", read_data_o, read_expected);
                        read_expected++;
                    end
                end
            end
        join

        $display("✅ Operação simultânea finalizada com sucesso!");

        $display("✅ Testbench finalizado com sucesso!");
        $finish;
    end

    always_ff @(posedge clk_rd or negedge rst_n) begin
        make_read <= 0;
        if (!rst_n) begin
            make_read <= 0;
        end else if (rd_en_i && !empty_o) begin
            make_read <= 1;
        end
    end

endmodule
