# =============================================================================
# sta_analyze.tcl — Inter-Pass Static Timing Analysis with Metric Extraction
# =============================================================================
# Runs OpenSTA timing analysis between synthesis passes and writes
# machine-parseable results that run_eda.sh uses for pass/fail decisions.
#
# This is the opencos-adapted version of timing_flow/tcl/sta_analyze.tcl.
# It always uses an SDC file for constraints (either user-supplied or
# generated by run_eda.sh from env vars).
#
# Environment Variables:
#   Required:
#     LIBERTY_FILE    — Path(s) to Liberty timing library (.lib), space-delimited
#     NETLIST_FILE    — Path to synthesized netlist (.v)
#     TOP_MODULE      — Top-level module name
#     OUT_DIR         — Output directory
#     PASS_NAME       — Pass identifier (e.g., "pass1", "pass2", "pass2_full")
#     SDC_FILE        — Path to SDC constraints file
#
# Output:
#   $OUT_DIR/${PASS_NAME}_timing.txt       — Full timing report
#   $OUT_DIR/${PASS_NAME}_endpoints.txt    — Endpoint slack report
#   $OUT_DIR/${PASS_NAME}_metrics.txt      — Key=value parseable metrics
#   $OUT_DIR/${PASS_NAME}_metrics.json     — JSON metrics
# =============================================================================

# --- Read Liberty (may be space-delimited for multi-lib PDKs) ---
foreach _lib $::env(LIBERTY_FILE) {
    read_liberty $_lib
}

# --- Read Netlist ---
read_verilog $::env(NETLIST_FILE)
link_design $::env(TOP_MODULE)

# --- Apply Constraints from SDC ---
puts "INFO: Sourcing SDC file: $::env(SDC_FILE)"
source $::env(SDC_FILE)

# --- Report Configuration ---
set pass_name $::env(PASS_NAME)
set out_dir $::env(OUT_DIR)

puts "========================================"
puts "STA: $pass_name"
puts "========================================"
puts "Netlist:  $::env(NETLIST_FILE)"
puts "========================================"

# --- Generate Reports ---
set timing_file    "$out_dir/${pass_name}_timing.txt"
set endpoints_file "$out_dir/${pass_name}_endpoints.txt"
set metrics_file   "$out_dir/${pass_name}_metrics.txt"
set json_file      "$out_dir/${pass_name}_metrics.json"

# Full timing report (for human inspection)
report_checks -path_delay min_max -format full_clock_expanded > $timing_file

# Endpoint report (for metric extraction)
report_checks \
    -slack_max 1e30 \
    -format end \
    -digits 4 \
    -no_line_splits \
    -path_delay max \
    -group_path_count 1000000 \
    > $endpoints_file

# --- Clock Period ---
set clock_period_ns "null"
if {[info exists ::env(CLOCK_NS)] && $::env(CLOCK_NS) ne ""} {
    set clock_period_ns $::env(CLOCK_NS)
}

# --- Extract Metrics from Endpoint Report ---
set worst_slack 0.0
set tns 0.0
set failing_endpoints 0
set total_endpoints 0
set total_failing_path_delay 0.0

set fp [open $endpoints_file r]
set in_data 0

while {[gets $fp line] >= 0} {
    if {[regexp {^-+$} $line]} {
        set in_data 1
        continue
    }
    if {!$in_data} { continue }
    if {[string trim $line] eq ""} { continue }

    if {[regexp {([-\d.]+)\s+\((VIOLATED|MET)\)\s*$} $line match slack status]} {
        incr total_endpoints
        if {$total_endpoints == 1 || $slack < $worst_slack} {
            set worst_slack $slack
        }
        if {$slack < 0} {
            set tns [expr {$tns + $slack}]
            incr failing_endpoints
            # Path delay = clock_period - slack (slack is negative)
            if {$clock_period_ns ne "null"} {
                set total_failing_path_delay [expr {$total_failing_path_delay + $clock_period_ns - $slack}]
            }
        }
    }
}
close $fp

# --- Extract Power Metrics ---
# Note: OpenSTA report_power prints to stdout but returns "".
# We must use file redirection to capture the output.
set leakage_power "null"
set internal_power "null"
set switching_power "null"
set total_power "null"

set _pwr_file "$out_dir/${pass_name}_power.rpt"
if {[catch {
    report_power -digits 6 > $_pwr_file
    set _pwr_fp [open $_pwr_file r]
    set pwr_output [read $_pwr_fp]
    close $_pwr_fp
    # OpenSTA report_power output contains a "Total" row:
    # Total              X.XXe-XX X.XXe-XX X.XXe-XX X.XXe-XX
    foreach line [split $pwr_output "\n"] {
        if {[regexp {^Total\s+([\d.eE+-]+)\s+([\d.eE+-]+)\s+([\d.eE+-]+)\s+([\d.eE+-]+)} $line match int sw leak tot]} {
            set internal_power $int
            set switching_power $sw
            set leakage_power $leak
            set total_power $tot
        }
    }
} err]} {
    puts "WARNING: report_power failed (non-fatal): $err"
}

# --- Extract Worst Path Delay (arrival time) ---
# We already wrote the full timing report to $timing_file — read it back.
set worst_path_delay "null"
if {[catch {
    set _tf [open $timing_file r]
    set _timing_data [read $_tf]
    close $_tf
    set _in_max_path 0
    foreach line [split $_timing_data "\n"] {
        # Track which path type section we're in
        if {[regexp {Path Type:\s+max} $line]} {
            set _in_max_path 1
        } elseif {[regexp {Path Type:\s+min} $line]} {
            set _in_max_path 0
        }
        # Format: "         4.25   data arrival time" — number BEFORE text.
        # Take the largest arrival time among max-delay paths.
        if {$_in_max_path && [regexp {([\d.]+)\s+data arrival time} $line match val]} {
            if {$worst_path_delay eq "null" || $val > $worst_path_delay} {
                set worst_path_delay $val
            }
        }
    }
} err]} {
    puts "WARNING: Could not extract worst path delay (non-fatal): $err"
}

# --- Write Key=Value Metrics ---
set mfp [open $metrics_file "w"]
puts $mfp "CLOCK_PERIOD_NS=$clock_period_ns"
puts $mfp "WNS=$worst_slack"
puts $mfp "TNS=$tns"
puts $mfp "FAILING_ENDPOINTS=$failing_endpoints"
puts $mfp "TOTAL_ENDPOINTS=$total_endpoints"
puts $mfp "WORST_PATH_DELAY=$worst_path_delay"
puts $mfp "TOTAL_FAILING_PATH_DELAY=$total_failing_path_delay"
puts $mfp "LEAKAGE_POWER=$leakage_power"
puts $mfp "INTERNAL_POWER=$internal_power"
puts $mfp "SWITCHING_POWER=$switching_power"
puts $mfp "TOTAL_POWER=$total_power"
close $mfp

# --- Helper: format number or null for JSON ---
proc _sta_json_num {val} {
    if {$val eq "null" || $val eq ""} { return "null" }
    return $val
}

# --- Write JSON Metrics ---
set jfp [open $json_file "w"]
puts $jfp "\{"
puts $jfp "  \"clock_period_ns\": [_sta_json_num $clock_period_ns],"
puts $jfp "  \"wns\": $worst_slack,"
puts $jfp "  \"tns\": $tns,"
puts $jfp "  \"failing_endpoints\": $failing_endpoints,"
puts $jfp "  \"total_endpoints\": $total_endpoints,"
puts $jfp "  \"worst_path_delay\": [_sta_json_num $worst_path_delay],"
puts $jfp "  \"total_failing_path_delay\": $total_failing_path_delay,"
puts $jfp "  \"leakage_power\": [_sta_json_num $leakage_power],"
puts $jfp "  \"internal_power\": [_sta_json_num $internal_power],"
puts $jfp "  \"switching_power\": [_sta_json_num $switching_power],"
puts $jfp "  \"total_power\": [_sta_json_num $total_power]"
puts $jfp "\}"
close $jfp

# --- Console Summary ---
puts ""
puts "========================================"
puts "TIMING SUMMARY ($pass_name)"
puts "========================================"
puts "Clock Period:       $clock_period_ns ns"
puts "WNS:                $worst_slack ns"
puts "TNS:                $tns ns"
puts "Failing Endpoints:  $failing_endpoints / $total_endpoints"
puts "Worst Path Delay:   $worst_path_delay ns"
if {$failing_endpoints > 0} {
    puts "Failing Delay Sum:  $total_failing_path_delay ns"
}
puts "Power (total):      $total_power W"
puts "  Leakage:          $leakage_power W"
puts "  Internal:         $internal_power W"
puts "  Switching:        $switching_power W"

if {$worst_slack < 0} {
    puts "STATUS: TIMING VIOLATED"
} else {
    puts "STATUS: TIMING MET"
}
puts "========================================"

exit
