// retroDE_ps2 — sif_dma_ee_ram_bridge_stub
//
// Width-adapting bridge from a 32-bit SIF DMA endpoint (IOP→EE egress)
// to the 128-bit EE memory map. Accumulates four incoming 32-bit beats
// into a qword and issues one qword write through ee_memory_map_stub's
// bridge write port.
//
// Mirror of sif_dma_iop_ram_bridge_stub, but in the other direction
// (words → qword, EE-side landing).
//
// Contract refs:
//   docs/contracts/sif.md        (DMA-linked data movement endpoints)
//   docs/contracts/memory.md     (EE RAM is 128-bit qword-aligned)
//
// Handshake (upstream, from DMAC ep_* port or equivalent):
//   in_valid / in_data[31:0] / in_last / in_ready
//   Bridge asserts in_ready while it's accumulating (up to the 3rd beat
//   of a quad, inclusive). It drops in_ready during the one-cycle emit
//   that follows the 4th beat, so the DMAC naturally stalls with
//   back-pressure for a single cycle between qwords.
//
// Handshake (downstream, to ee_memory_map_stub bridge-write port):
//   bridge_wr_en / bridge_wr_addr[31:0] / bridge_wr_data[127:0] /
//   bridge_wr_be[15:0] / bridge_master_id[7:0]
//
// Data layout (little-endian):
//   beat 0 → bridge_wr_data[31:0]
//   beat 1 → bridge_wr_data[63:32]
//   beat 2 → bridge_wr_data[95:64]
//   beat 3 → bridge_wr_data[127:96]
//   qword address advances DEST_BASE_ADDR by 16 per emit.
//
// Partial quad on in_last:
//   If `in_last` arrives before the 4th beat of a quad, the bridge
//   emits the partial qword with wr_be masked to cover only the bytes
//   that were actually accepted. Not exercised by the current TB (BCR
//   is chosen to be a multiple of 4), but kept defensively.
//
// Payload-complete indication (last_seen_o):
//   Level-held output, set when `in_last && accept_beat` fires on the
//   upstream handshake. Intended for EE-side protocol combiners that
//   need to gate an ack on "payload fully moved" independently of when
//   the IOP posted a control doorbell. Latch stays high until reset —
//   this mirrors sif_dma_stub.last_seen.
//
// Parameters:
//   DEST_BASE_ADDR  — byte offset where the first qword lands. Advances
//                     by 16 per emit for the life of the transfer.
//   MASTER_ID       — bridge's identity for MEM / EE-map trace attribution
//                     (default 5 = SIF EE-side bridge).
//
// Non-goals:
//   - multiple in-flight qwords
//   - arbitration against other bridge writers on the EE map's write path

`timescale 1ns/1ps

module sif_dma_ee_ram_bridge_stub
#(
    parameter logic [31:0] DEST_BASE_ADDR = 32'h0000_0000,
    parameter logic [7:0]  MASTER_ID      = 8'd5
) (
    input  logic          clk,
    input  logic          rst_n,

    // Upstream (DMAC endpoint side)
    input  logic          in_valid,
    input  logic [31:0]   in_data,
    input  logic          in_last,
    output logic          in_ready,

    // Downstream (EE map bridge-write port)
    output logic          bridge_wr_en,
    output logic [31:0]   bridge_wr_addr,
    output logic [127:0]  bridge_wr_data,
    output logic [15:0]   bridge_wr_be,
    output logic [7:0]    bridge_master_id,

    // Payload-complete indication (level, latched). Consumers gate on
    // "full payload landed" without needing to count beats.
    output logic          last_seen_o,

    // Ch239 — single-cycle "rewind" pulse. When asserted (and the
    // bridge is idle in S_ACCUM with no beat in flight), the running
    // `wr_offset` returns to 0 so the NEXT emit lands at
    // DEST_BASE_ADDR. Lets a producer that wants single-slot buffer
    // semantics (e.g. a libpad-style pad packet) overwrite the same
    // 16-byte slot on every transfer instead of streaming forward.
    // Existing producers that don't need this leave it tied to 1'b0
    // and the bridge keeps its streaming behaviour exactly as before.
    // Pulse must be asserted between transfers; firing mid-transfer
    // (`state==S_EMIT` or `pos != 0`) is illegal and logged as a
    // sim-only `$error` (no defensive RTL gating — keeps the path
    // single-purpose). See `docs/contracts/sio2_pad.md` Ch239.
    input  logic          rewind_i = 1'b0
);

    typedef enum logic [0:0] {
        S_ACCUM = 1'b0,
        S_EMIT  = 1'b1
    } state_e;

    state_e       state;
    logic [127:0] acc_data;
    logic [15:0]  acc_be;
    logic [1:0]   pos;                              // 0..3 within qword
    logic [31:0]  wr_offset;                        // running byte offset

    assign in_ready         = (state == S_ACCUM);
    assign bridge_master_id = MASTER_ID;

    logic accept_beat;
    assign accept_beat = in_valid && in_ready;

    // ------------------------------------------------------------------
    // Accumulator / state machine
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            state     <= S_ACCUM;
            acc_data  <= 128'd0;
            acc_be    <= 16'd0;
            pos       <= 2'd0;
            wr_offset <= 32'd0;
        end else begin
            // Ch239 — between-transfer rewind. Resets only the
            // streaming offset; `acc_data`/`acc_be`/`pos` are
            // already 0 after every emit's tail. Misuse (rewind
            // pulse during a transfer) is reported via sim $error
            // below; the RTL still applies the rewind because the
            // guard would otherwise hide producer-side bugs.
            if (rewind_i) wr_offset <= 32'd0;

            unique case (state)
                S_ACCUM: begin
                    if (accept_beat) begin
                        // Place the incoming word in slot `pos` and mark
                        // its four bytes enabled.
                        acc_data[pos*32 +: 32] <= in_data;
                        acc_be[pos*4  +: 4]    <= 4'b1111;

                        if (pos == 2'd3 || in_last) begin
                            state <= S_EMIT;
                        end else begin
                            pos <= pos + 2'd1;
                        end
                    end
                end

                S_EMIT: begin
                    // Single-cycle emit; bridge_wr_en is combinationally
                    // tied to state. Advance qword offset, reset slot /
                    // accumulator for the next quad. The Ch239 rewind
                    // above runs first, so a `rewind_i` pulse coincident
                    // with an emit cycle leaves wr_offset at 0 (no +16
                    // increment) — but that combination is the illegal
                    // "rewind mid-transfer" case and the $error below
                    // catches it for the producer to fix.
                    wr_offset <= wr_offset + 32'd16;
                    acc_data  <= 128'd0;
                    acc_be    <= 16'd0;
                    pos       <= 2'd0;
                    state     <= S_ACCUM;
                end

                default: state <= S_ACCUM;
            endcase
        end
    end

`ifndef SYNTHESIS
    // Misuse detector — `rewind_i` while a transfer is in flight is
    // a producer-side bug. Caught here so the path stays clean.
    always_ff @(posedge clk) begin
        if (rst_n && rewind_i && (state != S_ACCUM || pos != 2'd0)) begin
            $error("[sif_dma_ee_ram_bridge_stub] illegal rewind_i mid-transfer (state=%0d pos=%0d)",
                   state, pos);
        end
    end
`endif

    // ------------------------------------------------------------------
    // Downstream write-port drive (combinational on state)
    // ------------------------------------------------------------------

    assign bridge_wr_en   = (state == S_EMIT);
    assign bridge_wr_addr = DEST_BASE_ADDR + wr_offset;
    assign bridge_wr_data = acc_data;
    assign bridge_wr_be   = acc_be;

    // ------------------------------------------------------------------
    // last_seen_o: set once the upstream asserts in_last on a beat that
    // is actually accepted. Level-held until reset.
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n)                      last_seen_o <= 1'b0;
        else if (accept_beat && in_last) last_seen_o <= 1'b1;
    end

endmodule : sif_dma_ee_ram_bridge_stub
