// ============================================================================
// tb_gs_lpddr_wr_probe.sv  (Ch322 Brick 3)
//
// Verifies the HPS->LPDDR4B write-probe (cloned from ao486 lpddr4b_loader write
// half): each wr_pulse toggle issues ONE single-beat 32-bit lane write into a
// behavioral 256-bit EMIF write target. Checks:
//   (1) each written word lands at the correct 32-bit lane (addr[4:2]) with the
//       correct WSTRB nibble (neighbouring lanes untouched)
//   (2) done_toggle flips once per write; busy returns low
//   (3) a forced non-OKAY bresp increments bresp_errs
// ============================================================================
`timescale 1ns/1ps

module tb_gs_lpddr_wr_probe;
    logic emif_clk = 0, emif_rst_n = 0;
    always #5 emif_clk = ~emif_clk;

    logic        wr_pulse; logic [29:0] wr_addr; logic [31:0] wr_data;
    logic        busy, done_toggle; logic [31:0] bresp_errs;
    logic [29:0] awaddr; logic [1:0] awburst; logic [6:0] awid;
    logic [7:0]  awlen;  logic [2:0] awsize;  logic awvalid, awready;
    logic [255:0] wdata; logic [31:0] wstrb;  logic wlast, wvalid, wready;
    logic [1:0]  bresp;  logic bvalid, bready;

    gs_lpddr_wr_probe dut (
        .emif_clk(emif_clk), .emif_rst_n(emif_rst_n),
        .wr_pulse(wr_pulse), .wr_addr(wr_addr), .wr_data(wr_data), .full_beat(1'b0),
        .busy(busy), .done_toggle(done_toggle), .bresp_errs(bresp_errs),
        .awaddr(awaddr), .awburst(awburst), .awid(awid), .awlen(awlen),
        .awsize(awsize), .awvalid(awvalid), .awready(awready),
        .wdata(wdata), .wstrb(wstrb), .wlast(wlast), .wvalid(wvalid), .wready(wready),
        .bresp(bresp), .bvalid(bvalid), .bready(bready)
    );

    // ---- behavioral 256-bit EMIF write target ----
    logic [255:0] mem [0:63];      // 64 beats
    int force_err = 0;             // when >0, return SLVERR on the next B
    typedef enum logic [1:0] { W_IDLE, W_DATA, W_B } wstate_t; wstate_t wst;
    logic [29:0] beat;
    always_ff @(posedge emif_clk or negedge emif_rst_n) begin
        if (!emif_rst_n) begin
            wst<=W_IDLE; awready<=0; wready<=0; bvalid<=0; bresp<=2'b00;
        end else begin
            awready<=0; wready<=0; bvalid<=0;
            case (wst)
                W_IDLE: if (awvalid) begin awready<=1; beat<=(awaddr>>5); wst<=W_DATA; end
                W_DATA: if (wvalid) begin
                    wready<=1;
                    for (int by=0; by<32; by++)
                        if (wstrb[by]) mem[beat][by*8 +: 8] <= wdata[by*8 +: 8];
                    wst<=W_B;
                end
                W_B: begin
                    bvalid<=1;
                    bresp <= (force_err>0) ? 2'b10 : 2'b00;
                    if (bready) wst<=W_IDLE;
                end
            endcase
        end
    end

    int errors = 0;
    task automatic check(input bit c, input string m);
        if (!c) begin errors++; $display("[wrprobe] FAIL: %s", m); end endtask

    // issue one write and wait for completion (done_toggle flips)
    task automatic do_write(input [29:0] a, input [31:0] d);
        logic prev; prev = done_toggle;
        @(posedge emif_clk); wr_addr <= a; wr_data <= d; wr_pulse <= ~wr_pulse;
        begin int g=0; while ((done_toggle===prev) && g<500) begin @(posedge emif_clk); g++; end end
    endtask

    // expected lane within a beat
    function automatic [31:0] lane(input [255:0] beatval, input [2:0] sel);
        lane = beatval[sel*32 +: 32]; endfunction

    initial begin
        wr_pulse=0; wr_addr=0; wr_data=0;
        for (int i=0;i<64;i++) mem[i] = 256'd0;
        repeat (4) @(posedge emif_clk); emif_rst_n=1; repeat (2) @(posedge emif_clk);

        // write four distinct lanes of beat 0 (addr 0,4,8,12) + a lane in beat 2
        do_write(30'h0000_0000, 32'hAAAA_0000);
        do_write(30'h0000_0004, 32'hBBBB_0001);
        do_write(30'h0000_0008, 32'hCCCC_0002);
        do_write(30'h0000_000C, 32'hDDDD_0003);
        do_write(30'h0000_0044, 32'hEEEE_0044);   // beat 2 (0x40), lane 1 (addr[4:2]=001)

        check(lane(mem[0],3'd0)===32'hAAAA_0000, "beat0 lane0");
        check(lane(mem[0],3'd1)===32'hBBBB_0001, "beat0 lane1");
        check(lane(mem[0],3'd2)===32'hCCCC_0002, "beat0 lane2");
        check(lane(mem[0],3'd3)===32'hDDDD_0003, "beat0 lane3");
        check(lane(mem[0],3'd4)===32'h0000_0000, "beat0 lane4 untouched");
        check(lane(mem[2],3'd1)===32'hEEEE_0044, "beat2 lane1");
        check(lane(mem[2],3'd0)===32'h0000_0000, "beat2 lane0 untouched");
        check(bresp_errs===32'd0, "bresp_errs nonzero on clean writes");
        check(busy===1'b0, "busy stuck high");

        // forced error write
        force_err = 1;
        do_write(30'h0000_0010, 32'h1234_5678);
        check(bresp_errs===32'd1, "forced bresp err not counted");

        $display("[wrprobe] errors=%0d bresp_errs=%0d", errors, bresp_errs);
        if (errors==0) $display("[tb_gs_lpddr_wr_probe] PASS");
        else           $display("[tb_gs_lpddr_wr_probe] FAIL");
        $finish;
    end
    initial begin #200000; $display("[tb_gs_lpddr_wr_probe] TIMEOUT"); $finish; end
endmodule
