# OpenSTA, Static Timing Analyzer
# Copyright (c) 2025, 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.

cmake_minimum_required (VERSION 3.10)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
# Use standard target names
cmake_policy(SET CMP0078 NEW)
endif()
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.14)
# Allows SWIG_MODULE_NAME to be set
cmake_policy(SET CMP0086 NEW)
endif()

project(STA VERSION 3.0.1
  LANGUAGES CXX
)

option(CUDD_DIR "CUDD BDD package directory")
option(USE_TCL_READLINE "Use TCL readline package" ON)
option(ENABLE_TSAN "Compile with thread santizer enabled" OFF)
option(ENABLE_ASAN "Compile with address santizer enabled" OFF)

# Turn on to debug compiler args.
set(CMAKE_VERBOSE_MAKEFILE OFF)

set(STA_HOME ${PROJECT_SOURCE_DIR})
message(STATUS "STA version: ${PROJECT_VERSION}")

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC STA_GIT_SHA1)
message(STATUS "STA git sha: ${STA_GIT_SHA1}")

# Default to bulding optimnized/release executable.
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE RELEASE)
endif()

message(STATUS "System name: ${CMAKE_SYSTEM_NAME}")
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "Build CXX_FLAGS: ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}}")
message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}")

# Enable Link-Time Optimization (LTO) for Release builds.
include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
if(ipo_supported)
  set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
  message(STATUS "IPO/LTO: enabled for Release builds")
else()
  message(STATUS "IPO/LTO: not supported - ${ipo_error}")
endif()

################################################################
#
# Source files.
#
################################################################

set(STA_SOURCE
  app/StaMain.cc
  
  dcalc/ArcDcalcWaveforms.cc
  dcalc/ArcDelayCalc.cc
  dcalc/ArnoldiDelayCalc.cc
  dcalc/ArnoldiReduce.cc
  dcalc/CcsCeffDelayCalc.cc
  dcalc/Delay.cc
  dcalc/DelayCalc.cc
  dcalc/DelayCalcBase.cc
  dcalc/DelayNormal.cc
  dcalc/DelayScalar.cc
  dcalc/DelaySkewNormal.cc
  dcalc/DmpCeff.cc
  dcalc/DmpDelayCalc.cc
  dcalc/FindRoot.cc
  dcalc/GraphDelayCalc.cc
  dcalc/LumpedCapDelayCalc.cc
  dcalc/NetCaps.cc
  dcalc/ParallelDelayCalc.cc
  dcalc/PrimaDelayCalc.cc
  dcalc/UnitDelayCalc.cc
  
  graph/Graph.cc
  graph/GraphCmp.cc
  
  liberty/EquivCells.cc
  liberty/FuncExpr.cc
  liberty/InternalPower.cc
  liberty/LeakagePower.cc
  liberty/Liberty.cc
  liberty/LibertyBuilder.cc
  liberty/LibExprReader.cc
  liberty/LibertyParser.cc
  liberty/LibertyReader.cc
  liberty/LibertyWriter.cc
  liberty/LinearModel.cc
  liberty/Sequential.cc
  liberty/TableModel.cc
  liberty/TimingArc.cc
  liberty/TimingModel.cc
  liberty/TimingRole.cc
  liberty/Units.cc
  liberty/Wireload.cc
  
  network/ConcreteLibrary.cc
  network/ConcreteNetwork.cc
  network/HpinDrvrLoad.cc
  network/Network.cc
  network/NetworkCmp.cc
  network/ParseBus.cc
  network/PortDirection.cc
  network/SdcNetwork.cc
  network/VerilogNamespace.cc
  
  parasitics/ConcreteParasitics.cc
  parasitics/EstimateParasitics.cc
  parasitics/Parasitics.cc
  parasitics/ReduceParasitics.cc
  parasitics/ReportParasiticAnnotation.cc
  parasitics/SpefNamespace.cc
  parasitics/SpefReader.cc
  parasitics/SpefReaderPvt.hh

  power/Power.cc
  power/ReportPower.cc
  power/VcdReader.cc
  power/SaifReader.cc
  power/VcdParse.cc
  
  sdc/Clock.cc
  sdc/ClockGatingCheck.cc
  sdc/ClockGroups.cc
  sdc/ClockInsertion.cc
  sdc/ClockLatency.cc
  sdc/CycleAccting.cc
  sdc/DataCheck.cc
  sdc/DeratingFactors.cc
  sdc/DisabledPorts.cc
  sdc/ExceptionPath.cc
  sdc/InputDrive.cc
  sdc/PinPair.cc
  sdc/PortDelay.cc
  sdc/PortExtCap.cc
  sdc/Sdc.cc
  sdc/SdcCmdComment.cc
  sdc/Variables.cc
  sdc/WriteSdc.cc
  
  sdf/ReportAnnotation.cc
  sdf/SdfReader.cc
  sdf/SdfWriter.cc
  
  search/Bdd.cc
  search/Bfs.cc
  search/CheckMaxSkews.cc
  search/CheckMinPeriods.cc
  search/CheckMinPulseWidths.cc
  search/CheckCapacitances.cc
  search/CheckFanouts.cc
  search/CheckSlews.cc
  search/CheckTiming.cc
  search/ClkInfo.cc
  search/ClkLatency.cc
  search/ClkNetwork.cc
  search/ClkSkew.cc
  search/Crpr.cc
  search/FindRegister.cc
  search/GatedClk.cc
  search/Genclks.cc
  search/Latches.cc
  search/Levelize.cc
  search/MakeTimingModel.cc
  search/Mode.cc
  search/Path.cc
  search/Path.cc
  search/PathEnd.cc
  search/PathEnum.cc
  search/PathExpanded.cc
  search/PathGroup.cc
  search/PocvMode.cc
  search/Property.cc
  search/ReportPath.cc
  search/Search.cc
  search/SearchPred.cc
  search/Scene.cc
  search/Sim.cc
  search/Sta.cc
  search/StaState.cc
  search/Tag.cc
  search/TagGroup.cc
  search/VertexVisitor.cc
  search/VisitPathEnds.cc
  search/WorstSlack.cc

  spice/WritePathSpice.cc
  spice/WriteSpice.cc
  spice/Xyce.cc

  tcl/TclTypeHelpers.cc

  util/Debug.cc
  util/DispatchQueue.cc
  util/Error.cc
  util/Fuzzy.cc
  util/Hash.cc
  util/Machine.cc
  util/MinMax.cc
  util/PatternMatch.cc
  util/Report.cc
  util/ReportStd.cc
  util/ReportTcl.cc
  util/RiseFallMinMax.cc
  util/RiseFallMinMaxDelay.cc
  util/RiseFallValues.cc
  util/Stats.cc
  util/StringUtil.cc
  util/Transition.cc
  
  verilog/VerilogReader.cc
  verilog/VerilogWriter.cc
  )

# Source files.
set(STA_TCL_FILES
  tcl/Init.tcl
  tcl/Util.tcl
  tcl/CmdArgs.tcl
  tcl/CmdUtil.tcl
  tcl/Property.tcl
  tcl/Sta.tcl
  tcl/Splash.tcl
  tcl/Variables.tcl
  dcalc/DelayCalc.tcl
  graph/Graph.tcl
  liberty/Liberty.tcl
  network/Link.tcl
  network/Network.tcl
  network/NetworkEdit.tcl
  parasitics/Parasitics.tcl
  power/Power.tcl
  sdc/Sdc.tcl
  sdf/Sdf.tcl
  search/Search.tcl
  spice/WriteSpice.tcl
  verilog/Verilog.tcl
  )

################################################################
#
# Flex/bison scanner/parsers
#
################################################################

# Earlier versions of flex use 'register' declarations that are illegal in c++17.
#find_package(FLEX 2.6.4)
find_package(FLEX)
find_package(BISON REQUIRED 3.2)

# Verilog scan/parse.
flex_target(VerilogLex ${STA_HOME}/verilog/VerilogLex.ll
  ${CMAKE_CURRENT_BINARY_DIR}/VerilogLex.cc)
bison_target(VerilogParse ${STA_HOME}/verilog/VerilogParse.yy
  ${CMAKE_CURRENT_BINARY_DIR}/VerilogParse.cc)
add_flex_bison_dependency(VerilogLex VerilogParse)

# Liberty scan/parse.
flex_target(LibertyLex ${STA_HOME}/liberty/LibertyLex.ll
  ${CMAKE_CURRENT_BINARY_DIR}/LibertyLex.cc)
bison_target(LibertyParse ${STA_HOME}/liberty/LibertyParse.yy
  ${CMAKE_CURRENT_BINARY_DIR}/LibertyParse.cc)
add_flex_bison_dependency(LibertyLex LibertyParse)

# Liberty Expr scan/parse.
flex_target(LibExprLex ${STA_HOME}/liberty/LibExprLex.ll
  ${CMAKE_CURRENT_BINARY_DIR}/LibExprLex.cc)
bison_target(LibExprParse ${STA_HOME}/liberty/LibExprParse.yy
  ${CMAKE_CURRENT_BINARY_DIR}/LibExprParse.cc)
add_flex_bison_dependency(LibExprLex LibExprParse)

# Sdf scan/parse.
flex_target(SdfLex ${STA_HOME}/sdf/SdfLex.ll
  ${CMAKE_CURRENT_BINARY_DIR}/SdfLex.cc)
bison_target(SdfParse ${STA_HOME}/sdf/SdfParse.yy
  ${CMAKE_CURRENT_BINARY_DIR}/SdfParse.cc)
add_flex_bison_dependency(SdfLex SdfParse)

# Spef scan/parse.
flex_target(SpefLex ${STA_HOME}/parasitics/SpefLex.ll
  ${CMAKE_CURRENT_BINARY_DIR}/SpefLex.cc)
bison_target(SpefParse ${STA_HOME}/parasitics/SpefParse.yy
  ${CMAKE_CURRENT_BINARY_DIR}/SpefParse.cc)
add_flex_bison_dependency(SpefLex SpefParse)

# Saif scan/parse.
flex_target(SaifLex ${STA_HOME}/power/SaifLex.ll
  ${CMAKE_CURRENT_BINARY_DIR}/SaifLex.cc)
bison_target(SaifParse ${STA_HOME}/power/SaifParse.yy
  ${CMAKE_CURRENT_BINARY_DIR}/SaifParse.cc)
add_flex_bison_dependency(SaifLex SaifParse)

# Suppress -Wsign-compare in flex-generated code (yyleng vs int loop counter).
# Only needed with older GCC (e.g. CentOS 7 stock 4.8.5); newer GCC/flex handle it.
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  set_source_files_properties(
    ${FLEX_VerilogLex_OUTPUTS}
    ${FLEX_LibertyLex_OUTPUTS}
    ${FLEX_LibExprLex_OUTPUTS}
    ${FLEX_SdfLex_OUTPUTS}
    ${FLEX_SpefLex_OUTPUTS}
    ${FLEX_SaifLex_OUTPUTS}
    PROPERTIES COMPILE_FLAGS "-Wno-sign-compare"
  )
endif()

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

set(STA_TCL_INIT ${CMAKE_CURRENT_BINARY_DIR}/StaTclInitVar.cc)

# TCL files included as part of the executable are shoved into StaTclInitVar.cc.
# These files are encoded and shipped as part of the executable 
# so that they do not have to be installed on the client host.
add_custom_command(OUTPUT ${STA_TCL_INIT}
  COMMAND etc/TclEncode.tcl ${STA_TCL_INIT} tcl_inits ${STA_TCL_FILES}
  WORKING_DIRECTORY ${STA_HOME}
  DEPENDS ${STA_TCL_FILES} etc/TclEncode.tcl
  )

################################################################
#
# Library dependencies
#
################################################################

# OSX tcl is deprecated and prints a warning, so look for a user installed
# version before using the system version.
# I tried to override the library search order instead but failed.
# CMAKE_FIND_FRAMEWORK LAST bypasses the version in the framework directory
# but not the one in /usr/lib.
# This calls cmake/FindTCL.cmake
# Do not use REQUIRED because it also requires TK, which is not used by OpenSTA.
find_package(TCL)

# Referenced by util/StaConfig.hh.cmake
set(TCL_READLINE 0)
# check for tclReadline
if (USE_TCL_READLINE)
  set(TCL_READLINE_POSSIBLE_NAMES
    tclreadline-2.1.0
    tclreadline-2.3.2
    tclreadline-2.3.6
    tclreadline-2.3.7
    tclreadline-2.3.8
    )
  find_library(TCL_READLINE_LIBRARY
    NAMES tclreadline ${TCL_READLINE_POSSIBLE_NAMES}
    PATHS ${TCL_LIB_PATHS}
    )
  if (TCL_READLINE_LIBRARY)
    message(STATUS "TCL readline library: ${TCL_READLINE_LIBRARY}")
  else()
    message(STATUS "TCL readline library: NOT FOUND")
  endif()

  find_path(TCL_READLINE_INCLUDE tclreadline.h)
  if (TCL_READLINE_INCLUDE)
    message(STATUS "TCL readline header: ${TCL_READLINE_INCLUDE}/tclreadline.h")
  else()
    message(STATUS "TCL readline header: NOT FOUND")
  endif()

  if (TCL_READLINE_LIBRARY AND TCL_READLINE_INCLUDE)
    set(TCL_READLINE 1)
  endif()
endif()

# Zlib
include(FindZLIB)

find_package(Threads)

find_package(Eigen3 REQUIRED)

# fmt library: fallback when std::format is not available (e.g. GCC 11 on Ubuntu 22.04)
find_package(fmt QUIET)
if(NOT fmt_FOUND)
  include(FetchContent)
  FetchContent_Declare(fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 10.2.1
  )
  FetchContent_MakeAvailable(fmt)
endif()

include(cmake/FindCUDD.cmake)

# configure a header file to pass some of the CMake settings
configure_file(${STA_HOME}/util/StaConfig.hh.cmake
  ${CMAKE_CURRENT_BINARY_DIR}/include/sta/StaConfig.hh
  )

###########################################################
# Swig
###########################################################

find_package(SWIG 3.0 REQUIRED)
include(UseSWIG)

set(STA_SWIG_FILE app/StaApp.i)

set_property(SOURCE ${STA_SWIG_FILE}
  PROPERTY CPLUSPLUS ON
)
# Ubuntu 18.04 cmake version 3.10.2 does not support the
# COMPILE_OPTIONS and INCLUDE_DIRECTORIES properties, so cram
# them into SWIG_FLAGS for the time being.
set_property(SOURCE ${STA_SWIG_FILE}
  PROPERTY SWIG_FLAGS
  -module sta
  -namespace -prefix sta
  -I${STA_HOME}
)

set(SWIG_FILES
  ${STA_HOME}/dcalc/DelayCalc.i
  ${STA_HOME}/graph/Graph.i
  ${STA_HOME}/liberty/Liberty.i
  ${STA_HOME}/network/Network.i
  ${STA_HOME}/network/NetworkEdit.i
  ${STA_HOME}/parasitics/Parasitics.i
  ${STA_HOME}/power/Power.i
  ${STA_HOME}/sdc/Sdc.i
  ${STA_HOME}/sdf/Sdf.i
  ${STA_HOME}/search/Property.i
  ${STA_HOME}/search/Search.i
  ${STA_HOME}/spice/WriteSpice.i
  ${STA_HOME}/tcl/Exception.i
  ${STA_HOME}/tcl/StaTclTypes.i
  ${STA_HOME}/util/Util.i
  ${STA_HOME}/verilog/Verilog.i
  )

set_property(SOURCE ${STA_SWIG_FILE}
  PROPERTY DEPENDS ${SWIG_FILES}
)

swig_add_library(sta_swig
  LANGUAGE tcl
  TYPE STATIC
  SOURCES ${STA_SWIG_FILE}
)

get_target_property(STA_SWIG_CXX_FILE sta_swig SOURCES)

set_source_files_properties(${STA_SWIG_CXX_FILE}
  PROPERTIES
  # No simple way to modify the swig template that emits code full of warnings
  # so suppress them.
  COMPILE_OPTIONS "-Wunused-variable;-Wno-cast-qual;-Wno-missing-braces;-Wno-deprecated-declarations"
  )

target_link_libraries(sta_swig
  PUBLIC
  OpenSTA
)

# result build/CMakeFiles/sta_swig.dir/StaAppTCL_wrap.cxx
target_include_directories(sta_swig
  PUBLIC
  include/sta

  PRIVATE
  ${STA_HOME}
  ${TCL_INCLUDE_PATH}
  ${CMAKE_CURRENT_BINARY_DIR}/include/sta
)

###########################################################
# Library
###########################################################

add_library(OpenSTA)

target_sources(OpenSTA
  PRIVATE
  ${STA_SOURCE}

  ${STA_TCL_INIT}

  ${FLEX_LibExprLex_OUTPUTS}
  ${BISON_LibExprParse_OUTPUTS}
  ${FLEX_LibertyLex_OUTPUTS}
  ${BISON_LibertyParse_OUTPUTS}

  ${FLEX_SpefLex_OUTPUTS}
  ${BISON_SpefParse_OUTPUTS}

  ${FLEX_SdfLex_OUTPUTS}
  ${BISON_SdfParse_OUTPUTS}

  ${FLEX_VerilogLex_OUTPUTS}
  ${BISON_VerilogParse_OUTPUTS}

  ${FLEX_SaifLex_OUTPUTS}
  ${BISON_SaifParse_OUTPUTS}
)

target_link_libraries(OpenSTA
  Eigen3::Eigen
  fmt::fmt
  ${TCL_LIBRARY}
  ${CMAKE_THREAD_LIBS_INIT}
  ${CUDD_LIB}
  )

if (ZLIB_LIBRARIES)
  target_link_libraries(OpenSTA ${ZLIB_LIBRARIES})
endif()

target_include_directories(OpenSTA
  PUBLIC
  include
  ${TCL_INCLUDE_PATH}
  ${FLEX_INCLUDE_DIRS}
  ${CMAKE_CURRENT_BINARY_DIR}/include/sta

  PRIVATE
  include/sta
  ${STA_HOME}
  ${CUDD_INCLUDE}
  # For flex to find location.hh
  ${CMAKE_CURRENT_BINARY_DIR}
  )

if (TCL_READLINE)
  target_link_libraries(OpenSTA ${TCL_READLINE_LIBRARY})
  target_include_directories(OpenSTA
    PUBLIC
    ${TCL_READLINE_INCLUDE})
endif()

# common to gcc/clang
set(CXX_FLAGS -Wall -Wextra -pedantic -Wcast-qual -Wredundant-decls
  -Wformat-security -Werror=misleading-indentation -Wundef)

if(ENABLE_TSAN)
  message(STATUS "Thread sanitizer: ${ENABLE_TSAN}")
  set(CXX_FLAGS "${CXX_FLAGS};-fsanitize=thread")
  set(CMAKE_EXE_LINKER_FLAGS "-fsanitize=thread")
endif()

if(ENABLE_ASAN)
  message(STATUS "Address sanitizer: ${ENABLE_ASAN}")
  set(CXX_FLAGS "${CXX_FLAGS};-fsanitize=address")
  set(CMAKE_EXE_LINKER_FLAGS "-fsanitize=address")
endif()

target_compile_options(OpenSTA
  PRIVATE
  $<$<CXX_COMPILER_ID:GNU>:${CXX_FLAGS} -Wno-format-zero-length>
  $<$<CXX_COMPILER_ID:Clang>:${CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments>
  $<$<CXX_COMPILER_ID:AppleClang>:${CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments>
  )

# Disable compiler specific extensions like gnu++11.
set_target_properties(OpenSTA PROPERTIES CXX_EXTENSIONS OFF)
target_compile_features(OpenSTA PUBLIC cxx_std_20)

message(STATUS "STA library: ${CMAKE_BINARY_DIR}/libOpenSTA.a")

###########################################################
# Executable
###########################################################

# Note executable and lib name cannot be the same because
# on osx something is case insensitive. Using STA for the
# lib name results in "No rule to make target ../depend.
add_executable(sta app/Main.cc)

target_link_libraries(sta
  sta_swig
  OpenSTA
  )

message(STATUS "STA executable: ${CMAKE_BINARY_DIR}/sta")

################################################################
# Install
# cmake .. -DCMAKE_INSTALL_PREFIX=<prefix_path>

# executable
install(TARGETS sta DESTINATION bin)

# library
install(TARGETS OpenSTA DESTINATION lib)

# include
install(DIRECTORY include/sta  DESTINATION include)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/sta  DESTINATION include)

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

add_custom_target(sta_tags
  COMMAND etags -o TAGS
  ${STA_SOURCE}
  */*.hh
  include/sta/*.hh
  ${CMAKE_CURRENT_BINARY_DIR}/include/sta/*.hh
  ${SWIG_FILES}
  ${SWIG_FILES}

  # Append tcl tags.
  # Note that the regexp used for tcl files is broken in etags so provide one that works.
  COMMAND etags -a -o TAGS --language=none
  --regex='/^[ \\t]*proc[ \\t]+[a-zA-Z0-9_]+/'
  ${STA_TCL_FILES}
  WORKING_DIRECTORY ${STA_HOME}
)

add_custom_command(
  TARGET OpenSTA
  POST_BUILD
  COMMAND ${STA_HOME}/etc/FindMessages.tcl > ${STA_HOME}/doc/messages.txt || true
  WORKING_DIRECTORY ${STA_HOME}
)
