#!/usr/bin/env python3
"""
Advanced SystemVerilog Power Management Module

This module provides sophisticated power management logic generation for PCIe devices,
including D-states, L-states, clock gating, and ASPM support.

Advanced Power Management feature for the PCILeechFWGenerator project.
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional


class PowerState(Enum):
    """PCIe power states."""

    D0 = "D0"  # Fully operational
    D1 = "D1"  # Intermediate power state
    D2 = "D2"  # Intermediate power state
    D3_HOT = "D3_HOT"  # Software power down
    D3_COLD = "D3_COLD"  # Hardware power down


class LinkState(Enum):
    """PCIe link power states."""

    L0 = "L0"  # Active state
    L0S = "L0s"  # Standby state
    L1 = "L1"  # Low power standby
    L2 = "L2"  # Auxiliary power
    L3 = "L3"  # Off state


@dataclass
class PowerManagementConfig:
    """Configuration for power management features."""

    # Power state support
    supported_power_states: List[PowerState] = field(
        default_factory=lambda: [PowerState.D0, PowerState.D1, PowerState.D3_HOT]
    )
    supported_link_states: List[LinkState] = field(
        default_factory=lambda: [LinkState.L0, LinkState.L0S, LinkState.L1]
    )

    # Power transition timing (in clock cycles)
    d0_to_d1_cycles: int = 100
    d1_to_d0_cycles: int = 50
    d0_to_d3_cycles: int = 1000
    d3_to_d0_cycles: int = 10000

    # Link state transition timing
    l0_to_l0s_cycles: int = 10
    l0s_to_l0_cycles: int = 20
    l0_to_l1_cycles: int = 100
    l1_to_l0_cycles: int = 200

    # Power management features
    enable_clock_gating: bool = True
    enable_power_domains: bool = True
    enable_aspm: bool = True  # Active State Power Management
    enable_wake_on_lan: bool = False

    # Power consumption estimates (mW)
    d0_power_mw: float = 1000.0
    d1_power_mw: float = 500.0
    d3_power_mw: float = 10.0


class PowerManagementGenerator:
    """Generator for advanced power management SystemVerilog logic."""

    def __init__(self, config: Optional[PowerManagementConfig] = None):
        """Initialize the power management generator."""
        self.config = config or PowerManagementConfig()

    def generate_power_declarations(self) -> str:
        """Generate power management signal declarations."""

        declarations = []

        declarations.append("    // Power Management Signals")
        declarations.append("    logic [1:0] current_power_state = 2'b00;  // D0 state")
        declarations.append("    logic [1:0] current_link_state = 2'b00;   // L0 state")
        declarations.append("    logic [15:0] power_transition_timer = 16'h0;")
        declarations.append("    logic power_state_changing = 1'b0;")
        declarations.append("    logic [15:0] link_transition_timer = 16'h0;")
        declarations.append("    logic link_state_changing = 1'b0;")

        if self.config.enable_clock_gating:
            declarations.append("    logic gated_clk;")
            declarations.append("    logic clock_enable;")

        if self.config.enable_power_domains:
            declarations.append("    logic core_power_enable = 1'b1;")
            declarations.append("    logic io_power_enable = 1'b1;")
            declarations.append("    logic memory_power_enable = 1'b1;")

        declarations.append("")

        return "\n".join(declarations)

    def generate_power_state_machine(self) -> str:
        """Generate the main power state machine logic."""

        if not self.config.supported_power_states:
            return "    // Power management disabled\n"

        power_logic = []

        power_logic.append("    // Advanced Power Management State Machine")
        power_logic.append("    typedef enum logic [2:0] {")
        power_logic.append("        PM_D0_ACTIVE    = 3'b000,")
        power_logic.append("        PM_D0_TO_D1     = 3'b001,")
        power_logic.append("        PM_D1_STANDBY   = 3'b010,")
        power_logic.append("        PM_D1_TO_D0     = 3'b011,")
        power_logic.append("        PM_D0_TO_D3     = 3'b100,")
        power_logic.append("        PM_D3_SUSPEND   = 3'b101,")
        power_logic.append("        PM_D3_TO_D0     = 3'b110,")
        power_logic.append("        PM_ERROR        = 3'b111")
        power_logic.append("    } power_state_t;")
        power_logic.append("")
        power_logic.append("    power_state_t pm_state = PM_D0_ACTIVE;")
        power_logic.append("    power_state_t pm_next_state;")
        power_logic.append("")

        # Power state transition logic
        power_logic.append("    // Power state transition logic")
        power_logic.append("    always_ff @(posedge clk or negedge reset_n) begin")
        power_logic.append("        if (!reset_n) begin")
        power_logic.append("            pm_state <= PM_D0_ACTIVE;")
        power_logic.append("            power_transition_timer <= 16'h0;")
        power_logic.append("            power_state_changing <= 1'b0;")
        power_logic.append("        end else begin")
        power_logic.append("            pm_state <= pm_next_state;")
        power_logic.append("            ")
        power_logic.append("            if (power_state_changing) begin")
        power_logic.append(
            "                power_transition_timer <= power_transition_timer + 1;"
        )
        power_logic.append("            end else begin")
        power_logic.append("                power_transition_timer <= 16'h0;")
        power_logic.append("            end")
        power_logic.append("        end")
        power_logic.append("    end")
        power_logic.append("")

        # Power state combinational logic
        power_logic.append("    // Power state combinational logic")
        power_logic.append("    always_comb begin")
        power_logic.append("        pm_next_state = pm_state;")
        power_logic.append("        power_state_changing = 1'b0;")
        power_logic.append("        ")
        power_logic.append("        case (pm_state)")
        power_logic.append("            PM_D0_ACTIVE: begin")
        power_logic.append("                if (power_state_req == 2'b01) begin")
        power_logic.append("                    pm_next_state = PM_D0_TO_D1;")
        power_logic.append("                    power_state_changing = 1'b1;")
        power_logic.append(
            "                end else if (power_state_req == 2'b11) begin"
        )
        power_logic.append("                    pm_next_state = PM_D0_TO_D3;")
        power_logic.append("                    power_state_changing = 1'b1;")
        power_logic.append("                end")
        power_logic.append("            end")
        power_logic.append("            ")
        power_logic.append("            PM_D0_TO_D1: begin")
        power_logic.append("                power_state_changing = 1'b1;")
        power_logic.append(
            f"                if (power_transition_timer >= {self.config.d0_to_d1_cycles}) begin"
        )
        power_logic.append("                    pm_next_state = PM_D1_STANDBY;")
        power_logic.append("                end")
        power_logic.append("            end")
        power_logic.append("            ")
        power_logic.append("            PM_D1_STANDBY: begin")
        power_logic.append("                if (power_state_req == 2'b00) begin")
        power_logic.append("                    pm_next_state = PM_D1_TO_D0;")
        power_logic.append("                    power_state_changing = 1'b1;")
        power_logic.append("                end")
        power_logic.append("            end")
        power_logic.append("            ")
        power_logic.append("            PM_D1_TO_D0: begin")
        power_logic.append("                power_state_changing = 1'b1;")
        power_logic.append(
            f"                if (power_transition_timer >= {self.config.d1_to_d0_cycles}) begin"
        )
        power_logic.append("                    pm_next_state = PM_D0_ACTIVE;")
        power_logic.append("                end")
        power_logic.append("            end")
        power_logic.append("            ")
        power_logic.append("            PM_D0_TO_D3: begin")
        power_logic.append("                power_state_changing = 1'b1;")
        power_logic.append(
            f"                if (power_transition_timer >= {self.config.d0_to_d3_cycles}) begin"
        )
        power_logic.append("                    pm_next_state = PM_D3_SUSPEND;")
        power_logic.append("                end")
        power_logic.append("            end")
        power_logic.append("            ")
        power_logic.append("            PM_D3_SUSPEND: begin")
        power_logic.append("                if (power_state_req == 2'b00) begin")
        power_logic.append("                    pm_next_state = PM_D3_TO_D0;")
        power_logic.append("                    power_state_changing = 1'b1;")
        power_logic.append("                end")
        power_logic.append("            end")
        power_logic.append("            ")
        power_logic.append("            PM_D3_TO_D0: begin")
        power_logic.append("                power_state_changing = 1'b1;")
        power_logic.append(
            f"                if (power_transition_timer >= {self.config.d3_to_d0_cycles}) begin"
        )
        power_logic.append("                    pm_next_state = PM_D0_ACTIVE;")
        power_logic.append("                end")
        power_logic.append("            end")
        power_logic.append("            ")
        power_logic.append("            default: pm_next_state = PM_D0_ACTIVE;")
        power_logic.append("        endcase")
        power_logic.append("    end")
        power_logic.append("")

        return "\n".join(power_logic)

    def generate_link_state_machine(self) -> str:
        """Generate link power state management logic."""

        if not self.config.enable_aspm:
            return "    // Link power management disabled\n"

        link_logic = []

        link_logic.append("    // Link Power State Management (ASPM)")
        link_logic.append("    typedef enum logic [1:0] {")
        link_logic.append("        LINK_L0  = 2'b00,")
        link_logic.append("        LINK_L0S = 2'b01,")
        link_logic.append("        LINK_L1  = 2'b10,")
        link_logic.append("        LINK_L2  = 2'b11")
        link_logic.append("    } link_state_t;")
        link_logic.append("")
        link_logic.append("    link_state_t link_state = LINK_L0;")
        link_logic.append("    logic [15:0] link_idle_counter = 16'h0;")
        link_logic.append("")

        # Link state transition logic
        link_logic.append("    // Link state transition logic")
        link_logic.append("    always_ff @(posedge clk or negedge reset_n) begin")
        link_logic.append("        if (!reset_n) begin")
        link_logic.append("            link_state <= LINK_L0;")
        link_logic.append("            link_idle_counter <= 16'h0;")
        link_logic.append("            link_transition_timer <= 16'h0;")
        link_logic.append("        end else begin")
        link_logic.append("            case (link_state)")
        link_logic.append("                LINK_L0: begin")
        link_logic.append("                    if (bar_wr_en || bar_rd_en) begin")
        link_logic.append("                        link_idle_counter <= 16'h0;")
        link_logic.append("                    end else begin")
        link_logic.append(
            "                        link_idle_counter <= link_idle_counter + 1;"
        )
        link_logic.append(
            f"                        if (link_idle_counter >= {self.config.l0_to_l0s_cycles}) begin"
        )
        link_logic.append("                            link_state <= LINK_L0S;")
        link_logic.append("                            link_transition_timer <= 16'h0;")
        link_logic.append("                        end")
        link_logic.append("                    end")
        link_logic.append("                end")
        link_logic.append("                ")
        link_logic.append("                LINK_L0S: begin")
        link_logic.append("                    if (bar_wr_en || bar_rd_en) begin")
        link_logic.append("                        link_state <= LINK_L0;")
        link_logic.append("                        link_idle_counter <= 16'h0;")
        link_logic.append("                    end else begin")
        link_logic.append(
            "                        link_transition_timer <= link_transition_timer + 1;"
        )
        link_logic.append(
            f"                        if (link_transition_timer >= {self.config.l0_to_l1_cycles}) begin"
        )
        link_logic.append("                            link_state <= LINK_L1;")
        link_logic.append("                        end")
        link_logic.append("                    end")
        link_logic.append("                end")
        link_logic.append("                ")
        link_logic.append("                LINK_L1: begin")
        link_logic.append("                    if (bar_wr_en || bar_rd_en) begin")
        link_logic.append("                        link_state <= LINK_L0;")
        link_logic.append("                        link_idle_counter <= 16'h0;")
        link_logic.append("                        link_transition_timer <= 16'h0;")
        link_logic.append("                    end")
        link_logic.append("                end")
        link_logic.append("                ")
        link_logic.append("                default: link_state <= LINK_L0;")
        link_logic.append("            endcase")
        link_logic.append("        end")
        link_logic.append("    end")
        link_logic.append("")

        return "\n".join(link_logic)

    def generate_clock_gating(self) -> str:
        """Generate clock gating logic for power savings."""

        if not self.config.enable_clock_gating:
            return "    // Clock gating disabled\n"

        clock_logic = []

        clock_logic.append("    // Clock Gating for Power Management")
        clock_logic.append("    always_comb begin")
        clock_logic.append("        case (pm_state)")
        clock_logic.append("            PM_D0_ACTIVE: clock_enable = 1'b1;")
        clock_logic.append(
            "            PM_D1_STANDBY: clock_enable = link_state == LINK_L0;"
        )
        clock_logic.append("            PM_D3_SUSPEND: clock_enable = 1'b0;")
        clock_logic.append("            default: clock_enable = power_state_changing;")
        clock_logic.append("        endcase")
        clock_logic.append("    end")
        clock_logic.append("    ")
        clock_logic.append("    assign gated_clk = clk & clock_enable;")
        clock_logic.append("")

        return "\n".join(clock_logic)

    def generate_power_outputs(self) -> str:
        """Generate power management output assignments."""

        outputs = []

        outputs.append("    // Power Management Outputs")
        outputs.append("    always_comb begin")
        outputs.append("        case (pm_state)")
        outputs.append(
            "            PM_D0_ACTIVE, PM_D1_TO_D0, PM_D3_TO_D0: current_power_state = 2'b00;  // D0"
        )
        outputs.append(
            "            PM_D0_TO_D1, PM_D1_STANDBY: current_power_state = 2'b01;  // D1"
        )
        outputs.append(
            "            PM_D0_TO_D3, PM_D3_SUSPEND: current_power_state = 2'b11;  // D3"
        )
        outputs.append("            default: current_power_state = 2'b00;")
        outputs.append("        endcase")
        outputs.append("    end")
        outputs.append("    ")
        outputs.append("    assign current_link_state = link_state;")
        outputs.append("    assign power_state_ack = current_power_state;")
        outputs.append("    assign link_state_ack = current_link_state;")
        outputs.append("")

        return "\n".join(outputs)

    def generate_complete_power_management(self) -> str:
        """Generate complete power management logic."""

        components = [
            self.generate_power_declarations(),
            self.generate_power_state_machine(),
            self.generate_link_state_machine(),
            self.generate_clock_gating(),
            self.generate_power_outputs(),
        ]

        return "\n".join(components)
