// retroDE_ps2 — sif_mailbox_stub
//
// Minimal EE↔IOP subsystem-interface mailbox shell. First stub on the SIF
// track. Standalone unit — does not yet integrate with any live IOP core.
// Testbenches drive both the EE-side port and the IOP-side port directly,
// playing both roles, to prove the register semantics without requiring
// a full dual-CPU bring-up.
//
// Contract refs:
//   docs/stub_module_plan.md        (Wave 2, item 10)
//   docs/contracts/sif.md           (mailbox/flag-only stub is allowed here)
//
// Register surface (offsets within the SIF block):
//   0x00  MSCOM  — 32-bit mailbox, conventionally EE→IOP
//   0x10  SMCOM  — 32-bit mailbox, conventionally IOP→EE
//   0x20  MSFLG  — 32-bit flag word, conventionally EE-owned for set,
//                  IOP-owned for clear (directional semantics deferred)
//   0x30  SMFLG  — 32-bit flag word, conventionally IOP-owned for set,
//                  EE-owned for clear (directional semantics deferred)
//
// Wave 2 scope intentionally does NOT enforce direction or set/clear
// semantics. Both ports can read and write any register with plain
// replace-on-write. The trace records which side initiated each access
// (side_id in arg2) so future-wave work can layer directional rules on
// top without changing the storage model.
//
// Port semantics:
//   Each side (EE / IOP) has an independent register port:
//     wr_en, rd_en, addr[7:0], wr_data[31:0], rd_data[31:0], rd_valid
//   Reads have 1-cycle latency to match the existing stub ecosystem.
//
// Write arbitration (per-register):
//   - EE and IOP writes to *different* registers on the same cycle both
//     land. Storage is not serialized across independent registers.
//   - EE and IOP writes to the *same* register on the same cycle: EE
//     wins, IOP write is dropped that cycle.
//   - Trace is limited to one event per cycle by the shared trace bus
//     (priority EE > IOP). An IOP write that lands silently when EE is
//     driving a different register will not be traced this wave — future
//     waves can add a second trace output port if that becomes a gap.
//
// Trace payload schema (SUBSYS_SIF, existing EV_READ/EV_WRITE codes):
//   SIF WRITE  arg0=offset arg1=data arg2=side_id arg3=0  flags[0]=1
//   SIF READ   arg0=offset arg1=data arg2=side_id arg3=0  flags[0]=0
//     side_id: 0 = EE, 1 = IOP
//
// Trace priority on same cycle: EE write > IOP write > EE read > IOP read.
// In practice TBs drive at most one operation per cycle.

`timescale 1ns/1ps

module sif_mailbox_stub
    import trace_pkg::*;
(
    input  logic          clk,
    input  logic          rst_n,

    // EE-side register port
    input  logic          ee_wr_en,
    input  logic          ee_rd_en,
    input  logic [7:0]    ee_addr,
    input  logic [31:0]   ee_wr_data,
    output logic [31:0]   ee_rd_data,
    output logic          ee_rd_valid,

    // IOP-side register port
    input  logic          iop_wr_en,
    input  logic          iop_rd_en,
    input  logic [7:0]    iop_addr,
    input  logic [31:0]   iop_wr_data,
    output logic [31:0]   iop_rd_data,
    output logic          iop_rd_valid,

    // 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 logic [7:0] MSCOM_OFF = 8'h00;
    localparam logic [7:0] SMCOM_OFF = 8'h10;
    localparam logic [7:0] MSFLG_OFF = 8'h20;
    localparam logic [7:0] SMFLG_OFF = 8'h30;

    localparam logic [63:0] SIDE_EE  = 64'd0;
    localparam logic [63:0] SIDE_IOP = 64'd1;

    // ------------------------------------------------------------------
    // Register file
    // ------------------------------------------------------------------

    logic [31:0] mscom;
    logic [31:0] smcom;
    logic [31:0] msflg;
    logic [31:0] smflg;

    function automatic logic [31:0] select_reg(input logic [7:0] offset,
                                               input logic [31:0] mscom_v,
                                               input logic [31:0] smcom_v,
                                               input logic [31:0] msflg_v,
                                               input logic [31:0] smflg_v);
        case (offset)
            MSCOM_OFF: select_reg = mscom_v;
            SMCOM_OFF: select_reg = smcom_v;
            MSFLG_OFF: select_reg = msflg_v;
            SMFLG_OFF: select_reg = smflg_v;
            default:   select_reg = 32'hDEAD_BEEF;
        endcase
    endfunction

    // Per-register write arbitration: EE wins on same-register collision,
    // but writes to different registers land independently.
    logic ee_hits_mscom, ee_hits_smcom, ee_hits_msflg, ee_hits_smflg;
    logic iop_hits_mscom, iop_hits_smcom, iop_hits_msflg, iop_hits_smflg;

    assign ee_hits_mscom = ee_wr_en && (ee_addr == MSCOM_OFF);
    assign ee_hits_smcom = ee_wr_en && (ee_addr == SMCOM_OFF);
    assign ee_hits_msflg = ee_wr_en && (ee_addr == MSFLG_OFF);
    assign ee_hits_smflg = ee_wr_en && (ee_addr == SMFLG_OFF);

    assign iop_hits_mscom = iop_wr_en && (iop_addr == MSCOM_OFF);
    assign iop_hits_smcom = iop_wr_en && (iop_addr == SMCOM_OFF);
    assign iop_hits_msflg = iop_wr_en && (iop_addr == MSFLG_OFF);
    assign iop_hits_smflg = iop_wr_en && (iop_addr == SMFLG_OFF);

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            mscom <= 32'd0;
            smcom <= 32'd0;
            msflg <= 32'd0;
            smflg <= 32'd0;
        end else begin
            if      (ee_hits_mscom)  mscom <= ee_wr_data;
            else if (iop_hits_mscom) mscom <= iop_wr_data;

            if      (ee_hits_smcom)  smcom <= ee_wr_data;
            else if (iop_hits_smcom) smcom <= iop_wr_data;

            if      (ee_hits_msflg)  msflg <= ee_wr_data;
            else if (iop_hits_msflg) msflg <= iop_wr_data;

            if      (ee_hits_smflg)  smflg <= ee_wr_data;
            else if (iop_hits_smflg) smflg <= iop_wr_data;
        end
    end

    // ------------------------------------------------------------------
    // Reads (1-cycle latency, both ports independent)
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ee_rd_data  <= 32'd0;
            ee_rd_valid <= 1'b0;
            iop_rd_data <= 32'd0;
            iop_rd_valid <= 1'b0;
        end else begin
            ee_rd_valid <= ee_rd_en;
            if (ee_rd_en)
                ee_rd_data <= select_reg(ee_addr, mscom, smcom, msflg, smflg);

            iop_rd_valid <= iop_rd_en;
            if (iop_rd_en)
                iop_rd_data <= select_reg(iop_addr, mscom, smcom, msflg, smflg);
        end
    end

    // ------------------------------------------------------------------
    // Trace emission — priority EE_wr > IOP_wr > EE_rd > IOP_rd.
    // Reads emit with the data that will be delivered next cycle, keeping
    // the trace line self-consistent.
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ev_valid  <= 1'b0;
            ev_subsys <= SUBSYS_SIF;
            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 (ee_wr_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_SIF;
            ev_event  <= EV_WRITE;
            ev_arg0   <= {56'd0, ee_addr};
            ev_arg1   <= {32'd0, ee_wr_data};
            ev_arg2   <= SIDE_EE;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'h0000_0001;
        end else if (iop_wr_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_SIF;
            ev_event  <= EV_WRITE;
            ev_arg0   <= {56'd0, iop_addr};
            ev_arg1   <= {32'd0, iop_wr_data};
            ev_arg2   <= SIDE_IOP;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'h0000_0001;
        end else if (ee_rd_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_SIF;
            ev_event  <= EV_READ;
            ev_arg0   <= {56'd0, ee_addr};
            ev_arg1   <= {32'd0, select_reg(ee_addr, mscom, smcom, msflg, smflg)};
            ev_arg2   <= SIDE_EE;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'd0;
        end else if (iop_rd_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_SIF;
            ev_event  <= EV_READ;
            ev_arg0   <= {56'd0, iop_addr};
            ev_arg1   <= {32'd0, select_reg(iop_addr, mscom, smcom, msflg, smflg)};
            ev_arg2   <= SIDE_IOP;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'd0;
        end else begin
            ev_valid <= 1'b0;
        end
    end

endmodule : sif_mailbox_stub
