// ============================================================================
// tb_gs_lpddr_rd_arb — Ch320 Brick 2
//
// Verifies the 2:1 read arbiter: each port alone returns its own data, and on
// simultaneous request port 0 (scanout) wins first, then port 1 (probe) is
// served — neither loses its transaction. Slave returns rdata = araddr so each
// port's captured data proves it got ITS read.
// ============================================================================
`timescale 1ns/1ps

module tb_gs_lpddr_rd_arb;
    logic clk = 0, rst_n;
    always #5 clk = ~clk;

    // port 0
    logic [29:0] s0_araddr; logic s0_arvalid, s0_arready, s0_rready, s0_rvalid, s0_rlast;
    logic [255:0] s0_rdata; logic [1:0] s0_rresp;
    // port 1
    logic [29:0] s1_araddr; logic s1_arvalid, s1_arready, s1_rready, s1_rvalid, s1_rlast;
    logic [255:0] s1_rdata; logic [1:0] s1_rresp;
    // port 2 (texture fill) + port 3 (tile reload)
    logic [29:0] s2_araddr; logic s2_arvalid, s2_arready, s2_rready, s2_rvalid, s2_rlast;
    logic [255:0] s2_rdata; logic [1:0] s2_rresp;
    logic [29:0] s3_araddr; logic s3_arvalid, s3_arready, s3_rready, s3_rvalid, s3_rlast;
    logic [255:0] s3_rdata; logic [1:0] s3_rresp;
    // master
    logic [29:0] m_araddr; logic [1:0] m_arburst; logic [6:0] m_arid; logic [7:0] m_arlen;
    logic [2:0] m_arsize; logic m_arvalid, m_arready, m_rvalid, m_rlast, m_rready;
    logic [255:0] m_rdata; logic [1:0] m_rresp;

    gs_lpddr_rd_arb dut (
        .clk(clk), .rst_n(rst_n),
        .s0_araddr(s0_araddr), .s0_arburst(2'b01), .s0_arid(7'd2), .s0_arlen(8'd0),
        .s0_arsize(3'b101), .s0_arvalid(s0_arvalid), .s0_arready(s0_arready),
        .s0_rdata(s0_rdata), .s0_rresp(s0_rresp), .s0_rlast(s0_rlast), .s0_rvalid(s0_rvalid), .s0_rready(s0_rready),
        .s1_araddr(s1_araddr), .s1_arburst(2'b01), .s1_arid(7'd1), .s1_arlen(8'd0),
        .s1_arsize(3'b101), .s1_arvalid(s1_arvalid), .s1_arready(s1_arready),
        .s1_rdata(s1_rdata), .s1_rresp(s1_rresp), .s1_rlast(s1_rlast), .s1_rvalid(s1_rvalid), .s1_rready(s1_rready),
        // Ch322 port 2 (texture fill) + Ch323 port 3 (tile reload) — TB-driven for the
        // explicit 4-way priority proof; inert (arvalid=0) for the 2:1 contention tests.
        .s2_araddr(s2_araddr), .s2_arburst(2'b01), .s2_arid(7'd4), .s2_arlen(8'd0),
        .s2_arsize(3'b101), .s2_arvalid(s2_arvalid), .s2_arready(s2_arready),
        .s2_rdata(s2_rdata), .s2_rresp(s2_rresp), .s2_rlast(s2_rlast), .s2_rvalid(s2_rvalid), .s2_rready(s2_rready),
        .s3_araddr(s3_araddr), .s3_arburst(2'b01), .s3_arid(7'd6), .s3_arlen(8'd0),
        .s3_arsize(3'b101), .s3_arvalid(s3_arvalid), .s3_arready(s3_arready),
        .s3_rdata(s3_rdata), .s3_rresp(s3_rresp), .s3_rlast(s3_rlast), .s3_rvalid(s3_rvalid), .s3_rready(s3_rready),
        .s4_araddr('0), .s4_arburst(2'b01), .s4_arid('0), .s4_arlen('0),
        .s4_arsize(3'b101), .s4_arvalid(1'b0), .s4_arready(),
        .s4_rdata(), .s4_rresp(), .s4_rlast(), .s4_rvalid(), .s4_rready(1'b0),
        .m_araddr(m_araddr), .m_arburst(m_arburst), .m_arid(m_arid), .m_arlen(m_arlen),
        .m_arsize(m_arsize), .m_arvalid(m_arvalid), .m_arready(m_arready),
        .m_rdata(m_rdata), .m_rresp(m_rresp), .m_rlast(m_rlast), .m_rvalid(m_rvalid), .m_rready(m_rready)
    );

    // slave: rdata = {226'd0, araddr}
    localparam [1:0] S_IDLE=0,S_LAT=1,S_RESP=2; logic [1:0] sst; logic [2:0] sdly; logic [29:0] sa;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin sst<=S_IDLE; m_arready<=0; m_rvalid<=0; m_rlast<=0; m_rdata<=0; m_rresp<=0; sdly<=0; sa<=0;
        end else begin
            m_arready <= 0;
            case (sst)
              S_IDLE: if (m_arvalid) begin m_arready<=1; sa<=m_araddr; sdly<=3'd2; sst<=S_LAT; end
              S_LAT:  if (sdly==0) begin m_rdata<={226'd0,sa}; m_rlast<=1; m_rvalid<=1; sst<=S_RESP; end else sdly<=sdly-1;
              S_RESP: if (m_rready) begin m_rvalid<=0; m_rlast<=0; sst<=S_IDLE; end
              default: sst<=S_IDLE;
            endcase
        end
    end

    integer errors = 0;
    task automatic do_port0(input [29:0] a);
        begin s0_araddr=a; s0_arvalid=1; @(posedge clk); while(!s0_arready) @(posedge clk);
              s0_arvalid=0; s0_rready=1; while(!s0_rvalid) @(posedge clk);
              if (s0_rdata[29:0]!==a) begin errors=errors+1; $display("[tb] port0 data %h exp %h", s0_rdata[29:0], a); end
              @(posedge clk); s0_rready=0; end
    endtask
    task automatic do_port1(input [29:0] a);
        begin s1_araddr=a; s1_arvalid=1; @(posedge clk); while(!s1_arready) @(posedge clk);
              s1_arvalid=0; s1_rready=1; while(!s1_rvalid) @(posedge clk);
              if (s1_rdata[29:0]!==a) begin errors=errors+1; $display("[tb] port1 data %h exp %h", s1_rdata[29:0], a); end
              @(posedge clk); s1_rready=0; end
    endtask

    // contention: both request; expect port0 first, then port1, both correct.
    logic p0_done, p1_done;
    task automatic contend(input [29:0] a0, input [29:0] a1);
        begin
            p0_done=0; p1_done=0;
            fork
                begin do_port0(a0); p0_done=1; end
                begin do_port1(a1); p1_done=1; end
            join
        end
    endtask

    // ---- explicit 4-way priority proof (Ch323, Codex): scanout > tile_reload > probe >
    // texture_fill, i.e. grant order s0(1) > s3(4) > s1(2) > s2(3). Hold all four arvalid,
    // record the grant sequence the arbiter chooses.
    int grant_seq [0:3]; int gn;
    task automatic priority_test();
        begin
            // Completion is deliberately registered, so ownership remains
            // visible for one cycle after the requester's RLAST handshake.
            while (dut.grant!=3'd0) @(posedge clk);
            gn = 0;
            s0_araddr=30'h10; s1_araddr=30'h11; s2_araddr=30'h12; s3_araddr=30'h13;
            s0_arvalid=1; s1_arvalid=1; s2_arvalid=1; s3_arvalid=1;
            s0_rready=1; s1_rready=1; s2_rready=1; s3_rready=1;
            repeat (4) begin
                while (dut.grant==3'd0) @(posedge clk);     // a port granted
                grant_seq[gn] = int'(dut.grant); gn++;
                // drop that port's request so it doesn't re-win, let it complete.
                case (dut.grant)
                    3'd1: s0_arvalid=0; 3'd2: s1_arvalid=0; 3'd3: s2_arvalid=0; 3'd4: s3_arvalid=0;
                endcase
                while (dut.grant!=3'd0) @(posedge clk);     // transaction completes
            end
            s0_rready=0; s1_rready=0; s2_rready=0; s3_rready=0;
            if (!(grant_seq[0]==1 && grant_seq[1]==4 && grant_seq[2]==2 && grant_seq[3]==3)) begin
                errors=errors+1;
                $display("[tb] priority order = %0d %0d %0d %0d (exp 1 4 2 3 = scanout,tile_reload,probe,texfill)",
                         grant_seq[0], grant_seq[1], grant_seq[2], grant_seq[3]);
            end else
                $display("[tb] read priority OK: scanout(1) > tile_reload(4) > probe(2) > texfill(3)");
        end
    endtask

    initial begin
        s0_arvalid=0; s1_arvalid=0; s2_arvalid=0; s3_arvalid=0;
        s0_rready=0; s1_rready=0; s2_rready=0; s3_rready=0;
        s0_araddr=0; s1_araddr=0; s2_araddr=0; s3_araddr=0;
        rst_n=0; repeat(6) @(posedge clk); rst_n=1; repeat(2) @(posedge clk);

        do_port1(30'h0000_0040);                 // probe alone
        do_port0(30'h0000_0020);                 // scanout alone
        contend(30'h0000_0100, 30'h0000_0200);   // scanout vs probe: scanout first
        contend(30'h0000_0AA0, 30'h0000_0550);
        priority_test();                          // 4-way explicit priority

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