// retroDE_ps2 — ee_dmac_ctrl_stub
//
// Ch287 — EE DMAC global control/status registers at
// 0x1000_E000..0x1000_E0FF (256 bytes). NOT the per-channel registers
// (those live in dmac_reg_stub at 0x1000_A000+ for channel 2; per-
// channel registers for other channels are not modelled yet).
//
// Surface modelled here (R5900 DMAC global):
//   offset 0x00  D_CTRL  — DMAC enable / cycle-stealing / RELE / etc.
//                          Latched write, read returns last-written.
//   offset 0x10  D_STAT  — Per-channel interrupt status (CIS) + per-
//                          channel interrupt mask (CIM) + stall / MEIS.
//                          Read returns current latch (reset = 0 = no
//                          pending interrupts). Writes are W1C against
//                          the CIS/MEIS half (bits where write_data has
//                          a 1 are cleared); CIM half is NOT W1C — bits
//                          are unconditionally written. Real R5900
//                          splits the word: bits[15:0] = CIS (W1C), bits
//                          [31:16] = CIM (write). With nothing in the
//                          stub yet setting bits, qbert sees "no
//                          interrupts pending" on every read, which is
//                          exactly the wait-for-quiet pattern its init
//                          loop polls for.
//   offset 0x20  D_PCR   — Per-channel priority + W1C enables. Latched
//                          write, read returns last-written.
//   offset 0x30  D_SQWC  — Stall/skip cycles. Latched.
//   offset 0x40  D_RBSR  — Ring-buffer size. Latched.
//   offset 0x50  D_RBOR  — Ring-buffer base. Latched.
//   any other offset      — write traced + dropped; read returns 0.
//
// Codex framing: "If the hot PC is truly a D_STAT poll, read-as-zero
// may or may not be the right 'ready' value. Let the next run tell us.
// If it still loops, the next chapter should decode the branch
// condition and choose the exact D_STAT bit semantics, not guess the
// whole region." The implementation honors that — every offset has
// minimal-sufficient behavior; future chapters can refine specific
// bits once a real ELF surfaces a divergence.
//
// Port interface mirrors the dmac_reg_stub / intc_stub conventions:
//   reg_wr_en / reg_offset / reg_wr_data : write port
//   reg_rd_en / reg_offset / reg_rd_data / reg_rd_valid : read port,
//                                                          1-cycle latency
//   trace_pkg::* : ev_* events tagged SUBSYS_DMAC + EV_READ/EV_WRITE
//                  with arg0 = offset, arg1 = data.

`timescale 1ns/1ps

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

    // Write port (single-cycle, shared offset with read).
    input  logic          reg_wr_en,
    input  logic [7:0]    reg_offset,
    input  logic [31:0]   reg_wr_data,

    // Read port (1-cycle latency).
    input  logic          reg_rd_en,
    output logic [31:0]   reg_rd_data,
    output logic          reg_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] D_CTRL_OFFSET = 8'h00;
    localparam logic [7:0] D_STAT_OFFSET = 8'h10;
    localparam logic [7:0] D_PCR_OFFSET  = 8'h20;
    localparam logic [7:0] D_SQWC_OFFSET = 8'h30;
    localparam logic [7:0] D_RBSR_OFFSET = 8'h40;
    localparam logic [7:0] D_RBOR_OFFSET = 8'h50;

    // ------------------------------------------------------------------
    // Register file
    // ------------------------------------------------------------------
    logic [31:0] d_ctrl;
    logic [31:0] d_stat;     // CIS in low half (W1C), CIM in high half (W)
    logic [31:0] d_pcr;
    logic [31:0] d_sqwc;
    logic [31:0] d_rbsr;
    logic [31:0] d_rbor;

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            d_ctrl <= 32'd0;
            d_stat <= 32'd0;
            d_pcr  <= 32'd0;
            d_sqwc <= 32'd0;
            d_rbsr <= 32'd0;
            d_rbor <= 32'd0;
        end else if (reg_wr_en) begin
            unique case (reg_offset)
                D_CTRL_OFFSET: d_ctrl <= reg_wr_data;
                D_STAT_OFFSET: begin
                    // W1C on the low half (interrupt-status bits): a 1
                    // in reg_wr_data clears that bit; a 0 leaves it.
                    // Direct-write on the high half (mask bits).
                    d_stat[15:0]  <= d_stat[15:0]  & ~reg_wr_data[15:0];
                    d_stat[31:16] <= reg_wr_data[31:16];
                end
                D_PCR_OFFSET:  d_pcr  <= reg_wr_data;
                D_SQWC_OFFSET: d_sqwc <= reg_wr_data;
                D_RBSR_OFFSET: d_rbsr <= reg_wr_data;
                D_RBOR_OFFSET: d_rbor <= reg_wr_data;
                default: ;   // unknown offsets: write dropped (traced)
            endcase
        end
    end

    // Read mux (1-cycle latency to match the 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
                unique case (reg_offset)
                    D_CTRL_OFFSET: reg_rd_data <= d_ctrl;
                    D_STAT_OFFSET: reg_rd_data <= d_stat;
                    D_PCR_OFFSET:  reg_rd_data <= d_pcr;
                    D_SQWC_OFFSET: reg_rd_data <= d_sqwc;
                    D_RBSR_OFFSET: reg_rd_data <= d_rbsr;
                    D_RBOR_OFFSET: reg_rd_data <= d_rbor;
                    default:       reg_rd_data <= 32'd0;
                endcase
            end
        end
    end

    // ------------------------------------------------------------------
    // Trace — one event per cycle, write priority over read (consistent
    // with the rest of the stub ecosystem).
    // ------------------------------------------------------------------
    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ev_valid  <= 1'b0;
            ev_subsys <= SUBSYS_DMAC;
            ev_event  <= EV_WRITE;
            ev_arg0   <= 64'd0;
            ev_arg1   <= 64'd0;
            ev_arg2   <= 64'd0;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'd0;
        end else if (reg_wr_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_DMAC;
            ev_event  <= EV_WRITE;
            ev_arg0   <= {56'd0, reg_offset};
            ev_arg1   <= {32'd0, reg_wr_data};
            ev_arg2   <= 64'd0;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'h0000_0001;   // write
        end else if (reg_rd_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_DMAC;
            ev_event  <= EV_READ;
            ev_arg0   <= {56'd0, reg_offset};
            ev_arg1   <= 64'd0;
            ev_arg2   <= 64'd0;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'd0;
        end else begin
            ev_valid <= 1'b0;
        end
    end

endmodule : ee_dmac_ctrl_stub
