// tb_storage_xsim.v - Vivado xsim testbench for s_bram and sd_bram
//
// Tests both single-port (s_bram) and simple dual-port (sd_bram) BRAM
// using $readmemh file-based test vectors.
//
// Vector format (64 bits = 16 hex chars per line):
//   bit [63]     : module (0=s_bram, 1=sd_bram)
//   bit [62]     : cmd    (0=write, 1=read-verify)
//   bits [61:56] : addr   (6-bit address)
//   bits [55:32] : reserved
//   bits [31:0]  : data   (write data / expected read data)
//
// Test coverage:
//   - Write then read (same address)
//   - Write multiple addresses then read all
//   - Read during write on different addresses (sd_bram only)
//   - Address wrap (addr 63, max for A=6)
//   - All-zeros data edge case
//
// Parameters: W=32, D=64, A=6

`timescale 1ns / 1ps

module tb_storage_xsim;

    // ================================================================
    // Parameters
    // ================================================================
    parameter W = 32;
    parameter D = 64;
    parameter A = 6;
    parameter VECTOR_FILE  = "sync_rtl/storage/TB/vectors/storage_input.hex";
    parameter RESULT_FILE  = "sync_rtl/storage/TB/vectors/storage_result.hex";
    parameter MAX_VECTORS  = 256;
    parameter TIMEOUT_CYCLES = 2000;

    // ================================================================
    // DUT signals — s_bram (single-port)
    // ================================================================
    reg              clk;
    reg              rst_n;
    reg              s_rd_en;
    reg  [A-1:0]     s_rd_addr;
    wire [W-1:0]     s_rd_data;
    reg              s_wr_en;
    reg  [A-1:0]     s_wr_addr;
    reg  [W-1:0]     s_wr_data;

    // ================================================================
    // DUT signals — sd_bram (simple dual-port)
    // ================================================================
    reg  [A-1:0]     sd_rd_addr;
    wire [W-1:0]     sd_rd_data;
    reg              sd_wr_en;
    reg  [A-1:0]     sd_wr_addr;
    reg  [W-1:0]     sd_wr_data;

    // ================================================================
    // DUT instantiations
    // ================================================================
    s_bram #(.W(W), .D(D), .A(A)) u_sbram (
        .clk      (clk),
        .rd_en    (s_rd_en),
        .rd_addr  (s_rd_addr),
        .rd_data  (s_rd_data),
        .wr_en    (s_wr_en),
        .wr_addr  (s_wr_addr),
        .wr_data  (s_wr_data)
    );

    sd_bram #(.W(W), .D(D), .A(A)) u_sdbram (
        .clk      (clk),
        .rd_addr  (sd_rd_addr),
        .rd_data  (sd_rd_data),
        .wr_en    (sd_wr_en),
        .wr_addr  (sd_wr_addr),
        .wr_data  (sd_wr_data)
    );

    // ================================================================
    // Clock generation: 100 MHz (10 ns period)
    // ================================================================
    initial clk = 1'b0;
    always #5 clk = ~clk;

    // ================================================================
    // Vector memory (loaded by $readmemh)
    // ================================================================
    reg [63:0] vector_mem [0:MAX_VECTORS-1];
    integer    vec_count;
    integer    idx;
    integer    cycle_count;
    integer    result_fd;
    integer    pass_count;
    integer    fail_count;

    // ================================================================
    // Main test sequence
    // ================================================================
    initial begin
        // Load vectors from hex file
        vec_count = 0;
        $readmemh(VECTOR_FILE, vector_mem);

        // Count non-X entries
        begin
            integer found_end;
            found_end = 0;
            for (idx = 0; idx < MAX_VECTORS; idx = idx + 1) begin
                if (!found_end && (vector_mem[idx] === 64'hx || vector_mem[idx] === 64'hz))
                    found_end = 1;
                else if (!found_end)
                    vec_count = vec_count + 1;
            end
        end

        if (vec_count == 0) begin
            $display("ERROR: No vectors loaded from %s", VECTOR_FILE);
            $display("       Check that the file exists.");
            $display("       Format: 16 hex chars = {module, cmd, addr, reserved, data}");
            $finish;
        end

        $display("INFO: Loaded %0d test vectors from %s", vec_count, VECTOR_FILE);

        // Open result file
        result_fd = $fopen(RESULT_FILE, "w");
        if (result_fd == 0) begin
            $display("ERROR: Cannot open result file: %s", RESULT_FILE);
            $finish;
        end

        // Initialize all DUT inputs
        s_rd_en   <= 1'b0;
        s_rd_addr <= {A{1'b0}};
        s_wr_en   <= 1'b0;
        s_wr_addr <= {A{1'b0}};
        s_wr_data <= {W{1'b0}};

        sd_rd_addr <= {A{1'b0}};
        sd_wr_en   <= 1'b0;
        sd_wr_addr <= {A{1'b0}};
        sd_wr_data <= {W{1'b0}};

        // Reset sequence: rst_n low for 3 cycles, then high
        rst_n <= 1'b0;
        repeat (3) @(posedge clk);
        rst_n <= 1'b1;
        @(posedge clk);

        pass_count = 0;
        fail_count = 0;

        // ============================================================
        // Process each vector
        // ============================================================
        for (idx = 0; idx < vec_count; idx = idx + 1) begin
            begin
                reg        vec_module;
                reg        vec_cmd;
                reg [A-1:0] vec_addr;
                reg [W-1:0] vec_data;

                vec_module = vector_mem[idx][63];
                vec_cmd    = vector_mem[idx][62];
                vec_addr   = vector_mem[idx][61:56];
                vec_data   = vector_mem[idx][31:0];

                if (vec_module == 1'b0) begin
                    // ── s_bram ──────────────────────────────
                    if (vec_cmd == 1'b0) begin
                        // Write
                        s_wr_en   <= 1'b1;
                        s_wr_addr <= vec_addr;
                        s_wr_data <= vec_data;
                        s_rd_en   <= 1'b0;
                        s_rd_addr <= {A{1'b0}};
                        @(posedge clk);
                        s_wr_en   <= 1'b0;
                    end else begin
                        // Read-verify
                        s_rd_en   <= 1'b1;
                        s_rd_addr <= vec_addr;
                        s_wr_en   <= 1'b0;
                        s_wr_addr <= {A{1'b0}};
                        s_wr_data <= {W{1'b0}};
                        @(posedge clk);  // 1-cycle read latency
                        @(posedge clk);  // rd_data valid now
                        if (s_rd_data !== vec_data) begin
                            $display("FAIL: s_bram addr=%0d  expected=0x%08h  got=0x%08h",
                                     vec_addr, vec_data, s_rd_data);
                            $fwrite(result_fd, "FAIL: s_bram addr=%0d exp=0x%08h got=0x%08h\n",
                                    vec_addr, vec_data, s_rd_data);
                            fail_count = fail_count + 1;
                        end else begin
                            pass_count = pass_count + 1;
                        end
                        s_rd_en <= 1'b0;
                    end

                end else begin
                    // ── sd_bram ─────────────────────────────
                    if (vec_cmd == 1'b0) begin
                        // Write
                        sd_wr_en   <= 1'b1;
                        sd_wr_addr <= vec_addr;
                        sd_wr_data <= vec_data;
                        @(posedge clk);
                        sd_wr_en   <= 1'b0;
                    end else begin
                        // Read-verify: drive read address, wait 1 cycle
                        sd_rd_addr <= vec_addr;
                        @(posedge clk);  // 1-cycle read latency
                        @(posedge clk);  // rd_data valid now
                        if (sd_rd_data !== vec_data) begin
                            $display("FAIL: sd_bram addr=%0d  expected=0x%08h  got=0x%08h",
                                     vec_addr, vec_data, sd_rd_data);
                            $fwrite(result_fd, "FAIL: sd_bram addr=%0d exp=0x%08h got=0x%08h\n",
                                    vec_addr, vec_data, sd_rd_data);
                            fail_count = fail_count + 1;
                        end else begin
                            pass_count = pass_count + 1;
                        end
                    end
                end
            end  // inner begin block
        end

        // ============================================================
        // Edge-case tests (hardcoded, beyond hex vectors)
        // ============================================================

        // ── s_bram: write-priority (rd_en + wr_en both high)  ──
        begin
            $display("INFO: s_bram write-priority test");
            // Write 0xCAFECAFE to addr 5
            s_wr_en   <= 1'b1; s_wr_addr <= 6'd5; s_wr_data <= 32'hCAFECAFE;
            s_rd_en   <= 1'b1; s_rd_addr <= 6'd5;  // same addr, rd also asserted
            @(posedge clk);
            s_wr_en <= 1'b0;
            // Write should win — memory now has CAFECAFE at addr 5
            // Read back to verify
            s_rd_en   <= 1'b1; s_rd_addr <= 6'd5;
            @(posedge clk);
            @(posedge clk);
            if (s_rd_data !== 32'hCAFECAFE) begin
                $display("FAIL: s_bram write-priority (expected CAFECAFE, got 0x%08h)", s_rd_data);
                fail_count = fail_count + 1;
            end else begin
                $display("PASS: s_bram write-priority");
                pass_count = pass_count + 1;
            end
            s_rd_en <= 1'b0;
        end

        // ── sd_bram: concurrent read-during-write (different addresses) ──
        begin
            $display("INFO: sd_bram read-during-write test");
            // Pre-fill addr 10 with known value
            sd_wr_en <= 1'b1; sd_wr_addr <= 6'd10; sd_wr_data <= 32'h12345678;
            @(posedge clk);
            sd_wr_en <= 1'b0;
            // Now write addr 20 while reading addr 10 (different addrs)
            sd_wr_en   <= 1'b1; sd_wr_addr <= 6'd20; sd_wr_data <= 32'hFEEDFACE;
            sd_rd_addr <= 6'd10;
            @(posedge clk);
            sd_wr_en <= 1'b0;
            @(posedge clk);  // rd_data valid after 1-cycle read latency
            // rd_data should be OLD value at addr 10 (12345678)
            if (sd_rd_data !== 32'h12345678) begin
                $display("FAIL: sd_bram rd-during-wr: expected 0x12345678, got 0x%08h", sd_rd_data);
                fail_count = fail_count + 1;
            end else begin
                $display("PASS: sd_bram read-during-write (old data)");
                pass_count = pass_count + 1;
            end
            // Verify addr 20 got new data
            sd_rd_addr <= 6'd20;
            @(posedge clk);
            @(posedge clk);
            if (sd_rd_data !== 32'hFEEDFACE) begin
                $display("FAIL: sd_bram wr-during-rd: expected 0xFEEDFACE at addr 20, got 0x%08h", sd_rd_data);
                fail_count = fail_count + 1;
            end else begin
                $display("PASS: sd_bram write-during-read (new data)");
                pass_count = pass_count + 1;
            end
        end

        // ── sd_bram: concurrent read-during-write (SAME address) ──
        begin
            $display("INFO: sd_bram read-during-write (same address)");
            sd_wr_en   <= 1'b1; sd_wr_addr <= 6'd30; sd_wr_data <= 32'hAAAAAAAA;
            sd_rd_addr <= 6'd30;  // same address
            @(posedge clk);
            sd_wr_en <= 1'b0;
            @(posedge clk);
            // On sd_bram, mem write happens at posedge, rd_addr is registered
            // So rd_data gets OLD mem[30] (before write), not new AAAAAAAA
            // We just verify the read port didn't get X
            if (sd_rd_data === 32'hx || sd_rd_data === 32'hz) begin
                $display("FAIL: sd_bram rd-during-wr same addr: got X/Z");
                fail_count = fail_count + 1;
            end else begin
                $display("PASS: sd_bram read-during-write (same addr, no X)");
                pass_count = pass_count + 1;
            end
            // Now read addr 30 to verify write took effect
            sd_rd_addr <= 6'd30;
            @(posedge clk);
            @(posedge clk);
            if (sd_rd_data !== 32'hAAAAAAAA) begin
                $display("FAIL: sd_bram same-addr write: expected 0xAAAAAAAA, got 0x%08h", sd_rd_data);
                fail_count = fail_count + 1;
            end else begin
                $display("PASS: sd_bram same-addr write verified");
                pass_count = pass_count + 1;
            end
        end

        // ── s_bram: address wrap / all-zeros data ──
        begin
            $display("INFO: s_bram address wrap test");
            // Write to max address (63) and read back
            s_wr_en   <= 1'b1; s_wr_addr <= 6'd63; s_wr_data <= 32'hFFFF0000;
            @(posedge clk);
            s_wr_en <= 1'b0;
            s_rd_en   <= 1'b1; s_rd_addr <= 6'd63;
            @(posedge clk);
            @(posedge clk);
            if (s_rd_data !== 32'hFFFF0000) begin
                $display("FAIL: s_bram addr 63: expected 0xFFFF0000, got 0x%08h", s_rd_data);
                fail_count = fail_count + 1;
            end else begin
                $display("PASS: s_bram addr 63 (max)");
                pass_count = pass_count + 1;
            end
            s_rd_en <= 1'b0;

            // Write all-zeros to addr 0 and read back
            s_wr_en   <= 1'b1; s_wr_addr <= 6'd0; s_wr_data <= 32'h00000000;
            @(posedge clk);
            s_wr_en <= 1'b0;
            s_rd_en   <= 1'b1; s_rd_addr <= 6'd0;
            @(posedge clk);
            @(posedge clk);
            if (s_rd_data !== 32'h00000000) begin
                $display("FAIL: s_bram all-zeros: expected 0x00000000, got 0x%08h", s_rd_data);
                fail_count = fail_count + 1;
            end else begin
                $display("PASS: s_bram all-zeros data");
                pass_count = pass_count + 1;
            end
            s_rd_en <= 1'b0;
        end

        // ============================================================
        // Summary
        // ============================================================
        $fclose(result_fd);

        $display("========================================");
        $display("STORAGE TEST COMPLETE");
        $display("  Total vectors:   %0d", vec_count);
        $display("  Edge-case tests: %0d", 7);  // hardcoded count above
        $display("  Passed:          %0d", pass_count);
        $display("  Failed:          %0d", fail_count);
        $display("  Results written to: %s", RESULT_FILE);
        if (fail_count == 0)
            $display("  RESULT: ALL TESTS PASSED");
        else
            $display("  RESULT: SOME TESTS FAILED");
        $display("========================================");

        $finish;
    end

    // ================================================================
    // Timeout watchdog
    // ================================================================
    initial begin
        #(TIMEOUT_CYCLES * 10 * 100);
        $display("FATAL: Global simulation timeout reached");
        $finish;
    end

endmodule
