From 8b813d640f57723572eb5c8a3c5d4b8c9089d423 Mon Sep 17 00:00:00 2001 From: Sitao Huang Date: Wed, 13 Jun 2018 16:37:04 -0500 Subject: [PATCH] adding C++ host code --- triangle_counting_host/cpp/Makefile | 9 + triangle_counting_host/cpp/libsds_lib.so | Bin 0 -> 90272 bytes triangle_counting_host/cpp/libxlnk_cma.h | 56 +++ triangle_counting_host/cpp/tc.cpp | 342 ++++++++++++++++++ triangle_counting_host/cpp/tc_1pe.cpp | 214 +++++++++++ triangle_counting_host/python/graph_parser.py | 37 ++ .../python/intersect_host.py | 52 +++ triangle_counting_host/python/tc_host.py | 101 ++++++ .../python/tc_host_opt_4.py | 162 +++++++++ .../python/tc_host_opt_7.py | 210 +++++++++++ 10 files changed, 1183 insertions(+) create mode 100644 triangle_counting_host/cpp/Makefile create mode 100755 triangle_counting_host/cpp/libsds_lib.so create mode 100644 triangle_counting_host/cpp/libxlnk_cma.h create mode 100644 triangle_counting_host/cpp/tc.cpp create mode 100644 triangle_counting_host/cpp/tc_1pe.cpp create mode 100644 triangle_counting_host/python/graph_parser.py create mode 100644 triangle_counting_host/python/intersect_host.py create mode 100644 triangle_counting_host/python/tc_host.py create mode 100644 triangle_counting_host/python/tc_host_opt_4.py create mode 100644 triangle_counting_host/python/tc_host_opt_7.py diff --git a/triangle_counting_host/cpp/Makefile b/triangle_counting_host/cpp/Makefile new file mode 100644 index 0000000..aff882c --- /dev/null +++ b/triangle_counting_host/cpp/Makefile @@ -0,0 +1,9 @@ +all: + g++ -O3 tc.cpp -std=c++11 -o tc -L. -lsds_lib + g++ -O3 tc_1pe.cpp -std=c++11 -o tc_1pe -L. -lsds_lib + +cpu: + g++ -O3 triangle_counting.cc tc_1pe.cpp -std=c++11 -o tc_cpu -L. -lsds_lib + +clean: + rm -rf tc tc_1pe tc_cpu diff --git a/triangle_counting_host/cpp/libsds_lib.so b/triangle_counting_host/cpp/libsds_lib.so new file mode 100755 index 0000000000000000000000000000000000000000..7fb49713bf37e407f1db0a8411efd474915542c6 GIT binary patch literal 90272 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 literal 0 HcmV?d00001 diff --git a/triangle_counting_host/cpp/libxlnk_cma.h b/triangle_counting_host/cpp/libxlnk_cma.h new file mode 100644 index 0000000..1b8998e --- /dev/null +++ b/triangle_counting_host/cpp/libxlnk_cma.h @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// kernel buffer pool +#define XLNK_BUFPOOL_SIZE 100 + +#define XLNK_DRIVER_PATH "/dev/xlnk" + +// counter of buffer currently instantiated +static uint32_t xlnkBufCnt = 0; +// virtual address of buffer +static void *xlnkBufPool[2 * XLNK_BUFPOOL_SIZE]; +// length in bytes of buffer +static size_t xlnkBufLens[2 * XLNK_BUFPOOL_SIZE]; +// physical address of buffer +static uint32_t xlnkBufPhyPool[2 * XLNK_BUFPOOL_SIZE]; + +/* + * Get the virtual address referencing the physical address resulting from + * mmaping /dev/mem. + * Required to use bare-metal drivers on linux. Return -1 in case of error. + */ +unsigned long cma_mmap(unsigned long phyAddr, uint32_t len); +/* + * Unmap a previously mapped memory space. + */ +uint32_t cma_munmap(void *buf, uint32_t len); +/* + * Allocate a physically contiguos chunk of CMA memory and map it into + * virtual memory space. Return this Virtual pointer. Returns -1 on failure. + */ +void *cma_alloc(uint32_t len, uint32_t cacheable); +/* + * Return a physical memory address corresponding to a given Virtual address + * pointer. Returns NULL on failure. + */ +unsigned long cma_get_phy_addr(void *buf); +/* + * Free a previously allocated CMA memory chunk. + */ +void cma_free(void *buf); +/* + * Returns the number of available CMA memiry pages which can be allocated. + */ +uint32_t cma_pages_available(); +/* + * Extra functions in case user needs to flush or invalidate Cache. + */ +void cma_flush_cache(void *buf, unsigned int phys_addr, int size); +void cma_invalidate_cache(void *buf, unsigned int phys_addr, int size); diff --git a/triangle_counting_host/cpp/tc.cpp b/triangle_counting_host/cpp/tc.cpp new file mode 100644 index 0000000..5ce9026 --- /dev/null +++ b/triangle_counting_host/cpp/tc.cpp @@ -0,0 +1,342 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for high_resolution_clock + +extern "C" +{ +#include "libxlnk_cma.h" +} + +using namespace std; + +class accelerator +{ +public: + accelerator(int base_addr=0x43C00000, int range=0x00010000) : base_addr(base_addr), range(range) + { + // virt_base = base_addr & ~(getpagesize() - 1); + virt_base = base_addr & ~(sysconf(_SC_PAGE_SIZE) - 1); + virt_offset = base_addr - virt_base; + mmap_file = open("/dev/mem", O_RDWR | O_SYNC); + if (mmap_file == -1) + cout << "Unable to open /dev/mem" << endl; + mmap_addr = (int*)mmap(NULL, range + virt_offset, PROT_READ | PROT_WRITE, + MAP_SHARED, mmap_file, virt_base); + if (mmap_addr == MAP_FAILED) + cout << "mmap fails. " << endl; + + mmap_space = mmap_addr + virt_offset; + } + ~accelerator() { close(mmap_file); } + + int get(int offset) { return mmap_space[offset >> 2]; } + + void set(int offset, int value) { mmap_space[offset >> 2] = value; } + + void start() { mmap_space[0x00] |= 1; } + + bool done() { return (mmap_space[0x00] & (1 << 1)); } + + bool idle() { return (mmap_space[0x00] & (1 << 2)); } + + bool ready() { return (mmap_space[0x00] & (1 << 3)); } + + int get_return() { return mmap_space[0x10 >> 2]; } + + int program(string bitfile_name) + { + char buf[4194304]; + const string BS_XDEVCFG = "/dev/xdevcfg"; + const string BS_IS_PARTIAL = "/sys/devices/soc0/amba/f8007000.devcfg/is_partial_bitstream"; + + int partial_bs_dev = open(BS_IS_PARTIAL.c_str(), O_WRONLY | O_NONBLOCK); + if (partial_bs_dev < 0) + { + printf("ERROR opening %s\n", BS_IS_PARTIAL.c_str()); + return -1; + } + int write_size = write(partial_bs_dev, "0", 1); + + int fpga_dev = open(BS_XDEVCFG.c_str(), O_WRONLY | O_NONBLOCK); + // int fpga_dev = open(BS_XDEVCFG.c_str(), O_WRONLY); + if (fpga_dev < 0) + { + printf("ERROR opening %s\n", BS_XDEVCFG.c_str()); + return -1; + } + + int bit_file = open(bitfile_name.c_str(), O_RDONLY); + if (bit_file < 0) + { + printf("ERROR opening %s\n", bitfile_name.c_str()); + return -1; + } + + int bit_file_size = read(bit_file, buf, 4194304); + write_size = write(fpga_dev, buf, bit_file_size); + + close(partial_bs_dev); + close(fpga_dev); + close(bit_file); + return 0; + } + +private: + int base_addr; + int range; + int virt_base; + int virt_offset; + int mmap_file; + int *mmap_addr; + int *mmap_space; +}; + +void read_graph(const char *filename, + std::vector *edge_list, + unsigned int num_pe, + std::vector &neighbor_list, + std::vector &offset_list) +{ + std::ifstream ifs(filename); + + int degree_count = 0; + int prev_node = 0; + int pe_idx = 0; + offset_list.push_back(0); + + if (ifs.is_open() && ifs.good()) + { + std::string str; + while (std::getline(ifs, str)) + { + if (!str.empty() && str[0] != '#') + { + std::istringstream ss(str); + int u, v; + ss >> u >> v; + if (prev_node != v) + { + offset_list.push_back(degree_count); + } + + prev_node = v; + if (u < v) + { + edge_list[pe_idx % num_pe].push_back(v); + edge_list[pe_idx % num_pe].push_back(u); + pe_idx++; + } + else + { + neighbor_list.push_back(u); + degree_count++; + } + } + } + } + ifs.close(); + offset_list.push_back(degree_count); +// num_edge = edge_list.size() / 2; +} + +int main( int argc, char** argv ) +{ + + auto t_start = std::chrono::high_resolution_clock::now(); + +// int num_edge = 0; + std::vector edge_list[7], neighbor_list, offset_list; + read_graph("../graph/soc-Epinions1_adj.tsv", edge_list, 7, neighbor_list, offset_list); + std::cout << "neighbor_list size= " << neighbor_list.size() << std::endl; + std::cout << "offset_list size= " << offset_list.size() << std::endl; + + auto t_file_done = std::chrono::high_resolution_clock::now(); + + int *edges0 = (int *)cma_alloc( edge_list[0].size()*sizeof(int), false); + int *edges1 = (int *)cma_alloc( edge_list[1].size()*sizeof(int), false); + int *edges2 = (int *)cma_alloc( edge_list[2].size()*sizeof(int), false); + int *edges3 = (int *)cma_alloc( edge_list[3].size()*sizeof(int), false); + int *edges4 = (int *)cma_alloc( edge_list[4].size()*sizeof(int), false); + int *edges5 = (int *)cma_alloc( edge_list[5].size()*sizeof(int), false); + int *edges6 = (int *)cma_alloc( edge_list[6].size()*sizeof(int), false); + int *neighbors = (int *)cma_alloc(neighbor_list.size()*sizeof(int), false); + int *offsets = (int *)cma_alloc( offset_list.size()*sizeof(int), false); + int *progress = (int *)cma_alloc( 5*sizeof(int), false); + + auto t_malloc_done = std::chrono::high_resolution_clock::now(); + + std::memcpy(edges0 , edge_list[0].data(), edge_list[0].size()*sizeof(int)); + std::memcpy(edges1 , edge_list[1].data(), edge_list[1].size()*sizeof(int)); + std::memcpy(edges2 , edge_list[2].data(), edge_list[2].size()*sizeof(int)); + std::memcpy(edges3 , edge_list[3].data(), edge_list[3].size()*sizeof(int)); + std::memcpy(edges4 , edge_list[4].data(), edge_list[4].size()*sizeof(int)); + std::memcpy(edges5 , edge_list[5].data(), edge_list[5].size()*sizeof(int)); + std::memcpy(edges6 , edge_list[6].data(), edge_list[6].size()*sizeof(int)); + std::memcpy(neighbors, neighbor_list.data(), neighbor_list.size()*sizeof(int)); + std::memcpy(offsets , offset_list.data(), offset_list.size()*sizeof(int)); + + auto t_memcpy_done = std::chrono::high_resolution_clock::now(); + + accelerator acc0(0x43C00000, 0x00010000); + accelerator acc1(0x43C10000, 0x00010000); + accelerator acc2(0x43C20000, 0x00010000); + accelerator acc3(0x43C30000, 0x00010000); + accelerator acc4(0x43C40000, 0x00010000); + accelerator acc5(0x43C50000, 0x00010000); + accelerator acc6(0x43C60000, 0x00010000); + acc0.program("/home/xilinx/code/tc/tc_opt.bit"); + + auto t_program_done = std::chrono::high_resolution_clock::now(); + + acc0.set(0x18, cma_get_phy_addr(neighbors)); + acc0.set(0x20, cma_get_phy_addr(offsets)); + acc0.set(0x28, cma_get_phy_addr(edges0)); + acc0.set(0x30, edge_list[0].size()); + acc0.set(0x38, cma_get_phy_addr(progress)); + + acc1.set(0x18, cma_get_phy_addr(neighbors)); + acc1.set(0x20, cma_get_phy_addr(offsets)); + acc1.set(0x28, cma_get_phy_addr(edges1)); + acc1.set(0x30, edge_list[1].size()); + acc1.set(0x38, cma_get_phy_addr(progress)); + + acc2.set(0x18, cma_get_phy_addr(neighbors)); + acc2.set(0x20, cma_get_phy_addr(offsets)); + acc2.set(0x28, cma_get_phy_addr(edges2)); + acc2.set(0x30, edge_list[2].size()); + acc2.set(0x38, cma_get_phy_addr(progress)); + + acc3.set(0x18, cma_get_phy_addr(neighbors)); + acc3.set(0x20, cma_get_phy_addr(offsets)); + acc3.set(0x28, cma_get_phy_addr(edges3)); + acc3.set(0x30, edge_list[3].size()); + acc3.set(0x38, cma_get_phy_addr(progress)); + + acc4.set(0x18, cma_get_phy_addr(neighbors)); + acc4.set(0x20, cma_get_phy_addr(offsets)); + acc4.set(0x28, cma_get_phy_addr(edges4)); + acc4.set(0x30, edge_list[4].size()); + acc4.set(0x38, cma_get_phy_addr(progress)); + + acc5.set(0x18, cma_get_phy_addr(neighbors)); + acc5.set(0x20, cma_get_phy_addr(offsets)); + acc5.set(0x28, cma_get_phy_addr(edges5)); + acc5.set(0x30, edge_list[5].size()); + acc5.set(0x38, cma_get_phy_addr(progress)); + + acc6.set(0x18, cma_get_phy_addr(neighbors)); + acc6.set(0x20, cma_get_phy_addr(offsets)); + acc6.set(0x28, cma_get_phy_addr(edges6)); + acc6.set(0x30, edge_list[6].size()); + acc6.set(0x38, cma_get_phy_addr(progress)); + + cout << "start execute.." << endl; + + auto t_acc_start = std::chrono::high_resolution_clock::now(); + acc0.start(); + acc1.start(); + acc2.start(); + acc3.start(); + acc4.start(); + acc5.start(); + acc6.start(); + + int tik = 1; + while(!acc0.done()) + { + tik++; + if ((tik % 10000) == 0) std::cout << "."; + } + + std::cout << "acc0 done! " << std::endl; + + while(!acc1.done()) + { + tik++; + if ((tik % 10000) == 0) std::cout << "."; + } + std::cout << "acc1 done! " << std::endl; + + while(!acc2.done()) + { + tik++; + if ((tik % 10000) == 0) std::cout << "."; + } + std::cout << "acc2 done! " << std::endl; + + while(!acc3.done()) + { + tik++; + if ((tik % 10000) == 0) std::cout << "."; + } + std::cout << "acc3 done! " << std::endl; + + while(!acc4.done()) + { + tik++; + if ((tik % 10000) == 0) std::cout << "."; + } + std::cout << "acc4 done! " << std::endl; + + while(!acc5.done()) + { + tik++; + if ((tik % 10000) == 0) std::cout << "."; + } + std::cout << "acc5 done! " << std::endl; + + while(!acc6.done()) + { + tik++; + if ((tik % 10000) == 0) std::cout << "."; + } + std::cout << "acc6 done! " << std::endl; + + auto t_acc_finish = std::chrono::high_resolution_clock::now(); + + cout << "\ndone execute.." << endl; + + std::cout << "result = " << acc0.get_return() + acc1.get_return() + acc2.get_return() + acc3.get_return() + + acc4.get_return() + acc5.get_return() + acc6.get_return() << std::endl; + + std::cout << "acc0 result = " << acc0.get_return() << std::endl; + std::cout << "acc1 result = " << acc1.get_return() << std::endl; + std::cout << "acc2 result = " << acc2.get_return() << std::endl; + std::cout << "acc3 result = " << acc3.get_return() << std::endl; + std::cout << "acc4 result = " << acc4.get_return() << std::endl; + std::cout << "acc5 result = " << acc5.get_return() << std::endl; + std::cout << "acc6 result = " << acc6.get_return() << std::endl; + + std::chrono::duration total_io_time = t_file_done - t_start; + std::chrono::duration total_malloc_time = t_malloc_done - t_file_done; + std::chrono::duration total_memcpy_time = t_memcpy_done - t_malloc_done; + std::chrono::duration total_program_time = t_program_done - t_memcpy_done; + std::chrono::duration total_exec_time = t_acc_finish - t_acc_start; + std::cout << "File IO time: " << total_io_time.count() << "s" << std::endl; + std::cout << "CMA alloc time: " << total_malloc_time.count() << "s" << std::endl; + std::cout << "Memcpy time: " << total_memcpy_time.count() << "s" << std::endl; + std::cout << "FPGA program time: " << total_program_time.count() << "s" << std::endl; + std::cout << "Kernel exec time: " << total_exec_time.count() << "s" << std::endl; + + cma_free(edges0); + cma_free(edges1); + cma_free(edges2); + cma_free(edges3); + cma_free(edges4); + cma_free(edges5); + cma_free(edges6); + cma_free(neighbors); + cma_free(offsets); + cma_free(progress); + + return 0; +} diff --git a/triangle_counting_host/cpp/tc_1pe.cpp b/triangle_counting_host/cpp/tc_1pe.cpp new file mode 100644 index 0000000..fbbdedd --- /dev/null +++ b/triangle_counting_host/cpp/tc_1pe.cpp @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for high_resolution_clock + +extern "C" +{ +#include "libxlnk_cma.h" +} + +using namespace std; + +class accelerator +{ +public: + accelerator(int base_addr=0x43C00000, int range=0x00010000) : base_addr(base_addr), range(range) + { + // virt_base = base_addr & ~(getpagesize() - 1); + virt_base = base_addr & ~(sysconf(_SC_PAGE_SIZE) - 1); + virt_offset = base_addr - virt_base; + mmap_file = open("/dev/mem", O_RDWR | O_SYNC); + if (mmap_file == -1) + cout << "Unable to open /dev/mem" << endl; + mmap_addr = (int*)mmap(NULL, range + virt_offset, PROT_READ | PROT_WRITE, + MAP_SHARED, mmap_file, virt_base); + if (mmap_addr == MAP_FAILED) + cout << "mmap fails. " << endl; + + mmap_space = mmap_addr + virt_offset; + } + ~accelerator() { close(mmap_file); } + + int get(int offset) { return mmap_space[offset >> 2]; } + + void set(int offset, int value) { mmap_space[offset >> 2] = value; } + + void start() { mmap_space[0x00] |= 1; } + + bool done() { return (mmap_space[0x00] & (1 << 1)); } + + bool idle() { return (mmap_space[0x00] & (1 << 2)); } + + bool ready() { return (mmap_space[0x00] & (1 << 3)); } + + int get_return() { return mmap_space[0x10 >> 2]; } + + int program(string bitfile_name) + { + char buf[4194304]; + const string BS_XDEVCFG = "/dev/xdevcfg"; + const string BS_IS_PARTIAL = "/sys/devices/soc0/amba/f8007000.devcfg/is_partial_bitstream"; + + int partial_bs_dev = open(BS_IS_PARTIAL.c_str(), O_WRONLY | O_NONBLOCK); + if (partial_bs_dev < 0) + { + printf("ERROR opening %s\n", BS_IS_PARTIAL.c_str()); + return -1; + } + int write_size = write(partial_bs_dev, "0", 1); + + int fpga_dev = open(BS_XDEVCFG.c_str(), O_WRONLY | O_NONBLOCK); + // int fpga_dev = open(BS_XDEVCFG.c_str(), O_WRONLY); + if (fpga_dev < 0) + { + printf("ERROR opening %s\n", BS_XDEVCFG.c_str()); + return -1; + } + + int bit_file = open(bitfile_name.c_str(), O_RDONLY); + if (bit_file < 0) + { + printf("ERROR opening %s\n", bitfile_name.c_str()); + return -1; + } + + int bit_file_size = read(bit_file, buf, 4194304); + write_size = write(fpga_dev, buf, bit_file_size); + + close(partial_bs_dev); + close(fpga_dev); + close(bit_file); + return 0; + } + +private: + int base_addr; + int range; + int virt_base; + int virt_offset; + int mmap_file; + int *mmap_addr; + int *mmap_space; +}; + +void read_graph(const char *filename, + std::vector &edge_list, + std::vector &neighbor_list, + std::vector &offset_list, + int &num_edge) +{ + std::ifstream ifs(filename); + + int degree_count = 0; + int prev_node = 0; + offset_list.push_back(0); + + if (ifs.is_open() && ifs.good()) + { + std::string str; + while (std::getline(ifs, str)) + { + if (!str.empty() && str[0] != '#') + { + std::istringstream ss(str); + int u, v; + ss >> u >> v; + if (prev_node != v) + { + offset_list.push_back(degree_count); + } + + prev_node = v; + if (u < v) + { + edge_list.push_back(v); + edge_list.push_back(u); + } + else + { + neighbor_list.push_back(u); + degree_count++; + } + } + } + } + ifs.close(); + offset_list.push_back(degree_count); + num_edge = edge_list.size() / 2; +} + +int main( int argc, char** argv ) +{ + + auto t_start = std::chrono::high_resolution_clock::now(); + + int num_edge = 0; + std::vector edge_list, neighbor_list, offset_list; + read_graph("../graph/soc-Epinions1_adj.tsv", edge_list, neighbor_list, offset_list, num_edge); + std::cout << "neighbor_list size= " << neighbor_list.size() << std::endl; + std::cout << "offset_list size= " << offset_list.size() << std::endl; + std::cout << "edge_list size= " << edge_list.size() << std::endl; + std::cout << "initialized num_edge = " << num_edge << std::endl; + + int *edges = (int *)cma_alloc( edge_list.size()*sizeof(int), false); + int *neighbors = (int *)cma_alloc(neighbor_list.size()*sizeof(int), false); + int *offsets = (int *)cma_alloc( offset_list.size()*sizeof(int), false); + int *progress = (int *)cma_alloc( 5*sizeof(int), false); + + std::memcpy(edges , edge_list.data(), edge_list.size()*sizeof(int)); + std::memcpy(neighbors, neighbor_list.data(), neighbor_list.size()*sizeof(int)); + std::memcpy(offsets , offset_list.data(), offset_list.size()*sizeof(int)); + + accelerator acc; + acc.program("/home/xilinx/code/tc/triangle_counting.bit"); + + auto t_program_done = std::chrono::high_resolution_clock::now(); + + acc.set(0x18, cma_get_phy_addr(neighbors)); + acc.set(0x20, cma_get_phy_addr(offsets)); + acc.set(0x28, cma_get_phy_addr(edges)); + acc.set(0x30, edge_list.size()); + acc.set(0x38, cma_get_phy_addr(progress)); + + cout << "start execute.." << endl; + + auto t_acc_start = std::chrono::high_resolution_clock::now(); + + acc.start(); + + int tik = 1; + while(!acc.done()) + { + tik++; + if ((tik % 10000) == 0) std::cout << "."; + //std::cout << tik << std::endl; +/* std::cout << progress[0] << " " << progress[1] << + " " << progress[2] << " " << progress[3] << " " << progress[4] << + " " << acc.get_return() << std::endl;*/ + } + + auto t_acc_finish = std::chrono::high_resolution_clock::now(); + + cout << "\ndone execute.." << endl; + + std::cout << "result = " << acc.get_return() << std::endl; + + std::chrono::duration total_exec_time = t_acc_finish - t_acc_start; + std::cout << "Kernel exec time: " << total_exec_time.count() << "s" << std::endl; + + cma_free(edges); + cma_free(neighbors); + cma_free(offsets); + cma_free(progress); + + return 0; +} diff --git a/triangle_counting_host/python/graph_parser.py b/triangle_counting_host/python/graph_parser.py new file mode 100644 index 0000000..5a42a09 --- /dev/null +++ b/triangle_counting_host/python/graph_parser.py @@ -0,0 +1,37 @@ + +neighbor_list = [] +offset_list = [0] +edge_list = [] + +graph_file = open("graph/test.tsv") +lines = graph_file.readlines() + +degree_count = 0 +prev_node = 0 + +for line in lines: + node_a, node_b, _ = map(int, line.split()) + if prev_node != node_b: + offset_list.append(degree_count) + + prev_node = node_b + if node_a < node_b: + edge_list.extend([node_b, node_a]) + else: + neighbor_list.append(node_a) + degree_count += 1 + +offset_list.append(degree_count) + +graph_file.close() + +print("neighbor_list size = ", len(neighbor_list)) +print("offset_list size = ", len(offset_list)) +print("edge_list size = ", len(edge_list)) + +f = open("test_parsed.tsv", "w") +f.write("%d %d %d\n" % (len(neighbor_list), len(offset_list), len(edge_list))) +f.write(" ".join(str(e) for e in neighbor_list) + "\n") +f.write(" ".join(str(e) for e in offset_list) + "\n") +f.write(" ".join(str(e) for e in edge_list) + "\n") +f.close() diff --git a/triangle_counting_host/python/intersect_host.py b/triangle_counting_host/python/intersect_host.py new file mode 100644 index 0000000..b9afa3a --- /dev/null +++ b/triangle_counting_host/python/intersect_host.py @@ -0,0 +1,52 @@ +# coding: utf-8 + +import sys +import numpy as np +import os +import time +from datetime import datetime +from pynq import Xlnk +from pynq import Overlay + +# load our design overlay +overlay = Overlay('intersect_hw.bit') +print("intersect_hw.bit loaded") + +myIP = overlay.intersect_0 + +xlnk = Xlnk() + +t1 = time.time() + +input_a = xlnk.cma_array(shape=(4096,), dtype=np.int32) +input_b = xlnk.cma_array(shape=(4096,), dtype=np.int32) + +for i in range(4096): + input_a[i] = i + input_b[i] = i + 1 + +myIP.write(0x18, input_a.physical_address) +myIP.write(0x20, input_b.physical_address) + +myIP.write(0x28, 2) +myIP.write(0x30, 2) + + +t2 = time.time() +t = t2 - t1 +print("Preparing input data time: ", str(t)) + +isready = 0; +myIP.write(0x00, 1) + +while( isready != 6 ): + isready = myIP.read(0x00) + +t3 = time.time() +t = t3 - t2 +#tbatch = tbatch + t +#print("Computation finished") +print("PL Time: ", str(t)) + +print("Return value: ", myIP.read(0x10)) + diff --git a/triangle_counting_host/python/tc_host.py b/triangle_counting_host/python/tc_host.py new file mode 100644 index 0000000..046f90d --- /dev/null +++ b/triangle_counting_host/python/tc_host.py @@ -0,0 +1,101 @@ +# coding: utf-8 + +import sys +import numpy as np +import os +import time +from datetime import datetime +from pynq import Xlnk +from pynq import Overlay + +# load our design overlay +overlay = Overlay('triangle_counting.bit') +print("triangle_counting.bit loaded") + +myIP = overlay.triangle_counting_0 + +t0 = time.time() + +neighbor_list = [] +offset_list = [0] +edge_list = [] + +graph_file = open("graph/soc-Epinions1_adj.tsv") +# graph_file = open("graph/test.tsv") +lines = graph_file.readlines() + +degree_count = 0 +prev_node = 0 + +for line in lines: + node_a, node_b, _ = map(int, line.split()) + if prev_node != node_b: + offset_list.append(degree_count) + + prev_node = node_b + if node_a < node_b: + edge_list.extend([node_b, node_a]) + else: + neighbor_list.append(node_a) + degree_count += 1 + +offset_list.append(degree_count) + +print("neighbor_list size= ", len(neighbor_list)) +print("offset_list size= ", len(offset_list)) +print("edge_list size= ", len(edge_list)) + +t1 = time.time() + +print("Finished reading graph file. ") +t = t1 - t0 +print("Reading input file time: ", str(t)) + +xlnk = Xlnk() + +neighbor = xlnk.cma_array(shape=(len(neighbor_list),), dtype=np.int32) +offset = xlnk.cma_array(shape=(len(offset_list),), dtype=np.int32) +edge = xlnk.cma_array(shape=(len(edge_list),), dtype=np.int32) +progress = xlnk.cma_array(shape=(5,), dtype=np.int32) + +neighbor[:] = neighbor_list +offset[:] = offset_list +edge[:] = edge_list + +# neighbor[:] = [2, 4, 5, 3, 4, 5, 4, 5, 5] +# offset[:] = [0, 0, 3, 6, 8, 9, 9] +# edge[:] = [5, 4, 5, 3, 5, 2, 5, 1, 4, 3, 4, 2, 4, 1, 3, 2, 2, 1] + +myIP.write(0x18, neighbor.physical_address) +myIP.write(0x20, offset.physical_address) +myIP.write(0x28, edge.physical_address) +myIP.write(0x30, len(edge_list)) +myIP.write(0x38, progress.physical_address) + +# for i in range(neighbor.size): +# print("neighbor[%d] = %d" % (i, neighbor[i])) + +# for i in range(offset.size): +# print("offset[%d] = %d" % (i, offset[i])) + +# for i in range(edge.size): +# print("edge[%d] = %d" % (i, edge[i])) + +t2 = time.time() +t = t2 - t1 +print("Preparing input data time: ", str(t)) + +isready = 0; +myIP.write(0x00, 1) + +while( isready != 6 ): +# print(progress[0], progress[1], progress[2], progress[3], progress[4]) + isready = myIP.read(0x00) + +t3 = time.time() +t = t3 - t2 +#tbatch = tbatch + t +#print("Computation finished") +print("PL Time: ", str(t)) + +print("Return value: ", myIP.read(0x10)) diff --git a/triangle_counting_host/python/tc_host_opt_4.py b/triangle_counting_host/python/tc_host_opt_4.py new file mode 100644 index 0000000..9e295d2 --- /dev/null +++ b/triangle_counting_host/python/tc_host_opt_4.py @@ -0,0 +1,162 @@ +# coding: utf-8 + +import sys +import numpy as np +import os +import time +import math +from datetime import datetime +from pynq import Xlnk +from pynq import Overlay + +# load our design overlay +overlay = Overlay('tc_opt_4.bit') +print("tc_opt_4.bit loaded") + +acc0 = overlay.triangle_counting_0 +acc1 = overlay.triangle_counting_1 +acc2 = overlay.triangle_counting_2 +acc3 = overlay.triangle_counting_3 + +t0 = time.time() + +neighbor_list = [] +offset_list = [0] +edge_list = [] + +graph_file = open("../../graph/soc-Epinions1_adj.tsv") +# graph_file = open("graph/test.tsv") +lines = graph_file.readlines() + +degree_count = 0 +prev_node = 0 + +for line in lines: + node_a, node_b, _ = map(int, line.split()) + if prev_node != node_b: + offset_list.append(degree_count) + + prev_node = node_b + if node_a < node_b: + edge_list.extend([node_b, node_a]) + else: + neighbor_list.append(node_a) + degree_count += 1 + +offset_list.append(degree_count) + +print("neighbor_list size= ", len(neighbor_list)) +print("offset_list size= ", len(offset_list)) +print("edge_list size= ", len(edge_list)) + +t1 = time.time() + +print("Finished reading graph file. ") +t = t1 - t0 +print("Reading input file time: ", str(t)) + +xlnk = Xlnk() + +num_edge = int(len(edge_list) / 2) +num_batch = 4 +num_edge_batch = int(math.floor(float(num_edge) / num_batch)) +num_edge_last_batch = num_edge - (num_batch-1)*num_edge_batch + +print(num_edge) +print(num_batch) +print(num_edge_batch) +print(num_edge_last_batch) + +neighbor = xlnk.cma_array(shape=(len(neighbor_list),), dtype=np.int32) +offset = xlnk.cma_array(shape=(len(offset_list),), dtype=np.int32) +edge1 = xlnk.cma_array(shape=(2*num_edge_batch,), dtype=np.int32) +edge2 = xlnk.cma_array(shape=(2*num_edge_batch,), dtype=np.int32) +edge3 = xlnk.cma_array(shape=(2*num_edge_batch,), dtype=np.int32) +edge4 = xlnk.cma_array(shape=(2*num_edge_last_batch,), dtype=np.int32) +progress = xlnk.cma_array(shape=(5,), dtype=np.int32) + +neighbor[:] = neighbor_list +offset[:] = offset_list +edge1[:] = edge_list[0:2*num_edge_batch] +edge2[:] = edge_list[2*num_edge_batch:4*num_edge_batch] +edge3[:] = edge_list[4*num_edge_batch:6*num_edge_batch] +edge4[:] = edge_list[6*num_edge_batch:] + +# neighbor[:] = [2, 4, 5, 3, 4, 5, 4, 5, 5] +# offset[:] = [0, 0, 3, 6, 8, 9, 9] +# edge[:] = [5, 4, 5, 3, 5, 2, 5, 1, 4, 3, 4, 2, 4, 1, 3, 2, 2, 1] + +acc0.write(0x00018, neighbor.physical_address) +acc0.write(0x00020, offset.physical_address) +acc0.write(0x00028, edge1.physical_address) +acc0.write(0x00030, 2*num_edge_batch) +acc0.write(0x00038, progress.physical_address) + +acc1.write(0x00018, neighbor.physical_address) +acc1.write(0x00020, offset.physical_address) +acc1.write(0x00028, edge2.physical_address) +acc1.write(0x00030, 2*num_edge_batch) +acc1.write(0x00038, progress.physical_address) + +acc2.write(0x00018, neighbor.physical_address) +acc2.write(0x00020, offset.physical_address) +acc2.write(0x00028, edge3.physical_address) +acc2.write(0x00030, 2*num_edge_batch) +acc2.write(0x00038, progress.physical_address) + +acc3.write(0x00018, neighbor.physical_address) +acc3.write(0x00020, offset.physical_address) +acc3.write(0x00028, edge4.physical_address) +acc3.write(0x00030, 2*num_edge_last_batch) +acc3.write(0x00038, progress.physical_address) + +# for i in range(neighbor.size): +# print("neighbor[%d] = %d" % (i, neighbor[i])) + +# for i in range(offset.size): +# print("offset[%d] = %d" % (i, offset[i])) + +# for i in range(edge.size): +# print("edge[%d] = %d" % (i, edge[i])) + +t2 = time.time() +t = t2 - t1 +print("Preparing input data time: ", str(t)) + +acc0.write(0x00000, 1) +acc1.write(0x00000, 1) +acc2.write(0x00000, 1) +acc3.write(0x00000, 1) + +isready = 0; +while( isready != 6 ): + isready = acc0.read(0x00000) + +isready = 0; +while( isready != 6 ): + isready = acc1.read(0x00000) + +isready = 0; +while( isready != 6 ): + isready = acc2.read(0x00000) + +isready = 0; +while( isready != 6 ): + isready = acc3.read(0x00000) + +t3 = time.time() +t = t3 - t2 +#tbatch = tbatch + t +#print("Computation finished") +print("PL Time: ", str(t)) + +result1 = acc0.read(0x00010) +result2 = acc1.read(0x00010) +result3 = acc2.read(0x00010) +result4 = acc3.read(0x00010) + +print("Return value 1: ", result1) +print("Return value 2: ", result2) +print("Return value 3: ", result3) +print("Return value 4: ", result4) +print("Number of triangles: ", result1+result2+result3+result4) diff --git a/triangle_counting_host/python/tc_host_opt_7.py b/triangle_counting_host/python/tc_host_opt_7.py new file mode 100644 index 0000000..22df986 --- /dev/null +++ b/triangle_counting_host/python/tc_host_opt_7.py @@ -0,0 +1,210 @@ +# coding: utf-8 + +import sys +import numpy as np +import os +import time +import math +from datetime import datetime +from pynq import Xlnk +from pynq import Overlay + +# load our design overlay +overlay = Overlay('tc_opt.bit') +print("tc_opt.bit loaded") + +acc0 = overlay.triangle_counting_0 +acc1 = overlay.triangle_counting_1 +acc2 = overlay.triangle_counting_2 +acc3 = overlay.triangle_counting_3 +acc4 = overlay.triangle_counting_4 +acc5 = overlay.triangle_counting_5 +acc6 = overlay.triangle_counting_6 + +t0 = time.time() + +neighbor_list = [] +offset_list = [0] +edge_list = [] + +graph_file = open("graph/soc-Epinions1_adj.tsv") +# graph_file = open("graph/test.tsv") +lines = graph_file.readlines() + +degree_count = 0 +prev_node = 0 + +for line in lines: + node_a, node_b, _ = map(int, line.split()) + if prev_node != node_b: + offset_list.append(degree_count) + + prev_node = node_b + if node_a < node_b: + edge_list.extend([node_b, node_a]) + else: + neighbor_list.append(node_a) + degree_count += 1 + +offset_list.append(degree_count) + +print("neighbor_list size= ", len(neighbor_list)) +print("offset_list size= ", len(offset_list)) +print("edge_list size= ", len(edge_list)) + +t1 = time.time() + +print("Finished reading graph file. ") +t = t1 - t0 +print("Reading input file time: ", str(t)) + +xlnk = Xlnk() + +num_edge = int(len(edge_list) / 2) +num_batch = 7 +num_edge_batch = int(math.floor(float(num_edge) / num_batch)) +num_edge_last_batch = num_edge - (num_batch-1)*num_edge_batch + +print(num_edge) +print(num_batch) +print(num_edge_batch) +print(num_edge_last_batch) + +neighbor = xlnk.cma_array(shape=(len(neighbor_list),), dtype=np.int32) +offset = xlnk.cma_array(shape=(len(offset_list),), dtype=np.int32) +edge1 = xlnk.cma_array(shape=(2*num_edge_batch,), dtype=np.int32) +edge2 = xlnk.cma_array(shape=(2*num_edge_batch,), dtype=np.int32) +edge3 = xlnk.cma_array(shape=(2*num_edge_batch,), dtype=np.int32) +edge4 = xlnk.cma_array(shape=(2*num_edge_batch,), dtype=np.int32) +edge5 = xlnk.cma_array(shape=(2*num_edge_batch,), dtype=np.int32) +edge6 = xlnk.cma_array(shape=(2*num_edge_batch,), dtype=np.int32) +edge7 = xlnk.cma_array(shape=(2*num_edge_last_batch,), dtype=np.int32) +progress = xlnk.cma_array(shape=(5,), dtype=np.int32) + +neighbor[:] = neighbor_list +offset[:] = offset_list +edge1[:] = edge_list[0:2*num_edge_batch] +edge2[:] = edge_list[2*num_edge_batch:4*num_edge_batch] +edge3[:] = edge_list[4*num_edge_batch:6*num_edge_batch] +edge4[:] = edge_list[6*num_edge_batch:8*num_edge_batch] +edge5[:] = edge_list[8*num_edge_batch:10*num_edge_batch] +edge6[:] = edge_list[10*num_edge_batch:12*num_edge_batch] +edge7[:] = edge_list[12*num_edge_batch:] + +# neighbor[:] = [2, 4, 5, 3, 4, 5, 4, 5, 5] +# offset[:] = [0, 0, 3, 6, 8, 9, 9] +# edge[:] = [5, 4, 5, 3, 5, 2, 5, 1, 4, 3, 4, 2, 4, 1, 3, 2, 2, 1] + +acc0.write(0x18, neighbor.physical_address) +acc0.write(0x20, offset.physical_address) +acc0.write(0x28, edge1.physical_address) +acc0.write(0x30, 2*num_edge_batch) +acc0.write(0x38, progress.physical_address) + +acc1.write(0x18, neighbor.physical_address) +acc1.write(0x20, offset.physical_address) +acc1.write(0x28, edge2.physical_address) +acc1.write(0x30, 2*num_edge_batch) +acc1.write(0x38, progress.physical_address) + +acc2.write(0x18, neighbor.physical_address) +acc2.write(0x20, offset.physical_address) +acc2.write(0x28, edge3.physical_address) +acc2.write(0x30, 2*num_edge_batch) +acc2.write(0x38, progress.physical_address) + +acc3.write(0x18, neighbor.physical_address) +acc3.write(0x20, offset.physical_address) +acc3.write(0x28, edge4.physical_address) +acc3.write(0x30, 2*num_edge_batch) +acc3.write(0x38, progress.physical_address) + +acc4.write(0x18, neighbor.physical_address) +acc4.write(0x20, offset.physical_address) +acc4.write(0x28, edge5.physical_address) +acc4.write(0x30, 2*num_edge_batch) +acc4.write(0x38, progress.physical_address) + +acc5.write(0x18, neighbor.physical_address) +acc5.write(0x20, offset.physical_address) +acc5.write(0x28, edge6.physical_address) +acc5.write(0x30, 2*num_edge_batch) +acc5.write(0x38, progress.physical_address) + +acc6.write(0x18, neighbor.physical_address) +acc6.write(0x20, offset.physical_address) +acc6.write(0x28, edge7.physical_address) +acc6.write(0x30, 2*num_edge_last_batch) +acc6.write(0x38, progress.physical_address) + +# for i in range(neighbor.size): +# print("neighbor[%d] = %d" % (i, neighbor[i])) + +# for i in range(offset.size): +# print("offset[%d] = %d" % (i, offset[i])) + +# for i in range(edge.size): +# print("edge[%d] = %d" % (i, edge[i])) + +t2 = time.time() +t = t2 - t1 +print("Preparing input data time: ", str(t)) + +acc0.write(0x00, 1) +acc1.write(0x00, 1) +acc2.write(0x00, 1) +acc3.write(0x00, 1) +acc4.write(0x00, 1) +acc5.write(0x00, 1) +acc6.write(0x00, 1) + +isready = 0; +while( isready != 6 ): + isready = acc0.read(0x00) + +isready = 0; +while( isready != 6 ): + isready = acc1.read(0x00) + +isready = 0; +while( isready != 6 ): + isready = acc2.read(0x00) + +isready = 0; +while( isready != 6 ): + isready = acc3.read(0x00) + +isready = 0; +while( isready != 6 ): + isready = acc4.read(0x00) + +isready = 0; +while( isready != 6 ): + isready = acc5.read(0x00) + +isready = 0; +while( isready != 6 ): + isready = acc6.read(0x00) + +t3 = time.time() +t = t3 - t2 +#tbatch = tbatch + t +#print("Computation finished") +print("PL Time: ", str(t)) + +result1 = acc0.read(0x10) +result2 = acc1.read(0x10) +result3 = acc2.read(0x10) +result4 = acc3.read(0x10) +result5 = acc4.read(0x10) +result6 = acc5.read(0x10) +result7 = acc6.read(0x10) + +print("Return value 1: ", result1) +print("Return value 2: ", result2) +print("Return value 3: ", result3) +print("Return value 4: ", result4) +print("Return value 5: ", result5) +print("Return value 6: ", result6) +print("Return value 7: ", result7) +print("Number of triangles: ", result1+result2+result3+result4+result5+result6+result7)