// retroDE_ps2 — iop_dmac_reg_stub
//
// IOP DMAC channel 9 (SIF0 IOP→EE) with a real, bounded data path.
// Upgraded from the earlier register+lifecycle shell: MADR is a real
// source pointer into IOP RAM, BCR is a real word count, and the
// state machine pulls 32-bit beats out of IOP RAM through the IOP map
// and emits them on a word-granularity endpoint with ready/valid/last
// handshake. Mirrors the EE DMAC shape (dmac_reg_stub) at 32-bit width.
//
// Contract refs:
//   docs/contracts/iop.md         (IOP DMAC ownership)
//
// Register surface (per-channel, low-byte offset):
//   0x00  MADR        — real source address in IOP physical space
//   0x04  BCR         — transfer length in 32-bit beats
//   0x08  CHCR        — channel control; bit[0] is the start bit
//   0x0C  DONE_COUNT  — monotonic completion counter (read-only; writes
//                       are accepted but ignored). Software reads this
//                       to distinguish "nth completion" without needing
//                       to count interrupts externally.
//   Other offsets: writes accepted but ignored; reads return 0.
//
// Memory master interface (to iop_memory_map_stub's dma_rd_* port):
//   mem_rd_en / mem_rd_addr issue the request (one cycle)
//   mem_rd_valid / mem_rd_data return the word one cycle later
//   mem_master_id drives the map trace attribution (convention: 4)
//
// Endpoint (to sif_dma_ee_ram_bridge_stub or similar 32-bit sink):
//   ep_valid / ep_data[31:0] / ep_last
//   ep_ready is the backpressure signal — when low, the state machine
//   holds in ACTIVE_SEND with the current beat. No false completion.
//
// State machine:
//   IDLE         → FETCH_WAIT   on CHCR start
//   FETCH_WAIT   → ACTIVE_SEND  on mem_rd_valid (word latched)
//   ACTIVE_SEND  → FETCH_WAIT   on endpoint accept with more beats left
//                → DONE          on endpoint accept for the final beat
//   DONE         → IDLE          next cycle (clears CHCR.start)
//
// Source stepping: src_addr = madr_latched + (beat_index * 4).
//
// Trace payload schema (SUBSYS_DMAC):
//   DMA_CFG    arg0=channel arg1=chcr arg2=madr arg3=bcr flags=reg_offset
//   DMA_START  arg0=channel arg1=bcr  arg2=madr arg3=path_id
//   DMA_BEAT   arg0=channel arg1=beat_index arg2=src_addr arg3=remaining
//   DMA_DONE   arg0=channel arg1=beats arg2=completion_code arg3=path_id
//     completion_code 0 = OK.

`timescale 1ns/1ps

module iop_dmac_reg_stub
    import trace_pkg::*;
#(
    parameter logic [3:0] CHANNEL   = 4'd9,        // SIF0 (IOP → EE)
    parameter logic [3:0] PATH_ID   = 4'd9,
    parameter logic [7:0] MASTER_ID = 8'd4         // for dma_rd trace attribution
) (
    input  logic          clk,
    input  logic          rst_n,

    // IOP-side register access (from the memory map's iop_dmac_* port)
    input  logic          reg_wr_en,
    input  logic          reg_rd_en,
    input  logic [3:0]    reg_offset,
    input  logic [31:0]   reg_wr_data,
    output logic [31:0]   reg_rd_data,
    output logic          reg_rd_valid,

    // Memory read master (to iop_memory_map_stub dma_rd_* port)
    output logic          mem_rd_en,
    output logic [31:0]   mem_rd_addr,
    output logic [7:0]    mem_master_id,
    input  logic [31:0]   mem_rd_data,
    input  logic          mem_rd_valid,

    // Endpoint (word-granularity stream to SIF egress bridge)
    output logic          ep_valid,
    output logic [31:0]   ep_data,
    output logic          ep_last,
    input  logic          ep_ready,

    // Completion pulse — one cycle high when the channel reaches S_DONE.
    // Intended as an IOP INTC source; latching is the interrupt
    // controller's responsibility.
    output logic          irq_completion_o,

    // Status
    output logic          busy_o,
    output logic [31:0]   done_count_o,

    // 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 [3:0] MADR_OFFSET       = 4'h0;
    localparam logic [3:0] BCR_OFFSET        = 4'h4;
    localparam logic [3:0] CHCR_OFFSET       = 4'h8;
    localparam logic [3:0] DONE_COUNT_OFFSET = 4'hC;

    typedef enum logic [1:0] {
        S_IDLE        = 2'd0,
        S_FETCH_WAIT  = 2'd1,
        S_ACTIVE_SEND = 2'd2,
        S_DONE        = 2'd3
    } state_e;

    logic [31:0] madr;
    logic [31:0] bcr;
    logic [31:0] chcr;

    state_e      state;
    logic [31:0] madr_latched;
    logic [31:0] bcr_latched;
    logic [31:0] beat_index;
    logic [31:0] beat_payload;

    logic start_pulse;
    assign start_pulse = reg_wr_en && (reg_offset == CHCR_OFFSET)
                         && reg_wr_data[0] && !chcr[0];

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

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            madr <= 32'd0;
            bcr  <= 32'd0;
            chcr <= 32'd0;
        end else begin
            if (reg_wr_en) begin
                case (reg_offset)
                    MADR_OFFSET: madr <= reg_wr_data;
                    BCR_OFFSET:  bcr  <= reg_wr_data;
                    CHCR_OFFSET: chcr <= reg_wr_data;
                    default: ;
                endcase
            end
            if (state == S_DONE) chcr[0] <= 1'b0;
        end
    end

    // ------------------------------------------------------------------
    // Register read (1-cycle latency, matches rest of stub ecosystem)
    // ------------------------------------------------------------------

    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_offset)
                    MADR_OFFSET:       reg_rd_data <= madr;
                    BCR_OFFSET:        reg_rd_data <= bcr;
                    CHCR_OFFSET:       reg_rd_data <= chcr;
                    DONE_COUNT_OFFSET: reg_rd_data <= done_count_o;
                    default:           reg_rd_data <= 32'd0;
                endcase
            end
        end
    end

    // ------------------------------------------------------------------
    // Transfer state machine
    // ------------------------------------------------------------------

    logic [31:0] src_addr;
    assign src_addr = madr_latched + (beat_index << 2);        // 4 bytes/beat

    logic beat_accepted;
    assign beat_accepted = ep_valid && ep_ready;

    // Pulse mem_rd_en for one cycle whenever we first enter FETCH_WAIT.
    logic prev_state_fw;
    always_ff @(posedge clk) begin
        if (!rst_n) prev_state_fw <= 1'b0;
        else        prev_state_fw <= (state == S_FETCH_WAIT);
    end
    logic entering_fw;
    assign entering_fw = (state == S_FETCH_WAIT) && !prev_state_fw;

    assign mem_rd_en     = entering_fw;
    assign mem_rd_addr   = src_addr;
    assign mem_master_id = MASTER_ID;

    // Drive endpoint only in ACTIVE_SEND with the latched payload.
    assign ep_valid = (state == S_ACTIVE_SEND);
    assign ep_data  = beat_payload;
    assign ep_last  = (state == S_ACTIVE_SEND) &&
                          (beat_index + 32'd1 == bcr_latched);

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            state        <= S_IDLE;
            madr_latched <= 32'd0;
            bcr_latched  <= 32'd0;
            beat_index   <= 32'd0;
            beat_payload <= 32'd0;
        end else begin
            unique case (state)
                S_IDLE: begin
                    if (start_pulse) begin
                        state        <= S_FETCH_WAIT;
                        madr_latched <= madr;
                        bcr_latched  <= bcr;
                        beat_index   <= 32'd0;
                    end
                end

                S_FETCH_WAIT: begin
                    if (mem_rd_valid) begin
                        beat_payload <= mem_rd_data;
                        state        <= S_ACTIVE_SEND;
                    end
                end

                S_ACTIVE_SEND: begin
                    if (beat_accepted) begin
                        if (beat_index + 32'd1 == bcr_latched) begin
                            state <= S_DONE;
                        end else begin
                            beat_index <= beat_index + 32'd1;
                            state      <= S_FETCH_WAIT;
                        end
                    end
                end

                S_DONE: begin
                    state <= S_IDLE;
                end

                default: state <= S_IDLE;
            endcase
        end
    end

    assign busy_o           = (state != S_IDLE);
    assign irq_completion_o = (state == S_DONE);

    // ------------------------------------------------------------------
    // Trace emission — one event per cycle. Priority:
    //   DONE > BEAT > START > CFG (register write)
    // ------------------------------------------------------------------

    logic prev_in_transfer;
    always_ff @(posedge clk) begin
        if (!rst_n) prev_in_transfer <= 1'b0;
        else        prev_in_transfer <= (state != S_IDLE);
    end

    logic enter_start;
    assign enter_start = (state == S_FETCH_WAIT) && !prev_in_transfer;

    logic enter_done;
    assign enter_done = (state == S_DONE);

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ev_valid      <= 1'b0;
            ev_subsys     <= SUBSYS_DMAC;
            ev_event      <= EV_DMA_CFG;
            ev_arg0       <= 64'd0;
            ev_arg1       <= 64'd0;
            ev_arg2       <= 64'd0;
            ev_arg3       <= 64'd0;
            ev_flags      <= 32'd0;
            done_count_o  <= 32'd0;
        end else if (enter_done) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_DMAC;
            ev_event  <= EV_DMA_DONE;
            ev_arg0   <= {60'd0, CHANNEL};
            ev_arg1   <= {32'd0, beat_index + 32'd1};   // beats completed
            ev_arg2   <= 64'd0;                         // completion OK
            ev_arg3   <= {60'd0, PATH_ID};
            ev_flags  <= 32'd0;
            done_count_o <= done_count_o + 32'd1;
        end else if (beat_accepted) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_DMAC;
            ev_event  <= EV_DMA_BEAT;
            ev_arg0   <= {60'd0, CHANNEL};
            ev_arg1   <= {32'd0, beat_index};
            ev_arg2   <= {32'd0, src_addr};
            ev_arg3   <= {32'd0, bcr_latched - beat_index - 32'd1};
            ev_flags  <= 32'd0;
        end else if (enter_start) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_DMAC;
            ev_event  <= EV_DMA_START;
            ev_arg0   <= {60'd0, CHANNEL};
            ev_arg1   <= {32'd0, bcr_latched};
            ev_arg2   <= {32'd0, madr_latched};
            ev_arg3   <= {60'd0, PATH_ID};
            ev_flags  <= 32'd0;
        end else if (reg_wr_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_DMAC;
            ev_event  <= EV_DMA_CFG;
            ev_arg0   <= {60'd0, CHANNEL};
            ev_arg1   <= {32'd0, (reg_offset == CHCR_OFFSET) ? reg_wr_data : chcr};
            ev_arg2   <= {32'd0, (reg_offset == MADR_OFFSET) ? reg_wr_data : madr};
            ev_arg3   <= {32'd0, (reg_offset == BCR_OFFSET)  ? reg_wr_data : bcr};
            ev_flags  <= {28'd0, reg_offset};
        end else begin
            ev_valid <= 1'b0;
        end
    end

endmodule : iop_dmac_reg_stub
