diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index 741ce875..c076d64c 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -1489,7 +1489,8 @@ LibertyCell::outputPortSequential(LibertyPort *port) bool LibertyCell::hasSequentials() const { - return !sequentials_.empty(); + return !sequentials_.empty() + || statetable_ != nullptr; } void @@ -2479,7 +2480,7 @@ LibertyPort::less(const LibertyPort *port1, { if (port1 == nullptr && port2 != nullptr) return true; - if (port1 != nullptr && port2 == nullptr) + if (port1 == nullptr || port2 == nullptr) return false; const std::string &name1 = port1->name(); const std::string &name2 = port2->name(); diff --git a/liberty/Liberty.i b/liberty/Liberty.i index f0a9bc09..81704bad 100644 --- a/liberty/Liberty.i +++ b/liberty/Liberty.i @@ -303,6 +303,7 @@ ensure_voltage_waveforms() } LibertyCell *test_cell() { return self->testCell(); } +bool has_sequentials() { return self->hasSequentials(); } } // LibertyCell methods diff --git a/liberty/test/cpp/TestLibertyStaCallbacks.cc b/liberty/test/cpp/TestLibertyStaCallbacks.cc index 70f3502f..e6e67e0c 100644 --- a/liberty/test/cpp/TestLibertyStaCallbacks.cc +++ b/liberty/test/cpp/TestLibertyStaCallbacks.cc @@ -4308,4 +4308,52 @@ library(test_r11_ccs) { }() )); } +// Regression: hasSequentials must return true for cells that define +// sequential behavior via a statetable group (no ff/latch). +// Multi-bit flip-flops (MBFFs) and clock-gated cells commonly use +// statetable instead of ff/latch groups. Without the statetable_ +// check in hasSequentials(), these cells are misclassified as +// combinational — breaking power categorization, resizer guards, +// and dbSta cell-type mapping. +TEST_F(StaLibertyTest, HasSequentialsStatetableMBFF) { + const char *content = R"( +library(test_mbff_statetable) { + delay_model : table_lookup ; + time_unit : "1ns" ; + voltage_unit : "1V" ; + current_unit : "1mA" ; + capacitive_load_unit(1, ff) ; + cell(MBFF2) { + area : 6.0 ; + pin(D0) { direction : input ; capacitance : 0.01 ; } + pin(D1) { direction : input ; capacitance : 0.01 ; } + pin(CLK) { direction : input ; capacitance : 0.01 ; clock : true ; } + pin(Q0) { direction : output ; function : "IQ0" ; } + pin(Q1) { direction : output ; function : "IQ1" ; } + statetable("D0 D1 CLK", "IQ0 IQ1") { + table : "- - ~R : - - : N N ,\ + H - R : - - : H N ,\ + L - R : - - : L N ,\ + - H R : - - : N H ,\ + - L R : - - : N L" ; + } + } +} +)"; + LibertyLibrary *lib = writeAndReadLibReturn(sta_, content); + ASSERT_NE(lib, nullptr); + LibertyCell *mbff = lib->findLibertyCell("MBFF2"); + ASSERT_NE(mbff, nullptr); + + // The cell has no ff/latch group, so sequentials_ is empty. + EXPECT_TRUE(mbff->sequentials().empty()); + // But it has a statetable, so it IS sequential. + EXPECT_NE(mbff->statetable(), nullptr); + // hasSequentials() must return true for statetable-only cells. + EXPECT_TRUE(mbff->hasSequentials()) + << "MBFF2 uses statetable (no ff/latch) but hasSequentials() " + "returned false — statetable cells are misclassified as " + "combinational"; +} + } // namespace sta