From 4a47ae8b63638807ca9cb8457f83e3c63f2fcf43 Mon Sep 17 00:00:00 2001 From: zxy Date: Tue, 21 Apr 2026 14:23:16 +0800 Subject: [PATCH] fix: restore ICS55 runtime harden export semantics --- src/database/manager/builder/builder.cpp | 5 + .../iSTA/source/module/sta/StaAnalyze.cc | 12 + .../source/module/sta/StaCharacterTiming.cc | 63 +++- .../iSTA/test/CharacterTimingTest.cc | 8 + .../iSTA/test/CharacterTimingTestCommon.hh | 285 ++++++++++++++++++ src/operation/iSTA/test/Ics55GcdExportTest.cc | 95 ++++++ src/platform/data_manager/idm.cpp | 12 + src/platform/data_manager/idm.h | 1 + 8 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 src/operation/iSTA/test/Ics55GcdExportTest.cc diff --git a/src/database/manager/builder/builder.cpp b/src/database/manager/builder/builder.cpp index 14d6fe15b..5be145eb6 100644 --- a/src/database/manager/builder/builder.cpp +++ b/src/database/manager/builder/builder.cpp @@ -51,6 +51,11 @@ IdbBuilder::IdbBuilder() IdbBuilder::~IdbBuilder() { + delete _def_service; + _def_service = nullptr; + + delete _lef_service; + _lef_service = nullptr; } void IdbBuilder::log() diff --git a/src/operation/iSTA/source/module/sta/StaAnalyze.cc b/src/operation/iSTA/source/module/sta/StaAnalyze.cc index b7c5f4142..e61ef91d4 100644 --- a/src/operation/iSTA/source/module/sta/StaAnalyze.cc +++ b/src/operation/iSTA/source/module/sta/StaAnalyze.cc @@ -411,9 +411,21 @@ unsigned StaAnalyze::analyzeSetupHold(StaVertex* end_vertex, StaArc* check_arc, StaClockData* launch_clock_data = (dynamic_cast(delay_data)) ->get_launch_clock_data(); + if (!launch_clock_data) { + DLOG_INFO_FIRST_N(10) + << "skip setup/hold analysis without launch clock data at " + << end_vertex->getName(); + continue; + } auto* launch_clock = (dynamic_cast(launch_clock_data)) ->get_prop_clock(); + if (!launch_clock || !capture_clock) { + DLOG_INFO_FIRST_N(10) + << "skip setup/hold analysis with incomplete clock binding at " + << end_vertex->getName(); + continue; + } std::optional cppr; if (launch_clock == capture_clock) { diff --git a/src/operation/iSTA/source/module/sta/StaCharacterTiming.cc b/src/operation/iSTA/source/module/sta/StaCharacterTiming.cc index 7a878f628..d96d1577b 100644 --- a/src/operation/iSTA/source/module/sta/StaCharacterTiming.cc +++ b/src/operation/iSTA/source/module/sta/StaCharacterTiming.cc @@ -718,8 +718,69 @@ unsigned StaCharacterTiming::init(StaGraph* the_graph) { } auto io_delays = ista->getIODelayConstrain(the_vertex); - bool created = false; + auto synthesize_default_input_arrival = [this, ista, + the_vertex]() -> bool { + if (!the_vertex || the_vertex->is_clock() || the_vertex->is_sdc_clock_pin()) { + return false; + } + + auto clocks = ista->getClocks(); + if (clocks.size() != 1) { + return false; + } + + auto* launch_clock = clocks.front(); + if (!launch_clock) { + return false; + } + + auto ensure_launch_clock_data = + [this, the_vertex, launch_clock](AnalysisMode analysis_mode) + -> StaClockData* { + auto clock_datas = the_vertex->getClockData(analysis_mode, TransType::kRise); + for (auto* clock_data : clock_datas) { + auto* candidate_clock_data = dynamic_cast(clock_data); + if (candidate_clock_data && + candidate_clock_data->get_prop_clock() == launch_clock) { + return candidate_clock_data; + } + } + + auto* launch_clock_data = new StaClockData( + analysis_mode, TransType::kRise, 0, the_vertex, launch_clock); + launch_clock_data->set_data_epoch(_characterization_epoch); + launch_clock_data->set_clock_wave_type(TransType::kRise); + the_vertex->addData(launch_clock_data); + return launch_clock_data; + }; + + auto add_default_delay_data = + [this, the_vertex, &ensure_launch_clock_data](AnalysisMode analysis_mode) { + auto* launch_clock_data = ensure_launch_clock_data(analysis_mode); + if (!launch_clock_data) { + return false; + } + + for (auto data_trans_type : {TransType::kRise, TransType::kFall}) { + auto* path_delay_data = new StaPathDelayData( + analysis_mode, data_trans_type, 0, launch_clock_data, the_vertex); + path_delay_data->set_data_epoch(_characterization_epoch); + path_delay_data->set_launch_delay_data(path_delay_data); + the_vertex->addData(path_delay_data); + } + return true; + }; + + return add_default_delay_data(AnalysisMode::kMax) | + add_default_delay_data(AnalysisMode::kMin); + }; + + if (io_delays.empty()) { + return synthesize_default_input_arrival(); + } + + bool created = false; for (auto* io_delay : io_delays) { if (!io_delay) { continue; diff --git a/src/operation/iSTA/test/CharacterTimingTest.cc b/src/operation/iSTA/test/CharacterTimingTest.cc index d3a6ecb5e..cd3a5a0ac 100644 --- a/src/operation/iSTA/test/CharacterTimingTest.cc +++ b/src/operation/iSTA/test/CharacterTimingTest.cc @@ -18,6 +18,8 @@ #include "CharacterTimingTestCommon.hh" #include "gtest/gtest.h" #include "log/Log.hh" +#include "idm.h" +#include "sta/Sta.hh" #include "sta/StaVertex.hh" #include "usage/usage.hh" @@ -37,6 +39,9 @@ class CharacterTimingTest : public testing::Test { } void TearDown() final { ieval::TimingAPI::destroyInst(); + TimingEngine::destroyTimingEngine(); + Sta::destroySta(); + dmInst->reset(); Log::end(); } }; @@ -56,6 +61,9 @@ TimingEngine* buildGoldenCaseTimingWithSdc(const std::filesystem::path& sdc_path } ieval::TimingAPI::destroyInst(); + TimingEngine::destroyTimingEngine(); + Sta::destroySta(); + dmInst->reset(); auto* timing_api = ieval::TimingAPI::getInst(); EXPECT_TRUE(dmInst->init(db_config_path.string())) diff --git a/src/operation/iSTA/test/CharacterTimingTestCommon.hh b/src/operation/iSTA/test/CharacterTimingTestCommon.hh index bf10ed160..e07aca97e 100644 --- a/src/operation/iSTA/test/CharacterTimingTestCommon.hh +++ b/src/operation/iSTA/test/CharacterTimingTestCommon.hh @@ -17,7 +17,9 @@ #pragma once #include "api/TimingEngine.hh" +#include "api/TimingIDBAdapter.hh" #include "idm.h" +#include "sta/Sta.hh" #include "timing_api.hh" #include "gtest/gtest.h" @@ -88,6 +90,128 @@ inline fs::path goldenCaseOutputDir() { return defaultOutputRoot() / "NV_NVDLA_partition_m"; } +inline const fs::path& ics55GcdServiceRoot() { + static const fs::path kServiceRoot = + "/nfs/share/home/huangzengrong/benchmark/test/ics55_gcd_service"; + return kServiceRoot; +} + +inline const fs::path& ics55GcdHardenRoot() { + static const fs::path kHardenRoot = + "/nfs/share/home/huangzengrong/benchmark/test/gcd_harden"; + return kHardenRoot; +} + +inline const fs::path& ics55PdkRoot() { + static const fs::path kPdkRoot = [] { + if (const char* pdk_root = + std::getenv("IEDA_CHARACTER_TIMING_ICS55_PDK_ROOT"); + pdk_root && *pdk_root) { + return fs::path(pdk_root); + } + + for (const fs::path& candidate : { + fs::path("/nfs/home/huangzengrong/ecc_project/ecc/chipcompiler/" + "thirdparty/icsprout55-pdk"), + fs::path("/nfs/share/home/zhaoxueyan/icsprout55-pdk")}) { + if (fs::exists(candidate)) { + return candidate; + } + } + + return fs::path(); + }(); + return kPdkRoot; +} + +inline fs::path ics55GcdOutputDir() { + return defaultOutputRoot() / "ics55_gcd"; +} + +inline fs::path ics55GcdIntegrationOutputDir() { + return ics55GcdOutputDir() / "integration"; +} + +inline fs::path ics55GcdRuntimeOutputDir() { + return ics55GcdOutputDir() / "runtime"; +} + +inline fs::path ics55GcdDefPath() { + return ics55GcdHardenRoot() / "gcd_filler.def.gz"; +} + +inline fs::path ics55GcdSdcPath() { + return ics55GcdHardenRoot() / "gcd.sdc"; +} + +inline fs::path ics55GcdVerilogPath() { + const auto filler_output = + ics55GcdServiceRoot() / "filler_ecc/output/gcd_filler.v"; + if (fs::exists(filler_output)) { + return filler_output; + } + + return ics55GcdServiceRoot() / "origin/gcd.v"; +} + +inline fs::path ics55GcdTechLefPath() { + return ics55PdkRoot() / "prtech/techLEF/N551P6M.lef"; +} + +inline std::vector ics55GcdLefFiles() { + const auto pdk_root = ics55PdkRoot(); + return { + (pdk_root / + "IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/lef/" + "ics55_LLSC_H7CR_ecos.lef") + .string(), + (pdk_root / + "IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/lef/" + "ics55_LLSC_H7CL_ecos.lef") + .string(), + }; +} + +inline std::vector ics55GcdLibertyFiles() { + const auto pdk_root = ics55PdkRoot(); + return { + (pdk_root / + "IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/" + "ics55_LLSC_H7CR_ss_rcworst_1p08_125_nldm.lib") + .string(), + (pdk_root / + "IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/" + "ics55_LLSC_H7CL_ss_rcworst_1p08_125_nldm.lib") + .string(), + }; +} + +inline bool ics55GcdCaseAvailable() { + if (ics55PdkRoot().empty()) { + return false; + } + + std::vector required_paths = { + ics55GcdServiceRoot(), + ics55GcdHardenRoot(), + ics55GcdTechLefPath(), + ics55GcdDefPath(), + ics55GcdSdcPath(), + ics55GcdVerilogPath(), + }; + for (const auto& lef_file : ics55GcdLefFiles()) { + required_paths.emplace_back(lef_file); + } + for (const auto& liberty_file : ics55GcdLibertyFiles()) { + required_paths.emplace_back(liberty_file); + } + + return std::all_of(required_paths.begin(), required_paths.end(), + [](const fs::path& candidate) { + return !candidate.empty() && fs::exists(candidate); + }); +} + inline fs::path goldenCaseIntegrationOutputDir() { return goldenCaseOutputDir() / "integration"; } @@ -262,6 +386,89 @@ inline fs::path writeGoldenCaseDbConfig(const fs::path& output_root) { return config_path; } +inline fs::path writeIcs55GcdDbConfig(const fs::path& output_root) { + const auto config_dir = output_root / "config"; + std::error_code ec; + fs::create_directories(config_dir, ec); + EXPECT_FALSE(ec) << "failed to create config directory: " << config_dir + << ", error=" << ec.message(); + + const auto config_path = config_dir / "ics55_gcd.integration.db.json"; + std::ofstream output(config_path); + EXPECT_TRUE(output.is_open()) << "failed to open generated db config at " + << config_path; + + const auto lef_files = ics55GcdLefFiles(); + const auto liberty_files = ics55GcdLibertyFiles(); + output << "{\n"; + output << " \"INPUT\": {\n"; + output << " \"tech_lef_path\": \"" << ics55GcdTechLefPath().string() + << "\",\n"; + output << " \"lef_paths\": [\n"; + for (size_t i = 0; i < lef_files.size(); ++i) { + output << " \"" << lef_files[i] << "\""; + output << (i + 1 == lef_files.size() ? "\n" : ",\n"); + } + output << " ],\n"; + output << " \"def_path\": \"" << ics55GcdDefPath().string() << "\",\n"; + output << " \"verilog_path\": \"" << ics55GcdVerilogPath().string() + << "\",\n"; + output << " \"lib_path\": [\n"; + for (size_t i = 0; i < liberty_files.size(); ++i) { + output << " \"" << liberty_files[i] << "\""; + output << (i + 1 == liberty_files.size() ? "\n" : ",\n"); + } + output << " ],\n"; + output << " \"sdc_path\": \"" << ics55GcdSdcPath().string() << "\"\n"; + output << " },\n"; + output << " \"OUTPUT\": {\n"; + output << " \"output_dir_path\": \"" << output_root.string() << "\"\n"; + output << " },\n"; + output << " \"LayerSettings\": {\n"; + output << " \"routing_layer_1st\": \"MET1\"\n"; + output << " }\n"; + output << "}\n"; + output.close(); + + return config_path; +} + +inline bool prepareIcs55GcdDatabase( + const fs::path& integration_output_dir = ics55GcdIntegrationOutputDir()) { + std::error_code ec; + fs::create_directories(integration_output_dir, ec); + EXPECT_FALSE(ec) << "failed to create ICS55 gcd integration directory: " + << integration_output_dir << ", error=" << ec.message(); + + const auto db_config_path = writeIcs55GcdDbConfig(integration_output_dir); + EXPECT_TRUE(fs::exists(db_config_path)) + << "generated db config missing: " << db_config_path; + if (!fs::exists(db_config_path)) { + return false; + } + + ieval::TimingAPI::destroyInst(); + TimingEngine::destroyTimingEngine(); + Sta::destroySta(); + dmInst->reset(); + return dmInst->init(db_config_path.string()); +} + +inline unsigned readLibertySequentially(TimingEngine* timing_engine, + const std::vector& lib_files) { + EXPECT_NE(timing_engine, nullptr); + if (!timing_engine) { + return 0; + } + + unsigned read_ok = 1; + for (const auto& lib_file : lib_files) { + read_ok &= timing_engine->get_ista()->readLiberty(lib_file.c_str()); + } + + return read_ok; +} + inline fs::path generateGoldenCaseTimingModel(AnalysisMode analysis_mode, const fs::path& output_lib) { static std::mutex generation_mutex; @@ -296,6 +503,9 @@ inline fs::path generateGoldenCaseTimingModel(AnalysisMode analysis_mode, } ieval::TimingAPI::destroyInst(); + TimingEngine::destroyTimingEngine(); + Sta::destroySta(); + dmInst->reset(); auto* timing_api = ieval::TimingAPI::getInst(); EXPECT_TRUE(dmInst->init(db_config_path.string())) @@ -357,6 +567,9 @@ inline TimingEngine* prepareGoldenCaseTimingRuntime( } ieval::TimingAPI::destroyInst(); + TimingEngine::destroyTimingEngine(); + Sta::destroySta(); + dmInst->reset(); auto* timing_api = ieval::TimingAPI::getInst(); EXPECT_TRUE(dmInst->init(db_config_path.string())) @@ -387,6 +600,78 @@ inline TimingEngine* prepareGoldenCaseTimingRuntime( return timing_engine; } +inline TimingEngine* prepareIcs55GcdTimingRuntime( + const fs::path& design_workspace, + const fs::path& integration_output_dir = ics55GcdRuntimeOutputDir() / + "integration") { + static std::mutex runtime_mutex; + std::lock_guard lock(runtime_mutex); + + std::error_code ec; + fs::create_directories(design_workspace, ec); + EXPECT_FALSE(ec) << "failed to create runtime output directory: " + << design_workspace << ", error=" << ec.message(); + + fs::create_directories(integration_output_dir, ec); + EXPECT_FALSE(ec) << "failed to create runtime integration directory: " + << integration_output_dir << ", error=" << ec.message(); + + const auto db_config_path = writeIcs55GcdDbConfig(integration_output_dir); + EXPECT_TRUE(fs::exists(db_config_path)) + << "generated runtime db config missing: " << db_config_path; + if (!fs::exists(db_config_path)) { + return nullptr; + } + + ieval::TimingAPI::destroyInst(); + TimingEngine::destroyTimingEngine(); + Sta::destroySta(); + dmInst->reset(); + + EXPECT_TRUE(dmInst->init(db_config_path.string())) + << "failed to initialize dmInst with " << db_config_path; + + auto* timing_engine = TimingEngine::getOrCreateTimingEngine(); + timing_engine->set_num_threads(1); + + EXPECT_TRUE(readLibertySequentially(timing_engine, ics55GcdLibertyFiles())) + << "failed to load ICS55 liberty files"; + + auto db_adapter = + std::make_unique(timing_engine->get_ista()); + db_adapter->set_idb(dmInst->get_idb_builder()); + EXPECT_TRUE(db_adapter->convertDBToTimingNetlist(true)) + << "failed to convert ICS55 gcd DB into timing netlist"; + timing_engine->set_db_adapter(std::move(db_adapter)); + + timing_engine->readSdc(ics55GcdSdcPath().c_str()); + + const auto output_dir_string = design_workspace.string(); + timing_engine->set_design_work_space(output_dir_string.c_str()); + timing_engine->get_ista()->set_top_module_name("gcd"); + timing_engine->get_ista()->set_n_worst_path_per_clock(1); + timing_engine->get_ista()->set_n_worst_path_per_endpoint(256); + timing_engine->get_ista()->set_analysis_mode(AnalysisMode::kMaxMin); + timing_engine->buildGraph(); + + auto* clk_port = timing_engine->get_ista()->get_netlist()->findPort("clk"); + EXPECT_NE(clk_port, nullptr) << "ICS55 gcd netlist is missing port clk"; + if (!clk_port) { + return nullptr; + } + + auto* clk_vertex = timing_engine->get_ista()->findVertex(clk_port); + EXPECT_NE(clk_vertex, nullptr) + << "ICS55 gcd graph is missing clk vertex after buildGraph"; + if (!clk_vertex) { + return nullptr; + } + + timing_engine->updateTiming(); + + return timing_engine; +} + inline std::string readFile(const fs::path& file_path) { std::ifstream input(file_path); return std::string((std::istreambuf_iterator(input)), diff --git a/src/operation/iSTA/test/Ics55GcdExportTest.cc b/src/operation/iSTA/test/Ics55GcdExportTest.cc new file mode 100644 index 000000000..8d1867283 --- /dev/null +++ b/src/operation/iSTA/test/Ics55GcdExportTest.cc @@ -0,0 +1,95 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** + +#include "CharacterTimingTestCommon.hh" +#include "py_ista.h" + +#include "gtest/gtest.h" + +#include +#include + +namespace { + +namespace fs = std::filesystem; + +TEST(Ics55GcdExportTest, write_abstract_lef_exports_block_macro_from_runtime) { + if (!ista::test::ics55GcdCaseAvailable()) { + GTEST_SKIP() << "ICS55 gcd harden case or PDK is not available"; + } + + const fs::path output_root = + ista::test::ics55GcdOutputDir() / "python_interface"; + ASSERT_TRUE(ista::test::prepareIcs55GcdDatabase(output_root / "integration")); + + const fs::path output_lef = output_root / "gcd.python.lef"; + std::error_code ec; + fs::remove(output_lef, ec); + + EXPECT_TRUE(python_interface::writeAbstractLef(output_lef.string())); + ASSERT_TRUE(fs::exists(output_lef)) << output_lef; + EXPECT_GT(fs::file_size(output_lef), 0); + + const std::string lef_text = ista::test::readFile(output_lef); + EXPECT_NE(lef_text.find("MACRO gcd"), std::string::npos); + EXPECT_NE(lef_text.find("CLASS BLOCK"), std::string::npos); + EXPECT_NE(lef_text.find("PIN clk"), std::string::npos); +} + +TEST(Ics55GcdExportTest, prepare_timing_runtime_keeps_clk_vertex_available) { + if (!ista::test::ics55GcdCaseAvailable()) { + GTEST_SKIP() << "ICS55 gcd harden case or PDK is not available"; + } + + const fs::path output_root = ista::test::ics55GcdRuntimeOutputDir(); + auto* timing_engine = ista::test::prepareIcs55GcdTimingRuntime(output_root); + ASSERT_NE(timing_engine, nullptr); + + auto* clk_port = timing_engine->get_ista()->get_netlist()->findPort("clk"); + ASSERT_NE(clk_port, nullptr); + EXPECT_NE(timing_engine->get_ista()->findVertex(clk_port), nullptr); +} + +TEST(Ics55GcdExportTest, write_timing_model_exports_liberty_from_runtime) { + if (!ista::test::ics55GcdCaseAvailable()) { + GTEST_SKIP() << "ICS55 gcd harden case or PDK is not available"; + } + + const fs::path output_root = + ista::test::ics55GcdRuntimeOutputDir() / "python_interface"; + auto* timing_engine = ista::test::prepareIcs55GcdTimingRuntime(output_root); + ASSERT_NE(timing_engine, nullptr); + + const fs::path output_lib = output_root / "gcd.python.max.lib"; + std::error_code ec; + fs::remove(output_lib, ec); + + EXPECT_TRUE(python_interface::writeTimingModel(output_lib.string(), "max")); + ASSERT_TRUE(fs::exists(output_lib)) << output_lib; + EXPECT_GT(fs::file_size(output_lib), 0); + + const std::string liberty_text = ista::test::readFile(output_lib); + EXPECT_NE(liberty_text.find("library (gcd)"), std::string::npos); + EXPECT_NE(liberty_text.find("cell (gcd)"), std::string::npos); + EXPECT_NE(liberty_text.find("related_pin : \"clk\""), + std::string::npos); + EXPECT_NE(liberty_text.find("timing_type : setup_rising"), + std::string::npos); + EXPECT_NE(liberty_text.find("timing_type : hold_rising"), + std::string::npos); +} +} // namespace diff --git a/src/platform/data_manager/idm.cpp b/src/platform/data_manager/idm.cpp index 1bb8bbad1..528ea478b 100644 --- a/src/platform/data_manager/idm.cpp +++ b/src/platform/data_manager/idm.cpp @@ -61,6 +61,18 @@ bool DataManager::init(string config_path) return true; } +void DataManager::reset() +{ + delete _idb_builder; + _idb_builder = nullptr; + + _idb_def_service = nullptr; + _idb_lef_service = nullptr; + _design = nullptr; + _layout = nullptr; + _config = DataConfig(); +} + bool DataManager::readLef(string config_path) { if (_idb_builder == nullptr) { diff --git a/src/platform/data_manager/idm.h b/src/platform/data_manager/idm.h index c05f83cf5..c57064585 100644 --- a/src/platform/data_manager/idm.h +++ b/src/platform/data_manager/idm.h @@ -84,6 +84,7 @@ class DataManager //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// iDB init bool init(string config_path); + void reset(); bool readLef(string config_path); bool readLef(vector lef_paths, bool b_techlef = false); bool readDef(string path);