From 227710d0a2c76adad22b116ed83ac68552df9b0f Mon Sep 17 00:00:00 2001 From: tyoungsc Date: Tue, 4 May 2021 12:46:30 -0400 Subject: [PATCH 01/31] First commit for merge sort design Signed-off-by: tyoungsc --- .../merge_sort/CMakeLists.txt | 20 + .../ReferenceDesigns/merge_sort/License.txt | 23 ++ .../ReferenceDesigns/merge_sort/README.md | 203 ++++++++++ .../ReferenceDesigns/merge_sort/basic.png | Bin 0 -> 49248 bytes .../merge_sort/basic_runtime_graph.png | Bin 0 -> 41398 bytes .../merge_sort/merge_sort.sln | 25 ++ .../merge_sort/merge_sort.vcxproj | 174 +++++++++ .../merge_sort/merge_unit.png | Bin 0 -> 25222 bytes .../merge_sort/parallel_tree.png | Bin 0 -> 70175 bytes .../ReferenceDesigns/merge_sort/sample.json | 61 +++ .../ReferenceDesigns/merge_sort/sort_api.png | Bin 0 -> 13241 bytes .../merge_sort/src/CMakeLists.txt | 107 ++++++ .../merge_sort/src/Consume.hpp | 36 ++ .../ReferenceDesigns/merge_sort/src/Merge.hpp | 122 ++++++ .../merge_sort/src/MergeSort.hpp | 348 ++++++++++++++++++ .../ReferenceDesigns/merge_sort/src/Misc.hpp | 64 ++++ .../merge_sort/src/Produce.hpp | 64 ++++ .../merge_sort/src/Shuffle.hpp | 60 +++ .../merge_sort/src/UnrolledLoop.hpp | 185 ++++++++++ .../merge_sort/src/merge_sort.cpp | 335 +++++++++++++++++ .../merge_sort/third-party-programs.txt | 253 +++++++++++++ 21 files changed, 2080 insertions(+) create mode 100755 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/CMakeLists.txt create mode 100755 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/License.txt create mode 100755 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/README.md create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/basic.png create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/basic_runtime_graph.png create mode 100755 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.sln create mode 100755 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.vcxproj create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_unit.png create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/parallel_tree.png create mode 100755 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/sample.json create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/sort_api.png create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/CMakeLists.txt create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Consume.hpp create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Merge.hpp create mode 100755 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp create mode 100755 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Misc.hpp create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Produce.hpp create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Shuffle.hpp create mode 100755 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/UnrolledLoop.hpp create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/merge_sort.cpp create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/third-party-programs.txt diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/CMakeLists.txt b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/CMakeLists.txt new file mode 100755 index 0000000000..53394170b4 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/CMakeLists.txt @@ -0,0 +1,20 @@ +if(UNIX) + # Direct CMake to use dpcpp rather than the default C++ compiler/linker + set(CMAKE_CXX_COMPILER dpcpp) +else() # Windows + # Force CMake to use dpcpp rather than the default C++ compiler/linker + # (needed on Windows only) + include (CMakeForceCompiler) + CMAKE_FORCE_CXX_COMPILER (dpcpp IntelDPCPP) + include (Platform/Windows-Clang) +endif() + +cmake_minimum_required (VERSION 3.4) + +project(CRR CXX) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_subdirectory (src) diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/License.txt b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/License.txt new file mode 100755 index 0000000000..7c8b8a36c6 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/License.txt @@ -0,0 +1,23 @@ +Copyright Intel Corporation + +SPDX-License-Identifier: MIT +https://opensource.org/licenses/MIT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/README.md b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/README.md new file mode 100755 index 0000000000..ade1ea98ad --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/README.md @@ -0,0 +1,203 @@ +# Merge Sort +This DPC++ reference design demonstrates a highly paramaterizable merge sort algorithm on an FPGA. + +***Documentation***: The [DPC++ FPGA Code Samples Guide](https://software.intel.com/content/www/us/en/develop/articles/explore-dpcpp-through-intel-fpga-code-samples.html) helps you to navigate the samples and build your knowledge of DPC++ for FPGA.
+The [oneAPI DPC++ FPGA Optimization Guide](https://software.intel.com/content/www/us/en/develop/documentation/oneapi-fpga-optimization-guide) is the reference manual for targeting FPGAs through DPC++.
+The [oneAPI Programming Guide](https://software.intel.com/en-us/oneapi-programming-guide) is a general resource for target-independent DPC++ programming. + +| Optimized for | Description +--- |--- +| OS | Linux* Ubuntu* 18.04; Windows* 10 +| Hardware | Intel® Programmable Acceleration Card (PAC) with Intel Arria® 10 GX FPGA
Intel® FPGA Programmable Acceleration Card (PAC) D5005 (with Intel Stratix® 10 SX)
Intel Xeon® CPU E5-1650 v2 @ 3.50GHz (host machine) +| Software | Intel® oneAPI DPC++ Compiler
Intel® FPGA Add-On for oneAPI Base Toolkit +| What you will learn | Implementing a merge sort algorithm on an FPGA. +| Time to complete | 1 hour (not including compile time) + +
+ +**Performance** +The performance data below was gathered using the Intel® FPGA PAC D5005 (with Intel Stratix® 10 SX) sorting `2^24=16777216` elements using 1-16 merge units and the best throughput across 5 seeds. + +| Merge Units | Execution time (ms) | Throughput (Melements/s) | +| :---------- | :-----------------: | :----------------------: | +| 1 | 1476 | 11 | +| 2 | 569.8 | 28 | +| 4 | 195.2 | 82 | +| 8 | 99.9 | 160 | +| 16 | 69.9 | 228 | + +## Purpose +This FPGA reference design demonstrates a highly paramaterizable merge sort design that utilizes the spatial computing of the FPGA. The basic merge sort algorithm is described [here](https://en.wikipedia.org/wiki/Merge_sort). See the [Additional Design Information Section](#additional-design-information) for more details on how the merge sort algorithm was implemented for the FPGA. + +## License +Code samples are licensed under the MIT license. See +[License.txt](https://github.com/oneapi-src/oneAPI-samples/blob/master/License.txt) for details. + +Third party program Licenses can be found here: [third-party-programs.txt](https://github.com/oneapi-src/oneAPI-samples/blob/master/third-party-programs.txt) + +## Building the Reference Design + +### Include Files +The include folder is located at `%ONEAPI_ROOT%\dev-utilities\latest\include` on your development system. + +### Running Code Samples in DevCloud +If running a sample in the Intel DevCloud, remember that you must specify the compute node (fpga_compile, fpga_runtime:arria10, or fpga_runtime:stratix10) and whether to run in batch or interactive mode. For more information, see the Intel® oneAPI Base Toolkit Get Started Guide ([https://devcloud.intel.com/oneapi/documentation/base-toolkit/](https://devcloud.intel.com/oneapi/documentation/base-toolkit/)). + +When compiling for FPGA hardware, it is recommended to increase the job timeout to 24h. + +### On a Linux* System +1. Install the design into a directory `build` from the design directory by running `cmake`: + + ``` + mkdir build + cd build + ``` + + If you are compiling for the Intel® PAC with Intel Arria® 10 GX FPGA, run `cmake` using the command: + + ``` + cmake .. + ``` + + If instead you are compiling for the Intel® FPGA PAC D5005 (with Intel Stratix® 10 SX), run `cmake` using the command: + + ``` + cmake .. -DFPGA_BOARD=intel_s10sx_pac:pac_s10 + ``` + +2. Compile the design through the generated `Makefile`. The following targets are provided, and they match the recommended development flow: + + * Compile for emulation (fast compile time, targets emulated FPGA device). + + ``` + make fpga_emu + ``` + + * Generate HTML performance report. Find the report in `merge_sort_report.prj/reports/report.html`directory. + + ``` + make report + ``` + + * Compile for FPGA hardware (longer compile time, targets FPGA device). + + ``` + make fpga + ``` + +3. (Optional) As the above hardware compile may take several hours to complete, FPGA precompiled binaries (compatible with Linux* Ubuntu* 18.04) can be downloaded here. + +### On a Windows* System +1. Generate the `Makefile` by running `cmake`. + ``` + mkdir build + cd build + ``` + To compile for the Intel® PAC with Intel Arria® 10 GX FPGA, run `cmake` using the command: + ``` + cmake -G "NMake Makefiles" .. + ``` + Alternatively, to compile for the Intel® FPGA PAC D5005 (with Intel Stratix® 10 SX), run `cmake` using the command: + ``` + cmake -G "NMake Makefiles" .. -DFPGA_BOARD=intel_s10sx_pac:pac_s10 + ``` + +2. Compile the design through the generated `Makefile`. The following build targets are provided, matching the recommended development flow: + * Compile for emulation (fast compile time, targets emulated FPGA device): + ``` + nmake fpga_emu + ``` + * Generate the optimization report: + ``` + nmake report + ``` + * An FPGA hardware target is not provided on Windows*. + +*Note:* The Intel® PAC with Intel Arria® 10 GX FPGA and Intel® FPGA PAC D5005 (with Intel Stratix® 10 SX) do not yet support Windows*. Compiling to FPGA hardware on Windows* requires a third-party or custom Board Support Package (BSP) with Windows* support. + +### In Third-Party Integrated Development Environments (IDEs) + +You can compile and run this Reference Design in the Eclipse* IDE (in Linux*) and the Visual Studio* IDE (in Windows*). For instructions, refer to the following link: [Intel® oneAPI DPC++ FPGA Workflows on Third-Party IDEs](https://software.intel.com/en-us/articles/intel-oneapi-dpcpp-fpga-workflow-on-ide) + +## Running the Reference Design + + 1. Run the sample on the FPGA emulator (the kernel executes on the CPU). + ``` + ./merge_sort.fpga_emu (Linux) + merge_sort.fpga_emu.exe (Windows) + ``` + +2. Run the sample on the FPGA device. + ``` + ./merge_sort.fpga (Linux) + ``` + +### Example of Output +You should see the following output in the console: + +1. When running on the FPGA emulator + ``` + Running sort 2 times for an input size of 32 using 4 merge units + Streaming data from device memory + PASSED + ``` + +2. When running on the FPGA device + ``` + Running sort 17 times for an input size of 16777216 using 16 merge units + Streaming data from host memory + Execution time: 69.9848 ms + Throughput: 228.621 Melements/s + PASSED + ``` + +## Additional Design Information +### Source Code Breakdown +The following source files can be found in the `src/` sub-directory. + +| File | Description +|:--- |:--- +|`merge_sort.cpp` | Contains the `main()` function and the top-level interfaces. +|`MergeSort.hpp` | The function to submit all of the merge sort kernels (`Shuffle`, `Produce`, `Merge`, and `Consume`) +|`Consume.hpp` | The `Consume` kernel for the merge unit +|`Merge.hpp` | The `Merge` kernel for the merge unit and the merge tree +|`Misc.hpp` | Miscellaneous helper functions +|`Produce.hpp` | The `Produce` kernel for the merge unit +|`Shuffle.hpp` | The `Shuffle` kernel +|`UnrolledLoop.hpp` | A templated-based loop unroller that unrolls loops in the compiler front end + +### Merge Sort Details +This section will describe how the merge sort design is layed out and how it takes advantage of the spatial computing of the FPGA.
+ +The figure below shows the conceptual view of the merge sort design to the user. The user streams data into a SYCL pipe and, after some delay, the element are streamed out of the sorter, in sorted order. The number of elements the merge sort design sorts can be a runtime parameter, but it must be a power of 2. However, this restriction can be worked around by padding the input stream with min/max elements, depending on the order of the sort. This technique is demonstrated in this design (see the `fpga_sort` function in *merge_sort.cpp*). + +sort_api + +This basis of the merge sort design is what we call a *merge unit*, which is shown in the figure below. A single merge unit streams in sorted lists of size `count` in parallel and merges them into a sorted list of size `2*count`. The lists are streamed in by the `Produce` kernel, which can be configured to stream the data from a SYCL pipe or device memory (e.g., DDR or HBM). This configuration can be easily made at either compile time or at runtime with a dynamic parameter that changes over time. Similarly, the `Consumer` kernel can stream data out either a SYCL pipe or to device memory. + +merge_unit + +A single merge unit requires `lg(N)` iterations to sort `N` elements. This requires the host enqueuing `lg(N)` iterations of the merge unit kernels that merge sublists of size {`1`, `2`, `4`, ...} into larger lists of size {`2`, `4`, `8`, ...}, respectively. This results in a timeline that looks like the figure below. + +basic_runtime_graph + +To improve performance, the merge sort design accepts a template parameter `units` which allows one to instantiate multiple instances of the merge unit, as shown in the figure below. Choosing the number of units is an area-performance tradeoff (note: the number of instantiated merge units must be a power of 2). In this design, each merge unit sorts a partition of the input data of size `N/units`. However, since the data is coming in one element at a time from a SYCL pipe, the data must be partitioned in the first iteration of the sort. This is done using the `Shuffle` kernel, which shuffles the data between the merge units and the producers (`ProduceA` and `ProduceB`) of each merge unit to perform the first iteration of the sort from the input pipe. + +parallel_tree + +After the merge units sort their `N/units`-sized partition, the partitions must be reduced into a single sorted list. There are two options to do this: (1) reuse the merge units to perform `lg(units)` more iterations to sort the partitions, or (2) create a merge tree to reduce the partitions into a single sorted list. Option (1) saves area at the expense of performance, since it has to perform additional sorting iterations. Option (2), which we choose for this design, improves performance by creating a merge tree to reduce the final partitions into a single sorted list. The `Merge` kernels in the merge tree (shown in the figure above) use the same kernel code that is used in the `Merge` kernel of the merge unit. + +Once the merge units perform their last iteration, they output to a pipe (instead of writing to device memory) that feed the merge tree. We found that the using a merge tree increased the throughput by roughly 18% over the option of reusing the merge units. It is worth noting that if the number of merge units is set to `1` by the user, the merge tree is omitted entirely. + +### Performance disclaimers +Tests document performance of components on a particular test, in specific systems. Differences in hardware, software, or configuration will affect actual performance. Consult other sources of information to evaluate performance as you consider your purchase. For more complete information about performance and benchmark results, visit [www.intel.com/benchmarks](www.intel.com/benchmarks). + +Performance results are based on testing as of July 29, 2020 and may not reflect all publicly available security updates. See configuration disclosure for details. No product or component can be absolutely secure. + +Intel technologies’ features and benefits depend on system configuration and may require enabled hardware, software or service activation. Performance varies depending on system configuration. Check with your system manufacturer or retailer or learn more at [intel.com](www.intel.com). + +The performance was measured by Intel on July 29, 2020. + +Intel and the Intel logo are trademarks of Intel Corporation or its subsidiaries in the U.S. and/or other countries. + +(C) Intel Corporation. diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/basic.png b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/basic.png new file mode 100644 index 0000000000000000000000000000000000000000..d53ad6e77efeac93fab7c2885cd77d0a2dd4bf0d GIT binary patch literal 49248 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 QzyJBCO+Oa>ZP)St1{>Z>$p8QV literal 0 HcmV?d00001 diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/basic_runtime_graph.png b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/basic_runtime_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..8b4c8ce172704b40f5a48b3ac79882d03cf78404 GIT binary patch literal 41398 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 ByXF7@ literal 0 HcmV?d00001 diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.sln b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.sln new file mode 100755 index 0000000000..4e67133f38 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.705 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "merge_sort", "merge_sort.vcxproj", "{ACDE6B7A-6F9A-428E-B040-CEDC5B1E2C79}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ACDE6B7A-6F9A-428E-B040-CEDC5B1E2C79}.Debug|x64.ActiveCfg = Debug|x64 + {ACDE6B7A-6F9A-428E-B040-CEDC5B1E2C79}.Debug|x64.Build.0 = Debug|x64 + {ACDE6B7A-6F9A-428E-B040-CEDC5B1E2C79}.Release|x64.ActiveCfg = Release|x64 + {ACDE6B7A-6F9A-428E-B040-CEDC5B1E2C79}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {97D1BD74-AAAB-4835-8F00-37A58B70871A} + EndGlobalSection +EndGlobal diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.vcxproj b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.vcxproj new file mode 100755 index 0000000000..9ff9d082d3 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.vcxproj @@ -0,0 +1,174 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + 15.0 + {acde6b7a-6f9a-428e-b040-cedc5b1e2c79} + Win32Proj + qrd + $(WindowsSDKVersion.Replace("\","")) + + + + Application + true + Intel(R) oneAPI DPC++ Compiler + Unicode + + + Application + false + Intel(R) oneAPI DPC++ Compiler + true + Unicode + + + Application + true + Intel(R) oneAPI DPC++ Compiler + Unicode + + + Application + false + Intel(R) oneAPI DPC++ Compiler + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + true + true + pch.h + $(ONEAPI_ROOT)dev-utilities\latest\include + + + Console + true + + + + + Use + Level3 + Disabled + true + true + pch.h + true + -DFPGA_EMULATOR -DFIXED_ITERATIONS=64 -DROWS_COMPONENT=128 -DCOLS_COMPONENT=128 %(AdditionalOptions) + + + $(ONEAPI_ROOT)dev-utilities\latest\include + + + Console + true + -Xsclock=360MHz;-Xsfp-relaxed;-fno-fast-math;-Xsparallel=2 + + + + + + Use + Level3 + MaxSpeed + true + true + true + true + pch.h + $(ONEAPI_ROOT)dev-utilities\latest\include + + + Console + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + true + true + pch.h + true + -DFPGA_EMULATOR -DFIXED_ITERATIONS=64 -DROWS_COMPONENT=128 -DCOLS_COMPONENT=128 %(AdditionalOptions) + + + $(ONEAPI_ROOT)dev-utilities\latest\include + + + Console + true + true + true + -Xsclock=330MHz;-Xsfp-relaxed;-Xsparallel=2 + + + + + + diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_unit.png b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_unit.png new file mode 100644 index 0000000000000000000000000000000000000000..ce12e1642f231c6cc0c7a02f6c3fe3820be6b04d GIT binary patch literal 25222 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

GTa}a^xeq@RA;V+E*q7-J?l+_2?~sdV`&1# zV@Z<7asTYH7JB=gVWaj@py01C4s-*jCoX`m(*|G3tDq~cnn}G+fTxB9$Q(%`~Uu(#IN$~Wq;6QjM(2r=F=rS z|1F8^;tgAxGYE$4tn8|-Fc1wy5X4%op!`x}luK#t@T}T^=J>y`+G1hhj-{Avv}W{PQwsBdq9bF7M{cFF;4PC?BcePmJDMOcdVzRgKW zS273?>|_Xb6Pwkc7O>`kL(oM&abILq|CC_nQJ61jO-gC7XKLvR?Z3K0jjkh$LurVB z)xdn7HuL>baKUUt7;+5w85~(-Ioto4K03N>FIMY)L^g5}H{iP^E_X@bsL~8q35Hi4 z$`+u*3a$bvI_Eipe&lTIb_#G)4i38K4X&-iW4n!qYd><*H&sUE_2BYIP8kVJnQikn zIcweXvM71(Nam}^nNa7~AH+@0bQh1xHopRPwm?!?&bSP1$hxv5hvDbjcEdCU4tTMK zu7p<&Jc2&rFu4}ot{N53mv18eg2bWJD6(jl{^g^gz?36$AKW#s( z5D2vyay}p4y!FzHi(Fh^jxXDX)+*29`r2siqw6_CXL(7Xuv$xF@fDFCd@S6JHQ8 zfKa?$raPp)IB)Yxc7_iy@#_IIThBV&p%EHU15NE;3FYpPvbTYR_(u;&G&%KE(1q;JyvIH(UV~*+%?mPDj1q0 z1u;Gv5S6bd>TF{MusQv-PMiD8Zc+w;P2y;8ZxV$Vm^NBje>xN3sr#=kKLeIBdm8YE zP2NCJKIDFX11k(MFglIZ;NAc}1E-V?Ctg*9xFu zHgL+nE4X4#^lE7FWgReN(A$RGRCo8lImpuSJ^{nSA^({!gnZ2-rPrp`qTrqhdFg*f z8~Yz980g~<*1vwKrEz@RRyR*iIS$0gvEZ-Hum4(d`fme@DV`3EFF^bL-4Nto@%C@h14-GG_ to set extra flags for FPGA backend compilation + +############################################################################### +### FPGA Emulator +############################################################################### +# To compile in a single command: +# dpcpp -fintelfpga -DFPGA_EMULATOR merge_sort.cpp -o merge_sort.fpga_emu +# CMake executes: +# [compile] dpcpp -fintelfpga -DFPGA_EMULATOR -o merge_sort.cpp.o -c merge_sort.cpp +# [link] dpcpp -fintelfpga merge_sort.cpp.o -o merge_sort.fpga_emu +add_executable(${EMULATOR_TARGET} ${SOURCE_FILE}) +set_target_properties(${EMULATOR_TARGET} PROPERTIES COMPILE_FLAGS "${EMULATOR_COMPILE_FLAGS}") +set_target_properties(${EMULATOR_TARGET} PROPERTIES LINK_FLAGS "${EMULATOR_LINK_FLAGS}") +add_custom_target(fpga_emu DEPENDS ${EMULATOR_TARGET}) + +############################################################################### +### Generate Report +############################################################################### +# To compile manually: +# dpcpp -fintelfpga -Xshardware -Xsboard= -fsycl-link=early merge_sort.cpp -o merge_sort_report.a +set(FPGA_EARLY_IMAGE ${TARGET_NAME}_report.a) +add_custom_target(report DEPENDS ${FPGA_EARLY_IMAGE}) +set(CMAKE_CXX_FLAGS_LIST ${CMAKE_CXX_FLAGS}) +separate_arguments(CMAKE_CXX_FLAGS_LIST) +set(HARDWARE_LINK_FLAGS_LIST ${HARDWARE_LINK_FLAGS}) +separate_arguments(HARDWARE_LINK_FLAGS_LIST) +add_custom_command(OUTPUT ${FPGA_EARLY_IMAGE} + COMMAND ${CMAKE_CXX_COMPILER} ${WIN_FLAG} ${CMAKE_CXX_FLAGS_LIST} ${HARDWARE_LINK_FLAGS_LIST} -fsycl-link=early ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE_FILE} -o ${CMAKE_BINARY_DIR}/${FPGA_EARLY_IMAGE} + DEPENDS ${SOURCE_FILE}) + # fsycl-link=early stops the compiler after RTL generation, before invoking Quartus + +############################################################################### +### FPGA Hardware +############################################################################### +# To compile in a single command: +# dpcpp -fintelfpga -Xshardware -Xsboard= merge_sort.cpp -o merge_sort.fpga +# CMake executes: +# [compile] dpcpp -fintelfpga -o merge_sort.cpp.o -c merge_sort.cpp +# [link] dpcpp -fintelfpga -Xshardware -Xsboard= merge_sort.cpp.o -o merge_sort.fpga +add_executable(${FPGA_TARGET} EXCLUDE_FROM_ALL ${SOURCE_FILE}) +add_custom_target(fpga DEPENDS ${FPGA_TARGET}) +set_target_properties(${FPGA_TARGET} PROPERTIES COMPILE_FLAGS "${HARDWARE_COMPILE_FLAGS}") +set_target_properties(${FPGA_TARGET} PROPERTIES LINK_FLAGS "${HARDWARE_LINK_FLAGS} -reuse-exe=${CMAKE_BINARY_DIR}/${FPGA_TARGET}") +# The -reuse-exe flag enables rapid recompilation of host-only code changes. +# See DPC++FPGA/GettingStarted/fast_recompile for details. + diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Consume.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Consume.hpp new file mode 100644 index 0000000000..2eba33b986 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Consume.hpp @@ -0,0 +1,36 @@ +#ifndef __CONSUME_HPP__ +#define __CONSUME_HPP__ + +#include +#include + +using namespace sycl; + +// +// Streams in data from a SYCL pipe and either writes it to memory +// (to_pipe == false) or writes it to a pipe (to_pipe == true) +// +template +event Consume(queue& q, ValueT *out_ptr, IndexT total_count, bool to_pipe) { + return q.submit([&](handler& h) { + h.single_task([=]() [[intel::kernel_args_restrict]] { + device_ptr out(out_ptr); + + for (IndexT i = 0; i < total_count; i++) { + // read in data from the input pipe + auto data = InPipe::read(); + + // write to either the output pipe or to device memory, depending + // on a runtime argument + if (to_pipe) { + OutPipe::write(data); + } else { + out[i] = data; + } + } + }); + }); +} + +#endif /* __CONSUME_HPP__ */ \ No newline at end of file diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Merge.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Merge.hpp new file mode 100644 index 0000000000..e1e78283f0 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Merge.hpp @@ -0,0 +1,122 @@ +#ifndef __MERGE_HPP__ +#define __MERGE_HPP__ + +#include +#include + +using namespace sycl; + +// +// Streams in a sorted list of size 'in_count` from both InPipeA and InPipeB +// and merges them into a single sorted list of size 'in_count*2' to OutPipe. +// Does this for total_count/(in_count*2) iterations. +// +template +event Merge(queue& q, IndexT total_count, IndexT in_count, + CompareFunc compare) { + // the output size is 2x the input size, since we are merging 2 sorted lists + // of size 'count' into a single sorted list + // NOTE: this is NOT computed on the FPGA, it is an argument to the kernel + const IndexT out_size = in_count * 2; + + return q.submit([&](handler& h) { + h.single_task([=] { + // the values read from pipe A and B, respectively + ValueT a, b; + + // signals that it is time to drain input pipe A and B, respectively + bool drain_a = false; + bool drain_b = false; + + // signals if the 'a' and 'b' inputs are valid, respectively + bool a_valid = false; + bool b_valid = false; + + // track the number of elements read from each input pipe + IndexT read_from_a = 0; + IndexT read_from_b = 0; + + // track the number of elements written to the output pipe for one + // output sub-array + IndexT written_out_inner = 0; + + // track the total number of elements written to the output pipe + IndexT written_out = 0; + + // signals that the current read is the last read from each input pipe + bool read_from_a_is_last = false; + bool read_from_b_is_last = false; + + // signals that the NEXT read will be the last read from each input pipe + bool next_read_from_a_is_last = (in_count == 1); + bool next_read_from_b_is_last = (in_count == 1); + + // repeat until we have seen all of the elements to sort + while (written_out != total_count) { + // read from InPipeA if 'a' is not valid and we aren't in the process + // of draining InPipeB. Vice versa for InPipeB. + if (!a_valid && !drain_b) { + // read from pipe A and mark the input as valid + a = InPipeA::read(); + a_valid = true; + + // 2-element shift register to track whether the element read from + // pipe A was the last element to read from the input pipe before + // draining pipe B. This shift register removes an addition and + // comparison from the critical path + read_from_a_is_last = next_read_from_a_is_last; + next_read_from_a_is_last = (read_from_a == in_count-2); + read_from_a++; + } + if (!b_valid && !drain_a) { + // read from pipe B and mark the input as valid + b = InPipeB::read(); + b_valid = true; + + // same technique as the if-case + read_from_b_is_last = next_read_from_b_is_last; + next_read_from_b_is_last = (read_from_b == in_count-2); + read_from_b++; + } + + // determine which element we want to output + bool choose_a = ((compare(a, b) || drain_a) && !drain_b); + ValueT out_data = choose_a ? a : b; + + // write the output + OutPipe::write(out_data); + written_out++; + + // check if we are switching to a new set of 'in_count' inputs + if (written_out_inner == out_size-1) { + // switching, so reset all internal counters and flags + drain_a = false; + drain_b = false; + a_valid = false; + b_valid = false; + read_from_a = 0; + read_from_b = 0; + read_from_a_is_last = false; + read_from_b_is_last = false; + next_read_from_a_is_last = (in_count == 1); + next_read_from_b_is_last = (in_count == 1); + written_out_inner = 0; + } else { + // increment the internal counter + written_out_inner++; + + // determine whether we are draining input pipe A or B, respectively + drain_a = drain_a | (read_from_b_is_last && !choose_a); + drain_b = drain_b | (read_from_a_is_last && choose_a); + + // if we chose 'a', it is no longer valid, otherwise 'b' is invalid + a_valid = !choose_a; + b_valid = choose_a; + } + } + }); + }); +} + +#endif /* __MERGE_HPP__ */ \ No newline at end of file diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp new file mode 100755 index 0000000000..e4aa97ca89 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp @@ -0,0 +1,348 @@ +#ifndef __MERGESORT_HPP__ +#define __MERGESORT_HPP__ + +#include +#include +#include +#include +#include + +#include +#include + +#include "Consume.hpp" +#include "Merge.hpp" +#include "Misc.hpp" +#include "Produce.hpp" +#include "Shuffle.hpp" +#include "Tuple.hpp" +#include "UnrolledLoop.hpp" + +using namespace sycl; + +/////////////////////////////////////////////////////////////// +// Convenient default comparators +struct LessThan { + template + bool operator()(T const &a, T const &b) const { + return a < b; + } +}; + +struct GreaterThan { + template + bool operator()(T const &a, T const &b) const { + return a > b; + } +}; +/////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////// +// Kernel and pipe ID classes for the merge units +template +class ProduceAKernelID; +template +class ProduceBKernelID; +template +class MergeKernelID; +template +class ConsumeKernelID; + +class ShuffleKernelID; + +class APipeID; +class BPipeID; +class MergePipeID; +class AShufflePipeID; +class BShufflePipeID; +class InternalOutPipeID; + +// Kernel and pipe ID classes for the merge tree +template +class MergeTreeMergeKernelID; + +class InternalMergeTreePipeID; +/////////////////////////////////////////////////////////////// + +// +// Submits all of the merge sort kernels necessary to sort 'count' elements. +// Returns all of the events to wait on. +// NOTE: there is no need to worry about returing a std::vector by value here; +// C++ return-value-optimization (RVO) will take care of it! +// +template +std::vector SubmitMergeSort(queue& q, size_t count, + ValueT* buf_0, ValueT* buf_1, Compare comp) { + // sanity check the number of merge units + static_assert(units >= 1); + static_assert(IsPow2(units)); + + // sanity check on IndexT + static_assert(std::is_integral_v); + + // ensure we have a valid compare function + static_assert(std::is_invocable_r_v, + "The 'Compare' function type must be invocable (i.e. operator()) with two" + "'ValueT' arguments and returning a boolean"); + + // depth of the merge tree to reduce the partial results of each merge unit + constexpr size_t kReductionLevels = Log2(units); + + // the various pipes connecting the different kernels + using APipes = PipeArray; + using BPipes = PipeArray; + using MergePipes = PipeArray; + using AShufflePipes = PipeArray; + using BShufflePipes = PipeArray; + using InternalOutPipes = PipeArray; + + // validate 'count' + if (count == 0) { + std::cerr << "ERROR: 'count' must be greater than 0\n"; + std::terminate(); + } else if (!IsPow2(count)) { + std::cerr << "ERROR: 'count' must be a power of 2\n"; + std::terminate(); + } else if (count < 2*units) { + std::cerr << "ERROR: 'count' must be at least 2x greater than " + << "the number of merge units (" << units << ")\n"; + std::terminate(); + } else if (count > std::numeric_limits::max()) { + std::cerr << "ERROR: the index type does not have bits to count to " + << "'count'\n"; + std::terminate(); + } + + // validate the input buffers + if (buf_0 == nullptr) { + std::cerr << "ERROR: 'buf_0' is nullptr\n"; + std::terminate(); + } + if (buf_1 == nullptr) { + std::cerr << "ERROR: 'buf_1' is nullptr\n"; + std::terminate(); + } + + // double buffering is more convenient with an array of pointers, + // so create one from the two buffers passed by the caller + ValueT* buf[2] = {buf_0, buf_1}; + + // using double buffering, so track the current buffer and have a simple + // lamda to compute the next buffer index + unsigned buf_idx = 0; + auto next_buf_idx = [](unsigned buf_idx) { + return buf_idx ^ 0x1; + }; + + // the number of elements each merge unit will sort + IndexT count_per_unit = count / units; + + // the number of sorting iterations each merge unit will perform + size_t iterations = Log2(count_per_unit); + + // memory to store the various merge unit and merge tree kernel events + std::array, units> produce_a_events, produce_b_events, + merge_events, consume_events; + std::array, kReductionLevels> mt_merge_events; + for (size_t i = 0; i < units; i++) { + produce_a_events[i].reserve(iterations); + produce_b_events[i].reserve(iterations); + merge_events[i].reserve(iterations); + consume_events[i].reserve(iterations); + } + + //////////////////////////////////////////////////////////////////////////// + // The initial shuffle stage which feeds the producers of each merge unit + // with data coming from the input pipe to the sorter + event shuffle_event = Shuffle(q, count); + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + // Launching all of the merge unit kernels + + // start with inputs of size 1 + IndexT in_count = 1; + + for (size_t i = 0; i < iterations; i++) { + // Producers will read from pipe on first iteration (from shuffle), + // Consumers will write to pipe on last iteration + bool producer_from_pipe = (i == 0); + bool consumer_to_pipe = (i == (iterations-1)); + + // launch the merge unit kernels for this iteration of the sort + UnrolledLoop([&](auto u) { + // Except for the first iteration, producers for the current iteration + // must wait for the consumer to be done writing to global memory from the + // previous iteration. This is coarse grain synchronization between the + // producer and consumer of each merge unit. + std::vector wait_events; + if (i != 0) { + wait_events.push_back(consume_events[u].back()); + } + + // the temporary device buffers reside in a single device allocation, + // so compute the offset into the buffer for each merge unit. + size_t unit_buf_offset = count_per_unit * u; + + // the intra merge unit pipes + using APipe = typename APipes::template pipe; + using BPipe = typename BPipes::template pipe; + using MergePipe = typename MergePipes::template pipe; + using AShufflePipe = typename AShufflePipes::template pipe; + using BShufflePipe = typename BShufflePipes::template pipe; + + // if there is only 1 merge unit, there will be no merge tree, + // so the single unit's output pipe will be the entire sort output pipe + using InternalOutPipe = + std::conditional_t<(units == 1), + OutPipe, + typename InternalOutPipes::template pipe>; + + // get device pointers for this merge unit's producers and consumer + ValueT* in_buf_a = buf[buf_idx] + unit_buf_offset; + ValueT* in_buf_b = buf[buf_idx] + unit_buf_offset + in_count; + ValueT* out_buf = buf[next_buf_idx(buf_idx)] + unit_buf_offset; + + //////////////////////////////////////////////////////////////////////// + // Enqueue the merge unit kernels + // Produce A + produce_a_events[u].push_back( + Produce, ValueT, IndexT, + AShufflePipe, APipe>(q, in_buf_a, count_per_unit, in_count, + producer_from_pipe, wait_events) + ); + + // Produce B + produce_b_events[u].push_back( + Produce, ValueT, IndexT, + BShufflePipe, BPipe>(q, in_buf_b, count_per_unit, in_count, + producer_from_pipe, wait_events) + ); + + // Merge + merge_events[u].push_back( + Merge, ValueT, IndexT, + APipe, BPipe, MergePipe>(q, count_per_unit, in_count, comp) + ); + + // Consume + consume_events[u].push_back( + Consume, ValueT, IndexT, + MergePipe, InternalOutPipe>(q, out_buf, count_per_unit, + consumer_to_pipe) + ); + }); + //////////////////////////////////////////////////////////////////////// + + // swap buffers + buf_idx = next_buf_idx(buf_idx); + + // increase the input size + in_count *= 2; + } + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + // Launching all of the merge tree kernels + // static merge tree connected by pipes to merge the sorted output of + // each merge unit into a single sorted output through OutPipe + // NOTE: if units==1, then there is no merge tree! + using InternalMTPipes = PipeArray2D; + UnrolledLoop([&](auto level) { + // each level of the merge tree reduces the number merge kernels necessary. + // level 0 has 'units' merge kernels, level 1 has 'units/2', and so on. + constexpr size_t level_merge_units = units / ((1 << level)*2); + + UnrolledLoop([&](auto merge_unit) { + // InPipeA for this merge kernel in the merge tree. + // If the merge tree level is 0, the pipe is from a merge unit + // otherwise it is from the previous level of the merge tree. + using APipeFromMergeUnit = + typename InternalOutPipes::template pipe; + using APipeFromMergeTree = + typename InternalMTPipes::template pipe; + using MTAPipe = + typename std::conditional_t<(level == 0), + APipeFromMergeUnit, + APipeFromMergeTree>; + + // InPipeB for this merge kernel in the merge tree. + // If the merge tree level is 0, the pipe is from a merge unit + // otherwise it is from the previous level of the merge tree. + using BPipeFromMergeUnit = + typename InternalOutPipes::template pipe; + using BPipeFromMergeTree = + typename InternalMTPipes::template pipe; + using MTBPipe = + typename std::conditional_t<(level == 0), + BPipeFromMergeUnit, + BPipeFromMergeTree>; + + // OutPipe for this merge kernel in the merge tree. + // If this is the last level, then the output pipe is the output pipe + // of the entire sorter, otherwise it is going to another level of the + // merge tree. + using OutPipeToMT = + typename InternalMTPipes::template pipe; + using MTOutPipe = + typename std::conditional_t<(level == (kReductionLevels-1)), + OutPipe, + OutPipeToMT>; + + // Launch the merge kernel + mt_merge_events[level].push_back( + Merge, ValueT, IndexT, + MTAPipe, MTBPipe, MTOutPipe>(q, + in_count*2, + in_count, + comp) + ); + }); + + // increase the input size + in_count *= 2; + }); + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + // Combine all kernel events into a single return vector + std::vector ret; + + // add the shuffle event + ret.push_back(shuffle_event); + + // add each merge unit's sorting events + for (size_t u = 0; u < units; u++) { + ret.insert(ret.end(), produce_a_events[u].begin(), + produce_a_events[u].end()); + ret.insert(ret.end(), produce_b_events[u].begin(), + produce_b_events[u].end()); + ret.insert(ret.end(), merge_events[u].begin(), merge_events[u].end()); + ret.insert(ret.end(), consume_events[u].begin(), consume_events[u].end()); + } + + // add the merge tree kernel events + for (size_t level = 0; level < kReductionLevels; level++) { + ret.insert(ret.end(), mt_merge_events[level].begin(), + mt_merge_events[level].end()); + } + + return ret; + //////////////////////////////////////////////////////////////////////////// +} + +// +// A convenience method which defaults the sorters comparator to 'LessThan' +// (i.e., operator<) +// +template +std::vector SubmitMergeSort(queue& q, size_t count, + ValueT* buf_0, ValueT* buf_1) { + return SubmitMergeSort(q, count, + buf_0, buf_1, + LessThan()); +} + +#endif /* __MERGESORT_HPP__ */ \ No newline at end of file diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Misc.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Misc.hpp new file mode 100755 index 0000000000..16ec97f177 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Misc.hpp @@ -0,0 +1,64 @@ +#ifndef __MISC_HPP__ +#define __MISC_HPP__ + +template +static constexpr T Pow2(T n) { + static_assert(std::is_integral::value); + static_assert(std::is_unsigned::value); + return T(1) << n; +} + +template +constexpr bool IsPow2(T n) { + static_assert(std::is_integral::value); + static_assert(std::is_unsigned::value); + return (n != 0) && ((n & (n - 1)) == 0); +} + +template +constexpr T Log2(T n) { + static_assert(std::is_integral::value); + static_assert(std::is_unsigned::value); + return ((n < 2) ? T(0) : T(1) + Log2(n / 2)); +} + +// return 'n' rounded up to the nearest power of 2 +template +constexpr T RoundUpPow2(T n) { + static_assert(std::is_integral::value); + static_assert(std::is_unsigned::value); + if (n == 0) { + return 2; + } else if (IsPow2(n)) { + return n; + } else { + return T(1) << (Log2(n) + 1); + } +} + +/////////////////////////////////////////////////////////// +// Simple 1D and 2D pipe arrays +template +struct PipeArray { + PipeArray() = delete; + + template + struct PipeId; + + template + using pipe = sycl::INTEL::pipe, T, depth>; +}; + +template +struct PipeArray2D { + PipeArray2D() = delete; + + template + struct PipeId; + + template + using pipe = sycl::INTEL::pipe, T, depth>; +}; +/////////////////////////////////////////////////////////// + +#endif /* __MISC_HPP__ */ \ No newline at end of file diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Produce.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Produce.hpp new file mode 100644 index 0000000000..e0e58697ca --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Produce.hpp @@ -0,0 +1,64 @@ +#ifndef __PRODUCE_HPP__ +#define __PRODUCE_HPP__ + +#include +#include + +using namespace sycl; + +// +// Produces data into the merge unit either from an input pipe (from_pipe==true) +// or from memory (from_pipe==false). +// +template +event Produce(queue& q, ValueT *in_ptr, IndexT total_count, IndexT in_block_count, + bool from_pipe, std::vector& depend_events) { + // producer always produces half of the total count + const IndexT half_total_count = total_count / 2; + + // number of input blocks to produce + const IndexT num_blocks = half_total_count / in_block_count; + + // a producer produces a single block and then steps over an entire block + // to produce the next block + const IndexT in_block_step = in_block_count*2; + + return q.submit([&](handler& h) { + h.depends_on(depend_events); + + h.single_task([=]() [[intel::kernel_args_restrict]] { + device_ptr in(in_ptr); + + IndexT block_idx = 0; // the index of the current block + IndexT block_offset = 0; // the offset to the start of the current block + IndexT inter_block_offset = 0; // the offset within the current block + + while (block_idx != num_blocks) { + // get the input data from either the input pipe, or device memory + ValueT data; + if (from_pipe) { + data = InPipe::read(); + } else { + data = *(in + block_offset + inter_block_offset); + } + + // write to the output pipe + OutPipe::write(data); + + // move to the next input + if (inter_block_offset == in_block_count-1) { + // move to the next block + block_idx++; + block_offset += in_block_step; + inter_block_offset = 0; + } else { + // move within the current block + inter_block_offset++; + } + } + }); + }); +} + +#endif /* __PRODUCE_HPP__ */ \ No newline at end of file diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Shuffle.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Shuffle.hpp new file mode 100644 index 0000000000..0608b59013 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Shuffle.hpp @@ -0,0 +1,60 @@ +#ifndef __SHUFFLE_HPP__ +#define __SHUFFLE_HPP__ + +#include +#include + +#include "UnrolledLoop.hpp" + +using namespace sycl; + +// +// Shuffle data between pipe A and pipe B across the merge units +// +template +event Shuffle(queue& q, IndexT total_size) { + // the number of elements per merge unit + // NOTE: this is NOT computed on the FPGA, it is an argument to the kernel + const IndexT count_per_unit = total_size / units; + + return q.submit([&](handler& h) { + h.single_task([=] { + bool write_a = 0; + unsigned char current_unit = 0; + IndexT unit_counter = 0; + + for (IndexT i = 0; i < total_size; i++) { + // read the data from the input pipe + auto data = InPipe::read(); + + // write it to the current output pipe + if (write_a) { + // writing to pipe A of the current merge unit + UnrolledLoop([&](auto u) { + if (u == current_unit) APipes::template pipe::write(data); + }); + } else { + // writing to pipe B of the current merge unit + UnrolledLoop([&](auto u) { + if (u == current_unit) BPipes::template pipe::write(data); + }); + } + + // shuffle between A and B pipe on every iteration + write_a = !write_a; + + // shuffle to each of the merge units + if (unit_counter == count_per_unit-1) { + // time to switch to the next merge unit + unit_counter = 0; + current_unit++; + } else { + unit_counter++; + } + } + }); + }); +} + +#endif /* __SHUFFLE_HPP__ */ \ No newline at end of file diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/UnrolledLoop.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/UnrolledLoop.hpp new file mode 100755 index 0000000000..020c2b9ba9 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/UnrolledLoop.hpp @@ -0,0 +1,185 @@ +#ifndef __UNROLLEDLOOP_HPP__ +#define __UNROLLEDLOOP_HPP__ +#pragma once + +#include +#include + +// +// The code below creates the constexprs 'make_integer_range' +// and 'make_index_range' these are akin to 'std::make_integer_sequence' +// and 'std::make_index_sequence', respectively. +// However they allow you to specificy a range and can either increment +// or decrement, rather than a strict increasing sequence +// +template +struct integer_range_impl; + +// incrementing case +template +struct integer_range_impl, begin, true> { + using type = std::integer_sequence; +}; + +// decrementing case +template +struct integer_range_impl, begin, false> { + using type = std::integer_sequence; +}; + +// integer_range +template +using integer_range = typename integer_range_impl, + begin, + (begin < end)>::type; + +// +// make_integer_range +// +// USAGE: +// make_integer_range{} ==> 1,2,...,9 +// make_integer_range{} ==> 10,9,...,2 +// +template +using make_integer_range = integer_range; + +// +// make_index_range +// +// USAGE: +// make_index_range<1,10>{} ==> 1,2,...,9 +// make_index_range<10,1>{} ==> 10,9,...,2 +// +template +using make_index_range = integer_range; + +// +// The code below creates the constexprs 'make_integer_pow2_sequence' +// and 'make_index_pow2_sequence'. These generate the sequence +// 2^0, 2^1, 2^2, ... , 2^(N-1) = 1,2,4,...,2^(N-1) +// +template +struct integer_pow2_sequence_impl; + +template +struct integer_pow2_sequence_impl> { + using type = std::integer_sequence; +}; + +// integer_pow2_sequence +template +using integer_pow2_sequence = + typename integer_pow2_sequence_impl>::type; + +// +// make_integer_pow2_sequence +// +// USAGE: +// make_integer_pow2_sequence{} ==> 1,2,4,8,16 +// +template +using make_integer_pow2_sequence = integer_pow2_sequence; + +// +// make_index_pow2_sequence +// +// USAGE: +// make_index_pow2_sequence<5>{} ==> 1,2,4,8,16 +// +template +using make_index_pow2_sequence = integer_pow2_sequence; + +/////////////////////////////////////////////////////////////////////////////// +// +// Example usage for UnrolledLoop constexpr: +// +// Base +// UnrolledLoop(std::integer_sequence{},[&](auto i) { +// /* i = 5,2,7,8 */ +// }); +// +// Case A +// UnrolledLoop<10>([&](auto i) { +// /* i = 0,1,...,9 */ +// }); +// +// Case B +// UnrolledLoop<10>([&](auto i) { +// /* i = 0,1,...,9 */ +// }); +// +// Case C +// UnrolledLoop([&](auto i) { +// /* i = 1,2,...,9 */ +// }); +// UnrolledLoop([&](auto i) { +// /* i = 10,9,...,2 */ +// }); +// +// Case D +// UnrolledLoop<1, 10>([&](auto i) { +// /* i = 1,2,...,9 */ +// }); +// UnrolledLoop<10, 1>([&](auto i) { +// /* i = 10,9,...,2 */ +// }); +// +/////////////////////////////////////////////////////////////////////////////// + +// +// Base implementation +// Templated on: +// ItType - the type of the iterator (size_t, int, char, ...) +// ItType... - the indices to iterate on +// F - the function to run for each index (i.e. the lamda) +// +template +constexpr void UnrolledLoop(std::integer_sequence, F&& f) { + (f(std::integral_constant{}), ...); +} + +// +// Convience implementation (A) +// performs UnrolledLoop in range [0,n) with iterator of type ItType +// +template +constexpr void UnrolledLoop(F&& f) { + UnrolledLoop(std::make_integer_sequence{}, std::forward(f)); +} + +// +// Convenience implementation (B) +// performs UnrolledLoop in range [0,n) with an iterator of type std::size_t +// +template +constexpr void UnrolledLoop(F&& f) { + UnrolledLoop(std::make_index_sequence{}, std::forward(f)); +} + +// +// Convenience implementation (C) +// performs UnrolledLoop from start...end with an iterator of type ItType +// NOTE: start is INCLUSIVE, end is EXCLUSIVE +// NOTE: if start<=end, sequence is start,start+1,...,end-1 +// if end<=start, sequence is start,start-1,...,end+1 +// +template +constexpr void UnrolledLoop(F&& f) { + UnrolledLoop(make_integer_range{}, std::forward(f)); +} + +// +// Convenience implementation (C) +// performs UnrolledLoop from start...end with an iterator of type size_t +// NOTE: start is INCLUSIVE, end is EXCLUSIVE +// NOTE: if start<=end, sequence is start,start+1,...,end-1 +// if end<=start, sequence is start,start-1,...,end+1 +// +template +constexpr void UnrolledLoop(F&& f) { + UnrolledLoop(make_index_range{}, std::forward(f)); +} + +#endif /* __UNROLLEDLOOP_HPP__ */ diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/merge_sort.cpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/merge_sort.cpp new file mode 100644 index 0000000000..665a577706 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/merge_sort.cpp @@ -0,0 +1,335 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// dpc_common.hpp can be found in the dev-utilities include folder. +// e.g., $ONEAPI_ROOT/dev-utilities/include/dpc_common.hpp +#include "dpc_common.hpp" + +#include "MergeSort.hpp" + +using namespace sycl; +using namespace std::chrono; + +// determines whether we will use USM host or device allocations to move data +// between host and the device. +#if defined(USM_HOST_ALLOCATIONS) +constexpr bool kUseUSMHostAllocation = true; +#else +constexpr bool kUseUSMHostAllocation = false; +#endif + +// the number of merge units, must be a power of 2 +#ifndef MERGE_UNITS +#ifdef FPGA_EMULATOR +#define MERGE_UNITS 4 +#else +#define MERGE_UNITS 16 +#endif +#endif +constexpr size_t kMergeUnits = MERGE_UNITS; + +// sanity check on number of merge units +static_assert(kMergeUnits > 0); +static_assert(IsPow2(kMergeUnits)); + +//////////////////////////////////////////////////////////////////////////////// +// Forward declare functions used in main +template +double fpga_sort(queue& q, ValueT *in_vec, ValueT *out_vec, IndexT count); + +template +bool validate(T *val, T *ref, unsigned int count); +//////////////////////////////////////////////////////////////////////////////// + +// main +int main(int argc, char* argv[]) { + // type to sort, needs a compare function! + using ValueT = int; + + // the type used to index in the sorter + using IndexT = unsigned int; + + bool passed = true; + +#ifdef FPGA_EMULATOR + IndexT count = 32; + int runs = 1; +#else + IndexT count = 1 << 24; + int runs = 17; +#endif + + // get the size of the input as the first command line argument + if(argc > 1) { + count = atoi(argv[1]); + } + + // get the number of runs as the second command line argument + if (argc > 2) { + runs = atoi(argv[2]); + } + + // enforce at least two runs + runs = std::max((int)2, runs); + + // check args + if (count <= kMergeUnits) { + std::cerr << "ERROR: count must be greater than number of merge units\n"; + std::terminate(); + } else if (count > std::numeric_limits::max()) { + std::cerr << "ERROR: the index type does not have bits to count to " + << "'count'\n"; + std::terminate(); + } + + // the device selector +#ifdef FPGA_EMULATOR + INTEL::fpga_emulator_selector selector; +#else + INTEL::fpga_selector selector; +#endif + + // create the device queue + queue q(selector, dpc_common::exception_handler); + + // make sure the device supports USM device allocations + device d = q.get_device(); + if (!d.get_info()) { + std::cerr << "ERROR: The selected device does not support USM device" + << " allocations\n"; + std::terminate(); + } + + // make sure the device support USM host allocations if we chose to use them + if (!d.get_info() && + kUseUSMHostAllocation) { + std::cerr << "ERROR: The selected device does not support USM host" + << " allocations\n"; + std::terminate(); + } + + // the input and output data + std::vector in_vec(count), out_vec(count), ref(count); + + // generate some random input data + srand(time(NULL)); + std::generate(in_vec.begin(), in_vec.end(), [] { return rand() % 100; }); + + // copy the input to the output reference and compute the expected result + std::copy(in_vec.begin(), in_vec.end(), ref.begin()); + std::sort(ref.begin(), ref.end()); + + // allocate the input and output data either in USM host or device allocations + ValueT *in, *out; + if constexpr(kUseUSMHostAllocation) { + // streaming data using USM host allocations + if ((in = malloc_host(count, q)) == nullptr) { + std::cerr << "ERROR: could not allocate space for 'in'\n"; + std::terminate(); + } + if ((out = malloc_host(count, q)) == nullptr) { + std::cerr << "ERROR: could not allocate space for 'out'\n"; + std::terminate(); + } + + // Copy input to USM memory and reset the output + // this is NOT efficient since, in the case of USM host allocations, + // we could have simply generated the input data into the host allocation + // and avoided this copy. However, it makes the code cleaner to assume the + // input is always in 'in_vec' and this portion of the code is not part of + // the performance timing anyways. + std::copy(in_vec.begin(), in_vec.end(), in); + std::fill(out, out + count, ValueT(0)); + } else { + // streaming data using device allocations + if ((in = malloc_device(count, q)) == nullptr) { + std::cerr << "ERROR: could not allocate space for 'in'\n"; + std::terminate(); + } + if ((out = malloc_device(count, q)) == nullptr) { + std::cerr << "ERROR: could not allocate space for 'out'\n"; + std::terminate(); + } + + // copy input to the device memory adn wait for the copy to finish + q.memcpy(in, in_vec.data(), count*sizeof(ValueT)).wait(); + } + + // track timing information, in ms + std::vector time(runs); + + try { + std::cout << "Running sort " << runs << " times for an " + << "input size of " << count << " using " + << kMergeUnits << " merge units\n"; + std::cout << "Streaming data from " + << (kUseUSMHostAllocation ? "host" : "device") << " memory\n"; + + // the pointer type for the kernel depends on whether we are streaming + // data via USM host allocations or device memory + using KernelPtrType = typename std::conditional_t, + device_ptr>; + + + // run some sort iterations + for (int i = 0; i < runs; i++) { + // run the sort + time[i] = fpga_sort(q, in, out, count); + + // Copy output to out_vec. In the case where we are using USM host + // allocations this is unnecessary since we could simply deference + // 'out'. However, it makes the following code cleaner since output + // is always in 'out_vec' and this copy is not part of the performance + // timing anyway. + q.memcpy(out_vec.data(), out, count*sizeof(ValueT)).wait(); + + // validate the output + passed &= validate(out_vec.data(), ref.data(), count); + } + } catch (exception const& e) { + std::cout << "Caught a synchronous SYCL exception: " << e.what() << "\n"; + std::terminate(); + } + + // free the memory allocated with malloc_host or malloc_device + sycl::free(in, q); + sycl::free(out, q); + + // print the performance results + if (passed) { + // don't print performance when running in the emulator +#ifndef FPGA_EMULATOR + double avg_time = std::accumulate(time.begin() + 1, time.end(), 0.0); + avg_time /= (double)(runs - 1); + + IndexT input_count_m = count * 1e-6; + + std::cout << "Execution time: " << avg_time << " ms\n"; + std::cout << "Throughput: " + << (input_count_m / (avg_time * 1e-3)) << " Melements/s\n"; +#endif + + std::cout << "PASSED\n"; + return 0; + } else { + std::cout << "FAILED\n"; + return 0; + } +} + +// forward declare the kernel and pipe IDs to reduce name mangling +class ProducerKernelID; +class ConsumerKernelID; +class SortInPipeID; +class SortOutPipeID; + +// +// perform the actual sort on the FPGA. +// +template +double fpga_sort(queue& q, ValueT *in_ptr, ValueT* out_ptr, IndexT count) { + // the input and output pipe for the sorter + using SortInPipe = sycl::INTEL::pipe; + using SortOutPipe = sycl::INTEL::pipe; + + // the sorter must sort a power of 2, so round up the requested count + // to the nearest power of 2; we will pad the input to make sure the + // output is still correct + IndexT sorter_count = RoundUpPow2(count); + + // This is the element we will pad the input with. In the case of this design, + // we are sorting from smallest to largest and we want the last output + // elements to be this element, so pad with MAX. If you are sorting from + // largest to smallest, make this the minimum element. If you are sorting + // custom types which are not supported by std::numeric_limits, then you will + // have to set this padding element differently. + const auto padding_element = std::numeric_limits::max(); + + // launch the producer + auto producer_event = q.submit([&](handler& h) { + h.single_task([=] { + // read from the input pointer and write it to the sorter's input pipe + KernelPtrType in(in_ptr); + for (unsigned int i = 0; i < sorter_count; i++) { + auto data = (i < count) ? in[i] : padding_element; + SortInPipe::write(data); + } + }); + }); + + + // launch the consumer + auto consumer_event = q.submit([&](handler& h) { + h.single_task([=] { + // read from the sorters output pipe and write to the output pointer + KernelPtrType out(out_ptr); + for (unsigned int i = 0; i < sorter_count; i++) { + auto data = SortOutPipe::read(); + if (i < count) out[i] = data; + } + }); + }); + + // allocate some memory for the merge sort to use as temporary storage + ValueT* buf_0, *buf_1; + if ((buf_0 = malloc_device(sorter_count, q)) == nullptr) { + std::cerr << "ERROR: could not allocate memory for 'buf_0'\n"; + std::terminate(); + } + if ((buf_1 = malloc_device(sorter_count, q)) == nullptr) { + std::cerr << "ERROR: could not allocate memory for 'buf_1'\n"; + std::terminate(); + } + + // launch the merge sort kernels + auto merge_sort_events = + SubmitMergeSort(q, + sorter_count, + buf_0, buf_1); + + // wait for the producer and consumer to finish + auto start = high_resolution_clock::now(); + producer_event.wait(); + consumer_event.wait(); + auto end = high_resolution_clock::now(); + + // wait for the merge sort events to all finish + for (auto& e : merge_sort_events) { + e.wait(); + } + + // free the memory allocated for the merge sort temporary buffers + sycl::free(buf_0, q); + sycl::free(buf_1, q); + + // return the duration of the sort in milliseconds, excluding memory transfers + duration diff = end - start; + return diff.count(); +} + +// +// simple function to check if two regions of memory contain the same values +// +template +bool validate(T *val, T *ref, unsigned int count) { + for (unsigned int i = 0; i < count; i++) { + if (val[i] != ref[i]) { + std::cout << "ERROR: mismatch at entry " << i << "\n"; + std::cout << "\t" << val[i] << " != " << ref[i] + << " (val[i] != ref[i])\n"; + return false; + } + } + + return true; +} diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/third-party-programs.txt b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/third-party-programs.txt new file mode 100644 index 0000000000..90daff458d --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/third-party-programs.txt @@ -0,0 +1,253 @@ +oneAPI Code Samples - Third Party Programs File + +This file contains the list of third party software ("third party programs") +contained in the Intel software and their required notices and/or license +terms. This third party software, even if included with the distribution of the +Intel software, may be governed by separate license terms, including without +limitation, third party license terms, other Intel software license terms, and +open source software license terms. These separate license terms govern your use +of the third party programs as set forth in the “third-party-programs.txt” or +other similarly named text file. + +Third party programs and their corresponding required notices and/or license +terms are listed below. + +-------------------------------------------------------------------------------- + +1. Nothings STB Libraries + +stb/LICENSE + + This software is available under 2 licenses -- choose whichever you prefer. + ------------------------------------------------------------------------------ + ALTERNATIVE A - MIT License + Copyright (c) 2017 Sean Barrett + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + ------------------------------------------------------------------------------ + ALTERNATIVE B - Public Domain (www.unlicense.org) + This is free and unencumbered software released into the public domain. + Anyone is free to copy, modify, publish, use, compile, sell, or distribute this + software, either in source code form or as a compiled binary, for any purpose, + commercial or non-commercial, and by any means. + In jurisdictions that recognize copyright laws, the author or authors of this + software dedicate any and all copyright interest in the software to the public + domain. We make this dedication for the benefit of the public at large and to + the detriment of our heirs and successors. We intend this dedication to be an + overt act of relinquishment in perpetuity of all present and future rights to + this software under copyright law. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +2. FGPA example designs-gzip + + SDL2.0 + +zlib License + + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + +-------------------------------------------------------------------------------- + +3. Nbody + (c) 2019 Fabio Baruffa + + Plotly.js + Copyright (c) 2020 Plotly, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +© 2020 GitHub, Inc. + +-------------------------------------------------------------------------------- + +4. GNU-EFI + Copyright (c) 1998-2000 Intel Corporation + +The files in the "lib" and "inc" subdirectories are using the EFI Application +Toolkit distributed by Intel at http://developer.intel.com/technology/efi + +This code is covered by the following agreement: + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. THE EFI SPECIFICATION AND ALL OTHER INFORMATION +ON THIS WEB SITE ARE PROVIDED "AS IS" WITH NO WARRANTIES, AND ARE SUBJECT +TO CHANGE WITHOUT NOTICE. + +-------------------------------------------------------------------------------- + +5. Edk2 + Copyright (c) 2019, Intel Corporation. All rights reserved. + + Edk2 Basetools + Copyright (c) 2019, Intel Corporation. All rights reserved. + +SPDX-License-Identifier: BSD-2-Clause-Patent + +-------------------------------------------------------------------------------- + +6. Heat Transmission + +GNU LESSER GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. + +0. Additional Definitions. +As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License. + +“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. + +An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. + +A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”. + +The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. + +The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. +You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. +If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: + +a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or +b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. +3. Object Code Incorporating Material from Library Header Files. +The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: + +a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. +b) Accompany the object code with a copy of the GNU GPL and this license document. +4. Combined Works. +You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: + +a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. +b) Accompany the Combined Work with a copy of the GNU GPL and this license document. +c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. +d) Do one of the following: +0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. +1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. +e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) +5. Combined Libraries. +You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. +b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. +6. Revised Versions of the GNU Lesser General Public License. +The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + +-------------------------------------------------------------------------------- +7. Rodinia + Copyright (c)2008-2011 University of Virginia +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted without royalty fees or other restrictions, provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of the University of Virginia, the Dept. of Computer Science, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF VIRGINIA OR THE SOFTWARE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +If you use this software or a modified version of it, please cite the most relevant among the following papers: + + - M. A. Goodrum, M. J. Trotter, A. Aksel, S. T. Acton, and K. Skadron. Parallelization of Particle Filter Algorithms. In Proceedings of the 3rd Workshop on Emerging Applications and Many-core Architecture (EAMA), in conjunction with the IEEE/ACM International +Symposium on Computer Architecture (ISCA), June 2010. + + - S. Che, M. Boyer, J. Meng, D. Tarjan, J. W. Sheaffer, Sang-Ha Lee and K. Skadron. +Rodinia: A Benchmark Suite for Heterogeneous Computing. IEEE International Symposium +on Workload Characterization, Oct 2009. + +- J. Meng and K. Skadron. "Performance Modeling and Automatic Ghost Zone Optimization +for Iterative Stencil Loops on GPUs." In Proceedings of the 23rd Annual ACM International +Conference on Supercomputing (ICS), June 2009. + +- L.G. Szafaryn, K. Skadron and J. Saucerman. "Experiences Accelerating MATLAB Systems +Biology Applications." in Workshop on Biomedicine in Computing (BiC) at the International +Symposium on Computer Architecture (ISCA), June 2009. + +- M. Boyer, D. Tarjan, S. T. Acton, and K. Skadron. "Accelerating Leukocyte Tracking using CUDA: +A Case Study in Leveraging Manycore Coprocessors." In Proceedings of the International Parallel +and Distributed Processing Symposium (IPDPS), May 2009. + +- S. Che, M. Boyer, J. Meng, D. Tarjan, J. W. Sheaffer, and K. Skadron. "A Performance +Study of General Purpose Applications on Graphics Processors using CUDA" Journal of +Parallel and Distributed Computing, Elsevier, June 2008. + +-------------------------------------------------------------------------------- +Other names and brands may be claimed as the property of others. + +-------------------------------------------------------------------------------- \ No newline at end of file From c1d63f391e6aed585866c37c2a981c684a7c27bf Mon Sep 17 00:00:00 2001 From: tyoungsc Date: Wed, 5 May 2021 10:30:33 -0400 Subject: [PATCH 02/31] Cleaned up MergeSort.hpp a bit using defines Signed-off-by: tyoungsc --- .../merge_sort/src/MergeSort.hpp | 114 +++++++++--------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp index e4aa97ca89..a5e7d2b257 100755 --- a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp @@ -15,7 +15,6 @@ #include "Misc.hpp" #include "Produce.hpp" #include "Shuffle.hpp" -#include "Tuple.hpp" #include "UnrolledLoop.hpp" using namespace sycl; @@ -136,20 +135,20 @@ std::vector SubmitMergeSort(queue& q, size_t count, }; // the number of elements each merge unit will sort - IndexT count_per_unit = count / units; + const IndexT count_per_unit = count / units; // the number of sorting iterations each merge unit will perform - size_t iterations = Log2(count_per_unit); + const size_t iterations = Log2(count_per_unit); // memory to store the various merge unit and merge tree kernel events std::array, units> produce_a_events, produce_b_events, merge_events, consume_events; std::array, kReductionLevels> mt_merge_events; for (size_t i = 0; i < units; i++) { - produce_a_events[i].reserve(iterations); - produce_b_events[i].reserve(iterations); - merge_events[i].reserve(iterations); - consume_events[i].reserve(iterations); + produce_a_events[i].resize(iterations); + produce_b_events[i].resize(iterations); + merge_events[i].resize(iterations); + consume_events[i].resize(iterations); } //////////////////////////////////////////////////////////////////////////// @@ -161,7 +160,6 @@ std::vector SubmitMergeSort(queue& q, size_t count, //////////////////////////////////////////////////////////////////////////// // Launching all of the merge unit kernels - // start with inputs of size 1 IndexT in_count = 1; @@ -173,19 +171,6 @@ std::vector SubmitMergeSort(queue& q, size_t count, // launch the merge unit kernels for this iteration of the sort UnrolledLoop([&](auto u) { - // Except for the first iteration, producers for the current iteration - // must wait for the consumer to be done writing to global memory from the - // previous iteration. This is coarse grain synchronization between the - // producer and consumer of each merge unit. - std::vector wait_events; - if (i != 0) { - wait_events.push_back(consume_events[u].back()); - } - - // the temporary device buffers reside in a single device allocation, - // so compute the offset into the buffer for each merge unit. - size_t unit_buf_offset = count_per_unit * u; - // the intra merge unit pipes using APipe = typename APipes::template pipe; using BPipe = typename BPipes::template pipe; @@ -193,13 +178,36 @@ std::vector SubmitMergeSort(queue& q, size_t count, using AShufflePipe = typename AShufflePipes::template pipe; using BShufflePipe = typename BShufflePipes::template pipe; - // if there is only 1 merge unit, there will be no merge tree, - // so the single unit's output pipe will be the entire sort output pipe + // if there is only 1 merge unit, there will be no merge tree, so the + // single merge unit's output pipe will be the entire sort's output pipe using InternalOutPipe = std::conditional_t<(units == 1), OutPipe, typename InternalOutPipes::template pipe>; + // alias the templated function names to make them shorter + #define SubmitProduceA \ + Produce, ValueT, IndexT, AShufflePipe, APipe> + #define SubmitProduceB \ + Produce, ValueT, IndexT, BShufflePipe, BPipe> + #define SubmitMerge \ + Merge, ValueT, IndexT, APipe, BPipe, MergePipe> + #define SubmitConsume \ + Consume, ValueT, IndexT, MergePipe, InternalOutPipe> + + // Except for the first iteration, producers for the current iteration + // must wait for the consumer to be done writing to global memory from the + // previous iteration. This is coarse grain synchronization between the + // producer and consumer of each merge unit. + std::vector wait_events; + if (i != 0) { + wait_events.push_back(consume_events[u][i-1]); + } + + // the temporary device buffers reside in a single device allocation, + // so compute the offset into the buffer for each merge unit. + size_t unit_buf_offset = count_per_unit * u; + // get device pointers for this merge unit's producers and consumer ValueT* in_buf_a = buf[buf_idx] + unit_buf_offset; ValueT* in_buf_b = buf[buf_idx] + unit_buf_offset + in_count; @@ -208,31 +216,21 @@ std::vector SubmitMergeSort(queue& q, size_t count, //////////////////////////////////////////////////////////////////////// // Enqueue the merge unit kernels // Produce A - produce_a_events[u].push_back( - Produce, ValueT, IndexT, - AShufflePipe, APipe>(q, in_buf_a, count_per_unit, in_count, - producer_from_pipe, wait_events) - ); + produce_a_events[u][i] = SubmitProduceA(q, in_buf_a, count_per_unit, + in_count, producer_from_pipe, + wait_events); // Produce B - produce_b_events[u].push_back( - Produce, ValueT, IndexT, - BShufflePipe, BPipe>(q, in_buf_b, count_per_unit, in_count, - producer_from_pipe, wait_events) - ); + produce_b_events[u][i] = SubmitProduceB(q, in_buf_b, count_per_unit, + in_count, producer_from_pipe, + wait_events); // Merge - merge_events[u].push_back( - Merge, ValueT, IndexT, - APipe, BPipe, MergePipe>(q, count_per_unit, in_count, comp) - ); + merge_events[u][i] = SubmitMerge(q, count_per_unit, in_count, comp); // Consume - consume_events[u].push_back( - Consume, ValueT, IndexT, - MergePipe, InternalOutPipe>(q, out_buf, count_per_unit, - consumer_to_pipe) - ); + consume_events[u][i] = SubmitConsume(q, out_buf, count_per_unit, + consumer_to_pipe); }); //////////////////////////////////////////////////////////////////////// @@ -259,46 +257,44 @@ std::vector SubmitMergeSort(queue& q, size_t count, // InPipeA for this merge kernel in the merge tree. // If the merge tree level is 0, the pipe is from a merge unit // otherwise it is from the previous level of the merge tree. - using APipeFromMergeUnit = + using MTAPipeFromMergeUnit = typename InternalOutPipes::template pipe; - using APipeFromMergeTree = + using MTAPipeFromMergeTree = typename InternalMTPipes::template pipe; using MTAPipe = typename std::conditional_t<(level == 0), - APipeFromMergeUnit, - APipeFromMergeTree>; + MTAPipeFromMergeUnit, + MTAPipeFromMergeTree>; // InPipeB for this merge kernel in the merge tree. // If the merge tree level is 0, the pipe is from a merge unit // otherwise it is from the previous level of the merge tree. - using BPipeFromMergeUnit = + using MTBPipeFromMergeUnit = typename InternalOutPipes::template pipe; - using BPipeFromMergeTree = + using MTBPipeFromMergeTree = typename InternalMTPipes::template pipe; using MTBPipe = typename std::conditional_t<(level == 0), - BPipeFromMergeUnit, - BPipeFromMergeTree>; + MTBPipeFromMergeUnit, + MTBPipeFromMergeTree>; // OutPipe for this merge kernel in the merge tree. // If this is the last level, then the output pipe is the output pipe // of the entire sorter, otherwise it is going to another level of the // merge tree. - using OutPipeToMT = + using MTOutPipeToMT = typename InternalMTPipes::template pipe; using MTOutPipe = typename std::conditional_t<(level == (kReductionLevels-1)), OutPipe, - OutPipeToMT>; + MTOutPipeToMT>; // Launch the merge kernel - mt_merge_events[level].push_back( - Merge, ValueT, IndexT, - MTAPipe, MTBPipe, MTOutPipe>(q, - in_count*2, - in_count, - comp) - ); + #define SubmitMTMerge \ + Merge, ValueT, IndexT, \ + MTAPipe, MTBPipe, MTOutPipe> + mt_merge_events[level].push_back(SubmitMTMerge(q, in_count*2, in_count, + comp)); }); // increase the input size From 079f209a8ec172732faa8e1143c7de6c9e956083 Mon Sep 17 00:00:00 2001 From: tyoungsc Date: Wed, 5 May 2021 14:43:56 -0400 Subject: [PATCH 03/31] Formatting/comments --- .../ReferenceDesigns/merge_sort/src/MergeSort.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp index a5e7d2b257..863c864ffd 100755 --- a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/MergeSort.hpp @@ -206,7 +206,7 @@ std::vector SubmitMergeSort(queue& q, size_t count, // the temporary device buffers reside in a single device allocation, // so compute the offset into the buffer for each merge unit. - size_t unit_buf_offset = count_per_unit * u; + const size_t unit_buf_offset = count_per_unit * u; // get device pointers for this merge unit's producers and consumer ValueT* in_buf_a = buf[buf_idx] + unit_buf_offset; @@ -289,10 +289,12 @@ std::vector SubmitMergeSort(queue& q, size_t count, OutPipe, MTOutPipeToMT>; - // Launch the merge kernel + // create an alias for the long templated function call #define SubmitMTMerge \ Merge, ValueT, IndexT, \ MTAPipe, MTBPipe, MTOutPipe> + + // Launch the merge kernel mt_merge_events[level].push_back(SubmitMTMerge(q, in_count*2, in_count, comp)); }); @@ -330,7 +332,7 @@ std::vector SubmitMergeSort(queue& q, size_t count, } // -// A convenience method which defaults the sorters comparator to 'LessThan' +// A convenience method that defaults the sorters comparator to 'LessThan' // (i.e., operator<) // template From 72f9447b2da34a2f8c4a526f0e00bf7d3c930ad8 Mon Sep 17 00:00:00 2001 From: tyoungsc Date: Thu, 6 May 2021 11:39:10 -0400 Subject: [PATCH 04/31] Reduced area by connecting Partition unit (previously called Shuffle) to only the first merge unit Changed Shuffle to Partition Updated pictures and README Used existing pipe_array code (instead of using my own) Code cleanup and comments Tested in emulation and reports, doing a HW build now --- .../ReferenceDesigns/merge_sort/README.md | 10 +- .../merge_sort/merge_sort.vcxproj | 4 +- .../merge_sort/parallel_tree.png | Bin 70175 -> 68170 bytes .../merge_sort/src/MergeSort.hpp | 244 ++++++++++++------ .../ReferenceDesigns/merge_sort/src/Misc.hpp | 25 -- .../merge_sort/src/Partition.hpp | 36 +++ .../merge_sort/src/Produce.hpp | 49 +++- .../merge_sort/src/Shuffle.hpp | 60 ----- .../merge_sort/src/pipe_array.hpp | 95 +++++++ .../merge_sort/src/pipe_array_internal.hpp | 60 +++++ 10 files changed, 415 insertions(+), 168 deletions(-) create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Partition.hpp delete mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Shuffle.hpp create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/pipe_array.hpp create mode 100644 DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/pipe_array_internal.hpp diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/README.md b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/README.md index ade1ea98ad..2fbb1975ed 100755 --- a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/README.md +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/README.md @@ -158,12 +158,14 @@ The following source files can be found in the `src/` sub-directory. | File | Description |:--- |:--- |`merge_sort.cpp` | Contains the `main()` function and the top-level interfaces. -|`MergeSort.hpp` | The function to submit all of the merge sort kernels (`Shuffle`, `Produce`, `Merge`, and `Consume`) +|`MergeSort.hpp` | The function to submit all of the merge sort kernels (`Partition`, `Produce`, `Merge`, and `Consume`) |`Consume.hpp` | The `Consume` kernel for the merge unit |`Merge.hpp` | The `Merge` kernel for the merge unit and the merge tree |`Misc.hpp` | Miscellaneous helper functions +|`Partition.hpp` | The `Partition` kernel +|`pipe_array.hpp` | Header file containing the definition of an array of pipes +|`pipe_array_internal.hpp` | Helper for pipe_array.hpp |`Produce.hpp` | The `Produce` kernel for the merge unit -|`Shuffle.hpp` | The `Shuffle` kernel |`UnrolledLoop.hpp` | A templated-based loop unroller that unrolls loops in the compiler front end ### Merge Sort Details @@ -181,9 +183,9 @@ A single merge unit requires `lg(N)` iterations to sort `N` elements. This requi basic_runtime_graph -To improve performance, the merge sort design accepts a template parameter `units` which allows one to instantiate multiple instances of the merge unit, as shown in the figure below. Choosing the number of units is an area-performance tradeoff (note: the number of instantiated merge units must be a power of 2). In this design, each merge unit sorts a partition of the input data of size `N/units`. However, since the data is coming in one element at a time from a SYCL pipe, the data must be partitioned in the first iteration of the sort. This is done using the `Shuffle` kernel, which shuffles the data between the merge units and the producers (`ProduceA` and `ProduceB`) of each merge unit to perform the first iteration of the sort from the input pipe. +To improve performance, the merge sort design accepts a template parameter `units` which allows one to instantiate multiple instances of the merge unit, as shown in the figure below. Choosing the number of units is an area-performance tradeoff (note: the number of instantiated merge units must be a power of 2). In this design, each merge unit sorts a partition of the input data of size `N/units`. However, since the data is coming in one element at a time from a SYCL pipe, the data must be partitioned in the first iteration of the sort. This is done using the `Partition` kernel, which feeds the data to producers of merge unit 0 (alternating between the) to perform the first iteration of the sort from the input pipe. Notice that only `ProduceA` and `ProduceB` of merge unit 0 have input pipes to perform this initial partition, the other merge units do not. This is reflected in the two different versions of the `Produce` kernel in *Produce.hpp*. -parallel_tree +parallel_tree After the merge units sort their `N/units`-sized partition, the partitions must be reduced into a single sorted list. There are two options to do this: (1) reuse the merge units to perform `lg(units)` more iterations to sort the partitions, or (2) create a merge tree to reduce the partitions into a single sorted list. Option (1) saves area at the expense of performance, since it has to perform additional sorting iterations. Option (2), which we choose for this design, improves performance by creating a merge tree to reduce the final partitions into a single sorted list. The `Merge` kernels in the merge tree (shown in the figure above) use the same kernel code that is used in the `Merge` kernel of the merge unit. diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.vcxproj b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.vcxproj index 9ff9d082d3..706f9c70c7 100755 --- a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.vcxproj +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/merge_sort.vcxproj @@ -18,8 +18,10 @@ + + + - diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/parallel_tree.png b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/parallel_tree.png index c8f62946736c9993070e7d39017d661e175ce400..8dd3b238be6b79cead6e19a40cce1102d3f9dc29 100644 GIT binary patch literal 68170 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 literal 70175 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 class ConsumeKernelID; -class ShuffleKernelID; +class PartitionKernelID; class APipeID; class BPipeID; class MergePipeID; -class AShufflePipeID; -class BShufflePipeID; +class APartitionPipeID; +class BPartitionPipeID; class InternalOutPipeID; // Kernel and pipe ID classes for the merge tree @@ -85,16 +87,52 @@ std::vector SubmitMergeSort(queue& q, size_t count, "The 'Compare' function type must be invocable (i.e. operator()) with two" "'ValueT' arguments and returning a boolean"); + // a depth of 0 allows the compiler to pick a depth to try and + // balance the pipeline + constexpr size_t kDefaultPipeDepth = 0; + + // the various pipes connecting the different kernels + using APipes = PipeArray; + using BPipes = PipeArray; + using MergePipes = PipeArray; + using InternalOutPipes = + PipeArray; + + using APartitionPipe = sycl::INTEL::pipe; + using BPartitionPipe = sycl::INTEL::pipe; + // depth of the merge tree to reduce the partial results of each merge unit constexpr size_t kReductionLevels = Log2(units); - // the various pipes connecting the different kernels - using APipes = PipeArray; - using BPipes = PipeArray; - using MergePipes = PipeArray; - using AShufflePipes = PipeArray; - using BShufflePipes = PipeArray; - using InternalOutPipes = PipeArray; + ////////////////////////////////////////////////////////////////////////////// + // These defines make the code much cleaner but one must be careful + // to make sure that changing variable/template names in the code later + // is propagated here + #define SubmitPartition \ + Partition + #define SubmitPartitionProduceA \ + Produce, ValueT, IndexT, APartitionPipe, APipe> + #define SubmitPartitionProduceB \ + Produce, ValueT, IndexT, BPartitionPipe, BPipe> + #define SubmitPartitionMerge \ + Merge, ValueT, IndexT, APipe, BPipe, MergePipe> + #define SubmitPartitionConsume \ + Consume, ValueT, IndexT, MergePipe, InternalOutPipe> + + #define SubmitProduceA \ + Produce, ValueT, IndexT, APipe> + #define SubmitProduceB \ + Produce, ValueT, IndexT, BPipe> + #define SubmitMerge \ + Merge, ValueT, IndexT, APipe, BPipe, MergePipe> + #define SubmitConsume \ + Consume, ValueT, IndexT, MergePipe, InternalOutPipe> + + #define SubmitMTMerge \ + Merge, ValueT, IndexT, \ + MTAPipe, MTBPipe, MTOutPipe> + ////////////////////////////////////////////////////////////////////////////// // validate 'count' if (count == 0) { @@ -103,8 +141,8 @@ std::vector SubmitMergeSort(queue& q, size_t count, } else if (!IsPow2(count)) { std::cerr << "ERROR: 'count' must be a power of 2\n"; std::terminate(); - } else if (count < 2*units) { - std::cerr << "ERROR: 'count' must be at least 2x greater than " + } else if (count < 4*units) { + std::cerr << "ERROR: MergeSorter 'count' must be at least 4x greater than " << "the number of merge units (" << units << ")\n"; std::terminate(); } else if (count > std::numeric_limits::max()) { @@ -138,7 +176,9 @@ std::vector SubmitMergeSort(queue& q, size_t count, const IndexT count_per_unit = count / units; // the number of sorting iterations each merge unit will perform - const size_t iterations = Log2(count_per_unit); + // NOTE: we subtract 1 because the first partition iteration will use merge + // unit 0 to perform the first iteration of sorting + const size_t iterations = Log2(count_per_unit) - 1; // memory to store the various merge unit and merge tree kernel events std::array, units> produce_a_events, produce_b_events, @@ -152,55 +192,77 @@ std::vector SubmitMergeSort(queue& q, size_t count, } //////////////////////////////////////////////////////////////////////////// - // The initial shuffle stage which feeds the producers of each merge unit - // with data coming from the input pipe to the sorter - event shuffle_event = Shuffle(q, count); + // Submit the kernels to do the initial partition of the input data from the + // input pipe. We will use merge unit 0 (which is always there, regardless + // of the number of merge units) to do the initial partitioning + using APipe = typename APipes::template PipeAt<0>; + using BPipe = typename BPipes::template PipeAt<0>; + using MergePipe = typename MergePipes::template PipeAt<0>; + using InternalOutPipe = + std::conditional_t<(units == 1), OutPipe, + typename InternalOutPipes::template PipeAt<0>>; + + std::vector empty_event_vec; + + // NOTE: check the macros defined earlier in this function for an explanation + // of what SubmitPartition* do + // Partition Kernel + auto partition_event = SubmitPartition(q, count); + + // NOTE: 'buf[buf_idx]' are unused by the producers below + // Produce A (merge unit 0) + auto partition_produce_a_event = + SubmitPartitionProduceA(q, buf[buf_idx], count, 1, true, empty_event_vec); + + // Produce B (merge unit 0) + auto partition_produce_b_event = + SubmitPartitionProduceB(q, buf[buf_idx], count, 1, true, empty_event_vec); + + // Merge + auto partition_merge_event = SubmitPartitionMerge(q, count, 1, comp); + + // Consume + auto partition_consume_event = + SubmitPartitionConsume(q, buf[buf_idx], count, false); //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// // Launching all of the merge unit kernels - // start with inputs of size 1 - IndexT in_count = 1; + // start with inputs of size 2 since the partition stage used merge unit 0 + // to perform the first sort iteration that sorted inputs of size 1 into 2 + IndexT in_count = 2; + // perform the sort iterations for each unit for (size_t i = 0; i < iterations; i++) { - // Producers will read from pipe on first iteration (from shuffle), - // Consumers will write to pipe on last iteration - bool producer_from_pipe = (i == 0); + // Consumer will write to pipe on last iteration bool consumer_to_pipe = (i == (iterations-1)); // launch the merge unit kernels for this iteration of the sort UnrolledLoop([&](auto u) { // the intra merge unit pipes - using APipe = typename APipes::template pipe; - using BPipe = typename BPipes::template pipe; - using MergePipe = typename MergePipes::template pipe; - using AShufflePipe = typename AShufflePipes::template pipe; - using BShufflePipe = typename BShufflePipes::template pipe; + using APipe = typename APipes::template PipeAt; + using BPipe = typename BPipes::template PipeAt; + using MergePipe = typename MergePipes::template PipeAt; // if there is only 1 merge unit, there will be no merge tree, so the // single merge unit's output pipe will be the entire sort's output pipe using InternalOutPipe = std::conditional_t<(units == 1), OutPipe, - typename InternalOutPipes::template pipe>; - - // alias the templated function names to make them shorter - #define SubmitProduceA \ - Produce, ValueT, IndexT, AShufflePipe, APipe> - #define SubmitProduceB \ - Produce, ValueT, IndexT, BShufflePipe, BPipe> - #define SubmitMerge \ - Merge, ValueT, IndexT, APipe, BPipe, MergePipe> - #define SubmitConsume \ - Consume, ValueT, IndexT, MergePipe, InternalOutPipe> - - // Except for the first iteration, producers for the current iteration - // must wait for the consumer to be done writing to global memory from the - // previous iteration. This is coarse grain synchronization between the - // producer and consumer of each merge unit. + typename InternalOutPipes::template PipeAt>; + + // build the dependency event vector std::vector wait_events; - if (i != 0) { + if (i == 0) { + // on the first iteration, wait for the consume kernel from the + // partition stage to be done so that all of the data is in the temp + // buffers in device memory + wait_events.push_back(partition_consume_event); + } else { + // on all iterations (except the first), producers for the current + // iteration must wait for the consumer to be done writing to global + // memory from the previous iteration. This is coarse grain + // synchronization between the producer and consumer of each merge unit. wait_events.push_back(consume_events[u][i-1]); } @@ -215,15 +277,28 @@ std::vector SubmitMergeSort(queue& q, size_t count, //////////////////////////////////////////////////////////////////////// // Enqueue the merge unit kernels - // Produce A - produce_a_events[u][i] = SubmitProduceA(q, in_buf_a, count_per_unit, - in_count, producer_from_pipe, - wait_events); - - // Produce B - produce_b_events[u][i] = SubmitProduceB(q, in_buf_b, count_per_unit, - in_count, producer_from_pipe, - wait_events); + + // Produce A and Produce B + // merge unit 0 is different from the rest, since it is connected to the + // Partition kernel, so it must be handled specially. + // NOTE: See the SubmitPartitionProduce* and SubmitProduce* macros + // defined earlier in this function + if constexpr (u == 0) { + // the first merge unit is special, since it is connected to the + // partition kernel. + produce_a_events[u][i] = + SubmitPartitionProduceA(q, in_buf_a, count_per_unit, in_count, false, + wait_events); + produce_b_events[u][i] = + SubmitPartitionProduceB(q, in_buf_b, count_per_unit, in_count, false, + wait_events); + } else { + // all other merge units (not the first) are not connected to pipes + produce_a_events[u][i] = + SubmitProduceA(q, in_buf_a, count_per_unit, in_count, wait_events); + produce_b_events[u][i] = + SubmitProduceB(q, in_buf_b, count_per_unit, in_count, wait_events); + } // Merge merge_events[u][i] = SubmitMerge(q, count_per_unit, in_count, comp); @@ -244,23 +319,40 @@ std::vector SubmitMergeSort(queue& q, size_t count, //////////////////////////////////////////////////////////////////////////// // Launching all of the merge tree kernels - // static merge tree connected by pipes to merge the sorted output of - // each merge unit into a single sorted output through OutPipe + + // the merge tree pipe array + // NOTE: we actually only need 2^(kReductionLevels) - 2 total pipes, + // but we have created a 2D pipe array with kReductionLevels*units*2 + // pipes. The 2D pipe array makes the programming much easier and the + // front-end compiler will not use the extra pipes and therefore they + // will NOT be instantiated in hardware + using InternalMTPipes = + PipeArray; + + // create the static merge tree connected by pipes to merge the sorted output + // of each merge unit into a single sorted output through OutPipe // NOTE: if units==1, then there is no merge tree! - using InternalMTPipes = PipeArray2D; UnrolledLoop([&](auto level) { // each level of the merge tree reduces the number merge kernels necessary. // level 0 has 'units' merge kernels, level 1 has 'units/2', and so on. - constexpr size_t level_merge_units = units / ((1 << level)*2); + constexpr size_t kLevelMergeUnits = units / ((1 << level)*2); + + UnrolledLoop([&](auto merge_unit) { + // When level == 0, we know we will use MTAPipeFromMergeUnit and + // MTBPipeFromMergeUnit below. Howevever, we cannot access + // PipeAt<-1, ...> without a compiler error. So, we will set the previous + // level to 0, knowing that we will not use MTAPipeFromMergeTree nor + // MTBPipeFromMergeTree + constexpr size_t prev_level = (level == 0) ? 0 : level - 1; - UnrolledLoop([&](auto merge_unit) { // InPipeA for this merge kernel in the merge tree. // If the merge tree level is 0, the pipe is from a merge unit // otherwise it is from the previous level of the merge tree. using MTAPipeFromMergeUnit = - typename InternalOutPipes::template pipe; + typename InternalOutPipes::template PipeAt; using MTAPipeFromMergeTree = - typename InternalMTPipes::template pipe; + typename InternalMTPipes::template PipeAt; using MTAPipe = typename std::conditional_t<(level == 0), MTAPipeFromMergeUnit, @@ -270,9 +362,9 @@ std::vector SubmitMergeSort(queue& q, size_t count, // If the merge tree level is 0, the pipe is from a merge unit // otherwise it is from the previous level of the merge tree. using MTBPipeFromMergeUnit = - typename InternalOutPipes::template pipe; + typename InternalOutPipes::template PipeAt; using MTBPipeFromMergeTree = - typename InternalMTPipes::template pipe; + typename InternalMTPipes::template PipeAt; using MTBPipe = typename std::conditional_t<(level == 0), MTBPipeFromMergeUnit, @@ -283,20 +375,15 @@ std::vector SubmitMergeSort(queue& q, size_t count, // of the entire sorter, otherwise it is going to another level of the // merge tree. using MTOutPipeToMT = - typename InternalMTPipes::template pipe; + typename InternalMTPipes::template PipeAt; using MTOutPipe = typename std::conditional_t<(level == (kReductionLevels-1)), - OutPipe, - MTOutPipeToMT>; - - // create an alias for the long templated function call - #define SubmitMTMerge \ - Merge, ValueT, IndexT, \ - MTAPipe, MTBPipe, MTOutPipe> + OutPipe, + MTOutPipeToMT>; // Launch the merge kernel - mt_merge_events[level].push_back(SubmitMTMerge(q, in_count*2, in_count, - comp)); + const auto e = SubmitMTMerge(q, in_count*2, in_count, comp); + mt_merge_events[level].push_back(e); }); // increase the input size @@ -308,8 +395,12 @@ std::vector SubmitMergeSort(queue& q, size_t count, // Combine all kernel events into a single return vector std::vector ret; - // add the shuffle event - ret.push_back(shuffle_event); + // add events from the partition stage + ret.push_back(partition_event); + ret.push_back(partition_produce_a_event); + ret.push_back(partition_produce_b_event); + ret.push_back(partition_merge_event); + ret.push_back(partition_consume_event); // add each merge unit's sorting events for (size_t u = 0; u < units; u++) { @@ -335,7 +426,8 @@ std::vector SubmitMergeSort(queue& q, size_t count, // A convenience method that defaults the sorters comparator to 'LessThan' // (i.e., operator<) // -template +template std::vector SubmitMergeSort(queue& q, size_t count, ValueT* buf_0, ValueT* buf_1) { return SubmitMergeSort(q, count, diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Misc.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Misc.hpp index 16ec97f177..7ab60ce7bd 100755 --- a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Misc.hpp +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Misc.hpp @@ -36,29 +36,4 @@ constexpr T RoundUpPow2(T n) { } } -/////////////////////////////////////////////////////////// -// Simple 1D and 2D pipe arrays -template -struct PipeArray { - PipeArray() = delete; - - template - struct PipeId; - - template - using pipe = sycl::INTEL::pipe, T, depth>; -}; - -template -struct PipeArray2D { - PipeArray2D() = delete; - - template - struct PipeId; - - template - using pipe = sycl::INTEL::pipe, T, depth>; -}; -/////////////////////////////////////////////////////////// - #endif /* __MISC_HPP__ */ \ No newline at end of file diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Partition.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Partition.hpp new file mode 100644 index 0000000000..41aaa741eb --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Partition.hpp @@ -0,0 +1,36 @@ +#ifndef __PARTITION_HPP__ +#define __PARTITION_HPP__ + +#include +#include + +using namespace sycl; + +// +// Partition data between pipe A and pipe B +// +template +event Partition(queue& q, IndexT total_count) { + return q.submit([&](handler& h) { + h.single_task([=] { + bool write_a = true; + for (IndexT i = 0; i < total_count; i++) { + // read from the input pipe + auto d = InPipe::read(); + + // write to either APipe or BPipe + if (write_a) { + APipe::write(d); + } else { + BPipe::write(d); + } + + // move to the other pipe + write_a = !write_a; + } + }); + }); +} + +#endif /* __PARTITION_HPP__ */ \ No newline at end of file diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Produce.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Produce.hpp index e0e58697ca..a9a8526518 100644 --- a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Produce.hpp +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Produce.hpp @@ -12,8 +12,9 @@ using namespace sycl; // template -event Produce(queue& q, ValueT *in_ptr, IndexT total_count, IndexT in_block_count, - bool from_pipe, std::vector& depend_events) { +event Produce(queue& q, ValueT *in_ptr, IndexT total_count, + IndexT in_block_count, bool from_pipe, + std::vector& depend_events) { // producer always produces half of the total count const IndexT half_total_count = total_count / 2; @@ -61,4 +62,48 @@ event Produce(queue& q, ValueT *in_ptr, IndexT total_count, IndexT in_block_coun }); } +// +// Same as the produce about, but with no input pipe +// +template +event Produce(queue& q, ValueT *in_ptr, IndexT total_count, + IndexT in_block_count, std::vector& depend_events) { + // producer always produces half of the total count + const IndexT half_total_count = total_count / 2; + + // number of input blocks to produce + const IndexT num_blocks = half_total_count / in_block_count; + + // a producer produces a single block and then steps over an entire block + const IndexT in_block_step = in_block_count*2; + + return q.submit([&](handler& h) { + h.depends_on(depend_events); + + h.single_task([=]() [[intel::kernel_args_restrict]] { + device_ptr in(in_ptr); + IndexT block_idx = 0; // the index of the current block + IndexT block_offset = 0; // the offset to the start of the current block + IndexT inter_block_offset = 0; // the offset within the current block + + while (block_idx != num_blocks) { + // get the input data from either the input pipe, or device memory + ValueT data = *(in + block_offset + inter_block_offset); + + // write to the output pipe + OutPipe::write(data); + + // move to the next input + if (inter_block_offset == in_block_count-1) { + block_idx++; + block_offset += in_block_step; + inter_block_offset = 0; + } else { + inter_block_offset++; + } + } + }); + }); +} + #endif /* __PRODUCE_HPP__ */ \ No newline at end of file diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Shuffle.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Shuffle.hpp deleted file mode 100644 index 0608b59013..0000000000 --- a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/Shuffle.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef __SHUFFLE_HPP__ -#define __SHUFFLE_HPP__ - -#include -#include - -#include "UnrolledLoop.hpp" - -using namespace sycl; - -// -// Shuffle data between pipe A and pipe B across the merge units -// -template -event Shuffle(queue& q, IndexT total_size) { - // the number of elements per merge unit - // NOTE: this is NOT computed on the FPGA, it is an argument to the kernel - const IndexT count_per_unit = total_size / units; - - return q.submit([&](handler& h) { - h.single_task([=] { - bool write_a = 0; - unsigned char current_unit = 0; - IndexT unit_counter = 0; - - for (IndexT i = 0; i < total_size; i++) { - // read the data from the input pipe - auto data = InPipe::read(); - - // write it to the current output pipe - if (write_a) { - // writing to pipe A of the current merge unit - UnrolledLoop([&](auto u) { - if (u == current_unit) APipes::template pipe::write(data); - }); - } else { - // writing to pipe B of the current merge unit - UnrolledLoop([&](auto u) { - if (u == current_unit) BPipes::template pipe::write(data); - }); - } - - // shuffle between A and B pipe on every iteration - write_a = !write_a; - - // shuffle to each of the merge units - if (unit_counter == count_per_unit-1) { - // time to switch to the next merge unit - unit_counter = 0; - current_unit++; - } else { - unit_counter++; - } - } - }); - }); -} - -#endif /* __SHUFFLE_HPP__ */ \ No newline at end of file diff --git a/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/pipe_array.hpp b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/pipe_array.hpp new file mode 100644 index 0000000000..af0bd2e969 --- /dev/null +++ b/DirectProgramming/DPC++FPGA/ReferenceDesigns/merge_sort/src/pipe_array.hpp @@ -0,0 +1,95 @@ +//============================================================== +// Copyright Intel Corporation +// +// SPDX-License-Identifier: MIT +// ============================================================= +#ifndef __PIPE_ARRAY_HPP__ +#define __PIPE_ARRAY_HPP__ + +#include +#include +#include +//#include + +#include "pipe_array_internal.hpp" + +template +struct PipeArray { + PipeArray() = delete; // ensure we cannot create an instance + + template + struct StructId; // the ID of each pipe in the array + + // VerifyIndices checks that we only access pipe indicies that are in range + template + struct VerifyIndices { + static_assert(sizeof...(idxs) == sizeof...(dims), + "Indexing into a PipeArray requires as many indices as " + "dimensions of the PipeArray."); + static_assert(VerifierDimLayer::template VerifierIdxLayer< + idxs...>::IsValid(), + "Index out of bounds"); + using VerifiedPipe = + cl::sycl::INTEL::pipe, BaseTy, min_depth>; + }; + + // helpers for accessing the dimensions of the pipe array + // usage: + // MyPipeArray::GetNumDims() - number of dimensions in this pipe array + // MyPipeArray::GetDimSize<3>() - size of dimension 3 in this pipe array + static constexpr size_t GetNumDims() { return (sizeof...(dims)); } + template + static constexpr size_t GetDimSize() { + return std::get(dims...); + } + + // PipeAt is used to reference a pipe at a particular index + template + using PipeAt = typename VerifyIndices::VerifiedPipe; + + // functor to impllement blocking write to all pipes in the array + template + struct BlockingWriteFunc { + void operator()(const BaseTy &data, bool &success) const { + PipeAt::write(data); + } + }; + // functor to impllement non-blocking write to all pipes in the array + template + struct NonBlockingWriteFunc { + void operator()(const BaseTy &data, bool &success) const { + PipeAt::write(data, success); + } + }; + // helper function for implementing write() call to all pipes in the array + template