// retroDE_ps2 — tb_sif_dma_mid_stall
//
// Backpressure hardening for the SIF DMA path. Extends the smoke test by
// asserting the receive-side stall MID-TRANSFER, after beats 0 and 1 have
// been accepted. Pressure-tests the DMAC's ACTIVE_SEND hold behavior
// specifically — the previous smoke test only covered stall-at-start.
//
// Scenario:
//   1. Preload 4 qwords into ee_ram_stub
//   2. Start a QWC=4 DMAC transfer with stall_in=0
//   3. Wait for dma_beat_count to reach 2 (beats 0+1 accepted)
//   4. Assert stall_in=1
//   5. Spin for a window; verify:
//        - no new DMA_BEAT events fire
//        - no DMA_DONE fires
//        - rx_count stays at 2
//   6. Release stall_in=0
//   7. Wait for dma_done_seen
//   8. Verify:
//        - final beat count == 4
//        - final rx_count == 4
//        - beat 2's source address == MADR + 32 (resumes at correct offset)
//        - all four receive slots hold the expected qwords in order
//
// Pass criteria prove:
//   - DMAC honors ep_ready backpressure mid-transfer, not just at start
//   - No beats duplicated or dropped across the stall window
//   - Source-address stepping resumes correctly after release

`timescale 1ns/1ps

module tb_sif_dma_mid_stall;

    logic clk;
    logic rst_n;

    initial clk = 1'b0;
    always #5 clk = ~clk;

    localparam int QWC_VAL = 4;
    localparam int RAM_SIZE_BYTES = 4 * 1024;
    localparam int RAM_ADDR_W     = $clog2(RAM_SIZE_BYTES);
    localparam int SIF_DEPTH      = 8;
    localparam int SIF_IDX_W      = $clog2(SIF_DEPTH);

    localparam logic [31:0] PAYLOAD_MADR = 32'h0000_0300;

    // ------------------------------------------------------------------
    // ee_ram_stub
    // ------------------------------------------------------------------

    logic                  ram_rd_en;
    logic [RAM_ADDR_W-1:0] ram_rd_addr;
    logic [127:0]          ram_rd_data;
    logic                  ram_rd_valid;

    logic                  ram_wr_en;
    logic [RAM_ADDR_W-1:0] ram_wr_addr;
    logic [127:0]          ram_wr_data;
    logic [15:0]           ram_wr_be;

    logic [7:0]            ram_master_id;

    logic                ram_ev_valid;
    trace_pkg::subsys_e  ram_ev_subsys;
    trace_pkg::event_e   ram_ev_event;
    logic [63:0]         ram_ev_arg0, ram_ev_arg1, ram_ev_arg2, ram_ev_arg3;
    logic [31:0]         ram_ev_flags;

    ee_ram_stub #(.SIZE_BYTES(RAM_SIZE_BYTES)) u_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(ram_rd_en), .rd_addr(ram_rd_addr),
        .rd_data(ram_rd_data), .rd_valid(ram_rd_valid),
        .wr_en(ram_wr_en), .wr_addr(ram_wr_addr),
        .wr_data(ram_wr_data), .wr_be(ram_wr_be),
        .master_id(ram_master_id),
        .ev_valid(ram_ev_valid), .ev_subsys(ram_ev_subsys),
        .ev_event(ram_ev_event),
        .ev_arg0(ram_ev_arg0), .ev_arg1(ram_ev_arg1),
        .ev_arg2(ram_ev_arg2), .ev_arg3(ram_ev_arg3),
        .ev_flags(ram_ev_flags)
    );

    assign ram_master_id = ram_rd_en ? 8'd1 : 8'd0;

    // ------------------------------------------------------------------
    // DMAC (channel 5)
    // ------------------------------------------------------------------

    logic        dmac_reg_wr_en;
    logic [7:0]  dmac_reg_offset;
    logic [31:0] dmac_reg_wr_data;

    logic         dmac_mem_rd_en;
    logic [31:0]  dmac_mem_rd_addr;
    logic [127:0] map_to_dmac_rd_data;
    logic         map_to_dmac_rd_valid;

    logic         dmac_ep_valid;
    logic [127:0] dmac_ep_data;
    logic         dmac_ep_last;
    logic         dmac_ep_ready;

    logic                dmac_ev_valid;
    trace_pkg::subsys_e  dmac_ev_subsys;
    trace_pkg::event_e   dmac_ev_event;
    logic [63:0]         dmac_ev_arg0, dmac_ev_arg1, dmac_ev_arg2, dmac_ev_arg3;
    logic [31:0]         dmac_ev_flags;

    dmac_reg_stub #(.CHANNEL(4'd5), .PATH_ID(4'd5)) u_dmac (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(dmac_reg_wr_en), .reg_offset(dmac_reg_offset),
        .reg_wr_data(dmac_reg_wr_data),
        .reg_rd_en(1'b0), .reg_rd_data(), .reg_rd_valid(),
        .mem_rd_en(dmac_mem_rd_en), .mem_rd_addr(dmac_mem_rd_addr),
        .mem_rd_data(map_to_dmac_rd_data), .mem_rd_valid(map_to_dmac_rd_valid),
        .ep_valid(dmac_ep_valid), .ep_data(dmac_ep_data),
        .ep_last(dmac_ep_last), .ep_ready(dmac_ep_ready),
        .irq_completion_o(),
        .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys),
        .ev_event(dmac_ev_event),
        .ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1),
        .ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3),
        .ev_flags(dmac_ev_flags)
    );

    // ------------------------------------------------------------------
    // ee_memory_map_stub
    // ------------------------------------------------------------------

    logic        map_ram_rd_en;
    logic [24:0] map_ram_rd_addr;

    logic                map_ev_valid;
    trace_pkg::subsys_e  map_ev_subsys;
    trace_pkg::event_e   map_ev_event;
    logic [63:0]         map_ev_arg0, map_ev_arg1, map_ev_arg2, map_ev_arg3;
    logic [31:0]         map_ev_flags;

    ee_memory_map_stub u_map (
        .clk(clk), .rst_n(rst_n),
        .ee_rd_en(1'b0), .ee_rd_addr(32'd0),
        .ee_rd_data(), .ee_rd_valid(),
        .ee_wr_en(1'b0), .ee_wr_addr(32'd0),
        .ee_wr_data(32'd0), .ee_wr_be(4'd0),
        .dmac_rd_en(dmac_mem_rd_en), .dmac_rd_addr(dmac_mem_rd_addr),
        .dmac_rd_data(map_to_dmac_rd_data),
        .dmac_rd_valid(map_to_dmac_rd_valid),
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        .ram_rd_en(map_ram_rd_en), .ram_rd_addr(map_ram_rd_addr),
        .ram_rd_data(ram_rd_data), .ram_rd_valid(ram_rd_valid),
        .bridge_wr_en(1'b0), .bridge_wr_addr(32'd0),
        .bridge_wr_data(128'd0), .bridge_wr_be(16'd0),
        .bridge_master_id(8'd0),
        .ram_wr_en(), .ram_wr_addr(), .ram_wr_data(),
        .ram_wr_be(), .ram_master_id(),
        .ee_dmac_ch2_wr_en(), .ee_dmac_ch2_wr_addr(), .ee_dmac_ch2_wr_data(),
        .ee_dmac_ch2_rd_en(), .ee_dmac_ch2_rd_addr(),
        .ee_dmac_ch2_rd_data(32'd0), .ee_dmac_ch2_rd_valid(1'b0),
        .ee_intc_wr_en(), .ee_intc_wr_addr(), .ee_intc_wr_data(),
        .ee_intc_rd_en(), .ee_intc_rd_addr(),
        .ee_intc_rd_data(32'd0), .ee_intc_rd_valid(1'b0),
        .ee_misc_mmio_wr_en(), .ee_misc_mmio_wr_addr(), .ee_misc_mmio_wr_data(), .ee_misc_mmio_wr_be(),
        .ee_misc_mmio_rd_en(), .ee_misc_mmio_rd_addr(),
        .ee_misc_mmio_rd_data(32'd0), .ee_misc_mmio_rd_valid(1'b0),
        .ee_biu_wr_en(), .ee_biu_wr_addr(), .ee_biu_wr_data(), .ee_biu_wr_be(),
        .ee_biu_rd_en(), .ee_biu_rd_addr(),
        .ee_biu_rd_data(32'd0), .ee_biu_rd_valid(1'b0),
        .ev_valid(map_ev_valid), .ev_subsys(map_ev_subsys),
        .ev_event(map_ev_event),
        .ev_arg0(map_ev_arg0), .ev_arg1(map_ev_arg1),
        .ev_arg2(map_ev_arg2), .ev_arg3(map_ev_arg3),
        .ev_flags(map_ev_flags)
    );

    assign ram_rd_en   = map_ram_rd_en;
    assign ram_rd_addr = map_ram_rd_addr[RAM_ADDR_W-1:0];

    // ------------------------------------------------------------------
    // sif_dma_stub
    // ------------------------------------------------------------------

    logic                  sif_rd_en;
    logic [SIF_IDX_W-1:0]  sif_rd_idx;
    logic [127:0]          sif_rd_data;
    logic                  sif_rd_valid;

    logic                  sif_stall;

    logic [31:0]           sif_rx_count;
    logic                  sif_last_seen;

    logic                sif_ev_valid;
    trace_pkg::subsys_e  sif_ev_subsys;
    trace_pkg::event_e   sif_ev_event;
    logic [63:0]         sif_ev_arg0, sif_ev_arg1, sif_ev_arg2, sif_ev_arg3;
    logic [31:0]         sif_ev_flags;

    sif_dma_stub #(.DEPTH(SIF_DEPTH)) u_sif (
        .clk(clk), .rst_n(rst_n),
        .in_valid(dmac_ep_valid), .in_data(dmac_ep_data),
        .in_last(dmac_ep_last),   .in_ready(dmac_ep_ready),
        .rd_en(sif_rd_en), .rd_idx(sif_rd_idx),
        .rd_data(sif_rd_data), .rd_valid(sif_rd_valid),
        .stall_in(sif_stall),
        .rx_count(sif_rx_count), .last_seen(sif_last_seen),
        .ev_valid(sif_ev_valid), .ev_subsys(sif_ev_subsys),
        .ev_event(sif_ev_event),
        .ev_arg0(sif_ev_arg0), .ev_arg1(sif_ev_arg1),
        .ev_arg2(sif_ev_arg2), .ev_arg3(sif_ev_arg3),
        .ev_flags(sif_ev_flags)
    );

    // ------------------------------------------------------------------
    // Trace sinks
    // ------------------------------------------------------------------

    trace_sink_stub #(.FILENAME("sif_dma_mid_stall_ram.trace"),  .SINK_LABEL("ee_ram"))
        u_trace_ram (.clk(clk), .rst_n(rst_n),
            .ev_valid(ram_ev_valid), .ev_subsys(ram_ev_subsys),
            .ev_event(ram_ev_event), .ev_arg0(ram_ev_arg0),
            .ev_arg1(ram_ev_arg1), .ev_arg2(ram_ev_arg2),
            .ev_arg3(ram_ev_arg3), .ev_flags(ram_ev_flags));

    trace_sink_stub #(.FILENAME("sif_dma_mid_stall_map.trace"),  .SINK_LABEL("map"))
        u_trace_map (.clk(clk), .rst_n(rst_n),
            .ev_valid(map_ev_valid), .ev_subsys(map_ev_subsys),
            .ev_event(map_ev_event), .ev_arg0(map_ev_arg0),
            .ev_arg1(map_ev_arg1), .ev_arg2(map_ev_arg2),
            .ev_arg3(map_ev_arg3), .ev_flags(map_ev_flags));

    trace_sink_stub #(.FILENAME("sif_dma_mid_stall_dmac.trace"), .SINK_LABEL("dmac"))
        u_trace_dmac (.clk(clk), .rst_n(rst_n),
            .ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys),
            .ev_event(dmac_ev_event), .ev_arg0(dmac_ev_arg0),
            .ev_arg1(dmac_ev_arg1), .ev_arg2(dmac_ev_arg2),
            .ev_arg3(dmac_ev_arg3), .ev_flags(dmac_ev_flags));

    trace_sink_stub #(.FILENAME("sif_dma_mid_stall_sif.trace"),  .SINK_LABEL("sif"))
        u_trace_sif (.clk(clk), .rst_n(rst_n),
            .ev_valid(sif_ev_valid), .ev_subsys(sif_ev_subsys),
            .ev_event(sif_ev_event), .ev_arg0(sif_ev_arg0),
            .ev_arg1(sif_ev_arg1), .ev_arg2(sif_ev_arg2),
            .ev_arg3(sif_ev_arg3), .ev_flags(sif_ev_flags));

    // ------------------------------------------------------------------
    // State / counters
    // ------------------------------------------------------------------

    int          dma_beat_count;
    int          dma_done_count;
    logic        dma_done_seen;
    int          errors;

    // Per-beat source address capture — verifies post-stall addressing.
    logic [63:0] beat_src [0:QWC_VAL-1];

    initial begin
        dma_beat_count = 0;
        dma_done_count = 0;
        dma_done_seen  = 1'b0;
        errors         = 0;
        for (int i = 0; i < QWC_VAL; i++) beat_src[i] = 64'd0;
    end

    always_ff @(posedge clk) begin
        if (rst_n && dmac_ev_valid) begin
            if (dmac_ev_event == trace_pkg::EV_DMA_BEAT) begin
                // arg1 is beat index; arg2 is source address.
                if (dmac_ev_arg1 < QWC_VAL)
                    beat_src[dmac_ev_arg1[1:0]] <= dmac_ev_arg2;
                dma_beat_count <= dma_beat_count + 1;
            end
            if (dmac_ev_event == trace_pkg::EV_DMA_DONE) begin
                dma_done_count <= dma_done_count + 1;
                dma_done_seen  <= 1'b1;
            end
        end
    end

    // ------------------------------------------------------------------
    // Payload table
    // ------------------------------------------------------------------

    logic [127:0] expected_payload [0:QWC_VAL-1];

    initial begin
        expected_payload[0] = 128'h0F0F0F0F_0F0F0F0F_1010_1010_1010_1010;
        expected_payload[1] = 128'hF0F0F0F0_F0F0F0F0_2020_2020_2020_2020;
        expected_payload[2] = 128'h55AA55AA_55AA55AA_3030_3030_3030_3030;
        expected_payload[3] = 128'hAA55AA55_AA55AA55_4040_4040_4040_4040;
    end

    // ------------------------------------------------------------------
    // Helpers
    // ------------------------------------------------------------------

    task automatic write_dmac(input logic [7:0] offset, input logic [31:0] data);
        @(negedge clk);
        dmac_reg_wr_en   = 1'b1;
        dmac_reg_offset  = offset;
        dmac_reg_wr_data = data;
        @(negedge clk);
        dmac_reg_wr_en   = 1'b0;
        dmac_reg_offset  = 8'd0;
        dmac_reg_wr_data = 32'd0;
    endtask

    task automatic preload_ram_qword(input logic [RAM_ADDR_W-1:0] addr,
                                     input logic [127:0] data);
        @(negedge clk);
        ram_wr_en   = 1'b1;
        ram_wr_addr = addr;
        ram_wr_data = data;
        ram_wr_be   = 16'hFFFF;
        @(negedge clk);
        ram_wr_en   = 1'b0;
        ram_wr_addr = '0;
        ram_wr_data = 128'd0;
        ram_wr_be   = 16'd0;
    endtask

    task automatic sif_read_slot(input logic [SIF_IDX_W-1:0] idx,
                                 output logic [127:0] data);
        @(negedge clk);
        sif_rd_en  = 1'b1;
        sif_rd_idx = idx;
        @(negedge clk);
        sif_rd_en  = 1'b0;
        sif_rd_idx = '0;
        data = sif_rd_data;
    endtask

    // ------------------------------------------------------------------
    // Stimulus
    // ------------------------------------------------------------------

    int           wait_spin;
    int           stalled_beat_count;
    logic         stalled_done;
    int           stall_rx_snapshot;
    logic [127:0] rb;

    initial begin
        rst_n            = 1'b0;
        dmac_reg_wr_en   = 1'b0;
        dmac_reg_offset  = 8'd0;
        dmac_reg_wr_data = 32'd0;
        ram_wr_en        = 1'b0;
        ram_wr_addr      = '0;
        ram_wr_data      = 128'd0;
        ram_wr_be        = 16'd0;
        sif_rd_en        = 1'b0;
        sif_rd_idx       = '0;
        sif_stall        = 1'b0;

        repeat (4) @(posedge clk);
        rst_n = 1'b1;
        repeat (2) @(posedge clk);

        // Preload 4 qwords at MADR, MADR+16, MADR+32, MADR+48.
        begin : do_preload
            logic [31:0] a;
            for (int i = 0; i < QWC_VAL; i++) begin
                a = PAYLOAD_MADR + (i * 32'd16);
                preload_ram_qword(a[RAM_ADDR_W-1:0], expected_payload[i]);
            end
        end

        // Start transfer WITHOUT stall. Beats should progress normally.
        write_dmac(8'h10, PAYLOAD_MADR);       // MADR
        write_dmac(8'h20, 32'd4);              // QWC = 4
        write_dmac(8'h00, 32'h0000_0001);      // CHCR start

        // Wait for beats 0 and 1 to complete, then stall mid-transfer.
        wait_spin = 0;
        while (dma_beat_count < 2 && wait_spin < 200) begin
            @(posedge clk);
            wait_spin = wait_spin + 1;
        end
        if (dma_beat_count < 2) begin
            $error("[tb_sif_dma_mid_stall] pre-stall: never reached 2 beats (got %0d)",
                   dma_beat_count);
            errors = errors + 1;
        end

        // Assert stall mid-transfer.
        @(negedge clk);
        sif_stall = 1'b1;

        // Hold for a window and verify no progress.
        repeat (60) @(posedge clk);
        stalled_beat_count = dma_beat_count;
        stalled_done       = dma_done_seen;
        stall_rx_snapshot  = sif_rx_count;

        if (stalled_beat_count > 2) begin
            $error("[tb_sif_dma_mid_stall] mid-stall: beat count advanced (%0d → %0d)",
                   2, stalled_beat_count);
            errors = errors + 1;
        end
        if (stalled_done) begin
            $error("[tb_sif_dma_mid_stall] mid-stall: DMA_DONE fired during stall");
            errors = errors + 1;
        end
        if (stall_rx_snapshot > 32'd2) begin
            $error("[tb_sif_dma_mid_stall] mid-stall: rx_count advanced (2 → %0d)",
                   stall_rx_snapshot);
            errors = errors + 1;
        end

        // Release stall; remainder must finish cleanly.
        @(negedge clk);
        sif_stall = 1'b0;

        begin : wait_done
            int spin;
            spin = 0;
            while (!dma_done_seen && spin < 200) begin
                @(posedge clk);
                spin = spin + 1;
            end
            if (!dma_done_seen) begin
                $error("[tb_sif_dma_mid_stall] DMA_DONE never fired after stall release");
                errors = errors + 1;
            end
        end

        repeat (4) @(posedge clk);

        // Payload integrity + ordering checks.
        if (dma_beat_count !== 4) begin
            $error("[tb_sif_dma_mid_stall] final beat count=%0d, expected 4",
                   dma_beat_count);
            errors = errors + 1;
        end
        if (sif_rx_count !== 32'd4) begin
            $error("[tb_sif_dma_mid_stall] final rx_count=%0d, expected 4",
                   sif_rx_count);
            errors = errors + 1;
        end

        // Beat 2's source address (post-stall resume point) must be MADR+32.
        if (beat_src[2] !== 64'(PAYLOAD_MADR + 32'd32)) begin
            $error("[tb_sif_dma_mid_stall] beat[2] src=0x%h, expected 0x%h (MADR+32)",
                   beat_src[2], PAYLOAD_MADR + 32'd32);
            errors = errors + 1;
        end

        // Full buffer payload verification.
        for (int i = 0; i < QWC_VAL; i++) begin
            sif_read_slot(SIF_IDX_W'(i), rb);
            if (rb !== expected_payload[i]) begin
                $error("[tb_sif_dma_mid_stall] slot[%0d]: got 0x%032h expected 0x%032h",
                       i, rb, expected_payload[i]);
                errors = errors + 1;
            end
        end

        repeat (4) @(posedge clk);

        // ------------------------------------------------------------------
        $display("[tb_sif_dma_mid_stall] stalled_beats=%0d stalled_done=%0d final_beats=%0d rx=%0d beat2_src=0x%h last=%0b errors=%0d",
                 stalled_beat_count, stalled_done, dma_beat_count,
                 sif_rx_count, beat_src[2], sif_last_seen, errors);

        if (errors == 0 &&
            stalled_beat_count == 2 && !stalled_done &&
            dma_beat_count == 4 && sif_rx_count === 32'd4 &&
            beat_src[2] === 64'(PAYLOAD_MADR + 32'd32) &&
            sif_last_seen)
            $display("[tb_sif_dma_mid_stall] PASS");
        else
            $display("[tb_sif_dma_mid_stall] FAIL");

        $finish;
    end

    initial begin
        #400000;
        $error("[tb_sif_dma_mid_stall] timeout");
        $finish;
    end

endmodule : tb_sif_dma_mid_stall
