From 52d4cfe5c9afbffbe1f1b0dfb2cabf363f7155b7 Mon Sep 17 00:00:00 2001 From: Benjamin Ramhorst Date: Wed, 12 Mar 2025 14:47:10 +0100 Subject: [PATCH 01/52] Delete unnecessary files for the tutorial/test --- driver-guest/Makefile | 10 - driver-guest/guest_dev.h | 202 -- driver-guest/guest_drv.c | 103 - driver-guest/guest_drv.h | 13 - driver-guest/guest_fops.c | 329 --- driver-guest/guest_fops.h | 12 - driver-guest/guest_irq.c | 96 - driver-guest/guest_irq.h | 36 - driver-guest/guest_mm.c | 277 --- driver-guest/guest_mm.h | 10 - driver-guest/guest_pci.c | 226 --- driver-guest/guest_pci.h | 10 - driver-guest/setup.sh | 5 - driver-guest/test/Makefile | 18 - driver-guest/test/bench_huge.c | 165 -- driver-guest/test/test | Bin 16344 -> 0 bytes driver-guest/test/test.c | 178 -- driver-guest/test/test.h | 54 - driver-guest/test/test2.c | 72 - driver-guest/test/test_inter.c | 58 - driver/hypervisor/hypervisor.h | 171 -- driver/hypervisor/hypervisor_drv.c | 110 - driver/hypervisor/hypervisor_drv.h | 37 - driver/hypervisor/hypervisor_interrupts.c | 331 --- driver/hypervisor/hypervisor_interrupts.h | 43 - driver/hypervisor/hypervisor_mmu.c | 546 ----- driver/hypervisor/hypervisor_mmu.h | 39 - driver/hypervisor/hypervisor_ops.c | 1772 ----------------- driver/hypervisor/hypervisor_ops.h | 38 - examples_hw/CMakeLists.txt | 257 --- examples_hw/apps/gbm_dtrees/hdl/DTPackage.sv | 91 - .../apps/gbm_dtrees/hdl/DTProcessor.sv | 557 ------ .../apps/gbm_dtrees/hdl/Data_Memory.sv | 73 - .../gbm_dtrees/hdl/FPAdder_2cycles_latency.sv | 389 ---- .../gbm_dtrees/hdl/FPAdder_3cycles_latency.v | 458 ----- .../gbm_dtrees/hdl/FPAdder_4cycles_latency.v | 420 ---- .../apps/gbm_dtrees/hdl/FPAddersReduceTree.sv | 156 -- .../gbm_dtrees/hdl/FPAddersReduceTree_sync.sv | 286 --- .../apps/gbm_dtrees/hdl/FPAggregator.sv | 174 -- .../apps/gbm_dtrees/hdl/LineRateConvertor.sv | 134 -- .../apps/gbm_dtrees/hdl/RegBasedFIFO.sv | 122 -- .../apps/gbm_dtrees/hdl/Tree_Memory.sv | 192 -- examples_hw/apps/gbm_dtrees/hdl/bram.sv | 55 - .../apps/gbm_dtrees/hdl/bram_1_in_2_out.sv | 136 -- .../apps/gbm_dtrees/hdl/bus_aligner.sv | 183 -- .../apps/gbm_dtrees/hdl/compute_unit.sv | 241 --- examples_hw/apps/gbm_dtrees/hdl/delay.sv | 75 - .../apps/gbm_dtrees/hdl/dual_port_mem.sv | 128 -- examples_hw/apps/gbm_dtrees/hdl/gbm_slave.sv | 293 --- .../apps/gbm_dtrees/hdl/pe_datapath.sv | 369 ---- .../apps/gbm_dtrees/hdl/processing_element.sv | 442 ---- .../hdl/processing_element_async.sv | 453 ----- examples_hw/apps/gbm_dtrees/hdl/quick_fifo.sv | 149 -- .../apps/gbm_dtrees/hdl/smart_shifter.sv | 83 - examples_hw/apps/gbm_dtrees/init_ip.tcl | 2 - examples_hw/apps/gbm_dtrees/vfpga_top.svh | 240 --- .../hls/hllsketch_16x32/Collector.hpp | 144 -- .../hls/hllsketch_16x32/HllSketch.hpp | 183 -- .../hls/hllsketch_16x32/bit_utils.hpp | 165 -- .../hls/hllsketch_16x32/hashes.hpp | 108 - .../hls/hllsketch_16x32/hllsketch_16x32.cpp | 108 - .../hls/hllsketch_16x32/hllsketch_16x32.hpp | 101 - .../hls/hllsketch_16x32/stream.hpp | 310 --- examples_hw/apps/hyperloglog/init_ip.tcl | 2 - examples_hw/apps/hyperloglog/vfpga_top.svh | 70 - .../apps/rdma_aes/hdl/aes_pipe_stage.vhd | 75 - .../apps/rdma_aes/hdl/aes_pipeline.vhd | 129 -- examples_hw/apps/rdma_aes/hdl/aes_round.vhd | 81 - .../apps/rdma_aes/hdl/aes_round_last.vhd | 64 - examples_hw/apps/rdma_aes/hdl/aes_top.vhd | 256 --- .../apps/rdma_aes/hdl/data_pipeline.vhd | 86 - examples_hw/apps/rdma_aes/hdl/gf_mult.vhd | 127 -- .../apps/rdma_aes/hdl/inv_mix_columns.vhd | 81 - .../apps/rdma_aes/hdl/inv_s_box_lut.vhd | 41 - .../apps/rdma_aes/hdl/inv_shift_rows.vhd | 54 - .../apps/rdma_aes/hdl/key_expansion.vhd | 100 - .../apps/rdma_aes/hdl/key_pipe_reg.vhd | 50 - .../apps/rdma_aes/hdl/key_pipe_stage.vhd | 52 - .../apps/rdma_aes/hdl/key_pipeline.vhd | 106 - examples_hw/apps/rdma_aes/hdl/key_top.vhd | 66 - examples_hw/apps/rdma_aes/hdl/mix_columns.vhd | 51 - .../rdma_aes/hdl/mux_host_card_rd_rdma.sv | 178 -- .../rdma_aes/hdl/mux_host_card_wr_rdma.sv | 187 -- examples_hw/apps/rdma_aes/hdl/pipe_reg.vhd | 66 - examples_hw/apps/rdma_aes/hdl/rdma_aes_slv.sv | 245 --- examples_hw/apps/rdma_aes/hdl/s_box_lut.vhd | 40 - examples_hw/apps/rdma_aes/hdl/shift_rows.vhd | 54 - examples_hw/apps/rdma_aes/hdl/utils.vhd | 58 - examples_hw/apps/rdma_aes/vfpga_top.svh | 158 -- .../rdma_base/hdl/mux_host_card_rd_rdma.sv | 178 -- .../rdma_base/hdl/mux_host_card_wr_rdma.sv | 187 -- .../apps/rdma_base/hdl/rdma_base_slv.sv | 214 -- examples_hw/apps/rdma_base/vfpga_top.svh | 51 - examples_hw/apps/tcp_iperf/hls/CMakeLists.txt | 80 - examples_hw/apps/tcp_iperf/hls/axi_utils.cpp | 311 --- examples_hw/apps/tcp_iperf/hls/axi_utils.hpp | 1393 ------------- examples_hw/apps/tcp_iperf/hls/make.tcl.in | 34 - examples_hw/apps/tcp_iperf/hls/packet.hpp | 166 -- examples_hw/apps/tcp_iperf/hls/send_recv.cpp | 470 ----- examples_hw/apps/tcp_iperf/hls/send_recv.hpp | 99 - .../tcp_iperf/hls/send_recv_config.hpp.in | 3 - .../apps/tcp_iperf/hls/test_send_recv.cpp | 122 -- examples_hw/apps/tcp_iperf/hls/toe.hpp | 195 -- examples_hw/apps/tcp_iperf/perf_tcp_c0_0.svh | 56 - examples_hw/apps/tcp_iperf/send_recv.sv | 116 -- examples_hw/apps/tcp_iperf/send_recv_role.sv | 630 ------ examples_hw/apps/tcp_iperf/send_recv_slave.sv | 372 ---- examples_hw/apps/tcp_iperf/set_ila.tcl | 33 - .../floorplans/streaming_fplan_u55c.xdc | 9 - examples_sw/CMakeLists.txt | 116 -- .../apps/streaming_service/client/main.cpp | 142 -- .../apps/streaming_service/include/types.hpp | 23 - .../server/include/gbm_dtrees.hpp | 179 -- .../apps/streaming_service/server/main.cpp | 257 --- examples_sw/apps/tcp_iperf/main.cpp | 179 -- 115 files changed, 20330 deletions(-) delete mode 100644 driver-guest/Makefile delete mode 100644 driver-guest/guest_dev.h delete mode 100644 driver-guest/guest_drv.c delete mode 100644 driver-guest/guest_drv.h delete mode 100644 driver-guest/guest_fops.c delete mode 100644 driver-guest/guest_fops.h delete mode 100644 driver-guest/guest_irq.c delete mode 100644 driver-guest/guest_irq.h delete mode 100644 driver-guest/guest_mm.c delete mode 100644 driver-guest/guest_mm.h delete mode 100644 driver-guest/guest_pci.c delete mode 100644 driver-guest/guest_pci.h delete mode 100755 driver-guest/setup.sh delete mode 100644 driver-guest/test/Makefile delete mode 100644 driver-guest/test/bench_huge.c delete mode 100644 driver-guest/test/test delete mode 100644 driver-guest/test/test.c delete mode 100644 driver-guest/test/test.h delete mode 100644 driver-guest/test/test2.c delete mode 100644 driver-guest/test/test_inter.c delete mode 100644 driver/hypervisor/hypervisor.h delete mode 100644 driver/hypervisor/hypervisor_drv.c delete mode 100644 driver/hypervisor/hypervisor_drv.h delete mode 100644 driver/hypervisor/hypervisor_interrupts.c delete mode 100644 driver/hypervisor/hypervisor_interrupts.h delete mode 100644 driver/hypervisor/hypervisor_mmu.c delete mode 100644 driver/hypervisor/hypervisor_mmu.h delete mode 100644 driver/hypervisor/hypervisor_ops.c delete mode 100644 driver/hypervisor/hypervisor_ops.h delete mode 100644 examples_hw/CMakeLists.txt delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/DTPackage.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/DTProcessor.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/Data_Memory.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/FPAdder_2cycles_latency.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/FPAdder_3cycles_latency.v delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/FPAdder_4cycles_latency.v delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/FPAddersReduceTree.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/FPAddersReduceTree_sync.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/FPAggregator.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/LineRateConvertor.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/RegBasedFIFO.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/Tree_Memory.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/bram.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/bram_1_in_2_out.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/bus_aligner.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/compute_unit.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/delay.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/dual_port_mem.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/gbm_slave.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/pe_datapath.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/processing_element.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/processing_element_async.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/quick_fifo.sv delete mode 100644 examples_hw/apps/gbm_dtrees/hdl/smart_shifter.sv delete mode 100644 examples_hw/apps/gbm_dtrees/init_ip.tcl delete mode 100644 examples_hw/apps/gbm_dtrees/vfpga_top.svh delete mode 100644 examples_hw/apps/hyperloglog/hls/hllsketch_16x32/Collector.hpp delete mode 100644 examples_hw/apps/hyperloglog/hls/hllsketch_16x32/HllSketch.hpp delete mode 100644 examples_hw/apps/hyperloglog/hls/hllsketch_16x32/bit_utils.hpp delete mode 100644 examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hashes.hpp delete mode 100644 examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hllsketch_16x32.cpp delete mode 100644 examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hllsketch_16x32.hpp delete mode 100644 examples_hw/apps/hyperloglog/hls/hllsketch_16x32/stream.hpp delete mode 100644 examples_hw/apps/hyperloglog/init_ip.tcl delete mode 100644 examples_hw/apps/hyperloglog/vfpga_top.svh delete mode 100644 examples_hw/apps/rdma_aes/hdl/aes_pipe_stage.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/aes_pipeline.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/aes_round.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/aes_round_last.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/aes_top.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/data_pipeline.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/gf_mult.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/inv_mix_columns.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/inv_s_box_lut.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/inv_shift_rows.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/key_expansion.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/key_pipe_reg.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/key_pipe_stage.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/key_pipeline.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/key_top.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/mix_columns.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/mux_host_card_rd_rdma.sv delete mode 100644 examples_hw/apps/rdma_aes/hdl/mux_host_card_wr_rdma.sv delete mode 100644 examples_hw/apps/rdma_aes/hdl/pipe_reg.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/rdma_aes_slv.sv delete mode 100644 examples_hw/apps/rdma_aes/hdl/s_box_lut.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/shift_rows.vhd delete mode 100644 examples_hw/apps/rdma_aes/hdl/utils.vhd delete mode 100644 examples_hw/apps/rdma_aes/vfpga_top.svh delete mode 100644 examples_hw/apps/rdma_base/hdl/mux_host_card_rd_rdma.sv delete mode 100644 examples_hw/apps/rdma_base/hdl/mux_host_card_wr_rdma.sv delete mode 100644 examples_hw/apps/rdma_base/hdl/rdma_base_slv.sv delete mode 100644 examples_hw/apps/rdma_base/vfpga_top.svh delete mode 100644 examples_hw/apps/tcp_iperf/hls/CMakeLists.txt delete mode 100644 examples_hw/apps/tcp_iperf/hls/axi_utils.cpp delete mode 100644 examples_hw/apps/tcp_iperf/hls/axi_utils.hpp delete mode 100644 examples_hw/apps/tcp_iperf/hls/make.tcl.in delete mode 100644 examples_hw/apps/tcp_iperf/hls/packet.hpp delete mode 100644 examples_hw/apps/tcp_iperf/hls/send_recv.cpp delete mode 100644 examples_hw/apps/tcp_iperf/hls/send_recv.hpp delete mode 100644 examples_hw/apps/tcp_iperf/hls/send_recv_config.hpp.in delete mode 100644 examples_hw/apps/tcp_iperf/hls/test_send_recv.cpp delete mode 100755 examples_hw/apps/tcp_iperf/hls/toe.hpp delete mode 100644 examples_hw/apps/tcp_iperf/perf_tcp_c0_0.svh delete mode 100644 examples_hw/apps/tcp_iperf/send_recv.sv delete mode 100644 examples_hw/apps/tcp_iperf/send_recv_role.sv delete mode 100644 examples_hw/apps/tcp_iperf/send_recv_slave.sv delete mode 100644 examples_hw/apps/tcp_iperf/set_ila.tcl delete mode 100644 examples_hw/floorplans/streaming_fplan_u55c.xdc delete mode 100644 examples_sw/CMakeLists.txt delete mode 100644 examples_sw/apps/streaming_service/client/main.cpp delete mode 100644 examples_sw/apps/streaming_service/include/types.hpp delete mode 100644 examples_sw/apps/streaming_service/server/include/gbm_dtrees.hpp delete mode 100644 examples_sw/apps/streaming_service/server/main.cpp delete mode 100644 examples_sw/apps/tcp_iperf/main.cpp diff --git a/driver-guest/Makefile b/driver-guest/Makefile deleted file mode 100644 index 448f886b0..000000000 --- a/driver-guest/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -KERNELDIR ?= /lib/modules/$(shell uname -r)/build - -obj-m := coyote-guest.o -coyote-guest-objs := guest_drv.o guest_fops.o guest_pci.o guest_mm.o guest_irq.o - -all: - $(MAKE) -C $(KERNELDIR) M=$(PWD) modules - -clean: - $(MAKE) -C $(KERNELDIR) M=$(PWD) clean \ No newline at end of file diff --git a/driver-guest/guest_dev.h b/driver-guest/guest_dev.h deleted file mode 100644 index 7b366d01a..000000000 --- a/driver-guest/guest_dev.h +++ /dev/null @@ -1,202 +0,0 @@ -#ifndef __GUEST_DEV_H__ -#define __GUEST_DEV_H__ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define GUEST_MODULE_AUTHOR "Paul Dahlke " -#define GUEST_MODULE_LICENSE "GPL" -#define GUEST_MODULE_DESCRIPTION "Coyote hypervisor guest driver" -#define DRV_NAME "coyote-guest" - -#define COYOTE_DEBUG 0 -#define MAX_USER_ARGS 32 -#define INVALID_CPID 0xffffffffffffffff -#define N_CPID_MAX 64 - -/* Hash */ -#define USER_HASH_TABLE_ORDER 8 - -/* MMAP Regions */ -#define MMAP_CTRL 0x0 -#define MMAP_CNFG 0x1 -#define MMAP_CNFG_AVX 0x2 -#define MMAP_WB 0x3 -#define MMAP_BUFF 0x200 -#define MMAP_PR 0x400 -#define FPGA_CTRL_SIZE 256 * 1024 -#define FPGA_CTRL_OFFS 0x100000 -#define FPGA_CTRL_LTLB_SIZE FPGA_CTRL_SIZE / 4 -#define FPGA_CTRL_LTLB_OFFS 0x0 -#define FPGA_CTRL_STLB_SIZE FPGA_CTRL_SIZE / 4 -#define FPGA_CTRL_STLB_OFFS 0x10000 -#define FPGA_CTRL_USER_SIZE FPGA_CTRL_SIZE / 4 -#define FPGA_CTRL_USER_OFFS 0x20000 -#define FPGA_CTRL_CNFG_SIZE FPGA_CTRL_SIZE / 4 -#define FPGA_CTRL_CNFG_OFFS 0x30000 -#define FPGA_CTRL_CNFG_AVX_SIZE 256 * 1024 -#define FPGA_CTRL_CNFG_AVX_OFFS 0x1000000 - -/* FPGA dynamic control config */ -#define FPGA_CNFG_CTRL_IRQ_RESTART 0x100 - -/* IOCTL */ -#define IOCTL_ALLOC_HOST_USER_MEM _IOW('D', 1, unsigned long) // large pages (no hugepage support) -#define IOCTL_FREE_HOST_USER_MEM _IOW('D', 2, unsigned long) -#define IOCTL_ALLOC_HOST_RECONFIG_MEM _IOW('D', 3, unsigned long) // pr pages -#define IOCTL_FREE_HOST_RECONFIG_MEM _IOW('D', 4, unsigned long) -#define IOCTL_MAP_USER _IOW('D', 5, unsigned long) // map -#define IOCTL_UNMAP_USER _IOW('D', 6, unsigned long) -#define IOCTL_REGISTER_PID _IOW('D', 7, unsigned long) // register pid -#define IOCTL_UNREGISTER_PID _IOW('D', 8, unsigned long) -#define IOCTL_RECONFIG_LOAD _IOW('D', 9, unsigned long) // reconfiguration - -#define IOCTL_ARP_LOOKUP _IOW('D', 10, unsigned long) // arp lookup -#define IOCTL_SET_IP_ADDRESS _IOW('D', 11, unsigned long) -#define IOCTL_SET_MAC_ADDRESS _IOW('D', 12, unsigned long) -#define IOCTL_WRITE_CTX _IOW('D', 13, unsigned long) // qp context -#define IOCTL_WRITE_CONN _IOW('D', 14, unsigned long) // qp connection -#define IOCTL_SET_TCP_OFFS _IOW('D', 15, unsigned long) // tcp mem offsets - -#define IOCTL_READ_CNFG _IOR('D', 32, unsigned long) // status cnfg -#define IOCTL_XDMA_STATS _IOR('D', 33, unsigned long) // status xdma -#define IOCTL_NET_STATS _IOR('D', 34, unsigned long) // status network -#define IOCTL_READ_ENG_STATUS _IOR('D', 35, unsigned long) // status engines - -#define IOCTL_NET_DROP _IOW('D', 36, unsigned long) // net dropper -#define IOCTL_TEST_INTERRUPT _IO('D', 37) - -/* BAR2 offsets */ -#define REGISTER_PID_OFFSET 0x00 -#define UNREGISTER_PID_OFFSET 0x08 -#define MAP_USER_OFFSET 0x10 -#define UNMAP_USER_OFFSET 0x18 -#define READ_CNFG_OFFSET 0x20 -#define PUT_ALL_USER_PAGES 0x28 -#define TEST_INTERRUPT_OFFSET 0x30 - -/* Interrupts */ -#define NUM_USER_INTERRUPTS 1 - -/* LTLB values */ -// TODO: read the config from the device to popoulate those -#define LTLB_PAGE_SHIFT 21 -#define LTLB_PAGE_SIZE (1 << LTLB_PAGE_SHIFT) - -/* Obtain the 32 most significant (high) bits of a 32-bit or 64-bit address */ -#define HIGH_32(addr) ((addr >> 16) >> 16) -/* Obtain the 32 least significant (low) bits of a 32-bit or 64-bit address */ -#define LOW_32(addr) (addr & 0xffffffffUL) - -#if (COYOTE_DEBUG == 0) -#define dbg_info(...) -#else -#define dbg_info(fmt, ...) pr_info("%s():" fmt, \ - __func__, ##__VA_ARGS__) -#endif - -/* FPGA dynamic config reg map */ -struct fpga_cnfg_regs -{ - uint64_t ctrl; - uint64_t vaddr_rd; - uint64_t len_rd; - uint64_t vaddr_wr; - uint64_t len_wr; - uint64_t vaddr_miss; - uint64_t len_miss; - uint64_t datapath_set; - uint64_t datapath_clr; - uint64_t stat_cmd_used_rd; - uint64_t stat_cmd_used_wr; - uint64_t stat_sent[6]; - uint64_t stat_pfaults; - uint64_t wback[4]; - // Rest of regs not used in the driver -} __packed; - -/* FPGA dynamic config reg map */ -struct fpga_cnfg_regs_avx -{ - uint64_t ctrl[4]; - uint64_t vaddr_miss; - uint64_t len_miss; - uint64_t pf[2]; - uint64_t datapath_set[4]; - uint64_t datapath_clr[4]; - uint64_t stat[4]; - uint64_t wback[4]; - // Rest not used in the driver -} __packed; - -/* vfpga device */ -struct vfpga -{ - struct pci_dev *pdev; - struct cdev cdev; - struct device *dev; - pid_t *pid_array; - - spinlock_t cpid_lock; - spinlock_t lock; - - uint64_t curr_offset; - uint64_t n_pages; - - struct - { - volatile void __iomem *bar0; - volatile void __iomem *bar2; - volatile void __iomem *bar4; - - resource_size_t bar0_start, bar0_end; - resource_size_t bar2_start, bar2_end; - resource_size_t bar4_start, bar4_end; - } pci_resources; - - struct msix_entry irq_entry[32]; - - // Control region - uint64_t fpga_phys_addr_ctrl; - uint64_t fpga_phys_addr_ctrl_avx; - - struct fpga_cnfg_regs *fpga_cnfg; // config - struct fpga_cnfg_regs_avx *fpga_cnfg_avx; // config AVX - - int en_avx; -}; - -struct user_pages -{ - struct hlist_node entry; - uint64_t vaddr; - uint64_t n_pages; - int32_t cpid; - struct page **hpages; -}; - -struct hypervisor_map_notifier -{ - uint64_t npages; - uint64_t len; - uint64_t gva; - uint64_t cpid; - uint64_t dirtied; - uint64_t is_huge; - uint64_t gpas[0]; -}; - -extern struct hlist_head user_sbuff_map[1 << (USER_HASH_TABLE_ORDER)]; - -extern struct class *guest_class; -extern struct vfpga vfpga; -extern dev_t devt; -extern struct file_operations fops; - -#endif \ No newline at end of file diff --git a/driver-guest/guest_drv.c b/driver-guest/guest_drv.c deleted file mode 100644 index b1b943904..000000000 --- a/driver-guest/guest_drv.c +++ /dev/null @@ -1,103 +0,0 @@ - -#include "guest_drv.h" - -struct class *guest_class; - -// -// Device driver struct -// - -static struct pci_device_id ids[] = { - { PCI_DEVICE(0x0102, 0x0304) }, - { 0 } -}; -MODULE_DEVICE_TABLE(pci, ids); - -static struct pci_driver driver = { - .name = DRV_NAME, - .probe = guest_probe, - .remove = guest_remove, - .id_table = ids -}; - -/** - * @brief Sets the permissions for the device file. Harcoded - * to rw for all users - * - * @param dev - * @param env - * @return int - */ -static int guest_uevent(struct device *dev, struct kobj_uevent_env *env) -{ - add_uevent_var(env, "DEVMODE=%#o", 0666); - - return 0; -} - -// -// Module enty points -// - -/** - * @brief Called on load of the module and sets up a driver that - * can handle the emulated pci device - * - * @return int - */ -static int __init guest_init() -{ - int ret_val; - - ret_val = 0; - - // Create class - guest_class = class_create(THIS_MODULE, "coyote-guest"); - guest_class->dev_uevent = guest_uevent; - - if (IS_ERR_OR_NULL(guest_class)) - { - pr_err("Failed to create pci class\n"); - return -1; - } - - // Register driver - ret_val = pci_register_driver(&driver); - if (ret_val) - { - pr_err("Failed to register driver\n"); - } - - dbg_info("init coyote guest driver"); - - return ret_val; - -} - -/** - * @brief clean up of management structures - * and removes all sysfs entries. Deregisters the driver - * with the kernel. - * - */ -static void __exit guest_exit() -{ - int ret_val; - - ret_val = 0; - - pci_unregister_driver(&driver); - class_destroy(guest_class); - - dbg_info("exit coyote guest driver"); -} - -// -// Registering module -// - -module_init(guest_init); -module_exit(guest_exit); -MODULE_AUTHOR(GUEST_MODULE_AUTHOR); -MODULE_LICENSE(GUEST_MODULE_LICENSE); -MODULE_DESCRIPTION(GUEST_MODULE_DESCRIPTION); \ No newline at end of file diff --git a/driver-guest/guest_drv.h b/driver-guest/guest_drv.h deleted file mode 100644 index d067b8cb7..000000000 --- a/driver-guest/guest_drv.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef __GUEST_DRV_H__ -#define __GUEST_DRV_H__ - -#include "guest_dev.h" -#include "guest_pci.h" - -/* -* Main functions -*/ -static int __init guest_init(void); -static void __exit guest_exit(void); - -#endif \ No newline at end of file diff --git a/driver-guest/guest_fops.c b/driver-guest/guest_fops.c deleted file mode 100644 index 7e45802c8..000000000 --- a/driver-guest/guest_fops.c +++ /dev/null @@ -1,329 +0,0 @@ -#include "guest_fops.h" - -/** - * @brief file operations that handle this device - * - */ -struct file_operations fops = { - .open = guest_open, - .release = guest_release, - .unlocked_ioctl = guest_ioctl, - .mmap = guest_mmap}; - -/** - * @brief called on opening a file. - * Sets the file descriptor private data to later on access the - * device struct easier. - * - * @param inode - * @param f - * @return int - */ -int guest_open(struct inode *inode, struct file *f) -{ - struct vfpga *d; - - d = container_of(inode->i_cdev, struct vfpga, cdev); - BUG_ON(!d); - - dbg_info("fpga device acquired\n"); - - f->private_data = (void *)d; - - return 0; -} - -/** - * @brief Called when the device file is closed. - * Releases all memory associated with this device by performing a - * call to the hypervisor and cleaning up all local mappings. - * - * @param inode - * @param f - * @return int - */ -int guest_release(struct inode *inode, struct file *f) -{ - struct vfpga *d; - - d = (struct vfpga *)f->private_data; - BUG_ON(!d); - - // release all user pages - guest_put_all_user_pages(d, 1); - - dbg_info("fpga device released"); - return 0; -} - -/** - * @brief guest version of register pid. - * Writes to the REGISTER_PID register to - * notify hypervisor of pid and then reads the same register - * to get a cpid - * - * @param d vfpga struct - * @param pid pid of the process - * @return int32_t returns the cpid - */ -static int32_t register_pid(struct vfpga *d, pid_t pid) -{ - uint64_t cpid; - unsigned long addr; - - BUG_ON(!d); - addr = (unsigned long)d->pci_resources.bar2 + REGISTER_PID_OFFSET; - cpid = INVALID_CPID; - - // Write pid to device - writeq((uint64_t)pid, (void __iomem *)addr); - - // pull cpid - while (cpid == INVALID_CPID) - { - cpid = readq((void __iomem *)addr); - } - - // store for bookkeeping - d->pid_array[cpid] = pid; - - return cpid; -} - -/** - * @brief Notifies the hypervisor of the unregistration of a process. Management - * is handled in the hypervisor. - * - * @param d - * @param cpid - */ -static void unregister_pid(struct vfpga *d, pid_t cpid) -{ - unsigned long addr; - - BUG_ON(!d); - addr = (unsigned long)d->pci_resources.bar2 + UNREGISTER_PID_OFFSET; - - // write cpid to unregister offset - writeq((uint64_t)cpid, (void __iomem *)addr); - d->pid_array[cpid] = 0; -} - -/** - * @brief Handles ioctls to the device. Exposes - * a similar interface as the normal driver to make it - * easy to use the library and existing code. The following - * ioctl calls are implemented: - * - * - IOCTL_REGISTER_PID - * Called to register a process. Passes its pid as argument and returns - * a cpid - * - * - IOCTL_UNREGISTER_PID - * Called with the cpid and deregisters this cpid again. - * - * - IOCTL_MAP_USER - * Called with vaddr, length, and cpid. Maps a region allocated by the user - * to the fpga. - * - * - IOCTL_UNMAP_USER - * Called with vaddr and cpid. Unmaps a previously mapped region. - * - * - IOCTL_READ_CNFG - * Called without any arguments and returns a number that encodes - * the platform configuration. - * - * @param f - * @param cmd - * @param arg - * @return long - */ -long guest_ioctl(struct file *f, unsigned int cmd, unsigned long arg) -{ - struct vfpga *d; - int ret_val; - uint64_t tmp[MAX_USER_ARGS]; - uint64_t cpid; - - d = (struct vfpga *)f->private_data; - BUG_ON(!d); - - dbg_info("ioctl called with cmd %d and arg %lx\n", cmd, arg); - - switch (cmd) - { - case IOCTL_REGISTER_PID: - { - ret_val = copy_from_user(&tmp, (unsigned long *)arg, sizeof(unsigned long)); - if (ret_val) - { - pr_info("Failed to copy from user"); - return ret_val; - } - - spin_lock(&d->cpid_lock); - cpid = register_pid(d, tmp[0]); - if (cpid == -1) - { - dbg_info("registraion failed for pid %lld\n", tmp[0]); - spin_unlock(&d->cpid_lock); - return -EIO; - } - - dbg_info("registration succeeded pid %lld, cpid %lld\n", tmp[0], cpid); - ret_val = copy_to_user((unsigned long *)arg + 1, &cpid, sizeof(unsigned long)); - if (ret_val) - { - pr_info("Failed to wirte data"); - spin_unlock(&d->cpid_lock); - return ret_val; - } - spin_unlock(&d->cpid_lock); - return 0; - } - case IOCTL_UNREGISTER_PID: - { - ret_val = copy_from_user(&tmp, (unsigned long *)arg, sizeof(unsigned long)); - if (ret_val) - { - pr_info("Failed to copy from user"); - return ret_val; - } - - spin_lock(&d->cpid_lock); - - unregister_pid(d, tmp[0]); - - spin_unlock(&d->cpid_lock); - dbg_info("unregistration succeeded cpid %lld\n", tmp[0]); - - return 0; - } - case IOCTL_MAP_USER: - { - // read vaddr + len + cpid - ret_val = copy_from_user(&tmp, (unsigned long *)arg, sizeof(unsigned long) * 3); - if (ret_val) - { - pr_info("Failed to copy addr info from user\n"); - return ret_val; - } - - cpid = tmp[2]; - - dbg_info("Mapping user vaddr %llx of size %llu for cpid %llu\n", tmp[0], tmp[1], tmp[2]); - ret_val = guest_get_user_pages(d, tmp[0], tmp[1], (int32_t)tmp[2], d->pid_array[cpid]); - return ret_val; - } - case IOCTL_UNMAP_USER: - { - // read vaddr + cpid - ret_val = copy_from_user(&tmp, (unsigned long *)arg, sizeof(unsigned long) * 2); - if (ret_val) - { - pr_info("could not get addr info from user\n"); - return ret_val; - } - - dbg_info("Putting user pages\n"); - ret_val = guest_put_user_pages(d, tmp[0], (int32_t)tmp[1], 1); - return ret_val; - } - case IOCTL_READ_CNFG: - { - tmp[0] = readq((void __iomem *)d->pci_resources.bar2 + READ_CNFG_OFFSET); - ret_val = copy_to_user((unsigned long *)arg, &tmp, sizeof(unsigned long)); - if (ret_val) - { - pr_info("Cannot read configuration\n"); - return ret_val; - } - return 0; - } - case IOCTL_TEST_INTERRUPT: - { - writeq(0, d->pci_resources.bar2 + TEST_INTERRUPT_OFFSET); - return 0; - } - } - - // Not supported IOCTL - return -EINVAL; -} - -/** - * @brief mmap of regions of the fpga. The actual fpga is here not available - * we map into bar0 of the virtual device that acts as a passthrough to the - * real hardware. Transparent to the user. - * - * @param f - * @param vma - * @return int - */ -int guest_mmap(struct file *f, struct vm_area_struct *vma) -{ - struct vfpga *d; - unsigned long vaddr; - unsigned long target_addr; - int ret_val; - - d = (struct vfpga *)f->private_data; - BUG_ON(!d); - - vaddr = vma->vm_start; - - dbg_info("called mmap with offset %lu\n", vma->vm_pgoff); - - // Do not cache this pages - vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); - - // user ctrl region - if (vma->vm_pgoff == MMAP_CTRL) - { - dbg_info("mapping user ctrl region at %llx of size %x\n", - d->fpga_phys_addr_ctrl + FPGA_CTRL_USER_OFFS, FPGA_CTRL_USER_SIZE); - target_addr = (unsigned long)d->fpga_phys_addr_ctrl + FPGA_CTRL_USER_OFFS; - - ret_val = remap_pfn_range(vma, vma->vm_start, target_addr >> PAGE_SHIFT, - FPGA_CTRL_USER_SIZE, vma->vm_page_prot); - if (ret_val) - { - return -EIO; - } - return 0; - } - - // cnfg region - if (vma->vm_pgoff == MMAP_CNFG) - { - dbg_info("mapping config region at %llx of size %x\n", - d->fpga_phys_addr_ctrl + FPGA_CTRL_CNFG_OFFS, FPGA_CTRL_USER_SIZE); - target_addr = (unsigned long)d->fpga_phys_addr_ctrl + FPGA_CTRL_CNFG_OFFS; - - ret_val = remap_pfn_range(vma, vma->vm_start, target_addr >> PAGE_SHIFT, - FPGA_CTRL_CNFG_SIZE, vma->vm_page_prot); - if (ret_val) - { - return -EIO; - } - return 0; - } - - // cnfg AVX region - if (vma->vm_pgoff == MMAP_CNFG_AVX) - { - dbg_info("mapping config AVX region at %llx of size %x\n", - d->fpga_phys_addr_ctrl_avx, FPGA_CTRL_CNFG_AVX_SIZE); - target_addr = (unsigned long)d->fpga_phys_addr_ctrl_avx; - - ret_val = remap_pfn_range(vma, vma->vm_start, target_addr >> PAGE_SHIFT, - FPGA_CTRL_CNFG_AVX_SIZE, vma->vm_page_prot); - if (ret_val) - { - return -EIO; - } - return 0; - } - - return -EINVAL; -} \ No newline at end of file diff --git a/driver-guest/guest_fops.h b/driver-guest/guest_fops.h deleted file mode 100644 index 37eb9b473..000000000 --- a/driver-guest/guest_fops.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef __GUEST_FOPS_H__ -#define __GUEST_FOPS_H__ - -#include "guest_dev.h" -#include "guest_mm.h" - -int guest_open(struct inode* inode, struct file *f); -int guest_release(struct inode *inode, struct file *f); -long guest_ioctl(struct file *f, unsigned int cmd, unsigned long arg); -int guest_mmap(struct file *f, struct vm_area_struct *vma); - -#endif \ No newline at end of file diff --git a/driver-guest/guest_irq.c b/driver-guest/guest_irq.c deleted file mode 100644 index 6dcb3e7e9..000000000 --- a/driver-guest/guest_irq.c +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) 2021, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#include "guest_irq.h" - -/** - * @brief Guest callback for a page fault on the fpga. - * This handles an interrupt that is fired by the hypervisor - * as a response to a real interrupt happening in hardware. - * The function reads the faulting address from special hardware - * registers and uses the get user pages function - * to install mappings on the hardware. - * - * @param irq - * @param dev_id - * @return irqreturn_t - */ -irqreturn_t guest_fpga_tlb_miss_isr(int irq, void *dev_id) -{ - unsigned long flags; - uint64_t vaddr; - uint32_t len; - int32_t cpid; - struct vfpga *d; - int ret_val = 0; - pid_t pid; - uint64_t tmp; - - dbg_info("(irq=%d) page fault ISR\n", irq); - BUG_ON(!dev_id); - - d = (struct vfpga *)dev_id; - BUG_ON(!d); - - // lock - spin_lock_irqsave(&(d->lock), flags); - - // read page fault from hardware register - if (d->en_avx) { - vaddr = d->fpga_cnfg_avx->vaddr_miss; - tmp = d->fpga_cnfg_avx->len_miss; - len = LOW_32(tmp); - cpid = (int32_t)HIGH_32(tmp); - } - else { - vaddr = d->fpga_cnfg->vaddr_miss; - tmp = d->fpga_cnfg->len_miss; - len = LOW_32(tmp); - cpid = (int32_t)HIGH_32(tmp); - } - dbg_info("page fault, vaddr %llx, length %x, cpid %d\n", vaddr, len, cpid); - - // get user pages - pid = d->pid_array[cpid]; - ret_val = guest_get_user_pages(d, vaddr, len, cpid, pid); - - if (!ret_val) { - // restart the engine - if (d->en_avx) - d->fpga_cnfg_avx->ctrl[0] = FPGA_CNFG_CTRL_IRQ_RESTART; - else - d->fpga_cnfg->ctrl = FPGA_CNFG_CTRL_IRQ_RESTART; - } - else { - dbg_info("pages could not be obtained\n"); - } - - // unlock - spin_unlock_irqrestore(&(d->lock), flags); - - return IRQ_HANDLED; -} \ No newline at end of file diff --git a/driver-guest/guest_irq.h b/driver-guest/guest_irq.h deleted file mode 100644 index a0c3f90e3..000000000 --- a/driver-guest/guest_irq.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2021, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#ifndef __GUEST_IRQ_H__ -#define __GUEST_IRQ_H__ - -#include "guest_dev.h" -#include "guest_mm.h" - -irqreturn_t guest_fpga_tlb_miss_isr(int irq, void *dev_id); - -#endif \ No newline at end of file diff --git a/driver-guest/guest_mm.c b/driver-guest/guest_mm.c deleted file mode 100644 index da72379a6..000000000 --- a/driver-guest/guest_mm.c +++ /dev/null @@ -1,277 +0,0 @@ -#include "guest_mm.h" - -struct hlist_head user_sbuff_map[1 << (USER_HASH_TABLE_ORDER)]; - -/** - * @brief Release all pages that are hold by the device - * d. The param dirtied specifies if the pages should be marked - * dirty before they can be evicted. - * - * @param d vfpga struct - * @param dirtied - * @return int - */ -int guest_put_all_user_pages(struct vfpga *d, int dirtied) -{ - int i, bkt; - struct user_pages *tmp_buff; - - BUG_ON(!d); - - hash_for_each(user_sbuff_map, bkt, tmp_buff, entry) - { - if (dirtied) - { - for (i = 0; i < tmp_buff->n_pages; i++) - SetPageDirty(tmp_buff->hpages[i]); - } - - for (i = 0; i < tmp_buff->n_pages; i++) - { - put_page(tmp_buff->hpages[i]); - } - - kfree(tmp_buff->hpages); - hash_del(&tmp_buff->entry); - } - - // tell hypervisor to flush everything - writeq((uint64_t) dirtied, (void __iomem *)d->pci_resources.bar2 + PUT_ALL_USER_PAGES); - - return 0; -} - -/** - * @brief explictly unmap an existing mapping. - * Takes the vaddr and cpid and unmaps an existing mapping - * from the fpga. It also removes this mapping from the hashtable. - * - * @param d vfpga device - * @param vaddr start address that is to be unmapped - * @param cpid cpid that mapped this address - * @param dirtied mark pages as dirty prior to putting them - * @return int - */ -int guest_put_user_pages(struct vfpga *d, uint64_t vaddr, int32_t cpid, int dirtied) -{ - int i; - struct user_pages *tmp_buff; - struct hypervisor_map_notifier *notifier; - uint64_t notifier_addr; - - BUG_ON(!d); - - /* - Iterate through all possible buckets there the vaddr could be mapped - to. Check if the found the correct entry and if so, remove it. - - The unmapping involves a call to the hypervisor. - */ - hash_for_each_possible(user_sbuff_map, tmp_buff, entry, vaddr) - { - if (tmp_buff->vaddr == vaddr && tmp_buff->cpid == cpid) - { - if (dirtied) - { - for (i = 0; i < tmp_buff->n_pages; i++) - SetPageDirty(tmp_buff->hpages[i]); - } - - for (i = 0; i < tmp_buff->n_pages; i++) - { - put_page(tmp_buff->hpages[i]); - } - - // Notifiy hypervisor of unmap - notifier = kzalloc(sizeof(struct hypervisor_map_notifier), GFP_KERNEL); - if (!notifier) - { - pr_info("failed to allocate memory for the hypervisor notifier"); - return -ENOMEM; - } - - // populate notifier - notifier->gva = vaddr; - notifier->cpid = cpid; - notifier->dirtied = dirtied; - - // notify - notifier_addr = virt_to_phys(notifier); - writeq(notifier_addr, d->pci_resources.bar2 + UNMAP_USER_OFFSET); - - // free - kfree(notifier); - - // Clean up - hash_del(&tmp_buff->entry); - kfree(tmp_buff->hpages); - // kfree(tmp_buff); - } - } - - return 0; -} - -/** - * @brief Pins a user buffer so that it will not be evicted from cache and makes - * it readable to the vfpga by a call to the hypervisor. - * - * @param d vfpga device - * @param start start address - * @param count number of bytes that should be mapped - * @param cpid cpid of the calling process - * @param pid pid of the calling process - * @return int - */ -int guest_get_user_pages(struct vfpga *d, uint64_t start, size_t count, int32_t cpid, pid_t pid) -{ - uint64_t first, last; - uint64_t notifier_addr; - int n_pages; - int ret_val; - int i; - - struct task_struct *curr_task; - struct mm_struct *curr_mm; - struct vm_area_struct *vma_area_init; - int hugepages; - - struct user_pages *user_pg; - struct hypervisor_map_notifier *notifier; - - dbg_info("Getting user pages\n"); - - ret_val = 0; - BUG_ON(!d); - - // get mmu context - curr_task = pid_task(find_vpid(pid), PIDTYPE_PID); - if (!curr_task) - { - dbg_info("failed to get pid\n"); - return -1; - } - dbg_info("pid found %d", pid); - curr_mm = curr_task->mm; - - // Determine if hugepages - vma_area_init = find_vma(curr_mm, start); - hugepages = is_vm_hugetlb_page(vma_area_init); - - // number of pages - first = (start & PAGE_MASK) >> PAGE_SHIFT; - last = ((start + count - 1) & PAGE_MASK) >> PAGE_SHIFT; - n_pages = last - first + 1; - - if (start + count < start) - return -EINVAL; - if (count == 0) - return 0; - - // alloc user_pages - user_pg = kzalloc(sizeof(struct user_pages), GFP_KERNEL); - if (!user_pg) - { - pr_info("Failed to allocate memory\n"); - ret_val = -ENOMEM; - goto err_user_pg; - } - - // alloc hardware pages - user_pg->hpages = kcalloc(n_pages, sizeof(*user_pg->hpages), GFP_KERNEL); - if (!user_pg->hpages) - { - pr_info("Failed to allocate memory\n"); - ret_val = -ENOMEM; - goto err_hpages; - } - - dbg_info("allocated buffer for %d pages, passed size %ld.\n", n_pages, count); - dbg_info("pages=0x%p\n", user_pg->hpages); - dbg_info("first = %llx, last = %llx\n", first, last); - dbg_info("hugepages: %d\n", hugepages); - - // Pin the pages -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) - ret_val = get_user_pages_remote(curr_mm, (unsigned long)start, n_pages, 1, user_pg->hpages, NULL, NULL); -#else - ret_val = get_user_pages_remote(curr_task, curr_mm, (unsigned long)start, n_pages, 1, user_pg->hpages, NULL); -#endif - - // It was not possible to pin all pages - if (ret_val < n_pages) - { - pr_info("could not get all user pages, %d\n", ret_val); - goto fail_host_unmap; - } - - // flush data cache for those pages - for (i = 0; i < n_pages; i++) - flush_dcache_page(user_pg->hpages[i]); - - // populte user pages struct - user_pg->vaddr = start; - user_pg->n_pages = n_pages; - user_pg->cpid = cpid; - - // Prepare map notifier - notifier = kzalloc(sizeof(struct hypervisor_map_notifier) + sizeof(uint64_t) * n_pages, GFP_KERNEL); - if (!notifier) - { - pr_info("Could not allocate notifier\n"); - goto err_notifier; - } - - // populate notifier - notifier->gva = start; - notifier->len = count; - notifier->cpid = cpid; - notifier->npages = n_pages; - notifier->is_huge = hugepages; - - /* - Do the first step of the page table walk inside the vm - and pass the guest physical addresses to the hypervisor. - */ - for (i = 0; i < n_pages; i++) - { - notifier->gpas[i] = page_to_phys(user_pg->hpages[i]); - // dbg_info("Notifier gpa %d is set to %llx\n", i, notifier->gpas[i]); - } - - dbg_info("populated notifier with %lld pages\n", notifier->npages); - - /* - Pass physical address to the hypervisor - */ - notifier_addr = virt_to_phys(notifier); - writeq(notifier_addr, d->pci_resources.bar2 + MAP_USER_OFFSET); - - dbg_info("Notified hypervisor with notifier at addr 0x%llx\n", notifier_addr); - - kfree(notifier); - - // Add to the hash table - hash_add(user_sbuff_map, &user_pg->entry, start); - - return 0; - -err_notifier: - for (i = 0; i < n_pages; i++) - { - put_page(user_pg->hpages[i]); - } - kfree(user_pg->hpages); -err_hpages: - kfree(user_pg); -err_user_pg: - return ret_val; -fail_host_unmap: - for (i = 0; i < ret_val; i++) - { - put_page(user_pg->hpages[i]); - } - kfree(user_pg->hpages); - kfree(user_pg); - return -ENOMEM; -} \ No newline at end of file diff --git a/driver-guest/guest_mm.h b/driver-guest/guest_mm.h deleted file mode 100644 index e31f85f7e..000000000 --- a/driver-guest/guest_mm.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __GUEST_MM_H__ -#define __GUEST_MM_H__ - -#include "guest_dev.h" - -int guest_put_all_user_pages(struct vfpga *d, int dirtied); -int guest_get_user_pages(struct vfpga *d, uint64_t start, size_t count, int32_t cpid, pid_t pid); -int guest_put_user_pages(struct vfpga *d, uint64_t vaddr, int32_t cpid, int dirtied); - -#endif \ No newline at end of file diff --git a/driver-guest/guest_pci.c b/driver-guest/guest_pci.c deleted file mode 100644 index 919ddd9f5..000000000 --- a/driver-guest/guest_pci.c +++ /dev/null @@ -1,226 +0,0 @@ -#include "guest_pci.h" - -dev_t devt; -struct vfpga vfpga; - -/** - * @brief Sets-up the msix interrupts such that the hypervisor can interrupt - * in a case of a page fault on the system. - * - * @param pdev - * @return int - */ -static int register_msix(struct pci_dev *pdev) -{ - int nvecs; - int i; - int ret_val; - - nvecs = NUM_USER_INTERRUPTS; - ret_val = 0; - - for (i = 0; i < nvecs; i++) - { - vfpga.irq_entry[i].entry = i; - } - - /* Enable as many interrupts as possible */ - ret_val = pci_enable_msix_range(pdev, vfpga.irq_entry, 0, nvecs); - if (ret_val < 0) - { - dbg_info("Failed to allocate all msix vectors"); - return ret_val; - } - - /* For now, the guest only needs one interrupt, for the page fault - this will be extended eventaully to support user interrupts*/ - - ret_val = request_irq(vfpga.irq_entry[0].vector, - guest_fpga_tlb_miss_isr, 0, DRV_NAME, &vfpga); - - if (ret_val) - { - dbg_info("could not register irq!\n"); - return ret_val; - } - - return 0; -} - -/** - * @brief Load configuration for the fpga from the hypervisor - * This is done by reading from the READ_CNFG offset. - * - * @param d vfpga struct - */ -static void load_fpga_config(struct vfpga *d) -{ - uint64_t config; - // load from bar2 offset - - config = readq((volatile __iomem void *)d->pci_resources.bar2 + READ_CNFG_OFFSET); - - // set avx enabled - d->en_avx = config & 0x01; -} - -/** - * @brief Called upon the bus picks up the emulated - * pci device. Creates chardevs, claims resources - * and maps the BARs. - * - * @param pdev - * @param id - * @return int - */ -int guest_probe(struct pci_dev *pdev, const struct pci_device_id *id) -{ - int ret_val; - - ret_val = 0; - vfpga.pdev = pdev; - dbg_info("probed driver\n"); - - // Alloc char region - ret_val = alloc_chrdev_region(&devt, 0, 1, "coyote-guest"); - if (ret_val) - { - dbg_info("failed to allocate chrdev region\n"); - goto err; - } - - /* Init the chardev */ - cdev_init(&vfpga.cdev, &fops); - ret_val = cdev_add(&vfpga.cdev, devt, 1); - if (ret_val) - { - dbg_info("Failed to add chrdev\n"); - goto err_cdev_add; - } - - /* Enable the pci device */ - ret_val = pci_enable_device(pdev); - if (ret_val) - { - dbg_info("Failed to enable pci dev\n"); - goto err_pci_enable; - } - - /* Allocate all regions of the pci device */ - ret_val = pci_request_regions(pdev, "coyote-guest-drv"); - if (ret_val) - { - dbg_info("failed to request regions"); - goto err_pci_request_regions; - } - - // This device is allowed to master the bus - pci_set_master(pdev); - - dbg_info("Successfully requested resources\n"); - - /* Requesting all bars and store information about the bars*/ - vfpga.pci_resources.bar0 = pci_iomap(pdev, 0, pci_resource_len(pdev, 0)); - vfpga.pci_resources.bar2 = pci_iomap(pdev, 2, pci_resource_len(pdev, 2)); - vfpga.pci_resources.bar4 = pci_iomap(pdev, 4, pci_resource_len(pdev, 4)); - - vfpga.pci_resources.bar0_start = pci_resource_start(pdev, 0); - vfpga.pci_resources.bar0_end = pci_resource_end(pdev, 0); - - vfpga.pci_resources.bar2_start = pci_resource_start(pdev, 2); - vfpga.pci_resources.bar2_end = pci_resource_end(pdev, 2); - - vfpga.pci_resources.bar4_start = pci_resource_start(pdev, 4); - vfpga.pci_resources.bar4_end = pci_resource_end(pdev, 4); - - dbg_info("Loaded BAR 0, 2 and 4 into the driver with sizes\nBAR0: %llx\nBAR2: %llx\nBAR4: %llx\n", - pci_resource_len(pdev, 0), - pci_resource_len(pdev, 2), - pci_resource_len(pdev, 4)); - - /* CTRL is BAR0 and AVX-CTRL is BAR4 */ - vfpga.fpga_phys_addr_ctrl = vfpga.pci_resources.bar0_start; - vfpga.fpga_phys_addr_ctrl_avx = vfpga.pci_resources.bar4_start; - - /* Map the CTRL registers to allow the access from the kernel */ - vfpga.fpga_cnfg = ioremap(vfpga.fpga_phys_addr_ctrl + FPGA_CTRL_CNFG_OFFS, FPGA_CTRL_CNFG_SIZE); - vfpga.fpga_cnfg_avx = ioremap(vfpga.fpga_phys_addr_ctrl_avx, FPGA_CTRL_CNFG_AVX_SIZE); - - /* Enable MSIX interrupts */ - ret_val = register_msix(pdev); - if (ret_val) - { - dbg_info("Failed to register interrupts\n"); - goto err_interrupt; - } - - /* Load config from hypervisor */ - load_fpga_config(&vfpga); - - // Init pid array - vfpga.pid_array = vcalloc(N_CPID_MAX, sizeof(pid_t)); - - // Init locks - spin_lock_init(&vfpga.cpid_lock); - spin_lock_init(&vfpga.lock); - - /* Create kernel device */ - vfpga.dev = device_create(guest_class, NULL, devt, NULL, "fpga0"); - if (IS_ERR_OR_NULL(vfpga.dev)) - { - dbg_info("Failed to create device\n"); - ret_val = -1; - goto err_device_create; - } - - dbg_info("created coyote device\n"); - - return 0; - -err_device_create: - pci_free_irq_vectors(pdev); - pci_disable_msix(pdev); -err_interrupt: - pci_iounmap(pdev, (void __iomem *)vfpga.pci_resources.bar0); - pci_iounmap(pdev, (void __iomem *)vfpga.pci_resources.bar2); - pci_iounmap(pdev, (void __iomem *)vfpga.pci_resources.bar4); - vfpga.pci_resources.bar0 = NULL; - vfpga.pci_resources.bar2 = NULL; - vfpga.pci_resources.bar4 = NULL; - pci_release_regions(pdev); -err_pci_request_regions: - pci_disable_device(pdev); -err_pci_enable: - cdev_del(&vfpga.cdev); -err_cdev_add: - unregister_chrdev_region(devt, 1); -err: - return ret_val; -} - -/** - * @brief Called on deletion of the device. - * Frees resources and unmaps the bar regions. - * - * @param pdev - */ -void guest_remove(struct pci_dev *pdev) -{ - // Destory device - device_destroy(guest_class, devt); - - // Unmap pci regions - pci_iounmap(pdev, (void __iomem *)vfpga.pci_resources.bar0); - pci_iounmap(pdev, (void __iomem *)vfpga.pci_resources.bar2); - pci_iounmap(pdev, (void __iomem *)vfpga.pci_resources.bar4); - - /* Release regions */ - pci_release_regions(pdev); - - // Disable device - free_irq(vfpga.irq_entry[0].vector, &vfpga); - pci_disable_msix(pdev); - pci_disable_device(pdev); - cdev_del(&vfpga.cdev); - unregister_chrdev_region(devt, 1); -} \ No newline at end of file diff --git a/driver-guest/guest_pci.h b/driver-guest/guest_pci.h deleted file mode 100644 index 35636dd82..000000000 --- a/driver-guest/guest_pci.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __GUEST_PCI_H__ -#define __GUEST_PCI_H__ - -#include "guest_dev.h" -#include "guest_irq.h" - -int guest_probe(struct pci_dev *pdev, const struct pci_device_id *id); -void guest_remove(struct pci_dev *pdev); - -#endif \ No newline at end of file diff --git a/driver-guest/setup.sh b/driver-guest/setup.sh deleted file mode 100755 index 1a511587a..000000000 --- a/driver-guest/setup.sh +++ /dev/null @@ -1,5 +0,0 @@ -cd guest/guest-driver -make -sudo insmod coyote-guest.ko -echo 100 | sudo tee /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages -cd diff --git a/driver-guest/test/Makefile b/driver-guest/test/Makefile deleted file mode 100644 index a357e9ed5..000000000 --- a/driver-guest/test/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -.PHONY = run clean - -SRCS := $(wildcard *.c) -OBJS := $(SRCS:%.c=%.o) - -LINKERFLAGS = -lm - -%.o: %.c - $(CC) -c $< - -%: %.o - $(CC) $< -o $@ - -run: test - ./test - -clean: - rm -rvf *.o test test2 diff --git a/driver-guest/test/bench_huge.c b/driver-guest/test/bench_huge.c deleted file mode 100644 index 362ee37ba..000000000 --- a/driver-guest/test/bench_huge.c +++ /dev/null @@ -1,165 +0,0 @@ -#include "test.h" -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -static const char *filename = "/dev/coyote-dev-0"; -static const int remote_pages = 16; -static const size_t native_size = remote_pages*(1 << 21); -static const uint64_t iterations = 10; - -void test_native_lat(int fd, int cpid, uint64_t iterations) -{ - if (iterations * 8 >= native_size) - { - printf("Aborted %s, too many iterations for chosen pages\n", __func__); - return; - } - - int ret_val = 0; - uint64_t tmp[64]; - size_t len = native_size; - volatile uint64_t *buffer = malloc(len); - uint64_t *data = calloc(1, len); - - tmp[0] = (uint64_t)buffer; - tmp[1] = len; - tmp[2] = cpid; - - ret_val = ioctl(fd, IOCTL_MAP_USER, tmp); - if (ret_val) - { - printf("Failed to map user buffer: %d\n", ret_val); - return; - } - - printf("--- NATIVE BENCHMARKS ---\n"); - uint64_t start = _rdtsc(); - for (uint64_t i = 0; i < iterations; i++) - { - memcpy((void *) buffer, data, len); - } - uint64_t end = _rdtsc(); - printf("Average write latency of %lu runs: %lu\n", iterations, (end - start) * 8 / (iterations * len)); - - start = _rdtsc(); - for (uint64_t i = 0; i < iterations; i++) - { - memcpy(data, (void *) buffer, len); - } - end = _rdtsc(); - printf("Average read latency of %lu runs: %lu\n", iterations, (end - start) * 8 / (iterations * len)); - - tmp[0] = (uint64_t)buffer; - tmp[1] = cpid; - ret_val = ioctl(fd, IOCTL_UNMAP_USER, tmp); - if (ret_val) - { - printf("Failed to unmap buffer!\n"); - } - - free((void *) buffer); - free((void *) data); -} - -void test_legacy_lat(int fd, int cpid, uint64_t iterations) -{ - uint64_t tmp[64]; - int ret_val; - - if (iterations * 8 >= remote_pages * LTLB_PAGE_SIZE) - { - printf("Aborted %s, too many iterations for chosen pages\n", __func__); - } - - tmp[0] = remote_pages; - tmp[1] = cpid; - - ret_val = ioctl(fd, IOCTL_ALLOC_HOST_USER_MEM, tmp); - if (ret_val) - { - printf("failed to allocate memory\n"); - return; - } - - uint64_t *buff = mmap(NULL, (remote_pages + 1) * LTLB_PAGE_SIZE, PROT_READ | PROT_WRITE, - MAP_SHARED, fd, MMAP_BUFF << 12); - if (buff == (void *)(-1)) - { - printf("failed to map memory\n"); - return; - } - void *buff_old = buff; - buff = (uint64_t *)((((uint64_t)buff + LTLB_PAGE_SIZE - 1) >> LTLB_PAGE_SHIFT) << LTLB_PAGE_SHIFT); - - printf("--- LEGACY BENCHMARKS ---\n"); - - uint64_t start = _rdtsc(); - for (uint64_t i = 0; i < iterations; i++) - { - *(buff + i) = i; - } - uint64_t end = _rdtsc(); - printf("Average write latency of %lu runs: %lu\n", iterations, (end - start) / iterations); - - start = _rdtsc(); - for (uint64_t i = 0; i < iterations; i++) - { - uint64_t val = *(buff + i); - } - end = _rdtsc(); - printf("Average read latency of %lu runs: %lu\n", iterations, (end - start) / iterations); - - munmap(buff_old, (remote_pages + 1) * LTLB_PAGE_SIZE); - - tmp[0] = (uint64_t) buff; - tmp[1] = cpid; - ret_val = ioctl(fd, IOCTL_FREE_HOST_USER_MEM, tmp); - if (ret_val) - { - printf("Failed to free buffer\n"); - } - - return; -} - -int main() -{ - int fd = open(filename, O_RDWR); - uint64_t tmp[64]; - int ret_val = 0; - - if (fd < 0) - { - printf("Failed to open device file\n"); - return -1; - } - - pid_t pid = getpid(); - tmp[0] = pid; - ret_val = ioctl(fd, IOCTL_REGISTER_PID, tmp); - if (ret_val) - { - printf("Failed to register pid\n"); - goto err; - } - - int32_t cpid = (int32_t)tmp[1]; - printf("registered with cpid %d, running benchmarks...\n", cpid); - - test_native_lat(fd, cpid, iterations); - test_legacy_lat(fd, cpid, iterations); - - tmp[0] = cpid; - ret_val = ioctl(fd, IOCTL_UNREGISTER_PID, tmp); -err: - close(fd); - return ret_val; -} \ No newline at end of file diff --git a/driver-guest/test/test b/driver-guest/test/test deleted file mode 100644 index c538a522dba3660ca94501211df75f10ec71ed41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16344 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 K0*`@*75@Uc3dr^V diff --git a/driver-guest/test/test.c b/driver-guest/test/test.c deleted file mode 100644 index da039524c..000000000 --- a/driver-guest/test/test.c +++ /dev/null @@ -1,178 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "test.h" - -static const char *filename = "/dev/fpga0"; - -int main() -{ - int ret_val = 0; - printf("Opening device %s\n", filename); - uint64_t tmp[64]; - - int fd = open(filename, O_RDWR); - if (fd < 0) - { - printf("Failed to open device file\n"); - return -1; - } - - pid_t pid = getpid(); - tmp[0] = pid; - ret_val = ioctl(fd, IOCTL_REGISTER_PID, tmp); - if (ret_val) - { - printf("Failed to register pid\n"); - goto err; - } - - int32_t cpid = (int32_t) tmp[1]; - - printf("Got cpid %d\n", cpid); - - printf("Trying to map regions\n"); - - // Map ctrl - void * user_ctrl = mmap(NULL, FPGA_CTRL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, MMAP_CTRL << 12); - if (!user_ctrl) - { - printf("Failed to map ctrl region\n"); - } - else - { - printf("Mapped ctrl region\n"); - *((uint64_t *) user_ctrl) = 0; - printf("Wrote test to user ctrl\n"); - ret_val = munmap(user_ctrl, FPGA_CTRL_SIZE); - if (ret_val) - { - printf("Failed to unmap with code %d\n", ret_val); - } - else - { - printf("Unmapped ctrl region\n"); - } - } - - // map cnfg - void * user_cnfg = mmap(NULL, FPGA_CTRL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, MMAP_CNFG << 12); - if (!user_cnfg) - { - printf("Failed to map ctrl region\n"); - } - else - { - printf("Mapped user cnfg\n"); - *((uint64_t *) user_cnfg) = 0; - printf("Wrote test to user cnfg\n"); - ret_val = munmap(user_cnfg, FPGA_CTRL_SIZE); - if (ret_val) - { - printf("Failed to unmap with code %d\n", ret_val); - } - else - { - printf("Unmapped user confg\n"); - } - } - - // map avx cnfg - void * user_cnfg_avx = mmap(NULL, FPGA_CTRL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, MMAP_CNFG_AVX << 12); - if (!user_cnfg_avx) - { - printf("Failed to map ctrl region\n"); - } - else - { - printf("Mapped cnfg avx\n"); - *((__m256i *) user_cnfg_avx) = _mm256_set_epi64x(0,0,0,0); - printf("Wrote test to cnfg avx\n"); - ret_val = munmap(user_cnfg_avx, FPGA_CTRL_SIZE); - if (ret_val) - { - printf("Failed to unmap with code %d\n", ret_val); - } - else - { - printf("Unmapped user cnfg\n"); - } - } - - // Test get config - ret_val = ioctl(fd, IOCTL_READ_CNFG, tmp); - if (ret_val) - { - printf("Failed to read config!\n"); - } - else - { - printf("Config is 0x%lx\n", tmp[0]); - if (tmp[0] != 0xdeadbeef) - { - printf("WARNING: Config value differs from test value. Ignore this if running on actual hardware\n"); - } - } - - // TODO: extend this to more test cases - // map 4 pages - size_t len = 1*(1 << 12); - // void *buffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); - void *buffer = malloc(len); - if (!buffer) - { - printf("Failed to allocate buffer\n"); - goto err_cpid; - } - tmp[0] = (uint64_t) buffer; - tmp[1] = len; - tmp[2] = cpid; - - ret_val = ioctl(fd, IOCTL_MAP_USER, tmp); - if (ret_val) - { - printf("Failed to allocate with code %d\n", ret_val); - goto err_map; - } - - printf("Mapped user buffer for fpga use\n"); - - tmp[0] = (uint64_t) buffer; - tmp[1] = cpid; - ret_val = ioctl(fd, IOCTL_UNMAP_USER, tmp); - if (ret_val) - { - printf("Failed to unmap with code %d\n", ret_val); - } - else - { - printf("Unmapped user buffer successfully\n"); - } - - - -err_map: - munmap(buffer, len); -err_cpid: - tmp[0] = cpid; - ret_val = ioctl(fd, IOCTL_UNREGISTER_PID, tmp); - if (ret_val) - { - printf("Failed to unreguster cpid\n"); - goto err; - } - - printf("Unregistered cpid %d\n", cpid); - -err: - printf("Closing device file %d\n", fd); - close(fd); - return ret_val; -} \ No newline at end of file diff --git a/driver-guest/test/test.h b/driver-guest/test/test.h deleted file mode 100644 index 278665e27..000000000 --- a/driver-guest/test/test.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef __TEST_H__ - -/* MMAP */ -/* MMAP Regions */ -#define MMAP_CTRL 0x0 -#define MMAP_CNFG 0x1 -#define MMAP_CNFG_AVX 0x2 -#define MMAP_WB 0x3 -#define MMAP_BUFF 0x200 -#define MMAP_PR 0x400 -#define FPGA_CTRL_SIZE 256 * 1024 -#define FPGA_CTRL_OFFS 0x100000 -#define FPGA_CTRL_LTLB_SIZE FPGA_CTRL_SIZE / 4 -#define FPGA_CTRL_LTLB_OFFS 0x0 -#define FPGA_CTRL_STLB_SIZE FPGA_CTRL_SIZE / 4 -#define FPGA_CTRL_STLB_OFFS 0x10000 -#define FPGA_CTRL_USER_SIZE FPGA_CTRL_SIZE / 4 -#define FPGA_CTRL_USER_OFFS 0x20000 -#define FPGA_CTRL_CNFG_SIZE FPGA_CTRL_SIZE / 4 -#define FPGA_CTRL_CNFG_OFFS 0x30000 -#define FPGA_CTRL_CNFG_AVX_SIZE 256 * 1024 -#define FPGA_CTRL_CNFG_AVX_OFFS 0x1000000 - -/* IOCTL */ -#define IOCTL_ALLOC_HOST_USER_MEM _IOW('D', 1, unsigned long) // large pages (no hugepage support) -#define IOCTL_FREE_HOST_USER_MEM _IOW('D', 2, unsigned long) -#define IOCTL_ALLOC_HOST_RECONFIG_MEM _IOW('D', 3, unsigned long) // pr pages -#define IOCTL_FREE_HOST_RECONFIG_MEM _IOW('D', 4, unsigned long) -#define IOCTL_MAP_USER _IOW('D', 5, unsigned long) // map -#define IOCTL_UNMAP_USER _IOW('D', 6, unsigned long) -#define IOCTL_REGISTER_PID _IOW('D', 7, unsigned long) // register pid -#define IOCTL_UNREGISTER_PID _IOW('D', 8, unsigned long) -#define IOCTL_RECONFIG_LOAD _IOW('D', 9, unsigned long) // reconfiguration - -#define IOCTL_ARP_LOOKUP _IOW('D', 10, unsigned long) // arp lookup -#define IOCTL_SET_IP_ADDRESS _IOW('D', 11, unsigned long) -#define IOCTL_SET_MAC_ADDRESS _IOW('D', 12, unsigned long) -#define IOCTL_WRITE_CTX _IOW('D', 13, unsigned long) // qp context -#define IOCTL_WRITE_CONN _IOW('D', 14, unsigned long) // qp connection -#define IOCTL_SET_TCP_OFFS _IOW('D', 15, unsigned long) // tcp mem offsets - -#define IOCTL_READ_CNFG _IOR('D', 32, unsigned long) // status cnfg -#define IOCTL_XDMA_STATS _IOR('D', 33, unsigned long) // status xdma -#define IOCTL_NET_STATS _IOR('D', 34, unsigned long) // status network -#define IOCTL_READ_ENG_STATUS _IOR('D', 35, unsigned long) // status engines - -#define IOCTL_NET_DROP _IOW('D', 36, unsigned long) // net dropper - -#define IOCTL_TEST_INTERRUPT _IO('D', 37) - -#define LTLB_PAGE_SHIFT 21 -#define LTLB_PAGE_SIZE (1 << 21) - -#endif \ No newline at end of file diff --git a/driver-guest/test/test2.c b/driver-guest/test/test2.c deleted file mode 100644 index 9d612576d..000000000 --- a/driver-guest/test/test2.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "test.h" - -#include -#include -#include -#include -#include - -#include -#include - -static const char *filename = "/dev/fpga0"; - -int main() -{ - uint64_t tmp[64]; - int ret_val = 0; - int fd = open(filename, O_RDWR); - if (fd < 0) - { - printf("Failed to open file\n"); - return -1; - } - - pid_t pid = getpid(); - tmp[0] = pid; - ret_val = ioctl(fd, IOCTL_REGISTER_PID, tmp); - if (ret_val) - { - printf("Failed to register pid with code %d\n", ret_val); - goto err; - } - - int cpid = tmp[1]; - tmp[0] = 1; - tmp[1] = cpid; - ret_val = ioctl(fd, IOCTL_ALLOC_HOST_USER_MEM, tmp); - if (ret_val) - { - printf("failed to allocate memory with code %d\n", ret_val); - goto err; - } - uint64_t *m = mmap(NULL, (tmp[0] + 1) * (1 << 21), PROT_READ | PROT_WRITE, MAP_SHARED, fd, MMAP_BUFF << 12); - if (m == (void *)(-1)) - { - printf("Failed ot mmap\n"); - goto err; - } - void *m_old = m; - m = (uint64_t *)((((uint64_t)m + LTLB_PAGE_SIZE - 1) >> LTLB_PAGE_SHIFT) << LTLB_PAGE_SHIFT); - - printf("Mapped region @%p of size %lu\n", m, (uint64_t)2 * (1 << 21)); - - // test if read and writes work as expected - for (int i = 0; i < 1000; i++) - { - // printf("Iteration %d\n", i); - *(m + i) = i; - if (*(m + i) != i) - { - printf("ERROR: read does not return written data!\n"); - } - } - - printf("Done with test\n"); - - munmap(m_old, (tmp[0] + 1) * (1 << 21)); - -err: - close(fd); - return ret_val; -} \ No newline at end of file diff --git a/driver-guest/test/test_inter.c b/driver-guest/test/test_inter.c deleted file mode 100644 index 2c4288ede..000000000 --- a/driver-guest/test/test_inter.c +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include - -#include "test.h" - -static const char *filename = "/dev/fpga0"; - -int main() -{ - int ret_val = 0; - printf("Opening device %s\n", filename); - uint64_t tmp[64]; - - int fd = open(filename, O_RDWR); - if (fd < 0) - { - printf("Failed to open device file\n"); - return -1; - } - - pid_t pid = getpid(); - tmp[0] = pid; - ret_val = ioctl(fd, IOCTL_REGISTER_PID, tmp); - if (ret_val) - { - printf("Failed to register pid\n"); - goto err; - } - - int32_t cpid = (int32_t)tmp[1]; - - printf("Got cpid %d\n", cpid); - - ret_val = ioctl(fd, IOCTL_TEST_INTERRUPT); - if (ret_val) - { - printf("Failed to trigger interrupt\n"); - } - - tmp[0] = cpid; - ret_val = ioctl(fd, IOCTL_UNREGISTER_PID, tmp); - if (ret_val) - { - printf("Failed to unreguster cpid\n"); - goto err; - } - - printf("Unregistered cpid %d\n", cpid); - -err: - return ret_val; -} \ No newline at end of file diff --git a/driver/hypervisor/hypervisor.h b/driver/hypervisor/hypervisor.h deleted file mode 100644 index 777a8d5b1..000000000 --- a/driver/hypervisor/hypervisor.h +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright (c) 2023, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#ifndef __HYPERVISOR_H__ -#define __HYPERVISOR_H__ - -#include "../coyote_dev.h" -#include - -// #define HYPERVISOR_TEST - -#define NUM_INTERRUPTS 1 -#define MAX_VMS 16 - -#define COYOTE_HYPERVISOR_CONFIG_SIZE 0x100 -#define COYOTE_HYPERVISOR_BAR0_SIZE ((uint64_t)FPGA_CTRL_SIZE) -#define COYOTE_HYPERVISOR_BAR2_SIZE ((uint64_t)0x4000) -#define COYOTE_HYPERVISOR_BAR4_SIZE ((uint64_t)FPGA_CTRL_CNFG_AVX_SIZE) -#define COYOTE_HYPERVISOR_BAR0_MASK ~(COYOTE_HYPERVISOR_BAR0_SIZE - 1) -#define COYOTE_HYPERVISOR_BAR2_MASK ~(COYOTE_HYPERVISOR_BAR2_SIZE - 1) -#define COYOTE_HYPERVISOR_BAR4_MASK ~(COYOTE_HYPERVISOR_BAR4_SIZE - 1) -#define COYOTE_HYPERVISOR_PCI_MASK(__size) (~(__size - 1)) - -#define BAR0_INDEX_SHIFT 16 - -#define COYOTE_REGION_OFFSET 32 -#define COYOTE_GET_INDEX(__addr) (__addr >> COYOTE_REGION_OFFSET) -#define COYOTE_INDEX_TO_ADDR(__index) (__index << COYOTE_REGION_OFFSET); -#define HYPERVISOR_OFFSET_MASK (((uint64_t)1 << COYOTE_REGION_OFFSET) - 1) - -/* Offsets into BAR2 */ -#define REGISTER_PID_OFFSET 0x00 -#define UNREGISTER_PID_OFFSET 0x08 -#define MAP_USER_OFFSET 0x10 -#define UNMAP_USER_OFFSET 0x18 -#define READ_CNFG_OFFSET 0x20 -#define PUT_ALL_USER_PAGES 0x28 -#define TEST_INTERRUPT_OFFSET 0x30 - -#define INVALID_CPID 0xffffffffffffffff - -#define HYPERVISOR_HASH_TABLE_ORDER 8 -#define HASH_TABLE_BUCKETS (1 << HYPERVISOR_HASH_TABLE_ORDER) - -#define MAX_USER_BUF_SHIFT 27 -#define MAX_USER_BUF_SIZE (1 << MAX_USER_BUF_SHIFT) - -#define LARGE_TABLE_SHIFT 21 -#define LARGE_TABLE_SIZE (1 << 21) - -#define MSIX_OFFSET 0x40 -#define MSIX_SIZE sizeof(struct msix_cap_header) - -/* PCI-Capability header */ -struct cap_header -{ - uint8_t cap_id; - uint8_t next_pointer; -} __packed; - -/* MSI-X header*/ -struct msix_cap_header -{ - uint8_t cap_id; - uint8_t next_pointer; - uint16_t message_control; - uint32_t table_offset; - uint32_t pba_offset; - uint32_t mua; -} __packed; - -// struct to manage information about a single entry of an interrupt vector -struct msix_interrupt -{ - int eventfd; - struct eventfd_ctx * ctx; - int masked; -}; - -/* PCI config header */ -struct pci_config_space -{ - uint16_t vendor_id; // 0x00 RO - uint16_t device_id; // 0x02 RO - uint16_t command; // 0x04 - uint16_t status; // 0x06 - uint8_t revison_id; // 0x08 RO - uint8_t programming_interface; // 0x09 - uint8_t subclass; // 0x0a - uint8_t class_code; // 0x0b RO - uint8_t cache_line_size; // 0x0c - uint8_t lat_time; // 0x0d - uint8_t header_typer; // 0x0e RO - uint8_t bist; // 0x0f - uint32_t bar0; // 0x10 - uint32_t bar1; // 0x14 - uint32_t bar2; // 0x18 - uint32_t bar3; // 0x1c - uint32_t bar4; // 0x20 - uint32_t bar5; // 0x24 - uint32_t cardbus_pointer; // 0x28 - uint16_t subsys_vendor_id; // 0x2c - uint16_t subsys_id; // 0x2e - uint32_t expansion_rom_base; // 0x30 - uint8_t cap_pointer; // 0x34 - uint8_t reserved[7]; // 0x35 - uint8_t interrupt_line; // 0x3c - uint8_t interrupt_pin; // 0x3d - uint8_t min_gnt; // 0x3e - uint8_t max_lat; // 0x3f - uint8_t cap_section[0xbf]; // 0x40 -} __packed; - -struct hypervisor_map_notifier -{ - uint64_t npages; - uint64_t len; - uint64_t gva; - uint64_t cpid; - uint64_t dirtied; - uint64_t is_huge; - uint64_t gpas[0]; -}; - -/* Mediated vFPGA management data */ -struct m_fpga_dev -{ - struct fpga_dev *fpga; // Parent fpga struct - uint32_t id; - - struct list_head next; // next virtual fpga - struct pci_config_space pci_config; - spinlock_t current_cpid_lock; - uint64_t current_cpid; - struct kvm *kvm; - uint32_t in_use; - struct notifier_block notifier; - - void *msix_table; - struct msix_interrupt *msix_vector; - - struct hlist_head sbuff_map[HASH_TABLE_BUCKETS]; - - spinlock_t lock; -}; - -#endif diff --git a/driver/hypervisor/hypervisor_drv.c b/driver/hypervisor/hypervisor_drv.c deleted file mode 100644 index 6e2ec2875..000000000 --- a/driver/hypervisor/hypervisor_drv.c +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) 2023, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#include "hypervisor_drv.h" - -/** - * @brief Initalizes the hypervisor and creates - * the mdev types inside the device tree. - * - * @param bd bus_drvdata, initiliazed prior to this call - * @return int 0 if succeeded - */ -int hypervisor_init(struct bus_drvdata *bd) { - struct mdev_parent_ops *ops; - int ret_val; - int i, j; - - BUG_ON(!bd); - - for (i = 0; i < bd->n_fpga_reg; i++) { - - // Get ops for the device - ops = hypervisor_get_ops(&bd->fpga_dev[i]); - if (!ops) { - pr_err ("Failed to get mdev-vfio ops\n"); - goto rollback; - } - - // Allocate vcid chunks - bd->fpga_dev[i].vcid_chunks = vzalloc(sizeof(struct chunk) * MAX_VMS); - BUG_ON(!bd->fpga_dev->vcid_chunks); - - for (j = 1; j < MAX_VMS; j++) - { - bd->fpga_dev[i].vcid_chunks[j].id = j; - bd->fpga_dev[i].vcid_chunks[j-1].next = &bd->fpga_dev->vcid_chunks[j]; - } - - bd->fpga_dev[i].vcid_alloc = bd->fpga_dev->vcid_chunks; - bd->fpga_dev[i].num_free_vcid_chunks = MAX_VMS; - - spin_lock_init(&bd->fpga_dev[i].vcid_lock); - - // Register with mdev - ret_val = mdev_register_device(bd->fpga_dev[i].dev, ops); - if (ret_val) { - pr_err ("Error while registering mdev\n"); - goto rollback; - } - - dbg_info("Registered mdev device %d\n", i); - } - - dbg_info("Hypervisor initialized\n"); - - return 0; - -rollback: - for (i = i - 1; i >= 0; i--) { - mdev_unregister_device(bd->fpga_dev[i].dev); - } - return ret_val; -} - -/** - * @brief tear down code for the hypervisor. Unregisters the mdev - * class and therefore removes the types for the fpgas. - * Might block if there are still mediated devices - * opened and used by vms. - * - * @param bd bus_drvdata, initiliazed prior to this call - * @return int 0 if succeeded - */ -void hypervisor_exit(struct bus_drvdata *bd) { - int i; - - for (i = 0; i < bd->n_fpga_reg; i++) - { - mdev_unregister_device(bd->fpga_dev[i].dev); - } - - dbg_info("Unregistered all mdev devices"); - return; -} - -MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/driver/hypervisor/hypervisor_drv.h b/driver/hypervisor/hypervisor_drv.h deleted file mode 100644 index f38a25f85..000000000 --- a/driver/hypervisor/hypervisor_drv.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2023, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#ifndef __HYPERVISOR_INIT_H__ -#define __HYPERVISOR_INIT_H__ - -#include "../coyote_dev.h" -#include "hypervisor_ops.h" - -int hypervisor_init(struct bus_drvdata *bd); -void hypervisor_exit(struct bus_drvdata *bd); - -#endif // hypervisor_init.h \ No newline at end of file diff --git a/driver/hypervisor/hypervisor_interrupts.c b/driver/hypervisor/hypervisor_interrupts.c deleted file mode 100644 index a92ff9223..000000000 --- a/driver/hypervisor/hypervisor_interrupts.c +++ /dev/null @@ -1,331 +0,0 @@ -/** - * Copyright (c) 2023, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#include "hypervisor_interrupts.h" - -/** - * @brief Set the up msix header. - * This function will place the MSIX capapbility in the pci config space - * to communicate to the guest vm that we this emulated device supports - * MSIX interrupts. The next pointer of the capapbility is set to zero - * and has to be set by the caller if there are more capabilities this - * one should point to. - * - * @param cfg Pointer to the pci config space - * @param offset offset from the the start of the config region where the cap should - * be placed. - * @param regs Number of interrupts that should be supported - */ -void set_up_msix_header(struct pci_config_space *cfg, uint8_t offset, uint16_t regs) -{ - struct msix_cap_header *header; - - BUG_ON(regs > 2048); - - header = (struct msix_cap_header *)((uint8_t *)cfg + offset); - - header->cap_id = 0x11; - // explictly set to null, if this is not the last element, this will be handled by the caller - header->next_pointer = 0x00; - - // message control - header->message_control = (regs - 1) & GENMASK(10, 0); - header->message_control |= (0x0 & GENMASK(14, 11)); - header->message_control |= 0 & BIT(15); - - // message upper address - header->mua = 0x0; - - // table offset - // lower 3 bits are for identfying the bar (BAR2) - header->table_offset = 2 & GENMASK(2, 0); - // The table located at an 0x2000 offset of the BAR2 region - header->table_offset |= 0x2000 & GENMASK(31, 3); - - // pba table - header->pba_offset = 2 & GENMASK(2, 0); - header->pba_offset |= 0x3000 & GENMASK(31, 3); -} - -/** - * @brief Emulating writes to the msix header of the - * pci device. This happens according to the - * pci documentation. - * - * @param cfg pointer to the start of the capability - * @param offset into the capability - * @param val val that is to be written - * @param count number of bytes to write - * @return size_t number of bytes actually written (or error code) - */ -size_t write_to_msix_header(void *cfg, uint8_t offset, uint32_t val, uint8_t count) -{ - struct msix_cap_header *header; - - header = (struct msix_cap_header *)cfg; - switch (offset) - { - case 0x2: // message control - { - if (count != 2) - { - return -EFAULT; - } - - header->message_control = val & BIT(15); - return count; - } - case 0x04: - { - if (count != 4) - { - return -EFAULT; - } - - header->mua = val; - return count; - } - default: - { - return -EFAULT; - } - } -} - -/** - * @brief Unsets all handlers for all interrupts. - * Should only be used in an emulated manner since - * this function is only unsetting internal management structs - * and could lead to problems if it does not match the state of - * the vm. - * - * @param d mediated fpga device - */ - -void msix_unset_all_interrupts(struct m_fpga_dev *d) -{ - int i; - for (i = 0; i < NUM_INTERRUPTS; i++) - { - // Check if interrupt was ever registered - if (d->msix_vector[i].eventfd != -1) - { - // Only put the eventfd context if we got it before - if (!IS_ERR_OR_NULL(d->msix_vector[i].ctx)) - { - eventfd_ctx_put(d->msix_vector[i].ctx); - } - - // Invalidate - d->msix_vector[i].eventfd = -1; - } - } -} - -/** - * @brief Handles the VFIO ioctl call. Have a look at the documentation - * of VFIO for more information about the vfio_irq_set struct, it contains - * framework specific fields. - * - * The following cases are supported: - * -+-------------------------------------------------------------------+----------------------------------+ -| flags | action | -+-------------------------------------------------------------------+----------------------------------+ -| VFIO_IRQ_SET_ACTION_TRIGGER & VFIO_IRQ_SET_DATA_NONE & count == 0 | unset all interrupts | -| VFIO_IRQ_SET_ACTION_TRIGGER & VFIO_IRQ_SET_DATA_EVENTFD | set interrupt with given eventfd | -| VFIO_IRQ_SET_ACTION_MASK || VFIO_IRQ_SET_ACTION_UNMASK | Mask interrupt | -+-------------------------------------------------------------------+----------------------------------+ - * this function should only called from the SET_INTERRUPT vfio ioctl. For more information consult the - * VFIO documentation. - * - * @param d mediated fpga device - * @param irq_set irq_set struct passed from userspace from the vfio ioctl - * @return int error code - */ -int handle_set_irq_msix(struct m_fpga_dev *d, struct vfio_irq_set *irq_set) -{ - int i; - int ret_val; - int start, end; - int mask_val; - - ret_val = 0; - - // Disable all interrupts - if ((irq_set->flags & VFIO_IRQ_SET_ACTION_TRIGGER) && (irq_set->flags & VFIO_IRQ_SET_DATA_NONE) && (irq_set->count == 0)) - { - msix_unset_all_interrupts(d); - } - - // Set eventfd - else if ((irq_set->flags & VFIO_IRQ_SET_ACTION_TRIGGER) && (irq_set->flags & VFIO_IRQ_SET_DATA_EVENTFD)) - { - // start and end allows to only specify a range of interrupts to set - start = irq_set->start; - end = start + irq_set->count; - - for (i = start; i < end; i++) - { - // There is already an interrupt, deregister - if (d->msix_vector[i].eventfd != -1 && !IS_ERR_OR_NULL(d->msix_vector[i].ctx)) - { - eventfd_ctx_put(d->msix_vector[i].ctx); - } - - // Register the interrupt and get the eventfd context - d->msix_vector[i].eventfd = irq_set->data[i - start]; - d->msix_vector[i].ctx = eventfd_ctx_fdget(irq_set->data[i - start]); - if (IS_ERR_OR_NULL(d->msix_vector[i].ctx)) - { - dbg_info("Failed to get eventfd ctx, fd: %d, ctx: %p", d->msix_vector[i].eventfd, d->msix_vector[i].ctx); - } - dbg_info("Set interrupt %d to %d, ctx: %p\n", i, irq_set->data[i - start], d->msix_vector[i].ctx); - } - } - - // Mask interrupts - else if ((irq_set->flags & VFIO_IRQ_SET_ACTION_MASK) || (irq_set->flags & VFIO_IRQ_SET_ACTION_UNMASK)) - { - mask_val = (irq_set->flags & VFIO_IRQ_SET_ACTION_MASK) ? 1 : 0; - // start and end allow to mask a range of interrupts - start = irq_set->start; - end = start + irq_set->count; - - // If data none is set, then all interrupts should be set accordingly to the - // mask or unmask flask - if (irq_set->flags & VFIO_IRQ_SET_DATA_NONE) - { - for (i = start; i < end; i++) - { - d->msix_vector[i].masked = mask_val; - dbg_info("Set %d to %s\n", i, (mask_val ? "masked" : "unmasked")); - } - } - // Else, we have a bool array and the interrupts should be masked based on the - // values in this array. - else - { - for (i = start; i < end; i++) - { - if (irq_set->data[i - start]) - { - d->msix_vector[i].masked = mask_val; - dbg_info("Set %d to %s\n", i, (mask_val ? "masked" : "unmasked")); - } - } - } - } - - else - { - dbg_info("Invalid args\n"); - return -EINVAL; - } - - return ret_val; -} - -/** - * @brief forwards an interrupt to the vm. Uses the eventfd context - * stored in the passed argument. This function should only - * be called when currently handling an interrupt! - * - * @param inter msix interrupt storage. - * @return uint64_t eventfd_signal value. Can be discarded for our use. - */ -inline uint64_t fire_interrupt(struct msix_interrupt *inter) -{ - if (IS_ERR_OR_NULL(inter->ctx)) - { - pr_info("Failed ot get context for eventfd %d", inter->eventfd); - return -1; - } - - // dbg_info("Firing interrupt!\n"); - return eventfd_signal(inter->ctx, 0); -} - -/** - * @brief Hypervisor version of the tlb miss interrupt - * service routine. This function uses the same registers from the - * fpga to read the faulting address. Instead of handling the page fault - * it is forwarded to the vm to be handled there. This is because - * the fault address will be a guest virtual address and cannot be - * resolved from the hypervisor. - * - * @param irq interrupt request number - * @param dev_id pointer to device, given then registered the interrupt - * @return irqreturn_t - */ -irqreturn_t hypervisor_tlb_miss_isr(int irq, void *dev_id) -{ - struct m_fpga_dev *md; - unsigned long flags; - int32_t cpid; - struct fpga_dev *d; - struct bus_drvdata *pd; - uint64_t tmp; - - dbg_info("(irq=%d) page fault ISR\n", irq); - BUG_ON(!dev_id); - - d = (struct fpga_dev *)dev_id; - BUG_ON(!d); - - pd = d->pd; - BUG_ON(!pd); - - // lock - spin_lock_irqsave(&(d->lock), flags); - - // read page fault - // tmp cointains the cpid. - if (pd->en_avx) - { - tmp = d->fpga_cnfg_avx->len_miss; - cpid = (int32_t)HIGH_32(tmp); - } - else - { - tmp = d->fpga_cnfg->len_miss; - cpid = (int32_t)HIGH_32(tmp); - } - - // Get the medaited device by looking up the cpid - md = d->vdevs[cpid]; - BUG_ON(!md); - - // Fire interrupt in vm - fire_interrupt(&md->msix_vector[0]); - // dbg_info("Interrupt forwarded to vm!\n"); - - // unlock - spin_unlock_irqrestore(&(d->lock), flags); - - return IRQ_HANDLED; -} \ No newline at end of file diff --git a/driver/hypervisor/hypervisor_interrupts.h b/driver/hypervisor/hypervisor_interrupts.h deleted file mode 100644 index b96f5d28d..000000000 --- a/driver/hypervisor/hypervisor_interrupts.h +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2023, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#ifndef __HYPERVISOR_INTERRUPTS_H__ -#define __HYPERVISOR_INTERRUPTS_H__ - -#include "../coyote_dev.h" - -/* MSIX header set up */ -void set_up_msix_header(struct pci_config_space *cfg, uint8_t offset, uint16_t regs); -size_t write_to_msix_header(void *cfg, uint8_t offset, uint32_t val, uint8_t count); - -/* MSIX management */ -void msix_unset_all_interrupts(struct m_fpga_dev *d); -int handle_set_irq_msix(struct m_fpga_dev *d, struct vfio_irq_set *irq_set); -uint64_t fire_interrupt(struct msix_interrupt *inter); -irqreturn_t hypervisor_tlb_miss_isr(int irq, void *dev_id); - -#endif \ No newline at end of file diff --git a/driver/hypervisor/hypervisor_mmu.c b/driver/hypervisor/hypervisor_mmu.c deleted file mode 100644 index 9e2824c90..000000000 --- a/driver/hypervisor/hypervisor_mmu.c +++ /dev/null @@ -1,546 +0,0 @@ -/** - * Copyright (c) 2023, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#include "hypervisor_mmu.h" - -/** - * @brief Pin user pages allocated in a vm. - * This is a modified version to work on the notifier that is passed - * down from the vm. - * - * The function reads the notifier and pins the pages from the vm. - * The notifier contains gpa addresses. So first the function performs - * a page table walk from guest physical address (gpa) to host virtual address (hva). - * We use the hva to pin the corresponding physical pages. - * - * After this step, this function will install mappings on the fpga TLB that correspond - * to the virtual address given in the notifier. If the function uses the - * huge table TLB or the small table TLB depends on the notifier. This information - * is passed down from the hypervisor. This is because if the vm runs with - * huge pages all pages inside the vm are seen as huge pages by the hypervisor - * so we have to pass through the intent on the user with the hypervisor. - * - * In a last step, we actual fire the mapping to the fpga. - * - * @param d mediated dev - * @param notifier notifier from the vm, already copied into the kernel - * @return int 0 if successfull - */ -int hypervisor_tlb_get_user_pages(struct m_fpga_dev *d, struct hypervisor_map_notifier *notifier) -{ - int ret_val, i, j; - struct fpga_dev *fd; - struct bus_drvdata *pd; - struct mm_struct *curr_mm; - struct task_struct *curr_task; - struct kvm *kvm; - pid_t pid; - uint64_t first, last; - uint64_t curr_vaddr, last_vaddr, vaddr_tmp, gva; - int n_pages, n_pages_huge; - int hugepages; - struct user_pages *user_pg; - uint64_t *hpages_phys, *map_array; - uint64_t count; - uint64_t *kvm_hvas; - - ret_val = 0; - - BUG_ON(!d); - fd = d->fpga; - BUG_ON(!fd); - pd = fd->pd; - BUG_ON(!pd); - kvm = d->kvm; - BUG_ON(!kvm); - BUG_ON(!notifier); - BUG_ON(notifier->npages == 0); - - // number of pages - n_pages = notifier->npages; - gva = notifier->gva; - dbg_info("Going to pin %d pages for gva %llx\n", n_pages, gva); - - // Get mmu context from kvm process - curr_mm = kvm->mm; - pid = kvm->userspace_pid; - curr_task = pid_task(find_vpid(pid), PIDTYPE_PID); - - // get first host virtual address in kvm space - kvm_hvas = kcalloc(notifier->npages, sizeof(uint64_t), GFP_KERNEL); - if (!kvm_hvas) - { - goto err_hvas; - } - - for (i = 0; i < n_pages; i++) - { - kvm_hvas[i] = gfn_to_hva(kvm, gpa_to_gfn(notifier->gpas[i])); - } - count = notifier->len; - - // hugepages support passed from vm - hugepages = (int) notifier->is_huge; - - if (hugepages) - { - if (n_pages > MAX_N_MAP_HUGE_PAGES) - n_pages = MAX_N_MAP_HUGE_PAGES; - } - else - { - if (n_pages > MAX_N_MAP_PAGES) - n_pages = MAX_N_MAP_PAGES; - } - - // overflow check - if (gva + count < gva) - return -EINVAL; - if (count == 0) - return 0; - - // allocate management structs - user_pg = kzalloc(sizeof(struct user_pages), GFP_KERNEL); - if (!user_pg) - { - ret_val = -ENOMEM; - goto err_user_pg; - } - - user_pg->hpages = kcalloc(n_pages, sizeof(struct page *), GFP_KERNEL); - if (!user_pg->hpages) - { - ret_val = -ENOMEM; - goto err_hpages; - } - - // Pin all pages obtained from the vm - for (i = 0; i < n_pages; i++) - { - // pin pages of the kvm -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) - ret_val = get_user_pages_remote(curr_mm, (unsigned long)kvm_hvas[i], 1, 1, user_pg->hpages + i, NULL, NULL); -#else - ret_val = get_user_pages_remote(curr_task, curr_mm, (unsigned long)kvm_hvas[i], n_pages, 1, user_pg->hpages + i, NULL); -#endif - if (ret_val != 1 || !user_pg->hpages[i]) - { - pr_info("%s.%d: Failed to pin all pages, failed to map %d with ret_val %d", __func__, __LINE__, i, ret_val); - goto err_pin_pages; - } - // dbg_info("pinned page hpa: %llx\n", page_to_phys(user_pg->hpages[i])); - } - - dbg_info("Pinned pages\n"); - - // Reset ret_val - ret_val = 0; - - // flush cache - for (i = 0; i < n_pages; i++) - { - flush_dcache_page(user_pg->hpages[i]); - } - - // populate map entry - user_pg->vaddr = gva; - user_pg->n_hpages = n_pages; - user_pg->huge = hugepages; - - dbg_info("mapping vaddr %llx, cpid %llu, hugepages %llu\n", gva, notifier->cpid, notifier->is_huge); - - vaddr_tmp = gva; - - if (hugepages) // For hugepages - { - // Shift page numbers to work on huge pages - first = (gva & pd->ltlb_order->page_mask) >> pd->ltlb_order->page_shift; - last = ((gva + count - 1) & pd->ltlb_order->page_mask) >> pd->ltlb_order->page_shift; - n_pages_huge = last - first + 1; - user_pg->n_pages = n_pages_huge; - - // allocate pages array - hpages_phys = kzalloc(n_pages_huge * sizeof(uint64_t), GFP_KERNEL); - if (!hpages_phys) - { - ret_val = -ENOMEM; - goto err_phys_pages; - } - - j = 0; - curr_vaddr = gva; - last_vaddr = -1; - - // Get the hpa for the huge pages - for (i = 0; i < n_pages; i++) - { - // Only store an entry if we encounter a new huge page - if (((curr_vaddr & pd->ltlb_order->page_mask) >> pd->ltlb_order->page_shift) != - ((last_vaddr & pd->ltlb_order->page_mask) >> pd->ltlb_order->page_shift)) - { - hpages_phys[j] = page_to_phys(user_pg->hpages[i]) & pd->ltlb_order->page_mask; - last_vaddr = curr_vaddr; - j++; - } - curr_vaddr += PAGE_SIZE; - } - - // If we have memory attached on the card we want to allocate - // the same amount of memory on the card - if (pd->en_mem) - { - // Allocate memory - user_pg->cpages = kzalloc(n_pages_huge * sizeof(uint64_t), GFP_KERNEL); - if (!user_pg->cpages) - { - pr_info("Failed to allocate card buffer"); - ret_val = -ENOMEM; - goto err_cpages; - } - - // Alloc 2MB chunks from the card - ret_val = card_alloc(fd, user_pg->cpages, n_pages_huge, LARGE_CHUNK_ALLOC); - if (ret_val) - { - pr_info("Failed to allocate card memory"); - ret_val = -ENOMEM; - goto err_card_mem; - } - dbg_info("card allocated %d hugepages in hypervisor\n", n_pages_huge); - } - - // alloc map array - map_array = kzalloc(n_pages_huge * 2 * sizeof(uint64_t), GFP_KERNEL); - if (!map_array) - { - pr_info("Failed to allocate map buffers\n"); - goto err_map_buffer; - } - - vaddr_tmp = gva; - - // populate map with mappings from guest virtual address - // to host physical addresses - for (i = 0; i < n_pages_huge; i++) - { - tlb_create_map(pd->ltlb_order, - vaddr_tmp, - hpages_phys[i], - (pd->en_mem ? user_pg->cpages[i] : 0), - notifier->cpid, - &map_array[2 * i]); - vaddr_tmp += pd->ltlb_order->page_size; - } - -#ifndef HYPERVISOR_TEST - // Fire the fpga to the fpga - tlb_service_dev(fd, pd->ltlb_order, map_array, n_pages_huge); -#endif - - kfree(map_array); - kfree(hpages_phys); - } - else // For small pages - { - user_pg->n_pages = n_pages; - - // if memory is attached to the card we want to allocate - // the same amount on the card - if (pd->en_mem) - { - // Allocate pages management array - user_pg->cpages = kzalloc(n_pages * sizeof(uint64_t), GFP_KERNEL); - if (!user_pg->cpages) - { - dbg_info("could not allocate card buffer\n"); - ret_val = -ENOMEM; - goto err_cpages; - } - - // Allocate 4KB chunks of card memory - ret_val = card_alloc(fd, user_pg->cpages, n_pages, SMALL_CHUNK_ALLOC); - if (ret_val) - { - dbg_info("could not get all card pages, %d\n", ret_val); - goto err_card_mem; - } - } - - // allocate map array - map_array = (uint64_t *)kzalloc(n_pages * 2 * sizeof(uint64_t), GFP_KERNEL); - if (!map_array) - { - dbg_info("map buffers could not be allocated\n"); - return -ENOMEM; - } - - // populate mappings array with guest virtual address - // to host physical mapping. - for (i = 0; i < n_pages; i++) - { - tlb_create_map(pd->stlb_order, - vaddr_tmp, - page_to_phys(user_pg->hpages[i]), - (pd->en_mem ? user_pg->cpages[i] : 0), - notifier->cpid, &map_array[2 * i]); - vaddr_tmp += pd->stlb_order->page_size; - } - -#ifndef HYPERVISOR_TEST - // fire interrupt to install the mappings on the fpga - tlb_service_dev(fd, pd->stlb_order, map_array, n_pages); -#endif - - // free buffers - kfree(map_array); - kfree(hpages_phys); - } - - // Add entry into the sbuff hash map. This is for - // managment of allocated memory and allows deallocation - // later on. - hash_add(d->sbuff_map, &user_pg->entry, notifier->gva); - - return ret_val; - -err_pin_pages: - for (j = 0; j < i; j++) - { - put_page(user_pg->hpages[j]); - } - - kfree(user_pg->hpages); - kfree(user_pg); - kfree(kvm_hvas); - return -ENOMEM; - -err_map_buffer: - card_free(fd, user_pg->cpages, n_pages_huge, LARGE_CHUNK_ALLOC); -err_card_mem: - kfree(user_pg->cpages); -err_cpages: - kfree(hpages_phys); -err_phys_pages: - for (i = 0; i < user_pg->n_hpages; i++) - { - put_page(user_pg->hpages[i]); - } -err_hpages: - kfree(user_pg); -err_user_pg: - kfree(kvm_hvas); -err_hvas: - return ret_val; -} - -/** - * @brief Unmap an entry described by a - * tmp_buffer. This is the hypervisor version - * of the put user pages. This code is a small refactor of the original - * version since this function contains the code to actually unmap - * a buffer and in turn is used by the put and put all functions. - * - * @param md mediated device - * @param tmp_buffer user pages struct that describes the mapped region - * @param dirtied Indicates if all pages should be marked dirty before putting - * @return int 0 if successfull - */ -static int unmap_entry(struct m_fpga_dev *md, struct user_pages *tmp_buffer, int dirtied) -{ - int i; - struct fpga_dev *d; - struct bus_drvdata *pd; - uint64_t vaddr_tmp, vaddr; - uint64_t *map_array; - int32_t cpid; - struct tlb_order *tlb_order; - - BUG_ON(!md); - d = md->fpga; - BUG_ON(!d); - pd = d->pd; - BUG_ON(!pd); - BUG_ON(!tmp_buffer); - BUG_ON(!(tmp_buffer->hpages)); - - dbg_info("Putting user_pages entry\n"); - - vaddr = tmp_buffer->vaddr; - cpid = tmp_buffer->cpid; - - // If the pages should be dirtied, - // go through all pages and mark them as dirty. - if (dirtied) - { - for (i = 0; i < tmp_buffer->n_hpages; i++) - { - if (tmp_buffer->hpages[i]) - SetPageDirty(tmp_buffer->hpages[i]); - else - dbg_info("entry for page %d is NULL!\n", i); - } - dbg_info("Marked pages as dirty\n"); - } - - // Put all pages that belong to this buffer - // and allow the machine to evict them from memory - // if it desires to do so. - for (i = 0; i < tmp_buffer->n_hpages; i++) - { - // dbg_info("Putting page %d...\n", i); - if (tmp_buffer->hpages[i]) - put_page(tmp_buffer->hpages[i]); - else - dbg_info("entry for page %d is NULL!\n", i); - } - - // release card pages - if (pd->en_mem) - { - card_free(d, tmp_buffer->cpages, tmp_buffer->n_pages, - tmp_buffer->huge ? LARGE_CHUNK_ALLOC : SMALL_CHUNK_ALLOC); - } - - // - // Unmap from the TLB - // - vaddr_tmp = vaddr; - - // alloc map array - map_array = (uint64_t *)kzalloc(tmp_buffer->n_pages * 2 * sizeof(uint64_t), GFP_KERNEL); - if (!map_array) - { - dbg_info("map buffers could not be allocated\n"); - return -ENOMEM; - } - - tlb_order = tmp_buffer->huge ? pd->ltlb_order : pd->stlb_order; - - for (i = 0; i < tmp_buffer->n_pages; i++) - { - // This code works for huge and small pages and therefore - // there is no need here to seperate the code for the two cases. - // Create unmap entries in the map array. - tlb_create_unmap(tlb_order, vaddr_tmp, cpid, &map_array[2 * i]); - vaddr_tmp += tlb_order->page_size; - } -#ifndef HYPERVISOR_TEST - // Fire to actually remove the mappings from the tlb. - tlb_service_dev(d, tlb_order, map_array, tmp_buffer->n_pages); -#endif - kfree(map_array); - - dbg_info("Successfully put user pages at gva %llx consisting of %llu pages for cpid %d\n", - tmp_buffer->vaddr, tmp_buffer->n_hpages, tmp_buffer->cpid); - return 0; -} - -/** - * @brief Put all card and kernel pages and therefore allow evicitions, - * as described by the notifier. The notifier contains the vaddr of the - * region that should be put. This function searches the hash table - * for a corresponding mapping and uses the unmap_entry function - * to unmap the corresponding buffer. Afterwards it removes - * the mapping from the hashtable. - * - * @param md mediated device - * @param notifier notifier passed from the vm, copied into kernelspace - * @return int 0 if successfull - */ -int hypervisor_tlb_put_user_pages(struct m_fpga_dev *md, struct hypervisor_map_notifier *notifier) -{ - struct user_pages *tmp_buff; - struct fpga_dev *d; - struct bus_drvdata *pd; - - uint64_t vaddr; - uint64_t dirtied; - int32_t cpid; - - BUG_ON(!md); - d = md->fpga; - BUG_ON(!d); - pd = d->pd; - - BUG_ON(!notifier); - vaddr = notifier->gva; - dirtied = notifier->dirtied; - cpid = notifier->cpid; - - // Find all user mappings that map to the same bucket as - // vaddr. - hash_for_each_possible(md->sbuff_map, tmp_buff, entry, vaddr) - { - // Check if it is the correct entry - if (tmp_buff->vaddr == vaddr && tmp_buff->cpid == cpid) - { - // unmap from TLB - unmap_entry(md, tmp_buff, dirtied); - // delete from hashtable - hash_del(&tmp_buff->entry); - // free memory - kfree(tmp_buff->hpages); - kfree(tmp_buff); - } - } - return 0; -} - -/** - * @brief Similar to hypervisor_tlb_put_user_pages but put all pages - * hold by the device md. - * - * @param md mediated device - * @param dirtied indicates if all pages should be marked dirty before putting - * @return int 0 if successfull - */ -int hypervisor_tlb_put_user_pages_all(struct m_fpga_dev *md, int dirtied) -{ - struct fpga_dev *d; - struct bus_drvdata *pd; - int bkt; - struct user_pages *tmp_buff; - - BUG_ON(!md); - d = md->fpga; - BUG_ON(!d); - pd = d->pd; - BUG_ON(!pd); - - // Iterate through all mappings and unmap all - hash_for_each(md->sbuff_map, bkt, tmp_buff, entry) - { - // unmap from TLB - unmap_entry(md, tmp_buff, dirtied); - // delete from hash table - hash_del(&tmp_buff->entry); - // free memory - kfree(tmp_buff->hpages); - kfree(tmp_buff); - } - return 0; -} \ No newline at end of file diff --git a/driver/hypervisor/hypervisor_mmu.h b/driver/hypervisor/hypervisor_mmu.h deleted file mode 100644 index bc1dc8d3c..000000000 --- a/driver/hypervisor/hypervisor_mmu.h +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2023, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#ifndef __HYPERVISOR_MMU_H__ -#define __HYPERVISOR_MMU_H__ - -#include "../coyote_dev.h" -#include "../fpga_mmu.h" -#include "hypervisor.h" - -int hypervisor_tlb_get_user_pages(struct m_fpga_dev *d, struct hypervisor_map_notifier *notifier); -int hypervisor_tlb_put_user_pages(struct m_fpga_dev *md, struct hypervisor_map_notifier *notifier); -int hypervisor_tlb_put_user_pages_all(struct m_fpga_dev *md, int dirtied); - -#endif \ No newline at end of file diff --git a/driver/hypervisor/hypervisor_ops.c b/driver/hypervisor/hypervisor_ops.c deleted file mode 100644 index e60976484..000000000 --- a/driver/hypervisor/hypervisor_ops.c +++ /dev/null @@ -1,1772 +0,0 @@ -/** - * Copyright (c) 2023, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#include "hypervisor_ops.h" - -/** - * @brief Set the up capability section of the pci config space. - * At the moment this includes the MSIX capability and the PCI express - * endpoint capability which is copied from the actual hardware. - * This of course only happens if we are on PCI platform. - * - * @param cfg - * @param pd - */ -static void set_up_cap_section(struct pci_config_space *cfg, struct bus_drvdata *pd) -{ - struct cap_header *header; - uint8_t offset; - int i; - - // Init at the first position for the cap list - cfg->cap_pointer = MSIX_OFFSET; - // Let the OS know that this device has caps - cfg->status |= BIT(4); - - set_up_msix_header(cfg, MSIX_OFFSET, NUM_INTERRUPTS); - - // Get header - header = (struct cap_header *)((uint8_t *)cfg + MSIX_OFFSET); - - // If we are on a PCI platform, copy the endpoint header - if (cyt_arch == CYT_ARCH_PCI) - { - // Point to the next cap - header->next_pointer = MSIX_OFFSET + MSIX_SIZE; - - // Copy cap struct from pci device - offset = pci_find_capability(pd->pci_dev, PCI_CAP_ID_EXP); - for (i = 0; i < PCI_CAP_PCIX_SIZEOF_V2; i++) - { - pci_read_config_byte(pd->pci_dev, - offset + i, (uint8_t *)cfg + MSIX_OFFSET + MSIX_SIZE + i); - } - - header = (struct cap_header *)((uint8_t *)cfg + MSIX_OFFSET + MSIX_SIZE); - } - header->next_pointer = 0; -} - -/** - * @brief Sets the pci configuration of the mdev - * on creation of the virtual device. The configuration space - * communicates to the guest os that we use 3 64bit bars - * and communicates the size of these bars. Furthermore - * we emulate the the vendor_id 0x0102 and device_id 0x0304. - * TODO: Discuss a change to the illeagal address 0xffff - * for the vendor id. QEMU replaces this illeageal id - * with an free vendor id. However, this might not be the - * case for other emulation solutions. - * - * @param cfg pointer to pci_config_space - */ -static void set_pci_config(struct pci_config_space *cfg, - struct bus_drvdata *pd) -{ - - uint64_t bar0_addr, bar2_addr, bar4_addr; - - // Clear memory - memset(cfg, 0, sizeof(struct pci_config_space)); - - // Device id - cfg->vendor_id = 0x0102; - cfg->device_id = 0x0304; - - // Can control the (virtual) bus - cfg->command = PCI_COMMAND_IO | PCI_COMMAND_MEMORY; - - // No status - cfg->status = 0x0200; - - cfg->revison_id = 0x10; - cfg->programming_interface = 0x00; - cfg->subclass = 0x00; - cfg->class_code = 0xff; - - // Bar0: 64 bit in memory mode - bar0_addr = COYOTE_HYPERVISOR_BAR0_MASK | - PCI_BASE_ADDRESS_MEM_TYPE_64 | - PCI_BASE_ADDRESS_SPACE_MEMORY; - - if (COYOTE_HYPERVISOR_BAR0_SIZE) - { - cfg->bar0 = bar0_addr; - cfg->bar1 = (uint32_t)(bar0_addr >> 32); - } - - // Bar2: 64 bit in memory mode - bar2_addr = COYOTE_HYPERVISOR_BAR2_MASK | - PCI_BASE_ADDRESS_MEM_TYPE_64 | - PCI_BASE_ADDRESS_SPACE_MEMORY; - - if (COYOTE_HYPERVISOR_BAR2_SIZE) - { - cfg->bar2 = (uint32_t)bar2_addr; - cfg->bar3 = (uint32_t)(bar2_addr >> 32); - } - - // Bar4: 64 bit in memory mode - bar4_addr = COYOTE_HYPERVISOR_BAR4_MASK | - PCI_BASE_ADDRESS_MEM_TYPE_64 | - PCI_BASE_ADDRESS_SPACE_MEMORY; - - if (COYOTE_HYPERVISOR_BAR4_SIZE) - { - cfg->bar4 = (uint32_t)bar4_addr; - cfg->bar5 = (uint32_t)(bar4_addr >> 32); - } - - // Init cap list - set_up_cap_section(cfg, pd); - - // Interrupt pin - cfg->interrupt_pin = 0x01; -} - -/** - * @brief handles create operation for the mediated device - * allocates data structures and does basic initizalization. - * This function should only called from the mdev framework! - * - * @param mdev passed from the vfio_mdev framework - * @return int zero on success - */ -static int hypervisor_vfpga_create(struct mdev_device *mdev) -{ - struct device *dev; - struct bus_drvdata *d; - struct fpga_dev *fpga; - struct m_fpga_dev *m_vfpga; - int i; - int ret_val; - - ret_val = 0; - dbg_info("Create start.\n"); - - // Load parent data to find out - // the fpga region we belong to - BUG_ON(!mdev); - dev = mdev_parent_dev(mdev); - if (!dev) - { - pr_err("Failed to get mdev parent device\n"); - return -EIO; - } - d = dev_get_drvdata(dev); - if (!d) - { - pr_err("Failed to get bus drv data\n"); - return -EIO; - } - - dbg_info("Got info\n"); - - // Get id of parent device by parsing the name - sscanf(dev->kobj.name, "fpga%d", &i); - dbg_info("Create for parent device %d\n", i); - // use the id to get the fpga_dev - fpga = &d->fpga_dev[i]; - - BUG_ON(!fpga); - dbg_info("Got fpga\n"); - - // allocate mangement struct for the new mediated device - m_vfpga = kzalloc(sizeof(struct m_fpga_dev), GFP_KERNEL); - if (!m_vfpga) - { - pr_err("failed to allocate m_vfpga memory\n"); - return -ENOMEM; - } - - m_vfpga->fpga = fpga; - m_vfpga->current_cpid = INVALID_CPID; - - // Get vcid. Unique per fpga region and - // used to make the pid unique - spin_lock(&fpga->vcid_lock); - m_vfpga->id = fpga->vcid_alloc->id; - fpga->vcid_alloc = fpga->vcid_alloc->next; - fpga->num_free_vcid_chunks -= 1; - spin_unlock(&fpga->vcid_lock); - - // Set up pci config - set_pci_config(&m_vfpga->pci_config, d); - - // Init locks - spin_lock_init(&m_vfpga->lock); - spin_lock_init(&m_vfpga->current_cpid_lock); - - // add into the list of virtual fpgas - spin_lock(&fpga->list_lock); - list_add(&m_vfpga->next, &fpga->mdev_list); - spin_unlock(&fpga->list_lock); - - mdev_set_drvdata(mdev, m_vfpga); - - dbg_info("successfully created medaited vfpga device\n"); - - return ret_val; -} - -/** - * @brief Called on deletion of the mediated device. - * Furthermore it frees up the vcid and removes it from - * the list of fpgas. This function should only be called from the - * mdev framework! - * - * @param mdev passed from the mdev framework - * @return int zero on success - */ -static int hypervisor_vfpga_remove(struct mdev_device *mdev) -{ - struct fpga_dev *fpga; - struct m_fpga_dev *m_vfpga; - - m_vfpga = mdev_get_drvdata(mdev); - fpga = m_vfpga->fpga; - - // release vcid - spin_lock(&fpga->vcid_lock); - fpga->vcid_chunks[m_vfpga->id].next = fpga->vcid_alloc; - fpga->vcid_alloc = &fpga->vcid_chunks[m_vfpga->id]; - fpga->num_free_pid_chunks += 1; - spin_unlock(&fpga->vcid_lock); - - // Remove from the list of virtual vFPGAs - spin_lock(&fpga->list_lock); - list_del(&m_vfpga->next); - spin_unlock(&fpga->list_lock); - - // Free memory - kfree(m_vfpga); - - dbg_info("successfully removed mdeiated vfpga device\n"); - - return 0; -} - -/** - * @brief handler function for the vfio kvm notify event. - * Only called by the vfio framework. For example, if we use qemu - * this function is called by qemu to let out module know which - * kvm it is using. This is later needed to do page table walks inside - * this module. - * - * @param nb notifier block from vfio - * @param action what event happend - * @param data payload - * @return int zero on success - */ -int hypervisor_vfio_notifier(struct notifier_block *nb, unsigned long action, void *data) -{ - struct m_fpga_dev *vfpga; - - vfpga = container_of(nb, struct m_fpga_dev, notifier); - - spin_lock(&vfpga->lock); - - // only do something if this is a set kvm event - if (action == VFIO_GROUP_NOTIFY_SET_KVM) - { - vfpga->kvm = data; - dbg_info("kvm set successfully"); - } - - spin_unlock(&vfpga->lock); - - return 0; -} - -/** - * @brief handles the opening of the mediated device file (after creation) - * this happens if the vm boots and therefore the file - * is opened from the hypervisor to emulate the pci device. This function - * does most of the allocation to allow the emulation. - * This includes the MSIX region setup and the registration - * of the kvm notifier. - * - * @param mdev passed from the mdev framework - * @return int zero on success - */ -static int hypervisor_vfpga_open(struct mdev_device *mdev) -{ - struct m_fpga_dev *vfpga; - long unsigned int events; - int ret_val; - int i; - - ret_val = 0; - - // Get virtual vFGPA struct - vfpga = mdev_get_drvdata(mdev); - - spin_lock(&vfpga->lock); - // mediated vfpgas are only inteded for the use by one vm at the time - if (vfpga->in_use) - { - dbg_info("Failed to open vfio device: busy\n"); - spin_unlock(&vfpga->lock); - return -EBUSY; - } - - // Set busy - vfpga->in_use = 1; - spin_unlock(&vfpga->lock); - - // allocate msix table - // TODO: remove this, seems to be handled by QEMU - vfpga->msix_table = kzalloc(2000, GFP_KERNEL); - if (!vfpga->msix_table) - { - dbg_info("Failed to allocate msix tabel\n"); - ret_val = -ENOMEM; - goto err_msix_table; - } - - // allocate array for eventfd fds - vfpga->msix_vector = kzalloc(NUM_INTERRUPTS * sizeof(struct msix_interrupt), GFP_KERNEL); - if (!vfpga->msix_vector) - { - dbg_info("Failed to allocate msix interrupts\n"); - ret_val = -ENOMEM; - goto err_msix_interrupts; - } - - // expliclty invalidate all file descriptors entries - for (i = 0; i < NUM_INTERRUPTS; i++) - { - vfpga->msix_vector[i].eventfd = -1; - } - - // Init memory maps - hash_init(vfpga->sbuff_map); - - /* We know that this is the only thread accessing this device */ - - // Register KVM notifier - events = VFIO_GROUP_NOTIFY_SET_KVM; - vfpga->notifier.notifier_call = hypervisor_vfio_notifier; - ret_val = vfio_register_notifier(mdev_dev(mdev), VFIO_GROUP_NOTIFY, &events, &vfpga->notifier); - if (ret_val) - { - pr_err("notifier registration failed\n"); - } - - dbg_info("successfully opened mediated vfpga\n"); - - return ret_val; - -err_msix_interrupts: - kfree(vfpga->msix_table); -err_msix_table: - return ret_val; -} - -/** - * @brief called when the device file is closed. This happens if the vm shuts down - * removes every state that was associated with the current vm. This includes - * to disable all current interrupts and free all msix data. - * - * @param mdev mdev device in question - */ -static void hypervisor_vfpga_close(struct mdev_device *mdev) -{ - struct m_fpga_dev *vfpga; - int ret_val; - - ret_val = 0; - - // Get virtual vFGPA struct - vfpga = mdev_get_drvdata(mdev); - - spin_lock(&vfpga->lock); - - BUG_ON(!vfpga->in_use); - - // unset all interrupts - msix_unset_all_interrupts(vfpga); - - // Set free - vfpga->in_use = 0; - spin_unlock(&vfpga->lock); - - // free allocated memory - kfree(vfpga->msix_table); - kfree(vfpga->msix_vector); - - // Unregister vfio notifier - ret_val = vfio_unregister_notifier(mdev_dev(mdev), VFIO_GROUP_NOTIFY, &vfpga->notifier); - if (ret_val) - { - dbg_info("Failed to unregister VFIO notifier\n"); - } - - dbg_info("Closed mediated vfpga file\n"); -} - -/** - * @brief General function to access data inside of the vm, - * allows to read and write from the vm that is associated with the vfpga. - * Only works after the device file was opened! - * - * @param vfpga mediated vfpga device - * @param gpa guest physical address - * @param count bytes to read/write - * @param buf source/target buffer - * @param write - * @return ssize_t - */ -static ssize_t hypervisor_access_kvm(struct m_fpga_dev *vfpga, - gpa_t gpa, - size_t count, - void *buf, - int write) -{ - struct kvm *kvm; - int ret_val; - int idx; - - kvm = vfpga->kvm; - BUG_ON(!kvm); - - ret_val = 0; - - dbg_info("Accessing kvm at gpa %llx to %s %lu bytes", gpa, (write ? "write" : "read"), count); - - /* - The function uses the page tables of the vm and - therefore they are not allowed to change during the translation. - Therefore we have to hold the srce lock. - */ - idx = srcu_read_lock(&kvm->srcu); - if (write) - { - ret_val = kvm_write_guest(kvm, gpa, buf, count); - } - else - { - ret_val = kvm_read_guest(kvm, gpa, buf, count); - } - - if (ret_val) - { - pr_info("Failed to %s kvm\n", (write ? "write" : "read")); - } - srcu_read_unlock(&kvm->srcu, idx); - - return ret_val; -} - -/** - * @brief Bar0 access are relayed to the actual hardware for - * calls that would be mmap in a not vm scenario. BAR0 acts as a passthrough - * for this cases and are mmaped in the vm. Therefore this function is - * not used and should not be used since the trap comes at a very high cost - * and should be avoided whenever possible! - * - * @param vfpga mediated vfpga - * @param buf read/write buffer - * @param count bytes to read/write - * @param pos offset into the bar register - * @param write 1 for write - * @return ssize_t bytes read/write - */ -static ssize_t handle_bar0_access(struct m_fpga_dev *vfpga, char __user *buf, - size_t count, loff_t pos, int write) -{ - uint64_t offset; - uint64_t index; - struct fpga_dev *d; - uint64_t tmp[64]; - void __iomem *ioaddr; - int ret_val; - ret_val = 0; - - BUG_ON(!vfpga); - d = vfpga->fpga; - BUG_ON(!d); - - /* - 20 is the shift that is used for the subregions between the not avx and avx - regions. Therefore to differentiate between accesses of this two regions - the fastes way is to compute the value of the upper bits. - */ - index = pos >> 20; - offset = pos & ((1 << 20) - 1); - if (write) - { - dbg_info("Accessing bar0 at addr %#llx and offset %#llx with count %lu at index %llx\n", pos, offset, count, index); - } - - switch (index) - { - case 0x1: - /* - Depending on the offset into the region we want to access at different - mappings. This is happening here. This is one of the reasons why a trap - is not very efficent. - */ - if (offset >= FPGA_CTRL_LTLB_OFFS && offset < FPGA_CTRL_LTLB_SIZE + FPGA_CTRL_LTLB_OFFS) - { - ioaddr = (void __iomem *)vfpga->fpga->fpga_lTlb + (offset - FPGA_CTRL_LTLB_OFFS); - } - else if (offset >= FPGA_CTRL_STLB_OFFS && offset < FPGA_CTRL_STLB_SIZE + FPGA_CTRL_STLB_OFFS) - { - ioaddr = (void __iomem *)vfpga->fpga->fpga_sTlb + (offset - FPGA_CTRL_STLB_OFFS); - } - else if (offset >= FPGA_CTRL_USER_OFFS && offset < FPGA_CTRL_USER_OFFS + FPGA_CTRL_USER_SIZE) - { - ioaddr = (void __iomem *)vfpga->fpga->fpga_user + (offset - FPGA_CTRL_USER_OFFS); - } - else if (offset >= FPGA_CTRL_CNFG_OFFS && offset < FPGA_CTRL_CNFG_OFFS + FPGA_CTRL_CNFG_SIZE) - { - ioaddr = (void __iomem *)vfpga->fpga->fpga_cnfg + (offset - FPGA_CTRL_CNFG_OFFS); - } - else - { - return -EFAULT; - } - break; - case 0x10: - /* - For avx accesses we do not have this problem. However, during testing we found out - that this part is not working. The corresponding avx instrunction cannot - be trapped and therefore if the corresponding region is not mmaped we cannot - use avx. - */ - ioaddr = (void __iomem *)vfpga->fpga->fpga_cnfg_avx + offset; - break; - default: - dbg_info("Access to unspported offset!\n"); - return -EFAULT; - } - - if (write) - dbg_info("%s at ioaddr %p\n", write ? "write" : "read", ioaddr); - - if (write) - dbg_info("tmp addr %p and buf addr %p\n", tmp, buf); - -#ifndef HYPERVISOR_TEST - if (write) - { - // copy from user - ret_val = copy_from_user(tmp, buf, count); - BUG_ON(ret_val); - memcpy_toio(ioaddr, tmp, count); - // write through to hardware - dbg_info("performed write to ioaddr %p of %lu bytes, first 8 bytes %#016llx", ioaddr, count, tmp[0]); - } - else - { - // copy from io to user - memcpy_fromio(tmp, ioaddr, count); - ret_val = copy_to_user(buf, tmp, count); - BUG_ON(ret_val); - } - -#endif - - return count; -} - -/** - * @brief BAR2 is for communication between the hypervisor - * and the guest driver. These are virtualized versions of the IOCTL - * calls in fpga_fops. This function only handles reads from the register. - * Here are control functions trapped and handled according to the - * desired behaviour but in a mediated manner. All these - * calls are made by the guest driver to communicate with the hypervisor. - * The instructions are: - * - * REGISTER_PID: - * Register PID. This is used in combination with a write to REGISTER_PID. - * The read returns a new CPID that can be used by the guest. - * - * READ_CNFG: - * Read through of the the corresponding ioctl call. Returns key - * configuration parameters of the fpga platform. - * - * @param vfpga mediated vfpga - * @param buf read buffer - * @param count bytes to read - * @param pos offset into the register - * @return ssize_t bytes read - */ -static ssize_t handle_bar2_read(struct m_fpga_dev *vfpga, char __user *buf, - size_t count, loff_t pos) -{ - loff_t offset; - int ret_val; - uint64_t tmp[MAX_USER_ARGS]; - struct fpga_dev *d; - struct bus_drvdata *pd; - - BUG_ON(!vfpga); - d = vfpga->fpga; - BUG_ON(!d); - pd = d->pd; - BUG_ON(!pd); - - // The lower 32 bit represent a command that should be executed - offset = pos & HYPERVISOR_OFFSET_MASK; - switch (offset) - { - case REGISTER_PID_OFFSET: - { - spin_lock(&vfpga->current_cpid_lock); - ret_val = copy_to_user(buf, &vfpga->current_cpid, sizeof(&vfpga->current_cpid)); - if (ret_val) - { - pr_err("%s.%u: Failed to read\n", __func__, __LINE__); - spin_unlock(&vfpga->current_cpid_lock); - return -EFAULT; - } - - // Reset cpid - vfpga->current_cpid = INVALID_CPID; - spin_unlock(&vfpga->current_cpid_lock); - - return count; - } - case READ_CNFG_OFFSET: - { - if (!IS_ALIGNED(pos, 8)) - { - dbg_info("READ_CNFG not correctly alligned\n"); - return -EFAULT; - } - - if (count != 8) - { - dbg_info("READ_CNFG not 8 byte read\n"); - return -EFAULT; - } - -#ifdef HYPERVISOR_TEST - tmp[0] = 0xdeadbeef; -#else - tmp[0] = ((uint64_t)pd->n_fpga_chan << 32) | ((uint64_t)pd->n_fpga_reg << 48) | - ((uint64_t)pd->en_avx) | ((uint64_t)pd->en_bypass << 1) | ((uint64_t)pd->en_tlbf << 2) | ((uint64_t)pd->en_wb << 3) | - ((uint64_t)pd->en_strm << 4) | ((uint64_t)pd->en_mem << 5) | ((uint64_t)pd->en_pr << 6) | - ((uint64_t)pd->en_rdma_0 << 16) | ((uint64_t)pd->en_rdma_1 << 17) | ((uint64_t)pd->en_tcp_0 << 18) | ((uint64_t)pd->en_tcp_1 << 19); -#endif - dbg_info("reading config 0x%llx\n", tmp[0]); - - ret_val = copy_to_user(buf, tmp, count); - - if (ret_val) - { - dbg_info("Failed to copy to user\n"); - return -EFAULT; - } - - return count; - } - default: - { - // Not mapped, return no meaningful data - return count; - } - } -} - -/** - * @brief AVX passthrough. this is not implemented for the simple reason, - * that trapping AVX instrunctions does not work at the moment. Therefore if - * this function would be needed it would not work and therefore the user should - * fallback to a bitstream that does not use avx. - * - * @param vfpga - * @param buf - * @param count - * @param pos - * @param is_write - * @return ssize_t - */ -static ssize_t handle_bar4_access(struct m_fpga_dev *vfpga, char __user *buf, - size_t count, loff_t pos, int is_write) -{ - return count; -} - -/** - * @brief Called by the vfpga framework for any access to the virtual pci - * device in the vm. Based on the region accessed the access is handled - * by a special function. Therefore this function acts as a simple - * demultiplexer. - * - * @param mdev mediated vfpga device - * @param buf read buffer - * @param count bytes to read - * @param ppos position, this is determined by VFIO by IOCTL calls to the driver, handled below - * @return ssize_t bytes read - */ -static ssize_t hypervisor_vfpga_read(struct mdev_device *mdev, char __user *buf, - size_t count, loff_t *ppos) -{ - loff_t pos, offset, index; - int ret_val; - struct m_fpga_dev *vfpga; - - pos = *ppos; - offset = pos & HYPERVISOR_OFFSET_MASK; - index = COYOTE_GET_INDEX(pos); - ret_val = 0; - vfpga = mdev_get_drvdata(mdev); - - switch (index) - { - case VFIO_PCI_CONFIG_REGION_INDEX: - { - // dbg_info("Reading pci config at offset %llu, reading %lu bytes\n", offset, count); - if (offset + count > COYOTE_HYPERVISOR_CONFIG_SIZE) - { - pr_err("%s.%u: Out of bound read\n", __func__, __LINE__); - return -EFAULT; - } - - // Read from in memory config - ret_val = copy_to_user(buf, ((char *)&vfpga->pci_config) + offset, count); - if (ret_val) - { - pr_err("%s.%u: Failed to read\n", __func__, __LINE__); - return -EFAULT; - } - - return count; - } - case VFIO_PCI_BAR0_REGION_INDEX: - { - // Check bounds - if (offset + count > COYOTE_HYPERVISOR_BAR0_SIZE) - { - pr_err("%s.%u: Out of bound read\n", __func__, __LINE__); - return -EFAULT; - } - - return handle_bar0_access(vfpga, buf, count, pos, 0); - } - case VFIO_PCI_BAR2_REGION_INDEX: - { - // Check bounds - if (offset + count > COYOTE_HYPERVISOR_BAR2_SIZE) - { - pr_err("%s.%u: Out of bound read\n", __func__, __LINE__); - return -EFAULT; - } - - return handle_bar2_read(vfpga, buf, count, pos); - } - case VFIO_PCI_BAR4_REGION_INDEX: - { - // Check bounds - if (offset + count > COYOTE_HYPERVISOR_BAR4_SIZE) - { - pr_err("%s.%u: Out of bound read\n", __func__, __LINE__); - return -EFAULT; - } - - return handle_bar4_access(vfpga, buf, count, pos, 0); - } - default: - { - // Not a valid region to read - pr_debug("%s.%u: Read to invalid region %lld\n", __func__, __LINE__, index); - return 0; - } - } -} - -/** - * @brief write to a base address register (BAR). Keeps the lowest 4 bits - * at all time, since they contain non writeable bits. - * - * @param bar pointer to BAR - * @param val value to be written - * @param low write to low(1) or high(0) register - */ -static void cfg_bar_write(uint32_t *bar, uint32_t val, int low) -{ - if (low) - { - *bar = (val & GENMASK(31, 4)) | - (*bar & GENMASK(3, 0)); - } - else - { - *bar = val; - } -} - -/** - * @brief Function to handle writes to the PCI config. Not all writes should be permitted - * so this function emulates that should happen if we would wirte to an actual pci device - * TODO: Here is some functionaly missing, but at the moment the relevant subset is implemented - * - * @param fpga mediated vfpga that the wrote is to - * @param buf write buffer - * @param count bytes to write - * @param pos offset into the configuration space - * @return int zero on success - */ -static int handle_pci_cfg_write(struct m_fpga_dev *fpga, char __user *buf, size_t count, loff_t pos) -{ - uint32_t new_val; - int ret_val; - - ret_val = copy_from_user(&new_val, buf, count); - if (ret_val) - { - dbg_info("could not copy new pci cfg value from userspace\n"); - return -EFAULT; - } - - pos = pos & HYPERVISOR_OFFSET_MASK; - - switch (pos) - { - case 0x10: // BAR 0 - if (new_val == GENMASK(31, 0)) - { - cfg_bar_write(&fpga->pci_config.bar0, (uint32_t)(COYOTE_HYPERVISOR_BAR0_MASK | PCI_BASE_ADDRESS_MEM_TYPE_64 | PCI_BASE_ADDRESS_SPACE_MEMORY), 1); - } - else - { - cfg_bar_write(&fpga->pci_config.bar0, new_val, 1); - } - break; - case 0x14: // BAR 1 - if (new_val == GENMASK(31, 0)) - { - cfg_bar_write(&fpga->pci_config.bar1, (uint32_t)(~(COYOTE_HYPERVISOR_BAR0_SIZE - 1) >> 32), 0); - } - else - { - cfg_bar_write(&fpga->pci_config.bar1, new_val, 0); - } - break; - case 0x18: // BAR 2 - if (new_val == GENMASK(31, 0)) - { - cfg_bar_write(&fpga->pci_config.bar2, (uint32_t)(COYOTE_HYPERVISOR_BAR2_MASK | PCI_BASE_ADDRESS_MEM_TYPE_64 | PCI_BASE_ADDRESS_SPACE_MEMORY), 1); - } - else - { - cfg_bar_write(&fpga->pci_config.bar2, new_val, 1); - } - break; - case 0x1C: // BAR 3 - if (new_val == GENMASK(31, 0)) - { - cfg_bar_write(&fpga->pci_config.bar3, (uint32_t)(~(COYOTE_HYPERVISOR_BAR2_SIZE - 1) >> 32), 0); - } - else - { - cfg_bar_write(&fpga->pci_config.bar3, new_val, 0); - } - break; - case 0x20: // BAR 4 - if (new_val == GENMASK(31, 0)) - { - cfg_bar_write(&fpga->pci_config.bar4, (uint32_t)(COYOTE_HYPERVISOR_BAR4_MASK | PCI_BASE_ADDRESS_MEM_TYPE_64 | PCI_BASE_ADDRESS_SPACE_MEMORY), 1); - } - else - { - cfg_bar_write(&fpga->pci_config.bar4, new_val, 1); - } - break; - case 0x24: // BAR 5 - if (new_val == GENMASK(31, 0)) - { - cfg_bar_write(&fpga->pci_config.bar5, (uint32_t)(~(COYOTE_HYPERVISOR_BAR4_SIZE - 1) >> 32), 0); - } - else - { - cfg_bar_write(&fpga->pci_config.bar5, new_val, 0); - } - break; - case 0x3C: // Interrupt line - fpga->pci_config.interrupt_line = (uint8_t)new_val; - break; - default: - if (pos >= MSIX_OFFSET && pos < MSIX_OFFSET + MSIX_SIZE) - { - // Emulate write to msix header - ret_val = write_to_msix_header((void *)(fpga->pci_config.cap_section + MSIX_OFFSET), pos - MSIX_OFFSET, new_val, count); - if (ret_val < 0) - { - return ret_val; - } - } - else - { - // dbg_info("write to fpga config with %lu not handled writes at offset 0x%llx", - // count, pos); - } - } - - return 0; -} - -/** - * @brief This function handles most of the communication. - * The offset is interpreted as command to the hypervisor and - * the value of the write can be used as argument either to pass a simple - * number or in case of more complicated data it passes a pointer to data - * inside the vm. - * The following functionalities are handled: - * - * REGISTER_PID: - * A write to this command allocates a new CPID (if any are free) - * and registers the pid passed as argument. The CPID can be retrieved by the - * guest by a read at the same offset. - * - * UNREGISTER_PID: - * Complement to REGISTER_PID. The guest writes the CPID to this offset - * to deregister a guest process. - * - * MAP_USER: - * The guest writes the address to the notifier to this offset. The hypervisor - * copies the notifier from the vm. The notifier contains information about - * the address range that should be mapped on the fpga. - * - * UNMAP_USER: - * Counterpart to MAP_USER. Works in a similar matter. The guest writes - * the address of a notifier to this offset. The hypervisor reads the struct from the - * vm and unmaps the range. The range has to be mapped previsously with a call to - * MAP_USER. - * - * PUT_ALL_USER_PAGES: - * The guest writes 0 or non zero to this offset to indicate if the pages - * should not be dirtied/should be dirtied before releasing them. - * The call then puts all pages that were mapped by the mediated device. - * The guest usally calls this if the device is closed in the vm. - * - * TEST_INTERRUPT: - * TODO: delete - * - * @param vfpga mediated vfpga - * @param buf write buffer - * @param count bytes to write - * @param pos offset into BAR2 region - * @return ssize_t bytes written - */ -static ssize_t handle_bar2_write(struct m_fpga_dev *vfpga, const char __user *buf, - size_t count, loff_t pos) -{ - loff_t offset; - int ret_val; - uint64_t pid, epid; - uint64_t cpid; - struct bus_drvdata *pd; - uint64_t tmp[MAX_USER_ARGS + 2]; - struct hypervisor_map_notifier map_notifier, *full_map_notifier; - uint64_t map_full_size; - - ret_val = 0; - offset = pos & HYPERVISOR_OFFSET_MASK; - pd = vfpga->fpga->pd; - - switch (offset) - { - case REGISTER_PID_OFFSET: - { - ret_val = copy_from_user(tmp, buf, count); - if (ret_val) - { - pr_err("%s.%u: Failed to copy data from user\n", __func__, __LINE__); - return -EFAULT; - } - - pid = tmp[0]; - dbg_info("Registering pid %llu\n", pid); - - spin_lock(&pd->stat_lock); - spin_lock(&vfpga->current_cpid_lock); - - // Calculate an effective pid to avoid clashes with other vms - // lower 16 bits are the pid inside the vm and the upper 16 - // bits are to identify the vm - epid = pid | (vfpga->id << 16); - - // register epid - cpid = register_pid(vfpga->fpga, epid); - if (cpid == -1) - { - pr_err("registration failed in hypervisor pid: %lld id: %d", pid, vfpga->id); - spin_unlock(&pd->stat_lock); - return -EIO; - } - - // bookeeping which virtual devices belongs to which region - vfpga->fpga->vdevs[cpid] = vfpga; - - vfpga->current_cpid = cpid; - - spin_unlock(&pd->stat_lock); - spin_unlock(&vfpga->current_cpid_lock); - - dbg_info("Successfully registered pid %llu\n", pid); - - return count; - } - case UNREGISTER_PID_OFFSET: - { - // read cpid - ret_val = copy_from_user(&tmp, buf, count); - if (ret_val) - { - pr_err("%s.%u: Failed to copy data from user\n", __func__, __LINE__); - return -EFAULT; - } - - cpid = tmp[0]; - dbg_info("Unregistering cpid %llu\n", cpid); - - // Unregister cpid - spin_lock(&pd->stat_lock); - - ret_val = unregister_pid(vfpga->fpga, cpid); - if (ret_val) - { - pr_err("unregestration failed in hypervisor pid: %lld id: %d\n", pid, vfpga->id); - spin_unlock(&pd->stat_lock); - return -EIO; - } - - // Remove reverse mapping, cpid is not in use anymore - vfpga->fpga->vdevs[cpid] = NULL; - dbg_info("unregestration succesfull in hypervisor pid: %lld id: %d\n", pid, vfpga->id); - spin_unlock(&pd->stat_lock); - return count; - } - case MAP_USER_OFFSET: - { - // read gpa of the arguments - ret_val = copy_from_user(&tmp, buf, count); - if (ret_val) - { - pr_info("%s.%d: Failed to copy gpa", __func__, __LINE__); - return -EFAULT; - } - - // read notifier header from guest - ret_val = hypervisor_access_kvm(vfpga, tmp[0], sizeof(struct hypervisor_map_notifier), &map_notifier, 0); - if (ret_val) - { - pr_info("%s.%d: Failed to read from guest", __func__, __LINE__); - return -EIO; - } - - // read complete notifier from guest - map_full_size = sizeof(struct hypervisor_map_notifier) + map_notifier.npages * sizeof(uint64_t); - full_map_notifier = kzalloc(map_full_size, GFP_KERNEL); - - ret_val = hypervisor_access_kvm(vfpga, tmp[0], map_full_size, full_map_notifier, 0); - if (ret_val) - { - pr_info("%s.%d: Failed to read from guest", __func__, __LINE__); - kfree(full_map_notifier); - return -EIO; - } - - dbg_info("Mapping user pages from hypervisor: gva: %llx, len: %llu, cpid: %llu", full_map_notifier->gva, - full_map_notifier->len, full_map_notifier->cpid); - - // Pin pages and install user mappings onto fpga. - ret_val = hypervisor_tlb_get_user_pages(vfpga, full_map_notifier); - if (ret_val) - { - pr_info("%s.%d: Failed to get all user pages", __func__, __LINE__); - } - else - { - ret_val = count; - dbg_info("Successfully mapped user buffer\n"); - } - - kfree(full_map_notifier); - return ret_val; - } - case UNMAP_USER_OFFSET: - { - // Copy gpa - ret_val = copy_from_user(&tmp, buf, count); - if (ret_val) - { - pr_info("%s.%d: Failed to copy gpa", __func__, __LINE__); - return -EFAULT; - } - - // read gva + cpid from kvm - ret_val = hypervisor_access_kvm(vfpga, tmp[0], sizeof(struct hypervisor_map_notifier), &map_notifier, - 0); - if (ret_val) - { - pr_info("%s.%d: Failed to read from kvm", __func__, __LINE__); - return -EFAULT; - } - - ret_val = hypervisor_tlb_put_user_pages(vfpga, &map_notifier); - if (ret_val) - { - pr_info("%s.%d: Failed to put all user pages", __func__, __LINE__); - } - else - { - ret_val = count; - dbg_info("Successfully unmapped user buffer\n"); - } - - return ret_val; - } - case PUT_ALL_USER_PAGES: - { - // read dirtied flag - ret_val = copy_from_user(&tmp, buf, count); - if (ret_val) - { - pr_info("could not copy dirtied flag\n"); - return -EFAULT; - } - - // put all user pages - ret_val = hypervisor_tlb_put_user_pages_all(vfpga, tmp[0]); - if (ret_val) - { - pr_info("could not put all user pages\n"); - return -EIO; - } - - return count; - } - case TEST_INTERRUPT_OFFSET: - { - dbg_info("Fired interrupt with ret_val %llu!\n", fire_interrupt(&vfpga->msix_vector[0])); - return count; - } - default: - { - // Not used, should not cause any state change - return count; - } - } - - return count; -} - -/** - * @brief Similar to vfpga_read. Demultiplexes the writes - * to the emulated pci device. - * - * @param mdev mediated virtual device - * @param buf write buffer - * @param count bytes to write - * @param ppos address - * @return ssize_t bytes written - */ -static ssize_t hypervisor_vfpga_write(struct mdev_device *mdev, const char __user *buf, - size_t count, loff_t *ppos) -{ - loff_t pos, offset, index; - int ret_val; - struct m_fpga_dev *vfpga; - - pos = *ppos; - offset = pos & HYPERVISOR_OFFSET_MASK; - index = COYOTE_GET_INDEX(pos); - ret_val = 0; - - vfpga = mdev_get_drvdata(mdev); - BUG_ON(!vfpga); - - switch (index) - { - case VFIO_PCI_CONFIG_REGION_INDEX: - { - if (offset + count > COYOTE_HYPERVISOR_CONFIG_SIZE) - { - pr_err("%s.%u: Out of bound write\n", __func__, __LINE__); - return -EFAULT; - } - - ret_val = handle_pci_cfg_write(vfpga, (char __user *)buf, count, pos); - if (ret_val) - { - dbg_info("could not write pci cfg\n"); - return -EIO; - } - - return count; - } - case VFIO_PCI_BAR0_REGION_INDEX: - { - // Check bounds - if (offset + count > COYOTE_HYPERVISOR_BAR0_SIZE) - { - pr_err("%s.%u: Out of bound read\n", __func__, __LINE__); - return -EFAULT; - } - - return handle_bar0_access(vfpga, (char __user *)buf, count, pos, 1); - } - case VFIO_PCI_BAR2_REGION_INDEX: - { - // Check bounds - if (offset + count > COYOTE_HYPERVISOR_BAR2_SIZE) - { - pr_err("%s.%u: Out of bound read\n", __func__, __LINE__); - return -EFAULT; - } - - return handle_bar2_write(vfpga, buf, count, pos); - } - case VFIO_PCI_BAR4_REGION_INDEX: - { - // Check bounds - if (offset + count > COYOTE_HYPERVISOR_BAR4_SIZE) - { - pr_err("%s.%u: Out of bound read\n", __func__, __LINE__); - return -EFAULT; - } - - return handle_bar4_access(vfpga, (char *)buf, count, pos, 1); - } - default: - { - // Not a valid region to read - pr_debug("%s.%d: Write to invalid region %lld\n", __func__, __LINE__, index); - return 0; - } - } -} - -/** - * @brief IOCTL calls to the device. The vm cannot perform any ioctl calls - * but the VFIO framework has a set of IOCTL functions that it will use to gather - * information over the pci device that is emulated by this driver. - * - * These are: - * - VFIO_DEVICE_GET_INFO - * Passes a pointer to a struct. This is filled by this function with the - * amount of regions and interrupts. Furthermore it communicates the - * capabilties of the device. - * - * - VFIO_DEVICE_GET_REGION_INFO - * Passes a pointer to a struct. The user set the index of the region - * it wants more information about and this function will set the remaining fields - * with relevant information such as offset and size. - * - * - VFIO_DEVICE_GET_IRQ_INFO - * Similar to GET_REGION_INFO, but for interrupts. The user passes - * a struct and specifies about which kind of interrupt the information should - * be provided and the this function will set the other fields. - * - * - VFIO_DEVICE_SET_IRQS - * Managment for the interrupts. The user calls with a combination of flags that - * set interrupts. For more information consult the VFIO documentation. - * - * - VFIO_DEVICE_RESET - * TODO: Implement - * - * @param mdev mediated vfpga - * @param cmd - * @param arg - * @return long - */ -static long hypervisor_vfpga_ioctl(struct mdev_device *mdev, unsigned int cmd, unsigned long arg) -{ - struct m_fpga_dev *vfpga; - struct vfio_device_info dev_info; - struct vfio_region_info region_info; - struct vfio_irq_info irq_info; - struct vfio_irq_set *irq_set; - int ret_val; - unsigned int bytes; - void __user *argp; - uint64_t index; - - BUG_ON(!mdev); - vfpga = mdev_get_drvdata(mdev); - if (!vfpga) - { - pr_err("Failed to get drv data\n"); - return -EIO; - } - - ret_val = 0; - argp = (void __user *)arg; - - switch (cmd) - { - case VFIO_DEVICE_GET_INFO: - { - // Copy the argsz paramter from user space - ret_val = copy_from_user(&bytes, argp, sizeof(bytes)); - if (ret_val) - { - pr_err("%s.%u: Failed to copy from user space", __func__, __LINE__); - return -EFAULT; - } - - // Copy the dev_info struct - ret_val = copy_from_user(&dev_info, argp, bytes); - if (ret_val) - { - pr_err("%s.%u: Failed to copy from user space", __func__, __LINE__); - return -EFAULT; - } - - // Resetable PCI device - dev_info.flags = VFIO_DEVICE_FLAGS_PCI | VFIO_DEVICE_FLAGS_RESET; - - // Propagate default values from the framework - dev_info.num_regions = VFIO_PCI_NUM_REGIONS; - dev_info.num_irqs = VFIO_PCI_NUM_IRQS; - - // Copy the updated info struct back to the user - ret_val = copy_to_user(argp, &dev_info, bytes); - if (ret_val) - { - pr_err("%s.%u: Failed to copy from user space", __func__, __LINE__); - return -EFAULT; - } - - return 0; - } - case VFIO_DEVICE_GET_REGION_INFO: - { - // Copy the argsz paramter from user space - ret_val = copy_from_user(&bytes, argp, sizeof(bytes)); - if (ret_val) - { - pr_err("%s.%u: Failed to copy from user space", __func__, __LINE__); - return -EFAULT; - } - - // Copy the dev_info struct - ret_val = copy_from_user(®ion_info, argp, bytes); - if (ret_val) - { - pr_err("%s.%u: Failed to copy from user space", __func__, __LINE__); - return -EFAULT; - } - - // For the config region and the - // 3 64 bit registers return the size. Everything else does not - // exist and is therefore 0. - index = region_info.index; - switch (index) - { - case VFIO_PCI_CONFIG_REGION_INDEX: - { - region_info.size = COYOTE_HYPERVISOR_CONFIG_SIZE; - break; - } - case VFIO_PCI_BAR0_REGION_INDEX: - { - region_info.size = COYOTE_HYPERVISOR_BAR0_SIZE; - // direct pass through, for high performance, do not trap this instructions - region_info.flags |= VFIO_REGION_INFO_FLAG_MMAP; - break; - } - case VFIO_PCI_BAR2_REGION_INDEX: - { - region_info.size = COYOTE_HYPERVISOR_BAR2_SIZE; - break; - } - case VFIO_PCI_BAR4_REGION_INDEX: - { - region_info.size = COYOTE_HYPERVISOR_BAR4_SIZE; - // direct pass through, for high performance, do not trap this instructions - region_info.flags |= VFIO_REGION_INFO_FLAG_MMAP; - break; - } - default: - { - region_info.size = 0; - } - } - - // Upper 32 bits are used to indicate the index - region_info.offset = COYOTE_INDEX_TO_ADDR(index); - - // Allow read and write - region_info.flags |= VFIO_REGION_INFO_FLAG_READ | VFIO_REGION_INFO_FLAG_WRITE; - - // Copy the updated info struct back to the user - ret_val = copy_to_user(argp, ®ion_info, bytes); - if (ret_val) - { - pr_err("%s.%u: Failed to copy from user space", __func__, __LINE__); - return -EFAULT; - } - - return 0; - } - case VFIO_DEVICE_GET_IRQ_INFO: - { - // Copy the argsz paramter from user space - ret_val = copy_from_user(&bytes, argp, sizeof(bytes)); - if (ret_val) - { - pr_err("%s.%u: Failed to copy from user space", __func__, __LINE__); - return -EFAULT; - } - - // Copy the dev_info struct - ret_val = copy_from_user(&irq_info, argp, bytes); - if (ret_val) - { - pr_err("%s.%u: Failed to copy from user space", __func__, __LINE__); - return -EFAULT; - } - - // This device supports MSIX interrupts - if (irq_info.index == VFIO_PCI_MSIX_IRQ_INDEX) - { - irq_info.flags = VFIO_IRQ_INFO_NORESIZE | VFIO_IRQ_INFO_EVENTFD; - - // Defined by a define in the header - irq_info.count = NUM_INTERRUPTS; - } - // All other types are not supported - else - { - irq_info.flags = 0; - irq_info.flags = 0; - } - - // Copy the updated info struct back to the user - ret_val = copy_to_user(argp, &irq_info, bytes); - if (ret_val) - { - pr_err("%s.%u: Failed to copy from user space", __func__, __LINE__); - return -EFAULT; - } - - return 0; - } - case VFIO_DEVICE_SET_IRQS: - { - // copy argz field - ret_val = copy_from_user(&bytes, argp, sizeof(bytes)); - if (ret_val) - { - dbg_info("Failed to copy from user space\n"); - return -EFAULT; - } - - // Allocate memory for addtional data - irq_set = kzalloc(bytes, GFP_KERNEL); - BUG_ON(!irq_set); - - // copy struct from user - ret_val = copy_from_user(irq_set, argp, bytes); - if (ret_val) - { - dbg_info("Failed to copy from user space\n"); - kfree(irq_set); - return -EFAULT; - } - - // Only MSIX interrupts are supported and needed by the - // guest driver. - switch (irq_set->index) - { - case VFIO_PCI_MSIX_IRQ_INDEX: - ret_val = handle_set_irq_msix(vfpga, irq_set); - break; - default: - // dbg_info("Tried to set IRQ! Flags: %x, Index: %u, Start: %u, Count: %u\n", - // irq_set->flags, irq_set->index, irq_set->start, irq_set->count); - break; - } - - kfree(irq_set); - return ret_val; - } - case VFIO_DEVICE_RESET: - { - return 0; - } - default: - { - return -EINVAL; - } - } -} - -/** - * @brief Allows mmap of BAR 0 and 4, and therefore enables - * direct pass through of the control registers. The vma struct contains an offset - * into the pci region and from this we can determine which BAR is mapped. - * BAR 0 and BAR 4 are seperated from each other. This allows to adjust sizes of - * these control registers later on without to much effort to change the hypervisor. - * - * @param mdev - * @param vma - * @return int - */ -int hypervisor_vfpga_mmap(struct mdev_device *mdev, struct vm_area_struct *vma) -{ - int region; - unsigned long vaddr; - unsigned long offset; - - struct m_fpga_dev *md; - struct fpga_dev *d; - struct bus_drvdata *pd; - - int ret_val; - - md = mdev_get_drvdata(mdev); - BUG_ON(!md); - d = md->fpga; - BUG_ON(!d); - pd = d->pd; - BUG_ON(!pd); - - // offset into the pci region - vaddr = vma->vm_pgoff << PAGE_SHIFT; - // get the region that should be mapped - region = COYOTE_GET_INDEX(vaddr); - // offset into this region - offset = vaddr & HYPERVISOR_OFFSET_MASK; - ret_val = 0; - - dbg_info("MMAP with vaddr %lu, VFIO region index %d, offset %lu\n", vaddr, region, offset); - - // Do not cache - vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); - - // Allow passthrough to hardware - if (region == VFIO_PCI_BAR0_REGION_INDEX) - { - // BAR0 is for the address ctrl registers - ret_val = remap_pfn_range(vma, vma->vm_start, d->fpga_phys_addr_ctrl >> PAGE_SHIFT, - FPGA_CTRL_SIZE, vma->vm_page_prot); - if (ret_val) - { - dbg_info("Failed to mmap BAR0.\n"); - } - else - { - dbg_info("Mapped addr %llx with size %u\n", d->fpga_phys_addr_ctrl, - FPGA_CTRL_SIZE); - } - } - else if (region == VFIO_PCI_BAR4_REGION_INDEX) - { - // BAR4 is for the avx address ctrl registers - ret_val = remap_pfn_range(vma, vma->vm_start, d->fpga_phys_addr_ctrl_avx >> PAGE_SHIFT, - FPGA_CTRL_CNFG_AVX_SIZE, vma->vm_page_prot); - if (ret_val) - { - dbg_info("Failed to mmap BAR4.\n"); - } - else - { - dbg_info("Mapped addr %llx with size %u\n", d->fpga_phys_addr_ctrl_avx, - FPGA_CTRL_CNFG_AVX_SIZE); - } - } - else - { - // Not allowed region - ret_val = -EINVAL; - } - - return ret_val; -} - -// -// -// START: This section contains static functions and arrays that display information 0x2000for the mediated devices in sysfs -// -// - -static ssize_t -info_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - - return sprintf(buf, "fpga region\n"); -} -static DEVICE_ATTR_RO(info); - -static struct attribute *dummy_attrs[] = { - &dev_attr_info.attr, - NULL, -}; - -static const struct attribute_group fpga_dev_group = { - .name = "vfpga", - .attrs = dummy_attrs, -}; - -const struct attribute_group *fpga_dev_groups[] = { - &fpga_dev_group, - NULL, -}; - -static ssize_t -vfpga_info_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct mdev_device *mdev = mdev_from_dev(dev); - struct m_fpga_dev *vfpga = mdev_get_drvdata(mdev); - - return sprintf(buf, "virtual vfpga device with id %d\n", vfpga->id); -} -DEVICE_ATTR_RO(vfpga_info); - -static struct attribute *vpga_attrs[] = { - &dev_attr_vfpga_info.attr, - NULL, -}; - -static const struct attribute_group vfpga_dev_group = { - .name = "vpga info", - .attrs = vpga_attrs, -}; - -const struct attribute_group *vfpga_dev_groups[] = { - &vfpga_dev_group, - NULL, -}; - -static ssize_t -name_show(struct device *dev, struct device_attribute *attrs, char *buf) -{ - return sprintf(buf, "%s-type\n", dev->kobj.name); -} -DEVICE_ATTR_RO(name); - -static ssize_t -device_api_show(struct device *dev, struct device_attribute *attrs, char *buf) -{ - return sprintf(buf, VFIO_DEVICE_API_PCI_STRING); -} -DEVICE_ATTR_RO(device_api); - -static struct attribute *vfpga_type_attrs[] = { - &dev_attr_name.attr, - &dev_attr_device_api.attr, - NULL, -}; - -// -// END -// - -/** - * @brief This function populates a mdev_parent_ops - * struct that is used to create a mediated type - * that is shown in the sysfs and provides all the callbacks - * used to manage all the devices - * - * @param vfpga virtual fpga region - * @return struct mdev_parent_ops* - */ -struct mdev_parent_ops *hypervisor_get_ops(struct fpga_dev *vfpga) -{ - struct mdev_parent_ops *ops; - struct attribute_group *type_group, **type_groups; - char *name; - - BUG_ON(!vfpga); - - // alloc type group - type_group = kzalloc(sizeof(struct attribute_group), GFP_KERNEL); - - if (!type_group) - { - dbg_info("Failed to allocate type group\n"); - goto err; - } - - // alloc name - name = kzalloc(64, GFP_KERNEL); - if (!name) - { - dbg_info("could not allocate name\n"); - goto err_name; - } - - // Create first and only type group - sprintf(name, "fpga_mdev.%d", vfpga->id); - type_group->name = name; - type_group->attrs = vfpga_type_attrs; - - // Create type groups array - type_groups = kzalloc(sizeof(struct attribute_group *) * 2, GFP_KERNEL); - if (!type_groups) - { - dbg_info("Could not allocate type group array\n"); - goto err_type_groups; - } - type_groups[0] = type_group; - type_groups[1] = NULL; - - ops = kzalloc(sizeof(struct mdev_parent_ops), GFP_KERNEL); - if (!ops) - { - dbg_info("could not allocate mdev parent ops\n"); - goto err_ops; - } - - // Set attributes - ops->owner = THIS_MODULE; - ops->dev_attr_groups = fpga_dev_groups; - ops->mdev_attr_groups = vfpga_dev_groups; - ops->supported_type_groups = type_groups; - - // Set handler functions - ops->open_device = hypervisor_vfpga_open; - ops->close_device = hypervisor_vfpga_close; - ops->create = hypervisor_vfpga_create; - ops->remove = hypervisor_vfpga_remove; - ops->write = hypervisor_vfpga_write; - ops->read = hypervisor_vfpga_read; - ops->ioctl = hypervisor_vfpga_ioctl; - ops->mmap = hypervisor_vfpga_mmap; - - dbg_info("created vfio-mdev operations\n"); - - return ops; - -err_ops: - kfree(type_groups); -err_type_groups: - kfree(name); -err_name: - kfree(type_group); -err: - return NULL; -} \ No newline at end of file diff --git a/driver/hypervisor/hypervisor_ops.h b/driver/hypervisor/hypervisor_ops.h deleted file mode 100644 index 042b4edc1..000000000 --- a/driver/hypervisor/hypervisor_ops.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2023, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#ifndef __HYPERVISOR_OPS_H__ -#define __HYPERVISOR_OPS_H__ - -#include "../coyote_dev.h" -#include "../fpga_fops.h" -#include "hypervisor_mmu.h" -#include "hypervisor_interrupts.h" - -struct mdev_parent_ops *hypervisor_get_ops(struct fpga_dev *vfpga); - -#endif \ No newline at end of file diff --git a/examples_hw/CMakeLists.txt b/examples_hw/CMakeLists.txt deleted file mode 100644 index baba026e5..000000000 --- a/examples_hw/CMakeLists.txt +++ /dev/null @@ -1,257 +0,0 @@ -cmake_minimum_required(VERSION 3.0) -project(test) - -set(CYT_DIR ${CMAKE_SOURCE_DIR}/../) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CYT_DIR}/cmake) - -find_package(CoyoteHW REQUIRED) - -# -# TEST -# - -# -# Static -# @brief: Default shell, contains interconnect and memory link testing circuits (perf_local) -# -# @note: Add a target device (FDEV_NAME) -# -# This is what every other shell is built against -# - -if(EXAMPLE STREQUAL "static") - set(BUILD_STATIC 1) - set(BUILD_SHELL 0) - set(BUILD_OPT 1) - set(N_REGIONS 4) - set(EN_STRM 1) - set(EN_MEM 1) - set(HBM_SPLIT 1) - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/perf_local" - VFPGA_C0_1 "apps/perf_local" - VFPGA_C0_2 "apps/perf_local" - VFPGA_C0_3 "apps/perf_local" - ) - - create_hw() -endif() - - -if(EXAMPLE STREQUAL "interrupts") - message("** User interrupts example") - set(N_REGIONS 1) - set(EN_STRM 1) - set(EN_MEM 0) - set(EN_PR 0) - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/user_interrupts" - ) - - create_hw() -endif() - -if(EXAMPLE STREQUAL "hls_vadd") - message("** HLS vector addition example") - set(N_REGIONS 1) - set(EN_STRM 1) - set(EN_MEM 0) - set(N_STRM_AXI 2) - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/hls_vadd" - ) - - create_hw() -endif() - -# -# Shells with examples -# - -# Application examples, streaming examples -# @brief: Streaming interface example -# The first application is gradient boosting decision trees inference - RTL integration example -# The second one is HyperLogLog cardinality estimation - HLS integration example -# -# @note: Floorplan provided for u55c only -> add manual app floorplans for other devices -# -if(EXAMPLE STREQUAL "streaming") - message("** Streaming examples: HyperLogLog (cardinality estimation), GBM decision trees (inference) and AES CBC mode") - set(SHELL_PROBE 1) - set(FDEV_NAME "u55c") - set(N_REGIONS 1) - set(EN_STRM 1) - set(EN_MEM 0) - set(EN_PR 1) - set(N_CONFIG 2) - set(FPLAN_PATH "${CMAKE_SOURCE_DIR}/floorplans/streaming_fplan_u55c.xdc") - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/hyperloglog" - VFPGA_C1_0 "apps/gbm_dtrees" - ) - - create_hw() -endif() - -# @brief Shell add app -# This is an example how a previous shell can be extended with additional application on the fly -# This means you don't have to recompile the whole shell, just this new apps will additionally compiled -# -# @note: Need to provide a path to the existing shell build dir ($SHELL_PATH) -# -if(EXAMPLE STREQUAL "streaming_add_aes") - message("** Streaming examples: Add AES to the previous two apps (app build flow)") - set(BUILD_APP 1) - set(BUILD_SHELL 0) - set(N_CONFIG 1) - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/aes_ecb" - ) - - create_hw() -endif() - -# @brief: AES encryption multithreading -# Running multiple threads within one vFPGA -# -# -if(EXAMPLE STREQUAL "multithreading") - message("** Streaming examples: Multithreading AES CBC") - set(SHELL_PROBE 2) - set(FDEV_NAME "u55c") - set(N_REGIONS 1) - set(EN_STRM 1) - set(N_STRM_AXI 8) - set(EN_MEM 0) - set(EN_PR 0) - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/aes_cbc" - ) - - create_hw() -endif() - - -# GPU example -# @brief: GPU perf test -# -# @note: Add a target device (FDEV_NAME) -# -if(EXAMPLE STREQUAL "perf_gpu") - message("** GPU perf example") - set(SHELL_PROBE 4) - set(N_REGIONS 1) - set(EN_MEM 1) - set(N_CARD_AXI 2) - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/kmeans" - VFPGA_C0_1 "apps/kmeans" - ) - - create_hw() -endif() - -# Network example, RDMA -# @brief: RDMA with AES encryption example -# -# @note: Add a target device (FDEV_NAME) -# -if(EXAMPLE STREQUAL "rdma_aes") - message("** RDMA AES-en(de)cryption test") - set(SHELL_PROBE 5) - set(N_REGIONS 2) - set(EN_STRM 1) - set(EN_MEM 1) - set(EN_RDMA 1) - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/rdma_base" - VFPGA_C0_1 "apps/rdma_aes" - ) - - create_hw() -endif() - -if(EXAMPLE STREQUAL "rdma_perf") - message("** RDMA perf test") - set(SHELL_PROBE 6) - set(N_REGIONS 1) - set(EN_STRM 1) - set(EN_RDMA 1) - set(N_STRM_AXI 2) - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/rdma_perf" - ) - - create_hw() -endif() - -# Network example, TCP iperf -# @brief: TCP iperf -# -# @note: Add a target device (FDEV_NAME) -# -if(EXAMPLE STREQUAL "tcp_iperf") - message("** TCP iperf") - set(SHELL_PROBE 7) - set(N_REGIONS 1) - set(EN_STRM 1) - set(EN_TCP 1) - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/tcp_iperf" - ) - - create_hw() -endif() - -# -# Perf FPGA -# @brief: Link benchmarks, FPGA initiated -# -# @note: Add a target device (FDEV_NAME) -# - -if(EXAMPLE STREQUAL "perf_fpga") - set(N_REGIONS 1) - set(EN_STRM 1) - set(N_STRM_AXI 2) - set(EN_MEM 0) # Should work with MEM as well - set(N_CARD_AXI 2) - - validation_checks_hw() - - load_apps ( - VFPGA_C0_0 "apps/perf_fpga" - ) - - create_hw() -endif() diff --git a/examples_hw/apps/gbm_dtrees/hdl/DTPackage.sv b/examples_hw/apps/gbm_dtrees/hdl/DTPackage.sv deleted file mode 100644 index 1e97d417c..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/DTPackage.sv +++ /dev/null @@ -1,91 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - - - -package DTPackage; - -parameter TREE_OFFSET_BITS = 10; -parameter TUPLE_OFFSET_BITS = 9; -parameter TREE_MEM_WIDTH_BITS = 1; -parameter MAX_TREE_DEPTH_BITS = 4; - -parameter NUM_PUS_PER_CLUSTER_BITS = 5; -parameter NUM_PUS_PER_CLUSTER = 28; -parameter NUM_DTPU_CLUSTERS = 4; -parameter NUM_DTPU_CLUSTERS_BITS = 2; -parameter NUM_TREES_PER_PU = 32; -parameter FEATURES_DISTR_DELAY = 8; -parameter DATA_PRECISION = 32; -parameter DATA_LINE_WIDTH = 64; - - -parameter TREE_MEM_READ_LATENCY = 2; -parameter DATA_MEM_READ_LATENCY = 2; - -parameter EMPTY_PIPELINE_WAIT_CYCLES = 128; - -parameter FP_ADDER_LATENCY = 2; - - - -typedef struct packed { - bit [TREE_OFFSET_BITS-1:0] tree_offset; - bit [TUPLE_OFFSET_BITS-1:0] tuple_offset; - bit last_tree; - bit empty_tree; -} TreeInstruction_t; - -typedef struct packed { - bit [TREE_OFFSET_BITS+TREE_MEM_WIDTH_BITS-1:0] node_address; - bit [TUPLE_OFFSET_BITS-1:0] tuple_offset; - bit [MAX_TREE_DEPTH_BITS-1:0] node_level; - bit empty_tree; - bit node_nop; - bit last_tree; - bit leaf_node; - - bit [31:0] node_res_val; -} NodeInstruction_t; - - - -typedef struct packed { - bit [3:0] split_dir; - bit [7:0] findex; - bit right_child; - bit left_child; - bit [1:0] op_type; -} NodeType_t; - -typedef struct packed { - bit [15:0] word_1_h; - bit [15:0] word_1_l; - bit [15:0] right_child_offset; - NodeType_t node_type; -} TreeNode_t; - - - -endpackage - diff --git a/examples_hw/apps/gbm_dtrees/hdl/DTProcessor.sv b/examples_hw/apps/gbm_dtrees/hdl/DTProcessor.sv deleted file mode 100644 index c4b148d26..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/DTProcessor.sv +++ /dev/null @@ -1,557 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - -/* - - The Core module is where computations happen - - core_data_in carry a stream of Trees/Data for processing in the core - - tuple_out_data carries the result of inference on one tuple, this can be - a partial result if not the complete model is stored in the core or the - full result if the complete model fits in the core. -*/ - -import DTPackage::*; - -module DTProcessor ( - input wire clk, - input wire rst_n, - // parameters - input wire start_core, - - input wire [5:0] tuple_length, - input wire [4:0] num_trees_per_pu_minus_one, - input wire [3:0] tree_depth, - input wire [8:0] num_lines_per_tuple, - // input trees - input wire [511:0] core_data_in, - input wire core_data_in_type, // 0: trees, 1: data - input wire core_data_in_valid, - input wire core_data_in_last, - output wire core_data_in_ready, - // output - input wire last_result_line, - input wire [15:0] last_result_line_mask, - output wire [511:0] core_result_out, - output wire core_result_valid, - input wire core_result_ready -); - - - -localparam IDLE = 1'b0, - RUN_MODE = 1'b1; - - -wire [511:0] ctrl_line; - -wire in_fifo_re; -wire in_fifo_full; -wire in_fifo_valid; -wire in_fifo_data_last; -wire in_fifo_data_type; -wire [511:0] in_fifo_data; - -reg tree_length_set; -reg [9:0] tree_received_words; -reg [9:0] curr_tree_length; - -wire [9:0] tree_possible_words; -wire [9:0] tree_remaining_words; -wire [9:0] curr_tree_line_words; -wire tree_data_in_last; -wire in_fifo_trees_re; - - -reg aligned_fifo_data_type_d1; -reg tuple_start_set; -reg [5:0] tuple_received_words; -reg [5:0] curr_tuple_off; - -wire [5:0] tuple_possible_words; -wire [5:0] tuple_remaining_words; -wire [5:0] curr_tuple_line_words; -wire tuple_data_in_last; -wire in_fifo_tuples_re; - -wire in_fifo_item_last; -wire [3:0] in_fifo_data_off; -wire [4:0] in_fifo_data_word_count; -wire [2:0] in_fifo_data_size; -wire [4:0] in_fifo_data_size_t; - -wire aligned_fifo_almfull; -wire aligned_fifo_valid; -wire aligned_fifo_re; -wire aligned_fifo_data_last; -wire aligned_fifo_data_type; - -wire [2:0] aligned_fifo_data_size; -wire [511:0] aligned_fifo_data; - -wire aligner_data_out_valid; -wire aligner_data_out_last; -wire aligner_data_out_type; -wire [2:0] aligner_data_out_size; -wire [511:0] aligner_data_out; - -reg core_fsm_state; -reg [NUM_DTPU_CLUSTERS_BITS-1:0] data_line_cu; -reg [4:0] data_line_pu; -reg [NUM_DTPU_CLUSTERS_BITS-1:0] data_line_cu_d1; -reg [4:0] data_line_pu_d1; -reg [511:0] data_line; -reg data_line_last; -reg [2:0] data_line_last_valid_pos; -reg data_line_valid; - -logic [511:0] data_line_array[NUM_DTPU_CLUSTERS:0]; -logic data_line_valid_array[NUM_DTPU_CLUSTERS:0]; -logic data_line_prog_array[NUM_DTPU_CLUSTERS:0]; -logic data_line_ctrl_array[NUM_DTPU_CLUSTERS:0]; -logic data_line_last_array[NUM_DTPU_CLUSTERS:0]; - -logic [2:0] data_line_last_valid_pos_array[NUM_DTPU_CLUSTERS:0]; -logic [4:0] data_line_pu_array[NUM_DTPU_CLUSTERS:0]; -logic [NUM_DTPU_CLUSTERS_BITS-1:0] data_line_cu_array[NUM_DTPU_CLUSTERS:0]; - -wire [NUM_DTPU_CLUSTERS-1:0] data_line_ready_array; - -wire [31:0] cu_tuple_result_out[NUM_DTPU_CLUSTERS-1:0]; -wire [NUM_DTPU_CLUSTERS-1:0] cu_tuple_result_out_valid; - -reg [3-NUM_DTPU_CLUSTERS_BITS:0] curr_dest_result_fifo[NUM_DTPU_CLUSTERS-1:0]; - -wire [15:0] res_fifo_we; -wire [511:0] res_fifo_dout; -wire [15:0] res_fifo_valid; -wire [15:0] res_fifo_full; -wire res_fifo_re; - - - -reg start_core_d1; - -reg [7:0] data_present_age; -reg last_tree_line_sent; -reg pipeline_emptied; -wire aligned_fifo_empty; - - -// Reset (handled badly within this core ...) -localparam integer N_RST_STG = 2; -logic [N_RST_STG:0] rst_n_int_s; -logic rst_n_int; -assign rst_n_int_s[0] = rst_n; -assign rst_n_int = rst_n_int_s[N_RST_STG]; - -always_ff @(posedge clk) begin - for(int i = 1; i <= N_RST_STG; i++) - rst_n_int_s[i] <= rst_n_int_s[i-1]; -end - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Core Input FIFO ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -assign ctrl_line = {480'b0, 7'b0, num_lines_per_tuple, 4'h0, tree_depth, 3'b0, num_trees_per_pu_minus_one}; - - -quick_fifo #(.FIFO_WIDTH(514), // data + data valid flag + last flag + prog flags - .FIFO_DEPTH_BITS(9), - .FIFO_ALMOSTFULL_THRESHOLD(508) - ) InDataFIFO - ( - .clk (clk), - .reset_n (rst_n_int), - .din ({core_data_in_last, core_data_in_type, core_data_in}), - .we (core_data_in_valid), - - .re (in_fifo_re), - .dout ({in_fifo_data_last, in_fifo_data_type, in_fifo_data}), - .empty (), - .valid (in_fifo_valid), - .full (in_fifo_full), - .count (), - .almostfull () - ); - - -assign core_data_in_ready = ~in_fifo_full; -assign in_fifo_re = ~aligned_fifo_almfull && ((in_fifo_data_type)? in_fifo_tuples_re && pipeline_emptied : in_fifo_trees_re ); -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Tracking Trees/Tuples ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -// Trees input stream decoding -always@(posedge clk) begin - // buffer start signal - start_core_d1 <= start_core; - - // Reset the rest - if(~rst_n_int || start_core) begin - tree_length_set <= 1'b0; - tree_received_words <= 10'd0; - curr_tree_length <= 10'd0; - last_tree_line_sent <= 1'b0; - data_present_age <= 0; - end - else begin - if(in_fifo_valid && ~aligned_fifo_almfull && !in_fifo_data_type) begin - if(!tree_length_set) begin - curr_tree_length <= in_fifo_data[9:0]; - if(in_fifo_data[9:0] > 15) begin - tree_length_set <= 1'b1; - tree_received_words <= 10'd15; - end - end - else if( tree_data_in_last ) begin - tree_length_set <= 1'b0; - tree_received_words <= 10'd0; - end - else begin - tree_received_words <= tree_received_words + 10'd16; - end - if(in_fifo_data_last) begin - last_tree_line_sent <= 1'b1; - end - end - // - if(last_tree_line_sent) begin - data_present_age <= data_present_age + 1'b1; - end - end -end -// -assign tree_possible_words = (tree_length_set)? 16 : 15; -assign tree_remaining_words = (tree_length_set)? (curr_tree_length - tree_received_words) : in_fifo_data[9:0]; -assign curr_tree_line_words = (tree_remaining_words > tree_possible_words)? tree_possible_words : tree_remaining_words; -assign tree_data_in_last = (tree_remaining_words > tree_possible_words)? 1'b0 : 1'b1; -assign in_fifo_trees_re = 1'b1; - -// Tuples input stream decoding -always@(posedge clk) begin - if(~rst_n_int || start_core) begin - tuple_received_words <= 6'd0; - tuple_start_set <= 1'b0; - curr_tuple_off <= 6'd0; - - pipeline_emptied <= 1'b0; - end - else begin - if(in_fifo_valid && ~aligned_fifo_almfull && in_fifo_data_type && pipeline_emptied) begin - if(!tuple_start_set) begin - if( (6'd16 - curr_tuple_off[3:0]) < tuple_length ) begin - tuple_start_set <= 1'b1; - tuple_received_words <= 6'd16 - curr_tuple_off[3:0]; - end - end - else if( tuple_data_in_last ) begin - tuple_start_set <= 1'b0; - tuple_received_words <= 6'd0; - end - else begin - tuple_received_words <= tuple_received_words + 6'd16; - end - // Tuple offset - if( tuple_data_in_last ) begin - curr_tuple_off <= curr_tuple_off + tuple_length; - end - end - // - if((data_present_age > EMPTY_PIPELINE_WAIT_CYCLES) && aligned_fifo_empty) begin - pipeline_emptied <= 1'b1; - end - end -end - - -assign tuple_possible_words = (tuple_start_set)? 6'd16 : (6'd16 - curr_tuple_off[3:0]); -assign tuple_remaining_words = tuple_length - tuple_received_words; -assign curr_tuple_line_words = (tuple_remaining_words > tuple_possible_words)? tuple_possible_words : tuple_remaining_words; -assign tuple_data_in_last = (tuple_remaining_words > tuple_possible_words)? 1'b0 : 1'b1; -assign in_fifo_tuples_re = (tuple_data_in_last)? (tuple_remaining_words == 6'd16) : 1'b1; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Bus Aligner ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -assign in_fifo_item_last = (in_fifo_data_type)? tuple_data_in_last : tree_data_in_last; -assign in_fifo_data_off = (in_fifo_data_type)? curr_tuple_off[3:0] : 4'd1; -assign in_fifo_data_size_t = ((in_fifo_data_type)? tuple_length[4:0] : ((tree_length_set)? curr_tree_length[4:0] : in_fifo_data[4:0])) - 5'd1; -assign in_fifo_data_size = in_fifo_data_size_t[3:1]; -assign in_fifo_data_word_count = (in_fifo_data_type)? curr_tuple_line_words : curr_tree_line_words; - -bus_aligner bus_aligner - ( - .clk (clk), - .rst_n (rst_n_int), - - .data_in (in_fifo_data), - .data_in_last (in_fifo_item_last), - .data_in_type (in_fifo_data_type), - .data_in_valid (in_fifo_valid && ~aligned_fifo_almfull && (~in_fifo_data_type || pipeline_emptied)), - .data_in_off (in_fifo_data_off), - .data_in_size (in_fifo_data_size), - .data_in_word_count (in_fifo_data_word_count), - .stream_last (in_fifo_data_last), - - .data_out (aligner_data_out), - .data_out_last (aligner_data_out_last), - .data_out_type (aligner_data_out_type), - .data_out_size (aligner_data_out_size), - .data_out_valid (aligner_data_out_valid) - ); - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Aligned Data FIFO ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -quick_fifo #(.FIFO_WIDTH(517), // data + data valid flag + last flag + prog flags - .FIFO_DEPTH_BITS(9), - .FIFO_ALMOSTFULL_THRESHOLD(490) - ) AlignedDataFIFO - ( - .clk (clk), - .reset_n (rst_n_int), - .din ({aligner_data_out_last, aligner_data_out_type, aligner_data_out_size, aligner_data_out}), - .we (aligner_data_out_valid), - - .re (aligned_fifo_re), - .dout ({aligned_fifo_data_last, aligned_fifo_data_type, aligned_fifo_data_size, aligned_fifo_data}), - .empty (aligned_fifo_empty), - .valid (aligned_fifo_valid), - .full (), - .count (), - .almostfull (aligned_fifo_almfull) - ); - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Distribute Trees/Tuples ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -always@(posedge clk) begin - if(~rst_n_int || start_core) begin - core_fsm_state <= IDLE; - data_line_cu <= 0; - data_line_pu <= 5'd0; - data_line_cu_d1 <= 0; - data_line_pu_d1 <= 0; - data_line_last_valid_pos <= 4'd0; - data_line_valid <= 1'b0; - data_line_last <= 1'b0; - aligned_fifo_data_type_d1<= 1'b0; - end - else begin - // - data_line_last_valid_pos <= aligned_fifo_data_size; - data_line_valid <= aligned_fifo_valid; - data_line_last <= aligned_fifo_data_last; - aligned_fifo_data_type_d1<= aligned_fifo_data_type; - data_line_cu_d1 <= data_line_cu; - data_line_pu_d1 <= data_line_pu; - // - case (core_fsm_state) - IDLE: begin - data_line_cu <= 0; - data_line_pu <= 5'd0; - - if(start_core_d1) begin - core_fsm_state <= RUN_MODE; - end - end - RUN_MODE: begin - if(aligned_fifo_valid && aligned_fifo_re) begin - if(aligned_fifo_data_type == 1'b0) begin // trees stream - // PU - if(aligned_fifo_data_last) begin - if(data_line_pu == NUM_PUS_PER_CLUSTER-1) begin - data_line_pu <= 5'd0; - end - else begin - data_line_pu <= data_line_pu + 5'd1; - end - end - // CU - data_line_cu <= 0; - end - else begin // tuples stream - // PU - data_line_pu <= 5'd0; - // CU - if(aligned_fifo_data_last) begin - if(data_line_cu == NUM_DTPU_CLUSTERS-1) begin - data_line_cu <= 0; - end - else begin - data_line_cu <= data_line_cu + 1; - end - end - end - end - end - default : begin - core_fsm_state <= IDLE; - data_line_cu <= 0; - data_line_pu <= 5'd0; - end - endcase - end - // - data_line <= aligned_fifo_data; - // -end -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Engine Clusters ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -/*assign data_line_array[0] = (core_fsm_state == IDLE)? ctrl_line : data_line; -assign data_line_valid_array[0] = (core_fsm_state == IDLE)? 1'b0 : data_line_valid && aligned_fifo_data_type_d1 && aligned_fifo_re; -assign data_line_last_valid_pos_array[0] = (core_fsm_state == IDLE)? 3'b0 : data_line_last_valid_pos; -assign data_line_prog_array[0] = (core_fsm_state == IDLE)? 1'b0 : data_line_valid && ~aligned_fifo_data_type_d1 && aligned_fifo_re; -assign data_line_last_array[0] = (core_fsm_state == IDLE)? start_core : data_line_last; -assign data_line_ctrl_array[0] = (core_fsm_state == IDLE)? start_core : 1'b0; -assign data_line_pu_array[0] = data_line_pu_d1; -assign data_line_cu_array[0] = data_line_cu_d1; -*/ - -always@(posedge clk) begin - if(~rst_n_int) begin - data_line_ctrl_array[0] <= 0; - data_line_prog_array[0] <= 0; - data_line_valid_array[0] <= 0; - end - else begin - data_line_ctrl_array[0] <= start_core; - data_line_prog_array[0] <= (start_core || (core_fsm_state == IDLE))? 1'b0 : aligned_fifo_valid && ~aligned_fifo_data_type && aligned_fifo_re; - data_line_valid_array[0] <= (start_core || (core_fsm_state == IDLE))? 1'b0 : aligned_fifo_valid && aligned_fifo_data_type && aligned_fifo_re; - end - // - data_line_array[0] <= (start_core)? ctrl_line : aligned_fifo_data; - data_line_last_valid_pos_array[0] <= (start_core)? 3'b0 : aligned_fifo_data_size; - data_line_last_array[0] <= start_core | aligned_fifo_data_last; - - data_line_pu_array[0] <= data_line_pu; - data_line_cu_array[0] <= data_line_cu; -end - -assign aligned_fifo_re = (aligned_fifo_data_type)? data_line_ready_array[ data_line_cu ] : data_line_ready_array[0]; - - -genvar i; -generate - for (i = 0; i < NUM_DTPU_CLUSTERS; i = i + 1) begin: cus - compute_unit #(.CU_ID (i) ) - cu_x( - .clk (clk), - .rst_n (rst_n_int && ~start_core), - - .data_line_in (data_line_array[i]), - .data_line_in_valid (data_line_valid_array[i]), - .data_line_in_last_valid_pos (data_line_last_valid_pos_array[i]), - .data_line_in_last (data_line_last_array[i]), - .data_line_in_ctrl (data_line_ctrl_array[i]), - .data_line_in_prog (data_line_prog_array[i]), - .data_line_in_pu (data_line_pu_array[i]), - .data_line_in_cu (data_line_cu_array[i]), - .data_line_in_ready (data_line_ready_array[i]), - - .data_line_out (data_line_array[i+1]), - .data_line_out_valid (data_line_valid_array[i+1]), - .data_line_out_last_valid_pos (data_line_last_valid_pos_array[i+1]), - .data_line_out_last (data_line_last_array[i+1]), - .data_line_out_ctrl (data_line_ctrl_array[i+1]), - .data_line_out_prog (data_line_prog_array[i+1]), - .data_line_out_pu (data_line_pu_array[i+1]), - .data_line_out_cu (data_line_cu_array[i+1]), - - .tuple_result_out (cu_tuple_result_out[i]), - .tuple_result_out_valid (cu_tuple_result_out_valid[i]), - .tuple_result_out_ready ( ~res_fifo_full[i] ) - ); - - - // - always@(posedge clk) begin - if(~rst_n_int) begin - curr_dest_result_fifo[i] <= 0; - end - else begin - if(cu_tuple_result_out_valid[i]) begin - if(~res_fifo_full[ ({curr_dest_result_fifo[i], {NUM_DTPU_CLUSTERS_BITS{1'b0}}} + i) ]) begin - curr_dest_result_fifo[i] <= curr_dest_result_fifo[i] + 1'b1; - end - end - end -end - end -endgenerate -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Push Results Out ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -generate - for (i = 0; i < 16; i = i + 1) begin: out_fifos - quick_fifo #(.FIFO_WIDTH(32), // data - .FIFO_DEPTH_BITS(9), - .FIFO_ALMOSTFULL_THRESHOLD(490) ) - ResultsFIFO_x( - .clk (clk), - .reset_n (rst_n_int), - .din ( cu_tuple_result_out[i%NUM_DTPU_CLUSTERS] ), - .we ( res_fifo_we[i] ), - - .re (res_fifo_re), - .dout (res_fifo_dout[32*i+31:i*32]), - .empty (), - .valid (res_fifo_valid[i]), - .full (res_fifo_full[i]), - .count (), - .almostfull () - ); - - assign res_fifo_we[i] = cu_tuple_result_out_valid[i%NUM_DTPU_CLUSTERS] && (curr_dest_result_fifo[i%NUM_DTPU_CLUSTERS] == (i/NUM_DTPU_CLUSTERS)); - - end -endgenerate - -assign res_fifo_re = core_result_ready && core_result_valid; -assign core_result_out = res_fifo_dout; -assign core_result_valid = &res_fifo_valid || ((&(res_fifo_valid | last_result_line_mask)) && last_result_line) ; - - -endmodule diff --git a/examples_hw/apps/gbm_dtrees/hdl/Data_Memory.sv b/examples_hw/apps/gbm_dtrees/hdl/Data_Memory.sv deleted file mode 100644 index ee541e4bf..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/Data_Memory.sv +++ /dev/null @@ -1,73 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - -module Data_Memory #( - parameter DATA_WIDTH = 32, - parameter ADDR_WIDTH = 8 -) ( - input wire clk, - input wire rst_n, - input wire we, - input wire re, - input wire [ADDR_WIDTH-1:0] raddr, - input wire [ADDR_WIDTH-2:0] waddr, - input wire [DATA_WIDTH-1:0] din, - output reg [31:0] dout, - output reg valid_out -); - -wire [DATA_WIDTH-1:0] dline; -reg raddr_d1; -reg re_d1; - - - -dual_port_mem Dualport_mem_inst ( - .clk ( clk ), - .da ( din ), - .wea ( we), - .ena ( we), - .addra ( waddr ), - - .web (1'b0), - .addrb ( raddr[ADDR_WIDTH-1:1] ), - .enb ( re ), - .qb ( dline ) - ); - - -always @(posedge clk) begin - raddr_d1 <= raddr[0]; - re_d1 <= re; - dout <= (raddr_d1)? dline[63:32] : dline[31:0]; - - if(~rst_n) begin - valid_out <= 1'b0; - end - else begin - valid_out <= re_d1; - end -end - -endmodule - diff --git a/examples_hw/apps/gbm_dtrees/hdl/FPAdder_2cycles_latency.sv b/examples_hw/apps/gbm_dtrees/hdl/FPAdder_2cycles_latency.sv deleted file mode 100644 index 178202d4e..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/FPAdder_2cycles_latency.sv +++ /dev/null @@ -1,389 +0,0 @@ -// File 2cycles_latency.vhdl translated with vhd2vl v2.4 VHDL to Verilog RTL translator -// vhd2vl settings: -// * Verilog Module Declaration Style: 1995 - -// vhd2vl is Free (libre) Software: -// Copyright (C) 2001 Vincenzo Liguori - Ocean Logic Pty Ltd -// http://www.ocean-logic.com -// Modifications Copyright (C) 2006 Mark Gonzales - PMC Sierra Inc -// Modifications (C) 2010 Shankar Giri -// Modifications Copyright (C) 2002, 2005, 2008-2010 Larry Doolittle - LBNL -// http://doolittle.icarus.com/~larry/vhd2vl/ -// -// vhd2vl comes with ABSOLUTELY NO WARRANTY. Always check the resulting -// Verilog for correctness, ideally with a formal verification tool. -// -// You are welcome to redistribute vhd2vl under certain conditions. -// See the license (GPLv2) file included with the source for details. - -// The result of translation follows. Its copyright status should be -// considered unchanged from the original VHDL. - -//------------------------------------------------------------------------------ -// FPAdder_8_23_uid2_RightShifter -// (RightShifter_24_by_max_26_uid4) -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Florent de Dinechin, Bogdan Pasca (2007,2008,2009,2010) -//------------------------------------------------------------------------------ -// no timescale needed - -module FPAdder_8_23_uid2_RightShifter_l2( -X, -S, -R -); - -input [23:0] X; -input [4:0] S; -output [49:0] R; - -wire clk; -wire rst; -wire [23:0] X; -wire [4:0] S; -wire [49:0] R; - - -wire [23:0] level0; -wire [4:0] ps; -wire [24:0] level1; -wire [26:0] level2; -wire [30:0] level3; -wire [38:0] level4; -wire [54:0] level5; - - - assign level0 = X; - assign ps = S; - assign level1 = ps[0] == 1'b1 ? {1'b0,level0} : {level0,1'b0}; - assign level2 = ps[1] == 1'b1 ? {2'b00,level1} : {level1,2'b00}; - assign level3 = ps[2] == 1'b1 ? {4'b0000,level2} : {level2,4'b0000}; - assign level4 = ps[3] == 1'b1 ? {8'b00000000,level3} : {level3,8'b00000000}; - assign level5 = ps[4] == 1'b1 ? {16'b0000000000000000,level4} : {level4,16'b0000000000000000}; - assign R = level5[54:5]; - -endmodule - -//------------------------------------------------------------------------------ -// IntAdder_27_f110_uid6 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Bogdan Pasca, Florent de Dinechin (2008-2010) -//------------------------------------------------------------------------------ -// Pipeline depth: 0 cycles -// no timescale needed - -module IntAdder_27_f110_uid6_l2( -X, -Y, -Cin, -R -); - -input [26:0] X; -input [26:0] Y; -input Cin; -output [26:0] R; - -wire clk; -wire rst; -wire [26:0] X; -wire [26:0] Y; -wire Cin; -wire [26:0] R; - - //Alternative - assign R = X + Y + Cin; -//------------------------------------------------------------------------------ -// LZCShifter_28_to_28_counting_32_uid16 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Florent de Dinechin, Bogdan Pasca (2007) -//------------------------------------------------------------------------------ -// Pipeline depth: 1 cycles - -endmodule - -module LZCShifter_28_to_28_counting_32_uid16_l2( -clk, -stall, -I, -Count, -O -); - -input clk, stall; -input [27:0] I; -output [4:0] Count; -output [27:0] O; - -wire clk; -wire rst; -wire [27:0] I; -wire [4:0] Count; -wire [27:0] O; - - -wire [27:0] level5; -wire count4; reg count4_d1; -wire [27:0] level4; -wire count3; reg count3_d1; -wire [27:0] level3; -wire count2; reg count2_d1; -wire [27:0] level2; reg [27:0] level2_d1; -wire count1; -wire [27:0] level1; -wire count0; -wire [27:0] level0; -wire [4:0] sCount; - - always @(posedge clk) begin - if( ~stall ) begin - count4_d1 <= count4; - count3_d1 <= count3; - count2_d1 <= count2; - level2_d1 <= level2; - end - end - - assign level5 = I; - assign count4 = level5[27:12] == 16'b0000000000000000 ? 1'b1 : 1'b0; - assign level4 = count4 == 1'b0 ? level5[27:0] : {level5[11:0],16'b0000000000000000}; - assign count3 = level4[27:20] == 8'b00000000 ? 1'b1 : 1'b0; - assign level3 = count3 == 1'b0 ? level4[27:0] : {level4[19:0],8'b00000000}; - assign count2 = level3[27:24] == 4'b0000 ? 1'b 1 : 1'b0; - assign level2 = count2 == 1'b0 ? level3[27:0] : {level3[23:0],4'b0000}; - //--------------Synchro barrier, entering cycle 1---------------- - assign count1 = level2_d1[27:26] == 2'b00 ? 1'b1 : 1'b0; - assign level1 = count1 == 1'b0 ? level2_d1[27:0] : {level2_d1[25:0],2'b00}; - assign count0 = level1[27:27] == 1'b0 ? 1'b1 : 1'b0; - assign level0 = count0 == 1'b0 ? level1[27:0] : {level1[26:0],1'b0}; - assign O = level0; - assign sCount = {count4_d1,count3_d1,count2_d1,count1,count0}; - assign Count = sCount; -//------------------------------------------------------------------------------ -// IntAdder_34_f110_uid18 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Bogdan Pasca, Florent de Dinechin (2008-2010) -//------------------------------------------------------------------------------ -// Pipeline depth: 0 cycles - -endmodule - -module IntAdder_34_f110_uid18_l2( -X, -Y, -Cin, -R -); - - -input [33:0] X; -input [33:0] Y; -input Cin; -output [33:0] R; - -wire [33:0] X; -wire [33:0] Y; -wire Cin; -wire [33:0] R; - - //Alternative - assign R = X + Y + Cin; -//------------------------------------------------------------------------------ -// FPAdder_8_23_uid2 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Bogdan Pasca, Florent de Dinechin (2010) -//------------------------------------------------------------------------------ -// Pipeline depth: 2 cycles - -endmodule - -module FPAdder_8_23_uid2_l2( -clk, -rst, -seq_stall, -X, -Y, -R -); - -input clk, rst; -input seq_stall; -input [8 + 23 + 2:0] X; -input [8 + 23 + 2:0] Y; -output [8 + 23 + 2:0] R; - -wire clk; -wire rst; -wire [8 + 23 + 2:0] X; -wire [8 + 23 + 2:0] Y; -wire [8 + 23 + 2:0] R; - - -wire [32:0] excExpFracX; -wire [32:0] excExpFracY; -wire [8:0] eXmeY; -wire [8:0] eYmeX; -wire swap; -wire [33:0] newX; reg [33:0] newX_d1; -wire [33:0] newY; -wire [7:0] expX; reg [7:0] expX_d1; -wire [1:0] excX; -wire [1:0] excY; -wire signX; -wire signY; -wire EffSub; reg EffSub_d1; reg EffSub_d2; -wire [5:0] sdsXsYExnXY; -wire [3:0] sdExnXY; -wire [23:0] fracY; -reg [1:0] excRt; reg [1:0] excRt_d1; reg [1:0] excRt_d2; -wire signR; reg signR_d1; reg signR_d2; -wire [8:0] expDiff; -wire shiftedOut; -wire [4:0] shiftVal; -wire [49:0] shiftedFracY; reg [49:0] shiftedFracY_d1; -wire sticky; -wire [26:0] fracYfar; -wire [26:0] fracYfarXorOp; -wire [26:0] fracXfar; -wire cInAddFar; -wire [26:0] fracAddResult; -wire [27:0] fracGRS; -wire [9:0] extendedExpInc; reg [9:0] extendedExpInc_d1; -wire [4:0] nZerosNew; -wire [27:0] shiftedFrac; -wire [9:0] updatedExp; -wire eqdiffsign; -wire [33:0] expFrac; -wire stk; -wire rnd; -wire grd; -wire lsb; -wire addToRoundBit; -wire [33:0] RoundedExpFrac; -wire [1:0] upExc; -wire [22:0] fracR; -wire [7:0] expR; -wire [3:0] exExpExc; -reg [1:0] excRt2; -wire [1:0] excR; -wire [33:0] computedR; - - always @(posedge clk) begin - if( ~seq_stall)begin - newX_d1 <= newX; - expX_d1 <= expX; - EffSub_d1 <= EffSub; - EffSub_d2 <= EffSub_d1; - excRt_d1 <= excRt; - excRt_d2 <= excRt_d1; - signR_d1 <= signR; - signR_d2 <= signR_d1; - shiftedFracY_d1 <= shiftedFracY; - extendedExpInc_d1 <= extendedExpInc; - end - end - - // Exponent difference and swap -- - assign excExpFracX = {X[33:32],X[30:0]}; - assign excExpFracY = {Y[33:32],Y[30:0]}; - assign eXmeY = ({1'b 0,X[30:23]}) - ({1'b 0,Y[30:23]}); - assign eYmeX = ({1'b 0,Y[30:23]}) - ({1'b 0,X[30:23]}); - assign swap = excExpFracX >= excExpFracY ? 1'b 0 : 1'b 1; - assign newX = swap == 1'b0 ? X : Y; - assign newY = swap == 1'b0 ? Y : X; - assign expX = newX[30:23]; - assign excX = newX[33:32]; - assign excY = newY[33:32]; - assign signX = newX[31]; - assign signY = newY[31]; - assign EffSub = signX ^ signY; - assign sdsXsYExnXY = {signX,signY,excX,excY}; - assign sdExnXY = {excX,excY}; - assign fracY = excY == 2'b00 ? 24'b000000000000000000000000 : {1'b1,newY[22:0]}; - always @(*) begin - case(sdsXsYExnXY) - 6'b000000,6'b010000,6'b100000,6'b110000 : excRt <= 2'b00; - 6'b000101,6'b010101,6'b100101,6'b110101,6'b000100,6'b010100,6'b100100,6'b110100,6'b000001,6'b010001,6'b100001,6'b110001 : excRt <= 2'b01; - 6'b111010,6'b001010,6'b001000,6'b011000,6'b101000,6'b111000,6'b000010,6'b010010,6'b100010,6'b110010,6'b001001,6'b011001,6'b101001,6'b111001,6'b000110,6'b010110,6'b100110,6'b110110 : excRt <= 2'b10; - default : excRt <= 2'b 11; - endcase - end - - assign signR = (sdsXsYExnXY == 6'b100000 || sdsXsYExnXY == 6'b010000) ? 1'b0 : signX; - //-------------- cycle 0---------------- - assign expDiff = swap == 1'b0 ? eXmeY : eYmeX; - assign shiftedOut = (expDiff >= 25) ? 1'b1 : 1'b0; - assign shiftVal = shiftedOut == 1'b0 ? expDiff[4:0] : 5'b11010; - FPAdder_8_23_uid2_RightShifter_l2 RightShifterComponent( - .R(shiftedFracY), - .S(shiftVal), - .X(fracY)); - - //--------------Synchro barrier, entering cycle 1---------------- - assign sticky = (shiftedFracY_d1[23:0] == 23'b00000000000000000000000) ? 1'b0 : 1'b1; - //-------------- cycle 0---------------- - //--------------Synchro barrier, entering cycle 1---------------- - assign fracYfar = {1'b 0,shiftedFracY_d1[49:24]}; - assign fracYfarXorOp = fracYfar ^ ({EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1}); - assign fracXfar = {2'b01,(newX_d1[22:0]),2'b00}; - assign cInAddFar = EffSub_d1 & ~sticky; - IntAdder_27_f110_uid6_l2 fracAdder( - .Cin(cInAddFar), - .R(fracAddResult), - .X(fracXfar), - .Y(fracYfarXorOp)); - - assign fracGRS = {fracAddResult,sticky}; - assign extendedExpInc = ({2'b00,expX_d1}) + 1'b1; - LZCShifter_28_to_28_counting_32_uid16_l2 LZC_component( - .clk(clk), - .stall(seq_stall), - .Count(nZerosNew), - .I(fracGRS), - .O(shiftedFrac)); - - //--------------Synchro barrier, entering cycle 2---------------- - assign updatedExp = extendedExpInc_d1 - ({5'b00000,nZerosNew}); - assign eqdiffsign = nZerosNew == 5'b11111 ? 1'b1 : 1'b0; - assign expFrac = {updatedExp,shiftedFrac[26:3]}; - //-------------- cycle 2---------------- - assign stk = shiftedFrac[1] | shiftedFrac[0]; - assign rnd = shiftedFrac[2]; - assign grd = shiftedFrac[3]; - assign lsb = shiftedFrac[4]; - assign addToRoundBit = (lsb == 1'b0 && grd == 1'b1 && rnd == 1'b0 && stk == 1'b0) ? 1'b0 : 1'b1; - IntAdder_34_f110_uid18_l2 roundingAdder( - .Cin(addToRoundBit), - .R(RoundedExpFrac), - .X(expFrac), - .Y(34'b0000000000000000000000000000000000)); - - //-------------- cycle 2---------------- - assign upExc = RoundedExpFrac[33:32]; - assign fracR = RoundedExpFrac[23:1]; - assign expR = RoundedExpFrac[31:24]; - assign exExpExc = {upExc,excRt_d2}; - always @(*) begin - case((exExpExc)) - 4'b0000,4'b0100,4'b1000,4'b1100,4'b1001,4'b1101 : excRt2 <= 2'b00; - 4'b0001 : excRt2 <= 2'b01; - 4'b0010,4'b0110,4'b0101 : excRt2 <= 2'b10; - default : excRt2 <= 2'b11; - endcase - end - - assign excR = (eqdiffsign == 1'b1 && EffSub_d2 == 1'b1) ? 2'b00 : excRt2; - assign computedR = {excR,signR_d2,expR,fracR}; - assign R = computedR; - -endmodule diff --git a/examples_hw/apps/gbm_dtrees/hdl/FPAdder_3cycles_latency.v b/examples_hw/apps/gbm_dtrees/hdl/FPAdder_3cycles_latency.v deleted file mode 100644 index ab8c0e7a6..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/FPAdder_3cycles_latency.v +++ /dev/null @@ -1,458 +0,0 @@ -//------------------------------------------------------------------------------ -// FPAdder_8_23_uid2_RightShifter -// (RightShifter_24_by_max_26_uid4) -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Florent de Dinechin, Bogdan Pasca (2007,2008,2009,2010) -//------------------------------------------------------------------------------ -// no timescale needed - -module FPAdder_8_23_uid2_RightShifter_l3( -clk, -rst, -X, -S, -R -); - -input clk, rst; -input [23:0] X; -input [4:0] S; -output [49:0] R; - -wire clk; -wire rst; -wire [23:0] X; -wire [4:0] S; -wire [49:0] R; - - -wire [23:0] level0; -wire [4:0] ps; -wire [24:0] level1; -wire [26:0] level2; -wire [30:0] level3; -wire [38:0] level4; -wire [54:0] level5; - - - assign level0 = X; - assign ps = S; - assign level1 = ps[0] == 1'b1 ? {1'b0,level0} : {level0,1'b0}; - assign level2 = ps[1] == 1'b1 ? {2'b00,level1} : {level1,2'b00}; - assign level3 = ps[2] == 1'b1 ? {4'b0000,level2} : {level2,4'b0000}; - assign level4 = ps[3] == 1'b1 ? {8'b00000000,level3} : {level3,8'b00000000}; - assign level5 = ps[4] == 1'b1 ? {16'b0000000000000000,level4} : {level4,16'b0000000000000000}; - assign R = level5[54:5]; - -endmodule - -//------------------------------------------------------------------------------ -// IntAdder_27_f150_uid6 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Bogdan Pasca, Florent de Dinechin (2008-2010) -//------------------------------------------------------------------------------ -// Pipeline depth: 0 cycles -// no timescale needed - -module IntAdder_27_f150_uid6_l3( -clk, -rst, -X, -Y, -Cin, -R -); - -input clk, rst; -input [26:0] X; -input [26:0] Y; -input Cin; -output [26:0] R; - -wire clk; -wire rst; -wire [26:0] X; -wire [26:0] Y; -wire Cin; -wire [26:0] R; - - - //Alternative - assign R = X + Y + Cin; - -endmodule - -//------------------------------------------------------------------------------ -// LZCShifter_28_to_28_counting_32_uid16 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Florent de Dinechin, Bogdan Pasca (2007) -//------------------------------------------------------------------------------ -// Pipeline depth: 1 cycles -// no timescale needed - -module LZCShifter_28_to_28_counting_32_uid16_l3( -clk, -rst, -I, -Count, -O -); - -input clk, rst; -input [27:0] I; -output [4:0] Count; -output [27:0] O; - -wire clk; -wire rst; -wire [27:0] I; -wire [4:0] Count; -wire [27:0] O; -wire [27:0] level5; - -wire count4; -//reg count4_d1; -wire count4_d1; - -wire [27:0] level4; -//reg [27:0] level4_d1; -wire [27:0] level4_d1; - -wire count3; -//reg count3_d1; -wire count3_d1; - -wire [27:0] level3; -wire count2; -wire [27:0] level2; -wire count1; -wire [27:0] level1; -wire count0; -wire [27:0] level0; -wire [4:0] sCount; - - assign level5 = I; - assign count4 = level5[27:12] == 16'b0000000000000000 ? 1'b1 : 1'b0; - assign level4 = count4 == 1'b0 ? level5[27:0] : {level5[11:0],16'b0000000000000000}; - assign count3 = level4[27:20] == 8'b00000000 ? 1'b1 : 1'b0; - //--------------Synchro barrier, entering cycle 1---------------- - - /* Added by Babis */ - assign count4_d1 = count4; - assign level4_d1 = level4; - assign count3_d1 = count3; - - assign level3 = count3_d1 == 1'b0 ? level4_d1[27:0] : {level4_d1[19:0],8'b00000000}; - assign count2 = level3[27:24] == 4'b0000 ? 1'b1 : 1'b0; - assign level2 = count2 == 1'b0 ? level3[27:0] : {level3[23:0],4'b0000}; - assign count1 = level2[27:26] == 2'b00 ? 1'b1 : 1'b0; - assign level1 = count1 == 1'b0 ? level2[27:0] : {level2[25:0],2'b00}; - assign count0 = level1[27:27] == 1'b0 ? 1'b1 : 1'b0; - assign level0 = count0 == 1'b0 ? level1[27:0] : {level1[26:0],1'b0}; - assign O = level0; - assign sCount = {count4_d1,count3_d1,count2,count1,count0}; - assign Count = sCount; - -endmodule - -//------------------------------------------------------------------------------ -// IntAdder_34_f150_uid18 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Bogdan Pasca, Florent de Dinechin (2008-2010) -//------------------------------------------------------------------------------ -// Pipeline depth: 0 cycles -// no timescale needed - -module IntAdder_34_f150_uid18_l3( -clk, -rst, -X, -Y, -Cin, -R -); - -input clk, rst; -input [33:0] X; -input [33:0] Y; -input Cin; -output [33:0] R; - -wire clk; -wire rst; -wire [33:0] X; -wire [33:0] Y; -wire Cin; -wire [33:0] R; - - always @(posedge clk) begin - end - - //Alternative - assign R = X + Y + Cin; - -endmodule - -//------------------------------------------------------------------------------ -// FPAdder_8_23_uid2 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Bogdan Pasca, Florent de Dinechin (2010) -//------------------------------------------------------------------------------ -// Pipeline depth: 3 cycles -// no timescale needed - -module FPAdder_8_23_uid2_l3( -clk, -rst, -seq_stall, -X, -Y, -R -); - -input clk, rst, seq_stall; -input [8 + 23 + 2:0] X; -input [8 + 23 + 2:0] Y; -output [8 + 23 + 2:0] R; - -wire clk; -wire rst; -wire seq_stall; -wire [8 + 23 + 2:0] X; -wire [8 + 23 + 2:0] Y; -wire [8 + 23 + 2:0] R; - - -wire [32:0] excExpFracX; -wire [32:0] excExpFracY; -wire [8:0] eXmeY; -wire [8:0] eYmeX; -wire swap; -wire [33:0] newX; reg [33:0] newX_d1; -wire [33:0] newY; -wire [7:0] expX; reg [7:0] expX_d1; -wire [1:0] excX; -wire [1:0] excY; -wire signX; -wire signY; -wire EffSub; reg EffSub_d1; reg EffSub_d2; reg EffSub_d3; -wire [5:0] sdsXsYExnXY; -wire [3:0] sdExnXY; -wire [23:0] fracY; -reg [1:0] excRt; reg [1:0] excRt_d1; reg [1:0] excRt_d2; reg [1:0] excRt_d3; -wire signR; reg signR_d1; reg signR_d2; reg signR_d3; -wire [8:0] expDiff; -wire shiftedOut; -wire [4:0] shiftVal; -wire [49:0] shiftedFracY; - -wire sticky; -wire [26:0] fracYfar; -wire [26:0] fracYfarXorOp; -wire [26:0] fracXfar; -wire cInAddFar; -wire [26:0] fracAddResult; -wire [27:0] fracGRS; - -//added by Babis -reg [27:0] fracGRS_d1; - -wire [9:0] extendedExpInc; reg [9:0] extendedExpInc_d1; reg [9:0] extendedExpInc_d2; -wire [4:0] nZerosNew; reg [4:0] nZerosNew_d1; -wire [27:0] shiftedFrac; -reg [27:0] shiftedFrac_d1; - -//reg [49:0] shiftedFracY_d1; - -/* Added by Babis */ -reg [4:0] shiftVal_d1; -reg [23:0] fracY_d1; - -wire [9:0] updatedExp; -wire eqdiffsign; -wire [33:0] expFrac; -wire stk; reg stk_d1; -wire rnd; reg rnd_d1; -wire grd; reg grd_d1; -wire lsb; reg lsb_d1; -wire addToRoundBit; -wire [33:0] RoundedExpFrac; -wire [1:0] upExc; -wire [22:0] fracR; -wire [7:0] expR; -wire [3:0] exExpExc; -reg [1:0] excRt2; -wire [1:0] excR; -wire [33:0] computedR; - - always @(posedge clk) begin - if(rst == 1'b1) begin - newX_d1 <= {34{1'b0}}; - expX_d1 <= {8{1'b0}}; - EffSub_d1 <= 1'b 0; - EffSub_d2 <= 1'b 0; - EffSub_d3 <= 1'b 0; - excRt_d1 <= {2{1'b0}}; - excRt_d2 <= {2{1'b0}}; - excRt_d3 <= {2{1'b0}}; - signR_d1 <= 1'b0; - signR_d2 <= 1'b0; - signR_d3 <= 1'b0; - //shiftedFracY_d1 <= {50{1'b0}}; - /* Added by Babis */ - shiftVal_d1 <= {4{1'b0}}; - fracY_d1 <= {24{1'b0}}; - fracGRS_d1 <= {28{1'b0}}; - - extendedExpInc_d1 <= {10{1'b0}}; - extendedExpInc_d2 <= {10{1'b0}}; - nZerosNew_d1 <= {5{1'b0}}; - shiftedFrac_d1 <= {28{1'b0}}; - stk_d1 <= 1'b 0; - rnd_d1 <= 1'b 0; - grd_d1 <= 1'b 0; - lsb_d1 <= 1'b 0; - end - else begin - if(~seq_stall) begin - newX_d1 <= newX; - expX_d1 <= expX; - EffSub_d1 <= EffSub; - EffSub_d2 <= EffSub_d1; - EffSub_d3 <= EffSub_d2; - excRt_d1 <= excRt; - excRt_d2 <= excRt_d1; - excRt_d3 <= excRt_d2; - signR_d1 <= signR; - signR_d2 <= signR_d1; - signR_d3 <= signR_d2; - //shiftedFracY_d1 <= shiftedFracY; - /* Added by Babis */ - shiftVal_d1 <= shiftVal; - fracY_d1 <= fracY; - fracGRS_d1 <= fracGRS; - - extendedExpInc_d1 <= extendedExpInc; - extendedExpInc_d2 <= extendedExpInc_d1; - nZerosNew_d1 <= nZerosNew; - shiftedFrac_d1 <= shiftedFrac; - stk_d1 <= stk; - rnd_d1 <= rnd; - grd_d1 <= grd; - lsb_d1 <= lsb; - end - end - end - - // Exponent difference and swap -- - assign excExpFracX = {X[33:32],X[30:0]}; - assign excExpFracY = {Y[33:32],Y[30:0]}; - assign eXmeY = ({1'b0,X[30:23]}) - ({1'b0,Y[30:23]}); - assign eYmeX = ({1'b0,Y[30:23]}) - ({1'b0,X[30:23]}); - assign swap = excExpFracX >= excExpFracY ? 1'b0 : 1'b1; - assign newX = swap == 1'b0 ? X : Y; - assign newY = swap == 1'b0 ? Y : X; - assign expX = newX[30:23]; - assign excX = newX[33:32]; - assign excY = newY[33:32]; - assign signX = newX[31]; - assign signY = newY[31]; - assign EffSub = signX ^ signY; - assign sdsXsYExnXY = {signX,signY,excX,excY}; - assign sdExnXY = {excX,excY}; - assign fracY = excY == 2'b00 ? 24'b000000000000000000000000 : {1'b1, newY[22:0]}; - always @(*) begin - case(sdsXsYExnXY) - 6'b000000,6'b010000,6'b100000,6'b110000 : excRt <= 2'b00; - 6'b000101,6'b010101,6'b100101,6'b110101,6'b000100,6'b010100,6'b100100,6'b110100,6'b000001,6'b010001,6'b100001,6'b110001 : excRt <= 2'b01; - 6'b111010,6'b001010,6'b001000,6'b011000,6'b101000,6'b111000,6'b000010,6'b010010,6'b100010,6'b110010,6'b001001,6'b011001,6'b101001,6'b111001,6'b000110,6'b010110,6'b100110,6'b110110 : excRt <= 2'b10; - default : excRt <= 2'b11; - endcase - end - - assign signR = (sdsXsYExnXY == 6'b100000 || sdsXsYExnXY == 6'b010000) ? 1'b0 : signX; - //-------------- cycle 0---------------- - assign expDiff = swap == 1'b0 ? eXmeY : eYmeX; - assign shiftedOut = (expDiff >= 25) ? 1'b1 : 1'b0; - assign shiftVal = shiftedOut == 1'b0 ? expDiff[4:0] : 5'b11010; - FPAdder_8_23_uid2_RightShifter_l3 RightShifterComponent( - .clk(clk), - .rst(rst), - .R(shiftedFracY), - .S(shiftVal_d1), - .X(fracY_d1)); - - //--------------Synchro barrier, entering cycle 1---------------- - assign sticky = (shiftedFracY[23:0] == 23'b00000000000000000000000) ? 1'b0 : 1'b1; - //-------------- cycle 0---------------- - //--------------Synchro barrier, entering cycle 1---------------- - assign fracYfar = {1'b0,shiftedFracY[49:24]}; - assign fracYfarXorOp = fracYfar ^ ({EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1}); - assign fracXfar = {2'b01,(newX_d1[22:0]),2'b00}; - assign cInAddFar = EffSub_d1 & ~sticky; - IntAdder_27_f150_uid6_l3 fracAdder( - .clk(clk), - .rst(rst), - .Cin(cInAddFar), - .R(fracAddResult), - .X(fracXfar), - .Y(fracYfarXorOp)); - - assign fracGRS = {fracAddResult,sticky}; - assign extendedExpInc = ({2'b 00,expX_d1}) + 1'b 1; - LZCShifter_28_to_28_counting_32_uid16_l3 LZC_component( - .clk(clk), - .rst(rst), - .Count(nZerosNew), - .I(fracGRS_d1), /* Added by Babis */ - .O(shiftedFrac)); - - //--------------Synchro barrier, entering cycle 2---------------- - //--------------Synchro barrier, entering cycle 3---------------- - assign updatedExp = extendedExpInc_d2 - ({5'b00000,nZerosNew_d1}); - assign eqdiffsign = nZerosNew_d1 == 5'b11111 ? 1'b1 : 1'b0; - assign expFrac = {updatedExp,shiftedFrac_d1[26:3]}; - //-------------- cycle 2---------------- - assign stk = shiftedFrac[1] | shiftedFrac[0]; - assign rnd = shiftedFrac[2]; - assign grd = shiftedFrac[3]; - assign lsb = shiftedFrac[4]; - //--------------Synchro barrier, entering cycle 3---------------- - assign addToRoundBit = (lsb_d1 == 1'b 0 && grd_d1 == 1'b 1 && rnd_d1 == 1'b 0 && stk_d1 == 1'b 0) ? 1'b 0 : 1'b 1; - IntAdder_34_f150_uid18_l3 roundingAdder( - .clk(clk), - .rst(rst), - .Cin(addToRoundBit), - .R(RoundedExpFrac), - .X(expFrac), - .Y(34'b0000000000000000000000000000000000)); - - //-------------- cycle 3---------------- - assign upExc = RoundedExpFrac[33:32]; - assign fracR = RoundedExpFrac[23:1]; - assign expR = RoundedExpFrac[31:24]; - assign exExpExc = {upExc,excRt_d3}; - always @(*) begin - case((exExpExc)) - 4'b 0000,4'b 0100,4'b 1000,4'b 1100,4'b 1001,4'b 1101 : excRt2 <= 2'b 00; - 4'b 0001 : excRt2 <= 2'b 01; - 4'b 0010,4'b 0110,4'b 0101 : excRt2 <= 2'b 10; - default : excRt2 <= 2'b 11; - endcase - end - - assign excR = (eqdiffsign == 1'b 1 && EffSub_d3 == 1'b 1) ? 2'b 00 : excRt2; - assign computedR = {excR,signR_d3,expR,fracR}; - assign R = computedR; - -endmodule diff --git a/examples_hw/apps/gbm_dtrees/hdl/FPAdder_4cycles_latency.v b/examples_hw/apps/gbm_dtrees/hdl/FPAdder_4cycles_latency.v deleted file mode 100644 index f6b677ceb..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/FPAdder_4cycles_latency.v +++ /dev/null @@ -1,420 +0,0 @@ -//------------------------------------------------------------------------------ -// FPAdder_8_23_uid2_RightShifter -// (RightShifter_24_by_max_26_uid4) -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Florent de Dinechin, Bogdan Pasca (2007,2008,2009,2010) -//------------------------------------------------------------------------------ -// no timescale needed - -module FPAdder_8_23_uid2_RightShifter_l4( X, S, R ); - -// input/output definition - -input [23:0] X; -input [4:0] S; -output [49:0] R; - -//signals - -wire [23:0] X; -wire [4:0] S; -wire [49:0] R; - -wire [23:0] level0; -wire [4:0] ps; -wire [24:0] level1; -wire [26:0] level2; -wire [30:0] level3; -wire [38:0] level4; -wire [54:0] level5; - - assign level0 = X; - assign ps = S; - assign level1 = ps[0] == 1'b 1 ? { 1'b0,level0 } : { level0,1'b0 }; - assign level2 = ps[1] == 1'b 1 ? { 2'b00,level1 } : { level1,2'b00 }; - assign level3 = ps[2] == 1'b 1 ? { 4'b0000,level2 } : { level2,4'b0000 }; - assign level4 = ps[3] == 1'b 1 ? { 8'b00000000,level3 } : { level3,8'b00000000 }; - assign level5 = ps[4] == 1'b 1 ? { 16'b0000000000000000,level4 } : { level4,16'b0000000000000000 }; - assign R = level5[54:5]; - -endmodule - -//------------------------------------------------------------------------------ -// IntAdder_27_f200_uid6 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Bogdan Pasca, Florent de Dinechin (2008-2010) -//------------------------------------------------------------------------------ -// Pipeline depth: 0 cycles -// no timescale needed - -module IntAdder_27_f200_uid6_l4( X, Y, Cin, R); - -input [26:0] X; -input [26:0] Y; -input Cin; -output [26:0] R; - -wire [26:0] X; -wire [26:0] Y; -wire Cin; -wire [26:0] R; - - //Classical - assign R = X + Y + Cin; - -endmodule - -//------------------------------------------------------------------------------ -// LZCShifter_28_to_28_counting_32_uid16 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Florent de Dinechin, Bogdan Pasca (2007) -//------------------------------------------------------------------------------ -// Pipeline depth: 2 cycles -// no timescale needed - -module LZCShifter_28_to_28_counting_32_uid16_l4( clk, rst, seq_stall, I, Count, O ); - -input clk, rst; -input seq_stall; -input [27:0] I; -output [4:0] Count; -output [27:0] O; - -wire clk; -wire rst; -wire seq_stall; -wire [27:0] I; -wire [4:0] Count; -wire [27:0] O; - -wire [27:0] level5; -wire count4; reg count4_d1; reg count4_d2; -wire [27:0] level4; reg [27:0] level4_d1; -wire count3; reg count3_d1; -wire [27:0] level3; -wire count2; reg count2_d1; -wire [27:0] level2; -wire count1; reg count1_d1; -wire [27:0] level1; reg [27:0] level1_d1; -wire count0; -wire [27:0] level0; -wire [4:0] sCount; - - always @( posedge clk, posedge rst ) begin - - if( rst==1'b1 ) - begin - count4_d1 <= 0; - count4_d2 <= 0; - level4_d1 <= 0; - count3_d1 <= 0; - count2_d1 <= 0; - count1_d1 <= 0; - level1_d1 <= 0; - end - - else if(~seq_stall) - begin - count4_d1 <= count4; - count4_d2 <= count4_d1; - level4_d1 <= level4; - count3_d1 <= count3; - count2_d1 <= count2; - count1_d1 <= count1; - level1_d1 <= level1; - end - end - - assign level5 = I; - assign count4 = level5[27:12] == 16'b0000000000000000 ? 1'b1 : 1'b0; - assign level4 = count4 == 1'b 0 ? level5[27:0] : { level5[11:0],16'b0000000000000000 }; - - //--------------Synchro barrier, entering cycle 1---------------- - assign count3 = level4_d1[27:20] == 8'b00000000 ? 1'b1 : 1'b0; - assign level3 = count3 == 1'b 0 ? level4_d1[27:0] : { level4_d1[19:0],8'b00000000 }; - assign count2 = level3[27:24] == 4'b0000 ? 1'b1 : 1'b0; - assign level2 = count2 == 1'b 0 ? level3[27:0] : { level3[23:0],4'b0000 }; - assign count1 = level2[27:26] == 2'b00 ? 1'b1 : 1'b0; - assign level1 = count1 == 1'b 0 ? level2[27:0] : { level2[25:0],2'b00 }; - - //--------------Synchro barrier, entering cycle 2---------------- - assign count0 = level1_d1[27:27] == 1'b0 ? 1'b 1 : 1'b0; - assign level0 = count0 == 1'b0 ? level1_d1[27:0] : { level1_d1[26:0],1'b0 }; - assign O = level0; - assign sCount = { count4_d2,count3_d1,count2_d1,count1_d1,count0 }; - assign Count = sCount; - -endmodule - -//------------------------------------------------------------------------------ -// IntAdder_34_f200_uid18 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Bogdan Pasca, Florent de Dinechin (2008-2010) -//------------------------------------------------------------------------------ -// Pipeline depth: 1 cycles -// no timescale needed - -module IntAdder_34_f200_uid18_l4( clk, rst, seq_stall, X, Y, Cin, R ); - -input clk, rst; -input seq_stall; -input [33:0] X; -input [33:0] Y; -input Cin; -output [33:0] R; - -wire clk; -wire rst; -wire seq_stall; -wire [33:0] X; -wire [33:0] Y; -wire Cin; -wire [33:0] R; - -reg [33:0] X_d1; -reg [33:0] Y_d1; -reg Cin_d1; - - always @(posedge clk, posedge rst) begin - - if ( rst == 1'b1 ) - begin - X_d1 <= 0; - Y_d1 <= 0; - Cin_d1 <= 0; - end - - else if(~seq_stall) - begin - X_d1 <= X; - Y_d1 <= Y; - Cin_d1 <= Cin; - end - end - - //Classical - //--------------Synchro barrier, entering cycle 1---------------- - - assign R = X_d1 + Y_d1 + Cin_d1; - -endmodule - -//------------------------------------------------------------------------------ -// FPAdder_8_23_uid2 -// This operator is part of the Infinite Virtual Library FloPoCoLib -// and is distributed under the terms of the GNU Lesser General Public Licence -// with a Tobin Tax restriction (see README file for details). -// Authors: Bogdan Pasca, Florent de Dinechin (2010) -//------------------------------------------------------------------------------ -// Pipeline depth: 4 cycles -// no timescale needed - -module FPAdder_8_23_uid2_l4( clk, rst, seq_stall, X, Y, R); - -input clk, rst; -input seq_stall; -input [8 + 23 + 2:0] X; -input [8 + 23 + 2:0] Y; -output [8 + 23 + 2:0] R; - -wire clk; -wire rst; -wire seq_stall; -wire [8 + 23 + 2:0] X; -wire [8 + 23 + 2:0] Y; -wire [8 + 23 + 2:0] R; - -wire [32:0] excExpFracX; -wire [32:0] excExpFracY; -wire [8:0] eXmeY; -wire [8:0] eYmeX; -wire swap; -wire [33:0] newX; reg [33:0] newX_d1; -wire [33:0] newY; -wire [7:0] expX; reg [7:0] expX_d1; -wire [1:0] excX; -wire [1:0] excY; -wire signX; -wire signY; -wire EffSub; reg EffSub_d1; reg EffSub_d2; reg EffSub_d3; reg EffSub_d4; -wire [5:0] sdsXsYExnXY; -wire [3:0] sdExnXY; -wire [23:0] fracY; -reg [1:0] excRt; reg [1:0] excRt_d1; reg [1:0] excRt_d2; reg [1:0] excRt_d3; reg [1:0] excRt_d4; -wire signR; reg signR_d1; reg signR_d2; reg signR_d3; reg signR_d4; -wire [8:0] expDiff; -wire shiftedOut; -wire [4:0] shiftVal; -wire [49:0] shiftedFracY; reg [49:0] shiftedFracY_d1; -wire sticky; -wire [26:0] fracYfar; -wire [26:0] fracYfarXorOp; -wire [26:0] fracXfar; -wire cInAddFar; -wire [26:0] fracAddResult; -wire [27:0] fracGRS; -wire [9:0] extendedExpInc; reg [9:0] extendedExpInc_d1; reg [9:0] extendedExpInc_d2; -wire [4:0] nZerosNew; -wire [27:0] shiftedFrac; -wire [9:0] updatedExp; -wire eqdiffsign; reg eqdiffsign_d1; -wire [33:0] expFrac; -wire stk; -wire rnd; -wire grd; -wire lsb; -wire addToRoundBit; -wire [33:0] RoundedExpFrac; -wire [1:0] upExc; -wire [22:0] fracR; -wire [7:0] expR; -wire [3:0] exExpExc; -reg [1:0] excRt2; -wire [1:0] excR; -wire [33:0] computedR; - - always @(posedge clk) begin - - if(~seq_stall) - begin - newX_d1 <= newX; - expX_d1 <= expX; - EffSub_d1 <= EffSub; - EffSub_d2 <= EffSub_d1; - EffSub_d3 <= EffSub_d2; - EffSub_d4 <= EffSub_d3; - excRt_d1 <= excRt; - excRt_d2 <= excRt_d1; - excRt_d3 <= excRt_d2; - excRt_d4 <= excRt_d3; - signR_d1 <= signR; - signR_d2 <= signR_d1; - signR_d3 <= signR_d2; - signR_d4 <= signR_d3; - shiftedFracY_d1 <= shiftedFracY; - extendedExpInc_d1 <= extendedExpInc; - extendedExpInc_d2 <= extendedExpInc_d1; - eqdiffsign_d1 <= eqdiffsign; - end - end - - // Exponent difference and swap -- - - assign excExpFracX = {X[33:32],X[30:0]}; - assign excExpFracY = {Y[33:32],Y[30:0]}; - assign eXmeY = ({1'b0,X[30:23]}) - ({1'b0,Y[30:23]}); - assign eYmeX = ({1'b0,Y[30:23]}) - ({1'b0,X[30:23]}); - assign swap = excExpFracX >= excExpFracY ? 1'b0 : 1'b1; - assign newX = swap == 1'b0 ? X : Y; - assign newY = swap == 1'b0 ? Y : X; - assign expX = newX[30:23]; - assign excX = newX[33:32]; - assign excY = newY[33:32]; - assign signX = newX[31]; - assign signY = newY[31]; - assign EffSub = signX ^ signY; - assign sdsXsYExnXY = {signX,signY,excX,excY}; - assign sdExnXY = {excX,excY}; - assign fracY = excY == 2'b00 ? 24'b000000000000000000000000 : {1'b 1,newY[22:0]}; - - always @(*) begin - case(sdsXsYExnXY) - 6'b000000,6'b010000,6'b100000,6'b110000 : excRt <= 2'b00; - 6'b000101,6'b010101,6'b100101,6'b110101,6'b000100,6'b010100,6'b100100,6'b110100,6'b000001,6'b010001,6'b100001,6'b110001 : excRt <= 2'b01; - 6'b111010,6'b001010,6'b001000,6'b011000,6'b101000,6'b111000,6'b000010,6'b010010,6'b100010,6'b110010,6'b001001,6'b011001,6'b101001,6'b111001,6'b000110,6'b010110,6'b100110,6'b110110 : excRt <= 2'b10; - default : excRt <= 2'b11; - endcase - end - - assign signR = (sdsXsYExnXY == 6'b100000 || sdsXsYExnXY == 6'b010000) ? 1'b0 : signX; - - //-------------- cycle 0---------------- - assign expDiff = swap == 1'b0 ? eXmeY : eYmeX; - assign shiftedOut = (expDiff >= 25) ? 1'b1 : 1'b0; - assign shiftVal = shiftedOut == 1'b0 ? expDiff[4:0] : 5'b11010; - - FPAdder_8_23_uid2_RightShifter_l4 RightShifterComponent( - .R(shiftedFracY), - .S(shiftVal), - .X(fracY)); - - //--------------Synchro barrier, entering cycle 1---------------- - assign sticky = (shiftedFracY_d1[23:0] == 23'b00000000000000000000000) ? 1'b0 : 1'b1; - - //-------------- cycle 0---------------- - - //--------------Synchro barrier, entering cycle 1---------------- - - assign fracYfar = {1'b 0,shiftedFracY_d1[49:24]}; - assign fracYfarXorOp = fracYfar ^({EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1,EffSub_d1}); - assign fracXfar = {2'b 01,(newX_d1[22:0]),2'b 00}; - assign cInAddFar = EffSub_d1 & ~sticky; - - IntAdder_27_f200_uid6_l4 fracAdder( - .Cin(cInAddFar), - .R(fracAddResult), - .X(fracXfar), - .Y(fracYfarXorOp)); - - assign fracGRS = {fracAddResult,sticky}; - assign extendedExpInc = ( {2'b00,expX_d1} ) + 1'b1; - - LZCShifter_28_to_28_counting_32_uid16_l4 LZC_component( - .clk(clk), - .rst(rst), - .seq_stall(seq_stall), - .Count(nZerosNew), - .I(fracGRS), - .O(shiftedFrac)); - - //--------------Synchro barrier, entering cycle 3---------------- - assign updatedExp = extendedExpInc_d2 - ({5'b00000,nZerosNew}); - assign eqdiffsign = nZerosNew == 5'b11111 ? 1'b1 : 1'b0; - assign expFrac = {updatedExp,shiftedFrac[26:3]}; - //-------------- cycle 3---------------- - - assign stk = shiftedFrac[1] | shiftedFrac[0]; - assign rnd = shiftedFrac[2]; - assign grd = shiftedFrac[3]; - assign lsb = shiftedFrac[4]; - assign addToRoundBit = (lsb == 1'b0 && grd == 1'b1 && rnd == 1'b0 && stk == 1'b0) ? 1'b0 : 1'b1; - - IntAdder_34_f200_uid18_l4 roundingAdder( - .clk(clk), - .rst(rst), - .seq_stall(seq_stall), - .Cin(addToRoundBit), - .R(RoundedExpFrac), - .X(expFrac), - .Y(34'b0000000000000000000000000000000000)); - - //-------------- cycle 4---------------- - - assign upExc = RoundedExpFrac[33:32]; - assign fracR = RoundedExpFrac[23:1]; - assign expR = RoundedExpFrac[31:24]; - assign exExpExc = {upExc,excRt_d4}; - - always @(*) begin - case((exExpExc)) - 4'b0000,4'b0100,4'b1000,4'b1100,4'b1001,4'b1101 : excRt2 <= 2'b00; - 4'b0001 : excRt2 <= 2'b01; - 4'b0010,4'b0110,4'b0101 : excRt2 <= 2'b10; - default : excRt2 <= 2'b 11; - endcase - end - - assign excR = (eqdiffsign_d1 == 1'b1 && EffSub_d4 == 1'b1) ? 2'b00 : excRt2; - assign computedR = {excR,signR_d4,expR,fracR}; - assign R = computedR; - -endmodule - diff --git a/examples_hw/apps/gbm_dtrees/hdl/FPAddersReduceTree.sv b/examples_hw/apps/gbm_dtrees/hdl/FPAddersReduceTree.sv deleted file mode 100644 index f4d88c2d3..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/FPAddersReduceTree.sv +++ /dev/null @@ -1,156 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - - -module FPAddersReduceTree #(parameter NUM_FP_POINTS = 8, - parameter FP_ADDER_LATENCY = 3) - ( - input wire clk, - input wire rst_n, - - input wire [31:0] fp_in_vector[NUM_FP_POINTS-1:0], - input wire fp_in_vector_valid[NUM_FP_POINTS-1:0], - input wire fp_in_vector_last[NUM_FP_POINTS-1:0], - - output wire [31:0] reduce_out, - output wire reduce_out_valid, - input wire reduce_out_ready - ); - - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Local Parameters ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -localparam NUM_TREE_LEVELS = (NUM_FP_POINTS <= 2 )? 1 : - (NUM_FP_POINTS <= 4 )? 2 : - (NUM_FP_POINTS <= 8 )? 3 : - (NUM_FP_POINTS <= 16)? 4 : - (NUM_FP_POINTS <= 32)? 5 : 6; - - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Signals Declarations ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -wire [33:0] tree_data[NUM_TREE_LEVELS:0][(NUM_FP_POINTS>>1)-1:0][1:0]; -wire [31:0] tree_out; - -wire fp_in_valid_delayed; -wire fp_in_last_delayed; - - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// FP Adders Tree ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// first level of tree adders -generate - genvar i; - for (i = 0; i < (NUM_FP_POINTS>>1); i = i + 1) - begin:treeLevel1 - - assign tree_data[0][i][0] = {1'b0, {|(fp_in_vector[i<<1])}, fp_in_vector[i<<1] }; - assign tree_data[0][i][1] = {1'b0, {|(fp_in_vector[(i<<1)+1])}, fp_in_vector[(i<<1)+1] }; - - FPAdder_8_23_uid2_l3 fpadder_1_x( - .clk (clk), - .rst (~rst_n), - .seq_stall (1'b0), - .X (tree_data[0][i][0]), - .Y (tree_data[0][i][1]), - .R (tree_data[1][i>>1][i%2]) - ); - end -endgenerate - -// the rest of levels -generate - genvar j; - for (i = 1; i < NUM_TREE_LEVELS; i = i + 1) - begin:treeLevels - for (j = 0; j < (NUM_FP_POINTS >> (i+1)); j = j + 1) - begin:levelAdders - FPAdder_8_23_uid2_l3 fpadder_i_x( - .clk (clk), - .rst (~rst_n), - .seq_stall (1'b0), - .X (tree_data[i][j][0]), - .Y (tree_data[i][j][1]), - .R (tree_data[i+1][j>>1][j%2]) - ); - end - end -endgenerate - -// delay valid and last -delay #(.DATA_WIDTH(1), - .DELAY_CYCLES(FP_ADDER_LATENCY*NUM_TREE_LEVELS) - ) fpadder_delay( - - .clk (clk), - .rst_n (rst_n), - .data_in (fp_in_vector_last[0]), // - .data_in_valid (fp_in_vector_valid[0]), - .data_out (fp_in_last_delayed), - .data_out_valid (fp_in_valid_delayed) - ); - -// assign tree output -assign tree_out = (tree_data[NUM_TREE_LEVELS][0][0][33:32] == 2'b00)? 0 : tree_data[NUM_TREE_LEVELS][0][0][31:0]; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// FP Aggregator ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -FPAggregator #(.FP_ADDER_LATENCY(2)) - - tree_aggregator( - - .clk (clk), - .rst_n (rst_n), - - .fp_in (tree_out), - .fp_in_valid (fp_in_valid_delayed), - .fp_in_last (fp_in_last_delayed), - .fp_in_ready (), - - .aggreg_out (reduce_out), - .aggreg_out_valid (reduce_out_valid), - .aggreg_out_ready (reduce_out_ready) - ); - - - - - - -endmodule - diff --git a/examples_hw/apps/gbm_dtrees/hdl/FPAddersReduceTree_sync.sv b/examples_hw/apps/gbm_dtrees/hdl/FPAddersReduceTree_sync.sv deleted file mode 100644 index 6d7972174..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/FPAddersReduceTree_sync.sv +++ /dev/null @@ -1,286 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - - -module FPAddersReduceTree_sync #(parameter NUM_FP_POINTS = 8, - parameter FP_ADDER_LATENCY = 3) - ( - input wire clk, - input wire rst_n, - - input wire [31:0] fp_in_vector[NUM_FP_POINTS-1:0], - input wire fp_in_vector_valid[NUM_FP_POINTS-1:0], - input wire fp_in_vector_last[NUM_FP_POINTS-1:0], - - output wire [31:0] reduce_out, - output wire reduce_out_valid, - input wire reduce_out_ready - ); - - - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Signals Declarations ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -wire [33:0] tree_data_il1[27:0]; -wire [33:0] tree_data_ol1[13:0]; - -wire [33:0] tree_data_il2[13:0]; -wire [33:0] tree_data_ol2[6:0]; - -wire [33:0] tree_data_il3[6:0]; -wire [33:0] tree_data_ol3[3:0]; - -wire [33:0] tree_data_il4[3:0]; -wire [33:0] tree_data_ol4[1:0]; - -wire [33:0] tree_data_il5[1:0]; -wire [33:0] tree_data_ol5; - -reg [31:0] tree_out; - -wire fp_in_valid_delayed; -wire fp_in_last_delayed; - -genvar i; -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// FP Adders Tree ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Level 0: introduce delay on even words - -generate for (i = 0; i < 28; i=i+1) begin : Level0 - if(i%2 == 0) begin - delay #(.DATA_WIDTH(34), - .DELAY_CYCLES(1) - ) level0_regs - ( - .clk (clk), - .rst_n (rst_n), - .data_in ({1'b0, {|(fp_in_vector[i])}, fp_in_vector[i]}), // - .data_in_valid (1'b0), - .data_out (tree_data_il1[i]), - .data_out_valid () - ); - end - else begin - assign tree_data_il1[i] = {1'b0, {|(fp_in_vector[i])}, fp_in_vector[i] }; - end -end -endgenerate - -// Level 1: Adders -generate for (i = 0; i < 28; i = i + 2) begin:treeLevel1 - - FPAdder_8_23_uid2_l3 fpadder_l1 - ( - .clk (clk), - .rst (~rst_n), - .seq_stall (1'b0), - .X (tree_data_il1[i]), - .Y (tree_data_il1[i+1]), - .R (tree_data_ol1[i/2]) - ); - - if(i%4 == 0) begin - delay #(.DATA_WIDTH(34), - .DELAY_CYCLES(2) - ) level1_regs - ( - .clk (clk), - .rst_n (rst_n), - .data_in (tree_data_ol1[i/2]), // - .data_in_valid (1'b0), - .data_out (tree_data_il2[i/2]), - .data_out_valid () - ); - end - else begin - assign tree_data_il2[i/2] = tree_data_ol1[i/2]; - end -end -endgenerate - -// Level 2: Adders -generate for (i = 0; i < 14; i = i + 2) begin:treeLevel2 - - FPAdder_8_23_uid2_l3 fpadder_l2 - ( - .clk (clk), - .rst (~rst_n), - .seq_stall (1'b0), - .X (tree_data_il2[i]), - .Y (tree_data_il2[i+1]), - .R (tree_data_ol2[i/2]) - ); - - if((i%4 == 0) && (i < 12)) begin - delay #(.DATA_WIDTH(34), - .DELAY_CYCLES(4) - ) level2_regs - ( - .clk (clk), - .rst_n (rst_n), - .data_in (tree_data_ol2[i/2]), // - .data_in_valid (1'b0), - .data_out (tree_data_il3[i/2]), - .data_out_valid () - ); - end - else begin - assign tree_data_il3[i/2] = tree_data_ol2[i/2]; - end -end -endgenerate -// Level 3: Adders -generate for (i = 0; i < 7; i = i + 2) begin:treeLevel3 -if(i < 6) begin - FPAdder_8_23_uid2_l3 fpadder_l3 - ( - .clk (clk), - .rst (~rst_n), - .seq_stall (1'b0), - .X (tree_data_il3[i]), - .Y (tree_data_il3[i+1]), - .R (tree_data_ol3[i/2]) - ); - - if(i%4 == 0) begin - delay #(.DATA_WIDTH(34), - .DELAY_CYCLES( (i == 0)?8:1) - ) level3_regs - ( - .clk (clk), - .rst_n (rst_n), - .data_in (tree_data_ol3[i/2]), // - .data_in_valid (1'b0), - .data_out (tree_data_il4[i/2]), - .data_out_valid () - ); - end - else begin - assign tree_data_il4[i/2] = tree_data_ol3[i/2]; - end -end -else begin - assign tree_data_il4[i/2] = tree_data_il3[i]; -end -end -endgenerate - -// Level 4: Adders -generate for (i = 0; i < 4; i = i + 2) begin:treeLevel4 - - FPAdder_8_23_uid2_l3 fpadder_l4 - ( - .clk (clk), - .rst (~rst_n), - .seq_stall (1'b0), - .X (tree_data_il4[i]), - .Y (tree_data_il4[i+1]), - .R (tree_data_ol4[i/2]) - ); - - if(i%4 == 0) begin - delay #(.DATA_WIDTH(34), - .DELAY_CYCLES(9) - ) level4_regs - ( - .clk (clk), - .rst_n (rst_n), - .data_in (tree_data_ol4[i/2]), // - .data_in_valid (1'b0), - .data_out (tree_data_il5[i/2]), - .data_out_valid () - ); - end - else begin - assign tree_data_il5[i/2] = tree_data_ol4[i/2]; - end -end -endgenerate - -// Level 5: Adders - - FPAdder_8_23_uid2_l3 fpadder_l5 - ( - .clk (clk), - .rst (~rst_n), - .seq_stall (1'b0), - .X (tree_data_il5[0]), - .Y (tree_data_il5[1]), - .R (tree_data_ol5) - ); - -// delay valid and last -delay #(.DATA_WIDTH(1), - .DELAY_CYCLES(FP_ADDER_LATENCY*5 + 1 + 24) - ) fpadder_delay( - - .clk (clk), - .rst_n (rst_n), - .data_in (fp_in_vector_last[0]), // - .data_in_valid (fp_in_vector_valid[0]), - .data_out (fp_in_last_delayed), - .data_out_valid (fp_in_valid_delayed) - ); - -// assign tree output -always@(posedge clk) begin - tree_out <= (tree_data_ol5[33:32] == 2'b00)? 0 : tree_data_ol5[31:0]; -end - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// FP Aggregator ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -FPAggregator #(.FP_ADDER_LATENCY(3)) - - tree_aggregator( - - .clk (clk), - .rst_n (rst_n), - - .fp_in (tree_out), - .fp_in_valid (fp_in_valid_delayed), - .fp_in_last (fp_in_last_delayed), - .fp_in_ready (), - - .aggreg_out (reduce_out), - .aggreg_out_valid (reduce_out_valid), - .aggreg_out_ready (reduce_out_ready) - ); - - - - - - -endmodule - diff --git a/examples_hw/apps/gbm_dtrees/hdl/FPAggregator.sv b/examples_hw/apps/gbm_dtrees/hdl/FPAggregator.sv deleted file mode 100644 index 4124cd2f2..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/FPAggregator.sv +++ /dev/null @@ -1,174 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - -module FPAggregator #(parameter FP_ADDER_LATENCY = 2) ( - - input wire clk, - input wire rst_n, - - input wire [31:0] fp_in, - input wire fp_in_valid, - input wire fp_in_last, - output wire fp_in_ready, - - output wire [31:0] aggreg_out, - output wire aggreg_out_valid, - input wire aggreg_out_ready - ); - - - - -wire aggreg_in_fifo_full; -wire aggreg_in_fifo_valid; -wire aggreg_in_fifo_re; -wire [32:0] aggreg_in_fifo_dout; - -wire [33:0] input_A; -reg [33:0] prev_aggreg_value; -wire [33:0] aggreg_value; -reg [3:0] fpadder_latency_count; - - -wire fp_in_valid_delayed; -wire fp_in_last_delayed; -wire aggregator_ready; - -reg [31:0] aggreg_out_d1; -reg aggreg_out_valid_d1; - -wire aggreg_out_fifo_almfull; -//////////////////////////////////////////////////////////////////////////////// -assign fp_in_ready = ~aggreg_in_fifo_full; - -quick_fifo #(.FIFO_WIDTH(32+1), - .FIFO_DEPTH_BITS(9), - .FIFO_ALMOSTFULL_THRESHOLD(508) - ) aggreg_in_fifo ( - .clk (clk), - .reset_n (rst_n), - .din ({fp_in_last, fp_in}), - .we (fp_in_valid), - - .re (aggreg_in_fifo_re), - .dout (aggreg_in_fifo_dout), - .empty (), - .valid (aggreg_in_fifo_valid), - .full (aggreg_in_fifo_full), - .count (), - .almostfull () - ); - -assign aggreg_in_fifo_re = aggregator_ready; -//////////////////////////////////////////////////////////////////////////////// - -always @(posedge clk) begin - if (~rst_n) begin - // reset - prev_aggreg_value <= 0; - fpadder_latency_count <= 0; - aggreg_out_valid_d1 <= 1'b0; - aggreg_out_d1 <= 0; - end - else begin - if(aggregator_ready & aggreg_in_fifo_valid) begin - fpadder_latency_count <= FP_ADDER_LATENCY; - end - else if(!(fpadder_latency_count == 0)) begin - fpadder_latency_count <= fpadder_latency_count - 1'b1; - end - //--------------------- Do aggregation --------------------------// - if(fp_in_valid_delayed) begin - if(~fp_in_last_delayed) begin - prev_aggreg_value <= aggreg_value; - end - else begin - prev_aggreg_value <= 0; - end - end - - //--------------------- Tuple Output ----------------------------// - aggreg_out_valid_d1 <= 1'b0; - - if(fp_in_valid_delayed & fp_in_last_delayed) begin - if(aggreg_value[33:32] == 2'b00) begin - aggreg_out_d1 <= 0; - end - else begin - aggreg_out_d1 <= aggreg_value[31:0]; - end - - aggreg_out_valid_d1 <= 1'b1; - end - end -end - -assign aggregator_ready = (fpadder_latency_count == 0) & ~aggreg_out_fifo_almfull; - -assign input_A = {1'b0, {|(aggreg_in_fifo_dout[31:0])}, aggreg_in_fifo_dout[31:0]}; - -FPAdder_8_23_uid2_l3 fpadder( - .clk (clk), - .rst (~rst_n), - .seq_stall (1'b0), - .X (input_A), - .Y (prev_aggreg_value), - .R (aggreg_value) - ); - -// delay valid, last with FPAdder Latency -delay #(.DATA_WIDTH(1), - .DELAY_CYCLES(FP_ADDER_LATENCY) - ) fpadder_delay( - - .clk (clk), - .rst_n (rst_n), - .data_in (aggreg_in_fifo_dout[32]), // - .data_in_valid (aggreg_in_fifo_valid & aggregator_ready), - .data_out (fp_in_last_delayed), - .data_out_valid (fp_in_valid_delayed) - ); - - -quick_fifo #(.FIFO_WIDTH(32), - .FIFO_DEPTH_BITS(9), - .FIFO_ALMOSTFULL_THRESHOLD(490) - ) aggreg_out_fifo ( - .clk (clk), - .reset_n (rst_n), - .din (aggreg_out_d1), - .we (aggreg_out_valid_d1), - - .re (aggreg_out_ready), - .dout (aggreg_out), - .empty (), - .valid (aggreg_out_valid), - .full (), - .count (), - .almostfull (aggreg_out_fifo_almfull) - ); - - - - -endmodule diff --git a/examples_hw/apps/gbm_dtrees/hdl/LineRateConvertor.sv b/examples_hw/apps/gbm_dtrees/hdl/LineRateConvertor.sv deleted file mode 100644 index dfbb6f8a1..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/LineRateConvertor.sv +++ /dev/null @@ -1,134 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - -import DTPackage::*; - - -module LineRateConvertor #(parameter CU_ID = 0 ) - ( - input wire clk, - input wire rst_n, - - input wire [511:0] data_line_in, - input wire data_line_in_valid, - input wire [2:0] data_line_in_last_valid_pos, - input wire data_line_in_last, - input wire data_line_in_ctrl, - input wire data_line_in_prog, - input wire [NUM_PUS_PER_CLUSTER_BITS-1:0] data_line_in_pu, - input wire [NUM_DTPU_CLUSTERS_BITS-1:0] data_line_in_cu, - output wire data_line_in_ready, - - output reg [DATA_LINE_WIDTH-1:0] data_line_out, - output reg data_line_out_valid, - output reg data_line_out_ctrl, - output reg data_line_out_last, - output reg data_line_out_prog, - output reg [NUM_PUS_PER_CLUSTER_BITS-1:0] data_line_out_pu, - input wire data_line_out_ready -); - - -wire data_line_fifo_we; -wire data_line_fifo_almfull; -wire data_line_fifo_valid; -wire data_line_fifo_re; - -wire [63:0] data_line_array[7:0]; - -wire [511:0] data_line_in_fifo_data; -wire data_line_in_fifo_valid; -wire [2:0] data_line_in_fifo_last_valid_pos; -wire data_line_in_fifo_last; -wire data_line_in_fifo_ctrl; -wire data_line_in_fifo_prog; -wire [NUM_PUS_PER_CLUSTER_BITS-1:0] data_line_in_fifo_pu; - -reg [2:0] curr_word; - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -assign data_line_fifo_we = (data_line_in_valid && (data_line_in_cu == CU_ID)) || data_line_in_ctrl || data_line_in_prog; -assign data_line_in_ready = ~data_line_fifo_almfull; - -// Input Line FIFO -quick_fifo #(.FIFO_WIDTH(512+3+1+1+1+1+5), - .FIFO_DEPTH_BITS(9), - .FIFO_ALMOSTFULL_THRESHOLD(500) - ) data_line_fifo ( - .clk (clk), - .reset_n (rst_n), - .din ({data_line_in_pu, data_line_in_prog, data_line_in_ctrl, data_line_in_last, data_line_in_valid, data_line_in_last_valid_pos, data_line_in}), - .we (data_line_fifo_we), - - .re (data_line_fifo_re), - .dout ({data_line_in_fifo_pu, data_line_in_fifo_prog, data_line_in_fifo_ctrl, data_line_in_fifo_last, data_line_in_fifo_valid, data_line_in_fifo_last_valid_pos, data_line_in_fifo_data}), - .empty (), - .valid (data_line_fifo_valid), - .full (), - .count (), - .almostfull (data_line_fifo_almfull) - ); - - -// Put the input data line in an array -genvar i; -generate for (i = 0; i < 8; i=i+1) begin - assign data_line_array[i] = data_line_in_fifo_data[64*i+63:64*i]; -end -endgenerate - -// Select output 64-bit word -always@(posedge clk) begin - data_line_out <= data_line_array[ curr_word ]; - data_line_out_valid <= data_line_fifo_valid && data_line_in_fifo_valid && data_line_out_ready; - data_line_out_last <= data_line_in_fifo_last && (curr_word == data_line_in_fifo_last_valid_pos); - data_line_out_ctrl <= data_line_fifo_valid && data_line_in_fifo_ctrl; - data_line_out_prog <= data_line_fifo_valid && data_line_in_fifo_prog; - data_line_out_pu <= data_line_in_fifo_pu; -end - -// data_line_fifo_re -assign data_line_fifo_re = data_line_out_ready && ( (data_line_in_fifo_last && (curr_word == data_line_in_fifo_last_valid_pos)) || (curr_word == 3'b111) ); - -// curr_word calculation -always@(posedge clk) begin - if(~rst_n) begin - curr_word <= 3'b000; - end - else begin - if(data_line_out_ready && data_line_fifo_valid) begin - if(data_line_in_fifo_last && (curr_word == data_line_in_fifo_last_valid_pos) ) begin - curr_word <= 3'b000; - end - else if(curr_word == 3'b111) begin - curr_word <= 3'b000; - end - else begin - curr_word <= curr_word + 1'b1; - end - end - end -end - - -endmodule \ No newline at end of file diff --git a/examples_hw/apps/gbm_dtrees/hdl/RegBasedFIFO.sv b/examples_hw/apps/gbm_dtrees/hdl/RegBasedFIFO.sv deleted file mode 100644 index 19b8a46ea..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/RegBasedFIFO.sv +++ /dev/null @@ -1,122 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - -module RegBasedFIFO #(parameter FIFO_WIDTH = 32, - parameter FIFO_DEPTH_BITS = 2 - )( - input wire clk, - input wire rst_n, - - input wire [FIFO_WIDTH-1:0] data_in, - input wire data_in_valid, - output wire data_in_ready, - - output wire [FIFO_WIDTH-1:0] data_out, - output wire data_out_valid, - input wire data_out_ready - ); - - - -localparam FIFO_NUM_REGS = 2**FIFO_DEPTH_BITS; - - -reg [FIFO_WIDTH-1:0] fifo_reg_data[FIFO_NUM_REGS-1:0]; -reg fifo_reg_valid[FIFO_NUM_REGS-1:0]; - -// Last Reg in the FIFO -// valid -always@(posedge clk) begin - // data - if(data_out_ready || ~fifo_reg_valid[FIFO_NUM_REGS-1]) begin - fifo_reg_data[FIFO_NUM_REGS-1] <= data_in; - end - // valid - if(~rst_n) begin - fifo_reg_valid[FIFO_NUM_REGS-1] <= 1'b0; - end - else begin - if(data_out_ready) begin - fifo_reg_valid[FIFO_NUM_REGS-1] <= 1'b0; - end - else if(!fifo_reg_valid[FIFO_NUM_REGS-1] && fifo_reg_valid[FIFO_NUM_REGS-2]) begin - fifo_reg_valid[FIFO_NUM_REGS-1] <= data_in_valid; - end - end -end - -// Rest of Regs -genvar i; - -generate for (i = 0; i < FIFO_NUM_REGS-1; i=i+1) begin: fifo_regs - // valid - always@(posedge clk) begin - // Data - if(~fifo_reg_valid[i]) begin - fifo_reg_data[i] <= data_in; - end - else if(data_out_ready) begin - if(fifo_reg_valid[i+1]) begin - fifo_reg_data[i] <= fifo_reg_data[i+1]; - end - else begin - fifo_reg_data[i] <= data_in; - end - end - // valid - if(~rst_n) begin - fifo_reg_valid[i] <= 1'b0; - end - else if(~fifo_reg_valid[i]) begin - if(i == 0) begin - fifo_reg_valid[i] <= data_in_valid; - end - else begin - if(!data_out_ready && fifo_reg_valid[i-1]) begin - fifo_reg_valid[i] <= data_in_valid; - end - end - end - else if(data_out_ready) begin - if(fifo_reg_valid[i+1]) begin - fifo_reg_valid[i] <= 1'b1; - end - else begin - fifo_reg_valid[i] <= data_in_valid; - end - end - end -end -endgenerate - - -// -assign data_in_ready = ~fifo_reg_valid[FIFO_NUM_REGS-2]; -assign data_out = fifo_reg_data[0]; -assign data_out_valid = fifo_reg_valid[0]; - - - - - -endmodule \ No newline at end of file diff --git a/examples_hw/apps/gbm_dtrees/hdl/Tree_Memory.sv b/examples_hw/apps/gbm_dtrees/hdl/Tree_Memory.sv deleted file mode 100644 index a01e31345..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/Tree_Memory.sv +++ /dev/null @@ -1,192 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - - - - -module Tree_Memory #( - parameter DATA_WIDTH = 32, - parameter ADDR_WIDTH = 10 -) ( - input wire clk, - input wire rst_n, - input wire we, - input wire rea, - input wire reb, - input wire [ADDR_WIDTH-1:0] addr_port_b, - input wire [ADDR_WIDTH-1:0] addr_port_a, - input wire [DATA_WIDTH-1:0] din, - output reg [DATA_WIDTH-1:0] dout1, - output reg valid_out1, - output reg [31:0] dout2, - output reg valid_out2 -); - - -// Port A -reg rea_d1; -reg addr_port_a_d1; - -wire [31:0] dout_a_1; -wire [31:0] dout_a_2; - -wire [9:0] addr_a_1; -wire [9:0] addr_a_2; - -// Port B -reg reb_d1; -reg addr_port_b_d1; - -wire [31:0] dout_b_1; -wire [31:0] dout_b_2; - - - -bram_1_in_2_out bram1in2out_inst_1 ( - .clk ( clk ), - .da ( din[31:0] ), - .ena (rea || we), - .addra ( addr_a_1 ), - .wea ( we ), - .qa ( dout_a_1 ), - - .enb (reb), - .db ( 0 ), - .addrb ( addr_port_b[10:1] ), - .web ( 1'b0 ), - .qb ( dout_b_1 ) - ); - - bram_1_in_2_out bram1in2out_inst_2 ( - .clk ( clk ), - .da ( din[63:32] ), - .ena (rea || we), - .addra ( addr_a_2 ), - .wea ( we ), - .qa ( dout_a_2 ), - - .enb (reb), - .db ( 0 ), - .addrb ( addr_port_b[10:1] ), - .web ( 1'b0 ), - .qb ( dout_b_2 ) - ); - - - -/*bram_1_in_2_out bram1in2out_inst_1 ( - .address_a ( addr_a_1 ), - .address_b ( addr_port_b[10:2] ), - .clock ( clk ), - .data_a ( din[31:0] ), - .data_b ( 0 ), - .wren_a ( we ), - .wren_b ( 1'b0 ), - .q_a ( dout_a_1 ), - .q_b ( dout_b_1 ) - ); - -bram_1_in_2_out bram1in2out_inst_2 ( - .address_a ( addr_a_2 ), - .address_b ( addr_port_b[10:2] ), - .clock ( clk ), - .data_a ( din[63:32] ), - .data_b ( 0 ), - .wren_a ( we ), - .wren_b ( 1'b0 ), - .q_a ( dout_a_2 ), - .q_b ( dout_b_2 ) - ); - -bram_1_in_2_out bram1in2out_inst_3 ( - .address_a ( addr_a_3 ), - .address_b ( addr_port_b[10:2] ), - .clock ( clk ), - .data_a ( din[95:64] ), - .data_b ( 0 ), - .wren_a ( we ), - .wren_b ( 1'b0 ), - .q_a ( dout_a_3 ), - .q_b ( dout_b_3 ) - ); - -bram_1_in_2_out bram1in2out_inst_4 ( - .address_a ( addr_a_4 ), - .address_b ( addr_port_b[10:2] ), - .clock ( clk ), - .data_a ( din[127:96] ), - .data_b ( 0 ), - .wren_a ( we ), - .wren_b ( 1'b0 ), - .q_a ( dout_a_4 ), - .q_b ( dout_b_4 ) - ); -*/ -//------------------------ Port A ---------------------------// -// rd_addr_a -assign addr_a_1 = (we || (addr_port_a[0] == 1'b0))? addr_port_a[10:1] : addr_port_a[10:1] + 1'b1; -assign addr_a_2 = addr_port_a[10:1]; -// -always @(posedge clk) begin - addr_port_a_d1 <= addr_port_a[0]; - - if(~rst_n) begin - valid_out1 <= 1'b0; - rea_d1 <= 1'b0; - end - else begin - rea_d1 <= rea; - valid_out1 <= rea_d1; - end - // - case (addr_port_a_d1) - 1'b0: dout1 <= {dout_a_2, dout_a_1}; - 1'b1: dout1 <= {dout_a_1, dout_a_2}; - default: dout1 <= 64'b0; - endcase -end -//----------------------- Port B ----------------------------// -always @(posedge clk) begin - - addr_port_b_d1 <= addr_port_b[0]; - - if(~rst_n) begin - reb_d1 <= 1'b0; - valid_out2 <= 1'b0; - end - else begin - reb_d1 <= reb; - valid_out2 <= reb_d1; - end - // - if (addr_port_b_d1) begin - dout2 <= dout_b_2; - end - else begin - dout2 <= dout_b_1; - end -end - - - -endmodule // Mem1in2out diff --git a/examples_hw/apps/gbm_dtrees/hdl/bram.sv b/examples_hw/apps/gbm_dtrees/hdl/bram.sv deleted file mode 100644 index a7d650ff1..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/bram.sv +++ /dev/null @@ -1,55 +0,0 @@ - - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - -module bram #( - parameter DATA_WIDTH = 32, - parameter ADDR_WIDTH = 8 -) ( - input wire clk, - input wire we, - input wire re, - input wire [ADDR_WIDTH-1:0] raddr, - input wire [ADDR_WIDTH-1:0] waddr, - input wire [DATA_WIDTH-1:0] din, - output reg [DATA_WIDTH-1:0] dout -); - - -`ifdef VENDOR_XILINX - (* ram_extract = "yes", ram_style = "block" *) - reg [DATA_WIDTH-1:0] mem[0:2**ADDR_WIDTH-1]; -`else -(* ramstyle = "no_rw_check" *) reg [DATA_WIDTH-1:0] mem[0:2**ADDR_WIDTH-1]; -`endif - - - always @(posedge clk) begin - if (we) - mem[waddr] <= din; - - if (re) - dout <= mem[raddr]; - end - -endmodule \ No newline at end of file diff --git a/examples_hw/apps/gbm_dtrees/hdl/bram_1_in_2_out.sv b/examples_hw/apps/gbm_dtrees/hdl/bram_1_in_2_out.sv deleted file mode 100644 index 7cabab16b..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/bram_1_in_2_out.sv +++ /dev/null @@ -1,136 +0,0 @@ -//--------------------------------------------------------------------------------------- -// Amazon FPGA Hardware Development Kit -// -// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Amazon Software License (the "License"). You may not use -// this file except in compliance with the License. A copy of the License is -// located at -// -// http://aws.amazon.com/asl/ -// -// or in the "license" file accompanying this file. This file is distributed on -// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or -// implied. See the License for the specific language governing permissions and -// limitations under the License. -//--------------------------------------------------------------------------------------- -//---------------------------------------------------- -// This is a dual ported BRAM -//---------------------------------------------------- -module bram_1_in_2_out #(parameter WIDTH=32, parameter ADDR_WIDTH=10, parameter DEPTH=1024, parameter PIPELINE=0, parameter MEMORY_TYPE = "auto") -( - input clk, - input wea, - input ena, - input[ADDR_WIDTH-1:0] addra, - input[WIDTH-1:0] da, - output logic[WIDTH-1:0] qa, - - input web, - input enb, - input[ADDR_WIDTH-1:0] addrb, - input[WIDTH-1:0] db, - output logic[WIDTH-1:0] qb - - - ); - -`ifndef NO_XILINX_XPM_RAM - - xpm_memory_tdpram # ( - // Common module parameters - .MEMORY_SIZE (WIDTH*DEPTH), //positive integer - .MEMORY_PRIMITIVE (MEMORY_TYPE), //string; "auto", "distributed", "block" or "ultra"; - .CLOCKING_MODE ("common_clock"), //string; "common_clock", "independent_clock" - .MEMORY_INIT_FILE ("none"), //string; "none" or ".mem" - .MEMORY_INIT_PARAM ("" ), //string; - .USE_MEM_INIT (1), //integer; 0,1 - .WAKEUP_TIME ("disable_sleep"), //string; "disable_sleep" or "use_sleep_pin" - .MESSAGE_CONTROL (0), //integer; 0,1 - // Port A module parameters - .WRITE_DATA_WIDTH_A (WIDTH), //positive integer - .READ_DATA_WIDTH_A (WIDTH), //positive integer - .BYTE_WRITE_WIDTH_A (WIDTH), //integer; 8, 9, or WRITE_DATA_WIDTH_A value - .ADDR_WIDTH_A (ADDR_WIDTH), //positive integer - .READ_RESET_VALUE_A ("0"), //string - .READ_LATENCY_A (PIPELINE+1), //non-negative integer - .WRITE_MODE_A ("read_first"), //string; "write_first", "read_first", "no_change" - // Port B module parameters - .WRITE_DATA_WIDTH_B (WIDTH), //positive integer - .READ_DATA_WIDTH_B (WIDTH), //positive integer - .BYTE_WRITE_WIDTH_B (WIDTH), //integer; 8, 9, or WRITE_DATA_WIDTH_B value - .ADDR_WIDTH_B (ADDR_WIDTH), //positive integer - .READ_RESET_VALUE_B ("0"), //vector of READ_DATA_WIDTH_B bits - .READ_LATENCY_B (PIPELINE+1), //non-negative integer - .WRITE_MODE_B ("read_first") //string; "write_first", "read_first", "no_change" - ) xpm_memory_tdpram_inst ( - // Common module ports - .sleep (1'b0), - // Port A module ports - .clka (clk), - .rsta (1'b0), - .ena (ena), - .regcea (1'b1), - .wea (wea), - .addra (addra), - .dina (da), - .injectsbiterra (1'b0), //do not change - .injectdbiterra (1'b0), //do not change - .douta (qa), - .sbiterra (), //do not change - .dbiterra (), //do not change - // Port B module ports - .clkb (clk), - .rstb (1'b0), - .enb (enb), - .regceb (1'b1), - .web (web), - .addrb (addrb), - .dinb (db), - .injectsbiterrb (1'b0), //do not change - .injectdbiterrb (1'b0), //do not change - .doutb (qb), - .sbiterrb (), //do not change - .dbiterrb () //do not change - ); - // End of xpm_memory_tdpram instance declaration - -`else - - logic[WIDTH-1:0] ram[DEPTH-1:0]; - - logic[WIDTH-1:0] rddata_a, rddata_a_q; - logic[WIDTH-1:0] rddata_b, rddata_b_q; - - always @(posedge clk) - if (ena) - begin - if (wea) - ram[addra] <= da; - else - rddata_a <= ram[addra]; - end - - always @(posedge clk) - rddata_a_q <= rddata_a; - - always @(posedge clk) - if (enb) - begin - if (web) - ram[addrb] <= db; - else - rddata_b <= ram[addrb]; - end - - always @(posedge clk) - rddata_b_q <= rddata_b; - - assign qa = (PIPELINE)? rddata_a_q: rddata_a; - assign qb = (PIPELINE)? rddata_b_q: rddata_b; - -`endif - - - -endmodule diff --git a/examples_hw/apps/gbm_dtrees/hdl/bus_aligner.sv b/examples_hw/apps/gbm_dtrees/hdl/bus_aligner.sv deleted file mode 100644 index b023dce91..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/bus_aligner.sv +++ /dev/null @@ -1,183 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - -import DTPackage::*; - -module bus_aligner - ( - input wire clk, - input wire rst_n, - - input wire [511:0] data_in, - input wire data_in_last, - input wire data_in_type, - input wire data_in_valid, - input wire [3:0] data_in_off, - input wire [2:0] data_in_size, - input wire [4:0] data_in_word_count, - input wire stream_last, - - output wire [511:0] data_out, - output wire data_out_last, - output wire data_out_type, - output wire [2:0] data_out_size, - output wire data_out_valid - ); - - -reg [511:0] data_in_d1; -reg [511:0] data_in_d2; - -reg data_in_valid_d1; -reg data_in_valid_d2; - -reg data_in_last_d1; -reg data_in_last_d2; - -reg [3:0] data_in_off_d1; -reg [3:0] data_in_off_d2; - -reg [2:0] data_in_size_d1; -reg [2:0] data_in_size_d2; - -reg [4:0] data_in_word_count_d1; -reg [4:0] data_in_word_count_d2; - -reg stream_last_d1; -reg stream_last_d2; - -reg data_in_type_d1; -reg data_in_type_d2; - -wire shifter_in_last; -/////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Prepare lines for Shifter ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -always@(posedge clk)begin - ////////////// Signals with reset ////////////////// - // Line 1 - if(~rst_n) begin - data_in_valid_d1 <= 1'b0; - data_in_valid_d2 <= 1'b0; - end - else begin - // Line 1 - data_in_valid_d1 <= data_in_valid; - - // Line 2 - if(data_in_valid_d2) begin - if(data_in_valid_d1) begin - if(data_in_last_d2) begin - data_in_valid_d2 <= 1'b1; - end - else if(data_in_last_d1) begin - data_in_valid_d2 <= data_in_word_count_d1 > data_in_off_d2; - end - else begin - data_in_valid_d2 <= 1'b1; - end - end - else if(stream_last_d2) begin - data_in_valid_d2 <= 1'b0; - end - end - else begin - data_in_valid_d2 <= data_in_valid_d1; - end - end - ////////////// Signals with no reset ////////////////// - // Line 1 - data_in_last_d1 <= data_in_last; - data_in_d1 <= data_in; - data_in_off_d1 <= data_in_off; - data_in_word_count_d1 <= data_in_word_count; - data_in_size_d1 <= data_in_size; - data_in_type_d1 <= data_in_type; - stream_last_d1 <= stream_last; - - // Line 2 - if(data_in_valid_d2) begin - if(data_in_valid_d1) begin - data_in_last_d2 <= data_in_last_d1; - data_in_d2 <= data_in_d1; - data_in_off_d2 <= data_in_off_d1; - data_in_word_count_d2 <= data_in_word_count_d1; - data_in_size_d2 <= data_in_size_d1; - data_in_type_d2 <= data_in_type_d1; - stream_last_d2 <= stream_last_d1; - end - end - else begin - data_in_last_d2 <= data_in_last_d1; - data_in_d2 <= data_in_d1; - data_in_off_d2 <= data_in_off_d1; - data_in_word_count_d2 <= data_in_word_count_d1; - data_in_type_d2 <= data_in_type_d1; - data_in_size_d2 <= data_in_size_d1; - stream_last_d2 <= stream_last_d1; - end -end - - -delay #(.DATA_WIDTH( 4 ), - .DELAY_CYCLES(16) - ) smart_shifter_delay( - .clk (clk), - .rst_n (rst_n), - .data_in ( {data_in_type_d2, data_in_size_d2} ), // - .data_in_valid ( (data_in_valid_d1 || stream_last_d2) && data_in_valid_d2 ), - .data_out ( {data_out_type, data_out_size} ), - .data_out_valid ( ) - ); - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Smart Shifter ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -assign shifter_in_last = data_in_last_d2 || (data_in_last_d1 && (data_in_word_count_d1 <= data_in_off_d1)); - -smart_shifter #(.ELEMENTS_PER_CL(16), - .ELEMENTS_PER_CL_BITS(4)) - smart_shifter_x( - .clk (clk), - .rst_n (rst_n), - - .inValid ((data_in_valid_d1 || stream_last_d2) && data_in_valid_d2), - .inOffs (data_in_off_d2), - .inLast (shifter_in_last), - .inData ({data_in_d1, data_in_d2}), - - .outValid (data_out_valid), - .outLast (data_out_last), - .outData (data_out) - ); - - -endmodule - - - diff --git a/examples_hw/apps/gbm_dtrees/hdl/compute_unit.sv b/examples_hw/apps/gbm_dtrees/hdl/compute_unit.sv deleted file mode 100644 index 08aba4737..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/compute_unit.sv +++ /dev/null @@ -1,241 +0,0 @@ - - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - - -import DTPackage::*; - - -module compute_unit #(parameter CU_ID = 0 ) - ( - input wire clk, - input wire rst_n, - - input wire [511:0] data_line_in, - input wire data_line_in_valid, - input wire [2:0] data_line_in_last_valid_pos, - input wire data_line_in_last, - input wire data_line_in_ctrl, - input wire data_line_in_prog, - input wire [NUM_PUS_PER_CLUSTER_BITS-1:0] data_line_in_pu, - input wire [NUM_DTPU_CLUSTERS_BITS-1:0] data_line_in_cu, - output wire data_line_in_ready, - - output reg [511:0] data_line_out, - output reg data_line_out_valid, - output reg [2:0] data_line_out_last_valid_pos, - output reg data_line_out_ctrl, - output reg data_line_out_last, - output reg data_line_out_prog, - output reg [NUM_PUS_PER_CLUSTER_BITS-1:0] data_line_out_pu, - output reg [NUM_DTPU_CLUSTERS_BITS-1:0] data_line_out_cu, - - output wire [DATA_PRECISION-1:0] tuple_result_out, - output wire tuple_result_out_valid, - input wire tuple_result_out_ready - - ); - - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Signals Declarations ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -wire [DATA_LINE_WIDTH-1:0] line_rate_convertor_data_out; -wire line_rate_convertor_out_valid; -wire line_rate_convertor_out_last; -wire line_rate_convertor_out_ctrl; -wire line_rate_convertor_out_prog; -wire line_rate_convertor_out_ready; -wire [NUM_PUS_PER_CLUSTER_BITS-1:0] line_rate_convertor_out_pu; - - -wire [DATA_LINE_WIDTH-1:0] data_line_array[NUM_PUS_PER_CLUSTER:0]; -wire data_line_valid_array[NUM_PUS_PER_CLUSTER:0]; -wire data_line_last_array[NUM_PUS_PER_CLUSTER:0]; -wire data_line_ready_array[NUM_PUS_PER_CLUSTER:0]; -wire data_line_ctrl_array[NUM_PUS_PER_CLUSTER:0]; -wire data_line_prog_array[NUM_PUS_PER_CLUSTER:0]; -wire [NUM_PUS_PER_CLUSTER_BITS-1:0] data_line_pu_array[NUM_PUS_PER_CLUSTER:0]; - -wire [DATA_PRECISION-1:0] pu_tree_leaf_out[NUM_PUS_PER_CLUSTER-1:0]; -wire pu_tree_leaf_out_valid[NUM_PUS_PER_CLUSTER-1:0]; -wire pu_tree_leaf_out_last[NUM_PUS_PER_CLUSTER-1:0]; - -wire [31:0] fp_in_vector[31:0]; -wire fp_in_vector_valid[31:0]; -wire fp_in_vector_last[31:0]; -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Pipeline to next CU ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -always @(posedge clk) begin - // info - data_line_out <= data_line_in; - data_line_out_pu <= data_line_in_pu; - data_line_out_last_valid_pos <= data_line_in_last_valid_pos; - data_line_out_cu <= data_line_in_cu; - data_line_out_last <= data_line_in_last; - - // valids - if(~rst_n) begin - data_line_out_valid <= 1'b0; - data_line_out_ctrl <= 1'b0; - data_line_out_prog <= 1'b0; - end - else begin - data_line_out_valid <= data_line_in_valid; - data_line_out_ctrl <= data_line_in_ctrl; - data_line_out_prog <= data_line_in_prog; - end -end -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Line Rate Convertor ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -LineRateConvertor #(.CU_ID (CU_ID) ) - bus_convertor( - .clk (clk), - .rst_n (rst_n), - - .data_line_in (data_line_in), - .data_line_in_valid (data_line_in_valid), - .data_line_in_last_valid_pos (data_line_in_last_valid_pos), - .data_line_in_last (data_line_in_last), - .data_line_in_ctrl (data_line_in_ctrl), - .data_line_in_prog (data_line_in_prog), - .data_line_in_pu (data_line_in_pu), - .data_line_in_cu (data_line_in_cu), - .data_line_in_ready (data_line_in_ready), - - - .data_line_out (line_rate_convertor_data_out), - .data_line_out_valid (line_rate_convertor_out_valid), - .data_line_out_ctrl (line_rate_convertor_out_ctrl), - .data_line_out_last (line_rate_convertor_out_last), - .data_line_out_prog (line_rate_convertor_out_prog), - .data_line_out_pu (line_rate_convertor_out_pu), - .data_line_out_ready (line_rate_convertor_out_ready) - ); - - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Generate DTPU Instances ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -// initialize input to first PU -assign data_line_array[0] = line_rate_convertor_data_out; -assign data_line_valid_array[0] = line_rate_convertor_out_valid; -assign data_line_last_array[0] = line_rate_convertor_out_last; -assign data_line_ctrl_array[0] = line_rate_convertor_out_ctrl; -assign data_line_prog_array[0] = line_rate_convertor_out_prog; -assign data_line_pu_array[0] = line_rate_convertor_out_pu; - -assign line_rate_convertor_out_ready = data_line_ready_array[0]; - - -// generate a cascade of PUs -genvar i; -generate - for (i = 0; i < NUM_PUS_PER_CLUSTER; i = i + 1) begin: pus - processing_element_async #(.PE_ID (i) ) - pe_x( - .clk (clk), - .rst_n (rst_n), - - .data_line_in (data_line_array[i]), - .data_line_in_valid (data_line_valid_array[i]), - .data_line_in_last (data_line_last_array[i]), - .data_line_in_ctrl (data_line_ctrl_array[i]), - .data_line_in_prog (data_line_prog_array[i]), - .data_line_in_pu (data_line_pu_array[i]), - .data_line_in_ready (data_line_ready_array[i]), - - .data_line_out (data_line_array[i+1]), - .data_line_out_valid (data_line_valid_array[i+1]), - .data_line_out_ctrl (data_line_ctrl_array[i+1]), - .data_line_out_last (data_line_last_array[i+1]), - .data_line_out_prog (data_line_prog_array[i+1]), - .data_line_out_pu (data_line_pu_array[i+1]), - - .pu_tree_leaf_out (pu_tree_leaf_out[i]), - .pu_tree_leaf_out_valid (pu_tree_leaf_out_valid[i]), - .pu_tree_leaf_out_last (pu_tree_leaf_out_last[i]) - ); - end -endgenerate -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Instance of FPAdders Tree ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -generate for (i = 0; i < 32; i=i+1) begin - if( i < NUM_PUS_PER_CLUSTER ) begin - assign fp_in_vector[i] = pu_tree_leaf_out[i]; - assign fp_in_vector_valid[i] = pu_tree_leaf_out_valid[i]; - assign fp_in_vector_last[i] = pu_tree_leaf_out_last[i]; - end - else begin - assign fp_in_vector[i] = 32'b0; - assign fp_in_vector_valid[i] = 1'b0; - assign fp_in_vector_last[i] = 1'b0; - end -end -endgenerate - -FPAddersReduceTree_sync #(.NUM_FP_POINTS(32) - ) reduce_leaves( - .clk (clk), - .rst_n (rst_n), - - .fp_in_vector (fp_in_vector), - .fp_in_vector_valid (fp_in_vector_valid), - .fp_in_vector_last (fp_in_vector_last), - - .reduce_out (tuple_result_out), - .reduce_out_valid (tuple_result_out_valid), - .reduce_out_ready (tuple_result_out_ready) - ); - - -endmodule - - - - - - - - - - - - diff --git a/examples_hw/apps/gbm_dtrees/hdl/delay.sv b/examples_hw/apps/gbm_dtrees/hdl/delay.sv deleted file mode 100644 index fe4b7a76b..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/delay.sv +++ /dev/null @@ -1,75 +0,0 @@ - - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - - -module delay #(parameter DATA_WIDTH = 32, - parameter DELAY_CYCLES = 4 - ) ( - - input wire clk, - input wire rst_n, - input wire [DATA_WIDTH-1:0] data_in, - input wire data_in_valid, - output wire [DATA_WIDTH-1:0] data_out, - output wire data_out_valid - ); - - -reg [DATA_WIDTH-1:0] data_array[DELAY_CYCLES]; -reg data_array_valid[DELAY_CYCLES]; - - -always @(posedge clk) begin - // Valid Bit - if(~rst_n) begin - data_array_valid[0] <= 0; - end - else begin - data_array_valid[0] <= data_in_valid; - end - // Data word - data_array[0] <= data_in; -end - - -genvar i; -generate for (i = 1; i < DELAY_CYCLES; i = i +1) begin: delayPipe - always @(posedge clk) begin - // Valid Bit - if(~rst_n) begin - data_array_valid[i] <= 0; - end - else begin - data_array_valid[i] <= data_array_valid[i-1]; - end - // Data word - data_array[i] <= data_array[i-1]; - end -end -endgenerate - -assign data_out = data_array[DELAY_CYCLES-1]; -assign data_out_valid = data_array_valid[DELAY_CYCLES-1]; - -endmodule // delay diff --git a/examples_hw/apps/gbm_dtrees/hdl/dual_port_mem.sv b/examples_hw/apps/gbm_dtrees/hdl/dual_port_mem.sv deleted file mode 100644 index c6640267d..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/dual_port_mem.sv +++ /dev/null @@ -1,128 +0,0 @@ -//--------------------------------------------------------------------------------------- -// Amazon FPGA Hardware Development Kit -// -// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Amazon Software License (the "License"). You may not use -// this file except in compliance with the License. A copy of the License is -// located at -// -// http://aws.amazon.com/asl/ -// -// or in the "license" file accompanying this file. This file is distributed on -// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or -// implied. See the License for the specific language governing permissions and -// limitations under the License. -//--------------------------------------------------------------------------------------- -//---------------------------------------------------- -// This is a dual ported BRAM -//---------------------------------------------------- -module dual_port_mem #(parameter WIDTH=64, parameter ADDR_WIDTH=9, parameter DEPTH=512, parameter PIPELINE=0, parameter MEMORY_TYPE = "auto") -( - input clk, - input wea, - input ena, - input[ADDR_WIDTH-1:0] addra, - input[WIDTH-1:0] da, - output logic[WIDTH-1:0] qa, - - input web, - input enb, - input[ADDR_WIDTH-1:0] addrb, - input[WIDTH-1:0] db, - output logic[WIDTH-1:0] qb - - - ); - -`ifndef NO_XILINX_XPM_RAM - - xpm_memory_tdpram # ( - // Common module parameters - .MEMORY_SIZE (WIDTH*DEPTH), //positive integer - .MEMORY_PRIMITIVE (MEMORY_TYPE), //string; "auto", "distributed", "block" or "ultra"; - .CLOCKING_MODE ("common_clock"), //string; "common_clock", "independent_clock" - .MEMORY_INIT_FILE ("none"), //string; "none" or ".mem" - .MEMORY_INIT_PARAM ("" ), //string; - .USE_MEM_INIT (1), //integer; 0,1 - .WAKEUP_TIME ("disable_sleep"), //string; "disable_sleep" or "use_sleep_pin" - .MESSAGE_CONTROL (0), //integer; 0,1 - // Port A module parameters - .WRITE_DATA_WIDTH_A (WIDTH), //positive integer - .READ_DATA_WIDTH_A (WIDTH), //positive integer - .BYTE_WRITE_WIDTH_A (WIDTH), //integer; 8, 9, or WRITE_DATA_WIDTH_A value - .ADDR_WIDTH_A (ADDR_WIDTH), //positive integer - .READ_RESET_VALUE_A ("0"), //string - .READ_LATENCY_A (PIPELINE+1), //non-negative integer - .WRITE_MODE_A ("read_first"), //string; "write_first", "read_first", "no_change" - // Port B module parameters - .WRITE_DATA_WIDTH_B (WIDTH), //positive integer - .READ_DATA_WIDTH_B (WIDTH), //positive integer - .BYTE_WRITE_WIDTH_B (WIDTH), //integer; 8, 9, or WRITE_DATA_WIDTH_B value - .ADDR_WIDTH_B (ADDR_WIDTH), //positive integer - .READ_RESET_VALUE_B ("0"), //vector of READ_DATA_WIDTH_B bits - .READ_LATENCY_B (PIPELINE+1), //non-negative integer - .WRITE_MODE_B ("read_first") //string; "write_first", "read_first", "no_change" - ) xpm_memory_tdpram_inst ( - // Common module ports - .sleep (1'b0), - // Port A module ports - .clka (clk), - .rsta (1'b0), - .ena (ena), - .regcea (1'b1), - .wea (wea), - .addra (addra), - .dina (da), - .injectsbiterra (1'b0), //do not change - .injectdbiterra (1'b0), //do not change - .douta (qa), - .sbiterra (), //do not change - .dbiterra (), //do not change - // Port B module ports - .clkb (clk), - .rstb (1'b0), - .enb (enb), - .regceb (1'b1), - .web (web), - .addrb (addrb), - .dinb (db), - .injectsbiterrb (1'b0), //do not change - .injectdbiterrb (1'b0), //do not change - .doutb (qb), - .sbiterrb (), //do not change - .dbiterrb () //do not change - ); - // End of xpm_memory_tdpram instance declaration - -`else - - logic[WIDTH-1:0] ram[DEPTH-1:0]; - - logic[WIDTH-1:0] rddata_a, rddata_a_q; - logic[WIDTH-1:0] rddata_b, rddata_b_q; - - always @(posedge clk) - if (enb) begin - rddata_b <= ram[addrb]; - end - - always @(posedge clk) - rddata_b_q <= rddata_b; - - always @(posedge clk) - if (wea) begin - ram[addra] <= da; - end - - always @(posedge clk) - rddata_a_q <= 0; - - assign qa = (PIPELINE)? rddata_a_q: rddata_a; - assign qb = (PIPELINE)? rddata_b_q: rddata_b; - -`endif - - - -endmodule diff --git a/examples_hw/apps/gbm_dtrees/hdl/gbm_slave.sv b/examples_hw/apps/gbm_dtrees/hdl/gbm_slave.sv deleted file mode 100644 index 5de9d2b53..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/gbm_slave.sv +++ /dev/null @@ -1,293 +0,0 @@ -/** - * GBM slave - */ -import lynxTypes::*; - -module gbm_slave #( - parameter integer NFEAUTRES_BITS = 16, - parameter integer TREEDEPTH_BITS = 8, - parameter integer PUTREES_BITS = 8, - parameter integer OUTNUMCLS_BITS = 32, - parameter integer LSTOUTMASK_BITS = 16 -) ( - input logic aclk, - input logic aresetn, - - AXI4L.s axi_ctrl, - - // User defined arguments - output logic ap_start, - output logic [NFEAUTRES_BITS-1:0] numFeatures, - output logic [TREEDEPTH_BITS-1:0] treeDepth, - output logic [PUTREES_BITS-1:0] puTrees, - output logic [OUTNUMCLS_BITS-1:0] outputNumCLs, - output logic [LSTOUTMASK_BITS-1:0] lastOutLineMask -); - -// -- Decl ---------------------------------------------------------- -// ------------------------------------------------------------------ -// Constants -localparam integer N_REGS = 6; -localparam integer ADDR_LSB = $clog2(AXIL_DATA_BITS/8); -localparam integer ADDR_MSB = $clog2(N_REGS); -localparam integer AXIL_ADDR_BITS = ADDR_LSB + ADDR_MSB; - -// Internal registers -logic [AXIL_ADDR_BITS-1:0] axi_awaddr; -logic axi_awready; -logic [AXIL_ADDR_BITS-1:0] axi_araddr; -logic axi_arready; -logic [1:0] axi_bresp; -logic axi_bvalid; -logic axi_wready; -logic [AXIL_DATA_BITS-1:0] axi_rdata; -logic [1:0] axi_rresp; -logic axi_rvalid; - -// Registers -logic [N_REGS-1:0][AXIL_DATA_BITS-1:0] slv_reg; -logic slv_reg_rden; -logic slv_reg_wren; -logic aw_en; - -// -- Def ----------------------------------------------------------- -// ------------------------------------------------------------------ - -// -- Register map ----------------------------------------------------------------------- -// 0 (W1S) : AP start ( -localparam integer AP_CTRL_REG = 0; -// 0 - start -// 1 (WR) : Number of features -localparam integer NFEAUTERS_REG = 1; -// 2 (WR) : Treedepth -localparam integer TREEDEPTH_REG = 2; -// 3 (WR) : Putrees -localparam integer PUTREES_REG = 3; -// 4 (WR) : OutputNumCLs -localparam integer OUTNUMCLS_REG = 4; -// 5 (WR) : LastOutLineMask -localparam integer LSTOUTMASK_REG = 5; - -// Write process -assign slv_reg_wren = axi_wready && axi_ctrl.wvalid && axi_awready && axi_ctrl.awvalid; - -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) begin - slv_reg <= 0; - end - else begin - // Control - slv_reg[AP_CTRL_REG] <= 0; - - if(slv_reg_wren) begin - case (axi_awaddr[ADDR_LSB+:ADDR_MSB]) - AP_CTRL_REG: // Control - for (int i = 0; i < (AXIL_DATA_BITS/8); i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[AP_CTRL_REG][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - NFEAUTERS_REG: // Number of features - for (int i = 0; i < (AXIL_DATA_BITS/8); i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[NFEAUTERS_REG][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - TREEDEPTH_REG: // Treedepth - for (int i = 0; i < (AXIL_DATA_BITS/8); i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[TREEDEPTH_REG][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - PUTREES_REG: // Putrees - for (int i = 0; i < (AXIL_DATA_BITS/8); i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[PUTREES_REG][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - OUTNUMCLS_REG: // Output number of CLs - for (int i = 0; i < (AXIL_DATA_BITS/8); i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[OUTNUMCLS_REG][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - LSTOUTMASK_REG: // Last out line mask - for (int i = 0; i < (AXIL_DATA_BITS/8); i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[LSTOUTMASK_REG][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - default : ; - endcase - end - end -end - -// Read process -assign slv_reg_rden = axi_arready & axi_ctrl.arvalid & ~axi_rvalid; - -always_ff @(posedge aclk) begin - if( aresetn == 1'b0 ) begin - axi_rdata <= 0; - end - else begin - if(slv_reg_rden) begin - axi_rdata <= 0; - - case (axi_araddr[ADDR_LSB+:ADDR_MSB]) - NFEAUTERS_REG: // Key low - axi_rdata[NFEAUTRES_BITS-1:0] <= slv_reg[NFEAUTERS_REG][NFEAUTRES_BITS-1:0]; - TREEDEPTH_REG: // Key high - axi_rdata[TREEDEPTH_BITS-1:0] <= slv_reg[TREEDEPTH_REG][TREEDEPTH_BITS-1:0]; - PUTREES_REG: // Key high - axi_rdata[PUTREES_BITS-1:0] <= slv_reg[PUTREES_REG][PUTREES_BITS-1:0]; - OUTNUMCLS_REG: // Key high - axi_rdata[OUTNUMCLS_BITS-1:0] <= slv_reg[OUTNUMCLS_REG][OUTNUMCLS_BITS-1:0]; - LSTOUTMASK_REG: // Key high - axi_rdata[LSTOUTMASK_BITS-1:0] <= slv_reg[LSTOUTMASK_REG][LSTOUTMASK_BITS-1:0]; - default: ; - endcase - end - end -end - -// Output -always_comb begin - ap_start = slv_reg[AP_CTRL_REG][0]; - numFeatures = slv_reg[NFEAUTERS_REG][15:0]; - treeDepth = slv_reg[TREEDEPTH_REG][7:0]; - puTrees = slv_reg[PUTREES_REG][7:0]; - outputNumCLs = slv_reg[OUTNUMCLS_REG][31:0]; - lastOutLineMask = slv_reg[LSTOUTMASK_REG][15:0]; -end - - -// -------------------------------------------------------------------------------------- -// AXI CTRL -// -------------------------------------------------------------------------------------- -// Don't edit - -// I/O -assign axi_ctrl.awready = axi_awready; -assign axi_ctrl.arready = axi_arready; -assign axi_ctrl.bresp = axi_bresp; -assign axi_ctrl.bvalid = axi_bvalid; -assign axi_ctrl.wready = axi_wready; -assign axi_ctrl.rdata = axi_rdata; -assign axi_ctrl.rresp = axi_rresp; -assign axi_ctrl.rvalid = axi_rvalid; - -// awready and awaddr -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_awready <= 1'b0; - axi_awaddr <= 0; - aw_en <= 1'b1; - end - else - begin - if (~axi_awready && axi_ctrl.awvalid && axi_ctrl.wvalid && aw_en) - begin - axi_awready <= 1'b1; - aw_en <= 1'b0; - axi_awaddr <= axi_ctrl.awaddr; - end - else if (axi_ctrl.bready && axi_bvalid) - begin - aw_en <= 1'b1; - axi_awready <= 1'b0; - end - else - begin - axi_awready <= 1'b0; - end - end -end - -// arready and araddr -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_arready <= 1'b0; - axi_araddr <= 0; - end - else - begin - if (~axi_arready && axi_ctrl.arvalid) - begin - axi_arready <= 1'b1; - axi_araddr <= axi_ctrl.araddr; - end - else - begin - axi_arready <= 1'b0; - end - end -end - -// bvalid and bresp -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_bvalid <= 0; - axi_bresp <= 2'b0; - end - else - begin - if (axi_awready && axi_ctrl.awvalid && ~axi_bvalid && axi_wready && axi_ctrl.wvalid) - begin - axi_bvalid <= 1'b1; - axi_bresp <= 2'b0; - end - else - begin - if (axi_ctrl.bready && axi_bvalid) - begin - axi_bvalid <= 1'b0; - end - end - end -end - -// wready -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_wready <= 1'b0; - end - else - begin - if (~axi_wready && axi_ctrl.wvalid && axi_ctrl.awvalid && aw_en ) - begin - axi_wready <= 1'b1; - end - else - begin - axi_wready <= 1'b0; - end - end -end - -// rvalid and rresp (1Del?) -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_rvalid <= 0; - axi_rresp <= 0; - end - else - begin - if (axi_arready && axi_ctrl.arvalid && ~axi_rvalid) - begin - axi_rvalid <= 1'b1; - axi_rresp <= 2'b0; - end - else if (axi_rvalid && axi_ctrl.rready) - begin - axi_rvalid <= 1'b0; - end - end -end - -endmodule // gbm slave \ No newline at end of file diff --git a/examples_hw/apps/gbm_dtrees/hdl/pe_datapath.sv b/examples_hw/apps/gbm_dtrees/hdl/pe_datapath.sv deleted file mode 100644 index d0fe17479..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/pe_datapath.sv +++ /dev/null @@ -1,369 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - -/* - PE Datapath flow: - - - Read Tree Node: This stage consumes either the input tree instruction or the next node instruction. - This stage issues a read address to tree memory and delays all the necessary information - for the tree memory delay. By the end of the stage, the tree node basic and extra part are - available in addition to the delayed information. - - - Read feature: Use the feature index in the tree node to compute the feature address and read it from - the data memory. Then, we delay the current node instruction and obtained tree node info. - - - Read Split Set: In this stage, we compute the address for large bitset word to read from - tree memory, If there is no large split bitset then the tree memory output is ignored. - In addition, the early tree evaluation occurs. In parallel, we use the feature value to select - from the small bitset, we use it also to compare to the split value, and we use the feature value - to check if it is outside the boundaries of the large bitset. In addition, If node_nop flag is zero, - then the node_res_val is not used, hence we store W2 of the tree node in its place to save resources. - In addition, we check if the feature value is NaN or not. - - Evaluate Node: At this stage we have all the necessary information to process a node. - Then based on the operation type the evaluation happens. - Pick A branch: In this stage, based on the node evaluation result then we compute next node - to evaluate, or pick result if no children on the next branch and set NOP if we - are not at the last level. In this branch, also if we at the last level, then we - output the result if the tree is not empty. -*/ - -import DTPackage::*; - -module pe_datapath ( - - input wire clk, - input wire rst_n, - - input wire [MAX_TREE_DEPTH_BITS-1:0] tree_depth, - - // tree instruction - input TreeInstruction_t tree_instruction, - input wire tree_instruction_valid, - output wire tree_instruction_ready, - - // data memory read port - output wire [TUPLE_OFFSET_BITS:0] data_mem_rd_addr, - output wire data_mem_ren, - input wire [31:0] data_mem_feature, - - // tree memory read port A - output wire [TREE_OFFSET_BITS:0] tree_mem_rd_addr_a, - output wire tree_mem_ren_a, - input TreeNode_t tree_node_basic, - - // tree memory read port B - output wire [TREE_OFFSET_BITS:0] tree_mem_rd_addr_b, - output wire tree_mem_ren_b, - input wire [31:0] node_large_bitset, - - // result output - output wire [31:0] tree_eval_result, - output wire tree_eval_last, - output wire tree_eval_result_valid -); - - - - -NodeInstruction_t next_node_instr; -wire next_node_instr_valid; - -NodeInstruction_t curr_node_instr_s1; -wire curr_node_instr_valid_s1; -NodeInstruction_t curr_node_instr_s2; -wire curr_node_instr_valid_s2; -NodeInstruction_t curr_node_instr_s3; -NodeInstruction_t curr_node_instr_s3_1; -wire curr_node_instr_valid_s3; -NodeInstruction_t curr_node_instr_s4; -NodeInstruction_t curr_node_instr_s4_1; -wire curr_node_instr_valid_s4; -NodeInstruction_t curr_node_instr_s5; -NodeInstruction_t curr_node_instr_s5_1; -wire curr_node_instr_valid_s5; - -wire [31:0] small_bitset; -wire is_Feature_NaN; -wire small_bitset_eval; -wire split_value_eval; -wire feature_data_outside_bitset; - -wire is_Feature_NaN_s4; -wire small_bitset_eval_s4; -wire split_value_eval_s4; -wire feature_data_outside_bitset_s4; -wire [4:0] feature_data_s4; - -wire is_Feature_NaN_s5; - -TreeNode_t tree_node_basic_s3; -TreeNode_t tree_node_basic_s4; -TreeNode_t tree_node_basic_s5; - -reg go_right; -reg node_eval; - -wire [10:0] left_child_offset_1; -wire [10:0] left_child_offset_2; - -wire [10:0] right_child_offset; - -wire [10:0] next_node_address; -wire [MAX_TREE_DEPTH_BITS-1:0] next_node_level; - -wire next_node_nop; -wire [10:0] next_child_offset; -wire [10:0] result_offset; -wire next_node_leaf; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Read Node Stage ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -/* -*/ -always @(*) begin - if(next_node_instr_valid) begin - curr_node_instr_s1 = next_node_instr; - end - else begin - curr_node_instr_s1 = '{node_address: {tree_instruction.tree_offset, 1'b0}, - tuple_offset: tree_instruction.tuple_offset, - node_level : 3'b000, - empty_tree : tree_instruction.empty_tree, - node_nop : 1'b0, - last_tree : tree_instruction.last_tree, - leaf_node : 1'b0, - node_res_val: 32'b0 - }; - end -end - -assign curr_node_instr_valid_s1 = next_node_instr_valid | tree_instruction_valid; - -assign tree_instruction_ready = !next_node_instr_valid; - -// Send read requests to tree memory -assign tree_mem_rd_addr_a = curr_node_instr_s1.node_address; -assign tree_mem_ren_a = curr_node_instr_valid_s1; - -// Pipeline to next stage -delay #(.DATA_WIDTH( $bits(NodeInstruction_t)), - .DELAY_CYCLES(TREE_MEM_READ_LATENCY) - ) ReadNodeStageDelay( - .clk ( clk ), - .rst_n ( rst_n ), - .data_in ( curr_node_instr_s1 ), // - .data_in_valid ( curr_node_instr_valid_s1 ), - .data_out ( curr_node_instr_s2 ), - .data_out_valid ( curr_node_instr_valid_s2 ) - ); - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Read Feature Stage ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -/* - This stage reads the feature data corresponding to current tree node. -*/ -assign data_mem_ren = curr_node_instr_valid_s2 && (tree_node_basic.node_type.findex != 8'hFF); -assign data_mem_rd_addr = {curr_node_instr_s2.tuple_offset, 1'b0} + tree_node_basic.node_type.findex; // 10-bit address - -// Pipeline to next stage -delay #(.DATA_WIDTH($bits(NodeInstruction_t) + $bits(TreeNode_t)), - .DELAY_CYCLES(DATA_MEM_READ_LATENCY) - ) ReadFeatureStageDelay( - .clk (clk), - .rst_n (rst_n), - .data_in ( {curr_node_instr_s2, tree_node_basic} ), // - .data_in_valid ( curr_node_instr_valid_s2 ), - .data_out ( {curr_node_instr_s3, tree_node_basic_s3} ), - .data_out_valid ( curr_node_instr_valid_s3 ) - ); -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Read Split Set/Value Stage ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -assign tree_mem_rd_addr_b = curr_node_instr_s3.node_address + - ((curr_node_instr_s3.leaf_node)? 0 : - (((!tree_node_basic_s3.node_type.left_child)? 1'b1 : 0) + - ((!tree_node_basic_s3.node_type.right_child)? 1'b1 : 0) + - data_mem_feature[15:5] + 11'b00000010)); -assign tree_mem_ren_b = curr_node_instr_valid_s3; - -// Early Node evaluation -assign curr_node_instr_s3_1 = '{ node_address: curr_node_instr_s3.node_address, - tuple_offset: curr_node_instr_s3.tuple_offset, - node_level : curr_node_instr_s3.node_level, - empty_tree : curr_node_instr_s3.empty_tree, - node_nop : ((curr_node_instr_s3.node_nop)? 1'b1 : !curr_node_instr_s3.leaf_node && (tree_node_basic_s3.node_type.findex == 8'hFF) ), - last_tree : curr_node_instr_s3.last_tree, - leaf_node : curr_node_instr_s3.leaf_node, - node_res_val: ( (curr_node_instr_s3.node_nop)? curr_node_instr_s3.node_res_val : {tree_node_basic_s3.word_1_h, tree_node_basic_s3.word_1_l}) - }; - -assign small_bitset = {tree_node_basic_s3.word_1_h, tree_node_basic_s3.word_1_l}; - -assign is_Feature_NaN = (data_mem_feature[30:23] == 7'b1111111) && (|data_mem_feature[22:0]); -assign small_bitset_eval = (data_mem_feature[31:5] == 0)? small_bitset[ data_mem_feature[4:0] ] : 1'b0; -assign split_value_eval = data_mem_feature < {tree_node_basic_s3.word_1_h, tree_node_basic_s3.word_1_l}; - -assign feature_data_outside_bitset = (data_mem_feature < tree_node_basic_s3.word_1_h) || - (data_mem_feature > (tree_node_basic_s3.word_1_h + (tree_node_basic_s3.word_1_l << 5) ) ); - -// Pipeline to next stage -delay #(.DATA_WIDTH($bits(NodeInstruction_t) + $bits(TreeNode_t) + 5 + 1 + 1 + 1 + 1), - .DELAY_CYCLES(TREE_MEM_READ_LATENCY) - ) ReadSplitSetStageDelay( - .clk (clk), - .rst_n (rst_n), - .data_in ( {curr_node_instr_s3_1, tree_node_basic_s3, data_mem_feature[4:0], small_bitset_eval, split_value_eval, feature_data_outside_bitset, is_Feature_NaN} ), // - .data_in_valid ( curr_node_instr_valid_s3 ), - .data_out ( {curr_node_instr_s4, tree_node_basic_s4, feature_data_s4, small_bitset_eval_s4, split_value_eval_s4, feature_data_outside_bitset_s4, is_Feature_NaN_s4} ), - .data_out_valid ( curr_node_instr_valid_s4 ) - ); - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Node Evaluation Stage ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -/* -*/ - -always@(posedge clk)begin - if(~rst_n) begin - node_eval <= 1'b0; - end - else begin - case (tree_node_basic_s4.node_type.op_type) - 2'b00: node_eval <= split_value_eval_s4; - 2'b10: node_eval <= small_bitset_eval_s4; - 2'b11: node_eval <= (feature_data_outside_bitset_s4)? 1'b0 : node_large_bitset[ feature_data_s4 ]; - default : node_eval <= 1'b0; - endcase - end -end - -// Early Node evaluation -assign curr_node_instr_s4_1 = '{ node_address: curr_node_instr_s4.node_address, - tuple_offset: curr_node_instr_s4.tuple_offset, - node_level : curr_node_instr_s4.node_level, - empty_tree : curr_node_instr_s4.empty_tree, - node_nop : curr_node_instr_s4.node_nop, - last_tree : curr_node_instr_s4.last_tree, - leaf_node : curr_node_instr_s4.leaf_node, - node_res_val: ( (curr_node_instr_s4.node_nop || !curr_node_instr_s4.leaf_node)? curr_node_instr_s4.node_res_val : node_large_bitset ) - }; - -// Pipeline to next stage -delay #(.DATA_WIDTH($bits(NodeInstruction_t) + $bits(TreeNode_t) + 1), - .DELAY_CYCLES(1) - ) EvalNodeStageDelay( - .clk (clk), - .rst_n (rst_n), - .data_in ( {curr_node_instr_s4_1, tree_node_basic_s4, is_Feature_NaN_s4} ), // - .data_in_valid ( curr_node_instr_valid_s4 ), - .data_out ( {curr_node_instr_s5, tree_node_basic_s5, is_Feature_NaN_s5} ), - .data_out_valid ( curr_node_instr_valid_s5 ) - ); - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Pick Branch Stage ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -/* -*/ - -always@(*)begin - if(tree_node_basic_s5.node_type.split_dir == 4'b0001) begin - go_right = is_Feature_NaN_s5; - end - else if(tree_node_basic_s5.node_type.split_dir[0] == 1'b0) begin - if(is_Feature_NaN_s5) begin - go_right = 1'b0; - end - else begin - go_right = node_eval; - end - end - else begin - if(is_Feature_NaN_s5) begin - go_right = 1'b1; - end - else begin - go_right = node_eval; - end - end -end - -// compute left child offset, -assign left_child_offset_1 = 11'b00000000010 + ((tree_node_basic_s5.node_type.left_child)? 0 : 1'b1) + ((tree_node_basic_s5.node_type.right_child)? 0 : 1'b1); -assign left_child_offset_2 = left_child_offset_1 + ( (tree_node_basic_s5.node_type.op_type != 3)? 11'd0 : tree_node_basic_s5.word_1_l[10:0]); -// compute right child offset -assign right_child_offset = left_child_offset_2 + tree_node_basic_s5.right_child_offset[10:0]; -assign next_child_offset = (go_right && tree_node_basic_s5.node_type.left_child)? right_child_offset : left_child_offset_2; - -// compute leaf node result offset -assign result_offset = (go_right)? 11'b00000000010 + ((tree_node_basic_s5.node_type.left_child)? 0 : 1'b1) : 11'b00000000010; -assign next_node_leaf = (go_right)? !tree_node_basic_s5.node_type.right_child : !tree_node_basic_s5.node_type.left_child; - -// Next level node address -assign next_node_address = curr_node_instr_s5.node_address + ( (next_node_leaf)? result_offset : next_child_offset); - -assign next_node_level = (curr_node_instr_s5.node_level == (tree_depth))? (tree_depth) : curr_node_instr_s5.node_level + 1'b1; -assign next_node_nop = curr_node_instr_s5.node_nop || ((curr_node_instr_s5.node_level < (tree_depth) && curr_node_instr_s5.leaf_node)? 1'b1 : 1'b0); - -assign curr_node_instr_s5_1 = '{ node_address: next_node_address, - tuple_offset: curr_node_instr_s5.tuple_offset, - node_level : next_node_level, - empty_tree : curr_node_instr_s5.empty_tree, - node_nop : next_node_nop, - last_tree : curr_node_instr_s5.last_tree, - leaf_node : ( curr_node_instr_s5.leaf_node || next_node_leaf), - node_res_val: curr_node_instr_s5.node_res_val - }; -// Pipeline to next stage -delay #(.DATA_WIDTH( $bits(NodeInstruction_t) ), - .DELAY_CYCLES(1) - ) PickBranchStageDelay( - .clk (clk), - .rst_n (rst_n), - .data_in ( curr_node_instr_s5_1 ), // - .data_in_valid ( curr_node_instr_valid_s5 && (curr_node_instr_s5.node_level < (tree_depth)) ), - .data_out ( next_node_instr ), - .data_out_valid ( next_node_instr_valid ) - ); - - -assign tree_eval_result = (curr_node_instr_s5.empty_tree)? 32'b0 : curr_node_instr_s5.node_res_val; -assign tree_eval_result_valid = curr_node_instr_valid_s5 && (curr_node_instr_s5.node_level == (tree_depth) ); -assign tree_eval_last = curr_node_instr_s5.last_tree; - - - -endmodule \ No newline at end of file diff --git a/examples_hw/apps/gbm_dtrees/hdl/processing_element.sv b/examples_hw/apps/gbm_dtrees/hdl/processing_element.sv deleted file mode 100644 index 19194934e..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/processing_element.sv +++ /dev/null @@ -1,442 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - - -import DTPackage::*; - -/* - -*/ -module processing_element #(parameter PE_ID = 0 ) - ( - input wire clk, - input wire rst_n, - - input wire [DATA_LINE_WIDTH-1:0] data_line_in, - input wire data_line_in_valid, - input wire data_line_in_last, - input wire data_line_in_ctrl, - input wire data_line_in_prog, - input wire [NUM_PUS_PER_CLUSTER_BITS-1:0] data_line_in_pu, - output reg data_line_in_ready, - - output reg [DATA_LINE_WIDTH-1:0] data_line_out, - output reg data_line_out_valid, - output reg data_line_out_ctrl, - output reg data_line_out_last, - output reg data_line_out_prog, - output reg [NUM_PUS_PER_CLUSTER_BITS-1:0] data_line_out_pu, - - output reg [DATA_PRECISION-1:0] pu_tree_leaf_out, - output reg pu_tree_leaf_out_valid, - output reg pu_tree_leaf_out_last - ); - -localparam INSTRUCTION_DELAY = NUM_PUS_PER_CLUSTER - PE_ID - 1; // PU pipeline depth-1 - PU_ID -localparam TREE_MODE = 1'b0; -localparam DATA_MODE = 1'b1; -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Signals Declarations ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - - -TreeInstruction_t dp_tree_instruction; -wire dp_tree_instruction_valid; -wire dp_tree_instruction_ready; - -wire dp_data_mem_ren; -wire [31:0] dp_data_mem_feature; -wire [TUPLE_OFFSET_BITS:0] dp_data_mem_rd_addr; - -// tree memory read port A -wire [TREE_OFFSET_BITS:0] dp_tree_mem_rd_addr_a; -wire dp_tree_mem_ren_a; -TreeNode_t dp_tree_node_basic; -wire [63:0] dp_tree_node_basic_vec; - -// tree memory read port B -wire [TREE_OFFSET_BITS:0] dp_tree_mem_rd_addr_b; -wire dp_tree_mem_ren_b; -wire [31:0] dp_node_large_bitset; - -// result output -wire [31:0] dp_tree_eval_result; -wire dp_tree_eval_last; -wire dp_tree_eval_result_valid; - - -reg [1:0] curr_tree_id; -reg [8:0] curr_tuple_offset; -reg [9:0] time_stamp; -reg tuple_old_enough_set; - -wire tuple_instr_fifo_ready; -wire tuple_instr_fifo_valid; -wire [9:0] tuple_instr_fifo_dout; -wire tuple_instr_re; -wire tuple_old_enough; - - -wire curr_feature_done; -reg [9:0] features_mem_count; -reg [8:0] features_wr_addr; - -reg [2:0] local_num_trees; -reg [2:0] local_num_trees_minus_one; -reg [TREE_OFFSET_BITS-1:0] received_tree_lines; -reg [TREE_OFFSET_BITS-1:0] tree_prog_addr; -reg [TREE_OFFSET_BITS-1:0] tree_offsets[3:0]; -wire [TREE_OFFSET_BITS :0] tree_mem_addr_a; - -reg [4:0] num_trees_per_pu_minus_one; -reg [3:0] tree_depth; -reg [8:0] num_lines_per_tuple; - -reg [7:0] tree_addra[5:0]; -reg [7:0] tree_addrb[5:0]; - -reg [2:0] rd_a_count; -reg [2:0] rd_b_count; - -reg first_word_correct; -reg [63:0] first_word; -reg pe_state; - -reg [63:0] tree_mem_out_a; -reg [31:0] data_mem_out_a; -reg tree_out_a_set; -reg data_out_a_set; -wire dp_tree_valid_out1; -wire dp_data_mem_feature_valid; -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// PU Programming Logic ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -always @(posedge clk) begin - if(~rst_n) begin - data_line_out_valid <= 0; - data_line_out_last <= 0; - data_line_out_ctrl <= 0; - data_line_out_prog <= 0; - - pe_state <= TREE_MODE; - end - else begin - data_line_out_valid <= data_line_in_valid; - data_line_out_last <= data_line_in_last; - data_line_out_ctrl <= data_line_in_ctrl; - data_line_out_prog <= data_line_in_prog; - - if(data_line_in_valid | (pe_state == DATA_MODE) ) begin - pe_state <= DATA_MODE; - end - end - // - data_line_out <= data_line_in; - data_line_out_pu <= data_line_in_pu; -end - -always @(posedge clk) begin - if(~rst_n) begin - num_trees_per_pu_minus_one <= 5'b0; - tree_depth <= 4'b0; - num_lines_per_tuple <= 9'b0; - end - else if(data_line_in_ctrl) begin - num_trees_per_pu_minus_one <= data_line_in[4:0]; - tree_depth <= data_line_in[8+4-1:8]; - num_lines_per_tuple <= data_line_in[16+9-1:16]; - end -end - -//assign pu_debug_counters = {local_num_trees, received_tree_lines[8:0], tree_offsets[3][8:0], tree_offsets[2][8:0], tree_offsets[1][8:0], tree_offsets[0][8:0]}; -assign pu_debug_counters = {tree_addrb[5], tree_addrb[4], tree_addrb[3], tree_addrb[2], tree_addrb[1], tree_addrb[0], tree_addra[5], tree_addra[4], tree_addra[3], tree_addra[2], tree_addra[1], tree_addra[0]}; -assign pu_debug_counters2 = {tree_mem_out_a[31:0], first_word}; -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Memory Banks ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -genvar j; -generate for ( j = 0; j < 6; j=j+1) begin - always@(posedge clk) begin - if(~rst_n) begin - tree_addra[j] <= 0; - tree_addrb[j] <= 0; - end - else begin - if(dp_tree_mem_ren_a && (rd_a_count == j)) begin - tree_addra[j] <= tree_mem_addr_a[7:0]; - end - - if(dp_tree_mem_ren_b && (rd_b_count == j)) begin - tree_addrb[j] <= dp_tree_mem_rd_addr_b[7:0]; - end - end - end -end -endgenerate - -integer i; -always@(posedge clk) begin - if(~rst_n) begin - rd_a_count <= 0; - rd_b_count <= 0; - - tree_mem_out_a <= 0; - data_mem_out_a <= 0; - tree_out_a_set <= 1'b0; - data_out_a_set <= 1'b0; - first_word_correct <= 0; - first_word <= 0; - end - else begin - if(dp_tree_mem_ren_a && rd_a_count < 6) begin - rd_a_count <= rd_a_count + 1'b1; - end - - if(dp_tree_mem_ren_b && rd_b_count < 6) begin - rd_b_count <= rd_b_count + 1'b1; - end - // - if(dp_tree_valid_out1 && ~tree_out_a_set) begin - tree_mem_out_a <= dp_tree_node_basic_vec; - tree_out_a_set <= 1'b1; - end - - if(dp_data_mem_feature_valid && ~data_out_a_set) begin - data_mem_out_a <= dp_data_mem_feature; - data_out_a_set <= 1'b1; - end - - // - if((tree_prog_addr == 0) && (data_line_in_prog && (data_line_in_pu == PE_ID) )) begin - first_word_correct <= data_line_in[15:0] == 16'h200c; - first_word <= data_line_in; - end - end -end - - -//----------------------------- Tree Nodes Weight memory ------------------------------// -Tree_Memory #( .DATA_WIDTH(64), - .ADDR_WIDTH(11) ) -TreeNodes( - - .clk ( clk ), - .rst_n ( rst_n ), - .we ( data_line_in_prog && (data_line_in_pu == PE_ID) ), - .rea ( dp_tree_mem_ren_a ), - .reb ( dp_tree_mem_ren_b ), - .addr_port_a ( tree_mem_addr_a ), - .addr_port_b ( dp_tree_mem_rd_addr_b ), - .din ( data_line_in ), - .dout1 ( dp_tree_node_basic_vec ), - .valid_out1 ( dp_tree_valid_out1), - .dout2 ( dp_node_large_bitset ), - .valid_out2 () -); -assign dp_tree_node_basic.word_1_h = dp_tree_node_basic_vec[63:48]; -assign dp_tree_node_basic.word_1_l = dp_tree_node_basic_vec[47:32]; -assign dp_tree_node_basic.right_child_offset = dp_tree_node_basic_vec[31:16]; -assign dp_tree_node_basic.node_type.op_type = dp_tree_node_basic_vec[1:0]; -assign dp_tree_node_basic.node_type.left_child = dp_tree_node_basic_vec[2]; -assign dp_tree_node_basic.node_type.right_child = dp_tree_node_basic_vec[3]; -assign dp_tree_node_basic.node_type.findex = dp_tree_node_basic_vec[11:4]; -assign dp_tree_node_basic.node_type.split_dir = dp_tree_node_basic_vec[15:12]; - - -assign tree_mem_addr_a = ( pe_state == TREE_MODE )? {tree_prog_addr, 1'b0} : dp_tree_mem_rd_addr_a; - -always @(posedge clk) begin - // - if(~rst_n) begin - tree_prog_addr <= 0; - local_num_trees <= 0; - received_tree_lines <= 0; - - local_num_trees_minus_one <= 0; - - for (i = 0; i < 4; i++) begin - tree_offsets[ i ] <= 0; - end - end - else begin - local_num_trees_minus_one <= local_num_trees; - // - if(data_line_in_prog && (data_line_in_pu == PE_ID)) begin - tree_prog_addr <= tree_prog_addr + 1'b1; - received_tree_lines <= received_tree_lines + 1'b1; - - if(data_line_in_last && (local_num_trees[1:0] < num_trees_per_pu_minus_one[1:0])) begin - tree_offsets[ local_num_trees[1:0] + 1'b1 ] <= received_tree_lines + 1'b1; - local_num_trees <= local_num_trees + 1'b1; - end - end - end -end -//--------------------------- Input tuple features memory -----------------------------// -/* We write to the features memory when flags indicate incoming data is tuples and not programming data -*/ -Data_Memory #( .DATA_WIDTH(64), - .ADDR_WIDTH(10) ) -SamplesFeatures_Mem( - .clk (clk), - .rst_n (rst_n), - .we (data_line_in_valid), - .re (dp_data_mem_ren), - .raddr (dp_data_mem_rd_addr), - .waddr (features_wr_addr), - .din (data_line_in), - .dout (dp_data_mem_feature), - .valid_out (dp_data_mem_feature_valid) -); - -always @(posedge clk) begin - if(~rst_n) begin - features_wr_addr <= 9'b0; - features_mem_count <= 0; - end - else begin - features_wr_addr <= features_wr_addr + ((data_line_in_valid)? 9'd1 : 9'd0); - features_mem_count <= features_mem_count + ((data_line_in_valid)? 9'd1 : 9'd0) - ((curr_feature_done)? num_lines_per_tuple : 9'b0); - end -end - -assign curr_feature_done = dp_tree_eval_result_valid & dp_tree_eval_last; - -assign data_line_in_ready = tuple_instr_fifo_ready & (features_mem_count < (512- PE_ID - 1)); -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Tuple Instruction FIFO ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -/* Once all features of a tuple are in features memory, we enqueue an instruction to execute all the - trees in the PU on the current tuple features, the instruction simply include the tuple offset. -*/ -RegBasedFIFO #(.FIFO_WIDTH(10), - .FIFO_DEPTH_BITS(2) - ) tuple_instr_fifo ( - .clk (clk), - .rst_n (rst_n), - .data_in ( time_stamp ), - .data_in_valid ( data_line_in_valid && data_line_in_last ), - .data_out_ready (tuple_instr_re), - .data_out (tuple_instr_fifo_dout), - .data_out_valid (tuple_instr_fifo_valid), - .data_in_ready (tuple_instr_fifo_ready) - ); - -assign tuple_instr_re = dp_tree_instruction_ready && (tuple_old_enough || tuple_old_enough_set) && (curr_tree_id == num_trees_per_pu_minus_one); - -assign tuple_old_enough = tuple_instr_fifo_valid && ((time_stamp <= tuple_instr_fifo_dout) || ((time_stamp - tuple_instr_fifo_dout) > INSTRUCTION_DELAY)); - -assign dp_tree_instruction_valid = tuple_instr_fifo_valid && (tuple_old_enough || tuple_old_enough_set); - -assign dp_tree_instruction = '{tree_offset: tree_offsets[curr_tree_id], - tuple_offset: curr_tuple_offset, - last_tree: (curr_tree_id == num_trees_per_pu_minus_one), - empty_tree: (curr_tree_id > local_num_trees)}; - -// Flags, counters used in issuing tree instructions to the datapath and synchronizing logic with other PEs. -always @(posedge clk) begin - if(~rst_n) begin - time_stamp <= 0; - tuple_old_enough_set <= 0; - curr_tuple_offset <= 0; - curr_tree_id <= 0; - end - else begin - // tuple_old_enough_set - tuple_old_enough_set <= (tuple_instr_re && tuple_instr_fifo_valid)? 1'b0 : (tuple_old_enough_set || tuple_old_enough); - - // time_stamp - time_stamp <= time_stamp + 1'b1; - - // curr_tuple_offset - if(tuple_instr_fifo_valid && tuple_instr_re) begin - curr_tuple_offset <= curr_tuple_offset + num_lines_per_tuple; - end - - // curr_tree_id - if(tuple_instr_fifo_valid && dp_tree_instruction_ready && (tuple_old_enough || tuple_old_enough_set)) begin - if(curr_tree_id == num_trees_per_pu_minus_one) begin - curr_tree_id <= 0; - end - else begin - curr_tree_id <= curr_tree_id + 1'b1; - end - end - end -end -//////////////////////////////////////////////////////////////////////////////////////////////////// -// PE Datapath -pe_datapath pe_datapath( - .clk (clk), - .rst_n (rst_n), - .tree_depth (tree_depth), - // tree instruction - .tree_instruction (dp_tree_instruction), - .tree_instruction_valid (dp_tree_instruction_valid), - .tree_instruction_ready (dp_tree_instruction_ready), - // data memory read port - .data_mem_rd_addr (dp_data_mem_rd_addr), - .data_mem_ren (dp_data_mem_ren), - .data_mem_feature (dp_data_mem_feature), - // tree memory read port A - .tree_mem_rd_addr_a (dp_tree_mem_rd_addr_a), - .tree_mem_ren_a (dp_tree_mem_ren_a), - .tree_node_basic (dp_tree_node_basic), - // tree memory read port B - .tree_mem_rd_addr_b (dp_tree_mem_rd_addr_b), - .tree_mem_ren_b (dp_tree_mem_ren_b), - .node_large_bitset (dp_node_large_bitset), - // result output - .tree_eval_result (dp_tree_eval_result), - .tree_eval_last (dp_tree_eval_last), - .tree_eval_result_valid (dp_tree_eval_result_valid) -); - -always @(posedge clk) begin - if(~rst_n) begin - pu_tree_leaf_out <= 0; - pu_tree_leaf_out_last <= 0; - pu_tree_leaf_out_valid <= 0; - end - else begin - pu_tree_leaf_out <= dp_tree_eval_result; - pu_tree_leaf_out_last <= dp_tree_eval_last; - pu_tree_leaf_out_valid <= dp_tree_eval_result_valid; - end -end - - - -endmodule // DTPU - - diff --git a/examples_hw/apps/gbm_dtrees/hdl/processing_element_async.sv b/examples_hw/apps/gbm_dtrees/hdl/processing_element_async.sv deleted file mode 100644 index 87929fcde..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/processing_element_async.sv +++ /dev/null @@ -1,453 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - - -import DTPackage::*; - -/* - -*/ -module processing_element_async #(parameter PE_ID = 0 ) - ( - input wire clk, - input wire rst_n, - - input wire [DATA_LINE_WIDTH-1:0] data_line_in, - input wire data_line_in_valid, - input wire data_line_in_last, - input wire data_line_in_ctrl, - input wire data_line_in_prog, - input wire [NUM_PUS_PER_CLUSTER_BITS-1:0] data_line_in_pu, - output reg data_line_in_ready, - - output reg [DATA_LINE_WIDTH-1:0] data_line_out, - output reg data_line_out_valid, - output reg data_line_out_ctrl, - output reg data_line_out_last, - output reg data_line_out_prog, - output reg [NUM_PUS_PER_CLUSTER_BITS-1:0] data_line_out_pu, - - output reg [DATA_PRECISION-1:0] pu_tree_leaf_out, - output reg pu_tree_leaf_out_valid, - output reg pu_tree_leaf_out_last, - - // debug counters - output wire [95:0] pu_debug_counters, - output wire [95:0] pu_debug_counters2 - - ); - -localparam INSTRUCTION_DELAY = NUM_PUS_PER_CLUSTER - PE_ID - 1; // PU pipeline depth-1 - PU_ID -localparam TREE_MODE = 1'b0; -localparam DATA_MODE = 1'b1; -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Signals Declarations ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - - -TreeInstruction_t dp_tree_instruction; -wire dp_tree_instruction_valid; -wire dp_tree_instruction_ready; - -wire dp_data_mem_ren; -wire [31:0] dp_data_mem_feature; -wire [TUPLE_OFFSET_BITS:0] dp_data_mem_rd_addr; - -// tree memory read port A -wire [TREE_OFFSET_BITS:0] dp_tree_mem_rd_addr_a; -wire dp_tree_mem_ren_a; -TreeNode_t dp_tree_node_basic; -wire [63:0] dp_tree_node_basic_vec; - -// tree memory read port B -wire [TREE_OFFSET_BITS:0] dp_tree_mem_rd_addr_b; -wire dp_tree_mem_ren_b; -wire [31:0] dp_node_large_bitset; - -// result output -wire [31:0] dp_tree_eval_result; -wire dp_tree_eval_last; -wire dp_tree_eval_result_valid; - - -reg [2:0] curr_tree_id; -reg [8:0] curr_tuple_offset; -reg [9:0] time_stamp; -reg tuple_old_enough_set; - -wire tuple_instr_fifo_ready; -wire tuple_instr_fifo_valid; -wire [9:0] tuple_instr_fifo_dout; -wire tuple_instr_re; -wire tuple_old_enough; - - -wire curr_feature_done; -reg [9:0] features_mem_count; -reg [8:0] features_wr_addr; - -reg [2:0] local_num_trees; -reg [2:0] local_num_trees_minus_one; -reg [TREE_OFFSET_BITS-1:0] received_tree_lines; -reg [TREE_OFFSET_BITS-1:0] tree_prog_addr; -reg [TREE_OFFSET_BITS-1:0] tree_offsets[7:0]; -wire [TREE_OFFSET_BITS :0] tree_mem_addr_a; - -reg [4:0] num_trees_per_pu_minus_one; -reg [3:0] tree_depth; -reg [8:0] num_lines_per_tuple; - -reg [7:0] tree_addra[5:0]; -reg [7:0] tree_addrb[5:0]; - -reg [2:0] rd_a_count; -reg [2:0] rd_b_count; - -reg first_word_correct; -reg [63:0] first_word; -reg pe_state; - -reg [63:0] tree_mem_out_a; -reg [31:0] data_mem_out_a; -reg tree_out_a_set; -reg data_out_a_set; -wire dp_tree_valid_out1; -wire dp_data_mem_feature_valid; - -reg [31:0] num_tuples_received; -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// PU Programming Logic ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -always @(posedge clk) begin - if(~rst_n) begin - data_line_out_valid <= 0; - data_line_out_last <= 0; - data_line_out_ctrl <= 0; - data_line_out_prog <= 0; - - pe_state <= TREE_MODE; - - num_tuples_received <= 0; - end - else begin - data_line_out_valid <= data_line_in_valid; - data_line_out_last <= data_line_in_last; - data_line_out_ctrl <= data_line_in_ctrl; - data_line_out_prog <= data_line_in_prog; - - if(data_line_in_valid | (pe_state == DATA_MODE) ) begin - pe_state <= DATA_MODE; - end - - if(data_line_in_valid && data_line_in_last) begin - num_tuples_received <= num_tuples_received + 1'b1; - end - end - // - data_line_out <= data_line_in; - data_line_out_pu <= data_line_in_pu; -end - -always @(posedge clk) begin - if(~rst_n) begin - num_trees_per_pu_minus_one <= 5'b0; - tree_depth <= 4'b0; - num_lines_per_tuple <= 9'b0; - end - else if(data_line_in_ctrl) begin - num_trees_per_pu_minus_one <= data_line_in[4:0]; - tree_depth <= data_line_in[8+4-1:8]; - num_lines_per_tuple <= data_line_in[16+9-1:16]; - end -end - -//assign pu_debug_counters = {local_num_trees, received_tree_lines[8:0], tree_offsets[3][8:0], tree_offsets[2][8:0], tree_offsets[1][8:0], tree_offsets[0][8:0]}; -assign pu_debug_counters = {tree_addrb[5], tree_addrb[4], tree_addrb[3], tree_addrb[2], tree_addrb[1], tree_addrb[0], tree_addra[5], tree_addra[4], tree_addra[3], tree_addra[2], tree_addra[1], tree_addra[0]}; -assign pu_debug_counters2 = {tree_mem_out_a[31:0], first_word}; -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Memory Banks ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -genvar j; -generate for ( j = 0; j < 6; j=j+1) begin - always@(posedge clk) begin - if(~rst_n) begin - tree_addra[j] <= 0; - tree_addrb[j] <= 0; - end - else begin - if(dp_tree_mem_ren_a && (rd_a_count == j)) begin - tree_addra[j] <= tree_mem_addr_a[7:0]; - end - - if(dp_tree_mem_ren_b && (rd_b_count == j)) begin - tree_addrb[j] <= dp_tree_mem_rd_addr_b[7:0]; - end - end - end -end -endgenerate - -integer i; -always@(posedge clk) begin - if(~rst_n) begin - rd_a_count <= 0; - rd_b_count <= 0; - - tree_mem_out_a <= 0; - data_mem_out_a <= 0; - tree_out_a_set <= 1'b0; - data_out_a_set <= 1'b0; - first_word_correct <= 0; - first_word <= 0; - end - else begin - if(dp_tree_mem_ren_a && rd_a_count < 6) begin - rd_a_count <= rd_a_count + 1'b1; - end - - if(dp_tree_mem_ren_b && rd_b_count < 6) begin - rd_b_count <= rd_b_count + 1'b1; - end - // - if(dp_tree_valid_out1 && ~tree_out_a_set) begin - tree_mem_out_a <= dp_tree_node_basic_vec; - tree_out_a_set <= 1'b1; - end - - if(dp_data_mem_feature_valid && ~data_out_a_set) begin - data_mem_out_a <= dp_data_mem_feature; - data_out_a_set <= 1'b1; - end - - // - if((tree_prog_addr == 0) && (data_line_in_prog && (data_line_in_pu == PE_ID) )) begin - first_word_correct <= data_line_in[15:0] == 4'h200c; - first_word <= data_line_in; - end - end -end - - -//----------------------------- Tree Nodes Weight memory ------------------------------// -Tree_Memory #( .DATA_WIDTH(64), - .ADDR_WIDTH(11) ) -TreeNodes( - - .clk ( clk ), - .rst_n ( rst_n ), - .we ( data_line_in_prog && (data_line_in_pu == PE_ID) ), - .rea ( dp_tree_mem_ren_a ), - .reb ( dp_tree_mem_ren_b ), - .addr_port_a ( tree_mem_addr_a ), - .addr_port_b ( dp_tree_mem_rd_addr_b ), - .din ( data_line_in ), - .dout1 ( dp_tree_node_basic_vec ), - .valid_out1 ( dp_tree_valid_out1), - .dout2 ( dp_node_large_bitset ), - .valid_out2 () -); -assign dp_tree_node_basic.word_1_h = dp_tree_node_basic_vec[63:48]; -assign dp_tree_node_basic.word_1_l = dp_tree_node_basic_vec[47:32]; -assign dp_tree_node_basic.right_child_offset = dp_tree_node_basic_vec[31:16]; -assign dp_tree_node_basic.node_type.op_type = dp_tree_node_basic_vec[1:0]; -assign dp_tree_node_basic.node_type.left_child = dp_tree_node_basic_vec[2]; -assign dp_tree_node_basic.node_type.right_child = dp_tree_node_basic_vec[3]; -assign dp_tree_node_basic.node_type.findex = dp_tree_node_basic_vec[11:4]; -assign dp_tree_node_basic.node_type.split_dir = dp_tree_node_basic_vec[15:12]; - - -assign tree_mem_addr_a = ( pe_state == TREE_MODE )? {tree_prog_addr, 1'b0} : dp_tree_mem_rd_addr_a; - -always @(posedge clk) begin - // - if(~rst_n) begin - tree_prog_addr <= 0; - local_num_trees <= 0; - received_tree_lines <= 0; - - local_num_trees_minus_one <= 0; - - for (i = 0; i < 8; i++) begin - tree_offsets[ i ] <= 0; - end - end - else begin - local_num_trees_minus_one <= local_num_trees; - // - if(data_line_in_prog && (data_line_in_pu == PE_ID)) begin - tree_prog_addr <= tree_prog_addr + 1'b1; - received_tree_lines <= received_tree_lines + 1'b1; - - if(data_line_in_last && (local_num_trees[2:0] < num_trees_per_pu_minus_one[2:0])) begin - tree_offsets[ local_num_trees[2:0] + 1'b1 ] <= received_tree_lines + 1'b1; - local_num_trees <= local_num_trees + 1'b1; - end - end - end -end -//--------------------------- Input tuple features memory -----------------------------// -/* We write to the features memory when flags indicate incoming data is tuples and not programming data -*/ -Data_Memory #( .DATA_WIDTH(64), - .ADDR_WIDTH(10) ) -SamplesFeatures_Mem( - .clk (clk), - .rst_n (rst_n), - .we (data_line_in_valid), - .re (dp_data_mem_ren), - .raddr (dp_data_mem_rd_addr), - .waddr (features_wr_addr), - .din (data_line_in), - .dout (dp_data_mem_feature), - .valid_out (dp_data_mem_feature_valid) -); - -always @(posedge clk) begin - if(~rst_n) begin - features_wr_addr <= 9'b0; - features_mem_count <= 0; - end - else begin - features_wr_addr <= features_wr_addr + ((data_line_in_valid)? 9'd1 : 9'd0); - features_mem_count <= features_mem_count + ((data_line_in_valid)? 9'd1 : 9'd0) - ((curr_feature_done)? num_lines_per_tuple : 9'b0); - end -end - -assign curr_feature_done = dp_tree_eval_result_valid & dp_tree_eval_last; - - -always @(posedge clk) begin - if(~rst_n) begin - data_line_in_ready <= 0; - end - else begin - data_line_in_ready <= tuple_instr_fifo_ready & (features_mem_count < (512- PE_ID - 4)); - end -end -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Tuple Instruction FIFO ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// -/* Once all features of a tuple are in features memory, we enqueue an instruction to execute all the - trees in the PU on the current tuple features, the instruction simply include the tuple offset. -*/ -RegBasedFIFO #(.FIFO_WIDTH(1), - .FIFO_DEPTH_BITS(2) - ) tuple_instr_fifo ( - .clk (clk), - .rst_n (rst_n), - .data_in ( 1'b1 ), - .data_in_valid ( data_line_in_valid && data_line_in_last ), - .data_out_ready (tuple_instr_re), - .data_out (tuple_instr_fifo_dout), - .data_out_valid (tuple_instr_fifo_valid), - .data_in_ready (tuple_instr_fifo_ready) - ); - -assign tuple_instr_re = dp_tree_instruction_ready && (curr_tree_id == num_trees_per_pu_minus_one); - -assign dp_tree_instruction_valid = tuple_instr_fifo_valid; - -assign dp_tree_instruction = '{tree_offset: tree_offsets[curr_tree_id], - tuple_offset: curr_tuple_offset, - last_tree: (curr_tree_id == num_trees_per_pu_minus_one), - empty_tree: (curr_tree_id > local_num_trees)}; - -// Flags, counters used in issuing tree instructions to the datapath and synchronizing logic with other PEs. -always @(posedge clk) begin - if(~rst_n) begin - curr_tuple_offset <= 0; - curr_tree_id <= 0; - end - else begin - // curr_tuple_offset - if(tuple_instr_fifo_valid && tuple_instr_re) begin - curr_tuple_offset <= curr_tuple_offset + num_lines_per_tuple; - end - - // curr_tree_id - if(tuple_instr_fifo_valid && dp_tree_instruction_ready) begin - if(curr_tree_id == num_trees_per_pu_minus_one) begin - curr_tree_id <= 0; - end - else begin - curr_tree_id <= curr_tree_id + 1'b1; - end - end - end -end -//////////////////////////////////////////////////////////////////////////////////////////////////// -// PE Datapath -pe_datapath pe_datapath( - .clk (clk), - .rst_n (rst_n), - .tree_depth (tree_depth), - // tree instruction - .tree_instruction (dp_tree_instruction), - .tree_instruction_valid (dp_tree_instruction_valid), - .tree_instruction_ready (dp_tree_instruction_ready), - // data memory read port - .data_mem_rd_addr (dp_data_mem_rd_addr), - .data_mem_ren (dp_data_mem_ren), - .data_mem_feature (dp_data_mem_feature), - // tree memory read port A - .tree_mem_rd_addr_a (dp_tree_mem_rd_addr_a), - .tree_mem_ren_a (dp_tree_mem_ren_a), - .tree_node_basic (dp_tree_node_basic), - // tree memory read port B - .tree_mem_rd_addr_b (dp_tree_mem_rd_addr_b), - .tree_mem_ren_b (dp_tree_mem_ren_b), - .node_large_bitset (dp_node_large_bitset), - // result output - .tree_eval_result (dp_tree_eval_result), - .tree_eval_last (dp_tree_eval_last), - .tree_eval_result_valid (dp_tree_eval_result_valid) -); - - -always @(posedge clk) begin - if(~rst_n) begin - pu_tree_leaf_out <= 0; - pu_tree_leaf_out_last <= 0; - pu_tree_leaf_out_valid <= 0; - end - else begin - pu_tree_leaf_out <= dp_tree_eval_result; - pu_tree_leaf_out_last <= dp_tree_eval_last; - pu_tree_leaf_out_valid <= dp_tree_eval_result_valid; - end -end - - -endmodule // DTPU - - diff --git a/examples_hw/apps/gbm_dtrees/hdl/quick_fifo.sv b/examples_hw/apps/gbm_dtrees/hdl/quick_fifo.sv deleted file mode 100644 index 79360b2e9..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/quick_fifo.sv +++ /dev/null @@ -1,149 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - - -module quick_fifo #( - parameter FIFO_WIDTH = 32, - parameter FIFO_DEPTH_BITS = 8, - parameter FIFO_ALMOSTFULL_THRESHOLD = 2**FIFO_DEPTH_BITS - 4 -) ( - input wire clk, - input wire reset_n, - - input wire we, // input write enable - input wire [FIFO_WIDTH - 1:0] din, // input write data with configurable width - - input wire re, // input read enable - output reg valid, // dout valid - output reg [FIFO_WIDTH - 1:0] dout, // output read data with configurable width - - output reg [FIFO_DEPTH_BITS - 1:0] count, // output FIFOcount - output reg empty, // output FIFO empty - output reg full, // output FIFO full - output reg almostfull // output configurable programmable full/ almost full -); - - reg [FIFO_DEPTH_BITS - 1:0] rp = 0; - reg [FIFO_DEPTH_BITS - 1:0] wp = 0; - - reg [FIFO_DEPTH_BITS - 1:0] mem_count = 0; // output FIFOcount - reg mem_empty = 1'b1; - - reg valid_t1 = 0, valid_t2 = 0; - reg valid0 = 0; - - wire remem; - wire wemem; - wire remem_valid; - - wire [FIFO_WIDTH-1:0] dout_mem; - - assign remem = (re & valid_t1 & valid_t2) | ~(valid_t1 & valid_t2); - assign wemem = we & ~full; - - assign remem_valid = remem & ~mem_empty; - - - bram #(.DATA_WIDTH(FIFO_WIDTH), - .ADDR_WIDTH(FIFO_DEPTH_BITS)) fifo_mem( - .clk (clk), - .we (wemem), - .re (remem), - .raddr (rp), - .waddr (wp), - .din (din), - .dout (dout_mem) - ); - - // data - always @(posedge clk) begin - dout <= (valid_t2)? ((re)? dout_mem : dout) : dout_mem; - end - - // valids, flags - always @(posedge clk) begin - if (~reset_n) begin - empty <= 1'b1; - full <= 1'b0; - almostfull <= 1'b0; - count <= 0; //32'b0; - rp <= 0; - wp <= 0; - valid_t2 <= 1'b0; - valid_t1 <= 1'b0; - mem_empty <= 1'b1; - mem_count <= 'b0; - - //dout <= 0; - valid <= 0; - valid0 <= 0; - end - - else begin - - valid <= (valid)? ((re)? valid0 : 1'b1) : valid0; - valid0 <= (remem)? ~mem_empty : valid0; - - valid_t2 <= (valid_t2)? ((re)? valid_t1 : 1'b1) : valid_t1; - - valid_t1 <= (remem)? ~mem_empty : valid_t1; - rp <= (remem & ~mem_empty)? (rp + 1'b1) : rp; - wp <= (wemem)? (wp + 1'b1) : wp; - - // mem_empty - if (we) mem_empty <= 1'b0; - else if(remem & (mem_count == 1'b1)) mem_empty <= 1'b1; - - // mem_count - if( wemem & ~remem_valid) mem_count <= mem_count + 1'b1; - else if (~wemem & remem_valid) mem_count <= mem_count - 1'b1; - - - // empty - if (we) empty <= 1'b0; - else if((re & valid_t2 & ~valid_t1) & (count == 1'b1)) empty <= 1'b1; - - // count - if( wemem & (~(re & valid_t2) | ~re) ) count <= count + 1'b1; - else if (~wemem & (re & valid_t2)) count <= count - 1'b1; - - // - if (we & ~re) begin - - if (count == (2**FIFO_DEPTH_BITS-1)) - full <= 1'b1; - - if (count == (FIFO_ALMOSTFULL_THRESHOLD-1)) - almostfull <= 1'b1; - end - // - if ((~we | full) & re) begin // - full <= 1'b0; - - if (count == FIFO_ALMOSTFULL_THRESHOLD) - almostfull <= 1'b0; - end - end - end - -endmodule diff --git a/examples_hw/apps/gbm_dtrees/hdl/smart_shifter.sv b/examples_hw/apps/gbm_dtrees/hdl/smart_shifter.sv deleted file mode 100644 index 65331caed..000000000 --- a/examples_hw/apps/gbm_dtrees/hdl/smart_shifter.sv +++ /dev/null @@ -1,83 +0,0 @@ - -/* - * Copyright 2019 - 2020 Systems Group, ETH Zurich - * - * 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. - */ - - -module smart_shifter #(parameter ELEMENTS_PER_CL = 16, - parameter ELEMENTS_PER_CL_BITS = 4) - ( - input wire clk, - input wire rst_n, - - input wire inValid, - input wire inLast, - input wire [3:0] inOffs, - input wire [1023:0] inData, - - output wire outValid, - output wire outLast, - output reg [511:0] outData - ); - - reg [1023:0] shData [14:0]; - reg [3:0] shOffs [14:0]; - - integer i; - - always @(posedge clk) begin - // Level 0 - if ( |inOffs ) begin - shData[0] <= {32'b0, inData[1023:32]}; - shOffs[0] <= inOffs - 4'd1; - end - else begin - shData[0] <= inData; - shOffs[0] <= inOffs; - end - // Rest of levels: Data, shOffs - for ( i = 0; i <14; i = i+1) begin - if ( |shOffs[i] ) begin - shData[i+1] <= {32'b0, shData[i][1023:32]}; - shOffs[i+1] <= shOffs[i] - 4'd1; - end - else begin - shData[i+1] <= shData[i]; - shOffs[i+1] <= shOffs[i]; - end - end - - outData <= shData[14][511:0]; - end - // - - delay #(.DATA_WIDTH(1), - .DELAY_CYCLES(16) - ) validDelay( - .clk (clk), - .rst_n (rst_n), - .data_in ( inLast ), // - .data_in_valid ( inValid ), - .data_out ( outLast ), - .data_out_valid ( outValid ) - ); - -endmodule diff --git a/examples_hw/apps/gbm_dtrees/init_ip.tcl b/examples_hw/apps/gbm_dtrees/init_ip.tcl deleted file mode 100644 index 44e4ad4a6..000000000 --- a/examples_hw/apps/gbm_dtrees/init_ip.tcl +++ /dev/null @@ -1,2 +0,0 @@ -create_ip -name vio -vendor xilinx.com -library ip -version 3.0 -module_name vio_gbm -set_property -dict [list CONFIG.C_PROBE_IN2_WIDTH {32} CONFIG.C_PROBE_IN1_WIDTH {32} CONFIG.C_PROBE_IN0_WIDTH {32} CONFIG.C_NUM_PROBE_OUT {0} CONFIG.C_NUM_PROBE_IN {3} CONFIG.Component_Name {vio_gbm}] [get_ips vio_gbm] \ No newline at end of file diff --git a/examples_hw/apps/gbm_dtrees/vfpga_top.svh b/examples_hw/apps/gbm_dtrees/vfpga_top.svh deleted file mode 100644 index 86aa97f99..000000000 --- a/examples_hw/apps/gbm_dtrees/vfpga_top.svh +++ /dev/null @@ -1,240 +0,0 @@ -/** - * VFPGA TOP - * - * Tie up all signals to the user kernels - * Still to this day, interfaces are not supported by Vivado packager ... - * This means verilog style port connections are needed. - * - */ - -// -// Instantiate top level -// - -// UL -AXI4SR axis_sink (); -AXI4SR axis_src (); - -axisr_reg inst_reg_sink (.aclk(aclk), .aresetn(aresetn), .s_axis(axis_host_recv[0]), .m_axis(axis_sink)); -axisr_reg inst_reg_src (.aclk(aclk), .aresetn(aresetn), .s_axis(axis_src), .m_axis(axis_host_send[0])); - -// GBM dtrees -localparam [1:0] IDLE = 2'b00, - READ_TREES = 2'b01, - WAIT_ALL_TREES = 2'b10, - READ_DATA = 2'b11; - -reg [1:0] reader_state; -reg [1:0] nxt_reader_state; -wire trees_read_done; -wire data_read_done; - -wire wr_tvalid; -wire wr_tready; -wire [511:0] wr_tdata; - -wire rd_tvalid; -wire rd_ttype; -wire rd_tlast; -wire rd_tready; -wire [511:0] rd_tdata; - -reg [31:0] sentOutCLs; -wire [31:0] next_sentOutCLs; - -logic ap_start_r = 1'b0; -logic ap_start_pulse_d1 = 1'b0; -wire ap_start ; -wire [16-1:0] numFeatures ; -wire [8-1:0] treeDepth ; -wire [8-1:0] puTrees ; -wire [16-1:0] lastOutLineMask ; -wire [31:0] outputNumCLs; - -logic [16-1:0] num_64bit_words_per_tuple; -logic [8-1:0] num_trees_per_pu_minus_one; - -logic [31:0] num_data_cls; -logic [31:0] num_trees_cls; -logic [31:0] num_result_cls; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Parameters on AxiLite ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -// AXI4-Lite slave interface -gbm_slave inst_control_s_axi ( - .aclk ( aclk ), - .aresetn ( aresetn ), - .axi_ctrl ( axi_ctrl ), - .ap_start ( ap_start ), - .numFeatures ( numFeatures ), - .treeDepth ( treeDepth ), - .puTrees ( puTrees ), - .outputNumCLs ( outputNumCLs ), - .lastOutLineMask ( lastOutLineMask ) -); - -always @(posedge aclk) begin - if (~aresetn) begin - num_trees_per_pu_minus_one <= 0; - num_64bit_words_per_tuple <= 0; - - ap_start_r <= 0; - ap_start_pulse_d1 <= 0; - end - else begin - num_trees_per_pu_minus_one <= puTrees - 1'b1; - num_64bit_words_per_tuple <= numFeatures[7:1] + numFeatures[0]; - - ap_start_r <= ap_start; - ap_start_pulse_d1 <= ap_start_pulse; - end -end - -assign ap_start_pulse = ap_start & ~ap_start_r; -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Decode Input Streams ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -// Reader State -always@(posedge aclk) begin - if(~aresetn) begin - reader_state <= IDLE; - end - else begin - reader_state <= nxt_reader_state; - end -end - -always@(*) begin - case (reader_state) - IDLE : nxt_reader_state = (ap_start_pulse_d1)? READ_TREES : IDLE; - READ_TREES : nxt_reader_state = (trees_read_done)? WAIT_ALL_TREES: READ_TREES; - WAIT_ALL_TREES: nxt_reader_state = READ_DATA; - READ_DATA : nxt_reader_state = (data_read_done)? IDLE : READ_DATA; - default : nxt_reader_state = IDLE; - endcase -end - -assign trees_read_done = axis_sink.tlast && axis_sink.tvalid && axis_sink.tready; -assign data_read_done = axis_sink.tlast && axis_sink.tvalid && axis_sink.tready; - - -//////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////// ////////////////////////////////// -////////////////////////////// Engine Core ///////////////////////// -////////////////////////////////////// ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -// Input Streams -assign rd_tdata = axis_sink.tdata; -assign rd_tvalid = axis_sink.tvalid; -assign rd_tlast = axis_sink.tlast; -assign rd_ttype = reader_state == READ_DATA; - -assign axis_sink.tready = rd_tready && (reader_state == READ_TREES || reader_state == READ_DATA); - -// Output Stream -assign axis_src.tdata = wr_tdata; -assign axis_src.tkeep = 64'hffffffffffffffff; -assign axis_src.tid = 0; -assign axis_src.tvalid = wr_tvalid; -assign axis_src.tlast = next_sentOutCLs == outputNumCLs; - -assign wr_tready = axis_src.tready; - -// Count output numCLs - -assign next_sentOutCLs = sentOutCLs + 1'b1; - -always@(posedge aclk) begin - if(~aresetn) begin - sentOutCLs <= 0; - end - else begin - if(sentOutCLs == outputNumCLs) begin - sentOutCLs <= 0; - end - else if(wr_tvalid && wr_tready) begin - sentOutCLs <= next_sentOutCLs; - end - end -end - - -DTProcessor DTProcessor( - .clk (aclk), - .rst_n (aresetn), - .start_core (ap_start_pulse_d1), - // parameters - - .tuple_length (numFeatures[5:0]), - .num_trees_per_pu_minus_one (num_trees_per_pu_minus_one[4:0]), - .tree_depth (treeDepth[3:0]), - .num_lines_per_tuple ({2'b00, num_64bit_words_per_tuple[6:0]}), - // input trees - .core_data_in (rd_tdata), - .core_data_in_type (rd_ttype), - .core_data_in_valid (rd_tvalid), - .core_data_in_last (rd_tlast), - .core_data_in_ready (rd_tready), - // output - .last_result_line ( (next_sentOutCLs == outputNumCLs) ), - .last_result_line_mask (lastOutLineMask[15:0]), - .core_result_out (wr_tdata), - .core_result_valid (wr_tvalid), - .core_result_ready (wr_tready) -); - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// debug counters -always@(posedge aclk) begin - if(~aresetn) begin - num_data_cls <= 0; - num_trees_cls <= 0; - num_result_cls <= 0; - end - else begin - // - if(ap_start_pulse) begin - num_data_cls <= 0; - num_trees_cls <= 0; - num_result_cls <= 0; - end - else begin - // - if(rd_tvalid && rd_tready && rd_ttype) begin - num_data_cls <= num_data_cls + 1'b1; - end - // - if(rd_tvalid && rd_tready && !rd_ttype) begin - num_trees_cls <= num_trees_cls + 1'b1; - end - // - if(wr_tvalid && wr_tready) begin - num_result_cls <= num_result_cls + 1'b1; - end - end - end -end - - -// Tie-off the rest of the interfaces -always_comb notify.tie_off_m(); -always_comb sq_rd.tie_off_m(); -always_comb sq_wr.tie_off_m(); -always_comb cq_rd.tie_off_s(); -always_comb cq_wr.tie_off_s(); - -// ILA -vio_gbm inst_vio_gbm ( - .clk(aclk), - .probe_in0(num_data_cls), // 32 - .probe_in1(num_trees_cls), // 32 - .probe_in2(num_result_cls) // 32 -); diff --git a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/Collector.hpp b/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/Collector.hpp deleted file mode 100644 index 335f94b4d..000000000 --- a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/Collector.hpp +++ /dev/null @@ -1,144 +0,0 @@ -#ifndef COLLECTOR_HPP -#define COLLECTOR_HPP - -#include -#include - -#include "stream.hpp" - -/** - * Statistics collector over an input stream using a key space of 2**K and - * the value type TV. - */ -template< - unsigned K, - typename TV -> -class Collector { - - static unsigned const FEED = 0; - static unsigned const SETTLE1 = 1; - static unsigned const SETTLE2 = 2; - static unsigned const DUMP = 3; - using TK = ap_uint; - - unsigned state = FEED; - TK dump_ptr = 0; - TV mem[1< - void collect( - F &&f, // Update Value Folding Function: (TV, TV) -> TV - hls::stream &src, // Input: tixl(x) -> last, tixk(x) -> key, tixv(x) -> val - hls::stream &dst, // Output: toxl(y) <- last, toxv(y) <- val - TIXL &&tixl = TIXL(), // default: TI.last - TIXK &&tixk = TIXK(), // default: TI.key - TIXV &&tixv = TIXV(), // default: TI.val - TOXL &&toxl = TOXL(), // default: TO.last - TOXV &&toxv = TOXV() // default: TO.val - ) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - -#if defined( __VITIS_HLS__) - #pragma HLS bind_storage variable=mem type=RAM_T2P impl=BRAM -#else - #pragma HLS RESOURCE variable=mem core=RAM_T2P_BRAM -#endif - -#pragma HLS DEPENDENCE variable=mem inter distance=2 - - // Execute pending Memory Update and Output - bool const z_stb0 = z_stb; - TK const z_key0 = z_key; - TV const z_val0 = z_val; - if(z_stb) mem[z_key] = z_val; - - if(y_stb) { - TO y; - toxl(y) = y_last; - toxv(y) = y_val; - if(dst.write_nb(y)) y_stb = false; - } - - // Controlling FSM - switch(state) { - - // Read and process Data - case FEED: { - TI x; - bool const x_stb = src.read_nb(x); - TK const x_key = tixk(x); - bool const z_match = z_stb && (z_key == x_key); - bool const zz_match = zz_stb && (zz_key == x_key); - bool const zzz_match = zzz_stb && (zzz_key == x_key); - TV const x_val = f(tixv(x), z_match? z_val : zz_match? zz_val : zzz_match? zzz_val : mem[x_key]); - - z_stb = x_stb; - z_key = x_key; - z_val = x_val; - if(x_stb && tixl(x)) state = SETTLE1; - } - break; - - // Cover Memory Update Latency - case SETTLE1: state = SETTLE2; break; - case SETTLE2: state = DUMP; break; - - // Stream out Contents - case DUMP: { - if(!y_stb) { - bool const last = ~dump_ptr == 0; - - y_stb = true; - y_last = last; - y_val = mem[dump_ptr]; - - z_stb = true; - z_key = dump_ptr; - z_val = 0; - - if(last) state = FEED; - dump_ptr++; - } - } - break; - } - zzz_stb = zz_stb; - zzz_key = zz_key; - zzz_val = zz_val; - zz_stb = z_stb0; - zz_key = z_key0; - zz_val = z_val0; - - } // collect() - -}; // class Collector - -#endif diff --git a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/HllSketch.hpp b/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/HllSketch.hpp deleted file mode 100644 index 426bc6deb..000000000 --- a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/HllSketch.hpp +++ /dev/null @@ -1,183 +0,0 @@ -#ifndef HLLSKETCH_HPP -#define HLLSKETCH_HPP - -#include "Collector.hpp" -#include "stream.hpp" -#include "bit_utils.hpp" -#include "hls_math.h" - -#include - -template< - unsigned H, // Total Number of Hash Bits - unsigned P // Precision: Bits used to differentiate Buckets -> -class HllEstimator { - static unsigned constexpr M = 1u<= 128 - - /** - * Exact accumulation until float conversion for output for M=2^P buckets: - * - * | <- P -> . <- H-P+1 -> | - * - * Numeric Range: - * 0.0 .. (2^P)*1.0 - * - * All values are represented exact except for the maximum final result - * in the case that *all* buckets report a rank of zero, i.e. empty. The - * accumulator will then be ignored in favor of linear counting. - */ - using accu_t = ap_ufixed; - ap_uint zeros = 0; - accu_t accu = 0; - -public: - template - void collect(TR rank) { -#pragma HLS inline - if(rank == 0) zeros++; - accu_t d = 0; // d = 2^(-rank) - d[H-P+1 - rank] = 1; - accu += d; - - } // collect() - -public: - float estimate() { -#pragma HLS inline - // Raw Cardinality - float card = ALPHAM2 / accu.to_float(); - - // Estimate Refinement - if(card <= 2.5*M){ - // Linear Counting if there are empty Buckets - if(zeros != 0) card = M*logf((float)M / (float)zeros); - } - - // State Reset - zeros = 0; - accu = 0; - - return card; - - } // estimate() - -}; // HllEstimator - -template< - unsigned N, - unsigned H, // Total Number of Hash Bits - unsigned P // Precision: Bits used to differentiate Buckets -> -class HllSketch { - using buck_t = ap_uint

; // Key - using rank_t = ap_uint::value>; // Rank: 0, 1, .., (H-P+1) - using ranked_t = flit_kv_t; - using res_t = flit_v_t; - - //- Structure ----------------------------------------------------------- - hls::stream ranked[N]; - Collector collector[N]; - hls::stream dsti[N]; - HllEstimator estimator; - -public: - template< - typename TIXL = MemberLast, - typename TIXV = MemberVal, - typename TOXL = MemberLast, - typename TOXV = MemberVal, - typename TI, - typename TO - > - void sketch( - hls::stream (&src)[N], - hls::stream &dst, - TIXL &&tixl = TIXL(), // TI.last - TIXV &&tixv = TIXV(), // TI.val - TOXL &&toxl = TOXL(), // TO.last - TOXV &&toxv = TOXV() // TO.val - ) { -#pragma HLS inline off -#pragma HLS dataflow -#pragma HLS array_partition variable=collector dim=1 - -#if defined( __VITIS_HLS__) - #pragma HLS aggregate variable=ranked compact=bit - #pragma HLS aggregate variable=dsti compact=bit -#else - #pragma HLS data_pack variable=ranked - #pragma HLS data_pack variable=dsti -#endif - - rank(src, tixl, tixv); - collect(); - fold(dst, toxl, toxv); - - } // sketch() - -private: - template - void rank(hls::stream (&src)[N], TIXL &&tixl, TIXV &&tixv) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - auto const f_rank = [&tixl,&tixv](TI const& x)->ranked_t{ -#pragma HLS inline - auto const& hash = tixv(x); - return ranked_t{tixl(x), hash(H-1, H-P), 1+btl::clz(ap_uint(hash))}; - }; - for(unsigned i = 0; i < N; i++) { -#pragma HLS unroll - stage_guarded(src[i], ranked[i], f_rank); - } - } // rank() - - void collect() { -#pragma HLS inline - for(unsigned i = 0; i < N; i++) { -#pragma HLS unroll - collector[i].collect( - [](rank_t const &a, rank_t const &b)->rank_t{ return std::max(a, b); }, - ranked[i], dsti[i]); - } - } // collect() - - template - void fold(hls::stream &dst, TOXL &&toxl, TOXV &&toxv) { -#pragma HLS pipeline II=1 - ap_uint empty; - for(unsigned i = 0; i < N; i++) { -#pragma HLS unroll - empty[i] = dsti[i].empty(); - } - if(empty == 0) { - - // Maximum Fold across parallel Channels - bool last = false; - rank_t rank = 0; - for(unsigned i = 0; i < N; i++) { -#pragma HLS unroll - res_t x; - dsti[i].read_nb(x); - last |= x.last; - rank = std::max(rank, x.val); - } - - // HLL Metrics Update - estimator.collect(rank); - - // Result Output - if(last) { - TO y{}; - toxl(y) = true; - toxv(y) = estimator.estimate(); - dst.write(y); - } - } - - } // fold() - -}; // class HllSketch - -#endif diff --git a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/bit_utils.hpp b/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/bit_utils.hpp deleted file mode 100644 index 5194eb980..000000000 --- a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/bit_utils.hpp +++ /dev/null @@ -1,165 +0,0 @@ -/**************************************************************************** - * Copyright (c) 2020, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - **************************************************************************** - * @brief - * Utilities for de- and transcoding low-level data representations. - * - * This implementation is fiercely templated and supports both signed and - * unsigned representations as ap_(u)int as much as semantically sensible. - * - * @author - * Thomas B. Preußer - ****************************************************************************/ -#ifndef BIT_UTILS_HPP -#define BIT_UTILS_HPP - -#include - -namespace btl { // Part of the Bit-level Template Library -//--------------------------------------------------------------------------- -// Compile-Time Arithmetic: LOG2 / POW2 - -// As soon as the HLS compiler supports modern C++ features, -// the template computations below may be simplified to: -// constexpr unsigned flog2(unsigned n) { return n < 2? 0 : 1+flog2(n/2); } -// constexpr unsigned clog2(unsigned n) { return n < 2? 0 : 1+flog2(n-1); } -// constexpr unsigned pow2 (unsigned n) { return n == 0? 1 : 2*pow2(n-1); } - -/** floor(log2(N)) */ -template struct flog2 { - static unsigned const value = 1+flog2::value; -}; -template<> struct flog2<0> {}; -template<> struct flog2<1> { - static unsigned const value = 0; -}; - -/** ceil(log2(N)) */ -template struct clog2 { - static unsigned const value = 1+flog2::value; -}; -template<> struct clog2<0> {}; -template<> struct clog2<1> { - static unsigned const value = 0; -}; - -/** 2**N */ -template struct pow2 { - static unsigned const value = 2*pow2::value; -}; -template<> struct pow2<0> { - static unsigned const value = 1; -}; - -//--------------------------------------------------------------------------- -// Transcoding - -/** Least-Significant Asserted Bit as 1-Hot (or zero if it does not exist). */ -template -inline ap_int_base lsab_hot(ap_int_base x) { -#pragma HLS inline - return x & ap_int_base(~x+1); -} - -/** Most-Significant Asserted Bit as 1-Hot (or zero if it does not exist). */ -template -inline ap_int_base msab_hot(ap_int_base x) { -#pragma HLS inline - return lsab_hot(x.reverse()).reverse(); -} - -/** One-Hot to binary. - * The returned result is undefined if the input is not one-hot encoded. - */ -template::value> -inline ap_int_base hot2bin(ap_int_base x) { -#pragma HLS inline - // Selective ORing to obtain binary index - ap_int_base res = 0; - - // This is what would be done in VHDL/Verilog: - // for(unsigned i = 0; i < W+1; i++) if(h[i]) res |= i; - - // This is what HLS needs for a decent synthesis: - for(unsigned i = 0; i < M; i++) { -#pragma HLS unroll - for(ap_uint::value> j = 0; j < W; j++) { -#pragma HLS unroll -#pragma TODO ap_fix - if(j[i]) res[i] = res[i] | x[j]; - } - } - return res; -} - -/** Binary to One-Hot. */ -template::value> -inline ap_int_base bin2hot(ap_int_base x) { -#pragma HLS inline - ap_int_base res(0); - res[x] = 1; - return res; -} - -/** Binary to reflected GRAY code. */ -template -inline ap_int_base bin2gray(ap_int_base x) { -#pragma HLS inline - return x ^ x(W-1, 1); -} - -/** Reflected GRAY code to binary. */ -template -inline ap_int_base gray2bin(ap_int_base x) { -#pragma HLS inline - ap_int_base y = x; - for(unsigned i = W-1; i > 0; i--) { -#pragma HLS unroll -#pragma TODO ap_fix - y[i-1] = y[i] ^ y[i-1]; - } - return y; -} - -//--------------------------------------------------------------------------- -// Prefix and suffix counting - -/** Count leading zeros. */ -template::value> -inline ap_int_base clz(ap_int_base x) { -#pragma HLS inline - return ap_int_base(x.countLeadingZeros()); -} - -/** Count trailing zeros. */ -template::value> -inline ap_int_base ctz(ap_int_base x) { -#pragma HLS inline - return clz(x.reverse()); -} - -} // namespace btl -#endif diff --git a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hashes.hpp b/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hashes.hpp deleted file mode 100644 index 536f7ce1f..000000000 --- a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hashes.hpp +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef HASHES_HPP -#define HASHES_HPP - -#include -#include - -//--------------------------------------------------------------------------- -// ConcatHash -// Allows to assemble a wider hash function by concatenating sub hashes. -template -class ConcatHash; - -template -class ConcatHash : public Hash { - Hash hash; -public: - ConcatHash() {} - ~ConcatHash() {} -public: - template - auto operator()(T val) const -> ap_uint { - return hash(val); - } -}; - -template -class ConcatHash { - Hash hash; - ConcatHash tail; -public: - ConcatHash() {} - ~ConcatHash() {} -public: - template - auto operator()(T val) const -> ap_uint { - return (hash(val), tail(val)); - } -}; - -//--------------------------------------------------------------------------- -// Murmur3_128 -template -class Murmur3_128 { - static uint64_t const c1 = 0x87c37b91114253d5; - static uint64_t const c2 = 0x4cf5ad432745937f; - static uint64_t const c3 = 0xff51afd7ed558ccd; - static uint64_t const c4 = 0xc4ceb9fe1a85ec53; - -public: - Murmur3_128() {} - ~Murmur3_128() {} - -public: - ap_uint<128> operator()(ap_uint<32> data) const { - ap_uint<64> const len = 4; - ap_uint<64> k1 = data; - - ap_uint<64> h1 = SEED; - ap_uint<64> h2 = SEED; - - k1 *= c1; - k1 = (k1 << 31) | (k1 >> (64 - 31)); - k1 *= c2; - h1 ^= k1; - - h1 ^= len; - h2 ^= len; - - h1 += h2; - h2 += h1; - - h1 ^= h1 >> 33; - h1 *= c3; - h1 ^= h1 >> 33; - h1 *= c4; - h1 ^= h1 >> 33; - - h2 ^= h2 >> 33; - h2 *= c3; - h2 ^= h2 >> 33; - h2 *= c4; - h2 ^= h2 >> 33; - - h1 += h2; - h2 += h1; - - return (h2, h1); - } - -}; // Murmur3_128 - -// A truncated implementation of the 128-bit Murmur3 hash. -template -class Murmur3_64 { - Murmur3_128 hash; - -public: - Murmur3_64() {} - ~Murmur3_64() {} - -public: - ap_uint<64> operator()(ap_uint<32> data) const { - return hash(data); - } - -}; // Murmur3_64 - -#endif diff --git a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hllsketch_16x32.cpp b/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hllsketch_16x32.cpp deleted file mode 100644 index 605e66290..000000000 --- a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hllsketch_16x32.cpp +++ /dev/null @@ -1,108 +0,0 @@ -//#include "user_hls_c0_0.hpp" - -#include "hllsketch_16x32.hpp" - -#include -#include "ap_int.h" -#include -#include -#include -#include -#include "ap_axi_sdata.h" - -#define AXI_DATA_BITS 512 - -#define VADDR_BITS 48 -#define LEN_BITS 28 -#define DEST_BITS 4 -#define PID_BITS 6 -#define RID_BITS 4 - -/** - * User logic - * - */ - -// Interface adjustments -// Sink - -void input_cnvrt ( - hls::stream >& axis_host_sink, - hls::stream& hll_sink -) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - - ap_axiu in_data; - input_t out_data; - - if (!axis_host_sink.empty()) { - axis_host_sink.read(in_data); - - out_data.data = in_data.data; - out_data.keep = in_data.keep; - out_data.id = in_data.id; - out_data.last = in_data.last; - - hll_sink.write(out_data); - } -} - -void output_cnvrt ( - hls::stream& hll_src, - hls::stream >& axis_host_src -) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - - output_t in_data; - ap_axiu out_data; - - if(!hll_src.empty()) { - hll_src.read(in_data); - - out_data.data = in_data.data; - out_data.keep = in_data.keep; - out_data.id = in_data.id; - out_data.last = in_data.last; - - axis_host_src.write(out_data); - } -} - -/** - * Main - */ -void hllsketch_16x32 ( - // Host streams - hls::stream >& axis_host_sink, - hls::stream >& axis_host_src, - - ap_uint<64> axi_ctrl -) { - #pragma HLS DATAFLOW disable_start_propagation - #pragma HLS INTERFACE ap_ctrl_none port=return - - #pragma HLS INTERFACE axis register port=axis_host_sink name=s_axis_host_sink - #pragma HLS INTERFACE axis register port=axis_host_src name=m_axis_host_src - - - #pragma HLS INTERFACE s_axilite port=return bundle=control - //#pragma HLS INTERFACE s_axilite port=axi_ctrl_a bundle=control - //#pragma HLS INTERFACE s_axilite port=axi_ctrl_b bundle=control - //#pragma HLS INTERFACE s_axilite port=axi_ctrl_c bundle=control - #pragma HLS INTERFACE s_axilite port=axi_ctrl bundle=control - - // - // User logic - // - static hls::stream hll_sink; - static hls::stream hll_src; - #pragma HLS STREAM depth=2 variable=hll_sink - #pragma HLS STREAM depth=2 variable=hll_src - - input_cnvrt(axis_host_sink, hll_sink); - top(hll_sink, hll_src); - output_cnvrt(hll_src, axis_host_src); - -} diff --git a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hllsketch_16x32.hpp b/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hllsketch_16x32.hpp deleted file mode 100644 index 3c4037849..000000000 --- a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/hllsketch_16x32.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#include -#include -#include - -#include "HllSketch.hpp" -#include "stream.hpp" -#include "hashes.hpp" - -//=========================================================================== -// Design Dimensioning -unsigned const N = 16; // Parallel Data Lanes -unsigned const P = 14; // Precision -using Hash = Murmur3_64<0xDEADF00D>; -unsigned const H = 64; - -template -struct net_axis { - ap_uint data; - ap_uint keep; - ap_uint<6> id; - ap_uint<1> last; -}; - -unsigned const DATA_WIDTH = 32; -unsigned const LINE_WIDTH = N*DATA_WIDTH; -using input_t = net_axis; // Input Tuple -using item_t = flit_v_t>; // Decomposed Input Item -using output_t = net_axis<32>; // Output: Wrapped float - - -//=========================================================================== -// Input Split -static void divide_data(hls::stream &src, hls::stream (&dst)[N]) { -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - static item_t buf[N] = {}; - static bool flush = false; - - if(flush) { - for(unsigned i = 0; i < N; i++) { -#pragma HLS unroll - if(buf[i].last) dst[i].write(buf[i]); - } - flush = false; - } - else { - input_t x; - if(src.read_nb(x)) { - bool const last = x.last; - for(unsigned i = 0; i < N; i++) { -#pragma HLS unroll - bool const keep = (x.keep(4*i+3, 4*i) == 0xF); - dst[i].write(item_t{last&&!keep, buf[i].val}); - buf[i] = item_t{keep, x.data((i+1)*DATA_WIDTH-1, i*DATA_WIDTH)}; - } - flush = last; - } - } -} // divide_data() - -//=========================================================================== -void top( - hls::stream &s_axis_data, - hls::stream &m_axis_card -) { -//#pragma HLS INTERFACE axis port=s_axis_data -//#pragma HLS INTERFACE axis port=m_axis_card - -#pragma HLS dataflow - - // Split the Data Lanes - static hls::stream src_hll[N]; - -#if defined( __VITIS_HLS__) - #pragma HLS aggregate variable=src_hll compact=bit -#else - #pragma HLS data_pack variable=src_hll -#endif - - divide_data(s_axis_data, src_hll); - - // HLL Sketch - class YAssign { - output_t& m_y; - public: - YAssign(output_t &y) : m_y(y) {} - void operator=(float v) { - union { float f; uint32_t i; } const conv = { .f = v }; - m_y.data = conv.i; - m_y.keep = 0xF; - } - }; - - static HllSketch hll_sketch; - hll_sketch.sketch( - src_hll, m_axis_card, - MemberLast(), [](item_t const& x){ return Hash()(x.val); }, - MemberLast(), [](output_t &y){ return YAssign(y); } - ); - -} // top diff --git a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/stream.hpp b/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/stream.hpp deleted file mode 100644 index 928393470..000000000 --- a/examples_hw/apps/hyperloglog/hls/hllsketch_16x32/stream.hpp +++ /dev/null @@ -1,310 +0,0 @@ -#ifndef STREAM_HPP -#define STREAM_HPP - -#include -#include -#include "bit_utils.hpp" - -#include -#include - -//=========================================================================== -// Flit structs carrying user data and batch boundary info (`last`) - -// Flit Base -struct flit_t { - bool last; - flit_t(bool l = false) : last(l) {} -}; - -// Flit with User Data -template -struct flit_v_t : public flit_t { - TV val; - flit_v_t() : flit_t(), val() {} - flit_v_t(bool l, TV const &v) : flit_t(l), val(v) {} -}; - -// Flit with User Key-Value Pair -template -struct flit_kv_t : public flit_v_t { - TK key; - flit_kv_t() : flit_v_t(), key() {} - flit_kv_t(bool l, TK const &k, TV const &v) : flit_v_t(l, v), key(k) {} -}; - -//=========================================================================== -// Lightweight plug-in member accessor functors. - -/** - * DevNull f; - * f(...) is assignable from any expression. - */ -struct DevNull { - struct Sink { - template - void operator=(T&& rhs) const {} - }; - template - constexpr Sink operator()(Args&&... args) const { return Sink(); } -}; - -/** - * Const f(1); - * f(arg0, arg1, ...) returns 1 - */ -template -class Const { - T const m_val; -public: - Const(T const& val) : m_val(val) {} -public: - template - constexpr T const& operator()(Args&&...) const noexcept { return m_val; } -}; - -/** - * Arg f; - * f(arg0, arg1, ...) returns arg_i - * Maintains references except for rvalue refs, which are reduced to prvalues. - */ -template -struct Arg { - template - constexpr auto operator()(A&& a, Args&&... args) const noexcept - -> decltype(Arg()(std::forward(args)...)) { - return Arg()(std::forward(args)...); - } -}; -template<> -struct Arg<0> { - template struct remove_rvref { using type = T; }; - template struct remove_rvref { using type = T; }; - template - constexpr auto operator()(A&& a, Args&&... args) const noexcept - -> typename remove_rvref::type { - return a; - } -}; - -// Flit Inspection Functors -#define BUILD_MEMBER_ACCESSOR(NAME, MEMBER) \ -struct NAME { \ - template \ - auto operator()(T& x) const noexcept \ - -> typename std::add_lvalue_reference::type { \ - return x.MEMBER; \ - } \ - template \ - auto operator()(T const& x) const noexcept \ - -> typename std::add_lvalue_reference::type>::type { \ - return x.MEMBER; \ - } \ - template \ - auto operator()(T&& x) const noexcept \ - -> decltype(x.MEMBER) { \ - return x.MEMBER; \ - } \ -}; -BUILD_MEMBER_ACCESSOR(MemberLast, last) -BUILD_MEMBER_ACCESSOR(MemberKey, key) -BUILD_MEMBER_ACCESSOR(MemberVal, val) -#undef BUILD_MEMBER_ACCESSOR - -//=========================================================================== -// Stateless Stream Manipulation Stages -template< - typename F = Arg<0>, - typename TI, - typename TO -> -void stage_guarded( - hls::stream &src, - hls::stream &dst, - F &&f = F() -) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - TI x; - if(src.read_nb(x)) dst.write(f(x)); -} - -template< - typename F = Arg<0>, // f(TI, 0:N-1) -> TO - typename TI, - typename TO, - unsigned N -> -void stream_split( - hls::stream &src, - hls::stream (&dst)[N], - F &&f = F() -) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - TI x; - if(src.read_nb(x)) { - for(unsigned i = 0; i < N; i++) { -#pragma HLS unroll - dst[i].write(f(x, i)); - } - } -} // stream_split() - -/** - * Folds N source streams into one destination stream output by output - * yielding f(...f(f(null_val, src[0].read()), src[1].read()), ... ). - */ -template< - typename F, // f(TO, TI) -> TO - typename TI, - typename TO, - unsigned N -> -void stream_fold( - hls::stream (&src)[N], - hls::stream &dst, - F&& f -) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - ap_uint empty; - for(unsigned i = 0; i < N; i++) { -#pragma HLS unroll - empty[i] = src[i].empty(); - } - - if(empty == 0) { - TO y = 0; - for(unsigned i = 0; i < N; i++) { -#pragma HLS unroll - y = f(y, src[i].read()); - } - dst.write(y); - } - -} // stream_fold() - -//=========================================================================== -/** - * Stream concatenation of N streams 0, .., N-1 switching to the next - * when seeing T.last. - */ -template -class StreamConcatenator { - ap_uint::value> idx = 0; - -public: - template< - typename TXL = MemberLast, - typename F = Arg<0>, - typename TI, - typename TO - > - void concat( - hls::stream (&src)[N], - hls::stream &dst, - TXL &&txl = TXL(), - F &&f = F() - ) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - TI x; - if(src[idx].read_nb(x)) { - if(txl(x)) { - if(idx == N-1) idx = 0; else idx++; - } - dst.write(f(x)); - } - } - -}; // class StreamConcatenator - -//--------------------------------------------------------------------------- -// Trivial Specialization for N=1 -template<> -class StreamConcatenator<1> { -public: - template< - typename TXL = MemberLast, - typename F = Arg<0>, - typename TI, - typename TO - > - void concat( - hls::stream (&src)[1], - hls::stream &dst, - TXL &&txl = TXL(), - F &&f = F() - ) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - TI x; - if(src[0].read_nb(x)) dst.write(f(x)); - } -}; // class StreamConcatenator - -template<> -class StreamConcatenator<0> {}; - -//=========================================================================== -template -class StreamInterleaver { - ap_uint::value> idx = 0; - -public: - /** - * Round-robin through source streams 0,..N-1 forwarding each - * value x to the destination stream after manipulation by f(x, idx). - * The default manipulation passes the value unchanged. - */ - template< - typename F = Arg<0>, - typename TI, - typename TO - > - void interleave( - hls::stream (&src)[N], - hls::stream &dst, - F &&f = F() - ) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - TI x; - if(src[idx].read_nb(x)) { - dst.write(f(x, idx)); - if(idx == N-1) idx = 0; else idx++; - } - } - -}; // class StreamInterleaver - -//--------------------------------------------------------------------------- -// Trivial Specialization for N=1 -template<> -class StreamInterleaver<1> { -public: - template< - typename F = Arg<0>, - typename TI, - typename TO - > - void interleave( - hls::stream (&src)[1], - hls::stream &dst, - F &&f = F() - ) { -#pragma HLS inline off -#pragma HLS pipeline II=1 - TI x; - if(src[0].read_nb(x)) { - dst.write(f(x, 0)); - } - } - -}; // class StreamInterleaver - -template<> -class StreamInterleaver<0> {}; - -#endif diff --git a/examples_hw/apps/hyperloglog/init_ip.tcl b/examples_hw/apps/hyperloglog/init_ip.tcl deleted file mode 100644 index 0280e42a5..000000000 --- a/examples_hw/apps/hyperloglog/init_ip.tcl +++ /dev/null @@ -1,2 +0,0 @@ -create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_hll -set_property -dict [list CONFIG.C_PROBE6_WIDTH {32} CONFIG.C_NUM_OF_PROBES {7} CONFIG.Component_Name {ila_hll} CONFIG.C_EN_STRG_QUAL {1} CONFIG.C_PROBE6_MU_CNT {2} CONFIG.C_PROBE5_MU_CNT {2} CONFIG.C_PROBE4_MU_CNT {2} CONFIG.C_PROBE3_MU_CNT {2} CONFIG.C_PROBE2_MU_CNT {2} CONFIG.C_PROBE1_MU_CNT {2} CONFIG.C_PROBE0_MU_CNT {2} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_hll] \ No newline at end of file diff --git a/examples_hw/apps/hyperloglog/vfpga_top.svh b/examples_hw/apps/hyperloglog/vfpga_top.svh deleted file mode 100644 index 7a11c0327..000000000 --- a/examples_hw/apps/hyperloglog/vfpga_top.svh +++ /dev/null @@ -1,70 +0,0 @@ -/** - * VFPGA TOP - * - * Tie up all signals to the user kernels - * Still to this day, interfaces are not supported by Vivado packager ... - * This means verilog style port connections are needed. - * - */ - -// -// Instantiate top level -// - -hllsketch_16x32 ( - .s_axis_host_sink_TDATA (axis_host_recv[0].tdata), - .s_axis_host_sink_TKEEP (axis_host_recv[0].tkeep), - .s_axis_host_sink_TLAST (axis_host_recv[0].tlast), - .s_axis_host_sink_TID (axis_host_recv[0].tid), - .s_axis_host_sink_TSTRB (0), - .s_axis_host_sink_TVALID (axis_host_recv[0].tvalid), - .s_axis_host_sink_TREADY (axis_host_recv[0].tready), - - .m_axis_host_src_TDATA (axis_host_send[0].tdata), - .m_axis_host_src_TKEEP (axis_host_send[0].tkeep), - .m_axis_host_src_TLAST (axis_host_send[0].tlast), - .m_axis_host_src_TID (axis_host_send[0].tid), - .m_axis_host_src_TSTRB (), - .m_axis_host_src_TVALID (axis_host_send[0].tvalid), - .m_axis_host_src_TREADY (axis_host_send[0].tready), - - .s_axi_control_ARADDR (axi_ctrl.araddr), - .s_axi_control_ARVALID (axi_ctrl.arvalid), - .s_axi_control_ARREADY (axi_ctrl.arready), - .s_axi_control_AWADDR (axi_ctrl.awaddr), - .s_axi_control_AWVALID (axi_ctrl.awvalid), - .s_axi_control_AWREADY (axi_ctrl.awready), - .s_axi_control_RDATA (axi_ctrl.rdata), - .s_axi_control_RRESP (axi_ctrl.rresp), - .s_axi_control_RVALID (axi_ctrl.rvalid), - .s_axi_control_RREADY (axi_ctrl.rready), - .s_axi_control_WDATA (axi_ctrl.wdata), - .s_axi_control_WSTRB (axi_ctrl.wstrb), - .s_axi_control_WVALID (axi_ctrl.wvalid), - .s_axi_control_WREADY (axi_ctrl.wready), - .s_axi_control_BRESP (axi_ctrl.bresp), - .s_axi_control_BVALID (axi_ctrl.bvalid), - .s_axi_control_BREADY (axi_ctrl.bready), - - .ap_clk (aclk), - .ap_rst_n (aresetn) -); - -// Tie-off the rest of the interfaces -always_comb notify.tie_off_m(); -always_comb sq_rd.tie_off_m(); -always_comb sq_wr.tie_off_m(); -always_comb cq_rd.tie_off_s(); -always_comb cq_wr.tie_off_s(); - -// ILA -ila_hll inst_ila_hll ( - .clk(aclk), - .probe0(axis_host_recv[0].tvalid), - .probe1(axis_host_recv[0].tready), - .probe2(axis_host_recv[0].tlast), - .probe3(axis_host_send[0].tvalid), - .probe4(axis_host_send[0].tready), - .probe5(axis_host_send[0].tlast), - .probe6(axis_host_send[0].tdata[31:0]) -); diff --git a/examples_hw/apps/rdma_aes/hdl/aes_pipe_stage.vhd b/examples_hw/apps/rdma_aes/hdl/aes_pipe_stage.vhd deleted file mode 100644 index 6c152fa92..000000000 --- a/examples_hw/apps/rdma_aes/hdl/aes_pipe_stage.vhd +++ /dev/null @@ -1,75 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity aes_pipe_stage is - generic( - OPERATION : integer := 0; -- [0-encryption, 1-decryption] - LAST : boolean := false - ); - port( - clk : in std_logic; - reset_n : in std_logic; - stall : in std_logic; - key_in : in std_logic_vector(127 downto 0); - last_in : in std_logic; - last_out : out std_logic; - keep_in : in std_logic_vector(15 downto 0); - keep_out : out std_logic_vector(15 downto 0); - dVal_in : in std_logic; - dVal_out : out std_logic; - data_in : in std_logic_vector(127 downto 0); - data_out : out std_logic_vector(127 downto 0) - ); -end entity aes_pipe_stage; - -architecture RTL of aes_pipe_stage is - - -- Internal signals - signal data_aes_out : std_logic_vector(127 downto 0); - -begin - - -- Instantiate AES round - GEN_RND: if LAST = false generate - GEN_AES_RND: entity work.aes_round - generic map( - OPERATION => OPERATION - ) - port map( - key_in => key_in, - data_in => data_in, - data_out => data_aes_out - ); - end generate GEN_RND; - - GEN_LAST: if LAST = true generate - GEN_AES_RND: entity work.aes_round_last - generic map( - OPERATION => OPERATION - ) - port map( - key_in => key_in, - key_last => (others => '0'), - data_in => data_in, - data_out => data_aes_out - ); - end generate GEN_LAST; - - -- Instantiate Pipe register - GEN_AES_REG: entity work.pipe_reg - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - last_in => last_in, - last_out => last_out, - keep_in => keep_in, - keep_out => keep_out, - dVal_in => dVal_in, - dVal_out => dVal_out, - data_in => data_aes_out, - data_out => data_out - ); - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/aes_pipeline.vhd b/examples_hw/apps/rdma_aes/hdl/aes_pipeline.vhd deleted file mode 100644 index a8f12ae23..000000000 --- a/examples_hw/apps/rdma_aes/hdl/aes_pipeline.vhd +++ /dev/null @@ -1,129 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity aes_pipeline is - generic( - KEY_WIDTH : integer := 128; - KEY_ROUNDS: integer := 11; - ENC_ROUNDS: integer := 10; - OPERATION : integer := 0; --[0-encryption, 1-decryption] - MODE : integer := 0 --[0-ECB, 1-CTR, 2-CBC] - ); - port( - clk : in std_logic; - reset_n : in std_logic; - stall : in std_logic; - -- Key - key_in : in std_logic_vector(KEY_ROUNDS*KEY_WIDTH-1 downto 0); - last_in : in std_logic; - last_out : out std_logic; - keep_in : in std_logic_vector(15 downto 0); - keep_out : out std_logic_vector(15 downto 0); - -- Data valid - dVal_in : in std_logic; -- Data valid - dVal_out : out std_logic; - -- Data - data_in : in std_logic_vector(127 downto 0); - data_out : out std_logic_vector(127 downto 0); - -- CTR - cntr_in : in std_logic_vector(127 downto 0) - ); -end entity aes_pipeline; - -architecture RTL of aes_pipeline is - - -- Internal signals - type dVal_array is array ((ENC_ROUNDS) downto 0) of std_logic; - type data_array is array ((ENC_ROUNDS) downto 0) of std_logic_vector(127 downto 0); - type last_array is array ((ENC_ROUNDS) downto 0) of std_logic; - type keep_array is array ((ENC_ROUNDS) downto 0) of std_logic_vector(15 downto 0); - - signal s_data_to_block : std_logic_vector(127 downto 0); - signal s_data_from_block : std_logic_vector(127 downto 0); - - signal dVal_pipe : dVal_array; -- Data valid signal pipeline - signal data_pipe : data_array; -- Data pipeline - signal last_pipe : last_array; - signal keep_pipe : keep_array; - -begin - - GEN_EBC: if (MODE = 0) generate - s_data_to_block <= data_in; - s_data_from_block <= data_pipe(ENC_ROUNDS); - end generate GEN_EBC; - - GEN_CTR: if (MODE = 1) generate - s_data_to_block <= data_in; - s_data_from_block <= cntr_in xor data_pipe(ENC_ROUNDS); - end generate GEN_CTR; - - GEN_CBC: if (MODE = 2) generate - -- encryption - GEN_CBC_EN: if (OPERATION = 0) generate - s_data_to_block <= data_in xor cntr_in; - s_data_from_block <= data_pipe(ENC_ROUNDS); - end generate GEN_CBC_EN; - - -- decryption - GEN_CBC_DE: if (OPERATION = 1) generate - s_data_to_block <= data_in; - s_data_from_block <= cntr_in xor data_pipe(ENC_ROUNDS); - end generate GEN_CBC_DE; - end generate GEN_CBC; - - -- Instantiate regular AES stages - GEN_AES: for i in 0 to ENC_ROUNDS generate - - GEN_S0: if i = 0 generate - S0: entity work.aes_pipe_stage - generic map( - OPERATION => OPERATION - ) - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - key_in => key_in(255 downto 128), - last_in => last_in, - last_out => last_pipe(0), - keep_in => keep_in, - keep_out => keep_pipe(0), - dVal_in => dVal_in, - dVal_out => dVal_pipe(0), - data_in => (s_data_to_block xor key_in(127 downto 0)), - data_out => data_pipe(0) - ); - end generate GEN_S0; - - GEN_SX: if i > 0 generate - SX: entity work.aes_pipe_stage - generic map( - OPERATION => OPERATION, - LAST => (i=ENC_ROUNDS) - ) - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - key_in => key_in(128*(i+1)+127 downto 128*(i+1)), - last_in => last_pipe(i-1), - last_out => last_pipe(i), - keep_in => keep_pipe(i-1), - keep_out => keep_pipe(i), - dVal_in => dVal_pipe(i-1), - dVal_out => dVal_pipe(i), - data_in => data_pipe(i-1), - data_out => data_pipe(i) - ); - end generate GEN_SX; - - end generate GEN_AES; - - last_out <= last_pipe(ENC_ROUNDS); - keep_out <= keep_pipe(ENC_ROUNDS); - dVal_out <= dVal_pipe(ENC_ROUNDS); - data_out <= s_data_from_block; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/aes_round.vhd b/examples_hw/apps/rdma_aes/hdl/aes_round.vhd deleted file mode 100644 index a0f145e58..000000000 --- a/examples_hw/apps/rdma_aes/hdl/aes_round.vhd +++ /dev/null @@ -1,81 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity aes_round is - generic( - OPERATION : integer := 0 -- [0-encryption, 1-decryption] - ); - port( - key_in : in std_logic_vector(127 downto 0); - data_in : in std_logic_vector(127 downto 0); - data_out : out std_logic_vector(127 downto 0) - ); -end entity aes_round; - -architecture RTL of aes_round is - - -- Internal signals - signal data_step_one : std_logic_vector(127 downto 0); - signal data_step_two : std_logic_vector(127 downto 0); - signal data_step_three : std_logic_vector(127 downto 0); - signal data_step_four : std_logic_vector(127 downto 0); - -begin - - -- Encryption - GEN_ENC: if OPERATION = 0 generate - -- S-box stage - GEN_SBOX: for i in 0 to 15 generate - SBOX: entity work.s_box_lut port map( - data_in => data_in(8*i+7 downto 8*i), - data_out => data_step_one(8*i+7 downto 8*i) - ); - end generate GEN_SBOX; - - -- Shift row - GEN_SROW: entity work.shift_rows port map( - data_in => data_step_one, - data_out => data_step_two - ); - - -- Mix columns - GEN_MCOL: entity work.mix_columns port map( - data_in => data_step_two, - data_out => data_step_three - ); - - -- Add round key - data_step_four <= data_step_three xor key_in; - end generate GEN_ENC; - - -- Decryption - GEN_DEC: if OPERATION = 1 generate - -- Inverse Shift row - GEN_INV_SROW: entity work.inv_shift_rows port map( - data_in => data_in, - data_out => data_step_one - ); - - -- Inverse S-box stage - GEN_INV_SBOX: for i in 0 to 15 generate - INV_SBOX: entity work.inv_s_box_lut port map( - data_in => data_step_one(8*i+7 downto 8*i), - data_out => data_step_two(8*i+7 downto 8*i) - ); - end generate GEN_INV_SBOX; - - -- Add round key - data_step_three <= data_step_two xor key_in; - - -- Inverse Mix columns - GEN_INV_MCOL: entity work.inv_mix_columns port map( - data_in => data_step_three, - data_out => data_step_four - ); - end generate GEN_DEC; - - -- Output - data_out <= data_step_four; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/aes_round_last.vhd b/examples_hw/apps/rdma_aes/hdl/aes_round_last.vhd deleted file mode 100644 index c2397d03e..000000000 --- a/examples_hw/apps/rdma_aes/hdl/aes_round_last.vhd +++ /dev/null @@ -1,64 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity aes_round_last is - generic( - OPERATION : integer := 0 -- [0-encryption, 1-decryption] - ); - port( - key_in : in std_logic_vector(127 downto 0); - key_last : in std_logic_vector(127 downto 0); - data_in : in std_logic_vector(127 downto 0); - data_out : out std_logic_vector(127 downto 0) - ); -end entity aes_round_last; - -architecture RTL of aes_round_last is - - -- Internal signals - signal data_step_one : std_logic_vector(127 downto 0); - signal data_step_two : std_logic_vector(127 downto 0); - -begin - - -- Encryption - GEN_ENC: if OPERATION = 0 generate - -- S-box stage - GEN_SBOX: for i in 0 to 15 generate - SBOX: entity work.s_box_lut port map( - data_in => data_in(8*i+7 downto 8*i), - data_out => data_step_one(8*i+7 downto 8*i) - ); - end generate GEN_SBOX; - - -- Shift row - GEN_SROW: entity work.shift_rows port map( - data_in => data_step_one, - data_out => data_step_two - ); - - end generate GEN_ENC; - - -- Decryption - GEN_DEC: if OPERATION = 1 generate - -- Inverse Shift row - GEN_INV_SROW: entity work.inv_shift_rows port map( - data_in => data_in, - data_out => data_step_one - ); - - -- Inverse S-box stage - GEN_INV_SBOX: for i in 0 to 15 generate - INV_SBOX: entity work.inv_s_box_lut port map( - data_in => data_step_one(8*i+7 downto 8*i), - data_out => data_step_two(8*i+7 downto 8*i) - ); - end generate GEN_INV_SBOX; - - end generate GEN_DEC; - - -- Last add round key - data_out <= data_step_two xor key_in; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/aes_top.vhd b/examples_hw/apps/rdma_aes/hdl/aes_top.vhd deleted file mode 100644 index bd7d9d36a..000000000 --- a/examples_hw/apps/rdma_aes/hdl/aes_top.vhd +++ /dev/null @@ -1,256 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -library work; -use work.rounds.all; - -entity aes_top is - generic( - NPAR : integer := 2; - KEY_WIDTH : integer := 128; - KEY_ROUNDS: integer := 11; - OPERATION : integer := 0; -- [0-encryption, 1-decryption] - MODE : integer := 0 -- [0-ECB, 1-CTR, 2-CBC] - ); - port( - clk : in std_logic; - reset_n : in std_logic; - stall : in std_logic; - -- key/Last/ keep - key_in : in std_logic_vector(KEY_ROUNDS*KEY_WIDTH-1 downto 0); - last_in : in std_logic; - last_out : out std_logic; - keep_in : in std_logic_vector(NPAR*16-1 downto 0); - keep_out : out std_logic_vector(NPAR*16-1 downto 0); - -- Data valid - dVal_in : in std_logic; -- Data valid - dVal_out : out std_logic; - -- Data - data_in : in std_logic_vector(NPAR*128-1 downto 0); - data_out : out std_logic_vector(NPAR*128-1 downto 0); - -- CTR - nonce+value - cntr_in : in std_logic_vector(NPAR*128-1 downto 0) - ); -end entity aes_top; - -architecture RTL of aes_top is - - type keep_array is array (NPAR-1 downto 0) of std_logic_vector(15 downto 0); - - type dataf_array is array (NPAR-1 downto 0) of std_logic_vector(NPAR*128-1 downto 0); - type data_array is array (NPAR-1 downto 0) of std_logic_vector(128-1 downto 0); - - type keepf_array is array (NPAR-1 downto 0) of std_logic_vector(NPAR*16-1 downto 0); - - --variable key_rounds : integer := key_rounds_number(KEY_WIDTH); - -- Internal signals - signal dVal : std_logic_vector(NPAR-1 downto 0); - signal last : std_logic_vector(NPAR-1 downto 0); - - signal s_cntr : std_logic_vector(NPAR*128-1 downto 0); - - signal keep : keep_array; - signal s_data : data_array; - - signal s_data_full : dataf_array; - signal s_data_piped: dataf_array; - signal s_keep_piped: keepf_array; - -begin - -GEN_4: if (MODE=0 OR MODE=1 OR (MODE=2 AND OPERATION=1)) generate - -- Instantiate AES pipelines - GEN_AES_PAR: for i in 0 to NPAR-1 generate - GEN_AES_PIPE: entity work.aes_pipeline - generic map( - KEY_WIDTH => KEY_WIDTH, - KEY_ROUNDS => key_rounds_number(KEY_WIDTH), - ENC_ROUNDS => encrypt_rounds_number(KEY_WIDTH), - MODE => MODE, - OPERATION => OPERATION - ) - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - -- Key - key_in => key_in, - -- Data valid - dVal_in => dVal_in, - dVal_out => dVal(i), - last_in => last_in, - last_out => last(i), - keep_in => keep_in(i*16+15 downto i*16), - keep_out => keep(i), - -- Data - data_in => data_in(i*128+127 downto i*128), - data_out => data_out(i*128+127 downto i*128), - -- CTR - cntr_in => s_cntr(i*128+127 downto i*128) - ); - end generate GEN_AES_PAR; - - GEN_DAT: if (MODE=2 OR MODE=1) generate - DATA_PIPE_0: entity work.data_pipeline - generic map( - NPAR => NPAR, - ENC_ROUNDS => encrypt_rounds_number(KEY_WIDTH) - ) - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - -- Control - keep_in => keep_in, - keep_out => s_keep_piped(0), - -- Data - data_in => cntr_in, - data_out => s_data_piped(0) - ); - end generate GEN_DAT; - - GEN_CNTR2: if (MODE=2 OR MODE=1) generate - s_cntr <= s_data_piped(0); - end generate; - - GEN_CNTR1: if (MODE=0) generate - s_cntr <= cntr_in; - end generate GEN_CNTR1; - - GEN_VALID: process (dVal) is - variable tmp : std_logic; - begin - tmp := '0'; - for i in 0 to NPAR-1 loop - tmp := tmp or dVal(i); - end loop; - dVal_out <= tmp; - end process GEN_VALID; - - GEN_LAST: process (last) is - variable tmp : std_logic; - begin - tmp := '0'; - for i in 0 to NPAR-1 loop - tmp := tmp or last(i); - end loop; - last_out <= tmp; - end process GEN_LAST; - - GEN_KEEP: for i in 0 to NPAR-1 generate - keep_out(i*16+15 downto i*16) <= keep(i); - end generate GEN_KEEP; - -end generate GEN_4; - -GEN_1: if (MODE=2 AND OPERATION=0) generate - -- Instantiate AES pipelines - AES_PIPE_0: entity work.aes_pipeline - generic map( - KEY_WIDTH => KEY_WIDTH, - KEY_ROUNDS => key_rounds_number(KEY_WIDTH), - ENC_ROUNDS => encrypt_rounds_number(KEY_WIDTH), - MODE => MODE, - OPERATION => OPERATION - ) - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - -- Key - key_in => key_in, - -- Data valid - dVal_in => dVal_in, - dVal_out => dVal(0), - last_in => last_in, - last_out => last(0), - keep_in => keep_in(0*16+15 downto 0*16), - keep_out => open, - -- Data - data_in => data_in(0*128+127 downto 0*128), - data_out => s_data(0), - -- CTR - cntr_in => cntr_in(0*128+127 downto 0*128) - ); - - DATA_PIPE_0: entity work.data_pipeline - generic map( - NPAR => NPAR, - ENC_ROUNDS => encrypt_rounds_number(KEY_WIDTH) - ) - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - -- Control - keep_in => keep_in, - keep_out => s_keep_piped(0), - -- Data - data_in => data_in, - data_out => s_data_piped(0) - ); - - s_data_full(0)(0*128+127 downto 0*128) <= s_data(0); - s_data_full(0)((NPAR-1)*128+127 downto 0*128+128) <= s_data_piped(0)((NPAR-1)*128+127 downto 0*128+128); - - AES_PAR: for i in 1 to NPAR-1 generate - AES_PIPE_X: entity work.aes_pipeline - generic map( - KEY_WIDTH => KEY_WIDTH, - KEY_ROUNDS => key_rounds_number(KEY_WIDTH), - ENC_ROUNDS => encrypt_rounds_number(KEY_WIDTH), - MODE => MODE, --[0-encryption, 1-decryption] - OPERATION => OPERATION --[0-ECB, 1-CTR, 2-CBC] - ) - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - -- Key - key_in => key_in, - -- Data valid - dVal_in => dVal(i-1), - dVal_out => dVal(i), - last_in => last(i-1), - last_out => last(i), - keep_in => s_keep_piped(i-1)(i*16+15 downto i*16), - keep_out => keep(i), - -- Data - data_in => s_data_full(i-1)(i*128+127 downto i*128), - data_out => s_data(i), - -- CTR - cntr_in => s_data_full(i-1)((i-1)*128+127 downto (i-1)*128) - ); - - DATA_PIPE_X: entity work.data_pipeline - generic map( - NPAR => NPAR, - ENC_ROUNDS => encrypt_rounds_number(KEY_WIDTH) - ) - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - -- Control - keep_in => s_keep_piped(i-1), - keep_out => s_keep_piped(i), - -- Data - data_in => s_data_full(i-1), - data_out => s_data_piped(i) - ); - - s_data_full(i)(i*128+127 downto i*128) <= s_data(i); - s_data_full(i)((NPAR-1)*128+127 downto i*128+128) <= s_data_piped(i)((NPAR-1)*128+127 downto i*128+128); - s_data_full(i)((i-1)*128+127 downto 0) <= s_data_piped(i)((i-1)*128+127 downto 0); - - end generate AES_PAR; - - data_out <= s_data_full(NPAR-1); - dVal_out <= dVal(NPAR-1); - last_out <= last(NPAR-1); - keep_out <= s_keep_piped(NPAR-1); - -end generate GEN_1; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/data_pipeline.vhd b/examples_hw/apps/rdma_aes/hdl/data_pipeline.vhd deleted file mode 100644 index 0d6a9faed..000000000 --- a/examples_hw/apps/rdma_aes/hdl/data_pipeline.vhd +++ /dev/null @@ -1,86 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity data_pipeline is - generic( - NPAR : integer := 2; - ENC_ROUNDS: integer := 10 - ); - port( - clk : in std_logic; - reset_n : in std_logic; - stall : in std_logic; - -- Control - keep_in : in std_logic_vector(NPAR*16-1 downto 0); - keep_out : out std_logic_vector(NPAR*16-1 downto 0); - -- Data - data_in : in std_logic_vector(NPAR*128-1 downto 0); - data_out : out std_logic_vector(NPAR*128-1 downto 0) - ); -end entity data_pipeline; - -architecture RTL of data_pipeline is - - -- Internal signals - type dVal_array is array ((ENC_ROUNDS) downto 0) of std_logic; - type data_array is array ((ENC_ROUNDS) downto 0) of std_logic_vector(NPAR*128-1 downto 0); - type last_array is array ((ENC_ROUNDS) downto 0) of std_logic; - type keep_array is array ((ENC_ROUNDS) downto 0) of std_logic_vector(NPAR*16-1 downto 0); - - signal dVal_pipe : dVal_array; -- Data valid signal pipeline - signal data_pipe : data_array; -- Data pipeline - signal last_pipe : last_array; - signal keep_pipe : keep_array; - -begin - - -- Instantiate data stages - GEN_DAT: for i in 0 to ENC_ROUNDS generate - - GEN_D0: if i = 0 generate - D0: entity work.pipe_reg - generic map( - DATA_WIDTH => NPAR*128 - ) - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - last_in => '0', - last_out => last_pipe(0), - keep_in => keep_in, - keep_out => keep_pipe(0), - dVal_in => '1', - dVal_out => dVal_pipe(0), - data_in => data_in, - data_out => data_pipe(0) - ); - end generate GEN_D0; - - GEN_DX: if i > 0 generate - DX: entity work.pipe_reg - generic map( - DATA_WIDTH => NPAR*128 - ) - port map( - clk => clk, - reset_n => reset_n, - stall => stall, - last_in => last_pipe(i-1), - last_out => last_pipe(i), - keep_in => keep_pipe(i-1), - keep_out => keep_pipe(i), - dVal_in => dVal_pipe(i-1), - dVal_out => dVal_pipe(i), - data_in => data_pipe(i-1), - data_out => data_pipe(i) - ); - end generate GEN_DX; - - end generate GEN_DAT; - - keep_out <= keep_pipe(ENC_ROUNDS); - data_out <= data_pipe(ENC_ROUNDS); - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/gf_mult.vhd b/examples_hw/apps/rdma_aes/hdl/gf_mult.vhd deleted file mode 100644 index 06b16a6cc..000000000 --- a/examples_hw/apps/rdma_aes/hdl/gf_mult.vhd +++ /dev/null @@ -1,127 +0,0 @@ -------------------------------------------------------------------------- --- Copyright (C) 2018 Kaan Kara - Systems Group, ETH Zurich - --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU Affero General Public License as published --- by the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. - --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU Affero General Public License for more details. - --- You should have received a copy of the GNU Affero General Public License --- along with this program. If not, see . ----------------------------------------------------------------------------- - -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity gf_mult is - generic ( - SELECTION : integer := 9 - ); - port ( - data_in : in std_logic_vector(7 downto 0); - data_out : out std_logic_vector(7 downto 0) - ); -end entity gf_mult; - -architecture behavioral of gf_mult is - -signal x : integer range 0 to 15; -signal y : integer range 0 to 15; - -type lookup_type is array(15 downto 0) of std_logic_vector(127 downto 0); -signal lookup : lookup_type; - -begin - - SELECTION_9: if SELECTION = 9 generate - lookup <= ( - 0 => X"00_09_12_1b_24_2d_36_3f_48_41_5a_53_6c_65_7e_77", - 1 => X"90_99_82_8b_b4_bd_a6_af_d8_d1_ca_c3_fc_f5_ee_e7", - 2 => X"3b_32_29_20_1f_16_0d_04_73_7a_61_68_57_5e_45_4c", - 3 => X"ab_a2_b9_b0_8f_86_9d_94_e3_ea_f1_f8_c7_ce_d5_dc", - 4 => X"76_7f_64_6d_52_5b_40_49_3e_37_2c_25_1a_13_08_01", - 5 => X"e6_ef_f4_fd_c2_cb_d0_d9_ae_a7_bc_b5_8a_83_98_91", - 6 => X"4d_44_5f_56_69_60_7b_72_05_0c_17_1e_21_28_33_3a", - 7 => X"dd_d4_cf_c6_f9_f0_eb_e2_95_9c_87_8e_b1_b8_a3_aa", - 8 => X"ec_e5_fe_f7_c8_c1_da_d3_a4_ad_b6_bf_80_89_92_9b", - 9 => X"7c_75_6e_67_58_51_4a_43_34_3d_26_2f_10_19_02_0b", - 10 => X"d7_de_c5_cc_f3_fa_e1_e8_9f_96_8d_84_bb_b2_a9_a0", - 11 => X"47_4e_55_5c_63_6a_71_78_0f_06_1d_14_2b_22_39_30", - 12 => X"9a_93_88_81_be_b7_ac_a5_d2_db_c0_c9_f6_ff_e4_ed", - 13 => X"0a_03_18_11_2e_27_3c_35_42_4b_50_59_66_6f_74_7d", - 14 => X"a1_a8_b3_ba_85_8c_97_9e_e9_e0_fb_f2_cd_c4_df_d6", - 15 => X"31_38_23_2a_15_1c_07_0e_79_70_6b_62_5d_54_4f_46"); - end generate; - - SELECTION_11: if SELECTION = 11 generate - lookup <= ( - 0 => X"00_0b_16_1d_2c_27_3a_31_58_53_4e_45_74_7f_62_69", - 1 => X"b0_bb_a6_ad_9c_97_8a_81_e8_e3_fe_f5_c4_cf_d2_d9", - 2 => X"7b_70_6d_66_57_5c_41_4a_23_28_35_3e_0f_04_19_12", - 3 => X"cb_c0_dd_d6_e7_ec_f1_fa_93_98_85_8e_bf_b4_a9_a2", - 4 => X"f6_fd_e0_eb_da_d1_cc_c7_ae_a5_b8_b3_82_89_94_9f", - 5 => X"46_4d_50_5b_6a_61_7c_77_1e_15_08_03_32_39_24_2f", - 6 => X"8d_86_9b_90_a1_aa_b7_bc_d5_de_c3_c8_f9_f2_ef_e4", - 7 => X"3d_36_2b_20_11_1a_07_0c_65_6e_73_78_49_42_5f_54", - 8 => X"f7_fc_e1_ea_db_d0_cd_c6_af_a4_b9_b2_83_88_95_9e", - 9 => X"47_4c_51_5a_6b_60_7d_76_1f_14_09_02_33_38_25_2e", - 10 => X"8c_87_9a_91_a0_ab_b6_bd_d4_df_c2_c9_f8_f3_ee_e5", - 11 => X"3c_37_2a_21_10_1b_06_0d_64_6f_72_79_48_43_5e_55", - 12 => X"01_0a_17_1c_2d_26_3b_30_59_52_4f_44_75_7e_63_68", - 13 => X"b1_ba_a7_ac_9d_96_8b_80_e9_e2_ff_f4_c5_ce_d3_d8", - 14 => X"7a_71_6c_67_56_5d_40_4b_22_29_34_3f_0e_05_18_13", - 15 => X"ca_c1_dc_d7_e6_ed_f0_fb_92_99_84_8f_be_b5_a8_a3"); - end generate; - - SELECTION_13: if SELECTION = 13 generate - lookup <= ( - 0 => X"00_0d_1a_17_34_39_2e_23_68_65_72_7f_5c_51_46_4b", - 1 => X"d0_dd_ca_c7_e4_e9_fe_f3_b8_b5_a2_af_8c_81_96_9b", - 2 => X"bb_b6_a1_ac_8f_82_95_98_d3_de_c9_c4_e7_ea_fd_f0", - 3 => X"6b_66_71_7c_5f_52_45_48_03_0e_19_14_37_3a_2d_20", - 4 => X"6d_60_77_7a_59_54_43_4e_05_08_1f_12_31_3c_2b_26", - 5 => X"bd_b0_a7_aa_89_84_93_9e_d5_d8_cf_c2_e1_ec_fb_f6", - 6 => X"d6_db_cc_c1_e2_ef_f8_f5_be_b3_a4_a9_8a_87_90_9d", - 7 => X"06_0b_1c_11_32_3f_28_25_6e_63_74_79_5a_57_40_4d", - 8 => X"da_d7_c0_cd_ee_e3_f4_f9_b2_bf_a8_a5_86_8b_9c_91", - 9 => X"0a_07_10_1d_3e_33_24_29_62_6f_78_75_56_5b_4c_41", - 10 => X"61_6c_7b_76_55_58_4f_42_09_04_13_1e_3d_30_27_2a", - 11 => X"b1_bc_ab_a6_85_88_9f_92_d9_d4_c3_ce_ed_e0_f7_fa", - 12 => X"b7_ba_ad_a0_83_8e_99_94_df_d2_c5_c8_eb_e6_f1_fc", - 13 => X"67_6a_7d_70_53_5e_49_44_0f_02_15_18_3b_36_21_2c", - 14 => X"0c_01_16_1b_38_35_22_2f_64_69_7e_73_50_5d_4a_47", - 15 => X"dc_d1_c6_cb_e8_e5_f2_ff_b4_b9_ae_a3_80_8d_9a_97"); - end generate; - - SELECTION_14: if SELECTION = 14 generate - lookup <= ( - 0 => X"00_0e_1c_12_38_36_24_2a_70_7e_6c_62_48_46_54_5a", - 1 => X"e0_ee_fc_f2_d8_d6_c4_ca_90_9e_8c_82_a8_a6_b4_ba", - 2 => X"db_d5_c7_c9_e3_ed_ff_f1_ab_a5_b7_b9_93_9d_8f_81", - 3 => X"3b_35_27_29_03_0d_1f_11_4b_45_57_59_73_7d_6f_61", - 4 => X"ad_a3_b1_bf_95_9b_89_87_dd_d3_c1_cf_e5_eb_f9_f7", - 5 => X"4d_43_51_5f_75_7b_69_67_3d_33_21_2f_05_0b_19_17", - 6 => X"76_78_6a_64_4e_40_52_5c_06_08_1a_14_3e_30_22_2c", - 7 => X"96_98_8a_84_ae_a0_b2_bc_e6_e8_fa_f4_de_d0_c2_cc", - 8 => X"41_4f_5d_53_79_77_65_6b_31_3f_2d_23_09_07_15_1b", - 9 => X"a1_af_bd_b3_99_97_85_8b_d1_df_cd_c3_e9_e7_f5_fb", - 10 => X"9a_94_86_88_a2_ac_be_b0_ea_e4_f6_f8_d2_dc_ce_c0", - 11 => X"7a_74_66_68_42_4c_5e_50_0a_04_16_18_32_3c_2e_20", - 12 => X"ec_e2_f0_fe_d4_da_c8_c6_9c_92_80_8e_a4_aa_b8_b6", - 13 => X"0c_02_10_1e_34_3a_28_26_7c_72_60_6e_44_4a_58_56", - 14 => X"37_39_2b_25_0f_01_13_1d_47_49_5b_55_7f_71_63_6d", - 15 => X"d7_d9_cb_c5_ef_e1_f3_fd_a7_a9_bb_b5_9f_91_83_8d"); - end generate; - - x <= to_integer(unsigned(data_in(7 downto 4))); - y <= 15 - to_integer(unsigned(data_in(3 downto 0))); - - data_out <= lookup(x)(y*8+7 downto y*8 ); - -end architecture; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/inv_mix_columns.vhd b/examples_hw/apps/rdma_aes/hdl/inv_mix_columns.vhd deleted file mode 100644 index 520b69060..000000000 --- a/examples_hw/apps/rdma_aes/hdl/inv_mix_columns.vhd +++ /dev/null @@ -1,81 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity inv_mix_columns is - port( - data_in : in std_logic_vector(127 downto 0); - data_out : out std_logic_vector(127 downto 0) - ); -end entity inv_mix_columns; - -architecture RTL of inv_mix_columns is - - -- Internal signals - type data_array is array (15 downto 0) of std_logic_vector(7 downto 0); - - -- x9, xb, xd, xe multiplication - signal in_array, out_array, in_array_x9, in_array_xb, in_array_xd, in_array_xe: data_array; - -begin - - -- Input generation - GEN_IN: for i in 15 downto 0 generate - in_array(15-i) <= data_in(i*8+7 downto i*8); - end generate GEN_IN; - - -- Multiplication - GEN_M: for i in 15 downto 0 generate - -- x9 - gf_inst0: entity work.gf_mult - generic map( - SELECTION => 9 - ) - port map( - data_in => in_array(i), - data_out => in_array_x9(i) - ); - - -- x11 - gf_inst1: entity work.gf_mult - generic map( - SELECTION => 11 - ) - port map( - data_in => in_array(i), - data_out => in_array_xb(i) - ); - -- x13 - gf_inst2: entity work.gf_mult - generic map( - SELECTION => 13 - ) - port map( - data_in => in_array(i), - data_out => in_array_xd(i) - ); - -- x14 - gf_inst3: entity work.gf_mult - generic map( - SELECTION => 14 - ) - port map( - data_in => in_array(i), - data_out => in_array_xe(i) - ); - end generate GEN_M; - - -- Mixed columns generation - GEN_MC: for i in 0 to 3 generate - out_array(4*i) <= in_array_xe(4*i) xor in_array_xb(4*i+1) xor in_array_xd(4*i+2) xor in_array_x9(4*i+3); - out_array(4*i+1) <= in_array_x9(4*i) xor in_array_xe(4*i+1) xor in_array_xb(4*i+2) xor in_array_xd(4*i+3); - out_array(4*i+2) <= in_array_xd(4*i) xor in_array_x9(4*i+1) xor in_array_xe(4*i+2) xor in_array_xb(4*i+3); - out_array(4*i+3) <= in_array_xb(4*i) xor in_array_xd(4*i+1) xor in_array_x9(4*i+2) xor in_array_xe(4*i+3); - end generate; - - -- Output generation - GEN_O: for i in 15 downto 0 generate - data_out(i*8+7 downto i*8) <= out_array(15-i); - end generate GEN_O; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/inv_s_box_lut.vhd b/examples_hw/apps/rdma_aes/hdl/inv_s_box_lut.vhd deleted file mode 100644 index 23fa43f1d..000000000 --- a/examples_hw/apps/rdma_aes/hdl/inv_s_box_lut.vhd +++ /dev/null @@ -1,41 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity inv_s_box_lut is - port( - data_in : in std_logic_vector(7 downto 0); - data_out : out std_logic_vector(7 downto 0) - ); -end entity inv_s_box_lut; - -architecture RTL of inv_s_box_lut is - - -- Internal RAM - type ram_type is array(natural range<>) of std_logic_vector(7 downto 0); - - constant inv_sbox_ram : ram_type(255 downto 0) := ( - X"7d", X"0c", X"21", X"55", X"63", X"14", X"69", X"e1", X"26", X"d6", X"77", X"ba", X"7e", X"04", X"2b", X"17", - X"61", X"99", X"53", X"83", X"3c", X"bb", X"eb", X"c8", X"b0", X"f5", X"2a", X"ae", X"4d", X"3b", X"e0", X"a0", - X"ef", X"9c", X"c9", X"93", X"9f", X"7a", X"e5", X"2d", X"0d", X"4a", X"b5", X"19", X"a9", X"7f", X"51", X"60", - X"5f", X"ec", X"80", X"27", X"59", X"10", X"12", X"b1", X"31", X"c7", X"07", X"88", X"33", X"a8", X"dd", X"1f", - X"f4", X"5a", X"cd", X"78", X"fe", X"c0", X"db", X"9a", X"20", X"79", X"d2", X"c6", X"4b", X"3e", X"56", X"fc", - X"1b", X"be", X"18", X"aa", X"0e", X"62", X"b7", X"6f", X"89", X"c5", X"29", X"1d", X"71", X"1a", X"f1", X"47", - X"6e", X"df", X"75", X"1c", X"e8", X"37", X"f9", X"e2", X"85", X"35", X"ad", X"e7", X"22", X"74", X"ac", X"96", - X"73", X"e6", X"b4", X"f0", X"ce", X"cf", X"f2", X"97", X"ea", X"dc", X"67", X"4f", X"41", X"11", X"91", X"3a", - X"6b", X"8a", X"13", X"01", X"03", X"bd", X"af", X"c1", X"02", X"0f", X"3f", X"ca", X"8f", X"1e", X"2c", X"d0", - X"06", X"45", X"b3", X"b8", X"05", X"58", X"e4", X"f7", X"0a", X"d3", X"bc", X"8c", X"00", X"ab", X"d8", X"90", - X"84", X"9d", X"8d", X"a7", X"57", X"46", X"15", X"5e", X"da", X"b9", X"ed", X"fd", X"50", X"48", X"70", X"6c", - X"92", X"b6", X"65", X"5d", X"cc", X"5c", X"a4", X"d4", X"16", X"98", X"68", X"86", X"64", X"f6", X"f8", X"72", - X"25", X"d1", X"8b", X"6d", X"49", X"a2", X"5b", X"76", X"b2", X"24", X"d9", X"28", X"66", X"a1", X"2e", X"08", - X"4e", X"c3", X"fa", X"42", X"0b", X"95", X"4c", X"ee", X"3d", X"23", X"c2", X"a6", X"32", X"94", X"7b", X"54", - X"cb", X"e9", X"de", X"c4", X"44", X"43", X"8e", X"34", X"87", X"ff", X"2f", X"9b", X"82", X"39", X"e3", X"7c", - X"fb", X"d7", X"f3", X"81", X"9e", X"a3", X"40", X"bf", X"38", X"a5", X"36", X"30", X"d5", X"6a", X"09", X"52" - ); - -begin - - -- Output - data_out <= inv_sbox_ram(to_integer(unsigned(data_in))); - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/inv_shift_rows.vhd b/examples_hw/apps/rdma_aes/hdl/inv_shift_rows.vhd deleted file mode 100644 index da73d5d95..000000000 --- a/examples_hw/apps/rdma_aes/hdl/inv_shift_rows.vhd +++ /dev/null @@ -1,54 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity inv_shift_rows is - port( - data_in : in std_logic_vector(127 downto 0); - data_out : out std_logic_vector(127 downto 0) - ); -end entity inv_shift_rows; - -architecture RTL of inv_shift_rows is - - -- Internal signals - type data_array is array (15 downto 0) of std_logic_vector(7 downto 0); - signal in_array, out_array : data_array; - -begin - - -- Input generation - GEN_IN: for i in 15 downto 0 generate - in_array(15-i) <= data_in(i*8+7 downto i*8); - end generate GEN_IN; - - --First mixed column input - out_array(0) <= in_array(0); - out_array(5) <= in_array(1); - out_array(10) <= in_array(2); - out_array(15) <= in_array(3); - - -- Second mixed column input - out_array(4) <= in_array(4); - out_array(9) <= in_array(5); - out_array(14) <= in_array(6); - out_array(3) <= in_array(7); - - -- Third mixed column input - out_array(8) <= in_array(8); - out_array(13) <= in_array(9); - out_array(2) <= in_array(10); - out_array(7) <= in_array(11); - - -- Fourth mixed column input - out_array(12) <= in_array(12); - out_array(1) <= in_array(13); - out_array(6) <= in_array(14); - out_array(11) <= in_array(15); - - -- Output generation - GEN_OUT: for i in 15 downto 0 generate - data_out(i*8+7 downto i*8) <= out_array(15-i); - end generate GEN_OUT; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/key_expansion.vhd b/examples_hw/apps/rdma_aes/hdl/key_expansion.vhd deleted file mode 100644 index 6fabb5d1e..000000000 --- a/examples_hw/apps/rdma_aes/hdl/key_expansion.vhd +++ /dev/null @@ -1,100 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity key_expansion is - generic( - KEY_WIDTH : integer := 128 - ); - port( - key_in : in std_logic_vector(KEY_WIDTH-1 downto 0); - key_out : out std_logic_vector(KEY_WIDTH-1 downto 0); - rnd_const : in std_logic_vector(7 downto 0) - ); -end entity key_expansion; - -architecture RTL of key_expansion is - - -- Internal signals - type word_array is array ((KEY_WIDTH/32-1) downto 0) of std_logic_vector(31 downto 0); - signal key_word : word_array; - signal key_next : word_array; - - signal key_shift : std_logic_vector(31 downto 0); - signal key_s_box_0 : std_logic_vector(31 downto 0); - signal key_s_box_4 : std_logic_vector(31 downto 0); - signal temp_0 : std_logic_vector(31 downto 0); - signal temp_4 : std_logic_vector(31 downto 0); - signal key_temp : std_logic_vector(KEY_WIDTH-1 downto 0); - - -begin - - -- Key words - GEN_KW: for i in 0 to (KEY_WIDTH/32-1) generate - key_word(((KEY_WIDTH/32-1))-i) <= key_in(32*i+31 downto 32*i); --moc: key_word(((KEY_WIDTH/32-1))-i) - end generate GEN_KW; - - -- Rotate 8 bits - key_shift <= key_word((KEY_WIDTH/32-1))(23 downto 0) & key_word((KEY_WIDTH/32-1))(31 downto 24); - - -- S-box-0: applied at state level for temporal value %(KEY_WIDTH/32) == 0 - GEN_SBOX_0: for i in 0 to 3 generate - SBOX_0: entity work.s_box_lut port map( - data_in => key_shift(i*8+7 downto i*8), - data_out => key_s_box_0(i*8+7 downto i*8) - ); - end generate GEN_SBOX_0; - - -- Add round constant - temp_0(31 downto 24) <= key_s_box_0(31 downto 24) xor rnd_const; - temp_0(23 downto 0) <= key_s_box_0(23 downto 0); - - -- Next key - -- 0%(KEY_WIDTH/32) == 0 - key_next(0) <= key_word(0) xor temp_0; - - GEN_TEMP_MIDDLE: if (KEY_WIDTH/32)>6 generate - -- S-box-4: applied at state level for temporal value %(KEY_WIDTH/32) == 4 && (KEY_WIDTH>6) - GEN_SBOX_4: for i in 0 to 3 generate - SBOX_4: entity work.s_box_lut port map( - data_in => key_next(3)(i*8+7 downto i*8), - data_out => key_s_box_4(i*8+7 downto i*8) - ); - end generate GEN_SBOX_4; - - temp_4 <= key_s_box_4 xor key_word(4); - key_next(4) <= temp_4; - - -- (1-(KEY_WIDTH/32-1))%(KEY_WIDTH/32) != 0 - GEN_NEXT_KEY_4: for i in 0 to (KEY_WIDTH/32-1-1) generate - GEN_4: if i /= 3 generate - key_next(i+1) <= key_word(i+1) xor key_next(i); - end generate GEN_4; - end generate GEN_NEXT_KEY_4; - - end generate GEN_TEMP_MIDDLE; - - GEN_NEXT_KEY: if (KEY_WIDTH/32)<=6 generate - GEN_KEY: for i in 0 to (KEY_WIDTH/32-1-1) generate - key_next(i+1) <= key_word(i+1) xor key_next(i); - end generate GEN_KEY; - end generate GEN_NEXT_KEY; - - -- Output - - GEN_KEY_TEMP_4: if (KEY_WIDTH/32)=4 generate - key_temp <= key_next(0) & key_next(1) & key_next(2) & key_next(3); - end generate GEN_KEY_TEMP_4; - - GEN_KEY_TEMP_6: if (KEY_WIDTH/32)=6 generate - key_temp <= key_next(0) & key_next(1) & key_next(2) & key_next(3) & key_next(4) & key_next(5); - end generate GEN_KEY_TEMP_6; - - GEN_KEY_TEMP_8: if (KEY_WIDTH/32)=8 generate - key_temp <= key_next(0) & key_next(1) & key_next(2) & key_next(3) & key_next(4) & key_next(5) & key_next(6) & key_next(7); - end generate GEN_KEY_TEMP_8; - - key_out <= key_temp; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/key_pipe_reg.vhd b/examples_hw/apps/rdma_aes/hdl/key_pipe_reg.vhd deleted file mode 100644 index 747ba417a..000000000 --- a/examples_hw/apps/rdma_aes/hdl/key_pipe_reg.vhd +++ /dev/null @@ -1,50 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity key_pipe_reg is - generic( - KEY_WIDTH : integer := 128 - ); - port( - clk : in std_logic; - reset_n : in std_logic; - dVal_in : in std_logic; -- Data valid signal - dVal_out : out std_logic; - data_in : in std_logic_vector(KEY_WIDTH-1 downto 0); - data_out : out std_logic_vector(KEY_WIDTH-1 downto 0) - ); -end entity key_pipe_reg; - -architecture RTL of key_pipe_reg is - - -- Internal registers - signal dVal_reg : std_logic; - signal data_reg : std_logic_vector(KEY_WIDTH-1 downto 0); - -begin - - -- Register process - -- - CR_REG: process (clk, reset_n) is - begin - if rising_edge(clk) then - if reset_n = '0' then - dVal_reg <= '0'; - data_reg <= (others => '0'); - else - dVal_reg <= '0'; - if dVal_in = '1' then - dVal_reg <= '1'; - data_reg <= data_in; - end if; - end if; - end if; - - end process CR_REG; - - -- Output - dVal_out <= dVal_reg; - data_out <= data_reg; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/key_pipe_stage.vhd b/examples_hw/apps/rdma_aes/hdl/key_pipe_stage.vhd deleted file mode 100644 index b71b3c2bb..000000000 --- a/examples_hw/apps/rdma_aes/hdl/key_pipe_stage.vhd +++ /dev/null @@ -1,52 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity key_pipe_stage is - generic( - KEY_WIDTH : integer := 128 - ); - port( - clk : in std_logic; - reset_n : in std_logic; - keyVal_in : in std_logic; -- Key valid signal - keyVal_out : out std_logic; - key_in : in std_logic_vector(KEY_WIDTH-1 downto 0); - key_out : out std_logic_vector(KEY_WIDTH-1 downto 0); - rnd_const : in std_logic_vector(7 downto 0) - ); -end entity key_pipe_stage; - -architecture RTL of key_pipe_stage is - - -- Internal signals - signal key_exp : std_logic_vector(KEY_WIDTH-1 downto 0); - -begin - - -- Instantiate Key expansion - GEN_KEY_EXP: entity work.key_expansion - generic map( - KEY_WIDTH => KEY_WIDTH - ) - port map( - key_in => key_in, - key_out => key_exp, - rnd_const => rnd_const - ); - - -- Instantiate Pipe register - GEN_AES_REG: entity work.key_pipe_reg - generic map( - KEY_WIDTH => KEY_WIDTH - ) - port map( - clk => clk, - reset_n => reset_n, - dVal_in => keyVal_in, - dVal_out => keyVal_out, - data_in => key_exp, - data_out => key_out - ); - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/key_pipeline.vhd b/examples_hw/apps/rdma_aes/hdl/key_pipeline.vhd deleted file mode 100644 index 03a811ee3..000000000 --- a/examples_hw/apps/rdma_aes/hdl/key_pipeline.vhd +++ /dev/null @@ -1,106 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - - -entity key_pipeline is - generic( - KEY_WIDTH : integer := 128; - KEY_ROUNDS: integer := 11 - ); - port( - clk : in std_logic; - reset_n : in std_logic; - keyVal_in : in std_logic; -- Key valid - keyVal_out : out std_logic; - key_in : in std_logic_vector(KEY_WIDTH-1 downto 0); - key_out : out std_logic_vector(KEY_ROUNDS*KEY_WIDTH-1 downto 0) - ); -end entity key_pipeline; - -architecture RTL of key_pipeline is - - -- Internal signals - type keyVal_array is array (KEY_ROUNDS-1 downto 0) of std_logic; - type key_array is array (KEY_ROUNDS-1 downto 0) of std_logic_vector((KEY_WIDTH-1) downto 0); - - signal keyVal_pipe : keyVal_array; -- Key valid signal pipeline - signal key_pipe : key_array; -- Key pipeline - - -- Internal RAM for round constants - type ram_type is array(natural range<>) of std_logic_vector(7 downto 0); - constant rcon: ram_type(0 to 9) := (X"01", X"02", X"04", X"08", X"10", X"20", X"40", X"80", X"1b", X"36"); - -begin - - -- Instantiate base key register - GEN_KEY_BASE: entity work.key_pipe_reg - generic map ( - KEY_WIDTH => KEY_WIDTH - ) - port map( - clk => clk, - reset_n => reset_n, - dVal_in => keyVal_in, - dVal_out => keyVal_pipe(0), - data_in => key_in, - data_out => key_pipe(0) - ); - - -- Instantiate key expansion pipeline - GEN_KEY_EXP: for i in 0 to KEY_ROUNDS-1-1 generate - KEY_X: entity work.key_pipe_stage - generic map( - KEY_WIDTH => KEY_WIDTH - ) - port map( - clk => clk, - reset_n => reset_n, - keyVal_in => keyVal_pipe(i), - keyVal_out => keyVal_pipe(i+1), - key_in => key_pipe(i), - key_out => key_pipe(i+1), - rnd_const => rcon(i) - ); - end generate GEN_KEY_EXP; - - -- Key valid out - keyVal_out <= keyVal_pipe(KEY_ROUNDS-1); - - -- Keys out - - - GEN_KEYS_OUT_4: if (KEY_WIDTH/32)=4 generate - GEN_4: for i in 0 to KEY_ROUNDS-1 generate - key_out(i*KEY_WIDTH+(KEY_WIDTH-1) downto i*KEY_WIDTH) <= key_pipe(i); - end generate GEN_4; - end generate GEN_KEYS_OUT_4; - - GEN_KEYS_OUT_6: if (KEY_WIDTH/32)=6 generate - key_out(127 downto 0) <= key_pipe(0)(191 downto 64); - key_out(255 downto 128) <= key_pipe(0)(63 downto 0) & key_pipe(1)(191 downto 128); - key_out(383 downto 256) <= key_pipe(1)(127 downto 0); - - key_out(511 downto 384) <= key_pipe(2)(191 downto 64); - key_out(639 downto 512) <= key_pipe(2)(63 downto 0) & key_pipe(3)(191 downto 128); - key_out(767 downto 640) <= key_pipe(3)(127 downto 0); - - key_out(895 downto 768) <= key_pipe(4)(191 downto 64); - key_out(1023 downto 896) <= key_pipe(4)(63 downto 0) & key_pipe(5)(191 downto 128); - key_out(1151 downto 1024) <= key_pipe(5)(127 downto 0); - - key_out(1279 downto 1152) <= key_pipe(6)(191 downto 64); - key_out(1407 downto 1280) <= key_pipe(6)(63 downto 0) & key_pipe(7)(191 downto 128); - key_out(1535 downto 1408) <= key_pipe(7)(127 downto 0); - - key_out(1663 downto 1536) <= key_pipe(8)(191 downto 64); - - end generate GEN_KEYS_OUT_6; - - GEN_KEYS_OUT_8: if (KEY_WIDTH/32)=8 generate - GEN_8: for i in 0 to KEY_ROUNDS-1 generate - key_out(i*KEY_WIDTH+(KEY_WIDTH-1) downto i*KEY_WIDTH) <= key_pipe(i)(127 downto 0) & key_pipe(i)(255 downto 128); - end generate GEN_8; - end generate GEN_KEYS_OUT_8; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/key_top.vhd b/examples_hw/apps/rdma_aes/hdl/key_top.vhd deleted file mode 100644 index 4796ed496..000000000 --- a/examples_hw/apps/rdma_aes/hdl/key_top.vhd +++ /dev/null @@ -1,66 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -library work; -use work.rounds.all; - -entity key_top is - generic( - NPAR : integer := 2; - KEY_WIDTH : integer := 128; - KEY_ROUNDS: integer := 11; - OPERATION : integer := 0 -- [0-encryption, 1-decryption] - ); - port( - clk : in std_logic; - reset_n : in std_logic; - stall : in std_logic; - -- Key - key_in : in std_logic_vector(KEY_WIDTH-1 downto 0); - keyVal_in : in std_logic; - keyVal_out: out std_logic; - key_out : out std_logic_vector(KEY_ROUNDS*KEY_WIDTH-1 downto 0) - ); -end entity key_top; - -architecture RTL of key_top is - - type keep_array is array (NPAR-1 downto 0) of std_logic_vector(15 downto 0); - - --variable key_rounds : integer := key_rounds_number(KEY_WIDTH); - -- Internal signals - signal key_exp : std_logic_vector(key_rounds_number(KEY_WIDTH)*KEY_WIDTH-1 downto 0); - signal key_op: std_logic_vector(key_rounds_number(KEY_WIDTH)*KEY_WIDTH-1 downto 0); - -begin - - -- Instantiate key pipeline - GEN_KEY_PIPE: entity work.key_pipeline - generic map( - KEY_WIDTH => KEY_WIDTH, - KEY_ROUNDS => key_rounds_number(KEY_WIDTH) - ) - port map( - clk => clk, - reset_n => reset_n, - keyVal_in => keyVal_in, - keyVal_out => keyVal_out, - key_in => key_in, - key_out => key_exp - ); - - -- Gen key depending on the OPERATION - GEN_KEY_ENC: if OPERATION=0 generate - key_op <= key_exp; - end generate GEN_KEY_ENC; - - GEN_KEY_DEC: if OPERATION=1 generate - GEN_KEY: for i in 0 to encrypt_rounds_number(KEY_WIDTH)+1 generate --ENC_ROUNDS - key_op(128*i+127 downto 128*i) <= key_exp((encrypt_rounds_number(KEY_WIDTH)+1-i)*128+127 downto (encrypt_rounds_number(KEY_WIDTH)+1-i)*128); - end generate GEN_KEY; - end generate GEN_KEY_DEC; - - key_out <= key_op; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/mix_columns.vhd b/examples_hw/apps/rdma_aes/hdl/mix_columns.vhd deleted file mode 100644 index f73f96c5a..000000000 --- a/examples_hw/apps/rdma_aes/hdl/mix_columns.vhd +++ /dev/null @@ -1,51 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity mix_columns is - port( - data_in : in std_logic_vector(127 downto 0); - data_out : out std_logic_vector(127 downto 0) - ); -end entity mix_columns; - -architecture RTL of mix_columns is - - -- Internal signals - type data_array is array (15 downto 0) of std_logic_vector(7 downto 0); - -- x1, x2, x3 multiplication - signal in_array, out_array, in_array_x2, in_array_x3 : data_array; - -begin - - -- Input generation - GEN_IN: for i in 15 downto 0 generate - in_array(15-i) <= data_in(i*8+7 downto i*8); - end generate GEN_IN; - - -- Multiplication - GEN_M: for i in 15 downto 0 generate - -- x2 - in_array_x2(15-i) <= - (in_array(15-i)(6 downto 0) & '0') xor "00011011" when in_array(15-i)(7) = '1' else - (in_array(15-i)(6 downto 0) & '0'); - -- x3 - in_array_x3(15-i) <= - (in_array(15-i)(6 downto 0) & '0') xor in_array(15-i) xor "00011011" when in_array(15-i)(7) = '1' else - (in_array(15-i)(6 downto 0) & '0') xor in_array(15-i); - end generate GEN_M; - - -- Mixed columns generation - GEN_MC: for i in 0 to 3 generate - out_array(4*i) <= in_array_x2(4*i) xor in_array_x3(4*i+1) xor in_array(4*i+2) xor in_array(4*i+3); - out_array(4*i+1) <= in_array(4*i) xor in_array_x2(4*i+1) xor in_array_x3(4*i+2) xor in_array(4*i+3); - out_array(4*i+2) <= in_array(4*i) xor in_array(4*i+1) xor in_array_x2(4*i+2) xor in_array_x3(4*i+3); - out_array(4*i+3) <= in_array_x3(4*i) xor in_array(4*i+1) xor in_array(4*i+2) xor in_array_x2(4*i+3); - end generate; - - -- Output generation - GEN_O: for i in 15 downto 0 generate - data_out(i*8+7 downto i*8) <= out_array(15-i); - end generate GEN_O; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/mux_host_card_rd_rdma.sv b/examples_hw/apps/rdma_aes/hdl/mux_host_card_rd_rdma.sv deleted file mode 100644 index cf5904be8..000000000 --- a/examples_hw/apps/rdma_aes/hdl/mux_host_card_rd_rdma.sv +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2021, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -`timescale 1ns / 1ps - -import lynxTypes::*; - - -module mux_host_card_rd_rdma ( - input logic aclk, - input logic aresetn, - - input logic [PID_BITS-1:0] mux_ctid, - metaIntf.s s_rq, - metaIntf.m m_sq, - - AXI4SR.s s_axis_host, - AXI4SR.s s_axis_card, - AXI4SR.m m_axis -); - -// -- FSM -typedef enum logic[0:0] {ST_IDLE, ST_MUX} state_t; -logic [0:0] state_C, state_N; - -// -- Meta -metaIntf #(.STYPE(req_t)) sq (); -queue_meta inst_queue_sq (.aclk(aclk), .aresetn(aresetn), .s_meta(sq), .m_meta(m_sq)); - -// -- Internal regs -logic host_C, host_N; -logic [BLEN_BITS-1:0] cnt_C, cnt_N; - -// -- Internal signals -logic tr_done; - -// -- AXIS -AXI4SR axis_int (); - -// ----------------------------------------------------------------------------------------------------------------------- -// Mux -// ----------------------------------------------------------------------------------------------------------------------- -always_comb begin - if(state_C == ST_MUX) begin - axis_int.tvalid = host_C ? s_axis_host.tvalid : s_axis_card.tvalid; - axis_int.tlast = host_C ? s_axis_host.tlast : s_axis_card.tlast; - axis_int.tkeep = host_C ? s_axis_host.tkeep : s_axis_card.tkeep; - axis_int.tdata = host_C ? s_axis_host.tdata : s_axis_card.tdata; - axis_int.tid = host_C ? s_axis_host.tid : s_axis_card.tid; - - s_axis_card.tready = host_C ? 1'b0 : axis_int.tready; - s_axis_host.tready = host_C ? axis_int.tready : 1'b0; - end - else begin - axis_int.tvalid = 1'b0; - axis_int.tlast = 1'b0; - axis_int.tkeep = 0; - axis_int.tdata = 0; - axis_int.tid = 0; - - s_axis_card.tready = 1'b0; - s_axis_host.tready = 1'b0; - end -end - -// ----------------------------------------------------------------------------------------------------------------------- -// State -// ----------------------------------------------------------------------------------------------------------------------- -always_ff @(posedge aclk) begin: PROC_REG -if (aresetn == 1'b0) begin - state_C <= ST_IDLE; - - cnt_C <= 'X; - host_C <= 'X; -end -else - state_C <= state_N; - - cnt_C <= cnt_N; - host_C <= host_N; -end - -// -- NSL -always_comb begin: NSL - state_N = state_C; - - case(state_C) - ST_IDLE: - state_N = s_rq.valid && sq.ready ? ST_MUX : ST_IDLE; - - ST_MUX: - state_N = tr_done ? (s_rq.valid && sq.ready ? ST_MUX : ST_IDLE) : ST_MUX; - - endcase // state_C -end - -// -- DP -always_comb begin : DP - cnt_N = cnt_C; - host_N = host_C; - - // Transfer done - tr_done = (cnt_C == 0) && (axis_int.tvalid & axis_int.tready); - - // Mux - s_rq.ready = 1'b0; - sq.valid = 1'b0; - sq.data = s_rq.data; - - case(state_C) - ST_IDLE: begin - if(s_rq.valid && sq.ready) begin - s_rq.ready = 1'b1; - sq.valid = 1'b1; - cnt_N = s_rq.data.len; - host_N = (mux_ctid == s_rq.data.pid); - end - end - - ST_MUX: begin - if(tr_done) begin - if(s_rq.valid && sq.ready) begin - s_rq.ready = 1'b1; - sq.valid = 1'b1; - cnt_N = s_rq.data.len; - host_N = (mux_ctid == s_rq.data.pid); - end - end - else begin - cnt_N = (axis_int.tvalid & axis_int.tready) ? cnt_C - 1 : cnt_C; - end - end - - endcase -end - -axisr_data_fifo_512 inst_data_fifo ( - .s_axis_aresetn(aresetn), - .s_axis_aclk(aclk), - .s_axis_tvalid(axis_int.tvalid), - .s_axis_tready(axis_int.tready), - .s_axis_tdata (axis_int.tdata), - .s_axis_tkeep (axis_int.tkeep), - .s_axis_tlast (axis_int.tlast), - .s_axis_tid (axis_int.tid), - .m_axis_tvalid(m_axis.tvalid), - .m_axis_tready(m_axis.tready), - .m_axis_tdata (m_axis.tdata), - .m_axis_tkeep (m_axis.tkeep), - .m_axis_tlast (m_axis.tlast), - .m_axis_tid (m_axis.tid) -); - -endmodule \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/mux_host_card_wr_rdma.sv b/examples_hw/apps/rdma_aes/hdl/mux_host_card_wr_rdma.sv deleted file mode 100644 index 563a94385..000000000 --- a/examples_hw/apps/rdma_aes/hdl/mux_host_card_wr_rdma.sv +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright (c) 2021, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -`timescale 1ns / 1ps - -import lynxTypes::*; - -module mux_host_card_wr_rdma ( - input logic aclk, - input logic aresetn, - - input logic [PID_BITS-1:0] mux_ctid, - metaIntf.s s_rq, - metaIntf.m m_sq, - - AXI4SR.s s_axis, - AXI4SR.m m_axis_host, - AXI4SR.m m_axis_card -); - -// -- FSM -typedef enum logic[0:0] {ST_IDLE, ST_MUX} state_t; -logic [0:0] state_C, state_N; - -// -- Meta -metaIntf #(.STYPE(req_t)) sq (); -queue_meta inst_queue_sq (.aclk(aclk), .aresetn(aresetn), .s_meta(sq), .m_meta(m_sq)); - -// -- Internal regs -logic host_C, host_N; -logic [BLEN_BITS-1:0] cnt_C, cnt_N; - -// -- Internal signals -logic tr_done; - -// -- AXIS -AXI4SR axis_int (); - -// ----------------------------------------------------------------------------------------------------------------------- -// Mux -// ----------------------------------------------------------------------------------------------------------------------- -always_comb begin - if(state_C == ST_MUX) begin - m_axis_host.tvalid = host_C ? axis_int.tvalid : 1'b0; - m_axis_host.tlast = host_C ? axis_int.tlast : 1'b0; - m_axis_host.tdata = host_C ? axis_int.tdata : 0; - m_axis_host.tkeep = host_C ? axis_int.tkeep : 0; - m_axis_host.tid = host_C ? axis_int.tid : 0; - - m_axis_card.tvalid = ~host_C ? axis_int.tvalid : 1'b0; - m_axis_card.tlast = ~host_C ? axis_int.tlast : 1'b0; - m_axis_card.tdata = ~host_C ? axis_int.tdata : 0; - m_axis_card.tkeep = ~host_C ? axis_int.tkeep : 0; - m_axis_card.tid = ~host_C ? axis_int.tid : 0; - - axis_int.tready = host_C ? m_axis_host.tready : m_axis_card.tready; - end - else begin - m_axis_host.tvalid = 1'b0; - m_axis_host.tlast = 1'b0; - m_axis_host.tdata = 0; - m_axis_host.tkeep = 0; - m_axis_host.tid = 0; - - m_axis_card.tvalid = 1'b0; - m_axis_card.tlast = 1'b0; - m_axis_card.tdata = 0; - m_axis_card.tkeep = 0; - m_axis_card.tid = 0; - - axis_int.tready = 1'b0; - end -end - -// ----------------------------------------------------------------------------------------------------------------------- -// State -// ----------------------------------------------------------------------------------------------------------------------- -always_ff @(posedge aclk) begin: PROC_REG -if (aresetn == 1'b0) begin - state_C <= ST_IDLE; - - cnt_C <= 'X; - host_C <= 'X; -end -else - state_C <= state_N; - - cnt_C <= cnt_N; - host_C <= host_N; -end - -// -- NSL -always_comb begin: NSL - state_N = state_C; - - case(state_C) - ST_IDLE: - state_N = s_rq.valid && sq.ready ? ST_MUX : ST_IDLE; - - ST_MUX: - state_N = tr_done ? (s_rq.valid && sq.ready ? ST_MUX : ST_IDLE) : ST_MUX; - - endcase // state_C -end - -// -- DP -always_comb begin : DP - cnt_N = cnt_C; - host_N = host_C; - - // Transfer done - tr_done = (cnt_C == 0) && (axis_int.tvalid & axis_int.tready); - - // Mux - s_rq.ready = 1'b0; - sq.valid = 1'b0; - sq.data = s_rq.data; - - case(state_C) - ST_IDLE: begin - if(s_rq.valid && sq.ready) begin - s_rq.ready = 1'b1; - sq.valid = 1'b1; - cnt_N = s_rq.data.len; - host_N = (mux_ctid == s_rq.data.pid); - end - end - - ST_MUX: begin - if(tr_done) begin - if(s_rq.valid && sq.ready) begin - s_rq.ready = 1'b1; - sq.valid = 1'b1; - cnt_N = s_rq.data.len; - host_N = (mux_ctid == s_rq.data.pid); - end - end - else begin - cnt_N = (axis_int.tvalid & axis_int.tready) ? cnt_C - 1 : cnt_C; - end - end - - endcase -end - -axisr_data_fifo_512 inst_data_fifo ( - .s_axis_aresetn(aresetn), - .s_axis_aclk(aclk), - .s_axis_tvalid(s_axis.tvalid), - .s_axis_tready(s_axis.tready), - .s_axis_tdata (s_axis.tdata), - .s_axis_tkeep (s_axis.tkeep), - .s_axis_tlast (s_axis.tlast), - .s_axis_tid (s_axis.tid), - .m_axis_tvalid(axis_int.tvalid), - .m_axis_tready(axis_int.tready), - .m_axis_tdata (axis_int.tdata), - .m_axis_tkeep (axis_int.tkeep), - .m_axis_tlast (axis_int.tlast), - .m_axis_tid (axis_int.tid) -); - -endmodule \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/pipe_reg.vhd b/examples_hw/apps/rdma_aes/hdl/pipe_reg.vhd deleted file mode 100644 index bd1badd02..000000000 --- a/examples_hw/apps/rdma_aes/hdl/pipe_reg.vhd +++ /dev/null @@ -1,66 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity pipe_reg is - generic( - DATA_WIDTH : integer := 128 - ); - port( - clk : in std_logic; - reset_n : in std_logic; - stall : in std_logic; - last_in : in std_logic; - last_out : out std_logic; - keep_in : in std_logic_vector(DATA_WIDTH/8-1 downto 0); - keep_out : out std_logic_vector(DATA_WIDTH/8-1 downto 0); - dVal_in : in std_logic; -- Data valid signal - dVal_out : out std_logic; - data_in : in std_logic_vector(DATA_WIDTH-1 downto 0); - data_out : out std_logic_vector(DATA_WIDTH-1 downto 0) - ); -end entity pipe_reg; - -architecture RTL of pipe_reg is - - -- Internal registers - signal dVal_reg : std_logic; - signal last_reg : std_logic; - signal keep_reg : std_logic_vector(DATA_WIDTH/8-1 downto 0); - signal data_reg : std_logic_vector(DATA_WIDTH-1 downto 0); - -begin - - -- Register process - -- - CR_REG: process (clk, reset_n) is - begin - if rising_edge(clk) then - if reset_n = '0' then - dVal_reg <= '0'; - last_reg <= '0'; - keep_reg <= (others => '0'); - data_reg <= (others => '0'); - else --if rising_edge(clk) then - if stall = '0' then - dVal_reg <= '0'; - if dVal_in = '1' then - dVal_reg <= '1'; - last_reg <= last_in; - keep_reg <= keep_in; - data_reg <= data_in; - end if; - end if; - - end if; - end if; - - end process CR_REG; - - -- Output - dVal_out <= dVal_reg; - last_out <= last_reg; - keep_out <= keep_reg; - data_out <= data_reg; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/rdma_aes_slv.sv b/examples_hw/apps/rdma_aes/hdl/rdma_aes_slv.sv deleted file mode 100644 index f7fbb2412..000000000 --- a/examples_hw/apps/rdma_aes/hdl/rdma_aes_slv.sv +++ /dev/null @@ -1,245 +0,0 @@ -import lynxTypes::*; - -module rdma_aes_slv ( - input logic aclk, - input logic aresetn, - - AXI4L.s axi_ctrl, - - output logic [PID_BITS-1:0] mux_ctid, - - output logic [127:0] key_out, - output logic keyStart -); - -//`define DEBUG_CNFG_SLAVE - -// -- Decl ---------------------------------------------------------- -// ------------------------------------------------------------------ - -// Constants -localparam integer N_REGS = 3; - -localparam integer ADDR_LSB = $clog2(AXIL_DATA_BITS/8); -localparam integer ADDR_MSB = $clog2(N_REGS); -localparam integer AXI_ADDR_BITS = ADDR_LSB + ADDR_MSB; - -// Internal registers -logic [AXI_ADDR_BITS-1:0] axi_awaddr; -logic axi_awready; -logic [AXI_ADDR_BITS-1:0] axi_araddr; -logic axi_arready; -logic [1:0] axi_bresp; -logic axi_bvalid; -logic axi_wready; -logic [AXIL_DATA_BITS-1:0] axi_rdata; -logic [1:0] axi_rresp; -logic axi_rvalid; - -// Registers -logic [N_REGS-1:0][AXIL_DATA_BITS-1:0] slv_reg; -logic slv_reg_rden; -logic slv_reg_wren; -logic aw_en; - -// -- Def ----------------------------------------------------------- -// ------------------------------------------------------------------ - -// -- Register map ----------------------------------------------------------------------- -localparam integer CTID_REG = 0; -// 0 (WR) : Ctid -localparam integer KEY_LOW_REG = 1; -// 1 (WR) : Key low -localparam integer KEY_HIGH_REG = 2; -// 2 (WR) : Key high - -// Write process -assign slv_reg_wren = axi_wready && axi_ctrl.wvalid && axi_awready && axi_ctrl.awvalid; - -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) begin - slv_reg <= 0; - - keyStart <= 1'b0; - end - else begin - keyStart <= 1'b0; - - if(slv_reg_wren) begin - case (axi_awaddr[ADDR_LSB+:ADDR_MSB]) - CTID_REG: // Ctid select - for (int i = 0; i < (AXIL_DATA_BITS/8); i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[CTID_REG][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - KEY_LOW_REG: // Key low - for (int i = 0; i < (AXIL_DATA_BITS/8); i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[KEY_LOW_REG][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - KEY_HIGH_REG: // Key high - for (int i = 0; i < (AXIL_DATA_BITS/8); i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[KEY_HIGH_REG][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - keyStart <= 1'b1; - end - end - default : ; - endcase - end - end -end - -assign mux_ctid = slv_reg[CTID_REG][PID_BITS-1:0]; -assign key_out[63:0] = slv_reg[KEY_LOW_REG]; -assign key_out[127:64] = slv_reg[KEY_HIGH_REG]; - -// Read process -assign slv_reg_rden = axi_arready & axi_ctrl.arvalid & ~axi_rvalid; - -always_ff @(posedge aclk) begin - if( aresetn == 1'b0 ) begin - axi_rdata <= 0; - end - else begin - if(slv_reg_rden) begin - axi_rdata <= 0; - case (axi_araddr[ADDR_LSB+:ADDR_MSB]) - CTID_REG: // Ctid - axi_rdata <= slv_reg[CTID_REG]; - KEY_LOW_REG: // Key low - axi_rdata <= slv_reg[KEY_LOW_REG]; - KEY_HIGH_REG: // Key high - axi_rdata <= slv_reg[KEY_HIGH_REG]; - default: ; - endcase - end - end -end - -// I/O -assign axi_ctrl.awready = axi_awready; -assign axi_ctrl.arready = axi_arready; -assign axi_ctrl.bresp = axi_bresp; -assign axi_ctrl.bvalid = axi_bvalid; -assign axi_ctrl.wready = axi_wready; -assign axi_ctrl.rdata = axi_rdata; -assign axi_ctrl.rresp = axi_rresp; -assign axi_ctrl.rvalid = axi_rvalid; - -// awready and awaddr -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_awready <= 1'b0; - axi_awaddr <= 0; - aw_en <= 1'b1; - end - else - begin - if (~axi_awready && axi_ctrl.awvalid && axi_ctrl.wvalid && aw_en) - begin - axi_awready <= 1'b1; - aw_en <= 1'b0; - axi_awaddr <= axi_ctrl.awaddr; - end - else if (axi_ctrl.bready && axi_bvalid) - begin - aw_en <= 1'b1; - axi_awready <= 1'b0; - end - else - begin - axi_awready <= 1'b0; - end - end -end - -// arready and araddr -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_arready <= 1'b0; - axi_araddr <= 0; - end - else - begin - if (~axi_arready && axi_ctrl.arvalid) - begin - axi_arready <= 1'b1; - axi_araddr <= axi_ctrl.araddr; - end - else - begin - axi_arready <= 1'b0; - end - end -end - -// bvalid and bresp -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_bvalid <= 0; - axi_bresp <= 2'b0; - end - else - begin - if (axi_awready && axi_ctrl.awvalid && ~axi_bvalid && axi_wready && axi_ctrl.wvalid) - begin - axi_bvalid <= 1'b1; - axi_bresp <= 2'b0; - end - else - begin - if (axi_ctrl.bready && axi_bvalid) - begin - axi_bvalid <= 1'b0; - end - end - end -end - -// wready -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_wready <= 1'b0; - end - else - begin - if (~axi_wready && axi_ctrl.wvalid && axi_ctrl.awvalid && aw_en ) - begin - axi_wready <= 1'b1; - end - else - begin - axi_wready <= 1'b0; - end - end -end - -// rvalid and rresp (1Del?) -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_rvalid <= 0; - axi_rresp <= 0; - end - else - begin - if (axi_arready && axi_ctrl.arvalid && ~axi_rvalid) - begin - axi_rvalid <= 1'b1; - axi_rresp <= 2'b0; - end - else if (axi_rvalid && axi_ctrl.rready) - begin - axi_rvalid <= 1'b0; - end - end -end - -endmodule // cnfg_slave \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/s_box_lut.vhd b/examples_hw/apps/rdma_aes/hdl/s_box_lut.vhd deleted file mode 100644 index b556e9064..000000000 --- a/examples_hw/apps/rdma_aes/hdl/s_box_lut.vhd +++ /dev/null @@ -1,40 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity s_box_lut is - port( - data_in : in std_logic_vector(7 downto 0); - data_out : out std_logic_vector(7 downto 0) - ); -end entity s_box_lut; - -architecture RTL of s_box_lut is - - -- Internal RAM - type ram_type is array(natural range<>) of std_logic_vector(7 downto 0); - constant sbox_ram : ram_type(255 downto 0) := ( - X"16", X"bb", X"54", X"b0", X"0f", X"2d", X"99", X"41", X"68", X"42", X"e6", X"bf", X"0d", X"89", X"a1", X"8c", - X"df", X"28", X"55", X"ce", X"e9", X"87", X"1e", X"9b", X"94", X"8e", X"d9", X"69", X"11", X"98", X"f8", X"e1", - X"9e", X"1d", X"c1", X"86", X"b9", X"57", X"35", X"61", X"0e", X"f6", X"03", X"48", X"66", X"b5", X"3e", X"70", - X"8a", X"8b", X"bd", X"4b", X"1f", X"74", X"dd", X"e8", X"c6", X"b4", X"a6", X"1c", X"2e", X"25", X"78", X"ba", - X"08", X"ae", X"7a", X"65", X"ea", X"f4", X"56", X"6c", X"a9", X"4e", X"d5", X"8d", X"6d", X"37", X"c8", X"e7", - X"79", X"e4", X"95", X"91", X"62", X"ac", X"d3", X"c2", X"5c", X"24", X"06", X"49", X"0a", X"3a", X"32", X"e0", - X"db", X"0b", X"5e", X"de", X"14", X"b8", X"ee", X"46", X"88", X"90", X"2a", X"22", X"dc", X"4f", X"81", X"60", - X"73", X"19", X"5d", X"64", X"3d", X"7e", X"a7", X"c4", X"17", X"44", X"97", X"5f", X"ec", X"13", X"0c", X"cd", - X"d2", X"f3", X"ff", X"10", X"21", X"da", X"b6", X"bc", X"f5", X"38", X"9d", X"92", X"8f", X"40", X"a3", X"51", - X"a8", X"9f", X"3c", X"50", X"7f", X"02", X"f9", X"45", X"85", X"33", X"4d", X"43", X"fb", X"aa", X"ef", X"d0", - X"cf", X"58", X"4c", X"4a", X"39", X"be", X"cb", X"6a", X"5b", X"b1", X"fc", X"20", X"ed", X"00", X"d1", X"53", - X"84", X"2f", X"e3", X"29", X"b3", X"d6", X"3b", X"52", X"a0", X"5a", X"6e", X"1b", X"1a", X"2c", X"83", X"09", - X"75", X"b2", X"27", X"eb", X"e2", X"80", X"12", X"07", X"9a", X"05", X"96", X"18", X"c3", X"23", X"c7", X"04", - X"15", X"31", X"d8", X"71", X"f1", X"e5", X"a5", X"34", X"cc", X"f7", X"3f", X"36", X"26", X"93", X"fd", X"b7", - X"c0", X"72", X"a4", X"9c", X"af", X"a2", X"d4", X"ad", X"f0", X"47", X"59", X"fa", X"7d", X"c9", X"82", X"ca", - X"76", X"ab", X"d7", X"fe", X"2b", X"67", X"01", X"30", X"c5", X"6f", X"6b", X"f2", X"7b", X"77", X"7c", X"63" - ); - -begin - - -- Output - data_out <= sbox_ram(to_integer(unsigned(data_in))); - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/shift_rows.vhd b/examples_hw/apps/rdma_aes/hdl/shift_rows.vhd deleted file mode 100644 index 4f3d227bc..000000000 --- a/examples_hw/apps/rdma_aes/hdl/shift_rows.vhd +++ /dev/null @@ -1,54 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - -entity shift_rows is - port( - data_in : in std_logic_vector(127 downto 0); - data_out : out std_logic_vector(127 downto 0) - ); -end entity shift_rows; - -architecture RTL of shift_rows is - - -- Internal signals - type data_array is array (15 downto 0) of std_logic_vector(7 downto 0); - signal in_array, out_array : data_array; - -begin - - -- Input generation - GEN_IN: for i in 15 downto 0 generate - in_array(15-i) <= data_in(i*8+7 downto i*8); - end generate GEN_IN; - - --First mixed column input - out_array(0) <= in_array(0); - out_array(1) <= in_array(5); - out_array(2) <= in_array(10); - out_array(3) <= in_array(15); - - -- Second mixed column input - out_array(4) <= in_array(4); - out_array(5) <= in_array(9); - out_array(6) <= in_array(14); - out_array(7) <= in_array(3); - - -- Third mixed column input - out_array(8) <= in_array(8); - out_array(9) <= in_array(13); - out_array(10) <= in_array(2); - out_array(11) <= in_array(7); - - -- Fourth mixed column input - out_array(12) <= in_array(12); - out_array(13) <= in_array(1); - out_array(14) <= in_array(6); - out_array(15) <= in_array(11); - - -- Output generation - GEN_OUT: for i in 15 downto 0 generate - data_out(i*8+7 downto i*8) <= out_array(15-i); - end generate GEN_OUT; - -end architecture RTL; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/hdl/utils.vhd b/examples_hw/apps/rdma_aes/hdl/utils.vhd deleted file mode 100644 index 8ed12ab55..000000000 --- a/examples_hw/apps/rdma_aes/hdl/utils.vhd +++ /dev/null @@ -1,58 +0,0 @@ -library ieee; -use ieee.std_logic_1164.all; -use ieee.numeric_std.all; - ---package utils is --- function bitlength(number : integer) return positive; --- ---end package utils; - ---package body utils is --- --- -- purpose: returns the minimum # of bits needed to represent the input number --- function bitlength(number : integer) return positive is --- variable acc : positive := 1; --- variable i : natural := 0; --- begin --- if number = 0 or number = -1 then --- return 1; --- else --- while True loop --- if acc > number then --- return i; --- end if; --- --- acc := acc * 2; --- i := i + 1; --- end loop; --- end if; --- end function bitlength; --- ---end package body utils; - -package rounds is - function key_rounds_number (key_width:integer) return integer; - function encrypt_rounds_number (key_width:integer) return integer; -end package rounds; - -package body rounds is - function key_rounds_number (key_width:integer) return integer is - begin - case key_width is - when 128 => return 11; -- 128*11/128 = 11 - when 192 => return 9; -- 128*13/192 = 9 - when 256 => return 8; -- 128*15/256 = 8 - when others => return 11; - end case; - end function; - - function encrypt_rounds_number (key_width:integer) return integer is - begin - case key_width is - when 128 => return 9; -- 10(total rounds)-1(final round) = 9 - when 192 => return 11; -- 12(total rounds)-1(final round) = 11 - when 256 => return 13; -- 14(total rounds)-1(final round) = 13 - when others => return 9; - end case; - end function; -end package body rounds; \ No newline at end of file diff --git a/examples_hw/apps/rdma_aes/vfpga_top.svh b/examples_hw/apps/rdma_aes/vfpga_top.svh deleted file mode 100644 index 1a6d574ed..000000000 --- a/examples_hw/apps/rdma_aes/vfpga_top.svh +++ /dev/null @@ -1,158 +0,0 @@ -/** - * VFPGA TOP - * - * Tie up all signals to the user kernels - * Still to this day, interfaces are not supported by Vivado packager ... - * This means verilog style port connections are needed. - * - */ - -// Consts -localparam integer N_AES_PIPELINES = 4; -localparam integer KEY_ROUNDS = 11; - -// CSR -logic [PID_BITS-1:0] mux_ctid; // go to card with this ctid -logic [128*KEY_ROUNDS-1:0] key_dec; -logic [128*KEY_ROUNDS-1:0] key_enc; -logic [128-1:0] key_slv; -logic key_start; - -rdma_aes_slv inst_rdma_aes_slv ( - .aclk(aclk), - .aresetn(aresetn), - - .axi_ctrl(axi_ctrl), - - .key_out(key_slv), - .keyStart(key_start), - - .mux_ctid(mux_ctid) -); - -AXI4SR axis_s0_send (); -AXI4SR axis_s0_recv (); - -mux_host_card_rd_rdma inst_mux_send ( - .aclk(aclk), - .aresetn(aresetn), - - .mux_ctid(mux_ctid), - .s_rq(rq_rd), - .m_sq(sq_rd), - - .s_axis_host(axis_host_recv[0]), - .s_axis_card(axis_card_recv[0]), - .m_axis(axis_s0_send) -); - -mux_host_card_wr_rdma inst_mux_recv ( - .aclk(aclk), - .aresetn(aresetn), - - .mux_ctid(mux_ctid), - .s_rq(rq_wr), - .m_sq(sq_wr), - - .s_axis(axis_s0_recv), - .m_axis_host(axis_host_send[0]), - .m_axis_card(axis_card_send[0]) -); - -// -// AES encryption and decryption -// - -// Key decryption -key_top #( - .OPERATION(1) -) inst_key_top_dec ( - .clk(aclk), - .reset_n(aresetn), - - .stall(1'b0), - - .key_in(key_slv), - .keyVal_in(keyStart), - .keyVal_out(), - .key_out(key_dec) -); - -// AES pipeline - receive (decrypt) -aes_top #( - .NPAR(N_AES_PIPELINES), - .OPERATION(1), // 0 - enc, 1 - dec - .MODE(0) // 0 - ECB, 1 - CTR, 2 - CBC -) inst_aes_top_dec ( - .clk(aclk), - .reset_n(aresetn), - .stall(~axis_s0_recv.tready), - // Key - .key_in(key_dec), - // - .last_in(axis_rdma_recv[0].tlast), - .last_out(axis_s0_recv.tlast), - .keep_in(axis_rdma_recv[0].tkeep), - .keep_out(axis_s0_recv.tkeep), - // Data - .dVal_in(axis_rdma_recv[0].tvalid), - .dVal_out(axis_s0_recv.tvalid), - .data_in(axis_rdma_recv[0].tdata), - .data_out(axis_s0_recv.tdata), - // Counter mode - .cntr_in(0) -); - -assign axis_rdma_recv[0].tready = axis_s0_recv.tready; -assign axis_s0_recv.tid = 0; - -// Key encryption -key_top #( - .OPERATION(1) -) inst_key_top_enc ( - .clk(aclk), - .reset_n(aresetn), - - .stall(1'b0), - - .key_in(key_slv), - .keyVal_in(keyStart), - .keyVal_out(), - .key_out(key_enc) -); - -// AES pipeline - send (encrypt) -aes_top #( - .NPAR(N_AES_PIPELINES), - .OPERATION(0), // 0 - enc, 1 - dec - .MODE(0) // 0 - ECB, 1 - CTR, 2 - CBC -) inst_aes_top_enc ( - .clk(aclk), - .reset_n(aresetn), - .stall(~axis_rdma_send[0].tready), - // Key - .key_in(key), - // - .last_in(axis_s0_send.tlast), - .last_out(axis_rdma_send[0].tlast), - .keep_in(axis_s0_send.tkeep), - .keep_out(axis_rdma_send[0].tkeep), - // Data - .dVal_in(axis_s0_send.tvalid), - .dVal_out(axis_rdma_send[0].tvalid), - .data_in(axis_s0_send.tdata), - .data_out(axis_rdma_send[0].tdata), - // Counter mode - .cntr_in(0) -); - -assign axis_s0_send.tready = axis_rdma_send[0].tready; -assign axis_rdma_send[0].tid = 0; - -// -// Tie-off unused -// - -always_comb notify.tie_off_m(); -always_comb cq_wr.tie_off_s(); -always_comb cq_rd.tie_off_s(); \ No newline at end of file diff --git a/examples_hw/apps/rdma_base/hdl/mux_host_card_rd_rdma.sv b/examples_hw/apps/rdma_base/hdl/mux_host_card_rd_rdma.sv deleted file mode 100644 index cf5904be8..000000000 --- a/examples_hw/apps/rdma_base/hdl/mux_host_card_rd_rdma.sv +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2021, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -`timescale 1ns / 1ps - -import lynxTypes::*; - - -module mux_host_card_rd_rdma ( - input logic aclk, - input logic aresetn, - - input logic [PID_BITS-1:0] mux_ctid, - metaIntf.s s_rq, - metaIntf.m m_sq, - - AXI4SR.s s_axis_host, - AXI4SR.s s_axis_card, - AXI4SR.m m_axis -); - -// -- FSM -typedef enum logic[0:0] {ST_IDLE, ST_MUX} state_t; -logic [0:0] state_C, state_N; - -// -- Meta -metaIntf #(.STYPE(req_t)) sq (); -queue_meta inst_queue_sq (.aclk(aclk), .aresetn(aresetn), .s_meta(sq), .m_meta(m_sq)); - -// -- Internal regs -logic host_C, host_N; -logic [BLEN_BITS-1:0] cnt_C, cnt_N; - -// -- Internal signals -logic tr_done; - -// -- AXIS -AXI4SR axis_int (); - -// ----------------------------------------------------------------------------------------------------------------------- -// Mux -// ----------------------------------------------------------------------------------------------------------------------- -always_comb begin - if(state_C == ST_MUX) begin - axis_int.tvalid = host_C ? s_axis_host.tvalid : s_axis_card.tvalid; - axis_int.tlast = host_C ? s_axis_host.tlast : s_axis_card.tlast; - axis_int.tkeep = host_C ? s_axis_host.tkeep : s_axis_card.tkeep; - axis_int.tdata = host_C ? s_axis_host.tdata : s_axis_card.tdata; - axis_int.tid = host_C ? s_axis_host.tid : s_axis_card.tid; - - s_axis_card.tready = host_C ? 1'b0 : axis_int.tready; - s_axis_host.tready = host_C ? axis_int.tready : 1'b0; - end - else begin - axis_int.tvalid = 1'b0; - axis_int.tlast = 1'b0; - axis_int.tkeep = 0; - axis_int.tdata = 0; - axis_int.tid = 0; - - s_axis_card.tready = 1'b0; - s_axis_host.tready = 1'b0; - end -end - -// ----------------------------------------------------------------------------------------------------------------------- -// State -// ----------------------------------------------------------------------------------------------------------------------- -always_ff @(posedge aclk) begin: PROC_REG -if (aresetn == 1'b0) begin - state_C <= ST_IDLE; - - cnt_C <= 'X; - host_C <= 'X; -end -else - state_C <= state_N; - - cnt_C <= cnt_N; - host_C <= host_N; -end - -// -- NSL -always_comb begin: NSL - state_N = state_C; - - case(state_C) - ST_IDLE: - state_N = s_rq.valid && sq.ready ? ST_MUX : ST_IDLE; - - ST_MUX: - state_N = tr_done ? (s_rq.valid && sq.ready ? ST_MUX : ST_IDLE) : ST_MUX; - - endcase // state_C -end - -// -- DP -always_comb begin : DP - cnt_N = cnt_C; - host_N = host_C; - - // Transfer done - tr_done = (cnt_C == 0) && (axis_int.tvalid & axis_int.tready); - - // Mux - s_rq.ready = 1'b0; - sq.valid = 1'b0; - sq.data = s_rq.data; - - case(state_C) - ST_IDLE: begin - if(s_rq.valid && sq.ready) begin - s_rq.ready = 1'b1; - sq.valid = 1'b1; - cnt_N = s_rq.data.len; - host_N = (mux_ctid == s_rq.data.pid); - end - end - - ST_MUX: begin - if(tr_done) begin - if(s_rq.valid && sq.ready) begin - s_rq.ready = 1'b1; - sq.valid = 1'b1; - cnt_N = s_rq.data.len; - host_N = (mux_ctid == s_rq.data.pid); - end - end - else begin - cnt_N = (axis_int.tvalid & axis_int.tready) ? cnt_C - 1 : cnt_C; - end - end - - endcase -end - -axisr_data_fifo_512 inst_data_fifo ( - .s_axis_aresetn(aresetn), - .s_axis_aclk(aclk), - .s_axis_tvalid(axis_int.tvalid), - .s_axis_tready(axis_int.tready), - .s_axis_tdata (axis_int.tdata), - .s_axis_tkeep (axis_int.tkeep), - .s_axis_tlast (axis_int.tlast), - .s_axis_tid (axis_int.tid), - .m_axis_tvalid(m_axis.tvalid), - .m_axis_tready(m_axis.tready), - .m_axis_tdata (m_axis.tdata), - .m_axis_tkeep (m_axis.tkeep), - .m_axis_tlast (m_axis.tlast), - .m_axis_tid (m_axis.tid) -); - -endmodule \ No newline at end of file diff --git a/examples_hw/apps/rdma_base/hdl/mux_host_card_wr_rdma.sv b/examples_hw/apps/rdma_base/hdl/mux_host_card_wr_rdma.sv deleted file mode 100644 index 563a94385..000000000 --- a/examples_hw/apps/rdma_base/hdl/mux_host_card_wr_rdma.sv +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright (c) 2021, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -`timescale 1ns / 1ps - -import lynxTypes::*; - -module mux_host_card_wr_rdma ( - input logic aclk, - input logic aresetn, - - input logic [PID_BITS-1:0] mux_ctid, - metaIntf.s s_rq, - metaIntf.m m_sq, - - AXI4SR.s s_axis, - AXI4SR.m m_axis_host, - AXI4SR.m m_axis_card -); - -// -- FSM -typedef enum logic[0:0] {ST_IDLE, ST_MUX} state_t; -logic [0:0] state_C, state_N; - -// -- Meta -metaIntf #(.STYPE(req_t)) sq (); -queue_meta inst_queue_sq (.aclk(aclk), .aresetn(aresetn), .s_meta(sq), .m_meta(m_sq)); - -// -- Internal regs -logic host_C, host_N; -logic [BLEN_BITS-1:0] cnt_C, cnt_N; - -// -- Internal signals -logic tr_done; - -// -- AXIS -AXI4SR axis_int (); - -// ----------------------------------------------------------------------------------------------------------------------- -// Mux -// ----------------------------------------------------------------------------------------------------------------------- -always_comb begin - if(state_C == ST_MUX) begin - m_axis_host.tvalid = host_C ? axis_int.tvalid : 1'b0; - m_axis_host.tlast = host_C ? axis_int.tlast : 1'b0; - m_axis_host.tdata = host_C ? axis_int.tdata : 0; - m_axis_host.tkeep = host_C ? axis_int.tkeep : 0; - m_axis_host.tid = host_C ? axis_int.tid : 0; - - m_axis_card.tvalid = ~host_C ? axis_int.tvalid : 1'b0; - m_axis_card.tlast = ~host_C ? axis_int.tlast : 1'b0; - m_axis_card.tdata = ~host_C ? axis_int.tdata : 0; - m_axis_card.tkeep = ~host_C ? axis_int.tkeep : 0; - m_axis_card.tid = ~host_C ? axis_int.tid : 0; - - axis_int.tready = host_C ? m_axis_host.tready : m_axis_card.tready; - end - else begin - m_axis_host.tvalid = 1'b0; - m_axis_host.tlast = 1'b0; - m_axis_host.tdata = 0; - m_axis_host.tkeep = 0; - m_axis_host.tid = 0; - - m_axis_card.tvalid = 1'b0; - m_axis_card.tlast = 1'b0; - m_axis_card.tdata = 0; - m_axis_card.tkeep = 0; - m_axis_card.tid = 0; - - axis_int.tready = 1'b0; - end -end - -// ----------------------------------------------------------------------------------------------------------------------- -// State -// ----------------------------------------------------------------------------------------------------------------------- -always_ff @(posedge aclk) begin: PROC_REG -if (aresetn == 1'b0) begin - state_C <= ST_IDLE; - - cnt_C <= 'X; - host_C <= 'X; -end -else - state_C <= state_N; - - cnt_C <= cnt_N; - host_C <= host_N; -end - -// -- NSL -always_comb begin: NSL - state_N = state_C; - - case(state_C) - ST_IDLE: - state_N = s_rq.valid && sq.ready ? ST_MUX : ST_IDLE; - - ST_MUX: - state_N = tr_done ? (s_rq.valid && sq.ready ? ST_MUX : ST_IDLE) : ST_MUX; - - endcase // state_C -end - -// -- DP -always_comb begin : DP - cnt_N = cnt_C; - host_N = host_C; - - // Transfer done - tr_done = (cnt_C == 0) && (axis_int.tvalid & axis_int.tready); - - // Mux - s_rq.ready = 1'b0; - sq.valid = 1'b0; - sq.data = s_rq.data; - - case(state_C) - ST_IDLE: begin - if(s_rq.valid && sq.ready) begin - s_rq.ready = 1'b1; - sq.valid = 1'b1; - cnt_N = s_rq.data.len; - host_N = (mux_ctid == s_rq.data.pid); - end - end - - ST_MUX: begin - if(tr_done) begin - if(s_rq.valid && sq.ready) begin - s_rq.ready = 1'b1; - sq.valid = 1'b1; - cnt_N = s_rq.data.len; - host_N = (mux_ctid == s_rq.data.pid); - end - end - else begin - cnt_N = (axis_int.tvalid & axis_int.tready) ? cnt_C - 1 : cnt_C; - end - end - - endcase -end - -axisr_data_fifo_512 inst_data_fifo ( - .s_axis_aresetn(aresetn), - .s_axis_aclk(aclk), - .s_axis_tvalid(s_axis.tvalid), - .s_axis_tready(s_axis.tready), - .s_axis_tdata (s_axis.tdata), - .s_axis_tkeep (s_axis.tkeep), - .s_axis_tlast (s_axis.tlast), - .s_axis_tid (s_axis.tid), - .m_axis_tvalid(axis_int.tvalid), - .m_axis_tready(axis_int.tready), - .m_axis_tdata (axis_int.tdata), - .m_axis_tkeep (axis_int.tkeep), - .m_axis_tlast (axis_int.tlast), - .m_axis_tid (axis_int.tid) -); - -endmodule \ No newline at end of file diff --git a/examples_hw/apps/rdma_base/hdl/rdma_base_slv.sv b/examples_hw/apps/rdma_base/hdl/rdma_base_slv.sv deleted file mode 100644 index a1de99995..000000000 --- a/examples_hw/apps/rdma_base/hdl/rdma_base_slv.sv +++ /dev/null @@ -1,214 +0,0 @@ -import lynxTypes::*; - -module rdma_base_slv ( - input logic aclk, - input logic aresetn, - - AXI4L.s axi_ctrl, - - output logic [PID_BITS-1:0] mux_ctid -); - -//`define DEBUG_CNFG_SLAVE - -// -- Decl ---------------------------------------------------------- -// ------------------------------------------------------------------ - -// Constants -localparam integer N_REGS = 2; - -localparam integer ADDR_LSB = $clog2(AXIL_DATA_BITS/8); -localparam integer ADDR_MSB = $clog2(N_REGS); -localparam integer AXI_ADDR_BITS = ADDR_LSB + ADDR_MSB; - -// Internal registers -logic [AXI_ADDR_BITS-1:0] axi_awaddr; -logic axi_awready; -logic [AXI_ADDR_BITS-1:0] axi_araddr; -logic axi_arready; -logic [1:0] axi_bresp; -logic axi_bvalid; -logic axi_wready; -logic [AXIL_DATA_BITS-1:0] axi_rdata; -logic [1:0] axi_rresp; -logic axi_rvalid; - -// Registers -logic [N_REGS-1:0][AXIL_DATA_BITS-1:0] slv_reg; -logic slv_reg_rden; -logic slv_reg_wren; -logic aw_en; - -// -- Def ----------------------------------------------------------- -// ------------------------------------------------------------------ - -// -- Register map ----------------------------------------------------------------------- -localparam integer CTID_REG = 0; - -// Write process -assign slv_reg_wren = axi_wready && axi_ctrl.wvalid && axi_awready && axi_ctrl.awvalid; - -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) begin - slv_reg <= 0; - end - else begin - if(slv_reg_wren) begin - case (axi_awaddr[ADDR_LSB+:ADDR_MSB]) - CTID_REG: // Ctid select - for (int i = 0; i < (AXIL_DATA_BITS/8); i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[CTID_REG][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - default : ; - endcase - end - end -end - -assign mux_ctid = slv_reg[CTID_REG][PID_BITS-1:0]; - -// Read process -assign slv_reg_rden = axi_arready & axi_ctrl.arvalid & ~axi_rvalid; - -always_ff @(posedge aclk) begin - if( aresetn == 1'b0 ) begin - axi_rdata <= 0; - end - else begin - if(slv_reg_rden) begin - axi_rdata <= 0; - case (axi_araddr[ADDR_LSB+:ADDR_MSB]) - CTID_REG: // Ctid - axi_rdata <= slv_reg[CTID_REG]; - default: ; - endcase - end - end -end - -// I/O -assign axi_ctrl.awready = axi_awready; -assign axi_ctrl.arready = axi_arready; -assign axi_ctrl.bresp = axi_bresp; -assign axi_ctrl.bvalid = axi_bvalid; -assign axi_ctrl.wready = axi_wready; -assign axi_ctrl.rdata = axi_rdata; -assign axi_ctrl.rresp = axi_rresp; -assign axi_ctrl.rvalid = axi_rvalid; - -// awready and awaddr -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_awready <= 1'b0; - axi_awaddr <= 0; - aw_en <= 1'b1; - end - else - begin - if (~axi_awready && axi_ctrl.awvalid && axi_ctrl.wvalid && aw_en) - begin - axi_awready <= 1'b1; - aw_en <= 1'b0; - axi_awaddr <= axi_ctrl.awaddr; - end - else if (axi_ctrl.bready && axi_bvalid) - begin - aw_en <= 1'b1; - axi_awready <= 1'b0; - end - else - begin - axi_awready <= 1'b0; - end - end -end - -// arready and araddr -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_arready <= 1'b0; - axi_araddr <= 0; - end - else - begin - if (~axi_arready && axi_ctrl.arvalid) - begin - axi_arready <= 1'b1; - axi_araddr <= axi_ctrl.araddr; - end - else - begin - axi_arready <= 1'b0; - end - end -end - -// bvalid and bresp -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_bvalid <= 0; - axi_bresp <= 2'b0; - end - else - begin - if (axi_awready && axi_ctrl.awvalid && ~axi_bvalid && axi_wready && axi_ctrl.wvalid) - begin - axi_bvalid <= 1'b1; - axi_bresp <= 2'b0; - end - else - begin - if (axi_ctrl.bready && axi_bvalid) - begin - axi_bvalid <= 1'b0; - end - end - end -end - -// wready -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_wready <= 1'b0; - end - else - begin - if (~axi_wready && axi_ctrl.wvalid && axi_ctrl.awvalid && aw_en ) - begin - axi_wready <= 1'b1; - end - else - begin - axi_wready <= 1'b0; - end - end -end - -// rvalid and rresp (1Del?) -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_rvalid <= 0; - axi_rresp <= 0; - end - else - begin - if (axi_arready && axi_ctrl.arvalid && ~axi_rvalid) - begin - axi_rvalid <= 1'b1; - axi_rresp <= 2'b0; - end - else if (axi_rvalid && axi_ctrl.rready) - begin - axi_rvalid <= 1'b0; - end - end -end - -endmodule // cnfg_slave \ No newline at end of file diff --git a/examples_hw/apps/rdma_base/vfpga_top.svh b/examples_hw/apps/rdma_base/vfpga_top.svh deleted file mode 100644 index 899aa76bf..000000000 --- a/examples_hw/apps/rdma_base/vfpga_top.svh +++ /dev/null @@ -1,51 +0,0 @@ -/** - * VFPGA TOP - * - * Tie up all signals to the user kernels - * Still to this day, interfaces are not supported by Vivado packager ... - * This means verilog style port connections are needed. - * - */ - -// CSR -logic [PID_BITS-1:0] mux_ctid; // go to card with this ctid - -rdma_base_slv inst_rdma_base_slv ( - .aclk(aclk), - .aresetn(aresetn), - - .axi_ctrl(axi_ctrl), - - .mux_ctid(mux_ctid) -); - -mux_host_card_rd_rdma inst_mux_send ( - .aclk(aclk), - .aresetn(aresetn), - - .mux_ctid(mux_ctid), - .s_rq(rq_rd), - .m_sq(sq_rd), - - .s_axis_host(axis_host_recv[0]), - .s_axis_card(axis_card_recv[0]), - .m_axis(axis_rdma_send[0]) -); - -mux_host_card_wr_rdma inst_mux_recv ( - .aclk(aclk), - .aresetn(aresetn), - - .mux_ctid(mux_ctid), - .s_rq(rq_wr), - .m_sq(sq_wr), - - .s_axis(axis_rdma_recv[0]), - .m_axis_host(axis_host_send[0]), - .m_axis_card(axis_card_send[0]) -); - -// Tie-off unused -always_comb notify.tie_off_m(); -always_comb cq_rd.tie_off_s(); -always_comb cq_wr.tie_off_s(); \ No newline at end of file diff --git a/examples_hw/apps/tcp_iperf/hls/CMakeLists.txt b/examples_hw/apps/tcp_iperf/hls/CMakeLists.txt deleted file mode 100644 index 8e58388b7..000000000 --- a/examples_hw/apps/tcp_iperf/hls/CMakeLists.txt +++ /dev/null @@ -1,80 +0,0 @@ -# Author: David Sidler (david.sidler@inf.ethz.ch) - -cmake_minimum_required(VERSION 3.0) - -set (PROJECT_NAME send_recv) -project(${PROJECT_NAME}) - -# Include custom Find.cmake scripts to enable searching for Vivado & Vitis HLS -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake) - -# Without this variable set, CMake will build tests when running install -#set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY ON) - -# Generate Doxygen if available -#find_package(Doxygen) -#if(Doxygen_FOUND) -# configure_file(${CMAKE_SOURCE_DIR}/Doxyfile.in Doxyfile) -# add_custom_target(doxygen ALL -# COMMAND ${DOXYGEN_EXECUTABLE} Doxyfile -# WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -#endif() - -if (NOT hasParent) - if (DEVICE_NAME STREQUAL "u280") - set(FPGA_PART xcu280-fsvh2892-2L-e) - set(FPGA_FAMILY ultraplus) - set(NETWORK_BANDWIDTH 100 CACHE STRING "Network bandwidth") - endif() -endif() -set(DATA_WIDTH 64 CACHE STRING "Width of data path in bytes") -set(CLOCK_PERIOD 3.2 CACHE STRING "Target clock period in nanoseconds") - - -# Find Xilinx Vitis HLS -find_package(VitisHLS REQUIRED) -if (NOT VITIS_HLS_FOUND) - message(FATAL_ERROR "Vitis HLS not found.") -endif() - - -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/send_recv_config.hpp.in send_recv_config.hpp) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/make.tcl.in make.tcl) - - -set(EXAMPLE_HLS_DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/send_recv.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/send_recv.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/send_recv_config.hpp.in - ${CMAKE_CURRENT_SOURCE_DIR}/test_send_recv.cpp) - - -#Setup HLS custom targets -set(HLS_TARGETS synthesis csim ip services) - -foreach (target ${HLS_TARGETS}) - if (NOT TARGET ${target}) - add_custom_target(${target}) - endif() - - add_custom_target(${target}.${PROJECT_NAME} - COMMAND ${VITIS_HLS_BINARY} -f make.tcl -tclargs ${target} - DEPENDS ${EXAMPLE_HLS_DEPENDS}) - add_dependencies(${target} ${target}.${PROJECT_NAME}) -endforeach() - -#target dependencies -add_dependencies(ip.${PROJECT_NAME} synthesis.${PROJECT_NAME}) -add_dependencies(services.${PROJECT_NAME} ip.${PROJECT_NAME}) - -# Installation -if (DEFINED ENV{IPREPO_DIR}) - set(IPREPO_DIR $ENV{IPREPO_DIR}) -elseif(NOT IPREPO_DIR) - set(IPREPO_DIR ${CMAKE_CURRENT_SOURCE_DIR}/iprepo/) -endif() -#install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_prj/solution1/impl/ip/ -# DESTINATION ${IPREPO_DIR}/${PROJECT_NAME}/) - diff --git a/examples_hw/apps/tcp_iperf/hls/axi_utils.cpp b/examples_hw/apps/tcp_iperf/hls/axi_utils.cpp deleted file mode 100644 index c0699fdf0..000000000 --- a/examples_hw/apps/tcp_iperf/hls/axi_utils.cpp +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright (c) 2019, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ -#include "axi_utils.hpp" - -ap_uint<64> lenToKeep(ap_uint<6> length) -{ - switch (length) - { - case 1: - return 0x01; - case 2: - return 0x03; - case 3: - return 0x07; - case 4: - return 0x0F; - case 5: - return 0x1F; - case 6: - return 0x3F; - case 7: - return 0x7F; - case 8: - return 0xFF; - case 9: - return 0x01FF; - case 10: - return 0x03FF; - case 11: - return 0x07FF; - case 12: - return 0x0FFF; - case 13: - return 0x1FFF; - case 14: - return 0x3FFF; - case 15: - return 0x7FFF; - case 16: - return 0xFFFF; - case 17: - return 0x01FFFF; - case 18: - return 0x03FFFF; - case 19: - return 0x07FFFF; - case 20: - return 0x0FFFFF; - case 21: - return 0x1FFFFF; - case 22: - return 0x3FFFFF; - case 23: - return 0x7FFFFF; - case 24: - return 0xFFFFFF; - case 25: - return 0x01FFFFFF; - case 26: - return 0x03FFFFFF; - case 27: - return 0x07FFFFFF; - case 28: - return 0x0FFFFFFF; - case 29: - return 0x1FFFFFFF; - case 30: - return 0x3FFFFFFF; - case 31: - return 0x7FFFFFFF; - case 32: - return 0xFFFFFFFF; - case 33: - return 0x01FFFFFFFF; - case 34: - return 0x03FFFFFFFF; - case 35: - return 0x07FFFFFFFF; - case 36: - return 0x0FFFFFFFFF; - case 37: - return 0x1FFFFFFFFF; - case 38: - return 0x3FFFFFFFFF; - case 39: - return 0x7FFFFFFFFF; - case 40: - return 0xFFFFFFFFFF; - case 41: - return 0x01FFFFFFFFFF; - case 42: - return 0x03FFFFFFFFFF; - case 43: - return 0x07FFFFFFFFFF; - case 44: - return 0x0FFFFFFFFFFF; - case 45: - return 0x1FFFFFFFFFFF; - case 46: - return 0x3FFFFFFFFFFF; - case 47: - return 0x7FFFFFFFFFFF; - case 48: - return 0xFFFFFFFFFFFF; - case 49: - return 0x01FFFFFFFFFFFF; - case 50: - return 0x03FFFFFFFFFFFF; - case 51: - return 0x07FFFFFFFFFFFF; - case 52: - return 0x0FFFFFFFFFFFFF; - case 53: - return 0x1FFFFFFFFFFFFF; - case 54: - return 0x3FFFFFFFFFFFFF; - case 55: - return 0x7FFFFFFFFFFFFF; - case 56: - return 0xFFFFFFFFFFFFFF; - case 57: - return 0x01FFFFFFFFFFFFFF; - case 58: - return 0x03FFFFFFFFFFFFFF; - case 59: - return 0x07FFFFFFFFFFFFFF; - case 60: - return 0x0FFFFFFFFFFFFFFF; - case 61: - return 0x1FFFFFFFFFFFFFFF; - case 62: - return 0x3FFFFFFFFFFFFFFF; - case 63: - return 0x7FFFFFFFFFFFFFFF; - default: - return 0xFFFFFFFFFFFFFFFF; - }//switch -} - -//Input argument cannot be templatized, otherwise the case statement leads to duplicate definitions -ap_uint<8> keepToLen(ap_uint<64> keepValue) -{ -#pragma HLS INLINE - - switch (keepValue) - { - case 0x01: - return 0x1; - case 0x3: - return 0x2; - case 0x07: - return 0x3; - case 0x0F: - return 0x4; - case 0x1F: - return 0x5; - case 0x3F: - return 0x6; - case 0x7F: - return 0x7; - case 0xFF: - return 0x8; -//#if W > 64 - case 0x01FF: - return 0x9; - case 0x3FF: - return 0xA; - case 0x07FF: - return 0xB; - case 0x0FFF: - return 0xC; - case 0x1FFF: - return 0xD; - case 0x3FFF: - return 0xE; - case 0x7FFF: - return 0xF; - case 0xFFFF: - return 0x10; -//#if W > 128 - case 0x01FFFF: - return 0x11; - case 0x3FFFF: - return 0x12; - case 0x07FFFF: - return 0x13; - case 0x0FFFFF: - return 0x14; - case 0x1FFFFF: - return 0x15; - case 0x3FFFFF: - return 0x16; - case 0x7FFFFF: - return 0x17; - case 0xFFFFFF: - return 0x18; - case 0x01FFFFFF: - return 0x19; - case 0x3FFFFFF: - return 0x1A; - case 0x07FFFFFF: - return 0x1B; - case 0x0FFFFFFF: - return 0x1C; - case 0x1FFFFFFF: - return 0x1D; - case 0x3FFFFFFF: - return 0x1E; - case 0x7FFFFFFF: - return 0x1F; - case 0xFFFFFFFF: - return 0x20; -//#if W > 256 - case 0x01FFFFFFFF: - return 0x21; - case 0x3FFFFFFFF: - return 0x22; - case 0x07FFFFFFFF: - return 0x23; - case 0x0FFFFFFFFF: - return 0x24; - case 0x1FFFFFFFFF: - return 0x25; - case 0x3FFFFFFFFF: - return 0x26; - case 0x7FFFFFFFFF: - return 0x27; - case 0xFFFFFFFFFF: - return 0x28; - case 0x01FFFFFFFFFF: - return 0x29; - case 0x3FFFFFFFFFF: - return 0x2A; - case 0x07FFFFFFFFFF: - return 0x2B; - case 0x0FFFFFFFFFFF: - return 0x2C; - case 0x1FFFFFFFFFFF: - return 0x2D; - case 0x3FFFFFFFFFFF: - return 0x2E; - case 0x7FFFFFFFFFFF: - return 0x2F; - case 0xFFFFFFFFFFFF: - return 0x30; - - case 0x01FFFFFFFFFFFF: - return 0x31; - case 0x3FFFFFFFFFFFF: - return 0x32; - case 0x07FFFFFFFFFFFF: - return 0x33; - case 0x0FFFFFFFFFFFFF: - return 0x34; - case 0x1FFFFFFFFFFFFF: - return 0x35; - case 0x3FFFFFFFFFFFFF: - return 0x36; - case 0x7FFFFFFFFFFFFF: - return 0x37; - case 0xFFFFFFFFFFFFFF: - return 0x38; - case 0x01FFFFFFFFFFFFFF: - return 0x39; - case 0x3FFFFFFFFFFFFFF: - return 0x3A; - case 0x07FFFFFFFFFFFFFF: - return 0x3B; - case 0x0FFFFFFFFFFFFFFF: - return 0x3C; - case 0x1FFFFFFFFFFFFFFF: - return 0x3D; - case 0x3FFFFFFFFFFFFFFF: - return 0x3E; - case 0x7FFFFFFFFFFFFFFF: - return 0x3F; - case 0xFFFFFFFFFFFFFFFF: - return 0x40; - default: - return 0xFF; -//#endif -//#endif -//#endif - } -} - - diff --git a/examples_hw/apps/tcp_iperf/hls/axi_utils.hpp b/examples_hw/apps/tcp_iperf/hls/axi_utils.hpp deleted file mode 100644 index c09965bc9..000000000 --- a/examples_hw/apps/tcp_iperf/hls/axi_utils.hpp +++ /dev/null @@ -1,1393 +0,0 @@ -/* - * Copyright (c) 2019, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ -#pragma once - -#include -#include "ap_int.h" -#include -#include -#include -#include -#if defined( __VITIS_HLS__) -#include "ap_axi_sdata.h" -#endif - -//Adaptation of ap_axiu<> - -/*template -struct axis -{ - ap_uint data; - ap_uint keep; - ap_uint<1> last; - axis() {} - axis(ap_uint data, ap_uint keep, ap_uint<1> last) - :data(data), keep(keep), last(last) {} -};*/ - -template -struct net_axis -{ - ap_uint data; - ap_uint keep; - ap_uint<1> last; - net_axis() {} - net_axis(ap_uint data, ap_uint keep, ap_uint<1> last) - :data(data), keep(keep), last(last) {} -}; - -#if defined( __VITIS_HLS__) -template -void convert_net_axis_to_axis(hls::stream >& input, - hls::stream >& output) -{ -#pragma HLS pipeline II=1 - - net_axis inputWord; - ap_axiu outputWord; - - if (!input.empty()) - { - inputWord = input.read(); - outputWord.data = inputWord.data; - outputWord.keep = inputWord.keep; - outputWord.last = inputWord.last; - output.write(outputWord); - } -} - - -template -void convert_axis_to_net_axis(hls::stream >& input, - hls::stream >& output) -{ -#pragma HLS pipeline II=1 - - ap_axiu inputWord; - net_axis outputWord; - - if (!input.empty()) - { - inputWord = input.read(); - outputWord.data = inputWord.data; - outputWord.keep = inputWord.keep; - outputWord.last = inputWord.last; - output.write(outputWord); - } -} -#endif -template -ap_uint reverse(const ap_uint& w) -{ - ap_uint temp; - for (int i = 0; i < D/8; i++) - { - #pragma HLS UNROLL - temp(i*8+7, i*8) = w(D-(i*8)-1, D-(i*8)-8); - } - return temp; -} - -template -ap_uint reverse_bits(const ap_uint& w) -{ - ap_uint temp; - for (int i = 0; i < D; i++) - { - #pragma HLS UNROLL - temp[i] = w[D-i-1]; - } - return temp; -} - -template -bool scan(std::istream& inputFile, ap_uint& data) -{ - uint16_t temp; - for (int i = 0; i < D/8; i++) - { - if (inputFile >> std::hex >> temp) - { - data(i*8+7, i*8) = temp; - } - else - { - //std::cerr << "[ERROR]: could not scan input" << std::endl; - return false; - } - } - return (bool) inputFile; -} - -template -bool scan(std::istream& inputFile, net_axis& word) -{ - uint16_t temp; - uint32_t keepTemp; - uint16_t lastTemp; - for (int i = 0; i < D/8; i++) - { - if (inputFile >> std::hex >> temp) - { - word.data(i*8+7, i*8) = temp; - } - else - { - //std::cerr << "[ERROR]: could not scan input" << std::endl; - return false; - } - } - inputFile >> keepTemp; - inputFile >> lastTemp; - word.keep = keepTemp; - word.last = lastTemp; - //if (!inputFile) - // std::cerr << "[ERROR]: could not scan input" << std::endl; - return (bool) inputFile; -} - -template -bool scanLE(std::istream& inputFile, ap_uint& data) -{ - uint16_t temp; - for (int i = (D/8)-1; i >= 0; i--) - { - if (inputFile >> std::hex >> temp) - { - data(i*8+7, i*8) = temp; - } - else - { - //std::cerr << "[ERROR]: could not scan input" << std::endl; - return false; - } - } - return (bool) inputFile; -} - -template -bool scanLE(std::istream& inputFile, net_axis& word) -{ - uint16_t temp; - uint32_t keepTemp; - uint16_t lastTemp; - for (int i = (D/8)-1; i >= 0; i--) - { - if (inputFile >> std::hex >> temp) - { - word.data(i*8+7, i*8) = temp; - } - else - { - //std::cerr << "[ERROR]: could not scan input" << std::endl; - return false; - } - } - inputFile >> keepTemp; - inputFile >> lastTemp; - word.keep = keepTemp; - word.last = lastTemp; - return (bool) inputFile; -} - -template -void print(std::ostream& output, ap_uint data) -{ - output << std::hex; - output << std::setfill('0'); - for (int i = 0; i < D/8; i++) - { - output << std::noshowbase << std::setw(2) << (uint16_t) data(i*8+7, i*8) << " "; - } -} - -template -void print(std::ostream& output, net_axis& word) -{ -#ifndef __SYNTHESIS__ - output << std::hex; - output << std::setfill('0'); - for (int i = 0; i < D/8; i++) - { - output << std::noshowbase << std::setw(2) << (uint16_t) word.data(i*8+7, i*8) << " "; - } - output << std::setw(D/8/4) << (uint64_t) word.keep << " "; - output << std::setw(1) << (uint16_t)word.last; -#endif -} - -template -void printLE(std::ostream& output, ap_uint& data) -{ -#ifndef __SYNTHESIS__ - output << std::hex; - output << std::setfill('0') ; - for (int i = (D/8)-1; i >= 0; i--) - { - output << std::noshowbase << std::setw(2) << (uint16_t) data(i*8+7, i*8) << " "; - } -#endif -} - -template -void printLE(std::ostream& output, net_axis& word) -{ -#ifndef __SYNTHESIS__ - output << std::hex; - output << std::setfill('0') ; - for (int i = (D/8)-1; i >= 0; i--) - { - output << std::noshowbase << std::setw(2) << (uint16_t) word.data(i*8+7, i*8) << " "; - } - output << std::setw(D/8/4) << (uint64_t) word.keep << " "; - output << std::setw(1) << (uint16_t)word.last; -#endif -} - -template -void increaseStreamWidth(hls::stream >& input, hls::stream >&output) -{ -#pragma HLS INLINE - - static int count = 0; - - static net_axis temp; - - if (!input.empty()) - { - net_axis currWord = input.read(); - temp.data((W*count)+W-1, (W*count)) = currWord.data; - temp.keep(((W/8)*count+(W/8)-1), ((W/8)*count)) = currWord.keep; - temp.last = currWord.last; - - count++; - if (currWord.last || count == D) - { - output.write(temp); - count = 0; -#ifndef __SYNTHESIS__ - temp.data = 0; -#endif - temp.keep = 0; - } - } - -} - -template -void reduceStreamWidth(hls::stream >& input, hls::stream >&output) -{ -#pragma HLS INLINE - - enum fsmStateType {FIRST, SECOND}; - static fsmStateType fsmState = FIRST; - static int count = 0; - - static net_axis currWord; - net_axis temp; - - switch (fsmState) - { - case FIRST: - if (!input.empty()) - { - input.read(currWord); - temp.data = currWord.data((W/D)-1, 0); - temp.keep = currWord.keep(((W/D)/8)-1, 0); - temp.last = (currWord.keep[(W/8)/D] == 0); //(currWord.keep((W/8)-1, (W/8)/2) == 0); - output.write(temp); - - if (currWord.keep[(W/8)/D]) - { - count = 1; - fsmState = SECOND; - } - - //shift word - currWord.data(W-(W/D)-1, 0) = currWord.data(W-1, W/D); - currWord.keep((W/8)-((W/8)/D)-1, 0) = currWord.keep((W/8)-1, (W/8)/D); - currWord.keep((W/8)-1, (W/8)-((W/8)/D)) = 0; - } - break; - case SECOND: - temp.data = currWord.data((W/D)-1, 0); - temp.keep = currWord.keep(((W/D)/8)-1, 0); - if (count < D-1) - { - temp.last = (currWord.keep[(W/8)/D] == 0); //(currWord.keep((W/8)-1, (W/8)/2) == 0); - } - else - { - temp.last = currWord.last; - } - output.write(temp); - //shift word - currWord.data(W-(W/D)-1, 0) = currWord.data(W-1, W/D); - currWord.keep((W/8)-((W/8)/D)-1, 0) = currWord.keep((W/8)-1, (W/8)/D); - currWord.keep((W/8)-1, (W/8)-((W/8)/D)) = 0; - - - count++; - if (count == D || temp.last) - { - - fsmState = FIRST; - } - break; - } -} - -template -void convertStreamWidth(hls::stream >& input, hls::stream >&output) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - if (!input.empty()) - { - output.write(input.read()); - } -} - -template -void convertStreamWidth(hls::stream >& input, hls::stream >&output) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - increaseStreamWidth(input, output); -} - -template -void convertStreamWidth(hls::stream >& input, hls::stream >&output) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - increaseStreamWidth(input, output); -} - -template -void convertStreamWidth(hls::stream >& input, hls::stream >&output) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - increaseStreamWidth(input, output); -} - -template -void convertStreamWidth(hls::stream >& input, hls::stream >&output) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - reduceStreamWidth(input, output); -} - -template -void convertStreamWidth(hls::stream >& input, hls::stream >&output) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - reduceStreamWidth(input, output); -} - -template -void convertStreamWidth(hls::stream >& input, hls::stream >&output) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - reduceStreamWidth(input, output); -} - -// The 2nd template parameter is a hack to use this function multiple times -template -void rshiftWordByOctet( uint16_t offset, - hls::stream& input, - hls::stream& output) -{ -#pragma HLS inline off -#pragma HLS pipeline II=1 //TODO this has a bug, the bug might come from how it is used - - enum fsmStateType {PKG, REMAINDER}; - static fsmStateType fsmState = PKG; - static bool rs_firstWord = (offset != 0); - static T prevWord; - - T currWord; - T sendWord; - - sendWord.last = 0; - switch (fsmState) - { - case PKG: - if (!input.empty()) - { - input.read(currWord); - - if (!rs_firstWord) - { - if (offset == 0) - { - sendWord = currWord; - } - else - { - sendWord.data((W-1)-(8*offset), 0) = prevWord.data((W-1), 8*offset); - sendWord.data((W-1), W-(8*offset)) = currWord.data((8*offset)-1, 0); - - sendWord.keep((W/8-1)-offset, 0) = prevWord.keep((W/8-1), offset); - sendWord.keep((W/8-1), (W/8)-offset) = currWord.keep(offset-1, 0); - - sendWord.last = (currWord.keep((W/8-1), offset) == 0); - //sendWord.dest = currWord.dest; - //assignDest(sendWord, currWord); - }//else offset - output.write(sendWord); - } - - prevWord = currWord; - rs_firstWord = false; - if (currWord.last) - { - rs_firstWord = (offset != 0); - //rs_writeRemainder = (sendWord.last == 0); - if (!sendWord.last) - { - fsmState = REMAINDER; - } - } - //}//else offset - } - break; - case REMAINDER: - sendWord.data((W-1)-(8*offset), 0) = prevWord.data((W-1), 8*offset); - sendWord.data((W-1), W-(8*offset)) = 0; - sendWord.keep((W/8-1)-offset, 0) = prevWord.keep((W/8-1), offset); - sendWord.keep((W/8-1), (W/8)-offset) = 0; - sendWord.last = 1; - //sendWord.dest = prevWord.dest; - //assignDest(sendWord, currWord); - - output.write(sendWord); - fsmState = PKG; - break; - } -} - -// The 2nd template parameter is a hack to use this function multiple times -template -void lshiftWordByOctet( uint16_t offset, - hls::stream >& input, - hls::stream >& output) -{ -#pragma HLS inline off -#pragma HLS pipeline II=1 - static bool ls_firstWord = true; - static bool ls_writeRemainder = false; - static net_axis prevWord; - - net_axis currWord; - net_axis sendWord; - - //TODO use states - if (ls_writeRemainder) - { - sendWord.data((8*offset)-1, 0) = prevWord.data((W-1), W-(8*offset)); - sendWord.data((W-1), (8*offset)) = 0; - sendWord.keep(offset-1, 0) = prevWord.keep((W/8-1), (W/8)-offset); - sendWord.keep((W/8-1), offset) = 0; - sendWord.last = 1; - - output.write(sendWord); - ls_writeRemainder = false; - } - else if (!input.empty()) - { - input.read(currWord); - - if (offset == 0) - { - output.write(currWord); - } - else - { - - if (ls_firstWord) - { - sendWord.data((8*offset)-1, 0) = 0; - sendWord.data((W-1), (8*offset)) = currWord.data((W-1)-(8*offset), 0); - sendWord.keep(offset-1, 0) = 0xFFFFFFFF; - sendWord.keep((W/8-1), offset) = currWord.keep((W/8-1)-offset, 0); - sendWord.last = (currWord.keep((W/8-1), (W/8)-offset) == 0); - } - else - { - sendWord.data((8*offset)-1, 0) = prevWord.data((W-1), W-(8*offset)); - sendWord.data((W-1), (8*offset)) = currWord.data((W-1)-(8*offset), 0); - - sendWord.keep(offset-1, 0) = prevWord.keep((W/8-1), (W/8)-offset); - sendWord.keep((W/8-1), offset) = currWord.keep((W/8-1)-offset, 0); - - sendWord.last = (currWord.keep((W/8-1), (W/8)-offset) == 0); - - } - output.write(sendWord); - - prevWord = currWord; - ls_firstWord = false; - if (currWord.last) - { - ls_firstWord = true; - ls_writeRemainder = !sendWord.last; - } - } //else offset - } - -} - -//TODO move to utils -template -void stream_merger(hls::stream& in1, hls::stream& in2, hls::stream& out) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS inline off - - if (!in1.empty()) - { - out.write(in1.read()); - } - else if (!in2.empty()) - { - out.write(in2.read()); - } -} - -template -void stream_merger( hls::stream >& originIn, - hls::stream& input0, - hls::stream& input1, - hls::stream& output) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - enum stateType {IDLE, FWD0, FWD1}; - static stateType state = IDLE; - T word; - ap_uint<1> origin; - - switch (state) - { - case IDLE: - if (!originIn.empty()) - { - originIn.read(origin); - if (origin == 0) - { - if (!input0.empty()) - { - input0.read(word); - output.write(word); - } - else - { - state = FWD0; - } - } - else - { - if (!input1.empty()) - { - input1.read(word); - output.write(word); - } - else - { - state = FWD1; - } - } - } - break; - case FWD0: - if (!input0.empty()) - { - input0.read(word); - output.write(word); - state = IDLE; - } - break; - case FWD1: - if (!input1.empty()) - { - input1.read(word); - output.write(word); - state = IDLE; - } - break; - }//switch -} - -template -void fair_merger(hls::stream& in0, hls::stream& in1, hls::stream& out) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - static bool merge_pref = true; - - if (merge_pref) - { - if (!in0.empty()) - { - out.write(in0.read()); - merge_pref = false; - } - else if(!in1.empty()) - { - out.write(in1.read()); - } - } - else - { - if(!in1.empty()) - { - out.write(in1.read()); - merge_pref = true; - } - else if (!in0.empty()) - { - out.write(in0.read()); - } - } -} - -template -void fair_pkg_merger(hls::stream >& in0, hls::stream >& in1, hls::stream >& out) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - enum fpmStateType{IDLE, FWD0, FWD1}; - static fpmStateType state = IDLE; - static bool merge_pref = true; - net_axis currWord; - - currWord.last = 0; - switch (state) - { - case IDLE: - if (merge_pref) - { - if (!in0.empty()) - { - in0.read(currWord); - out.write(currWord); - state = FWD1; - - } - else if(!in1.empty()) - { - in1.read(currWord); - out.write(currWord); - state = FWD1; - } - } - else - { - if(!in1.empty()) - { - in1.read(currWord); - out.write(currWord); - state = FWD1; - - } - else if (!in0.empty()) - { - in0.read(currWord); - out.write(currWord); - state = FWD0; - } - } - if (currWord.last) - { - state = IDLE; - } - break; - case FWD0: - if (!in0.empty()) - { - in0.read(currWord); - out.write(currWord); - if (currWord.last) - { - merge_pref = false; - state = IDLE; - } - } - break; - case FWD1: - if (!in1.empty()) - { - in1.read(currWord); - out.write(currWord); - if (currWord.last) - { - merge_pref = true; - state = IDLE; - } - } - break; - }//switch -} - -template -void stream_pkg_merger( hls::stream >& originIn, - hls::stream >& input0, - hls::stream >& input1, - hls::stream >& output) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - enum stateType {IDLE, FWD0, FWD1}; - static stateType state = IDLE; - net_axis currWord; - ap_uint<1> origin; - - switch (state) - { - case IDLE: - if (!originIn.empty()) - { - originIn.read(origin); - if (origin == 0) - { - if (!input0.empty()) - { - input0.read(currWord); - output.write(currWord); - if (!currWord.last) - { - state = FWD0; - } - } - else - { - state = FWD0; - } - } - else - { - if (!input1.empty()) - { - input1.read(currWord); - output.write(currWord); - if (!currWord.last) - { - state = FWD1; - } - } - else - { - state = FWD1; - } - } - } - break; - case FWD0: - if (!input0.empty()) - { - input0.read(currWord); - output.write(currWord); - if (currWord.last) - { - state = IDLE; - } - } - break; - case FWD1: - if (!input1.empty()) - { - input1.read(currWord); - output.write(currWord); - if (currWord.last) - { - state = IDLE; - } - } - break; - }//switch -} - -template -void stream_pkg_splitter( hls::stream >& destIn, - hls::stream >& input, - hls::stream >& output0, - hls::stream >& output1) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - enum stateType {IDLE, FWD0, FWD1}; - static stateType state = IDLE; - net_axis currWord; - ap_uint<1> dest; - - switch (state) - { - case IDLE: - if (!destIn.empty()) - { - destIn.read(dest); - state = (dest == 0) ? FWD0 : FWD1; - if (!input.empty()) - { - input.read(currWord); - if (dest == 0) - { - output0.write(currWord); - } - else - { - output1.write(currWord); - - } - if (currWord.last) - { - state = IDLE; - } - } - } - break; - case FWD0: - if (!input.empty()) - { - input.read(currWord); - output0.write(currWord); - if (currWord.last) - { - state = IDLE; - } - } - break; - case FWD1: - if (!input.empty()) - { - input.read(currWord); - output1.write(currWord); - if (currWord.last) - { - state = IDLE; - } - } - break; - }//switch -} - - -template -void pass_valid_pkg(hls::stream& pkgValidIn, - hls::stream >& input, - hls::stream >& output) -{ - #pragma HLS PIPELINE II=1 - #pragma HLS INLINE off - - enum fsmStateType {VALID, FWD, DROP}; - static fsmStateType state = VALID; - - switch (state) - { - case VALID: - if (!pkgValidIn.empty() && !input.empty()) - { - bool valid = pkgValidIn.read(); - net_axis word = input.read(); - - if (valid) - { - output.write(word); - if (!word.last) - { - state = FWD; - } - } - else - { - if (!word.last) - { - state = DROP; - } - } - - } - break; - case FWD: - if (!input.empty()) - { - net_axis word = input.read(); - output.write(word); - if (word.last) - { - state = VALID; - } - } - break; - case DROP: - if (!input.empty()) - { - net_axis word = input.read(); - if (word.last) - { - state = VALID; - } - } - break; - } //switch -} - - -template -void toe_duplicate_stream( hls::stream& in, - hls::stream& out0, - hls::stream& out1) -{ - #pragma HLS PIPELINE II=1 - #pragma HLS INLINE off - - if (!in.empty()) - { - T item = in.read(); - out0.write(item); - out1.write(item); - } -} - -template -void ip_handler_duplicate_stream( hls::stream& in, - hls::stream& out0, - hls::stream& out1) -{ - #pragma HLS PIPELINE II=1 - #pragma HLS INLINE off - - if (!in.empty()) - { - T item = in.read(); - out0.write(item); - out1.write(item); - } -} - - -ap_uint<64> lenToKeep(ap_uint<6> length); -ap_uint<8> keepToLen(ap_uint<64> keepValue); - -template -net_axis alignWords(ap_uint<6> offset, net_axis prevWord, net_axis currWord) -{ - - net_axis alignedWord; - - alignedWord.data(WIDTH-1, WIDTH - (offset*8)) = currWord.data(offset*8-1, 0); - alignedWord.keep(WIDTH/8-1, WIDTH/8 - offset) = currWord.keep(offset - 1, 0); - alignedWord.data(WIDTH - (offset*8) -1, 0) = prevWord.data(WIDTH-1, offset*8); - alignedWord.keep(WIDTH/8 - offset - 1, 0) = prevWord.keep(WIDTH/8-1, offset); - //alignedWord.last = (currWord.keep[offset] == 0); - - return alignedWord; -} - - -// SOLUTION NEEDED -------------------------------------------------------------------------------------------------------------------------------------------------- - -// The 2nd template parameter is a hack to use this function multiple times -template -void udp_lshiftWordByOctet( uint16_t offset, - hls::stream >& input, - hls::stream >& output) -{ -#pragma HLS inline off -#pragma HLS pipeline II=1 - static bool ls_firstWord = true; - static bool ls_writeRemainder = false; - static net_axis prevWord; - - net_axis currWord; - net_axis sendWord; - - //TODO use states - if (ls_writeRemainder) - { - sendWord.data((8*offset)-1, 0) = prevWord.data((W-1), W-(8*offset)); - sendWord.data((W-1), (8*offset)) = 0; - sendWord.keep(offset-1, 0) = prevWord.keep((W/8-1), (W/8)-offset); - sendWord.keep((W/8-1), offset) = 0; - sendWord.last = 1; - - output.write(sendWord); - ls_writeRemainder = false; - } - else if (!input.empty()) - { - input.read(currWord); - - if (offset == 0) - { - output.write(currWord); - } - else - { - - if (ls_firstWord) - { - sendWord.data((8*offset)-1, 0) = 0; - sendWord.data((W-1), (8*offset)) = currWord.data((W-1)-(8*offset), 0); - sendWord.keep(offset-1, 0) = 0xFFFFFFFF; - sendWord.keep((W/8-1), offset) = currWord.keep((W/8-1)-offset, 0); - sendWord.last = (currWord.keep((W/8-1), (W/8)-offset) == 0); - } - else - { - sendWord.data((8*offset)-1, 0) = prevWord.data((W-1), W-(8*offset)); - sendWord.data((W-1), (8*offset)) = currWord.data((W-1)-(8*offset), 0); - - sendWord.keep(offset-1, 0) = prevWord.keep((W/8-1), (W/8)-offset); - sendWord.keep((W/8-1), offset) = currWord.keep((W/8-1)-offset, 0); - - sendWord.last = (currWord.keep((W/8-1), (W/8)-offset) == 0); - - } - output.write(sendWord); - - prevWord = currWord; - ls_firstWord = false; - if (currWord.last) - { - ls_firstWord = true; - ls_writeRemainder = !sendWord.last; - } - } //else offset - } - -} - -// The 2nd template parameter is a hack to use this function multiple times -template -void mac_lshiftWordByOctet( uint16_t offset, - hls::stream >& input, - hls::stream >& output) -{ -#pragma HLS inline off -#pragma HLS pipeline II=1 - static bool ls_firstWord = true; - static bool ls_writeRemainder = false; - static net_axis prevWord; - - net_axis currWord; - net_axis sendWord; - - //TODO use states - if (ls_writeRemainder) - { - sendWord.data((8*offset)-1, 0) = prevWord.data((W-1), W-(8*offset)); - sendWord.data((W-1), (8*offset)) = 0; - sendWord.keep(offset-1, 0) = prevWord.keep((W/8-1), (W/8)-offset); - sendWord.keep((W/8-1), offset) = 0; - sendWord.last = 1; - - output.write(sendWord); - ls_writeRemainder = false; - } - else if (!input.empty()) - { - input.read(currWord); - - if (offset == 0) - { - output.write(currWord); - } - else - { - - if (ls_firstWord) - { - sendWord.data((8*offset)-1, 0) = 0; - sendWord.data((W-1), (8*offset)) = currWord.data((W-1)-(8*offset), 0); - sendWord.keep(offset-1, 0) = 0xFFFFFFFF; - sendWord.keep((W/8-1), offset) = currWord.keep((W/8-1)-offset, 0); - sendWord.last = (currWord.keep((W/8-1), (W/8)-offset) == 0); - } - else - { - sendWord.data((8*offset)-1, 0) = prevWord.data((W-1), W-(8*offset)); - sendWord.data((W-1), (8*offset)) = currWord.data((W-1)-(8*offset), 0); - - sendWord.keep(offset-1, 0) = prevWord.keep((W/8-1), (W/8)-offset); - sendWord.keep((W/8-1), offset) = currWord.keep((W/8-1)-offset, 0); - - sendWord.last = (currWord.keep((W/8-1), (W/8)-offset) == 0); - - } - output.write(sendWord); - - prevWord = currWord; - ls_firstWord = false; - if (currWord.last) - { - ls_firstWord = true; - ls_writeRemainder = !sendWord.last; - } - } //else offset - } - -} - -// The 2nd template parameter is a hack to use this function multiple times -template -void ipv4_lshiftWordByOctet( uint16_t offset, - hls::stream >& input, - hls::stream >& output) -{ -#pragma HLS inline off -#pragma HLS pipeline II=1 - static bool ls_firstWord = true; - static bool ls_writeRemainder = false; - static net_axis prevWord; - - net_axis currWord; - net_axis sendWord; - - //TODO use states - if (ls_writeRemainder) - { - sendWord.data((8*offset)-1, 0) = prevWord.data((W-1), W-(8*offset)); - sendWord.data((W-1), (8*offset)) = 0; - sendWord.keep(offset-1, 0) = prevWord.keep((W/8-1), (W/8)-offset); - sendWord.keep((W/8-1), offset) = 0; - sendWord.last = 1; - - output.write(sendWord); - ls_writeRemainder = false; - } - else if (!input.empty()) - { - input.read(currWord); - - if (offset == 0) - { - output.write(currWord); - } - else - { - - if (ls_firstWord) - { - sendWord.data((8*offset)-1, 0) = 0; - sendWord.data((W-1), (8*offset)) = currWord.data((W-1)-(8*offset), 0); - sendWord.keep(offset-1, 0) = 0xFFFFFFFF; - sendWord.keep((W/8-1), offset) = currWord.keep((W/8-1)-offset, 0); - sendWord.last = (currWord.keep((W/8-1), (W/8)-offset) == 0); - } - else - { - sendWord.data((8*offset)-1, 0) = prevWord.data((W-1), W-(8*offset)); - sendWord.data((W-1), (8*offset)) = currWord.data((W-1)-(8*offset), 0); - - sendWord.keep(offset-1, 0) = prevWord.keep((W/8-1), (W/8)-offset); - sendWord.keep((W/8-1), offset) = currWord.keep((W/8-1)-offset, 0); - - sendWord.last = (currWord.keep((W/8-1), (W/8)-offset) == 0); - - } - output.write(sendWord); - - prevWord = currWord; - ls_firstWord = false; - if (currWord.last) - { - ls_firstWord = true; - ls_writeRemainder = !sendWord.last; - } - } //else offset - } - -} - -// The 2nd template parameter is a hack to use this function multiple times -template -void ip_handler_rshiftWordByOctet( uint16_t offset, - hls::stream& input, - hls::stream& output) -{ -#pragma HLS inline off -#pragma HLS pipeline II=1 //TODO this has a bug, the bug might come from how it is used - - enum fsmStateType {PKG, REMAINDER}; - static fsmStateType fsmState = PKG; - static bool rs_firstWord = (offset != 0); - static T prevWord; - - T currWord; - T sendWord; - - sendWord.last = 0; - switch (fsmState) - { - case PKG: - if (!input.empty()) - { - input.read(currWord); - - if (!rs_firstWord) - { - if (offset == 0) - { - sendWord = currWord; - } - else - { - sendWord.data((W-1)-(8*offset), 0) = prevWord.data((W-1), 8*offset); - sendWord.data((W-1), W-(8*offset)) = currWord.data((8*offset)-1, 0); - - sendWord.keep((W/8-1)-offset, 0) = prevWord.keep((W/8-1), offset); - sendWord.keep((W/8-1), (W/8)-offset) = currWord.keep(offset-1, 0); - - sendWord.last = (currWord.keep((W/8-1), offset) == 0); - //sendWord.dest = currWord.dest; - //assignDest(sendWord, currWord); - }//else offset - output.write(sendWord); - } - - prevWord = currWord; - rs_firstWord = false; - if (currWord.last) - { - rs_firstWord = (offset != 0); - //rs_writeRemainder = (sendWord.last == 0); - if (!sendWord.last) - { - fsmState = REMAINDER; - } - } - //}//else offset - } - break; - case REMAINDER: - sendWord.data((W-1)-(8*offset), 0) = prevWord.data((W-1), 8*offset); - sendWord.data((W-1), W-(8*offset)) = 0; - sendWord.keep((W/8-1)-offset, 0) = prevWord.keep((W/8-1), offset); - sendWord.keep((W/8-1), (W/8)-offset) = 0; - sendWord.last = 1; - //sendWord.dest = prevWord.dest; - //assignDest(sendWord, currWord); - - output.write(sendWord); - fsmState = PKG; - break; - } -} - -// The 2nd template parameter is a hack to use this function multiple times -template -void udp_rshiftWordByOctet( uint16_t offset, - hls::stream& input, - hls::stream& output) -{ -#pragma HLS inline off -#pragma HLS pipeline II=1 //TODO this has a bug, the bug might come from how it is used - - enum fsmStateType {PKG, REMAINDER}; - static fsmStateType fsmState = PKG; - static bool rs_firstWord = (offset != 0); - static T prevWord; - - T currWord; - T sendWord; - - sendWord.last = 0; - switch (fsmState) - { - case PKG: - if (!input.empty()) - { - input.read(currWord); - - if (!rs_firstWord) - { - if (offset == 0) - { - sendWord = currWord; - } - else - { - sendWord.data((W-1)-(8*offset), 0) = prevWord.data((W-1), 8*offset); - sendWord.data((W-1), W-(8*offset)) = currWord.data((8*offset)-1, 0); - - sendWord.keep((W/8-1)-offset, 0) = prevWord.keep((W/8-1), offset); - sendWord.keep((W/8-1), (W/8)-offset) = currWord.keep(offset-1, 0); - - sendWord.last = (currWord.keep((W/8-1), offset) == 0); - //sendWord.dest = currWord.dest; - //assignDest(sendWord, currWord); - }//else offset - output.write(sendWord); - } - - prevWord = currWord; - rs_firstWord = false; - if (currWord.last) - { - rs_firstWord = (offset != 0); - //rs_writeRemainder = (sendWord.last == 0); - if (!sendWord.last) - { - fsmState = REMAINDER; - } - } - //}//else offset - } - break; - case REMAINDER: - sendWord.data((W-1)-(8*offset), 0) = prevWord.data((W-1), 8*offset); - sendWord.data((W-1), W-(8*offset)) = 0; - sendWord.keep((W/8-1)-offset, 0) = prevWord.keep((W/8-1), offset); - sendWord.keep((W/8-1), (W/8)-offset) = 0; - sendWord.last = 1; - //sendWord.dest = prevWord.dest; - //assignDest(sendWord, currWord); - - output.write(sendWord); - fsmState = PKG; - break; - } -} diff --git a/examples_hw/apps/tcp_iperf/hls/make.tcl.in b/examples_hw/apps/tcp_iperf/hls/make.tcl.in deleted file mode 100644 index 2174797db..000000000 --- a/examples_hw/apps/tcp_iperf/hls/make.tcl.in +++ /dev/null @@ -1,34 +0,0 @@ - -open_project ${PROJECT_NAME}_prj - -open_solution "solution1" -set_part ${FPGA_PART} -create_clock -period ${CLOCK_PERIOD} -name default - -set_top ${PROJECT_NAME} - -add_files ${CMAKE_CURRENT_SOURCE_DIR}/send_recv.cpp -cflags "-I${CMAKE_CURRENT_BINARY_DIR}" - - -add_files -tb ${CMAKE_CURRENT_SOURCE_DIR}/test_send_recv.cpp - - -#Check which command -set command [lindex $argv 2] - -if {$command == "synthesis"} { - csynth_design -} elseif {$command == "csim"} { - csim_design -} elseif {$command == "ip"} { - export_design -format ip_catalog -ipname "send_recv" -display_name "send_recv" -vendor "ethz.systems.fpga" -version "1.0" -} elseif {$command == "services"} { - file mkdir ${IPREPO_DIR} - file delete -force ${IPREPO_DIR}/${PROJECT_NAME} - file copy -force ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_prj/solution1/impl/ip ${IPREPO_DIR}/${PROJECT_NAME}/ -} else { - puts "No valid command specified. Use vitis_hls -f make.tcl ." -} - - -exit diff --git a/examples_hw/apps/tcp_iperf/hls/packet.hpp b/examples_hw/apps/tcp_iperf/hls/packet.hpp deleted file mode 100644 index fc4fdd11d..000000000 --- a/examples_hw/apps/tcp_iperf/hls/packet.hpp +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2019, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ -#ifndef PACKET_HPP -#define PACKET_HPP - -#include "stdint.h" -#include "axi_utils.hpp" - -using namespace hls; - -template -class packetHeader { -public: - bool ready; - uint16_t idx; - ap_uint header; - -public: - packetHeader() - :ready(false), idx(0) {} - packetHeader& operator=(const packetHeader& other) - { - ready = other.ready; - idx = other.idx; - header = other.header; - return *this; - } - - void parseWord(ap_uint& w) - { - if (ready) - return; - - if (idx*W+W < HEADER_SIZE) - { - header(idx*W+W-1, idx*W) = w; - } - else //(idx*W+W >= HEADER_SIZE) - { - header(HEADER_SIZE-1, idx*W) = w; - ready = true; - } - idx++; - /*(header(idx*W+W-1, idx*W) = w; - if (idx*W+W >= HEADER_SIZE) - { - ready = true; - }*/ - } - ap_uint<8> consumeWord(ap_uint& w) - { - if ((idx+1)*W <= HEADER_SIZE) - { - w = header(((idx+1)*W)-1, idx*W); - idx++; - return ((HEADER_SIZE - (idx*W)) / 8); - } - else if (idx*W < HEADER_SIZE) - { - w((HEADER_SIZE%W)-1, 0) = header(HEADER_SIZE-1, idx*W); - idx++; - return 0;//(HEADER_SIZE - (idx*W)); - } - return 0; - } - /*bool consumeWord(ap_uint& w) - { - if ((idx+2)*W <= HEADER_SIZE) - { - w = header(((idx+1)*W)-1, idx*W); - idx++; - return false; - /*if ((idx+1)*W > HEADER_SIZE) - { - return true; - } - else - { - return false; - }*//* - } - else if ((idx+1)*W <= HEADER_SIZE) - { - w = header(((idx+1)*W)-1, idx*W); - idx++; - return true; - } - return true; - }*/ - /*void consumePartialWord(ap_uint& w) - { - if (idx*W < HEADER_SIZE) - { - w((HEADER_SIZE%AXI_WIDTH)-1, 0) = header(HEADER_SIZE-1, idx*W); - idx++; - } - //return true; - } - /*bool consumeWord(ap_uint& w) - { - if ((idx+1)*W <= HEADER_SIZE) - { - w = header(((idx+1)*W)-1, idx*W); - idx++; - return true; - } - return false; - } - bool consumePartialWord(ap_uint& w) - { - if (idx*W < HEADER_SIZE) - { - w((HEADER_SIZE%AXI_WIDTH)-1, 0) = header(HEADER_SIZE-1, idx*W); - idx++; - return true; - } - return false; - }*/ - void setRawHeader(ap_uint h) - { - header = h; - } - ap_uint getRawHeader() - { - return header; - } - bool isReady() - { - return ready; - } - - void clear() - { -//#pragma HLS pipeline II=1 -#pragma HLS INLINE - //header = 0; - ready = false; - idx = 0; - } -}; - -#endif diff --git a/examples_hw/apps/tcp_iperf/hls/send_recv.cpp b/examples_hw/apps/tcp_iperf/hls/send_recv.cpp deleted file mode 100644 index a602131aa..000000000 --- a/examples_hw/apps/tcp_iperf/hls/send_recv.cpp +++ /dev/null @@ -1,470 +0,0 @@ - /************************************************ -Copyright (c) 2019, Systems Group, ETH Zurich. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. 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. - -3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. -************************************************/ -#include "send_recv_config.hpp" -#include "send_recv.hpp" -#include -#if defined( __VITIS_HLS__) -#include "ap_axi_sdata.h" -#endif - -//Buffers responses coming from the TCP stack -void status_handler(hls::stream& txStatus, - hls::stream& txStatusBuffer) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - if (!txStatus.empty()) - { - appTxRsp resp = txStatus.read(); - txStatusBuffer.write(resp); - } -} - -//Buffers open status coming from the TCP stack -void openStatus_handler(hls::stream& openConStatus, - hls::stream& openConStatusBuffer) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - if (!openConStatus.empty()) - { - openStatus resp = openConStatus.read(); - openConStatusBuffer.write(resp); - } -} - - -void txMetaData_handler(hls::stream& txMetaDataBuffer, - hls::stream& txMetaData) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - if (!txMetaDataBuffer.empty()) - { - appTxMeta metaDataReq = txMetaDataBuffer.read(); - txMetaData.write(metaDataReq); - } -} - -template -void txDataBuffer_handler(hls::stream >& txDataBuffer, - hls::stream >& txData) -{ - #pragma HLS PIPELINE II=1 - #pragma HLS INLINE off - - if (!txDataBuffer.empty()) - { - net_axis word = txDataBuffer.read(); - txData.write(word); - } -} - -template -void client( - hls::stream& txMetaDataBuffer, - hls::stream >& txDataBuffer, - hls::stream& txStatus, - ap_uint<32> pkgWordCount, - ap_uint<32> sessionID, - ap_uint<32> transferSize, - ap_uint<1> runTx - ) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - enum txHandlerStateType {WAIT_CMD, CHECK_REQ, WRITE_PKG}; - static txHandlerStateType txHandlerState = WAIT_CMD; - - // static ap_uint<32> sessionID; - // static ap_uint<32> transferSize; - // static ap_uint<32> pkgWordCount; - - static ap_uint<16> length; - static ap_uint<16> remaining_space; - static ap_uint<8> error; - static ap_uint<32> currentPkgWord = 0; - static ap_uint<32> wordCnt = 0; - - static ap_uint<32> sentByteCnt = 0; - - - static appTxMeta tx_meta_pkt; - - switch(txHandlerState) - { - case WAIT_CMD: - if (runTx) - { - tx_meta_pkt.sessionID = sessionID; - - if (pkgWordCount*(512/8) > transferSize) - tx_meta_pkt.length = transferSize; - else - tx_meta_pkt.length = pkgWordCount*(512/8); - - txMetaDataBuffer.write(tx_meta_pkt); - - txHandlerState = CHECK_REQ; - } - break; - case CHECK_REQ: - if (!txStatus.empty()) - { - appTxRsp txStatus_pkt = txStatus.read(); - sessionID = txStatus_pkt.sessionID; - length = txStatus_pkt.length; - remaining_space = txStatus_pkt.remaining_space; - error = txStatus_pkt.error; - currentPkgWord = (length + (512/8) -1 ) >> 6; //current packet word length - - //if no error, perpare the tx meta of the next packet - if (error == 0) - { - sentByteCnt = sentByteCnt + length; - - if (sentByteCnt < transferSize) - { - tx_meta_pkt.sessionID = sessionID; - - if (sentByteCnt + pkgWordCount*64 < transferSize ) - { - tx_meta_pkt.length = pkgWordCount*(512/8); - // currentPkgWord = pkgWordCount; - } - else - { - tx_meta_pkt.length = transferSize - sentByteCnt; - // currentPkgWord = (transferSize - sentByteCnt)>>6; - } - - txMetaDataBuffer.write(tx_meta_pkt); - } - txHandlerState = WRITE_PKG; - } - //if error, resend the tx meta of current packet - else - { - //Check if connection was torn down - if (error == 1) - { - // std::cout << "Connection was torn down. " << sessionID << std::endl; - } - else - { - tx_meta_pkt.sessionID = sessionID; - tx_meta_pkt.length = length; - txMetaDataBuffer.write(tx_meta_pkt); - } - } - } - break; - case WRITE_PKG: - wordCnt ++; - net_axis currPkt; - currPkt.data = 0xDEADBEEF; - currPkt.keep = 0xFFFFFFFFFFFFFFFF; - currPkt.last = (wordCnt == currentPkgWord); - txDataBuffer.write(currPkt); - if (wordCnt == currentPkgWord) - { - wordCnt = 0; - if (sentByteCnt >= transferSize) - { - sentByteCnt = 0; - currentPkgWord = 0; - txHandlerState = WAIT_CMD; - } - else - { - txHandlerState = CHECK_REQ; - } - } - break; - } - -} - - -template -void server( hls::stream >& listenPort, - hls::stream& listenPortStatus, - hls::stream& notifications, - hls::stream& readRequest, - hls::stream >& rxMetaData, - hls::stream >& rxData) -{ -#pragma HLS PIPELINE II=1 -#pragma HLS INLINE off - - enum listenFsmStateType {OPEN_PORT, WAIT_PORT_STATUS}; - static listenFsmStateType listenState = OPEN_PORT; - enum consumeFsmStateType {WAIT_PKG, CONSUME}; - static consumeFsmStateType serverFsmState = WAIT_PKG; - #pragma HLS RESET variable=listenState - - switch (listenState) - { - case OPEN_PORT: - // Open Port 5001 - listenPort.write(5001); - listenState = WAIT_PORT_STATUS; - break; - case WAIT_PORT_STATUS: - if (!listenPortStatus.empty()) - { - bool open = listenPortStatus.read(); - if (!open) - { - listenState = OPEN_PORT; - } - } - break; - } - - if (!notifications.empty()) - { - appNotification notification = notifications.read(); - - if (notification.length != 0) - { - readRequest.write(appReadRequest(notification.sessionID, notification.length)); - } - } - - switch (serverFsmState) - { - case WAIT_PKG: - if (!rxMetaData.empty() && !rxData.empty()) - { - rxMetaData.read(); - net_axis receiveWord = rxData.read(); - if (!receiveWord.last) - { - serverFsmState = CONSUME; - } - } - break; - case CONSUME: - if (!rxData.empty()) - { - net_axis receiveWord = rxData.read(); - if (receiveWord.last) - { - serverFsmState = WAIT_PKG; - } - } - break; - } -} - - -#if defined( __VITIS_HLS__) -void send_recv( hls::stream >& listenPort, - hls::stream& listenPortStatus, - hls::stream& notifications, - hls::stream& readRequest, - hls::stream >& rxMetaData, - hls::stream >& rxData, - hls::stream& txMetaData, - hls::stream >& txData, - hls::stream& txStatus, - ap_uint<32> pkgWordCount, - ap_uint<32> sessionID, - ap_uint<32> transferSize, - ap_uint<1> runTx - ) - -{ - #pragma HLS DATAFLOW disable_start_propagation - #pragma HLS INTERFACE ap_ctrl_none port=return - - #pragma HLS INTERFACE axis register port=listenPort name=m_axis_listen_port - #pragma HLS INTERFACE axis register port=listenPortStatus name=s_axis_listen_port_status - - #pragma HLS INTERFACE axis register port=notifications name=s_axis_notifications - #pragma HLS INTERFACE axis register port=readRequest name=m_axis_read_package - #pragma HLS aggregate compact=bit variable=notifications - #pragma HLS aggregate compact=bit variable=readRequest - - #pragma HLS INTERFACE axis register port=rxMetaData name=s_axis_rx_metadata - #pragma HLS INTERFACE axis register port=rxData name=s_axis_rx_data - - #pragma HLS INTERFACE axis register port=txMetaData name=m_axis_tx_metadata - #pragma HLS INTERFACE axis register port=txData name=m_axis_tx_data - #pragma HLS INTERFACE axis register port=txStatus name=s_axis_tx_status - #pragma HLS aggregate compact=bit variable=txMetaData - #pragma HLS aggregate compact=bit variable=txStatus - - #pragma HLS INTERFACE ap_none register port=pkgWordCount - #pragma HLS INTERFACE ap_none register port=sessionID - #pragma HLS INTERFACE ap_none register port=transferSize - #pragma HLS INTERFACE ap_none register port=runTx - - - //This is required to buffer up to 1024 reponses => supporting up to 1024 connections - static hls::stream txStatusBuffer("txStatusBuffer"); - #pragma HLS STREAM variable=txStatusBuffer depth=512 - - //This is required to buffer up to 512 tx_meta_data => supporting up to 512 connections - static hls::stream txMetaDataBuffer("txMetaDataBuffer"); - #pragma HLS STREAM variable=txMetaDataBuffer depth=512 - - //This is required to buffer up to MAX_SESSIONS txData - static hls::stream > txDataBuffer("txDataBuffer"); - #pragma HLS STREAM variable=txDataBuffer depth=512 - - static hls::stream > rxData_internal; - #pragma HLS STREAM depth=2 variable=rxData_internal - - static hls::stream > txData_internal; - #pragma HLS STREAM depth=2 variable=txData_internal - - /* - * Client - */ - status_handler(txStatus, txStatusBuffer); - txMetaData_handler(txMetaDataBuffer, txMetaData); - txDataBuffer_handler(txDataBuffer, txData_internal); - - convert_axis_to_net_axis(rxData, - rxData_internal); - - convert_net_axis_to_axis(txData_internal, - txData); - - client( - txMetaDataBuffer, - txDataBuffer, - txStatusBuffer, - pkgWordCount, - sessionID, - transferSize, - runTx - ); - - /* - * Server - */ - server( listenPort, - listenPortStatus, - notifications, - readRequest, - rxMetaData, - rxData_internal); - -} -#else -void send_recv( hls::stream >& listenPort, - hls::stream& listenPortStatus, - hls::stream& notifications, - hls::stream& readRequest, - hls::stream >& rxMetaData, - hls::stream >& rxData, - hls::stream& txMetaData, - hls::stream >& txData, - hls::stream& txStatus, - ap_uint<32> pkgWordCount, - ap_uint<32> sessionID, - ap_uint<32> transferSize, - ap_uint<1> runTx - ) - -{ - #pragma HLS DATAFLOW disable_start_propagation - #pragma HLS INTERFACE ap_ctrl_none port=return - - #pragma HLS INTERFACE axis register port=listenPort name=m_axis_listen_port - #pragma HLS INTERFACE axis register port=listenPortStatus name=s_axis_listen_port_status - - #pragma HLS INTERFACE axis register port=notifications name=s_axis_notifications - #pragma HLS INTERFACE axis register port=readRequest name=m_axis_read_package - #pragma HLS DATA_PACK variable=notifications - #pragma HLS DATA_PACK variable=readRequest - - #pragma HLS INTERFACE axis register port=rxMetaData name=s_axis_rx_metadata - #pragma HLS INTERFACE axis register port=rxData name=s_axis_rx_data - - #pragma HLS INTERFACE axis register port=txMetaData name=m_axis_tx_metadata - #pragma HLS INTERFACE axis register port=txData name=m_axis_tx_data - #pragma HLS INTERFACE axis register port=txStatus name=s_axis_tx_status - #pragma HLS DATA_PACK variable=txMetaData - #pragma HLS DATA_PACK variable=txStatus - - #pragma HLS INTERFACE ap_none register port=pkgWordCount - #pragma HLS INTERFACE ap_none register port=sessionID - #pragma HLS INTERFACE ap_none register port=transferSize - #pragma HLS INTERFACE ap_none register port=runTx - - - //This is required to buffer up to 1024 reponses => supporting up to 1024 connections - static hls::stream txStatusBuffer("txStatusBuffer"); - #pragma HLS STREAM variable=txStatusBuffer depth=512 - - //This is required to buffer up to 512 tx_meta_data => supporting up to 512 connections - static hls::stream txMetaDataBuffer("txMetaDataBuffer"); - #pragma HLS STREAM variable=txMetaDataBuffer depth=512 - - //This is required to buffer up to MAX_SESSIONS txData - static hls::stream > txDataBuffer("txDataBuffer"); - #pragma HLS STREAM variable=txDataBuffer depth=512 - - /* - * Client - */ - status_handler(txStatus, txStatusBuffer); - txMetaData_handler(txMetaDataBuffer, txMetaData); - txDataBuffer_handler(txDataBuffer, txData); - - client( - txMetaDataBuffer, - txDataBuffer, - txStatusBuffer, - pkgWordCount, - sessionID, - transferSize, - runTx - ); - - /* - * Server - */ - server( listenPort, - listenPortStatus, - notifications, - readRequest, - rxMetaData, - rxData); - -} -#endif \ No newline at end of file diff --git a/examples_hw/apps/tcp_iperf/hls/send_recv.hpp b/examples_hw/apps/tcp_iperf/hls/send_recv.hpp deleted file mode 100644 index 103bdc5c4..000000000 --- a/examples_hw/apps/tcp_iperf/hls/send_recv.hpp +++ /dev/null @@ -1,99 +0,0 @@ -/************************************************ -Copyright (c) 2019, Systems Group, ETH Zurich. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. 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. - -3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. -************************************************/ -#pragma once - -#include "send_recv_config.hpp" -#include "axi_utils.hpp" -#include "packet.hpp" -#include "toe.hpp" - -#ifndef __SYNTHESIS__ -static const ap_uint<32> END_TIME = 1000; //1000000; -static const ap_uint<40> END_TIME_120 = 1000; - -#else -static const ap_uint<32> END_TIME = 1546546546;//1501501501; -static const ap_uint<40> END_TIME_120 = 18750000000; -#endif - -const uint32_t IPERF_TCP_HEADER_SIZE = 512; - -/** - * iperf TCP Header - */ -template -class iperfTcpHeader : public packetHeader { - using packetHeader::header; - -public: - iperfTcpHeader() - { - header(63, 32) = 0x01000000; - header(79, 64) = 0x0000; - header(127, 96) = 0x00000000; - header(159, 128) = 0; - } - - void setDualMode(bool en) - { - if (en) - { - header(31, 0) = 0x01000080; - } - else - { - header(31, 0) = 0x00000000; - } - } - //This is used for dual tes - void setListenPort(ap_uint<16> port) - { - header(95, 80) = reverse(port); - } - void setSeconds(ap_uint<32> seconds) - { - header(191, 160) = reverse(seconds); - } -}; - - -void send_recv( hls::stream >& listenPort, - hls::stream& listenPortStatus, - hls::stream& notifications, - hls::stream& readRequest, - hls::stream >& rxMetaData, - hls::stream >& rxData, - hls::stream& txMetaData, - hls::stream >& txData, - hls::stream& txStatus, - ap_uint<32> pkgWordCount, - ap_uint<32> sessionID, - ap_uint<32> transferSize, - ap_uint<1> runTx - ); \ No newline at end of file diff --git a/examples_hw/apps/tcp_iperf/hls/send_recv_config.hpp.in b/examples_hw/apps/tcp_iperf/hls/send_recv_config.hpp.in deleted file mode 100644 index e6c018a8f..000000000 --- a/examples_hw/apps/tcp_iperf/hls/send_recv_config.hpp.in +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -const unsigned DATA_WIDTH = ${DATA_WIDTH} * 8; diff --git a/examples_hw/apps/tcp_iperf/hls/test_send_recv.cpp b/examples_hw/apps/tcp_iperf/hls/test_send_recv.cpp deleted file mode 100644 index 8f79356fe..000000000 --- a/examples_hw/apps/tcp_iperf/hls/test_send_recv.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/************************************************ -Copyright (c) 2018, Systems Group, ETH Zurich. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. 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. - -3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. -************************************************/ -#include "send_recv_config.hpp" -#include "send_recv.hpp" -#include - -using namespace hls; - - -int main() -{ - hls::stream > listenPort("listenPort"); - hls::stream listenPortStatus("listenPortStatus"); - hls::stream notifications("notifications"); - hls::stream readRequest("readRequest"); - hls::stream > rxMetaData("rxMetaData"); - hls::stream > rxData("rxData"); - hls::stream openConnection("openConnection"); - hls::stream openConStatus("openConStatus"); - hls::stream > closeConnection("closeConnection"); - hls::stream txMetaData("txMetaData"); - hls::stream > txData("txData"); - hls::stream txStatus("txStatus"); - ap_uint<1> runExperiment; - ap_uint<1> dualModeEn; - ap_uint<13> useConn; - ap_uint<8> pkgWordCount; - ap_uint<8> pkgGap; - ap_uint<32> timeInSeconds; - ap_uint<64> timeInCycles; - ap_uint<16> useIpAddr = 2; - ap_uint<16> regBasePort = 5001; - ap_uint<32> sessionID = 1; - ap_uint<32> transferSize = 2*1024; - - dualModeEn = 0; - pkgWordCount = 16; - pkgGap = 0; - timeInSeconds = 100; - timeInCycles = 1000; - - int count = 0; - while (count < 10000) - { - useConn = 2; - runExperiment = 0; - if (count == 20) - { - runExperiment = 1; - } - send_recv( listenPort, - listenPortStatus, - notifications, - readRequest, - rxMetaData, - rxData, - txMetaData, - txData, - txStatus, - pkgWordCount, - sessionID, - transferSize, - runExperiment - ); - - - if (!listenPort.empty()) - { - ap_uint<16> port = listenPort.read(); - std::cout << "Port " << port << " openend." << std::endl; - listenPortStatus.write(true); - } - - - if (!txMetaData.empty()) - { - appTxMeta meta = txMetaData.read(); - std::cout << "New Pkg: " << std::dec << meta.sessionID << ", length[B]: " << meta.length << std::endl; - // int toss = rand() % 2; - // toss = (toss == 0 || meta.length == IPERF_TCP_HEADER_SIZE/8) ? 0 : -1; - int toss = 0; - std::cout << "toss: " << toss << std::endl; - txStatus.write(appTxRsp(meta.sessionID, meta.length, 0xFFFF, toss)); - } - while (!txData.empty()) - { - net_axis currWord = txData.read(); - printLE(std::cout, currWord); - std::cout << std::endl; - - } - - count++; - } - return 0; -} diff --git a/examples_hw/apps/tcp_iperf/hls/toe.hpp b/examples_hw/apps/tcp_iperf/hls/toe.hpp deleted file mode 100755 index 8e0b732f8..000000000 --- a/examples_hw/apps/tcp_iperf/hls/toe.hpp +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2019, Systems Group, ETH Zurich - * Copyright (c) 2016, Xilinx, Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ -#ifndef TOE_HPP_INCLUDED -#define TOE_HPP_INCLUDED - -#include "./axi_utils.hpp" - -const uint16_t TCP_PROTOCOL = 0x06; - -// Forward declarations. -struct rtlSessionUpdateRequest; -struct rtlSessionUpdateReply; -struct rtlSessionLookupReply; -struct rtlSessionLookupRequest; - -struct ipTuple -{ - ap_uint<32> ip_address; - ap_uint<16> ip_port; -}; - -struct mmCmd -{ - ap_uint<23> bbt; - ap_uint<1> type; - ap_uint<6> dsa; - ap_uint<1> eof; - ap_uint<1> drr; - ap_uint<32> saddr; - ap_uint<4> tag; - ap_uint<4> rsvd; - mmCmd() {} - mmCmd(ap_uint<32> addr, ap_uint<16> len) - :bbt(len), type(1), dsa(0), eof(1), drr(1), saddr(addr), tag(0), rsvd(0) {} - /*mm_cmd(ap_uint<32> addr, ap_uint<16> len, ap_uint<1> last) - :bbt(len), type(1), dsa(0), eof(last), drr(1), saddr(addr), tag(0), rsvd(0) {}*/ - /*mm_cmd(ap_uint<32> addr, ap_uint<16> len, ap_uint<4> dsa) - :bbt(len), type(1), dsa(dsa), eof(1), drr(1), saddr(addr), tag(0), rsvd(0) {}*/ -}; - -struct mmStatus -{ - ap_uint<4> tag; - ap_uint<1> interr; - ap_uint<1> decerr; - ap_uint<1> slverr; - ap_uint<1> okay; -}; - -//TODO is this required?? -struct mm_ibtt_status -{ - ap_uint<4> tag; - ap_uint<1> interr; - ap_uint<1> decerr; - ap_uint<1> slverr; - ap_uint<1> okay; - ap_uint<22> brc_vd; - ap_uint<1> eop; -}; - -struct openStatus -{ - ap_uint<16> sessionID; - ap_uint<8> success; - ap_uint<32> ip; - ap_uint<16> port; - openStatus() {} - openStatus(ap_uint<16> id, ap_uint<8> success) - :sessionID(id), success(success), ip(0), port(0) {} - openStatus(ap_uint<16> id, ap_uint<8> success, ap_uint<32> ip, ap_uint<16> port) - :sessionID(id), success(success), ip(ip), port(port) {} -}; - -struct appNotification -{ - ap_uint<16> sessionID; - ap_uint<16> length; - ap_uint<32> ipAddress; - ap_uint<16> dstPort; - bool closed; - appNotification() {} - appNotification(ap_uint<16> id, ap_uint<16> len, ap_uint<32> addr, ap_uint<16> port) - :sessionID(id), length(len), ipAddress(addr), dstPort(port), closed(false) {} - appNotification(ap_uint<16> id, bool closed) - :sessionID(id), length(0), ipAddress(0), dstPort(0), closed(closed) {} - appNotification(ap_uint<16> id, ap_uint<32> addr, ap_uint<16> port, bool closed) - :sessionID(id), length(0), ipAddress(addr), dstPort(port), closed(closed) {} - appNotification(ap_uint<16> id, ap_uint<16> len, ap_uint<32> addr, ap_uint<16> port, bool closed) - :sessionID(id), length(len), ipAddress(addr), dstPort(port), closed(closed) {} -}; - - -struct appReadRequest -{ - ap_uint<16> sessionID; - //ap_uint<16> address; - ap_uint<16> length; - appReadRequest() {} - appReadRequest(ap_uint<16> id, ap_uint<16> len) - :sessionID(id), length(len) {} -}; - -struct appTxMeta -{ - ap_uint<16> sessionID; - ap_uint<16> length; - appTxMeta() {} - appTxMeta(ap_uint<16> id, ap_uint<16> len) - :sessionID(id), length(len) {} -}; - -struct appTxRsp -{ - ap_uint<16> sessionID; - ap_uint<16> length; - ap_uint<30> remaining_space; - ap_uint<2> error; - appTxRsp() {} - appTxRsp(ap_uint<16> id, ap_uint<16> len, ap_uint<30> rem_space, ap_uint<2> err) - :sessionID(id), length(len), remaining_space(rem_space), error(err) {} -}; - - -template -void toe( // Data & Memory Interface - hls::stream >& ipRxData, - hls::stream& rxBufferWriteStatus, - hls::stream& txBufferWriteStatus, - hls::stream >& rxBufferReadData, - hls::stream >& txBufferReadData, - hls::stream >& ipTxData, - hls::stream& rxBufferWriteCmd, - hls::stream& rxBufferReadCmd, - hls::stream& txBufferWriteCmd, - hls::stream& txBufferReadCmd, - hls::stream >& rxBufferWriteData, - hls::stream >& txBufferWriteData, - // SmartCam Interface - hls::stream& sessionLookup_rsp, - hls::stream& sessionUpdate_rsp, - //hls::stream >& readFinSessionId, - hls::stream& sessionLookup_req, - hls::stream& sessionUpdate_req, - //hls::stream& sessionInsert_req, - //hls::stream& sessionDelete_req, - //hls::stream >& writeNewSessionId, - // Application Interface - hls::stream >& listenPortReq, - // This is disabled for the time being, due to complexity concerns - //hls::stream >& appClosePortIn, - hls::stream& rxDataReq, - hls::stream& openConnReq, - hls::stream >& closeConnReq, - hls::stream& txDataReqMeta, - hls::stream >& txDataReq, - - hls::stream& listenPortRsp, - hls::stream& notification, - hls::stream >& rxDataRspMeta, - hls::stream >& rxDataRsp, - hls::stream& openConnRsp, - hls::stream& txDataRsp, - //IP Address Input - ap_uint<32> myIpAddress, - //statistic - ap_uint<16>& regSessionCount); - - -#endif diff --git a/examples_hw/apps/tcp_iperf/perf_tcp_c0_0.svh b/examples_hw/apps/tcp_iperf/perf_tcp_c0_0.svh deleted file mode 100644 index 3184cb2fd..000000000 --- a/examples_hw/apps/tcp_iperf/perf_tcp_c0_0.svh +++ /dev/null @@ -1,56 +0,0 @@ -// Tie-off -always_comb axis_host_sink.tie_off_s(); -always_comb axis_host_src.tie_off_m(); - -// UL -send_recv_role #( - .C_S_AXI_CONTROL_DATA_WIDTH(AXIL_DATA_BITS), - .C_S_AXI_CONTROL_ADDR_WIDTH(AXI_ADDR_BITS) -)user_role ( - .ap_clk(aclk), - .ap_rst_n(aresetn), - - .axi_ctrl (axi_ctrl), - - .m_axis_tcp_listen_port_tvalid (tcp_0_listen_req.valid), - .m_axis_tcp_listen_port_tready (tcp_0_listen_req.ready), - .m_axis_tcp_listen_port_tdata (tcp_0_listen_req.data), - .s_axis_tcp_port_status_tvalid (tcp_0_listen_rsp.valid), - .s_axis_tcp_port_status_tready (tcp_0_listen_rsp.ready), - .s_axis_tcp_port_status_tdata (tcp_0_listen_rsp.data), - .m_axis_tcp_open_connection_tvalid (tcp_0_open_req.valid), - .m_axis_tcp_open_connection_tready (tcp_0_open_req.ready), - .m_axis_tcp_open_connection_tdata (tcp_0_open_req.data), - .s_axis_tcp_open_status_tvalid (tcp_0_open_rsp.valid), - .s_axis_tcp_open_status_tready (tcp_0_open_rsp.ready), - .s_axis_tcp_open_status_tdata (tcp_0_open_rsp.data), - .m_axis_tcp_close_connection_tvalid(tcp_0_close_req.valid), - .m_axis_tcp_close_connection_tready(tcp_0_close_req.ready), - .m_axis_tcp_close_connection_tdata (tcp_0_close_req.data), - .s_axis_tcp_notification_tvalid (tcp_0_notify.valid), - .s_axis_tcp_notification_tready (tcp_0_notify.ready), - .s_axis_tcp_notification_tdata (tcp_0_notify.data), - .m_axis_tcp_read_pkg_tvalid (tcp_0_rd_pkg.valid), - .m_axis_tcp_read_pkg_tready (tcp_0_rd_pkg.ready), - .m_axis_tcp_read_pkg_tdata (tcp_0_rd_pkg.data), - .s_axis_tcp_rx_meta_tvalid (tcp_0_rx_meta.valid), - .s_axis_tcp_rx_meta_tready (tcp_0_rx_meta.ready), - .s_axis_tcp_rx_meta_tdata (tcp_0_rx_meta.data), - .s_axis_tcp_rx_data_tvalid (axis_tcp_0_sink.tvalid), - .s_axis_tcp_rx_data_tready (axis_tcp_0_sink.tready), - .s_axis_tcp_rx_data_tdata (axis_tcp_0_sink.tdata), - .s_axis_tcp_rx_data_tkeep (axis_tcp_0_sink.tkeep), - .s_axis_tcp_rx_data_tlast (axis_tcp_0_sink.tlast), - .m_axis_tcp_tx_meta_tvalid (tcp_0_tx_meta.valid), - .m_axis_tcp_tx_meta_tready (tcp_0_tx_meta.ready), - .m_axis_tcp_tx_meta_tdata (tcp_0_tx_meta.data), - .m_axis_tcp_tx_data_tvalid (axis_tcp_0_src.tvalid), - .m_axis_tcp_tx_data_tready (axis_tcp_0_src.tready), - .m_axis_tcp_tx_data_tdata (axis_tcp_0_src.tdata), - .m_axis_tcp_tx_data_tkeep (axis_tcp_0_src.tkeep), - .m_axis_tcp_tx_data_tlast (axis_tcp_0_src.tlast), - .s_axis_tcp_tx_status_tvalid (tcp_0_tx_stat.valid), - .s_axis_tcp_tx_status_tready (tcp_0_tx_stat.ready), - .s_axis_tcp_tx_status_tdata (tcp_0_tx_stat.data) - -); \ No newline at end of file diff --git a/examples_hw/apps/tcp_iperf/send_recv.sv b/examples_hw/apps/tcp_iperf/send_recv.sv deleted file mode 100644 index 51d7c2a4a..000000000 --- a/examples_hw/apps/tcp_iperf/send_recv.sv +++ /dev/null @@ -1,116 +0,0 @@ -`timescale 1ns / 1ps - -`include "axi_macros.svh" -`include "lynx_macros.svh" - -import lynxTypes::*; - -/** - * User logic - * - */ -module design_user_logic_0 ( - // AXI4L CONTROL - // Slave control. Utilize this interface for any kind of CSR implementation. - AXI4L.s axi_ctrl, - - // TCP/IP - metaIntf.m tcp_listen_req, - metaIntf.s tcp_listen_rsp, - metaIntf.m tcp_open_req, - metaIntf.s tcp_open_rsp, - metaIntf.m tcp_close_req, - metaIntf.s tcp_notify, - metaIntf.m tcp_rd_package, - metaIntf.s tcp_rx_meta, - metaIntf.m tcp_tx_meta, - metaIntf.s tcp_tx_stat, - - // AXI4S TCP/IP data - AXI4S.m axis_tcp_src, - AXI4S.s axis_tcp_sink, - - // AXI4S host data - // Host streams. - AXI4S.m axis_host_src, - AXI4S.s axis_host_sink, - - // Clock and reset - input wire aclk, - input wire[0:0] aresetn -); - -/* -- Tie-off unused interfaces and signals ----------------------------- */ -// always_comb axi_ctrl.tie_off_s(); -// always_comb tcp_listen_req.tie_off_m(); -// always_comb tcp_listen_rsp.tie_off_s(); -// always_comb tcp_open_req.tie_off_m(); -// always_comb tcp_open_rsp.tie_off_s(); -// always_comb tcp_close_req.tie_off_m(); -// always_comb tcp_notify.tie_off_s(); -// always_comb tcp_rd_package.tie_off_m(); -// always_comb tcp_rx_meta.tie_off_s(); -// always_comb tcp_tx_meta.tie_off_m(); -// always_comb tcp_tx_stat.tie_off_s(); -// always_comb axis_tcp_src.tie_off_m(); -// always_comb axis_tcp_sink.tie_off_s(); -always_comb axis_host_src.tie_off_m(); -always_comb axis_host_sink.tie_off_s(); - -/* -- USER LOGIC -------------------------------------------------------- */ - - -send_recv_role #( - .C_S_AXI_CONTROL_DATA_WIDTH(AXIL_DATA_BITS), - .C_S_AXI_CONTROL_ADDR_WIDTH(AXI_ADDR_BITS) -)user_role ( - .ap_clk(aclk), - .ap_rst_n(aresetn), - - .axi_ctrl (axi_ctrl), - - .m_axis_tcp_listen_port_tvalid (tcp_listen_req.valid), - .m_axis_tcp_listen_port_tready (tcp_listen_req.ready), - .m_axis_tcp_listen_port_tdata (tcp_listen_req.data), - .s_axis_tcp_port_status_tvalid (tcp_listen_rsp.valid), - .s_axis_tcp_port_status_tready (tcp_listen_rsp.ready), - .s_axis_tcp_port_status_tdata (tcp_listen_rsp.data), - .m_axis_tcp_open_connection_tvalid (tcp_open_req.valid), - .m_axis_tcp_open_connection_tready (tcp_open_req.ready), - .m_axis_tcp_open_connection_tdata (tcp_open_req.data), - .s_axis_tcp_open_status_tvalid (tcp_open_rsp.valid), - .s_axis_tcp_open_status_tready (tcp_open_rsp.ready), - .s_axis_tcp_open_status_tdata (tcp_open_rsp.data), - .m_axis_tcp_close_connection_tvalid(tcp_close_req.valid), - .m_axis_tcp_close_connection_tready(tcp_close_req.ready), - .m_axis_tcp_close_connection_tdata (tcp_close_req.data), - .s_axis_tcp_notification_tvalid (tcp_notify.valid), - .s_axis_tcp_notification_tready (tcp_notify.ready), - .s_axis_tcp_notification_tdata (tcp_notify.data), - .m_axis_tcp_read_pkg_tvalid (tcp_rd_package.valid), - .m_axis_tcp_read_pkg_tready (tcp_rd_package.ready), - .m_axis_tcp_read_pkg_tdata (tcp_rd_package.data), - .s_axis_tcp_rx_meta_tvalid (tcp_rx_meta.valid), - .s_axis_tcp_rx_meta_tready (tcp_rx_meta.ready), - .s_axis_tcp_rx_meta_tdata (tcp_rx_meta.data), - .s_axis_tcp_rx_data_tvalid (axis_tcp_sink.tvalid), - .s_axis_tcp_rx_data_tready (axis_tcp_sink.tready), - .s_axis_tcp_rx_data_tdata (axis_tcp_sink.tdata), - .s_axis_tcp_rx_data_tkeep (axis_tcp_sink.tkeep), - .s_axis_tcp_rx_data_tlast (axis_tcp_sink.tlast), - .m_axis_tcp_tx_meta_tvalid (tcp_tx_meta.valid), - .m_axis_tcp_tx_meta_tready (tcp_tx_meta.ready), - .m_axis_tcp_tx_meta_tdata (tcp_tx_meta.data), - .m_axis_tcp_tx_data_tvalid (axis_tcp_src.tvalid), - .m_axis_tcp_tx_data_tready (axis_tcp_src.tready), - .m_axis_tcp_tx_data_tdata (axis_tcp_src.tdata), - .m_axis_tcp_tx_data_tkeep (axis_tcp_src.tkeep), - .m_axis_tcp_tx_data_tlast (axis_tcp_src.tlast), - .s_axis_tcp_tx_status_tvalid (tcp_tx_stat.valid), - .s_axis_tcp_tx_status_tready (tcp_tx_stat.ready), - .s_axis_tcp_tx_status_tdata (tcp_tx_stat.data) - -); - -endmodule - diff --git a/examples_hw/apps/tcp_iperf/send_recv_role.sv b/examples_hw/apps/tcp_iperf/send_recv_role.sv deleted file mode 100644 index f900392ed..000000000 --- a/examples_hw/apps/tcp_iperf/send_recv_role.sv +++ /dev/null @@ -1,630 +0,0 @@ -/* - * Copyright (c) 2019, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ -`timescale 1ns / 1ps -`default_nettype none - -import lynxTypes::*; - -module send_recv_role - #( - parameter integer C_S_AXI_CONTROL_DATA_WIDTH = 32, - parameter integer C_S_AXI_CONTROL_ADDR_WIDTH = 12, - parameter integer NETWORK_STACK_WIDTH=512 -) -( - input wire ap_clk, - input wire ap_rst_n, - - AXI4L.s axi_ctrl, - - /* NETWORK - TCP/IP INTERFACE */ - //Network TCP/IP - output wire m_axis_tcp_listen_port_tvalid , - input wire m_axis_tcp_listen_port_tready , - output wire [16-1:0] m_axis_tcp_listen_port_tdata , - - input wire s_axis_tcp_port_status_tvalid , - output wire s_axis_tcp_port_status_tready , - input wire [8-1:0] s_axis_tcp_port_status_tdata , - - output wire m_axis_tcp_open_connection_tvalid , - input wire m_axis_tcp_open_connection_tready , - output wire [48-1:0] m_axis_tcp_open_connection_tdata , - - input wire s_axis_tcp_open_status_tvalid , - output wire s_axis_tcp_open_status_tready , - input wire [128-1:0] s_axis_tcp_open_status_tdata , - - output wire m_axis_tcp_close_connection_tvalid , - input wire m_axis_tcp_close_connection_tready , - output wire [16-1:0] m_axis_tcp_close_connection_tdata , - - input wire s_axis_tcp_notification_tvalid , - output wire s_axis_tcp_notification_tready , - input wire [88-1:0] s_axis_tcp_notification_tdata , - - output wire m_axis_tcp_read_pkg_tvalid , - input wire m_axis_tcp_read_pkg_tready , - output wire [32-1:0] m_axis_tcp_read_pkg_tdata , - - input wire s_axis_tcp_rx_meta_tvalid , - output wire s_axis_tcp_rx_meta_tready , - input wire [16-1:0] s_axis_tcp_rx_meta_tdata , - - input wire s_axis_tcp_rx_data_tvalid , - output wire s_axis_tcp_rx_data_tready , - input wire [NETWORK_STACK_WIDTH-1:0] s_axis_tcp_rx_data_tdata , - input wire [NETWORK_STACK_WIDTH/8-1:0] s_axis_tcp_rx_data_tkeep , - input wire s_axis_tcp_rx_data_tlast , - - output wire m_axis_tcp_tx_meta_tvalid , - input wire m_axis_tcp_tx_meta_tready , - output wire [32-1:0] m_axis_tcp_tx_meta_tdata , - - output wire m_axis_tcp_tx_data_tvalid , - input wire m_axis_tcp_tx_data_tready , - output wire [NETWORK_STACK_WIDTH-1:0] m_axis_tcp_tx_data_tdata , - output wire [NETWORK_STACK_WIDTH/8-1:0] m_axis_tcp_tx_data_tkeep , - output wire m_axis_tcp_tx_data_tlast , - - input wire s_axis_tcp_tx_status_tvalid , - output wire s_axis_tcp_tx_status_tready , - input wire [64-1:0] s_axis_tcp_tx_status_tdata - - - -); - -wire ap_start, ap_done, ap_ready, ap_idle, interrupt; -wire [63:0] axi00_ptr0; -logic ap_start_pulse; -logic ap_start_r = 1'b0; -logic ap_idle_r = 1'b1; - -logic runExperiment; -logic finishExperiment; -logic runTx, sentRunTx; -logic openConnectionSuccess; - -// create pulse when ap_start transitions to 1 -always @(posedge ap_clk) begin - begin - ap_start_r <= ap_start; - end -end - -assign ap_start_pulse = ap_start & ~ap_start_r; -assign runExperiment = ap_start_pulse; - -// ap_idle is asserted when done is asserted, it is de-asserted when ap_start_pulse -// is asserted -always @(posedge ap_clk) begin - if (~ap_rst_n) begin - ap_idle_r <= 1'b1; - end - else begin - ap_idle_r <= ap_done ? 1'b1 : - ap_start_pulse ? 1'b0 : ap_idle; - end -end - -assign ap_idle = ap_idle_r; - -// Done logic - -assign ap_done = finishExperiment; - -// Ready Logic (non-pipelined case) -assign ap_ready = ap_done; - - -/* - * TCP/IP Benchmark - */ - - - -logic[7:0] listenCounter; -logic[7:0] openReqCounter; -logic[7:0] closeReqCounter; -logic[7:0] successOpenCounter; -logic[7:0] openStatusCounter; -logic[63:0] execution_cycles; -logic[63:0] openCon_cycles; -logic[31:0] connections; -logic running; - -wire [31:0] useConn, useIpAddr, pkgWordCount, basePort ,baseIpAddress; - -logic[31:0] timeInSeconds, transferSize, isServer; -logic[63:0] timeInCycles; - -logic [15:0] UseIpAddrReg; -logic [15:0] useConnReg; -logic [15:0] regBasePort; -logic [15:0] pkgWordCountReg; -logic [31:0] baseIpAddressReg; - -reg[63:0] consumed_bytes; -reg[63:0] produced_bytes; - -reg [47:0] rdRqstByteCnt; -reg [31:0] rcvPktCnt; - -reg [31:0] tx_meta_down; -reg [31:0] tx_status_down; -reg [31:0] tx_data_down; - -logic [15:0] sessionID; - - - - -// send open connection request when it is runExperiment and not server -assign m_axis_tcp_open_connection_tvalid = (!isServer) & runExperiment; -assign m_axis_tcp_open_connection_tdata = {regBasePort[15:0], baseIpAddress}; - -assign m_axis_tcp_close_connection_tvalid = 1'b0; -assign s_axis_tcp_open_status_tready = 1'b1; - -always @ (posedge ap_clk) begin - if (~ap_rst_n) begin - baseIpAddressReg <= '0; - regBasePort <= '0; - pkgWordCountReg <= '0; - UseIpAddrReg <= '0; - useConnReg <= '0; - end - else begin - baseIpAddressReg <= baseIpAddress ; - regBasePort <= basePort ; - pkgWordCountReg <= pkgWordCount; - UseIpAddrReg <= useIpAddr; - useConnReg <= useConn ; - end - -end - -always @(posedge ap_clk) begin - if (~ap_rst_n) begin - running <= 1'b0; - listenCounter <= '0; - openReqCounter <= '0; - closeReqCounter <= '0; - successOpenCounter <= '0; - connections <= '0; - openStatusCounter <= '0; - finishExperiment <= 1'b0; - rdRqstByteCnt <= '0; - rcvPktCnt <= '0; - tx_meta_down <= '0; - tx_data_down <= '0; - tx_status_down <= '0; - runTx <= 1'b0; - sentRunTx <= 1'b0; - sessionID <= 0; - openCon_cycles <= '0; - end - else begin - if (runExperiment) begin - finishExperiment <= 1'b0; - running <= 1'b1; - execution_cycles <= '0; - closeReqCounter <= '0; - openReqCounter <= '0; - successOpenCounter <= '0; - openStatusCounter <= '0; - rdRqstByteCnt <= '0; - rcvPktCnt <= '0; - tx_meta_down <= '0; - tx_data_down <= '0; - tx_status_down <= '0; - runTx <= 1'b0; - sentRunTx <= 1'b0; - sessionID <= 0; - openCon_cycles <= '0; - end - - if (running & isServer) begin - if (s_axis_tcp_rx_meta_tvalid & s_axis_tcp_rx_meta_tready) begin - sessionID <= s_axis_tcp_rx_meta_tdata; - end - end - else if (running & !isServer) begin - if (s_axis_tcp_open_status_tvalid & s_axis_tcp_open_status_tready & s_axis_tcp_open_status_tdata[16] ) begin - sessionID <= s_axis_tcp_open_status_tdata[15:0]; - end - end - - // if server node, run tx when receive expected amount of bytes - if (running) begin - if (isServer) begin - runTx <= (consumed_bytes >= transferSize) & !sentRunTx; - end - // if not server node, run tx when receiving open connection status - else begin - runTx <= s_axis_tcp_open_status_tvalid & s_axis_tcp_open_status_tready & s_axis_tcp_open_status_tdata[16] & !sentRunTx; - end - end - - if (runTx) begin - sentRunTx <= 1'b1; - end - - if (running) begin - execution_cycles <= execution_cycles + 1; - end - if (m_axis_tcp_listen_port_tvalid && m_axis_tcp_listen_port_tready) begin - listenCounter <= listenCounter +1; - end - if (m_axis_tcp_close_connection_tvalid && m_axis_tcp_close_connection_tready) begin - closeReqCounter <= closeReqCounter + 1; - end - - if ( running & (consumed_bytes >= transferSize) & (produced_bytes >= transferSize) ) begin - running <= 1'b0; - finishExperiment <= 1'b1; - end - - if (m_axis_tcp_open_connection_tvalid && m_axis_tcp_open_connection_tready) begin - openReqCounter <= openReqCounter + 1; - end - if (s_axis_tcp_open_status_tvalid & s_axis_tcp_open_status_tready ) begin - openStatusCounter <= openStatusCounter + 1'b1; - if (s_axis_tcp_open_status_tdata[16]) begin - successOpenCounter <= successOpenCounter + 1'b1; - end - end - - if (running & successOpenCounter== 0 ) begin - openCon_cycles <= openCon_cycles + 1; - end - - if (m_axis_tcp_read_pkg_tvalid & m_axis_tcp_read_pkg_tready) begin - rdRqstByteCnt <= rdRqstByteCnt + m_axis_tcp_read_pkg_tdata[31:16]; - end - - if (s_axis_tcp_rx_data_tvalid & s_axis_tcp_rx_data_tready & s_axis_tcp_rx_data_tlast) begin - rcvPktCnt <= rcvPktCnt + 1'b1; - end - - if (m_axis_tcp_tx_meta_tvalid & ~m_axis_tcp_tx_meta_tready) begin - tx_meta_down <= tx_meta_down + 1'b1; - end - - if (s_axis_tcp_tx_status_tvalid & ~s_axis_tcp_tx_status_tready) begin - tx_status_down <= tx_status_down + 1'b1; - end - - if (m_axis_tcp_tx_data_tvalid & ~m_axis_tcp_tx_data_tready) begin - tx_data_down <= tx_data_down + 1'b1; - end - - connections <= {listenCounter,openReqCounter,successOpenCounter,closeReqCounter}; - end -end - -`ifdef VITIS_HLS -send_recv_ip send_recv ( - // .m_axis_close_connection_V_V_TVALID(m_axis_tcp_close_connection_tvalid), // output wire m_axis_close_connection_TVALID - // .m_axis_close_connection_V_V_TREADY(m_axis_tcp_close_connection_tready), // input wire m_axis_close_connection_TREADY - // .m_axis_close_connection_V_V_TDATA(m_axis_tcp_close_connection_tdata), // output wire [15 : 0] m_axis_close_connection_TDATA - .m_axis_listen_port_TVALID(m_axis_tcp_listen_port_tvalid), // output wire m_axis_listen_port_TVALID - .m_axis_listen_port_TREADY(m_axis_tcp_listen_port_tready), // input wire m_axis_listen_port_TREADY - .m_axis_listen_port_TDATA(m_axis_tcp_listen_port_tdata), // output wire [15 : 0] m_axis_listen_port_TDATA - // .m_axis_open_connection_V_TVALID(m_axis_tcp_open_connection_tvalid), // output wire m_axis_open_connection_TVALID - // .m_axis_open_connection_V_TREADY(m_axis_tcp_open_connection_tready), // input wire m_axis_open_connection_TREADY - // .m_axis_open_connection_V_TDATA(m_axis_tcp_open_connection_tdata), // output wire [47 : 0] m_axis_open_connection_TDATA - .m_axis_read_package_TVALID(m_axis_tcp_read_pkg_tvalid), // output wire m_axis_read_package_TVALID - .m_axis_read_package_TREADY(m_axis_tcp_read_pkg_tready), // input wire m_axis_read_package_TREADY - .m_axis_read_package_TDATA(m_axis_tcp_read_pkg_tdata), // output wire [31 : 0] m_axis_read_package_TDATA - .m_axis_tx_data_TVALID(m_axis_tcp_tx_data_tvalid), // output wire m_axis_tx_data_TVALID - .m_axis_tx_data_TREADY(m_axis_tcp_tx_data_tready), // input wire m_axis_tx_data_TREADY - .m_axis_tx_data_TDATA(m_axis_tcp_tx_data_tdata), // output wire [63 : 0] m_axis_tx_data_TDATA - .m_axis_tx_data_TKEEP(m_axis_tcp_tx_data_tkeep), // output wire [7 : 0] m_axis_tx_data_TKEEP - .m_axis_tx_data_TLAST(m_axis_tcp_tx_data_tlast), // output wire [0 : 0] m_axis_tx_data_TLAST - .m_axis_tx_metadata_TVALID(m_axis_tcp_tx_meta_tvalid), // output wire m_axis_tx_metadata_TVALID - .m_axis_tx_metadata_TREADY(m_axis_tcp_tx_meta_tready), // input wire m_axis_tx_metadata_TREADY - .m_axis_tx_metadata_TDATA(m_axis_tcp_tx_meta_tdata), // output wire [15 : 0] m_axis_tx_metadata_TDATA - .s_axis_listen_port_status_TVALID(s_axis_tcp_port_status_tvalid), // input wire s_axis_listen_port_status_TVALID - .s_axis_listen_port_status_TREADY(s_axis_tcp_port_status_tready), // output wire s_axis_listen_port_status_TREADY - .s_axis_listen_port_status_TDATA(s_axis_tcp_port_status_tdata), // input wire [7 : 0] s_axis_listen_port_status_TDATA - .s_axis_notifications_TVALID(s_axis_tcp_notification_tvalid), // input wire s_axis_notifications_TVALID - .s_axis_notifications_TREADY(s_axis_tcp_notification_tready), // output wire s_axis_notifications_TREADY - .s_axis_notifications_TDATA(s_axis_tcp_notification_tdata), // input wire [87 : 0] s_axis_notifications_TDATA - // .s_axis_open_status_TVALID(s_axis_tcp_open_status_tvalid), // input wire s_axis_open_status_TVALID - // .s_axis_open_status_TREADY(s_axis_tcp_open_status_tready), // output wire s_axis_open_status_TREADY - // .s_axis_open_status_TDATA(s_axis_tcp_open_status_tdata), // input wire [23 : 0] s_axis_open_status_TDATA - .s_axis_rx_data_TVALID(s_axis_tcp_rx_data_tvalid), // input wire s_axis_rx_data_TVALID - .s_axis_rx_data_TREADY(s_axis_tcp_rx_data_tready), // output wire s_axis_rx_data_TREADY - .s_axis_rx_data_TDATA(s_axis_tcp_rx_data_tdata), // input wire [63 : 0] s_axis_rx_data_TDATA - .s_axis_rx_data_TKEEP(s_axis_tcp_rx_data_tkeep), // input wire [7 : 0] s_axis_rx_data_TKEEP - .s_axis_rx_data_TLAST(s_axis_tcp_rx_data_tlast), // input wire [0 : 0] s_axis_rx_data_TLAST - .s_axis_rx_metadata_TVALID(s_axis_tcp_rx_meta_tvalid), // input wire s_axis_rx_metadata_TVALID - .s_axis_rx_metadata_TREADY(s_axis_tcp_rx_meta_tready), // output wire s_axis_rx_metadata_TREADY - .s_axis_rx_metadata_TDATA(s_axis_tcp_rx_meta_tdata), // input wire [15 : 0] s_axis_rx_metadata_TDATA - .s_axis_tx_status_TVALID(s_axis_tcp_tx_status_tvalid), // input wire s_axis_tx_status_TVALID - .s_axis_tx_status_TREADY(s_axis_tcp_tx_status_tready), // output wire s_axis_tx_status_TREADY - .s_axis_tx_status_TDATA(s_axis_tcp_tx_status_tdata), // input wire [23 : 0] s_axis_tx_status_TDATA - - //Client only - .runTx(runTx), - .transferSize(transferSize), // input wire [0 : 0] transferSize_V - .sessionID(sessionID), // input wire [7 : 0] sessionID_V - .pkgWordCount(pkgWordCountReg), // input wire [7 : 0] pkgWordCount_V - .ap_clk(ap_clk), // input wire aclk - .ap_rst_n(ap_rst_n) // input wire aresetn - ); -`else -send_recv_ip send_recv ( - // .m_axis_close_connection_V_V_TVALID(m_axis_tcp_close_connection_tvalid), // output wire m_axis_close_connection_TVALID - // .m_axis_close_connection_V_V_TREADY(m_axis_tcp_close_connection_tready), // input wire m_axis_close_connection_TREADY - // .m_axis_close_connection_V_V_TDATA(m_axis_tcp_close_connection_tdata), // output wire [15 : 0] m_axis_close_connection_TDATA - .m_axis_listen_port_V_V_TVALID(m_axis_tcp_listen_port_tvalid), // output wire m_axis_listen_port_TVALID - .m_axis_listen_port_V_V_TREADY(m_axis_tcp_listen_port_tready), // input wire m_axis_listen_port_TREADY - .m_axis_listen_port_V_V_TDATA(m_axis_tcp_listen_port_tdata), // output wire [15 : 0] m_axis_listen_port_TDATA - // .m_axis_open_connection_V_TVALID(m_axis_tcp_open_connection_tvalid), // output wire m_axis_open_connection_TVALID - // .m_axis_open_connection_V_TREADY(m_axis_tcp_open_connection_tready), // input wire m_axis_open_connection_TREADY - // .m_axis_open_connection_V_TDATA(m_axis_tcp_open_connection_tdata), // output wire [47 : 0] m_axis_open_connection_TDATA - .m_axis_read_package_V_TVALID(m_axis_tcp_read_pkg_tvalid), // output wire m_axis_read_package_TVALID - .m_axis_read_package_V_TREADY(m_axis_tcp_read_pkg_tready), // input wire m_axis_read_package_TREADY - .m_axis_read_package_V_TDATA(m_axis_tcp_read_pkg_tdata), // output wire [31 : 0] m_axis_read_package_TDATA - .m_axis_tx_data_TVALID(m_axis_tcp_tx_data_tvalid), // output wire m_axis_tx_data_TVALID - .m_axis_tx_data_TREADY(m_axis_tcp_tx_data_tready), // input wire m_axis_tx_data_TREADY - .m_axis_tx_data_TDATA(m_axis_tcp_tx_data_tdata), // output wire [63 : 0] m_axis_tx_data_TDATA - .m_axis_tx_data_TKEEP(m_axis_tcp_tx_data_tkeep), // output wire [7 : 0] m_axis_tx_data_TKEEP - .m_axis_tx_data_TLAST(m_axis_tcp_tx_data_tlast), // output wire [0 : 0] m_axis_tx_data_TLAST - .m_axis_tx_metadata_V_TVALID(m_axis_tcp_tx_meta_tvalid), // output wire m_axis_tx_metadata_TVALID - .m_axis_tx_metadata_V_TREADY(m_axis_tcp_tx_meta_tready), // input wire m_axis_tx_metadata_TREADY - .m_axis_tx_metadata_V_TDATA(m_axis_tcp_tx_meta_tdata), // output wire [15 : 0] m_axis_tx_metadata_TDATA - .s_axis_listen_port_status_V_TVALID(s_axis_tcp_port_status_tvalid), // input wire s_axis_listen_port_status_TVALID - .s_axis_listen_port_status_V_TREADY(s_axis_tcp_port_status_tready), // output wire s_axis_listen_port_status_TREADY - .s_axis_listen_port_status_V_TDATA(s_axis_tcp_port_status_tdata), // input wire [7 : 0] s_axis_listen_port_status_TDATA - .s_axis_notifications_V_TVALID(s_axis_tcp_notification_tvalid), // input wire s_axis_notifications_TVALID - .s_axis_notifications_V_TREADY(s_axis_tcp_notification_tready), // output wire s_axis_notifications_TREADY - .s_axis_notifications_V_TDATA(s_axis_tcp_notification_tdata), // input wire [87 : 0] s_axis_notifications_TDATA - // .s_axis_open_status_V_TVALID(s_axis_tcp_open_status_tvalid), // input wire s_axis_open_status_TVALID - // .s_axis_open_status_V_TREADY(s_axis_tcp_open_status_tready), // output wire s_axis_open_status_TREADY - // .s_axis_open_status_V_TDATA(s_axis_tcp_open_status_tdata), // input wire [23 : 0] s_axis_open_status_TDATA - .s_axis_rx_data_TVALID(s_axis_tcp_rx_data_tvalid), // input wire s_axis_rx_data_TVALID - .s_axis_rx_data_TREADY(s_axis_tcp_rx_data_tready), // output wire s_axis_rx_data_TREADY - .s_axis_rx_data_TDATA(s_axis_tcp_rx_data_tdata), // input wire [63 : 0] s_axis_rx_data_TDATA - .s_axis_rx_data_TKEEP(s_axis_tcp_rx_data_tkeep), // input wire [7 : 0] s_axis_rx_data_TKEEP - .s_axis_rx_data_TLAST(s_axis_tcp_rx_data_tlast), // input wire [0 : 0] s_axis_rx_data_TLAST - .s_axis_rx_metadata_V_V_TVALID(s_axis_tcp_rx_meta_tvalid), // input wire s_axis_rx_metadata_TVALID - .s_axis_rx_metadata_V_V_TREADY(s_axis_tcp_rx_meta_tready), // output wire s_axis_rx_metadata_TREADY - .s_axis_rx_metadata_V_V_TDATA(s_axis_tcp_rx_meta_tdata), // input wire [15 : 0] s_axis_rx_metadata_TDATA - .s_axis_tx_status_V_TVALID(s_axis_tcp_tx_status_tvalid), // input wire s_axis_tx_status_TVALID - .s_axis_tx_status_V_TREADY(s_axis_tcp_tx_status_tready), // output wire s_axis_tx_status_TREADY - .s_axis_tx_status_V_TDATA(s_axis_tcp_tx_status_tdata), // input wire [23 : 0] s_axis_tx_status_TDATA - - //Client only - .runTx_V(runTx), - .transferSize_V(transferSize), // input wire [0 : 0] transferSize_V - .sessionID_V(sessionID), // input wire [7 : 0] sessionID_V - .pkgWordCount_V(pkgWordCountReg), // input wire [7 : 0] pkgWordCount_V - .ap_clk(ap_clk), // input wire aclk - .ap_rst_n(ap_rst_n) // input wire aresetn - ); -`endif - -/* - * Role Controller - */ - -// AXI4-Lite slave interface - -send_recv_slave send_recv_slave_inst ( - .aclk (ap_clk), - .aresetn (ap_rst_n), - .axi_ctrl (axi_ctrl), - .ap_start (ap_start), - .ap_done (ap_done), - .useConn (useConn), - .useIpAddr (useIpAddr), - .pkgWordCount (pkgWordCount), - .basePort (basePort), - .baseIpAddress(baseIpAddress), - .transferSize (transferSize), - .isServer (isServer), - .timeInSeconds(timeInSeconds), - .timeInCycles (timeInCycles), - .execution_cycles(execution_cycles), - .consumed_bytes (consumed_bytes), - .produced_bytes (produced_bytes), - .openCon_cycles (openCon_cycles) -); - - -/* - * Statistics - */ - - -always @(posedge ap_clk) begin - if (~ap_rst_n) begin - consumed_bytes <= '0; - produced_bytes <= '0; - end - else begin - if (ap_start_pulse) begin - consumed_bytes <= '0; - produced_bytes <= '0; - end - - if (s_axis_tcp_rx_data_tvalid && s_axis_tcp_rx_data_tready) begin - case (s_axis_tcp_rx_data_tkeep) - 64'h1: consumed_bytes <= consumed_bytes + 1; - 64'h3: consumed_bytes <= consumed_bytes + 2; - 64'h7: consumed_bytes <= consumed_bytes + 4; - 64'hF: consumed_bytes <= consumed_bytes + 4; - 64'h1F: consumed_bytes <= consumed_bytes + 5; - 64'h3F: consumed_bytes <= consumed_bytes + 6; - 64'h7F: consumed_bytes <= consumed_bytes + 7; - 64'hFF: consumed_bytes <= consumed_bytes + 8; - 64'hFFFF: consumed_bytes <= consumed_bytes + 16; - 64'hFFFFF: consumed_bytes <= consumed_bytes + 20; - 64'hFFFFFF: consumed_bytes <= consumed_bytes + 24; - 64'hFFFFFFF: consumed_bytes <= consumed_bytes + 28; - 64'hFFFFFFFF: consumed_bytes <= consumed_bytes + 32; - 64'hFFFFFFFFF: consumed_bytes <= consumed_bytes + 36; - 64'hFFFFFFFFFF: consumed_bytes <= consumed_bytes + 40; - 64'hFFFFFFFFFFF: consumed_bytes <= consumed_bytes + 44; - 64'hFFFFFFFFFFFF: consumed_bytes <= consumed_bytes + 48; - 64'hFFFFFFFFFFFFF: consumed_bytes <= consumed_bytes + 52; - 64'hFFFFFFFFFFFFFF: consumed_bytes <= consumed_bytes + 56; - 64'hFFFFFFFFFFFFFFF: consumed_bytes <= consumed_bytes + 60; - 64'hFFFFFFFFFFFFFFFF: consumed_bytes <= consumed_bytes + 64; - endcase - end - - if (m_axis_tcp_tx_data_tvalid && m_axis_tcp_tx_data_tready) begin - case (m_axis_tcp_tx_data_tkeep) - 64'hF: produced_bytes <= produced_bytes + 4; - 64'hFF: produced_bytes <= produced_bytes + 8; - 64'hFFFF: produced_bytes <= produced_bytes + 16; - 64'hFFFFF: produced_bytes <= produced_bytes + 20; - 64'hFFFFFF: produced_bytes <= produced_bytes + 24; - 64'hFFFFFFF: produced_bytes <= produced_bytes + 28; - 64'hFFFFFFFF: produced_bytes <= produced_bytes + 32; - 64'hFFFFFFFFF: produced_bytes <= produced_bytes + 36; - 64'hFFFFFFFFFF: produced_bytes <= produced_bytes + 40; - 64'hFFFFFFFFFFF: produced_bytes <= produced_bytes + 44; - 64'hFFFFFFFFFFFF: produced_bytes <= produced_bytes + 48; - 64'hFFFFFFFFFFFFF: produced_bytes <= produced_bytes + 52; - 64'hFFFFFFFFFFFFFF: produced_bytes <= produced_bytes + 56; - 64'hFFFFFFFFFFFFFFF: produced_bytes <= produced_bytes + 60; - 64'hFFFFFFFFFFFFFFFF: produced_bytes <= produced_bytes + 64; - endcase - end - - end -end - - -logic[31:0] tx_cmd_counter; -logic[31:0] tx_pkg_counter; -logic[31:0] tx_sts_counter; -logic[31:0] tx_sts_good_counter; -always @(posedge ap_clk) begin - if (~ap_rst_n | runExperiment) begin - tx_cmd_counter <= '0; - tx_pkg_counter <= '0; - tx_sts_counter <= '0; - tx_sts_good_counter <= '0; - end - else begin - if (m_axis_tcp_tx_meta_tvalid && m_axis_tcp_tx_meta_tready) begin - tx_cmd_counter <= tx_cmd_counter + 1; - end - if (m_axis_tcp_tx_data_tvalid && m_axis_tcp_tx_data_tready && m_axis_tcp_tx_data_tlast) begin - tx_pkg_counter <= tx_pkg_counter + 1; - end - if (s_axis_tcp_tx_status_tvalid && s_axis_tcp_tx_status_tready) begin - tx_sts_counter <= tx_sts_counter + 1; - if (s_axis_tcp_tx_status_tdata[63:62] == 0) begin - tx_sts_good_counter <= tx_sts_good_counter + 1; - end - end - end -end - -`define DEBUG -`ifdef DEBUG - -ila_controller controller_debug -( -.clk(ap_clk), // input wire clk - .probe0(ap_start_pulse), //1 - .probe1(isServer), // 1 - .probe2(useConnReg), // 16 - .probe3(pkgWordCountReg), // 16 - .probe4(baseIpAddress), //32 - .probe5(transferSize), //32 - .probe6(timeInCycles), //64 - .probe7(regBasePort), //16 - .probe8(UseIpAddrReg), //16 - .probe9(ap_start), //1 - .probe10(ap_done) //1 -); - - -ila_perf benchmark_debug ( - .clk(ap_clk), // input wire clk - - .probe0(m_axis_tcp_open_connection_tvalid), // input wire [0:0] probe0 - .probe1(m_axis_tcp_open_connection_tready), // input wire [0:0] probe1 - .probe2(s_axis_tcp_open_status_tvalid), // input wire [0:0] probe2 - .probe3(s_axis_tcp_open_status_tready), // input wire [0:0] probe3 - .probe4(s_axis_tcp_rx_data_tvalid), // input wire [0:0] probe4 - .probe5(s_axis_tcp_rx_data_tready), // input wire [0:0] probe5 - .probe6(finishExperiment), // input wire [0:0] probe6 - .probe7(runTx), // input wire [0:0] probe7 - .probe8(m_axis_tcp_tx_data_tvalid), //1 - .probe9(m_axis_tcp_tx_data_tready),//1 - .probe10(m_axis_tcp_tx_meta_tvalid),//1 - .probe11(m_axis_tcp_tx_meta_tready),//1 - .probe12(s_axis_tcp_tx_status_tvalid),//1 - .probe13(s_axis_tcp_tx_status_tready), //1 - .probe14(s_axis_tcp_open_status_tdata[16]), //1 - .probe15(s_axis_tcp_tx_status_tdata[63:62]), //2 - .probe16(m_axis_tcp_open_connection_tdata[31:0]), // 32 - .probe17(produced_bytes[63:0]), // 64 - .probe18(consumed_bytes[63:0]),// 64 - .probe19(sessionID[15:0]), // input wire [15:0] - .probe20(tx_cmd_counter[31:0]), // input wire [31:0] - .probe21(m_axis_tcp_open_connection_tdata[47:32]), // input wire [15:0] - .probe22(running), //1 - .probe23(s_axis_tcp_notification_tvalid), //1 - .probe24(s_axis_tcp_notification_tready), //1 - .probe25(s_axis_tcp_tx_status_tdata[61:32]), //30 - .probe26(execution_cycles[63:0]), //64 - .probe27(transferSize[15:0]), //16 - .probe28(tx_pkg_counter[31:0]),//32 - .probe29(s_axis_tcp_open_status_tdata[15:0]), //16 - .probe30(tx_sts_good_counter[31:0]), //32 - - .probe31(s_axis_tcp_port_status_tvalid), // 1 - .probe32(s_axis_tcp_port_status_tready), // 1 - .probe33(m_axis_tcp_listen_port_tvalid), // 1 - .probe34(m_axis_tcp_listen_port_tready), //1 - .probe35(s_axis_tcp_rx_meta_tvalid), // 1 - .probe36(s_axis_tcp_rx_meta_tready), // 1 - .probe37(m_axis_tcp_read_pkg_tvalid), // 1 - .probe38(m_axis_tcp_read_pkg_tready), //1 - .probe39(tx_sts_counter), //32 - .probe40(tx_meta_down), //32 - .probe41(tx_status_down), //32 - .probe42(tx_data_down) //32 -); - - -`endif - - -endmodule -`default_nettype wire diff --git a/examples_hw/apps/tcp_iperf/send_recv_slave.sv b/examples_hw/apps/tcp_iperf/send_recv_slave.sv deleted file mode 100644 index b1a90ebc0..000000000 --- a/examples_hw/apps/tcp_iperf/send_recv_slave.sv +++ /dev/null @@ -1,372 +0,0 @@ -import lynxTypes::*; - -/** - * PT Config Slave - */ -module send_recv_slave ( - input logic aclk, - input logic aresetn, - - AXI4L.s axi_ctrl, - - output logic ap_start, - input logic ap_done, - output logic [31:0] useConn, - output logic [31:0] useIpAddr, - output logic [31:0] pkgWordCount, - output logic [31:0] basePort, - output logic [31:0] baseIpAddress, - output logic [31:0] transferSize, - output logic [31:0] isServer, - output logic [31:0] timeInSeconds, - output logic [63:0] timeInCycles, - input logic [63:0] execution_cycles, - input logic [63:0] consumed_bytes, - input logic [63:0] produced_bytes, - input logic [63:0] openCon_cycles -); - -// `define DEBUG_CNFG_SLAVE - -// -- Decl ---------------------------------------------------------- -// ------------------------------------------------------------------ - -// Constants -localparam integer N_REGS = 15; -localparam integer ADDR_LSB = $clog2(AXIL_DATA_BITS/8); -localparam integer ADDR_MSB = $clog2(N_REGS); -localparam integer AXIL_ADDR_BITS = ADDR_LSB + ADDR_MSB; - -// Internal registers -logic [AXIL_ADDR_BITS-1:0] axi_awaddr; -logic axi_awready; -logic [AXIL_ADDR_BITS-1:0] axi_araddr; -logic axi_arready; -logic [1:0] axi_bresp; -logic axi_bvalid; -logic axi_wready; -logic [AXIL_DATA_BITS-1:0] axi_rdata; -logic [1:0] axi_rresp; -logic axi_rvalid; - -// Registers -logic [N_REGS-1:0][AXIL_DATA_BITS-1:0] slv_reg; -logic slv_reg_rden; -logic slv_reg_wren; -logic aw_en; - -// -- Def ----------------------------------------------------------- -// ------------------------------------------------------------------ - -/* -- Register map ----------------------------------------------------------------------- -/ 0 (WO) : Control -/ 1 (RO) : Status -/ 2 (RW) : useConn -/ 3 (RW) : useIpAddr -/ 4 (RW) : pkgWordCount -/ 5 (RW) : basePort -/ 6 (RW) : baseIpAddr -/ 7 (RW) : transferSize -/ 8 (RW) : isServer -/ 9 (RW) : timeInSeconds -/ 10 (RW) : timeInCycles -/ 11 (R) : execution_cycles -/ 12 (R) : consumed_bytes -/ 13 (R) : produced_bytes -/ 14 (R) : openCon_cycles -*/ - -// Write process -assign slv_reg_wren = axi_wready && axi_ctrl.wvalid && axi_awready && axi_ctrl.awvalid; - -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) begin - slv_reg <= 0; - end - else begin - slv_reg[0][0] <= 0; - - if(slv_reg_wren) begin - case (axi_awaddr[ADDR_LSB+:ADDR_MSB]) - 4'h0: // Control - for (int i = 0; i < 1; i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[0][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - 4'h2: // useConn - for (int i = 0; i < AXIL_DATA_BITS/8; i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[2][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - 4'h3: // useIpAddr - for (int i = 0; i < AXIL_DATA_BITS/8; i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[3][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - 4'h4: // pkgWordCount - for (int i = 0; i < AXIL_DATA_BITS/8; i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[4][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - 4'h5: // basePort - for (int i = 0; i < AXIL_DATA_BITS/8; i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[5][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - 4'h6: // baseIpAddr - for (int i = 0; i < AXIL_DATA_BITS/8; i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[6][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - 4'h7: // transferSize - for (int i = 0; i < AXIL_DATA_BITS/8; i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[7][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - 4'h8: // isServer - for (int i = 0; i < AXIL_DATA_BITS/8; i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[8][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - 4'h9: // timeInSeconds - for (int i = 0; i < AXIL_DATA_BITS/8; i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[9][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - 4'ha: // timeInCycles - for (int i = 0; i < AXIL_DATA_BITS/8; i++) begin - if(axi_ctrl.wstrb[i]) begin - slv_reg[10][(i*8)+:8] <= axi_ctrl.wdata[(i*8)+:8]; - end - end - default : ; - endcase - end - end -end - -// Output -always_comb begin - ap_start = slv_reg[0][0]; - - useConn = slv_reg[2]; - useIpAddr = slv_reg[3]; - pkgWordCount = slv_reg[4]; - basePort = slv_reg[5]; - baseIpAddress = slv_reg[6]; - transferSize = slv_reg[7]; - isServer = slv_reg[8]; - timeInSeconds = slv_reg[9]; - timeInCycles = slv_reg[10]; -end - -// Read process -assign slv_reg_rden = axi_arready & axi_ctrl.arvalid & ~axi_rvalid; - -always_ff @(posedge aclk) begin - if( aresetn == 1'b0 ) begin - axi_rdata <= 0; - end - else begin - if(slv_reg_rden) begin - axi_rdata <= 0; - - case (axi_araddr[ADDR_LSB+:ADDR_MSB]) - 4'h1: // Status - axi_rdata[0] <= ap_done; - 4'h2: // useConn - axi_rdata <= slv_reg[2]; - 4'h3: // useIpAddr - axi_rdata <= slv_reg[3]; - 4'h4: // pkgWordCount - axi_rdata <= slv_reg[4]; - 4'h5: // basePort - axi_rdata <= slv_reg[5]; - 4'h6: // baseIpAddr - axi_rdata <= slv_reg[6]; - 4'h7: // transferSize - axi_rdata <= slv_reg[7]; - 4'h8: // isServer - axi_rdata <= slv_reg[8]; - 4'h9: // timeInSeconds - axi_rdata <= slv_reg[9]; - 4'ha: // timeInCycles - axi_rdata <= slv_reg[10]; - 4'hb: //execution_cycles - axi_rdata <= execution_cycles; - 4'hc: //consumed_bytes - axi_rdata <= consumed_bytes; - 4'hd: //produced_bytes - axi_rdata <= produced_bytes; - 4'he: //openCon_cycles - axi_rdata <= openCon_cycles; - default: ; - endcase - end - end -end - -//`define DEBUG_CNFG_SLAVE -`ifdef DEBUG_CNFG_SLAVE -ila_slave ila_slave -( - .clk(aclk), - .probe0(slv_reg_rden), - .probe1(slv_reg_wren), - .probe2(axi_ctrl.arvalid), - .probe3(axi_ctrl.arready), - .probe4(axi_ctrl.araddr), // 64 - .probe5(axi_ctrl.awvalid), - .probe6(axi_ctrl.awready), - .probe7(axi_ctrl.awaddr), // 64 - .probe8(axi_ctrl.rvalid), - .probe9(axi_ctrl.rready), - .probe10(axi_ctrl.rdata), // 64 - .probe11(axi_ctrl.wvalid), - .probe12(axi_ctrl.wready), - .probe13(axi_ctrl.wdata), // 64 - .probe14(axi_ctrl.wstrb), // 8 - .probe15(axi_ctrl.bvalid), - .probe16(axi_ctrl.bready) - ); -`endif - -// -------------------------------------------------------------------------------------- -// AXI CTRL -// -------------------------------------------------------------------------------------- -// Don't edit - -// I/O -assign axi_ctrl.awready = axi_awready; -assign axi_ctrl.arready = axi_arready; -assign axi_ctrl.bresp = axi_bresp; -assign axi_ctrl.bvalid = axi_bvalid; -assign axi_ctrl.wready = axi_wready; -assign axi_ctrl.rdata = axi_rdata; -assign axi_ctrl.rresp = axi_rresp; -assign axi_ctrl.rvalid = axi_rvalid; - -// awready and awaddr -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_awready <= 1'b0; - axi_awaddr <= 0; - aw_en <= 1'b1; - end - else - begin - if (~axi_awready && axi_ctrl.awvalid && axi_ctrl.wvalid && aw_en) - begin - axi_awready <= 1'b1; - aw_en <= 1'b0; - axi_awaddr <= axi_ctrl.awaddr; - end - else if (axi_ctrl.bready && axi_bvalid) - begin - aw_en <= 1'b1; - axi_awready <= 1'b0; - end - else - begin - axi_awready <= 1'b0; - end - end -end - -// arready and araddr -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_arready <= 1'b0; - axi_araddr <= 0; - end - else - begin - if (~axi_arready && axi_ctrl.arvalid) - begin - axi_arready <= 1'b1; - axi_araddr <= axi_ctrl.araddr; - end - else - begin - axi_arready <= 1'b0; - end - end -end - -// bvalid and bresp -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_bvalid <= 0; - axi_bresp <= 2'b0; - end - else - begin - if (axi_awready && axi_ctrl.awvalid && ~axi_bvalid && axi_wready && axi_ctrl.wvalid) - begin - axi_bvalid <= 1'b1; - axi_bresp <= 2'b0; - end - else - begin - if (axi_ctrl.bready && axi_bvalid) - begin - axi_bvalid <= 1'b0; - end - end - end -end - -// wready -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_wready <= 1'b0; - end - else - begin - if (~axi_wready && axi_ctrl.wvalid && axi_ctrl.awvalid && aw_en ) - begin - axi_wready <= 1'b1; - end - else - begin - axi_wready <= 1'b0; - end - end -end - -// rvalid and rresp (1Del?) -always_ff @(posedge aclk) begin - if ( aresetn == 1'b0 ) - begin - axi_rvalid <= 0; - axi_rresp <= 0; - end - else - begin - if (axi_arready && axi_ctrl.arvalid && ~axi_rvalid) - begin - axi_rvalid <= 1'b1; - axi_rresp <= 2'b0; - end - else if (axi_rvalid && axi_ctrl.rready) - begin - axi_rvalid <= 1'b0; - end - end -end - - -endmodule // cnfg_slave \ No newline at end of file diff --git a/examples_hw/apps/tcp_iperf/set_ila.tcl b/examples_hw/apps/tcp_iperf/set_ila.tcl deleted file mode 100644 index bb2faa8db..000000000 --- a/examples_hw/apps/tcp_iperf/set_ila.tcl +++ /dev/null @@ -1,33 +0,0 @@ -create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_perf -set_property -dict [list CONFIG.C_PROBE42_WIDTH {32} CONFIG.C_PROBE41_WIDTH {32} CONFIG.C_PROBE40_WIDTH {32} CONFIG.C_PROBE39_WIDTH {32} CONFIG.C_PROBE30_WIDTH {32} CONFIG.C_PROBE29_WIDTH {16} CONFIG.C_PROBE28_WIDTH {32} CONFIG.C_PROBE27_WIDTH {16} CONFIG.C_PROBE26_WIDTH {64} CONFIG.C_PROBE25_WIDTH {30} CONFIG.C_PROBE21_WIDTH {16} CONFIG.C_PROBE20_WIDTH {32} CONFIG.C_PROBE19_WIDTH {16} CONFIG.C_PROBE18_WIDTH {64} CONFIG.C_PROBE17_WIDTH {64} CONFIG.C_PROBE16_WIDTH {32} CONFIG.C_PROBE15_WIDTH {2} CONFIG.C_NUM_OF_PROBES {43} CONFIG.Component_Name {ila_perf} CONFIG.C_INPUT_PIPE_STAGES {1} CONFIG.ALL_PROBE_SAME_MU_CNT {5}] [get_ips ila_perf] - -create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_mem_inf -set_property -dict [list CONFIG.C_PROBE13_WIDTH {32} CONFIG.C_PROBE10_WIDTH {32} CONFIG.C_PROBE7_WIDTH {23} CONFIG.C_PROBE6_WIDTH {64} CONFIG.C_PROBE3_WIDTH {23} CONFIG.C_PROBE2_WIDTH {64} CONFIG.C_NUM_OF_PROBES {31} CONFIG.Component_Name {ila_mem_inf} CONFIG.C_INPUT_PIPE_STAGES {1}] [get_ips ila_mem_inf] - -create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_network_statistic -set_property -dict [list CONFIG.C_PROBE10_WIDTH {8} CONFIG.C_PROBE9_WIDTH {16} CONFIG.C_PROBE8_WIDTH {16} CONFIG.C_PROBE7_WIDTH {16} CONFIG.C_PROBE6_WIDTH {16} CONFIG.C_PROBE5_WIDTH {16} CONFIG.C_PROBE4_WIDTH {16} CONFIG.C_PROBE3_WIDTH {16} CONFIG.C_PROBE2_WIDTH {16} CONFIG.C_PROBE1_WIDTH {16} CONFIG.C_PROBE0_WIDTH {16} CONFIG.C_NUM_OF_PROBES {11} CONFIG.Component_Name {ila_network_statistic} CONFIG.C_INPUT_PIPE_STAGES {1}] [get_ips ila_network_statistic] - -create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_controller -set_property -dict [list CONFIG.C_PROBE8_WIDTH {16} CONFIG.C_PROBE7_WIDTH {16} CONFIG.C_PROBE6_WIDTH {64} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE3_WIDTH {16} CONFIG.C_PROBE2_WIDTH {16} CONFIG.C_NUM_OF_PROBES {11} CONFIG.Component_Name {ila_controller} CONFIG.C_INPUT_PIPE_STAGES {1}] [get_ips ila_controller] - - -set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/s_axis_tcp_port_status_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/m_axis_tcp_listen_port_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/m_axis_tcp_open_connection_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/m_axis_tcp_read_pkg_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/m_axis_tcp_tx_meta_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE eq1'bB [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/running -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/s_axis_tcp_notification_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/s_axis_tcp_open_status_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE {eq1'b1 eq1'b1} [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/s_axis_tcp_port_status_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE {eq1'b1 eq1'b1} [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/s_axis_tcp_port_status_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/s_axis_tcp_port_status_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/s_axis_tcp_rx_meta_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property TRIGGER_COMPARE_VALUE eq1'b1 [get_hw_probes inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/s_axis_tcp_tx_status_tvalid -of_objects [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}]] -set_property CONTROL.TRIGGER_CONDITION OR [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}] -startgroup -set_property CONTROL.DATA_DEPTH 16 [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}] -set_property CONTROL.TRIGGER_POSITION 15 [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}] -set_property CONTROL.WINDOW_COUNT 64 [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}] -endgroup -set_property CONTROL.TRIGGER_POSITION 1 [get_hw_ilas -of_objects [get_hw_devices xcvu9p_0] -filter {CELL_NAME=~"inst_dynamic/inst_user_wrapper_0/inst_user_c0_0/user_role/benchmark_debug"}] diff --git a/examples_hw/floorplans/streaming_fplan_u55c.xdc b/examples_hw/floorplans/streaming_fplan_u55c.xdc deleted file mode 100644 index 721bdd1a3..000000000 --- a/examples_hw/floorplans/streaming_fplan_u55c.xdc +++ /dev/null @@ -1,9 +0,0 @@ -create_pblock pblock_inst_user_wrapper_0 -add_cells_to_pblock [get_pblocks pblock_inst_user_wrapper_0] [get_cells -quiet [list inst_shell/inst_dynamic/inst_user_wrapper_0]] -resize_pblock [get_pblocks pblock_inst_user_wrapper_0] -add {SLICE_X0Y660:SLICE_X116Y719} -resize_pblock [get_pblocks pblock_inst_user_wrapper_0] -add {DSP48E2_X0Y258:DSP48E2_X15Y281} -resize_pblock [get_pblocks pblock_inst_user_wrapper_0] -add {RAMB18_X0Y264:RAMB18_X7Y287} -resize_pblock [get_pblocks pblock_inst_user_wrapper_0] -add {RAMB36_X0Y132:RAMB36_X7Y143} -resize_pblock [get_pblocks pblock_inst_user_wrapper_0] -add {URAM288_X0Y176:URAM288_X1Y191} -resize_pblock [get_pblocks pblock_inst_user_wrapper_0] -add {CLOCKREGION_X0Y7:CLOCKREGION_X5Y10 CLOCKREGION_X0Y0:CLOCKREGION_X3Y6} -set_property SNAPPING_MODE ON [get_pblocks pblock_inst_user_wrapper_0] \ No newline at end of file diff --git a/examples_sw/CMakeLists.txt b/examples_sw/CMakeLists.txt deleted file mode 100644 index 4da8133e5..000000000 --- a/examples_sw/CMakeLists.txt +++ /dev/null @@ -1,116 +0,0 @@ -cmake_minimum_required(VERSION 3.5) -project(test) - -set(CYT_DIR ${CMAKE_SOURCE_DIR}/../) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CYT_DIR}/cmake) - -find_package(CoyoteSW REQUIRED) - -# -# Pass an additional command-line argument to specify the verbosity of the compilation -# If VERBOSITY is passed as an argument, VERBOSE is set as preprocessor-macro -# - -set(VERBOSITY OFF CACHE BOOL "Enable VERBOSITY for compilation") - -if (VERBOSITY) - message("You selected VERBOSITY for compilation") - add_compile_definitions(VERBOSE) -else() - message("VERBOSITY is not selected for compilation") -endif() - - -# -# Shell reconfiguration -# -if(EXAMPLE STREQUAL "reconfigure_shell") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/reconfigure_shell") - message("** Shell load") -endif() - -# -# TEST -# -if(EXAMPLE STREQUAL "perf_gpu") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/perf_gpu") - message("** Example: Validation tests - GPU perf") -endif() - -if(EXAMPLE STREQUAL "hls_vadd") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/hls_vadd") - message("** Example: HLS vector addition") -endif() - -# -# Example builds -# - -# Used in conjunction with the default shell -if(EXAMPLE STREQUAL "perf_local") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/perf_local") - message("** Example: Validation tests - Link perf") -endif() - -if(EXAMPLE STREQUAL "interrupts") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/user_interrupts") - message("** Example: User interrupts") -endif() - -# FPGA perf benchmark -if(EXAMPLE STREQUAL "perf_fpga") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/perf_fpga") - message("** Example: FPGA perf benchmark") -endif() - -# Streaming services example -if(EXAMPLE STREQUAL "streaming_server") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/streaming_service/server") - message("** Example: Streaming service - Server (Coyote daemon)") - include_directories(${TARGET_DIR}/../include ${TARGET_DIR}/include) -endif() - -if(EXAMPLE STREQUAL "streaming_client") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/streaming_service/client") - message("** Example: Streaming service - Client") - include_directories(${TARGET_DIR}/../include) -endif() - -if(EXAMPLE STREQUAL "multithreading") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/multithreading") - message("** Example: Multithreading") -endif() - -# Network - RDMA -if(EXAMPLE STREQUAL "rdma_server") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/rdma_service/server") - message("** Example: RDMA service - Server (Coyote daemon)") - include_directories(${TARGET_DIR}/../include) -endif() - -if(EXAMPLE STREQUAL "rdma_client") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/rdma_service/client") - message("** Example: RDMA service - Client") - include_directories(${TARGET_DIR}/../include) -endif() - -# Network - TCP -if(EXAMPLE STREQUAL "tcp_iperf") - set(TARGET_DIR "${CYT_DIR}/examples_sw/apps/tcp_iperf") - message("** Example: TCP - iperf") -endif() - -# -# Create build targets -# -set(EXEC test) -add_executable(${EXEC} ${TARGET_DIR}/main.cpp) -target_link_libraries(${EXEC} PUBLIC Coyote) -target_link_directories(${EXEC} PUBLIC /usr/local/lib) - -if(EN_GPU) - set(AMD_GPU "gfx908" CACHE STRING "AMD GPU device to use") - set_property(TARGET ${EXEC} PROPERTY HIP_ARCHITECTURES ${AMD_GPU}) - set_target_properties(${EXEC} PROPERTIES LINKER_LANGUAGE CXX) - target_link_libraries(${EXEC} PUBLIC hip::device numa pthread drm drm_amdgpu rt dl hsa-runtime64 hsakmt) -endif() \ No newline at end of file diff --git a/examples_sw/apps/streaming_service/client/main.cpp b/examples_sw/apps/streaming_service/client/main.cpp deleted file mode 100644 index 209cd8747..000000000 --- a/examples_sw/apps/streaming_service/client/main.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Copyright (c) 2021, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cLib.hpp" -#include "types.hpp" - -using namespace std; -using namespace fpga; - -// Runtime -constexpr auto const defRunHLL = false; -constexpr auto const defRunDtrees = false; -constexpr auto const defNTuples = 128 * 1024; -constexpr auto const defNFeatures = 5; - -int main(int argc, char *argv[]) -{ - - - // Read arguments - boost::program_options::options_description programDescription("Options:"); - programDescription.add_options() - ("tuples,t", boost::program_options::value(), "Number of tuples") - ("features,f", boost::program_options::value(), "Number of features") - ("hloglog,h", boost::program_options::value(), "Run HyperLogLog") - ("dtrees,d", boost::program_options::value(), "Run Decision Trees"); - - boost::program_options::variables_map commandLineArgs; - boost::program_options::store(boost::program_options::parse_command_line(argc, argv, programDescription), commandLineArgs); - boost::program_options::notify(commandLineArgs); - - bool runHLL = defRunHLL; - bool runDtrees = defRunDtrees; - uint32_t n_tuples = defNTuples; - uint32_t n_features = defNFeatures; - - if(commandLineArgs.count("hloglog") > 0) runHLL = commandLineArgs["hloglog"].as(); - if(commandLineArgs.count("dtrees") > 0) runDtrees = commandLineArgs["dtrees"].as(); - if(commandLineArgs.count("tuples") > 0) n_tuples = commandLineArgs["tuples"].as(); - if(commandLineArgs.count("features") > 0) n_features = commandLineArgs["features"].as(); - - // - // HyperLogLog operator - // - if(runHLL) { - // Connect to service - cLib clib_hll("/tmp/coyote-daemon-vfid-0-streaming", fidHLL); - - // Let's get some buffers and fill it with some random data ... - uint32_t* dMem = (uint32_t*) memalign(axiDataWidth, n_tuples * defDW); - uint32_t* rMem = (uint32_t*) memalign(axiDataWidth, defDW); - - for(int i = 0; i < n_tuples; i++) { - dMem[i] = rand(); - } - - // Execute the HLL - // This is the only place of interaction with Coyote ... - double cmpl_ev = clib_hll.task(opPriority, (uint64_t)dMem, (uint64_t)rMem, n_tuples); - - PR_HEADER("Hyper-Log-Log"); - std::cout << std::fixed << std::setprecision(2) << std::dec; - std::cout << "Estimation completed, run time: " << cmpl_ev << " us" << std::endl; - std::cout << "Estimated cardinality: " << ((float*)rMem)[0] << std::endl << std::endl; - - free(dMem); - free(rMem); - } - - // - // Decision trees operator - // - if(runDtrees) { - // Connect to service - cLib clib_dtrees("/tmp/coyote-daemon-vfid-0-streaming", fidDtrees); - - // Buffers ... - uint32_t* dMem = (uint32_t*) mmap(NULL, n_tuples * n_features * defDW, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); - uint32_t* rMem = (uint32_t*) memalign(axiDataWidth, n_tuples * defDW); - - for (int i = 0; i < n_tuples; ++i) { - for (int j = 0; j < n_features; ++j) { - dMem[i * n_features + j] = ((float)(i+1))/((float)(j+i+1)); - } - } - - // Execute the Decision trees - // This is the only place of interaction with Coyote ... - double cmpl_ev = clib_dtrees.task(opPriority, (uint64_t)dMem, (uint64_t)rMem, n_tuples, n_features); - - PR_HEADER("GBM Decision Trees"); - std::cout << std::fixed << std::setprecision(2) << std::dec; - std::cout << "Estimation completed, run time: " << cmpl_ev << " us" << std::endl; - std::cout << "Throughput achieved: " << ((double) n_tuples / (double)cmpl_ev) << ", MT/s" << std::endl << std::endl; - - munmap(dMem, n_tuples * n_features * defDW); - munmap(rMem, n_tuples * defDW); - } - - return (EXIT_SUCCESS); -} diff --git a/examples_sw/apps/streaming_service/include/types.hpp b/examples_sw/apps/streaming_service/include/types.hpp deleted file mode 100644 index 0097e4ca7..000000000 --- a/examples_sw/apps/streaming_service/include/types.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -#include "cDefs.hpp" - -#include -#include -#include - -using namespace fpga; - -// Runtime -constexpr auto const fidHLL = 1; -constexpr auto const fidDtrees = 2; - -constexpr auto const operatorHLL = 1; -constexpr auto const operatorDtrees = 2; - -constexpr auto const opPriority = 1; - -constexpr auto const defDevice = 0; -constexpr auto const defTargetVfid = 0; - -constexpr auto const defDW = 4; \ No newline at end of file diff --git a/examples_sw/apps/streaming_service/server/include/gbm_dtrees.hpp b/examples_sw/apps/streaming_service/server/include/gbm_dtrees.hpp deleted file mode 100644 index 65a862df2..000000000 --- a/examples_sw/apps/streaming_service/server/include/gbm_dtrees.hpp +++ /dev/null @@ -1,179 +0,0 @@ -#include - -#include "cDefs.hpp" -#include "cTask.hpp" - -#include -#include -#include - -using namespace fpga; - -void initTrees(uint* trees, int numtrees, int numnodes, int depth); - -// -// Util -// - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -uint find_node_index(uint nd, uint* indexes, uint* pnodes) -{ - int parent = nd/2; - - if (nd%2 != 0) return indexes[parent] + 1; - else return indexes[parent-1] + pnodes[parent-1] + 1; - - return 0; -} -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -uint find_depth(uint nd, uint depth) -{ - uint currLevel = depth -1; - - while(1) - { - int nd1 = pow(2, currLevel) - 1; - - if (nd >= nd1) return currLevel; - else currLevel = currLevel - 1; - } - return 0; -} -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void initTrees(uint* trees, int numtrees, int numnodes, int depth) -{ - uint node_t; - uint curr_tree_offset = 0; - - uint* nodes_ids = new uint[numnodes]; - uint* nodes_size = new uint[numnodes]; - uint* indexes = new uint[numnodes]; - uint* pnodes = new uint[numnodes]; - - // set root - nodes_ids[0] = 0; - nodes_size[0] = 2; - indexes[0] = 0; - pnodes[0] = numnodes/2; - - int lastLevelNodeID = numnodes/2; - - for (int i = 1; i < numnodes; ++i) - { - int idx = find_node_index(i, indexes, pnodes); - nodes_ids[idx] = i; - nodes_size[idx] = (i < lastLevelNodeID)? 2 : 10; - indexes[i] = idx; - - uint ndLevel = find_depth(i, depth); - pnodes[i] = pow(2, (depth-1-ndLevel) ) - 1; - } - - for(int i = 0; i < numnodes; i++) printf("(%d, %d)", nodes_ids[i], nodes_size[i]); - printf("\n"); - - delete [] pnodes; - delete [] indexes; - - // - for (int i = 0; i < numtrees; ++i) - { - // initialize tree i - uint tree_i[4096]; - uint node_off = 0; - for (int j = 0; j < numnodes; ++j) - { - // initialize node j; - // Node type - node_t = 0; - - uint op_type = (nodes_ids[j] >= lastLevelNodeID)? 3 : (i%2 == 0)? 0 : 2; - uint left_child = (nodes_ids[j] >= lastLevelNodeID)? 0 : 1; - uint right_child = (nodes_ids[j] >= lastLevelNodeID)? 0 : 1; - uint findex = nodes_ids[j]%25; - uint split_dir = 2; - - uint rc_id = nodes_ids[j]*2 + 2; - - uint rc_off = 0; - for (int f = j+1; f < numnodes; ++f) - { - if (nodes_ids[f] != rc_id) rc_off += nodes_size[f]; - else break; - } - - uint rchild_offset = (nodes_ids[j] >= lastLevelNodeID)? 0 : rc_off; - - node_t = (node_t & 0xFFFFFFFC) | ((op_type << 0) & 0x00000003); - node_t = (node_t & 0xFFFFFFFB) | ((left_child << 2) & 0x00000004); - node_t = (node_t & 0xFFFFFFF7) | ((right_child << 3) & 0x00000008); - node_t = (node_t & 0xFFFFF00F) | ((findex << 4) & 0x00000FF0); - node_t = (node_t & 0xFFFF0FFF) | ((split_dir << 12) & 0x0000F000); - node_t = (node_t & 0x0000FFFF) | ((rchild_offset << 16) & 0xFFFF0000); - - tree_i[node_off] = node_t; - node_off += 1; - - // split value/set - float sval = 1.5; - if (nodes_ids[j] < lastLevelNodeID) - { - if (i%2 == 0) // split value - { - memcpy( &(tree_i[node_off]), &sval, 4); - } - else // small split Set - { - tree_i[node_off] = 0xAAAAAAAA; - } - node_off += 1; - } - else - { - // large split set offset and length - tree_i[node_off] = 0x00000006; - node_off += 1; - // - float val = 2.5; - memcpy( &(tree_i[node_off]), &val, 4); - val = 0.5; - memcpy( &(tree_i[node_off+1]), &val, 4); - - for (int s = 0; s < 6; ++s) // large split set words - { - tree_i[node_off + 2 + s] = 0x55555555; - } - - node_off += 8; - } - - } - // trees - trees[curr_tree_offset] = node_off; - for (uint32_t t = 0; t < node_off; ++t) - { - trees[curr_tree_offset+1+t] = tree_i[t]; - } - - curr_tree_offset += node_off + 1; - - printf("Tree Size = %d\n", curr_tree_offset); - - curr_tree_offset = curr_tree_offset + (((curr_tree_offset%16) != 0)? 16 - (curr_tree_offset%16) : 0); - } - - // - delete [] nodes_ids; - delete [] nodes_size; - - printf("Initialization done!\n"); fflush(stdout); -} diff --git a/examples_sw/apps/streaming_service/server/main.cpp b/examples_sw/apps/streaming_service/server/main.cpp deleted file mode 100644 index d766dc725..000000000 --- a/examples_sw/apps/streaming_service/server/main.cpp +++ /dev/null @@ -1,257 +0,0 @@ -/** - * Copyright (c) 2021, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cService.hpp" -#include "cFunc.hpp" -#include "gbm_dtrees.hpp" -#include "types.hpp" - -using namespace std; -using namespace fpga; - -/** - * @brief Main - * - */ -int main(int argc, char *argv[]) -{ - /* Args */ - boost::program_options::options_description programDescription("Options:"); - programDescription.add_options() - ("device,d", boost::program_options::value(), "Target device") - ("vfid,i", boost::program_options::value(), "Target vFPGA"); - - boost::program_options::variables_map commandLineArgs; - boost::program_options::store(boost::program_options::parse_command_line(argc, argv, programDescription), commandLineArgs); - boost::program_options::notify(commandLineArgs); - - uint32_t cs_dev = defDevice; - uint32_t vfid = defTargetVfid; - - if(commandLineArgs.count("device") > 0) cs_dev = commandLineArgs["device"].as(); - if(commandLineArgs.count("vfid") > 0) vfid = commandLineArgs["vfid"].as(); - - /** - * @brief Load all service functions and start the server - */ - cService *cservice = cService::getInstance("streaming", false, vfid, cs_dev); - std::cout << std::endl << "Shell loading ..." << std::endl << std::endl; - cservice->shellReconfigure("shell_bstream.bin"); - - /** - * @brief Load all operators (partial images) into the scheduler and start the operation - * - */ - - cservice->addBitstream("app_bstream_hloglog.bin", operatorHLL); - cservice->addBitstream("app_bstream_dtrees.bin", operatorDtrees); - - // The Hyper-Log-Log task - cservice->addFunction(fidHLL, std::unique_ptr(new cFunc(operatorHLL, - [=] (cThread *cthread, uint64_t d_mem, uint64_t r_mem, uint32_t n_tuples) -> double { // returns time - void* dMem = (void*) d_mem; - void* rMem = (void*) r_mem; - - syslog(LOG_NOTICE, "Executing HLL task, params: dMem %lx, rMem %lx, tuples %d", (uint64_t)dMem, (uint64_t)rMem, n_tuples); - - // SG entries -------------------------------------------------------------------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - sgEntry sg; - memset(&sg, 0, sizeof(localSg)); - sg.local.src_addr = dMem; sg.local.src_len = n_tuples * defDW; - sg.local.dst_addr = rMem; sg.local.dst_len = defDW; - - // User map ---------------------------------------------------------------------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - cthread->userMap(dMem, n_tuples * defDW); - cthread->userMap(rMem, defDW); - - // Lock vFPGA (scheduler will load the required bitstream if necessary) ---------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - cthread->pLock(operatorHLL, opPriority); - - // Invoke (move the data) -------------------------------------------------------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - auto begin_time = chrono::high_resolution_clock::now(); - cthread->invoke(CoyoteOper::LOCAL_TRANSFER, &sg, {true, true, true}); - auto end_time = chrono::high_resolution_clock::now(); - - // Unlock vFPGA ------------------------------------------------------------------------------------------------ - // ------------------------------------------------------------------------------------------------------------- - cthread->pUnlock(); - - // User unmap - cthread->userUnmap(dMem); - cthread->userUnmap(rMem); - - double time = chrono::duration_cast(end_time - begin_time).count(); - syslog(LOG_NOTICE, "Task HLL executed, time %f", time); - - return { time }; - } - ))); - - // Load Decision trees task - cservice->addFunction(fidDtrees, std::unique_ptr(new cFunc(operatorDtrees, - [=] (cThread *cthread, uint64_t d_mem, uint64_t r_mem, int32_t n_tuples, int32_t n_features) -> double { // returns time - void* dMem = (void*) d_mem; - void* rMem = (void*) r_mem; - - syslog(LOG_NOTICE, "Executing D-trees task, params: dMem %lx, rMem %lx, tuples %d, features %d", (uint64_t)dMem, (uint64_t)rMem, n_tuples, n_features); - - // Prep the dtrees parameters ---------------------------------------------------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - int32_t depth = 5; - int32_t numTrees = 109; - - int32_t result_size = defDW * n_tuples; - int32_t data_size = n_features * n_tuples * defDW; - - uint outputNumCLs = result_size/64 + ((result_size%64 > 0)? 1 : 0); - - unsigned char puTrees = numTrees/28 + ((numTrees%28 == 0)? 0 : 1); - - int numnodes = pow(2, depth) - 1; - int tree_size = 2*(pow(2,depth-1) - 1) + 10*pow(2,depth-1) + 1; - tree_size = tree_size + ( ((tree_size%16) > 0)? 16 - (tree_size%16) : 0); - - int trees_size = tree_size*numTrees*4; - - //uint64_t n_trees_pages = trees_size/hugePageSize + ((trees_size%hugePageSize > 0)? 1 : 0); - short lastOutLineMask = ((n_tuples%16) > 0)? 0xFFFF << (n_tuples%16) : 0x0000; - - // Initialize the trees ---------------------------------------------------------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - std::unique_ptr> cthread_model; - void* tMem; - - // Allocate Trees Memory - try { - // Thread to offload the model - cthread_model = std::make_unique>(vfid, getpid(), cs_dev); - } catch(...) { - return { 0 }; - } - - tMem = cthread_model->getMem({CoyoteAlloc::HPF, (uint32_t)trees_size}); - initTrees(((uint32_t*)(tMem)), numTrees, numnodes, depth); - - // User map ---------------------------------------------------------------------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - cthread->userMap(dMem, n_tuples * n_features * defDW); - cthread->userMap(rMem, n_tuples * defDW); - - // SG entries (prep for model offload) ------------------------------------------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - sgEntry sg; - memset(&sg, 0, sizeof(localSg)); - sg.local.src_addr = tMem; sg.local.src_len = (uint32_t) trees_size; - - // Lock vFPGA (scheduler will load the required bitstream if necessary) ---------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - cthread->pLock(operatorDtrees, opPriority); - - // Set HW kernel params ---------------------------------------------------------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - cthread->setCSR(n_features, 1); - cthread->setCSR(depth, 2); - cthread->setCSR(puTrees, 3); - cthread->setCSR(outputNumCLs, 4); - cthread->setCSR(lastOutLineMask, 5); - cthread->setCSR(0x1, 0); // ap_start - - // Push trees to the FPGA, blocking, returns when all trees have been streamed to the FPGA --------------------- - // ------------------------------------------------------------------------------------------------------------- - //cthread->invoke(cs_invoke); - cthread_model->invoke(CoyoteOper::LOCAL_READ, &sg, {true, true, true}); - - - // Stream data into the FPGA, blocking, initiate transfer in both directions (results writen back) ------------- - // ------------------------------------------------------------------------------------------------------------- - sg.local.src_addr = dMem; // Read - sg.local.src_len = (uint32_t) data_size; - sg.local.dst_addr = rMem; - sg.local.src_len = (uint32_t) result_size; - - auto begin_time = chrono::high_resolution_clock::now(); - cthread->invoke(CoyoteOper::LOCAL_TRANSFER, &sg, {true, true, true}); - auto end_time = chrono::high_resolution_clock::now(); - - // Unlock vFPGA ------------------------------------------------------------------------------------------------ - // ------------------------------------------------------------------------------------------------------------- - cthread->pUnlock(); - - // User unmap -------------------------------------------------------------------------------------------------- - // ------------------------------------------------------------------------------------------------------------- - cthread->userUnmap(dMem); - cthread->userUnmap(rMem); - cthread_model->freeMem(tMem); - - double time = chrono::duration_cast(end_time - begin_time).count(); - syslog(LOG_NOTICE, "Task D-trees executed, time %f", time); - - return { time }; - }))); - - // - // Start a daemon - // - std::cout << "Forking ..." << std::endl << std::endl; - cservice->start(); -} - diff --git a/examples_sw/apps/tcp_iperf/main.cpp b/examples_sw/apps/tcp_iperf/main.cpp deleted file mode 100644 index 0980a55b7..000000000 --- a/examples_sw/apps/tcp_iperf/main.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright (c) 2021, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#include "cDefs.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "cBench.hpp" -#include "cThread.hpp" - -using namespace std; -using namespace std::chrono; -using namespace fpga; - -/* Def params */ -constexpr auto const defDevice = 0; -constexpr auto const targetRegion = 0; -constexpr auto const freq = 300; // MHz - -/** - * @brief TCP benchmarking - * - */ -int main(int argc, char *argv[]) -{ - // --------------------------------------------------------------- - // Initialization - // --------------------------------------------------------------- - // const char* masterAddr = "10.1.212.121"; - - // Read arguments - boost::program_options::options_description programDescription("Options:"); - programDescription.add_options() - ("device,d", boost::program_options::value(), "Device") - ("useConn,c", boost::program_options::value(), "Number of connections") - ("useIpAddr,i", boost::program_options::value(), "Number of IP addresses") - ("port,p", boost::program_options::value(), "Port number") - ("pkgWordCount,w", boost::program_options::value(), "Number of 512-bit work in a packet") - ("timeInSeconds,t", boost::program_options::value(), "Time in second") - ("transferBytes,b", boost::program_options::value(), "TransferBytes") - ("server,s", boost::program_options::value(), "Run as iperf server") - ("target,r", boost::program_options::value(), "Target IP"); - - boost::program_options::variables_map commandLineArgs; - boost::program_options::store(boost::program_options::parse_command_line(argc, argv, programDescription), commandLineArgs); - boost::program_options::notify(commandLineArgs); - - // Stat - uint32_t cs_dev = defDevice; - uint32_t target_ip = 0x0A01D498; - uint64_t useConn = 1; - uint64_t useIpAddr = 1; - uint64_t port = 5001; - uint64_t pkgWordCount = 64; - uint64_t timeInSeconds = 1; - uint64_t timeInCycles; - uint64_t dualModeEn = 64; - uint64_t packetGap = 0; - uint64_t server = 0; - uint64_t transferBytes = 1024; - - // Runs - char const* env_var_ip = std::getenv("FPGA_0_IP_ADDRESS"); - if(env_var_ip == nullptr) - throw std::runtime_error("Local IP address not provided"); - - string local_ip(env_var_ip); - if(commandLineArgs.count("device") > 0) cs_dev = commandLineArgs["device"].as(); - if(commandLineArgs.count("useConn") > 0) useConn = commandLineArgs["useConn"].as(); - if(commandLineArgs.count("useIpAddr") > 0) useIpAddr = commandLineArgs["useIpAddr"].as(); - if(commandLineArgs.count("port") > 0) port = commandLineArgs["port"].as(); - if(commandLineArgs.count("pkgWordCount") > 0) pkgWordCount = commandLineArgs["pkgWordCount"].as(); - if(commandLineArgs.count("timeInSeconds") > 0) timeInSeconds = commandLineArgs["timeInSeconds"].as(); - if(commandLineArgs.count("server") > 0) server = commandLineArgs["server"].as(); - if(commandLineArgs.count("transferBytes") > 0) transferBytes = commandLineArgs["transferBytes"].as(); - if(commandLineArgs.count("target") > 0) target_ip = commandLineArgs["target"].as(); - - timeInCycles = timeInSeconds * freq * 1000000; - - printf("usecon:%ld, useIP:%ld, pkgWordCount:%ld,port:%ld, local ip:%s, target ip:%x, time:%ld, is server:%ld, transferBytes:%ld\n", useConn, useIpAddr, pkgWordCount, port, local_ip, target_ip, timeInCycles, server, transferBytes); - - // FPGA handles - cThread cthread(targetRegion, getpid(), cs_dev); - -/** - * -- Register map - * 0 (WO) : Control - * 1 (RO) : Status - * 2 (RW) : useConn - * 3 (RW) : useIpAddr - * 4 (RW) : pkgWordCount - * 5 (RW) : basePort - * 6 (RW) : baseIpAddr - * 7 (RW) : transferSize - * 8 (RW) : isServer - * 9 (RW) : timeInSeconds - * 10 (RW) : timeInCycles - * 11 (R) : execution_cycles - * 12 (R) : consumed_bytes - * 13 (R) : produced_bytes - * 14 (R) : openCon_cycles - */ - cthread.setCSR(useConn, 2); - cthread.setCSR(useIpAddr, 3); - cthread.setCSR(pkgWordCount, 4); - cthread.setCSR(port, 5); - cthread.setCSR(target_ip, 6); - cthread.setCSR(transferBytes, 7); - cthread.setCSR(server, 8); - cthread.setCSR(timeInSeconds, 9); - cthread.setCSR(timeInCycles, 10); - std::cout << "Start" << std::endl; - auto start = std::chrono::high_resolution_clock::now(); - - //set the control bit to start the kernel - cthread.setCSR(1, 0); - - //Probe the done signal - while (cthread.getCSR(1) != 1) {} - // std::this_thread::sleep_for(1s); - - auto end = std::chrono::high_resolution_clock::now(); - double durationUs = 0.0; - durationUs = (std::chrono::duration_cast(end - start).count() / 1000.0); - cout<< "Experiment finished durationUs: " << durationUs << endl; - - if(server == 0) { - uint64_t tx_bytes = cthread.getCSR(13); - uint64_t openCon_cycles = cthread.getCSR(14); - uint64_t total_cycles = cthread.getCSR(11); - uint64_t cycles = (total_cycles - openCon_cycles) / 2; - - cout << "tx_bytes: " << tx_bytes << " open con cycles: " << openCon_cycles << " total_cycles: " << total_cycles << " single trip transfer cycle: " << cycles << endl; - double throughput = (double)tx_bytes * 8.0 * freq / ((double)cycles*1000.0); - double latency = (double)cycles / freq; - cout << "throughput [gbps]: " << throughput << " latency[us]: " << latency << endl; - } - - return EXIT_SUCCESS; -} - From 8fa45f0fe80b58377e3dd1effc9040603d5f12a5 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Thu, 8 May 2025 03:46:19 +0200 Subject: [PATCH 02/52] changed the linked network service (submodule) --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index be6de654e..2d31cd238 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "hw/services/network"] path = hw/services/network - url = https://github.com/fpgasystems/fpga-network-stack.git + url = https://github.com/pomsi/fpga-network-stack.git branch = coyote-TCP-RDMA From 801198829a2f612c83149dd2f59160c5d000b3b4 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Wed, 21 May 2025 04:49:16 +0200 Subject: [PATCH 03/52] forward ecn to flow control --- hw/hdl/network/rdma/roce_stack.sv | 3 +++ scripts/wr_hdl/template_gen/lynx_pkg_tmplt.txt | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/hw/hdl/network/rdma/roce_stack.sv b/hw/hdl/network/rdma/roce_stack.sv index 543aa530f..299b6a509 100644 --- a/hw/hdl/network/rdma/roce_stack.sv +++ b/hw/hdl/network/rdma/roce_stack.sv @@ -122,6 +122,9 @@ assign rdma_ack.data.ack.dest = ack_meta_data[32+RDMA_QPN_BITS+1+:DEST_BITS]; assign rdma_ack.data.ack.strm = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+:STRM_BITS]; assign rdma_ack.data.ack.rsrvd = 0; assign rdma_ack.data.last = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+:1]; +//MT added +assign rdma_ack.data.ecn = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+1+:ECN_BITS]; + rdma_flow inst_rdma_flow ( .aclk(nclk), diff --git a/scripts/wr_hdl/template_gen/lynx_pkg_tmplt.txt b/scripts/wr_hdl/template_gen/lynx_pkg_tmplt.txt index bb4344bf6..ac52c24ea 100644 --- a/scripts/wr_hdl/template_gen/lynx_pkg_tmplt.txt +++ b/scripts/wr_hdl/template_gen/lynx_pkg_tmplt.txt @@ -194,6 +194,8 @@ package lynxTypes; parameter integer STRM_RDMA = 2; parameter integer STRM_TCP = 3; + parameter integer ECN_BITS = 2; //MT added + // Queue depth parameter integer QUEUE_DEPTH = 8; @@ -427,7 +429,8 @@ package lynxTypes; logic [DEST_BITS-1:0] dest; logic [PID_BITS-1:0] pid; logic [DEST_BITS-1:0] vfid; - logic [32-OPCODE_BITS-STRM_BITS-2-DEST_BITS-PID_BITS-DEST_BITS-1:0] rsrvd; + logic [ECN_BITS-1:0] ecn; // MT added + logic [32-OPCODE_BITS-STRM_BITS-2-DEST_BITS-PID_BITS-DEST_BITS-ECN_BITS-1:0] rsrvd; } ack_t; typedef struct packed { From 7eb956008f45dea2355c577eee6a168b866a6078 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Thu, 29 May 2025 01:27:28 +0200 Subject: [PATCH 04/52] bug fix --- hw/hdl/network/rdma/roce_stack.sv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/hdl/network/rdma/roce_stack.sv b/hw/hdl/network/rdma/roce_stack.sv index 299b6a509..c768d2965 100644 --- a/hw/hdl/network/rdma/roce_stack.sv +++ b/hw/hdl/network/rdma/roce_stack.sv @@ -123,7 +123,7 @@ assign rdma_ack.data.ack.strm = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+:STRM assign rdma_ack.data.ack.rsrvd = 0; assign rdma_ack.data.last = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+:1]; //MT added -assign rdma_ack.data.ecn = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+1+:ECN_BITS]; +assign rdma_ack.data.ack.ecn = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+1+:ECN_BITS]; rdma_flow inst_rdma_flow ( From 98e5cdea27cedd90bcc0f7bcd64da648f0bf4905 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Thu, 5 Jun 2025 12:38:49 +0200 Subject: [PATCH 05/52] added ila cores (hopefully) --- hw/hdl/network/rdma/init_ip.tcl | 2 ++ hw/hdl/network/rdma/roce_stack.sv | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 hw/hdl/network/rdma/init_ip.tcl diff --git a/hw/hdl/network/rdma/init_ip.tcl b/hw/hdl/network/rdma/init_ip.tcl new file mode 100644 index 000000000..a65554048 --- /dev/null +++ b/hw/hdl/network/rdma/init_ip.tcl @@ -0,0 +1,2 @@ +create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_rdma +set_property -dict [list CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_NUM_OF_PROBES {5} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma] \ No newline at end of file diff --git a/hw/hdl/network/rdma/roce_stack.sv b/hw/hdl/network/rdma/roce_stack.sv index c768d2965..117ecd8ec 100644 --- a/hw/hdl/network/rdma/roce_stack.sv +++ b/hw/hdl/network/rdma/roce_stack.sv @@ -107,7 +107,8 @@ always_comb begin end // -// FC and CQ +// FC and CQ .probe0(s_rdma_qp_interface.valid), + // metaIntf #(.STYPE(dack_t)) rdma_ack (); @@ -223,6 +224,14 @@ assign rdma_wr_req.ready = m_rdma_wr_req.ready; // RoCE stack // +ila_rdma inst_ila_rdma ( + .clk(nclk), + .probe6(rdma_ack.valid), + .probe7(rdma_ack.ready), + .probe8(rdma_ack.data.ack), // 32 + .probe9(rdma_ack.data.last), +); + /* ila_rdma inst_ila_rdma ( .clk(nclk), .probe0(s_rdma_qp_interface.valid), From e03ba0da458c4e3d02bd960d4d19158a0a5e764d Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Thu, 5 Jun 2025 15:06:10 +0200 Subject: [PATCH 06/52] typos --- hw/hdl/network/rdma/roce_stack.sv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hw/hdl/network/rdma/roce_stack.sv b/hw/hdl/network/rdma/roce_stack.sv index 117ecd8ec..6e3204fc9 100644 --- a/hw/hdl/network/rdma/roce_stack.sv +++ b/hw/hdl/network/rdma/roce_stack.sv @@ -226,10 +226,10 @@ assign rdma_wr_req.ready = m_rdma_wr_req.ready; ila_rdma inst_ila_rdma ( .clk(nclk), - .probe6(rdma_ack.valid), - .probe7(rdma_ack.ready), - .probe8(rdma_ack.data.ack), // 32 - .probe9(rdma_ack.data.last), + .probe0(rdma_ack.valid), + .probe1(rdma_ack.ready), + .probe2(rdma_ack.data.ack), // 32 + .probe3(rdma_ack.data.last) ); /* ila_rdma inst_ila_rdma ( From 381749e480e0222154d5c0f865596f930ae38187 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Thu, 5 Jun 2025 16:38:10 +0200 Subject: [PATCH 07/52] ila fix try --- hw/hdl/network/rdma/init_ip.tcl | 2 -- scripts/ip_inst/network_infrastructure.tcl | 13 +++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) delete mode 100644 hw/hdl/network/rdma/init_ip.tcl diff --git a/hw/hdl/network/rdma/init_ip.tcl b/hw/hdl/network/rdma/init_ip.tcl deleted file mode 100644 index a65554048..000000000 --- a/hw/hdl/network/rdma/init_ip.tcl +++ /dev/null @@ -1,2 +0,0 @@ -create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_rdma -set_property -dict [list CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_NUM_OF_PROBES {5} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma] \ No newline at end of file diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 54f21485e..952dfdb7e 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -131,6 +131,19 @@ if {$cfg(en_rdma) eq 1} { create_ip -name rocev2 -vendor ethz.systems.fpga -library hls -version 0.82 -module_name rocev2_ip } +# MT_pomsarc added +create_ip -name rdma_ack -vendor xilinx.com -library ip -version 2.0 -module_name ila_rdma_ack +set_property -dict [list + CONFIG.C_NUM_OF_PROBES {4} + CONFIG.C_DATA_DEPTH {2048} + CONFIG.C_INPUT_PIPE_STAGES {2} + CONFIG.C_PROBE0_WIDTH {1} + CONFIG.C_PROBE1_WIDTH {1} + CONFIG.C_PROBE2_WIDTH {32} + CONFIG.C_PROBE3_WIDTH {1} +] [get_ips ila_rdma_ack] + + # Cmd create_ip -name axis_data_fifo -vendor xilinx.com -library ip -version 2.0 -module_name axis_data_fifo_req_512_used set_property -dict [list CONFIG.TDATA_NUM_BYTES {64} CONFIG.FIFO_DEPTH {32} CONFIG.HAS_WR_DATA_COUNT {1} ] [get_ips axis_data_fifo_req_512_used] From ba5fbdf27b5965aea090d872b1e328314b74785e Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Thu, 5 Jun 2025 16:45:23 +0200 Subject: [PATCH 08/52] ... --- hw/hdl/network/rdma/roce_stack.sv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hw/hdl/network/rdma/roce_stack.sv b/hw/hdl/network/rdma/roce_stack.sv index 6e3204fc9..5cd9a80f2 100644 --- a/hw/hdl/network/rdma/roce_stack.sv +++ b/hw/hdl/network/rdma/roce_stack.sv @@ -224,7 +224,8 @@ assign rdma_wr_req.ready = m_rdma_wr_req.ready; // RoCE stack // -ila_rdma inst_ila_rdma ( +//MT_pomsarc added +ila_rdma_ack inst_ila_rdma_ack ( .clk(nclk), .probe0(rdma_ack.valid), .probe1(rdma_ack.ready), From 0c3323e18af2bec44792202764ca793bbcb95acd Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Thu, 5 Jun 2025 16:57:08 +0200 Subject: [PATCH 09/52] :q --- scripts/ip_inst/common_infrastructure.tcl | 12 ++++++++++++ scripts/ip_inst/network_infrastructure.tcl | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/ip_inst/common_infrastructure.tcl b/scripts/ip_inst/common_infrastructure.tcl index c03818ee3..83d5c380d 100644 --- a/scripts/ip_inst/common_infrastructure.tcl +++ b/scripts/ip_inst/common_infrastructure.tcl @@ -190,3 +190,15 @@ eval $cmd create_ip -name axis_data_fifo -vendor xilinx.com -library ip -version 2.0 -module_name axisr_data_fifo_512 set cmd "set_property -dict \[list CONFIG.TDATA_NUM_BYTES {64} CONFIG.FIFO_DEPTH {$nn512} CONFIG.HAS_TKEEP {1} CONFIG.HAS_TLAST {1} CONFIG.TID_WIDTH {6}] \[get_ips axisr_data_fifo_512]" eval $cmd + +# MT_pomsarc added +create_ip -name ila -vendor xilinx.com -library ip -version 2.0 -module_name ila_rdma_ack +set_property -dict [list + CONFIG.C_NUM_OF_PROBES {4} + CONFIG.C_DATA_DEPTH {2048} + CONFIG.C_INPUT_PIPE_STAGES {2} + CONFIG.C_PROBE0_WIDTH {1} + CONFIG.C_PROBE1_WIDTH {1} + CONFIG.C_PROBE2_WIDTH {32} + CONFIG.C_PROBE3_WIDTH {1} +] [get_ips ila_rdma_ack] diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 952dfdb7e..4c7ed2d15 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -131,18 +131,6 @@ if {$cfg(en_rdma) eq 1} { create_ip -name rocev2 -vendor ethz.systems.fpga -library hls -version 0.82 -module_name rocev2_ip } -# MT_pomsarc added -create_ip -name rdma_ack -vendor xilinx.com -library ip -version 2.0 -module_name ila_rdma_ack -set_property -dict [list - CONFIG.C_NUM_OF_PROBES {4} - CONFIG.C_DATA_DEPTH {2048} - CONFIG.C_INPUT_PIPE_STAGES {2} - CONFIG.C_PROBE0_WIDTH {1} - CONFIG.C_PROBE1_WIDTH {1} - CONFIG.C_PROBE2_WIDTH {32} - CONFIG.C_PROBE3_WIDTH {1} -] [get_ips ila_rdma_ack] - # Cmd create_ip -name axis_data_fifo -vendor xilinx.com -library ip -version 2.0 -module_name axis_data_fifo_req_512_used From 86384ea5bdd2acfb8f36cafc8f3f8581191c382e Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Mon, 23 Jun 2025 01:14:04 +0200 Subject: [PATCH 10/52] safety backup --- scripts/ip_inst/common_infrastructure.tcl | 11 ----------- scripts/ip_inst/network_infrastructure.tcl | 6 +++++- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/scripts/ip_inst/common_infrastructure.tcl b/scripts/ip_inst/common_infrastructure.tcl index 83d5c380d..f7157bd4f 100644 --- a/scripts/ip_inst/common_infrastructure.tcl +++ b/scripts/ip_inst/common_infrastructure.tcl @@ -191,14 +191,3 @@ create_ip -name axis_data_fifo -vendor xilinx.com -library ip -version 2.0 -modu set cmd "set_property -dict \[list CONFIG.TDATA_NUM_BYTES {64} CONFIG.FIFO_DEPTH {$nn512} CONFIG.HAS_TKEEP {1} CONFIG.HAS_TLAST {1} CONFIG.TID_WIDTH {6}] \[get_ips axisr_data_fifo_512]" eval $cmd -# MT_pomsarc added -create_ip -name ila -vendor xilinx.com -library ip -version 2.0 -module_name ila_rdma_ack -set_property -dict [list - CONFIG.C_NUM_OF_PROBES {4} - CONFIG.C_DATA_DEPTH {2048} - CONFIG.C_INPUT_PIPE_STAGES {2} - CONFIG.C_PROBE0_WIDTH {1} - CONFIG.C_PROBE1_WIDTH {1} - CONFIG.C_PROBE2_WIDTH {32} - CONFIG.C_PROBE3_WIDTH {1} -] [get_ips ila_rdma_ack] diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 4c7ed2d15..090314bc7 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -579,4 +579,8 @@ set_property -dict [list CONFIG.TDATA_NUM_BYTES {4} CONFIG.FIFO_DEPTH {128} ] [g #create_ip -name dhcp_client -vendor xilinx.labs -library hls -version 1.05 -module_name dhcp_client_ip -#update_compile_order -fileset sources_1 \ No newline at end of file +#update_compile_order -fileset sources_1 + +# MT_pomsarc added +create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_rdma_ack +set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_NUM_OF_PROBES {4} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] From 71f8bc99f1673d3aa76996d7afd055deb28c9776 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Fri, 4 Jul 2025 17:33:40 +0200 Subject: [PATCH 11/52] test --- hw/hdl/network/rdma/roce_stack.sv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/hdl/network/rdma/roce_stack.sv b/hw/hdl/network/rdma/roce_stack.sv index 5cd9a80f2..2342707f7 100644 --- a/hw/hdl/network/rdma/roce_stack.sv +++ b/hw/hdl/network/rdma/roce_stack.sv @@ -123,8 +123,8 @@ assign rdma_ack.data.ack.dest = ack_meta_data[32+RDMA_QPN_BITS+1+:DEST_BITS]; assign rdma_ack.data.ack.strm = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+:STRM_BITS]; assign rdma_ack.data.ack.rsrvd = 0; assign rdma_ack.data.last = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+:1]; -//MT added -assign rdma_ack.data.ack.ecn = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+1+:ECN_BITS]; +//MT added changr back +assign rdma_ack.data.ack.ecn = 3;//ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+1+:ECN_BITS]; rdma_flow inst_rdma_flow ( From b5dd9dd4d6b8a69e4fed23cee8bd0783f9bf6399 Mon Sep 17 00:00:00 2001 From: pomsarc Date: Sat, 19 Jul 2025 18:11:17 +0200 Subject: [PATCH 12/52] added readable parts :) --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 170 ++++++++++++++++++ hw/hdl/network/rdma/roce_stack.sv | 4 +- 2 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv new file mode 100644 index 000000000..208e9f6a4 --- /dev/null +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -0,0 +1,170 @@ +import lynxTypes::*; + +module cc_queue +( + input logic aclk, + input logic aresetn, + + input logic ecn_mark, + input logic ecn_valid, + output logic ecn_ready, + + + metaIntf.s s_req, + metaIntf.m m_req +); + +localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; +localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); +localparam integer RD_OP = 0; +localparam integer WR_OP = 1; + +localparam integer Rt_default = 100000; +localparam integer Rc_default = 100000; +localparam integer N_min_time_between_ecn_marks = 50; +localparam integer Rai = 50; + +localparam integer S1 = 128; // 1 << 8 +localparam integer Sg = 8; // (1/16) << 8 +localparam integer F = 5; + +localparam integer time_threshhold = 100; +localparam integer byte_threshhold = 10; + + + +logic[31:0] Rt; +logic[31:0] Rc; +logic[31:0] Sa; +logic[31:0] timer; + +logic[31:0] timer_send_rate; + +logic[31:0] time_of_last_marked_packet; +logic[31:0] time_last_update; + +logic[31:0] byte_counter; +logic[31:0] time_counter; + +logic[4:0] step_counter; + + +//typedef enum logic[2:0] {ST_IDLE, ST_CALC_SR, ST_ADD_TO_QUEUE} state_t; +logic [2:0] state_C, state_N; +metaIntf #(.STYPE(dreq_t)) queue_in(); +metaIntf #(.STYPE(dreq_t)) queue_out(); + +logic ecn_data; + + + + +always_ff @(posedge aclk) begin + if (aresetn == 1'b0) begin + state_C <= ST_IDLE; + + Rt <= Rt_default; + Rc <= Rc_default; + Sa <= S1; + timer <= 0; + + timer_send_rate = 0; + + byte_counter <= 0; + time_counter <= 0 + + ecn_ready <= 1; + + step_counter <= 0; + + + end + else begin + timer <= timer + 1; + time_counter = time_counter + 1; + + ecn_ready <= 1'b0; + ecn_data <= ecn_mark; + + s_ack.ready <= 1'b0; + + s_req.ready <= 1'b0; + req_out.valid <= 1'b0; + req_out.data <= s_req.data; + + if(ecn_valid) begin + ecn_ready <= 1'b1; + byte_counter <= bytecounter + 1; + if(ecn_data == 1'b1) begin // Marked Packet arrived + if(timer - time_last_update > N_min_time_between_ecn_marks) begin + Rt <= Rc; + Rc <= (Rc << 8) / (S1 - (Sa>>1)); + alpha <= (((S1-Sg)*Sa)>> 8) + Sg; + time_last_update <= timer + end + time_of_last_marked_packet <= timer + end + end + + if (timer - time_of_last_marked_packet > N_min_time_between_ecn_marks) begin + alpha <= (((S1-Sg)*alpha)>> 8); + + end + + if(time_counter >= F || byte_counter >= F) begin + + if(time_counter >= F && byte_counter >=F) begin + // case Hyper Increse + // Still missing + // + + time_counter <= 0; + byte_counter <= 0; + end + else begin + // case Additive Increase + Rt <= Rt + Rai; + Rc <= (Rt + Rc) >> 1; + if(time_counter >= F)begin + time_counter <= 0; + end + else begin + byte_counter <= 0; + end + end + else begin + // case Fast Recovery + Rc = (Rt + Rc) >> 1 + end + + + + + + + if(s_req.valid & queue_in.ready) begin + if(timer_send_rate > Rc) begin + timer_send_rate <= 0; + req_out.valid <= 1'b1; + s_req.ready <= 1'b1; + end + end + + + end +end +end + + + +queue_meta #( + .QDEPTH(RDMA_N_OST) +) inst_cq ( + .aclk(aclk), + .aresetn(aresetn), + .s_meta(queue_in), + .m_meta(m_req) +); + + +endmodule \ No newline at end of file diff --git a/hw/hdl/network/rdma/roce_stack.sv b/hw/hdl/network/rdma/roce_stack.sv index 2342707f7..3a90c7800 100644 --- a/hw/hdl/network/rdma/roce_stack.sv +++ b/hw/hdl/network/rdma/roce_stack.sv @@ -123,8 +123,8 @@ assign rdma_ack.data.ack.dest = ack_meta_data[32+RDMA_QPN_BITS+1+:DEST_BITS]; assign rdma_ack.data.ack.strm = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+:STRM_BITS]; assign rdma_ack.data.ack.rsrvd = 0; assign rdma_ack.data.last = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+:1]; -//MT added changr back -assign rdma_ack.data.ack.ecn = 3;//ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+1+:ECN_BITS]; +//MT_pomsarc added +assign rdma_ack.data.ack.ecn = ack_meta_data[32+RDMA_QPN_BITS+1+DEST_BITS+STRM_BITS+1+:ECN_BITS]; rdma_flow inst_rdma_flow ( From 8f54baff008329495f5a4b85ef36db496d98d94c Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Fri, 8 Aug 2025 05:24:59 +0200 Subject: [PATCH 13/52] added DCQCN bench --- examples/MT_pomsarc_09_CC_Testbench/README.md | 186 ++++++++++++++++ .../hw/CMakeLists.txt | 32 +++ .../hw/src/init_ip.tcl | 2 + .../hw/src/vfpga_top.svh | 71 +++++++ .../img/rdma_results_read.png | Bin 0 -> 130953 bytes .../img/rdma_results_write.png | Bin 0 -> 131862 bytes .../img/rdma_system_overview.svg | 1 + .../img/rdma_traffic_pattern.svg | 1 + .../sw/CMakeLists.txt | 26 +++ .../sw/src/client/main.cpp | 140 ++++++++++++ .../sw/src/include/constants.hpp | 9 + .../sw/src/server/main.cpp | 118 +++++++++++ .../rdma/MT_pomsarc_CC_queue_wrapper.sv | 199 ++++++++++++++++++ hw/hdl/network/rdma/MT_pomsarc_benchy.sv | 135 ++++++++++++ hw/hdl/network/rdma/MT_pomsarc_new _flow.sv | 189 +++++++++++++++++ scripts/ip_inst/network_infrastructure.tcl | 3 + 16 files changed, 1112 insertions(+) create mode 100644 examples/MT_pomsarc_09_CC_Testbench/README.md create mode 100644 examples/MT_pomsarc_09_CC_Testbench/hw/CMakeLists.txt create mode 100644 examples/MT_pomsarc_09_CC_Testbench/hw/src/init_ip.tcl create mode 100644 examples/MT_pomsarc_09_CC_Testbench/hw/src/vfpga_top.svh create mode 100644 examples/MT_pomsarc_09_CC_Testbench/img/rdma_results_read.png create mode 100644 examples/MT_pomsarc_09_CC_Testbench/img/rdma_results_write.png create mode 100644 examples/MT_pomsarc_09_CC_Testbench/img/rdma_system_overview.svg create mode 100644 examples/MT_pomsarc_09_CC_Testbench/img/rdma_traffic_pattern.svg create mode 100644 examples/MT_pomsarc_09_CC_Testbench/sw/CMakeLists.txt create mode 100644 examples/MT_pomsarc_09_CC_Testbench/sw/src/client/main.cpp create mode 100644 examples/MT_pomsarc_09_CC_Testbench/sw/src/include/constants.hpp create mode 100644 examples/MT_pomsarc_09_CC_Testbench/sw/src/server/main.cpp create mode 100644 hw/hdl/network/rdma/MT_pomsarc_CC_queue_wrapper.sv create mode 100644 hw/hdl/network/rdma/MT_pomsarc_benchy.sv create mode 100644 hw/hdl/network/rdma/MT_pomsarc_new _flow.sv diff --git a/examples/MT_pomsarc_09_CC_Testbench/README.md b/examples/MT_pomsarc_09_CC_Testbench/README.md new file mode 100644 index 000000000..f5387b7fd --- /dev/null +++ b/examples/MT_pomsarc_09_CC_Testbench/README.md @@ -0,0 +1,186 @@ +# Coyote Example 8: Using the FPGA as a SmartNIC for Remote Direct Memory Access (RDMA) +Welcome to the eigth Coyote example! If you've made it to this point, you've already learnt all the essential concepts of FPGA-CPU interaction for data movement and control flow. Thus, we're adding a new component by demonstrating how Coyote can be used to turn the FPGA into a SmartNIC for 100G RDMA-over-Converged-Ethernet (RoCE v2), allowing for efficient, high-performance data exchange between remote processing nodes and FPGA-offloaded preprocessing of the data streams. As with all Coyote examples, a brief description of the core Coyote concepts covered in this example is included below. + +##### Table of Contents +[RDMA Overview](#rdma-overview) + +[Example Overview](#example-overview) + +[Hardware Concepts](#hardware-concepts) + +[Software Concepts](#software-concepts) + +[Additional Information](#additional-information) + +## RDMA Overview +Remote Direct Memory Access over Converged Ethernet (RoCE v2) is a high-performance networking protocol originally developed for High-Performance Computing. It aims to combine high throughput, low latency and low CPU-utilization by offloading the network stack and direct memory access to the Network Interface Card (NIC), thus grounding its performance claims on host-bypassing and zero-copy: applications running on the host CPUs directly expose memory buffers for DMA to the NIC, so that data can be exchanged without including the host CPU or the OS running on it. In this example, we demonstrate how Coyote can be configured to use the FPGA in the role of such a NIC, allowing for fully protocol-compliant RoCE v2-traffic at 100 Gbps. +For further understanding of the RDMA example, we need to shed some light on the intrinsics of this network standard. +For RDMA communication, two nodes are connected via a so-called Queue Pair (QP), based on previously exchanged information such as respective IP-addresses, location and size of the allocated memory buffers and access keys, and comprising of the idea of queues for work commands and data. The initial exchange of RDMA operations are managed through so-called InfiniBand-verbs, which describe the form of memory transaction between the remote nodes. We have to separate between *one-sided operations* that initiate data transfers over the network without a previous request for consent, and *two-sided operations* where every data transaction is preceeded by an additional exchange between the communicating nodes. Coyote implements the two one-sided RDMA operations: +- ```RDMA WRITE```: One node writes data of certain length to a remote buffer in the other node. Every sent WRITE packet has to be explicitly acked by the receiving node. +- ```RDMA READ```: One node requests to read data of certain length from a remote buffer in the other node. The READ REQUEST packet is implicitly ACK'ed when the remote node sends back RDMA RESPONSE packets with the requested data. + +**IMPORTANT TERMINOLOGY:** +To have a better understanding of this example and the following descriptions, a short overview of the RDMA-specific terminology is very helpful: +- *QPN*: Queue Pair Number. Identifies a RDMA connection between two remote nodes. +- *vaddr*: Virtual address of the remote exposed buffer. For setting up a Queue Pair, a node has to share the virtual address of its exposed buffer with the remote node. +- *rkey*: Remote key for accessing the exposed buffer of the remote node. The remote key is communicated alongside the virtual address as a form of access control. +- *PSN*: Packet Sequence Number used to organize and structure a communication flow between remote RDMA-nodes. Normally, PSN-sequencing is handled by the FPGA-offloaded network stack and thus is not visible to the user. However, following the PSN order is very helpful for debugging RDMA networks. The initial PSN is agreed upon between the remote nodes as part of the initialization of the QP. +- *MTU*: Maximum Transmission Unit, defines the maximum size of a packet. Since RDMA operates on the notion of buffer transmission, the length of the data to be transmitted determines the number of MTU-sized packets that are required for the operation. While the MTU is a compile-time parameter in Coyote, the default size is 4 KB. This means that a ```RDMA WRITE``` of a buffer of up to 4 KB size results in a single packet with ```RDMA WRITE ONLY``` opcode, while a buffer of 8 KB size would be transmitted in two packets, one carrying ```RDMA WRITE FIRST```, the other with ```RDMA WRITE LAST```. For even longer buffers, an arbitrary number of ```RDMA WRITE MIDDLE``` packets is inserted in the exchange. + +## Example Overview +This example measures throughput and latency of RDMA data exchange between two remode nodes via a 100G switched network with both ```RDMA WRITE``` and ```RDMA READ``` operations. Assuming the simpler write functionality, we can very generally think about the process as moving of a data buffer from the host CPU to the local FPGA, where it is streamed to the FPGA-offloaded RDMA stack and then sent via the network to the remote FPGA. There, data is received and then written to the previously specified address of the remote buffer of this remote host CPU. Different to all previous examples, this requires two servers with FPGAs, which also run different software application. The active node, which initiates communication exchange, is referred to as the "client", while the remote node is used as "server". An example of the dataflow is given in the figure below; and as shown in the figure, the steps are the following: +1) *QP Exchange*: As explained above, the RDMA connection in form of a Queue Pair needs to be set up between the remote nodes before the actual benchmarking test can begin. Since RDMA cannot be bootstrapped, this requires out-of-band communication between the two remote CPUs via classic TCP sockets. In the context of the HACC, we are using the 10G management network for this purpose. Using ```ifconfig```, the IP address of the CPU determined to be the server can be evaluated and then given to the experiment code as a parameter. The two nodes follow a standardized protocol to exchange relevant start-up information, such as the IP- addresses of the FPGAs in the 100G data network, the initial PSN and the address and access key of the network exposed data buffers. +2) *QP Setup*: After having exchanged these essential pieces of information, both CPUs forward the aggregated QP data to the FPGAs to create the endpoints of the RDMA flow in the hardware-offloaded network stack. This means making information such as the target IP address or buffer virtual address retrievable by the QPN as essential key. +3) *REMOTE WRITE*: In case of a WRITE-based benchmark, the client now begins with a ```REMOTE WRITE``` Coyote operation to transmit a local buffer via the network to the remote node. For this, both data and commands traverse the RDMA-allocated vFGPA and reach the RDMA stack on the FPGA, where RoCE v2 packets are created and then sent out via the network. +4) *Receiving data*: In the above example, the server FPGA receives the incoming RDMA packets and automatically checks for correctness of the PSN-sequence and QP-specific information. If the received packet fits into the expected communication flow, it gets ACK'ed: an ```RDMA ACK``` is sent back to the client as a reply. At the same time, the server FPGA sends the received data as a ```LOCAL WRITE``` (see Example 1) to the local buffer on the CPU. +5) *Processing ACKs & creating completions*: The client FPGA is constantly listening to the network for ACKs for its outstanding packets. If an ACK is received, the original work command for this transaction is marked as completed, which is also communicated to the local client CPU. If no ACK is received within a certain time interval after sending out a packet, a retransmission is issued. In this case, the same packet is sent again, assuming that it was originally lost in the network or not properly received by the server. + +

+ +Generally speaking, throughput and latency tests behave vastly different, also depending on the chosen mode of operation (WRITE vs. READ). The different cases are depicted in the figure below and can be understood as following: +- *Latency* for ```RDMA WRITE```: The client issues a single WRITE of a buffer of specified length to the remote server. Upon reception, the server ACKs and then writes back this very buffer to the client, thus creating a typical "ping-pong pattern" of communication, for which the two-way latency can be measured from sending out a buffer to receiving it back. Depending on the specified experimental arguments, this exchange is repeated for a certain number of times before the average latency of all transmissions is reported. +- *Throughput* for ```RDMA WRITE```: The client issues *n* WRITEs (depending on specification of the argument in experiment execution) of a buffer of specified length to the remote server and waits for all required ACKs. Upon reception, the server writes back the same buffer *n* times again. +- *Latency* for ```RDMA READ```: The client issues a single READ of a buffer of specified length from the remote server. Instead of ACK'ing the server sends the requested data via ```RDMA READ RESPONSEs```. +- *Throughput* for ```RDMA READ``` In this case, the client issues *n* READs (depending on specification of the argument in experiment execution) of a buffer of specified length from the remote server and waits for data delivery via ```RDMA READ RESPONSEs``` from there. The server in this case does not issue reflective READs to the client. + +
+ +
+ +**IMPORTANT:** When executing RDMA benchmarks, it's important to always start the server's software first before doing the same on the client. The reason for this lies in the intrinsics of the QP exchange: The server software is constructed to listening for incoming TCP connections from the client to then take the passive role in the off-channel exchange of information. + + +## Hardware Concepts +The core complexity for RDMA in Coyote is hidden from the user within the network stack. The RDMA-dedicated vFPGA is mainly used for connecting interfaces without any additional user logic required. +The previously introduced send- and receive-queues are connected as following in the vFPGA: +```Verilog +always_comb begin + /* + * CONTROL SIGNALS + * + * rq_(wr|rd) are two more Coyote interfaces, which act as inputs to the user application + * They corresponds to network write/read requests, set from the host software and driver + * Here, they are used to set Coyote's generic send queues, previously discussed in Example 7. + */ + // Write + sq_wr.valid = rq_wr.valid; + rq_wr.ready = sq_wr.ready; + sq_wr.data = rq_wr.data; // Data field holds information such as remote, virtual address, buffer length etc. + sq_wr.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host + sq_wr.data.dest = is_opcode_rd_resp(rq_wr.data.opcode) ? 0 : 1; + + // Reads + sq_rd.valid = rq_rd.valid; + rq_rd.ready = sq_rd.ready; + sq_rd.data = rq_rd.data; // Data field holds information such as remote, virtual address, buffer length etc. + sq_rd.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host + sq_rd.data.dest = 1; +end +``` + +On the other hand, the data interfaces are connected as following in the module: + +```Verilog +/* + * DATA SIGNALS + * + */ +// Data streams for outgoing RDMA WRITEs (from local host to network stack to remote node) +`AXISR_ASSIGN(axis_host_recv[0], axis_rreq_send[0]) + +// Data streams for incoming RDMA READ RESPONSEs (from remote node to network stack to local host) +`AXISR_ASSIGN(axis_rreq_recv[0], axis_host_send[0]) + +// Data streams for outgoing RDMA READ RESPONSEs (from local host to network stack to remote node) +`AXISR_ASSIGN(axis_host_recv[1], axis_rrsp_send[0]) + +// Data streams for incoming RDMA WRITEs (from remote node to network stack to local host) +`AXISR_ASSIGN(axis_rrsp_recv[0], axis_host_send[1]) +``` + +Thinking one step further, beyond the scope of this performance benchmark, it becomes obvious how any pipelined user logic can be placed on these interfaces to process incoming or outgoing RDMA traffic. Placing customized user logic directly on these datapaths is one of the key benefits of a FPGA-based SmartNIC such as Coyote with the network configuration. + + +## Software Concepts +As described above, the main notion of logic abstraction in RDMA is the Queue-Pair (QP), which connects two remote nodes. Both nodes first form their own local Queue, which again is directly linked to the memory buffer that is exposed via the network. Afterwards, an off-channel exchange via TCP/IP is started to exchange the local queues and then form a QP. + +In Coyote, the notion of a QP is directly linked to a ``coyoteThread``. Thus, the first step of setting up RDMA in Coyote is creating such a thread: +```C++ +coyote::cThread coyote_thread(DEFAULT_VFPGA_ID, getpid(), 0); +```` + +Following the creation of the thread, it's necessary to call the function ```initRDMA()```, which allocates an RDMA buffer of the requested size and performs the QP exchange between the client and server. Importantly, the client needs to pass the server's IP address, while the server code leaves this field blank (nullptr, indicating that it's the server). The function return a pointer to the memory buffer to be used for RDMA communication. +```C++ +int *mem = (int *) coyote_thread.initRDMA(max_size, coyote::defPort, server_ip.c_str()); +``` + +After the setup is completed, we follow the standard approach in Coyote: creating a scatter-gather entry and issuing an ``ìnvoke(...)``` call, but this time indicating a remote operation: +```C++ +coyote::sgEntry sg; +sg.rdma = { .len = curr_size }; +coyote_thread.invoke(coyote::CoyoteOper::REMOTE_RDMA_WRITE, &sg); +``` + + +## Additional Information + +### Special remarks on building Coyote for RDMA experiments +Since different software versions are required for server and client to run a benchmark, two cmake builds have to be triggered to obtain the correct executables. In order to build the server code, one needs to specify `-DINSTANCE=server`, while a build of the client software is specified with `-DINSTANCE=client`: +```bash +cd sw/ + +mkdir build_server && cd build_server +cmake ../../ -DINSTANCE=server && make + +cd ../ +mkdir build_client && cd build_client +cmake ../../ -DINSTANCE=client && make +``` + +### Command line parameters and hints on running the experiment +As said above, it's crucial to start the software for experiments first on the node that we want to use as server, before doing the same for the client. Furthermore, it's important that the IP address specified as argument on the client machine **belongs to the server-CPU (not the client CPU, not the server FPGA)**. The different available network interfaces can be explored with ```ifconfig```. +The following description helps to match the relevant command line parameters to details of the experiment execution described before: + +- `[--ip_address | -i] ` IP address of the server CPU for out-of-band QP-exchange via TCP-sockets before the actual RDMA experiment can begin. +- `[--operation | -o] ` Decides whether the benchmark is performed for WRITE (1) or READ (0) operations. Default: 0 +- `[--min_size | -x] ` Minimum size of transferred buffer in the experiment. Default: 64 [B] +- `[--max_size | -X] ` Maximum size of transferred buffer in the experiment. Default: 1048576 [B] ~ 1 [MB] +- `[--runs | -r] ` Number of test runs, to obtain statistically significant results For latency-tests, `r` ping-pong exchanges will be executed. For throughput tests, `r` independent exchanges of 64 messages are executed. + +### Network debugging +Coyote provides tooling to check the status of network transmissions and identify potential problems, most notable packet losses and retransmissions. These statistics can be queried via +`cat /sys/kernel/coyote_sysfs_0/cyt_attr_nstats`. + +A typical output for this could look like this: +``` + -- NET STATS QSFP0 + +RX pkgs: 316 +TX pkgs: 242 +ARP RX pkgs: 4 +ARP TX pkgs: 2 +ICMP RX pkgs: 0 +ICMP TX pkgs: 0 +TCP RX pkgs: 0 +TCP TX pkgs: 0 +ROCE RX pkgs: 245 +ROCE TX pkgs: 240 +IBV RX pkgs: 240 +IBV TX pkgs: 240 +PSN drop cnt: 0 +Retrans cnt: 0 +TCP session cnt: 0 +STRM down: 0 +``` +In this case, 240 RDMA packets have been received and sent (``IBV RX/TX``), no packet has been lost or retransmitted. A major problem would be indicated by ``STRM down: 1``, as this would signal a complete shutdown of the networking stack. In such a case, only a hard reset including reprogramming the FPGA can save the user. +Apart from these Coyote-provided utilities, standard network debugging tools such as switch- or external NIC-based traffic capturing can be used to understand traffic issues and patterns. As described in the next example, we also offer a 100G-capable traffic sniffer for Coyote that directly produces pcap-files compatible to any standard network analysis tool such as wireshark. If even this low-level debugging capabilities are not deemed satisfactory, also classic ILA-based probing directly on data- and controlpaths of the FPGA-design is possible. + +### Expeted results +The following results are expected for reads (left) and writes (right): + +

+ + +

\ No newline at end of file diff --git a/examples/MT_pomsarc_09_CC_Testbench/hw/CMakeLists.txt b/examples/MT_pomsarc_09_CC_Testbench/hw/CMakeLists.txt new file mode 100644 index 000000000..e729f3666 --- /dev/null +++ b/examples/MT_pomsarc_09_CC_Testbench/hw/CMakeLists.txt @@ -0,0 +1,32 @@ +# CMake configuration +cmake_minimum_required(VERSION 3.5) +set(CYT_DIR ${CMAKE_SOURCE_DIR}/../../../) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CYT_DIR}/cmake) +find_package(CoyoteHW REQUIRED) + +project(example_08_perf_rdma) +message("*** Coyote Example 8: Remote Direct Memory Access (RDMA) [Hardware Build] ***") + +# Enables two streams from host memory (1 for requests, 1 for response) +set(EN_STRM 1) +set(N_STRM_AXI 2) + +# Incldue Coyote's RDMA stack during synthesis +set(EN_RDMA 1) + +# Number of vFPGAs (user applications) +set(N_REGIONS 1) + +# Build with optimization, since timing closure with RDMA can be difficult +set(BUILD_OPT 1) + +# Confirm that the selected options are allowed +validation_checks_hw() + +# Load a user application in Configuration #0, Region #0 +load_apps ( + VFPGA_C0_0 "src" +) + +# Create the hardware project +create_hw() diff --git a/examples/MT_pomsarc_09_CC_Testbench/hw/src/init_ip.tcl b/examples/MT_pomsarc_09_CC_Testbench/hw/src/init_ip.tcl new file mode 100644 index 000000000..64dc40e3d --- /dev/null +++ b/examples/MT_pomsarc_09_CC_Testbench/hw/src/init_ip.tcl @@ -0,0 +1,2 @@ +create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_perf_rdma +set_property -dict [list CONFIG.C_PROBE17_WIDTH {128} CONFIG.C_PROBE14_WIDTH {128} CONFIG.C_NUM_OF_PROBES {18} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_perf_rdma] \ No newline at end of file diff --git a/examples/MT_pomsarc_09_CC_Testbench/hw/src/vfpga_top.svh b/examples/MT_pomsarc_09_CC_Testbench/hw/src/vfpga_top.svh new file mode 100644 index 000000000..240e06b7e --- /dev/null +++ b/examples/MT_pomsarc_09_CC_Testbench/hw/src/vfpga_top.svh @@ -0,0 +1,71 @@ +always_comb begin + /* + * CONTROL SIGNALS + * + * rq_(wr|rd) are two more Coyote interfaces, which act as inputs to the user application + * They corresponds to network write/read requests, set from the host software and driver + * Here, they are used to set Coyote's generic send queues, previously discussed in Example 7. + */ + // Write + sq_wr.valid = rq_wr.valid; + rq_wr.ready = sq_wr.ready; + sq_wr.data = rq_wr.data; // Data field holds information such as remote, virtual address, buffer length etc. + sq_wr.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host + sq_wr.data.dest = is_opcode_rd_resp(rq_wr.data.opcode) ? 0 : 1; + + // Reads + sq_rd.valid = rq_rd.valid; + rq_rd.ready = sq_rd.ready; + sq_rd.data = rq_rd.data; // Data field holds information such as remote, virtual address, buffer length etc. + sq_rd.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host + sq_rd.data.dest = 1; +end + +/* + * DATA SIGNALS + * + */ +// Data streams for outgoing RDMA WRITEs (from local host to network stack to remote node) +`AXISR_ASSIGN(axis_host_recv[0], axis_rreq_send[0]) + +// Data streams for incoming RDMA READ RESPONSEs (from remote node to network stack to local host) +`AXISR_ASSIGN(axis_rreq_recv[0], axis_host_send[0]) + +// Data streams for outgoing RDMA READ RESPONSEs (from local host to network stack to remote node) +`AXISR_ASSIGN(axis_host_recv[1], axis_rrsp_send[0]) + +// Data streams for incoming RDMA WRITEs (from remote node to network stack to local host) +`AXISR_ASSIGN(axis_rrsp_recv[0], axis_host_send[1]) + +// Tie off unused interfaces +always_comb axi_ctrl.tie_off_s(); +always_comb notify.tie_off_m(); +always_comb cq_rd.tie_off_s(); +always_comb cq_wr.tie_off_s(); + +// ILA for debugging +ila_perf_rdma inst_ila_perf_rdma ( + .clk(aclk), + .probe0(axis_host_recv[0].tvalid), // 1 + .probe1(axis_host_recv[0].tready), // 1 + .probe2(axis_host_recv[0].tlast), // 1 + + .probe3(axis_host_recv[1].tvalid), // 1 + .probe4(axis_host_recv[1].tready), // 1 + .probe5(axis_host_recv[1].tlast), // 1 + + .probe6(axis_host_send[0].tvalid), // 1 + .probe7(axis_host_send[0].tready), // 1 + .probe8(axis_host_send[0].tlast), // 1 + + .probe9(axis_host_send[1].tvalid), // 1 + .probe10(axis_host_send[1].tready), // 1 + .probe11(axis_host_send[1].tlast), // 1 + + .probe12(sq_wr.valid), // 1 + .probe13(sq_wr.ready), // 1 + .probe14(sq_wr.data), // 128 + .probe15(sq_rd.valid), // 1 + .probe16(sq_rd.ready), // 1 + .probe17(sq_rd.data) // 128 +); diff --git a/examples/MT_pomsarc_09_CC_Testbench/img/rdma_results_read.png b/examples/MT_pomsarc_09_CC_Testbench/img/rdma_results_read.png new file mode 100644 index 0000000000000000000000000000000000000000..efc149483e13286d8c1704987b5085e0bb1f47c4 GIT binary patch literal 130953 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

Mi0$EN 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

SJQT_*5a%AsT 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

-f^R--}5GfRj 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 z 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 b^FI+Ie(1zcrai7h0RJ96P?9N?GW7p{s90*( literal 0 HcmV?d00001 diff --git a/examples/MT_pomsarc_09_CC_Testbench/img/rdma_results_write.png b/examples/MT_pomsarc_09_CC_Testbench/img/rdma_results_write.png new file mode 100644 index 0000000000000000000000000000000000000000..83b0413f6627d8529212911d5e4d0a6dbfc56b0e GIT binary patch literal 131862 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 P;nQ? zSWGd#|E-8Y3l$(M`GFl^I5={t(s5-LvCW7#`2eJLL{kx;D<0gn0SX4V0)kg{HGvbK zoll@a>XJbyBY!K^LF`wYRhmT7&Z<7gkKtfW&MRO)B(5A)_nN}c7bN_{>u_`*OJ_Yw z3~h(%xZti4^^7Yj<8F={M_G;4J?-0gzcZ!%giBhrnL%>BD8(N=3>(5LGF3I`Li`HM zR3~z*w4i-v5@7gDN2glIJt}EdHN<%~$TvtHBZ}uvo#jFl1*x*UA|uTUiK;4=VLb#a zOy#vz!gp;TD(grYx?asdN^6i)qE_N1@SGdBR4S+QQ38{_rBy;%FWAGE{D=a|KIt$5 zF{0}yius_2@xzcHICEq_>tc-l*2RbzujmoL#RyQluw0wDg8*y>2E7L9=LTv3lq7_a z6mXzjxgjZVTT4O&&Xs5ML_uwZ)gU)YRF9HEcivx3Zq?wtB$HQ9uNd(#9y1p+S$km* zm_mWwYO9MW2dde?e}rEESq#IoEM^yV{u=#+6T1!QPMOk2c5R&au9+F#;uuY=kMp9XHGZx|!*}bT6ESG*kq5IcJNwr(Wq&l`BU=`+%=gCF(tC$nSo;Mkh zUY*zbfsTE^Pp66g3Bvh6)AL#4xCY4h?|3tr%-Fl8t&k;jbgJEeRFT`jzFqYvV8Q$< zF5$v$lPm~F(N@BisWR#IwVkP1L|vG`sMy#i7gVu{NQS}b+J=w%YL4BP^EmI}0Z_~+ zSm&%YM=g1NTinn=KI<;oS7C1=FE_TNoAU+xzJmyX+<}7rs>5S{m?7`SL4iU3j!%$u zv+bZZRQ>!Vg=lOp%^8j48HSmIhP~3}+cOq4h{so*B^VNYa5iYv$G&wV6Wc2F zFlD{Ex-8KWfaUtHmEfNMQwcwCL=)f`gde4wx+i2-XBc~xGKjVM@S9|mav1O$JbiT< zc`K)Vp3Cy(@@(Zjb+bZ*q4DW#@a>`Odi2U4IEMJk#Jhq?(|32KW4^D-<0o)}nWxa@ zNX7H8O4&O3+5Sn#n7K*+MaL{gdYchNDZ{a7boGE9|Xf=%YVS zJxm|3b~ZN>%Nlgv1i3!;M;Kz3#ub^ZjYy5;jp1%H7QU20Tt()TPmVTwwNe&cVY1lj z-$C4Qy11)iw(vaS;A7@~dH=U2!B=tC61y&zh)d&lw5N4h^W|>$@H+&YU~mCgWm*bCf!laOd-hxtm1cFXZntLP0QmUcsdolKnW>Oc>U&EP zh=y&SM!0!OJ9jo8G?j%Ub|D*fd#*(33HmEh8DfL|N|IR&klQE9hh)oMtwr!Q{8KBl z2P)@MpPO&FJKU><_pdkv`V7sxm_jp=`UX-SIjGQv^jI1SUq&IT(gFo~dto3vm#dhJ zdvUj`t7q2?M%6!EGyRPxfo(Jscp@vG4}MJF9tAMkyIQ7z1oRItYJSkQQ1rb_<}&6G zDaQz)MyKDgZGET_kNL<7L;KCz)r>5$$#fF+z%L5$T;Mr1rc30=_Lf-}80+Ug;1nRT z;J%p0IIR9M_X33`Ku0SVpx1VK*0TM>c?D}~S$fRdvpsdMJhUHVt?G?ZP#-K*wKuwv zTg<#c$})FdiF}9WlQV`QK?8}*5IX`dGlX;?=UY;zz384&f)mc>34AKIP2leTDD-%} z-gQSABe(WOaCYox2h=(^)>xK_lklWF%n)K3r%othN28f=Yql$CpA$!sY-d#_`qOVE z!k=rO)8Hu@zMcQ;K^>s3R7uA@Vlw1JXJ`Y{Xo^b|l>TKiVN>eeVY`9>5D~A2&R%tVu8Z^9zy>L&uZHnOxJ$x|BZ&sK%4(OCqn}s z1zxa;i!4&Ipz2Dl#R!>1>vZOOjD*HTkdV~_X*GrjrM0S{D&>f%!JbTdJ=>0iY#4OB zoy!L)H<3g-NH~GK)t&))#|{-DkwcW{W-TVGG<&c?BLt;bu0|rL=5o$5uj9i75r%ob zal%_^0n(omp;6Wj;nMRD`g~C0Hm> zSV_ag&9WdZU?r{t_zq+mKqi^1l^^j)^}X0kBv_KMH#>n0Xmuh|j7+cJ=m!#Q9lRzh z-gMxmMiu>2u|R#ji%6h7r+{l;VI9X9ICq!{VRR#6H4w*y*or~4KyDj%QP$$F48f>U z2CVDqU;A)up6iuH#T2i4V#&q5GI3?p@je=cziFyLGo-%Ap>g3=c!qeUyM>Zr|6NiO z{8Vg7v>Xj^BGDvf>kcSSUneC)>vk%*9MfaC9%XLHgnUSm>E_E$hbr(8bpv0c?;y9y z$}w}yBYf<2+Cx@b*Zr3d)$j+Ej0!6;RrI6-7S(ZdQ~Sy>*_k0Z({3G$j|#Fom@?P{ zP1V;ZC1gXQ)HP1a7W(VestWaER-=FuB8*w4-W|LzbX<P>UWiC zAV$oH6Qff``rnn|ihl$&qU@xh>T$!_bOQ<4UVIC1fircX6MlHH`>}lxu#X5pWhe)Nn1>i3uTO zW--+~ZIpw_>2p{=@SX_b317vzwv3f#Z{$u}udC|_&il(&Ip1|Se;XM2+9oB{Ma-E9 zS7Y#@! zqR7PDM-)o0*`Em>ww!R$+ra`MJHS_CZ_=%@Zx}RcSkc}g{`wmLQjv$j7iE+0-vBb; z!vhw77fhc>#{^gG|Lm)w7@%c3H2BT`afg84nWfClI_n~W2Vi&-u(V&BT1P1%8fk?@ z;aqmbDz5F6sm*f2V=*TSY8Y>N!v)!<*ebGDJTpcw)@sqn@y2{9P5`?xGHob}4jMKF zsgeJ*jjMYb?HdQ6ifqBWr2Yntw|T~{Ac&oK!)-N=xRV}RnZB(| z&xFZ*y@NU&eX;>Mmr==5CLhXrQ&LW#)1p(m^o2NGY9trP>f!29;+ce*2wMlV!o$uyA zFib?!Hw(w)6F230TAXG_XXG z({{8j-DY+6x)VIB%#YTCXcVI$CQ()xJtU5L;^Pogd9tUrnl}&fBPPa}MW_h90psST zi`(Ib&)Ssl@OcdfvMok(QlAqJjEsZrQ%N+nSr)C#P24*Jl$HuAI295kcn)+U^Bz1> zQabEF))2aPHendk=~l;!<&TU62VeuLnt$7AN?`>c~d9AZ9Ub?;bv zPL9)5Z28&Cuw_P2#K(CPLS0A>C&LuvV!CRhp0SBn}=c9)*;*3k>RH8x3U7WK< zF*-sK9D#V!#w>9xA0^hqt+nT9>jnGd?$!Pl-SEy*m7$t-`T;#y8Q>cP6*QkEH->CG z-Hb)WbGD0D9TU-|X5Kse<=RSfs&%MR5U$E*r9d%diho6dz?kzAq>GZy5wA&BAqIQR zDZLbU&Mia58G`T7=IO7aTO_Y?t$O>ipNnSONAzWcER75#C*e}=D78yqe`@j!c4$7pUx>zUe1&D54OJw z&rj)XmAtXp)QCfWpzaEO2}9u= z1oH`9DvFexHt1df2~k>!Tcn<1z3;UKoD3$FsZZdFSQIJgC%Pc=<8Ag{r-Flnq_mx+ zv_+5Q((T!_r1XeM0qx02l}TR5H0>+EWh^knEbGy_bM%u5^j#rsjfb6U5GO`gKtUf*k|Vrq zFz{DzLYGnVk9cPyaJ*-85yFKNU3z1$N&ud+JNBhll57OZ z@n+GR=4&RK+G@_<#uHrM;qwk(r6`Q!-9QWkJBm|J0?OaR$gTx(+UW*(k+t||pJ$Oc z`?P)p0*tZU)8;;}lwI^?e`JI$6Ozm?rY;}W96nF&513Ly-Utka_FQrtg~FDHw%P}G0(m3v?E~LeiWObCpYVvsf$N~ zgO>n$TcQ!~p(r$I{(eB+TTPhWe~8@a7o_wW4GScX`?i+>ExAdG)(XC}#>!5NM9Vw9 z;E$l{j_!&Wi?!M=_Xm=^u-MosGWV|oTyb6jXR;3c>{YXYHDf z&RAy7i=#O_!iv>Djmj%osto1sb(#Hh0wn;cZnr+W#QDN2K#LSll*## zqU71*q*=DFuD`IIWL}S=FjZ$^taI9f32;gMusnLd*jlT<7-Ad|A02LKZ}Pp@smXY` z)kyxa9EK6e4LsMT-j^4WLejy515+(;F$x*Ym})PyIX$0aR#>C;zj?!4w>-?~f#d~TUv#U+21>5bHOFM>IoVy}KG zTrVVVoHS zlKC3S%+(N!P`SniR*U*V-52`|>yL|ysobpk?EUc9JhKMwjgwJl`9BFVEKh?rK?!~FSB+yc-r-ZRV zeh$t3p3roUkY5RI*FrbCQc{etMPTbA&hcQQqv7kKhr#i|i;*vIOoC)es&jO!Y&*ZH z-JJ$#V@lC@x8j9B!|tZHTP*cMr&SXrrS4wG86%+UVe)g%u^k(A#;fOdxn;L2v=z?F zOz0YSf#!If&j|JB8V@>4eVin_C}h;ec=ViKSIR|xq=I)hi3_c2$^Y~V**R?yS~Ci> z^_YY)4j>gdpB@OAOyY`BstwNnb=qPVu=r0GfPv0KClobwX@Sh_TzUDX+RF{okn8I= zuRGjvhTNjo-dqroeW0aN{~bWGNAOK6gLAUQ<_mH46(BcytxeU4rOR6(qxAhru5zq) z%EckN>&uTaX^k8*g`c%*j_IYyks5RSi>J)buXUTGMT48z8K)Rx zd7KRJneqCO#b)x7m1P94?Fv`0f1|p-uV0dPk+OzwAmq%QMnO^*v~|P%IiBJQ0D~jq zzxO&yqM)E4_;R=-e7~$9$AE?PhUW3r0k8t)i&N|f6$^A#__@^>zjEy?tHgYgz1nbU90gx;wj!!yw4nXPetYy z_H8`V5A5HWZSi1Cxpy2)BW8J9LWhV^iBa*58Gcwhp`L}xA%6ZT9-7Hw!$!IyT`N#Q ze!7YPN67miK->T0-%q+J!{CXyoPEETDFgUq;-ji9j(v!L6M=90KY#dcC_k#CgtQWx z;WA4@}id19L`;q7@0Pz!dDk)&A(&>cKUwtxQuPd|SU zVa4YGpokd6sJ#y02EAUkJ6KR1@u&VFe@FNC+wy&*1gsMnV*i%yIXqu8Y1qUphVjo) zz(d6dK$FM^DunmHG_+VjL(8Ek+}+rcf4uCBX~kkR=lNC&@U!#dK=~a6bOV72XLWVI z>0r?I77VIIDMB-VK=I8?wRJBb^)uG1fVP7Lfnx~`_0W59H7tj6j%I&vpl}G<1@8Q0 zDDVJj4a4*=Z;6soz$Bc}`~HVLcuI4oa*bwEEAN40Xova|fKEz?`Sdryj}nOj2`n+` zR7~;_Ql^9Jp?v{n#aHiv`-BD9WR!FlfT-k&4u~L#0sGUc>{~<>*Lwu_5_F9MyeSQD z!0_U`@6V|0W(|z`I@slp9UV?Eq6q?{jix!NQ^}IAug4lZ|0{lTHZUN~c*^ zbVV`qwozhM0r!${YkbA@6fels4|^RDBmK$|4BeQB zsGRqpdCa2pnY{M*d&TRiMtQ3Spckx3-@`wHZ>jnw-)w8OzR=cxU%Z(KL*QK>vU?!D zVmcN{<`}u274%OijUAgoVL$&NA{ylla3J-4qB9C7#rr(&;|oHTz5oL3u?Hwy&opj> zg_AOzu{nd^{Kc%O%I20}4`&;g2`ZU&jZ;;3;R`Xake2-kUvL)n-w#V;vg%aVm1Md>h(Jyi^3*t$ z>qlo#xTgcSqE*2qR<7CA;I>ga(MN6n?1A?;VmZ_#jUQr=nW)AsSs@Y4i>q&X9*epE zIeJfk{Xn-9qRvJBV0XEK<^-gXu^?cw9dDr^s||~-+=PAd$1RXtN~w1?fDJpY_32N1 zNVfzSKhyS7a}FDD7z(EG*gljsAHR4H97U`EsYxXY7aFk*Sy5nZCaiXQr2OHh1W1wW?518}<6@Q7i|68vfpbSwK^aN~LI)=M11 zP+1?}s z9=AS~>&fr(W>kRm?(K0Wi7E?V`>e0xyN=~0uSVX)X=pmecx2?Oq$Pidv0zCYdMdKF zx9(4%|0Y`Qi?ZN!jz&*!413*rou+Z~CrSstmlo-6U&wumVz%QQfDAU^2UTFm-j*!OAU@-mG-N_AaxC@ zwdVu{T9*tnIE>V7FrX1m76Z(XwT{)@K4>+SyhjjhY>1GrS7W-?wwtWC-{9tLfMLv@ zDB`Z2`syNyby9<-h4pZXXcGe*Iu66eUs*q{7XUtKivp<|syv3(VTU2(W?YTue*wX4 z*VaUr4K{t5m;mFo)MWu=5~>?9&ci9ziG*}QUUA7hsX%tRYAeoh)fj1s;! zKyY2kwtY=&i2tbh%2f_SVK%KR_?1cQ*+Dr4m>)eQ&ah}ZzgFaoa``ReH;*w!OaZ1O z@WimR_4caT6LLKTvTMD!6boP>B3J!5#xq91boQnoH~y!Av9&yT293gm0oFfolErdf z>C^!tMy=ow&xjCVTM!-pbA&IQ3hXxgmmq^M*?7>ufLfBA*uw!lSo3lD`ezTX{rT|l zpKkjH!kZh|a=blU92Y>?knalz4D6eej60j}Edagy;(N0KDFN=C{a@r(^pul{z2z|k zc9JJaLo6Q2Ul{*LIbywRyMMy|=LaQ%Oj3auM;w*;{^1Ol_ich{KRpTb>FEi<)wqgm z4qXmb{)7;IS?eJdX5;eMa3~;y5}?Z@89zFG6|T3pFW5FH>Wq%lsR9Fdp-Z@pwgz<| zHSzVLEDZdUI|OY~oL`*+W$j=CRi&3R_e&3%l{l#fpxHJYq{ML2!DgvWu4Yn&WUAnU zq)YN!yal%_!N=>vm4628ePWX4FHq>!P#{XTp-2{Eu&-e}$bt#aK|{!?t>oycC9Hj3B@(*Id>`L+g(owGk$ljXX1#*i6-+eMGH|mgz#4BXz2_Fl2A=u-M?WG&oQPkzM9VFo< zK7uQe-vRWHb>VW*O*fAxCp#^X`gWCFvX`_ob`SG7xUJHO!30HM!3SJaI~6XhN`txH zD3EnCKS3+Cg3hcz@>iAa6^OtpMP{NGAZ)NzK}C2eDE9L8pZX8gWf4C1o<0?}`>wnt zFP1NZmTEJIUo<5%wA^%6AD5;ajwu_xX>lDR^?p|00hbDl_2` zWpy|i$VaM-$PwZsvti^5+2X~;`h;#%>4GYFv8wvx?WcyVOsa|2&};gEqhkEjyw+@m z{JHt(Uxqze?u*RntSmD!f&jBCq5ukaj?L4_mh+m}g;C+$Tsb&4`Hw>ztQG^exbwUaN@*&kM+Qj+xx1ZQ9z|S6&&o$ggZ1@mKW9 zhJQGSSTMJ_axE+!ElmbNm{<$Z2%> z;%9S5Y9&Wdn@~&@(`0}3JN~fjtW}ct?`5c@ByP|^P)K5jH9IZMYGiX&*a=WW<{?6> zP^?=R4q-}CVRDoxl*Bir-e8<-k0^Dq?s+@?`hArUX)IW2b&&Xj4#Ss3bL;}9X>zjm82F}2i5Nwe8gu(A6yJBX-Gxv=$D@oY!C*(ji zvuKsM+_bKCJR@$tdKW3HyMo=9%?d@~5Myqf7Jdax0c9?yUJgsB@kF+t znKB*w^G4Z0_6yTPf;kG?(nNq^6rR6f(gh0k=N6Et+x>?VZyzHPV++7=S#Jo$rG*mx+QzLV4D6TRqxo;{GUm+jjTlJMTEG3j_3`T79d^RAORAIsquOR#o)nZY3Rs^7L0AJdq8 z0+>)B{fh7g*wdcpN=RXbTJRj^LKFIrJt(hWXhF$ONQgwgyB@zvb*IC_D-UGI4}t0< zI&|ByNw+6-jCyV*#9tIpQPm^4#yT4N2KorILZlWjK9`|Mc;**rlvrL&kii+;{S{F6 zOC1dsh;fm~?G;8#90DsZ6<2-iGG#mF_-eY?hY&==%0njrPeEn2K-rcf)EW zA~NUJ-hP|09qnd*yNU@TdcG)2*OxjPoUOMriBok?;1y-j?s?YNG<3OZX?dkbzUcrI z=WhEuNq3Iy-ImDMYR5*Z<5IDd^~drA3$O_?;N?}PsaYMel9%M=Afg|KTvD{4M@Cyt z)13iJMxExHdl&d@sD)F;`Vy^OzXtF0eakaSncKsAHVkq8B8an0TEM9*jMYc%`8CZf zIBO!cJ%K`ZNGM6gMC>J-fGAAF)a-qgXUNP_=*FaCxzuWZ#bC#nFMYb?7mU-*|>v z|0^Jc#4E=<7h2K~WG{B#aO9oU-Skb-Vi%6r4-(`^f#TxOYHr^7))3_CGc?`mog_}D znnmDVOtE`Z=BmV4%!B$pxr{hqGpO4x1gZY4BrHOsEle-J*wT8LPo6KIpF(STRSF)U z8e_+xjJmw`dTw)4nAOSCvV8k>IX@@&N6H9bjhyM`1$uX zbLrUCrg!$tX`}$I9b{bsFeEXY=>H>$`=EX!Bxo|dysb&ZzpK?1rvlT}Uz$yd^|OII zZLKcs6E~82U{b(0fW4MdU2|svm@s-0GA@7oYFjen7SU^XZBT zxd}LevP?^6H1D!|+r0Q$`}SF}X3AqPsl}DIpBWAtz&Om<0S1}UyL;?e?ET+epoTqe zEYN@QrXVI($BV0%;IXDASi-4-ggz1Skt|?Kyvc5tzy77=_3vh{Gp=x+l-9G{^^Ul3 zy&MRv0Y)bu^k{;LlP^`+vjhj+Mt%5QVPt|cH+=T~-8yYwy%$cs)lU=^nur7+l@5=m zfX&->Gs%rf;U^@8WSiB2PX=>HF(Q5k6NB<|qj5=PO>1i{2~@25y8uH zF8AM?tE0EYSM%(RjC*X2{ETL!UDv?!82)U&g5Mer_kAPFhx;a?3E{Pud)~oa(nCn; z@m20MY4TMBQ(F2ob9UU{w5bRKAw#;GFngZ96?S1bhSW*j=5Hw|+JzM=a&ghFzTX6) zpKI2d;uy^BF8RvNHpzT{Cv~GQ_T3737Gaiie|+{eC<|6MOT+SKlH5vOL>?dS|7v=L z0?|Mr!Szw_Ix1^pQit&1Dke%jM0%m#L)PTb{{$)M1z$4HlpGcTc@uI|gv62z>9KQ% z{=xk9Rp97hR-kJFTIgr->ycj?%;aADRGnxb zCx&p_CWH6RFv#(qGW`u$7Y%qBe1jANz^4y$)UQJLw2Si|`HZjN!OP}1xnqe-UR0!3 zrasuVJHT!lb>)m=1n;l(shTS2_)JP7wSzgVMIMH{^!rS1J*=!e>QdLb$zbI8OE+In ze}X@F&v6+39FMC{J?+si>0%Mc#o~i#EAt9s2+g~%yw<~{9&j%Y6HCb_>vqeovLd(v z&R|s`9tR9g%c{eaVP0#F>6^K0widABu{8Cr9@!$g?V`(&%i!L_H`sVHBKfOrjoq2a ztj=8i=ffju-E-XP`(D_k}?Q)Zbcc0--@_YrehF! zySHOLuk4Z_9L*pfW0?4_fkl6FSeV3{4%L^IqGo1)r0^0(yTfGt#=~ zxEe4L@PDelY``rRY0b9#jDChXob%<$KXRRAz5MLn;=#AOOGxyKiEfa7k)7g}MZtkA zlO*;aO#46_H1aLXC+RFrTx>f)FX*eAJl=#$nnuN1eP{C_%f0s=!mr~;B2$TG6#8hU z3@b{8{kDW-I=| zm;81a(tK&MK5$=Bb46oKz@x_b@#t1kB4OIb&Z?}ZIED;7VOZpse?q0RI$HxSSGm;I zTYC_FyhZ!zLnHKVtDJ}*LFqIeO0bnX8S+kW3Y1`Ze+!GSzRmsJe5PiwrWO&*c4*zV zpY@$@a21Xjziw`bhvG}9v@I>+-|m!I2UEHHc@WVaz=g) zr1s^_0S5D)&*Fzb+F@##KS$Ve_|wEgAL*PiAKwt_1LioDkYnDgmGQxc$|X+2%Vyq| z1La{85DGL7tl_)Ft9GNEMS7_Gu^mf5x43Oz3V0tG#!8G!_xv2575RCxltTe82F*Nv z@%N6}DX@^wYHQ{)-)M58k<<(3+Kc&%y9w_7`nt|y=Tl4T+PLQ4A5M{x(&aa&QS)|H z*Z5jO3e=#>>r zY%U3%r#fN<>6L44=n|th6-|*ZWDO1;11ma6O}-iF-_%Enz%E3+i+e&O{o&aqyO?^5k>u!l=nek(Vu6HN9gf9S~?q3$4zr5CCFHs(_EJ@Oo=E7 zia&pYrMRS~{7BPW(!{QhX*932-lX%&M(Y12%_Y>>($?|Rs}ZY9^W+Io?c(+<`8ipT zO5~YSt!#jc3q4c_Z$&4M(1B$l{m#y)TBg9ZTq>Mn%!siNBJ6uD^E;PnwY$&DBH>v- z))5gB{G*;B{gHt^pYe4t$>cVYfTgo8A&N{AL45D5czi8*+OeUcsPLR1-)gCVDW2FU z8KpV&C!*4egrK6ao>CSUERL+~?*f(oS@h!u|Xvp>`E z&srQyiBpuD1#Lu~N2?Ic9CRo}I+PL)pNrLaR}&(ya|uQqXKq9POz7Fym>)>YCV1pa z^C!Gz46vsv!nZg%oTJD!Ge2DB%+D}!eYlLXJklOr8v^xwUBJ2U00qLJ+QB!v^*~$Y z%gEFG;QQ+=g3PP-&z8=qLn%_^sjiHz)i*_Y;tf{kOM!rALq`{W+%XitJ_d%M#lvM% z9EUS7JCyE1`%ifOIA)wUsK-iR9ketzj#LnN>g9?V(UEDQWrXUT>c*9w#Hq*9Uyh#h z+e~~->R5H!Jp#u0Va1cm7PNo%K(0%+X)mlQu2GUq=- z;yOo^6C$j3ML-jy1=VTXRUTd55$Wwbt@wcH*#0-ZZUV7G*f;^@X*uT(Gbxusu8+N& zLA{nRNur-4_p&Ud&o`iD=Q&U%u!yVeElQjglTRjxNW}2cH4T;u1TZrE7rrHVpCOkF zxZP%|jEte6n*O}aee(`bQ)*PH=hQab8-USpj zfN^Nu5(tl0<$$yG|D*!W9oBM|2rJYJb2!Da9>)vFl|Kc%&t*b~y@Y2W<)-@E?V&;a zQ$KPzQi*6^_PzX+DhnftKvK;fP|8nd+mszUt%bnH{|a`75HyQfQTd0+dosh9$V6HN z0z2ZkNB`kj1QO+%(N*qb#3_uAc>IQUoY{i6er_2gf5ci5z~BDSPXBM7MecPEnUG;h z2a@Q)-G%R6f)2w9&(0rUAz4-4xW}G$j&W3LHpOE#_nF9D1<(}u`&rZ;u(X2o7BN{RI_@P$S<2~J~Pu&@p(~z*VgY`g;jpt!} z{t<@}LC)5Cu?OY)yPEGfbj*?b?rKSY>X}Gu15ETVt93Al-^zq4+hMB9fD`JbK0FvJxxs>T9*;ATIBPmhFU?mnwhNsD0D7gJD$iU z_R09wKIcv@2Q^Z*Ctz+p)|*N1MiV9~^uL&uh|CQE{$EcxOHzNj&=k$hH@WvcAnb8p z)bnl=D{F|cw*-yRPeXX9MwV8ux38J#BwdfV4IFl;W_&e{_uPxmaf5q86uzO8Wt@W^-e!phaRVvMF$8Lp2?b~~VWmVPw(3c>P44Q3cJ%vI z%PvhbH*;czqZO6zTbcmA`n6EfL^F9YPm$-$g*4r3`G``y91mxmOo0$svY#t2en>gMNY z2%_LI_p}O2p8<_TN|beX=`bDGinYo_N+-OLp#EoaxWuu$Dc#EeC8_WER76EEl$)D58kdcTu%C{8YOv{WI+uyJY%);4Zo+X(Rppu^;tyV zh;4i6>pX2cY#=e27W1OYXav3F80^|;zAPTp(Y{Kvc@s)wnUxUBYp`KqrZWnhu^-D< zRi$WqdmrUK)`~)4UJEmn*hE4A1Q57b>BM&3-@co1Z13l=$9w#%rP6lO4eIcNfWE#; zCH`a^D0MzlJ!+PJxRL9$0G2I6wi74MXl!Hozu6cw0BYqujpfeVO%KOV<4pd|vvx9H zh5qSb(55k%;6$Uw-s2kkVDi<>e1?oV;XrADmaWdiC++y>;&tF%5JISvRTr(YVN!k5 zq>%qdxQK-J_TEj_pG6~wCKw2bddv{EAqMNzq8`Aqk`IlOOWio}{C?Bqv-Bvo3^z-D zt>TrqoOKLOs6Nn_j2WFRr}X+i|2F${TuIsVzT88YihbSO{8x`Nk@OoSH3A6^kO=j? zRGs(Oep^)LuT7^UjW*%I50Z1D{DSiWiH`Slk1Uk73TM*bqo0%m`2SdbiEF7D%noBI zE%KK8NgXc?HJWRwFtJuNIHP3~(bP&gIgXx&(Aa0S(;pLF-ZG^P?j#)_Fr}TZ6st7D z6%Rws1AYA`xjPO_sWCIr#a;!8SF;bE7-varQb@>d;*=05YeBk0*IrNM6luFY)a-<9 zcED`w<|f`cQ)Qr)JUz<0hv7gTR%PJzg^FrfAa=~jPYl9j9R!~g!t1=1eMk7jW^gJYL!x5cLM?(i3?_J)68c$Rh(f)^>I1c1u} zjG|n7&-60Q*~CUWyz-;&Eas-YTD>`)o*Tq(!1;@x_(RQN{~=6TjQ`>%!qyMk9ZVU{ z7aY#@id{OT5Fa(XPLCFjJk#cXesY`gBTB}$@aHH05F7u~pLnDgU&q${wq!>f7p3Y34y7G@k&T1KA{ISQ9*?3pGo73CJx(Gl5A(!8T6ER8@f^ zzi<^K7unSi6YeT~xT-OH^IIud;J&xhYP9?_e^rc#XRi(cLY$_QBFOClM*YVu_JFad z7lc+t&2|FTgS7z|jJ^P&A~OP@xEJBJDz6I>LwFVd7ZXiCGVt9m+ZbVLS_RF}#(+k{ zf(?WM1c&t#6d)na%42WPS|o$=V9=9+1X`#5hsxxTzQT{l-k4$Bwc3-Zz>L|y3WA1S z)Gqj3a~wXLqJGvKyX7`E?%z@(auA;e^p_~Zz*loW7axJ9gl2$J-ha>(&K8+XwMnH% zOsVe6pL)SxC4EZ=8C0s3Kwg&XYy%qiTM8Ov8FW+@Xt16+K&)=*t!Dx2NGtO?Xs2iV zb{1R|T6wZ`3Kl5ARu-ndv0gpY(~yfMW8_CG)i%X={^A>pgd?Ah)k zMc(hPISlHcJD$EEe5p=(0%W4PSN8MwjmLTS=Z8=lS2Q9B7gkyzj_CzXi^+~r(}}Am%i zE3cM6f;HLJ z*8#t;XzebfYyTyuoi&IBDJ;h%L?J(<3=^e*>3zaNo)$sa$u#h#a$;5m=pW!5{)%V=W#ctKJ|Nhv7OC?o!Bn9k3?9-B4jUT{4lm`nU z1oy$fGh+`B_rg1%qm3mI6iMI=tG2e(M6@G`nKYoh;jfu<0xwBWRjN^)Ax}ta4cj zlQ(9IyuO!;Gg7aolB|eeP!JomGh^l2g=QrM-8F+KLe!2dXqk|^$NO-g>lwbc&XZaJ zPjfe}zzre7eFuWh6ngm9AA-_zh;3}G7dNT#D>W}#0l$Y7nIAsAN$;)u+?zbw^X{J) z3kODFe-q=0Si29J9Fl-J;LqP`0F~K|#W6ya^q4L_USVfVR4D88$GX}&J)@YPPK*`K zkj;A&`hapwJyFoM?5MRA zx0)-nEamXbB>ssZGC?!^X0zz5M=x$(Fj#fAG}^~Vd$EH_FDgGh{;?Gp->%q4sohl= z_m)cH8f`)z?EzELTtRA_*8p5e-7n<^L5u~q9(7{G99ZPq#{R?=vpMGiF1f1bpyWVK zf?Z{ZmYC=V7P@s{#jDd!zmL59X{6KMPj%|iBa1KHW7;w_+=`2+W)Yp#F*IytAWVs^ z2UoNcDWDudPLvjvcnSumFH{zCiBQqU2AvD9xMZ1 zZ+<8Xi3C-;PeuEY_E^heVCmb7I0$cmu}0-_0-u%{aWrP#f{)jXQDPl;-S79`Sq&jSLNbyZK&?W$3=G2CWB4y|RZpz_D;U;<^58-O$#5-Twfqip+JiZO>BT zB*~fB`fK&vhf`E4sq+6t=A;=gLKb&c!0*(eR+Z7EiLCg@hVdsNl8~bC6yibFg`08@ zkw#IG4`el0*1!WT4uCk2=ho#kWxWRz z++I=XvRgQ|uu)_`Ue{pptuQ}H0j;%C)RtKsXmU;+vW7h?_R@~-r-g{vy7Z(A`#BG4d%qYDg4@=%F3b$|?@jnE&O@v*A?~*ATdsiP|PA`CuX* zws!Mpv@_G1`~uF}ujXwy+P{O>5CmK8t~QtA;@Eln2_Lu%YGkGj&XCQk6r`B3doof{ zUZW+`^Ugd$3wZhBRHzTE$Kf0l3+KhS(bDy}E4Ya3j|W;C($By1a_ z68?lI52a|TzbT>4U*hgbjwXQq<2KV~g+Rc;9Yrf#?tMEpdrp7;cF*G+Z-8oyorKvs z{kksp5OPkREM+`9#@^ejm%*|F!xG{+wvxCM$PyI#Qi5dnfRxe#b~M7S*if1zH%7Mxk3pSlZqQ{GpQ*nVVJw)hTu7 z#;WcvS!Q4* z$?y`m?tBNXJ4x`S75FpPct(@6@v^b~=79uePrr-*V`MLIeJ;e?#+=rcEPf$mgr

kf5|yR$meVdgHLD+sJmHPj@ZLh9O?3l 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 z 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 threadCoyote driverDRAMCoyote threadCoyote driverDRAMvFPGAvFPGARDMARDMAFPGAFPGASwitchMeta-Exchange:ip_addr, QPN, PSN, rkey, vaddr, sizeTCP / IPRoCE v2122QP-SetUp: QPN, vaddr34OUTGOING: Command & DataREMOTEWRITEdata={1, 2, 3, …}INCOMING: Command & DataLOCAL WRITEdata={1, 2, 3, …}5INCOMING: CompletionDataHeader \ No newline at end of file diff --git a/examples/MT_pomsarc_09_CC_Testbench/img/rdma_traffic_pattern.svg b/examples/MT_pomsarc_09_CC_Testbench/img/rdma_traffic_pattern.svg new file mode 100644 index 000000000..cc7223ad0 --- /dev/null +++ b/examples/MT_pomsarc_09_CC_Testbench/img/rdma_traffic_pattern.svg @@ -0,0 +1 @@ +LATENCYTHROUGHPUTRDMA WRITERDMA READCPUFPGACPUFPGACPUFPGACPUFPGACPUFPGACPUFPGACPUFPGACPUFPGARDMA WRITEACKRDMA WRITEACKRDMA WRITERDMA WRITERDMA WRITEACKACKRDMA WRITERDMA WRITEREAD REQUESTREAD RESPONSEREAD REQUESTREAD REQUESTREAD REQUESTREAD RESPONSEREAD RESPONSEREAD RESPONSEREAD REQUESTREAD RESPONSE \ No newline at end of file diff --git a/examples/MT_pomsarc_09_CC_Testbench/sw/CMakeLists.txt b/examples/MT_pomsarc_09_CC_Testbench/sw/CMakeLists.txt new file mode 100644 index 000000000..c3e977433 --- /dev/null +++ b/examples/MT_pomsarc_09_CC_Testbench/sw/CMakeLists.txt @@ -0,0 +1,26 @@ +# CMake configuration +cmake_minimum_required(VERSION 3.5) +project(example_08_perf_rdma) + +set(CYT_DIR ${CMAKE_SOURCE_DIR}/../../../) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CYT_DIR}/cmake) +find_package(CoyoteSW REQUIRED) + +# Add source files +set(INSTANCE "client" CACHE STRING "RDMA software build targe: client or server") +if(INSTANCE STREQUAL "server") + set(TARGET_DIR "${CMAKE_SOURCE_DIR}/src/server") + message("*** Coyote Example 8: RDMA Server [Software Build] ***") + include_directories("${CMAKE_SOURCE_DIR}/src/include") +endif() +if(INSTANCE STREQUAL "client") + set(TARGET_DIR "${CMAKE_SOURCE_DIR}/src/client") + message("*** Coyote Example 8: RDMA Client [Software Build] ***") + include_directories("${CMAKE_SOURCE_DIR}/src/include") +endif() + +# Create build targets and link against required libraries +set(EXEC test) +add_executable(${EXEC} ${TARGET_DIR}/main.cpp) +target_link_libraries(${EXEC} PUBLIC Coyote) +target_link_directories(${EXEC} PUBLIC /usr/local/lib) diff --git a/examples/MT_pomsarc_09_CC_Testbench/sw/src/client/main.cpp b/examples/MT_pomsarc_09_CC_Testbench/sw/src/client/main.cpp new file mode 100644 index 000000000..1f2dbac0a --- /dev/null +++ b/examples/MT_pomsarc_09_CC_Testbench/sw/src/client/main.cpp @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2021-2024, Systems Group, ETH Zurich + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. + */ + +#include +#include +#include + +// External library for easier parsing of CLI arguments by the executable +#include + +// Coyote-specific includes +#include "cBench.hpp" +#include "cThread.hpp" +#include "constants.hpp" + +constexpr bool const IS_CLIENT = true; + +double run_bench( + coyote::cThread &coyote_thread, coyote::sgEntry &sg, + int *mem, uint transfers, uint n_runs, bool operation +) { + // When writing, the server asserts the written payload is correct (which the client sets) + // When reading, the client asserts the read payload is correct (which the server sets) + for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { + mem[i] = operation ? i : 0; + } + + // Before every benchmark, clear previous completion flags and sync with server + // Sync is in a way equivalent to MPI_Barrier() + auto prep_fn = [&]() { + coyote_thread.clearCompleted(); + coyote_thread.connSync(IS_CLIENT); + }; + + /* Benchmark function; as eplained in the README + * For RDMA_WRITEs, the client writes multiple times to the server and then the server writes the same content back + * For RDMA READs, the client reads from the server multiple times + * In boths cases, that means there will be n_transfers completed writes to local memory (LOCAL_WRITE) + */ + coyote::CoyoteOper coyote_operation = operation ? coyote::CoyoteOper::REMOTE_RDMA_WRITE : coyote::CoyoteOper::REMOTE_RDMA_READ; + auto bench_fn = [&]() { + for (int i = 0; i < transfers; i++) { + coyote_thread.invoke(coyote_operation, &sg); + } + + while (coyote_thread.checkCompleted(coyote::CoyoteOper::LOCAL_WRITE) != transfers) {} + }; + + // Execute benchmark + coyote::cBench bench(n_runs, 0); + bench.execute(bench_fn, prep_fn); + + // Functional correctness check + if (!operation) { + for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { + assert(mem[i] == i); + } + } + + // For writes, divide by 2, since that is sent two ways (from client to server and then from server to client) + // Reads are one way, so no need to scale + return bench.getAvg() / (1. + (double) operation); +} + +int main(int argc, char *argv[]) { + // CLI arguments + bool operation; + std::string server_ip; + unsigned int min_size, max_size, n_runs; + + boost::program_options::options_description runtime_options("Coyote Perf RDMA Options"); + runtime_options.add_options() + ("ip_address,i", boost::program_options::value(&server_ip), "Server's IP address") + ("operation,o", boost::program_options::value(&operation)->default_value(false), "Benchmark operation: READ(0) or WRITE(1)") + ("runs,r", boost::program_options::value(&n_runs)->default_value(10), "Number of times to repeat the test") + ("min_size,x", boost::program_options::value(&min_size)->default_value(64), "Starting (minimum) transfer size") + ("max_size,X", boost::program_options::value(&max_size)->default_value(1 * 1024 * 1024), "Ending (maximum) transfer size"); + boost::program_options::variables_map command_line_arguments; + boost::program_options::store(boost::program_options::parse_command_line(argc, argv, runtime_options), command_line_arguments); + boost::program_options::notify(command_line_arguments); + + PR_HEADER("CLI PARAMETERS:"); + std::cout << "Server's TCP address: " << server_ip << std::endl; + std::cout << "Benchmark operation: " << (operation ? "WRITE" : "READ") << std::endl; + std::cout << "Number of test runs: " << n_runs << std::endl; + std::cout << "Starting transfer size: " << min_size << std::endl; + std::cout << "Ending transfer size: " << max_size << std::endl << std::endl; + + /* Coyote completely abstracts the complexity behind exchanging QPs and setting up an RDMA connection + * Instead, given a cThread, the target RDMA buffer size and the remote server's TCP address, + * One can use the function initRDMA, which will allocate the buffer and + * Exchange the necessary information with the server; the server calls the equivalent function but without the IP address + */ + coyote::cThread coyote_thread(DEFAULT_VFPGA_ID, getpid(), 0); + int *mem = (int *) coyote_thread.initRDMA(max_size, coyote::defPort, server_ip.c_str()); + if (!mem) { throw std::runtime_error("Could not allocate memory; exiting..."); } + + // Benchmark sweep of latency and throughput + PR_HEADER("RDMA BENCHMARK: CLIENT"); + unsigned int curr_size = max_size; + std::cout << "Size: " << std::setw(8) << curr_size << "; "; + + coyote::sgEntry sg; + sg.rdma = { .len = curr_size }; + + double throughput_time = run_bench(coyote_thread, sg, mem, N_THROUGHPUT_REPS, n_runs, operation); + double throughput = ((double) N_THROUGHPUT_REPS * (double) curr_size) / (1024.0 * 1024.0 * throughput_time * 1e-9); + std::cout << "Average throughput: " << std::setw(8) << throughput << " MB/s; "; + + double latency_time = run_bench(coyote_thread, sg, mem, N_LATENCY_REPS, n_runs, operation); + std::cout << "Average latency: " << std::setw(8) << latency_time / 1e3 << " us" << std::endl; + + // Final sync and exit + coyote_thread.connSync(IS_CLIENT); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/examples/MT_pomsarc_09_CC_Testbench/sw/src/include/constants.hpp b/examples/MT_pomsarc_09_CC_Testbench/sw/src/include/constants.hpp new file mode 100644 index 000000000..225739d5f --- /dev/null +++ b/examples/MT_pomsarc_09_CC_Testbench/sw/src/include/constants.hpp @@ -0,0 +1,9 @@ +// Constants shared by both client and server; +// Placed in separate file to avoid any weird changes in one file not reflect in the other + +// Benchmark parameters +#define N_LATENCY_REPS 1 +#define N_THROUGHPUT_REPS 64 + +// Default vFPGA to assign cThreads to; for designs with one region (vFPGA) this is the only possible value +#define DEFAULT_VFPGA_ID 0 \ No newline at end of file diff --git a/examples/MT_pomsarc_09_CC_Testbench/sw/src/server/main.cpp b/examples/MT_pomsarc_09_CC_Testbench/sw/src/server/main.cpp new file mode 100644 index 000000000..20b6b984e --- /dev/null +++ b/examples/MT_pomsarc_09_CC_Testbench/sw/src/server/main.cpp @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2021-2024, Systems Group, ETH Zurich + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. + */ + +#include +#include +#include + +// External library for easier parsing of CLI arguments by the executable +#include + +// Coyote-specific includes +#include "cThread.hpp" +#include "constants.hpp" + +constexpr bool const IS_CLIENT = false; + +void run_bench( + coyote::cThread &coyote_thread, coyote::sgEntry &sg, + int *mem, uint transfers, uint n_runs, bool operation +) { + // When writing, the server asserts the written payload is correct (which the client sets) + // When reading, the client asserts the read payload is correct (which the server sets) + for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { + mem[i] = operation ? 0 : i; + } + + for (int i = 0; i < n_runs; i++) { + // Clear previous completion flags and sync with client + coyote_thread.clearCompleted(); + coyote_thread.connSync(IS_CLIENT); + + // For writes, wait until client has written the targer number of messages; then write them back + if (operation) { + while (coyote_thread.checkCompleted(coyote::CoyoteOper::LOCAL_WRITE) != transfers) {} + + for (int i = 0; i < transfers; i++) { + coyote_thread.invoke(coyote::CoyoteOper::REMOTE_RDMA_WRITE, &sg); + } + // For reads, the server is completely passive + } else { + + } + } + + // Functional correctness check + if (operation) { + for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { + assert(mem[i] == i); + } + } +} + +int main(int argc, char *argv[]) { + // CLI arguments + bool operation; + unsigned int min_size, max_size, n_runs; + + boost::program_options::options_description runtime_options("Coyote Perf RDMA Options"); + runtime_options.add_options() + ("operation,o", boost::program_options::value(&operation)->default_value(false), "Benchmark operation: READ(0) or WRITE(1)") + ("runs,r", boost::program_options::value(&n_runs)->default_value(10), "Number of times to repeat the test") + ("min_size,x", boost::program_options::value(&min_size)->default_value(64), "Starting (minimum) transfer size") + ("max_size,X", boost::program_options::value(&max_size)->default_value(1 * 1024 * 1024), "Ending (maximum) transfer size"); + boost::program_options::variables_map command_line_arguments; + boost::program_options::store(boost::program_options::parse_command_line(argc, argv, runtime_options), command_line_arguments); + boost::program_options::notify(command_line_arguments); + + PR_HEADER("CLI PARAMETERS:"); + std::cout << "Benchmark operation: " << (operation ? "WRITE" : "READ") << std::endl; + std::cout << "Number of test runs: " << n_runs << std::endl; + std::cout << "Starting transfer size: " << min_size << std::endl; + std::cout << "Ending transfer size: " << max_size << std::endl << std::endl; + + // Allocate Coyothe threa and set-up RDMA connections, buffer etc. + // initRDMA is explained in more detail in client/main.cpp + coyote::cThread coyote_thread(DEFAULT_VFPGA_ID, getpid(), 0); + int *mem = (int *) coyote_thread.initRDMA(max_size, coyote::defPort); + if (!mem) { throw std::runtime_error("Could not allocate memory; exiting..."); } + + // Benchmark sweep; exactly like done in the client code + PR_HEADER("RDMA BENCHMARK: SERVER"); + unsigned int curr_size = min_size; + while(curr_size <= max_size) { + coyote::sgEntry sg; + sg.rdma = { .len = curr_size }; + run_bench(coyote_thread, sg, mem, N_THROUGHPUT_REPS, n_runs, operation); + run_bench(coyote_thread, sg, mem, N_LATENCY_REPS, n_runs, operation); + curr_size *= 2; + } + + // Final sync and exit + coyote_thread.connSync(IS_CLIENT); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_queue_wrapper.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_queue_wrapper.sv new file mode 100644 index 000000000..ceb193d54 --- /dev/null +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_queue_wrapper.sv @@ -0,0 +1,199 @@ +import lynxTypes::*; + +module cc_queue +( + input logic aclk, + input logic aresetn, + + input logic ecn_mark, + input logic ecn_valid, + output logic ecn_ready, + + + metaIntf.s s_req, + metaIntf.m m_req +); + +localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; +localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); +localparam integer RD_OP = 0; +localparam integer WR_OP = 1; + +localparam integer Rt_default = 100000; +localparam integer Rc_default = 100000; +localparam integer N_min_time_between_ecn_marks = 50; +localparam integer Rai = 50; +localparam integer g = 8; +localparam integer F = 5; + +localparam integer time_threshhold = 100; +localparam integer byte_threshhold = 10; + +logic[31:0] Rt; +logic[31:0] Rc; +logic[31:0] alpha; +logic[31:0] timer; + +logic[31:0] timer_send_rate; + +logic[31:0] time_of_last_marked_packet; +logic[31:0] time_last_update; + +logic[31:0] byte_counter; +logic[31:0] time_counter; + +logic[4:0] step_counter; + + +//typedef enum logic[2:0] {ST_IDLE, ST_CALC_SR, ST_ADD_TO_QUEUE} state_t; +logic [2:0] state_C, state_N; +metaIntf #(.STYPE(dreq_t)) queue_in(); +metaIntf #(.STYPE(dreq_t)) queue_out(); + +logic ecn_data; + + + + +always_ff @(posedge aclk) begin + if (aresetn == 1'b0) begin + state_C <= ST_IDLE; + + Rt <= Rt_default; + Rc <= Rc_default; + alpha <= 1; + timer <= 0; + + timer_send_rate = 0; + + byte_counter <= 0; + time_counter <= 0 + + ecn_ready <= 1; + + step_counter <= 0; + + + end + else begin + timer <= timer + 1; + time_counter = time_counter + 1; + + ecn_ready <= 1'b0; + ecn_data <= ecn_mark; + + s_ack.ready <= 1'b0; + + s_req.ready <= 1'b0; + req_out.valid <= 1'b0; + req_out.data <= s_req.data; + + if(ecn_valid) begin + ecn_ready <= 1'b1; + byte_counter <= bytecounter + 1; + if(ecn_data == 1'b1) begin // Marked Packet arrived + Rt = Rc; + Rc = Rc(1-(alpha/2)); + alpha = (1-g)*alpha+g; + end + end + + if(time_counter >) + + + if(s_req.valid & queue_in.ready) begin + if(timer_send_rate > Rc) begin + timer_send_rate = 0; + req_out.valid = 1'b1; + s_req.ready = 1'b1; + end + end + + + end +end + + + + + +/* +always_ff @(posedge aclk) begin: PROC_REG + if (aresetn == 1'b0) begin + state_C <= ST_IDLE; + + Rt <= Rt_default; + Rc <= Rc_default; + alpha <= 1; + byte_counter <= 0; + timer <= 0; + ecn_ready <= 1; + end + else begin + state_C <= state_N; + addr_C <= addr_N; + end +end + +// NSL +always_comb begin: NSL + state_N = state_C; + + case(state_C) + ST_CALC_SR: + state_N = (ecn_valid) ? ST_CALC_SR : (s_req.valid & queue_in.ready ? ST_ADD_TO_QUEUE : ST_CALC_SR); + ST_ADD_TO_QUEUE: + state_N = ST_CALC_SR; + + endcase // state_C +end + + +always_comb begin: DP + + s_ack.ready = 1'b0; + + s_req.ready = 1'b0; + req_out.valid = 1'b0; + req_out.data = s_req.data; + + ecn_ready = 1'b0; + ecn_data = ecn_mark; + + case(state_C): + ST_CALC_SR: begin + if(ecn_valid) begin + ecn_ready = 1'b1; + byte_counter = bytecounter + 1; + if(ecn_data == 1'b1) begin // Marked Packet arrived + Rt = Rc; + Rc = Rc(1-(alpha/2)); + alpha = (1-g)*alpha+g; + end + end + end + + ST_ADD_TO_QUEUE: begin + req_out.valid = 1'b1; + s_req.ready = 1'b1; + end + + + + endcase + +end +*/ + + +queue_meta #( + .QDEPTH(RDMA_N_OST) +) inst_cq ( + .aclk(aclk), + .aresetn(aresetn), + .s_meta(queue_in), + .m_meta(m_req) +); + + +endmodule \ No newline at end of file diff --git a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv new file mode 100644 index 000000000..ae82f2447 --- /dev/null +++ b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv @@ -0,0 +1,135 @@ +`timescale 1ns / 1ps + +`define DBG_IBV + +import lynxTypes::*; + +/** + * @brief RoCE instantiation + * + * RoCE stack + */ +module benchy +( + input logic aclk, + input logic aresetn, + + metaIntf.s s_req, + metaIntf.m m_req +); + +localparam integer packet_gap = 5; //inverse of marked percentage +localparam integer measurement_gap + + +localparam integer max_timer = 10000; + +logic[31:0] timer; +logic[31:0] packet_counter; + +logic[31:0] packet_gap_counter; + + + +logic running; + +logic ecn, ecn_v, ecn_r; + +logic[31:0] measurement_gap_timer; + +always_ff @(posedge aclk) begin + if (aresetn == 1'b0) begin + timer <= 0; + measurement_gap_timer <= 0; + packet_counter <= 0; + packet_gap_counter <=0; + + ecn <= 0; + ecn_v <= 0; + running <= 0; + measurement_gap_timer <=0; + measurement_trigger <=0; + + end + else begin + timer <= timer + 1; + + if(timer >= max_timer) begin + timer <= 0; + measurement_gap_timer <= 0; + packet_counter <= 0; + packet_gap_counter <=0; + + ecn <= 0; + ecn_v <= 0; + running <= 0; + measurement_gap_timer <=0; + measurement_trigger <=0; + end + else begin + measurement_gap_timer <= measurement_gap_timer + 1; + ecn_v <= 0; + ecn <= 0; + if(s_req.valid == 1) begin + if(running == 0) begin + running <= 1; + measurement_gap_timer <= 0; + end + + ecn_v <= 1; + + if(packet_gap_counter < packet_gap) begin + ecn <= 0; + end + else begin + ecn <= 1 + end + if(s_req.ready) begin + if(packet_gap_counter >= packet_gap) begin + packet_gap_counter <=0; + end + else begin + packet_gap_counter <= packet_gap_counter + 1; + end + packet_counter = packet_counter + 1; + + end + end + + if(measurement_gap_timer >= measurement_gap) begin + measurement_gap_timer <= 0; + end + end + + end +end + +ila_testbench_CC inst_ila_testbench_CC( + .clk(nclk), + .probe0(timer), + .probe1(measurement_gap_timer), + .probe2(running), + .probe3(ecn) + .probe5(ecn_v) + .probe6(ecn_r) +); + + + +cc_queue inst_cc_queue( + .aclk(aclk), + .aresetn(aresetn), + + .ecn_mark(ecn), + .ecn_valid(ecn_v), + .ecn_ready(ecn_r), + + + .s_req(s_req), + .m_req(m_req) +); + + + + +endmodule \ No newline at end of file diff --git a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv new file mode 100644 index 000000000..02910c647 --- /dev/null +++ b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv @@ -0,0 +1,189 @@ +import lynxTypes::*; + +module rdma_flow ( + metaIntf.s s_req, + metaIntf.m m_req, + + metaIntf.s s_ack, + metaIntf.m m_ack, + + input logic aclk, + input logic aresetn +); + +localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; +localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); +localparam integer RD_OP = 0; +localparam integer WR_OP = 1; + +// -- FSM +typedef enum logic[2:0] {ST_IDLE, ST_ACK_LUP_WAIT, ST_REQ_LUP_WAIT, ST_ACK_LUP, ST_REQ_LUP} state_t; +logic [2:0] state_C, state_N; +logic [1+N_REGIONS_BITS+PID_BITS-1:0] addr_C, addr_N; + +logic [1:0] ssn_wr; +logic [1+N_REGIONS_BITS+PID_BITS-1:0] ssn_addr; +logic [15:0] ssn_in; +logic [15:0] ssn_out; + +metaIntf #(.STYPE(ack_t)) ack_que_in (); +metaIntf #(.STYPE(dreq_t)) req_out (); + +logic [RDMA_OST_BITS-1:0] tail, tail_next; +logic [RDMA_OST_BITS-1:0] head, head_next; +logic issued, issued_next; + + +// Pointer table +ram_sp_nc #( + .ADDR_BITS(1+N_REGIONS_BITS+PID_BITS), + .DATA_BITS(16) +) inst_pntr_table ( + .clk(aclk), + .a_en(1'b1), + .a_we(ssn_wr), + .a_addr(ssn_addr), + .a_data_in(ssn_in), + .a_data_out(ssn_out) +); + +// REG +always_ff @(posedge aclk) begin: PROC_REG + if (aresetn == 1'b0) begin + state_C <= ST_IDLE; + addr_C <= 'X; + end + else begin + state_C <= state_N; + addr_C <= addr_N; + end +end + +// NSL +always_comb begin: NSL + state_N = state_C; + + case(state_C) + ST_IDLE: + state_N = (s_ack.valid) ? ST_ACK_LUP_WAIT : (s_req.valid & req_out.ready ? ST_REQ_LUP_WAIT : ST_IDLE); + + ST_ACK_LUP_WAIT: + state_N = ST_ACK_LUP; + + ST_REQ_LUP_WAIT: + state_N = ST_REQ_LUP; + + ST_REQ_LUP: + state_N = ST_IDLE; + + ST_ACK_LUP: + state_N = ST_IDLE; + + endcase // state_C +end + +// DP +assign ssn_in = {7'h00, issued_next, head_next, tail_next}; +assign issued = ssn_out[2*RDMA_OST_BITS]; +assign head = ssn_out[RDMA_OST_BITS+:RDMA_OST_BITS]; +assign tail = ssn_out[0+:RDMA_OST_BITS]; + +always_comb begin: DP + addr_N = addr_C; + + // ACKs + s_ack.ready = 1'b0; + + ack_que_in.valid = 1'b0; + ack_que_in.data = s_ack.data.ack; + + // Req + s_req.ready = 1'b0; + req_out.valid = 1'b0; + req_out.data = s_req.data; + req_out.data.req_1.offs = head; + + // Table + ssn_wr = 0; + ssn_addr = 0; + + // Pointers + head_next = 0; + tail_next = 0; + issued_next = 0; + + case(state_C) + ST_IDLE: begin + if(s_ack.valid & ) begin + s_ack.ready = 1'b1; + ack_que_in.valid = s_ack.data.last; + + ssn_addr = {is_opcode_rd_resp(s_ack.data.ack.opcode), s_ack.data.ack.vfid[N_REGIONS_BITS-1:0], s_ack.data.ack.pid}; + addr_N = ssn_addr; + end + else if(s_req.valid & req_out.ready) begin + ssn_addr = {is_opcode_rd_req(s_req.data.req_1.opcode), s_req.data.req_1.vfid[N_REGIONS_BITS-1:0], s_req.data.req_1.pid}; + addr_N = ssn_addr; + end + end + + ST_ACK_LUP: begin + head_next = head; + tail_next = tail + 1; + if(head == tail_next) + issued_next = 1'b0; + else + issued_next = issued; + + ssn_wr = ~0; + ssn_addr = addr_C; + end + + ST_REQ_LUP: begin + if(!issued || (head != tail)) begin + req_out.valid = 1'b1; + s_req.ready = 1'b1; + + head_next = head + 1; + tail_next = tail; + issued_next = 1'b1; + + ssn_wr = ~0; + ssn_addr = addr_C; + end + end + + endcase + +end + +// ACK queue +queue_meta #( + .QDEPTH(RDMA_N_OST) +) inst_cq ( + .aclk(aclk), + .aresetn(aresetn), + .s_meta(ack_que_in), + .m_meta(m_ack) +); + +// REQ queue +/* +queue_meta #( + .QDEPTH(RDMA_N_OST) +) inst_sq ( + .aclk(aclk), + .aresetn(aresetn), + .s_meta(req_out), + .m_meta(m_req) +);*/ + +benchy inst_cc_bench( + .aclk(aclk), + .aresetn(aresetn), + + .s_req(req_out), + .m_req(m_req) +); + +endmodule \ No newline at end of file diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 090314bc7..4af1e10f8 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -584,3 +584,6 @@ set_property -dict [list CONFIG.TDATA_NUM_BYTES {4} CONFIG.FIFO_DEPTH {128} ] [g # MT_pomsarc added create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_rdma_ack set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_NUM_OF_PROBES {4} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] + +create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_testbench_CC +set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_NUM_OF_PROBES {7} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] \ No newline at end of file From 21ef62462e7a05937db50409492c9203302505a7 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Fri, 8 Aug 2025 06:27:57 +0200 Subject: [PATCH 14/52] fix typos --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index 208e9f6a4..daf68e85f 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -61,8 +61,6 @@ logic ecn_data; always_ff @(posedge aclk) begin if (aresetn == 1'b0) begin - state_C <= ST_IDLE; - Rt <= Rt_default; Rc <= Rc_default; Sa <= S1; @@ -71,7 +69,7 @@ always_ff @(posedge aclk) begin timer_send_rate = 0; byte_counter <= 0; - time_counter <= 0 + time_counter <= 0; ecn_ready <= 1; @@ -94,20 +92,20 @@ always_ff @(posedge aclk) begin if(ecn_valid) begin ecn_ready <= 1'b1; - byte_counter <= bytecounter + 1; + byte_counter <= byte_counter + 1; if(ecn_data == 1'b1) begin // Marked Packet arrived if(timer - time_last_update > N_min_time_between_ecn_marks) begin Rt <= Rc; Rc <= (Rc << 8) / (S1 - (Sa>>1)); - alpha <= (((S1-Sg)*Sa)>> 8) + Sg; - time_last_update <= timer + Sa <= (((S1-Sg)*Sa)>> 8) + Sg; + time_last_update <= timer; end - time_of_last_marked_packet <= timer + time_of_last_marked_packet <= timer; end end if (timer - time_of_last_marked_packet > N_min_time_between_ecn_marks) begin - alpha <= (((S1-Sg)*alpha)>> 8); + Sa <= (((S1-Sg)*Sa)>> 8); end @@ -115,8 +113,8 @@ always_ff @(posedge aclk) begin if(time_counter >= F && byte_counter >=F) begin // case Hyper Increse - // Still missing - // + //replaced by fast recovery + Rc = (Rt + Rc) >> 1; time_counter <= 0; byte_counter <= 0; @@ -134,13 +132,9 @@ always_ff @(posedge aclk) begin end else begin // case Fast Recovery - Rc = (Rt + Rc) >> 1 + Rc = (Rt + Rc) >> 1; end - - - - if(s_req.valid & queue_in.ready) begin if(timer_send_rate > Rc) begin From edef0734d35101af03c2f3951a7e76109deeb096 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Mon, 11 Aug 2025 03:02:37 +0200 Subject: [PATCH 15/52] :) --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 1 + hw/hdl/network/rdma/MT_pomsarc_benchy.sv | 29 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index daf68e85f..eb29d2e28 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -130,6 +130,7 @@ always_ff @(posedge aclk) begin byte_counter <= 0; end end + end else begin // case Fast Recovery Rc = (Rt + Rc) >> 1; diff --git a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv index ae82f2447..4a65cf4f1 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv @@ -19,14 +19,13 @@ module benchy ); localparam integer packet_gap = 5; //inverse of marked percentage -localparam integer measurement_gap +localparam integer measurement_gap = 50; -localparam integer max_timer = 10000; +//localparam integer max_timer = 10000000000; logic[31:0] timer; logic[31:0] packet_counter; - logic[31:0] packet_gap_counter; @@ -47,25 +46,25 @@ always_ff @(posedge aclk) begin ecn <= 0; ecn_v <= 0; running <= 0; - measurement_gap_timer <=0; - measurement_trigger <=0; + measurement_gap_timer <= 0; + //measurement_trigger <= 0; end else begin timer <= timer + 1; - if(timer >= max_timer) begin + /*if(timer >= max_timer) begin timer <= 0; measurement_gap_timer <= 0; packet_counter <= 0; - packet_gap_counter <=0; + packet_gap_counter <= 0; ecn <= 0; ecn_v <= 0; running <= 0; - measurement_gap_timer <=0; - measurement_trigger <=0; - end + measurement_gap_timer <= 0; + //measurement_trigger <= 0; + end*/ else begin measurement_gap_timer <= measurement_gap_timer + 1; ecn_v <= 0; @@ -82,16 +81,16 @@ always_ff @(posedge aclk) begin ecn <= 0; end else begin - ecn <= 1 + ecn <= 1; end if(s_req.ready) begin if(packet_gap_counter >= packet_gap) begin - packet_gap_counter <=0; + packet_gap_counter <= 0; end else begin packet_gap_counter <= packet_gap_counter + 1; end - packet_counter = packet_counter + 1; + packet_counter <= packet_counter + 1; end end @@ -109,8 +108,8 @@ ila_testbench_CC inst_ila_testbench_CC( .probe0(timer), .probe1(measurement_gap_timer), .probe2(running), - .probe3(ecn) - .probe5(ecn_v) + .probe3(ecn), + .probe5(ecn_v), .probe6(ecn_r) ); From f147a2bed4d4d35d34ac3b0b95ccea54585abbcd Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Mon, 11 Aug 2025 04:53:20 +0200 Subject: [PATCH 16/52] :) --- hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 10 +++++----- hw/hdl/network/rdma/MT_pomsarc_benchy.sv | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index eb29d2e28..de6c036c7 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -79,7 +79,7 @@ always_ff @(posedge aclk) begin end else begin timer <= timer + 1; - time_counter = time_counter + 1; + time_counter <= time_counter + 1; ecn_ready <= 1'b0; ecn_data <= ecn_mark; @@ -114,7 +114,7 @@ always_ff @(posedge aclk) begin if(time_counter >= F && byte_counter >=F) begin // case Hyper Increse //replaced by fast recovery - Rc = (Rt + Rc) >> 1; + Rc <= (Rt + Rc) >> 1; time_counter <= 0; byte_counter <= 0; @@ -123,7 +123,7 @@ always_ff @(posedge aclk) begin // case Additive Increase Rt <= Rt + Rai; Rc <= (Rt + Rc) >> 1; - if(time_counter >= F)begin + if(time_counter >= F) begin time_counter <= 0; end else begin @@ -133,7 +133,7 @@ always_ff @(posedge aclk) begin end else begin // case Fast Recovery - Rc = (Rt + Rc) >> 1; + Rc <= (Rt + Rc) >> 1; end @@ -148,7 +148,7 @@ always_ff @(posedge aclk) begin end end -end + diff --git a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv index 4a65cf4f1..062ed8d8c 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv @@ -64,8 +64,8 @@ always_ff @(posedge aclk) begin running <= 0; measurement_gap_timer <= 0; //measurement_trigger <= 0; - end*/ - else begin + end + else begin*/ measurement_gap_timer <= measurement_gap_timer + 1; ecn_v <= 0; ecn <= 0; @@ -98,13 +98,13 @@ always_ff @(posedge aclk) begin if(measurement_gap_timer >= measurement_gap) begin measurement_gap_timer <= 0; end - end + //end end end ila_testbench_CC inst_ila_testbench_CC( - .clk(nclk), + .clk(aclk), .probe0(timer), .probe1(measurement_gap_timer), .probe2(running), From 0c4ab1de3624fde434cf897e89edf0c6c9af8922 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Mon, 11 Aug 2025 06:43:41 +0200 Subject: [PATCH 17/52] removed superfluous file --- .../rdma/MT_pomsarc_CC_queue_wrapper.sv | 199 ------------------ scripts/ip_inst/network_infrastructure.tcl | 2 +- 2 files changed, 1 insertion(+), 200 deletions(-) delete mode 100644 hw/hdl/network/rdma/MT_pomsarc_CC_queue_wrapper.sv diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_queue_wrapper.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_queue_wrapper.sv deleted file mode 100644 index ceb193d54..000000000 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_queue_wrapper.sv +++ /dev/null @@ -1,199 +0,0 @@ -import lynxTypes::*; - -module cc_queue -( - input logic aclk, - input logic aresetn, - - input logic ecn_mark, - input logic ecn_valid, - output logic ecn_ready, - - - metaIntf.s s_req, - metaIntf.m m_req -); - -localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; -localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); -localparam integer RD_OP = 0; -localparam integer WR_OP = 1; - -localparam integer Rt_default = 100000; -localparam integer Rc_default = 100000; -localparam integer N_min_time_between_ecn_marks = 50; -localparam integer Rai = 50; -localparam integer g = 8; -localparam integer F = 5; - -localparam integer time_threshhold = 100; -localparam integer byte_threshhold = 10; - -logic[31:0] Rt; -logic[31:0] Rc; -logic[31:0] alpha; -logic[31:0] timer; - -logic[31:0] timer_send_rate; - -logic[31:0] time_of_last_marked_packet; -logic[31:0] time_last_update; - -logic[31:0] byte_counter; -logic[31:0] time_counter; - -logic[4:0] step_counter; - - -//typedef enum logic[2:0] {ST_IDLE, ST_CALC_SR, ST_ADD_TO_QUEUE} state_t; -logic [2:0] state_C, state_N; -metaIntf #(.STYPE(dreq_t)) queue_in(); -metaIntf #(.STYPE(dreq_t)) queue_out(); - -logic ecn_data; - - - - -always_ff @(posedge aclk) begin - if (aresetn == 1'b0) begin - state_C <= ST_IDLE; - - Rt <= Rt_default; - Rc <= Rc_default; - alpha <= 1; - timer <= 0; - - timer_send_rate = 0; - - byte_counter <= 0; - time_counter <= 0 - - ecn_ready <= 1; - - step_counter <= 0; - - - end - else begin - timer <= timer + 1; - time_counter = time_counter + 1; - - ecn_ready <= 1'b0; - ecn_data <= ecn_mark; - - s_ack.ready <= 1'b0; - - s_req.ready <= 1'b0; - req_out.valid <= 1'b0; - req_out.data <= s_req.data; - - if(ecn_valid) begin - ecn_ready <= 1'b1; - byte_counter <= bytecounter + 1; - if(ecn_data == 1'b1) begin // Marked Packet arrived - Rt = Rc; - Rc = Rc(1-(alpha/2)); - alpha = (1-g)*alpha+g; - end - end - - if(time_counter >) - - - if(s_req.valid & queue_in.ready) begin - if(timer_send_rate > Rc) begin - timer_send_rate = 0; - req_out.valid = 1'b1; - s_req.ready = 1'b1; - end - end - - - end -end - - - - - -/* -always_ff @(posedge aclk) begin: PROC_REG - if (aresetn == 1'b0) begin - state_C <= ST_IDLE; - - Rt <= Rt_default; - Rc <= Rc_default; - alpha <= 1; - byte_counter <= 0; - timer <= 0; - ecn_ready <= 1; - end - else begin - state_C <= state_N; - addr_C <= addr_N; - end -end - -// NSL -always_comb begin: NSL - state_N = state_C; - - case(state_C) - ST_CALC_SR: - state_N = (ecn_valid) ? ST_CALC_SR : (s_req.valid & queue_in.ready ? ST_ADD_TO_QUEUE : ST_CALC_SR); - ST_ADD_TO_QUEUE: - state_N = ST_CALC_SR; - - endcase // state_C -end - - -always_comb begin: DP - - s_ack.ready = 1'b0; - - s_req.ready = 1'b0; - req_out.valid = 1'b0; - req_out.data = s_req.data; - - ecn_ready = 1'b0; - ecn_data = ecn_mark; - - case(state_C): - ST_CALC_SR: begin - if(ecn_valid) begin - ecn_ready = 1'b1; - byte_counter = bytecounter + 1; - if(ecn_data == 1'b1) begin // Marked Packet arrived - Rt = Rc; - Rc = Rc(1-(alpha/2)); - alpha = (1-g)*alpha+g; - end - end - end - - ST_ADD_TO_QUEUE: begin - req_out.valid = 1'b1; - s_req.ready = 1'b1; - end - - - - endcase - -end -*/ - - -queue_meta #( - .QDEPTH(RDMA_N_OST) -) inst_cq ( - .aclk(aclk), - .aresetn(aresetn), - .s_meta(queue_in), - .m_meta(m_req) -); - - -endmodule \ No newline at end of file diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 4af1e10f8..c305a410e 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -586,4 +586,4 @@ create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_NUM_OF_PROBES {4} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_testbench_CC -set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_NUM_OF_PROBES {7} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] \ No newline at end of file +set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_NUM_OF_PROBES {7} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] \ No newline at end of file From 37cc18fcdb329dc2bb1a0def3146bb9e02fa3045 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Mon, 1 Sep 2025 16:06:06 +0200 Subject: [PATCH 18/52] ... --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 59 ++++-- .../rdma/MT_pomsarc_CC_QP_unit_Timely.sv | 0 hw/hdl/network/rdma/MT_pomsarc_benchy.sv | 4 +- hw/hdl/network/rdma/MT_pomsarc_new _flow.sv | 4 +- .../rdma/MT_pomsarc_new_flow_multiqueue.sv | 0 hw/hdl/network/rdma/rdma_flow.sv | 180 ------------------ scripts/ip_inst/network_infrastructure.tcl | 5 +- 7 files changed, 48 insertions(+), 204 deletions(-) create mode 100644 hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_Timely.sv create mode 100644 hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv delete mode 100644 hw/hdl/network/rdma/rdma_flow.sv diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index de6c036c7..600d7ee5d 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -14,15 +14,16 @@ module cc_queue metaIntf.m m_req ); -localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; +//localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; +localparam integer RDMA_N_OST = 20; localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); localparam integer RD_OP = 0; localparam integer WR_OP = 1; localparam integer Rt_default = 100000; localparam integer Rc_default = 100000; -localparam integer N_min_time_between_ecn_marks = 50; -localparam integer Rai = 50; +localparam integer N_min_time_between_ecn_marks = 12500; +localparam integer Rai = 6250; localparam integer S1 = 128; // 1 << 8 localparam integer Sg = 8; // (1/16) << 8 @@ -50,15 +51,13 @@ logic[4:0] step_counter; //typedef enum logic[2:0] {ST_IDLE, ST_CALC_SR, ST_ADD_TO_QUEUE} state_t; -logic [2:0] state_C, state_N; -metaIntf #(.STYPE(dreq_t)) queue_in(); +//logic [2:0] state_C, state_N; +//metaIntf #(.STYPE(dreq_t)) queue_in(); metaIntf #(.STYPE(dreq_t)) queue_out(); logic ecn_data; - - always_ff @(posedge aclk) begin if (aresetn == 1'b0) begin Rt <= Rt_default; @@ -66,15 +65,21 @@ always_ff @(posedge aclk) begin Sa <= S1; timer <= 0; - timer_send_rate = 0; + timer_send_rate <= 0; + + time_of_last_marked_packet <= 0; + time_last_update <= 0; byte_counter <= 0; time_counter <= 0; - - ecn_ready <= 1; step_counter <= 0; + ecn_ready <= 1; + ecn_data <= 0; + + m_req.valid <= 0; + end else begin @@ -84,11 +89,12 @@ always_ff @(posedge aclk) begin ecn_ready <= 1'b0; ecn_data <= ecn_mark; - s_ack.ready <= 1'b0; + //s_ack.ready <= 1'b0; - s_req.ready <= 1'b0; - req_out.valid <= 1'b0; - req_out.data <= s_req.data; + m_req.valid <= 1'b0; + m_req.data <= queue_out.data; + queue_out.ready <= 1'b0; + //req_out.data <= s_req.data; if(ecn_valid) begin ecn_ready <= 1'b1; @@ -115,7 +121,6 @@ always_ff @(posedge aclk) begin // case Hyper Increse //replaced by fast recovery Rc <= (Rt + Rc) >> 1; - time_counter <= 0; byte_counter <= 0; end @@ -137,10 +142,11 @@ always_ff @(posedge aclk) begin end - if(s_req.valid & queue_in.ready) begin + if(queue_out.valid) begin if(timer_send_rate > Rc) begin + timer_send_rate <= 0; - req_out.valid <= 1'b1; + queue_in.valid <= 1'b1; s_req.ready <= 1'b1; end end @@ -150,6 +156,21 @@ always_ff @(posedge aclk) begin end +ila_DCQCN inst_ila_DCQCN( + .clk(aclk), + .probe0(timer), + .probe1(Rt), + .probe2(Rc), + .probe3(Sa), + .probe4(byte_counter), + .probe5(time_counter), + .probe6(step_counter), + .probe7(timer_send_rate), + .probe8(ecn_valid), + .probe9(ecn_mark) +); + + queue_meta #( @@ -157,8 +178,8 @@ queue_meta #( ) inst_cq ( .aclk(aclk), .aresetn(aresetn), - .s_meta(queue_in), - .m_meta(m_req) + .s_meta(s_req), + .m_meta(queue_out) ); diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_Timely.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_Timely.sv new file mode 100644 index 000000000..e69de29bb diff --git a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv index 062ed8d8c..709ee4228 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv @@ -109,8 +109,8 @@ ila_testbench_CC inst_ila_testbench_CC( .probe1(measurement_gap_timer), .probe2(running), .probe3(ecn), - .probe5(ecn_v), - .probe6(ecn_r) + .probe4(ecn_v), + .probe5(ecn_r) ); diff --git a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv index 02910c647..ec549d812 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv @@ -114,7 +114,7 @@ always_comb begin: DP case(state_C) ST_IDLE: begin - if(s_ack.valid & ) begin + if(s_ack.valid) begin s_ack.ready = 1'b1; ack_que_in.valid = s_ack.data.last; @@ -186,4 +186,4 @@ benchy inst_cc_bench( .m_req(m_req) ); -endmodule \ No newline at end of file +endmodule diff --git a/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv b/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv new file mode 100644 index 000000000..e69de29bb diff --git a/hw/hdl/network/rdma/rdma_flow.sv b/hw/hdl/network/rdma/rdma_flow.sv deleted file mode 100644 index 9486ed1a9..000000000 --- a/hw/hdl/network/rdma/rdma_flow.sv +++ /dev/null @@ -1,180 +0,0 @@ -import lynxTypes::*; - -module rdma_flow ( - metaIntf.s s_req, - metaIntf.m m_req, - - metaIntf.s s_ack, - metaIntf.m m_ack, - - input logic aclk, - input logic aresetn -); - -localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; -localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); -localparam integer RD_OP = 0; -localparam integer WR_OP = 1; - -// -- FSM -typedef enum logic[2:0] {ST_IDLE, ST_ACK_LUP_WAIT, ST_REQ_LUP_WAIT, ST_ACK_LUP, ST_REQ_LUP} state_t; -logic [2:0] state_C, state_N; -logic [1+N_REGIONS_BITS+PID_BITS-1:0] addr_C, addr_N; - -logic [1:0] ssn_wr; -logic [1+N_REGIONS_BITS+PID_BITS-1:0] ssn_addr; -logic [15:0] ssn_in; -logic [15:0] ssn_out; - -metaIntf #(.STYPE(ack_t)) ack_que_in (); -metaIntf #(.STYPE(dreq_t)) req_out (); - -logic [RDMA_OST_BITS-1:0] tail, tail_next; -logic [RDMA_OST_BITS-1:0] head, head_next; -logic issued, issued_next; - - -// Pointer table -ram_sp_nc #( - .ADDR_BITS(1+N_REGIONS_BITS+PID_BITS), - .DATA_BITS(16) -) inst_pntr_table ( - .clk(aclk), - .a_en(1'b1), - .a_we(ssn_wr), - .a_addr(ssn_addr), - .a_data_in(ssn_in), - .a_data_out(ssn_out) -); - -// REG -always_ff @(posedge aclk) begin: PROC_REG - if (aresetn == 1'b0) begin - state_C <= ST_IDLE; - addr_C <= 'X; - end - else begin - state_C <= state_N; - addr_C <= addr_N; - end -end - -// NSL -always_comb begin: NSL - state_N = state_C; - - case(state_C) - ST_IDLE: - state_N = (s_ack.valid) ? ST_ACK_LUP_WAIT : (s_req.valid & req_out.ready ? ST_REQ_LUP_WAIT : ST_IDLE); - - ST_ACK_LUP_WAIT: - state_N = ST_ACK_LUP; - - ST_REQ_LUP_WAIT: - state_N = ST_REQ_LUP; - - ST_REQ_LUP: - state_N = ST_IDLE; - - ST_ACK_LUP: - state_N = ST_IDLE; - - endcase // state_C -end - -// DP -assign ssn_in = {7'h00, issued_next, head_next, tail_next}; -assign issued = ssn_out[2*RDMA_OST_BITS]; -assign head = ssn_out[RDMA_OST_BITS+:RDMA_OST_BITS]; -assign tail = ssn_out[0+:RDMA_OST_BITS]; - -always_comb begin: DP - addr_N = addr_C; - - // ACKs - s_ack.ready = 1'b0; - - ack_que_in.valid = 1'b0; - ack_que_in.data = s_ack.data.ack; - - // Req - s_req.ready = 1'b0; - req_out.valid = 1'b0; - req_out.data = s_req.data; - req_out.data.req_1.offs = head; - - // Table - ssn_wr = 0; - ssn_addr = 0; - - // Pointers - head_next = 0; - tail_next = 0; - issued_next = 0; - - case(state_C) - ST_IDLE: begin - if(s_ack.valid) begin - s_ack.ready = 1'b1; - ack_que_in.valid = s_ack.data.last; - - ssn_addr = {is_opcode_rd_resp(s_ack.data.ack.opcode), s_ack.data.ack.vfid[N_REGIONS_BITS-1:0], s_ack.data.ack.pid}; - addr_N = ssn_addr; - end - else if(s_req.valid & req_out.ready) begin - ssn_addr = {is_opcode_rd_req(s_req.data.req_1.opcode), s_req.data.req_1.vfid[N_REGIONS_BITS-1:0], s_req.data.req_1.pid}; - addr_N = ssn_addr; - end - end - - ST_ACK_LUP: begin - head_next = head; - tail_next = tail + 1; - if(head == tail_next) - issued_next = 1'b0; - else - issued_next = issued; - - ssn_wr = ~0; - ssn_addr = addr_C; - end - - ST_REQ_LUP: begin - if(!issued || (head != tail)) begin - req_out.valid = 1'b1; - s_req.ready = 1'b1; - - head_next = head + 1; - tail_next = tail; - issued_next = 1'b1; - - ssn_wr = ~0; - ssn_addr = addr_C; - end - end - - endcase - -end - -// ACK queue -queue_meta #( - .QDEPTH(RDMA_N_OST) -) inst_cq ( - .aclk(aclk), - .aresetn(aresetn), - .s_meta(ack_que_in), - .m_meta(m_ack) -); - -// REQ queue -queue_meta #( - .QDEPTH(RDMA_N_OST) -) inst_sq ( - .aclk(aclk), - .aresetn(aresetn), - .s_meta(req_out), - .m_meta(m_req) -); - -endmodule \ No newline at end of file diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index c305a410e..816fc22e7 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -586,4 +586,7 @@ create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_NUM_OF_PROBES {4} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_testbench_CC -set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_NUM_OF_PROBES {7} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] \ No newline at end of file +set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] + +create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN +set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {32} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_NUM_OF_PROBES {10} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] \ No newline at end of file From 78a9cf3afc126c41b8a5a4ff799f330c242ca1ee Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Wed, 3 Sep 2025 01:04:29 +0200 Subject: [PATCH 19/52] submodule changes --- hw/services/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/services/network b/hw/services/network index 4928877fa..0032c5545 160000 --- a/hw/services/network +++ b/hw/services/network @@ -1 +1 @@ -Subproject commit 4928877faf472a06d41a7b111403c820c731a311 +Subproject commit 0032c5545507f15005386200759c7d4c10f94635 From 8031c9a025626e25be93c4b501370d93bdbc90b9 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Fri, 5 Sep 2025 13:03:40 +0200 Subject: [PATCH 20/52] commented out CC --- hw/hdl/network/rdma/MT_pomsarc_new _flow.sv | 8 ++++---- hw/hdl/network/rdma/roce_stack.sv | 13 ++++++++++++- scripts/ip_inst/network_infrastructure.tcl | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv index ec549d812..d5bd9e581 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv @@ -168,7 +168,7 @@ queue_meta #( ); // REQ queue -/* + queue_meta #( .QDEPTH(RDMA_N_OST) ) inst_sq ( @@ -176,14 +176,14 @@ queue_meta #( .aresetn(aresetn), .s_meta(req_out), .m_meta(m_req) -);*/ +); -benchy inst_cc_bench( +/*benchy inst_cc_bench( .aclk(aclk), .aresetn(aresetn), .s_req(req_out), .m_req(m_req) -); +);/* endmodule diff --git a/hw/hdl/network/rdma/roce_stack.sv b/hw/hdl/network/rdma/roce_stack.sv index 3a90c7800..4d3f8100a 100644 --- a/hw/hdl/network/rdma/roce_stack.sv +++ b/hw/hdl/network/rdma/roce_stack.sv @@ -230,9 +230,20 @@ ila_rdma_ack inst_ila_rdma_ack ( .probe0(rdma_ack.valid), .probe1(rdma_ack.ready), .probe2(rdma_ack.data.ack), // 32 - .probe3(rdma_ack.data.last) + .probe3(rdma_ack.data.last), + .probe4(s_axis_rx.tvalid), + .probe5(s_axis_rx.tready), + .probe6(s_axis_rx.tdata), // 512 + .probe7(s_axis_rx.tkeep), // 64 + .probe8(s_axis_rx.tlast), + .probe9(m_axis_tx.tvalid), + .probe10(m_axis_tx.tready), + .probe11(m_axis_tx.tdata), // 512 + .probe12(m_axis_tx.tkeep), // 64 + .probe13(m_axis_tx.tlast) ); + /* ila_rdma inst_ila_rdma ( .clk(nclk), .probe0(s_rdma_qp_interface.valid), diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 816fc22e7..2246934a3 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -583,7 +583,7 @@ set_property -dict [list CONFIG.TDATA_NUM_BYTES {4} CONFIG.FIFO_DEPTH {128} ] [g # MT_pomsarc added create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_rdma_ack -set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_NUM_OF_PROBES {4} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {512} CONFIG.C_PROBE7_WIDTH {64} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {512} CONFIG.C_PROBE12_WIDTH {64} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_NUM_OF_PROBES {14} CONFIG.C_DATA_DEPTH {8096} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_testbench_CC set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] From bd401bca614557c75ce4990f93913a9cac4057a5 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Sun, 7 Sep 2025 03:31:28 +0200 Subject: [PATCH 21/52] more ilas in flowcontrol --- hw/hdl/network/rdma/MT_pomsarc_new _flow.sv | 50 ++++++++++++++++++++- hw/hdl/network/rdma/roce_stack.sv | 1 - scripts/ip_inst/network_infrastructure.tcl | 9 ++-- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv index d5bd9e581..91e74f8e3 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv @@ -157,6 +157,36 @@ always_comb begin: DP end + +ila_rdma_ack ila_flowcontrol ( + .clk(aclk), + .probe0(ack_que_in.valid), + .probe1(ack_que_in.ready), + //.probe2(ack_que_in.data), // 32 + + .probe2(s_ack.valid), + .probe3(s_ack.ready), + //.probe5(s_ack.data.ack), // 32 + //.probe6(s_ack.data.last), + + .probe4(m_ack.valid), + .probe5(m_ack.ready), + //.probe9(m_ack.data.ack), // 32 + //.probe10(m_ack.data.last), + + .probe6(req_out.valid), + .probe7(req_out.ready), + //.probe8(s_rdma_sq.data), + + .probe8(s_req.valid), + .probe9(s_req.ready), + //.probe8(s_rdma_sq.data), + + .probe10(m_req.valid), + .probe11(m_req.ready) + //.probe8(s_rdma_sq.data), +); + // ACK queue queue_meta #( .QDEPTH(RDMA_N_OST) @@ -168,7 +198,6 @@ queue_meta #( ); // REQ queue - queue_meta #( .QDEPTH(RDMA_N_OST) ) inst_sq ( @@ -178,12 +207,29 @@ queue_meta #( .m_meta(m_req) ); + +/* +cc_queue +( + .aclk(aclk), + .aresetn(aresetn), + + .ecn_mark(), + .ecn_valid, + .ecn_ready, + + + .s_req(req_out), + .m_req(m_req) +);*/ + + /*benchy inst_cc_bench( .aclk(aclk), .aresetn(aresetn), .s_req(req_out), .m_req(m_req) -);/* +);*/ endmodule diff --git a/hw/hdl/network/rdma/roce_stack.sv b/hw/hdl/network/rdma/roce_stack.sv index 4d3f8100a..b2ee04326 100644 --- a/hw/hdl/network/rdma/roce_stack.sv +++ b/hw/hdl/network/rdma/roce_stack.sv @@ -243,7 +243,6 @@ ila_rdma_ack inst_ila_rdma_ack ( .probe13(m_axis_tx.tlast) ); - /* ila_rdma inst_ila_rdma ( .clk(nclk), .probe0(s_rdma_qp_interface.valid), diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 2246934a3..8e81e9b12 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -583,10 +583,13 @@ set_property -dict [list CONFIG.TDATA_NUM_BYTES {4} CONFIG.FIFO_DEPTH {128} ] [g # MT_pomsarc added create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_rdma_ack -set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {512} CONFIG.C_PROBE7_WIDTH {64} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {512} CONFIG.C_PROBE12_WIDTH {64} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_NUM_OF_PROBES {14} CONFIG.C_DATA_DEPTH {8096} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {512} CONFIG.C_PROBE7_WIDTH {64} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {512} CONFIG.C_PROBE12_WIDTH {64} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_NUM_OF_PROBES {14} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_testbench_CC -set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN -set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {32} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_NUM_OF_PROBES {10} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] \ No newline at end of file +set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {32} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_NUM_OF_PROBES {10} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] + +create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_flowcontrol +set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_NUM_OF_PROBES {12} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] \ No newline at end of file From 3b8f3b975649d5cb5c692ead1d95f2050dfa7eb5 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Sun, 7 Sep 2025 03:37:17 +0200 Subject: [PATCH 22/52] update submodule link --- hw/services/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/services/network b/hw/services/network index 0032c5545..ef47e1f81 160000 --- a/hw/services/network +++ b/hw/services/network @@ -1 +1 @@ -Subproject commit 0032c5545507f15005386200759c7d4c10f94635 +Subproject commit ef47e1f81e6a7fbe8177103add8866edc7f0405c From b8c29d9a7ea70c796f8de1f482156d57965ee7a8 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Sun, 7 Sep 2025 12:06:46 +0200 Subject: [PATCH 23/52] ila fix --- hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 6 +++--- hw/hdl/network/rdma/MT_pomsarc_new _flow.sv | 10 +--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index 600d7ee5d..627f7a518 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -142,12 +142,12 @@ always_ff @(posedge aclk) begin end - if(queue_out.valid) begin + if(queue_out.valid & m_req.ready) begin if(timer_send_rate > Rc) begin timer_send_rate <= 0; - queue_in.valid <= 1'b1; - s_req.ready <= 1'b1; + m_req.valid <= 1'b1; + queue_out.ready <= 1'b1; end end diff --git a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv index 91e74f8e3..0214c47c3 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv @@ -158,33 +158,25 @@ always_comb begin: DP end -ila_rdma_ack ila_flowcontrol ( +ila_flowcontrol inst_ila_flowcontrol ( .clk(aclk), .probe0(ack_que_in.valid), .probe1(ack_que_in.ready), - //.probe2(ack_que_in.data), // 32 .probe2(s_ack.valid), .probe3(s_ack.ready), - //.probe5(s_ack.data.ack), // 32 - //.probe6(s_ack.data.last), .probe4(m_ack.valid), .probe5(m_ack.ready), - //.probe9(m_ack.data.ack), // 32 - //.probe10(m_ack.data.last), .probe6(req_out.valid), .probe7(req_out.ready), - //.probe8(s_rdma_sq.data), .probe8(s_req.valid), .probe9(s_req.ready), - //.probe8(s_rdma_sq.data), .probe10(m_req.valid), .probe11(m_req.ready) - //.probe8(s_rdma_sq.data), ); // ACK queue From 9f004d2a87d8e033c5a1c8a5138b89e30dce1bf4 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Mon, 8 Sep 2025 01:36:31 +0200 Subject: [PATCH 24/52] added DCQCN back on --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 48 +++++++++---------- hw/hdl/network/rdma/MT_pomsarc_benchy.sv | 7 ++- hw/hdl/network/rdma/MT_pomsarc_new _flow.sv | 31 +++++++----- scripts/ip_inst/network_infrastructure.tcl | 4 +- 4 files changed, 48 insertions(+), 42 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index 627f7a518..c00adcc01 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -6,9 +6,7 @@ module cc_queue input logic aresetn, input logic ecn_mark, - input logic ecn_valid, - output logic ecn_ready, - + input logic ecn_write_rdy, metaIntf.s s_req, metaIntf.m m_req @@ -53,10 +51,8 @@ logic[4:0] step_counter; //typedef enum logic[2:0] {ST_IDLE, ST_CALC_SR, ST_ADD_TO_QUEUE} state_t; //logic [2:0] state_C, state_N; //metaIntf #(.STYPE(dreq_t)) queue_in(); -metaIntf #(.STYPE(dreq_t)) queue_out(); - -logic ecn_data; +metaIntf #(.STYPE(dreq_t)) queue_out; always_ff @(posedge aclk) begin if (aresetn == 1'b0) begin @@ -72,22 +68,18 @@ always_ff @(posedge aclk) begin byte_counter <= 0; time_counter <= 0; - step_counter <= 0; - ecn_ready <= 1; - ecn_data <= 0; - - m_req.valid <= 0; + m_req.valid <= 1'b0; + queue_out.ready <= 1'b0; end else begin timer <= timer + 1; time_counter <= time_counter + 1; + timer_send_rate <= timer_send_rate + 1; - ecn_ready <= 1'b0; - ecn_data <= ecn_mark; //s_ack.ready <= 1'b0; @@ -96,10 +88,9 @@ always_ff @(posedge aclk) begin queue_out.ready <= 1'b0; //req_out.data <= s_req.data; - if(ecn_valid) begin - ecn_ready <= 1'b1; + if(ecn_write_rdy) begin byte_counter <= byte_counter + 1; - if(ecn_data == 1'b1) begin // Marked Packet arrived + if(ecn_mark == 1'b1) begin // Marked Packet arrived if(timer - time_last_update > N_min_time_between_ecn_marks) begin Rt <= Rc; Rc <= (Rc << 8) / (S1 - (Sa>>1)); @@ -141,14 +132,15 @@ always_ff @(posedge aclk) begin Rc <= (Rt + Rc) >> 1; end - - if(queue_out.valid & m_req.ready) begin - if(timer_send_rate > Rc) begin - + // CHANGE BACK + //if(timer_send_rate > Rc) begin + if(timer_send_rate > 1) begin + m_req.valid <= queue_out.valid; + queue_out.ready <= m_req.ready; + if(queue_out.valid & m_req.ready) begin timer_send_rate <= 0; - m_req.valid <= 1'b1; - queue_out.ready <= 1'b1; end + end @@ -166,13 +158,17 @@ ila_DCQCN inst_ila_DCQCN( .probe5(time_counter), .probe6(step_counter), .probe7(timer_send_rate), - .probe8(ecn_valid), - .probe9(ecn_mark) + .probe8(s_req.valid), + .probe9(s_req.ready), + .probe10(m_req.valid), + .probe11(m_req.ready), + .probe12(queue_out.valid), + .probe13(queue_out.ready), + .probe14(ecn_mark), + .probe15(ecn_write_rdy) ); - - queue_meta #( .QDEPTH(RDMA_N_OST) ) inst_cq ( diff --git a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv index 709ee4228..a08dead5f 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv @@ -1,4 +1,4 @@ -`timescale 1ns / 1ps +/*`timescale 1ns / 1ps `define DBG_IBV @@ -9,6 +9,7 @@ import lynxTypes::*; * * RoCE stack */ +/* module benchy ( input logic aclk, @@ -66,6 +67,7 @@ always_ff @(posedge aclk) begin //measurement_trigger <= 0; end else begin*/ +/* measurement_gap_timer <= measurement_gap_timer + 1; ecn_v <= 0; ecn <= 0; @@ -131,4 +133,5 @@ cc_queue inst_cc_queue( -endmodule \ No newline at end of file +endmodule +*/ \ No newline at end of file diff --git a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv index 0214c47c3..8eb32281f 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv @@ -176,7 +176,12 @@ ila_flowcontrol inst_ila_flowcontrol ( .probe9(s_req.ready), .probe10(m_req.valid), - .probe11(m_req.ready) + .probe11(m_req.ready), + + .probe12(ecn_marked), + .probe13(ecn_consumed), + .probe14(s_ack.data.ack.ecn), + .probe15(s_ack.data.last) ); // ACK queue @@ -189,7 +194,7 @@ queue_meta #( .m_meta(m_ack) ); -// REQ queue +/*// REQ queue queue_meta #( .QDEPTH(RDMA_N_OST) ) inst_sq ( @@ -197,23 +202,25 @@ queue_meta #( .aresetn(aresetn), .s_meta(req_out), .m_meta(m_req) -); +);*/ -/* -cc_queue -( - .aclk(aclk), - .aresetn(aresetn), +logic ecn_marked; +assign ecn_marked = (s_ack.data.ack.ecn == 3); +logic ecn_consumed; +assign ecn_consumed = (s_ack.valid & s_ack.ready) & s_ack.data.last; - .ecn_mark(), - .ecn_valid, - .ecn_ready, +cc_queue inst_DCQCN( + .aclk(aclk), + .aresetn(aresetn), + .ecn_mark(ecn_marked), + .ecn_write_rdy(ecn_consumed), + .s_req(req_out), .m_req(m_req) -);*/ +); /*benchy inst_cc_bench( diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 8e81e9b12..0622a923a 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -589,7 +589,7 @@ create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN -set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {32} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_NUM_OF_PROBES {10} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {32} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_flowcontrol -set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_NUM_OF_PROBES {12} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] \ No newline at end of file +set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1}CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] \ No newline at end of file From c6307d55b447a40e3f08bde3c8b36c3f759c23dd Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Mon, 8 Sep 2025 14:41:55 +0200 Subject: [PATCH 25/52] ... --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 32 ++++++++----------- hw/hdl/network/rdma/MT_pomsarc_new _flow.sv | 7 ++-- scripts/ip_inst/network_infrastructure.tcl | 7 ++-- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index c00adcc01..1876ea200 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -48,11 +48,7 @@ logic[31:0] time_counter; logic[4:0] step_counter; -//typedef enum logic[2:0] {ST_IDLE, ST_CALC_SR, ST_ADD_TO_QUEUE} state_t; -//logic [2:0] state_C, state_N; -//metaIntf #(.STYPE(dreq_t)) queue_in(); - -metaIntf #(.STYPE(dreq_t)) queue_out; +metaIntf #(.STYPE(dreq_t)) queue_out (); always_ff @(posedge aclk) begin if (aresetn == 1'b0) begin @@ -72,6 +68,7 @@ always_ff @(posedge aclk) begin m_req.valid <= 1'b0; queue_out.ready <= 1'b0; + m_req.data <= 0; end @@ -81,7 +78,7 @@ always_ff @(posedge aclk) begin timer_send_rate <= timer_send_rate + 1; - //s_ack.ready <= 1'b0; + step_counter <= 0; m_req.valid <= 1'b0; m_req.data <= queue_out.data; @@ -126,8 +123,7 @@ always_ff @(posedge aclk) begin byte_counter <= 0; end end - end - else begin + end else begin // case Fast Recovery Rc <= (Rt + Rc) >> 1; end @@ -137,7 +133,7 @@ always_ff @(posedge aclk) begin if(timer_send_rate > 1) begin m_req.valid <= queue_out.valid; queue_out.ready <= m_req.ready; - if(queue_out.valid & m_req.ready) begin + if(queue_out.valid && m_req.ready) begin timer_send_rate <= 0; end @@ -150,14 +146,14 @@ end ila_DCQCN inst_ila_DCQCN( .clk(aclk), - .probe0(timer), - .probe1(Rt), - .probe2(Rc), - .probe3(Sa), - .probe4(byte_counter), - .probe5(time_counter), - .probe6(step_counter), - .probe7(timer_send_rate), + .probe0(timer), //32 + .probe1(Rt), //32 + .probe2(Rc), //32 + .probe3(Sa), //32 + .probe4(byte_counter), //32 + .probe5(time_counter), //32 + .probe6(step_counter), //5 + .probe7(timer_send_rate), //32 .probe8(s_req.valid), .probe9(s_req.ready), .probe10(m_req.valid), @@ -166,7 +162,7 @@ ila_DCQCN inst_ila_DCQCN( .probe13(queue_out.ready), .probe14(ecn_mark), .probe15(ecn_write_rdy) -); +); queue_meta #( diff --git a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv index 8eb32281f..6644a9683 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv @@ -184,6 +184,7 @@ ila_flowcontrol inst_ila_flowcontrol ( .probe15(s_ack.data.last) ); + // ACK queue queue_meta #( .QDEPTH(RDMA_N_OST) @@ -194,7 +195,8 @@ queue_meta #( .m_meta(m_ack) ); -/*// REQ queue +/* +// REQ queue queue_meta #( .QDEPTH(RDMA_N_OST) ) inst_sq ( @@ -202,7 +204,8 @@ queue_meta #( .aresetn(aresetn), .s_meta(req_out), .m_meta(m_req) -);*/ +); +*/ logic ecn_marked; diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 0622a923a..53ac0fd0b 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -585,11 +585,12 @@ set_property -dict [list CONFIG.TDATA_NUM_BYTES {4} CONFIG.FIFO_DEPTH {128} ] [g create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_rdma_ack set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {512} CONFIG.C_PROBE7_WIDTH {64} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {512} CONFIG.C_PROBE12_WIDTH {64} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_NUM_OF_PROBES {14} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_rdma_ack] -create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_testbench_CC -set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] + +#create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_testbench_CC +#set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {32} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_flowcontrol -set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1}CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] \ No newline at end of file +set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] From df182a2a00eda4abd63c2ee70bfd3b90efcf20cb Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Tue, 9 Sep 2025 01:33:28 +0200 Subject: [PATCH 26/52] submodule adjusted --- hw/services/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/services/network b/hw/services/network index ef47e1f81..c1f85bbf4 160000 --- a/hw/services/network +++ b/hw/services/network @@ -1 +1 @@ -Subproject commit ef47e1f81e6a7fbe8177103add8866edc7f0405c +Subproject commit c1f85bbf483bbdad6e103946886a55963722fe3c From fcf4a56e784308bc9ccae49d47d447a57a50f99a Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Tue, 9 Sep 2025 10:48:22 +0200 Subject: [PATCH 27/52] fix DCQCN --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 95 ++++++++++++------- scripts/ip_inst/network_infrastructure.tcl | 2 +- 2 files changed, 61 insertions(+), 36 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index 1876ea200..62626024c 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -21,6 +21,7 @@ localparam integer WR_OP = 1; localparam integer Rt_default = 100000; localparam integer Rc_default = 100000; localparam integer N_min_time_between_ecn_marks = 12500; +localparam integer K = 13750; localparam integer Rai = 6250; localparam integer S1 = 128; // 1 << 8 @@ -45,7 +46,10 @@ logic[31:0] time_last_update; logic[31:0] byte_counter; logic[31:0] time_counter; -logic[4:0] step_counter; +logic[4:0] tC_C; //timecounter counter +logic[4:0] bC_C; //bytecounter counter + +logic rate_increase_event; metaIntf #(.STYPE(dreq_t)) queue_out (); @@ -64,7 +68,11 @@ always_ff @(posedge aclk) begin byte_counter <= 0; time_counter <= 0; - step_counter <= 0; + + bC_C <= 0; + tC_C <= 0; + + rate_increase_event <= 0; m_req.valid <= 1'b0; queue_out.ready <= 1'b0; @@ -77,13 +85,12 @@ always_ff @(posedge aclk) begin time_counter <= time_counter + 1; timer_send_rate <= timer_send_rate + 1; + rate_increase_event <= 0; - step_counter <= 0; m_req.valid <= 1'b0; m_req.data <= queue_out.data; queue_out.ready <= 1'b0; - //req_out.data <= s_req.data; if(ecn_write_rdy) begin byte_counter <= byte_counter + 1; @@ -93,44 +100,60 @@ always_ff @(posedge aclk) begin Rc <= (Rc << 8) / (S1 - (Sa>>1)); Sa <= (((S1-Sg)*Sa)>> 8) + Sg; time_last_update <= timer; + + time_counter <= 0; + byte_counter <= 0; + bC_C <= 0; + tC_C <= 0; + end time_of_last_marked_packet <= timer; end end - if (timer - time_of_last_marked_packet > N_min_time_between_ecn_marks) begin + if (timer - time_of_last_marked_packet > K) begin Sa <= (((S1-Sg)*Sa)>> 8); end + + if(time_counter > time_threshhold) begin + time_counter <= 0; + rate_increase_event <= 1; + if(tC_C < F) begin + tC_C <= tC_C + 1; + end + end - if(time_counter >= F || byte_counter >= F) begin - - if(time_counter >= F && byte_counter >=F) begin - // case Hyper Increse - //replaced by fast recovery - Rc <= (Rt + Rc) >> 1; - time_counter <= 0; - byte_counter <= 0; + if(byte_counter > byte_threshhold) begin + byte_counter <= 0; + rate_increase_event <= 1; + if(bC_C < F) begin + bC_C <= bC_C + 1; end - else begin - // case Additive Increase - Rt <= Rt + Rai; - Rc <= (Rt + Rc) >> 1; - if(time_counter >= F) begin - time_counter <= 0; + end + + if(rate_increase_event) begin + if(tC_C >= F || bC_C >= F) begin + + if(tC_C >= F && bC_C >= F) begin + // case Hyper Increse + //replaced by fast recovery + Rc <= (Rt + Rc) >> 1; end else begin - byte_counter <= 0; + // case Additive Increase + Rt <= Rt + Rai; + Rc <= (Rt + Rc) >> 1; end + end else begin + // case Fast Recovery + Rc <= (Rt + Rc) >> 1; end - end else begin - // case Fast Recovery - Rc <= (Rt + Rc) >> 1; end // CHANGE BACK - //if(timer_send_rate > Rc) begin - if(timer_send_rate > 1) begin + if(timer_send_rate > Rc) begin + //if(timer_send_rate > 1) begin m_req.valid <= queue_out.valid; queue_out.ready <= m_req.ready; if(queue_out.valid && m_req.ready) begin @@ -152,16 +175,18 @@ ila_DCQCN inst_ila_DCQCN( .probe3(Sa), //32 .probe4(byte_counter), //32 .probe5(time_counter), //32 - .probe6(step_counter), //5 - .probe7(timer_send_rate), //32 - .probe8(s_req.valid), - .probe9(s_req.ready), - .probe10(m_req.valid), - .probe11(m_req.ready), - .probe12(queue_out.valid), - .probe13(queue_out.ready), - .probe14(ecn_mark), - .probe15(ecn_write_rdy) + .probe6(TC_C), //5 + .probe7(BC_C), //5 + .probe8(rate_increase_event), + .probe9(timer_send_rate), //32 + .probe10(s_req.valid), + .probe11(s_req.ready), + .probe12(m_req.valid), + .probe13(m_req.ready), + .probe14(queue_out.valid), + .probe15(queue_out.ready), + .probe16(ecn_mark), + .probe17(ecn_write_rdy) ); diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 53ac0fd0b..87eaeac72 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -590,7 +590,7 @@ set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CON #set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN -set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {32} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {5} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {32} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_NUM_OF_PROBES {18} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_flowcontrol set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] From 88f52c06522e59c55029332178d52c3c582ed1c1 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Wed, 10 Sep 2025 11:27:09 +0200 Subject: [PATCH 28/52] more ILAS in CC module --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 154 +++++++++++++++--- scripts/ip_inst/network_infrastructure.tcl | 2 +- 2 files changed, 133 insertions(+), 23 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index 62626024c..e55610cab 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -33,24 +33,42 @@ localparam integer byte_threshhold = 10; -logic[31:0] Rt; -logic[31:0] Rc; -logic[31:0] Sa; -logic[31:0] timer; +logic[63:0] Rt; +logic[63:0] Rc; +logic[63:0] Sa; +logic[63:0] timer; -logic[31:0] timer_send_rate; +logic[63:0] timer_send_rate; -logic[31:0] time_of_last_marked_packet; -logic[31:0] time_last_update; +logic[63:0] time_of_last_marked_packet; +logic[63:0] time_last_update; -logic[31:0] byte_counter; -logic[31:0] time_counter; +logic[63:0] byte_counter; +logic[63:0] time_counter; logic[4:0] tC_C; //timecounter counter logic[4:0] bC_C; //bytecounter counter logic rate_increase_event; +//debug signals +logic z_ack_arrives; +logic z_marked_packet_triggers; +logic z_alpha_decrease; + +logic z_time_counter_expires; +logic z_byte_counter_expires; + +logic z_hyper_increase; +logic z_additive_increase; +logic z_fast_recovery; + +logic z_next_req_ready; + +//simul part +//logic ecn_flood; +//logic[31:0] ecn_flood_counter; + metaIntf #(.STYPE(dreq_t)) queue_out (); @@ -78,6 +96,22 @@ always_ff @(posedge aclk) begin queue_out.ready <= 1'b0; m_req.data <= 0; + z_ack_arrives <= 0; + z_marked_packet_triggers <= 0; + z_alpha_decrease <= 0; + + z_time_counter_expires <= 0; + z_byte_counter_expires <= 0; + + z_hyper_increase <= 0; + z_additive_increase <= 0; + z_fast_recovery <= 0; + + z_next_req_ready <= 0; + + //ecn_flood <= 0; + //ecn_flood_counter <= 0; + end else begin @@ -92,10 +126,39 @@ always_ff @(posedge aclk) begin m_req.data <= queue_out.data; queue_out.ready <= 1'b0; + z_ack_arrives <= 0; + z_marked_packet_triggers <= 0; + z_alpha_decrease <= 0; + + z_time_counter_expires <= 0; + z_byte_counter_expires <= 0; + + z_hyper_increase <= 0; + z_additive_increase <= 0; + z_fast_recovery <= 0; + + z_next_req_ready <= 0; + + //symul-par + /* + ecn_flood_counter <= ecn_flood_counter + 1; + if(ecn_flood_counter > 250000000 ) begin + ecn_flood_counter <= 0; + ecn_flood <= ~ecn_flood; + end*/ + if(ecn_write_rdy) begin + //===Debug Signal=== + z_ack_arrives <= 1; + //================== byte_counter <= byte_counter + 1; + //CHANGE BACK THE ECN_FLOOD + //if(ecn_mark == 1'b1/* || ecn_flood == 1'b*/) begin if(ecn_mark == 1'b1) begin // Marked Packet arrived if(timer - time_last_update > N_min_time_between_ecn_marks) begin + //===Debug Signal=== + z_marked_packet_triggers <= 1; + //================== Rt <= Rc; Rc <= (Rc << 8) / (S1 - (Sa>>1)); Sa <= (((S1-Sg)*Sa)>> 8) + Sg; @@ -112,11 +175,21 @@ always_ff @(posedge aclk) begin end if (timer - time_of_last_marked_packet > K) begin - Sa <= (((S1-Sg)*Sa)>> 8); - + //===Debug Signal=== + z_alpha_decrease <= 1; + //================== + if(Sa <= 1) begin + Sa <= 1; + end + else begin + Sa <= (((S1-Sg)*Sa)>> 8); + end end if(time_counter > time_threshhold) begin + //===Debug Signal=== + z_time_counter_expires <= 1; + //================== time_counter <= 0; rate_increase_event <= 1; if(tC_C < F) begin @@ -125,6 +198,9 @@ always_ff @(posedge aclk) begin end if(byte_counter > byte_threshhold) begin + //===Debug Signal=== + z_byte_counter_expires <= 1; + //================== byte_counter <= 0; rate_increase_event <= 1; if(bC_C < F) begin @@ -138,15 +214,29 @@ always_ff @(posedge aclk) begin if(tC_C >= F && bC_C >= F) begin // case Hyper Increse //replaced by fast recovery + //===Debug Signal=== + z_hyper_increase <= 1; + //================== Rc <= (Rt + Rc) >> 1; end else begin // case Additive Increase - Rt <= Rt + Rai; + //===Debug Signal=== + z_additive_increase <= 1; + //================== + if(Rt + 3 < Rai) begin + Rt <= 1; + end + else begin + Rt <= Rt - Rai; + end Rc <= (Rt + Rc) >> 1; end end else begin // case Fast Recovery + //===Debug Signal=== + z_fast_recovery <= 1; + //================== Rc <= (Rt + Rc) >> 1; end end @@ -154,6 +244,10 @@ always_ff @(posedge aclk) begin // CHANGE BACK if(timer_send_rate > Rc) begin //if(timer_send_rate > 1) begin + + //===Debug Signal=== + z_next_req_ready <= 1; + //================== m_req.valid <= queue_out.valid; queue_out.ready <= m_req.ready; if(queue_out.valid && m_req.ready) begin @@ -175,18 +269,34 @@ ila_DCQCN inst_ila_DCQCN( .probe3(Sa), //32 .probe4(byte_counter), //32 .probe5(time_counter), //32 - .probe6(TC_C), //5 - .probe7(BC_C), //5 + .probe6(tC_C), //5 + .probe7(bC_C), //5 .probe8(rate_increase_event), .probe9(timer_send_rate), //32 - .probe10(s_req.valid), - .probe11(s_req.ready), - .probe12(m_req.valid), - .probe13(m_req.ready), - .probe14(queue_out.valid), - .probe15(queue_out.ready), - .probe16(ecn_mark), - .probe17(ecn_write_rdy) + .probe10(time_last_update), //32 + .probe11(time_of_last_marked_packet), //32 + + .probe12(s_req.valid), + .probe13(s_req.ready), + .probe14(m_req.valid), + .probe15(m_req.ready), + .probe16(queue_out.valid), + .probe17(queue_out.ready), + .probe18(ecn_mark), + .probe19(ecn_write_rdy), + + .probe20(z_ack_arrives), + .probe21(z_marked_packet_triggers), + .probe22(z_alpha_decrease), + .probe23(z_time_counter_expires), + .probe24(z_byte_counter_expires), + .probe25(z_hyper_increase), + .probe26(z_additive_increase), + .probe27(z_fast_recovery), + .probe28(z_next_req_ready) + /* + .probe29(ecn_flood), + .probe30(ecn_flood_counter)*/ ); diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 87eaeac72..067c55906 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -590,7 +590,7 @@ set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CON #set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN -set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {5} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {32} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_NUM_OF_PROBES {18} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {5} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {32} CONFIG.C_PROBE10_WIDTH {32} CONFIG.C_PROBE11_WIDTH {32} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_PROBE21_WIDTH {1} CONFIG.C_PROBE22_WIDTH {1} CONFIG.C_PROBE23_WIDTH {1} CONFIG.C_PROBE24_WIDTH {1} CONFIG.C_PROBE25_WIDTH {1} CONFIG.C_PROBE26_WIDTH {1} CONFIG.C_PROBE27_WIDTH {1} CONFIG.C_PROBE28_WIDTH {1} CONFIG.C_NUM_OF_PROBES {29} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_flowcontrol set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] From 19a8a940def1c9fe6befdcf38f308896a0380d6c Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Thu, 11 Sep 2025 11:40:23 +0200 Subject: [PATCH 29/52] stepsized alphas added to remove division --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 97 ++++++++++++++----- scripts/ip_inst/network_infrastructure.tcl | 2 +- 2 files changed, 74 insertions(+), 25 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index e55610cab..b51240f63 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -18,19 +18,31 @@ localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); localparam integer RD_OP = 0; localparam integer WR_OP = 1; -localparam integer Rt_default = 100000; -localparam integer Rc_default = 100000; -localparam integer N_min_time_between_ecn_marks = 12500; -localparam integer K = 13750; -localparam integer Rai = 6250; +//localparam integer Rt_default = 100000; +localparam integer Rt_default = 128; +localparam integer Rc_default = 8192; +localparam integer N_min_time_between_ecn_marks = 200; +localparam integer K = 600; +//localparam integer Rai = 6250; +localparam integer Rai = 60; localparam integer S1 = 128; // 1 << 8 localparam integer Sg = 8; // (1/16) << 8 localparam integer F = 5; -localparam integer time_threshhold = 100; +localparam integer time_threshhold = 150; localparam integer byte_threshhold = 10; +localparam integer Max_Sa_index = 3; +//coarse_grained +localparam integer Sa_fixed[0:3] = {16, 8, 3, 2}; +//finegrained +//localparam integer Sa_fixed[0:28] = {256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139}; + +logic[3:0] Sa_index; +//logic[8:0] Sa_index; + + logic[63:0] Rt; @@ -66,8 +78,8 @@ logic z_fast_recovery; logic z_next_req_ready; //simul part -//logic ecn_flood; -//logic[31:0] ecn_flood_counter; +logic[31:0] ecn_counter; +logic ecn_alternator; metaIntf #(.STYPE(dreq_t)) queue_out (); @@ -109,8 +121,10 @@ always_ff @(posedge aclk) begin z_next_req_ready <= 0; - //ecn_flood <= 0; - //ecn_flood_counter <= 0; + Sa_index <= 0; + + ecn_alternator <= 1; + ecn_counter <= 0; end @@ -139,29 +153,53 @@ always_ff @(posedge aclk) begin z_next_req_ready <= 0; - //symul-par - /* - ecn_flood_counter <= ecn_flood_counter + 1; - if(ecn_flood_counter > 250000000 ) begin - ecn_flood_counter <= 0; - ecn_flood <= ~ecn_flood; - end*/ if(ecn_write_rdy) begin //===Debug Signal=== z_ack_arrives <= 1; //================== byte_counter <= byte_counter + 1; - //CHANGE BACK THE ECN_FLOOD - //if(ecn_mark == 1'b1/* || ecn_flood == 1'b*/) begin - if(ecn_mark == 1'b1) begin // Marked Packet arrived + + /* + if(ecn_counter >= 20) begin + ecn_counter <= 0; + ecn_alternator <= ~ecn_alternator; + end/* + else begin + ecn_counter <= ecn_counter + 1; + end*/ + + + + + if(ecn_mark == 1'b1/* && ecn_alternator == 1*/) begin // Marked Packet arrived if(timer - time_last_update > N_min_time_between_ecn_marks) begin //===Debug Signal=== z_marked_packet_triggers <= 1; //================== Rt <= Rc; + + //Rc mul by 2 every time + /* + if(Rc <= 8192) begin + Rc <= Rc << 2; + end + */ + //fixed size Sa + + if(Rc <= 8192) begin + Rc <= (Rc * Sa_fixed[Sa_index]); + end + if(!(Sa_index == 0)) begin + Sa_index <= Sa_index - 1; + end + + + /* Rc <= (Rc << 8) / (S1 - (Sa>>1)); Sa <= (((S1-Sg)*Sa)>> 8) + Sg; + + */ time_last_update <= timer; time_counter <= 0; @@ -178,12 +216,21 @@ always_ff @(posedge aclk) begin //===Debug Signal=== z_alpha_decrease <= 1; //================== + /* if(Sa <= 1) begin Sa <= 1; end else begin Sa <= (((S1-Sg)*Sa)>> 8); end + */ + + + if(!(Sa_index == Max_Sa_index)) begin + Sa_index <= Sa_index + 1; + end + + end if(time_counter > time_threshhold) begin @@ -248,6 +295,7 @@ always_ff @(posedge aclk) begin //===Debug Signal=== z_next_req_ready <= 1; //================== + m_req.valid <= queue_out.valid; queue_out.ready <= m_req.ready; if(queue_out.valid && m_req.ready) begin @@ -293,10 +341,11 @@ ila_DCQCN inst_ila_DCQCN( .probe25(z_hyper_increase), .probe26(z_additive_increase), .probe27(z_fast_recovery), - .probe28(z_next_req_ready) - /* - .probe29(ecn_flood), - .probe30(ecn_flood_counter)*/ + .probe28(z_next_req_ready), + + .probe29(Sa_index), + .probe30(ecn_alternator), + .probe31(ecn_counter) ); diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 067c55906..4ded341ad 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -590,7 +590,7 @@ set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CON #set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN -set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {5} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {32} CONFIG.C_PROBE10_WIDTH {32} CONFIG.C_PROBE11_WIDTH {32} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_PROBE21_WIDTH {1} CONFIG.C_PROBE22_WIDTH {1} CONFIG.C_PROBE23_WIDTH {1} CONFIG.C_PROBE24_WIDTH {1} CONFIG.C_PROBE25_WIDTH {1} CONFIG.C_PROBE26_WIDTH {1} CONFIG.C_PROBE27_WIDTH {1} CONFIG.C_PROBE28_WIDTH {1} CONFIG.C_NUM_OF_PROBES {29} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {5} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {32} CONFIG.C_PROBE10_WIDTH {32} CONFIG.C_PROBE11_WIDTH {32} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_PROBE21_WIDTH {1} CONFIG.C_PROBE22_WIDTH {1} CONFIG.C_PROBE23_WIDTH {1} CONFIG.C_PROBE24_WIDTH {1} CONFIG.C_PROBE25_WIDTH {1} CONFIG.C_PROBE26_WIDTH {1} CONFIG.C_PROBE27_WIDTH {1} CONFIG.C_PROBE28_WIDTH {1} CONFIG.C_PROBE29_WIDTH {4} CONFIG.C_PROBE30_WIDTH {1} CONFIG.C_PROBE31_WIDTH {32} CONFIG.C_NUM_OF_PROBES {32} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_flowcontrol set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] From 8d8108f8b059643fcbfc4a88f7e540021ced3a09 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Fri, 12 Sep 2025 07:52:50 +0200 Subject: [PATCH 30/52] submodule ref update --- hw/services/network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/services/network b/hw/services/network index c1f85bbf4..ffd8b038b 160000 --- a/hw/services/network +++ b/hw/services/network @@ -1 +1 @@ -Subproject commit c1f85bbf483bbdad6e103946886a55963722fe3c +Subproject commit ffd8b038b2b7145c266fed2dd9d706f24b8e7869 From a0d0be4590117ad77998443f43a2df74751507c9 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Fri, 12 Sep 2025 10:58:40 +0200 Subject: [PATCH 31/52] ... --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 24 ++++++++++++------- scripts/ip_inst/network_infrastructure.tcl | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index b51240f63..a9de28667 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -33,14 +33,16 @@ localparam integer F = 5; localparam integer time_threshhold = 150; localparam integer byte_threshhold = 10; -localparam integer Max_Sa_index = 3; +localparam integer ecn_alternation_timeframe = 10000; + +localparam integer Max_Sa_index = 28; //coarse_grained -localparam integer Sa_fixed[0:3] = {16, 8, 3, 2}; +//localparam integer Sa_fixed[0:3] = {16, 8, 3, 2}; //finegrained -//localparam integer Sa_fixed[0:28] = {256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139}; +localparam integer Sa_fixed[0:28] = {256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139}; -logic[3:0] Sa_index; -//logic[8:0] Sa_index; +//logic[3:0] Sa_index; +logic[8:0] Sa_index; @@ -78,7 +80,7 @@ logic z_fast_recovery; logic z_next_req_ready; //simul part -logic[31:0] ecn_counter; +//logic[31:0] ecn_counter; logic ecn_alternator; @@ -169,10 +171,14 @@ always_ff @(posedge aclk) begin ecn_counter <= ecn_counter + 1; end*/ + if(timer % ecn_alternation_timeframe == 0) begin + ecn_alternator <= ~ecn_alternator; + end + - if(ecn_mark == 1'b1/* && ecn_alternator == 1*/) begin // Marked Packet arrived + if(ecn_mark == 1'b1 && ecn_alternator == 1) begin // Marked Packet arrived if(timer - time_last_update > N_min_time_between_ecn_marks) begin //===Debug Signal=== z_marked_packet_triggers <= 1; @@ -187,8 +193,8 @@ always_ff @(posedge aclk) begin */ //fixed size Sa - if(Rc <= 8192) begin - Rc <= (Rc * Sa_fixed[Sa_index]); + if(Rc <= 4096) begin + Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; end if(!(Sa_index == 0)) begin Sa_index <= Sa_index - 1; diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 4ded341ad..aed4fb44b 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -590,7 +590,7 @@ set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CON #set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN -set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {32} CONFIG.C_PROBE4_WIDTH {32} CONFIG.C_PROBE5_WIDTH {32} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {5} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {32} CONFIG.C_PROBE10_WIDTH {32} CONFIG.C_PROBE11_WIDTH {32} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_PROBE21_WIDTH {1} CONFIG.C_PROBE22_WIDTH {1} CONFIG.C_PROBE23_WIDTH {1} CONFIG.C_PROBE24_WIDTH {1} CONFIG.C_PROBE25_WIDTH {1} CONFIG.C_PROBE26_WIDTH {1} CONFIG.C_PROBE27_WIDTH {1} CONFIG.C_PROBE28_WIDTH {1} CONFIG.C_PROBE29_WIDTH {4} CONFIG.C_PROBE30_WIDTH {1} CONFIG.C_PROBE31_WIDTH {32} CONFIG.C_NUM_OF_PROBES {32} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {64} CONFIG.C_PROBE1_WIDTH {64} CONFIG.C_PROBE2_WIDTH {64} CONFIG.C_PROBE3_WIDTH {64} CONFIG.C_PROBE4_WIDTH {64} CONFIG.C_PROBE5_WIDTH {64} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {5} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {64} CONFIG.C_PROBE10_WIDTH {64} CONFIG.C_PROBE11_WIDTH {64} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_PROBE21_WIDTH {1} CONFIG.C_PROBE22_WIDTH {1} CONFIG.C_PROBE23_WIDTH {1} CONFIG.C_PROBE24_WIDTH {1} CONFIG.C_PROBE25_WIDTH {1} CONFIG.C_PROBE26_WIDTH {1} CONFIG.C_PROBE27_WIDTH {1} CONFIG.C_PROBE28_WIDTH {1} CONFIG.C_PROBE29_WIDTH {9} CONFIG.C_PROBE30_WIDTH {1} CONFIG.C_PROBE31_WIDTH {32} CONFIG.C_NUM_OF_PROBES {32} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_flowcontrol set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] From df2d0d9fbdfe2bbcba2ba2ca8cbdc3271a6b934b Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Mon, 15 Sep 2025 22:53:00 +0200 Subject: [PATCH 32/52] ... --- .../README.md | 186 ++++++++++++++++++ .../hw/CMakeLists.txt | 32 +++ .../hw/src/init_ip.tcl | 2 + .../hw/src/vfpga_top.svh | 71 +++++++ .../img/rdma_results_read.png | Bin 0 -> 130953 bytes .../img/rdma_results_write.png | Bin 0 -> 131862 bytes .../img/rdma_system_overview.svg | 1 + .../img/rdma_traffic_pattern.svg | 1 + .../sw/CMakeLists.txt | 26 +++ .../sw/src/client/main.cpp | 147 ++++++++++++++ .../sw/src/include/constants.hpp | 9 + .../sw/src/server/main.cpp | 120 +++++++++++ .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 70 +++---- scripts/ip_inst/network_infrastructure.tcl | 2 +- 14 files changed, 627 insertions(+), 40 deletions(-) create mode 100644 examples/MT_pomsarc_10_single_sided_writes/README.md create mode 100644 examples/MT_pomsarc_10_single_sided_writes/hw/CMakeLists.txt create mode 100644 examples/MT_pomsarc_10_single_sided_writes/hw/src/init_ip.tcl create mode 100644 examples/MT_pomsarc_10_single_sided_writes/hw/src/vfpga_top.svh create mode 100644 examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_read.png create mode 100644 examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_write.png create mode 100644 examples/MT_pomsarc_10_single_sided_writes/img/rdma_system_overview.svg create mode 100644 examples/MT_pomsarc_10_single_sided_writes/img/rdma_traffic_pattern.svg create mode 100644 examples/MT_pomsarc_10_single_sided_writes/sw/CMakeLists.txt create mode 100644 examples/MT_pomsarc_10_single_sided_writes/sw/src/client/main.cpp create mode 100644 examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp create mode 100644 examples/MT_pomsarc_10_single_sided_writes/sw/src/server/main.cpp diff --git a/examples/MT_pomsarc_10_single_sided_writes/README.md b/examples/MT_pomsarc_10_single_sided_writes/README.md new file mode 100644 index 000000000..f5387b7fd --- /dev/null +++ b/examples/MT_pomsarc_10_single_sided_writes/README.md @@ -0,0 +1,186 @@ +# Coyote Example 8: Using the FPGA as a SmartNIC for Remote Direct Memory Access (RDMA) +Welcome to the eigth Coyote example! If you've made it to this point, you've already learnt all the essential concepts of FPGA-CPU interaction for data movement and control flow. Thus, we're adding a new component by demonstrating how Coyote can be used to turn the FPGA into a SmartNIC for 100G RDMA-over-Converged-Ethernet (RoCE v2), allowing for efficient, high-performance data exchange between remote processing nodes and FPGA-offloaded preprocessing of the data streams. As with all Coyote examples, a brief description of the core Coyote concepts covered in this example is included below. + +##### Table of Contents +[RDMA Overview](#rdma-overview) + +[Example Overview](#example-overview) + +[Hardware Concepts](#hardware-concepts) + +[Software Concepts](#software-concepts) + +[Additional Information](#additional-information) + +## RDMA Overview +Remote Direct Memory Access over Converged Ethernet (RoCE v2) is a high-performance networking protocol originally developed for High-Performance Computing. It aims to combine high throughput, low latency and low CPU-utilization by offloading the network stack and direct memory access to the Network Interface Card (NIC), thus grounding its performance claims on host-bypassing and zero-copy: applications running on the host CPUs directly expose memory buffers for DMA to the NIC, so that data can be exchanged without including the host CPU or the OS running on it. In this example, we demonstrate how Coyote can be configured to use the FPGA in the role of such a NIC, allowing for fully protocol-compliant RoCE v2-traffic at 100 Gbps. +For further understanding of the RDMA example, we need to shed some light on the intrinsics of this network standard. +For RDMA communication, two nodes are connected via a so-called Queue Pair (QP), based on previously exchanged information such as respective IP-addresses, location and size of the allocated memory buffers and access keys, and comprising of the idea of queues for work commands and data. The initial exchange of RDMA operations are managed through so-called InfiniBand-verbs, which describe the form of memory transaction between the remote nodes. We have to separate between *one-sided operations* that initiate data transfers over the network without a previous request for consent, and *two-sided operations* where every data transaction is preceeded by an additional exchange between the communicating nodes. Coyote implements the two one-sided RDMA operations: +- ```RDMA WRITE```: One node writes data of certain length to a remote buffer in the other node. Every sent WRITE packet has to be explicitly acked by the receiving node. +- ```RDMA READ```: One node requests to read data of certain length from a remote buffer in the other node. The READ REQUEST packet is implicitly ACK'ed when the remote node sends back RDMA RESPONSE packets with the requested data. + +**IMPORTANT TERMINOLOGY:** +To have a better understanding of this example and the following descriptions, a short overview of the RDMA-specific terminology is very helpful: +- *QPN*: Queue Pair Number. Identifies a RDMA connection between two remote nodes. +- *vaddr*: Virtual address of the remote exposed buffer. For setting up a Queue Pair, a node has to share the virtual address of its exposed buffer with the remote node. +- *rkey*: Remote key for accessing the exposed buffer of the remote node. The remote key is communicated alongside the virtual address as a form of access control. +- *PSN*: Packet Sequence Number used to organize and structure a communication flow between remote RDMA-nodes. Normally, PSN-sequencing is handled by the FPGA-offloaded network stack and thus is not visible to the user. However, following the PSN order is very helpful for debugging RDMA networks. The initial PSN is agreed upon between the remote nodes as part of the initialization of the QP. +- *MTU*: Maximum Transmission Unit, defines the maximum size of a packet. Since RDMA operates on the notion of buffer transmission, the length of the data to be transmitted determines the number of MTU-sized packets that are required for the operation. While the MTU is a compile-time parameter in Coyote, the default size is 4 KB. This means that a ```RDMA WRITE``` of a buffer of up to 4 KB size results in a single packet with ```RDMA WRITE ONLY``` opcode, while a buffer of 8 KB size would be transmitted in two packets, one carrying ```RDMA WRITE FIRST```, the other with ```RDMA WRITE LAST```. For even longer buffers, an arbitrary number of ```RDMA WRITE MIDDLE``` packets is inserted in the exchange. + +## Example Overview +This example measures throughput and latency of RDMA data exchange between two remode nodes via a 100G switched network with both ```RDMA WRITE``` and ```RDMA READ``` operations. Assuming the simpler write functionality, we can very generally think about the process as moving of a data buffer from the host CPU to the local FPGA, where it is streamed to the FPGA-offloaded RDMA stack and then sent via the network to the remote FPGA. There, data is received and then written to the previously specified address of the remote buffer of this remote host CPU. Different to all previous examples, this requires two servers with FPGAs, which also run different software application. The active node, which initiates communication exchange, is referred to as the "client", while the remote node is used as "server". An example of the dataflow is given in the figure below; and as shown in the figure, the steps are the following: +1) *QP Exchange*: As explained above, the RDMA connection in form of a Queue Pair needs to be set up between the remote nodes before the actual benchmarking test can begin. Since RDMA cannot be bootstrapped, this requires out-of-band communication between the two remote CPUs via classic TCP sockets. In the context of the HACC, we are using the 10G management network for this purpose. Using ```ifconfig```, the IP address of the CPU determined to be the server can be evaluated and then given to the experiment code as a parameter. The two nodes follow a standardized protocol to exchange relevant start-up information, such as the IP- addresses of the FPGAs in the 100G data network, the initial PSN and the address and access key of the network exposed data buffers. +2) *QP Setup*: After having exchanged these essential pieces of information, both CPUs forward the aggregated QP data to the FPGAs to create the endpoints of the RDMA flow in the hardware-offloaded network stack. This means making information such as the target IP address or buffer virtual address retrievable by the QPN as essential key. +3) *REMOTE WRITE*: In case of a WRITE-based benchmark, the client now begins with a ```REMOTE WRITE``` Coyote operation to transmit a local buffer via the network to the remote node. For this, both data and commands traverse the RDMA-allocated vFGPA and reach the RDMA stack on the FPGA, where RoCE v2 packets are created and then sent out via the network. +4) *Receiving data*: In the above example, the server FPGA receives the incoming RDMA packets and automatically checks for correctness of the PSN-sequence and QP-specific information. If the received packet fits into the expected communication flow, it gets ACK'ed: an ```RDMA ACK``` is sent back to the client as a reply. At the same time, the server FPGA sends the received data as a ```LOCAL WRITE``` (see Example 1) to the local buffer on the CPU. +5) *Processing ACKs & creating completions*: The client FPGA is constantly listening to the network for ACKs for its outstanding packets. If an ACK is received, the original work command for this transaction is marked as completed, which is also communicated to the local client CPU. If no ACK is received within a certain time interval after sending out a packet, a retransmission is issued. In this case, the same packet is sent again, assuming that it was originally lost in the network or not properly received by the server. + +
+ +
+ +Generally speaking, throughput and latency tests behave vastly different, also depending on the chosen mode of operation (WRITE vs. READ). The different cases are depicted in the figure below and can be understood as following: +- *Latency* for ```RDMA WRITE```: The client issues a single WRITE of a buffer of specified length to the remote server. Upon reception, the server ACKs and then writes back this very buffer to the client, thus creating a typical "ping-pong pattern" of communication, for which the two-way latency can be measured from sending out a buffer to receiving it back. Depending on the specified experimental arguments, this exchange is repeated for a certain number of times before the average latency of all transmissions is reported. +- *Throughput* for ```RDMA WRITE```: The client issues *n* WRITEs (depending on specification of the argument in experiment execution) of a buffer of specified length to the remote server and waits for all required ACKs. Upon reception, the server writes back the same buffer *n* times again. +- *Latency* for ```RDMA READ```: The client issues a single READ of a buffer of specified length from the remote server. Instead of ACK'ing the server sends the requested data via ```RDMA READ RESPONSEs```. +- *Throughput* for ```RDMA READ``` In this case, the client issues *n* READs (depending on specification of the argument in experiment execution) of a buffer of specified length from the remote server and waits for data delivery via ```RDMA READ RESPONSEs``` from there. The server in this case does not issue reflective READs to the client. + +
+ +
+ +**IMPORTANT:** When executing RDMA benchmarks, it's important to always start the server's software first before doing the same on the client. The reason for this lies in the intrinsics of the QP exchange: The server software is constructed to listening for incoming TCP connections from the client to then take the passive role in the off-channel exchange of information. + + +## Hardware Concepts +The core complexity for RDMA in Coyote is hidden from the user within the network stack. The RDMA-dedicated vFPGA is mainly used for connecting interfaces without any additional user logic required. +The previously introduced send- and receive-queues are connected as following in the vFPGA: +```Verilog +always_comb begin + /* + * CONTROL SIGNALS + * + * rq_(wr|rd) are two more Coyote interfaces, which act as inputs to the user application + * They corresponds to network write/read requests, set from the host software and driver + * Here, they are used to set Coyote's generic send queues, previously discussed in Example 7. + */ + // Write + sq_wr.valid = rq_wr.valid; + rq_wr.ready = sq_wr.ready; + sq_wr.data = rq_wr.data; // Data field holds information such as remote, virtual address, buffer length etc. + sq_wr.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host + sq_wr.data.dest = is_opcode_rd_resp(rq_wr.data.opcode) ? 0 : 1; + + // Reads + sq_rd.valid = rq_rd.valid; + rq_rd.ready = sq_rd.ready; + sq_rd.data = rq_rd.data; // Data field holds information such as remote, virtual address, buffer length etc. + sq_rd.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host + sq_rd.data.dest = 1; +end +``` + +On the other hand, the data interfaces are connected as following in the module: + +```Verilog +/* + * DATA SIGNALS + * + */ +// Data streams for outgoing RDMA WRITEs (from local host to network stack to remote node) +`AXISR_ASSIGN(axis_host_recv[0], axis_rreq_send[0]) + +// Data streams for incoming RDMA READ RESPONSEs (from remote node to network stack to local host) +`AXISR_ASSIGN(axis_rreq_recv[0], axis_host_send[0]) + +// Data streams for outgoing RDMA READ RESPONSEs (from local host to network stack to remote node) +`AXISR_ASSIGN(axis_host_recv[1], axis_rrsp_send[0]) + +// Data streams for incoming RDMA WRITEs (from remote node to network stack to local host) +`AXISR_ASSIGN(axis_rrsp_recv[0], axis_host_send[1]) +``` + +Thinking one step further, beyond the scope of this performance benchmark, it becomes obvious how any pipelined user logic can be placed on these interfaces to process incoming or outgoing RDMA traffic. Placing customized user logic directly on these datapaths is one of the key benefits of a FPGA-based SmartNIC such as Coyote with the network configuration. + + +## Software Concepts +As described above, the main notion of logic abstraction in RDMA is the Queue-Pair (QP), which connects two remote nodes. Both nodes first form their own local Queue, which again is directly linked to the memory buffer that is exposed via the network. Afterwards, an off-channel exchange via TCP/IP is started to exchange the local queues and then form a QP. + +In Coyote, the notion of a QP is directly linked to a ``coyoteThread``. Thus, the first step of setting up RDMA in Coyote is creating such a thread: +```C++ +coyote::cThread coyote_thread(DEFAULT_VFPGA_ID, getpid(), 0); +```` + +Following the creation of the thread, it's necessary to call the function ```initRDMA()```, which allocates an RDMA buffer of the requested size and performs the QP exchange between the client and server. Importantly, the client needs to pass the server's IP address, while the server code leaves this field blank (nullptr, indicating that it's the server). The function return a pointer to the memory buffer to be used for RDMA communication. +```C++ +int *mem = (int *) coyote_thread.initRDMA(max_size, coyote::defPort, server_ip.c_str()); +``` + +After the setup is completed, we follow the standard approach in Coyote: creating a scatter-gather entry and issuing an ``ìnvoke(...)``` call, but this time indicating a remote operation: +```C++ +coyote::sgEntry sg; +sg.rdma = { .len = curr_size }; +coyote_thread.invoke(coyote::CoyoteOper::REMOTE_RDMA_WRITE, &sg); +``` + + +## Additional Information + +### Special remarks on building Coyote for RDMA experiments +Since different software versions are required for server and client to run a benchmark, two cmake builds have to be triggered to obtain the correct executables. In order to build the server code, one needs to specify `-DINSTANCE=server`, while a build of the client software is specified with `-DINSTANCE=client`: +```bash +cd sw/ + +mkdir build_server && cd build_server +cmake ../../ -DINSTANCE=server && make + +cd ../ +mkdir build_client && cd build_client +cmake ../../ -DINSTANCE=client && make +``` + +### Command line parameters and hints on running the experiment +As said above, it's crucial to start the software for experiments first on the node that we want to use as server, before doing the same for the client. Furthermore, it's important that the IP address specified as argument on the client machine **belongs to the server-CPU (not the client CPU, not the server FPGA)**. The different available network interfaces can be explored with ```ifconfig```. +The following description helps to match the relevant command line parameters to details of the experiment execution described before: + +- `[--ip_address | -i] ` IP address of the server CPU for out-of-band QP-exchange via TCP-sockets before the actual RDMA experiment can begin. +- `[--operation | -o] ` Decides whether the benchmark is performed for WRITE (1) or READ (0) operations. Default: 0 +- `[--min_size | -x] ` Minimum size of transferred buffer in the experiment. Default: 64 [B] +- `[--max_size | -X] ` Maximum size of transferred buffer in the experiment. Default: 1048576 [B] ~ 1 [MB] +- `[--runs | -r] ` Number of test runs, to obtain statistically significant results For latency-tests, `r` ping-pong exchanges will be executed. For throughput tests, `r` independent exchanges of 64 messages are executed. + +### Network debugging +Coyote provides tooling to check the status of network transmissions and identify potential problems, most notable packet losses and retransmissions. These statistics can be queried via +`cat /sys/kernel/coyote_sysfs_0/cyt_attr_nstats`. + +A typical output for this could look like this: +``` + -- NET STATS QSFP0 + +RX pkgs: 316 +TX pkgs: 242 +ARP RX pkgs: 4 +ARP TX pkgs: 2 +ICMP RX pkgs: 0 +ICMP TX pkgs: 0 +TCP RX pkgs: 0 +TCP TX pkgs: 0 +ROCE RX pkgs: 245 +ROCE TX pkgs: 240 +IBV RX pkgs: 240 +IBV TX pkgs: 240 +PSN drop cnt: 0 +Retrans cnt: 0 +TCP session cnt: 0 +STRM down: 0 +``` +In this case, 240 RDMA packets have been received and sent (``IBV RX/TX``), no packet has been lost or retransmitted. A major problem would be indicated by ``STRM down: 1``, as this would signal a complete shutdown of the networking stack. In such a case, only a hard reset including reprogramming the FPGA can save the user. +Apart from these Coyote-provided utilities, standard network debugging tools such as switch- or external NIC-based traffic capturing can be used to understand traffic issues and patterns. As described in the next example, we also offer a 100G-capable traffic sniffer for Coyote that directly produces pcap-files compatible to any standard network analysis tool such as wireshark. If even this low-level debugging capabilities are not deemed satisfactory, also classic ILA-based probing directly on data- and controlpaths of the FPGA-design is possible. + +### Expeted results +The following results are expected for reads (left) and writes (right): + +

+ + +

\ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/hw/CMakeLists.txt b/examples/MT_pomsarc_10_single_sided_writes/hw/CMakeLists.txt new file mode 100644 index 000000000..e729f3666 --- /dev/null +++ b/examples/MT_pomsarc_10_single_sided_writes/hw/CMakeLists.txt @@ -0,0 +1,32 @@ +# CMake configuration +cmake_minimum_required(VERSION 3.5) +set(CYT_DIR ${CMAKE_SOURCE_DIR}/../../../) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CYT_DIR}/cmake) +find_package(CoyoteHW REQUIRED) + +project(example_08_perf_rdma) +message("*** Coyote Example 8: Remote Direct Memory Access (RDMA) [Hardware Build] ***") + +# Enables two streams from host memory (1 for requests, 1 for response) +set(EN_STRM 1) +set(N_STRM_AXI 2) + +# Incldue Coyote's RDMA stack during synthesis +set(EN_RDMA 1) + +# Number of vFPGAs (user applications) +set(N_REGIONS 1) + +# Build with optimization, since timing closure with RDMA can be difficult +set(BUILD_OPT 1) + +# Confirm that the selected options are allowed +validation_checks_hw() + +# Load a user application in Configuration #0, Region #0 +load_apps ( + VFPGA_C0_0 "src" +) + +# Create the hardware project +create_hw() diff --git a/examples/MT_pomsarc_10_single_sided_writes/hw/src/init_ip.tcl b/examples/MT_pomsarc_10_single_sided_writes/hw/src/init_ip.tcl new file mode 100644 index 000000000..64dc40e3d --- /dev/null +++ b/examples/MT_pomsarc_10_single_sided_writes/hw/src/init_ip.tcl @@ -0,0 +1,2 @@ +create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_perf_rdma +set_property -dict [list CONFIG.C_PROBE17_WIDTH {128} CONFIG.C_PROBE14_WIDTH {128} CONFIG.C_NUM_OF_PROBES {18} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_perf_rdma] \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/hw/src/vfpga_top.svh b/examples/MT_pomsarc_10_single_sided_writes/hw/src/vfpga_top.svh new file mode 100644 index 000000000..240e06b7e --- /dev/null +++ b/examples/MT_pomsarc_10_single_sided_writes/hw/src/vfpga_top.svh @@ -0,0 +1,71 @@ +always_comb begin + /* + * CONTROL SIGNALS + * + * rq_(wr|rd) are two more Coyote interfaces, which act as inputs to the user application + * They corresponds to network write/read requests, set from the host software and driver + * Here, they are used to set Coyote's generic send queues, previously discussed in Example 7. + */ + // Write + sq_wr.valid = rq_wr.valid; + rq_wr.ready = sq_wr.ready; + sq_wr.data = rq_wr.data; // Data field holds information such as remote, virtual address, buffer length etc. + sq_wr.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host + sq_wr.data.dest = is_opcode_rd_resp(rq_wr.data.opcode) ? 0 : 1; + + // Reads + sq_rd.valid = rq_rd.valid; + rq_rd.ready = sq_rd.ready; + sq_rd.data = rq_rd.data; // Data field holds information such as remote, virtual address, buffer length etc. + sq_rd.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host + sq_rd.data.dest = 1; +end + +/* + * DATA SIGNALS + * + */ +// Data streams for outgoing RDMA WRITEs (from local host to network stack to remote node) +`AXISR_ASSIGN(axis_host_recv[0], axis_rreq_send[0]) + +// Data streams for incoming RDMA READ RESPONSEs (from remote node to network stack to local host) +`AXISR_ASSIGN(axis_rreq_recv[0], axis_host_send[0]) + +// Data streams for outgoing RDMA READ RESPONSEs (from local host to network stack to remote node) +`AXISR_ASSIGN(axis_host_recv[1], axis_rrsp_send[0]) + +// Data streams for incoming RDMA WRITEs (from remote node to network stack to local host) +`AXISR_ASSIGN(axis_rrsp_recv[0], axis_host_send[1]) + +// Tie off unused interfaces +always_comb axi_ctrl.tie_off_s(); +always_comb notify.tie_off_m(); +always_comb cq_rd.tie_off_s(); +always_comb cq_wr.tie_off_s(); + +// ILA for debugging +ila_perf_rdma inst_ila_perf_rdma ( + .clk(aclk), + .probe0(axis_host_recv[0].tvalid), // 1 + .probe1(axis_host_recv[0].tready), // 1 + .probe2(axis_host_recv[0].tlast), // 1 + + .probe3(axis_host_recv[1].tvalid), // 1 + .probe4(axis_host_recv[1].tready), // 1 + .probe5(axis_host_recv[1].tlast), // 1 + + .probe6(axis_host_send[0].tvalid), // 1 + .probe7(axis_host_send[0].tready), // 1 + .probe8(axis_host_send[0].tlast), // 1 + + .probe9(axis_host_send[1].tvalid), // 1 + .probe10(axis_host_send[1].tready), // 1 + .probe11(axis_host_send[1].tlast), // 1 + + .probe12(sq_wr.valid), // 1 + .probe13(sq_wr.ready), // 1 + .probe14(sq_wr.data), // 128 + .probe15(sq_rd.valid), // 1 + .probe16(sq_rd.ready), // 1 + .probe17(sq_rd.data) // 128 +); diff --git a/examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_read.png b/examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_read.png new file mode 100644 index 0000000000000000000000000000000000000000..efc149483e13286d8c1704987b5085e0bb1f47c4 GIT binary patch literal 130953 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

Mi0$EN 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

SJQT_*5a%AsT z5%J)sup?NsX>q`{eRKCAEgf9w1kFOPe0GFfPaREnmHcSlBRQG{T`zsavz4z}(OQ)1 z#F81*IgrtG>CDq_`=F4PqCY&gF>dyBuS}MOnFa-Ff9>$0AG9^=@wMI5uQDoR9T3-A zlKDSFTT%>Y2DwabhDltYiM-zYHDT`y%fAcoU7khDq$QqtA{1oVFDnrdnN75w6TmC|Q`T$O^c@X%|Pc#e>t* zz7n^={afKr9klU`TSs&w{a9|5val?-AMX6V(?^0~P4s+XQKqPr3O^1~v|0PUH9t9) zdI9qR*#+eeild2GgJz4G5+vJADsENhZXQzp;L%SCEM%>IZQ4@N&h^;xvuxwGt(brI z!2-KwoChj|30MOc+x_mxCK((dP%B}T0g*MdArnntLB~Z99a+3EoI>7 z*EgZ>FoiVb05k2*^arc>MTohih*N!~a^~&feJDNyhWg-9Bnia&3E{42eA!94)0Ye; z2i3!2q7e%<=9&3ppp&S@1Bj}2g@4koFEJvbM=8Ml3mAo2KvceM^Ck|E$C5#5mA1t zW;PI4&(q*E6@QK5ujm8Zh4nuO6`;F%PUao$?-cJ&pK?-#My4SjV4m z5(Xz!#1u|@47{=T)jtv^P@Fs=>0uJ-M{7U8V)IDUa~`g3s=H)?;5-bl6g#3=Zv>YY zMT$MLj%}=rc12L;#5V1tKqYg^hbWr?@E@NHY3BqS#_o*j{^cqbI8PR=Dh2v2>PaXu z%a9POA76?ajXVVy3aw#|9Vg!Qb=zT@Mm6k*MS8^umo6;&dn~0uo%4TrZTC3*Z*PP5 zE?%JItraQ=F{&~Db0pZ&{%^0GqOmaG0LlP%6VyR+tFky2z>ZY&{Y$;T&Z>Pck@{1R zMeba)^lA7R2*!B-K60Im@bs1N?dt}?fBt5UHtE{V91jm4V{0&Z{JhkhZ-|r~C6&ZfDSQ?9|HBbqXw{2QSapC5^LWrha zcHNAM2d)q$ajMGcbCAsA(j`To4SJ-hrl!gK{u@)0+1p?9-cW3{6C`TPaTWm~kf<9F zM|?SKTou8v#cDvoXP3HJ=62GzjTkx4J^u@MlQiL*ov(wExUYZj_{Vm0)@IN*f^q-F zw?E-<7a(5lGdD;a(RDah*Dwe&201?OVM@Xzs0rdwf(wW(V&q$KHOg}z17)>APz*ob zSq*2d&}gtk_gn%f>eiV7wctqyqV`z^aNB$W`k3!0nr4|i0^pF#_rSZ5w&QA(9?B2v z>#y0#ivWtDns={OJK=oZ-(bl!;=xq&^^~cd?9yy>Q5sqIu!#I=t48WN* zvo(?JUF0GccXO`+TZ96v3HoEXo@H^t7nsyTeF+jLv8$B1D{<`)v7vc+j48xqdfE!W z5*|)jivj)IWpvshIDd-hvnUR3&J$3$ehQNcbiFZIhqYFK2_h@Sv||jM=~9z}yn(xh zP5lhzc{$jd!Uv~Fh+_KF_~``8C4W*40f*8GjQcR37FB3|>(90tNhS1)%BS4W8$csW zvMkks)SLA&oN)NYe&`vl8)1VWL29a@0j4^Uw66ucPkoe7hAs}dZusRb?O&6hZfzr; ztSfYwqG*^Y+ay}%xrNVRW|JB)Yh(ffjrO#zzrJv0U4iKCEMC~G$AC_ddmBT27)YX- z2(!n`#;uqXs5U8V!U_w-8FZe0#{{8`0pB0ST-v^_jWBb2g7EDT&*OG6nS6nx1ZBwX zjl?{A``$KqLiCwmop0VsK4;Ra_ zG3y}-a8k&F-MFF$eOq)PlwUF&`pAlR30(o4#D{J}xS}xIa@^KZEC|vj%m|mb4W5d? zMw@9rzX>;8C(P7Jbv-YqexICOAyB##9E}600T1vGNK=Y_0+HJS>rq2%?jxN2+=09v z$3GeuIov@x{|OSYHCrsP4K8ndiSNXj#`7*^cxy|WhIvy+&L?1RX959faUQ4)xHuDx z{?ShlGlEO#scgkp(gM>2Ysjm}{4AbUxd195dKip{k z#0UzR{MtNKtOPFZB+efzx=!pb9ES}D)&110TQ5P}amgaQ;m>r`x$AI_I?fYtYqp@| zZH(F?`zOF8GE@{^K-a;ukrM}_FPh94h5G1Pyiyc3J4ZoX>d;yF8J@NA$@-{z`;Dr) zNIabpX>I(!2+PzDQkN8YB$0cRw29(b7>NeU(?X9(qA%3H|5d1w4IA@2d&CBW^-SA{ zX$-4-QJ#<|qiy|+egjqb@mmzvK-2)P&KyJnMg0a}XS=hbmSaI>T{-O%-&cR=0#PR@ zpRjJE%Cn8|)ZGrX>J_YQ{Y$$1?nQ1B)vsaAA%ew$Hq)MsGBkTby?_7RjV zf3HM1#gOh$qnxM@uwzCGpAGaeBVC!$B~Z^~R$%EV5=#es0i7Vhy)|$?begfxsCsM@ zq#AMOtQpoJh(pHJ%jk1%LuW*t{=_^-Ks&H0r(kQWKmj)X&*g7I5!xO1a6o+uchA6Bt+Vn@De$%HXx zMmOgewtJN92dxNbb$~a=WNON_0V`m&2mz9jtBc*K_+Db|GS*zNbo7o{#FjacKu>9o zij-$nO*s{FcB^2^0pc3W9?0&sE6Zwm0fo?x!6u@2V0Rs2n(gIW^e&K$G{Vo>9_+CR zi7x^~ELooaRXu(|==LeD81%r{j)P`2OTK*eL3K(K_Tzx$p+qBDKs%vz9J(^H>70#qQGyfgB~%@w#- zCG<{}@&OKG3lJ3ui0N7-93rMx@~2QaJcrw|fzxV$1DXR?8y62A{h`gTrlk`0kMinOVW;~yir@@@~ zyhRIRVW$6KVeI7pR~ANB`11)u?Aua_J)_=eu;S9^?Y6+mIFSbKl}c`%-#AU zdOX1&?oRA;PBDr)ewB@h2L>;tR*iKQ1PWOO=)*YJ14nu!=ZGVgSWuU0pMBjv$GiO< zH+5U+Pr6wfWY`ESmZH`ad~T+qJS>p$V;SojH}UCQb(<%jceJ3hjzjW{ZTg-{ zN^p5W7W69VI5yY!T}y&RX3Yg15_?&4&J;{-N7Y{7@Lu-(gn-3yecpJ?g7YFm?=H`x zHmTKuLAV|%l~?5I#CL-SkTThIXtsL~w)ZzP_tvvinY^cGzgYhnmy|uga#q_{xf2soAxh2Js*VouxbET<3=9uR|!D*FazviK9Ep=@fBqpsZ`B zVf*{Sl-5!NfXd9iK@4)wbE4~;C0Xi-WoYtK{Av-%7>>*Dk1`OFol|m*Hid%zo5RJ( zp(_vREfqGFAGZ?TN7~p)LyRsQNk4RW98NEIH1C9`bp}~oxr`BR&SB5$l*lf%RBx@b zG_TB&HzE+ln5As=)t5Iit_~mtot55ynMooU$t@cW9650j(9CL(KC^X&__*D|_sK;P zu98P+?6KLrxWT5O`@aG*pD`muAr~+H1!S-cZ-me5SJ{x*ZdS+|Gc!nUcmG;j#CA2i z{T=?g;#oT4Qu*UjE52JlN^@^&?7Pp;o4glMYg>)&_vHjjGAB`65j*l~S^tnR1w?Q7 z&mEX1UWi;Q4#Y7if0f*PTIYu=<{Rmoa-Hx!L$KCr6&G*!LVt4il@U{*&kISKp)+~$2N4Geo-reJ$_k|A)xpvU!EgK}{f5qQ zR7REzbOaV%FS;+$Z-dcksWHXj3D7MKAwDr%N&AR8t+0xMRffdp{o}vEF$K4(UeX+Acyvz} z)nvryJ%F)Ka@HR?BmVaP_QFA z0ZqanK8#|J*Ybfb8rT)r;ZI+Lfg65lLF2`A6qFbm3arA0OVxLhbT5xF`S<1aX? z;oXxlQn)cVYPfrPyvtYWAf*9odNLz6i&CQ{^kBoh zGnhCNIdE|Dtag1#eOW~G(N?&2*gA+e!63-yS0sJu9poO)wwK?&D>9IGXgrNBKZ>b$ z*aI7sj(x~lHFB}tY7~?D;#D;p{`B4+8RkrRmQRuup^4RhmF1Vfc17@^79UoirSr@B zUPDF;1#^SE#WkzcGmRZu;lw^X@_1|EP+t0{#b1im84Ad{DLXSTaHe<#2-~NK=rBY? z1^t{K!e+-_RA;Vw=3-l1+m8NhlE37FsrA0)T0#q*}Wn%N@ybonnd^Vcixr0wO}8)nwI>g2zEQcZ9(ubn6DlN6KoR^oHOXnEg@-NQHe<{#RF z9TJHnR{nhd#iMKAFgS*+Uq4bN_{^U|F95L<8%OBcR*|P}<=`0%q4CZufkp=kaT|o2 zo32S>j*@ei^N;<5(3D|U5Si}2ZqYYazE(83y=!gxo59Pg`5S%GzQE<<`2`|B33M~m zzE98yxeq_2(1Z@K7~gYUt0g4UX-pX=_?cjUt z+{@fu7D`VaHMB;uM{}rr3P-2FRDL$+%$rySZ2q&Yz~UOkLg7BDw3k{>mFzOU6F_cH zHvMDCb-%su7!;r&o%FAhrf+0MWhJvHG-wE{LJ=pRFo*K=h|*JdXwq`2Ly)ad)j}h) zS+GO>d$JSjoYIvdQK$=22Wo2tdT^(7R>MIq|Kxt_LuMm)tA%gIEns;K$w2Z@zh z96u{yJCpWt@@i}t(Ootw!R~0(@@}>E0{Z~VyEO}%f$RfAU-wRGE}qwkfKHQ_Xj91E z9K;;5EX04|YsB(vr3MlU!1E zq=eSd00A{0XEa(|NdNR%yrA4~j`UMM9VZKO2Nvt+)110^;TUDghFu)VT_1g?c=?~q zBJ4HcX{UHkA4<_`a?5`EJ8I`W#b}|GkbE6IFg@#T^>RR%Rzso`*FZ<8%G*J)7S&vj z+}=`=T!lv=|Ic#yKR}JzPAW0Gf~ZDkR2!ctyG6bR<&DFc2yE;WC1ls!E#UQ{K@&uW@m8=!kT}3a{a7?cEyQOPD;vhF!bIH-JC10 z(9KSqJ*~@yaFivDU;o3laL`3=B{aTtF%huM5RizFFn>?2FH%K8Z+&9Zv>pucKzYCu zUhGX9ox+%z+Nq6-5IZWX9s5Lq$NxG}Srsb3lgu4#P5#&xef)fKmrR4k)=@Cf=d|B! z#Z1;i^1AlFsGFRfy8*ubLUK|iTo4K5;%?=!L;cZW@RC6pn~f2ccw-U z*ck!4ptd37Y$(=uwyP0J1(frArE(A(1dW9MVsLuI3xjz@BlI@Zuc@32@t))A|9r+p zRVTkb>wyC%uU=r>tPPG&y|k zUpgK0$N5*1r(d2OY--rU<4n{dX@nD0{^5*uX|q*-;*aXdQB{moB~#Vqiu4QU0fKWl z`?~x%;U2pTP+ldEo@dsThO7i7gzWmZm4XehoOkAuZ4;clL!kkW#V?Whe&gB&HH(;* zAMP+a2*F2iBJsf?a3ZmY?4a52C}dHYMiD^8*TY(`4Rle)^?4NU&-Pp!904UtMv9xc zjT#0C0c6f*HAXAoL|@Tmnq|3RsC$5&Bu8Ox^3AiTB1z7HvQQL-^)A$>@P+1Z+~}Py zGk6j)-z1lJty_3j34Ym9nN3Z*Ro=|Ji+PV}%i>>k>OW_+p;#X-fN5@lUlnTSa`#O~ zaY%YrKZbk%$y_wP92w2;?&-ky$^#3{CaB^cmxK^;{p1f46!t7F z=GyFFy)-9*$@-hRw`b91Nn1S7OWowwy6xUGyND)VUQ%c>qv=V-)Y4rhAJvRGvQ>{A zYVJKKgtQ))F*v59{1>m&W5tUSJ~BK1eZ(~&hJaKLY3{>eqXp@zd3iqk*bhIJ<}XQw z${Ih<@((52K$D)QfZa&~zxg-@gD492Q+FE|lntnl=;Hi*#Z?Vo`25dq|Wo zPL{6}ohSW>BW%*~lq}j&Fu@0%s_4g>-b9I!m)U6g`7fF&Z&wc}x193&u#*^e8xiYj zHs5$IVa#{A{TSPun-}k60ymqdlx!0{{TAY!S%+t5 zGVdp4tPN3!T+jQyNWH}LXF=#bDpHXJIFCYerfjoWfOs_A>2izWgAx0A8Etngi_j8Wh=eXMF0?~{Sy zw6wX_Tbr>er`jtuILrRFjZog&`0xf5C4IUjuW^zHfcF?&&!9U!SyLlS6(RRDL zibYeF*9W!ZN4AUTEE1=AubLL@@~8N0cRx|xk^SA&M#PJ)0k-(f

-f^R--}5GfRj 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 z zVdcUwg8vAmi_}rh2pY1MzoG697*3u8-pZ05PQopq22ry`1!lGzyxz{13E+rjj*Mxe z_xve5?B9$~u0(mesSdq5FfftXd^`OK)`~yVbz6p)E_jA$1F_sB{+Lit<8#PnjOLPT zWqLTerI1tb>pSb;kkIr$LqfC&9-sG-^2VgL5{H$!bjmiXx91L~05C!8GMYOBP(00k z6;+@=Du{(TiNB|UoY>hw!>3_E9(mLHdGqx|nZQ5^aBj>;J?a+|0V_6nv&)}96r(Xn zGD&cV`KZ8&5&OeXV&a)S4FYLCwzv{L zfTb_n{@uJWOHY$u(9QxL$l3+gonq@$%tfo%>6;ic;YY^>CHOQJ*{;BE)LS0U3RD)o zoAGhx%hmCX3oni#N+PxDe8&7&O!ZzZAxJp;T$ytC7im!Pp-d@?_|+fDfSKz=A;J(` zJUG=%r5!(Eqf7*YpTXbuV`i1R@B}Xa?9&@OqP&=0Vgk?mzk#3)&Pb0gYA9x6xtIF8 zRdBq!7%0>Hr_fY!aJ+maef|PnsL5hbFeSi1*@kS}VvuPOWNjcBN+6HdaQ#uDrQ#%D=bm-Mq`M7fBR$h>^oTr+PkP?y$=Dka5K!{k zI82r5(;?1C&$+Q#Cl=&FTa?G`!!Z?mK{Pm)L$AfmPg=y8YfhQ=M}7Yp``HTFp%*y{ zd&)g*iLDMD(JTfyHyXSF9gWj`WZQ1o^%ODc zsnW2Q%mQFXvEVgJu@{du;1gqtpPvk~hd=f!ksQkNgbg3R7hG75ZKdm&QGCg5Wlh9P zM9xi8yaf!{bMMP`3IXx=qW}Ryhgg|@BS7x$00B~5bC62B0&<*wgt|9vKc4W*+qZk7 z4I?mP=(L0YO@{P!Hkgfsf#XSd%4U}eP8oV06{M~t%1p#q$=noeUes?8%&k{mXn?oP zmu^paf!ioNc#Dd+gSI#f6oTZ~{skj=9ed~8xn3qaftm0=JV@LlGQim!GT({lZCUVXEzs^_b32UZhfiDDX{ej01W+R3;fQ(@HBFiyJ+}3nVWECYpZ}KNMvNj=9zS7$UOaC>(G1SD^6qzr-Q9 z!nl}LDUcg-y#0A5$2lVL@NNMHWV<4b&e=e`sB?#n#$}hBeUv&b~`}#B;cmLSvRPr=TR6 zrQOXlhefJczFl7XlMud! zriQtflj`4Ix>`IRvAaa-*`d^4Gt-g0d2toABDKil)uJGL`GZh>9UtE(l}!yFyc;T= zH%+dK-M>#3ZhG2`xvYL4&3t_917dq=89bMGslVD#scwIe>=TG76Sof7`P^vH*Tyc9 z3=n`gQa#6%rhe%c7?<@`g-m77M7yWaq)EhN#9)EHCluckv3}29ZH2U%&j*NLK#6V3 zDX9Xtt@(WxrpeU@tok0_d~`y4(75vI8!)R*bB`Wy5%f7GClBHethPuwT`}m!{PnIK z{@P+8Hbfz0v|`vPt<#UpEN#VQ)$2F(6QPR#fQ^C)iqPrbKtBraq0}3%C1}Z_&~U|s z@cm)vaK^KfWTxFe$Grth*j(9tvBZ-R66w^H`ES@Qvm(OKXK|oVWdr$`N@;7WJ_lXV z`dpLx>5z-%rF6iGSW}XkU+e33v_7K$FWOlP$2(rpUHFo3yXKCAu)sW&ZGFbe z%JsqP@7`iG*!uIUsapTjma}J{?V9zo;8a`k$e$Im7Nsf{Z*Mu#sOe4WWP%Cpr|pZU z@Q9oF3qob4f&EipW~A4Zx_n)XNAAMHN$NnNH1Z*Tp~+o6plVVbO)lS7)6`1#GNc5{ z;{<+3VjW4SkdU{^f2)e2IbxAKV%-R&O}fk{bs2~$4mm4{0{=BbC1Rczl|nP9E`CK+osb?ZF;8ZYThVT-@@VGY$h?LK;H{W>Y2Sv ztU167-jjuKnS-O4ADD}>v!;^F-jCg1*x%uzoM*m(b>EXR-^g&=OU0SH8X%Z`n{Y5` zxrH+c#(D)DV8oV1ozf|2AyN+{QK>SOr96fOmI!yvM9VVZgd8QdLatERzn5i@5md+Vt2-C+nb?9)L+;Driw ze+>Tabl)`;Cxe(&!~!>IoMwv#Bb1~6)%*Bk?C7g7Jc)P%-EP*lF2WWB)zx)G(Mvzh zl&eddcOd%w%R*=nT&VRxOoL`G+xJC}pm%T!u&>%l3YFS@Wl1`khU(2KdIvK@ags^+ zh90J}`!5JdKzHgAj{_d4zVDs~YC4WD<0=YT>YQ2*yyMjbv8Lsj3gEQkwV@H$w>|;RK9a)mH{QFi}-75;slzH}&fu9?wl!q!$zq zUkT)<|62;^F_a#+h@E)T<&A_sl8v|)hQ}JJ%qNCsb8@8k>C3keyH1^su6d6PCwkSx zKyGV*#??%~1o*{aID zvWoN&rX}KDt~1Fy%MZ2lSdd^7PNVuKlioJ9&#Bs!t5sj|YSV6gPPo8&)>xJk6Ri&G zrV|5s-)PS8YgAO*S6KO|x9XdJi*zXU0<|rQY7Kd#lHh#pk)HM=Hr)55^j(>w>cZQS z6%rZm(mUSD&9iB=M|Ok^d9HItQR2rM?qkog`N6&nd78|q@2Z`wN_wCPM_WxLO2;8s z>J5fQ1eyt$?)X&@sox{~2hWZK^a(4r4E_L4wijU9t!0%bT%Y7OO=>BNuS5GWX?h{; z{Cm&Ne~SebSz4t-mKc#~4uR@(_mye3-jSEdPd)Yvqo?7>nP*_h5&qWbB}&`TR`6sw zPwi3`6Ru3EZVR|R-(R<6M#*~Q?t9g;|Gu*I3lJR(>=Df(r0*FHtXl+aTip6a^(owe zMP6$>pf>fmpy)7VnAHMphU?CYY*7h_Udl!0A%d%w7|@^$S2r4zK%)e{{kukH}fhmSxJvWlc zbPEWkt3UZSr3_!cWdU$r5)dWfoI5bpLLqKyz%T&`UJRgu(lOJ@L|Fmc+ef$~eVsX89R zUK*zObX0h8r?QIr+s)Iyi()7S{{-dfVqabrdEv?8`yz7k{Li(q7>Fytxv2Au5v+?2^mx@KC#;?n=;3;N>XFWauJ> z#7&t_y8EJr^d=AbIynquHL^v33aR@y@;&A6vgFLED+4_7E`Z3D^QRek-IQl3AF00y3h$ZnB1F^p5bOGjmIIWd<-U_`^Ya&3U+N;`v zsC~wAvLUaE&5_31PjD)^y8iUIA7?42kVaW=Wm!*(m_~>Ix&c{#ATs>N$bmx%)OfVl zgjOSzU{j(69{geF|1a_fS5N=HA%CvZ?(!z;bky;7qOm+)y;t~R1$!W|rEXp?b!QwOj)TM5Dug7^n6!&|PHmAEV}QVqTg%c_0MV;3jDp0yA;OmE>39jV z_$z)O%gbmu3xe?&nh?eK3|wV&x-be1&tPxk4Qh#Can2K5SR?*^nS2Lx0o=v46llvd_74T_7t9`;^9+v5myo z5C~b5Q+wckNvFQaIFAc>iDxQ5uV`hqf>2I2g*aqekaqjNW^)2Z40r*${C@;dip>h# zQP-Gjn5;xRve{r8#c8=$0Lj0>I)wV||4FF$?f-$P_!0r7b`*1`?(|6~(Vt!9yXT2K zy>Vy7XHTYSu6gi*YxYE*m~I^^%E`!&HVaI~c}ZhE62|&IYU@2-pdA=GmsffD&jKLc zjtNO~0N~9q?#1qjxQi3Ok%rh(<$Cc5U;Las@D@H1`H-}6k`RlJ37y?}W@ zvfUOV(@xAqaci{lI!XiP6dsWSI)OpMVcwmAB2ftfQh^(PgKKSnuWF0EXKyszof(Bj zx2#?&kq8W3BrA*&Udj2}wP~ju1rICvO9Q;mUN6vxswt@PG>ZWEYJf@lv$pfq_a6mv zXCVh|4`Cdwi8J9hhMdz^p&wx+JOYs2vao{P(VYVw+W+Ew2A(`Q zSoutfKxyk@Jq@3cQROqvgX(Y{tivB@-86~5%FLs|OCX3qo=EC*dsNy4=HxO+ zrkM1TcHywbvkO!n*~~Da>WK?xG2CUqA=QDU;eQK)Jm+WOoV}$lXOtGOYL5e!%{bss z2Pa=#jK@jAF7mYLTBse7L%LHPQ=bRV3*~gMjeDjlWw@F}P-E@)Lyo;XLq4au=~9 zvN-CDB#OhIuS_go6j>z_FXVQuOd4)hv6SWb{_Eh^-4OnW6DC=o2gPVpe!rEc7{n4( zV?K*y^BGEb>`TIg0*T&5qOJ~@WsT;+LXa0T8aXE73*7^vJK@t!E~BuqK|Vjk-{GnH zAMc*e^D(tIPakOpik$lUEZ_Lk!xDfQw$dAM!J~%^;;Mqn7ZCi+&Es<&*{?!A$s^5+ z`W&=0j69>Pt?m3VpoM`9*F=$o($4ZQ<-;ZDkbsF?zxp`3roNS74W+F{J%b_&U_R@nlm&T-RZd$*1 zyh(q}tY@f}fnn}Ce96#`s?G{muRe*dSawA7!XuOhMK;XX%)2``r}=OEhtC&+|Ckp9@{qo)0_(MRRQe{8 z*V+kk>*x+~uU1D_=3@k9hD@f&M96Q=A|hN4tcCWOn4n z%+_*xnWNtNp?ANDvE;XO(AGfnGynf4e-0gFJ5eHD5lY~ft14bO6t6e@&P_=ebW_<& zqHkYEA5H{l3|dzWOlF@cJ}CG&VTFivlIF7L{V%zx9l2VaufxVkJT{u!*e!dbR|nyz z85uk~?g8jL_K|cX*hqH1g~|nVe0W{0-sB%438rv|q7EqUD7NMx3Vm2g`9x)vu=njT zxV(2@nXZ6H;R5L^mAaqFn2SkG{Bjyqx5&;#qKg!_^jt7^oS0vuoa%8^BfiEs36SZj ztUUZ0csi_m$eQSPyeepn-c4OgrvL6Sx)EH-$hHAOjl?9`99iyQ_sVbHA_QLlA4wmdb)*T^hUMlZJdp+dZSeNIcM+GQ0KuVL&}M zl`Xm$yhNA#@ON=)&CUzoS(9zzzk`526Y7(1auK{zpG9B!e&hGrgS>`5-&L6xzp8*PuSOJ1|c_?GvBH zVfui5c#5!v7hk0DM{4b0?^6LC`Wmgi@Doo)h2ZB%rU!h?3GK(RG}0I!^1ED~qP+_D z(~nA#wZi6%ASAR$>u&u4=A(DUYY-dxVby+ajTO-I1;2Nck)tqnls8gb9(WxsVjo53 zj~D%u7fz6y=aC^LJnZG?EwD>mVC)}a;03H}hfkSo7eOfeJm!)*zXZe{1f)By@(!sT z53pgG3Dtp(K(ozI%!^QrEKb=MG+%5tQe=Q6{s>?w?F`k0WsLr^MGe0WO<>jOJ;m{2 z!XQc<1O!;XSJZskPTzPlH>+MB1I;shs0@?7tsd1TUWInXIH0tj(^(ZOYe-1)!GMQx_?MFaK3f4cp*Dx%YI z{v!bC#euUAN)4kZ?sro78now6Ax{KyLqRC5T?zjI$N_}}Af`2rS5bWy#x4{i~poZWrsI(-h^Of<}tou{5RUBg_v{6=}oK=V@RCVbVY&*s$+Fxr@ggoCCZA z0v?)Wb#d>8{dy}?V*J{IG?(P(kVI6i8;Af&RF29rKD^njdSNQ?_41!PQXeyo!EdJk zW;?bNSS;brFN6QZBot%yAcEB&x(QYv$XIt1cmEv#L{>W|baG2%mKqD_!_PKz5R|9C zV-%20$@9Ql6t;f#SL=z)OC6v_TF1;e43v6RYx8&X;?(U%$gbAOe)mbkY=F)eSyp6Kc3*Wj46jX21xf zfUn~;VjL&W^9;mOL~aD8eu8jVsy##?R8+J6xE2*65Q2HxkoxeXCx}3I`eiRuRsTDF zg3nJd& zdAc3<(8n#fE-sKE+bjx$xm#q>QOFv~Cd+wF@8*lI2M>2^E`6~=Xaf0P*Xu16eJZuY zVFA~yjy*WfnMPPABF@YMpaUcR&t{eV_62w`s)j`7xI*&IPC#sH9_JQp4myw?b%}U$ za|{S2HUIJq3dbv;D>Bog&syU)dAUSYo!&*}s%`mbn2twasRB!r1<*f*N5=@%)pg+I zARcM`kg@Ss$qKe~Z~Z9^uA}4VGkSjFCP2X@51)Uz4_P7YA&>zNOCz~9V5H)5q6LbS z9QKJ07J`?bP}E2DiFb>>7kO`?HR6mx-|U>cp^^bqQaWFrnG0}yOi|!{EKJD@R%C{d zxu^$(8m)ZuhqXa)k^W~e2=*UfkR%)|K)oDsa}pC@e^sj9Db%|ufm-X&yL0I?+p zE&?DYg$+N3ZzVLJDH^;95&RZ_*e5M>?GLH54cVJIwvo7;3?Cmu`_Zlv>KD8{ioz#q z1B-lXX}|$CIU~1V=$-_+`d6pWDGaRphWkX6y9;oFOd5?je+$Ky9}+5mJE|SU<^=(Z zU_$2XnuY6t(`vpU?u7_Hq-GWO>*Cj)-t+71wa>Jc`eldHtjpWuhn1^v|^l=;TD5WIL1_BQ}fB7U=J{6Ep2jbwLm9J zSP&~(Tq&gTjljp5G9=~!(eq^APqLa87*tvkn`)(;cc2315m06g*O?OP_l}T3l0pK+ zuLi|;V$am@g@9TF*$Gx15Mv}G5@XyPmxuW*0ycf5fj3~tp+7tKSrs_Xtxo(@3`$tG zA*lJxn3+V_25tsoFt~|){jrt4I**}=kpWH@`No(-pOiYbBd{067HggI5IQjxoZ+wT zU(JC{7ze(1flcQ*af5r*2a507E;A-kK~UQ(IZvZ1%Tnla^9h7KhMzn;N_3#Y4k=u8 z=puXbDo5S=evjLNhV~m_XB@<*oh8MwAoXppXIOr)!Qq?z8ww8UDS zzEWi!YyCD0+YyNCX+LI3?&yNZoa{h<8Q6Aotx;Th*21IvM?WhDc;>jiY673vJ0LzA z4~n%T2mfgFtQt^@Tx-0cn6V0&hGiWB(rDuWeu~#vnhkHAF}(4v{Dg(&5{G!BZ#!ew^vJ_>)WZPTw(4h=&Z{5Xefo2c&#CM6rFs+Tn+NcN#u!VHW* zy!a!?ks=IOLMY#IYXakGwR}{etkn*zuWu*#b*D8Du*_{hILp?r&GntB3&MR#>(|it zB&a!+2fu#7VMQgvWj&~Pz`#s^pJQ0}z@M;~5+yvdxb(>*WUv?%DFVgEJ|di0@f91K z6#Ns#2io?~ye&^!(Lc}khK7eSr1;;m-CoIElHUxbR1NT7%-?)ZDXqV-t~zI07#mxN zvjNay9xhw598{CR&xR@2Dk48Q4Vta}nkmFc(>uh85~*Xx!R?L!p;#uq)VC{m5Y7sRbFg z6Ej=mr`(65LX^4Rc4I^^ZCz(f2)>weizCA zr)6L*THcH>ZyDK#Sc~LuVpVm3wa6Z#r>nOHT<%~9;w3oUL9YTkzf!DU>}NYg3pc5H zw=NdP^Tz3R?@HTd-O1%KDS*CWL)#vghN;GwBMk4%RHB~wQAUQ_qfS>kW(ORMh211w-BJblrM>t1kh#)?J%HsY& zF)7m3S!LnmOwm3y(>OOwlcKP20}?k4wV7i<_^u{I?XUVoW`hGp7>~&$NOyBT`I7P6 z3yL(*U}z}Hak~&QI&_fKp^U<5^szbc#$8l0n#zzYuo9Mpa1dLGtFvCK`$ad4Rgdbd zRBpyE1xw{a!K?)6Fi`(c6bcO3rs1QE_G`L{a5)B@%r0oN&mT>0a2aNwHzLyWC8I$b z%7LEqw+XxEJ{gxq~Ba~b=2U~Xzq^k~?Y{U`c{ z9h4EI+?8@Y<5aXi<9zP$ob*=Z(l<%0`^k^*Zw>|^#{_Nec(-v}Gcc6qqM0R@XCe5O zCGuSwtUJBFgD0>XZnHNZD=55|^e0(2@)ejq>TZ}mr?FKAdLkGYk|>{1!*n3>tEyb< z`yfd7&ecEh+IeK(YlyYTZp4LINxg%egM=BW$$@A70oFOnG}9-T&GOqlbsG&)axPfh0BW%~Ez?Wy#HKE#>@jcD zZ$l?uBU@n)OMlS?`&c}uFg%38gijM$@`WXB&hwU)-}z%BDz#*H0%mTr2m;nY?hBa| zfwr6HZ!Ov#F)u892+ zly7SAzN+DYeCFutsV8aP1peS4JM{G_F1pGqm-<9R=s->>e}00cyXN{mC?mS3;T6Ge z<0_wPg}bzeyy_OOJ+^!uWh1(V9YJ7`G=i`@9`GXC&&1Z?s4Gm$CM&|lPAj-+g*R!# zCW%iRS_IIfOZ0V!g2m%Tu=XD8njRa%3 zuDt%bx4-l!pIG)-T%5v}EkX9%=Oov|2|i(2DUs|pExwKJF!=YYr*J?hhc?u#HU3T_tvUhsCqZzPP?gI20*oQ zq+Vjz9``aXjJ(}t(|^Zd`BsN%ZFt((FMg?$HA6+ijCX=yo>I(j8m4Ky@sk4_&Cri% z<2jT^-6<7J`YcJJK24ksqk^ToXjFmXMj;-ScS?l()q!DCFurz36WlQUP% zBw6tl{*6e@Pix`|x7H7xZ9%9fxgRmRNUKspl}~CSjn($j3GC(Uk_S&}h?g`Kw22n^ z0;CfW(=f=CqL48ps6D5LMa)e(FtDm)@qeCo)5MUnRoTR4M9uwZvf(E(dL0>4{_2F# zu@LT)e6KQo!e1y;EEHN+KDRPbo}a3d3`AfA&S{+5JogLy4&|@#Y{sz>4MKhE=8oeU zLrS%xXoJW)xJR~no)yfU#uMJ#{AodEy zn`~hJEif28j}5ryvEdkWm>UhDbKdbtYo{ys3|}eDV&Mv#de8b%E4Z{RDxADzsv3q= z*}^p-Phn^U_DqwJ-zM25?vTv{%H+t^%V1qy5emtqcsST~cirN6II#5wZovqO4vQ0G z>2fuuEFL;Qh0SV`$(1n+eJ{AC6N67v_(&TXvnNPlUL z!~_1)939jh)O&khx4W5sm_f1WZbz9bPLff&Q`v*Em3qr>@Kk~%&ujoo;^(vFZC#_e zq5Q>rz9|_A=E-WYY}o+o0;VCQX}TIpHJMQg{|8CTV*6BnEr};V8@&>~7o&XQn*lvp z=7!6rEHsx_jy;7}sz^%3LpS=~v8)2#>DcxaXoRi9`L_A&3W+9dI-!AF7}r-9zYqD4T-ZQG1U_?e z%a9Tx&&ba@wMLSYYO?mgFF)IkIXqLghZ97&T4Qt=oht?_8YxoRowIBkMYfz4DsPsp z_4eb)xkqcWhWTutOhac$3APGgtfy1ps4WtN=`F*Ha}xdK0^k{9bKMTSlsbf_d+A$* zmyFLx_u02f6>6Qc6Z=%tXATN0#zfzr$ksI*~L~p)MBLNq(5jF5gG0>p^WdUe3E? zCavX~r?QR7%Zvcc28yD-I3we{JYCWVX@wPOS$45@(gM+*(nigq!ieiyHp-ga1EFGp$W)-_7Taa?d>U>j5 zQHpw(_h{jVF9DQpz(?f3{zJGG`YwgO+VD`?uEpda}OM4oh64-B$%46qT$JgcEkyh^_KMfSt` zKdioHd%+01Lx9Nr#`ii1#K4t(@#mox4|6^!RS*R3q|TI#1g@7%7(dyZ zGaw&niii){ZrhxZ9~}Hd);}cHcMhKXz{_rE>|*C_du;>7exOPBOoC+HHq}5=WC>W4 z9lXP_CSRi@FQ7*b#Bg+oS@Xve$gu;{68$EnX>8(Kzng4o*qF&L5fd?Y zO%X{v!L{fokSg2DY5HrD`%>BQ(7sd$g;BIT_oaZgu{jHr9!G4+>zv`P+LDh?H!>hQYxr%; zG|6;T*zt=(4b}Ymqi}OgJUzvP3$`X_@fgEiJmy*@QzrxjT2ap~nE|}uY^#6jlRbv? z$?BrXYE6*hpVrrO`N~ZXS1? z&jW)TO4u(%42^|=e|q{NRi)3m4~Hp5ZXM}^wu9G#06GFEX)AN0^(eNwf~)wD6HXmnfHVzuw>AX$ zl(3vMniaurn_lQ!zD@zz5B*r7@Wh8{rZiC@)5ln>;9#6y;r8e^IW>1k4V};?w_&v_ zq!+|#b!XxFL8(NoS@LEC#YP6V8QPi_8{wCns=ac8nUc4OLvIs$PAZb>A$w~+Q496Q zo_rY%_cQeGD9$eZnqJpMY|dp&oz*j~!C@U0qr(8B$`BCzVjjA}i0-ZU(-$Ch!1LmZPrHflvO9>u z7eCj}Vr7QGKl=C*&o6%RDqim9IJX%#1QmTC$ z@_v!%S4?(~-Mfk%Vwlt~Fg)o4W#&}8j_Yy+cJBobnJDwfBn^sBB-Qv%y4s`wLNx%# zD2nxZpYe?{oR)|afAlsR((X%Hb4k6oI@Qb{;CRo|6y9Uw+BG}a!DF9meK@Pyiazm5 zb9u{yeul(cK0T5I>Wz8ZPh;UZ>QayUsH}SL@H*wB^wloI&ROJ#WiPw+P3O%CI zHa;C@d5iNQZ`Xb4pPjFBbmxY~x^5$!L-$AaYb21ma&NU1#rTwZY44{X9${Rs*r3P_ zQwiNwhFb1#$fM-h5)x}}8K3k%DxxKtg5(jO-BVxq7cJRXB1-GcJPLXjVG3q5PPt#$34#&9g1bd>kR z>F7M!8RJf)`&r|uvv~SZ8I;h(q!RoVxMd`LrGl#I!jA!5g%&Na4xLqPEwU>Vg8wed zZEHaQO+UlVCY4!Nzw%po#ibGT$hVa*1cKo;@>_bExx5Zfy4e>E$(h^jyaLrZv9)yh zvnmbtT=!=d;w`rV$WJqik!iLI@I2N-Uqct=t6t__K&G*cbR8nQ9R~0z-$zo|`_?_U z#6LoUAXQ(8q-)SjE>x#Uh^?ef!aHCMJQ2HjbW(g4&#K&q64_~dPGd#~L?bslSK=Hj zk8-IkS~pQ`bHj{WVezEfRE2jePpn40!xz_!JT5Ih7K2(^V&e5o^<4E$?P$)l`}dWH zPIf^olW5ZZ)GO-BXem$o6Cd{uI~0Wd+pE2hr{9R)B#aXMl=?a92k-u@_e!?C*7yuz04(Ic4zYj z1iHv?aWr`!ObGL&XXug%>~$4-*!E35^6ErvraP5!NYN%iwpDLjiOKBm+1#G^q;q!{ z+b)l?7GeTj1n=5SR6+5brI!lASGC6rS6NZ#``dps3JHCsK|J6*@AHy35Qs1=u-{sR;Y>Zb&7!ZK_4(P@?Mx#cFy;ZGl=uvZBj>!X0M z0()CJ6Bq@3WN2KDu1yK7t1DD1uHhEp^_Kz_5l*|$SY*FMIOJAuYcuEI7&e;FZ55c1 z?do_Qb?l$Ak|5Ag!Ff#$C5TJNexKlX?&u^t|C-2oEKbuW(7H}$>SkE$$%z-pW**dI z9digHXnPiIWqmWlWq2@hXh`2ZnN-cz4rNWT%%AW54fk8k4c{1eO?CMm)oWh&J$nly zh-gG^M8JkpWjXQKbqCeqOxTIJcPLn=!KK8j|JLlrL4B=y9scgJ>MExFtI6cg8ZpMF zBy7j-Bl-Q2Dsa5M+<9kXa!#{dGF2N}PYQyFE*W_6z9DXZ{R+;f@<#$i)MpL z)qSxNg_|@8q{k<uMbp|e zp}wPdn`X7S*LQiRR7izQ>lI5N3t#a$e7m`zOnJ4FZ6td;r!Zdwyp=qxzejXNdx zcAMdxpqT2P@&-oZ2;n@f_Pe3GMO|hd&%?}VK|NtZ)PaiRz2a5AT5dRGM8PD7sb1RE zR!DqY@GZ+JzpX+2dN_aIW{O`;+d)b}%jXLCyk(F>iWukwOeQK%i6e(L1G!3I1yestimq? zp{z9|kr|?@RllI5iT63hk*v$)iz)&z4}uBmDJhI#NdO^QVG@-?HsGpek?1@0X z_?j{TmHlew zQhtB#eVoUV?-w*epU6T7sK5r*7OTD|B%`YMnP!%sAd3XNAAjvSMJZ9YP?EqMR)3!v z-OU$DhDR$Pe74Eu1z;O~h?&MQ(c%!E1)ACs=JQqH0D$)Fgt|k$~ z9wt&Qy_Lj3zK5>b;{9#|u{vix6FQKC{#CN{=ymCj0^a?aA11X#U3U1*QnkWYKjP>U znUx5xsr-}P`jDhjX*w%Ukm=ze!AiD~2Z?*sg9gi%LuT3V0X7D{A6_6<{zpB$jxvvX z?@~PIvGw^SQcNbGYhMB8YI!b_CeLI}>p(&+5KfitVHSED0U_-b>Cu>EH-hAgh4H(_ z9N|5T&p^8LU1xkKUkxH%s&25)E#e^Ph(D>$uE=&%S@ddrl9j&vNh~xv0xT2`g4}YQ^3ZJ}V-Hr+w3EK@e@NSS@407CvvqS(OEsg=@#j z+(1`GTh!b{!W=87w6m2f|0~;SmWPdJ&77tDB_ZCh!(F%M?bf3bDYx&V^$>q)EIUO# zqBm_Ic9reY&GLz?7{07p-k$&DRkV$LP3}=3WTECVIrF|0y8mtW1smEMr9oZ5_yQR06q5{HM*E$Qv8K1IBChbMlGT<14k5 zgodrVFU(DjZtk9HOciUaT90bnUPV4xTzhuK-R}EJ`BNaiEM7C79O1Ml^-`fGA!B;! zH?yo>m`UEQmxOP=9=4$ZAJc)lz1UJd@hSIj3`>&y_${&~ro2PA=2sVAw#7#zH(nEE z7GzJ!$Vn@65cZ2Xmn&aq^}GhT7x)+`mWT}>aH8ZrpS^K)vLW~s*N>@2gTmlxJiMi*9xCp#WK@ z&IdQ-4J>a#lskA%HSb8tNna-aKBy{t3ql%mc~#7vay($HKh)mP8go`i<7zE?oYYuPKcl> zty^cJOhH$fA5GBJ+4Vre&0N5AZk^zVsdihHoxk0Gv*Kxz^K5pbvbKE)`w`JAs%_Mkf z(OHc8G(5U!bRl(Ka98ZqEWwlfY7bjKIq9Z%@;42kp z;`2*qIK`*=!z=9Z!jo{TJTjp4g}$AQM8QcT$6KFTOIg1fS@C&Qw~G1=X1%y+)$K-j zfgX((?ST;`vTe9SToWzYcxOwa`-x?UJaj`Blx2J>OyCKZCs= z;o@EhD@lv1$Fxb>OHE{0WZ2apzb6IKbL3t_CpjfHy$fAXz`I?jU2C89>ya?z5yroX zRXeUEZx!f6?v4!m)o2Nfz&b_Cz8T^(nMMX%$l1U1y-b@JxV20hQVyIL&2bgp-`ObK zkG7U*EcjW6ETPPQx($XYQ~bv}5E3e*(Y6jG^2j|_y?-Rgqy1mE7MV0$J-6kc88GAY zVXwR$W5$U5BypVh_wSAj3ytQvjH=gtcNF7e{KCCgxzbGJB*_nJ>_nxl@6`I@)lQ%v z2nDwcU0Sd;XwgoC{LEVi0QeH;wMw$s?S1x1Z_T8`h%YYL z`zpjg#iTExj`hI%q9JZ6A4*zz?JPv(G;4Q9@0Z6pSm!yTvIP!*<~*+Z^SkJFZj$HK z1;QoO$$+DibOB`?Garx!)8SjO21BAj4(dw~Rl*?TkM%3zv%(hx>363EW)K#J?K@bn zKDP@oa0tFk1{$ajO`B4zy}CEEQ`-~M+C)A`qJA1xrX~k(7H>N9tqUn92o-(2$8*S4 zh%VzYZjH|&evdccS5JfEMbL`FW|f~^c;)dPS1%VzL1nhGoiks;`2Jp`DnzEA!V$^Nv0`(SA$E?H<;A7fEW``8M zb^%V>^yb$RqA1^+4Z+2&{a?o)nvH>_=_z%84sbN@R>XK&p98ZE-y85eZAGW*AyL_d z9$!+L`CQC#*~Seq@L7vI#Hb8-@pHUDgqtc4q$l(@{Zk6%DCj7;zS@(#{(q#sbySsY zzi3S^x+J9$1(aHJw+Kp0gLF$vNSA~(iiC8hG$PWCbV^HicgMMwPrT2w_de%4XMBS( z_``uNxofTa{>^JnxpBrab4!^>7~-=Hx&h_d+F~W2Q!mQs7~f5}$Xx`Nv&&}Nh9~2X zXq34B+JeM?!4=owRENjHrmkLKm_t&=`OOVE19EP)lOyQ$52d~mx1WV{9+$ww9__h^ zpZcQ#xNxvNuE4Jmzf-*wfw*_P4_=7f9UKDd5&mNW?vg$ghVAHA-Dp%27t^BTjC~J2 zEHQN=NAND9zP(hq$t=@_Lv0%IGPWu)nGD>JOhwsqH*Z)3AJY;HC@2d){jQ*gxdU-g;^M+3H0Ti|h| z`^-{-U(Q}M0V1y8CB(;W_YtdWun}hCQSzQ~Dmbiv@gT7cxb{1+S85|kYfWNVq3^kk z*S<;bOPoEXi(7;FVlV>JXheX-o8B6 zo;{4+2FI2;!<6)_j?+(7#0xKQBFOk9!iQxA!A@^sD6YVOGWV+2IVz_!)oE!EA667l zxV7~C9E`}8iQ`|8(-dmB=(EK}weD^?EE;h2A~-25@E$s|_1w~)1htm#YmO;iHS=ii zeVY_u)~R&{t|k~pcN!#eO;dY3EThA42_aQcP5Aoa5WFWie{1`UZu&S_zR-kz-~=zy z<>@K#!$4X0VTj~W%teo5G<5JDqV+f<+hpfprrz};@%O<%IV=Ams}N}C(N!~!R}>8y zFZjTU5<);$f+fPH==1n;P};6FBF0%8n{PP^ZBuE$ZLDG@f=E_{LX_h{^JoWL3zAQ= zG9#0iM$pt_ak6LrzR(KLMBG25cMVFB2V6Qh$Zs{a z!!n=8b$>2~U1^o^5zCJ1&e1{%x%~#i#1tdOFdg`h(|U1I%rRQvk#=ZE+6Cv#Zp58z zE_Y#D(`UXTH%4+};R?|p9@*Jo?sDu~je3^0lwE5Fy2w0Gfz{W_uQoo-rFYoME~m%D z#6c|Eis60~k}U<>R7X}rxZa?qw4E|EFUI0?`iM@YVV;g#mr3LU@tcr2&U-T&b5!Ry zAmLRvkKC*<5w4ZWi|${7|8RzeT7jrf)rX+$hdL65R<8{#!y#ZDNiVp71`2Oa7aOfq@_z(H4SZrf$?;@P_5{VIMjm%tOel4A6-947LDh!95d$m z<*3XJp3VLldi%{vX;;JaMm;I#ZS_0Nu$Vh+8q1oZ^VIGo>mB1Hvzl8wJrY2#nr+a2ud;G-m z<2Ndz9g9`%a}JMg3#)>G`s)?e0YFYY5YgJ;5X=Bf0n#XZ?;@PD(_8QvvxUJNUI*ZP zHeM}tNVl*}<#0<>fb_HgKNu&mM8=VS#@~AN!eC^>E_kcay1UU)k}6?7zzb~vULm0I zN6l|?iu||PsqO%Lnu53(cLxwr8YT4IJkWnttW(Q9{kpJ-9P708gwN)tF6h8&!!-J& z8`>?NU~>Ha?*wff%Nq*~n{&NGpD)J@9C8 zdl6wd5Qb54@ck%D#zI+=%teE@7vA^9@fXIblYpDYumP(&LqABoB7^S?*z??T2eG$? zaUl$Sl}02c@f^RY;?0rH7&Hx^z6>0uZsajy_`QAi5rV92iMl8|$zFyzyf^0p{&hIb zpn;m&_)LZY$@2`@o))$`Q`r5rsuYl-+>R)d=FQoW{6AEZOAQiDh0&7JapW)5VM369 zDq_HlVZX2$hD*vXb{5@~qIwL@T5}1D@NpzImgX<6s3kcu_PxMuORcvi9#q#oo z9l>+>g1tTFoeV>3CxKIP2}$WvT8M1{_!39Zk)Oy-`&Jr!+9uDtu72Ps1WLI(jBf`7 z_>`~#-WUnLsozPvXE+^MEq7o@e1&So6z+4u3xF#2Ys(3~Fb~7camm^2HIY)pN%b)Ul& z^I^clC{TR^V^7;>LHebo)$f$d*fcpmFzD7)wZauR=B=e)IxmIuVDk!p3Pj-};3Kn% zbGNs0-q`e$|3&R#&ZfhYT-?&En0W)2=Ib|zQG~(P&@c}PByYy@Ck?0J1alK#5>fuK zaYgl|_rhtxNy5z<#vf2JBOrmI2@I$#xY8E(I0ixKX;cfiBg8)QIIw}}WAfMK@n&%v z;$$lZB|1L`ax@(y)u@F9;VNIMpg0pU^L39UHdkL88Ng78a9u2~#f53O1otVoGWN%dnF4z+3cWgKuX004F`uJ@fv|i)a z{@tS|PAbQFA3%we3s!t%f)iyy`W%ds^X;{IU^1DPA3q?kxz8^|gPJFqnihW&KiDug z<|kOtKt+$2h9(+3Fj)I3FOD8v{7WBB)gs=~V**M;TPB&DjEpzzHnj%hh7=n^`z8y9 zwViCUI|Y1^yxdr79&ATFL!u*)r1O+K2s+R`P0cDQgGXd+4o}Vf7dRCxufBx(j1o)e zcL8oT%mm({>-yp-IXHUO-oy-*29s1^RQV{f**YRP4~h2yX}RVLL#j6_5#@|W zxk#UIfuNDW=x6>*ZXNR0`cA>8zm@~gm22fR^h6pHNL^S0n>By$WRCOd0U z?Ljl?l5WoQJf>66-}4EW@hJdS0{lI4r`ed6CJ;U=j?&I{|OvS4ztIra6 zmSUA2%p#pA+@4=nA8kEdwFiKOa}W|wf*!Ohi3J*V@K?Uk)#Nz}iRPiiBw2Gj}SQa>DY~OfMAqSH;cpziWT*F*HCHbz`^#$$m zzEXc<(M|5j&As7&n4YTVzi-*M8BWk2qP7fD;wjQ!5BoRWb1DoRV2L1dGau3vjb~yO zxk|Yz-df8Q2{Uu%0ic%Pm(sojDxwv=pV;$i@(uq75(;K|>NxI*wJn(Ymb|uMi^~3$ z1ET?#RD4?E2)+e5d0%~{vH^QH=+FHlN@CFgm4fE_U2!K8qAKlQC?QW>(7*buY$(up zAx^f)ZnIMi-Ft=%2DS2V4A-6gN72FZo%^gU3L}=pE&y1YEXCVvFdxc5yC!!6RkyW` z_S)vVX*OAxX;voI)^GWktr{hx?dte$S*LV6;12ma6NpsS!`(Fi33Bb3njJn_r2o_* zelb+&da_}LUg&j}JtE~GzbGJ|+UpPq8R5^aqpB~fY|%&BWAKYc#Y*E&;B(}CHYxu@ zJ0TrCX1rMzdOiLdc%rG_Kxaajv>%~O!duT#B`Ln6lnh?a6XGPt2a}L5v6H9MwyDHe&EX}@zVi^N z)G#tx1ZV}Gv$0|6#SY6`siJ_`rc6^l=0D`BvNEZdY+>$2V#lcGEwyM-V9wH#>G9NLT_36-r zPrsQx^VB<%v8tcyKQa$Ch(OT^)HNbS^VEmOJ(PLma32XPy~_4wB;Aomw4?Qe(%3+3 z<|ZZm8fse>vPI-mmeA1HFwPOEwzx(s1ncjrnq;Ddw>sg zPSh$T0z9~sMYfd=aAT^@EQ4JV=2w~xqsU(KB_;NjN93oT<6ALwE7szXFwwZ+2DmF^ ztp!|YvKo`8j}sp5;mv)r$YZc`!6{dnEmdY>#Z;~IES_cGNDMo=-B1ZJ3i(0uOaE8) zHGgTI-UqzLab}~jo$w;L3Ofy8tzK}yH>SbV{!7OEoiP<;vT7WrpS*rvnzvd>N_SRO z>bUW8%^fj^iVE#8TD0a9JO|AvQc+`$VhYogaY&E_#VWVzpp>^5!;@0^VAr7U1f~!G zFe%jlfXO5SnKeE?ShUD< zMKeE|hU>_s;6F?`CLk~W7*U2vVqvrL9pOw9G(eQ}F;P&uozrZ4RjQl-5v%0Es2K_C z5`t$hYnhX^zyR&56?%PNIOX?fBSTVDJ4x7~&oYr8g-?Nq`YWZ3Vzwg>*=LyrdL!j; z*eM+Ri2?>c@i%$szm1xlhpEJqPcg4)94A=DtP<4nbS2I3mSGDJZ4eQ1bKex8H=^Bq z?1++^Yuqy;f5cypy)_aJJ3qbdzFb*Z*fM#mJ-t(bq^62eBdZ(?utZ%L^_JyB^nyY$ z`;g}`n90++XTC<-TZYW^SgUWvcD z!G$rdkQ3u(zHl|3`%-TI8vh??Quz{%vP5Fa-e`4?y-J{MH=Pg34_TBV?1)HXJm%nr z-k*Pz-Z;ODH?fGJ)_l=pxG|{1r!*WX9>IQ7DorWb`%(U4=sbcM=KxIZR_}+#Jusl^ z^uw^LriV!G4WD?EkaP$}!olmITVcaPukH>1;3UNomPe|fmcyGZla3d1uNwIgHP{(Q zu_8Dpbz`NIIMb?afDo)%h!iikI9i17M)SwoV?cK0^FnvpUq9c$9U85 zoyA1n`xk@c02Ys!l!sQWe4OuSnD#j8`;{5*aux2*_`Y-wke+gHllHbM-&I=VN&V>J z*>iI9X*|lIjVaO(-#5XX_A;>;yD_C$pX7~qNBuHLm*e%qtUx!2YhbDHsg~c%A(S9? zhk%Q?RXNme77SFtM2}GPC zUMBVlX@caF7$dOatA`#}EgL$EioiLiCqCzshoII@Vy~N5~!~EQ5C2 z)K{Rv_HH%^qR5%{W#rBS-SY_A_qdYY27UdQH>3`aUEkBgC%wEJZ09{3VREkud-48& zpF4Z5E~!m?EnVvA}( zBEvqtlnmEbgWFc6$552BH@I&&K0EfA^S^OL27O0r;j^b46soLAQ;ZIYBpx2=8rF|n zz9z4Y7wcG7^jxp`MiSz*=IKes)_Ygw3S4qh7Ll-B!)PKO7)>PlxkE2w#JV)eWF%M~ zLwR3`NpU4oa&L!=h?P5>?znk4XTm9>ueQHl=ZdD3rNRR>JBaE@-`qJHd|t(gEl~(h z|1qn|EX!<6k@}|w1@CLKfIDuq} zQ^N;ts;g@C`fCQtjs(Lt0Q~5=zp*haXKgN z7cFx=aT}FhdM51)Jwh~oS8V&rq6sYQnNaVilKS8XhE$OhsG$<#qq^`!dkCo9jktDG zpXdl0GW#X(Yfcymw`4l%4FyDEIkpwNf;dNOQIIFWT8lxJ1lW@J$%RmCir^>J1wIfD-DI2JvWYpDInk0 z?VoY(eGi&X=`!!@=BQ4`;I&PO=7Cl->#*ZMzF;P0A~D~M6R5ZP>e93)*S)(d!?!HF zRK0fB1BJdSk^y^Mc+2c)g%UnJslF?}wgswZaF6|oK1$MUtBZIKo<}){>6$!|X~Q$b zC7J5Z&i4^;CLUtM%dJ03)UB>AHrfQ2^f0glS7`)TX)uq8KLPAyg>=pN+sYUBInxxB zIaRN=b+hB-Drksn!@%X&c7??A-)*)W0&C@1>u6+oRg%q zKC<$?DQXX2v5y0IB0`tMm_km*3X89+os~oqb$_{!1_Emn%C9F5O0W17 z3O5U301@FzP^s{ks`$dHxb?_$|K-k0*+!?9kw1v^eYG!rywY>*s!q_{% zIQp10twr8G1{QZ_{URK&<)QLM=P9u46&x^tZ0e=IX|)}U6Te|1NO?=7ARKYMmce=L zXvHgn1-P7@S}*RsnzI&lO(AKZ}s` z@GUP#D^O0XI8pV23%7xnI>SrvL5CqHfC??Z!BC-fGoo$Y_wAu_C67Jd!=AIr5sl3w z7{tO&Jx<3unw1yW$x^^C9%G_vpk5Ylj$A&I33U+JHg=29ESb2xaM@3_o~Sy0|6Mg# zaLVy>>m8V7jtgQK=mBaxv&uD_YAEFEpl}t2}ej^H ze;F3jMnd5EnO0)|yV?dIp&;2oH}eqX(ao=j{ulC-6MKbTa$C^N{rM;gE<3Yiw73`y zx!7r&T4om{J59km?da2^B_g4LRFOHh1godib@04eq`8Z{6D5eh`IzX(m_~UUf&(7m z5|+-Tb}o|vfqlFSRt4iFXvOz`GeDQOtD8CP=kMSA8)s*w*za%4@Mv9 zw($ASMfidDeh!fi8cR{4W)mOHYR)jtQToClrY-mS;aJKpmD1PvlY1y^2ZrZpB6D;D zWga@1La$%4ka2gbgdp*j{7RX$!KdhSr;9E?Ga>I0is>FTBP(rM9w+{RgMYDbmWho& zsXkfLn_I&Dlvhz*R8duAOFO?n;aIh?fIV{h3?{9x|FGTfvh;&aJo=Plh;&YizRT&s zTh&H>SGgQ0n^E5DSMoWRLEZd6w}=ZIh6FydRrnGyR?_VJrhb$P+v*h@`NO`k9Ng=t zx#l+A#mZH=W3IEo|J`RRnt52M8DzFDk;<82tt&ajwe?#0nhL*UPnbeNJ`5YHRJXL^o#h)AS& zc!>I<&_6(-j+pZms8qUdUh^Ge%0YtV$&AD1x%-Z2MK9xxgC_;#Lsv>Yz&q~!=#SZk z054*}d}=u(Tl_j_m<|(@7!kPZZbagKFI^n13QE*|@vE~5FhI!*Brp4E*utaxxD1pQ znBPR~=EkI4b&9@jytPkxBTGVp4A~EcK9P2GQ;FA$vrSl&3cXWWiD1RWsD9p5_Qnr3 z%U&gs_Dfvlm0`OFq0`r&t#ien%_hDv@EHl;*Y^hHC_bte_omV!8n=pT>C)xARL=YcX`mjfDuYEn7n@YD9^0S{B^wquW&eRiQ~p`ZL_1{{V?R4XCy3fTLy2c<#J|r(Ex5Q{7ebfTTYwNAEU$-gL6u zWVfew?q>I4_2Z@?-i8KX#4ZPp50NW?MQnzg1@hFL~SYTUg{c9j9-#KRHopELUj9BAo$$R;dxj>!1-|oWoEX@pz>M{cD1IWE#mvz607!wStGg!<8*?^M& zay^qCsS%`zD!@ehYWLJlLbQ~fw;M%Q~0!OG87E1h1M1pum*69)opFeVq zO!kKAc0Z^B_U*!LC11hw_33f0h!8juS_1=<4N5v;G><84z-bwC7p&l6SY_PxakzA; zfs&EgqmzmZ!$#w2I#Dy2JAupwvm?tDXQYS5z`d+oX62M84t`%1S5?2a|XO%qyJ|>V@U}OV|zdo!iQx0gYgQb^sDiQZEMZcU) z7~v2v-t2jMpVqAjl{|F3FS*YXM+5?Hmvl{?ggU= zstnshF#t@b$7A^ckLq!(GwaXNf5WM%LIrKQ7_l2-~{ZB$to*w5z3ETVVrHWXTL+r z(j~D0jN|#9+zjJ*%U>6r~iSCzTtT7zq@Zy(W8X~i$tN2 zOl8<*dcZE1SjKnYtYAg6fj~b}!Y)UbO`=Sm>jEiu2O;WpEuSe2GdzLc$rSd)V+0aF zSHHq++a-KC6!~PmiHmzy^Z^%TO@lB;?h0Bne7ATsE3p? zo97+)O=E7r@%@9P{`e(vNKM*fZS|287%Ft|o`bOW<-O!n{+@>}9&NOgAL{o5SbJsM zI_{gvYP?#b8j))eeF&L7DSo?hvnglm7Cqf`3yzvZ82t4naT(QiP`K?$HsiXbP+-+E zo)o!~#y0FwzkdvIX~xi1B#FR@HV{(N@{JJT2D!7NhjIy^n%DL@dE!uTOLO3jfEM+4 zY-$TxC?9k`SLCLPkRX~CB8<5QVugAjpYFNq@SPf&Ya4iWY^mjkd{jj$eDQG0X;F>B z#|Buv4no7;Xx>Sqvcip4>=*~x;c}__bHja{XKxN)Y_T$&<+4dbi+p2X^UM?XHDUf` zr1pifD{L$0U_%TyBvKDd2U2#NdA=GLm%)ry0yPj0w4G)q`%va8J2Ath$>YS?x00O+ zt5-249wwr8w0&^}j{7&hiajmhI%iRm z?qRh-oI(?J8V+>r-i&{56+y~#gdz%AP3hX=`J91UKM3CbGQ9|&`(%&qDiTjvbQB*k z{vGVY#2=cad}ryhBFUbv-}q2S2%9*2cS!PPfokE*BB~zhG(cjIxZFJGal!SUYf_Hq z*QfMn!Du8RM2IG(ZYQtDb&oN&O*h`6UNfN7Hk&xe$=-B#FM8L4cBq3xGfaVY7lt+m z>m!Od&~g;`#;y7u1d%d(QqJs_o0r%r|K$ScVbT1&YA7oZMS)>y*M0q5U^qyTR?3nE z>Aa;H>{uP%scDqU2LdlD1%#m<<|AHNs>G~TEU=(wF$>IM2dl!7*;E@;{c@frTolRi z()sG1dK5axqB>A^oIc=ku7ieVt-^zZv&c0_84GBylwoVPosbBwuDF9<4-D_X5}I(i zlkahj*DI5f#5$u7pRaL9`+k6D`%n4=Vs9F^2gO4X(uL?XJ@I%FGXNPg`-U z;pe^(&Eo%XUV2gUG`Cf5!Zd}7I~hB!`gS_!T1Ndi9Zh#VZl8Q#?EvlCAizhZNTH)pFca+xlxvX0undaA#5K88l@_@NPJ+!BL#( zOWgh=bZWZ;-@xAN|yb<^FrsAhyRTiiY|=5=i_EL zWj|$+ONg$LP=&bB0_t#4ZxQ^{U}j9pBgQ=thXW^yFeL9CQBVl^v{##L2yqW4b?K0x zq*mBnML$2Ic7w-vqX(|^@ALHl9&&b_aXD7sF^Dp}E1;|CIi}}*0~pTFPBh%QN|!o= zWf_${vlaJ)M9BOTw&5J0aAYI^5Yqh%5Q6g(yLH~)t)Rk5@V!vQOY72kn6~8(CR{>g zF@|dw4eQ5k_r|^PTN^90aoAjZgZ$*FfE_Yo?xjd}j?+0BQO&;98yS5z{}O0zm(0{C zUW%5VELn=Xi|7F<5EU*WHOtK{jj_%_Q?jjJOiev3z0kOQj}=hmm)J0FRK;>zucIDg z{Sq7(*VhFpM(6z5lYx|v!*j+jXS!}4Pxl8b7yp3=838UDuF-g%unOkU( zCX}@1@2kF5>9db8pA#N(Cr-V99D4I$L7qW3bx%KUg#ibPpgVXhfJ6@nz$7B( zA>w`VZ=^N4o-@L2?BhFfMVRATunaw8lE*D>&dc##hN?Ak< z&hPFKh_L7GXSW%q=op?5{}UT(NC4Q-!!|L2-o>Q7ut?k_fFACifQJFgmAMlehLlLS z{RC*MuY9iVHmeZBK9fA|xG#%n5?4jA6DY?W_L>-sa=O7FGy&fn(Rs6(zOzZ3O5@s| zT}OL8%UY7D&I_o%l1-`{Aph>vBr3 zR<$sCPt=CwQyXBVd}7r5rz*fGvJ&#tPLTB5;Hwf`bzO5r~5smiGQ( zSPI3DfFmktzB+jKsxBch6aO=V2CfV~P?{x(gWTdrnU0H zOj-%`I+=RZ9b$LxAJ~wd4HD1Gzl=-p13s)byRXTce~g$)R~1G1)!pwwSp5iF3x!W# zztr82D^9}coR_;w74ShqCf!NsyS$qCS`jtdk0u0?qR|7sv4>g za%tDgc7!aCFwOoEyqD-UIgao_NCbvGT2+dFI8G1nf@R+m(wLua|_BMgOzp3o+C zez}925hQ3f)`*08l+^tzGtS6Ito~tA0;go?8B+s%_u}4Vxw)AbJoTqCakAO;i%eJ? zZV%rv6kcGGy3zpe{W3wW%F6&#vG7U%U5z8Y4YjLE47a{)v!`^$k9v(Gb((bnp$3`LT5VT0}9?G1KaG{=~%8B28moKOx=wI z6ir2_jP$+I=9$M|?u+M%Jbv{HwhsMxdcGYd-Vh5nPg33ZdXasV9{?&WP0z%E=t4cl zi;1G}#kb8`PNR6*c;(NbTj*ut7PL>B*Q5wR1J2GwK6vMP6~p>{Pj!(|tmK4&M8?Qx zd^mJ4mAE#}7-72(-5)ugLe@@PWE;?JI9^z`go zKS9)5LqID!={*5nx*t@wKw)_;d}>OtRi>!PJfN}6__q8@=ihQ9yoqpem>eloy#M;V zLrCOd@t6_kTn&06!Tb3RBf>(jzRaPfySboi)e;{7D$fDL`~WH{zV66eD{S#;NC~< zz0;AMH!nwzG6jv)#fJuRgsa_+w0*=sI-^p)moq#iLq~W@g%1BNF%_-_-XT1MzcaLg z)@B+A`3?iis&1BGx}aM|jW!4Vh^+c78Z73Bh0p-uOAK)(cwv3urVHS^pKEPrT!TDn zPg8|j0wu%uH6HpR*|S@_XOoG^)D4y#-#hs_3t?x+L5&^UYz-^M{Gvxbx(&Zcj3AGN zE~b9;lwrr9erfp=do{RU?G_$?jlx*%Yk=+xGaLb6s4mo6B+;2kH)Jt?0IJ)8d>OCT zkzMQ=gSe0+Tg;Z;@ueKQt)noS+rV&L<5y)*i#>a?Ir~D|V<%90v*|8v{stZ@g3_D0 zfLF)1VP6hNy{Vp?wXXJZ3^^g~!?xsGxLQ=10O#Ec+xT&obtT!03}l|}u>K~WWt1gaF7>E6 zF#qhgcpgqEgkyNkUvQqtg@~crR|MMNQP^XbT(1dr<67#t)n;QTZ5vr>{2;a(B zS>cf);KHM6D}Mi!gEb`p`jr~j5NiF0m|g-8t3TvZkkTYUK^ZQx?%jHCiqQ1VzNWzk zERk(@wt{8aE7O&(d^xzMP5;msjdbO#DP-wHWIloT9>Sxp{F#6&e?Rt^mEEY6)RC7T zW$+7nGk82m>?V}f#i6;1wm_qiPRa^E6>_GJukb3eMrJr?UmNR?JOm~qzyD$~%Gz3P zg-ZxLpFzM?{DGsPDrdN)nq9b8<|unGt-Da9Xx8~f6`^;6Q!~s$IS~0h*M@2WKLYus zawTG6>8k+mKd~SVi{Drfw~gLeY%v`SYgPKUyz7f%ESJn{5G%QJXH1S z-ps^Xem+^*x|@!A!lx6qI$%O3(0}GMB{s!5O1sP%dG^L}Z4U`h*o+#q9`^z3pqb#* zNSHk4^F62u-P&+7*Kyd%1KM=WV8D#vj%HWzf`qK(iO!W!Y{Vqz)J(;1D-i}Z(Iesc zPO-|yZrSb*6Qe*XGGTJvCJ44x_5XwiQT*FUR6*yX&`R*$RUKt?UVEsRcr%BOu%+47 z01FoZI}vUZwNm0>M<8@gc+7~~cK37P*M=uYCY&KB8rluTwr->O^MM^=f4Pa|SsoiJ zQL4Rz(B&0>4opqy3rb!a6d#U^PPq$$-*RtF(7R@R8Y}Rm!eHkt!=24tEm0gXg;z~- zJZFJQyPM~FT|?W7jM7xmwM^Qir2;<Tyi6)qdwqeEzY z^I5Au&_(>hQXwBk_w6freibnM`cnR6e<}YTS|axhDWd3uv)?Qe_E2|a4!=o$M+dE( zvx>>bb`CGiEQ^+k01bZA5t2A}M!2t@dMo(5LoVWpRgX)fmOFPjdpUAi26;TT36kFc zrKvI#PT;p=$AS@ypRdTmOb|jf>KJf}ox@ZPkYicCX|7FN7w+D-ghP1AE%b#b=t}V* z2tui4uVGyJfq{QZfCKvx$FKD{uJKn(tL%14m3Y7Wizm3$sJ7S|v5F8^2i+tVYPBLC z*Yl58c_bgcPnQarUIw1scPJ*;c%^Vm2=Qc1B5$f0Jd)3tM89g|zj?NuYaqb)R4aks ztGe9GS*}e57aPL*G^=|!es^vHPZR?~)q?zGSKV_Lu|t-tF*?~q)so6pMQ*Io9A*RM z6xf=wZ_f55nURDYx*LcONG8Unf4wD(aKGanEk@>ve~reQ%!nlW<3FrWIE2)x<<;x4 zFPg6R(w8P`Nu*pMTsBO9dWh)%@(_Ju|Jy^yJ-IQX^mEC*sY9o9$F1Q^#hHS)TdK&Va|UC4 zAA+J?6)`GbueuD0EWW2b#J2@G3=}f1%M?kDP&+vIutr7HN3tj@|Dl8={y_-^w8CL< zf5xBug}i$*n{bHx{G;Q0N_jJ8yX8Fnz525;>kj{R4s7b32;OL(uv6Gl-=MFdFJyk~ z>)TQw-Sbsu$PzMIS_uxn2#+6T5Bd-b7bu(WIK$&2+Js1u-+m?(!DQqO=1n-gtg@@l zc=PDDO;n*aKQR`YmirG5Q9mEZvr=LFLqxRrr-*1F>)#@xbJPDf5s_S~(K@2*F(J;a zWGM1sJJlRmpG6h5`^c^P)c0D_2`%5og$|RBfguGeRtC3o3Kds!4*TI}q&fflBsW;;nMjfTc-Hd` zXm{c+7)~3l4EqS)U1G#=OLDi;DqiO-vWi1UXz*Hgu~Ls@W0V&a`IT2FLa^sUPAcFM z+Hy)<#EVQ$>ifP9oWwJ)5vsrXP*U%aG89Pmv;Mbjs5~MB=10YQNz;1`cR@PDQ=A9S zs!`h1@aC2`BnuWw*cR2`#kG^29A#)$^q5hwokYY}GHgux~ z8xe)VQ4yE$cDYEo5jE`HYx0vaJ0qpONas>((b|j7!xkp>3kn~$N-1CQAG{VJMlCd@ zKa#vZw~v;ggf!PoK?7y zim&z0P5QHKpM0x-DNlrT%;;Y~{P6)MZ<*(CBu{~>UjS_cr@`&=sE&zIgSd&KN)tBk zO;Gt?>Y>Q>2_>ewHqUs<=$rM_~S-2KQV*SRgSKN-&rm--WirfOPj z9rM4>t-1A%Ni>AHs4st-2;938jx8Z0gslQ;UG}fS3VrPRD8BC9B-*46NW8T(!xC?E zAn~Sjo?-Xz#G9o2$7_!wR>C-qOAJHN>ME)aFLuApd0jA0LyqbFZHS;HPw?djafUL` z^VOqqa(ShtrS1scNmI)D#gB|4Pvdl7`oLJB!kUZA1*Y_+tZeqEOanfIqt>bNGLo}M z!n0Oj@q5v;sPx-6RJHS{QtTYz-NRH`FSkk_Knq3Ju;!(&9KTElIGw-0Jo5#N~*!k{`yhSo~i|w%Ur8r%n z2F7`!XF5AkeZ@_hyiVxw!CdE|ll8_2y@;9tj@@s_4J9`EWY~Wig)$H?@e{B_Hr=~x z!1M%@OcW9@ARSiYG4jY8ECPWuHZZro2;Ee0Ay0lV*@lPRdX#p`h-4+uh-%cVej(q!fe_ zw4LWG0P1dEUYVEAfILq#txwP5#au#VD%H1j*W2rhh@q!;Vf&D3kU?Tk3W=N4n?44* zaybrQhdC!%kB+!-#aa4npTidP*z&_&U_sr#EJ07ca9^dYJCf+>EIvw-INxD7Cno1U zG9oXB7~{Qy#KS#rB@weMchh*gcn<-4EH^2E?B43911LzEHIIpZ6E(|g-C+x-y9^4$t2d*{P zh1jJP`=p`Uj*Tm+dT$nbshUuJ+XGCL_0ogM4zvayoor zki!benM!(C0mnUP7{GlT_=gV&c4zsw4@jw(@NX9oL-e4jjzCMC+Ck2ixbxK<=bX}~ zvT0KFTp|f|X)bPEr(n83bK@smY;JDRfFyx~IBF4|$yuGErKSl%>~;f{HQf)1@)yaY zN1A+CtOIoXHcB`!vre}#V;-mb`<8XUHI)5U0(lYv<&toj5L*f7fI?fBqwBj#fe4pB zh@j6bstPBvB;(u@4@_LZMx3Y53*bvo1!tRYF~b92+6tV9AdF%ETM&;DUUs z;(W{}b&~x<^g1TF`o79na?Q`=R9}#w>ccg1!&V|n1zb2?Qsro+Fg`Oe2sp z(!=~Y<`F=+V-Zn7Kx?3n09gg$j|CmKAu5QSOUGkTUa{8P5B}VFOH1v-Xx5jOq47zV zHXY5bP|_ zh4aj=lfOJmvF|R$hix3B@W&ru{}$Ll?V<^5omCbufB*3RExi+!gr6(?KVd!p`7>w^ z40>X-j#v5e$1DBszJK7SZvZB*3a~bsbsCWG<(-17Z24S&VMYiffCFlXx>=&S`vEwh zT;l=CjMXiifsw@& zuo!cPhW1~ma!<)U_M0yR+>gN2nHyMq8df7-uj)2^NMm&y3N0URIQ{Atqu2NswsN#q zo=6Wslu59N$xlr9@I$0AAVeZ7fKNy2;>=8N>Hehf)%KD+b+=wZB97;}#3bMiPPPZU zZ!6rd27`n-69wMdoWdw1_oqA@$L+aYwDpQD=BfM zY~BDql!a~Z%|MV z_vuFCuN>q~K@4wlh-gf`*Zn#zSx175f2=ZzZL6br%v)#4I%(LFsXAS`{x=#_S12y) z%t;^;pNOQ4f(T=N{9+8u5Fj79&G5cntR}84;BMA$s+M%8?qG>00W*M5E$A}65{w8Y zup9OvtXlV6=XY4`MJw3|BEOTJ^q|&>Kv-Qj$M}mATJ4nHB9oHg&^(~+%eZ9lNF9gK z6^JgEzxKT1guss2t3Z8RB8@33_!|hi`U?nRWzQzCW;f7(^6d)dq2I<&J%0*WGBGHr zKP`{M-!)n~;T!my*D)PL#P}DmgMO8N^aqOM@pJC(_N;ffFCG(b$^#sz{h8u(Ojv8J zFc=BmOTmxKPDwIXr#9r`1H_(qqUqyTA%2}~|@=;yE( zd2?+P#H0LK*ztW%ne3C;h_bRiatq%R4v}^AL}VQ?GJYcE{&**&=@UKmYSJ}FQVg5f z)2eSPPs#D=iO9G4Zva8l>jf-XW~mpwW`}#KgyZtvOO$8T1!)^sB>MAw1FjL4ww#5O zzhR&n7!1T#TzNN~Et8B(r!uNuw@BFEix^$4ZClSJ0P+LL+?ak%-|AMFCt-RVLDG+_ zv=O`yyiaYtZjVyUU?Q5CEtodu){I^wOAt=L>sYy4Ru_0Xd;92`iR2% zzbS|~%^4HEJ8`KE2w(j##@;%r%5G7-rn|dC6eJ|1yFn$TK~fs&?k;JirKOebk`C#X zZs`_~hHq_r-}jvRopbN_jb{x1LfCshwV$=-n$wannn$^f>B;E!mqm8(CCcMaX(JY3 zLObzKv+E|lycpKYO9}KtWW|!{0SB4hPq7qfa8uD#$R&o` z8Y%W%I@~on_fGx=YzjF*+=7l`K1OE$of>zdF8!>?ls6JCDT1#RL(RX}54yVX*)te^ z=Pj(Jvi`p1yYY;>xRh^$kdacQS0bis2cWkXbu5J>KAYqRkqR**c?(?p4d1{Jpgy>C zOem@k0dO?zJtXE&0Yh~PrMO!&z%3>@VTe5owZ|G2017yaeSw#R>tdk<&jo2@AIR7T zjhh=~YZaa!3e447v~ctD#%Witp%d2O5iZ{ba>o%-w{IB2H%EKI_m|P^=&=be?#eqz zM;`}LCf#G8Hvft*MMJXQ`GLGdX*r|;F<+&9J1e$vDt%EKwJb#)1t2fAqkbU~#xX z1rj%pn20SS>Xz|8)!!UJ43veiMWuMn04SoxYL&RJNc=W{Y~5=7!i!=9+$|4>YJfgK z$uG(I#qo@9@JCFxHKcBQe8WJUIQ;8<)vs#22IS3HpiGv6Y;k%aPc`s~^IZi1cj!CF zFmPGDnGpK)&vquQ4a9{z@3C*8plSZI+1C75v#p~M?Am{tZ6blMvvUvq=X9KBo-mGU zN0=j41W-fg4`fu)cp-7xS51rgCU>N{EN-{IM=pm1+Zaavw_uw9tS6+jIRm~ntB2l$ z4%g&?*AR<$ql%!o2O*1YD$A952p<+{>9E`4SP0gj-_Y_nqDbaXRMws4EG9HDQjYK> z+|1$*ta;Y-WSf=YqtVRrLZNejItny`gkxVA?+PizKUA%c{mY@K*4pyd`xv8ZY!%f@Hf09sNgEOPCC45oe_5Y*}fWhq1lrFr3F!Tia&| z=UbUFnZ#vk$E@!!h~3evMm(l#F80_(dU6{hd3I?T}8&y91NdaL4CKB}5K0 z|ce&b|Tt@RCq9+3!vd5qWDue^YHF-S`#@Kg>Th-W-)WO3S-WDNd4uZ;r4iP z>XF3C-`*i3bOd-+IaHo3lf<)}Sb6)>@=YD^)QA9u1nGtCJuE*#k_AfDn}{_< z6;0^>P!C;%{1=oc1qFfDQu^6$0af}+kg-3@%U|nJ?|-II0S>Lo zW9cM(=goQ8eJN4s`>At*(gl?r!ddOITcQ=WM1Rsij#FulU%nQ ztoc(x>)T(df;}T_jK1gG?M154Zk6>@2_>Cl-cfM#Rw{`iKz zP5y!{)XEDYFU|8u%W35Cg`eIpa5q;bHC+jxFw&JKu z;KI7YL>o?v(HLD z!~_f3_@OpWp^EX4?M8l8@NDwma>8wMc0?vf|kO2|AA`&o$0VW6((((-RL(V)#$`lj&} z>TEIbhfjVkFpMf=jnj-`c-m6~No@gRxA4J;&N{1m*X<^O*MjT|08BN!x(Mi8qUWJxlTCoF@(w%S^G z1yyY8dxj@QcJu>Y=Y6iBPF!WUli}o*4c@JZ%Bk76y!drjmPS%0CwBtMf+?+D^YN;b zKvQgVMFO9x#4hgtR1uNDB8h0&zVeqxBXFsLzLVJT$N%+RHjjZh{3&!h66M~{;E`ba za^?54St_NPt%i99$f(;Q8X84nMo=6oSWGYR@$xtxGuiuj+{K>Q>_+!oc_W*D=+*P> z$_k4_uMk=D-z@3D4mZg7PE#iCAt90$cJ<-^B8}QL{vwTb8UK(*a5<^0G+B{jl_p48 zcR99BrRDpk<2C_=V0D=s9Oq{{HO{Gx!~^LNzvfN1Dp+_N=B;GLf$C*Yav!z&%Gi#Z zR;AI?>+p3z*65F)=sDrkj0)RJwE0K>4Lz!EY;Ph38vHa~=*%NZ{$T}LNT~@)Sg!Oz zsA1G0C2;&%GMx6vVZu+&F+-i;5JAtXhjqoOrK|~UDepN_^n@LlU@Q3ohBZSv2yL2< zyAQtQx*bS%>M@b;eooaXB7Ig$taUG$nd5{vmw256$sH6tnZ-upr>ZjJ%>4Cu`619I zrC`udXa6~#@3}X&r|&j)WM&&X3jB$yHJV{Ebah3uwp1$P4!1l{SRs*Q%Nu*?cyM_>WGVMPA0gzPEYKl9E=4qCt*?|Gq({t@x$?sRaPz{=F`7LE0`~U7OLL!H!*4`$a z;@gKBg7+3)74%ALhFJ?0AFlzBC)bijlSWpS9A+i9St>r(?4j4vIcLkMPHEB&I1RDG z*sVvwek4!&`=BBLG&SYV%D6MFs^>(7iat>(h_)Z=j zd(}M^YD9#&6Fu6hBzRm!jSeSMqnBpiM?Z83AMPVRN&oTp?|)0N(R$0z zEr&Xz7OaV3VTl>sJ%Q={tSMFnmL)7m5o^SO$%@B2@4lfM8Bt&{Wle@?z9H6V;VGDb zonV1WCBgGA*?6xw+u`2ROK+_I-twt-nsXU|Z}PBS&~&Y`;*|`6#BQZ|&uZ?|^eePA zW9`i7OJnUFEw!lE11BrSpRPaf&j7dhq#`=dx)&Qm=T(@krE~>l>7m?n53UL^TNkAM% zKU5kULQMH9@GzmjvrmX*%K*jbYOgVGUYy1F_OQ_I< z0mqS@<%<^tqqA-?nrV}n9izOy!7518gGi!@XQ;Wt5iafGma_nl!64|(4%Hmnwj<## zU#HduOs9b1vC`wcH4R&6A=StE zKonHSokpaBaB6y&bStLC9krEr62AjOG{F*LZBO z24CLOne`6efr!8yd!)WUO0QBjf*h``87uDF23<38N!vVXBv+<2GBlpf#E0S<*N@ArjAs`K zfLo$I`nW^^Tt^E}QL!i+$fTW!;GF57RPln2+w&R8Is2EkhD+OUtZc`>Nzc`=3|gEt zg;NfAmZr=R&Bqm^W7yRe`$dadPHMoSo)b5J^DiEnM}%|U@V%VwR^*2>q5sH^=yV#} zS_?MZy5VONsaxrJ6s3=_!fcj$=TSbud)kKMlnUv{f8J&m851ppUTNAIHuJ2~5`|^H24RJz8vbsk>5l@AxuX0&wWm{PV zZ?1x=6UR%x0|qA>dsL#5>gkH4%80&ZId*v$4!*2^x4#rN9zLU5#Cw08L@Q!a=Sa?) zqYJqUz4J8w{DD-5;crXlgSxwm<9P@49>eM1&QF~(wjEy&Nb;riwdDMk;orE{+RRk# zxf=S!&HYR`?{;9}$UG^j0%HW4^TM(G#C@E@)Sl;!-UhQi5b21kj$Jcvh2ccy8m=)s z>~HIaJLOxY5@3L7-hQ3$Pl^>B#su5=E+!Z%O}_Dsv6}`u3BkJD0IZn#jOVl)#KkH0Tv0im!qH$~}$$rPt3Obsd zq+TH_a~*`B=tW|u(^upB&~ht}&IR$T~yric*3>5N&Ri~oV9 z*r*|XCBpH4#oQtYQR&YrkLRj9@}wKt%r_5S4xH2o)-P2Uho>mlv*6xj7t-Jww67#e zexen4(@N=khW@r?ltuC#v4^FiM)z!RTJRr+${3f$7lFC43@oz$NU5dpk73EV&pdCu zT!R9`W_#N*fBZ{5*Uc9=f_4F3axC37tkBN0HZaiz zs#eRs>(jh;{NZ9k_R6GYRMvnXl3{NJJWIoPz_Y|QCHNMma;tb2`q49jZC{_CWgf{FJ%3x4DiYlMU*tnI(z4=T zy?mbf@rPx!;Uu{iI}L|~qXjCiuk6BGTmJ+ksO&KPGPG&G7*iYGiVA~dIAeu($r2vX z4Ngck)(bnj*J9A539!9_vxj{1dFi=tLJ#IhvagE+tnH7D&VUr2cjSGb?P{ee}3XaD2Np_6<1 z-h)OkP1Un7Qe(Vx*XX=Y61E{c7tLguv@0g;@tLey7YX-Mqm_GXQ>|>R0{>{(XuAXi zp_+_RefMfNKY6<<`a|7jJtWy?r5Nfn;o%_%EFCn#LeqEY4ItRYU2l4#ph)Zb<9uZD z>UC%7V?a+W4JbCvytr3H-~lVI5hl-cd^EfZ2LuBYq;EDwL9n0?aw9}D)~mLzT1hFn zw@jVB2HC`?!u&#Cw}516q48-O`T!r@u5CMpg`e6W8Os%;S`iV5p79&$P=9HrF`j%& zg0JC|g%#Ehldg;bMPLPhFmmKzM?0|oOf-JzL#x9LJnU7HVU%5d(KNXQtum4H*F{qW zzRxXY_6R{2!mIPwBMKO=yi^=is&guk=yyaudgM&<)K|A+=fmvKl)<76pZ_SQGT`|J z4AN)HH_at`v@og|G|!;Xl9jgWY<3*Lx!Q2r_gBzusm!2b?H^jJ9dM7@T>CVhdz2*odyVD8f5cD4h*`OrBvNK;DzuMmFE5qOVezb~1Pm zN-N}j5_{2`+Nh^AF|*z@$WNLxg(?>4khlDO*G8V@ObmB#dTY%su$b<#!w4pyPcN84 z=rDJN+c;M+b!+*eyb(Er#y2WUwXmEqU3_VCES}tTpzC4k z0>L)h_pRE*AlSwf{VIv?A=q}TgF-$o#cfxXsm?BXtSd2dh0L)urj z2tdHCjmlU51QnVDH8{*r2I%tFjFp%;^#PASoa`yHLY>+erb72K`flDT+P6DL>Ud zYyLg3sRph&NAG`@-B=u$y(-to1=o#6 zMfhpaR>Hz*ai%wFS@dleHEZMsnJt;J#Tn10IBFZ8neIW_ApWmC zHx?5st!4>2N=Msa;?G1KkmcjFoQS z^Kx%%S9c`o0_N;{7E$DiFM-pRpDOH_3s!~kO=z|IH_92V5Tn~j@b<-RSJ(XO**CBf z`2=#u$|vmeib$=<-%j~(Sr2$I=yB7#U!zJ>Bwhf* zZbP%4YF(&*!ft%RtrCWW6a_?yyp3n$Un|>k=;}n0p7=VyGV5?}vyNbhDEU!^+uVBP^qS~? z=Z}<$LpWrM$RR;~z1rB++9*47patE)OWwuY^=}W8y4hwMSd|vZ3bm zXHHqpAQ6ss-xf9>&KBi zw3Q@W;~K(!{r<5*X39?DS15M+NbgPIo7^hj4D9ejc&ix*Z2O7z4(d5B>hpkeu(!Wt508iwEnWWPl5 zpm!A(3A%+>ECUit-M4-(pq*Mj?B4!b&Tj4JXJa(SP-suXl@6I9ZhqMYu0q-Tcdt2q zK)Iz;GP>vU+5OdK1LL!)JHIyCjnQNo5k7KS=T(cL*R-Q|jAl|S~ z=XW`9#3)=R!@)(E(GDO~aqUmd9yV^0lUZ=MmXneBX>WuUK?r+corGt^CrCeVIK(}C z65Dm3W8Xs<3O|pztidbZEz&I_fKahaOJT|@)!mAOK)|HI`ir?`3lo;7r29m0sOVVk zaTfZ{MXFRn5Z7(BcGIR5+{#Z*S@o!$LODWea00v5m?FT{ctDWwVEL*-~FXUQ2iD#9ON2 z6XiLM`iCD@4)bHGOCAgE?dl`?{(o&l?;R-r&mSO4QbIFo8;s+UWmbFmOOU_z-?mnX zG+dj-rusS{v{m-cHut;*@Ke@BpL(;B=ViYh1>T{xOXC{|=jKu|yu7Z;ZR5E{ z8s5*+GT%&6FMM~gCLL;E(F6$$bU|3G)rx**Mo9S${nzS+s63%ojpfX;&J0ABF}AfP zxQ4kxR|cpHJc#+@`a9R=C^iBFtEh{!wvSDj$h-Ja+gyDCq)*yF5u)(jpERI2IW#fJ znOilDtrmjggn@f(siI&8K+^DP8yEI108fP}x&eeHSF195S4zhat5U8{KL0{tUBAwO zlEA*X!q-6-<8<(xCUXo5bKlpIy;hRmcu&}A$0!Zu8T&|z( zIYNjPX5kt%-y1KU==!$ zM}Vu9GjqAQzKu-XELk%^XX7z9C#6&evJSlMU!&kGF2znvaGNuY)p)P)45)(d zoXUq@7Q3Sex-abNs(bDkQj(5r?`m)Ib%7PtJ6jvT1GV|#vnTMTus~ooJm7LMMLmGO zKM}rH7WhexNe?L*?M$XN0n@j>8U!dpe-;+ROUus(_^U#+N>tb?mL7&m(gz3A^!f`E zR5U)u0yn|t3Lf_e)Pa-vep2MKr2V*ptAAk_+A zv|)z5S=dn%OrSE24}&I1lO}hH3%>)$NJSm+WTkvkiQ*1mWlfp81yMcS*YAGoBDcVE4V5r`f1Vwr+-diaYN}Fy(y?~vdCw2)M zUXk`j`p^(KVc#7Goizxkp>5H)wJ3h;KrTpmjAHftaUJxzQ`N0%se62frVb?$-trgH zKnWy%C@v=xK5*?h(K-S}K4x9uiNZt~`X)SuCgkkOp0X_-V)jY(W$s7|*KVrH&s>lb zyT6+10>+s%ti_FfjkmaglLij~%ce^cj{od*9=KyNl z6g+f;eH8T`7Zt;~9#Y04gkVqME=_CX0f$Z+6Y_n}Y~1=AGB2<3^r8I7_tDB?rLMD$ zgIFAKp)NckROHJc;40BtOmZiK{^N8nd}a@L=V!aRxXyNLHfA zE790tA2>t=SS+U<$#4R6{2M<^&*hQI`_@d^8Nu+#{5$g#>9SC`M86?xR@n;(i%EUb zRC88&d1i+h@T^rfGEE^+*+C~rk*9YG^0D8swtAvu!k`BrW}l^Q3gp*7(H~=dWDSA+ zSq&zAM1|-wJnfHx%WNUp;^jNTqvJ0r6K)j6$j~JAwcqxw7HIMWjwJWs-il4eFVj}1 zG8pchbpR<_%rb3YhvMwToo{cA?XbsUr7PIkySg8&aoZv}@huB?sLj~{8f1eeBBkK zzL}AI2{H4LVc1gIhQFC_M@Ys!e+?`-xT4>iB$xUKjtjJazX>I}m=xy#D~2vo1@Q%C zzonSw$mgiv;5ClCytW3ZKmN!M4IBoYaDZG`Dw@81?e(_w1s`-ET=NJmSMkdr=~YHf z?}4x)@jd39DN`TOx9DZQt{}3xPAk;i3xO}O&qR|B)A(Ho5Gh1)3ZH&V(gnzpf7Xqc31xDcxYhlAe5AD#+)*$vmb`<2)QQ7Lo0XN5InQ-q(Z zv2wt1n?2{Mt&iL-MLK{unp7R+Ufq5Hj;3n}9>~gm0|+~gi-?9T%KM9+Hg@jk*(C1f z6dFx$)7yr)>bzQEDHK)Ane(b(XWBG;|5V)qNqA?SsapqDAb>DxepfNNw4MaJzjF^_ zFs9XPbk(QTpuwOP@Zw6CuMb;VAa16qh9A{o{PQaqxfcrmYPv13SGjKKBu>@W9iQ;A%F;U8(=L`fM3sN#zS^R_>k0WjN zZqO%@LsjT-D7p0#w;k({=LSyJ^2S$ixP3_bA(gkcM`A63o4)z&_ZrF#+lJ8YQKT^5 zN9IhQ3Bh->s-!$^vlX_ynRyo2?sbrP3xm|nNA-oZGuD0#duR{SpO%&|sqUsW)EO@+ zZ+*xeH#55O#gg(T-SvjOH>1vqTooUFg4^1ckJ=`OqL=^6)(R4OlRM0Y<941R zQH6xwgl2=qaW=1|MqO6*>2wesp@1)^(zk&FhKBOU#r}t;X?<7PeWWQtYk?4|M&JNK zwaz5nM%a-oPbc)TEd0AZCvXLIQH%sfn~Tq={NhRorO?kJf@(`2whn8y!{lXK4q}*g zvs#UCilBGwQMO3~Il(9LM0m1fOikbp=0I!70u*rb?`|Y4=j%ohh#T^oKkbDGX`i*q z^W=Sk*#}lLym%E~HcUf2ui5^*DHeA91q97>v zLoO}SH!ynom!Ap0y~voyr!M+MdWCfyqcs%DLNHf>QCEk z$Jf1oDQfcbuQgHjqI>Ln{!jD95k8>pHg4YHH1?owGJBuW@)Ed)$3pO$TE#{4DbMSA zc)ewA<34e%&}nS7SN5WHzeS>atQM*O)J;~h$j_}@4@RqSOOHV&(?`N~{Jie0pSxre z?@C=gp#rG!n<@?qPm!Qgq4c6)+pi?x;b%0Uo2F@I$<9df`AfBp+)%C)2j^~jn|(Pi4(_SwjYtq1}3LNVPuj= ztXdhB%62$s$Oz+~=9^;6)FZTZ3~|4mbw~vm8ye}36O@$F+YxcY-JIV~9`tqSHiC6X zm3tJ$BA(39X1OmyJFEk{c$bLPsa+ue>W@<9%WikeJi3U$C_Kg&s|v}%SV-!4J7cMFai$20r#k-KsNqC+wKhC8FasZa@Q z+A1t_KUZj`9nP$;iz^ab=RkfHys^oYHwo4FcJj;oxzjW}5Ulqy>u<>!k?T(4(r=vv z`nVT8!^$ofPF!>Qsl6_)cpF%&!Cc#29WfN%iqCTlGA+_#6W%tSQ8oz)(0N{iMWbv# zFLav-*WZfQ&~M>Mw1;qUr{s-G*1a6f24mdc0dQp~YB=)rkIbn7$eg-v;N(Es((35M z4>=r{=|5ynP8NU5oFIdo8YXe@&Bou4IFEqrw2wS36pkV`5{oK1F;<9=e<(t;XcQxh z=0&`eg+OQs8ExfvytVfUNd!54+2IcdK?Ug(mPm9W4P@(^=Nj=&&dT6GfWbS0zo6yQ ziKHQNvyC>IL!tkkQVyIXykH4)>i?B}lin{qYH(H+&Q*6ZzQgnLF9%7x=iR9cK~NJ{ z;q5;$(Xrti_L^16zs*;XkGz0O61ICp2R#b_eI3#ff>YwLuk3OK?eCj&Ycf_HgJ^g9b)!;|9P<}dxZjl?i|}%I>!bfhH$)c0*U^qUk;x7<2@D{ zW%7aNhTamDKnXH>qvY)SGKDL7(9ZA&Z=owFIkLt}#!^yp9lbUy5^I^k3TW|f@w^(( z5Hj-CR+V?vHZL$7)7^NR%Mi)jA{R!nkNpNYin_9~O)1~>*w4KQKZ>XQtOvytdmx*B^6Nk@G_C;!Mcl+!?5KtVlzV~26Vt?oVze0Y#l^OW!Rp}> zK~tQZ6vx<4%UKh0GF;+Pn*B2FH}SY8kE>q0|3IUj z$iOSv24}M6mDLegxtD_+KuzKTn%L8pziz?fgO#8k!L|DaiX8S0P#jP0$0$Atj%w3K zc`zX}0`vQmlPS;}T=0;Q!K3?aF&;{9YAKX;G!|hHr7v&HY~4Zj;D92BIG{l0;co|2 zwbtvV6fjPs!vjF948GxEz+|-p5UYHO`3Oe_#NtLhUHjVdj~a93w;20@xvL(D|9P=7>Fc7ie; zeR-I6c!9RLHrgpwusnnpx1nHy{R$<5219O6%;I{+Q~CI9P%xL^fK;ZPnuuA;x!2Ov z&6vwKrC;JNgtejNS1TbKCzK>Rr6*VbQ1 zmUtg%wFKp|O6}sdPY(!^zd}Sq=CdxhwmL^F<&Lqix=bl!CkhS59Kp`pPDg_(jL3P zI!={{?yg4ddc1Lu(<(8th8>6Z=0FO6>wL)xrAgJU{XQn2fEh2E{OQ1Bs6cT?ILf`Z zV)>#M27By{_S7rw+5|r|Zm8l3zpFzmQFY>9k;TOH%COoQm&)ZNrfHxlkN2ovMCLbX z7M^l5hBBrYxL0TpfMquc+dSNrajtQ=Yk8g&rkT>&wU7nb3>FDT)1p z04z&}c#vnFiiiK=S__r%Dz43n7=ZPEQgy}J@)c&65Rucv_bSL&IY4Y=QULpvhxQ>4 zZ!XH-BX+!!m;4&w3xG0G&WE>lL?}}~b-<3TQa)B9Q1A*qe*SEv=jQn{r((t)u-ah5 zlWA1wqO8KZC~24tQ8IG=YF#F6)5bP{6fLY{Iwl%_>wR~~fP*DPz>5#uT#*{?q{)*n zY~}NCpKa1`EVB7&9LhnKP$s)TlJq%HlIAR&DL>pD_iAc`93XhWsx_ zDI+ZAXsWrRJv@Dm?k=bUoRAY(hN9>b6;-qkKcfK7cZZz>gw5~hg8|0J?3;vqy9>ru=4=I64AcggHe zqmZO*v)_M1UMzBpUJxt@Pr<ay2Q3VGrFPM1`8g5egJX z9wC9Y^ifFLv=WdP$QsVRvf7kMtE2bRBi)<6X~?OS>xZ)mLVj#gY_;3*RyKtfX^d%K z&CL;x=c0-v}|+OUTNIQ}On~OW;idxNTZTyb{lTmCE0t$>Y}9kTsx=c#`LX}B;p#|r81I+Hu}8ktwRZ2#ubr7-wX+ZC+eI zh>QMAneTG;8NwEL6bOQ%Tkyq)%NaTWJV#0^MAfY7<8E^@UOWADuvErs^@Ccq$KZ*Pr|xj4LlLQbLph`ES*)l4X1G1 z9BonN@?WrK0p5u~oIru$BV0!FveYLYXKqEis=CR_60Vk{-^w>!>XRJ`;>OitOzi-t z$Xm!;U>B@NVrqFHoz{>Hh`T4;7B?wJCaMhmg&4E!k`1*v0&;fb>W}&#$>FAhXN&PX zwrA%LHMu@Ic6w)}nP*Y(7YN>Fnr#Ax3}{{*J@Pa3`N{GHE)>a%2-GSLy-U zC%jcdY#C{JJ9XP1&LR!vXsvj^TwT;y%^@T}o9%|GO<(9N)UNNUME#3$;+Zm+Gd>pK zTBhXHCTQ;UAjq68M&_mYJzh3IBEbhg&6$1Pa{4Z8Vouct*Uf|u zQq5!ED~wyw6ukPQXxd*O(NqeOMyJJN#Aw)u zRv8G*G#53=5ZNM|Kc`>+bOR@iAg@Z4T1XuPC7fzia+t{o~jpVu<;ysKj(7_D!7V@ zQ;m#5DFP;_uH0<9K4@Pfm;KplnzrfqH}&?$in$JtZN1reDk2voQ+u?Qw-nphddRZh zxG*tF6INbS^5dW4N0}#5`7cQG+hq%-fqPEcjh&IP%UJR$@rByAh4k+deov003zsrK z3oewUAgS<1RJr~^m>&Ov2-##xWuG-4+8NOuqLXr=4YKTwO-uSRUQGHRk*Ki9wqfnr zF$v1s*gaF%kxVDys zs&5gHtD3SP6chb_rI@&!FJ}G?W3t4`>VkQO{jgH1|Fz*u0pDf^xwOqEN3`ezm6`NV z&9YS=pvWr+zM#fjBx((H?rBK_W7G+U_XXKoWr#7VTEE$AQO?znZ7bz00Us#~6_Mn^ z-{2}K$z$TO_=l3F)|z|9_Nk=kL>`Dp=Z&jK3B@j_u)=1yP&>vge_;KLKHqc15?DJD zCnO8Qa+FBs2+g`Uol^z16l9L-;D9vZUn#&~u#<`tGL|fN_;y<_#YvuFQEBS~zP9b> zB&TV4ULJhKPomC(!KtERg6%8ZgtHV^+xn>O)zFa(M9uLxw35*uWpk+@(kL`LO!fP= zRwkW>35YeS?`EtlwAL3^6Qt$%s-gUgl|xVE)j7yGvW_sEO!|6ZVz<;H%waR7 z7BIV@kC#z@-t+dAz||8Ng|7!z?D=V8d0z@o1iYYip`T%MdEM~h^8Lsi@0_sLB0F`9 zeo#j(E^ic74po$rQD&8^{kRjyF7a+XMJd)ZgM|Y6sMn}aiz`&lN-^O1;7?_4!p-|5 zq%s$VPU=4*%oVdoJLa~Twh8e^b*(-PhV1$X7T4#t<#uaQ=K;>EGK&61F(cYeMh@}3 zD$B#U?!xLN#wU9C3$kEtVwEF^f?DGzoZg1_#-_47Z&xqsGBM}5GQ}0ft%k4S#L%X5 zwxWTfm3v%Wd!i0tULd1Ck))xIXSta2;0W59)&zdXpronNsLWS;v;b#Y>>(6o~ingp(iGD{w9P0u8tFMz)AB!6XYs0T*P64OG74e${V^(FOqRu{D zjT>{)>-<!QAqbL zG)?>^h5hL$mB3KMdvlkrlexj3jCQ$e)!Roksonyqi^1J!C%~JnDi#NJ7g$vupp0yF znZ<|{(|0q6^}~kcekL#fdcvN;JNE09s?HzR6y0e`niJQ$E$jQ5prEtesYYpAQEsO$%!dvxwe2H6CwopE8y)RDLQJu+hR=e^DWhj*{ z61f~>;d$%Iq@RoPKdJJ=Jm;w43paZA$H~1uB_@Cg@Psq{S%OjRL(d8ZQGpX z4MPzj9cpaT)Q#goHg8!=XNP&8#J6*@StGXG+j8lLbxx zdGtLe*T|Jn4gdIdvlg#VhTXu)aqf4v?nr1?Lr=XdNq;WgV91Op51K^stF9+$;-+W= z_MyLaSkahQR`cz4`ia-ljN*7qYZQ4sw!;E6*IFgt8gb5Y^H32Ic^`N`Zd%}7?bBesEcnC4k1L%7q%F9?#YW@J$3{V1AVZ# zHL`tOWUUP#h)$XL@wr|ILMnBE@$eP0Oky~a4R!GMD00rrDLl~;_K*;C*zi;$iBF^#xx#+Rs@ejWQ z@{9iK2G3oO`G*zy-~N@QN1)wFbv@Y_?21>{)g%L+&aOdg=Sq6X*MH$ntwwAhszrzg zsd9z!gA`U5AePR12~5K5hh39DcYl}cec-8*;34feV0*L8p&eNSyvQQW;Ys+PH_^-* z!&Ju$+%w^j0#}y{K$S%QwBweR#d|@O=iPiF`>+5zs`V>dj-jc4c%Tb_FRWC!^(m(- z%^x_|qMBSDxU@8009TY6L;k;Yq<9PLfnz>DzDs?-P8g)7XV@msbi3;>-!oFq{EHNIHSh7L8EA3*rUPL# z@o<;PAbG@qj_4rg;O)*V64CNsf51gya(LHrdF}yBugm!DJrF+V$kHZB zL>DN+2q3~Bl${>CgmD;1Q!ixa8d!%i>n4k>IiGd)uNSmJbzT)@N7+;BUiPUIoC5<9 zqu8cGB|L0p{et1Y?;$ zY8?6rp0iAKcgca+tP7I!6K$){Z;2)~VfP~9KA=tWMiIOMWBod*w(ZC?Uc105X3-Yy zGz0);{$eer+jlbgz%(>>!zcM8j+WqAo!jC`Iycp75>NTE2;cXE?+_Mml^kfLmOXUR zs$=c@-z-)zo82v9Bj}ztOyTcU?(hjJx?bvduNt)onXB5ySYpe%Ol{)jF8%MsO=UWWE8z?sfRfJlnGsE6B)8&TJuPWm0GNU1<6rusv zo(B3{8{**x1XY9fX8kuX$?=mull$nvH^4jYb)g7bPnWfMQ4LwL%Je$&%@2*=KrC+t z$C-$NW{RCoyFVhCN)Rbe22*=P7zEuxFph6a9nw%tqq+mOFPCHR?pm!%XqrT?vA)JJ zXQJQI+-va3rE)nkJrVwux{vjwP85e$?h~$pfL*8dI?!%~jB5k@NN5zv<^>7+X}Dj; z-Dy5^2L$c27NO2`1!+FzH%Qfu=%KAqW{0fwG0kV{ai1s~RbV)9tN}OJARWkl zD$ZfRnpNRXdgF7%7(n=KOEN$KFi;(c=fFi&8tX0-!*gLfISBYO;Sjn&p=BtC_&4*b z1YF-G4xacva+uhd@skV%$#)%GQN(V^C5xTUd10lmaS zVk9e8J<$CaReWwtiYe}gLm_mtf{BT2opY&_h}SHvU-4}IB>kKz{d#`0K|N5!Ig5TsL(R9Zm{knTP)p8Rn5S3nBmwVop}hdpWVQHvGfaAa^hmT`U6-p%rnGNWI~wIc6IfZ zBbVhVEhJlF53MwA{^+u${sF&@v<-wn&)YO|y4;YAYV7CqFMt|}gU7lZC6YG6k8Qlh zHf8u#Htk{;MivRu!|ql)hi|&ED$PZeCv$MZ5x0j1oGaouVkY(%Kq==4>xt?q4AxIZ z@8qnp%F{}Q#?g?-xu5MNW^NT1g&HTRhSiQPqi0E_{(NEUHt)iLpRD%oGcb-Z~ zYg0W9tfx;y90%gr&NMeih!TtTCh zmyl^^5#7aAdy~_*rR&nze@5i0ZRCGN(KFEO8^J{wJx$V!te~z$GpA;3vbSJE ziw##~olZ93(!o1gM|`(_jmQIKmkBn;*l0f6h0cm!)#pecf2Fc+Kq-d&8FZkgaY1W~yA!`i9t{dmSs9wvOv2l7xN!C`2E)#fM43<8a2Xk* z1$0LoA8PlT_j-`Xo>3u2yqd_YiRkqjGgPZ7^PJhjy`AxfHku6e3Te=)7eDxcJz(|% z1wOsgAtLN-yIblQ)ymMfAvN%?cv1!io$14$d9tC*I^+^b&knzuIvQuwXX!mc)>X@_~GaKw7=0Ynu1;P4o;yF+(PY z&-5WDWfAZNsiO+$=ls6VA{H_;d6m8FwU$R^tJsmK=aF~CT~0$#gu?xQ9#pz7nVGxk zTXFVzC8(i7s>>rs3=j_eOyR^V9#XpU3Vx*3)xWu<_!w*X%X*9BrZrNXHKt?MeJ{Ow zwS7Bz$WkWzNLKjOw{N@$X}xTRvQ+rTs2JKX^8n>oYD3&xhW|++&3GUEMj=u1ddK7_ z6kLw7hL5~#;ug8D4NebmuUT_IkID^G9@qeQr{i@(PVqV+cV`7k*2bJWL&apf8Jf_H zk|c2N80NcpHywc7;2@sSe1M#8rUO0=>MSP;CAT?c5HL-7?^M(+%yp*mu%PQfSND(N zBF~u*L`_PTHCeLq3`Z3DK?%9WqNH!2nv!TkGaV764I>jfHZPEX^2ziMeI|UbnJ7g~WJSamlk`8`nA45q4se_Tk#bUg-O=MB%PHf){1i!qIfzPoz zaa;YTD$NmSh}k5Y9$TO;Qr?hYe;`G5A+4FZpEmOjzY(mK&Sir#?HsStxKNKbfBX zEq)UDC4MqRVz)>Q6o4WA-jXXo1b1;s&h?6Oa7b++q~_xBjhW|>mdI$j_W>;bxA@74 z^IybIqfvf!-1s-=iPH~UTmIXYlYl9J$1wIJ^M+@C&g?l_7 zT^jE2fHw5s=uzzJ|3Hua?#P`|bRxu7QZZDz6H13j$Y6bR=vlSjLCFc?ViK@;jIbPl zAJP$@M%Dd7LX%!cr6=4N!6=0P7Ecxn-$ulvDqGD!Qlx9_=s;1UxU$c#2;BbrN;M!g z38-i+VLVJ1^Z$45$amJ!)Et8zn?iKt*?vsh=d9ZbjU)(OCVl~#vX`o0SF#8qU$)tO z@dYn4=COk`xrtJI8{&uiwy480{4(ps+f7+#x@d}1h*2`Wr~B~bOTxDXgo4M=ps3!# z2b&6n-QC7>1vMFp48+_gLz+!qr*y2F?Yv#vOpA==oD36c4R|+%(Cs>TcU>hl&N6*m z`H=kTiG3xy#17v*sS}Q-wpUB9G$ugdr%T|hj;546Gcf#y1St65)X)=Lp`Yt-N3R2veFXLHsvhN;QicHUZ+GeLcG z+5*zb=v3gE1$Y4-3^rit3=sp;0zOO*qQnDK5$j{F&% z1d;m@cq7&&%lra6i7+?=uIB)c$1N7dqoVpMciymd4_S?2!c!z`>{pIKB$FjceUnKT z&_QkMdmln-_>KD*)XCrG1`0qaC(+=Rt=$v_3yvlf3|`}w8#&MW2nl_tj8-lvIT8K- z1d1ZCIy(L(7)KB|n2xlmIvT_A^n=yO_M#k507)8Z;FqBvRl9Y&^$^I}lDpa4iEEw+ zhN+4U$BAyQF9(ua-e=Xb4#wYna%1cuc@iRUDc}xrcSADD3>4;y9JY{CNFy0pSK}0_ z-bTQcR&wwF+F5uVvsv36dgG?{=>e`+^%ewhh0`l^C&!fD8-Xk*7lp_>7lftVtQBc< zURgMfmdd(!Zbm_9H0ox(Wc>>O{;;H50$taY1O^w$|#A!6>)WzK~ zKMrtexQ`#jWb@le+|Wjc14G+rS!^8F-X_wpHZa02!|rlp+tk~zOYVQR<7^OJ_jywg zeCCM>NgO`QQ2(HPqp}OfY62`gw<|)XjARRb-Gk57SFO$P2d53s5XvcU=IBS4 zl%5HmPKJ##?%JJiatqA>BBos1Z1e)FHL9%DZypVOD3VeYXJy;45W9!Tm-q>=gtn`p zA$2;G8eXD@BhPGM>c(2 zB2rWRKk~R>-+a8IbW+x8cPDIuXiZA>njs&ZzGKh%Z6Tlt4}H$35>u38)ge9TU)7N< zxV-Zr&&1_)qVmvXE`cU5flu2ZCz3~RK7=TyLp0(6js!`lvpm6zj*Z1`Tq3iZ2yOHt zO?a-n^xTsWvhzs(VYs9T3rLz==dLA9vW0#(D7z9WrK#KU+63!n=g|boP)rC|Q(S+5 zsm@&7-e^f;&0acaTQX9FK+>X-c1WEf^}%mWCKTP&npT1x66LO-@mtT^?L#maBg`-h z;A-T5n=NgFO@8gfxg za!X05`*q~1_!XN@ZKw(I??h|WwtBh%4^sj35oz)eN+RBShp<2SALWM^k$-rYw$?c) zWy~|YYo>f#Hr}Ncwb@HG=kh)uKHfbilLbTRooBi^!>kt(bK)stI+H2g8S#VS@vUpj z&Nts2x)qhHX>?MhSixLsKM&|4G!3*NCTfwArXV@PvkGaD(XTcD1;xA6ULvW7B}$m%Dx(2;HHC)0wwpsH8rl}%I7M&Vd22F4`{+AHIc1i?AC07RSaBpZA( zx%f)OZgrIiCcJX1Z$(**hfU#w0q^3=a`J1cETSKchgim-2uUYBS@(nUKP>((kaf~9 z4>`d9;Oo-Y`c1?;8Jx2CDW43i2wAn!l|N?9>V=m0 zt2UJOy<=Tp3E&)J2=^|1r0PEvl@uu&yfKPlfckDnBi?M-e$*%(d}tJkz5jnE0x@l zNP=KtJj3vnD#Dm+XhRkJ(KD3%eI$#c57X}2+i61-&XWQbhIvr^n@54=NFBQG1T06= zLMJ7pO|YQWB8PDlKwM!y#BPM1d^=-x)gzWJu!X#H=3r`;h^K6}`Z{)~7$ZVF?R#7G zIr+F=06)t5@*Amy6{EWr%FK4gH2CsgSKIrXEa=I@1xe?^PT7X-LR-V;^etfH0(m}` zuk}|#2ShHcvZX!(1(F}r4JLQ>poT}M!rrn)c|dq)PS74I@F;`92(>Udm}y@zML>9! zxGR)u%mz{F%ZZ;~r*3$dn(Gk#g=Q*HFVtZFAE6(NMo@-h{9ST{_3x4+_0R{2ac2|I zDd@RPo~-SfUc_9{dFi@5ON_`_S9FnreOQbEiDmN1hE|5!sLfz->@yk4QAkF@wb^Ke zFv@g|WG7G4kp4@|jAf#^LQt0d$vCU%3jgqiCI?;!sKeF3>u~-^e^yAPqL?3RR zAU%G2KHtwmYl0&o(VZXit;m7a>S~}xnjJR+Tj1scb8qv`tmkIzmssE8-E?Yf9G%FI z0gA=)mLN2X-FCx(BdrF?8Wi2JD(IgYN@{y5V%l*sPbwWlB)D2Ht6-RjYRMDVfjj}6 zMh;phOuJgXRv(y_Bgdb#yS@l@zEN(C^eiibv7Obj_ee;6hIiSCrRECb3$EyPxg`G_ zBQE!Wf6=pS=Sabb$0*@yzDyBHZ;kw8AL*-6{iN*5dQndz?ipB2%XLV8ZirYAInOxx z(=ko}L6YdHDpl!_M7wTy2A%VJ8`34Y4Z%9?hqs+?KGI70%VeZv_5KtAft%!!jHIe5 zGxDLHxDHPPCBc8U8l_WzIPwPYDZk3@Ak27d79D^Pe_M^jbT*!n?w2~~xb1|Oo}BV$ z`TS)w+RohyydgaLVQvEY${>`;C`8eE`q*{<6KP=%aSjgIX`{t68d3FP{;NA_b^$Bd zi9m3XID{6|nKnCR&8BZd!F$kpbUXr(tcEWi?;B<-z;xrI4|Cjy9x>Ntj!c~OVs~Ve z9B`{@d=v#iJhT&5P!in;{rMhRTa)F(3L10NkH7Sf-aTsKG$8HnkM3vlAPUzx=8^|P zUVV-Y%&89kqQc!pNlOoWK#n$m(0;qEp%hD$DN@HYpu@O7oVzKB?;_Q5ihl60v%2)>$fYv`il#zl*rrb&W$Ug6j43YMk2eD zH{>`szdG&I@3Im9M;NZzs2%M?&e9F{Z?p~kt^}FKx&-`%`WRd5v{bQ=br#08#Yk+Z zz5~%%MsuR1>*Ck$^{$O2@?pB2+*>w-Vcg468*vKiFORwsCSOrYZ1E5{1%n-E0#g)~ zDSO7OPoyKVHRp_odiV}h^+Dj{cryLOy5W2!(mOhBZK|)K;`C_-QTod=%u3>{@I56MfzAzVDG&Y9CRcJXwtIRO2e;rULNL%gIFs1YV zNoFL#%Gu(!V1=Og3O^Io{H1E!i12OPJie%1m34yaIGk@a`sCRnW_7~1V}UWEv$0^M zf`JMFu+%;@5e&A!T?Xh5#%HV;eBu&Kiqq}id^ie0!Pd$^*oVp#VyPs4d5~2uRR3Uy z*pLT7E2{_kwp}Kfrwa=q#qD)W)6qVJ zd{mk0f5N;%{?l^XqLqiQA2TuF0@c*&GLC7?(#}n%Z~T1xWz$Y0WeV-#qda!obps9> z%>Gu~Nbyzgz`=`ogYA=mIabj=vD^Iuj`-iT5z7>@w7tQkhOkc+y$9_A7ZE#Uyaz@%FR+%d z(y?{nbC8#rX(u=1=!)gXeap$y^_4q(*m@Ld6M9Y5w%A}DooRP-s$GKS)`wQ4Zd_VR z7d%_nOu69rZv6c(fQN}0YWuz<^>!j%_yb$HpuQr4QF>K~6o$F2Sn*;|Th>!w68uPH zperJ$?UC+I9X;D{S!l>Bx)?rE6w1*pwAH}GS|_!*Tm96n<`k0@WJ+mL)$`nRzr=)< z0%y*ou!`ja(GCOMNoZ}1rB1T}3nx3qf}U2h%E@@~ZRb>|D(VYmVsZ?^-djkvLON=^N$O0s#5wkwAh@q zbcLT3Zxy@Dc{j$dgtQyNN@@(#$ELq%t}xaOxXV(%t&>VTPK3r>S7f>Eq13 zEuY#NRh{I;_WLTca!qweJ&rVNv zKAmfdj0%Xy-(F4b9;?oiWRNeR*O^0GvBWbXNVQ!YV zvJIM;4BB`VLJvyxMI{nrulC)sh`vQMSW6L}DG;xl+DYeTu{idTBKh63(tY@-K7Is=pOL0QZvr&;pb~o#a&+dL<41SDxWr@PN#*>wJun*np(JU;nJnXv;vM4&srw8Wgfx~9VJDUYIx!R7vlE**XR(nie}Ik z<}j#rSkQgvQEJ{PQ9vd)TDt)y60V5!BacQ*JK*)X@&d8A_F}%J@Mfnqr8SsJAN|w2 z@x6EBC4)Zn2%kk3Z*y=s*fnd77uLy>BO_wZkD0+i$6TIf(Ybs5Pe^{oc2gVBk-%cZ zO;)hs7|tHqXa5xU)D<#vWCk+La>Lfo4Hwzs*z>*=F{eU#omzSDR-99jCduT%7zwNJ z;SA)Y5>cw}K&3ll2~N-`h@*jxX&cJJ`~6R9YJ}Zrk%1w*-~~u!ToqiTf}4GA3*$vI z!3Hh6s6WXg1T?PNdH+Sg9q2iBiOyEk0tAQ#gICbNw6UkxwZFRW7x+U)iY8Ql)`rDR z^@wKD6Zi`}m%Smce*sB=00V>?psnrpfNrGmq|dF1kA|vEk-MW5*$Gq-pkL@;qdC4hnk0XJLRmh7f3*{HUSt1uWfqA z&W)XfOLtxAh`h0JGe6=2@*_5b2&63Zfp_Sl?e=9{!M(iNnH@Xxs&|ddvsn5tBxXj& zc@Ue*?}nWvQast~@;1EyTmN9BvlsQle2kkMFCU+NEeLlJKy#>N0QJJ@^==A@8S(D6xSis^T8IKs*dbA>+3UNvO8_ zz+u&-0rF$^tceaBRNe=JM-@j`0AYSX7=?5NgdUiqPv%?{qY6%znwo%>hW2LqC75Mr zv2!Wbi4vVkg`ku9YSOxlJmt#M0KKXXxG@7)rT3A7D4Lm5Ls#FJ_b|Frc|{72nzt!n zh-kd*bowNvIPE(nWAG>8GsrY?*jMv#8`3?eeeiZKL+4`WSHW?z<`gqHEYctbOcI;1 zg@DwpKx%}ZQ-u&X(t}0w&qz-^gz^jqHoO~v0EzJ7*{BBS75dY*MF9NPO{H4(te|HC zX3q7YMI?iEMdpOaf82$^vYe(Q=Nk(}k6I|n^gR&Kqr`VqNY!+pqrL#Azk%0r!yG4| z?A)-M#^}{VS>d6!E&tVBYLoeKlNc8>(!|0`pwnRyu$^3nTHt?%$IMV~KB2`QAM8UA zwS}r~{`~ct0fyD01J`A3kWnaD($YxA*^p z*O@x4$2)83n-3>ummdV85wDyR;HTMWf`{+0gCjW2d0q#;^&Vu{1hy4%h_V!lXqIBV zq;IkPcYtJ(0u~#Lc>?n4&d?O3Z!vp%G$LN=vV9>v4>ICpWKlQxkk!c-LMltLM#6Gd*aYD0v z;N3bjsT9hY^@q{Xm3948{kz~>z}U|Q9_3=_u&J_p?tKchMjnic`tWXB>#@^ z!Xw;@V@rAm*<^99&dJgpZM`S`50 zBYWv@X^*jecL))UTNKZO=v8}W>_^OTx%9K{ALecZ7%)@mAz$N7)vRmEQek-S4p^8( zJNfoGFUZBh;o#2rp%?f@q>RnoPhxJxVM0_y;IsABn?VP%x4fqVNN3gpXc6`{6k6WG6~1u~&ey>p#Exb+4@$QlQ@7lh~E!AK|K?9McO z)0ag2K&s6;7!`{IyM&sBRNFn$QsDTjuVWoBQ7>i&;J)A=#~WL#J=`z#P|CxD*&$`D z!qQzxn(iZ_&@~xOd#}UFBCl9|NHb{Wf%g2KE(*KBv=EntuX{htmjNZL{>C zyoF%^bRTB?U6cHY4dnS2q(v8ToYQ(_-eV3tF@I776oc6<~R_;W+M9e@{$)8|Z zChqqbtwLj>{*;rRD9{8Xf~>@apK6dd+E^xpU%4UiAL#FeYpgputfZx~grErabRoD5 zEGoZ#HWhPH;a(A8gd&hUCl~Q@>2GM~Uu8nJYg@`q(?X48??SIuw-Eh9$6=0_Mli|vZzwWC* z;Ta8h6m?$mjOg9Imw~ef6r3HoFmBro=^Og+Oj`{2=A>%QgUBnUzz8{<7bccPT8%Lb zl3a)5q3;+PYyXkjnY{OQ5Z&_Ko0=I$4gCah<7cj^{wZu{=7NovIHJXQ@^uq|s={5> zc0`c+#(bDdcQej?6^e?npYQu9KS9&59%;i3Lf^q8m!8tLtAOx=U)n@vY;+>_bD#Ub zx|u~mhq?T(^v)j+A$u-8mwFVJnuv!BiN`#S*nPiDLS3}1B5mA(sHPN8K4D2}L9)s# zb{PW=2WehFbrbZh8Y>Sv_h_!;Y*`Qsz0kcwR6(9)3_H3J3zeY2cp(~OxsZ&^V#mPK zGz^5zcyzt$;vc|WImx?eVTh3%=0A^g=t;m0@3TR?XwJvGqA?_3AwY<_Pb8M8*;DnH zBP;4h`B*8a)^jw-vcmx8oPHju9v&!k5>hV2jIhLp0xzx3Si6cTdz%(S{HH<4u$Hd# zPlAWbNv_}`S^wG86Y#9^H;5+-kK+ndhhtbHmaR#5(o;{dqlJ{t=5rn_1@{&LL1F3D zTKAe`Le&$G!2cw7LJdo+TXgMFw|lx=GM{-R+IL?ogdDVqk)FdHLOm;SNqXm&zTkR^ zDa~%!4L(D3kj44)jg%m{$)qo$MvT`oL)t2?%TIXg8R|CRPY9!?2@(#h%~JH(ZK0iJ@kozpNSz!RSQY@<(T!Ff?k^a{;)Qu3BRAxo%bEej)rj79VhiqHh`Dgx=C@dB=h*x$_ zzmT2>1aEBFbLs3mD}C#hLm8qS{#TRieMb?swyi+QuTQU zHa-r)*}Vq)Lb11!!>$qx1cq1eNU<1xCMOu9tVXnz%%J}$G8|Z0IerXW2*Yy!suF4l z6T-ClD(Cv`djL|OUy9=pI;}TG)rvJj)i4>Ex$t?uEYAwYM*{t9PLrVIQ3d)y+J6@Y zF{iUMS4N2}(qR+uxamON9K-U}LKs=>Xvh9I=7QJ99P8?Kf?cUrRcBUcHJx@-LdFu9 zWrMjOjfTM>9NUB2xR`eQ&2hrnYTA4|+7D>wr%*=keQO&2NbJ9^r!gjSW{xU2>HcX2 zadHcR(hI}U!a%2wYcvXG_~JY6TtB^#&5sI5*VtyR(J#a&ym{M{n{x6WAs;3@pD|L| z5)KxGz&q*|?V`D^$@ln;U=XeV*RfVaBr+^lo?B2O-Vi1%s~0MkW=4^x0cqv76j$DA z5Xg9te$f^QO^++RD~)l;9AxdGXe*uqp#)7Y9LrmUuRCMa=xa__&i9NBbr0xOt)@bX zy^ezxql;4k&Gx4Ys3t{-o=Wwjbf|xSjL1Nr_(Bno{GaxcPU63Hv~~-6DF`-)>m86J z7Q|iBGadmt+lK&R48m;SC{-FU>=%N;P1C@P99*01b0mj7JC3#;GN#5I(uB7QQwkw$ zXTGC#5>nRpogR?gx`uoD%3Kbf>?n4gZ&$^9HISkl#e(hr!lHJx2WJc3Q*NXjP>v>3 z1wW5RYKamc)$1fYtMaaU;^EvoewH2OSB@+daTJs~@do$!;#KWgjeW(bYES{eH`emE zhlu&IzD2}C3RJP$En4gdPX4EH@sJ-h`-JR)R_%?iX8Dl?-dT0QbMJkT9SNknt0WF| z7R=Nmql|J(Q|}7WtpZ}E$5sSy(k}*$6dFn7MGY}}Sl!E;`Bi0mV+*gc9YU_FY$*}k zP2*9Ns)8j&S%GhPHb$pQA{-enGBF8rlKk*E8Moz*giB}9Q)e*Wmy{EmZHP7)ZtI^9 zm|k8$yAG)~heyXWF*Is86ekf=u`VE=JkLbDQ9B7j@@?sX^Qq|_fV8AjXv`P}gxKkz zbLUOF){u<`{j7`5N3>F>Mil5gPsUHViAI|!&4s6i7r4MNTTaez1);d>ZX z)B;H|8I3yW*&kcZjmS_CnAH4`W7;RuKZLR}GxlvYO)5s^D>-NF(}#&%FlmaApO_>O z6uunz8*#Um!-qSFzfk{;1Nr5A5s^qH-(;-P(qu?27kd4LoauRD{E6@KGdcrFkJau) z+!7`XOz*54$;%qV&6|`BOGD5_p^y2mDEgl?|7Hi`&3ZA}Np7BMBrZ%y&7{qA@4_`y zqS7K^;JXo?=eTkzfNR6fwC$zIlh9ErEe?$!8mOb(xK>99hggUOXn!FkN=omL9zT~} zNQMRwPq**{Nl>togk(7=&H_Q3q9`%9>E4Jr8TT-fcMo@L>6Q>6^zArqjdV8Imn*w} z1c$RBTb9kbCq8yV=4%+bq9sxfx+`DEpXg5u;kpF)oMNj>x8qWs+} zmp3@OZ}a-i`8i?2NG+LP@WKtyL>)?ojVYrU{0XKlj6cjd&+&@~#eD33j@yf?6kd@@ zEi)^54}Pot;aL~TYl{B-xv`IAYCmGVtA0GBz>z@lDxsLo3GU`%iLK@baipuZyswU` za8nS1F&!T@R76PZ%>yXo*`1}o@u2OSf&YjH>4Ciomj~rm(08UFE0SWsrr#atO@+nP zqOOA-KD%OlOhzW?jS)}w&Bqtwo0)|f*^nBWFAGyI>^B)@Lo_373~XyDx8@5$4ZXXe z=1z@>duf@y$9U0xU0*Bgzpk%|dFylP^*Vb9uKo#9z8!%~z&Xl%1=ph~`{s3HTCnRC zef-cww`tFr1ud+@0w!ZVYl71I7poQA;!!=pJV{JBndS1kUwPW^NuuOQ`CWR z7UpHumiVgrs_q5J#G$`ZYRy4gr*)Ox!Paxj&~GC9?T3f&kG{wL3bZX7{uO8=nH{>7 zIjl-8Zk6X!<1|s(cj(EV%Qzx6K#!<|!L9y6yJlbl%-iXy?&zOwd|F=XvOVMYb}!VQ z-(JemQ4Z%G7#(y?R)4oAWf`+ijU{K<^(qEnLu*(~d1^WK915TozSmt>wyi@?B<3s} zoy>!=ts-wBJ@uJuGU2g5D zePiTk15!g$Dh_$C77ai5U=ffOBXzQYKdJQap!nQRw!GbTCj!L>o+8Kvv5GrlvXmmrKBdB;C!L>C=S zwCq5_dIZ^4YX3iLgPiMnfu+X4tzQ!v|5fFNQ8n9iNU}ITY#_Y-RzQ@@pw?WHBTJHH zfsgIFbYasqOf&~i3M=Cmyc>=yJ;CJ9^SL@B7bL3LZLP8#9uW!tVBH$$l4GEMUuO&R zP}ckL$C{O`IPR5&Yr=@)cCC?Pv^Wxpuc^cu>IFl{*EYeI{eO1TC~9J*bMxLUg%o?3 zjmapWF5o)^hN`Mj44>dc;iP4^`NnN%scF65&QT2N=8Jw0vwiL-$u6pvaVr0%KcCvk zV8t}GX5>>$RO8HpCsAu}d&P09t-)rEKGMCC5MRtUOF!sBFD`=aQdrC*IkCPBWv1S$ z(A#jm_6(V-N=3S98C2sQ^@;!Va;s13#bcsJ@gD0>kd%_1y({d}(Mf&$@piDqb1mla zx66Uj{CMEI$pPKTc5@6BgKII(FR&nXl{qK_5p0Ddca4i%7XJTsnEZh!sS^^ee5D zOV=qE0;Id=vzN%Cc*xMVhWrD(ydcoduo*b^CcT|dR#kAPMoX|LGOJkct&@~9=|Dmm zR(uxg{Tm%6o*!iO5YDO1(w1DG2rZzvy@*re>x#Gno7iZ~vjYQq2obh6ZxK;=uB7PW z(5ZueYp3x$0f9GztU(M)>H}^Iam55xEFYOvY1*?dMukP(VM3p<)+$JZL{uGmc)Qj| zIlrsyGZ2u3-tnw#AfMj4Pq?a!$eZ_DAq^UX4?{4u+VhTK_&N^X=-yecMbbY+ulNvw z@Num>Tj$W9jJLvxCL^8IZjb$mKXu8aY*@Wq2dqycGi%EmYFxldb{Zq;vOL3!}5r*XR z@?;^TVe!uFa?InEkli*^WQt&V39P;!g@Ny!o?w4sDMozuRI7@@Zhrk>_=y|j&F7Sh zi>6QKL<8ed3p!uSRSsC>mmXsHfqoj@n>&(&Zus?J#^ruJd&K8E8yrzah*7uzs7vHS zMA4beJygpf`6HV{_LVf zMHk|9&>lA^6u4pWaQGNGri=7Hd870Uq9nSMOSeu-Dd$QUW|FcI@;}39I5yw^45KBN z*{&m>7%%lDi%!(H**lliJvrk&h=IYR-p3EV$=$gSFp0*awu`N_V-!D;DaLOR#ah-l zoC&slk8&q)8!2Ym(lAU{}-)YOHIw&}6eROPOR{Z_1Y zX_32Fj>>46YWm-x5l2@FL{mqAWa+A65pp81Bxk90v8A^j{;K6hJ|k4}1SxY-k&w~4`O*Uw_I^xBd(i`9M zh|g#|3ZPn_D=543sc=OHYs$+ZmmbF$Pc~AFCmpVSa|OKzG5`y=@fe^CdnzU$oc@hA zSRaiBDCE=+7r2GU!ysm@(E+NB;|IoI9~hXT?XowJx$w;}Gt+KP&a1cD&BF_69JHX2 z_V}+tn$284uj$^F-~JK)E_z194=Fh^T0GO(o%};3R6V_4T8Y|p=k}eVmBJ23Q(hVL17>KPa~E;Q zwMYoSfe)J#Iyv5QD>rlJ?y80BP5emNXY8T{W)15@~GdKRNR+FKSKuWvLtk zP523Za|O9vDaDzAO*{|4&G3-&v(f1~5+gnGS#iNaTH|cfYep zWEt1q%|7}IS3u~ka$JywyhqeR(LiL97~*+I#`E?)O*gE~=_?(p#<({RljQTBzXHfj z4=T0|u<0rHlBHZq=6Vg^Ko;U^{}o0#H_dG=KdD{aF$W1Oa!y?;MPD2d&k$Fqx|j4FX!x7MLl_5K*2N#-+bM(b&3?Qpnr z2)oOd%Cz^|IRxA6sa@Rt(>bK7XzHYBdhHxqViU!}N}aMN5wI#%>6Ab~2KN~MltsJ9 zUDOo*Us<$5)x}eqf*hgZxB)a5FQuq2QYlxHw{90SP2m{``0jG>| zv9<+3HS}dvVC}n!FAo1jHitpe^v}2qxYCHim~KMYvHifQ{2JVu<#hnQA)&5`kLIa- z+bGC&mm-oTN{qi?Aww(PS8J#5Y?F0=y%?B;NTwAm+n0^6d8PlR33|Tv>HlC7`akhY b^FI+Ie(1zcrai7h0RJ96P?9N?GW7p{s90*( literal 0 HcmV?d00001 diff --git a/examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_write.png b/examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_write.png new file mode 100644 index 0000000000000000000000000000000000000000..83b0413f6627d8529212911d5e4d0a6dbfc56b0e GIT binary patch literal 131862 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 P;nQ? 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

kf5|yR$meVdgHLD+sJmHPj@ZLh9O?3l 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 z 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 threadCoyote driverDRAMCoyote threadCoyote driverDRAMvFPGAvFPGARDMARDMAFPGAFPGASwitchMeta-Exchange:ip_addr, QPN, PSN, rkey, vaddr, sizeTCP / IPRoCE v2122QP-SetUp: QPN, vaddr34OUTGOING: Command & DataREMOTEWRITEdata={1, 2, 3, …}INCOMING: Command & DataLOCAL WRITEdata={1, 2, 3, …}5INCOMING: CompletionDataHeader \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/img/rdma_traffic_pattern.svg b/examples/MT_pomsarc_10_single_sided_writes/img/rdma_traffic_pattern.svg new file mode 100644 index 000000000..cc7223ad0 --- /dev/null +++ b/examples/MT_pomsarc_10_single_sided_writes/img/rdma_traffic_pattern.svg @@ -0,0 +1 @@ +LATENCYTHROUGHPUTRDMA WRITERDMA READCPUFPGACPUFPGACPUFPGACPUFPGACPUFPGACPUFPGACPUFPGACPUFPGARDMA WRITEACKRDMA WRITEACKRDMA WRITERDMA WRITERDMA WRITEACKACKRDMA WRITERDMA WRITEREAD REQUESTREAD RESPONSEREAD REQUESTREAD REQUESTREAD REQUESTREAD RESPONSEREAD RESPONSEREAD RESPONSEREAD REQUESTREAD RESPONSE \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/CMakeLists.txt b/examples/MT_pomsarc_10_single_sided_writes/sw/CMakeLists.txt new file mode 100644 index 000000000..c3e977433 --- /dev/null +++ b/examples/MT_pomsarc_10_single_sided_writes/sw/CMakeLists.txt @@ -0,0 +1,26 @@ +# CMake configuration +cmake_minimum_required(VERSION 3.5) +project(example_08_perf_rdma) + +set(CYT_DIR ${CMAKE_SOURCE_DIR}/../../../) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CYT_DIR}/cmake) +find_package(CoyoteSW REQUIRED) + +# Add source files +set(INSTANCE "client" CACHE STRING "RDMA software build targe: client or server") +if(INSTANCE STREQUAL "server") + set(TARGET_DIR "${CMAKE_SOURCE_DIR}/src/server") + message("*** Coyote Example 8: RDMA Server [Software Build] ***") + include_directories("${CMAKE_SOURCE_DIR}/src/include") +endif() +if(INSTANCE STREQUAL "client") + set(TARGET_DIR "${CMAKE_SOURCE_DIR}/src/client") + message("*** Coyote Example 8: RDMA Client [Software Build] ***") + include_directories("${CMAKE_SOURCE_DIR}/src/include") +endif() + +# Create build targets and link against required libraries +set(EXEC test) +add_executable(${EXEC} ${TARGET_DIR}/main.cpp) +target_link_libraries(${EXEC} PUBLIC Coyote) +target_link_directories(${EXEC} PUBLIC /usr/local/lib) diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/src/client/main.cpp b/examples/MT_pomsarc_10_single_sided_writes/sw/src/client/main.cpp new file mode 100644 index 000000000..8bdb04fdc --- /dev/null +++ b/examples/MT_pomsarc_10_single_sided_writes/sw/src/client/main.cpp @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2021-2024, Systems Group, ETH Zurich + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. + */ + +#include +#include +#include + +// External library for easier parsing of CLI arguments by the executable +#include + +// Coyote-specific includes +#include "cBench.hpp" +#include "cThread.hpp" +#include "constants.hpp" + +constexpr bool const IS_CLIENT = true; + +double run_bench( + coyote::cThread &coyote_thread, coyote::sgEntry &sg, + int *mem, uint transfers, uint n_runs, bool operation +) { + // When writing, the server asserts the written payload is correct (which the client sets) + // When reading, the client asserts the read payload is correct (which the server sets) + for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { + mem[i] = operation ? i : 0; + } + + // Before every benchmark, clear previous completion flags and sync with server + // Sync is in a way equivalent to MPI_Barrier() + auto prep_fn = [&]() { + coyote_thread.clearCompleted(); + coyote_thread.connSync(IS_CLIENT); + }; + + /* Benchmark function; as eplained in the README + * For RDMA_WRITEs, the client writes multiple times to the server and then the server writes the same content back + * For RDMA READs, the client reads from the server multiple times + * In boths cases, that means there will be n_transfers completed writes to local memory (LOCAL_WRITE) + */ + coyote::CoyoteOper coyote_operation = operation ? coyote::CoyoteOper::REMOTE_RDMA_WRITE : coyote::CoyoteOper::REMOTE_RDMA_READ; + auto bench_fn = [&]() { + for (int i = 0; i < transfers; i++) { + coyote_thread.invoke(coyote_operation, &sg); + } + /* + while (coyote_thread.checkCompleted(coyote::CoyoteOper::LOCAL_WRITE) != transfers) {} + */ + }; + + // Execute benchmark + coyote::cBench bench(n_runs, 0); + bench.execute(bench_fn, prep_fn); + + // Functional correctness check + /* + if (!operation) { + for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { + assert(mem[i] == i); + } + }*/ + + // For writes, divide by 2, since that is sent two ways (from client to server and then from server to client) + // Reads are one way, so no need to scale + return bench.getAvg(); +} + +int main(int argc, char *argv[]) { + // CLI arguments + bool operation; + std::string server_ip; + unsigned int min_size, max_size, n_runs; + + boost::program_options::options_description runtime_options("Coyote Perf RDMA Options"); + runtime_options.add_options() + ("ip_address,i", boost::program_options::value(&server_ip), "Server's IP address") + ("operation,o", boost::program_options::value(&operation)->default_value(false), "Benchmark operation: READ(0) or WRITE(1)") + ("runs,r", boost::program_options::value(&n_runs)->default_value(10), "Number of times to repeat the test") + ("min_size,x", boost::program_options::value(&min_size)->default_value(64), "Starting (minimum) transfer size") + ("max_size,X", boost::program_options::value(&max_size)->default_value(1 * 1024 * 1024), "Ending (maximum) transfer size"); + boost::program_options::variables_map command_line_arguments; + boost::program_options::store(boost::program_options::parse_command_line(argc, argv, runtime_options), command_line_arguments); + boost::program_options::notify(command_line_arguments); + + PR_HEADER("CLI PARAMETERS:"); + std::cout << "SINGLE SIDED WRITE MT_ex 10: " << n_runs << std::endl; + std::cout << "Server's TCP address: " << server_ip << std::endl; + std::cout << "Benchmark operation: " << (operation ? "WRITE" : "READ") << std::endl; + std::cout << "Number of test runs: " << n_runs << std::endl; + std::cout << "Starting transfer size: " << min_size << std::endl; + std::cout << "Ending transfer size: " << max_size << std::endl << std::endl; + + /* Coyote completely abstracts the complexity behind exchanging QPs and setting up an RDMA connection + * Instead, given a cThread, the target RDMA buffer size and the remote server's TCP address, + * One can use the function initRDMA, which will allocate the buffer and + * Exchange the necessary information with the server; the server calls the equivalent function but without the IP address + */ + coyote::cThread coyote_thread(DEFAULT_VFPGA_ID, getpid(), 0); + int *mem = (int *) coyote_thread.initRDMA(max_size, coyote::defPort, server_ip.c_str()); + if (!mem) { throw std::runtime_error("Could not allocate memory; exiting..."); } + + // Benchmark sweep of latency and throughput + PR_HEADER("RDMA BENCHMARK: CLIENT"); + unsigned int curr_size = min_size; + while(curr_size <= max_size) { + std::cout << "Size: " << std::setw(8) << curr_size << "; "; + + coyote::sgEntry sg; + sg.rdma = { .len = curr_size }; + + double throughput_time = run_bench(coyote_thread, sg, mem, N_THROUGHPUT_REPS, n_runs, operation); + double throughput = ((double) N_THROUGHPUT_REPS * (double) curr_size) / (1024.0 * 1024.0 * throughput_time * 1e-9); + std::cout << "Average throughput: " << std::setw(8) << throughput << " MB/s; "; + + double latency_time = run_bench(coyote_thread, sg, mem, N_LATENCY_REPS, n_runs, operation); + std::cout << "Average latency: " << std::setw(8) << latency_time / 1e3 << " us" << std::endl; + + curr_size *= 2; + } + + // Final sync and exit + coyote_thread.connSync(IS_CLIENT); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp b/examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp new file mode 100644 index 000000000..225739d5f --- /dev/null +++ b/examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp @@ -0,0 +1,9 @@ +// Constants shared by both client and server; +// Placed in separate file to avoid any weird changes in one file not reflect in the other + +// Benchmark parameters +#define N_LATENCY_REPS 1 +#define N_THROUGHPUT_REPS 64 + +// Default vFPGA to assign cThreads to; for designs with one region (vFPGA) this is the only possible value +#define DEFAULT_VFPGA_ID 0 \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/src/server/main.cpp b/examples/MT_pomsarc_10_single_sided_writes/sw/src/server/main.cpp new file mode 100644 index 000000000..de55dbf72 --- /dev/null +++ b/examples/MT_pomsarc_10_single_sided_writes/sw/src/server/main.cpp @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2021-2024, Systems Group, ETH Zurich + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. + */ + +#include +#include +#include + +// External library for easier parsing of CLI arguments by the executable +#include + +// Coyote-specific includes +#include "cThread.hpp" +#include "constants.hpp" + +constexpr bool const IS_CLIENT = false; + +void run_bench( + coyote::cThread &coyote_thread, coyote::sgEntry &sg, + int *mem, uint transfers, uint n_runs, bool operation +) { + // When writing, the server asserts the written payload is correct (which the client sets) + // When reading, the client asserts the read payload is correct (which the server sets) + for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { + mem[i] = operation ? 0 : i; + } + + for (int i = 0; i < n_runs; i++) { + // Clear previous completion flags and sync with client + coyote_thread.clearCompleted(); + coyote_thread.connSync(IS_CLIENT); + + // For writes, wait until client has written the targer number of messages; then write them back + if (operation) { + while (coyote_thread.checkCompleted(coyote::CoyoteOper::LOCAL_WRITE) != transfers) {} + + /* + for (int i = 0; i < transfers; i++) { + coyote_thread.invoke(coyote::CoyoteOper::REMOTE_RDMA_WRITE, &sg); + }*/ + // For reads, the server is completely passive + } else { + + } + } + + // Functional correctness check + if (operation) { + for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { + assert(mem[i] == i); + } + } +} + +int main(int argc, char *argv[]) { + // CLI arguments + bool operation; + unsigned int min_size, max_size, n_runs; + + boost::program_options::options_description runtime_options("Coyote Perf RDMA Options"); + runtime_options.add_options() + ("operation,o", boost::program_options::value(&operation)->default_value(false), "Benchmark operation: READ(0) or WRITE(1)") + ("runs,r", boost::program_options::value(&n_runs)->default_value(10), "Number of times to repeat the test") + ("min_size,x", boost::program_options::value(&min_size)->default_value(64), "Starting (minimum) transfer size") + ("max_size,X", boost::program_options::value(&max_size)->default_value(1 * 1024 * 1024), "Ending (maximum) transfer size"); + boost::program_options::variables_map command_line_arguments; + boost::program_options::store(boost::program_options::parse_command_line(argc, argv, runtime_options), command_line_arguments); + boost::program_options::notify(command_line_arguments); + + PR_HEADER("CLI PARAMETERS:"); + std::cout << "SINGLE SIDED WRITE MT_ex 10: " << n_runs << std::endl; + std::cout << "Benchmark operation: " << (operation ? "WRITE" : "READ") << std::endl; + std::cout << "Number of test runs: " << n_runs << std::endl; + std::cout << "Starting transfer size: " << min_size << std::endl; + std::cout << "Ending transfer size: " << max_size << std::endl << std::endl; + + // Allocate Coyothe threa and set-up RDMA connections, buffer etc. + // initRDMA is explained in more detail in client/main.cpp + coyote::cThread coyote_thread(DEFAULT_VFPGA_ID, getpid(), 0); + int *mem = (int *) coyote_thread.initRDMA(max_size, coyote::defPort); + if (!mem) { throw std::runtime_error("Could not allocate memory; exiting..."); } + + // Benchmark sweep; exactly like done in the client code + PR_HEADER("RDMA BENCHMARK: SERVER"); + unsigned int curr_size = min_size; + while(curr_size <= max_size) { + coyote::sgEntry sg; + sg.rdma = { .len = curr_size }; + run_bench(coyote_thread, sg, mem, N_THROUGHPUT_REPS, n_runs, operation); + run_bench(coyote_thread, sg, mem, N_LATENCY_REPS, n_runs, operation); + curr_size *= 2; + } + + // Final sync and exit + coyote_thread.connSync(IS_CLIENT); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index a9de28667..f349db77f 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -42,26 +42,26 @@ localparam integer Max_Sa_index = 28; localparam integer Sa_fixed[0:28] = {256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139}; //logic[3:0] Sa_index; -logic[8:0] Sa_index; +logic[4:0] Sa_index; -logic[63:0] Rt; -logic[63:0] Rc; -logic[63:0] Sa; -logic[63:0] timer; +logic[15:0] Rt; +logic[15:0] Rc; +//logic[63:0] Sa; +logic[31:0] timer; -logic[63:0] timer_send_rate; +logic[31:0] timer_send_rate; -logic[63:0] time_of_last_marked_packet; -logic[63:0] time_last_update; +logic[31:0] time_of_last_marked_packet; +logic[31:0] time_last_update; -logic[63:0] byte_counter; -logic[63:0] time_counter; +logic[7:0] byte_counter; +logic[7:0] time_counter; -logic[4:0] tC_C; //timecounter counter -logic[4:0] bC_C; //bytecounter counter +logic[2:0] tC_C; //timecounter counter +logic[2:0] bC_C; //bytecounter counter logic rate_increase_event; @@ -80,7 +80,6 @@ logic z_fast_recovery; logic z_next_req_ready; //simul part -//logic[31:0] ecn_counter; logic ecn_alternator; @@ -90,7 +89,7 @@ always_ff @(posedge aclk) begin if (aresetn == 1'b0) begin Rt <= Rt_default; Rc <= Rc_default; - Sa <= S1; + //Sa <= S1; timer <= 0; timer_send_rate <= 0; @@ -126,7 +125,6 @@ always_ff @(posedge aclk) begin Sa_index <= 0; ecn_alternator <= 1; - ecn_counter <= 0; end @@ -162,22 +160,10 @@ always_ff @(posedge aclk) begin //================== byte_counter <= byte_counter + 1; - /* - if(ecn_counter >= 20) begin - ecn_counter <= 0; - ecn_alternator <= ~ecn_alternator; - end/* - else begin - ecn_counter <= ecn_counter + 1; - end*/ - if(timer % ecn_alternation_timeframe == 0) begin ecn_alternator <= ~ecn_alternator; end - - - if(ecn_mark == 1'b1 && ecn_alternator == 1) begin // Marked Packet arrived if(timer - time_last_update > N_min_time_between_ecn_marks) begin //===Debug Signal=== @@ -193,7 +179,7 @@ always_ff @(posedge aclk) begin */ //fixed size Sa - if(Rc <= 4096) begin + if(Rc <= 2048) begin Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; end if(!(Sa_index == 0)) begin @@ -235,6 +221,8 @@ always_ff @(posedge aclk) begin if(!(Sa_index == Max_Sa_index)) begin Sa_index <= Sa_index + 1; end + + time_of_last_marked_packet <= timer; end @@ -294,6 +282,13 @@ always_ff @(posedge aclk) begin end end + if(Rc < 50) begin + Rc <= 50; + end + if(Rt < 50) begin + Rt <= 50; + end + // CHANGE BACK if(timer_send_rate > Rc) begin //if(timer_send_rate > 1) begin @@ -318,13 +313,13 @@ end ila_DCQCN inst_ila_DCQCN( .clk(aclk), .probe0(timer), //32 - .probe1(Rt), //32 - .probe2(Rc), //32 - .probe3(Sa), //32 - .probe4(byte_counter), //32 - .probe5(time_counter), //32 - .probe6(tC_C), //5 - .probe7(bC_C), //5 + .probe1(Rt), //16 + .probe2(Rc), //16 + .probe3(Sa_index), //5 + .probe4(byte_counter), //8 + .probe5(time_counter), //8 + .probe6(tC_C), //3 + .probe7(bC_C), //3 .probe8(rate_increase_event), .probe9(timer_send_rate), //32 .probe10(time_last_update), //32 @@ -348,10 +343,7 @@ ila_DCQCN inst_ila_DCQCN( .probe26(z_additive_increase), .probe27(z_fast_recovery), .probe28(z_next_req_ready), - - .probe29(Sa_index), - .probe30(ecn_alternator), - .probe31(ecn_counter) + .probe29(ecn_alternator) ); diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index aed4fb44b..b517448a2 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -590,7 +590,7 @@ set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CON #set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN -set_property -dict [list CONFIG.C_PROBE0_WIDTH {64} CONFIG.C_PROBE1_WIDTH {64} CONFIG.C_PROBE2_WIDTH {64} CONFIG.C_PROBE3_WIDTH {64} CONFIG.C_PROBE4_WIDTH {64} CONFIG.C_PROBE5_WIDTH {64} CONFIG.C_PROBE6_WIDTH {5} CONFIG.C_PROBE7_WIDTH {5} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {64} CONFIG.C_PROBE10_WIDTH {64} CONFIG.C_PROBE11_WIDTH {64} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_PROBE21_WIDTH {1} CONFIG.C_PROBE22_WIDTH {1} CONFIG.C_PROBE23_WIDTH {1} CONFIG.C_PROBE24_WIDTH {1} CONFIG.C_PROBE25_WIDTH {1} CONFIG.C_PROBE26_WIDTH {1} CONFIG.C_PROBE27_WIDTH {1} CONFIG.C_PROBE28_WIDTH {1} CONFIG.C_PROBE29_WIDTH {9} CONFIG.C_PROBE30_WIDTH {1} CONFIG.C_PROBE31_WIDTH {32} CONFIG.C_NUM_OF_PROBES {32} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {16} CONFIG.C_PROBE2_WIDTH {16} CONFIG.C_PROBE3_WIDTH {5} CONFIG.C_PROBE4_WIDTH {8} CONFIG.C_PROBE5_WIDTH {8} CONFIG.C_PROBE6_WIDTH {3} CONFIG.C_PROBE7_WIDTH {3} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {32} CONFIG.C_PROBE10_WIDTH {32} CONFIG.C_PROBE11_WIDTH {32} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_PROBE21_WIDTH {1} CONFIG.C_PROBE22_WIDTH {1} CONFIG.C_PROBE23_WIDTH {1} CONFIG.C_PROBE24_WIDTH {1} CONFIG.C_PROBE25_WIDTH {1} CONFIG.C_PROBE26_WIDTH {1} CONFIG.C_PROBE27_WIDTH {1} CONFIG.C_PROBE28_WIDTH {1} CONFIG.C_PROBE29_WIDTH {9} CONFIG.C_NUM_OF_PROBES {30} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_flowcontrol set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] From ce9db7d9fce1eb60ba114a4aa2ac562be49ece93 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Tue, 16 Sep 2025 12:09:31 +0200 Subject: [PATCH 33/52] ex10 changed --- .../sw/src/client/main.cpp | 12 ++++++------ .../sw/src/include/constants.hpp | 4 ++-- .../sw/src/server/main.cpp | 9 +++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/src/client/main.cpp b/examples/MT_pomsarc_10_single_sided_writes/sw/src/client/main.cpp index 8bdb04fdc..09e4f9344 100644 --- a/examples/MT_pomsarc_10_single_sided_writes/sw/src/client/main.cpp +++ b/examples/MT_pomsarc_10_single_sided_writes/sw/src/client/main.cpp @@ -124,8 +124,8 @@ int main(int argc, char *argv[]) { // Benchmark sweep of latency and throughput PR_HEADER("RDMA BENCHMARK: CLIENT"); - unsigned int curr_size = min_size; - while(curr_size <= max_size) { + unsigned int curr_size = 4096; + //while(curr_size <= max_size) { std::cout << "Size: " << std::setw(8) << curr_size << "; "; coyote::sgEntry sg; @@ -135,11 +135,11 @@ int main(int argc, char *argv[]) { double throughput = ((double) N_THROUGHPUT_REPS * (double) curr_size) / (1024.0 * 1024.0 * throughput_time * 1e-9); std::cout << "Average throughput: " << std::setw(8) << throughput << " MB/s; "; - double latency_time = run_bench(coyote_thread, sg, mem, N_LATENCY_REPS, n_runs, operation); - std::cout << "Average latency: " << std::setw(8) << latency_time / 1e3 << " us" << std::endl; + //double latency_time = run_bench(coyote_thread, sg, mem, N_LATENCY_REPS, n_runs, operation); + //std::cout << "Average latency: " << std::setw(8) << latency_time / 1e3 << " us" << std::endl; + std::cout << std::endl; - curr_size *= 2; - } + //} // Final sync and exit coyote_thread.connSync(IS_CLIENT); diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp b/examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp index 225739d5f..2b753a69a 100644 --- a/examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp +++ b/examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp @@ -2,8 +2,8 @@ // Placed in separate file to avoid any weird changes in one file not reflect in the other // Benchmark parameters -#define N_LATENCY_REPS 1 -#define N_THROUGHPUT_REPS 64 +#define N_LATENCY_REPS 1000 +#define N_THROUGHPUT_REPS 1000 // Default vFPGA to assign cThreads to; for designs with one region (vFPGA) this is the only possible value #define DEFAULT_VFPGA_ID 0 \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/src/server/main.cpp b/examples/MT_pomsarc_10_single_sided_writes/sw/src/server/main.cpp index de55dbf72..4aad7635e 100644 --- a/examples/MT_pomsarc_10_single_sided_writes/sw/src/server/main.cpp +++ b/examples/MT_pomsarc_10_single_sided_writes/sw/src/server/main.cpp @@ -105,16 +105,17 @@ int main(int argc, char *argv[]) { // Benchmark sweep; exactly like done in the client code PR_HEADER("RDMA BENCHMARK: SERVER"); - unsigned int curr_size = min_size; - while(curr_size <= max_size) { + unsigned int curr_size = 4096; + //while(curr_size <= max_size) { coyote::sgEntry sg; sg.rdma = { .len = curr_size }; run_bench(coyote_thread, sg, mem, N_THROUGHPUT_REPS, n_runs, operation); - run_bench(coyote_thread, sg, mem, N_LATENCY_REPS, n_runs, operation); + //run_bench(coyote_thread, sg, mem, N_LATENCY_REPS, n_runs, operation); curr_size *= 2; - } + //} // Final sync and exit coyote_thread.connSync(IS_CLIENT); + return EXIT_SUCCESS; } \ No newline at end of file From c0e76de4cf0412c3aa5ac27cddf897f98327ce40 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Tue, 16 Sep 2025 18:58:04 +0200 Subject: [PATCH 34/52] ... --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 111 +++++++++--------- scripts/ip_inst/network_infrastructure.tcl | 2 +- 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index f349db77f..3d997111f 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -33,7 +33,7 @@ localparam integer F = 5; localparam integer time_threshhold = 150; localparam integer byte_threshhold = 10; -localparam integer ecn_alternation_timeframe = 10000; +localparam integer ecn_alternation_timeframe = 5000; localparam integer Max_Sa_index = 28; //coarse_grained @@ -50,12 +50,12 @@ logic[4:0] Sa_index; logic[15:0] Rt; logic[15:0] Rc; //logic[63:0] Sa; -logic[31:0] timer; +logic[23:0] timer; -logic[31:0] timer_send_rate; +logic[23:0] timer_send_rate; -logic[31:0] time_of_last_marked_packet; -logic[31:0] time_last_update; +logic[23:0] time_of_last_marked_packet; +logic[23:0] time_last_update; logic[7:0] byte_counter; logic[7:0] time_counter; @@ -66,18 +66,18 @@ logic[2:0] bC_C; //bytecounter counter logic rate_increase_event; //debug signals -logic z_ack_arrives; -logic z_marked_packet_triggers; -logic z_alpha_decrease; +//logic z_ack_arrives; +//logic z_marked_packet_triggers; +//logic z_alpha_decrease; -logic z_time_counter_expires; -logic z_byte_counter_expires; +//logic z_time_counter_expires; +//logic z_byte_counter_expires; -logic z_hyper_increase; -logic z_additive_increase; -logic z_fast_recovery; +//logic z_hyper_increase; +//logic z_additive_increase; +//logic z_fast_recovery; -logic z_next_req_ready; +//logic z_next_req_ready; //simul part logic ecn_alternator; @@ -108,7 +108,7 @@ always_ff @(posedge aclk) begin m_req.valid <= 1'b0; queue_out.ready <= 1'b0; m_req.data <= 0; - + /* z_ack_arrives <= 0; z_marked_packet_triggers <= 0; z_alpha_decrease <= 0; @@ -121,7 +121,7 @@ always_ff @(posedge aclk) begin z_fast_recovery <= 0; z_next_req_ready <= 0; - + */ Sa_index <= 0; ecn_alternator <= 1; @@ -129,7 +129,7 @@ always_ff @(posedge aclk) begin end else begin - timer <= timer + 1; + timer = timer + 1; time_counter <= time_counter + 1; timer_send_rate <= timer_send_rate + 1; @@ -140,34 +140,35 @@ always_ff @(posedge aclk) begin m_req.data <= queue_out.data; queue_out.ready <= 1'b0; - z_ack_arrives <= 0; - z_marked_packet_triggers <= 0; - z_alpha_decrease <= 0; + //z_ack_arrives <= 0; + //z_marked_packet_triggers <= 0; + //z_alpha_decrease <= 0; - z_time_counter_expires <= 0; - z_byte_counter_expires <= 0; + //z_time_counter_expires <= 0; + //z_byte_counter_expires <= 0; - z_hyper_increase <= 0; - z_additive_increase <= 0; - z_fast_recovery <= 0; + //z_hyper_increase <= 0; + //z_additive_increase <= 0; + //z_fast_recovery <= 0; - z_next_req_ready <= 0; + //z_next_req_ready <= 0; + + if(timer % ecn_alternation_timeframe == 0) begin + ecn_alternator <= ~ecn_alternator; + end if(ecn_write_rdy) begin //===Debug Signal=== - z_ack_arrives <= 1; + //z_ack_arrives <= 1; //================== byte_counter <= byte_counter + 1; - if(timer % ecn_alternation_timeframe == 0) begin - ecn_alternator <= ~ecn_alternator; - end - + if(ecn_mark == 1'b1 && ecn_alternator == 1) begin // Marked Packet arrived if(timer - time_last_update > N_min_time_between_ecn_marks) begin //===Debug Signal=== - z_marked_packet_triggers <= 1; + //z_marked_packet_triggers <= 1; //================== Rt <= Rc; @@ -179,7 +180,7 @@ always_ff @(posedge aclk) begin */ //fixed size Sa - if(Rc <= 2048) begin + if(Rc <= 8192) begin Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; end if(!(Sa_index == 0)) begin @@ -206,7 +207,7 @@ always_ff @(posedge aclk) begin if (timer - time_of_last_marked_packet > K) begin //===Debug Signal=== - z_alpha_decrease <= 1; + //z_alpha_decrease <= 1; //================== /* if(Sa <= 1) begin @@ -222,14 +223,12 @@ always_ff @(posedge aclk) begin Sa_index <= Sa_index + 1; end - time_of_last_marked_packet <= timer; - - + time_of_last_marked_packet <= timer; end if(time_counter > time_threshhold) begin //===Debug Signal=== - z_time_counter_expires <= 1; + //z_time_counter_expires <= 1; //================== time_counter <= 0; rate_increase_event <= 1; @@ -240,7 +239,7 @@ always_ff @(posedge aclk) begin if(byte_counter > byte_threshhold) begin //===Debug Signal=== - z_byte_counter_expires <= 1; + //z_byte_counter_expires <= 1; //================== byte_counter <= 0; rate_increase_event <= 1; @@ -256,14 +255,14 @@ always_ff @(posedge aclk) begin // case Hyper Increse //replaced by fast recovery //===Debug Signal=== - z_hyper_increase <= 1; + //z_hyper_increase <= 1; //================== Rc <= (Rt + Rc) >> 1; end else begin // case Additive Increase //===Debug Signal=== - z_additive_increase <= 1; + //z_additive_increase <= 1; //================== if(Rt + 3 < Rai) begin Rt <= 1; @@ -276,17 +275,17 @@ always_ff @(posedge aclk) begin end else begin // case Fast Recovery //===Debug Signal=== - z_fast_recovery <= 1; + //z_fast_recovery <= 1; //================== Rc <= (Rt + Rc) >> 1; end end - if(Rc < 50) begin - Rc <= 50; + if(Rc < 200) begin + Rc <= 200; end - if(Rt < 50) begin - Rt <= 50; + if(Rt < 200) begin + Rt <= 200; end // CHANGE BACK @@ -294,7 +293,7 @@ always_ff @(posedge aclk) begin //if(timer_send_rate > 1) begin //===Debug Signal=== - z_next_req_ready <= 1; + //z_next_req_ready <= 1; //================== m_req.valid <= queue_out.valid; @@ -312,18 +311,18 @@ end ila_DCQCN inst_ila_DCQCN( .clk(aclk), - .probe0(timer), //32 - .probe1(Rt), //16 - .probe2(Rc), //16 + .probe0(timer), //24 + .probe1(Rt), //13 + .probe2(Rc), //13 .probe3(Sa_index), //5 .probe4(byte_counter), //8 .probe5(time_counter), //8 .probe6(tC_C), //3 .probe7(bC_C), //3 .probe8(rate_increase_event), - .probe9(timer_send_rate), //32 - .probe10(time_last_update), //32 - .probe11(time_of_last_marked_packet), //32 + .probe9(timer_send_rate), //24 + .probe10(time_last_update), //24 + .probe11(time_of_last_marked_packet), //24 .probe12(s_req.valid), .probe13(s_req.ready), @@ -332,7 +331,7 @@ ila_DCQCN inst_ila_DCQCN( .probe16(queue_out.valid), .probe17(queue_out.ready), .probe18(ecn_mark), - .probe19(ecn_write_rdy), + .probe19(ecn_write_rdy),/*, .probe20(z_ack_arrives), .probe21(z_marked_packet_triggers), @@ -342,8 +341,8 @@ ila_DCQCN inst_ila_DCQCN( .probe25(z_hyper_increase), .probe26(z_additive_increase), .probe27(z_fast_recovery), - .probe28(z_next_req_ready), - .probe29(ecn_alternator) + .probe28(z_next_req_ready),*/ + .probe20(ecn_alternator) ); diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index b517448a2..4035c27c3 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -590,7 +590,7 @@ set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CON #set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN -set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {16} CONFIG.C_PROBE2_WIDTH {16} CONFIG.C_PROBE3_WIDTH {5} CONFIG.C_PROBE4_WIDTH {8} CONFIG.C_PROBE5_WIDTH {8} CONFIG.C_PROBE6_WIDTH {3} CONFIG.C_PROBE7_WIDTH {3} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {32} CONFIG.C_PROBE10_WIDTH {32} CONFIG.C_PROBE11_WIDTH {32} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_PROBE21_WIDTH {1} CONFIG.C_PROBE22_WIDTH {1} CONFIG.C_PROBE23_WIDTH {1} CONFIG.C_PROBE24_WIDTH {1} CONFIG.C_PROBE25_WIDTH {1} CONFIG.C_PROBE26_WIDTH {1} CONFIG.C_PROBE27_WIDTH {1} CONFIG.C_PROBE28_WIDTH {1} CONFIG.C_PROBE29_WIDTH {9} CONFIG.C_NUM_OF_PROBES {30} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {24} CONFIG.C_PROBE1_WIDTH {16} CONFIG.C_PROBE2_WIDTH {16} CONFIG.C_PROBE3_WIDTH {5} CONFIG.C_PROBE4_WIDTH {8} CONFIG.C_PROBE5_WIDTH {8} CONFIG.C_PROBE6_WIDTH {3} CONFIG.C_PROBE7_WIDTH {3} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {24} CONFIG.C_PROBE10_WIDTH {24} CONFIG.C_PROBE11_WIDTH {24} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_NUM_OF_PROBES {21} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_flowcontrol set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] From f7488bb398f2656846e51006148226eb0798f408 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Tue, 23 Sep 2025 09:20:00 +0200 Subject: [PATCH 35/52] ... --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 139 ++++++++++++------ scripts/ip_inst/network_infrastructure.tcl | 2 +- 2 files changed, 98 insertions(+), 43 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index 3d997111f..d37679e84 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -19,21 +19,19 @@ localparam integer RD_OP = 0; localparam integer WR_OP = 1; //localparam integer Rt_default = 100000; -localparam integer Rt_default = 128; +localparam integer Rt_default = 200; localparam integer Rc_default = 8192; -localparam integer N_min_time_between_ecn_marks = 200; -localparam integer K = 600; +localparam integer N_min_time_between_ecn_marks = 12500; +localparam integer K = 13750; //localparam integer Rai = 6250; -localparam integer Rai = 60; +localparam integer Rai = 128; localparam integer S1 = 128; // 1 << 8 localparam integer Sg = 8; // (1/16) << 8 localparam integer F = 5; -localparam integer time_threshhold = 150; -localparam integer byte_threshhold = 10; - -localparam integer ecn_alternation_timeframe = 5000; +localparam integer time_threshhold = 50000; +localparam integer byte_threshhold = 35; localparam integer Max_Sa_index = 28; //coarse_grained @@ -58,7 +56,7 @@ logic[23:0] time_of_last_marked_packet; logic[23:0] time_last_update; logic[7:0] byte_counter; -logic[7:0] time_counter; +logic[15:0] time_counter; logic[2:0] tC_C; //timecounter counter logic[2:0] bC_C; //bytecounter counter @@ -80,7 +78,26 @@ logic rate_increase_event; //logic z_next_req_ready; //simul part +logic[23:0] ecn_timer; +localparam integer ecn_timer_trigger_threshhold = 5000000; logic ecn_alternator; +logic[15:0] ecn_counter; +//1 +//localparam integer Ecn_rates[0:6] = {9999, 1, 9 ,99, 999, 9999}; +//2 +//localparam integer Ecn_rates[0:6] = {9999, 1, 9, 49 ,99, 999, 9999}; +//3 +//localparam integer Ecn_rates[0:6] = {9999, 1, 9, 14 , 19, 24, 9999}; +//4 +localparam integer Ecn_rates[0:7] = {9999, 99, 29, 24, 19, 9, 29, 39}; + +logic ecn_test_starter; +localparam Ecn_rates_max_index = 7; +logic[2:0] ecn_rates_index; + +logic[15:0] ila_readout_timer; +localparam integer Ila_readout_timer_threshhold = 40000; +logic ila_trigger; metaIntf #(.STYPE(dreq_t)) queue_out (); @@ -124,7 +141,15 @@ always_ff @(posedge aclk) begin */ Sa_index <= 0; - ecn_alternator <= 1; + + //simul + ecn_alternator <= 0; + ecn_counter <= 0; + ecn_test_starter <= 0; + ecn_timer <= 0; + ecn_rates_index <= 0; + ila_readout_timer <= 0; + ila_trigger <= 0; end @@ -140,6 +165,16 @@ always_ff @(posedge aclk) begin m_req.data <= queue_out.data; queue_out.ready <= 1'b0; + if(ila_readout_timer >= Ila_readout_timer_threshhold) begin + ila_readout_timer <= 0; + ila_trigger = 1; + end else begin + ila_readout_timer <= ila_readout_timer + 1; + ila_trigger = 0; + end + + + //z_ack_arrives <= 0; //z_marked_packet_triggers <= 0; //z_alpha_decrease <= 0; @@ -152,18 +187,47 @@ always_ff @(posedge aclk) begin //z_fast_recovery <= 0; //z_next_req_ready <= 0; + if(ecn_test_starter == 1) begin + - if(timer % ecn_alternation_timeframe == 0) begin - ecn_alternator <= ~ecn_alternator; + if(ecn_timer > ecn_timer_trigger_threshhold) begin + if(!(ecn_rates_index == Ecn_rates_max_index)) begin + ecn_rates_index <= ecn_rates_index + 1; + end + ecn_timer <= 0; + end + else begin + ecn_timer <= ecn_timer + 1; + end end + + + if(ecn_write_rdy) begin //===Debug Signal=== //z_ack_arrives <= 1; + //================== byte_counter <= byte_counter + 1; + //======simul====== + ecn_test_starter <= 1; + + + if(ecn_counter >= Ecn_rates[ecn_rates_index]) begin + ecn_counter <= 0; + ecn_alternator = 1; + end + else begin + ecn_counter <= ecn_counter + 1; + ecn_alternator = 0; + end + + + //=============================== + if(ecn_mark == 1'b1 && ecn_alternator == 1) begin // Marked Packet arrived if(timer - time_last_update > N_min_time_between_ecn_marks) begin @@ -171,28 +235,14 @@ always_ff @(posedge aclk) begin //z_marked_packet_triggers <= 1; //================== Rt <= Rc; - - //Rc mul by 2 every time - /* - if(Rc <= 8192) begin - Rc <= Rc << 2; - end - */ - //fixed size Sa - if(Rc <= 8192) begin + if(Rc <= 16384) begin Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; end if(!(Sa_index == 0)) begin Sa_index <= Sa_index - 1; end - - /* - Rc <= (Rc << 8) / (S1 - (Sa>>1)); - Sa <= (((S1-Sg)*Sa)>> 8) + Sg; - - */ time_last_update <= timer; time_counter <= 0; @@ -201,7 +251,7 @@ always_ff @(posedge aclk) begin tC_C <= 0; end - time_of_last_marked_packet <= timer; + time_of_last_marked_packet = timer; end end @@ -209,15 +259,7 @@ always_ff @(posedge aclk) begin //===Debug Signal=== //z_alpha_decrease <= 1; //================== - /* - if(Sa <= 1) begin - Sa <= 1; - end - else begin - Sa <= (((S1-Sg)*Sa)>> 8); - end - */ - + if(!(Sa_index == Max_Sa_index)) begin Sa_index <= Sa_index + 1; @@ -257,7 +299,13 @@ always_ff @(posedge aclk) begin //===Debug Signal=== //z_hyper_increase <= 1; //================== - Rc <= (Rt + Rc) >> 1; + if(Rt + 3 < Rai) begin + Rt <= 1; + end + else begin + Rt <= Rt - Rai; + end + Rc <= (Rt + Rc) >> 1; end else begin // case Additive Increase @@ -312,11 +360,11 @@ end ila_DCQCN inst_ila_DCQCN( .clk(aclk), .probe0(timer), //24 - .probe1(Rt), //13 - .probe2(Rc), //13 + .probe1(Rt), //16 + .probe2(Rc), //16 .probe3(Sa_index), //5 .probe4(byte_counter), //8 - .probe5(time_counter), //8 + .probe5(time_counter), //16 .probe6(tC_C), //3 .probe7(bC_C), //3 .probe8(rate_increase_event), @@ -342,7 +390,14 @@ ila_DCQCN inst_ila_DCQCN( .probe26(z_additive_increase), .probe27(z_fast_recovery), .probe28(z_next_req_ready),*/ - .probe20(ecn_alternator) + .probe20(ecn_alternator), //1 + .probe21(ecn_counter), //16 + .probe22(ecn_timer), //24 + .probe23(ecn_test_starter), + .probe24(ecn_rates_index), // 3 + .probe25(ila_readout_timer), //16 + .probe26(ila_trigger) + ); diff --git a/scripts/ip_inst/network_infrastructure.tcl b/scripts/ip_inst/network_infrastructure.tcl index 4035c27c3..50e1ba829 100644 --- a/scripts/ip_inst/network_infrastructure.tcl +++ b/scripts/ip_inst/network_infrastructure.tcl @@ -590,7 +590,7 @@ set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CON #set_property -dict [list CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {32} CONFIG.C_PROBE2_WIDTH {32} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_NUM_OF_PROBES {6} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_testbench_CC] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_DCQCN -set_property -dict [list CONFIG.C_PROBE0_WIDTH {24} CONFIG.C_PROBE1_WIDTH {16} CONFIG.C_PROBE2_WIDTH {16} CONFIG.C_PROBE3_WIDTH {5} CONFIG.C_PROBE4_WIDTH {8} CONFIG.C_PROBE5_WIDTH {8} CONFIG.C_PROBE6_WIDTH {3} CONFIG.C_PROBE7_WIDTH {3} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {24} CONFIG.C_PROBE10_WIDTH {24} CONFIG.C_PROBE11_WIDTH {24} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_NUM_OF_PROBES {21} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] +set_property -dict [list CONFIG.C_PROBE0_WIDTH {24} CONFIG.C_PROBE1_WIDTH {16} CONFIG.C_PROBE2_WIDTH {16} CONFIG.C_PROBE3_WIDTH {5} CONFIG.C_PROBE4_WIDTH {8} CONFIG.C_PROBE5_WIDTH {16} CONFIG.C_PROBE6_WIDTH {3} CONFIG.C_PROBE7_WIDTH {3} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {24} CONFIG.C_PROBE10_WIDTH {24} CONFIG.C_PROBE11_WIDTH {24} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_PROBE16_WIDTH {1} CONFIG.C_PROBE17_WIDTH {1} CONFIG.C_PROBE18_WIDTH {1} CONFIG.C_PROBE19_WIDTH {1} CONFIG.C_PROBE20_WIDTH {1} CONFIG.C_PROBE21_WIDTH {16} CONFIG.C_PROBE22_WIDTH {24} CONFIG.C_PROBE23_WIDTH {1} CONFIG.C_PROBE24_WIDTH {3} CONFIG.C_PROBE25_WIDTH {16} CONFIG.C_PROBE26_WIDTH {1} CONFIG.C_NUM_OF_PROBES {27} CONFIG.C_DATA_DEPTH {16384} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_DCQCN] create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_flowcontrol set_property -dict [list CONFIG.C_PROBE0_WIDTH {1} CONFIG.C_PROBE1_WIDTH {1} CONFIG.C_PROBE2_WIDTH {1} CONFIG.C_PROBE3_WIDTH {1} CONFIG.C_PROBE4_WIDTH {1} CONFIG.C_PROBE5_WIDTH {1} CONFIG.C_PROBE6_WIDTH {1} CONFIG.C_PROBE7_WIDTH {1} CONFIG.C_PROBE8_WIDTH {1} CONFIG.C_PROBE9_WIDTH {1} CONFIG.C_PROBE10_WIDTH {1} CONFIG.C_PROBE11_WIDTH {1} CONFIG.C_PROBE12_WIDTH {1} CONFIG.C_PROBE13_WIDTH {1} CONFIG.C_PROBE14_WIDTH {1} CONFIG.C_PROBE15_WIDTH {1} CONFIG.C_NUM_OF_PROBES {16} CONFIG.C_DATA_DEPTH {8192} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_flowcontrol] From 04b5a122a35c3e0ba486b0aa354bb0cbde5e7183 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Thu, 30 Oct 2025 23:21:03 +0100 Subject: [PATCH 36/52] safety commit --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 231 ++++++++ .../rdma/MT_pomsarc_CC_QP_unit_Timely.sv | 0 hw/hdl/network/rdma/MT_pomsarc_new _flow.sv | 552 ++++++++++++------ .../rdma/MT_pomsarc_new_flow_multiqueue.sv | 319 ++++++++++ 4 files changed, 914 insertions(+), 188 deletions(-) delete mode 100644 hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_Timely.sv diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index d37679e84..de90a3fb2 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -411,4 +411,235 @@ queue_meta #( ); +endmodule + + + +// NO TEST PART + + + +import lynxTypes::*; + +module cc_queue +( + input logic aclk, + input logic aresetn, + + input logic ecn_mark, + input logic ecn_write_rdy, + + metaIntf.s s_req, + metaIntf.m m_req +); + +//localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; +//localparam integer RDMA_N_OST = 20; +//localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); + +localparam integer RDMA_N_OST = 100; +localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); + +localparam integer RD_OP = 0; +localparam integer WR_OP = 1; + +//localparam integer Rt_default = 100000; +localparam integer Rt_default = 200; +localparam integer Rc_default = 8192; +localparam integer N_min_time_between_ecn_marks = 12500; +localparam integer K = 13750; +//localparam integer Rai = 6250; +localparam integer Rai = 128; + +localparam integer S1 = 128; // 1 << 8 +localparam integer Sg = 8; // (1/16) << 8 +localparam integer F = 5; + +localparam integer time_threshhold = 50000; +localparam integer byte_threshhold = 35; + +localparam integer Max_Sa_index = 28; +//coarse_grained +//localparam integer Sa_fixed[0:3] = {16, 8, 3, 2}; +//finegrained +localparam integer Sa_fixed[0:28] = {256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139}; + +//logic[3:0] Sa_index; +logic[4:0] Sa_index; + + + + +logic[15:0] Rt; +logic[15:0] Rc; +//logic[63:0] Sa; +logic[23:0] timer; + +logic[23:0] timer_send_rate; + +logic[23:0] time_of_last_marked_packet; +logic[23:0] time_last_update; + +logic[7:0] byte_counter; +logic[15:0] time_counter; + +logic[2:0] tC_C; //timecounter counter +logic[2:0] bC_C; //bytecounter counter + +logic rate_increase_event; + + +metaIntf #(.STYPE(dreq_t)) queue_out (); + +always_ff @(posedge aclk) begin + if (aresetn == 1'b0) begin + Rt <= Rt_default; + Rc <= Rc_default; + + timer <= 0; + + timer_send_rate <= 0; + + time_of_last_marked_packet <= 0; + time_last_update <= 0; + + byte_counter <= 0; + time_counter <= 0; + + bC_C <= 0; + tC_C <= 0; + + rate_increase_event <= 0; + + m_req.valid <= 1'b0; + queue_out.ready <= 1'b0; + m_req.data <= 0; + + + Sa_index <= 0; + + + end + else begin + timer = timer + 1; + time_counter <= time_counter + 1; + timer_send_rate <= timer_send_rate + 1; + + rate_increase_event <= 0; + + + m_req.valid <= 1'b0; + m_req.data <= queue_out.data; + queue_out.ready <= 1'b0; + + + + if(ecn_write_rdy) begin + byte_counter <= byte_counter + 1; + + + if(ecn_mark == 1'b1) begin // Marked Packet arrived + if(timer - time_last_update > N_min_time_between_ecn_marks) begin + Rt <= Rc; + + if(Rc <= 16384) begin + Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; + end + if(!(Sa_index == 0)) begin + Sa_index <= Sa_index - 1; + end + + time_last_update <= timer; + + time_counter <= 0; + byte_counter <= 0; + bC_C <= 0; + tC_C <= 0; + + end + time_of_last_marked_packet = timer; + end + end + + if (timer - time_of_last_marked_packet > K) begin + if(!(Sa_index == Max_Sa_index)) begin + Sa_index <= Sa_index + 1; + end + time_of_last_marked_packet <= timer; + end + + if(time_counter > time_threshhold) begin + time_counter <= 0; + rate_increase_event <= 1; + if(tC_C < F) begin + tC_C <= tC_C + 1; + end + end + + if(byte_counter > byte_threshhold) begin + byte_counter <= 0; + rate_increase_event <= 1; + if(bC_C < F) begin + bC_C <= bC_C + 1; + end + end + + if(rate_increase_event) begin + if(tC_C >= F || bC_C >= F) begin + + if(tC_C >= F && bC_C >= F) begin + if(Rt + 3 < Rai) begin + Rt <= 1; + end + else begin + Rt <= Rt - Rai; + end + Rc <= (Rt + Rc) >> 1; + end + else begin + if(Rt + 3 < Rai) begin + Rt <= 1; + end + else begin + Rt <= Rt - Rai; + end + Rc <= (Rt + Rc) >> 1; + end + end else begin + Rc <= (Rt + Rc) >> 1; + end + end + + if(Rc < 200) begin + Rc <= 200; + end + if(Rt < 200) begin + Rt <= 200; + end + + if(timer_send_rate > Rc) begin + + m_req.valid <= queue_out.valid; + queue_out.ready <= m_req.ready; + if(queue_out.valid && m_req.ready) begin + timer_send_rate <= 0; + end + + end + + + end +end + + +queue_meta #( + .QDEPTH(RDMA_N_OST) +) inst_cq ( + .aclk(aclk), + .aresetn(aresetn), + .s_meta(s_req), + .m_meta(queue_out) +); + + endmodule \ No newline at end of file diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_Timely.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_Timely.sv deleted file mode 100644 index e69de29bb..000000000 diff --git a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv index 6644a9683..2f9e9312d 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv @@ -1,237 +1,413 @@ import lynxTypes::*; -module rdma_flow ( - metaIntf.s s_req, - metaIntf.m m_req, +module cc_queue +( + input logic aclk, + input logic aresetn, - metaIntf.s s_ack, - metaIntf.m m_ack, + input logic ecn_mark, + input logic ecn_write_rdy, - input logic aclk, - input logic aresetn + metaIntf.s s_req, + metaIntf.m m_req ); -localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; -localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); -localparam integer RD_OP = 0; -localparam integer WR_OP = 1; - -// -- FSM -typedef enum logic[2:0] {ST_IDLE, ST_ACK_LUP_WAIT, ST_REQ_LUP_WAIT, ST_ACK_LUP, ST_REQ_LUP} state_t; -logic [2:0] state_C, state_N; -logic [1+N_REGIONS_BITS+PID_BITS-1:0] addr_C, addr_N; - -logic [1:0] ssn_wr; -logic [1+N_REGIONS_BITS+PID_BITS-1:0] ssn_addr; -logic [15:0] ssn_in; -logic [15:0] ssn_out; - -metaIntf #(.STYPE(ack_t)) ack_que_in (); -metaIntf #(.STYPE(dreq_t)) req_out (); - -logic [RDMA_OST_BITS-1:0] tail, tail_next; -logic [RDMA_OST_BITS-1:0] head, head_next; -logic issued, issued_next; - - -// Pointer table -ram_sp_nc #( - .ADDR_BITS(1+N_REGIONS_BITS+PID_BITS), - .DATA_BITS(16) -) inst_pntr_table ( - .clk(aclk), - .a_en(1'b1), - .a_we(ssn_wr), - .a_addr(ssn_addr), - .a_data_in(ssn_in), - .a_data_out(ssn_out) -); +//======================================================== +// DCQCN PARAMETERS +//======================================================== + +localparam integer REQ_QUEUE_SIZE = 10; + +//localparam integer Rt_default = 100000; +localparam integer Rt_default = 200; +localparam integer Rc_default = 8192; +localparam integer N_min_time_between_ecn_marks = 12500; +localparam integer K = 13750; +localparam integer Rai = 128; + + + +localparam integer S1 = 128; // 1 << 8 +localparam integer Sg = 8; // (1/16) << 8 +localparam integer F = 5; + +//normal +localparam integer time_threshhold = 50000; +//increased T test +//localparam integer time_threshhold = 100000; + +localparam integer byte_threshhold = 35; + + +localparam integer Max_Sa_index = 28; +localparam integer Sa_fixed[0:28] = {256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139}; +logic[4:0] Sa_index; + + + + +logic[15:0] Rt; +logic[15:0] Rc; +logic[23:0] timer; + +logic[23:0] timer_send_rate; + +logic[23:0] time_of_last_marked_packet; +logic[23:0] time_last_update; + +logic[7:0] byte_counter; +logic[15:0] time_counter; + +logic[2:0] tC_C; //timecounter counter +logic[2:0] bC_C; //bytecounter counter -// REG -always_ff @(posedge aclk) begin: PROC_REG +logic rate_increase_event; + +//debug signals +//logic z_ack_arrives; +//logic z_marked_packet_triggers; +//logic z_alpha_decrease; + +//logic z_time_counter_expires; +//logic z_byte_counter_expires; + +//logic z_hyper_increase; +//logic z_additive_increase; +//logic z_fast_recovery; + +//logic z_next_req_ready; + +//simul part +logic[23:0] ecn_timer; +localparam integer ecn_timer_trigger_threshhold = 5000000; +logic ecn_alternator; +logic[15:0] ecn_counter; +//1 +//localparam integer Ecn_rates[0:6] = {9999, 1, 9 ,99, 999, 9999}; +//2 +//localparam integer Ecn_rates[0:6] = {9999, 1, 9, 49 ,99, 999, 9999}; +//3 +//localparam integer Ecn_rates[0:6] = {9999, 1, 9, 14 , 19, 24, 9999}; +//4 +localparam integer Ecn_rates[0:15] = {9999, 99, 29, 24, 19, 9, 29, 39, 39, 99, 99, 149, 149,199,199,9999}; + +logic ecn_test_starter; +localparam Ecn_rates_max_index = 15; +logic[4:0] ecn_rates_index; + +logic[15:0] ila_readout_timer; +localparam integer Ila_readout_timer_threshhold = 40000; +logic ila_trigger; + + +metaIntf #(.STYPE(dreq_t)) queue_out (); + +always_ff @(posedge aclk) begin if (aresetn == 1'b0) begin - state_C <= ST_IDLE; - addr_C <= 'X; - end - else begin - state_C <= state_N; - addr_C <= addr_N; - end -end + Rt <= Rt_default; + Rc <= Rc_default; + timer <= 0; -// NSL -always_comb begin: NSL - state_N = state_C; + timer_send_rate <= 0; - case(state_C) - ST_IDLE: - state_N = (s_ack.valid) ? ST_ACK_LUP_WAIT : (s_req.valid & req_out.ready ? ST_REQ_LUP_WAIT : ST_IDLE); + time_of_last_marked_packet <= 0; + time_last_update <= 0; - ST_ACK_LUP_WAIT: - state_N = ST_ACK_LUP; + byte_counter <= 0; + time_counter <= 0; + + bC_C <= 0; + tC_C <= 0; - ST_REQ_LUP_WAIT: - state_N = ST_REQ_LUP; + rate_increase_event <= 0; - ST_REQ_LUP: - state_N = ST_IDLE; + m_req.valid <= 1'b0; + queue_out.ready <= 1'b0; + m_req.data <= 0; + /* + z_ack_arrives <= 0; + z_marked_packet_triggers <= 0; + z_alpha_decrease <= 0; - ST_ACK_LUP: - state_N = ST_IDLE; + z_time_counter_expires <= 0; + z_byte_counter_expires <= 0; - endcase // state_C -end + z_hyper_increase <= 0; + z_additive_increase <= 0; + z_fast_recovery <= 0; + + z_next_req_ready <= 0; + */ + Sa_index <= 0; -// DP -assign ssn_in = {7'h00, issued_next, head_next, tail_next}; -assign issued = ssn_out[2*RDMA_OST_BITS]; -assign head = ssn_out[RDMA_OST_BITS+:RDMA_OST_BITS]; -assign tail = ssn_out[0+:RDMA_OST_BITS]; - -always_comb begin: DP - addr_N = addr_C; - - // ACKs - s_ack.ready = 1'b0; - - ack_que_in.valid = 1'b0; - ack_que_in.data = s_ack.data.ack; - - // Req - s_req.ready = 1'b0; - req_out.valid = 1'b0; - req_out.data = s_req.data; - req_out.data.req_1.offs = head; - - // Table - ssn_wr = 0; - ssn_addr = 0; - - // Pointers - head_next = 0; - tail_next = 0; - issued_next = 0; - - case(state_C) - ST_IDLE: begin - if(s_ack.valid) begin - s_ack.ready = 1'b1; - ack_que_in.valid = s_ack.data.last; - - ssn_addr = {is_opcode_rd_resp(s_ack.data.ack.opcode), s_ack.data.ack.vfid[N_REGIONS_BITS-1:0], s_ack.data.ack.pid}; - addr_N = ssn_addr; - end - else if(s_req.valid & req_out.ready) begin - ssn_addr = {is_opcode_rd_req(s_req.data.req_1.opcode), s_req.data.req_1.vfid[N_REGIONS_BITS-1:0], s_req.data.req_1.pid}; - addr_N = ssn_addr; - end - end - ST_ACK_LUP: begin - head_next = head; - tail_next = tail + 1; - if(head == tail_next) - issued_next = 1'b0; - else - issued_next = issued; + //simul + ecn_alternator <= 0; + ecn_counter <= 0; + ecn_test_starter <= 0; + ecn_timer <= 0; + ecn_rates_index <= 0; + ila_readout_timer <= 0; + ila_trigger <= 0; - ssn_wr = ~0; - ssn_addr = addr_C; + + end + else begin + timer = timer + 1; + time_counter <= time_counter + 1; + timer_send_rate <= timer_send_rate + 1; + + rate_increase_event <= 0; + + + m_req.valid <= 1'b0; + m_req.data <= queue_out.data; + queue_out.ready <= 1'b0; + + if(ila_readout_timer >= Ila_readout_timer_threshhold) begin + ila_readout_timer <= 0; + ila_trigger = 1; + end else begin + ila_readout_timer <= ila_readout_timer + 1; + ila_trigger = 0; end - ST_REQ_LUP: begin - if(!issued || (head != tail)) begin - req_out.valid = 1'b1; - s_req.ready = 1'b1; - head_next = head + 1; - tail_next = tail; - issued_next = 1'b1; - ssn_wr = ~0; - ssn_addr = addr_C; + //z_ack_arrives <= 0; + //z_marked_packet_triggers <= 0; + //z_alpha_decrease <= 0; + + //z_time_counter_expires <= 0; + //z_byte_counter_expires <= 0; + + //z_hyper_increase <= 0; + //z_additive_increase <= 0; + //z_fast_recovery <= 0; + + //z_next_req_ready <= 0; + if(ecn_test_starter == 1) begin + + + if(ecn_timer > ecn_timer_trigger_threshhold) begin + if(!(ecn_rates_index == Ecn_rates_max_index)) begin + ecn_rates_index <= ecn_rates_index + 1; + end + ecn_timer <= 0; + end + else begin + ecn_timer <= ecn_timer + 1; end end - endcase -end -ila_flowcontrol inst_ila_flowcontrol ( - .clk(aclk), - .probe0(ack_que_in.valid), - .probe1(ack_que_in.ready), - .probe2(s_ack.valid), - .probe3(s_ack.ready), + if(ecn_write_rdy) begin + //===Debug Signal=== + //z_ack_arrives <= 1; - .probe4(m_ack.valid), - .probe5(m_ack.ready), + //================== + byte_counter <= byte_counter + 1; - .probe6(req_out.valid), - .probe7(req_out.ready), + //======simul====== + ecn_test_starter <= 1; - .probe8(s_req.valid), - .probe9(s_req.ready), + + if(ecn_counter >= Ecn_rates[ecn_rates_index]) begin + ecn_counter <= 0; + ecn_alternator = 1; + end + else begin + ecn_counter <= ecn_counter + 1; + ecn_alternator = 0; + end - .probe10(m_req.valid), - .probe11(m_req.ready), + + //=============================== + + + if(ecn_mark == 1'b1 && ecn_alternator == 1) begin // Marked Packet arrived + if(timer - time_last_update > N_min_time_between_ecn_marks) begin + //===Debug Signal=== + //z_marked_packet_triggers <= 1; + //================== + Rt <= Rc; + + if(Rc <= 16384) begin + Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; + end + if(!(Sa_index == 0)) begin + Sa_index <= Sa_index - 1; + end + + time_last_update <= timer; + + time_counter <= 0; + byte_counter <= 0; + bC_C <= 0; + tC_C <= 0; + + end + time_of_last_marked_packet = timer; + end + end - .probe12(ecn_marked), - .probe13(ecn_consumed), - .probe14(s_ack.data.ack.ecn), - .probe15(s_ack.data.last) -); + if (timer - time_of_last_marked_packet > K) begin + //===Debug Signal=== + //z_alpha_decrease <= 1; + //================== + + + if(!(Sa_index == Max_Sa_index)) begin + Sa_index <= Sa_index + 1; + end + + time_of_last_marked_packet <= timer; + end + + if(time_counter > time_threshhold) begin + //===Debug Signal=== + //z_time_counter_expires <= 1; + //================== + time_counter <= 0; + rate_increase_event <= 1; + if(tC_C < F) begin + tC_C <= tC_C + 1; + end + end + if(byte_counter > byte_threshhold) begin + //===Debug Signal=== + //z_byte_counter_expires <= 1; + //================== + byte_counter <= 0; + rate_increase_event <= 1; + if(bC_C < F) begin + bC_C <= bC_C + 1; + end + end -// ACK queue -queue_meta #( - .QDEPTH(RDMA_N_OST) -) inst_cq ( - .aclk(aclk), - .aresetn(aresetn), - .s_meta(ack_que_in), - .m_meta(m_ack) -); + if(rate_increase_event) begin + if(tC_C >= F || bC_C >= F) begin + + if(tC_C >= F && bC_C >= F) begin + // case Hyper Increse + //replaced by fast recovery + //===Debug Signal=== + //z_hyper_increase <= 1; + //================== + if(Rt + 3 < Rai) begin + Rt <= 1; + end + else begin + Rt <= Rt - Rai; + end + Rc <= (Rt + Rc) >> 1; + end + else begin + // case Additive Increase + //===Debug Signal=== + //z_additive_increase <= 1; + //================== + if(Rt + 3 < Rai) begin + Rt <= 1; + end + else begin + Rt <= Rt - Rai; + end + Rc <= (Rt + Rc) >> 1; + end + end else begin + // case Fast Recovery + //===Debug Signal=== + //z_fast_recovery <= 1; + //================== + Rc <= (Rt + Rc) >> 1; + end + end -/* -// REQ queue -queue_meta #( - .QDEPTH(RDMA_N_OST) -) inst_sq ( - .aclk(aclk), - .aresetn(aresetn), - .s_meta(req_out), - .m_meta(m_req) -); -*/ + if(Rc < 200) begin + Rc <= 200; + end + if(Rt < 200) begin + Rt <= 200; + end + // CHANGE BACK + if(timer_send_rate > Rc) begin + //if(timer_send_rate > 1) begin -logic ecn_marked; -assign ecn_marked = (s_ack.data.ack.ecn == 3); -logic ecn_consumed; -assign ecn_consumed = (s_ack.valid & s_ack.ready) & s_ack.data.last; + //===Debug Signal=== + //z_next_req_ready <= 1; + //================== + m_req.valid <= queue_out.valid; + queue_out.ready <= m_req.ready; + if(queue_out.valid && m_req.ready) begin + timer_send_rate <= 0; + end -cc_queue inst_DCQCN( - .aclk(aclk), - .aresetn(aresetn), + end + + + end +end + + +ila_DCQCN inst_ila_DCQCN( + .clk(aclk), + .probe0(timer), //24 + .probe1(Rt), //16 + .probe2(Rc), //16 + .probe3(Sa_index), //5 + .probe4(byte_counter), //8 + .probe5(time_counter), //16 + .probe6(tC_C), //3 + .probe7(bC_C), //3 + .probe8(rate_increase_event), + .probe9(timer_send_rate), //24 + .probe10(time_last_update), //24 + .probe11(time_of_last_marked_packet), //24 + + .probe12(s_req.valid), + .probe13(s_req.ready), + .probe14(m_req.valid), + .probe15(m_req.ready), + .probe16(queue_out.valid), + .probe17(queue_out.ready), + .probe18(ecn_mark), + .probe19(ecn_write_rdy), + + //.probe20(z_ack_arrives), + //.probe21(z_marked_packet_triggers), + //.probe22(z_alpha_decrease), + //.probe23(z_time_counter_expires), + //.probe24(z_byte_counter_expires), + //.probe25(z_hyper_increase), + //.probe26(z_additive_increase), + //.probe27(z_fast_recovery), + //.probe28(z_next_req_ready), + .probe20(ecn_alternator), //1 + .probe21(ecn_counter), //16 + .probe22(ecn_timer), //24 + .probe23(ecn_test_starter), + .probe24(ecn_rates_index), // 5 + .probe25(ila_readout_timer), //16 + .probe26(ila_trigger) - .ecn_mark(ecn_marked), - .ecn_write_rdy(ecn_consumed), - - .s_req(req_out), - .m_req(m_req) ); -/*benchy inst_cc_bench( +queue_meta #( + .QDEPTH(REQ_QUEUE_SIZE) +) inst_cq ( .aclk(aclk), .aresetn(aresetn), + .s_meta(s_req), + .m_meta(queue_out) +); - .s_req(req_out), - .m_req(m_req) -);*/ -endmodule +endmodule \ No newline at end of file diff --git a/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv b/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv index e69de29bb..953afe93f 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv @@ -0,0 +1,319 @@ +import lynxTypes::*; + +module rdma_flow ( + metaIntf.s s_req, + metaIntf.m m_req, + + metaIntf.s s_ack, + metaIntf.m m_ack, + + input logic aclk, + input logic aresetn +); + +localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; +localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); +localparam integer RD_OP = 0; +localparam integer WR_OP = 1; + +// -- FSM +typedef enum logic[2:0] {ST_IDLE, ST_ACK_LUP_WAIT, ST_REQ_LUP_WAIT, ST_ACK_LUP, ST_REQ_LUP} state_t; +logic [2:0] state_C, state_N; +logic [1+N_REGIONS_BITS+PID_BITS-1:0] addr_C, addr_N; + +logic [1:0] ssn_wr; +logic [1+N_REGIONS_BITS+PID_BITS-1:0] ssn_addr; +logic [15:0] ssn_in; +logic [15:0] ssn_out; + +metaIntf #(.STYPE(ack_t)) ack_que_in (); +metaIntf #(.STYPE(dreq_t)) req_out (); + +logic [RDMA_OST_BITS-1:0] tail, tail_next; +logic [RDMA_OST_BITS-1:0] head, head_next; +logic issued, issued_next; + + +// Pointer table +ram_sp_nc #( + .ADDR_BITS(1+N_REGIONS_BITS+PID_BITS), + .DATA_BITS(16) +) inst_pntr_table ( + .clk(aclk), + .a_en(1'b1), + .a_we(ssn_wr), + .a_addr(ssn_addr), + .a_data_in(ssn_in), + .a_data_out(ssn_out) +); + +// REG +always_ff @(posedge aclk) begin: PROC_REG + if (aresetn == 1'b0) begin + state_C <= ST_IDLE; + addr_C <= 'X; + end + else begin + state_C <= state_N; + addr_C <= addr_N; + end +end + +// NSL +always_comb begin: NSL + state_N = state_C; + + case(state_C) + ST_IDLE: + state_N = (s_ack.valid) ? ST_ACK_LUP_WAIT : (s_req.valid & req_out.ready ? ST_REQ_LUP_WAIT : ST_IDLE); + + ST_ACK_LUP_WAIT: + state_N = ST_ACK_LUP; + + ST_REQ_LUP_WAIT: + state_N = ST_REQ_LUP; + + ST_REQ_LUP: + state_N = ST_IDLE; + + ST_ACK_LUP: + state_N = ST_IDLE; + + endcase // state_C +end + +// DP +assign ssn_in = {7'h00, issued_next, head_next, tail_next}; +assign issued = ssn_out[2*RDMA_OST_BITS]; +assign head = ssn_out[RDMA_OST_BITS+:RDMA_OST_BITS]; +assign tail = ssn_out[0+:RDMA_OST_BITS]; + +always_comb begin: DP + addr_N = addr_C; + + // ACKs + s_ack.ready = 1'b0; + + ack_que_in.valid = 1'b0; + ack_que_in.data = s_ack.data.ack; + + // Req + s_req.ready = 1'b0; + req_out.valid = 1'b0; + req_out.data = s_req.data; + req_out.data.req_1.offs = head; + + // Table + ssn_wr = 0; + ssn_addr = 0; + + // Pointers + head_next = 0; + tail_next = 0; + issued_next = 0; + + case(state_C) + ST_IDLE: begin + if(s_ack.valid) begin + s_ack.ready = 1'b1; + ack_que_in.valid = s_ack.data.last; + + ssn_addr = {is_opcode_rd_resp(s_ack.data.ack.opcode), s_ack.data.ack.vfid[N_REGIONS_BITS-1:0], s_ack.data.ack.pid}; + addr_N = ssn_addr; + end + else if(s_req.valid & req_out.ready) begin + ssn_addr = {is_opcode_rd_req(s_req.data.req_1.opcode), s_req.data.req_1.vfid[N_REGIONS_BITS-1:0], s_req.data.req_1.pid}; + addr_N = ssn_addr; + end + end + + ST_ACK_LUP: begin + head_next = head; + tail_next = tail + 1; + if(head == tail_next) + issued_next = 1'b0; + else + issued_next = issued; + + ssn_wr = ~0; + ssn_addr = addr_C; + end + + ST_REQ_LUP: begin + if(!issued || (head != tail)) begin + req_out.valid = 1'b1; + s_req.ready = 1'b1; + + head_next = head + 1; + tail_next = tail; + issued_next = 1'b1; + + ssn_wr = ~0; + ssn_addr = addr_C; + end + end + + endcase + +end + +/* +ila_flowcontrol inst_ila_flowcontrol ( + .clk(aclk), + .probe0(ack_que_in.valid), + .probe1(ack_que_in.ready), + + .probe2(s_ack.valid), + .probe3(s_ack.ready), + + .probe4(m_ack.valid), + .probe5(m_ack.ready), + + .probe6(req_out.valid), + .probe7(req_out.ready), + + .probe8(s_req.valid), + .probe9(s_req.ready), + + .probe10(m_req.valid), + .probe11(m_req.ready), + + .probe12(ecn_marked), + .probe13(ecn_consumed), + .probe14(s_ack.data.ack.ecn), + .probe15(s_ack.data.last) +); +*/ + +// ACK queue +queue_meta #( + .QDEPTH(RDMA_N_OST) +) inst_cq ( + .aclk(aclk), + .aresetn(aresetn), + .s_meta(ack_que_in), + .m_meta(m_ack) +); + + +localparam integer N_QUEUES = 4; +localparam integer QUEUE_SEL_BITS = $clog2(N_QUEUES); + + +logic [DEST_BITS+PID_BITS-1:0] routing_key; +logic [QUEUE_SEL_BITS-1:0] queue_idx; + +logic [DEST_BITS+PID_BITS-1:0] ack_routing_key; +logic [QUEUE_SEL_BITS-1:0] ack_queue_idx; + +logic [N_QUEUES-1:0] ecn_mark_queue; +logic [N_QUEUES-1:0] ecn_write_rdy_queue; + +metaIntf #(.STYPE(dreq_t)) ccq_s_req [N_QUEUES] (); +metaIntf #(.STYPE(dreq_t)) ccq_m_req [N_QUEUES] (); + +assign routing_key = {s_req.data.req_1.vfid, s_req.data.req_1.pid}; +assign queue_idx = routing_key[DEST_BITS+PID_BITS-1 -: QUEUE_SEL_BITS]; + +assign ack_routing_key = {s_ack.data.ack.vfid, s_ack.data.ack.pid}; +assign ack_queue_idx = ack_routing_key[DEST_BITS+PID_BITS-1 -: QUEUE_SEL_BITS]; + +logic ecn_marked; +assign ecn_marked = (s_ack.data.ack.ecn == 3); + +logic ecn_consumed; +assign ecn_consumed = (s_ack.valid & s_ack.ready) & s_ack.data.last; + +always_comb begin + ecn_mark_queue = 0; + ecn_write_rdy_queue = 0; + + if (ecn_marked) + ecn_mark_queue[ack_queue_idx] = 1'b1; + + if (ecn_consumed) + ecn_write_rdy_queue[ack_queue_idx] = 1'b1; +end + +always_comb begin + req_out.ready = 0; + ccq_s_req[0].valid = 0; + ccq_s_req[0].data = 0; + ccq_s_req[1].valid = 0; + ccq_s_req[1].data = 0; + ccq_s_req[2].valid = 0; + ccq_s_req[2].data = 0; + ccq_s_req[3].valid = 0; + ccq_s_req[3].data = 0; + + if (req_out.valid && (queue_idx == 0) && ccq_s_req[0].ready) begin + ccq_s_req[0].valid = 1; + ccq_s_req[0].data = req_out.data; + req_out.ready = 1; + end else if (req_out.valid && (queue_idx == 1) && ccq_s_req[1].ready) begin + ccq_s_req[1].valid = 1; + ccq_s_req[1].data = req_out.data; + req_out.ready = 1; + + end else if (req_out.valid && (queue_idx == 2) && ccq_s_req[2].ready) begin + ccq_s_req[2].valid = 1; + ccq_s_req[2].data = req_out.data; + req_out.ready = 1; + + end else if (req_out.valid && (queue_idx == 3) && ccq_s_req[3].ready) begin + ccq_s_req[3].valid = 1; + ccq_s_req[3].data = req_out.data; + req_out.ready = 1; + end +end + + +always_comb begin + m_req.valid = 0; + m_req.data = 0; + + ccq_m_req[0].ready = 0; + ccq_m_req[1].ready = 0; + ccq_m_req[2].ready = 0; + ccq_m_req[3].ready = 0; + + if (ccq_m_req[0].valid) begin + m_req.valid = 1; + m_req.data = ccq_m_req[0].data; + ccq_m_req[0].ready = m_req.ready; + end else if (ccq_m_req[1].valid) begin + m_req.valid = 1; + m_req.data = ccq_m_req[1].data; + ccq_m_req[1].ready = m_req.ready; + end else if (ccq_m_req[2].valid) begin + m_req.valid = 1; + m_req.data = ccq_m_req[2].data; + ccq_m_req[2].ready = m_req.ready; + end else if (ccq_m_req[3].valid) begin + m_req.valid = 1; + m_req.data = ccq_m_req[3].data; + ccq_m_req[3].ready = m_req.ready; + end +end + + + + +genvar i_gen; +generate + for (i_gen = 0; i_gen < N_QUEUES; i_gen++) begin: gen_ccq + cc_queue inst_ccq ( + .aclk(aclk), + .aresetn(aresetn), + + .ecn_mark(ecn_mark_queue[i_gen]), + .ecn_write_rdy(ecn_write_rdy_queue[i_gen]), + + .s_req(ccq_s_req[i_gen]), + .m_req(ccq_m_req[i_gen]) + ); + end +endgenerate + + +endmodule From f1ee77925174243ad20d989bc0bcfe5ab14f54c7 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Fri, 31 Oct 2025 18:52:05 +0100 Subject: [PATCH 37/52] ... --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 254 ++------- hw/hdl/network/rdma/MT_pomsarc_new _flow.sv | 536 ++++++------------ .../rdma/MT_pomsarc_new_flow_multiqueue.sv | 7 +- 3 files changed, 222 insertions(+), 575 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index de90a3fb2..3ad7d2865 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -1,5 +1,7 @@ import lynxTypes::*; +`define CC_TEST + module cc_queue ( input logic aclk, @@ -18,28 +20,28 @@ localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); localparam integer RD_OP = 0; localparam integer WR_OP = 1; -//localparam integer Rt_default = 100000; + localparam integer Rt_default = 200; localparam integer Rc_default = 8192; + +localparam integer R_MIN = 200; +localparam integer R_CAP = 16384; + localparam integer N_min_time_between_ecn_marks = 12500; localparam integer K = 13750; -//localparam integer Rai = 6250; -localparam integer Rai = 128; -localparam integer S1 = 128; // 1 << 8 -localparam integer Sg = 8; // (1/16) << 8 +localparam integer R_ADDITIVE_INCREASE = 128; +localparam integer R_HYPER_INCREASE = 128; + + +localparam integer Sg = 8; localparam integer F = 5; localparam integer time_threshhold = 50000; localparam integer byte_threshhold = 35; localparam integer Max_Sa_index = 28; -//coarse_grained -//localparam integer Sa_fixed[0:3] = {16, 8, 3, 2}; -//finegrained localparam integer Sa_fixed[0:28] = {256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139}; - -//logic[3:0] Sa_index; logic[4:0] Sa_index; @@ -63,19 +65,10 @@ logic[2:0] bC_C; //bytecounter counter logic rate_increase_event; -//debug signals -//logic z_ack_arrives; -//logic z_marked_packet_triggers; -//logic z_alpha_decrease; - -//logic z_time_counter_expires; -//logic z_byte_counter_expires; +metaIntf #(.STYPE(dreq_t)) queue_out (); -//logic z_hyper_increase; -//logic z_additive_increase; -//logic z_fast_recovery; +`ifdef CC_TEST -//logic z_next_req_ready; //simul part logic[23:0] ecn_timer; @@ -100,13 +93,10 @@ localparam integer Ila_readout_timer_threshhold = 40000; logic ila_trigger; -metaIntf #(.STYPE(dreq_t)) queue_out (); - always_ff @(posedge aclk) begin if (aresetn == 1'b0) begin Rt <= Rt_default; Rc <= Rc_default; - //Sa <= S1; timer <= 0; timer_send_rate <= 0; @@ -125,20 +115,7 @@ always_ff @(posedge aclk) begin m_req.valid <= 1'b0; queue_out.ready <= 1'b0; m_req.data <= 0; - /* - z_ack_arrives <= 0; - z_marked_packet_triggers <= 0; - z_alpha_decrease <= 0; - - z_time_counter_expires <= 0; - z_byte_counter_expires <= 0; - - z_hyper_increase <= 0; - z_additive_increase <= 0; - z_fast_recovery <= 0; - - z_next_req_ready <= 0; - */ + Sa_index <= 0; @@ -174,19 +151,6 @@ always_ff @(posedge aclk) begin end - - //z_ack_arrives <= 0; - //z_marked_packet_triggers <= 0; - //z_alpha_decrease <= 0; - - //z_time_counter_expires <= 0; - //z_byte_counter_expires <= 0; - - //z_hyper_increase <= 0; - //z_additive_increase <= 0; - //z_fast_recovery <= 0; - - //z_next_req_ready <= 0; if(ecn_test_starter == 1) begin @@ -206,10 +170,7 @@ always_ff @(posedge aclk) begin if(ecn_write_rdy) begin - //===Debug Signal=== - //z_ack_arrives <= 1; - - //================== + byte_counter <= byte_counter + 1; //======simul====== @@ -224,19 +185,15 @@ always_ff @(posedge aclk) begin ecn_counter <= ecn_counter + 1; ecn_alternator = 0; end - - //=============================== - if(ecn_mark == 1'b1 && ecn_alternator == 1) begin // Marked Packet arrived + if(ecn_alternator == 1) begin // Marked Packet arrived if(timer - time_last_update > N_min_time_between_ecn_marks) begin - //===Debug Signal=== - //z_marked_packet_triggers <= 1; - //================== + Rt <= Rc; - if(Rc <= 16384) begin + if(Rc <= R_CAP) begin Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; end if(!(Sa_index == 0)) begin @@ -256,10 +213,6 @@ always_ff @(posedge aclk) begin end if (timer - time_of_last_marked_packet > K) begin - //===Debug Signal=== - //z_alpha_decrease <= 1; - //================== - if(!(Sa_index == Max_Sa_index)) begin Sa_index <= Sa_index + 1; @@ -269,9 +222,6 @@ always_ff @(posedge aclk) begin end if(time_counter > time_threshhold) begin - //===Debug Signal=== - //z_time_counter_expires <= 1; - //================== time_counter <= 0; rate_increase_event <= 1; if(tC_C < F) begin @@ -280,9 +230,7 @@ always_ff @(posedge aclk) begin end if(byte_counter > byte_threshhold) begin - //===Debug Signal=== - //z_byte_counter_expires <= 1; - //================== + byte_counter <= 0; rate_increase_event <= 1; if(bC_C < F) begin @@ -294,56 +242,38 @@ always_ff @(posedge aclk) begin if(tC_C >= F || bC_C >= F) begin if(tC_C >= F && bC_C >= F) begin - // case Hyper Increse - //replaced by fast recovery - //===Debug Signal=== - //z_hyper_increase <= 1; - //================== - if(Rt + 3 < Rai) begin + + if(Rt + 3 < R_HYPER_INCREASE) begin Rt <= 1; end else begin - Rt <= Rt - Rai; + Rt <= Rt - R_HYPER_INCREASE; end Rc <= (Rt + Rc) >> 1; end else begin - // case Additive Increase - //===Debug Signal=== - //z_additive_increase <= 1; - //================== - if(Rt + 3 < Rai) begin + if(Rt + 3 < R_ADDITIVE_INCREASE) begin Rt <= 1; end else begin - Rt <= Rt - Rai; + Rt <= Rt - R_ADDITIVE_INCREASE; end Rc <= (Rt + Rc) >> 1; end end else begin - // case Fast Recovery - //===Debug Signal=== - //z_fast_recovery <= 1; - //================== Rc <= (Rt + Rc) >> 1; end end - if(Rc < 200) begin - Rc <= 200; + if(Rc < R_MIN) begin + Rc <= R_MIN; end - if(Rt < 200) begin - Rt <= 200; + if(Rt < R_MIN) begin + Rt <= R_MIN; end // CHANGE BACK if(timer_send_rate > Rc) begin - //if(timer_send_rate > 1) begin - - //===Debug Signal=== - //z_next_req_ready <= 1; - //================== - m_req.valid <= queue_out.valid; queue_out.ready <= m_req.ready; if(queue_out.valid && m_req.ready) begin @@ -379,17 +309,8 @@ ila_DCQCN inst_ila_DCQCN( .probe16(queue_out.valid), .probe17(queue_out.ready), .probe18(ecn_mark), - .probe19(ecn_write_rdy),/*, - - .probe20(z_ack_arrives), - .probe21(z_marked_packet_triggers), - .probe22(z_alpha_decrease), - .probe23(z_time_counter_expires), - .probe24(z_byte_counter_expires), - .probe25(z_hyper_increase), - .probe26(z_additive_increase), - .probe27(z_fast_recovery), - .probe28(z_next_req_ready),*/ + .probe19(ecn_write_rdy), + .probe20(ecn_alternator), //1 .probe21(ecn_counter), //16 .probe22(ecn_timer), //24 @@ -401,96 +322,10 @@ ila_DCQCN inst_ila_DCQCN( ); -queue_meta #( - .QDEPTH(RDMA_N_OST) -) inst_cq ( - .aclk(aclk), - .aresetn(aresetn), - .s_meta(s_req), - .m_meta(queue_out) -); - - -endmodule - - +`else // NO TEST PART - - -import lynxTypes::*; - -module cc_queue -( - input logic aclk, - input logic aresetn, - - input logic ecn_mark, - input logic ecn_write_rdy, - - metaIntf.s s_req, - metaIntf.m m_req -); - -//localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; -//localparam integer RDMA_N_OST = 20; -//localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); - -localparam integer RDMA_N_OST = 100; -localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); - -localparam integer RD_OP = 0; -localparam integer WR_OP = 1; - -//localparam integer Rt_default = 100000; -localparam integer Rt_default = 200; -localparam integer Rc_default = 8192; -localparam integer N_min_time_between_ecn_marks = 12500; -localparam integer K = 13750; -//localparam integer Rai = 6250; -localparam integer Rai = 128; - -localparam integer S1 = 128; // 1 << 8 -localparam integer Sg = 8; // (1/16) << 8 -localparam integer F = 5; - -localparam integer time_threshhold = 50000; -localparam integer byte_threshhold = 35; - -localparam integer Max_Sa_index = 28; -//coarse_grained -//localparam integer Sa_fixed[0:3] = {16, 8, 3, 2}; -//finegrained -localparam integer Sa_fixed[0:28] = {256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139}; - -//logic[3:0] Sa_index; -logic[4:0] Sa_index; - - - - -logic[15:0] Rt; -logic[15:0] Rc; -//logic[63:0] Sa; -logic[23:0] timer; - -logic[23:0] timer_send_rate; - -logic[23:0] time_of_last_marked_packet; -logic[23:0] time_last_update; - -logic[7:0] byte_counter; -logic[15:0] time_counter; - -logic[2:0] tC_C; //timecounter counter -logic[2:0] bC_C; //bytecounter counter - -logic rate_increase_event; - - -metaIntf #(.STYPE(dreq_t)) queue_out (); - always_ff @(posedge aclk) begin if (aresetn == 1'b0) begin Rt <= Rt_default; @@ -542,7 +377,7 @@ always_ff @(posedge aclk) begin if(timer - time_last_update > N_min_time_between_ecn_marks) begin Rt <= Rc; - if(Rc <= 16384) begin + if(Rc <= R_CAP) begin Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; end if(!(Sa_index == 0)) begin @@ -588,20 +423,20 @@ always_ff @(posedge aclk) begin if(tC_C >= F || bC_C >= F) begin if(tC_C >= F && bC_C >= F) begin - if(Rt + 3 < Rai) begin + if(Rt + 3 < R_HYPER_INCREASE) begin Rt <= 1; end else begin - Rt <= Rt - Rai; + Rt <= Rt - R_HYPER_INCREASE; end Rc <= (Rt + Rc) >> 1; end else begin - if(Rt + 3 < Rai) begin + if(Rt + 3 < R_ADDITIVE_INCREASE) begin Rt <= 1; end else begin - Rt <= Rt - Rai; + Rt <= Rt - R_ADDITIVE_INCREASE; end Rc <= (Rt + Rc) >> 1; end @@ -610,11 +445,11 @@ always_ff @(posedge aclk) begin end end - if(Rc < 200) begin - Rc <= 200; + if(Rc < R_MIN) begin + Rc <= R_MIN; end - if(Rt < 200) begin - Rt <= 200; + if(Rt < R_MIN) begin + Rt <= R_MIN; end if(timer_send_rate > Rc) begin @@ -632,6 +467,12 @@ always_ff @(posedge aclk) begin end +`endif + + + + + queue_meta #( .QDEPTH(RDMA_N_OST) ) inst_cq ( @@ -642,4 +483,5 @@ queue_meta #( ); + endmodule \ No newline at end of file diff --git a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv index 2f9e9312d..0418c4c1d 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new _flow.sv @@ -1,413 +1,217 @@ import lynxTypes::*; -module cc_queue -( - input logic aclk, - input logic aresetn, - - input logic ecn_mark, - input logic ecn_write_rdy, - +module rdma_flow ( metaIntf.s s_req, - metaIntf.m m_req -); - -//======================================================== -// DCQCN PARAMETERS -//======================================================== - -localparam integer REQ_QUEUE_SIZE = 10; - -//localparam integer Rt_default = 100000; -localparam integer Rt_default = 200; -localparam integer Rc_default = 8192; -localparam integer N_min_time_between_ecn_marks = 12500; -localparam integer K = 13750; -localparam integer Rai = 128; - - - -localparam integer S1 = 128; // 1 << 8 -localparam integer Sg = 8; // (1/16) << 8 -localparam integer F = 5; - -//normal -localparam integer time_threshhold = 50000; -//increased T test -//localparam integer time_threshhold = 100000; - -localparam integer byte_threshhold = 35; - - -localparam integer Max_Sa_index = 28; -localparam integer Sa_fixed[0:28] = {256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139}; -logic[4:0] Sa_index; - - - - -logic[15:0] Rt; -logic[15:0] Rc; -logic[23:0] timer; - -logic[23:0] timer_send_rate; - -logic[23:0] time_of_last_marked_packet; -logic[23:0] time_last_update; - -logic[7:0] byte_counter; -logic[15:0] time_counter; + metaIntf.m m_req, -logic[2:0] tC_C; //timecounter counter -logic[2:0] bC_C; //bytecounter counter - -logic rate_increase_event; - -//debug signals -//logic z_ack_arrives; -//logic z_marked_packet_triggers; -//logic z_alpha_decrease; - -//logic z_time_counter_expires; -//logic z_byte_counter_expires; - -//logic z_hyper_increase; -//logic z_additive_increase; -//logic z_fast_recovery; - -//logic z_next_req_ready; - -//simul part -logic[23:0] ecn_timer; -localparam integer ecn_timer_trigger_threshhold = 5000000; -logic ecn_alternator; -logic[15:0] ecn_counter; -//1 -//localparam integer Ecn_rates[0:6] = {9999, 1, 9 ,99, 999, 9999}; -//2 -//localparam integer Ecn_rates[0:6] = {9999, 1, 9, 49 ,99, 999, 9999}; -//3 -//localparam integer Ecn_rates[0:6] = {9999, 1, 9, 14 , 19, 24, 9999}; -//4 -localparam integer Ecn_rates[0:15] = {9999, 99, 29, 24, 19, 9, 29, 39, 39, 99, 99, 149, 149,199,199,9999}; - -logic ecn_test_starter; -localparam Ecn_rates_max_index = 15; -logic[4:0] ecn_rates_index; - -logic[15:0] ila_readout_timer; -localparam integer Ila_readout_timer_threshhold = 40000; -logic ila_trigger; + metaIntf.s s_ack, + metaIntf.m m_ack, + input logic aclk, + input logic aresetn +); -metaIntf #(.STYPE(dreq_t)) queue_out (); +localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; +localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); +localparam integer RD_OP = 0; +localparam integer WR_OP = 1; + +// -- FSM +typedef enum logic[2:0] {ST_IDLE, ST_ACK_LUP_WAIT, ST_REQ_LUP_WAIT, ST_ACK_LUP, ST_REQ_LUP} state_t; +logic [2:0] state_C, state_N; +logic [1+N_REGIONS_BITS+PID_BITS-1:0] addr_C, addr_N; + +logic [1:0] ssn_wr; +logic [1+N_REGIONS_BITS+PID_BITS-1:0] ssn_addr; +logic [15:0] ssn_in; +logic [15:0] ssn_out; + +metaIntf #(.STYPE(ack_t)) ack_que_in (); +metaIntf #(.STYPE(dreq_t)) req_out (); + +logic [RDMA_OST_BITS-1:0] tail, tail_next; +logic [RDMA_OST_BITS-1:0] head, head_next; +logic issued, issued_next; + + +// Pointer table +ram_sp_nc #( + .ADDR_BITS(1+N_REGIONS_BITS+PID_BITS), + .DATA_BITS(16) +) inst_pntr_table ( + .clk(aclk), + .a_en(1'b1), + .a_we(ssn_wr), + .a_addr(ssn_addr), + .a_data_in(ssn_in), + .a_data_out(ssn_out) +); -always_ff @(posedge aclk) begin +// REG +always_ff @(posedge aclk) begin: PROC_REG if (aresetn == 1'b0) begin - Rt <= Rt_default; - Rc <= Rc_default; - timer <= 0; - - timer_send_rate <= 0; - - time_of_last_marked_packet <= 0; - time_last_update <= 0; - - byte_counter <= 0; - time_counter <= 0; - - bC_C <= 0; - tC_C <= 0; - - rate_increase_event <= 0; - - m_req.valid <= 1'b0; - queue_out.ready <= 1'b0; - m_req.data <= 0; - /* - z_ack_arrives <= 0; - z_marked_packet_triggers <= 0; - z_alpha_decrease <= 0; - - z_time_counter_expires <= 0; - z_byte_counter_expires <= 0; - - z_hyper_increase <= 0; - z_additive_increase <= 0; - z_fast_recovery <= 0; - - z_next_req_ready <= 0; - */ - Sa_index <= 0; - - - //simul - ecn_alternator <= 0; - ecn_counter <= 0; - ecn_test_starter <= 0; - ecn_timer <= 0; - ecn_rates_index <= 0; - ila_readout_timer <= 0; - ila_trigger <= 0; - - + state_C <= ST_IDLE; + addr_C <= 'X; end else begin - timer = timer + 1; - time_counter <= time_counter + 1; - timer_send_rate <= timer_send_rate + 1; - - rate_increase_event <= 0; - - - m_req.valid <= 1'b0; - m_req.data <= queue_out.data; - queue_out.ready <= 1'b0; - - if(ila_readout_timer >= Ila_readout_timer_threshhold) begin - ila_readout_timer <= 0; - ila_trigger = 1; - end else begin - ila_readout_timer <= ila_readout_timer + 1; - ila_trigger = 0; - end - - - - //z_ack_arrives <= 0; - //z_marked_packet_triggers <= 0; - //z_alpha_decrease <= 0; - - //z_time_counter_expires <= 0; - //z_byte_counter_expires <= 0; - - //z_hyper_increase <= 0; - //z_additive_increase <= 0; - //z_fast_recovery <= 0; - - //z_next_req_ready <= 0; - if(ecn_test_starter == 1) begin - - - if(ecn_timer > ecn_timer_trigger_threshhold) begin - if(!(ecn_rates_index == Ecn_rates_max_index)) begin - ecn_rates_index <= ecn_rates_index + 1; - end - ecn_timer <= 0; - end - else begin - ecn_timer <= ecn_timer + 1; - end - end + state_C <= state_N; + addr_C <= addr_N; + end +end +// NSL +always_comb begin: NSL + state_N = state_C; + case(state_C) + ST_IDLE: + state_N = (s_ack.valid) ? ST_ACK_LUP_WAIT : (s_req.valid & req_out.ready ? ST_REQ_LUP_WAIT : ST_IDLE); + ST_ACK_LUP_WAIT: + state_N = ST_ACK_LUP; + ST_REQ_LUP_WAIT: + state_N = ST_REQ_LUP; - if(ecn_write_rdy) begin - //===Debug Signal=== - //z_ack_arrives <= 1; + ST_REQ_LUP: + state_N = ST_IDLE; - //================== - byte_counter <= byte_counter + 1; + ST_ACK_LUP: + state_N = ST_IDLE; - //======simul====== - ecn_test_starter <= 1; + endcase // state_C +end - - if(ecn_counter >= Ecn_rates[ecn_rates_index]) begin - ecn_counter <= 0; - ecn_alternator = 1; - end - else begin - ecn_counter <= ecn_counter + 1; - ecn_alternator = 0; +// DP +assign ssn_in = {7'h00, issued_next, head_next, tail_next}; +assign issued = ssn_out[2*RDMA_OST_BITS]; +assign head = ssn_out[RDMA_OST_BITS+:RDMA_OST_BITS]; +assign tail = ssn_out[0+:RDMA_OST_BITS]; + +always_comb begin: DP + addr_N = addr_C; + + // ACKs + s_ack.ready = 1'b0; + + ack_que_in.valid = 1'b0; + ack_que_in.data = s_ack.data.ack; + + // Req + s_req.ready = 1'b0; + req_out.valid = 1'b0; + req_out.data = s_req.data; + req_out.data.req_1.offs = head; + + // Table + ssn_wr = 0; + ssn_addr = 0; + + // Pointers + head_next = 0; + tail_next = 0; + issued_next = 0; + + case(state_C) + ST_IDLE: begin + if(s_ack.valid) begin + s_ack.ready = 1'b1; + ack_que_in.valid = s_ack.data.last; + + ssn_addr = {is_opcode_rd_resp(s_ack.data.ack.opcode), s_ack.data.ack.vfid[N_REGIONS_BITS-1:0], s_ack.data.ack.pid}; + addr_N = ssn_addr; end - - - //=============================== - - - if(ecn_mark == 1'b1 && ecn_alternator == 1) begin // Marked Packet arrived - if(timer - time_last_update > N_min_time_between_ecn_marks) begin - //===Debug Signal=== - //z_marked_packet_triggers <= 1; - //================== - Rt <= Rc; - - if(Rc <= 16384) begin - Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; - end - if(!(Sa_index == 0)) begin - Sa_index <= Sa_index - 1; - end - - time_last_update <= timer; - - time_counter <= 0; - byte_counter <= 0; - bC_C <= 0; - tC_C <= 0; - - end - time_of_last_marked_packet = timer; + else if(s_req.valid & req_out.ready) begin + ssn_addr = {is_opcode_rd_req(s_req.data.req_1.opcode), s_req.data.req_1.vfid[N_REGIONS_BITS-1:0], s_req.data.req_1.pid}; + addr_N = ssn_addr; end end - if (timer - time_of_last_marked_packet > K) begin - //===Debug Signal=== - //z_alpha_decrease <= 1; - //================== - - - if(!(Sa_index == Max_Sa_index)) begin - Sa_index <= Sa_index + 1; - end - - time_of_last_marked_packet <= timer; - end - - if(time_counter > time_threshhold) begin - //===Debug Signal=== - //z_time_counter_expires <= 1; - //================== - time_counter <= 0; - rate_increase_event <= 1; - if(tC_C < F) begin - tC_C <= tC_C + 1; - end - end + ST_ACK_LUP: begin + head_next = head; + tail_next = tail + 1; + if(head == tail_next) + issued_next = 1'b0; + else + issued_next = issued; - if(byte_counter > byte_threshhold) begin - //===Debug Signal=== - //z_byte_counter_expires <= 1; - //================== - byte_counter <= 0; - rate_increase_event <= 1; - if(bC_C < F) begin - bC_C <= bC_C + 1; - end + ssn_wr = ~0; + ssn_addr = addr_C; end - if(rate_increase_event) begin - if(tC_C >= F || bC_C >= F) begin - - if(tC_C >= F && bC_C >= F) begin - // case Hyper Increse - //replaced by fast recovery - //===Debug Signal=== - //z_hyper_increase <= 1; - //================== - if(Rt + 3 < Rai) begin - Rt <= 1; - end - else begin - Rt <= Rt - Rai; - end - Rc <= (Rt + Rc) >> 1; - end - else begin - // case Additive Increase - //===Debug Signal=== - //z_additive_increase <= 1; - //================== - if(Rt + 3 < Rai) begin - Rt <= 1; - end - else begin - Rt <= Rt - Rai; - end - Rc <= (Rt + Rc) >> 1; - end - end else begin - // case Fast Recovery - //===Debug Signal=== - //z_fast_recovery <= 1; - //================== - Rc <= (Rt + Rc) >> 1; + ST_REQ_LUP: begin + if(!issued || (head != tail)) begin + req_out.valid = 1'b1; + s_req.ready = 1'b1; + + head_next = head + 1; + tail_next = tail; + issued_next = 1'b1; + + ssn_wr = ~0; + ssn_addr = addr_C; end end - if(Rc < 200) begin - Rc <= 200; - end - if(Rt < 200) begin - Rt <= 200; - end + endcase - // CHANGE BACK - if(timer_send_rate > Rc) begin - //if(timer_send_rate > 1) begin +end - //===Debug Signal=== - //z_next_req_ready <= 1; - //================== - m_req.valid <= queue_out.valid; - queue_out.ready <= m_req.ready; - if(queue_out.valid && m_req.ready) begin - timer_send_rate <= 0; - end +ila_flowcontrol inst_ila_flowcontrol ( + .clk(aclk), + .probe0(ack_que_in.valid), + .probe1(ack_que_in.ready), - end + .probe2(s_ack.valid), + .probe3(s_ack.ready), + .probe4(m_ack.valid), + .probe5(m_ack.ready), - end -end + .probe6(req_out.valid), + .probe7(req_out.ready), + .probe8(s_req.valid), + .probe9(s_req.ready), -ila_DCQCN inst_ila_DCQCN( - .clk(aclk), - .probe0(timer), //24 - .probe1(Rt), //16 - .probe2(Rc), //16 - .probe3(Sa_index), //5 - .probe4(byte_counter), //8 - .probe5(time_counter), //16 - .probe6(tC_C), //3 - .probe7(bC_C), //3 - .probe8(rate_increase_event), - .probe9(timer_send_rate), //24 - .probe10(time_last_update), //24 - .probe11(time_of_last_marked_packet), //24 - - .probe12(s_req.valid), - .probe13(s_req.ready), - .probe14(m_req.valid), - .probe15(m_req.ready), - .probe16(queue_out.valid), - .probe17(queue_out.ready), - .probe18(ecn_mark), - .probe19(ecn_write_rdy), - - //.probe20(z_ack_arrives), - //.probe21(z_marked_packet_triggers), - //.probe22(z_alpha_decrease), - //.probe23(z_time_counter_expires), - //.probe24(z_byte_counter_expires), - //.probe25(z_hyper_increase), - //.probe26(z_additive_increase), - //.probe27(z_fast_recovery), - //.probe28(z_next_req_ready), - .probe20(ecn_alternator), //1 - .probe21(ecn_counter), //16 - .probe22(ecn_timer), //24 - .probe23(ecn_test_starter), - .probe24(ecn_rates_index), // 5 - .probe25(ila_readout_timer), //16 - .probe26(ila_trigger) + .probe10(m_req.valid), + .probe11(m_req.ready), + .probe12(ecn_marked), + .probe13(ecn_consumed), + .probe14(s_ack.data.ack.ecn), + .probe15(s_ack.data.last) ); +// ACK queue queue_meta #( - .QDEPTH(REQ_QUEUE_SIZE) + .QDEPTH(RDMA_N_OST) ) inst_cq ( .aclk(aclk), .aresetn(aresetn), - .s_meta(s_req), - .m_meta(queue_out) + .s_meta(ack_que_in), + .m_meta(m_ack) +); + + +logic ecn_marked; +assign ecn_marked = (s_ack.data.ack.ecn == 3); +logic ecn_consumed; +assign ecn_consumed = (s_ack.valid & s_ack.ready) & s_ack.data.last; + + +cc_queue inst_DCQCN( + .aclk(aclk), + .aresetn(aresetn), + + .ecn_mark(ecn_marked), + .ecn_write_rdy(ecn_consumed), + + .s_req(req_out), + .m_req(m_req) ); -endmodule \ No newline at end of file +endmodule diff --git a/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv b/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv index 953afe93f..f13108712 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv @@ -1,4 +1,4 @@ -import lynxTypes::*; +/*import lynxTypes::*; module rdma_flow ( metaIntf.s s_req, @@ -156,7 +156,7 @@ always_comb begin: DP endcase end - +*/ /* ila_flowcontrol inst_ila_flowcontrol ( .clk(aclk), @@ -184,7 +184,7 @@ ila_flowcontrol inst_ila_flowcontrol ( .probe15(s_ack.data.last) ); */ - +/* // ACK queue queue_meta #( .QDEPTH(RDMA_N_OST) @@ -317,3 +317,4 @@ endgenerate endmodule +*/ \ No newline at end of file From 5b5f16fc5b79376743e683a2bba36d02f2c440f7 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Fri, 31 Oct 2025 21:08:37 +0100 Subject: [PATCH 38/52] ... --- .../rdma/MT_pomsarc_new_flow_multiqueue.sv | 136 ++++++++++-------- 1 file changed, 79 insertions(+), 57 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv b/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv index f13108712..78cfd82c3 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv @@ -158,6 +158,7 @@ always_comb begin: DP end */ /* + ila_flowcontrol inst_ila_flowcontrol ( .clk(aclk), .probe0(ack_que_in.valid), @@ -185,6 +186,7 @@ ila_flowcontrol inst_ila_flowcontrol ( ); */ /* + // ACK queue queue_meta #( .QDEPTH(RDMA_N_OST) @@ -228,75 +230,95 @@ always_comb begin ecn_mark_queue = 0; ecn_write_rdy_queue = 0; - if (ecn_marked) + if (ecn_marked) begin ecn_mark_queue[ack_queue_idx] = 1'b1; - - if (ecn_consumed) + end + if (ecn_consumed) begin ecn_write_rdy_queue[ack_queue_idx] = 1'b1; + end end -always_comb begin - req_out.ready = 0; - ccq_s_req[0].valid = 0; - ccq_s_req[0].data = 0; - ccq_s_req[1].valid = 0; - ccq_s_req[1].data = 0; - ccq_s_req[2].valid = 0; - ccq_s_req[2].data = 0; - ccq_s_req[3].valid = 0; - ccq_s_req[3].data = 0; - - if (req_out.valid && (queue_idx == 0) && ccq_s_req[0].ready) begin - ccq_s_req[0].valid = 1; - ccq_s_req[0].data = req_out.data; - req_out.ready = 1; - end else if (req_out.valid && (queue_idx == 1) && ccq_s_req[1].ready) begin - ccq_s_req[1].valid = 1; - ccq_s_req[1].data = req_out.data; - req_out.ready = 1; - - end else if (req_out.valid && (queue_idx == 2) && ccq_s_req[2].ready) begin - ccq_s_req[2].valid = 1; - ccq_s_req[2].data = req_out.data; - req_out.ready = 1; - - end else if (req_out.valid && (queue_idx == 3) && ccq_s_req[3].ready) begin - ccq_s_req[3].valid = 1; - ccq_s_req[3].data = req_out.data; - req_out.ready = 1; +logic [N_QUEUES-1:0] in_ready; + +genvar i_gen_in; +generate + for (i_gen_in = 0; i_gen_in < N_QUEUES; i_gen_in++) begin: gen_in + always_comb begin + ccq_s_req[i_gen_in].valid = 0; + ccq_s_req[i_gen_in].data = 0; + in_ready[i_gen_in] = 0; + if (queue_idx == i_gen_in) begin + ccq_s_req[i_gen_in].valid = req_out.valid; + ccq_s_req[i_gen_in].data = req_out.data; + in_ready[i_gen_in] = ccq_s_req[i_gen_in].ready; + end + end end -end +endgenerate +assign req_out.ready = |in_ready; -always_comb begin - m_req.valid = 0; - m_req.data = 0; - ccq_m_req[0].ready = 0; - ccq_m_req[1].ready = 0; - ccq_m_req[2].ready = 0; - ccq_m_req[3].ready = 0; - - if (ccq_m_req[0].valid) begin - m_req.valid = 1; - m_req.data = ccq_m_req[0].data; - ccq_m_req[0].ready = m_req.ready; - end else if (ccq_m_req[1].valid) begin - m_req.valid = 1; - m_req.data = ccq_m_req[1].data; - ccq_m_req[1].ready = m_req.ready; - end else if (ccq_m_req[2].valid) begin - m_req.valid = 1; - m_req.data = ccq_m_req[2].data; - ccq_m_req[2].ready = m_req.ready; - end else if (ccq_m_req[3].valid) begin - m_req.valid = 1; - m_req.data = ccq_m_req[3].data; - ccq_m_req[3].ready = m_req.ready; +logic [QUEUE_SEL_BITS-1:0] readout_counter; +logic [N_QUEUES-1:0] ccq_m_req_valid; +logic state; + +genvar i_gen_out_valid; +generate + for (i_gen_out_valid = 0; i_gen_out_valid < N_QUEUES; i_gen_out_valid++) begin + assign ccq_m_req_valid[i_gen_out_valid] = ccq_m_req[i_gen_out_valid].valid; + end +endgenerate + + +always_ff @(posedge aclk) begin + if (aresetn == 1'b0) begin + readout_counter <= 0; + state <= 0; + end + else begin + if(state == 0) begin + if(ccq_m_req_valid[readout_counter]) begin + state <= 1; + end + else begin + if(readout_counter == N_QUEUES-1) begin + readout_counter <= 0; + end + else begin + readout_counter <= readout_counter + 1; + end + end + end + else begin + if(m_req.ready == 1) begin + if(readout_counter == N_QUEUES-1) begin + readout_counter <= 0; + end + else begin + readout_counter <= readout_counter + 1; + end + end + end end end +genvar i_gen_out; +generate + for (i_gen_out = 0; i_gen_out < N_QUEUES; i_gen_out++) begin: gen_out + always_comb begin + ccq_m_req[i_gen_out].ready = 0; + + if(readout_conter == i_gen_out) begin + ccq_s_req[i_gen_in].ready = m_req.ready; + end + end + end +endgenerate + +assign m_req.data = ccq_m_req[readout_counter].data; +assign m_req.valid = ccq_m_req[readout_counter].valid; genvar i_gen; From db9a9e94a61533c7780e0dc8a92c791b7423a6e1 Mon Sep 17 00:00:00 2001 From: pomsarc Date: Fri, 7 Nov 2025 13:14:57 +0100 Subject: [PATCH 39/52] added comments --- .../README.md | 0 .../hw/CMakeLists.txt | 0 .../hw/src/init_ip.tcl | 0 .../hw/src/vfpga_top.svh | 0 .../img/rdma_results_read.png | Bin .../img/rdma_results_write.png | Bin .../img/rdma_system_overview.svg | 0 .../img/rdma_traffic_pattern.svg | 0 .../sw/CMakeLists.txt | 0 .../sw/src/client/main.cpp | 0 .../sw/src/include/constants.hpp | 4 +- .../sw/src/server/main.cpp | 0 .../sw/src/client/main.cpp | 140 ------------- .../sw/src/include/constants.hpp | 9 - .../sw/src/server/main.cpp | 118 ----------- .../README.md | 186 ------------------ .../hw/CMakeLists.txt | 32 --- .../hw/src/init_ip.tcl | 2 - .../hw/src/vfpga_top.svh | 71 ------- .../img/rdma_results_read.png | Bin 130953 -> 0 bytes .../img/rdma_results_write.png | Bin 131862 -> 0 bytes .../img/rdma_system_overview.svg | 1 - .../img/rdma_traffic_pattern.svg | 1 - .../sw/CMakeLists.txt | 26 --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 58 ++++-- 25 files changed, 45 insertions(+), 603 deletions(-) rename examples/{MT_pomsarc_09_CC_Testbench => Congestion_Control_Test}/README.md (100%) rename examples/{MT_pomsarc_09_CC_Testbench => Congestion_Control_Test}/hw/CMakeLists.txt (100%) rename examples/{MT_pomsarc_09_CC_Testbench => Congestion_Control_Test}/hw/src/init_ip.tcl (100%) rename examples/{MT_pomsarc_09_CC_Testbench => Congestion_Control_Test}/hw/src/vfpga_top.svh (100%) rename examples/{MT_pomsarc_09_CC_Testbench => Congestion_Control_Test}/img/rdma_results_read.png (100%) rename examples/{MT_pomsarc_09_CC_Testbench => Congestion_Control_Test}/img/rdma_results_write.png (100%) rename examples/{MT_pomsarc_09_CC_Testbench => Congestion_Control_Test}/img/rdma_system_overview.svg (100%) rename examples/{MT_pomsarc_09_CC_Testbench => Congestion_Control_Test}/img/rdma_traffic_pattern.svg (100%) rename examples/{MT_pomsarc_09_CC_Testbench => Congestion_Control_Test}/sw/CMakeLists.txt (100%) rename examples/{MT_pomsarc_10_single_sided_writes => Congestion_Control_Test}/sw/src/client/main.cpp (100%) rename examples/{MT_pomsarc_10_single_sided_writes => Congestion_Control_Test}/sw/src/include/constants.hpp (82%) rename examples/{MT_pomsarc_10_single_sided_writes => Congestion_Control_Test}/sw/src/server/main.cpp (100%) delete mode 100644 examples/MT_pomsarc_09_CC_Testbench/sw/src/client/main.cpp delete mode 100644 examples/MT_pomsarc_09_CC_Testbench/sw/src/include/constants.hpp delete mode 100644 examples/MT_pomsarc_09_CC_Testbench/sw/src/server/main.cpp delete mode 100644 examples/MT_pomsarc_10_single_sided_writes/README.md delete mode 100644 examples/MT_pomsarc_10_single_sided_writes/hw/CMakeLists.txt delete mode 100644 examples/MT_pomsarc_10_single_sided_writes/hw/src/init_ip.tcl delete mode 100644 examples/MT_pomsarc_10_single_sided_writes/hw/src/vfpga_top.svh delete mode 100644 examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_read.png delete mode 100644 examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_write.png delete mode 100644 examples/MT_pomsarc_10_single_sided_writes/img/rdma_system_overview.svg delete mode 100644 examples/MT_pomsarc_10_single_sided_writes/img/rdma_traffic_pattern.svg delete mode 100644 examples/MT_pomsarc_10_single_sided_writes/sw/CMakeLists.txt diff --git a/examples/MT_pomsarc_09_CC_Testbench/README.md b/examples/Congestion_Control_Test/README.md similarity index 100% rename from examples/MT_pomsarc_09_CC_Testbench/README.md rename to examples/Congestion_Control_Test/README.md diff --git a/examples/MT_pomsarc_09_CC_Testbench/hw/CMakeLists.txt b/examples/Congestion_Control_Test/hw/CMakeLists.txt similarity index 100% rename from examples/MT_pomsarc_09_CC_Testbench/hw/CMakeLists.txt rename to examples/Congestion_Control_Test/hw/CMakeLists.txt diff --git a/examples/MT_pomsarc_09_CC_Testbench/hw/src/init_ip.tcl b/examples/Congestion_Control_Test/hw/src/init_ip.tcl similarity index 100% rename from examples/MT_pomsarc_09_CC_Testbench/hw/src/init_ip.tcl rename to examples/Congestion_Control_Test/hw/src/init_ip.tcl diff --git a/examples/MT_pomsarc_09_CC_Testbench/hw/src/vfpga_top.svh b/examples/Congestion_Control_Test/hw/src/vfpga_top.svh similarity index 100% rename from examples/MT_pomsarc_09_CC_Testbench/hw/src/vfpga_top.svh rename to examples/Congestion_Control_Test/hw/src/vfpga_top.svh diff --git a/examples/MT_pomsarc_09_CC_Testbench/img/rdma_results_read.png b/examples/Congestion_Control_Test/img/rdma_results_read.png similarity index 100% rename from examples/MT_pomsarc_09_CC_Testbench/img/rdma_results_read.png rename to examples/Congestion_Control_Test/img/rdma_results_read.png diff --git a/examples/MT_pomsarc_09_CC_Testbench/img/rdma_results_write.png b/examples/Congestion_Control_Test/img/rdma_results_write.png similarity index 100% rename from examples/MT_pomsarc_09_CC_Testbench/img/rdma_results_write.png rename to examples/Congestion_Control_Test/img/rdma_results_write.png diff --git a/examples/MT_pomsarc_09_CC_Testbench/img/rdma_system_overview.svg b/examples/Congestion_Control_Test/img/rdma_system_overview.svg similarity index 100% rename from examples/MT_pomsarc_09_CC_Testbench/img/rdma_system_overview.svg rename to examples/Congestion_Control_Test/img/rdma_system_overview.svg diff --git a/examples/MT_pomsarc_09_CC_Testbench/img/rdma_traffic_pattern.svg b/examples/Congestion_Control_Test/img/rdma_traffic_pattern.svg similarity index 100% rename from examples/MT_pomsarc_09_CC_Testbench/img/rdma_traffic_pattern.svg rename to examples/Congestion_Control_Test/img/rdma_traffic_pattern.svg diff --git a/examples/MT_pomsarc_09_CC_Testbench/sw/CMakeLists.txt b/examples/Congestion_Control_Test/sw/CMakeLists.txt similarity index 100% rename from examples/MT_pomsarc_09_CC_Testbench/sw/CMakeLists.txt rename to examples/Congestion_Control_Test/sw/CMakeLists.txt diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/src/client/main.cpp b/examples/Congestion_Control_Test/sw/src/client/main.cpp similarity index 100% rename from examples/MT_pomsarc_10_single_sided_writes/sw/src/client/main.cpp rename to examples/Congestion_Control_Test/sw/src/client/main.cpp diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp b/examples/Congestion_Control_Test/sw/src/include/constants.hpp similarity index 82% rename from examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp rename to examples/Congestion_Control_Test/sw/src/include/constants.hpp index 2b753a69a..2cffcfb43 100644 --- a/examples/MT_pomsarc_10_single_sided_writes/sw/src/include/constants.hpp +++ b/examples/Congestion_Control_Test/sw/src/include/constants.hpp @@ -2,8 +2,8 @@ // Placed in separate file to avoid any weird changes in one file not reflect in the other // Benchmark parameters -#define N_LATENCY_REPS 1000 -#define N_THROUGHPUT_REPS 1000 +#define N_LATENCY_REPS 100000 +#define N_THROUGHPUT_REPS 100000 // Default vFPGA to assign cThreads to; for designs with one region (vFPGA) this is the only possible value #define DEFAULT_VFPGA_ID 0 \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/src/server/main.cpp b/examples/Congestion_Control_Test/sw/src/server/main.cpp similarity index 100% rename from examples/MT_pomsarc_10_single_sided_writes/sw/src/server/main.cpp rename to examples/Congestion_Control_Test/sw/src/server/main.cpp diff --git a/examples/MT_pomsarc_09_CC_Testbench/sw/src/client/main.cpp b/examples/MT_pomsarc_09_CC_Testbench/sw/src/client/main.cpp deleted file mode 100644 index 1f2dbac0a..000000000 --- a/examples/MT_pomsarc_09_CC_Testbench/sw/src/client/main.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright (c) 2021-2024, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#include -#include -#include - -// External library for easier parsing of CLI arguments by the executable -#include - -// Coyote-specific includes -#include "cBench.hpp" -#include "cThread.hpp" -#include "constants.hpp" - -constexpr bool const IS_CLIENT = true; - -double run_bench( - coyote::cThread &coyote_thread, coyote::sgEntry &sg, - int *mem, uint transfers, uint n_runs, bool operation -) { - // When writing, the server asserts the written payload is correct (which the client sets) - // When reading, the client asserts the read payload is correct (which the server sets) - for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { - mem[i] = operation ? i : 0; - } - - // Before every benchmark, clear previous completion flags and sync with server - // Sync is in a way equivalent to MPI_Barrier() - auto prep_fn = [&]() { - coyote_thread.clearCompleted(); - coyote_thread.connSync(IS_CLIENT); - }; - - /* Benchmark function; as eplained in the README - * For RDMA_WRITEs, the client writes multiple times to the server and then the server writes the same content back - * For RDMA READs, the client reads from the server multiple times - * In boths cases, that means there will be n_transfers completed writes to local memory (LOCAL_WRITE) - */ - coyote::CoyoteOper coyote_operation = operation ? coyote::CoyoteOper::REMOTE_RDMA_WRITE : coyote::CoyoteOper::REMOTE_RDMA_READ; - auto bench_fn = [&]() { - for (int i = 0; i < transfers; i++) { - coyote_thread.invoke(coyote_operation, &sg); - } - - while (coyote_thread.checkCompleted(coyote::CoyoteOper::LOCAL_WRITE) != transfers) {} - }; - - // Execute benchmark - coyote::cBench bench(n_runs, 0); - bench.execute(bench_fn, prep_fn); - - // Functional correctness check - if (!operation) { - for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { - assert(mem[i] == i); - } - } - - // For writes, divide by 2, since that is sent two ways (from client to server and then from server to client) - // Reads are one way, so no need to scale - return bench.getAvg() / (1. + (double) operation); -} - -int main(int argc, char *argv[]) { - // CLI arguments - bool operation; - std::string server_ip; - unsigned int min_size, max_size, n_runs; - - boost::program_options::options_description runtime_options("Coyote Perf RDMA Options"); - runtime_options.add_options() - ("ip_address,i", boost::program_options::value(&server_ip), "Server's IP address") - ("operation,o", boost::program_options::value(&operation)->default_value(false), "Benchmark operation: READ(0) or WRITE(1)") - ("runs,r", boost::program_options::value(&n_runs)->default_value(10), "Number of times to repeat the test") - ("min_size,x", boost::program_options::value(&min_size)->default_value(64), "Starting (minimum) transfer size") - ("max_size,X", boost::program_options::value(&max_size)->default_value(1 * 1024 * 1024), "Ending (maximum) transfer size"); - boost::program_options::variables_map command_line_arguments; - boost::program_options::store(boost::program_options::parse_command_line(argc, argv, runtime_options), command_line_arguments); - boost::program_options::notify(command_line_arguments); - - PR_HEADER("CLI PARAMETERS:"); - std::cout << "Server's TCP address: " << server_ip << std::endl; - std::cout << "Benchmark operation: " << (operation ? "WRITE" : "READ") << std::endl; - std::cout << "Number of test runs: " << n_runs << std::endl; - std::cout << "Starting transfer size: " << min_size << std::endl; - std::cout << "Ending transfer size: " << max_size << std::endl << std::endl; - - /* Coyote completely abstracts the complexity behind exchanging QPs and setting up an RDMA connection - * Instead, given a cThread, the target RDMA buffer size and the remote server's TCP address, - * One can use the function initRDMA, which will allocate the buffer and - * Exchange the necessary information with the server; the server calls the equivalent function but without the IP address - */ - coyote::cThread coyote_thread(DEFAULT_VFPGA_ID, getpid(), 0); - int *mem = (int *) coyote_thread.initRDMA(max_size, coyote::defPort, server_ip.c_str()); - if (!mem) { throw std::runtime_error("Could not allocate memory; exiting..."); } - - // Benchmark sweep of latency and throughput - PR_HEADER("RDMA BENCHMARK: CLIENT"); - unsigned int curr_size = max_size; - std::cout << "Size: " << std::setw(8) << curr_size << "; "; - - coyote::sgEntry sg; - sg.rdma = { .len = curr_size }; - - double throughput_time = run_bench(coyote_thread, sg, mem, N_THROUGHPUT_REPS, n_runs, operation); - double throughput = ((double) N_THROUGHPUT_REPS * (double) curr_size) / (1024.0 * 1024.0 * throughput_time * 1e-9); - std::cout << "Average throughput: " << std::setw(8) << throughput << " MB/s; "; - - double latency_time = run_bench(coyote_thread, sg, mem, N_LATENCY_REPS, n_runs, operation); - std::cout << "Average latency: " << std::setw(8) << latency_time / 1e3 << " us" << std::endl; - - // Final sync and exit - coyote_thread.connSync(IS_CLIENT); - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/examples/MT_pomsarc_09_CC_Testbench/sw/src/include/constants.hpp b/examples/MT_pomsarc_09_CC_Testbench/sw/src/include/constants.hpp deleted file mode 100644 index 225739d5f..000000000 --- a/examples/MT_pomsarc_09_CC_Testbench/sw/src/include/constants.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// Constants shared by both client and server; -// Placed in separate file to avoid any weird changes in one file not reflect in the other - -// Benchmark parameters -#define N_LATENCY_REPS 1 -#define N_THROUGHPUT_REPS 64 - -// Default vFPGA to assign cThreads to; for designs with one region (vFPGA) this is the only possible value -#define DEFAULT_VFPGA_ID 0 \ No newline at end of file diff --git a/examples/MT_pomsarc_09_CC_Testbench/sw/src/server/main.cpp b/examples/MT_pomsarc_09_CC_Testbench/sw/src/server/main.cpp deleted file mode 100644 index 20b6b984e..000000000 --- a/examples/MT_pomsarc_09_CC_Testbench/sw/src/server/main.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright (c) 2021-2024, Systems Group, ETH Zurich - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. 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. - * 3. Neither the name of the copyright holder 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. - */ - -#include -#include -#include - -// External library for easier parsing of CLI arguments by the executable -#include - -// Coyote-specific includes -#include "cThread.hpp" -#include "constants.hpp" - -constexpr bool const IS_CLIENT = false; - -void run_bench( - coyote::cThread &coyote_thread, coyote::sgEntry &sg, - int *mem, uint transfers, uint n_runs, bool operation -) { - // When writing, the server asserts the written payload is correct (which the client sets) - // When reading, the client asserts the read payload is correct (which the server sets) - for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { - mem[i] = operation ? 0 : i; - } - - for (int i = 0; i < n_runs; i++) { - // Clear previous completion flags and sync with client - coyote_thread.clearCompleted(); - coyote_thread.connSync(IS_CLIENT); - - // For writes, wait until client has written the targer number of messages; then write them back - if (operation) { - while (coyote_thread.checkCompleted(coyote::CoyoteOper::LOCAL_WRITE) != transfers) {} - - for (int i = 0; i < transfers; i++) { - coyote_thread.invoke(coyote::CoyoteOper::REMOTE_RDMA_WRITE, &sg); - } - // For reads, the server is completely passive - } else { - - } - } - - // Functional correctness check - if (operation) { - for (int i = 0; i < sg.rdma.len / sizeof(int); i++) { - assert(mem[i] == i); - } - } -} - -int main(int argc, char *argv[]) { - // CLI arguments - bool operation; - unsigned int min_size, max_size, n_runs; - - boost::program_options::options_description runtime_options("Coyote Perf RDMA Options"); - runtime_options.add_options() - ("operation,o", boost::program_options::value(&operation)->default_value(false), "Benchmark operation: READ(0) or WRITE(1)") - ("runs,r", boost::program_options::value(&n_runs)->default_value(10), "Number of times to repeat the test") - ("min_size,x", boost::program_options::value(&min_size)->default_value(64), "Starting (minimum) transfer size") - ("max_size,X", boost::program_options::value(&max_size)->default_value(1 * 1024 * 1024), "Ending (maximum) transfer size"); - boost::program_options::variables_map command_line_arguments; - boost::program_options::store(boost::program_options::parse_command_line(argc, argv, runtime_options), command_line_arguments); - boost::program_options::notify(command_line_arguments); - - PR_HEADER("CLI PARAMETERS:"); - std::cout << "Benchmark operation: " << (operation ? "WRITE" : "READ") << std::endl; - std::cout << "Number of test runs: " << n_runs << std::endl; - std::cout << "Starting transfer size: " << min_size << std::endl; - std::cout << "Ending transfer size: " << max_size << std::endl << std::endl; - - // Allocate Coyothe threa and set-up RDMA connections, buffer etc. - // initRDMA is explained in more detail in client/main.cpp - coyote::cThread coyote_thread(DEFAULT_VFPGA_ID, getpid(), 0); - int *mem = (int *) coyote_thread.initRDMA(max_size, coyote::defPort); - if (!mem) { throw std::runtime_error("Could not allocate memory; exiting..."); } - - // Benchmark sweep; exactly like done in the client code - PR_HEADER("RDMA BENCHMARK: SERVER"); - unsigned int curr_size = min_size; - while(curr_size <= max_size) { - coyote::sgEntry sg; - sg.rdma = { .len = curr_size }; - run_bench(coyote_thread, sg, mem, N_THROUGHPUT_REPS, n_runs, operation); - run_bench(coyote_thread, sg, mem, N_LATENCY_REPS, n_runs, operation); - curr_size *= 2; - } - - // Final sync and exit - coyote_thread.connSync(IS_CLIENT); - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/README.md b/examples/MT_pomsarc_10_single_sided_writes/README.md deleted file mode 100644 index f5387b7fd..000000000 --- a/examples/MT_pomsarc_10_single_sided_writes/README.md +++ /dev/null @@ -1,186 +0,0 @@ -# Coyote Example 8: Using the FPGA as a SmartNIC for Remote Direct Memory Access (RDMA) -Welcome to the eigth Coyote example! If you've made it to this point, you've already learnt all the essential concepts of FPGA-CPU interaction for data movement and control flow. Thus, we're adding a new component by demonstrating how Coyote can be used to turn the FPGA into a SmartNIC for 100G RDMA-over-Converged-Ethernet (RoCE v2), allowing for efficient, high-performance data exchange between remote processing nodes and FPGA-offloaded preprocessing of the data streams. As with all Coyote examples, a brief description of the core Coyote concepts covered in this example is included below. - -##### Table of Contents -[RDMA Overview](#rdma-overview) - -[Example Overview](#example-overview) - -[Hardware Concepts](#hardware-concepts) - -[Software Concepts](#software-concepts) - -[Additional Information](#additional-information) - -## RDMA Overview -Remote Direct Memory Access over Converged Ethernet (RoCE v2) is a high-performance networking protocol originally developed for High-Performance Computing. It aims to combine high throughput, low latency and low CPU-utilization by offloading the network stack and direct memory access to the Network Interface Card (NIC), thus grounding its performance claims on host-bypassing and zero-copy: applications running on the host CPUs directly expose memory buffers for DMA to the NIC, so that data can be exchanged without including the host CPU or the OS running on it. In this example, we demonstrate how Coyote can be configured to use the FPGA in the role of such a NIC, allowing for fully protocol-compliant RoCE v2-traffic at 100 Gbps. -For further understanding of the RDMA example, we need to shed some light on the intrinsics of this network standard. -For RDMA communication, two nodes are connected via a so-called Queue Pair (QP), based on previously exchanged information such as respective IP-addresses, location and size of the allocated memory buffers and access keys, and comprising of the idea of queues for work commands and data. The initial exchange of RDMA operations are managed through so-called InfiniBand-verbs, which describe the form of memory transaction between the remote nodes. We have to separate between *one-sided operations* that initiate data transfers over the network without a previous request for consent, and *two-sided operations* where every data transaction is preceeded by an additional exchange between the communicating nodes. Coyote implements the two one-sided RDMA operations: -- ```RDMA WRITE```: One node writes data of certain length to a remote buffer in the other node. Every sent WRITE packet has to be explicitly acked by the receiving node. -- ```RDMA READ```: One node requests to read data of certain length from a remote buffer in the other node. The READ REQUEST packet is implicitly ACK'ed when the remote node sends back RDMA RESPONSE packets with the requested data. - -**IMPORTANT TERMINOLOGY:** -To have a better understanding of this example and the following descriptions, a short overview of the RDMA-specific terminology is very helpful: -- *QPN*: Queue Pair Number. Identifies a RDMA connection between two remote nodes. -- *vaddr*: Virtual address of the remote exposed buffer. For setting up a Queue Pair, a node has to share the virtual address of its exposed buffer with the remote node. -- *rkey*: Remote key for accessing the exposed buffer of the remote node. The remote key is communicated alongside the virtual address as a form of access control. -- *PSN*: Packet Sequence Number used to organize and structure a communication flow between remote RDMA-nodes. Normally, PSN-sequencing is handled by the FPGA-offloaded network stack and thus is not visible to the user. However, following the PSN order is very helpful for debugging RDMA networks. The initial PSN is agreed upon between the remote nodes as part of the initialization of the QP. -- *MTU*: Maximum Transmission Unit, defines the maximum size of a packet. Since RDMA operates on the notion of buffer transmission, the length of the data to be transmitted determines the number of MTU-sized packets that are required for the operation. While the MTU is a compile-time parameter in Coyote, the default size is 4 KB. This means that a ```RDMA WRITE``` of a buffer of up to 4 KB size results in a single packet with ```RDMA WRITE ONLY``` opcode, while a buffer of 8 KB size would be transmitted in two packets, one carrying ```RDMA WRITE FIRST```, the other with ```RDMA WRITE LAST```. For even longer buffers, an arbitrary number of ```RDMA WRITE MIDDLE``` packets is inserted in the exchange. - -## Example Overview -This example measures throughput and latency of RDMA data exchange between two remode nodes via a 100G switched network with both ```RDMA WRITE``` and ```RDMA READ``` operations. Assuming the simpler write functionality, we can very generally think about the process as moving of a data buffer from the host CPU to the local FPGA, where it is streamed to the FPGA-offloaded RDMA stack and then sent via the network to the remote FPGA. There, data is received and then written to the previously specified address of the remote buffer of this remote host CPU. Different to all previous examples, this requires two servers with FPGAs, which also run different software application. The active node, which initiates communication exchange, is referred to as the "client", while the remote node is used as "server". An example of the dataflow is given in the figure below; and as shown in the figure, the steps are the following: -1) *QP Exchange*: As explained above, the RDMA connection in form of a Queue Pair needs to be set up between the remote nodes before the actual benchmarking test can begin. Since RDMA cannot be bootstrapped, this requires out-of-band communication between the two remote CPUs via classic TCP sockets. In the context of the HACC, we are using the 10G management network for this purpose. Using ```ifconfig```, the IP address of the CPU determined to be the server can be evaluated and then given to the experiment code as a parameter. The two nodes follow a standardized protocol to exchange relevant start-up information, such as the IP- addresses of the FPGAs in the 100G data network, the initial PSN and the address and access key of the network exposed data buffers. -2) *QP Setup*: After having exchanged these essential pieces of information, both CPUs forward the aggregated QP data to the FPGAs to create the endpoints of the RDMA flow in the hardware-offloaded network stack. This means making information such as the target IP address or buffer virtual address retrievable by the QPN as essential key. -3) *REMOTE WRITE*: In case of a WRITE-based benchmark, the client now begins with a ```REMOTE WRITE``` Coyote operation to transmit a local buffer via the network to the remote node. For this, both data and commands traverse the RDMA-allocated vFGPA and reach the RDMA stack on the FPGA, where RoCE v2 packets are created and then sent out via the network. -4) *Receiving data*: In the above example, the server FPGA receives the incoming RDMA packets and automatically checks for correctness of the PSN-sequence and QP-specific information. If the received packet fits into the expected communication flow, it gets ACK'ed: an ```RDMA ACK``` is sent back to the client as a reply. At the same time, the server FPGA sends the received data as a ```LOCAL WRITE``` (see Example 1) to the local buffer on the CPU. -5) *Processing ACKs & creating completions*: The client FPGA is constantly listening to the network for ACKs for its outstanding packets. If an ACK is received, the original work command for this transaction is marked as completed, which is also communicated to the local client CPU. If no ACK is received within a certain time interval after sending out a packet, a retransmission is issued. In this case, the same packet is sent again, assuming that it was originally lost in the network or not properly received by the server. - -
- -
- -Generally speaking, throughput and latency tests behave vastly different, also depending on the chosen mode of operation (WRITE vs. READ). The different cases are depicted in the figure below and can be understood as following: -- *Latency* for ```RDMA WRITE```: The client issues a single WRITE of a buffer of specified length to the remote server. Upon reception, the server ACKs and then writes back this very buffer to the client, thus creating a typical "ping-pong pattern" of communication, for which the two-way latency can be measured from sending out a buffer to receiving it back. Depending on the specified experimental arguments, this exchange is repeated for a certain number of times before the average latency of all transmissions is reported. -- *Throughput* for ```RDMA WRITE```: The client issues *n* WRITEs (depending on specification of the argument in experiment execution) of a buffer of specified length to the remote server and waits for all required ACKs. Upon reception, the server writes back the same buffer *n* times again. -- *Latency* for ```RDMA READ```: The client issues a single READ of a buffer of specified length from the remote server. Instead of ACK'ing the server sends the requested data via ```RDMA READ RESPONSEs```. -- *Throughput* for ```RDMA READ``` In this case, the client issues *n* READs (depending on specification of the argument in experiment execution) of a buffer of specified length from the remote server and waits for data delivery via ```RDMA READ RESPONSEs``` from there. The server in this case does not issue reflective READs to the client. - -
- -
- -**IMPORTANT:** When executing RDMA benchmarks, it's important to always start the server's software first before doing the same on the client. The reason for this lies in the intrinsics of the QP exchange: The server software is constructed to listening for incoming TCP connections from the client to then take the passive role in the off-channel exchange of information. - - -## Hardware Concepts -The core complexity for RDMA in Coyote is hidden from the user within the network stack. The RDMA-dedicated vFPGA is mainly used for connecting interfaces without any additional user logic required. -The previously introduced send- and receive-queues are connected as following in the vFPGA: -```Verilog -always_comb begin - /* - * CONTROL SIGNALS - * - * rq_(wr|rd) are two more Coyote interfaces, which act as inputs to the user application - * They corresponds to network write/read requests, set from the host software and driver - * Here, they are used to set Coyote's generic send queues, previously discussed in Example 7. - */ - // Write - sq_wr.valid = rq_wr.valid; - rq_wr.ready = sq_wr.ready; - sq_wr.data = rq_wr.data; // Data field holds information such as remote, virtual address, buffer length etc. - sq_wr.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host - sq_wr.data.dest = is_opcode_rd_resp(rq_wr.data.opcode) ? 0 : 1; - - // Reads - sq_rd.valid = rq_rd.valid; - rq_rd.ready = sq_rd.ready; - sq_rd.data = rq_rd.data; // Data field holds information such as remote, virtual address, buffer length etc. - sq_rd.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host - sq_rd.data.dest = 1; -end -``` - -On the other hand, the data interfaces are connected as following in the module: - -```Verilog -/* - * DATA SIGNALS - * - */ -// Data streams for outgoing RDMA WRITEs (from local host to network stack to remote node) -`AXISR_ASSIGN(axis_host_recv[0], axis_rreq_send[0]) - -// Data streams for incoming RDMA READ RESPONSEs (from remote node to network stack to local host) -`AXISR_ASSIGN(axis_rreq_recv[0], axis_host_send[0]) - -// Data streams for outgoing RDMA READ RESPONSEs (from local host to network stack to remote node) -`AXISR_ASSIGN(axis_host_recv[1], axis_rrsp_send[0]) - -// Data streams for incoming RDMA WRITEs (from remote node to network stack to local host) -`AXISR_ASSIGN(axis_rrsp_recv[0], axis_host_send[1]) -``` - -Thinking one step further, beyond the scope of this performance benchmark, it becomes obvious how any pipelined user logic can be placed on these interfaces to process incoming or outgoing RDMA traffic. Placing customized user logic directly on these datapaths is one of the key benefits of a FPGA-based SmartNIC such as Coyote with the network configuration. - - -## Software Concepts -As described above, the main notion of logic abstraction in RDMA is the Queue-Pair (QP), which connects two remote nodes. Both nodes first form their own local Queue, which again is directly linked to the memory buffer that is exposed via the network. Afterwards, an off-channel exchange via TCP/IP is started to exchange the local queues and then form a QP. - -In Coyote, the notion of a QP is directly linked to a ``coyoteThread``. Thus, the first step of setting up RDMA in Coyote is creating such a thread: -```C++ -coyote::cThread coyote_thread(DEFAULT_VFPGA_ID, getpid(), 0); -```` - -Following the creation of the thread, it's necessary to call the function ```initRDMA()```, which allocates an RDMA buffer of the requested size and performs the QP exchange between the client and server. Importantly, the client needs to pass the server's IP address, while the server code leaves this field blank (nullptr, indicating that it's the server). The function return a pointer to the memory buffer to be used for RDMA communication. -```C++ -int *mem = (int *) coyote_thread.initRDMA(max_size, coyote::defPort, server_ip.c_str()); -``` - -After the setup is completed, we follow the standard approach in Coyote: creating a scatter-gather entry and issuing an ``ìnvoke(...)``` call, but this time indicating a remote operation: -```C++ -coyote::sgEntry sg; -sg.rdma = { .len = curr_size }; -coyote_thread.invoke(coyote::CoyoteOper::REMOTE_RDMA_WRITE, &sg); -``` - - -## Additional Information - -### Special remarks on building Coyote for RDMA experiments -Since different software versions are required for server and client to run a benchmark, two cmake builds have to be triggered to obtain the correct executables. In order to build the server code, one needs to specify `-DINSTANCE=server`, while a build of the client software is specified with `-DINSTANCE=client`: -```bash -cd sw/ - -mkdir build_server && cd build_server -cmake ../../ -DINSTANCE=server && make - -cd ../ -mkdir build_client && cd build_client -cmake ../../ -DINSTANCE=client && make -``` - -### Command line parameters and hints on running the experiment -As said above, it's crucial to start the software for experiments first on the node that we want to use as server, before doing the same for the client. Furthermore, it's important that the IP address specified as argument on the client machine **belongs to the server-CPU (not the client CPU, not the server FPGA)**. The different available network interfaces can be explored with ```ifconfig```. -The following description helps to match the relevant command line parameters to details of the experiment execution described before: - -- `[--ip_address | -i] ` IP address of the server CPU for out-of-band QP-exchange via TCP-sockets before the actual RDMA experiment can begin. -- `[--operation | -o] ` Decides whether the benchmark is performed for WRITE (1) or READ (0) operations. Default: 0 -- `[--min_size | -x] ` Minimum size of transferred buffer in the experiment. Default: 64 [B] -- `[--max_size | -X] ` Maximum size of transferred buffer in the experiment. Default: 1048576 [B] ~ 1 [MB] -- `[--runs | -r] ` Number of test runs, to obtain statistically significant results For latency-tests, `r` ping-pong exchanges will be executed. For throughput tests, `r` independent exchanges of 64 messages are executed. - -### Network debugging -Coyote provides tooling to check the status of network transmissions and identify potential problems, most notable packet losses and retransmissions. These statistics can be queried via -`cat /sys/kernel/coyote_sysfs_0/cyt_attr_nstats`. - -A typical output for this could look like this: -``` - -- NET STATS QSFP0 - -RX pkgs: 316 -TX pkgs: 242 -ARP RX pkgs: 4 -ARP TX pkgs: 2 -ICMP RX pkgs: 0 -ICMP TX pkgs: 0 -TCP RX pkgs: 0 -TCP TX pkgs: 0 -ROCE RX pkgs: 245 -ROCE TX pkgs: 240 -IBV RX pkgs: 240 -IBV TX pkgs: 240 -PSN drop cnt: 0 -Retrans cnt: 0 -TCP session cnt: 0 -STRM down: 0 -``` -In this case, 240 RDMA packets have been received and sent (``IBV RX/TX``), no packet has been lost or retransmitted. A major problem would be indicated by ``STRM down: 1``, as this would signal a complete shutdown of the networking stack. In such a case, only a hard reset including reprogramming the FPGA can save the user. -Apart from these Coyote-provided utilities, standard network debugging tools such as switch- or external NIC-based traffic capturing can be used to understand traffic issues and patterns. As described in the next example, we also offer a 100G-capable traffic sniffer for Coyote that directly produces pcap-files compatible to any standard network analysis tool such as wireshark. If even this low-level debugging capabilities are not deemed satisfactory, also classic ILA-based probing directly on data- and controlpaths of the FPGA-design is possible. - -### Expeted results -The following results are expected for reads (left) and writes (right): - -

- - -

\ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/hw/CMakeLists.txt b/examples/MT_pomsarc_10_single_sided_writes/hw/CMakeLists.txt deleted file mode 100644 index e729f3666..000000000 --- a/examples/MT_pomsarc_10_single_sided_writes/hw/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# CMake configuration -cmake_minimum_required(VERSION 3.5) -set(CYT_DIR ${CMAKE_SOURCE_DIR}/../../../) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CYT_DIR}/cmake) -find_package(CoyoteHW REQUIRED) - -project(example_08_perf_rdma) -message("*** Coyote Example 8: Remote Direct Memory Access (RDMA) [Hardware Build] ***") - -# Enables two streams from host memory (1 for requests, 1 for response) -set(EN_STRM 1) -set(N_STRM_AXI 2) - -# Incldue Coyote's RDMA stack during synthesis -set(EN_RDMA 1) - -# Number of vFPGAs (user applications) -set(N_REGIONS 1) - -# Build with optimization, since timing closure with RDMA can be difficult -set(BUILD_OPT 1) - -# Confirm that the selected options are allowed -validation_checks_hw() - -# Load a user application in Configuration #0, Region #0 -load_apps ( - VFPGA_C0_0 "src" -) - -# Create the hardware project -create_hw() diff --git a/examples/MT_pomsarc_10_single_sided_writes/hw/src/init_ip.tcl b/examples/MT_pomsarc_10_single_sided_writes/hw/src/init_ip.tcl deleted file mode 100644 index 64dc40e3d..000000000 --- a/examples/MT_pomsarc_10_single_sided_writes/hw/src/init_ip.tcl +++ /dev/null @@ -1,2 +0,0 @@ -create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name ila_perf_rdma -set_property -dict [list CONFIG.C_PROBE17_WIDTH {128} CONFIG.C_PROBE14_WIDTH {128} CONFIG.C_NUM_OF_PROBES {18} CONFIG.C_EN_STRG_QUAL {1} CONFIG.ALL_PROBE_SAME_MU_CNT {2}] [get_ips ila_perf_rdma] \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/hw/src/vfpga_top.svh b/examples/MT_pomsarc_10_single_sided_writes/hw/src/vfpga_top.svh deleted file mode 100644 index 240e06b7e..000000000 --- a/examples/MT_pomsarc_10_single_sided_writes/hw/src/vfpga_top.svh +++ /dev/null @@ -1,71 +0,0 @@ -always_comb begin - /* - * CONTROL SIGNALS - * - * rq_(wr|rd) are two more Coyote interfaces, which act as inputs to the user application - * They corresponds to network write/read requests, set from the host software and driver - * Here, they are used to set Coyote's generic send queues, previously discussed in Example 7. - */ - // Write - sq_wr.valid = rq_wr.valid; - rq_wr.ready = sq_wr.ready; - sq_wr.data = rq_wr.data; // Data field holds information such as remote, virtual address, buffer length etc. - sq_wr.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host - sq_wr.data.dest = is_opcode_rd_resp(rq_wr.data.opcode) ? 0 : 1; - - // Reads - sq_rd.valid = rq_rd.valid; - rq_rd.ready = sq_rd.ready; - sq_rd.data = rq_rd.data; // Data field holds information such as remote, virtual address, buffer length etc. - sq_rd.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host - sq_rd.data.dest = 1; -end - -/* - * DATA SIGNALS - * - */ -// Data streams for outgoing RDMA WRITEs (from local host to network stack to remote node) -`AXISR_ASSIGN(axis_host_recv[0], axis_rreq_send[0]) - -// Data streams for incoming RDMA READ RESPONSEs (from remote node to network stack to local host) -`AXISR_ASSIGN(axis_rreq_recv[0], axis_host_send[0]) - -// Data streams for outgoing RDMA READ RESPONSEs (from local host to network stack to remote node) -`AXISR_ASSIGN(axis_host_recv[1], axis_rrsp_send[0]) - -// Data streams for incoming RDMA WRITEs (from remote node to network stack to local host) -`AXISR_ASSIGN(axis_rrsp_recv[0], axis_host_send[1]) - -// Tie off unused interfaces -always_comb axi_ctrl.tie_off_s(); -always_comb notify.tie_off_m(); -always_comb cq_rd.tie_off_s(); -always_comb cq_wr.tie_off_s(); - -// ILA for debugging -ila_perf_rdma inst_ila_perf_rdma ( - .clk(aclk), - .probe0(axis_host_recv[0].tvalid), // 1 - .probe1(axis_host_recv[0].tready), // 1 - .probe2(axis_host_recv[0].tlast), // 1 - - .probe3(axis_host_recv[1].tvalid), // 1 - .probe4(axis_host_recv[1].tready), // 1 - .probe5(axis_host_recv[1].tlast), // 1 - - .probe6(axis_host_send[0].tvalid), // 1 - .probe7(axis_host_send[0].tready), // 1 - .probe8(axis_host_send[0].tlast), // 1 - - .probe9(axis_host_send[1].tvalid), // 1 - .probe10(axis_host_send[1].tready), // 1 - .probe11(axis_host_send[1].tlast), // 1 - - .probe12(sq_wr.valid), // 1 - .probe13(sq_wr.ready), // 1 - .probe14(sq_wr.data), // 128 - .probe15(sq_rd.valid), // 1 - .probe16(sq_rd.ready), // 1 - .probe17(sq_rd.data) // 128 -); diff --git a/examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_read.png b/examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_read.png deleted file mode 100644 index efc149483e13286d8c1704987b5085e0bb1f47c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130953 zcmeFZRaBhM5;%wjx1b@xNzj48f?Lqw76|Tx1a}`GxCe*ePH=a3CwOpo*TLDz_07G% zm)(8XhyQTqOn==~UESqf{T+f8fvrbIg!bS?XFkKg zpbMCZh$u*kh>$DTSsR;KfM8%g2FHCyQd1rvNYjk|CS(eWkmI+E080m(d9Abdxd>6@I80Rus$F?bT}3c;WkTa^zyli_<7;n?wtq=Kwo(PC$L*; z2mQc_&BQJduSjT-MkX@ss~YY)7F^menL%vyI~2)p_t}Vx9t6ciKYe~h^`Rj9{(@O2 z*S9jN|0+L*jZi@mz<`iSO{>=@QjB&c3(^4LGt~UO)ODVIdQ`}&!#U-^odEkou%c#>%s1?eZo3-v5^jWzJHKHHdTHJK z)N*<77P?)mF!9hAP7&VmEoc{16(GnDvdhMQs;3j=R2xhdK8)6VmH#S~hM@fg-E1G5 z=!@~QbH1}I3pvwjJ~9s?WF!DK%&rieCQl-IrB6mxSp}BPfcsY%q@WMftMKkG*w`s0 zXQ=%8xD*hc{DQE~1N&}=+-@_l&vgUUZ^ucE`rXh#RCevSK^H==`u%5d=4jV!szpy< zv3~Uu-D&UjzuoSnK6-{cVBe?e3sY-#d<>wRm=53z_UVQ(KxPRt^*z`Ss3#=Fj0Bf+ zZ=f$4nv$+OqR1m_`vv-$`&o6CNPaaqlUEn_wkjaYDGE4 zYbSN~u}!Q8h8?1+$5UFHsq6}xJzF9QE@JqT%|(SpOMH9mpAn4wRHNcZq8c@G*LT-% zFH2U+X~D?%^aJA$O|dpYJ|0UlOaX_B*~3b9QoY zdLeY@8P(Rh+}+>$CP=Q&+3JZ5#)XWGn3(S&6^$RWUBLO~hSO`OnCNse6TN-TxKvb& zcE@JV2WAE5%c0*2O)Cg7mDPhZ+(NA?*a9rJ$m$sDBC_t3YhUe z(-}Ax2%7DfGP)5DcR&nYPkK3mw;dQ*hr)sB3Tb( z#wL4M$xJoID&U6nsBRKM}vlQ}um{W-6Ru9#MuYUJrtp z;JI$1?w;nr_$#YQO`!Qis$usx$P%?t@qJMHgdO&l4aWiz2^xPx_u_t6*HaOo% zd0TNtX`e2i&Y8|SHZD#!jz88g_Qjxm+${Zxia^nW&~0`|UPuY5^g9)?tV&T((;!Nm zCcSmRr^0vBk>7tPRC`?e%EsUB5OeKE7oer6z zpTaEA{&b*BQ>0l~HO0MOHC;57JZ)WoW%`png*}V?oSne@aEdGcOeQAB><3Z7z$9RN zG0Roz;w46Q(zqHslcj(q!x4LlL}&phw`XQ>hJJRwWctT+S;>#1tkeR2*(Ry`0;f96 zJi{MdGXvA=g;lycjidQ51E>K_`NK;jIhjG>SS3S zS%GAYBa@0bVNKZ;-ofjGA`a0v0kRPzZpqTM1tmq6@s@E%V!TtMBXW9m z%cbUc`mueqYX#pGIS*A1ldk=*9j~#79f&(gLP%bd=tOXkrLwx+?p}|t5NngzlhpAn z+fF#3I?_9Rv2{DLI2d*;wQJ=G;Gs4hPGONX)M@zDaH`Ewzvz(LAYRX8W9j&~zkN*Y zROEc%$muL^U*^=-@zX$*jeu>`4V&+Qf7^xl>}GdwTYa@*xE>dCy|X=C$A{yVX!4Pgx*f9jO!kH^*{3u z#k>!7q=Z33#-v5tMV~^KK`usbCNp{+OZ16witO+;AJvU0GR0efk!wFjEk*KBDF_nS z7&wY*iKdJaidKZ~{>thN6Q*^r5S1{Ms`z4fbr7C7hqw=|)6`&r`?%`3au!w}wqcfG zp+Q)t~Mw<7m~J)(V#9b}VkD|hQX+IxTQ&e{R-n0(n|oVj2ru_a}`{Nl6;ASSN$ zki8J4M-KS(j?{6U zNu%xNBrIbV6U(&lVY&1ro<0k?ft0j{4twX}?BSs#hFR0@P>w;KBQhGgMKs)`p|sEO&S)b1bcOBRi()PJ(j5>cm8pKRgqu$<(% za2%TP{4reEth%LJlRux~rg(LK)qIt}d1G0yVp?C}v5_zPRhE+z$=a%*qWH%z;YFqT zt$~^DlA|^jaM4m;TZzZQ>`~{@60d=C^>pLx%rX0~hHA``n5nB{sU=4<=l0_~xBI&h zcZciPlNavq+|y5_7o0u4em%{=hrwSVF=DEcWs%UE3%$;_rteyQOg<2(qnysC$1E3 zXANbS^5KF-x*BbKHwi1cB##?+?mEG`7_B-F;wK~5bxhWpNLfh7ZA0#mThBS97A#%? zR)JbCsk_U|iz#3Z(7bKl!}6Bk`si41c{L-5YP%o&g8A3aFmxClYb5XQ=r9<^W3(Gv z_ku|j=ln(JKF@wz?xINzunJhuD~5NgD%*H-=uz=E=PR3UbJZx<}@2qLz{fa@=n zp1D&&He}^=`{r&sJU5)wEA~&{AY&%vDvq#%>CZ1db$f6#8X_)14;1krHA!PxSr~d~ z8yV&WEDj7jv;_c|18y+Cg+1Z)NnT^TX&IHK9&CLyDW(BgcGD0C3?Om-L^j#RO?5X}f z$v@{21=$$+%+R=fZlJYm9e|`SGPLPY)zbRSS|6^Ow4FZ3U09lxr zf&UB5!OZwC(0-5n9qn)X`a3zk-;?nun7M!~)I`lJp{#~3O@Nh!nVs)%bpC7T-z)t) zs){|xPQ=<0is>NmZ(;od`9BB$MEn~~^?%c3dC$iBpEUnt=s!?@Z-Pe&WN&TZ_?w9; zR%Q+YP_+Nm{U?_CztIF(*rEIT8`6LF{)wUXKQaEZ_fHIYJ2NOF^nWi+faM<){hN@)EewnhjHKv?PcE>BsYvy2#jdW+)$M-y zi`n75?T?Nk&%)^rf_+CziQG>^yB6u4kwJ^7#o|p_Q0}b`xDQxi(!Yt(o=#kk*l@x^$jGo1>CpY#4Fk(_$PH-Aar zo07a|#9KA2^4%XR`_~L~R#S-o_eK9}h=Mr-`J*jc27a~$%75?e?_eQsYW`{n8J!kR zX%XGDhgS1{bI1Gl)-C@h<9<*6f42VrTZfTlgM<-TIN23(7=WSHC9)XiSOqAbA)Q-o>nxUPb!*H zsrzP6>g`>Y8V~Xyr3=>SrxU|TdsmdnEmVM(Er|(pxiqiC3Y$p^q`E~-ml)YDR$mkrr2KYUeF6t;_~z^;`xw~s?D&7rnc zVQAKO9peKVUL>BMT=vsB^FJg!c1P%EUU7abGZ8rNS7_FiEBnm5S)@PePas!zWOP?E z8*o3yOEY`}v0R(uwU)OUW8F#7U*9+Pe-~(@wL9{L8nS+u{LtguwHON2A4f@CkTh}Fw@my`QLC5bZ|Jl}XfxcyiPdHiD zU`RZIVnHpVRiS+qV6rH{_e`3YjJYVt=X|oXR|0aLD>;!$X1$W=<(<~o;Bx*7M_u|EQorTU zNgp$LkhkDC&ZmKxu4zufJFWPK-}7M!Yfsdncbv8D^OGoREws7H2Gk$9D|ce(@|7R) zMrwo2?j9=4%jCuuQu&-lPCfLr&pS6F>)cCd#z>nk*H2H-<=`f|_Z&~yClZUu#ON>4z%>ebg7 z?z{TaL54EFo#QqjpW7{HEedT+I>z;Mv8ogG*}Cp>K7IHi0KY5eYUR*6cW0wFTzafZ z?wA*{SpI;?Ys~+pA@JSsW81UrP)kFCyNKE*$q72Z)%|?i%MWSnPGsn!k?DJBTj9~< zAg^VA^j=ixXx$L=Y_xQpnoKU%Yd*8eCpDeAslMmgJkrM@F-EE49>HprSAt`Fl6yP4 zp5a<}ZcC*J%lZlN)npB)5AhyM71I)3Hx0TYG_W)!HBQnl+J8nsW{#Jya6^*CI2Qnl zSqeQd0r6yurw#lQ^Wbek=e(V>0XyE4?|1GEd*6Wm63=!YUTyR1Fzte zmWEOvlzq9+DUMumF|6N^kjU%F)A{^7v$p5hCilHmaD_D8>oSVKzkl!<(jd%y*{I#7 zzoXhSG8{;1I(+)w+g${oucv^?7;P}~d==sI?`@7n*Nd-XZblX^6@ZK~12`uuZS51=5oD@O$I&H)uyybeJgpnoJaZ;D?xZcD5hC)Z`BnJ0Cn_%v*B_L|zwT|!rDc2= zg;XPeR*5I!4y?W{8gNfjeb$N>NGqTJzUS)kHM-sOsb(3EK+V#zNd{n>mgMx=Z7CFY zup>fd44u2}{decB0Vj>2C=jhUB;7F}b({2Yf<698M$IgDvD_6)$7TKO^dUPkuZ=L> zoe5~_*0%C{8&965gsg3cSSAbL+0tqSXh4|NiQyXVeI2%PA;L8+>o}#mGCQFJ%DcX%tt}nf7~ihbHRmsg^Wg8iydA+aAE| zHPrHAMvH;Zt|L9#9g=z%x*5c3{BT%R=XXJP7-7V2`_T;dezapuHi|KdF@jP8afZ<9 zVV`5()}h5QyF?Zsd4RrxVY2>HiDshP=eWy@v~y?m;i$)OtA}r> z&Ptv%Kgrp1^65DNLJI*4?n9N$<1XFHnl5mKqGans7op>{Zr7aLyaRB-adJ!1?azEE z3=^T(uV>`S9n7Oudxg|h({zGE+IH@x%3K9(HdO=Xr=rbn9$wVZo7uF#o_r`OC0Iel zowd+T+k%&ATSN3B+D?DizXwyVXl_;`9Cy4R_-O8NAH;kX-c<(xMlVVOpE%im)vFs5 zX~12tUCyQOR>1fEL~O=*;zsA+CKvc2i)^1f%WQ=4;g~8$ob+?Y9>yeXqiB1>$yRU<;|O&v z8nz8)Tz60|N^g3ZH)=H}OwiDej4lqfpC!v42QD)~3~2;vhT)qyX!x3q>`Ruw?-67CfqzJ1WugN?83P>}MZ5qYew5`~zBb);>Qrpe;>sT0X zmGfnYtz})8iZQiLQ^PqS7Rm)vmBs7L_{{SpRzORVEi3ZX=W0(mEyJVJ&G6aW-1>_g zT#F?QANoy>pd*qIyKbCpS^EP)YZ^lTF8*x70+$B;HX@1Gn0M2Bt#R!5 zdq5(`W?< zj<#L;?3rFBr}eL_de`Gt_k*C?cbHE621AxkBIj~Rns8!-Tx&hngDKo0m9#Fi39_@g z)7K4~-*C`1+yb>bmqdng!j8GF^C^~Fm(#8`emOi|WrwNEI|`ovVE-@o=!zVMveT`~ zNNY{tI!54>M2nS|>h#5J>3CDH?Z$OzW{8U;BEhfPUGQM;vPd(*yyf^P`NeaxeZ{+p zS>r`Fd$;AH7{WBS#`pW^xu49vzkCk$I;A4f)zW*MAt`&ZKbK58S#&<;PJ$%Yt2_VB zvem>~(F%XZcbD@SO?!%Zd*mT6k}Ka!$S}Y2PC&_>-rFhN>B_B^&pFyVEk*bLQ|;6j zH^o?^kAY1~*K3*fqcT-|N1M1^teV!zI`=VwfdB*2+lAb3#nEH&yGHS*q%*$tUc`($qg z2HFfzIz@Kc<&!X8mpc15$qTe%;0-ii!qme`v2}BC?I*g#IdS)D7vB2e^~mtB zQ=jg4b}@__?*eh3@0~6RY$<4CJ(^4~pG$SZa3;Ozx|})cBgUWMKKB}0o>*R`LQ&fb4D%b*nkjd-~wF8^gqsmNh+JtIHu zNaO)4XWgZoXvL82OM)31=jY=(im*2c%T>4!!!m9c;`+fQf&?=>=ZVa>?frW;co2oR z*Iy3C`}i+MH#loGk^$xWUrN?=o4!{sBkp+u9DX%6vEK5DMy*zvw5q70Nn2cvmp_fk zTWSkj&(|K0Go%7N-CoM60bJVvl17R=Rr23s0g#T{V~ZF9;&{@NHY*VmpUufPs3f!Q zLFeB@xej&UuLyIAcG${1Bi_u;M;HplKeQvkUzv7~nqg_5e{LG)@7>!$1l(pgJiheu zlWlt2mwT0}Y@!a#KSAdYo7Yr59d;LppluVIuj!d%SY!l~{1-Dwki*IXicH!Q{w)YPW~PA|19F4<-GsDaAH`4y72LO0XUq(>pEn;2FcZ`s&_1hJUF| z1}$7J9_MFmM-`XrIg`*{lHN$Nbie8U59V|~tdL0Lwl^NH?^+K9rv1O4b3=zHE6WF8 z?yqb@ID;EuGo_xpI4=6HEKBOQanjY}sr}U)BfkOc)fnFWi$VWXf`6RMVugi5l9g=e z%KwVQcM&MScDbt5zpVL>Gaaoz)G~8dmGu6K1p9B(9nmj|{Y%qDg{8BS>d0;XD-x!d zP=N1U8gzeE>(0P~8h`9pX`eqG-#@GGKZ&6L?142(e^u=lqC?23r+bm`KQha|FB>Y1 zoO&}%^V~m+3IAk=kJ4`^hsyMV;IABzJ@W9pmQt58&r$VcCqr2?4#%QRSNUeNG$vLC z@89>14Sv2~3EZ2^51dxjWGrqvZWP{pkKh;j{p;W5GZozK^>mSb8YuXnuNHf{T-UtC z*mZmm@Ow0a4uPJ*N{;WW`*cU}nS0Ugpu|ikLCqjIM>*S&VWL4ciS?Z;M04dX9*>hQ zgy#pEk6{EiHFOCWYxCx1ve{ZDsg9j9n${%CXA#ahkaIawkJFCRxH6A-icC1$G=jI1 zIzL+%ckV@QQARjdJl1$uZsl~(GewIigzmo*XgiEYyi9SJkX$=%I>Ai8>88o4w>1qH z)f${?V%RSb;NLIE?!4VeCxZ^R=A_t<@jI(|UQKu93tatx9-z#(;~zs9N|8;{J)P>I zkDXmug2c<0d7SSAmk;olpx|q<%5o^GkvUhroUIj>b}EB`S=SPRcZy0JU7+yyGsxy8 zHLB3#5C44L=SvE^#g*qL$Xl+*pHKHEf``+S@`C&iN7i1_$73vg#B0eb55{)j=SN6j z8ThKGs}tAwtcMUwnz81S%ZdBS8v->wAANnEC9czU_^ype!L+HZ(x&5ix*H)u!gSKz z{efZPm4z{=AoRzKQypi74U_eO@%8$TQZE}aM5|GG^pI1ao@~vz?Or=mtE7HCM1RE- z!iZOEnnAyjUFmUny&-Iu@m0$76ykhvg^(F~kIg8FPNxOS1oVl=7^pZnGGf;xbX%d0`o3>~8*c3b63LVuS%dG& z)vRthEx!h-lk3@u*E8?Kp@|?Ga(r6tqMfwYoSjaMM;)|vbD1wl(%vx{&bj>RMzQsW za{iq}+^FLG!xqp>a^=NN4}m72WqY%JBf>liB4aDE{E+i;NV-hh18N8I`voN8XYRDY z3Qj`2hvgy?gTunLr zepbtA{;Fc|rT~?!tAHxhS{WvmOYmV0tLsO8+UrD*i;Y6?+Qj>E3~>bw+Y9JwxIyax zTZ8rv0Cp8s-I9~$4YV&w;N%l#vZT6I!xqnrX?qHVSFm`Y)e=EOg7b7UdbA|t4+6KF zF(ey}t6$MH5auI*mlW-w1nrsV{oDi{`W{&{%0{SR>V12uCuHRLUiBN4PxrVz9lFnV zv!|iD50tJ>-7h8lFgSjZGfSIXLN$s_WggIc+NodGaxLZ8#x};scG`44$N*nCm>Ed= zrOd(6DDUM#Y~`mKk3bS9H6DfEOiz|!Z@!$6UW@d4FwEi;cJ)<3of&kT@F&!HCGd0> znXj1j%g5R&3n~GwA5`(Jf)~SVYfyET+V_F9uU?gTd1?;Pkv*UFk)>;$%e@@3d_E>m z3zf$*D5)Ns6h>pjTfQoF&h?29xZAySm$BX#MRxFknaMmY;k)Y-e0sSXnK{C-0064( z-K+FUsN5!ktcpr%rQ&ZlaJGVJ(vWG=J?tu9!!6sLb|3|kJ>T>Zuvb(bA~BgEinmMB z^I@SOloa8G16!%50YdU~@4pM}QzFB-<3XF3S8yoj2S z<2{P)ZQDt657PJ$snYFry_Os0-bo?oksW70ee|(8Hha99#nQc-NQl<#Ua%TS zZZRPc(>kdfVsditChf_Wu$ZRX5YEU)UM>5kOX~S}pes+tlBtNKLPDmpW{68I^RZZb zM5t@$8S0(fI~Nq>GL_pg+9#xesR(;WOaj(aYt6Ciy=97qo*JZE7!fSv=K{8eo->u% zYonACId;4++3Z}+g^7L|Yo0W-^LC1gMhH6z`CE z?}zmQ5Nl9)PC`*{U@)8!+FS{~FDxpR;xYH2D6Sl&r{Ku$bB5E;lT9Wdb=%GKPNJ1= zzhQZ0D)Cv@(@{wm@WxP^`3NRd=)A+irl~bg>UMtX9oXq&gfo8#k_NupyJQ*qkmk1k zHet?IwG=-#EW9dn?sE>QnMh)kVNbjmz&?@1 zP#k9zaZ-TT^5qchJ_*??7Dr{TZWTO2YQi+A|IGoQG1cp>cfs>Pz1Vg6eqqt}x^AwA zh?+Y@GRI)>T7i$rXr9$bhHgsdP*>X8^^P zjC);OB?mRTrxq-aRrs!GpRzSWq{1Q(kFm?T)j%w=dybM3&rK>al(xqs>%gttB@81` z<;C*LB4m9IEPb)7W9K$r2)^-`G2WBr94`Y`l=C^tP(bwXjFS?!dQwpHtX=AjgA=~f zZ zdasLTSbDHVS+^%Ij+ zh=nw0#_3A)7D7ihEvv`H8Ardc3`Ov|8Rs|1_XTAS04T#ob7tgYhf3DxN;vytDb1fFt*oshMTKCP4*jy{*Z|05%Rb|CP-_fOOMQ- zc z(s%y^3~6#oB5g2{i><=ryzIjUm3L&eXw^Kqv;mlOxI;U8?=aTa?**Zy)vhQB(j?@l zMYckvR-XNqy{DeRg&``q#+T;1hQem%o3riiVs6DkmsPRNp-R=5Js4o8L8a;QBuA`0 z5K7fk(}&ZNY>yoOtQTG!wC^h}8;%JF`!iBC(9?N<~YR%Qa<>} zN50NzUhDRsm(Fhm_?zPkuPJs#5Pe7 z8~ZJ4ho`ChPH@ULsjUWsWGq8u$kObMgks)fa&Laq4@u60Du$P=?!B!N~&7won_iI{2qdc)r&5FbS*Q90?c%tK4Ca2VK`K^-Jtd3*<&Bf6I-Z8ypFEPaTn zEGVmW!M9t%Ah>yOtOMIm*x*&*OzmR;Yc>)|2 z)+fdhIkwC2yHsKT9(+2;cu5?k9jDyuTwhH=_RvgYJZ8DxT}IfJSoQOwYEl6JVa>uY z5`i9LfTpYwr9k9|#~k`Ch8W#Qu_Q=krvY8~`gU>Es1zP&-c9d$&yDPi`(k$4m8k9s z{^4a`YQNK2=J7eT&Z=!*b!M6(c{X{TVD9k%Th+C%(E21*jJ)%tQ(-@F=`_%4D}ml4 z#(J1quYKQ_>_N|H{7K5z_q5ylE24L~7^w5xo(r}c@5mr? z;Menl62_v(99%DI_-WLQ5#a#K->uMI3DC~Re|xRuO25qt>E-y$eG z(IRfp>u=UWV;D)3==97aWspl~IZYX8?-1iGTIg=8tl?p2@HlN-TLyTlepIX@z6eQr z>YIOTY^6eQO~n3gS(Za?*$Se%i-w*;_|(^pAY80$emIDsB!gy;k^U@!@yKT!FsnpT z06czI_{hdVlUq~goabs-+OOesR{e%a+POB1+s>N$+++ll)!N1@^Omw&)uiPg{dNUy zam-PxQ=ON&f57j5P}NB5p+*CRM9%kt++pK%2!k*t>rf6cxh6;gc=PE zC*M019Xgd>$8Oz}!qZOi;}D<1iTt|c6W`wUsW`^fDO$~kT~0m8pv-l!&TW#u(}Z;IhR&%f+h>P(;mX!x3^T@xF{VcQzO&Gk1Gw%`nf@{9 z!n$pF>K;5$Ny+8~i!N$tfKeIcNvJjZNW$_eB8iWLV}?pR7Nw0{eKV<%v&)2_506c4 z+*LTqLKXZZu;Z8jjA$dA9cFdU#elx{nXIc5odC{vgVDq#{-xg=%R!K?8}My94WKn{ zIDL0<=Ik5#c^;bv6`{)IJg-53KXuj@2994!*v}n#NC<(4twVwc8?Ed*>=c@qkOh7+ z6L}W%VtszNpn>qqf3#wM_?+vZM)f+0wX2jnE0)#mu%hpsVt+xbD<%S*Z>?JJ2_Sc3>ys3UFt49f?r)>I^Nad{0rGQ(B=xad z$uhsxo|_=c?pQH9wD2*zw&|9YZyuYT15i#D{1ZSf90UVHpJW$q2>uuco7e%T^GgsSRpJTTva z^NQ!>sxdfx^@}QCuM3VwwuUg}nAINEm4VG#E_j`~c2&sM{dIf_zWI4Ho4)}87 zk+qVimC6@WI*~P==;mi}Jjo)YEIeX)d^1P5Aqvt)gkbRya%g#^X zmF1Nt#GzC0iEnz2g6^vw=TW~>Vahh;7GCX4mV|_UetmbN#sL?3fhzpmymygzmykE~ zqE*f4m!9`^@TA>wSMu5CudD=V)|Z`Vz+YyGpm=p0X{q?*%z_o`w#R4_vDxALmsh(v zQnw17zVPuu(6>WnQ@|6X>PjJ$eSoWeyWxvQM#T668NEdOoE4k~?M1bLM9}M@c!ik^ zZ$mhYwtyQ6BLiThMbZKE4~3XiGH65TyJ}jqW3UXXTf3d5KM@>zjPBjFaJS2nPUj_1%0!gaIx-*7hq(qn{>T$ zQ!;XRQ9mrh&1*Rl33h)#jJESa3(#niJr;zL_K-Uoe1wsmX}#$F*K%~h-X|p zJecd=Yfu(vI)FIYl08${&6K{&&KJ@xy@V@inYSYv!<|TY&G$n|pwhV=7U``(j;Ytp z?m4)T7I#Qny>Rytb4C$RtItU)>2VrMDeATQ!iM^0`F7NqdWP*(a*r)7E7DqkaQZ%;8^t&W1_mnt%WHVDw}SMY!&*e>vnBN(D2$#M z5|b*n$l?ik@6=7Kqd{XE*|Xn!#vJ(VuutJU)2bM1S;cJ)(Ss)Dlz0KcP)8Rt!e@t- z$EM%uXA}UG9^Y9xm~KER?6y8gW@L1-{JMK%SoHeXFklcso0_3cdV6CauDIhoB`HMi zh3yN}%=1-2T99B3swHw@E4n0b;Nl5P{4qvpyjAs2K?aCS*N0B|a;v=!=hHV!bKRD# z)QiVax#)6NIT*`V$4}q`ku7|EQ^B!_2j?W;cl@$lba@_YCQ`>tZHM9G^zwz z5K%X;yI~P3m&C!71xuD~4b{TrKZOi48&ZUnv2T7b7wjI11UXluSo?h0V|TsuQ1Q4^ z*(|!gpbGviy~5fd9viwLPD``x?qFPL6Zav;IvFEVK<@}=nq|lbBM`tvibgzWSEognGOY5gjgu&oK`L<~`v+bn|AtYk+Ngb)lRA zjHN09ve{AX&ZX}c;P7-3SOcDG+tC*W~}y2bc4`*X%Un3N{)C$yq> zIsc}N^bq_~OlT?^+ixn>Bd3jHup;twAiCI&C+@?lpMO9 zp8I&Y)Ncm3HOiXeDCpo!I++da<^J9|o-lWlP1p+NYX$X6ES{4v~KheII`X7%}qE{ZQSz}NOj>ednTnBiY{@C#OE{)-Y|hbB6%aTP($MB2HGE6 zKG`Jwl1#3i&;N5i*LSyL{EXHk&AVEe5F)jBv$%JN(GTUq z!Y;-?m>)36cwL)PLgW*~8s0bWWcdJO%61k_e;3)e*1*)&_mG%B zd{dw);B+xa&~lv z%g|c$sn{_aOwHVPfJwJ4u}-3o9wN+i>reQH+U;=LWx^4qHA!28DqjW&M~j$1RpPNY$@NOH*q^;@15WBxoqjG zs(b#*8hI)od~?vCyx{kA1nZ8W+GkG zv@+o8`zJMgLNW=Wr_}}bY1;_-rhe`iFL4#TrR7LUurm2z7}c^9`|Dd>EuG>uiuu=w z*kZKXnrAhU+50yIZv=W#VHav*P!jlvmUq|$%DzeyOXMF|Z8a9(JVNFo$UrR%dpgj< zY$p#dU-=}wvCr}_G4Rg)sOEFyMmS+H4=8KNzPgxtN+0eG7GC9Yu21C*(ROyoaO3*s46bI%YtNf5vf9a)_C_2pzw9tiu(cQGeyUyK zR28L3b1}ftO;h)nT!|t)j1+vjiGO;r^_>jiEs@s|mY8o`s2nMXVWDN?lAtel+pwdp z%XM-qbdpp5ThengU+)RK?Z^aqvLp!Gg17Y>hjhNh0R1d27QZ?PEBr1Ab!E!hZg%AL zhsle=s|4|%TPRxh6b|@DP0Gqz^tvLbmNOa5s?5Ks^WKqM zx)`IHj;hP~>0zyDb{k)oXPpU~;ADgf;>Fjd%#4-VTR|R`W~>JL5MEXN=vNV+)ppi4 z=rQy3AK}&K5ESG`p)zC7@0?5t? zJ836jyB!|O0%gcbC9y>qSz4U=u-US=!loS4|1?oPMgoB@KJt{!IO3cBr>VCiod;aM zkXNzatfl%;`nrIpn#%0wC6_tceKmLn4X5PjLY`@;Y<^ia9UZcAPkcBNsQ}@PsV#U$ z$buzq<|>*qZMFj2t%n*8lg83hW8@Jrrq~8`tX0vdYjIQk>kMPIs@RW^sn~W3x2KlU z#&s+F%5=NYxsH%J@E9acjf{uAB+!SOp9#-HaDJBe(ZpK9qDFcH6S1=EwNws(5&47f zpOnkBg!DM1Dc3&`y~3g`iXYoC|8}snQ#HGT&Ud}y^=xyq7D&U|z3hIH+K*dJr;9a( zcGQLI2!GQ1#aA-19!rFYZsXx+U#fHKq#Q3uAOs0GGwYx%(8`O~22w^dSsyaV>Q6l>Vtjf~Xi~mC>}H?6GYu(r zk^)i7?p1}aw=Nt<=4R`TE7mC+L>k7ODXItr*iAx)z@1||!S?9F&>OwF8>fBvV5FAr z+(ND2C7r=_MC#xg3|~4bU6w`o2LLXQwYcfYHpz%Q^4R0|vF20PwQ|~aROAlWtn4#* ztoXo9Zl93Jk%c7cU^Opze;AFQ(0dz>kr=3it&>19tt8dv@D&-+v!xR zQQWUbHD`7&diD60Rrw;Cl27{A&$;GXim9aF$`V;~F$M_{-d=Fy^aa{s`lMARHLyTT zk(O`?;V_W>-;X?OT?47DFkB*^3(7Uehqc?LxI5MyBlG*Gc6{pW{xuCbw zErhe0Vd?&F%p_Xnot846DCVWKu+)dhP~3Mj0}-iR4TR-BtVQjt1e~&f&m4(O#?8$k z1g&3gZT6F=X?{Ml{7^|y8k|?jKR4kYYc$z1Chr^OiLv9#>z?nNocf_0BfU>wn|Hpp zZhzxlx9D>+VN5=cZP}@5*mDvpZ~ana!p`z2bIZ85M{Rvl)ngOQNxZUk@c8~DgPw_i zu&?qV6I$iPSTQ2UA^tA4tY9z^SdSa3G?Z$BKe2n+*4v7hm+1ss6EyTz z6IXFXpa)>HuVa>c)Y1l}YJ@#3^IIr;q8Hf_j63O;K1 zwl`wJ@k$hO6YlRlnq>X_8C&$@VMeqR(?x`Mw4RxT`kl*@u7M8OMW3@(gqn^cl|JTt zgF5%0Ws8AD-5qw>VX3F%oDD?%lVH#9=igkv=x%J*_iNC9ufQ!xw=2tZ9?CWZ%1er; zWsKaFn+6KmYHD+_%dE@bYo{hY4p3&4MdlCF!dUM*IjVRHmD7gadb16^qzp+-COIn= z@@@Sfcm{6z2Q* z2r`;%Vh=3YYujKC)Gc}JSG;#hR6&~zmoSS+*LKY54Qxe|ZFPa9`98q#s}Aw>Gw_n{ zQ+X{IvA+!o(aU4l4DkiE1qzG^-Gs6oFb|gR*%7E0htnhR;I@`2w!HSLH|T9iE4_g| ztSy&Ko+7V>nUz;`xc}DO23X7n9blM1{2sJO7k%?1*( zN@Pd?VQ46U7rSOI!6q~NKY=2@4E&NO|A`cVAM=L`{qNr{VUURrcA2wUP1MjwC$s%w zr0LMdphor&B^2x5`X~vwyl>9Sf$eUP^NgMVUFWYx2HkG+rjp6W|KzVv(>gun3+%aq zrE!kE2XvNr4w`NM{61+?*_CTso%qUFkCdq6d)-pn*0Y#Zrw^C%R2vxzk4gyo-OgJ+ zJe@q!NhA>Hq%vhV^s}tCp;9TVTu&B)o-G0P!d%Mn_OLEQ{C-!tZ+bL?tO9x06EH00 z>(L@}-v`&<$x=KU7XN+s36L)gBR~uNs2q6oL>b2QNPb2u+Veu6#cKzYus)cEH4Ck=r z_d^HN;bylm?uqT-8<$k`p zJ9og=NWlfglx$Vcb+KktB!Kj_m&aRcMsb5)U!e0i5`vuKJgvohn`;E$@Pm5}_L68c zvRGpT7`-`ZSiUcPo2ssB-&KCxd`pOtPE?eBUcc$P7+DexyguNv1Olzgn)o^9g!du% zwZKJZ$~1evMJ&wn{f9fSN$CJcR$nVOqj1BcC+Wj6lA}kems|nhlk4^Dh!5e;C((~T zHA|~TAJv<&Oom|s#?s%dY7&A2Q923 z-%0R0r%qSO`P^$M z!xuB2vkz$=c?OpVTIzbapJH~CbZpVV59fu5_f>^tc8%O8FG*fF@2UPSDmja;8^Zi6 z3!oS_D0hC+fuo$DczG};O9hb4G*&9P^cKJfB_#&7Z~(8j%g5A1;i?>gFOZRi>9sFEtCFQ%$xDv`V2LKIJE>V=MzF>3F0gBK3G`<%Y80Tz6V zFnmO8^PiNycc-6#H!8^B0h+vf)+l(;JoJ?v5Kb=gvNW;Oe!)qzt;N*<+G7}y)BJn| zTwQ^WrG&A$+)tv{D*=e4rpjFAb6n@qu8nk!zweKgYvUgP&P58469f8@Xu-A6@-`GQ zB#0}D|MP}}$|M_IkKO$D3)R%Qn84zbCXqe=Kx(}hR+^4 zGXclNzMV|#=3$U`BoN?+Y#HubseLML`@k=E(@t2@L*?rQr96%dHe*?vu(u|;+Ycht zW^UZqJC@`1I3JTOyjS#4VUo(mL@H-o0M1qi6X5FnmkJWl9Yb~gs*&{DnYXlEqO;pn z(>B+R+WUI!5xzZc2XdvUoLA+v77WJ>lU@MCN2k)Oldcjt)fssC(U_z|NqFOMd!<%@ z&A6g%m1-CcG4dc$%XJqdd$$Q4pOL<*yZ~2$3M;b_xM-Eva(KT!sX8#-%}-Ve$UgVu z+P`cE$w?2IyqlA-^gxxgH=z)AZ?}o{0@fI5isa9D`hO^A5xzg`eT_00R8!>K1+2Ct z53mtm#|oqffdZy~7M)*+(n$7WGq|Qve4O5n3}Ke|NBzZl^Q^0{_85@~8iXgHFWC$pdRKYtwD(tt>5mYUWeEj)oUX8TWl3 zQ%y72n|j|%n%esBIL8Q8FEjL7SzkU^f3uhA4UQR?> z?>`<)@~ZhGBQ2Cb;GUUknfIOpN&*T$n6Ks!;hVJcy`6vmnuacIEjZsmI@D}%6lhA3 zFWn!xZa23>_T9HLlh2jeIVC|lrg72;RsER+uhbc7k85<9K$&_$i|1!eTNASY@~DH{ zy(jWz4!}Honu?f;;TtCnGmk{LBTr4Cavu5jEbEob*>dNg z37l*Al;k1S(1xgqqaHpoP6;j1QX<$l5wVe;{^$}-8hDzP9=UY=gr;!a?X$5b;?Pq9 zG0yxSrvaF(4A!4!o*N0H>QEha(2SOgGDNtAIFDzB`a45@NNTp~k_Np~5JWQlE)>wf zZVWeA$wh}klhxZV?^oYxuNjV5E%nKqd6mXf%u+5W(1;;_pc%0FkB zM`%%#V%9?;W_|DzN2P1bln`%g^d*iW{&xH8sa} z!c2lC2Tt(wlV=_873tDBy5F(L3sE<_BVj5p0%g>R-3+a5Vx6TnPE7@N{9bKZDB$#i zADQkqbdKY9N=Xm*E2!eVYDEd(QP-s3LcOW4BHx+={Aaml;JjDNF*hUsM~-+fbHEeq z!_DH0o?A2f(*R2X{w!IpUw`92N#fCJ{Ti}O0zhs950TKZv~Ol|sn*rWLc%c!VyD4e z1*|jyUxHnNkXi2I(EC8kE=y(59LqNF6FEipRN!H>{?%CETKb!~%D~E*(7sBgPWnvP z{HeO?spkon!pRw${81x%MnMZ5fPHFIYruBl$~YtTry`Hteb~!qy0JMc6I8dqdY-xu z%&{oix3_}| z6!Jw}haF+buA`Po%#9&y*~@_vAETk=5|XKbQ$gs>0PgT7?|KqRDxb%0#vo!Z+1W8t zRCx zRh~*3GTJqLWJH5|xkAasK&6BP`~SXH@Cs^yJVwk z7;5z2XpcVSuC>_Mj3=_ly**QT0@|2|r*VA1Pb>Lt5p8IM3|SEkhPv-e|~)?KFK;A6KUakT|?X z(r4;G-i-&`l3hk3-oI$7yesSf(cgVekoE?VeH>UTpq=gHKT}RIO)`GH&D*9nqX+ye zpnN*ApRK?p!NVmYyewFk+&O%~_LAX*mo@i?(JV?!%1-$&do<${Qnol7Ytuluc5d7k zxpZxJ;n9Jq8q4Tf#1yhb2+SOVmHzp-r{kgu^?0SH&k)yJoyZ+sP;3WTN25Uii(!y5 zZi%aK&fDl{euAW_vd{@TW85Ds=nf|%VCK?co<5qnprGmP2pqNXWI(n$;~*PLwrPfK zg@t`xT<;ykl_O?%VJ^H7C+sHIrtO@E^RsX|VfKKcP&y}$K*x?=|F#MjM

Mi0$EN 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

SJQT_*5a%AsT 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

-f^R--}5GfRj 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 z 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 b^FI+Ie(1zcrai7h0RJ96P?9N?GW7p{s90*( diff --git a/examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_write.png b/examples/MT_pomsarc_10_single_sided_writes/img/rdma_results_write.png deleted file mode 100644 index 83b0413f6627d8529212911d5e4d0a6dbfc56b0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131862 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 P;nQ? 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

kf5|yR$meVdgHLD+sJmHPj@ZLh9O?3l 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 z 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 threadCoyote driverDRAMCoyote threadCoyote driverDRAMvFPGAvFPGARDMARDMAFPGAFPGASwitchMeta-Exchange:ip_addr, QPN, PSN, rkey, vaddr, sizeTCP / IPRoCE v2122QP-SetUp: QPN, vaddr34OUTGOING: Command & DataREMOTEWRITEdata={1, 2, 3, …}INCOMING: Command & DataLOCAL WRITEdata={1, 2, 3, …}5INCOMING: CompletionDataHeader \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/img/rdma_traffic_pattern.svg b/examples/MT_pomsarc_10_single_sided_writes/img/rdma_traffic_pattern.svg deleted file mode 100644 index cc7223ad0..000000000 --- a/examples/MT_pomsarc_10_single_sided_writes/img/rdma_traffic_pattern.svg +++ /dev/null @@ -1 +0,0 @@ -LATENCYTHROUGHPUTRDMA WRITERDMA READCPUFPGACPUFPGACPUFPGACPUFPGACPUFPGACPUFPGACPUFPGACPUFPGARDMA WRITEACKRDMA WRITEACKRDMA WRITERDMA WRITERDMA WRITEACKACKRDMA WRITERDMA WRITEREAD REQUESTREAD RESPONSEREAD REQUESTREAD REQUESTREAD REQUESTREAD RESPONSEREAD RESPONSEREAD RESPONSEREAD REQUESTREAD RESPONSE \ No newline at end of file diff --git a/examples/MT_pomsarc_10_single_sided_writes/sw/CMakeLists.txt b/examples/MT_pomsarc_10_single_sided_writes/sw/CMakeLists.txt deleted file mode 100644 index c3e977433..000000000 --- a/examples/MT_pomsarc_10_single_sided_writes/sw/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -# CMake configuration -cmake_minimum_required(VERSION 3.5) -project(example_08_perf_rdma) - -set(CYT_DIR ${CMAKE_SOURCE_DIR}/../../../) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CYT_DIR}/cmake) -find_package(CoyoteSW REQUIRED) - -# Add source files -set(INSTANCE "client" CACHE STRING "RDMA software build targe: client or server") -if(INSTANCE STREQUAL "server") - set(TARGET_DIR "${CMAKE_SOURCE_DIR}/src/server") - message("*** Coyote Example 8: RDMA Server [Software Build] ***") - include_directories("${CMAKE_SOURCE_DIR}/src/include") -endif() -if(INSTANCE STREQUAL "client") - set(TARGET_DIR "${CMAKE_SOURCE_DIR}/src/client") - message("*** Coyote Example 8: RDMA Client [Software Build] ***") - include_directories("${CMAKE_SOURCE_DIR}/src/include") -endif() - -# Create build targets and link against required libraries -set(EXEC test) -add_executable(${EXEC} ${TARGET_DIR}/main.cpp) -target_link_libraries(${EXEC} PUBLIC Coyote) -target_link_directories(${EXEC} PUBLIC /usr/local/lib) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index 3ad7d2865..e00eb64e7 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -1,5 +1,7 @@ import lynxTypes::*; + +// defines wether simulated ECN markings should be generated and also add test readout signals as ILAs `define CC_TEST module cc_queue @@ -14,42 +16,55 @@ module cc_queue metaIntf.m m_req ); + +//============ DCQCN PARAMETERS ================ +//(Test setup parameters after the ifdef CC_TEST) + +//defines the queue size //localparam integer RDMA_N_OST = RDMA_N_WR_OUTSTANDING; localparam integer RDMA_N_OST = 20; -localparam integer RDMA_OST_BITS = $clog2(RDMA_N_OST); -localparam integer RD_OP = 0; -localparam integer WR_OP = 1; - +//Send Rate default values (do not set too low or rounding might prevent rate increases (~100 cycles should be safe)) +//IMPORTANT: send rate is expressed here as the number of cycles between two packets, higher Rc/Rt -> lower send rate +//Same with R_MIN below localparam integer Rt_default = 200; localparam integer Rc_default = 8192; localparam integer R_MIN = 200; + +//R_CAP is the maximum after which Rc/Rt will no longer be increased. Current value is just chosen to fit nicely in the simulation localparam integer R_CAP = 16384; +//Time interval after a rate decrease in which no further rate decrease can happen (to give the network some time to adjust to new rate) localparam integer N_min_time_between_ecn_marks = 12500; +//Timeout values for the lowering of the rate reduction factor (num cycles needs to be higher than N_min_time_between_ecn_marks) localparam integer K = 13750; +//constant values by which send rate is adjusted in an additive rate increase step (higher value can be chosen for the hyper increase phase) localparam integer R_ADDITIVE_INCREASE = 128; localparam integer R_HYPER_INCREASE = 128; +//precision of rate reduction factor (do not change without changing all Sa values) +localparam integer Sg = 7; -localparam integer Sg = 8; +//Number of steps of recovery steps before additive rate increase sets in localparam integer F = 5; +//time out values for the time and byte counter in num cycles and num packets respectively localparam integer time_threshhold = 50000; localparam integer byte_threshhold = 35; +//Precomputed Rate reduction factors localparam integer Max_Sa_index = 28; localparam integer Sa_fixed[0:28] = {256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139}; -logic[4:0] Sa_index; +//================================================ +logic[4:0] Sa_index; logic[15:0] Rt; logic[15:0] Rc; -//logic[63:0] Sa; logic[23:0] timer; logic[23:0] timer_send_rate; @@ -70,11 +85,13 @@ metaIntf #(.STYPE(dreq_t)) queue_out (); `ifdef CC_TEST -//simul part -logic[23:0] ecn_timer; +//============ TEST PARAMETERS ================ + +//number of cycles after ECN rate is changed to the next entry in the ECN_rates list localparam integer ecn_timer_trigger_threshhold = 5000000; -logic ecn_alternator; -logic[15:0] ecn_counter; + +// List containing ECN rates during a test run, value indicates number of non marked packets after a marked packet. + //1 //localparam integer Ecn_rates[0:6] = {9999, 1, 9 ,99, 999, 9999}; //2 @@ -84,12 +101,23 @@ logic[15:0] ecn_counter; //4 localparam integer Ecn_rates[0:7] = {9999, 99, 29, 24, 19, 9, 29, 39}; -logic ecn_test_starter; +//needs to be number of different ECN rate steps - 1 localparam Ecn_rates_max_index = 7; + +// trigger interval for ila_trigger signal to view results in Vivado +localparam integer Ila_readout_timer_threshhold = 40000; + +//================================================ + +logic[23:0] ecn_timer; +logic ecn_alternator; +logic[15:0] ecn_counter; +logic ecn_test_starter; + logic[2:0] ecn_rates_index; logic[15:0] ila_readout_timer; -localparam integer Ila_readout_timer_threshhold = 40000; + logic ila_trigger; @@ -194,7 +222,7 @@ always_ff @(posedge aclk) begin Rt <= Rc; if(Rc <= R_CAP) begin - Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; + Rc <= (Rc * Sa_fixed[Sa_index]) >> Sg; end if(!(Sa_index == 0)) begin Sa_index <= Sa_index - 1; @@ -378,7 +406,7 @@ always_ff @(posedge aclk) begin Rt <= Rc; if(Rc <= R_CAP) begin - Rc <= (Rc * Sa_fixed[Sa_index]) >> 7; + Rc <= (Rc * Sa_fixed[Sa_index]) >> Sg; end if(!(Sa_index == 0)) begin Sa_index <= Sa_index - 1; From 35fc67262760d9986fa08fcdb5b7598ea0fdd993 Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Mon, 10 Nov 2025 00:05:27 +0100 Subject: [PATCH 40/52] submodule update --- .../rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 7 +- hw/hdl/network/rdma/MT_pomsarc_benchy.sv | 137 ------------------ hw/services/network | 2 +- 3 files changed, 5 insertions(+), 141 deletions(-) delete mode 100644 hw/hdl/network/rdma/MT_pomsarc_benchy.sv diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index e00eb64e7..5161bc2f7 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -99,10 +99,11 @@ localparam integer ecn_timer_trigger_threshhold = 5000000; //3 //localparam integer Ecn_rates[0:6] = {9999, 1, 9, 14 , 19, 24, 9999}; //4 -localparam integer Ecn_rates[0:7] = {9999, 99, 29, 24, 19, 9, 29, 39}; - +//localparam integer Ecn_rates[0:7] = {9999, 99, 29, 24, 19, 9, 29, 39}; +//5 +localparam integer Ecn_rates[0:15] = {9999, 99, 29, 24, 19, 9, 29, 39, 39, 99, 99, 149, 149,199,199,9999}; //needs to be number of different ECN rate steps - 1 -localparam Ecn_rates_max_index = 7; +localparam Ecn_rates_max_index = 15; // trigger interval for ila_trigger signal to view results in Vivado localparam integer Ila_readout_timer_threshhold = 40000; diff --git a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv b/hw/hdl/network/rdma/MT_pomsarc_benchy.sv deleted file mode 100644 index a08dead5f..000000000 --- a/hw/hdl/network/rdma/MT_pomsarc_benchy.sv +++ /dev/null @@ -1,137 +0,0 @@ -/*`timescale 1ns / 1ps - -`define DBG_IBV - -import lynxTypes::*; - -/** - * @brief RoCE instantiation - * - * RoCE stack - */ -/* -module benchy -( - input logic aclk, - input logic aresetn, - - metaIntf.s s_req, - metaIntf.m m_req -); - -localparam integer packet_gap = 5; //inverse of marked percentage -localparam integer measurement_gap = 50; - - -//localparam integer max_timer = 10000000000; - -logic[31:0] timer; -logic[31:0] packet_counter; -logic[31:0] packet_gap_counter; - - - -logic running; - -logic ecn, ecn_v, ecn_r; - -logic[31:0] measurement_gap_timer; - -always_ff @(posedge aclk) begin - if (aresetn == 1'b0) begin - timer <= 0; - measurement_gap_timer <= 0; - packet_counter <= 0; - packet_gap_counter <=0; - - ecn <= 0; - ecn_v <= 0; - running <= 0; - measurement_gap_timer <= 0; - //measurement_trigger <= 0; - - end - else begin - timer <= timer + 1; - - /*if(timer >= max_timer) begin - timer <= 0; - measurement_gap_timer <= 0; - packet_counter <= 0; - packet_gap_counter <= 0; - - ecn <= 0; - ecn_v <= 0; - running <= 0; - measurement_gap_timer <= 0; - //measurement_trigger <= 0; - end - else begin*/ -/* - measurement_gap_timer <= measurement_gap_timer + 1; - ecn_v <= 0; - ecn <= 0; - if(s_req.valid == 1) begin - if(running == 0) begin - running <= 1; - measurement_gap_timer <= 0; - end - - ecn_v <= 1; - - if(packet_gap_counter < packet_gap) begin - ecn <= 0; - end - else begin - ecn <= 1; - end - if(s_req.ready) begin - if(packet_gap_counter >= packet_gap) begin - packet_gap_counter <= 0; - end - else begin - packet_gap_counter <= packet_gap_counter + 1; - end - packet_counter <= packet_counter + 1; - - end - end - - if(measurement_gap_timer >= measurement_gap) begin - measurement_gap_timer <= 0; - end - //end - - end -end - -ila_testbench_CC inst_ila_testbench_CC( - .clk(aclk), - .probe0(timer), - .probe1(measurement_gap_timer), - .probe2(running), - .probe3(ecn), - .probe4(ecn_v), - .probe5(ecn_r) -); - - - -cc_queue inst_cc_queue( - .aclk(aclk), - .aresetn(aresetn), - - .ecn_mark(ecn), - .ecn_valid(ecn_v), - .ecn_ready(ecn_r), - - - .s_req(s_req), - .m_req(m_req) -); - - - - -endmodule -*/ \ No newline at end of file diff --git a/hw/services/network b/hw/services/network index ffd8b038b..4214e4c86 160000 --- a/hw/services/network +++ b/hw/services/network @@ -1 +1 @@ -Subproject commit ffd8b038b2b7145c266fed2dd9d706f24b8e7869 +Subproject commit 4214e4c8652bfb8a63a38e52d37c00018fc9646c From 24c25b4945cca4036af340efdd8f462ed266c0df Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Mon, 10 Nov 2025 00:22:49 +0100 Subject: [PATCH 41/52] added last example and scripts --- .../Output_Grap_Script-checkpoint.ipynb | 151 ++++++++++++++++++ ...duction_factor_calc_scrip-checkpoint.ipynb | 75 +++++++++ .../Test_script/Output_Grap_Script.ipynb | 134 ++++++++++++++++ .../Reduction_factor_calc_scrip.ipynb | 75 +++++++++ .../Test_script/example_graph.png | Bin 0 -> 378223 bytes 5 files changed, 435 insertions(+) create mode 100644 examples/Congestion_Control_Test/Test_script/.ipynb_checkpoints/Output_Grap_Script-checkpoint.ipynb create mode 100644 examples/Congestion_Control_Test/Test_script/.ipynb_checkpoints/Reduction_factor_calc_scrip-checkpoint.ipynb create mode 100644 examples/Congestion_Control_Test/Test_script/Output_Grap_Script.ipynb create mode 100644 examples/Congestion_Control_Test/Test_script/Reduction_factor_calc_scrip.ipynb create mode 100644 examples/Congestion_Control_Test/Test_script/example_graph.png diff --git a/examples/Congestion_Control_Test/Test_script/.ipynb_checkpoints/Output_Grap_Script-checkpoint.ipynb b/examples/Congestion_Control_Test/Test_script/.ipynb_checkpoints/Output_Grap_Script-checkpoint.ipynb new file mode 100644 index 000000000..483283ad3 --- /dev/null +++ b/examples/Congestion_Control_Test/Test_script/.ipynb_checkpoints/Output_Grap_Script-checkpoint.ipynb @@ -0,0 +1,151 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "f5274104-49ba-4f8d-9509-5322e1aa2ca5", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "import numpy as np\n", + "import sys" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8af48534-32bc-4d73-98a1-eba531c0bafe", + "metadata": {}, + "outputs": [ + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] No such file or directory: './ila_example.csv'", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mFileNotFoundError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 6\u001b[39m\n\u001b[32m 4\u001b[39m rates = [(\u001b[32m1\u001b[39m/\u001b[32m10000\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m100\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m30\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m25\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m20\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m10\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m30\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m40\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m40\u001b[39m) ,(\u001b[32m1\u001b[39m/\u001b[32m100\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m100\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m150\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m150\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m200\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m200\u001b[39m), (\u001b[32m1\u001b[39m/\u001b[32m10000\u001b[39m)]\n\u001b[32m 5\u001b[39m ila_tigger_interval = \u001b[32m40000\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m6\u001b[39m df = \u001b[43mpd\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread_csv\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/lib/python3.12/site-packages/pandas/io/parsers/readers.py:1026\u001b[39m, in \u001b[36mread_csv\u001b[39m\u001b[34m(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)\u001b[39m\n\u001b[32m 1013\u001b[39m kwds_defaults = _refine_defaults_read(\n\u001b[32m 1014\u001b[39m dialect,\n\u001b[32m 1015\u001b[39m delimiter,\n\u001b[32m (...)\u001b[39m\u001b[32m 1022\u001b[39m dtype_backend=dtype_backend,\n\u001b[32m 1023\u001b[39m )\n\u001b[32m 1024\u001b[39m kwds.update(kwds_defaults)\n\u001b[32m-> \u001b[39m\u001b[32m1026\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_read\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfilepath_or_buffer\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkwds\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/lib/python3.12/site-packages/pandas/io/parsers/readers.py:620\u001b[39m, in \u001b[36m_read\u001b[39m\u001b[34m(filepath_or_buffer, kwds)\u001b[39m\n\u001b[32m 617\u001b[39m _validate_names(kwds.get(\u001b[33m\"\u001b[39m\u001b[33mnames\u001b[39m\u001b[33m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m))\n\u001b[32m 619\u001b[39m \u001b[38;5;66;03m# Create the parser.\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m620\u001b[39m parser = \u001b[43mTextFileReader\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfilepath_or_buffer\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwds\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 622\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m chunksize \u001b[38;5;129;01mor\u001b[39;00m iterator:\n\u001b[32m 623\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m parser\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/lib/python3.12/site-packages/pandas/io/parsers/readers.py:1620\u001b[39m, in \u001b[36mTextFileReader.__init__\u001b[39m\u001b[34m(self, f, engine, **kwds)\u001b[39m\n\u001b[32m 1617\u001b[39m \u001b[38;5;28mself\u001b[39m.options[\u001b[33m\"\u001b[39m\u001b[33mhas_index_names\u001b[39m\u001b[33m\"\u001b[39m] = kwds[\u001b[33m\"\u001b[39m\u001b[33mhas_index_names\u001b[39m\u001b[33m\"\u001b[39m]\n\u001b[32m 1619\u001b[39m \u001b[38;5;28mself\u001b[39m.handles: IOHandles | \u001b[38;5;28;01mNone\u001b[39;00m = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m1620\u001b[39m \u001b[38;5;28mself\u001b[39m._engine = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_make_engine\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mengine\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/lib/python3.12/site-packages/pandas/io/parsers/readers.py:1880\u001b[39m, in \u001b[36mTextFileReader._make_engine\u001b[39m\u001b[34m(self, f, engine)\u001b[39m\n\u001b[32m 1878\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mb\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m mode:\n\u001b[32m 1879\u001b[39m mode += \u001b[33m\"\u001b[39m\u001b[33mb\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m-> \u001b[39m\u001b[32m1880\u001b[39m \u001b[38;5;28mself\u001b[39m.handles = \u001b[43mget_handle\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 1881\u001b[39m \u001b[43m \u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1882\u001b[39m \u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1883\u001b[39m \u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43moptions\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mencoding\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1884\u001b[39m \u001b[43m \u001b[49m\u001b[43mcompression\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43moptions\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mcompression\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1885\u001b[39m \u001b[43m \u001b[49m\u001b[43mmemory_map\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43moptions\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmemory_map\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1886\u001b[39m \u001b[43m \u001b[49m\u001b[43mis_text\u001b[49m\u001b[43m=\u001b[49m\u001b[43mis_text\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1887\u001b[39m \u001b[43m \u001b[49m\u001b[43merrors\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43moptions\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mencoding_errors\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mstrict\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1888\u001b[39m \u001b[43m \u001b[49m\u001b[43mstorage_options\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43moptions\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mstorage_options\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1889\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1890\u001b[39m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mself\u001b[39m.handles \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 1891\u001b[39m f = \u001b[38;5;28mself\u001b[39m.handles.handle\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/.local/lib/python3.12/site-packages/pandas/io/common.py:873\u001b[39m, in \u001b[36mget_handle\u001b[39m\u001b[34m(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)\u001b[39m\n\u001b[32m 868\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(handle, \u001b[38;5;28mstr\u001b[39m):\n\u001b[32m 869\u001b[39m \u001b[38;5;66;03m# Check whether the filename is to be opened in binary mode.\u001b[39;00m\n\u001b[32m 870\u001b[39m \u001b[38;5;66;03m# Binary mode does not support 'encoding' and 'newline'.\u001b[39;00m\n\u001b[32m 871\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m ioargs.encoding \u001b[38;5;129;01mand\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mb\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m ioargs.mode:\n\u001b[32m 872\u001b[39m \u001b[38;5;66;03m# Encoding\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m873\u001b[39m handle = \u001b[38;5;28;43mopen\u001b[39;49m\u001b[43m(\u001b[49m\n\u001b[32m 874\u001b[39m \u001b[43m \u001b[49m\u001b[43mhandle\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 875\u001b[39m \u001b[43m \u001b[49m\u001b[43mioargs\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmode\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 876\u001b[39m \u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[43m=\u001b[49m\u001b[43mioargs\u001b[49m\u001b[43m.\u001b[49m\u001b[43mencoding\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 877\u001b[39m \u001b[43m \u001b[49m\u001b[43merrors\u001b[49m\u001b[43m=\u001b[49m\u001b[43merrors\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 878\u001b[39m \u001b[43m \u001b[49m\u001b[43mnewline\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 879\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 880\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 881\u001b[39m \u001b[38;5;66;03m# Binary mode\u001b[39;00m\n\u001b[32m 882\u001b[39m handle = \u001b[38;5;28mopen\u001b[39m(handle, ioargs.mode)\n", + "\u001b[31mFileNotFoundError\u001b[39m: [Errno 2] No such file or directory: './ila_example.csv'" + ] + } + ], + "source": [ + "#enter path to csv file\n", + "path = './ila_example.csv'\n", + "savepath = '/home/cyril/Desktop/example_graph.png'\n", + "rates = [(1/10000), (1/100), (1/30), (1/25), (1/20), (1/10), (1/30), (1/40), (1/40) ,(1/100), (1/100), (1/150), (1/150), (1/200), (1/200), (1/10000)]\n", + "ila_tigger_interval = 40000\n", + "df = pd.read_csv(path)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b6981655-142b-4371-82fc-d0be6ecf0613", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Default Parameters, Sending rate')" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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5513tHDhQknShx9+qHLlyjmt5/hz5pUjR45kjkuWLJk5Pnv2rGJiYtShQwe1bt1atWvXVokSJTInhpwxY4Y2btyorVu36qqrrtLatWtVokQJj/v69ddf9dJLL8lisWjw4MHq2LGj4uLitH37dlWrVi1zvdTUVFmtVtWrV09du3ZVw4YNVaZMGUVFRenQoUNasmSJZs6cqXPnzmnYsGEqX768evXq5XHf6enpuv766/X777/r8ssvV58+fVSmTBnt3LlT48aN0/79+7V69Wrdf//9Gj16tK688kqlpKRoyJAh6tSpk4oWLarVq1fr/fffV0pKiqZOnaru3bu7neh0/vz5uvbaa5Wenq6IiAj16NFD3bt3V+XKlZWamqo1a9ZoypQpOn36dOakpY8//njm47t166Zvv/1Wv/zyi959911J0n333adu3bp5/Dkl6amnntJLL70kSYqLi9MNN9ygtm3bKjExUSdOnNCCBQv0zTff6OTJk+rVq5d++eUXdezY0eM2b775Zi1ZskQXX3yxBg4cqIsuukhJSUlavHhx5jpPPvmk5syZI0kqW7as+vfvr4svvliJiYlKTU3Vrl27tGrVqsy/+bxUu3btzPGsWbO0a9cu1axZ069tfvzxx7rrrrtktVoVExOja6+9Vp06dVL58uX177//atmyZfriiy+UmpqqIUOGKCYmRgMHDvS4zaeeekpffPGFatSooVtuuUUNGjRQenq6FixYoM8++0wZGRkaN26c2rVrp5tuusnlNh544IHM3vaRkZG6+eab1aVLF8XGxmr9+vX65JNPNHbs2IC3gfr444/1xRdfqGzZshoyZIguvfRSRURE6Ndff9X48eOVlpammTNn6rXXXnM7Me+LL76o119/XZJksVh03XXXqUePHipevLi2bdumCRMmaNq0aV5XdPti+/btuuGGG5SUlKTrr79e3bt3V6lSpbR3715FRdlPP8+dO6fz58+rWrVquuyyy9SoUSOVL19eMTExOnr0qFauXKmvvvpKKSkpeuaZZ5SYmKh77rnHaV+5PeafP39ePXr0yKx6r1SpkgYMGKBLL71UxYoV0/79+zVjxgwtW7ZMmzZtUqdOnbRu3TqVLVs24M8XAADwUrCz6AAAFBTKoYLTkyuvvDLzsW+//Xa2+wcMGJB5/+jRo63nz5/Pts6FCxeso0aNylzvySefdLmvnCqdbc6fP2+dN2+e9cKFC27XOXbsmLVt27aZla+7d+92uV5+VWLbOFbn7dq1K8d9esvbSuxz585Zq1atmrnu5MmTM+9bvHix9cSJE24fm5GRYf3vf/+b+dgXXnjB5XqO1Z+SrOXKlbNu2LDBY/y7d++2rl+/3uM669ats5YrV84qyVqnTh23VdG2ylL9f6XkJ598km2dQ4cOWStUqJD599GkSRNrYmKide3atdnWXbBggVPloysHDhzIrO4sV66cdcWKFS7X279/v/WSSy7J3O+WLVuyreNYkT1x4kQPz4gxf/78zIrNNm3aWPfv3+9yvWXLllnj4+Otkqw1atRwWS3r+Hek/68IdvWatlrN67BkyZJWSdaLLrrI49/O6dOnXT63gZSUlGRNTEzMjL1s2bLW5557zrp+/Xq3P4MnGzZssMbExGT+vbn6XVmtVuvmzZutlSpVskqyxsfHW48fP55tnaxVuf3797empqZmW+/TTz/NXKdRo0Yu97ds2bLM33dcXJx16dKl2dY5cOCAtX79+k77DEQltv6/cvjUqVPZ1lu0aFFmhXaZMmWsaWlp2db566+/rNHR0VZJ1ujoaOusWbOyrZOcnGy9/PLL3VZJ+yrrt33i4uKsP//8s8fHHD9+3OXzmnW7devWtUqylixZ0pqUlORyPV+P+Y899ljm+sOGDbOmpKS4XG/s2LGZ69188805bhcAAOQdktgAAASIP0nsO++8023yecOGDZn33X777Tluq3379pkn/K4SON4msb3l+HX0F1980eU6hSmJff78eevdd9/tlMzJ2t7FGx06dLBKstauXdvl/VmT2N9++63P+3Bn/PjxmdtdtmyZy3Uck9iO7VKyevHFF53inDZtmtt1L7vsssz19u7dm+3+Bx54IPP+JUuWePwZtmzZkpnsu/vuu7Pd72sSu1mzZplJW1cJVEcfffSRx5/X8e+oWbNmHj8oOnjwYOa6Dz/8cI5x5ocZM2a4bHVRrFgxa7t27awjRoywTp8+3Xrs2LEct9W3b1+rZFqF/P333x7X/fHHHzP39corr2S73/F1X7duXZfHP5vWrVtnruvqAwlbXJKs7733ntvtbNiwwem5CEQSu3Tp0h6fu/79+3t8fTq2UXn88cfdbufo0aPWhISEPEliv/XWW7neVlY///xz5nY/++wzl+v4csw/fPhwZqus7t2757j/m266KfMDMXcfXgEAgLzHxI4AAISAUqVKZY6PHz/udJ/jhFWPPPJIjtu69dZbJZlJ0n777bcARehe7dq1VaFCBUnSypUr83x/oWLZsmWaOXNm5mX69Ol6+eWX1aRJE33wwQeZ6z333HO5al/SoUMHSear+Vn/JrKqXr26rr32Wp/3kdO+Je9+p/fdd59X2ypfvrz69evndl3H1hu21jk2VqtVU6ZMkSS1bds2xzYd9evXV6tWrSSZifn88ccff2jt2rWSpDvuuEOlS5f2uP5NN92U2TYhp30PHz5cERHu35IXK1Ysc2yLIdj69u2rRYsWqVmzZk63nz17Vr/++qveeecd9e/fXxUrVtT111+vP//80+V2Tp06lTnBYt++fZ1albhy+eWXq2LFipJyfl7vuecexcbGetyWTdb40tLSNHfuXEmmFdAdd9zhdjuXXnqprrjiCo+x+OrWW29VYmKi2/s9xS5JM2fOlCRFRERoxIgRbrdTpkwZ3XLLLbkP1I2iRYt6fM585evxKCfTp09XamqqJOnhhx/Ocf3BgwdLki5cuKAFCxb4vX8AAJA79MQGACAEWK3WzLHFYnG6b8mSJZKkIkWKaPPmzdmSe1n9888/mePNmzerU6dOfsV24MABffrpp1qwYIE2b96skydP6uzZsy7X3b9/v1/7CidPP/20x/ujo6M1evRojRo1Ktt958+f14wZMzRz5kytX79eBw4cUFJSktv+tPv37/eY1Grfvn22vxtP1q9fr88++0wrVqzQ33//rTNnzigtLc3tvj2Ji4tz6quele0DDklq3ry5x4St47onT550um/z5s2ZyfxSpUplJuo8iYyMlCTt2rVLqampmT3ifWV7DUomkeXNvosXL65Tp07l+HrNKRlfokQJtWnTRitXrtSCBQt0zTXX6N5771WXLl0UExPjVfx5oUOHDvr999/122+/aebMmVq+fLnWrl2r5OTkzHXS09M1Y8YMfffdd/rggw80dOhQp20sX748828+NjbWq+c1Pj5eBw8ezPF5bdu2rcf7q1SpkjnO+re2YcMGnTt3TpJ5bXlKhkvSZZddpvnz53tcxxf+xH748OHMHt0NGjRwek250rVr18ze8IHStGlTFS9e3Ov1t2/frilTpmjJkiX666+/dPr0aaWkpLhcNxD/Yxxfz4cPH87x7y7r/1QAABAcJLEBAAgBjomIrMnK3bt3SzKT8vXt29en7Z44ccKvuD788EM9+OCDbpPWWZ05c8av/YWzmJgYJSQkqF69eurcubOGDh2qWrVqZVvvr7/+0nXXXedTMiSn59UxqeXJ+fPnNXz4cH388cdOH5z4s+/SpUt7TKA7JgA9JeKzrmurlLSxvQ4kad68eZo3b57HbWV14sQJVapUyafHuNq3r5OX5vQa9OZ3N27cOHXr1k2nT5/WnDlzNGfOHBUtWlQtW7ZUu3bt1K1bN3Xt2tVp0rz80rp1a7Vu3VqSmQx269atWrFihWbPnq3vvvtOGRkZmROF1q5d2ylp7/i8Tpo0SZMmTfJ6vzk9rzl9+8HT39qBAwcyxzlVh3u7ji8CFftFF12U475cHaP85e3xSJJGjx6tl156SefPn/dq/UD8j3H8u7N9c8lb/v5PBQAAuUcSGwCAEOB4Ul22bFmn+06dOpXr7dqqCXPjq6++0t133515vW3bturcubNq1qypkiVLOiVS7rzzTh09elQXLlzI9f7CzcKFC9WlSxefHnP69Gl169YtM9FUqVIlXX311WrQoIHKly+vIkWKZFYqT5s2TdOnT5ekHJ/XokWLerX/kSNH6qOPPpJkKsV79OihVq1aqUqVKoqLi1N0dLQk6ciRI7rrrru82renymp/1s3Kn9eB5N9rIS9fg9787po1a6YNGzboueee05dffqnk5GSlpKRoyZIlWrJkiV555RWVL19ejz32mEaMGOHX8+yPiIgINWzYUA0bNtTtt9+u9evX64orrsg8Njz//PP66aefMtf353lNT0/PMZbc+vfffzPHju1c3ImLi8v1vlzxJ3bHSvhgxC55fzx6/fXX9dxzz0kyP3PXrl3Vvn17VatWTfHx8U7fNLB9gBuI/zHB+p8KAAD8QxIbAIAgy8jIcOpdbatqtLG1JShdunSOvZED6YknnpBkWjJ8++236t27t9t1hw0bFrD9FuRE+HvvvZeZwL755ps1YcIEty0hli9fHtB979u3L7NXd+XKlbVw4ULVqVPH5bqbNm0K6L4DwbE9wYMPPqg333wzKPuePXu2x9dCXqlevbomTJig999/X7/99ptWrFihZcuWadGiRfr33391+PBhPfDAA9qwYYMmTpyY7/G50qRJE40dO1Y33XSTJGnx4sVKT0/P/LDE8Xl95513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7qypU6fqzz//1MmTJ5Wenh6QbfvjtttuU5MmTTRmzBgtXrxYhw8fVnx8vKpVq6bevXvr7rvvVqVKlQL2PDiqUaOG1q1bp7fffjvzdyBJFSpUULt27XT77berS5cu2r17d8D3HUgtWrTQ5s2bNWPGDM2aNUu//fabDh8+rOTkZBUvXlxVq1ZVo0aN1KVLF11zzTWqUKGCy+3cfvvtql69uj744AOtXr1aR44cybHn/K233qq+fftq8uTJ+uGHH7RhwwYdO3ZM58+fV8mSJVWrVi01a9ZMl112mXr27OlU2Z0bnTt31h9//KGffvpJK1as0KZNm7R//34lJyerSJEiqly5slq0aKEBAwaod+/ebrdz9uzZzHHZsmVzHU/Pnj11+PBhLV++XIsXL9bq1au1bds2HTx4UGfPnlVsbKwSEhJUr149tWnTRv3791fjxo1z3K6tpcMXX3yhefPm6ffff9fRo0eVmpqq+Ph41ahRQ40bN1a3bt3Uu3fvzLYyeSkiIkLjx4/XDTfcoPfff18rV67UqVOnVK5cObVs2VJ33XWXrrzyyoBPwhoor7/+uq655hq99957WrZsmY4dO6bExEQ1btxYd9xxh66//nr99ttvmeuXLl06X+MrVaqUli9frvfff1/Tpk3Tli1bdO7cOZUvX14tWrTQLbfcoj59+ni9PV+P+dHR0XrjjTd03333acKECVq4cKH+/vtvnThxQhEREUpMTFTdunXVunVrXXnllerUqVMAfmoAAJBbFqs1h+mbAQAAAIS1AQMGaPr06bJYLFqzZo2aNWsW7JAQAt59993M/tDffvutT0ljAACA/EQSGwAAACjArFarypUrp2PHjql///6aNm1asENCCEhPT1fTpk21adMmRUdH659//vGrSh8AACAv5d/sHQAAAADy3fr163Xs2DFFRUXpxRdfDHY4yAdHjhzR5s2b3d6fmpqq2267TZs2bZIk3XDDDSSwAQBASKMnNgAAAFCA/fTTT5JMD/DatWsHORrkh71796ply5Zq0aKFLrvsMtWrV08lSpRQUlKSNm7cqGnTpungwYOSpMTERL3xxhtBjhgAAMAz2okAAAAAQAGyZs0atWzZMsf1atasqVmzZqlRo0b5EBUAAEDukcQGAAAAgAIkLS1N3377rb7//ntt2LBBR48e1fHjxyVJZcqUUdOmTdW7d28N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OD4nNWvaZ0J13I4rS5aYZeXKzp+uSVKnTvZxTq0yduywj7PODOut+HipXTupd2/pppvMsl27PE9gS34msZs2lUaONOeFt98u9e9veoPb/PqraYsyfLj5ZG7JEnMu+fTTUvXq/oYOoKCzvck5f57JYAGfUIkNAADCjOP7/SJFnKt24+LsY8ckadZKbEdZ8zyOFcaOFdBZt+GYnPa0L8exY0V01na07iqbixY1SV53+3JMhLtLBGfdl7vqbcfnLyHBfUxly3q3r6zPheP2HNuMZF3PMceVNYnt+FwAQdenj308caLrdTIypClTzDghwXlWU29YLNK115rx1q3SypWu11u50l6Jfe212c/1ata0z4y6cKH7r7InJUk//2zGF13k3FcpTPiVxJakN9+U/vMfk8j++mszA67t+bzrLjNB5QcfmOfQajVJ76ee8nevAAqDuDj7V+JoKQJ4gYkds+O5AAAgLDjOW5aQYP/2du3azlW6jgnjrl2lG28042bNpGrV7PfFxdlbzrZq5ZxorVDB3vK2Vy/p6qvttzsmzx2TuJ07SwMGmHGbNlK9evb7HN9uNG0qXXGFGffr51zsWLeufVynjnMOyXGuto4dTYGjbezYLcBxX5deai/+HDTIxOX489tUrWpvV3LTTc6teFu0sI+rV3ful+24r4svllq2tO/LsejUMXFdqZL9ObzmGumqq8y4bFnn32PlyvbJHX1tJQzkuVatzItPkj75RFqxIvs6b74pbdlixiNHZv8kZtEi8yKyWKQhQ1zv5/777UmP++4zkwM4Skkxt0vmKxn33+96O489ZpbJye7XefBB6cwZM86pPcaZM/Z1/fHvv9Ls2eYSAH71xJbM72LcOPMhxSuvmAp4x0/TbOu0bWuS17YDGADkxGIxbxyPHzctRWzftAEAn9FOBACAkGW1Sj/8YL8eHS09+aR5/9+jh0mAzp1rJleMijJFicuXm7m5brjBJEP79DFtLb780l6FPWeOeVy/fuZxv/wipaaaZOqaNdJPP5ltZGSYpOqVV5p9zpljErMRESZHtWKF/VvzbdtKffuaRO306fbk8O+/mznDOnSQPvtM+uYbU+RXooTJgaWlmaTu0qXmcsstJpk8a5ZJzFss0qZN0qpV5me64grTJeC668zPP3WqPQm+erXpCtC6tTRjhvTtt2ZfxYubn69NG3vr1zNn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t61LSJD69ZMee0yqXTs/zhr7hgwZoiFDhqR1dwAKrQKQxJbc+p9ZJbElafVqe1WqZN8FM2Zwww8gn9ASG/no22/9l087je8zAACAmFWrlr18tWsnNW0akXCC7qu/caNUvbq9MhuwTJLWrLFu9KNGSbt2ua3G//1Xeu89G+Txt99CEDmAgquAJLEdr70W3H67dlkt7RNPtMFwt27N37gAFEK+SWxaYiPE5s/3X+7SJTJxAAAAIMSuuspe5ctHLISgW2J//70lVIoXly66KPP9LrlE2rzZXa5VyxLfy5ZJ+/ZZy+xLL5WWLPHv0QoAaWK8JnZ6N98sde5sD/GCsW6dvc47T2rTRrr9dqlevXwMEEDhQUts5BNnUGNfzZpFJhYAAACE2NixkY4g+JbYs2fbtEcPq90ayDffSAsWuDWwv/9eWr/e3rt1qzRokO23YoX0+ed5DR1AgVXAWmJLloROSMjZe2bPtlbc118vvfyylRwBgDzxeNwHhLTERggtX27jQPhKSopIKAAAAAgHr9cSFfPn22v16pAN4hhI0EnsJUvsvuf00zPf54MP3PkXXpDOOMNdLllSGjlSat7clidMyGmoAAqNApjETkiwckt//pnz906eLN1xh/V0WbgwX78TABQGTlc4WmIjhNKXEpGkmjXDHwcAAADy2aRJNghYQoKUnCx16GCv5GRbd8450g8/hPy0QSex162z6UknZb7PtGk2LVcu8EBmHo909dWWgFm8OPggARQyBTCJLVkvlsaNc//+BQusvMh774UuJgCFkFNShCQ2QijQQ1pKBwIAABQgR45YwrdPH+m776QDB9zBEJ3XgQM22nfv3rbvkSMhO33QPy337rVppUqBt69bJ23bZjmnrl2lYsUC7+fUhPWtmw0AfgpoEtsxY4a0ZYt08cW5e/+LL9p4CgCQK05mkXIiCKG//7bpzTdLY8ZIV1wR2XgAAAAQYpddJn35peVsiha1ch3t20snnGDbt26V5s2TfvxROnpU+uQTu+f49NOQnD7oJLaTS8osgT5vnjvfpk3mx0lMtOmBA8GeGUChU8AGdkyvSxebVq1qn+mffCLt2hX8+48etQFzn3jCercAQI7QEhv5YMMGm3btKj3/vBQfH9l4AAAAEELffit98YUliHv0kEaPlurUCbzv339bsmLKFBsU8bvvrPV2HgWdIXIGc1yx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rAh4nFGjRqWda8iQIRm2r1q1ypuQkOCV5G3QoIH36NGj2f8HQEyJlet19uzZ3s2bN2f6d6SmpnoffPDBtFhOOukkb2pqatZ/PGJOOK7XQIYNG5b2vqlTpwb1nqFDh6a959lnn82wfdasWd6iRYt6JXm7desWdCyILbFyzU6ZMsW7e/fuTLcfOXLEO2DAgLRj9u3bN+hYEDvCcb0uWrTIm5iY6JXkLV26tPeXX37JVax8xiJWrlc+X+H15v/1+tdff3knT56cZQx79uxJi0OSd+jQoQH3I0cAFHxF85wFR4HxyiuvqFOnTjp48KDOOOMMPfDAA+rRo4cOHjyojz/+WG+//bYke8p511135eocycnJuueee/T0009rwYIF6tSpk+677z7Vr19fq1ev1jPPPKNFixZJku655x41bNgw4HEGDBig0aNHa+bMmRoxYoS2bt2q6667TuXLl9e8efP0+OOPa+/evYqLi9Orr76qokW51AuaWLlev//+ez399NM688wzdfrpp6tp06ZKTEzU4cOH9fvvv2v06NGaO3euJKlUqVJ65513qC1YAIXjepX8W5dI0m+//ZY2//3332vdunVpyw0aNPDreum455579Mknn2jFihW69957tWrVKl1yySUqWbKkpk6dqv/+9786duyYSpYsqZdffjnXsSK6xco1O27cOJ1zzjk655xz1L17dzVq1EgJCQnav3+/Fi5cqLfffjutq3uVKlX0yiuv5DpWRK/8vl5Xr16tXr16pQ3G+MQTT6hcuXJaunRppu+pUqWKqlSpkmE9n7GIleuVz1dI+X+9bt68WT179tRJJ52k/v376+STT9YJJ5ygokWLauvWrZo5c6ZGjRqlrVu3SpKaNWum//znPwGPRY4AKAQinUVHdPnqq6/Snk4GeiUnJ3tXrlwZ8L3Btro6fvy49+qrr870HJK811xzjff48eNZxrp9+3Zv27ZtMz1GfHy895133snLfw5EuVi4Xn1bFWb1ql27dq5bdSE2hON6DeZaC+Y4K1eu9DZs2DDT9yYkJHi//vrrPP4XQbSLhWvWtxVgVq/mzZt7//jjjxD9l0E0ys/rdcyYMTm6VpVJi0QHn7GIheuVz1c48vN69d2e3euss87y/vPPP1nGSo4AKNh49AQ/ffv21e+//65XXnlF3377rTZu3KjixYurQYMGuvDCC3XzzTerVKlSeTpHXFycRo0apfPPP19vv/225s+frx07dqhSpUpq27atBg8erN69e2d7nEqVKmnWrFl655139OGHH+rPP//UgQMHVL16dfXs2VO33XZbyOu+IbrEwvU6aNAgVa1aVbNnz9bvv/+uf/75Rzt37lTRokVVqVIltW7dWn379tVll12mEiVK5ClWRLdwXK+h0qBBAy1atEgjRozQZ599plWrVunIkSOqVauW+vTpo9tuu0116tSJdJjIZ7Fwzd53331q2bKlZs+erWXLlmn79u3atWuX4uPjVbVqVbVp00YXXHCBzj33XBUpUiSisSJ/xcL16uAzFrFwvfL5Ckd+Xq+dOnXSpEmT9NNPP2nBggXauHGjtm3bppSUFCUkJKhu3brq0KGDLr300qAGYiRHABRsHq83kxFFAAAAAAAAAACIsLhIBwAAAAAAAAAAQGZIYgMAAAAAAAAAohZJbAAAAAAAAABA1CKJDQAAAAAAAACIWiSxAQAAAAAAAABRiyQ2AAAAAAAAACBqkcQGAAAAAAAAAEQtktgAAAAAAAAAgKhFEhsAAAAAAAAAELVIYgMAAAAAAAAAohZJbAAAAAAAAABA1CKJDQAAgKg1duxYeTweeTwerVu3LtLhhEVSUlLa3+y8kpKSQnqOdevWZTiHx+PRwIEDQ3oeAAAAIBRIYgMAACDkMkuS5vQFAAAAAEUjHQAAAACAjPr166cnnnhCklS8ePGQHrtGjRpasmRJ2nKvXr20efPmkJ4DAAAACBWS2AAAAAi59EnS9Jo3by5JatOmjcaMGZPpfs2aNSu0JS4SExPVrFmzfDl2sWLF/I5drFixfDkPAAAAEAoksQEAABBy6ZOkmSldunS+JWoBAAAAFAzUxAYAAAAAAAAARC2S2AAAAIhaY8eOTRvkcd26dRm2d+/eXR6PR927d5ckrVq1SjfccIPq1aunkiVLKikpSddcc43Wr1/v976lS5dq0KBBqlevnkqUKKFatWrpxhtv1D///BNUXOPHj9eFF16o2rVrq0SJEkpMTFSbNm306KOPavfu3Xn9s3NkypQpuvTSS1W3bl2VLFlSpUqVUp06ddShQwfdfffdmjJlSljjAQAAAEKNciIAAAAoEH766Sedd9552rdvX9q69evXa/To0frmm280ffp0NW7cWB999JEGDhyoI0eOpO23ceNGvfnmm5o4caJmzZql6tWrBzzH7t27dcEFF2RIDB8+fFgLFy7UwoUL9frrr2vChAnq0KFD/vyhPu644w69/PLLGdb//fff+vvvvzV37lyNHTtWO3bsyPdYAAAAgPxCS2wAAADEvM2bN+uiiy5SYmKiXnvtNc2dO1c///yzbr/9dnk8Hv3zzz+69tprNX/+fF111VWqX7++Ro4cqXnz5mnq1Km68sorJVnS+8477wx4jsOHD+u0007TlClTVKRIEV155ZX66KOPNGfOHP3888968sknVbFiRf3zzz/q06dPhtbfofbNN9+kJbBbtGihN954Q9OmTdOiRYs0depUDR8+XP3791d8fHy+xgEAAADkN1piAwAAIOatXLlSDRs21MyZM1W5cuW09Z07d1bRokX1/PPPa+bMmTrrrLPUrl07/fjjjypVqlTaft27d9ehQ4f02Wef6fPPP9f27dv9jiNJjz32mH799VclJibqp59+0sknn+y3vXPnzrr88svVsWNHbdmyRQ888IA++OCDfPubP/30U0lSnTp1NHPmTJUpU8Zve/fu3TVkyBDt2rUr32IAAAAAwoGW2AAAACgQXn311QyJZ0m66aab0uZ37NihkSNH+iWwHTfeeKMk6dixY5o9e7bftv3792vEiBGSpMcffzxDAttRp04dDR06VJL02Wef6cCBA7n7Y4KwdetWSVLr1q0zJLB9VahQId9iAAAAAMKBJDYAAABiXmJionr16hVwW926dVW2bFlJVnajSZMmAfc76aST0ubXrFnjt2369On6999/JUkXXHBBlrF07dpVknT06FEtXLgwuD8gF6pVqyZJmjFjhlavXp1v5wEAAAAijSQ2AAAAYl7Dhg3l8Xgy3Z6YmChJSk5OznYfSX6DQ0rSggUL0uarVasmj8eT6atZs2Zp+zqtpfPDVVddJUnauXOnmjVrpksuuURjxozRqlWr8u2cAAAAQCSQxAYAAEDMC1QexFdcXFy2+zn7SNLx48f9tv3zzz+5iislJSVX7wtGz549NXz4cJUsWVKHDh3SJ598oquvvloNGzZUzZo1dcMNN2jx4sX5dn4AAAAgXBjYEQAAAMiGb1L7119/VbFixYJ6X82aNfMrJEnSkCFDdOGFF+rDDz/Ujz/+qJkzZ+rff//Vpk2b9NZbb+ntt9/WAw88oCeeeCJf4wAAAADyE0lsAAAAIBsVK1ZMm69cuXK+J6dzokqVKrr99tt1++23KzU1Vb/99pu+/PJLDR8+XHv27NGTTz6ptm3bql+/fpEOFQAAAMgVyokAAAAA2WjVqlXa/MyZMyMYSdbi4uLUunVrPf7445o8eXLa+k8//TSCUQEAAAB5QxIbAAAAyMZpp52WVk/71VdfldfrjXBE2WvdurXKly8vSdqxY0eEowEAAAByjyQ2AAAAkI3ExETdfPPNkqRZs2bpjjvuUGpqaqb7b9u2TSNHjszXmD755BMdPHgw0+0LFizQ7t27JUl169bN11gAAACA/ERNbAAAACAIjz32mKZPn665c+fqlVde0bRp03TdddepZcuWKl26tHbv3q0//vhDP/30kyZOnKjmzZvr2muvzbd47rvvPt1www3q16+funbtquTkZJUuXVo7d+7UL7/8otdee02SVKRIkXyNAwAAAMhvJLEBAACAIMTHx+vHH3/UwIED9cUXX2jx4sVprbMDSUhIyPeY9uzZo3HjxmncuHEBt8fHx+vNN99UmzZt8j0WAAAAIL+QxAYAAACCVLZsWX3++ef65ZdfNG7cOP3888/avHmzDh48qISEBNWvX1/t2rXTWWedpTPOOCNfY5k6daq+/vprzZgxQytWrNDWrVu1e/dulSpVSvXr11fPnj114403ql69evkaBwAAAJDfPN5YGJUGAAAAKCSSkpK0fv16DRgwQGPHji2w5wQAAACCRUtsAAAAIArt2bNHS5culSQVL15cycnJITv20aNH9ddff/ktAwAAANGKJDYAAAAQhSZMmKAJEyZIkurUqaN169aF7NibNm1S8+bNQ3Y8AAAAID/FRToAAAAAAAAAAAAyQ01sAAAAAAAAAEDUoiU2AAAAAAAAACBqkcQGAAAAAAAAAEQtktgAAAAAAAAAgKhFEhsAAAAAAAAAELVIYgMAAAAAAAAAohZJbAAAAAAAAABA1CKJDQAAAAAAAACIWiSxAQAAAAAAAABRiyQ2AAAAAAAAACBqkcQGAAAAAAAAAESt/wN5axkehazqswAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "only_trigger_df = df[df['TRIGGER'] == '1']\n", + "rate_index = pd.to_numeric(only_trigger_df['inst_shell/inst_network_top/inst_network_stack/inst_roce_stack/inst_rdma_flow/inst_DCQCN/ecn_rates_index[4:0]']).to_list()\n", + "RC_gap_cycles = pd.to_numeric(only_trigger_df['inst_shell/inst_network_top/inst_network_stack/inst_roce_stack/inst_rdma_flow/inst_DCQCN/Rc[15:0]']).to_list()\n", + "RC_rate_hz = [1/((4*i)/1000000000) for i in RC_gap_cycles]\n", + "\n", + "rate_list = [rates[i] for i in rate_index]\n", + "\n", + "fig, ax1 = plt.subplots(figsize=(16, 7))\n", + "\n", + "x_pre = range(len(RC_gap_cycles))\n", + "x = [(i * (ila_tigger_interval)* 4)/1000000000 for i in x_pre]\n", + "\n", + "ax1.plot(x, RC_rate_hz, color='blue', label='Line 1 (left)')\n", + "ax1.set_ylabel('Sending rate [Hz]', color='blue', fontsize=20)\n", + "ax1.set_yscale('log')\n", + "ax1.tick_params(axis='y', colors='blue', labelsize=20)\n", + "\n", + "# For printing packet gap in cycles instead of sendrate\n", + "\n", + "#ax1.plot(x, RC_gap_cycles, color='blue', label='Line 1 (left)')\n", + "#ax1.set_ylabel('time between outgoing packets [cycles]', color='blue', fontsize=20)\n", + "#ax1.tick_params(axis='y', colors='blue', labelsize=20)\n", + "\n", + "ax2 = ax1.twinx()\n", + "\n", + "ax2.plot(x, rate_list, color='red', label='Line 2 (right)')\n", + "ax2.set_ylabel('ratio of packets ECN marked', color='red', fontsize=20)\n", + "ax2.tick_params(axis='y', colors='red', labelsize=20)\n", + "\n", + "# Labels and title\n", + "ax1.set_xlabel('Time [s]', fontsize=20)\n", + "ax1.tick_params(axis='x', labelsize=20)\n", + "plt.title('Default Parameters, Sending rate', fontsize=22)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e625b037-0c5f-4e47-bd6b-166350989e4d", + "metadata": {}, + "outputs": [], + "source": [ + "fig.savefig(savepath, dpi=400)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82e26e2a-0bee-458c-8e86-ab051748ff38", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/Congestion_Control_Test/Test_script/.ipynb_checkpoints/Reduction_factor_calc_scrip-checkpoint.ipynb b/examples/Congestion_Control_Test/Test_script/.ipynb_checkpoints/Reduction_factor_calc_scrip-checkpoint.ipynb new file mode 100644 index 000000000..d89f93525 --- /dev/null +++ b/examples/Congestion_Control_Test/Test_script/.ipynb_checkpoints/Reduction_factor_calc_scrip-checkpoint.ipynb @@ -0,0 +1,75 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 5, + "id": "e1ead512-fdcf-4470-886e-f076ae17f3a2", + "metadata": {}, + "outputs": [], + "source": [ + "#Generator Parameter\n", + "g = 1/16\n", + "#Number of bits for left shift (used to circumvent floating point calculations)\n", + "precision_bits = 7\n", + "#number of steps to print\n", + "reduction_steps = 29" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "34f5498c-decc-48ff-b3a0-41dbb827c04f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139, ]\n" + ] + } + ], + "source": [ + "a = 1.0\n", + "j = 1\n", + "prec = 2**precision_bits\n", + "str_list = \"\"\n", + "for i in range(0,reduction_steps):\n", + " str_list += str(round(prec *(1/(1-(a/2))))) + \", \"\n", + " a = (1-g)*a\n", + "\n", + "print('[' + str_list + ']')\n", + "#remove the last comma :)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8bc9f64-7430-4096-bd68-41c1fe4f8e82", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/Congestion_Control_Test/Test_script/Output_Grap_Script.ipynb b/examples/Congestion_Control_Test/Test_script/Output_Grap_Script.ipynb new file mode 100644 index 000000000..86e2cadbd --- /dev/null +++ b/examples/Congestion_Control_Test/Test_script/Output_Grap_Script.ipynb @@ -0,0 +1,134 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "f5274104-49ba-4f8d-9509-5322e1aa2ca5", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "import numpy as np\n", + "import sys" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8af48534-32bc-4d73-98a1-eba531c0bafe", + "metadata": {}, + "outputs": [], + "source": [ + "#enter path to csv file\n", + "path = './example_ila.csv'\n", + "savepath = './example_graph.png'\n", + "rates = [(1/10000), (1/100), (1/30), (1/25), (1/20), (1/10), (1/30), (1/40), (1/40) ,(1/100), (1/100), (1/150), (1/150), (1/200), (1/200), (1/10000)]\n", + "ila_tigger_interval = 40000\n", + "df = pd.read_csv(path)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b6981655-142b-4371-82fc-d0be6ecf0613", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Default Parameters, Sending rate')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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5513tHDhQknShx9+qHLlyjmt5/hz5pUjR45kjkuWLJk5Pnv2rGJiYtShQwe1bt1atWvXVokSJTInhpwxY4Y2btyorVu36qqrrtLatWtVokQJj/v69ddf9dJLL8lisWjw4MHq2LGj4uLitH37dlWrVi1zvdTUVFmtVtWrV09du3ZVw4YNVaZMGUVFRenQoUNasmSJZs6cqXPnzmnYsGEqX768evXq5XHf6enpuv766/X777/r8ssvV58+fVSmTBnt3LlT48aN0/79+7V69Wrdf//9Gj16tK688kqlpKRoyJAh6tSpk4oWLarVq1fr/fffV0pKiqZOnaru3bu7neh0/vz5uvbaa5Wenq6IiAj16NFD3bt3V+XKlZWamqo1a9ZoypQpOn36dOakpY8//njm47t166Zvv/1Wv/zyi959911J0n333adu3bp5/Dkl6amnntJLL70kSYqLi9MNN9ygtm3bKjExUSdOnNCCBQv0zTff6OTJk+rVq5d++eUXdezY0eM2b775Zi1ZskQXX3yxBg4cqIsuukhJSUlavHhx5jpPPvmk5syZI0kqW7as+vfvr4svvliJiYlKTU3Vrl27tGrVqsy/+bxUu3btzPGsWbO0a9cu1axZ069tfvzxx7rrrrtktVoVExOja6+9Vp06dVL58uX177//atmyZfriiy+UmpqqIUOGKCYmRgMHDvS4zaeeekpffPGFatSooVtuuUUNGjRQenq6FixYoM8++0wZGRkaN26c2rVrp5tuusnlNh544IHM3vaRkZG6+eab1aVLF8XGxmr9+vX65JNPNHbs2IC3gfr444/1xRdfqGzZshoyZIguvfRSRURE6Ndff9X48eOVlpammTNn6rXXXnM7Me+LL76o119/XZJksVh03XXXqUePHipevLi2bdumCRMmaNq0aV5XdPti+/btuuGGG5SUlKTrr79e3bt3V6lSpbR3715FRdlPP8+dO6fz58+rWrVquuyyy9SoUSOVL19eMTExOnr0qFauXKmvvvpKKSkpeuaZZ5SYmKh77rnHaV+5PeafP39ePXr0yKx6r1SpkgYMGKBLL71UxYoV0/79+zVjxgwtW7ZMmzZtUqdOnbRu3TqVLVs24M8XAADwUrCz6AAAFBTKoYLTkyuvvDLzsW+//Xa2+wcMGJB5/+jRo63nz5/Pts6FCxeso0aNylzvySefdLmvnCqdbc6fP2+dN2+e9cKFC27XOXbsmLVt27aZla+7d+92uV5+VWLbOFbn7dq1K8d9esvbSuxz585Zq1atmrnu5MmTM+9bvHix9cSJE24fm5GRYf3vf/+b+dgXXnjB5XqO1Z+SrOXKlbNu2LDBY/y7d++2rl+/3uM669ats5YrV84qyVqnTh23VdG2ylL9f6XkJ598km2dQ4cOWStUqJD599GkSRNrYmKide3atdnWXbBggVPloysHDhzIrO4sV66cdcWKFS7X279/v/WSSy7J3O+WLVuyreNYkT1x4kQPz4gxf/78zIrNNm3aWPfv3+9yvWXLllnj4+Otkqw1atRwWS3r+Hek/68IdvWatlrN67BkyZJWSdaLLrrI49/O6dOnXT63gZSUlGRNTEzMjL1s2bLW5557zrp+/Xq3P4MnGzZssMbExGT+vbn6XVmtVuvmzZutlSpVskqyxsfHW48fP55tnaxVuf3797empqZmW+/TTz/NXKdRo0Yu97ds2bLM33dcXJx16dKl2dY5cOCAtX79+k77DEQltv6/cvjUqVPZ1lu0aFFmhXaZMmWsaWlp2db566+/rNHR0VZJ1ujoaOusWbOyrZOcnGy9/PLL3VZJ+yrrt33i4uKsP//8s8fHHD9+3OXzmnW7devWtUqylixZ0pqUlORyPV+P+Y899ljm+sOGDbOmpKS4XG/s2LGZ69188805bhcAAOQdktgAAASIP0nsO++8023yecOGDZn33X777Tluq3379pkn/K4SON4msb3l+HX0F1980eU6hSmJff78eevdd9/tlMzJ2t7FGx06dLBKstauXdvl/VmT2N9++63P+3Bn/PjxmdtdtmyZy3Uck9iO7VKyevHFF53inDZtmtt1L7vsssz19u7dm+3+Bx54IPP+JUuWePwZtmzZkpnsu/vuu7Pd72sSu1mzZplJW1cJVEcfffSRx5/X8e+oWbNmHj8oOnjwYOa6Dz/8cI5x5ocZM2a4bHVRrFgxa7t27awjRoywTp8+3Xrs2LEct9W3b1+rZFqF/P333x7X/fHHHzP39corr2S73/F1X7duXZfHP5vWrVtnruvqAwlbXJKs7733ntvtbNiwwem5CEQSu3Tp0h6fu/79+3t8fTq2UXn88cfdbufo0aPWhISEPEliv/XWW7neVlY///xz5nY/++wzl+v4csw/fPhwZqus7t2757j/m266KfMDMXcfXgEAgLzHxI4AAISAUqVKZY6PHz/udJ/jhFWPPPJIjtu69dZbJZlJ0n777bcARehe7dq1VaFCBUnSypUr83x/oWLZsmWaOXNm5mX69Ol6+eWX1aRJE33wwQeZ6z333HO5al/SoUMHSear+Vn/JrKqXr26rr32Wp/3kdO+Je9+p/fdd59X2ypfvrz69evndl3H1hu21jk2VqtVU6ZMkSS1bds2xzYd9evXV6tWrSSZifn88ccff2jt2rWSpDvuuEOlS5f2uP5NN92U2TYhp30PHz5cERHu35IXK1Ysc2yLIdj69u2rRYsWqVmzZk63nz17Vr/++qveeecd9e/fXxUrVtT111+vP//80+V2Tp06lTnBYt++fZ1albhy+eWXq2LFipJyfl7vuecexcbGetyWTdb40tLSNHfuXEmmFdAdd9zhdjuXXnqprrjiCo+x+OrWW29VYmKi2/s9xS5JM2fOlCRFRERoxIgRbrdTpkwZ3XLLLbkP1I2iRYt6fM585evxKCfTp09XamqqJOnhhx/Ocf3BgwdLki5cuKAFCxb4vX8AAJA79MQGACAEWK3WzLHFYnG6b8mSJZKkIkWKaPPmzdmSe1n9888/mePNmzerU6dOfsV24MABffrpp1qwYIE2b96skydP6uzZsy7X3b9/v1/7CidPP/20x/ujo6M1evRojRo1Ktt958+f14wZMzRz5kytX79eBw4cUFJSktv+tPv37/eY1Grfvn22vxtP1q9fr88++0wrVqzQ33//rTNnzigtLc3tvj2Ji4tz6quele0DDklq3ry5x4St47onT550um/z5s2ZyfxSpUplJuo8iYyMlCTt2rVLqampmT3ifWV7DUomkeXNvosXL65Tp07l+HrNKRlfokQJtWnTRitXrtSCBQt0zTXX6N5771WXLl0UExPjVfx5oUOHDvr999/122+/aebMmVq+fLnWrl2r5OTkzHXS09M1Y8YMfffdd/rggw80dOhQp20sX748828+NjbWq+c1Pj5eBw8ezPF5bdu2rcf7q1SpkjnO+re2YcMGnTt3TpJ5bXlKhkvSZZddpvnz53tcxxf+xH748OHMHt0NGjRwek250rVr18ze8IHStGlTFS9e3Ov1t2/frilTpmjJkiX666+/dPr0aaWkpLhcNxD/Yxxfz4cPH87x7y7r/1QAABAcJLEBAAgBjomIrMnK3bt3SzKT8vXt29en7Z44ccKvuD788EM9+OCDbpPWWZ05c8av/YWzmJgYJSQkqF69eurcubOGDh2qWrVqZVvvr7/+0nXXXedTMiSn59UxqeXJ+fPnNXz4cH388cdOH5z4s+/SpUt7TKA7JgA9JeKzrmurlLSxvQ4kad68eZo3b57HbWV14sQJVapUyafHuNq3r5OX5vQa9OZ3N27cOHXr1k2nT5/WnDlzNGfOHBUtWlQtW7ZUu3bt1K1bN3Xt2tVp0rz80rp1a7Vu3VqSmQx269atWrFihWbPnq3vvvtOGRkZmROF1q5d2ylp7/i8Tpo0SZMmTfJ6vzk9rzl9+8HT39qBAwcyxzlVh3u7ji8CFftFF12U475cHaP85e3xSJJGjx6tl156SefPn/dq/UD8j3H8u7N9c8lb/v5PBQAAuUcSGwCAEOB4Ul22bFmn+06dOpXr7dqqCXPjq6++0t133515vW3bturcubNq1qypkiVLOiVS7rzzTh09elQXLlzI9f7CzcKFC9WlSxefHnP69Gl169YtM9FUqVIlXX311WrQoIHKly+vIkWKZFYqT5s2TdOnT5ekHJ/XokWLerX/kSNH6qOPPpJkKsV79OihVq1aqUqVKoqLi1N0dLQk6ciRI7rrrru82renymp/1s3Kn9eB5N9rIS9fg9787po1a6YNGzboueee05dffqnk5GSlpKRoyZIlWrJkiV555RWVL19ejz32mEaMGOHX8+yPiIgINWzYUA0bNtTtt9+u9evX64orrsg8Njz//PP66aefMtf353lNT0/PMZbc+vfffzPHju1c3ImLi8v1vlzxJ3bHSvhgxC55fzx6/fXX9dxzz0kyP3PXrl3Vvn17VatWTfHx8U7fNLB9gBuI/zHB+p8KAAD8QxIbAIAgy8jIcOpdbatqtLG1JShdunSOvZED6YknnpBkWjJ8++236t27t9t1hw0bFrD9FuRE+HvvvZeZwL755ps1YcIEty0hli9fHtB979u3L7NXd+XKlbVw4ULVqVPH5bqbNm0K6L4DwbE9wYMPPqg333wzKPuePXu2x9dCXqlevbomTJig999/X7/99ptWrFihZcuWadGiRfr33391+PBhPfDAA9qwYYMmTpyY7/G50qRJE40dO1Y33XSTJGnx4sVKT0/P/LDE8Xl95513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7qypU6fqzz//1MmTJ5Wenh6QbfvjtttuU5MmTTRmzBgtXrxYhw8fVnx8vKpVq6bevXvr7rvvVqVKlQL2PDiqUaOG1q1bp7fffjvzdyBJFSpUULt27XT77berS5cu2r17d8D3HUgtWrTQ5s2bNWPGDM2aNUu//fabDh8+rOTkZBUvXlxVq1ZVo0aN1KVLF11zzTWqUKGCy+3cfvvtql69uj744AOtXr1aR44cybHn/K233qq+fftq8uTJ+uGHH7RhwwYdO3ZM58+fV8mSJVWrVi01a9ZMl112mXr27OlU2Z0bnTt31h9//KGffvpJK1as0KZNm7R//34lJyerSJEiqly5slq0aKEBAwaod+/ebrdz9uzZzHHZsmVzHU/Pnj11+PBhLV++XIsXL9bq1au1bds2HTx4UGfPnlVsbKwSEhJUr149tWnTRv3791fjxo1z3K6tpcMXX3yhefPm6ffff9fRo0eVmpqq+Ph41ahRQ40bN1a3bt3Uu3fvzLYyeSkiIkLjx4/XDTfcoPfff18rV67UqVOnVK5cObVs2VJ33XWXrrzyyoBPwhoor7/+uq655hq99957WrZsmY4dO6bExEQ1btxYd9xxh66//nr99ttvmeuXLl06X+MrVaqUli9frvfff1/Tpk3Tli1bdO7cOZUvX14tWrTQLbfcoj59+ni9PV+P+dHR0XrjjTd03333acKECVq4cKH+/vtvnThxQhEREUpMTFTdunXVunVrXXnllerUqVMAfmoAAJBbFqs1h+mbAQAAAIS1AQMGaPr06bJYLFqzZo2aNWsW7JAQAt59993M/tDffvutT0ljAACA/EQSGwAAACjArFarypUrp2PHjql///6aNm1asENCCEhPT1fTpk21adMmRUdH659//vGrSh8AACAv5d/sHQAAAADy3fr163Xs2DFFRUXpxRdfDHY4yAdHjhzR5s2b3d6fmpqq2267TZs2bZIk3XDDDSSwAQBASKMnNgAAAFCA/fTTT5JMD/DatWsHORrkh71796ply5Zq0aKFLrvsMtWrV08lSpRQUlKSNm7cqGnTpungwYOSpMTERL3xxhtBjhgAAMAz2okAAAAAQAGyZs0atWzZMsf1atasqVmzZqlRo0b5EBUAAEDukcQGAAAAgAIkLS1N3377rb7//ntt2LBBR48e1fHjxyVJZcqUUdOmTdW7d28N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OD4nNWvaZ0J13I4rS5aYZeXKzp+uSVKnTvZxTq0yduywj7PODOut+HipXTupd2/pppvMsl27PE9gS34msZs2lUaONOeFt98u9e9veoPb/PqraYsyfLj5ZG7JEnMu+fTTUvXq/oYOoKCzvck5f57JYAGfUIkNAADCjOP7/SJFnKt24+LsY8ckadZKbEdZ8zyOFcaOFdBZt+GYnPa0L8exY0V01na07iqbixY1SV53+3JMhLtLBGfdl7vqbcfnLyHBfUxly3q3r6zPheP2HNuMZF3PMceVNYnt+FwAQdenj308caLrdTIypClTzDghwXlWU29YLNK115rx1q3SypWu11u50l6Jfe212c/1ata0z4y6cKH7r7InJUk//2zGF13k3FcpTPiVxJakN9+U/vMfk8j++mszA67t+bzrLjNB5QcfmOfQajVJ76ee8nevAAqDuDj7V+JoKQJ4gYkds+O5AAAgLDjOW5aQYP/2du3azlW6jgnjrl2lG28042bNpGrV7PfFxdlbzrZq5ZxorVDB3vK2Vy/p6qvttzsmzx2TuJ07SwMGmHGbNlK9evb7HN9uNG0qXXGFGffr51zsWLeufVynjnMOyXGuto4dTYGjbezYLcBxX5deai/+HDTIxOX489tUrWpvV3LTTc6teFu0sI+rV3ful+24r4svllq2tO/LsejUMXFdqZL9ObzmGumqq8y4bFnn32PlyvbJHX1tJQzkuVatzItPkj75RFqxIvs6b74pbdlixiNHZv8kZtEi8yKyWKQhQ1zv5/777UmP++4zkwM4Skkxt0vmKxn33+96O489ZpbJye7XefBB6cwZM86pPcaZM/Z1/fHvv9Ls2eYSAH71xJbM72LcOPMhxSuvmAp4x0/TbOu0bWuS17YDGADkxGIxbxyPHzctRWzftAEAn9FOBACAkGW1Sj/8YL8eHS09+aR5/9+jh0mAzp1rJleMijJFicuXm7m5brjBJEP79DFtLb780l6FPWeOeVy/fuZxv/wipaaaZOqaNdJPP5ltZGSYpOqVV5p9zpljErMRESZHtWKF/VvzbdtKffuaRO306fbk8O+/mznDOnSQPvtM+uYbU+RXooTJgaWlmaTu0qXmcsstJpk8a5ZJzFss0qZN0qpV5me64grTJeC668zPP3WqPQm+erXpCtC6tTRjhvTtt2ZfxYubn69NG3vr1zNn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t61LSJD69ZMee0yqXTs/zhr7hgwZoiFDhqR1dwAKrQKQxJbc+p9ZJbElafVqe1WqZN8FM2Zwww8gn9ASG/no22/9l087je8zAACAmFWrlr18tWsnNW0akXCC7qu/caNUvbq9MhuwTJLWrLFu9KNGSbt2ua3G//1Xeu89G+Txt99CEDmAgquAJLEdr70W3H67dlkt7RNPtMFwt27N37gAFEK+SWxaYiPE5s/3X+7SJTJxAAAAIMSuuspe5ctHLISgW2J//70lVIoXly66KPP9LrlE2rzZXa5VyxLfy5ZJ+/ZZy+xLL5WWLPHv0QoAaWK8JnZ6N98sde5sD/GCsW6dvc47T2rTRrr9dqlevXwMEEDhQUts5BNnUGNfzZpFJhYAAACE2NixkY4g+JbYs2fbtEcPq90ayDffSAsWuDWwv/9eWr/e3rt1qzRokO23YoX0+ed5DR1AgVXAWmJLloROSMjZe2bPtlbc118vvfyylRwBgDzxeNwHhLTERggtX27jQPhKSopIKAAAAAgHr9cSFfPn22v16pAN4hhI0EnsJUvsvuf00zPf54MP3PkXXpDOOMNdLllSGjlSat7clidMyGmoAAqNApjETkiwckt//pnz906eLN1xh/V0WbgwX78TABQGTlc4WmIjhNKXEpGkmjXDHwcAAADy2aRJNghYQoKUnCx16GCv5GRbd8450g8/hPy0QSex162z6UknZb7PtGk2LVcu8EBmHo909dWWgFm8OPggARQyBTCJLVkvlsaNc//+BQusvMh774UuJgCFkFNShCQ2QijQQ1pKBwIAABQgR45YwrdPH+m776QDB9zBEJ3XgQM22nfv3rbvkSMhO33QPy337rVppUqBt69bJ23bZjmnrl2lYsUC7+fUhPWtmw0AfgpoEtsxY4a0ZYt08cW5e/+LL9p4CgCQK05mkXIiCKG//7bpzTdLY8ZIV1wR2XgAAAAQYpddJn35peVsiha1ch3t20snnGDbt26V5s2TfvxROnpU+uQTu+f49NOQnD7oJLaTS8osgT5vnjvfpk3mx0lMtOmBA8GeGUChU8AGdkyvSxebVq1qn+mffCLt2hX8+48etQFzn3jCercAQI7QEhv5YMMGm3btKj3/vBQfH9l4AAAAEELffit98YUliHv0kEaPlurUCbzv339bsmLKFBsU8bvvrPV2HgWdIXIGc1yx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rAh4nFGjRqWda8iQIRm2r1q1ypuQkOCV5G3QoIH36NGj2f8HQEyJlet19uzZ3s2bN2f6d6SmpnoffPDBtFhOOukkb2pqatZ/PGJOOK7XQIYNG5b2vqlTpwb1nqFDh6a959lnn82wfdasWd6iRYt6JXm7desWdCyILbFyzU6ZMsW7e/fuTLcfOXLEO2DAgLRj9u3bN+hYEDvCcb0uWrTIm5iY6JXkLV26tPeXX37JVax8xiJWrlc+X+H15v/1+tdff3knT56cZQx79uxJi0OSd+jQoQH3I0cAFHxF85wFR4HxyiuvqFOnTjp48KDOOOMMPfDAA+rRo4cOHjyojz/+WG+//bYke8p511135eocycnJuueee/T0009rwYIF6tSpk+677z7Vr19fq1ev1jPPPKNFixZJku655x41bNgw4HEGDBig0aNHa+bMmRoxYoS2bt2q6667TuXLl9e8efP0+OOPa+/evYqLi9Orr76qokW51AuaWLlev//+ez399NM688wzdfrpp6tp06ZKTEzU4cOH9fvvv2v06NGaO3euJKlUqVJ65513qC1YAIXjepX8W5dI0m+//ZY2//3332vdunVpyw0aNPDreum455579Mknn2jFihW69957tWrVKl1yySUqWbKkpk6dqv/+9786duyYSpYsqZdffjnXsSK6xco1O27cOJ1zzjk655xz1L17dzVq1EgJCQnav3+/Fi5cqLfffjutq3uVKlX0yiuv5DpWRK/8vl5Xr16tXr16pQ3G+MQTT6hcuXJaunRppu+pUqWKqlSpkmE9n7GIleuVz1dI+X+9bt68WT179tRJJ52k/v376+STT9YJJ5ygokWLauvWrZo5c6ZGjRqlrVu3SpKaNWum//znPwGPRY4AKAQinUVHdPnqq6/Snk4GeiUnJ3tXrlwZ8L3Btro6fvy49+qrr870HJK811xzjff48eNZxrp9+3Zv27ZtMz1GfHy895133snLfw5EuVi4Xn1bFWb1ql27dq5bdSE2hON6DeZaC+Y4K1eu9DZs2DDT9yYkJHi//vrrPP4XQbSLhWvWtxVgVq/mzZt7//jjjxD9l0E0ys/rdcyYMTm6VpVJi0QHn7GIheuVz1c48vN69d2e3euss87y/vPPP1nGSo4AKNh49AQ/ffv21e+//65XXnlF3377rTZu3KjixYurQYMGuvDCC3XzzTerVKlSeTpHXFycRo0apfPPP19vv/225s+frx07dqhSpUpq27atBg8erN69e2d7nEqVKmnWrFl655139OGHH+rPP//UgQMHVL16dfXs2VO33XZbyOu+IbrEwvU6aNAgVa1aVbNnz9bvv/+uf/75Rzt37lTRokVVqVIltW7dWn379tVll12mEiVK5ClWRLdwXK+h0qBBAy1atEgjRozQZ599plWrVunIkSOqVauW+vTpo9tuu0116tSJdJjIZ7Fwzd53331q2bKlZs+erWXLlmn79u3atWuX4uPjVbVqVbVp00YXXHCBzj33XBUpUiSisSJ/xcL16uAzFrFwvfL5Ckd+Xq+dOnXSpEmT9NNPP2nBggXauHGjtm3bppSUFCUkJKhu3brq0KGDLr300qAGYiRHABRsHq83kxFFAAAAAAAAAACIsLhIBwAAAAAAAAAAQGZIYgMAAAAAAAAAohZJbAAAAAAAAABA1CKJDQAAAAAAAACIWiSxAQAAAAAAAABRiyQ2AAAAAAAAACBqkcQGAAAAAAAAAEQtktgAAAAAAAAAgKhFEhsAAAAAAAAAELVIYgMAAAAAAAAAohZJbAAAAAAAAABA1CKJDQAAgKg1duxYeTweeTwerVu3LtLhhEVSUlLa3+y8kpKSQnqOdevWZTiHx+PRwIEDQ3oeAAAAIBRIYgMAACDkMkuS5vQFAAAAAEUjHQAAAACAjPr166cnnnhCklS8ePGQHrtGjRpasmRJ2nKvXr20efPmkJ4DAAAACBWS2AAAAAi59EnS9Jo3by5JatOmjcaMGZPpfs2aNSu0JS4SExPVrFmzfDl2sWLF/I5drFixfDkPAAAAEAoksQEAABBy6ZOkmSldunS+JWoBAAAAFAzUxAYAAAAAAAAARC2S2AAAAIhaY8eOTRvkcd26dRm2d+/eXR6PR927d5ckrVq1SjfccIPq1aunkiVLKikpSddcc43Wr1/v976lS5dq0KBBqlevnkqUKKFatWrpxhtv1D///BNUXOPHj9eFF16o2rVrq0SJEkpMTFSbNm306KOPavfu3Xn9s3NkypQpuvTSS1W3bl2VLFlSpUqVUp06ddShQwfdfffdmjJlSljjAQAAAEKNciIAAAAoEH766Sedd9552rdvX9q69evXa/To0frmm280ffp0NW7cWB999JEGDhyoI0eOpO23ceNGvfnmm5o4caJmzZql6tWrBzzH7t27dcEFF2RIDB8+fFgLFy7UwoUL9frrr2vChAnq0KFD/vyhPu644w69/PLLGdb//fff+vvvvzV37lyNHTtWO3bsyPdYAAAAgPxCS2wAAADEvM2bN+uiiy5SYmKiXnvtNc2dO1c///yzbr/9dnk8Hv3zzz+69tprNX/+fF111VWqX7++Ro4cqXnz5mnq1Km68sorJVnS+8477wx4jsOHD+u0007TlClTVKRIEV155ZX66KOPNGfOHP3888968sknVbFiRf3zzz/q06dPhtbfofbNN9+kJbBbtGihN954Q9OmTdOiRYs0depUDR8+XP3791d8fHy+xgEAAADkN1piAwAAIOatXLlSDRs21MyZM1W5cuW09Z07d1bRokX1/PPPa+bMmTrrrLPUrl07/fjjjypVqlTaft27d9ehQ4f02Wef6fPPP9f27dv9jiNJjz32mH799VclJibqp59+0sknn+y3vXPnzrr88svVsWNHbdmyRQ888IA++OCDfPubP/30U0lSnTp1NHPmTJUpU8Zve/fu3TVkyBDt2rUr32IAAAAAwoGW2AAAACgQXn311QyJZ0m66aab0uZ37NihkSNH+iWwHTfeeKMk6dixY5o9e7bftv3792vEiBGSpMcffzxDAttRp04dDR06VJL02Wef6cCBA7n7Y4KwdetWSVLr1q0zJLB9VahQId9iAAAAAMKBJDYAAABiXmJionr16hVwW926dVW2bFlJVnajSZMmAfc76aST0ubXrFnjt2369On6999/JUkXXHBBlrF07dpVknT06FEtXLgwuD8gF6pVqyZJmjFjhlavXp1v5wEAAAAijSQ2AAAAYl7Dhg3l8Xgy3Z6YmChJSk5OznYfSX6DQ0rSggUL0uarVasmj8eT6atZs2Zp+zqtpfPDVVddJUnauXOnmjVrpksuuURjxozRqlWr8u2cAAAAQCSQxAYAAEDMC1QexFdcXFy2+zn7SNLx48f9tv3zzz+5iislJSVX7wtGz549NXz4cJUsWVKHDh3SJ598oquvvloNGzZUzZo1dcMNN2jx4sX5dn4AAAAgXBjYEQAAAMiGb1L7119/VbFixYJ6X82aNfMrJEnSkCFDdOGFF+rDDz/Ujz/+qJkzZ+rff//Vpk2b9NZbb+ntt9/WAw88oCeeeCJf4wAAAADyE0lsAAAAIBsVK1ZMm69cuXK+J6dzokqVKrr99tt1++23KzU1Vb/99pu+/PJLDR8+XHv27NGTTz6ptm3bql+/fpEOFQAAAMgVyokAAAAA2WjVqlXa/MyZMyMYSdbi4uLUunVrPf7445o8eXLa+k8//TSCUQEAAAB5QxIbAAAAyMZpp52WVk/71VdfldfrjXBE2WvdurXKly8vSdqxY0eEowEAAAByjyQ2AAAAkI3ExETdfPPNkqRZs2bpjjvuUGpqaqb7b9u2TSNHjszXmD755BMdPHgw0+0LFizQ7t27JUl169bN11gAAACA/ERNbAAAACAIjz32mKZPn665c+fqlVde0bRp03TdddepZcuWKl26tHbv3q0//vhDP/30kyZOnKjmzZvr2muvzbd47rvvPt1www3q16+funbtquTkZJUuXVo7d+7UL7/8otdee02SVKRIkXyNAwAAAMhvJLEBAACAIMTHx+vHH3/UwIED9cUXX2jx4sVprbMDSUhIyPeY9uzZo3HjxmncuHEBt8fHx+vNN99UmzZt8j0WAAAAIL+QxAYAAACCVLZsWX3++ef65ZdfNG7cOP3888/avHmzDh48qISEBNWvX1/t2rXTWWedpTPOOCNfY5k6daq+/vprzZgxQytWrNDWrVu1e/dulSpVSvXr11fPnj114403ql69evkaBwAAAJDfPN5YGJUGAAAAKCSSkpK0fv16DRgwQGPHji2w5wQAAACCRUtsAAAAIArt2bNHS5culSQVL15cycnJITv20aNH9ddff/ktAwAAANGKJDYAAAAQhSZMmKAJEyZIkurUqaN169aF7NibNm1S8+bNQ3Y8AAAAID/FRToAAAAAAAAAAAAyQ01sAAAAAAAAAEDUoiU2AAAAAAAAACBqkcQGAAAAAAAAAEQtktgAAAAAAAAAgKhFEhsAAAAAAAAAELVIYgMAAAAAAAAAohZJbAAAAAAAAABA1CKJDQAAAAAAAACIWiSxAQAAAAAAAABRiyQ2AAAAAAAAACBqkcQGAAAAAAAAAESt/wN5axkehazqswAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "only_trigger_df = df[df['TRIGGER'] == '1']\n", + "rate_index = pd.to_numeric(only_trigger_df['inst_shell/inst_network_top/inst_network_stack/inst_roce_stack/inst_rdma_flow/inst_DCQCN/ecn_rates_index[4:0]']).to_list()\n", + "RC_gap_cycles = pd.to_numeric(only_trigger_df['inst_shell/inst_network_top/inst_network_stack/inst_roce_stack/inst_rdma_flow/inst_DCQCN/Rc[15:0]']).to_list()\n", + "RC_rate_hz = [1/((4*i)/1000000000) for i in RC_gap_cycles]\n", + "\n", + "rate_list = [rates[i] for i in rate_index]\n", + "\n", + "fig, ax1 = plt.subplots(figsize=(16, 7))\n", + "\n", + "x_pre = range(len(RC_gap_cycles))\n", + "x = [(i * (ila_tigger_interval)* 4)/1000000000 for i in x_pre]\n", + "\n", + "ax1.plot(x, RC_rate_hz, color='blue', label='Line 1 (left)')\n", + "ax1.set_ylabel('Sending rate [Hz]', color='blue', fontsize=20)\n", + "ax1.set_yscale('log')\n", + "ax1.tick_params(axis='y', colors='blue', labelsize=20)\n", + "\n", + "# For printing packet gap in cycles instead of sendrate\n", + "\n", + "#ax1.plot(x, RC_gap_cycles, color='blue', label='Line 1 (left)')\n", + "#ax1.set_ylabel('time between outgoing packets [cycles]', color='blue', fontsize=20)\n", + "#ax1.tick_params(axis='y', colors='blue', labelsize=20)\n", + "\n", + "ax2 = ax1.twinx()\n", + "\n", + "ax2.plot(x, rate_list, color='red', label='Line 2 (right)')\n", + "ax2.set_ylabel('ratio of packets ECN marked', color='red', fontsize=20)\n", + "ax2.tick_params(axis='y', colors='red', labelsize=20)\n", + "\n", + "# Labels and title\n", + "ax1.set_xlabel('Time [s]', fontsize=20)\n", + "ax1.tick_params(axis='x', labelsize=20)\n", + "plt.title('Default Parameters, Sending rate', fontsize=22)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e625b037-0c5f-4e47-bd6b-166350989e4d", + "metadata": {}, + "outputs": [], + "source": [ + "fig.savefig(savepath, dpi=400)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82e26e2a-0bee-458c-8e86-ab051748ff38", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/Congestion_Control_Test/Test_script/Reduction_factor_calc_scrip.ipynb b/examples/Congestion_Control_Test/Test_script/Reduction_factor_calc_scrip.ipynb new file mode 100644 index 000000000..d89f93525 --- /dev/null +++ b/examples/Congestion_Control_Test/Test_script/Reduction_factor_calc_scrip.ipynb @@ -0,0 +1,75 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 5, + "id": "e1ead512-fdcf-4470-886e-f076ae17f3a2", + "metadata": {}, + "outputs": [], + "source": [ + "#Generator Parameter\n", + "g = 1/16\n", + "#Number of bits for left shift (used to circumvent floating point calculations)\n", + "precision_bits = 7\n", + "#number of steps to print\n", + "reduction_steps = 29" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "34f5498c-decc-48ff-b3a0-41dbb827c04f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[256, 241, 228, 218, 209, 201, 194, 188, 182, 178, 173, 170, 166, 163, 161, 158, 156, 154, 152, 150, 148, 147, 146, 144, 143, 142, 141, 140, 139, ]\n" + ] + } + ], + "source": [ + "a = 1.0\n", + "j = 1\n", + "prec = 2**precision_bits\n", + "str_list = \"\"\n", + "for i in range(0,reduction_steps):\n", + " str_list += str(round(prec *(1/(1-(a/2))))) + \", \"\n", + " a = (1-g)*a\n", + "\n", + "print('[' + str_list + ']')\n", + "#remove the last comma :)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8bc9f64-7430-4096-bd68-41c1fe4f8e82", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/Congestion_Control_Test/Test_script/example_graph.png b/examples/Congestion_Control_Test/Test_script/example_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..97f437adee44711c725725426afca3b73e6cbd93 GIT binary patch literal 378223 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%0AdUaPDSxap!*+)qgmco&%0Mlp57s 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 literal 0 HcmV?d00001 From 59db350210f79135fc338f1db354e8dd15402f22 Mon Sep 17 00:00:00 2001 From: pomsi <72771413+pomsi@users.noreply.github.com> Date: Mon, 10 Nov 2025 06:43:44 +0100 Subject: [PATCH 42/52] Update README.md --- examples/Congestion_Control_Test/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/Congestion_Control_Test/README.md b/examples/Congestion_Control_Test/README.md index f5387b7fd..a59b5850e 100644 --- a/examples/Congestion_Control_Test/README.md +++ b/examples/Congestion_Control_Test/README.md @@ -1,6 +1,6 @@ -# Coyote Example 8: Using the FPGA as a SmartNIC for Remote Direct Memory Access (RDMA) -Welcome to the eigth Coyote example! If you've made it to this point, you've already learnt all the essential concepts of FPGA-CPU interaction for data movement and control flow. Thus, we're adding a new component by demonstrating how Coyote can be used to turn the FPGA into a SmartNIC for 100G RDMA-over-Converged-Ethernet (RoCE v2), allowing for efficient, high-performance data exchange between remote processing nodes and FPGA-offloaded preprocessing of the data streams. As with all Coyote examples, a brief description of the core Coyote concepts covered in this example is included below. - +# Changed Files +- **(added) hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv:** DCQCN implementation, tunable parameters for algorithm and for the test run described in file. **`define CC_TEST ** controls wether to compile with the test setting or without. +- **(added) hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv:** ##### Table of Contents [RDMA Overview](#rdma-overview) @@ -183,4 +183,4 @@ The following results are expected for reads (left) and writes (right):

-

\ No newline at end of file +

From 07ec4adaca15f85412de531566f035cc2239abf3 Mon Sep 17 00:00:00 2001 From: pomsi <72771413+pomsi@users.noreply.github.com> Date: Mon, 10 Nov 2025 06:45:48 +0100 Subject: [PATCH 43/52] Update MT_pomsarc_CC_QP_unit_DCQCN.sv Made the dcqcn without test the default case --- hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv index 5161bc2f7..6c4090aa4 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv @@ -2,7 +2,7 @@ import lynxTypes::*; // defines wether simulated ECN markings should be generated and also add test readout signals as ILAs -`define CC_TEST +//`define CC_TEST module cc_queue ( @@ -513,4 +513,4 @@ queue_meta #( -endmodule \ No newline at end of file +endmodule From 945166714f2d13d57a4f10ecdbb2134b9c42fdd6 Mon Sep 17 00:00:00 2001 From: pomsi <72771413+pomsi@users.noreply.github.com> Date: Mon, 10 Nov 2025 07:08:30 +0100 Subject: [PATCH 44/52] Update README.md --- examples/Congestion_Control_Test/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/Congestion_Control_Test/README.md b/examples/Congestion_Control_Test/README.md index a59b5850e..168eae553 100644 --- a/examples/Congestion_Control_Test/README.md +++ b/examples/Congestion_Control_Test/README.md @@ -1,6 +1,11 @@ # Changed Files - **(added) hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv:** DCQCN implementation, tunable parameters for algorithm and for the test run described in file. **`define CC_TEST ** controls wether to compile with the test setting or without. -- **(added) hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv:** +- **(added) hw/hdl/network/rdma/MT_pomsarc_new _flow.sv:** replaces the old flow control by replacing the request queue with the dcqcn module. This flow control is the "default case" (changes starting Line 160) +- **(added) hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv:** the multiqueue version of the flow control commented out. to use thus version comment aut the whole previous file (MT_pomsarc_new _flow.sv) **!!SEE [1] BELOW FOR NOTES** +- **hw/hdl/network/rdma/roce_stack.sv:** added the ecn forward (Line 126, 228 (ila) ) + +- **scripts/wr_hdl/template_gen/lynx_pkg_tmplt.txt:** adjusted the ack interface (Line 197, 432) +- **scripts/ip_inst/network_infrastructure.tcl:** added more ILAs at the bottom ##### Table of Contents [RDMA Overview](#rdma-overview) From 3561c425fa26cb9f3c77bcdc9993d08607d72213 Mon Sep 17 00:00:00 2001 From: pomsi <72771413+pomsi@users.noreply.github.com> Date: Mon, 10 Nov 2025 07:16:36 +0100 Subject: [PATCH 45/52] Update README.md --- examples/Congestion_Control_Test/README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/Congestion_Control_Test/README.md b/examples/Congestion_Control_Test/README.md index 168eae553..efce905d9 100644 --- a/examples/Congestion_Control_Test/README.md +++ b/examples/Congestion_Control_Test/README.md @@ -1,4 +1,4 @@ -# Changed Files +# Changed Files (Coyote) - **(added) hw/hdl/network/rdma/MT_pomsarc_CC_QP_unit_DCQCN.sv:** DCQCN implementation, tunable parameters for algorithm and for the test run described in file. **`define CC_TEST ** controls wether to compile with the test setting or without. - **(added) hw/hdl/network/rdma/MT_pomsarc_new _flow.sv:** replaces the old flow control by replacing the request queue with the dcqcn module. This flow control is the "default case" (changes starting Line 160) - **(added) hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv:** the multiqueue version of the flow control commented out. to use thus version comment aut the whole previous file (MT_pomsarc_new _flow.sv) **!!SEE [1] BELOW FOR NOTES** @@ -6,6 +6,16 @@ - **scripts/wr_hdl/template_gen/lynx_pkg_tmplt.txt:** adjusted the ack interface (Line 197, 432) - **scripts/ip_inst/network_infrastructure.tcl:** added more ILAs at the bottom + +# Changed Files (HLS stack) +- **hls/ipv4/ipv4.cpp:** +- **hls/ipv4/ipv4.hpp:** +- **hls/udp/udp.cpp:** +- **hls/udp/udp.hpp:** +- **hls/ib_transport_protocol/ib_transport_protocol.cpp:** +- **hls/ib_transport_protocol/ib_transport_protocol.hpp:** + + ##### Table of Contents [RDMA Overview](#rdma-overview) From 5ba8117613512ef256bb74d030301ab4fdc487a5 Mon Sep 17 00:00:00 2001 From: pomsi <72771413+pomsi@users.noreply.github.com> Date: Mon, 10 Nov 2025 07:23:22 +0100 Subject: [PATCH 46/52] Update README.md --- examples/Congestion_Control_Test/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/Congestion_Control_Test/README.md b/examples/Congestion_Control_Test/README.md index efce905d9..83bf47381 100644 --- a/examples/Congestion_Control_Test/README.md +++ b/examples/Congestion_Control_Test/README.md @@ -15,6 +15,9 @@ - **hls/ib_transport_protocol/ib_transport_protocol.cpp:** - **hls/ib_transport_protocol/ib_transport_protocol.hpp:** +# Running a Test +To run a test make sure the CC_TEST variable is set in the MT_pomsarc_CC_QP_unit_DCQCN.sv file. compile sw of the Congestion_Control_Test example, exactly the same as example 08. when running set the -o flag to 1 (write) and the -i flag (didnt make sure wether the other flags behave). + ##### Table of Contents [RDMA Overview](#rdma-overview) From b6ab326520e96bf2bbfe54149d71bf688be7ba8b Mon Sep 17 00:00:00 2001 From: pomsi <72771413+pomsi@users.noreply.github.com> Date: Mon, 10 Nov 2025 07:41:37 +0100 Subject: [PATCH 47/52] Update README.md --- examples/Congestion_Control_Test/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Congestion_Control_Test/README.md b/examples/Congestion_Control_Test/README.md index 83bf47381..6932d07e0 100644 --- a/examples/Congestion_Control_Test/README.md +++ b/examples/Congestion_Control_Test/README.md @@ -16,7 +16,7 @@ - **hls/ib_transport_protocol/ib_transport_protocol.hpp:** # Running a Test -To run a test make sure the CC_TEST variable is set in the MT_pomsarc_CC_QP_unit_DCQCN.sv file. compile sw of the Congestion_Control_Test example, exactly the same as example 08. when running set the -o flag to 1 (write) and the -i flag (didnt make sure wether the other flags behave). +To run a test make sure the CC_TEST variable is set in the MT_pomsarc_CC_QP_unit_DCQCN.sv file. compile sw of the Congestion_Control_Test example, exactly the same as example 08. when running set the -o flag to 1 (write) and the -i flag (didnt make sure wether the other flags behave). Run Vivado from the client side. switch to ila 3 (i think, the one with Rc and Rt). select a good amount of trigger windows (i usualy take 2048) and trigger on the 'ila_trigger' AND 'ecn_tedt_starter' signals equal to 1. run the test and export as CSV. CSV file can be input directly to the python script for plotting. ##### Table of Contents From edfcd4dac0d3f296cb691556e1b9a4a454316cfe Mon Sep 17 00:00:00 2001 From: Cyril Pomsar Date: Sun, 16 Nov 2025 18:49:20 +0100 Subject: [PATCH 48/52] replaced program hacc ocal with new version --- util/program_hacc_local.sh | 126 ++++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 59 deletions(-) diff --git a/util/program_hacc_local.sh b/util/program_hacc_local.sh index 3bd1b6ce0..b7b30045f 100644 --- a/util/program_hacc_local.sh +++ b/util/program_hacc_local.sh @@ -1,75 +1,83 @@ -## -## Read in the arguments for programming the FPGA -## +#!/bin/bash -# Required arguments: Bitstream-path, driver-path and qsfp-port (assigned automatically if not provided) +###################################################################################### +# This file is part of the Coyote +# +# MIT Licence +# Copyright (c) 2025, Systems Group, ETH Zurich +# All rights reserved. +# +# 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. +###################################################################################### + +### A utility script to program a local FPGA with a Coyote bitstream and insert the driver on the ETHZ HACC cluster +### This script should be executed from the node where the FPGA is (e.g., alveo-u55c-05, alveo-box-01, etc.) and from the Coyote/util directory + +# Check Vivado is available + if ! [ -x "$(command -v vivado)" ]; then + echo "Vivado not found. Please make sure Vivado is installed." + exit 1 +fi + +# Arguments: bistream path, driver path and device ID (optional) if [ "$1" == "-h" ]; then echo "Usage: $0 " >&2 exit 0 fi -if ! [ -x "$(command -v vivado)" ]; then - echo "Vivado does NOT exist in the system." - exit 1 -fi - -# Get the current basepath of the script -BASE_PATH=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) - -# Set relevant parameters for FPGA-programming -PROGRAM_FPGA=1 -DRV_INSERT=1 - -BIT_PATH=$1 -DRV_PATH=$2 - +BITSTREAM_PATH=$1 +DRIVER_PATH=$2 if [ -z "$3" ]; then - DEVICE=1 + DEVICE=1 else - DEVICE=$3 + DEVICE=$3 fi -## -## Program the FPGA via the hdev call - only work locally for the server that you are currently logged in to -## - -if [ $PROGRAM_FPGA -eq 1 ]; then - echo "***" - echo "** Programming the FPGA with $BIT_PATH" - echo "***" - hdev program vivado -b $BIT_PATH -d $DEVICE - echo "***" - echo "** FPGA programmed" - echo "***" -fi -echo " " -echo " " +# Environment set-up --- get IP and MAC address of the FPGA; convert to hex +CLI_PATH=/opt/hdev/cli +IP_ADDRESS=$($CLI_PATH/hdev get network -d 1 | awk '$1 == "1:" {print $2}') +MAC_ADDRESS=$($CLI_PATH/hdev get network -d 1 | awk '$1 == "1:" {print $3}' | tr -d '()') +IP_HEX=$($CLI_PATH/common/address_to_hex IP $IP_ADDRESS) +MAC_HEX=$($CLI_PATH/common/address_to_hex MAC $MAC_ADDRESS) -## -## Insert the driver for the FPGA with the required IP- and MAC-Address -## +# First, remove any drivers from before +# This causes the DMA core to shut down gracefully, leading to less problems with PCIe hotplug. +hdev program driver --remove coyote_driver -if [ $DRV_INSERT -eq 1 ]; then - echo "***" - echo "** Inserting the driver from $DRV_PATH" - echo "***" - echo "** IP_ADDRESS: $DEVICE_1_IP_ADDRESS_HEX_0" - echo "** MAC_ADDRESS: $DEVICE_1_MAC_ADDRESS_0" - hdev program driver -i $DRV_PATH -p ip_addr=$DEVICE_1_IP_ADDRESS_HEX_0,mac_addr=$DEVICE_1_MAC_ADDRESS_0 - # hdev program driver -m $DRV_PATH - echo "***" - echo "** Driver loaded " - echo "***" -fi -echo " " -echo " " +# Bitstream loading +echo "** Programming the FPGA with $BITSTREAM_PATH" +$CLI_PATH/hdev program vivado -b $BITSTREAM_PATH -d $DEVICE +if [ $? -ne 0 ]; then + echo "Error: Failed to program the FPGA with the bitstream." + exit 1 +fi -## -## Final greetings -## +# Driver insertion +echo "** Inserting the driver from $DRIVER_PATH" +echo "** FPGA IP_ADDRESS: $IP_HEX" +echo "** FPGA MAC_ADDRESS: $MAC_HEX" +echo "y" | $CLI_PATH/hdev program driver -i $DRIVER_PATH -p ip_addr=$IP_HEX,mac_addr=$MAC_HEX +if [ $? -ne 0 ]; then + echo "Error: Failed to insert Coyote driver." + exit 1 +fi -echo "***" +# Final greetings echo "** It's Coyote after all, so thoughts & prayers!" echo "** Lasciate ogni speranza, voi ch'entrate - Ihr, die ihr hier eintretet, lasst alle Hoffnung fahren" -echo "***" - From 3ec58f96baccfc0ab45f7ea4acc0629b881f1178 Mon Sep 17 00:00:00 2001 From: pomsi <72771413+pomsi@users.noreply.github.com> Date: Mon, 17 Nov 2025 19:20:58 +0100 Subject: [PATCH 49/52] Update MT_pomsarc_new_flow_multiqueue.sv --- hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv b/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv index 78cfd82c3..4e7135c5d 100644 --- a/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv +++ b/hw/hdl/network/rdma/MT_pomsarc_new_flow_multiqueue.sv @@ -307,12 +307,8 @@ end genvar i_gen_out; generate for (i_gen_out = 0; i_gen_out < N_QUEUES; i_gen_out++) begin: gen_out - always_comb begin - ccq_m_req[i_gen_out].ready = 0; - - if(readout_conter == i_gen_out) begin - ccq_s_req[i_gen_in].ready = m_req.ready; - end + always_comb + ccq_m_req[i_gen_out].ready = (readout_counter == i_gen_out) ? m_req.ready : 1'b0; end end endgenerate @@ -339,4 +335,4 @@ endgenerate endmodule -*/ \ No newline at end of file +*/ From 04d5d56209cbcf828aae9a883e170953bbfd7219 Mon Sep 17 00:00:00 2001 From: pomsi <72771413+pomsi@users.noreply.github.com> Date: Mon, 17 Nov 2025 19:48:05 +0100 Subject: [PATCH 50/52] Update README.md --- examples/Congestion_Control_Test/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/Congestion_Control_Test/README.md b/examples/Congestion_Control_Test/README.md index 6932d07e0..090b02084 100644 --- a/examples/Congestion_Control_Test/README.md +++ b/examples/Congestion_Control_Test/README.md @@ -18,6 +18,10 @@ # Running a Test To run a test make sure the CC_TEST variable is set in the MT_pomsarc_CC_QP_unit_DCQCN.sv file. compile sw of the Congestion_Control_Test example, exactly the same as example 08. when running set the -o flag to 1 (write) and the -i flag (didnt make sure wether the other flags behave). Run Vivado from the client side. switch to ila 3 (i think, the one with Rc and Rt). select a good amount of trigger windows (i usualy take 2048) and trigger on the 'ila_trigger' AND 'ecn_tedt_starter' signals equal to 1. run the test and export as CSV. CSV file can be input directly to the python script for plotting. +# Notes + +- [1] To compile with multiqueue the dreq_t interface a(Lines 281, 316/317) + ##### Table of Contents [RDMA Overview](#rdma-overview) From c23493ca6187a2d9aa627817fe3db1d617c8ecd4 Mon Sep 17 00:00:00 2001 From: pomsi <72771413+pomsi@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:24:21 +0100 Subject: [PATCH 51/52] Update README.md --- examples/Congestion_Control_Test/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/Congestion_Control_Test/README.md b/examples/Congestion_Control_Test/README.md index 090b02084..5beb6cfd7 100644 --- a/examples/Congestion_Control_Test/README.md +++ b/examples/Congestion_Control_Test/README.md @@ -7,20 +7,20 @@ - **scripts/wr_hdl/template_gen/lynx_pkg_tmplt.txt:** adjusted the ack interface (Line 197, 432) - **scripts/ip_inst/network_infrastructure.tcl:** added more ILAs at the bottom +- **hls/ipv4/ipv4.cpp:** added ecn marks to outgoing ecn packets and forward ecn from incoming packets. (Lines: 59, 111, 212) # Changed Files (HLS stack) -- **hls/ipv4/ipv4.cpp:** -- **hls/ipv4/ipv4.hpp:** -- **hls/udp/udp.cpp:** -- **hls/udp/udp.hpp:** -- **hls/ib_transport_protocol/ib_transport_protocol.cpp:** -- **hls/ib_transport_protocol/ib_transport_protocol.hpp:** +- **hls/ipv4/ipv4.hpp:** adjusted interface and set/get ecn function (Lines 36, 657) +- **hls/udp/udp.cpp:** same as ipv4 (Lines 173, 195) +- **hls/udp/udp.hpp:** same as ipv4 (Lines 47) +- **hls/ib_transport_protocol/ib_transport_protocol.cpp:** Added the incoming ecn -> outgoing marked ack path and the ecn forwarding to the flow control (Lines: 78, 592, 615, 656, 689, 1533, 1636, 1657, 1687, 1728, 1742, 1779, 2115, 2125, 2365, 2741, 2770) +- **hls/ib_transport_protocol/ib_transport_protocol.hpp:** same as ipv4 (Lines 240, 259, 281) # Running a Test -To run a test make sure the CC_TEST variable is set in the MT_pomsarc_CC_QP_unit_DCQCN.sv file. compile sw of the Congestion_Control_Test example, exactly the same as example 08. when running set the -o flag to 1 (write) and the -i flag (didnt make sure wether the other flags behave). Run Vivado from the client side. switch to ila 3 (i think, the one with Rc and Rt). select a good amount of trigger windows (i usualy take 2048) and trigger on the 'ila_trigger' AND 'ecn_tedt_starter' signals equal to 1. run the test and export as CSV. CSV file can be input directly to the python script for plotting. +To run a test make sure the CC_TEST variable is set in the MT_pomsarc_CC_QP_unit_DCQCN.sv file (not by default). set the Ecn_rates to the different phases, an entry in that array corresponds to the amount of acks not marked between the marked ones. To simulate no marked packets just use a high value (eg. 9999). also adjust the Ecn_rates_max_index variable. compile sw of the Congestion_Control_Test example, exactly the same as example 08. when running set the -o flag to 1 (write) and the -i flag (didnt make sure wether the other flags behave). Run Vivado from the client side. switch to ila 3 (i think, the one with Rc and Rt). select a good amount of trigger windows (i usualy take 2048) and trigger on the 'ila_trigger' AND 'ecn_test_starter' signals equal to 1. ila_trigger goes to 1 in a defined cycle interval and ecn_test_started goes and stays at 1 as soon as the first ack arives. Run the test and export as CSV. CSV file can be input directly to the python script for plotting. # Notes -- [1] To compile with multiqueue the dreq_t interface a(Lines 281, 316/317) +- [1] To compile with multiqueue the dreq_t interface array needs to be changed because of non constant indexing (Lines 281, 316/317) im not 100% sure how to fix this (and also cant test it anymore :( ) ##### Table of Contents From 417f4e512cdb80566e6bc584addc6f8485a7241e Mon Sep 17 00:00:00 2001 From: pomsi <72771413+pomsi@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:28:36 +0100 Subject: [PATCH 52/52] Update README.md --- examples/Congestion_Control_Test/README.md | 185 --------------------- 1 file changed, 185 deletions(-) diff --git a/examples/Congestion_Control_Test/README.md b/examples/Congestion_Control_Test/README.md index 5beb6cfd7..c98ca4388 100644 --- a/examples/Congestion_Control_Test/README.md +++ b/examples/Congestion_Control_Test/README.md @@ -21,188 +21,3 @@ To run a test make sure the CC_TEST variable is set in the MT_pomsarc_CC_QP_unit # Notes - [1] To compile with multiqueue the dreq_t interface array needs to be changed because of non constant indexing (Lines 281, 316/317) im not 100% sure how to fix this (and also cant test it anymore :( ) - - -##### Table of Contents -[RDMA Overview](#rdma-overview) - -[Example Overview](#example-overview) - -[Hardware Concepts](#hardware-concepts) - -[Software Concepts](#software-concepts) - -[Additional Information](#additional-information) - -## RDMA Overview -Remote Direct Memory Access over Converged Ethernet (RoCE v2) is a high-performance networking protocol originally developed for High-Performance Computing. It aims to combine high throughput, low latency and low CPU-utilization by offloading the network stack and direct memory access to the Network Interface Card (NIC), thus grounding its performance claims on host-bypassing and zero-copy: applications running on the host CPUs directly expose memory buffers for DMA to the NIC, so that data can be exchanged without including the host CPU or the OS running on it. In this example, we demonstrate how Coyote can be configured to use the FPGA in the role of such a NIC, allowing for fully protocol-compliant RoCE v2-traffic at 100 Gbps. -For further understanding of the RDMA example, we need to shed some light on the intrinsics of this network standard. -For RDMA communication, two nodes are connected via a so-called Queue Pair (QP), based on previously exchanged information such as respective IP-addresses, location and size of the allocated memory buffers and access keys, and comprising of the idea of queues for work commands and data. The initial exchange of RDMA operations are managed through so-called InfiniBand-verbs, which describe the form of memory transaction between the remote nodes. We have to separate between *one-sided operations* that initiate data transfers over the network without a previous request for consent, and *two-sided operations* where every data transaction is preceeded by an additional exchange between the communicating nodes. Coyote implements the two one-sided RDMA operations: -- ```RDMA WRITE```: One node writes data of certain length to a remote buffer in the other node. Every sent WRITE packet has to be explicitly acked by the receiving node. -- ```RDMA READ```: One node requests to read data of certain length from a remote buffer in the other node. The READ REQUEST packet is implicitly ACK'ed when the remote node sends back RDMA RESPONSE packets with the requested data. - -**IMPORTANT TERMINOLOGY:** -To have a better understanding of this example and the following descriptions, a short overview of the RDMA-specific terminology is very helpful: -- *QPN*: Queue Pair Number. Identifies a RDMA connection between two remote nodes. -- *vaddr*: Virtual address of the remote exposed buffer. For setting up a Queue Pair, a node has to share the virtual address of its exposed buffer with the remote node. -- *rkey*: Remote key for accessing the exposed buffer of the remote node. The remote key is communicated alongside the virtual address as a form of access control. -- *PSN*: Packet Sequence Number used to organize and structure a communication flow between remote RDMA-nodes. Normally, PSN-sequencing is handled by the FPGA-offloaded network stack and thus is not visible to the user. However, following the PSN order is very helpful for debugging RDMA networks. The initial PSN is agreed upon between the remote nodes as part of the initialization of the QP. -- *MTU*: Maximum Transmission Unit, defines the maximum size of a packet. Since RDMA operates on the notion of buffer transmission, the length of the data to be transmitted determines the number of MTU-sized packets that are required for the operation. While the MTU is a compile-time parameter in Coyote, the default size is 4 KB. This means that a ```RDMA WRITE``` of a buffer of up to 4 KB size results in a single packet with ```RDMA WRITE ONLY``` opcode, while a buffer of 8 KB size would be transmitted in two packets, one carrying ```RDMA WRITE FIRST```, the other with ```RDMA WRITE LAST```. For even longer buffers, an arbitrary number of ```RDMA WRITE MIDDLE``` packets is inserted in the exchange. - -## Example Overview -This example measures throughput and latency of RDMA data exchange between two remode nodes via a 100G switched network with both ```RDMA WRITE``` and ```RDMA READ``` operations. Assuming the simpler write functionality, we can very generally think about the process as moving of a data buffer from the host CPU to the local FPGA, where it is streamed to the FPGA-offloaded RDMA stack and then sent via the network to the remote FPGA. There, data is received and then written to the previously specified address of the remote buffer of this remote host CPU. Different to all previous examples, this requires two servers with FPGAs, which also run different software application. The active node, which initiates communication exchange, is referred to as the "client", while the remote node is used as "server". An example of the dataflow is given in the figure below; and as shown in the figure, the steps are the following: -1) *QP Exchange*: As explained above, the RDMA connection in form of a Queue Pair needs to be set up between the remote nodes before the actual benchmarking test can begin. Since RDMA cannot be bootstrapped, this requires out-of-band communication between the two remote CPUs via classic TCP sockets. In the context of the HACC, we are using the 10G management network for this purpose. Using ```ifconfig```, the IP address of the CPU determined to be the server can be evaluated and then given to the experiment code as a parameter. The two nodes follow a standardized protocol to exchange relevant start-up information, such as the IP- addresses of the FPGAs in the 100G data network, the initial PSN and the address and access key of the network exposed data buffers. -2) *QP Setup*: After having exchanged these essential pieces of information, both CPUs forward the aggregated QP data to the FPGAs to create the endpoints of the RDMA flow in the hardware-offloaded network stack. This means making information such as the target IP address or buffer virtual address retrievable by the QPN as essential key. -3) *REMOTE WRITE*: In case of a WRITE-based benchmark, the client now begins with a ```REMOTE WRITE``` Coyote operation to transmit a local buffer via the network to the remote node. For this, both data and commands traverse the RDMA-allocated vFGPA and reach the RDMA stack on the FPGA, where RoCE v2 packets are created and then sent out via the network. -4) *Receiving data*: In the above example, the server FPGA receives the incoming RDMA packets and automatically checks for correctness of the PSN-sequence and QP-specific information. If the received packet fits into the expected communication flow, it gets ACK'ed: an ```RDMA ACK``` is sent back to the client as a reply. At the same time, the server FPGA sends the received data as a ```LOCAL WRITE``` (see Example 1) to the local buffer on the CPU. -5) *Processing ACKs & creating completions*: The client FPGA is constantly listening to the network for ACKs for its outstanding packets. If an ACK is received, the original work command for this transaction is marked as completed, which is also communicated to the local client CPU. If no ACK is received within a certain time interval after sending out a packet, a retransmission is issued. In this case, the same packet is sent again, assuming that it was originally lost in the network or not properly received by the server. - -
- -
- -Generally speaking, throughput and latency tests behave vastly different, also depending on the chosen mode of operation (WRITE vs. READ). The different cases are depicted in the figure below and can be understood as following: -- *Latency* for ```RDMA WRITE```: The client issues a single WRITE of a buffer of specified length to the remote server. Upon reception, the server ACKs and then writes back this very buffer to the client, thus creating a typical "ping-pong pattern" of communication, for which the two-way latency can be measured from sending out a buffer to receiving it back. Depending on the specified experimental arguments, this exchange is repeated for a certain number of times before the average latency of all transmissions is reported. -- *Throughput* for ```RDMA WRITE```: The client issues *n* WRITEs (depending on specification of the argument in experiment execution) of a buffer of specified length to the remote server and waits for all required ACKs. Upon reception, the server writes back the same buffer *n* times again. -- *Latency* for ```RDMA READ```: The client issues a single READ of a buffer of specified length from the remote server. Instead of ACK'ing the server sends the requested data via ```RDMA READ RESPONSEs```. -- *Throughput* for ```RDMA READ``` In this case, the client issues *n* READs (depending on specification of the argument in experiment execution) of a buffer of specified length from the remote server and waits for data delivery via ```RDMA READ RESPONSEs``` from there. The server in this case does not issue reflective READs to the client. - -
- -
- -**IMPORTANT:** When executing RDMA benchmarks, it's important to always start the server's software first before doing the same on the client. The reason for this lies in the intrinsics of the QP exchange: The server software is constructed to listening for incoming TCP connections from the client to then take the passive role in the off-channel exchange of information. - - -## Hardware Concepts -The core complexity for RDMA in Coyote is hidden from the user within the network stack. The RDMA-dedicated vFPGA is mainly used for connecting interfaces without any additional user logic required. -The previously introduced send- and receive-queues are connected as following in the vFPGA: -```Verilog -always_comb begin - /* - * CONTROL SIGNALS - * - * rq_(wr|rd) are two more Coyote interfaces, which act as inputs to the user application - * They corresponds to network write/read requests, set from the host software and driver - * Here, they are used to set Coyote's generic send queues, previously discussed in Example 7. - */ - // Write - sq_wr.valid = rq_wr.valid; - rq_wr.ready = sq_wr.ready; - sq_wr.data = rq_wr.data; // Data field holds information such as remote, virtual address, buffer length etc. - sq_wr.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host - sq_wr.data.dest = is_opcode_rd_resp(rq_wr.data.opcode) ? 0 : 1; - - // Reads - sq_rd.valid = rq_rd.valid; - rq_rd.ready = sq_rd.ready; - sq_rd.data = rq_rd.data; // Data field holds information such as remote, virtual address, buffer length etc. - sq_rd.data.strm = STRM_HOST; // For RDMA, by definition data is always on the host - sq_rd.data.dest = 1; -end -``` - -On the other hand, the data interfaces are connected as following in the module: - -```Verilog -/* - * DATA SIGNALS - * - */ -// Data streams for outgoing RDMA WRITEs (from local host to network stack to remote node) -`AXISR_ASSIGN(axis_host_recv[0], axis_rreq_send[0]) - -// Data streams for incoming RDMA READ RESPONSEs (from remote node to network stack to local host) -`AXISR_ASSIGN(axis_rreq_recv[0], axis_host_send[0]) - -// Data streams for outgoing RDMA READ RESPONSEs (from local host to network stack to remote node) -`AXISR_ASSIGN(axis_host_recv[1], axis_rrsp_send[0]) - -// Data streams for incoming RDMA WRITEs (from remote node to network stack to local host) -`AXISR_ASSIGN(axis_rrsp_recv[0], axis_host_send[1]) -``` - -Thinking one step further, beyond the scope of this performance benchmark, it becomes obvious how any pipelined user logic can be placed on these interfaces to process incoming or outgoing RDMA traffic. Placing customized user logic directly on these datapaths is one of the key benefits of a FPGA-based SmartNIC such as Coyote with the network configuration. - - -## Software Concepts -As described above, the main notion of logic abstraction in RDMA is the Queue-Pair (QP), which connects two remote nodes. Both nodes first form their own local Queue, which again is directly linked to the memory buffer that is exposed via the network. Afterwards, an off-channel exchange via TCP/IP is started to exchange the local queues and then form a QP. - -In Coyote, the notion of a QP is directly linked to a ``coyoteThread``. Thus, the first step of setting up RDMA in Coyote is creating such a thread: -```C++ -coyote::cThread coyote_thread(DEFAULT_VFPGA_ID, getpid(), 0); -```` - -Following the creation of the thread, it's necessary to call the function ```initRDMA()```, which allocates an RDMA buffer of the requested size and performs the QP exchange between the client and server. Importantly, the client needs to pass the server's IP address, while the server code leaves this field blank (nullptr, indicating that it's the server). The function return a pointer to the memory buffer to be used for RDMA communication. -```C++ -int *mem = (int *) coyote_thread.initRDMA(max_size, coyote::defPort, server_ip.c_str()); -``` - -After the setup is completed, we follow the standard approach in Coyote: creating a scatter-gather entry and issuing an ``ìnvoke(...)``` call, but this time indicating a remote operation: -```C++ -coyote::sgEntry sg; -sg.rdma = { .len = curr_size }; -coyote_thread.invoke(coyote::CoyoteOper::REMOTE_RDMA_WRITE, &sg); -``` - - -## Additional Information - -### Special remarks on building Coyote for RDMA experiments -Since different software versions are required for server and client to run a benchmark, two cmake builds have to be triggered to obtain the correct executables. In order to build the server code, one needs to specify `-DINSTANCE=server`, while a build of the client software is specified with `-DINSTANCE=client`: -```bash -cd sw/ - -mkdir build_server && cd build_server -cmake ../../ -DINSTANCE=server && make - -cd ../ -mkdir build_client && cd build_client -cmake ../../ -DINSTANCE=client && make -``` - -### Command line parameters and hints on running the experiment -As said above, it's crucial to start the software for experiments first on the node that we want to use as server, before doing the same for the client. Furthermore, it's important that the IP address specified as argument on the client machine **belongs to the server-CPU (not the client CPU, not the server FPGA)**. The different available network interfaces can be explored with ```ifconfig```. -The following description helps to match the relevant command line parameters to details of the experiment execution described before: - -- `[--ip_address | -i] ` IP address of the server CPU for out-of-band QP-exchange via TCP-sockets before the actual RDMA experiment can begin. -- `[--operation | -o] ` Decides whether the benchmark is performed for WRITE (1) or READ (0) operations. Default: 0 -- `[--min_size | -x] ` Minimum size of transferred buffer in the experiment. Default: 64 [B] -- `[--max_size | -X] ` Maximum size of transferred buffer in the experiment. Default: 1048576 [B] ~ 1 [MB] -- `[--runs | -r] ` Number of test runs, to obtain statistically significant results For latency-tests, `r` ping-pong exchanges will be executed. For throughput tests, `r` independent exchanges of 64 messages are executed. - -### Network debugging -Coyote provides tooling to check the status of network transmissions and identify potential problems, most notable packet losses and retransmissions. These statistics can be queried via -`cat /sys/kernel/coyote_sysfs_0/cyt_attr_nstats`. - -A typical output for this could look like this: -``` - -- NET STATS QSFP0 - -RX pkgs: 316 -TX pkgs: 242 -ARP RX pkgs: 4 -ARP TX pkgs: 2 -ICMP RX pkgs: 0 -ICMP TX pkgs: 0 -TCP RX pkgs: 0 -TCP TX pkgs: 0 -ROCE RX pkgs: 245 -ROCE TX pkgs: 240 -IBV RX pkgs: 240 -IBV TX pkgs: 240 -PSN drop cnt: 0 -Retrans cnt: 0 -TCP session cnt: 0 -STRM down: 0 -``` -In this case, 240 RDMA packets have been received and sent (``IBV RX/TX``), no packet has been lost or retransmitted. A major problem would be indicated by ``STRM down: 1``, as this would signal a complete shutdown of the networking stack. In such a case, only a hard reset including reprogramming the FPGA can save the user. -Apart from these Coyote-provided utilities, standard network debugging tools such as switch- or external NIC-based traffic capturing can be used to understand traffic issues and patterns. As described in the next example, we also offer a 100G-capable traffic sniffer for Coyote that directly produces pcap-files compatible to any standard network analysis tool such as wireshark. If even this low-level debugging capabilities are not deemed satisfactory, also classic ILA-based probing directly on data- and controlpaths of the FPGA-design is possible. - -### Expeted results -The following results are expected for reads (left) and writes (right): - -

- - -