Pipe Transfers
This guide will show how to read data from a PipeOut endpoint and write data to a PipeIn endpoint. Starting from the template project built using the steps from the Building the Template App guide, we will update the FrontPanel Component to present a Read and separate Write button to start a transfer.
Install Prereqisites
- Build the Template App – Building the Template App
- PipeTest Sample bitfile for you device – Opal Kelly Pins
Add the PipeTest Example Bitfile
Locate the pipetest.bit
file that is compatible with the Opal Kelly XEM device that will be connected to the PC.
Copy or move the pipetest.bit
file into the folder for the device you will be using, located within the assets folder at the root of the application project (e.g., assets/XEM8320-AU25P for the XEM8320-AU25P device).
Open the src/FPGAConfigurationFiles.ts
file and add an import statement with the path where the pipetest.bit file was copied. This import statement will instruct the Webpack bundler to include the file in the final output bundle generated when we build and package the application.
import '../assets/XEM8320-AU25P/pipetest.bit';
Code language: JavaScript (javascript)
Since we won’t be using the counters.bit
file in this example, comment out all the import statements that refer to it so they aren’t included in the application bundle.
Open the src/App.tsx
file and change the name of the file used to configure the FPGA to pipetest.bit
.
/**
* Initializes the device by configuring the FPGA on the device with the file
* corresponding to the product name of the device.
* @param device - The device to initialize.
* @returns {Promise<IDevice>} A promise that resolves to the opened device.
*/
const initializeDevice = async (device: IDevice): Promise<void> => {
const deviceInfo = await device.getDeviceInfo();
console.log(`Initializing Device Product: ${deviceInfo.productName} SerialNumber: '${deviceInfo.serialNumber}'`);
const fpgaConfiguration = device.getFPGAConfiguration();
//Choose the configuration file based on the product name of the device.
const configurationFilename = deviceInfo.productName + "/pipetest.bit";
await loadFPGAConfiguration(configurationFilename, fpgaConfiguration);
};
Code language: JavaScript (javascript)
Replace the FrontPanel Component Function
Open the src/FrontPanel.tsx
file, locate the FrontPanel function component and replace it with following. We will be rewritting this component to do Pipe reads and writes instead.
function FrontPanel(props: FrontPanelProps) {
}
Code language: JavaScript (javascript)
Remove the FPGADataPortClassicPeriodicUpdate timer from the list of imports. We will not be using it in the the new component.
import {
IFPGADataPortClassic,
WorkQueue
} from "@opalkelly/frontpanel-platform-api";
Code language: JavaScript (javascript)
Add Read and Write Status Message State Variables
Add two state variables that will be used to store the message indicating the status of the read and write transfers.
function FrontPanel(props: FrontPanelProps) {
const [readStatus, setReadStatus] = React.useState<string>("Ready");
const [writeStatus, setWriteStatus] = React.useState<string>("Ready");
Code language: JavaScript (javascript)
Add Target Device Initialize and Reset
Add an initializeTargetDevice
function within the FrontPanel component.
// Initialize Device
const initializeTargetDevice = () => {
props.fpgaDataPort.setWireInValue(0x03, 0x0, 0xffffffff); // Apply fixed pattern
props.fpgaDataPort.setWireInValue(0x02, 0xffffffff, 0xffffffff); // Pipe In throttle
props.fpgaDataPort.setWireInValue(0x01, 0xffffffff, 0xffffffff); // Pipe Out throttle
}
Code language: JavaScript (javascript)
Determine whether the Opal Kelly device that will be connected to the PC is a USB 3.0 or USB 2.0 device. Then add the resetTargetDevice
method that corresponds to the type of device.
// USB 3.0 Device Reset
const resetTargetDevice = async () => {
props.fpgaDataPort.setWireInValue(0x00, (0 << 2) | (0 << 1) | 1, 0xffffffff); // MODE=COUNT | SET_THROTTLE=0 | RESET=1
await props.fpgaDataPort.updateWireIns();
props.fpgaDataPort.setWireInValue(0x00, (0 << 2) | (0 << 1) | 0, 0xffffffff); // MODE=COUNT | SET_THROTTLE=0 | RESET=0
await props.fpgaDataPort.updateWireIns();
}
Code language: JavaScript (javascript)
// USB 2.0 Device Reset
const resetTargetDevice = async () => {
props.fpgaDataPort.setWireInValue(0x00, (0 << 5) | (0 << 4) | (1 << 2), 0xffffffff); // SET_THROTTLE=0 | MODE=COUNT | RESET=1
await props.fpgaDataPort.updateWireIns();
props.fpgaDataPort.setWireInValue(0x00, (0 << 5) | (0 << 4) | (0 << 2), 0xffffffff); // SET_THROTTLE=0 | MODE=COUNT | RESET=0
await props.fpgaDataPort.updateWireIns();
}
Code language: JavaScript (javascript)
Add Read Button Event Handler
Add an event handler named onReadDataButtonClick
to the FrontPanel
component class. This handler will read data from a PipeOut endpoint and verify that the data retrieved is correct.
const onReadDataButtonClick = () => {
props.workQueue.post(async () => {
initializeTargetDevice();
await resetTargetDevice();
setReadStatus("Reading...");
const buffer: ArrayBuffer = new ArrayBuffer(1024);
const bytesRead = await props.fpgaDataPort.readFromPipeOut(0xa0, 1024, buffer);
// Verify that the data was read sucessfully
let isVerified: boolean = bytesRead === 1024;
if(isVerified) {
const dataArray = new Uint8Array(buffer);
const mask = 0xffffffff >>> (32 - props.fpgaDataPort.hostInterfaceInfo.pipeWidth);
const byteWidth = props.fpgaDataPort.hostInterfaceInfo.pipeWidth / 8;
let nextValue = 0x00000001;
for (let index = 0; (index < dataArray.length) && isVerified; index += byteWidth) {
switch(byteWidth) {
case 1: // 8-bit
isVerified = dataArray[index] === nextValue;
break;
case 2: // 16-bit
isVerified = (dataArray[index] | (dataArray[index + 1] << 8)) === nextValue;
break;
case 4: // 32-bit
isVerified = (dataArray[index] | (dataArray[index + 1] << 8) | (dataArray[index + 2] << 16) | (dataArray[index + 3] << 24)) === nextValue;
break;
}
nextValue = (nextValue + 1) % mask;
}
}
if(isVerified) {
setReadStatus(`Read ${bytesRead} of ${buffer.byteLength} bytes successfully.`);
}
else {
setReadStatus(`Failed to read ${buffer.byteLength} bytes.`);
}
});
}
Code language: JavaScript (javascript)
Add Write Button Event Handler
Add an event handler named onWriteDataButtonClick
to the FrontPanel
component class. This handler will write data to a PipeIn endpoint then query a WireOut endpoint to whether the data received by the device was correct.
const onWriteDataButtonClick = () => {
props.workQueue.post(async () => {
initializeTargetDevice();
await resetTargetDevice();
setWriteStatus("Writing...");
const data: ArrayBuffer = new ArrayBuffer(1024);
const dataArray = new Uint8Array(data);
// Write data to the data array
const byteWidth = props.fpgaDataPort.hostInterfaceInfo.pipeWidth / 8;
let nextValue = 0x00000001;
for (let index = 0; index < dataArray.length; index += byteWidth) {
switch(byteWidth) {
case 1: // 8-bit
dataArray[index] = nextValue;
break;
case 2: // 16-bit
dataArray[index] = nextValue & 0xff;
dataArray[index + 1] = (nextValue >> 8) & 0xff;
break;
case 4: // 32-bit
dataArray[index] = nextValue & 0xff;
dataArray[index + 1] = (nextValue >> 8) & 0xff;
dataArray[index + 2] = (nextValue >> 16) & 0xff;
dataArray[index + 3] = (nextValue >> 24) & 0xff;
break;
}
nextValue = (nextValue + 1) % 0xffffffff;
}
const bytesWritten = await props.fpgaDataPort.writeToPipeIn(0x80, 1024, data);
// Verify that the data was written sucessfully
await props.fpgaDataPort.updateWireOuts();
const verificationResult: number = props.fpgaDataPort.getWireOutValue(0x21);
if(verificationResult === 0) {
setWriteStatus(`Wrote ${bytesWritten} of ${data.byteLength} bytes successfully.`);
}
else {
setWriteStatus(`Failed to write ${data.byteLength} bytes.`);
}
});
}
Code language: JavaScript (javascript)
Add the UI Definition
At the top of the FrontPanel.tsx file change the import statement for @radix-ui/themes to the following:
import { Card, Flex, Box, Heading, Text, Button } from "@radix-ui/themes";
Code language: JavaScript (javascript)
Add the following return statement to the end of the FrontPanel function component. This will be the UI that will be provided. It has a Read and Separate write button and the status of each operation will be reported above the corresponding button.
return (
<Card>
<Flex direction="column" gap="4">
<Flex direction="column" gap="4">
<Heading size="5" weight="medium">Read Data</Heading>
<Box width="260px">
<Text size="2">{readStatus}</Text>
</Box>
<Flex direction="row" align="center" justify="end" gap="2">
<Button onClick={onReadDataButtonClick}>Read</Button>
</Flex>
</Flex>
<Flex direction="column" gap="4">
<Heading size="5" weight="medium">Write Data</Heading>
<Box width="260px">
<Text size="2">{writeStatus}</Text>
</Box>
<Flex direction="row" align="center" justify="end" gap="2">
<Button onClick={onWriteDataButtonClick}>Write</Button>
</Flex>
</Flex>
</Flex>
</Card>
);
Code language: JavaScript (javascript)
Build and Run the Application
Run the following command in the terminal:
npm run pack
Connect a USB cable from the PC to the OpalKelly XEM device and power on the device.
Launch the application from the command line.
The application should run, and clicking both the ‘Read Data’ and the ‘Write Data’ buttons should indicate that the corresponding operation was successful.