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
- Node.js – Download | Node.js (nodejs.org)
- Node Package Manager – Downloading and installing Node.js and npm | npm Docs (npmjs.com)
- Source Code Editor
- FrontPanel Application version 6.0.0 or greater
- FrontPanel Application Template – FrontPanel App Template
- PipeTest Sample bitfile for you device – Opal Kelly Pins
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 FrontPanel
component 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.