⚠️ Work In Progress
These interfaces are new and may still change. Some features are missing and the API is not yet at feature parity with the legacy Device interface.
Overview
The experimental::Device C++ API provides a hierarchical interface for managing EPU devices, kernel execution, and interrupt handling. The main classes are:
- experimental::DeviceManager - Factory for creating experimental::Device instances
- experimental::Device - Represents a device and manages memory allocation and kernel loading
- experimental::KernelGroup - A group of virtual cores that execute kernels together
- experimental::VirtualCore - Represents a single virtual core within a kernel group
- experimental::Interrupt - Provides access to interrupt status and the interrupted core within an interrupt handler
- experimental::InterruptedCore - A specialized interface for interacting with a core during interrupt handling
- experimental::InterruptStatus - Parses and provides access to interrupt status flags and codes
Class Hierarchy
experimental::DeviceManager
└── experimental::Device
└── experimental::KernelGroup
└── experimental::VirtualCore
experimental::Interrupt (passed to interrupt handlers)
├── experimental::InterruptedCore (accessed via Interrupt::core())
└── experimental::InterruptStatus (accessed via Interrupt::readInterruptStatus())
Getting Started
Creating a Device
Use experimental::DeviceManager to create a experimental::Device:
#include <quadric/host.h>
using namespace chimera;
// Create a device manager
experimental::DeviceManager manager;
// Get a device with specified cluster configuration
// Parameters: clusterSize, numClusters, deviceConfig (optional)
experimental::Device device = manager.getDevice(4, 1); // 4 cores per cluster, 1 cluster
Loading a Kernel
Once you have a device, load your kernel binary:
// Load a kernel from the kernel registry
device.loadKernel(myKernel);
// Or load with an offset
device.loadKernel(myKernel, loadOffset);
Memory Management
The experimental::Device provides methods for allocating and transferring data between host and device memory:
Allocating Memory
Host::DeviceBufferRef inputBuffer;
Host::DeviceBufferRef outputBuffer;
// Allocate using a tensor shape (recommended for structured data)
device.allocate<MyTensorShape>(inputBuffer);
// Or allocate a raw buffer by size
device.allocate(sizeInBytes, outputBuffer);
Copying Data to Device
// Copy a tensor to device
MyTensorShape hostTensor;
// ... fill hostTensor with data ...
device.copyBufferToDevice(hostTensor, inputBuffer);
// Or allocate and copy in one step
device.allocateAndCopyToDevice(hostTensor, inputBuffer);
// Also works with std::vector
std::vector<float> hostData = {...};
device.allocateAndCopyToDevice(hostData, inputBuffer);
Copying Data from Device
// Copy results back to host
MyTensorShape hostOutput;
device.copyBufferFromDevice(outputBuffer, hostOutput);
// Also works with std::vector
std::vector<float> hostResults(size);
device.copyBufferFromDevice(outputBuffer, hostResults);
Working with Kernel Groups
A KernelGroup represents a set of virtual cores that can execute kernels. Allocate a kernel group from the device:
std::uint8_t numCores = 4;
KernelGroup kernelGroup = device.allocKernelGroup(numCores);
Starting a Kernel
Start kernel execution on all cores in the group:
// Start the kernel with arguments
kernelGroup.startKernel(entrypoint, arg1, arg2, arg3);
// Kernel runs asynchronously - wait for completion if needed
device.blockUntilIdle();
Accessing Individual Virtual Cores
You can access individual virtual cores to read/write kernel parameters:
// Get a specific virtual core using operator[]
VirtualCore vc = kernelGroup[0];
// Iterate over all virtual cores using range-based for loop
for (auto vc : kernelGroup) {
std::uint32_t result = vc.readKernelParameterRegister<std::uint32_t>(paramIndex);
}
// Or get all virtual cores as a vector
std::vector<VirtualCore> allCores = kernelGroup.getAllVirtualCores();
// Write a kernel parameter register
vc.writeKernelParameterRegister(paramIndex, value);
// Read a kernel parameter register
std::uint32_t result = vc.readKernelParameterRegister<std::uint32_t>(paramIndex);
Interrupt Handling
The API supports interrupt-driven communication between host and device using the Interrupt class.
Setting Up an Interrupt Handler
Register an interrupt handler on a kernel group or individual virtual core:
// Set handler on entire kernel group (applies to all cores)
kernelGroup.setInterruptHandler([](chimera-compute-library-ccl-api-reference/Interrupt& interrupt) {
const auto& interruptStatus = interrupt.readInterruptStatus();
// End-of-kernel interrupt: clear without resuming
if (interruptStatus.isEndOfKernel()) {
interrupt.core().clearInterrupt();
return;
}
// Read kernel parameter registers if needed
std::uint32_t status = interrupt.core().readKernelParameterRegister<std::uint32_t>(statusRegister);
// Process the interrupt...
// Clear interrupt and resume execution
interrupt.core().clearInterruptsAndResume();
});
// Or set handler on a specific virtual core
VirtualCore vc = kernelGroup[0];
vc.setInterruptHandler([](chimera-compute-library-ccl-api-reference/Interrupt& interrupt) {
const auto& interruptStatus = interrupt.readInterruptStatus();
if (interruptStatus.isEndOfKernel()) {
interrupt.core().clearInterrupt();
return;
}
// Handle interrupt for this specific core
interrupt.core().clearInterruptsAndResume();
});
Interrupt and InterruptedCore Methods
The Interrupt class provides access to interrupt status and the interrupted core:
readInterruptStatus()- Read the current interrupt status as anInterruptStatusobjectcore()- Get a reference to theInterruptedCorefor core operations
The InterruptedCore class provides methods for interacting with the interrupted core:
readKernelParameterRegister<T>(index)- Read a kernel parameter registerwriteKernelParameterRegister(index, value)- Write a kernel parameter registerclearInterrupt()- Clear the interrupt flag without resuming execution. Use this for end-of-kernel interrupts.clearInterruptsAndResume()- Clear the interrupt flag and resume kernel execution. Use this for all other interrupts (e.g. software blocking).
Important: Every interrupt handler must call clearInterrupt() or clearInterruptsAndResume() before returning. For end-of-kernel interrupts, use clearInterrupt() since there is no execution to resume. For all other interrupts, use clearInterruptsAndResume() to clear the interrupt and allow the kernel to continue.
Complete Example
#include <quadric/host.h>
using namespace chimera;
int main() {
// 1. Create device
experimental::DeviceManager manager;
experimental::Device device = manager.getDevice(4, 1);
// 2. Load kernel
device.loadKernel(myKernel);
// 3. Allocate and copy input data
Host::DeviceBufferRef inputBuffer, outputBuffer;
std::vector<float> inputData = {...};
device.allocateAndCopyToDevice(inputData, inputBuffer);
device.allocate<OutputTensorShape>(outputBuffer);
// 4. Allocate kernel group
KernelGroup kernelGroup = device.allocKernelGroup(4);
// 5. Set up interrupt handler (optional)
kernelGroup.setInterruptHandler([](chimera-compute-library-ccl-api-reference/Interrupt& interrupt) {
const auto& interruptStatus = interrupt.readInterruptStatus();
if (interruptStatus.isEndOfKernel()) {
interrupt.core().clearInterrupt();
return;
}
// Handle interrupt...
interrupt.core().clearInterruptsAndResume();
});
// 6. Start kernel execution
kernelGroup.startKernel(entrypoint, inputBuffer, outputBuffer);
// 7. Wait for completion
device.blockUntilIdle();
// 8. Copy results back
std::vector<float> results(outputSize);
device.copyBufferFromDevice(outputBuffer, results);
return 0;
}
API Reference
experimental::DeviceManager
| Method | Description |
|---|---|
getDevice(clusterSize, numClusters, config) | Create a experimental::Device with the specified configuration |
experimental::Device
| Method | Description |
|---|---|
allocKernelGroup(numCores) | Allocate a kernel group with the specified number of cores |
loadKernel(kernel, offset) | Load a kernel binary to the device |
allocate<TensorShape>(bufferRef) | Allocate device memory for a tensor |
allocate(size, bufferRef) | Allocate raw device memory |
allocateAndCopyToDevice(host, bufferRef) | Allocate and copy data to device |
copyBufferToDevice(host, bufferRef) | Copy data to an allocated buffer |
copyBufferFromDevice(bufferRef, host) | Copy data from device to host |
blockUntilIdle() | Wait for all kernel execution to complete |
printProfile() | Print profiling information |
experimental::KernelGroup
| Method | Description |
|---|---|
startKernel(entrypoint, args...) | Start async kernel execution on all cores |
operator[](chimera-compute-library-ccl-api-reference/index) | Get a specific virtual core by index |
begin() / end() | Iterator support for range-based for loops |
getAllVirtualCores() | Get all virtual cores in the group |
size() | Get the number of cores in the group |
kernelGroupId() | Get the kernel group ID |
setInterruptHandler(handler) | Set interrupt handler for all cores |
writeKernelParameterRegister(index, value) | Write to a kernel parameter register |
experimental::VirtualCore
| Method | Description |
|---|---|
writeKernelParameterRegister(index, value) | Write to a kernel parameter register |
readKernelParameterRegister<T>(index) | Read a kernel parameter register |
setInterruptHandler(handler) | Set interrupt handler for this core |
experimental::Interrupt
| Method | Description |
|---|---|
readInterruptStatus() | Read the interrupt status as InterruptStatus |
core() | Get a reference to the InterruptedCore |
experimental::InterruptedCore
| Method | Description |
|---|---|
readKernelParameterRegister<T>(index) | Read a kernel parameter register |
writeKernelParameterRegister(index, value) | Write to a kernel parameter register |
clearInterrupt() | Clear interrupt without resuming (use for end-of-kernel interrupts) |
clearInterruptsAndResume() | Clear interrupt and resume execution |
experimental::InterruptStatus
| Method | Description |
|---|---|
isEndOfKernel() | Check if this is an end-of-kernel interrupt |
isHwBlocking() | Check if this is a hardware blocking interrupt |
isHwNonBlocking() | Check if this is a hardware non-blocking interrupt |
isSwBlocking() | Check if this is a software blocking interrupt |
isSwNonBlocking() | Check if this is a software non-blocking interrupt |
getHwBlockingCode() | Get the hardware blocking interrupt code |
getHwNonBlockingCode() | Get the hardware non-blocking interrupt code |
getSwBlockingCode() | Get the software blocking interrupt code |
getSwNonBlockingCode() | Get the software non-blocking interrupt code |