// OpenSTA, Static Timing Analyzer
// Copyright (c) 2026, Parallax Software, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// 
// The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software.
// 
// Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 
// This notice may not be removed or altered from any source distribution.

#include "GatedClk.hh"

#include "ClkNetwork.hh"
#include "FuncExpr.hh"
#include "Graph.hh"
#include "Liberty.hh"
#include "Mode.hh"
#include "Network.hh"
#include "PortDirection.hh"
#include "Sdc.hh"
#include "Search.hh"

namespace sta {

GatedClk::GatedClk(const StaState *sta) :
  StaState(sta)
{
}

bool
GatedClk::isGatedClkEnable(Vertex *vertex,
                           const Mode *mode) const
{
  bool is_gated_clk_enable;
  const Pin *clk_pin;
  LogicValue logic_active_value;
  isGatedClkEnable(vertex, mode,
                   is_gated_clk_enable, clk_pin, logic_active_value);
  return is_gated_clk_enable;
}

void
GatedClk::isGatedClkEnable(Vertex *enable_vertex,
                           const Mode *mode,
                           // Return values.
                           bool &is_gated_clk_enable,
                           const Pin *&clk_pin,
                           LogicValue &logic_active_value) const
{
  is_gated_clk_enable = false;
  const Pin *enable_pin = enable_vertex->pin();
  const Instance *inst = network_->instance(enable_pin);
  LibertyPort *enable_port = network_->libertyPort(enable_pin);
  EvalPred *eval_pred = search_->evalPred();
  ClkNetwork *clk_network = mode->clkNetwork();
  if (enable_port
      && !clk_network->isClock(enable_vertex)
      && enable_port->direction()->isInput()
      && eval_pred->searchFrom(enable_vertex, mode)) {
    const Sdc *sdc = mode->sdc();
    FuncExpr *func = nullptr;
    Vertex *gclk_vertex = nullptr;
    VertexOutEdgeIterator edge_iter(enable_vertex, graph_);
    while (edge_iter.hasNext()) {
      Edge *edge = edge_iter.next();
      gclk_vertex = edge->to(graph_);
      const Pin *gclk_pin = gclk_vertex->pin();
      if (edge->role() == TimingRole::combinational()
          && eval_pred->searchTo(gclk_vertex, mode)
          && eval_pred->searchThru(edge, mode)) {
        LibertyPort *gclk_port = network_->libertyPort(gclk_pin);
        func = gclk_port->function();
        if (gclk_port
            && func
            && clk_network->isClock(gclk_vertex)) {
          LibertyPortSet clk_ports = func->ports();
          for (LibertyPort *clk_port : clk_ports) {
            if (clk_port != enable_port)  {
              bool is_clk_gate = false;
              isClkGatingFunc(func, enable_port, clk_port,
                              is_clk_gate, logic_active_value);
              if (is_clk_gate) {
                clk_pin = network_->findPin(inst, clk_port);
                if (clk_pin
                    && !sdc->isDisableClockGatingCheck(clk_pin)
                    && clk_network->isClock(graph_->pinLoadVertex(clk_pin))) {
                  is_gated_clk_enable = true;
                  return;
                }
              }
            }
          }
        }
      }
    }
  }
}

PinSet
GatedClk::gatedClkEnables(Vertex *clk_vertex,
                          const Mode *mode)
{
  PinSet enable_pins(network_);
  const Pin *clk_pin = clk_vertex->pin();
  const Instance *inst = network_->instance(clk_pin);
  LibertyPort *clk_port = network_->libertyPort(clk_pin);
  EvalPred *eval_pred = search_->evalPred();
  if (clk_port
      && eval_pred->searchFrom(clk_vertex, mode)) {
    ClkNetwork *clk_network = mode->clkNetwork();
    FuncExpr *func = nullptr;
    Vertex *gclk_vertex = nullptr;
    VertexOutEdgeIterator edge_iter(clk_vertex, graph_);
    while (edge_iter.hasNext()) {
      Edge *edge = edge_iter.next();
      gclk_vertex = edge->to(graph_);
      if (edge->role() == TimingRole::combinational()
          && eval_pred->searchTo(gclk_vertex, mode)
          && eval_pred->searchThru(edge, mode)) {
        const Pin *gclk_pin = gclk_vertex->pin();
        LibertyPort *gclk_port = network_->libertyPort(gclk_pin);
        if (gclk_port) {
          func = gclk_port->function();
          if (func) {
            if (clk_network->isClock(gclk_vertex)) {
              LibertyPortSet enable_ports = func->ports();
              for (LibertyPort *enable_port : enable_ports) {
                if (enable_port != clk_port)  {
                  bool is_clk_gate;
                  LogicValue logic_value;
                  isClkGatingFunc(func, enable_port, clk_port,
                                  is_clk_gate, logic_value);
                  if (is_clk_gate) {
                    Pin *enable_pin = network_->findPin(inst, enable_port);
                    if (enable_pin
                        && !clk_network->isClock(graph_->pinLoadVertex(enable_pin))) {
                      enable_pins.insert(enable_pin);
                    }
                  }
                }
              }
            }
            break;
          }
        }
      }
    }
  }
  return enable_pins;
}

void
GatedClk::isClkGatingFunc(FuncExpr *func,
                          LibertyPort *enable_port,
                          LibertyPort *clk_port,
                          bool &is_clk_gate,
                          LogicValue &logic_value) const
{
  // The function should be in two-level SOP or POS form depending on "cost".
  // We need to apply literal cofactor if any input port is constant and
  // do "simple" logic minimization based on SOP and POS.
  while (func->op() == FuncExpr::Op::not_)
    func = func->left();
  if (func->op() == FuncExpr::Op::and_)
    logic_value = LogicValue::one;
  else if (func->op() == FuncExpr::Op::or_)
    logic_value = LogicValue::zero;
  else {
    is_clk_gate = false;
    return;
  }

  FuncExprSet funcs;
  functionClkOperands(func, func->left(), funcs);
  functionClkOperands(func, func->right(), funcs);

  bool need_gating_check = false;
  for (FuncExpr *expr : funcs) {
    if (expr->op() == FuncExpr::Op::not_) {
      if (expr->left()->op() == FuncExpr::Op::port
          && expr->left()->port() == clk_port) {
        need_gating_check = true;
        logic_value = (logic_value == LogicValue::one)
          ? LogicValue::zero
          : LogicValue::one;
      }
    }
    else {
      if (expr->op() == FuncExpr::Op::port
          && expr->port() == clk_port) {
        need_gating_check = true;
      }
    }
  }

  if (need_gating_check) {
    for (FuncExpr *expr : funcs) {
      LibertyPortSet ports = expr->ports();
      for (LibertyPort *port : ports) {
        if (port == enable_port) {
          is_clk_gate = true;
          return;
        }
      }
    }
  }
  is_clk_gate = false;
}

void
GatedClk::functionClkOperands(FuncExpr *root_expr,
                              FuncExpr *expr,
                              FuncExprSet &funcs) const
{
  if (expr->op() != root_expr->op())
    funcs.insert(expr);
  else {
    functionClkOperands(root_expr, expr->left(), funcs);
    functionClkOperands(root_expr, expr->right(), funcs);
  }
}

const RiseFall *
GatedClk::gatedClkActiveTrans(LogicValue active_value,
                              const MinMax *min_max) const
{
  const RiseFall *leading_rf;
  switch (active_value) {
  case LogicValue::one:
  case LogicValue::unknown:
    leading_rf = RiseFall::rise();
    break;
  case LogicValue::zero:
    leading_rf = RiseFall::fall();
    break;
  default:
    criticalError(249, "illegal gated clock active value");
    leading_rf = RiseFall::rise();
    break;
  }
  if (min_max == MinMax::max())
    return leading_rf;
  else
    return leading_rf->opposite();
}

} // namespace sta
