mirror of
https://github.com/The-OpenROAD-Project/OpenSTA.git
synced 2026-09-03 08:25:52 +02:00
test: Apply review feedback - part1
Signed-off-by: Jaehyun Kim <[email protected]>
This commit is contained in:
@@ -4138,4 +4138,750 @@ TEST_F(DesignDcalcTest, LevelChangedBefore) {
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// NangateDcalcTest - Loads Nangate45 + dcalc_test1.v (BUF->INV->DFF chain)
|
||||
|
||||
class NangateDcalcTest : 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_);
|
||||
registerDelayCalcs();
|
||||
|
||||
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("dcalc/test/dcalc_test1.v");
|
||||
ASSERT_TRUE(ok);
|
||||
ok = sta_->linkDesign("dcalc_test1", true);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
// Create clock and set constraints
|
||||
Network *network = sta_->network();
|
||||
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);
|
||||
|
||||
// Set input/output delay constraints to create constrained timing paths
|
||||
Clock *clk = sta_->sdc()->findClock("clk");
|
||||
ASSERT_NE(clk, nullptr);
|
||||
|
||||
Pin *in1_pin = network->findPin(top, "in1");
|
||||
ASSERT_NE(in1_pin, nullptr);
|
||||
sta_->setInputDelay(in1_pin, RiseFallBoth::riseFall(), clk,
|
||||
RiseFall::rise(), nullptr, false, false,
|
||||
MinMaxAll::all(), false, 0.0f);
|
||||
|
||||
Pin *out1_pin = network->findPin(top, "out1");
|
||||
ASSERT_NE(out1_pin, nullptr);
|
||||
sta_->setOutputDelay(out1_pin, RiseFallBoth::riseFall(), clk,
|
||||
RiseFall::rise(), nullptr, false, false,
|
||||
MinMaxAll::all(), false, 0.0f);
|
||||
|
||||
design_loaded_ = true;
|
||||
}
|
||||
void TearDown() override {
|
||||
deleteDelayCalcs();
|
||||
deleteAllMemory();
|
||||
sta_ = nullptr;
|
||||
if (interp_) Tcl_DeleteInterp(interp_);
|
||||
interp_ = nullptr;
|
||||
}
|
||||
Sta *sta_;
|
||||
Tcl_Interp *interp_;
|
||||
bool design_loaded_ = false;
|
||||
};
|
||||
|
||||
// Run updateTiming with each calculator, verify all complete without crash
|
||||
// and graph has delays.
|
||||
TEST_F(NangateDcalcTest, TimingAllCalcsNangate) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
const char *calcs[] = {"unit", "lumped_cap", "dmp_ceff_elmore",
|
||||
"dmp_ceff_two_pole", "ccs_ceff"};
|
||||
for (const char *name : calcs) {
|
||||
sta_->setArcDelayCalc(name);
|
||||
sta_->updateTiming(true);
|
||||
Graph *graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Set various loads on output, run dmp_ceff_elmore for each, verify slack changes.
|
||||
TEST_F(NangateDcalcTest, DmpExtremeLoads) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Cell *top_cell = network->cell(top);
|
||||
const Port *out_port = network->findPort(top_cell, "out1");
|
||||
ASSERT_NE(out_port, nullptr);
|
||||
|
||||
Corner *corner = sta_->cmdCorner();
|
||||
float loads[] = {0.00001f, 0.1f, 1.0f, 5.0f, 10.0f};
|
||||
Slack prev_slack = 0.0f;
|
||||
bool first = true;
|
||||
for (float load : loads) {
|
||||
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
|
||||
corner, MinMaxAll::all(), load);
|
||||
sta_->updateTiming(true);
|
||||
Slack slack = sta_->worstSlack(MinMax::max());
|
||||
if (!first) {
|
||||
// With increasing load, slack should generally decrease (become worse)
|
||||
// but we just verify it's a valid number and changes
|
||||
EXPECT_TRUE(slack != prev_slack || load == loads[0]);
|
||||
}
|
||||
prev_slack = slack;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set various input transitions via setInputSlew, verify timing completes.
|
||||
TEST_F(NangateDcalcTest, DmpExtremeSlews) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Cell *top_cell = network->cell(top);
|
||||
const Port *in_port = network->findPort(top_cell, "in1");
|
||||
ASSERT_NE(in_port, nullptr);
|
||||
|
||||
float slews[] = {0.0001f, 0.1f, 5.0f, 10.0f};
|
||||
for (float slew : slews) {
|
||||
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
|
||||
MinMaxAll::all(), slew);
|
||||
sta_->updateTiming(true);
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
// Large load + fast slew, tiny load + slow slew combinations.
|
||||
TEST_F(NangateDcalcTest, DmpCombinedExtremes) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Cell *top_cell = network->cell(top);
|
||||
const Port *out_port = network->findPort(top_cell, "out1");
|
||||
const Port *in_port = network->findPort(top_cell, "in1");
|
||||
ASSERT_NE(out_port, nullptr);
|
||||
ASSERT_NE(in_port, nullptr);
|
||||
|
||||
Corner *corner = sta_->cmdCorner();
|
||||
|
||||
// Large load + fast slew
|
||||
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
|
||||
corner, MinMaxAll::all(), 10.0f);
|
||||
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
|
||||
MinMaxAll::all(), 0.0001f);
|
||||
sta_->updateTiming(true);
|
||||
Slack slack1 = sta_->worstSlack(MinMax::max());
|
||||
|
||||
// Tiny load + slow slew
|
||||
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
|
||||
corner, MinMaxAll::all(), 0.00001f);
|
||||
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
|
||||
MinMaxAll::all(), 10.0f);
|
||||
sta_->updateTiming(true);
|
||||
Slack slack2 = sta_->worstSlack(MinMax::max());
|
||||
|
||||
// Just verify both complete and produce different slacks
|
||||
EXPECT_NE(slack1, slack2);
|
||||
}
|
||||
|
||||
// Same as DmpExtremeLoads but with dmp_ceff_two_pole.
|
||||
TEST_F(NangateDcalcTest, TwoPoleExtremeLoads) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_two_pole");
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Cell *top_cell = network->cell(top);
|
||||
const Port *out_port = network->findPort(top_cell, "out1");
|
||||
ASSERT_NE(out_port, nullptr);
|
||||
|
||||
Corner *corner = sta_->cmdCorner();
|
||||
float loads[] = {0.00001f, 0.1f, 1.0f, 5.0f, 10.0f};
|
||||
for (float load : loads) {
|
||||
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
|
||||
corner, MinMaxAll::all(), load);
|
||||
sta_->updateTiming(true);
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
// Switch calculator from dmp_ceff_elmore->lumped_cap->unit->dmp_ceff_two_pole,
|
||||
// verify timing works at each switch.
|
||||
TEST_F(NangateDcalcTest, CalcSwitchingIncremental) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
const char *calcs[] = {"dmp_ceff_elmore", "lumped_cap", "unit",
|
||||
"dmp_ceff_two_pole"};
|
||||
for (const char *name : calcs) {
|
||||
sta_->setArcDelayCalc(name);
|
||||
sta_->updateTiming(true);
|
||||
Graph *graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Set ccs_ceff (falls back to table-based for NLDM), verify timing works.
|
||||
TEST_F(NangateDcalcTest, CcsWithNldmFallback) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("ccs_ceff");
|
||||
sta_->updateTiming(true);
|
||||
Graph *graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 0);
|
||||
|
||||
Slack slack = sta_->worstSlack(MinMax::max());
|
||||
// CCS with NLDM fallback should still produce valid timing
|
||||
(void)slack;
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
// Set ccs_ceff, change load, verify incremental timing.
|
||||
TEST_F(NangateDcalcTest, CcsIncrementalLoadChange) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("ccs_ceff");
|
||||
sta_->updateTiming(true);
|
||||
Slack slack1 = sta_->worstSlack(MinMax::max());
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Cell *top_cell = network->cell(top);
|
||||
const Port *out_port = network->findPort(top_cell, "out1");
|
||||
ASSERT_NE(out_port, nullptr);
|
||||
|
||||
Corner *corner = sta_->cmdCorner();
|
||||
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
|
||||
corner, MinMaxAll::all(), 5.0f);
|
||||
sta_->updateTiming(false);
|
||||
Slack slack2 = sta_->worstSlack(MinMax::max());
|
||||
|
||||
// With large load, slack should change
|
||||
EXPECT_NE(slack1, slack2);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// MultiDriverDcalcTest - Loads Nangate45 + dcalc_multidriver_test.v
|
||||
|
||||
class MultiDriverDcalcTest : 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_);
|
||||
registerDelayCalcs();
|
||||
|
||||
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("dcalc/test/dcalc_multidriver_test.v");
|
||||
ASSERT_TRUE(ok);
|
||||
ok = sta_->linkDesign("dcalc_multidriver_test", true);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
// Create clock
|
||||
Network *network = sta_->network();
|
||||
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 delays on in1-in4, sel
|
||||
Cell *top_cell = network->cell(top);
|
||||
const char *input_ports[] = {"in1", "in2", "in3", "in4", "sel"};
|
||||
for (const char *pname : input_ports) {
|
||||
const Port *port = network->findPort(top_cell, pname);
|
||||
if (port) {
|
||||
sta_->setInputSlew(port, RiseFallBoth::riseFall(),
|
||||
MinMaxAll::all(), 0.1f);
|
||||
// Also set SDC input delay to constrain the path
|
||||
Pin *pin = network->findPin(top, pname);
|
||||
if (pin) {
|
||||
sta_->setInputDelay(pin, RiseFallBoth::riseFall(), clk,
|
||||
RiseFall::rise(), nullptr, false, false,
|
||||
MinMaxAll::all(), false, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set output loads and output delays on out1-out3
|
||||
const char *output_ports[] = {"out1", "out2", "out3"};
|
||||
for (const char *pname : output_ports) {
|
||||
const Port *port = network->findPort(top_cell, pname);
|
||||
if (port) {
|
||||
sta_->setPortExtPinCap(port, RiseFallBoth::riseFall(),
|
||||
corner, MinMaxAll::all(), 0.01f);
|
||||
Pin *pin = network->findPin(top, pname);
|
||||
if (pin) {
|
||||
sta_->setOutputDelay(pin, RiseFallBoth::riseFall(), clk,
|
||||
RiseFall::rise(), nullptr, false, false,
|
||||
MinMaxAll::all(), false, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
design_loaded_ = true;
|
||||
}
|
||||
void TearDown() override {
|
||||
deleteDelayCalcs();
|
||||
deleteAllMemory();
|
||||
sta_ = nullptr;
|
||||
if (interp_) Tcl_DeleteInterp(interp_);
|
||||
interp_ = nullptr;
|
||||
}
|
||||
Sta *sta_;
|
||||
Tcl_Interp *interp_;
|
||||
bool design_loaded_ = false;
|
||||
};
|
||||
|
||||
// updateTiming, query paths from each input to each output, verify graph has paths.
|
||||
TEST_F(MultiDriverDcalcTest, AllPathQueries) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
sta_->updateTiming(true);
|
||||
|
||||
Graph *graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 10);
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
// Verify output pins have vertices
|
||||
const char *out_names[] = {"out1", "out2", "out3"};
|
||||
for (const char *name : out_names) {
|
||||
Pin *pin = network->findPin(top, name);
|
||||
ASSERT_NE(pin, nullptr);
|
||||
Vertex *v = graph->pinDrvrVertex(pin);
|
||||
EXPECT_NE(v, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Sweep loads 0.001->0.1 on out1, verify delays change monotonically.
|
||||
TEST_F(MultiDriverDcalcTest, DmpCeffLoadSweep) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Cell *top_cell = network->cell(top);
|
||||
const Port *out_port = network->findPort(top_cell, "out1");
|
||||
ASSERT_NE(out_port, nullptr);
|
||||
|
||||
Corner *corner = sta_->cmdCorner();
|
||||
float loads[] = {0.001f, 0.005f, 0.01f, 0.05f, 0.1f};
|
||||
Slack prev_slack = 1e30f; // Start with large positive value
|
||||
for (float load : loads) {
|
||||
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
|
||||
corner, MinMaxAll::all(), load);
|
||||
sta_->updateTiming(true);
|
||||
Slack slack = sta_->worstSlack(MinMax::max());
|
||||
// With increasing load, slack should decrease (more negative = worse)
|
||||
EXPECT_LE(slack, prev_slack + 1e-6f);
|
||||
prev_slack = slack;
|
||||
}
|
||||
}
|
||||
|
||||
// Set large tolerance (0.5), change slew, verify timing completes.
|
||||
TEST_F(MultiDriverDcalcTest, IncrementalToleranceLarge) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
sta_->setIncrementalDelayTolerance(0.5f);
|
||||
sta_->updateTiming(true);
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Cell *top_cell = network->cell(top);
|
||||
const Port *in_port = network->findPort(top_cell, "in1");
|
||||
ASSERT_NE(in_port, nullptr);
|
||||
|
||||
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
|
||||
MinMaxAll::all(), 0.5f);
|
||||
sta_->updateTiming(false);
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
// Set small tolerance (0.001), change slew, verify timing recomputes.
|
||||
TEST_F(MultiDriverDcalcTest, IncrementalToleranceSmall) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
sta_->setIncrementalDelayTolerance(0.001f);
|
||||
sta_->updateTiming(true);
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Cell *top_cell = network->cell(top);
|
||||
const Port *in_port = network->findPort(top_cell, "in1");
|
||||
ASSERT_NE(in_port, nullptr);
|
||||
|
||||
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
|
||||
MinMaxAll::all(), 0.5f);
|
||||
sta_->updateTiming(false);
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
// Set loads on multiple outputs, verify incremental update works.
|
||||
TEST_F(MultiDriverDcalcTest, IncrementalLoadChanges) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
sta_->updateTiming(true);
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Cell *top_cell = network->cell(top);
|
||||
Corner *corner = sta_->cmdCorner();
|
||||
|
||||
const char *output_ports[] = {"out1", "out2", "out3"};
|
||||
for (const char *pname : output_ports) {
|
||||
const Port *port = network->findPort(top_cell, pname);
|
||||
ASSERT_NE(port, nullptr);
|
||||
sta_->setPortExtPinCap(port, RiseFallBoth::riseFall(),
|
||||
corner, MinMaxAll::all(), 1.0f);
|
||||
}
|
||||
sta_->updateTiming(false);
|
||||
|
||||
Slack slack = sta_->worstSlack(MinMax::max());
|
||||
(void)slack;
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
// Change clock period, verify timing updates.
|
||||
TEST_F(MultiDriverDcalcTest, IncrementalClockPeriodChange) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
sta_->updateTiming(true);
|
||||
Slack slack1 = sta_->worstSlack(MinMax::max());
|
||||
|
||||
// Create new clock with different period
|
||||
Network *network = sta_->network();
|
||||
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(1.0f);
|
||||
sta_->makeClock("clk", clk_pins, false, 2.0f, waveform, nullptr);
|
||||
sta_->updateTiming(true);
|
||||
Slack slack2 = sta_->worstSlack(MinMax::max());
|
||||
|
||||
// Tighter clock => smaller (worse) slack
|
||||
EXPECT_NE(slack1, slack2);
|
||||
}
|
||||
|
||||
// Replace buf1 with BUF_X4, verify timing completes, replace back.
|
||||
TEST_F(MultiDriverDcalcTest, ReplaceCellIncremental) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
sta_->updateTiming(true);
|
||||
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Instance *buf1 = network->findChild(top, "buf1");
|
||||
ASSERT_NE(buf1, nullptr);
|
||||
|
||||
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
|
||||
ASSERT_NE(buf_x4, nullptr);
|
||||
|
||||
LibertyCell *buf_x1 = network->findLibertyCell("BUF_X1");
|
||||
ASSERT_NE(buf_x1, nullptr);
|
||||
|
||||
// Check vertex delay on buf1 output before replacement
|
||||
Graph *graph = sta_->graph();
|
||||
Pin *buf1_z = network->findPin(buf1, "Z");
|
||||
ASSERT_NE(buf1_z, nullptr);
|
||||
Vertex *v1 = graph->pinDrvrVertex(buf1_z);
|
||||
ASSERT_NE(v1, nullptr);
|
||||
|
||||
sta_->replaceCell(buf1, buf_x4);
|
||||
sta_->updateTiming(true);
|
||||
|
||||
// Verify timing completes after replacement
|
||||
graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 0);
|
||||
|
||||
// Replace back to original
|
||||
sta_->replaceCell(buf1, buf_x1);
|
||||
sta_->updateTiming(true);
|
||||
|
||||
graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 0);
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
// Switch through all 5 calculators, verify timing at each.
|
||||
TEST_F(MultiDriverDcalcTest, CalcSwitchAllEngines) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
const char *calcs[] = {"unit", "lumped_cap", "dmp_ceff_elmore",
|
||||
"dmp_ceff_two_pole", "ccs_ceff"};
|
||||
for (const char *name : calcs) {
|
||||
sta_->setArcDelayCalc(name);
|
||||
sta_->updateTiming(true);
|
||||
Graph *graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Call findDelays() directly, invalidate, call again.
|
||||
TEST_F(MultiDriverDcalcTest, FindDelaysExplicit) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
sta_->findDelays();
|
||||
Graph *graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 0);
|
||||
|
||||
// Change something and call findDelays again
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Cell *top_cell = network->cell(top);
|
||||
const Port *in_port = network->findPort(top_cell, "in1");
|
||||
ASSERT_NE(in_port, nullptr);
|
||||
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
|
||||
MinMaxAll::all(), 1.0f);
|
||||
sta_->findDelays();
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// MultiCornerDcalcTest - Loads Nangate45 fast/slow + dcalc_test1.v
|
||||
|
||||
class MultiCornerDcalcTest : 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_);
|
||||
registerDelayCalcs();
|
||||
|
||||
// Define corners
|
||||
StringSet corner_names;
|
||||
corner_names.insert("fast");
|
||||
corner_names.insert("slow");
|
||||
sta_->makeCorners(&corner_names);
|
||||
|
||||
Corner *fast_corner = sta_->findCorner("fast");
|
||||
Corner *slow_corner = sta_->findCorner("slow");
|
||||
ASSERT_NE(fast_corner, nullptr);
|
||||
ASSERT_NE(slow_corner, nullptr);
|
||||
|
||||
const MinMaxAll *min_max = MinMaxAll::all();
|
||||
|
||||
LibertyLibrary *fast_lib = sta_->readLiberty(
|
||||
"test/nangate45/Nangate45_fast.lib", fast_corner, min_max, false);
|
||||
ASSERT_NE(fast_lib, nullptr);
|
||||
|
||||
LibertyLibrary *slow_lib = sta_->readLiberty(
|
||||
"test/nangate45/Nangate45_slow.lib", slow_corner, min_max, false);
|
||||
ASSERT_NE(slow_lib, nullptr);
|
||||
|
||||
bool ok = sta_->readVerilog("dcalc/test/dcalc_test1.v");
|
||||
ASSERT_TRUE(ok);
|
||||
ok = sta_->linkDesign("dcalc_test1", true);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
// Create clock
|
||||
Network *network = sta_->network();
|
||||
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);
|
||||
|
||||
// Set input/output delay constraints to create constrained timing paths
|
||||
Clock *clk = sta_->sdc()->findClock("clk");
|
||||
ASSERT_NE(clk, nullptr);
|
||||
|
||||
Pin *in1_pin = network->findPin(top, "in1");
|
||||
ASSERT_NE(in1_pin, nullptr);
|
||||
sta_->setInputDelay(in1_pin, RiseFallBoth::riseFall(), clk,
|
||||
RiseFall::rise(), nullptr, false, false,
|
||||
MinMaxAll::all(), false, 0.0f);
|
||||
|
||||
Pin *out1_pin = network->findPin(top, "out1");
|
||||
ASSERT_NE(out1_pin, nullptr);
|
||||
sta_->setOutputDelay(out1_pin, RiseFallBoth::riseFall(), clk,
|
||||
RiseFall::rise(), nullptr, false, false,
|
||||
MinMaxAll::all(), false, 0.0f);
|
||||
|
||||
design_loaded_ = true;
|
||||
}
|
||||
void TearDown() override {
|
||||
deleteDelayCalcs();
|
||||
deleteAllMemory();
|
||||
sta_ = nullptr;
|
||||
if (interp_) Tcl_DeleteInterp(interp_);
|
||||
interp_ = nullptr;
|
||||
}
|
||||
Sta *sta_;
|
||||
Tcl_Interp *interp_;
|
||||
bool design_loaded_ = false;
|
||||
};
|
||||
|
||||
// Verify timing with both corners produces valid results and
|
||||
// that the slow corner does not have better slack than the fast corner.
|
||||
TEST_F(MultiCornerDcalcTest, TimingDiffersPerCorner) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
sta_->updateTiming(true);
|
||||
|
||||
Corner *fast_corner = sta_->findCorner("fast");
|
||||
Corner *slow_corner = sta_->findCorner("slow");
|
||||
ASSERT_NE(fast_corner, nullptr);
|
||||
ASSERT_NE(slow_corner, nullptr);
|
||||
|
||||
Slack fast_slack, slow_slack;
|
||||
Vertex *fast_vertex, *slow_vertex;
|
||||
sta_->worstSlack(fast_corner, MinMax::max(), fast_slack, fast_vertex);
|
||||
sta_->worstSlack(slow_corner, MinMax::max(), slow_slack, slow_vertex);
|
||||
|
||||
// Both corners should produce valid slack (not infinity)
|
||||
EXPECT_LT(fast_slack, 1e29f);
|
||||
EXPECT_LT(slow_slack, 1e29f);
|
||||
|
||||
// Fast corner should have slack >= slow corner (better or equal)
|
||||
EXPECT_GE(fast_slack, slow_slack);
|
||||
}
|
||||
|
||||
// Run each calculator with multi-corner, verify completes.
|
||||
TEST_F(MultiCornerDcalcTest, AllCalcsMultiCorner) {
|
||||
EXPECT_TRUE(design_loaded_);
|
||||
const char *calcs[] = {"unit", "lumped_cap", "dmp_ceff_elmore",
|
||||
"dmp_ceff_two_pole", "ccs_ceff"};
|
||||
for (const char *name : calcs) {
|
||||
sta_->setArcDelayCalc(name);
|
||||
sta_->updateTiming(true);
|
||||
Graph *graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Additional DesignDcalcTest tests for SPEF-based scenarios
|
||||
|
||||
// Run all delay calculators with SPEF loaded.
|
||||
TEST_F(DesignDcalcTest, TimingAllCalcsWithSpef) {
|
||||
ASSERT_TRUE(design_loaded_);
|
||||
const char *calcs[] = {"unit", "lumped_cap", "dmp_ceff_elmore",
|
||||
"dmp_ceff_two_pole", "arnoldi", "ccs_ceff", "prima"};
|
||||
for (const char *name : calcs) {
|
||||
sta_->setArcDelayCalc(name);
|
||||
sta_->updateTiming(true);
|
||||
Graph *graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Set prima reduce order 1,2,3,4,5, verify each completes.
|
||||
TEST_F(DesignDcalcTest, PrimaReduceOrderVariation) {
|
||||
ASSERT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("prima");
|
||||
|
||||
ArcDelayCalc *calc = sta_->arcDelayCalc();
|
||||
ASSERT_NE(calc, nullptr);
|
||||
PrimaDelayCalc *prima = dynamic_cast<PrimaDelayCalc*>(calc);
|
||||
ASSERT_NE(prima, nullptr);
|
||||
|
||||
size_t orders[] = {1, 2, 3, 4, 5};
|
||||
for (size_t order : orders) {
|
||||
prima->setPrimaReduceOrder(order);
|
||||
sta_->updateTiming(true);
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
// Change load, slew, clock period with SPEF, verify updates.
|
||||
TEST_F(DesignDcalcTest, IncrementalWithSpef) {
|
||||
ASSERT_TRUE(design_loaded_);
|
||||
sta_->setArcDelayCalc("dmp_ceff_elmore");
|
||||
sta_->updateTiming(true);
|
||||
Slack slack1 = sta_->worstSlack(MinMax::max());
|
||||
|
||||
// Change clock period
|
||||
Network *network = sta_->network();
|
||||
Instance *top = network->topInstance();
|
||||
Pin *clk1 = network->findPin(top, "clk1");
|
||||
Pin *clk2 = network->findPin(top, "clk2");
|
||||
Pin *clk3 = network->findPin(top, "clk3");
|
||||
PinSet *clk_pins = new PinSet(network);
|
||||
clk_pins->insert(clk1);
|
||||
clk_pins->insert(clk2);
|
||||
clk_pins->insert(clk3);
|
||||
FloatSeq *waveform = new FloatSeq;
|
||||
waveform->push_back(0.0f);
|
||||
waveform->push_back(50.0f);
|
||||
sta_->makeClock("clk", clk_pins, false, 100.0f, waveform, nullptr);
|
||||
sta_->updateTiming(true);
|
||||
Slack slack2 = sta_->worstSlack(MinMax::max());
|
||||
|
||||
// Tighter clock => different slack
|
||||
EXPECT_NE(slack1, slack2);
|
||||
}
|
||||
|
||||
// Rapidly switch between all calcs with SPEF loaded.
|
||||
TEST_F(DesignDcalcTest, RapidCalcSwitchingSpef) {
|
||||
ASSERT_TRUE(design_loaded_);
|
||||
const char *calcs[] = {"dmp_ceff_elmore", "lumped_cap", "unit",
|
||||
"dmp_ceff_two_pole", "arnoldi", "ccs_ceff",
|
||||
"prima", "dmp_ceff_elmore", "ccs_ceff"};
|
||||
for (const char *name : calcs) {
|
||||
sta_->setArcDelayCalc(name);
|
||||
sta_->updateTiming(true);
|
||||
Graph *graph = sta_->graph();
|
||||
ASSERT_NE(graph, nullptr);
|
||||
EXPECT_GT(graph->vertexCount(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sta
|
||||
|
||||
@@ -7,6 +7,10 @@ namespace sta {
|
||||
|
||||
class FindRootTest : public ::testing::Test {};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Original 7 tests
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Test finding root of f(x) = x^2 - 4 (root at x=2)
|
||||
TEST_F(FindRootTest, QuadraticPositiveRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
@@ -92,4 +96,572 @@ TEST_F(FindRootTest, WithPrecomputedY) {
|
||||
EXPECT_NEAR(root, 3.0, 1e-8);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Tolerance edge cases
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Very tight tolerance: 1e-15 (near machine epsilon)
|
||||
TEST_F(FindRootTest, VeryTightTolerance) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x - 5.0;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 3.0, 7.0, 1e-15, 500, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 5.0, 1e-13);
|
||||
}
|
||||
|
||||
// Very loose tolerance: 1e-1
|
||||
TEST_F(FindRootTest, LooseTolerance) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x - 25.0;
|
||||
dy = 2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 3.0, 7.0, 1e-1, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
// With 10% relative tolerance, result should still be in the right ballpark
|
||||
EXPECT_NEAR(root, 5.0, 0.6);
|
||||
}
|
||||
|
||||
// Zero tolerance: convergence check becomes abs(dx) <= 0, which is only
|
||||
// satisfied when dx is exactly 0. Likely hits max_iter and fails.
|
||||
TEST_F(FindRootTest, ZeroTolerance) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x - 3.0;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.0, 5.0, 0.0, 100, fail);
|
||||
// May or may not converge -- for a linear function Newton converges in 1 step
|
||||
// so dx can be exactly 0. Accept either outcome.
|
||||
if (!fail) {
|
||||
EXPECT_NEAR(root, 3.0, 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Iteration limit edge cases
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Only 1 iteration allowed
|
||||
TEST_F(FindRootTest, OneIteration) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x - 4.0;
|
||||
dy = 2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.0, 3.0, 1e-10, 1, fail);
|
||||
// With only 1 iteration, a quadratic likely won't converge to tight tol
|
||||
// The algorithm may or may not fail depending on initial bisection step
|
||||
(void)root; // just ensure no crash
|
||||
}
|
||||
|
||||
// Two iterations
|
||||
TEST_F(FindRootTest, TwoIterations) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x - 7.0;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 5.0, 9.0, 1e-10, 2, fail);
|
||||
// Linear function: Newton should converge very fast
|
||||
// After the initial midpoint (7.0), Newton step should nail it
|
||||
if (!fail) {
|
||||
EXPECT_NEAR(root, 7.0, 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
// Zero max iterations: the for-loop body never executes, so fail is set to true
|
||||
TEST_F(FindRootTest, ZeroMaxIterations) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x - 1.0;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
findRoot(func, 0.0, 2.0, 1e-10, 0, fail);
|
||||
EXPECT_TRUE(fail);
|
||||
}
|
||||
|
||||
// Large max_iter (should still converge quickly and not hang)
|
||||
TEST_F(FindRootTest, LargeMaxIter) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x - 16.0;
|
||||
dy = 2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.0, 10.0, 1e-12, 10000, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 4.0, 1e-10);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Special function types
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Cubic: f(x) = x^3 - 8 (root at x=2)
|
||||
TEST_F(FindRootTest, CubicRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x * x - 8.0;
|
||||
dy = 3.0 * x * x;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.0, 3.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 2.0, 1e-8);
|
||||
}
|
||||
|
||||
// Quartic: f(x) = x^4 - 16 (root at x=2)
|
||||
TEST_F(FindRootTest, QuarticRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x * x * x - 16.0;
|
||||
dy = 4.0 * x * x * x;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.0, 3.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 2.0, 1e-8);
|
||||
}
|
||||
|
||||
// Exponential: f(x) = e^x - 10 (root at x=ln(10))
|
||||
TEST_F(FindRootTest, ExponentialRoot2) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = exp(x) - 10.0;
|
||||
dy = exp(x);
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.0, 4.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, log(10.0), 1e-8);
|
||||
}
|
||||
|
||||
// Square root function: f(x) = sqrt(x) - 3, root at x=9
|
||||
// Derivative: 1/(2*sqrt(x))
|
||||
TEST_F(FindRootTest, SqrtFunctionRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = sqrt(x) - 3.0;
|
||||
dy = 0.5 / sqrt(x);
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.0, 20.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 9.0, 1e-6);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Near-zero roots
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// f(x) = x - 1e-10 (root very close to zero)
|
||||
// Note: convergence check is abs(dx) <= x_tol * abs(root).
|
||||
// When root is near zero, the relative tolerance is very tight.
|
||||
// This may require many iterations or not converge.
|
||||
TEST_F(FindRootTest, NearZeroRootLinear) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x - 1e-10;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, -1.0, 1.0, 1e-6, 200, fail);
|
||||
// Newton on a linear function converges in 1-2 steps regardless of root location
|
||||
if (!fail) {
|
||||
EXPECT_NEAR(root, 1e-10, 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
// f(x) = x (root exactly at zero)
|
||||
// Convergence test: abs(dx) <= x_tol * abs(root) = x_tol * 0 = 0
|
||||
// Will likely hit max_iter because relative tolerance at root=0 requires dx=0 exactly
|
||||
TEST_F(FindRootTest, RootExactlyAtZero) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, -1.0, 1.0, 1e-10, 200, fail);
|
||||
// Even if fail is true, root should be very close to 0
|
||||
EXPECT_NEAR(root, 0.0, 1e-6);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Negative domain
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Root in deeply negative domain: f(x) = x + 100, root at x=-100
|
||||
TEST_F(FindRootTest, NegativeDomainRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x + 100.0;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, -200.0, 0.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, -100.0, 1e-6);
|
||||
}
|
||||
|
||||
// Both bracket endpoints negative: f(x) = x^2 - 1, root at x=-1
|
||||
TEST_F(FindRootTest, NegativeBracketRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x - 1.0;
|
||||
dy = 2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, -2.0, -0.5, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, -1.0, 1e-8);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Trigonometric functions
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// sin(x) root at x=0 (bracket [-1, 1])
|
||||
TEST_F(FindRootTest, SinRootAtZero) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = sin(x);
|
||||
dy = cos(x);
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, -1.0, 1.0, 1e-10, 100, fail);
|
||||
// Root at 0 has the relative-tolerance issue, but Newton converges fast for sin
|
||||
EXPECT_NEAR(root, 0.0, 1e-4);
|
||||
}
|
||||
|
||||
// sin(x) root at x=2*pi (bracket [5.5, 7.0])
|
||||
TEST_F(FindRootTest, SinRootAt2Pi) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = sin(x);
|
||||
dy = cos(x);
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 5.5, 7.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 2.0 * M_PI, 1e-6);
|
||||
}
|
||||
|
||||
// cos(x) root at x=pi/2
|
||||
TEST_F(FindRootTest, CosRootAtPiOver2) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = cos(x);
|
||||
dy = -sin(x);
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.0, 2.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, M_PI / 2.0, 1e-6);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Multiple roots nearby
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// f(x) = (x-1)(x-2) = x^2 - 3x + 2, roots at x=1 and x=2
|
||||
// Bracket [0.5, 1.5] should find x=1
|
||||
TEST_F(FindRootTest, MultipleRootsFindFirst) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = (x - 1.0) * (x - 2.0);
|
||||
dy = 2.0 * x - 3.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 0.5, 1.5, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 1.0, 1e-8);
|
||||
}
|
||||
|
||||
// Same function, bracket [1.5, 2.5] should find x=2
|
||||
TEST_F(FindRootTest, MultipleRootsFindSecond) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = (x - 1.0) * (x - 2.0);
|
||||
dy = 2.0 * x - 3.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.5, 2.5, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 2.0, 1e-8);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Discontinuous derivative (sharp corner)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// f(x) = |x| - 1 with piecewise derivative.
|
||||
// Root at x=1 (bracket [0.5, 2.0] avoids the corner at 0)
|
||||
TEST_F(FindRootTest, AbsValueRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = fabs(x) - 1.0;
|
||||
dy = (x >= 0.0) ? 1.0 : -1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 0.5, 2.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 1.0, 1e-8);
|
||||
}
|
||||
|
||||
// f(x) = |x| - 1, root at x=-1
|
||||
TEST_F(FindRootTest, AbsValueNegativeRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = fabs(x) - 1.0;
|
||||
dy = (x >= 0.0) ? 1.0 : -1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, -2.0, -0.5, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, -1.0, 1e-8);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Very flat function (slow convergence)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// f(x) = (x - 3)^5 has a repeated root at x=3 where the derivative is also
|
||||
// zero. Newton-Raphson divides by dy which becomes 0, producing NaN.
|
||||
// The algorithm is expected to fail on this degenerate case.
|
||||
TEST_F(FindRootTest, FlatFifthOrderRootFails) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
double d = x - 3.0;
|
||||
double d2 = d * d;
|
||||
double d4 = d2 * d2;
|
||||
y = d4 * d; // (x-3)^5
|
||||
dy = 5.0 * d4; // 5*(x-3)^4
|
||||
};
|
||||
bool fail = false;
|
||||
findRoot(func, 2.0, 4.0, 1e-6, 500, fail);
|
||||
// The algorithm is expected to fail because dy -> 0 at the root
|
||||
EXPECT_TRUE(fail);
|
||||
}
|
||||
|
||||
// A function that is very flat near the root but still has nonzero derivative
|
||||
// at the root: f(x) = sinh(x - 3) which is ~0 near x=3 but never has dy=0.
|
||||
// sinh is flat near 0 (sinh(e) ~ e for small e) but derivative cosh(e) >= 1.
|
||||
TEST_F(FindRootTest, FlatSinhRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = sinh(x - 3.0);
|
||||
dy = cosh(x - 3.0);
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 2.0, 4.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 3.0, 1e-6);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Very steep function (fast convergence)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// f(x) = 1000*(x - 5), root at x=5. Very steep gradient.
|
||||
TEST_F(FindRootTest, SteepLinearRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = 1000.0 * (x - 5.0);
|
||||
dy = 1000.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 3.0, 7.0, 1e-12, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 5.0, 1e-10);
|
||||
}
|
||||
|
||||
// f(x) = 1e6 * (x - 2), very steep
|
||||
TEST_F(FindRootTest, VerySteepLinearRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = 1e6 * (x - 2.0);
|
||||
dy = 1e6;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.0, 3.0, 1e-14, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 2.0, 1e-12);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Large bracket
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// f(x) = x - 42, bracket [-1000, 1000]
|
||||
TEST_F(FindRootTest, LargeBracket) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x - 42.0;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, -1000.0, 1000.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 42.0, 1e-6);
|
||||
}
|
||||
|
||||
// Quadratic with large bracket: f(x) = x^2 - 100, root at 10
|
||||
TEST_F(FindRootTest, LargeBracketQuadratic) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x - 100.0;
|
||||
dy = 2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.0, 1000.0, 1e-10, 200, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 10.0, 1e-6);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Small bracket
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// f(x) = x - 1.0, bracket [0.999999, 1.000001] (very tight bracket around root)
|
||||
TEST_F(FindRootTest, SmallBracket) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x - 1.0;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 0.999999, 1.000001, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 1.0, 1e-6);
|
||||
}
|
||||
|
||||
// Quadratic with very small bracket around root=2
|
||||
TEST_F(FindRootTest, SmallBracketQuadratic) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x - 4.0;
|
||||
dy = 2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 1.9999, 2.0001, 1e-12, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 2.0, 1e-8);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Both overloads tested together
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Compare 2-arg and 4-arg overloads produce same result
|
||||
TEST_F(FindRootTest, OverloadsProduceSameResult) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x * x - 27.0;
|
||||
dy = 3.0 * x * x;
|
||||
};
|
||||
|
||||
bool fail_2arg = false;
|
||||
double root_2arg = findRoot(func, 2.0, 4.0, 1e-12, 100, fail_2arg);
|
||||
|
||||
// Pre-compute y values for 4-arg version
|
||||
double y1 = 2.0 * 2.0 * 2.0 - 27.0; // 8 - 27 = -19
|
||||
double y2 = 4.0 * 4.0 * 4.0 - 27.0; // 64 - 27 = 37
|
||||
bool fail_4arg = false;
|
||||
double root_4arg = findRoot(func, 2.0, y1, 4.0, y2, 1e-12, 100, fail_4arg);
|
||||
|
||||
EXPECT_FALSE(fail_2arg);
|
||||
EXPECT_FALSE(fail_4arg);
|
||||
EXPECT_NEAR(root_2arg, 3.0, 1e-10);
|
||||
EXPECT_NEAR(root_4arg, 3.0, 1e-10);
|
||||
EXPECT_NEAR(root_2arg, root_4arg, 1e-14);
|
||||
}
|
||||
|
||||
// 4-arg overload: x1 endpoint is exact root (y1 == 0)
|
||||
TEST_F(FindRootTest, FourArgX1IsRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x - 5.0;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
// y1 = 5 - 5 = 0
|
||||
double root = findRoot(func, 5.0, 0.0, 8.0, 3.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_DOUBLE_EQ(root, 5.0);
|
||||
}
|
||||
|
||||
// 4-arg overload: x2 endpoint is exact root (y2 == 0)
|
||||
TEST_F(FindRootTest, FourArgX2IsRoot) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x - 5.0;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
// y2 = 5 - 5 = 0
|
||||
double root = findRoot(func, 2.0, -3.0, 5.0, 0.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_DOUBLE_EQ(root, 5.0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Same-sign y values (should fail)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Both endpoints positive: should fail
|
||||
TEST_F(FindRootTest, BothEndpointsPositiveFails) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x + 1.0; // Always positive, no real root
|
||||
dy = 2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
findRoot(func, 1.0, 3.0, 1e-10, 100, fail);
|
||||
EXPECT_TRUE(fail);
|
||||
}
|
||||
|
||||
// Both endpoints negative: should fail
|
||||
TEST_F(FindRootTest, BothEndpointsNegativeFails) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = -x * x - 1.0; // Always negative
|
||||
dy = -2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
findRoot(func, -3.0, 3.0, 1e-10, 100, fail);
|
||||
EXPECT_TRUE(fail);
|
||||
}
|
||||
|
||||
// 4-arg version: same-sign y values
|
||||
TEST_F(FindRootTest, FourArgSameSignFails) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x;
|
||||
dy = 2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
// Both y values positive
|
||||
findRoot(func, 1.0, 1.0, 2.0, 4.0, 1e-10, 100, fail);
|
||||
EXPECT_TRUE(fail);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Symmetry test
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// f(x) = x^2 - 4: bracket [0, 3] finds +2, bracket [-3, 0] finds -2
|
||||
TEST_F(FindRootTest, SymmetryPositiveBracket) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x - 4.0;
|
||||
dy = 2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, 0.5, 3.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 2.0, 1e-8);
|
||||
}
|
||||
|
||||
TEST_F(FindRootTest, SymmetryNegativeBracket) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x * x - 4.0;
|
||||
dy = 2.0 * x;
|
||||
};
|
||||
bool fail = false;
|
||||
double root = findRoot(func, -3.0, -0.5, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, -2.0, 1e-8);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Swapped bracket order (x1 > x2)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// The algorithm should work regardless of bracket order
|
||||
TEST_F(FindRootTest, SwappedBracketOrder) {
|
||||
FindRootFunc func = [](double x, double &y, double &dy) {
|
||||
y = x - 3.0;
|
||||
dy = 1.0;
|
||||
};
|
||||
bool fail = false;
|
||||
// x1=5 > x2=1 (reversed order)
|
||||
double root = findRoot(func, 5.0, 1.0, 1e-10, 100, fail);
|
||||
EXPECT_FALSE(fail);
|
||||
EXPECT_NEAR(root, 3.0, 1e-8);
|
||||
}
|
||||
|
||||
} // namespace sta
|
||||
|
||||
Reference in New Issue
Block a user