Pipe Transfers

In this tutorial you will build a FrontPanel application that reads data from a PipeOut endpoint and writes data to a PipeIn endpoint.

Install Prereqisites

Setup the FrontPanel App Project

Copy the FrontPanel Application template project to your workspace directory and use npm install to install the project’s dependencies. Test that the project is configured correctly by packaging then running the application.

Add Target Device Initialize and Reset

In the project folder open the src/FrontPanel.tsx file in an code editor and locate the FrontPanelcomponent class.

Add an InitializeTargetDevice method to the FrontPanel component class.

    // Initialize Device
    private InitializeTargetDevice() {
        this.props.frontpanel.setWireInValue(0x03, 0x0, 0xffffffff);         // Apply fixed pattern
        this.props.frontpanel.setWireInValue(0x02, 0xffffffff, 0xffffffff);  // Pipe In throttle
        this.props.frontpanel.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
    private async ResetTargetDevice() {
        this.props.frontpanel.setWireInValue(0x00, (0 << 2) | (0 << 1) | 1, 0xffffffff); // MODE=COUNT | SET_THROTTLE=0 | RESET=1
        await this.props.frontpanel.updateWireIns();

        this.props.frontpanel.setWireInValue(0x00, (0 << 2) | (0 << 1) | 0, 0xffffffff); // MODE=COUNT | SET_THROTTLE=0 | RESET=0
        await this.props.frontpanel.updateWireIns();
    }Code language: JavaScript (javascript)
    // USB 2.0 Device Reset
    private async ResetTargetDevice() {
        this.props.frontpanel.setWireInValue(0x00, (0 << 5) | (0 << 4) | (1 << 2), 0xffffffff); // SET_THROTTLE=0 | MODE=COUNT | RESET=1
        await this.props.frontpanel.updateWireIns();

        this.props.frontpanel.setWireInValue(0x00, (0 << 5) | (0 << 4) | (0 << 2), 0xffffffff); // SET_THROTTLE=0 | MODE=COUNT | RESET=0
        await this.props.frontpanel.updateWireIns();
    }Code language: JavaScript (javascript)

Add Read and Write Status Message State Variables

In the src/FrontPanel.tsx file, locate the FrontPanelState definition and add two members to store a message indicating the status of the read and write operations.

export interface FrontPanelState {
    readStatusMessage: string;
    writeStatusMessage: string;
}Code language: CSS (css)
    constructor(props: FrontPanelProps) {
        super(props);

        this.state = {
            readStatusMessage: "Ready",
            writeStatusMessage: "Ready"
        };
    }Code language: JavaScript (javascript)

Add Read and Write Button Eventhandlers

In the src/FrontPanel.tsx file, locate the FrontPanel component class.

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.

    private OnReadDataButtonClick() {
        this.props.workQueue.post(async () => {
            this.InitializeTargetDevice();
            await this.ResetTargetDevice();

            this.setState({ readStatusMessage: "Reading..." });
            const buffer: ArrayBuffer = new ArrayBuffer(1024);

            const bytesRead = await this.props.frontpanel.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 - this.props.frontpanel.hostInterfaceInfo.pipeWidth);
                const byteWidth = this.props.frontpanel.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) {
                this.setState({ readStatusMessage: `Read ${bytesRead} of ${buffer.byteLength} bytes successfully.` });
            }
            else {
                this.setState({ readStatusMessage: `Failed to read ${buffer.byteLength} bytes.` });
            }
        });
    }Code language: JavaScript (javascript)

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.

    private OnWriteDataButtonClick() {
        this.props.workQueue.post(async () => {
            this.InitializeTargetDevice();
            await this.ResetTargetDevice();

            this.setState({ writeStatusMessage: "Writing..." });

            const data: ArrayBuffer = new ArrayBuffer(1024);

            const dataArray = new Uint8Array(data);

            // Write data to the data array
            const byteWidth = this.props.frontpanel.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 this.props.frontpanel.writeToPipeIn(0x80, 1024, data);

            // Verify that the data was written sucessfully
            await this.props.frontpanel.updateWireOuts();
            const verificationResult: number = this.props.frontpanel.getWireOutValue(0x21);

            if(verificationResult === 0) {
                this.setState({ writeStatusMessage: `Wrote ${bytesWritten} of ${data.byteLength} bytes successfully.` });
            }
            else {
                this.setState({ writeStatusMessage: `Failed to write ${data.byteLength} bytes.` });
            }
        });
    }Code language: JavaScript (javascript)

Add FrontPanel UI Components

In the src/FrontPanel.tsx file, locate the render() function definition for the FrontPanel component.

    render() {
        return (
            <FrontPanelContext
                device={this.props.frontpanel}
                workQueue={this.props.workQueue}>
                <div className="ControlPanel">{/* TODO: Add your FrontPanel controls here */}</div>
            </FrontPanelContext>
        );
    }Code language: JavaScript (javascript)
import {
    Button
} from "@opalkelly/frontpanel-react-components";Code language: JavaScript (javascript)

Add two Button components to capture onButtonClick events and perform a read or write operation.

    render() {
        return (
            <div>
                <div className="ControlPanel">
                    <span>{this.state.readStatusMessage}</span>
                    <Button 
                        label="Read Data"
                        tooltip="Read data from PipeOut endpoint"
                        onButtonClick={this.OnReadDataButtonClick.bind(this)}
                        />
                </div>
                <div className="ControlPanel">
                    <span>{this.state.writeStatusMessage}</span>
                    <Button 
                        label="Write Data"
                        tooltip="Write data to PipeIn endpoint"
                        onButtonClick={this.OnWriteDataButtonClick.bind(this)}
                        />
                </div>
            </div>
        );
    }Code language: JavaScript (javascript)

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 and copy or move it to the assets folder at the root of the application project.

Open the src/index.tsx file using the code editor and add an import statement that specifies the location of the pipetest.bit file relative to the source file.

import "../assets/pipetest.bit";Code language: JavaScript (javascript)

This import statement tells the Webpack bundler to include this file in the application bundle it generates.

Open the App.tsx file using the code editor and set the configurationFilename parameter passed to the initializeDevice() function to pipetest.bit.

function App() {
    const [frontpanel, setFrontPanel] = React.useState<IFrontPanel>();

    React.useEffect(() => {
        let device: IDevice;

        DeviceWorkQueue.post(async () => {
            try {
                device = await initializeDevice("", "pipetest.bit");

                const frontpanel = await device.getFPGA().getFrontPanel();

                setFrontPanel(frontpanel);
            }
            catch (error) {
                device?.close();
                console.error(error);
            }
        });Code language: JavaScript (javascript)

Build and Run the Application

Run the following command in the terminal:

npm run build

Connect a USB cable from the PC to the OpalKelly XEM device and power on the device.

Launch the FrontPanel Platform application and in the Open dialog navigate to the folder where the FrontPanel App package is generated and select the app.asar file.

The application should run, and clicking both the ‘Read Data’ and the ‘Write Data’ buttons should indicate that the corresponding operation was successful.