Skip to content

Commit f2b35d2

Browse files
authored
Merge pull request #10338 from The-OpenROAD-Project-staging/mbff_hier_issue
gpl/mbff: preserve hierarchy in cluster_flops
2 parents b53c665 + 0f8adee commit f2b35d2

7 files changed

Lines changed: 493 additions & 101 deletions

File tree

src/gpl/src/mbff.cpp

Lines changed: 125 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ namespace gpl {
6464
using odb::dbInst;
6565
using odb::dbITerm;
6666
using odb::dbMaster;
67+
using odb::dbModule;
6768
using odb::dbMTerm;
6869
using odb::dbNet;
6970
using utl::GPL;
@@ -171,6 +172,61 @@ const sta::LibertyCell* MBFF::getLibertyCell(const sta::Cell* cell)
171172
return lib_cell;
172173
}
173174

175+
namespace {
176+
// Rebind a tray iterm to the given (flat, mod) pair. The plain
177+
// dbITerm::connect(dbNet*) overload only updates the flat side; if a
178+
// stale modnet was attached from an earlier per-orig-flop iteration
179+
// on a shared role pin, it would form an inconsistent (flat, mod)
180+
// pair. Clear the mod side explicitly when only flat is being
181+
// rebound. dbITerm::connect(dbNet*, dbModNet*) cannot be used with a
182+
// null mod_net because it dereferences the modnet unconditionally.
183+
void reconnectIterm(dbITerm* tray_iterm, dbNet* net, odb::dbModNet* mod_net)
184+
{
185+
if (net && mod_net) {
186+
tray_iterm->connect(net, mod_net);
187+
} else if (net) {
188+
tray_iterm->disconnectDbModNet();
189+
tray_iterm->connect(net);
190+
} else if (mod_net) {
191+
tray_iterm->connect(mod_net);
192+
}
193+
}
194+
195+
// Check whether lib_port appears in any sequential's FuncExpr accessed
196+
// via `get` (e.g., &Sequential::clear or &Sequential::preset). Scans
197+
// both the regular lib_cell sequentials and the test cell's
198+
// sequentials. The test cell's seq.clear()/preset() FuncExpr port set
199+
// holds test-cell port pointers, so the test cell branch resolves the
200+
// matching port by name first.
201+
bool portInSequentialFunc(const sta::LibertyCell* lib_cell,
202+
const sta::LibertyPort* lib_port,
203+
sta::FuncExpr* (sta::Sequential::*get)() const)
204+
{
205+
for (const sta::Sequential& seq : lib_cell->sequentials()) {
206+
if (const sta::FuncExpr* fe = (seq.*get)()) {
207+
if (fe->hasPort(lib_port)) {
208+
return true;
209+
}
210+
}
211+
}
212+
if (const sta::LibertyCell* test_cell = lib_cell->testCell()) {
213+
const sta::LibertyPort* test_lib_port
214+
= test_cell->findLibertyPort(lib_port->name());
215+
if (test_lib_port == nullptr) {
216+
return false;
217+
}
218+
for (const sta::Sequential& seq : test_cell->sequentials()) {
219+
if (const sta::FuncExpr* fe = (seq.*get)()) {
220+
if (fe->hasPort(test_lib_port)) {
221+
return true;
222+
}
223+
}
224+
}
225+
}
226+
return false;
227+
}
228+
} // namespace
229+
174230
float MBFF::GetDist(const Point& a, const Point& b)
175231
{
176232
return (abs(a.x - b.x) + abs(a.y - b.y));
@@ -222,9 +278,14 @@ bool MBFF::IsDPin(dbITerm* iterm)
222278
{
223279
dbInst* inst = iterm->getInst();
224280
const sta::Cell* cell = network_->dbToSta(inst->getMaster());
225-
const sta::LibertyCell* lib_cell = getLibertyCell(cell);
281+
// Use raw libertyCell (not getLibertyCell, which substitutes the
282+
// test cell). portInSequentialFunc scans both the regular and the
283+
// test cell views with the appropriate LibertyPort lookup.
284+
const sta::LibertyCell* lib_cell = network_->libertyCell(cell);
285+
if (lib_cell == nullptr) {
286+
return false;
287+
}
226288

227-
// check that the iterm isn't a (re)set pin
228289
const sta::Pin* pin = network_->dbToSta(iterm);
229290
if (pin == nullptr) {
230291
return false;
@@ -234,13 +295,9 @@ bool MBFF::IsDPin(dbITerm* iterm)
234295
return false;
235296
}
236297

237-
for (const sta::Sequential& seq : lib_cell->sequentials()) {
238-
if (seq.clear() && seq.clear()->hasPort(lib_port)) {
239-
return false;
240-
}
241-
if (seq.preset() && seq.preset()->hasPort(lib_port)) {
242-
return false;
243-
}
298+
if (portInSequentialFunc(lib_cell, lib_port, &sta::Sequential::clear)
299+
|| portInSequentialFunc(lib_cell, lib_port, &sta::Sequential::preset)) {
300+
return false;
244301
}
245302

246303
const bool exclude = (IsClockPin(iterm) || IsSupplyPin(iterm)
@@ -347,42 +404,11 @@ bool MBFF::IsClearPin(dbITerm* iterm)
347404
if (lib_port == nullptr) {
348405
return false;
349406
}
350-
351407
const sta::LibertyCell* lib_cell = network_->libertyCell(cell);
352408
if (lib_cell == nullptr) {
353409
return false;
354410
}
355-
356-
// Check the lib cell if the port is a clear.
357-
for (const sta::Sequential& seq : lib_cell->sequentials()) {
358-
if (seq.clear() && seq.clear()->hasPort(lib_port)) {
359-
return true;
360-
}
361-
}
362-
363-
// If it exists, check the test lib cell if the port is a clear.
364-
const sta::LibertyCell* test_cell = lib_cell->testCell();
365-
if (test_cell == nullptr) {
366-
return false;
367-
}
368-
369-
// Find the equivalent lib_port on the test cell by name.
370-
//
371-
// TODO: NA - Make retrieving the port on the lib cell possible without doing
372-
// a name match each time
373-
const sta::LibertyPort* test_lib_port
374-
= test_cell->findLibertyPort(lib_port->name());
375-
if (test_lib_port == nullptr) {
376-
return false;
377-
}
378-
379-
for (const sta::Sequential& seq : test_cell->sequentials()) {
380-
if (seq.clear() && seq.clear()->hasPort(test_lib_port)) {
381-
return true;
382-
}
383-
}
384-
385-
return false;
411+
return portInSequentialFunc(lib_cell, lib_port, &sta::Sequential::clear);
386412
}
387413

388414
bool MBFF::HasPreset(dbInst* inst)
@@ -414,37 +440,7 @@ bool MBFF::IsPresetPin(dbITerm* iterm)
414440
if (lib_cell == nullptr) {
415441
return false;
416442
}
417-
418-
// Check the lib cell if the port is a preset.
419-
for (const sta::Sequential& seq : lib_cell->sequentials()) {
420-
if (seq.preset() && seq.preset()->hasPort(lib_port)) {
421-
return true;
422-
}
423-
}
424-
425-
// If it exists, check the test lib cell if the port is a preset.
426-
const sta::LibertyCell* test_cell = lib_cell->testCell();
427-
if (test_cell == nullptr) {
428-
return false;
429-
}
430-
431-
// Find the equivalent lib_port on the test cell by name.
432-
//
433-
// TODO: NA - Make retrieving the port on the lib cell possible without doing
434-
// a name match each time
435-
const sta::LibertyPort* test_lib_port
436-
= test_cell->findLibertyPort(lib_port->name());
437-
if (test_lib_port == nullptr) {
438-
return false;
439-
}
440-
441-
for (const sta::Sequential& seq : test_cell->sequentials()) {
442-
if (seq.preset() && seq.preset()->hasPort(test_lib_port)) {
443-
return true;
444-
}
445-
}
446-
447-
return false;
443+
return portInSequentialFunc(lib_cell, lib_port, &sta::Sequential::preset);
448444
}
449445

450446
bool MBFF::IsScanCell(dbInst* inst)
@@ -797,8 +793,14 @@ void MBFF::ModifyPinConnections(const std::vector<Flop>& flops,
797793
+ "_" + std::to_string(unused_.back());
798794
unused_.pop_back();
799795
const int bit_idx = GetBitIdx(trays[tray_idx].slots.size());
800-
auto new_tray = dbInst::create(
801-
block_, best_master_[array_mask][bit_idx], new_name.c_str());
796+
// SeparateFlops partitions by (parent module, mask), so flops in this
797+
// cluster share one parent module; place the new tray there.
798+
dbModule* parent = insts_[flops[i].idx]->getModule();
799+
auto new_tray = dbInst::create(block_,
800+
best_master_[array_mask][bit_idx],
801+
new_name.c_str(),
802+
/*physical_only=*/false,
803+
parent);
802804
const Point tray_center = GetTrayCenter(array_mask, bit_idx);
803805
new_tray->setLocation(
804806
multiplier_ * (trays[tray_idx].pt.x - tray_center.x),
@@ -810,6 +812,7 @@ void MBFF::ModifyPinConnections(const std::vector<Flop>& flops,
810812
}
811813

812814
dbNet* clk_net = nullptr;
815+
odb::dbModNet* clk_mod_net = nullptr;
813816
for (int i = 0; i < num_flops; i++) {
814817
// single bit flop?
815818
if (new_mapping[i].first == std::numeric_limits<int>::max()) {
@@ -876,56 +879,55 @@ void MBFF::ModifyPinConnections(const std::vector<Flop>& flops,
876879
const bool is_qn_inv = is_q && IsInvertingQPin(iterm);
877880
const std::string orig_port_name = iterm->getMTerm()->getName();
878881

882+
// Capture both flat and hierarchical nets of the original iterm
883+
// before disconnecting, then rebind the matching tray iterm to
884+
// both. Preserves the hierarchical netlist after clustering.
879885
dbNet* net = iterm->getNet();
880-
while (net) {
886+
odb::dbModNet* mod_net = iterm->getModNet();
887+
auto reconnect = [&](dbITerm* tray_iterm) {
888+
reconnectIterm(tray_iterm, net, mod_net);
889+
};
890+
if (net || mod_net) {
881891
iterm->disconnect();
882892

883-
// standard pins
884893
if (is_d) {
885-
tray_inst[tray_idx]->findITerm(d_pin->name().c_str())->connect(net);
894+
reconnect(tray_inst[tray_idx]->findITerm(d_pin->name().c_str()));
886895
}
887896
if (is_q) {
888897
if (is_qn_inv) {
889-
tray_inst[tray_idx]
890-
->findITerm(qn_pin->name().c_str())
891-
->connect(net);
898+
reconnect(tray_inst[tray_idx]->findITerm(qn_pin->name().c_str()));
892899
} else {
893-
tray_inst[tray_idx]->findITerm(q_pin->name().c_str())->connect(net);
900+
reconnect(tray_inst[tray_idx]->findITerm(q_pin->name().c_str()));
894901
}
895902
}
896903
if (IsSupplyPin(iterm)) {
897904
if (iterm->getSigType() == odb::dbSigType::GROUND) {
898905
if (ground) {
899-
ground->connect(net);
906+
reconnect(ground);
900907
}
901908
} else {
902909
if (power) {
903-
power->connect(net);
910+
reconnect(power);
904911
}
905912
}
906913
}
907914
if (IsClockPin(iterm)) {
908-
// reconnect pins later
915+
// reconnect clock pins later (shared across the tray)
909916
clk_net = net;
917+
clk_mod_net = mod_net;
910918
}
911-
912-
// scan pins
913919
if (IsScanIn(iterm)) {
914-
scan_in->connect(net);
920+
reconnect(scan_in);
915921
}
916922
if (IsScanEnable(iterm)) {
917-
scan_enable->connect(net);
923+
reconnect(scan_enable);
918924
}
919-
920-
// preset/clear pins
921925
if (IsPresetPin(iterm)) {
922-
preset->connect(net);
926+
reconnect(preset);
923927
}
924928
if (IsClearPin(iterm)) {
925-
clear->connect(net);
929+
reconnect(clear);
926930
}
927-
928-
net = iterm->getNet();
929931
}
930932

931933
// Store original FF→tray pin mapping as a property on the tray
@@ -958,10 +960,11 @@ void MBFF::ModifyPinConnections(const std::vector<Flop>& flops,
958960
std::vector<bool> isConnected(tray_inst.size());
959961
for (int i = 0; i < num_flops; i++) {
960962
if (new_mapping[i].first != std::numeric_limits<int>::max()) {
961-
if (!isConnected[new_mapping[i].first] && clk_net != nullptr) {
963+
if (!isConnected[new_mapping[i].first]
964+
&& (clk_net != nullptr || clk_mod_net != nullptr)) {
962965
for (dbITerm* iterm : tray_inst[new_mapping[i].first]->getITerms()) {
963966
if (IsClockPin(iterm)) {
964-
iterm->connect(clk_net);
967+
reconnectIterm(iterm, clk_net, clk_mod_net);
965968
}
966969
}
967970
isConnected[new_mapping[i].first] = true;
@@ -2433,23 +2436,44 @@ void MBFF::SeparateFlops(std::vector<std::vector<Flop>>& ffs)
24332436
}
24342437
}
24352438

2439+
// Order modules by id, not pointer value — pointer order varies
2440+
// across runs and would make tray naming / ILP seed consumption
2441+
// non-deterministic.
2442+
struct ModMaskLess
2443+
{
2444+
bool operator()(const std::pair<dbModule*, Mask>& a,
2445+
const std::pair<dbModule*, Mask>& b) const
2446+
{
2447+
if (a.first != b.first) {
2448+
return odb::compare_by_id(a.first, b.first);
2449+
}
2450+
return a.second < b.second;
2451+
}
2452+
};
2453+
24362454
for (const auto& [clk_net, indices] : clk_terms) {
2437-
ArrayMaskVector<Flop> flops_by_mask;
2455+
// Partition by (parent module, mask) so flops in different
2456+
// hierarchical modules are never clustered into the same tray.
2457+
std::map<std::pair<dbModule*, Mask>, std::vector<Flop>, ModMaskLess>
2458+
flops_by_mod_mask;
24382459
for (const int idx : indices) {
24392460
const Mask vec_mask = GetArrayMask(insts_[idx], false);
2440-
flops_by_mask[vec_mask].push_back(flops_[idx]);
2461+
flops_by_mod_mask[{insts_[idx]->getModule(), vec_mask}].push_back(
2462+
flops_[idx]);
24412463
}
24422464

2443-
for (const auto& [mask, flops] : flops_by_mask) {
2465+
for (const auto& [key, flops] : flops_by_mod_mask) {
24442466
if (!flops.empty()) {
24452467
ffs.push_back(flops);
24462468
debugPrint(log_,
24472469
GPL,
24482470
"mbff",
24492471
1,
2450-
"Flop cluster for net {} with mask {} of size {}",
2472+
"Flop cluster for net {} in module {} with mask {} of "
2473+
"size {}",
24512474
clk_net->getName(),
2452-
mask.to_string(),
2475+
key.first ? key.first->getHierarchicalName() : "<top>",
2476+
key.second.to_string(),
24532477
flops.size());
24542478
}
24552479
}

src/gpl/test/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ TESTS = [
1717
"diverge01",
1818
"error01",
1919
"incremental01",
20+
"mbff_hier",
2021
"mbff_orig_name",
2122
"nograd01",
2223
"simple01",

src/gpl/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ or_integration_tests(
77
clust02
88
clust03
99
cluster_place01
10+
mbff_hier
1011
mbff_orig_name
1112
convergence01
1213
core01

src/gpl/test/mbff_hier.ok

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[INFO ODB-0227] LEF file: ./asap7/asap7_tech_1x_201209.lef, created 30 layers, 9 vias
2+
[INFO ODB-0227] LEF file: ./SingleBit/asap7sc7p5t_28_L_1x_220121a.lef, created 212 library cells
3+
[INFO ODB-0227] LEF file: ./2BitTrayH2/asap7sc7p5t_DFFHQNV2X.lef, created 9 library cells
4+
[INFO ODB-0394] Duplicate site asap7sc7p5t_pg in asap7sc7p5t_DFFHQNV4X already seen in asap7sc7p5t_DFFHQNV2X
5+
[INFO ODB-0227] LEF file: ./4BitTrayH4/asap7sc7p5t_DFFHQNV4X.lef, created 9 library cells
6+
[WARNING ORD-0011] Hierarchical flow (-hier) is currently in development and may cause multiple issues. Do not use in production environments.
7+
Alpha = 40.0, Beta = 0.0, #paths = 0, max size = -1
8+
Total ILP Cost: 229.603
9+
Total Timing Critical Path Displacement: 0.0
10+
Average slot-to-flop displacement: 2.270
11+
Final Objective Value: 229.603
12+
Sizes used
13+
4-bit: 2
14+
[WARNING EST-0010] Signal/clock horizontal wire resistance is 0.
15+
[WARNING EST-0011] Signal/clock vertical wire resistance is 0.
16+
[WARNING EST-0012] Signal/clock horizontal wire capacitance is 0.
17+
[WARNING EST-0013] Signal/clock vertical wire capacitance is 0.
18+
[WARNING EST-0018] wire capacitance for corner default is zero. Use the set_wire_rc command to set wire resistance and capacitance.
19+
ESTIMATE_PARASITICS_OK

0 commit comments

Comments
 (0)