// retroDE_ps2 — tb_ee_ram_cpu_dmac_contention (Ch115)
//
// Minimal red/green guardrail for the CPU-fetch-vs-DMAC-read RAM
// contention surfaced by Ch114. Reproduces the failure with a
// RAM-resident EE bootlet running concurrently with a DMAC
// channel-2 transfer that fetches from RAM, and asserts that
// each beat the DMAC delivers byte-matches the pre-staged
// payload value at the corresponding RAM byte offset.
//
// Pre-Ch115 (no fix): the map's arbitration is
//   ram_rd_en  = cpu_rd_hit_ram || dmac_rd_hit_ram
//   ram_rd_addr = cpu_rd_hit_ram ? cpu_addr : dmac_addr
// When both fire same-cycle the CPU wins the bus, but the map
// still completes the DMAC's "request" next cycle with the CPU's
// data → DMAC sees corrupted payload. This TB fails on those
// cycles.
//
// Post-Ch115 (after the arbitration fix in ee_memory_map_stub):
// the map stalls the DMAC for one cycle on contention, replays
// its read once the CPU has cleared, and the DMAC sees clean
// data. This TB passes.
//
// Scope: keep narrow — no GS stack at all. Just RAM, map, EE
// core (ee_core_stub running from RAM at kseg0 0x80000800),
// DMAC, and a beat-capture observer. The intentional contention
// is created by the EE bootlet busy-jumping to itself in RAM
// while the DMAC is mid-transfer.

`timescale 1ns/1ps

module tb_ee_ram_cpu_dmac_contention;

    localparam int RAM_SIZE_BYTES  = 4 * 1024;
    localparam int RAM_ADDR_W      = $clog2(RAM_SIZE_BYTES);

    localparam int          QWC_TOTAL    = 16;
    localparam logic [31:0] PAYLOAD_MADR = 32'h0000_0100;
    localparam logic [31:0] EE_PROG_VA   = 32'h8000_0800;
    localparam int          EE_PROG_PHYS = 32'h0000_0800;

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

    // ------------------------------------------------------------
    // 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;

    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(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );
    assign ram_master_id = ram_rd_en ? 8'd1 : 8'd0;

    // ------------------------------------------------------------
    // DMAC + map
    // ------------------------------------------------------------
    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         dmac_gif_valid;
    logic [127:0] dmac_gif_data;
    logic         dmac_gif_last;
    logic         dmac_gif_ready;
    assign dmac_gif_ready = 1'b1;   // always-ready sink (no GIF in this TB)

    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;

    logic [127:0] map_to_dmac_rd_data;
    logic         map_to_dmac_rd_valid;

    dmac_reg_stub 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_gif_valid), .ep_data(dmac_gif_data),
        .ep_last(dmac_gif_last), .ep_ready(dmac_gif_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)
    );

    logic        ee_cpu_rd_en;
    logic [31:0] ee_cpu_rd_addr;
    logic [31:0] ee_cpu_rd_data;
    logic        ee_cpu_rd_valid;
    logic        ee_cpu_wr_en;
    logic [31:0] ee_cpu_wr_addr;
    logic [31:0] ee_cpu_wr_data;
    logic [3:0]  ee_cpu_wr_be;

    logic        map_ram_rd_en;
    logic [24:0] map_ram_rd_addr;

    ee_memory_map_stub u_map (
        .clk(clk), .rst_n(rst_n),
        .ee_rd_en  (ee_cpu_rd_en),
        .ee_rd_addr(ee_cpu_rd_addr),
        .ee_rd_data(ee_cpu_rd_data),
        .ee_rd_valid(ee_cpu_rd_valid),
        .ee_wr_en  (ee_cpu_wr_en),
        .ee_wr_addr(ee_cpu_wr_addr),
        .ee_wr_data(ee_cpu_wr_data),
        .ee_wr_be  (ee_cpu_wr_be),
        .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  (dmac_reg_wr_en),
        .ee_dmac_ch2_wr_addr(dmac_reg_offset),
        .ee_dmac_ch2_wr_data(dmac_reg_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(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

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

    // ------------------------------------------------------------
    // ee_core_stub running from RAM at 0x80000800.
    // ------------------------------------------------------------
    logic        core_go;
    logic        core_halt;
    logic [31:0] core_pc;
    logic        core_trap;
    logic [31:0] core_trap_pc;
    logic [31:0] core_trap_instr;
    logic        core_cpu_irq;
    assign core_cpu_irq = 1'b0;

    logic                core_ev_valid;
    trace_pkg::subsys_e  core_ev_subsys;
    trace_pkg::event_e   core_ev_event;
    logic [63:0]         core_ev_arg0, core_ev_arg1, core_ev_arg2, core_ev_arg3;
    logic [31:0]         core_ev_flags;

    ee_core_stub #(
        .PC_RESET(EE_PROG_VA),
        .STRICT_UNSUPPORTED(1'b0)
    ) u_core (
        .clk(clk), .rst_n(rst_n),
        .go_i(core_go),
        .map_rd_en (ee_cpu_rd_en),
        .map_rd_addr(ee_cpu_rd_addr),
        .map_rd_data(ee_cpu_rd_data),
        .map_rd_valid(ee_cpu_rd_valid),
        .map_wr_en (ee_cpu_wr_en),
        .map_wr_addr(ee_cpu_wr_addr),
        .map_wr_data(ee_cpu_wr_data),
        .map_wr_be (ee_cpu_wr_be),
        .cpu_irq(core_cpu_irq),
        .halt_o(core_halt), .pc_o(core_pc),
        .trap_o(core_trap), .trap_pc_o(core_trap_pc),
        .trap_instr_o(core_trap_instr),
        .ev_valid(core_ev_valid), .ev_subsys(core_ev_subsys),
        .ev_event(core_ev_event),
        .ev_arg0(core_ev_arg0), .ev_arg1(core_ev_arg1),
        .ev_arg2(core_ev_arg2), .ev_arg3(core_ev_arg3),
        .ev_flags(core_ev_flags)
    );

    // ------------------------------------------------------------
    // Beat-capture observer + payload check.
    // ------------------------------------------------------------
    logic [127:0] expected_payload [0:QWC_TOTAL-1];
    int           beat_idx;
    int           beat_mismatches;
    int           dmac_dones;

    initial begin
        beat_idx        = 0;
        beat_mismatches = 0;
        dmac_dones      = 0;
    end

    // Capture every accepted beat and compare against expected.
    always_ff @(posedge clk) begin
        if (rst_n && dmac_gif_valid && dmac_gif_ready) begin
            if (beat_idx < QWC_TOTAL) begin
                if (dmac_gif_data !== expected_payload[beat_idx]) begin
                    $error("contention corruption: beat %0d data=0x%032h expected=0x%032h",
                           beat_idx, dmac_gif_data, expected_payload[beat_idx]);
                    beat_mismatches <= beat_mismatches + 1;
                end
                beat_idx <= beat_idx + 1;
            end
        end
    end

    always_ff @(posedge clk) begin
        if (rst_n && dmac_ev_valid &&
            dmac_ev_event == trace_pkg::EV_DMA_DONE)
            dmac_dones <= dmac_dones + 1;
    end

    // ------------------------------------------------------------
    // Helpers.
    // ------------------------------------------------------------
    function automatic logic [31:0] enc_lui(input int rt, input logic [15:0] imm);
        return {6'h0F, 5'd0, rt[4:0], imm};
    endfunction
    function automatic logic [31:0] enc_ori(input int rt, input int rs,
                                            input logic [15:0] imm);
        return {6'h0D, rs[4:0], rt[4:0], imm};
    endfunction
    function automatic logic [31:0] enc_sw(input int rt, input int rs,
                                           input logic [15:0] imm);
        return {6'h2B, rs[4:0], rt[4:0], imm};
    endfunction
    function automatic logic [31:0] enc_j(input logic [31:0] target_va);
        // J target: opcode 0x02, target = (target_va >> 2)[25:0]
        return {6'h02, target_va[27:2]};
    endfunction

    task automatic ram_word(input int byte_off, input logic [31:0] word);
        int qw_idx;
        int lane;
        qw_idx = byte_off >> 4;
        lane   = (byte_off >> 2) & 3;
        u_ram.mem[qw_idx][lane*32 +: 32] = word;
    endtask

    task automatic ram_qword(input int byte_off, input logic [127:0] qword);
        int qw_idx;
        qw_idx = byte_off >> 4;
        u_ram.mem[qw_idx] = qword;
    endtask

    initial begin
        rst_n     = 1'b0;
        core_go   = 1'b0;
        ram_wr_en = 1'b0;
        ram_wr_addr = '0;
        ram_wr_data = 128'd0;
        ram_wr_be = 16'd0;

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

        // Stage QWC_TOTAL distinct GIF-shaped qwords at PAYLOAD_MADR.
        // Use a recognisable per-qword pattern so corruption shows up
        // unambiguously: qword[i] = (0xCAFE_BABE_DEAD_BEEFi + i) ×128.
        for (int i = 0; i < QWC_TOTAL; i++) begin
            logic [127:0] q;
            q = {64'hAAAA_BBBB_CCCC_0000 + 64'(i),
                 64'h1111_2222_3333_4444 + 64'(i)};
            expected_payload[i] = q;
            ram_qword(PAYLOAD_MADR + i*16, q);
        end

        // Preload RAM-resident EE bootlet at phys 0x800.
        // Sequence:
        //   LUI r10, 0x1000          ; DMAC ch2 base
        //   ORI r10, r10, 0xA000
        //   ORI r11, r0,  0x0100     ; MADR
        //   SW  r11, 0x10(r10)
        //   ORI r11, r0,  QWC_TOTAL  ; QWC
        //   SW  r11, 0x20(r10)
        //   ORI r11, r0,  0x0001     ; CHCR.start
        //   SW  r11, 0x00(r10)
        //   J   loop                 ; busy-loop forever (max contention)
        //   NOP                      ; delay slot
        // loop is at byte 0x800 + 32 = 0x820.
        begin
            int off = EE_PROG_PHYS;
            ram_word(off       , enc_lui(10, 16'h1000));
            ram_word(off  +  4 , enc_ori(10, 10, 16'hA000));
            ram_word(off  +  8 , enc_ori(11, 0, 16'(PAYLOAD_MADR)));
            ram_word(off  + 12 , enc_sw (11, 10, 16'h0010));
            ram_word(off  + 16 , enc_ori(11, 0, 16'(QWC_TOTAL)));
            ram_word(off  + 20 , enc_sw (11, 10, 16'h0020));
            ram_word(off  + 24 , enc_ori(11, 0, 16'h0001));
            ram_word(off  + 28 , enc_sw (11, 10, 16'h0000));
            // Self-jump: target VA = 0x80000820 (the J itself).
            ram_word(off  + 32 , enc_j(EE_PROG_VA + 32'd32));
            ram_word(off  + 36 , 32'd0);   // NOP delay slot
        end

        // Pulse core_go. The EE will: kick DMAC, then busy-jump to
        // itself in RAM. The busy-jump generates instruction-fetch
        // pressure on RAM, contending with DMAC's GIF-payload
        // reads. Pre-Ch115: contention silently corrupts beats.
        @(negedge clk);
        core_go = 1'b1;
        @(negedge clk);
        core_go = 1'b0;

        // Wait for DMA to complete.
        wait (dmac_dones == 1);
        repeat (20) @(posedge clk);

        // Aggregate. PASS requires BOTH the beat count to match AND
        // every captured beat to byte-match the expected payload —
        // a beat-count short-fall (missing beats) was previously
        // only logged via $error and didn't fail the displayed
        // PASS/FAIL summary.
        begin
            int errors;
            errors = 0;
            if (beat_idx != QWC_TOTAL) begin
                $error("DMA delivered %0d beats (expected %0d)", beat_idx, QWC_TOTAL);
                errors = errors + 1;
            end
            if (beat_mismatches != 0) begin
                $error("DMA payload had %0d mismatches", beat_mismatches);
                errors = errors + beat_mismatches;
            end
            $display("[tb_ee_ram_cpu_dmac_contention] beats=%0d mismatches=%0d core_pc=0x%08x",
                     beat_idx, beat_mismatches, core_pc);
            if (errors == 0) $display("[tb_ee_ram_cpu_dmac_contention] PASS");
            else             $display("[tb_ee_ram_cpu_dmac_contention] FAIL");
        end
        $finish;
    end

    initial begin
        #2000000;
        $error("[tb_ee_ram_cpu_dmac_contention] timeout");
        $finish;
    end

endmodule : tb_ee_ram_cpu_dmac_contention
