// tb_lpddr_tex_staging.sv  (Ch322 — bridge -> write-probe staging integration)
//
// Catches the class of bug that reached the board: the HPS bridge auto-increments
// the LPDDR write address, and the write-probe samples that address ACROSS A CDC, so
// if the bridge presents the live (already-incremented) address each staged word lands
// +4 bytes past its slot. This drives REAL bridge register writes (WRADDR + a streamed
// run of WRDATA) through the real ps2_hps_bridge + gs_lpddr_wr_probe + a behavioral EMIF
// write target, and asserts every word landed at its intended byte address.
`timescale 1ns/1ps

module tb_lpddr_tex_staging;
    logic clk = 0;       always #5 clk = ~clk;        // bridge (CLOCK2_50-ish), 100 MHz
    logic emif_clk = 0;  always #3 emif_clk = ~emif_clk; // EMIF/probe, faster + async
    logic reset_n, emif_rst_n;

    // AXI4 slave stimulus (HPS side of the bridge)
    logic [3:0] s_axi_awid, s_axi_awcache, s_axi_arid, s_axi_arcache;
    logic [37:0] s_axi_awaddr, s_axi_araddr;
    logic [7:0] s_axi_awlen, s_axi_arlen;
    logic [2:0] s_axi_awsize, s_axi_awprot, s_axi_arsize, s_axi_arprot;
    logic [1:0] s_axi_awburst, s_axi_arburst;
    logic s_axi_awlock, s_axi_awvalid, s_axi_awready, s_axi_arlock, s_axi_arvalid, s_axi_arready;
    logic [127:0] s_axi_wdata; logic [15:0] s_axi_wstrb; logic s_axi_wlast, s_axi_wvalid, s_axi_wready;
    logic [3:0] s_axi_bid, s_axi_rid; logic [1:0] s_axi_bresp, s_axi_rresp;
    logic s_axi_bvalid, s_axi_bready, s_axi_rlast, s_axi_rvalid, s_axi_rready;
    logic [127:0] s_axi_rdata;

    // bridge -> write-probe
    logic [31:0] lpddr_wr_addr_o, lpddr_wr_data_o; logic lpddr_wr_pulse_o;
    logic        lpddr_wr_busy_i, lpddr_wr_done_i; logic [31:0] lpddr_wr_bresp_err_i;

    // probe AXI write -> EMIF model
    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;

    // ---- ps2_hps_bridge: connect clk/reset + AXI slave + the LPDDR write-probe nets.
    // Other ports (status counters, OSD, DS2, input, scanout, read-probe, tile RAM, tex-fill)
    // are left unconnected — irrelevant to the staging path under test.
    ps2_hps_bridge u_bridge (
        .clk(clk), .reset_n(reset_n),
        .s_axi_awid(s_axi_awid), .s_axi_awaddr(s_axi_awaddr), .s_axi_awlen(s_axi_awlen),
        .s_axi_awsize(s_axi_awsize), .s_axi_awburst(s_axi_awburst), .s_axi_awlock(s_axi_awlock),
        .s_axi_awcache(s_axi_awcache), .s_axi_awprot(s_axi_awprot), .s_axi_awvalid(s_axi_awvalid),
        .s_axi_awready(s_axi_awready),
        .s_axi_wdata(s_axi_wdata), .s_axi_wstrb(s_axi_wstrb), .s_axi_wlast(s_axi_wlast),
        .s_axi_wvalid(s_axi_wvalid), .s_axi_wready(s_axi_wready),
        .s_axi_bid(s_axi_bid), .s_axi_bresp(s_axi_bresp), .s_axi_bvalid(s_axi_bvalid), .s_axi_bready(s_axi_bready),
        .s_axi_arid(s_axi_arid), .s_axi_araddr(s_axi_araddr), .s_axi_arlen(s_axi_arlen),
        .s_axi_arsize(s_axi_arsize), .s_axi_arburst(s_axi_arburst), .s_axi_arlock(s_axi_arlock),
        .s_axi_arcache(s_axi_arcache), .s_axi_arprot(s_axi_arprot), .s_axi_arvalid(s_axi_arvalid),
        .s_axi_arready(s_axi_arready),
        .s_axi_rid(s_axi_rid), .s_axi_rdata(s_axi_rdata), .s_axi_rresp(s_axi_rresp),
        .s_axi_rlast(s_axi_rlast), .s_axi_rvalid(s_axi_rvalid), .s_axi_rready(s_axi_rready),
        .lpddr_wr_addr_o(lpddr_wr_addr_o), .lpddr_wr_data_o(lpddr_wr_data_o), .lpddr_wr_pulse_o(lpddr_wr_pulse_o),
        .lpddr_wr_busy_i(lpddr_wr_busy_i), .lpddr_wr_done_i(lpddr_wr_done_i), .lpddr_wr_bresp_err_i(lpddr_wr_bresp_err_i)
    );

    gs_lpddr_wr_probe u_wr (
        .emif_clk(emif_clk), .emif_rst_n(emif_rst_n),
        .wr_pulse(lpddr_wr_pulse_o), .wr_addr(lpddr_wr_addr_o[29:0]), .wr_data(lpddr_wr_data_o),
        .full_beat(1'b0),
        .busy(lpddr_wr_busy_i), .done_toggle(lpddr_wr_done_i), .bresp_errs(lpddr_wr_bresp_err_i),
        .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:255];
    typedef enum logic [1:0] { W_IDLE, W_DATA, W_B } wst_t; wst_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<=0; 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[7:0]][by*8 +: 8] <= wdata[by*8 +: 8];
                    wst<=W_B;
                end
                W_B: begin bvalid<=1; bresp<=2'b00; if (bready) wst<=W_IDLE; end
            endcase
        end
    end

    // bridge base/offsets (Ch322 map)
    localparam [37:0] WRADDR = 38'h04C, WRDATA = 38'h050;

    task automatic axi_write32(input logic [37:0] addr, input logic [31:0] data);
        @(posedge clk);
        s_axi_awid<=0; s_axi_awaddr<=addr; s_axi_awlen<=0; s_axi_awsize<=2; s_axi_awburst<=1;
        s_axi_awlock<=0; s_axi_awcache<=0; s_axi_awprot<=0; s_axi_awvalid<=1;
        case (addr[3:2])
            2'b00: s_axi_wdata<={96'd0,data};
            2'b01: s_axi_wdata<={64'd0,data,32'd0};
            2'b10: s_axi_wdata<={32'd0,data,64'd0};
            default: s_axi_wdata<={data,96'd0};
        endcase
        s_axi_wstrb<=16'h000F<<({addr[3:2],2'b00}); s_axi_wlast<=1; s_axi_wvalid<=1; s_axi_bready<=1;
        wait (s_axi_awready); @(posedge clk); s_axi_awvalid<=0;
        wait (s_axi_wready);  @(posedge clk); s_axi_wvalid<=0;
        wait (s_axi_bvalid);  @(posedge clk); s_axi_bready<=0;
        repeat (8) @(posedge clk);   // let the probe complete the EMIF write
    endtask

    function automatic [31:0] memword(input [29:0] byteaddr);
        memword = mem[byteaddr[12:5]][ (byteaddr[4:2])*32 +: 32 ];
    endfunction

    // Ch352 — read a bridge register over the REAL AXI-lite slave (the 32-bit word is in the lane picked by
    // addr[3:2], same convention as the write path).
    task automatic axi_read32(input logic [37:0] addr, output logic [31:0] data);
        @(posedge clk);
        s_axi_arid<=0; s_axi_araddr<=addr; s_axi_arlen<=0; s_axi_arsize<=2; s_axi_arburst<=1; s_axi_arvalid<=1; s_axi_rready<=1;
        wait (s_axi_arready); @(posedge clk); s_axi_arvalid<=0;
        wait (s_axi_rvalid);  data = s_axi_rdata[ (addr[3:2])*32 +: 32 ]; @(posedge clk); s_axi_rready<=0;
    endtask
    // Faithful handshake (Codex): after each WRDATA, poll the STABLE write_pending flag (0x054 bit2) to 0 —
    // exactly what the real uploader does — instead of a magic fixed delay. Proves the write committed.
    int pend_to;
    task automatic wait_write_done;
        logic [31:0] st; pend_to=0;
        do begin axi_read32(38'h054, st); pend_to++; end while (st[2] && pend_to<2000);
        chk(pend_to<2000, "write_pending stuck high (write never completed)");
    endtask

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

    localparam [29:0] TBASE = 30'h0000_0200;   // small base (beat 16) for the TB mem
    initial begin
        s_axi_awvalid=0; s_axi_wvalid=0; s_axi_bready=0; s_axi_arvalid=0; s_axi_rready=0;
        s_axi_araddr=0; s_axi_arid=0; s_axi_arlen=0; s_axi_arsize=0; s_axi_arburst=0;
        s_axi_arlock=0; s_axi_arcache=0; s_axi_arprot=0;
        for (int i=0;i<256;i++) mem[i]=256'd0;
        reset_n=0; emif_rst_n=0; repeat (5) @(posedge clk); reset_n=1; emif_rst_n=1; repeat (3) @(posedge clk);

        // Row 0: set WRADDR=TBASE, stream 8 words. Each must land at TBASE + u*4 (NOT +4 offset).
        axi_write32(WRADDR, {2'd0, TBASE});
        for (int u=0; u<8; u++) begin axi_write32(WRDATA, 32'hA0000000 | u); wait_write_done(); end
        for (int u=0; u<8; u++)
            chk(memword(TBASE + u*4) === (32'hA0000000 | u),
                $sformatf("row0 word %0d @0x%0X = 0x%08X exp 0x%08X", u, TBASE+u*4, memword(TBASE+u*4), 32'hA0000000|u));

        // Row 1: re-set WRADDR (+256), stream 8 words — confirms re-addressing + no carryover.
        axi_write32(WRADDR, {2'd0, TBASE + 30'd256});
        for (int u=0; u<8; u++) begin axi_write32(WRDATA, 32'hB0000000 | u); wait_write_done(); end
        for (int u=0; u<8; u++)
            chk(memword(TBASE + 256 + u*4) === (32'hB0000000 | u),
                $sformatf("row1 word %0d @0x%0X = 0x%08X exp 0x%08X", u, TBASE+256+u*4, memword(TBASE+256+u*4), 32'hB0000000|u));

        chk(lpddr_wr_bresp_err_i === 32'd0, "wr_bresp_errs nonzero");

        $display("[texstage] errors=%0d", errors);
        if (errors==0) $display("[tb_lpddr_tex_staging] PASS"); else $display("[tb_lpddr_tex_staging] FAIL");
        $finish;
    end
    initial begin #500000; $display("[tb_lpddr_tex_staging] TIMEOUT"); $finish; end
endmodule
