// retroDE_ps2 — intc_stub
//
// Generic PS2-style interrupt controller shell. Register-visible
// status/mask behaviour plus a 16-source injection port; the same
// module is reusable as either the EE-side or IOP-side INTC by picking
// the appropriate address offsets and instantiating with different
// sources. The aggregate output `cpu_irq` is side-neutral.
//
// Contract refs:
//   docs/stub_module_plan.md    (Wave 1, item 7)
//   docs/contracts/intc.md
//
// Register layout (Wave 1):
//   offset 0x000: INTC_STAT    read: current pending, write: W1C
//   offset 0x010: INTC_MASK    read: current mask,    write: plain set
//
//   Real PS2 INTC_MASK uses write-to-toggle (XOR) semantics. Wave 1 uses
//   plain write semantics for stub simplicity; toggle semantics are a
//   Wave 2+ concern if BIOS traces demand them.
//
// Injection:
//   irq_src[i] high on any cycle latches bit i in INTC_STAT. Sticky until
//   cleared by a W1C write. Sixteen sources are exposed (matches real PS2
//   INTC source count); testbenches drive whichever they need.
//
// Trace payload schema (per stub plan):
//   INTC IRQ  arg0=source_bitmap arg1=masked arg2=pending arg3=ack
//     one event per cycle max. Priority if multiple triggers coincide:
//       ack (STAT W1C) > new assertion > mask write.
//     ack arg3=1 when the event is a W1C ack, 0 otherwise.
//     flags bit 0 = register write (vs. source-driven assertion)

`timescale 1ns/1ps

module intc_stub
    import trace_pkg::*;
#(
    parameter logic [7:0] INTC_STAT_OFFSET = 8'h00,
    parameter logic [7:0] INTC_MASK_OFFSET = 8'h10
) (
    input  logic          clk,
    input  logic          rst_n,

    // Register port
    input  logic          reg_wr_en,
    input  logic          reg_rd_en,
    input  logic [7:0]    reg_addr,
    input  logic [31:0]   reg_wr_data,
    output logic [31:0]   reg_rd_data,
    output logic          reg_rd_valid,

    // Synthetic interrupt sources
    input  logic [15:0]   irq_src,

    // Aggregate interrupt line to whichever CPU side this INTC serves
    // (EE or IOP). Named generically because this module is reused on
    // both sides.
    output logic          cpu_irq,

    // Trace
    output logic          ev_valid,
    output subsys_e       ev_subsys,
    output event_e        ev_event,
    output logic [63:0]   ev_arg0,
    output logic [63:0]   ev_arg1,
    output logic [63:0]   ev_arg2,
    output logic [63:0]   ev_arg3,
    output logic [31:0]   ev_flags
);

    logic [15:0] intc_stat;
    logic [15:0] intc_mask;

    // ------------------------------------------------------------------
    // Register reads (1-cycle latency, matches bios_rom_stub pattern)
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            reg_rd_data  <= 32'd0;
            reg_rd_valid <= 1'b0;
        end else begin
            reg_rd_valid <= reg_rd_en;
            if (reg_rd_en) begin
                case (reg_addr)
                    INTC_STAT_OFFSET: reg_rd_data <= {16'd0, intc_stat};
                    INTC_MASK_OFFSET: reg_rd_data <= {16'd0, intc_mask};
                    default:          reg_rd_data <= 32'd0;
                endcase
            end
        end
    end

    // ------------------------------------------------------------------
    // Pending/mask update + synthetic injection
    //   - W1C on INTC_STAT clears bits where write_data has 1.
    //   - Plain write on INTC_MASK replaces current mask.
    //   - irq_src sets bits in INTC_STAT (sticky).
    //   - If W1C and irq_src collide on the same cycle and same bit, the
    //     assertion wins — we don't want to swallow an interrupt.
    // ------------------------------------------------------------------

    logic [15:0] stat_w1c_mask;
    logic [15:0] stat_inject;
    logic        mask_wr;

    assign stat_w1c_mask = (reg_wr_en && (reg_addr == INTC_STAT_OFFSET))
                           ? reg_wr_data[15:0] : 16'd0;
    assign stat_inject   = irq_src;
    assign mask_wr       = reg_wr_en && (reg_addr == INTC_MASK_OFFSET);

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            intc_stat <= 16'd0;
            intc_mask <= 16'd0;
        end else begin
            intc_stat <= (intc_stat & ~stat_w1c_mask) | stat_inject;
            if (mask_wr) intc_mask <= reg_wr_data[15:0];
        end
    end

    assign cpu_irq = |(intc_stat & intc_mask);

    // ------------------------------------------------------------------
    // Trace
    // ------------------------------------------------------------------

    logic [15:0] new_assertions;
    logic [15:0] bits_acked;
    logic        had_ack;
    logic        had_assertion;
    logic        had_mask_wr;

    // "new_assertions" = bits becoming pending this cycle that weren't pending
    // before. Combinational on the pre-edge state.
    assign new_assertions = stat_inject & ~intc_stat;
    assign bits_acked     = stat_w1c_mask & intc_stat;
    assign had_ack        = |bits_acked;
    assign had_assertion  = |new_assertions;
    assign had_mask_wr    = mask_wr;

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ev_valid  <= 1'b0;
            ev_subsys <= SUBSYS_INTC;
            ev_event  <= EV_IRQ;
            ev_arg0   <= 64'd0;
            ev_arg1   <= 64'd0;
            ev_arg2   <= 64'd0;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'd0;
        end else if (had_ack) begin
            // arg1/arg2 must reflect the post-update state. The state
            // update preserves simultaneous stat_inject over W1C clears
            // (see always_ff above), so if inject and ack collide on the
            // same bit, that bit stays pending. arg0 still reports what
            // software tried to ack, regardless of whether it took effect.
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_INTC;
            ev_event  <= EV_IRQ;
            ev_arg0   <= {48'd0, bits_acked};
            ev_arg1   <= {48'd0, ((intc_stat & ~stat_w1c_mask) | stat_inject) & intc_mask};
            ev_arg2   <= {48'd0, (intc_stat & ~stat_w1c_mask) | stat_inject};
            ev_arg3   <= 64'd1;       // ack = 1
            ev_flags  <= 32'h0000_0001;
        end else if (had_assertion) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_INTC;
            ev_event  <= EV_IRQ;
            ev_arg0   <= {48'd0, new_assertions};
            ev_arg1   <= {48'd0, (intc_stat | stat_inject) & intc_mask};
            ev_arg2   <= {48'd0, (intc_stat | stat_inject)};
            ev_arg3   <= 64'd0;       // ack = 0
            ev_flags  <= 32'd0;
        end else if (had_mask_wr) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_INTC;
            ev_event  <= EV_IRQ;
            ev_arg0   <= 64'd0;
            ev_arg1   <= {48'd0, intc_stat & reg_wr_data[15:0]};
            ev_arg2   <= {48'd0, intc_stat};
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'h0000_0001;
        end else begin
            ev_valid <= 1'b0;
        end
    end

endmodule : intc_stub
