`ifdef VERILATOR
    `include "sdvd_defs.sv"
`endif
import sdvd_defs::SPEED;

module read_data_tb;
    logic clk, sd_clk, reset;

    // Control signals
    logic play, stop;
    SPEED speed;

    // Whether the audio buffer is currently playing
    logic playing;

    // A 16-bit audio sample to output
    logic [15:0] sample;

    logic sd_data;

    audio_buffer_interface bufferInterface(); 

    logic [10:0] counter;

    logic [15:0] test_memory [1023:0];
    audio_buffer audioDut(.driver(bufferInterface.receiver), .*);
    read_data readerDut(sd_clk,reset,sd_data,bufferInterface.driver);

    // The writer's clock should be much faster than the 48khz buffer clock
    initial sd_clk = 0;
    always #5 sd_clk = ~sd_clk;


    // An order of magnitude difference is fine
    initial clk = 0;
    always #500 clk = ~clk;

    // Reader
    initial begin
        `ifdef DEBUG
        $monitor("PLAYING: %b ADDR: 0x%x DATA: 0x%x WILLDELAY: %b",
                 playing, dut.address, dut.doutb,dut.delay);
        `endif
        fork
        //Reader
        begin
            play = 0;
            stop = 0;
            clk = 0;
            reset = 0;
            speed = 1;
            @(posedge clk)
            reset = 1;
            @(posedge clk)
            reset = 0;
            play = 1;
            @(posedge clk)
            assert (playing == 1) else $error("Audio buffer not playing");
            // Wait an extra clock cycle because of the blockmem delay
            @(posedge clk)
            // The most basic test we can do here is an incrementing counter,
            // where the stored sample is the same as the address
            $display("Running linear test");
            for (counter = 0; counter < 1024; counter++) begin
                #1
                assert ({5'b0, counter} === sample) else
                    $error("Invalid sample, expected 0x%x but found 0x%x",counter,sample);
                @(posedge clk);
            end
            $display("Running randomized test");
            counter = 0;
            while(counter < 1024) begin
                #1
                assert (test_memory[counter[9:0]] === sample) else
                    $error("Invalid sample, expected 0x%x but found 0x%x at location 0x%x",test_memory[counter[9:0]],sample,counter);
                @(posedge clk);
                counter++;
            end

        end
        // Writer
        begin
            for (int i=0; i<1024; i++)
                test_memory[i]=$urandom;
            // Wait til buffer is done resetting
            @(posedge clk);
            @(posedge clk);
            // linear test
            write_linear_sd(0);
            write_linear_sd(256);
            write_linear_sd(512);
            write_linear_sd(256*3);
            wait (bufferInterface.address_half==1);
            // random test, write to lower half of memory
            write_random_sd(0);
            write_random_sd(256);
            wait (bufferInterface.address_half==0);
            // random test, write to upper half of memory
            write_random_sd(512);
            write_random_sd(256*3);
        end
        join
        $finish;
    end
    task automatic write_random_sd(int start);
        @(posedge sd_clk);
        sd_data = 1;
        // send start bit
        @(posedge sd_clk);
        // send 256 data bits
        sd_data = 0;
        for (int i = 0; i < 256; i++) begin
            write_byte(test_memory[start+i][7:0]);
            write_byte(test_memory[start+i][15:8]);
        end
        // Simulate randomized crc bits and stop bit
        repeat (16) @(posedge sd_clk) sd_data=$urandom;
        @(posedge sd_clk) sd_data=1;
    endtask
    task automatic write_linear_sd(int start);
        @(posedge sd_clk);
        sd_data = 1;
        // send start bit
        @(posedge sd_clk);
        // send 256 data bits
        sd_data = 0;
        for (int i = 0; i < 256; i++) begin
            write_byte(start[7:0]);
            write_byte(start[15:8]);
            start = start+1;
        end
        // Simulate crc bits and stop bit
        repeat (17) @(posedge sd_clk) sd_data=1;
    endtask
    task automatic write_byte(logic [7:0] b);
        for (int i=0; i<8; i++)
            @(posedge sd_clk) sd_data=b[7-i];
    endtask

endmodule
