From 466312df689fbcf41d16d6deb3fcf0b2008e3e49 Mon Sep 17 00:00:00 2001 From: klin02 Date: Sat, 18 Jul 2026 01:03:26 +0800 Subject: [PATCH 1/6] feat(fpga_diff): support partition synthesis Run configured hierarchy partitions as parallel OOC synthesis jobs and link their checkpoints into the project synthesis DCP. Expose the managed runs in one XPR, share top-level clock definitions with OOC synthesis, and invalidate stale implementation results after relinking. (cherry picked from commit 5e4d1ecf634b7e71fcbb742989f80cfe0d3a290e) --- fpga_diff/.gitignore | 1 + fpga_diff/Makefile | 16 +- fpga_diff/src/constr/common/clock_defs.tcl | 40 +++ fpga_diff/src/constr/common/fpga.xdc | 21 +- fpga_diff/tools/gen_synth.tcl | 14 + fpga_diff/tools/partition_synth/defs.tcl | 219 +++++++++++++ .../tools/partition_synth/header_files.tcl | 91 ++++++ fpga_diff/tools/partition_synth/link.tcl | 126 ++++++++ .../tools/partition_synth/partitions.tcl | 41 +++ .../tools/partition_synth/project_launch.tcl | 103 ++++++ .../tools/partition_synth/project_link.tcl | 170 ++++++++++ .../tools/partition_synth/project_runs.tcl | 219 +++++++++++++ fpga_diff/tools/partition_synth/run.sh | 271 ++++++++++++++++ fpga_diff/tools/partition_synth/sources.tcl | 302 ++++++++++++++++++ fpga_diff/tools/partition_synth/synth.tcl | 161 ++++++++++ 15 files changed, 1784 insertions(+), 11 deletions(-) create mode 100644 fpga_diff/src/constr/common/clock_defs.tcl create mode 100644 fpga_diff/tools/partition_synth/defs.tcl create mode 100644 fpga_diff/tools/partition_synth/header_files.tcl create mode 100644 fpga_diff/tools/partition_synth/link.tcl create mode 100644 fpga_diff/tools/partition_synth/partitions.tcl create mode 100644 fpga_diff/tools/partition_synth/project_launch.tcl create mode 100644 fpga_diff/tools/partition_synth/project_link.tcl create mode 100644 fpga_diff/tools/partition_synth/project_runs.tcl create mode 100755 fpga_diff/tools/partition_synth/run.sh create mode 100644 fpga_diff/tools/partition_synth/sources.tcl create mode 100644 fpga_diff/tools/partition_synth/synth.tcl diff --git a/fpga_diff/.gitignore b/fpga_diff/.gitignore index daea8b10..5538d346 100644 --- a/fpga_diff/.gitignore +++ b/fpga_diff/.gitignore @@ -6,4 +6,5 @@ !*.tcl fpga_*/ +partition-synth-*/ src/tcl/cpu_files.tcl diff --git a/fpga_diff/Makefile b/fpga_diff/Makefile index 9f3d2ee4..7ee870c6 100755 --- a/fpga_diff/Makefile +++ b/fpga_diff/Makefile @@ -12,6 +12,11 @@ ILA_DEPTH ?= 16384 XDMA_LINK_WIDTH ?= X4 VIVADO_JOBS ?= DDR_RANK_WIDTH ?= 2 +PARTITION_SYNTH ?= 0 +PARTITION_SYNTH_OUT ?= +PARTITION_SYNTH_PARALLEL ?= 16 +PARTITION_SYNTH_JOBS ?= 8 +PARTITION_SYNTH_PROJECT_RUNS ?= 1 export ENABLE_ILA ILA_DEPTH XDMA_LINK_WIDTH VIVADO_JOBS DDR_RANK_WIDTH # Supported CPU parameters: # kmh @@ -23,7 +28,7 @@ PRJ_NAME = fpga_$(CPU)$(if $(strip $(SUFFIX)),-$(strip $(SUFFIX)),) PRJ_DIR ?= $(ENV_SCRIPTS_HOME)/$(PRJ_NAME) PRJ ?= $(PRJ_DIR)/$(PRJ_NAME).xpr -.PHONY: bitstream vivado dump_ila write_jtag_flash update_core_flist +.PHONY: synth bitstream vivado dump_ila write_jtag_flash update_core_flist # Get Vivado version VIVADO_VERSION := $(shell vivado -version 2>/dev/null | head -1 | grep -o '[0-9]\{4\}\.[0-9]' || echo "unknown") @@ -33,7 +38,16 @@ check_vivado_version: # Run synthesis using Vivado synth: +ifeq ($(PARTITION_SYNTH),1) + @test -f "$(PRJ)" || { echo "ERROR: project not found: $(PRJ)" >&2; exit 1; } + tools/partition_synth/run.sh \ + --origin-dir . --project "$(PRJ)" \ + --parallel "$(PARTITION_SYNTH_PARALLEL)" \ + --jobs-per-partition "$(PARTITION_SYNTH_JOBS)" \ + $(if $(filter 1,$(PARTITION_SYNTH_PROJECT_RUNS)),--project-runs,) $(if $(strip $(PARTITION_SYNTH_OUT)),--out-dir "$(PARTITION_SYNTH_OUT)",) +else vivado -mode batch -source ./tools/gen_synth.tcl -tclargs $(PRJ) +endif # Generate FPGA bitstream bitstream: diff --git a/fpga_diff/src/constr/common/clock_defs.tcl b/fpga_diff/src/constr/common/clock_defs.tcl new file mode 100644 index 00000000..9516a537 --- /dev/null +++ b/fpga_diff/src/constr/common/clock_defs.tcl @@ -0,0 +1,40 @@ +######################################################################## +# Shared primary-clock definitions for top-level and OOC synthesis. +######################################################################## + +set fpga_primary_clock_specs { + {PCIE_EP_CLK_IN 10.000 pcie_ep_gt_ref_clk_p} + {mac_rx_clk 40.000 RGMII_RXCLK} + {mac_tx_clk 40.000 RGMII_TXCLK} + {mdc_clk 400.000 MDC} + {CPU_CLK_IN 5.000 clk6_p} + {TMCLK 1000.000 clk8_p} + {DEBUG_CLK_IN 40.000 clk5_p} + {PCIE_CLK_IN 10.000 refclk_p} + {PCIE2_CLK_IN 10.000 refclk2_p} + {jtag_vclk 83.333 JTAG_TCK} +} + +proc fpga_clock_spec {clock_name} { + global fpga_primary_clock_specs + + foreach spec $fpga_primary_clock_specs { + if {[lindex $spec 0] eq $clock_name} { + return $spec + } + } + error "unknown FPGA primary clock '$clock_name'" +} + +proc fpga_clock_period {clock_name} { + return [lindex [fpga_clock_spec $clock_name] 1] +} + +proc fpga_create_clock {clock_name objects} { + create_clock -period [fpga_clock_period $clock_name] -name $clock_name $objects +} + +proc fpga_create_top_clock {clock_name} { + set spec [fpga_clock_spec $clock_name] + fpga_create_clock $clock_name [get_ports [lindex $spec 2]] +} diff --git a/fpga_diff/src/constr/common/fpga.xdc b/fpga_diff/src/constr/common/fpga.xdc index d290f61f..cf8efe56 100755 --- a/fpga_diff/src/constr/common/fpga.xdc +++ b/fpga_diff/src/constr/common/fpga.xdc @@ -1,6 +1,7 @@ ####### Main board #clk +source [file normalize [file join [file dirname [info script]] clock_defs.tcl]] set_property PACKAGE_PIN AY50 [get_ports clk8_p] @@ -54,7 +55,7 @@ set_property PACKAGE_PIN AD15 [get_ports pcie_ep_lnk_up] set_property IOSTANDARD LVCMOS33 [get_ports pcie_ep_lnk_up] set_property IOSTANDARD LVCMOS18 [get_ports pcie_ep_perstn] -create_clock -name PCIE_EP_CLK_IN -period 10.000 [get_ports pcie_ep_gt_ref_clk_p] +fpga_create_top_clock PCIE_EP_CLK_IN #gpio #set_property PACKAGE_PIN AA15 [get_ports GPIO_O0] @@ -351,8 +352,8 @@ set_property PACKAGE_PIN CB23 [get_ports PERST2_N] set_property PACKAGE_PIN Y52 [get_ports clk7_p] set_property PACKAGE_PIN Y53 [get_ports clk7_n] -create_clock -period 40.000 -name mac_rx_clk [get_ports RGMII_RXCLK] -create_clock -period 40.000 -name mac_tx_clk [get_ports RGMII_TXCLK] +fpga_create_top_clock mac_rx_clk +fpga_create_top_clock mac_tx_clk set_clock_groups -asynchronous -group [get_clocks mac_rx_clk -include_generated_clocks] create_clock -period 40.000 -name rgmii_rx_vclk_1 set_false_path -setup -rise_from [get_clocks rgmii_rx_vclk_1] -fall_to [get_clocks mac_rx_clk] @@ -422,13 +423,13 @@ set_property IOSTANDARD LVCMOS18 [get_ports RGMII_TXD3] set_property IOSTANDARD LVCMOS18 [get_ports MDC] set_property IOSTANDARD LVCMOS18 [get_ports MDIO] set_property IOSTANDARD LVCMOS18 [get_ports PHY_RESET_B] -create_clock -period 400.000 -name mdc_clk [get_ports MDC] -create_clock -period 5.000 -name CPU_CLK_IN [get_ports clk6_p] -create_clock -period 1000.000 -name TMCLK [get_ports clk8_p] -create_clock -period 40.000 -name DEBUG_CLK_IN [get_ports clk5_p] -create_clock -period 10.000 -name PCIE_CLK_IN [get_ports refclk_p] -create_clock -period 10.000 -name PCIE2_CLK_IN [get_ports refclk2_p] -create_clock -period 83.333 -name jtag_vclk [get_ports JTAG_TCK] +fpga_create_top_clock mdc_clk +fpga_create_top_clock CPU_CLK_IN +fpga_create_top_clock TMCLK +fpga_create_top_clock DEBUG_CLK_IN +fpga_create_top_clock PCIE_CLK_IN +fpga_create_top_clock PCIE2_CLK_IN +fpga_create_top_clock jtag_vclk set_clock_groups -asynchronous -group [get_clocks jtag_vclk -include_generated_clocks] set_clock_groups -asynchronous -group [get_clocks -include_generated_clocks CPU_CLK_IN] -group [get_clocks -include_generated_clocks TMCLK] set_clock_groups -asynchronous -group [get_clocks -include_generated_clocks CPU_CLK_IN] -group [get_clocks -include_generated_clocks DEBUG_CLK_IN] diff --git a/fpga_diff/tools/gen_synth.tcl b/fpga_diff/tools/gen_synth.tcl index 0a892fce..7cc51637 100644 --- a/fpga_diff/tools/gen_synth.tcl +++ b/fpga_diff/tools/gen_synth.tcl @@ -33,6 +33,20 @@ if {[llength $synth_runs] == 0} { } set synth_run [lindex $synth_runs 0] + +# A completed partition run leaves a generated root-module blackbox in the +# project. Restore the ordinary RTL before running the regular synth target. +set partition_dir [file normalize [file join [file dirname [info script]] partition_synth]] +if {[file isfile "$partition_dir/defs.tcl"] && [file isfile "$partition_dir/sources.tcl"]} { + source "$partition_dir/defs.tcl" + source "$partition_dir/sources.tcl" + ps_load_partitions + set root_module [ps_partition_top [ps_root_partition]] + if {[ps_restore_project_sources $root_module]} { + reset_run $synth_run + } +} + set status [get_property STATUS $synth_run] puts "INFO: synth_1 status: $status" diff --git a/fpga_diff/tools/partition_synth/defs.tcl b/fpga_diff/tools/partition_synth/defs.tcl new file mode 100644 index 00000000..ee15e50e --- /dev/null +++ b/fpga_diff/tools/partition_synth/defs.tcl @@ -0,0 +1,219 @@ +######################################################################## +# Common helpers for partition synthesis declarations. +######################################################################## + +set ps_dir [file normalize [file dirname [info script]]] +set ps_partition_file [file normalize "$ps_dir/partitions.tcl"] + +proc ps_load_partitions {} { + global ps_partition_file ps_partition_specs ps_partitions + global ps_partition_top ps_partition_blackboxes ps_partition_by_top + global ps_partition_children ps_partition_clocks ps_link_order ps_root_partition + + unset -nocomplain ps_partition_specs ps_partition_clock_ports ps_partitions + unset -nocomplain ps_partition_top ps_partition_blackboxes + unset -nocomplain ps_partition_by_top ps_partition_children ps_partition_clocks + unset -nocomplain ps_link_order ps_root_partition ps_visit_state + + source $ps_partition_file + if {![info exists ps_partition_specs]} { + error "partition file does not set ps_partition_specs: $ps_partition_file" + } + + set ps_partitions {} + foreach spec $ps_partition_specs { + if {[llength $spec] == 0} { + continue + } + + set kind [lindex $spec 0] + switch -- $kind { + module { + if {[llength $spec] != 3} { + error "invalid module partition declaration: $spec" + } + lassign $spec _ name top + set blackboxes {} + } + without { + if {[llength $spec] != 4} { + error "invalid without partition declaration: $spec" + } + lassign $spec _ name top blackboxes + if {[llength $blackboxes] == 0} { + error "without partition needs at least one child module: $spec" + } + } + default { + error "unknown partition declaration '$kind': $spec" + } + } + + if {$name eq "" || $top eq ""} { + error "partition name and top module must be nonempty: $spec" + } + if {![regexp {^[A-Za-z_][A-Za-z0-9_]*$} $top]} { + error "unsupported top module name '$top'" + } + foreach blackbox $blackboxes { + if {![regexp {^[A-Za-z_][A-Za-z0-9_]*$} $blackbox]} { + error "unsupported child module name '$blackbox'" + } + } + if {[lsearch -exact $ps_partitions $name] >= 0} { + error "duplicate partition name '$name'" + } + if {[info exists ps_partition_by_top($top)]} { + error "module '$top' is the top of more than one partition" + } + + lappend ps_partitions $name + set ps_partition_top($name) $top + set ps_partition_by_top($top) $name + if {[llength $blackboxes] > 0} { + set ps_partition_blackboxes($name) $blackboxes + } + } + + if {![info exists ps_partition_clock_ports]} { + set ps_partition_clock_ports {} + } + if {[llength $ps_partition_clock_ports] % 2 != 0} { + error "partition clock mapping must be a key/value list: $ps_partition_clock_ports" + } + array set declared_partition_clocks $ps_partition_clock_ports + foreach partition [array names declared_partition_clocks] { + if {[lsearch -exact $ps_partitions $partition] < 0} { + error "clock mapping references unknown partition '$partition'" + } + } + foreach partition $ps_partitions { + set clocks {} + if {[info exists declared_partition_clocks($partition)]} { + unset -nocomplain seen_clock_names seen_port_names + array set seen_clock_names {} + array set seen_port_names {} + foreach mapping $declared_partition_clocks($partition) { + if {[llength $mapping] != 2} { + error "invalid clock mapping for partition '$partition': $mapping" + } + lassign $mapping clock_name port_name + if {$clock_name eq "" || $port_name eq ""} { + error "clock mapping needs a clock and port for partition '$partition': $mapping" + } + if {[info exists seen_clock_names($clock_name)]} { + error "duplicate clock '$clock_name' for partition '$partition'" + } + if {[info exists seen_port_names($port_name)]} { + error "duplicate port '$port_name' for partition '$partition'" + } + set seen_clock_names($clock_name) 1 + set seen_port_names($port_name) 1 + lappend clocks [list $clock_name $port_name] + } + } + set ps_partition_clocks($partition) $clocks + } + + array set referenced {} + foreach partition $ps_partitions { + set children {} + foreach module [ps_partition_blackboxes $partition] { + if {![info exists ps_partition_by_top($module)]} { + error "partition '$partition' has no child partition for module '$module'" + } + set child $ps_partition_by_top($module) + if {$child eq $partition} { + error "partition '$partition' cannot contain itself" + } + lappend children $child + set referenced($child) 1 + } + set ps_partition_children($partition) $children + } + + set roots {} + foreach partition $ps_partitions { + if {![info exists referenced($partition)]} { + lappend roots $partition + } + } + if {[llength $roots] != 1} { + error "partition hierarchy must have one root, found: $roots" + } + set ps_root_partition [lindex $roots 0] + + set ps_link_order {} + ps_visit_partition $ps_root_partition + if {[llength $ps_link_order] != [llength $ps_partitions]} { + error "some partitions are disconnected from root '$ps_root_partition'" + } +} + +proc ps_visit_partition {partition} { + global ps_partition_children ps_link_order ps_visit_state + + if {[info exists ps_visit_state($partition)]} { + if {$ps_visit_state($partition) eq "visiting"} { + error "cycle detected at partition '$partition'" + } + return + } + + set ps_visit_state($partition) visiting + foreach child $ps_partition_children($partition) { + ps_visit_partition $child + } + set ps_visit_state($partition) done + lappend ps_link_order $partition +} + +proc ps_partition_top {partition} { + global ps_partition_top + return $ps_partition_top($partition) +} + +proc ps_partition_blackboxes {partition} { + global ps_partition_blackboxes + if {[info exists ps_partition_blackboxes($partition)]} { + return $ps_partition_blackboxes($partition) + } + return {} +} + +proc ps_partition_children {partition} { + global ps_partition_children + return $ps_partition_children($partition) +} + +proc ps_partition_clocks {partition} { + global ps_partition_clocks + return $ps_partition_clocks($partition) +} + +proc ps_root_partition {} { + global ps_root_partition + return $ps_root_partition +} + +proc ps_link_order {} { + global ps_link_order + return $ps_link_order +} + +if {[info exists argv0] && [file normalize $argv0] eq [file normalize [info script]]} { + if {[llength $argv] != 1} { + puts "Usage: tclsh defs.tcl partitions|link-order|root" + exit 1 + } + ps_load_partitions + switch -- [lindex $argv 0] { + partitions { puts [join $ps_partitions ","] } + link-order { puts [join [ps_link_order] ","] } + root { puts [ps_root_partition] } + default { + puts "Usage: tclsh defs.tcl partitions|link-order|root" + exit 1 + } + } +} diff --git a/fpga_diff/tools/partition_synth/header_files.tcl b/fpga_diff/tools/partition_synth/header_files.tcl new file mode 100644 index 00000000..42736e44 --- /dev/null +++ b/fpga_diff/tools/partition_synth/header_files.tcl @@ -0,0 +1,91 @@ +######################################################################## +# Mark files that are parsed through include as Verilog headers. +######################################################################## + +proc fpga_append_unique {var_name value} { + upvar 1 $var_name values + if {[lsearch -exact $values $value] < 0} { + lappend values $value + } +} + +proc fpga_included_source_files {files include_dirs} { + array set known_files {} + array set files_by_tail {} + set search_dirs {} + + foreach dir $include_dirs { + if {[file isdirectory $dir]} { + fpga_append_unique search_dirs [file normalize $dir] + } + } + foreach file $files { + if {![file isfile $file]} { + continue + } + set path [file normalize $file] + set known_files($path) 1 + lappend files_by_tail([file tail $path]) $path + fpga_append_unique search_dirs [file dirname $path] + } + + array set included_files {} + foreach file [array names known_files] { + set in [open $file r] + while {[gets $in line] >= 0} { + if {![regexp {^[[:space:]]*\x60include[[:space:]]+["<]([^">]+)[">]} $line -> include_path]} { + continue + } + + if {[file pathtype $include_path] eq "absolute"} { + set candidates [list [file normalize $include_path]] + } else { + set candidates [list [file normalize [file join [file dirname $file] $include_path]]] + foreach dir $search_dirs { + lappend candidates [file normalize [file join $dir $include_path]] + } + } + + set included_path "" + foreach candidate $candidates { + if {[info exists known_files($candidate)]} { + set included_path $candidate + break + } + } + if {$included_path eq "" && [info exists files_by_tail([file tail $include_path])]} { + set same_tail $files_by_tail([file tail $include_path]) + if {[llength $same_tail] == 1} { + set included_path [lindex $same_tail 0] + } + } + if {$included_path ne ""} { + set included_files($included_path) 1 + } + } + close $in + } + return [array names included_files] +} + +proc fpga_set_header_file_types {files include_dirs} { + array set header_files {} + foreach file [fpga_included_source_files $files $include_dirs] { + set header_files($file) 1 + } + + foreach file $files { + set objects [get_files -quiet $file] + if {[llength $objects] == 0} { + continue + } + + set extension [string tolower [file extension $file]] + set path [file normalize $file] + if {$extension in {.svh .vh} || [info exists header_files($path)]} { + set_property -name file_type -value {Verilog Header} -objects $objects + } elseif {$extension eq ".v"} { + set_property -name file_type -value {SystemVerilog} -objects $objects + } + } +} diff --git a/fpga_diff/tools/partition_synth/link.tcl b/fpga_diff/tools/partition_synth/link.tcl new file mode 100644 index 00000000..61b51883 --- /dev/null +++ b/fpga_diff/tools/partition_synth/link.tcl @@ -0,0 +1,126 @@ +######################################################################## +# Link synthesized partitions from leaves to the declared root module. +######################################################################## + +source [file normalize [file join [file dirname [info script]] defs.tcl]] +source [file normalize [file join [file dirname [info script]] sources.tcl]] + +proc usage {} { + puts "Usage:" + puts " vivado -mode batch -source tools/partition_synth/link.tcl -tclargs \\\\" + puts " --out-dir " + exit 1 +} + +proc parse_args {} { + array set opt {--out-dir ""} + for {set i 0} {$i < [llength $::argv]} {incr i} { + set key [lindex $::argv $i] + switch -- $key { + --out-dir { + incr i + if {$i >= [llength $::argv]} { usage } + set opt($key) [lindex $::argv $i] + } + --help { usage } + default { + puts "ERROR: unknown argument '$key'" + usage + } + } + } + if {$opt(--out-dir) eq ""} { usage } + return [array get opt] +} + +proc ps_write_blackbox_report {partition report_file} { + set blackboxes [get_cells -hier -quiet -filter {IS_BLACKBOX}] + set out [open $report_file w] + puts $out "blackbox_count [llength $blackboxes]" + foreach cell $blackboxes { + puts $out "$cell [get_property REF_NAME $cell]" + } + close $out + puts "INFO: linked partition $partition blackbox_count=[llength $blackboxes]" + return [llength $blackboxes] +} + +proc ps_export_partition {partition dcp edif report} { + ps_require_file $dcp + puts "INFO: exporting partition $partition: $dcp" + open_checkpoint $dcp + if {[ps_write_blackbox_report $partition $report] != 0} { + error "leaf partition '$partition' still contains blackboxes" + } + write_edif -force $edif + close_design +} + +array set opt [parse_args] +set out_dir [file normalize $opt(--out-dir)] +set linked_dir [file normalize "$out_dir/linked"] +set linked_edif_dir [file normalize "$linked_dir/edif"] +set shell_edif_dir [file normalize "$linked_dir/shell_edif"] +file mkdir $linked_dir +file mkdir $linked_edif_dir +file mkdir $shell_edif_dir + +ps_load_partitions +set part_name xcvu19p-fsva3824-2-e + +foreach partition [ps_link_order] { + set top [ps_partition_top $partition] + set children [ps_partition_children $partition] + set input_dcp [file normalize "$out_dir/${partition}.dcp"] + set linked_dcp [file normalize "$linked_dir/${partition}.dcp"] + set partition_edif_dir [file normalize "$linked_edif_dir/$partition"] + file mkdir $partition_edif_dir + set linked_edif [file normalize "$partition_edif_dir/${top}.edf"] + set report [file normalize "$linked_dir/${partition}_blackboxes.rpt"] + + if {[llength $children] == 0} { + ps_export_partition $partition $input_dcp $linked_edif $report + continue + } + + ps_require_file $input_dcp + set partition_shell_dir [file normalize "$shell_edif_dir/$partition"] + file mkdir $partition_shell_dir + set shell_edif [file normalize "$partition_shell_dir/${top}.edf"] + open_checkpoint $input_dcp + write_edif -force $shell_edif + close_design + + puts "INFO: linking partition $partition children=$children" + create_project -in_memory -part $part_name + set_property source_mgmt_mode None [current_project] + read_edif $shell_edif + foreach child $children { + set child_top [ps_partition_top $child] + set child_edif [file normalize "$linked_edif_dir/$child/${child_top}.edf"] + ps_require_file $child_edif + read_edif $child_edif + } + link_design -part $part_name -top $top -mode out_of_context + if {[ps_write_blackbox_report $partition $report] != 0} { + error "linked partition '$partition' still contains blackboxes" + } + report_utilization -file [file normalize "$linked_dir/${partition}_utilization.rpt"] + write_checkpoint -force $linked_dcp + write_edif -force $linked_edif + close_design + close_project +} + +set root [ps_root_partition] +set root_dcp [file normalize "$linked_dir/${root}.dcp"] +if {![file isfile $root_dcp]} { + set root_dcp [file normalize "$out_dir/${root}.dcp"] +} +set marker [open [file normalize "$linked_dir/root.txt"] w] +puts $marker "partition=$root" +puts $marker "module=[ps_partition_top $root]" +puts $marker "dcp=$root_dcp" +puts $marker "edif=[file normalize \"$linked_edif_dir/$root/[ps_partition_top $root].edf\"]" +close $marker +puts "INFO: partition link complete: root=$root module=[ps_partition_top $root]" diff --git a/fpga_diff/tools/partition_synth/partitions.tcl b/fpga_diff/tools/partition_synth/partitions.tcl new file mode 100644 index 00000000..b068f9a8 --- /dev/null +++ b/fpga_diff/tools/partition_synth/partitions.tcl @@ -0,0 +1,41 @@ +######################################################################## +# Partition declarations for this design. +# +# {module } +# {without } +# +# without accepts one or more immediate child modules. Each child is +# replaced with an automatically generated blackbox stub for that partition. +######################################################################## + +set ps_partition_specs { + {module external_llc_wrapper ExternalLLCWrapper} + {without external_llc_shell ExternalLLC {ExternalLLCWrapper}} + {module openncb OpenNCB} + {module memmisc MemMisc} + {without xstop_shell XSTop {XSTile ExternalLLC OpenNCB MemMisc}} + {without xstile_shell XSTile {XSCore L2Top}} + {without xscore_shell XSCore {Frontend Backend MemBlock}} + {module frontend Frontend} + {module memblock MemBlock} + {module l2top L2Top} + {without backend_without_ctrlblock Backend {CtrlBlock}} + {module backend_ctrlblock CtrlBlock} +} + +# OOC clock mapping: { }. +# Clock periods and global top-level ports stay in src/constr/common/clock_defs.tcl. +set ps_partition_clock_ports { + external_llc_wrapper {{DEBUG_CLK_IN clock}} + external_llc_shell {{DEBUG_CLK_IN clock}} + openncb {{DEBUG_CLK_IN clock}} + memmisc {{DEBUG_CLK_IN clock}} + xstop_shell {{DEBUG_CLK_IN io_clock}} + xstile_shell {{DEBUG_CLK_IN clock}} + xscore_shell {{DEBUG_CLK_IN clock}} + frontend {{DEBUG_CLK_IN clock}} + memblock {{DEBUG_CLK_IN clock}} + l2top {{DEBUG_CLK_IN clock}} + backend_without_ctrlblock {{DEBUG_CLK_IN clock}} + backend_ctrlblock {{DEBUG_CLK_IN clock}} +} diff --git a/fpga_diff/tools/partition_synth/project_launch.tcl b/fpga_diff/tools/partition_synth/project_launch.tcl new file mode 100644 index 00000000..6dd87606 --- /dev/null +++ b/fpga_diff/tools/partition_synth/project_launch.tcl @@ -0,0 +1,103 @@ +######################################################################## +# Launch previously configured project OOC runs through Vivado's run manager. +######################################################################## + +proc usage {} { + puts "Usage:" + puts { vivado -mode batch -source tools/partition_synth/project_launch.tcl -tclargs} + puts { --project --manifest --parallel } + exit 1 +} + +proc parse_args {} { + array set opt { + --project "" + --manifest "" + --parallel "" + } + + for {set i 0} {$i < [llength $::argv]} {incr i} { + set key [lindex $::argv $i] + switch -- $key { + --project - + --manifest - + --parallel { + incr i + if {$i >= [llength $::argv]} { usage } + set opt($key) [lindex $::argv $i] + } + --help { usage } + default { + puts "ERROR: unknown argument '$key'" + usage + } + } + } + + if {$opt(--project) eq "" || $opt(--manifest) eq "" || $opt(--parallel) eq ""} { + usage + } + if {![string is integer -strict $opt(--parallel)] || $opt(--parallel) < 1} { + puts "ERROR: --parallel must be a positive integer" + usage + } + return [array get opt] +} + +array set opt [parse_args] +set project [file normalize $opt(--project)] +set manifest_path [file normalize $opt(--manifest)] +if {![file isfile $project]} { + error "project not found: $project" +} +if {![file isfile $manifest_path]} { + error "manifest not found: $manifest_path" +} + +set records {} +set manifest [open $manifest_path r] +while {[gets $manifest line] >= 0} { + if {$line eq ""} { + continue + } + set record [split $line "\t"] + if {[llength $record] != 4} { + error "invalid manifest entry: $line" + } + lappend records $record +} +close $manifest +if {[llength $records] == 0} { + error "manifest is empty: $manifest_path" +} + +open_project $project +set runs {} +foreach record $records { + lassign $record partition top_module run_name run_dir + set run [get_runs -quiet $run_name] + if {[llength $run] != 1} { + error "project run not found: $run_name" + } + lappend runs $run +} + +foreach run $runs { + reset_run $run +} +puts "INFO: launching [llength $runs] project OOC run(s) with -jobs $opt(--parallel)" +launch_runs $runs -jobs $opt(--parallel) +set failed_runs {} +foreach record $records run $runs { + lassign $record partition top_module run_name run_dir + wait_on_run $run + set status [get_property STATUS $run] + puts "INFO: $run_name status=$status" + if {![string match "synth_design Complete*" $status]} { + lappend failed_runs "$run_name ($status)" + } +} +close_project +if {[llength $failed_runs] > 0} { + error "project run(s) failed: [join $failed_runs {, }]" +} diff --git a/fpga_diff/tools/partition_synth/project_link.tcl b/fpga_diff/tools/partition_synth/project_link.tcl new file mode 100644 index 00000000..a51cca52 --- /dev/null +++ b/fpga_diff/tools/partition_synth/project_link.tcl @@ -0,0 +1,170 @@ +######################################################################## +# Synthesize the FPGA top shell and link the complete root partition into it. +######################################################################## + +source [file normalize [file join [file dirname [info script]] defs.tcl]] +source [file normalize [file join [file dirname [info script]] sources.tcl]] + +proc usage {} { + puts "Usage:" + puts " vivado -mode batch -source tools/partition_synth/project_link.tcl -tclargs" + puts { --project --out-dir [--jobs ]} + puts { [--reuse-shell-dcp ]} + exit 1 +} + +proc parse_args {} { + array set opt {--project "" --out-dir "" --jobs "" --reuse-shell-dcp ""} + for {set i 0} {$i < [llength $::argv]} {incr i} { + set key [lindex $::argv $i] + switch -- $key { + --project - + --out-dir - + --jobs - + --reuse-shell-dcp { + incr i + if {$i >= [llength $::argv]} { usage } + set opt($key) [lindex $::argv $i] + } + --help { usage } + default { + puts "ERROR: unknown argument '$key'" + usage + } + } + } + if {$opt(--project) eq "" || $opt(--out-dir) eq ""} { usage } + if {$opt(--jobs) ne "" && + (![string is integer -strict $opt(--jobs)] || $opt(--jobs) < 1)} { + error "--jobs must be a positive integer" + } + return [array get opt] +} + +proc default_jobs {} { + set threads 1 + if {![catch {exec nproc} output] && + [scan [string trim $output] "%d" threads] != 1} { + set threads 1 + } + return [expr {max(1, int(ceil($threads / 2.0)))}] +} + +array set opt [parse_args] +set project [file normalize $opt(--project)] +set out_dir [file normalize $opt(--out-dir)] +ps_require_file $project +if {$opt(--reuse-shell-dcp) ne ""} { + set opt(--reuse-shell-dcp) [file normalize $opt(--reuse-shell-dcp)] + ps_require_file $opt(--reuse-shell-dcp) +} +ps_load_partitions + +set root [ps_root_partition] +set root_module [ps_partition_top $root] +set linked_dir [file normalize "$out_dir/linked"] +set project_shell_dir [file normalize "$linked_dir/project_shell"] +set root_dcp [file normalize "$linked_dir/${root}.dcp"] +if {![file isfile $root_dcp]} { + set root_dcp [file normalize "$out_dir/${root}.dcp"] +} +ps_require_file $root_dcp + +open_project $project +set project_top [get_property TOP [get_filesets sources_1]] +if {$project_top eq ""} { + error "project sources_1 has no top module" +} +set synth_runs [get_runs synth_1] +if {[llength $synth_runs] != 1} { + error "project must contain one synth_1 run" +} +set synth_run [lindex $synth_runs 0] +set synth_run_dir [file normalize [get_property DIRECTORY $synth_run]] +set final_dcp [file normalize "$synth_run_dir/${project_top}.dcp"] +set utilization_rpt [file normalize "$synth_run_dir/${project_top}_utilization_synth.rpt"] +set utilization_pb [file normalize "$synth_run_dir/${project_top}_utilization_synth.pb"] + +if {$opt(--reuse-shell-dcp) eq ""} { + ps_prepare_project_shell $root_module $out_dir + # Do not reuse a previous monolithic checkpoint for the generated shell. + set_property INCREMENTAL_CHECKPOINT {} $synth_run + set_property AUTO_INCREMENTAL_CHECKPOINT 0 $synth_run + reset_run $synth_run + + set jobs $opt(--jobs) + if {$jobs eq ""} { set jobs [default_jobs] } + puts "INFO: launching top shell synth_1 with -jobs $jobs" + launch_runs $synth_run -jobs $jobs + wait_on_run $synth_run + + set status [get_property STATUS $synth_run] + if {![string match "synth_design Complete*" $status]} { + error "top shell synthesis failed with status: $status" + } + ps_require_file $final_dcp +} else { + puts "INFO: reusing top shell checkpoint $opt(--reuse-shell-dcp)" +} + +close_project + +file mkdir $project_shell_dir +if {$opt(--reuse-shell-dcp) eq ""} { + set shell_dcp [file normalize "$project_shell_dir/${project_top}.dcp"] + file copy -force $final_dcp $shell_dcp +} else { + set shell_dcp $opt(--reuse-shell-dcp) +} +open_checkpoint $shell_dcp +set root_cells [get_cells -hier -quiet -filter "REF_NAME == $root_module && IS_BLACKBOX"] +if {[llength $root_cells] != 1} { + error "top shell must contain one $root_module blackbox, found [llength $root_cells]" +} +set shell_blackbox_count [llength [get_cells -hier -quiet -filter {IS_BLACKBOX}]] +puts "INFO: linking root checkpoint $root_dcp at [lindex $root_cells 0]" +read_checkpoint -cell [lindex $root_cells 0] $root_dcp + +set report_file [file normalize "$linked_dir/${project_top}_blackboxes.rpt"] +set report [open $report_file w] +set blackboxes [get_cells -hier -quiet -filter {IS_BLACKBOX}] +puts $report "blackbox_count [llength $blackboxes]" +foreach cell $blackboxes { + puts $report "$cell [get_property REF_NAME $cell]" +} +close $report +set unresolved_root [get_cells -hier -quiet -filter "REF_NAME == $root_module && IS_BLACKBOX"] +if {[llength $unresolved_root] != 0} { + error "root module $root_module remains a blackbox after project link" +} +# Project-managed OOC IPs remain blackboxes in a synthesis DCP. Only the +# partition root is expected to disappear after the checkpoint is inserted. +if {[llength $blackboxes] != ($shell_blackbox_count - 1)} { + error "project link changed the OOC IP blackbox count; see $report_file" +} + +set final_tmp "${final_dcp}.partition_linked" +set utilization_rpt_tmp "${utilization_rpt}.partition_linked" +set utilization_pb_tmp "${utilization_pb}.partition_linked" +write_checkpoint -force $final_tmp +report_utilization -file $utilization_rpt_tmp -pb $utilization_pb_tmp +close_design +file rename -force $final_tmp $final_dcp +file rename -force $utilization_rpt_tmp $utilization_rpt +file rename -force $utilization_pb_tmp $utilization_pb + +open_project $project +set impl_run [get_runs -quiet impl_1] +if {[llength $impl_run] > 0} { + reset_run $impl_run + puts "INFO: reset impl_1 after replacing the synthesis checkpoint" +} +close_project + +set marker [open [file normalize "$linked_dir/final.txt"] w] +puts $marker "project=$project" +puts $marker "top=$project_top" +puts $marker "dcp=$final_dcp" +puts $marker "blackbox_report=$report_file" +close $marker +puts "INFO: project link complete: $final_dcp" diff --git a/fpga_diff/tools/partition_synth/project_runs.tcl b/fpga_diff/tools/partition_synth/project_runs.tcl new file mode 100644 index 00000000..b9236d90 --- /dev/null +++ b/fpga_diff/tools/partition_synth/project_runs.tcl @@ -0,0 +1,219 @@ +######################################################################## +# Create GUI-visible OOC synthesis runs in an existing top-level project. +######################################################################## + +source [file normalize [file join [file dirname [info script]] defs.tcl]] +source [file normalize [file join [file dirname [info script]] sources.tcl]] + +proc usage {} { + puts "Usage:" + puts { vivado -mode batch -source tools/partition_synth/project_runs.tcl -tclargs} + puts { --project --origin-dir --out-dir } + puts { --partitions [--jobs ]} + exit 1 +} + +proc parse_args {} { + array set opt { + --project "" + --origin-dir . + --out-dir partition-synth + --partitions "" + --jobs "" + } + + for {set i 0} {$i < [llength $::argv]} {incr i} { + set key [lindex $::argv $i] + switch -- $key { + --project - + --origin-dir - + --out-dir - + --partitions - + --jobs { + incr i + if {$i >= [llength $::argv]} { usage } + set opt($key) [lindex $::argv $i] + } + --help { usage } + default { + puts "ERROR: unknown argument '$key'" + usage + } + } + } + + if {$opt(--project) eq "" || $opt(--partitions) eq ""} { + usage + } + if {$opt(--jobs) ne "" && + (![string is integer -strict $opt(--jobs)] || $opt(--jobs) < 1)} { + puts "ERROR: --jobs must be a positive integer" + usage + } + return [array get opt] +} + +proc ps_project_run_name {partition} { + return "partition_synth_$partition" +} + +proc ps_project_srcset_name {partition} { + return "partition_synth_${partition}_srcs" +} + +proc ps_project_constrset_name {partition} { + return "partition_synth_${partition}_constrs" +} + +proc ps_remove_project_run {partition} { + set run_name [ps_project_run_name $partition] + set old_run [get_runs -quiet $run_name] + if {[llength $old_run] > 0} { + set old_dir [get_property DIRECTORY [lindex $old_run 0]] + delete_runs $old_run + if {[file exists $old_dir]} { + file delete -force $old_dir + } + } + + foreach fileset_name [list \ + [ps_project_srcset_name $partition] \ + [ps_project_constrset_name $partition]] { + set fileset [get_filesets -quiet $fileset_name] + if {[llength $fileset] > 0} { + delete_fileset $fileset + } + } +} + +proc ps_write_project_pre_hook {path jobs} { + set hook [open $path w] + puts $hook "set_param general.maxThreads $jobs" + close $hook +} + +proc ps_write_project_post_hook {path utilization_rpt clocks_rpt} { + set hook [open $path w] + puts $hook [format {report_utilization -file {%s}} $utilization_rpt] + if {$clocks_rpt ne ""} { + puts $hook [format {report_clocks -file {%s}} $clocks_rpt] + } + close $hook +} + +array set opt [parse_args] +set project [file normalize $opt(--project)] +set origin_dir [file normalize $opt(--origin-dir)] +set out_dir [file normalize $opt(--out-dir)] +ps_require_file $project +ps_load_partitions + +set partitions [split $opt(--partitions) ,] +foreach partition $partitions { + if {[lsearch -exact $ps_partitions $partition] < 0} { + error "unknown partition '$partition'; expected one of: [join $ps_partitions {, }]" + } +} + +file mkdir $out_dir +set project_runs_dir [file normalize "$out_dir/project_runs"] +file mkdir $project_runs_dir +set manifest_path [file normalize "$project_runs_dir/manifest.tsv"] + +open_project $project +ps_restore_project_sources [ps_partition_top [ps_root_partition]] +set project_part [get_property PART [current_project]] +if {$project_part eq ""} { + error "project has no part: $project" +} + +set manifest [open $manifest_path w] +set run_records {} +array set seen_files {} +array set seen_include_dirs {} +set all_files {} +set all_include_dirs {} +foreach partition $partitions { + set setup_dir [file normalize "$project_runs_dir/$partition"] + file delete -force $setup_dir + file mkdir $setup_dir + set config [ps_partition_source_config $origin_dir $partition $setup_dir] + set files [dict get $config files] + set include_dirs [dict get $config include_dirs] + set defines [dict get $config defines] + set top_module [dict get $config top] + set clocks [dict get $config clocks] + + ps_remove_project_run $partition + set srcset_name [ps_project_srcset_name $partition] + set constrset_name [ps_project_constrset_name $partition] + create_fileset -srcset $srcset_name + create_fileset -constrset $constrset_name + set srcset [get_filesets $srcset_name] + set constrset [get_filesets $constrset_name] + add_files -norecurse -fileset $srcset $files + foreach file $files { + set normalized_file [file normalize $file] + if {![info exists seen_files($normalized_file)]} { + set seen_files($normalized_file) 1 + lappend all_files $normalized_file + } + } + foreach include_dir $include_dirs { + set normalized_dir [file normalize $include_dir] + if {![info exists seen_include_dirs($normalized_dir)]} { + set seen_include_dirs($normalized_dir) 1 + lappend all_include_dirs $normalized_dir + } + } + set_property include_dirs $include_dirs $srcset + set_property verilog_define $defines $srcset + set_property top $top_module $srcset + set_property top_auto_set 0 $srcset + + set clocks_xdc [ps_write_partition_clock_xdc $partition $clocks $setup_dir] + if {$clocks_xdc ne ""} { + add_files -norecurse -fileset $constrset $clocks_xdc + set_property used_in_implementation false [get_files -quiet $clocks_xdc] + } + + set run_name [ps_project_run_name $partition] + create_run -name $run_name -part $project_part \ + -flow {Vivado Synthesis 2020} -strategy {Vivado Synthesis Defaults} \ + -report_strategy {Vivado Synthesis Default Reports} \ + -constrset $constrset -srcset $srcset + set run [get_runs $run_name] + set_property AUTO_INCREMENTAL_CHECKPOINT 0 $run + set_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} \ + -value {-mode out_of_context} -objects $run + set_property STEPS.SYNTH_DESIGN.ARGS.GATED_CLOCK_CONVERSION auto $run + set_property STEPS.SYNTH_DESIGN.ARGS.BUFG 52 $run + set_property STEPS.SYNTH_DESIGN.ARGS.DIRECTIVE AlternateRoutability $run + + set utilization_rpt [file normalize "$out_dir/${partition}_utilization_synth.rpt"] + set clocks_rpt "" + if {[llength $clocks] > 0} { + set clocks_rpt [file normalize "$out_dir/${partition}_clocks_synth.rpt"] + } + set post_hook [file normalize "$setup_dir/${partition}_post.tcl"] + ps_write_project_post_hook $post_hook $utilization_rpt $clocks_rpt + set_property STEPS.SYNTH_DESIGN.TCL.POST $post_hook $run + if {$opt(--jobs) ne ""} { + set pre_hook [file normalize "$setup_dir/${partition}_pre.tcl"] + ps_write_project_pre_hook $pre_hook $opt(--jobs) + set_property STEPS.SYNTH_DESIGN.TCL.PRE $pre_hook $run + } + + lappend run_records [list $partition $top_module $run_name] +} + +fpga_set_header_file_types $all_files $all_include_dirs +foreach record $run_records { + lassign $record partition top_module run_name + set run_dir [file normalize [get_property DIRECTORY [get_runs $run_name]]] + puts $manifest [join [list $partition $top_module $run_name $run_dir] "\t"] + puts "INFO: configured $run_name (top=$top_module, run_dir=$run_dir)" +} +close $manifest +close_project +puts "INFO: project OOC runs configured: $manifest_path" diff --git a/fpga_diff/tools/partition_synth/run.sh b/fpga_diff/tools/partition_synth/run.sh new file mode 100755 index 00000000..5e7556d9 --- /dev/null +++ b/fpga_diff/tools/partition_synth/run.sh @@ -0,0 +1,271 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +origin_dir="$(cd "$script_dir/../.." && pwd)" +out_dir="" +partitions="" +jobs_per_partition="" +parallel=16 +project="" +project_runs=0 +dry_run=0 + +usage() { + cat <<'EOF' +Usage: + tools/partition_synth/run.sh [options] + +Options: + --origin-dir DIR fpga_diff directory, default is this script's parent + --out-dir DIR output directory, default partition-synth- + --partitions LIST comma-separated partitions; overrides list expansion + --jobs-per-partition N Vivado maxThreads per partition process + --parallel N max concurrent Vivado partition processes + --project XPR link partition DCPs into this top-level project + --project-runs create GUI-visible OOC runs in --project + --dry-run validate file lists without launching synth_design + --help show this help +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --origin-dir) + origin_dir="$(cd "$2" && pwd)" + shift 2 + ;; + --out-dir) + out_dir="$2" + shift 2 + ;; + --partitions) + partitions="$2" + shift 2 + ;; + --jobs-per-partition) + jobs_per_partition="$2" + shift 2 + ;; + --parallel) + parallel="$2" + shift 2 + ;; + --project) + project="$(realpath "$2")" + shift 2 + ;; + --project-runs) + project_runs=1 + shift + ;; + --dry-run) + dry_run=1 + shift + ;; + --help) + usage + exit 0 + ;; + *) + echo "ERROR: unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if ! [[ "$parallel" =~ ^[0-9]+$ ]] || [[ "$parallel" -lt 1 ]]; then + echo "ERROR: --parallel must be a positive integer" >&2 + exit 1 +fi + +if [[ -n "$jobs_per_partition" ]]; then + if ! [[ "$jobs_per_partition" =~ ^[0-9]+$ ]] || [[ "$jobs_per_partition" -lt 1 ]]; then + echo "ERROR: --jobs-per-partition must be a positive integer" >&2 + exit 1 + fi +fi + +if [[ -n "$project" && ! -f "$project" ]]; then + echo "ERROR: project not found: $project" >&2 + exit 1 +fi + +if [[ "$project_runs" -eq 1 && -z "$project" ]]; then + echo "ERROR: --project-runs requires --project" >&2 + exit 1 +fi + +if [[ "$project_runs" -eq 1 && "$dry_run" -eq 1 ]]; then + echo "ERROR: --project-runs cannot be used with --dry-run" >&2 + exit 1 +fi + +if [[ -z "$out_dir" ]]; then + out_dir="$origin_dir/partition-synth-$(date +%Y%m%d-%H%M%S)" +fi + +if [[ -z "$partitions" ]]; then + if ! command -v tclsh >/dev/null 2>&1; then + echo "ERROR: tclsh is required to expand partitions.tcl" >&2 + exit 1 + fi + partitions="$(tclsh "$script_dir/defs.tcl" partitions)" +fi + +mkdir -p "$out_dir" +out_dir="$(cd "$out_dir" && pwd)" +log_dir="$out_dir/logs" +mkdir -p "$log_dir" + +IFS=',' read -r -a partition_array <<< "$partitions" + +echo "INFO: origin_dir=$origin_dir" +echo "INFO: out_dir=$out_dir" +echo "INFO: partitions=${partition_array[*]}" +echo "INFO: parallel=$parallel" +if [[ -n "$jobs_per_partition" ]]; then + echo "INFO: jobs_per_partition=$jobs_per_partition" +fi +if [[ "$dry_run" -eq 1 ]]; then + echo "INFO: dry_run=1" +fi +if [[ "$project_runs" -eq 1 ]]; then + echo "INFO: project_runs=1" +fi + +failed=0 + +wait_for_slot() { + while (( $(jobs -rp | wc -l) >= parallel )); do + wait -n || failed=1 + done +} + +if [[ "$project_runs" -eq 1 ]]; then + setup_args=( + -mode batch + -source "$script_dir/project_runs.tcl" + -tclargs + --project "$project" + --origin-dir "$origin_dir" + --out-dir "$out_dir" + --partitions "$partitions" + ) + if [[ -n "$jobs_per_partition" ]]; then + setup_args+=(--jobs "$jobs_per_partition") + fi + + echo "INFO: creating project OOC runs in $project" + (cd "$origin_dir" && vivado "${setup_args[@]}") >"$log_dir/project_runs_setup.log" 2>&1 + + manifest="$out_dir/project_runs/manifest.tsv" + if [[ ! -s "$manifest" ]]; then + echo "ERROR: missing project run manifest: $manifest" >&2 + exit 1 + fi + + declare -a project_partitions=() + declare -a project_tops=() + declare -a project_run_dirs=() + while IFS=$'\t' read -r partition top_module run_name run_dir; do + [[ -n "$partition" ]] || continue + project_partitions+=("$partition") + project_tops+=("$top_module") + project_run_dirs+=("$run_dir") + rm -f "$out_dir/$partition.dcp" \ + "$out_dir/${partition}_utilization_synth.rpt" \ + "$out_dir/${partition}_clocks_synth.rpt" \ + "$log_dir/${partition}.log" + ln -s "$run_dir/runme.log" "$log_dir/${partition}.log" + done < "$manifest" + + launch_args=( + -mode batch + -source "$script_dir/project_launch.tcl" + -tclargs + --project "$project" + --manifest "$manifest" + --parallel "$parallel" + ) + echo "INFO: launching project OOC runs through Vivado" + (cd "$origin_dir" && vivado "${launch_args[@]}") >"$log_dir/project_runs_launch.log" 2>&1 +else + for partition in "${partition_array[@]}"; do + args=( + -mode batch + -source "$script_dir/synth.tcl" + -tclargs + --origin-dir "$origin_dir" + --out-dir "$out_dir" + --partition "$partition" + ) + if [[ -n "$jobs_per_partition" ]]; then + args+=(--jobs "$jobs_per_partition") + fi + if [[ "$dry_run" -eq 1 ]]; then + args+=(--dry-run) + fi + + echo "INFO: launching partition $partition" + (cd "$origin_dir" && vivado "${args[@]}") >"$log_dir/${partition}.log" 2>&1 & + wait_for_slot + done +fi + +while (( $(jobs -rp | wc -l) )); do + wait -n || failed=1 +done + +if [[ "$failed" -ne 0 ]]; then + echo "ERROR: one or more partitions failed; see $log_dir" >&2 + exit 1 +fi + +if [[ "$project_runs" -eq 1 ]]; then + for index in "${!project_partitions[@]}"; do + partition="${project_partitions[$index]}" + top_module="${project_tops[$index]}" + run_dir="${project_run_dirs[$index]}" + run_dcp="$run_dir/$top_module.dcp" + run_complete="$run_dir/__synthesis_is_complete__" + if [[ ! -f "$run_dcp" || ! -f "$run_complete" ]]; then + echo "ERROR: project run did not complete successfully: $partition" >&2 + failed=1 + continue + fi + cp -f "$run_dcp" "$out_dir/$partition.dcp" + done + if [[ "$failed" -ne 0 ]]; then + exit 1 + fi +fi + +if [[ -n "$project" && "$dry_run" -eq 0 ]]; then + link_args=( + -mode batch + -source "$script_dir/link.tcl" + -tclargs + --out-dir "$out_dir" + ) + + echo "INFO: linking partition DCPs" + (cd "$origin_dir" && vivado "${link_args[@]}") >"$log_dir/link.log" 2>&1 + + project_link_args=( + -mode batch + -source "$script_dir/project_link.tcl" + -tclargs + --project "$project" + --out-dir "$out_dir" + ) + if [[ -n "$jobs_per_partition" ]]; then + project_link_args+=(--jobs "$jobs_per_partition") + fi + + echo "INFO: linking root partition into $project" + (cd "$origin_dir" && vivado "${project_link_args[@]}") >"$log_dir/project_link.log" 2>&1 +fi + +echo "INFO: partition synthesis run finished: $out_dir" diff --git a/fpga_diff/tools/partition_synth/sources.tcl b/fpga_diff/tools/partition_synth/sources.tcl new file mode 100644 index 00000000..f7b7c2db --- /dev/null +++ b/fpga_diff/tools/partition_synth/sources.tcl @@ -0,0 +1,302 @@ +######################################################################## +# RTL source and project-shell helpers. +######################################################################## + +source [file normalize [file join [file dirname [info script]] header_files.tcl]] + +proc ps_require_file {path} { + if {![file isfile $path]} { + error "required file not found: $path" + } +} + +proc ps_module_declaration_index {module text} { + set pattern [format \ + {^[ \t]*(?:\(\*[^\n]*\*\)[ \t]*)?module[ \t]+%s([ \t(#]|$)} $module] + if {[regexp -line -indices -- $pattern $text match]} { + return [lindex $match 0] + } + return -1 +} + +proc ps_module_declarations {text} { + set declarations {} + set pattern \ + {^[ \t]*(?:\(\*[^\n]*\*\)[ \t]*)?module[ \t]+([A-Za-z_][A-Za-z0-9_]*)} + foreach {declaration module} [regexp -all -inline -line -- $pattern $text] { + lappend declarations $module + } + return $declarations +} + +proc ps_file_module_index {module file} { + if {![file isfile $file] || [file extension $file] ni {.v .sv}} { + return -1 + } + set in [open $file r] + set text [read $in] + close $in + return [ps_module_declaration_index $module $text] +} + +proc ps_find_module_source {module files} { + foreach file $files { + if {[file tail $file] in [list "$module.sv" "$module.v"] && + [ps_file_module_index $module $file] >= 0} { + return [file normalize $file] + } + } + + foreach file $files { + if {[ps_file_module_index $module $file] >= 0} { + return [file normalize $file] + } + } + return "" +} + +proc ps_find_blackbox_sources {modules files} { + set sources {} + array set source_modules {} + foreach module $modules { + set source [ps_find_module_source $module $files] + if {$source eq ""} { + error "cannot find source for module $module" + } + set source [file normalize $source] + if {[info exists source_modules($source)]} { + error "child modules $source_modules($source) and $module share source $source" + } + set source_modules($source) $module + lappend sources $source + } + return $sources +} + +proc ps_remove_source_files {files removed_sources} { + array set removed {} + foreach source $removed_sources { + set removed([file normalize $source]) 1 + } + + set filtered {} + foreach file $files { + if {![info exists removed([file normalize $file])]} { + lappend filtered $file + } + } + return $filtered +} + +proc ps_write_blackbox_stub {module src_file out_file} { + ps_require_file $src_file + + set in [open $src_file r] + set text [read $in] + close $in + + set module_idx [ps_module_declaration_index $module $text] + if {$module_idx < 0} { + error "module $module not found in $src_file" + } + set declaration_count 0 + foreach declaration [ps_module_declarations $text] { + if {$declaration eq $module} { + incr declaration_count + } + } + if {$declaration_count != 1} { + error "expected one declaration of $module in $src_file, found $declaration_count" + } + set header_end [string first ";" $text $module_idx] + if {$header_end < 0} { + error "failed to find $module declaration terminator in $src_file" + } + if {![regexp -line -indices -start $header_end -- \ + {^[ \t]*endmodule[^\n]*} $text module_end_match] && + ![regexp -indices -start $header_end -- \ + {\mendmodule\M[^\n]*} $text module_end_match]} { + error "failed to find $module endmodule in $src_file" + } + set module_end [lindex $module_end_match 1] + + file mkdir [file dirname $out_file] + set out [open $out_file w] + puts $out [string range $text 0 [expr {$module_idx - 1}]] + puts $out "(* black_box *) [string range $text $module_idx $header_end]" + puts $out "endmodule" + puts -nonewline $out [string range $text [expr {$module_end + 1}] end] + close $out +} + +proc ps_add_blackbox_stubs {modules source_files run_dir} { + set stubs {} + foreach module $modules source_file $source_files { + set stub_file [file normalize "$run_dir/${module}_blackbox.sv"] + ps_write_blackbox_stub $module $source_file $stub_file + lappend stubs $stub_file + } + return $stubs +} + +proc ps_partition_source_config {origin_dir partition run_dir} { + global fpga_primary_clock_specs + + set origin_dir [file normalize $origin_dir] + set run_dir [file normalize $run_dir] + set tcl_dir [file normalize "$origin_dir/src/tcl"] + set clock_defs [file normalize "$origin_dir/src/constr/common/clock_defs.tcl"] + + foreach file [list \ + "$tcl_dir/common/defines.tcl" \ + "$tcl_dir/common/include_dirs.tcl" \ + "$tcl_dir/cpu_files.tcl" \ + $clock_defs] { + ps_require_file $file + } + + source "$tcl_dir/common/defines.tcl" + source "$tcl_dir/common/include_dirs.tcl" + source $clock_defs + source "$tcl_dir/cpu_files.tcl" + + set chi_files {} + if {[file isfile "$tcl_dir/chi_files.tcl"]} { + source "$tcl_dir/chi_files.tcl" + } + + set all_source_files [list {*}$cpu_files {*}$chi_files] + if {[llength $all_source_files] == 0} { + error "source_files is empty" + } + + set partition_files [list {*}$all_source_files] + set partition_include_dirs [list {*}$include_dirs] + foreach source_file $all_source_files { + set source_dir [file normalize [file dirname $source_file]] + if {[lsearch -exact $partition_include_dirs $source_dir] < 0} { + lappend partition_include_dirs $source_dir + } + } + + set partition_defines [list {*}$defines] + if {[llength $chi_files] > 0} { + lappend partition_defines MSI_MODE CONFIG_USE_XSCORE_CHI + } else { + lappend partition_defines CONFIG_USE_XSCORE_AXI + } + lappend partition_defines XDMA_PCIE_LANES=4 + + set top_module [ps_partition_top $partition] + set blackbox_modules [ps_partition_blackboxes $partition] + set partition_clocks [ps_partition_clocks $partition] + foreach mapping $partition_clocks { + lassign $mapping clock_name port_name + fpga_clock_period $clock_name + } + + set missing 0 + foreach file $partition_files { + if {![file isfile $file]} { + puts "ERROR: missing source: $file" + set missing 1 + } + } + if {$missing} { + error "partition '$partition' has missing sources" + } + + set generated_stubs {} + if {[llength $blackbox_modules] > 0} { + set blackbox_sources [ps_find_blackbox_sources $blackbox_modules $partition_files] + set generated_stubs [ps_add_blackbox_stubs $blackbox_modules $blackbox_sources $run_dir] + set partition_files [ps_remove_source_files $partition_files $blackbox_sources] + lappend partition_files {*}$generated_stubs + } + if {[llength $partition_files] == 0} { + error "partition_files($partition) is empty" + } + + return [dict create \ + files $partition_files \ + include_dirs $partition_include_dirs \ + defines $partition_defines \ + top $top_module \ + blackboxes $blackbox_modules \ + clocks $partition_clocks \ + generated_stubs $generated_stubs] +} + +proc ps_write_partition_clock_xdc {partition clocks run_dir} { + if {[llength $clocks] == 0} { + return "" + } + + file mkdir $run_dir + set clocks_xdc_file [file normalize "$run_dir/${partition}_clocks.xdc"] + set clocks_xdc [open $clocks_xdc_file w] + puts $clocks_xdc "# Generated from src/constr/common/clock_defs.tcl." + foreach mapping $clocks { + lassign $mapping clock_name port_name + puts $clocks_xdc [format {create_clock -period %s -name %s [get_ports {%s}]} \ + [fpga_clock_period $clock_name] $clock_name $port_name] + } + close $clocks_xdc + return $clocks_xdc_file +} + +proc ps_project_source_files {} { + set files {} + foreach file [get_files -quiet -of_objects [get_filesets sources_1]] { + if {[file extension $file] in {.v .sv} && [file isfile $file]} { + lappend files [file normalize $file] + } + } + return $files +} + +proc ps_project_root_stubs {module} { + set tail "${module}_partition_blackbox.sv" + set stubs {} + foreach file [get_files -quiet -of_objects [get_filesets sources_1]] { + if {[file tail $file] eq $tail} { + lappend stubs $file + } + } + return $stubs +} + +proc ps_restore_project_sources {module} { + set stubs [ps_project_root_stubs $module] + if {[llength $stubs] == 0} { + return 0 + } + + remove_files $stubs + set source [ps_find_module_source $module [ps_project_source_files]] + if {$source eq ""} { + error "cannot restore source for root module $module" + } + set_property USED_IN_SYNTHESIS true [get_files $source] + update_compile_order -fileset sources_1 + puts "INFO: restored project source for $module: $source" + return 1 +} + +proc ps_prepare_project_shell {module out_dir} { + ps_restore_project_sources $module + + set source [ps_find_module_source $module [ps_project_source_files]] + if {$source eq ""} { + error "cannot find project source for root module $module" + } + + set stub [file normalize "$out_dir/top_shell/${module}_partition_blackbox.sv"] + ps_write_blackbox_stub $module $source $stub + set_property USED_IN_SYNTHESIS false [get_files $source] + set stub_object [add_files -norecurse -fileset sources_1 $stub] + set_property file_type SystemVerilog $stub_object + update_compile_order -fileset sources_1 + puts "INFO: project shell replaces $source with $stub" + return $stub +} diff --git a/fpga_diff/tools/partition_synth/synth.tcl b/fpga_diff/tools/partition_synth/synth.tcl new file mode 100644 index 00000000..9852d778 --- /dev/null +++ b/fpga_diff/tools/partition_synth/synth.tcl @@ -0,0 +1,161 @@ +######################################################################## +# Partition synthesis for FPGA DiffTest experiments. +# +# This script reuses the normal fpga_diff file lists. It is an experiment +# helper, not a replacement for the project creation script. +######################################################################## + +source [file normalize [file join [file dirname [info script]] defs.tcl]] +source [file normalize [file join [file dirname [info script]] sources.tcl]] + +proc usage {} { + puts "Usage:" + puts " vivado -mode batch -source tools/partition_synth/synth.tcl -tclargs \\" + puts " --origin-dir --out-dir \\" + puts " --partition \[--jobs \] \[--dry-run\]" + exit 1 +} + +proc parse_args {} { + array set opt { + --origin-dir . + --out-dir partition-synth + --partition "" + --jobs "" + --dry-run 0 + } + + for {set i 0} {$i < [llength $::argv]} {incr i} { + set key [lindex $::argv $i] + switch -- $key { + --origin-dir - + --out-dir - + --partition - + --jobs { + incr i + if {$i >= [llength $::argv]} { usage } + set opt($key) [lindex $::argv $i] + } + --dry-run { + set opt(--dry-run) 1 + } + --help { + usage + } + default { + puts "ERROR: unknown argument '$key'" + usage + } + } + } + + if {$opt(--jobs) ne ""} { + if {![string is integer -strict $opt(--jobs)] || $opt(--jobs) < 1} { + puts "ERROR: --jobs must be a positive integer" + usage + } + } + + return [array get opt] +} + +array set opt [parse_args] + +set origin_dir [file normalize $opt(--origin-dir)] +set out_dir [file normalize $opt(--out-dir)] +set partition $opt(--partition) +ps_load_partitions + +if {$partition eq ""} { + puts "ERROR: --partition is required" + usage +} +if {[lsearch -exact $ps_partitions $partition] < 0} { + puts "ERROR: --partition must be one of: [join $ps_partitions {, }]" + usage +} + +file mkdir $out_dir +set run_dir [file normalize "$out_dir/$partition"] +file mkdir $run_dir +set partition_config [ps_partition_source_config $origin_dir $partition $run_dir] +set partition_files [dict get $partition_config files] +set partition_include_dirs [dict get $partition_config include_dirs] +set partition_defines [dict get $partition_config defines] +set top_module [dict get $partition_config top] +set blackbox_modules [dict get $partition_config blackboxes] +set partition_clocks [dict get $partition_config clocks] +set generated_stubs [dict get $partition_config generated_stubs] +set dcp_file [file normalize "$out_dir/$partition.dcp"] +set rpt_file [file normalize [format "%s/%s_utilization_synth.rpt" $out_dir $partition]] +set clocks_rpt_file [file normalize [format "%s/%s_clocks_synth.rpt" $out_dir $partition]] + +puts "INFO: origin_dir=$origin_dir" +puts "INFO: out_dir=$out_dir" +puts "INFO: partition=$partition" +puts "INFO: top_module=$top_module" +if {[llength $blackbox_modules] > 0} { + puts "INFO: blackbox_modules=$blackbox_modules" +} +if {[llength $partition_clocks] > 0} { + puts "INFO: partition_clocks=$partition_clocks" +} +puts "INFO: source_count=[llength $partition_files]" +puts "INFO: include_dir_count=[llength $partition_include_dirs]" +puts "INFO: dcp_file=$dcp_file" +if {[llength $generated_stubs] > 0} { + puts "INFO: generated_files=$generated_stubs" +} + +if {$opt(--dry-run)} { + puts "INFO: dry-run requested; exiting before Vivado project creation" + exit 0 +} + +if {$opt(--jobs) ne ""} { + set max_threads $opt(--jobs) + if {$max_threads > 32} { + puts "WARNING: clamping --jobs $max_threads to Vivado general.maxThreads limit 32" + set max_threads 32 + } + set_param general.maxThreads $max_threads +} + +cd $run_dir +create_project "ps_$partition" . -part xcvu19p-fsva3824-2-e -force +set_property -name source_mgmt_mode -value "None" -objects [current_project] +set_property -name xpm_libraries -value "XPM_CDC XPM_MEMORY" -objects [current_project] + +set srcset [get_filesets sources_1] +add_files -norecurse -fileset $srcset $partition_files +fpga_set_header_file_types $partition_files $partition_include_dirs + +set_property -name include_dirs -value $partition_include_dirs -objects $srcset +set_property -name verilog_define -value $partition_defines -objects $srcset +set_property -name top -value $top_module -objects $srcset +set_property -name top_auto_set -value 0 -objects $srcset + +set clocks_xdc_file [ps_write_partition_clock_xdc $partition $partition_clocks $run_dir] +if {$clocks_xdc_file ne ""} { + set constrset [get_filesets constrs_1] + add_files -norecurse -fileset $constrset $clocks_xdc_file + set_property used_in_implementation false [get_files -quiet $clocks_xdc_file] + puts "INFO: clocks_xdc=$clocks_xdc_file" +} + +update_compile_order -fileset sources_1 + +synth_design \ + -mode out_of_context \ + -top $top_module \ + -part xcvu19p-fsva3824-2-e \ + -gated_clock_conversion auto \ + -bufg 52 \ + -directive AlternateRoutability + +write_checkpoint -force $dcp_file +report_utilization -file $rpt_file +if {[llength $partition_clocks] > 0} { + report_clocks -file $clocks_rpt_file +} +puts "INFO: partition synthesis complete: $dcp_file" From 986ca6cbecf4c17f18fe933caccb2380857af259 Mon Sep 17 00:00:00 2001 From: klin02 Date: Sat, 18 Jul 2026 01:28:04 +0800 Subject: [PATCH 2/6] test(fpga_diff): partition external RTL sources Reuse generated RTL include lists when running the local CMN partition synthesis experiment. (cherry picked from commit 081b4324c36bce0759b192a10d9d827ae862c461) --- fpga_diff/tools/partition_synth/sources.tcl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fpga_diff/tools/partition_synth/sources.tcl b/fpga_diff/tools/partition_synth/sources.tcl index f7b7c2db..00d19fcc 100644 --- a/fpga_diff/tools/partition_synth/sources.tcl +++ b/fpga_diff/tools/partition_synth/sources.tcl @@ -158,6 +158,8 @@ proc ps_partition_source_config {origin_dir partition run_dir} { source "$tcl_dir/common/defines.tcl" source "$tcl_dir/common/include_dirs.tcl" source $clock_defs + set cpu_files_has_rtl_include 0 + set rtl_include_files {} source "$tcl_dir/cpu_files.tcl" set chi_files {} @@ -165,7 +167,7 @@ proc ps_partition_source_config {origin_dir partition run_dir} { source "$tcl_dir/chi_files.tcl" } - set all_source_files [list {*}$cpu_files {*}$chi_files] + set all_source_files [list {*}$cpu_files {*}$chi_files {*}$rtl_include_files] if {[llength $all_source_files] == 0} { error "source_files is empty" } @@ -182,6 +184,8 @@ proc ps_partition_source_config {origin_dir partition run_dir} { set partition_defines [list {*}$defines] if {[llength $chi_files] > 0} { lappend partition_defines MSI_MODE CONFIG_USE_XSCORE_CHI + } elseif {$cpu_files_has_rtl_include} { + lappend partition_defines MSI_MODE CONFIG_USE_XSCORE_AXI } else { lappend partition_defines CONFIG_USE_XSCORE_AXI } From 04e9ee7731ad292fb51834296f878f29d2f780b3 Mon Sep 17 00:00:00 2001 From: klin02 Date: Sat, 18 Jul 2026 02:11:11 +0800 Subject: [PATCH 3/6] perf(fpga_diff): speed up header file detection (cherry picked from commit f6995899e648fd84672a42afc665c06fee77b776) --- fpga_diff/src/tcl/common/header_files.tcl | 44 ++++++++++++++++--- .../tools/partition_synth/header_files.tcl | 33 ++++++++++---- 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/fpga_diff/src/tcl/common/header_files.tcl b/fpga_diff/src/tcl/common/header_files.tcl index 6083b621..642dfd91 100644 --- a/fpga_diff/src/tcl/common/header_files.tcl +++ b/fpga_diff/src/tcl/common/header_files.tcl @@ -11,6 +11,7 @@ proc fpga_append_unique {var_name value} { proc fpga_included_source_files {files include_dirs} { array set known_files {} + array set files_by_tail {} set search_dirs {} foreach dir $include_dirs { @@ -24,6 +25,8 @@ proc fpga_included_source_files {files include_dirs} { } set path [file normalize $file] set known_files($path) 1 + lappend files_by_tail([file tail $path]) $path + fpga_append_unique search_dirs [file dirname $path] } array set included_files {} @@ -34,6 +37,13 @@ proc fpga_included_source_files {files include_dirs} { continue } + set include_tail [file tail $include_path] + if {[info exists files_by_tail($include_tail)] && + [llength $files_by_tail($include_tail)] == 1} { + set included_files([lindex $files_by_tail($include_tail) 0]) 1 + continue + } + if {[file pathtype $include_path] eq "absolute"} { set candidates [list [file normalize $include_path]] } else { @@ -43,12 +53,22 @@ proc fpga_included_source_files {files include_dirs} { } } + set included_path "" foreach candidate $candidates { if {[info exists known_files($candidate)]} { - set included_files($candidate) 1 + set included_path $candidate break } } + if {$included_path eq "" && [info exists files_by_tail($include_tail)]} { + set same_tail $files_by_tail($include_tail) + if {[llength $same_tail] == 1} { + set included_path [lindex $same_tail 0] + } + } + if {$included_path ne ""} { + set included_files($included_path) 1 + } } close $in } @@ -61,17 +81,27 @@ proc fpga_set_header_file_types {files include_dirs} { set header_files($file) 1 } + set header_paths {} + set verilog_paths {} foreach file $files { - set objects [get_files -quiet $file] - if {[llength $objects] == 0} { - continue - } - set extension [string tolower [file extension $file]] set path [file normalize $file] if {$extension in {.svh .vh} || [info exists header_files($path)]} { - set_property -name file_type -value {Verilog Header} -objects $objects + lappend header_paths $path } elseif {$extension eq ".v"} { + lappend verilog_paths $path + } + } + + if {[llength $header_paths] > 0} { + set objects [get_files -quiet $header_paths] + if {[llength $objects] > 0} { + set_property -name file_type -value {Verilog Header} -objects $objects + } + } + if {[llength $verilog_paths] > 0} { + set objects [get_files -quiet $verilog_paths] + if {[llength $objects] > 0} { set_property -name file_type -value {SystemVerilog} -objects $objects } } diff --git a/fpga_diff/tools/partition_synth/header_files.tcl b/fpga_diff/tools/partition_synth/header_files.tcl index 42736e44..642dfd91 100644 --- a/fpga_diff/tools/partition_synth/header_files.tcl +++ b/fpga_diff/tools/partition_synth/header_files.tcl @@ -37,6 +37,13 @@ proc fpga_included_source_files {files include_dirs} { continue } + set include_tail [file tail $include_path] + if {[info exists files_by_tail($include_tail)] && + [llength $files_by_tail($include_tail)] == 1} { + set included_files([lindex $files_by_tail($include_tail) 0]) 1 + continue + } + if {[file pathtype $include_path] eq "absolute"} { set candidates [list [file normalize $include_path]] } else { @@ -53,8 +60,8 @@ proc fpga_included_source_files {files include_dirs} { break } } - if {$included_path eq "" && [info exists files_by_tail([file tail $include_path])]} { - set same_tail $files_by_tail([file tail $include_path]) + if {$included_path eq "" && [info exists files_by_tail($include_tail)]} { + set same_tail $files_by_tail($include_tail) if {[llength $same_tail] == 1} { set included_path [lindex $same_tail 0] } @@ -74,17 +81,27 @@ proc fpga_set_header_file_types {files include_dirs} { set header_files($file) 1 } + set header_paths {} + set verilog_paths {} foreach file $files { - set objects [get_files -quiet $file] - if {[llength $objects] == 0} { - continue - } - set extension [string tolower [file extension $file]] set path [file normalize $file] if {$extension in {.svh .vh} || [info exists header_files($path)]} { - set_property -name file_type -value {Verilog Header} -objects $objects + lappend header_paths $path } elseif {$extension eq ".v"} { + lappend verilog_paths $path + } + } + + if {[llength $header_paths] > 0} { + set objects [get_files -quiet $header_paths] + if {[llength $objects] > 0} { + set_property -name file_type -value {Verilog Header} -objects $objects + } + } + if {[llength $verilog_paths] > 0} { + set objects [get_files -quiet $verilog_paths] + if {[llength $objects] > 0} { set_property -name file_type -value {SystemVerilog} -objects $objects } } From 75c37730abf4ef59bbac48c1383e51ce533101ce Mon Sep 17 00:00:00 2001 From: klin02 Date: Thu, 23 Jul 2026 03:56:38 +0800 Subject: [PATCH 4/6] fix(fpga_diff): restore partition DCP linking --- fpga_diff/Makefile | 4 +- fpga_diff/tools/gen_synth.tcl | 10 +- fpga_diff/tools/partition_synth/defs.tcl | 86 ++----------- fpga_diff/tools/partition_synth/link.tcl | 87 +++++++++++-- .../tools/partition_synth/partitions.tcl | 3 + .../tools/partition_synth/project_link.tcl | 115 ++++++++---------- .../tools/partition_synth/project_runs.tcl | 2 +- fpga_diff/tools/partition_synth/run.sh | 31 ++--- fpga_diff/tools/partition_synth/sources.tcl | 31 +++-- 9 files changed, 171 insertions(+), 198 deletions(-) diff --git a/fpga_diff/Makefile b/fpga_diff/Makefile index 7ee870c6..98d90287 100755 --- a/fpga_diff/Makefile +++ b/fpga_diff/Makefile @@ -16,7 +16,6 @@ PARTITION_SYNTH ?= 0 PARTITION_SYNTH_OUT ?= PARTITION_SYNTH_PARALLEL ?= 16 PARTITION_SYNTH_JOBS ?= 8 -PARTITION_SYNTH_PROJECT_RUNS ?= 1 export ENABLE_ILA ILA_DEPTH XDMA_LINK_WIDTH VIVADO_JOBS DDR_RANK_WIDTH # Supported CPU parameters: # kmh @@ -43,8 +42,7 @@ ifeq ($(PARTITION_SYNTH),1) tools/partition_synth/run.sh \ --origin-dir . --project "$(PRJ)" \ --parallel "$(PARTITION_SYNTH_PARALLEL)" \ - --jobs-per-partition "$(PARTITION_SYNTH_JOBS)" \ - $(if $(filter 1,$(PARTITION_SYNTH_PROJECT_RUNS)),--project-runs,) $(if $(strip $(PARTITION_SYNTH_OUT)),--out-dir "$(PARTITION_SYNTH_OUT)",) + --jobs-per-partition "$(PARTITION_SYNTH_JOBS)"$(if $(strip $(PARTITION_SYNTH_OUT)), --out-dir "$(PARTITION_SYNTH_OUT)") else vivado -mode batch -source ./tools/gen_synth.tcl -tclargs $(PRJ) endif diff --git a/fpga_diff/tools/gen_synth.tcl b/fpga_diff/tools/gen_synth.tcl index 7cc51637..a65ed2c2 100644 --- a/fpga_diff/tools/gen_synth.tcl +++ b/fpga_diff/tools/gen_synth.tcl @@ -34,19 +34,13 @@ if {[llength $synth_runs] == 0} { set synth_run [lindex $synth_runs 0] -# A completed partition run leaves a generated root-module blackbox in the -# project. Restore the ordinary RTL before running the regular synth target. set partition_dir [file normalize [file join [file dirname [info script]] partition_synth]] -if {[file isfile "$partition_dir/defs.tcl"] && [file isfile "$partition_dir/sources.tcl"]} { - source "$partition_dir/defs.tcl" +if {[file isfile "$partition_dir/sources.tcl"]} { source "$partition_dir/sources.tcl" - ps_load_partitions - set root_module [ps_partition_top [ps_root_partition]] - if {[ps_restore_project_sources $root_module]} { + if {[ps_restore_project_sources] > 0} { reset_run $synth_run } } - set status [get_property STATUS $synth_run] puts "INFO: synth_1 status: $status" diff --git a/fpga_diff/tools/partition_synth/defs.tcl b/fpga_diff/tools/partition_synth/defs.tcl index ee15e50e..a75393a0 100644 --- a/fpga_diff/tools/partition_synth/defs.tcl +++ b/fpga_diff/tools/partition_synth/defs.tcl @@ -1,5 +1,5 @@ ######################################################################## -# Common helpers for partition synthesis declarations. +# Common helpers for parallel OOC partition declarations. ######################################################################## set ps_dir [file normalize [file dirname [info script]]] @@ -7,13 +7,11 @@ set ps_partition_file [file normalize "$ps_dir/partitions.tcl"] proc ps_load_partitions {} { global ps_partition_file ps_partition_specs ps_partitions - global ps_partition_top ps_partition_blackboxes ps_partition_by_top - global ps_partition_children ps_partition_clocks ps_link_order ps_root_partition + global ps_partition_top ps_partition_blackboxes ps_partition_clocks unset -nocomplain ps_partition_specs ps_partition_clock_ports ps_partitions unset -nocomplain ps_partition_top ps_partition_blackboxes - unset -nocomplain ps_partition_by_top ps_partition_children ps_partition_clocks - unset -nocomplain ps_link_order ps_root_partition ps_visit_state + unset -nocomplain ps_partition_clocks source $ps_partition_file if {![info exists ps_partition_specs]} { @@ -21,6 +19,7 @@ proc ps_load_partitions {} { } set ps_partitions {} + array set seen_tops {} foreach spec $ps_partition_specs { if {[llength $spec] == 0} { continue @@ -63,13 +62,13 @@ proc ps_load_partitions {} { if {[lsearch -exact $ps_partitions $name] >= 0} { error "duplicate partition name '$name'" } - if {[info exists ps_partition_by_top($top)]} { + if {[info exists seen_tops($top)]} { error "module '$top' is the top of more than one partition" } lappend ps_partitions $name set ps_partition_top($name) $top - set ps_partition_by_top($top) $name + set seen_tops($top) $name if {[llength $blackboxes] > 0} { set ps_partition_blackboxes($name) $blackboxes } @@ -114,58 +113,6 @@ proc ps_load_partitions {} { } set ps_partition_clocks($partition) $clocks } - - array set referenced {} - foreach partition $ps_partitions { - set children {} - foreach module [ps_partition_blackboxes $partition] { - if {![info exists ps_partition_by_top($module)]} { - error "partition '$partition' has no child partition for module '$module'" - } - set child $ps_partition_by_top($module) - if {$child eq $partition} { - error "partition '$partition' cannot contain itself" - } - lappend children $child - set referenced($child) 1 - } - set ps_partition_children($partition) $children - } - - set roots {} - foreach partition $ps_partitions { - if {![info exists referenced($partition)]} { - lappend roots $partition - } - } - if {[llength $roots] != 1} { - error "partition hierarchy must have one root, found: $roots" - } - set ps_root_partition [lindex $roots 0] - - set ps_link_order {} - ps_visit_partition $ps_root_partition - if {[llength $ps_link_order] != [llength $ps_partitions]} { - error "some partitions are disconnected from root '$ps_root_partition'" - } -} - -proc ps_visit_partition {partition} { - global ps_partition_children ps_link_order ps_visit_state - - if {[info exists ps_visit_state($partition)]} { - if {$ps_visit_state($partition) eq "visiting"} { - error "cycle detected at partition '$partition'" - } - return - } - - set ps_visit_state($partition) visiting - foreach child $ps_partition_children($partition) { - ps_visit_partition $child - } - set ps_visit_state($partition) done - lappend ps_link_order $partition } proc ps_partition_top {partition} { @@ -181,38 +128,21 @@ proc ps_partition_blackboxes {partition} { return {} } -proc ps_partition_children {partition} { - global ps_partition_children - return $ps_partition_children($partition) -} - proc ps_partition_clocks {partition} { global ps_partition_clocks return $ps_partition_clocks($partition) } -proc ps_root_partition {} { - global ps_root_partition - return $ps_root_partition -} - -proc ps_link_order {} { - global ps_link_order - return $ps_link_order -} - if {[info exists argv0] && [file normalize $argv0] eq [file normalize [info script]]} { if {[llength $argv] != 1} { - puts "Usage: tclsh defs.tcl partitions|link-order|root" + puts "Usage: tclsh defs.tcl partitions" exit 1 } ps_load_partitions switch -- [lindex $argv 0] { partitions { puts [join $ps_partitions ","] } - link-order { puts [join [ps_link_order] ","] } - root { puts [ps_root_partition] } default { - puts "Usage: tclsh defs.tcl partitions|link-order|root" + puts "Usage: tclsh defs.tcl partitions" exit 1 } } diff --git a/fpga_diff/tools/partition_synth/link.tcl b/fpga_diff/tools/partition_synth/link.tcl index 61b51883..8d207a5b 100644 --- a/fpga_diff/tools/partition_synth/link.tcl +++ b/fpga_diff/tools/partition_synth/link.tcl @@ -1,5 +1,5 @@ ######################################################################## -# Link synthesized partitions from leaves to the declared root module. +# Link OOC partition checkpoints into a complete root-partition DCP. ######################################################################## source [file normalize [file join [file dirname [info script]] defs.tcl]] @@ -7,7 +7,7 @@ source [file normalize [file join [file dirname [info script]] sources.tcl]] proc usage {} { puts "Usage:" - puts " vivado -mode batch -source tools/partition_synth/link.tcl -tclargs \\\\" + puts { vivado -mode batch -source tools/partition_synth/link.tcl -tclargs} puts " --out-dir " exit 1 } @@ -33,6 +33,75 @@ proc parse_args {} { return [array get opt] } +proc ps_link_visit {partition} { + global ps_link_children ps_link_order ps_link_visit_state + + if {[info exists ps_link_visit_state($partition)]} { + if {$ps_link_visit_state($partition) eq "visiting"} { + error "cycle detected at partition '$partition'" + } + return + } + + set ps_link_visit_state($partition) visiting + foreach child $ps_link_children($partition) { + ps_link_visit $child + } + set ps_link_visit_state($partition) done + lappend ps_link_order $partition +} + +proc ps_build_link_graph {} { + global ps_partitions ps_link_children ps_link_order ps_link_visit_state + + array set top_to_partition {} + foreach partition $ps_partitions { + set top [ps_partition_top $partition] + if {[info exists top_to_partition($top)]} { + error "module '$top' is the top of more than one partition" + } + set top_to_partition($top) $partition + } + + unset -nocomplain ps_link_children ps_link_order ps_link_visit_state + array set ps_link_children {} + array set referenced {} + foreach partition $ps_partitions { + set children {} + foreach module [ps_partition_blackboxes $partition] { + if {![info exists top_to_partition($module)]} { + error "partition '$partition' has no child partition for module '$module'" + } + set child $top_to_partition($module) + if {$child eq $partition} { + error "partition '$partition' cannot contain itself" + } + lappend children $child + set referenced($child) 1 + } + set ps_link_children($partition) $children + } + + set roots {} + foreach partition $ps_partitions { + if {![info exists referenced($partition)]} { + lappend roots $partition + } + } + if {[llength $roots] != 1} { + error "partition link requires one root, found: $roots" + } + + set root [lindex $roots 0] + set ps_link_order {} + array set ps_link_visit_state {} + ps_link_visit $root + if {[llength $ps_link_order] != [llength $ps_partitions]} { + error "some partitions are disconnected from root '$root'" + } + return [list $root $ps_link_order] +} + proc ps_write_blackbox_report {partition report_file} { set blackboxes [get_cells -hier -quiet -filter {IS_BLACKBOX}] set out [open $report_file w] @@ -66,11 +135,12 @@ file mkdir $linked_edif_dir file mkdir $shell_edif_dir ps_load_partitions +lassign [ps_build_link_graph] root link_order set part_name xcvu19p-fsva3824-2-e -foreach partition [ps_link_order] { +foreach partition $link_order { set top [ps_partition_top $partition] - set children [ps_partition_children $partition] + set children $ps_link_children($partition) set input_dcp [file normalize "$out_dir/${partition}.dcp"] set linked_dcp [file normalize "$linked_dir/${partition}.dcp"] set partition_edif_dir [file normalize "$linked_edif_dir/$partition"] @@ -112,15 +182,16 @@ foreach partition [ps_link_order] { close_project } -set root [ps_root_partition] +set root_top [ps_partition_top $root] set root_dcp [file normalize "$linked_dir/${root}.dcp"] if {![file isfile $root_dcp]} { set root_dcp [file normalize "$out_dir/${root}.dcp"] } +set root_edif [file normalize "$linked_edif_dir/$root/${root_top}.edf"] set marker [open [file normalize "$linked_dir/root.txt"] w] puts $marker "partition=$root" -puts $marker "module=[ps_partition_top $root]" +puts $marker "module=$root_top" puts $marker "dcp=$root_dcp" -puts $marker "edif=[file normalize \"$linked_edif_dir/$root/[ps_partition_top $root].edf\"]" +puts $marker "edif=$root_edif" close $marker -puts "INFO: partition link complete: root=$root module=[ps_partition_top $root]" +puts "INFO: partition link complete: root=$root module=$root_top" diff --git a/fpga_diff/tools/partition_synth/partitions.tcl b/fpga_diff/tools/partition_synth/partitions.tcl index b068f9a8..9e442d0c 100644 --- a/fpga_diff/tools/partition_synth/partitions.tcl +++ b/fpga_diff/tools/partition_synth/partitions.tcl @@ -6,6 +6,9 @@ # # without accepts one or more immediate child modules. Each child is # replaced with an automatically generated blackbox stub for that partition. +# OOC partitions are independent, so a blackboxed child need not have a +# separate declaration in this file. link.tcl validates a complete hierarchy +# only when a top-level synthesis DCP is requested. ######################################################################## set ps_partition_specs { diff --git a/fpga_diff/tools/partition_synth/project_link.tcl b/fpga_diff/tools/partition_synth/project_link.tcl index a51cca52..17e7e9bd 100644 --- a/fpga_diff/tools/partition_synth/project_link.tcl +++ b/fpga_diff/tools/partition_synth/project_link.tcl @@ -1,27 +1,24 @@ ######################################################################## -# Synthesize the FPGA top shell and link the complete root partition into it. +# Synthesize the FPGA top shell and insert the linked root partition DCP. ######################################################################## -source [file normalize [file join [file dirname [info script]] defs.tcl]] source [file normalize [file join [file dirname [info script]] sources.tcl]] proc usage {} { puts "Usage:" - puts " vivado -mode batch -source tools/partition_synth/project_link.tcl -tclargs" + puts { vivado -mode batch -source tools/partition_synth/project_link.tcl -tclargs} puts { --project --out-dir [--jobs ]} - puts { [--reuse-shell-dcp ]} exit 1 } proc parse_args {} { - array set opt {--project "" --out-dir "" --jobs "" --reuse-shell-dcp ""} + array set opt {--project "" --out-dir "" --jobs ""} for {set i 0} {$i < [llength $::argv]} {incr i} { set key [lindex $::argv $i] switch -- $key { --project - --out-dir - - --jobs - - --reuse-shell-dcp { + --jobs { incr i if {$i >= [llength $::argv]} { usage } set opt($key) [lindex $::argv $i] @@ -50,24 +47,37 @@ proc default_jobs {} { return [expr {max(1, int(ceil($threads / 2.0)))}] } +proc ps_read_link_marker {path} { + set marker [open $path r] + array set data {} + while {[gets $marker line] >= 0} { + if {$line eq ""} { + continue + } + if {![regexp {^([^=]+)=(.*)$} $line -> key value]} { + error "invalid link marker entry: $line" + } + set data($key) $value + } + close $marker + foreach key {partition module dcp} { + if {![info exists data($key)] || $data($key) eq ""} { + error "link marker is missing '$key': $path" + } + } + return [array get data] +} + array set opt [parse_args] set project [file normalize $opt(--project)] set out_dir [file normalize $opt(--out-dir)] -ps_require_file $project -if {$opt(--reuse-shell-dcp) ne ""} { - set opt(--reuse-shell-dcp) [file normalize $opt(--reuse-shell-dcp)] - ps_require_file $opt(--reuse-shell-dcp) -} -ps_load_partitions - -set root [ps_root_partition] -set root_module [ps_partition_top $root] set linked_dir [file normalize "$out_dir/linked"] -set project_shell_dir [file normalize "$linked_dir/project_shell"] -set root_dcp [file normalize "$linked_dir/${root}.dcp"] -if {![file isfile $root_dcp]} { - set root_dcp [file normalize "$out_dir/${root}.dcp"] -} +set marker [file normalize "$linked_dir/root.txt"] +ps_require_file $project +ps_require_file $marker +array set root_info [ps_read_link_marker $marker] +set root_module $root_info(module) +set root_dcp [file normalize $root_info(dcp)] ps_require_file $root_dcp open_project $project @@ -85,38 +95,25 @@ set final_dcp [file normalize "$synth_run_dir/${project_top}.dcp"] set utilization_rpt [file normalize "$synth_run_dir/${project_top}_utilization_synth.rpt"] set utilization_pb [file normalize "$synth_run_dir/${project_top}_utilization_synth.pb"] -if {$opt(--reuse-shell-dcp) eq ""} { - ps_prepare_project_shell $root_module $out_dir - # Do not reuse a previous monolithic checkpoint for the generated shell. - set_property INCREMENTAL_CHECKPOINT {} $synth_run - set_property AUTO_INCREMENTAL_CHECKPOINT 0 $synth_run - reset_run $synth_run +ps_prepare_project_shell $root_module $out_dir +set_property INCREMENTAL_CHECKPOINT {} $synth_run +set_property AUTO_INCREMENTAL_CHECKPOINT 0 $synth_run +reset_run $synth_run - set jobs $opt(--jobs) - if {$jobs eq ""} { set jobs [default_jobs] } - puts "INFO: launching top shell synth_1 with -jobs $jobs" - launch_runs $synth_run -jobs $jobs - wait_on_run $synth_run +set jobs $opt(--jobs) +if {$jobs eq ""} { set jobs [default_jobs] } +puts "INFO: launching top shell synth_1 with -jobs $jobs" +launch_runs $synth_run -jobs $jobs +wait_on_run $synth_run - set status [get_property STATUS $synth_run] - if {![string match "synth_design Complete*" $status]} { - error "top shell synthesis failed with status: $status" - } - ps_require_file $final_dcp -} else { - puts "INFO: reusing top shell checkpoint $opt(--reuse-shell-dcp)" +set status [get_property STATUS $synth_run] +if {![string match "synth_design Complete*" $status]} { + error "top shell synthesis failed with status: $status" } - +ps_require_file $final_dcp close_project -file mkdir $project_shell_dir -if {$opt(--reuse-shell-dcp) eq ""} { - set shell_dcp [file normalize "$project_shell_dir/${project_top}.dcp"] - file copy -force $final_dcp $shell_dcp -} else { - set shell_dcp $opt(--reuse-shell-dcp) -} -open_checkpoint $shell_dcp +open_checkpoint $final_dcp set root_cells [get_cells -hier -quiet -filter "REF_NAME == $root_module && IS_BLACKBOX"] if {[llength $root_cells] != 1} { error "top shell must contain one $root_module blackbox, found [llength $root_cells]" @@ -137,8 +134,6 @@ set unresolved_root [get_cells -hier -quiet -filter "REF_NAME == $root_module && if {[llength $unresolved_root] != 0} { error "root module $root_module remains a blackbox after project link" } -# Project-managed OOC IPs remain blackboxes in a synthesis DCP. Only the -# partition root is expected to disappear after the checkpoint is inserted. if {[llength $blackboxes] != ($shell_blackbox_count - 1)} { error "project link changed the OOC IP blackbox count; see $report_file" } @@ -153,18 +148,10 @@ file rename -force $final_tmp $final_dcp file rename -force $utilization_rpt_tmp $utilization_rpt file rename -force $utilization_pb_tmp $utilization_pb -open_project $project -set impl_run [get_runs -quiet impl_1] -if {[llength $impl_run] > 0} { - reset_run $impl_run - puts "INFO: reset impl_1 after replacing the synthesis checkpoint" -} -close_project - -set marker [open [file normalize "$linked_dir/final.txt"] w] -puts $marker "project=$project" -puts $marker "top=$project_top" -puts $marker "dcp=$final_dcp" -puts $marker "blackbox_report=$report_file" -close $marker +set final_marker [open [file normalize "$linked_dir/final.txt"] w] +puts $final_marker "project=$project" +puts $final_marker "top=$project_top" +puts $final_marker "dcp=$final_dcp" +puts $final_marker "blackbox_report=$report_file" +close $final_marker puts "INFO: project link complete: $final_dcp" diff --git a/fpga_diff/tools/partition_synth/project_runs.tcl b/fpga_diff/tools/partition_synth/project_runs.tcl index b9236d90..a1af0ba1 100644 --- a/fpga_diff/tools/partition_synth/project_runs.tcl +++ b/fpga_diff/tools/partition_synth/project_runs.tcl @@ -121,7 +121,7 @@ file mkdir $project_runs_dir set manifest_path [file normalize "$project_runs_dir/manifest.tsv"] open_project $project -ps_restore_project_sources [ps_partition_top [ps_root_partition]] +ps_restore_project_sources set project_part [get_property PART [current_project]] if {$project_part eq ""} { error "project has no part: $project" diff --git a/fpga_diff/tools/partition_synth/run.sh b/fpga_diff/tools/partition_synth/run.sh index 5e7556d9..4effc698 100755 --- a/fpga_diff/tools/partition_synth/run.sh +++ b/fpga_diff/tools/partition_synth/run.sh @@ -8,7 +8,6 @@ partitions="" jobs_per_partition="" parallel=16 project="" -project_runs=0 dry_run=0 usage() { @@ -22,8 +21,7 @@ Options: --partitions LIST comma-separated partitions; overrides list expansion --jobs-per-partition N Vivado maxThreads per partition process --parallel N max concurrent Vivado partition processes - --project XPR link partition DCPs into this top-level project - --project-runs create GUI-visible OOC runs in --project + --project XPR create GUI-visible OOC runs and link their DCPs --dry-run validate file lists without launching synth_design --help show this help EOF @@ -55,10 +53,6 @@ while [[ $# -gt 0 ]]; do project="$(realpath "$2")" shift 2 ;; - --project-runs) - project_runs=1 - shift - ;; --dry-run) dry_run=1 shift @@ -92,13 +86,8 @@ if [[ -n "$project" && ! -f "$project" ]]; then exit 1 fi -if [[ "$project_runs" -eq 1 && -z "$project" ]]; then - echo "ERROR: --project-runs requires --project" >&2 - exit 1 -fi - -if [[ "$project_runs" -eq 1 && "$dry_run" -eq 1 ]]; then - echo "ERROR: --project-runs cannot be used with --dry-run" >&2 +if [[ -n "$project" && "$dry_run" -eq 1 ]]; then + echo "ERROR: --project cannot be used with --dry-run" >&2 exit 1 fi @@ -131,8 +120,8 @@ fi if [[ "$dry_run" -eq 1 ]]; then echo "INFO: dry_run=1" fi -if [[ "$project_runs" -eq 1 ]]; then - echo "INFO: project_runs=1" +if [[ -n "$project" ]]; then + echo "INFO: project=$project" fi failed=0 @@ -143,7 +132,7 @@ wait_for_slot() { done } -if [[ "$project_runs" -eq 1 ]]; then +if [[ -n "$project" ]]; then setup_args=( -mode batch -source "$script_dir/project_runs.tcl" @@ -223,7 +212,7 @@ if [[ "$failed" -ne 0 ]]; then exit 1 fi -if [[ "$project_runs" -eq 1 ]]; then +if [[ -n "$project" ]]; then for index in "${!project_partitions[@]}"; do partition="${project_partitions[$index]}" top_module="${project_tops[$index]}" @@ -240,16 +229,13 @@ if [[ "$project_runs" -eq 1 ]]; then if [[ "$failed" -ne 0 ]]; then exit 1 fi -fi -if [[ -n "$project" && "$dry_run" -eq 0 ]]; then link_args=( -mode batch -source "$script_dir/link.tcl" -tclargs --out-dir "$out_dir" ) - echo "INFO: linking partition DCPs" (cd "$origin_dir" && vivado "${link_args[@]}") >"$log_dir/link.log" 2>&1 @@ -263,8 +249,7 @@ if [[ -n "$project" && "$dry_run" -eq 0 ]]; then if [[ -n "$jobs_per_partition" ]]; then project_link_args+=(--jobs "$jobs_per_partition") fi - - echo "INFO: linking root partition into $project" + echo "INFO: writing linked synthesis DCP into $project" (cd "$origin_dir" && vivado "${project_link_args[@]}") >"$log_dir/project_link.log" 2>&1 fi diff --git a/fpga_diff/tools/partition_synth/sources.tcl b/fpga_diff/tools/partition_synth/sources.tcl index 00d19fcc..f77efdd0 100644 --- a/fpga_diff/tools/partition_synth/sources.tcl +++ b/fpga_diff/tools/partition_synth/sources.tcl @@ -1,5 +1,5 @@ ######################################################################## -# RTL source and project-shell helpers. +# RTL source helpers for partition synthesis. ######################################################################## source [file normalize [file join [file dirname [info script]] header_files.tcl]] @@ -259,36 +259,41 @@ proc ps_project_source_files {} { return $files } -proc ps_project_root_stubs {module} { - set tail "${module}_partition_blackbox.sv" +proc ps_project_partition_stubs {} { set stubs {} foreach file [get_files -quiet -of_objects [get_filesets sources_1]] { - if {[file tail $file] eq $tail} { + if {[string match "*_partition_blackbox.sv" [file tail $file]]} { lappend stubs $file } } return $stubs } -proc ps_restore_project_sources {module} { - set stubs [ps_project_root_stubs $module] +proc ps_restore_project_sources {} { + set stubs [ps_project_partition_stubs] if {[llength $stubs] == 0} { return 0 } + set source_files [ps_remove_source_files [ps_project_source_files] $stubs] remove_files $stubs - set source [ps_find_module_source $module [ps_project_source_files]] - if {$source eq ""} { - error "cannot restore source for root module $module" + foreach stub $stubs { + set suffix "_partition_blackbox.sv" + set tail [file tail $stub] + set module [string range $tail 0 [expr {[string length $tail] - [string length $suffix] - 1}]] + set source [ps_find_module_source $module $source_files] + if {$source eq ""} { + error "cannot restore source for partition module $module" + } + set_property USED_IN_SYNTHESIS true [get_files $source] } - set_property USED_IN_SYNTHESIS true [get_files $source] update_compile_order -fileset sources_1 - puts "INFO: restored project source for $module: $source" - return 1 + puts "INFO: restored [llength $stubs] partition source(s)" + return [llength $stubs] } proc ps_prepare_project_shell {module out_dir} { - ps_restore_project_sources $module + ps_restore_project_sources set source [ps_find_module_source $module [ps_project_source_files]] if {$source eq ""} { From ccadcbdb9739dcdeb015a1a29c3a04c447cf37bf Mon Sep 17 00:00:00 2001 From: klin02 Date: Thu, 23 Jul 2026 04:08:24 +0800 Subject: [PATCH 5/6] test(fpga_diff): report linked partition timing --- fpga_diff/tools/partition_synth/project_link.tcl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fpga_diff/tools/partition_synth/project_link.tcl b/fpga_diff/tools/partition_synth/project_link.tcl index 17e7e9bd..cc10b3dc 100644 --- a/fpga_diff/tools/partition_synth/project_link.tcl +++ b/fpga_diff/tools/partition_synth/project_link.tcl @@ -94,6 +94,11 @@ set synth_run_dir [file normalize [get_property DIRECTORY $synth_run]] set final_dcp [file normalize "$synth_run_dir/${project_top}.dcp"] set utilization_rpt [file normalize "$synth_run_dir/${project_top}_utilization_synth.rpt"] set utilization_pb [file normalize "$synth_run_dir/${project_top}_utilization_synth.pb"] +set clocks_rpt [file normalize "$linked_dir/${project_top}_clocks.rpt"] +set clock_interaction_rpt [file normalize "$linked_dir/${project_top}_clock_interaction.rpt"] +set cdc_rpt [file normalize "$linked_dir/${project_top}_cdc.rpt"] +set check_timing_rpt [file normalize "$linked_dir/${project_top}_check_timing.rpt"] +set timing_summary_rpt [file normalize "$linked_dir/${project_top}_timing_summary_synth.rpt"] ps_prepare_project_shell $root_module $out_dir set_property INCREMENTAL_CHECKPOINT {} $synth_run @@ -143,6 +148,11 @@ set utilization_rpt_tmp "${utilization_rpt}.partition_linked" set utilization_pb_tmp "${utilization_pb}.partition_linked" write_checkpoint -force $final_tmp report_utilization -file $utilization_rpt_tmp -pb $utilization_pb_tmp +report_clocks -file $clocks_rpt +report_clock_interaction -file $clock_interaction_rpt +report_cdc -file $cdc_rpt +check_timing -file $check_timing_rpt +report_timing_summary -file $timing_summary_rpt close_design file rename -force $final_tmp $final_dcp file rename -force $utilization_rpt_tmp $utilization_rpt @@ -153,5 +163,10 @@ puts $final_marker "project=$project" puts $final_marker "top=$project_top" puts $final_marker "dcp=$final_dcp" puts $final_marker "blackbox_report=$report_file" +puts $final_marker "clocks_report=$clocks_rpt" +puts $final_marker "clock_interaction_report=$clock_interaction_rpt" +puts $final_marker "cdc_report=$cdc_rpt" +puts $final_marker "check_timing_report=$check_timing_rpt" +puts $final_marker "timing_summary_report=$timing_summary_rpt" close $final_marker puts "INFO: project link complete: $final_dcp" From 8fe201b65a991b1a433b99dbee1603a3ee5dad53 Mon Sep 17 00:00:00 2001 From: klin02 Date: Wed, 29 Jul 2026 22:42:50 +0800 Subject: [PATCH 6/6] fix(fpga_diff): restore partition link constraints --- .../tools/partition_synth/partitions.tcl | 4 +- .../tools/partition_synth/project_link.tcl | 72 +++++++++++++++++-- .../tools/partition_synth/project_runs.tcl | 2 +- fpga_diff/tools/partition_synth/sources.tcl | 54 +++++++++----- 4 files changed, 104 insertions(+), 28 deletions(-) diff --git a/fpga_diff/tools/partition_synth/partitions.tcl b/fpga_diff/tools/partition_synth/partitions.tcl index 9e442d0c..7056fa55 100644 --- a/fpga_diff/tools/partition_synth/partitions.tcl +++ b/fpga_diff/tools/partition_synth/partitions.tcl @@ -14,9 +14,8 @@ set ps_partition_specs { {module external_llc_wrapper ExternalLLCWrapper} {without external_llc_shell ExternalLLC {ExternalLLCWrapper}} - {module openncb OpenNCB} {module memmisc MemMisc} - {without xstop_shell XSTop {XSTile ExternalLLC OpenNCB MemMisc}} + {without xstop_shell XSTop {XSTile ExternalLLC MemMisc}} {without xstile_shell XSTile {XSCore L2Top}} {without xscore_shell XSCore {Frontend Backend MemBlock}} {module frontend Frontend} @@ -31,7 +30,6 @@ set ps_partition_specs { set ps_partition_clock_ports { external_llc_wrapper {{DEBUG_CLK_IN clock}} external_llc_shell {{DEBUG_CLK_IN clock}} - openncb {{DEBUG_CLK_IN clock}} memmisc {{DEBUG_CLK_IN clock}} xstop_shell {{DEBUG_CLK_IN io_clock}} xstile_shell {{DEBUG_CLK_IN clock}} diff --git a/fpga_diff/tools/partition_synth/project_link.tcl b/fpga_diff/tools/partition_synth/project_link.tcl index cc10b3dc..df7dbf9f 100644 --- a/fpga_diff/tools/partition_synth/project_link.tcl +++ b/fpga_diff/tools/partition_synth/project_link.tcl @@ -68,6 +68,56 @@ proc ps_read_link_marker {path} { return [array get data] } +proc ps_restore_top_clock_constraints {} { + global fpga_primary_clock_specs + + set clock_defs [file normalize [file join [file dirname [info script]] \ + ../../src/constr/common/clock_defs.tcl]] + ps_require_file $clock_defs + source $clock_defs + + set restored {} + foreach spec $fpga_primary_clock_specs { + lassign $spec clock_name _ port_name + if {[llength [get_ports -quiet $port_name]] == 0 || + [llength [get_clocks -quiet $clock_name]] != 0} { + continue + } + fpga_create_top_clock $clock_name + lappend restored $clock_name + } + + foreach pair { + {CPU_CLK_IN TMCLK} + {CPU_CLK_IN DEBUG_CLK_IN} + {TMCLK DEBUG_CLK_IN} + {PCIE_CLK_IN DEBUG_CLK_IN} + {PCIE_CLK_IN TMCLK} + {PCIE_CLK_IN CPU_CLK_IN} + {jtag_vclk CPU_CLK_IN} + {jtag_vclk TMCLK} + {jtag_vclk DEBUG_CLK_IN} + {jtag_vclk PCIE_CLK_IN} + {PCIE2_CLK_IN jtag_vclk} + {PCIE2_CLK_IN CPU_CLK_IN} + {PCIE2_CLK_IN TMCLK} + {PCIE2_CLK_IN DEBUG_CLK_IN} + {PCIE2_CLK_IN PCIE_CLK_IN} + } { + lassign $pair first second + set first_clocks [get_clocks -quiet -include_generated_clocks $first] + set second_clocks [get_clocks -quiet -include_generated_clocks $second] + if {[llength $first_clocks] != 0 && [llength $second_clocks] != 0} { + set_clock_groups -asynchronous -group $first_clocks -group $second_clocks + } + } + + if {[llength [get_clocks -quiet *]] == 0} { + error "no top-level clocks were restored after partition checkpoint link" + } + puts "INFO: restored top-level clocks: $restored" +} + array set opt [parse_args] set project [file normalize $opt(--project)] set out_dir [file normalize $opt(--out-dir)] @@ -100,19 +150,28 @@ set cdc_rpt [file normalize "$linked_dir/${project_top}_cdc.rpt"] set check_timing_rpt [file normalize "$linked_dir/${project_top}_check_timing.rpt"] set timing_summary_rpt [file normalize "$linked_dir/${project_top}_timing_summary_synth.rpt"] -ps_prepare_project_shell $root_module $out_dir set_property INCREMENTAL_CHECKPOINT {} $synth_run set_property AUTO_INCREMENTAL_CHECKPOINT 0 $synth_run reset_run $synth_run -set jobs $opt(--jobs) -if {$jobs eq ""} { set jobs [default_jobs] } -puts "INFO: launching top shell synth_1 with -jobs $jobs" -launch_runs $synth_run -jobs $jobs -wait_on_run $synth_run +set shell_result [catch { + ps_prepare_project_shell $root_module $out_dir $synth_run + set jobs $opt(--jobs) + if {$jobs eq ""} { set jobs [default_jobs] } + puts "INFO: launching top shell synth_1 with -jobs $jobs" + launch_runs $synth_run -jobs $jobs + wait_on_run $synth_run +} shell_error] +if {$shell_result} { + ps_restore_project_sources $root_module + close_project + error "top shell synthesis failed: $shell_error" +} set status [get_property STATUS $synth_run] +ps_restore_project_sources $root_module if {![string match "synth_design Complete*" $status]} { + close_project error "top shell synthesis failed with status: $status" } ps_require_file $final_dcp @@ -126,6 +185,7 @@ if {[llength $root_cells] != 1} { set shell_blackbox_count [llength [get_cells -hier -quiet -filter {IS_BLACKBOX}]] puts "INFO: linking root checkpoint $root_dcp at [lindex $root_cells 0]" read_checkpoint -cell [lindex $root_cells 0] $root_dcp +ps_restore_top_clock_constraints set report_file [file normalize "$linked_dir/${project_top}_blackboxes.rpt"] set report [open $report_file w] diff --git a/fpga_diff/tools/partition_synth/project_runs.tcl b/fpga_diff/tools/partition_synth/project_runs.tcl index a1af0ba1..4fb73fbe 100644 --- a/fpga_diff/tools/partition_synth/project_runs.tcl +++ b/fpga_diff/tools/partition_synth/project_runs.tcl @@ -121,7 +121,7 @@ file mkdir $project_runs_dir set manifest_path [file normalize "$project_runs_dir/manifest.tsv"] open_project $project -ps_restore_project_sources +ps_require_clean_project_sources set project_part [get_property PART [current_project]] if {$project_part eq ""} { error "project has no part: $project" diff --git a/fpga_diff/tools/partition_synth/sources.tcl b/fpga_diff/tools/partition_synth/sources.tcl index f77efdd0..e116bfe6 100644 --- a/fpga_diff/tools/partition_synth/sources.tcl +++ b/fpga_diff/tools/partition_synth/sources.tcl @@ -160,6 +160,7 @@ proc ps_partition_source_config {origin_dir partition run_dir} { source $clock_defs set cpu_files_has_rtl_include 0 set rtl_include_files {} + set rtl_include_dirs {} source "$tcl_dir/cpu_files.tcl" set chi_files {} @@ -174,6 +175,11 @@ proc ps_partition_source_config {origin_dir partition run_dir} { set partition_files [list {*}$all_source_files] set partition_include_dirs [list {*}$include_dirs] + foreach include_dir $rtl_include_dirs { + if {[lsearch -exact $partition_include_dirs $include_dir] < 0} { + lappend partition_include_dirs $include_dir + } + } foreach source_file $all_source_files { set source_dir [file normalize [file dirname $source_file]] if {[lsearch -exact $partition_include_dirs $source_dir] < 0} { @@ -259,41 +265,53 @@ proc ps_project_source_files {} { return $files } -proc ps_project_partition_stubs {} { +proc ps_project_root_stubs {module} { + set tail "${module}_partition_blackbox.sv" set stubs {} foreach file [get_files -quiet -of_objects [get_filesets sources_1]] { - if {[string match "*_partition_blackbox.sv" [file tail $file]]} { + if {[file tail $file] eq $tail} { lappend stubs $file } } return $stubs } -proc ps_restore_project_sources {} { - set stubs [ps_project_partition_stubs] +proc ps_shell_srcset_name {} { + return partition_synth_top_shell_srcs +} + +proc ps_detach_project_shell_sources {synth_run} { + set shell_srcset_name [ps_shell_srcset_name] + set shell_srcset [get_filesets -quiet $shell_srcset_name] + if {[llength $shell_srcset] == 0} { + return + } + + if {[get_property SRCSET $synth_run] eq $shell_srcset_name} { + set_property SRCSET sources_1 $synth_run + } +} + +proc ps_restore_project_sources {module} { + set stubs [ps_project_root_stubs $module] if {[llength $stubs] == 0} { return 0 } - set source_files [ps_remove_source_files [ps_project_source_files] $stubs] remove_files $stubs - foreach stub $stubs { - set suffix "_partition_blackbox.sv" - set tail [file tail $stub] - set module [string range $tail 0 [expr {[string length $tail] - [string length $suffix] - 1}]] - set source [ps_find_module_source $module $source_files] - if {$source eq ""} { - error "cannot restore source for partition module $module" - } - set_property USED_IN_SYNTHESIS true [get_files $source] + set source [ps_find_module_source $module [ps_project_source_files]] + if {$source eq ""} { + error "cannot restore source for root module $module" } + set_property USED_IN_SYNTHESIS true [get_files $source] update_compile_order -fileset sources_1 - puts "INFO: restored [llength $stubs] partition source(s)" - return [llength $stubs] + puts "INFO: restored project source for $module: $source" + return 1 } -proc ps_prepare_project_shell {module out_dir} { - ps_restore_project_sources +proc ps_prepare_project_shell {module out_dir synth_run} { + ps_detach_project_shell_sources $synth_run + ps_restore_project_sources $module set source [ps_find_module_source $module [ps_project_source_files]] if {$source eq ""} {