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

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.