// retroDE_ps2 — dmac_reg_stub
//
// EE DMAC stub. Channel-agnostic: the module's behaviour is generic across
// PS2 DMA channels and downstream endpoints. The specific channel and path
// id are set via parameters; the downstream endpoint wires (ep_*) are
// valid/data/last/ready regardless of what consumer is connected. Current
// uses: CHANNEL=2 (GIF path), CHANNEL=5 (SIF0 path).
//
// Payload source: memory-backed via the `mem_rd_*` master port, typically
// routed through `ee_memory_map_stub` to `ee_ram_stub`. MADR is the real
// fetch source address.
//
// Contract refs:
//   docs/stub_module_plan.md             (Wave 2, item 8)
//   docs/wave2_dma_gif_plan.md           (Wave 2 scope)
//   docs/wave25_memory_backed_dma_plan.md (Wave 2.5 scope — THIS REVISION)
//   docs/contracts/dmac.md
//
// Register surface (single channel, selected by CHANNEL parameter):
//   offset 0x00  CHCR  — start bit at [0], other bits recorded
//   offset 0x10  MADR  — real fetch source address (Wave 2.5)
//   offset 0x20  QWC   — transfer length in 128-bit qwords (first sign-off
//                        path requires QWC == 1; state machine is QWC-
//                        generic for a future Wave 2.6 extension)
//   offset 0x30  TADR  — recorded for future chain-mode use
//   offset 0x40  DONE_COUNT — monotonic completion counter (read-only;
//                        writes are accepted but ignored). Software reads
//                        this to distinguish "nth completion" without
//                        counting interrupts externally. EE-core chapter 4
//                        addition; mirrors iop_dmac_reg_stub's DONE_COUNT
//                        but at a new slot (0x0C is occupied on the IOP
//                        stub; EE stub's 16-byte register spacing puts
//                        DONE_COUNT at 0x40).
//
// Register reads (EE-core chapter 4, added alongside the original write
// surface): reg_rd_en / reg_rd_data / reg_rd_valid with 1-cycle latency,
// matching the rest of the stub ecosystem. All four config registers plus
// DONE_COUNT are readable; all other offsets return 0.
//
// Memory master interface (to ee_ram_stub in Wave 2.5):
//   mem_rd_en / mem_rd_addr drive the request
//   mem_rd_valid / mem_rd_data return data one cycle later
//
// Downstream endpoint: ep_{valid,data,last,ready}. The port names are
// channel-agnostic because the DMAC's behaviour is generic across PS2
// channels (ch2 = GIF, ch5 = SIF0, etc.). Connect the endpoint side to
// whichever consumer matches the instantiated CHANNEL/PATH_ID.
//
// State machine:
//   IDLE         → FETCH_WAIT on CHCR start
//   FETCH_WAIT   → ACTIVE_SEND on mem_rd_valid (data latched)
//   ACTIVE_SEND  → FETCH_WAIT on endpoint accept with more beats pending
//                → DONE on endpoint accept for the final beat
//   DONE         → IDLE next cycle (clears CHCR.start)
//
// Trace payload schemas (per wave25_memory_backed_dma_plan.md):
//   DMAC DMA_CFG   arg0=channel arg1=chcr  arg2=madr arg3=qwc
//                  flags=reg_offset (which reg was written)
//   DMAC DMA_START arg0=channel arg1=qwc   arg2=MADR arg3=path_id
//   DMAC DMA_BEAT  arg0=channel arg1=beat  arg2=src_addr arg3=remaining
//   DMAC DMA_DONE  arg0=channel arg1=beats arg2=completion arg3=path_id
//     completion code: 0 = OK

`timescale 1ns/1ps

module dmac_reg_stub
    import trace_pkg::*;
#(
    parameter logic [3:0] CHANNEL = 4'd2,
    parameter logic [3:0] PATH_ID = 4'd2
) (
    input  logic          clk,
    input  logic          rst_n,

    // CPU / testbench register write port (single-channel, see CHANNEL).
    // reg_offset is shared by read and write; callers must not assert both
    // enables in the same cycle (the map ensures this because the EE CPU
    // emits either rd or wr per transaction, never both).
    input  logic          reg_wr_en,
    input  logic [7:0]    reg_offset,
    input  logic [31:0]   reg_wr_data,

    // Register read port (EE-core chapter 4). 1-cycle latency.
    input  logic          reg_rd_en,
    output logic [31:0]   reg_rd_data,
    output logic          reg_rd_valid,

    // Memory master (Wave 2.5) — direct link to ee_ram_stub in this phase.
    // Future waves will route this through ee_memory_map_stub.
    output logic          mem_rd_en,
    output logic [31:0]   mem_rd_addr,
    input  logic [127:0]  mem_rd_data,
    input  logic          mem_rd_valid,

    // Downstream to gif_path_stub
    output logic          ep_valid,
    output logic [127:0]  ep_data,
    output logic          ep_last,
    input  logic          ep_ready,

    // Completion pulse — one cycle high when the transfer reaches S_DONE.
    // Intended as an INTC source; level-held bit latching happens in the
    // interrupt controller, not here.
    output logic          irq_completion_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 [7:0] CHCR_OFFSET       = 8'h00;
    localparam logic [7:0] MADR_OFFSET       = 8'h10;
    localparam logic [7:0] QWC_OFFSET        = 8'h20;
    localparam logic [7:0] TADR_OFFSET       = 8'h30;
    localparam logic [7:0] DONE_COUNT_OFFSET = 8'h40;

    // ------------------------------------------------------------------
    // Register file (ch2 only)
    // ------------------------------------------------------------------

    logic [31:0] chcr;
    logic [31:0] madr;
    logic [31:0] qwc;
    logic [31:0] tadr;
    logic [31:0] done_count;

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

    // Single owner for the config regs: software writes win over the
    // S_DONE auto-clear on CHCR[0] in the unlikely same-cycle case
    // (the NBA queue lets the case-statement full-width assign
    // override the partial bit-0 clear). Software writing CHCR while
    // the DMA is completing is not part of any sane flow, so this
    // ordering is defensive — the point is: chcr has one procedural
    // driver, not two.
    always_ff @(posedge clk) begin
        if (!rst_n) begin
            chcr <= 32'd0;
            madr <= 32'd0;
            qwc  <= 32'd0;
            tadr <= 32'd0;
        end else begin
            if (state == S_DONE) chcr[0] <= 1'b0;
            if (reg_wr_en) begin
                case (reg_offset)
                    CHCR_OFFSET: chcr <= reg_wr_data;
                    MADR_OFFSET: madr <= reg_wr_data;
                    QWC_OFFSET:  qwc  <= reg_wr_data;
                    TADR_OFFSET: tadr <= reg_wr_data;
                    default: ;
                endcase
            end
        end
    end

    // DONE_COUNT: monotonic completion counter. Increments on S_DONE
    // entry. Reset-only clear path; writes at the DONE_COUNT offset are
    // silently dropped by the write always_ff above (read-only register).
    always_ff @(posedge clk) begin
        if (!rst_n)                 done_count <= 32'd0;
        else if (state == S_DONE)   done_count <= done_count + 32'd1;
    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)
                    CHCR_OFFSET:       reg_rd_data <= chcr;
                    MADR_OFFSET:       reg_rd_data <= madr;
                    QWC_OFFSET:        reg_rd_data <= qwc;
                    TADR_OFFSET:       reg_rd_data <= tadr;
                    DONE_COUNT_OFFSET: reg_rd_data <= done_count;
                    default:           reg_rd_data <= 32'd0;
                endcase
            end
        end
    end

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

    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;

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

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

    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;

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

    assign irq_completion_o = (state == S_DONE);

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            state        <= S_IDLE;
            madr_latched <= 32'd0;
            qwc_latched  <= 32'd0;
            beat_index   <= 32'd0;
            beat_payload <= 128'd0;
        end else begin
            unique case (state)
                S_IDLE: begin
                    if (start_pulse) begin
                        // start_pulse is gated by reg_wr_en && reg_offset ==
                        // CHCR_OFFSET, so a same-cycle QWC write is
                        // structurally impossible through this interface.
                        // Latch the currently-visible register state.
                        state        <= S_FETCH_WAIT;
                        madr_latched <= madr;
                        qwc_latched  <= qwc;
                        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 == qwc_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;
                    // chcr[0] auto-clear on S_DONE now lives in the
                    // register-ownership always_ff above (single
                    // procedural driver for chcr).
                end

                default: state <= S_IDLE;
            endcase
        end
    end

    // ------------------------------------------------------------------
    // Trace emission — one event per cycle; priority:
    //   DONE pulse > BEAT accept > START on transition > CFG on write
    // ------------------------------------------------------------------

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

    logic enter_start;   // transitioning from IDLE into the transfer
    assign enter_start = (state == S_FETCH_WAIT) && !prev_state_fetch_or_later;

    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;
        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;
        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};             // this beat's source
            ev_arg3   <= {32'd0, qwc_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, qwc_latched};
            ev_arg2   <= {32'd0, madr_latched};         // MADR is the source
            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 == QWC_OFFSET)  ? reg_wr_data : qwc};
            ev_flags  <= {24'd0, reg_offset};
        end else begin
            ev_valid <= 1'b0;
        end
    end

endmodule : dmac_reg_stub
