// ============================================================================
// tb_gs_lpddr_wr_arb.sv  (Ch322 Brick 3)
//
// Verifies the 2:1 EMIF WRITE arbiter. Two masters (port0 = FB writer with
// PRIORITY, port1 = HPS write-probe) request single-beat writes concurrently:
//   - both transactions complete (each sees its own bvalid)
//   - both data words land at the right beats in the EMIF target
//   - port0 wins the bus first when both request simultaneously
//   - a port1-only write works when port0 is idle
// ============================================================================
`timescale 1ns/1ps

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

    // port0
    logic [29:0] s0_awaddr; logic [1:0] s0_awburst; logic [6:0] s0_awid;
    logic [7:0] s0_awlen; logic [2:0] s0_awsize; logic s0_awvalid, s0_awready;
    logic [255:0] s0_wdata; logic [31:0] s0_wstrb; logic s0_wlast, s0_wvalid, s0_wready;
    logic [1:0] s0_bresp; logic s0_bvalid, s0_bready;
    // port1
    logic [29:0] s1_awaddr; logic [1:0] s1_awburst; logic [6:0] s1_awid;
    logic [7:0] s1_awlen; logic [2:0] s1_awsize; logic s1_awvalid, s1_awready;
    logic [255:0] s1_wdata; logic [31:0] s1_wstrb; logic s1_wlast, s1_wvalid, s1_wready;
    logic [1:0] s1_bresp; logic s1_bvalid, s1_bready;
    // port2 (Z writer)
    logic [29:0] s2_awaddr; logic [1:0] s2_awburst; logic [6:0] s2_awid;
    logic [7:0] s2_awlen; logic [2:0] s2_awsize; logic s2_awvalid, s2_awready;
    logic [255:0] s2_wdata; logic [31:0] s2_wstrb; logic s2_wlast, s2_wvalid, s2_wready;
    logic [1:0] s2_bresp; logic s2_bvalid, s2_bready;
    // master
    logic [29:0] m_awaddr; logic [1:0] m_awburst; logic [6:0] m_awid;
    logic [7:0] m_awlen; logic [2:0] m_awsize; logic m_awvalid, m_awready;
    logic [255:0] m_wdata; logic [31:0] m_wstrb; logic m_wlast, m_wvalid, m_wready;
    logic [1:0] m_bresp; logic m_bvalid, m_bready;

    gs_lpddr_wr_arb dut (
        .clk(clk), .rst_n(rst_n),
        .s0_awaddr(s0_awaddr), .s0_awburst(s0_awburst), .s0_awid(s0_awid), .s0_awlen(s0_awlen),
        .s0_awsize(s0_awsize), .s0_awvalid(s0_awvalid), .s0_awready(s0_awready),
        .s0_wdata(s0_wdata), .s0_wstrb(s0_wstrb), .s0_wlast(s0_wlast), .s0_wvalid(s0_wvalid), .s0_wready(s0_wready),
        .s0_bresp(s0_bresp), .s0_bvalid(s0_bvalid), .s0_bready(s0_bready),
        .s1_awaddr(s1_awaddr), .s1_awburst(s1_awburst), .s1_awid(s1_awid), .s1_awlen(s1_awlen),
        .s1_awsize(s1_awsize), .s1_awvalid(s1_awvalid), .s1_awready(s1_awready),
        .s1_wdata(s1_wdata), .s1_wstrb(s1_wstrb), .s1_wlast(s1_wlast), .s1_wvalid(s1_wvalid), .s1_wready(s1_wready),
        .s1_bresp(s1_bresp), .s1_bvalid(s1_bvalid), .s1_bready(s1_bready),
        .s2_awaddr(s2_awaddr), .s2_awburst(s2_awburst), .s2_awid(s2_awid), .s2_awlen(s2_awlen),
        .s2_awsize(s2_awsize), .s2_awvalid(s2_awvalid), .s2_awready(s2_awready),
        .s2_wdata(s2_wdata), .s2_wstrb(s2_wstrb), .s2_wlast(s2_wlast), .s2_wvalid(s2_wvalid), .s2_wready(s2_wready),
        .s2_bresp(s2_bresp), .s2_bvalid(s2_bvalid), .s2_bready(s2_bready),
        // Ch323 diag — port 3 (HPS write-probe) unused in this priority test; tie inert.
        .s3_awaddr(30'd0), .s3_awburst(2'b01), .s3_awid(7'd5), .s3_awlen(8'd0), .s3_awsize(3'b101),
        .s3_awvalid(1'b0), .s3_awready(), .s3_wdata(256'd0), .s3_wstrb(32'd0), .s3_wlast(1'b0),
        .s3_wvalid(1'b0), .s3_wready(), .s3_bresp(), .s3_bvalid(), .s3_bready(1'b1),
        
        .m_awaddr(m_awaddr), .m_awburst(m_awburst), .m_awid(m_awid), .m_awlen(m_awlen),
        .m_awsize(m_awsize), .m_awvalid(m_awvalid), .m_awready(m_awready),
        .m_wdata(m_wdata), .m_wstrb(m_wstrb), .m_wlast(m_wlast), .m_wvalid(m_wvalid), .m_wready(m_wready),
        .m_bresp(m_bresp), .m_bvalid(m_bvalid), .m_bready(m_bready)
    );

    // behavioral EMIF write target
    logic [255:0] mem [0:63];
    int grant_log [0:7]; int grant_n = 0;   // record which port won each transaction
    typedef enum logic [1:0] { W_IDLE, W_DATA, W_B } wst_t; wst_t wst;
    logic [29:0] beat;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin wst<=W_IDLE; m_awready<=0; m_wready<=0; m_bvalid<=0; m_bresp<=0; end
        else begin
            m_awready<=0; m_wready<=0; m_bvalid<=0;
            case (wst)
                W_IDLE: if (m_awvalid) begin
                    m_awready<=1; beat<=(m_awaddr>>5);
                    grant_log[grant_n] <= dut.grant; grant_n <= grant_n+1;  // record winner
                    wst<=W_DATA;
                end
                W_DATA: if (m_wvalid) begin
                    m_wready<=1;
                    for (int by=0; by<32; by++) if (m_wstrb[by]) mem[beat][by*8 +: 8] <= m_wdata[by*8 +: 8];
                    wst<=W_B;
                end
                W_B: begin m_bvalid<=1; m_bresp<=2'b00; if (m_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("[wrarb] FAIL: %s", m); end endtask

    // ---- master driver tasks (port 0 / port 1) ----
    task automatic m0_write(input [29:0] a, input [31:0] d);
        @(posedge clk);
        s0_awaddr<=a; s0_awlen<=0; s0_awsize<=3'b101; s0_awburst<=2'b01; s0_awid<=7'd0; s0_awvalid<=1;
        do @(posedge clk); while (!s0_awready);
        s0_awvalid<=0;
        s0_wdata<=256'd0; s0_wdata[31:0]<=d; s0_wstrb<=32'h0000_000F; s0_wlast<=1; s0_wvalid<=1;
        do @(posedge clk); while (!s0_wready);
        s0_wvalid<=0; s0_wlast<=0; s0_bready<=1;
        do @(posedge clk); while (!s0_bvalid);
        s0_bready<=0;
    endtask
    task automatic m1_write(input [29:0] a, input [31:0] d);
        @(posedge clk);
        s1_awaddr<=a; s1_awlen<=0; s1_awsize<=3'b101; s1_awburst<=2'b01; s1_awid<=7'd5; s1_awvalid<=1;
        do @(posedge clk); while (!s1_awready);
        s1_awvalid<=0;
        s1_wdata<=256'd0; s1_wdata[63:32]<=d; s1_wstrb<=32'h0000_00F0; s1_wlast<=1; s1_wvalid<=1;
        do @(posedge clk); while (!s1_wready);
        s1_wvalid<=0; s1_wlast<=0; s1_bready<=1;
        do @(posedge clk); while (!s1_bvalid);
        s1_bready<=0;
    endtask
    task automatic m2_write(input [29:0] a, input [31:0] d);
        @(posedge clk);
        s2_awaddr<=a; s2_awlen<=0; s2_awsize<=3'b101; s2_awburst<=2'b01; s2_awid<=7'd6; s2_awvalid<=1;
        do @(posedge clk); while (!s2_awready);
        s2_awvalid<=0;
        s2_wdata<=256'd0; s2_wdata[95:64]<=d; s2_wstrb<=32'h0000_0F00; s2_wlast<=1; s2_wvalid<=1;
        do @(posedge clk); while (!s2_wready);
        s2_wvalid<=0; s2_wlast<=0; s2_bready<=1;
        do @(posedge clk); while (!s2_bvalid);
        s2_bready<=0;
    endtask

    initial begin
        s0_awvalid=0; s0_wvalid=0; s0_wlast=0; s0_bready=0; s0_wstrb=0; s0_wdata=0; s0_awaddr=0; s0_awlen=0; s0_awsize=0; s0_awburst=0; s0_awid=0;
        s1_awvalid=0; s1_wvalid=0; s1_wlast=0; s1_bready=0; s1_wstrb=0; s1_wdata=0; s1_awaddr=0; s1_awlen=0; s1_awsize=0; s1_awburst=0; s1_awid=0;
        s2_awvalid=0; s2_wvalid=0; s2_wlast=0; s2_bready=0; s2_wstrb=0; s2_wdata=0; s2_awaddr=0; s2_awlen=0; s2_awsize=0; s2_awburst=0; s2_awid=0;
        for (int i=0;i<64;i++) mem[i]=256'd0;
        repeat (4) @(posedge clk); rst_n=1; repeat (2) @(posedge clk);

        // concurrent: both masters request the SAME cycle. port0 must win first.
        fork
            m0_write(30'h0000_0000, 32'hF00D_0000);   // beat0 lane0
            m1_write(30'h0000_0020, 32'hBEEF_0001);   // beat1 lane1
        join

        check(mem[0][31:0]===32'hF00D_0000, "port0 data landed beat0 lane0");
        check(mem[1][63:32]===32'hBEEF_0001, "port1 data landed beat1 lane1");
        check(grant_n>=2, "fewer than 2 transactions recorded");
        check(grant_log[0]===2'd1, "port0 (priority) did not win first");

        // ---- explicit 3-way priority proof (Ch323, Codex): FB-writer > Z-writer >
        // wr-probe, i.e. grant order s0(1) > s2(3) > s1(2). All three fire concurrently.
        grant_n = 0;
        fork
            m0_write(30'h0000_0060, 32'h0FB0_0003);   // beat3 lane0  (FB)
            m2_write(30'h0000_0080, 32'h0ED0_0004);   // beat4 lane2  (Z)
            m1_write(30'h0000_00A0, 32'h0DB0_0005);   // beat5 lane1  (probe)
        join
        check(mem[3][31:0] ===32'h0FB0_0003, "FB    data landed beat3 lane0");
        check(mem[4][95:64]===32'h0ED0_0004, "Z     data landed beat4 lane2");
        check(mem[5][63:32]===32'h0DB0_0005, "probe data landed beat5 lane1");
        check(grant_n>=3, "fewer than 3 transactions recorded in priority test");
        if (!(grant_log[0]===2'd1 && grant_log[1]===2'd3 && grant_log[2]===2'd2)) begin
            errors++;
            $display("[wrarb] priority order = %0d %0d %0d (exp 1 3 2 = FB,Z,probe)",
                     grant_log[0], grant_log[1], grant_log[2]);
        end else
            $display("[wrarb] write priority OK: FB(1) > Z(3) > probe(2)");

        // port1-only write when others idle
        m1_write(30'h0000_0040, 32'hCAFE_0002);       // beat2 lane1
        check(mem[2][63:32]===32'hCAFE_0002, "port1 solo write landed");

        $display("[wrarb] errors=%0d transactions=%0d first_grant=%0d", errors, grant_n, grant_log[0]);
        if (errors==0) $display("[tb_gs_lpddr_wr_arb] PASS");
        else           $display("[tb_gs_lpddr_wr_arb] FAIL");
        $finish;
    end
    initial begin #200000; $display("[tb_gs_lpddr_wr_arb] TIMEOUT"); $finish; end
endmodule
