#
# Copyright 2015 Ettus Research
#

# ---------------------------------------------------
# Create namespace and initialize global parameters
# ---------------------------------------------------
namespace eval ::vivado_strategies {
    # Export commands
    namespace export \
        get_preset \
        implement_design \
        check_strategy \
        print_strategy

    variable g_viv_version [version -short]
}

# ---------------------------------------------------
# Return a preset strategy with the most commonly used options
# ---------------------------------------------------
proc ::vivado_strategies::get_impl_preset {preset} {
    variable g_viv_version

    set strategy [dict create]
    switch -nocase $preset {
        "Default" {
            dict set strategy "opt_design.is_enabled"                   1
            dict set strategy "opt_design.directive"                    "Default"
            dict set strategy "post_opt_power_opt_design.is_enabled"    0
            dict set strategy "place_design.directive"                  "Default"
            dict set strategy "post_place_power_opt_design.is_enabled"  0
            dict set strategy "post_place_phys_opt_design.is_enabled"   0
            dict set strategy "post_place_phys_opt_design.directive"    "Default"
            dict set strategy "route_design.directive"                  "Default"
            dict set strategy "route_design.more_options"               ""
            dict set strategy "post_route_phys_opt_design.is_enabled"   0
            dict set strategy "post_route_phys_opt_design.directive"    "Default"
        }
        "Performance_Explore" {
            dict set strategy "opt_design.is_enabled"                   1
            dict set strategy "opt_design.directive"                    "Explore"
            dict set strategy "post_opt_power_opt_design.is_enabled"    0
            dict set strategy "place_design.directive"                  "Explore"
            dict set strategy "post_place_power_opt_design.is_enabled"  0
            dict set strategy "post_place_phys_opt_design.is_enabled"   1
            dict set strategy "post_place_phys_opt_design.directive"    "Explore"
            dict set strategy "route_design.directive"                  "Explore"
            dict set strategy "route_design.more_options"               ""
            dict set strategy "post_route_phys_opt_design.is_enabled"   0
            dict set strategy "post_route_phys_opt_design.directive"    "Explore"
        }
        "Performance_ExplorePostRoutePhysOpt" {
            dict set strategy "opt_design.is_enabled"                   1
            dict set strategy "opt_design.directive"                    "Explore"
            dict set strategy "post_opt_power_opt_design.is_enabled"    0
            dict set strategy "place_design.directive"                  "Explore"
            dict set strategy "post_place_power_opt_design.is_enabled"  0
            dict set strategy "post_place_phys_opt_design.is_enabled"   1
            dict set strategy "post_place_phys_opt_design.directive"    "Explore"
            dict set strategy "route_design.directive"                  "Explore"
            dict set strategy "route_design.more_options"               "-tns_cleanup"
            dict set strategy "post_route_phys_opt_design.is_enabled"   1
            dict set strategy "post_route_phys_opt_design.directive"    "Explore"
        }
    }
    return $strategy
}

# ---------------------------------------------------
# Execute the specified implementation strategy
# ---------------------------------------------------
proc ::vivado_strategies::implement_design {strategy} {
    variable g_viv_version

    # Check strategy for validity and print
    vivado_strategies::check_strategy $strategy
    puts "BUILDER: Running implementation strategy with:"
    vivado_strategies::print_strategy $strategy

    # Optimize the current netlist.
    # This will perform the retarget, propconst, sweep and bram_power_opt optimizations by default.
    if [dict get $strategy "opt_design.is_enabled"] {
        set opt_dir [dict get $strategy "opt_design.directive"]
        opt_design -directive $opt_dir
    }

    # Optimize dynamic power using intelligent clock gating after optimization
    if [dict get $strategy "post_opt_power_opt_design.is_enabled"] {
        power_opt_design
    }

    # Automatically place ports and leaf-level instances
    set pla_dir [dict get $strategy "place_design.directive"]
    place_design -directive $pla_dir

    # Optimize dynamic power using intelligent clock gating after placement
    if [dict get $strategy "post_place_power_opt_design.is_enabled"] {
        power_opt_design
    }

    # Optimize the current placed netlist
    if [dict get $strategy "post_place_phys_opt_design.is_enabled"] {
        set pp_physopt_dir [dict get $strategy "post_place_phys_opt_design.directive"]
        phys_opt_design -directive $pp_physopt_dir
    }

    # Option to run commands before route
    if {[dict exist $strategy "route_design.pre_hook"]} {
      set pre_route_cmds [dict get $strategy "route_design.pre_hook"]
      eval $pre_route_cmds
    }

    # Route the current design
    set rt_dir [dict get $strategy "route_design.directive"]
    puts "BUILDER: Choosing routing directive: $rt_dir"
    if {[dict get $strategy "route_design.more_options"] eq ""} {
        route_design -directive $rt_dir
    } else {
        set rt_more [dict get $strategy "route_design.more_options"]
        puts "BUILDER: Choosing additional routing options: $rt_more"
        route_design -directive $rt_dir $rt_more
    }

    # Optimize the current routed netlist.
    if [dict get $strategy "post_route_phys_opt_design.is_enabled"] {
        set pr_physopt_dir [dict get $strategy "post_route_phys_opt_design.directive"]
        phys_opt_design -directive $pr_physopt_dir
    }
}

# ---------------------------------------------------
# Sanity-check the specified strategy
# ---------------------------------------------------
proc ::vivado_strategies::check_strategy {strategy} {
    variable g_viv_version

    set strategy_options [dict keys $strategy]
    set required_options {\
        opt_design.is_enabled \
        opt_design.directive \
        post_opt_power_opt_design.is_enabled \
        place_design.directive \
        post_place_power_opt_design.is_enabled \
        post_place_phys_opt_design.is_enabled \
        post_place_phys_opt_design.directive \
        route_design.directive \
        post_route_phys_opt_design.is_enabled \
        post_route_phys_opt_design.directive \
    }

    set invalid 0
    foreach req $required_options {
        if [expr [lsearch $strategy_options $req] < 0] {
            puts "BUILDER: ERROR: Invalid strategy. Missing option $req"
            set invalid 1
        }
    }
    if $invalid {
        error "Strategy check failed!"
    }
}

# ---------------------------------------------------
# Print strategy parameters to the console
# ---------------------------------------------------
proc ::vivado_strategies::print_strategy {strategy} {
    variable g_viv_version

    foreach opt [dict keys $strategy] {
        set val [dict get $strategy $opt]
        puts "         * $opt = $val"
    }
}


