#
# Copyright 2014-2015 Ettus Research
#

# ---------------------------------------------------
# Create namespace and initialize global parameters
# ---------------------------------------------------
namespace eval ::vivado_utils {
    # Export commands
    namespace export \
        initialize_project \
        synthesize_design \
        export_synth_netlist \
        separate_encrypted \
        export_encrypted_netlist \
        check_design \
        generate_post_synth_reports \
        generate_post_place_reports \
        generate_post_route_reports \
        write_implementation_outputs \
        get_top_module \
        get_part_name \
        get_vivado_mode

    # Required environment variables
    variable g_tools_dir    $::env(VIV_TOOLS_DIR)
    variable g_top_module   $::env(VIV_TOP_MODULE)
    variable g_part_name    $::env(VIV_PART_NAME)
    variable g_output_dir   $::env(VIV_OUTPUT_DIR)
    variable g_source_files $::env(VIV_DESIGN_SRCS)
    variable g_oot_srcs_file $::env(VIV_OOT_SRCS_FILE)
    variable g_vivado_mode  $::env(VIV_MODE)
    variable g_project_save $::env(VIV_PROJECT)
    variable g_secure_key   $::env(VIV_SECURE_KEY)

    # Optional environment variables
    variable g_verilog_defs ""
    if { [info exists ::env(VIV_VERILOG_DEFS) ] } {
        set g_verilog_defs  $::env(VIV_VERILOG_DEFS)
    }
    variable g_include_dirs ""
    if { [info exists ::env(VIV_INCLUDE_DIRS) ] } {
        set g_include_dirs  $::env(VIV_INCLUDE_DIRS)
    }
}

# ---------------------------------------------------
# Create a new project in memory and add source files
# ---------------------------------------------------
proc ::vivado_utils::initialize_project { {save_to_disk 0} } {
    variable g_top_module
    variable g_part_name
    variable g_output_dir
    variable g_source_files
    variable g_oot_srcs_file
    variable g_project_save

    variable bd_files ""

    file delete -force $g_output_dir/build.rpt

    if {$save_to_disk == 1 || $g_project_save == 1} {
        puts "BUILDER: Creating Vivado project ${g_top_module}_project.xpr for part $g_part_name"
        create_project -force -part $g_part_name ${g_top_module}_project
    } else {
        puts "BUILDER: Creating Vivado project in memory for part $g_part_name"
        create_project -in_memory -part $g_part_name
    }

    # Get OOT sources from resources file to avoid ARG_MAX issue
    if {[info exists g_oot_srcs_file] && [file exists $g_oot_srcs_file]} {
        puts "BUILDER: Reading sources from $g_oot_srcs_file"
        set fh [open $g_oot_srcs_file r];
        while {[gets $fh line] >= 0} {
            if {$line ne ""} {
                append g_source_files " " $line
            }
        }
        close $fh
    }

    # Expand directories to include their contents (needed for HLS outputs)
    foreach src_file $g_source_files {
        if [expr [file isdirectory $src_file] == 1] {
            puts "BUILDER: Expanding Directory : $src_file"
            set dir_contents [glob $src_file/*.*]
            append g_source_files " " $dir_contents
        }
    }

    foreach src_file $g_source_files {
        set src_ext [file extension $src_file ]
        if [expr [lsearch {.vhd .vhdl} $src_ext] >= 0] {
            puts "BUILDER: Adding VHDL: $src_file"
            read_vhdl -library work $src_file
        } elseif [expr [lsearch {.v .vh} $src_ext] >= 0] {
            puts "BUILDER: Adding Verilog: $src_file"
            read_verilog $src_file
        } elseif [expr [lsearch {.vp} $src_ext] >= 0] {
            puts "BUILDER: Adding encrypted Verilog: $src_file"
            read_verilog $src_file
        } elseif [expr [lsearch {.sv .svh} $src_ext] >= 0] {
            puts "BUILDER: Adding SystemVerilog: $src_file"
            read_verilog -sv $src_file
        } elseif [expr [lsearch {.xdc} $src_ext] >= 0] {
            puts "BUILDER: Adding XDC: $src_file"
            read_xdc $src_file
        } elseif [expr [lsearch {.sdc} $src_ext] >= 0] {
            puts "BUILDER: Adding SDC: $src_file"
            read_xdc $src_file
        } elseif [expr [lsearch {.xci} $src_ext] >= 0] {
            puts "BUILDER: Adding IP: $src_file"
            read_ip $src_file
            if {[catch {set_property generate_synth_checkpoint true [get_files $src_file]} errorstring]} {
                puts "BUILDER: Failed to set synth checkpoint generation ($errorstring). The process will continue."
            }
        } elseif [expr [lsearch {.ngc .edif .edf} $src_ext] >= 0] {
            puts "BUILDER: Adding Netlist: $src_file"
            read_edif $src_file
        } elseif [expr [lsearch {.bd} $src_ext] >= 0] {
            puts "BUILDER: Adding Block Design to list (added after IP regeneration): $src_file"
            append bd_files "$src_file "
        } elseif [expr [lsearch {.bxml} $src_ext] >= 0] {
            puts "BUILDER: Adding Block Design XML to list (added after IP regeneration): $src_file"
            append bd_files "$src_file "
        } elseif [expr [lsearch {.dat} $src_ext] >= 0] {
            puts "BUILDER: Adding Data File: $src_file"
            add_files $src_file
        } else {
            puts "BUILDER: \[WARNING\] File ignored!!!: $src_file"
        }
    }

    # The 'synth_ip [get_ips *]' step causes builds in Windows to recompile various
    # pieces of the IP. This is time-consuming and unnecessary behavior, thus is removed.
    # These steps are redundant anyway since the IP builder performs both of them.
    # puts "BUILDER: Refreshing IP"
    # generate_target all [get_ips *]
    # synth_ip [get_ips *]

    #might seem silly, but we need to add the bd files after the ip regeneration.
    foreach file $bd_files {
        puts "BUILDER: Adding file from Block Design list: $file"
        add_files -norecurse $file
    }

    puts "BUILDER: Setting $g_top_module as the top module"
    set_property top $g_top_module [current_fileset]

    # Turn off the cs_server version check so that Vivado doesn't give a
    # version mismatch error when users have a patch installed. The error
    # prevents users from using JTAG after the build finishes. It will still
    # give a warning, in case they really do have a legitimate mismatch.
    set_param labtools.override_cs_server_version_check 1
}

# ---------------------------------------------------
# Synthesize design (Shortcut for Vivado's synth_design)
# ---------------------------------------------------
proc ::vivado_utils::synthesize_design {args} {
    variable g_top_module
    variable g_part_name
    variable g_verilog_defs
    variable g_include_dirs

    set vdef_args ""
    foreach vdef $g_verilog_defs {
        set vdef_args [concat $vdef_args "-verilog_define $vdef"]
    }
    set incdir_args ""
    if { [string compare $g_include_dirs ""] != 0 } {
        set incdir_args "-include_dirs $g_include_dirs"
    }

    set synth_cmd "synth_design -top $g_top_module -part $g_part_name -assert"
    set synth_cmd [concat $synth_cmd $vdef_args]
    set synth_cmd [concat $synth_cmd $incdir_args]
    set synth_cmd [concat $synth_cmd $args]
    puts "BUILDER: Synthesizing design"
    eval $synth_cmd
}

# ---------------------------------------------------
# Generate netlist from Synthesis
# ---------------------------------------------------
proc ::vivado_utils::export_synth_netlist { {suffix ""} } {
    variable g_output_dir
    variable g_top_module

    puts "BUILDER: Writing EDIF netlist for $g_top_module"
    set filename ${g_output_dir}/${g_top_module}
    if { [expr [string length $suffix] > 0] } {
        set filename ${filename}_${suffix}
    }
    write_edif -security_mode all -force ${filename}.edf
    write_verilog -mode synth_stub -force -file ${filename}_stub.v
}

# ---------------------------------------------------
# Extract encrypted sections from a file
# ---------------------------------------------------
proc ::vivado_utils::separate_encrypted {input_filename unencrypted_filename encrypted_filename} {
    set protected_sections ""
    set unprotected_sections ""
    set inside_protected_section 0

    # Read each line from the input file and separate it into protected and
    # unprotected sections.
    set input_file [open $input_filename r]
    while {[gets $input_file line] >= 0} {
        # Check if we are inside a protected section
        if {[string match "*`pragma protect begin_protected*" $line]} {
            set inside_protected_section 1
        }

        # Append the line to the appropriate section
        if {$inside_protected_section} {
            append protected_sections "$line\n"
        } else {
            append unprotected_sections "$line\n"
        }

        if {[string match "*`pragma protect end_protected*" $line]} {
            set inside_protected_section 0
        }
    }
    close $input_file

    # Write the extracted portions to files
    set encrypted_file [open $encrypted_filename w]
    puts $encrypted_file $protected_sections
    close $encrypted_file
    set unencrypted_file [open $unencrypted_filename w]
    puts $unencrypted_file $unprotected_sections
    close $unencrypted_file
}

# ---------------------------------------------------------
# Generate an IEEE-1735 encrypted netlist in a single file
# ---------------------------------------------------------
proc ::vivado_utils::export_encrypted_netlist { {suffix ""} } {
    variable g_output_dir
    variable g_top_module
    variable g_secure_key

    puts "BUILDER: Writing encrypted netlist for $g_top_module"
    set filename ${g_output_dir}/${g_top_module}
    if { [expr [string length $suffix] > 0] } {
        set filename ${filename}_${suffix}
    }

    # Uniquify the module names to prevent collisions when the netlist is used
    rename_ref -prefix_all $g_top_module

    # Write the design to a Verilog netlist
    write_verilog -force ${filename}_netlist.v
    puts "BUILDER: Unencrypted netlist written to ${filename}_netlist.v"
    write_xdc -force ${filename}.xdc
    puts "BUILDER: Constraints written to ${filename}.xdc"
    if {$g_secure_key ne ""} {
        # The netlist may have parts that are already encrypted and Vivado doesn't
        # let us encrypt stuff that's already encrypted, so we need to split it up.
        vivado_utils::separate_encrypted ${filename}_netlist.v ${filename}_netlist_user.v ${filename}_netlist_other.vp
        # Encrypt the parts that aren't already encrypted
        encrypt -key ${g_secure_key} -lang verilog -ext .vp ${filename}_netlist_user.v
        # Merge the two encrypted files
        set combined_netlists [read [open ${filename}_netlist_user.vp r]][read [open ${filename}_netlist_other.vp r]]
        set combined_netlists_file [open ${filename}.vp w]
        puts $combined_netlists_file $combined_netlists
        close $combined_netlists_file
        puts "BUILDER: Encrypted netlist written to ${filename}.vp"
    }
}

# ---------------------------------------------------
# Check design (Shortcut for Vivado's synth_design -rtl)
# ---------------------------------------------------
proc ::vivado_utils::check_design {args} {
    variable g_top_module
    variable g_part_name
    variable g_verilog_defs
    variable g_include_dirs

    set vdef_args ""
    foreach vdef $g_verilog_defs {
        set vdef_args [concat $vdef_args "-verilog_define $vdef"]
    }
    set incdir_args ""
    if { [string compare $g_include_dirs ""] != 0 } {
        set incdir_args "-include_dirs $g_include_dirs"
    }

    set synth_cmd "synth_design -top $g_top_module -part $g_part_name -rtl -rtl_skip_ip -rtl_skip_constraints -assert"
    set synth_cmd [concat $synth_cmd $vdef_args]
    set synth_cmd [concat $synth_cmd $incdir_args]
    set synth_cmd [concat $synth_cmd $args]
    puts "BUILDER: Checking syntax and elaborating design"
    eval $synth_cmd
}

# ---------------------------------------------------
# Generate post synthesis reports and checkpoint
# ---------------------------------------------------
proc ::vivado_utils::generate_post_synth_reports {} {
    variable g_output_dir

    puts "BUILDER: Writing post-synthesis checkpoint"
    write_checkpoint -force $g_output_dir/post_synth
    puts "BUILDER: Writing post-synthesis reports"
    report_utilization -file $g_output_dir/post_synth_util.rpt
    report_utilization -hierarchical -file $g_output_dir/post_synth_util_hier.rpt
    report_drc -ruledeck methodology_checks -file $g_output_dir/methodology.rpt
    report_high_fanout_nets -file $g_output_dir/high_fanout_nets.rpt
}

# ---------------------------------------------------
# Generate post placement reports and checkpoint
# ---------------------------------------------------
proc ::vivado_utils::generate_post_place_reports {} {
    variable g_output_dir

    puts "BUILDER: Writing post-placement checkpoint"
    write_checkpoint -force $g_output_dir/post_place
    puts "BUILDER: Writing post-placement reports"
    report_clock_utilization -file $g_output_dir/clock_util.rpt
    report_utilization -file $g_output_dir/post_place_util.rpt
    report_utilization -hierarchical -file $g_output_dir/post_place_util_hier.rpt
    report_timing -sort_by group -max_paths 5 -path_type summary -file $g_output_dir/post_place_timing.rpt
}

# ---------------------------------------------------
# Generate post route reports and checkpoint
# ---------------------------------------------------
proc ::vivado_utils::generate_post_route_reports {} {
    variable g_output_dir

    puts "BUILDER: Writing post-route checkpoint"
    write_checkpoint -force $g_output_dir/post_route
    puts "BUILDER: Writing post-route reports"
    if {[file exists "$g_output_dir/clock_util.rpt"] == 0} {
        report_clock_utilization -file $g_output_dir/clock_util.rpt
    }
    report_timing_summary -file $g_output_dir/post_route_timing_summary.rpt
    report_utilization -file $g_output_dir/post_route_util.rpt
    report_utilization -hierarchical -file $g_output_dir/post_route_util_hier.rpt
    report_power -file $g_output_dir/post_route_power.rpt
    report_drc -file $g_output_dir/post_imp_drc.rpt
    report_timing -sort_by group -max_paths 10 -path_type summary -file $g_output_dir/post_route_timing.rpt
}

# ---------------------------------------------------
# Export implementation
# ---------------------------------------------------
proc ::vivado_utils::check_timing_report {} {
    variable g_output_dir
    set s "(Implementation outputs were still generated)"

    if {! [string match -nocase {*timing constraints are met*} [read [open $g_output_dir/build.rpt]]]} {
        send_msg_id {Builder 0-0} error "The design did not satisfy timing constraints. ${s}"
    }
    if {! [string match -nocase {*checking no_clock (0)*} [read [open $g_output_dir/build.rpt]]]} {
        send_msg_id {Builder 0-0} error "There are register/latch pins with no clock driven by root clock pin. ${s}"
    }
    if {! [string match -nocase {*checking constant_clock (0)*} [read [open $g_output_dir/build.rpt]]]} {
        send_msg_id {Builder 0-0} error "There are register/latch pins with constant clock. ${s}"
    }
    if {! [string match -nocase {*checking multiple_clock (0)*} [read [open $g_output_dir/build.rpt]]]} {
        send_msg_id {Builder 0-0} error "There are register/latch pins with multiple clocks. ${s}"
    }
    if {! [string match -nocase {*checking generated_clocks (0)*} [read [open $g_output_dir/build.rpt]]]} {
        send_msg_id {Builder 0-0} error "There are generated clocks that are not connected to a clock source. ${s}"
    }
    if {! [string match -nocase {*checking loops (0)*} [read [open $g_output_dir/build.rpt]]]} {
        send_msg_id {Builder 0-0} error "There are combinational loops in the design. ${s}"
    }
    if {! [string match -nocase {*checking latch_loops (0)*} [read [open $g_output_dir/build.rpt]]]} {
        send_msg_id {Builder 0-0} error "There are combinational latch loops in the design. ${s}"
    }
}

proc ::vivado_utils::write_implementation_outputs { {byte_swap_bin 0} } {
    variable g_output_dir
    variable g_top_module
    variable g_tools_dir

    puts "BUILDER: Writing implementation netlist and XDC"
    write_verilog -force $g_output_dir/${g_top_module}_impl_netlist.v
    write_xdc -no_fixed_only -force $g_output_dir/${g_top_module}_impl.xdc
    puts "BUILDER: Writing bitfile"
    write_bitstream -force $g_output_dir/${g_top_module}.bit
    puts "BUILDER: Writing config bitstream"
    set binsize [expr [file size $g_output_dir/${g_top_module}.bit]/(1024*1024)]
    set binsize_pow2 [expr {int(pow(2,ceil(log($binsize)/log(2))))}]
    set bin_iface [expr $byte_swap_bin?"SMAPx32":"SMAPx8"]
    write_cfgmem -force -quiet -interface $bin_iface -format BIN -size $binsize_pow2 -disablebitswap -loadbit "up 0x0 $g_output_dir/${g_top_module}.bit" $g_output_dir/${g_top_module}.bin
    puts "BUILDER: Writing debug probes"
    write_debug_probes -force $g_output_dir/${g_top_module}.ltx
    puts "BUILDER: Writing export report"
    report_utilization -omit_locs -file $g_output_dir/build.rpt
    report_timing_summary -no_detailed_paths -file $g_output_dir/build.rpt -append
    vivado_utils::check_timing_report
}

proc ::vivado_utils::write_netlist_outputs { {suffix ""} } {
    variable g_output_dir
    variable g_top_module

    puts "BUILDER: Writing EDIF netlist and XDC"
    set filename ${g_output_dir}/${g_top_module}
    if { [expr [string length $suffix] > 0] } {
        set filename ${filename}_${suffix}
    }
    write_edif -force ${filename}.edf
    write_xdc -no_fixed_only -force ${filename}.xdc
    puts "BUILDER: Writing export report"
    report_utilization -omit_locs -file $g_output_dir/build.rpt
    report_timing_summary -no_detailed_paths -file $g_output_dir/build.rpt -append
    vivado_utils::check_timing_report
}

# ---------------------------------------------------
# Close project
# ---------------------------------------------------
proc ::vivado_utils::close_batch_project {} {
    variable g_vivado_mode

    if [string equal $g_vivado_mode "batch"] {
        puts "BUILDER: Closing project"
        close_project
    } else {
        puts "BUILDER: In GUI mode. Leaving project open."
    }
}

# ---------------------------------------------------
# Get state variables
# ---------------------------------------------------
proc ::vivado_utils::get_top_module {} {
    variable g_top_module
    return $g_top_module
}

proc ::vivado_utils::get_part_name {} {
    variable g_part_name
    return $g_part_name
}

proc ::vivado_utils::get_vivado_mode {} {
    variable g_vivado_mode
    return $g_vivado_mode
}
