diff --git a/src/dft/src/Dft.cpp b/src/dft/src/Dft.cpp index faa9d4da9c5..894efd26e34 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, 20, "Failed to create net for scan_opt output."); + } + 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); + } } } @@ -392,6 +421,26 @@ 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++); + 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, + 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/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 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..a7356c8abe0 100644 --- a/src/dft/test/scan_opt_sky130.vok +++ b/src/dft/test/scan_opt_sky130.vok @@ -11,6 +11,7 @@ module place_sort (clock, output9, port1, set_b, + scan_out_0, scan_enable_0, scan_in_0); input clock; @@ -26,6 +27,7 @@ module place_sort (clock, output output9; input port1; input set_b; + output scan_out_0; input scan_enable_0; input scan_in_0; @@ -90,4 +92,5 @@ module place_sort (clock, .SCE(scan_enable_0), .SET_B(set_b), .CLK(clock)); + assign scan_out_0 = output8; endmodule