#include #include #include #include "MinMax.hh" #include "Transition.hh" #include "Corner.hh" #include "Sta.hh" #include "Sdc.hh" #include "ReportTcl.hh" #include "Network.hh" #include "Liberty.hh" #include "Graph.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(sta_->report()); if (report) report->setTclInterp(interp_); Corner *corner = sta_->cmdCorner(); 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); sta_->makeClock("clk", clk_pins, false, 10.0f, waveform, nullptr); Clock *clk = sta_->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); sta_->setInputDelay(in2, RiseFallBoth::riseFall(), clk, RiseFall::rise(), nullptr, false, false, MinMaxAll::all(), false, 0.5f); // 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); // 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); sta_->setPortExtPinCap(out1_port, RiseFallBoth::riseFall(), sta_->cmdCorner(), MinMaxAll::all(), 0.05f); // 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); // 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); sta_->setPortExtPinCap(out1_port, RiseFallBoth::riseFall(), sta_->cmdCorner(), MinMaxAll::all(), 0.5f); 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(), sta_->cmdCorner(), MinMaxAll::all(), 0.001f); 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, nullptr); 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, nullptr); Slack loose_slack = sta_->worstSlack(MinMax::max()); // Looser clock should give better slack than tight clock EXPECT_GT(loose_slack, tight_slack); } } // namespace sta