From 0f8a15ca7fe14bc2445ec4db8a4df0832a136cb8 Mon Sep 17 00:00:00 2001 From: abhinav Date: Sat, 3 Oct 2026 15:31:57 +0000 Subject: [PATCH 1/5] dft: qualify dbNetwork member calls in scan optimization Signed-off-by: abhinav --- src/dft/src/cells/OneBitScanCell.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dft/src/cells/OneBitScanCell.cpp b/src/dft/src/cells/OneBitScanCell.cpp index 63fa519bf54..17027203e6e 100644 --- a/src/dft/src/cells/OneBitScanCell.cpp +++ b/src/dft/src/cells/OneBitScanCell.cpp @@ -103,12 +103,14 @@ static odb::Point iTermLocation(odb::dbITerm* iterm, odb::dbInst* inst) odb::Point OneBitScanCell::getScanInLocation() const { - return iTermLocation(findITerm(getLibertyScanIn(test_cell_)), inst_); + return iTermLocation(findITerm(db_network_->getLibertyScanIn(test_cell_)), + inst_); } odb::Point OneBitScanCell::getScanOutLocation() const { - return iTermLocation(findITerm(getLibertyScanOut(test_cell_)), inst_); + return iTermLocation(findITerm(db_network_->getLibertyScanOut(test_cell_)), + inst_); } } // namespace dft From 867f9afec874d035d2baafd1d8530aeebcd5cd8a Mon Sep 17 00:00:00 2001 From: abhinav Date: Sat, 3 Oct 2026 19:34:31 +0000 Subject: [PATCH 2/5] dft: preserve scan endpoints and synchronize optimized chain order Signed-off-by: abhinav --- src/dft/src/Dft.cpp | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/dft/src/Dft.cpp b/src/dft/src/Dft.cpp index faa9d4da9c5..9e1745d0cb7 100644 --- a/src/dft/src/Dft.cpp +++ b/src/dft/src/Dft.cpp @@ -68,14 +68,13 @@ odb::dbScanList* getScanList(odb::dbScanChain* chain) // Rewires a scan chain to match the given ordered cell list: // chain.scan_in → cells[0].SI // cells[i].SO → cells[i+1].SI for i in [0, n-2] -// cells[n-1].SO → chain.scan_out (metadata only — see note below) +// cells[n-1].SO → chain.scan_out // // Safe to call multiple times (idempotent): connect() disconnects from the // old net before attaching to the new one. // -// Note on scan_out: the chain-level scan_out BTerm/ITerm is typically a -// functional Q output that must remain on its original net. We only update -// the chain metadata pointer (setScanOut) rather than physically moving nets. +// Preserve the fixed scan_out endpoint and the functional fanout of each +// cell output. Only the endpoint load moves when the tail changes. void RestitchChain(odb::dbScanChain* chain, const std::vector& cells, odb::dbBlock* block, @@ -120,10 +119,40 @@ void RestitchChain(odb::dbScanChain* chain, si_iterm->connect(net); } - // Update chain scan_out metadata to the last cell's SO ITerm. + // Connect the fixed scan output load to the final tail's existing net. odb::dbITerm* last_so = cells.back()->getScanOutITerm(); if (last_so != nullptr) { - chain->setScanOut(last_so); + odb::dbNet* net = last_so->getNet(); + if (net == nullptr) { + net = odb::dbNet::create(block, last_so->getName().c_str()); + if (net == nullptr) { + logger->error( + utl::DFT, 15, "Failed to create net for scan_opt restitching."); + } + net->setSigType(odb::dbSigType::SCAN); + last_so->connect(net); + } + std::visit( + [&](auto&& pin) { + if (pin != nullptr) { + pin->connect(net); + } + }, + chain->getScanOut()); + } + + // Keep ODB scan-list order consistent with the restitched physical order. + // insertAtFront stores the reverse of the logical scan order. + odb::dbScanList* list = getScanList(chain); + if (list != nullptr) { + std::map scan_insts; + for (odb::dbScanInst* scan_inst : list->getScanInsts()) { + scan_insts.emplace(scan_inst->getInst()->getName(), scan_inst); + } + list->clear(); + for (const auto* cell : cells) { + scan_insts.at(std::string(cell->getName()))->insertAtFront(list); + } } } From 5fd98068c41dce164873a854601e92d16e18fe84 Mon Sep 17 00:00:00 2001 From: abhinav Date: Sat, 3 Oct 2026 19:38:29 +0000 Subject: [PATCH 3/5] dft: guard functional output reuse and exercise fixed scan endpoints Signed-off-by: abhinav --- src/dft/src/Dft.cpp | 21 ++++++++++++- src/dft/test/scan_opt_sky130.tcl | 53 ++++++++++++++++++++++++++++++++ src/dft/test/scan_opt_sky130.vok | 5 ++- 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/dft/src/Dft.cpp b/src/dft/src/Dft.cpp index 9e1745d0cb7..a9ffecacf86 100644 --- a/src/dft/src/Dft.cpp +++ b/src/dft/src/Dft.cpp @@ -127,7 +127,7 @@ void RestitchChain(odb::dbScanChain* chain, net = odb::dbNet::create(block, last_so->getName().c_str()); if (net == nullptr) { logger->error( - utl::DFT, 15, "Failed to create net for scan_opt restitching."); + utl::DFT, 20, "Failed to create net for scan_opt output."); } net->setSigType(odb::dbSigType::SCAN); last_so->connect(net); @@ -421,6 +421,25 @@ void Dft::scanOpt() odb::dbBlock* block = db_->getChip()->getBlock(); odb::dbDft* db_dft = block->getDft(); + // A functional output implicitly reused by stitching must not be moved. + // Require an explicitly configured scan output before changing chain tails. + size_t ordinal = 0; + for (odb::dbScanChain* chain : db_dft->getScanChains()) { + const auto output_name = fmt::format( + FMT_RUNTIME(dft_config_->getScanStitchConfig().getOutNamePattern()), + ordinal++); + if (auto* term = std::get_if(&chain->getScanOut()); + term != nullptr && *term != nullptr + && (*term)->getName() != output_name) { + logger_->error(utl::DFT, + 22, + "scan_opt requires a configured scan output {}; {} is an " + "implicitly reused functional output.", + output_name, + (*term)->getName()); + } + } + // --------------------------------------------------------------------------- // Spatial pre-clustering: reassign scan cells between chains of the same // clock domain so that each chain holds a spatially compact set of cells. diff --git a/src/dft/test/scan_opt_sky130.tcl b/src/dft/test/scan_opt_sky130.tcl index 6ca3e29e160..905f674298e 100644 --- a/src/dft/test/scan_opt_sky130.tcl +++ b/src/dft/test/scan_opt_sky130.tcl @@ -35,11 +35,64 @@ place_inst ff8_clk1_rising 9000 6000 place_inst ff9_clk1_rising 4000 4000 place_inst ff10_clk1_rising 5000 5000 +# Dedicated scan output: functional output ports must retain their Q nets. +set block [ord::get_db_block] +set so_net [odb::dbNet_create $block scan_out_0] +set so_port [odb::dbBTerm_create $so_net scan_out_0] +$so_port setIoType OUTPUT +$so_port setSigType SCAN +set functional_outputs {} +for {set i 1} {$i <= 10} {incr i} { + dict set functional_outputs output$i [[$block findBTerm output$i] getNet] +} + execute_dft_plan # Reorder the stitched chain to reduce wirelength and re-stitch in odb. scan_opt +# Independently trace the optimized physical order to the original fixed SO. +set net [[$block findBTerm scan_in_0] getNet] +set so_net [$so_port getNet] +set order {} +set visited {} +while {$net != $so_net} { + if {[lsearch -exact $visited $net] >= 0} { error "scan cycle" } + lappend visited $net + set successors {} + foreach pin [$net getITerms] { + if {[[$pin getMTerm] getName] == "SCD"} { + lappend successors [$pin getInst] + } + } + if {[llength $successors] != 1} { error "scan fork or missing fixed SO" } + set inst [lindex $successors 0] + lappend order [$inst getName] + set net [[$inst findITerm Q] getNet] +} +if {[llength $order] != 10 || [llength [lsort -unique $order]] != 10} { + error "scan membership changed or SO is interior" +} +foreach pin [$so_net getITerms] { + if {[[$pin getMTerm] getName] == "SCD"} { error "SO is not the tail" } +} +dict for {name net} $functional_outputs { + if {[[$block findBTerm $name] getNet] != $net} { + error "functional output moved" + } +} +set metadata {} +foreach chain [[$block getDft] getScanChains] { + foreach partition [$chain getScanPartitions] { + foreach scan_list [$partition getScanLists] { + foreach scan_inst [$scan_list getScanInsts] { + lappend metadata [[$scan_inst getInst] getName] + } + } + } +} +if {[lreverse $metadata] != $order} { error "stale optimized scan metadata" } + set verilog_file [make_result_file scan_opt_sky130.v] write_verilog $verilog_file diff_files $verilog_file scan_opt_sky130.vok diff --git a/src/dft/test/scan_opt_sky130.vok b/src/dft/test/scan_opt_sky130.vok index 8647cc7e084..f40b4b95bb0 100644 --- a/src/dft/test/scan_opt_sky130.vok +++ b/src/dft/test/scan_opt_sky130.vok @@ -12,7 +12,8 @@ module place_sort (clock, port1, set_b, scan_enable_0, - scan_in_0); + scan_in_0, + scan_out_0); input clock; output output1; output output10; @@ -28,6 +29,7 @@ module place_sort (clock, input set_b; input scan_enable_0; input scan_in_0; + output scan_out_0; sky130_fd_sc_hd__sdfsbp_1 ff10_clk1_rising (.D(port1), @@ -90,4 +92,5 @@ module place_sort (clock, .SCE(scan_enable_0), .SET_B(set_b), .CLK(clock)); + assign scan_out_0 = output8; endmodule From 3acca55d014b45e62eea19b8bf754e17d361d08b Mon Sep 17 00:00:00 2001 From: abhinav Date: Sat, 3 Oct 2026 19:39:53 +0000 Subject: [PATCH 4/5] dft: retain endpoint variant while checking fixed output identity Signed-off-by: abhinav --- src/dft/src/Dft.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dft/src/Dft.cpp b/src/dft/src/Dft.cpp index a9ffecacf86..894efd26e34 100644 --- a/src/dft/src/Dft.cpp +++ b/src/dft/src/Dft.cpp @@ -428,7 +428,8 @@ void Dft::scanOpt() const auto output_name = fmt::format( FMT_RUNTIME(dft_config_->getScanStitchConfig().getOutNamePattern()), ordinal++); - if (auto* term = std::get_if(&chain->getScanOut()); + const auto scan_out = chain->getScanOut(); + if (auto* term = std::get_if(&scan_out); term != nullptr && *term != nullptr && (*term)->getName() != output_name) { logger_->error(utl::DFT, From 03f7b75bae946796aa854c6a596bed6412e8bd63 Mon Sep 17 00:00:00 2001 From: abhinav Date: Sat, 3 Oct 2026 19:54:06 +0000 Subject: [PATCH 5/5] test: match native insertion order of dedicated scan output Signed-off-by: abhinav --- src/dft/test/scan_opt_sky130.vok | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dft/test/scan_opt_sky130.vok b/src/dft/test/scan_opt_sky130.vok index f40b4b95bb0..a7356c8abe0 100644 --- a/src/dft/test/scan_opt_sky130.vok +++ b/src/dft/test/scan_opt_sky130.vok @@ -11,9 +11,9 @@ module place_sort (clock, output9, port1, set_b, + scan_out_0, scan_enable_0, - scan_in_0, - scan_out_0); + scan_in_0); input clock; output output1; output output10; @@ -27,9 +27,9 @@ module place_sort (clock, output output9; input port1; input set_b; + output scan_out_0; input scan_enable_0; input scan_in_0; - output scan_out_0; sky130_fd_sc_hd__sdfsbp_1 ff10_clk1_rising (.D(port1),