OpenSTA/search/test/cpp/TestSearchIncremental.cc

1593 lines
58 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include <tcl.h>
#include <cmath>
#include "MinMax.hh"
#include "Transition.hh"
#include "Scene.hh"
#include "Sta.hh"
#include "Sdc.hh"
#include "ReportTcl.hh"
#include "Network.hh"
#include "Liberty.hh"
#include "Graph.hh"
#include "TimingArc.hh"
namespace sta {
// Test fixture that loads a design, creates constraints, and runs
// initial timing so that incremental timing tests can modify the
// netlist and verify timing updates.
class IncrementalTimingTest : public ::testing::Test {
protected:
void SetUp() override {
interp_ = Tcl_CreateInterp();
initSta();
sta_ = new Sta;
Sta::setSta(sta_);
sta_->makeComponents();
ReportTcl *report = dynamic_cast<ReportTcl*>(sta_->report());
if (report)
report->setTclInterp(interp_);
Scene *corner = sta_->cmdScene();
const MinMaxAll *min_max = MinMaxAll::all();
LibertyLibrary *lib = sta_->readLiberty(
"test/nangate45/Nangate45_typ.lib", corner, min_max, false);
ASSERT_NE(lib, nullptr);
bool ok = sta_->readVerilog("search/test/search_test1.v");
ASSERT_TRUE(ok);
ok = sta_->linkDesign("search_test1", true);
ASSERT_TRUE(ok);
// Create clock on 'clk' pin with 10ns period
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Pin *clk_pin = network->findPin(top, "clk");
ASSERT_NE(clk_pin, nullptr);
PinSet *clk_pins = new PinSet(network);
clk_pins->insert(clk_pin);
FloatSeq *waveform = new FloatSeq;
waveform->push_back(0.0f);
waveform->push_back(5.0f);
Sdc *sdc = sta_->cmdSdc();
sta_->makeClock("clk", clk_pins, false, 10.0f, waveform, "",
sta_->cmdMode());
Clock *clk = sdc->findClock("clk");
ASSERT_NE(clk, nullptr);
// Set input delay on in1 and in2
Pin *in1 = network->findPin(top, "in1");
Pin *in2 = network->findPin(top, "in2");
ASSERT_NE(in1, nullptr);
ASSERT_NE(in2, nullptr);
sta_->setInputDelay(in1, RiseFallBoth::riseFall(),
clk, RiseFall::rise(), nullptr,
false, false, MinMaxAll::all(), false, 0.5f, sdc);
sta_->setInputDelay(in2, RiseFallBoth::riseFall(),
clk, RiseFall::rise(), nullptr,
false, false, MinMaxAll::all(), false, 0.5f, sdc);
// Set output delay on out1
Pin *out1 = network->findPin(top, "out1");
ASSERT_NE(out1, nullptr);
sta_->setOutputDelay(out1, RiseFallBoth::riseFall(),
clk, RiseFall::rise(), nullptr,
false, false, MinMaxAll::all(), false, 0.5f, sdc);
// Run full timing
sta_->updateTiming(true);
}
void TearDown() override {
deleteAllMemory();
sta_ = nullptr;
if (interp_)
Tcl_DeleteInterp(interp_);
interp_ = nullptr;
}
Sta *sta_;
Tcl_Interp *interp_;
};
////////////////////////////////////////////////////////////////
// Test 1: Replace a buffer with a larger one and verify timing changes,
// then replace back and verify timing returns to original.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ReplaceCellAndVerifyTiming) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Get initial worst slack
Slack initial_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(initial_slack));
// Find buf1 (BUF_X1) instance
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
// Find larger buffer cell BUF_X4
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
// Replace BUF_X1 with BUF_X4 (larger = faster = better slack)
sta_->replaceCell(buf1, buf_x4);
Slack after_upsize_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(after_upsize_slack));
// Larger buffer should yield better (larger) or equal slack
EXPECT_GE(after_upsize_slack, initial_slack);
// Replace back with BUF_X1
LibertyCell *buf_x1 = network->findLibertyCell("BUF_X1");
ASSERT_NE(buf_x1, nullptr);
sta_->replaceCell(buf1, buf_x1);
Slack restored_slack = sta_->worstSlack(MinMax::max());
// Slack should return to original value
EXPECT_NEAR(restored_slack, initial_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 2: Replace a cell with a smaller variant and verify
// timing degrades (worse slack).
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ReplaceCellDownsize) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Get initial worst slack
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Find buf1 and upsize it first so we have room to downsize
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
Slack upsized_slack = sta_->worstSlack(MinMax::max());
// Now downsize back to BUF_X1
LibertyCell *buf_x1 = network->findLibertyCell("BUF_X1");
ASSERT_NE(buf_x1, nullptr);
sta_->replaceCell(buf1, buf_x1);
Slack downsized_slack = sta_->worstSlack(MinMax::max());
// Downsized slack should be worse (smaller) or equal to upsized slack
EXPECT_LE(downsized_slack, upsized_slack);
}
////////////////////////////////////////////////////////////////
// Test 3: Insert a buffer into an existing path and verify
// timing includes the new buffer (path delay increases).
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, InsertBufferAndVerify) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Get initial worst slack
Slack initial_slack = sta_->worstSlack(MinMax::max());
// We will insert a buffer between buf1/Z and reg1/D.
// Current: buf1/Z --[n2]--> reg1/D
// After: buf1/Z --[n2]--> new_buf/A, new_buf/Z --[new_net]--> reg1/D
Instance *reg1 = network->findChild(top, "reg1");
ASSERT_NE(reg1, nullptr);
Pin *reg1_d = network->findPin(reg1, "D");
ASSERT_NE(reg1_d, nullptr);
// Disconnect reg1/D from net n2
sta_->disconnectPin(reg1_d);
// Create a new buffer instance
LibertyCell *buf_x1 = network->findLibertyCell("BUF_X1");
ASSERT_NE(buf_x1, nullptr);
Instance *new_buf = sta_->makeInstance("inserted_buf", buf_x1, top);
ASSERT_NE(new_buf, nullptr);
// Create a new net
Net *new_net = sta_->makeNet("new_net", top);
ASSERT_NE(new_net, nullptr);
// Find the existing net n2
Net *n2 = network->findNet(top, "n2");
ASSERT_NE(n2, nullptr);
// Connect new_buf/A to n2 (existing net from buf1/Z)
LibertyPort *buf_a_port = buf_x1->findLibertyPort("A");
LibertyPort *buf_z_port = buf_x1->findLibertyPort("Z");
ASSERT_NE(buf_a_port, nullptr);
ASSERT_NE(buf_z_port, nullptr);
sta_->connectPin(new_buf, buf_a_port, n2);
// Connect new_buf/Z to new_net
sta_->connectPin(new_buf, buf_z_port, new_net);
// Connect reg1/D to new_net
LibertyCell *dff_cell = network->findLibertyCell("DFF_X1");
ASSERT_NE(dff_cell, nullptr);
LibertyPort *dff_d_port = dff_cell->findLibertyPort("D");
ASSERT_NE(dff_d_port, nullptr);
sta_->connectPin(reg1, dff_d_port, new_net);
// Check timing after insertion
Slack after_insert_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(after_insert_slack));
// Inserting a buffer adds delay, so slack should degrade
EXPECT_LE(after_insert_slack, initial_slack);
// Clean up: reverse the insertion
// Disconnect new_buf pins and reg1/D
Pin *new_buf_a = network->findPin(new_buf, "A");
Pin *new_buf_z = network->findPin(new_buf, "Z");
Pin *reg1_d_new = network->findPin(reg1, "D");
ASSERT_NE(new_buf_a, nullptr);
ASSERT_NE(new_buf_z, nullptr);
ASSERT_NE(reg1_d_new, nullptr);
sta_->disconnectPin(reg1_d_new);
sta_->disconnectPin(new_buf_a);
sta_->disconnectPin(new_buf_z);
sta_->deleteInstance(new_buf);
sta_->deleteNet(new_net);
// Reconnect reg1/D to n2
sta_->connectPin(reg1, dff_d_port, n2);
// Verify timing restores
Slack restored_slack = sta_->worstSlack(MinMax::max());
EXPECT_NEAR(restored_slack, initial_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 4: Remove a buffer from the path and verify timing improves.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, RemoveBufferAndVerify) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Current path: and1/ZN --[n1]--> buf1/A, buf1/Z --[n2]--> reg1/D
// After removing buf1: and1/ZN --[n1]--> reg1/D (shorter path)
Slack initial_slack = sta_->worstSlack(MinMax::max());
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
Instance *reg1 = network->findChild(top, "reg1");
ASSERT_NE(reg1, nullptr);
// Get the net n1 (and1 output to buf1 input)
Net *n1 = network->findNet(top, "n1");
ASSERT_NE(n1, nullptr);
// Disconnect buf1 pins and reg1/D
Pin *buf1_a = network->findPin(buf1, "A");
Pin *buf1_z = network->findPin(buf1, "Z");
Pin *reg1_d = network->findPin(reg1, "D");
ASSERT_NE(buf1_a, nullptr);
ASSERT_NE(buf1_z, nullptr);
ASSERT_NE(reg1_d, nullptr);
sta_->disconnectPin(reg1_d);
sta_->disconnectPin(buf1_a);
sta_->disconnectPin(buf1_z);
// Connect reg1/D directly to n1
LibertyCell *dff_cell = network->findLibertyCell("DFF_X1");
ASSERT_NE(dff_cell, nullptr);
LibertyPort *dff_d_port = dff_cell->findLibertyPort("D");
ASSERT_NE(dff_d_port, nullptr);
sta_->connectPin(reg1, dff_d_port, n1);
// Timing should improve (buffer removed from path)
Slack after_remove_slack = sta_->worstSlack(MinMax::max());
EXPECT_GE(after_remove_slack, initial_slack);
// Restore: reconnect buf1
Pin *reg1_d_new = network->findPin(reg1, "D");
ASSERT_NE(reg1_d_new, nullptr);
sta_->disconnectPin(reg1_d_new);
LibertyCell *buf_x1 = network->findLibertyCell("BUF_X1");
LibertyPort *buf_a_port = buf_x1->findLibertyPort("A");
LibertyPort *buf_z_port = buf_x1->findLibertyPort("Z");
Net *n2 = network->findNet(top, "n2");
ASSERT_NE(n2, nullptr);
sta_->connectPin(buf1, buf_a_port, n1);
sta_->connectPin(buf1, buf_z_port, n2);
sta_->connectPin(reg1, dff_d_port, n2);
Slack restored_slack = sta_->worstSlack(MinMax::max());
EXPECT_NEAR(restored_slack, initial_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 5: Make multiple edits before retiming to verify combined effect.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, MultipleEditsBeforeRetiming) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Edit 1: Upsize buf1 to BUF_X4
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
// Edit 2: Set output load on out1
Pin *out1_pin = network->findPin(top, "out1");
ASSERT_NE(out1_pin, nullptr);
Port *out1_port = network->findPort(
network->cell(top), "out1");
ASSERT_NE(out1_port, nullptr);
Sdc *sdc = sta_->cmdSdc();
sta_->setPortExtPinCap(out1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.05f, sdc);
// Edit 3: Set input slew on in1
Port *in1_port = network->findPort(
network->cell(top), "in1");
ASSERT_NE(in1_port, nullptr);
sta_->setInputSlew(in1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.1f, sdc);
// Now run timing once (implicitly via worstSlack)
Slack combined_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(combined_slack));
// The combined effect should differ from initial
// (upsizing helps, load/slew may hurt -- just verify it's valid)
EXPECT_NE(combined_slack, initial_slack);
}
////////////////////////////////////////////////////////////////
// Test 6: Verify incremental vs full timing produce the same result.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, IncrementalVsFullConsistency) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Make an edit: upsize buf2 to BUF_X4
Instance *buf2 = network->findChild(top, "buf2");
ASSERT_NE(buf2, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf2, buf_x4);
// Run incremental timing
sta_->updateTiming(false);
Slack incremental_slack = sta_->worstSlack(MinMax::max());
// Run full timing
sta_->updateTiming(true);
Slack full_slack = sta_->worstSlack(MinMax::max());
// Both should produce the same result
EXPECT_NEAR(incremental_slack, full_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 7: Set output load and verify timing updates incrementally.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, SetLoadIncremental) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Set a large output load on out1
Port *out1_port = network->findPort(
network->cell(top), "out1");
ASSERT_NE(out1_port, nullptr);
Sdc *sdc = sta_->cmdSdc();
sta_->setPortExtPinCap(out1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.5f, sdc);
Slack loaded_slack = sta_->worstSlack(MinMax::max());
// Large load should degrade timing
EXPECT_LE(loaded_slack, initial_slack);
// Reduce the load
sta_->setPortExtPinCap(out1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.001f, sdc);
Slack reduced_load_slack = sta_->worstSlack(MinMax::max());
// Reduced load should improve timing relative to large load
EXPECT_GE(reduced_load_slack, loaded_slack);
}
////////////////////////////////////////////////////////////////
// Test 8: Replace a cell and change clock period, verify both
// changes are reflected in timing.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ClockConstraintAfterEdit) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Edit: Replace buf1 with BUF_X4
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
Slack after_replace_slack = sta_->worstSlack(MinMax::max());
// Now tighten the clock period (smaller period = tighter timing)
Pin *clk_pin = network->findPin(top, "clk");
ASSERT_NE(clk_pin, nullptr);
PinSet *clk_pins = new PinSet(network);
clk_pins->insert(clk_pin);
FloatSeq *tight_waveform = new FloatSeq;
tight_waveform->push_back(0.0f);
tight_waveform->push_back(1.0f); // 2ns period
sta_->makeClock("clk", clk_pins, false, 2.0f, tight_waveform, "",
sta_->cmdMode());
Slack tight_slack = sta_->worstSlack(MinMax::max());
// Tighter clock should give worse slack
EXPECT_LT(tight_slack, after_replace_slack);
// Now loosen the clock period significantly
PinSet *clk_pins2 = new PinSet(network);
clk_pins2->insert(clk_pin);
FloatSeq *loose_waveform = new FloatSeq;
loose_waveform->push_back(0.0f);
loose_waveform->push_back(50.0f); // 100ns period
sta_->makeClock("clk", clk_pins2, false, 100.0f, loose_waveform, "",
sta_->cmdMode());
Slack loose_slack = sta_->worstSlack(MinMax::max());
// Looser clock should give better slack than tight clock
EXPECT_GT(loose_slack, tight_slack);
}
////////////////////////////////////////////////////////////////
// Test 9: Replace AND gate with larger variant (AND2_X4)
// and verify timing improves due to stronger drive.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ReplaceAndGateWithLargerVariant) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Find and1 instance (AND2_X1) and replace with AND2_X4
Instance *and1 = network->findChild(top, "and1");
ASSERT_NE(and1, nullptr);
LibertyCell *and2_x4 = network->findLibertyCell("AND2_X4");
ASSERT_NE(and2_x4, nullptr);
sta_->replaceCell(and1, and2_x4);
Slack after_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(after_slack));
// Larger AND gate has stronger drive, should improve or maintain slack
EXPECT_GE(after_slack, initial_slack);
// Replace back and verify restoration
LibertyCell *and2_x1 = network->findLibertyCell("AND2_X1");
ASSERT_NE(and2_x1, nullptr);
sta_->replaceCell(and1, and2_x1);
Slack restored_slack = sta_->worstSlack(MinMax::max());
EXPECT_NEAR(restored_slack, initial_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 10: Chain of replacements: upsize -> downsize -> upsize
// and verify timing is consistent after each step.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ChainedReplacementsConsistency) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x1 = network->findLibertyCell("BUF_X1");
LibertyCell *buf_x2 = network->findLibertyCell("BUF_X2");
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x1, nullptr);
ASSERT_NE(buf_x2, nullptr);
ASSERT_NE(buf_x4, nullptr);
// Step 1: Upsize to BUF_X4
sta_->replaceCell(buf1, buf_x4);
Slack slack_x4 = sta_->worstSlack(MinMax::max());
// Step 2: Downsize to BUF_X2
sta_->replaceCell(buf1, buf_x2);
Slack slack_x2 = sta_->worstSlack(MinMax::max());
// BUF_X4 should be at least as good as BUF_X2
EXPECT_GE(slack_x4, slack_x2);
// Step 3: Upsize again to BUF_X4
sta_->replaceCell(buf1, buf_x4);
Slack slack_x4_again = sta_->worstSlack(MinMax::max());
// Same cell should produce same slack
EXPECT_NEAR(slack_x4, slack_x4_again, 1e-6);
// Step 4: Return to original
sta_->replaceCell(buf1, buf_x1);
Slack slack_original = sta_->worstSlack(MinMax::max());
// X2 should be between X1 and X4
EXPECT_GE(slack_x2, slack_original);
EXPECT_LE(slack_x2, slack_x4);
}
////////////////////////////////////////////////////////////////
// Test 11: Replace all cells on the combinational path
// (and1 + buf1) and verify cumulative timing effect.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ReplaceAllCellsOnPath) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Upsize and1 to AND2_X4
Instance *and1 = network->findChild(top, "and1");
ASSERT_NE(and1, nullptr);
LibertyCell *and2_x4 = network->findLibertyCell("AND2_X4");
ASSERT_NE(and2_x4, nullptr);
sta_->replaceCell(and1, and2_x4);
Slack after_and_slack = sta_->worstSlack(MinMax::max());
// Also upsize buf1 to BUF_X4
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
Slack after_both_slack = sta_->worstSlack(MinMax::max());
// Both upsized should be at least as good as just and1 upsized
EXPECT_GE(after_both_slack, initial_slack);
// Cumulative improvement: both should be better than initial
EXPECT_GE(after_and_slack, initial_slack);
EXPECT_GE(after_both_slack, initial_slack);
}
////////////////////////////////////////////////////////////////
// Test 12: Set timing derate (cell delay) after initial timing
// and verify setup slack degrades for late derate > 1.0.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, TimingDerateAffectsSlack) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Sdc *sdc = sta_->cmdSdc();
// Timing derate requires OCV analysis mode to distinguish early/late.
sta_->setAnalysisType(AnalysisType::ocv, sdc);
// Add significant load to make gate delays visible for derating
Port *out1_port = network->findPort(network->cell(top), "out1");
ASSERT_NE(out1_port, nullptr);
sta_->setPortExtPinCap(out1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.5f, sdc);
sta_->updateTiming(true);
Slack initial_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(initial_slack));
// Apply a large cell delay derate on data paths for late analysis
sta_->setTimingDerate(TimingDerateType::cell_delay,
PathClkOrData::data,
RiseFallBoth::riseFall(),
EarlyLate::late(),
5.0f, sdc);
Slack derated_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(derated_slack));
// Late derate > 1.0 increases data path delay, worsening setup slack
// With 0.5pF load, gate delays are significant enough that 5x derate
// should produce a visible effect on slack
EXPECT_LE(derated_slack, initial_slack);
// Remove the derate and verify slack restores
sta_->unsetTimingDerate(sdc);
Slack restored_slack = sta_->worstSlack(MinMax::max());
EXPECT_NEAR(restored_slack, initial_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 13: Set clock uncertainty and verify setup slack degrades.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ClockUncertaintyDegradeSetupSlack) {
Slack initial_slack = sta_->worstSlack(MinMax::max());
Clock *clk = sta_->cmdSdc()->findClock("clk");
ASSERT_NE(clk, nullptr);
// Add 0.5ns setup uncertainty -- eats into the timing margin
sta_->setClockUncertainty(clk, SetupHoldAll::max(), 0.5f);
Slack after_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(after_slack));
// Uncertainty reduces available margin, slack should worsen
EXPECT_LT(after_slack, initial_slack);
// The slack difference should be approximately 0.5ns
float slack_diff = initial_slack - after_slack;
EXPECT_NEAR(slack_diff, 0.5f, 0.01f);
// Remove uncertainty
sta_->removeClockUncertainty(clk, SetupHoldAll::max());
Slack restored_slack = sta_->worstSlack(MinMax::max());
EXPECT_NEAR(restored_slack, initial_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 14: Set input transition (slew) on port and verify
// path delay changes.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, InputSlewChangesPathDelay) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
Sdc *sdc = sta_->cmdSdc();
// Set a very large input slew on in1 (1ns)
Port *in1_port = network->findPort(network->cell(top), "in1");
ASSERT_NE(in1_port, nullptr);
sta_->setInputSlew(in1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 1.0f, sdc);
Slack after_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(after_slack));
// Large input slew increases gate delays downstream, worsening slack
EXPECT_LE(after_slack, initial_slack);
// Now set a small slew (fast transition)
sta_->setInputSlew(in1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.001f, sdc);
Slack fast_slack = sta_->worstSlack(MinMax::max());
// Fast slew should give better timing than slow slew
EXPECT_GE(fast_slack, after_slack);
}
////////////////////////////////////////////////////////////////
// Test 15: Disable timing on the AND cell and verify the
// path through it is no longer constrained (slack improves
// dramatically or becomes unconstrained).
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, DisableCellTimingExcludesPath) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Check pin slack on reg1/D (endpoint of the input path through and1)
Instance *reg1 = network->findChild(top, "reg1");
ASSERT_NE(reg1, nullptr);
Pin *reg1_d = network->findPin(reg1, "D");
ASSERT_NE(reg1_d, nullptr);
Slack initial_pin_slack = sta_->slack(reg1_d, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_FALSE(std::isnan(initial_pin_slack));
// Find and1 instance and disable timing through it
Instance *and1 = network->findChild(top, "and1");
ASSERT_NE(and1, nullptr);
// Disable all timing arcs through and1 instance
Sdc *sdc = sta_->cmdSdc();
sta_->disable(and1, nullptr, nullptr, sdc);
// After disabling and1, the path in1/in2 -> and1 -> buf1 -> reg1 is broken.
// The pin slack at reg1/D should become unconstrained (NaN/INF) or improve
// significantly because no constrained path reaches it.
Slack after_disable_pin_slack = sta_->slack(reg1_d, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
// Either unconstrained (NaN/very large) or much better than before
if (std::isnan(after_disable_pin_slack) == false) {
EXPECT_GT(after_disable_pin_slack, initial_pin_slack);
}
// else: NaN means unconstrained, which is expected
// Re-enable timing through and1
sta_->removeDisable(and1, nullptr, nullptr, sdc);
Slack restored_pin_slack = sta_->slack(reg1_d, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_NEAR(restored_pin_slack, initial_pin_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 16: Disconnect and reconnect a pin, verify timing restores.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, DisconnectReconnectPinRestoresTiming) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Use pin slack at reg1/D to track the specific input path
Instance *reg1 = network->findChild(top, "reg1");
ASSERT_NE(reg1, nullptr);
Pin *reg1_d = network->findPin(reg1, "D");
ASSERT_NE(reg1_d, nullptr);
Slack initial_pin_slack = sta_->slack(reg1_d, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_FALSE(std::isnan(initial_pin_slack));
// Disconnect reg1/D from n2 and reconnect it
sta_->disconnectPin(reg1_d);
// Reconnect reg1/D to n2
Net *n2 = network->findNet(top, "n2");
ASSERT_NE(n2, nullptr);
LibertyCell *dff_cell = network->findLibertyCell("DFF_X1");
ASSERT_NE(dff_cell, nullptr);
LibertyPort *dff_d_port = dff_cell->findLibertyPort("D");
ASSERT_NE(dff_d_port, nullptr);
sta_->connectPin(reg1, dff_d_port, n2);
// After disconnect/reconnect to same net, timing should restore
Slack restored_pin_slack = sta_->slack(reg1_d, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
// The pin slack should be restored to close to the initial value
// after reconnecting to the same net
EXPECT_FALSE(std::isnan(restored_pin_slack));
EXPECT_NEAR(restored_pin_slack, initial_pin_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 17: Connect reg1/D to a different net (n1 instead of n2),
// bypassing buf1, and verify timing updates for new topology.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ConnectPinToDifferentNet) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Add significant output load on buf1 output (net n2) to make
// the buf1 delay visible when bypassing it.
Port *out1_port = network->findPort(network->cell(top), "out1");
ASSERT_NE(out1_port, nullptr);
sta_->setPortExtPinCap(out1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.1f, sta_->cmdSdc());
sta_->updateTiming(true);
// Track pin slack at reg1/D for the input path
Instance *reg1 = network->findChild(top, "reg1");
ASSERT_NE(reg1, nullptr);
Pin *reg1_d = network->findPin(reg1, "D");
ASSERT_NE(reg1_d, nullptr);
Slack initial_pin_slack = sta_->slack(reg1_d, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_FALSE(std::isnan(initial_pin_slack));
// Current: and1/ZN --[n1]--> buf1/A, buf1/Z --[n2]--> reg1/D
// Change to: reg1/D connected to n1 (bypass buf1)
sta_->disconnectPin(reg1_d);
Net *n1 = network->findNet(top, "n1");
ASSERT_NE(n1, nullptr);
LibertyCell *dff_cell = network->findLibertyCell("DFF_X1");
ASSERT_NE(dff_cell, nullptr);
LibertyPort *dff_d_port = dff_cell->findLibertyPort("D");
ASSERT_NE(dff_d_port, nullptr);
sta_->connectPin(reg1, dff_d_port, n1);
// After bypassing buf1, the path is shorter so pin slack should improve
reg1_d = network->findPin(reg1, "D");
ASSERT_NE(reg1_d, nullptr);
Slack bypassed_pin_slack = sta_->slack(reg1_d, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_FALSE(std::isnan(bypassed_pin_slack));
// Shorter path should improve slack at this pin
EXPECT_GE(bypassed_pin_slack, initial_pin_slack);
// Restore: reconnect reg1/D to n2
reg1_d = network->findPin(reg1, "D");
ASSERT_NE(reg1_d, nullptr);
sta_->disconnectPin(reg1_d);
Net *n2 = network->findNet(top, "n2");
ASSERT_NE(n2, nullptr);
sta_->connectPin(reg1, dff_d_port, n2);
// After restoring, pin slack should return to original
reg1_d = network->findPin(reg1, "D");
ASSERT_NE(reg1_d, nullptr);
Slack restored_pin_slack = sta_->slack(reg1_d, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_NEAR(restored_pin_slack, initial_pin_slack, 1e-3);
}
////////////////////////////////////////////////////////////////
// Test 18: Annotate net wire capacitance and verify it increases
// delay through the path.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, NetWireCapAnnotation) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Annotate large wire cap on net n1 (and1 output)
Net *n1 = network->findNet(top, "n1");
ASSERT_NE(n1, nullptr);
Sdc *sdc = sta_->cmdSdc();
sta_->setNetWireCap(n1, false,
MinMaxAll::all(), 0.5f, sdc);
Slack after_cap_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(after_cap_slack));
// Large wire cap should slow down and1's output, degrading slack
EXPECT_LT(after_cap_slack, initial_slack);
// Reduce the cap
sta_->setNetWireCap(n1, false,
MinMaxAll::all(), 0.001f, sdc);
Slack small_cap_slack = sta_->worstSlack(MinMax::max());
// Smaller cap should be better than large cap
EXPECT_GT(small_cap_slack, after_cap_slack);
}
////////////////////////////////////////////////////////////////
// Test 19: Set annotated slew on a vertex and verify
// delay calculation uses the annotation.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, AnnotatedSlewAffectsDelay) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Get the graph vertex for and1/ZN (driver pin)
Instance *and1 = network->findChild(top, "and1");
ASSERT_NE(and1, nullptr);
Pin *and1_zn = network->findPin(and1, "ZN");
ASSERT_NE(and1_zn, nullptr);
Graph *graph = sta_->ensureGraph();
ASSERT_NE(graph, nullptr);
Vertex *and1_zn_vertex = graph->pinDrvrVertex(and1_zn);
ASSERT_NE(and1_zn_vertex, nullptr);
// Annotate a very large slew (2.0ns) on the and1 output
sta_->setAnnotatedSlew(and1_zn_vertex, sta_->cmdScene(),
MinMaxAll::all(), RiseFallBoth::riseFall(),
2.0f);
Slack after_slew_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(after_slew_slack));
// Large slew annotation on and1 output should increase downstream
// delay through buf1, degrading timing
EXPECT_LT(after_slew_slack, initial_slack);
// Remove annotations and verify restoration
sta_->removeDelaySlewAnnotations();
// Need full timing update after removing annotations
sta_->updateTiming(true);
Slack restored_slack = sta_->worstSlack(MinMax::max());
EXPECT_NEAR(restored_slack, initial_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 20: Annotate arc delay on an edge and verify it affects timing.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ArcDelayAnnotation) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Find buf1 instance and get its timing arcs
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
Pin *buf1_a = network->findPin(buf1, "A");
Pin *buf1_z = network->findPin(buf1, "Z");
ASSERT_NE(buf1_a, nullptr);
ASSERT_NE(buf1_z, nullptr);
Graph *graph = sta_->ensureGraph();
ASSERT_NE(graph, nullptr);
Vertex *buf1_a_vertex = graph->pinLoadVertex(buf1_a);
ASSERT_NE(buf1_a_vertex, nullptr);
// Find an edge from buf1/A to buf1/Z and annotate a large delay
bool found_edge = false;
VertexOutEdgeIterator edge_iter(buf1_a_vertex, graph);
while (edge_iter.hasNext()) {
Edge *edge = edge_iter.next();
TimingArcSet *arc_set = edge->timingArcSet();
for (TimingArc *arc : arc_set->arcs()) {
// Annotate a large delay (5ns) on this arc
sta_->setArcDelay(edge, arc, sta_->cmdScene(),
MinMaxAll::all(), 5.0f);
found_edge = true;
}
}
ASSERT_TRUE(found_edge);
Slack annotated_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(annotated_slack));
// A 5ns delay annotation on buf1 should significantly worsen slack
EXPECT_LT(annotated_slack, initial_slack);
// Remove annotations and restore
sta_->removeDelaySlewAnnotations();
sta_->updateTiming(true);
Slack restored_slack = sta_->worstSlack(MinMax::max());
EXPECT_NEAR(restored_slack, initial_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 21: Verify that multiple incremental edit-query cycles
// produce results consistent with a final full timing update.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, RapidEditQueryCycles) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
Instance *and1 = network->findChild(top, "and1");
ASSERT_NE(and1, nullptr);
LibertyCell *buf_x1 = network->findLibertyCell("BUF_X1");
LibertyCell *buf_x2 = network->findLibertyCell("BUF_X2");
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
LibertyCell *and2_x2 = network->findLibertyCell("AND2_X2");
ASSERT_NE(buf_x1, nullptr);
ASSERT_NE(buf_x2, nullptr);
ASSERT_NE(buf_x4, nullptr);
ASSERT_NE(and2_x2, nullptr);
// Cycle 1: Edit buf1 -> BUF_X2, query
sta_->replaceCell(buf1, buf_x2);
Slack slack1 = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(slack1));
// Cycle 2: Edit and1 -> AND2_X2, query
sta_->replaceCell(and1, and2_x2);
Slack slack2 = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(slack2));
// Cycle 3: Edit buf1 -> BUF_X4, query
sta_->replaceCell(buf1, buf_x4);
Slack slack3 = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(slack3));
// Now do a full timing update and verify consistency
sta_->updateTiming(true);
Slack full_slack = sta_->worstSlack(MinMax::max());
// The last incremental result should match full timing
EXPECT_NEAR(slack3, full_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 22: Verify TNS (total negative slack) updates
// incrementally after edits.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, TnsUpdatesIncrementally) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_tns = sta_->totalNegativeSlack(MinMax::max());
EXPECT_FALSE(std::isnan(initial_tns));
// TNS is <= 0 by definition (sum of negative slacks)
EXPECT_LE(initial_tns, 0.0f);
// Tighten the clock severely to create violations
Pin *clk_pin = network->findPin(top, "clk");
ASSERT_NE(clk_pin, nullptr);
PinSet *clk_pins = new PinSet(network);
clk_pins->insert(clk_pin);
FloatSeq *tight_waveform = new FloatSeq;
tight_waveform->push_back(0.0f);
tight_waveform->push_back(0.2f); // 0.4ns period (very tight)
sta_->makeClock("clk", clk_pins, false, 0.4f, tight_waveform, "",
sta_->cmdMode());
Slack tight_tns = sta_->totalNegativeSlack(MinMax::max());
// Very tight clock should create large negative TNS
EXPECT_LT(tight_tns, initial_tns);
// Upsize cells to partially improve TNS
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
Slack improved_tns = sta_->totalNegativeSlack(MinMax::max());
// Upsizing should improve (make less negative) TNS
EXPECT_GE(improved_tns, tight_tns);
// Verify incremental TNS matches full timing
sta_->updateTiming(true);
Slack full_tns = sta_->totalNegativeSlack(MinMax::max());
EXPECT_NEAR(improved_tns, full_tns, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 23: Verify arrival time at specific pins after an edit.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ArrivalTimeAtPinAfterEdit) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Get initial arrival at reg1/D
Instance *reg1 = network->findChild(top, "reg1");
ASSERT_NE(reg1, nullptr);
Pin *reg1_d = network->findPin(reg1, "D");
ASSERT_NE(reg1_d, nullptr);
Arrival initial_arrival = sta_->arrival(reg1_d, RiseFallBoth::rise(),
MinMax::max());
EXPECT_FALSE(std::isnan(initial_arrival));
EXPECT_GT(initial_arrival, 0.0f);
// Upsize buf1 to reduce delay to reg1/D
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
Arrival after_arrival = sta_->arrival(reg1_d, RiseFallBoth::rise(),
MinMax::max());
EXPECT_FALSE(std::isnan(after_arrival));
// Faster buffer means earlier arrival at reg1/D
EXPECT_LE(after_arrival, initial_arrival);
// Restore and verify arrival restores
LibertyCell *buf_x1 = network->findLibertyCell("BUF_X1");
ASSERT_NE(buf_x1, nullptr);
sta_->replaceCell(buf1, buf_x1);
Arrival restored_arrival = sta_->arrival(reg1_d, RiseFallBoth::rise(),
MinMax::max());
EXPECT_NEAR(restored_arrival, initial_arrival, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 24: Verify hold (min) slack after cell replacement.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, HoldSlackAfterCellReplacement) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_hold_slack = sta_->worstSlack(MinMax::min());
EXPECT_FALSE(std::isnan(initial_hold_slack));
// Upsize buf1 -- this makes the path faster, which can help hold
// violations (hold requires minimum delay) or worsen them depending
// on the design. Either way, the value should be valid.
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
Slack after_hold_slack = sta_->worstSlack(MinMax::min());
EXPECT_FALSE(std::isnan(after_hold_slack));
// Faster cell should worsen hold timing (data arrives earlier)
EXPECT_LE(after_hold_slack, initial_hold_slack);
// Restore
LibertyCell *buf_x1 = network->findLibertyCell("BUF_X1");
ASSERT_NE(buf_x1, nullptr);
sta_->replaceCell(buf1, buf_x1);
Slack restored_hold_slack = sta_->worstSlack(MinMax::min());
EXPECT_NEAR(restored_hold_slack, initial_hold_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 25: Check both setup and hold after multiple edits.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, SetupAndHoldAfterEdits) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_setup = sta_->worstSlack(MinMax::max());
Slack initial_hold = sta_->worstSlack(MinMax::min());
// Edit 1: Add 0.3ns clock uncertainty for both setup and hold
Clock *clk = sta_->cmdSdc()->findClock("clk");
ASSERT_NE(clk, nullptr);
sta_->setClockUncertainty(clk, SetupHoldAll::all(), 0.3f);
Slack setup_after_unc = sta_->worstSlack(MinMax::max());
Slack hold_after_unc = sta_->worstSlack(MinMax::min());
// Setup uncertainty eats into margin from the top
EXPECT_LT(setup_after_unc, initial_setup);
// Hold uncertainty eats into margin from the bottom
EXPECT_LT(hold_after_unc, initial_hold);
// Edit 2: Upsize buf1 to offset some of the setup degradation
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
Slack setup_after_both = sta_->worstSlack(MinMax::max());
Slack hold_after_both = sta_->worstSlack(MinMax::min());
// Upsizing helps setup (but may hurt hold)
EXPECT_GE(setup_after_both, setup_after_unc);
// Valid results
EXPECT_FALSE(std::isnan(setup_after_both));
EXPECT_FALSE(std::isnan(hold_after_both));
}
////////////////////////////////////////////////////////////////
// Test 26: Verify incremental vs full consistency after multiple
// heterogeneous edits (cell swap + constraint change).
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, IncrementalVsFullAfterMixedEdits) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Edit 1: Replace buf2 with BUF_X4
Instance *buf2 = network->findChild(top, "buf2");
ASSERT_NE(buf2, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf2, buf_x4);
// Edit 2: Add output load
Port *out1_port = network->findPort(network->cell(top), "out1");
ASSERT_NE(out1_port, nullptr);
sta_->setPortExtPinCap(out1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.1f, sta_->cmdSdc());
// Edit 3: Add clock uncertainty
Clock *clk = sta_->cmdSdc()->findClock("clk");
ASSERT_NE(clk, nullptr);
sta_->setClockUncertainty(clk, SetupHoldAll::max(), 0.2f);
// Get incremental result
sta_->updateTiming(false);
Slack inc_setup = sta_->worstSlack(MinMax::max());
Slack inc_tns = sta_->totalNegativeSlack(MinMax::max());
// Get full timing result
sta_->updateTiming(true);
Slack full_setup = sta_->worstSlack(MinMax::max());
Slack full_tns = sta_->totalNegativeSlack(MinMax::max());
EXPECT_NEAR(inc_setup, full_setup, 1e-6);
EXPECT_NEAR(inc_tns, full_tns, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 27: Set input delay to different values and verify
// arrival times update correctly.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, InputDelayChangeUpdatesTiming) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Change input delay on in1 from 0.5ns to 3.0ns
Pin *in1 = network->findPin(top, "in1");
ASSERT_NE(in1, nullptr);
Clock *clk = sta_->cmdSdc()->findClock("clk");
ASSERT_NE(clk, nullptr);
Sdc *sdc = sta_->cmdSdc();
sta_->setInputDelay(in1, RiseFallBoth::riseFall(),
clk, RiseFall::rise(), nullptr,
false, false, MinMaxAll::all(), false, 3.0f, sdc);
Slack large_delay_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(large_delay_slack));
// Larger input delay means data arrives later, worsening setup slack
EXPECT_LT(large_delay_slack, initial_slack);
// Set it very small
sta_->setInputDelay(in1, RiseFallBoth::riseFall(),
clk, RiseFall::rise(), nullptr,
false, false, MinMaxAll::all(), false, 0.01f, sdc);
Slack small_delay_slack = sta_->worstSlack(MinMax::max());
// Smaller input delay should give better slack
EXPECT_GT(small_delay_slack, large_delay_slack);
}
////////////////////////////////////////////////////////////////
// Test 28: Set output delay to different values and verify
// timing updates.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, OutputDelayChangeUpdatesTiming) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// Increase output delay on out1 from 0.5ns to 5.0ns
Pin *out1 = network->findPin(top, "out1");
ASSERT_NE(out1, nullptr);
Clock *clk = sta_->cmdSdc()->findClock("clk");
ASSERT_NE(clk, nullptr);
Sdc *sdc = sta_->cmdSdc();
sta_->setOutputDelay(out1, RiseFallBoth::riseFall(),
clk, RiseFall::rise(), nullptr,
false, false, MinMaxAll::all(), false, 5.0f, sdc);
Slack large_out_delay_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(large_out_delay_slack));
// Larger output delay reduces available path time, worsening slack
EXPECT_LT(large_out_delay_slack, initial_slack);
// Set a very small output delay
sta_->setOutputDelay(out1, RiseFallBoth::riseFall(),
clk, RiseFall::rise(), nullptr,
false, false, MinMaxAll::all(), false, 0.01f, sdc);
Slack small_out_delay_slack = sta_->worstSlack(MinMax::max());
EXPECT_GT(small_out_delay_slack, large_out_delay_slack);
}
////////////////////////////////////////////////////////////////
// Test 29: Set clock latency and verify it shifts arrival/required
// times at register pins.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ClockLatencyAffectsTiming) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
Clock *clk = sta_->cmdSdc()->findClock("clk");
ASSERT_NE(clk, nullptr);
Sdc *sdc = sta_->cmdSdc();
// Add 1ns source latency to clock
sta_->setClockLatency(clk, nullptr, RiseFallBoth::riseFall(),
MinMaxAll::all(), 1.0f, sdc);
Slack latency_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(latency_slack));
// Clock latency applied to both source and capture should not change
// setup slack (it cancels out for same-clock paths). But it does
// shift arrivals.
// For same-clock paths, latency cancels, so slack should be similar.
EXPECT_NEAR(latency_slack, initial_slack, 0.01f);
// Remove latency
sta_->removeClockLatency(clk, nullptr, sdc);
Slack restored_slack = sta_->worstSlack(MinMax::max());
EXPECT_NEAR(restored_slack, initial_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 30: Verify pin slack query at specific pins after edit.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, PinSlackQueryAfterEdit) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Get pin slack at buf1/Z
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
Pin *buf1_z = network->findPin(buf1, "Z");
ASSERT_NE(buf1_z, nullptr);
Slack initial_pin_slack = sta_->slack(buf1_z, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_FALSE(std::isnan(initial_pin_slack));
// Also check worst slack correlation
Slack initial_worst = sta_->worstSlack(MinMax::max());
// Pin slack at buf1/Z should be >= worst slack (worst is the minimum)
EXPECT_GE(initial_pin_slack, initial_worst);
// Upsize buf1
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
Slack after_pin_slack = sta_->slack(buf1_z, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_FALSE(std::isnan(after_pin_slack));
// Upsizing should improve the slack at this pin
EXPECT_GE(after_pin_slack, initial_pin_slack);
}
////////////////////////////////////////////////////////////////
// Test 31: Verify slew at a vertex updates after cell replacement.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, VertexSlewUpdatesAfterReplace) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Get slew at buf1/Z (output of BUF_X1)
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
Pin *buf1_z = network->findPin(buf1, "Z");
ASSERT_NE(buf1_z, nullptr);
Graph *graph = sta_->ensureGraph();
ASSERT_NE(graph, nullptr);
Vertex *buf1_z_vertex = graph->pinDrvrVertex(buf1_z);
ASSERT_NE(buf1_z_vertex, nullptr);
Slew initial_slew = sta_->slew(buf1_z_vertex, RiseFallBoth::rise(),
sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_FALSE(std::isnan(initial_slew));
EXPECT_GT(initial_slew, 0.0f);
// Replace buf1 with BUF_X4 (stronger driver = faster slew)
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
// Need to refetch the vertex since the graph may be rebuilt
graph = sta_->ensureGraph();
buf1_z = network->findPin(buf1, "Z");
buf1_z_vertex = graph->pinDrvrVertex(buf1_z);
ASSERT_NE(buf1_z_vertex, nullptr);
Slew after_slew = sta_->slew(buf1_z_vertex, RiseFallBoth::rise(),
sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_FALSE(std::isnan(after_slew));
// Stronger driver (BUF_X4) should produce faster (smaller) slew
EXPECT_LE(after_slew, initial_slew);
}
////////////////////////////////////////////////////////////////
// Test 32: Replace buf2 (output path) and verify output path timing.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, OutputPathCellReplacement) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
// buf2 is on the output path: reg1/Q -> buf2 -> out1
Instance *buf2 = network->findChild(top, "buf2");
ASSERT_NE(buf2, nullptr);
// Get pin slack at out1 before edit
Pin *out1_pin = network->findPin(top, "out1");
ASSERT_NE(out1_pin, nullptr);
Slack out1_slack_before = sta_->slack(out1_pin, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
// Replace buf2 with BUF_X4
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf2, buf_x4);
Slack out1_slack_after = sta_->slack(out1_pin, RiseFallBoth::riseFall(), sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
EXPECT_FALSE(std::isnan(out1_slack_after));
// BUF_X4 is faster, out1 slack should improve
EXPECT_GE(out1_slack_after, out1_slack_before);
// Also check worst slack
Slack after_worst = sta_->worstSlack(MinMax::max());
EXPECT_GE(after_worst, initial_slack);
}
////////////////////////////////////////////////////////////////
// Test 33: Endpoint violation count changes with clock period.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, EndpointViolationCountChanges) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// With 10ns clock, there should be no or few violations
int initial_violations = sta_->endpointViolationCount(MinMax::max());
EXPECT_GE(initial_violations, 0);
// Tighten the clock to create violations
Pin *clk_pin = network->findPin(top, "clk");
ASSERT_NE(clk_pin, nullptr);
PinSet *clk_pins = new PinSet(network);
clk_pins->insert(clk_pin);
FloatSeq *tight_waveform = new FloatSeq;
tight_waveform->push_back(0.0f);
tight_waveform->push_back(0.1f); // 0.2ns period
sta_->makeClock("clk", clk_pins, false, 0.2f, tight_waveform, "",
sta_->cmdMode());
int tight_violations = sta_->endpointViolationCount(MinMax::max());
// Very tight clock should cause violations
EXPECT_GT(tight_violations, initial_violations);
// Loosen the clock
PinSet *clk_pins2 = new PinSet(network);
clk_pins2->insert(clk_pin);
FloatSeq *loose_waveform = new FloatSeq;
loose_waveform->push_back(0.0f);
loose_waveform->push_back(50.0f);
sta_->makeClock("clk", clk_pins2, false, 100.0f, loose_waveform, "",
sta_->cmdMode());
int loose_violations = sta_->endpointViolationCount(MinMax::max());
// Loose clock should have fewer violations
EXPECT_LT(loose_violations, tight_violations);
}
////////////////////////////////////////////////////////////////
// Test 34: Net slack query updates incrementally.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, NetSlackUpdatesIncrementally) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Get net slack on n2 (buf1/Z -> reg1/D)
Net *n2 = network->findNet(top, "n2");
ASSERT_NE(n2, nullptr);
Slack initial_net_slack = sta_->slack(n2, MinMax::max());
EXPECT_FALSE(std::isnan(initial_net_slack));
// Upsize buf1 to improve the path through n2
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
sta_->replaceCell(buf1, buf_x4);
Slack after_net_slack = sta_->slack(n2, MinMax::max());
EXPECT_FALSE(std::isnan(after_net_slack));
// Net slack on n2 should improve after upsizing buf1
EXPECT_GE(after_net_slack, initial_net_slack);
}
////////////////////////////////////////////////////////////////
// Test 35: Clock latency insertion delay affects timing check.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, ClockInsertionDelayAffectsTiming) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
// Clock insertion for same-clock paths cancels in slack calculation.
// Verify that arrival/required times shift by the insertion amount
// even though slack remains the same.
Instance *reg1 = network->findChild(top, "reg1");
ASSERT_NE(reg1, nullptr);
Pin *reg1_d = network->findPin(reg1, "D");
ASSERT_NE(reg1_d, nullptr);
Graph *graph = sta_->ensureGraph();
ASSERT_NE(graph, nullptr);
Vertex *reg1_d_vertex = graph->pinLoadVertex(reg1_d);
ASSERT_NE(reg1_d_vertex, nullptr);
Arrival initial_arrival = sta_->arrival(reg1_d, RiseFallBoth::rise(),
MinMax::max());
Required initial_required = sta_->required(reg1_d_vertex, RiseFallBoth::riseFall(),
sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
Slack initial_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(initial_arrival));
EXPECT_FALSE(std::isnan(initial_required));
Clock *clk = sta_->cmdSdc()->findClock("clk");
ASSERT_NE(clk, nullptr);
Sdc *sdc = sta_->cmdSdc();
// Add 1ns source insertion delay to the clock
sta_->setClockInsertion(clk, nullptr, RiseFallBoth::riseFall(),
MinMaxAll::all(), EarlyLateAll::all(), 1.0f, sdc);
Arrival after_arrival = sta_->arrival(reg1_d, RiseFallBoth::rise(),
MinMax::max());
Required after_required = sta_->required(reg1_d_vertex, RiseFallBoth::riseFall(),
sta_->makeSceneSeq(sta_->cmdScene()), MinMax::max());
Slack after_slack = sta_->worstSlack(MinMax::max());
// For same-clock paths, insertion shifts both arrival and required
// by the same amount, so slack should stay the same
EXPECT_NEAR(after_slack, initial_slack, 0.01f);
// But arrival should shift by the insertion delay (1ns)
// The arrival includes the clock insertion on the launch side
EXPECT_NEAR(after_arrival - initial_arrival, 1.0f, 0.01f);
// And required should also shift (capture side)
EXPECT_NEAR(after_required - initial_required, 1.0f, 0.01f);
// Remove insertion delay
sta_->removeClockInsertion(clk, nullptr, sdc);
Slack restored_slack = sta_->worstSlack(MinMax::max());
EXPECT_NEAR(restored_slack, initial_slack, 1e-6);
}
////////////////////////////////////////////////////////////////
// Test 36: Verify timing with drive resistance set on input port.
////////////////////////////////////////////////////////////////
TEST_F(IncrementalTimingTest, DriveResistanceAffectsTiming) {
Network *network = sta_->cmdNetwork();
Instance *top = network->topInstance();
Slack initial_slack = sta_->worstSlack(MinMax::max());
Sdc *sdc = sta_->cmdSdc();
// Set a large drive resistance on in1 (slow driver)
Port *in1_port = network->findPort(network->cell(top), "in1");
ASSERT_NE(in1_port, nullptr);
sta_->setDriveResistance(in1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 1000.0f, sdc);
Slack after_slack = sta_->worstSlack(MinMax::max());
EXPECT_FALSE(std::isnan(after_slack));
// High drive resistance means slow input transition, degrading timing
EXPECT_LE(after_slack, initial_slack);
// Set a very low drive resistance (fast driver)
sta_->setDriveResistance(in1_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.001f, sdc);
Slack fast_slack = sta_->worstSlack(MinMax::max());
// Fast driver should give better timing
EXPECT_GE(fast_slack, after_slack);
}
} // namespace sta