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.FrontPanel namespace 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; becomes using OpalKelly.FrontPanel.Api;.
  • Naming. Names are already PascalCase, so most method names are unchanged. What changes is that acronyms normalize to PascalCase — FPGAFpga, AXIAxi, PLLPll (GetFPGADataPortClassicGetFpgaDataPortClassic).
  • Object model. Instead of one okCFrontPanel object carrying everything, the device is decomposed: enumeration/opening is done by a DeviceManager, configuration lives on an FpgaConfiguration, and wires/triggers/pipes/registers live on the classic data port.
  • Resource management. Device and the data ports implement IDisposable, so a using block releases them deterministically instead of an explicit Close().
  • Errors. Operations throw a typed FrontPanelException on failure instead of returning an ErrorCode you must check; the code is still available as FrontPanelException.Code.
  • Buffers. Pipe transfers keep the same shape — you pass a byte[] and get back the number of bytes transferred as a long. (This is unchanged from the Binding API.)

Quick Reference: Old → New

TaskBinding API (OpalKelly.FrontPanel)Idiomatic API (OpalKelly.FrontPanel.Api)
Namespaceusing OpalKelly.FrontPanel;using OpalKelly.FrontPanel.Api;
Open first devicenew okCFrontPanelDevices().Open()new DeviceManager().OpenDevice()
Release devicedevice.Close()using (Device device = ...) (automatic)
Read device infookTDeviceInfo info = new okTDeviceInfo(); device.GetDeviceInfo(info);DeviceInfo info = device.GetDeviceInfo();
— field accessinfo.serialNumber, info.productNameinfo.SerialNumber, info.ProductName
Configure FPGAdevice.ConfigureFPGA("d.bit")device.GetFpgaConfiguration().LoadConfigurationFromFile("d.bit")
Get classic data portdevice.GetFPGADataPortClassic()device.GetFpgaDataPortClassic()
Set / send wire inSetWireInValue(...), UpdateWireIns()SetWireInValue(...), UpdateWireIns() (same)
Read wire outUpdateWireOuts(), GetWireOutValue(0x20)UpdateWireOuts(), GetWireOutValue(0x20) (same)
Trigger in / outActivateTriggerIn(...), IsTriggered(...)ActivateTriggerIn(...), IsTriggered(...) (same)
PipesWriteToPipeIn(ep, buf), ReadFromPipeOut(ep, buf)WriteToPipeIn(ep, buf), ReadFromPipeOut(ep, buf) (same)
RegistersReadRegister(a), WriteRegister(a, v)ReadRegister(a), WriteRegister(a, v) (same)
Error handlingif (... != 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 (GetFPGADataPortClassicGetFpgaDataPortClassic). 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