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

# Graph utilities

namespace eval sta {

define_cmd_args "report_edges" {[-from from_pin] [-to to_pin]\
                                  [-digits digits] [-report_variance]}

proc report_edges { args } {
  global sta_report_default_digits

  parse_key_args "report_edges" args keys {-from -to -digits} flags {-report_variance}
  check_argc_eq0 "report_edges" $args

  if [info exists keys(-digits)] {
    set digits $keys(-digits)
    check_positive_integer "-digits" $digits
  } else {
    set digits $sta_report_default_digits
  }

  set report_variance [info exists flags(-report_variance)]

  if { [info exists keys(-from)] && [info exists keys(-to)] } {
    set from_pin [get_port_pin_error "from_pin" $keys(-from)]
    set to_pin [get_port_pin_error "to_pin" $keys(-to)]
    foreach from_vertex [$from_pin vertices] {
      foreach to_vertex [$to_pin vertices] {
        report_edges_between_ $from_vertex $to_vertex $digits $report_variance
      }
    }
  } elseif [info exists keys(-from)] {
    set from_pin [get_port_pin_error "from_pin" $keys(-from)]
    foreach from_vertex [$from_pin vertices] {
      report_edges_ $from_vertex out_edge_iterator \
        vertex_port_name vertex_path_name $digits $report_variance
    }
  } elseif [info exists keys(-to)] {
    set to_pin [get_port_pin_error "to_pin" $keys(-to)]
    foreach to_vertex [$to_pin vertices] {
      report_edges_ $to_vertex in_edge_iterator \
        vertex_path_name vertex_port_name $digits $report_variance
    }
  }
}

proc report_edges_between_ { from_vertex to_vertex digits report_variance } {
  set iter [$from_vertex out_edge_iterator]
  while {[$iter has_next]} {
    set edge [$iter next]
    if { [$edge to] == $to_vertex } {
      if { [$edge role] == "wire" } {
        report_edge_ $edge vertex_path_name vertex_path_name $digits $report_variance
      } else {
        report_edge_ $edge vertex_port_name vertex_port_name $digits $report_variance
      }
    }
  }
  $iter finish
}

proc report_edges_ { vertex iter_proc wire_from_name_proc wire_to_name_proc \
                       digits report_variance } {
  # First report edges internal to the device.
  set device_header 0
  set iter [$vertex $iter_proc]
  while {[$iter has_next]} {
    set edge [$iter next]
    if { [$edge role] != "wire" } {
      if { !$device_header } {
        set pin [$vertex pin]
        if { ![$pin is_top_level_port] } {
          set inst [$pin instance]
        }
        set device_header 1
      }
      report_edge_ $edge vertex_port_name vertex_port_name $digits $report_variance
    }
  }
  $iter finish

  # Then wires. 
  set iter [$vertex $iter_proc]
  while {[$iter has_next]} {
    set edge [$iter next]
    if { [$edge role] == "wire" } {
      report_edge_ $edge $wire_from_name_proc $wire_to_name_proc $digits $report_variance
    }
  }
  $iter finish
}

proc report_edge_ { edge vertex_from_name_proc vertex_to_name_proc \
                      digits report_variance } {
  set latch_enable [$edge latch_d_to_q_en]
  if { $latch_enable != "" } {
    set latch_enable " enable $latch_enable"
  }
  report_line "[$vertex_from_name_proc [$edge from]] -> [$vertex_to_name_proc [$edge to]] [$edge role]$latch_enable"

  set disables [edge_disable_reason $edge]
  if { $disables != "" } {
    report_line "  Disabled by $disables"
  }

  set cond [$edge cond]
  if { $cond != "" } {
    report_line "  Condition: $cond"
  }

  set mode_name [$edge mode_name]
  if { $mode_name != "" } {
    report_line "  Mode: $mode_name [$edge mode_value]"
  }

  foreach arc [$edge timing_arcs] {
    set delays [$edge arc_delay_strings $arc $report_variance $digits]
    set delays_fmt [format_delays $delays]
    set disable_reason ""
    if { [timing_arc_disabled $edge $arc] } {
      set disable_reason " disabled"
    }
    report_line "  [$arc from_edge] -> [$arc to_edge] $delays_fmt$disable_reason"
  }
}

# Separate delay list elements with colons.
proc format_delays { values } {
  set result ""
  foreach value $values {
    if { $result != "" } {
      append result ":"
    }
    append result $value
  }
  return $result
}

proc edge_disable_reason { edge } {
  set disables ""
  if [$edge is_disabled_constraint] {
    append disables "constraint"
  }
  if [$edge is_disabled_constant] {
    if { $disables != "" } { append disables ", " }
    append disables "constant"
  }
  if [$edge is_disabled_cond_default] {
    if { $disables != "" } { append disables ", " }
    append disables "cond_default"
  }
  if [$edge is_disabled_loop] {
    if { $disables != "" } { append disables ", " }
    append disables "loop"
  }
  if [$edge is_disabled_bidirect_inst_path] {
    if { $disables != "" } { append disables ", " }
    append disables "bidirect instance path"
  }
  if { [$edge is_disabled_preset_clear] } {
    if { $disables != "" } { append disables ", " }
    append disables "sta_preset_clear_arcs_enabled"
  }
  return $disables
}

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

define_cmd_args "report_constant" {pin|instance|net}

proc report_constant { obj } {
  parse_inst_port_pin_net_arg $obj insts pins nets
  foreach pin $pins {
    report_pin_constant $pin
  }
  foreach inst $insts {
    set pin_iter [$inst pin_iterator]
    while {[$pin_iter has_next]} {
      set pin [$pin_iter next]
      report_pin_constant $pin
    }
    $pin_iter finish
  }
  foreach net $nets {
    set pin_iter [$net pin_iterator]
    while {[$pin_iter has_next]} {
      set pin [$pin_iter next]
      report_pin_constant $pin
    }
    $pin_iter finish
  }
}

proc report_pin_constant { pin } {
  set sim_value [pin_sim_logic_value $pin]

  set case_value [pin_case_logic_value $pin]
  if { $case_value != "X" } {
    set case " case=$case_value"
  } else {
    set case ""
  }
  set logic_value [pin_logic_value $pin]
  if { $logic_value != "X" } {
    set logic " logic=$logic_value"
  } else {
    set logic ""
  }
  report_line "[pin_property $pin lib_pin_name] $sim_value$case$logic"
}

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

proc_redirect report_disabled_edges {
  foreach edge [disabled_edges_sorted] {
    if { [$edge role] == "wire" } {
      set from_pin_name [get_full_name [[$edge from] pin]]
      set to_pin_name [get_full_name [[$edge to] pin]]
      report_line "$from_pin_name $to_pin_name [edge_disable_reason_verbose $edge]"
    } else {
      set from_pin [[$edge from] pin]
      set to_pin [[$edge to] pin]
      set inst_name [get_full_name [$from_pin instance]]
      set from_port_name [get_name [$from_pin port]]
      set to_port_name [get_name [$to_pin port]]
      set cond [$edge cond]
      if { $cond != "" } {
        set when " when: $cond"
      } else {
        set when ""
      }
      report_line "$inst_name $from_port_name $to_port_name$when [edge_disable_reason_verbose $edge]"
    }
  }
}

proc edge_disable_reason_verbose { edge } {
  set disables ""
  if { [$edge is_disabled_constraint] } {
    append disables "constraint"
  }
  if { [$edge is_disabled_constant] } {
    if { $disables != "" } { append disables ", " }
    append disables "constant"
    set sense [$edge sim_timing_sense]
    if { $sense == "positive_unate" || $sense == "negative_unate" } {
      append disables " $sense"
    }
    set const_pins [$edge disabled_constant_pins]
    foreach pin [sort_by_full_name $const_pins] {
      set port_name [pin_property $pin lib_pin_name]
      set value [pin_sim_logic_value $pin]
      append disables " $port_name=$value"
    }
  }
  if { [$edge is_disabled_cond_default] } {
    if { $disables != "" } { append disables ", " }
    append disables "cond_default"
  }
  if { [$edge is_disabled_loop] } {
    if { $disables != "" } { append disables ", " }
    append disables "loop"
  }
  if { [$edge is_disabled_preset_clear] } {
    if { $disables != "" } { append disables ", " }
    append disables "timing_enable_preset_clear_arcs"
  }
  return $disables
}

proc vertex_path_name { vertex } {
  set pin [$vertex pin]
  set pin_name [get_full_name $pin]
  return [vertex_name_ $vertex $pin $pin_name]
}

proc vertex_port_name { vertex } {
  set pin [$vertex pin]
  set pin_name [pin_property $pin lib_pin_name]
  return [vertex_name_ $vertex $pin $pin_name]
}

# Append driver/load for bidirect pin vertices.
proc vertex_name_ { vertex pin pin_name } {
  if { [pin_direction $pin] == "bidirect" } {
    if [$vertex is_bidirect_driver] {
      return "$pin_name driver"
    } else {
      return "$pin_name load  "
    }
  } else {
    return $pin_name
  }
}

# Return a list of hierarchical pins crossed by a graph edge.
proc hier_pins_crossed_by_edge { edge } {
  set from_pins [hier_pins_above [[$edge from] pin]]
  set to_pins [hier_pins_above [[$edge to] pin]]
  foreach p $to_pins { report_line [$p path_name] }
  while { [llength $from_pins] > 0 \
            && [llength $to_pins] > 0 \
              && [lindex $from_pins 0] == [lindex $to_pins 0] } {
    set from_pins [lrange $from_pins 1 end]
    set to_pins [lrange $to_pins 1 end]
  }
  return [concat $from_pins $to_pins]
}

proc hier_pins_above { pin } {
  set pins_above {}
  set inst [$pin instance]
  set net [$pin net]
  set parent [$inst parent]
  while { $parent != "NULL" } {
    set found 0
    set parent_pin_iter [$parent pin_iterator]
    while {[$parent_pin_iter has_next]} {
      set parent_pin [$parent_pin_iter next]
      if {[$parent_pin net] == $net} {
        set pins_above [concat [list $parent_pin] $pins_above]
        set found 1
        break
      }
    }
    $parent_pin_iter finish
    if { !$found} {
      break
    }
    set parent [$parent parent]
  }
  return $pins_above
}

proc report_level { pin } {
  set pin1 [get_port_pin_error "pin" $pin]
  foreach vertex [$pin1 vertices] {
    if { $vertex != "NULL" } {
      report_line "[vertex_path_name $vertex] level = [$vertex level]"
    }
  }
}

# Show how many pins are at each level from an input.
proc report_level_distribution {} {
  set max_level 0
  set iter [vertex_iterator]
  while {[$iter has_next]} {
    set vertex [$iter next]
    set level [$vertex level]
    if { $level > $max_level } {
      set max_level $level
    }
    if [info exists count($level)] {
      incr count($level)
    } else {
      set count($level) 1
    }
  }
  $iter finish

  report_line "level  pin count"
  for { set level 0 } { $level < $max_level } { incr level } {
    report_line "  $level      $count($level)"
  }
}

# sta namespace end
}
