# 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.

namespace eval sta {

################################################################
#  
# Non-SDC commands
#
################################################################

define_cmd_args "define_scene" {name -mode mode_name\
                                  -liberty liberty_files \
                                  | -liberty_min liberty_min_files -liberty_max liberty_max_files\
                                  [-spef spef_file | -spef_min spef_min_file -spef_max spef_max_file]}

proc define_scene { args } {
  parse_key_args "define_scene" args \
    keys {-mode -liberty -liberty_min -liberty_max \
          -spef -spef_min -spef_max} \
  flags {}

  check_argc_eq1 "define_scene" $args
  set name [lindex $args 0]

  if { [info exists keys(-mode)] } {
    set mode_name $keys(-mode)
  } else {
    set mode_name [cmd_mode_name]
  }

  set liberty_min_files {}
  set liberty_max_files {}
  if { [info exists keys(-liberty)] } {
    set liberty_files $keys(-liberty)
    set liberty_min_files $liberty_files
    set liberty_max_files $liberty_files
  } else {
    if { [info exists keys(-liberty_min)] && [info exists keys(-liberty_max)] } {
      set liberty_min_files $keys(-liberty_min)
      set liberty_max_files $keys(-liberty_max)
    } else {
      sta_error 483 "-liberty or -liberty_min and -liberty_max are required arguments."
    }
  }

  set spef_min_file ""
  set spef_max_file ""
  if { [info exists keys(-spef)] } {
    set spef_file $keys(-spef)
    set spef_min_file $spef_file
    set spef_max_file $spef_file
  } elseif { [info exists keys(-spef_min)] && [info exists keys(-spef_max)] } {
    set spef_min_file $keys(-spef_min)
    set spef_max_file $keys(-spef_max)
  } elseif { [info exists keys(-spef_min)] ||[info exists keys(-spef_max)] } {
    sta_error 484 "-spef_min and -spef_max are required arguments."
  }

  define_scene_cmd $name $mode_name \
    $liberty_min_files $liberty_max_files \
    $spef_min_file $spef_max_file
}

# deprecated 11/22/2025
define_cmd_args "define_corners" { corner1 [corner2]... }

proc define_corners { args } {
  if { [get_libs -quiet *] != {} } {
    sta_error 482 "define_corners must be called before read_liberty."
  }
  if { [llength $args] == 0 } { 
    sta_error 577 "define_corners must define at least one corner."
  }
  define_scenes_cmd $args
}

################################################################

define_cmd_args "set_scene" {scene_name}

proc set_scene { args } {
  check_argc_eq1 "set_scene" $args
  set_cmd_scene [lindex $args 0]
}

################################################################

define_cmd_args "get_scenes" {[-modes mode_names] scene_names}

proc get_scenes { args } {
  parse_key_args "get_scenes" args keys {-modes} flags {}
  check_argc_eq0or1 "get_scenes" $args

  if { [llength $args] == 0 } {
    set scene_name "*"
  } else {
    set scene_name [lindex $args 0]
  }
  set mode_names {}
  if { [info exists keys(-modes)] } {
    set mode_names $keys(-modes)
    return [find_mode_scenes_matching $scene_name $mode_names]
  } else {
    return [find_scenes_matching $scene_name]
  }
}

################################################################

define_cmd_args "get_modes" {mode_name}

proc get_modes { args } {
  return [find_modes [lindex $args 0]]
}

################################################################

define_cmd_args "set_mode" {mode_name}

proc set_mode { args } {
  check_argc_eq1 "set_mode" $args
  set_cmd_mode [lindex $args 0]
}

################################################################

define_cmd_args "get_fanin" \
  {-to sink_list [-flat] [-only_cells] [-startpoints_only]\
     [-levels level_count] [-pin_levels pin_count]\
     [-trace_arcs timing|enabled|all]}

proc get_fanin { args } {
  parse_key_args "get_fanin" args \
    keys {-to -levels -pin_levels -trace_arcs} \
    flags {-flat -only_cells -startpoints_only}
  if { [llength $args] != 0 } {
    cmd_usage_error "get_fanin"
  }
  if { ![info exists keys(-to)] } {
    cmd_usage_error "get_fanin"
  }
  parse_port_pin_net_arg $keys(-to) pins nets
  foreach net $nets {
    set net_pins [net_pins $net]
    if { $net_pins != {} } {
      lappend pins $net_pins
    } else {
      sta_warn 541 "No load pins connected to net [get_full_name $net]."
    }
  }
  set flat [info exists flags(-flat)]
  set only_insts [info exists flags(-only_cells)]
  set startpoints_only [info exists flags(-startpoints_only)]
  set inst_levels 0
  if { [info exists keys(-levels)] } {
    set inst_levels $keys(-levels)
  }
  set pin_levels 0
  if { [info exists keys(-pin_levels)] } {
    set pin_levels $keys(-pin_levels)
  }

  set thru_disabled 0
  set thru_constants 0
  if { [info exists keys(-trace_arcs)] } {
    set trace_arcs $keys(-trace_arcs)
    if { $trace_arcs == "timing" } {
      set thru_disabled 0
      set thru_constants 0
    } elseif { $trace_arcs == "enabled" } {
      set thru_disabled 0
      set thru_constants 0
    } elseif { $trace_arcs == "all" } {
      set thru_disabled 1
      set thru_constants 1
    } else {
      cmd_usage_error "get_fanin"
    }
  }
  if { $only_insts } {
    return [find_fanin_insts $pins $flat $startpoints_only \
              $inst_levels $pin_levels $thru_disabled $thru_constants]
 } else {
    return [find_fanin_pins $pins $flat $startpoints_only \
              $inst_levels $pin_levels $thru_disabled $thru_constants]
  }
}

################################################################

define_cmd_args "get_fanout" \
  {-from source_list [-flat] [-only_cells] [-endpoints_only]\
     [-levels level_count] [-pin_levels pin_count]\
     [-trace_arcs timing|enabled|all]}

proc get_fanout { args } {
  parse_key_args "get_fanout" args \
    keys {-from -levels -pin_levels -trace_arcs} \
    flags {-flat -only_cells -endpoints_only}
  if { [llength $args] != 0 } {
    cmd_usage_error "get_fanout"
  }
  parse_port_pin_net_arg $keys(-from) pins nets
  foreach net $nets {
    lappend pins [net_load_pins $net]
  }
  set flat [info exists flags(-flat)]
  set only_insts [info exists flags(-only_cells)]
  set endpoints_only [info exists flags(-endpoints_only)]

  set inst_levels 0
  if { [info exists keys(-levels)] } {
    set inst_levels $keys(-levels)
  }

  set pin_levels 0
  if { [info exists keys(-pin_levels)] } {
    set pin_levels $keys(-pin_levels)
  }

  set thru_disabled 0
  set thru_constants 0
  if { [info exists keys(-trace_arcs)] } {
    set trace_arcs $keys(-trace_arcs)
    if { $trace_arcs == "timing" } {
      set thru_disabled 0
      set thru_constants 0
    } elseif { $trace_arcs == "enabled" } {
      set thru_disabled 0
      set thru_constants 0
    } elseif { $trace_arcs == "all" } {
      set thru_disabled 1
      set thru_constants 1
    } else {
      cmd_usage_error "get_fanout"
    }
  }
  if { $only_insts } {
    return [find_fanout_insts $pins $flat $endpoints_only \
              $inst_levels $pin_levels $thru_disabled $thru_constants]
  } else {
    return [find_fanout_pins $pins $flat $endpoints_only \
              $inst_levels $pin_levels $thru_disabled $thru_constants]
  }
}

################################################################

define_cmd_args "get_timing_edges" \
  {[-from from_pin] [-to to_pin] [-of_objects objects] [-filter expr]}

proc get_timing_edges { args } {
  return [get_timing_edges_cmd "get_timing_edges" $args]
}

proc get_timing_edges_cmd { cmd cmd_args } {
  parse_key_args $cmd cmd_args \
    keys {-from -to -of_objects -filter} flags {}
  check_argc_eq0 $cmd $cmd_args

  set arcs {}
  if { [info exists keys(-of_objects)] } {
    if { [info exists keys(-from)] \
           || [info exists keys(-from)] } {
      sta_error 540 "-from/-to arguments not supported with -of_objects."
    }
    set arcs [get_timing_arcs_objects $keys(-of_objects)]
  } elseif { [info exists keys(-from)] \
           && [info exists keys(-to)] } {
    set arcs [get_timing_arcs_from_to \
              [get_port_pin_error "from" $keys(-from)] \
              [get_port_pin_error "to" $keys(-to)]]
  } elseif { [info exists keys(-from)] } {
    set arcs [get_timing_arcs_from $keys(-from)]
  } elseif { [info exists keys(-to)] } {
    set arcs [get_timing_arcs_to $keys(-to)]
  } else {
    cmd_usage_error $cmd
  }
  if [info exists keys(-filter)] {
    set arcs [filter_timing_arcs $keys(-filter) $arcs]
  }
  return $arcs
}

proc get_timing_arcs_objects { object_arg } {
  parse_libcell_inst_arg $object_arg libcells insts
  if { $insts != {} } {
    set edges {}
    foreach inst $insts {
      lappend edges [instance_edges $inst]
    }
    return $edges
  } elseif { $libcells != {} } {
    set arc_sets {}
    foreach libcell $libcells {
      lappend arc_sets [$libcell timing_arc_sets]
    }
    return $arc_sets
  }
}

proc instance_edges { inst } {
  set edges {}
  set pin_iter [$inst pin_iterator]
  while {[$pin_iter has_next]} {
    set pin [$pin_iter next]
    foreach vertex [$pin vertices] {
      set edge_iter [$vertex out_edge_iterator]
      while {[$edge_iter has_next]} {
        set edge [$edge_iter next]
        if { [$edge role] != "wire" } {
          lappend edges $edge
        }
      }
      $edge_iter finish
    }
  }
  $pin_iter finish
  return $edges
}

proc get_timing_arcs_from_to { from_pin_arg to_pin_arg } {
  set edges {}
  set from_pin [get_port_pin_error "from" $from_pin_arg]
  set to_pin [get_port_pin_error "to" $to_pin_arg]
  foreach from_vertex [$from_pin vertices] {
    foreach to_vertex [$to_pin vertices] {
      set edge_iter [$from_vertex out_edge_iterator]
      while {[$edge_iter has_next]} {
        set edge [$edge_iter next]
        if { [$edge to] == $to_vertex } {
          lappend edges $edge
        }
      }
      $edge_iter finish
    }
  }
  return $edges
}

proc get_timing_arcs_from { from_pin_arg } {
  set from_pin [get_port_pin_error "from" $from_pin_arg]
  set edges {}
  foreach from_vertex [$from_pin vertices] {
    set edge_iter [$from_vertex out_edge_iterator]
    while {[$edge_iter has_next]} {
      set edge [$edge_iter next]
      lappend edges $edge
    }
    $edge_iter finish
  }
  return $edges
}

proc get_timing_arcs_to { to_pin_arg } {
  set to_pin [get_port_pin_error "to" $to_pin_arg]
  set edges {}
  foreach to_vertex [$to_pin vertices] {
    set edge_iter [$to_vertex in_edge_iterator]
    while {[$edge_iter has_next]} {
      set edge [$edge_iter next]
      lappend edges $edge
    }
    $edge_iter finish
  }
  return $edges
}

################################################################

define_cmd_args "report_clock_properties" {[clocks]}

proc_redirect report_clock_properties {
  check_argc_eq0or1 "report_clock_properties" $args
  update_generated_clks
  report_line "Clock                   Period          Waveform"
  report_line "----------------------------------------------------"
  if { [llength $args] == 0 } {
    foreach clk [all_clocks] {
      report_clock1 $clk
    }
  } else {
    foreach clk [get_clocks_warn "clock_name" [lindex $args 0]] {
      report_clock1 $clk
    }
  }
}

proc report_clock1 { clk } {
  global sta_report_default_digits

  if { [$clk waveform_valid] } {
    set digits $sta_report_default_digits
    set waveform [$clk waveform]
    if { $waveform == {} } {
      set wave "                    "
    } else {
      set wave ""
      foreach edge $waveform {
        set wave "$wave[format %10s [format_time $edge $digits]]"
      }
    }
    if {[$clk is_generated]} {
      set generated " (generated)"
    } else {
      set generated ""
    }
    report_line "[format %-20s [get_name $clk]][format %10s [format_time [$clk period] $digits]]  $wave$generated"
  }
}

# sta namespace end.
}
