// retroDE_ps2 — boot_install_agent_stub (Ch55 / Ch56)
//
// Minimal external producer that streams a coordinated low-RAM handler
// image into EE RAM through the SIF EE-RAM bridge. Emits 32-bit beats
// on a ready/valid handshake compatible with sif_dma_ee_ram_bridge_stub.
//
// NOT an IOP, NOT a full boot firmware. This is the thinnest possible
// stand-in for "whatever on real PS2 populates EE useg [0x80..0x1FF]
// with exception-entry + safe-return stubs before the EE starts
// faulting" (IOP→EE SIF DMA, BootROM/CDVD handoff, etc.). The point
// is to validate the transport path and the coordinated-install
// thesis, not to model the producer's identity.
//
// Payload source (Ch56):
//   USE_IMAGE_FILE=0 (default) — built-in Ch54 image, hardcoded below
//   USE_IMAGE_FILE=1           — $readmemh(IMAGE_FILE, payload) once
//                                at sim start, expects TOTAL_WORDS
//                                hex words
// Transport (timing, handshake, trace) is identical across both modes.
//
// Built-in image (USE_IMAGE_FILE=0):
//   word[0..3]   → AdES handler at useg 0x80..0x8C:
//                    MFC0  $26, $14          (32'h401A7000)
//                    ADDIU $26, $26, 4       (32'h275A0004)
//                    JR    $26               (32'h03400008)
//                    RFE                     (32'h42000010)
//   word[4..95]  → 46× (JR $31; NOP) safe-return pairs covering
//                  useg 0x90..0x1FC.
//
// Downstream contract (matches sif_dma_ee_ram_bridge_stub upstream):
//   out_valid / out_data[31:0] / out_last / out_ready
//   out_last asserted on the final word. One-beat-per-cycle while
//   out_ready is high.
//
// Trace:
//   SUBSYS_SIF / EV_DMA_START once on go.
//   SUBSYS_SIF / EV_DMA_BEAT per accepted beat.
//     arg0 = word index, arg1 = word data, arg2 = MASTER_ID,
//     arg3 = TOTAL_WORDS, flags bit0 = out_last.
//   SUBSYS_SIF / EV_DMA_DONE once on completion.

`timescale 1ns/1ps

module boot_install_agent_stub
    import trace_pkg::*;
#(
    parameter int          TOTAL_WORDS     = 96,
    parameter logic [7:0]  MASTER_ID       = 8'd6, // install agent
    parameter bit          USE_IMAGE_FILE  = 1'b0, // 0: built-in ROM, 1: $readmemh
    parameter string       IMAGE_FILE      = ""
) (
    input  logic          clk,
    input  logic          rst_n,

    input  logic          go_i,

    output logic          out_valid,
    output logic [31:0]   out_data,
    output logic          out_last,
    input  logic          out_ready,

    output logic          busy_o,
    output logic          done_o,

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

    // ------------------------------------------------------------------
    // Payload ROM
    // ------------------------------------------------------------------
    // The 4-word exception-return stub at [0..3] plus (JR $31; NOP)
    // pairs filling the rest requires TOTAL_WORDS to be even and at
    // least 4. Guard explicitly (Ch55 audit low-1): odd values would
    // walk past the array end in the pair loop.
    initial begin
        if (TOTAL_WORDS < 4 || (TOTAL_WORDS & 1) != 0) begin
            $fatal(1, "boot_install_agent_stub: TOTAL_WORDS must be even and >= 4, got %0d",
                   TOTAL_WORDS);
        end
    end

    logic [31:0] payload [0:TOTAL_WORDS-1];

    initial begin
        if (USE_IMAGE_FILE) begin
            if (IMAGE_FILE == "") begin
                $fatal(1, "boot_install_agent_stub: USE_IMAGE_FILE=1 but IMAGE_FILE is empty");
            end
            $readmemh(IMAGE_FILE, payload);
        end else begin
            payload[0] = 32'h401A7000;             // MFC0  $26, $14
            payload[1] = 32'h275A0004;             // ADDIU $26, $26, 4
            payload[2] = 32'h03400008;             // JR    $26
            payload[3] = 32'h42000010;             // RFE (delay slot)
            for (int i = 4; i < TOTAL_WORDS; i = i + 2) begin
                payload[i]     = 32'h03E00008;     // JR $31
                payload[i + 1] = 32'h00000000;     // NOP
            end
        end
    end

    // ------------------------------------------------------------------
    // Streaming FSM
    // ------------------------------------------------------------------
    typedef enum logic [1:0] {
        S_IDLE   = 2'd0,
        S_STREAM = 2'd1,
        S_DONE   = 2'd2
    } state_e;

    state_e      state;
    logic [31:0] idx;                              // next word to emit

    logic accept_beat;
    assign accept_beat = out_valid && out_ready;

    assign out_valid = (state == S_STREAM);
    assign out_data  = (state == S_STREAM) ? payload[idx[$clog2(TOTAL_WORDS)-1:0]]
                                           : 32'd0;
    assign out_last  = (state == S_STREAM) && (idx == TOTAL_WORDS - 1);
    assign busy_o    = (state == S_STREAM);
    assign done_o    = (state == S_DONE);

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            state <= S_IDLE;
            idx   <= 32'd0;
        end else begin
            unique case (state)
                S_IDLE: begin
                    if (go_i) begin
                        state <= S_STREAM;
                        idx   <= 32'd0;
                    end
                end
                S_STREAM: begin
                    if (accept_beat) begin
                        if (idx == TOTAL_WORDS - 1) state <= S_DONE;
                        else                        idx   <= idx + 32'd1;
                    end
                end
                S_DONE: ;                          // terminal
                default: state <= S_IDLE;
            endcase
        end
    end

    // ------------------------------------------------------------------
    // Trace
    // ------------------------------------------------------------------
    // START fires combinationally on the cycle the caller pulses go_i
    // while we're still in S_IDLE. That cycle has out_valid=0 and
    // accept_beat=0, so the event doesn't compete with a BEAT event
    // in the priority if-else below (the bug pre-fix: flopping
    // go_latched delayed START onto the same cycle as beat 0, dropping
    // one of the two).
    logic go_pulse;
    assign go_pulse = (state == S_IDLE) && go_i;

    logic done_edge;
    state_e state_prev;
    always_ff @(posedge clk) begin
        if (!rst_n) state_prev <= S_IDLE;
        else        state_prev <= state;
    end
    assign done_edge = (state == S_DONE) && (state_prev != S_DONE);

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ev_valid  <= 1'b0;
            ev_subsys <= SUBSYS_SIF;
            ev_event  <= EV_DMA_START;
            ev_arg0   <= 64'd0;
            ev_arg1   <= 64'd0;
            ev_arg2   <= 64'd0;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'd0;
        end else if (go_pulse) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_SIF;
            ev_event  <= EV_DMA_START;
            ev_arg0   <= 64'd0;
            ev_arg1   <= 64'd0;
            ev_arg2   <= {56'd0, MASTER_ID};
            ev_arg3   <= 64'(TOTAL_WORDS);
            ev_flags  <= 32'd0;
        end else if (accept_beat) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_SIF;
            ev_event  <= EV_DMA_BEAT;
            ev_arg0   <= {32'd0, idx};
            ev_arg1   <= {32'd0, out_data};
            ev_arg2   <= {56'd0, MASTER_ID};
            ev_arg3   <= 64'(TOTAL_WORDS);
            ev_flags  <= {31'd0, out_last};
        end else if (done_edge) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_SIF;
            ev_event  <= EV_DMA_DONE;
            ev_arg0   <= 64'(TOTAL_WORDS);
            ev_arg1   <= 64'd0;
            ev_arg2   <= {56'd0, MASTER_ID};
            ev_arg3   <= 64'(TOTAL_WORDS);
            ev_flags  <= 32'd0;
        end else begin
            ev_valid  <= 1'b0;
        end
    end

endmodule : boot_install_agent_stub
