|
| 1 | +// SPDX-License-Identifier: BSD-3-Clause |
| 2 | +// Copyright (c) 2026, The OpenROAD Authors |
| 3 | + |
| 4 | +#include <string> |
| 5 | + |
| 6 | +#include "gtest/gtest.h" |
| 7 | +#include "odb/db.h" |
| 8 | +#include "odb/dbWireCodec.h" |
| 9 | +#include "tst/fixture.h" |
| 10 | + |
| 11 | +namespace odb { |
| 12 | + |
| 13 | +// Unit tests for dbBlock::designIsRouted(): a design is "routed" when every |
| 14 | +// signal (non-special) net with more than one pin either has routing |
| 15 | +// (wires/vias) or is connected by abutment. Special nets are ignored. |
| 16 | +class TestDesignIsRouted : public tst::Fixture |
| 17 | +{ |
| 18 | + protected: |
| 19 | + void SetUp() override |
| 20 | + { |
| 21 | + loadTechAndLib( |
| 22 | + "tech", "Nangate45.lef", "_main/test/Nangate45/Nangate45.lef"); |
| 23 | + dbChip* chip = dbChip::create(db_.get(), db_->getTech()); |
| 24 | + block_ = dbBlock::create(chip, "top"); |
| 25 | + } |
| 26 | + |
| 27 | + // Create a 2-pin signal net connecting the output of one inverter to the |
| 28 | + // input of another (pin_count == 2, so it must be routed to pass). |
| 29 | + dbNet* makeMultiPinNet(const std::string& name) |
| 30 | + { |
| 31 | + dbNet* net = dbNet::create(block_, name.c_str()); |
| 32 | + dbMaster* inv = db_->findMaster("INV_X1"); |
| 33 | + dbInst* i0 = dbInst::create(block_, inv, (name + "_i0").c_str()); |
| 34 | + dbInst* i1 = dbInst::create(block_, inv, (name + "_i1").c_str()); |
| 35 | + i0->findITerm("ZN")->connect(net); |
| 36 | + i1->findITerm("A")->connect(net); |
| 37 | + return net; |
| 38 | + } |
| 39 | + |
| 40 | + // Attach a trivial metal1 wire so the net counts as routed. |
| 41 | + void routeNet(dbNet* net) |
| 42 | + { |
| 43 | + dbTechLayer* metal1 = db_->getTech()->findLayer("metal1"); |
| 44 | + dbWire* wire = dbWire::create(net); |
| 45 | + dbWireEncoder encoder; |
| 46 | + encoder.begin(wire); |
| 47 | + encoder.newPath(metal1, dbWireType::ROUTED); |
| 48 | + encoder.addPoint(0, 0); |
| 49 | + encoder.addPoint(0, 1000); |
| 50 | + encoder.end(); |
| 51 | + } |
| 52 | + |
| 53 | + dbBlock* block_; |
| 54 | +}; |
| 55 | + |
| 56 | +// A design whose multi-pin signal net carries wires is fully routed. |
| 57 | +TEST_F(TestDesignIsRouted, RoutedSignalNet) |
| 58 | +{ |
| 59 | + dbNet* net = makeMultiPinNet("n1"); |
| 60 | + routeNet(net); |
| 61 | + |
| 62 | + EXPECT_TRUE(block_->designIsRouted(/*verbose=*/false)); |
| 63 | +} |
| 64 | + |
| 65 | +// Special (power/ground) nets are skipped entirely: an unrouted special net |
| 66 | +// does not make the design unrouted. |
| 67 | +TEST_F(TestDesignIsRouted, SpecialNetsAreIgnored) |
| 68 | +{ |
| 69 | + dbNet* net = makeMultiPinNet("vdd"); |
| 70 | + net->setSpecial(); |
| 71 | + // Intentionally left unrouted. |
| 72 | + |
| 73 | + EXPECT_TRUE(block_->designIsRouted(/*verbose=*/false)); |
| 74 | +} |
| 75 | + |
| 76 | +// A mix of an (ignored) special net and a routed signal net is fully routed. |
| 77 | +TEST_F(TestDesignIsRouted, MixedSpecialAndRoutedSignalNets) |
| 78 | +{ |
| 79 | + dbNet* special = makeMultiPinNet("vdd"); |
| 80 | + special->setSpecial(); |
| 81 | + |
| 82 | + dbNet* signal = makeMultiPinNet("n1"); |
| 83 | + routeNet(signal); |
| 84 | + |
| 85 | + EXPECT_TRUE(block_->designIsRouted(/*verbose=*/false)); |
| 86 | +} |
| 87 | + |
| 88 | +// A net with a single pin needs no routing and never makes a design unrouted. |
| 89 | +TEST_F(TestDesignIsRouted, SinglePinNetIsRouted) |
| 90 | +{ |
| 91 | + dbNet* net = dbNet::create(block_, "n1"); |
| 92 | + dbInst* inst = dbInst::create(block_, db_->findMaster("INV_X1"), "i0"); |
| 93 | + inst->findITerm("A")->connect(net); |
| 94 | + |
| 95 | + EXPECT_TRUE(block_->designIsRouted(/*verbose=*/false)); |
| 96 | +} |
| 97 | + |
| 98 | +// A design with no nets is trivially routed. |
| 99 | +TEST_F(TestDesignIsRouted, EmptyDesignIsRouted) |
| 100 | +{ |
| 101 | + EXPECT_TRUE(block_->designIsRouted(/*verbose=*/false)); |
| 102 | +} |
| 103 | + |
| 104 | +// A multi-pin signal net with no wires (and no abutment) is unrouted. |
| 105 | +TEST_F(TestDesignIsRouted, UnroutedSignalNetFails) |
| 106 | +{ |
| 107 | + makeMultiPinNet("n1"); |
| 108 | + |
| 109 | + EXPECT_FALSE(block_->designIsRouted(/*verbose=*/false)); |
| 110 | +} |
| 111 | + |
| 112 | +// An unrouted signal net is detected even when ignored special nets coexist. |
| 113 | +TEST_F(TestDesignIsRouted, UnroutedSignalNetWithSpecialNetsFails) |
| 114 | +{ |
| 115 | + dbNet* special = makeMultiPinNet("vdd"); |
| 116 | + special->setSpecial(); |
| 117 | + |
| 118 | + makeMultiPinNet("n1"); // unrouted signal net |
| 119 | + |
| 120 | + EXPECT_FALSE(block_->designIsRouted(/*verbose=*/false)); |
| 121 | +} |
| 122 | + |
| 123 | +// Non-verbose mode reports an unrouted design without emitting any warning. |
| 124 | +TEST_F(TestDesignIsRouted, NonVerboseEmitsNoWarning) |
| 125 | +{ |
| 126 | + makeMultiPinNet("n1"); |
| 127 | + makeMultiPinNet("n2"); |
| 128 | + |
| 129 | + const int warnings_before = logger_.getWarningCount(); |
| 130 | + EXPECT_FALSE(block_->designIsRouted(/*verbose=*/false)); |
| 131 | + EXPECT_EQ(logger_.getWarningCount(), warnings_before); |
| 132 | +} |
| 133 | + |
| 134 | +// Verbose mode returns false and warns once per unrouted net (ODB-0232), |
| 135 | +// scanning all nets rather than short-circuiting. |
| 136 | +TEST_F(TestDesignIsRouted, VerboseWarnsForEachUnroutedNet) |
| 137 | +{ |
| 138 | + makeMultiPinNet("n1"); |
| 139 | + makeMultiPinNet("n2"); |
| 140 | + |
| 141 | + const int warnings_before = logger_.getWarningCount(); |
| 142 | + EXPECT_FALSE(block_->designIsRouted(/*verbose=*/true)); |
| 143 | + EXPECT_EQ(logger_.getWarningCount(), warnings_before + 2); |
| 144 | +} |
| 145 | + |
| 146 | +} // namespace odb |
0 commit comments