{{ header | safe }}

module pmcsr_stub #(
    // Fabric clock frequency (Hz). Used only for the fake transition timer.
    parameter int CLK_HZ = {{ clk_hz }},
    // Maximum transition time per PCIe spec ≤ 10 ms; choose a safe sub‑spec.
    parameter int TR_NS  = {{ tr_ns }}           // {{ timeout_ms }} ms
) (
    input  logic        clk,
    input  logic        reset_n,

    // ── Config‑space CSR handshake (boilerplate wrapper drives these) ────────
    input  logic        pmcsr_wr,              // write strobe (one clk pulse)
    input  logic [15:0] pmcsr_wdata,           // PMCSR bits [15:0]
    output logic [15:0] pmcsr_rdata,           // read‑back value

    // ── Optional external wake event input ──────────────────────────────────
    input  logic        wake_evt,              // pulse => wake request
    // PME TurnOff/To_Ack handshake (from/to Root Complex)
    input  logic        pme_turnoff,           // RC requests D3hot entry
    output logic        pme_to_ack,            // device acks D3hot readiness
    // Aux power presence (1=aux present, 0=no aux -> D3cold allowed)
    input  logic        aux_pwr_present,
    // Sideband WAKE# (active-low) for D3cold wake
    output logic        wake_n,
    // PME# request toward PCIe hard-macro/message generator
    output logic        pme_req               // to PCIe hard‑macro PME# pin
);

    {%- set _enable_pme = enable_pme|default(false) %}

    // ───────────────────────── Internal registers ───────────────────────────
    logic [1:0] pwr_state;        // 00=D0, 11=D3hot (PMCSR view)
    logic       in_d3cold;        // explicit D3cold tracking
    logic       pme_en;
    logic       pme_stat;

    // Per-transition timers derived from TR_NS (simple model)
    // You may tune these ratios if needed; keep sub-spec safe values.
    localparam int D0_TO_D3_NS        = (TR_NS*3)/4;  // enter low-power faster
    localparam int D3_TO_D0_NS        = (TR_NS*1);
    localparam int D3HOT_TO_D3COLD_NS = (TR_NS*1);
    localparam int WAKE_PULSE_NS      = (TR_NS/8);

    localparam int D0_TO_D3_CLKS        = (D0_TO_D3_NS        > 0) ? (D0_TO_D3_NS        * CLK_HZ / 1_000_000_000) : 1;
    localparam int D3_TO_D0_CLKS        = (D3_TO_D0_NS        > 0) ? (D3_TO_D0_NS        * CLK_HZ / 1_000_000_000) : 1;
    localparam int D3HOT_TO_D3COLD_CLKS = (D3HOT_TO_D3COLD_NS > 0) ? (D3HOT_TO_D3COLD_NS * CLK_HZ / 1_000_000_000) : 1;
    localparam int WAKE_PULSE_CLKS      = (WAKE_PULSE_NS      > 0) ? (WAKE_PULSE_NS      * CLK_HZ / 1_000_000_000) : 1;

    localparam int MAX_TR_CLKS = (D3_TO_D0_CLKS > D0_TO_D3_CLKS) ?
                                    ((D3_TO_D0_CLKS > D3HOT_TO_D3COLD_CLKS) ? D3_TO_D0_CLKS : D3HOT_TO_D3COLD_CLKS) :
                                    ((D0_TO_D3_CLKS > D3HOT_TO_D3COLD_CLKS) ? D0_TO_D3_CLKS : D3HOT_TO_D3COLD_CLKS);
    typedef logic [$clog2(MAX_TR_CLKS)-1:0] t_tr_cnt;
    t_tr_cnt tr_cnt;
    t_tr_cnt wake_cnt;
    logic    wake_pulse_active;
    logic    pending_turnoff_ack;

    // ────────────────────────── State machine ───────────────────────────────
    always_ff @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            pwr_state <= 2'b00;   // D0 on reset
            pme_en    <= 1'b0;
            pme_stat  <= 1'b0;
            tr_cnt    <= '0;
            wake_cnt  <= '0;
            in_d3cold <= 1'b0;
            pme_to_ack<= 1'b0;
            wake_pulse_active <= 1'b0;
            pending_turnoff_ack <= 1'b0;
        end else begin
            // Handle config‑space writes
            if (pmcsr_wr) begin
                // Power state request & PME enable latch
                pme_en    <= pmcsr_wdata[15];
                // Clear PME_Status when driver writes a 1 to bit 14 per spec
                if (pmcsr_wdata[14]) begin
                    pme_stat <= 1'b0;
                    // Deassert WAKE# when PME status is cleared
                    wake_pulse_active <= 1'b0;
                end
                // If state request changed, start countdown
                if (pmcsr_wdata[1:0] != pwr_state) begin
                    // Only D0<->D3hot are modeled here; D3cold not writable
                    unique case (pmcsr_wdata[1:0])
                        2'b00: begin // request D0
                            pwr_state <= 2'b00;
                            tr_cnt    <= D3_TO_D0_CLKS[$bits(tr_cnt)-1:0];
                            in_d3cold <= 1'b0; // powered back up cancels D3cold
                        end
                        2'b11: begin // request D3hot
                            pwr_state <= 2'b11;
                            tr_cnt    <= D0_TO_D3_CLKS[$bits(tr_cnt)-1:0];
                        end
                        default: begin
                            // Ignore unsupported D1/D2 requests; remain
                        end
                    endcase
                end
            end

            // Transition countdown logic
            if (tr_cnt != 0) begin
                tr_cnt <= tr_cnt - 1;
            end

            // PME_TurnOff handling: move to D3hot then ack once stable
            if (pme_turnoff) begin
                if (pwr_state != 2'b11) begin
                    // request D3hot; start timer if not already running
                    pwr_state <= 2'b11;
                    if (tr_cnt == '0) tr_cnt <= D0_TO_D3_CLKS[$bits(tr_cnt)-1:0];
                    pending_turnoff_ack <= 1'b1;
                end else if (tr_cnt == '0) begin
                    // Stable in D3hot -> send To_Ack once
                    pme_to_ack <= 1'b1;
                    pending_turnoff_ack <= 1'b0;
                end
            end else begin
                pme_to_ack <= 1'b0; // pulse behavior
            end

            // If aux power is removed while in D3hot, transition to D3cold
            if ((pwr_state == 2'b11) && !aux_pwr_present && !in_d3cold) begin
                if (tr_cnt == '0) begin
                    tr_cnt <= D3HOT_TO_D3COLD_CLKS[$bits(tr_cnt)-1:0];
                end else if (tr_cnt == '0) begin
                    // will be taken next cycle; keep logic simple
                end
            end
            if ((pwr_state == 2'b11) && !aux_pwr_present && (tr_cnt == '0)) begin
                in_d3cold <= 1'b1;
            end

            // WAKE event handling
            // In D3hot: PME message path is valid; set PME_Status (pme_req)
            // In D3cold: assert WAKE# (active-low) for a short pulse and set PME_Status
            if (wake_evt && pme_en) begin
                pme_stat <= 1'b1;
                if (in_d3cold) begin
                    wake_pulse_active <= 1'b1;
                    wake_cnt <= WAKE_PULSE_CLKS[$bits(wake_cnt)-1:0];
                end
            end

            // Wake pulse countdown
            if (wake_pulse_active) begin
                if (wake_cnt != 0) begin
                    wake_cnt <= wake_cnt - 1;
                end else begin
                    // keep WAKE# asserted while PME_Status uncleared? choose pulse
                    wake_pulse_active <= 1'b0; // pulse-style; status remains until cleared
                end
            end
        end
    end

    // ───────────────────────── Outputs ──────────────────────────────────────
    assign pmcsr_rdata = { pme_en,         // bit 15 (project-local mapping)
                           pme_stat,       // bit 14 (project-local mapping)
                           13'd0,          // bits 13:2 reserved/RO
                           pwr_state };    // bits 1:0

{%- if _enable_pme %}
    // Pulse PME# (active‑low in PCIe) for one cycle when PME_Status is set.
    // Disable PME# while in D3cold; WAKE# is used instead.
    assign pme_req = pme_stat & ~in_d3cold;
{%- else %}
    // PME disabled
    assign pme_req = 1'b0;
{%- endif %}

    // WAKE# is active low; drive low when in D3cold and wake pulse active.
    assign wake_n = ~(in_d3cold && wake_pulse_active);

endmodule