Get Started with HeteroSTA
Introduction
HeteroSTA is a high-performance, GPU-accelerated static timing analysis (STA) engine designed for both standard verification and integration into custom EDA tools. This guide will walk you through the core concepts of a standard STA workflow, explaining the different API choices available at each step.
We will cover two primary usage models:
- Standard Sign-off STA: A complete workflow for analyzing a design from Verilog and SPEF files.
- Integration with an Optimization Engine: A workflow for using HeteroSTA to guide a tool like a placer by providing raw timing data. Of course. Here is the updated section with the added note on how to obtain a license.
Prerequisite: License Initialization
Before you begin any STA workflow, it is mandatory to initialize and validate the HeteroSTA license. This must be the very first API call you make. If the license is not successfully initialized, the subsequent call to heterosta_new() will fail by returning NULL, preventing any further interaction with the library.
You can obtain a license key by following the instructions in our getting started page.
- API:
heterosta_init_license()
Example:
#include "heterosta.h"
#include <stdio.h>
#include <stdbool.h>
// The license key must be provided first.
const char* lic = "lic:heterosta:your-license-key-string-here";
bool license_ok = heterosta_init_license(lic);
if (!license_ok) {
fprintf(stderr, "Error: Failed to initialize HeteroSTA license.\n");
return 1; // Cannot proceed
}
// Now it is safe to proceed with creating the environment.
STAHoldings* sta = heterosta_new();
// ...The Standard STA Workflow
Any timing analysis follows a logical sequence of operations. This section breaks down the essential steps and explains the different APIs HeteroSTA provides for each, allowing you to choose the approach that best fits your application.
Step 1: Initialize the Environment
Every session begins with creating an STAHoldings environment. This object serves as the central context for all STA operations. It is also best practice to initialize the logger to receive feedback from the engine.
- APIs:
heterosta_new(),heterosta_init_logger(),heterosta_free()
Example:
#include "heterosta.h"
// Define a C-compatible logger callback.
extern "C" void cpp_log_callback(uint8_t level, const char* message) {
const char* level_str;
switch (level) {
case 1: level_str = "ERROR"; break;
case 2: level_str = "WARN "; break;
case 3: level_str = "INFO "; break;
case 4: level_str = "DEBUG"; break;
case 5: level_str = "TRACE"; break;
default: level_str = "UNKNW"; break;
}
if (level <= 2) { // ERROR and WARN to stderr
std::cerr << "[" << level_str << "] " << message << std::endl;
} else { // INFO, DEBUG, TRACE to stdout
std::cout << "[" << level_str << "] " << message << std::endl;
}
}
// Create and initialize the STA environment.
STAHoldings* sta = heterosta_new();
heterosta_init_logger(cpp_log_callback);
// ... perform all analysis ...
// Free the environment at the end.
heterosta_free(sta);Step 2: Select a Delay Model
You must choose a delay calculator, which determines the accuracy and performance of the analysis. This choice is made once per session. The Arnoldi calculator is generally recommended for its balance of accuracy and speed.
- APIs:
heterosta_set_delay_calculator_elmore(),heterosta_set_delay_calculator_arnoldi(), etc.
Example:
// Select the Arnoldi delay calculator for high accuracy.
heterosta_set_delay_calculator_arnoldi(sta);Step 3: Load Timing Libraries
Before the netlist can be understood, HeteroSTA needs the characterization data for the standard cells, which is provided in Liberty (.lib) files. You must load libraries for both EARLY (min/hold) and LATE (max/setup) timing corners.
- APIs:
heterosta_read_liberty()(for single files),heterosta_read_libertys()(for multiple files in parallel)
Example:
// Load separate library files for both corners.
heterosta_read_liberty(sta, EARLY, "fast.lib");
heterosta_read_liberty(sta, LATE, "slow.lib");Step 4: Load the Design
Provide the circuit's logical structure and physical parasitics.
Loading the Netlist
-
Method A: From a Verilog File This is the standard method for sign-off STA. It handles file parsing and database population in one call.
- API:
heterosta_read_netlist() - Example:
heterosta_read_netlist(sta, "design.v", "top_module_name");
- API:
-
Method B: From an In-Memory Database This method is for integration with tools like placers that already have a netlist in memory. It provides fine-grained control over pin and cell IDs.
- API:
heterosta_set_netlistdb() - Example (C++ from the demo):
// 1. Parse Verilog into custom C++ database objects. db::FlatPlaceDB flatdb = create_from_custom_data(); // 2. Build the NetlistDB object. NetlistDB* netlistdb = flatdb.build_netlistdb(false); // 3. Set it in the STA environment. heterosta_set_netlistdb(sta, netlistdb);
- API:
-
A detailed demonstration of building a
NetlistDBfrom scratch is available in the Netlist Loading Demonstration.
Loading RC Parasitics
To accurately model signal propagation time, the delay calculator needs the resistance (R) and capacitance for each net.
-
Method A: From a SPEF File This is the standard approach for accurate, post-layout STA, reading parasitics from a SPEF file.
- API:
heterosta_read_spef() - Example:
heterosta_read_spef(sta, "design.spef");
- API:
-
Method B: Estimation from Placement Used during physical design when a SPEF is unavailable. It estimates parasitics from pin coordinates.
- API:
heterosta_extract_rc_from_placement() - Example:
heterosta_extract_rc_from_placement(sta, xs, ys, 0.10, 0.10, 0.05, 0.05, use_cuda);
- API:
-
Method C: From an In-Memory Database Analogous to
heterosta_set_netlistdb, a planned future feature will allow for setting RC parasitics directly from an in-memory data structure, bypassing the need for intermediate SPEF files.
Step 5: Prepare the Timing Graph
After all design data is loaded, it must be finalized into a performance-optimized format and used to construct the timing graph. This is a mandatory step before analysis can proceed.
- APIs:
heterosta_flatten_all(),heterosta_build_graph()
Example:
// Finalize the loaded data. This is a one-way operation.
heterosta_flatten_all(sta);
// Construct the internal timing graph for analysis.
heterosta_build_graph(sta);Step 6: Apply Constraints and Run Analysis
With the graph built, apply timing constraints from an SDC file and run the core analysis functions. The sequence of these calls is critical.
- APIs:
heterosta_read_sdc(): Loads constraints like clocks and input/output delays.heterosta_update_delay(): Calculates delays for all cell and net arcs. Must be called beforeupdate_arrivals.heterosta_update_arrivals(): Propagates arrival times through the graph to determine slack.
Example:
// Apply timing constraints.
heterosta_read_sdc(sta, "design.sdc");
// Run the core STA calculations in order.
heterosta_update_delay(sta, false);
heterosta_update_arrivals(sta, false);Step 7: Retrieve Timing Results
Finally, retrieve the results as either human-readable reports or as raw data arrays for other tools.
Method A: Formatted Text Reports
This method generates files summarizing timing, including WNS/TNS and critical path reports.
- APIs:
heterosta_report_wns_tns(),heterosta_dump_paths_setup_to_file()
Example:
#include <stdio.h>
float wns, tns;
heterosta_report_wns_tns(sta, &wns, &tns, false);
printf("WNS: %.3f, TNS: %.3f\n", wns, tns);Method B: Raw Data Arrays
This method provides direct access to the raw slack values for every pin, ideal for guiding optimization engines.
- API:
heterosta_report_slacks_at_max()
Example:
#include <stdlib.h>
/* Assume 'num_pins' is known. */
float (*slacks)[2] = malloc(num_pins * sizeof(float[2]));
heterosta_report_slacks_at_max(sta, slacks, false);
/* Use the raw slack data, e.g., for pin 5 */
float pin_5_rise_slack = slacks[5][0];
free(slacks);Use Case 1: Standard Sign-off STA Flow
Goal: To run a complete STA on a design described by Verilog and SPEF files and generate a timing report.
Workflow: This example combines the "Method A" choices from the conceptual steps into a single, linear program.
Example:
#include "heterosta.h"
#include <stdio.h>
int main() {
// 1. Initialize STA Environment
const char* lic = "lic:heterosta:your-license-key-string-here";
bool license_ok = heterosta_init_license(lic);
heterosta_init_logger(my_logger);
STAHoldings* sta = heterosta_new();
heterosta_set_delay_calculator_arnoldi(sta); // Set delay model
// 2. Load Libraries
heterosta_read_liberty(sta, EARLY, "early.lib");
heterosta_read_liberty(sta, LATE, "late.lib");
// 3. Load Design
heterosta_read_netlist(sta, "design.v", "top_module");
heterosta_read_spef(sta, "design.spef");
// 4. Prepare Graph
heterosta_flatten_all(sta);
heterosta_build_graph(sta);
// 5. Apply Constraints & Run Analysis
heterosta_read_sdc(sta, "design.sdc");
heterosta_update_delay(sta, false);
heterosta_update_arrivals(sta, false);
// 6. Report Results
float wns, tns;
heterosta_report_wns_tns(sta, &wns, &tns, false);
printf("WNS: %.3f, TNS: %.3f\n", wns, tns);
heterosta_dump_paths_setup_to_file(sta, 100, 5, "report.txt", false);
// 7. Clean up
heterosta_free(sta);
return 0;
}Use Case 2: Integration with an Optimization Engine
Goal: To use HeteroSTA's timing capabilities to guide an external tool like a placer.
Workflow: This example uses the "Method B" choices (in-memory netlist, estimated parasitics, raw slack data) inside a conceptual optimization loop.
Example:
// 1. Initialize and Configure STA Environment
const char* lic = "lic:heterosta:your-license-key-string-here";
bool license_ok = heterosta_init_license(lic);
heterosta_init_logger(my_logger);
STAHoldings* sta = heterosta_new();
heterosta_set_delay_calculator_arnoldi(sta); // Set delay model
heterosta_read_liberty(sta, 0, "early.lib");
heterosta_read_liberty(sta, 1, "late.lib");
// 2. Build and Load Custom Netlist (using the demo's C++ helpers)
db::FlatPlaceDB flatdb = create_from_placer_db();
NetlistDB* netlistdb = flatdb.build_netlistdb(false);
heterosta_set_netlistdb(sta, netlistdb);
// 3. Prepare Graph and Load Constraints
heterosta_flatten_all(sta);
heterosta_build_graph(sta);
heterosta_read_sdc(sta, "design.sdc");
// 4. Enter Optimization Loop
for (int i = 0; i < NUM_ITERATIONS; ++i) {
// 4.1 Get current pin coordinates from placer
float* pin_coords_x = my_placer->get_x_coords();
float* pin_coords_y = my_placer->get_y_coords();
// 4.2 Estimate Parasitics from placement
heterosta_extract_rc_from_placement(sta, pin_coords_x, pin_coords_y,
0.10, 0.10, 0.05, 0.05, false);
// 4.3 Run STA
heterosta_update_delay(sta, false);
heterosta_update_arrivals(sta, false);
// 4.4 Get Raw Slacks
float (*slacks)[2] = malloc(num_pins * sizeof(float[2]));
heterosta_report_slacks_at_max(sta, slacks, false);
// 4.5 Placer uses the 'slacks' array to update its solution
my_placer->update_placement_with_slacks(slacks);
free(slacks);
}
// 5. Clean up
heterosta_free(sta);Next Steps
For detailed information on every function, including all parameters and data structures, please refer to the complete API Reference document.