Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 55 additions & 6 deletions src/dft/src/Dft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<OdbScanCellAdapter*>& cells,
odb::dbBlock* block,
Expand Down Expand Up @@ -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<std::string, odb::dbScanInst*> 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);
}
}
}

Expand Down Expand Up @@ -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<odb::dbBTerm*>(&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.
Expand Down
6 changes: 4 additions & 2 deletions src/dft/src/cells/OneBitScanCell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
53 changes: 53 additions & 0 deletions src/dft/test/scan_opt_sky130.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions src/dft/test/scan_opt_sky130.vok
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module place_sort (clock,
output9,
port1,
set_b,
scan_out_0,
scan_enable_0,
scan_in_0);
input clock;
Expand All @@ -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;

Expand Down Expand Up @@ -90,4 +92,5 @@ module place_sort (clock,
.SCE(scan_enable_0),
.SET_B(set_b),
.CLK(clock));
assign scan_out_0 = output8;
endmodule