C# Migration Guide
This guide covers moving an existing FrontPanel 6 C# application from the Binding API (the okC* classes in the OpalKelly.FrontPanel namespace) to the Idiomatic API (OpalKelly.FrontPanel.Api).
Both APIs ship in the same NuGet package and target the same devices. This is a change of style, not of version — if you are upgrading a FrontPanel 5.x application to 6.x, do that first with the C# FP6 migration guide.
The Binding API is not going away. The
OpalKelly.FrontPanelnamespace continues to work exactly as before, with no deprecation. The Idiomatic API is an additive layer built on top of it and bundled in the same package, so you can migrate one file at a time and use both namespaces side by side in the same program.
Overview
The Idiomatic API keeps the same underlying operations but repackages them the way modern .NET developers expect:
- Namespace.
using OpalKelly.FrontPanel;becomesusing OpalKelly.FrontPanel.Api;. - Naming. Names are already PascalCase, so most method names are unchanged. What changes is that acronyms normalize to PascalCase —
FPGA→Fpga,AXI→Axi,PLL→Pll(GetFPGADataPortClassic→GetFpgaDataPortClassic). - Object model. Instead of one
okCFrontPanelobject carrying everything, the device is decomposed: enumeration/opening is done by aDeviceManager, configuration lives on anFpgaConfiguration, and wires/triggers/pipes/registers live on the classic data port. - Resource management.
Deviceand the data ports implementIDisposable, so ausingblock releases them deterministically instead of an explicitClose(). - Errors. Operations throw a typed
FrontPanelExceptionon failure instead of returning anErrorCodeyou must check; the code is still available asFrontPanelException.Code. - Buffers. Pipe transfers keep the same shape — you pass a
byte[]and get back the number of bytes transferred as along. (This is unchanged from the Binding API.)
Quick Reference: Old → New
| Task | Binding API (OpalKelly.FrontPanel) | Idiomatic API (OpalKelly.FrontPanel.Api) |
|---|---|---|
| Namespace | using OpalKelly.FrontPanel; | using OpalKelly.FrontPanel.Api; |
| Open first device | new okCFrontPanelDevices().Open() | new DeviceManager().OpenDevice() |
| Release device | device.Close() | using (Device device = ...) (automatic) |
| Read device info | okTDeviceInfo info = new okTDeviceInfo(); device.GetDeviceInfo(info); | DeviceInfo info = device.GetDeviceInfo(); |
| — field access | info.serialNumber, info.productName | info.SerialNumber, info.ProductName |
| Configure FPGA | device.ConfigureFPGA("d.bit") | device.GetFpgaConfiguration().LoadConfigurationFromFile("d.bit") |
| Get classic data port | device.GetFPGADataPortClassic() | device.GetFpgaDataPortClassic() |
| Set / send wire in | SetWireInValue(...), UpdateWireIns() | SetWireInValue(...), UpdateWireIns() (same) |
| Read wire out | UpdateWireOuts(), GetWireOutValue(0x20) | UpdateWireOuts(), GetWireOutValue(0x20) (same) |
| Trigger in / out | ActivateTriggerIn(...), IsTriggered(...) | ActivateTriggerIn(...), IsTriggered(...) (same) |
| Pipes | WriteToPipeIn(ep, buf), ReadFromPipeOut(ep, buf) | WriteToPipeIn(ep, buf), ReadFromPipeOut(ep, buf) (same) |
| Registers | ReadRegister(a), WriteRegister(a, v) | ReadRegister(a), WriteRegister(a, v) (same) |
| Error handling | if (... != okCFrontPanel.ErrorCode.NoError) | try { ... } catch (FrontPanelException ex) |
Migration Steps
Step 1: Change the namespace and how you open a device
Device enumeration and opening move to DeviceManager. The opened Device implements IDisposable, so a using block replaces the explicit Close().
// Binding API:
using OpalKelly.FrontPanel;
okCFrontPanel device = new okCFrontPanelDevices().Open(); // first available device
// ... use device ...
device.Close();
// Idiomatic API:
using OpalKelly.FrontPanel.Api;
DeviceManager manager = new DeviceManager();
using (Device device = manager.OpenDevice()) // disposed automatically on exit
{
// ... use device ...
}Code language: JavaScript (javascript)OpenDevice() opens the first available device when called with no argument; pass a serial number to target a specific device (manager.OpenDevice("1234567890")). DeviceManager also offers GetDeviceCount() and GetSerialNumbers() for enumeration.
Step 2: Read device info from the returned object
The Binding API fills an okTDeviceInfo struct you pass in. The Idiomatic API returns a DeviceInfo object with PascalCase properties.
// Binding API:
okTDeviceInfo info = new okTDeviceInfo();
device.GetDeviceInfo(info);
Console.WriteLine($"{info.serialNumber} {info.productName}");
// Idiomatic API:
DeviceInfo info = device.GetDeviceInfo();
Console.WriteLine($"{info.SerialNumber} {info.ProductName}");Code language: JavaScript (javascript)Step 3: Configure the FPGA through FpgaConfiguration
Configuration moves off the device object onto a dedicated FpgaConfiguration, and failure is reported by an exception rather than a return code.
// Binding API:
if (device.ConfigureFPGA("my_design.bit") != okCFrontPanel.ErrorCode.NoError)
{
throw new Exception("FPGA configuration failed");
}
// Idiomatic API:
try
{
device.GetFpgaConfiguration().LoadConfigurationFromFile("my_design.bit");
}
catch (FrontPanelException ex)
{
throw new Exception($"FPGA configuration failed: {ex.Message}", ex);
}Code language: PHP (php)FpgaConfiguration also provides ClearConfiguration() and GetInfo().
Step 4: Move wire/trigger/pipe/register calls onto the classic data port
Get the classic data port from the device — note the acronym casing change (GetFPGADataPortClassic → GetFpgaDataPortClassic). The wire/trigger/pipe/register method names and parameters on the port are otherwise unchanged. The port is also IDisposable, so wrap it in a using block.
// Binding API:
okCFPGADataPortClassic classic = device.GetFPGADataPortClassic();
classic.SetWireInValue(0x00, 0x01);
classic.UpdateWireIns();
classic.ActivateTriggerIn(0x40, 0);
classic.UpdateWireOuts();
uint status = classic.GetWireOutValue(0x20);
// Idiomatic API:
using (FpgaDataPortClassic classic = device.GetFpgaDataPortClassic())
{
classic.SetWireInValue(0x00, 0x01);
classic.UpdateWireIns();
classic.ActivateTriggerIn(0x40, 0);
classic.UpdateWireOuts();
uint status = classic.GetWireOutValue(0x20);
}Code language: JavaScript (javascript)Registers keep the same single-value methods. The Idiomatic API additionally offers bulk overloads: ReadRegisters(IReadOnlyList<uint>) returns an IReadOnlyList<RegisterEntry>, and WriteRegisters(IReadOnlyList<RegisterEntry>) writes several at once.
Step 5: Pipe buffers are unchanged
Pipe transfers work the same way in both APIs: pass a byte[] buffer and receive the number of bytes transferred as a long. No changes are needed here beyond calling through the classic data port object.
// Both APIs:
byte[] outbound = BuildPayload();
long written = classic.WriteToPipeIn(0x80, outbound);
byte[] inbound = new byte[1024];
long read = classic.ReadFromPipeOut(0xA0, inbound);
byte[] block = new byte[1024];
long blockRead = classic.ReadFromBlockPipeOut(0xA0, 256, block);Code language: JavaScript (javascript)Step 6: Replace return-code checks with exception handling
Wherever the Binding API returned an ErrorCode you compared against okCFrontPanel.ErrorCode.NoError, the Idiomatic API throws FrontPanelException (which carries the same ErrorCode as its .Code property). AXI transfers throw the more specific AxiTransferException / AxiOperationException, both derived from FrontPanelException, so a single catch (FrontPanelException) still handles everything.
// Binding API:
okCFrontPanel.ErrorCode rc = device.ConfigureFPGA("design.bit");
if (rc != okCFrontPanel.ErrorCode.NoError)
{
Console.WriteLine($"configure failed: {rc}");
return;
}
// Idiomatic API:
try
{
device.GetFpgaConfiguration().LoadConfigurationFromFile("design.bit");
}
catch (FrontPanelException ex)
{
Console.WriteLine($"configure failed: {ex.Code}");
return;
}Code language: JavaScript (javascript)Complete Example
The same program written both ways.
// Binding API:
using OpalKelly.FrontPanel;
okCFrontPanel device = new okCFrontPanelDevices().Open();
okTDeviceInfo info = new okTDeviceInfo();
device.GetDeviceInfo(info);
Console.WriteLine($"Serial: {info.serialNumber}");
Console.WriteLine($"Product: {info.productName}");
if (device.ConfigureFPGA("my_design.bit") != okCFrontPanel.ErrorCode.NoError)
{
throw new Exception("configuration failed");
}
okCFPGADataPortClassic classic = device.GetFPGADataPortClassic();
classic.SetWireInValue(0x00, 0x01);
classic.UpdateWireIns();
classic.ActivateTriggerIn(0x40, 0);
device.Close();Code language: PHP (php)// Idiomatic API:
using OpalKelly.FrontPanel.Api;
DeviceManager manager = new DeviceManager();
using (Device device = manager.OpenDevice())
{
DeviceInfo info = device.GetDeviceInfo();
Console.WriteLine($"Serial: {info.SerialNumber}");
Console.WriteLine($"Product: {info.ProductName}");
try
{
device.GetFpgaConfiguration().LoadConfigurationFromFile("my_design.bit");
}
catch (FrontPanelException ex)
{
throw new Exception($"configuration failed: {ex.Message}", ex);
}
using (FpgaDataPortClassic classic = device.GetFpgaDataPortClassic())
{
classic.SetWireInValue(0x00, 0x01);
classic.UpdateWireIns();
classic.ActivateTriggerIn(0x40, 0);
}
}Code language: PHP (php)Migrating Incrementally
Because both APIs live in the same package on the same underlying SDK, you do not have to convert everything at once:
1. Add using OpalKelly.FrontPanel.Api; alongside your existing using OpalKelly.FrontPanel; (alias one namespace if a type name collides). 2. Convert a self-contained file — device open, configuration, or one data path — to the Idiomatic API and test it against hardware. 3. Repeat file by file. Leave the rest on the Binding API for as long as you like; there is no deprecation deadline.
Reference
- Idiomatic API mirrors the FrontPanel Platform (TypeScript) API — synchronous and PascalCase here, asynchronous (Promises) and camelCase there.
- Binding API mirrors the C++ API reference name-for-name.
- See also Using FrontPanel from C# for installation and a side-by-side introduction to both APIs.