// retroDE_ps2 — tb_iop_dmac_intc
//
// First IOP-side interrupt flow. Proves:
//   - IOP DMAC ch9 completion surfaces into an IOP-local INTC via a
//     one-cycle pulse, latching the pending bit.
//   - The INTC is reached through the real IOP map at 0x1F80_1070
//     (PS2-faithful IOP INTC placement). Both the doorbell-check read
//     and the W1C ack write go through the map.
//   - Mask / pending / clear semantics hold, including the "no false
//     reassertion after clear unless a new transfer" invariant.
//
// Chain:
//   iop_ram_stub (source, preloaded via CPU map writes)
//     └─► iop_memory_map_stub (dma_rd_* for source, CPU for registers)
//           └─► iop_dmac_reg_stub (ch9) — irq_completion_o pulse
//                 │
//                 ▼
//         intc_stub (IOP instance) — bit 0 = DMAC ch9 completion
//
//   TB (as IOP CPU) reaches INTC_STAT / INTC_MASK through the real
//   IOP map at:
//     0x1F80_1070  INTC_STAT    (W1C on write)
//     0x1F80_1080  INTC_MASK    (plain write)
//
//   DMAC endpoint drained by TB-held ep_ready=1 (no bridge for this
//   focused TB; payload on the floor).
//
// Scenarios:
//   1. Pre-transfer quiescence: INTC_STAT reads 0, cpu_irq = 0.
//   2. First transfer: program MADR/BCR/CHCR, wait for DONE.
//      INTC_STAT[0] reads 1 through the map; cpu_irq still 0 (mask=0).
//   3. Mask on via map: cpu_irq goes 1.
//   4. Ack via map (W1C to INTC_STAT): cpu_irq falls, INTC_STAT=0.
//   5. Quiescent hold: INTC_STAT stays 0 for N cycles.
//   6. Second transfer: INTC_STAT re-latches. Ack clears. Done.

`timescale 1ns/1ps

module tb_iop_dmac_intc;

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

    localparam logic [31:0] DMAC_CH9_BASE  = 32'h1F80_1520;
    localparam logic [31:0] DMAC_MADR_ADDR = DMAC_CH9_BASE | 32'h00;
    localparam logic [31:0] DMAC_BCR_ADDR  = DMAC_CH9_BASE | 32'h04;
    localparam logic [31:0] DMAC_CHCR_ADDR = DMAC_CH9_BASE | 32'h08;

    // Real PS2 IOP INTC placement: I_STAT at 0x1F80_1070, I_MASK at
    // 0x1F80_1074 (4-byte stride, NOT the 16-byte stride the EE INTC
    // uses). The intc_stub is parameterized accordingly on the IOP
    // instance.
    localparam logic [31:0] INTC_STAT_ADDR   = 32'h1F80_1070;
    localparam logic [31:0] INTC_MASK_ADDR   = 32'h1F80_1074;
    localparam logic [7:0]  INTC_STAT_OFFSET = 8'h70;
    localparam logic [7:0]  INTC_MASK_OFFSET = 8'h74;

    localparam logic [31:0] SRC_BASE = 32'h0000_0200;
    localparam int          BEATS    = 4;              // small + fast

    logic clk;
    logic rst_n;

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

    // ------------------------------------------------------------------
    // IOP map + IOP RAM
    // ------------------------------------------------------------------

    logic        iop_rd_en;
    logic [31:0] iop_rd_addr;
    logic [31:0] iop_rd_data;
    logic        iop_rd_valid;

    logic        iop_wr_en;
    logic [31:0] iop_wr_addr;
    logic [31:0] iop_wr_data;
    logic [3:0]  iop_wr_be;

    logic        iop_ram_rd_en;
    logic [20:0] iop_ram_rd_addr;
    logic [31:0] iop_ram_rd_data;
    logic        iop_ram_rd_valid;
    logic        iop_ram_wr_en;
    logic [20:0] iop_ram_wr_addr;
    logic [31:0] iop_ram_wr_data;
    logic [3:0]  iop_ram_wr_be;
    logic [7:0]  iop_ram_master_id;

    logic        map_dmac_rd_en;
    logic [3:0]  map_dmac_rd_addr;
    logic [31:0] map_dmac_rd_data;
    logic        map_dmac_rd_valid;
    logic        map_dmac_wr_en;
    logic [3:0]  map_dmac_wr_addr;
    logic [31:0] map_dmac_wr_data;

    logic        map_intc_rd_en;
    logic [7:0]  map_intc_rd_addr;
    logic [31:0] map_intc_rd_data;
    logic        map_intc_rd_valid;
    logic        map_intc_wr_en;
    logic [7:0]  map_intc_wr_addr;
    logic [31:0] map_intc_wr_data;

    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                iop_map_ev_valid;
    trace_pkg::subsys_e  iop_map_ev_subsys;
    trace_pkg::event_e   iop_map_ev_event;
    logic [63:0]         iop_map_ev_arg0, iop_map_ev_arg1, iop_map_ev_arg2, iop_map_ev_arg3;
    logic [31:0]         iop_map_ev_flags;

    iop_memory_map_stub u_iop_map (
        .clk(clk), .rst_n(rst_n),
        .iop_rd_en(iop_rd_en), .iop_rd_addr(iop_rd_addr),
        .iop_rd_data(iop_rd_data), .iop_rd_valid(iop_rd_valid),
        .iop_wr_en(iop_wr_en), .iop_wr_addr(iop_wr_addr),
        .iop_wr_data(iop_wr_data), .iop_wr_be(iop_wr_be),
        .master_id(8'd2),
        .bridge_wr_en(1'b0), .bridge_wr_addr(32'd0),
        .bridge_wr_data(32'd0), .bridge_wr_be(4'd0),
        .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(map_dmac_rd_en), .iop_dmac_rd_addr(map_dmac_rd_addr),
        .iop_dmac_rd_data(map_dmac_rd_data), .iop_dmac_rd_valid(map_dmac_rd_valid),
        .iop_dmac_wr_en(map_dmac_wr_en), .iop_dmac_wr_addr(map_dmac_wr_addr),
        .iop_dmac_wr_data(map_dmac_wr_data),
        .iop_intc_rd_en(map_intc_rd_en), .iop_intc_rd_addr(map_intc_rd_addr),
        .iop_intc_rd_data(map_intc_rd_data), .iop_intc_rd_valid(map_intc_rd_valid),
        .iop_intc_wr_en(map_intc_wr_en), .iop_intc_wr_addr(map_intc_wr_addr),
        .iop_intc_wr_data(map_intc_wr_data),
        .input_p1(32'd0), .input_p2(32'd0),
        // BIOS ROM port — unused by this TB
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        .ram_rd_en(iop_ram_rd_en), .ram_rd_addr(iop_ram_rd_addr),
        .ram_rd_data(iop_ram_rd_data), .ram_rd_valid(iop_ram_rd_valid),
        .ram_wr_en(iop_ram_wr_en), .ram_wr_addr(iop_ram_wr_addr),
        .ram_wr_data(iop_ram_wr_data), .ram_wr_be(iop_ram_wr_be),
        .ram_master_id(iop_ram_master_id),
        .ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys),
        .ev_event(iop_map_ev_event),
        .ev_arg0(iop_map_ev_arg0), .ev_arg1(iop_map_ev_arg1),
        .ev_arg2(iop_map_ev_arg2), .ev_arg3(iop_map_ev_arg3),
        .ev_flags(iop_map_ev_flags)
    );

    iop_ram_stub #(.SIZE_BYTES(IOP_RAM_BYTES)) u_iop_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(iop_ram_rd_en), .rd_addr(iop_ram_rd_addr[IOP_RAM_ADDR_W-1:0]),
        .rd_data(iop_ram_rd_data), .rd_valid(iop_ram_rd_valid),
        .wr_en(iop_ram_wr_en), .wr_addr(iop_ram_wr_addr[IOP_RAM_ADDR_W-1:0]),
        .wr_data(iop_ram_wr_data), .wr_be(iop_ram_wr_be),
        .master_id(iop_ram_master_id),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    // ------------------------------------------------------------------
    // IOP DMAC ch9 — payload-on-the-floor endpoint
    // ------------------------------------------------------------------

    logic        ep_valid;
    logic [31:0] ep_data;
    logic        ep_last;
    logic        ep_ready;
    assign ep_ready = 1'b1;

    logic        dmac_irq_completion;
    logic        dmac_busy;
    logic [31:0] dmac_done_count;

    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;

    iop_dmac_reg_stub u_dmac (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(map_dmac_wr_en), .reg_rd_en(map_dmac_rd_en),
        .reg_offset(map_dmac_wr_en ? map_dmac_wr_addr : map_dmac_rd_addr),
        .reg_wr_data(map_dmac_wr_data),
        .reg_rd_data(map_dmac_rd_data), .reg_rd_valid(map_dmac_rd_valid),
        .mem_rd_en(dma_rd_en), .mem_rd_addr(dma_rd_addr),
        .mem_master_id(dma_master_id),
        .mem_rd_data(dma_rd_data), .mem_rd_valid(dma_rd_valid),
        .ep_valid(ep_valid), .ep_data(ep_data), .ep_last(ep_last),
        .ep_ready(ep_ready),
        .irq_completion_o(dmac_irq_completion),
        .busy_o(dmac_busy), .done_count_o(dmac_done_count),
        .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)
    );

    // ------------------------------------------------------------------
    // IOP INTC — intc_stub reused; bit 0 wired to DMAC ch9 completion
    // ------------------------------------------------------------------

    logic [15:0] iop_irq_src;
    assign iop_irq_src = {15'd0, dmac_irq_completion};
    logic        iop_cpu_irq;

    logic                intc_ev_valid;
    trace_pkg::subsys_e  intc_ev_subsys;
    trace_pkg::event_e   intc_ev_event;
    logic [63:0]         intc_ev_arg0, intc_ev_arg1, intc_ev_arg2, intc_ev_arg3;
    logic [31:0]         intc_ev_flags;

    intc_stub #(
        .INTC_STAT_OFFSET(INTC_STAT_OFFSET),
        .INTC_MASK_OFFSET(INTC_MASK_OFFSET)
    ) u_intc (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(map_intc_wr_en), .reg_rd_en(map_intc_rd_en),
        .reg_addr(map_intc_wr_en ? map_intc_wr_addr : map_intc_rd_addr),
        .reg_wr_data(map_intc_wr_data),
        .reg_rd_data(map_intc_rd_data), .reg_rd_valid(map_intc_rd_valid),
        .irq_src(iop_irq_src), .cpu_irq(iop_cpu_irq),
        .ev_valid(intc_ev_valid), .ev_subsys(intc_ev_subsys),
        .ev_event(intc_ev_event),
        .ev_arg0(intc_ev_arg0), .ev_arg1(intc_ev_arg1),
        .ev_arg2(intc_ev_arg2), .ev_arg3(intc_ev_arg3),
        .ev_flags(intc_ev_flags)
    );

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

    trace_sink_stub #(.FILENAME("iop_intc_iop_map.trace"), .SINK_LABEL("iop_map"))
        u_trace_iop_map (.clk(clk), .rst_n(rst_n),
            .ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys),
            .ev_event(iop_map_ev_event), .ev_arg0(iop_map_ev_arg0),
            .ev_arg1(iop_map_ev_arg1), .ev_arg2(iop_map_ev_arg2),
            .ev_arg3(iop_map_ev_arg3), .ev_flags(iop_map_ev_flags));

    trace_sink_stub #(.FILENAME("iop_intc_dmac.trace"), .SINK_LABEL("iop_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("iop_intc_intc.trace"), .SINK_LABEL("iop_intc"))
        u_trace_intc (.clk(clk), .rst_n(rst_n),
            .ev_valid(intc_ev_valid), .ev_subsys(intc_ev_subsys),
            .ev_event(intc_ev_event), .ev_arg0(intc_ev_arg0),
            .ev_arg1(intc_ev_arg1), .ev_arg2(intc_ev_arg2),
            .ev_arg3(intc_ev_arg3), .ev_flags(intc_ev_flags));

    // ------------------------------------------------------------------
    // Counters
    // ------------------------------------------------------------------

    int dmac_done_events;
    int intc_assert_events;
    int intc_ack_events;
    int map_intc_events;
    int errors;

    initial begin
        dmac_done_events   = 0;
        intc_assert_events = 0;
        intc_ack_events    = 0;
        map_intc_events    = 0;
        errors             = 0;
    end

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

        if (rst_n && intc_ev_valid &&
            intc_ev_subsys == trace_pkg::SUBSYS_INTC &&
            intc_ev_event  == trace_pkg::EV_IRQ) begin
            if (intc_ev_arg3 == 64'd0 && (intc_ev_flags & 32'h1) == 32'd0)
                intc_assert_events <= intc_assert_events + 1;
            else if (intc_ev_arg3 == 64'd1)
                intc_ack_events <= intc_ack_events + 1;
        end

        if (rst_n && iop_map_ev_valid &&
            iop_map_ev_subsys == trace_pkg::SUBSYS_IOP &&
            iop_map_ev_arg3[7:0] == 8'd5)               // REGION_IOP_INTC
            map_intc_events <= map_intc_events + 1;
    end

    // ------------------------------------------------------------------
    // Helpers — all INTC access flows through the real IOP map
    // ------------------------------------------------------------------

    task automatic iop_write(input logic [31:0] addr, input logic [31:0] data);
        @(negedge clk);
        iop_wr_en   = 1'b1;
        iop_wr_addr = addr;
        iop_wr_data = data;
        iop_wr_be   = 4'b1111;
        @(negedge clk);
        iop_wr_en   = 1'b0;
        iop_wr_addr = 32'd0;
        iop_wr_data = 32'd0;
        iop_wr_be   = 4'd0;
    endtask

    task automatic iop_read(input logic [31:0] addr, output logic [31:0] data);
        @(negedge clk);
        iop_rd_en   = 1'b1;
        iop_rd_addr = addr;
        @(negedge clk);
        iop_rd_en   = 1'b0;
        iop_rd_addr = 32'd0;
        data = iop_rd_data;
    endtask

    task automatic run_ch9_dma_and_wait;
        iop_write(DMAC_MADR_ADDR, SRC_BASE);
        iop_write(DMAC_BCR_ADDR,  32'(BEATS));
        iop_write(DMAC_CHCR_ADDR, 32'h0000_0001);
        repeat (10 * BEATS) @(posedge clk);
    endtask

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

    logic [31:0] stat_val;
    logic [31:0] payload [0:BEATS-1];

    initial begin
        rst_n       = 1'b0;
        iop_rd_en   = 1'b0;
        iop_rd_addr = 32'd0;
        iop_wr_en   = 1'b0;
        iop_wr_addr = 32'd0;
        iop_wr_data = 32'd0;
        iop_wr_be   = 4'd0;

        for (int i = 0; i < BEATS; i++) payload[i] = 32'hBEEF_0000 | i[31:0];

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

        // Preload IOP RAM
        for (int i = 0; i < BEATS; i++) begin
            iop_write(SRC_BASE + (i << 2), payload[i]);
        end

        // --------------------------------------------------------------
        // Scenario 1: pre-transfer quiescence
        // --------------------------------------------------------------
        iop_read(INTC_STAT_ADDR, stat_val);
        if (stat_val !== 32'd0) begin
            $error("[tb_iop_dmac_intc] pre-transfer INTC_STAT nonzero: 0x%08h", stat_val);
            errors = errors + 1;
        end
        if (iop_cpu_irq !== 1'b0) begin
            $error("[tb_iop_dmac_intc] pre-transfer iop_cpu_irq asserted");
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Scenario 2: first transfer latches INTC_STAT[0]
        // --------------------------------------------------------------
        run_ch9_dma_and_wait();

        if (dmac_done_events != 1) begin
            $error("[tb_iop_dmac_intc] expected 1 DMA_DONE after first run, got %0d",
                   dmac_done_events);
            errors = errors + 1;
        end
        iop_read(INTC_STAT_ADDR, stat_val);
        if ((stat_val & 32'h1) == 32'd0) begin
            $error("[tb_iop_dmac_intc] INTC_STAT bit 0 not set post-DMA: 0x%08h", stat_val);
            errors = errors + 1;
        end
        if (iop_cpu_irq !== 1'b0) begin
            $error("[tb_iop_dmac_intc] iop_cpu_irq asserted with mask=0");
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Scenario 3: mask on via map — iop_cpu_irq rises
        // --------------------------------------------------------------
        iop_write(INTC_MASK_ADDR, 32'h0000_0001);
        @(posedge clk);
        if (iop_cpu_irq !== 1'b1) begin
            $error("[tb_iop_dmac_intc] iop_cpu_irq not asserted after unmasking");
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Scenario 4: ack via map (W1C)
        // --------------------------------------------------------------
        iop_write(INTC_STAT_ADDR, 32'h0000_0001);
        @(posedge clk);
        iop_read(INTC_STAT_ADDR, stat_val);
        if (stat_val !== 32'd0) begin
            $error("[tb_iop_dmac_intc] INTC_STAT not cleared after ack: 0x%08h", stat_val);
            errors = errors + 1;
        end
        if (iop_cpu_irq !== 1'b0) begin
            $error("[tb_iop_dmac_intc] iop_cpu_irq still high after ack");
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Scenario 5: quiescent hold
        // --------------------------------------------------------------
        repeat (30) @(posedge clk);
        iop_read(INTC_STAT_ADDR, stat_val);
        if (stat_val !== 32'd0) begin
            $error("[tb_iop_dmac_intc] INTC_STAT reasserted without new DMA: 0x%08h", stat_val);
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Scenario 6: second transfer re-latches
        // --------------------------------------------------------------
        run_ch9_dma_and_wait();

        if (dmac_done_events != 2) begin
            $error("[tb_iop_dmac_intc] expected 2 DMA_DONE after second run, got %0d",
                   dmac_done_events);
            errors = errors + 1;
        end
        iop_read(INTC_STAT_ADDR, stat_val);
        if ((stat_val & 32'h1) == 32'd0) begin
            $error("[tb_iop_dmac_intc] second DMA did not re-latch STAT[0]: 0x%08h",
                   stat_val);
            errors = errors + 1;
        end
        if (iop_cpu_irq !== 1'b1) begin
            $error("[tb_iop_dmac_intc] iop_cpu_irq not high (mask=1, stat=1)");
            errors = errors + 1;
        end

        iop_write(INTC_STAT_ADDR, 32'h0000_0001);
        @(posedge clk);
        iop_read(INTC_STAT_ADDR, stat_val);
        if (stat_val !== 32'd0) begin
            $error("[tb_iop_dmac_intc] second ack didn't clear STAT");
            errors = errors + 1;
        end

        repeat (4) @(posedge clk);

        $display("[tb_iop_dmac_intc] done=%0d asserts=%0d acks=%0d map_intc_events=%0d errors=%0d",
                 dmac_done_events, intc_assert_events, intc_ack_events,
                 map_intc_events, errors);

        if (dmac_done_events   != 2) $error("expected 2 DMA_DONE, got %0d", dmac_done_events);
        if (intc_assert_events != 2) $error("expected 2 INTC asserts, got %0d", intc_assert_events);
        if (intc_ack_events    != 2) $error("expected 2 INTC acks, got %0d", intc_ack_events);
        // Map should have seen at least: 4 reads of STAT + 1 mask write + 2 ack writes = 7
        if (map_intc_events < 7)
            $error("expected >=7 map events tagged IOP_INTC, got %0d", map_intc_events);

        if (errors == 0 &&
            dmac_done_events   == 2 &&
            intc_assert_events == 2 &&
            intc_ack_events    == 2 &&
            map_intc_events    >= 7)
            $display("[tb_iop_dmac_intc] PASS");
        else
            $display("[tb_iop_dmac_intc] FAIL");

        $finish;
    end

    initial begin
        #500000;
        $error("[tb_iop_dmac_intc] timeout");
        $finish;
    end

endmodule : tb_iop_dmac_intc
