// ============================================================================
// tb_gs_lpddr_rd_probe — Ch319 Brick 3
//
// Unit test for the HPS-triggered LPDDR4B read probe. A small AXI4 read-slave
// model returns a 256-bit beat whose 8 lanes carry distinct values, so we can
// verify (a) araddr is 32-byte aligned, (b) the requested 32-bit word is the
// lane selected by addr[4:2], (c) rd_done toggles per request, (d) backpressure
// on AR/R is tolerated.
// ============================================================================
`timescale 1ns/1ps

module tb_gs_lpddr_rd_probe;
    logic        axi_clk = 0;
    logic        axi_rst_n;
    always #5 axi_clk = ~axi_clk;   // 100 MHz (rate is irrelevant to the logic)

    logic        rd_pulse;
    logic [31:0] rd_addr;
    logic        rd_done;
    logic [31:0] rd_data;
    logic        rd_busy;

    logic [29:0] araddr;
    logic [1:0]  arburst;
    logic [6:0]  arid;
    logic [7:0]  arlen;
    logic [2:0]  arsize;
    logic        arvalid, arready;
    logic [255:0] rdata;
    logic [1:0]  rresp;
    logic        rlast, rvalid, rready;

    gs_lpddr_rd_probe #(.ADDR_W(30)) dut (
        .axi_clk(axi_clk), .axi_rst_n(axi_rst_n),
        .rd_pulse(rd_pulse), .rd_addr(rd_addr),
        .rd_done(rd_done), .rd_data(rd_data), .rd_busy(rd_busy),
        .araddr(araddr), .arburst(arburst), .arid(arid),
        .arlen(arlen), .arsize(arsize), .arvalid(arvalid), .arready(arready),
        .rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready)
    );

    // ---- AXI4 read-slave model ----------------------------------------
    // lane i (0..7) of the beat = 32'h10000000 * (i+1)  => 0x10000000..0x80000000
    // (independent of address, so lane selection is the thing under test).
    // Adds AR/R latency to exercise the probe's handshakes.
    localparam [1:0] SL_IDLE = 0, SL_LAT = 1, SL_RESP = 2;
    logic [1:0]  sl_st;
    logic [3:0]  sl_dly;
    logic [29:0] sl_addr;
    integer      align_errors;

    function automatic [255:0] beat_for(input [29:0] a);
        integer i;
        for (i = 0; i < 8; i = i + 1)
            beat_for[32*i +: 32] = 32'h1000_0000 * (i + 1);
    endfunction

    always_ff @(posedge axi_clk or negedge axi_rst_n) begin
        if (!axi_rst_n) begin
            sl_st <= SL_IDLE; arready <= 1'b0; rvalid <= 1'b0; rlast <= 1'b0;
            rdata <= '0; rresp <= 2'b00; sl_dly <= 0; sl_addr <= '0; align_errors <= 0;
        end else begin
            arready <= 1'b0;
            case (sl_st)
                SL_IDLE: begin
                    if (arvalid) begin
                        arready <= 1'b1;             // accept this cycle
                        sl_addr <= araddr;
                        if (araddr[4:0] != 5'd0) begin
                            align_errors <= align_errors + 1;
                            $display("[tb] ALIGN ERROR: araddr=%h not 32B-aligned", araddr);
                        end
                        sl_dly  <= 4'd3;             // AR->R latency
                        sl_st   <= SL_LAT;
                    end
                end
                SL_LAT: begin
                    if (sl_dly == 0) begin
                        rdata  <= beat_for(sl_addr);
                        rresp  <= 2'b00; rlast <= 1'b1; rvalid <= 1'b1;
                        sl_st  <= SL_RESP;
                    end else sl_dly <= sl_dly - 1;
                end
                SL_RESP: begin
                    if (rready) begin
                        rvalid <= 1'b0; rlast <= 1'b0; sl_st <= SL_IDLE;
                    end
                end
                default: sl_st <= SL_IDLE;
            endcase
        end
    end

    // ---- stimulus / checks --------------------------------------------
    integer errors = 0;
    logic   prev_done;

    task automatic do_read(input [31:0] addr, output [31:0] data);
        begin
            prev_done = rd_done;
            rd_addr   = addr;
            rd_pulse  = ~rd_pulse;            // toggle = request
            // wait for completion (rd_done toggles)
            wait (rd_done != prev_done);
            @(posedge axi_clk);
            data = rd_data;
        end
    endtask

    task automatic check32(input [255:0] name, input [31:0] got, input [31:0] exp);
        begin
            if (got !== exp) begin
                errors = errors + 1;
                $display("[tb] MISMATCH %0s: got=%h exp=%h", name, got, exp);
            end
        end
    endtask

    logic [31:0] d;
    initial begin
        rd_pulse = 1'b0; rd_addr = 32'd0;
        axi_rst_n = 1'b0; repeat (5) @(posedge axi_clk); axi_rst_n = 1'b1;
        repeat (3) @(posedge axi_clk);

        do_read(32'h0000_0000, d); check32("lane0@0x00", d, 32'h1000_0000);
        do_read(32'h0000_0004, d); check32("lane1@0x04", d, 32'h2000_0000);
        do_read(32'h0000_0008, d); check32("lane2@0x08", d, 32'h3000_0000);
        do_read(32'h0000_001C, d); check32("lane7@0x1C", d, 32'h8000_0000);
        // different 32-byte block, lane 0 — also checks block alignment
        do_read(32'h0000_0024, d); check32("lane1@0x24", d, 32'h2000_0000);
        do_read(32'h0000_2000, d); check32("lane0@0x2000", d, 32'h1000_0000);

        if (align_errors != 0) begin
            errors = errors + align_errors;
            $display("[tb] %0d araddr-alignment errors", align_errors);
        end

        $display("[tb_gs_lpddr_rd_probe] errors=%0d", errors);
        if (errors == 0) $display("[tb_gs_lpddr_rd_probe] PASS");
        else             $display("[tb_gs_lpddr_rd_probe] FAIL");
        $finish;
    end

    initial begin
        #200000;
        $display("[tb_gs_lpddr_rd_probe] TIMEOUT");
        $display("[tb_gs_lpddr_rd_probe] FAIL");
        $finish;
    end
endmodule
