# =============================================================================
# pnr_flow.tcl — Full OpenROAD Place & Route Flow with PPA Milestones
# =============================================================================
# Production-quality PnR flow supporting 6 PDKs with PPA metric extraction
# at each milestone stage.
#
# Flow stages:
#   1. Read design (liberty, LEF, netlist, SDC)
#   2. Wire RC parasitics (from platform setRC.tcl)
#   3. Floorplan (initialize, tracks, pin placement)
#   4. PDN (from platform pdn.tcl)
#   5. Tap cells (from platform tapcell.tcl)
#   6. Global placement
#   7. Pre-CTS timing repair
#   8. Detailed placement
#   → MILESTONE: post_place_opt (stop here if STOP_STAGE=place)
#   9. Clock Tree Synthesis
#   10. Post-CTS timing repair
#   11. Re-legalize
#   → MILESTONE: post_cts_opt (stop here if STOP_STAGE=cts)
#   12. Global routing
#   13. Post-route timing repair
#   14. Re-legalize
#   15. Detailed routing
#   → MILESTONE: post_route_opt (stop here if STOP_STAGE=route)
#   16. Write outputs
#
# Environment Variables:
#   Required:
#     LIBERTY_FILE — PDK liberty file(s), space-delimited for multi-lib PDKs
#     LEF_TECH_FILE — PDK tech LEF file
#     LEF_FILE — PDK cell LEF file(s), space-delimited for multi-lef PDKs
#     TOP_MODULE, OUT_DIR, FILE_SUFFIX — Design identifiers
#     SDC_FILE — Timing constraints (generated by run_eda.sh)
#
#   Optional:
#     PLATFORM (default: sky130hs)
#     STOP_STAGE (default: route) — synth, place, cts, route
#     CORE_UTILIZATION (default: 65)
#     CORE_ASPECT_RATIO (default: 1.0)
#     PLACE_DENSITY — Override PDK default
#     CTS_BUF_LIST — Override auto-detected CTS buffers
#     SETUP_SLACK_MARGIN (default: 0) — ps
#     HOLD_SLACK_MARGIN (default: 0) — ps
#     MAX_WIRE_LENGTH — µm, for repair_design
# =============================================================================

puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

# --- Source infrastructure scripts ---
# These are copied to the work directory alongside this script
source ./pdk_config.tcl
source ./ppa_metrics.tcl

# --- Read environment variables ---
set top_module  $::env(TOP_MODULE)
set out_dir     $::env(OUT_DIR)
set file_suffix $::env(FILE_SUFFIX)
set stop_stage  [expr {[info exists ::env(STOP_STAGE)] ? $::env(STOP_STAGE) : "route"}]

set core_util   [expr {[info exists ::env(CORE_UTILIZATION)] ? $::env(CORE_UTILIZATION) : 65}]
set core_ar     [expr {[info exists ::env(CORE_ASPECT_RATIO)] ? $::env(CORE_ASPECT_RATIO) : 1.0}]
set setup_margin [expr {[info exists ::env(SETUP_SLACK_MARGIN)] ? $::env(SETUP_SLACK_MARGIN) : 0}]
set hold_margin  [expr {[info exists ::env(HOLD_SLACK_MARGIN)] ? $::env(HOLD_SLACK_MARGIN) : 0}]

puts ""
puts "========================================================"
puts "OPENROAD PnR FLOW"
puts "========================================================"
puts "Design:         $top_module"
puts "Platform:       $pdk_platform"
puts "Stop Stage:     $stop_stage"
puts "Utilization:    $core_util%"
puts "Aspect Ratio:   $core_ar"
puts "Setup Margin:   $setup_margin ps"
puts "Hold Margin:    $hold_margin ps"
puts "========================================================"

# =============================================================================
# 1. Read Design
# =============================================================================
puts "\n========== Reading Design =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

# Read liberty files — may be space-delimited for multi-lib PDKs (e.g. ASAP7)
foreach _lib $::env(LIBERTY_FILE) {
    puts "  read_liberty $_lib"
    read_liberty $_lib
}

# Read LEF files — tech LEF first, then cell LEF(s)
puts "  read_lef $::env(LEF_TECH_FILE)"
read_lef $::env(LEF_TECH_FILE)
foreach _lef $::env(LEF_FILE) {
    puts "  read_lef $_lef"
    read_lef $_lef
}

read_verilog $out_dir/${top_module}_${file_suffix}.syn.v
link_design $top_module

# =============================================================================
# 1b. Reclassify Mistyped Nets (DRT-0305 workaround)
# =============================================================================
# Yosys may produce nets with names like "zero_" or "one_" from SystemVerilog
# literals ('0, '1).  OpenROAD classifies these as GROUND/POWER by name
# pattern matching, causing TritonRoute DRT-0305 errors ("not routable by
# TritonRoute. Move to special nets.").
#
# Fix: scan all nets and reclassify any POWER/GROUND net that is actually a
# regular signal (connected to standard cell instance pins, not to a special
# net / power domain).  This is safe because real power/ground nets are
# defined by the PDN generator and have ITerm connections to the power grid,
# not to logic gates.
# See: https://github.com/The-OpenROAD-Project/OpenROAD/issues/2041
# -----------------------------------------------------------------------------
set _block [ord::get_db_block]
set _reclass_count 0
foreach _net [$_block getNets] {
    set _stype [$_net getSigType]
    if {$_stype eq "POWER" || $_stype eq "GROUND"} {
        # Check if the net is a special net (real power/ground): these have
        # SWires or are connected only to supply pins.  A signal net that was
        # misclassified will have ITerms on logic cells but no SWires.
        set _is_special 0
        if {[$_net isSpecial]} {
            set _is_special 1
        }
        if {!$_is_special} {
            set _nname [$_net getName]
            puts "INFO: Reclassifying net '$_nname' from $_stype to SIGNAL (DRT-0305 workaround)"
            $_net setSigType SIGNAL
            incr _reclass_count
        }
    }
}
if {$_reclass_count > 0} {
    puts "INFO: Reclassified $_reclass_count mistyped POWER/GROUND net(s) to SIGNAL"
}

# Read SDC constraints
if {[info exists ::env(SDC_FILE)] && $::env(SDC_FILE) ne ""} {
    puts "INFO: Reading SDC: $::env(SDC_FILE)"
    read_sdc $::env(SDC_FILE)
} else {
    puts "WARNING: No SDC_FILE set. Timing analysis will have no constraints."
}

# =============================================================================
# 2. Wire RC Parasitics
# =============================================================================
puts "\n========== Wire RC =========="
pdk_source_setrc

# =============================================================================
# 3. Floorplan
# =============================================================================
puts "\n========== Floorplan =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

initialize_floorplan \
    -utilization $core_util \
    -aspect_ratio $core_ar \
    -core_space $pdk_core_space \
    -site $pdk_place_site

# =============================================================================
# 4. Tracks
# =============================================================================
puts "\n========== Routing Tracks =========="
pdk_source_tracks

# =============================================================================
# 5. IO Pin Placement
# =============================================================================
puts "\n========== IO Pin Placement =========="
place_pins -hor_layers $pdk_io_placer_h -ver_layers $pdk_io_placer_v

# =============================================================================
# 6. Power Distribution Network
# =============================================================================
puts "\n========== Power Distribution Network =========="
pdk_source_pdn
pdngen

# =============================================================================
# 7. Tap Cells
# =============================================================================
puts "\n========== Tap Cells =========="
pdk_source_tapcell

# =============================================================================
# 8. Global Placement
# =============================================================================
puts "\n========== Global Placement =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

# Set dont-use cells
if {$pdk_dont_use ne ""} {
    set_dont_use $pdk_dont_use
}

global_placement -density $pdk_place_density -routability_use_grt

# =============================================================================
# 9. Pre-CTS Timing Repair
# =============================================================================
puts "\n========== Pre-CTS Timing Repair =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

estimate_parasitics -placement

# Repair design: fix max slew, max cap, max fanout violations
if {[info exists ::env(MAX_WIRE_LENGTH)] && $::env(MAX_WIRE_LENGTH) ne ""} {
    repair_design -max_wire_length $::env(MAX_WIRE_LENGTH)
} else {
    repair_design
}

# Repair timing: setup + hold
if {$setup_margin > 0} {
    repair_timing -setup -setup_margin $setup_margin
} else {
    repair_timing -setup
}
if {$hold_margin > 0} {
    repair_timing -hold -hold_margin $hold_margin
} else {
    repair_timing -hold
}

# =============================================================================
# 10. Detailed Placement (legalize)
# =============================================================================
puts "\n========== Detailed Placement =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

detailed_placement
check_placement -verbose

# =============================================================================
# → MILESTONE: post_place_opt
# =============================================================================
puts "\n========== Post-Placement Metrics =========="
estimate_parasitics -placement
extract_ppa_metrics "post_place_opt"

# Write placed DEF
write_def $out_dir/${top_module}_placed.def

report_design_area

# Congestion probe: run global_route after placement for demand/supply data.
# Source fastroute config once (idempotent — safe to call before real routing).
pdk_source_fastroute
puts ">>> REPORT_CONGESTION_START post_place_opt"
if {[catch {global_route -allow_congestion -verbose} grt_err]} {
    puts "WARNING: post_place_opt global_route failed (non-fatal): $grt_err"
}
puts "<<< REPORT_CONGESTION_END post_place_opt"

if {$stop_stage eq "place"} {
    puts "\nSTOP_STAGE=place reached. Exiting after placement."
    puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"
    exit
}

# =============================================================================
# 11. Clock Tree Synthesis
# =============================================================================
puts "\n========== Clock Tree Synthesis =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

# Clone clock inverters before CTS (recommended by ORFS)
if {[catch {repair_clock_inverters} err]} {
    puts "INFO: repair_clock_inverters: $err (non-fatal)"
}

# Build CTS command args
set cts_args [list -sink_clustering_enable]

if {[info exists ::env(CTS_BUF_LIST)] && $::env(CTS_BUF_LIST) ne ""} {
    lappend cts_args -buf_list $::env(CTS_BUF_LIST)
}

clock_tree_synthesis {*}$cts_args

set_propagated_clock [all_clocks]

# =============================================================================
# 12. Post-CTS Timing Repair
# =============================================================================
puts "\n========== Post-CTS Timing Repair =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

estimate_parasitics -placement

if {$setup_margin > 0} {
    repair_timing -setup -setup_margin $setup_margin
} else {
    repair_timing -setup
}
if {$hold_margin > 0} {
    repair_timing -hold -hold_margin $hold_margin
} else {
    repair_timing -hold
}

# Re-legalize after CTS buffer insertion
detailed_placement
check_placement -verbose

# =============================================================================
# → MILESTONE: post_cts_opt
# =============================================================================
puts "\n========== Post-CTS Metrics =========="
estimate_parasitics -placement
extract_ppa_metrics "post_cts_opt"

# Congestion probe: run global_route after CTS for demand/supply data.
puts ">>> REPORT_CONGESTION_START post_cts_opt"
if {[catch {global_route -allow_congestion -verbose} grt_err]} {
    puts "WARNING: post_cts_opt global_route failed (non-fatal): $grt_err"
}
puts "<<< REPORT_CONGESTION_END post_cts_opt"

if {$stop_stage eq "cts"} {
    puts "\nSTOP_STAGE=cts reached. Exiting after CTS."
    puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"
    exit
}

# =============================================================================
# 13. Global Routing
# =============================================================================
puts "\n========== Global Routing =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

# Source platform-specific routing config — already sourced at post_place_opt
# milestone, but safe to call again (idempotent).
pdk_source_fastroute

# global_route does NOT support > file redirect, but it prints the
# GRT-0096 Final congestion report to stdout.  We bracket the call with
# markers so run_eda.sh can reliably extract the congestion table from
# the flow log (same pattern as hw_design_generator's openroad_emitter).
puts ">>> REPORT_CONGESTION_START post_route_opt"
global_route -verbose -allow_congestion
puts "<<< REPORT_CONGESTION_END post_route_opt"

# =============================================================================
# 14. Post-Route Timing Repair
# =============================================================================
puts "\n========== Post-Route Timing Repair =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

estimate_parasitics -global_routing

if {$setup_margin > 0} {
    repair_timing -setup -setup_margin [expr {$setup_margin / 2}]
} else {
    repair_timing -setup
}
if {$hold_margin > 0} {
    repair_timing -hold -hold_margin $hold_margin
} else {
    repair_timing -hold
}

# Re-legalize after post-route buffer insertion
detailed_placement
check_placement -verbose

# =============================================================================
# 15. Detailed Routing
# =============================================================================
puts "\n========== Detailed Routing =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

set drc_rpt "$out_dir/${top_module}_drc.rpt"
detailed_route -output_drc $drc_rpt

# Parse DRC count from the report file
set drc_count "null"
if {[file exists $drc_rpt]} {
    if {[catch {
        set _fp [open $drc_rpt r]
        set _drc_text [read $_fp]
        close $_fp
        # Format: "Total number of violations = N"
        if {[regexp {[Tt]otal.*violations\s*=\s*(\d+)} $_drc_text match n]} {
            set drc_count $n
            puts "  DRC violations: $drc_count"
        } else {
            # Fallback: count "violation type:" lines (one per violation)
            set drc_count 0
            foreach line [split $_drc_text "\n"] {
                if {[regexp {violation type:} $line]} {
                    incr drc_count
                }
            }
            puts "  DRC violations: $drc_count (counted from entries)"
        }
    } err]} {
        puts "WARNING: Could not parse DRC report: $err"
    }
} else {
    puts "  DRC report not generated"
}

# =============================================================================
# → MILESTONE: post_route_opt
# =============================================================================
puts "\n========== Post-Route Metrics =========="
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

estimate_parasitics -global_routing
extract_ppa_metrics "post_route_opt" $drc_count

# =============================================================================
# 16. Final Reports & Write Outputs
# =============================================================================
puts "\n========== Final Reports =========="

puts "\n===== SETUP TIMING ====="
report_checks -path_delay max -fields {slew cap input_pins nets fanout} -digits 4

puts "\n===== HOLD TIMING ====="
report_checks -path_delay min -fields {slew cap input_pins nets fanout} -digits 4

puts "\n===== WNS / TNS ====="
report_tns
report_wns

puts "\n===== DESIGN AREA ====="
report_design_area

puts "\n===== WIRE LENGTH ====="
report_wire_length -net * -summary -global_route

puts "\n========== Writing Outputs =========="

write_def $out_dir/${top_module}_routed.def
write_verilog $out_dir/${top_module}_final.v

# Write timing reports to files for downstream tools
report_checks -path_delay max -fields {slew cap input_pins nets fanout} -digits 4 \
    > $out_dir/${top_module}_setup.rpt

report_checks -path_delay min -fields {slew cap input_pins nets fanout} -digits 4 \
    > $out_dir/${top_module}_hold.rpt

puts ""
puts "========================================================"
puts "PnR FLOW COMPLETE"
puts "========================================================"
puts "  Placed DEF:    $out_dir/${top_module}_placed.def"
puts "  Routed DEF:    $out_dir/${top_module}_routed.def"
puts "  Final netlist: $out_dir/${top_module}_final.v"
puts "  Setup report:  $out_dir/${top_module}_setup.rpt"
puts "  Hold report:   $out_dir/${top_module}_hold.rpt"
puts "  Metrics:       $out_dir/metrics_post_*.json"
puts "========================================================"
puts "\[DATE\]: [clock format [clock seconds] -format "%d-%m-%Y_%H:%M:%S"]"

exit
