// retroDE_ps2 — tb_top_psmct32_sh3_zarb  (Ch357 — SHARED-LPDDR arbitration integration sim, randomized backpressure)
//
// Codex pre-fit gate 1: "Shared-LPDDR integration sim with randomized backpressure. Arbitration is part of Ch357, not
// just wiring." This routes gs_lpddr_zc_emit's THREE AXI streams through the REAL de25 arbiters onto ONE shared LPDDR:
//   * color writes (c_*)  -> gs_lpddr_wr_arb port s0 (FB-writer, priority)
//   * Z writes    (z_aw/w/b) -> gs_lpddr_wr_arb port s2 (repurposed tile-Z-flush port)
//   * Z reads     (z_ar/r)   -> gs_lpddr_rd_arb port s0
// During render only color/Z traffic is active (scanout/texture idle, per gate 2). One behavioral LPDDR serves BOTH the
// merged write channel (m_aw/w/b) and the read channel (m_ar/r) with RANDOM backpressure on every channel. Replays the
// raster fragment trace (zsched_frags.txt) + an independent clamp16 scoreboard; asserts final color+Z == scoreboard,
// suppression, per-epoch ordered drain, disjoint ranges, no deadlock. This proves the arbitration under contention before
// wiring the de25 top. Strong-reject primary fixture, FB 256x210.
`timescale 1ns/1ps

module tb_top_psmct32_sh3_zarb;
    localparam int FB_PXW=256, FB_H=210;
    localparam int NPX=FB_PXW*FB_H;
    localparam [31:0] COLBASE=32'h0000_0000, ZBASE=32'h0014_0000, TEXBASE=32'h0020_0000;
    localparam int MEMBEATS=(TEXBASE>>5);         // one array up to the texture base (0x200000/32 = 65536 beats)

    logic gs_clk=0;  always #5 gs_clk=~gs_clk;
    logic axi_clk=0; always #2 axi_clk=~axi_clk;
    logic gs_rst_n, axi_rst_n; int errors; initial errors=0;

    logic enable, clear_start, clear_done, frame_drained;
    logic g_valid, g_ready; logic [11:0] g_x, g_y; logic [15:0] g_zq; logic g_zmsk, g_ztest, g_scene; logic [31:0] g_color;
    // zc_emit Z AXI
    logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
    logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
    logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
    logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
    // zc_emit Color AXI
    logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
    logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
    logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic idle;

    gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(FB_PXW), .FB_H(FB_H), .REQ_DEPTH(32), .COL_DEPTH(64)) dut (
        .gs_clk(gs_clk), .gs_rst_n(gs_rst_n), .enable(enable),
        .g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(g_zmsk),
        .g_ztest(g_ztest), .g_ztst(2'd2), .g_color(g_color), .g_be(4'hF), .g_scene(g_scene),
        .axi_clk(axi_clk), .axi_rst_n(axi_rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
        .z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
        .z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
        .z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
        .z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
        .z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
        .c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
        .c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
        .c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
        .z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
        .col_ovf(col_ovf), .bresp_err(bresp_err), .idle(idle)
    );

    // ---- write arbiter: s0 = color, s2 = Z, s1/s3 inert ; master -> shared LPDDR write ----
    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 m_bvalid, m_bready; logic [1:0] m_bresp;
    gs_lpddr_wr_arb u_wr_arb (
        .clk(axi_clk), .rst_n(axi_rst_n),
        .s0_awaddr(c_awaddr[29:0]), .s0_awburst(c_awburst), .s0_awid(7'd0), .s0_awlen(c_awlen), .s0_awsize(c_awsize),
        .s0_awvalid(c_awvalid), .s0_awready(c_awready), .s0_wdata(c_wdata), .s0_wstrb(c_wstrb), .s0_wlast(c_wlast),
        .s0_wvalid(c_wvalid), .s0_wready(c_wready), .s0_bresp(c_bresp), .s0_bvalid(c_bvalid), .s0_bready(c_bready),
        .s1_awaddr(30'd0), .s1_awburst(2'b01), .s1_awid(7'd1), .s1_awlen(8'd0), .s1_awsize(3'b101),
        .s1_awvalid(1'b0), .s1_awready(), .s1_wdata(256'd0), .s1_wstrb(32'd0), .s1_wlast(1'b0),
        .s1_wvalid(1'b0), .s1_wready(), .s1_bresp(), .s1_bvalid(), .s1_bready(1'b0),
        .s2_awaddr(z_awaddr[29:0]), .s2_awburst(z_awburst), .s2_awid(7'd2), .s2_awlen(z_awlen), .s2_awsize(z_awsize),
        .s2_awvalid(z_awvalid), .s2_awready(z_awready), .s2_wdata(z_wdata), .s2_wstrb(z_wstrb), .s2_wlast(z_wlast),
        .s2_wvalid(z_wvalid), .s2_wready(z_wready), .s2_bresp(z_bresp), .s2_bvalid(z_bvalid), .s2_bready(z_bready),
        .s3_awaddr(30'd0), .s3_awburst(2'b01), .s3_awid(7'd3), .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'b0),
        .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_bvalid(m_bvalid), .m_bready(m_bready), .m_bresp(m_bresp)
    );

    // ---- read arbiter: s0 = Z read ; s1/s2/s3 inert (scanout/tex idle during render) ; master -> shared LPDDR read ----
    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; logic [255:0] m_rdata; logic [1:0] m_rresp; logic m_rlast, m_rvalid, m_rready;
    gs_lpddr_rd_arb u_rd_arb (
        .clk(axi_clk), .rst_n(axi_rst_n),
        .s0_araddr(z_araddr[29:0]), .s0_arburst(z_arburst), .s0_arid(7'd0), .s0_arlen(z_arlen), .s0_arsize(z_arsize),
        .s0_arvalid(z_arvalid), .s0_arready(z_arready), .s0_rdata(z_rdata), .s0_rresp(z_rresp), .s0_rlast(z_rlast),
        .s0_rvalid(z_rvalid), .s0_rready(z_rready),
        .s1_araddr(30'd0), .s1_arburst(2'b01), .s1_arid(7'd1), .s1_arlen(8'd0), .s1_arsize(3'b101),
        .s1_arvalid(1'b0), .s1_arready(), .s1_rdata(), .s1_rresp(), .s1_rlast(), .s1_rvalid(), .s1_rready(1'b0),
        .s2_araddr(30'd0), .s2_arburst(2'b01), .s2_arid(7'd2), .s2_arlen(8'd0), .s2_arsize(3'b101),
        .s2_arvalid(1'b0), .s2_arready(), .s2_rdata(), .s2_rresp(), .s2_rlast(), .s2_rvalid(), .s2_rready(1'b0),
        .s3_araddr(30'd0), .s3_arburst(2'b01), .s3_arid(7'd3), .s3_arlen(8'd0), .s3_arsize(3'b101),
        .s3_arvalid(1'b0), .s3_arready(), .s3_rdata(), .s3_rresp(), .s3_rlast(), .s3_rvalid(), .s3_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)
    );

    logic [15:0] lf=16'hF00D; always_ff @(posedge axi_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};

    // ---- ONE shared behavioral LPDDR (write + read channels), address-decoded, random backpressure ----
    logic [255:0] shmem [0:MEMBEATS-1];
    logic [29:0] wa; logic [255:0] wd; logic [31:0] ws; logic aw_s, w_s; logic [3:0] bd; logic bp;
    logic [29:0] ra; logic rp; logic [3:0] rd_dly;
    always_ff @(posedge axi_clk or negedge axi_rst_n) begin
        if(!axi_rst_n) begin
            m_awready<=0; m_wready<=0; m_bvalid<=0; m_bresp<=0; aw_s<=0; w_s<=0; bp<=0; bd<=0;
            m_arready<=0; m_rvalid<=0; m_rlast<=0; m_rresp<=0; m_rdata<=0; rp<=0; rd_dly<=0;
        end else begin
            // write channel
            m_awready<=(!aw_s)?lf[0]:1'b0; m_wready<=(!w_s)?lf[1]:1'b0;
            if(m_awvalid&&m_awready) begin wa<=m_awaddr; aw_s<=1; m_awready<=0; end
            if(m_wvalid&&m_wready)  begin wd<=m_wdata; ws<=m_wstrb; w_s<=1; m_wready<=0; end
            if(aw_s&&w_s&&!bp&&!m_bvalid) begin
                for(int b=0;b<32;b++) if(ws[b]) shmem[wa>>5][b*8+:8]<=wd[b*8+:8];
                bp<=1; bd<={1'b0,lf[4:2]};
            end
            if(bp&&!m_bvalid) begin if(bd==0) begin m_bvalid<=1;m_bresp<=0;bp<=0;aw_s<=0;w_s<=0; end else bd<=bd-1; end
            if(m_bvalid&&m_bready) m_bvalid<=0;
            // read channel
            m_arready<=(!rp&&!m_rvalid)?lf[8]:1'b0;
            if(m_arvalid&&m_arready) begin ra<=m_araddr; rp<=1; rd_dly<={1'b0,lf[7:5]}; m_arready<=0; end
            if(rp&&!m_rvalid) begin if(rd_dly==0) begin m_rdata<=shmem[ra>>5]; m_rresp<=0; m_rvalid<=1; m_rlast<=1; rp<=0; end else rd_dly<=rd_dly-1; end
            if(m_rvalid&&m_rready) begin m_rvalid<=0; m_rlast<=0; end
        end
    end

    // disjoint-range monitors on the MERGED master streams
    always_ff @(posedge axi_clk) if(axi_rst_n) begin
        if(m_awvalid) begin
            if(!((m_awaddr<30'(COLBASE+18288*32)) || (m_awaddr>=30'(ZBASE) && m_awaddr<30'(ZBASE+13440*32))))
                begin $error("[zarb] merged AW %h not in color/Z range",m_awaddr); errors++; end
        end
        if(m_arvalid && (m_araddr<30'(ZBASE) || m_araddr>=30'(ZBASE+13440*32))) begin $error("[zarb] merged AR %h not in Z range",m_araddr); errors++; end
    end

    // ---- scoreboard + trace feed (same clamp16 persistent-Z model as the ROP gate) ----
    logic [15:0] sb_z [0:NPX-1]; logic [31:0] sb_col [0:NPX-1]; logic sb_seen [0:NPX-1];
    function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction
    int nfed;
    task automatic feed_frag(input int ep, input int x, input int y, input int z, input logic [31:0] col);
        int o; logic [15:0] zq; logic pass;
        @(negedge gs_clk); g_valid<=1; g_scene<=0; g_x<=x[11:0]; g_y<=y[11:0]; g_zq<=cl16(z); g_zmsk<=0; g_ztest<=1; g_color<=col;
        @(posedge gs_clk); while(!(g_valid && g_ready)) @(posedge gs_clk);
        @(negedge gs_clk); g_valid<=0;
        o=y*FB_PXW+x; zq=cl16(z); pass=(zq>=sb_z[o]);
        if(pass) begin sb_col[o]=col; sb_seen[o]=1; sb_z[o]=zq; end
        nfed++;
        if(lf[7]) repeat(1+lf[1:0]) @(posedge gs_clk);
    endtask
    task automatic send_marker();
        @(negedge gs_clk); g_valid<=1; g_scene<=1; @(posedge gs_clk);
        while(!(g_valid && g_ready)) @(posedge gs_clk);
        @(negedge gs_clk); g_valid<=0; g_scene<=0;
    endtask

    int fh, r, ep, x, y, z, cep, drains; logic [31:0] col;
    initial begin
        errors=0; enable=0; clear_start=0; g_valid=0; g_scene=0; g_x=0; g_y=0; g_zq=0; g_zmsk=0; g_ztest=1; g_color=0; nfed=0; drains=0;
        for(int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_seen[i]=1'b0; end
        for(int b=0;b<MEMBEATS;b++) shmem[b]=256'd0;              // host preclears color+Z (Z re-precleared by clear_start)
        gs_rst_n=0; axi_rst_n=0; repeat(6) @(posedge axi_clk); gs_rst_n=1; axi_rst_n=1; repeat(4) @(posedge axi_clk);
        enable=1;
        @(negedge axi_clk) clear_start=1; @(negedge axi_clk) clear_start=0;
        begin int g=0; while(!clear_done && g<600000) begin @(posedge axi_clk); g++; end end
        if(!clear_done) begin $error("[zarb] Z preclear timeout"); errors++; end

        fh=$fopen("zsched_frags.txt","r");
        if(fh==0) begin $display("[tb_top_psmct32_sh3_zarb] SKIP — zsched_frags.txt absent (run make tb_top_psmct32_sh3_zsched)"); $finish; end
        cep=0;
        while(!$feof(fh)) begin
            r=$fscanf(fh, "%d %d %d %d %h\n", ep, x, y, z, col);
            if(r==5) begin
                if(ep!=cep) begin
                    send_marker();
                    begin int g=0; while(!frame_drained && g<1000000) begin @(posedge axi_clk); g++; end end
                    if(!frame_drained) begin $error("[zarb] epoch %0d: frame_drained never rose",cep); errors++; end else drains++;
                    cep=ep;
                end
                feed_frag(ep, x, y, z, col);
            end else r=$fgetc(fh);
        end
        $fclose(fh);
        send_marker();
        begin int g=0; while(!frame_drained && g<1000000) begin @(posedge axi_clk); g++; end end
        if(!frame_drained) begin $error("[zarb] final: frame_drained never rose"); errors++; end else drains++;
        repeat(80) @(posedge axi_clk);

        // final: shared LPDDR (via the merged/arbitrated path) == scoreboard
        for(int o=0;o<NPX;o++) begin
            logic [15:0] zs; int zb, zl; zb=(ZBASE + o*2)>>5; zl=o&15; zs=shmem[zb][zl*16+:16];
            if(zs!==sb_z[o]) begin if(errors<12)$error("[zarb] Z px%0d=%04x exp %04x",o,zs,sb_z[o]);errors++; end
        end
        for(int o=0;o<NPX;o++) begin
            logic [31:0] cs; cs=shmem[o>>3][(o[2:0])*32+:32];       // COLBASE=0
            if(sb_seen[o]) begin if(cs!==sb_col[o]) begin if(errors<12)$error("[zarb] COLOR px%0d=%08x exp %08x",o,cs,sb_col[o]);errors++; end end
            else            begin if(cs!==32'd0)    begin if(errors<12)$error("[zarb] px%0d never-passed color=%08x (suppress)",o,cs);errors++; end end
        end
        if(col_ovf!==0)  begin $error("[zarb] col_ovf=%0d",col_ovf);errors++; end
        if(bresp_err!==0)begin $error("[zarb] bresp_err=%0d",bresp_err);errors++; end
        $display("[zarb] fed=%0d drains=%0d z_wr=%0d c_wr=%0d col_ovf=%0d bresp_err=%0d errors=%0d (COL 0x%0h..0x%0h, Z 0x%0h..0x%0h)",
                 nfed, drains, z_beats_written, c_beats_written, col_ovf, bresp_err, errors, COLBASE, COLBASE+18288*32, ZBASE, ZBASE+13440*32);
        if(errors==0) $display("[tb_top_psmct32_sh3_zarb] PASS"); else $display("[tb_top_psmct32_sh3_zarb] FAIL");
        $finish;
    end
    initial begin #500000000; $error("[tb_top_psmct32_sh3_zarb] TIMEOUT"); $finish; end
endmodule : tb_top_psmct32_sh3_zarb
