// retroDE_ps2 — iop_ram_stub
//
// First narrow IOP-side primitive. 32-bit IOP-RAM stub, architecturally
// honest to the IOP's R3000-class 32-bit bus. NOT an IOP CPU — this is
// pure memory. No fetch, no execution, no BIOS bring-up. Future IOP-side
// work (fetch stub, IOP memory map, DMAC routing) can build on top of it.
//
// Contract refs:
//   docs/contracts/iop.md           (IOP-local RAM/I/O decode)
//   docs/contracts/memory.md        (2 MiB IOP RAM in the PS2 memory map)
//
// Scope:
//   - read/write 32-bit data
//   - byte-enable granularity on writes
//   - one-cycle read latency (matches existing stub ecosystem)
//   - caller-provided master_id for trace attribution
//   - trace events tagged as SUBSYS_IOP so IOP-side memory traffic is
//     distinct from EE MEM events even when both are active
//
// Explicit non-goals (Wave 3 IOP first step):
//   - IOP CPU execution
//   - full 2 MiB sizing (default is 16 KiB — plenty for stub tests)
//   - integration into any IOP memory map yet
//   - connection to SIF receive path (intentional: kept independent so
//     future bridging is explicit, not accidental)
//
// Trace payload schema:
//   IOP READ   arg0=addr arg1=data arg2=master_id arg3=region_id
//   IOP WRITE  arg0=addr arg1=data arg2=master_id arg3=region_id
//     master_id : caller-provided (e.g. 0 = TB direct, future: 2 = IOP CPU,
//                 3 = SIF bridge, etc.)
//     region_id : 2 = IOP_RAM (constant for this module)
//     flags[0]  : 1 = write, 0 = read

`timescale 1ns/1ps

module iop_ram_stub
    import trace_pkg::*;
#(
    parameter int    SIZE_BYTES = 16 * 1024,   // 16 KiB default
    parameter string IMAGE_FILE = ""
) (
    input  logic                          clk,
    input  logic                          rst_n,

    // Read port
    input  logic                          rd_en,
    input  logic [$clog2(SIZE_BYTES)-1:0] rd_addr,
    output logic [31:0]                   rd_data,
    output logic                          rd_valid,

    // Write port
    input  logic                          wr_en,
    input  logic [$clog2(SIZE_BYTES)-1:0] wr_addr,
    input  logic [31:0]                   wr_data,
    input  logic [3:0]                    wr_be,

    // Caller-provided master id for trace attribution
    input  logic [7:0]                    master_id,

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

    localparam int ADDR_WIDTH       = $clog2(SIZE_BYTES);
    localparam int WORD_COUNT       = SIZE_BYTES / 4;
    localparam int WORD_INDEX_WIDTH = ADDR_WIDTH - 2;
    localparam logic [63:0] REGION_IOP_RAM = 64'd2;

    logic [31:0] mem [0:WORD_COUNT-1];

    initial begin
        if (IMAGE_FILE != "") begin
            $display("[iop_ram_stub] loading image: %0s", IMAGE_FILE);
            $readmemh(IMAGE_FILE, mem);
        end else begin
            for (int i = 0; i < WORD_COUNT; i++) mem[i] = 32'd0;
            $display("[iop_ram_stub] zero-initialised (%0d words / %0d bytes)",
                     WORD_COUNT, SIZE_BYTES);
        end
    end

    logic [WORD_INDEX_WIDTH-1:0] rd_word_idx;
    logic [WORD_INDEX_WIDTH-1:0] wr_word_idx;
    assign rd_word_idx = rd_addr[ADDR_WIDTH-1:2];
    assign wr_word_idx = wr_addr[ADDR_WIDTH-1:2];

    // ------------------------------------------------------------------
    // Read + write (one-cycle latency)
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            rd_data  <= 32'd0;
            rd_valid <= 1'b0;
        end else begin
            rd_valid <= rd_en;
            if (rd_en) rd_data <= mem[rd_word_idx];

            if (wr_en) begin
                for (int b = 0; b < 4; b++) begin
                    if (wr_be[b]) mem[wr_word_idx][b*8 +: 8] <= wr_data[b*8 +: 8];
                end
            end
        end
    end

    // ------------------------------------------------------------------
    // Trace emission — read wins on same-cycle collision (single-port
    // RAM wouldn't see that anyway in Wave 3).
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ev_valid  <= 1'b0;
            ev_subsys <= SUBSYS_IOP;
            ev_event  <= EV_READ;
            ev_arg0   <= 64'd0;
            ev_arg1   <= 64'd0;
            ev_arg2   <= 64'd0;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'd0;
        end else if (rd_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_IOP;
            ev_event  <= EV_READ;
            ev_arg0   <= {{(64-ADDR_WIDTH){1'b0}}, rd_addr};
            ev_arg1   <= {32'd0, mem[rd_word_idx]};
            ev_arg2   <= {56'd0, master_id};
            ev_arg3   <= REGION_IOP_RAM;
            ev_flags  <= 32'd0;
        end else if (wr_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_IOP;
            ev_event  <= EV_WRITE;
            ev_arg0   <= {{(64-ADDR_WIDTH){1'b0}}, wr_addr};
            ev_arg1   <= {32'd0, wr_data};
            ev_arg2   <= {56'd0, master_id};
            ev_arg3   <= REGION_IOP_RAM;
            ev_flags  <= 32'h0000_0001;
        end else begin
            ev_valid <= 1'b0;
        end
    end

endmodule : iop_ram_stub
