// retroDE_ps2 — tb_iop_memory_map_collision (Ch261)
// ============================================================================
// Focused arbitration test for the iop_memory_map_stub deferred-CPU-RAM-read
// slot landed alongside Ch261. The Ch261 SIF-landing TB surfaced a real bug:
// when CPU and DMA both wanted the iop_ram_stub read port in the same cycle,
// the map prioritized the CPU's address but routed `ram_rd_data` to BOTH
// the CPU and the DMA paths — silent DMA data corruption.
//
// This TB:
//   1. Preloads two distinct sentinels at two distinct IOP RAM word offsets.
//   2. Drives `iop_rd_en` (CPU) and `dma_rd_en` (DMA) on the SAME cycle, with
//      DIFFERENT addresses (one each).
//   3. Asserts:
//        - `dma_rd_data` returns the DMA-target sentinel on the next cycle
//          (DMA wins the bus immediately).
//        - `iop_rd_data` returns the CPU-target sentinel one cycle LATER
//          (deferred CPU read services on the next non-DMA cycle).
//        - `iop_rd_valid` stays LOW during the collision-deferral cycle and
//          pulses only when the deferred read actually finishes.
//
// No CPU or DMAC core here — the TB drives the map's CPU- and DMA-read ports
// directly, so any future change to those clients can't mask the regression.

`timescale 1ns/1ps

module tb_iop_memory_map_collision;

    localparam int IOP_RAM_BYTES  = 4 * 1024;
    localparam int IOP_RAM_ADDR_W = $clog2(IOP_RAM_BYTES);

    localparam logic [31:0] CPU_RAM_ADDR = 32'h0000_0100;  // word 0x40
    localparam logic [31:0] DMA_RAM_ADDR = 32'h0000_0200;  // word 0x80
    localparam logic [31:0] CPU_SENTINEL = 32'hCAFE_C0DE;
    localparam logic [31:0] DMA_SENTINEL = 32'hDEAD_BEEF;

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

    // ------------------------------------------------------------------
    // DUT ports
    // ------------------------------------------------------------------
    logic        cpu_rd_en;
    logic [31:0] cpu_rd_addr;
    logic [31:0] cpu_rd_data;
    logic        cpu_rd_valid;

    logic        cpu_wr_en;
    logic [31:0] cpu_wr_addr;
    logic [31:0] cpu_wr_data;
    logic [3:0]  cpu_wr_be;

    logic        tb_br_wr_en;
    logic [31:0] tb_br_wr_addr;
    logic [31:0] tb_br_wr_data;
    logic [3:0]  tb_br_wr_be;

    logic        dma_rd_en;
    logic [31:0] dma_rd_addr;
    logic [7:0]  dma_master_id;
    logic [31:0] dma_rd_data;
    logic        dma_rd_valid;

    logic        ram_rd_en;
    logic [20:0] ram_rd_addr;
    logic [31:0] ram_rd_data;
    logic        ram_rd_valid;
    logic        ram_wr_en;
    logic [20:0] ram_wr_addr;
    logic [31:0] ram_wr_data;
    logic [3:0]  ram_wr_be;
    logic [7:0]  ram_master_id;

    iop_memory_map_stub u_map (
        .clk(clk), .rst_n(rst_n),
        .iop_rd_en(cpu_rd_en), .iop_rd_addr(cpu_rd_addr),
        .iop_rd_data(cpu_rd_data), .iop_rd_valid(cpu_rd_valid),
        .iop_wr_en(cpu_wr_en), .iop_wr_addr(cpu_wr_addr),
        .iop_wr_data(cpu_wr_data), .iop_wr_be(cpu_wr_be),
        .master_id(8'd2),
        .bridge_wr_en(tb_br_wr_en), .bridge_wr_addr(tb_br_wr_addr),
        .bridge_wr_data(tb_br_wr_data), .bridge_wr_be(tb_br_wr_be),
        .bridge_master_id(8'd0),
        .dma_rd_en(dma_rd_en), .dma_rd_addr(dma_rd_addr),
        .dma_master_id(dma_master_id),
        .dma_rd_data(dma_rd_data), .dma_rd_valid(dma_rd_valid),
        .sif_rd_en(), .sif_rd_addr(),
        .sif_rd_data(32'd0), .sif_rd_valid(1'b0),
        .sif_wr_en(), .sif_wr_addr(), .sif_wr_data(),
        .iop_dmac_rd_en(), .iop_dmac_rd_addr(),
        .iop_dmac_rd_data(32'd0), .iop_dmac_rd_valid(1'b0),
        .iop_dmac_wr_en(), .iop_dmac_wr_addr(), .iop_dmac_wr_data(),
        .iop_intc_rd_en(), .iop_intc_rd_addr(),
        .iop_intc_rd_data(32'd0), .iop_intc_rd_valid(1'b0),
        .iop_intc_wr_en(), .iop_intc_wr_addr(), .iop_intc_wr_data(),
        .input_p1(32'd0), .input_p2(32'd0),
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        .ram_rd_en(ram_rd_en), .ram_rd_addr(ram_rd_addr),
        .ram_rd_data(ram_rd_data), .ram_rd_valid(ram_rd_valid),
        .ram_wr_en(ram_wr_en), .ram_wr_addr(ram_wr_addr),
        .ram_wr_data(ram_wr_data), .ram_wr_be(ram_wr_be),
        .ram_master_id(ram_master_id),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    iop_ram_stub #(.SIZE_BYTES(IOP_RAM_BYTES)) u_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(ram_rd_en),
        .rd_addr(ram_rd_addr[IOP_RAM_ADDR_W-1:0]),
        .rd_data(ram_rd_data), .rd_valid(ram_rd_valid),
        .wr_en(ram_wr_en),
        .wr_addr(ram_wr_addr[IOP_RAM_ADDR_W-1:0]),
        .wr_data(ram_wr_data), .wr_be(ram_wr_be),
        .master_id(ram_master_id),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    // ------------------------------------------------------------------
    // Helpers
    // ------------------------------------------------------------------
    task automatic bridge_write(input logic [31:0] addr,
                                input logic [31:0] data);
        @(negedge clk);
        tb_br_wr_en   = 1'b1;
        tb_br_wr_addr = addr;
        tb_br_wr_data = data;
        tb_br_wr_be   = 4'b1111;
        @(negedge clk);
        tb_br_wr_en   = 1'b0;
        tb_br_wr_addr = 32'd0;
        tb_br_wr_data = 32'd0;
        tb_br_wr_be   = 4'd0;
    endtask

    // ------------------------------------------------------------------
    // Stimulus + assertions
    // ------------------------------------------------------------------
    int errors;
    initial errors = 0;

    initial begin
        rst_n         = 1'b0;
        cpu_rd_en     = 1'b0;
        cpu_rd_addr   = 32'd0;
        cpu_wr_en     = 1'b0;
        cpu_wr_addr   = 32'd0;
        cpu_wr_data   = 32'd0;
        cpu_wr_be     = 4'd0;
        tb_br_wr_en   = 1'b0;
        tb_br_wr_addr = 32'd0;
        tb_br_wr_data = 32'd0;
        tb_br_wr_be   = 4'd0;
        dma_rd_en     = 1'b0;
        dma_rd_addr   = 32'd0;
        dma_master_id = 8'd5;

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

        // Preload the two distinct sentinels.
        bridge_write(CPU_RAM_ADDR, CPU_SENTINEL);
        bridge_write(DMA_RAM_ADDR, DMA_SENTINEL);
        repeat (2) @(posedge clk);

        // --------------------------------------------------------------
        // The collision cycle. Drive both reads on the same posedge.
        // Sample valids/data at the negedge between each posedge —
        // that mirrors the existing tb_iop_memory_map_stub do_read_expect
        // pattern (rd_pending pulses for one cycle, the negedge sits
        // inside that cycle so the valid is observable).
        // --------------------------------------------------------------
        @(negedge clk);
        cpu_rd_en   = 1'b1;
        cpu_rd_addr = CPU_RAM_ADDR;
        dma_rd_en   = 1'b1;
        dma_rd_addr = DMA_RAM_ADDR;

        @(negedge clk);
        // Pulses go low — single-cycle requests. The posedge between
        // these two negedges latched cpu_pend_addr (deferred) and
        // serviced DMA's read.
        cpu_rd_en   = 1'b0;
        cpu_rd_addr = 32'd0;
        dma_rd_en   = 1'b0;
        dma_rd_addr = 32'd0;

        // Sample now (negedge just after the cycle that serviced DMA
        // and deferred CPU). DMA's data should be on dma_rd_data with
        // dma_rd_valid high; CPU's valid still LOW.
        if (dma_rd_valid !== 1'b1) begin
            $error("[tb_iop_memory_map_collision] cycle+1: dma_rd_valid not asserted (got %0b)", dma_rd_valid);
            errors = errors + 1;
        end
        if (dma_rd_data !== DMA_SENTINEL) begin
            $error("[tb_iop_memory_map_collision] cycle+1: dma_rd_data=0x%08h expected DMA_SENTINEL=0x%08h",
                   dma_rd_data, DMA_SENTINEL);
            errors = errors + 1;
        end
        if (cpu_rd_valid !== 1'b0) begin
            $error("[tb_iop_memory_map_collision] cycle+1: cpu_rd_valid asserted prematurely (should be deferred)");
            errors = errors + 1;
        end

        // Next negedge: the cycle that serviced the deferred CPU read
        // (no DMA contending) is now visible. cpu_rd_valid pulses with
        // CPU_SENTINEL; dma_rd_valid back to 0.
        @(negedge clk);
        if (cpu_rd_valid !== 1'b1) begin
            $error("[tb_iop_memory_map_collision] cycle+2: cpu_rd_valid not asserted (deferred read should land)");
            errors = errors + 1;
        end
        if (cpu_rd_data !== CPU_SENTINEL) begin
            $error("[tb_iop_memory_map_collision] cycle+2: cpu_rd_data=0x%08h expected CPU_SENTINEL=0x%08h",
                   cpu_rd_data, CPU_SENTINEL);
            errors = errors + 1;
        end
        if (dma_rd_valid !== 1'b0) begin
            $error("[tb_iop_memory_map_collision] cycle+2: dma_rd_valid stuck high (should have de-asserted)");
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Sanity 2 — solo CPU read (no DMA contention) returns its
        // sentinel one cycle later with no deferral.
        // --------------------------------------------------------------
        repeat (3) @(posedge clk);
        @(negedge clk);
        cpu_rd_en   = 1'b1;
        cpu_rd_addr = CPU_RAM_ADDR;
        @(negedge clk);
        cpu_rd_en   = 1'b0;
        cpu_rd_addr = 32'd0;
        if (cpu_rd_valid !== 1'b1) begin
            $error("[tb_iop_memory_map_collision] solo CPU read: cpu_rd_valid not asserted");
            errors = errors + 1;
        end
        if (cpu_rd_data !== CPU_SENTINEL) begin
            $error("[tb_iop_memory_map_collision] solo CPU read: cpu_rd_data=0x%08h expected 0x%08h",
                   cpu_rd_data, CPU_SENTINEL);
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Sanity 3 — solo DMA read returns its sentinel one cycle later
        // with no deferral and no CPU interference.
        // --------------------------------------------------------------
        repeat (3) @(posedge clk);
        @(negedge clk);
        dma_rd_en   = 1'b1;
        dma_rd_addr = DMA_RAM_ADDR;
        @(negedge clk);
        dma_rd_en   = 1'b0;
        dma_rd_addr = 32'd0;
        if (dma_rd_valid !== 1'b1) begin
            $error("[tb_iop_memory_map_collision] solo DMA read: dma_rd_valid not asserted");
            errors = errors + 1;
        end
        if (dma_rd_data !== DMA_SENTINEL) begin
            $error("[tb_iop_memory_map_collision] solo DMA read: dma_rd_data=0x%08h expected 0x%08h",
                   dma_rd_data, DMA_SENTINEL);
            errors = errors + 1;
        end

        repeat (4) @(posedge clk);
        $display("[tb_iop_memory_map_collision] errors=%0d", errors);
        if (errors == 0) $display("[tb_iop_memory_map_collision] PASS");
        else             $display("[tb_iop_memory_map_collision] FAIL");
        $finish;
    end

    initial begin
        #100_000;
        $error("[tb_iop_memory_map_collision] TIMEOUT");
        $finish;
    end

endmodule : tb_iop_memory_map_collision
