Get Started with HeteroSTA3D

Introduction

HeteroSTA3D performs static timing analysis for face-to-face hybrid-bonded 3D ICs whose dies may use different process technologies.

In-memory NetlistDB input makes HeteroSTA3D easier to embed directly in 3D EDA tools. Applications can transfer an existing netlist, query topology, and run incremental cell-sizing and all-corner timing workflows without a file-only handoff.

HeteroSTA3D is built on top of HeteroSTA. The two runtime libraries and their headers are released and tested as one unit: use libheterosta.so, libheterosta3d.so, heterosta.h, and heterosta3d.h from the same archive. Do not replace only the bundled HeteroSTA library or header unless that exact combination is explicitly listed as compatible.

Requirements

  • Linux x86-64 with glibc 2.34 or newer
  • CUDA Toolkit/runtime 12.8
  • NVIDIA Linux driver 580.65.06 or newer
  • For GPU mode: an NVIDIA GPU with compute capability 8.0, 8.6, 8.9, 9.0, or 12.0 (sm_80, sm_86, sm_89, sm_90, or sm_120)
  • OpenSSL 3 and jansson runtime libraries

This guide shows the required API sequence for a 3D STA run.

Prerequisite: License Initialization

Before you begin any 3D STA workflow, you must initialize and validate both the HeteroSTA and HeteroSTA3D licenses. This must be the very first API call. If either license is not successfully initialized, the subsequent call to heterosta3d_new() will fail by returning NULL, preventing any further interaction with the library.

An existing valid, unexpired v1.x HeteroSTA3D license can continue to be used with v2.0. The HeteroSTA and HeteroSTA3D licenses are validated independently; both must be valid and unexpired.

You can obtain licenses by following the instructions on our getting started page.

  • API: heterosta3d_init_license()

Store licenses outside your program

Set both licenses in the process environment instead of embedding them in the program:

export HeteroSTA_Lic="your-heterosta-license-string"
export HeteroSTA3D_Lic="your-heterosta3d-license-string"

Passing NULL lets the API read these variables. The following C++ example checks both licenses:

#include <iostream>
 
if (!heterosta3d_init_license(nullptr, nullptr)) {
    std::cerr << "Failed to initialize both licenses." << std::endl;
    return 1;
}

The Standard 3D STA Workflow

Any 3D timing analysis follows a logical sequence of operations. This section breaks down the essential steps based on the example in run_cpu.cpp, providing a clear and concise workflow.

Step 1: Initialize the Environment

Create one Heterosta3D context and release it when analysis is complete.

  • APIs: heterosta3d_init_license(), heterosta3d_new(), heterosta3d_free()

Example:

#include "heterosta3d.h"
#include <iostream>
 
if (!heterosta3d_init_license(nullptr, nullptr)) {
    std::cerr << "Failed to initialize licenses." << std::endl;
    return 1;
}
 
// Create and initialize the 3D STA environment.
Heterosta3D* sta = heterosta3d_new();
if (!sta) {
    std::cerr << "Failed to create Heterosta3D instance." << std::endl;
    return 1;
}
 
// ... perform all analysis ...
 
// Free the environment at the end.
heterosta3d_free(sta);

Step 2: Create Liberty Sets

Register the standard-cell Liberty paths for both dies. Each named set needs an EARLY (min/hold) and a LATE (max/setup) entry. This API records the paths; the files are parsed when a delay corner is created.

  • API: heterosta3d_create_liberty_set_batch()

Example:

// Setup liberty sets for 4 combinations: top/btm × ss/ff = 2×2
// Each liberty set needs both Early and Late timing corners
const char *top_early_ss[] = {"simple_top_Early_ss.lib"};
const char *top_late_ss[] = {"simple_top_Late_ss.lib"};
const char *top_early_ff[] = {"simple_top_Early_ff.lib"};
const char *top_late_ff[] = {"simple_top_Late_ff.lib"};
const char *btm_early_ss[] = {"simple_btm_Early_ss.lib"};
const char *btm_late_ss[] = {"simple_btm_Late_ss.lib"};
const char *btm_early_ff[] = {"simple_btm_Early_ff.lib"};
const char *btm_late_ff[] = {"simple_btm_Late_ff.lib"};
 
bool ok = true;
 
// Create liberty set "top_ss"
ok &= heterosta3d_create_liberty_set_batch(sta, EARLY, "top_ss", top_early_ss, 1);
ok &= heterosta3d_create_liberty_set_batch(sta, LATE, "top_ss", top_late_ss, 1);
 
// Create liberty set "top_ff"
ok &= heterosta3d_create_liberty_set_batch(sta, EARLY, "top_ff", top_early_ff, 1);
ok &= heterosta3d_create_liberty_set_batch(sta, LATE, "top_ff", top_late_ff, 1);
 
// Create liberty set "btm_ss"
ok &= heterosta3d_create_liberty_set_batch(sta, EARLY, "btm_ss", btm_early_ss, 1);
ok &= heterosta3d_create_liberty_set_batch(sta, LATE, "btm_ss", btm_late_ss, 1);
 
// Create liberty set "btm_ff"
ok &= heterosta3d_create_liberty_set_batch(sta, EARLY, "btm_ff", btm_early_ff, 1);
ok &= heterosta3d_create_liberty_set_batch(sta, LATE, "btm_ff", btm_late_ff, 1);

Step 3: Create Delay Corners

A delay corner combines one top-die Liberty set and one bottom-die Liberty set, parses their files, and assigns the corner to a CPU or GPU device. You can create multiple combinations such as ss_ss, ss_ff, ff_ss, and ff_ff.

  • API: heterosta3d_create_delay_corner()

Example:

// Create 4 delay corners: top/btm × ss/ff = 2×2
const char *corner_names[] = {"ss_ss", "ss_ff", "ff_ss", "ff_ff"};
const char *top_sets[] = {"top_ss", "top_ss", "top_ff", "top_ff"};
const char *btm_sets[] = {"btm_ss", "btm_ff", "btm_ss", "btm_ff"};
 
for (int i = 0; i < 4; ++i) {
    // Use HETEROSTA3D_CPU_DEVICE_ID for CPU mode, or 0, 1, ... for GPU devices
    ok &= heterosta3d_create_delay_corner(sta, corner_names[i], top_sets[i], btm_sets[i], HETEROSTA3D_CPU_DEVICE_ID);
}

Step 4: Load the Design

Provide the circuit's logical structure from a Verilog netlist file.

  • API: heterosta3d_read_netlist()

Important Note on Cell Naming:

Cell names in the Verilog netlist must carry _top or _bottom suffix to indicate die location. For example:

  • NAND2_X1_top - indicates this cell is on the top die
  • INV_X1_bottom - indicates this cell is on the bottom die

Example:

// Read netlist
ok &= heterosta3d_read_netlist(sta, "simple.v");
if (!ok) {
    std::cerr << "Failed to read netlist" << std::endl;
    heterosta3d_free(sta);
    return 1;
}

Step 5: Prepare the Timing Graph

After loading the netlist, flatten it and build the timing graph. Both calls are required before timing analysis.

  • APIs: heterosta3d_flatten_all(), heterosta3d_build_graph()

Example:

// Finalize the loaded data. This is a one-way operation.
heterosta3d_flatten_all(sta);
 
// Build the timing graph for analysis.
heterosta3d_build_graph(sta);

Step 6: Apply Constraints

With the graph built, apply timing constraints from an SDC file for each delay corner.

  • API: heterosta3d_read_sdc()

Example:

// Read SDC for all corners
for (int i = 0; i < 4; ++i) {
    heterosta3d_read_sdc(sta, "simple.sdc", corner_names[i]);
}

Step 7: Extract 3D RC Parasitics

To model signal propagation time in 3D ICs, the delay calculator needs the resistance (R) and capacitance (C) for each net, including the vertical connections through HBTs. This function extracts RC parasitics from 3D placement data.

  • API: heterosta3d_extract_rc_from_placement()

Key Parameters:

  • pos_x, pos_y: Arrays of pin coordinates (indexed by internal pin order)
  • hbt_x, hbt_y: Arrays of HBT (Hybrid Bonding Terminal) coordinates per net
  • unit_cap_x_top/y_top: Unit capacitance for top die (fF)
  • unit_res_x_top/y_top: Unit resistance for top die (kΩ)
  • unit_cap_x_btm/y_btm: Unit capacitance for bottom die (fF)
  • unit_res_x_btm/y_btm: Unit resistance for bottom die (kΩ)
  • hbt_r: Vertical link resistance (kΩ)
  • hbt_c: Vertical link capacitance (fF)
  • flute_accuracy: Integer accuracy setting for RC extraction

Memory Requirements:

  • For GPU corners: pos_x, pos_y, hbt_x, hbt_y must be on GPU memory
  • For CPU corners: pos_x, pos_y, hbt_x, hbt_y must be on host memory

Example:

// Prepare placement data
std::vector<float> pos_x{500.f, 600.f, 700.f, ...};  // Pin X coordinates
std::vector<float> pos_y{500.f, 600.f, 700.f, ...};  // Pin Y coordinates
std::vector<float> hbt_x{2.f, 12.f, 22.f, ...};     // HBT X coordinates per net
std::vector<float> hbt_y{2.f, 2.f, 2.f, ...};        // HBT Y coordinates per net
 
float unit_cap_x_top = 0.002f, unit_cap_y_top = 0.002f;
float unit_res_x_top = 0.0005f, unit_res_y_top = 0.0005f;
float unit_cap_x_btm = 0.002f, unit_cap_y_btm = 0.002f;
float unit_res_x_btm = 0.0005f, unit_res_y_btm = 0.0005f;
float hbt_r = 0.003f, hbt_c = 0.6f;
 
// Extract RC for all corners
for (int i = 0; i < 4; ++i) {
    heterosta3d_extract_rc_from_placement(
        sta, pos_x.data(), pos_y.data(), hbt_x.data(), hbt_y.data(),
        unit_cap_x_top, unit_cap_y_top, unit_res_x_top, unit_res_y_top,
        unit_cap_x_btm, unit_cap_y_btm, unit_res_x_btm, unit_res_y_btm,
        hbt_r, hbt_c, 4, corner_names[i]);
}

Step 8: Run Timing Analysis

With the graph built and parasitics extracted, run the core analysis functions. The sequence of these calls is critical.

  • APIs:
    • heterosta3d_update_delay(): Calculates delays for all cell and net arcs. Must be called before update_arrivals.
    • heterosta3d_update_arrivals(): Propagates arrival times through the graph to determine slack.

Example:

// Run the core STA calculations in order for all corners.
for (int i = 0; i < 4; ++i) {
    heterosta3d_update_delay(sta, corner_names[i]);
    heterosta3d_update_arrivals(sta, corner_names[i]);
}

Step 9: Retrieve Timing Results

Finally, retrieve the results as either summary metrics (WNS/TNS) or detailed slack arrays.

  • APIs:
    • heterosta3d_report_wns_tns_max(): Reports setup WNS/TNS
    • heterosta3d_report_wns_tns_min(): Reports hold WNS/TNS
    • heterosta3d_report_slacks_at_max(): Gets setup slack array
    • heterosta3d_report_slacks_at_min(): Gets hold slack array
    • heterosta3d_dump_paths_max_to_file(): Exports setup path report
    • heterosta3d_dump_paths_min_to_file(): Exports hold path report

Example:

// Report results for all corners
const uintptr_t num_pins = heterosta3d_get_num_of_pins(sta);
std::vector<float> slack(num_pins * 2);
float (*slack_ptr)[2] = reinterpret_cast<float (*)[2]>(slack.data());
 
for (int i = 0; i < 4; ++i) {
    float wns_max, tns_max, wns_min, tns_min;
    heterosta3d_report_wns_tns_max(sta, &wns_max, &tns_max, corner_names[i]);
    heterosta3d_report_wns_tns_min(sta, &wns_min, &tns_min, corner_names[i]);
    
    heterosta3d_report_slacks_at_max(sta, slack_ptr, corner_names[i]);
    // Process slack data...
    
    std::printf("Corner %s:\n", corner_names[i]);
    std::printf("  Setup: WNS=%.3f, TNS=%.3f\n", wns_max, tns_max);
    std::printf("  Hold:  WNS=%.3f, TNS=%.3f\n", wns_min, tns_min);
    
    // Dump timing paths to files
    char paths_max_file[64], paths_min_file[64];
    std::sprintf(paths_max_file, "paths_max_%s.rpt", corner_names[i]);
    std::sprintf(paths_min_file, "paths_min_%s.rpt", corner_names[i]);
    heterosta3d_dump_paths_max_to_file(sta, 10, 1, paths_max_file, corner_names[i]);
    heterosta3d_dump_paths_min_to_file(sta, 10, 1, paths_min_file, corner_names[i]);
}

Next Steps

For detailed information on every function, including all parameters and data structures, please refer to the complete API Reference document.