Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/operation/iSTA/source/module/reader/ReadLiberty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
Expand Down Expand Up @@ -82,12 +83,46 @@ void ReadLiberty::read()
STALOG.info(Loc::current(), "Liberty conversion starting: libraries=", lib_list.size(), ", configured_workers=", configured_workers,
", active_workers=", active_workers, ".");
readCells(lib_list);
checkCellReferences();
readLibraryInfo(lib_list);
const double conversion_seconds = std::chrono::duration<double>(std::chrono::steady_clock::now() - conversion_start).count();
STALOG.info(Loc::current(), "Liberty conversion completed: libraries=", lib_list.size(), ", configured_workers=", configured_workers,
", active_workers=", active_workers, ", conversion_seconds=", conversion_seconds, monitor.getStatsInfo());
}

void ReadLiberty::checkCellReferences() const
{
std::map<std::string, std::set<std::string>> missing;
// ReadNetwork has already removed PG terminals. Instance pin lists are
// linked later by DataManager, so inspect the imported signal pins here.
// Filler/tap cells with no signal terminals do not need a timing model.
for (const auto& [name, pin] : _network.pins()) {
if (pin.get_is_port())
continue;
const auto& instance = _network.instances().at(pin.get_instance_name());
if (!_liberty.get_cell_map().contains(instance.get_cell_name()))
missing[instance.get_cell_name()].insert(pin.get_instance_name());
}
if (missing.empty())
return;

// Continuing would turn an unlinked SRAM into the graph builder's generic
// input/output connectivity, propagating clocks through its data pins and
// potentially disabling path-state pruning across the entire design.
std::ostringstream message;
message << "Missing Liberty models for " << missing.size() << " signal cell type(s):";
std::size_t shown = 0;
for (const auto& [cell, instances] : missing) {
if (shown++ == 8) {
message << " ...";
break;
}
message << " '" << cell << "' (" << instances.size() << " instance(s), e.g. '" << *instances.begin() << "');";
}
message << " load the matching libraries for the selected timing corner, including macro libraries, before initializing STA.";
throw std::runtime_error(message.str());
}

void ReadLiberty::readCells(const std::vector<idb::LibLibrary*>& lib_list)
{
_liberty.get_cell_map().clear();
Expand Down
1 change: 1 addition & 0 deletions src/operation/iSTA/source/module/reader/ReadLiberty.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class ReadLiberty
LibertyLibrary& _liberty;
const Network& _network;
void readCells(const std::vector<idb::LibLibrary*>& lib_list);
void checkCellReferences() const;
void readLibraryInfo(const std::vector<idb::LibLibrary*>& lib_list);
idb::LibLibrary* findReferenceLibrary(const std::vector<idb::LibLibrary*>& lib_list);
TimingCapacitiveUnit capacitanceUnit(idb::LibLibrary* lib_library);
Expand Down
5 changes: 5 additions & 0 deletions src/operation/iSTA/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ target_link_libraries(test_ista_read_parasitics PRIVATE ista_reader ista_model i
add_test(NAME ista_read_parasitics COMMAND test_ista_read_parasitics)
set_tests_properties(ista_read_parasitics PROPERTIES TIMEOUT 30)

add_executable(test_ista_missing_liberty EXCLUDE_FROM_ALL missing_liberty_test.cpp)
target_link_libraries(test_ista_missing_liberty PRIVATE ista_reader ista_model liberty idm)
add_test(NAME ista_missing_liberty COMMAND test_ista_missing_liberty ${CMAKE_CURRENT_SOURCE_DIR})
set_tests_properties(ista_missing_liberty PROPERTIES TIMEOUT 30)

add_executable(test_ista_read_network EXCLUDE_FROM_ALL read_network_test.cpp)
target_link_libraries(test_ista_read_network PRIVATE ista_reader ista_data_manager idm idb)
add_test(NAME ista_read_network COMMAND test_ista_read_network)
Expand Down
78 changes: 78 additions & 0 deletions src/operation/iSTA/test/missing_liberty_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MulanPSL-2.0
#include <filesystem>
#include <stdexcept>
#include <string>

#include "Database.hpp"
#include "Logger.hpp"
#include "ReadLiberty.hpp"
#include "idm.h"

using namespace ista;

static void require(bool condition, const char* message)
{
if (!condition)
throw std::runtime_error(message);
}

static void read(Database& database)
{
ReadLiberty(database.liberty(), database.network()).read();
}

static void requireMissing(Database& database)
{
std::string diagnostic;
try {
read(database);
} catch (const std::runtime_error& error) {
diagnostic = error.what();
}
require(diagnostic.find("memory") != std::string::npos && diagnostic.find("sram0") != std::string::npos,
"missing signal-cell model was silently accepted instead of identifying the cell and instance");
}

int main(int argc, char** argv)
{
require(argc == 2, "expected fixture directory");
Logger::initInst();
const auto fixtures = std::filesystem::path(argv[1]);
const auto standard = (fixtures / "clock_check_reader.lib").string();
const auto memory = (fixtures / "retaining_arc.lib").string();
Database database;
// Network import removes PG pins. Physical-only filler/tap cells therefore
// do not require a timing model, even when their master is not in Liberty.
auto& filler = database.network().instances()["fill0"];
filler.set_instance_name("fill0");
filler.set_cell_name("physical_filler");
require(dmInst->readLib({standard}), "failed to load standard-cell generation");
read(database);

auto& sram = database.network().instances()["sram0"];
sram.set_instance_name("sram0");
sram.set_cell_name("memory");
for (const auto* name : {"CLK", "Q"}) {
const std::string full_name = std::string("sram0:") + name;
auto& pin = database.network().pins()[full_name];
pin.set_full_name(full_name);
pin.set_instance_name("sram0");
pin.set_pin_name(name);
pin.set_direction(std::string(name) == "CLK" ? PinDirection::kInput : PinDirection::kOutput);
}
// Instance pin lists have not been linked at the reader stage. The check
// must use imported pins and must also catch unconnected signal terminals.
require(sram.get_pin_name_list().empty(), "fixture prematurely linked instance pins");
requireMissing(database);

require(dmInst->readLib({standard, memory}), "failed to load complete generation");
read(database);
require(database.liberty().get_cell_map().contains("memory"), "complete generation lost SRAM model");
require(!database.liberty().get_cell_map().at("memory").get_cell_arc_list().empty(), "SRAM timing arcs were discarded");

// A later corner reload must not silently reuse the old corner's SRAM or
// replace its clock-to-Q arcs by input-to-output combinational connectivity.
require(dmInst->readLib({standard}), "failed to reload standard-cell generation");
requireMissing(database);
Logger::destroyInst();
}
Loading