The FrontPanel application provides a turnkey method to make basic user interaction available to your FPGA hardware. But it is not suitable for all applications, particularly those which require further data processing on the PC side of the interface or when data transfer between the PC and FPGA is required. In these cases, a custom software application is usually a better fit. To this end, Opal Kelly provides the FrontPanel Application Programmer’s Interface (API), a consistent (and in some cases cross-platform) interface to the underlying interface driver layer.
The FrontPanel API contains methods which communicate via the USB or PCIe bridge on the device, but the methods have been specifically designed to interface with FPGA hardware in a manner which is consistent with most hardware designs. The API provides methods to interface directly with the FrontPanel HDL modules such as wires, triggers, and pipes. Because of this abstraction, some flexibility in the hardware interface is sacrificed for a dramatically reduced development cycle (and learning curve!) for connecting your FPGA hardware to your custom software.
The library is written in C++ and is provided as a dynamically-linked ibrary. However, Python, Java, and C# versions of the API are also available and can make FPGA development even faster. Because the Python, Java, and C# APIs are generated automatically from the C++ API, most of the methods are identical and you can use the same API reference for all languages. Futher, the methods to communicate with PCIe and USB device are either identical or very similar.
API Reference Guide
The API documentation provided in this User’s Manual gives a general overview of how the FrontPanel API is organized and used. More detailed information about the specific calling methods and parameters can be found in the API Reference Guide.
Often, the best way to learn how to apply a programming interface is to see examples of its application. We encourage you to go through all of our samples to see how applications can be built with the FrontPanel SDK. If you have problems with your own design, it is a good practice to revisit our samples.
Additionally, we have several bite-sized examples that comprise a brief FrontPanel How-To Guide.
The FrontPanel API is provided as a dynamically-linked library that you include with your application. The interface to the DLL is C, but a C++ wrapper is provided to make the entire DLL appear as if it were a native C++ class in your application.
The library contains a small number of classes which you then instantiate within your code. The details of the USB or PCIe connection between the FPGA and your PC disappear within the neat confines of the API. These classes are shown in the table below in further detailed in what follows.
|okCPLL22150||This is a container class providing methods and structure used to configure the Cypress 22150 PLL on the XEM3001 and XEM3005. An instance of this class can be created and used to program the on-board PLL or the class can be generated from the EEPROM settings.|
|okCPLL22393||This is a container class providing methods and structure used to configure the Cypress 22393 PLL on the XEM6010, XEM3010, and XEM3050. An instance of this class can be created and used to program the on-board PLL or the class can be generated from the EEPROM settings.|
|okCFrontPanel||This is the base class used to find, configure, and communicate with a FrontPanel-enabled device. The methods in the API are organized into four main groups: Device Interaction, Device Configuration, and FPGA Communication.|
The four endpoint types (Wire, Trigger, Pipe, and Registers) provide a means by which the PC and FPGA communicate. Each type is suited to a specific type of data transfer and has its own associated usage and rules.
All API methods are blocking commands. This means that the call completes before it returns to the caller (your software). Therefore, you can be assured that if you perform two consecutive API calls that update registers on the FPGA, the updates from the first call are complete prior to starting the second call.
Recall that a wire is used to communicate asynchronous signal state between the host (PC) and the target (FPGA). The okHostInterface supports up to 32 Wire In endpoints and 32 Wire Out endpoints connected to it. To save bandwidth, all Wire In or Wire Out endpoints are updated at the same time and written or read by the host in one block.
All Wire In (to FPGA) endpoints are updated by the host at the same time with the call UpdateWireIns(). Prior to this call, the application sets new Wire In values using the API method SetWireInValue(). The SetWireInValue() simply updates the wire values in a data structure internal to the API. UpdateWireIns() then transfers these values to the FPGA.
All Wire Out (from FPGA) endpoints are likewise read by the host at the same time with a call to UpdateWireOuts(). This call reads all 32 Wire Out endpoints and stores their values in an internal data structure. The specific endpoint values can then be read out using GetWireOutValue().
Wires are 16-bits wide on USB 2.0 devices, 32-bits on USB 3.0 devices, and 32-bits on PCI Express devices. The API interface width is 32 bits. The upper bits are ignored if not supported.
Triggers are used to communicate a singular event between the host and target. A Trigger In provides a way for the host to convey a “one-shot” on an arbitrary FPGA clock. A Trigger Out provides a way for the FPGA to signal the host with a “one-shot” or other single-event indicator.
Triggers are read and updated in a manner similar to Wires. All Trigger Ins are transferred to the FPGA at the same time and all Trigger Outs are transferred from the FPGA at the same time.
Trigger Out information is read from the FPGA using the call UpdateTriggerOuts(). Subsequent calls to IsTriggered() then return ‘true’ if the trigger has been activated since the last call to UpdateTriggerOuts().
Triggers are 16-bits wide on USB 2.0 devices, 32-bits on USB 3.0 devices, and 32-bits on PCI Express devices. The API interface width is 32 bits. The upper bits are ignored if not supported.
Pipe communication is the synchronous communication of one or more bytes of data. In both Pipe In and Pipe Out cases, the host is the master. Therefore, the FPGA must be able to accept (or provide) data on any time. Wires, Triggers, and FIFOs can make things a little more negotiable.
When data is written by the host to a Pipe In endpoint using WriteToPipeIn(…), the device driver will packetize the data as necessary for the underlying protocol. Once the transfer has started, it will continue to completion, so the FPGA must be prepared to accept all of the data.
When data is read by the host from a Pipe Out endpoint using ReadFromPipeOut(…), the device driver will again packetize the data as necessary. The transfer will proceed from start to completion, so the FPGA must be prepared to provide data to the Pipe Out as requested.
When using the Pipes API, the length is specified in bytes. Firmware restrictions put a limitation on the maximum length per transfer. However, this API will automatically perform multiple transfers, if required, to complete the full length. Length must be an integer multiple of a minimal transfer size according to the list below:
|DEVICE||LENGTH RESTRICTIONS (IN BYTES)|
|USB 2.0||Multiple of 2|
|USB 3.0||Multiple of 16|
|PCIe||Multiple of 8|
Byte Order (USB 2.0)
Pipe data is transferred over the USB in 8-bit words but transferred to the FPGA in 16-bit words. Therefore, on the FPGA side (HDL), the Pipe interface has a 16-bit word width but on the PC side (API), the Pipe interface has an 8-bit word width.
When writing to Pipe Ins, the first byte written is transferred over the lower order bits of the data bus (7:0). The second byte written is transferred over the higher order bits of the data bus (15:8). Similarly, when reading from Pipe Outs, the lower order bits are the first byte read and the higher order bits are the second byte read.
Byte Order (USB 3.0)
Pipe data is transferred over the USB in 8-bit words but transferred to the FPGA in 32-bit words. Therefore, on the FPGA side (HDL), the Pipe interface has a 32-bit word width but on the PC side (API), the Pipe interface has an 8-bit word width.
When writing to Pipe Ins, the first byte written is transferred over the lower order bits of the data bus (7:0). The second byte written is transferred over next higher order bits of the data bus (15:8) and so on. Similarly, when reading from Pipe Outs, the lower order bits are the first byte read and the next higher order bits are the second byte read.
Byte Order (PCI Express)
The HDL pipe endpoints for PCI Express designs are 64-bits wide. The API methods are 8 bits wide for both USB and PCI Express. For PCI Express devices, pipe transfers must be in multiples of 8 bytes.
Block-Throttled Pipe communication is identical to Pipe communication with the additional specification of a block size.
Because the FPGA has the opportunity to stall the transfer by deasserting EP_READY, the call may fail with a timeout.
Block sizes and length sizes are determined by two factors:
- Device Firmware – The device microcontroller has limitations based on its internal design architecture.
- Cypress FX2 – Sometimes referred to as our “USB 2.0” devices. Examples include XEM3001, XEM5010, XEM7010, XEM7001
- Cypress FX3 – Sometimes referred to as our “USB 3.0” devices. Examples include XEM6310, XEM7310, ZEM5305, ZEM5310.
- USB Connection Speed – The underlying USB connection speed also places limitations on the block sizes. Use the
GetDeviceInfoAPI to retrieve an
okTDeviceInfostruct, then inspect the
usbSpeedfor this information.
- Full Speed (FS) – Uncommonly slow connection speed (12 Mbps, USB 1.0).
- High Speed (HS) – Typical USB 2.0 connection speed (480 Mbps).
- Super Speed (SS) – Only available on USB 3.0 ports (5 Gbps).
Block size is dependent on the device type (firmware) and connection speed. The software APIs will automatically break a transfer into appropriate lengths, when able. However, this will result in more transactions being performed and, therefore, lower performance. To optimize transfers, the following steps should be made, either during system design or algorithmically:
- Determine the connection speed using the
GetDeviceInfoAPI and inspecting the
- Determine an appropriate block size according to the table below. Larger block sizes will perform better.
- Determine an allowed transfer length according to the table below. Longer transfers will perform better.
Note that some of these restrictions are related to the connection speed. If a USB 3.0 device is connected to a USB 2.0 port, it will be forced to the lower connection speed and therefore have different restrictions. Your gateware and software should recognize this and act accordingly.
The API will return an
UnsupportedFeature Error Code if an invalid block size or transfer length is specified.
|DEVICE||CONNECTION SPEED||BLOCK SIZE RESTRICTIONS (BYTES)||LENGTH RESTRICTIONS (BYTES)|
|Full Speed||Multiple of 2 [2, 4, 6, 8, …, 64]||
|High Speed||Multiple of 2 [2, 4, 6, 8, …, 1024]|
|Full Speed||Power of 2 [16, 32, 64]||
|High Speed||Power of 2 [16, 32, 64, …, 1024]|
|Super Speed||Power of 2 [16, 32, 64, …, 16384]|
Registers (USB 3.0 Only)
The RegisterBridge HDL module provides a read / write interface with a 32-bit address range and 32-bit data width. Any address within the range may be read or written. Reads and writes through the API are performed on the hardware in the order provided by the caller and each is performed only after the previous has completed.
Some XEM products have a programmable PLL that can be configured through the API. In many cases, your application will require a single pre-set PLL configuration which can be stored to the on-board EEPROM (not the PLL’s EEPROM). In other cases, you may want to configure the PLL from your software.
Preset PLL Configuration
With a preset PLL configuration, you setup the PLL parameters using the FrontPanel Application. You then store these parameters to the on-board EEPROM for future recall. When you startup your application (and typically before FPGA configuration), you can then configure the PLL from this stored preset using a single command:
Code language: PHP (php)
Software PLL Configuration
You can also use the PLL classes to configure PLL parameters and then load them into the PLL. This allows dynamic PLL configuration from your own software. Software PLL configuration is a bit more complicated and requires more intimate knowledge of how the PLL parameters interoperate. Please refer to the corresponding PLL datasheet for details on the PLL parameters specific to that PLL. Then refer to the API Reference Guide for the methods available to set these parameters.
System Flash (USB 3.0)
Some FrontPanel devices have a non-volatile Flash memory attached to the USB microcontroller that is used for firmware and setting storage as well as user storage. The FrontPanel API includes methods for working with this Flash. The available storage and layout of the Flash is device-dependent. Information for each device is available in the User’s Manual for that device.
FrontPanel API Example Usage
Below is a short code snippet that illustrates how the API might be used in a C++ application. More useful and detailed examples can be found in the Samples folder of the FrontPanel installation.
Code language: PHP (php)
OpalKelly::FrontPanelPtr dev = OpalKelly::FrontPanelDevices().Open(); dev = devices.Open(); dev->LoadDefaultPLLConfiguration(); dev->ConfigureFPGA("mybitfile.bit"); // Set a value on WireIn endpoint 0x00. dev->SetWireInValue(0x00, 0x37); dev->UpdateWireIns(); // Activate TriggerIn 0x40:0 (clears address pointers). dev->ActivateTriggerIn(0x40, 0); // Send 1024 bytes to PipeIn 0x80. dev->WriteToPipeIn(0x80, 1024, buf); // Read 1024 from PipeOut 0xA0. dev->ReadFromPipeOut(0xA0, 1024, buf); // Read the result from WireOut endpoint 0x20. dev->UpdateWireOuts(); result = dev->GetWireOutValue(0x20);
Regarding Device Ownership (Multithread or Multiprocess Access)
In general, once an instance of okCFrontPanel has been opened, that instance “owns” the device. That means that, while the API will allow you to create another instance and communicate with the same device, there are likely going to be problems with doing so.
In situations where you must have multiple threads or processes communicating with the same device, it is better to have a single owner of the device instance and route all calls through that owner.
The exception to this is GetDeviceCount() and the associated calls under Linux and Mac OS X. (This exception does not apply to Windows.) You can call this method at any time (even before opening a device) to determine the number of attached FrontPanel devices and retrieve their model numbers, and serial numbers. You may not retrieve the Device ID string without opening the device and that implies “owning” the device.
Communicating with Multiple Devices
In most cases, your software will communicate with a single attached device attached. However, some applications require simultaneous communication with two or more devices. Multiple-device communication is fully supported by the driver and API but will require special consideration when initializing the communication.
Querying Attached Devices
You can call the method GetDeviceCount() to determine the number of supported devices attached to the bus before opening a specific a specific device. The GetDeviceCount() method also queries the device serial numbers and board types of all the attached devices. This information can then be accessed by calling the methods GetDeviceListSerial() and GetDeviceListModel(), respectively.
Windows, Linux, and Mac OS X behave slightly differently with regards to device enumeration using the FrontPanel API. Under Windows, if any process opens a device, that device will no longer be listed on subsequent calls to GetDeviceCount() from a different process. On the other hand, Linux and Mac OS X will allow the opened device to be enumerated. It is up to the user to assure that two processes are not communicating with the same device as this can lead to data corruption or other failures.
Connecting to a Specific Device
It is expected that you would identify a specific board using the serial number (factory-assigned and not user-mutable) or using the device ID string (user configurable via FrontPanel). A typical process for opening multiple devices would then be:
- Create two instances (call them x and y) of
- Create an instance (
devices.GetCount()to verify that two boards are connected and to query the serial numbers and other device information.
serX = devices.GetSerial(0)to get the first device’s serial number.
serY = devices.GetSerial(1)to get the second device’s serial number.
x = devices.Open(serX)to open the first device.
y = devices.Open(serY)to open the second device.
Using this procedure, you would then have two instances which point to the two devices in your system. They have also been clearly associated with the specific hardware you specified, so there is no ambiguity.