[HW][AIG] Add InstancePath CAPI and use native structures for AIG longest path analysis (#8760)

This commit refactors the AIG longest path analysis C API to use native C structures instead of JSON strings, providing better performance and type safety.

The API changes replace `aigLongestPathCollectionGetPath` returning JSON with `aigLongestPathCollectionGetDataflowPath` returning native objects. New opaque handle types are added including `AIGLongestPathObject`, `AIGLongestPathHistory`, and `AIGLongestPathDataflowPath`. Comprehensive APIs are provided for accessing path data, history, and object properties. 

InstancePath C API support is introduced in `circt-c/Support/InstanceGraph.h`. Currently `InstancePathCache` itself is not provided, as the use of LongestPathAnalysis is read-only and there is no need to mutate/construct InstancePath. Unfortunately due to that testing of CAPI of InstancePath got a bit tricky. For now AIGLongestPathAnalysis is used to produce InstancePath in CAPI.

The Python binding updates refactor Object, DataflowPath, and LongestPathHistory classes to use the native C API. JSON parsing dependencies and from_json_string() methods are removed. Proper property accessors using the new C API are added while maintaining backward compatibility for existing Python interfaces. So the existing integration tests cover most of the APIs. 

Testing updates include comprehensive coverage in the existing C API tests in `test/CAPI/aig.c`. A new `test/CAPI/support.c` is added for InstancePath API testing. Python integration tests are updated to work with the new API.

This change improves performance by eliminating JSON serialization/deserialization overhead and provides a more robust, type-safe interface for accessing longest path analysis results.
This commit is contained in:
Hideto Ueno 2025-07-22 11:30:43 -07:00 committed by GitHub
parent 9088c29f4b
commit 640daf0c92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 673 additions and 166 deletions

View File

@ -9,6 +9,7 @@
#ifndef CIRCT_C_DIALECT_AIG_H
#define CIRCT_C_DIALECT_AIG_H
#include "circt-c/Support/InstanceGraph.h"
#include "mlir-c/IR.h"
#ifdef __cplusplus
@ -28,6 +29,15 @@ MLIR_CAPI_EXPORTED void registerAIGPasses(void);
// LongestPathAnalysis
//===----------------------------------------------------------------------===//
// Opaque handle to LongestPathObject
DEFINE_C_API_STRUCT(AIGLongestPathObject, void);
// Opaque handle to LongestPathHistory
DEFINE_C_API_STRUCT(AIGLongestPathHistory, void);
// Opaque handle to LongestPathDataflowPath
DEFINE_C_API_STRUCT(AIGLongestPathDataflowPath, void);
// Opaque handle to LongestPathAnalysis
DEFINE_C_API_STRUCT(AIGLongestPathAnalysis, void);
@ -64,9 +74,57 @@ aigLongestPathCollectionDestroy(AIGLongestPathCollection collection);
MLIR_CAPI_EXPORTED size_t
aigLongestPathCollectionGetSize(AIGLongestPathCollection collection);
// Get a specific path from the collection as JSON
MLIR_CAPI_EXPORTED MlirStringRef aigLongestPathCollectionGetPath(
AIGLongestPathCollection collection, int pathIndex);
// Get a specific path from the collection as DataflowPath object
MLIR_CAPI_EXPORTED AIGLongestPathDataflowPath
aigLongestPathCollectionGetDataflowPath(AIGLongestPathCollection collection,
size_t pathIndex);
//===----------------------------------------------------------------------===//
// DataflowPath API
//===----------------------------------------------------------------------===//
MLIR_CAPI_EXPORTED int64_t
aigLongestPathDataflowPathGetDelay(AIGLongestPathDataflowPath dataflowPath);
MLIR_CAPI_EXPORTED AIGLongestPathObject
aigLongestPathDataflowPathGetFanIn(AIGLongestPathDataflowPath dataflowPath);
MLIR_CAPI_EXPORTED AIGLongestPathObject
aigLongestPathDataflowPathGetFanOut(AIGLongestPathDataflowPath dataflowPath);
MLIR_CAPI_EXPORTED AIGLongestPathHistory
aigLongestPathDataflowPathGetHistory(AIGLongestPathDataflowPath dataflowPath);
MLIR_CAPI_EXPORTED MlirOperation
aigLongestPathDataflowPathGetRoot(AIGLongestPathDataflowPath dataflowPath);
//===----------------------------------------------------------------------===//
// History API
//===----------------------------------------------------------------------===//
MLIR_CAPI_EXPORTED bool
aigLongestPathHistoryIsEmpty(AIGLongestPathHistory history);
MLIR_CAPI_EXPORTED void
aigLongestPathHistoryGetHead(AIGLongestPathHistory history,
AIGLongestPathObject *object, int64_t *delay,
MlirStringRef *comment);
MLIR_CAPI_EXPORTED AIGLongestPathHistory
aigLongestPathHistoryGetTail(AIGLongestPathHistory history);
//===----------------------------------------------------------------------===//
// Object API
//===----------------------------------------------------------------------===//
MLIR_CAPI_EXPORTED IgraphInstancePath
aigLongestPathObjectGetInstancePath(AIGLongestPathObject object);
MLIR_CAPI_EXPORTED MlirStringRef
aigLongestPathObjectName(AIGLongestPathObject object);
MLIR_CAPI_EXPORTED size_t
aigLongestPathObjectBitPos(AIGLongestPathObject object);
#ifdef __cplusplus
}

View File

@ -0,0 +1,37 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_C_SUPPORT_INSTANCEGRAPH_H
#define CIRCT_C_SUPPORT_INSTANCEGRAPH_H
#include "mlir-c/IR.h"
#ifdef __cplusplus
extern "C" {
#endif
struct IgraphInstancePath {
void *ptr;
size_t size;
};
typedef struct IgraphInstancePath IgraphInstancePath;
//===----------------------------------------------------------------------===//
// InstancePath API
//===----------------------------------------------------------------------===//
MLIR_CAPI_EXPORTED MlirOperation igraphInstancePathGet(IgraphInstancePath path,
size_t index);
MLIR_CAPI_EXPORTED size_t igraphInstancePathSize(IgraphInstancePath path);
#ifdef __cplusplus
}
#endif
#endif // CIRCT_C_SUPPORT_INSTANCEPATH_H

View File

@ -3,7 +3,7 @@
import circt
from circt.dialects import aig, hw
from circt.ir import Context, Location, Module, InsertionPoint, IntegerType
from circt.ir import Context, Location, Module, InsertionPoint, IntegerType, ArrayAttr
from circt.dialects.aig import LongestPathAnalysis, LongestPathCollection, DataflowPath
with Context() as ctx, Location.unknown():
@ -15,7 +15,7 @@ with Context() as ctx, Location.unknown():
i32 = IntegerType.get_signless(32)
# Create a simple hardware module with AIG operations
def build_module(module):
def build_child(module):
a, b = module.entry_block.arguments
result1 = aig.and_inv([a, b], [False, True])
@ -23,10 +23,29 @@ with Context() as ctx, Location.unknown():
hw.OutputOp([result1, result2])
hw.HWModuleOp(name="test_aig",
hw.HWModuleOp(name="test_child",
input_ports=[("a", i32), ("b", i32)],
output_ports=[("out1", i32), ("out2", i32)],
body_builder=build_module)
body_builder=build_child)
def build_top(module):
a, b = module.entry_block.arguments
out1, out2 = hw.instance(
[i32, i32],
"child",
"test_child",
[a, b],
["a", "b"],
["out1", "out2"],
parameters=ArrayAttr.get([]),
)
hw.OutputOp([out1, out2])
hw.HWModuleOp(name="test_aig",
input_ports=[("c", i32), ("d", i32)],
output_ports=[("out1", i32), ("out2", i32)],
body_builder=build_top)
# CHECK-LABEL: AIG dialect registration and basic operations successful!
print("AIG dialect registration and basic operations successful!")
# Test aig.and_inv operation
@ -47,6 +66,7 @@ with Context() as ctx, Location.unknown():
# CHECK-NEXT: 50th percentile delay: 1
# CHECK-NEXT: 90th percentile delay: 2
# CHECK-NEXT: 95th percentile delay: 2
# CHECK-NEXT: 99th percentile delay: 2
# CHECK-NEXT: 99.9th percentile delay: 2
collection.print_summary()
@ -56,11 +76,6 @@ with Context() as ctx, Location.unknown():
# CHECK-NEXT: index -1 delay: 1
print("index 1 delay:", collection[1].delay)
print("index -1 delay:", collection[-1].delay)
# CHECK-NEXT: collection.get_path(5) == collection[5]: True
print(
"collection.get_path(5) == collection[5]:",
DataflowPath.from_json_string(
collection.collection.get_path(5)) == collection[5])
# Check that len and get_size are the same
# CHECK-NEXT: 128 128
print(len(collection), collection.collection.get_size())
@ -84,6 +99,6 @@ with Context() as ctx, Location.unknown():
print("minus index slice:", len(collection[:-2]) == len(collection) - 2)
# Test framegraph emission.
# CHECK: top:test_aig;a[0] 0
# CHECK-NEXT: top:test_aig;out2[0] 2
# CHECK: top:test_aig;c[0] 0
# CHECK: top:test_aig;child:test_child;a[0] 2
print(collection.longest_path.to_flamegraph())

View File

@ -69,9 +69,66 @@ void circt::python::populateDialectAIGSubmodule(nb::module_ &m) {
})
.def("get_path",
[](AIGLongestPathCollection &self,
int pathIndex) -> std::string_view {
MlirStringRef pathRef =
aigLongestPathCollectionGetPath(self, pathIndex);
return std::string_view(pathRef.data, pathRef.length);
int pathIndex) -> AIGLongestPathDataflowPath {
return aigLongestPathCollectionGetDataflowPath(self, pathIndex);
});
nb::class_<AIGLongestPathDataflowPath>(m, "_LongestPathDataflowPath")
.def_prop_ro("delay",
[](AIGLongestPathDataflowPath &self) {
return aigLongestPathDataflowPathGetDelay(self);
})
.def_prop_ro("fan_in",
[](AIGLongestPathDataflowPath &self) {
return aigLongestPathDataflowPathGetFanIn(self);
})
.def_prop_ro("fan_out",
[](AIGLongestPathDataflowPath &self) {
return aigLongestPathDataflowPathGetFanOut(self);
})
.def_prop_ro("history",
[](AIGLongestPathDataflowPath &self) {
return aigLongestPathDataflowPathGetHistory(self);
})
.def_prop_ro("root", [](AIGLongestPathDataflowPath &self) {
return aigLongestPathDataflowPathGetRoot(self);
});
nb::class_<AIGLongestPathHistory>(m, "_LongestPathHistory")
.def_prop_ro("empty",
[](AIGLongestPathHistory &self) {
return aigLongestPathHistoryIsEmpty(self);
})
.def_prop_ro("head",
[](AIGLongestPathHistory &self) {
AIGLongestPathObject object;
int64_t delay;
MlirStringRef comment;
aigLongestPathHistoryGetHead(self, &object, &delay,
&comment);
return std::make_tuple(object, delay, comment);
})
.def_prop_ro("tail", [](AIGLongestPathHistory &self) {
return aigLongestPathHistoryGetTail(self);
});
nb::class_<AIGLongestPathObject>(m, "_LongestPathObject")
.def_prop_ro("instance_path",
[](AIGLongestPathObject &self) {
auto path = aigLongestPathObjectGetInstancePath(self);
if (!path.ptr)
return std::vector<MlirOperation>();
size_t size = igraphInstancePathSize(path);
std::vector<MlirOperation> result;
for (size_t i = 0; i < size; ++i)
result.push_back(igraphInstancePathGet(path, i));
return result;
})
.def_prop_ro("name",
[](AIGLongestPathObject &self) {
return aigLongestPathObjectName(self);
})
.def_prop_ro("bit_pos", [](AIGLongestPathObject &self) {
return aigLongestPathObjectBitPos(self);
});
}

View File

@ -53,6 +53,7 @@ set(PYTHON_BINDINGS_LINK_LIBS
CIRCTCAPIRtgTool
CIRCTCAPISeq
CIRCTCAPISV
CIRCTCAPISupport
CIRCTCAPIVerif
CIRCTCAPITransforms
CIRCTCAPISynthesis

View File

@ -2,11 +2,10 @@
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from . import aig
from . import aig, hw
from ._aig_ops_gen import *
from .._mlir_libs._circt._aig import _LongestPathAnalysis, _LongestPathCollection
from .._mlir_libs._circt._aig import _LongestPathAnalysis, _LongestPathCollection, _LongestPathDataflowPath, _LongestPathHistory, _LongestPathObject
import json
from dataclasses import dataclass
from typing import Any, Dict, List, Union
@ -16,7 +15,7 @@ from typing import Any, Dict, List, Union
@dataclass
class InstancePathElement:
class Instance:
"""
Represents a single element in a hierarchical instance path.
In hardware design, modules are instantiated hierarchically. This class
@ -26,15 +25,18 @@ class InstancePathElement:
instance_name: The name of this specific instance
module_name: The type/name of the module being instantiated
"""
_instance: hw.InstanceOp
instance_name: str
module_name: str
def __init__(self, instance: hw.InstanceOp):
self._instance = instance
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "InstancePathElement":
"""Create an InstancePathElement from a dictionary representation."""
return cls(instance_name=data["instance_name"],
module_name=data["module_name"])
@property
def instance_name(self) -> str:
return self._instance.attributes["instanceName"].value
@property
def module_name(self) -> str:
return self._instance.attributes["moduleName"].value
@dataclass
@ -50,9 +52,7 @@ class Object:
bit_pos: Bit position for multi-bit signals (0 for single-bit)
"""
instance_path: List[InstancePathElement]
name: str
bit_pos: int
_object: _LongestPathObject
# TODO: Associate with an MLIR value/op
@ -65,15 +65,25 @@ class Object:
for elem in self.instance_path)
return f"{path} {self.name}[{self.bit_pos}]"
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Object":
"""Create an Object from a dictionary representation."""
instance_path = [
InstancePathElement.from_dict(elem) for elem in data["instance_path"]
]
return cls(instance_path=instance_path,
name=data["name"],
bit_pos=data["bit_pos"])
def __repr__(self) -> str:
return f"Object({self.instance_path}, {self.name}, {self.bit_pos})"
@property
def instance_path(self) -> List[Instance]:
"""Get the hierarchical instance path to this object."""
operations = self._object.instance_path
return [Instance(op) for op in operations]
@property
def name(self) -> str:
"""Get the name of this signal/port."""
return self._object.name
@property
def bit_pos(self) -> int:
"""Get the bit position for multi-bit signals."""
return self._object.bit_pos
@dataclass
@ -103,34 +113,6 @@ class DebugPoint:
)
@dataclass
class OpenPath:
"""
Represents an open timing path with detailed history.
An open path represents a timing path that hasn't reached its final
destination yet. It contains the current fan-in point, accumulated delay,
and a history of debug points showing how the signal propagated.
Attributes:
fan_in: The input signal/object where this path segment begins
delay: Total accumulated delay for this path segment
history: Chronological list of debug points along the path
"""
fan_in: Object
delay: int
history: List[DebugPoint]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "OpenPath":
"""Create an OpenPath from a dictionary representation."""
history = [DebugPoint.from_dict(point) for point in data["history"]]
return cls(
fan_in=Object.from_dict(data["fan_in"]),
delay=data["delay"],
history=history,
)
@dataclass
class DataflowPath:
"""
@ -144,43 +126,32 @@ class DataflowPath:
root: The root module name for this analysis
"""
fan_out: Object # Output endpoint of the path
path: OpenPath # Detailed path information with history
root: str # Root module name
# ========================================================================
# Factory Methods for Object Creation
# ========================================================================
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "DataflowPath":
"""Create a DataflowPath from a dictionary representation."""
return cls(
fan_out=Object.from_dict(data["fan_out"]),
path=OpenPath.from_dict(data["path"]),
root=data["root"],
)
@classmethod
def from_json_string(cls, json_str: str) -> "DataflowPath":
"""Create a DataflowPath from a JSON string representation."""
data = json.loads(json_str)
return cls.from_dict(data)
_path: _LongestPathDataflowPath
@property
def delay(self) -> int:
"""Get the total delay of this path in timing units."""
return self.path.delay
return self._path.delay
@property
def fan_in(self) -> "DataflowPath":
def fan_in(self) -> Object:
"""Get the input signal/object where this path begins."""
return self.path.fan_in
return Object(self._path.fan_in)
@property
def fan_out(self) -> Object:
"""Get the output signal/object where this path ends."""
return Object(self._path.fan_out)
@property
def history(self) -> List[DebugPoint]:
"""Get the history of debug points along this path."""
return self.path.history
return [i for i in LongestPathHistory(self._path.history)]
@property
def root(self) -> str:
"""Get the root module name for this analysis."""
return self._path.root.attributes["sym_name"].value
# ========================================================================
# Visualization and Analysis Methods
@ -244,7 +215,7 @@ class DataflowPath:
# Add each level of the instance hierarchy
for elem in obj.instance_path:
parts.append(f"{elem.module_name}:{elem.instance_name}")
parts.append(f"{elem.instance_name}:{elem.module_name}")
# Add the signal name with bit position if applicable
signal_part = obj.name
@ -268,7 +239,6 @@ class LongestPathCollection:
Attributes:
collection: The underlying C++ collection object
length: Number of paths in the collection
cache: Cache for parsed DataflowPath objects to avoid re-parsing JSON
"""
def __init__(self, collection):
@ -279,7 +249,6 @@ class LongestPathCollection:
"""
self.collection = collection
self.length = self.collection.get_size()
self.cache = [None for _ in range(self.length)]
# ========================================================================
# Collection Interface Methods
@ -295,7 +264,6 @@ class LongestPathCollection:
"""
Get a specific path from the collection by index.
Supports both integer and slice indexing. Integer indices can be negative.
Results are cached to avoid expensive JSON parsing on repeated access.
Args:
index: Integer index or slice object to access paths
@ -305,7 +273,7 @@ class LongestPathCollection:
Raises:
IndexError: If index is out of range
"""
"""
if isinstance(index, slice):
return [self[i] for i in range(*index.indices(len(self)))]
@ -315,14 +283,7 @@ class LongestPathCollection:
if index < 0 or index >= self.length:
raise IndexError("Index out of range")
# Use cache to avoid expensive JSON parsing
if self.cache[index] is not None:
return self.cache[index]
# Parse JSON and cache the result
json_str = self.collection.get_path(index)
self.cache[index] = DataflowPath.from_json_string(json_str)
return self.cache[index]
return DataflowPath(self.collection.get_path(index))
# ========================================================================
# Analysis and Query Methods
@ -387,7 +348,9 @@ class LongestPathAnalysis:
"""
self.analysis = aig._LongestPathAnalysis(module, trace_debug_points)
def get_all_paths(self, module_name: str) -> LongestPathCollection:
def get_all_paths(self,
module_name: str,
elaborate_paths: bool = True) -> LongestPathCollection:
"""
Perform longest path analysis and return all timing paths.
This method analyzes the specified module and returns a collection
@ -397,4 +360,24 @@ class LongestPathAnalysis:
Returns:
LongestPathCollection containing all paths sorted by delay
"""
return LongestPathCollection(self.analysis.get_all_paths(module_name, True))
return LongestPathCollection(
self.analysis.get_all_paths(module_name, elaborate_paths))
@dataclass
class LongestPathHistory:
"""
Represents the history of a timing path, including intermediate debug points.
This class provides a Python wrapper around the C++ LongestPathHistory,
enabling iteration over the path's history and access to debug points.
Attributes:
history: The underlying C++ history object
"""
history: _LongestPathHistory
def __iter__(self):
"""Iterate over the debug points in the history."""
while not self.history.empty:
object, delay, comment = self.history.head
yield DebugPoint(Object(object), delay, comment)
self.history = self.history.tail

View File

@ -4,5 +4,6 @@ add_subdirectory(ExportVerilog)
add_subdirectory(Dialect)
add_subdirectory(Firtool)
add_subdirectory(RtgTool)
add_subdirectory(Support)
add_subdirectory(Synthesis)
add_subdirectory(Transforms)

View File

@ -7,9 +7,12 @@
//===----------------------------------------------------------------------===//
#include "circt-c/Dialect/AIG.h"
#include "circt-c/Support/InstanceGraph.h"
#include "circt/Dialect/AIG/AIGDialect.h"
#include "circt/Dialect/AIG/AIGPasses.h"
#include "circt/Dialect/AIG/Analysis/LongestPathAnalysis.h"
#include "circt/Support/InstanceGraph.h"
#include "circt/Support/InstanceGraphInterface.h"
#include "mlir-c/BuiltinAttributes.h"
#include "mlir-c/IR.h"
#include "mlir-c/Support.h"
@ -17,7 +20,11 @@
#include "mlir/CAPI/Registration.h"
#include "mlir/CAPI/Support.h"
#include "mlir/Pass/AnalysisManager.h"
#include "llvm/ADT/ImmutableList.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/Support/JSON.h"
#include <memory>
#include <tuple>
using namespace circt;
using namespace circt::aig;
@ -34,6 +41,34 @@ struct LongestPathAnalysisWrapper {
DEFINE_C_API_PTR_METHODS(AIGLongestPathAnalysis, LongestPathAnalysisWrapper)
DEFINE_C_API_PTR_METHODS(AIGLongestPathCollection, LongestPathCollection)
DEFINE_C_API_PTR_METHODS(AIGLongestPathDataflowPath, DataflowPath)
DEFINE_C_API_PTR_METHODS(AIGLongestPathHistory,
llvm::ImmutableListImpl<DebugPoint>)
// AIGLongestPathObject is a pointer to either an Object or an OutputPort so we
// can not use DEFINE_C_API_PTR_METHODS.
llvm::PointerUnion<Object *, DataflowPath::OutputPort *>
unwrap(AIGLongestPathObject object) {
return llvm::PointerUnion<
Object *, DataflowPath::OutputPort *>::getFromOpaqueValue(object.ptr);
}
AIGLongestPathObject
wrap(llvm::PointerUnion<Object *, DataflowPath::OutputPort *> object) {
return AIGLongestPathObject{object.getOpaqueValue()};
}
AIGLongestPathObject wrap(const Object *object) {
auto ptr = llvm::PointerUnion<Object *, DataflowPath::OutputPort *>(
const_cast<Object *>(object));
return wrap(ptr);
}
AIGLongestPathObject wrap(const DataflowPath::OutputPort *object) {
auto ptr = llvm::PointerUnion<Object *, DataflowPath::OutputPort *>(
const_cast<DataflowPath::OutputPort *>(object));
return wrap(ptr);
}
//===----------------------------------------------------------------------===//
// LongestPathAnalysis C API
@ -92,30 +127,116 @@ size_t aigLongestPathCollectionGetSize(AIGLongestPathCollection collection) {
return wrapper->paths.size();
}
MlirStringRef
aigLongestPathCollectionGetPath(AIGLongestPathCollection collection,
int pathIndex) {
// Get a specific path from the collection as DataflowPath object
AIGLongestPathDataflowPath
aigLongestPathCollectionGetDataflowPath(AIGLongestPathCollection collection,
size_t index) {
auto *wrapper = unwrap(collection);
// Check if pathIndex is valid
if (pathIndex < 0 || pathIndex >= static_cast<int>(wrapper->paths.size()))
return wrap(llvm::StringRef(""));
// Convert the specific path to JSON
// FIXME: Avoid converting to JSON and then back to string. Use native
// CAPI instead once data structure is stabilized.
llvm::json::Value pathJson = toJSON(wrapper->paths[pathIndex]);
std::string jsonStr;
llvm::raw_string_ostream os(jsonStr);
os << pathJson;
auto ctx = wrap(wrapper->getContext());
// Use MLIR StringAttr to manage the string lifetime.
// FIXME: This is safe but expensive. Consider manually managing the string
// lifetime.
MlirAttribute strAttr =
mlirStringAttrGet(ctx, mlirStringRefCreateFromCString(os.str().c_str()));
return mlirStringAttrGetValue(strAttr);
auto &path = wrapper->paths[index];
return wrap(&path);
}
//===----------------------------------------------------------------------===//
// DataflowPath
//===----------------------------------------------------------------------===//
int64_t aigLongestPathDataflowPathGetDelay(AIGLongestPathDataflowPath path) {
auto *wrapper = unwrap(path);
return wrapper->getDelay();
}
AIGLongestPathObject
aigLongestPathDataflowPathGetFanIn(AIGLongestPathDataflowPath path) {
auto *wrapper = unwrap(path);
auto &fanIn = wrapper->getFanIn();
return wrap(const_cast<Object *>(&fanIn));
}
AIGLongestPathObject
aigLongestPathDataflowPathGetFanOut(AIGLongestPathDataflowPath path) {
auto *wrapper = unwrap(path);
if (auto *object = std::get_if<Object>(&wrapper->getFanOut())) {
return wrap(object);
}
auto *ptr = std::get_if<DataflowPath::OutputPort>(&wrapper->getFanOut());
return wrap(ptr);
}
AIGLongestPathHistory
aigLongestPathDataflowPathGetHistory(AIGLongestPathDataflowPath path) {
auto *wrapper = unwrap(path);
return wrap(const_cast<llvm::ImmutableListImpl<DebugPoint> *>(
wrapper->getHistory().getInternalPointer()));
}
MlirOperation
aigLongestPathDataflowPathGetRoot(AIGLongestPathDataflowPath path) {
auto *wrapper = unwrap(path);
return wrap(wrapper->getRoot());
}
//===----------------------------------------------------------------------===//
// History
//===----------------------------------------------------------------------===//
bool aigLongestPathHistoryIsEmpty(AIGLongestPathHistory history) {
auto *wrapper = unwrap(history);
return llvm::ImmutableList<DebugPoint>(wrapper).isEmpty();
}
void aigLongestPathHistoryGetHead(AIGLongestPathHistory history,
AIGLongestPathObject *object, int64_t *delay,
MlirStringRef *comment) {
auto *wrapper = unwrap(history);
auto list = llvm::ImmutableList<DebugPoint>(wrapper);
auto &head = list.getHead();
*object = wrap(&head.object);
*delay = head.delay;
*comment = mlirStringRefCreate(head.comment.data(), head.comment.size());
}
AIGLongestPathHistory
aigLongestPathHistoryGetTail(AIGLongestPathHistory history) {
auto *wrapper = unwrap(history);
auto list = llvm::ImmutableList<DebugPoint>(wrapper);
auto *tail = list.getTail().getInternalPointer();
return wrap(const_cast<llvm::ImmutableListImpl<DebugPoint> *>(tail));
}
//===----------------------------------------------------------------------===//
// Object
//===----------------------------------------------------------------------===//
IgraphInstancePath
aigLongestPathObjectGetInstancePath(AIGLongestPathObject object) {
auto *ptr = dyn_cast<Object *>(unwrap(object));
if (ptr) {
IgraphInstancePath result;
result.ptr = const_cast<igraph::InstanceOpInterface *>(
ptr->instancePath.getPath().data());
result.size = ptr->instancePath.getPath().size();
return result;
}
// This is output port so the instance path is empty.
return {nullptr, 0};
}
MlirStringRef aigLongestPathObjectName(AIGLongestPathObject rawObject) {
auto ptr = unwrap(rawObject);
if (auto *object = dyn_cast<Object *>(ptr)) {
auto name = object->getName();
return mlirStringRefCreate(name.data(), name.size());
}
auto [module, resultNumber, _] = *dyn_cast<DataflowPath::OutputPort *>(ptr);
auto name = module.getOutputName(resultNumber);
return mlirStringRefCreate(name.data(), name.size());
}
size_t aigLongestPathObjectBitPos(AIGLongestPathObject rawObject) {
auto ptr = unwrap(rawObject);
if (auto *object = dyn_cast<Object *>(ptr))
return object->bitPos;
return std::get<2>(*dyn_cast<DataflowPath::OutputPort *>(ptr));
}

View File

@ -35,6 +35,7 @@ add_circt_public_c_api_library(CIRCTCAPIAIG
CIRCTAIG
CIRCTAIGAnalysis
CIRCTAIGTransforms
CIRCTCAPISupport
)
add_circt_public_c_api_library(CIRCTCAPIArc

View File

@ -0,0 +1,6 @@
add_circt_public_c_api_library(CIRCTCAPISupport
InstanceGraph.cpp
LINK_LIBS PUBLIC
MLIRCAPIIR
)

View File

@ -0,0 +1,49 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "circt-c/Support/InstanceGraph.h"
#include "circt/Support/InstanceGraph.h"
#include "mlir/CAPI/IR.h"
#include "mlir/CAPI/Support.h"
#include "mlir/CAPI/Utils.h"
#include <string>
using namespace circt;
//===----------------------------------------------------------------------===//
// C API Helpers
//===----------------------------------------------------------------------===//
ArrayRef<igraph::InstanceOpInterface> unwrap(IgraphInstancePath instancePath) {
return ArrayRef(
reinterpret_cast<igraph::InstanceOpInterface *>(instancePath.ptr),
instancePath.size);
}
IgraphInstancePath wrap(ArrayRef<igraph::InstanceOpInterface> instancePath) {
return IgraphInstancePath{
const_cast<igraph::InstanceOpInterface *>(instancePath.data()),
instancePath.size()};
}
//===----------------------------------------------------------------------===//
// InstancePath
//===----------------------------------------------------------------------===//
size_t igraphInstancePathSize(IgraphInstancePath instancePath) {
return unwrap(instancePath).size();
}
MlirOperation igraphInstancePathGet(IgraphInstancePath instancePath,
size_t index) {
assert(instancePath.ptr);
auto path = unwrap(instancePath);
Operation *operation = path[index];
return wrap(operation);
}

View File

@ -125,3 +125,19 @@ target_link_libraries(circt-capi-aig-test
CIRCTCAPIHW
CIRCTCAPISeq
)
add_llvm_executable(circt-capi-support-test
PARTIAL_SOURCES_INTENDED
support.c
)
llvm_update_compile_flags(circt-capi-support-test)
target_link_libraries(circt-capi-support-test
PRIVATE
MLIRCAPIIR
CIRCTCAPIAIG
CIRCTCAPIHW
CIRCTCAPISeq
CIRCTCAPISupport
)

View File

@ -42,14 +42,14 @@ void testLongestPathAnalysis(void) {
// clang-format off
const char *moduleStr =
"hw.module private @ch(in %c : !seq.clock, in %a: i1, out x: i1) {\n"
" %p = seq.compreg %q, %c : i1\n"
" %q = aig.and_inv %p, %p {sv.namehint = \"q\"}: i1\n"
" hw.output %p: i1\n"
"hw.module private @ch(in %c : !seq.clock, in %a: i2, out x: i2) {\n"
" %p = seq.compreg %q, %c : i2\n"
" %q = aig.and_inv %p, %p {sv.namehint = \"q\"}: i2\n"
" hw.output %p: i2\n"
"}\n"
"hw.module private @top(in %c : !seq.clock, in %a: i1) {\n"
" %0 = hw.instance \"i1\" @ch(c: %c: !seq.clock, a: %a: i1) -> (x: i1)\n"
" %1 = hw.instance \"i2\" @ch(c: %c: !seq.clock, a: %a: i1) -> (x: i1)\n"
"hw.module private @top(in %c : !seq.clock, in %a: i2) {\n"
" %0 = hw.instance \"i2\" @ch(c: %c: !seq.clock, a: %a: i2) -> (x: i2)\n"
" %1 = hw.instance \"i2\" @ch(c: %c: !seq.clock, a: %a: i2) -> (x: i2)\n"
"}\n";
// clang-format on
@ -71,19 +71,78 @@ void testLongestPathAnalysis(void) {
size_t pathCount = aigLongestPathCollectionGetSize(collection1);
printf("Path count with elaboration: %zu\n", pathCount);
// CHECK: Path count with elaboration: 2
// CHECK: Path count with elaboration: 4
pathCount = aigLongestPathCollectionGetSize(collection2);
printf("Path count without elaboration: %zu\n", pathCount);
// CHECK: Path count without elaboration: 1
// CHECK: Path count without elaboration: 2
// Test getting individual paths
// Test DataflowPath API
if (pathCount > 0) {
AIGLongestPathDataflowPath path =
aigLongestPathCollectionGetDataflowPath(collection1, 0);
// Test multiple path access
MlirStringRef pathJson = aigLongestPathCollectionGetPath(collection1, 1);
printf("Path: %.*s\n", (int)pathJson.length, pathJson.data);
// CHECK: Path: {"fan_out":{"bit_pos":
// CHECK-SAME: "history":[{
int64_t delay = aigLongestPathDataflowPathGetDelay(path);
printf("Path delay: %lld\n", (long long)delay);
// CHECK: Path delay: 1
AIGLongestPathObject fanIn = aigLongestPathDataflowPathGetFanIn(path);
AIGLongestPathObject fanOut = aigLongestPathDataflowPathGetFanOut(path);
// Test Object API
MlirStringRef fanInName = aigLongestPathObjectName(fanIn);
MlirStringRef fanOutName = aigLongestPathObjectName(fanOut);
size_t fanInBitPos = aigLongestPathObjectBitPos(fanIn);
size_t fanOutBitPos = aigLongestPathObjectBitPos(fanOut);
printf("FanIn: %.*s[%zu]\n", (int)fanInName.length, fanInName.data,
fanInBitPos);
printf("FanOut: %.*s[%zu]\n", (int)fanOutName.length, fanOutName.data,
fanOutBitPos);
// CHECK: FanIn: p[[[BIT:[0-9]]]]
// CHECK: FanOut: p[[[BIT]]]
// Test instance path
IgraphInstancePath fanInPath = aigLongestPathObjectGetInstancePath(fanIn);
IgraphInstancePath fanOutPath =
aigLongestPathObjectGetInstancePath(fanOut);
printf("FanIn instance path size: %zu\n", fanInPath.size);
printf("FanOut instance path size: %zu\n", fanOutPath.size);
// CHECK: FanIn instance path size: 1
// CHECK: FanOut instance path size: 1
// Test History API
AIGLongestPathHistory history =
aigLongestPathDataflowPathGetHistory(path);
bool isEmpty = aigLongestPathHistoryIsEmpty(history);
printf("History is empty: %s\n", isEmpty ? "true" : "false");
// CHECK: History is empty: false
if (!isEmpty) {
AIGLongestPathObject historyObject;
int64_t historyDelay;
MlirStringRef historyComment;
aigLongestPathHistoryGetHead(history, &historyObject, &historyDelay,
&historyComment);
printf("History head delay: %lld\n", (long long)historyDelay);
printf("History head comment: %.*s\n", (int)historyComment.length,
historyComment.data);
// CHECK: History head delay: 1
// CHECK: History head comment: namehint
AIGLongestPathHistory tail = aigLongestPathHistoryGetTail(history);
bool tailIsEmpty = aigLongestPathHistoryIsEmpty(tail);
printf("History tail is empty: %s\n", tailIsEmpty ? "true" : "false");
// CHECK: History tail is empty: true
}
// Test root operation
MlirOperation root = aigLongestPathDataflowPathGetRoot(path);
printf("Root operation is null: %s\n",
mlirOperationIsNull(root) ? "true" : "false");
// CHECK: Root operation is null: false
}
// Cleanup
aigLongestPathCollectionDestroy(collection1);
@ -103,9 +162,20 @@ void testLongestPathAnalysis(void) {
AIGLongestPathCollection collection =
aigLongestPathAnalysisGetAllPaths(analysis, moduleName, true);
MlirStringRef pathJson = aigLongestPathCollectionGetPath(collection, 1);
printf("Path: %.*s\n", (int)pathJson.length, pathJson.data);
// CHECK: "history":[]
if (!aigLongestPathCollectionIsNull(collection)) {
size_t size = aigLongestPathCollectionGetSize(collection);
if (size > 1) {
AIGLongestPathDataflowPath path =
aigLongestPathCollectionGetDataflowPath(collection, 1);
AIGLongestPathHistory history =
aigLongestPathDataflowPathGetHistory(path);
bool isEmpty = aigLongestPathHistoryIsEmpty(history);
printf("History without debug points is empty: %s\n",
isEmpty ? "true" : "false");
// CHECK: History without debug points is empty: true
}
}
// Cleanup
aigLongestPathCollectionDestroy(collection);
@ -146,17 +216,7 @@ void testErrorHandling(void) {
printf("Invalid module name handling: PASS\n");
}
MlirStringRef moduleName = mlirStringRefCreateFromCString("test");
AIGLongestPathCollection collection =
aigLongestPathAnalysisGetAllPaths(analysis, moduleName, true);
MlirStringRef invalidPath = aigLongestPathCollectionGetPath(collection, 100);
if (invalidPath.length == 0) {
printf("Invalid path index handling: PASS\n");
// CHECK: Invalid path index handling: PASS
}
// Cleanup
aigLongestPathCollectionDestroy(collection);
aigLongestPathAnalysisDestroy(analysis);
mlirModuleDestroy(module);
mlirContextDestroy(ctx);

100
test/CAPI/support.c Normal file
View File

@ -0,0 +1,100 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
/* RUN: circt-capi-support-test 2>&1 | FileCheck %s
*/
#include "circt-c/Support/InstanceGraph.h"
#include "circt-c/Dialect/AIG.h"
#include "circt-c/Dialect/HW.h"
#include "circt-c/Dialect/Seq.h"
#include "mlir-c/BuiltinAttributes.h"
#include "mlir-c/BuiltinTypes.h"
#include "mlir-c/IR.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void testInstancePath(void) {
MlirContext ctx = mlirContextCreate();
mlirDialectHandleLoadDialect(mlirGetDialectHandle__hw__(), ctx);
mlirDialectHandleLoadDialect(mlirGetDialectHandle__seq__(), ctx);
// clang-format off
const char *moduleStr =
"hw.module private @foo(in %c : !seq.clock, in %a: i1, out x: i1) {\n"
" %0 = seq.compreg %0, %c : i1\n"
" hw.output %0: i1\n"
"}\n"
"hw.module private @bar(in %c : !seq.clock, in %a: i1, out x: i1) {\n"
" %0 = hw.instance \"foo\" @foo(c: %c: !seq.clock, a: %a: i1) -> (x: i1)\n"
" hw.output %0: i1\n"
"}\n"
"hw.module private @top(in %c : !seq.clock, in %a: i1, out x: i1) {\n"
" %0 = hw.instance \"bar\" @bar(c: %c: !seq.clock, a: %a: i1) -> (x: i1)\n"
" hw.output %0: i1\n"
"}\n";
// clang-format on
MlirModule module =
mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(moduleStr));
MlirOperation moduleOp = mlirModuleGetOperation(module);
// Note: We test IgraphInstancePath functionality through the AIG longest path
// analysis since it provides access to InstancePath objects. While
// InstancePath objects are created in InstancePathCache, that interface is
// not yet exposed through the C API. Using the analysis results is sufficient
// for testing purposes without needing to expose additional InstancePathCache
// APIs.
AIGLongestPathAnalysis analysis =
aigLongestPathAnalysisCreate(moduleOp, true);
MlirStringRef moduleName = mlirStringRefCreateFromCString("top");
AIGLongestPathCollection collection =
aigLongestPathAnalysisGetAllPaths(analysis, moduleName, true);
if (aigLongestPathCollectionIsNull(collection) ||
aigLongestPathCollectionGetSize(collection) < 1)
return;
// Get instance path of fanin object.
IgraphInstancePath instancePath =
aigLongestPathObjectGetInstancePath(aigLongestPathDataflowPathGetFanIn(
aigLongestPathCollectionGetDataflowPath(collection, 0)));
if (instancePath.size != 2) {
printf("Instance path size mismatch\n");
return;
}
MlirOperation bar = igraphInstancePathGet(instancePath, 0);
MlirOperation foo = igraphInstancePathGet(instancePath, 1);
// CHECK: hw.instance "bar" @bar
mlirOperationDump(bar);
// CHECK-NEXT: hw.instance "foo" @foo
mlirOperationDump(foo);
aigLongestPathCollectionDestroy(collection);
aigLongestPathAnalysisDestroy(analysis);
mlirModuleDestroy(module);
mlirContextDestroy(ctx);
}
int main(void) {
testInstancePath();
printf("=== All tests completed ===\n");
// CHECK: === All tests completed ===
return 0;
}

View File

@ -26,6 +26,7 @@ set(CIRCT_TEST_DEPENDS
circt-capi-rtg-pipelines-test
circt-capi-rtg-test
circt-capi-rtgtest-test
circt-capi-support-test
circt-as
circt-bmc
circt-dis

View File

@ -61,9 +61,10 @@ tools = [
'arcilator', 'circt-as', 'circt-bmc', 'circt-capi-aig-test',
'circt-capi-ir-test', 'circt-capi-om-test', 'circt-capi-firrtl-test',
'circt-capi-firtool-test', 'circt-capi-rtg-pipelines-test',
'circt-capi-rtg-test', 'circt-capi-rtgtest-test', 'circt-dis', 'circt-lec',
'circt-reduce', 'circt-synth', 'circt-test', 'circt-translate', 'firld',
'firtool', 'hlstool', 'om-linker', 'kanagawatool'
'circt-capi-rtg-test', 'circt-capi-rtgtest-test', 'circt-capi-support-test',
'circt-dis', 'circt-lec', 'circt-reduce', 'circt-synth', 'circt-test',
'circt-translate', 'firld', 'firtool', 'hlstool', 'om-linker',
'kanagawatool'
]
if "CIRCT_OPT_CHECK_IR_ROUNDTRIP" in os.environ: