// retroDE_ps2 — tb_ee_dmac_intc
//
// First completion-visible EE-side interrupt flow. Wires an EE DMAC
// completion pulse into intc_stub as a real interrupt source, and
// proves the full mask / pending / ack / re-assertion semantics by
// driving two back-to-back transfers.
//
// Chain:
//   ee_memory_map_stub (DMAC read master, RAM region)
//     └─► ee_ram_stub (payload source, preloaded by TB)
//
//   dmac_reg_stub (ch2) — minimal QWC=1 transfer
//     ep_* drained by a TB-level always-ready sink (no GIF in scope)
//     irq_completion_o (1-cycle pulse on S_DONE)
//                 │
//                 ▼
//       intc_stub.irq_src[0]   (sticky latch into INTC_STAT[0])
//
// TB drives INTC register port directly — the EE map does not route
// INTC addresses in current scope, per the deferred-follow-on list in
// rtl/sif/README.md.
//
// Scenarios:
//   1. Pre-DMA quiescence:    INTC_STAT = 0, cpu_irq = 0.
//   2. First transfer:        DMA runs, DMA_DONE pulses irq_src[0],
//                             INTC_STAT[0] latches to 1. cpu_irq still
//                             0 because INTC_MASK[0] is 0.
//   3. Mask on:               Write INTC_MASK = 0x1. cpu_irq becomes 1.
//   4. Ack:                   Write INTC_STAT = 0x1 (W1C). INTC_STAT
//                             reads 0, cpu_irq falls to 0.
//   5. Quiescent hold:        Wait N cycles. INTC_STAT stays 0 — no
//                             false re-assertion without a new transfer.
//   6. Second transfer:       DMA runs again. INTC_STAT[0] re-latches.
//                             Ack clears; quiescent again.
//
// Success = every assertion above holds and the trace files are
// consistent (one IRQ event on each assertion, one on each ack).

`timescale 1ns/1ps

module tb_ee_dmac_intc;

    localparam int EE_RAM_BYTES  = 4 * 1024;
    localparam int EE_RAM_ADDR_W = $clog2(EE_RAM_BYTES);

    localparam logic [31:0] SRC_BASE = 32'h0000_0100;   // qword-aligned

    localparam logic [7:0] DMAC_CHCR_OFF = 8'h00;
    localparam logic [7:0] DMAC_MADR_OFF = 8'h10;
    localparam logic [7:0] DMAC_QWC_OFF  = 8'h20;

    localparam logic [7:0] INTC_STAT_OFF = 8'h00;
    localparam logic [7:0] INTC_MASK_OFF = 8'h10;

    logic clk;
    logic rst_n;

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

    // ------------------------------------------------------------------
    // EE map + EE RAM (TB also preloads RAM through the map's fetch
    // write path — but the existing ee_memory_map_stub has no bridge
    // for the EE side separate from the new 128-bit bridge port. For
    // this TB we preload the EE RAM through the new bridge_wr_* port.)
    // ------------------------------------------------------------------

    logic        dmac_mem_rd_en;
    logic [31:0] dmac_mem_rd_addr;
    logic [127:0] dmac_mem_rd_data;
    logic        dmac_mem_rd_valid;

    logic        ee_map_ram_rd_en;
    logic [24:0] ee_map_ram_rd_addr;
    logic [127:0] ee_ram_rd_data;
    logic        ee_ram_rd_valid;

    logic        ee_map_ram_wr_en;
    logic [24:0] ee_map_ram_wr_addr;
    logic [127:0] ee_map_ram_wr_data;
    logic [15:0] ee_map_ram_wr_be;
    logic [7:0]  ee_map_ram_master_id;

    // TB drives the bridge_wr_* port directly to preload RAM
    logic         tb_preload_wr_en;
    logic [31:0]  tb_preload_wr_addr;
    logic [127:0] tb_preload_wr_data;
    logic [15:0]  tb_preload_wr_be;

    ee_memory_map_stub u_ee_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(dmac_mem_rd_data), .dmac_rd_valid(dmac_mem_rd_valid),
        .bios_rd_en(), .bios_rd_addr(),
        .bios_rd_data(32'd0), .bios_rd_valid(1'b0),
        .ram_rd_en(ee_map_ram_rd_en), .ram_rd_addr(ee_map_ram_rd_addr),
        .ram_rd_data(ee_ram_rd_data), .ram_rd_valid(ee_ram_rd_valid),
        .bridge_wr_en(tb_preload_wr_en), .bridge_wr_addr(tb_preload_wr_addr),
        .bridge_wr_data(tb_preload_wr_data), .bridge_wr_be(tb_preload_wr_be),
        .bridge_master_id(8'd0),                            // TB direct
        .ram_wr_en(ee_map_ram_wr_en), .ram_wr_addr(ee_map_ram_wr_addr),
        .ram_wr_data(ee_map_ram_wr_data), .ram_wr_be(ee_map_ram_wr_be),
        .ram_master_id(ee_map_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(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    ee_ram_stub #(.SIZE_BYTES(EE_RAM_BYTES)) u_ee_ram (
        .clk(clk), .rst_n(rst_n),
        .rd_en(ee_map_ram_rd_en), .rd_addr(ee_map_ram_rd_addr[EE_RAM_ADDR_W-1:0]),
        .rd_data(ee_ram_rd_data), .rd_valid(ee_ram_rd_valid),
        .wr_en(ee_map_ram_wr_en), .wr_addr(ee_map_ram_wr_addr[EE_RAM_ADDR_W-1:0]),
        .wr_data(ee_map_ram_wr_data), .wr_be(ee_map_ram_wr_be),
        .master_id(ee_map_ram_master_id),
        .ev_valid(), .ev_subsys(), .ev_event(),
        .ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
    );

    // ------------------------------------------------------------------
    // DMAC (ch2). Endpoint drained by TB-level always-ready sink.
    // ------------------------------------------------------------------

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

    logic         dmac_ep_valid;
    logic [127:0] dmac_ep_data;
    logic         dmac_ep_last;
    logic         dmac_ep_ready;
    assign        dmac_ep_ready = 1'b1;               // immediately drain

    logic        dmac_irq_completion;

    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 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(dmac_mem_rd_data), .mem_rd_valid(dmac_mem_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(dmac_irq_completion),
        .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)
    );

    // ------------------------------------------------------------------
    // INTC — bit 0 wired to DMAC completion, rest tied low
    // ------------------------------------------------------------------

    logic        intc_reg_wr_en;
    logic        intc_reg_rd_en;
    logic [7:0]  intc_reg_addr;
    logic [31:0] intc_reg_wr_data;
    logic [31:0] intc_reg_rd_data;
    logic        intc_reg_rd_valid;

    logic [15:0] intc_irq_src;
    assign intc_irq_src = {15'd0, dmac_irq_completion};
    logic        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 u_intc (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(intc_reg_wr_en), .reg_rd_en(intc_reg_rd_en),
        .reg_addr(intc_reg_addr),
        .reg_wr_data(intc_reg_wr_data),
        .reg_rd_data(intc_reg_rd_data),
        .reg_rd_valid(intc_reg_rd_valid),
        .irq_src(intc_irq_src), .cpu_irq(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("ee_dmac_intc_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("ee_dmac_intc_intc.trace"), .SINK_LABEL("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 errors;

    initial begin
        dmac_done_events   = 0;
        intc_assert_events = 0;
        intc_ack_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
            // intc_stub labels: arg3=1 is ack (W1C); arg3=0 can be either
            // a new source assertion OR a mask-register write. flags bit 0
            // distinguishes writes (mask wr) from source-driven assertions.
            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
    end

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

    task automatic preload_qword(input logic [31:0] addr, input logic [127:0] data);
        @(negedge clk);
        tb_preload_wr_en   = 1'b1;
        tb_preload_wr_addr = addr;
        tb_preload_wr_data = data;
        tb_preload_wr_be   = 16'hFFFF;
        @(negedge clk);
        tb_preload_wr_en   = 1'b0;
        tb_preload_wr_addr = 32'd0;
        tb_preload_wr_data = 128'd0;
        tb_preload_wr_be   = 16'd0;
    endtask

    task automatic dmac_write(input logic [7:0] off, input logic [31:0] data);
        @(negedge clk);
        dmac_reg_wr_en   = 1'b1;
        dmac_reg_offset  = off;
        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 intc_write(input logic [7:0] addr, input logic [31:0] data);
        @(negedge clk);
        intc_reg_wr_en   = 1'b1;
        intc_reg_addr    = addr;
        intc_reg_wr_data = data;
        @(negedge clk);
        intc_reg_wr_en   = 1'b0;
        intc_reg_addr    = 8'd0;
        intc_reg_wr_data = 32'd0;
    endtask

    task automatic intc_read(input logic [7:0] addr, output logic [31:0] data);
        @(negedge clk);
        intc_reg_rd_en = 1'b1;
        intc_reg_addr  = addr;
        @(negedge clk);
        intc_reg_rd_en = 1'b0;
        intc_reg_addr  = 8'd0;
        @(posedge clk);
        data = intc_reg_rd_data;
    endtask

    task automatic run_dma_and_wait;
        dmac_write(DMAC_MADR_OFF, SRC_BASE);
        dmac_write(DMAC_QWC_OFF,  32'h0000_0001);
        dmac_write(DMAC_CHCR_OFF, 32'h0000_0001);
        repeat (20) @(posedge clk);                   // ample time for QWC=1
    endtask

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

    logic [31:0] stat_val;

    initial begin
        rst_n              = 1'b0;
        tb_preload_wr_en   = 1'b0;
        tb_preload_wr_addr = 32'd0;
        tb_preload_wr_data = 128'd0;
        tb_preload_wr_be   = 16'd0;
        dmac_reg_wr_en     = 1'b0;
        dmac_reg_offset    = 8'd0;
        dmac_reg_wr_data   = 32'd0;
        intc_reg_wr_en     = 1'b0;
        intc_reg_rd_en     = 1'b0;
        intc_reg_addr      = 8'd0;
        intc_reg_wr_data   = 32'd0;

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

        // Preload a distinctive qword into EE RAM for the DMAC to fetch.
        preload_qword(SRC_BASE, 128'hCAFE_BABE_DEAD_BEEF_1234_5678_AABB_CCDD);

        // --------------------------------------------------------------
        // Scenario 1: pre-DMA quiescence
        // --------------------------------------------------------------
        intc_read(INTC_STAT_OFF, stat_val);
        if (stat_val !== 32'd0) begin
            $error("[tb_ee_dmac_intc] pre-DMA INTC_STAT nonzero: 0x%08h", stat_val);
            errors = errors + 1;
        end
        if (cpu_irq !== 1'b0) begin
            $error("[tb_ee_dmac_intc] pre-DMA cpu_irq asserted");
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Scenario 2: first DMA — completion latches INTC_STAT[0]
        // --------------------------------------------------------------
        run_dma_and_wait();

        if (dmac_done_events != 1) begin
            $error("[tb_ee_dmac_intc] expected 1 DMA_DONE after first run, got %0d",
                   dmac_done_events);
            errors = errors + 1;
        end
        intc_read(INTC_STAT_OFF, stat_val);
        if ((stat_val & 32'h1) == 32'd0) begin
            $error("[tb_ee_dmac_intc] INTC_STAT bit 0 not set after DMA: 0x%08h", stat_val);
            errors = errors + 1;
        end
        // Mask is still 0 → cpu_irq should be 0
        if (cpu_irq !== 1'b0) begin
            $error("[tb_ee_dmac_intc] cpu_irq asserted with mask=0");
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Scenario 3: mask on — cpu_irq rises
        // --------------------------------------------------------------
        intc_write(INTC_MASK_OFF, 32'h0000_0001);
        @(posedge clk);
        if (cpu_irq !== 1'b1) begin
            $error("[tb_ee_dmac_intc] cpu_irq not asserted after unmasking");
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Scenario 4: ack — W1C clears STAT, cpu_irq falls
        // --------------------------------------------------------------
        intc_write(INTC_STAT_OFF, 32'h0000_0001);
        @(posedge clk);
        intc_read(INTC_STAT_OFF, stat_val);
        if (stat_val !== 32'd0) begin
            $error("[tb_ee_dmac_intc] INTC_STAT not cleared after ack: 0x%08h", stat_val);
            errors = errors + 1;
        end
        if (cpu_irq !== 1'b0) begin
            $error("[tb_ee_dmac_intc] cpu_irq still high after ack");
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Scenario 5: quiescent hold — no false re-assertion
        // --------------------------------------------------------------
        repeat (30) @(posedge clk);
        intc_read(INTC_STAT_OFF, stat_val);
        if (stat_val !== 32'd0) begin
            $error("[tb_ee_dmac_intc] INTC_STAT reasserted without new DMA: 0x%08h", stat_val);
            errors = errors + 1;
        end

        // --------------------------------------------------------------
        // Scenario 6: second DMA re-latches; ack clears again
        // --------------------------------------------------------------
        run_dma_and_wait();

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

        intc_write(INTC_STAT_OFF, 32'h0000_0001);
        @(posedge clk);
        intc_read(INTC_STAT_OFF, stat_val);
        if (stat_val !== 32'd0) begin
            $error("[tb_ee_dmac_intc] second ack didn't clear INTC_STAT");
            errors = errors + 1;
        end

        repeat (4) @(posedge clk);

        $display("[tb_ee_dmac_intc] done=%0d asserts=%0d acks=%0d errors=%0d",
                 dmac_done_events, intc_assert_events, intc_ack_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 assertions, got %0d", intc_assert_events);
        if (intc_ack_events    != 2) $error("expected 2 INTC acks, got %0d", intc_ack_events);

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

        $finish;
    end

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

endmodule : tb_ee_dmac_intc
