`default_nettype none

`define CP 10
`define HCP 5

`define BRAM_DEPTH 256
`define BRAM_WIDTH 33
`define ADDR_WIDTH $clog2(`BRAM_DEPTH)

task read_reg_bus_side (
    input [15:0] addr,
    output [15:0] data
    );

    block_memory_tb.tb_bc_addr = addr;
    block_memory_tb.tb_bc_rw = 0;
    block_memory_tb.tb_bc_valid = 1;
    #`CP
    block_memory_tb.tb_bc_rw = 0;
    block_memory_tb.tb_bc_valid = 0;
    while (!block_memory_tb.bc_tb_valid) #`CP;
    data = block_memory_tb.bc_tb_data;

    $display(" -> bus  read  0x%h from addr 0x%h", data, addr);
endtask

task write_reg_bus_side(
    input [15:0] addr,
    input [15:0] data
    );

    block_memory_tb.tb_bc_addr = addr;
    block_memory_tb.tb_bc_data = data;
    block_memory_tb.tb_bc_rw = 1;
    block_memory_tb.tb_bc_valid = 1;
    #`CP
    block_memory_tb.tb_bc_rw = 0;
    block_memory_tb.tb_bc_valid = 0;
    while (!block_memory_tb.bc_tb_valid) #`CP;

    $display(" -> bus  wrote 0x%h to   addr 0x%h", data, addr);
endtask

task write_and_verify_bus_side(
    input [15:0] addr,
    input [15:0] write_data
    );

    reg [15:0] read_data;

    write_reg_bus_side(addr, write_data);
    read_reg_bus_side(addr, read_data);
    assert(read_data == write_data) else $fatal(0, "data read does not match data written!");
endtask

task read_user_side(
    input [`ADDR_WIDTH-1:0] addr
    );

    block_memory_tb.bram_user_we = 0;
    block_memory_tb.bram_user_addr = addr;
    #(2*`CP);
    $display("user read  0x%h from addr 0x%h", block_memory_tb.bram_user_dout, addr);
endtask

task write_user_side(
    input [`ADDR_WIDTH-1:0] addr,
    input [`BRAM_WIDTH-1:0] data
    );

    block_memory_tb.bram_user_we = 1;
    block_memory_tb.bram_user_addr = addr;
    block_memory_tb.bram_user_din = data;
    #(2*`CP);
    $display("user wrote 0x%h to   addr 0x%h", data, addr);
endtask


module block_memory_tb;

    // boilerplate
    logic clk;
    integer test_num;
    reg [15:0] read_value;

    // tb -> bram_core bus
    logic [15:0] tb_bc_addr;
    logic [15:0] tb_bc_data;
    logic tb_bc_rw;
    logic tb_bc_valid;

    // bram_core -> tb bus
    logic [15:0] bc_tb_addr;
    logic [15:0] bc_tb_data;
    logic bc_tb_rw;
    logic bc_tb_valid;

    // bram itself
    localparam BRAM_DEPTH = `BRAM_DEPTH;
    localparam BRAM_WIDTH = `BRAM_WIDTH;
    localparam ADDR_WIDTH = $clog2(BRAM_DEPTH);
    logic [ADDR_WIDTH-1:0] bram_user_addr = 0;
    logic [BRAM_WIDTH-1:0] bram_user_din = 0;
    logic [BRAM_WIDTH-1:0] bram_user_dout;
    logic bram_user_we = 0;

    block_memory #(.DEPTH(BRAM_DEPTH), .WIDTH(BRAM_WIDTH)) block_mem (
        .clk(clk),

        .addr_i(tb_bc_addr),
        .data_i(tb_bc_data),
        .rw_i(tb_bc_rw),
        .valid_i(tb_bc_valid),

        .addr_o(bc_tb_addr),
        .data_o(bc_tb_data),
        .rw_o(bc_tb_rw),
        .valid_o(bc_tb_valid),

        .user_clk(clk),
        .user_addr(bram_user_addr),
        .user_din(bram_user_din),
        .user_dout(bram_user_dout),
        .user_we(bram_user_we));

    always begin
        #`HCP
        clk = !clk;
    end

    initial begin
        $dumpfile("block_memory_tb.vcd");
        $dumpvars(0, block_memory_tb);

        // setup and reset
        clk = 0;
        test_num = 0;

        tb_bc_addr = 0;
        tb_bc_data = 0;
        tb_bc_rw = 0;
        tb_bc_valid = 0;

        bram_user_addr = 0;
        bram_user_din = 0;
        bram_user_we = 0;

        #`HCP



        #(10*`CP);

        /* ==== Test 1 Begin ==== */
        $display("\n=== test 1: read/write from BRAM, verify ===");
        test_num = 1;
        write_and_verify_bus_side(0, 'h6789);
        write_and_verify_bus_side(1, 'h2345);
        write_and_verify_bus_side(2, 'h0001);

        // now query what's on the the user side at address 0
        read_user_side(0);

        write_and_verify_bus_side(3, 'h1111);
        write_and_verify_bus_side(4, 'h1111);
        write_and_verify_bus_side(5, 'h0001);

        // now query what's on the the user side at address 0
        read_user_side(1);

        write_user_side(1, 0);
        read_user_side(1);

        #(10*`CP);

        /* ==== Test 1 End ==== */

        $finish();
    end
endmodule

`default_nettype wire