Skip to content

Commit c900248

Browse files
MrSidimsbader
authored andcommitted
[SYCL][FPGA] Implement IO pipes interface
kernel_readable_io_pipe and kernel_writeable_io_pipe classes were added. Signed-off-by: Dmitry Sidorov <[email protected]>
1 parent 7d493dd commit c900248

File tree

4 files changed

+331
-0
lines changed

4 files changed

+331
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
IO pipes design
2+
===============
3+
4+
Requirements
5+
------------
6+
- Device shall be able to distinguish kernel-to-kernel pipes and I/O pipes;
7+
- No changes shall be in SYCL pipe specification;
8+
- I/O pipe namings/IDs are provided by a vendor in a separated header, like:
9+
.. code:: cpp
10+
namespace intelfpga {
11+
template <unsigned ID>
12+
struct ethernet_pipe_id {
13+
static constexpr unsigned id = ID;
14+
};
15+
using ethernet_read_pipe =
16+
cl::sycl::intel::kernel_readable_io_pipe<ethernet_pipe_id<0>, int, 0>;
17+
using ethernet_write_pipe =
18+
cl::sycl::intel::kernel_writeable_io_pipe<ethernet_pipe_id<1>, int, 0>;
19+
}
20+
21+
Thus, the user interacts only with vendor-defined pipe objects.
22+
23+
Links
24+
-----
25+
.. _Spec: https://github.com/intel/llvm/blob/sycl/sycl/doc/extensions/DataFlowPipes/data_flow_pipes.asciidoc
26+
.. _Interesting comment from Ronan: https://github.com/intel/llvm/pull/635#discussion_r325851766
27+
28+
Summary
29+
-------
30+
This document describes a design of I/O pipes implementation in SYCL compiler.
31+
It includes changes in SYCL library, clang and SPIR-V to LLVM IR translator.
32+
It adds extra attribute '__attribute__((io_pipe_id(ID)))' that generates
33+
metadata, attached to a pipe storage declaration. By this metadata a backend can
34+
recognize I/O pipe and distinguish different I/O pipes from each other.
35+
36+
There is another notable solution which was proposed by Ronan (see the link
37+
above): don't make any compiler/library changes - just make backend recognizing
38+
I/O pipe by demangling it's name. This proposal wasn't picked, because it will
39+
make backend support of the feature more difficult. So far we already have
40+
two devices' backends that support SYCL_INTEL_data_flow_pipes extension
41+
(Intel FPGA HW and Intel FPGA emulator) and in the future this number may
42+
increase. So efforts put in compiler implementation shall be payed off even
43+
more.
44+
45+
clang
46+
-----
47+
Need to implement additional attribute, that possible to attach to pipe storage
48+
declaration. The attribute shall accept a compile-time known integer argument
49+
(the pipe ID). With the attribute applied, clang generates a metadata attached
50+
the to pipe storage declaration, that contains the I/O pipe ID (argument).
51+
52+
llvm-spirv translator
53+
---------------------
54+
Need to implement additional decoration, that saves the I/O pipe ID information,
55+
that can be collected from a metadata attached to the pipe storage object.
56+
57+
SYCL implementation in headers
58+
------------------------------
59+
Following the spec, we need to add two more classes for pipes:
60+
- 'kernel_readable_io_pipe'
61+
- 'kernel_writeable_io_pipe'
62+
63+
with the same member functions and fields as it is already done for pipe class.
64+
65+
The attribute should be attached to a pipe storage declaration in the headers
66+
and it would be looking like:
67+
.. code:: cpp
68+
static constexpr int32_t ID = name::id;
69+
static constexpr struct ConstantPipeStorage
70+
m_Storage __attribute__((io_pipe_id(ID))) = {m_Size, m_Alignment, m_Capacity};
71+
72+
73+
where 'name' is some class used in pipe type construction, like:
74+
.. code:: cpp
75+
using pipe_type = pipe<name, dataT, min_capacity>;
76+
77+
When specific io_pipe_def.h is included in a user's code and 'name' is mapped to
78+
the pipe name defined in this header (for example 'ethernet_pipe_id' structure
79+
defined above) 'name::id' returns the actual I/O pipe ID (compile-time known
80+
integer constant) that is passed as the attribute's argument and used to
81+
indentify the I/O pipe in some backend.

sycl/include/CL/sycl/intel/pipes.hpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,112 @@ template <class name, class dataT, int32_t min_capacity = 0> class pipe {
8484
#endif // __SYCL_DEVICE_ONLY__
8585
};
8686

87+
// IO pipes that provide interface to connect with hardware peripheral.
88+
// Their name aliases are defined in vendor-provided header, below you can see
89+
// an example of this header. There are defined aliases to ethernet_read_pipe
90+
// and ethernet_write_pipe that users can use in their code to connect with
91+
// HW peripheral.
92+
/* namespace intelfpga {
93+
template <int32_t ID>
94+
struct ethernet_pipe_id {
95+
static constexpr int32_t id = ID;
96+
};
97+
98+
template <class dataT, size_t min_capacity>
99+
using ethernet_read_pipe =
100+
kernel_readable_io_pipe<ethernet_pipe_id<0>, dataT, min_capacity>;
101+
102+
template <class dataT, size_t min_capacity>
103+
using ethernet_write_pipe =
104+
kernel_writeable_io_pipe<ethernet_pipe_id<1>, dataT, min_capacity>;
105+
} // namespace intelfpga */
106+
107+
template <class name, class dataT, size_t min_capacity = 0>
108+
class kernel_readable_io_pipe {
109+
public:
110+
// Non-blocking pipes
111+
// Reading from pipe is lowered to SPIR-V instruction OpReadPipe via SPIR-V
112+
// friendly LLVM IR.
113+
static dataT read(bool &Success) {
114+
#ifdef __SYCL_DEVICE_ONLY__
115+
RPipeTy<dataT> RPipe =
116+
__spirv_CreatePipeFromPipeStorage_read<dataT>(&m_Storage);
117+
dataT TempData;
118+
Success = !static_cast<bool>(
119+
__spirv_ReadPipe(RPipe, &TempData, m_Size, m_Alignment));
120+
return TempData;
121+
#else
122+
assert(!"Pipes are not supported on a host device!");
123+
#endif // __SYCL_DEVICE_ONLY__
124+
}
125+
126+
// Blocking pipes
127+
// Reading from pipe is lowered to SPIR-V instruction OpReadPipe via SPIR-V
128+
// friendly LLVM IR.
129+
static dataT read() {
130+
#ifdef __SYCL_DEVICE_ONLY__
131+
RPipeTy<dataT> RPipe =
132+
__spirv_CreatePipeFromPipeStorage_read<dataT>(&m_Storage);
133+
dataT TempData;
134+
__spirv_ReadPipeBlockingINTEL(RPipe, &TempData, m_Size, m_Alignment);
135+
return TempData;
136+
#else
137+
assert(!"Pipes are not supported on a host device!");
138+
#endif // __SYCL_DEVICE_ONLY__
139+
}
140+
141+
private:
142+
static constexpr int32_t m_Size = sizeof(dataT);
143+
static constexpr int32_t m_Alignment = alignof(dataT);
144+
static constexpr int32_t m_Capacity = min_capacity;
145+
static constexpr int32_t ID = name::id;
146+
#ifdef __SYCL_DEVICE_ONLY__
147+
static constexpr struct ConstantPipeStorage m_Storage
148+
__attribute__((io_pipe_id(ID))) = {m_Size, m_Alignment, m_Capacity};
149+
#endif // __SYCL_DEVICE_ONLY__
150+
};
151+
152+
template <class name, class dataT, size_t min_capacity = 0>
153+
class kernel_writeable_io_pipe {
154+
public:
155+
// Non-blocking pipes
156+
// Writing to pipe is lowered to SPIR-V instruction OpWritePipe via SPIR-V
157+
// friendly LLVM IR.
158+
static void write(const dataT &Data, bool &Success) {
159+
#ifdef __SYCL_DEVICE_ONLY__
160+
WPipeTy<dataT> WPipe =
161+
__spirv_CreatePipeFromPipeStorage_write<dataT>(&m_Storage);
162+
Success = !static_cast<bool>(
163+
__spirv_WritePipe(WPipe, &Data, m_Size, m_Alignment));
164+
#else
165+
assert(!"Pipes are not supported on a host device!");
166+
#endif // __SYCL_DEVICE_ONLY__
167+
}
168+
169+
// Blocking pipes
170+
// Writing to pipe is lowered to SPIR-V instruction OpWritePipe via SPIR-V
171+
// friendly LLVM IR.
172+
static void write(const dataT &Data) {
173+
#ifdef __SYCL_DEVICE_ONLY__
174+
WPipeTy<dataT> WPipe =
175+
__spirv_CreatePipeFromPipeStorage_write<dataT>(&m_Storage);
176+
__spirv_WritePipeBlockingINTEL(WPipe, &Data, m_Size, m_Alignment);
177+
#else
178+
assert(!"Pipes are not supported on a host device!");
179+
#endif // __SYCL_DEVICE_ONLY__
180+
}
181+
182+
private:
183+
static constexpr int32_t m_Size = sizeof(dataT);
184+
static constexpr int32_t m_Alignment = alignof(dataT);
185+
static constexpr int32_t m_Capacity = min_capacity;
186+
static constexpr int32_t ID = name::id;
187+
#ifdef __SYCL_DEVICE_ONLY__
188+
static constexpr struct ConstantPipeStorage m_Storage
189+
__attribute__((io_pipe_id(ID))) = {m_Size, m_Alignment, m_Capacity};
190+
#endif // __SYCL_DEVICE_ONLY__
191+
};
192+
87193
} // namespace intel
88194
} // namespace sycl
89195
} // __SYCL_INLINE_NAMESPACE(cl)
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// RUN: %clangxx -fsycl %s -o %t.out
2+
// RUNx: %ACC_RUN_PLACEHOLDER %t.out
3+
//==------------ fpga_io_pipes.cpp - SYCL FPGA pipes test ------------------==//
4+
//
5+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6+
// See https://llvm.org/LICENSE.txt for license information.
7+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8+
//
9+
//===----------------------------------------------------------------------===//
10+
#include <CL/sycl.hpp>
11+
#include <CL/sycl/intel/fpga_extensions.hpp>
12+
#include <fstream>
13+
#include <iostream>
14+
15+
#include "io_pipe_def.h"
16+
17+
// TODO: run is disabled, since no support added in FPGA backend yet. Check
18+
// implementation correctness from CXX and SYCL languages perspective.
19+
20+
// This test is supposed to be run only on Intel FPGA emulator. Change it when
21+
// we have more experience with IO pipe feature in SYCL.
22+
// The emulator creates files (one for I pipe, another for O pipe) with the
23+
// appropriate naming, where a data flowing through a pipe can be stored.
24+
// So in the test we need to create these files and use them appropriately.
25+
// The name is taken as IO pipe ID.
26+
const size_t InputData = 42;
27+
const std::string InputFileName = "0.txt";
28+
const std::string OutputFileName = "1.txt";
29+
30+
void createInputFile(const std::string &filename) {
31+
std::ofstream Input(filename);
32+
if (Input.is_open()) {
33+
Input << InputData;
34+
Input.close();
35+
}
36+
}
37+
38+
int validateOutputFile(const std::string &filename) {
39+
std::ifstream Output(filename);
40+
std::string Line;
41+
std::vector<size_t> Result;
42+
if (Output.is_open()) {
43+
// In the test we write only one number into the pipe, but a backend might
44+
// have a bug of incorrect interpretetion of capacity of the pipe. In this
45+
// case let's read all the lines of the output file to catch this.
46+
while (std::getline(Output, Line))
47+
Result.push_back(stoi(Line));
48+
Output.close();
49+
}
50+
if (Result.size() != 1 || Result[0] != InputData) {
51+
std::cout << "Result mismatches " << Result[0] << " Vs expected "
52+
<< InputData << std::endl;
53+
return -1;
54+
}
55+
56+
return 0;
57+
}
58+
59+
// Test for simple non-blocking pipes
60+
int test_io_nb_pipe(cl::sycl::queue Queue) {
61+
createInputFile(InputFileName);
62+
63+
cl::sycl::buffer<int, 1> writeBuf(1);
64+
Queue.submit([&](cl::sycl::handler &cgh) {
65+
auto write_acc = writeBuf.get_access<cl::sycl::access::mode::write>(cgh);
66+
67+
cgh.single_task<class nb_io_transfer>([=]() {
68+
bool SuccessCodeI = false;
69+
do {
70+
write_acc[0] = intelfpga::ethernet_read_pipe::read(SuccessCodeI);
71+
} while (!SuccessCodeI);
72+
bool SuccessCodeO = false;
73+
do {
74+
intelfpga::ethernet_write_pipe::write(write_acc[0], SuccessCodeO);
75+
} while (!SuccessCodeO);
76+
});
77+
});
78+
79+
auto readHostBuffer = writeBuf.get_access<cl::sycl::access::mode::read>();
80+
if (readHostBuffer[0] != InputData) {
81+
std::cout << "Read from a file mismatches " << readHostBuffer[0]
82+
<< " Vs expected " << InputData << std::endl;
83+
84+
return -1;
85+
}
86+
87+
return validateOutputFile(OutputFileName);
88+
}
89+
90+
// Test for simple blocking pipes
91+
int test_io_bl_pipe(cl::sycl::queue Queue) {
92+
createInputFile(InputFileName);
93+
94+
cl::sycl::buffer<int, 1> writeBuf(1);
95+
Queue.submit([&](cl::sycl::handler &cgh) {
96+
auto write_acc = writeBuf.get_access<cl::sycl::access::mode::write>(cgh);
97+
98+
cgh.single_task<class bl_io_transfer>([=]() {
99+
write_acc[0] = intelfpga::ethernet_read_pipe::read();
100+
intelfpga::ethernet_write_pipe::write(write_acc[0]);
101+
});
102+
});
103+
104+
auto readHostBuffer = writeBuf.get_access<cl::sycl::access::mode::read>();
105+
if (readHostBuffer[0] != InputData) {
106+
std::cout << "Read from a file mismatches " << readHostBuffer[0]
107+
<< " Vs expected " << InputData << std::endl;
108+
109+
return -1;
110+
}
111+
112+
return validateOutputFile(OutputFileName);
113+
}
114+
115+
int main() {
116+
cl::sycl::queue Queue{cl::sycl::intel::fpga_emulator_selector{}};
117+
118+
if (!Queue.get_device()
119+
.get_info<cl::sycl::info::device::kernel_kernel_pipe_support>()) {
120+
std::cout << "SYCL_INTEL_data_flow_pipes not supported, skipping"
121+
<< std::endl;
122+
return 0;
123+
}
124+
125+
// Non-blocking pipes
126+
int Result = test_io_nb_pipe(Queue);
127+
128+
// Blocking pipes
129+
Result &= test_io_bl_pipe(Queue);
130+
131+
return Result;
132+
}

sycl/test/fpga_tests/io_pipe_def.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include <CL/sycl/intel/fpga_extensions.hpp>
2+
3+
namespace intelfpga {
4+
template <unsigned ID> struct ethernet_pipe_id {
5+
static constexpr unsigned id = ID;
6+
};
7+
8+
using ethernet_read_pipe =
9+
sycl::intel::kernel_readable_io_pipe<ethernet_pipe_id<0>, int, 0>;
10+
using ethernet_write_pipe =
11+
sycl::intel::kernel_writeable_io_pipe<ethernet_pipe_id<1>, int, 0>;
12+
} // namespace intelfpga

0 commit comments

Comments
 (0)